diff --git a/code/core/src/csf-tools/CsfFile.test.ts b/code/core/src/csf-tools/CsfFile.test.ts index 4299b8d586ff..a3ff6cee8cee 100644 --- a/code/core/src/csf-tools/CsfFile.test.ts +++ b/code/core/src/csf-tools/CsfFile.test.ts @@ -515,6 +515,57 @@ describe('CsfFile', () => { `); }); + it('typescript satisfies as', () => { + expect( + parse( + dedent` + import type { Meta, StoryFn, StoryObj } from '@storybook/react'; + type PropTypes = {}; + export default { title: 'foo/bar' } satisfies Meta as Meta; + export const A = { name: 'AA' } satisfies StoryObj; + export const B = ((args) => {}) satisfies StoryFn; + `, + true + ) + ).toMatchInlineSnapshot(` + meta: + title: foo/bar + stories: + - id: foo-bar--a + name: AA + parameters: + __isArgsStory: true + __id: foo-bar--a + __stats: + factory: false + play: false + render: false + loaders: false + beforeEach: false + globals: false + tags: false + storyFn: false + mount: false + moduleMock: false + - id: foo-bar--b + name: B + parameters: + __isArgsStory: true + __id: foo-bar--b + __stats: + factory: false + play: false + render: false + loaders: false + beforeEach: false + globals: false + tags: false + storyFn: false + mount: false + moduleMock: false + `); + }); + it('typescript meta var', () => { expect( parse( @@ -605,6 +656,136 @@ describe('CsfFile', () => { `); }); + it('typescript satisfies as meta', () => { + expect( + parse( + dedent` + import type { Meta, StoryFn } from '@storybook/react'; + type PropTypes = {}; + const meta = { title: 'foo/bar/baz' } satisfies Meta as Meta; + export default meta; + export const A: StoryFn = () => <>A; + export const B: StoryFn = () => <>B; + ` + ) + ).toMatchInlineSnapshot(` + meta: + title: foo/bar/baz + stories: + - id: foo-bar-baz--a + name: A + __stats: + factory: false + play: false + render: false + loaders: false + beforeEach: false + globals: false + tags: false + storyFn: true + mount: false + moduleMock: false + - id: foo-bar-baz--b + name: B + __stats: + factory: false + play: false + render: false + loaders: false + beforeEach: false + globals: false + tags: false + storyFn: true + mount: false + moduleMock: false + `); + }); + + it('typescript satisfies as stories', () => { + expect( + parse( + dedent` + import type { Meta, StoryFn, StoryObj } from '@storybook/react'; + type PropTypes = {}; + export default { title: 'foo/bar' } as Meta; + export const A = { name: 'AA' } satisfies StoryObj as StoryObj; + export const B = ((args) => {}) satisfies StoryFn as StoryFn; + `, + true + ) + ).toMatchInlineSnapshot(` + meta: + title: foo/bar + stories: + - id: foo-bar--a + name: AA + parameters: + __isArgsStory: true + __id: foo-bar--a + __stats: + factory: false + play: false + render: false + loaders: false + beforeEach: false + globals: false + tags: false + storyFn: false + mount: false + moduleMock: false + - id: foo-bar--b + name: B + parameters: + __isArgsStory: true + __id: foo-bar--b + __stats: + factory: false + play: false + render: false + loaders: false + beforeEach: false + globals: false + tags: false + storyFn: false + mount: false + moduleMock: false + `); + }); + + it('typescript satisfies as export specifier', () => { + expect( + parse( + dedent` + import type { Meta, StoryFn } from '@storybook/react'; + type PropTypes = {}; + const meta = { title: 'foo/bar/baz' } satisfies Meta as Meta; + const story = { name: 'Story A' }; + export { meta as default, story as A }; + `, + true + ) + ).toMatchInlineSnapshot(` + meta: + title: foo/bar/baz + stories: + - id: foo-bar-baz--a + name: A + localName: story + parameters: + __id: foo-bar-baz--a + __stats: + play: false + render: false + loaders: false + beforeEach: false + globals: false + tags: false + storyFn: false + mount: false + moduleMock: false + `); + }); + it('component object', () => { expect( parse( @@ -1995,7 +2176,7 @@ describe('CsfFile', () => { const { indexInputs } = loadCsf( dedent` const Component = (props) =>
hello
; - + export default { title: 'custom foo title', component: Component, @@ -2513,7 +2694,7 @@ describe('CsfFile', () => { ).toThrowErrorMatchingInlineSnapshot(` [MultipleMetaError: CSF: multiple meta objects (line 4, col 24) - More info: https://storybook.js.org/docs/writing-stories#default-export?ref=error] + More info: https://storybook.js.org/docs/writing-stories?ref=error#default-export] `); }); @@ -2531,7 +2712,7 @@ describe('CsfFile', () => { ).toThrowErrorMatchingInlineSnapshot(` [MultipleMetaError: CSF: multiple meta objects (line 3, col 25) - More info: https://storybook.js.org/docs/writing-stories#default-export?ref=error] + More info: https://storybook.js.org/docs/writing-stories?ref=error#default-export] `); }); @@ -2549,7 +2730,7 @@ describe('CsfFile', () => { ).toThrowErrorMatchingInlineSnapshot(` [MultipleMetaError: CSF: multiple meta objects - More info: https://storybook.js.org/docs/writing-stories#default-export?ref=error] + More info: https://storybook.js.org/docs/writing-stories?ref=error#default-export] `); }); @@ -2565,7 +2746,7 @@ describe('CsfFile', () => { ).toThrowErrorMatchingInlineSnapshot(` [BadMetaError: CSF: meta() factory must be imported from .storybook/preview configuration (line 1, col 0) - More info: https://storybook.js.org/docs/writing-stories#default-export?ref=error] + More info: https://storybook.js.org/docs/writing-stories?ref=error#default-export] `); }); @@ -2582,7 +2763,7 @@ describe('CsfFile', () => { ).toThrowErrorMatchingInlineSnapshot(` [BadMetaError: CSF: meta() factory must be imported from .storybook/preview configuration (line 4, col 28) - More info: https://storybook.js.org/docs/writing-stories#default-export?ref=error] + More info: https://storybook.js.org/docs/writing-stories?ref=error#default-export] `); }); @@ -2599,7 +2780,7 @@ describe('CsfFile', () => { ).toThrowErrorMatchingInlineSnapshot(` [MixedFactoryError: CSF: expected factory story (line 4, col 17) - More info: https://storybook.js.org/docs/writing-stories#default-export?ref=error] + More info: https://storybook.js.org/docs/writing-stories?ref=error#default-export] `); }); @@ -2616,7 +2797,7 @@ describe('CsfFile', () => { ).toThrowErrorMatchingInlineSnapshot(` [MixedFactoryError: CSF: expected non-factory story (line 4, col 28) - More info: https://storybook.js.org/docs/writing-stories#default-export?ref=error] + More info: https://storybook.js.org/docs/writing-stories?ref=error#default-export] `); }); }); diff --git a/code/core/src/csf-tools/CsfFile.ts b/code/core/src/csf-tools/CsfFile.ts index 7e88d731e6e9..ab23eb33045a 100644 --- a/code/core/src/csf-tools/CsfFile.ts +++ b/code/core/src/csf-tools/CsfFile.ts @@ -181,7 +181,7 @@ export class NoMetaError extends Error { super(dedent` CSF: ${msg} ${formatLocation(ast, fileName)} - More info: https://storybook.js.org/docs/writing-stories#default-export?ref=error + More info: https://storybook.js.org/docs/writing-stories?ref=error#default-export `); this.name = this.constructor.name; } @@ -193,7 +193,7 @@ export class MultipleMetaError extends Error { super(dedent` CSF: ${message} ${formatLocation(ast, fileName)} - More info: https://storybook.js.org/docs/writing-stories#default-export?ref=error + More info: https://storybook.js.org/docs/writing-stories?ref=error#default-export `); this.name = this.constructor.name; } @@ -205,7 +205,7 @@ export class MixedFactoryError extends Error { super(dedent` CSF: ${message} ${formatLocation(ast, fileName)} - More info: https://storybook.js.org/docs/writing-stories#default-export?ref=error + More info: https://storybook.js.org/docs/writing-stories?ref=error#default-export `); this.name = this.constructor.name; } @@ -217,7 +217,7 @@ export class BadMetaError extends Error { super(dedent` CSF: ${message} ${formatLocation(ast, fileName)} - More info: https://storybook.js.org/docs/writing-stories#default-export?ref=error + More info: https://storybook.js.org/docs/writing-stories?ref=error#default-export `); this.name = this.constructor.name; } @@ -442,10 +442,18 @@ export class CsfFile { metaNode = decl; } else if ( // export default { ... } as Meta<...> + // export default { ... } satisfies Meta<...> (t.isTSAsExpression(decl) || t.isTSSatisfiesExpression(decl)) && t.isObjectExpression(decl.expression) ) { metaNode = decl.expression; + } else if ( + // export default { ... } satisfies Meta as Meta<...> + t.isTSAsExpression(decl) && + t.isTSSatisfiesExpression(decl.expression) && + t.isObjectExpression(decl.expression.expression) + ) { + metaNode = decl.expression.expression; } if (metaNode && t.isProgram(parent)) { @@ -495,10 +503,22 @@ export class CsfFile { } let storyNode; if (t.isVariableDeclarator(decl)) { - storyNode = - t.isTSAsExpression(decl.init) || t.isTSSatisfiesExpression(decl.init) - ? decl.init.expression - : decl.init; + if ( + t.isTSAsExpression(decl.init) && + t.isTSSatisfiesExpression(decl.init.expression) + ) { + // { ... } satisfies Meta<...> as Meta<...> + storyNode = decl.init.expression.expression; + } else if ( + t.isTSAsExpression(decl.init) || + t.isTSSatisfiesExpression(decl.init) + ) { + // { ... } as Meta<...> + // { ... } satisfies Meta<...> + storyNode = decl.init.expression; + } else { + storyNode = decl.init; + } } else { storyNode = decl; } @@ -596,10 +616,18 @@ export class CsfFile { metaNode = decl; } else if ( // export default { ... } as Meta<...> - t.isTSAsExpression(decl) && + // export default { ... } satisfies Meta<...> + (t.isTSAsExpression(decl) || t.isTSSatisfiesExpression(decl)) && t.isObjectExpression(decl.expression) ) { metaNode = decl.expression; + } else if ( + // export default { ... } satisfies Meta as Meta<...> + t.isTSAsExpression(decl) && + t.isTSSatisfiesExpression(decl.expression) && + t.isObjectExpression(decl.expression.expression) + ) { + metaNode = decl.expression.expression; } if (metaNode && t.isProgram(parent)) { @@ -681,7 +709,7 @@ export class CsfFile { throw new Error(dedent` Unexpected \`storiesOf\` usage: ${formatLocation(node, self._options.fileName)}. - SB8 does not support \`storiesOf\`. + SB8 does not support \`storiesOf\`. `); } if (