UNPKG

@v4fire/client

Version:

V4Fire client core library

183 lines (166 loc) 5.75 kB
/*! * V4Fire Client Core * https://github.com/V4Fire/Client * * Released under the MIT license * https://github.com/V4Fire/Client/blob/master/LICENSE */ import type { ModuleMocker } from 'jest-mock'; import type { JSHandle, Page } from 'playwright'; import { expandedStringify, setSerializerAsMockFn } from 'core/prelude/test-env/components/json'; import type { ExtractFromJSHandle, SpyExtractor, SpyObject } from 'tests/helpers/mock/interface'; export * from 'tests/helpers/mock/interface'; /** * Wraps an object as a spy object by adding additional properties for accessing spy information. * * @param agent - the JSHandle representing the spy or mock function. * @param obj - the object to wrap as a spy object. * @returns The wrapped object with spy properties. */ export function wrapAsSpy<T extends object>(agent: JSHandle<ReturnType<ModuleMocker['fn']> | ReturnType<ModuleMocker['spyOn']>>, obj: T): T & SpyObject { Object.defineProperties(obj, { calls: { get: () => agent.evaluate((ctx) => ctx.mock.calls) }, callsCount: { get: () => agent.evaluate((ctx) => ctx.mock.calls.length) }, lastCall: { get: () => agent.evaluate((ctx) => ctx.mock.calls[ctx.mock.calls.length - 1]) }, results: { get: () => agent.evaluate((ctx) => ctx.mock.results) } }); return <T & SpyObject>obj; } /** * Creates a spy object. * * @param ctx - the `JSHandle` to spy on. * @param spyCtor - the function that creates the spy. * @param argsToCtor - the arguments to pass to the spy constructor function. * @returns A promise that resolves to the created spy object. * * @example * ```typescript * const ctx = ...; // JSHandle to spy on * const spyCtor = (ctx) => jestMock.spy(ctx, 'prop'); // Spy constructor function * const spy = await createSpy(ctx, spyCtor); * * // Access spy properties * console.log(await spy.calls); * console.log(await spy.callsCount); * console.log(await spy.lastCall); * console.log(await spy.results); * ``` */ export async function createSpy<T extends JSHandle, ARGS extends any[]>( ctx: T, spyCtor: (ctx: ExtractFromJSHandle<T>, ...args: ARGS) => ReturnType<ModuleMocker['spyOn']>, ...argsToCtor: ARGS ): Promise<SpyObject> { const agent = await ctx.evaluateHandle<ReturnType<ModuleMocker['spyOn']>>(<any>spyCtor, ...argsToCtor); return wrapAsSpy(agent, {}); } /** * Retrieves an existing {@link SpyObject} from a `JSHandle`. * * @param ctx - the `JSHandle` containing the spy object. * @param spyExtractor - the function to extract the spy object. * @returns A promise that resolves to the spy object. * * @example * ```typescript * const component = await Component.createComponent(page, 'b-button', { * attrs: { * '@componentHook:beforeDataCreate': (ctx) => jestMock.spy(ctx.localEmitter, 'emit') * } * }); * * const spyExtractor = (ctx) => ctx.unsafe.localEmitter.emit; // Spy extractor function * const spy = await getSpy(ctx, spyExtractor); * * // Access spy properties * console.log(await spy.calls); * console.log(await spy.callsCount); * console.log(await spy.lastCall); * console.log(await spy.results); * ``` */ export async function getSpy<T extends JSHandle>( ctx: T, spyExtractor: SpyExtractor<ExtractFromJSHandle<T>, []> ): Promise<SpyObject> { return createSpy(ctx, spyExtractor); } /** * Creates a mock function and injects it into a Page object. * * @param page - the Page object to inject the mock function into. * @param fn - the mock function. * @param args - the arguments to pass to the function. * @returns A promise that resolves to the mock function as a {@link SpyObject}. * * @example * ```typescript * const page = ...; // Page object * const fn = () => {}; // The mock function * const mockFn = await createMockFn(page, fn); * * // Access spy properties * console.log(await mockFn.calls); * console.log(await mockFn.callsCount); * console.log(await mockFn.lastCall); * console.log(await mockFn.results); * ``` */ export async function createMockFn( page: Page, fn: (...args: any[]) => any, ...args: any[] ): Promise<SpyObject> { const {agent, id} = await injectMockIntoPage(page, fn, ...args); return setSerializerAsMockFn(agent, id); } /** * Injects a mock function into a Page object and returns the {@link SpyObject}. * * This function also returns the ID of the injected mock function, which is stored in `globalThis`. * This binding allows the function to be found during object serialization within the page context. * * @param page - the Page object to inject the mock function into. * @param fn - the mock function. * @param args - the arguments to pass to the function. * @returns A promise that resolves to an object containing the spy object and the ID of the injected mock function. * * @example * ```typescript * const page = ...; // Page object * const fn = () => {}; // The mock function * const { agent, id } = await injectMockIntoPage(page, fn); * * // Access spy properties * console.log(await agent.calls); * console.log(await agent.callsCount); * console.log(await agent.lastCall); * console.log(await agent.results); * ``` */ async function injectMockIntoPage( page: Page, fn: (...args: any[]) => any, ...args: any[] ): Promise<{agent: SpyObject; id: string}> { const tmpFn = `tmp_${Math.random().toString()}`, argsToProvide = <const>[tmpFn, fn.toString(), expandedStringify(args)]; const agent = await page.evaluateHandle(([tmpFn, fnString, args]) => globalThis[tmpFn] = jestMock.mock((...fnArgs) => // eslint-disable-next-line no-new-func new Function(`return ${fnString}`)()(...fnArgs, ...globalThis.expandedParse(args))), argsToProvide); return {agent: wrapAsSpy(agent, {}), id: tmpFn}; }