@c.a.f/testing 1.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (50) hide show
  1. package/.build/__tests__/react/renderWithCAF.spec.d.ts +1 -0
  2. package/.build/__tests__/react/renderWithCAF.spec.js +53 -0
  3. package/.build/index.d.ts +1 -0
  4. package/.build/index.js +1 -0
  5. package/.build/src/core/IntegrationTestHelpers.d.ts +77 -0
  6. package/.build/src/core/IntegrationTestHelpers.js +78 -0
  7. package/.build/src/core/PlocTestHelpers.d.ts +133 -0
  8. package/.build/src/core/PlocTestHelpers.js +205 -0
  9. package/.build/src/core/PulseTestHelpers.d.ts +71 -0
  10. package/.build/src/core/PulseTestHelpers.js +106 -0
  11. package/.build/src/core/RepositoryTestHelpers.d.ts +48 -0
  12. package/.build/src/core/RepositoryTestHelpers.js +76 -0
  13. package/.build/src/core/RouteTestHelpers.d.ts +67 -0
  14. package/.build/src/core/RouteTestHelpers.js +94 -0
  15. package/.build/src/core/UseCaseTestHelpers.d.ts +100 -0
  16. package/.build/src/core/UseCaseTestHelpers.js +161 -0
  17. package/.build/src/core/index.d.ts +6 -0
  18. package/.build/src/core/index.js +6 -0
  19. package/.build/src/i18n/I18nTestHelpers.d.ts +76 -0
  20. package/.build/src/i18n/I18nTestHelpers.js +122 -0
  21. package/.build/src/i18n/index.d.ts +1 -0
  22. package/.build/src/i18n/index.js +1 -0
  23. package/.build/src/index.d.ts +5 -0
  24. package/.build/src/index.js +10 -0
  25. package/.build/src/permission/PermissionTestHelpers.d.ts +75 -0
  26. package/.build/src/permission/PermissionTestHelpers.js +121 -0
  27. package/.build/src/permission/index.d.ts +1 -0
  28. package/.build/src/permission/index.js +1 -0
  29. package/.build/src/react/createTestPloc.d.ts +19 -0
  30. package/.build/src/react/createTestPloc.js +21 -0
  31. package/.build/src/react/index.d.ts +12 -0
  32. package/.build/src/react/index.js +12 -0
  33. package/.build/src/react/mockUseCase.d.ts +36 -0
  34. package/.build/src/react/mockUseCase.js +44 -0
  35. package/.build/src/react/renderWithCAF.d.ts +31 -0
  36. package/.build/src/react/renderWithCAF.js +23 -0
  37. package/.build/src/react/waitForPlocState.d.ts +22 -0
  38. package/.build/src/react/waitForPlocState.js +24 -0
  39. package/.build/src/validation/ValidationTestHelpers.d.ts +66 -0
  40. package/.build/src/validation/ValidationTestHelpers.js +118 -0
  41. package/.build/src/validation/index.d.ts +1 -0
  42. package/.build/src/validation/index.js +1 -0
  43. package/.build/src/workflow/WorkflowTestHelpers.d.ts +75 -0
  44. package/.build/src/workflow/WorkflowTestHelpers.js +146 -0
  45. package/.build/src/workflow/index.d.ts +1 -0
  46. package/.build/src/workflow/index.js +1 -0
  47. package/.build/vitest.config.d.ts +7 -0
  48. package/.build/vitest.config.js +6 -0
  49. package/README.md +503 -0
  50. package/package.json +87 -0
