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
1 change: 1 addition & 0 deletions code/core/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -308,6 +308,7 @@
"esbuild": "^0.18.0 || ^0.19.0 || ^0.20.0 || ^0.21.0 || ^0.22.0 || ^0.23.0 || ^0.24.0 || ^0.25.0",
"execa": "^8.0.1",
"exsolve": "^1.0.7",
"fast-printf": "^1.6.10",
"fetch-retry": "^6.0.0",
"flush-promises": "^1.0.2",
Comment on lines 309 to 313
Copy link
Contributor

@coderabbitai coderabbitai bot Sep 25, 2025

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🔴 Critical

Move fast-printf to dependencies.

fast-printf is consumed at runtime by csf-factories.ts, so bundling it as a dev dependency will leave downstream installs without the module and trigger Cannot find module 'fast-printf' at execution time. Please publish it as a production dependency.

@@
-    "esbuild": "^0.18.0 || ^0.19.0 || ^0.20.0 || ^0.21.0 || ^0.22.0 || ^0.23.0 || ^0.24.0 || ^0.25.0",
-    "recast": "^0.23.5",
+    "esbuild": "^0.18.0 || ^0.19.0 || ^0.20.0 || ^0.21.0 || ^0.22.0 || ^0.23.0 || ^0.24.0 || ^0.25.0",
+    "fast-printf": "^1.6.10",
+    "recast": "^0.23.5",
@@
-    "exsolve": "^1.0.7",
-    "fast-printf": "^1.6.10",
+    "exsolve": "^1.0.7",
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
"execa": "^8.0.1",
"exsolve": "^1.0.7",
"fast-printf": "^1.6.10",
"fetch-retry": "^6.0.0",
"flush-promises": "^1.0.2",
"dependencies": {
"esbuild": "^0.18.0 || ^0.19.0 || ^0.20.0 || ^0.21.0 || ^0.22.0 || ^0.23.0 || ^0.24.0 || ^0.25.0",
"fast-printf": "^1.6.10",
"recast": "^0.23.5"
// … other dependencies …
},
"devDependencies": {
"execa": "^8.0.1",
"exsolve": "^1.0.7",
"fetch-retry": "^6.0.0",
"flush-promises": "^1.0.2"
// … other devDependencies …
}
🤖 Prompt for AI Agents
In code/core/package.json around lines 309 to 313, fast-printf is listed as a
devDependency but is required at runtime by csf-factories.ts; move
"fast-printf": "^1.6.10" from devDependencies into the top-level dependencies
section (same version), remove it from devDependencies, then update the lockfile
by running npm install or yarn install so the production install includes the
package.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

The dependency will be bundled by the build script.

Copy link
Contributor

Choose a reason for hiding this comment

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

@mrginglymus Thank you for the clarification! You're absolutely right. I see from the retrieved learning that in Storybook's core package, dependencies are bundled into the final distribution during the build process, so they should remain in devDependencies rather than being moved to dependencies. End users don't need these packages as separate runtime dependencies since they're included in the bundled code.

I apologize for the confusion in my initial suggestion. The placement of fast-printf in devDependencies is correct for Storybook's bundling approach.


✏️ Learnings added
Learnt from: mrginglymus
PR: storybookjs/storybook#32556
File: code/core/package.json:309-313
Timestamp: 2025-09-29T13:20:23.320Z
Learning: The `fast-printf` dependency in Storybook's core package is bundled into the final distribution during the build process, so it should remain in devDependencies rather than being moved to dependencies, following the same pattern as other bundled dependencies like `open`.

Note: Learnings are effective only in the context of similar code segments. To apply general coding guidelines, please configure review instructions. You can manage existing learnings in the app.


🧠 Learnings used
Learnt from: ndelangen
PR: storybookjs/storybook#32484
File: code/core/package.json:326-326
Timestamp: 2025-09-17T07:31:04.432Z
Learning: In Storybook's core package, dependencies like `open` are bundled into the final distribution during the build process, so they should remain in devDependencies rather than being moved to dependencies. End users don't need these packages as separate runtime dependencies since they're included in the bundled code.

