Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
@@ -1,12 +1,19 @@
import { describe, expect, it, vi } from 'vitest';

import { formatFileContent } from 'storybook/internal/common';
import { logger } from 'storybook/internal/node-logger';

import path from 'path';
import { dedent } from 'ts-dedent';

import { storyToCsfFactory } from './story-to-csf-factory';

vi.mock('storybook/internal/node-logger', () => ({
logger: {
warn: vi.fn(),
},
}));

expect.addSnapshotSerializer({
serialize: (val: any) => (typeof val === 'string' ? val : val.toString()),
test: () => true,
Expand Down Expand Up @@ -261,7 +268,7 @@
});

it('does not migrate reused properties from disallowed list', async () => {
await expect(

Check failure on line 271 in code/lib/cli-storybook/src/codemod/helpers/story-to-csf-factory.test.ts

View workflow job for this annotation

GitHub Actions / Core Unit Tests, windows-latest

src/codemod/helpers/story-to-csf-factory.test.ts > stories codemod > javascript > does not migrate reused properties from disallowed list

Error: Snapshot `stories codemod > javascript > does not migrate reused properties from disallowed list 1` mismatched - Expected + Received - import preview from '#.storybook/preview'; - - const meta = preview.meta({ - title: 'Component', - }); + export default { title: 'Component' }; - - export const A = meta.story(); + export const A = {}; - export const B = meta.story({ + export const B = { play: async () => { await A.play(); }, - }); + }; export const C = A.run; export const D = A.extends({}); ❯ src/codemod/helpers/story-to-csf-factory.test.ts:271:7
transform(dedent`
export default { title: 'Component' };
export const A = {};
Expand Down Expand Up @@ -310,7 +317,7 @@
export { C, D as E };
`);

expect(transformed).toMatchInlineSnapshot(`

Check failure on line 320 in code/lib/cli-storybook/src/codemod/helpers/story-to-csf-factory.test.ts

View workflow job for this annotation

GitHub Actions / Core Unit Tests, windows-latest

src/codemod/helpers/story-to-csf-factory.test.ts > stories codemod > javascript > should support non-conventional formats (INCOMPLETE)

Error: Snapshot `stories codemod > javascript > should support non-conventional formats (INCOMPLETE) 1` mismatched - Expected + Received - import preview from '#.storybook/preview'; - import { A as Component } from './Button'; import * as Stories from './Other.stories'; import someData from './fixtures'; - const meta = preview.meta({ + export default { component: Component, - // not supported yet (story coming from another file) args: Stories.A.args, - }); + }; - const data = {}; - export const A = meta.story(() => {}); + export const A = () => {}; - export const B = meta.story(() => {}); + export function B() {} // not supported yet (story redeclared) - const C = { ...A.input, args: data }; + const C = { ...A, args: data }; const D = { args: data }; export { C, D as E }; ❯ src/codemod/helpers/story-to-csf-factory.test.ts:320:27
import preview from '#.storybook/preview';

import { A as Component } from './Button';
Expand Down Expand Up @@ -629,13 +636,15 @@
const data = {};
export const A: StoryObj = () => {};
export function B() { };
export const C = () => <Component />;
export const D = C;
// not supported yet (story redeclared)
const C = { ...A, args: data, } satisfies CSF3<ComponentProps>;
const D = { args: data };
export { C, D as E };
const E = { ...A, args: data, } satisfies CSF3<ComponentProps>;
const F = { args: data };
export { E, F as G };
`);

expect(transformed).toMatchInlineSnapshot(`

Check failure on line 647 in code/lib/cli-storybook/src/codemod/helpers/story-to-csf-factory.test.ts

View workflow job for this annotation

GitHub Actions / Core Unit Tests, windows-latest

src/codemod/helpers/story-to-csf-factory.test.ts > stories codemod > typescript > should support non-conventional formats

Error: Snapshot `stories codemod > typescript > should support non-conventional formats 1` mismatched - Expected + Received - import preview from '#.storybook/preview'; + import { StoryObj as CSF3, Meta } from '@storybook/react'; import { A as Component } from './Button'; import { ComponentProps } from './Component'; import * as Stories from './Other.stories'; import someData from './fixtures'; - const meta = preview.meta({ + export default { title: 'Component', component: Component, - // not supported yet (story coming from another file) args: Stories.A.args, - }); + }; - const data = {}; - export const A = meta.story(() => {}); + export const A: StoryObj = () => {}; - export const B = meta.story(() => {}); + export function B() {} - export const C = meta.story(() => <Component />); + export const C = () => <Component />; - export const D = C.input; + export const D = C; // not supported yet (story redeclared) - const E = { ...A.input, args: data } satisfies CSF3<ComponentProps>; + const E = { ...A, args: data } satisfies CSF3<ComponentProps>; const F = { args: data }; export { E, F as G }; ❯ src/codemod/helpers/story-to-csf-factory.test.ts:647:27
import preview from '#.storybook/preview';

import { A as Component } from './Button';
Expand All @@ -654,16 +663,39 @@
const data = {};
export const A = meta.story(() => {});
export const B = meta.story(() => {});
export const C = meta.story(() => <Component />);
export const D = C.input;
// not supported yet (story redeclared)
const C = { ...A.input, args: data } satisfies CSF3<ComponentProps>;
const D = { args: data };
export { C, D as E };
const E = { ...A.input, args: data } satisfies CSF3<ComponentProps>;
const F = { args: data };
export { E, F as G };
`);

expect(transformed).toContain('A = meta.story');
expect(transformed).toContain('B = meta.story');
// @TODO: when we support these, uncomment this line
// expect(transformed).toContain('C = meta.story');
});
it('should not complete transformation if no stories are not transformed', async () => {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

syntax: Test description has double negative 'should not complete transformation if no stories are not transformed' - should be 'if not all stories are transformed'

Suggested change
it('should not complete transformation if no stories are not transformed', async () => {
it('should not complete transformation if not all stories are transformed', async () => {

const source = dedent`
export default {
title: 'Component',
};
export const A = {};
// not supported yet (story redeclared)
const B = { args: data };
const C = { args: data };
export { B, C as D };
`;
const transformed = await transform(source);
expect(transformed).toEqual(source);

Check failure on line 691 in code/lib/cli-storybook/src/codemod/helpers/story-to-csf-factory.test.ts

View workflow job for this annotation

GitHub Actions / Core Unit Tests, windows-latest

src/codemod/helpers/story-to-csf-factory.test.ts > stories codemod > typescript > should not complete transformation if no stories are not transformed

AssertionError: expected 'export default {\n title: \'Componen…' to deeply equal 'export default {\n title: \'Componen…' - Expected + Received @@ -4,5 +4,6 @@ export const A = {}; // not supported yet (story redeclared) const B = { args: data }; const C = { args: data }; export { B, C as D }; + ❯ src/codemod/helpers/story-to-csf-factory.test.ts:691:27

expect(transformed).not.toContain('preview.meta');
expect(transformed).not.toContain('meta.story');

expect(logger.warn).toHaveBeenCalledWith(
'Skipping codemod for Component.stories.tsx:\nDetected stories ["A", "B", "D"] were not fully transformed because some use an unsupported format.'
);
});
});
});
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,11 @@ export async function storyToCsfFactory(
return info.source;
}

// Track detected stories and which ones we actually transform
const detectedStories = csf.stories;
const detectedStoryNames = detectedStories.map((story) => story.name);
const transformedStoryExports = new Set<string>();

const metaVariableName = csf._metaVariableName ?? 'meta';

/**
Expand Down Expand Up @@ -95,7 +100,7 @@ export async function storyToCsfFactory(
// @TODO: Support unconventional formats:
// `export function Story() { };` and `export { Story };
// These are not part of csf._storyExports but rather csf._storyStatements and are tricky to support.
Object.entries(csf._storyExports).forEach(([, decl]) => {
Object.entries(csf._storyExports).forEach(([exportName, decl]) => {
const id = decl.id;
const declarator = decl as t.VariableDeclarator;
let init = t.isVariableDeclarator(declarator) ? declarator.init : undefined;
Expand All @@ -118,12 +123,18 @@ export async function storyToCsfFactory(
t.memberExpression(t.identifier(metaVariableName), t.identifier('story')),
init.properties.length === 0 ? [] : [init]
);
if (t.isIdentifier(id)) {
transformedStoryExports.add(exportName);
}
} else if (t.isArrowFunctionExpression(init)) {
// Transform CSF1 to meta.story({ render: <originalFn> })
declarator.init = t.callExpression(
t.memberExpression(t.identifier(metaVariableName), t.identifier('story')),
[init]
);
if (t.isIdentifier(id)) {
transformedStoryExports.add(exportName);
}
}
}
});
Expand Down Expand Up @@ -152,6 +163,7 @@ export async function storyToCsfFactory(
)._storyPaths?.[exportName];
if (pathForExport && pathForExport.replaceWith) {
pathForExport.replaceWith(replacement);
transformedStoryExports.add(exportName);
}
}
});
Expand Down Expand Up @@ -227,6 +239,16 @@ export async function storyToCsfFactory(
},
});

// If some stories were detected but not all could be transformed, we skip the codemod to avoid mixed csf syntax and therefore a broken indexer.
if (detectedStoryNames.length > 0 && transformedStoryExports.size !== detectedStoryNames.length) {
logger.warn(
`Skipping codemod for ${info.path}:\nDetected stories [${detectedStoryNames
.map((name) => `"${name}"`)
.join(', ')}] were not fully transformed because some use an unsupported format.`
);
return info.source;
}

// modify meta
if (csf._metaPath) {
let declaration = csf._metaPath.node.declaration;
Expand Down
Loading