Skip to content

Testing

import * as Harness from '@dxos/plugin-testing/Harness';

A plugin’s modules run in a plugin manager, so a test starts one. Harness.createComposerTestApp starts a headless app with the core plugins Composer gives every plugin, plus the plugins under test. It runs on an in-memory client, so it needs no browser, account or server.

import * as
import Harness
Harness
from '@dxos/plugin-testing/Harness';
import {
const make: PluginFactory<void>
make
as
const makePlugin: PluginFactory<void>
makePlugin
} from './dx.config.ts';
// A headless app with the core plugins Composer gives a plugin, plus this one.
await using
await using harness: TestHarness
harness
= await
import Harness
Harness
.
const createComposerTestApp: (opts?: Harness.ComposerTestAppOptions) => Promise<TestHarness>

Creates a TestHarness pre-loaded with the Composer core plugins (Attention, Graph, ProcessManager, Settings, optionally Theme).

For a ClientPlugin-backed harness, pass ClientPlugin.make({ ... }) via plugins.

@idiom ― org.dxos.plugin-testing.pluginModuleActivation applies: Writing a basic activation smoke-test for any Composer plugin instead-of: Manually inspecting PluginManager internals or relying on e2e tests for activation checks uses: createComposerTestApp, AppActivationEvents related: org.dxos.app-framework.testing.operationCapture

createComposerTestApp
({
plugins?: Plugin[] | undefined

Plugins to register in addition to the Composer core plugins.

plugins
: [
function makePlugin(options: void): Plugin
makePlugin
()],
});

await using closes the app when the test ends, which deactivates every module and runs its finalizers.

The harness fires the startup events. A module gated on a later event stays inactive until the test fires that event, so a test can check what a module does before and after it activates.

import * as
import ActivationEvents
ActivationEvents
from '@dxos/app-framework/ActivationEvents';
import * as
import AppCapabilities
AppCapabilities
from '@dxos/app-toolkit/AppCapabilities';
// Startup has fired; modules gated on later events wait until the test fires them.
await
await using harness: TestHarness
harness
.
TestHarness.fire(event: ActivationEvent | string): Promise<boolean>

Activate the given event. Equivalent to manager.activate(event).

fire
(
import ActivationEvents
ActivationEvents
.
const Idle: ActivationEvent

Fired once by the host at main-thread idle after the app is interactive.

For REGISTRATION contributions only — graph builders, operation handler sets, settings: the app-wide indexes a user can reach from anywhere (the navtree, the operation registry, a settings read) and which therefore no single surface can gate. A feature's own modules ride its plugin start event, fired when its surface renders; putting a feature body here instead is what turned the former DeferredStartup into a second startup pass.

This is also what omitting activatesOn normalizes to, so a module that must run at boot has to say activatesOn: ActivationEvents.Startup explicitly. Defined in core; see

ActivationEvent$.Idle

.

Idle
);
const
const builders: BuilderExtensions[]
builders
=
await using harness: TestHarness
harness
.
TestHarness.getAll<BuilderExtensions>(iface: InterfaceDef<BuilderExtensions>): BuilderExtensions[]

Returns all contributed capabilities for the given interface.

getAll
(
import AppCapabilities
AppCapabilities
.
const AppGraphBuilder: MultiTag<BuilderExtensions, "org.dxos.app-framework.capability.appGraphBuilder">

@category ― Capability

AppGraphBuilder
);
var console: Console

The console module provides a simple debugging console that is similar to the JavaScript console mechanism provided by web browsers.

The module exports two specific components:

  • A Console class with methods such as console.log(), console.error() and console.warn() that can be used to write to any Node.js stream.
  • A global console instance configured to write to process.stdout and process.stderr. The global console can be used without importing the node:console module.

Warning: The global console object's methods are neither consistently synchronous like the browser APIs they resemble, nor are they consistently asynchronous like all other Node.js streams. See the note on process I/O for more information.

Example using the global console:

console.log('hello world');
// Prints: hello world, to stdout
console.log('hello %s', 'world');
// Prints: hello world, to stdout
console.error(new Error('Whoops, something bad happened'));
// Prints error message and stack trace to stderr:
// Error: Whoops, something bad happened
// at [eval]:5:15
// at Script.runInThisContext (node:vm:132:18)
// at Object.runInThisContext (node:vm:309:38)
// at node:internal/process/execution:77:19
// at [eval]-wrapper:6:22
// at evalScript (node:internal/process/execution:76:60)
// at node:internal/main/eval_string:23:3
const name = 'Will Robinson';
console.warn(`Danger ${name}! Danger!`);
// Prints: Danger Will Robinson! Danger!, to stderr

Example using the Console class:

