From 8f0ad552e22301b9901bf6a5b12408157bc2dbf0 Mon Sep 17 00:00:00 2001 From: Gert Hengeveld Date: Mon, 18 Nov 2024 16:40:56 +0100 Subject: [PATCH 01/13] Refactor "run all tests" behavior to reuse the RUN_REQUEST event and use the story index to count the total number of tests and only run tests known by Storybook --- code/addons/test/src/constants.ts | 1 + code/addons/test/src/manager.tsx | 5 +- .../test/src/node/boot-test-runner.test.ts | 8 -- code/addons/test/src/node/boot-test-runner.ts | 5 - code/addons/test/src/node/reporter.ts | 10 +- .../addons/test/src/node/test-manager.test.ts | 62 ++++++------ code/addons/test/src/node/test-manager.ts | 35 ++----- code/addons/test/src/node/vitest-manager.ts | 89 +++++++----------- code/addons/test/src/preset.ts | 2 - .../src/core-events/data/testing-module.ts | 17 +--- code/core/src/core-events/index.ts | 2 - .../modules/experimental_testmodule.ts | 94 ++++--------------- .../components/sidebar/SidebarBottom.tsx | 18 +--- code/core/src/manager/globals/exports.ts | 3 - 14 files changed, 109 insertions(+), 242 deletions(-) diff --git a/code/addons/test/src/constants.ts b/code/addons/test/src/constants.ts index b1c1808db1a4..3801fae52d97 100644 --- a/code/addons/test/src/constants.ts +++ b/code/addons/test/src/constants.ts @@ -2,6 +2,7 @@ export const ADDON_ID = 'storybook/test'; export const TEST_PROVIDER_ID = `${ADDON_ID}/test-provider`; export const PANEL_ID = `${ADDON_ID}/panel`; export const STORYBOOK_ADDON_TEST_CHANNEL = 'STORYBOOK_ADDON_TEST_CHANNEL'; +export const RUN_TESTS_EVENT = `${ADDON_ID}/RUN_TESTS`; export const TUTORIAL_VIDEO_LINK = 'https://youtu.be/Waht9qq7AoA'; export const DOCUMENTATION_LINK = 'writing-tests/test-addon'; diff --git a/code/addons/test/src/manager.tsx b/code/addons/test/src/manager.tsx index ed8a3053f2fe..1ace0aec8443 100644 --- a/code/addons/test/src/manager.tsx +++ b/code/addons/test/src/manager.tsx @@ -1,7 +1,6 @@ import React, { useEffect, useState } from 'react'; import { AddonPanel, Button, Link as LinkComponent } from 'storybook/internal/components'; -import { TESTING_MODULE_RUN_ALL_REQUEST } from 'storybook/internal/core-events'; import type { Combo } from 'storybook/internal/manager-api'; import { Consumer, addons, types } from 'storybook/internal/manager-api'; import { styled } from 'storybook/internal/theming'; @@ -210,9 +209,7 @@ addons.register(ADDON_ID, (api) => { }} onRerun={() => { setIsModalOpen(false); - api - .getChannel() - .emit(TESTING_MODULE_RUN_ALL_REQUEST, { providerId: TEST_PROVIDER_ID }); + api.runTestProvider(TEST_PROVIDER_ID); }} /> diff --git a/code/addons/test/src/node/boot-test-runner.test.ts b/code/addons/test/src/node/boot-test-runner.test.ts index 7792bffdc9b3..f5d9fde5bf28 100644 --- a/code/addons/test/src/node/boot-test-runner.test.ts +++ b/code/addons/test/src/node/boot-test-runner.test.ts @@ -5,7 +5,6 @@ import { Channel, type ChannelTransport } from '@storybook/core/channels'; import { TESTING_MODULE_CANCEL_TEST_RUN_REQUEST, TESTING_MODULE_PROGRESS_REPORT, - TESTING_MODULE_RUN_ALL_REQUEST, TESTING_MODULE_RUN_REQUEST, TESTING_MODULE_WATCH_MODE_REQUEST, } from '@storybook/core/core-events'; @@ -103,13 +102,6 @@ describe('bootTestRunner', () => { type: TESTING_MODULE_RUN_REQUEST, }); - mockChannel.emit(TESTING_MODULE_RUN_ALL_REQUEST, 'bar'); - expect(child.send).toHaveBeenCalledWith({ - args: ['bar'], - from: 'server', - type: TESTING_MODULE_RUN_ALL_REQUEST, - }); - mockChannel.emit(TESTING_MODULE_WATCH_MODE_REQUEST, 'baz'); expect(child.send).toHaveBeenCalledWith({ args: ['baz'], diff --git a/code/addons/test/src/node/boot-test-runner.ts b/code/addons/test/src/node/boot-test-runner.ts index 032be588b107..51f353bfe04e 100644 --- a/code/addons/test/src/node/boot-test-runner.ts +++ b/code/addons/test/src/node/boot-test-runner.ts @@ -5,7 +5,6 @@ import type { Channel } from 'storybook/internal/channels'; import { TESTING_MODULE_CANCEL_TEST_RUN_REQUEST, TESTING_MODULE_CRASH_REPORT, - TESTING_MODULE_RUN_ALL_REQUEST, TESTING_MODULE_RUN_REQUEST, TESTING_MODULE_WATCH_MODE_REQUEST, type TestingModuleCrashReportPayload, @@ -40,8 +39,6 @@ const bootTestRunner = async (channel: Channel, initEvent?: string, initArgs?: a const forwardRun = (...args: any[]) => child?.send({ args, from: 'server', type: TESTING_MODULE_RUN_REQUEST }); - const forwardRunAll = (...args: any[]) => - child?.send({ args, from: 'server', type: TESTING_MODULE_RUN_ALL_REQUEST }); const forwardWatchMode = (...args: any[]) => child?.send({ args, from: 'server', type: TESTING_MODULE_WATCH_MODE_REQUEST }); const forwardCancel = (...args: any[]) => @@ -49,7 +46,6 @@ const bootTestRunner = async (channel: Channel, initEvent?: string, initArgs?: a const killChild = () => { channel.off(TESTING_MODULE_RUN_REQUEST, forwardRun); - channel.off(TESTING_MODULE_RUN_ALL_REQUEST, forwardRunAll); channel.off(TESTING_MODULE_WATCH_MODE_REQUEST, forwardWatchMode); channel.off(TESTING_MODULE_CANCEL_TEST_RUN_REQUEST, forwardCancel); child?.kill(); @@ -88,7 +84,6 @@ const bootTestRunner = async (channel: Channel, initEvent?: string, initArgs?: a // Forward all events from the channel to the child process channel.on(TESTING_MODULE_RUN_REQUEST, forwardRun); - channel.on(TESTING_MODULE_RUN_ALL_REQUEST, forwardRunAll); channel.on(TESTING_MODULE_WATCH_MODE_REQUEST, forwardWatchMode); channel.on(TESTING_MODULE_CANCEL_TEST_RUN_REQUEST, forwardCancel); diff --git a/code/addons/test/src/node/reporter.ts b/code/addons/test/src/node/reporter.ts index 833ab0bf3823..191d53d69649 100644 --- a/code/addons/test/src/node/reporter.ts +++ b/code/addons/test/src/node/reporter.ts @@ -64,7 +64,7 @@ export class StorybookReporter implements Reporter { sendReport: (payload: TestingModuleProgressReportPayload) => void; - constructor(private testManager: TestManager) { + constructor(public testManager: TestManager) { this.sendReport = throttle((payload) => this.testManager.sendProgressReport(payload), 1000); } @@ -76,8 +76,12 @@ export class StorybookReporter implements Reporter { getProgressReport(finishedAt?: number) { const files = this.ctx.state.getFiles(); const fileTests = getTests(files); - // The number of total tests is dynamic and can change during the run - const numTotalTests = fileTests.length; + + // The total number of tests reported by Vitest is dynamic and can change during the run, so we + // use `storyCountForCurrentRun` instead, based on the list of stories provided in the run request. + const numTotalTests = finishedAt + ? fileTests.length + : Math.max(fileTests.length, this.testManager.vitestManager.storyCountForCurrentRun); const numFailedTests = fileTests.filter((t) => t.result?.state === 'fail').length; const numPassedTests = fileTests.filter((t) => t.result?.state === 'pass').length; diff --git a/code/addons/test/src/node/test-manager.test.ts b/code/addons/test/src/node/test-manager.test.ts index 32a404cfc385..66915235a579 100644 --- a/code/addons/test/src/node/test-manager.test.ts +++ b/code/addons/test/src/node/test-manager.test.ts @@ -17,6 +17,7 @@ const vitest = vi.hoisted(() => ({ cancelCurrentRun: vi.fn(), globTestSpecs: vi.fn(), getModuleProjects: vi.fn(() => []), + configOverride: {}, })); vi.mock('vitest/node', () => ({ @@ -37,6 +38,31 @@ const tests = [ }, ]; +global.fetch = vi.fn().mockResolvedValue({ + json: () => + new Promise((resolve) => + resolve({ + v: 5, + entries: { + 'story--one': { + type: 'story', + id: 'story--one', + name: 'One', + title: 'story/one', + importPath: 'path/to/file', + }, + 'another--one': { + type: 'story', + id: 'another--one', + name: 'One', + title: 'another/one', + importPath: 'path/to/another/file', + }, + }, + }) + ), +}); + const options: ConstructorParameters[1] = { onError: (message, error) => { throw error; @@ -80,16 +106,7 @@ describe('TestManager', () => { await testManager.handleRunRequest({ providerId: TEST_PROVIDER_ID, - payload: [ - { - stories: [], - importPath: 'path/to/file', - }, - { - stories: [], - importPath: 'path/to/another/file', - }, - ], + indexUrl: 'http://localhost:6006/index.json', }); expect(createVitest).toHaveBeenCalledTimes(1); expect(vitest.runFiles).toHaveBeenCalledWith(tests, true); @@ -101,33 +118,16 @@ describe('TestManager', () => { await testManager.handleRunRequest({ providerId: TEST_PROVIDER_ID, - payload: [ - { - stories: [], - importPath: 'path/to/unknown/file', - }, - ], + indexUrl: 'http://localhost:6006/index.json', + storyIds: [], }); expect(vitest.runFiles).toHaveBeenCalledWith([], true); await testManager.handleRunRequest({ providerId: TEST_PROVIDER_ID, - payload: [ - { - stories: [], - importPath: 'path/to/file', - }, - ], + indexUrl: 'http://localhost:6006/index.json', + storyIds: ['story--one'], }); expect(vitest.runFiles).toHaveBeenCalledWith(tests.slice(0, 1), true); }); - - it('should handle run all request', async () => { - const testManager = await TestManager.start(mockChannel, options); - expect(createVitest).toHaveBeenCalledTimes(1); - - await testManager.handleRunAllRequest({ providerId: TEST_PROVIDER_ID }); - expect(createVitest).toHaveBeenCalledTimes(1); - expect(vitest.runFiles).toHaveBeenCalledWith(tests, true); - }); }); diff --git a/code/addons/test/src/node/test-manager.ts b/code/addons/test/src/node/test-manager.ts index a462fb1f7371..8d9a33a21e68 100644 --- a/code/addons/test/src/node/test-manager.ts +++ b/code/addons/test/src/node/test-manager.ts @@ -2,12 +2,10 @@ import type { Channel } from 'storybook/internal/channels'; import { TESTING_MODULE_CANCEL_TEST_RUN_REQUEST, TESTING_MODULE_PROGRESS_REPORT, - TESTING_MODULE_RUN_ALL_REQUEST, TESTING_MODULE_RUN_REQUEST, TESTING_MODULE_WATCH_MODE_REQUEST, type TestingModuleCancelTestRunRequestPayload, type TestingModuleProgressReportPayload, - type TestingModuleRunAllRequestPayload, type TestingModuleRunRequestPayload, type TestingModuleWatchModeRequestPayload, } from 'storybook/internal/core-events'; @@ -16,7 +14,7 @@ import { TEST_PROVIDER_ID } from '../constants'; import { VitestManager } from './vitest-manager'; export class TestManager { - private vitestManager: VitestManager; + vitestManager: VitestManager; watchMode = false; @@ -30,7 +28,6 @@ export class TestManager { this.vitestManager = new VitestManager(channel, this); this.channel.on(TESTING_MODULE_RUN_REQUEST, this.handleRunRequest.bind(this)); - this.channel.on(TESTING_MODULE_RUN_ALL_REQUEST, this.handleRunAllRequest.bind(this)); this.channel.on(TESTING_MODULE_WATCH_MODE_REQUEST, this.handleWatchModeRequest.bind(this)); this.channel.on(TESTING_MODULE_CANCEL_TEST_RUN_REQUEST, this.handleCancelRequest.bind(this)); @@ -43,14 +40,14 @@ export class TestManager { await this.vitestManager.startVitest(watchMode); } - async handleWatchModeRequest(request: TestingModuleWatchModeRequestPayload) { + async handleWatchModeRequest(payload: TestingModuleWatchModeRequestPayload) { try { - if (request.providerId !== TEST_PROVIDER_ID) { + if (payload.providerId !== TEST_PROVIDER_ID) { return; } - if (this.watchMode !== request.watchMode) { - this.watchMode = request.watchMode; + if (this.watchMode !== payload.watchMode) { + this.watchMode = payload.watchMode; await this.restartVitest(this.watchMode); } } catch (e) { @@ -58,33 +55,21 @@ export class TestManager { } } - async handleRunRequest(request: TestingModuleRunRequestPayload) { + async handleRunRequest(payload: TestingModuleRunRequestPayload) { try { - if (request.providerId !== TEST_PROVIDER_ID) { + if (payload.providerId !== TEST_PROVIDER_ID) { return; } - await this.vitestManager.runTests(request.payload); + await this.vitestManager.runTests(payload); } catch (e) { this.reportFatalError('Failed to run tests', e); } } - async handleRunAllRequest(request: TestingModuleRunAllRequestPayload) { + async handleCancelRequest(payload: TestingModuleCancelTestRunRequestPayload) { try { - if (request.providerId !== TEST_PROVIDER_ID) { - return; - } - - await this.vitestManager.runAllTests(); - } catch (e) { - this.reportFatalError('Failed to run all tests', e); - } - } - - async handleCancelRequest(request: TestingModuleCancelTestRunRequestPayload) { - try { - if (request.providerId !== TEST_PROVIDER_ID) { + if (payload.providerId !== TEST_PROVIDER_ID) { return; } diff --git a/code/addons/test/src/node/vitest-manager.ts b/code/addons/test/src/node/vitest-manager.ts index f69f065aa42a..df7eedce67e4 100644 --- a/code/addons/test/src/node/vitest-manager.ts +++ b/code/addons/test/src/node/vitest-manager.ts @@ -6,6 +6,8 @@ import type { TestProject, TestSpecification, Vitest, WorkspaceProject } from 'v import type { Channel } from 'storybook/internal/channels'; import type { TestingModuleRunRequestPayload } from 'storybook/internal/core-events'; +import type { StoryIndex } from '@storybook/types'; + import slash from 'slash'; import { log } from '../logger'; @@ -17,6 +19,8 @@ export class VitestManager { vitestStartupCounter = 0; + storyCountForCurrentRun: number = 0; + constructor( private channel: Channel, private testManager: TestManager @@ -54,23 +58,6 @@ export class VitestManager { } } - async runAllTests() { - if (!this.vitest) { - await this.startVitest(); - } - this.resetTestNamePattern(); - - const storybookTests = await this.getStorybookTestSpecs(); - for (const storybookTest of storybookTests) { - // make sure to clear the file cache so test results are updated even if watch mode is not enabled - if (!this.testManager.watchMode) { - this.updateLastChanged(storybookTest.moduleId); - } - } - await this.cancelCurrentRun(); - await this.vitest!.runFiles(storybookTests, true); - } - private updateLastChanged(filepath: string) { const projects = this.vitest!.getModuleProjects(filepath); projects.forEach(({ server, browser }) => { @@ -84,55 +71,48 @@ export class VitestManager { }); } - async runTests(testPayload: TestingModuleRunRequestPayload['payload']) { + async runTests(requestPayload: TestingModuleRunRequestPayload) { if (!this.vitest) { await this.startVitest(); } this.resetTestNamePattern(); - // This list contains all the test files (story files) that need to be run - // based on the test files that are passed in the tests array - // This list does NOT contain any filtering of specific - // test cases (story) within the test files - const testList: TestSpecification[] = []; - - const storybookTests = await this.getStorybookTestSpecs(); - - const filteredStoryNames: string[] = []; - - for (const storybookTest of storybookTests) { - const match = testPayload.find((test) => { - const absoluteImportPath = path.join(process.cwd(), test.importPath); - return absoluteImportPath === storybookTest.moduleId; - }); - if (match) { - // make sure to clear the file cache so test results are updated even if watch mode is not enabled - if (!this.testManager.watchMode) { - this.updateLastChanged(storybookTest.moduleId); - } - - if (match.stories?.length) { - filteredStoryNames.push(...match.stories.map((story) => story.name)); + const index = (await fetch(requestPayload.indexUrl).then((res) => res.json())) as StoryIndex; + const { storyIds = Object.keys(index.entries) } = requestPayload; + const stories = storyIds.map((id) => index.entries[id]); + const vitestTestSpecs = await this.getStorybookTestSpecs(); + const isSingleStoryRun = requestPayload.storyIds?.length === 1; + + const { filteredTestFiles, totalTestCount } = vitestTestSpecs.reduce( + (acc, vitestTestSpec) => { + const matches = stories.filter((story) => { + const absoluteImportPath = path.join(process.cwd(), story.importPath); + return absoluteImportPath === vitestTestSpec.moduleId; + }); + if (matches.length) { + if (!this.testManager.watchMode) { + // Clear the file cache if watch mode is not enabled + this.updateLastChanged(vitestTestSpec.moduleId); + } + acc.filteredTestFiles.push(vitestTestSpec); + acc.totalTestCount += matches.length; } - testList.push(storybookTest); - } - } + return acc; + }, + { filteredTestFiles: [] as TestSpecification[], totalTestCount: 0 } + ); await this.cancelCurrentRun(); + this.storyCountForCurrentRun = totalTestCount; - if (filteredStoryNames.length > 0) { - // temporarily set the test name pattern to only run the selected stories - // converting a list of story names to a single regex pattern - // ie. ['My Story', 'Other Story'] => /^(My Story|Other Story)$/ - const testNamePattern = new RegExp( - `^(${filteredStoryNames - .map((name) => name.replace(/[.*+?^${}()|[\]\\]/g, '\\$&')) - .join('|')})$` + if (isSingleStoryRun) { + const storyName = stories[0].name; + this.vitest!.configOverride.testNamePattern = new RegExp( + `^${storyName.replace(/[.*+?^${}()|[\]\\]/g, '\\$&')}$` ); - this.vitest!.configOverride.testNamePattern = testNamePattern; } - await this.vitest!.runFiles(testList, true); + await this.vitest!.runFiles(filteredTestFiles, true); this.resetTestNamePattern(); } @@ -224,6 +204,7 @@ export class VitestManager { const id = slash(file); this.vitest?.logger.clearHighlightCache(id); this.updateLastChanged(id); + this.storyCountForCurrentRun = 0; await this.runAffectedTests(file); } diff --git a/code/addons/test/src/preset.ts b/code/addons/test/src/preset.ts index 685a56e2baf4..2e1e361337f7 100644 --- a/code/addons/test/src/preset.ts +++ b/code/addons/test/src/preset.ts @@ -4,7 +4,6 @@ import { isAbsolute, join } from 'node:path'; import type { Channel } from 'storybook/internal/channels'; import { checkAddonOrder, getFrameworkName, serverRequire } from 'storybook/internal/common'; import { - TESTING_MODULE_RUN_ALL_REQUEST, TESTING_MODULE_RUN_REQUEST, TESTING_MODULE_WATCH_MODE_REQUEST, } from 'storybook/internal/core-events'; @@ -68,7 +67,6 @@ export const experimental_serverChannel = async (channel: Channel, options: Opti runTestRunner(channel, eventName, args); }; - channel.on(TESTING_MODULE_RUN_ALL_REQUEST, execute(TESTING_MODULE_RUN_ALL_REQUEST)); channel.on(TESTING_MODULE_RUN_REQUEST, execute(TESTING_MODULE_RUN_REQUEST)); channel.on(TESTING_MODULE_WATCH_MODE_REQUEST, (payload) => { if (payload.watchMode) { diff --git a/code/core/src/core-events/data/testing-module.ts b/code/core/src/core-events/data/testing-module.ts index ee483364d93e..48544c8669d4 100644 --- a/code/core/src/core-events/data/testing-module.ts +++ b/code/core/src/core-events/data/testing-module.ts @@ -8,22 +8,11 @@ export type TestProviderState = Addon_TestProviderState; export type TestProviders = Record; -export type TestingModuleRunRequestStory = { - id: string; // button--primary - name: string; // Primary -}; - export type TestingModuleRunRequestPayload = { providerId: TestProviderId; - payload: { - importPath: string; // ./.../button.stories.tsx - stories?: TestingModuleRunRequestStory[]; - componentPath?: string; // ./.../button.tsx - }[]; -}; - -export type TestingModuleRunAllRequestPayload = { - providerId: TestProviderId; + // TODO: Avoid needing to do a fetch request server-side to retrieve the index + indexUrl: string; // e.g. http://localhost:6006/index.json + storyIds?: string[]; // ['button--primary', 'button--secondary'] }; export type TestingModuleProgressReportPayload = diff --git a/code/core/src/core-events/index.ts b/code/core/src/core-events/index.ts index d5d51eeecdf4..f0b09f946607 100644 --- a/code/core/src/core-events/index.ts +++ b/code/core/src/core-events/index.ts @@ -88,7 +88,6 @@ enum events { TESTING_MODULE_CRASH_REPORT = 'testingModuleCrashReport', TESTING_MODULE_PROGRESS_REPORT = 'testingModuleProgressReport', TESTING_MODULE_RUN_REQUEST = 'testingModuleRunRequest', - TESTING_MODULE_RUN_ALL_REQUEST = 'testingModuleRunAllRequest', TESTING_MODULE_CANCEL_TEST_RUN_REQUEST = 'testingModuleCancelTestRunRequest', TESTING_MODULE_CANCEL_TEST_RUN_RESPONSE = 'testingModuleCancelTestRunResponse', TESTING_MODULE_WATCH_MODE_REQUEST = 'testingModuleWatchModeRequest', @@ -158,7 +157,6 @@ export const { TESTING_MODULE_CRASH_REPORT, TESTING_MODULE_PROGRESS_REPORT, TESTING_MODULE_RUN_REQUEST, - TESTING_MODULE_RUN_ALL_REQUEST, TESTING_MODULE_CANCEL_TEST_RUN_REQUEST, TESTING_MODULE_CANCEL_TEST_RUN_RESPONSE, TESTING_MODULE_WATCH_MODE_REQUEST, diff --git a/code/core/src/manager-api/modules/experimental_testmodule.ts b/code/core/src/manager-api/modules/experimental_testmodule.ts index b16956be7a83..78d4510f51c8 100644 --- a/code/core/src/manager-api/modules/experimental_testmodule.ts +++ b/code/core/src/manager-api/modules/experimental_testmodule.ts @@ -1,14 +1,12 @@ -import { type API_StoryEntry, Addon_TypesEnum, type StoryId } from '@storybook/core/types'; +import { Addon_TypesEnum, type StoryId } from '@storybook/core/types'; import { TESTING_MODULE_CANCEL_TEST_RUN_REQUEST, - TESTING_MODULE_RUN_ALL_REQUEST, TESTING_MODULE_RUN_REQUEST, TESTING_MODULE_WATCH_MODE_REQUEST, type TestProviderId, type TestProviderState, type TestProviders, - type TestingModuleRunAllRequestPayload, type TestingModuleRunRequestPayload, } from '@storybook/core/core-events'; @@ -76,96 +74,42 @@ export const init: ModuleFn = ({ store, fullAPI }) => { ); }, runTestProvider(id, options) { - console.log('LOG: runTestProvider', id, options); - if (!options?.entryId) { - console.log('LOG: runTestProvider: no entryId, running all tests'); - const payload: TestingModuleRunAllRequestPayload = { providerId: id }; - fullAPI.emit(TESTING_MODULE_RUN_ALL_REQUEST, payload); - return () => api.cancelTestProvider(id); - } - - const index = store.getState().index; + const { index } = store.getState(); if (!index) { - throw new Error('no index?'); + throw new Error('No story index available. This is likely a bug.'); } - const entry = index[options.entryId]; - - if (!entry) { - throw new Error('no entry?'); - } + const indexUrl = new URL('index.json', window.location.href).toString(); - if (entry.type === 'story') { - console.log('LOG: runTestProvider: running single story', entry); + if (!options?.entryId) { const payload: TestingModuleRunRequestPayload = { providerId: id, - payload: [ - { - importPath: entry.importPath, - stories: [ - { - id: entry.id, - name: entry.name, - }, - ], - }, - ], + indexUrl, }; fullAPI.emit(TESTING_MODULE_RUN_REQUEST, payload); return () => api.cancelTestProvider(id); } - const payloads = new Set(); - - const findComponents = (entryId: StoryId) => { - const foundEntry = index[entryId]; - console.log(`Processing entry: ${entryId}`, foundEntry); - switch (foundEntry.type) { - case 'component': - console.log(`Adding component entry: ${entryId}`); - const firstStoryId = foundEntry.children.find( - (childId) => index[childId].type === 'story' - ); - if (!firstStoryId) { - // happens when there are only docs in the component - return; - } - payloads.add({ importPath: (index[firstStoryId] as API_StoryEntry).importPath }); - return; - case 'story': { - // this shouldn't happen because we don't visit components' children. - // so we never get to a story directly. - // unless groups can have direct stories without components? - console.log(`Adding story entry: ${entryId}`); - payloads.add({ - importPath: foundEntry.importPath, - stories: [ - { - id: foundEntry.id, - name: foundEntry.name, - }, - ], - }); - return; - } - case 'docs': { - return; - } - default: - console.log(`Processing children of entry: ${entryId}`); - foundEntry.children.forEach(findComponents); + if (!index[options.entryId]) { + throw new Error('Could not find story entry for id: ' + options.entryId); + } + + const findStories = (entryId: StoryId, results: StoryId[] = []): StoryId[] => { + const node = index[entryId]; + if (node.type === 'story') { + results.push(node.id); + } else if ('children' in node) { + node.children.forEach((childId) => findStories(childId, results)); } + return results; }; - console.log(`Starting to find components for entryId:`, options.entryId); - findComponents(options.entryId); const payload: TestingModuleRunRequestPayload = { providerId: id, - payload: Array.from(payloads), + indexUrl, + storyIds: findStories(options.entryId), }; - console.log('LOG: payload', payload); fullAPI.emit(TESTING_MODULE_RUN_REQUEST, payload); - return () => api.cancelTestProvider(id); }, setTestProviderWatchMode(id, watchMode) { diff --git a/code/core/src/manager/components/sidebar/SidebarBottom.tsx b/code/core/src/manager/components/sidebar/SidebarBottom.tsx index 9eea4a4855b8..ccad267e2125 100644 --- a/code/core/src/manager/components/sidebar/SidebarBottom.tsx +++ b/code/core/src/manager/components/sidebar/SidebarBottom.tsx @@ -1,17 +1,12 @@ -import React, { useCallback, useEffect, useRef, useState } from 'react'; +import React, { useEffect, useRef, useState } from 'react'; import { styled } from '@storybook/core/theming'; -import { type API_FilterFunction, Addon_TypesEnum } from '@storybook/core/types'; +import { type API_FilterFunction } from '@storybook/core/types'; import { - TESTING_MODULE_CANCEL_TEST_RUN_REQUEST, TESTING_MODULE_CRASH_REPORT, TESTING_MODULE_PROGRESS_REPORT, - TESTING_MODULE_RUN_ALL_REQUEST, - TESTING_MODULE_WATCH_MODE_REQUEST, - type TestProviderId, type TestProviderState, - type TestProviders, type TestingModuleCrashReportPayload, type TestingModuleProgressReportPayload, } from '@storybook/core/core-events'; @@ -145,13 +140,6 @@ export const SidebarBottomBase = ({ }); }; - const clearState = ({ providerId }: { providerId: TestProviderId }) => { - api.clearTestProviderState(providerId); - api.experimental_updateStatus(providerId, (state = {}) => - Object.fromEntries(Object.keys(state).map((key) => [key, null])) - ); - }; - const onProgressReport = ({ providerId, ...result }: TestingModuleProgressReportPayload) => { if (result.status === 'failed') { api.updateTestProviderState(providerId, { ...result, running: false, failed: true }); @@ -168,13 +156,11 @@ export const SidebarBottomBase = ({ }; api.on(TESTING_MODULE_CRASH_REPORT, onCrashReport); - api.on(TESTING_MODULE_RUN_ALL_REQUEST, clearState); api.on(TESTING_MODULE_PROGRESS_REPORT, onProgressReport); return () => { api.off(TESTING_MODULE_CRASH_REPORT, onCrashReport); api.off(TESTING_MODULE_PROGRESS_REPORT, onProgressReport); - api.off(TESTING_MODULE_RUN_ALL_REQUEST, clearState); }; }, [api, testProviders]); diff --git a/code/core/src/manager/globals/exports.ts b/code/core/src/manager/globals/exports.ts index b9141e853f95..dc0b4b6139d9 100644 --- a/code/core/src/manager/globals/exports.ts +++ b/code/core/src/manager/globals/exports.ts @@ -811,7 +811,6 @@ export default { 'TESTING_MODULE_CANCEL_TEST_RUN_RESPONSE', 'TESTING_MODULE_CRASH_REPORT', 'TESTING_MODULE_PROGRESS_REPORT', - 'TESTING_MODULE_RUN_ALL_REQUEST', 'TESTING_MODULE_RUN_REQUEST', 'TESTING_MODULE_WATCH_MODE_REQUEST', 'TOGGLE_WHATS_NEW_NOTIFICATIONS', @@ -875,7 +874,6 @@ export default { 'TESTING_MODULE_CANCEL_TEST_RUN_RESPONSE', 'TESTING_MODULE_CRASH_REPORT', 'TESTING_MODULE_PROGRESS_REPORT', - 'TESTING_MODULE_RUN_ALL_REQUEST', 'TESTING_MODULE_RUN_REQUEST', 'TESTING_MODULE_WATCH_MODE_REQUEST', 'TOGGLE_WHATS_NEW_NOTIFICATIONS', @@ -939,7 +937,6 @@ export default { 'TESTING_MODULE_CANCEL_TEST_RUN_RESPONSE', 'TESTING_MODULE_CRASH_REPORT', 'TESTING_MODULE_PROGRESS_REPORT', - 'TESTING_MODULE_RUN_ALL_REQUEST', 'TESTING_MODULE_RUN_REQUEST', 'TESTING_MODULE_WATCH_MODE_REQUEST', 'TOGGLE_WHATS_NEW_NOTIFICATIONS', From 51a4c483ecd9765703d532a24668105a849c5ae8 Mon Sep 17 00:00:00 2001 From: Gert Hengeveld Date: Tue, 19 Nov 2024 12:00:18 +0100 Subject: [PATCH 02/13] Drop unused event --- code/addons/test/src/constants.ts | 1 - 1 file changed, 1 deletion(-) diff --git a/code/addons/test/src/constants.ts b/code/addons/test/src/constants.ts index 3801fae52d97..b1c1808db1a4 100644 --- a/code/addons/test/src/constants.ts +++ b/code/addons/test/src/constants.ts @@ -2,7 +2,6 @@ export const ADDON_ID = 'storybook/test'; export const TEST_PROVIDER_ID = `${ADDON_ID}/test-provider`; export const PANEL_ID = `${ADDON_ID}/panel`; export const STORYBOOK_ADDON_TEST_CHANNEL = 'STORYBOOK_ADDON_TEST_CHANNEL'; -export const RUN_TESTS_EVENT = `${ADDON_ID}/RUN_TESTS`; export const TUTORIAL_VIDEO_LINK = 'https://youtu.be/Waht9qq7AoA'; export const DOCUMENTATION_LINK = 'writing-tests/test-addon'; From 50b6b2234ba8ac55b565e3cf1ddcf602620d1a43 Mon Sep 17 00:00:00 2001 From: Gert Hengeveld Date: Tue, 19 Nov 2024 12:02:30 +0100 Subject: [PATCH 03/13] Add type --- code/addons/test/src/node/test-manager.test.ts | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/code/addons/test/src/node/test-manager.test.ts b/code/addons/test/src/node/test-manager.test.ts index 66915235a579..7f676fe819d0 100644 --- a/code/addons/test/src/node/test-manager.test.ts +++ b/code/addons/test/src/node/test-manager.test.ts @@ -2,6 +2,7 @@ import { describe, expect, it, vi } from 'vitest'; import { createVitest } from 'vitest/node'; import { Channel, type ChannelTransport } from '@storybook/core/channels'; +import type { StoryIndex } from '@storybook/types'; import path from 'path'; @@ -59,7 +60,7 @@ global.fetch = vi.fn().mockResolvedValue({ importPath: 'path/to/another/file', }, }, - }) + } as StoryIndex) ), }); From 9c473ef67866429c8eab30e79c665f357fe51f47 Mon Sep 17 00:00:00 2001 From: Gert Hengeveld Date: Tue, 19 Nov 2024 14:48:15 +0100 Subject: [PATCH 04/13] Extend tests --- code/addons/test/src/node/test-manager.test.ts | 14 +++++++++++++- 1 file changed, 13 insertions(+), 1 deletion(-) diff --git a/code/addons/test/src/node/test-manager.test.ts b/code/addons/test/src/node/test-manager.test.ts index 7f676fe819d0..983e1e746612 100644 --- a/code/addons/test/src/node/test-manager.test.ts +++ b/code/addons/test/src/node/test-manager.test.ts @@ -9,6 +9,7 @@ import path from 'path'; import { TEST_PROVIDER_ID } from '../constants'; import { TestManager } from './test-manager'; +const setTestNamePattern = vi.hoisted(() => vi.fn()); const vitest = vi.hoisted(() => ({ projects: [{}], init: vi.fn(), @@ -18,7 +19,16 @@ const vitest = vi.hoisted(() => ({ cancelCurrentRun: vi.fn(), globTestSpecs: vi.fn(), getModuleProjects: vi.fn(() => []), - configOverride: {}, + configOverride: { + actualTestNamePattern: undefined, + get testNamePattern() { + return this.actualTestNamePattern; + }, + set testNamePattern(value: string) { + setTestNamePattern(value); + this.actualTestNamePattern = value; + }, + }, })); vi.mock('vitest/node', () => ({ @@ -123,12 +133,14 @@ describe('TestManager', () => { storyIds: [], }); expect(vitest.runFiles).toHaveBeenCalledWith([], true); + expect(vitest.configOverride.testNamePattern).toBeUndefined(); await testManager.handleRunRequest({ providerId: TEST_PROVIDER_ID, indexUrl: 'http://localhost:6006/index.json', storyIds: ['story--one'], }); + expect(setTestNamePattern).toHaveBeenCalledWith(/^One$/); expect(vitest.runFiles).toHaveBeenCalledWith(tests.slice(0, 1), true); }); }); From a6d1c152e56ada43bbf9c7364e5774f6a6da694f Mon Sep 17 00:00:00 2001 From: Gert Hengeveld Date: Tue, 19 Nov 2024 16:18:59 +0100 Subject: [PATCH 05/13] Extract fetch request and add timeout + error handling --- code/addons/test/src/node/vitest-manager.ts | 18 +++++++++++++++--- 1 file changed, 15 insertions(+), 3 deletions(-) diff --git a/code/addons/test/src/node/vitest-manager.ts b/code/addons/test/src/node/vitest-manager.ts index df7eedce67e4..13290f9f098a 100644 --- a/code/addons/test/src/node/vitest-manager.ts +++ b/code/addons/test/src/node/vitest-manager.ts @@ -71,15 +71,27 @@ export class VitestManager { }); } + private async fetchStories(indexUrl: string, requestStoryIds?: string[]) { + try { + const index = (await Promise.race([ + fetch(indexUrl).then((res) => res.json()), + new Promise((_, reject) => setTimeout(reject, 3000, new Error('Request took too long'))), + ])) as StoryIndex; + const storyIds = requestStoryIds || Object.keys(index.entries); + return storyIds.map((id) => index.entries[id]); + } catch (e) { + log('Failed to fetch story index: ' + e.message); + return []; + } + } + async runTests(requestPayload: TestingModuleRunRequestPayload) { if (!this.vitest) { await this.startVitest(); } this.resetTestNamePattern(); - const index = (await fetch(requestPayload.indexUrl).then((res) => res.json())) as StoryIndex; - const { storyIds = Object.keys(index.entries) } = requestPayload; - const stories = storyIds.map((id) => index.entries[id]); + const stories = await this.fetchStories(requestPayload.indexUrl, requestPayload.storyIds); const vitestTestSpecs = await this.getStorybookTestSpecs(); const isSingleStoryRun = requestPayload.storyIds?.length === 1; From d15b6a39d5a6301ff237fe4dd0bd55d05ca4f193 Mon Sep 17 00:00:00 2001 From: Gert Hengeveld Date: Tue, 19 Nov 2024 23:11:13 +0100 Subject: [PATCH 06/13] Don't count skipped tests, nor docs --- code/addons/test/src/node/reporter.ts | 20 ++++++++------------ code/addons/test/src/node/vitest-manager.ts | 2 +- 2 files changed, 9 insertions(+), 13 deletions(-) diff --git a/code/addons/test/src/node/reporter.ts b/code/addons/test/src/node/reporter.ts index 191d53d69649..51d2dc010cff 100644 --- a/code/addons/test/src/node/reporter.ts +++ b/code/addons/test/src/node/reporter.ts @@ -75,7 +75,7 @@ export class StorybookReporter implements Reporter { getProgressReport(finishedAt?: number) { const files = this.ctx.state.getFiles(); - const fileTests = getTests(files); + const fileTests = getTests(files).filter((t) => t.mode === 'run' || t.mode === 'only'); // The total number of tests reported by Vitest is dynamic and can change during the run, so we // use `storyCountForCurrentRun` instead, based on the list of stories provided in the run request. @@ -85,12 +85,9 @@ export class StorybookReporter implements Reporter { const numFailedTests = fileTests.filter((t) => t.result?.state === 'fail').length; const numPassedTests = fileTests.filter((t) => t.result?.state === 'pass').length; - const numPendingTests = fileTests.filter( - (t) => t.result?.state === 'run' || t.mode === 'skip' || t.result?.state === 'skip' - ).length; - const testResults: TestResult[] = []; + const numPendingTests = fileTests.filter((t) => t.result?.state === 'run').length; - for (const file of files) { + const testResults: TestResult[] = files.map((file) => { const tests = getTests([file]); let startTime = tests.reduce( (prev, next) => Math.min(prev, next.result?.startTime ?? Number.POSITIVE_INFINITY), @@ -106,7 +103,7 @@ export class StorybookReporter implements Reporter { startTime ); - const assertionResults = tests.flatMap((t) => { + const results = tests.flatMap((t) => { const ancestorTitles: string[] = []; let iter: Suite | undefined = t.suite; while (iter) { @@ -133,15 +130,14 @@ export class StorybookReporter implements Reporter { }); const hasFailedTests = tests.some((t) => t.result?.state === 'fail'); - - testResults.push({ - results: assertionResults, + return { + results, startTime, endTime, status: file.result?.state === 'fail' || hasFailedTests ? 'failed' : 'passed', message: file.result?.errors?.[0]?.stack || file.result?.errors?.[0]?.message, - }); - } + }; + }); return { cancellable: !finishedAt, diff --git a/code/addons/test/src/node/vitest-manager.ts b/code/addons/test/src/node/vitest-manager.ts index 13290f9f098a..058ab9545696 100644 --- a/code/addons/test/src/node/vitest-manager.ts +++ b/code/addons/test/src/node/vitest-manager.ts @@ -78,7 +78,7 @@ export class VitestManager { new Promise((_, reject) => setTimeout(reject, 3000, new Error('Request took too long'))), ])) as StoryIndex; const storyIds = requestStoryIds || Object.keys(index.entries); - return storyIds.map((id) => index.entries[id]); + return storyIds.map((id) => index.entries[id]).filter((story) => story.type === 'story'); } catch (e) { log('Failed to fetch story index: ' + e.message); return []; From 2f6e0eaeb3d86a052750e4700faf05974d9210ef Mon Sep 17 00:00:00 2001 From: Gert Hengeveld Date: Wed, 20 Nov 2024 11:34:46 +0100 Subject: [PATCH 07/13] Respect story tags and Vitest config when filtering stories for testing --- code/addons/test/src/node/vitest-manager.ts | 45 +++++++++++++++---- code/addons/test/src/vitest-plugin/index.ts | 3 ++ .../csf-tools/vitest-plugin/transformer.ts | 15 ++++--- 3 files changed, 50 insertions(+), 13 deletions(-) diff --git a/code/addons/test/src/node/vitest-manager.ts b/code/addons/test/src/node/vitest-manager.ts index 058ab9545696..d0829acb7d67 100644 --- a/code/addons/test/src/node/vitest-manager.ts +++ b/code/addons/test/src/node/vitest-manager.ts @@ -6,7 +6,7 @@ import type { TestProject, TestSpecification, Vitest, WorkspaceProject } from 'v import type { Channel } from 'storybook/internal/channels'; import type { TestingModuleRunRequestPayload } from 'storybook/internal/core-events'; -import type { StoryIndex } from '@storybook/types'; +import type { StoryIndex, StoryIndexEntry } from '@storybook/types'; import slash from 'slash'; @@ -14,6 +14,12 @@ import { log } from '../logger'; import { StorybookReporter } from './reporter'; import type { TestManager } from './test-manager'; +type TagsFilter = { + include: string[]; + exclude: string[]; + skip: string[]; +}; + export class VitestManager { vitest: Vitest | null = null; @@ -85,6 +91,23 @@ export class VitestManager { } } + private filterStories(story: StoryIndexEntry, moduleId: string, tagsFilter: TagsFilter) { + const absoluteImportPath = path.join(process.cwd(), story.importPath); + if (absoluteImportPath !== moduleId) { + return false; + } + if (tagsFilter.include.length && !tagsFilter.include.some((tag) => story.tags?.includes(tag))) { + return false; + } + if (tagsFilter.exclude.some((tag) => story.tags?.includes(tag))) { + return false; + } + if (tagsFilter.skip.some((tag) => story.tags?.includes(tag))) { + return false; + } + return true; + } + async runTests(requestPayload: TestingModuleRunRequestPayload) { if (!this.vitest) { await this.startVitest(); @@ -96,17 +119,23 @@ export class VitestManager { const isSingleStoryRun = requestPayload.storyIds?.length === 1; const { filteredTestFiles, totalTestCount } = vitestTestSpecs.reduce( - (acc, vitestTestSpec) => { - const matches = stories.filter((story) => { - const absoluteImportPath = path.join(process.cwd(), story.importPath); - return absoluteImportPath === vitestTestSpec.moduleId; - }); + (acc, spec) => { + /* eslint-disable no-underscore-dangle */ + const { env = {} } = spec.project.config; + const include = env.__VITEST_INCLUDE_TAGS__?.split(',').filter(Boolean) ?? ['test']; + const exclude = env.__VITEST_EXCLUDE_TAGS__?.split(',').filter(Boolean) ?? []; + const skip = env.__VITEST_SKIP_TAGS__?.split(',').filter(Boolean) ?? []; + /* eslint-enable no-underscore-dangle */ + + const matches = stories.filter((story) => + this.filterStories(story, spec.moduleId, { include, exclude, skip }) + ); if (matches.length) { if (!this.testManager.watchMode) { // Clear the file cache if watch mode is not enabled - this.updateLastChanged(vitestTestSpec.moduleId); + this.updateLastChanged(spec.moduleId); } - acc.filteredTestFiles.push(vitestTestSpec); + acc.filteredTestFiles.push(spec); acc.totalTestCount += matches.length; } return acc; diff --git a/code/addons/test/src/vitest-plugin/index.ts b/code/addons/test/src/vitest-plugin/index.ts index 968b0e656141..2e1a21afe6b0 100644 --- a/code/addons/test/src/vitest-plugin/index.ts +++ b/code/addons/test/src/vitest-plugin/index.ts @@ -99,6 +99,9 @@ export const storybookTest = (options?: UserOptions): Plugin => { ...config.test.env, // To be accessed by the setup file __STORYBOOK_URL__: storybookUrl, + __VITEST_INCLUDE_TAGS__: finalOptions.tags.include.join(','), + __VITEST_EXCLUDE_TAGS__: finalOptions.tags.exclude.join(','), + __VITEST_SKIP_TAGS__: finalOptions.tags.skip.join(','), }; if (config.test.browser) { diff --git a/code/core/src/csf-tools/vitest-plugin/transformer.ts b/code/core/src/csf-tools/vitest-plugin/transformer.ts index 677235975cb1..8d0ee5b6347b 100644 --- a/code/core/src/csf-tools/vitest-plugin/transformer.ts +++ b/code/core/src/csf-tools/vitest-plugin/transformer.ts @@ -19,11 +19,16 @@ type TagsFilter = { }; const isValidTest = (storyTags: string[], tagsFilter: TagsFilter) => { - const isIncluded = - tagsFilter?.include.length === 0 || tagsFilter?.include.some((tag) => storyTags.includes(tag)); - const isNotExcluded = tagsFilter?.exclude.every((tag) => !storyTags.includes(tag)); - - return isIncluded && isNotExcluded; + if (tagsFilter.include.length && !tagsFilter.include.some((tag) => storyTags?.includes(tag))) { + return false; + } + if (tagsFilter.exclude.some((tag) => storyTags?.includes(tag))) { + return false; + } + if (tagsFilter.skip.some((tag) => storyTags?.includes(tag))) { + return false; + } + return true; }; /** * TODO: the functionality in this file can be moved back to the vitest plugin itself It can use From fa73dd0801b009ffb10f95ccc3e303dce6dcbe01 Mon Sep 17 00:00:00 2001 From: Gert Hengeveld Date: Fri, 22 Nov 2024 11:24:01 +0100 Subject: [PATCH 08/13] Only filter out skipped stories in the progress report, not when loading tests for Vitest --- code/addons/test/src/node/vitest-manager.ts | 9 +++++---- code/core/src/csf-tools/vitest-plugin/transformer.ts | 4 +--- 2 files changed, 6 insertions(+), 7 deletions(-) diff --git a/code/addons/test/src/node/vitest-manager.ts b/code/addons/test/src/node/vitest-manager.ts index a8891a5f8742..b14da16ecce7 100644 --- a/code/addons/test/src/node/vitest-manager.ts +++ b/code/addons/test/src/node/vitest-manager.ts @@ -106,9 +106,7 @@ export class VitestManager { if (tagsFilter.exclude.some((tag) => story.tags?.includes(tag))) { return false; } - if (tagsFilter.skip.some((tag) => story.tags?.includes(tag))) { - return false; - } + // Skipped tests are intentionally included here return true; } @@ -140,7 +138,10 @@ export class VitestManager { this.updateLastChanged(spec.moduleId); } acc.filteredTestFiles.push(spec); - acc.totalTestCount += matches.length; + acc.totalTestCount += matches.filter( + // Don't count skipped stories, because StorybookReporter doesn't include them either + (story) => !skip.some((tag) => story.tags?.includes(tag)) + ).length; } return acc; }, diff --git a/code/core/src/csf-tools/vitest-plugin/transformer.ts b/code/core/src/csf-tools/vitest-plugin/transformer.ts index 8d0ee5b6347b..c318faa1d29e 100644 --- a/code/core/src/csf-tools/vitest-plugin/transformer.ts +++ b/code/core/src/csf-tools/vitest-plugin/transformer.ts @@ -25,9 +25,7 @@ const isValidTest = (storyTags: string[], tagsFilter: TagsFilter) => { if (tagsFilter.exclude.some((tag) => storyTags?.includes(tag))) { return false; } - if (tagsFilter.skip.some((tag) => storyTags?.includes(tag))) { - return false; - } + // Skipped tests are intentionally included here return true; }; /** From a27d8fd803e77e24523d5d666a992df663671903 Mon Sep 17 00:00:00 2001 From: Gert Hengeveld Date: Fri, 22 Nov 2024 12:04:11 +0100 Subject: [PATCH 09/13] Add missing tags to mock data --- code/addons/test/src/node/test-manager.test.ts | 2 ++ 1 file changed, 2 insertions(+) diff --git a/code/addons/test/src/node/test-manager.test.ts b/code/addons/test/src/node/test-manager.test.ts index bf7f7e3a8091..b674c1d786f7 100644 --- a/code/addons/test/src/node/test-manager.test.ts +++ b/code/addons/test/src/node/test-manager.test.ts @@ -61,6 +61,7 @@ global.fetch = vi.fn().mockResolvedValue({ name: 'One', title: 'story/one', importPath: 'path/to/file', + tags: ['test'], }, 'another--one': { type: 'story', @@ -68,6 +69,7 @@ global.fetch = vi.fn().mockResolvedValue({ name: 'One', title: 'another/one', importPath: 'path/to/another/file', + tags: ['test'], }, }, } as StoryIndex) From 28e06febbfd78875a1b7b831917a4829b7b17976 Mon Sep 17 00:00:00 2001 From: Norbert de Langen Date: Fri, 22 Nov 2024 14:30:45 +0100 Subject: [PATCH 10/13] delete dependency injected components feature --- code/addons/test/src/components/ContextMenuItem.tsx | 5 ++--- code/addons/test/src/manager.tsx | 4 ++-- code/core/src/manager/components/sidebar/ContextMenu.tsx | 2 +- 3 files changed, 5 insertions(+), 6 deletions(-) diff --git a/code/addons/test/src/components/ContextMenuItem.tsx b/code/addons/test/src/components/ContextMenuItem.tsx index 5cc6b58297a1..8fa63f84819a 100644 --- a/code/addons/test/src/components/ContextMenuItem.tsx +++ b/code/addons/test/src/components/ContextMenuItem.tsx @@ -7,7 +7,7 @@ import React, { useState, } from 'react'; -import { Button, type ListItem } from 'storybook/internal/components'; +import { Button, ListItem } from 'storybook/internal/components'; import { useStorybookApi } from 'storybook/internal/manager-api'; import { useTheme } from 'storybook/internal/theming'; import { type API_HashEntry, type Addon_TestProviderState } from 'storybook/internal/types'; @@ -23,8 +23,7 @@ export const ContextMenuItem: FC<{ state: Addon_TestProviderState<{ testResults: TestResult[]; }>; - ListItem: typeof ListItem; -}> = ({ context, state, ListItem }) => { +}> = ({ context, state }) => { const api = useStorybookApi(); const [isDisabled, setDisabled] = useState(false); diff --git a/code/addons/test/src/manager.tsx b/code/addons/test/src/manager.tsx index bba18f8018cc..8f1888b30bf0 100644 --- a/code/addons/test/src/manager.tsx +++ b/code/addons/test/src/manager.tsx @@ -82,7 +82,7 @@ addons.register(ADDON_ID, (api) => { watchable: true, name: 'Component tests', - sidebarContextMenu: ({ context, state }, { ListItem }) => { + sidebarContextMenu: ({ context, state }) => { if (context.type === 'docs') { return null; } @@ -90,7 +90,7 @@ addons.register(ADDON_ID, (api) => { return null; } - return ; + return ; }, render: (state) => { diff --git a/code/core/src/manager/components/sidebar/ContextMenu.tsx b/code/core/src/manager/components/sidebar/ContextMenu.tsx index 76118ffc8711..867ed8da72b7 100644 --- a/code/core/src/manager/components/sidebar/ContextMenu.tsx +++ b/code/core/src/manager/components/sidebar/ContextMenu.tsx @@ -106,7 +106,7 @@ export function generateTestProviderLinks( if (!state) { return null; } - const content = state.sidebarContextMenu?.({ context, state }, ContextMenu); + const content = state.sidebarContextMenu?.({ context, state }); if (!content) { return null; From 9a2767f6bf093b5890b23e29dff56bd380b9afd6 Mon Sep 17 00:00:00 2001 From: Norbert de Langen Date: Fri, 22 Nov 2024 14:39:59 +0100 Subject: [PATCH 11/13] remove from type --- code/core/src/types/modules/addons.ts | 11 ++++------- 1 file changed, 4 insertions(+), 7 deletions(-) diff --git a/code/core/src/types/modules/addons.ts b/code/core/src/types/modules/addons.ts index d01563530bb7..1f9649cfd405 100644 --- a/code/core/src/types/modules/addons.ts +++ b/code/core/src/types/modules/addons.ts @@ -479,13 +479,10 @@ export interface Addon_TestProviderType< /** @deprecated Use render instead */ description?: (state: TestProviderConfig & Addon_TestProviderState
) => ReactNode; render?: (state: TestProviderConfig & Addon_TestProviderState
) => ReactNode; - sidebarContextMenu?: ( - options: { - context: API_HashEntry; - state: Addon_TestProviderState
; - }, - components: { ListItem: typeof ListItem } - ) => ReactNode; + sidebarContextMenu?: (options: { + context: API_HashEntry; + state: Addon_TestProviderState
; + }) => ReactNode; mapStatusUpdate?: ( state: Addon_TestProviderState
) => API_StatusUpdate | ((state: API_StatusState) => API_StatusUpdate); From 96bbf988ec684a9e3004e68e12c8fbe16fdba51a Mon Sep 17 00:00:00 2001 From: Norbert de Langen Date: Mon, 25 Nov 2024 10:50:37 +0100 Subject: [PATCH 12/13] make the check on CI reliable, and run earlier --- .circleci/config.yml | 38 ++++++++++++++++++++++---------------- 1 file changed, 22 insertions(+), 16 deletions(-) diff --git a/.circleci/config.yml b/.circleci/config.yml index d89b0e260576..550dc9e6b994 100644 --- a/.circleci/config.yml +++ b/.circleci/config.yml @@ -165,17 +165,15 @@ jobs: root: . paths: - code/node_modules + - code/addons - scripts/node_modules - code/bench - code/examples - - code/node_modules - - code/addons - code/frameworks - code/deprecated - code/lib - code/core - code/builders - - code/ui - code/renderers - code/presets - .verdaccio-cache @@ -269,16 +267,30 @@ jobs: steps: - git-shallow-clone/checkout_advanced: clone_options: "--depth 1 --verbose" - - attach_workspace: - at: . - nx/set-shas: main-branch-name: "next" workflow-name: << pipeline.parameters.workflow >> + - run: + name: install in scripts + command: | + cd scripts + yarn install + - run: + name: install in code + command: | + cd code + yarn install + - run: + name: Compile + command: | + yarn task --task compile --start-from=compile --no-link --debug - run: name: Check command: | - yarn task --task compile --start-from=auto --no-link --debug - yarn task --task check --start-from=auto --no-link --debug + yarn task --task check --start-from=check --no-link --debug + - run: + name: Ensure no changes pending + command: | git diff --exit-code - report-workflow-on-failure - cancel-workflow-on-failure @@ -807,9 +819,7 @@ workflows: - bench-packages: requires: - build - - check: - requires: - - build + - check - unit-tests: requires: - build @@ -885,9 +895,7 @@ workflows: - bench-packages: requires: - build - - check: - requires: - - build + - check - unit-tests: requires: - build @@ -964,9 +972,7 @@ workflows: - bench-packages: requires: - build - - check: - requires: - - build + - check - unit-tests: requires: - build From 23aa9b82f2b0982a6d51d9313fefc4cc91e68c82 Mon Sep 17 00:00:00 2001 From: Norbert de Langen Date: Mon, 25 Nov 2024 11:02:44 +0100 Subject: [PATCH 13/13] remove test for disabled button --- .../react/e2e-tests/component-testing.spec.ts | 2 -- 1 file changed, 2 deletions(-) diff --git a/test-storybooks/portable-stories-kitchen-sink/react/e2e-tests/component-testing.spec.ts b/test-storybooks/portable-stories-kitchen-sink/react/e2e-tests/component-testing.spec.ts index 1882070794ac..bb76651cc9be 100644 --- a/test-storybooks/portable-stories-kitchen-sink/react/e2e-tests/component-testing.spec.ts +++ b/test-storybooks/portable-stories-kitchen-sink/react/e2e-tests/component-testing.spec.ts @@ -136,8 +136,6 @@ test.describe("component testing", () => { await expect(watchModeButton).toBeEnabled(); await runTestsButton.click(); - - await expect(runTestsButton).toBeDisabled(); await expect(watchModeButton).toBeDisabled(); await expect(testingModuleDescription).toContainText('Testing');