diff --git a/code/addons/test/package.json b/code/addons/test/package.json index 06ea13ddacb0..8e4db505d183 100644 --- a/code/addons/test/package.json +++ b/code/addons/test/package.json @@ -48,6 +48,11 @@ "import": "./dist/vitest-plugin/test-utils.mjs", "require": "./dist/vitest-plugin/test-utils.js" }, + "./internal/coverage-reporter": { + "types": "./dist/node/coverage-reporter.d.ts", + "import": "./dist/node/coverage-reporter.mjs", + "require": "./dist/node/coverage-reporter.js" + }, "./preview": { "types": "./dist/preview.d.ts", "import": "./dist/preview.mjs", @@ -87,7 +92,7 @@ }, "devDependencies": { "@devtools-ds/object-inspector": "^1.1.2", - "@storybook/icons": "^1.2.12", + "@types/istanbul-lib-report": "^3.0.3", "@types/node": "^22.0.0", "@types/semver": "^7", "@vitest/browser": "^2.1.3", @@ -98,6 +103,7 @@ "execa": "^8.0.1", "find-up": "^7.0.0", "formik": "^2.2.9", + "istanbul-lib-report": "^3.0.1", "pathe": "^1.1.2", "picocolors": "^1.1.0", "react": "^18.2.0", @@ -146,7 +152,8 @@ "./src/vitest-plugin/index.ts", "./src/vitest-plugin/global-setup.ts", "./src/postinstall.ts", - "./src/node/vitest.ts" + "./src/node/vitest.ts", + "./src/node/coverage-reporter.ts" ] }, "storybook": { diff --git a/code/addons/test/src/components/TestProviderRender.stories.tsx b/code/addons/test/src/components/TestProviderRender.stories.tsx index 13a9e9bd3f35..fbdb3d488c7c 100644 --- a/code/addons/test/src/components/TestProviderRender.stories.tsx +++ b/code/addons/test/src/components/TestProviderRender.stories.tsx @@ -124,29 +124,83 @@ export const Running: Story = { }, }; -export const EnableA11y: Story = { +export const Watching: Story = { + args: { + state: { + ...config, + ...baseState, + watching: true, + }, + }, +}; + +export const WithCoverageNegative: Story = { args: { state: { ...config, ...baseState, details: { testResults: [], + coverageSummary: { + percentage: 20, + status: 'negative', + }, }, config: { - a11y: true, - coverage: false, + a11y: false, + coverage: true, }, }, }, }; -export const EnableEditing: Story = { +export const WithCoverageWarning: Story = { args: { state: { ...config, ...baseState, + details: { + testResults: [], + coverageSummary: { + percentage: 50, + status: 'warning', + }, + }, config: { - a11y: true, + a11y: false, + coverage: true, + }, + }, + }, +}; + +export const WithCoveragePositive: Story = { + args: { + state: { + ...config, + ...baseState, + details: { + testResults: [], + coverageSummary: { + percentage: 80, + status: 'positive', + }, + }, + config: { + a11y: false, + coverage: true, + }, + }, + }, +}; + +export const Editing: Story = { + args: { + state: { + ...config, + ...baseState, + config: { + a11y: false, coverage: false, }, details: { @@ -161,3 +215,21 @@ export const EnableEditing: Story = { screen.getByLabelText(/Open settings/).click(); }, }; + +export const EditingAndWatching: Story = { + args: { + state: { + ...config, + ...baseState, + watching: true, + config: { + a11y: true, + coverage: true, // should be automatically disabled in the UI + }, + details: { + testResults: [], + }, + }, + }, + play: Editing.play, +}; diff --git a/code/addons/test/src/components/TestProviderRender.tsx b/code/addons/test/src/components/TestProviderRender.tsx index 588523f89ad9..ca6be687c88d 100644 --- a/code/addons/test/src/components/TestProviderRender.tsx +++ b/code/addons/test/src/components/TestProviderRender.tsx @@ -77,6 +77,7 @@ export const TestProviderRender: FC<{ const title = state.crashed || state.failed ? 'Local tests failed' : 'Run local tests'; const errorMessage = state.error?.message; + const coverageSummary = state.details?.coverageSummary; const [config, updateConfig] = useConfig( api, @@ -159,8 +160,8 @@ export const TestProviderRender: FC<{ right={ updateConfig({ coverage: !config.coverage })} /> } @@ -185,11 +186,27 @@ export const TestProviderRender: FC<{ title="Component tests" icon={} /> - } - right={`60%`} - /> + {coverageSummary ? ( + + } + right={`${coverageSummary.percentage}%`} + /> + ) : ( + } + /> + )} } diff --git a/code/addons/test/src/constants.ts b/code/addons/test/src/constants.ts index 838594e212a3..0453930e3758 100644 --- a/code/addons/test/src/constants.ts +++ b/code/addons/test/src/constants.ts @@ -10,6 +10,8 @@ export const DOCUMENTATION_LINK = 'writing-tests/test-addon'; export const DOCUMENTATION_DISCREPANCY_LINK = `${DOCUMENTATION_LINK}#what-happens-when-there-are-different-test-results-in-multiple-environments`; export const DOCUMENTATION_FATAL_ERROR_LINK = `${DOCUMENTATION_LINK}#what-happens-if-vitest-itself-has-an-error`; +export const COVERAGE_DIRECTORY = 'coverage'; + export interface Config { coverage: boolean; a11y: boolean; @@ -17,4 +19,8 @@ export interface Config { export type Details = { testResults: TestResult[]; + coverageSummary?: { + status: 'positive' | 'warning' | 'negative' | 'unknown'; + percentage: number; + }; }; diff --git a/code/addons/test/src/node/boot-test-runner.ts b/code/addons/test/src/node/boot-test-runner.ts index 0771604861d9..9cf4880ec35e 100644 --- a/code/addons/test/src/node/boot-test-runner.ts +++ b/code/addons/test/src/node/boot-test-runner.ts @@ -3,6 +3,7 @@ import { type ChildProcess } from 'node:child_process'; import type { Channel } from 'storybook/internal/channels'; import { TESTING_MODULE_CANCEL_TEST_RUN_REQUEST, + TESTING_MODULE_CONFIG_CHANGE, TESTING_MODULE_CRASH_REPORT, TESTING_MODULE_RUN_REQUEST, TESTING_MODULE_WATCH_MODE_REQUEST, @@ -43,11 +44,14 @@ const bootTestRunner = async (channel: Channel, initEvent?: string, initArgs?: a child?.send({ args, from: 'server', type: TESTING_MODULE_WATCH_MODE_REQUEST }); const forwardCancel = (...args: any[]) => child?.send({ args, from: 'server', type: TESTING_MODULE_CANCEL_TEST_RUN_REQUEST }); + const forwardConfigChange = (...args: any[]) => + child?.send({ args, from: 'server', type: TESTING_MODULE_CONFIG_CHANGE }); const killChild = () => { channel.off(TESTING_MODULE_RUN_REQUEST, forwardRun); channel.off(TESTING_MODULE_WATCH_MODE_REQUEST, forwardWatchMode); channel.off(TESTING_MODULE_CANCEL_TEST_RUN_REQUEST, forwardCancel); + channel.off(TESTING_MODULE_CONFIG_CHANGE, forwardConfigChange); child?.kill(); child = null; }; @@ -86,6 +90,7 @@ const bootTestRunner = async (channel: Channel, initEvent?: string, initArgs?: a channel.on(TESTING_MODULE_RUN_REQUEST, forwardRun); channel.on(TESTING_MODULE_WATCH_MODE_REQUEST, forwardWatchMode); channel.on(TESTING_MODULE_CANCEL_TEST_RUN_REQUEST, forwardCancel); + channel.on(TESTING_MODULE_CONFIG_CHANGE, forwardConfigChange); resolve(); } else if (result.type === 'error') { diff --git a/code/addons/test/src/node/coverage-reporter.ts b/code/addons/test/src/node/coverage-reporter.ts new file mode 100644 index 000000000000..452643cd9d60 --- /dev/null +++ b/code/addons/test/src/node/coverage-reporter.ts @@ -0,0 +1,53 @@ +import type { ResolvedCoverageOptions } from 'vitest/node'; + +import type { ReportNode, Visitor } from 'istanbul-lib-report'; +import { ReportBase } from 'istanbul-lib-report'; + +import { type Details, TEST_PROVIDER_ID } from '../constants'; +import type { TestManager } from './test-manager'; + +export type StorybookCoverageReporterOptions = { + testManager: TestManager; + coverageOptions: ResolvedCoverageOptions<'v8'>; +}; + +export default class StorybookCoverageReporter extends ReportBase implements Partial { + #testManager: StorybookCoverageReporterOptions['testManager']; + + #coverageOptions: StorybookCoverageReporterOptions['coverageOptions']; + + constructor(opts: StorybookCoverageReporterOptions) { + super(); + this.#testManager = opts.testManager; + this.#coverageOptions = opts.coverageOptions; + } + + onSummary(node: ReportNode) { + if (!node.isRoot()) { + return; + } + const coverageSummary = node.getCoverageSummary(false); + + const percentage = Math.round(coverageSummary.data.statements.pct); + + // Fallback to Vitest's default watermarks https://vitest.dev/config/#coverage-watermarks + const [lowWatermark = 50, highWatermark = 80] = + this.#coverageOptions.watermarks?.statements ?? []; + + const coverageDetails: Details['coverageSummary'] = { + percentage, + status: + percentage < lowWatermark + ? 'negative' + : percentage < highWatermark + ? 'warning' + : 'positive', + }; + this.#testManager.sendProgressReport({ + providerId: TEST_PROVIDER_ID, + details: { + coverageSummary: coverageDetails, + }, + }); + } +} diff --git a/code/addons/test/src/node/test-manager.ts b/code/addons/test/src/node/test-manager.ts index 624916772056..3660081de58c 100644 --- a/code/addons/test/src/node/test-manager.ts +++ b/code/addons/test/src/node/test-manager.ts @@ -12,7 +12,7 @@ import { type TestingModuleWatchModeRequestPayload, } from 'storybook/internal/core-events'; -import { TEST_PROVIDER_ID } from '../constants'; +import { type Config, TEST_PROVIDER_ID } from '../constants'; import { VitestManager } from './vitest-manager'; export class TestManager { @@ -20,6 +20,8 @@ export class TestManager { watchMode = false; + coverage = false; + constructor( private channel: Channel, private options: { @@ -27,7 +29,7 @@ export class TestManager { onReady?: () => void; } = {} ) { - this.vitestManager = new VitestManager(channel, this); + this.vitestManager = new VitestManager(this); this.channel.on(TESTING_MODULE_RUN_REQUEST, this.handleRunRequest.bind(this)); this.channel.on(TESTING_MODULE_CONFIG_CHANGE, this.handleConfigChange.bind(this)); @@ -37,21 +39,29 @@ export class TestManager { this.vitestManager.startVitest().then(() => options.onReady?.()); } - async restartVitest(watchMode = false) { + async restartVitest({ watchMode, coverage }: { watchMode: boolean; coverage: boolean }) { await this.vitestManager.vitest?.runningPromise; await this.vitestManager.closeVitest(); - await this.vitestManager.startVitest(watchMode); + await this.vitestManager.startVitest({ watchMode, coverage }); } - async handleConfigChange(payload: TestingModuleConfigChangePayload) { - // TODO do something with the config - const config = payload.config; + async handleConfigChange( + payload: TestingModuleConfigChangePayload<{ coverage: boolean; a11y: boolean }> + ) { + if (payload.providerId !== TEST_PROVIDER_ID) { + return; + } + if (this.coverage !== payload.config.coverage) { + try { + this.coverage = payload.config.coverage; + await this.restartVitest({ watchMode: this.watchMode, coverage: this.coverage }); + } catch (e) { + this.reportFatalError('Failed to change coverage mode', e); + } + } } async handleWatchModeRequest(payload: TestingModuleWatchModeRequestPayload) { - // TODO do something with the config - const config = payload.config; - try { if (payload.providerId !== TEST_PROVIDER_ID) { return; @@ -59,20 +69,45 @@ export class TestManager { if (this.watchMode !== payload.watchMode) { this.watchMode = payload.watchMode; - await this.restartVitest(this.watchMode); + await this.restartVitest({ watchMode: this.watchMode, coverage: false }); } } catch (e) { this.reportFatalError('Failed to change watch mode', e); } } - async handleRunRequest(payload: TestingModuleRunRequestPayload) { + async handleRunRequest(payload: TestingModuleRunRequestPayload) { try { if (payload.providerId !== TEST_PROVIDER_ID) { return; } + if (payload.config && this.coverage !== payload.config.coverage) { + this.coverage = payload.config.coverage; + } + + 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. + */ + await this.restartVitest({ + watchMode: allTestsRun ? false : this.watchMode, + coverage: allTestsRun, + }); + } await this.vitestManager.runTests(payload); + + if (this.coverage && !allTestsRun) { + // 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 }); + } } 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 b14da16ecce7..37e6e0588aa1 100644 --- a/code/addons/test/src/node/vitest-manager.ts +++ b/code/addons/test/src/node/vitest-manager.ts @@ -1,8 +1,15 @@ import { existsSync } from 'node:fs'; -import type { TestProject, TestSpecification, Vitest, WorkspaceProject } from 'vitest/node'; - -import type { Channel } from 'storybook/internal/channels'; +import type { + CoverageOptions, + ResolvedCoverageOptions, + TestProject, + TestSpecification, + Vitest, + WorkspaceProject, +} from 'vitest/node'; + +import { resolvePathInStorybookCache } from 'storybook/internal/common'; import type { TestingModuleRunRequestPayload } from 'storybook/internal/core-events'; import type { DocsIndexEntry, StoryIndex, StoryIndexEntry } from '@storybook/types'; @@ -10,7 +17,9 @@ import type { DocsIndexEntry, StoryIndex, StoryIndexEntry } from '@storybook/typ import path, { normalize } from 'pathe'; import slash from 'slash'; +import { COVERAGE_DIRECTORY, type Config } from '../constants'; import { log } from '../logger'; +import type { StorybookCoverageReporterOptions } from './coverage-reporter'; import { StorybookReporter } from './reporter'; import type { TestManager } from './test-manager'; @@ -27,14 +36,31 @@ export class VitestManager { storyCountForCurrentRun: number = 0; - constructor( - private channel: Channel, - private testManager: TestManager - ) {} + constructor(private testManager: TestManager) {} - async startVitest(watchMode = false) { + async startVitest({ watchMode = false, coverage = false } = {}) { const { createVitest } = await import('vitest/node'); + const storybookCoverageReporter: [string, StorybookCoverageReporterOptions] = [ + '@storybook/experimental-addon-test/internal/coverage-reporter', + { + testManager: this.testManager, + coverageOptions: this.vitest?.config?.coverage as ResolvedCoverageOptions<'v8'>, + }, + ]; + const coverageOptions = ( + coverage + ? { + enabled: true, + clean: false, + cleanOnRerun: !watchMode, + reportOnFailure: true, + reporter: [['html', {}], storybookCoverageReporter], + reportsDirectory: resolvePathInStorybookCache(COVERAGE_DIRECTORY), + } + : { enabled: false } + ) as CoverageOptions; + this.vitest = await createVitest('test', { watch: watchMode, passWithNoTests: false, @@ -45,15 +71,12 @@ export class VitestManager { // find a way to just show errors and warnings for example // Otherwise it might be hard for the user to discover Storybook related logs reporters: ['default', new StorybookReporter(this.testManager)], - // @ts-expect-error we just want to disable coverage, not specify a provider - coverage: { - enabled: false, - }, + coverage: coverageOptions, }); if (this.vitest) { this.vitest.onCancel(() => { - // TODO: handle cancelation + // TODO: handle cancellation }); } @@ -110,10 +133,11 @@ export class VitestManager { return true; } - async runTests(requestPayload: TestingModuleRunRequestPayload) { + async runTests(requestPayload: TestingModuleRunRequestPayload) { if (!this.vitest) { await this.startVitest(); } + this.resetTestNamePattern(); const stories = await this.fetchStories(requestPayload.indexUrl, requestPayload.storyIds); @@ -242,7 +266,7 @@ export class VitestManager { if (triggerAffectedTests.length) { await this.vitest.cancelCurrentRun('keyboard-input'); await this.vitest.runningPromise; - await this.vitest.runFiles(triggerAffectedTests, true); + await this.vitest.runFiles(triggerAffectedTests, false); } } diff --git a/code/addons/test/src/preset.ts b/code/addons/test/src/preset.ts index a1a6e1233faf..30b0e9da7b3d 100644 --- a/code/addons/test/src/preset.ts +++ b/code/addons/test/src/preset.ts @@ -1,19 +1,26 @@ import { readFileSync } from 'node:fs'; +import { mkdir } from 'node:fs/promises'; import type { Channel } from 'storybook/internal/channels'; -import { checkAddonOrder, getFrameworkName, serverRequire } from 'storybook/internal/common'; import { + checkAddonOrder, + getFrameworkName, + resolvePathInStorybookCache, + serverRequire, +} from 'storybook/internal/common'; +import { + TESTING_MODULE_CONFIG_CHANGE, TESTING_MODULE_RUN_REQUEST, TESTING_MODULE_WATCH_MODE_REQUEST, } from 'storybook/internal/core-events'; import { oneWayHash, telemetry } from 'storybook/internal/telemetry'; -import type { Options, PresetProperty, StoryId } from 'storybook/internal/types'; +import type { Options, PresetProperty, PresetPropertyFn, StoryId } from 'storybook/internal/types'; import { isAbsolute, join } from 'pathe'; import picocolors from 'picocolors'; import { dedent } from 'ts-dedent'; -import { STORYBOOK_ADDON_TEST_CHANNEL } from './constants'; +import { COVERAGE_DIRECTORY, STORYBOOK_ADDON_TEST_CHANNEL, TEST_PROVIDER_ID } from './constants'; import { log } from './logger'; import { runTestRunner } from './node/boot-test-runner'; @@ -64,7 +71,9 @@ export const experimental_serverChannel = async (channel: Channel, options: Opti const execute = (eventName: string) => (...args: any[]) => { - runTestRunner(channel, eventName, args); + if (args[0]?.providerId === TEST_PROVIDER_ID) { + runTestRunner(channel, eventName, args); + } }; channel.on(TESTING_MODULE_RUN_REQUEST, execute(TESTING_MODULE_RUN_REQUEST)); @@ -73,6 +82,7 @@ export const experimental_serverChannel = async (channel: Channel, options: Opti execute(TESTING_MODULE_WATCH_MODE_REQUEST)(payload); } }); + channel.on(TESTING_MODULE_CONFIG_CHANGE, execute(TESTING_MODULE_CONFIG_CHANGE)); if (!core.disableTelemetry) { const packageJsonPath = require.resolve('@storybook/experimental-addon-test/package.json'); @@ -126,3 +136,19 @@ export const managerEntries: PresetProperty<'managerEntries'> = async (entry = [ // for whatever reason seems like the return type of managerEntries is not correct (it expects never instead of string[]) return entry as never; }; + +export const staticDirs: PresetPropertyFn<'staticDirs'> = async (values = [], options) => { + if (options.configType === 'PRODUCTION') { + return values; + } + + const coverageDirectory = resolvePathInStorybookCache(COVERAGE_DIRECTORY); + await mkdir(coverageDirectory, { recursive: true }); + return [ + { + from: coverageDirectory, + to: '/coverage', + }, + ...values, + ]; +}; diff --git a/code/core/src/core-events/data/testing-module.ts b/code/core/src/core-events/data/testing-module.ts index 80edea66aa64..ad843450723b 100644 --- a/code/core/src/core-events/data/testing-module.ts +++ b/code/core/src/core-events/data/testing-module.ts @@ -11,12 +11,14 @@ export type TestProviderState< export type TestProviders = Record; -export type TestingModuleRunRequestPayload = { +export type TestingModuleRunRequestPayload< + Config extends { [key: string]: any } = NonNullable, +> = { 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'] - config?: TestProviderState['config']; + config?: Config; }; export type TestingModuleProgressReportPayload = @@ -37,6 +39,10 @@ export type TestingModuleProgressReportPayload = message: string; stack?: string; }; + } + | { + providerId: TestProviderId; + details: { [key: string]: any }; }; export type TestingModuleCrashReportPayload = { @@ -71,13 +77,17 @@ export type TestingModuleCancelTestRunResponsePayload = message: string; }; -export type TestingModuleWatchModeRequestPayload = { +export type TestingModuleWatchModeRequestPayload< + Config extends { [key: string]: any } = NonNullable, +> = { providerId: TestProviderId; watchMode: boolean; - config?: TestProviderState['config']; + config?: Config; }; -export type TestingModuleConfigChangePayload = { +export type TestingModuleConfigChangePayload< + Config extends { [key: string]: any } = NonNullable, +> = { providerId: TestProviderId; - config: TestProviderState['config']; + config: Config; }; diff --git a/code/core/src/core-server/presets/common-preset.ts b/code/core/src/core-server/presets/common-preset.ts index 7bd893d0daf1..fe5f7db4f53a 100644 --- a/code/core/src/core-server/presets/common-preset.ts +++ b/code/core/src/core-server/presets/common-preset.ts @@ -303,26 +303,27 @@ export const experimental_serverChannel = async ( channel.on( TESTING_MODULE_PROGRESS_REPORT, async (payload: TestingModuleProgressReportPayload) => { - if ( - (payload.status === 'success' || payload.status === 'cancelled') && - payload.progress?.finishedAt - ) { + const status = 'status' in payload ? payload.status : undefined; + const progress = 'progress' in payload ? payload.progress : undefined; + const error = 'error' in payload ? payload.error : undefined; + + if ((status === 'success' || status === 'cancelled') && progress?.finishedAt) { await telemetry('testing-module-completed-report', { provider: payload.providerId, - duration: payload.progress.finishedAt - payload.progress.startedAt, - numTotalTests: payload.progress.numTotalTests, - numFailedTests: payload.progress.numFailedTests, - numPassedTests: payload.progress.numPassedTests, - status: payload.status, + duration: progress?.finishedAt - progress?.startedAt, + numTotalTests: progress?.numTotalTests, + numFailedTests: progress?.numFailedTests, + numPassedTests: progress?.numPassedTests, + status, }); } - if (payload.status === 'failed') { + if (status === 'failed') { await telemetry('testing-module-completed-report', { provider: payload.providerId, status: 'failed', ...(options.enableCrashReports && { - error: sanitizeError(payload.error), + error: error && sanitizeError(error), }), }); } diff --git a/code/core/src/manager-api/modules/experimental_testmodule.ts b/code/core/src/manager-api/modules/experimental_testmodule.ts index 289ffe51c81d..6b33f04c2e4f 100644 --- a/code/core/src/manager-api/modules/experimental_testmodule.ts +++ b/code/core/src/manager-api/modules/experimental_testmodule.ts @@ -57,7 +57,19 @@ export const init: ModuleFn = ({ store, fullAPI }) => { updateTestProviderState(id, update) { return store.setState( ({ testProviders }) => { - return { testProviders: { ...testProviders, [id]: { ...testProviders[id], ...update } } }; + return { + testProviders: { + ...testProviders, + [id]: { + ...testProviders[id], + ...update, + details: { + ...(testProviders[id].details || {}), + ...(update.details || {}), + }, + }, + }, + }; }, { persistence: 'session' } ); diff --git a/code/core/src/manager/components/sidebar/SidebarBottom.tsx b/code/core/src/manager/components/sidebar/SidebarBottom.tsx index ba64b4e500a5..e39f7595188d 100644 --- a/code/core/src/manager/components/sidebar/SidebarBottom.tsx +++ b/code/core/src/manager/components/sidebar/SidebarBottom.tsx @@ -130,10 +130,12 @@ export const SidebarBottomBase = ({ }; const onProgressReport = ({ providerId, ...result }: TestingModuleProgressReportPayload) => { - if (result.status === 'failed') { + const statusResult = 'status' in result ? result.status : undefined; + + if (statusResult === 'failed') { api.updateTestProviderState(providerId, { ...result, running: false, failed: true }); } else { - const update = { ...result, running: result.status === 'pending' }; + const update = { ...result, running: statusResult === 'pending' }; api.updateTestProviderState(providerId, update); const { mapStatusUpdate, ...state } = testProviders[providerId]; diff --git a/code/yarn.lock b/code/yarn.lock index 6623d2309c32..684f8f1118e2 100644 --- a/code/yarn.lock +++ b/code/yarn.lock @@ -6600,6 +6600,7 @@ __metadata: "@storybook/instrumenter": "workspace:*" "@storybook/test": "workspace:*" "@storybook/theming": "workspace:*" + "@types/istanbul-lib-report": "npm:^3.0.3" "@types/node": "npm:^22.0.0" "@types/semver": "npm:^7" "@vitest/browser": "npm:^2.1.3" @@ -6610,6 +6611,7 @@ __metadata: execa: "npm:^8.0.1" find-up: "npm:^7.0.0" formik: "npm:^2.2.9" + istanbul-lib-report: "npm:^3.0.1" pathe: "npm:^1.1.2" picocolors: "npm:^1.1.0" polished: "npm:^4.2.2" @@ -8358,6 +8360,13 @@ __metadata: languageName: node linkType: hard +"@types/istanbul-lib-coverage@npm:*": + version: 2.0.6 + resolution: "@types/istanbul-lib-coverage@npm:2.0.6" + checksum: 10c0/3948088654f3eeb45363f1db158354fb013b362dba2a5c2c18c559484d5eb9f6fd85b23d66c0a7c2fcfab7308d0a585b14dadaca6cc8bf89ebfdc7f8f5102fb7 + languageName: node + linkType: hard + "@types/istanbul-lib-coverage@npm:^2.0.1": version: 2.0.4 resolution: "@types/istanbul-lib-coverage@npm:2.0.4" @@ -8365,6 +8374,15 @@ __metadata: languageName: node linkType: hard +"@types/istanbul-lib-report@npm:^3.0.3": + version: 3.0.3 + resolution: "@types/istanbul-lib-report@npm:3.0.3" + dependencies: + "@types/istanbul-lib-coverage": "npm:*" + checksum: 10c0/247e477bbc1a77248f3c6de5dadaae85ff86ac2d76c5fc6ab1776f54512a745ff2a5f791d22b942e3990ddbd40f3ef5289317c4fca5741bedfaa4f01df89051c + languageName: node + linkType: hard + "@types/js-yaml@npm:^4.0.5": version: 4.0.9 resolution: "@types/js-yaml@npm:4.0.9"