@@ -0,0 +1 @@
1
+ export {};
@@ -0,0 +1,53 @@
1
+ import { jsx as _jsx } from "react/jsx-runtime";
2
+ import { describe, it, expect } from 'vitest';
3
+ import { createSuccessResult } from '../../src/core/UseCaseTestHelpers';
4
+ import { renderWithCAF, createTestPloc, waitForPlocState, mockUseCase } from '../../src/react';
5
+ // Full render tests require a single React instance (no duplicate react in node_modules).
6
+ // Use renderWithCAF in your app tests; see README for examples.
7
+ describe('renderWithCAF', () => {
8
+ it.skip('returns render result when given ui and options (run in app to avoid duplicate React)', () => {
9
+ const ploc = createTestPloc({ count: 0 });
10
+ const result = renderWithCAF(_jsx("div", { "data-testid": "root", children: "Hello" }), { plocs: { counter: ploc } });
11
+ expect(result).toBeDefined();
12
+ expect(result.getByTestId('root')).toHaveTextContent('Hello');
13
+ });
14
+ });
15
+ describe('createTestPloc', () => {
16
+ it('creates ploc with initial state', () => {
17
+ const ploc = createTestPloc({ count: 0 });
18
+ expect(ploc.state).toEqual({ count: 0 });
19
+ });
20
+ it('allows changing state', () => {
21
+ const ploc = createTestPloc({ count: 0 });
22
+ ploc.changeState({ count: 1 });
23
+ expect(ploc.state.count).toBe(1);
24
+ });
25
+ });
26
+ describe('waitForPlocState', () => {
27
+ it('resolves when predicate matches', async () => {
28
+ const ploc = createTestPloc({ count: 0 });
29
+ const promise = waitForPlocState(ploc, (s) => s.count === 2);
30
+ ploc.changeState({ count: 1 });
31
+ ploc.changeState({ count: 2 });
32
+ const state = await promise;
33
+ expect(state).toEqual({ count: 2 });
34
+ });
35
+ });
36
+ describe('mockUseCase', () => {
37
+ it('success returns data', async () => {
38
+ const uc = mockUseCase.success({ id: '1' });
39
+ const result = await uc.execute();
40
+ expect(result.data.value).toEqual({ id: '1' });
41
+ });
42
+ it('error returns error', async () => {
43
+ const err = new Error('Fail');
44
+ const uc = mockUseCase.error(err);
45
+ const result = await uc.execute();
46
+ expect(result.error.value).toBe(err);
47
+ });
48
+ it('fn uses custom implementation', async () => {
49
+ const uc = mockUseCase.fn((n) => Promise.resolve(createSuccessResult(n * 2)));
50
+ const result = await uc.execute(21);
51
+ expect(result.data.value).toBe(42);
52
+ });
53
+ });
@@ -0,0 +1 @@
1
+ export * from './src';
@@ -0,0 +1 @@
1
+ export * from './src';
@@ -0,0 +1,77 @@
1
+ /**
2
+ * Integration test helpers for CAF.
3
+ *
4
+ * Utilities to run Ploc + UseCase together, flush async work, and small
5
+ * helpers for integration-style tests.
6
+ *
7
+ * @example
8
+ * ```ts
9
+ * import {
10
+ * createPlocWithUseCase,
11
+ * flushPromises,
12
+ * runWithFakeTimers,
13
+ * } from '@c.a.f/testing/core';
14
+ * import { Ploc } from '@c.a.f/core';
15
+ *
16
+ * const ploc = createPlocWithUseCase(InitialState, (state, useCase) => ({ ... }));
17
+ * await flushPromises();
18
+ * ```
19
+ */
20
+ import type { Ploc } from '@c.a.f/core';
21
+ import type { UseCase } from '@c.a.f/core';
22
+ /**
23
+ * Resolve all pending promises (microtasks).
24
+ * Call after triggering async code to wait for Promise resolution in tests.
25
+ *
26
+ * @example
27
+ * ```ts
28
+ * ploc.load();
29
+ * await flushPromises();
30
+ * expect(ploc.state.items.length).toBe(1);
31
+ * ```
32
+ */
33
+ export declare function flushPromises(): Promise<void>;
34
+ /**
35
+ * Run a callback with fake timers (if the test environment supports it).
36
+ * Returns the result of the callback. Does not install/uninstall timers;
37
+ * use your test framework's fake timers (e.g. vi.useFakeTimers()) and this
38
+ * to run a tick or advance time.
39
+ *
40
+ * This helper is a no-op that just runs the callback; use it as a placeholder
41
+ * for "run this in a fake timer context" when you pair it with vi.useFakeTimers()
42
+ * or jest.useFakeTimers() in the test.
43
+ *
44
+ * @example
45
+ * ```ts
46
+ * vi.useFakeTimers();
47
+ * const p = doSomethingAsync();
48
+ * await runWithFakeTimers(async () => {
49
+ * vi.advanceTimersByTime(1000);
50
+ * await flushPromises();
51
+ * });
52
+ * vi.useRealTimers();
53
+ * ```
54
+ */
55
+ export declare function runWithFakeTimers<T>(fn: () => Promise<T>): Promise<T>;
56
+ /**
57
+ * Minimal context for integration tests: a Ploc and a UseCase.
58
+ * Use when your test needs to wire a Ploc to a UseCase without a full app.
59
+ */
60
+ export interface PlocUseCaseTestContext<S, A extends any[], T> {
61
+ ploc: Ploc<S>;
62
+ useCase: UseCase<A, T>;
63
+ }
64
+ /**
65
+ * Create a minimal integration context with a mock Ploc and a success-returning UseCase.
66
+ * Useful when testing a component or flow that expects both a Ploc and a UseCase from context.
67
+ *
68
+ * @example
69
+ * ```ts
70
+ * const { ploc, useCase } = createPlocUseCaseContext(
71
+ * { items: [], loading: false },
72
+ * []
73
+ * );
74
+ * // Inject into CAFProvider or pass as props
75
+ * ```
76
+ */
77
+ export declare function createPlocUseCaseContext<S, T>(initialState: S, useCaseResult: T): PlocUseCaseTestContext<S, [], T>;
@@ -0,0 +1,78 @@
1
+ /**
2
+ * Integration test helpers for CAF.
3
+ *
4
+ * Utilities to run Ploc + UseCase together, flush async work, and small
5
+ * helpers for integration-style tests.
6
+ *
7
+ * @example
8
+ * ```ts
9
+ * import {
10
+ * createPlocWithUseCase,
11
+ * flushPromises,
12
+ * runWithFakeTimers,
13
+ * } from '@c.a.f/testing/core';
14
+ * import { Ploc } from '@c.a.f/core';
15
+ *
16
+ * const ploc = createPlocWithUseCase(InitialState, (state, useCase) => ({ ... }));
17
+ * await flushPromises();
18
+ * ```
19
+ */
20
+ import { createMockPloc } from './PlocTestHelpers';
21
+ import { createMockUseCaseSuccess } from './UseCaseTestHelpers';
22
+ /**
23
+ * Resolve all pending promises (microtasks).
24
+ * Call after triggering async code to wait for Promise resolution in tests.
25
+ *
26
+ * @example
27
+ * ```ts
28
+ * ploc.load();
29
+ * await flushPromises();
30
+ * expect(ploc.state.items.length).toBe(1);
31
+ * ```
32
+ */
33
+ export function flushPromises() {
34
+ return new Promise((resolve) => setTimeout(resolve, 0));
35
+ }
36
+ /**
37
+ * Run a callback with fake timers (if the test environment supports it).
38
+ * Returns the result of the callback. Does not install/uninstall timers;
39
+ * use your test framework's fake timers (e.g. vi.useFakeTimers()) and this
40
+ * to run a tick or advance time.
41
+ *
42
+ * This helper is a no-op that just runs the callback; use it as a placeholder
43
+ * for "run this in a fake timer context" when you pair it with vi.useFakeTimers()
44
+ * or jest.useFakeTimers() in the test.
45
+ *
46
+ * @example
47
+ * ```ts
48
+ * vi.useFakeTimers();
49
+ * const p = doSomethingAsync();
50
+ * await runWithFakeTimers(async () => {
51
+ * vi.advanceTimersByTime(1000);
52
+ * await flushPromises();
53
+ * });
54
+ * vi.useRealTimers();
55
+ * ```
56
+ */
57
+ export async function runWithFakeTimers(fn) {
58
+ return fn();
59
+ }
60
+ /**
61
+ * Create a minimal integration context with a mock Ploc and a success-returning UseCase.
62
+ * Useful when testing a component or flow that expects both a Ploc and a UseCase from context.
63
+ *
64
+ * @example
65
+ * ```ts
66
+ * const { ploc, useCase } = createPlocUseCaseContext(
67
+ * { items: [], loading: false },
68
+ * []
69
+ * );
70
+ * // Inject into CAFProvider or pass as props
71
+ * ```
72
+ */
73
+ export function createPlocUseCaseContext(initialState, useCaseResult) {
74
+ return {
75
+ ploc: createMockPloc(initialState),
76
+ useCase: createMockUseCaseSuccess(useCaseResult),
77
+ };
78
+ }
@@ -0,0 +1,133 @@
1
+ /**
2
+ * Test helpers for Ploc (Presentation Logic Component).
3
+ *
4
+ * Provides utilities for testing Ploc instances.
5
+ *
6
+ * @example
7
+ * ```ts
8
+ * import { createPlocTester, waitForStateChange, createMockPloc } from '@c.a.f/testing/core';
9
+ * import { MyPloc } from './MyPloc';
10
+ *
11
+ * const tester = createPlocTester(new MyPloc());
12
+ *
13
+ * // Wait for state change
14
+ * await waitForStateChange(tester.ploc, (state) => state.count === 5);
15
+ *
16
+ * // Get state history
17
+ * const history = tester.getStateHistory();
18
+ *
19
+ * // Or use a mock Ploc with controllable state
20
+ * const mockPloc = createMockPloc({ count: 0 });
21
+ * mockPloc.changeState({ count: 1 });
22
+ * ```
23
+ */
24
+ import { Ploc } from '@c.a.f/core';
25
+ type PlocInstance<T> = {
26
+ state: T;
27
+ changeState(state: T): void;
28
+ subscribe(listener: (state: T) => void): void;
29
+ unsubscribe(listener: (state: T) => void): void;
30
+ };
31
+ /**
32
+ * Concrete Ploc implementation for testing.
33
+ * Provides a Ploc with controllable state and no business logic.
34
+ */
35
+ export declare class MockPloc<S> extends Ploc<S> {
36
+ constructor(initialState: S);
37
+ }
38
+ /**
39
+ * Create a mock Ploc with controllable state for unit tests.
40
+ * The returned Ploc has no logic; use changeState() to drive state in tests.
41
+ *
42
+ * @example
43
+ * ```ts
44
+ * const ploc = createMockPloc({ count: 0, loading: false });
45
+ * expect(ploc.state.count).toBe(0);
46
+ * ploc.changeState({ count: 1, loading: true });
47
+ * expect(ploc.state.count).toBe(1);
48
+ * ```
49
+ */
50
+ export declare function createMockPloc<S>(initialState: S): Ploc<S>;
51
+ /**
52
+ * Ploc tester utility.
53
+ * Tracks state changes and provides testing utilities.
54
+ */
55
+ export declare class PlocTester<T> {
56
+ readonly ploc: PlocInstance<T>;
57
+ private stateHistory;
58
+ private listener;
59
+ constructor(ploc: PlocInstance<T>);
60
+ /**
61
+ * Get the current state.
62
+ */
63
+ getState(): T;
64
+ /**
65
+ * Get the state history.
66
+ */
67
+ getStateHistory(): T[];
68
+ /**
69
+ * Get the initial state.
70
+ */
71
+ getInitialState(): T;
72
+ /**
73
+ * Get the last state change.
74
+ */
75
+ getLastStateChange(): T | undefined;
76
+ /**
77
+ * Get the number of state changes.
78
+ */
79
+ getStateChangeCount(): number;
80
+ /**
81
+ * Cleanup: unsubscribe from state changes.
82
+ */
83
+ cleanup(): void;
84
+ }
85
+ /**
86
+ * Create a Ploc tester instance.
87
+ */
88
+ export declare function createPlocTester<T>(ploc: PlocInstance<T>): PlocTester<T>;
89
+ /**
90
+ * Wait for a state change that matches a predicate.
91
+ *
92
+ * @param ploc The Ploc instance to watch
93
+ * @param predicate Function that returns true when the desired state is reached
94
+ * @param timeout Maximum time to wait in milliseconds (default: 5000)
95
+ * @returns Promise that resolves when the predicate returns true
96
+ */
97
+ export declare function waitForStateChange<T>(ploc: PlocInstance<T>, predicate: (state: T) => boolean, timeout?: number): Promise<T>;
98
+ /**
99
+ * Wait for a specific number of state changes.
100
+ */
101
+ export declare function waitForStateChanges<T>(ploc: PlocInstance<T>, count: number, timeout?: number): Promise<T[]>;
102
+ /**
103
+ * Assert that the Ploc tester's state history matches the expected states.
104
+ * Uses JSON comparison so objects are compared by value.
105
+ * Use with your test framework's expect (e.g. expect().toEqual).
106
+ *
107
+ * @example
108
+ * ```ts
109
+ * const tester = createPlocTester(ploc);
110
+ * ploc.changeState({ step: 1 });
111
+ * ploc.changeState({ step: 2 });
112
+ * assertStateHistory(tester, [initialState, { step: 1 }, { step: 2 }]);
113
+ * ```
114
+ */
115
+ export declare function assertStateHistory<T>(tester: PlocTester<T>, expected: T[]): void;
116
+ /**
117
+ * Return a serializable snapshot of the state history for snapshot testing.
118
+ * Use with your test framework's toMatchSnapshot() or similar.
119
+ *
120
+ * @example
121
+ * ```ts
122
+ * const tester = createPlocTester(ploc);
123
+ * // ... trigger state changes ...
124
+ * expect(getStateHistorySnapshot(tester)).toMatchSnapshot();
125
+ * ```
126
+ */
127
+ export declare function getStateHistorySnapshot<T>(tester: PlocTester<T>): T[];
128
+ /**
129
+ * Return a serialized (JSON) snapshot of the state history for snapshot testing.
130
+ * Useful when state is plain data and you want a string snapshot.
131
+ */
132
+ export declare function getStateHistorySnapshotJson<T>(tester: PlocTester<T>): string;
133
+ export {};
@@ -0,0 +1,205 @@
1
+ /**
2
+ * Test helpers for Ploc (Presentation Logic Component).
3
+ *
4
+ * Provides utilities for testing Ploc instances.
5
+ *
6
+ * @example
7
+ * ```ts
8
+ * import { createPlocTester, waitForStateChange, createMockPloc } from '@c.a.f/testing/core';
9
+ * import { MyPloc } from './MyPloc';
10
+ *
11
+ * const tester = createPlocTester(new MyPloc());
12
+ *
13
+ * // Wait for state change
14
+ * await waitForStateChange(tester.ploc, (state) => state.count === 5);
15
+ *
16
+ * // Get state history
17
+ * const history = tester.getStateHistory();
18
+ *
19
+ * // Or use a mock Ploc with controllable state
20
+ * const mockPloc = createMockPloc({ count: 0 });
21
+ * mockPloc.changeState({ count: 1 });
22
+ * ```
23
+ */
24
+ import { Ploc } from '@c.a.f/core';
25
+ /**
26
+ * Concrete Ploc implementation for testing.
27
+ * Provides a Ploc with controllable state and no business logic.
28
+ */
29
+ export class MockPloc extends Ploc {
30
+ constructor(initialState) {
31
+ super(initialState);
32
+ }
33
+ }
34
+ /**
35
+ * Create a mock Ploc with controllable state for unit tests.
36
+ * The returned Ploc has no logic; use changeState() to drive state in tests.
37
+ *
38
+ * @example
39
+ * ```ts
40
+ * const ploc = createMockPloc({ count: 0, loading: false });
41
+ * expect(ploc.state.count).toBe(0);
42
+ * ploc.changeState({ count: 1, loading: true });
43
+ * expect(ploc.state.count).toBe(1);
44
+ * ```
45
+ */
46
+ export function createMockPloc(initialState) {
47
+ return new MockPloc(initialState);
48
+ }
49
+ /**
50
+ * Ploc tester utility.
51
+ * Tracks state changes and provides testing utilities.
52
+ */
53
+ export class PlocTester {
54
+ ploc;
55
+ stateHistory = [];
56
+ listener = null;
57
+ constructor(ploc) {
58
+ this.ploc = ploc;
59
+ this.stateHistory.push(ploc.state);
60
+ this.listener = (state) => {
61
+ this.stateHistory.push(state);
62
+ };
63
+ ploc.subscribe(this.listener);
64
+ }
65
+ /**
66
+ * Get the current state.
67
+ */
68
+ getState() {
69
+ return this.ploc.state;
70
+ }
71
+ /**
72
+ * Get the state history.
73
+ */
74
+ getStateHistory() {
75
+ return [...this.stateHistory];
76
+ }
77
+ /**
78
+ * Get the initial state.
79
+ */
80
+ getInitialState() {
81
+ return this.stateHistory[0];
82
+ }
83
+ /**
84
+ * Get the last state change.
85
+ */
86
+ getLastStateChange() {
87
+ return this.stateHistory.length > 1
88
+ ? this.stateHistory[this.stateHistory.length - 1]
89
+ : undefined;
90
+ }
91
+ /**
92
+ * Get the number of state changes.
93
+ */
94
+ getStateChangeCount() {
95
+ return Math.max(0, this.stateHistory.length - 1);
96
+ }
97
+ /**
98
+ * Cleanup: unsubscribe from state changes.
99
+ */
100
+ cleanup() {
101
+ if (this.listener) {
102
+ this.ploc.unsubscribe(this.listener);
103
+ this.listener = null;
104
+ }
105
+ }
106
+ }
107
+ /**
108
+ * Create a Ploc tester instance.
109
+ */
110
+ export function createPlocTester(ploc) {
111
+ return new PlocTester(ploc);
112
+ }
113
+ /**
114
+ * Wait for a state change that matches a predicate.
115
+ *
116
+ * @param ploc The Ploc instance to watch
117
+ * @param predicate Function that returns true when the desired state is reached
118
+ * @param timeout Maximum time to wait in milliseconds (default: 5000)
119
+ * @returns Promise that resolves when the predicate returns true
120
+ */
121
+ export function waitForStateChange(ploc, predicate, timeout = 5000) {
122
+ return new Promise((resolve, reject) => {
123
+ // Check current state first
124
+ if (predicate(ploc.state)) {
125
+ resolve(ploc.state);
126
+ return;
127
+ }
128
+ const listener = (state) => {
129
+ if (predicate(state)) {
130
+ clearTimeout(timer);
131
+ ploc.unsubscribe(listener);
132
+ resolve(state);
133
+ }
134
+ };
135
+ const timer = setTimeout(() => {
136
+ ploc.unsubscribe(listener);
137
+ reject(new Error(`Timeout waiting for state change (${timeout}ms)`));
138
+ }, timeout);
139
+ ploc.subscribe(listener);
140
+ });
141
+ }
142
+ /**
143
+ * Wait for a specific number of state changes.
144
+ */
145
+ export function waitForStateChanges(ploc, count, timeout = 5000) {
146
+ return new Promise((resolve, reject) => {
147
+ const states = [];
148
+ const listener = (state) => {
149
+ states.push(state);
150
+ if (states.length >= count) {
151
+ clearTimeout(timer);
152
+ ploc.unsubscribe(listener);
153
+ resolve(states);
154
+ }
155
+ };
156
+ const timer = setTimeout(() => {
157
+ ploc.unsubscribe(listener);
158
+ reject(new Error(`Timeout waiting for ${count} state changes (${timeout}ms)`));
159
+ }, timeout);
160
+ ploc.subscribe(listener);
161
+ });
162
+ }
163
+ // --- Snapshot testing utilities ---
164
+ /**
165
+ * Assert that the Ploc tester's state history matches the expected states.
166
+ * Uses JSON comparison so objects are compared by value.
167
+ * Use with your test framework's expect (e.g. expect().toEqual).
168
+ *
169
+ * @example
170
+ * ```ts
171
+ * const tester = createPlocTester(ploc);
172
+ * ploc.changeState({ step: 1 });
173
+ * ploc.changeState({ step: 2 });
174
+ * assertStateHistory(tester, [initialState, { step: 1 }, { step: 2 }]);
175
+ * ```
176
+ */
177
+ export function assertStateHistory(tester, expected) {
178
+ const actual = tester.getStateHistory();
179
+ const actualJson = JSON.stringify(actual);
180
+ const expectedJson = JSON.stringify(expected);
181
+ if (actualJson !== expectedJson) {
182
+ throw new Error(`State history mismatch.\nExpected: ${expectedJson}\nActual: ${actualJson}`);
183
+ }
184
+ }
185
+ /**
186
+ * Return a serializable snapshot of the state history for snapshot testing.
187
+ * Use with your test framework's toMatchSnapshot() or similar.
188
+ *
189
+ * @example
190
+ * ```ts
191
+ * const tester = createPlocTester(ploc);
192
+ * // ... trigger state changes ...
193
+ * expect(getStateHistorySnapshot(tester)).toMatchSnapshot();
194
+ * ```
195
+ */
196
+ export function getStateHistorySnapshot(tester) {
197
+ return tester.getStateHistory();
198
+ }
199
+ /**
200
+ * Return a serialized (JSON) snapshot of the state history for snapshot testing.
201
+ * Useful when state is plain data and you want a string snapshot.
202
+ */
203
+ export function getStateHistorySnapshotJson(tester) {
204
+ return JSON.stringify(tester.getStateHistory(), null, 2);
205
+ }
@@ -0,0 +1,71 @@
1
+ /**
2
+ * Test helpers for Pulse (reactive values).
3
+ *
4
+ * Provides utilities for testing Pulse instances.
5
+ *
6
+ * @example
7
+ * ```ts
8
+ * import { createPulseTester, waitForPulseValue } from '@c.a.f/testing/core';
9
+ * import { pulse } from '@c.a.f/core';
10
+ *
11
+ * const count = pulse(0);
12
+ * const tester = createPulseTester(count);
13
+ *
14
+ * count.value = 5;
15
+ * await waitForPulseValue(count, (value) => value === 5);
16
+ * ```
17
+ */
18
+ import type { Pulse } from '@c.a.f/core';
19
+ /**
20
+ * Pulse tester utility.
21
+ * Tracks value changes and provides testing utilities.
22
+ */
23
+ type PulseLike<T> = Pulse<T> & {
24
+ value: T;
25
+ subscribe(listener: (value: T) => void): void;
26
+ unsubscribe(listener: (value: T) => void): void;
27
+ };
28
+ export declare class PulseTester<T> {
29
+ readonly pulse: PulseLike<T>;
30
+ private valueHistory;
31
+ private listener;
32
+ constructor(pulse: PulseLike<T>);
33
+ /**
34
+ * Get the current value.
35
+ */
36
+ getValue(): T;
37
+ /**
38
+ * Get the value history.
39
+ */
40
+ getValueHistory(): T[];
41
+ /**
42
+ * Get the initial value.
43
+ */
44
+ getInitialValue(): T;
45
+ /**
46
+ * Get the last value change.
47
+ */
48
+ getLastValueChange(): T | undefined;
49
+ /**
50
+ * Get the number of value changes.
51
+ */
52
+ getValueChangeCount(): number;
53
+ /**
54
+ * Cleanup: unsubscribe from value changes.
55
+ */
56
+ cleanup(): void;
57
+ }
58
+ /**
59
+ * Create a Pulse tester instance.
60
+ */
61
+ export declare function createPulseTester<T>(pulse: PulseLike<T>): PulseTester<T>;
62
+ /**
63
+ * Wait for a pulse value that matches a predicate.
64
+ *
65
+ * @param pulse The Pulse instance to watch
66
+ * @param predicate Function that returns true when the desired value is reached
67
+ * @param timeout Maximum time to wait in milliseconds (default: 5000)
68
+ * @returns Promise that resolves when the predicate returns true
69
+ */
70
+ export declare function waitForPulseValue<T>(pulse: PulseLike<T>, predicate: (value: T) => boolean, timeout?: number): Promise<T>;
71
+ export {};