Перейти к содержимому
GitHubXDiscord

createTestRuntime

Стабильный · с 0.1.0

Конструирует Runtime, расширенный тремя тестовыми хелперами — mockCondition, mockAction и flushMicrotasks. Стандартная точка входа для написания тестов триггеров без React: каждый тест получает свой рантайм, каждое условие и действие моделируются явно, очередь микротасков сливается по запросу.

Базовый рантайм принимает те же RuntimeOptions, что и createRuntime.

import { createTestRuntime } from '@triggery/testing';
function createTestRuntime(options?: TestRuntimeOptions): TestRuntime;

type TestRuntimeOptions = RuntimeOptions;

type TestRuntime = Runtime & {
  mockCondition<S, K>(
    trigger: Trigger<S>,
    name: K,
    valueOrGetter: ConditionMap<S>[K] | (() => ConditionMap<S>[K]),
  ): RegistrationToken;

  mockAction<S, K>(
    trigger: Trigger<S>,
    name: K,
    handler: ActionFn<ActionMap<S>[K]>,
  ): RegistrationToken;

  flushMicrotasks(): Promise<void>;
};
ПараметрТипПо умолчаниюОписание
optionsRuntimeOptions{}Те же опции, что и у createRuntime.

TestRuntimeRuntime плюс три хелпера. См. mockCondition, mockAction, flushMicrotasks.

