diff --git a/CHANGELOG.prerelease.md b/CHANGELOG.prerelease.md index 85ebf174dc26..1025f83ccc00 100644 --- a/CHANGELOG.prerelease.md +++ b/CHANGELOG.prerelease.md @@ -1,3 +1,10 @@ +## 10.3.0-alpha.6 + +- Addon-Vitest: Improve config file detection in monorepos - [#33814](https://github.com/storybookjs/storybook/pull/33814), thanks @valentinpalkovic! +- Addon-Vitest: Support Vitest canaries - [#33833](https://github.com/storybookjs/storybook/pull/33833), thanks @valentinpalkovic! +- Builder-Vite: Update dependencies react-vite framework - [#33810](https://github.com/storybookjs/storybook/pull/33810), thanks @valentinpalkovic! +- Next.js: Fix Link component override in appDirectory configuration - [#31251](https://github.com/storybookjs/storybook/pull/31251), thanks @yatishgoel! + ## 10.3.0-alpha.5 - Builder-Vite: Use relative path for mocker entry in production builds - [#33792](https://github.com/storybookjs/storybook/pull/33792), thanks @DukeDeSouth! diff --git a/code/addons/vitest/src/node/vitest-manager.ts b/code/addons/vitest/src/node/vitest-manager.ts index 8cf3e0f15e7e..f787556ed4b4 100644 --- a/code/addons/vitest/src/node/vitest-manager.ts +++ b/code/addons/vitest/src/node/vitest-manager.ts @@ -1,4 +1,4 @@ -import { existsSync } from 'node:fs'; +import { existsSync, readFileSync } from 'node:fs'; import type { CoverageOptions, @@ -13,7 +13,8 @@ import { Tag } from 'storybook/internal/core-server'; import type { StoryId, StoryIndex, StoryIndexEntry } from 'storybook/internal/types'; import * as find from 'empathic/find'; -import path, { dirname, join, normalize } from 'pathe'; +import * as walk from 'empathic/walk'; +import path, { dirname, join, normalize, resolve } from 'pathe'; // eslint-disable-next-line depend/ban-dependencies import slash from 'slash'; @@ -75,19 +76,53 @@ export class VitestManager { : { enabled: false } ) as CoverageOptions; - const vitestWorkspaceConfig = find.any( - [ - ...VITEST_WORKSPACE_FILE_EXTENSION.map((ext) => `vitest.workspace.${ext}`), - ...VITEST_CONFIG_FILE_EXTENSIONS.map((ext) => `vitest.config.${ext}`), - ], - { last: getProjectRoot() } - ); + // In monorepos, the Storybook configDir (e.g. packages/web-app/.storybook) identifies + // the sub-package. We start the Vitest config search from its parent (the package root) + // and traverse upward to the project root, so configs in both sub-packages and the + // monorepo root are found. Without this, find.any defaults to process.cwd() which may + // be the monorepo root and would miss sub-package configs entirely. + const configDir = this.testManager.storybookOptions.configDir; + const packageRoot = configDir ? dirname(resolve(configDir)) : undefined; + + const configFiles = [ + ...VITEST_WORKSPACE_FILE_EXTENSION.map((ext) => `vitest.workspace.${ext}`), + ...VITEST_CONFIG_FILE_EXTENSIONS.flatMap((ext) => [ + `vitest.config.${ext}`, + `vite.config.${ext}`, + ]), + ]; + + const potentialConfigFileLocations = walk.up(packageRoot || process.cwd(), { + last: getProjectRoot(), + }); + + let vitestWorkspaceConfig: string | undefined; + let firstVitestConfig: string | undefined; + + for (const location of potentialConfigFileLocations) { + for (const file of configFiles) { + const maybe = find.any([file], { cwd: location, last: getProjectRoot() }); + if (maybe && existsSync(maybe)) { + firstVitestConfig ??= maybe; + const content = readFileSync(maybe, 'utf8'); + if (content.includes('storybookTest') || content.includes('@storybook/addon-vitest')) { + vitestWorkspaceConfig = dirname(maybe); + break; + } + } + } + if (vitestWorkspaceConfig) { + break; + } + } const projectName = 'storybook:' + process.env.STORYBOOK_CONFIG_DIR; + const vitestConfigFallbackLocation = firstVitestConfig || packageRoot || process.cwd(); + try { this.vitest = await createVitest('test', { - root: vitestWorkspaceConfig ? dirname(vitestWorkspaceConfig) : process.cwd(), + root: vitestWorkspaceConfig ?? vitestConfigFallbackLocation, watch: true, passWithNoTests: false, project: [projectName], diff --git a/code/core/src/cli/AddonVitestService.test.ts b/code/core/src/cli/AddonVitestService.test.ts index 6c2f9fa19d2a..17379adac94a 100644 --- a/code/core/src/cli/AddonVitestService.test.ts +++ b/code/core/src/cli/AddonVitestService.test.ts @@ -168,6 +168,28 @@ describe('AddonVitestService', () => { expect(result.reasons).toBeUndefined(); }); + it('should return compatible when vitest prerelease >= 3.0.0', async () => { + vi.mocked(mockPackageManager.getInstalledVersion) + .mockResolvedValueOnce('3.0.0-beta.1') // vitest + .mockResolvedValueOnce(null); // msw + + const result = await service.validatePackageVersions(); + + expect(result.compatible).toBe(true); + expect(result.reasons).toBeUndefined(); + }); + + it('should return compatible when vitest canary is used', async () => { + vi.mocked(mockPackageManager.getInstalledVersion) + .mockResolvedValueOnce('0.0.0-833c515fa25cef20905a7f9affb156dfa6f151ab') // vitest + .mockResolvedValueOnce(null); // msw + + const result = await service.validatePackageVersions(); + + expect(result.compatible).toBe(true); + expect(result.reasons).toBeUndefined(); + }); + it('should return compatible when vitest >=4.0.0', async () => { vi.mocked(mockPackageManager.getInstalledVersion) .mockResolvedValueOnce('4.0.0') // vitest diff --git a/code/core/src/cli/AddonVitestService.ts b/code/core/src/cli/AddonVitestService.ts index d959c843c718..2212f74b9b4d 100644 --- a/code/core/src/cli/AddonVitestService.ts +++ b/code/core/src/cli/AddonVitestService.ts @@ -243,8 +243,9 @@ export class AddonVitestService { // Check Vitest version (>=3.0.0 - stricter requirement from postinstall) const vitestVersionSpecifier = await this.packageManager.getInstalledVersion('vitest'); const coercedVitestVersion = vitestVersionSpecifier ? coerce(vitestVersionSpecifier) : null; + const isCanary = coercedVitestVersion?.version.startsWith('0.0.0') ?? false; - if (coercedVitestVersion && !satisfies(coercedVitestVersion, '>=3.0.0')) { + if (coercedVitestVersion && !satisfies(coercedVitestVersion, '>=3.0.0') && !isCanary) { reasons.push( `The addon requires Vitest 3.0.0 or higher. You are currently using ${vitestVersionSpecifier}.` ); diff --git a/code/frameworks/nextjs/build-config.ts b/code/frameworks/nextjs/build-config.ts index e7c8f55e43c4..bcc08973d6d9 100644 --- a/code/frameworks/nextjs/build-config.ts +++ b/code/frameworks/nextjs/build-config.ts @@ -28,6 +28,10 @@ const config: BuildEntries = { exportEntries: ['./navigation.mock'], entryPoint: './src/export-mocks/navigation/index.ts', }, + { + exportEntries: ['./link.mock'], + entryPoint: './src/export-mocks/link/index.tsx', + }, { exportEntries: ['./router.mock'], entryPoint: './src/export-mocks/router/index.ts', diff --git a/code/frameworks/nextjs/package.json b/code/frameworks/nextjs/package.json index a291876de2c8..1761e5d51890 100644 --- a/code/frameworks/nextjs/package.json +++ b/code/frameworks/nextjs/package.json @@ -47,6 +47,11 @@ "./image-context": "./dist/image-context.js", "./images/next-image": "./dist/images/next-image.js", "./images/next-legacy-image": "./dist/images/next-legacy-image.js", + "./link.mock": { + "types": "./dist/export-mocks/link/index.d.ts", + "code": "./src/export-mocks/link/index.tsx", + "default": "./dist/export-mocks/link/index.js" + }, "./navigation.mock": { "types": "./dist/export-mocks/navigation/index.d.ts", "code": "./src/export-mocks/navigation/index.ts", diff --git a/code/frameworks/nextjs/src/export-mocks/link/index.tsx b/code/frameworks/nextjs/src/export-mocks/link/index.tsx new file mode 100644 index 000000000000..2237a67f7cc6 --- /dev/null +++ b/code/frameworks/nextjs/src/export-mocks/link/index.tsx @@ -0,0 +1,45 @@ +import React from 'react'; + +import { fn } from 'storybook/test'; + +const linkAction = fn().mockName('next/link::Link'); + +const MockLink = React.forwardRef(function MockLink( + { + href, + as: _as, + replace, + scroll, + shallow, + prefetch, + passHref, + legacyBehavior, + locale, + onClick, + children, + ...rest + }, + ref +) { + const hrefString = + typeof href === 'object' + ? `${href.pathname || ''}${href.query ? '?' + new URLSearchParams(href.query).toString() : ''}${href.hash || ''}` + : href; + + const handleClick = (e: React.MouseEvent) => { + e.preventDefault(); + onClick?.(e); + linkAction(hrefString, { replace, scroll, shallow, prefetch, locale }); + }; + + return ( + + {children} + + ); +}); + +MockLink.displayName = 'NextLink'; + +export default MockLink; +export { MockLink as Link }; diff --git a/code/frameworks/nextjs/src/export-mocks/webpack.ts b/code/frameworks/nextjs/src/export-mocks/webpack.ts index b5a813535737..9daaecee3a76 100644 --- a/code/frameworks/nextjs/src/export-mocks/webpack.ts +++ b/code/frameworks/nextjs/src/export-mocks/webpack.ts @@ -7,6 +7,7 @@ const mapping = { 'next/navigation': '@storybook/nextjs/navigation.mock', 'next/router': '@storybook/nextjs/router.mock', 'next/cache': '@storybook/nextjs/cache.mock', + 'next/link': '@storybook/nextjs/link.mock', ...getCompatibilityAliases(), }; diff --git a/code/frameworks/react-vite/package.json b/code/frameworks/react-vite/package.json index 3c65238df2db..ee077546099d 100644 --- a/code/frameworks/react-vite/package.json +++ b/code/frameworks/react-vite/package.json @@ -52,7 +52,7 @@ "!src/**/*" ], "dependencies": { - "@joshwooding/vite-plugin-react-docgen-typescript": "^0.6.3", + "@joshwooding/vite-plugin-react-docgen-typescript": "^0.6.4", "@rollup/pluginutils": "^5.0.2", "@storybook/builder-vite": "workspace:*", "@storybook/react": "workspace:*", diff --git a/code/package.json b/code/package.json index 25f738268513..a5869164b40d 100644 --- a/code/package.json +++ b/code/package.json @@ -220,5 +220,6 @@ "Dependency Upgrades" ] ] - } + }, + "deferredNextVersion": "10.3.0-alpha.6" } diff --git a/docs/versions/next.json b/docs/versions/next.json index 5e3e56283303..ab12a3d18ed7 100644 --- a/docs/versions/next.json +++ b/docs/versions/next.json @@ -1 +1 @@ -{"version":"10.3.0-alpha.5","info":{"plain":"- Builder-Vite: Use relative path for mocker entry in production builds - [#33792](https://github.com/storybookjs/storybook/pull/33792), thanks @DukeDeSouth!\n- CLI: Support addon-vitest setup when --skip-install is passed - [#33718](https://github.com/storybookjs/storybook/pull/33718), thanks @valentinpalkovic!\n- CSF: Fix cross-file story imports in csf-factories codemod - [#33723](https://github.com/storybookjs/storybook/pull/33723), thanks @yatishgoel!\n- Compile: reduce VCPUs for CI check task from 4 to 3 - [#33822](https://github.com/storybookjs/storybook/pull/33822), thanks @valentinpalkovic!\n- Core: Ignore empty files when indexing - [#33782](https://github.com/storybookjs/storybook/pull/33782), thanks @JReinhold!\n- Globals: Repair dynamicTitle: false for user-defined tools - [#33284](https://github.com/storybookjs/storybook/pull/33284), thanks @ia319!\n- Logger: Honor --loglevel for npmlog output - [#33776](https://github.com/storybookjs/storybook/pull/33776), thanks @LouisLau-art!\n- Telemetry: Add Expo metaframework - [#33783](https://github.com/storybookjs/storybook/pull/33783), thanks @copilot-swe-agent!\n- Telemetry: Add init exit event - [#33773](https://github.com/storybookjs/storybook/pull/33773), thanks @valentinpalkovic!\n- Telemetry: Add share events - [#33766](https://github.com/storybookjs/storybook/pull/33766), thanks @ndelangen!\n- Test: Update event creation logic in user-event package - [#33787](https://github.com/storybookjs/storybook/pull/33787), thanks @valentinpalkovic!\n- Viewport: Skip viewport validation before parameters load - [#33794](https://github.com/storybookjs/storybook/pull/33794), thanks @ia319!"}} +{"version":"10.3.0-alpha.6","info":{"plain":"- Addon-Vitest: Improve config file detection in monorepos - [#33814](https://github.com/storybookjs/storybook/pull/33814), thanks @valentinpalkovic!\n- Addon-Vitest: Support Vitest canaries - [#33833](https://github.com/storybookjs/storybook/pull/33833), thanks @valentinpalkovic!\n- Builder-Vite: Update dependencies react-vite framework - [#33810](https://github.com/storybookjs/storybook/pull/33810), thanks @valentinpalkovic!\n- Next.js: Fix Link component override in appDirectory configuration - [#31251](https://github.com/storybookjs/storybook/pull/31251), thanks @yatishgoel!"}} \ No newline at end of file diff --git a/yarn.lock b/yarn.lock index 7c29f8e7bba5..ab9fa385dbad 100644 --- a/yarn.lock +++ b/yarn.lock @@ -3301,12 +3301,12 @@ __metadata: languageName: node linkType: hard -"@isaacs/brace-expansion@npm:^5.0.0": - version: 5.0.0 - resolution: "@isaacs/brace-expansion@npm:5.0.0" +"@isaacs/brace-expansion@npm:^5.0.1": + version: 5.0.1 + resolution: "@isaacs/brace-expansion@npm:5.0.1" dependencies: "@isaacs/balanced-match": "npm:^4.0.1" - checksum: 10c0/b4d4812f4be53afc2c5b6c545001ff7a4659af68d4484804e9d514e183d20269bb81def8682c01a22b17c4d6aed14292c8494f7d2ac664e547101c1a905aa977 + checksum: 10c0/e5d67c7bbf1f17b88132a35bc638af306d48acbb72810d48fa6e6edd8ab375854773108e8bf70f021f7ef6a8273455a6d1f0c3b5aa2aff06ce7894049ab77fb8 languageName: node linkType: hard @@ -3395,11 +3395,11 @@ __metadata: languageName: node linkType: hard -"@joshwooding/vite-plugin-react-docgen-typescript@npm:^0.6.3": - version: 0.6.3 - resolution: "@joshwooding/vite-plugin-react-docgen-typescript@npm:0.6.3" +"@joshwooding/vite-plugin-react-docgen-typescript@npm:^0.6.4": + version: 0.6.4 + resolution: "@joshwooding/vite-plugin-react-docgen-typescript@npm:0.6.4" dependencies: - glob: "npm:^11.1.0" + glob: "npm:^13.0.1" react-docgen-typescript: "npm:^2.2.2" peerDependencies: typescript: ">= 4.3.x" @@ -3407,7 +3407,7 @@ __metadata: peerDependenciesMeta: typescript: optional: true - checksum: 10c0/e68d2884235b8290673c17a13bc303a088feba6ce0a275ab0778b50e90b967f5dffdcf71ed3197e9cdf07607594a9cb2a86e3ea6e4eb8962b50d61078107bac3 + checksum: 10c0/73149b2d41d5b8eff7dfe4d037a6903fe4123ae46f3928d88535020539f44159c4ea1b342e6a77d4c14219f2f743fea0ef96e81279cce8b6d247dc4d582e27ed languageName: node linkType: hard @@ -8371,7 +8371,7 @@ __metadata: version: 0.0.0-use.local resolution: "@storybook/react-vite@workspace:code/frameworks/react-vite" dependencies: - "@joshwooding/vite-plugin-react-docgen-typescript": "npm:^0.6.3" + "@joshwooding/vite-plugin-react-docgen-typescript": "npm:^0.6.4" "@rollup/pluginutils": "npm:^5.0.2" "@storybook/builder-vite": "workspace:*" "@storybook/react": "workspace:*" @@ -17605,7 +17605,7 @@ __metadata: languageName: node linkType: hard -"foreground-child@npm:^3.1.0, foreground-child@npm:^3.3.1": +"foreground-child@npm:^3.1.0": version: 3.3.1 resolution: "foreground-child@npm:3.3.1" dependencies: @@ -18215,30 +18215,14 @@ __metadata: languageName: node linkType: hard -"glob@npm:^11.1.0": - version: 11.1.0 - resolution: "glob@npm:11.1.0" +"glob@npm:^13.0.0, glob@npm:^13.0.1": + version: 13.0.1 + resolution: "glob@npm:13.0.1" dependencies: - foreground-child: "npm:^3.3.1" - jackspeak: "npm:^4.1.1" - minimatch: "npm:^10.1.1" + minimatch: "npm:^10.1.2" minipass: "npm:^7.1.2" - package-json-from-dist: "npm:^1.0.0" path-scurry: "npm:^2.0.0" - bin: - glob: dist/esm/bin.mjs - checksum: 10c0/1ceae07f23e316a6fa74581d9a74be6e8c2e590d2f7205034dd5c0435c53f5f7b712c2be00c3b65bf0a49294a1c6f4b98cd84c7637e29453b5aa13b79f1763a2 - languageName: node - linkType: hard - -"glob@npm:^13.0.0": - version: 13.0.0 - resolution: "glob@npm:13.0.0" - dependencies: - minimatch: "npm:^10.1.1" - minipass: "npm:^7.1.2" - path-scurry: "npm:^2.0.0" - checksum: 10c0/8e2f5821f3f7c312dd102e23a15b80c79e0837a9872784293ba2e15ec73b3f3749a49a42a31bfcb4e52c84820a474e92331c2eebf18819d20308f5c33876630a + checksum: 10c0/af7b863dec8dff74f61d7d6e53104e1f6bbdd482157a196cade8ed857481e876ec35181b38a059b2a7b93ea3b08248f4ff0792fef6dc91814fd5097a716f48e4 languageName: node linkType: hard @@ -20249,15 +20233,6 @@ __metadata: languageName: node linkType: hard -"jackspeak@npm:^4.1.1": - version: 4.1.1 - resolution: "jackspeak@npm:4.1.1" - dependencies: - "@isaacs/cliui": "npm:^8.0.2" - checksum: 10c0/84ec4f8e21d6514db24737d9caf65361511f75e5e424980eebca4199f400874f45e562ac20fa8aeb1dd20ca2f3f81f0788b6e9c3e64d216a5794fd6f30e0e042 - languageName: node - linkType: hard - "jake@npm:^10.8.5": version: 10.8.7 resolution: "jake@npm:10.8.7" @@ -22457,12 +22432,12 @@ __metadata: languageName: node linkType: hard -"minimatch@npm:^10.0.1, minimatch@npm:^10.1.1, minimatch@npm:^9.0.3 || ^10.0.1": - version: 10.1.1 - resolution: "minimatch@npm:10.1.1" +"minimatch@npm:^10.0.1, minimatch@npm:^10.1.2, minimatch@npm:^9.0.3 || ^10.0.1": + version: 10.1.2 + resolution: "minimatch@npm:10.1.2" dependencies: - "@isaacs/brace-expansion": "npm:^5.0.0" - checksum: 10c0/c85d44821c71973d636091fddbfbffe62370f5ee3caf0241c5b60c18cd289e916200acb2361b7e987558cd06896d153e25d505db9fc1e43e6b4b6752e2702902 + "@isaacs/brace-expansion": "npm:^5.0.1" + checksum: 10c0/0cccef3622201703de6ecf9d772c0be1d5513dcc038ed9feb866c20cf798243e678ac35605dac3f1a054650c28037486713fe9e9a34b184b9097959114daf086 languageName: node linkType: hard