diff --git a/code/addons/vitest/src/vitest-plugin/test-utils.ts b/code/addons/vitest/src/vitest-plugin/test-utils.ts index 4d811de01228..ec39bbc0734f 100644 --- a/code/addons/vitest/src/vitest-plugin/test-utils.ts +++ b/code/addons/vitest/src/vitest-plugin/test-utils.ts @@ -31,14 +31,25 @@ export const convertToFilePath = (url: string): string => { return normalizedPath.replace(/%20/g, ' '); }; -export const testStory = ( - exportName: string, - story: ComposedStoryFn | Story, - meta: ComponentAnnotations | Meta, - skipTags: string[], - storyId: string, - testName?: string -) => { +export const testStory = ({ + exportName, + story, + meta, + skipTags, + storyId, + componentPath, + testName, + componentName, +}: { + exportName: string; + story: ComposedStoryFn | Story; + meta: ComponentAnnotations | Meta; + skipTags: string[]; + storyId: string; + componentPath?: string; + testName?: string; + componentName?: string; +}) => { return async (context: TestContext & { story: ComposedStoryFn }) => { const annotations = getCsfFactoryAnnotations(story, meta); @@ -64,12 +75,19 @@ export const testStory = ( context.story = composedStory; const _task = context.task as RunnerTask & { - meta: TaskMeta & { storyId: string; reports: Report[] }; + meta: TaskMeta & { + storyId: string; + reports: Report[]; + componentPath?: string; + componentName?: string; + }; }; // The id will always be present, calculated by CsfFile // and is needed so that we can add the test to the story in Storybook's UI for the status _task.meta.storyId = storyId; + _task.meta.componentPath = componentPath; + _task.meta.componentName = componentName; await setViewport(composedStory.parameters, composedStory.globals); diff --git a/code/core/src/csf-tools/vitest-plugin/transformer.test.ts b/code/core/src/csf-tools/vitest-plugin/transformer.test.ts index 840d16f57499..732304cb56a6 100644 --- a/code/core/src/csf-tools/vitest-plugin/transformer.test.ts +++ b/code/core/src/csf-tools/vitest-plugin/transformer.test.ts @@ -73,7 +73,13 @@ describe('transformer', () => { export const Story = {}; const _isRunningFromThisFile = convertToFilePath(import.meta.url).includes(globalThis.__vitest_worker__.filepath ?? _expect.getState().testPath); if (_isRunningFromThisFile) { - _test("Story", _testStory("Story", Story, _meta, [], "automatic-calculated-title--story")); + _test("Story", _testStory({ + exportName: "Story", + story: Story, + meta: _meta, + skipTags: [], + storyId: "automatic-calculated-title--story" + })); } `); }); @@ -102,7 +108,13 @@ describe('transformer', () => { export const Story = {}; const _isRunningFromThisFile = convertToFilePath(import.meta.url).includes(globalThis.__vitest_worker__.filepath ?? _expect.getState().testPath); if (_isRunningFromThisFile) { - _test("Story", _testStory("Story", Story, _meta, [], "automatic-calculated-title--story")); + _test("Story", _testStory({ + exportName: "Story", + story: Story, + meta: _meta, + skipTags: [], + storyId: "automatic-calculated-title--story" + })); } `); }); @@ -132,7 +144,13 @@ describe('transformer', () => { export const Story = {}; const _isRunningFromThisFile = convertToFilePath(import.meta.url).includes(globalThis.__vitest_worker__.filepath ?? _expect.getState().testPath); if (_isRunningFromThisFile) { - _test("Story", _testStory("Story", Story, meta, [], "automatic-calculated-title--story")); + _test("Story", _testStory({ + exportName: "Story", + story: Story, + meta: meta, + skipTags: [], + storyId: "automatic-calculated-title--story" + })); } `); }); @@ -163,7 +181,13 @@ describe('transformer', () => { export const Story = {}; const _isRunningFromThisFile = convertToFilePath(import.meta.url).includes(globalThis.__vitest_worker__.filepath ?? _expect.getState().testPath); if (_isRunningFromThisFile) { - _test("Story", _testStory("Story", Story, meta, [], "automatic-calculated-title--story")); + _test("Story", _testStory({ + exportName: "Story", + story: Story, + meta: meta, + skipTags: [], + storyId: "automatic-calculated-title--story" + })); } `); }); @@ -199,7 +223,13 @@ describe('transformer', () => { }; const _isRunningFromThisFile = convertToFilePath(import.meta.url).includes(globalThis.__vitest_worker__.filepath ?? _expect.getState().testPath); if (_isRunningFromThisFile) { - _test("Primary", _testStory("Primary", Primary, _meta, [], "automatic-calculated-title--primary")); + _test("Primary", _testStory({ + exportName: "Primary", + story: Primary, + meta: _meta, + skipTags: [], + storyId: "automatic-calculated-title--primary" + })); } `); }); @@ -224,7 +254,13 @@ describe('transformer', () => { }; const _isRunningFromThisFile = convertToFilePath(import.meta.url).includes(globalThis.__vitest_worker__.filepath ?? _expect.getState().testPath); if (_isRunningFromThisFile) { - _test("custom name", _testStory("Primary", Primary, _meta, [], "automatic-calculated-title--primary")); + _test("custom name", _testStory({ + exportName: "Primary", + story: Primary, + meta: _meta, + skipTags: [], + storyId: "automatic-calculated-title--primary" + })); } `); }); @@ -247,7 +283,13 @@ describe('transformer', () => { Story.storyName = 'custom name'; const _isRunningFromThisFile = convertToFilePath(import.meta.url).includes(globalThis.__vitest_worker__.filepath ?? _expect.getState().testPath); if (_isRunningFromThisFile) { - _test("custom name", _testStory("Story", Story, _meta, [], "automatic-calculated-title--story")); + _test("custom name", _testStory({ + exportName: "Story", + story: Story, + meta: _meta, + skipTags: [], + storyId: "automatic-calculated-title--story" + })); } `); }); @@ -282,7 +324,13 @@ describe('transformer', () => { export { Primary }; const _isRunningFromThisFile = convertToFilePath(import.meta.url).includes(globalThis.__vitest_worker__.filepath ?? _expect.getState().testPath); if (_isRunningFromThisFile) { - _test("Primary", _testStory("Primary", Primary, _meta, [], "automatic-calculated-title--primary")); + _test("Primary", _testStory({ + exportName: "Primary", + story: Primary, + meta: _meta, + skipTags: [], + storyId: "automatic-calculated-title--primary" + })); } `); }); @@ -316,7 +364,13 @@ describe('transformer', () => { export { Primary as PrimaryStory }; const _isRunningFromThisFile = convertToFilePath(import.meta.url).includes(globalThis.__vitest_worker__.filepath ?? _expect.getState().testPath); if (_isRunningFromThisFile) { - _test("PrimaryStory", _testStory("PrimaryStory", Primary, _meta, [], "automatic-calculated-title--primary-story")); + _test("PrimaryStory", _testStory({ + exportName: "PrimaryStory", + story: Primary, + meta: _meta, + skipTags: [], + storyId: "automatic-calculated-title--primary-story" + })); } `); }); @@ -352,8 +406,20 @@ describe('transformer', () => { export { Primary }; const _isRunningFromThisFile = convertToFilePath(import.meta.url).includes(globalThis.__vitest_worker__.filepath ?? _expect.getState().testPath); if (_isRunningFromThisFile) { - _test("Secondary", _testStory("Secondary", Secondary, _meta, [], "automatic-calculated-title--secondary")); - _test("Primary", _testStory("Primary", Primary, _meta, [], "automatic-calculated-title--primary")); + _test("Secondary", _testStory({ + exportName: "Secondary", + story: Secondary, + meta: _meta, + skipTags: [], + storyId: "automatic-calculated-title--secondary" + })); + _test("Primary", _testStory({ + exportName: "Primary", + story: Primary, + meta: _meta, + skipTags: [], + storyId: "automatic-calculated-title--primary" + })); } `); }); @@ -384,7 +450,13 @@ describe('transformer', () => { export const nonStory = 123; const _isRunningFromThisFile = convertToFilePath(import.meta.url).includes(globalThis.__vitest_worker__.filepath ?? _expect.getState().testPath); if (_isRunningFromThisFile) { - _test("Story", _testStory("Story", Story, _meta, [], "automatic-calculated-title--story")); + _test("Story", _testStory({ + exportName: "Story", + story: Story, + meta: _meta, + skipTags: [], + storyId: "automatic-calculated-title--story" + })); } `); }); @@ -441,7 +513,13 @@ describe('transformer', () => { export const NotIncluded = {}; const _isRunningFromThisFile = convertToFilePath(import.meta.url).includes(globalThis.__vitest_worker__.filepath ?? _expect.getState().testPath); if (_isRunningFromThisFile) { - _test("Included", _testStory("Included", Included, _meta, [], "automatic-calculated-title--included")); + _test("Included", _testStory({ + exportName: "Included", + story: Included, + meta: _meta, + skipTags: [], + storyId: "automatic-calculated-title--included" + })); } `); }); @@ -472,7 +550,13 @@ describe('transformer', () => { }; const _isRunningFromThisFile = convertToFilePath(import.meta.url).includes(globalThis.__vitest_worker__.filepath ?? _expect.getState().testPath); if (_isRunningFromThisFile) { - _test("Included", _testStory("Included", Included, _meta, [], "automatic-calculated-title--included")); + _test("Included", _testStory({ + exportName: "Included", + story: Included, + meta: _meta, + skipTags: [], + storyId: "automatic-calculated-title--included" + })); } `); }); @@ -500,12 +584,70 @@ describe('transformer', () => { }; const _isRunningFromThisFile = convertToFilePath(import.meta.url).includes(globalThis.__vitest_worker__.filepath ?? _expect.getState().testPath); if (_isRunningFromThisFile) { - _test("Skipped", _testStory("Skipped", Skipped, _meta, ["skip-me"], "automatic-calculated-title--skipped")); + _test("Skipped", _testStory({ + exportName: "Skipped", + story: Skipped, + meta: _meta, + skipTags: ["skip-me"], + storyId: "automatic-calculated-title--skipped" + })); } `); }); }); + describe('component info extraction', () => { + it('should extract component name from named import specifier', async () => { + const code = ` + import { Button } from './Button'; + export default { + component: Button, + } + export const Primary = {}; + `; + + const result = await transform({ + code, + }); + + expect(result.code).toContain('componentPath: "./Button"'); + expect(result.code).toContain('componentName: "Button"'); + }); + it('should extract component name from default import specifier', async () => { + const code = ` + import Button from './Button'; + export default { + component: Button, + } + export const Primary = {}; + `; + + const result = await transform({ + code, + }); + + expect(result.code).toContain('componentPath: "./Button"'); + expect(result.code).toContain('componentName: "Button"'); + }); + + it('should extract component name from aliased import specifier', async () => { + const code = ` + import { Component as Button } from './Button'; + export default { + component: Button, + } + export const Primary = {}; + `; + + const result = await transform({ + code, + }); + + expect(result.code).toContain('componentPath: "./Button"'); + expect(result.code).toContain('componentName: "Button"'); + }); + }); + describe('source map calculation', () => { it('should remap the location of an inline named export to its relative testStory function', async () => { const originalCode = ` @@ -532,7 +674,13 @@ describe('transformer', () => { export const Primary = {}; const _isRunningFromThisFile = convertToFilePath(import.meta.url).includes(globalThis.__vitest_worker__.filepath ?? _expect.getState().testPath); if (_isRunningFromThisFile) { - _test("Primary", _testStory("Primary", Primary, meta, [], "automatic-calculated-title--primary")); + _test("Primary", _testStory({ + exportName: "Primary", + story: Primary, + meta: meta, + skipTags: [], + storyId: "automatic-calculated-title--primary" + })); } `); @@ -589,7 +737,13 @@ describe('transformer', () => { export const Story = meta.story({}); const _isRunningFromThisFile = convertToFilePath(import.meta.url).includes(globalThis.__vitest_worker__.filepath ?? _expect.getState().testPath); if (_isRunningFromThisFile) { - _test("Story", _testStory("Story", Story, meta, [], "automatic-calculated-title--story")); + _test("Story", _testStory({ + exportName: "Story", + story: Story, + meta: meta, + skipTags: [], + storyId: "automatic-calculated-title--story" + })); } `); }); @@ -616,7 +770,13 @@ describe('transformer', () => { }); const _isRunningFromThisFile = convertToFilePath(import.meta.url).includes(globalThis.__vitest_worker__.filepath ?? _expect.getState().testPath); if (_isRunningFromThisFile) { - _test("custom name", _testStory("Primary", Primary, meta, [], "automatic-calculated-title--primary")); + _test("custom name", _testStory({ + exportName: "Primary", + story: Primary, + meta: meta, + skipTags: [], + storyId: "automatic-calculated-title--primary" + })); } `); }); @@ -652,7 +812,13 @@ describe('transformer', () => { export { Primary }; const _isRunningFromThisFile = convertToFilePath(import.meta.url).includes(globalThis.__vitest_worker__.filepath ?? _expect.getState().testPath); if (_isRunningFromThisFile) { - _test("Primary", _testStory("Primary", Primary, meta, [], "automatic-calculated-title--primary")); + _test("Primary", _testStory({ + exportName: "Primary", + story: Primary, + meta: meta, + skipTags: [], + storyId: "automatic-calculated-title--primary" + })); } `); }); @@ -688,7 +854,13 @@ describe('transformer', () => { export { Primary as PrimaryStory }; const _isRunningFromThisFile = convertToFilePath(import.meta.url).includes(globalThis.__vitest_worker__.filepath ?? _expect.getState().testPath); if (_isRunningFromThisFile) { - _test("PrimaryStory", _testStory("PrimaryStory", Primary, meta, [], "automatic-calculated-title--primary-story")); + _test("PrimaryStory", _testStory({ + exportName: "PrimaryStory", + story: Primary, + meta: meta, + skipTags: [], + storyId: "automatic-calculated-title--primary-story" + })); } `); }); @@ -724,8 +896,20 @@ describe('transformer', () => { export { Primary }; const _isRunningFromThisFile = convertToFilePath(import.meta.url).includes(globalThis.__vitest_worker__.filepath ?? _expect.getState().testPath); if (_isRunningFromThisFile) { - _test("Secondary", _testStory("Secondary", Secondary, _meta, [], "automatic-calculated-title--secondary")); - _test("Primary", _testStory("Primary", Primary, _meta, [], "automatic-calculated-title--primary")); + _test("Secondary", _testStory({ + exportName: "Secondary", + story: Secondary, + meta: _meta, + skipTags: [], + storyId: "automatic-calculated-title--secondary" + })); + _test("Primary", _testStory({ + exportName: "Primary", + story: Primary, + meta: _meta, + skipTags: [], + storyId: "automatic-calculated-title--primary" + })); } `); }); @@ -756,7 +940,13 @@ describe('transformer', () => { export const nonStory = 123; const _isRunningFromThisFile = convertToFilePath(import.meta.url).includes(globalThis.__vitest_worker__.filepath ?? _expect.getState().testPath); if (_isRunningFromThisFile) { - _test("Story", _testStory("Story", Story, _meta, [], "automatic-calculated-title--story")); + _test("Story", _testStory({ + exportName: "Story", + story: Story, + meta: _meta, + skipTags: [], + storyId: "automatic-calculated-title--story" + })); } `); }); @@ -813,9 +1003,29 @@ describe('transformer', () => { const _isRunningFromThisFile = convertToFilePath(import.meta.url).includes(globalThis.__vitest_worker__.filepath ?? _expect.getState().testPath); if (_isRunningFromThisFile) { _describe("A ", () => { - _test("base story", _testStory("A", A, meta, [], "automatic-calculated-title--a")); - _test("foo", _testStory("A", A, meta, [], "automatic-calculated-title--a:foo", "foo")); - _test("bar", _testStory("A", A, meta, [], "automatic-calculated-title--a:bar", "bar")); + _test("base story", _testStory({ + exportName: "A", + story: A, + meta: meta, + skipTags: [], + storyId: "automatic-calculated-title--a" + })); + _test("foo", _testStory({ + exportName: "A", + story: A, + meta: meta, + skipTags: [], + storyId: "automatic-calculated-title--a:foo", + testName: "foo" + })); + _test("bar", _testStory({ + exportName: "A", + story: A, + meta: meta, + skipTags: [], + storyId: "automatic-calculated-title--a:bar", + testName: "bar" + })); }); } `); @@ -844,8 +1054,21 @@ describe('transformer', () => { const _isRunningFromThisFile = convertToFilePath(import.meta.url).includes(globalThis.__vitest_worker__.filepath ?? _expect.getState().testPath); if (_isRunningFromThisFile) { _describe("Primary ", () => { - _test("base story", _testStory("Primary", Primary, meta, [], "automatic-calculated-title--primary")); - _test("foo", _testStory("Primary", Primary, meta, [], "automatic-calculated-title--primary:foo", "foo")); + _test("base story", _testStory({ + exportName: "Primary", + story: Primary, + meta: meta, + skipTags: [], + storyId: "automatic-calculated-title--primary" + })); + _test("foo", _testStory({ + exportName: "Primary", + story: Primary, + meta: meta, + skipTags: [], + storyId: "automatic-calculated-title--primary:foo", + testName: "foo" + })); }); } `); @@ -880,7 +1103,13 @@ describe('transformer', () => { export const NotIncluded = meta.story({}); const _isRunningFromThisFile = convertToFilePath(import.meta.url).includes(globalThis.__vitest_worker__.filepath ?? _expect.getState().testPath); if (_isRunningFromThisFile) { - _test("Included", _testStory("Included", Included, meta, [], "automatic-calculated-title--included")); + _test("Included", _testStory({ + exportName: "Included", + story: Included, + meta: meta, + skipTags: [], + storyId: "automatic-calculated-title--included" + })); } `); }); @@ -912,7 +1141,13 @@ describe('transformer', () => { }); const _isRunningFromThisFile = convertToFilePath(import.meta.url).includes(globalThis.__vitest_worker__.filepath ?? _expect.getState().testPath); if (_isRunningFromThisFile) { - _test("Included", _testStory("Included", Included, meta, [], "automatic-calculated-title--included")); + _test("Included", _testStory({ + exportName: "Included", + story: Included, + meta: meta, + skipTags: [], + storyId: "automatic-calculated-title--included" + })); } `); }); @@ -941,7 +1176,13 @@ describe('transformer', () => { }); const _isRunningFromThisFile = convertToFilePath(import.meta.url).includes(globalThis.__vitest_worker__.filepath ?? _expect.getState().testPath); if (_isRunningFromThisFile) { - _test("Skipped", _testStory("Skipped", Skipped, meta, ["skip-me"], "automatic-calculated-title--skipped")); + _test("Skipped", _testStory({ + exportName: "Skipped", + story: Skipped, + meta: meta, + skipTags: ["skip-me"], + storyId: "automatic-calculated-title--skipped" + })); } `); }); @@ -972,8 +1213,21 @@ describe('transformer', () => { const _isRunningFromThisFile = convertToFilePath(import.meta.url).includes(globalThis.__vitest_worker__.filepath ?? _expect.getState().testPath); if (_isRunningFromThisFile) { _describe("Primary ", () => { - _test("base story", _testStory("Primary", Primary, meta, [], "automatic-calculated-title--primary")); - _test("foo", _testStory("Primary", Primary, meta, [], "automatic-calculated-title--primary:foo", "foo")); + _test("base story", _testStory({ + exportName: "Primary", + story: Primary, + meta: meta, + skipTags: [], + storyId: "automatic-calculated-title--primary" + })); + _test("foo", _testStory({ + exportName: "Primary", + story: Primary, + meta: meta, + skipTags: [], + storyId: "automatic-calculated-title--primary:foo", + testName: "foo" + })); }); } `); diff --git a/code/core/src/csf-tools/vitest-plugin/transformer.ts b/code/core/src/csf-tools/vitest-plugin/transformer.ts index cf43a9d3806b..bdbe2d04b03d 100644 --- a/code/core/src/csf-tools/vitest-plugin/transformer.ts +++ b/code/core/src/csf-tools/vitest-plugin/transformer.ts @@ -152,6 +152,18 @@ export async function vitestTransform({ const vitestExpectId = parsed._file.path.scope.generateUidIdentifier('expect'); const testStoryId = parsed._file.path.scope.generateUidIdentifier('testStory'); const skipTagsId = t.identifier(JSON.stringify(tagsFilter.skip)); + const componentPathLiteral = parsed._rawComponentPath + ? t.stringLiteral(parsed._rawComponentPath) + : null; + + let componentNameLiteral = null; + if ( + parsed._componentImportSpecifier && + (t.isImportSpecifier(parsed._componentImportSpecifier) || + t.isImportDefaultSpecifier(parsed._componentImportSpecifier)) + ) { + componentNameLiteral = t.stringLiteral(parsed._componentImportSpecifier.local.name); + } /** * In Storybook users might be importing stories from other story files. As a side effect, tests @@ -227,17 +239,27 @@ export async function vitestTransform({ overrideSourcemap?: boolean; storyId: string; }): t.ExpressionStatement => { + const objectProperties: t.ObjectProperty[] = [ + t.objectProperty(t.identifier('exportName'), t.stringLiteral(exportName)), + t.objectProperty(t.identifier('story'), t.identifier(localName)), + t.objectProperty(t.identifier('meta'), t.identifier(metaExportName)), + t.objectProperty(t.identifier('skipTags'), skipTagsId), + t.objectProperty(t.identifier('storyId'), t.stringLiteral(storyId)), + ]; + + if (componentPathLiteral) { + objectProperties.push(t.objectProperty(t.identifier('componentPath'), componentPathLiteral)); + } + + if (componentNameLiteral) { + objectProperties.push(t.objectProperty(t.identifier('componentName'), componentNameLiteral)); + } + // Create the _test expression directly using the exportName identifier const testStoryCall = t.expressionStatement( t.callExpression(vitestTestId, [ t.stringLiteral(testTitle), - t.callExpression(testStoryId, [ - t.stringLiteral(exportName), - t.identifier(localName), - t.identifier(metaExportName), - skipTagsId, - t.stringLiteral(storyId), - ]), + t.callExpression(testStoryId, [t.objectExpression(objectProperties)]), ]) ); @@ -271,17 +293,36 @@ export async function vitestTransform({ storyId: parentStoryId, }), ...tests.map(({ name: testName, node: testNode, id: storyId }) => { + const objectProperties: t.ObjectProperty[] = [ + t.objectProperty(t.identifier('exportName'), t.stringLiteral(exportName)), + t.objectProperty(t.identifier('story'), t.identifier(localName)), + t.objectProperty(t.identifier('meta'), t.identifier(metaExportName)), + t.objectProperty(t.identifier('skipTags'), t.arrayExpression([])), + t.objectProperty(t.identifier('storyId'), t.stringLiteral(storyId)), + ]; + + if (componentPathLiteral) { + objectProperties.push( + t.objectProperty(t.identifier('componentPath'), componentPathLiteral) + ); + } + + if (componentNameLiteral) { + objectProperties.push( + t.objectProperty(t.identifier('componentName'), componentNameLiteral) + ); + } + + if (testName) { + objectProperties.push( + t.objectProperty(t.identifier('testName'), t.stringLiteral(testName)) + ); + } + const testStatement = t.expressionStatement( t.callExpression(vitestTestId, [ t.stringLiteral(testName), - t.callExpression(testStoryId, [ - t.stringLiteral(exportName), - t.identifier(localName), - t.identifier(metaExportName), - t.arrayExpression([]), - t.stringLiteral(storyId), - t.stringLiteral(testName), - ]), + t.callExpression(testStoryId, [t.objectExpression(objectProperties)]), ]) ); testStatement.loc = testNode.loc;