@hurum/core 0.0.1
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.
- package/dist/index.cjs +1421 -0
- package/dist/index.cjs.map +1 -0
- package/dist/index.d.cts +114 -0
- package/dist/index.d.ts +114 -0
- package/dist/index.js +1404 -0
- package/dist/index.js.map +1 -0
- package/dist/store-BrPM22fc.d.cts +467 -0
- package/dist/store-BrPM22fc.d.ts +467 -0
- package/dist/testing.cjs +237 -0
- package/dist/testing.cjs.map +1 -0
- package/dist/testing.d.cts +73 -0
- package/dist/testing.d.ts +73 -0
- package/dist/testing.js +231 -0
- package/dist/testing.js.map +1 -0
- package/package.json +49 -0
package/dist/testing.cjs
ADDED
|
@@ -0,0 +1,237 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
// src/computed.ts
|
|
4
|
+
function structuralEqual(a, b) {
|
|
5
|
+
if (a === b) return true;
|
|
6
|
+
if (a === null || b === null) return false;
|
|
7
|
+
if (typeof a !== typeof b) return false;
|
|
8
|
+
if (typeof a !== "object") return false;
|
|
9
|
+
if (Array.isArray(a) && Array.isArray(b)) {
|
|
10
|
+
if (a.length !== b.length) return false;
|
|
11
|
+
for (let i = 0; i < a.length; i++) {
|
|
12
|
+
if (!structuralEqual(a[i], b[i])) return false;
|
|
13
|
+
}
|
|
14
|
+
return true;
|
|
15
|
+
}
|
|
16
|
+
if (Array.isArray(a) !== Array.isArray(b)) return false;
|
|
17
|
+
const keysA = Object.keys(a);
|
|
18
|
+
const keysB = Object.keys(b);
|
|
19
|
+
if (keysA.length !== keysB.length) return false;
|
|
20
|
+
for (const key of keysA) {
|
|
21
|
+
if (!structuralEqual(a[key], b[key])) return false;
|
|
22
|
+
}
|
|
23
|
+
return true;
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
// testing/test-store.ts
|
|
27
|
+
function TestStore(storeDef, options) {
|
|
28
|
+
const instance = storeDef.create(options);
|
|
29
|
+
const instanceSend = instance.send;
|
|
30
|
+
const testSendBase = async (...args) => {
|
|
31
|
+
instanceSend(...args);
|
|
32
|
+
await flushMicrotasks();
|
|
33
|
+
};
|
|
34
|
+
const testSend = new Proxy(testSendBase, {
|
|
35
|
+
get(_target, prop) {
|
|
36
|
+
if (typeof prop !== "string") return void 0;
|
|
37
|
+
if (prop === "then") return void 0;
|
|
38
|
+
if (prop === "bind" || prop === "call" || prop === "apply" || prop === "length" || prop === "name" || prop === "prototype")
|
|
39
|
+
return testSendBase[prop];
|
|
40
|
+
const shortcut = instanceSend[prop];
|
|
41
|
+
if (typeof shortcut === "function") {
|
|
42
|
+
return async (payload) => {
|
|
43
|
+
shortcut(payload);
|
|
44
|
+
await flushMicrotasks();
|
|
45
|
+
};
|
|
46
|
+
}
|
|
47
|
+
return void 0;
|
|
48
|
+
}
|
|
49
|
+
});
|
|
50
|
+
return {
|
|
51
|
+
send: testSend,
|
|
52
|
+
getState() {
|
|
53
|
+
return instance.getState();
|
|
54
|
+
},
|
|
55
|
+
get scope() {
|
|
56
|
+
return instance.scope;
|
|
57
|
+
},
|
|
58
|
+
assertState(expected) {
|
|
59
|
+
const state = instance.getState();
|
|
60
|
+
for (const [key, value] of Object.entries(expected)) {
|
|
61
|
+
if (!structuralEqual(state[key], value)) {
|
|
62
|
+
throw new Error(
|
|
63
|
+
`State mismatch for key "${key}":
|
|
64
|
+
Expected: ${JSON.stringify(value)}
|
|
65
|
+
Received: ${JSON.stringify(state[key])}`
|
|
66
|
+
);
|
|
67
|
+
}
|
|
68
|
+
}
|
|
69
|
+
},
|
|
70
|
+
assertEvents(expected) {
|
|
71
|
+
const actual = instance.__eventLog;
|
|
72
|
+
if (actual.length !== expected.length) {
|
|
73
|
+
throw new Error(
|
|
74
|
+
`Event count mismatch:
|
|
75
|
+
Expected ${expected.length} events: [${expected.map((e) => e.type).join(", ")}]
|
|
76
|
+
Received ${actual.length} events: [${actual.map((e) => e.type).join(", ")}]`
|
|
77
|
+
);
|
|
78
|
+
}
|
|
79
|
+
for (let i = 0; i < expected.length; i++) {
|
|
80
|
+
const exp = expected[i];
|
|
81
|
+
const act = actual[i];
|
|
82
|
+
if (!structuralEqual(exp, act)) {
|
|
83
|
+
throw new Error(
|
|
84
|
+
`Event mismatch at index ${i}:
|
|
85
|
+
Expected: ${JSON.stringify(exp)}
|
|
86
|
+
Received: ${JSON.stringify(act)}`
|
|
87
|
+
);
|
|
88
|
+
}
|
|
89
|
+
}
|
|
90
|
+
},
|
|
91
|
+
assertEventSequence(expected) {
|
|
92
|
+
const snapshots = instance.__stateSnapshots;
|
|
93
|
+
for (const { event: expectedEvent, state: expectedState } of expected) {
|
|
94
|
+
const snapshot = snapshots.find((s) => structuralEqual(s.event, expectedEvent));
|
|
95
|
+
if (!snapshot) {
|
|
96
|
+
throw new Error(
|
|
97
|
+
`Expected event not found in sequence:
|
|
98
|
+
${JSON.stringify(expectedEvent)}`
|
|
99
|
+
);
|
|
100
|
+
}
|
|
101
|
+
for (const [key, value] of Object.entries(expectedState)) {
|
|
102
|
+
if (!structuralEqual(snapshot.state[key], value)) {
|
|
103
|
+
throw new Error(
|
|
104
|
+
`State mismatch after event "${expectedEvent.type}" for key "${key}":
|
|
105
|
+
Expected: ${JSON.stringify(value)}
|
|
106
|
+
Received: ${JSON.stringify(snapshot.state[key])}`
|
|
107
|
+
);
|
|
108
|
+
}
|
|
109
|
+
}
|
|
110
|
+
}
|
|
111
|
+
},
|
|
112
|
+
assertNoRunningExecutors() {
|
|
113
|
+
const count = instance.__runningCount();
|
|
114
|
+
if (count > 0) {
|
|
115
|
+
throw new Error(
|
|
116
|
+
`Expected no running executors, but ${count} are still running`
|
|
117
|
+
);
|
|
118
|
+
}
|
|
119
|
+
}
|
|
120
|
+
};
|
|
121
|
+
}
|
|
122
|
+
async function flushMicrotasks() {
|
|
123
|
+
for (let i = 0; i < 10; i++) {
|
|
124
|
+
await new Promise((resolve) => setTimeout(resolve, 0));
|
|
125
|
+
}
|
|
126
|
+
}
|
|
127
|
+
function getExecutorFn(executor) {
|
|
128
|
+
return executor.__fn;
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
// testing/test-executor.ts
|
|
132
|
+
function TestExecutor(executor, options) {
|
|
133
|
+
const fn = getExecutorFn(executor);
|
|
134
|
+
const emittedEvents = [];
|
|
135
|
+
const abortController = new AbortController();
|
|
136
|
+
const state = options?.state ?? {};
|
|
137
|
+
function emit(event) {
|
|
138
|
+
emittedEvents.push(event);
|
|
139
|
+
}
|
|
140
|
+
return {
|
|
141
|
+
async run(input) {
|
|
142
|
+
const context = {
|
|
143
|
+
deps: options?.deps ?? {},
|
|
144
|
+
emit,
|
|
145
|
+
getState: () => state,
|
|
146
|
+
signal: abortController.signal,
|
|
147
|
+
scope: {}
|
|
148
|
+
};
|
|
149
|
+
const result = fn(input, context);
|
|
150
|
+
if (result instanceof Promise) {
|
|
151
|
+
await result;
|
|
152
|
+
}
|
|
153
|
+
},
|
|
154
|
+
abort() {
|
|
155
|
+
abortController.abort();
|
|
156
|
+
},
|
|
157
|
+
assertEmitted(expected) {
|
|
158
|
+
if (emittedEvents.length !== expected.length) {
|
|
159
|
+
throw new Error(
|
|
160
|
+
`Emitted event count mismatch:
|
|
161
|
+
Expected ${expected.length} events: [${expected.map((e) => e.type).join(", ")}]
|
|
162
|
+
Received ${emittedEvents.length} events: [${emittedEvents.map((e) => e.type).join(", ")}]`
|
|
163
|
+
);
|
|
164
|
+
}
|
|
165
|
+
for (let i = 0; i < expected.length; i++) {
|
|
166
|
+
const exp = expected[i];
|
|
167
|
+
const act = emittedEvents[i];
|
|
168
|
+
if (!structuralEqual(exp, act)) {
|
|
169
|
+
throw new Error(
|
|
170
|
+
`Emitted event mismatch at index ${i}:
|
|
171
|
+
Expected: ${JSON.stringify(exp)}
|
|
172
|
+
Received: ${JSON.stringify(act)}`
|
|
173
|
+
);
|
|
174
|
+
}
|
|
175
|
+
}
|
|
176
|
+
},
|
|
177
|
+
get emittedEvents() {
|
|
178
|
+
return emittedEvents;
|
|
179
|
+
}
|
|
180
|
+
};
|
|
181
|
+
}
|
|
182
|
+
|
|
183
|
+
// testing/test-reducer.ts
|
|
184
|
+
function TestReducer(storeDef) {
|
|
185
|
+
const config = storeDef.__config;
|
|
186
|
+
return {
|
|
187
|
+
apply(state, event) {
|
|
188
|
+
const handler = config.on?.[event.type];
|
|
189
|
+
if (!handler) {
|
|
190
|
+
return state;
|
|
191
|
+
}
|
|
192
|
+
const { type: _type, ...payload } = event;
|
|
193
|
+
return handler(
|
|
194
|
+
state,
|
|
195
|
+
payload
|
|
196
|
+
);
|
|
197
|
+
}
|
|
198
|
+
};
|
|
199
|
+
}
|
|
200
|
+
|
|
201
|
+
// testing/test-computed.ts
|
|
202
|
+
function TestComputed(storeDef, fieldName) {
|
|
203
|
+
const config = storeDef.__config;
|
|
204
|
+
const computedDef = config.computed;
|
|
205
|
+
if (!computedDef) {
|
|
206
|
+
throw new Error(
|
|
207
|
+
`[hurum] Store has no computed definition. Cannot test computed field "${fieldName}".`
|
|
208
|
+
);
|
|
209
|
+
}
|
|
210
|
+
return {
|
|
211
|
+
evaluate(rawState) {
|
|
212
|
+
const fn = computedDef[fieldName];
|
|
213
|
+
if (!fn || typeof fn !== "function") {
|
|
214
|
+
throw new Error(
|
|
215
|
+
`[hurum] Computed field "${fieldName}" not found in store.`
|
|
216
|
+
);
|
|
217
|
+
}
|
|
218
|
+
return fn(rawState);
|
|
219
|
+
}
|
|
220
|
+
};
|
|
221
|
+
}
|
|
222
|
+
|
|
223
|
+
// testing/test-intent.ts
|
|
224
|
+
function TestIntent(intent) {
|
|
225
|
+
return {
|
|
226
|
+
commands: intent.commands,
|
|
227
|
+
mode: intent.mode
|
|
228
|
+
};
|
|
229
|
+
}
|
|
230
|
+
|
|
231
|
+
exports.TestComputed = TestComputed;
|
|
232
|
+
exports.TestExecutor = TestExecutor;
|
|
233
|
+
exports.TestIntent = TestIntent;
|
|
234
|
+
exports.TestReducer = TestReducer;
|
|
235
|
+
exports.TestStore = TestStore;
|
|
236
|
+
//# sourceMappingURL=testing.cjs.map
|
|
237
|
+
//# sourceMappingURL=testing.cjs.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/computed.ts","../testing/test-store.ts","../src/command-executor.ts","../testing/test-executor.ts","../testing/test-reducer.ts","../testing/test-computed.ts","../testing/test-intent.ts"],"names":[],"mappings":"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","file":"testing.cjs","sourcesContent":["// @hurum/core — Computed\n\n/** Internal representation of a resolved computed field */\nexport interface ComputedNode<T = unknown> {\n readonly name: string\n fn: (state: Record<string, unknown>) => T\n deps: Set<string>\n value: T\n prevValue: T\n}\n\n/**\n * Create a dependency-tracking proxy for the raw state.\n * Records which properties are accessed during computed evaluation.\n */\nexport function createTrackingProxy<T extends Record<string, unknown>>(\n state: T,\n accessed: Set<string>,\n): T {\n return new Proxy(state, {\n get(target, prop, receiver) {\n if (typeof prop === 'string') {\n accessed.add(prop)\n }\n return Reflect.get(target, prop, receiver)\n },\n })\n}\n\n/**\n * Initialize computed nodes from a computed definition map.\n * Returns nodes in topological order for evaluation.\n */\nexport function initializeComputedNodes(\n rawState: Record<string, unknown>,\n computedDef: Record<string, (state: Record<string, unknown>) => unknown> | undefined,\n): ComputedNode[] {\n if (!computedDef) return []\n\n const nodes: ComputedNode[] = []\n const nodesByName = new Map<string, ComputedNode>()\n\n // Evaluate each computed and track dependencies\n for (const [name, fn] of Object.entries(computedDef)) {\n const accessed = new Set<string>()\n const trackingProxy = createTrackingProxy(rawState, accessed)\n const value = fn(trackingProxy)\n\n const node: ComputedNode = {\n name,\n fn,\n deps: accessed,\n value,\n prevValue: value,\n }\n nodes.push(node)\n nodesByName.set(name, node)\n }\n\n // Check for cycles via topological sort\n const sorted = topologicalSort(nodes, nodesByName)\n\n return sorted\n}\n\n/**\n * Topological sort of computed nodes. Throws on cycle detection.\n */\nfunction topologicalSort(\n nodes: ComputedNode[],\n nodesByName: Map<string, ComputedNode>,\n): ComputedNode[] {\n const visited = new Set<string>()\n const visiting = new Set<string>()\n const sorted: ComputedNode[] = []\n\n function visit(node: ComputedNode): void {\n if (visited.has(node.name)) return\n if (visiting.has(node.name)) {\n throw new Error(\n `Circular dependency detected in computed fields: ${node.name}`,\n )\n }\n\n visiting.add(node.name)\n\n // Visit dependencies that are also computed nodes\n for (const dep of node.deps) {\n const depNode = nodesByName.get(dep)\n if (depNode) {\n visit(depNode)\n }\n }\n\n visiting.delete(node.name)\n visited.add(node.name)\n sorted.push(node)\n }\n\n for (const node of nodes) {\n visit(node)\n }\n\n return sorted\n}\n\n/**\n * Re-evaluate computed nodes based on state changes.\n * Returns the new computed values and whether any changed.\n */\nexport function evaluateComputedNodes(\n nodes: ComputedNode[],\n rawState: Record<string, unknown>,\n changedKeys: Set<string>,\n): { values: Record<string, unknown>; changed: boolean } {\n const values: Record<string, unknown> = {}\n let anyChanged = false\n\n for (const node of nodes) {\n // Check if any dependency changed\n let needsReeval = false\n for (const dep of node.deps) {\n if (changedKeys.has(dep)) {\n needsReeval = true\n break\n }\n }\n\n if (needsReeval) {\n // Re-evaluate with fresh tracking\n const accessed = new Set<string>()\n const trackingProxy = createTrackingProxy(rawState, accessed)\n const newValue = node.fn(trackingProxy)\n\n // Update deps\n node.deps = accessed\n\n // Structural equality check\n if (!structuralEqual(node.value, newValue)) {\n node.prevValue = node.value\n node.value = newValue\n anyChanged = true\n changedKeys.add(node.name) // Propagate to dependent computed nodes\n }\n }\n\n values[node.name] = node.value\n }\n\n return { values, changed: anyChanged }\n}\n\n/**\n * Simple structural equality check.\n * Handles primitives, arrays, and plain objects.\n */\nexport function structuralEqual(a: unknown, b: unknown): boolean {\n if (a === b) return true\n if (a === null || b === null) return false\n if (typeof a !== typeof b) return false\n if (typeof a !== 'object') return false\n\n if (Array.isArray(a) && Array.isArray(b)) {\n if (a.length !== b.length) return false\n for (let i = 0; i < a.length; i++) {\n if (!structuralEqual(a[i], b[i])) return false\n }\n return true\n }\n\n if (Array.isArray(a) !== Array.isArray(b)) return false\n\n const keysA = Object.keys(a as object)\n const keysB = Object.keys(b as object)\n if (keysA.length !== keysB.length) return false\n\n for (const key of keysA) {\n if (!structuralEqual((a as Record<string, unknown>)[key], (b as Record<string, unknown>)[key])) return false\n }\n\n return true\n}\n","// @hurum/core/testing — TestStore\n\nimport type { StoreDefinition, StoreCreateOptions, StoreInternalFields, StoreInstance, ResolvedState, SendFn } from '../src/store'\nimport type { IntentDescriptor, PreparedIntent } from '../src/intent'\nimport type { EventInstance } from '../src/events'\nimport { structuralEqual } from '../src/computed'\n\n/** Async version of SendFn for test store — each call flushes microtasks before resolving. */\nexport type TestSendFn<TIntents extends Record<string, unknown> = Record<string, never>> = {\n <TInput>(prepared: PreparedIntent<TInput>): Promise<void>\n <TInput>(intent: IntentDescriptor<TInput>, payload: TInput): Promise<void>\n} & {\n readonly [K in keyof TIntents]: (payload: TIntents[K]) => Promise<void>\n}\n\nexport interface TestStoreInstance<TRawState = unknown, TComputed = unknown, TIntents extends Record<string, unknown> = Record<string, never>> {\n send: TestSendFn<TIntents>\n getState(): ResolvedState<TRawState> & TComputed\n /** Access nested child store instances via scope. */\n readonly scope: StoreInstance<unknown, TRawState, TComputed>['scope']\n assertState(expected: Partial<ResolvedState<TRawState> & TComputed>): void\n assertEvents(expected: EventInstance[]): void\n assertEventSequence(\n expected: Array<{ event: EventInstance; state: Partial<ResolvedState<TRawState> & TComputed> }>,\n ): void\n assertNoRunningExecutors(): void\n}\n\n/**\n * Create a test store for integration testing.\n * Wraps `Store.create()` with testing utilities.\n */\nexport function TestStore<TDeps, TRawState, TComputed, TIntents extends Record<string, unknown> = Record<string, never>>(\n storeDef: StoreDefinition<TDeps, TRawState, TComputed, TIntents>,\n options?: StoreCreateOptions<TRawState, TDeps>,\n): TestStoreInstance<TRawState, TComputed, TIntents> {\n const instance = storeDef.create(options) as StoreInstance<TDeps, TRawState, TComputed, TIntents> & StoreInternalFields\n\n // Create async test send that wraps the real send proxy\n const instanceSend = instance.send as SendFn<TIntents>\n\n const testSendBase = async (...args: unknown[]): Promise<void> => {\n ;(instanceSend as Function)(...args)\n await flushMicrotasks()\n }\n\n const testSend = new Proxy(testSendBase as TestSendFn<TIntents>, {\n get(_target, prop) {\n if (typeof prop !== 'string') return undefined\n // Prevent thenable detection (would break await)\n if (prop === 'then') return undefined\n // Standard function properties\n if (prop === 'bind' || prop === 'call' || prop === 'apply' || prop === 'length' || prop === 'name' || prop === 'prototype')\n return (testSendBase as unknown as Record<string, unknown>)[prop]\n // Delegate to instance.send's proxy for intent lookup\n const shortcut = (instanceSend as unknown as Record<string, unknown>)[prop]\n if (typeof shortcut === 'function') {\n return async (payload: unknown) => {\n ;(shortcut as Function)(payload)\n await flushMicrotasks()\n }\n }\n return undefined\n },\n })\n\n return {\n send: testSend,\n\n getState(): ResolvedState<TRawState> & TComputed {\n return instance.getState()\n },\n\n get scope() {\n return instance.scope\n },\n\n assertState(expected: Partial<ResolvedState<TRawState> & TComputed>): void {\n const state = instance.getState() as Record<string, unknown>\n for (const [key, value] of Object.entries(expected as Record<string, unknown>)) {\n if (!structuralEqual(state[key], value)) {\n throw new Error(\n `State mismatch for key \"${key}\":\\n` +\n ` Expected: ${JSON.stringify(value)}\\n` +\n ` Received: ${JSON.stringify(state[key])}`,\n )\n }\n }\n },\n\n assertEvents(expected: EventInstance[]): void {\n const actual = instance.__eventLog\n if (actual.length !== expected.length) {\n throw new Error(\n `Event count mismatch:\\n` +\n ` Expected ${expected.length} events: [${expected.map((e) => e.type).join(', ')}]\\n` +\n ` Received ${actual.length} events: [${actual.map((e) => e.type).join(', ')}]`,\n )\n }\n for (let i = 0; i < expected.length; i++) {\n const exp = expected[i]!\n const act = actual[i]!\n if (!structuralEqual(exp, act)) {\n throw new Error(\n `Event mismatch at index ${i}:\\n` +\n ` Expected: ${JSON.stringify(exp)}\\n` +\n ` Received: ${JSON.stringify(act)}`,\n )\n }\n }\n },\n\n assertEventSequence(\n expected: Array<{ event: EventInstance; state: Partial<ResolvedState<TRawState> & TComputed> }>,\n ): void {\n const snapshots = instance.__stateSnapshots\n\n for (const { event: expectedEvent, state: expectedState } of expected) {\n // Find the snapshot matching this event\n const snapshot = snapshots.find((s) => structuralEqual(s.event, expectedEvent))\n if (!snapshot) {\n throw new Error(\n `Expected event not found in sequence:\\n` +\n ` ${JSON.stringify(expectedEvent)}`,\n )\n }\n\n for (const [key, value] of Object.entries(expectedState as Record<string, unknown>)) {\n if (!structuralEqual(snapshot.state[key], value)) {\n throw new Error(\n `State mismatch after event \"${expectedEvent.type}\" for key \"${key}\":\\n` +\n ` Expected: ${JSON.stringify(value)}\\n` +\n ` Received: ${JSON.stringify(snapshot.state[key])}`,\n )\n }\n }\n }\n },\n\n assertNoRunningExecutors(): void {\n const count = instance.__runningCount()\n if (count > 0) {\n throw new Error(\n `Expected no running executors, but ${count} are still running`,\n )\n }\n },\n }\n}\n\nasync function flushMicrotasks(): Promise<void> {\n // Flush several rounds of microtasks to ensure all async work completes\n for (let i = 0; i < 10; i++) {\n await new Promise<void>((resolve) => setTimeout(resolve, 0))\n }\n}\n","// @hurum/core — CommandExecutor\n\nimport type { EventCreator, EventInstance } from './events'\n\n// Branding symbols\nconst COMMAND_BRAND = Symbol('hurum/command')\nconst EXECUTOR_BRAND = Symbol('hurum/executor')\n\n/** A branded command type. Commands are identified by their unique symbol. */\nexport interface Command<TInput = unknown> {\n readonly [COMMAND_BRAND]: symbol\n readonly name?: string\n readonly __inputType?: TInput\n}\n\n/** Context provided to executor functions */\nexport interface ExecutorContext<TDeps = unknown, TState = unknown> {\n readonly deps: TDeps\n readonly emit: (event: EventInstance) => void\n readonly getState: () => TState\n readonly signal: AbortSignal\n /** Access nested child store instances. Available when the store has Nested fields. */\n readonly scope: Record<string, unknown>\n}\n\n/** The executor function type */\nexport type ExecutorFn<TInput, TDeps, TState> = (\n command: TInput,\n context: ExecutorContext<TDeps, TState>,\n) => void | Promise<void>\n\n/** An executor registered with a Store.\n * __fn is type-erased (never params) to keep Executor covariant in TInput/TDeps/TState. */\nexport interface Executor<TInput = unknown, TDeps = unknown, TState = unknown> {\n readonly [EXECUTOR_BRAND]: true\n readonly __command: Command<TInput>\n readonly __fn: (command: never, context: never) => void | Promise<void>\n readonly __inputType?: TInput\n readonly __depsType?: TDeps\n readonly __stateType?: TState\n}\n\n/**\n * Create a CommandExecutor pair: [Command, Executor].\n *\n * Optionally provide a name as the first argument for devtools visibility.\n *\n * @example\n * ```ts\n * // With explicit name\n * const [SaveCmd, SaveExec] = CommandExecutor<\n * { purchase: Purchase },\n * { repo: PurchaseRepo },\n * >('SavePurchase', async (command, { deps, emit }) => {\n * const result = await deps.repo.save(command.purchase)\n * result.match(\n * (saved) => emit(PurchaseEvent.saved({ purchase: saved })),\n * (error) => emit(PurchaseEvent.saveFailed({ error })),\n * )\n * })\n *\n * // Without name (fn.name used as fallback for named functions)\n * const [SaveCmd, SaveExec] = CommandExecutor<{ purchase: Purchase }>(\n * async function SavePurchase(command, { emit }) => { ... }\n * )\n * ```\n */\nexport function CommandExecutor<\n TInput,\n TDeps = Record<string, never>,\n TState = unknown,\n>(\n nameOrFn: string | ExecutorFn<TInput, TDeps, TState>,\n maybeFn?: ExecutorFn<TInput, TDeps, TState>,\n): [Command<TInput>, Executor<TInput, TDeps, TState>] {\n const name = typeof nameOrFn === 'string' ? nameOrFn : (nameOrFn.name || undefined)\n const fn = typeof nameOrFn === 'string' ? maybeFn! : nameOrFn\n\n const commandId = Symbol('hurum/command-id')\n const command: Command<TInput> = {\n [COMMAND_BRAND]: commandId,\n ...(name ? { name } : {}),\n }\n const executor: Executor<TInput, TDeps, TState> = {\n [EXECUTOR_BRAND]: true,\n __command: command,\n __fn: fn as Executor['__fn'],\n }\n return [command, executor]\n}\n\n/**\n * Create a passthrough CommandExecutor that simply emits an event with\n * the command payload (optionally transformed).\n *\n * Optionally provide a name as the first argument for devtools visibility.\n *\n * @example\n * ```ts\n * // With name\n * const [IncrCmd, IncrExec] = CommandExecutor.passthrough('Increment', CounterEvent.incremented)\n *\n * // Without name\n * const [IncrCmd, IncrExec] = CommandExecutor.passthrough(CounterEvent.incremented)\n *\n * // With name + transform\n * const [SetCmd, SetExec] = CommandExecutor.passthrough(\n * 'SetUserName',\n * UserEvent.nameChanged,\n * (cmd: { name: string }) => ({ name: cmd.name.trim(), changedAt: Date.now() })\n * )\n * ```\n */\nCommandExecutor.passthrough = function passthrough<\n TType extends string,\n TPayload,\n TInput = TPayload,\n>(\n nameOrEventCreator: string | EventCreator<TType, TPayload>,\n eventCreatorOrTransform?: EventCreator<TType, TPayload> | ((input: TInput) => TPayload),\n maybeTransform?: (input: TInput) => TPayload,\n): [Command<TInput>, Executor<TInput, Record<string, never>, unknown>] {\n let name: string | undefined\n let eventCreator: EventCreator<TType, TPayload>\n let transform: ((input: TInput) => TPayload) | undefined\n\n if (typeof nameOrEventCreator === 'string') {\n name = nameOrEventCreator\n eventCreator = eventCreatorOrTransform as EventCreator<TType, TPayload>\n transform = maybeTransform\n } else {\n eventCreator = nameOrEventCreator\n transform = eventCreatorOrTransform as ((input: TInput) => TPayload) | undefined\n }\n\n const commandId = Symbol('hurum/command-id')\n const command: Command<TInput> = {\n [COMMAND_BRAND]: commandId,\n ...(name ? { name } : {}),\n }\n const executorFn: ExecutorFn<TInput, Record<string, never>, unknown> = (input, { emit }) => {\n const payload = transform ? transform(input) : input as unknown as TPayload\n emit(eventCreator(payload))\n }\n const executor: Executor<TInput, Record<string, never>, unknown> = {\n [EXECUTOR_BRAND]: true,\n __command: command,\n __fn: executorFn as Executor['__fn'],\n }\n return [command, executor]\n} as {\n <TType extends string, TPayload>(\n eventCreator: EventCreator<TType, TPayload>,\n ): [Command<TPayload>, Executor<TPayload, Record<string, never>, unknown>]\n\n <TType extends string, TPayload, TInput>(\n eventCreator: EventCreator<TType, TPayload>,\n transform: (input: TInput) => TPayload,\n ): [Command<TInput>, Executor<TInput, Record<string, never>, unknown>]\n\n <TType extends string, TPayload>(\n name: string,\n eventCreator: EventCreator<TType, TPayload>,\n ): [Command<TPayload>, Executor<TPayload, Record<string, never>, unknown>]\n\n <TType extends string, TPayload, TInput>(\n name: string,\n eventCreator: EventCreator<TType, TPayload>,\n transform: (input: TInput) => TPayload,\n ): [Command<TInput>, Executor<TInput, Record<string, never>, unknown>]\n}\n\n/** Check if a value is a Command */\nexport function isCommand(value: unknown): value is Command {\n return typeof value === 'object' && value !== null && COMMAND_BRAND in value\n}\n\n/** Check if a value is an Executor */\nexport function isExecutor(value: unknown): value is Executor {\n return typeof value === 'object' && value !== null && EXECUTOR_BRAND in value\n}\n\n/** Get the internal command symbol from a Command (for Store matching) */\nexport function getCommandId(command: Command): symbol {\n return command[COMMAND_BRAND]\n}\n\n/** Get the command from an executor */\nexport function getExecutorCommand(executor: Executor): Command {\n return executor.__command\n}\n\n/** Get the executor function (type-erased; callers cast as needed) */\nexport function getExecutorFn(executor: Executor): Executor['__fn'] {\n return executor.__fn\n}\n","// @hurum/core/testing — TestExecutor\n\nimport type { Executor } from '../src/command-executor'\nimport { getExecutorFn } from '../src/command-executor'\nimport type { EventInstance } from '../src/events'\nimport { structuralEqual } from '../src/computed'\n\nexport interface TestExecutorInstance<TInput = unknown> {\n run(input: TInput): Promise<void>\n abort(): void\n assertEmitted(expected: EventInstance[]): void\n readonly emittedEvents: EventInstance[]\n}\n\nexport interface TestExecutorOptions {\n deps?: Record<string, unknown>\n state?: Record<string, unknown>\n}\n\n/**\n * Create a test executor for unit testing.\n * Runs the executor function in isolation with mock context.\n */\nexport function TestExecutor<TInput>(\n executor: Executor<TInput>,\n options?: TestExecutorOptions,\n): TestExecutorInstance<TInput> {\n const fn = getExecutorFn(executor)\n const emittedEvents: EventInstance[] = []\n const abortController = new AbortController()\n\n const state = options?.state ?? {}\n\n function emit(event: EventInstance): void {\n emittedEvents.push(event)\n }\n\n return {\n async run(input: TInput): Promise<void> {\n const context = {\n deps: options?.deps ?? {},\n emit,\n getState: () => state,\n signal: abortController.signal,\n scope: {},\n }\n\n const result = (fn as (input: TInput, ctx: typeof context) => void | Promise<void>)(input, context)\n if (result instanceof Promise) {\n await result\n }\n },\n\n abort(): void {\n abortController.abort()\n },\n\n assertEmitted(expected: EventInstance[]): void {\n if (emittedEvents.length !== expected.length) {\n throw new Error(\n `Emitted event count mismatch:\\n` +\n ` Expected ${expected.length} events: [${expected.map((e) => e.type).join(', ')}]\\n` +\n ` Received ${emittedEvents.length} events: [${emittedEvents.map((e) => e.type).join(', ')}]`,\n )\n }\n for (let i = 0; i < expected.length; i++) {\n const exp = expected[i]!\n const act = emittedEvents[i]!\n if (!structuralEqual(exp, act)) {\n throw new Error(\n `Emitted event mismatch at index ${i}:\\n` +\n ` Expected: ${JSON.stringify(exp)}\\n` +\n ` Received: ${JSON.stringify(act)}`,\n )\n }\n }\n },\n\n get emittedEvents() {\n return emittedEvents\n },\n }\n}\n","// @hurum/core/testing — TestReducer\n\nimport type { StoreDefinition, ResolvedState } from '../src/store'\nimport type { EventInstance } from '../src/events'\n\nexport interface TestReducerInstance<TRawState = unknown> {\n apply(state: ResolvedState<TRawState>, event: EventInstance): ResolvedState<TRawState>\n}\n\n/**\n * Create a test reducer for unit testing on handlers.\n * Applies an event to a given state and returns the new state.\n */\nexport function TestReducer<TDeps, TRawState, TComputed>(\n storeDef: StoreDefinition<TDeps, TRawState, TComputed>,\n): TestReducerInstance<TRawState> {\n const config = storeDef.__config\n\n return {\n apply(state: ResolvedState<TRawState>, event: EventInstance): ResolvedState<TRawState> {\n const handler = config.on?.[event.type]\n if (!handler) {\n return state\n }\n\n const { type: _type, ...payload } = event\n return handler(\n state as Record<string, unknown>,\n payload as Record<string, unknown>,\n ) as ResolvedState<TRawState>\n },\n }\n}\n","// @hurum/core/testing — TestComputed\n\nimport type { StoreDefinition } from '../src/store'\n\nexport interface TestComputedInstance<TResult = unknown> {\n evaluate(rawState: Record<string, unknown>): TResult\n}\n\n/**\n * Create a test computed for unit testing computed fields.\n * Evaluates a single computed field against a given raw state.\n */\nexport function TestComputed<TDeps, TRawState, TComputed>(\n storeDef: StoreDefinition<TDeps, TRawState, TComputed>,\n fieldName: string & keyof TComputed,\n): TestComputedInstance<TComputed[typeof fieldName]> {\n const config = storeDef.__config\n const computedDef = config.computed\n\n if (!computedDef) {\n throw new Error(\n `[hurum] Store has no computed definition. Cannot test computed field \"${fieldName}\".`,\n )\n }\n\n return {\n evaluate(rawState: Record<string, unknown>): TComputed[typeof fieldName] {\n const fn = computedDef[fieldName]\n if (!fn || typeof fn !== 'function') {\n throw new Error(\n `[hurum] Computed field \"${fieldName}\" not found in store.`,\n )\n }\n return fn(rawState) as TComputed[typeof fieldName]\n },\n }\n}\n","// @hurum/core/testing — TestIntent\n\nimport type { IntentDescriptor, IntentMode } from '../src/intent'\nimport type { Command } from '../src/command-executor'\n\nexport interface TestIntentInstance {\n readonly commands: ReadonlyArray<Command>\n readonly mode: IntentMode\n}\n\n/**\n * Create a test intent for inspecting intent configuration.\n * Returns the commands and execution mode of an intent descriptor.\n */\nexport function TestIntent(intent: IntentDescriptor): TestIntentInstance {\n return {\n commands: intent.commands,\n mode: intent.mode,\n }\n}\n"]}
|
|
@@ -0,0 +1,73 @@
|
|
|
1
|
+
import { P as PreparedIntent, l as IntentDescriptor, S as StoreDefinition, A as StoreCreateOptions, R as ResolvedState, a as StoreInstance, E as EventInstance, i as Executor, b as Command, n as IntentMode } from './store-BrPM22fc.cjs';
|
|
2
|
+
|
|
3
|
+
/** Async version of SendFn for test store — each call flushes microtasks before resolving. */
|
|
4
|
+
type TestSendFn<TIntents extends Record<string, unknown> = Record<string, never>> = {
|
|
5
|
+
<TInput>(prepared: PreparedIntent<TInput>): Promise<void>;
|
|
6
|
+
<TInput>(intent: IntentDescriptor<TInput>, payload: TInput): Promise<void>;
|
|
7
|
+
} & {
|
|
8
|
+
readonly [K in keyof TIntents]: (payload: TIntents[K]) => Promise<void>;
|
|
9
|
+
};
|
|
10
|
+
interface TestStoreInstance<TRawState = unknown, TComputed = unknown, TIntents extends Record<string, unknown> = Record<string, never>> {
|
|
11
|
+
send: TestSendFn<TIntents>;
|
|
12
|
+
getState(): ResolvedState<TRawState> & TComputed;
|
|
13
|
+
/** Access nested child store instances via scope. */
|
|
14
|
+
readonly scope: StoreInstance<unknown, TRawState, TComputed>['scope'];
|
|
15
|
+
assertState(expected: Partial<ResolvedState<TRawState> & TComputed>): void;
|
|
16
|
+
assertEvents(expected: EventInstance[]): void;
|
|
17
|
+
assertEventSequence(expected: Array<{
|
|
18
|
+
event: EventInstance;
|
|
19
|
+
state: Partial<ResolvedState<TRawState> & TComputed>;
|
|
20
|
+
}>): void;
|
|
21
|
+
assertNoRunningExecutors(): void;
|
|
22
|
+
}
|
|
23
|
+
/**
|
|
24
|
+
* Create a test store for integration testing.
|
|
25
|
+
* Wraps `Store.create()` with testing utilities.
|
|
26
|
+
*/
|
|
27
|
+
declare function TestStore<TDeps, TRawState, TComputed, TIntents extends Record<string, unknown> = Record<string, never>>(storeDef: StoreDefinition<TDeps, TRawState, TComputed, TIntents>, options?: StoreCreateOptions<TRawState, TDeps>): TestStoreInstance<TRawState, TComputed, TIntents>;
|
|
28
|
+
|
|
29
|
+
interface TestExecutorInstance<TInput = unknown> {
|
|
30
|
+
run(input: TInput): Promise<void>;
|
|
31
|
+
abort(): void;
|
|
32
|
+
assertEmitted(expected: EventInstance[]): void;
|
|
33
|
+
readonly emittedEvents: EventInstance[];
|
|
34
|
+
}
|
|
35
|
+
interface TestExecutorOptions {
|
|
36
|
+
deps?: Record<string, unknown>;
|
|
37
|
+
state?: Record<string, unknown>;
|
|
38
|
+
}
|
|
39
|
+
/**
|
|
40
|
+
* Create a test executor for unit testing.
|
|
41
|
+
* Runs the executor function in isolation with mock context.
|
|
42
|
+
*/
|
|
43
|
+
declare function TestExecutor<TInput>(executor: Executor<TInput>, options?: TestExecutorOptions): TestExecutorInstance<TInput>;
|
|
44
|
+
|
|
45
|
+
interface TestReducerInstance<TRawState = unknown> {
|
|
46
|
+
apply(state: ResolvedState<TRawState>, event: EventInstance): ResolvedState<TRawState>;
|
|
47
|
+
}
|
|
48
|
+
/**
|
|
49
|
+
* Create a test reducer for unit testing on handlers.
|
|
50
|
+
* Applies an event to a given state and returns the new state.
|
|
51
|
+
*/
|
|
52
|
+
declare function TestReducer<TDeps, TRawState, TComputed>(storeDef: StoreDefinition<TDeps, TRawState, TComputed>): TestReducerInstance<TRawState>;
|
|
53
|
+
|
|
54
|
+
interface TestComputedInstance<TResult = unknown> {
|
|
55
|
+
evaluate(rawState: Record<string, unknown>): TResult;
|
|
56
|
+
}
|
|
57
|
+
/**
|
|
58
|
+
* Create a test computed for unit testing computed fields.
|
|
59
|
+
* Evaluates a single computed field against a given raw state.
|
|
60
|
+
*/
|
|
61
|
+
declare function TestComputed<TDeps, TRawState, TComputed>(storeDef: StoreDefinition<TDeps, TRawState, TComputed>, fieldName: string & keyof TComputed): TestComputedInstance<TComputed[typeof fieldName]>;
|
|
62
|
+
|
|
63
|
+
interface TestIntentInstance {
|
|
64
|
+
readonly commands: ReadonlyArray<Command>;
|
|
65
|
+
readonly mode: IntentMode;
|
|
66
|
+
}
|
|
67
|
+
/**
|
|
68
|
+
* Create a test intent for inspecting intent configuration.
|
|
69
|
+
* Returns the commands and execution mode of an intent descriptor.
|
|
70
|
+
*/
|
|
71
|
+
declare function TestIntent(intent: IntentDescriptor): TestIntentInstance;
|
|
72
|
+
|
|
73
|
+
export { TestComputed, TestExecutor, TestIntent, TestReducer, type TestSendFn, TestStore, type TestStoreInstance };
|
|
@@ -0,0 +1,73 @@
|
|
|
1
|
+
import { P as PreparedIntent, l as IntentDescriptor, S as StoreDefinition, A as StoreCreateOptions, R as ResolvedState, a as StoreInstance, E as EventInstance, i as Executor, b as Command, n as IntentMode } from './store-BrPM22fc.js';
|
|
2
|
+
|
|
3
|
+
/** Async version of SendFn for test store — each call flushes microtasks before resolving. */
|
|
4
|
+
type TestSendFn<TIntents extends Record<string, unknown> = Record<string, never>> = {
|
|
5
|
+
<TInput>(prepared: PreparedIntent<TInput>): Promise<void>;
|
|
6
|
+
<TInput>(intent: IntentDescriptor<TInput>, payload: TInput): Promise<void>;
|
|
7
|
+
} & {
|
|
8
|
+
readonly [K in keyof TIntents]: (payload: TIntents[K]) => Promise<void>;
|
|
9
|
+
};
|
|
10
|
+
interface TestStoreInstance<TRawState = unknown, TComputed = unknown, TIntents extends Record<string, unknown> = Record<string, never>> {
|
|
11
|
+
send: TestSendFn<TIntents>;
|
|
12
|
+
getState(): ResolvedState<TRawState> & TComputed;
|
|
13
|
+
/** Access nested child store instances via scope. */
|
|
14
|
+
readonly scope: StoreInstance<unknown, TRawState, TComputed>['scope'];
|
|
15
|
+
assertState(expected: Partial<ResolvedState<TRawState> & TComputed>): void;
|
|
16
|
+
assertEvents(expected: EventInstance[]): void;
|
|
17
|
+
assertEventSequence(expected: Array<{
|
|
18
|
+
event: EventInstance;
|
|
19
|
+
state: Partial<ResolvedState<TRawState> & TComputed>;
|
|
20
|
+
}>): void;
|
|
21
|
+
assertNoRunningExecutors(): void;
|
|
22
|
+
}
|
|
23
|
+
/**
|
|
24
|
+
* Create a test store for integration testing.
|
|
25
|
+
* Wraps `Store.create()` with testing utilities.
|
|
26
|
+
*/
|
|
27
|
+
declare function TestStore<TDeps, TRawState, TComputed, TIntents extends Record<string, unknown> = Record<string, never>>(storeDef: StoreDefinition<TDeps, TRawState, TComputed, TIntents>, options?: StoreCreateOptions<TRawState, TDeps>): TestStoreInstance<TRawState, TComputed, TIntents>;
|
|
28
|
+
|
|
29
|
+
interface TestExecutorInstance<TInput = unknown> {
|
|
30
|
+
run(input: TInput): Promise<void>;
|
|
31
|
+
abort(): void;
|
|
32
|
+
assertEmitted(expected: EventInstance[]): void;
|
|
33
|
+
readonly emittedEvents: EventInstance[];
|
|
34
|
+
}
|
|
35
|
+
interface TestExecutorOptions {
|
|
36
|
+
deps?: Record<string, unknown>;
|
|
37
|
+
state?: Record<string, unknown>;
|
|
38
|
+
}
|
|
39
|
+
/**
|
|
40
|
+
* Create a test executor for unit testing.
|
|
41
|
+
* Runs the executor function in isolation with mock context.
|
|
42
|
+
*/
|
|
43
|
+
declare function TestExecutor<TInput>(executor: Executor<TInput>, options?: TestExecutorOptions): TestExecutorInstance<TInput>;
|
|
44
|
+
|
|
45
|
+
interface TestReducerInstance<TRawState = unknown> {
|
|
46
|
+
apply(state: ResolvedState<TRawState>, event: EventInstance): ResolvedState<TRawState>;
|
|
47
|
+
}
|
|
48
|
+
/**
|
|
49
|
+
* Create a test reducer for unit testing on handlers.
|
|
50
|
+
* Applies an event to a given state and returns the new state.
|
|
51
|
+
*/
|
|
52
|
+
declare function TestReducer<TDeps, TRawState, TComputed>(storeDef: StoreDefinition<TDeps, TRawState, TComputed>): TestReducerInstance<TRawState>;
|
|
53
|
+
|
|
54
|
+
interface TestComputedInstance<TResult = unknown> {
|
|
55
|
+
evaluate(rawState: Record<string, unknown>): TResult;
|
|
56
|
+
}
|
|
57
|
+
/**
|
|
58
|
+
* Create a test computed for unit testing computed fields.
|
|
59
|
+
* Evaluates a single computed field against a given raw state.
|
|
60
|
+
*/
|
|
61
|
+
declare function TestComputed<TDeps, TRawState, TComputed>(storeDef: StoreDefinition<TDeps, TRawState, TComputed>, fieldName: string & keyof TComputed): TestComputedInstance<TComputed[typeof fieldName]>;
|
|
62
|
+
|
|
63
|
+
interface TestIntentInstance {
|
|
64
|
+
readonly commands: ReadonlyArray<Command>;
|
|
65
|
+
readonly mode: IntentMode;
|
|
66
|
+
}
|
|
67
|
+
/**
|
|
68
|
+
* Create a test intent for inspecting intent configuration.
|
|
69
|
+
* Returns the commands and execution mode of an intent descriptor.
|
|
70
|
+
*/
|
|
71
|
+
declare function TestIntent(intent: IntentDescriptor): TestIntentInstance;
|
|
72
|
+
|
|
73
|
+
export { TestComputed, TestExecutor, TestIntent, TestReducer, type TestSendFn, TestStore, type TestStoreInstance };
|
package/dist/testing.js
ADDED
|
@@ -0,0 +1,231 @@
|
|
|
1
|
+
// src/computed.ts
|
|
2
|
+
function structuralEqual(a, b) {
|
|
3
|
+
if (a === b) return true;
|
|
4
|
+
if (a === null || b === null) return false;
|
|
5
|
+
if (typeof a !== typeof b) return false;
|
|
6
|
+
if (typeof a !== "object") return false;
|
|
7
|
+
if (Array.isArray(a) && Array.isArray(b)) {
|
|
8
|
+
if (a.length !== b.length) return false;
|
|
9
|
+
for (let i = 0; i < a.length; i++) {
|
|
10
|
+
if (!structuralEqual(a[i], b[i])) return false;
|
|
11
|
+
}
|
|
12
|
+
return true;
|
|
13
|
+
}
|
|
14
|
+
if (Array.isArray(a) !== Array.isArray(b)) return false;
|
|
15
|
+
const keysA = Object.keys(a);
|
|
16
|
+
const keysB = Object.keys(b);
|
|
17
|
+
if (keysA.length !== keysB.length) return false;
|
|
18
|
+
for (const key of keysA) {
|
|
19
|
+
if (!structuralEqual(a[key], b[key])) return false;
|
|
20
|
+
}
|
|
21
|
+
return true;
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
// testing/test-store.ts
|
|
25
|
+
function TestStore(storeDef, options) {
|
|
26
|
+
const instance = storeDef.create(options);
|
|
27
|
+
const instanceSend = instance.send;
|
|
28
|
+
const testSendBase = async (...args) => {
|
|
29
|
+
instanceSend(...args);
|
|
30
|
+
await flushMicrotasks();
|
|
31
|
+
};
|
|
32
|
+
const testSend = new Proxy(testSendBase, {
|
|
33
|
+
get(_target, prop) {
|
|
34
|
+
if (typeof prop !== "string") return void 0;
|
|
35
|
+
if (prop === "then") return void 0;
|
|
36
|
+
if (prop === "bind" || prop === "call" || prop === "apply" || prop === "length" || prop === "name" || prop === "prototype")
|
|
37
|
+
return testSendBase[prop];
|
|
38
|
+
const shortcut = instanceSend[prop];
|
|
39
|
+
if (typeof shortcut === "function") {
|
|
40
|
+
return async (payload) => {
|
|
41
|
+
shortcut(payload);
|
|
42
|
+
await flushMicrotasks();
|
|
43
|
+
};
|
|
44
|
+
}
|
|
45
|
+
return void 0;
|
|
46
|
+
}
|
|
47
|
+
});
|
|
48
|
+
return {
|
|
49
|
+
send: testSend,
|
|
50
|
+
getState() {
|
|
51
|
+
return instance.getState();
|
|
52
|
+
},
|
|
53
|
+
get scope() {
|
|
54
|
+
return instance.scope;
|
|
55
|
+
},
|
|
56
|
+
assertState(expected) {
|
|
57
|
+
const state = instance.getState();
|
|
58
|
+
for (const [key, value] of Object.entries(expected)) {
|
|
59
|
+
if (!structuralEqual(state[key], value)) {
|
|
60
|
+
throw new Error(
|
|
61
|
+
`State mismatch for key "${key}":
|
|
62
|
+
Expected: ${JSON.stringify(value)}
|
|
63
|
+
Received: ${JSON.stringify(state[key])}`
|
|
64
|
+
);
|
|
65
|
+
}
|
|
66
|
+
}
|
|
67
|
+
},
|
|
68
|
+
assertEvents(expected) {
|
|
69
|
+
const actual = instance.__eventLog;
|
|
70
|
+
if (actual.length !== expected.length) {
|
|
71
|
+
throw new Error(
|
|
72
|
+
`Event count mismatch:
|
|
73
|
+
Expected ${expected.length} events: [${expected.map((e) => e.type).join(", ")}]
|
|
74
|
+
Received ${actual.length} events: [${actual.map((e) => e.type).join(", ")}]`
|
|
75
|
+
);
|
|
76
|
+
}
|
|
77
|
+
for (let i = 0; i < expected.length; i++) {
|
|
78
|
+
const exp = expected[i];
|
|
79
|
+
const act = actual[i];
|
|
80
|
+
if (!structuralEqual(exp, act)) {
|
|
81
|
+
throw new Error(
|
|
82
|
+
`Event mismatch at index ${i}:
|
|
83
|
+
Expected: ${JSON.stringify(exp)}
|
|
84
|
+
Received: ${JSON.stringify(act)}`
|
|
85
|
+
);
|
|
86
|
+
}
|
|
87
|
+
}
|
|
88
|
+
},
|
|
89
|
+
assertEventSequence(expected) {
|
|
90
|
+
const snapshots = instance.__stateSnapshots;
|
|
91
|
+
for (const { event: expectedEvent, state: expectedState } of expected) {
|
|
92
|
+
const snapshot = snapshots.find((s) => structuralEqual(s.event, expectedEvent));
|
|
93
|
+
if (!snapshot) {
|
|
94
|
+
throw new Error(
|
|
95
|
+
`Expected event not found in sequence:
|
|
96
|
+
${JSON.stringify(expectedEvent)}`
|
|
97
|
+
);
|
|
98
|
+
}
|
|
99
|
+
for (const [key, value] of Object.entries(expectedState)) {
|
|
100
|
+
if (!structuralEqual(snapshot.state[key], value)) {
|
|
101
|
+
throw new Error(
|
|
102
|
+
`State mismatch after event "${expectedEvent.type}" for key "${key}":
|
|
103
|
+
Expected: ${JSON.stringify(value)}
|
|
104
|
+
Received: ${JSON.stringify(snapshot.state[key])}`
|
|
105
|
+
);
|
|
106
|
+
}
|
|
107
|
+
}
|
|
108
|
+
}
|
|
109
|
+
},
|
|
110
|
+
assertNoRunningExecutors() {
|
|
111
|
+
const count = instance.__runningCount();
|
|
112
|
+
if (count > 0) {
|
|
113
|
+
throw new Error(
|
|
114
|
+
`Expected no running executors, but ${count} are still running`
|
|
115
|
+
);
|
|
116
|
+
}
|
|
117
|
+
}
|
|
118
|
+
};
|
|
119
|
+
}
|
|
120
|
+
async function flushMicrotasks() {
|
|
121
|
+
for (let i = 0; i < 10; i++) {
|
|
122
|
+
await new Promise((resolve) => setTimeout(resolve, 0));
|
|
123
|
+
}
|
|
124
|
+
}
|
|
125
|
+
function getExecutorFn(executor) {
|
|
126
|
+
return executor.__fn;
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
// testing/test-executor.ts
|
|
130
|
+
function TestExecutor(executor, options) {
|
|
131
|
+
const fn = getExecutorFn(executor);
|
|
132
|
+
const emittedEvents = [];
|
|
133
|
+
const abortController = new AbortController();
|
|
134
|
+
const state = options?.state ?? {};
|
|
135
|
+
function emit(event) {
|
|
136
|
+
emittedEvents.push(event);
|
|
137
|
+
}
|
|
138
|
+
return {
|
|
139
|
+
async run(input) {
|
|
140
|
+
const context = {
|
|
141
|
+
deps: options?.deps ?? {},
|
|
142
|
+
emit,
|
|
143
|
+
getState: () => state,
|
|
144
|
+
signal: abortController.signal,
|
|
145
|
+
scope: {}
|
|
146
|
+
};
|
|
147
|
+
const result = fn(input, context);
|
|
148
|
+
if (result instanceof Promise) {
|
|
149
|
+
await result;
|
|
150
|
+
}
|
|
151
|
+
},
|
|
152
|
+
abort() {
|
|
153
|
+
abortController.abort();
|
|
154
|
+
},
|
|
155
|
+
assertEmitted(expected) {
|
|
156
|
+
if (emittedEvents.length !== expected.length) {
|
|
157
|
+
throw new Error(
|
|
158
|
+
`Emitted event count mismatch:
|
|
159
|
+
Expected ${expected.length} events: [${expected.map((e) => e.type).join(", ")}]
|
|
160
|
+
Received ${emittedEvents.length} events: [${emittedEvents.map((e) => e.type).join(", ")}]`
|
|
161
|
+
);
|
|
162
|
+
}
|
|
163
|
+
for (let i = 0; i < expected.length; i++) {
|
|
164
|
+
const exp = expected[i];
|
|
165
|
+
const act = emittedEvents[i];
|
|
166
|
+
if (!structuralEqual(exp, act)) {
|
|
167
|
+
throw new Error(
|
|
168
|
+
`Emitted event mismatch at index ${i}:
|
|
169
|
+
Expected: ${JSON.stringify(exp)}
|
|
170
|
+
Received: ${JSON.stringify(act)}`
|
|
171
|
+
);
|
|
172
|
+
}
|
|
173
|
+
}
|
|
174
|
+
},
|
|
175
|
+
get emittedEvents() {
|
|
176
|
+
return emittedEvents;
|
|
177
|
+
}
|
|
178
|
+
};
|
|
179
|
+
}
|
|
180
|
+
|
|
181
|
+
// testing/test-reducer.ts
|
|
182
|
+
function TestReducer(storeDef) {
|
|
183
|
+
const config = storeDef.__config;
|
|
184
|
+
return {
|
|
185
|
+
apply(state, event) {
|
|
186
|
+
const handler = config.on?.[event.type];
|
|
187
|
+
if (!handler) {
|
|
188
|
+
return state;
|
|
189
|
+
}
|
|
190
|
+
const { type: _type, ...payload } = event;
|
|
191
|
+
return handler(
|
|
192
|
+
state,
|
|
193
|
+
payload
|
|
194
|
+
);
|
|
195
|
+
}
|
|
196
|
+
};
|
|
197
|
+
}
|
|
198
|
+
|
|
199
|
+
// testing/test-computed.ts
|
|
200
|
+
function TestComputed(storeDef, fieldName) {
|
|
201
|
+
const config = storeDef.__config;
|
|
202
|
+
const computedDef = config.computed;
|
|
203
|
+
if (!computedDef) {
|
|
204
|
+
throw new Error(
|
|
205
|
+
`[hurum] Store has no computed definition. Cannot test computed field "${fieldName}".`
|
|
206
|
+
);
|
|
207
|
+
}
|
|
208
|
+
return {
|
|
209
|
+
evaluate(rawState) {
|
|
210
|
+
const fn = computedDef[fieldName];
|
|
211
|
+
if (!fn || typeof fn !== "function") {
|
|
212
|
+
throw new Error(
|
|
213
|
+
`[hurum] Computed field "${fieldName}" not found in store.`
|
|
214
|
+
);
|
|
215
|
+
}
|
|
216
|
+
return fn(rawState);
|
|
217
|
+
}
|
|
218
|
+
};
|
|
219
|
+
}
|
|
220
|
+
|
|
221
|
+
// testing/test-intent.ts
|
|
222
|
+
function TestIntent(intent) {
|
|
223
|
+
return {
|
|
224
|
+
commands: intent.commands,
|
|
225
|
+
mode: intent.mode
|
|
226
|
+
};
|
|
227
|
+
}
|
|
228
|
+
|
|
229
|
+
export { TestComputed, TestExecutor, TestIntent, TestReducer, TestStore };
|
|
230
|
+
//# sourceMappingURL=testing.js.map
|
|
231
|
+
//# sourceMappingURL=testing.js.map
|