Mock bus for @constantant/openapi-resource-gen tokens.
Provides zero-HTTP, pure-DI mocks for Angular InjectionToken-based data-access libs — with a cross-boundary API so Playwright E2E tests and a future Chrome Extension devtools panel can observe and control every token's state from outside the Angular context.
npm install -D @constantant/openapi-resource-mocksPeer dependencies: @angular/core >=20, @angular/common >=20, rxjs ^7.8 — verified in CI against Angular 20.0.0 through 22.x (see the compatibility table).
Each mock token is registered in a MockResourceBus. The bus:
- Exposes
window.__openApiMocks__— a plain object keyed by token name, accessible from Playwright'spage.evaluate()or a Chrome Extension content script. - Emits DOM events (
openapi-mock-event) on every state change so the Chrome Extension DevTools panel can observe in real time. - Listens for DOM events (
openapi-mock-control) so the DevTools panel can push data into the app.
Add provideMockResourceBus() once, then one mock provider per token.
With --includeMocks (recommended) — the generator emits a typed provide{Operation}Mock() wrapper per endpoint. Import from the /mock subpath so mock utilities stay out of your production bundle:
// app.config.mock.ts (used in tests / E2E variant)
import { provideMockResourceBus } from '@constantant/openapi-resource-mocks';
import { provideFindPetsByStatusMock, provideUploadFileMock } from '@myapp/petstore-data-access/mock';
export const mockProviders = [
provideMockResourceBus(),
provideFindPetsByStatusMock(),
provideUploadFileMock(),
];Without --includeMocks — use provideMockResource() directly with the raw token:
// app.config.mock.ts
import { provideMockResourceBus, provideMockResource } from '@constantant/openapi-resource-mocks';
import { FIND_PETS_BY_STATUS, UPLOAD_FILE } from '@myapp/petstore-data-access';
export const mockProviders = [
provideMockResourceBus(),
provideMockResource(FIND_PETS_BY_STATUS, 'FIND_PETS_BY_STATUS'),
provideMockResource(UPLOAD_FILE, 'UPLOAD_FILE'),
];import { TestBed } from '@angular/core/testing';
import { injectMockResource, provideMockResourceBus, provideMockResource } from '@constantant/openapi-resource-mocks';
TestBed.configureTestingModule({
imports: [PetsComponent],
providers: [
provideMockResourceBus(),
provideMockResource(FIND_PETS_BY_STATUS, 'FIND_PETS_BY_STATUS'),
],
});
// Render the component first — this calls the token factory and registers the ref
const fixture = TestBed.createComponent(PetsComponent);
fixture.detectChanges();
// Now the ref is in the bus
const mock = TestBed.runInInjectionContext(() =>
injectMockResource('FIND_PETS_BY_STATUS'),
);
mock.resolve([{ id: 1, name: 'Rex', status: 'available' }]);
fixture.detectChanges();
mock.setLoading();
mock.fail(new Error('network'));
mock.reset();
// Delayed response (loading → data after 500 ms)
mock.resolveAfter(500, []);
// Reload — keeps previous value visible while re-fetching (status → 'reloading')
mock.resolve([{ id: 1, name: 'Rex', status: 'available' }]);
mock.reload(); // status: 'reloading', value still [Rex], isLoading() → true
mock.resolve([{ id: 1, name: 'Rex', status: 'sold' }]); // complete the reload
// Request count — total factory invocations including reactive param changes and reload()
expect(mock.requestCount()).toBe(1); // checked after fixture.detectChanges()
// Reset history between scenarios without remounting the component
mock.clearHistory(); // via bus.get(key)Types come from the generated lib — no hand-written interfaces needed.
@constantant/openapi-resource-mocks/testing is a lightweight sub-entry for unit tests and
Storybook. It does not import MockResourceBus, register DOM events, or touch window —
it simply wraps a token with a MockResourceRef and drops it into TestBed as a
FactoryProvider.
import { mockResource } from '@constantant/openapi-resource-mocks/testing';
import { FIND_PETS_BY_STATUS } from '@myapp/petstore-data-access';
const petsMock = mockResource(FIND_PETS_BY_STATUS, { value: [] });
TestBed.configureTestingModule({
imports: [PetsComponent],
providers: [petsMock], // MockResourceHandle extends FactoryProvider — drop it in directly
});
fixture.detectChanges();
petsMock.ref.resolve([{ id: 1, name: 'Rex', status: 'available', photoUrls: [] }]);
fixture.detectChanges();
petsMock.expectCalled();
petsMock.expectCalledWith({ status: 'available' });
console.log(petsMock.calls); // readonly unknown[][] — all factory invocations and their argsReturns MockResourceHandle<T>, which extends FactoryProvider.
The second argument sets the initial mock state (same shapes as provideMockResource):
| Shape | Effect |
|---|---|
{ value: T } |
Resolves immediately |
{ value: T, delay: ms } |
Loading for ms ms, then resolves |
{ loading: true } |
Stays loading indefinitely |
{ error: unknown } |
Fails immediately |
{ error: unknown, delay: ms } |
Loading for ms ms, then fails |
Pass { sequence: [...] } to advance through multiple states — each factory call (and
each reload() in resolved state) consumes the next entry; the last entry repeats when exhausted:
const mock = mockResource(FIND_PETS_BY_STATUS, {
sequence: [
{ loading: true },
{ error: new Error('timeout') },
{ value: [{ id: 1, name: 'Rex', status: 'available', photoUrls: [] }] },
],
});The same idea for tokens generated with clientType: 'httpClient' (functions returning
Observable<T>). Returns a MockObservableHandle (a FactoryProvider with .calls,
.subscriptions, .expectCalled() and .expectCalledWith() — there is no .ref, since the
result isn't a resource).
The observable is cold, like HttpClient: each subscription consumes the next behavior.
| Shape | Effect |
|---|---|
{ value: T } |
Emits once, then completes |
{ value: T, delay: ms } |
Emits after ms ms (unsubscribing cancels it) |
{ error: unknown } |
Errors |
{ loading: true } (or no behavior) |
Never emits |
{ sequence: [...] } |
One entry per subscription; the last repeats — e.g. fail once, then succeed on retry |
const petsMock = mockObservable(FIND_PETS_BY_STATUS, {
sequence: [{ error: new Error('timeout') }, { value: pets }],
});mockObservable for tokens generated with --reportProgress that return
Observable<HttpEvent<T>> (file uploads and blob downloads over httpClient). Each subscription
emits what a real HttpClient call with reportProgress: true emits: a Sent event, the
behavior's progress events, then a Response event (status 200) carrying value, then
completion. It returns the same handle as mockObservable (.calls, .subscriptions,
.expectCalled(), .expectCalledWith()).
| Shape | Effect |
|---|---|
{ value, progress? } |
Sent, the progress events, then the Response, then completes |
{ error, progress? } |
Sent, the progress events, then errors |
{ loading: true, progress? } |
Sent and the progress events, then stays open — hold a progress bar at a given percentage |
delay: ms |
defers the final response / error; Sent and progress are immediate |
{ sequence: [...] } |
one entry per subscription; the last repeats — e.g. fail at 50 %, then succeed on retry |
progress entries are { type: 'upload' | 'download', loaded, total? }.
// Component test: assert the progress bar while the upload is held at 25 %
const upload = mockHttpEvents(UPLOAD_FILE, {
loading: true,
progress: [{ type: 'upload', loaded: 1_000_000, total: 4_000_000 }],
});
TestBed.configureTestingModule({ imports: [PetsPage], providers: [upload] });
// ... trigger the upload ...
upload.expectCalledWith('7', file);
expect(bar.getAttribute('aria-valuenow')).toBe('25');| Member | Description |
|---|---|
.ref |
MockResourceRef<T> — the underlying ref; call .resolve(), .fail(), etc. to change state mid-test |
.calls |
readonly unknown[][] — all args the factory was invoked with |
.expectCalled() |
Throws if the factory was never called |
.expectCalledWith(...args) |
Throws if no call matches (deep equality via recursive comparison) |
const mock = TestBed.runInInjectionContext(() =>
injectMockResource<UploadResponse>('UPLOAD_FILE'),
);
// Animate through 10 steps over 2 s, then resolve
mock.simulateProgress('upload', 4_000_000, 2000, { id: 'abc123' });
// Or drive progress manually for precise test control
mock.setProgress('upload', 1_000_000, 4_000_000); // 25 %
mock.setProgress('upload', 4_000_000, 4_000_000); // 100 %
mock.resolve({ id: 'abc123' });
// Download progress (total unknown — streaming)
mock.setProgress('download', 16_384);
// Simulate failure mid-upload — progress is preserved so the UI can show "failed at 25%"
mock.setProgress('upload', 1_000_000, 4_000_000);
mock.fail(new Error('connection reset'));
console.log(mock.progress()); // { type: 'upload', loaded: 1_000_000, total: 4_000_000 }// Animate upload progress then resolve
await page.evaluate(() =>
openApiMock('UPLOAD_FILE').simulateProgress('upload', 4_000_000, 2000, { id: 'abc123' }),
);
await expect(page.locator('[data-testid="progress-bar"]')).toBeVisible();
// Manual steps
await page.evaluate(() =>
openApiMock('UPLOAD_FILE').setProgress('upload', 1_000_000, 4_000_000),
);
await expect(page.locator('[data-testid="progress-bar"]')).toHaveAttribute('aria-valuenow', '25');
// Fail mid-upload
await page.evaluate(() => openApiMock('UPLOAD_FILE').fail(new Error('timeout')));
const state = await page.evaluate(() => openApiMock('UPLOAD_FILE').getState());
console.log(state.progress); // { type: 'upload', loaded: 1_000_000, total: 4_000_000 }// Animate upload
document.dispatchEvent(new CustomEvent('openapi-mock-control', {
detail: {
key: 'UPLOAD_FILE',
action: 'simulateProgress',
progressType: 'upload',
total: 4_000_000,
delayMs: 2000,
value: { id: 'abc123' },
steps: 20, // optional, default 10
},
}));
// Single progress step
document.dispatchEvent(new CustomEvent('openapi-mock-control', {
detail: { key: 'UPLOAD_FILE', action: 'setProgress', progressType: 'download', loaded: 512, total: 1024 },
}));Serve the app with mock providers on a separate port so real and mock E2E suites never collide.
1. Mock app configuration — swap the real providers for mock ones:
// app.config.mock.ts
import { ApplicationConfig } from '@angular/core';
import { provideRouter } from '@angular/router';
import { provideMockResourceBus } from '@constantant/openapi-resource-mocks';
import { provideFindPetsByStatusMock } from '@myapp/petstore-data-access/mock';
import { appRoutes } from './app.routes';
export const appConfig: ApplicationConfig = {
providers: [
provideRouter(appRoutes),
provideMockResourceBus(),
provideFindPetsByStatusMock({
value: [{ id: 1, name: 'Rex', status: 'available', photoUrls: [] }],
delay: 500,
}),
],
};If you didn't use --includeMocks, replace the wrapper with provideMockResource(FIND_PETS_BY_STATUS, 'FIND_PETS_BY_STATUS', { ... }) imported from @constantant/openapi-resource-mocks.
2. Separate Playwright config — points to port 4201 and scopes testDir to the mock specs folder:
// playwright.mock.config.ts
import { defineConfig, devices } from '@playwright/test';
import { nxE2EPreset } from '@nx/playwright/preset';
import { workspaceRoot } from '@nx/devkit';
export default defineConfig({
...nxE2EPreset(__filename, { testDir: './src/mock' }),
use: {
baseURL: 'http://localhost:4201',
trace: 'on-first-retry',
},
webServer: {
command: 'npx nx run myapp:serve --configuration=mock-e2e',
url: 'http://localhost:4201',
reuseExistingServer: true,
cwd: workspaceRoot,
},
projects: [{ name: 'chromium', use: { ...devices['Desktop Chrome'] } }],
});3. Exclude mock specs from the main config — prevents mock specs from running against the real app:
// playwright.config.ts (existing main config)
export default defineConfig({
...nxE2EPreset(__filename, { testDir: './src' }),
testIgnore: ['**/mock/**'],
// ...
});4. TypeScript ambient declarations — required for openApiMock(key) to typecheck inside page.evaluate() function bodies:
// src/global.d.ts
interface MockEntry {
resolve(value: unknown): void;
resolveAfter(ms: number, value: unknown): void;
setLoading(): void;
fail(error: unknown): void;
reset(): void;
reload(): boolean;
setProgress(type: 'upload' | 'download', loaded: number, total?: number): void;
simulateProgress(type: 'upload' | 'download', totalBytes: number, durationMs: number, finalValue: unknown, steps?: number): void;
getState(): { status: string; value: unknown; error: unknown; progress: unknown; requestCount: number };
getHistory(): Array<{ type: string; ts: number; [key: string]: unknown }>;
clearHistory(): void;
onEvent(cb: (event: { type: string; [key: string]: unknown }) => void): () => void;
}
interface Window {
__openApiMocks__: Record<string, MockEntry>;
openApiMock: (key: string) => MockEntry;
}
// Needed for unqualified openApiMock('KEY') calls inside page.evaluate(() => ...)
declare function openApiMock(key: string): MockEntry;Also add "types": ["node"] to tsconfig.json if you use __filename in the Playwright config.
Always wait for a page landmark in beforeEach before running assertions — this guarantees Angular has bootstrapped and the mock factories have been called:
test.beforeEach(async ({ page }) => {
await page.goto('/pets');
await expect(page.getByRole('heading', { name: 'Pets' })).toBeVisible();
});Without this guard, assertions like toBeHidden() and toHaveCount(0) can pass immediately (before any elements render), and subsequent page.evaluate() calls will throw openApiMock is not defined.
// Resolve with data
await page.evaluate(() =>
openApiMock('FIND_PETS_BY_STATUS').resolve([{ id: 1, name: 'Rex', status: 'available' }]),
);
await expect(page.locator('mat-row')).toHaveCount(1);
// Test loading skeleton
await page.evaluate(() => openApiMock('FIND_PETS_BY_STATUS').setLoading());
await expect(page.locator('mat-progress-bar')).toBeVisible();
// Simulate slow network
await page.evaluate(() => openApiMock('FIND_PETS_BY_STATUS').resolveAfter(1000, []));
// Reload — keeps previous value visible while re-fetching
await page.evaluate(() => openApiMock('FIND_PETS_BY_STATUS').reload());
// status → 'reloading', pets still shown, spinner visible
await page.evaluate(() => openApiMock('FIND_PETS_BY_STATUS').resolve([{ id: 2, name: 'Luna', status: 'available' }]));
// Inspect current state (includes progress and requestCount)
const state = await page.evaluate(() => openApiMock('FIND_PETS_BY_STATUS').getState());
// { status, value, error, progress, requestCount }
// Full event history (requests + responses + progress ticks)
// resolveAfter and simulateProgress now emit loading/progress/resolve entries here
const history = await page.evaluate(() => openApiMock('FIND_PETS_BY_STATUS').getHistory());
// Reset history between test scenarios without remounting components
await page.evaluate(() => openApiMock('FIND_PETS_BY_STATUS').clearHistory());getHistory() records every factory invocation as a request entry. Function args (reactive lambdas) are resolved at call time, so args contains plain JSON values:
const history = await page.evaluate(() => openApiMock('FIND_PETS_BY_STATUS').getHistory());
const req = history.find((e) => e.type === 'request');
expect(req?.args[0]).toEqual({ status: 'available' });For tokens whose factory receives a reactive lambda (e.g. () => query() ? { q: query() } : undefined), args[0] will be the resolved value at mount time — undefined when suppressed, or the plain params object when active:
// Confirms the resource was suppressed on init (query was empty)
expect(req?.args[0]).toBeUndefined();Extension content script → app (control):
document.dispatchEvent(new CustomEvent('openapi-mock-control', {
detail: { key: 'FIND_PETS_BY_STATUS', action: 'resolve', value: [...] },
}));
// actions: resolve | resolveAfter | setLoading | fail | reset | reload
// setProgress | simulateProgress | clearHistory
// resolveAfter: delayMs: number
// setProgress: progressType: 'upload'|'download', loaded: number, total?: number
// simulateProgress: progressType, total: number, delayMs: number, value, steps?: number
// reload / clearHistory: no additional fields requiredApp → extension (observe):
document.addEventListener('openapi-mock-event', (e) => {
const { key, event } = e.detail;
// event.type: 'request' | 'resolve' | 'loading' | 'error' | 'reset' | 'progress'
devtoolsPanel.update(key, event);
});Returns EnvironmentProviders. Call once in your root providers or TestBed setup.
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.
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.
The optional options argument is a MockProviderOptions object — see below.
initialBehavior controls how the mock behaves on each invocation:
| Shape | Effect |
|---|---|
{ value: T } |
Resolves immediately with value |
{ value: T, delay: ms } |
Loading for ms ms, then resolves |
{ loading: true } |
Stays loading indefinitely |
{ error: unknown } |
Fails immediately |
{ error: unknown, delay: ms } |
Loading for ms ms, then fails |
Same as provideMockResource, for tokens generated with clientType: 'httpClient' (functions
returning Observable<T>). The key is registered when the function is called; every
subscription counts as a request. The observable emits once and completes on resolve, or
errors on fail; catch mode, delays and the DevTools controls behave as for resources, with one
difference: like a real HttpClient call, an observable is finished once it has emitted. So:
- a
resolve/failthat arrives while a request is pending (loading, delayed or caught) answers that request; - one that arrives when nothing is pending (e.g. from the DevTools Respond tab after the page
loaded) is remembered per key and replayed by the next request, ahead of
initialBehavior. Trigger that request with the app's own reload / refetch. The latest panel edit wins and it sticks until the page reloads.
Use catch mode to hold a request open while you choose its response. Requires rxjs ^7.8.
For tokens generated with --reportProgress whose function returns
Observable<HttpEvent<T>> (file uploads / blob downloads over httpClient). It behaves like
provideMockObservable but emits what a real HttpClient call with reportProgress: true emits:
- a
Sentevent on subscribe, UploadProgress/DownloadProgressevents for everysetProgress()/simulateProgress()step — from DevTools, e2e, orinjectMockResourcein unit tests,- a
Responseevent (status 200) carrying the resolved value, then completion.
fail() errors the observable (the progress events seen so far are kept), unsubscribing stops
delivery (a cancelled upload), and catch mode, delays, initialBehavior and the replay of panel
edits work as for provideMockObservable. The generator picks this provider automatically.
// e2e: hold the request, then drive the progress bar deterministically
await page.evaluate(() => openApiMock('UPLOAD_FILE').setProgress('upload', 1_000_000, 4_000_000));
await expect(bar).toHaveAttribute('aria-valuenow', '25');
await page.evaluate(() => openApiMock('UPLOAD_FILE').resolve({ code: 200, message: 'stored' }));Passed as the fifth argument to provideMockResource() and as the second argument to generated provide{Operation}Mock() wrappers.
interface MockProviderOptions {
keyDiscriminator?: () => string;
}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:
// List row component — each instance registers under a unique key
providers: [
providePetItemMock(undefined, { keyDiscriminator: () => inject(PET_ID).toString() }),
]
// Registers as 'GET_PET_BY_ID:42', 'GET_PET_BY_ID:43', etc.
// Control a specific instance from a Playwright test
await page.evaluate(() => openApiMock('GET_PET_BY_ID:42').resolve({ id: 42, name: 'Rex' }));Without keyDiscriminator, the last-mounted instance overwrites earlier registrations under the same key.
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>.
Union type accepted by provideMockResource() and generated provide{Operation}Mock() wrappers. Describes the mock's state immediately after each factory invocation:
| Shape | Effect |
|---|---|
{ value: DeepPartial<T> } |
Resolves immediately with value |
{ value: DeepPartial<T>, delay: ms } |
Loading for ms ms, then resolves |
{ loading: true } |
Stays loading indefinitely |
{ error: unknown } |
Fails immediately |
{ error: unknown, delay: ms } |
Loading for ms ms, then fails |
Metadata embedded in generated .mock.ts files and read by the DevTools panel to show response schemas, generate example payloads, and validate responses:
interface MockResourceMeta {
specId: string; // matches the --specId generator option (default: derived from baseUrlToken)
operationId: string; // OpenAPI operationId
path: string; // API path, e.g. '/pet/findByStatus'
method: string; // HTTP method, lowercase
tag?: string; // OpenAPI tag (omitted for untagged operations)
args?: readonly string[]; // names of the token function's arguments, in order (emitted by --callOptions)
}args (for example ['petId', 'body', 'params', 'options']) does two things:
- DevTools labels each argument of a recorded request by name (
Body,Query,Options, or the argument's own name) instead of guessing "Body [0]", "Body [1]". provideMockResourceuses it to find the per-calloptionsargument and honour itsdefaultValueexactly like the real resource:value()returns it until something resolves, and again afterreset(). Only the argument namedoptionscounts, so a body that merely has adefaultValuefield is never mistaken for it.
Per-call options can hold values that cannot be sent to the extension (an HttpContext, an
Injector, an equality function). The bus keeps the parts that can cross and replaces the rest with a
placeholder such as [HttpContext] or [Function], so such a request still shows up in DevTools.
window.__openApiMocks__[key].getHistory() keeps the raw arguments.
The DevTools panel resolves the response schema by looking up specId in its Specs store and then matching operationId. Import the spec (or its mocks.manifest.json) in the panel's Specs tab to enable schema-aware features.
Recursively marks all properties of T as optional. Used as the value type in ProviderInitialBehavior<T> so you can provide partial seed data without satisfying every nested field.
Utility type that extracts the response type T from a generated InjectionToken<(...args) => ResourceRef<T>>. Use it to type seed data without importing the response type by name:
import type { TokenValue } from '@constantant/openapi-resource-mocks';
import { FIND_PETS_BY_STATUS } from '@myapp/petstore-data-access';
// type is FindPetsByStatusResponse — inferred from the token
const mockPets: TokenValue<typeof FIND_PETS_BY_STATUS> = [
{ id: 1, name: 'Rex', status: 'available', photoUrls: [] },
];Creates a standalone ref without the bus — useful for Storybook decorators, custom test harnesses, or any scenario where you need a MockResourceRef outside of Angular DI.
| Member | Description |
|---|---|
value: Signal<T | undefined> |
Current response data |
status: Signal<ResourceStatus> |
'idle' | 'loading' | 'reloading' | 'resolved' | 'error' | 'local' |
error: Signal<unknown> |
Current error, if any |
isLoading: Signal<boolean> |
true while status is 'loading' or 'reloading' |
progress: Signal<MockProgress | undefined> |
Current transfer progress, if active |
requestCount: Signal<number> |
Total factory invocations (increments on mount, reactive param change, and reload()) |
hasValue(): boolean |
true when value is set |
resolve(value: T) |
Set value, clear error and progress → 'resolved' |
resolveAfter(ms, value) |
Set loading immediately, resolve after delay |
setLoading() |
Clear error → 'loading' |
fail(error) |
Set error → 'error' (progress preserved) |
reset() |
Clear all including progress → 'idle' |
reload() |
Keep value, clear error → 'reloading'; fires request event; returns true. Returns false if status is not 'resolved' or 'local' |
setProgress(type, loaded, total?) |
Set progress and status → 'loading' |
simulateProgress(type, totalBytes, durationMs, finalValue, steps?) |
Animate incremental progress over durationMs ms then resolve |
set(value) |
Local mutation → 'local' (ResourceRef interface) |
update(fn) |
Local update → 'local' (ResourceRef interface) |
onRequest(cb) |
Subscribe to factory invocations; returns unsubscribe fn |
interface MockProgress {
type: 'upload' | 'download';
loaded: number; // bytes transferred
total?: number; // total bytes (undefined for streaming / unknown-length responses)
}openApiMock is a shorthand exposed on window by MockResourceBus — equivalent to window.__openApiMocks__[key] but terser in page.evaluate() calls:
// Playwright
const history = await page.evaluate(() => openApiMock('FIND_PETS_BY_STATUS').getHistory());
await page.evaluate(() => openApiMock('FIND_PETS_BY_STATUS').resolve([...]));| Member | Description |
|---|---|
resolve(value) |
Set value (JSON-serializable) |
resolveAfter(ms, value) |
Loading immediately, resolve after delay — emits loading + resolve events |
setLoading() |
Start loading state |
fail(error) |
Set error state |
reset() |
Return to idle |
reload() |
Keep value → 'reloading', fire request event; returns true if was resolved/local |
setProgress(type, loaded, total?) |
Set transfer progress |
simulateProgress(type, totalBytes, durationMs, finalValue, steps?) |
Animate progress then resolve — emits loading, one progress event per step, then resolve |
getState() |
{ status, value, error, progress, requestCount } snapshot |
getHistory() |
Array of all MockEvent entries (requests, responses, progress ticks) |
clearHistory() |
Empty the event log — useful for resetting between test scenarios |
onEvent(cb) |
Subscribe to all events; returns unsubscribe fn |
type MockEvent =
| { type: 'request'; args: unknown[]; ts: number } // factory called by component — function args are called and replaced with their return value
| { type: 'resolve'; value: unknown; ts: number }
| { type: 'loading'; ts: number }
| { type: 'error'; error: unknown; ts: number }
| { type: 'reset'; ts: number }
| { type: 'progress'; progressType: 'upload' | 'download'; loaded: number; total?: number; ts: number };MIT