From e5cfe1e4894c5bd6a2db5f72eb48f44c29870cb1 Mon Sep 17 00:00:00 2001 From: Jeppe Reinhold Date: Tue, 20 Feb 2024 09:27:24 +0100 Subject: [PATCH 1/4] don't re-attach the same csf file again --- .../src/modules/preview-web/docs-context/DocsContext.ts | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/code/lib/preview-api/src/modules/preview-web/docs-context/DocsContext.ts b/code/lib/preview-api/src/modules/preview-web/docs-context/DocsContext.ts index da41240756d5..bd95c0e81253 100644 --- a/code/lib/preview-api/src/modules/preview-web/docs-context/DocsContext.ts +++ b/code/lib/preview-api/src/modules/preview-web/docs-context/DocsContext.ts @@ -71,6 +71,10 @@ export class DocsContext implements DocsContextProps if (!this.exportsToCSFFile.has(csfFile.moduleExports)) { throw new Error('Cannot attach a CSF file that has not been referenced'); } + if (this.attachedCSFFile === csfFile) { + // this CSF file is already attached, don't do anything + return; + } this.attachedCSFFile = csfFile; From 54286fceb884039c125b2ce7dff50c69db27dc25 Mon Sep 17 00:00:00 2001 From: Jeppe Reinhold Date: Tue, 20 Feb 2024 10:58:49 +0100 Subject: [PATCH 2/4] improve support for multiple attached CSF files in DocsContext --- .../docs-context/DocsContext.test.ts | 46 +++++++++++++++++++ .../preview-web/docs-context/DocsContext.ts | 27 ++++++----- .../preview-web/docs-context/test-utils.ts | 10 ++-- 3 files changed, 67 insertions(+), 16 deletions(-) diff --git a/code/lib/preview-api/src/modules/preview-web/docs-context/DocsContext.test.ts b/code/lib/preview-api/src/modules/preview-web/docs-context/DocsContext.test.ts index 22a3aacc8a41..5321f4bc697f 100644 --- a/code/lib/preview-api/src/modules/preview-web/docs-context/DocsContext.test.ts +++ b/code/lib/preview-api/src/modules/preview-web/docs-context/DocsContext.test.ts @@ -33,6 +33,52 @@ describe('referenceCSFFile', () => { }); }); +describe('attachCSFFile', () => { + const firstCsfParts = csfFileParts('first-meta--first-story', 'first-meta'); + const secondCsfParts = csfFileParts('second-meta--second-story', 'second-meta'); + const store = { + componentStoriesFromCSFFile: ({ csfFile }: { csfFile: CSFFile }) => + csfFile === firstCsfParts.csfFile ? [firstCsfParts.story] : [secondCsfParts.story], + } as unknown as StoryStore; + + it('attaches multiple CSF files', () => { + // Arrange - create a context with both CSF files + const context = new DocsContext(channel, store, renderStoryToElement, [ + firstCsfParts.csfFile, + secondCsfParts.csfFile, + ]); + + // Act - attach the first CSF file + context.attachCSFFile(firstCsfParts.csfFile); + + // Assert - the first story is now the primary story and the only component story + expect(context.storyById()).toEqual(firstCsfParts.story); + expect(context.componentStories()).toEqual([firstCsfParts.story]); + + // Assert - stories from both CSF files are available + expect(context.componentStoriesFromCSFFile(firstCsfParts.csfFile)).toEqual([ + firstCsfParts.story, + ]); + expect(context.componentStoriesFromCSFFile(secondCsfParts.csfFile)).toEqual([ + secondCsfParts.story, + ]); + + // Act - attach the second CSF file + context.attachCSFFile(secondCsfParts.csfFile); + + // Assert - the first story is still the primary story but both stories are available + expect(context.storyById()).toEqual(firstCsfParts.story); + expect(context.componentStories()).toEqual([firstCsfParts.story, secondCsfParts.story]); + + // Act - attach the second CSF file again + context.attachCSFFile(secondCsfParts.csfFile); + + // Assert - still only two stories are available + expect(context.storyById()).toEqual(firstCsfParts.story); + expect(context.componentStories()).toEqual([firstCsfParts.story, secondCsfParts.story]); + }); +}); + describe('resolveOf', () => { const { story, csfFile, storyExport, metaExport, moduleExports, component } = csfFileParts(); diff --git a/code/lib/preview-api/src/modules/preview-web/docs-context/DocsContext.ts b/code/lib/preview-api/src/modules/preview-web/docs-context/DocsContext.ts index bd95c0e81253..5c83e764ec02 100644 --- a/code/lib/preview-api/src/modules/preview-web/docs-context/DocsContext.ts +++ b/code/lib/preview-api/src/modules/preview-web/docs-context/DocsContext.ts @@ -17,7 +17,7 @@ import type { StoryStore } from '../../store'; import type { DocsContextProps } from './DocsContextProps'; export class DocsContext implements DocsContextProps { - private componentStoriesValue: PreparedStory[]; + private componentStoriesValue: Set>; private storyIdToCSFFile: Map>; @@ -27,7 +27,7 @@ export class DocsContext implements DocsContextProps private nameToStoryId: Map; - private attachedCSFFile?: CSFFile; + private attachedCSFFiles: Set>; private primaryStory?: PreparedStory; @@ -38,11 +38,12 @@ export class DocsContext implements DocsContextProps /** The CSF files known (via the index) to be refererenced by this docs file */ csfFiles: CSFFile[] ) { + this.componentStoriesValue = new Set(); this.storyIdToCSFFile = new Map(); this.exportToStory = new Map(); this.exportsToCSFFile = new Map(); this.nameToStoryId = new Map(); - this.componentStoriesValue = []; + this.attachedCSFFiles = new Set(); csfFiles.forEach((csfFile, index) => { this.referenceCSFFile(csfFile); @@ -71,17 +72,18 @@ export class DocsContext implements DocsContextProps if (!this.exportsToCSFFile.has(csfFile.moduleExports)) { throw new Error('Cannot attach a CSF file that has not been referenced'); } - if (this.attachedCSFFile === csfFile) { + if (this.attachedCSFFiles.has(csfFile)) { // this CSF file is already attached, don't do anything return; } - this.attachedCSFFile = csfFile; + this.attachedCSFFiles.add(csfFile); const stories = this.store.componentStoriesFromCSFFile({ csfFile }); + stories.forEach((story) => { this.nameToStoryId.set(story.name, story.id); - this.componentStoriesValue.push(story); + this.componentStoriesValue.add(story); if (!this.primaryStory) this.primaryStory = story; }); } @@ -119,15 +121,18 @@ export class DocsContext implements DocsContextProps return { type: 'story', story: this.primaryStory } as TResolvedExport; } - if (!this.attachedCSFFile) + if (this.attachedCSFFiles.size === 0) throw new Error( `No CSF file attached to this docs file, did you forget to use ?` ); - if (moduleExportType === 'meta') - return { type: 'meta', csfFile: this.attachedCSFFile } as TResolvedExport; + const firstAttachedCSFFile = Array.from(this.attachedCSFFiles)[0]; + + if (moduleExportType === 'meta') { + return { type: 'meta', csfFile: firstAttachedCSFFile } as TResolvedExport; + } - const { component } = this.attachedCSFFile.meta; + const { component } = firstAttachedCSFFile.meta; if (!component) throw new Error( `Attached CSF file does not defined a component, did you forget to export one?` @@ -202,7 +207,7 @@ export class DocsContext implements DocsContextProps }; componentStories = () => { - return this.componentStoriesValue; + return Array.from(this.componentStoriesValue); }; componentStoriesFromCSFFile = (csfFile: CSFFile) => { diff --git a/code/lib/preview-api/src/modules/preview-web/docs-context/test-utils.ts b/code/lib/preview-api/src/modules/preview-web/docs-context/test-utils.ts index 4be2710c8f10..5de026e902b8 100644 --- a/code/lib/preview-api/src/modules/preview-web/docs-context/test-utils.ts +++ b/code/lib/preview-api/src/modules/preview-web/docs-context/test-utils.ts @@ -1,6 +1,6 @@ import type { CSFFile, PreparedStory } from '@storybook/types'; -export function csfFileParts() { +export function csfFileParts(storyId = 'meta--story', metaId = 'meta') { // These compose the raw exports of the CSF file const component = {}; const metaExport = { component }; @@ -9,13 +9,13 @@ export function csfFileParts() { // This is the prepared story + CSF file after SB has processed them const storyAnnotations = { - id: 'meta--story', + id: storyId, moduleExport: storyExport, } as CSFFile['stories'][string]; - const story = { id: 'meta--story', moduleExport: storyExport } as PreparedStory; - const meta = { id: 'meta', title: 'Meta', component, moduleExports } as CSFFile['meta']; + const story = { id: storyId, moduleExport: storyExport } as PreparedStory; + const meta = { id: metaId, title: 'Meta', component, moduleExports } as CSFFile['meta']; const csfFile = { - stories: { 'meta--story': storyAnnotations }, + stories: { [storyId]: storyAnnotations }, meta, moduleExports, } as CSFFile; From e8b3c853b879fecc6a27ccd821fce96021187c23 Mon Sep 17 00:00:00 2001 From: Jeppe Reinhold Date: Mon, 26 Feb 2024 11:54:22 +0100 Subject: [PATCH 3/4] revert component values back to array --- .../src/modules/preview-web/docs-context/DocsContext.ts | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/code/lib/preview-api/src/modules/preview-web/docs-context/DocsContext.ts b/code/lib/preview-api/src/modules/preview-web/docs-context/DocsContext.ts index 5c83e764ec02..b22ad4afe444 100644 --- a/code/lib/preview-api/src/modules/preview-web/docs-context/DocsContext.ts +++ b/code/lib/preview-api/src/modules/preview-web/docs-context/DocsContext.ts @@ -17,7 +17,7 @@ import type { StoryStore } from '../../store'; import type { DocsContextProps } from './DocsContextProps'; export class DocsContext implements DocsContextProps { - private componentStoriesValue: Set>; + private componentStoriesValue: PreparedStory[]; private storyIdToCSFFile: Map>; @@ -38,7 +38,7 @@ export class DocsContext implements DocsContextProps /** The CSF files known (via the index) to be refererenced by this docs file */ csfFiles: CSFFile[] ) { - this.componentStoriesValue = new Set(); + this.componentStoriesValue = []; this.storyIdToCSFFile = new Map(); this.exportToStory = new Map(); this.exportsToCSFFile = new Map(); @@ -83,7 +83,7 @@ export class DocsContext implements DocsContextProps stories.forEach((story) => { this.nameToStoryId.set(story.name, story.id); - this.componentStoriesValue.add(story); + this.componentStoriesValue.push(story); if (!this.primaryStory) this.primaryStory = story; }); } @@ -207,7 +207,7 @@ export class DocsContext implements DocsContextProps }; componentStories = () => { - return Array.from(this.componentStoriesValue); + return this.componentStoriesValue; }; componentStoriesFromCSFFile = (csfFile: CSFFile) => { From 68c535031d93458638b1cd8b793f3551e1718856 Mon Sep 17 00:00:00 2001 From: Jeppe Reinhold Date: Tue, 27 Feb 2024 09:16:29 +0100 Subject: [PATCH 4/4] Add E2E test for multiple CSF files with same title in autodocs --- .../docs2/multiple-csf-files-a.stories.ts | 23 +++++++++++++++++++ .../docs2/multiple-csf-files-b.stories.ts | 23 +++++++++++++++++++ code/e2e-tests/addon-docs.spec.ts | 15 ++++++++++++ .../svelte/template/components/Html.svelte | 2 +- 4 files changed, 62 insertions(+), 1 deletion(-) create mode 100644 code/addons/docs/template/stories/docs2/multiple-csf-files-a.stories.ts create mode 100644 code/addons/docs/template/stories/docs2/multiple-csf-files-b.stories.ts diff --git a/code/addons/docs/template/stories/docs2/multiple-csf-files-a.stories.ts b/code/addons/docs/template/stories/docs2/multiple-csf-files-a.stories.ts new file mode 100644 index 000000000000..c77284a296f3 --- /dev/null +++ b/code/addons/docs/template/stories/docs2/multiple-csf-files-a.stories.ts @@ -0,0 +1,23 @@ +import { global as globalThis } from '@storybook/global'; + +export default { + title: 'Multiple CSF Files Same Title', + component: globalThis.Components.Html, + tags: ['autodocs'], + args: { + content: '

paragraph

', + }, + parameters: { + chromatic: { disable: true }, + }, +}; + +export const DefaultA = {}; + +export const SpanContent = { + args: { content: 'span' }, +}; + +export const CodeContent = { + args: { content: 'code' }, +}; diff --git a/code/addons/docs/template/stories/docs2/multiple-csf-files-b.stories.ts b/code/addons/docs/template/stories/docs2/multiple-csf-files-b.stories.ts new file mode 100644 index 000000000000..955c04af9f9e --- /dev/null +++ b/code/addons/docs/template/stories/docs2/multiple-csf-files-b.stories.ts @@ -0,0 +1,23 @@ +import { global as globalThis } from '@storybook/global'; + +export default { + title: 'Multiple CSF Files Same Title', + component: globalThis.Components.Html, + tags: ['autodocs'], + args: { + content: '

paragraph

', + }, + parameters: { + chromatic: { disable: true }, + }, +}; + +export const DefaultB = {}; + +export const H1Content = { + args: { content: '

heading 1

' }, +}; + +export const H2Content = { + args: { content: '

heading 2

' }, +}; diff --git a/code/e2e-tests/addon-docs.spec.ts b/code/e2e-tests/addon-docs.spec.ts index 4ae3da33e99e..72470acb62ab 100644 --- a/code/e2e-tests/addon-docs.spec.ts +++ b/code/e2e-tests/addon-docs.spec.ts @@ -210,4 +210,19 @@ test.describe('addon-docs', () => { await expect(componentReactVersion).toHaveText(expectedReactVersion); await expect(componentReactDomVersion).toHaveText(expectedReactVersion); }); + + test('should have stories from multiple CSF files in autodocs', async ({ page }) => { + const sbPage = new SbPage(page); + await sbPage.navigateToStory('/addons/docs/multiple-csf-files-same-title', 'docs'); + const root = sbPage.previewRoot(); + + const storyHeadings = root.locator('.sb-anchor > h3'); + await expect(await storyHeadings.count()).toBe(6); + await expect(storyHeadings.nth(0)).toHaveText('Default A'); + await expect(storyHeadings.nth(1)).toHaveText('Span Content'); + await expect(storyHeadings.nth(2)).toHaveText('Code Content'); + await expect(storyHeadings.nth(3)).toHaveText('Default B'); + await expect(storyHeadings.nth(4)).toHaveText('H 1 Content'); + await expect(storyHeadings.nth(5)).toHaveText('H 2 Content'); + }); }); diff --git a/code/renderers/svelte/template/components/Html.svelte b/code/renderers/svelte/template/components/Html.svelte index e341acf73280..bc155de5d4b1 100644 --- a/code/renderers/svelte/template/components/Html.svelte +++ b/code/renderers/svelte/template/components/Html.svelte @@ -5,4 +5,4 @@ export let content = ''; -
{@html content}>
+
{@html content}