Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
236 commits
Select commit Hold shift + click to select a range
81e9c77
wip prototype for test fn plugin
yannbf May 22, 2025
1803fb0
continue prototype
yannbf May 25, 2025
78abf69
fixes
yannbf May 25, 2025
c2ecdb5
do not apply loader/plugin to svelte files
yannbf May 25, 2025
af298b5
no-op on non csf-factories
yannbf May 25, 2025
5dc7ef7
Merge branch 'next' into yann/test-fn-prototype
yannbf Jul 24, 2025
1086746
Handle type 'test' in Storybook UI
yannbf Jul 25, 2025
f455ffa
workaround
yannbf Jul 25, 2025
5a08df8
Merge pull request #32168 from storybookjs/shilman/add-dev-cancel-event
shilman Aug 1, 2025
d37b759
Merge pull request #31785 from storybookjs/jeppe/fix-global-settings-…
yannbf Aug 1, 2025
2390147
Merge pull request #32166 from storybookjs/docs-automocking-2
kylegach Aug 1, 2025
ba2ec9c
Merge pull request #32185 from storybookjs/shilman/tweak-survey
yannbf Aug 4, 2025
8fe5aa6
Write changelog for 9.1.1 [skip ci]
storybook-bot Aug 4, 2025
2a43712
Merge pull request #32175 from storybookjs/version-patch-from-9.1.0
yannbf Aug 4, 2025
a6bb54c
Bump version from "9.1.0" to "9.1.1" [skip ci]
storybook-bot Aug 4, 2025
ff40d89
Merge branch 'next' into yann/test-fn-prototype
yannbf Aug 4, 2025
bc01f93
add vitest integration support
yannbf Aug 4, 2025
5f9bb80
Make vitest integration work
yannbf Aug 6, 2025
ff6eb10
Update ./docs/versions/next.json for v9.2.0-alpha.2
storybook-bot Aug 6, 2025
462d8ba
Docs: Fix incorrect @next version specifiers
kylegach Jun 9, 2025
e6f92b8
Merge pull request #32213 from storybookjs/docs-snippets-next-to-late…
kylegach Aug 6, 2025
629bdff
Merge pull request #32156 from storybookjs/docs-after-each
kylegach Aug 5, 2025
e10f871
Merge pull request #32198 from storybookjs/docs-bad-upgrading-link
kylegach Aug 5, 2025
d73c129
Merge pull request #32108 from gingeekrishna/fix-storybook-webpack-32105
valentinpalkovic Aug 7, 2025
bed61ea
Merge pull request #32224 from storybookjs/docs_fix_essentials_refere…
jonniebigodes Aug 8, 2025
933b626
Merge pull request #32220 from storybookjs/yann/fix-jsx-issue-2
yannbf Aug 8, 2025
b53a9e1
Merge pull request #32218 from storybookjs/shilman/fix-cancel-telemetry
shilman Aug 8, 2025
9c44db6
Merge pull request #32230 from sk-pub/bugfix/32229
valentinpalkovic Aug 11, 2025
bbf1e77
Merge pull request #31937 from mrginglymus/fix-addon-detection-windows
valentinpalkovic Aug 11, 2025
190ceef
Merge pull request #32131 from JulioJ11/fix/nextjs-15-link-component
valentinpalkovic Aug 11, 2025
9476827
Merge pull request #31928 from kachurun/SolidJS
shilman Aug 11, 2025
b5bbb76
Merge pull request #32240 from storybookjs/yann/fix-e2e-tests
yannbf Aug 11, 2025
fb9266c
Write changelog for 9.1.2 [skip ci]
storybook-bot Aug 11, 2025
c07651f
Update ./docs/versions/next.json for v9.2.0-alpha.3
storybook-bot Aug 11, 2025
6f86c4a
Merge pull request #32197 from storybookjs/version-patch-from-9.1.1
yannbf Aug 11, 2025
99c205c
Bump version from "9.1.1" to "9.1.2" [skip ci]
storybook-bot Aug 11, 2025
dda2670
Merge branch 'latest-release'
storybook-bot Aug 11, 2025
3c00cc8
more experiments
yannbf Aug 19, 2025
cde0b32
cleanup
yannbf Aug 19, 2025
6cc74e2
cleanup
yannbf Aug 19, 2025
9d544a6
prepare for index type test
yannbf Aug 19, 2025
bef2d9e
update tests
yannbf Aug 19, 2025
87446ca
update types
yannbf Aug 19, 2025
110822c
update tests
yannbf Aug 19, 2025
b803204
support function references
yannbf Aug 19, 2025
5dce7ad
add test render phase
yannbf Aug 19, 2025
62cdd05
Update ./docs/versions/next.json for v10.0.0-beta.0
storybook-bot Aug 20, 2025
08b3fb2
prepare for the UI work
yannbf Aug 20, 2025
40df0de
updates
yannbf Aug 20, 2025
8acca71
fix
yannbf Aug 20, 2025
67643a8
fix
yannbf Aug 20, 2025
d7dc33d
fix
yannbf Aug 20, 2025
991b3b5
fixes
yannbf Aug 20, 2025
beb32bf
fix
yannbf Aug 20, 2025
4e90611
Fix types
kasperpeulen Aug 20, 2025
9719ea6
Refactor test prototype
kasperpeulen Aug 20, 2025
82b84e4
Improve a bit
kasperpeulen Aug 20, 2025
f8ff03a
Merge pull request #32238 from storybookjs/sidnioulz/issue-31436-table
valentinpalkovic Aug 12, 2025
b21eadc
Merge pull request #32216 from storybookjs/valentin/add-error-handlin…
valentinpalkovic Aug 7, 2025
2bae930
Merge pull request #32283 from storybookjs/shilman/readme-utm-params
shilman Aug 19, 2025
200d9bb
Merge pull request #32231 from storybookjs/docs_snippets_fix_incorrec…
jonniebigodes Aug 11, 2025
3f0e5d7
Merge pull request #32232 from storybookjs/docs_fix_type_imports_snip…
jonniebigodes Aug 11, 2025
633478c
Write changelog for 9.1.3 [skip ci]
storybook-bot Aug 21, 2025
0f86613
Merge pull request #32287 from storybookjs/shilman/error-utm
shilman Aug 20, 2025
bbb4ffe
Merge pull request #32286 from storybookjs/shilman/configure-utm
shilman Aug 19, 2025
730bbf0
Merge pull request #32284 from storybookjs/shilman/package-json-keywords
shilman Aug 19, 2025
2d729ed
Merge pull request #32228 from storybookjs/docs-addon-vitest-config-typo
kylegach Aug 18, 2025
a0151ef
Merge pull request #32306 from storybookjs/valentin/fix-nextjs-webpac…
ndelangen Aug 20, 2025
eca1ace
fix unit tests
yannbf Aug 21, 2025
ee34e40
fix chromatic failures
yannbf Aug 21, 2025
6ab99d4
Fix override in portable stories
kasperpeulen Aug 21, 2025
024cde6
Merge remote-tracking branch 'origin/kasper/refactor-test-prototype' …
kasperpeulen Aug 21, 2025
7def7d9
add fixes for portable stories and prepareStory
yannbf Aug 21, 2025
1dba824
Next.js: Avoid multiple webpack versions at runtime
valentinpalkovic Aug 21, 2025
2c99471
Merge pull request #32241 from storybookjs/version-patch-from-9.1.2
ndelangen Aug 21, 2025
ce39157
Bump version from "9.1.2" to "9.1.3" [skip ci]
storybook-bot Aug 21, 2025
3ab5db3
Improved doTest type
kasperpeulen Aug 21, 2025
8e74582
Improve test execution in StoryRender
kasperpeulen Aug 21, 2025
df6dce7
Merge pull request #32314 from storybookjs/kasper/refactor-test-proto…
yannbf Aug 21, 2025
ad68ec3
Next.js: Fix version mismatch error in Webpack
yannbf Aug 21, 2025
91f0fc4
bring back all tests
yannbf Aug 21, 2025
864ed55
update tests
yannbf Aug 21, 2025
d14ea0e
support story tests in single test run
yannbf Aug 21, 2025
32f7796
update comments
yannbf Aug 21, 2025
50fc0b7
fix vitest regex
yannbf Aug 21, 2025
512c1e6
Refactor to see stories as tests
kasperpeulen Aug 26, 2025
0358dcc
Remove some todos
kasperpeulen Aug 26, 2025
620663c
Rename
kasperpeulen Aug 26, 2025
91a0149
Fix
kasperpeulen Aug 26, 2025
68603a9
fix
yannbf Aug 26, 2025
a444184
fix tests and revamp test filtering
yannbf Aug 26, 2025
c47c4fa
Merge pull request #32328 from storybookjs/kasper/see-tests-as-stories
yannbf Aug 26, 2025
7b564ea
Add TestNode for sidebar
ghengeveld Aug 21, 2025
52a3cf3
implement test metrics
yannbf Aug 27, 2025
5c6ef7f
Some cleanup
ghengeveld Aug 27, 2025
d509fc3
Fix types, avoid any
ghengeveld Aug 27, 2025
05e57e6
Add missing deps
ghengeveld Aug 27, 2025
9c78432
Render test entries in sidebar and add-in wrapper entries for stories…
ghengeveld Aug 27, 2025
700ec5b
Add story with play function
ghengeveld Aug 27, 2025
cbbd7be
Ensure wrapper gets proper children and tags
ghengeveld Aug 27, 2025
5fe23d4
Fix filtering by test-fn tag
ghengeveld Aug 27, 2025
dfe2b36
Refactor sandbox generation script: streamline local registry handlin…
ndelangen Aug 28, 2025
5aa436a
Update Storybook CLI path in sandbox generation script to use CommonJ…
ndelangen Aug 28, 2025
6b8ea8f
Update SOLID generator options to disable component addition
ndelangen Aug 28, 2025
05a264c
fix search item icon for story tests
yannbf Aug 28, 2025
f6c090e
add more stories
yannbf Aug 28, 2025
ebe497a
Merge pull request #32335 from storybookjs/yann/test-metrics
yannbf Aug 28, 2025
e335dc4
Add subtype property to distinguish tests from stories
ghengeveld Aug 28, 2025
6fa8664
Add autodocs to test-fn stories for dev/test/debug
ghengeveld Aug 28, 2025
8dee332
Refactor story index transformation logic for improved filtering and …
ghengeveld Aug 29, 2025
d9bf94a
Fix sortStory
ghengeveld Aug 29, 2025
b6c82bc
fix tests
yannbf Aug 29, 2025
8e06a46
fix build/type issues
yannbf Aug 29, 2025
f70fbda
fix unit tests, write E2E
yannbf Aug 29, 2025
eefc73e
fix indexing of tests
yannbf Aug 28, 2025
5640b6e
Merge pull request #32337 from storybookjs/sidebar-test-type
yannbf Aug 29, 2025
5344b3f
fix snapshot
yannbf Aug 29, 2025
c166dc4
fix check
yannbf Aug 29, 2025
c0bad22
Merge pull request #32217 from storybookjs/docs-svelte-csf-improvements
kylegach Aug 21, 2025
55ac169
Merge pull request #32320 from storybookjs/jeppe/fix-vps-dependency-r…
JReinhold Aug 27, 2025
470f1fd
Merge pull request #32319 from storybookjs/copilot/fix-32318
JReinhold Aug 27, 2025
58752c6
Merge pull request #32350 from storybookjs/norbert/deduplicate-babel
ndelangen Aug 29, 2025
6b36e49
Merge pull request #32272 from storybookjs/kroeder/angular-config-mer…
valentinpalkovic Aug 29, 2025
035243a
Write changelog for 9.1.4 [skip ci]
storybook-bot Sep 1, 2025
07d688d
Update tags filter UI
ghengeveld Sep 1, 2025
37a5853
Update ./docs/versions/next.json for v10.0.0-beta.1
storybook-bot Sep 2, 2025
e1fcd14
Update sandbox generation script to include resolutions for react and…
ndelangen Sep 2, 2025
c1dd869
Update sandbox generation script to include resolutions for @types/re…
ndelangen Sep 2, 2025
2aca744
Merge pull request #32317 from storybookjs/version-patch-from-9.1.3
ndelangen Sep 2, 2025
9f02684
Bump version from "9.1.3" to "9.1.4" [skip ci]
storybook-bot Sep 2, 2025
6813ee2
Merge branch 'latest-release'
storybook-bot Sep 2, 2025
cf297c8
Enhance sidebar to support test entries and improve story handling
ghengeveld Sep 2, 2025
a95c291
Add default selection for tags in sidebar and update TagsFilter compo…
yannbf Sep 2, 2025
b6fbeda
add jsdocs
yannbf Sep 2, 2025
a632036
Remove console.log
ghengeveld Sep 2, 2025
0b3d490
Enhance Button story rendering and improve sidebar node expansion logic
ghengeveld Sep 2, 2025
9f16a26
Update VitestManager to use correct RegExp
ghengeveld Sep 2, 2025
5705bb5
update to use defaultSelection instead
yannbf Sep 2, 2025
24253d8
small rename
yannbf Sep 2, 2025
8d18419
cleanup
yannbf Sep 2, 2025
15e965e
Merge pull request #32373 from storybookjs/yann/add-tags-preset-to-main
yannbf Sep 2, 2025
4831e99
fix build
yannbf Sep 2, 2025
3e8c306
Implement indeterminate state for unrepresentable combination of defa…
ghengeveld Sep 3, 2025
1a4ee93
disable save from controls in story tests
yannbf Sep 3, 2025
cd66148
fix build
yannbf Sep 3, 2025
bab3780
Add story for non-initial state
ghengeveld Sep 3, 2025
f570532
Fix filtering out stories which have tests
ghengeveld Sep 3, 2025
7a03431
Fix default invert toggle setting
ghengeveld Sep 3, 2025
07e99ae
Refactor CsfFile to support test tags and update related methods for …
yannbf Sep 3, 2025
b6c6e94
Make it possible to distinguish tests from stories in StoryStore.extr…
ghengeveld Sep 3, 2025
85cf726
Update ./docs/versions/next.json for v10.0.0-beta.2
storybook-bot Sep 3, 2025
639042c
small refactor
yannbf Sep 3, 2025
3fa6097
Merge pull request #32385 from storybookjs/yann/fix-test-tags-extraction
yannbf Sep 3, 2025
9b7fa17
fix tests
yannbf Sep 3, 2025
d61fba2
Merge branch 'yann/test-fn-prototype' into update-storystore-extract
yannbf Sep 3, 2025
319ace2
Merge pull request #32388 from storybookjs/update-storystore-extract
yannbf Sep 3, 2025
48cb2d9
Merge branch 'latest-release' into feature/test-syntax-in-csf
yannbf Sep 3, 2025
478e4f1
undo some merge changes
yannbf Sep 3, 2025
7aa48c2
Fix regression and failing E2E tests
yannbf Sep 3, 2025
f5e829d
fix test-runner tests
yannbf Sep 3, 2025
0b21aff
fix unit tests
yannbf Sep 3, 2025
f793686
Fix focused test run in Storybook sidebar
yannbf Sep 4, 2025
2d64830
Revert "Fix focused test run in Storybook sidebar"
yannbf Sep 4, 2025
86db945
Fix focused test run in Storybook sidebar
yannbf Sep 4, 2025
07beaea
remove log
yannbf Sep 4, 2025
c22aa61
use double spaces instead
yannbf Sep 4, 2025
f0cfe33
only use double space in parent stories
yannbf Sep 4, 2025
3113762
fix tests
yannbf Sep 4, 2025
c2aedef
Merge pull request #32403 from storybookjs/yann/fix-focused-test-runs
yannbf Sep 4, 2025
d98408f
Implement include/exclude toggle for each tag to replace global inver…
ghengeveld Sep 4, 2025
54ecd3a
Enhance codemod tests and support for non-conventional story formats …
yannbf Sep 4, 2025
41c83af
Merge pull request #32414 from storybookjs/yann/fix-csf-factories-cod…
yannbf Sep 4, 2025
c652477
Update tags filter UI and fix filter function logic
ghengeveld Sep 4, 2025
6941da2
Merge pull request #32329 from storybookjs/norbert/proceed-if-playwri…
ndelangen Sep 2, 2025
4aaaeba
Merge pull request #32341 from storybookjs/docs-csf-next
kylegach Sep 3, 2025
246e3ef
Merge pull request #32169 from diagramatics/satisfies-as
shilman Sep 5, 2025
7a47f95
Write changelog for 9.1.5 [skip ci]
storybook-bot Sep 5, 2025
b52b74a
Merge branch 'feature/test-syntax-in-csf' into tags-filter-exclude
yannbf Sep 5, 2025
4c348d3
Copy the default included/excluded sets to avoid accidental mutation
ghengeveld Sep 5, 2025
d28127c
Rename defaultSelection to defaultFilterSelection for clarity
ghengeveld Sep 5, 2025
8a65895
Remove some built-in tags that will no longer exist
ghengeveld Sep 5, 2025
7985f43
Revert check icon when not hovering
ghengeveld Sep 5, 2025
67dfbfb
Delay tooltips
ghengeveld Sep 5, 2025
00e53ca
Merge pull request #32417 from storybookjs/tags-filter-exclude
yannbf Sep 5, 2025
8b9cc24
fix children calculation
yannbf Sep 5, 2025
87ddc71
fix children calculation pt. 2
yannbf Sep 5, 2025
3b12d84
Merge pull request #32390 from storybookjs/version-patch-from-9.1.4
shilman Sep 6, 2025
56c04b0
Bump version from "9.1.4" to "9.1.5" [skip ci]
storybook-bot Sep 6, 2025
9c1c6d9
Merge pull request #32356 from storybookjs/vy/sentry-release
vanessayuenn Sep 5, 2025
a9d51fd
Merge pull request #32438 from storybookjs/dannyhw/fix/react-native-w…
shilman Sep 11, 2025
4e32f28
Merge pull request #32439 from storybookjs/norbert/fix-type-event-slo…
ndelangen Sep 12, 2025
11b3884
Write changelog for 9.1.6 [skip ci]
storybook-bot Sep 15, 2025
c69e759
Merge pull request #32344 from storybookjs/shilman/init-version-speci…
ndelangen Sep 11, 2025
aac8a9d
Merge pull request #32448 from storybookjs/shilman/cli-integratino-te…
ndelangen Sep 15, 2025
ce2cc67
Merge pull request #32436 from storybookjs/version-patch-from-9.1.5
ndelangen Sep 15, 2025
2b1bbd2
Update GitHub Actions workflow: change node-version-file quotes and u…
ndelangen Sep 15, 2025
304edc3
Bump version from "9.1.5" to "9.1.6" [skip ci]
storybook-bot Sep 15, 2025
05a4d33
Merge branch 'latest-release' into feature/test-syntax-in-csf
ndelangen Sep 15, 2025
09c03f9
Merge branch 'next' into feature/test-syntax-in-csf
ndelangen Sep 15, 2025
de396c4
fix merge conflict
yannbf Sep 15, 2025
b5d65ed
fix e2e tests
yannbf Sep 15, 2025
9f1fa3f
Merge branch 'next' into feature/test-syntax-in-csf-sb10
yannbf Sep 15, 2025
b868eb1
Special handling for built-in tags
ghengeveld Sep 16, 2025
f3a4f4a
replace tags filter play icon
yannbf Sep 16, 2025
ed673f0
Merge pull request #32481 from storybookjs/built-in-tags-icons
yannbf Sep 16, 2025
25f0f92
Add missing subtype properties
ghengeveld Sep 17, 2025
9183a9b
Change property order
ghengeveld Sep 17, 2025
7913c9d
Remove subtype prop
ghengeveld Sep 17, 2025
ba4758a
Merge pull request #32490 from storybookjs/add-missing-subtype-props
ghengeveld Sep 17, 2025
0bac997
Merge branch 'next' into feature/test-syntax-in-csf-sb10
yannbf Sep 18, 2025
a163b78
Make csf-factories codemod bail on transformation when story is not f…
yannbf Sep 5, 2025
6c7eb65
fix tests
yannbf Sep 18, 2025
8df5a47
Merge pull request #32498 from storybookjs/yann/improve-csf-factories…
yannbf Sep 18, 2025
6aceace
Merge branch 'next' into feature/test-syntax-in-csf-sb10
yannbf Sep 18, 2025
fa3371d
fix types and import path handling
yannbf Sep 19, 2025
9c8103e
Fix focused tests for stories with tests
ghengeveld Sep 19, 2025
3942b0b
Remove tests from children of component, as they are attached to the …
ghengeveld Sep 19, 2025
999aa03
Ensure we show the correct status icon for stories with tests
ghengeveld Sep 19, 2025
9884d89
Merge pull request #32509 from storybookjs/fix-focused-tests
ghengeveld Sep 19, 2025
f31a74c
Merge branch 'next' into feature/test-syntax-in-csf-sb10
yannbf Sep 22, 2025
669680c
fix tests
yannbf Sep 22, 2025
2828dec
improve csf factories codemod
yannbf Sep 22, 2025
f0ce3fe
Fix built-in tags filtering
ghengeveld Sep 22, 2025
a1da280
add E2E tests for filter types
yannbf Sep 23, 2025
0a995fa
add addons field even when there are no addons installed
yannbf Sep 23, 2025
fefa45e
Merge branch 'next' into feature/test-syntax-in-csf-sb10
yannbf Sep 23, 2025
8a83592
only run e2e test in react
yannbf Sep 23, 2025
a25c124
fix keyboard shortcuts on e2e helper
yannbf Sep 23, 2025
2f19c9d
Merge pull request #32526 from storybookjs/fix-built-in-tags-filtering
yannbf Sep 23, 2025
dea672e
Add feature flag
yannbf Sep 23, 2025
bc54bb1
Improve error message
yannbf Sep 23, 2025
3f1f0b0
fix sandboxes
yannbf Sep 23, 2025
6d28374
Fix types
yannbf Sep 24, 2025
2e03469
fix lint
yannbf Sep 24, 2025
5e1c061
enable test syntax in storybook:ui
yannbf Sep 24, 2025
02995a7
provide features via the vitest integration
yannbf Sep 24, 2025
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
2 changes: 1 addition & 1 deletion .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -57,7 +57,7 @@ code/bench-results/
/packs
code/.nx/cache
code/.nx/workspace-data
code/.vite-inspect
.vite-inspect
.nx/cache
.nx/workspace-data
!**/fixtures/**/yarn.lock
Expand Down
1 change: 1 addition & 0 deletions code/.storybook/main.ts
Original file line number Diff line number Diff line change
Expand Up @@ -136,6 +136,7 @@ const config = defineMain({
},
features: {
developmentModeForBuild: true,
experimentalTestSyntax: true,
},
staticDirs: [{ from: './bench/bundle-analyzer', to: '/bundle-analyzer' }],
viteFinal: async (viteConfig, { configType }) => {
Expand Down
13 changes: 8 additions & 5 deletions code/.storybook/preview.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -216,18 +216,21 @@ const decorators = [
* This decorator renders the stories side-by-side, stacked or default based on the theme switcher
* in the toolbar
*/
(StoryFn, { globals, playFunction, args, storyGlobals, parameters }) => {
(StoryFn, { globals, playFunction, testFunction, args, storyGlobals, parameters }) => {
let theme = globals.sb_theme;
let showPlayFnNotice = false;

// this makes the decorator be out of 'phase' with the actually selected theme in the toolbar
// but this is acceptable, I guess
// we need to ensure only a single rendering in chromatic
// a more 'correct' approach would be to set a specific theme global on every story that has a playFunction
if (playFunction && args.autoplay !== false && !(theme === 'light' || theme === 'dark')) {
if (
(testFunction || (playFunction && args.autoplay !== false)) &&
!(theme === 'light' || theme === 'dark')
) {
theme = 'light';
showPlayFnNotice = true;
} else if (isChromatic() && !storyGlobals.sb_theme && !playFunction) {
} else if (isChromatic() && !storyGlobals.sb_theme && !playFunction && !testFunction) {
theme = 'stacked';
}

Expand Down Expand Up @@ -282,8 +285,8 @@ const decorators = [
<>
<PlayFnNotice>
<span>
Detected play function in Chromatic. Rendering only light theme to avoid
multiple play functions in the same story.
Detected play/test function in Chromatic. Rendering only light theme to avoid
multiple play/test functions in the same story.
</span>
</PlayFnNotice>
<div style={{ marginBottom: 20 }} />
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -71,9 +71,11 @@ export class ExternalPreview<TRenderer extends Renderer = Renderer> extends Prev
title,
name,
type: 'story',
subtype: 'story',
};
});

// TODO: We probably need to do something here about story tests
this.onStoriesChanged({ storyIndex: this.storyIndex });

return csfFile;
Expand Down
7 changes: 0 additions & 7 deletions code/addons/docs/src/preview.ts
Original file line number Diff line number Diff line change
@@ -1,12 +1,5 @@
import type { PreparedStory } from 'storybook/internal/types';

import * as tocbot from 'tocbot';

if (!globalThis.__STORYBOOK_UNSAFE_TOCBOT__) {
// Users that load dynamic content need to have a way to refresh the TOC, so we expose the tocbot instance
globalThis.__STORYBOOK_UNSAFE_TOCBOT__ = tocbot.default ?? tocbot;
}

const excludeTags = Object.entries(globalThis.TAGS_OPTIONS ?? {}).reduce(
(acc, entry) => {
const [tag, option] = entry;
Expand Down
1 change: 0 additions & 1 deletion code/addons/docs/src/typings.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,6 @@ declare var __DOCS_CONTEXT__: any;
declare var PREVIEW_URL: any;
declare var LOGLEVEL: 'trace' | 'debug' | 'info' | 'warn' | 'error' | 'silent' | undefined;
declare var TAGS_OPTIONS: import('storybook/internal/types').TagsOptions;
declare var __STORYBOOK_UNSAFE_TOCBOT__: typeof import('tocbot').default;

declare module '*.md';
declare module '*.md?raw';
Original file line number Diff line number Diff line change
Expand Up @@ -310,6 +310,7 @@ export const InSidebarContextMenu: Story = {
entry: {
id: 'story-id-1',
type: 'story',
subtype: 'story',
Copy link
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue

🧩 Analysis chain

Good addition: subtype: 'story' aligns with the new discriminant.

This matches the PR’s subtype model and will help downstream narrowing. Please also ensure all mocked entry objects across stories/fixtures include subtype for consistency; add parentName if the new types expect it.

Run to find entry: objects missing subtype near their definitions:

Optional: add a test-entry variant to exercise the context menu for tests.

export const InSidebarContextMenu_TestEntry: Story = {
  args: {
    ...meta.args,
    testProviderState: 'test-provider-state:succeeded',
    entry: {
      id: 'story-id-1:test-name',
      type: 'story',
      subtype: 'test',
      parent: 'story-id-1',
      name: 'test-name',
      title: 'Example Story',
      tags: ['!autodocs'],
      importPath: './path/to/story',
      prepared: true,
      exportName: 'ExampleStory_testName',
      depth: 2,
      // parentName: 'Example Story', // include if required by types
    },
  },
};

🏁 Script executed:

#!/bin/bash
# List places where an `entry: { ... }` block lacks a `subtype:` within the next ~25 lines
while IFS= read -r hit; do
  file="${hit%%:*}"; line="${hit##*:}"
  block="$(tail -n +$line "$file" | head -n 25)"
  if ! grep -qE '\bsubtype\s*:' <<<"$block"; then
    echo "Missing subtype -> $file:$line"
  fi
done < <(rg -nP --glob 'code/**' 'entry\s*:\s*\{' -t)

Length of output: 458


🏁 Script executed:

#!/bin/bash
set -euo pipefail

# Find `entry: {` blocks under code/** and report those that do not contain `subtype:` within the next 40 lines.
if ! command -v rg >/dev/null 2>&1; then
  echo "ripgrep (rg) not found" >&2
  exit 2
fi

rg --no-heading --hidden --line-number --glob 'code/**' -P 'entry\s*:\s*\{' | while IFS=: read -r file line _; do
  if [ ! -f "$file" ]; then
    echo "File not found: $file" >&2
    continue
  fi
  block="$(tail -n +"$line" "$file" | head -n 40)"
  if ! grep -qE '\bsubtype\s*:' <<<"$block"; then
    echo "Missing subtype -> $file:$line"
  fi
done

Length of output: 204


🏁 Script executed:

#!/bin/sh
# Robust search for `entry: {` blocks missing `subtype:` within the next 40 lines.
# Uses Python to avoid prior shell/rg/process-substitution issues.

if [ ! -d "code" ]; then
  echo "No 'code' directory found; aborting." >&2
  exit 0
fi

python3 - <<'PY'
import os, re, sys
root = "code"
pat = re.compile(r'entry\s*:\s*\{', re.MULTILINE)
sub_re = re.compile(r'\bsubtype\s*:', re.IGNORECASE)

for dirpath, dirnames, filenames in os.walk(root):
    dirnames[:] = [d for d in dirnames if d not in ("node_modules", ".git", "dist", "build")]
    for fn in filenames:
        path = os.path.join(dirpath, fn)
        try:
            with open(path, "r", encoding="utf-8", errors="replace") as f:
                txt = f.read()
        except Exception:
            continue
        for m in pat.finditer(txt):
            start = m.start()
            lineno = txt.count("\n", 0, start) + 1
            block = "\n".join(txt[start:].splitlines()[:40])
            if not sub_re.search(block):
                print(f"Missing subtype -> {path}:{lineno}")
PY

Length of output: 336


Add missing subtype fields to mocked entry objects

Scan found these test fixtures lacking subtype. Add the appropriate subtype values (and parentName where the types require):

  • code/core/src/core-server/utils/tests/remove-mdx-stories.test.ts:194
  • code/core/src/core-server/utils/tests/remove-mdx-stories.test.ts:219
  • code/lib/core-webpack/src/merge-webpack-config.test.ts:9
  • code/lib/core-webpack/src/merge-webpack-config.test.ts:40
🤖 Prompt for AI Agents
In code/addons/vitest/src/components/TestProviderRender.stories.tsx around line
313 and the other test fixture files referenced
(code/core/src/core-server/utils/__tests__/remove-mdx-stories.test.ts at lines
~194 and ~219, and code/lib/core-webpack/src/merge-webpack-config.test.ts at
lines ~9 and ~40), the mocked `entry` objects are missing the required `subtype`
property (and `parentName` where the entry type expects it). Update each mocked
`entry` object to include the correct `subtype` value for that fixture (e.g.,
'story' or the appropriate subtype) and add `parentName` for entry types that
require it so the objects match the expected type shape used by the code under
test.

name: 'Example Story',
tags: [],
title: 'Example Story',
Expand Down
18 changes: 11 additions & 7 deletions code/addons/vitest/src/components/TestProviderRender.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -242,15 +242,19 @@ export const TestProviderRender: FC<TestProviderRenderProps> = ({
<IconButton
aria-label="Start test run"
size="medium"
onClick={() =>
onClick={() => {
let storyIds;
if (entry) {
// Don't send underlying child test ids when running on a story
// Vitest Manager already handles running the underlying tests
storyIds =
entry.type === 'story' ? [entry.id] : api.findAllLeafStoryIds(entry.id);
}
store.send({
type: 'TRIGGER_RUN',
payload: {
storyIds: entry ? api.findAllLeafStoryIds(entry.id) : undefined,
triggeredBy: entry ? entry.type : 'global',
},
})
}
payload: { storyIds, triggeredBy: entry?.type ?? 'global' },
});
}}
>
<PlayHollowIcon />
</IconButton>
Expand Down
70 changes: 69 additions & 1 deletion code/addons/vitest/src/node/test-manager.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@ import path from 'pathe';
import { STATUS_TYPE_ID_A11Y, STATUS_TYPE_ID_COMPONENT_TEST, storeOptions } from '../constants';
import type { StoreEvent, StoreState } from '../types';
import { TestManager, type TestManagerOptions } from './test-manager';
import { DOUBLE_SPACES } from './vitest-manager';

const setTestNamePattern = vi.hoisted(() => vi.fn());
const vitest = vi.hoisted(() => ({
Expand Down Expand Up @@ -103,6 +104,7 @@ global.fetch = vi.fn().mockResolvedValue({
entries: {
'story--one': {
type: 'story',
subtype: 'story',
id: 'story--one',
name: 'One',
title: 'story/one',
Expand All @@ -111,12 +113,32 @@ global.fetch = vi.fn().mockResolvedValue({
},
'another--one': {
type: 'story',
subtype: 'story',
id: 'another--one',
name: 'One',
title: 'another/one',
importPath: 'path/to/another/file',
tags: ['test'],
},
'parent--story': {
type: 'story',
subtype: 'story',
id: 'parent--story',
name: 'Parent story',
title: 'parent/story',
importPath: 'path/to/parent/file',
tags: ['test'],
},
'parent--story:test': {
type: 'story',
subtype: 'test',
id: 'parent--story:test',
name: 'Test name',
title: 'parent/story',
parent: 'parent--story',
importPath: 'path/to/parent/file',
tags: ['test', 'test-fn'],
},
},
} as StoryIndex)
),
Expand Down Expand Up @@ -184,10 +206,56 @@ describe('TestManager', () => {
triggeredBy: 'global',
},
});
expect(setTestNamePattern).toHaveBeenCalledWith(/^One$/);
expect(setTestNamePattern).toHaveBeenCalledWith(new RegExp(`^One$`));
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(new RegExp(`^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(
new RegExp(`^Parent story${DOUBLE_SPACES} Test name$`)
);
Comment on lines +239 to +242
Copy link
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue

Off‑by‑one space in regex (3 spaces instead of 2).

DOUBLE_SPACES already contains two spaces; the literal space after it makes three. This will cause a mismatch.

Apply this fix:

-      new RegExp(`^Parent story${DOUBLE_SPACES} Test name$`)
+      new RegExp(`^Parent story${DOUBLE_SPACES}Test name$`)
📝 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
// regex should be Parent Story Name + Test Name
expect(setTestNamePattern).toHaveBeenCalledWith(
new RegExp(`^Parent story${DOUBLE_SPACES} Test name$`)
);
// regex should be Parent Story Name + Test Name
expect(setTestNamePattern).toHaveBeenCalledWith(
new RegExp(`^Parent story${DOUBLE_SPACES}Test name$`)
);
🧰 Tools
🪛 ast-grep (0.38.6)

[warning] 240-240: Regular expression constructed from variable input detected. This can lead to Regular Expression Denial of Service (ReDoS) attacks if the variable contains malicious patterns. Use libraries like 'recheck' to validate regex safety or use static patterns.
Context: new RegExp(^Parent story${DOUBLE_SPACES} Test name$)
Note: [CWE-1333] Inefficient Regular Expression Complexity [REFERENCES]
- https://owasp.org/www-community/attacks/Regular_expression_Denial_of_Service_-_ReDoS
- https://cwe.mitre.org/data/definitions/1333.html

(regexp-from-variable)

🤖 Prompt for AI Agents
In code/addons/vitest/src/node/test-manager.test.ts around lines 239 to 242, the
regex passed to setTestNamePattern includes an extra literal space after
DOUBLE_SPACES causing three spaces instead of the intended two; remove the
trailing literal space so the RegExp concatenation uses only DOUBLE_SPACES
between "Parent story" and "Test name" (i.e., stop appending an additional space
after DOUBLE_SPACES when building the pattern).

});

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',
},
});
expect(setTestNamePattern).toHaveBeenCalledWith(new RegExp(`^Parent story${DOUBLE_SPACES}`));
});

it('should restart Vitest before a test run if coverage is enabled', async () => {
const testManager = await TestManager.start(options);
expect(createVitest).toHaveBeenCalledTimes(1);
Expand Down
54 changes: 48 additions & 6 deletions code/addons/vitest/src/node/vitest-manager.ts
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,6 @@ import * as find from 'empathic/find';
import path, { dirname, join, normalize } from 'pathe';
import slash from 'slash';

import { resolvePackageDir } from '../../../../core/src/shared/utils/module';
import { COVERAGE_DIRECTORY } from '../constants';
import { log } from '../logger';
import type { TriggerRunEvent } from '../types';
Expand All @@ -30,6 +29,17 @@ const VITEST_WORKSPACE_FILE_EXTENSION = ['ts', 'js', 'json'];
// We have to tell Vitest that it runs as part of Storybook
process.env.VITEST_STORYBOOK = 'true';

/**
* The Storybook vitest plugin adds double space characters so that it's possible to do a regex for
* all test run use cases. Otherwise, if there were two unrelated stories like "Primary Button" and
* "Primary Button Mobile", once you run tests for "Primary Button" and its children it would also
* match "Primary Button Mobile". 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, but because we use
* double spaces, we circumvent the issue.
*/
export const DOUBLE_SPACES = ' ';
const getTestName = (name: string) => `${name}${DOUBLE_SPACES}`;

export class VitestManager {
vitest: Vitest | null = null;

Expand Down Expand Up @@ -168,7 +178,7 @@ export class VitestManager {
});
}

private async fetchStories(requestStoryIds?: string[]) {
private async fetchStories(requestStoryIds?: string[]): Promise<StoryIndexEntry[]> {
const indexUrl = this.testManager.store.getState().indexUrl;
if (!indexUrl) {
throw new Error(
Expand Down Expand Up @@ -264,18 +274,50 @@ 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 storyName = stories[0].name;
const regex = new RegExp(`^${storyName.replace(/[.*+?^${}()|[\]\\]/g, '\\$&')}$`);
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;
let regex: RegExp;

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
const parentName = getTestName(selectedStory.name);
regex = new RegExp(`^${parentName}`);
} 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 + space + 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 = getTestName(parentStory.name);
regex = new RegExp(`^${parentName} ${storyName}$`);
} else {
// Use case 3: Single story run on a story without tests, should be exact match of story name
regex = new RegExp(`^${storyName}$`);
}
this.vitest!.setGlobalTestNamePattern(regex);
Comment on lines +277 to 315
Copy link
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue

Escape user/story names before building RegExp (ReDoS/invalid-pattern hardening)

Story names can contain regex meta characters. Escape before interpolation to avoid unexpected matches or runtime errors.

-      if (isParentStory) {
+      const escapeForRegex = (s: string) => s.replace(/[\\^$.*+?()[\]{}|]/g, '\\$&');
+      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
         const parentName = getTestName(selectedStory.name);
-        regex = new RegExp(`^${parentName}`);
+        regex = new RegExp(`^${escapeForRegex(parentName)}`);
       } else if (hasParentStory) {
@@
-        const parentName = getTestName(parentStory.name);
-        regex = new RegExp(`^${parentName} ${storyName}$`);
+        const parentName = getTestName(parentStory.name);
+        regex = new RegExp(`^${escapeForRegex(parentName)} ${escapeForRegex(storyName)}$`);
       } else {
         // Use case 3: Single story run on a story without tests, should be exact match of story name
-        regex = new RegExp(`^${storyName}$`);
+        regex = new RegExp(`^${escapeForRegex(storyName)}$`);
       }
📝 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
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 storyName = stories[0].name;
const regex = new RegExp(`^${storyName.replace(/[.*+?^${}()|[\]\\]/g, '\\$&')}$`);
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;
let regex: RegExp;
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
const parentName = getTestName(selectedStory.name);
regex = new RegExp(`^${parentName}`);
} 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 + space + 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 = getTestName(parentStory.name);
regex = new RegExp(`^${parentName} ${storyName}$`);
} else {
// Use case 3: Single story run on a story without tests, should be exact match of story name
regex = new RegExp(`^${storyName}$`);
}
this.vitest!.setGlobalTestNamePattern(regex);
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 = filteredStories.find((story) => story.id === runPayload.storyIds?.[0]);
if (!selectedStory) {
throw new Error(`Story ${runPayload.storyIds?.[0]} not found`);
}
const storyName = selectedStory.name;
let regex: RegExp;
const isParentStory = allStories.some((story) => selectedStory.id === story.parent);
const hasParentStory = allStories.some((story) => selectedStory.parent === story.id);
const escapeForRegex = (s: string) => s.replace(/[\\^$.*+?()[\]{}|]/g, '\\$&');
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
const parentName = getTestName(selectedStory.name);
regex = new RegExp(`^${escapeForRegex(parentName)}`);
} 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 + space + 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 = getTestName(parentStory.name);
regex = new RegExp(`^${escapeForRegex(parentName)} ${escapeForRegex(storyName)}$`);
} else {
// Use case 3: Single story run on a story without tests, should be exact match of story name
regex = new RegExp(`^${escapeForRegex(storyName)}$`);
}
this.vitest!.setGlobalTestNamePattern(regex);
🧰 Tools
🪛 ast-grep (0.38.6)

[warning] 300-300: Regular expression constructed from variable input detected. This can lead to Regular Expression Denial of Service (ReDoS) attacks if the variable contains malicious patterns. Use libraries like 'recheck' to validate regex safety or use static patterns.
Context: new RegExp(^${parentName})
Note: [CWE-1333] Inefficient Regular Expression Complexity [REFERENCES]
- https://owasp.org/www-community/attacks/Regular_expression_Denial_of_Service_-_ReDoS
- https://cwe.mitre.org/data/definitions/1333.html

(regexp-from-variable)


[warning] 310-310: Regular expression constructed from variable input detected. This can lead to Regular Expression Denial of Service (ReDoS) attacks if the variable contains malicious patterns. Use libraries like 'recheck' to validate regex safety or use static patterns.
Context: new RegExp(^${parentName} ${storyName}$)
Note: [CWE-1333] Inefficient Regular Expression Complexity [REFERENCES]
- https://owasp.org/www-community/attacks/Regular_expression_Denial_of_Service_-_ReDoS
- https://cwe.mitre.org/data/definitions/1333.html

(regexp-from-variable)


[warning] 313-313: Regular expression constructed from variable input detected. This can lead to Regular Expression Denial of Service (ReDoS) attacks if the variable contains malicious patterns. Use libraries like 'recheck' to validate regex safety or use static patterns.
Context: new RegExp(^${storyName}$)
Note: [CWE-1333] Inefficient Regular Expression Complexity [REFERENCES]
- https://owasp.org/www-community/attacks/Regular_expression_Denial_of_Service_-_ReDoS
- https://cwe.mitre.org/data/definitions/1333.html

(regexp-from-variable)

🤖 Prompt for AI Agents
In code/addons/vitest/src/node/vitest-manager.ts around lines 278 to 316 the
code interpolates story names directly into new RegExp(...) which allows regex
meta-characters in user/story names to produce invalid patterns or ReDoS; fix by
escaping storyName and parentName with a proper escapeRegExp helper (escape
characters like . * + ? ^ $ { } ( ) | [ ] \ / by prefixing with \) and use the
escaped values when constructing the RegExp patterns (preserve the ^ anchors and
the literal space between parent and story), and wrap RegExp construction in a
try/catch to surface a clear error if pattern creation still fails.

}

const { filteredTestSpecifications, filteredStoryIds } = this.filterTestSpecifications(
testSpecifications,
stories
filteredStories
);

this.testManager.store.setState((s) => ({
Expand Down
3 changes: 3 additions & 0 deletions code/addons/vitest/src/vitest-plugin/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -144,6 +144,7 @@ export const storybookTest = async (options?: UserOptions): Promise<Plugin[]> =>
previewLevelTags,
core,
extraOptimizeDeps,
features,
] = await Promise.all([
getStoryGlobsAndFiles(presets, directories),
presets.apply('framework', undefined),
Expand All @@ -153,6 +154,7 @@ export const storybookTest = async (options?: UserOptions): Promise<Plugin[]> =>
extractTagsFromPreview(finalOptions.configDir),
presets.apply('core'),
presets.apply('optimizeViteDeps', []),
presets.apply('features', {}),
]);

const pluginsToIgnore = [
Expand Down Expand Up @@ -332,6 +334,7 @@ export const storybookTest = async (options?: UserOptions): Promise<Plugin[]> =>
...(frameworkName?.includes('vue3')
? { __VUE_PROD_HYDRATION_MISMATCH_DETAILS__: 'false' }
: {}),
FEATURES: JSON.stringify(features),
},
};

Expand Down
29 changes: 22 additions & 7 deletions code/addons/vitest/src/vitest-plugin/test-utils.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import { type RunnerTask, type TaskMeta, type TestContext } from 'vitest';

import type { ComponentAnnotations, ComposedStoryFn } from 'storybook/internal/types';
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';
import { type Report, composeStory, getCsfFactoryAnnotations } from 'storybook/preview-api';
Expand Down Expand Up @@ -32,14 +33,24 @@ export const convertToFilePath = (url: string): string => {

export const testStory = (
exportName: string,
story: ComposedStoryFn,
meta: ComponentAnnotations,
skipTags: string[]
story: ComposedStoryFn | Story<Renderer>,
meta: ComponentAnnotations | Meta<Renderer>,
skipTags: string[],
storyId: string,
testName?: string
) => {
return async (context: TestContext & { story: ComposedStoryFn }) => {
const annotations = getCsfFactoryAnnotations(story, meta);

const test =
isStory(story) && testName
? getStoryChildren(story).find((child) => child.input.name === testName)
: undefined;

const storyAnnotations = test ? test.input : annotations.story;

Comment on lines +45 to +51
Copy link
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue

Fail fast when a specific testName is not found.

Silently running the base story when a test is missing is misleading.

-    const test =
-      isStory(story) && testName
-        ? getStoryChildren(story).find((child) => child.input.name === testName)
-        : undefined;
+    const test =
+      isStory(story) && testName
+        ? getStoryChildren(story).find((child) => child.input.name === testName)
+        : undefined;
+    if (isStory(story) && testName && !test) {
+      context.skip();
+      return;
+    }

Committable suggestion skipped: line range outside the PR's diff.

🤖 Prompt for AI Agents
In code/addons/vitest/src/vitest-plugin/test-utils.ts around lines 45 to 51,
when a specific testName is provided but not found the code falls back silently
to the base story; change this to fail fast by checking if testName is truthy
and story is a story but the lookup returned undefined, and throw a clear Error
(including story id/name and requested testName) instead of falling back; then
set storyAnnotations to test.input only after that check so the code never
silently runs the base story for a missing named test.

const composedStory = composeStory(
annotations.story,
storyAnnotations,
annotations.meta!,
{ initialGlobals: (await getInitialGlobals?.()) ?? {} },
annotations.preview ?? globalThis.globalProjectAnnotations,
Expand All @@ -55,10 +66,14 @@ export const testStory = (
const _task = context.task as RunnerTask & {
meta: TaskMeta & { storyId: string; reports: Report[] };
};
_task.meta.storyId = composedStory.id;

// 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;

await setViewport(composedStory.parameters, composedStory.globals);
await composedStory.run();

await composedStory.run(undefined);

_task.meta.reports = composedStory.reporting.reports;
};
Expand Down
Loading
Loading