From 86e4c9654c7178770320fb233396496a3f7bd786 Mon Sep 17 00:00:00 2001 From: Jeppe Reinhold <jeppe@reinhold.is> Date: Fri, 29 Nov 2024 13:08:57 +0100 Subject: [PATCH 1/4] always run vitest in watch mode --- code/addons/test/src/node/test-manager.ts | 50 ++++++++++----------- code/addons/test/src/node/vitest-manager.ts | 14 +++--- 2 files changed, 31 insertions(+), 33 deletions(-) diff --git a/code/addons/test/src/node/test-manager.ts b/code/addons/test/src/node/test-manager.ts index f424408b7bc3..2be5356ef118 100644 --- a/code/addons/test/src/node/test-manager.ts +++ b/code/addons/test/src/node/test-manager.ts @@ -39,10 +39,10 @@ export class TestManager { this.vitestManager.startVitest().then(() => options.onReady?.()); } - async restartVitest({ watchMode, coverage }: { watchMode: boolean; coverage: boolean }) { + async restartVitest({ coverage }: { coverage: boolean }) { await this.vitestManager.vitest?.runningPromise; await this.vitestManager.closeVitest(); - await this.vitestManager.startVitest({ watchMode, coverage }); + await this.vitestManager.startVitest({ coverage }); } async handleConfigChange( @@ -52,9 +52,9 @@ export class TestManager { return; } if (this.coverage !== payload.config.coverage) { + this.coverage = payload.config.coverage; try { - this.coverage = payload.config.coverage; - await this.restartVitest({ watchMode: this.watchMode, coverage: this.coverage }); + await this.restartVitest({ coverage: this.coverage }); } catch (e) { this.reportFatalError('Failed to change coverage mode', e); } @@ -62,14 +62,18 @@ export class TestManager { } async handleWatchModeRequest(payload: TestingModuleWatchModeRequestPayload) { - try { - if (payload.providerId !== TEST_PROVIDER_ID) { - return; - } + if (payload.providerId !== TEST_PROVIDER_ID) { + return; + } + this.watchMode = payload.watchMode; - if (this.watchMode !== payload.watchMode) { - this.watchMode = payload.watchMode; - await this.restartVitest({ watchMode: this.watchMode, coverage: false }); + try { + if (payload.watchMode && this.coverage) { + // if watch mode is toggled on and coverage is already enabled, restart vitest without coverage to automatically disable it + this.restartVitest({ coverage: false }); + } else if (!payload.watchMode && this.coverage) { + // if watch mode is toggled off and coverage is already enabled, restart vitest with coverage to automatically re-enable it + this.restartVitest({ coverage: this.coverage }); } } catch (e) { this.reportFatalError('Failed to change watch mode', e); @@ -82,28 +86,22 @@ export class TestManager { return; } - const allTestsRun = (payload.storyIds ?? []).length === 0; - if (this.coverage) { - /* - If we have coverage enabled and we're running all stories, - we have to restart Vitest AND disable watch mode otherwise the coverage report will be incorrect, - Vitest behaves wonky when re-using the same Vitest instance but with watch mode disabled, - among other things it causes the coverage report to be incorrect and stale. - - If we're only running a subset of stories, we have to temporarily disable coverage, - as a coverage report for a subset of stories is not useful. - */ + /* + If we're only running a subset of stories, we have to temporarily disable coverage, + as a coverage report for a subset of stories is not useful. + */ + const temporarilyDisableCoverage = this.coverage && (payload.storyIds ?? []).length === 0; + if (temporarilyDisableCoverage) { await this.restartVitest({ - watchMode: allTestsRun ? false : this.watchMode, - coverage: allTestsRun, + coverage: false, }); } await this.vitestManager.runTests(payload); - if (this.coverage && !allTestsRun) { + if (temporarilyDisableCoverage) { // Re-enable coverage if it was temporarily disabled because of a subset of stories was run - await this.restartVitest({ watchMode: this.watchMode, coverage: this.coverage }); + await this.restartVitest({ coverage: this.coverage }); } } catch (e) { this.reportFatalError('Failed to run tests', e); diff --git a/code/addons/test/src/node/vitest-manager.ts b/code/addons/test/src/node/vitest-manager.ts index 37e6e0588aa1..8d4abc854695 100644 --- a/code/addons/test/src/node/vitest-manager.ts +++ b/code/addons/test/src/node/vitest-manager.ts @@ -38,7 +38,7 @@ export class VitestManager { constructor(private testManager: TestManager) {} - async startVitest({ watchMode = false, coverage = false } = {}) { + async startVitest({ coverage = false } = {}) { const { createVitest } = await import('vitest/node'); const storybookCoverageReporter: [string, StorybookCoverageReporterOptions] = [ @@ -53,7 +53,7 @@ export class VitestManager { ? { enabled: true, clean: false, - cleanOnRerun: !watchMode, + cleanOnRerun: false, reportOnFailure: true, reporter: [['html', {}], storybookCoverageReporter], reportsDirectory: resolvePathInStorybookCache(COVERAGE_DIRECTORY), @@ -62,9 +62,8 @@ export class VitestManager { ) as CoverageOptions; this.vitest = await createVitest('test', { - watch: watchMode, + watch: true, passWithNoTests: false, - changed: watchMode, // TODO: // Do we want to enable Vite's default reporter? // The output in the terminal might be too spamy and it might be better to @@ -82,9 +81,7 @@ export class VitestManager { await this.vitest.init(); - if (watchMode) { - await this.setupWatchers(); - } + await this.setupWatchers(); } private updateLastChanged(filepath: string) { @@ -276,6 +273,9 @@ export class VitestManager { this.updateLastChanged(id); this.storyCountForCurrentRun = 0; + if (!this.testManager.watchMode) { + return; + } await this.runAffectedTests(file); } From e5fb87d51924cbc9d1b183f03304e413cdab460d Mon Sep 17 00:00:00 2001 From: Jeppe Reinhold <jeppe@reinhold.is> Date: Mon, 2 Dec 2024 15:40:29 +0100 Subject: [PATCH 2/4] fix coverage mode toggling --- code/addons/test/src/node/test-manager.ts | 7 ++++--- code/addons/test/src/node/vitest-manager.ts | 2 ++ 2 files changed, 6 insertions(+), 3 deletions(-) diff --git a/code/addons/test/src/node/test-manager.ts b/code/addons/test/src/node/test-manager.ts index a9b558c66acc..06cbe7d008f2 100644 --- a/code/addons/test/src/node/test-manager.ts +++ b/code/addons/test/src/node/test-manager.ts @@ -70,10 +70,10 @@ export class TestManager { try { if (payload.watchMode && this.coverage) { // if watch mode is toggled on and coverage is already enabled, restart vitest without coverage to automatically disable it - this.restartVitest({ coverage: false }); + await this.restartVitest({ coverage: false }); } else if (!payload.watchMode && this.coverage) { // if watch mode is toggled off and coverage is already enabled, restart vitest with coverage to automatically re-enable it - this.restartVitest({ coverage: this.coverage }); + await this.restartVitest({ coverage: this.coverage }); } } catch (e) { this.reportFatalError('Failed to change watch mode', e); @@ -93,7 +93,8 @@ export class TestManager { If we're only running a subset of stories, we have to temporarily disable coverage, as a coverage report for a subset of stories is not useful. */ - const temporarilyDisableCoverage = this.coverage && (payload.storyIds ?? []).length === 0; + const temporarilyDisableCoverage = + this.coverage && !this.watchMode && (payload.storyIds ?? []).length > 0; if (temporarilyDisableCoverage) { await this.restartVitest({ coverage: false, diff --git a/code/addons/test/src/node/vitest-manager.ts b/code/addons/test/src/node/vitest-manager.ts index 8d4abc854695..7404b94cd315 100644 --- a/code/addons/test/src/node/vitest-manager.ts +++ b/code/addons/test/src/node/vitest-manager.ts @@ -273,6 +273,8 @@ export class VitestManager { this.updateLastChanged(id); this.storyCountForCurrentRun = 0; + // when watch mode is disabled, don't trigger any tests (below) + // but still invalidate the cache for the changed file, which is handled above if (!this.testManager.watchMode) { return; } From 47101c6f96e289e0e7100873c702c17eb545691f Mon Sep 17 00:00:00 2001 From: Jeppe Reinhold <jeppe@reinhold.is> Date: Fri, 6 Dec 2024 11:27:15 +0100 Subject: [PATCH 3/4] add coverage unit tests --- .../addons/test/src/node/test-manager.test.ts | 58 ++++++++++++++++++- 1 file changed, 56 insertions(+), 2 deletions(-) diff --git a/code/addons/test/src/node/test-manager.test.ts b/code/addons/test/src/node/test-manager.test.ts index b674c1d786f7..db77ab2f3e2e 100644 --- a/code/addons/test/src/node/test-manager.test.ts +++ b/code/addons/test/src/node/test-manager.test.ts @@ -1,5 +1,5 @@ import { describe, expect, it, vi } from 'vitest'; -import { createVitest } from 'vitest/node'; +import { createVitest as actualCreateVitest } from 'vitest/node'; import { Channel, type ChannelTransport } from '@storybook/core/channels'; import type { StoryIndex } from '@storybook/types'; @@ -34,6 +34,7 @@ const vitest = vi.hoisted(() => ({ vi.mock('vitest/node', () => ({ createVitest: vi.fn(() => Promise.resolve(vitest)), })); +const createVitest = vi.mocked(actualCreateVitest); const transport = { setHandler: vi.fn(), send: vi.fn() } satisfies ChannelTransport; const mockChannel = new Channel({ transport }); @@ -109,7 +110,7 @@ describe('TestManager', () => { await testManager.handleWatchModeRequest({ providerId: TEST_PROVIDER_ID, watchMode: true }); expect(testManager.watchMode).toBe(true); - expect(createVitest).toHaveBeenCalledTimes(2); + expect(createVitest).toHaveBeenCalledTimes(1); // shouldn't restart vitest }); it('should handle run request', async () => { @@ -145,4 +146,57 @@ describe('TestManager', () => { expect(setTestNamePattern).toHaveBeenCalledWith(/^One$/); expect(vitest.runFiles).toHaveBeenCalledWith(tests.slice(0, 1), true); }); + + it('should handle coverage toggling', async () => { + const testManager = await TestManager.start(mockChannel, options); + expect(testManager.coverage).toBe(false); + expect(createVitest).toHaveBeenCalledTimes(1); + createVitest.mockClear(); + + await testManager.handleConfigChange({ + providerId: TEST_PROVIDER_ID, + config: { coverage: true, a11y: false }, + }); + expect(testManager.coverage).toBe(true); + expect(createVitest).toHaveBeenCalledTimes(1); + createVitest.mockClear(); + + await testManager.handleConfigChange({ + providerId: TEST_PROVIDER_ID, + config: { coverage: false, a11y: false }, + }); + expect(testManager.coverage).toBe(false); + expect(createVitest).toHaveBeenCalledTimes(1); + }); + + it('should temporarily disable coverage on focused tests', async () => { + vitest.globTestSpecs.mockImplementation(() => tests); + const testManager = await TestManager.start(mockChannel, options); + expect(testManager.coverage).toBe(false); + expect(createVitest).toHaveBeenCalledTimes(1); + + await testManager.handleConfigChange({ + providerId: TEST_PROVIDER_ID, + config: { coverage: true, a11y: false }, + }); + expect(testManager.coverage).toBe(true); + expect(createVitest).toHaveBeenCalledTimes(2); + + await testManager.handleRunRequest({ + providerId: TEST_PROVIDER_ID, + indexUrl: 'http://localhost:6006/index.json', + storyIds: ['button--primary', 'button--secondary'], + }); + // expect vitest to be restarted twice, without and with coverage + expect(createVitest).toHaveBeenCalledTimes(4); + expect(vitest.runFiles).toHaveBeenCalledWith([], true); + + await testManager.handleRunRequest({ + providerId: TEST_PROVIDER_ID, + indexUrl: 'http://localhost:6006/index.json', + }); + // don't expect vitest to be restarted, as we're running all tests + expect(createVitest).toHaveBeenCalledTimes(4); + expect(vitest.runFiles).toHaveBeenCalledWith(tests, true); + }); }); From 57cb78bfee77b8b8b6c17b1cd541a760bb517653 Mon Sep 17 00:00:00 2001 From: Jeppe Reinhold <jeppe@reinhold.is> Date: Tue, 10 Dec 2024 10:21:20 +0100 Subject: [PATCH 4/4] update yarn.lock --- test-storybooks/portable-stories-kitchen-sink/react/yarn.lock | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/test-storybooks/portable-stories-kitchen-sink/react/yarn.lock b/test-storybooks/portable-stories-kitchen-sink/react/yarn.lock index b6be56469636..e3484dc01721 100644 --- a/test-storybooks/portable-stories-kitchen-sink/react/yarn.lock +++ b/test-storybooks/portable-stories-kitchen-sink/react/yarn.lock @@ -1841,7 +1841,7 @@ __metadata: "@storybook/experimental-addon-test@file:../../../code/addons/test::locator=portable-stories-react%40workspace%3A.": version: 8.5.0-alpha.18 - resolution: "@storybook/experimental-addon-test@file:../../../code/addons/test#../../../code/addons/test::hash=f1c849&locator=portable-stories-react%40workspace%3A." + resolution: "@storybook/experimental-addon-test@file:../../../code/addons/test#../../../code/addons/test::hash=21369f&locator=portable-stories-react%40workspace%3A." dependencies: "@storybook/csf": "npm:0.1.12" "@storybook/global": "npm:^5.0.0" @@ -1864,7 +1864,7 @@ __metadata: optional: true vitest: optional: true - checksum: 10/29f2db97577ff0a3a79ae37a8b0eaebd86d7eca43c3ad364abbb5f4a63e5f9d12dab2927723802752a2c2e2d36561ac89db32a7fd770e31c5b6e573e265bdd27 + checksum: 10/2081814e214dc1dd31144870a6a4ea7637c9c241ab02044488be57e19402c206c0037d449197f77bb4262147703f6d0b27f09c9f6cc2ee358c97fd7d1cdfa908 languageName: node linkType: hard