From 88df14c1934d7b6973e052c989a4fea189986b63 Mon Sep 17 00:00:00 2001 From: Evyatar Alush Date: Mon, 11 Nov 2024 19:46:38 +0200 Subject: [PATCH] afterEach basic implementation --- .../integration.base.test.ts.snap | 2 + .../integration.stateful-async.test.ts.snap | 2 + .../integration.stateful-tests.test.ts.snap | 6 + packages/vest/src/core/Runtime.ts | 9 + packages/vest/src/core/VestBus/VestBus.ts | 7 +- .../__snapshots__/runtime.test.ts.snap | 2 + .../__tests__/__snapshots__/memo.test.ts.snap | 4 + .../__tests__/__snapshots__/test.test.ts.snap | 6 + .../__snapshots__/include.test.ts.snap | 34 ++ .../__snapshots__/focused.test.ts.snap | 4 + .../__snapshots__/staticSuite.test.ts.snap | 2 + packages/vest/src/suite/runCallbacks.ts | 15 +- .../vest/src/suiteResult/SuiteResultTypes.ts | 4 +- .../useProduceSuiteSummary.test.ts.snap | 2 + .../suiteResult/done/__tests__/done.test.ts | 445 ++++++++++++++++++ .../vest/src/suiteResult/suiteRunResult.ts | 22 +- 16 files changed, 561 insertions(+), 5 deletions(-) diff --git a/packages/vest/src/__tests__/__snapshots__/integration.base.test.ts.snap b/packages/vest/src/__tests__/__snapshots__/integration.base.test.ts.snap index 97d0c36ac..9e98c07ed 100644 --- a/packages/vest/src/__tests__/__snapshots__/integration.base.test.ts.snap +++ b/packages/vest/src/__tests__/__snapshots__/integration.base.test.ts.snap @@ -2,6 +2,8 @@ exports[`Base behavior > Should produce correct validation result 1`] = ` { + "after": [Function], + "afterEach": [Function], "done": [Function], "errorCount": 2, "errors": [ diff --git a/packages/vest/src/__tests__/__snapshots__/integration.stateful-async.test.ts.snap b/packages/vest/src/__tests__/__snapshots__/integration.stateful-async.test.ts.snap index 038af42aa..26efc4977 100644 --- a/packages/vest/src/__tests__/__snapshots__/integration.stateful-async.test.ts.snap +++ b/packages/vest/src/__tests__/__snapshots__/integration.stateful-async.test.ts.snap @@ -2,6 +2,8 @@ exports[`Stateful async tests > Merges skipped validations from previous suite 1`] = ` { + "after": [Function], + "afterEach": [Function], "done": [Function], "errorCount": 1, "errors": [ diff --git a/packages/vest/src/__tests__/__snapshots__/integration.stateful-tests.test.ts.snap b/packages/vest/src/__tests__/__snapshots__/integration.stateful-tests.test.ts.snap index 75fa4d93a..828427698 100644 --- a/packages/vest/src/__tests__/__snapshots__/integration.stateful-tests.test.ts.snap +++ b/packages/vest/src/__tests__/__snapshots__/integration.stateful-tests.test.ts.snap @@ -2,6 +2,8 @@ exports[`Stateful behavior > Should merge skipped fields with previous values 1`] = ` { + "after": [Function], + "afterEach": [Function], "done": [Function], "errorCount": 1, "errors": [ @@ -87,6 +89,8 @@ exports[`Stateful behavior > Should merge skipped fields with previous values 1` exports[`Stateful behavior > Should merge skipped fields with previous values 2`] = ` { + "after": [Function], + "afterEach": [Function], "done": [Function], "errorCount": 3, "errors": [ @@ -185,6 +189,8 @@ exports[`Stateful behavior > Should merge skipped fields with previous values 2` exports[`Stateful behavior > Should merge skipped fields with previous values 3`] = ` { + "after": [Function], + "afterEach": [Function], "done": [Function], "errorCount": 4, "errors": [ diff --git a/packages/vest/src/core/Runtime.ts b/packages/vest/src/core/Runtime.ts index 4d389cbaf..14a8f2637 100644 --- a/packages/vest/src/core/Runtime.ts +++ b/packages/vest/src/core/Runtime.ts @@ -21,6 +21,7 @@ import { export type DoneCallback = (res: SuiteResult) => void; type FieldCallbacks = Record; type DoneCallbacks = Array; +type AfterEachCallbaks = Array; type FailuresCache = { [Severity.ERRORS]: Record; [Severity.WARNINGS]: Record; @@ -33,6 +34,7 @@ export type PreAggCache = { type StateExtra = { doneCallbacks: TinyState; fieldCallbacks: TinyState; + afterEachCallbacks: TinyState; suiteName: Maybe; suiteId: string; suiteResultCache: CacheApi>; @@ -49,6 +51,7 @@ export function useCreateVestState({ VestReconciler: IRecociler; }) { const stateRef: StateExtra = { + afterEachCallbacks: tinyState.createTinyState(() => []), doneCallbacks: tinyState.createTinyState(() => []), fieldCallbacks: tinyState.createTinyState(() => ({})), preAggCache, @@ -72,6 +75,10 @@ export function useFieldCallbacks() { return useX().fieldCallbacks(); } +export function useAfterEachCallbacks() { + return useX().afterEachCallbacks(); +} + export function useSuiteName() { return useX().suiteName; } @@ -107,9 +114,11 @@ export function useExpireSuiteResultCache() { export function useResetCallbacks() { const [, , resetDoneCallbacks] = useDoneCallbacks(); const [, , resetFieldCallbacks] = useFieldCallbacks(); + const [, , resetAfterEachCallbacks] = useAfterEachCallbacks(); resetDoneCallbacks(); resetFieldCallbacks(); + resetAfterEachCallbacks(); } export function useResetSuite() { diff --git a/packages/vest/src/core/VestBus/VestBus.ts b/packages/vest/src/core/VestBus/VestBus.ts index 0714264b9..0272c7be0 100644 --- a/packages/vest/src/core/VestBus/VestBus.ts +++ b/packages/vest/src/core/VestBus/VestBus.ts @@ -14,7 +14,11 @@ import { TestWalker } from 'TestWalker'; import { VestIsolate } from 'VestIsolate'; import { VestTest } from 'VestTest'; import { useOmitOptionalFields } from 'omitOptionalFields'; -import { useRunDoneCallbacks, useRunFieldCallbacks } from 'runCallbacks'; +import { + useRunAfterEachCallbacks, + useRunDoneCallbacks, + useRunFieldCallbacks, +} from 'runCallbacks'; // eslint-disable-next-line max-statements, max-lines-per-function export function useInitVestBus() { @@ -45,6 +49,7 @@ export function useInitVestBus() { const { fieldName } = VestTest.getData(isolate); useRunFieldCallbacks(fieldName); + useRunAfterEachCallbacks(fieldName); } } diff --git a/packages/vest/src/core/__tests__/__snapshots__/runtime.test.ts.snap b/packages/vest/src/core/__tests__/__snapshots__/runtime.test.ts.snap index 07e283703..5d431162a 100644 --- a/packages/vest/src/core/__tests__/__snapshots__/runtime.test.ts.snap +++ b/packages/vest/src/core/__tests__/__snapshots__/runtime.test.ts.snap @@ -206,6 +206,8 @@ exports[`useLoadSuite > Calling useLoadSuite should resume from loaded state 1`] "key": null, "keys": null, "output": { + "after": [Function], + "afterEach": [Function], "done": [Function], "errorCount": 4, "errors": [ diff --git a/packages/vest/src/core/test/__tests__/__snapshots__/memo.test.ts.snap b/packages/vest/src/core/test/__tests__/__snapshots__/memo.test.ts.snap index ac111b588..baa0dfb01 100644 --- a/packages/vest/src/core/test/__tests__/__snapshots__/memo.test.ts.snap +++ b/packages/vest/src/core/test/__tests__/__snapshots__/memo.test.ts.snap @@ -2,6 +2,8 @@ exports[`test.memo > cache hit > Should produce correct initial result 1`] = ` { + "after": [Function], + "afterEach": [Function], "done": [Function], "errorCount": 1, "errors": [ @@ -75,6 +77,8 @@ exports[`test.memo > cache hit > Should produce correct initial result 1`] = ` exports[`test.memo > cache hit > sync > Should restore previous result on re-run 1`] = ` { + "after": [Function], + "afterEach": [Function], "done": [Function], "errorCount": 1, "errors": [ diff --git a/packages/vest/src/core/test/__tests__/__snapshots__/test.test.ts.snap b/packages/vest/src/core/test/__tests__/__snapshots__/test.test.ts.snap index 3fa46eb27..1a8ed12ef 100644 --- a/packages/vest/src/core/test/__tests__/__snapshots__/test.test.ts.snap +++ b/packages/vest/src/core/test/__tests__/__snapshots__/test.test.ts.snap @@ -30,6 +30,8 @@ exports[`Test Vest's \`test\` function > test params > creates a test without a "key": null, "keys": null, "output": { + "after": [Function], + "afterEach": [Function], "done": [Function], "errorCount": 0, "errors": [], @@ -103,6 +105,8 @@ exports[`Test Vest's \`test\` function > test params > creates a test without a "keyboardcat": [Circular], }, "output": { + "after": [Function], + "afterEach": [Function], "done": [Function], "errorCount": 0, "errors": [], @@ -174,6 +178,8 @@ exports[`Test Vest's \`test\` function > test params > creates a test without a "key": null, "keys": null, "output": { + "after": [Function], + "afterEach": [Function], "done": [Function], "errorCount": 0, "errors": [], diff --git a/packages/vest/src/hooks/__tests__/__snapshots__/include.test.ts.snap b/packages/vest/src/hooks/__tests__/__snapshots__/include.test.ts.snap index 803f9c880..657e8ec27 100644 --- a/packages/vest/src/hooks/__tests__/__snapshots__/include.test.ts.snap +++ b/packages/vest/src/hooks/__tests__/__snapshots__/include.test.ts.snap @@ -2,6 +2,8 @@ exports[`include > Field is excluded via \`skip\` > Should disregard \`include.when\` and avoid running the test 1`] = ` { + "after": [Function], + "afterEach": [Function], "done": [Function], "errorCount": 1, "errors": [ @@ -58,6 +60,8 @@ exports[`include > Field is excluded via \`skip\` > Should disregard \`include.w exports[`include > Field is excluded via \`skip\` > Should disregard \`include\` and avoid running the test 1`] = ` { + "after": [Function], + "afterEach": [Function], "done": [Function], "errorCount": 1, "errors": [ @@ -114,6 +118,8 @@ exports[`include > Field is excluded via \`skip\` > Should disregard \`include\` exports[`include > Test is excluded via \`skip.group\` > Should disregard \`include.when\` and avoid running the test 1`] = ` { + "after": [Function], + "afterEach": [Function], "done": [Function], "errorCount": 1, "errors": [ @@ -191,6 +197,8 @@ exports[`include > Test is excluded via \`skip.group\` > Should disregard \`incl exports[`include > Test is excluded via \`skip.group\` > Should disregard \`include\` and avoid running the test 1`] = ` { + "after": [Function], + "afterEach": [Function], "done": [Function], "errorCount": 1, "errors": [ @@ -268,6 +276,8 @@ exports[`include > Test is excluded via \`skip.group\` > Should disregard \`incl exports[`include > Test is excluded via \`skipWhen\` > Should disregard \`include.when\` and avoid running the matching tests 1`] = ` { + "after": [Function], + "afterEach": [Function], "done": [Function], "errorCount": 1, "errors": [ @@ -315,6 +325,8 @@ exports[`include > Test is excluded via \`skipWhen\` > Should disregard \`includ exports[`include > Test is excluded via \`skipWhen\` > Should disregard \`include\` and avoid running the matching tests 1`] = ` { + "after": [Function], + "afterEach": [Function], "done": [Function], "errorCount": 1, "errors": [ @@ -362,6 +374,8 @@ exports[`include > Test is excluded via \`skipWhen\` > Should disregard \`includ exports[`include > There is an \`onlyd\` field > \`include\` is run as-is without modifiers > Should run the included test along with the onlyd test 1`] = ` { + "after": [Function], + "afterEach": [Function], "done": [Function], "errorCount": 2, "errors": [ @@ -423,6 +437,8 @@ exports[`include > There is an \`onlyd\` field > \`include\` is run as-is withou exports[`include > There is an \`onlyd\` field > include().when() > \`when\` param is a boolean > when \`false\` > Should skip run included field 1`] = ` { + "after": [Function], + "afterEach": [Function], "done": [Function], "errorCount": 1, "errors": [ @@ -488,6 +504,8 @@ exports[`include > There is an \`onlyd\` field > include().when() > \`when\` par exports[`include > There is an \`onlyd\` field > include().when() > \`when\` param is a boolean > when \`true\` > Should run included field 1`] = ` { + "after": [Function], + "afterEach": [Function], "done": [Function], "errorCount": 2, "errors": [ @@ -558,6 +576,8 @@ exports[`include > There is an \`onlyd\` field > include().when() > \`when\` par exports[`include > There is an \`onlyd\` field > include().when() > \`when\` param is a function > Callback evaluation > Should evaluate per test run 1`] = ` { + "after": [Function], + "afterEach": [Function], "done": [Function], "errorCount": 2, "errors": [ @@ -610,6 +630,8 @@ exports[`include > There is an \`onlyd\` field > include().when() > \`when\` par exports[`include > There is an \`onlyd\` field > include().when() > \`when\` param is a function > when returning\`false\` > Should skip run included field 1`] = ` { + "after": [Function], + "afterEach": [Function], "done": [Function], "errorCount": 1, "errors": [ @@ -675,6 +697,8 @@ exports[`include > There is an \`onlyd\` field > include().when() > \`when\` par exports[`include > There is an \`onlyd\` field > include().when() > \`when\` param is a function > when returning \`true\` > Should run included field 1`] = ` { + "after": [Function], + "afterEach": [Function], "done": [Function], "errorCount": 2, "errors": [ @@ -745,6 +769,8 @@ exports[`include > There is an \`onlyd\` field > include().when() > \`when\` par exports[`include > There is an \`onlyd\` field > include().when() > \`when\` param is a string > \`when\` param is a name of a non-included field > Should avoid running the included field 1`] = ` { + "after": [Function], + "afterEach": [Function], "done": [Function], "errorCount": 1, "errors": [ @@ -810,6 +836,8 @@ exports[`include > There is an \`onlyd\` field > include().when() > \`when\` par exports[`include > There is an \`onlyd\` field > include().when() > \`when\` param is a string > \`when\` param is a name of a skipped field > Should avoid running the included field 1`] = ` { + "after": [Function], + "afterEach": [Function], "done": [Function], "errorCount": 1, "errors": [ @@ -875,6 +903,8 @@ exports[`include > There is an \`onlyd\` field > include().when() > \`when\` par exports[`include > There is an \`onlyd\` field > include().when() > \`when\` param is a string > \`when\` param is a name of an onlyd field > Should run included field along with the onlyd field 1`] = ` { + "after": [Function], + "afterEach": [Function], "done": [Function], "errorCount": 2, "errors": [ @@ -945,6 +975,8 @@ exports[`include > There is an \`onlyd\` field > include().when() > \`when\` par exports[`include > When no \`skip\` or \`only\` > include has no effect 1`] = ` { + "after": [Function], + "afterEach": [Function], "done": [Function], "errorCount": 2, "errors": [ @@ -1006,6 +1038,8 @@ exports[`include > When no \`skip\` or \`only\` > include has no effect 1`] = ` exports[`include > When no \`skip\` or \`only\` > include().when has no effect 1`] = ` { + "after": [Function], + "afterEach": [Function], "done": [Function], "errorCount": 2, "errors": [ diff --git a/packages/vest/src/hooks/focused/__tests__/__snapshots__/focused.test.ts.snap b/packages/vest/src/hooks/focused/__tests__/__snapshots__/focused.test.ts.snap index ea0ac466a..e4e7b1094 100644 --- a/packages/vest/src/hooks/focused/__tests__/__snapshots__/focused.test.ts.snap +++ b/packages/vest/src/hooks/focused/__tests__/__snapshots__/focused.test.ts.snap @@ -2,6 +2,8 @@ exports[`Top Level Focus > Top Level Skip > When passing false > Should run all fields 1`] = ` Promise { + "after": [Function], + "afterEach": [Function], "done": [Function], "dump": [Function], "errorCount": 3, @@ -84,6 +86,8 @@ Promise { exports[`Top Level Focus > Top Level Skip > When passing undefined > Should run all fields 1`] = ` Promise { + "after": [Function], + "afterEach": [Function], "done": [Function], "dump": [Function], "errorCount": 3, diff --git a/packages/vest/src/suite/__tests__/__snapshots__/staticSuite.test.ts.snap b/packages/vest/src/suite/__tests__/__snapshots__/staticSuite.test.ts.snap index e7b2b14c1..656c039e8 100644 --- a/packages/vest/src/suite/__tests__/__snapshots__/staticSuite.test.ts.snap +++ b/packages/vest/src/suite/__tests__/__snapshots__/staticSuite.test.ts.snap @@ -92,6 +92,8 @@ exports[`staticSuite > dump > should output a dump of the suite 1`] = ` "key": null, "keys": null, "output": { + "after": [Function], + "afterEach": [Function], "done": [Function], "errorCount": 3, "errors": [ diff --git a/packages/vest/src/suite/runCallbacks.ts b/packages/vest/src/suite/runCallbacks.ts index 813579805..090391a80 100644 --- a/packages/vest/src/suite/runCallbacks.ts +++ b/packages/vest/src/suite/runCallbacks.ts @@ -1,6 +1,10 @@ import { isArray, callEach } from 'vest-utils'; -import { useDoneCallbacks, useFieldCallbacks } from 'Runtime'; +import { + useAfterEachCallbacks, + useDoneCallbacks, + useFieldCallbacks, +} from 'Runtime'; import { TFieldName } from 'SuiteResultTypes'; import { SuiteWalker } from 'SuiteWalker'; @@ -26,3 +30,12 @@ export function useRunDoneCallbacks() { const [doneCallbacks] = useDoneCallbacks(); callEach(doneCallbacks); } + +export function useRunAfterEachCallbacks(fieldName: TFieldName) { + if (SuiteWalker.useHasRemainingWithTestNameMatching(fieldName)) { + return; + } + + const [afterEachCallbacks] = useAfterEachCallbacks(); + callEach(afterEachCallbacks); +} diff --git a/packages/vest/src/suiteResult/SuiteResultTypes.ts b/packages/vest/src/suiteResult/SuiteResultTypes.ts index e3b9fc05b..6e8474253 100644 --- a/packages/vest/src/suiteResult/SuiteResultTypes.ts +++ b/packages/vest/src/suiteResult/SuiteResultTypes.ts @@ -1,4 +1,4 @@ -import { Maybe, Nullable } from 'vest-utils'; +import { CB, Maybe, Nullable } from 'vest-utils'; import { Severity } from 'Severity'; import { SummaryFailure } from 'SummaryFailure'; @@ -55,6 +55,8 @@ export type SuiteRunResult< F extends TFieldName, G extends TGroupName, > = SuiteResult & { + after: Done; + afterEach: CB; done: Done; }; diff --git a/packages/vest/src/suiteResult/__tests__/__snapshots__/useProduceSuiteSummary.test.ts.snap b/packages/vest/src/suiteResult/__tests__/__snapshots__/useProduceSuiteSummary.test.ts.snap index 784f43cc2..fff055955 100644 --- a/packages/vest/src/suiteResult/__tests__/__snapshots__/useProduceSuiteSummary.test.ts.snap +++ b/packages/vest/src/suiteResult/__tests__/__snapshots__/useProduceSuiteSummary.test.ts.snap @@ -2,6 +2,8 @@ exports[`suite() > exposed methods > Should have all exposed methods 1`] = ` { + "after": [Function], + "afterEach": [Function], "done": [Function], "errorCount": 0, "errors": [], diff --git a/packages/vest/src/suiteResult/done/__tests__/done.test.ts b/packages/vest/src/suiteResult/done/__tests__/done.test.ts index fcb06248f..f35ccafa6 100644 --- a/packages/vest/src/suiteResult/done/__tests__/done.test.ts +++ b/packages/vest/src/suiteResult/done/__tests__/done.test.ts @@ -413,3 +413,448 @@ describe('done', () => { }); }); }); + +describe('after', () => { + describe('When no async tests', () => { + it('Should call done callback immediately', () => { + const result = vest.create(() => { + dummyTest.passing(); + dummyTest.passing(); + dummyTest.failing(); + dummyTest.failing(); + dummyTest.passing(); + dummyTest.failingWarning('field_2'); + })(); + + const doneCallback = vi.fn(); + const fieldDoneCallback = vi.fn(); + + result.after(doneCallback).after('field_2', fieldDoneCallback); + + expect(doneCallback).toHaveBeenCalled(); + expect(fieldDoneCallback).toHaveBeenCalled(); + }); + }); + + describe('When suite lags and callbacks are registered again', () => { + it('should only run most recent registered callbacks', async () => { + const test = []; + const suite = vest.create(() => { + test.push(dummyTest.failingAsync('test', { time: 100 })); + }); + + const doneCallback1 = vi.fn(); + const fieldDoneCallback1 = vi.fn(); + const doneCallback2 = vi.fn(); + const fieldDoneCallback2 = vi.fn(); + + suite().after(doneCallback1).after('test', fieldDoneCallback1); + await wait(10); + suite().after(doneCallback2).after('test', fieldDoneCallback2); + await wait(100); + expect(doneCallback2).toHaveBeenCalledTimes(1); + expect(fieldDoneCallback2).toHaveBeenCalledTimes(1); + expect(doneCallback1).toHaveBeenCalledTimes(0); + expect(fieldDoneCallback1).toHaveBeenCalledTimes(0); + }); + }); + + describe('When there are async tests', () => { + describe('When field name is not passed', () => { + it('Should run the done callback after all the fields finished running', () => { + const check1 = vi.fn(); + const check2 = vi.fn(); + const check3 = vi.fn(); + return TestPromise(done => { + const doneCallback = vi.fn(() => { + expect(check1).toHaveBeenCalled(); + expect(check2).toHaveBeenCalled(); + expect(check3).toHaveBeenCalled(); + done(); + }); + const result = vest.create(() => { + dummyTest.passingAsync('field_1', { time: 1000 }); + dummyTest.failingAsync('field_2', { time: 100 }); + dummyTest.passingAsync('field_3', { time: 0 }); + dummyTest.failing(); + dummyTest.passing(); + })(); + + result.after(doneCallback); + + setTimeout(() => { + expect(doneCallback).not.toHaveBeenCalled(); + check1(); + }); + setTimeout(() => { + expect(doneCallback).not.toHaveBeenCalled(); + check2(); + }, 150); + setTimeout(() => { + expect(doneCallback).not.toHaveBeenCalled(); + check3(); + }, 900); + }); + }); + }); + }); + + describe('done arguments', () => { + it('Should pass down the up to date validation result', () => { + return TestPromise(done => { + const result = vest.create(() => { + dummyTest.failing('field_1', 'error message'); + dummyTest.passing('field_2'); + dummyTest.passingAsync('field_3', { time: 0 }); + dummyTest.failingAsync('field_4', { + message: 'error_message', + time: 100, + }); + dummyTest.passingAsync('field_5', { time: 1000 }); + })(); + + result + .after('field_2', res => { + expect(res.getErrors()).toEqual({ + field_1: ['error message'], + }); + expect(res).toMatchObject({ + errorCount: 1, + groups: {}, + testCount: 5, + tests: { + field_1: { + errorCount: 1, + errors: ['error message'], + testCount: 1, + warnCount: 0, + }, + field_2: { + errorCount: 0, + testCount: 1, + warnCount: 0, + }, + field_3: { + errorCount: 0, + testCount: 1, + warnCount: 0, + }, + field_4: { + errorCount: 0, + testCount: 1, + warnCount: 0, + }, + field_5: { + errorCount: 0, + testCount: 1, + warnCount: 0, + }, + }, + warnCount: 0, + }); + }) + .after('field_3', res => { + expect(res).toMatchObject({ + errorCount: 1, + groups: {}, + testCount: 5, + tests: { + field_1: { + errorCount: 1, + errors: ['error message'], + testCount: 1, + warnCount: 0, + }, + field_2: { + errorCount: 0, + testCount: 1, + warnCount: 0, + }, + field_3: { + errorCount: 0, + testCount: 1, + warnCount: 0, + }, + field_4: { + errorCount: 0, + testCount: 1, + warnCount: 0, + }, + field_5: { + errorCount: 0, + testCount: 1, + warnCount: 0, + }, + }, + warnCount: 0, + }); + }) + .after('field_4', res => { + expect(res.getErrors()).toEqual({ + field_1: ['error message'], + field_4: ['error_message'], + }); + expect(res).toMatchObject({ + errorCount: 2, + groups: {}, + testCount: 5, + tests: { + field_1: { + errorCount: 1, + errors: ['error message'], + testCount: 1, + warnCount: 0, + }, + field_2: { + errorCount: 0, + testCount: 1, + warnCount: 0, + }, + field_3: { + errorCount: 0, + testCount: 1, + warnCount: 0, + }, + field_4: { + errorCount: 1, + errors: ['error_message'], + testCount: 1, + warnCount: 0, + }, + field_5: { + errorCount: 0, + testCount: 1, + warnCount: 0, + }, + }, + warnCount: 0, + }); + }) + .after(res => { + expect(res).toMatchObject({ + errorCount: 2, + groups: {}, + testCount: 5, + tests: { + field_1: { + errorCount: 1, + errors: ['error message'], + testCount: 1, + warnCount: 0, + }, + field_2: { + errorCount: 0, + testCount: 1, + warnCount: 0, + }, + field_3: { + errorCount: 0, + testCount: 1, + warnCount: 0, + }, + field_4: { + errorCount: 1, + errors: ['error_message'], + testCount: 1, + warnCount: 0, + }, + field_5: { + errorCount: 0, + testCount: 1, + warnCount: 0, + }, + }, + warnCount: 0, + }); + done(); + }); + }); + }); + }); + + describe('When a different field is run while a field is pending', () => { + it('Should wait running done callbacks until all tests complete', () => { + const suite = vest.create(only => { + vest.only(only); + + vest.test('async_1', async () => { + await wait(1000); + throw new Error(); + }); + + vest.test('sync_2', () => false); + }); + + suite('async_1'); + + return TestPromise(done => { + suite('sync_2').after(res => { + expect(res.hasErrors('async_1')).toBe(true); + done(); + }); + }); + }); + }); + + describe('When suite re-runs and a pending test is now skipped', () => { + it('Should immediately call the second done callback, omit the first', async () => { + const done_0 = vi.fn(); + const done_1 = vi.fn(); + + const suite = vest.create(username => { + vest.test('username', () => { + vest.enforce(username).isNotBlank(); + }); + + vest.skipWhen(suite.get().hasErrors('username'), () => { + vest.test('username', async () => { + await wait(1000); + if (username === 'ealush') { + throw new Error(); + } + }); + }); + }); + + suite('ealush').after(done_0); + await wait(0); + expect(done_0).not.toHaveBeenCalled(); + suite('').after(done_1); + expect(done_0).not.toHaveBeenCalled(); + expect(done_1).toHaveBeenCalled(); + await wait(1000); + expect(done_0).not.toHaveBeenCalled(); + }); + }); + + describe('Passing a field that does not exist', () => { + it('Should avoid calling the callback', () => { + const cb = vi.fn(); + + const suite = vest.create(() => { + vest.test('test', () => {}); + }); + + suite().after('non-existent', cb); + + expect(cb).not.toHaveBeenCalled(); + }); + }); + + describe('When no tests are run', () => { + it('Should run the callback', () => { + const cb = vi.fn(); + + const suite = vest.create(() => {}); + + suite().after(cb); + + expect(cb).toHaveBeenCalled(); + }); + + describe('When tests are omitted', () => { + it('Should run the callback', () => { + const cb = vi.fn(); + + const suite = vest.create(() => { + vest.optional({ f1: true }); + + vest.test('f1', () => {}); + }); + + suite().after(cb); + expect(suite.get().tests.f1.testCount).toBe(0); + expect(cb).toHaveBeenCalled(); + }); + }); + }); + + describe('When focused done call does not match executed tests', () => { + it('Should not call the callback', () => { + const cb = vi.fn(); + + const suite = vest.create(() => { + vest.test('test', () => false); + }); + + suite().after('non-existent', cb); + + expect(cb).not.toHaveBeenCalled(); + }); + }); + + describe('Async Isolate', () => { + describe('When async isolate is pending', () => { + it('Should not call the callback', () => { + const cb = vi.fn(); + + const suite = vest.create(() => { + vest.test('test', () => false); + + vest.group('group', async () => { + await wait(1000); + }); + }); + + suite().after(cb); + + expect(cb).not.toHaveBeenCalled(); + }); + }); + + describe('When async isolate is completed', () => { + it('Should call the callback', async () => { + const cb = vi.fn(); + + const suite = vest.create(() => { + vest.test('test', () => false); + + vest.group('group', async () => { + await wait(1000); + }); + }); + + suite().after(cb); + await wait(1000); + expect(cb).toHaveBeenCalled(); + }); + }); + }); +}); + +describe('afterEach', () => { + it('Should run once for each of the async fields', async () => { + const fn = vi.fn(); + const suite = vest.create(() => { + vest.test('async_1', async () => { + await wait(10); + }); + vest.test('async_2', async () => { + await wait(20); + }); + vest.test('async_2', async () => { + await wait(200); + }); + vest.test('async_3', async () => { + await wait(100); + }); + vest.test('async_3', async () => { + await wait(1); + }); + vest.test('async_3', async () => { + await wait(1); + }); + vest.test('async_3', async () => { + await wait(1); + }); + }); + + suite().afterEach(() => { + fn(); + }); + + await wait(300); + + expect(fn).toHaveBeenCalledTimes(3); + }); +}); diff --git a/packages/vest/src/suiteResult/suiteRunResult.ts b/packages/vest/src/suiteResult/suiteRunResult.ts index fd6f7357d..cc305988a 100644 --- a/packages/vest/src/suiteResult/suiteRunResult.ts +++ b/packages/vest/src/suiteResult/suiteRunResult.ts @@ -1,6 +1,7 @@ -import { freezeAssign } from 'vest-utils'; +import { CB, freezeAssign } from 'vest-utils'; import { VestRuntime } from 'vestjs-runtime'; +import { useAfterEachCallbacks } from 'Runtime'; import { SuiteResult, SuiteRunResult, @@ -16,14 +17,31 @@ export function useSuiteRunResult< F extends TFieldName, G extends TGroupName, >(): SuiteRunResult { + const persistedDone = VestRuntime.persist(done) as Done; + return freezeAssign>( { - done: VestRuntime.persist(done) as Done, + afterEach: VestRuntime.persist(afterEach), + done: persistedDone, + after: persistedDone, }, useCreateSuiteResult(), ); } +// @vx-allow use-use +function afterEach( + callback: CB, +): SuiteRunResult { + const output = useSuiteRunResult(); + + const [, setAfterEachCallbacks] = useAfterEachCallbacks(); + + setAfterEachCallbacks(prev => [...prev, callback]); + + return output; +} + /** * Registers done callbacks. * @register {Object} Vest output object.