import { CB, makeBrand, withCatch } from 'vest-utils'; import { VestRuntime } from 'vestjs-runtime'; import { useEmit, usePrepareEmitter, Subscribe } from '../core/VestBus/VestBus'; import { useLoadSuite } from '../core/Runtime'; import { TIsolateSuite } from '../core/isolate/IsolateSuite/IsolateSuite'; import { TFieldName, TGroupName, TSchema, InferSchemaData, } from '../suiteResult/SuiteResultTypes'; import { bindSuiteSelectors } from '../suiteResult/selectors/suiteSelectors'; import { useCreateSuiteResult } from '../suiteResult/suiteResult'; import { SuiteModifiers, SuiteCallbackWithSchema } from './SuiteTypes'; import { useDeferDoneCallback } from './after/deferDoneCallback'; import { createSuite } from './createSuite'; import { getStandardSchema } from './getStandardSchema'; import { getTypedMethods } from './getTypedMethods'; import { useCreateSuiteRunner } from './useCreateSuiteRunner'; /** * Creates the methods available on the Suite object (e.g., run, get, reset). * * @param {Function} suiteCallback - The body of the suite. * @param {Object} modifiers - The modifiers for the suite (e.g., only). * @param {Function} subscribe - The subscribe function for the suite bus. * @returns {Object} - The suite methods. */ export function useCreateSuiteMethods< F extends TFieldName, G extends TGroupName, T extends CB = CB, S extends TSchema = undefined, >( suiteCallback: SuiteCallbackWithSchema, modifiers: SuiteModifiers, subscribe: Subscribe, schema?: S, ) { const persistedRun = VestRuntime.persist( useCreateSuiteRunner(suiteCallback, modifiers, schema), ); const staticRunner = VestRuntime.persist( createStaticRunner(suiteCallback, schema), ); return useCreateSuiteMethodsHelper({ modifiers, persistedRun, schema, staticRunner, subscribe, suiteCallback, }); } function useCreateSuiteMethodsHelper< F extends TFieldName, G extends TGroupName, T extends CB = CB, S extends TSchema = undefined, >(ctx: { suiteCallback: SuiteCallbackWithSchema; modifiers: SuiteModifiers; subscribe: Subscribe; schema?: S; persistedRun: any; staticRunner: any; }): ReturnType> & { '~standard': ReturnType>; } { const suiteMethods = useGetSuiteMethods(ctx); return { ...suiteMethods, '~standard': getStandardSchema(ctx.staticRunner), }; } function useGetSuiteMethods< F extends TFieldName, G extends TGroupName, T extends CB = CB, S extends TSchema = undefined, >(ctx: { suiteCallback: SuiteCallbackWithSchema; modifiers: SuiteModifiers; subscribe: Subscribe; schema?: S; persistedRun: any; staticRunner: any; }): any { const { suiteCallback, modifiers, subscribe, schema } = ctx; const get = VestRuntime.persist(() => useCreateSuiteResult(schema)); return { ...useGetLifecycleMethods(ctx), dump: VestRuntime.persist(VestRuntime.useAvailableRoot), get, ...bindSuiteSelectors(get), ...getTypedMethods(), // focus and only must come after the spreads to prevent spread keys from overriding them focus: VestRuntime.persist( useCreateFocus(suiteCallback, modifiers, subscribe, schema), ), only: VestRuntime.persist( useCreateOnly(suiteCallback, modifiers, subscribe, schema), ), }; } function useGetLifecycleMethods< F extends TFieldName, G extends TGroupName, T extends CB = CB, S extends TSchema = undefined, >(ctx: { suiteCallback: SuiteCallbackWithSchema; modifiers: SuiteModifiers; subscribe: Subscribe; schema?: S; persistedRun: any; staticRunner: any; }) { const { persistedRun, staticRunner, subscribe } = ctx; return { afterEach: VestRuntime.persist((cb: CB) => useAddAfterHelper(ctx, cb), ), afterField: VestRuntime.persist((fieldName: F | string, cb: CB) => useAddAfterHelper( ctx, cb, makeBrand(fieldName) as F, ), ), remove: VestRuntime.persist((fieldName: F | string) => useEmit('REMOVE_FIELD', makeBrand(fieldName)), ), reset: VestRuntime.persist(usePrepareEmitter('RESET_SUITE')), resetField: VestRuntime.persist((fieldName: F | string) => useEmit('RESET_FIELD', makeBrand(fieldName)), ), resume: VestRuntime.persist(useLoadSuite), run: persistedRun, runStatic: staticRunner, subscribe, validate: createValidate(staticRunner), }; } function useAddAfterHelper< F extends TFieldName, G extends TGroupName, T extends CB = CB, S extends TSchema = undefined, >( ctx: { suiteCallback: SuiteCallbackWithSchema; modifiers: SuiteModifiers; subscribe: Subscribe; schema?: S; persistedRun: any; staticRunner: any; }, cb: CB, fieldName?: F, ): any { useDeferDoneCallback(withCatch(cb), fieldName); return useCreateSuiteMethodsHelper(ctx); } /** * Binds the lifecycle methods to the suite methods. * e.g. after, afterField, run... * * @param {Object} methods - The suite methods. * @returns {Object} - The suite methods with lifecycle methods bound. */ export function useBindSuiteLifecycle< F extends TFieldName, G extends TGroupName, T extends CB = CB, S extends TSchema = undefined, >( methods: ReturnType>, ): ReturnType> { return { ...methods, afterEach: VestRuntime.persist(useInitCallback(methods.afterEach)), afterField: VestRuntime.persist(useInitCallback(methods.afterField)), run: VestRuntime.persist(useInitCallback(methods.run)), }; } /** * Creates a focus function that can be used to create a focused suite. * * @param {Function} suiteCallback - The body of the suite. * @param {Object} modifiers - The modifiers for the suite (e.g., only). * @param {Function} subscribe - The subscribe function for the suite bus. * @param {Object} schema - The optional schema for the suite. * @returns {Function} - The focus function. */ function useCreateFocus< F extends TFieldName, G extends TGroupName, T extends CB = CB, S extends TSchema = undefined, >( suiteCallback: SuiteCallbackWithSchema, modifiers: SuiteModifiers, subscribe: Subscribe, schema?: S, ) { return function focus(config: SuiteModifiers) { return useCreateSuiteMethods( suiteCallback, { ...modifiers, ...config }, subscribe, schema, ); }; } /** * Creates an only function that can be used to create a focused suite on a specific field. * This is a shorthand for suite.focus({ only: 'fieldName' }). * * @param {Function} suiteCallback - The body of the suite. * @param {Object} modifiers - The modifiers for the suite (e.g., only). * @param {Function} subscribe - The subscribe function for the suite bus. * @param {Object} schema - The optional schema for the suite. * @returns {Function} - The only function. */ function useCreateOnly< F extends TFieldName, G extends TGroupName, T extends CB = CB, S extends TSchema = undefined, >( suiteCallback: SuiteCallbackWithSchema, modifiers: SuiteModifiers, subscribe: Subscribe, schema?: S, ) { const focus = useCreateFocus( suiteCallback, modifiers, subscribe, schema, ); return function only(onlyField: NonNullable['only']>) { return focus({ only: onlyField }); }; } /** * Creates a static runner for the suite. * * @param {Function} suiteCallback - The body of the suite. * @param {Object} schema - The optional schema for the suite. * @returns {Function} - The static runner. */ function createStaticRunner< F extends TFieldName, G extends TGroupName, T extends CB = CB, S extends TSchema = undefined, >(suiteCallback: SuiteCallbackWithSchema, schema?: S) { return function runStatic( ...runArgs: S extends undefined ? Parameters : [data: InferSchemaData, ...args: any[]] ) { const suite = createSuite(suiteCallback, schema); return suite.run(...(runArgs as Parameters)); }; } /** * Wraps a callback to emit the 'INITIALIZING_CALLBACKS' event before execution. * This is used to ensure that any previous callbacks are cleared or handled correctly * before the new one runs. * * @param {Function} cb - The callback to wrap. * @returns {Function} - The wrapped callback. */ function useInitCallback any>(cb: U): U { return ((...args: Parameters) => { useEmit('INITIALIZING_CALLBACKS'); return cb(...args); }) as U; } function createValidate( staticRunner: any, ) { return ( ...args: S extends undefined ? [value: Parameters[0]] : [data: InferSchemaData, ...args: any[]] ) => (staticRunner as (...args: any[]) => any)(...args); }