From 54653efb2abd6c6a1c3447761b8ab146d4454bfe Mon Sep 17 00:00:00 2001 From: Valentin Palkovic Date: Tue, 31 Dec 2024 16:04:00 +0100 Subject: [PATCH 01/27] Enhance a11y addon integration --- code/addons/a11y/src/preview.tsx | 3 - code/addons/test/src/postinstall.ts | 25 +- .../addon-a11y-addon-test.test.ts.snap | 74 +++ .../fixes/addon-a11y-addon-test.test.ts | 440 +++++++++++++++++- .../fixes/addon-a11y-addon-test.ts | 180 +++++-- 5 files changed, 644 insertions(+), 78 deletions(-) create mode 100644 code/lib/cli-storybook/src/automigrate/fixes/__snapshots__/addon-a11y-addon-test.test.ts.snap diff --git a/code/addons/a11y/src/preview.tsx b/code/addons/a11y/src/preview.tsx index e496894cb113..2616a8350941 100644 --- a/code/addons/a11y/src/preview.tsx +++ b/code/addons/a11y/src/preview.tsx @@ -85,6 +85,3 @@ export const initialGlobals = { manual: false, }, }; - -// A11Y_TEST_TAG constant in ./constants.ts. Has to be statically analyzable. -export const tags = ['a11ytest']; diff --git a/code/addons/test/src/postinstall.ts b/code/addons/test/src/postinstall.ts index b3c3dba95943..ac784ce2887b 100644 --- a/code/addons/test/src/postinstall.ts +++ b/code/addons/test/src/postinstall.ts @@ -316,8 +316,6 @@ export default async function postInstall(options: PostinstallOptions) { existsSync ); - const a11yAddon = info.addons.find((addon) => addon.includes(addonA11yName)); - const imports = [ `import { beforeAll } from 'vitest';`, `import { setProjectAnnotations } from '${annotationsImport}';`, @@ -325,11 +323,6 @@ export default async function postInstall(options: PostinstallOptions) { const projectAnnotations = []; - if (a11yAddon) { - imports.push(`import * as a11yAddonAnnotations from '@storybook/addon-a11y/preview';`); - projectAnnotations.push('a11yAddonAnnotations'); - } - if (previewExists) { imports.push(`import * as projectAnnotations from './preview';`); projectAnnotations.push('projectAnnotations'); @@ -348,6 +341,24 @@ export default async function postInstall(options: PostinstallOptions) { ` ); + const a11yAddon = info.addons.find((addon) => addon.includes(addonA11yName)); + + if (a11yAddon) { + try { + await $`storybook automigrate addonA11yAddonTest ${options.yes ? '--yes' : ''}`; + } catch (e) { + printError( + '🚨 Oh no!', + dedent` + We have detected that you have ${addonA11yName} installed but could not automatically set it up for @storybook/experimental-addon-test/vitest-plugin. + + Please refer to the documentation to complete the setup manually: + ${picocolors.cyan(`https://storybook.js.org/docs/writing-tests/accessibility-testing#test-addon-integration`)} + ` + ); + } + } + // Check for existing Vitest workspace. We can't extend it so manual setup is required. const vitestWorkspaceFile = await findFile('vitest.workspace'); if (vitestWorkspaceFile) { diff --git a/code/lib/cli-storybook/src/automigrate/fixes/__snapshots__/addon-a11y-addon-test.test.ts.snap b/code/lib/cli-storybook/src/automigrate/fixes/__snapshots__/addon-a11y-addon-test.test.ts.snap new file mode 100644 index 000000000000..a0c2c62f3c15 --- /dev/null +++ b/code/lib/cli-storybook/src/automigrate/fixes/__snapshots__/addon-a11y-addon-test.test.ts.snap @@ -0,0 +1,74 @@ +// Vitest Snapshot v1, https://vitest.dev/guide/snapshot.html + +exports[`addonA11yAddonTest > prompt > should return auto prompt if transformedSetupCode is defined and if transformedPreviewCode is null 1`] = ` +"We have detected that you have @storybook/addon-a11y and @storybook/experimental-addon-test installed. + +@storybook/addon-a11y integrates now with @storybook/experimental-addon-test to provide automatic accessibility checks for your stories, powered by Axe and Vitest. + +1) We have to update your .storybook/vitest.setup.ts to set up project annotations from @storybook/addon-a11y. + +2) We couldn't find or automatically update your .storybook/preview. in your project to smoothly set up tags from @storybook/addon-a11y. +Please manually update your .storybook/preview. file to include the following: + +export default { +... ++ tags: ["a11ytest"], +} + +For more information, please refer to the addon test documentation: +https://storybook.js.org/docs/writing-tests/addon-test" +`; + +exports[`addonA11yAddonTest > prompt > should return auto prompt if transformedSetupCode is null and if transformedPreviewCode is defined 1`] = ` +"We have detected that you have @storybook/addon-a11y and @storybook/experimental-addon-test installed. + +@storybook/addon-a11y integrates now with @storybook/experimental-addon-test to provide automatic accessibility checks for your stories, powered by Axe and Vitest. + +1) We couldn't find or automatically update your .storybook/vitest.setup. in your project to smoothly set up project annotations from @storybook/addon-a11y. +Please manually update your vitest.setup.ts file to include the following: + +... ++ import * as a11yAddonAnnotations from "@storybook/addon-a11y/preview"; + +const annotations = setProjectAnnotations([ + ... ++ a11yAddonAnnotations, +]); + +beforeAll(annotations.beforeAll); + +2) We have to update your .storybook/vitest.setup.js to set up tags from @storybook/addon-a11y. + +For more information, please refer to the addon test documentation: +https://storybook.js.org/docs/writing-tests/addon-test" +`; + +exports[`addonA11yAddonTest > prompt > should return manual prompt if transformedSetupCode is null and if transformedPreviewCode is null 1`] = ` +"We have detected that you have @storybook/addon-a11y and @storybook/experimental-addon-test installed. + +@storybook/addon-a11y integrates now with @storybook/experimental-addon-test to provide automatic accessibility checks for your stories, powered by Axe and Vitest. + +1) We couldn't find or automatically update your .storybook/vitest.setup. in your project to smoothly set up project annotations from @storybook/addon-a11y. +Please manually update your vitest.setup.ts file to include the following: + +... ++ import * as a11yAddonAnnotations from "@storybook/addon-a11y/preview"; + +const annotations = setProjectAnnotations([ + ... ++ a11yAddonAnnotations, +]); + +beforeAll(annotations.beforeAll); + +2) We couldn't find or automatically update your .storybook/preview. in your project to smoothly set up tags from @storybook/addon-a11y. +Please manually update your .storybook/preview. file to include the following: + +export default { +... ++ tags: ["a11ytest"], +} + +For more information, please refer to the addon test documentation: +https://storybook.js.org/docs/writing-tests/addon-test" +`; diff --git a/code/lib/cli-storybook/src/automigrate/fixes/addon-a11y-addon-test.test.ts b/code/lib/cli-storybook/src/automigrate/fixes/addon-a11y-addon-test.test.ts index 87da579183cb..7a4ec20fdb8a 100644 --- a/code/lib/cli-storybook/src/automigrate/fixes/addon-a11y-addon-test.test.ts +++ b/code/lib/cli-storybook/src/automigrate/fixes/addon-a11y-addon-test.test.ts @@ -1,11 +1,16 @@ import { beforeEach, describe, expect, it, vi } from 'vitest'; import { existsSync, readFileSync, writeFileSync } from 'fs'; +import * as jscodeshift from 'jscodeshift'; import path from 'path'; import dedent from 'ts-dedent'; import { getAddonNames } from '../helpers/mainConfigFile'; -import { addonA11yAddonTest, transformSetupFile } from './addon-a11y-addon-test'; +import { + addonA11yAddonTest, + transformPreviewFile, + transformSetupFile, +} from './addon-a11y-addon-test'; vi.mock('../helpers/mainConfigFile', async (importOriginal) => { const mod = (await importOriginal()) as any; @@ -26,6 +31,21 @@ vi.mock('fs', async (importOriginal) => { }; }); +vi.mock('picocolors', async (importOriginal) => { + const mod = (await importOriginal()) as any; + return { + ...mod, + default: { + gray: (s: string) => s, + green: (s: string) => s, + cyan: (s: string) => s, + magenta: (s: string) => s, + }, + }; +}); + +const j = jscodeshift.withParser('ts'); + describe('addonA11yAddonTest', () => { const configDir = '/path/to/config'; const mainConfig = {} as any; @@ -66,13 +86,61 @@ describe('addonA11yAddonTest', () => { expect(result).toBeNull(); }); - it('should return setupFile and transformedSetupCode if vitest.setup file exists', async () => { + it('should return null if vitest.setup file and preview file have the necessary transformations', async () => { vi.mocked(getAddonNames).mockReturnValue([ '@storybook/addon-a11y', '@storybook/experimental-addon-test', ]); vi.mocked(existsSync).mockReturnValue(true); - vi.mocked(readFileSync).mockReturnValue('const annotations = setProjectAnnotations([]);'); + vi.mocked(readFileSync).mockImplementation((p) => { + if (p.toString().includes('vitest.setup')) { + return ` + import * as a11yAddonAnnotations from "@storybook/addon-a11y/preview"; + import { beforeAll } from 'vitest'; + import { setProjectAnnotations } from 'storybook'; + import * as projectAnnotations from './preview'; + + const project = setProjectAnnotations([a11yAddonAnnotations, projectAnnotations]); + + beforeAll(project.beforeAll); + `; + } else { + return ` + export default { + tags: ['a11ytest'], + } + `; + } + }); + + const result = await addonA11yAddonTest.check({ + mainConfig: { + framework: '@storybook/react-vite', + }, + configDir, + } as any); + expect(result).toBeNull(); + }); + + it('should return setupFile and transformedSetupCode if vitest.setup file exists', async () => { + vi.mocked(getAddonNames).mockReturnValue([ + '@storybook/addon-a11y', + '@storybook/experimental-addon-test', + ]); + vi.mocked(existsSync).mockImplementation((p) => { + if (p.toString().includes('vitest.setup')) { + return true; + } else { + return false; + } + }); + vi.mocked(readFileSync).mockImplementation((p) => { + if (p.toString().includes('vitest.setup')) { + return 'const annotations = setProjectAnnotations([]);'; + } else { + return ''; + } + }); const result = await addonA11yAddonTest.check({ mainConfig: { @@ -82,18 +150,64 @@ describe('addonA11yAddonTest', () => { } as any); expect(result).toEqual({ setupFile: path.join(configDir, 'vitest.setup.js'), + previewFile: null, + transformedPreviewCode: null, transformedSetupCode: expect.any(String), }); }); + it('should return previewFile and transformedPreviewCode if preview file exists', async () => { + vi.mocked(getAddonNames).mockReturnValue([ + '@storybook/addon-a11y', + '@storybook/experimental-addon-test', + ]); + vi.mocked(existsSync).mockImplementation((p) => { + if (p.toString().includes('preview')) { + return true; + } else { + return false; + } + }); + vi.mocked(readFileSync).mockImplementation((p) => { + if (p.toString().includes('preview')) { + return 'export default {}'; + } else { + return ''; + } + }); + + const result = await addonA11yAddonTest.check({ + mainConfig: { + framework: '@storybook/react-vite', + }, + configDir, + } as any); + expect(result).toEqual({ + setupFile: null, + previewFile: path.join(configDir, 'preview.js'), + transformedPreviewCode: expect.any(String), + transformedSetupCode: null, + }); + }); + it('should return setupFile and null transformedSetupCode if transformation fails', async () => { vi.mocked(getAddonNames).mockReturnValue([ '@storybook/addon-a11y', '@storybook/experimental-addon-test', ]); - vi.mocked(existsSync).mockReturnValue(true); - vi.mocked(readFileSync).mockImplementation(() => { - throw new Error('Test error'); + vi.mocked(existsSync).mockImplementation((p) => { + if (p.toString().includes('vitest.setup')) { + return true; + } else { + return false; + } + }); + vi.mocked(readFileSync).mockImplementation((p) => { + if (p.toString().includes('vitest.setup')) { + throw new Error('Test error'); + } else { + return ''; + } }); const result = await addonA11yAddonTest.check({ @@ -104,23 +218,76 @@ describe('addonA11yAddonTest', () => { } as any); expect(result).toEqual({ setupFile: path.join(configDir, 'vitest.setup.js'), + previewFile: null, + transformedPreviewCode: null, + transformedSetupCode: null, + }); + }); + + it('should return previewFile and null transformedPreviewCode if transformation fails', async () => { + vi.mocked(getAddonNames).mockReturnValue([ + '@storybook/addon-a11y', + '@storybook/experimental-addon-test', + ]); + vi.mocked(existsSync).mockImplementation((p) => { + if (p.toString().includes('preview')) { + return true; + } else { + return false; + } + }); + vi.mocked(readFileSync).mockImplementation((p) => { + if (p.toString().includes('preview')) { + throw new Error('Test error'); + } else { + return ''; + } + }); + + const result = await addonA11yAddonTest.check({ + mainConfig: { + framework: '@storybook/sveltekit', + }, + configDir, + } as any); + expect(result).toEqual({ + setupFile: null, + previewFile: path.join(configDir, 'preview.js'), + transformedPreviewCode: null, transformedSetupCode: null, }); }); }); describe('prompt', () => { - it('should return manual prompt if setupFile is null', () => { - const result = addonA11yAddonTest.prompt({ setupFile: null, transformedSetupCode: null }); - expect(result).toContain("We couldn't find or automatically update your"); + it('should return manual prompt if transformedSetupCode is null and if transformedPreviewCode is null', () => { + const result = addonA11yAddonTest.prompt({ + setupFile: null, + transformedSetupCode: null, + previewFile: null, + transformedPreviewCode: null, + }); + expect(result).toMatchSnapshot(); + }); + + it('should return auto prompt if transformedSetupCode is null and if transformedPreviewCode is defined', () => { + const result = addonA11yAddonTest.prompt({ + setupFile: null, + transformedSetupCode: null, + previewFile: 'preview.js', + transformedPreviewCode: 'transformed code', + }); + expect(result).toMatchSnapshot(); }); - it('should return auto prompt if setupFile and transformedSetupCode are present', () => { + it('should return auto prompt if transformedSetupCode is defined and if transformedPreviewCode is null', () => { const result = addonA11yAddonTest.prompt({ - setupFile: '/path/to/vitest.setup.ts', + setupFile: 'vitest.setup.ts', transformedSetupCode: 'transformed code', + previewFile: null, + transformedPreviewCode: null, }); - expect(result).toContain('In order for these checks to be enabled we have to update your'); + expect(result).toMatchSnapshot(); }); }); @@ -129,11 +296,34 @@ describe('addonA11yAddonTest', () => { const setupFile = '/path/to/vitest.setup.ts'; const transformedSetupCode = 'transformed code'; - await addonA11yAddonTest.run?.({ result: { setupFile, transformedSetupCode } } as any); + await addonA11yAddonTest.run?.({ + result: { + setupFile, + transformedSetupCode, + previewFile: null, + transformedPreviewCode: null, + }, + } as any); expect(writeFileSync).toHaveBeenCalledWith(setupFile, transformedSetupCode, 'utf8'); }); + it('should write transformed preview code to file', async () => { + const previewFile = '/path/to/preview.ts'; + const transformedPreviewCode = 'transformed code'; + + await addonA11yAddonTest.run?.({ + result: { + setupFile: null, + transformedSetupCode: null, + previewFile: previewFile, + transformedPreviewCode: transformedPreviewCode, + }, + } as any); + + expect(writeFileSync).toHaveBeenCalledWith(previewFile, transformedPreviewCode, 'utf8'); + }); + it('should not write to file if setupFile or transformedSetupCode is null', async () => { await addonA11yAddonTest.run?.({ result: { setupFile: null, transformedSetupCode: null }, @@ -203,15 +393,15 @@ describe('addonA11yAddonTest', () => { const s = readFileSync(setupFile, 'utf8'); const transformedCode = transformSetupFile(s); expect(transformedCode).toMatchInlineSnapshot(` - "import * as a11yAddonAnnotations from "@storybook/addon-a11y/preview"; - import { beforeAll } from 'vitest'; - import { setProjectAnnotations } from 'storybook'; - import * as projectAnnotations from './preview'; + "import * as a11yAddonAnnotations from "@storybook/addon-a11y/preview"; + import { beforeAll } from 'vitest'; + import { setProjectAnnotations } from 'storybook'; + import * as projectAnnotations from './preview'; - const project = setProjectAnnotations([a11yAddonAnnotations, projectAnnotations]); + const project = setProjectAnnotations([a11yAddonAnnotations, projectAnnotations]); - beforeAll(project.beforeAll);" - `); + beforeAll(project.beforeAll);" + `); }); it('should transform setup file correctly - project annotation is not an array', () => { @@ -241,4 +431,214 @@ describe('addonA11yAddonTest', () => { `); }); }); + + describe('transformPreviewFile', () => { + it('should add a new tags property if it does not exist', () => { + const source = ` + import type { Preview } from '@storybook/react'; + + const preview: Preview = { + parameters: { + controls: { + matchers: { + color: /(background|color)$/i, + date: /Date$/i, + }, + }, + }, + }; + + export default preview; + `; + + const transformed = transformPreviewFile(source); + const expected = ` + import type { Preview } from '@storybook/react'; + + const preview: Preview = { + parameters: { + controls: { + matchers: { + color: /(background|color)$/i, + date: /Date$/i, + }, + }, + }, + + // a11ytest tag controls whether accessibility tests are run as part of a standalone Vitest test run + // For more information please visit: https://storybook.js.org/docs/writing-tests/accessibility-testing + tags: ['a11ytest'] + }; + + export default preview; + `; + + expect(transformed).toBe(expected); + }); + + it('should add a new tags property if it does not exist and a default export does not exist', () => { + const source = ` + export const parameters = { + controls: { + matchers: { + color: /(background|color)$/i, + date: /Date$/i, + }, + }, + } + `; + + const transformed = transformPreviewFile(source); + const expected = ` + export const parameters = { + controls: { + matchers: { + color: /(background|color)$/i, + date: /Date$/i, + }, + }, + } + export const tags = ["a11ytest"]; + `; + + expect(transformed).toBe(expected); + }); + + it('should extend the existing tags property', () => { + const source = ` + import type { Preview } from '@storybook/react'; + + const preview: Preview = { + tags: ['existingTag'], + parameters: { + controls: { + matchers: { + color: /(background|color)$/i, + date: /Date$/i, + }, + }, + }, + }; + + export default preview; + `; + + const transformed = transformPreviewFile(source); + const expected = ` + import type { Preview } from '@storybook/react'; + + const preview: Preview = { + // a11ytest tag controls whether accessibility tests are run as part of a standalone Vitest test run + // For more information please visit: https://storybook.js.org/docs/writing-tests/accessibility-testing + tags: ['existingTag', 'a11ytest'], + parameters: { + controls: { + matchers: { + color: /(background|color)$/i, + date: /Date$/i, + }, + }, + }, + }; + + export default preview; + `; + + expect(transformed).toBe(j(expected).toSource()); + }); + + it('should not add a11ytest if it already exists in the tags property', () => { + const source = ` + import type { Preview } from '@storybook/react'; + + const preview: Preview = { + tags: ['a11ytest'], + parameters: { + controls: { + matchers: { + color: /(background|color)$/i, + date: /Date$/i, + }, + }, + }, + }; + + export default preview; + `; + + const transformed = transformPreviewFile(source); + + expect(transformed).toBe(source); + }); + + it('should handle the default export without type annotations', () => { + const source = ` + export default { + parameters: { + controls: { + matchers: { + color: /(background|color)$/i, + date: /Date$/i, + }, + }, + }, + }; + `; + + const transformed = transformPreviewFile(source); + const expected = ` + export default { + parameters: { + controls: { + matchers: { + color: /(background|color)$/i, + date: /Date$/i, + }, + }, + }, + + // a11ytest tag controls whether accessibility tests are run as part of a standalone Vitest test run + // For more information please visit: https://storybook.js.org/docs/writing-tests/accessibility-testing + tags: ["a11ytest"] + }; + `; + + expect(transformed).toBe(expected); + }); + + it('should extend the existing tags property without type annotations', () => { + const source = ` + export default { + tags: ['existingTag'], + parameters: { + controls: { + matchers: { + color: /(background|color)$/i, + date: /Date$/i, + }, + }, + }, + }; + `; + + const transformed = transformPreviewFile(source); + const expected = ` + export default { + // a11ytest tag controls whether accessibility tests are run as part of a standalone Vitest test run + // For more information please visit: https://storybook.js.org/docs/writing-tests/accessibility-testing + tags: ['existingTag', 'a11ytest'], + parameters: { + controls: { + matchers: { + color: /(background|color)$/i, + date: /Date$/i, + }, + }, + }, + }; + `; + + expect(transformed).toBe(expected); + }); + }); }); diff --git a/code/lib/cli-storybook/src/automigrate/fixes/addon-a11y-addon-test.ts b/code/lib/cli-storybook/src/automigrate/fixes/addon-a11y-addon-test.ts index a8cd25cdf371..40c7952a3abe 100644 --- a/code/lib/cli-storybook/src/automigrate/fixes/addon-a11y-addon-test.ts +++ b/code/lib/cli-storybook/src/automigrate/fixes/addon-a11y-addon-test.ts @@ -1,4 +1,5 @@ import { rendererPackages } from 'storybook/internal/common'; +import { formatConfig, loadConfig } from 'storybook/internal/csf-tools'; import { existsSync, readFileSync, writeFileSync } from 'fs'; import * as jscodeshift from 'jscodeshift'; @@ -14,25 +15,28 @@ import { import { getAddonNames, getFrameworkPackageName, getRendererName } from '../helpers/mainConfigFile'; import type { Fix } from '../types'; -export const vitestFileExtensions = ['.js', '.ts', '.cts', '.mts', '.cjs', '.mjs'] as const; +export const fileExtensions = ['.js', '.ts', '.cts', '.mts', '.cjs', '.mjs'] as const; interface AddonA11yAddonTestOptions { setupFile: string | null; + previewFile: string | null; transformedSetupCode: string | null; + transformedPreviewCode: string | null; } /** * If addon-a11y and experimental-addon-test are already installed, we need to update - * `.storybook/vitest.setup.` to set up project annotations from addon-a11y. If we can't find - * `.storybook/vitest.setup.`, we need to set up a notification to the user to manually - * update the file. + * + * - `.storybook/vitest.setup.` to set up project annotations from addon-a11y. + * - `.storybook/preview.` to set up tags. + * - If we can't transform the files automatically, we'll prompt the user to do it manually. */ export const addonA11yAddonTest: Fix = { id: 'addonA11yAddonTest', versionRange: ['<8.5.0', '>=8.5.0'], promptType(result) { - if (result.setupFile === null) { + if (result.setupFile === null && result.previewFile === null) { return 'manual'; } @@ -59,53 +63,81 @@ export const addonA11yAddonTest: Fix = { return null; } + console.log(hasA11yAddon, hasTestAddon, configDir); + if (!hasA11yAddon || !hasTestAddon || !configDir) { return null; } - // get `${configDir}/vitest.setup.` absolute file path const vitestSetupFile = - vitestFileExtensions + fileExtensions .map((ext) => path.join(configDir, `vitest.setup${ext}`)) .find((filePath) => existsSync(filePath)) ?? null; - try { - if (vitestSetupFile) { - const source = readFileSync(vitestSetupFile, 'utf8'); - if (source.includes('@storybook/addon-a11y')) { - return null; - } - const transformedSetupCode = transformSetupFile(source); - return { - setupFile: vitestSetupFile, - transformedSetupCode, - }; - } else { - return { - setupFile: null, - transformedSetupCode: null, - }; + const previewFile = + fileExtensions + .map((ext) => path.join(configDir, `preview${ext}`)) + .find((filePath) => existsSync(filePath)) ?? null; + + if (vitestSetupFile && previewFile) { + const vitestSetupSource = readFileSync(vitestSetupFile, 'utf8'); + const previewSetupSource = readFileSync(previewFile, 'utf8'); + if ( + vitestSetupSource.includes('@storybook/addon-a11y') && + previewSetupSource.includes('a11ytest') + ) { + return null; } - } catch (e) { - return { - setupFile: vitestSetupFile, - transformedSetupCode: null, - }; } + + const getTransformedSetupCode = () => { + if (!vitestSetupFile) { + return null; + } + + try { + const vitestSetupSource = readFileSync(vitestSetupFile, 'utf8'); + return transformSetupFile(vitestSetupSource); + } catch (e) { + return null; + } + }; + + const getTransformedPreviewCode = () => { + if (!previewFile) { + return null; + } + + try { + const previewSetupSource = readFileSync(previewFile, 'utf8'); + return transformPreviewFile(previewSetupSource); + } catch (e) { + return null; + } + }; + + return { + setupFile: vitestSetupFile, + previewFile: previewFile, + transformedSetupCode: getTransformedSetupCode(), + transformedPreviewCode: getTransformedPreviewCode(), + }; }, - prompt({ setupFile, transformedSetupCode }) { + prompt({ setupFile, previewFile, transformedSetupCode, transformedPreviewCode }) { const introduction = dedent` We have detected that you have ${picocolors.magenta(`@storybook/addon-a11y`)} and ${picocolors.magenta(`@storybook/experimental-addon-test`)} installed. ${picocolors.magenta(`@storybook/addon-a11y`)} integrates now with ${picocolors.magenta(`@storybook/experimental-addon-test`)} to provide automatic accessibility checks for your stories, powered by Axe and Vitest. `; - if (setupFile === null || transformedSetupCode === null) { - return dedent` - ${introduction} + const prompt = [introduction]; + + let counter = 1; - We couldn't find or automatically update your ${picocolors.cyan(`.storybook/vitest.setup.`)} in your project to smoothly set up project annotations from ${picocolors.magenta(`@storybook/addon-a11y`)}. + if (transformedSetupCode === null) { + prompt.push(dedent` + ${counter++}) We couldn't find or automatically update your ${picocolors.cyan(`.storybook/vitest.setup.`)} in your project to smoothly set up project annotations from ${picocolors.magenta(`@storybook/addon-a11y`)}. Please manually update your ${picocolors.cyan(`vitest.setup.ts`)} file to include the following: ${picocolors.gray('...')} @@ -117,32 +149,53 @@ export const addonA11yAddonTest: Fix = { ${picocolors.gray(']);')} ${picocolors.gray('beforeAll(annotations.beforeAll);')} + `); + } else { + const fileExtensionSetupFile = path.extname(setupFile!); - For more information, please refer to the addon test documentation: - ${picocolors.cyan('https://storybook.js.org/docs/writing-tests/addon-test')} - `; + prompt.push( + dedent`${counter++}) We have to update your ${picocolors.cyan(`.storybook/vitest.setup${fileExtensionSetupFile}`)} to set up project annotations from ${picocolors.magenta(`@storybook/addon-a11y`)}.` + ); } - const fileExtension = path.extname(setupFile); - - return dedent` - We have detected that you have ${picocolors.magenta(`@storybook/addon-a11y`)} and ${picocolors.magenta(`@storybook/experimental-addon-test`)} installed. + if (transformedPreviewCode === null) { + prompt.push(dedent` + ${counter++}) We couldn't find or automatically update your ${picocolors.cyan(`.storybook/preview.`)} in your project to smoothly set up tags from ${picocolors.magenta(`@storybook/addon-a11y`)}. + Please manually update your ${picocolors.cyan(`.storybook/preview.`)} file to include the following: + + ${picocolors.gray('export default {')} + ${picocolors.gray('...')} + ${picocolors.green('+ tags: ["a11ytest"],')} + ${picocolors.gray('}')} + `); + } else { + const fileExtensionPreviewFile = path.extname(previewFile!); + + prompt.push( + dedent`${counter++}) We have to update your ${picocolors.cyan(`.storybook/vitest.setup${fileExtensionPreviewFile}`)} to set up tags from ${picocolors.magenta(`@storybook/addon-a11y`)}.` + ); + } - ${picocolors.magenta(`@storybook/addon-a11y`)} integrates now with ${picocolors.magenta(`@storybook/experimental-addon-test`)} to provide automatic accessibility checks for your stories, powered by Axe and Vitest. + if (transformedPreviewCode === null || transformedSetupCode === null) { + prompt.push(dedent` + For more information, please refer to the addon test documentation: + ${picocolors.cyan('https://storybook.js.org/docs/writing-tests/addon-test')} + `); + } - In order for these checks to be enabled we have to update your ${picocolors.cyan(`.storybook/vitest.setup${fileExtension}`)} file. - `; + return prompt.join('\n\n'); }, async run({ result }) { - const { setupFile, transformedSetupCode } = result; + const { setupFile, transformedSetupCode, transformedPreviewCode, previewFile } = result; - if (!setupFile || !transformedSetupCode) { - return; + if (transformedSetupCode && setupFile) { + writeFileSync(setupFile, transformedSetupCode, 'utf8'); } - // Write the transformed code back to the file - writeFileSync(setupFile, transformedSetupCode, 'utf8'); + if (transformedPreviewCode && previewFile) { + writeFileSync(previewFile, transformedPreviewCode, 'utf8'); + } }, }; @@ -184,3 +237,34 @@ export function transformSetupFile(source: string) { return root.toSource(); } + +export function transformPreviewFile(source: string) { + const previewConfig = loadConfig(source).parse(); + const tags = previewConfig.getFieldNode(['tags']); + const tagsValue = previewConfig.getFieldValue(['tags']) ?? []; + + if (tags && tagsValue && (tagsValue.includes('a11ytest') || tagsValue.includes('!a11ytest'))) { + return source; + } + + previewConfig.setFieldValue(['tags'], [...tagsValue, 'a11ytest']); + + const formattedPreviewConfig = formatConfig(previewConfig); + const lines = formattedPreviewConfig.split('\n'); + + // Find the line with the "tags" property + const tagsLineIndex = lines.findIndex((line) => line.includes('tags: [')); + if (tagsLineIndex === -1) { + return formattedPreviewConfig; + } + + // Determine the indentation level of the "tags" property + const tagsLine = lines[tagsLineIndex]; + const indentation = tagsLine?.match(/^\s*/)?.[0]; + + // Add the comment with the same indentation level + const comment = `${indentation}// a11ytest tag controls whether accessibility tests are run as part of a standalone Vitest test run\n${indentation}// For more information please visit: https://storybook.js.org/docs/writing-tests/accessibility-testing`; + lines.splice(tagsLineIndex, 0, comment); + + return lines.join('\n'); +} From a1506f373d3413280fc98fe1d1d39ef9e687b4f0 Mon Sep 17 00:00:00 2001 From: Valentin Palkovic Date: Thu, 2 Jan 2025 09:50:45 +0100 Subject: [PATCH 02/27] Refactor TestProviderRender to remove a11ytest tag and pre-select a11y checkbox if a11ytest tag is set on at least one story --- code/.storybook/preview.tsx | 2 +- .../src/components/TestProviderRender.tsx | 45 +++++++++++++++---- 2 files changed, 37 insertions(+), 10 deletions(-) diff --git a/code/.storybook/preview.tsx b/code/.storybook/preview.tsx index 23f95a0c5d5e..cc4481a56d1c 100644 --- a/code/.storybook/preview.tsx +++ b/code/.storybook/preview.tsx @@ -360,4 +360,4 @@ export const parameters = { }, }; -export const tags = ['test', 'vitest', '!a11ytest']; +export const tags = ['test', 'vitest']; diff --git a/code/addons/test/src/components/TestProviderRender.tsx b/code/addons/test/src/components/TestProviderRender.tsx index ab8e61fadf0d..1ead5c30a99d 100644 --- a/code/addons/test/src/components/TestProviderRender.tsx +++ b/code/addons/test/src/components/TestProviderRender.tsx @@ -12,10 +12,11 @@ import { type TestProviderConfig, type TestProviderState, } from 'storybook/internal/core-events'; -import { addons } from 'storybook/internal/manager-api'; +import { addons, useStorybookState } from 'storybook/internal/manager-api'; import type { API } from 'storybook/internal/manager-api'; import { styled, useTheme } from 'storybook/internal/theming'; +import type { Tag } from '@storybook/csf'; import { AccessibilityIcon, EditIcon, @@ -106,23 +107,49 @@ const statusMap: Record['statu pending: 'pending', }; -export const TestProviderRender: FC< - { - api: API; - state: TestProviderConfig & TestProviderState; - entryId?: string; - } & ComponentProps -> = ({ state, api, entryId, ...props }) => { +type TestProviderRenderProps = { + api: API; + state: TestProviderConfig & TestProviderState; + entryId?: string; +} & ComponentProps; + +export const TestProviderRender: FC = ({ + state, + api, + entryId, + ...props +}) => { const [isEditing, setIsEditing] = useState(false); const theme = useTheme(); const coverageSummary = state.details?.coverageSummary; + const storybookState = useStorybookState(); const isA11yAddon = addons.experimental_getRegisteredAddons().includes(A11Y_ADDON_ID); + const isA11yAddonInitiallyChecked = useMemo(() => { + const internalIndex = storybookState.internal_index; + if (!internalIndex || !isA11yAddon) { + return false; + } + + const allTags = Object.values(internalIndex.entries).reduce((acc, entry) => { + entry.tags?.forEach((tag: Tag) => { + acc.add(tag); + }); + return acc; + }, new Set()); + + if (allTags.has('a11ytest')) { + return true; + } + + return false; + }, [isA11yAddon, storybookState.internal_index]); + const [config, updateConfig] = useConfig( api, state.id, - state.config || { a11y: false, coverage: false } + state.config || { a11y: isA11yAddonInitiallyChecked, coverage: false } ); const isStoryEntry = entryId?.includes('--') ?? false; From 4283a92c00dfea76a8ccada5c3d17f605fe4b391 Mon Sep 17 00:00:00 2001 From: Valentin Palkovic Date: Thu, 2 Jan 2025 10:03:12 +0100 Subject: [PATCH 03/27] Small text adjustments --- code/addons/test/src/postinstall.ts | 2 +- .../__snapshots__/addon-a11y-addon-test.test.ts.snap | 12 ++++++------ .../src/automigrate/fixes/addon-a11y-addon-test.ts | 4 ++-- 3 files changed, 9 insertions(+), 9 deletions(-) diff --git a/code/addons/test/src/postinstall.ts b/code/addons/test/src/postinstall.ts index ac784ce2887b..2ed19aa13acb 100644 --- a/code/addons/test/src/postinstall.ts +++ b/code/addons/test/src/postinstall.ts @@ -350,7 +350,7 @@ export default async function postInstall(options: PostinstallOptions) { printError( '🚨 Oh no!', dedent` - We have detected that you have ${addonA11yName} installed but could not automatically set it up for @storybook/experimental-addon-test/vitest-plugin. + We have detected that you have ${addonA11yName} installed but could not automatically set it up for @storybook/experimental-addon-test. Please refer to the documentation to complete the setup manually: ${picocolors.cyan(`https://storybook.js.org/docs/writing-tests/accessibility-testing#test-addon-integration`)} diff --git a/code/lib/cli-storybook/src/automigrate/fixes/__snapshots__/addon-a11y-addon-test.test.ts.snap b/code/lib/cli-storybook/src/automigrate/fixes/__snapshots__/addon-a11y-addon-test.test.ts.snap index a0c2c62f3c15..9e069024f7bc 100644 --- a/code/lib/cli-storybook/src/automigrate/fixes/__snapshots__/addon-a11y-addon-test.test.ts.snap +++ b/code/lib/cli-storybook/src/automigrate/fixes/__snapshots__/addon-a11y-addon-test.test.ts.snap @@ -15,8 +15,8 @@ export default { + tags: ["a11ytest"], } -For more information, please refer to the addon test documentation: -https://storybook.js.org/docs/writing-tests/addon-test" +For more information, please refer to the accessibility addon documentation: +https://storybook.js.org/docs/writing-tests/accessibility-testing#test-addon-integration" `; exports[`addonA11yAddonTest > prompt > should return auto prompt if transformedSetupCode is null and if transformedPreviewCode is defined 1`] = ` @@ -39,8 +39,8 @@ beforeAll(annotations.beforeAll); 2) We have to update your .storybook/vitest.setup.js to set up tags from @storybook/addon-a11y. -For more information, please refer to the addon test documentation: -https://storybook.js.org/docs/writing-tests/addon-test" +For more information, please refer to the accessibility addon documentation: +https://storybook.js.org/docs/writing-tests/accessibility-testing#test-addon-integration" `; exports[`addonA11yAddonTest > prompt > should return manual prompt if transformedSetupCode is null and if transformedPreviewCode is null 1`] = ` @@ -69,6 +69,6 @@ export default { + tags: ["a11ytest"], } -For more information, please refer to the addon test documentation: -https://storybook.js.org/docs/writing-tests/addon-test" +For more information, please refer to the accessibility addon documentation: +https://storybook.js.org/docs/writing-tests/accessibility-testing#test-addon-integration" `; diff --git a/code/lib/cli-storybook/src/automigrate/fixes/addon-a11y-addon-test.ts b/code/lib/cli-storybook/src/automigrate/fixes/addon-a11y-addon-test.ts index 40c7952a3abe..dad86f655141 100644 --- a/code/lib/cli-storybook/src/automigrate/fixes/addon-a11y-addon-test.ts +++ b/code/lib/cli-storybook/src/automigrate/fixes/addon-a11y-addon-test.ts @@ -178,8 +178,8 @@ export const addonA11yAddonTest: Fix = { if (transformedPreviewCode === null || transformedSetupCode === null) { prompt.push(dedent` - For more information, please refer to the addon test documentation: - ${picocolors.cyan('https://storybook.js.org/docs/writing-tests/addon-test')} + For more information, please refer to the accessibility addon documentation: + ${picocolors.cyan('https://storybook.js.org/docs/writing-tests/accessibility-testing#test-addon-integration')} `); } From 5a422cf4a3935a4781aec942e79d466ce7386fac Mon Sep 17 00:00:00 2001 From: Valentin Palkovic Date: Thu, 2 Jan 2025 10:24:55 +0100 Subject: [PATCH 04/27] Add support for additional file extensions in addon-a11y-addon-test --- .../automigrate/fixes/addon-a11y-addon-test.ts | 17 ++++++++++++++--- 1 file changed, 14 insertions(+), 3 deletions(-) diff --git a/code/lib/cli-storybook/src/automigrate/fixes/addon-a11y-addon-test.ts b/code/lib/cli-storybook/src/automigrate/fixes/addon-a11y-addon-test.ts index dad86f655141..f1ccfdcbf9ec 100644 --- a/code/lib/cli-storybook/src/automigrate/fixes/addon-a11y-addon-test.ts +++ b/code/lib/cli-storybook/src/automigrate/fixes/addon-a11y-addon-test.ts @@ -15,7 +15,20 @@ import { import { getAddonNames, getFrameworkPackageName, getRendererName } from '../helpers/mainConfigFile'; import type { Fix } from '../types'; -export const fileExtensions = ['.js', '.ts', '.cts', '.mts', '.cjs', '.mjs'] as const; +export const fileExtensions = [ + '.js', + '.ts', + '.cts', + '.mts', + '.cjs', + '.mjs', + '.jsx', + '.tsx', + '.ctsx', + '.mtsx', + '.cjsx', + '.mjsx', +] as const; interface AddonA11yAddonTestOptions { setupFile: string | null; @@ -63,8 +76,6 @@ export const addonA11yAddonTest: Fix = { return null; } - console.log(hasA11yAddon, hasTestAddon, configDir); - if (!hasA11yAddon || !hasTestAddon || !configDir) { return null; } From c43aa097a54b9dccdf8ba88bc9be2cb608bbcfc7 Mon Sep 17 00:00:00 2001 From: Valentin Palkovic Date: Thu, 2 Jan 2025 10:52:16 +0100 Subject: [PATCH 05/27] Update a11y addon test snapshots and enhance transformation logic for setup and preview files --- .../addon-a11y-addon-test.test.ts.snap | 26 +++- .../fixes/addon-a11y-addon-test.test.ts | 123 ++++++++++++++++++ .../fixes/addon-a11y-addon-test.ts | 101 ++++++++------ 3 files changed, 208 insertions(+), 42 deletions(-) diff --git a/code/lib/cli-storybook/src/automigrate/fixes/__snapshots__/addon-a11y-addon-test.test.ts.snap b/code/lib/cli-storybook/src/automigrate/fixes/__snapshots__/addon-a11y-addon-test.test.ts.snap index 9e069024f7bc..508306a1987f 100644 --- a/code/lib/cli-storybook/src/automigrate/fixes/__snapshots__/addon-a11y-addon-test.test.ts.snap +++ b/code/lib/cli-storybook/src/automigrate/fixes/__snapshots__/addon-a11y-addon-test.test.ts.snap @@ -1,11 +1,22 @@ // Vitest Snapshot v1, https://vitest.dev/guide/snapshot.html +exports[`addonA11yAddonTest > prompt > should return auto prompt if transformedPreviewCode is defined and if transformedSetupCode is skipped 1`] = ` +"We have detected that you have @storybook/addon-a11y and @storybook/experimental-addon-test installed. + +@storybook/addon-a11y integrates now with @storybook/experimental-addon-test to provide automatic accessibility checks for your stories, powered by Axe and Vitest. + +1) We have to update your .storybook/preview.js file to set up tags from @storybook/addon-a11y. + +For more information, please refer to the accessibility addon documentation: +https://storybook.js.org/docs/writing-tests/accessibility-testing#test-addon-integration" +`; + exports[`addonA11yAddonTest > prompt > should return auto prompt if transformedSetupCode is defined and if transformedPreviewCode is null 1`] = ` "We have detected that you have @storybook/addon-a11y and @storybook/experimental-addon-test installed. @storybook/addon-a11y integrates now with @storybook/experimental-addon-test to provide automatic accessibility checks for your stories, powered by Axe and Vitest. -1) We have to update your .storybook/vitest.setup.ts to set up project annotations from @storybook/addon-a11y. +1) We have to update your .storybook/vitest.setup.ts file to set up project annotations from @storybook/addon-a11y. 2) We couldn't find or automatically update your .storybook/preview. in your project to smoothly set up tags from @storybook/addon-a11y. Please manually update your .storybook/preview. file to include the following: @@ -19,6 +30,17 @@ For more information, please refer to the accessibility addon documentation: https://storybook.js.org/docs/writing-tests/accessibility-testing#test-addon-integration" `; +exports[`addonA11yAddonTest > prompt > should return auto prompt if transformedSetupCode is defined and if transformedPreviewCode is skipped 1`] = ` +"We have detected that you have @storybook/addon-a11y and @storybook/experimental-addon-test installed. + +@storybook/addon-a11y integrates now with @storybook/experimental-addon-test to provide automatic accessibility checks for your stories, powered by Axe and Vitest. + +1) We have to update your .storybook/vitest.setup.ts file to set up project annotations from @storybook/addon-a11y. + +For more information, please refer to the accessibility addon documentation: +https://storybook.js.org/docs/writing-tests/accessibility-testing#test-addon-integration" +`; + exports[`addonA11yAddonTest > prompt > should return auto prompt if transformedSetupCode is null and if transformedPreviewCode is defined 1`] = ` "We have detected that you have @storybook/addon-a11y and @storybook/experimental-addon-test installed. @@ -37,7 +59,7 @@ const annotations = setProjectAnnotations([ beforeAll(annotations.beforeAll); -2) We have to update your .storybook/vitest.setup.js to set up tags from @storybook/addon-a11y. +2) We have to update your .storybook/preview.js file to set up tags from @storybook/addon-a11y. For more information, please refer to the accessibility addon documentation: https://storybook.js.org/docs/writing-tests/accessibility-testing#test-addon-integration" diff --git a/code/lib/cli-storybook/src/automigrate/fixes/addon-a11y-addon-test.test.ts b/code/lib/cli-storybook/src/automigrate/fixes/addon-a11y-addon-test.test.ts index 7a4ec20fdb8a..dd400e18bb80 100644 --- a/code/lib/cli-storybook/src/automigrate/fixes/addon-a11y-addon-test.test.ts +++ b/code/lib/cli-storybook/src/automigrate/fixes/addon-a11y-addon-test.test.ts @@ -153,6 +153,8 @@ describe('addonA11yAddonTest', () => { previewFile: null, transformedPreviewCode: null, transformedSetupCode: expect.any(String), + skipPreviewTransformation: false, + skipVitestSetupTransformation: false, }); }); @@ -187,6 +189,8 @@ describe('addonA11yAddonTest', () => { previewFile: path.join(configDir, 'preview.js'), transformedPreviewCode: expect.any(String), transformedSetupCode: null, + skipPreviewTransformation: false, + skipVitestSetupTransformation: false, }); }); @@ -221,6 +225,8 @@ describe('addonA11yAddonTest', () => { previewFile: null, transformedPreviewCode: null, transformedSetupCode: null, + skipPreviewTransformation: false, + skipVitestSetupTransformation: false, }); }); @@ -255,6 +261,93 @@ describe('addonA11yAddonTest', () => { previewFile: path.join(configDir, 'preview.js'), transformedPreviewCode: null, transformedSetupCode: null, + skipPreviewTransformation: false, + skipVitestSetupTransformation: false, + }); + }); + + it('should return skipPreviewTransformation=true if preview file has the necessary change', async () => { + vi.mocked(getAddonNames).mockReturnValue([ + '@storybook/addon-a11y', + '@storybook/experimental-addon-test', + ]); + vi.mocked(existsSync).mockReturnValue(true); + vi.mocked(readFileSync).mockImplementation((p) => { + if (p.toString().includes('vitest.setup')) { + return ` + import { beforeAll } from 'vitest'; + import { setProjectAnnotations } from 'storybook'; + import * as projectAnnotations from './preview'; + + const project = setProjectAnnotations([projectAnnotations]); + + beforeAll(project.beforeAll); + `; + } else { + return ` + export default { + tags: ['a11ytest'], + } + `; + } + }); + + const result = await addonA11yAddonTest.check({ + mainConfig: { + framework: '@storybook/sveltekit', + }, + configDir, + } as any); + expect(result).toEqual({ + setupFile: path.join(configDir, 'vitest.setup.js'), + previewFile: path.join(configDir, 'preview.js'), + transformedPreviewCode: null, + transformedSetupCode: expect.any(String), + skipPreviewTransformation: true, + skipVitestSetupTransformation: false, + }); + }); + + it('should return skipVitestSetupTransformation=true if setup file has the necessary change', async () => { + vi.mocked(getAddonNames).mockReturnValue([ + '@storybook/addon-a11y', + '@storybook/experimental-addon-test', + ]); + vi.mocked(existsSync).mockReturnValue(true); + vi.mocked(readFileSync).mockImplementation((p) => { + if (p.toString().includes('vitest.setup')) { + return ` + import * as a11yAddonAnnotations from "@storybook/addon-a11y/preview"; + import { beforeAll } from 'vitest'; + import { setProjectAnnotations } from 'storybook'; + import * as projectAnnotations from './preview'; + + const project = setProjectAnnotations([a11yAddonAnnotations, projectAnnotations]); + + beforeAll(project.beforeAll); + `; + } else { + return ` + export default { + tags: [], + } + `; + } + }); + + const result = await addonA11yAddonTest.check({ + mainConfig: { + framework: '@storybook/sveltekit', + }, + configDir, + } as any); + expect(result).toEqual({ + setupFile: path.join(configDir, 'vitest.setup.js'), + previewFile: path.join(configDir, 'preview.js'), + transformedPreviewCode: expect.any(String), + transformedSetupCode: null, + skipPreviewTransformation: false, + skipVitestSetupTransformation: true, }); }); }); @@ -266,6 +359,8 @@ describe('addonA11yAddonTest', () => { transformedSetupCode: null, previewFile: null, transformedPreviewCode: null, + skipPreviewTransformation: false, + skipVitestSetupTransformation: false, }); expect(result).toMatchSnapshot(); }); @@ -276,6 +371,8 @@ describe('addonA11yAddonTest', () => { transformedSetupCode: null, previewFile: 'preview.js', transformedPreviewCode: 'transformed code', + skipPreviewTransformation: false, + skipVitestSetupTransformation: false, }); expect(result).toMatchSnapshot(); }); @@ -286,6 +383,32 @@ describe('addonA11yAddonTest', () => { transformedSetupCode: 'transformed code', previewFile: null, transformedPreviewCode: null, + skipPreviewTransformation: false, + skipVitestSetupTransformation: false, + }); + expect(result).toMatchSnapshot(); + }); + + it('should return auto prompt if transformedSetupCode is defined and if transformedPreviewCode is skipped', () => { + const result = addonA11yAddonTest.prompt({ + setupFile: 'vitest.setup.ts', + transformedSetupCode: 'transformed code', + previewFile: null, + transformedPreviewCode: null, + skipPreviewTransformation: true, + skipVitestSetupTransformation: false, + }); + expect(result).toMatchSnapshot(); + }); + + it('should return auto prompt if transformedPreviewCode is defined and if transformedSetupCode is skipped', () => { + const result = addonA11yAddonTest.prompt({ + setupFile: null, + transformedSetupCode: null, + previewFile: 'preview.js', + transformedPreviewCode: 'transformed code', + skipPreviewTransformation: false, + skipVitestSetupTransformation: true, }); expect(result).toMatchSnapshot(); }); diff --git a/code/lib/cli-storybook/src/automigrate/fixes/addon-a11y-addon-test.ts b/code/lib/cli-storybook/src/automigrate/fixes/addon-a11y-addon-test.ts index f1ccfdcbf9ec..8f6ca6d6b5bc 100644 --- a/code/lib/cli-storybook/src/automigrate/fixes/addon-a11y-addon-test.ts +++ b/code/lib/cli-storybook/src/automigrate/fixes/addon-a11y-addon-test.ts @@ -35,6 +35,8 @@ interface AddonA11yAddonTestOptions { previewFile: string | null; transformedSetupCode: string | null; transformedPreviewCode: string | null; + skipVitestSetupTransformation: boolean; + skipPreviewTransformation: boolean; } /** @@ -90,19 +92,23 @@ export const addonA11yAddonTest: Fix = { .map((ext) => path.join(configDir, `preview${ext}`)) .find((filePath) => existsSync(filePath)) ?? null; + let skipVitestSetupTransformation = false; + let skipPreviewTransformation = false; + if (vitestSetupFile && previewFile) { const vitestSetupSource = readFileSync(vitestSetupFile, 'utf8'); const previewSetupSource = readFileSync(previewFile, 'utf8'); - if ( - vitestSetupSource.includes('@storybook/addon-a11y') && - previewSetupSource.includes('a11ytest') - ) { + + skipVitestSetupTransformation = vitestSetupSource.includes('@storybook/addon-a11y'); + skipPreviewTransformation = previewSetupSource.includes('a11ytest'); + + if (skipVitestSetupTransformation && skipPreviewTransformation) { return null; } } const getTransformedSetupCode = () => { - if (!vitestSetupFile) { + if (!vitestSetupFile || skipVitestSetupTransformation) { return null; } @@ -115,7 +121,7 @@ export const addonA11yAddonTest: Fix = { }; const getTransformedPreviewCode = () => { - if (!previewFile) { + if (!previewFile || skipPreviewTransformation) { return null; } @@ -132,10 +138,19 @@ export const addonA11yAddonTest: Fix = { previewFile: previewFile, transformedSetupCode: getTransformedSetupCode(), transformedPreviewCode: getTransformedPreviewCode(), + skipVitestSetupTransformation, + skipPreviewTransformation, }; }, - prompt({ setupFile, previewFile, transformedSetupCode, transformedPreviewCode }) { + prompt({ + setupFile, + previewFile, + transformedSetupCode, + transformedPreviewCode, + skipPreviewTransformation, + skipVitestSetupTransformation, + }) { const introduction = dedent` We have detected that you have ${picocolors.magenta(`@storybook/addon-a11y`)} and ${picocolors.magenta(`@storybook/experimental-addon-test`)} installed. @@ -146,45 +161,51 @@ export const addonA11yAddonTest: Fix = { let counter = 1; - if (transformedSetupCode === null) { - prompt.push(dedent` - ${counter++}) We couldn't find or automatically update your ${picocolors.cyan(`.storybook/vitest.setup.`)} in your project to smoothly set up project annotations from ${picocolors.magenta(`@storybook/addon-a11y`)}. - Please manually update your ${picocolors.cyan(`vitest.setup.ts`)} file to include the following: + if (!skipVitestSetupTransformation) { + if (transformedSetupCode === null) { + prompt.push(dedent` + ${counter++}) We couldn't find or automatically update your ${picocolors.cyan(`.storybook/vitest.setup.`)} in your project to smoothly set up project annotations from ${picocolors.magenta(`@storybook/addon-a11y`)}. + Please manually update your ${picocolors.cyan(`vitest.setup.ts`)} file to include the following: - ${picocolors.gray('...')} - ${picocolors.green('+ import * as a11yAddonAnnotations from "@storybook/addon-a11y/preview";')} + ${picocolors.gray('...')} + ${picocolors.green('+ import * as a11yAddonAnnotations from "@storybook/addon-a11y/preview";')} - ${picocolors.gray('const annotations = setProjectAnnotations([')} - ${picocolors.gray(' ...')} - ${picocolors.green('+ a11yAddonAnnotations,')} - ${picocolors.gray(']);')} + ${picocolors.gray('const annotations = setProjectAnnotations([')} + ${picocolors.gray(' ...')} + ${picocolors.green('+ a11yAddonAnnotations,')} + ${picocolors.gray(']);')} - ${picocolors.gray('beforeAll(annotations.beforeAll);')} - `); - } else { - const fileExtensionSetupFile = path.extname(setupFile!); + ${picocolors.gray('beforeAll(annotations.beforeAll);')} + `); + } else { + const fileExtensionSetupFile = path.extname(setupFile!); - prompt.push( - dedent`${counter++}) We have to update your ${picocolors.cyan(`.storybook/vitest.setup${fileExtensionSetupFile}`)} to set up project annotations from ${picocolors.magenta(`@storybook/addon-a11y`)}.` - ); + prompt.push( + dedent`${counter++}) We have to update your ${picocolors.cyan(`.storybook/vitest.setup${fileExtensionSetupFile}`)} file to set up project annotations from ${picocolors.magenta(`@storybook/addon-a11y`)}.` + ); + } } - if (transformedPreviewCode === null) { - prompt.push(dedent` - ${counter++}) We couldn't find or automatically update your ${picocolors.cyan(`.storybook/preview.`)} in your project to smoothly set up tags from ${picocolors.magenta(`@storybook/addon-a11y`)}. - Please manually update your ${picocolors.cyan(`.storybook/preview.`)} file to include the following: - - ${picocolors.gray('export default {')} - ${picocolors.gray('...')} - ${picocolors.green('+ tags: ["a11ytest"],')} - ${picocolors.gray('}')} - `); - } else { - const fileExtensionPreviewFile = path.extname(previewFile!); - - prompt.push( - dedent`${counter++}) We have to update your ${picocolors.cyan(`.storybook/vitest.setup${fileExtensionPreviewFile}`)} to set up tags from ${picocolors.magenta(`@storybook/addon-a11y`)}.` - ); + if (!skipPreviewTransformation) { + if (transformedPreviewCode === null) { + prompt.push(dedent` + ${counter++}) We couldn't find or automatically update your ${picocolors.cyan(`.storybook/preview.`)} in your project to smoothly set up tags from ${picocolors.magenta(`@storybook/addon-a11y`)}. + Please manually update your ${picocolors.cyan(`.storybook/preview.`)} file to include the following: + + ${picocolors.gray('export default {')} + ${picocolors.gray('...')} + ${picocolors.green('+ tags: ["a11ytest"],')} + ${picocolors.gray('}')} + `); + } else { + const fileExtensionPreviewFile = path.extname(previewFile!); + + prompt.push( + dedent` + ${counter++}) We have to update your ${picocolors.cyan(`.storybook/preview${fileExtensionPreviewFile}`)} file to set up tags from ${picocolors.magenta(`@storybook/addon-a11y`)}. + ` + ); + } } if (transformedPreviewCode === null || transformedSetupCode === null) { From b0d96bc5713703346f66619a21774b16508ca2a5 Mon Sep 17 00:00:00 2001 From: Valentin Palkovic Date: Thu, 2 Jan 2025 11:26:46 +0100 Subject: [PATCH 06/27] Update execa dependency to version 9.5.2 and enhance post-install logging for a11y addon setup --- code/addons/test/package.json | 2 +- code/addons/test/src/postinstall.ts | 5 ++++- code/yarn.lock | 2 +- 3 files changed, 6 insertions(+), 3 deletions(-) diff --git a/code/addons/test/package.json b/code/addons/test/package.json index 8ee188023bb5..ab0ffda5e379 100644 --- a/code/addons/test/package.json +++ b/code/addons/test/package.json @@ -99,7 +99,7 @@ "ansi-to-html": "^0.7.2", "boxen": "^8.0.1", "es-toolkit": "^1.22.0", - "execa": "^8.0.1", + "execa": "^9.5.2", "find-up": "^7.0.0", "formik": "^2.2.9", "istanbul-lib-report": "^3.0.1", diff --git a/code/addons/test/src/postinstall.ts b/code/addons/test/src/postinstall.ts index 2ed19aa13acb..95d0a25979d1 100644 --- a/code/addons/test/src/postinstall.ts +++ b/code/addons/test/src/postinstall.ts @@ -345,7 +345,10 @@ export default async function postInstall(options: PostinstallOptions) { if (a11yAddon) { try { - await $`storybook automigrate addonA11yAddonTest ${options.yes ? '--yes' : ''}`; + logger.plain(`${step} Setting up ${addonA11yName} for @storybook/experimental-addon-test:`); + await $({ + stdio: 'inherit', + })`storybook automigrate addonA11yAddonTest ${options.yes ? '--yes' : ''}`; } catch (e) { printError( '🚨 Oh no!', diff --git a/code/yarn.lock b/code/yarn.lock index 13e798756fd5..33c81835e56f 100644 --- a/code/yarn.lock +++ b/code/yarn.lock @@ -6421,7 +6421,7 @@ __metadata: ansi-to-html: "npm:^0.7.2" boxen: "npm:^8.0.1" es-toolkit: "npm:^1.22.0" - execa: "npm:^8.0.1" + execa: "npm:^9.5.2" find-up: "npm:^7.0.0" formik: "npm:^2.2.9" istanbul-lib-report: "npm:^3.0.1" From 8de05db935111e75d42ce8c4fe2139a196eb9073 Mon Sep 17 00:00:00 2001 From: Valentin Palkovic Date: Thu, 2 Jan 2025 11:48:05 +0100 Subject: [PATCH 07/27] Enhance post-install script to inherit stdio for better logging during addon migration --- code/addons/a11y/src/postinstall.ts | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/code/addons/a11y/src/postinstall.ts b/code/addons/a11y/src/postinstall.ts index 9a64796f5e1b..a9ae3cee42e2 100644 --- a/code/addons/a11y/src/postinstall.ts +++ b/code/addons/a11y/src/postinstall.ts @@ -11,5 +11,7 @@ const $ = execa({ }); export default async function postinstall(options: PostinstallOptions) { - await $`storybook automigrate addonA11yAddonTest ${options.yes ? '--yes' : ''}`; + await $({ + stdio: 'inherit', + })`storybook automigrate addonA11yAddonTest ${options.yes ? '--yes' : ''}`; } From 335c760fcf4c9946bb2e20370b7bf6208ca3516f Mon Sep 17 00:00:00 2001 From: Valentin Palkovic Date: Thu, 2 Jan 2025 11:51:02 +0100 Subject: [PATCH 08/27] Test From 15b941dbb4a64fa85bdd514cfd81dead31c94266 Mon Sep 17 00:00:00 2001 From: Valentin Palkovic Date: Thu, 2 Jan 2025 13:56:56 +0100 Subject: [PATCH 09/27] Refactor TestProviderRender to improve state management and update configuration handling --- .../test/src/components/TestProviderRender.tsx | 17 ++++++++++++++--- 1 file changed, 14 insertions(+), 3 deletions(-) diff --git a/code/addons/test/src/components/TestProviderRender.tsx b/code/addons/test/src/components/TestProviderRender.tsx index 1ead5c30a99d..f3a6c7cf3961 100644 --- a/code/addons/test/src/components/TestProviderRender.tsx +++ b/code/addons/test/src/components/TestProviderRender.tsx @@ -451,14 +451,25 @@ export const TestProviderRender: FC = ({ }; function useConfig(api: API, providerId: string, initialConfig: Config) { - const [currentConfig, setConfig] = useState(initialConfig); + const updateTestProviderState = useCallback( + (config: Config) => { + api.updateTestProviderState(providerId, { config }); + api.emit(TESTING_MODULE_CONFIG_CHANGE, { providerId, config }); + }, + [api, providerId] + ); + + const [currentConfig, setConfig] = useState(() => { + updateTestProviderState(initialConfig); + return initialConfig; + }); + const lastConfig = useRef(initialConfig); const saveConfig = useCallback( debounce((config: Config) => { if (!isEqual(config, lastConfig.current)) { - api.updateTestProviderState(providerId, { config }); - api.emit(TESTING_MODULE_CONFIG_CHANGE, { providerId, config }); + updateTestProviderState(config); lastConfig.current = config; } }, 500), From 3ff5a62d15f3bbaf8916104bd6f0faea24b938a2 Mon Sep 17 00:00:00 2001 From: Valentin Palkovic Date: Thu, 2 Jan 2025 14:06:46 +0100 Subject: [PATCH 10/27] Set a11ytest as preview annotation tags in portable stories run --- code/addons/test/src/vitest-plugin/index.ts | 9 +++++++++ code/addons/test/src/vitest-plugin/test-utils.ts | 5 +++-- 2 files changed, 12 insertions(+), 2 deletions(-) diff --git a/code/addons/test/src/vitest-plugin/index.ts b/code/addons/test/src/vitest-plugin/index.ts index 180075f9f891..9493811372a7 100644 --- a/code/addons/test/src/vitest-plugin/index.ts +++ b/code/addons/test/src/vitest-plugin/index.ts @@ -210,6 +210,15 @@ export const storybookTest = async (options?: UserOptions): Promise => { }, }; }, + getTags: () => { + const envConfig = JSON.parse(process.env.VITEST_STORYBOOK_CONFIG ?? '{}'); + + const isA11yEnabled = process.env.VITEST_STORYBOOK + ? (envConfig.a11y ?? false) + : true; + + return isA11yEnabled ? ['a11ytest'] : []; + }, }, // if there is a test.browser config AND test.browser.screenshotFailures is not explicitly set, we set it to false ...(inputConfig_ONLY_MUTATE_WHEN_STRICTLY_NEEDED_OR_YOU_WILL_BE_FIRED.test?.browser && diff --git a/code/addons/test/src/vitest-plugin/test-utils.ts b/code/addons/test/src/vitest-plugin/test-utils.ts index a00ff6d7f6fc..85150c5a7c2f 100644 --- a/code/addons/test/src/vitest-plugin/test-utils.ts +++ b/code/addons/test/src/vitest-plugin/test-utils.ts @@ -13,10 +13,11 @@ import { setViewport } from './viewports'; declare module '@vitest/browser/context' { interface BrowserCommands { getInitialGlobals: () => Promise>; + getTags: () => Promise; } } -const { getInitialGlobals } = server.commands; +const { getInitialGlobals, getTags } = server.commands; export const testStory = ( exportName: string, @@ -28,7 +29,7 @@ export const testStory = ( const composedStory = composeStory( story, meta, - { initialGlobals: (await getInitialGlobals?.()) ?? {} }, + { initialGlobals: (await getInitialGlobals?.()) ?? {}, tags: await getTags?.() }, undefined, exportName ); From e7b81bcf7e456c08cc51f2cecc7c994ce9094c75 Mon Sep 17 00:00:00 2001 From: Valentin Palkovic Date: Thu, 2 Jan 2025 14:31:28 +0100 Subject: [PATCH 11/27] Refactor a11y test configuration handling in vitest-plugin --- code/addons/test/src/vitest-plugin/index.ts | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/code/addons/test/src/vitest-plugin/index.ts b/code/addons/test/src/vitest-plugin/index.ts index 9493811372a7..314c52f9a928 100644 --- a/code/addons/test/src/vitest-plugin/index.ts +++ b/code/addons/test/src/vitest-plugin/index.ts @@ -200,24 +200,24 @@ export const storybookTest = async (options?: UserOptions): Promise => { getInitialGlobals: () => { const envConfig = JSON.parse(process.env.VITEST_STORYBOOK_CONFIG ?? '{}'); - const isA11yEnabled = process.env.VITEST_STORYBOOK + const shouldRunA11yTests = process.env.VITEST_STORYBOOK ? (envConfig.a11y ?? false) : true; return { a11y: { - manual: !isA11yEnabled, + manual: !shouldRunA11yTests, }, }; }, getTags: () => { const envConfig = JSON.parse(process.env.VITEST_STORYBOOK_CONFIG ?? '{}'); - const isA11yEnabled = process.env.VITEST_STORYBOOK + const shouldSetTag = process.env.VITEST_STORYBOOK ? (envConfig.a11y ?? false) - : true; + : false; - return isA11yEnabled ? ['a11ytest'] : []; + return shouldSetTag ? ['a11ytest'] : []; }, }, // if there is a test.browser config AND test.browser.screenshotFailures is not explicitly set, we set it to false From 21b452ac9ea24860ab38a3f91152cef1fea240f5 Mon Sep 17 00:00:00 2001 From: Valentin Palkovic Date: Tue, 7 Jan 2025 14:48:09 +0100 Subject: [PATCH 12/27] Rename a11ytest tag to a11y-test --- code/addons/a11y/src/constants.ts | 2 +- code/addons/a11y/src/preview.test.tsx | 2 +- .../src/components/TestProviderRender.tsx | 2 +- code/addons/test/src/vitest-plugin/index.ts | 2 +- .../core/template/stories/tags-add.stories.ts | 4 +-- .../template/stories/tags-config.stories.ts | 4 +-- .../template/stories/tags-remove.stories.ts | 4 +-- .../addon-a11y-addon-test.test.ts.snap | 4 +-- .../fixes/addon-a11y-addon-test.test.ts | 26 +++++++++---------- .../fixes/addon-a11y-addon-test.ts | 10 +++---- scripts/tasks/sandbox-parts.ts | 2 +- 11 files changed, 31 insertions(+), 31 deletions(-) diff --git a/code/addons/a11y/src/constants.ts b/code/addons/a11y/src/constants.ts index e7c1c319df06..2e3e1a06fc1f 100755 --- a/code/addons/a11y/src/constants.ts +++ b/code/addons/a11y/src/constants.ts @@ -14,4 +14,4 @@ export const TEST_PROVIDER_ID = 'storybook/addon-a11y/test-provider'; export const EVENTS = { RESULT, REQUEST, RUNNING, ERROR, MANUAL }; -export const A11Y_TEST_TAG = 'a11ytest'; +export const A11Y_TEST_TAG = 'a11y-test'; diff --git a/code/addons/a11y/src/preview.test.tsx b/code/addons/a11y/src/preview.test.tsx index d09ba89462ed..0ee4ebba829e 100644 --- a/code/addons/a11y/src/preview.test.tsx +++ b/code/addons/a11y/src/preview.test.tsx @@ -156,7 +156,7 @@ describe('afterEach', () => { }); }); - it('should run accessibility checks if "a11ytest" flag is not available and is not running in Vitest', async () => { + it('should run accessibility checks if "a11y-test" flag is not available and is not running in Vitest', async () => { const context = createContext({ tags: [], }); diff --git a/code/addons/test/src/components/TestProviderRender.tsx b/code/addons/test/src/components/TestProviderRender.tsx index 49e3a0a44127..f861ecac7848 100644 --- a/code/addons/test/src/components/TestProviderRender.tsx +++ b/code/addons/test/src/components/TestProviderRender.tsx @@ -139,7 +139,7 @@ export const TestProviderRender: FC = ({ return acc; }, new Set()); - if (allTags.has('a11ytest')) { + if (allTags.has('a11y-test')) { return true; } diff --git a/code/addons/test/src/vitest-plugin/index.ts b/code/addons/test/src/vitest-plugin/index.ts index 314c52f9a928..2c8ac257ebc1 100644 --- a/code/addons/test/src/vitest-plugin/index.ts +++ b/code/addons/test/src/vitest-plugin/index.ts @@ -217,7 +217,7 @@ export const storybookTest = async (options?: UserOptions): Promise => { ? (envConfig.a11y ?? false) : false; - return shouldSetTag ? ['a11ytest'] : []; + return shouldSetTag ? ['a11y-test'] : []; }, }, // if there is a test.browser config AND test.browser.screenshotFailures is not explicitly set, we set it to false diff --git a/code/core/template/stories/tags-add.stories.ts b/code/core/template/stories/tags-add.stories.ts index edb70171eba1..5e6c64b1caef 100644 --- a/code/core/template/stories/tags-add.stories.ts +++ b/code/core/template/stories/tags-add.stories.ts @@ -19,9 +19,9 @@ export const Inheritance = { tags: ['story-one', '!vitest'], play: async ({ canvasElement, tags }: PlayFunctionContext) => { const canvas = within(canvasElement); - if (tags.includes('a11ytest')) { + if (tags.includes('a11y-test')) { await expect(JSON.parse(canvas.getByTestId('pre').innerText)).toEqual({ - tags: ['a11ytest', 'story-one'], + tags: ['a11y-test', 'story-one'], }); } else { await expect(JSON.parse(canvas.getByTestId('pre').innerText)).toEqual({ diff --git a/code/core/template/stories/tags-config.stories.ts b/code/core/template/stories/tags-config.stories.ts index a57a70390ce1..5d6a1faacf0f 100644 --- a/code/core/template/stories/tags-config.stories.ts +++ b/code/core/template/stories/tags-config.stories.ts @@ -19,12 +19,12 @@ export const Inheritance = { tags: ['story-one', '!vitest'], play: async ({ canvasElement, tags }: PlayFunctionContext) => { const canvas = within(canvasElement); - if (tags.includes('a11ytest')) { + if (tags.includes('a11y-test')) { await expect(JSON.parse(canvas.getByTestId('pre').innerText)).toEqual({ tags: [ 'dev', 'test', - 'a11ytest', + 'a11y-test', 'component-one', 'component-two', 'autodocs', diff --git a/code/core/template/stories/tags-remove.stories.ts b/code/core/template/stories/tags-remove.stories.ts index 468f99614aa0..a2db33287754 100644 --- a/code/core/template/stories/tags-remove.stories.ts +++ b/code/core/template/stories/tags-remove.stories.ts @@ -19,9 +19,9 @@ export const Inheritance = { tags: ['story-one', '!vitest'], play: async ({ canvasElement, tags }: PlayFunctionContext) => { const canvas = within(canvasElement); - if (tags.includes('a11ytest')) { + if (tags.includes('a11y-test')) { await expect(JSON.parse(canvas.getByTestId('pre').innerText)).toEqual({ - tags: ['dev', 'test', 'a11ytest', 'component-one', 'autodocs', 'story-one'], + tags: ['dev', 'test', 'a11y-test', 'component-one', 'autodocs', 'story-one'], }); } else { await expect(JSON.parse(canvas.getByTestId('pre').innerText)).toEqual({ diff --git a/code/lib/cli-storybook/src/automigrate/fixes/__snapshots__/addon-a11y-addon-test.test.ts.snap b/code/lib/cli-storybook/src/automigrate/fixes/__snapshots__/addon-a11y-addon-test.test.ts.snap index 508306a1987f..d28ce40f1ca9 100644 --- a/code/lib/cli-storybook/src/automigrate/fixes/__snapshots__/addon-a11y-addon-test.test.ts.snap +++ b/code/lib/cli-storybook/src/automigrate/fixes/__snapshots__/addon-a11y-addon-test.test.ts.snap @@ -23,7 +23,7 @@ Please manually update your .storybook/preview. file to include the follo export default { ... -+ tags: ["a11ytest"], ++ tags: ["a11y-test"], } For more information, please refer to the accessibility addon documentation: @@ -88,7 +88,7 @@ Please manually update your .storybook/preview. file to include the follo export default { ... -+ tags: ["a11ytest"], ++ tags: ["a11y-test"], } For more information, please refer to the accessibility addon documentation: diff --git a/code/lib/cli-storybook/src/automigrate/fixes/addon-a11y-addon-test.test.ts b/code/lib/cli-storybook/src/automigrate/fixes/addon-a11y-addon-test.test.ts index dd400e18bb80..c3ce0425f837 100644 --- a/code/lib/cli-storybook/src/automigrate/fixes/addon-a11y-addon-test.test.ts +++ b/code/lib/cli-storybook/src/automigrate/fixes/addon-a11y-addon-test.test.ts @@ -107,7 +107,7 @@ describe('addonA11yAddonTest', () => { } else { return ` export default { - tags: ['a11ytest'], + tags: ['a11y-test'], } `; } @@ -286,7 +286,7 @@ describe('addonA11yAddonTest', () => { } else { return ` export default { - tags: ['a11ytest'], + tags: ['a11y-test'], } `; } @@ -588,9 +588,9 @@ describe('addonA11yAddonTest', () => { }, }, - // a11ytest tag controls whether accessibility tests are run as part of a standalone Vitest test run + // a11y-test tag controls whether accessibility tests are run as part of a standalone Vitest test run // For more information please visit: https://storybook.js.org/docs/writing-tests/accessibility-testing - tags: ['a11ytest'] + tags: ['a11y-test'] }; export default preview; @@ -621,7 +621,7 @@ describe('addonA11yAddonTest', () => { }, }, } - export const tags = ["a11ytest"]; + export const tags = ["a11y-test"]; `; expect(transformed).toBe(expected); @@ -651,9 +651,9 @@ describe('addonA11yAddonTest', () => { import type { Preview } from '@storybook/react'; const preview: Preview = { - // a11ytest tag controls whether accessibility tests are run as part of a standalone Vitest test run + // a11y-test tag controls whether accessibility tests are run as part of a standalone Vitest test run // For more information please visit: https://storybook.js.org/docs/writing-tests/accessibility-testing - tags: ['existingTag', 'a11ytest'], + tags: ['existingTag', 'a11y-test'], parameters: { controls: { matchers: { @@ -670,12 +670,12 @@ describe('addonA11yAddonTest', () => { expect(transformed).toBe(j(expected).toSource()); }); - it('should not add a11ytest if it already exists in the tags property', () => { + it('should not add a11y-test if it already exists in the tags property', () => { const source = ` import type { Preview } from '@storybook/react'; const preview: Preview = { - tags: ['a11ytest'], + tags: ['a11y-test'], parameters: { controls: { matchers: { @@ -720,9 +720,9 @@ describe('addonA11yAddonTest', () => { }, }, - // a11ytest tag controls whether accessibility tests are run as part of a standalone Vitest test run + // a11y-test tag controls whether accessibility tests are run as part of a standalone Vitest test run // For more information please visit: https://storybook.js.org/docs/writing-tests/accessibility-testing - tags: ["a11ytest"] + tags: ["a11y-test"] }; `; @@ -747,9 +747,9 @@ describe('addonA11yAddonTest', () => { const transformed = transformPreviewFile(source); const expected = ` export default { - // a11ytest tag controls whether accessibility tests are run as part of a standalone Vitest test run + // a11y-test tag controls whether accessibility tests are run as part of a standalone Vitest test run // For more information please visit: https://storybook.js.org/docs/writing-tests/accessibility-testing - tags: ['existingTag', 'a11ytest'], + tags: ['existingTag', 'a11y-test'], parameters: { controls: { matchers: { diff --git a/code/lib/cli-storybook/src/automigrate/fixes/addon-a11y-addon-test.ts b/code/lib/cli-storybook/src/automigrate/fixes/addon-a11y-addon-test.ts index 8f6ca6d6b5bc..1f0b04111eae 100644 --- a/code/lib/cli-storybook/src/automigrate/fixes/addon-a11y-addon-test.ts +++ b/code/lib/cli-storybook/src/automigrate/fixes/addon-a11y-addon-test.ts @@ -100,7 +100,7 @@ export const addonA11yAddonTest: Fix = { const previewSetupSource = readFileSync(previewFile, 'utf8'); skipVitestSetupTransformation = vitestSetupSource.includes('@storybook/addon-a11y'); - skipPreviewTransformation = previewSetupSource.includes('a11ytest'); + skipPreviewTransformation = previewSetupSource.includes('a11y-test'); if (skipVitestSetupTransformation && skipPreviewTransformation) { return null; @@ -194,7 +194,7 @@ export const addonA11yAddonTest: Fix = { ${picocolors.gray('export default {')} ${picocolors.gray('...')} - ${picocolors.green('+ tags: ["a11ytest"],')} + ${picocolors.green('+ tags: ["a11y-test"],')} ${picocolors.gray('}')} `); } else { @@ -275,11 +275,11 @@ export function transformPreviewFile(source: string) { const tags = previewConfig.getFieldNode(['tags']); const tagsValue = previewConfig.getFieldValue(['tags']) ?? []; - if (tags && tagsValue && (tagsValue.includes('a11ytest') || tagsValue.includes('!a11ytest'))) { + if (tags && tagsValue && (tagsValue.includes('a11y-test') || tagsValue.includes('!a11y-test'))) { return source; } - previewConfig.setFieldValue(['tags'], [...tagsValue, 'a11ytest']); + previewConfig.setFieldValue(['tags'], [...tagsValue, 'a11y-test']); const formattedPreviewConfig = formatConfig(previewConfig); const lines = formattedPreviewConfig.split('\n'); @@ -295,7 +295,7 @@ export function transformPreviewFile(source: string) { const indentation = tagsLine?.match(/^\s*/)?.[0]; // Add the comment with the same indentation level - const comment = `${indentation}// a11ytest tag controls whether accessibility tests are run as part of a standalone Vitest test run\n${indentation}// For more information please visit: https://storybook.js.org/docs/writing-tests/accessibility-testing`; + const comment = `${indentation}// a11y-test tag controls whether accessibility tests are run as part of a standalone Vitest test run\n${indentation}// For more information please visit: https://storybook.js.org/docs/writing-tests/accessibility-testing`; lines.splice(tagsLineIndex, 0, comment); return lines.join('\n'); diff --git a/scripts/tasks/sandbox-parts.ts b/scripts/tasks/sandbox-parts.ts index 271fbba9dcd8..fef29d8eb663 100644 --- a/scripts/tasks/sandbox-parts.ts +++ b/scripts/tasks/sandbox-parts.ts @@ -823,7 +823,7 @@ export const extendPreview: Task['run'] = async ({ template, sandboxDir }) => { const previewConfig = await readConfig({ cwd: sandboxDir, fileName: 'preview' }); if (template.expected.builder.includes('vite')) { - previewConfig.setFieldValue(['tags'], ['vitest', '!a11ytest']); + previewConfig.setFieldValue(['tags'], ['vitest', '!a11y-test']); } await writeConfig(previewConfig); From 45543214eb06044fa912586bf3fd80e1ea0a0b38 Mon Sep 17 00:00:00 2001 From: Valentin Palkovic Date: Tue, 7 Jan 2025 15:46:53 +0100 Subject: [PATCH 13/27] Refactor a11y-addon-test to return formatted config instead of raw lines --- .../src/automigrate/fixes/addon-a11y-addon-test.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/code/lib/cli-storybook/src/automigrate/fixes/addon-a11y-addon-test.ts b/code/lib/cli-storybook/src/automigrate/fixes/addon-a11y-addon-test.ts index 1f0b04111eae..53ff7dadd1bc 100644 --- a/code/lib/cli-storybook/src/automigrate/fixes/addon-a11y-addon-test.ts +++ b/code/lib/cli-storybook/src/automigrate/fixes/addon-a11y-addon-test.ts @@ -298,5 +298,5 @@ export function transformPreviewFile(source: string) { const comment = `${indentation}// a11y-test tag controls whether accessibility tests are run as part of a standalone Vitest test run\n${indentation}// For more information please visit: https://storybook.js.org/docs/writing-tests/accessibility-testing`; lines.splice(tagsLineIndex, 0, comment); - return lines.join('\n'); + return formatConfig(loadConfig(lines.join('\n')).parse()); } From 51cfe14361a0e89ef7b598b139233e502e7667df Mon Sep 17 00:00:00 2001 From: Valentin Palkovic Date: Tue, 7 Jan 2025 16:08:07 +0100 Subject: [PATCH 14/27] Fix tests for Windows --- .../fixes/addon-a11y-addon-test.test.ts | 18 ++++++++++-------- 1 file changed, 10 insertions(+), 8 deletions(-) diff --git a/code/lib/cli-storybook/src/automigrate/fixes/addon-a11y-addon-test.test.ts b/code/lib/cli-storybook/src/automigrate/fixes/addon-a11y-addon-test.test.ts index c3ce0425f837..15300ed951ba 100644 --- a/code/lib/cli-storybook/src/automigrate/fixes/addon-a11y-addon-test.test.ts +++ b/code/lib/cli-storybook/src/automigrate/fixes/addon-a11y-addon-test.test.ts @@ -1,5 +1,7 @@ import { beforeEach, describe, expect, it, vi } from 'vitest'; +import { formatConfig, loadConfig } from '@storybook/core/csf-tools'; + import { existsSync, readFileSync, writeFileSync } from 'fs'; import * as jscodeshift from 'jscodeshift'; import path from 'path'; @@ -596,7 +598,7 @@ describe('addonA11yAddonTest', () => { export default preview; `; - expect(transformed).toBe(expected); + expect(transformed).toBe(formatConfig(loadConfig(expected).parse())); }); it('should add a new tags property if it does not exist and a default export does not exist', () => { @@ -624,7 +626,7 @@ describe('addonA11yAddonTest', () => { export const tags = ["a11y-test"]; `; - expect(transformed).toBe(expected); + expect(transformed).toBe(formatConfig(loadConfig(expected).parse())); }); it('should extend the existing tags property', () => { @@ -667,11 +669,11 @@ describe('addonA11yAddonTest', () => { export default preview; `; - expect(transformed).toBe(j(expected).toSource()); + expect(transformed).toBe(formatConfig(loadConfig(expected).parse())); }); it('should not add a11y-test if it already exists in the tags property', () => { - const source = ` + const expected = ` import type { Preview } from '@storybook/react'; const preview: Preview = { @@ -689,9 +691,9 @@ describe('addonA11yAddonTest', () => { export default preview; `; - const transformed = transformPreviewFile(source); + const transformed = transformPreviewFile(expected); - expect(transformed).toBe(source); + expect(transformed).toBe(formatConfig(loadConfig(expected).parse())); }); it('should handle the default export without type annotations', () => { @@ -726,7 +728,7 @@ describe('addonA11yAddonTest', () => { }; `; - expect(transformed).toBe(expected); + expect(transformed).toBe(formatConfig(loadConfig(expected).parse())); }); it('should extend the existing tags property without type annotations', () => { @@ -761,7 +763,7 @@ describe('addonA11yAddonTest', () => { }; `; - expect(transformed).toBe(expected); + expect(transformed).toBe(formatConfig(loadConfig(expected).parse())); }); }); }); From 980a51e382452e91f7e4bce0cd2448f447c6138e Mon Sep 17 00:00:00 2001 From: Valentin Palkovic Date: Wed, 8 Jan 2025 11:27:19 +0100 Subject: [PATCH 15/27] Fix tests for Windows --- .../fixes/addon-a11y-addon-test.test.ts | 40 +++++++++---------- 1 file changed, 20 insertions(+), 20 deletions(-) diff --git a/code/lib/cli-storybook/src/automigrate/fixes/addon-a11y-addon-test.test.ts b/code/lib/cli-storybook/src/automigrate/fixes/addon-a11y-addon-test.test.ts index 15300ed951ba..5223417ccf90 100644 --- a/code/lib/cli-storybook/src/automigrate/fixes/addon-a11y-addon-test.test.ts +++ b/code/lib/cli-storybook/src/automigrate/fixes/addon-a11y-addon-test.test.ts @@ -544,7 +544,7 @@ describe('addonA11yAddonTest', () => { const s = readFileSync(setupFile, 'utf8'); const transformedCode = transformSetupFile(s); - expect(transformedCode).toMatchInlineSnapshot(` + expect(transformedCode).toMatchInlineSnapshot(dedent` "import * as a11yAddonAnnotations from "@storybook/addon-a11y/preview"; import { beforeAll } from 'vitest'; import { setProjectAnnotations } from 'storybook'; @@ -559,7 +559,7 @@ describe('addonA11yAddonTest', () => { describe('transformPreviewFile', () => { it('should add a new tags property if it does not exist', () => { - const source = ` + const source = dedent` import type { Preview } from '@storybook/react'; const preview: Preview = { @@ -577,7 +577,7 @@ describe('addonA11yAddonTest', () => { `; const transformed = transformPreviewFile(source); - const expected = ` + const expected = dedent` import type { Preview } from '@storybook/react'; const preview: Preview = { @@ -602,7 +602,7 @@ describe('addonA11yAddonTest', () => { }); it('should add a new tags property if it does not exist and a default export does not exist', () => { - const source = ` + const source = dedent` export const parameters = { controls: { matchers: { @@ -614,7 +614,7 @@ describe('addonA11yAddonTest', () => { `; const transformed = transformPreviewFile(source); - const expected = ` + const expected = dedent` export const parameters = { controls: { matchers: { @@ -630,11 +630,11 @@ describe('addonA11yAddonTest', () => { }); it('should extend the existing tags property', () => { - const source = ` - import type { Preview } from '@storybook/react'; + const source = dedent` + import type { Preview } from "@storybook/react"; const preview: Preview = { - tags: ['existingTag'], + tags: ["existingTag"], parameters: { controls: { matchers: { @@ -649,13 +649,13 @@ describe('addonA11yAddonTest', () => { `; const transformed = transformPreviewFile(source); - const expected = ` - import type { Preview } from '@storybook/react'; + const expected = dedent` + import type { Preview } from "@storybook/react"; const preview: Preview = { // a11y-test tag controls whether accessibility tests are run as part of a standalone Vitest test run // For more information please visit: https://storybook.js.org/docs/writing-tests/accessibility-testing - tags: ['existingTag', 'a11y-test'], + tags: ["existingTag", "a11y-test"], parameters: { controls: { matchers: { @@ -673,11 +673,11 @@ describe('addonA11yAddonTest', () => { }); it('should not add a11y-test if it already exists in the tags property', () => { - const expected = ` - import type { Preview } from '@storybook/react'; + const expected = dedent` + import type { Preview } from "@storybook/react"; const preview: Preview = { - tags: ['a11y-test'], + tags: ["a11y-test"], parameters: { controls: { matchers: { @@ -697,7 +697,7 @@ describe('addonA11yAddonTest', () => { }); it('should handle the default export without type annotations', () => { - const source = ` + const source = dedent` export default { parameters: { controls: { @@ -711,7 +711,7 @@ describe('addonA11yAddonTest', () => { `; const transformed = transformPreviewFile(source); - const expected = ` + const expected = dedent` export default { parameters: { controls: { @@ -732,9 +732,9 @@ describe('addonA11yAddonTest', () => { }); it('should extend the existing tags property without type annotations', () => { - const source = ` + const source = dedent` export default { - tags: ['existingTag'], + tags: ["existingTag"], parameters: { controls: { matchers: { @@ -747,11 +747,11 @@ describe('addonA11yAddonTest', () => { `; const transformed = transformPreviewFile(source); - const expected = ` + const expected = dedent` export default { // a11y-test tag controls whether accessibility tests are run as part of a standalone Vitest test run // For more information please visit: https://storybook.js.org/docs/writing-tests/accessibility-testing - tags: ['existingTag', 'a11y-test'], + tags: ["existingTag", "a11y-test"], parameters: { controls: { matchers: { From b06a35f2efd5a95a56be9f48185a533e046c182c Mon Sep 17 00:00:00 2001 From: Valentin Palkovic Date: Wed, 8 Jan 2025 11:43:20 +0100 Subject: [PATCH 16/27] Fix tests for Windows --- .../src/automigrate/fixes/addon-a11y-addon-test.test.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/code/lib/cli-storybook/src/automigrate/fixes/addon-a11y-addon-test.test.ts b/code/lib/cli-storybook/src/automigrate/fixes/addon-a11y-addon-test.test.ts index 5223417ccf90..b7901607b0d3 100644 --- a/code/lib/cli-storybook/src/automigrate/fixes/addon-a11y-addon-test.test.ts +++ b/code/lib/cli-storybook/src/automigrate/fixes/addon-a11y-addon-test.test.ts @@ -693,7 +693,7 @@ describe('addonA11yAddonTest', () => { const transformed = transformPreviewFile(expected); - expect(transformed).toBe(formatConfig(loadConfig(expected).parse())); + expect(transformed).toBe(expected); }); it('should handle the default export without type annotations', () => { From 03093e118ce057653f9f2653d1c1721b533ebe3b Mon Sep 17 00:00:00 2001 From: Valentin Palkovic Date: Thu, 9 Jan 2025 12:41:16 +0100 Subject: [PATCH 17/27] Refactor addon-a11y automigration to place a11y-test tag as a comment in the preview file --- .../fixes/addon-a11y-addon-test.test.ts | 8 ++-- .../fixes/addon-a11y-addon-test.ts | 37 +++++++++++++++++-- 2 files changed, 37 insertions(+), 8 deletions(-) diff --git a/code/lib/cli-storybook/src/automigrate/fixes/addon-a11y-addon-test.test.ts b/code/lib/cli-storybook/src/automigrate/fixes/addon-a11y-addon-test.test.ts index b7901607b0d3..d444b170362e 100644 --- a/code/lib/cli-storybook/src/automigrate/fixes/addon-a11y-addon-test.test.ts +++ b/code/lib/cli-storybook/src/automigrate/fixes/addon-a11y-addon-test.test.ts @@ -592,7 +592,7 @@ describe('addonA11yAddonTest', () => { // a11y-test tag controls whether accessibility tests are run as part of a standalone Vitest test run // For more information please visit: https://storybook.js.org/docs/writing-tests/accessibility-testing - tags: ['a11y-test'] + tags: [/*'a11y-test'*/] }; export default preview; @@ -655,7 +655,7 @@ describe('addonA11yAddonTest', () => { const preview: Preview = { // a11y-test tag controls whether accessibility tests are run as part of a standalone Vitest test run // For more information please visit: https://storybook.js.org/docs/writing-tests/accessibility-testing - tags: ["existingTag", "a11y-test"], + tags: ["existingTag"/*, "a11y-test"*/], parameters: { controls: { matchers: { @@ -724,7 +724,7 @@ describe('addonA11yAddonTest', () => { // a11y-test tag controls whether accessibility tests are run as part of a standalone Vitest test run // For more information please visit: https://storybook.js.org/docs/writing-tests/accessibility-testing - tags: ["a11y-test"] + tags: [/*"a11y-test"*/] }; `; @@ -751,7 +751,7 @@ describe('addonA11yAddonTest', () => { export default { // a11y-test tag controls whether accessibility tests are run as part of a standalone Vitest test run // For more information please visit: https://storybook.js.org/docs/writing-tests/accessibility-testing - tags: ["existingTag", "a11y-test"], + tags: ["existingTag"/*, "a11y-test"*/], parameters: { controls: { matchers: { diff --git a/code/lib/cli-storybook/src/automigrate/fixes/addon-a11y-addon-test.ts b/code/lib/cli-storybook/src/automigrate/fixes/addon-a11y-addon-test.ts index 53ff7dadd1bc..c2b9e3ceaa72 100644 --- a/code/lib/cli-storybook/src/automigrate/fixes/addon-a11y-addon-test.ts +++ b/code/lib/cli-storybook/src/automigrate/fixes/addon-a11y-addon-test.ts @@ -1,6 +1,9 @@ +import { EOL } from 'node:os'; + import { rendererPackages } from 'storybook/internal/common'; import { formatConfig, loadConfig } from 'storybook/internal/csf-tools'; +import { type ArrayExpression } from '@babel/types'; import { existsSync, readFileSync, writeFileSync } from 'fs'; import * as jscodeshift from 'jscodeshift'; import path from 'path'; @@ -273,13 +276,38 @@ export function transformSetupFile(source: string) { export function transformPreviewFile(source: string) { const previewConfig = loadConfig(source).parse(); const tags = previewConfig.getFieldNode(['tags']); + const tagsNode = previewConfig.getFieldNode(['tags']) as ArrayExpression; const tagsValue = previewConfig.getFieldValue(['tags']) ?? []; if (tags && tagsValue && (tagsValue.includes('a11y-test') || tagsValue.includes('!a11y-test'))) { return source; } - previewConfig.setFieldValue(['tags'], [...tagsValue, 'a11y-test']); + if (tagsNode) { + const tagsElementsLength = (tagsNode as ArrayExpression).elements.length; + const lastTagElement = tagsNode.elements[tagsElementsLength - 1]; + + if (lastTagElement) { + // t.addComment(lastTagElement, 'trailing', ", 'a11y-test'"); + // @ts-expect-error The commented line above would be the proper way of how to add a trailing comment but it is not supported by recast. + // https://github.com/benjamn/recast/issues/572 + lastTagElement.comments = [ + { + type: 'CommentBlock', + leading: false, + trailing: true, + value: ', "a11y-test"', + }, + ]; + previewConfig.setFieldNode(['tags'], tagsNode); + } + } else { + // t.addComment(newTagsNode, 'inner', 'a11y-test'); + // The commented line above would be the proper way of how to add a trailing comment but innercomments are not supported by recast. + // https://github.com/benjamn/recast/issues/1417 + // This case will be handled later by parsing the source code and editing it later. + previewConfig.setFieldValue(['tags'], ['a11y-test']); + } const formattedPreviewConfig = formatConfig(previewConfig); const lines = formattedPreviewConfig.split('\n'); @@ -291,12 +319,13 @@ export function transformPreviewFile(source: string) { } // Determine the indentation level of the "tags" property - const tagsLine = lines[tagsLineIndex]; - const indentation = tagsLine?.match(/^\s*/)?.[0]; + lines[tagsLineIndex] = lines[tagsLineIndex].replace("['a11y-test']", "[/*'a11y-test'*/]"); + lines[tagsLineIndex] = lines[tagsLineIndex].replace('["a11y-test"]', '[/*"a11y-test"*/]'); + const indentation = lines[tagsLineIndex]?.match(/^\s*/)?.[0]; // Add the comment with the same indentation level const comment = `${indentation}// a11y-test tag controls whether accessibility tests are run as part of a standalone Vitest test run\n${indentation}// For more information please visit: https://storybook.js.org/docs/writing-tests/accessibility-testing`; lines.splice(tagsLineIndex, 0, comment); - return formatConfig(loadConfig(lines.join('\n')).parse()); + return lines.join(EOL); } From 090276bc3e2a0d3d9d6b93a4a8fc314b53e5db46 Mon Sep 17 00:00:00 2001 From: Valentin Palkovic Date: Thu, 9 Jan 2025 14:25:12 +0100 Subject: [PATCH 18/27] Fix for windows --- .../fixes/addon-a11y-addon-test.test.ts | 38 ++++++++++--------- .../fixes/addon-a11y-addon-test.ts | 14 +++---- 2 files changed, 26 insertions(+), 26 deletions(-) diff --git a/code/lib/cli-storybook/src/automigrate/fixes/addon-a11y-addon-test.test.ts b/code/lib/cli-storybook/src/automigrate/fixes/addon-a11y-addon-test.test.ts index d444b170362e..1ea47fa33ac0 100644 --- a/code/lib/cli-storybook/src/automigrate/fixes/addon-a11y-addon-test.test.ts +++ b/code/lib/cli-storybook/src/automigrate/fixes/addon-a11y-addon-test.test.ts @@ -1,5 +1,7 @@ import { beforeEach, describe, expect, it, vi } from 'vitest'; +import { formatFileContent } from '@storybook/core/common'; + import { formatConfig, loadConfig } from '@storybook/core/csf-tools'; import { existsSync, readFileSync, writeFileSync } from 'fs'; @@ -558,7 +560,7 @@ describe('addonA11yAddonTest', () => { }); describe('transformPreviewFile', () => { - it('should add a new tags property if it does not exist', () => { + it('should add a new tags property if it does not exist', async () => { const source = dedent` import type { Preview } from '@storybook/react'; @@ -576,7 +578,7 @@ describe('addonA11yAddonTest', () => { export default preview; `; - const transformed = transformPreviewFile(source); + const transformed = await transformPreviewFile(source, process.cwd()); const expected = dedent` import type { Preview } from '@storybook/react'; @@ -598,10 +600,10 @@ describe('addonA11yAddonTest', () => { export default preview; `; - expect(transformed).toBe(formatConfig(loadConfig(expected).parse())); + expect(transformed).toBe(await formatFileContent(process.cwd(), expected)); }); - it('should add a new tags property if it does not exist and a default export does not exist', () => { + it('should add a new tags property if it does not exist and a default export does not exist', async () => { const source = dedent` export const parameters = { controls: { @@ -613,7 +615,7 @@ describe('addonA11yAddonTest', () => { } `; - const transformed = transformPreviewFile(source); + const transformed = await transformPreviewFile(source, process.cwd()); const expected = dedent` export const parameters = { controls: { @@ -626,10 +628,10 @@ describe('addonA11yAddonTest', () => { export const tags = ["a11y-test"]; `; - expect(transformed).toBe(formatConfig(loadConfig(expected).parse())); + expect(transformed).toBe(await formatFileContent(process.cwd(), expected)); }); - it('should extend the existing tags property', () => { + it('should extend the existing tags property', async () => { const source = dedent` import type { Preview } from "@storybook/react"; @@ -648,7 +650,7 @@ describe('addonA11yAddonTest', () => { export default preview; `; - const transformed = transformPreviewFile(source); + const transformed = await transformPreviewFile(source, process.cwd()); const expected = dedent` import type { Preview } from "@storybook/react"; @@ -669,10 +671,10 @@ describe('addonA11yAddonTest', () => { export default preview; `; - expect(transformed).toBe(formatConfig(loadConfig(expected).parse())); + expect(transformed).toBe(await formatFileContent(process.cwd(), expected)); }); - it('should not add a11y-test if it already exists in the tags property', () => { + it('should not add a11y-test if it already exists in the tags property', async () => { const expected = dedent` import type { Preview } from "@storybook/react"; @@ -691,12 +693,12 @@ describe('addonA11yAddonTest', () => { export default preview; `; - const transformed = transformPreviewFile(expected); + const transformed = await transformPreviewFile(expected, process.cwd()); - expect(transformed).toBe(expected); + expect(transformed).toBe(await formatFileContent(process.cwd(), expected)); }); - it('should handle the default export without type annotations', () => { + it('should handle the default export without type annotations', async () => { const source = dedent` export default { parameters: { @@ -710,7 +712,7 @@ describe('addonA11yAddonTest', () => { }; `; - const transformed = transformPreviewFile(source); + const transformed = await transformPreviewFile(source, process.cwd()); const expected = dedent` export default { parameters: { @@ -728,10 +730,10 @@ describe('addonA11yAddonTest', () => { }; `; - expect(transformed).toBe(formatConfig(loadConfig(expected).parse())); + expect(transformed).toBe(await formatFileContent(process.cwd(), expected)); }); - it('should extend the existing tags property without type annotations', () => { + it('should extend the existing tags property without type annotations', async () => { const source = dedent` export default { tags: ["existingTag"], @@ -746,7 +748,7 @@ describe('addonA11yAddonTest', () => { }; `; - const transformed = transformPreviewFile(source); + const transformed = await transformPreviewFile(source, process.cwd()); const expected = dedent` export default { // a11y-test tag controls whether accessibility tests are run as part of a standalone Vitest test run @@ -763,7 +765,7 @@ describe('addonA11yAddonTest', () => { }; `; - expect(transformed).toBe(formatConfig(loadConfig(expected).parse())); + expect(transformed).toBe(await formatFileContent(process.cwd(), expected)); }); }); }); diff --git a/code/lib/cli-storybook/src/automigrate/fixes/addon-a11y-addon-test.ts b/code/lib/cli-storybook/src/automigrate/fixes/addon-a11y-addon-test.ts index c2b9e3ceaa72..30fd3dcc8f04 100644 --- a/code/lib/cli-storybook/src/automigrate/fixes/addon-a11y-addon-test.ts +++ b/code/lib/cli-storybook/src/automigrate/fixes/addon-a11y-addon-test.ts @@ -1,6 +1,4 @@ -import { EOL } from 'node:os'; - -import { rendererPackages } from 'storybook/internal/common'; +import { formatFileContent, rendererPackages } from 'storybook/internal/common'; import { formatConfig, loadConfig } from 'storybook/internal/csf-tools'; import { type ArrayExpression } from '@babel/types'; @@ -123,14 +121,14 @@ export const addonA11yAddonTest: Fix = { } }; - const getTransformedPreviewCode = () => { + const getTransformedPreviewCode = async () => { if (!previewFile || skipPreviewTransformation) { return null; } try { const previewSetupSource = readFileSync(previewFile, 'utf8'); - return transformPreviewFile(previewSetupSource); + return await transformPreviewFile(previewSetupSource, previewFile); } catch (e) { return null; } @@ -140,7 +138,7 @@ export const addonA11yAddonTest: Fix = { setupFile: vitestSetupFile, previewFile: previewFile, transformedSetupCode: getTransformedSetupCode(), - transformedPreviewCode: getTransformedPreviewCode(), + transformedPreviewCode: await getTransformedPreviewCode(), skipVitestSetupTransformation, skipPreviewTransformation, }; @@ -273,7 +271,7 @@ export function transformSetupFile(source: string) { return root.toSource(); } -export function transformPreviewFile(source: string) { +export async function transformPreviewFile(source: string, filePath: string) { const previewConfig = loadConfig(source).parse(); const tags = previewConfig.getFieldNode(['tags']); const tagsNode = previewConfig.getFieldNode(['tags']) as ArrayExpression; @@ -327,5 +325,5 @@ export function transformPreviewFile(source: string) { const comment = `${indentation}// a11y-test tag controls whether accessibility tests are run as part of a standalone Vitest test run\n${indentation}// For more information please visit: https://storybook.js.org/docs/writing-tests/accessibility-testing`; lines.splice(tagsLineIndex, 0, comment); - return lines.join(EOL); + return formatFileContent(filePath, lines.join('\n')); } From fc2610f44a9f82b282fdbae9eb2ca33d2e78c7d8 Mon Sep 17 00:00:00 2001 From: Valentin Palkovic Date: Thu, 9 Jan 2025 14:56:12 +0100 Subject: [PATCH 19/27] Fix for windows --- .../addon-a11y-addon-test.test.ts.snap | 104 +++++++++++++++++ .../fixes/addon-a11y-addon-test.test.ts | 109 +++--------------- 2 files changed, 118 insertions(+), 95 deletions(-) diff --git a/code/lib/cli-storybook/src/automigrate/fixes/__snapshots__/addon-a11y-addon-test.test.ts.snap b/code/lib/cli-storybook/src/automigrate/fixes/__snapshots__/addon-a11y-addon-test.test.ts.snap index d28ce40f1ca9..5a567a94671f 100644 --- a/code/lib/cli-storybook/src/automigrate/fixes/__snapshots__/addon-a11y-addon-test.test.ts.snap +++ b/code/lib/cli-storybook/src/automigrate/fixes/__snapshots__/addon-a11y-addon-test.test.ts.snap @@ -94,3 +94,107 @@ export default { For more information, please refer to the accessibility addon documentation: https://storybook.js.org/docs/writing-tests/accessibility-testing#test-addon-integration" `; + +exports[`addonA11yAddonTest > transformPreviewFile > should add a new tags property if it does not exist 1`] = ` +"import type { Preview } from '@storybook/react'; + +const preview: Preview = { + parameters: { + controls: { + matchers: { + color: /(background|color)$/i, + date: /Date$/i, + }, + }, + }, + + // a11y-test tag controls whether accessibility tests are run as part of a standalone Vitest test run + // For more information please visit: https://storybook.js.org/docs/writing-tests/accessibility-testing + tags: [/*'a11y-test'*/] +}; + +export default preview;" +`; + +exports[`addonA11yAddonTest > transformPreviewFile > should add a new tags property if it does not exist and a default export does not exist 1`] = ` +"export const parameters = { + controls: { + matchers: { + color: /(background|color)$/i, + date: /Date$/i, + }, + }, +} +export const tags = ["a11y-test"];" +`; + +exports[`addonA11yAddonTest > transformPreviewFile > should extend the existing tags property 1`] = ` +"import type { Preview } from "@storybook/react"; + +const preview: Preview = { + // a11y-test tag controls whether accessibility tests are run as part of a standalone Vitest test run + // For more information please visit: https://storybook.js.org/docs/writing-tests/accessibility-testing + tags: ["existingTag"/*, "a11y-test"*/], + parameters: { + controls: { + matchers: { + color: /(background|color)$/i, + date: /Date$/i, + }, + }, + }, +}; + +export default preview;" +`; + +exports[`addonA11yAddonTest > transformPreviewFile > should extend the existing tags property without type annotations 1`] = ` +"export default { + // a11y-test tag controls whether accessibility tests are run as part of a standalone Vitest test run + // For more information please visit: https://storybook.js.org/docs/writing-tests/accessibility-testing + tags: ["existingTag"/*, "a11y-test"*/], + parameters: { + controls: { + matchers: { + color: /(background|color)$/i, + date: /Date$/i, + }, + }, + }, +};" +`; + +exports[`addonA11yAddonTest > transformPreviewFile > should handle the default export without type annotations 1`] = ` +"export default { + parameters: { + controls: { + matchers: { + color: /(background|color)$/i, + date: /Date$/i, + }, + }, + }, + + // a11y-test tag controls whether accessibility tests are run as part of a standalone Vitest test run + // For more information please visit: https://storybook.js.org/docs/writing-tests/accessibility-testing + tags: [/*"a11y-test"*/] +};" +`; + +exports[`addonA11yAddonTest > transformPreviewFile > should not add a11y-test if it already exists in the tags property 1`] = ` +"import type { Preview } from "@storybook/react"; + +const preview: Preview = { + tags: ["a11y-test"], + parameters: { + controls: { + matchers: { + color: /(background|color)$/i, + date: /Date$/i, + }, + }, + }, +}; + +export default preview;" +`; diff --git a/code/lib/cli-storybook/src/automigrate/fixes/addon-a11y-addon-test.test.ts b/code/lib/cli-storybook/src/automigrate/fixes/addon-a11y-addon-test.test.ts index 1ea47fa33ac0..e4f9906534a9 100644 --- a/code/lib/cli-storybook/src/automigrate/fixes/addon-a11y-addon-test.test.ts +++ b/code/lib/cli-storybook/src/automigrate/fixes/addon-a11y-addon-test.test.ts @@ -563,7 +563,7 @@ describe('addonA11yAddonTest', () => { it('should add a new tags property if it does not exist', async () => { const source = dedent` import type { Preview } from '@storybook/react'; - + const preview: Preview = { parameters: { controls: { @@ -574,33 +574,13 @@ describe('addonA11yAddonTest', () => { }, }, }; - + export default preview; `; const transformed = await transformPreviewFile(source, process.cwd()); - const expected = dedent` - import type { Preview } from '@storybook/react'; - - const preview: Preview = { - parameters: { - controls: { - matchers: { - color: /(background|color)$/i, - date: /Date$/i, - }, - }, - }, - - // a11y-test tag controls whether accessibility tests are run as part of a standalone Vitest test run - // For more information please visit: https://storybook.js.org/docs/writing-tests/accessibility-testing - tags: [/*'a11y-test'*/] - }; - - export default preview; - `; - expect(transformed).toBe(await formatFileContent(process.cwd(), expected)); + expect(transformed).toMatchSnapshot(); }); it('should add a new tags property if it does not exist and a default export does not exist', async () => { @@ -616,25 +596,14 @@ describe('addonA11yAddonTest', () => { `; const transformed = await transformPreviewFile(source, process.cwd()); - const expected = dedent` - export const parameters = { - controls: { - matchers: { - color: /(background|color)$/i, - date: /Date$/i, - }, - }, - } - export const tags = ["a11y-test"]; - `; - expect(transformed).toBe(await formatFileContent(process.cwd(), expected)); + expect(transformed).toMatchSnapshot(); }); it('should extend the existing tags property', async () => { const source = dedent` import type { Preview } from "@storybook/react"; - + const preview: Preview = { tags: ["existingTag"], parameters: { @@ -646,38 +615,19 @@ describe('addonA11yAddonTest', () => { }, }, }; - + export default preview; `; const transformed = await transformPreviewFile(source, process.cwd()); - const expected = dedent` - import type { Preview } from "@storybook/react"; - - const preview: Preview = { - // a11y-test tag controls whether accessibility tests are run as part of a standalone Vitest test run - // For more information please visit: https://storybook.js.org/docs/writing-tests/accessibility-testing - tags: ["existingTag"/*, "a11y-test"*/], - parameters: { - controls: { - matchers: { - color: /(background|color)$/i, - date: /Date$/i, - }, - }, - }, - }; - - export default preview; - `; - expect(transformed).toBe(await formatFileContent(process.cwd(), expected)); + expect(transformed).toMatchSnapshot(); }); it('should not add a11y-test if it already exists in the tags property', async () => { - const expected = dedent` + const source = dedent` import type { Preview } from "@storybook/react"; - + const preview: Preview = { tags: ["a11y-test"], parameters: { @@ -689,13 +639,13 @@ describe('addonA11yAddonTest', () => { }, }, }; - + export default preview; `; - const transformed = await transformPreviewFile(expected, process.cwd()); + const transformed = await transformPreviewFile(source, process.cwd()); - expect(transformed).toBe(await formatFileContent(process.cwd(), expected)); + expect(transformed).toMatchSnapshot(); }); it('should handle the default export without type annotations', async () => { @@ -713,24 +663,8 @@ describe('addonA11yAddonTest', () => { `; const transformed = await transformPreviewFile(source, process.cwd()); - const expected = dedent` - export default { - parameters: { - controls: { - matchers: { - color: /(background|color)$/i, - date: /Date$/i, - }, - }, - }, - - // a11y-test tag controls whether accessibility tests are run as part of a standalone Vitest test run - // For more information please visit: https://storybook.js.org/docs/writing-tests/accessibility-testing - tags: [/*"a11y-test"*/] - }; - `; - expect(transformed).toBe(await formatFileContent(process.cwd(), expected)); + expect(transformed).toMatchSnapshot(); }); it('should extend the existing tags property without type annotations', async () => { @@ -749,23 +683,8 @@ describe('addonA11yAddonTest', () => { `; const transformed = await transformPreviewFile(source, process.cwd()); - const expected = dedent` - export default { - // a11y-test tag controls whether accessibility tests are run as part of a standalone Vitest test run - // For more information please visit: https://storybook.js.org/docs/writing-tests/accessibility-testing - tags: ["existingTag"/*, "a11y-test"*/], - parameters: { - controls: { - matchers: { - color: /(background|color)$/i, - date: /Date$/i, - }, - }, - }, - }; - `; - expect(transformed).toBe(await formatFileContent(process.cwd(), expected)); + expect(transformed).toMatchSnapshot(); }); }); }); From a4a3b3d45d4086f47ade14d7e525099033bd4274 Mon Sep 17 00:00:00 2001 From: Valentin Palkovic Date: Fri, 10 Jan 2025 10:44:09 +0100 Subject: [PATCH 20/27] Return to execa 8.0.1 to reduce bundle size --- code/addons/test/package.json | 2 +- code/yarn.lock | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/code/addons/test/package.json b/code/addons/test/package.json index 43ef32de42d8..a4f8ed45bf0b 100644 --- a/code/addons/test/package.json +++ b/code/addons/test/package.json @@ -99,7 +99,7 @@ "ansi-to-html": "^0.7.2", "boxen": "^8.0.1", "es-toolkit": "^1.22.0", - "execa": "^9.5.2", + "execa": "^8.0.1", "find-up": "^7.0.0", "formik": "^2.2.9", "istanbul-lib-report": "^3.0.1", diff --git a/code/yarn.lock b/code/yarn.lock index 46effb13a7f7..0e87b9201c52 100644 --- a/code/yarn.lock +++ b/code/yarn.lock @@ -6324,7 +6324,7 @@ __metadata: ansi-to-html: "npm:^0.7.2" boxen: "npm:^8.0.1" es-toolkit: "npm:^1.22.0" - execa: "npm:^9.5.2" + execa: "npm:^8.0.1" find-up: "npm:^7.0.0" formik: "npm:^2.2.9" istanbul-lib-report: "npm:^3.0.1" From f31a131758ac0b8d4cdb9bce7cdc23de96a23ce7 Mon Sep 17 00:00:00 2001 From: Valentin Palkovic Date: Fri, 10 Jan 2025 11:06:52 +0100 Subject: [PATCH 21/27] Use inline snapshots instead of normal ones --- .../addon-a11y-addon-test.test.ts.snap | 200 ------------------ .../fixes/addon-a11y-addon-test.test.ts | 199 ++++++++++++++++- 2 files changed, 188 insertions(+), 211 deletions(-) delete mode 100644 code/lib/cli-storybook/src/automigrate/fixes/__snapshots__/addon-a11y-addon-test.test.ts.snap diff --git a/code/lib/cli-storybook/src/automigrate/fixes/__snapshots__/addon-a11y-addon-test.test.ts.snap b/code/lib/cli-storybook/src/automigrate/fixes/__snapshots__/addon-a11y-addon-test.test.ts.snap deleted file mode 100644 index 5a567a94671f..000000000000 --- a/code/lib/cli-storybook/src/automigrate/fixes/__snapshots__/addon-a11y-addon-test.test.ts.snap +++ /dev/null @@ -1,200 +0,0 @@ -// Vitest Snapshot v1, https://vitest.dev/guide/snapshot.html - -exports[`addonA11yAddonTest > prompt > should return auto prompt if transformedPreviewCode is defined and if transformedSetupCode is skipped 1`] = ` -"We have detected that you have @storybook/addon-a11y and @storybook/experimental-addon-test installed. - -@storybook/addon-a11y integrates now with @storybook/experimental-addon-test to provide automatic accessibility checks for your stories, powered by Axe and Vitest. - -1) We have to update your .storybook/preview.js file to set up tags from @storybook/addon-a11y. - -For more information, please refer to the accessibility addon documentation: -https://storybook.js.org/docs/writing-tests/accessibility-testing#test-addon-integration" -`; - -exports[`addonA11yAddonTest > prompt > should return auto prompt if transformedSetupCode is defined and if transformedPreviewCode is null 1`] = ` -"We have detected that you have @storybook/addon-a11y and @storybook/experimental-addon-test installed. - -@storybook/addon-a11y integrates now with @storybook/experimental-addon-test to provide automatic accessibility checks for your stories, powered by Axe and Vitest. - -1) We have to update your .storybook/vitest.setup.ts file to set up project annotations from @storybook/addon-a11y. - -2) We couldn't find or automatically update your .storybook/preview. in your project to smoothly set up tags from @storybook/addon-a11y. -Please manually update your .storybook/preview. file to include the following: - -export default { -... -+ tags: ["a11y-test"], -} - -For more information, please refer to the accessibility addon documentation: -https://storybook.js.org/docs/writing-tests/accessibility-testing#test-addon-integration" -`; - -exports[`addonA11yAddonTest > prompt > should return auto prompt if transformedSetupCode is defined and if transformedPreviewCode is skipped 1`] = ` -"We have detected that you have @storybook/addon-a11y and @storybook/experimental-addon-test installed. - -@storybook/addon-a11y integrates now with @storybook/experimental-addon-test to provide automatic accessibility checks for your stories, powered by Axe and Vitest. - -1) We have to update your .storybook/vitest.setup.ts file to set up project annotations from @storybook/addon-a11y. - -For more information, please refer to the accessibility addon documentation: -https://storybook.js.org/docs/writing-tests/accessibility-testing#test-addon-integration" -`; - -exports[`addonA11yAddonTest > prompt > should return auto prompt if transformedSetupCode is null and if transformedPreviewCode is defined 1`] = ` -"We have detected that you have @storybook/addon-a11y and @storybook/experimental-addon-test installed. - -@storybook/addon-a11y integrates now with @storybook/experimental-addon-test to provide automatic accessibility checks for your stories, powered by Axe and Vitest. - -1) We couldn't find or automatically update your .storybook/vitest.setup. in your project to smoothly set up project annotations from @storybook/addon-a11y. -Please manually update your vitest.setup.ts file to include the following: - -... -+ import * as a11yAddonAnnotations from "@storybook/addon-a11y/preview"; - -const annotations = setProjectAnnotations([ - ... -+ a11yAddonAnnotations, -]); - -beforeAll(annotations.beforeAll); - -2) We have to update your .storybook/preview.js file to set up tags from @storybook/addon-a11y. - -For more information, please refer to the accessibility addon documentation: -https://storybook.js.org/docs/writing-tests/accessibility-testing#test-addon-integration" -`; - -exports[`addonA11yAddonTest > prompt > should return manual prompt if transformedSetupCode is null and if transformedPreviewCode is null 1`] = ` -"We have detected that you have @storybook/addon-a11y and @storybook/experimental-addon-test installed. - -@storybook/addon-a11y integrates now with @storybook/experimental-addon-test to provide automatic accessibility checks for your stories, powered by Axe and Vitest. - -1) We couldn't find or automatically update your .storybook/vitest.setup. in your project to smoothly set up project annotations from @storybook/addon-a11y. -Please manually update your vitest.setup.ts file to include the following: - -... -+ import * as a11yAddonAnnotations from "@storybook/addon-a11y/preview"; - -const annotations = setProjectAnnotations([ - ... -+ a11yAddonAnnotations, -]); - -beforeAll(annotations.beforeAll); - -2) We couldn't find or automatically update your .storybook/preview. in your project to smoothly set up tags from @storybook/addon-a11y. -Please manually update your .storybook/preview. file to include the following: - -export default { -... -+ tags: ["a11y-test"], -} - -For more information, please refer to the accessibility addon documentation: -https://storybook.js.org/docs/writing-tests/accessibility-testing#test-addon-integration" -`; - -exports[`addonA11yAddonTest > transformPreviewFile > should add a new tags property if it does not exist 1`] = ` -"import type { Preview } from '@storybook/react'; - -const preview: Preview = { - parameters: { - controls: { - matchers: { - color: /(background|color)$/i, - date: /Date$/i, - }, - }, - }, - - // a11y-test tag controls whether accessibility tests are run as part of a standalone Vitest test run - // For more information please visit: https://storybook.js.org/docs/writing-tests/accessibility-testing - tags: [/*'a11y-test'*/] -}; - -export default preview;" -`; - -exports[`addonA11yAddonTest > transformPreviewFile > should add a new tags property if it does not exist and a default export does not exist 1`] = ` -"export const parameters = { - controls: { - matchers: { - color: /(background|color)$/i, - date: /Date$/i, - }, - }, -} -export const tags = ["a11y-test"];" -`; - -exports[`addonA11yAddonTest > transformPreviewFile > should extend the existing tags property 1`] = ` -"import type { Preview } from "@storybook/react"; - -const preview: Preview = { - // a11y-test tag controls whether accessibility tests are run as part of a standalone Vitest test run - // For more information please visit: https://storybook.js.org/docs/writing-tests/accessibility-testing - tags: ["existingTag"/*, "a11y-test"*/], - parameters: { - controls: { - matchers: { - color: /(background|color)$/i, - date: /Date$/i, - }, - }, - }, -}; - -export default preview;" -`; - -exports[`addonA11yAddonTest > transformPreviewFile > should extend the existing tags property without type annotations 1`] = ` -"export default { - // a11y-test tag controls whether accessibility tests are run as part of a standalone Vitest test run - // For more information please visit: https://storybook.js.org/docs/writing-tests/accessibility-testing - tags: ["existingTag"/*, "a11y-test"*/], - parameters: { - controls: { - matchers: { - color: /(background|color)$/i, - date: /Date$/i, - }, - }, - }, -};" -`; - -exports[`addonA11yAddonTest > transformPreviewFile > should handle the default export without type annotations 1`] = ` -"export default { - parameters: { - controls: { - matchers: { - color: /(background|color)$/i, - date: /Date$/i, - }, - }, - }, - - // a11y-test tag controls whether accessibility tests are run as part of a standalone Vitest test run - // For more information please visit: https://storybook.js.org/docs/writing-tests/accessibility-testing - tags: [/*"a11y-test"*/] -};" -`; - -exports[`addonA11yAddonTest > transformPreviewFile > should not add a11y-test if it already exists in the tags property 1`] = ` -"import type { Preview } from "@storybook/react"; - -const preview: Preview = { - tags: ["a11y-test"], - parameters: { - controls: { - matchers: { - color: /(background|color)$/i, - date: /Date$/i, - }, - }, - }, -}; - -export default preview;" -`; diff --git a/code/lib/cli-storybook/src/automigrate/fixes/addon-a11y-addon-test.test.ts b/code/lib/cli-storybook/src/automigrate/fixes/addon-a11y-addon-test.test.ts index e4f9906534a9..e570f31ade1b 100644 --- a/code/lib/cli-storybook/src/automigrate/fixes/addon-a11y-addon-test.test.ts +++ b/code/lib/cli-storybook/src/automigrate/fixes/addon-a11y-addon-test.test.ts @@ -366,7 +366,35 @@ describe('addonA11yAddonTest', () => { skipPreviewTransformation: false, skipVitestSetupTransformation: false, }); - expect(result).toMatchSnapshot(); + expect(result).toMatchInlineSnapshot(` + "We have detected that you have @storybook/addon-a11y and @storybook/experimental-addon-test installed. + + @storybook/addon-a11y integrates now with @storybook/experimental-addon-test to provide automatic accessibility checks for your stories, powered by Axe and Vitest. + + 1) We couldn't find or automatically update your .storybook/vitest.setup. in your project to smoothly set up project annotations from @storybook/addon-a11y. + Please manually update your vitest.setup.ts file to include the following: + + ... + + import * as a11yAddonAnnotations from "@storybook/addon-a11y/preview"; + + const annotations = setProjectAnnotations([ + ... + + a11yAddonAnnotations, + ]); + + beforeAll(annotations.beforeAll); + + 2) We couldn't find or automatically update your .storybook/preview. in your project to smoothly set up tags from @storybook/addon-a11y. + Please manually update your .storybook/preview. file to include the following: + + export default { + ... + + tags: ["a11y-test"], + } + + For more information, please refer to the accessibility addon documentation: + https://storybook.js.org/docs/writing-tests/accessibility-testing#test-addon-integration" + `); }); it('should return auto prompt if transformedSetupCode is null and if transformedPreviewCode is defined', () => { @@ -378,7 +406,29 @@ describe('addonA11yAddonTest', () => { skipPreviewTransformation: false, skipVitestSetupTransformation: false, }); - expect(result).toMatchSnapshot(); + expect(result).toMatchInlineSnapshot(` + "We have detected that you have @storybook/addon-a11y and @storybook/experimental-addon-test installed. + + @storybook/addon-a11y integrates now with @storybook/experimental-addon-test to provide automatic accessibility checks for your stories, powered by Axe and Vitest. + + 1) We couldn't find or automatically update your .storybook/vitest.setup. in your project to smoothly set up project annotations from @storybook/addon-a11y. + Please manually update your vitest.setup.ts file to include the following: + + ... + + import * as a11yAddonAnnotations from "@storybook/addon-a11y/preview"; + + const annotations = setProjectAnnotations([ + ... + + a11yAddonAnnotations, + ]); + + beforeAll(annotations.beforeAll); + + 2) We have to update your .storybook/preview.js file to set up tags from @storybook/addon-a11y. + + For more information, please refer to the accessibility addon documentation: + https://storybook.js.org/docs/writing-tests/accessibility-testing#test-addon-integration" + `); }); it('should return auto prompt if transformedSetupCode is defined and if transformedPreviewCode is null', () => { @@ -390,7 +440,24 @@ describe('addonA11yAddonTest', () => { skipPreviewTransformation: false, skipVitestSetupTransformation: false, }); - expect(result).toMatchSnapshot(); + expect(result).toMatchInlineSnapshot(` + "We have detected that you have @storybook/addon-a11y and @storybook/experimental-addon-test installed. + + @storybook/addon-a11y integrates now with @storybook/experimental-addon-test to provide automatic accessibility checks for your stories, powered by Axe and Vitest. + + 1) We have to update your .storybook/vitest.setup.ts file to set up project annotations from @storybook/addon-a11y. + + 2) We couldn't find or automatically update your .storybook/preview. in your project to smoothly set up tags from @storybook/addon-a11y. + Please manually update your .storybook/preview. file to include the following: + + export default { + ... + + tags: ["a11y-test"], + } + + For more information, please refer to the accessibility addon documentation: + https://storybook.js.org/docs/writing-tests/accessibility-testing#test-addon-integration" + `); }); it('should return auto prompt if transformedSetupCode is defined and if transformedPreviewCode is skipped', () => { @@ -402,7 +469,16 @@ describe('addonA11yAddonTest', () => { skipPreviewTransformation: true, skipVitestSetupTransformation: false, }); - expect(result).toMatchSnapshot(); + expect(result).toMatchInlineSnapshot(` + "We have detected that you have @storybook/addon-a11y and @storybook/experimental-addon-test installed. + + @storybook/addon-a11y integrates now with @storybook/experimental-addon-test to provide automatic accessibility checks for your stories, powered by Axe and Vitest. + + 1) We have to update your .storybook/vitest.setup.ts file to set up project annotations from @storybook/addon-a11y. + + For more information, please refer to the accessibility addon documentation: + https://storybook.js.org/docs/writing-tests/accessibility-testing#test-addon-integration" + `); }); it('should return auto prompt if transformedPreviewCode is defined and if transformedSetupCode is skipped', () => { @@ -414,7 +490,16 @@ describe('addonA11yAddonTest', () => { skipPreviewTransformation: false, skipVitestSetupTransformation: true, }); - expect(result).toMatchSnapshot(); + expect(result).toMatchInlineSnapshot(` + "We have detected that you have @storybook/addon-a11y and @storybook/experimental-addon-test installed. + + @storybook/addon-a11y integrates now with @storybook/experimental-addon-test to provide automatic accessibility checks for your stories, powered by Axe and Vitest. + + 1) We have to update your .storybook/preview.js file to set up tags from @storybook/addon-a11y. + + For more information, please refer to the accessibility addon documentation: + https://storybook.js.org/docs/writing-tests/accessibility-testing#test-addon-integration" + `); }); }); @@ -580,7 +665,26 @@ describe('addonA11yAddonTest', () => { const transformed = await transformPreviewFile(source, process.cwd()); - expect(transformed).toMatchSnapshot(); + expect(transformed).toMatchInlineSnapshot(` + "import type { Preview } from '@storybook/react'; + + const preview: Preview = { + parameters: { + controls: { + matchers: { + color: /(background|color)$/i, + date: /Date$/i, + }, + }, + }, + + // a11y-test tag controls whether accessibility tests are run as part of a standalone Vitest test run + // For more information please visit: https://storybook.js.org/docs/writing-tests/accessibility-testing + tags: [/*'a11y-test'*/] + }; + + export default preview;" + `); }); it('should add a new tags property if it does not exist and a default export does not exist', async () => { @@ -597,7 +701,17 @@ describe('addonA11yAddonTest', () => { const transformed = await transformPreviewFile(source, process.cwd()); - expect(transformed).toMatchSnapshot(); + expect(transformed).toMatchInlineSnapshot(` + "export const parameters = { + controls: { + matchers: { + color: /(background|color)$/i, + date: /Date$/i, + }, + }, + } + export const tags = ["a11y-test"];" + `); }); it('should extend the existing tags property', async () => { @@ -621,7 +735,25 @@ describe('addonA11yAddonTest', () => { const transformed = await transformPreviewFile(source, process.cwd()); - expect(transformed).toMatchSnapshot(); + expect(transformed).toMatchInlineSnapshot(` + "import type { Preview } from "@storybook/react"; + + const preview: Preview = { + // a11y-test tag controls whether accessibility tests are run as part of a standalone Vitest test run + // For more information please visit: https://storybook.js.org/docs/writing-tests/accessibility-testing + tags: ["existingTag"/*, "a11y-test"*/], + parameters: { + controls: { + matchers: { + color: /(background|color)$/i, + date: /Date$/i, + }, + }, + }, + }; + + export default preview;" + `); }); it('should not add a11y-test if it already exists in the tags property', async () => { @@ -645,7 +777,23 @@ describe('addonA11yAddonTest', () => { const transformed = await transformPreviewFile(source, process.cwd()); - expect(transformed).toMatchSnapshot(); + expect(transformed).toMatchInlineSnapshot(` + "import type { Preview } from "@storybook/react"; + + const preview: Preview = { + tags: ["a11y-test"], + parameters: { + controls: { + matchers: { + color: /(background|color)$/i, + date: /Date$/i, + }, + }, + }, + }; + + export default preview;" + `); }); it('should handle the default export without type annotations', async () => { @@ -664,7 +812,22 @@ describe('addonA11yAddonTest', () => { const transformed = await transformPreviewFile(source, process.cwd()); - expect(transformed).toMatchSnapshot(); + expect(transformed).toMatchInlineSnapshot(` + "export default { + parameters: { + controls: { + matchers: { + color: /(background|color)$/i, + date: /Date$/i, + }, + }, + }, + + // a11y-test tag controls whether accessibility tests are run as part of a standalone Vitest test run + // For more information please visit: https://storybook.js.org/docs/writing-tests/accessibility-testing + tags: [/*"a11y-test"*/] + };" + `); }); it('should extend the existing tags property without type annotations', async () => { @@ -684,7 +847,21 @@ describe('addonA11yAddonTest', () => { const transformed = await transformPreviewFile(source, process.cwd()); - expect(transformed).toMatchSnapshot(); + expect(transformed).toMatchInlineSnapshot(` + "export default { + // a11y-test tag controls whether accessibility tests are run as part of a standalone Vitest test run + // For more information please visit: https://storybook.js.org/docs/writing-tests/accessibility-testing + tags: ["existingTag"/*, "a11y-test"*/], + parameters: { + controls: { + matchers: { + color: /(background|color)$/i, + date: /Date$/i, + }, + }, + }, + };" + `); }); }); }); From 733be67647dc11bbd4e67d6c44f79ca5cc39f3c2 Mon Sep 17 00:00:00 2001 From: Valentin Palkovic Date: Fri, 10 Jan 2025 11:07:34 +0100 Subject: [PATCH 22/27] Simplify code --- .../test/src/components/TestProviderRender.tsx | 13 +------------ 1 file changed, 1 insertion(+), 12 deletions(-) diff --git a/code/addons/test/src/components/TestProviderRender.tsx b/code/addons/test/src/components/TestProviderRender.tsx index f861ecac7848..98fe3c65ec46 100644 --- a/code/addons/test/src/components/TestProviderRender.tsx +++ b/code/addons/test/src/components/TestProviderRender.tsx @@ -132,18 +132,7 @@ export const TestProviderRender: FC = ({ return false; } - const allTags = Object.values(internalIndex.entries).reduce((acc, entry) => { - entry.tags?.forEach((tag: Tag) => { - acc.add(tag); - }); - return acc; - }, new Set()); - - if (allTags.has('a11y-test')) { - return true; - } - - return false; + return Object.values(internalIndex.entries).some((entry) => entry.tags?.includes('a11y-test')); }, [isA11yAddon, storybookState.internal_index]); const [config, updateConfig] = useConfig( From 9d614f54b324662071f26761db99be24deeba244 Mon Sep 17 00:00:00 2001 From: Valentin Palkovic Date: Fri, 10 Jan 2025 11:09:09 +0100 Subject: [PATCH 23/27] Remove invalid file extensions --- .../src/automigrate/fixes/addon-a11y-addon-test.ts | 4 ---- 1 file changed, 4 deletions(-) diff --git a/code/lib/cli-storybook/src/automigrate/fixes/addon-a11y-addon-test.ts b/code/lib/cli-storybook/src/automigrate/fixes/addon-a11y-addon-test.ts index 30fd3dcc8f04..49c4b2b25c05 100644 --- a/code/lib/cli-storybook/src/automigrate/fixes/addon-a11y-addon-test.ts +++ b/code/lib/cli-storybook/src/automigrate/fixes/addon-a11y-addon-test.ts @@ -25,10 +25,6 @@ export const fileExtensions = [ '.mjs', '.jsx', '.tsx', - '.ctsx', - '.mtsx', - '.cjsx', - '.mjsx', ] as const; interface AddonA11yAddonTestOptions { From 3441720a8fe48b2db350d13c380c0e1ecc4f2f2f Mon Sep 17 00:00:00 2001 From: Valentin Palkovic Date: Fri, 10 Jan 2025 14:05:09 +0100 Subject: [PATCH 24/27] Streamline state initialization and add useEffect for state updates --- .../src/components/TestProviderRender.tsx | 19 ++++++++++++++----- 1 file changed, 14 insertions(+), 5 deletions(-) diff --git a/code/addons/test/src/components/TestProviderRender.tsx b/code/addons/test/src/components/TestProviderRender.tsx index 98fe3c65ec46..a238bee05e82 100644 --- a/code/addons/test/src/components/TestProviderRender.tsx +++ b/code/addons/test/src/components/TestProviderRender.tsx @@ -1,4 +1,12 @@ -import React, { type ComponentProps, type FC, useCallback, useMemo, useRef, useState } from 'react'; +import React, { + type ComponentProps, + type FC, + useCallback, + useEffect, + useMemo, + useRef, + useState, +} from 'react'; import { Button, @@ -449,10 +457,7 @@ function useConfig(api: API, providerId: string, initialConfig: Config) { [api, providerId] ); - const [currentConfig, setConfig] = useState(() => { - updateTestProviderState(initialConfig); - return initialConfig; - }); + const [currentConfig, setConfig] = useState(initialConfig); const lastConfig = useRef(initialConfig); @@ -477,5 +482,9 @@ function useConfig(api: API, providerId: string, initialConfig: Config) { [saveConfig] ); + useEffect(() => { + updateTestProviderState(initialConfig); + }, []); + return [currentConfig, updateConfig] as const; } From af159c992ff45d9f96c388a94526fbd0206a879c Mon Sep 17 00:00:00 2001 From: Kyle Gach Date: Sun, 12 Jan 2025 17:03:47 -0700 Subject: [PATCH 25/27] Docs updates for `a11y-test` tag behavior --- docs/_snippets/addon-a11y-meta-tag-exclude.md | 236 ++++++++++++++++++ ...ag.md => addon-a11y-meta-tag-reinclude.md} | 114 ++++++--- docs/writing-tests/accessibility-testing.mdx | 27 +- 3 files changed, 333 insertions(+), 44 deletions(-) create mode 100644 docs/_snippets/addon-a11y-meta-tag-exclude.md rename docs/_snippets/{addon-a11y-meta-tag.md => addon-a11y-meta-tag-reinclude.md} (63%) diff --git a/docs/_snippets/addon-a11y-meta-tag-exclude.md b/docs/_snippets/addon-a11y-meta-tag-exclude.md new file mode 100644 index 000000000000..da3637b82900 --- /dev/null +++ b/docs/_snippets/addon-a11y-meta-tag-exclude.md @@ -0,0 +1,236 @@ +```ts filename="DataGrid.stories.ts" renderer="angular" language="ts" +import type { Meta } from '@storybook/angular/'; + +import { DataGrid } from './dataGrid.component'; + +const meta: Meta = { + component: DataGrid, + // 👇 Remove the a11y-test tag for this component's stories + tags: ['!a11y-test'], +}; + +export default meta; +``` + +```js filename="DataGrid.stories.js" renderer="html" language="js" +export default { + // 👇 Remove the a11y-test tag for this component's stories + tags: ['!a11y-test'], +}; +``` + +```js filename="DataGrid.stories.js|jsx" renderer="common" language="js" +import { DataGrid } from './DataGrid'; + +export default { + component: DataGrid, + // 👇 Remove the a11y-test tag for this component's stories + tags: ['!a11y-test'], +}; +``` + +```ts filename="DataGrid.stories.ts|tsx" renderer="common" language="ts-4-9" +// Replace your-renderer with the renderer you are using (e.g., react, vue3) +import { Meta } from '@storybook/your-renderer'; + +import { DataGrid } from './DataGrid'; + +const meta = { + component: DataGrid, + // 👇 Remove the a11y-test tag for this component's stories + tags: ['!a11y-test'], +} satisfies Meta; + +export default meta; +``` + +```ts filename="DataGrid.stories.ts|tsx" renderer="common" language="ts" +// Replace your-renderer with the renderer you are using (e.g., react, vue3) +import { Meta } from '@storybook/your-renderer'; + +import { DataGrid } from './DataGrid'; + +const meta: Meta = { + component: DataGrid, + // 👇 Remove the a11y-test tag for this component's stories + tags: ['!a11y-test'], +}; + +export default meta; +``` + +```js filename="DataGrid.stories.js|jsx" renderer="solid" language="js" +import { DataGrid } from './DataGrid'; + +export default { + component: DataGrid, + // 👇 Remove the a11y-test tag for this component's stories + tags: ['!a11y-test'], +}; +``` + +```tsx filename="DataGrid.stories.ts|tsx" renderer="solid" language="ts-4-9" +import type { Meta } from 'storybook-solidjs'; + +import { DataGrid } from './DataGrid'; + +const meta = { + component: DataGrid, + // 👇 Remove the a11y-test tag for this component's stories + tags: ['!a11y-test'], +} satisfies Meta; + +export default meta; +``` + +```tsx filename="DataGrid.stories.ts|tsx" renderer="solid" language="ts" +import type { Meta } from 'storybook-solidjs'; + +import { DataGrid } from './DataGrid'; + +const meta: Meta = { + component: DataGrid, + // 👇 Remove the a11y-test tag for this component's stories + tags: ['!a11y-test'], +}; + +export default meta; +``` + +```svelte filename="DataGrid.stories.svelte" renderer="svelte" language="js" tabTitle="Svelte CSF" + +``` + +```js filename="DataGrid.stories.js" renderer="svelte" language="js" tabTitle="CSF" +import DataGrid from './DataGrid.svelte'; + +export default { + component: DataGrid, + // 👇 Remove the a11y-test tag for this component's stories + tags: ['!a11y-test'], +}; +``` + +```svelte filename="DataGrid.stories.svelte" renderer="svelte" language="ts-4-9" tabTitle="Svelte CSF" + +``` + +```ts filename="DataGrid.stories.ts" renderer="svelte" language="ts-4-9" tabTitle="CSF" +import type { Meta } from '@storybook/svelte'; + +import DataGrid from './DataGrid.svelte'; + +const meta = { + component: DataGrid, + // 👇 Remove the a11y-test tag for this component's stories + tags: ['!a11y-test'], +} satisfies Meta; + +export default meta; +``` + +```svelte filename="DataGrid.stories.svelte" renderer="svelte" language="ts" tabTitle="Svelte CSF" + +``` + +```ts filename="DataGrid.stories.ts" renderer="svelte" language="ts" tabTitle="CSF" +import type { Meta } from '@storybook/svelte'; + +import DataGrid from './DataGrid.svelte'; + +const meta: Meta = { + component: DataGrid, + // 👇 Remove the a11y-test tag for this component's stories + tags: ['!a11y-test'], +}; + +export default meta; +``` + +```js filename="DataGrid.stories.js" renderer="vue" language="js" +import DataGrid from './DataGrid.vue'; + +export default { + component: DataGrid, + // 👇 Remove the a11y-test tag for this component's stories + tags: ['!a11y-test'], +}; +``` + +```ts filename="DataGrid.stories.ts" renderer="vue" language="ts-4-9" +import type { Meta, StoryObj } from '@storybook/vue3'; + +import DataGrid from './DataGrid.vue'; + +const meta = { + component: DataGrid, + // 👇 Remove the a11y-test tag for this component's stories + tags: ['!a11y-test'], +} satisfies Meta; + +export default meta; +``` + +```ts filename="DataGrid.stories.ts" renderer="vue" language="ts" +import type { Meta, StoryObj } from '@storybook/vue3'; + +import DataGrid from './DataGrid.vue'; + +const meta: Meta = { + component: DataGrid, + // 👇 Remove the a11y-test tag for this component's stories + tags: ['!a11y-test'], +}; + +export default meta; +``` + +```js filename="DataGrid.stories.js" renderer="web-components" language="js" +export default { + component: 'demo-dataGrid', + // 👇 Remove the a11y-test tag for this component's stories + tags: ['!a11y-test'], +}; +``` + +```ts filename="DataGrid.stories.ts" renderer="web-components" language="ts" +import type { Meta, StoryObj } from '@storybook/web-components'; + +const meta: Meta = { + component: 'demo-dataGrid', + // 👇 Remove the a11y-test tag for this component's stories + tags: ['!a11y-test'], +}; + +export default meta; +``` diff --git a/docs/_snippets/addon-a11y-meta-tag.md b/docs/_snippets/addon-a11y-meta-tag-reinclude.md similarity index 63% rename from docs/_snippets/addon-a11y-meta-tag.md rename to docs/_snippets/addon-a11y-meta-tag-reinclude.md index 58de99af555f..491d5c294800 100644 --- a/docs/_snippets/addon-a11y-meta-tag.md +++ b/docs/_snippets/addon-a11y-meta-tag-reinclude.md @@ -5,8 +5,10 @@ import { Button } from './button.component'; const meta: Meta