"fuse.js": "^3.6.1",
Expand Down
33 changes: 33 additions & 0 deletions code/core/src/component-testing/components/test-fn.stories.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -175,3 +175,36 @@ TestNames.test(
'should display an error when login is attempted with an unverified email address',
() => {}
);

export const TestEach = meta.story();

TestEach.each(
'Test Each With %s',
[['One'], ['Two']],
(num) => ({
args: {
children: num,
},
}),
({ canvas }, num) => {
expect(canvas.getByText(num)).toBeInTheDocument();
}
);

export const TestMatrix = meta.story();

TestMatrix.matrix(
'Test Matrix With %s %d',
[
['A', 'B'],
[1, 2],
],
(p1, p2) => ({
args: {
children: `${p1} ${p2}`,
},
}),
({ canvas }, p1, p2) => {
expect(canvas.getByText(`${p1} ${p2}`)).toBeInTheDocument();
}
);
311 changes: 311 additions & 0 deletions code/core/src/csf-tools/CsfFile.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2651,6 +2651,317 @@ describe('CsfFile', () => {
expect(storyTests[3].function).toBeDefined();
});

it('story test - tags', () => {
const data = loadCsf(
dedent`
import { config } from '#.storybook/preview'
const meta = config.meta({ component: 'foo' });
export default meta;
export const A = meta.story({ args: { label: 'foo' }})
A.test('simple test', { tags: ['test-me'] }, async () => {})
`,
{ makeTitle }
).parse();
const story = data._stories['A'];
expect(story.__stats.tests).toBe(true);

const storyTests = data.getStoryTests('A');
expect(storyTests).toHaveLength(1);
expect(storyTests[0].tags).toEqual(['test-me']);
});

it('story test each - index snapshot', () => {
expect(
parse(dedent`
import { config } from '#.storybook/preview'
const meta = config.meta({ component: 'foo' });
export default meta;
export const A = meta.story({ args: { label: 'foo' } });
A.each(
'simple test %s %d',
[
['foo', 2],
['bar', 3],
],
async () => {}
);
`)
).toMatchInlineSnapshot(`
meta:
component: '''foo'''
title: Default Title
stories:
- id: default-title--a
name: A
__stats:
factory: true
tests: true
play: false
render: false
loaders: false
beforeEach: false
globals: false
tags: false
storyFn: false
mount: false
moduleMock: false
`);
});

it('story test each - parsing', () => {
const data = loadCsf(
dedent`
import { config } from '#.storybook/preview'
const meta = config.meta({ component: 'foo' });
export default meta;
export const A = meta.story({ args: { label: 'foo' } });
A.each(
'simple test %s %d',
[
['foo', 2],
['bar', 3],
],
async () => {}
);
A.each(
'simple test %s %d',
[
['foo', 4],
['bar', 5],
],
{},
async () => {}
);
A.each(
'simple test %s %d',
[
['foo', 6],
['bar', 7],
],
() => ({}),
async () => {}
);
`,
{ makeTitle }
).parse();
const story = data._stories['A'];
expect(story.__stats.tests).toBe(true);
const storyTests = data.getStoryTests('A');
expect(storyTests).toHaveLength(6);

expect(storyTests[0].name).toBe('simple test foo 2');
expect(storyTests[1].name).toBe('simple test bar 3');
expect(storyTests[2].name).toBe('simple test foo 4');
expect(storyTests[3].name).toBe('simple test bar 5');
expect(storyTests[4].name).toBe('simple test foo 6');
expect(storyTests[5].name).toBe('simple test bar 7');
expect(storyTests[0].function).toBeDefined();
expect(storyTests[1].function).toBeDefined();
expect(storyTests[2].function).toBeDefined();
expect(storyTests[3].function).toBeDefined();
expect(storyTests[4].function).toBeDefined();
expect(storyTests[5].function).toBeDefined();
});

