Skip to content

Commit 7d61ea9

Browse files
constantantclaude
andcommitted
docs(openapi-resource-mocks): document reload, requestCount, clearHistory, keyDiscriminator
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
1 parent 5de8968 commit 7d61ea9

1 file changed

Lines changed: 69 additions & 10 deletions

File tree

tools/openapi-resource-mocks/README.md

Lines changed: 69 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -94,6 +94,17 @@ mock.reset();
9494

9595
// Delayed response (loading → data after 500 ms)
9696
mock.resolveAfter(500, []);
97+
98+
// Reload — keeps previous value visible while re-fetching (status → 'reloading')
99+
mock.resolve([{ id: 1, name: 'Rex', status: 'available' }]);
100+
mock.reload(); // status: 'reloading', value still [Rex], isLoading() → true
101+
mock.resolve([{ id: 1, name: 'Rex', status: 'sold' }]); // complete the reload
102+
103+
// Request count — total factory invocations including reactive param changes and reload()
104+
expect(mock.requestCount()).toBe(1); // checked after fixture.detectChanges()
105+
106+
// Reset history between scenarios without remounting the component
107+
mock.clearHistory(); // via bus.get(key)
97108
```
98109

99110
Types come from the generated lib — no hand-written interfaces needed.
@@ -246,8 +257,13 @@ interface MockEntry {
246257
setLoading(): void;
247258
fail(error: unknown): void;
248259
reset(): void;
249-
getState(): { status: string; value: unknown; error: unknown; progress: unknown };
250-
getHistory(): Array<{ type: string; args: unknown[]; ts: number }>;
260+
reload(): boolean;
261+
setProgress(type: 'upload' | 'download', loaded: number, total?: number): void;
262+
simulateProgress(type: 'upload' | 'download', totalBytes: number, durationMs: number, finalValue: unknown, steps?: number): void;
263+
getState(): { status: string; value: unknown; error: unknown; progress: unknown; requestCount: number };
264+
getHistory(): Array<{ type: string; ts: number; [key: string]: unknown }>;
265+
clearHistory(): void;
266+
onEvent(cb: (event: { type: string; [key: string]: unknown }) => void): () => void;
251267
}
252268

253269
interface Window {
@@ -290,11 +306,21 @@ await expect(page.locator('mat-progress-bar')).toBeVisible();
290306
// Simulate slow network
291307
await page.evaluate(() => openApiMock('FIND_PETS_BY_STATUS').resolveAfter(1000, []));
292308

293-
// Inspect current state (includes progress if active)
309+
// Reload — keeps previous value visible while re-fetching
310+
await page.evaluate(() => openApiMock('FIND_PETS_BY_STATUS').reload());
311+
// status → 'reloading', pets still shown, spinner visible
312+
await page.evaluate(() => openApiMock('FIND_PETS_BY_STATUS').resolve([{ id: 2, name: 'Luna', status: 'available' }]));
313+
314+
// Inspect current state (includes progress and requestCount)
294315
const state = await page.evaluate(() => openApiMock('FIND_PETS_BY_STATUS').getState());
316+
// { status, value, error, progress, requestCount }
295317

296318
// Full event history (requests + responses + progress ticks)
319+
// resolveAfter and simulateProgress now emit loading/progress/resolve entries here
297320
const history = await page.evaluate(() => openApiMock('FIND_PETS_BY_STATUS').getHistory());
321+
322+
// Reset history between test scenarios without remounting components
323+
await page.evaluate(() => openApiMock('FIND_PETS_BY_STATUS').clearHistory());
298324
```
299325

