From 512c1e61a0467c728ced86d1feccacfcbc6c76ff Mon Sep 17 00:00:00 2001 From: Kasper Peulen Date: Tue, 26 Aug 2025 09:35:28 +0200 Subject: [PATCH 1/6] Refactor to see stories as tests --- .../vitest/src/vitest-plugin/test-utils.ts | 17 +- code/core/src/csf/csf-factories.test.ts | 14 +- code/core/src/csf/csf-factories.ts | 47 +- .../modules/preview-web/PreviewWeb.test.ts | 518 +++++++++--------- .../modules/preview-web/render/StoryRender.ts | 63 +-- .../modules/store/csf/normalizeStory.test.ts | 4 - .../modules/store/csf/normalizeStory.ts | 5 +- .../modules/store/csf/portable-stories.ts | 12 +- .../modules/store/csf/prepareStory.ts | 1 - .../modules/store/csf/processCSFFile.ts | 27 +- code/core/src/types/modules/api-stories.ts | 6 - code/core/src/types/modules/composedStory.ts | 5 +- code/core/src/types/modules/indexer.ts | 9 +- code/core/src/types/modules/story.ts | 9 +- code/renderers/server/src/preset.ts | 1 - 15 files changed, 350 insertions(+), 388 deletions(-) diff --git a/code/addons/vitest/src/vitest-plugin/test-utils.ts b/code/addons/vitest/src/vitest-plugin/test-utils.ts index 27a417f33abf..0501548275f1 100644 --- a/code/addons/vitest/src/vitest-plugin/test-utils.ts +++ b/code/addons/vitest/src/vitest-plugin/test-utils.ts @@ -1,6 +1,6 @@ import { type RunnerTask, type TaskMeta, type TestContext } from 'vitest'; -import { type Meta, type Story, isStory, toTestId } from 'storybook/internal/csf'; +import { type Meta, type Story, getStoryChildren, isStory, toTestId } from 'storybook/internal/csf'; import type { ComponentAnnotations, ComposedStoryFn, Renderer } from 'storybook/internal/types'; import { server } from '@vitest/browser/context'; @@ -41,8 +41,12 @@ export const testStory = ( return async (context: TestContext & { story: ComposedStoryFn }) => { const annotations = getCsfFactoryAnnotations(story, meta); - const storyAnnotations = - isStory(story) && testName ? story.getAllTests()[testName].story.input : annotations.story; + const test = + isStory(story) && testName + ? getStoryChildren(story).find((child) => child.input.name === testName) + : undefined; + + const storyAnnotations = test ? test.input : annotations.story; const composedStory = composeStory( storyAnnotations, @@ -63,6 +67,7 @@ export const testStory = ( }; if (testName) { + // TODO: [test-syntax] isn't this done by the csf plugin somehow? _task.meta.storyId = toTestId(composedStory.id, testName); } else { _task.meta.storyId = composedStory.id; @@ -70,11 +75,7 @@ export const testStory = ( await setViewport(composedStory.parameters, composedStory.globals); - if (isStory(story) && testName) { - await composedStory.run(undefined, story.getAllTests()[testName].test); - } else { - await composedStory.run(undefined); - } + await composedStory.run(undefined); _task.meta.reports = composedStory.reporting.reports; }; diff --git a/code/core/src/csf/csf-factories.test.ts b/code/core/src/csf/csf-factories.test.ts index f6ee8446e4f7..cd25dc723c08 100644 --- a/code/core/src/csf/csf-factories.test.ts +++ b/code/core/src/csf/csf-factories.test.ts @@ -1,7 +1,7 @@ //* @vitest-environment happy-dom */ import { describe, expect, test, vi } from 'vitest'; -import { definePreview, definePreviewAddon } from './csf-factories'; +import { definePreview, definePreviewAddon, getStoryChildren } from './csf-factories'; interface Addon1Types { parameters: { foo?: { value: string } }; @@ -53,11 +53,11 @@ describe('test function', () => { // register test MyStory.test(testName, testFn); - const { story: storyTestAnnotations } = MyStory.getAllTests()[testName]; - expect(storyTestAnnotations.input.args).toEqual({ label: 'foo' }); + const test = getStoryChildren(MyStory).find(({ input }) => input.name === testName)!; + expect(test.input.args).toEqual({ label: 'foo' }); // execute test - await MyStory.run(undefined, testName); + await test.run(undefined, testName); expect(testFn).toHaveBeenCalled(); }); test('with overrides', async () => { @@ -67,11 +67,11 @@ describe('test function', () => { // register test MyStory.test(testName, { args: { label: 'bar' } }, testFn); - const { story: storyTestAnnotations } = MyStory.getAllTests()[testName]; - expect(storyTestAnnotations.input.args).toEqual({ label: 'bar' }); + const test = getStoryChildren(MyStory).find(({ input }) => input.name === testName)!; + expect(test.input.args).toEqual({ label: 'bar' }); // execute test - await MyStory.run(undefined, testName); + await test.run(); expect(testFn).toHaveBeenCalled(); }); }); diff --git a/code/core/src/csf/csf-factories.ts b/code/core/src/csf/csf-factories.ts index 1737408b1a9e..2aa22739d632 100644 --- a/code/core/src/csf/csf-factories.ts +++ b/code/core/src/csf/csf-factories.ts @@ -11,6 +11,8 @@ import type { TestFunction, } from 'storybook/internal/types'; +import { mountDestructured } from 'core/src/preview-api/modules/preview-web/render/mount-utils'; + import { combineParameters, composeConfigs, @@ -154,7 +156,6 @@ export interface Story< annotations: StoryAnnotations, fn: TestFunction ): void; - getAllTests(): Record; test: TestFunction }>; } export function isStory(input: unknown): input is Story { @@ -178,7 +179,7 @@ function defineStory< return composed; }; - const tests: Record; test: TestFunction }> = {}; + const __children: Story[] = []; return { _tag: 'Story', @@ -186,6 +187,7 @@ function defineStory< meta, // @ts-expect-error this is a private property used only once in renderers/react/src/preview __compose: compose, + __children, get composed() { const composed = compose(); const { args, argTypes, parameters, id, tags, globals, storyName: name } = composed; @@ -194,9 +196,8 @@ function defineStory< get play() { return input.play ?? meta.input?.play ?? (async () => {}); }, - async run(context, testName?: string) { - const composedRun = compose().run; - await composedRun(context, tests[testName!]?.test); + async run(context) { + await compose().run(context); }, test( name: string, @@ -206,10 +207,26 @@ function defineStory< const annotations = typeof overridesOrTestFn !== 'function' ? overridesOrTestFn : {}; const testFunction = typeof overridesOrTestFn !== 'function' ? testFn! : overridesOrTestFn; - tests[name] = { - story: this.extend({ ...annotations, tags: [...(annotations.tags ?? []), 'test-fn'] }), - test: testFunction, - }; + const play = + mountDestructured(this.play) || mountDestructured(testFn) + ? async ({ mount, context }: StoryContext) => { + await this.play?.(context); + await testFunction(context); + } + : async (context: StoryContext) => { + await this.play?.(context); + await testFunction(context); + }; + + const test = this.extend({ + ...annotations, + name, + tags: ['test-fn', '!autodocs', ...(annotations.tags ?? [])], + play, + }); + __children.push(test); + + return test as unknown as void; }, extend>(input: TInput) { return defineStory( @@ -241,8 +258,14 @@ function defineStory< this.meta ); }, - getAllTests() { - return tests; - }, }; } + +export function getStoryChildren( + story: Story +): Story[] { + if ('__children' in story) { + return story.__children as Story[]; + } + return []; +} diff --git a/code/core/src/preview-api/modules/preview-web/PreviewWeb.test.ts b/code/core/src/preview-api/modules/preview-web/PreviewWeb.test.ts index 3d11097b6618..056d94e7e040 100644 --- a/code/core/src/preview-api/modules/preview-web/PreviewWeb.test.ts +++ b/code/core/src/preview-api/modules/preview-web/PreviewWeb.test.ts @@ -3847,271 +3847,267 @@ describe('PreviewWeb', () => { expect(extracted).toBe(true); expect(await preview.extract()).toMatchInlineSnapshot(` - { - "component-one--a": { - "argTypes": { - "foo": { - "name": "foo", - "type": { - "name": "string", - }, - }, - "one": { - "mapping": { - "1": "mapped-1", - }, - "name": "one", - "type": { - "name": "string", - }, - }, - }, - "args": { - "foo": "a", - "one": 1, - }, - "component": undefined, - "componentId": "component-one", - "globals": { - "a": "b", - "backgrounds": { - "grid": false, - "value": undefined, - }, - "measureEnabled": false, - "outline": false, - "viewport": { - "isRotated": false, - "value": undefined, - }, - }, - "id": "component-one--a", - "initialArgs": { - "foo": "a", - "one": 1, - }, - "kind": "Component One", - "name": "A", - "parameters": { - "__isArgsStory": false, - "backgrounds": { - "disable": false, - "grid": { - "cellAmount": 5, - "cellSize": 20, - "opacity": 0.5, - }, - }, - "docs": { - "container": [MockFunction spy], - "page": [MockFunction spy], - "renderer": [Function], - }, - "fileName": "./src/ComponentOne.stories.js", - "throwPlayFunctionExceptions": false, - }, - "story": "A", - "storyGlobals": {}, - "subcomponents": undefined, - "tags": [ - "dev", - "test", - ], - "testFunction": undefined, - "testingLibraryRender": undefined, - "title": "Component One", - "usesMount": false, + { + "component-one--a": { + "argTypes": { + "foo": { + "name": "foo", + "type": { + "name": "string", }, - "component-one--b": { - "argTypes": { - "foo": { - "name": "foo", - "type": { - "name": "string", - }, - }, - "one": { - "mapping": { - "1": "mapped-1", - }, - "name": "one", - "type": { - "name": "string", - }, - }, - }, - "args": { - "foo": "b", - "one": 1, - }, - "component": undefined, - "componentId": "component-one", - "globals": { - "a": "b", - "backgrounds": { - "grid": false, - "value": undefined, - }, - "measureEnabled": false, - "outline": false, - "viewport": { - "isRotated": false, - "value": undefined, - }, - }, - "id": "component-one--b", - "initialArgs": { - "foo": "b", - "one": 1, - }, - "kind": "Component One", - "name": "B", - "parameters": { - "__isArgsStory": false, - "backgrounds": { - "disable": false, - "grid": { - "cellAmount": 5, - "cellSize": 20, - "opacity": 0.5, - }, - }, - "docs": { - "container": [MockFunction spy], - "page": [MockFunction spy], - "renderer": [Function], - }, - "fileName": "./src/ComponentOne.stories.js", - "throwPlayFunctionExceptions": false, - }, - "story": "B", - "storyGlobals": {}, - "subcomponents": undefined, - "tags": [ - "dev", - "test", - ], - "testFunction": undefined, - "testingLibraryRender": undefined, - "title": "Component One", - "usesMount": false, + }, + "one": { + "mapping": { + "1": "mapped-1", }, - "component-one--e": { - "argTypes": {}, - "args": {}, - "component": undefined, - "componentId": "component-one", - "globals": { - "a": "b", - "backgrounds": { - "grid": false, - "value": undefined, - }, - "measureEnabled": false, - "outline": false, - "viewport": { - "isRotated": false, - "value": undefined, - }, - }, - "id": "component-one--e", - "initialArgs": {}, - "kind": "Component One", - "name": "E", - "parameters": { - "__isArgsStory": false, - "backgrounds": { - "disable": false, - "grid": { - "cellAmount": 5, - "cellSize": 20, - "opacity": 0.5, - }, - }, - "docs": { - "page": [MockFunction spy], - "renderer": [Function], - }, - "fileName": "./src/ExtraComponentOne.stories.js", - "throwPlayFunctionExceptions": false, - }, - "playFunction": undefined, - "story": "E", - "storyGlobals": {}, - "subcomponents": undefined, - "tags": [ - "dev", - "test", - ], - "testFunction": undefined, - "testingLibraryRender": undefined, - "title": "Component One", - "usesMount": false, + "name": "one", + "type": { + "name": "string", }, - "component-two--c": { - "argTypes": { - "foo": { - "name": "foo", - "type": { - "name": "string", - }, - }, - }, - "args": { - "foo": "c", - }, - "component": undefined, - "componentId": "component-two", - "globals": { - "a": "b", - "backgrounds": { - "grid": false, - "value": undefined, - }, - "measureEnabled": false, - "outline": false, - "viewport": { - "isRotated": false, - "value": undefined, - }, - }, - "id": "component-two--c", - "initialArgs": { - "foo": "c", - }, - "kind": "Component Two", - "name": "C", - "parameters": { - "__isArgsStory": false, - "backgrounds": { - "disable": false, - "grid": { - "cellAmount": 5, - "cellSize": 20, - "opacity": 0.5, - }, - }, - "docs": { - "renderer": [Function], - }, - "fileName": "./src/ComponentTwo.stories.js", - "throwPlayFunctionExceptions": false, - }, - "playFunction": undefined, - "story": "C", - "storyGlobals": {}, - "subcomponents": undefined, - "tags": [ - "dev", - "test", - ], - "testFunction": undefined, - "testingLibraryRender": undefined, - "title": "Component Two", - "usesMount": false, + }, + }, + "args": { + "foo": "a", + "one": 1, + }, + "component": undefined, + "componentId": "component-one", + "globals": { + "a": "b", + "backgrounds": { + "grid": false, + "value": undefined, + }, + "measureEnabled": false, + "outline": false, + "viewport": { + "isRotated": false, + "value": undefined, + }, + }, + "id": "component-one--a", + "initialArgs": { + "foo": "a", + "one": 1, + }, + "kind": "Component One", + "name": "A", + "parameters": { + "__isArgsStory": false, + "backgrounds": { + "disable": false, + "grid": { + "cellAmount": 5, + "cellSize": 20, + "opacity": 0.5, }, - } - `); + }, + "docs": { + "container": [MockFunction spy], + "page": [MockFunction spy], + "renderer": [Function], + }, + "fileName": "./src/ComponentOne.stories.js", + "throwPlayFunctionExceptions": false, + }, + "story": "A", + "storyGlobals": {}, + "subcomponents": undefined, + "tags": [ + "dev", + "test", + ], + "testingLibraryRender": undefined, + "title": "Component One", + "usesMount": false, + }, + "component-one--b": { + "argTypes": { + "foo": { + "name": "foo", + "type": { + "name": "string", + }, + }, + "one": { + "mapping": { + "1": "mapped-1", + }, + "name": "one", + "type": { + "name": "string", + }, + }, + }, + "args": { + "foo": "b", + "one": 1, + }, + "component": undefined, + "componentId": "component-one", + "globals": { + "a": "b", + "backgrounds": { + "grid": false, + "value": undefined, + }, + "measureEnabled": false, + "outline": false, + "viewport": { + "isRotated": false, + "value": undefined, + }, + }, + "id": "component-one--b", + "initialArgs": { + "foo": "b", + "one": 1, + }, + "kind": "Component One", + "name": "B", + "parameters": { + "__isArgsStory": false, + "backgrounds": { + "disable": false, + "grid": { + "cellAmount": 5, + "cellSize": 20, + "opacity": 0.5, + }, + }, + "docs": { + "container": [MockFunction spy], + "page": [MockFunction spy], + "renderer": [Function], + }, + "fileName": "./src/ComponentOne.stories.js", + "throwPlayFunctionExceptions": false, + }, + "story": "B", + "storyGlobals": {}, + "subcomponents": undefined, + "tags": [ + "dev", + "test", + ], + "testingLibraryRender": undefined, + "title": "Component One", + "usesMount": false, + }, + "component-one--e": { + "argTypes": {}, + "args": {}, + "component": undefined, + "componentId": "component-one", + "globals": { + "a": "b", + "backgrounds": { + "grid": false, + "value": undefined, + }, + "measureEnabled": false, + "outline": false, + "viewport": { + "isRotated": false, + "value": undefined, + }, + }, + "id": "component-one--e", + "initialArgs": {}, + "kind": "Component One", + "name": "E", + "parameters": { + "__isArgsStory": false, + "backgrounds": { + "disable": false, + "grid": { + "cellAmount": 5, + "cellSize": 20, + "opacity": 0.5, + }, + }, + "docs": { + "page": [MockFunction spy], + "renderer": [Function], + }, + "fileName": "./src/ExtraComponentOne.stories.js", + "throwPlayFunctionExceptions": false, + }, + "playFunction": undefined, + "story": "E", + "storyGlobals": {}, + "subcomponents": undefined, + "tags": [ + "dev", + "test", + ], + "testingLibraryRender": undefined, + "title": "Component One", + "usesMount": false, + }, + "component-two--c": { + "argTypes": { + "foo": { + "name": "foo", + "type": { + "name": "string", + }, + }, + }, + "args": { + "foo": "c", + }, + "component": undefined, + "componentId": "component-two", + "globals": { + "a": "b", + "backgrounds": { + "grid": false, + "value": undefined, + }, + "measureEnabled": false, + "outline": false, + "viewport": { + "isRotated": false, + "value": undefined, + }, + }, + "id": "component-two--c", + "initialArgs": { + "foo": "c", + }, + "kind": "Component Two", + "name": "C", + "parameters": { + "__isArgsStory": false, + "backgrounds": { + "disable": false, + "grid": { + "cellAmount": 5, + "cellSize": 20, + "opacity": 0.5, + }, + }, + "docs": { + "renderer": [Function], + }, + "fileName": "./src/ComponentTwo.stories.js", + "throwPlayFunctionExceptions": false, + }, + "playFunction": undefined, + "story": "C", + "storyGlobals": {}, + "subcomponents": undefined, + "tags": [ + "dev", + "test", + ], + "testingLibraryRender": undefined, + "title": "Component Two", + "usesMount": false, + }, + } +`); }); }); }); diff --git a/code/core/src/preview-api/modules/preview-web/render/StoryRender.ts b/code/core/src/preview-api/modules/preview-web/render/StoryRender.ts index 1854895b9bbb..4e5bc7d89c4c 100644 --- a/code/core/src/preview-api/modules/preview-web/render/StoryRender.ts +++ b/code/core/src/preview-api/modules/preview-web/render/StoryRender.ts @@ -40,8 +40,6 @@ export type RenderPhase = | 'rendering' | 'playing' | 'played' - | 'testing' - | 'tested' | 'completing' | 'completed' | 'afterEach' @@ -113,8 +111,8 @@ export class StoryRender implements Render implements Render implements Render 0; - if ( - this.renderOptions.autoplay && - forceRemount && - (playFunction || testFunction) && - this.phase !== 'errored' - ) { + if (this.renderOptions.autoplay && forceRemount && playFunction && this.phase !== 'errored') { window?.addEventListener?.('error', onError); window?.addEventListener?.('unhandledrejection', onUnhandledRejection); this.disableKeyListeners = true; try { - if (playFunction) { - if (!isMountDestructured) { - context.mount = async () => { - throw new MountMustBeDestructuredError({ playFunction: playFunction.toString() }); - }; - await this.runPhase(abortSignal, 'playing', async () => playFunction(context)); - } else { - // when mount is used the playing phase will start later, right after mount is called in the play function - await playFunction(context); - } - - if (!mounted) { - throw new NoStoryMountedError(); - } - - if (shouldHandleUnhandledErrors) { - await this.runPhase(abortSignal, 'errored'); - return; - } - await this.runPhase(abortSignal, 'played'); + if (!isMountDestructured) { + context.mount = async () => { + throw new MountMustBeDestructuredError({ playFunction: playFunction.toString() }); + }; + await this.runPhase(abortSignal, 'playing', async () => playFunction(context)); + } else { + // when mount is used the playing phase will start later, right after mount is called in the play function + await playFunction(context); } - if (testFunction) { - await this.runPhase(abortSignal, 'testing', async () => testFunction(context)); - if (shouldHandleUnhandledErrors) { - await this.runPhase(abortSignal, 'errored'); - return; - } - await this.runPhase(abortSignal, 'tested'); + if (!mounted) { + throw new NoStoryMountedError(); + } + this.checkIfAborted(abortSignal); + + if (!ignoreUnhandledErrors && unhandledErrors.size > 0) { + await this.runPhase(abortSignal, 'errored'); + } else { + await this.runPhase(abortSignal, 'played'); } } catch (error) { // Remove the loading screen, even if there was an error before rendering @@ -383,7 +364,7 @@ export class StoryRender implements Render 0) { this.channel.emit( UNHANDLED_ERRORS_WHILE_PLAYING, Array.from(unhandledErrors).map(serializeError) @@ -416,11 +397,13 @@ export class StoryRender implements Render 0; + const hasSomeReportsFailed = context.reporting.reports.some( (report) => report.status === 'failed' ); - const hasStoryErrored = shouldHandleUnhandledErrors || hasSomeReportsFailed; + const hasStoryErrored = hasUnhandledErrors || hasSomeReportsFailed; await this.runPhase(abortSignal, 'finished', async () => this.channel.emit(STORY_FINISHED, { diff --git a/code/core/src/preview-api/modules/store/csf/normalizeStory.test.ts b/code/core/src/preview-api/modules/store/csf/normalizeStory.test.ts index 06b772ab6b74..513685d1a690 100644 --- a/code/core/src/preview-api/modules/store/csf/normalizeStory.test.ts +++ b/code/core/src/preview-api/modules/store/csf/normalizeStory.test.ts @@ -64,7 +64,6 @@ describe('normalizeStory', () => { "name": "Story Export", "parameters": {}, "tags": [], - "testFunction": undefined, "userStoryFn": [Function], } `); @@ -137,7 +136,6 @@ describe('normalizeStory', () => { "name": "Story Export", "parameters": {}, "tags": [], - "testFunction": undefined, } `); expect(normalized.moduleExport).toBe(storyObj); @@ -182,7 +180,6 @@ describe('normalizeStory', () => { "storyParam": "val", }, "tags": [], - "testFunction": undefined, } `); expect(moduleExport).toBe(storyObj); @@ -244,7 +241,6 @@ describe('normalizeStory', () => { "storyParam2": "legacy", }, "tags": [], - "testFunction": undefined, } `); expect(moduleExport).toBe(storyObj); diff --git a/code/core/src/preview-api/modules/store/csf/normalizeStory.ts b/code/core/src/preview-api/modules/store/csf/normalizeStory.ts index 91420cf47fbb..07ae62060292 100644 --- a/code/core/src/preview-api/modules/store/csf/normalizeStory.ts +++ b/code/core/src/preview-api/modules/store/csf/normalizeStory.ts @@ -24,7 +24,7 @@ See https://github.com/storybookjs/storybook/blob/next/MIGRATION.md#hoisted-csf- export function normalizeStory( key: StoryId, - storyAnnotations: StoryAnnotationsOrFn & { testFunction?: any }, + storyAnnotations: StoryAnnotationsOrFn, meta: NormalizedComponentAnnotations ): NormalizedStoryAnnotations { const storyObject = storyAnnotations; @@ -61,7 +61,7 @@ export function normalizeStory( ...normalizeArrays(storyObject.afterEach), ...normalizeArrays(story?.afterEach), ]; - const { render, play, tags = [], globals = {}, testFunction } = storyObject; + const { render, play, tags = [], globals = {} } = storyObject; const id = parameters.__id || toId(meta.id, exportName); return { @@ -77,7 +77,6 @@ export function normalizeStory( beforeEach, afterEach, globals, - testFunction, ...(render && { render }), ...(userStoryFn && { userStoryFn }), ...(play && { play }), diff --git a/code/core/src/preview-api/modules/store/csf/portable-stories.ts b/code/core/src/preview-api/modules/store/csf/portable-stories.ts index bb456486406c..3d5750e2a1c8 100644 --- a/code/core/src/preview-api/modules/store/csf/portable-stories.ts +++ b/code/core/src/preview-api/modules/store/csf/portable-stories.ts @@ -214,13 +214,10 @@ export function composeStory>>, - test?: (context: StoryContext) => void | Promise - ) => { + const run = (extraContext?: Partial>>) => { const context = initializeContext(); Object.assign(context, extraContext); - return runStory(story, context, test); + return runStory(story, context); }; const playFunction = story.playFunction ? play : undefined; @@ -379,8 +376,7 @@ export function createPlaywrightTest( // Will make a follow up PR for that async function runStory( story: PreparedStory, - context: StoryContext, - testFunction?: (context: StoryContext) => void | Promise + context: StoryContext ) { for (const callback of [...cleanups].reverse()) { await callback(); @@ -427,8 +423,6 @@ async function runStory( await playFunction(context); } - await testFunction?.(context); - let cleanUp: CleanupCallback | undefined; if (isTestEnvironment()) { cleanUp = pauseAnimations(); diff --git a/code/core/src/preview-api/modules/store/csf/prepareStory.ts b/code/core/src/preview-api/modules/store/csf/prepareStory.ts index bd3b3984336d..42d17914ad48 100644 --- a/code/core/src/preview-api/modules/store/csf/prepareStory.ts +++ b/code/core/src/preview-api/modules/store/csf/prepareStory.ts @@ -165,7 +165,6 @@ export function prepareStory( testingLibraryRender, renderToCanvas: projectAnnotations.renderToCanvas, usesMount, - testFunction: storyAnnotations.testFunction, }; } export function prepareMeta( diff --git a/code/core/src/preview-api/modules/store/csf/processCSFFile.ts b/code/core/src/preview-api/modules/store/csf/processCSFFile.ts index 591c123f4070..3f76329936d4 100644 --- a/code/core/src/preview-api/modules/store/csf/processCSFFile.ts +++ b/code/core/src/preview-api/modules/store/csf/processCSFFile.ts @@ -1,6 +1,6 @@ import { logger } from 'storybook/internal/client-logger'; import type { Story } from 'storybook/internal/csf'; -import { isExportStory, isStory, toTestId } from 'storybook/internal/csf'; +import { getStoryChildren, isExportStory, isStory, toTestId } from 'storybook/internal/csf'; import type { ComponentTitle, Parameters, Path, Renderer } from 'storybook/internal/types'; import type { CSFFile, @@ -68,22 +68,15 @@ export function processCSFFile( // if the story has tests, we need to add those to the csfFile - Object.entries(story.getAllTests()).forEach( - ([testName, { story: storyTest, test: testFunction }]) => { - const testId = toTestId(storyMeta.id, testName); - // eslint-disable-next-line @typescript-eslint/ban-ts-comment - // @ts-ignore We provide the __id parameter because we don't want normalizeStory to calculate the id - storyTest.input.parameters.__id = testId; - - csfFile.stories[testId] = { - ...normalizeStory( - testName, - { ...(storyTest.input as any), testFunction }, - meta - ), - }; - } - ); + getStoryChildren(story).forEach((story) => { + const testName = story.input.name!; + const testId = toTestId(storyMeta.id, testName); + // eslint-disable-next-line @typescript-eslint/ban-ts-comment + // @ts-ignore We provide the __id parameter because we don't want normalizeStory to calculate the id + storyTest.input.parameters.__id = testId; + + csfFile.stories[testId] = normalizeStory(testName, story.input as any, meta); + }); } }); diff --git a/code/core/src/types/modules/api-stories.ts b/code/core/src/types/modules/api-stories.ts index dff4b2e13994..9bbd797145b7 100644 --- a/code/core/src/types/modules/api-stories.ts +++ b/code/core/src/types/modules/api-stories.ts @@ -55,11 +55,6 @@ export interface API_StoryEntry extends API_BaseEntry { initialArgs?: Args; } -export interface API_TestEntry extends Omit { - type: 'test'; - parentId: StoryId; -} -// TODO: [test-syntax] enable TestEntry once we start working on UI for tests export type API_LeafEntry = API_DocsEntry | API_StoryEntry; // | API_TestEntry; export type API_HashEntry = | API_RootEntry @@ -67,7 +62,6 @@ export type API_HashEntry = | API_ComponentEntry | API_DocsEntry | API_StoryEntry; -// | API_TestEntry; /** * The `IndexHash` is our manager-side representation of the `StoryIndex`. We create entries in the diff --git a/code/core/src/types/modules/composedStory.ts b/code/core/src/types/modules/composedStory.ts index 9000155f8886..00cd9d4ff79f 100644 --- a/code/core/src/types/modules/composedStory.ts +++ b/code/core/src/types/modules/composedStory.ts @@ -44,10 +44,7 @@ export type ComposedStoryFn< args: TArgs; id: StoryId; play?: (context?: Partial>>) => Promise; - run: ( - context?: Partial>>, - test?: (context: StoryContext) => void | Promise - ) => Promise; + run: (context?: Partial>>) => Promise; load: () => Promise; storyName: string; parameters: Parameters; diff --git a/code/core/src/types/modules/indexer.ts b/code/core/src/types/modules/indexer.ts index ef27e564a5f6..b75be780832c 100644 --- a/code/core/src/types/modules/indexer.ts +++ b/code/core/src/types/modules/indexer.ts @@ -70,23 +70,18 @@ export interface BaseIndexEntry { title: ComponentTitle; tags?: Tag[]; importPath: Path; + parentId: StoryId; } export type StoryIndexEntry = BaseIndexEntry & { type: 'story'; }; -export type TestIndexEntry = BaseIndexEntry & { - type: 'test'; - parentId: StoryId; -}; - export type DocsIndexEntry = BaseIndexEntry & { storiesImports: Path[]; type: 'docs'; }; -// TODO: [test-syntax] add this once we start working on UI for tests -export type IndexEntry = StoryIndexEntry | DocsIndexEntry; //| TestIndexEntry; +export type IndexEntry = StoryIndexEntry | DocsIndexEntry; export interface IndexInputStats { loaders?: boolean; diff --git a/code/core/src/types/modules/story.ts b/code/core/src/types/modules/story.ts index 93ffc215e669..3b5d204dfc9b 100644 --- a/code/core/src/types/modules/story.ts +++ b/code/core/src/types/modules/story.ts @@ -18,13 +18,11 @@ import type { StoryAnnotations, StoryContext, StoryContextForEnhancers, - StoryFn, StoryId, StoryIdentifier, StoryName, StrictArgTypes, StrictGlobalTypes, - TestFunction, } from './csf'; // Store Types @@ -90,15 +88,11 @@ export type NormalizedStoryAnnotations = userStoryFn?: ArgsStoryFn; decorators?: DecoratorFunction[]; loaders?: LoaderFunction[]; - testFunction?: TestFunction; }; export type CSFFile = { meta: NormalizedComponentAnnotations; - stories: Record< - StoryId, - NormalizedStoryAnnotations & { testFunction?: TestFunction } - >; + stories: Record>; projectAnnotations?: NormalizedProjectAnnotations; moduleExports: ModuleExports; }; @@ -119,7 +113,6 @@ export type PreparedStory = renderToCanvas?: ProjectAnnotations['renderToCanvas']; usesMount: boolean; storyGlobals: Globals; - testFunction?: TestFunction; }; export type PreparedMeta = Omit< diff --git a/code/renderers/server/src/preset.ts b/code/renderers/server/src/preset.ts index ffceec83cb93..acda308d5967 100644 --- a/code/renderers/server/src/preset.ts +++ b/code/renderers/server/src/preset.ts @@ -30,7 +30,6 @@ export const experimental_indexers: PresetProperty<'experimental_indexers'> = ( name: story.name, title: content.title, tags, - // TODO: [test-syntax] Does this need change now with story tests? type: 'story', }; }); From 0358dcca470191e335b03d28a76c0dfeab785493 Mon Sep 17 00:00:00 2001 From: Kasper Peulen Date: Tue, 26 Aug 2025 09:46:18 +0200 Subject: [PATCH 2/6] Remove some todos --- code/core/src/core-server/utils/StoryIndexGenerator.ts | 4 ---- code/core/src/csf-tools/CsfFile.ts | 2 -- code/core/src/csf/csf-factories.ts | 4 ---- code/core/src/types/modules/indexer.ts | 10 ++-------- .../react/template/stories/test-fn.stories.tsx | 9 ++++++--- 5 files changed, 8 insertions(+), 21 deletions(-) diff --git a/code/core/src/core-server/utils/StoryIndexGenerator.ts b/code/core/src/core-server/utils/StoryIndexGenerator.ts index 610137d2531d..4de4b1cb73f9 100644 --- a/code/core/src/core-server/utils/StoryIndexGenerator.ts +++ b/code/core/src/core-server/utils/StoryIndexGenerator.ts @@ -19,7 +19,6 @@ import type { StoryIndexEntry, StorybookConfigRaw, Tag, - TestIndexEntry, } from 'storybook/internal/types'; import { findUp } from 'find-up'; @@ -35,9 +34,6 @@ import { IndexingError, MultipleIndexingError } from './IndexingError'; import { autoName } from './autoName'; import { type IndexStatsSummary, addStats } from './summarizeStats'; -// TODO: [test-syntax] replace line 42 with this once we start working on UI for tests -// type StoryIndexEntryWithExtra = (StoryIndexEntry | TestIndexEntry) & { - // Extended type to keep track of the csf meta id so we know the component id when referencing docs in `extractDocs` type StoryIndexEntryWithExtra = StoryIndexEntry & { extra: { metaId?: string; stats: IndexInputStats }; diff --git a/code/core/src/csf-tools/CsfFile.ts b/code/core/src/csf-tools/CsfFile.ts index fc0623e31a18..8772bbc87a93 100644 --- a/code/core/src/csf-tools/CsfFile.ts +++ b/code/core/src/csf-tools/CsfFile.ts @@ -922,8 +922,6 @@ export class CsfFile { index.push({ ...storyInput, type: 'story', - // TODO: [test-syntax] enable this once we start working on UI for tests - // type: 'test', // @ts-expect-error TODO: discuss this later parentId: story.id, parentName: story.name, diff --git a/code/core/src/csf/csf-factories.ts b/code/core/src/csf/csf-factories.ts index 2aa22739d632..bc9474dc6a2c 100644 --- a/code/core/src/csf/csf-factories.ts +++ b/code/core/src/csf/csf-factories.ts @@ -111,10 +111,6 @@ function defineMeta< _tag: 'Meta', input, preview, - get composed() { - // TODO: [test-syntax] Kasper check this later - return composeConfigs([preview.input, input]); - }, // @ts-expect-error hard story( story: StoryAnnotations | (() => TRenderer['storyResult']) = {} diff --git a/code/core/src/types/modules/indexer.ts b/code/core/src/types/modules/indexer.ts index b75be780832c..87c0293c97cb 100644 --- a/code/core/src/types/modules/indexer.ts +++ b/code/core/src/types/modules/indexer.ts @@ -70,7 +70,7 @@ export interface BaseIndexEntry { title: ComponentTitle; tags?: Tag[]; importPath: Path; - parentId: StoryId; + parentId?: StoryId; } export type StoryIndexEntry = BaseIndexEntry & { type: 'story'; @@ -134,11 +134,6 @@ export type StoryIndexInput = BaseIndexInput & { type: 'story'; }; -export type TestIndexInput = BaseIndexInput & { - type: 'test'; - parentId: StoryId; -}; - /** The input for indexing a docs entry. */ export type DocsIndexInput = BaseIndexInput & { type: 'docs'; @@ -146,8 +141,7 @@ export type DocsIndexInput = BaseIndexInput & { storiesImports?: Path[]; }; -// TODO: [test-syntax] add this once we start working on UI for tests -export type IndexInput = StoryIndexInput | DocsIndexInput; // | TestIndexInput; +export type IndexInput = StoryIndexInput | DocsIndexInput; export interface V3CompatIndexEntry extends Omit { kind: ComponentTitle; diff --git a/code/renderers/react/template/stories/test-fn.stories.tsx b/code/renderers/react/template/stories/test-fn.stories.tsx index 43b0254633d4..39468654d3db 100644 --- a/code/renderers/react/template/stories/test-fn.stories.tsx +++ b/code/renderers/react/template/stories/test-fn.stories.tsx @@ -1,6 +1,6 @@ import React from 'react'; -import type { TestFunction } from 'storybook/internal/types'; +import type { StoryContext } from '@storybook/react'; import { expect, fn } from 'storybook/test'; @@ -28,10 +28,13 @@ Default.test('simple', async ({ canvas, userEvent, args }) => { await expect(args.onClick).toHaveBeenCalled(); }); -const doTest: TestFunction = async ({ canvas, userEvent, args }) => { +const doTest = async ({ + canvas, + userEvent, + args, +}: StoryContext>) => { const button = canvas.getByText('Arg from story'); await userEvent.click(button); - // @ts-expect-error TODO: [test-syntax] Fix later with Kasper await expect(args.onClick).toHaveBeenCalled(); }; Default.test('referring to function in file', doTest); From 620663c8c85455d76f83db90349a05ab3221a81e Mon Sep 17 00:00:00 2001 From: Kasper Peulen Date: Tue, 26 Aug 2025 09:47:56 +0200 Subject: [PATCH 3/6] Rename --- .../preview-api/modules/store/csf/processCSFFile.ts | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/code/core/src/preview-api/modules/store/csf/processCSFFile.ts b/code/core/src/preview-api/modules/store/csf/processCSFFile.ts index 3f76329936d4..bc0d449174f0 100644 --- a/code/core/src/preview-api/modules/store/csf/processCSFFile.ts +++ b/code/core/src/preview-api/modules/store/csf/processCSFFile.ts @@ -68,14 +68,14 @@ export function processCSFFile( // if the story has tests, we need to add those to the csfFile - getStoryChildren(story).forEach((story) => { - const testName = story.input.name!; - const testId = toTestId(storyMeta.id, testName); + getStoryChildren(story).forEach((child) => { + const name = child.input.name!; + const childId = toTestId(storyMeta.id, name); // eslint-disable-next-line @typescript-eslint/ban-ts-comment // @ts-ignore We provide the __id parameter because we don't want normalizeStory to calculate the id - storyTest.input.parameters.__id = testId; + storyTest.input.parameters.__id = childId; - csfFile.stories[testId] = normalizeStory(testName, story.input as any, meta); + csfFile.stories[childId] = normalizeStory(name, child.input as any, meta); }); } }); From 91a0149e0ba37219da3b16125e05e95e4d94908a Mon Sep 17 00:00:00 2001 From: Kasper Peulen Date: Tue, 26 Aug 2025 09:54:45 +0200 Subject: [PATCH 4/6] Fix --- code/core/src/preview-api/modules/store/csf/processCSFFile.ts | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/code/core/src/preview-api/modules/store/csf/processCSFFile.ts b/code/core/src/preview-api/modules/store/csf/processCSFFile.ts index bc0d449174f0..c9b4dcec177a 100644 --- a/code/core/src/preview-api/modules/store/csf/processCSFFile.ts +++ b/code/core/src/preview-api/modules/store/csf/processCSFFile.ts @@ -71,9 +71,11 @@ export function processCSFFile( getStoryChildren(story).forEach((child) => { const name = child.input.name!; const childId = toTestId(storyMeta.id, name); + + child.input.parameters ??= {}; // eslint-disable-next-line @typescript-eslint/ban-ts-comment // @ts-ignore We provide the __id parameter because we don't want normalizeStory to calculate the id - storyTest.input.parameters.__id = childId; + child.input.parameters.__id = childId; csfFile.stories[childId] = normalizeStory(name, child.input as any, meta); }); From 68603a9e60e378c03a2ddc8d5ccebc3a96b2d919 Mon Sep 17 00:00:00 2001 From: Yann Braga Date: Tue, 26 Aug 2025 14:40:01 +0200 Subject: [PATCH 5/6] fix --- code/core/src/csf/csf-factories.ts | 6 +++--- code/core/src/preview-api/modules/store/StoryStore.test.ts | 4 ---- 2 files changed, 3 insertions(+), 7 deletions(-) diff --git a/code/core/src/csf/csf-factories.ts b/code/core/src/csf/csf-factories.ts index bc9474dc6a2c..c4dd5550df4a 100644 --- a/code/core/src/csf/csf-factories.ts +++ b/code/core/src/csf/csf-factories.ts @@ -198,14 +198,14 @@ function defineStory< test( name: string, overridesOrTestFn: StoryAnnotations | TestFunction, - testFn?: TestFunction + testFn?: TestFunction ): void { const annotations = typeof overridesOrTestFn !== 'function' ? overridesOrTestFn : {}; const testFunction = typeof overridesOrTestFn !== 'function' ? testFn! : overridesOrTestFn; const play = - mountDestructured(this.play) || mountDestructured(testFn) - ? async ({ mount, context }: StoryContext) => { + mountDestructured(this.play) || mountDestructured(testFunction) + ? async ({ context }: StoryContext) => { await this.play?.(context); await testFunction(context); } diff --git a/code/core/src/preview-api/modules/store/StoryStore.test.ts b/code/core/src/preview-api/modules/store/StoryStore.test.ts index 6eb405bb68db..e8c91ee2211d 100644 --- a/code/core/src/preview-api/modules/store/StoryStore.test.ts +++ b/code/core/src/preview-api/modules/store/StoryStore.test.ts @@ -342,7 +342,6 @@ describe('StoryStore', () => { "dev", "test", ], - "testFunction": undefined, "testingLibraryRender": undefined, "title": "Component One", "usesMount": false, @@ -535,7 +534,6 @@ describe('StoryStore', () => { "dev", "test", ], - "testFunction": undefined, "testingLibraryRender": undefined, "title": "Component One", "usesMount": false, @@ -601,7 +599,6 @@ describe('StoryStore', () => { "dev", "test", ], - "testFunction": undefined, "testingLibraryRender": undefined, "title": "Component One", "usesMount": false, @@ -667,7 +664,6 @@ describe('StoryStore', () => { "dev", "test", ], - "testFunction": undefined, "testingLibraryRender": undefined, "title": "Component Two", "usesMount": false, From a444184d19ef4c7ecef6ecd1685953f39b354f54 Mon Sep 17 00:00:00 2001 From: Yann Braga Date: Tue, 26 Aug 2025 17:17:06 +0200 Subject: [PATCH 6/6] fix tests and revamp test filtering --- .../vitest/src/node/test-manager.test.ts | 62 ++++++++++++++++++ code/addons/vitest/src/node/vitest-manager.ts | 63 ++++++++++++------- .../core-server/utils/StoryIndexGenerator.ts | 23 +------ code/core/src/csf-tools/CsfFile.ts | 9 +-- code/core/src/csf/csf-factories.ts | 3 +- code/core/src/types/modules/indexer.ts | 5 +- 6 files changed, 112 insertions(+), 53 deletions(-) diff --git a/code/addons/vitest/src/node/test-manager.test.ts b/code/addons/vitest/src/node/test-manager.test.ts index ed353b0a0312..ee959c42e96e 100644 --- a/code/addons/vitest/src/node/test-manager.test.ts +++ b/code/addons/vitest/src/node/test-manager.test.ts @@ -116,6 +116,23 @@ global.fetch = vi.fn().mockResolvedValue({ importPath: 'path/to/another/file', tags: ['test'], }, + 'parent--story': { + type: 'story', + id: 'parent--story', + name: 'Parent story', + title: 'parent/story', + importPath: 'path/to/parent/file', + tags: ['test'], + }, + 'parent--story:test': { + type: 'story', + id: 'parent--story:test', + name: 'Test name', + title: 'parent/story', + parent: 'parent--story', + importPath: 'path/to/parent/file', + tags: ['test', 'test-fn'], + }, }, } as StoryIndex) ), @@ -187,6 +204,51 @@ describe('TestManager', () => { expect(vitest.runTestSpecifications).toHaveBeenCalledWith(tests.slice(0, 1), true); }); + it('should trigger a single story render test', async () => { + vitest.globTestSpecifications.mockImplementation(() => tests); + const testManager = await TestManager.start(options); + + await testManager.handleTriggerRunEvent({ + type: 'TRIGGER_RUN', + payload: { + storyIds: ['another--one'], + triggeredBy: 'global', + }, + }); + // regex should be exact match of the story name + expect(setTestNamePattern).toHaveBeenCalledWith(/^One$/); + }); + + it('should trigger a single story test', async () => { + vitest.globTestSpecifications.mockImplementation(() => tests); + const testManager = await TestManager.start(options); + + await testManager.handleTriggerRunEvent({ + type: 'TRIGGER_RUN', + payload: { + storyIds: ['parent--story:test'], + triggeredBy: 'global', + }, + }); + // regex should be Parent Story Name + Test Name + expect(setTestNamePattern).toHaveBeenCalledWith(/^Parent story Test name$/); + }); + + it('should trigger all tests of a story', async () => { + vitest.globTestSpecifications.mockImplementation(() => tests); + const testManager = await TestManager.start(options); + + await testManager.handleTriggerRunEvent({ + type: 'TRIGGER_RUN', + payload: { + storyIds: ['parent--story'], + triggeredBy: 'global', + }, + }); + // regex should be parent story name with no spaces in between plus a space at the end + expect(setTestNamePattern).toHaveBeenCalledWith(/^Parentstory /); + }); + it('should restart Vitest before a test run if coverage is enabled', async () => { const testManager = await TestManager.start(options); expect(createVitest).toHaveBeenCalledTimes(1); diff --git a/code/addons/vitest/src/node/vitest-manager.ts b/code/addons/vitest/src/node/vitest-manager.ts index 4cf4bb6d6e30..7b73691a261b 100644 --- a/code/addons/vitest/src/node/vitest-manager.ts +++ b/code/addons/vitest/src/node/vitest-manager.ts @@ -169,7 +169,7 @@ export class VitestManager { }); } - private async fetchStories(requestStoryIds?: string[]) { + private async fetchStories(requestStoryIds?: string[]): Promise { const indexUrl = this.testManager.store.getState().indexUrl; if (!indexUrl) { throw new Error( @@ -265,41 +265,60 @@ export class VitestManager { await this.cancelCurrentRun(); const testSpecifications = await this.getStorybookTestSpecifications(); - const stories = await this.fetchStories(runPayload?.storyIds); + const allStories = await this.fetchStories(); + + const filteredStories = runPayload.storyIds + ? allStories.filter((story) => runPayload.storyIds?.includes(story.id)) + : allStories; const isSingleStoryRun = runPayload.storyIds?.length === 1; if (isSingleStoryRun) { - const selectedStory = stories[0]; + const selectedStory = filteredStories.find((story) => story.id === runPayload.storyIds?.[0]); + if (!selectedStory) { + throw new Error(`Story ${runPayload.storyIds?.[0]} not found`); + } + const storyName = selectedStory.name.replace(/[.*+?^${}()|[\]\\]/g, '\\$&'); - // Use case 1: Single story run on a story without tests - let regex: RegExp = new RegExp(`^${storyName}$`); + let regex: RegExp; - if (selectedStory.tags?.includes('test-fn')) { - // in this case the regex pattern should be the story parentName + story.name - // @ts-expect-error TODO: fix this - const parentName = selectedStory.parentName.replace(/[.*+?^${}()|[\]\\]/g, '\\$&'); - // Use case 2: Single story run on a specific story test - regex = new RegExp(`^${parentName} ${storyName}$`); - } else if (selectedStory.tags?.includes('has-tests')) { - // Use case 3: "Single" story run on a story with tests + const isParentStory = allStories.some((story) => selectedStory.id === story.parent); + const hasParentStory = allStories.some((story) => selectedStory.parent === story.id); + + if (isParentStory) { + // Use case 1: "Single" story run on a story with tests // -> run all tests of that story, as storyName is a describe block /** - * TODO: [test-syntax] discuss. The vitest transformation keeps the export name as is, e.g. - * "PrimaryButton", while the storybook sidebar changes the name to "Primary Button". That's - * why we need to remove spaces from the story name, to match the test name. If we were to - * also beautify the test name, doing a regex wouldn't be precise because there could be two - * describes, for instance: "Primary Button" and "Primary Button Mobile" and both would - * match. The fact that there are no spaces in the test name is what makes "PrimaryButton" - * and "PrimaryButtonMobile" worth well in the regex. + * The vitest transformation keeps the export name as is, e.g. "PrimaryButton", while the + * storybook sidebar changes the name to "Primary Button". That's why we need to remove + * spaces from the story name, to match the test name. If we were to also beautify the test + * name, doing a regex wouldn't be precise because there could be two describes, for + * instance: "Primary Button" and "Primary Button Mobile" and both would match. The fact + * that there are no spaces in the test name is what makes "PrimaryButton" and + * "PrimaryButtonMobile" work well in the regex. As it turns out, this limitation is also + * present in the Vitest VSCode extension and the issue would occur with normal vitest tests + * as well. */ - regex = new RegExp(`^${storyName.replace(/\s+/g, '')} `); + regex = new RegExp(`^${storyName.replace(/\s+/g, '')} `); // the extra space is intentional! + } else if (hasParentStory) { + // Use case 2: Single story run on a specific story test + // in this case the regex pattern should be the story parentName + story.name + const parentStory = allStories.find((story) => story.id === selectedStory.parent); + if (!parentStory) { + throw new Error(`Parent story not found for story ${selectedStory.id}`); + } + + const parentName = parentStory.name.replace(/[.*+?^${}()|[\]\\]/g, '\\$&'); + regex = new RegExp(`^${parentName} ${storyName}$`); + } else { + // Use case 3: Single story run on a story without tests + regex = new RegExp(`^${storyName}$`); } this.vitest!.setGlobalTestNamePattern(regex); } const { filteredTestSpecifications, filteredStoryIds } = this.filterTestSpecifications( testSpecifications, - stories + filteredStories ); this.testManager.store.setState((s) => ({ diff --git a/code/core/src/core-server/utils/StoryIndexGenerator.ts b/code/core/src/core-server/utils/StoryIndexGenerator.ts index 4de4b1cb73f9..9285a29527f9 100644 --- a/code/core/src/core-server/utils/StoryIndexGenerator.ts +++ b/code/core/src/core-server/utils/StoryIndexGenerator.ts @@ -439,7 +439,8 @@ export class StoryIndexGenerator { const id = input.__id ?? toId(input.metaId ?? title, storyNameFromExport(input.exportName)); const tags = combineTags(...projectTags, ...(input.tags ?? [])); - const commonMetadata = { + return { + type: 'story', id, extra: { metaId: input.metaId, @@ -450,25 +451,7 @@ export class StoryIndexGenerator { importPath, componentPath, tags, - }; - - // TODO: [test-syntax] Enable this once we start working on UI for tests - if (input.tags?.includes('has-tests')) { - // if (input.type === 'test') { - return { - // type: 'test', - type: 'story', - // @ts-expect-error TODO: discuss this - parentId: input.parentId, - // @ts-expect-error TODO: discuss this - parentName: input.parentName, - ...commonMetadata, - }; - } - - return { - type: 'story', - ...commonMetadata, + ...(input.type === 'story' && input.parent ? { parent: input.parent } : {}), }; }); diff --git a/code/core/src/csf-tools/CsfFile.ts b/code/core/src/csf-tools/CsfFile.ts index 8772bbc87a93..5f261ef925bb 100644 --- a/code/core/src/csf-tools/CsfFile.ts +++ b/code/core/src/csf-tools/CsfFile.ts @@ -906,11 +906,6 @@ export class CsfFile { __stats: story.__stats, }; - if (this._storyTests[exportName]) { - // TODO: discuss this later - storyInput.tags = [...(storyInput.tags || []), 'has-tests']; - } - index.push({ ...storyInput, type: 'story', @@ -922,9 +917,7 @@ export class CsfFile { index.push({ ...storyInput, type: 'story', - // @ts-expect-error TODO: discuss this later - parentId: story.id, - parentName: story.name, + parent: story.id, name: test.name, tags: [...storyInput.tags, 'test-fn'], __id: test.id, diff --git a/code/core/src/csf/csf-factories.ts b/code/core/src/csf/csf-factories.ts index c4dd5550df4a..22af4a85f64c 100644 --- a/code/core/src/csf/csf-factories.ts +++ b/code/core/src/csf/csf-factories.ts @@ -11,8 +11,6 @@ import type { TestFunction, } from 'storybook/internal/types'; -import { mountDestructured } from 'core/src/preview-api/modules/preview-web/render/mount-utils'; - import { combineParameters, composeConfigs, @@ -20,6 +18,7 @@ import { normalizeArrays, normalizeProjectAnnotations, } from '../preview-api/index'; +import { mountDestructured } from '../preview-api/modules/preview-web/render/mount-utils'; import { getCoreAnnotations } from './core-annotations'; export interface Preview { diff --git a/code/core/src/types/modules/indexer.ts b/code/core/src/types/modules/indexer.ts index 87c0293c97cb..62b4fb5c35b0 100644 --- a/code/core/src/types/modules/indexer.ts +++ b/code/core/src/types/modules/indexer.ts @@ -70,10 +70,11 @@ export interface BaseIndexEntry { title: ComponentTitle; tags?: Tag[]; importPath: Path; - parentId?: StoryId; } export type StoryIndexEntry = BaseIndexEntry & { type: 'story'; + // currently used by story tests + parent?: StoryId; }; export type DocsIndexEntry = BaseIndexEntry & { @@ -132,6 +133,8 @@ export type BaseIndexInput = { /** The input for indexing a story entry. */ export type StoryIndexInput = BaseIndexInput & { type: 'story'; + // currently used by story tests + parent?: StoryId; }; /** The input for indexing a docs entry. */