UNPKG

@data-client/test

Version:
148 lines (137 loc) 5.36 kB
import { actionTypes, Controller, DataClientDispatch, GenericDispatch, } from '@data-client/react'; import { collapseFixture } from './collapseFixture.js'; import { createFixtureMap } from './createFixtureMap.js'; import type { Fixture, Interceptor } from './fixtureTypes.js'; import { MockProps } from './mockTypes.js'; export function MockController<TBase extends typeof Controller, T>( Base: TBase, { fixtures = [], getInitialInterceptorData = () => ({}) as any, }: MockProps<T>, ): TBase { const [fixtureMap, interceptors] = createFixtureMap(fixtures); // eslint-disable-next-line @typescript-eslint/ban-ts-comment // @ts-ignore return class MockedController< D extends GenericDispatch = DataClientDispatch, > extends Base<D> { // legacy compatibility (re-declaration) // TODO: drop when drop support for destructuring (0.14 and below) declare protected _dispatch: D; fixtureMap: Map<string, Fixture> = fixtureMap; interceptors: Interceptor<any>[] = interceptors; interceptorData: T = getInitialInterceptorData(); constructor(...args: any[]) { super(...args); // legacy compatibility // TODO: drop when drop support for destructuring (0.14 and below) if (!this._dispatch) { this._dispatch = (args[0] as any).dispatch; } } // legacy compatibility - we need this to work with 0.14 and below as they do not have this setter // TODO: drop when drop support for destructuring (0.14 and below) // eslint-disable-next-line @typescript-eslint/ban-ts-comment // @ts-ignore set dispatch(dispatch: D) { this._dispatch = dispatch; } get dispatch(): D { return ((action: Parameters<D>[0]): Promise<void> => { // support legacy that has _TYPE suffix if (action.type === (actionTypes.FETCH ?? actionTypes.FETCH_TYPE)) { // eslint-disable-next-line prefer-const let { key, args } = action; let fixture: Fixture | Interceptor | undefined; if (this.fixtureMap.has(key)) { fixture = this.fixtureMap.get(key) as Fixture; if (!args) args = fixture.args; // exact matches take priority; now test ComputedFixture } else { for (const cfix of this.interceptors) { if (cfix.endpoint.testKey(key)) { fixture = cfix; break; } } } // we have a match if (fixture !== undefined) { const replacedAction: typeof action = { ...action, }; const delayMs = typeof fixture.delay === 'function' ? fixture.delay(...(args as any)) : (fixture.delay ?? 0); if ('fetchResponse' in fixture) { const { fetchResponse } = fixture; fixture = { endpoint: fixture.endpoint, response(...args) { const endpoint = (action.endpoint as any).extend({ fetchResponse: (input: RequestInfo, init: RequestInit) => { const ret = fetchResponse.call(this, input, init); return Promise.resolve( new Response(JSON.stringify(ret), { status: 200, headers: new Headers({ 'Content-Type': 'application/json', }), }), ); }, }); return (endpoint as any)(...args); }, }; } const fetch = async () => { if (!fixture) { throw new Error('No fixture found'); } // delayCollapse determines when the fixture function is 'collapsed' (aka 'run') // collapsed: https://en.wikipedia.org/wiki/Copenhagen_interpretation if (fixture.delayCollapse) { await new Promise(resolve => setTimeout(resolve, delayMs)); } const result = await collapseFixture( fixture as any, args as any, this.interceptorData, ); if (!fixture.delayCollapse && delayMs) { await new Promise(resolve => setTimeout(resolve, delayMs)); } if (result.error) { throw result.response; } return result.response; }; if (typeof (replacedAction.endpoint as any).extend === 'function') { replacedAction.endpoint = (replacedAction.endpoint as any).extend( { fetch, }, ); } else { // TODO: full testing of this replacedAction.endpoint = fetch as any; (replacedAction.endpoint as any).__proto__ = action.endpoint; } // TODO: make super.dispatch (once we drop support for destructuring) return this._dispatch(replacedAction); } } // TODO: make super.dispatch (once we drop support for destructuring) return this._dispatch(action); }) as any; } }; }