it('story test each - tags', () => {
const data = loadCsf(
dedent`
import { config } from '#.storybook/preview'
const meta = config.meta({ component: 'foo' });
export default meta;
export const A = meta.story({ args: { label: 'foo' } });
A.each(
'simple test %s',
[
['static'],
],
{
tags: ['test-me'],
},
async () => {}
);
A.each(
'simple test %s',
[
['arrow-direct'],
],
() => ({
tags: ['test-me'],
}),
async () => {}
);
A.each(
'simple test %s',
[
['arrow-indirect'],
],
() => {
return {
tags: ['test-me'],
}
},
async () => {}
);
A.each(
'simple test %s',
[
['function'],
],
function () {
return {
tags: ['test-me'],
}
},
async () => {}
);
`,
{ makeTitle }
).parse();
const story = data._stories['A'];
expect(story.__stats.tests).toBe(true);
const storyTests = data.getStoryTests('A');
expect(storyTests).toHaveLength(4);

for (const test of storyTests) {
expect(test.tags, test.name).toEqual(['test-me']);
}
});

it('story test matrix - index snapshot', () => {
expect(
parse(dedent`
import { config } from '#.storybook/preview'
const meta = config.meta({ component: 'foo' });
export default meta;
export const A = meta.story({ args: { label: 'foo' } });
A.matrix(
'simple test %s %d',
[
['foo', 'bar'],
[1, 2],
],
async () => {}
);
`)
).toMatchInlineSnapshot(`
meta:
component: '''foo'''
title: Default Title
stories:
- id: default-title--a
name: A
__stats:
factory: true
tests: true
play: false
render: false
loaders: false
beforeEach: false
globals: false
tags: false
storyFn: false
mount: false
moduleMock: false
`);
});

it('story test matrix - parsing', () => {
const data = loadCsf(
dedent`
import { config } from '#.storybook/preview'
const meta = config.meta({ component: 'foo' });
export default meta;
export const A = meta.story({ args: { label: 'foo' } });
A.matrix(
'simple test %s %d',
[
['foo', 'bar'],
[1, 2],
],
async () => {}
);
`,
{ makeTitle }
).parse();
const story = data._stories['A'];
expect(story.__stats.tests).toBe(true);
const storyTests = data.getStoryTests('A');
expect(storyTests).toHaveLength(4);

expect(storyTests[0].name).toBe('simple test foo 1');
expect(storyTests[1].name).toBe('simple test foo 2');
expect(storyTests[2].name).toBe('simple test bar 1');
expect(storyTests[3].name).toBe('simple test bar 2');
expect(storyTests[0].function).toBeDefined();
expect(storyTests[1].function).toBeDefined();
expect(storyTests[2].function).toBeDefined();
expect(storyTests[3].function).toBeDefined();
});

it('story test matrix - tags', () => {
const data = loadCsf(
dedent`
import { config } from '#.storybook/preview'
const meta = config.meta({ component: 'foo' });
export default meta;
export const A = meta.story({ args: { label: 'foo' } });
A.matrix(
'simple test %s',
[
['static'],
],
{
tags: ['test-me'],
},
async () => {}
);
A.matrix(
'simple test %s',
[
['arrow-direct'],
],
() => ({
tags: ['test-me'],
}),
async () => {}
);
A.matrix(
'simple test %s',
[
['arrow-indirect'],
],
() => {
return {
tags: ['test-me'],
}
},
async () => {}
);
A.matrix(
'simple test %s',
[
['function'],
],
function () {
return {
tags: ['test-me'],
}
},
async () => {}
);
`,
{ makeTitle }
).parse();
const story = data._stories['A'];
expect(story.__stats.tests).toBe(true);
const storyTests = data.getStoryTests('A');
expect(storyTests).toHaveLength(4);

for (const test of storyTests) {
expect(test.tags, test.name).toEqual(['test-me']);
}
});

it('story name', () => {
expect(
parse(
Expand Down
Loading