From db634ea39271da590d64079b46a97184ccc63bfb Mon Sep 17 00:00:00 2001 From: Hiroshi Ogawa Date: Wed, 30 Oct 2024 12:38:58 +0900 Subject: [PATCH 01/21] wip: rework snapshot state --- packages/snapshot/src/client.ts | 42 ++++++++++++++++++++- packages/snapshot/src/manager.ts | 1 + packages/snapshot/src/port/state.ts | 1 + packages/vitest/src/runtime/runners/test.ts | 34 ++++++++++------- 4 files changed, 64 insertions(+), 14 deletions(-) diff --git a/packages/snapshot/src/client.ts b/packages/snapshot/src/client.ts index cfa9508eb2d3..00a5e57fe8d9 100644 --- a/packages/snapshot/src/client.ts +++ b/packages/snapshot/src/client.ts @@ -50,8 +50,10 @@ export interface SnapshotClientOptions { } export class SnapshotClient { + // TODO: remove state filepath?: string name?: string + // TODO: do we need file map here? snapshotState: SnapshotState | undefined snapshotStateMap: Map = new Map() @@ -65,6 +67,7 @@ export class SnapshotClient { this.filepath = filepath this.name = name + // TODO: remove and make it explicit if (this.snapshotState?.testFilePath !== filepath) { await this.finishCurrentRun() @@ -78,8 +81,44 @@ export class SnapshotClient { } } + async setup( + filepath: string, + options: SnapshotStateOptions, + ): Promise { + if (this.snapshotStateMap.has(filepath)) { + throw new Error('already setup') + } + this.snapshotStateMap.set( + filepath, + await SnapshotState.create(filepath, options), + ) + } + + async finish(filepath: string): Promise { + const state = this.snapshotStateMap.get(filepath) + if (!state) { + throw new Error('missing setup') + } + const result = await state.pack() + this.snapshotStateMap.delete(filepath) + return result + } + + // TODO: by test id + skip(filepath: string, name: string): void { + const state = this.snapshotStateMap.get(filepath) + if (!state) { + throw new Error('missing setup') + } + state.markSnapshotsAsCheckedForTest(name) + } + getSnapshotState(filepath: string): SnapshotState { - return this.snapshotStateMap.get(filepath)! + const state = this.snapshotStateMap.get(filepath) + if (!state) { + throw new Error('snapshot state not initialized') + } + return state } clearTest(): void { @@ -91,6 +130,7 @@ export class SnapshotClient { this.snapshotState?.markSnapshotsAsCheckedForTest(name) } + // TODO: add test id assert(options: AssertOptions): void { const { filepath = this.filepath, diff --git a/packages/snapshot/src/manager.ts b/packages/snapshot/src/manager.ts index 5a4caa0029af..87ffe7a199da 100644 --- a/packages/snapshot/src/manager.ts +++ b/packages/snapshot/src/manager.ts @@ -23,6 +23,7 @@ export class SnapshotManager { addSnapshotResult(this.summary, result) } + // TODO: can remove in favor of SnapshotEnvironment.resolvePath? resolvePath(testPath: string): string { const resolver = this.options.resolveSnapshotPath || (() => { diff --git a/packages/snapshot/src/port/state.ts b/packages/snapshot/src/port/state.ts index d12f66746068..232a3c980040 100644 --- a/packages/snapshot/src/port/state.ts +++ b/packages/snapshot/src/port/state.ts @@ -50,6 +50,7 @@ export default class SnapshotState { private _counters: Map private _dirty: boolean private _updateSnapshot: SnapshotUpdateState + // TODO: key by test id. how to match with `initialData`? private _snapshotData: SnapshotData private _initialData: SnapshotData private _inlineSnapshots: Array diff --git a/packages/vitest/src/runtime/runners/test.ts b/packages/vitest/src/runtime/runners/test.ts index 642fd526c8f2..9286f4b6c37e 100644 --- a/packages/vitest/src/runtime/runners/test.ts +++ b/packages/vitest/src/runtime/runners/test.ts @@ -45,7 +45,7 @@ export class VitestTestRunner implements VitestRunner { } onBeforeRunFiles() { - this.snapshotClient.clear() + // this.snapshotClient.clear() } onAfterRunFiles() { @@ -62,21 +62,24 @@ export class VitestTestRunner implements VitestRunner { for (const test of getTests(suite)) { if (test.mode === 'skip') { const name = getNames(test).slice(1).join(' > ') - this.snapshotClient.skipTestSnapshots(name) + // this.snapshotClient.skipTestSnapshots(name) + this.snapshotClient.skip(suite.file.filepath, name) } } - const result = await this.snapshotClient.finishCurrentRun() - if (result) { - await rpc().snapshotSaved(result) - } + const result = await this.snapshotClient.finish(suite.file.filepath) + await rpc().snapshotSaved(result) + // const result = await this.snapshotClient.finishCurrentRun() + // if (result) { + // await rpc().snapshotSaved(result) + // } } this.workerState.current = suite.suite || suite.file } onAfterRunTask(test: Task) { - this.snapshotClient.clearTest() + // this.snapshotClient.clearTest() if (this.config.logHeapUsage && typeof process !== 'undefined') { test.result!.heap = process.memoryUsage().heapUsed @@ -110,13 +113,17 @@ export class VitestTestRunner implements VitestRunner { // initialize snapshot state before running file suite if (suite.mode !== 'skip' && 'filepath' in suite) { - // default "name" is irrelevant for Vitest since each snapshot assertion - // (e.g. `toMatchSnapshot`) specifies "filepath" / "name" pair explicitly - await this.snapshotClient.startCurrentRun( - (suite as File).filepath, - '__default_name_', + await this.snapshotClient.setup( + suite.file.filepath, this.workerState.config.snapshotOptions, ) + // // default "name" is irrelevant for Vitest since each snapshot assertion + // // (e.g. `toMatchSnapshot`) specifies "filepath" / "name" pair explicitly + // await this.snapshotClient.startCurrentRun( + // (suite as File).filepath, + // '__default_name_', + // this.workerState.config.snapshotOptions, + // ) } this.workerState.current = suite @@ -132,7 +139,8 @@ export class VitestTestRunner implements VitestRunner { expectedAssertionsNumberErrorGen: null, testPath: test.file.filepath, currentTestName: getTestName(test), - snapshotState: this.snapshotClient.snapshotState, + snapshotState: this.snapshotClient.getSnapshotState(test.file.filepath), + // snapshotState: this.snapshotClient.snapshotState, }, (globalThis as any)[GLOBAL_EXPECT], ) From 7cf11f5fbf27b264e9f7d97e3dfdf30c0014a482 Mon Sep 17 00:00:00 2001 From: Hiroshi Ogawa Date: Wed, 30 Oct 2024 12:46:05 +0900 Subject: [PATCH 02/21] wip: remove more --- packages/snapshot/src/client.ts | 85 +++++-------------- .../vitest/src/integrations/snapshot/chai.ts | 15 ++-- packages/vitest/src/runtime/runners/test.ts | 24 +----- 3 files changed, 29 insertions(+), 95 deletions(-) diff --git a/packages/snapshot/src/client.ts b/packages/snapshot/src/client.ts index 00a5e57fe8d9..1daaaa407061 100644 --- a/packages/snapshot/src/client.ts +++ b/packages/snapshot/src/client.ts @@ -34,8 +34,8 @@ export interface Context { interface AssertOptions { received: unknown - filepath?: string - name?: string + filepath: string + name: string message?: string isInline?: boolean properties?: object @@ -50,37 +50,10 @@ export interface SnapshotClientOptions { } export class SnapshotClient { - // TODO: remove state - filepath?: string - name?: string - // TODO: do we need file map here? - snapshotState: SnapshotState | undefined snapshotStateMap: Map = new Map() constructor(private options: SnapshotClientOptions = {}) {} - async startCurrentRun( - filepath: string, - name: string, - options: SnapshotStateOptions, - ): Promise { - this.filepath = filepath - this.name = name - - // TODO: remove and make it explicit - if (this.snapshotState?.testFilePath !== filepath) { - await this.finishCurrentRun() - - if (!this.getSnapshotState(filepath)) { - this.snapshotStateMap.set( - filepath, - await SnapshotState.create(filepath, options), - ) - } - this.snapshotState = this.getSnapshotState(filepath) - } - } - async setup( filepath: string, options: SnapshotStateOptions, @@ -95,24 +68,24 @@ export class SnapshotClient { } async finish(filepath: string): Promise { - const state = this.snapshotStateMap.get(filepath) - if (!state) { - throw new Error('missing setup') - } + const state = this.getSnapshotState(filepath) const result = await state.pack() this.snapshotStateMap.delete(filepath) return result } - // TODO: by test id - skip(filepath: string, name: string): void { - const state = this.snapshotStateMap.get(filepath) - if (!state) { - throw new Error('missing setup') - } + skipTest(filepath: string, name: string): void { + const state = this.getSnapshotState(filepath) state.markSnapshotsAsCheckedForTest(name) } + // TODO: can we use name instead of test.id? + clearTest(filepath: string, name: string): void { + const state = this.getSnapshotState(filepath) + state + name + } + getSnapshotState(filepath: string): SnapshotState { const state = this.snapshotStateMap.get(filepath) if (!state) { @@ -121,20 +94,10 @@ export class SnapshotClient { return state } - clearTest(): void { - this.filepath = undefined - this.name = undefined - } - - skipTestSnapshots(name: string): void { - this.snapshotState?.markSnapshotsAsCheckedForTest(name) - } - - // TODO: add test id assert(options: AssertOptions): void { const { - filepath = this.filepath, - name = this.name, + filepath, + name, message, isInline = false, properties, @@ -149,6 +112,8 @@ export class SnapshotClient { throw new Error('Snapshot cannot be used outside of test') } + const snapshotState = this.getSnapshotState(filepath) + if (typeof properties === 'object') { if (typeof received !== 'object' || !received) { throw new Error( @@ -162,7 +127,7 @@ export class SnapshotClient { if (!pass) { throw createMismatchError( 'Snapshot properties mismatched', - this.snapshotState?.expand, + snapshotState.expand, received, properties, ) @@ -179,8 +144,6 @@ export class SnapshotClient { const testName = [name, ...(message ? [message] : [])].join(' > ') - const snapshotState = this.getSnapshotState(filepath) - const { actual, expected, key, pass } = snapshotState.match({ testName, received, @@ -193,7 +156,7 @@ export class SnapshotClient { if (!pass) { throw createMismatchError( `Snapshot \`${key || 'unknown'}\` mismatched`, - this.snapshotState?.expand, + snapshotState.expand, actual?.trim(), expected?.trim(), ) @@ -205,7 +168,7 @@ export class SnapshotClient { throw new Error('Raw snapshot is required') } - const { filepath = this.filepath, rawSnapshot } = options + const { filepath, rawSnapshot } = options if (rawSnapshot.content == null) { if (!filepath) { @@ -229,16 +192,6 @@ export class SnapshotClient { return this.assert(options) } - async finishCurrentRun(): Promise { - if (!this.snapshotState) { - return null - } - const result = await this.snapshotState.pack() - - this.snapshotState = undefined - return result - } - clear(): void { this.snapshotStateMap.clear() } diff --git a/packages/vitest/src/integrations/snapshot/chai.ts b/packages/vitest/src/integrations/snapshot/chai.ts index 3837d2b88d20..b7e89077bb69 100644 --- a/packages/vitest/src/integrations/snapshot/chai.ts +++ b/packages/vitest/src/integrations/snapshot/chai.ts @@ -44,10 +44,7 @@ function getError(expected: () => void | Error, promise: string | undefined) { throw new Error('snapshot function didn\'t throw') } -function getTestNames(test?: Test) { - if (!test) { - return {} - } +function getTestNames(test: Test) { return { filepath: test.file.filepath, name: getNames(test).slice(1).join(' > '), @@ -69,7 +66,7 @@ export const SnapshotPlugin: ChaiPlugin = (chai, utils) => { throw new Error(`${key} cannot be used with "not"`) } const expected = utils.flag(this, 'object') - const test = utils.flag(this, 'vitest-test') + const test: Test = utils.flag(this, 'vitest-test') if (typeof properties === 'string' && typeof message === 'undefined') { message = properties properties = undefined @@ -96,7 +93,7 @@ export const SnapshotPlugin: ChaiPlugin = (chai, utils) => { throw new Error('toMatchFileSnapshot cannot be used with "not"') } const expected = utils.flag(this, 'object') - const test = utils.flag(this, 'vitest-test') as Test + const test: Test = utils.flag(this, 'vitest-test') const errorMessage = utils.flag(this, 'message') const promise = getSnapshotClient().assertRaw({ @@ -127,7 +124,7 @@ export const SnapshotPlugin: ChaiPlugin = (chai, utils) => { if (isNot) { throw new Error('toMatchInlineSnapshot cannot be used with "not"') } - const test = utils.flag(this, 'vitest-test') + const test: Test = utils.flag(this, 'vitest-test') const isInsideEach = test && (test.each || test.suite?.each) if (isInsideEach) { throw new Error( @@ -169,7 +166,7 @@ export const SnapshotPlugin: ChaiPlugin = (chai, utils) => { ) } const expected = utils.flag(this, 'object') - const test = utils.flag(this, 'vitest-test') + const test: Test = utils.flag(this, 'vitest-test') const promise = utils.flag(this, 'promise') as string | undefined const errorMessage = utils.flag(this, 'message') getSnapshotClient().assert({ @@ -194,7 +191,7 @@ export const SnapshotPlugin: ChaiPlugin = (chai, utils) => { 'toThrowErrorMatchingInlineSnapshot cannot be used with "not"', ) } - const test = utils.flag(this, 'vitest-test') + const test: Test = utils.flag(this, 'vitest-test') const isInsideEach = test && (test.each || test.suite?.each) if (isInsideEach) { throw new Error( diff --git a/packages/vitest/src/runtime/runners/test.ts b/packages/vitest/src/runtime/runners/test.ts index 9286f4b6c37e..d9a96d6cea53 100644 --- a/packages/vitest/src/runtime/runners/test.ts +++ b/packages/vitest/src/runtime/runners/test.ts @@ -44,11 +44,8 @@ export class VitestTestRunner implements VitestRunner { this.workerState.current = file } - onBeforeRunFiles() { - // this.snapshotClient.clear() - } - onAfterRunFiles() { + this.snapshotClient.clear() this.workerState.current = undefined } @@ -62,25 +59,19 @@ export class VitestTestRunner implements VitestRunner { for (const test of getTests(suite)) { if (test.mode === 'skip') { const name = getNames(test).slice(1).join(' > ') - // this.snapshotClient.skipTestSnapshots(name) - this.snapshotClient.skip(suite.file.filepath, name) + // TODO: skip by test.id + this.snapshotClient.skipTest(suite.file.filepath, name) } } const result = await this.snapshotClient.finish(suite.file.filepath) await rpc().snapshotSaved(result) - // const result = await this.snapshotClient.finishCurrentRun() - // if (result) { - // await rpc().snapshotSaved(result) - // } } this.workerState.current = suite.suite || suite.file } onAfterRunTask(test: Task) { - // this.snapshotClient.clearTest() - if (this.config.logHeapUsage && typeof process !== 'undefined') { test.result!.heap = process.memoryUsage().heapUsed } @@ -117,19 +108,13 @@ export class VitestTestRunner implements VitestRunner { suite.file.filepath, this.workerState.config.snapshotOptions, ) - // // default "name" is irrelevant for Vitest since each snapshot assertion - // // (e.g. `toMatchSnapshot`) specifies "filepath" / "name" pair explicitly - // await this.snapshotClient.startCurrentRun( - // (suite as File).filepath, - // '__default_name_', - // this.workerState.config.snapshotOptions, - // ) } this.workerState.current = suite } onBeforeTryTask(test: Task) { + this.snapshotClient.clearTest(test.file.filepath, test.name) setState( { assertionCalls: 0, @@ -140,7 +125,6 @@ export class VitestTestRunner implements VitestRunner { testPath: test.file.filepath, currentTestName: getTestName(test), snapshotState: this.snapshotClient.getSnapshotState(test.file.filepath), - // snapshotState: this.snapshotClient.snapshotState, }, (globalThis as any)[GLOBAL_EXPECT], ) From 047badf842ce20e23d9e598e75641ce1b53ad768 Mon Sep 17 00:00:00 2001 From: Hiroshi Ogawa Date: Wed, 30 Oct 2024 14:14:22 +0900 Subject: [PATCH 03/21] wip: test --- test/core/test/repro.test.ts | 20 ++++++++++++++++++++ 1 file changed, 20 insertions(+) create mode 100644 test/core/test/repro.test.ts diff --git a/test/core/test/repro.test.ts b/test/core/test/repro.test.ts new file mode 100644 index 000000000000..e5d9f09f9c57 --- /dev/null +++ b/test/core/test/repro.test.ts @@ -0,0 +1,20 @@ +import { expect, test } from 'vitest' + +test('repro', () => { + expect +}) + +// test('inline repeats', { repeats: 1 }, () => { +// expect('foo').toMatchInlineSnapshot(`"foo"`); +// }); + +// test('inline retry', { retry: 1 }, (ctx) => { +// expect('foo').toMatchInlineSnapshot(`"foo"`) +// if (ctx.task.result?.retryCount === 0) { +// throw new Error('boom') +// } +// }) + +// test('foo', { retry: 1 }, () => { +// expect('foo').toMatchSnapshot() +// }); From 0aea548fe31ae22626c0dac36c89c0553f9b9cc5 Mon Sep 17 00:00:00 2001 From: Hiroshi Ogawa Date: Wed, 30 Oct 2024 14:27:40 +0900 Subject: [PATCH 04/21] wip: clear inline snapshots --- packages/snapshot/src/client.ts | 5 ++--- packages/snapshot/src/port/inlineSnapshot.ts | 1 + packages/snapshot/src/port/state.ts | 14 +++++++++++--- test/core/test/repro.test.ts | 20 ++++++++++---------- 4 files changed, 24 insertions(+), 16 deletions(-) diff --git a/packages/snapshot/src/client.ts b/packages/snapshot/src/client.ts index 1daaaa407061..755c93da53cf 100644 --- a/packages/snapshot/src/client.ts +++ b/packages/snapshot/src/client.ts @@ -79,11 +79,10 @@ export class SnapshotClient { state.markSnapshotsAsCheckedForTest(name) } - // TODO: can we use name instead of test.id? + // TODO: use test.id to handle multiple tests with same name clearTest(filepath: string, name: string): void { const state = this.getSnapshotState(filepath) - state - name + state.clearTest(name) } getSnapshotState(filepath: string): SnapshotState { diff --git a/packages/snapshot/src/port/inlineSnapshot.ts b/packages/snapshot/src/port/inlineSnapshot.ts index 2f252017fa88..c8474448b4ee 100644 --- a/packages/snapshot/src/port/inlineSnapshot.ts +++ b/packages/snapshot/src/port/inlineSnapshot.ts @@ -10,6 +10,7 @@ import { export interface InlineSnapshot { snapshot: string file: string + testName: string line: number column: number } diff --git a/packages/snapshot/src/port/state.ts b/packages/snapshot/src/port/state.ts index 232a3c980040..0f80174bedfb 100644 --- a/packages/snapshot/src/port/state.ts +++ b/packages/snapshot/src/port/state.ts @@ -54,7 +54,7 @@ export default class SnapshotState { private _snapshotData: SnapshotData private _initialData: SnapshotData private _inlineSnapshots: Array - private _inlineSnapshotStacks: Array + private _inlineSnapshotStacks: Array private _rawSnapshots: Array private _uncheckedKeys: Set private _snapshotFormat: PrettyFormatOptions @@ -119,6 +119,11 @@ export default class SnapshotState { }) } + clearTest(testName: string): void { + this._inlineSnapshots = this._inlineSnapshots.filter(s => s.testName !== testName) + this._inlineSnapshotStacks = this._inlineSnapshotStacks.filter(s => s.testName !== testName) + } + protected _inferInlineSnapshotStack(stacks: ParsedStack[]): ParsedStack | null { // if called inside resolves/rejects, stacktrace is different const promiseIndex = stacks.findIndex(i => @@ -139,12 +144,13 @@ export default class SnapshotState { private _addSnapshot( key: string, receivedSerialized: string, - options: { rawSnapshot?: RawSnapshotInfo; stack?: ParsedStack }, + options: { rawSnapshot?: RawSnapshotInfo; stack?: ParsedStack; testName: string }, ): void { this._dirty = true if (options.stack) { this._inlineSnapshots.push({ snapshot: receivedSerialized, + testName: options.testName, ...options.stack, }) } @@ -321,7 +327,7 @@ export default class SnapshotState { this._inlineSnapshots = this._inlineSnapshots.filter(s => !(s.file === stack!.file && s.line === stack!.line && s.column === stack!.column)) throw new Error('toMatchInlineSnapshot cannot be called multiple times at the same location.') } - this._inlineSnapshotStacks.push(stack) + this._inlineSnapshotStacks.push({ ...stack, testName }) } // These are the conditions on when to write snapshots: @@ -347,6 +353,7 @@ export default class SnapshotState { this._addSnapshot(key, receivedSerialized, { stack, + testName, rawSnapshot, }) } @@ -357,6 +364,7 @@ export default class SnapshotState { else { this._addSnapshot(key, receivedSerialized, { stack, + testName, rawSnapshot, }) this.added++ diff --git a/test/core/test/repro.test.ts b/test/core/test/repro.test.ts index e5d9f09f9c57..6cec0536c6f9 100644 --- a/test/core/test/repro.test.ts +++ b/test/core/test/repro.test.ts @@ -1,19 +1,19 @@ import { expect, test } from 'vitest' test('repro', () => { - expect + expect(0).toBe(0) }) -// test('inline repeats', { repeats: 1 }, () => { -// expect('foo').toMatchInlineSnapshot(`"foo"`); -// }); +test('inline repeats', { repeats: 1 }, () => { + expect('foo').toMatchInlineSnapshot(`"foo"`) +}) -// test('inline retry', { retry: 1 }, (ctx) => { -// expect('foo').toMatchInlineSnapshot(`"foo"`) -// if (ctx.task.result?.retryCount === 0) { -// throw new Error('boom') -// } -// }) +test('inline retry', { retry: 1 }, (ctx) => { + expect('foo').toMatchInlineSnapshot(`"foo"`) + if (ctx.task.result?.retryCount === 0) { + throw new Error('boom') + } +}) // test('foo', { retry: 1 }, () => { // expect('foo').toMatchSnapshot() From 4c835edca783f9157765c7b5cd1c7c1a6087cf6e Mon Sep 17 00:00:00 2001 From: Hiroshi Ogawa Date: Wed, 30 Oct 2024 15:04:36 +0900 Subject: [PATCH 05/21] wip: clear file --- packages/snapshot/src/port/state.ts | 11 ++++++ .../test/__snapshots__/repro.test.ts.snap | 13 +++++++ test/core/test/repro.test.ts | 35 +++++++++++++++++-- 3 files changed, 56 insertions(+), 3 deletions(-) create mode 100644 test/core/test/__snapshots__/repro.test.ts.snap diff --git a/packages/snapshot/src/port/state.ts b/packages/snapshot/src/port/state.ts index 0f80174bedfb..85ba10fedefa 100644 --- a/packages/snapshot/src/port/state.ts +++ b/packages/snapshot/src/port/state.ts @@ -122,6 +122,17 @@ export default class SnapshotState { clearTest(testName: string): void { this._inlineSnapshots = this._inlineSnapshots.filter(s => s.testName !== testName) this._inlineSnapshotStacks = this._inlineSnapshotStacks.filter(s => s.testName !== testName) + if (this._counters.has(testName)) { + let counter = this._counters.get(testName)! + for (const key in this._snapshotData) { + if (keyToTestName(key) === testName) { + counter-- + this._snapshotData[key] = this._initialData[key] + this._uncheckedKeys.add(key) + } + } + this._counters.set(testName, counter) + } } protected _inferInlineSnapshotStack(stacks: ParsedStack[]): ParsedStack | null { diff --git a/test/core/test/__snapshots__/repro.test.ts.snap b/test/core/test/__snapshots__/repro.test.ts.snap new file mode 100644 index 000000000000..a368365c6eda --- /dev/null +++ b/test/core/test/__snapshots__/repro.test.ts.snap @@ -0,0 +1,13 @@ +// Vitest Snapshot v1, https://vitest.dev/guide/snapshot.html + +exports[`file repeats 1`] = `"foo"`; + +exports[`file repeats many 1`] = `"foo"`; + +exports[`file repeats many 2`] = `"bar"`; + +exports[`file retry 1`] = `"foo"`; + +exports[`file retry many 1`] = `"foo"`; + +exports[`file retry many 2`] = `"bar"`; diff --git a/test/core/test/repro.test.ts b/test/core/test/repro.test.ts index 6cec0536c6f9..fbb5e03ec9c0 100644 --- a/test/core/test/repro.test.ts +++ b/test/core/test/repro.test.ts @@ -15,6 +15,35 @@ test('inline retry', { retry: 1 }, (ctx) => { } }) -// test('foo', { retry: 1 }, () => { -// expect('foo').toMatchSnapshot() -// }); +test('file repeats', { repeats: 1 }, () => { + expect('foo').toMatchSnapshot() +}) + +test('file retry', { retry: 1 }, (ctx) => { + expect('foo').toMatchSnapshot() + if (ctx.task.result?.retryCount === 0) { + throw new Error('boom') + } +}) + +test('file repeats many', { repeats: 1 }, () => { + expect('foo').toMatchSnapshot() + expect('bar').toMatchSnapshot() +}) + +test('file retry many', { retry: 1 }, (ctx) => { + expect('foo').toMatchSnapshot() + expect('bar').toMatchSnapshot() + if (ctx.task.result?.retryCount === 0) { + throw new Error('boom') + } +}) + +// TODO +test.skip('file retry partial', { retry: 1 }, (ctx) => { + expect('foo').toMatchSnapshot() + if (ctx.task.result?.retryCount === 0) { + throw new Error('boom') + } + expect('bar').toMatchSnapshot() +}) From 99abe4c158a0c1246a6bdad297b95ff4a37b99d6 Mon Sep 17 00:00:00 2001 From: Hiroshi Ogawa Date: Wed, 30 Oct 2024 15:47:26 +0900 Subject: [PATCH 06/21] fix: reset to _initialData --- packages/snapshot/src/port/state.ts | 10 +++++++--- 1 file changed, 7 insertions(+), 3 deletions(-) diff --git a/packages/snapshot/src/port/state.ts b/packages/snapshot/src/port/state.ts index 85ba10fedefa..795dfb2cd51e 100644 --- a/packages/snapshot/src/port/state.ts +++ b/packages/snapshot/src/port/state.ts @@ -75,8 +75,8 @@ export default class SnapshotState { ) { const { data, dirty } = getSnapshotData(snapshotContent, options) this._fileExists = snapshotContent != null // TODO: update on watch? - this._initialData = data - this._snapshotData = data + this._initialData = { ...data } + this._snapshotData = { ...data } this._dirty = dirty this._inlineSnapshots = [] this._inlineSnapshotStacks = [] @@ -120,6 +120,9 @@ export default class SnapshotState { } clearTest(testName: string): void { + // TODO: key by test.id + // TODO: reset this.added, matched, etc.. + this._inlineSnapshots = this._inlineSnapshots.filter(s => s.testName !== testName) this._inlineSnapshotStacks = this._inlineSnapshotStacks.filter(s => s.testName !== testName) if (this._counters.has(testName)) { @@ -176,8 +179,9 @@ export default class SnapshotState { } } + // TODO: not used. remove it clear(): void { - this._snapshotData = this._initialData + this._snapshotData = { ...this._initialData } // this._inlineSnapshots = [] this._counters = new Map() this.added = 0 From c17461564b4e16aad9c6e9d01418761ba95629f3 Mon Sep 17 00:00:00 2001 From: Hiroshi Ogawa Date: Wed, 30 Oct 2024 16:00:35 +0900 Subject: [PATCH 07/21] fix: fix retry partial --- packages/snapshot/src/port/state.ts | 13 ++++++------- test/core/test/__snapshots__/repro.test.ts.snap | 4 ++++ test/core/test/repro.test.ts | 3 +-- 3 files changed, 11 insertions(+), 9 deletions(-) diff --git a/packages/snapshot/src/port/state.ts b/packages/snapshot/src/port/state.ts index 795dfb2cd51e..35e2ee6de606 100644 --- a/packages/snapshot/src/port/state.ts +++ b/packages/snapshot/src/port/state.ts @@ -120,21 +120,20 @@ export default class SnapshotState { } clearTest(testName: string): void { - // TODO: key by test.id + // TODO: key by test.id (handle multiple tests with same title) // TODO: reset this.added, matched, etc.. this._inlineSnapshots = this._inlineSnapshots.filter(s => s.testName !== testName) this._inlineSnapshotStacks = this._inlineSnapshotStacks.filter(s => s.testName !== testName) if (this._counters.has(testName)) { - let counter = this._counters.get(testName)! - for (const key in this._snapshotData) { - if (keyToTestName(key) === testName) { - counter-- + const counter = this._counters.get(testName)! + for (let i = 1; i <= counter; i++) { + const key = testNameToKey(testName, counter) + if (key in this._snapshotData || key in this._initialData) { this._snapshotData[key] = this._initialData[key] - this._uncheckedKeys.add(key) } } - this._counters.set(testName, counter) + this._counters.delete(testName) } } diff --git a/test/core/test/__snapshots__/repro.test.ts.snap b/test/core/test/__snapshots__/repro.test.ts.snap index a368365c6eda..55fb93d2deec 100644 --- a/test/core/test/__snapshots__/repro.test.ts.snap +++ b/test/core/test/__snapshots__/repro.test.ts.snap @@ -11,3 +11,7 @@ exports[`file retry 1`] = `"foo"`; exports[`file retry many 1`] = `"foo"`; exports[`file retry many 2`] = `"bar"`; + +exports[`file retry partial 1`] = `"foo"`; + +exports[`file retry partial 2`] = `"bar"`; diff --git a/test/core/test/repro.test.ts b/test/core/test/repro.test.ts index fbb5e03ec9c0..ec0c8149fefe 100644 --- a/test/core/test/repro.test.ts +++ b/test/core/test/repro.test.ts @@ -39,8 +39,7 @@ test('file retry many', { retry: 1 }, (ctx) => { } }) -// TODO -test.skip('file retry partial', { retry: 1 }, (ctx) => { +test('file retry partial', { retry: 1 }, (ctx) => { expect('foo').toMatchSnapshot() if (ctx.task.result?.retryCount === 0) { throw new Error('boom') From 3dcf25d33b0f87138a490fce70303323753031a4 Mon Sep 17 00:00:00 2001 From: Hiroshi Ogawa Date: Wed, 30 Oct 2024 17:51:17 +0900 Subject: [PATCH 08/21] chore: cleanup more --- packages/snapshot/src/port/state.ts | 12 ------------ 1 file changed, 12 deletions(-) diff --git a/packages/snapshot/src/port/state.ts b/packages/snapshot/src/port/state.ts index 35e2ee6de606..8c3c4323474a 100644 --- a/packages/snapshot/src/port/state.ts +++ b/packages/snapshot/src/port/state.ts @@ -178,18 +178,6 @@ export default class SnapshotState { } } - // TODO: not used. remove it - clear(): void { - this._snapshotData = { ...this._initialData } - // this._inlineSnapshots = [] - this._counters = new Map() - this.added = 0 - this.matched = 0 - this.unmatched = 0 - this.updated = 0 - this._dirty = false - } - async save(): Promise { const hasExternalSnapshots = Object.keys(this._snapshotData).length const hasInlineSnapshots = this._inlineSnapshots.length From 8817ac558a34fd9d893884aa7ae80952152c2e80 Mon Sep 17 00:00:00 2001 From: Hiroshi Ogawa Date: Wed, 30 Oct 2024 19:01:09 +0900 Subject: [PATCH 09/21] test: more --- ...repro.test.ts.snap => repro1.test.ts.snap} | 0 .../test/{repro.test.ts => repro1.test.ts} | 0 test/core/test/repro2.test.ts | 21 +++++++++++++++++++ 3 files changed, 21 insertions(+) rename test/core/test/__snapshots__/{repro.test.ts.snap => repro1.test.ts.snap} (100%) rename test/core/test/{repro.test.ts => repro1.test.ts} (100%) create mode 100644 test/core/test/repro2.test.ts diff --git a/test/core/test/__snapshots__/repro.test.ts.snap b/test/core/test/__snapshots__/repro1.test.ts.snap similarity index 100% rename from test/core/test/__snapshots__/repro.test.ts.snap rename to test/core/test/__snapshots__/repro1.test.ts.snap diff --git a/test/core/test/repro.test.ts b/test/core/test/repro1.test.ts similarity index 100% rename from test/core/test/repro.test.ts rename to test/core/test/repro1.test.ts diff --git a/test/core/test/repro2.test.ts b/test/core/test/repro2.test.ts new file mode 100644 index 000000000000..7956a82fea03 --- /dev/null +++ b/test/core/test/repro2.test.ts @@ -0,0 +1,21 @@ +import { expect, test } from 'vitest' + +test('repro', () => { + expect(0).toBe(0) +}) + +test('inline same title', () => { + expect('foo').toMatchInlineSnapshot(`"foo"`) +}) + +test('inline same title', () => { + expect('bar').toMatchInlineSnapshot(`"bar"`) +}) + +test('file same title', () => { + expect('foo').toMatchSnapshot() +}) + +test('file same title', () => { + expect('bar').toMatchSnapshot() +}) From c5e45272e660d7e6bee5ec6d464992fb0a250dd3 Mon Sep 17 00:00:00 2001 From: Hiroshi Ogawa Date: Wed, 30 Oct 2024 19:27:09 +0900 Subject: [PATCH 10/21] wip: _testIdToKeys state --- packages/snapshot/src/client.ts | 12 +++-- packages/snapshot/src/port/inlineSnapshot.ts | 2 +- packages/snapshot/src/port/state.ts | 53 +++++++++++-------- packages/snapshot/src/types/index.ts | 1 + .../vitest/src/integrations/snapshot/chai.ts | 1 + packages/vitest/src/runtime/runners/test.ts | 8 ++- 6 files changed, 43 insertions(+), 34 deletions(-) diff --git a/packages/snapshot/src/client.ts b/packages/snapshot/src/client.ts index 755c93da53cf..f4b9bb83c2e2 100644 --- a/packages/snapshot/src/client.ts +++ b/packages/snapshot/src/client.ts @@ -36,6 +36,7 @@ interface AssertOptions { received: unknown filepath: string name: string + testId: string message?: string isInline?: boolean properties?: object @@ -74,15 +75,14 @@ export class SnapshotClient { return result } - skipTest(filepath: string, name: string): void { + skipTest(filepath: string, testId: string): void { const state = this.getSnapshotState(filepath) - state.markSnapshotsAsCheckedForTest(name) + state.markSnapshotsAsCheckedForTest(testId) } - // TODO: use test.id to handle multiple tests with same name - clearTest(filepath: string, name: string): void { + clearTest(filepath: string, testId: string): void { const state = this.getSnapshotState(filepath) - state.clearTest(name) + state.clearTest(testId) } getSnapshotState(filepath: string): SnapshotState { @@ -97,6 +97,7 @@ export class SnapshotClient { const { filepath, name, + testId, message, isInline = false, properties, @@ -144,6 +145,7 @@ export class SnapshotClient { const testName = [name, ...(message ? [message] : [])].join(' > ') const { actual, expected, key, pass } = snapshotState.match({ + testId, testName, received, isInline, diff --git a/packages/snapshot/src/port/inlineSnapshot.ts b/packages/snapshot/src/port/inlineSnapshot.ts index c8474448b4ee..09f059661655 100644 --- a/packages/snapshot/src/port/inlineSnapshot.ts +++ b/packages/snapshot/src/port/inlineSnapshot.ts @@ -9,8 +9,8 @@ import { export interface InlineSnapshot { snapshot: string + testId: string file: string - testName: string line: number column: number } diff --git a/packages/snapshot/src/port/state.ts b/packages/snapshot/src/port/state.ts index 8c3c4323474a..864acfc9868e 100644 --- a/packages/snapshot/src/port/state.ts +++ b/packages/snapshot/src/port/state.ts @@ -50,11 +50,11 @@ export default class SnapshotState { private _counters: Map private _dirty: boolean private _updateSnapshot: SnapshotUpdateState - // TODO: key by test id. how to match with `initialData`? private _snapshotData: SnapshotData private _initialData: SnapshotData private _inlineSnapshots: Array - private _inlineSnapshotStacks: Array + private _inlineSnapshotStacks: Array + private _testIdToKeys = new Map() private _rawSnapshots: Array private _uncheckedKeys: Set private _snapshotFormat: PrettyFormatOptions @@ -111,30 +111,34 @@ export default class SnapshotState { return this._environment } - markSnapshotsAsCheckedForTest(testName: string): void { - this._uncheckedKeys.forEach((uncheckedKey) => { - if (keyToTestName(uncheckedKey) === testName) { - this._uncheckedKeys.delete(uncheckedKey) - } - }) + markSnapshotsAsCheckedForTest(testId: string): void { + for (const key of this._testIdToKeys.get(testId) ?? []) { + this._uncheckedKeys.delete(key) + } } - clearTest(testName: string): void { - // TODO: key by test.id (handle multiple tests with same title) - // TODO: reset this.added, matched, etc.. + clearTest(testId: string): void { + // TODO: reset stats: added, matched, etc.. + + this._inlineSnapshots = this._inlineSnapshots.filter(s => s.testId !== testId) + this._inlineSnapshotStacks = this._inlineSnapshotStacks.filter(s => s.testId !== testId) - this._inlineSnapshots = this._inlineSnapshots.filter(s => s.testName !== testName) - this._inlineSnapshotStacks = this._inlineSnapshotStacks.filter(s => s.testName !== testName) - if (this._counters.has(testName)) { - const counter = this._counters.get(testName)! - for (let i = 1; i <= counter; i++) { - const key = testNameToKey(testName, counter) + for (const key of this._testIdToKeys.get(testId) ?? []) { + const name = keyToTestName(key) + const counter = this._counters.get(name) + if (typeof counter !== 'undefined') { if (key in this._snapshotData || key in this._initialData) { this._snapshotData[key] = this._initialData[key] } + if (counter > 0) { + this._counters.set(name, counter - 1) + } + else { + this._counters.delete(name) + } } - this._counters.delete(testName) } + this._testIdToKeys.delete(testId) } protected _inferInlineSnapshotStack(stacks: ParsedStack[]): ParsedStack | null { @@ -157,13 +161,13 @@ export default class SnapshotState { private _addSnapshot( key: string, receivedSerialized: string, - options: { rawSnapshot?: RawSnapshotInfo; stack?: ParsedStack; testName: string }, + options: { rawSnapshot?: RawSnapshotInfo; stack?: ParsedStack; testId: string }, ): void { this._dirty = true if (options.stack) { this._inlineSnapshots.push({ snapshot: receivedSerialized, - testName: options.testName, + testId: options.testId, ...options.stack, }) } @@ -237,6 +241,7 @@ export default class SnapshotState { } match({ + testId, testName, received, key, @@ -251,6 +256,8 @@ export default class SnapshotState { if (!key) { key = testNameToKey(testName, count) } + this._testIdToKeys.set(testId, (this._testIdToKeys.get(key) ?? [])) + this._testIdToKeys.get(testId)?.push(key) // Do not mark the snapshot as "checked" if the snapshot is inline and // there's an external snapshot. This way the external snapshot can be @@ -329,7 +336,7 @@ export default class SnapshotState { this._inlineSnapshots = this._inlineSnapshots.filter(s => !(s.file === stack!.file && s.line === stack!.line && s.column === stack!.column)) throw new Error('toMatchInlineSnapshot cannot be called multiple times at the same location.') } - this._inlineSnapshotStacks.push({ ...stack, testName }) + this._inlineSnapshotStacks.push({ ...stack, testId }) } // These are the conditions on when to write snapshots: @@ -355,7 +362,7 @@ export default class SnapshotState { this._addSnapshot(key, receivedSerialized, { stack, - testName, + testId, rawSnapshot, }) } @@ -366,7 +373,7 @@ export default class SnapshotState { else { this._addSnapshot(key, receivedSerialized, { stack, - testName, + testId, rawSnapshot, }) this.added++ diff --git a/packages/snapshot/src/types/index.ts b/packages/snapshot/src/types/index.ts index d1bf8af4fac8..a106f9a24741 100644 --- a/packages/snapshot/src/types/index.ts +++ b/packages/snapshot/src/types/index.ts @@ -24,6 +24,7 @@ export interface SnapshotStateOptions { } export interface SnapshotMatchOptions { + testId: string testName: string received: unknown key?: string diff --git a/packages/vitest/src/integrations/snapshot/chai.ts b/packages/vitest/src/integrations/snapshot/chai.ts index b7e89077bb69..9cd96fc2ba1b 100644 --- a/packages/vitest/src/integrations/snapshot/chai.ts +++ b/packages/vitest/src/integrations/snapshot/chai.ts @@ -48,6 +48,7 @@ function getTestNames(test: Test) { return { filepath: test.file.filepath, name: getNames(test).slice(1).join(' > '), + testId: test.id, } } diff --git a/packages/vitest/src/runtime/runners/test.ts b/packages/vitest/src/runtime/runners/test.ts index d9a96d6cea53..e55df4b6be75 100644 --- a/packages/vitest/src/runtime/runners/test.ts +++ b/packages/vitest/src/runtime/runners/test.ts @@ -14,7 +14,7 @@ import type { import type { SerializedConfig } from '../config' import type { VitestExecutor } from '../execute' import { getState, GLOBAL_EXPECT, setState } from '@vitest/expect' -import { getNames, getTestName, getTests } from '@vitest/runner/utils' +import { getTestName, getTests } from '@vitest/runner/utils' import { createExpect } from '../../integrations/chai/index' import { getSnapshotClient } from '../../integrations/snapshot/chai' import { vi } from '../../integrations/vi' @@ -58,9 +58,7 @@ export class VitestTestRunner implements VitestRunner { // mark snapshots in skipped tests as not obsolete for (const test of getTests(suite)) { if (test.mode === 'skip') { - const name = getNames(test).slice(1).join(' > ') - // TODO: skip by test.id - this.snapshotClient.skipTest(suite.file.filepath, name) + this.snapshotClient.skipTest(suite.file.filepath, test.id) } } @@ -114,7 +112,7 @@ export class VitestTestRunner implements VitestRunner { } onBeforeTryTask(test: Task) { - this.snapshotClient.clearTest(test.file.filepath, test.name) + this.snapshotClient.clearTest(test.file.filepath, test.id) setState( { assertionCalls: 0, From 558ba71733fb368a2d8baea7e2dff87d29601534 Mon Sep 17 00:00:00 2001 From: Hiroshi Ogawa Date: Wed, 30 Oct 2024 19:31:17 +0900 Subject: [PATCH 11/21] test: more --- test/core/test/__snapshots__/repro2.test.ts.snap | 5 +++++ 1 file changed, 5 insertions(+) create mode 100644 test/core/test/__snapshots__/repro2.test.ts.snap diff --git a/test/core/test/__snapshots__/repro2.test.ts.snap b/test/core/test/__snapshots__/repro2.test.ts.snap new file mode 100644 index 000000000000..c1acf6864dfd --- /dev/null +++ b/test/core/test/__snapshots__/repro2.test.ts.snap @@ -0,0 +1,5 @@ +// Vitest Snapshot v1, https://vitest.dev/guide/snapshot.html + +exports[`file same title 1`] = `"foo"`; + +exports[`file same title 2`] = `"bar"`; From 5b1182b16c2ceb43d54bd6fdcc3deda606c5f60b Mon Sep 17 00:00:00 2001 From: Hiroshi Ogawa Date: Wed, 30 Oct 2024 19:37:17 +0900 Subject: [PATCH 12/21] wip: _testIdToKeys --- packages/snapshot/src/port/state.ts | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/packages/snapshot/src/port/state.ts b/packages/snapshot/src/port/state.ts index 864acfc9868e..af82661249b9 100644 --- a/packages/snapshot/src/port/state.ts +++ b/packages/snapshot/src/port/state.ts @@ -112,6 +112,7 @@ export default class SnapshotState { } markSnapshotsAsCheckedForTest(testId: string): void { + // TODO: broken... for skipped test, we don't even have _testIdToKeys for (const key of this._testIdToKeys.get(testId) ?? []) { this._uncheckedKeys.delete(key) } @@ -256,7 +257,7 @@ export default class SnapshotState { if (!key) { key = testNameToKey(testName, count) } - this._testIdToKeys.set(testId, (this._testIdToKeys.get(key) ?? [])) + this._testIdToKeys.set(testId, (this._testIdToKeys.get(testId) ?? [])) this._testIdToKeys.get(testId)?.push(key) // Do not mark the snapshot as "checked" if the snapshot is inline and From 940dbb3f8ebc2c1ecd34dd0590ea1238e5657fd0 Mon Sep 17 00:00:00 2001 From: Hiroshi Ogawa Date: Wed, 30 Oct 2024 19:39:27 +0900 Subject: [PATCH 13/21] fix: fix markSnapshotsAsCheckedForTest --- packages/snapshot/src/client.ts | 4 ++-- packages/snapshot/src/port/state.ts | 11 ++++++----- packages/vitest/src/runtime/runners/test.ts | 5 +++-- 3 files changed, 11 insertions(+), 9 deletions(-) diff --git a/packages/snapshot/src/client.ts b/packages/snapshot/src/client.ts index f4b9bb83c2e2..b15bee07baea 100644 --- a/packages/snapshot/src/client.ts +++ b/packages/snapshot/src/client.ts @@ -75,9 +75,9 @@ export class SnapshotClient { return result } - skipTest(filepath: string, testId: string): void { + skipTest(filepath: string, testName: string): void { const state = this.getSnapshotState(filepath) - state.markSnapshotsAsCheckedForTest(testId) + state.markSnapshotsAsCheckedForTest(testName) } clearTest(filepath: string, testId: string): void { diff --git a/packages/snapshot/src/port/state.ts b/packages/snapshot/src/port/state.ts index af82661249b9..10dcb89469d7 100644 --- a/packages/snapshot/src/port/state.ts +++ b/packages/snapshot/src/port/state.ts @@ -111,11 +111,12 @@ export default class SnapshotState { return this._environment } - markSnapshotsAsCheckedForTest(testId: string): void { - // TODO: broken... for skipped test, we don't even have _testIdToKeys - for (const key of this._testIdToKeys.get(testId) ?? []) { - this._uncheckedKeys.delete(key) - } + markSnapshotsAsCheckedForTest(testName: string): void { + this._uncheckedKeys.forEach((uncheckedKey) => { + if (keyToTestName(uncheckedKey) === testName) { + this._uncheckedKeys.delete(uncheckedKey) + } + }) } clearTest(testId: string): void { diff --git a/packages/vitest/src/runtime/runners/test.ts b/packages/vitest/src/runtime/runners/test.ts index e55df4b6be75..fd2a931133af 100644 --- a/packages/vitest/src/runtime/runners/test.ts +++ b/packages/vitest/src/runtime/runners/test.ts @@ -14,7 +14,7 @@ import type { import type { SerializedConfig } from '../config' import type { VitestExecutor } from '../execute' import { getState, GLOBAL_EXPECT, setState } from '@vitest/expect' -import { getTestName, getTests } from '@vitest/runner/utils' +import { getNames, getTestName, getTests } from '@vitest/runner/utils' import { createExpect } from '../../integrations/chai/index' import { getSnapshotClient } from '../../integrations/snapshot/chai' import { vi } from '../../integrations/vi' @@ -58,7 +58,8 @@ export class VitestTestRunner implements VitestRunner { // mark snapshots in skipped tests as not obsolete for (const test of getTests(suite)) { if (test.mode === 'skip') { - this.snapshotClient.skipTest(suite.file.filepath, test.id) + const name = getNames(test).slice(1).join(' > ') + this.snapshotClient.skipTest(suite.file.filepath, name) } } From 80accd65524f6acb13fc158d567c10606b13e3df Mon Sep 17 00:00:00 2001 From: Hiroshi Ogawa Date: Thu, 31 Oct 2024 11:27:34 +0900 Subject: [PATCH 14/21] refactor: minor --- packages/snapshot/src/client.ts | 1 + packages/snapshot/src/port/state.ts | 9 +++++---- packages/snapshot/src/port/utils.ts | 16 ++++++++++++++++ 3 files changed, 22 insertions(+), 4 deletions(-) diff --git a/packages/snapshot/src/client.ts b/packages/snapshot/src/client.ts index b15bee07baea..32279959a0d8 100644 --- a/packages/snapshot/src/client.ts +++ b/packages/snapshot/src/client.ts @@ -50,6 +50,7 @@ export interface SnapshotClientOptions { isEqual?: (received: unknown, expected: unknown) => boolean } +// TODO: probably we don't need much refactoring here yet. separate refactoring export class SnapshotClient { snapshotStateMap: Map = new Map() diff --git a/packages/snapshot/src/port/state.ts b/packages/snapshot/src/port/state.ts index 10dcb89469d7..4c975155dfb9 100644 --- a/packages/snapshot/src/port/state.ts +++ b/packages/snapshot/src/port/state.ts @@ -23,6 +23,7 @@ import { saveRawSnapshots } from './rawSnapshot' import { addExtraLineBreaks, + DefaultMap, getSnapshotData, keyToTestName, normalizeNewlines, @@ -54,7 +55,7 @@ export default class SnapshotState { private _initialData: SnapshotData private _inlineSnapshots: Array private _inlineSnapshotStacks: Array - private _testIdToKeys = new Map() + private _testIdToKeys = new DefaultMap(() => []) private _rawSnapshots: Array private _uncheckedKeys: Set private _snapshotFormat: PrettyFormatOptions @@ -125,7 +126,7 @@ export default class SnapshotState { this._inlineSnapshots = this._inlineSnapshots.filter(s => s.testId !== testId) this._inlineSnapshotStacks = this._inlineSnapshotStacks.filter(s => s.testId !== testId) - for (const key of this._testIdToKeys.get(testId) ?? []) { + for (const key of this._testIdToKeys.get(testId)) { const name = keyToTestName(key) const counter = this._counters.get(name) if (typeof counter !== 'undefined') { @@ -252,14 +253,14 @@ export default class SnapshotState { error, rawSnapshot, }: SnapshotMatchOptions): SnapshotReturnOptions { + // this also increments counter for inline snapshots. maybe we don't need to? this._counters.set(testName, (this._counters.get(testName) || 0) + 1) const count = Number(this._counters.get(testName)) if (!key) { key = testNameToKey(testName, count) } - this._testIdToKeys.set(testId, (this._testIdToKeys.get(testId) ?? [])) - this._testIdToKeys.get(testId)?.push(key) + this._testIdToKeys.get(testId).push(key) // Do not mark the snapshot as "checked" if the snapshot is inline and // there's an external snapshot. This way the external snapshot can be diff --git a/packages/snapshot/src/port/utils.ts b/packages/snapshot/src/port/utils.ts index d3435a493354..111d15cb15f1 100644 --- a/packages/snapshot/src/port/utils.ts +++ b/packages/snapshot/src/port/utils.ts @@ -265,3 +265,19 @@ export function deepMergeSnapshot(target: any, source: any): any { } return target } + +export class DefaultMap extends Map { + constructor( + private defaultFn: (key: K) => V, + entries?: Iterable, + ) { + super(entries) + } + + override get(key: K): V { + if (!this.has(key)) { + this.set(key, this.defaultFn(key)) + } + return super.get(key)! + } +} From 3fc11d5d2fcbd030cd5cc401e43d6ca69c4d0d00 Mon Sep 17 00:00:00 2001 From: Hiroshi Ogawa Date: Thu, 31 Oct 2024 11:43:51 +0900 Subject: [PATCH 15/21] fix: clear added/updated stats --- packages/snapshot/src/port/state.ts | 44 +++++++++++++++-------------- packages/snapshot/src/port/utils.ts | 14 +++++++++ 2 files changed, 37 insertions(+), 21 deletions(-) diff --git a/packages/snapshot/src/port/state.ts b/packages/snapshot/src/port/state.ts index 4c975155dfb9..2dfe42cfebc0 100644 --- a/packages/snapshot/src/port/state.ts +++ b/packages/snapshot/src/port/state.ts @@ -23,6 +23,7 @@ import { saveRawSnapshots } from './rawSnapshot' import { addExtraLineBreaks, + CounterMap, DefaultMap, getSnapshotData, keyToTestName, @@ -61,12 +62,11 @@ export default class SnapshotState { private _snapshotFormat: PrettyFormatOptions private _environment: SnapshotEnvironment private _fileExists: boolean - - added: number + private added = new CounterMap() + private matched = new CounterMap() + private unmatched = new CounterMap() + private updated = new CounterMap() expand: boolean - matched: number - unmatched: number - updated: number private constructor( public testFilePath: string, @@ -85,11 +85,7 @@ export default class SnapshotState { this._uncheckedKeys = new Set(Object.keys(this._snapshotData)) this._counters = new Map() this.expand = options.expand || false - this.added = 0 - this.matched = 0 - this.unmatched = 0 this._updateSnapshot = options.updateSnapshot - this.updated = 0 this._snapshotFormat = { printBasicPrototype: false, escapeString: false, @@ -121,11 +117,11 @@ export default class SnapshotState { } clearTest(testId: string): void { - // TODO: reset stats: added, matched, etc.. - + // clear inline this._inlineSnapshots = this._inlineSnapshots.filter(s => s.testId !== testId) this._inlineSnapshotStacks = this._inlineSnapshotStacks.filter(s => s.testId !== testId) + // clear file for (const key of this._testIdToKeys.get(testId)) { const name = keyToTestName(key) const counter = this._counters.get(name) @@ -142,6 +138,12 @@ export default class SnapshotState { } } this._testIdToKeys.delete(testId) + + // clear stats + this.added.delete(testId) + this.updated.delete(testId) + this.matched.delete(testId) + this.unmatched.delete(testId) } protected _inferInlineSnapshotStack(stacks: ParsedStack[]): ParsedStack | null { @@ -357,10 +359,10 @@ export default class SnapshotState { if (this._updateSnapshot === 'all') { if (!pass) { if (hasSnapshot) { - this.updated++ + this.updated.increment(testId) } else { - this.added++ + this.added.increment(testId) } this._addSnapshot(key, receivedSerialized, { @@ -370,7 +372,7 @@ export default class SnapshotState { }) } else { - this.matched++ + this.matched.increment(testId) } } else { @@ -379,7 +381,7 @@ export default class SnapshotState { testId, rawSnapshot, }) - this.added++ + this.added.increment(testId) } return { @@ -392,7 +394,7 @@ export default class SnapshotState { } else { if (!pass) { - this.unmatched++ + this.unmatched.increment(testId) return { actual: removeExtraLineBreaks(receivedSerialized), count, @@ -405,7 +407,7 @@ export default class SnapshotState { } } else { - this.matched++ + this.matched.increment(testId) return { actual: '', count, @@ -436,10 +438,10 @@ export default class SnapshotState { const status = await this.save() snapshot.fileDeleted = status.deleted - snapshot.added = this.added - snapshot.matched = this.matched - snapshot.unmatched = this.unmatched - snapshot.updated = this.updated + snapshot.added = this.added.total() + snapshot.matched = this.matched.total() + snapshot.unmatched = this.unmatched.total() + snapshot.updated = this.updated.total() snapshot.unchecked = !status.deleted ? uncheckedCount : 0 snapshot.uncheckedKeys = Array.from(uncheckedKeys) diff --git a/packages/snapshot/src/port/utils.ts b/packages/snapshot/src/port/utils.ts index 111d15cb15f1..1773945f10de 100644 --- a/packages/snapshot/src/port/utils.ts +++ b/packages/snapshot/src/port/utils.ts @@ -281,3 +281,17 @@ export class DefaultMap extends Map { return super.get(key)! } } + +export class CounterMap extends Map { + increment(key: K): void { + this.set(key, (this.get(key) ?? 0) + 1) + } + + total(): number { + let total = 0 + for (const x of this.values()) { + total += x + } + return total + } +} From de9b88ef2db0844f8b4104c33fb8678a4a7e9d41 Mon Sep 17 00:00:00 2001 From: Hiroshi Ogawa Date: Thu, 31 Oct 2024 12:06:02 +0900 Subject: [PATCH 16/21] chore: comment --- packages/snapshot/src/client.ts | 1 - packages/snapshot/src/port/state.ts | 2 +- 2 files changed, 1 insertion(+), 2 deletions(-) diff --git a/packages/snapshot/src/client.ts b/packages/snapshot/src/client.ts index 32279959a0d8..b15bee07baea 100644 --- a/packages/snapshot/src/client.ts +++ b/packages/snapshot/src/client.ts @@ -50,7 +50,6 @@ export interface SnapshotClientOptions { isEqual?: (received: unknown, expected: unknown) => boolean } -// TODO: probably we don't need much refactoring here yet. separate refactoring export class SnapshotClient { snapshotStateMap: Map = new Map() diff --git a/packages/snapshot/src/port/state.ts b/packages/snapshot/src/port/state.ts index 2dfe42cfebc0..c7a0f837932c 100644 --- a/packages/snapshot/src/port/state.ts +++ b/packages/snapshot/src/port/state.ts @@ -255,7 +255,7 @@ export default class SnapshotState { error, rawSnapshot, }: SnapshotMatchOptions): SnapshotReturnOptions { - // this also increments counter for inline snapshots. maybe we don't need to? + // this also increments counter for inline snapshots. maybe we shouldn't? this._counters.set(testName, (this._counters.get(testName) || 0) + 1) const count = Number(this._counters.get(testName)) From 7c95367ab4ad8b83b3ed7e992d2f529343e49969 Mon Sep 17 00:00:00 2001 From: Hiroshi Ogawa Date: Thu, 31 Oct 2024 12:35:21 +0900 Subject: [PATCH 17/21] wip: ability to choose snapshot file per test case --- packages/snapshot/src/client.ts | 23 ++++++++++++++++++++++- packages/snapshot/src/port/state.ts | 2 +- 2 files changed, 23 insertions(+), 2 deletions(-) diff --git a/packages/snapshot/src/client.ts b/packages/snapshot/src/client.ts index b15bee07baea..95aa1cd02686 100644 --- a/packages/snapshot/src/client.ts +++ b/packages/snapshot/src/client.ts @@ -1,7 +1,7 @@ import type { RawSnapshotInfo } from './port/rawSnapshot' import type { SnapshotResult, SnapshotStateOptions } from './types' import SnapshotState from './port/state' -import { deepMergeSnapshot } from './port/utils' +import { deepMergeSnapshot, DefaultMap } from './port/utils' function createMismatchError( message: string, @@ -75,6 +75,27 @@ export class SnapshotClient { return result } + private fileToTestIds = new DefaultMap>(() => new Set()) + private testIdToSnapshotPath: Record = {} + private snapshotPathToState = new Map() + + async setupTest( + filepath: string, + testId: string, + options: SnapshotStateOptions, + ): Promise { + this.fileToTestIds.get(filepath).add(testId) + const snapshotPath = await options.snapshotEnvironment.resolvePath(filepath) + this.testIdToSnapshotPath[testId] = snapshotPath + if (!this.snapshotPathToState.has(snapshotPath)) { + const content = await options.snapshotEnvironment.readSnapshotFile(snapshotPath) + this.snapshotPathToState.set( + snapshotPath, + new SnapshotState(filepath, snapshotPath, content, options), + ) + } + } + skipTest(filepath: string, testName: string): void { const state = this.getSnapshotState(filepath) state.markSnapshotsAsCheckedForTest(testName) diff --git a/packages/snapshot/src/port/state.ts b/packages/snapshot/src/port/state.ts index c7a0f837932c..3475113d87e4 100644 --- a/packages/snapshot/src/port/state.ts +++ b/packages/snapshot/src/port/state.ts @@ -68,7 +68,7 @@ export default class SnapshotState { private updated = new CounterMap() expand: boolean - private constructor( + constructor( public testFilePath: string, public snapshotPath: string, snapshotContent: string | null, From 861c58b20ef135b9c67e89e648e8383b7a762e0d Mon Sep 17 00:00:00 2001 From: Hiroshi Ogawa Date: Thu, 31 Oct 2024 14:08:23 +0900 Subject: [PATCH 18/21] wip: state per test case --- packages/snapshot/src/client.ts | 96 ++++++++++++--------- packages/vitest/src/runtime/runners/test.ts | 29 ++++--- 2 files changed, 75 insertions(+), 50 deletions(-) diff --git a/packages/snapshot/src/client.ts b/packages/snapshot/src/client.ts index 95aa1cd02686..20bbc1fb8fab 100644 --- a/packages/snapshot/src/client.ts +++ b/packages/snapshot/src/client.ts @@ -51,67 +51,80 @@ export interface SnapshotClientOptions { } export class SnapshotClient { - snapshotStateMap: Map = new Map() + // snapshotStateMap: Map = new Map() constructor(private options: SnapshotClientOptions = {}) {} - async setup( - filepath: string, - options: SnapshotStateOptions, - ): Promise { - if (this.snapshotStateMap.has(filepath)) { - throw new Error('already setup') - } - this.snapshotStateMap.set( - filepath, - await SnapshotState.create(filepath, options), - ) - } + // async setup( + // filepath: string, + // options: SnapshotStateOptions, + // ): Promise { + // if (this.snapshotStateMap.has(filepath)) { + // throw new Error('already setup') + // } + // this.snapshotStateMap.set( + // filepath, + // await SnapshotState.create(filepath, options), + // ) + // } async finish(filepath: string): Promise { - const state = this.getSnapshotState(filepath) - const result = await state.pack() - this.snapshotStateMap.delete(filepath) - return result + const results: SnapshotResult[] = [] + for (const testId of this.fileToTestIds.get(filepath)) { + const state = this.getSnapshotState(testId) + const result = await state.pack() + this.testIdToSnapshotPath.delete(testId) + results.push(result) + } + this.fileToTestIds.get(filepath) + // TODO: aggregate result + return results[0] } private fileToTestIds = new DefaultMap>(() => new Set()) - private testIdToSnapshotPath: Record = {} + private testIdToSnapshotPath = new Map() private snapshotPathToState = new Map() + // resolve snapshot file for each test and reuse state for same snapshot file + // TODO: concurrent safe async setupTest( filepath: string, testId: string, options: SnapshotStateOptions, - ): Promise { + ): Promise { this.fileToTestIds.get(filepath).add(testId) const snapshotPath = await options.snapshotEnvironment.resolvePath(filepath) - this.testIdToSnapshotPath[testId] = snapshotPath - if (!this.snapshotPathToState.has(snapshotPath)) { + this.testIdToSnapshotPath.set(testId, snapshotPath) + // share same snapshot state for same snapshot path + let state = this.snapshotPathToState.get(snapshotPath) + if (!state) { const content = await options.snapshotEnvironment.readSnapshotFile(snapshotPath) - this.snapshotPathToState.set( - snapshotPath, - new SnapshotState(filepath, snapshotPath, content, options), - ) + state = new SnapshotState(filepath, snapshotPath, content, options) + this.snapshotPathToState.set(snapshotPath, state) } + state.clearTest(testId) + return state } - skipTest(filepath: string, testName: string): void { - const state = this.getSnapshotState(filepath) + skipTest(testId: string, testName: string): void { + const state = this.getSnapshotState(testId) state.markSnapshotsAsCheckedForTest(testName) } - clearTest(filepath: string, testId: string): void { - const state = this.getSnapshotState(filepath) - state.clearTest(testId) - } - - getSnapshotState(filepath: string): SnapshotState { - const state = this.snapshotStateMap.get(filepath) - if (!state) { - throw new Error('snapshot state not initialized') + // clearTest(testId: string): void { + // const state = this.getSnapshotState(testId) + // state.clearTest(testId) + // } + + getSnapshotState(testId: string): SnapshotState { + const snapshotPath = this.testIdToSnapshotPath.get(testId) + if (snapshotPath) { + const state = this.snapshotPathToState.get(snapshotPath) + if (state) { + return state + } } - return state + throw new Error('snapshot state not initialized') } assert(options: AssertOptions): void { @@ -133,7 +146,7 @@ export class SnapshotClient { throw new Error('Snapshot cannot be used outside of test') } - const snapshotState = this.getSnapshotState(filepath) + const snapshotState = this.getSnapshotState(testId) if (typeof properties === 'object') { if (typeof received !== 'object' || !received) { @@ -197,7 +210,7 @@ export class SnapshotClient { throw new Error('Snapshot cannot be used outside of test') } - const snapshotState = this.getSnapshotState(filepath) + const snapshotState = this.getSnapshotState(options.testId) // save the filepath, so it don't lose even if the await make it out-of-context options.filepath ||= filepath @@ -215,6 +228,9 @@ export class SnapshotClient { } clear(): void { - this.snapshotStateMap.clear() + this.fileToTestIds.clear() + this.testIdToSnapshotPath.clear() + this.snapshotPathToState.clear() + // this.snapshotStateMap.clear() } } diff --git a/packages/vitest/src/runtime/runners/test.ts b/packages/vitest/src/runtime/runners/test.ts index fd2a931133af..3888cea4069d 100644 --- a/packages/vitest/src/runtime/runners/test.ts +++ b/packages/vitest/src/runtime/runners/test.ts @@ -59,7 +59,12 @@ export class VitestTestRunner implements VitestRunner { for (const test of getTests(suite)) { if (test.mode === 'skip') { const name = getNames(test).slice(1).join(' > ') - this.snapshotClient.skipTest(suite.file.filepath, name) + await this.snapshotClient.setupTest( + test.file.filepath, + test.id, + this.workerState.config.snapshotOptions, + ) + this.snapshotClient.skipTest(test.id, name) } } @@ -102,18 +107,22 @@ export class VitestTestRunner implements VitestRunner { } // initialize snapshot state before running file suite - if (suite.mode !== 'skip' && 'filepath' in suite) { - await this.snapshotClient.setup( - suite.file.filepath, - this.workerState.config.snapshotOptions, - ) - } + // if (suite.mode !== 'skip' && 'filepath' in suite) { + // await this.snapshotClient.setup( + // suite.file.filepath, + // this.workerState.config.snapshotOptions, + // ) + // } this.workerState.current = suite } - onBeforeTryTask(test: Task) { - this.snapshotClient.clearTest(test.file.filepath, test.id) + async onBeforeTryTask(test: Task) { + const snapshotState = await this.snapshotClient.setupTest( + test.file.filepath, + test.id, + this.workerState.config.snapshotOptions, + ) setState( { assertionCalls: 0, @@ -123,7 +132,7 @@ export class VitestTestRunner implements VitestRunner { expectedAssertionsNumberErrorGen: null, testPath: test.file.filepath, currentTestName: getTestName(test), - snapshotState: this.snapshotClient.getSnapshotState(test.file.filepath), + snapshotState, }, (globalThis as any)[GLOBAL_EXPECT], ) From 44eadd6df298367d1a06bfcb35a29a45a7dd08de Mon Sep 17 00:00:00 2001 From: Hiroshi Ogawa Date: Thu, 31 Oct 2024 15:01:06 +0900 Subject: [PATCH 19/21] wip: deal with concurrent --- packages/snapshot/src/client.ts | 13 +++++-------- packages/snapshot/src/port/utils.ts | 16 ++++++++++++++++ test/core/test/nested-test.test.ts | 6 ++++-- 3 files changed, 25 insertions(+), 10 deletions(-) diff --git a/packages/snapshot/src/client.ts b/packages/snapshot/src/client.ts index 20bbc1fb8fab..575c304799de 100644 --- a/packages/snapshot/src/client.ts +++ b/packages/snapshot/src/client.ts @@ -1,7 +1,7 @@ import type { RawSnapshotInfo } from './port/rawSnapshot' import type { SnapshotResult, SnapshotStateOptions } from './types' import SnapshotState from './port/state' -import { deepMergeSnapshot, DefaultMap } from './port/utils' +import { deepMergeSnapshot, DefaultMap, PromiseMap } from './port/utils' function createMismatchError( message: string, @@ -83,7 +83,7 @@ export class SnapshotClient { private fileToTestIds = new DefaultMap>(() => new Set()) private testIdToSnapshotPath = new Map() - private snapshotPathToState = new Map() + private snapshotPathToState = new PromiseMap() // resolve snapshot file for each test and reuse state for same snapshot file // TODO: concurrent safe @@ -95,13 +95,10 @@ export class SnapshotClient { this.fileToTestIds.get(filepath).add(testId) const snapshotPath = await options.snapshotEnvironment.resolvePath(filepath) this.testIdToSnapshotPath.set(testId, snapshotPath) - // share same snapshot state for same snapshot path - let state = this.snapshotPathToState.get(snapshotPath) - if (!state) { + const state = await this.snapshotPathToState.getOrCreate(snapshotPath, async () => { const content = await options.snapshotEnvironment.readSnapshotFile(snapshotPath) - state = new SnapshotState(filepath, snapshotPath, content, options) - this.snapshotPathToState.set(snapshotPath, state) - } + return new SnapshotState(filepath, snapshotPath, content, options) + }) state.clearTest(testId) return state } diff --git a/packages/snapshot/src/port/utils.ts b/packages/snapshot/src/port/utils.ts index 1773945f10de..d609a80b977a 100644 --- a/packages/snapshot/src/port/utils.ts +++ b/packages/snapshot/src/port/utils.ts @@ -295,3 +295,19 @@ export class CounterMap extends Map { return total } } + +export class PromiseMap extends Map { + private inner = new Map>() + + getOrCreate(key: K, createFn: () => Promise): Promise { + let promise = this.inner.get(key) + if (!promise) { + promise = createFn().then((value) => { + this.set(key, value) + return value + }) + this.inner.set(key, promise) + } + return promise + } +} diff --git a/test/core/test/nested-test.test.ts b/test/core/test/nested-test.test.ts index b03cabffe8c7..8663b4e97f8e 100644 --- a/test/core/test/nested-test.test.ts +++ b/test/core/test/nested-test.test.ts @@ -15,14 +15,16 @@ test('nested test should throw error', () => { }) describe('parallel tests', () => { - test.concurrent('parallel test 1 with nested test', () => { + test.concurrent('parallel test 1 with nested test', ({ expect }) => { expect(() => { test('test inside test', () => {}) }).toThrowErrorMatchingInlineSnapshot(`[Error: Calling the test function inside another test function is not allowed. Please put it inside "describe" or "suite" so it can be properly collected.]`) }) test.concurrent('parallel test 2 without nested test', () => {}) test.concurrent('parallel test 3 without nested test', () => {}) - test.concurrent('parallel test 4 with nested test', () => { + // TODO: the error is not guaranteed since getCurrentTest can be already cleared + // https://stackblitz.com/edit/vitest-dev-vitest-44ezrs?file=test%2Fbasic.test.ts + test.skip.concurrent('parallel test 4 with nested test', () => { expect(() => { test('test inside test', () => {}) }).toThrowErrorMatchingInlineSnapshot(`[Error: Calling the test function inside another test function is not allowed. Please put it inside "describe" or "suite" so it can be properly collected.]`) From 95fa5ebc0aa1dff3066a121c5eba9d4049c8043c Mon Sep 17 00:00:00 2001 From: Hiroshi Ogawa Date: Thu, 31 Oct 2024 15:41:36 +0900 Subject: [PATCH 20/21] fix: fix SnapshotClient.finish --- packages/snapshot/src/client.ts | 11 +++++++---- test/snapshots/test/custom-environment.test.ts | 1 + 2 files changed, 8 insertions(+), 4 deletions(-) diff --git a/packages/snapshot/src/client.ts b/packages/snapshot/src/client.ts index 575c304799de..f9e1d12e8186 100644 --- a/packages/snapshot/src/client.ts +++ b/packages/snapshot/src/client.ts @@ -69,14 +69,17 @@ export class SnapshotClient { // } async finish(filepath: string): Promise { + const states = new Set( + [...this.fileToTestIds.get(filepath)].map(testId => + this.getSnapshotState(testId), + ), + ) + this.fileToTestIds.delete(filepath) const results: SnapshotResult[] = [] - for (const testId of this.fileToTestIds.get(filepath)) { - const state = this.getSnapshotState(testId) + for (const state of states) { const result = await state.pack() - this.testIdToSnapshotPath.delete(testId) results.push(result) } - this.fileToTestIds.get(filepath) // TODO: aggregate result return results[0] } diff --git a/test/snapshots/test/custom-environment.test.ts b/test/snapshots/test/custom-environment.test.ts index 2a5098cf7f5f..aa5227813a08 100644 --- a/test/snapshots/test/custom-environment.test.ts +++ b/test/snapshots/test/custom-environment.test.ts @@ -23,6 +23,7 @@ test('custom environment resolved correctly', async () => { expect(snapshotLogs).toMatchInlineSnapshot(` "## resolvePath test/fixtures/custom-snapshot-environment/test/snapshots.test.ts ## readSnapshotFile test/fixtures/custom-snapshot-environment/test/__snapshots__/snapshots.test.ts.snap + ## resolvePath test/fixtures/custom-snapshot-environment/test/snapshots.test.ts ## getHeader ## getVersion ## readSnapshotFile test/fixtures/custom-snapshot-environment/test/__snapshots__/snapshots.test.ts.snap From 177d3b937cd2b8ca2d2997a2d53aa7a24f16e25c Mon Sep 17 00:00:00 2001 From: Hiroshi Ogawa Date: Thu, 31 Oct 2024 15:54:04 +0900 Subject: [PATCH 21/21] fix: fix no result --- packages/vitest/src/runtime/runners/test.ts | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/packages/vitest/src/runtime/runners/test.ts b/packages/vitest/src/runtime/runners/test.ts index 3888cea4069d..ae6230219931 100644 --- a/packages/vitest/src/runtime/runners/test.ts +++ b/packages/vitest/src/runtime/runners/test.ts @@ -69,7 +69,9 @@ export class VitestTestRunner implements VitestRunner { } const result = await this.snapshotClient.finish(suite.file.filepath) - await rpc().snapshotSaved(result) + if (result) { + await rpc().snapshotSaved(result) + } } this.workerState.current = suite.suite || suite.file