300326
### Asserting request params
@@ -324,11 +350,12 @@ expect(req?.args[0]).toBeUndefined();
324350
document.dispatchEvent(new CustomEvent('openapi-mock-control', {
325351
detail: { key: 'FIND_PETS_BY_STATUS', action: 'resolve', value: [...] },
326352
}));
327-
// actions: resolve | resolveAfter | setLoading | fail | reset
328-
// setProgress | simulateProgress
353+
// actions: resolve | resolveAfter | setLoading | fail | reset | reload
354+
// setProgress | simulateProgress | clearHistory
329355
// resolveAfter: delayMs: number
330356
// setProgress: progressType: 'upload'|'download', loaded: number, total?: number
331357
// simulateProgress: progressType, total: number, delayMs: number, value, steps?: number
358+
// reload / clearHistory: no additional fields required
332359
```
333360

334361
**App → extension (observe):**
@@ -349,12 +376,14 @@ document.addEventListener('openapi-mock-event', (e) => {
349376

350377
Returns `EnvironmentProviders`. Call once in your root providers or TestBed setup.
351378

352-
### `provideMockResource(token, key, initialBehavior?, meta?)`
379+
### `provideMockResource(token, key, initialBehavior?, meta?, options?)`
353380

354381
Returns `FactoryProvider`. Each time a component invokes the factory function, a fresh ref is created, registered in the bus under `key`, and `initialBehavior` is applied — simulating the full request lifecycle on every mount.
355382

356383
The optional `meta` argument is a `MockResourceMeta` object. When provided, the DevTools panel uses it to look up response schemas and pre-populate the Respond tab's schema display. Generated `.mock.ts` files embed this automatically — you only need to pass it manually when using `provideMockResource()` directly.
357384

385+
The optional `options` argument is a `MockProviderOptions` object — see below.
386+
358387
`initialBehavior` controls how the mock behaves on each invocation:
359388

360389
| Shape | Effect |
@@ -365,6 +394,33 @@ The optional `meta` argument is a `MockResourceMeta` object. When provided, the
365394
| `{ error: unknown }` | Fails immediately |
366395
| `{ error: unknown, delay: ms }` | Loading for `ms` ms, then fails |
367396

397+
### `MockProviderOptions`
398+
399+
Passed as the fifth argument to `provideMockResource()` and as the second argument to generated `provide{Operation}Mock()` wrappers.
400+
401+
```typescript
402+
interface MockProviderOptions {
403+
keyDiscriminator?: () => string;
404+
}
405+
```
406+
407+
**`keyDiscriminator`** — called inside the factory injection context to produce a suffix appended to the bus key as `key:suffix`. Use this when the same token appears in multiple component instances simultaneously (e.g. list rows) so each instance is independently addressable:
408+
409+
```typescript
410+
// List row component — each instance registers under a unique key
411+
providers: [
412+
providePetItemMock(undefined, { keyDiscriminator: () => inject(PET_ID).toString() }),
413+
]
414+
// Registers as 'GET_PET_BY_ID:42', 'GET_PET_BY_ID:43', etc.
415+
416+
// Control a specific instance from a Playwright test
417+
await page.evaluate(() => openApiMock('GET_PET_BY_ID:42').resolve({ id: 42, name: 'Rex' }));
418+
```
419+
420+
Without `keyDiscriminator`, the last-mounted instance overwrites earlier registrations under the same key.
421+
422+
---
423+
368424
### `injectMockResource<T>(key)`
369425

370426
Must be called inside an injection context (e.g. `TestBed.runInInjectionContext`) and after the component has rendered — the ref is registered when the component first invokes the factory function, not at DI setup time. Returns `MockResourceRef<T>`.
@@ -428,18 +484,19 @@ Creates a standalone ref without the bus — useful for Storybook decorators, cu
428484
| `error: Signal<unknown>` | Current error, if any |
429485
| `isLoading: Signal<boolean>` | `true` while status is `'loading'` or `'reloading'` |
430486
| `progress: Signal<MockProgress \| undefined>` | Current transfer progress, if active |
487+
| `requestCount: Signal<number>` | Total factory invocations (increments on mount, reactive param change, and `reload()`) |
431488
| `hasValue(): boolean` | `true` when value is set |
432489
| `resolve(value: T)` | Set value, clear error and progress → `'resolved'` |
433490
| `resolveAfter(ms, value)` | Set loading immediately, resolve after delay |
434491
| `setLoading()` | Clear error → `'loading'` |
435492
| `fail(error)` | Set error → `'error'` (progress preserved) |
436493
| `reset()` | Clear all including progress → `'idle'` |
494+
| `reload()` | Keep value, clear error → `'reloading'`; fires request event; returns `true`. Returns `false` if status is not `'resolved'` or `'local'` |
437495
| `setProgress(type, loaded, total?)` | Set progress and status → `'loading'` |
438496
| `simulateProgress(type, totalBytes, durationMs, finalValue, steps?)` | Animate incremental progress over `durationMs` ms then resolve |
439497
| `set(value)` | Local mutation → `'local'` (ResourceRef interface) |
440498
| `update(fn)` | Local update → `'local'` (ResourceRef interface) |
441499
| `onRequest(cb)` | Subscribe to factory invocations; returns unsubscribe fn |
442-
| `reload()` | No-op, returns `false` |
443500

444501
### `MockProgress`
445502

@@ -466,14 +523,16 @@ await page.evaluate(() => openApiMock('FIND_PETS_BY_STATUS').resolve([...]));
466523
| Member | Description |
467524
|--------|-------------|
468525
| `resolve(value)` | Set value (JSON-serializable) |
469-
| `resolveAfter(ms, value)` | Delayed resolve |
526+
| `resolveAfter(ms, value)` | Loading immediately, resolve after delay — emits `loading` + `resolve` events |
470527
| `setLoading()` | Start loading state |
471528
| `fail(error)` | Set error state |
472529
| `reset()` | Return to idle |
530+
| `reload()` | Keep value → `'reloading'`, fire request event; returns `true` if was resolved/local |
473531
| `setProgress(type, loaded, total?)` | Set transfer progress |
474-
| `simulateProgress(type, totalBytes, durationMs, finalValue, steps?)` | Animate progress then resolve |
475-
| `getState()` | `{ status, value, error, progress }` snapshot |
532+
| `simulateProgress(type, totalBytes, durationMs, finalValue, steps?)` | Animate progress then resolve — emits `loading`, one `progress` event per step, then `resolve` |
533+
| `getState()` | `{ status, value, error, progress, requestCount }` snapshot |
476534
| `getHistory()` | Array of all `MockEvent` entries (requests, responses, progress ticks) |
535+
| `clearHistory()` | Empty the event log — useful for resetting between test scenarios |
477536
| `onEvent(cb)` | Subscribe to all events; returns unsubscribe fn |
478537

479538
### `MockEvent` types

0 commit comments

Comments
 (0)