@directive-run/claude-plugin 1.16.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.
- package/.claude-plugin/plugin.json +6 -0
- package/LICENSE +26 -0
- package/README.md +113 -0
- package/dist/index.cjs +2 -0
- package/dist/index.cjs.map +1 -0
- package/dist/index.d.cts +66 -0
- package/dist/index.d.ts +66 -0
- package/dist/index.js +2 -0
- package/dist/index.js.map +1 -0
- package/package.json +64 -0
- package/skills/README.md +9 -0
- package/skills/building-ai-agents/SKILL.md +414 -0
- package/skills/building-ai-agents/ai-adapters.md +257 -0
- package/skills/building-ai-agents/ai-agents-streaming.md +307 -0
- package/skills/building-ai-agents/ai-communication.md +304 -0
- package/skills/building-ai-agents/api-skeleton.md +5 -0
- package/skills/building-ai-agents/examples.md +849 -0
- package/skills/building-ai-orchestrators/SKILL.md +401 -0
- package/skills/building-ai-orchestrators/ai-multi-agent.md +375 -0
- package/skills/building-ai-orchestrators/ai-orchestrator.md +466 -0
- package/skills/building-ai-orchestrators/ai-tasks.md +268 -0
- package/skills/building-ai-orchestrators/api-skeleton.md +5 -0
- package/skills/building-ai-orchestrators/examples.md +1609 -0
- package/skills/building-directive-systems/SKILL.md +499 -0
- package/skills/building-directive-systems/api-skeleton.md +5 -0
- package/skills/building-directive-systems/examples.md +4149 -0
- package/skills/building-directive-systems/multi-module.md +327 -0
- package/skills/building-directive-systems/plugins.md +361 -0
- package/skills/building-directive-systems/react-adapter.md +376 -0
- package/skills/building-directive-systems/system-api.md +463 -0
- package/skills/getting-started-with-directive/SKILL.md +198 -0
- package/skills/getting-started-with-directive/api-skeleton.md +5 -0
- package/skills/getting-started-with-directive/core-patterns.md +240 -0
- package/skills/getting-started-with-directive/examples.md +87 -0
- package/skills/getting-started-with-directive/sitemap.md +202 -0
- package/skills/hardening-ai-systems/SKILL.md +377 -0
- package/skills/hardening-ai-systems/ai-budget-resilience.md +408 -0
- package/skills/hardening-ai-systems/ai-guardrails-memory.md +370 -0
- package/skills/hardening-ai-systems/ai-security.md +400 -0
- package/skills/hardening-ai-systems/api-skeleton.md +5 -0
- package/skills/hardening-ai-systems/examples.md +628 -0
- package/skills/migrating-to-directive/SKILL.md +249 -0
- package/skills/migrating-to-directive/anti-patterns.md +382 -0
- package/skills/migrating-to-directive/api-skeleton.md +5 -0
- package/skills/migrating-to-directive/core-patterns.md +240 -0
- package/skills/migrating-to-directive/examples.md +511 -0
- package/skills/migrating-to-directive/schema-types.md +273 -0
- package/skills/reviewing-directive-code/SKILL.md +232 -0
- package/skills/reviewing-directive-code/anti-patterns.md +382 -0
- package/skills/reviewing-directive-code/api-skeleton.md +5 -0
- package/skills/reviewing-directive-code/core-patterns.md +240 -0
- package/skills/reviewing-directive-code/examples.md +463 -0
- package/skills/reviewing-directive-code/naming.md +321 -0
- package/skills/scaffolding-directive-modules/SKILL.md +218 -0
- package/skills/scaffolding-directive-modules/api-skeleton.md +5 -0
- package/skills/scaffolding-directive-modules/core-patterns.md +240 -0
- package/skills/scaffolding-directive-modules/examples.md +1386 -0
- package/skills/scaffolding-directive-modules/naming.md +321 -0
- package/skills/scaffolding-directive-modules/schema-types.md +273 -0
- package/skills/testing-ai-systems/SKILL.md +398 -0
- package/skills/testing-ai-systems/ai-debug-observability.md +329 -0
- package/skills/testing-ai-systems/ai-mcp-rag.md +293 -0
- package/skills/testing-ai-systems/ai-testing-evals.md +485 -0
- package/skills/testing-ai-systems/api-skeleton.md +5 -0
- package/skills/testing-ai-systems/examples.md +1261 -0
- package/skills/testing-directive-code/SKILL.md +479 -0
- package/skills/testing-directive-code/api-skeleton.md +5 -0
- package/skills/testing-directive-code/examples.md +156 -0
- package/skills/testing-directive-code/history.md +351 -0
- package/skills/testing-directive-code/testing.md +412 -0
- package/skills/writing-directive-constraints/SKILL.md +486 -0
- package/skills/writing-directive-constraints/api-skeleton.md +5 -0
- package/skills/writing-directive-constraints/constraints.md +274 -0
- package/skills/writing-directive-constraints/error-boundaries.md +330 -0
- package/skills/writing-directive-constraints/examples.md +1361 -0
- package/skills/writing-directive-constraints/resolvers.md +367 -0
- package/skills/writing-directive-modules/SKILL.md +342 -0
- package/skills/writing-directive-modules/anti-patterns.md +382 -0
- package/skills/writing-directive-modules/api-skeleton.md +5 -0
- package/skills/writing-directive-modules/core-patterns.md +240 -0
- package/skills/writing-directive-modules/examples.md +1639 -0
- package/skills/writing-directive-modules/naming.md +321 -0
- package/skills/writing-directive-modules/schema-types.md +273 -0
|
@@ -0,0 +1,412 @@
|
|
|
1
|
+
# Testing
|
|
2
|
+
|
|
3
|
+
> Covers `@directive-run/core/testing` — `createTestSystem`, mock resolvers, assertion helpers, coverage tracker, test observer.
|
|
4
|
+
|
|
5
|
+
Testing utilities for Directive modules and systems. Import from `@directive-run/core/testing`.
|
|
6
|
+
|
|
7
|
+
## Decision tree
|
|
8
|
+
|
|
9
|
+
```
|
|
10
|
+
What are you testing?
|
|
11
|
+
├── A single module in isolation → createTestSystem({ module })
|
|
12
|
+
├── Multiple modules together → createTestSystem({ modules: {...} })
|
|
13
|
+
├── A constraint fires correctly → set facts, system.assertRequirement(type)
|
|
14
|
+
├── A resolver mutates facts → mocks.resolvers[TYPE], dispatch, await waitForIdle, assertFactSet
|
|
15
|
+
├── A derivation computes correctly → set facts, read system.derive.x
|
|
16
|
+
├── Async settling with retry/timeouts → await system.waitForIdle() (uses real timers)
|
|
17
|
+
├── Coverage tracking → createCoverageTracker(system)
|
|
18
|
+
└── Observation event capture → createTestObserver(system)
|
|
19
|
+
```
|
|
20
|
+
|
|
21
|
+
## Creating a test system
|
|
22
|
+
|
|
23
|
+
`createTestSystem` takes an options OBJECT — the same shape as `createSystem` — and adds an optional `mocks.resolvers` map keyed by requirement type. There is no separate `createTestSystemFromModules` factory; the same overload handles both single-module (`module:`) and namespaced (`modules:`) systems.
|
|
24
|
+
|
|
25
|
+
```typescript
|
|
26
|
+
import { createTestSystem } from "@directive-run/core/testing";
|
|
27
|
+
|
|
28
|
+
// Single module
|
|
29
|
+
const system = createTestSystem({
|
|
30
|
+
module: counterModule,
|
|
31
|
+
});
|
|
32
|
+
|
|
33
|
+
// Namespaced (multi-module)
|
|
34
|
+
const system = createTestSystem({
|
|
35
|
+
modules: { auth: authModule, cart: cartModule },
|
|
36
|
+
});
|
|
37
|
+
|
|
38
|
+
// With mocks — keyed by requirement type, value is a MockResolverOptions
|
|
39
|
+
const system = createTestSystem({
|
|
40
|
+
module: userModule,
|
|
41
|
+
mocks: {
|
|
42
|
+
resolvers: {
|
|
43
|
+
FETCH_USER: {
|
|
44
|
+
resolve: (req, context) => {
|
|
45
|
+
context.facts.user = { id: req.userId, name: "Test User" };
|
|
46
|
+
},
|
|
47
|
+
},
|
|
48
|
+
},
|
|
49
|
+
},
|
|
50
|
+
});
|
|
51
|
+
```
|
|
52
|
+
|
|
53
|
+
A `TestSystem` extends the production `SingleModuleSystem<S>` / `NamespacedSystem<Modules>` with extra fields for observability:
|
|
54
|
+
|
|
55
|
+
```typescript
|
|
56
|
+
system.waitForIdle(maxWait?); // wait for all in-flight resolvers (default 5000ms; throws on timeout)
|
|
57
|
+
system.eventHistory; // every dispatched event
|
|
58
|
+
system.resolverCalls; // Map<requirementType, Requirement[]>
|
|
59
|
+
system.allRequirements; // every requirement generated (both resolved and pending)
|
|
60
|
+
system.getFactsHistory(); // FactChangeRecord[] since start / last reset
|
|
61
|
+
system.resetFactsHistory();
|
|
62
|
+
```
|
|
63
|
+
|
|
64
|
+
## Assertions are METHODS on the test system
|
|
65
|
+
|
|
66
|
+
Assertion helpers live on the test-system instance, not as top-level imports. `assertFact` / `assertDerivation` / `assertRequirement` as top-level imports do not exist.
|
|
67
|
+
|
|
68
|
+
```typescript
|
|
69
|
+
system.assertRequirement("FETCH_USER"); // a requirement of this type was generated
|
|
70
|
+
system.assertResolverCalled("FETCH_USER", 1); // the resolver was called N times
|
|
71
|
+
system.assertFactSet("phase", "loaded"); // the fact was set to a specific value
|
|
72
|
+
system.assertFactChanges("count", 3); // the fact was changed N times
|
|
73
|
+
```
|
|
74
|
+
|
|
75
|
+
For derivation assertions, just read the derivation value and use your test framework's expect / equality helpers:
|
|
76
|
+
|
|
77
|
+
```typescript
|
|
78
|
+
import { expect } from "vitest";
|
|
79
|
+
expect(system.derive.isOverBudget).toBe(true);
|
|
80
|
+
```
|
|
81
|
+
|
|
82
|
+
## Testing constraints
|
|
83
|
+
|
|
84
|
+
```typescript
|
|
85
|
+
import { describe, it, expect } from "vitest";
|
|
86
|
+
import { createTestSystem } from "@directive-run/core/testing";
|
|
87
|
+
|
|
88
|
+
describe("fetchWhenAuth constraint", () => {
|
|
89
|
+
it("emits FETCH_USER when authenticated without profile", () => {
|
|
90
|
+
const system = createTestSystem({ module: userModule });
|
|
91
|
+
|
|
92
|
+
system.facts.isAuthenticated = true;
|
|
93
|
+
system.facts.profile = null;
|
|
94
|
+
|
|
95
|
+
system.assertRequirement("FETCH_USER");
|
|
96
|
+
});
|
|
97
|
+
|
|
98
|
+
it("does NOT emit when already has a profile", () => {
|
|
99
|
+
const system = createTestSystem({
|
|
100
|
+
module: userModule,
|
|
101
|
+
initialFacts: {
|
|
102
|
+
isAuthenticated: true,
|
|
103
|
+
profile: { id: "1", name: "Alice" },
|
|
104
|
+
},
|
|
105
|
+
});
|
|
106
|
+
|
|
107
|
+
const inspection = system.inspect();
|
|
108
|
+
const fetchReqs = inspection.unmet.filter((r) => r.type === "FETCH_USER");
|
|
109
|
+
expect(fetchReqs).toHaveLength(0);
|
|
110
|
+
});
|
|
111
|
+
});
|
|
112
|
+
```
|
|
113
|
+
|
|
114
|
+
`SystemInspection` exposes `unmet`, `inflight`, and other typed accessors — there is no flat `inspection.requirements` field. Filter the right list (`unmet` for pending requirements, `inflight` for in-progress).
|
|
115
|
+
|
|
116
|
+
## Testing resolvers
|
|
117
|
+
|
|
118
|
+
```typescript
|
|
119
|
+
describe("fetchUser resolver", () => {
|
|
120
|
+
it("stores fetched user in facts", async () => {
|
|
121
|
+
const system = createTestSystem({
|
|
122
|
+
module: userModule,
|
|
123
|
+
mocks: {
|
|
124
|
+
resolvers: {
|
|
125
|
+
FETCH_USER: {
|
|
126
|
+
resolve: (req, context) => {
|
|
127
|
+
context.facts.user = { id: req.userId, name: "Mocked User" };
|
|
128
|
+
context.facts.phase = "loaded";
|
|
129
|
+
},
|
|
130
|
+
},
|
|
131
|
+
},
|
|
132
|
+
},
|
|
133
|
+
});
|
|
134
|
+
|
|
135
|
+
// Trigger the constraint that emits FETCH_USER
|
|
136
|
+
system.facts.isAuthenticated = true;
|
|
137
|
+
system.facts.user = null;
|
|
138
|
+
|
|
139
|
+
// Wait for the resolver to finish (default 5s timeout)
|
|
140
|
+
await system.waitForIdle();
|
|
141
|
+
|
|
142
|
+
system.assertFactSet("user", { id: expect.anything(), name: "Mocked User" });
|
|
143
|
+
system.assertFactSet("phase", "loaded");
|
|
144
|
+
});
|
|
145
|
+
});
|
|
146
|
+
```
|
|
147
|
+
|
|
148
|
+
`mocks.resolvers[TYPE]` accepts a `MockResolverOptions` — the most common shape is `{ resolve: (req, context) => {…} }`. You can also pass full mock-resolver behaviors (`onCall`, `respondWith`, etc.) — see `MockResolverOptions` for the full surface.
|
|
149
|
+
|
|
150
|
+
For programmatic control of when a mock resolves (e.g. holding a request to test loading states), use the `mockResolver(type)` factory:
|
|
151
|
+
|
|
152
|
+
```typescript
|
|
153
|
+
import { mockResolver } from "@directive-run/core/testing";
|
|
154
|
+
|
|
155
|
+
const mock = mockResolver("FETCH_USER");
|
|
156
|
+
|
|
157
|
+
const system = createTestSystem({
|
|
158
|
+
module: userModule,
|
|
159
|
+
mocks: {
|
|
160
|
+
resolvers: { FETCH_USER: { resolve: mock.handler } },
|
|
161
|
+
},
|
|
162
|
+
});
|
|
163
|
+
|
|
164
|
+
system.facts.isAuthenticated = true;
|
|
165
|
+
|
|
166
|
+
// Now mock.pending contains the in-flight request — resolve it manually
|
|
167
|
+
expect(mock.calls).toHaveLength(1);
|
|
168
|
+
mock.pending[0].resolve({ id: "u1", name: "Alice" });
|
|
169
|
+
|
|
170
|
+
await system.waitForIdle();
|
|
171
|
+
```
|
|
172
|
+
|
|
173
|
+
## Testing derivations
|
|
174
|
+
|
|
175
|
+
Set facts, read the derived value via `system.derive`.
|
|
176
|
+
|
|
177
|
+
```typescript
|
|
178
|
+
describe("isOverBudget derivation", () => {
|
|
179
|
+
it("recomputes when facts change", () => {
|
|
180
|
+
const system = createTestSystem({
|
|
181
|
+
module: budgetModule,
|
|
182
|
+
initialFacts: { total: 50, budget: 100 },
|
|
183
|
+
});
|
|
184
|
+
|
|
185
|
+
expect(system.derive.isOverBudget).toBe(false);
|
|
186
|
+
|
|
187
|
+
system.facts.total = 200;
|
|
188
|
+
|
|
189
|
+
expect(system.derive.isOverBudget).toBe(true);
|
|
190
|
+
});
|
|
191
|
+
});
|
|
192
|
+
```
|
|
193
|
+
|
|
194
|
+
## Async testing with fake timers
|
|
195
|
+
|
|
196
|
+
`waitForIdle` uses real timers by default. For tests that depend on retry backoff / TTLs / timers without burning wall-clock seconds, use `createFakeTimers()` (a vitest/jest-compatible controller) or your test framework's fake-timer integration.
|
|
197
|
+
|
|
198
|
+
```typescript
|
|
199
|
+
import { describe, it, expect, vi } from "vitest";
|
|
200
|
+
import { createTestSystem } from "@directive-run/core/testing";
|
|
201
|
+
|
|
202
|
+
describe("retry behavior", () => {
|
|
203
|
+
it("retries on failure with exponential backoff", async () => {
|
|
204
|
+
vi.useFakeTimers();
|
|
205
|
+
let attempts = 0;
|
|
206
|
+
|
|
207
|
+
const system = createTestSystem({
|
|
208
|
+
module: myModule,
|
|
209
|
+
mocks: {
|
|
210
|
+
resolvers: {
|
|
211
|
+
FETCH_DATA: {
|
|
212
|
+
resolve: async (req, context) => {
|
|
213
|
+
attempts += 1;
|
|
214
|
+
if (attempts < 3) {
|
|
215
|
+
throw new Error("Temporary failure");
|
|
216
|
+
}
|
|
217
|
+
context.facts.data = "success";
|
|
218
|
+
},
|
|
219
|
+
},
|
|
220
|
+
},
|
|
221
|
+
},
|
|
222
|
+
});
|
|
223
|
+
|
|
224
|
+
system.facts.needsData = true;
|
|
225
|
+
|
|
226
|
+
// Advance through retry delays, then drain
|
|
227
|
+
await vi.runAllTimersAsync();
|
|
228
|
+
await system.waitForIdle();
|
|
229
|
+
|
|
230
|
+
expect(attempts).toBe(3);
|
|
231
|
+
system.assertFactSet("data", "success");
|
|
232
|
+
|
|
233
|
+
vi.useRealTimers();
|
|
234
|
+
});
|
|
235
|
+
});
|
|
236
|
+
```
|
|
237
|
+
|
|
238
|
+
## Microtask flush
|
|
239
|
+
|
|
240
|
+
For checking intermediate state without fully settling, use `flushMicrotasks()`.
|
|
241
|
+
|
|
242
|
+
```typescript
|
|
243
|
+
import { flushMicrotasks } from "@directive-run/core/testing";
|
|
244
|
+
|
|
245
|
+
it("processes intermediate state", async () => {
|
|
246
|
+
const system = createTestSystem({ module: myModule });
|
|
247
|
+
|
|
248
|
+
system.facts.trigger = true;
|
|
249
|
+
|
|
250
|
+
// Process one round of microtasks without waiting for resolvers
|
|
251
|
+
await flushMicrotasks();
|
|
252
|
+
|
|
253
|
+
system.assertFactSet("phase", "loading");
|
|
254
|
+
|
|
255
|
+
await system.waitForIdle();
|
|
256
|
+
|
|
257
|
+
system.assertFactSet("phase", "done");
|
|
258
|
+
});
|
|
259
|
+
```
|
|
260
|
+
|
|
261
|
+
## Coverage tracking
|
|
262
|
+
|
|
263
|
+
```typescript
|
|
264
|
+
import { createCoverageTracker } from "@directive-run/core/testing";
|
|
265
|
+
|
|
266
|
+
const tracker = createCoverageTracker(system);
|
|
267
|
+
|
|
268
|
+
await tracker.run(async () => {
|
|
269
|
+
system.facts.userId = 123;
|
|
270
|
+
await system.waitForIdle();
|
|
271
|
+
});
|
|
272
|
+
|
|
273
|
+
const coverage = tracker.report();
|
|
274
|
+
// coverage.constraintCoverage — 0-1
|
|
275
|
+
// coverage.resolverCoverage — 0-1
|
|
276
|
+
// coverage.effectCoverage — 0-1
|
|
277
|
+
// coverage.derivationCoverage — 0-1
|
|
278
|
+
// coverage.constraintsMissed — Set<string> of ids never triggered
|
|
279
|
+
```
|
|
280
|
+
|
|
281
|
+
## Test observer
|
|
282
|
+
|
|
283
|
+
Capture observation events for assertion-based testing:
|
|
284
|
+
|
|
285
|
+
```typescript
|
|
286
|
+
import { createTestObserver } from "@directive-run/core/testing";
|
|
287
|
+
|
|
288
|
+
const observer = createTestObserver(system);
|
|
289
|
+
|
|
290
|
+
system.facts.count = 5;
|
|
291
|
+
await system.waitForIdle();
|
|
292
|
+
|
|
293
|
+
const evals = observer.ofType("constraint.evaluate");
|
|
294
|
+
expect(evals).toHaveLength(1);
|
|
295
|
+
|
|
296
|
+
observer.clear(); // reset captured events
|
|
297
|
+
observer.dispose(); // stop capturing
|
|
298
|
+
```
|
|
299
|
+
|
|
300
|
+
## Anti-patterns
|
|
301
|
+
|
|
302
|
+
### `createTestSystem(myModule)` (module as positional arg)
|
|
303
|
+
|
|
304
|
+
```typescript
|
|
305
|
+
// WRONG — createTestSystem takes an options object, not a positional module
|
|
306
|
+
const system = createTestSystem(myModule);
|
|
307
|
+
|
|
308
|
+
// CORRECT — wrap in { module: ... }
|
|
309
|
+
const system = createTestSystem({ module: myModule });
|
|
310
|
+
```
|
|
311
|
+
|
|
312
|
+
### `createTestSystemFromModules({ a, b })`
|
|
313
|
+
|
|
314
|
+
```typescript
|
|
315
|
+
// WRONG — there is no separate factory; the same createTestSystem handles both forms
|
|
316
|
+
const system = createTestSystemFromModules({ auth, cart });
|
|
317
|
+
|
|
318
|
+
// CORRECT — pass `modules:`
|
|
319
|
+
const system = createTestSystem({ modules: { auth, cart } });
|
|
320
|
+
```
|
|
321
|
+
|
|
322
|
+
### Importing assertions as top-level helpers
|
|
323
|
+
|
|
324
|
+
```typescript
|
|
325
|
+
// WRONG — assertFact / assertDerivation / assertRequirement are not top-level exports
|
|
326
|
+
import { assertFact, assertDerivation, assertRequirement } from "@directive-run/core/testing";
|
|
327
|
+
assertRequirement(system, "FETCH_USER");
|
|
328
|
+
assertFact(system, "phase", "loaded");
|
|
329
|
+
|
|
330
|
+
// CORRECT — they live on the test system instance
|
|
331
|
+
system.assertRequirement("FETCH_USER");
|
|
332
|
+
system.assertFactSet("phase", "loaded");
|
|
333
|
+
|
|
334
|
+
// For derivation reads, use expect() directly
|
|
335
|
+
expect(system.derive.isOverBudget).toBe(true);
|
|
336
|
+
```
|
|
337
|
+
|
|
338
|
+
### `mockResolvers: [mockResolver(TYPE, fn)]` (positional array)
|
|
339
|
+
|
|
340
|
+
```typescript
|
|
341
|
+
// WRONG — mocks are keyed by type, not an array; and mockResolver only takes the type
|
|
342
|
+
const system = createTestSystem(myModule, {
|
|
343
|
+
mockResolvers: [
|
|
344
|
+
mockResolver("FETCH_USER", async (req, context) => { /* ... */ }),
|
|
345
|
+
],
|
|
346
|
+
});
|
|
347
|
+
|
|
348
|
+
// CORRECT — mocks.resolvers is a Record<type, MockResolverOptions>
|
|
349
|
+
const system = createTestSystem({
|
|
350
|
+
module: myModule,
|
|
351
|
+
mocks: {
|
|
352
|
+
resolvers: {
|
|
353
|
+
FETCH_USER: {
|
|
354
|
+
resolve: async (req, context) => { /* ... */ },
|
|
355
|
+
},
|
|
356
|
+
},
|
|
357
|
+
},
|
|
358
|
+
});
|
|
359
|
+
```
|
|
360
|
+
|
|
361
|
+
### `inspection.requirements`
|
|
362
|
+
|
|
363
|
+
```typescript
|
|
364
|
+
// WRONG — SystemInspection has `unmet` and `inflight`, not a flat `requirements`
|
|
365
|
+
inspection.requirements.filter((r) => r.type === "FETCH_USER")
|
|
366
|
+
|
|
367
|
+
// CORRECT — filter the right list
|
|
368
|
+
inspection.unmet.filter((r) => r.type === "FETCH_USER") // pending
|
|
369
|
+
inspection.inflight.filter((r) => r.type === "FETCH_USER") // in-progress
|
|
370
|
+
```
|
|
371
|
+
|
|
372
|
+
### Calling `system.settle()` instead of `system.waitForIdle()`
|
|
373
|
+
|
|
374
|
+
`system.settle(maxWait?)` exists on production systems. Test systems inherit it, but the recommended assertion-friendly drain on a `TestSystem` is `waitForIdle` — it shares the same semantics and integrates with the resolver-call tracking used by `assertResolverCalled`.
|
|
375
|
+
|
|
376
|
+
```typescript
|
|
377
|
+
// FINE — both work, but waitForIdle is the testing-flavored verb
|
|
378
|
+
await system.settle();
|
|
379
|
+
await system.waitForIdle();
|
|
380
|
+
```
|
|
381
|
+
|
|
382
|
+
### `ctx` instead of `context`
|
|
383
|
+
|
|
384
|
+
```typescript
|
|
385
|
+
// WRONG
|
|
386
|
+
resolve: (req, ctx) => { ctx.facts.x = 1; }
|
|
387
|
+
|
|
388
|
+
// CORRECT
|
|
389
|
+
resolve: (req, context) => { context.facts.x = 1; }
|
|
390
|
+
```
|
|
391
|
+
|
|
392
|
+
## Quick reference
|
|
393
|
+
|
|
394
|
+
| API | Path | Purpose |
|
|
395
|
+
|---|---|---|
|
|
396
|
+
| `createTestSystem(options)` | `@directive-run/core/testing` | Single overload; pass `module:` or `modules:` |
|
|
397
|
+
| `mockResolver(type)` | `@directive-run/core/testing` | Programmatic mock with `calls` / `pending` / `handler` |
|
|
398
|
+
| `createFakeTimers()` | `@directive-run/core/testing` | Vitest/Jest-compatible timer controller |
|
|
399
|
+
| `flushMicrotasks()` | `@directive-run/core/testing` | Advance one microtask round |
|
|
400
|
+
| `createCoverageTracker(system)` | `@directive-run/core/testing` | `run(fn)` + `report()` for constraint/resolver/effect/derivation hit rate |
|
|
401
|
+
| `createTestObserver(system)` | `@directive-run/core/testing` | Capture observation events for assertions |
|
|
402
|
+
| `system.assertRequirement(type)` | instance method | Assert a requirement was generated |
|
|
403
|
+
| `system.assertResolverCalled(type, n?)` | instance method | Assert a resolver was called |
|
|
404
|
+
| `system.assertFactSet(key, value?)` | instance method | Assert a fact value |
|
|
405
|
+
| `system.assertFactChanges(key, n)` | instance method | Assert the change count for a fact |
|
|
406
|
+
| `system.waitForIdle(maxWait?)` | instance method | Wait for in-flight resolvers (default 5000ms) |
|
|
407
|
+
|
|
408
|
+
## See also
|
|
409
|
+
|
|
410
|
+
- [`resolvers.md`](./resolvers.md) — the resolver shape `mocks.resolvers` replaces and the retry/cancel semantics tests need to know about
|
|
411
|
+
- [`core-patterns.md`](./core-patterns.md) — the module shape `createTestSystem({ module })` is testing
|
|
412
|
+
- [`history.md`](./history.md) — recorded snapshots for replay-based testing patterns
|