From bdd9a7a79b5cf7ab831f63e0991264a566644448 Mon Sep 17 00:00:00 2001 From: Wout Mertens Date: Tue, 28 Jan 2025 23:04:19 +0100 Subject: [PATCH] fix(runner): correctly initialize suite in task this adds a test with a custom runner that allows you to wait for other tests, even when they weren't scheduled. It requires `suite` to be defined at context extension time. --- packages/runner/src/suite.ts | 2 +- .../custom-runner/custom-runner.test.ts | 53 ++++++++++++++++ .../cli/fixtures/custom-runner/test-runner.ts | 60 +++++++++++++++++++ .../fixtures/custom-runner/vitest.config.ts | 7 +++ test/cli/test/custom-runner.test.ts | 16 +++++ 5 files changed, 137 insertions(+), 1 deletion(-) create mode 100644 test/cli/fixtures/custom-runner/custom-runner.test.ts create mode 100644 test/cli/fixtures/custom-runner/test-runner.ts create mode 100644 test/cli/fixtures/custom-runner/vitest.config.ts create mode 100644 test/cli/test/custom-runner.test.ts diff --git a/packages/runner/src/suite.ts b/packages/runner/src/suite.ts index 698763319279..3516311b3da0 100644 --- a/packages/runner/src/suite.ts +++ b/packages/runner/src/suite.ts @@ -304,7 +304,7 @@ function createSuiteCollector( const task: Test = { id: '', name, - suite: undefined!, + suite, each: options.each, fails: options.fails, context: undefined!, diff --git a/test/cli/fixtures/custom-runner/custom-runner.test.ts b/test/cli/fixtures/custom-runner/custom-runner.test.ts new file mode 100644 index 000000000000..b9dbd3f5b701 --- /dev/null +++ b/test/cli/fixtures/custom-runner/custom-runner.test.ts @@ -0,0 +1,53 @@ +import {describe, expect, test, type TestAPI} from 'vitest' + +const myTest = test as TestAPI<{result: (name: string) => Promise}> + +describe('await multiple prior test result', () => { + myTest('first', async () => { + expect(1).toBe(1) + return 1 + }) + + myTest('second', async () => { + expect(1).toBe(1) + return 2 + }) + + myTest('third', async () => { + expect(1).toBe(1) + return 3 + }) + + myTest.only('with await', async ({result}) => { + const third = await result('await multiple prior test result | third') + expect(third).toBe(3) + + const second = await result('await multiple prior test result | second') + expect(second).toBe(2) + + const first = await result('await multiple prior test result | first') + expect(first).toEqual(1) + }) +}) + +myTest.only('supports passing options', {repeats: 2}, context => { + expect(context.task.repeats).toBe(2) +}) + +describe.only('extendable', () => { + const newTest = myTest.extend({ + foo: 'bar', + // eslint-disable-next-line no-empty-pattern + baz: async ({}, use) => { + await use('qux') + }, + }) + newTest('check non func', ({foo}) => { + expect(foo).toBe('bar') + return 'bar' + }) + newTest('check func', async ({baz, result}) => { + expect(baz).toBe('qux') + await expect(result('extendable | check non func')).resolves.toBe('bar') + }) +}) diff --git a/test/cli/fixtures/custom-runner/test-runner.ts b/test/cli/fixtures/custom-runner/test-runner.ts new file mode 100644 index 000000000000..8851a8c1dc95 --- /dev/null +++ b/test/cli/fixtures/custom-runner/test-runner.ts @@ -0,0 +1,60 @@ +import type { Task, TestContext } from '@vitest/runner' +import { getFn } from '@vitest/runner' +import { VitestTestRunner } from 'vitest/runners' + +/** This adds `result` to the test context, which allows for awaiting prior test results, + * enabling sharing of expensive results between tests while enforcing a correct ordering of tests. */ +class CustomTestRunner extends VitestTestRunner { + constructor(config: ConstructorParameters[0]) { + super(config) + } + tasks: Record = {} + _results: Record = {} + + result = (prop: string) => { + if (!(prop in this._results)) { + const task = this.tasks[prop as string] + if (!task) { + throw new Error( + `Task ${String(prop)} not found. Known tests: ${Object.keys( + this.tasks + ) + .map(name => JSON.stringify(name)) + .join(', ')}` + ) + } + // this fills in the results for the task + this.runTask(task) + } + return this._results[prop] + } + + getName(test: Task) { + const name = test.suite?.name + ? `${test.suite.name} | ${test.name}` + : test.name + return name + } + + runTask(test: Task) { + const name = this.getName(test) + const fn = getFn(test) + + const results = fn() + this._results[name] = results + return results + } + + extendTaskContext(context: TestContext) { + super.extendTaskContext(context) + // we need to store the task so we can run it when the results are requested + const {task} = context + const testName = this.getName(task) + this.tasks[testName] = task + ;(context as any).result = this.result + + return context + } +} + +export default CustomTestRunner diff --git a/test/cli/fixtures/custom-runner/vitest.config.ts b/test/cli/fixtures/custom-runner/vitest.config.ts new file mode 100644 index 000000000000..22ac7fcd7a1d --- /dev/null +++ b/test/cli/fixtures/custom-runner/vitest.config.ts @@ -0,0 +1,7 @@ +import { defineConfig } from 'vitest/config' + +export default defineConfig({ + test: { + runner: './test-runner.ts', + }, +}) diff --git a/test/cli/test/custom-runner.test.ts b/test/cli/test/custom-runner.test.ts new file mode 100644 index 000000000000..92f7f6e5f66a --- /dev/null +++ b/test/cli/test/custom-runner.test.ts @@ -0,0 +1,16 @@ +import { expect, test } from 'vitest' +import { runVitest } from '../../test-utils' + +test('can run custom runner with Vitest', async () => { + const vitest = await runVitest({ + root: './fixtures/custom-runner', + reporters: [['default', { isTTY: false }]], + allowOnly: true, + }) + + expect(vitest.stderr).toMatchInlineSnapshot(`""`) + + expect(vitest.stdout).toContain('✓ custom-runner.test.ts') + expect(vitest.stdout).toContain('Test Files 1 passed') + expect(vitest.stdout).toContain('Tests 4 passed | 3 skipped') +})