import { createTriggerfunction createTrigger<S extends TriggerSchema>(config: CreateTriggerConfig<S>, runtime?: Runtime): Trigger<S>
Create a trigger and register it in a runtime (the default runtime if none is passed).
@example```ts const messageTrigger = createTrigger<{ events: { 'new-message': { author: string; text: string } }; conditions: { user: { id: string }; settings: { sound: boolean } }; actions: { showToast: { title: string }; playSound: void }; }>({ id: 'message-received', events: ['new-message'], required: ['user', 'settings'], handler({ event, conditions, actions, check }) { if (!conditions.user || !conditions.settings) return; // V1: manual narrowing if (check.is('settings', (s) => s.sound)) actions.playSound?.(); actions.showToast?.({ title: event.payload.author }); }, }); ```
} from '@triggery/core';
import { createTestRuntimefunction createTestRuntime(options?: TestRuntimeOptions): TestRuntime } from '@triggery/testing'; import { describeconst describe: SuiteAPI
Creates a suite of tests, allowing for grouping and hierarchical organization of tests. Suites can contain both tests and other suites, enabling complex test structures.
@paramname - The name of the suite, used for identification and reporting.@paramfn - A function that defines the tests and suites within this suite.@example```ts // Define a suite with two tests describe('Math operations', () => { test('should add two numbers', () => { expect(add(1, 2)).toBe(3); }); test('should subtract two numbers', () => { expect(subtract(5, 2)).toBe(3); }); }); ```@example```ts // Define nested suites describe('String operations', () => { describe('Trimming', () => { test('should trim whitespace from start and end', () => { expect(' hello '.trim()).toBe('hello'); }); }); describe('Concatenation', () => { test('should concatenate two strings', () => { expect('hello' + ' ' + 'world').toBe('hello world'); }); }); }); ```
, expectconst expect: ExpectStatic, itconst it: TestAPI
Defines a test case with a given name and test function. The test function can optionally be configured with test options.
@paramname - The name of the test or a function that will be used as a test name.@paramoptionsOrFn - Optional. The test options or the test function if no explicit name is provided.@paramoptionsOrTest - Optional. The test function or options, depending on the previous parameters.@throws{Error} If called inside another test function.@example```ts // Define a simple test it('adds two numbers', () => { expect(add(1, 2)).toBe(3); }); ```@example```ts // Define a test with options it('subtracts two numbers', { retry: 3 }, () => { expect(subtract(5, 2)).toBe(3); }); ```
, viconst vi: VitestUtils } from 'vitest';
describedescribe<object>(name: string | Function, fn?: SuiteFactory<object> | undefined, options?: number): SuiteCollector<object> (+1 overload)
Creates a suite of tests, allowing for grouping and hierarchical organization of tests. Suites can contain both tests and other suites, enabling complex test structures.
@paramname - The name of the suite, used for identification and reporting.@paramfn - A function that defines the tests and suites within this suite.@example```ts // Define a suite with two tests describe('Math operations', () => { test('should add two numbers', () => { expect(add(1, 2)).toBe(3); }); test('should subtract two numbers', () => { expect(subtract(5, 2)).toBe(3); }); }); ```@example```ts // Define nested suites describe('String operations', () => { describe('Trimming', () => { test('should trim whitespace from start and end', () => { expect(' hello '.trim()).toBe('hello'); }); }); describe('Concatenation', () => { test('should concatenate two strings', () => { expect('hello' + ' ' + 'world').toBe('hello world'); }); }); }); ```
('demo trigger', () => {
itit<object>(name: string | Function, fn?: TestFunction<object> | undefined, options?: number): void (+1 overload)
Defines a test case with a given name and test function. The test function can optionally be configured with test options.
@paramname - The name of the test or a function that will be used as a test name.@paramoptionsOrFn - Optional. The test options or the test function if no explicit name is provided.@paramoptionsOrTest - Optional. The test function or options, depending on the previous parameters.@throws{Error} If called inside another test function.@example```ts // Define a simple test it('adds two numbers', () => { expect(add(1, 2)).toBe(3); }); ```@example```ts // Define a test with options it('subtracts two numbers', { retry: 3 }, () => { expect(subtract(5, 2)).toBe(3); }); ```
('fires the action when the condition matches', () => {
const rtconst rt: TestRuntime = createTestRuntimefunction createTestRuntime(options?: TestRuntimeOptions): TestRuntime(); const t
const t: Trigger<{
    events: {
        tick: number;
    };
    conditions: {
        enabled: boolean;
    };
    actions: {
        log: number;
    };
}>
= createTrigger
createTrigger<{
    events: {
        tick: number;
    };
    conditions: {
        enabled: boolean;
    };
    actions: {
        log: number;
    };
}>(config: CreateTriggerConfig<{
    events: {
        tick: number;
    };
    conditions: {
        enabled: boolean;
    };
    actions: {
        log: number;
    };
}>, runtime?: Runtime): Trigger<{
    events: {
        tick: number;
    };
    conditions: {
        enabled: boolean;
    };
    actions: {
        log: number;
    };
}>
Create a trigger and register it in a runtime (the default runtime if none is passed).
@example```ts const messageTrigger = createTrigger<{ events: { 'new-message': { author: string; text: string } }; conditions: { user: { id: string }; settings: { sound: boolean } }; actions: { showToast: { title: string }; playSound: void }; }>({ id: 'message-received', events: ['new-message'], required: ['user', 'settings'], handler({ event, conditions, actions, check }) { if (!conditions.user || !conditions.settings) return; // V1: manual narrowing if (check.is('settings', (s) => s.sound)) actions.playSound?.(); actions.showToast?.({ title: event.payload.author }); }, }); ```
<{
events
events: {
    tick: number;
}
: { ticktick: number: number };
conditions
conditions: {
    enabled: boolean;
}
: { enabledenabled: boolean: boolean };
actions
actions: {
    log: number;
}
: { loglog: number: number };
}>( { idid: string: 'demo', eventsevents: readonly "tick"[]: ['tick'], requiredrequired?: readonly "enabled"[] | undefined
Required condition keys. The trigger will not run unless all of them are registered.
: ['enabled'],
handler
handler: TriggerHandler<{
    events: {
        tick: number;
    };
    conditions: {
        enabled: boolean;
    };
    actions: {
        log: number;
    };
}, never>
({ event
event: {
    readonly name: "tick";
    readonly payload: number;
}
, conditions
conditions: ConditionsCtx<{
    enabled: boolean;
}, never>
, actions
actions: ActionsCtx<{
    log: number;
}>
}) {
if (!conditions
conditions: ConditionsCtx<{
    enabled: boolean;
}, never>
.enabledenabled?: boolean | undefined) return;
actions
actions: ActionsCtx<{
    log: number;
}>
.loglog?: ((payload: number) => void) | undefined?.(event
event: {
    readonly name: "tick";
    readonly payload: number;
}
.payloadpayload: number);
}, }, rtconst rt: TestRuntime, ); rtconst rt: TestRuntime.mockCondition
mockCondition<{
    events: {
        tick: number;
    };
    conditions: {
        enabled: boolean;
    };
    actions: {
        log: number;
    };
}, "enabled">(trigger: Trigger<{
    events: {
        tick: number;
    };
    conditions: {
        enabled: boolean;
    };
    actions: {
        log: number;
    };
}>, name: "enabled", valueOrGetter: boolean | (() => boolean)): RegistrationToken
Register a condition for a trigger. Accepts either a static value or a zero-argument getter. When the condition's value type is itself a zero-argument function, pass an explicit getter so the runtime knows which one you mean — otherwise the heuristic below would call your value as if it were the getter.
(t
const t: Trigger<{
    events: {
        tick: number;
    };
    conditions: {
        enabled: boolean;
    };
    actions: {
        log: number;
    };
}>
, 'enabled', true);
const logconst log: Mock<Procedure> = viconst vi: VitestUtils.fnVitestUtils.fn: <Procedure>(originalImplementation?: Procedure | undefined) => Mock<Procedure>
Creates a spy on a function, though can be initiated without one. Every time a function is invoked, it stores its call arguments, returns, and instances. Also, you can manipulate its behavior with [methods](https://vitest.dev/api/mock). If no function is given, mock will return `undefined`, when invoked.
@example```ts const getApples = vi.fn(() => 0) getApples() expect(getApples).toHaveBeenCalled() expect(getApples).toHaveReturnedWith(0) getApples.mockReturnValueOnce(5) expect(getApples()).toBe(5) expect(getApples).toHaveNthReturnedWith(2, 5) ```
();
rtconst rt: TestRuntime.mockAction
mockAction<{
    events: {
        tick: number;
    };
    conditions: {
        enabled: boolean;
    };
    actions: {
        log: number;
    };
}, "log">(trigger: Trigger<{
    events: {
        tick: number;
    };
    conditions: {
        enabled: boolean;
    };
    actions: {
        log: number;
    };
}>, name: "log", handler: (payload: number) => void): RegistrationToken
Register an action handler — typically a `vi.fn()`.
(t
const t: Trigger<{
    events: {
        tick: number;
    };
    conditions: {
        enabled: boolean;
    };
    actions: {
        log: number;
    };
}>
, 'log', logconst log: Mock<Procedure>);
rtconst rt: TestRuntime.fireSyncfunction fireSync(eventName: string, payload?: unknown): void
Run dispatch synchronously (for tests and benchmarks).
('tick', 42);
expectexpect<Mock<Procedure>>(actual: Mock<Procedure>, message?: string): Assertion<Mock<Procedure>> (+1 overload)(logconst log: Mock<Procedure>).toHaveBeenCalledWithJestAssertion<Mock<Procedure>>.toHaveBeenCalledWith: <[number]>(args_0: number) => void
Ensure that a mock function is called with specific arguments. Also under the alias `expect.toBeCalledWith`.
@exampleexpect(mockFunc).toHaveBeenCalledWith('arg1', 42);
(42);
}); });
import { createTriggerfunction createTrigger<S extends TriggerSchema>(config: CreateTriggerConfig<S>, runtime?: Runtime): Trigger<S>
Create a trigger and register it in a runtime (the default runtime if none is passed).
@example```ts const messageTrigger = createTrigger<{ events: { 'new-message': { author: string; text: string } }; conditions: { user: { id: string }; settings: { sound: boolean } }; actions: { showToast: { title: string }; playSound: void }; }>({ id: 'message-received', events: ['new-message'], required: ['user', 'settings'], handler({ event, conditions, actions, check }) { if (!conditions.user || !conditions.settings) return; // V1: manual narrowing if (check.is('settings', (s) => s.sound)) actions.playSound?.(); actions.showToast?.({ title: event.payload.author }); }, }); ```
} from '@triggery/core';
import { createTestRuntimefunction createTestRuntime(options?: TestRuntimeOptions): TestRuntime } from '@triggery/testing'; import { expectconst expect: ExpectStatic, itconst it: TestAPI
Defines a test case with a given name and test function. The test function can optionally be configured with test options.
@paramname - The name of the test or a function that will be used as a test name.@paramoptionsOrFn - Optional. The test options or the test function if no explicit name is provided.@paramoptionsOrTest - Optional. The test function or options, depending on the previous parameters.@throws{Error} If called inside another test function.@example```ts // Define a simple test it('adds two numbers', () => { expect(add(1, 2)).toBe(3); }); ```@example```ts // Define a test with options it('subtracts two numbers', { retry: 3 }, () => { expect(subtract(5, 2)).toBe(3); }); ```
, viconst vi: VitestUtils } from 'vitest';
itit<object>(name: string | Function, fn?: TestFunction<object> | undefined, options?: number): void (+1 overload)
Defines a test case with a given name and test function. The test function can optionally be configured with test options.
@paramname - The name of the test or a function that will be used as a test name.@paramoptionsOrFn - Optional. The test options or the test function if no explicit name is provided.@paramoptionsOrTest - Optional. The test function or options, depending on the previous parameters.@throws{Error} If called inside another test function.@example```ts // Define a simple test it('adds two numbers', () => { expect(add(1, 2)).toBe(3); }); ```@example```ts // Define a test with options it('subtracts two numbers', { retry: 3 }, () => { expect(subtract(5, 2)).toBe(3); }); ```
('drains microtasks before assertion', async () => {
const rtconst rt: TestRuntime = createTestRuntimefunction createTestRuntime(options?: TestRuntimeOptions): TestRuntime(); const t
const t: Trigger<{
    events: {
        tick: void;
    };
    actions: {
        log: void;
    };
}>
= createTrigger
createTrigger<{
    events: {
        tick: void;
    };
    actions: {
        log: void;
    };
}>(config: CreateTriggerConfig<{
    events: {
        tick: void;
    };
    actions: {
        log: void;
    };
}>, runtime?: Runtime): Trigger<{
    events: {
        tick: void;
    };
    actions: {
        log: void;
    };
}>
Create a trigger and register it in a runtime (the default runtime if none is passed).
@example```ts const messageTrigger = createTrigger<{ events: { 'new-message': { author: string; text: string } }; conditions: { user: { id: string }; settings: { sound: boolean } }; actions: { showToast: { title: string }; playSound: void }; }>({ id: 'message-received', events: ['new-message'], required: ['user', 'settings'], handler({ event, conditions, actions, check }) { if (!conditions.user || !conditions.settings) return; // V1: manual narrowing if (check.is('settings', (s) => s.sound)) actions.playSound?.(); actions.showToast?.({ title: event.payload.author }); }, }); ```
<{
events
events: {
    tick: void;
}
: { ticktick: void: void };
actions
actions: {
    log: void;
}
: { loglog: void: void };
}>( { idid: string: 'demo', eventsevents: readonly "tick"[]: ['tick'], handler
handler: TriggerHandler<{
    events: {
        tick: void;
    };
    actions: {
        log: void;
    };
}, never>
({ actions
actions: ActionsCtx<{
    log: void;
}>
}) {
actions
actions: ActionsCtx<{
    log: void;
}>
.loglog?: (() => void) | undefined?.();
}, }, rtconst rt: TestRuntime, ); const logconst log: Mock<Procedure> = viconst vi: VitestUtils.fnVitestUtils.fn: <Procedure>(originalImplementation?: Procedure | undefined) => Mock<Procedure>
Creates a spy on a function, though can be initiated without one. Every time a function is invoked, it stores its call arguments, returns, and instances. Also, you can manipulate its behavior with [methods](https://vitest.dev/api/mock). If no function is given, mock will return `undefined`, when invoked.
@example```ts const getApples = vi.fn(() => 0) getApples() expect(getApples).toHaveBeenCalled() expect(getApples).toHaveReturnedWith(0) getApples.mockReturnValueOnce(5) expect(getApples()).toBe(5) expect(getApples).toHaveNthReturnedWith(2, 5) ```
();
rtconst rt: TestRuntime.mockAction
mockAction<{
    events: {
        tick: void;
    };
    actions: {
        log: void;
    };
}, "log">(trigger: Trigger<{
    events: {
        tick: void;
    };
    actions: {
        log: void;
    };
}>, name: "log", handler: () => void): RegistrationToken
Register an action handler — typically a `vi.fn()`.
(t
const t: Trigger<{
    events: {
        tick: void;
    };
    actions: {
        log: void;
    };
}>
, 'log', logconst log: Mock<Procedure>);
rtconst rt: TestRuntime.firefunction fire(eventName: string, payload?: unknown): void
Fire an event asynchronously (through the scheduler).
('tick'); // microtask scheduler — handler not yet ran
await rtconst rt: TestRuntime.flushMicrotasksfunction flushMicrotasks(): Promise<void>
Flush pending microtasks. The default scheduler uses `queueMicrotask` — after `rt.fire(...)` you await `flushMicrotasks()` before asserting. `fireSync` does not need this: it runs handlers immediately.
();
expectexpect<Mock<Procedure>>(actual: Mock<Procedure>, message?: string): Assertion<Mock<Procedure>> (+1 overload)(logconst log: Mock<Procedure>).toHaveBeenCalledTimesJestAssertion<Mock<Procedure>>.toHaveBeenCalledTimes: (times: number) => void
Ensures that a mock function is called an exact number of times. Also under the alias `expect.toBeCalledTimes`.
@exampleexpect(mockFunc).toHaveBeenCalledTimes(2);
(1);
});
import { createTriggerfunction createTrigger<S extends TriggerSchema>(config: CreateTriggerConfig<S>, runtime?: Runtime): Trigger<S>
Create a trigger and register it in a runtime (the default runtime if none is passed).
@example```ts const messageTrigger = createTrigger<{ events: { 'new-message': { author: string; text: string } }; conditions: { user: { id: string }; settings: { sound: boolean } }; actions: { showToast: { title: string }; playSound: void }; }>({ id: 'message-received', events: ['new-message'], required: ['user', 'settings'], handler({ event, conditions, actions, check }) { if (!conditions.user || !conditions.settings) return; // V1: manual narrowing if (check.is('settings', (s) => s.sound)) actions.playSound?.(); actions.showToast?.({ title: event.payload.author }); }, }); ```
} from '@triggery/core';
import { createTestRuntimefunction createTestRuntime(options?: TestRuntimeOptions): TestRuntime, type TestRuntime
type TestRuntime = Runtime & {
    mockCondition<S extends TriggerSchema, K extends ConditionKey<S>>(trigger: Trigger<S>, name: K, valueOrGetter: ConditionMap<S>[K] | (() => ConditionMap<S>[K])): RegistrationToken;
    mockAction<S extends TriggerSchema, K extends ActionKey<S>>(trigger: Trigger<S>, name: K, handler: ActionFn<ActionMap<S>[K]>): RegistrationToken;
    flushMicrotasks(): Promise<void>;
}
} from '@triggery/testing';
import { afterEachfunction afterEach<ExtraContext = object>(fn: AfterEachListener<ExtraContext>, timeout?: number): void
Registers a callback function to be executed after each test within the current suite has completed. This hook is useful for scenarios where you need to clean up or reset the test environment after each test runs, such as deleting temporary files, clearing test-specific database entries, or resetting mocked functions. **Note:** The `afterEach` hooks are running in reverse order of their registration. You can configure this by changing the `sequence.hooks` option in the config file.
@paramfn - The callback function to be executed after each test. This function receives an `TestContext` parameter if additional test context is needed.@paramtimeout - Optional timeout in milliseconds for the hook. If not provided, the default hook timeout from the runner's configuration is used.@returns@example```ts // Example of using afterEach to delete temporary files created during a test afterEach(async () => { await fileSystem.deleteTempFiles(); }); ```
, beforeEachfunction beforeEach<ExtraContext = object>(fn: BeforeEachListener<ExtraContext>, timeout?: number): void
Registers a callback function to be executed before each test within the current suite. This hook is useful for scenarios where you need to reset or reinitialize the test environment before each test runs, such as resetting database states, clearing caches, or reinitializing variables. **Note:** The `beforeEach` hooks are executed in the order they are defined one after another. You can configure this by changing the `sequence.hooks` option in the config file.
@paramfn - The callback function to be executed before each test. This function receives an `TestContext` parameter if additional test context is needed.@paramtimeout - Optional timeout in milliseconds for the hook. If not provided, the default hook timeout from the runner's configuration is used.@returns@example```ts // Example of using beforeEach to reset a database state beforeEach(async () => { await database.reset(); }); ```
} from 'vitest';
let rtlet rt: TestRuntime: TestRuntime
type TestRuntime = Runtime & {
    mockCondition<S extends TriggerSchema, K extends ConditionKey<S>>(trigger: Trigger<S>, name: K, valueOrGetter: ConditionMap<S>[K] | (() => ConditionMap<S>[K])): RegistrationToken;
    mockAction<S extends TriggerSchema, K extends ActionKey<S>>(trigger: Trigger<S>, name: K, handler: ActionFn<ActionMap<S>[K]>): RegistrationToken;
    flushMicrotasks(): Promise<void>;
}
;
beforeEachbeforeEach<object>(fn: BeforeEachListener<object>, timeout?: number): void
Registers a callback function to be executed before each test within the current suite. This hook is useful for scenarios where you need to reset or reinitialize the test environment before each test runs, such as resetting database states, clearing caches, or reinitializing variables. **Note:** The `beforeEach` hooks are executed in the order they are defined one after another. You can configure this by changing the `sequence.hooks` option in the config file.
@paramfn - The callback function to be executed before each test. This function receives an `TestContext` parameter if additional test context is needed.@paramtimeout - Optional timeout in milliseconds for the hook. If not provided, the default hook timeout from the runner's configuration is used.@returns@example```ts // Example of using beforeEach to reset a database state beforeEach(async () => { await database.reset(); }); ```
(() => {
rtlet rt: TestRuntime = createTestRuntimefunction createTestRuntime(options?: TestRuntimeOptions): TestRuntime(); }); afterEachafterEach<object>(fn: AfterEachListener<object>, timeout?: number): void
Registers a callback function to be executed after each test within the current suite has completed. This hook is useful for scenarios where you need to clean up or reset the test environment after each test runs, such as deleting temporary files, clearing test-specific database entries, or resetting mocked functions. **Note:** The `afterEach` hooks are running in reverse order of their registration. You can configure this by changing the `sequence.hooks` option in the config file.
@paramfn - The callback function to be executed after each test. This function receives an `TestContext` parameter if additional test context is needed.@paramtimeout - Optional timeout in milliseconds for the hook. If not provided, the default hook timeout from the runner's configuration is used.@returns@example```ts // Example of using afterEach to delete temporary files created during a test afterEach(async () => { await fileSystem.deleteTempFiles(); }); ```
(() => {
rtlet rt: TestRuntime.disposefunction dispose(): void
Tear down the runtime completely.
(); // aborts in-flight handlers, drops registrations
});
import { createTestRuntimefunction createTestRuntime(options?: TestRuntimeOptions): TestRuntime } from '@triggery/testing';
import type { Middleware
type Middleware = {
    readonly name: string;
    onFire?(ctx: FireContext): void | {
        cancel: true;
        reason: string;
    };
    onBeforeMatch?(ctx: MatchContext): void;
    onSkip?(ctx: SkipContext): void;
    onActionStart?(ctx: ActionContext): void;
    onActionEnd?(ctx: ActionContext & {
        durationMs: number;
        result?: unknown;
    }): void;
    onError?(ctx: ActionContext & {
        error: unknown;
    }): void;
    onCascade?(ctx: CascadeContext): void;
}
} from '@triggery/core';
const traceconst trace: Middleware: Middleware
type Middleware = {
    readonly name: string;
    onFire?(ctx: FireContext): void | {
        cancel: true;
        reason: string;
    };
    onBeforeMatch?(ctx: MatchContext): void;
    onSkip?(ctx: SkipContext): void;
    onActionStart?(ctx: ActionContext): void;
    onActionEnd?(ctx: ActionContext & {
        durationMs: number;
        result?: unknown;
    }): void;
    onError?(ctx: ActionContext & {
        error: unknown;
    }): void;
    onCascade?(ctx: CascadeContext): void;
}
= {
namename: string: 'trace', onActionEnd
onActionEnd?(ctx: ActionContext & {
    durationMs: number;
    result?: unknown;
}): void
({ actionNameactionName: string, durationMsdurationMs: number }) {
consolevar console: Console.logConsole.log(...data: any[]): void
The **`console.log()`** static method outputs a message to the console. [MDN Reference](https://developer.mozilla.org/docs/Web/API/console/log_static)
(actionNameactionName: string, durationMsdurationMs: number);
}, }; const rtconst rt: TestRuntime = createTestRuntimefunction createTestRuntime(options?: TestRuntimeOptions): TestRuntime({ middlewaremiddleware?: readonly Middleware[] | undefined
Global middleware applied to every trigger in this runtime.
: [traceconst trace: Middleware] });