const out = getStreamSomehow();
const err = getStreamSomehow();
const myConsole = new console.Console(out, err);
myConsole.log('hello world');
// Prints: hello world, to out
myConsole.log('hello %s', 'world');
// Prints: hello world, to out
myConsole.error(new Error('Whoops, something bad happened'));
// Prints: [Error: Whoops, something bad happened], to err
const name = 'Will Robinson';
myConsole.warn(`Danger ${name}! Danger!`);
// Prints: Danger Will Robinson! Danger!, to err

@see ― source

console
.
Console.log(message?: any, ...optionalParams: any[]): void (+1 overload)

Prints to stdout with newline. Multiple arguments can be passed, with the first used as the primary message and all additional used as substitution values similar to printf(3) (the arguments are all passed to util.format()).

const count = 5;
console.log('count: %d', count);
// Prints: count: 5, to stdout
console.log('count:', count);
// Prints: count: 5, to stdout

See util.format() for more information.

@since ― v0.1.100

log
(
const builders: BuilderExtensions[]
builders
.
Array<BuilderExtensions>.length: number

Gets or sets the length of the array. This is a number one higher than the highest index in the array.

length
> 0); // true

getAll returns every contribution to a capability, as Capability.getAll does inside a module. waitForCapability waits for a capability whose module has not activated yet.

import * as
import Capabilities
Capabilities
from '@dxos/app-framework/Capabilities';
import * as
import AppCapabilities
AppCapabilities
from '@dxos/app-toolkit/AppCapabilities';
const
const schemas: AnyEntity[]
schemas
=
await using harness: TestHarness
harness
.
TestHarness.getAll<AppCapabilities.Schema>(iface: InterfaceDef<AppCapabilities.Schema>): AppCapabilities.Schema[]

Returns all contributed capabilities for the given interface.

getAll
(
import AppCapabilities
AppCapabilities
.
const Schema: MultiTag<AppCapabilities.Schema, "org.dxos.app-framework.capability.schema">

@category ― Capability

Schema
).
Array<Schema>.flat<AppCapabilities.Schema[], 1>(this: AppCapabilities.Schema[], depth?: 1 | undefined): AnyEntity[]

Returns a new array with all sub-array elements concatenated into it recursively up to the specified depth.

@param ― depth The maximum recursion depth

flat
();
const
const handlers: OperationHandlerSet
handlers
= await
await using harness: TestHarness
harness
.
TestHarness.waitForCapability<OperationHandlerSet>(iface: InterfaceDef<OperationHandlerSet>, opts?: {
timeout?: number;
}): Promise<OperationHandlerSet>

Waits until at least one capability is contributed for the given interface.

waitForCapability
(
import Capabilities
Capabilities
.
const OperationHandler: MultiTag<OperationHandlerSet, "org.dxos.app-framework.capability.operationHandler">
OperationHandler
);
var console: Console

The console module provides a simple debugging console that is similar to the JavaScript console mechanism provided by web browsers.

The module exports two specific components:

  • A Console class with methods such as console.log(), console.error() and console.warn() that can be used to write to any Node.js stream.
  • A global console instance configured to write to process.stdout and process.stderr. The global console can be used without importing the node:console module.

Warning: The global console object's methods are neither consistently synchronous like the browser APIs they resemble, nor are they consistently asynchronous like all other Node.js streams. See the note on process I/O for more information.

Example using the global console:

console.log('hello world');
// Prints: hello world, to stdout
console.log('hello %s', 'world');
// Prints: hello world, to stdout
console.error(new Error('Whoops, something bad happened'));
// Prints error message and stack trace to stderr:
// Error: Whoops, something bad happened
// at [eval]:5:15
// at Script.runInThisContext (node:vm:132:18)
// at Object.runInThisContext (node:vm:309:38)
// at node:internal/process/execution:77:19
// at [eval]-wrapper:6:22
// at evalScript (node:internal/process/execution:76:60)
// at node:internal/main/eval_string:23:3
const name = 'Will Robinson';
console.warn(`Danger ${name}! Danger!`);
// Prints: Danger Will Robinson! Danger!, to stderr

Example using the Console class:

const out = getStreamSomehow();
const err = getStreamSomehow();
const myConsole = new console.Console(out, err);
myConsole.log('hello world');
// Prints: hello world, to out
myConsole.log('hello %s', 'world');
// Prints: hello world, to out
myConsole.error(new Error('Whoops, something bad happened'));
// Prints: [Error: Whoops, something bad happened], to err
const name = 'Will Robinson';
myConsole.warn(`Danger ${name}! Danger!`);
// Prints: Danger Will Robinson! Danger!, to err

@see ― source

console
.
Console.log(message?: any, ...optionalParams: any[]): void (+1 overload)

Prints to stdout with newline. Multiple arguments can be passed, with the first used as the primary message and all additional used as substitution values similar to printf(3) (the arguments are all passed to util.format()).

