import { expect } from "./assertions.ts"; import * as _mock from "./expect/mock.ts"; export const mockfn = _mock.fn; /** * Patch an object's method */ export function patch< O, K extends keyof O, F extends Function & O[K], >( obj: O, method: K, lifetime: (mock: MockManipulator) => void, ): F { const orig = obj[method] as F; let stubs: { impl: F; loop: boolean }[] = []; const createMock = () => mockfn((...args: any[]) => { if (stubs.length) { const result = stubs[0].impl(...args); if (!stubs[0].loop) { stubs.shift(); } return result; } else { return orig.call(obj, ...args); } }); let mock = createMock(); Object.assign(obj, { [method]: mock }); try { lifetime({ _toExpected() { return mock; }, stub(impl?: F, loop = false) { if (impl) { stubs.push({ impl, loop }); } else { stubs.push({ impl: function () {} as any, loop: true }); } }, reset() { mock = createMock(); Object.assign(obj, { [method]: mock }); stubs = []; }, }); } finally { Object.assign(obj, { [method]: orig }); } return mock as unknown as F; } /** * Similar to *patch*, but with stub as enforced default */ export function mock< O, K extends keyof O, F extends (...args: any) => any & O[K], >( obj: O, method: K, stub: F | ReturnType | undefined, lifetime: (mock: MockManipulator) => void, ): F { return patch(obj, method, (mock) => { mock.stub( typeof stub == "function" ? stub as any : (() => stub) as F, true, ); lifetime(mock as any); }) as unknown as F; } type MockManipulator = { _toExpected: () => Parameters[0]; reset: () => void; stub: (impl?: F, loop?: boolean) => void; };