diff --git a/packages/vest/src/core/Runtime.ts b/packages/vest/src/core/Runtime.ts index 6c5909ba8..caadb302d 100644 --- a/packages/vest/src/core/Runtime.ts +++ b/packages/vest/src/core/Runtime.ts @@ -10,6 +10,7 @@ import { import { IRecociler, TIsolate, VestRuntime } from 'vestjs-runtime'; import { TIsolateSuite } from 'IsolateSuite'; +import { Severity } from 'Severity'; import { SuiteName, SuiteResult, @@ -20,6 +21,14 @@ import { export type DoneCallback = (res: SuiteResult) => void; type FieldCallbacks = Record; type DoneCallbacks = Array; +type FailuresCache = { + [Severity.ERRORS]: Record; + [Severity.WARNINGS]: Record; +}; +export type PreAggCache = { + pending: TIsolate[]; + failures: FailuresCache; +}; type StateExtra = { doneCallbacks: TinyState; @@ -27,10 +36,10 @@ type StateExtra = { suiteName: Maybe; suiteId: string; suiteResultCache: CacheApi>; - pendingCache: CacheApi; + preAggCache: CacheApi; }; const suiteResultCache = cache>(); -const pendingCache = cache(); +const preAggCache = cache(); export function useCreateVestState({ suiteName, @@ -42,7 +51,7 @@ export function useCreateVestState({ const stateRef: StateExtra = { doneCallbacks: tinyState.createTinyState(() => []), fieldCallbacks: tinyState.createTinyState(() => ({})), - pendingCache, + preAggCache, suiteId: seq(), suiteName, suiteResultCache, @@ -79,20 +88,20 @@ export function useSuiteResultCache( return suiteResultCache([useSuiteId()], action) as SuiteResult; } -export function usePendingCache(action: CB) { - const pendingCache = useX().pendingCache; +export function usePreAggCache(action: CB) { + const preAggCache = useX().preAggCache; - return pendingCache([useSuiteId()], action); + return preAggCache([useSuiteId()], action); } export function useExpireSuiteResultCache() { const suiteResultCache = useX().suiteResultCache; suiteResultCache.invalidate([useSuiteId()]); - // whenever we invalidate the entire result, we also want to invalidate the pending cache + // whenever we invalidate the entire result, we also want to invalidate the preagg cache // so that we do not get stale results there. // there may be a better place to do this, but for now, this should work. - pendingCache.invalidate([useSuiteId()]); + preAggCache.invalidate([useSuiteId()]); } export function useResetCallbacks() { diff --git a/packages/vest/src/core/isolate/IsolateTest/__tests__/hasRemainingTests.test.ts b/packages/vest/src/core/isolate/IsolateTest/__tests__/hasRemainingTests.test.ts index a9079cbff..11de5a269 100644 --- a/packages/vest/src/core/isolate/IsolateTest/__tests__/hasRemainingTests.test.ts +++ b/packages/vest/src/core/isolate/IsolateTest/__tests__/hasRemainingTests.test.ts @@ -14,23 +14,20 @@ describe('SuiteWalker.useHasRemainingWithTestNameMatching', () => { describe('When no field specified', () => { describe('When no remaining tests', () => { it('should return false', () => { - vest.create(() => { - hasRemaining = SuiteWalker.useHasRemainingWithTestNameMatching(); - })(); - expect(hasRemaining).toBe(false); + const suite = vest.create(() => {})(); + expect(suite.isPending()).toBe(false); }); }); describe('When there are remaining tests', () => { it('pending tests return true', () => { - vest.create(() => { + const suite = vest.create(() => { vest.test('f1', async () => { await wait(100); }); - hasRemaining = SuiteWalker.useHasRemainingWithTestNameMatching(); })(); - expect(hasRemaining).toBe(true); + expect(suite.isPending()).toBe(true); }); it('lagging tests return true', () => { @@ -40,12 +37,11 @@ describe('SuiteWalker.useHasRemainingWithTestNameMatching', () => { await wait(100); }); count++; - hasRemaining = SuiteWalker.useHasRemainingWithTestNameMatching(); }); suite(); suite(); - expect(hasRemaining).toBe(true); + expect(suite.isPending()).toBe(true); }); it('lagging and pending tests return true', () => { @@ -58,13 +54,12 @@ describe('SuiteWalker.useHasRemainingWithTestNameMatching', () => { await wait(100); }); count++; - hasRemaining = SuiteWalker.useHasRemainingWithTestNameMatching(); }); suite(); suite(); - expect(hasRemaining).toBe(true); + expect(suite.isPending()).toBe(true); }); }); }); @@ -72,22 +67,19 @@ describe('SuiteWalker.useHasRemainingWithTestNameMatching', () => { describe('When field specified', () => { describe('When no remaining tests', () => { it('Should return false', () => { - vest.create(() => { - hasRemaining = SuiteWalker.useHasRemainingWithTestNameMatching('f1'); - })(); - expect(hasRemaining).toBe(false); + const suite = vest.create(() => {})(); + expect(suite.isPending('f1')).toBe(false); }); }); describe('When remaining tests', () => { it('pending tests return true', () => { - vest.create(() => { + const suite = vest.create(() => { vest.test('f1', async () => { await wait(100); }); - hasRemaining = SuiteWalker.useHasRemainingWithTestNameMatching('f1'); })(); - expect(hasRemaining).toBe(true); + expect(suite.isPending('f1')).toBe(true); }); it('lagging tests return true', () => { @@ -97,12 +89,11 @@ describe('SuiteWalker.useHasRemainingWithTestNameMatching', () => { await wait(100); }); count++; - hasRemaining = SuiteWalker.useHasRemainingWithTestNameMatching('f1'); }); suite(); suite(); - expect(hasRemaining).toBe(true); + expect(suite.isPending('f1')).toBe(true); }); it('lagging and pending tests return true', () => { @@ -115,15 +106,13 @@ describe('SuiteWalker.useHasRemainingWithTestNameMatching', () => { await wait(100); }); count++; - hasRemaining = - SuiteWalker.useHasRemainingWithTestNameMatching('f1') && - SuiteWalker.useHasRemainingWithTestNameMatching('f2'); }); suite(); suite(); - expect(hasRemaining).toBe(true); + expect(suite.isPending('f1')).toBe(true); + expect(suite.isPending('f2')).toBe(true); }); }); }); diff --git a/packages/vest/src/suite/SuiteWalker.ts b/packages/vest/src/suite/SuiteWalker.ts index 83452103f..1ebb15231 100644 --- a/packages/vest/src/suite/SuiteWalker.ts +++ b/packages/vest/src/suite/SuiteWalker.ts @@ -1,8 +1,8 @@ -import { Predicate, Predicates, isNullish } from 'vest-utils'; +import { Predicate, Predicates, isEmpty, isNullish } from 'vest-utils'; import { TIsolate, VestRuntime, Walker } from 'vestjs-runtime'; import { TIsolateTest } from 'IsolateTest'; -import { usePendingCache } from 'Runtime'; +import { PreAggCache, usePreAggCache } from 'Runtime'; import { TFieldName } from 'SuiteResultTypes'; import { VestIsolate } from 'VestIsolate'; import { VestTest } from 'VestTest'; @@ -18,23 +18,17 @@ export class SuiteWalker { return false; } - const allPending = usePendingCache(this.findAllPending); + const allPending = SuiteWalker.usePreAggs().pending; - if (!allPending.length) { + if (isEmpty(allPending)) { return false; } return allPending.some(Predicates.all(predicate ?? true)); } - static findAllPending(): TIsolate[] { - const root = SuiteWalker.defaultRoot(); - - if (!root) { - return []; - } - - return Walker.findAll(root, VestIsolate.isPending); + static usePreAggs() { + return usePreAggCache(buildPreAggCache); } // Checks whether there are pending isolates in the tree. @@ -53,3 +47,47 @@ export class SuiteWalker { ); } } + +function buildPreAggCache(): PreAggCache { + const root = SuiteWalker.defaultRoot(); + + const base: PreAggCache = { + pending: [], + failures: { + errors: {}, + warnings: {}, + }, + }; + + if (!root) { + return base; + } + + return Walker.reduce( + root, + // eslint-disable-next-line complexity, max-statements + (agg, isolate: TIsolate) => { + if (VestIsolate.isPending(isolate)) { + agg.pending.push(isolate); + } + + if (VestTest.is(isolate)) { + const fieldName = VestTest.getData(isolate).fieldName; + + if (VestTest.isWarning(isolate)) { + agg.failures.warnings[fieldName] = + agg.failures.warnings[fieldName] ?? []; + agg.failures.warnings[fieldName].push(isolate); + } + + if (VestTest.isFailing(isolate)) { + agg.failures.errors[fieldName] = agg.failures.errors[fieldName] ?? []; + agg.failures.errors[fieldName].push(isolate); + } + } + + return agg; + }, + base, + ); +} diff --git a/packages/vest/src/suiteResult/selectors/hasFailuresByTestObjects.ts b/packages/vest/src/suiteResult/selectors/hasFailuresByTestObjects.ts index 98418a9cf..d83df1e25 100644 --- a/packages/vest/src/suiteResult/selectors/hasFailuresByTestObjects.ts +++ b/packages/vest/src/suiteResult/selectors/hasFailuresByTestObjects.ts @@ -1,7 +1,9 @@ +import { isEmpty } from 'vest-utils'; import { TIsolateTest } from 'IsolateTest'; import { Severity } from 'Severity'; import { TFieldName, TGroupName } from 'SuiteResultTypes'; +import { SuiteWalker } from 'SuiteWalker'; import { TestWalker } from 'TestWalker'; import { VestTest } from 'VestTest'; import { nonMatchingFieldName } from 'matchingFieldName'; @@ -19,17 +21,25 @@ export function hasErrorsByTestObjects(fieldName?: TFieldName): boolean { function hasFailuresByTestObjects( severityKey: Severity, - fieldName?: TFieldName + fieldName?: TFieldName, ): boolean { - return TestWalker.someTests(testObject => { - return hasFailuresByTestObject(testObject, severityKey, fieldName); - }); + const allFailures = SuiteWalker.usePreAggs().failures; + + if (isEmpty(allFailures[severityKey])) { + return false; + } + + if (fieldName) { + return !isEmpty(allFailures[severityKey][fieldName]); + } + + return true; } export function hasGroupFailuresByTestObjects( severityKey: Severity, groupName: TGroupName, - fieldName?: TFieldName + fieldName?: TFieldName, ): boolean { return TestWalker.someTests(testObject => { if (nonMatchingGroupName(testObject, groupName)) { @@ -46,7 +56,7 @@ export function hasGroupFailuresByTestObjects( export function hasFailuresByTestObject( testObject: TIsolateTest, severityKey: Severity, - fieldName?: TFieldName + fieldName?: TFieldName, ): boolean { if (!VestTest.hasFailures(testObject)) { return false;