const count = 5;
console.log('count: %d', count);
// Prints: count: 5, to stdout
console.log('count:', count);
// Prints: count: 5, to stdout

See util.format() for more information.

@since ― v0.1.100

log
(
const schemas: AnyEntity[]
schemas
.
Array<AnyEntity>.length: number

Gets or sets the length of the array. This is a number one higher than the highest index in the array.

length
, typeof
const handlers: OperationHandlerSet
handlers
);

invoke runs an operation through the app’s invoker, with the handlers the plugins contributed, and returns its output. A typed error fails the promise.

import {
import Ref
Ref
} from '@dxos/echo';
import {
const make: (props?: Partial<MakeProps<typeof StashedLink>>) => StashedLink
make
} from './src/types/StashedLink.ts';
import {
const MarkRead: Definition<{
readonly link: Ref<StashedLink & OfKind<EntityKind.Object> & Unknown>;
}, void, Service>
MarkRead
} from './src/types/StashedLinkOperation.ts';
const
const link: StashedLink
link
=
function make(props?: Partial<MakeProps<typeof StashedLink>>): StashedLink
make
({
title?: string | undefined
title
: 'DXOS',
url?: string | undefined
url
: 'https://dxos.org' });
await
await using harness: TestHarness
harness
.
TestHarness.invoke<{
readonly link: Ref<StashedLink & OfKind<EntityKind.Object> & Unknown>;
}, void>(op: Definition<{
readonly link: Ref<StashedLink & OfKind<EntityKind.Object> & Unknown>;
}, void, any>, input: {
readonly link: Ref<StashedLink & OfKind<EntityKind.Object> & Unknown>;
}): Promise<void>

Invokes an operation through the Capabilities.OperationInvoker capability.

invoke
(
const MarkRead: Definition<{
readonly link: Ref<StashedLink & OfKind<EntityKind.Object> & Unknown>;
}, void, Service>
MarkRead
, {
link: Ref<StashedLink & OfKind<EntityKind.Object> & Unknown>
link
:
import Ref
Ref
.
const make: <StashedLink>(object: StashedLink) => Ref<StashedLink>

Create a reference to an entity.

@performance ― O(1); allocates a ref that inlines the target, with no lookup.

make
(
const link: StashedLink
link
) });
var console: Console

The console module provides a simple debugging console that is similar to the JavaScript console mechanism provided by web browsers.

The module exports two specific components:

  • A Console class with methods such as console.log(), console.error() and console.warn() that can be used to write to any Node.js stream.
  • A global console instance configured to write to process.stdout and process.stderr. The global console can be used without importing the node:console module.

Warning: The global console object's methods are neither consistently synchronous like the browser APIs they resemble, nor are they consistently asynchronous like all other Node.js streams. See the note on process I/O for more information.

Example using the global console:

console.log('hello world');
// Prints: hello world, to stdout
console.log('hello %s', 'world');
// Prints: hello world, to stdout
console.error(new Error('Whoops, something bad happened'));
// Prints error message and stack trace to stderr:
// Error: Whoops, something bad happened
// at [eval]:5:15
// at Script.runInThisContext (node:vm:132:18)
// at Object.runInThisContext (node:vm:309:38)
// at node:internal/process/execution:77:19
// at [eval]-wrapper:6:22
// at evalScript (node:internal/process/execution:76:60)
// at node:internal/main/eval_string:23:3
const name = 'Will Robinson';
console.warn(`Danger ${name}! Danger!`);
// Prints: Danger Will Robinson! Danger!, to stderr

Example using the Console class:

const out = getStreamSomehow();
const err = getStreamSomehow();
const myConsole = new console.Console(out, err);
myConsole.log('hello world');
// Prints: hello world, to out
myConsole.log('hello %s', 'world');
// Prints: hello world, to out
myConsole.error(new Error('Whoops, something bad happened'));
// Prints: [Error: Whoops, something bad happened], to err
const name = 'Will Robinson';
myConsole.warn(`Danger ${name}! Danger!`);
// Prints: Danger Will Robinson! Danger!, to err

@see ― source

console
.
Console.log(message?: any, ...optionalParams: any[]): void (+1 overload)

Prints to stdout with newline. Multiple arguments can be passed, with the first used as the primary message and all additional used as substitution values similar to printf(3) (the arguments are all passed to util.format()).

const count = 5;
console.log('count: %d', count);
// Prints: count: 5, to stdout
console.log('count:', count);
// Prints: count: 5, to stdout

See util.format() for more information.

@since ― v0.1.100

log
(
const link: StashedLink
link
.
read?: boolean | undefined
read
);
  • The harness renders no UI. Test a surface’s component on its own, with a story or a component test.
  • Plugins that need a real network or device, such as a connector, need their own test layers.