From 2beb72c90e3d42b533c0551b252b9058ed73d5e3 Mon Sep 17 00:00:00 2001 From: Michael Shilman Date: Tue, 16 Jul 2024 11:22:00 +0800 Subject: [PATCH 1/4] Update package.json --- package.json | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/package.json b/package.json index c535da55ec01..2f648df87978 100644 --- a/package.json +++ b/package.json @@ -11,7 +11,9 @@ "start": "yarn task --task dev --template react-vite/default-ts --start-from=install", "task": "echo 'Installing Script Dependencies...'; cd scripts; yarn install >/dev/null; cd ..; yarn --cwd=./scripts task", "test": "cd code; yarn test", - "upload-bench": "cd scripts; yarn upload-bench" - }, - "packageManager": "yarn@4.1.1" + "upload-bench": "cd scripts; yarn upload-bench", + "vite-ecosystem-ci:before-test": "node ./scripts/vite-ecosystem-ci/before-test.js && cd ./sandbox/react-vite-default-ts && yarn install", + "vite-ecosystem-ci:build": "yarn task --task sandbox --template react-vite/default-ts", + "vite-ecosystem-ci:test": "yarn task --task test-runner-dev --template react-vite/default-ts --start-from=dev" + } } From 443d0b2b050b8b3537bb811fd21771cca7da9a03 Mon Sep 17 00:00:00 2001 From: Michael Shilman Date: Tue, 16 Jul 2024 23:18:48 +0800 Subject: [PATCH 2/4] Telemetry: Record CSF feature usage --- .../core-server/utils/StoryIndexGenerator.ts | 51 +++- .../utils/__tests__/index-extraction.test.ts | 60 ++++- .../core/src/core-server/utils/doTelemetry.ts | 9 +- .../core-server/utils/summarizeStats.test.ts | 18 ++ .../src/core-server/utils/summarizeStats.ts | 18 ++ code/core/src/csf-tools/CsfFile.test.ts | 240 ++++++++++++++++++ code/core/src/csf-tools/CsfFile.ts | 32 ++- code/core/src/types/modules/indexer.ts | 10 + .../react/template/stories/csf1.stories.tsx | 11 + .../react/template/stories/csf2.stories.tsx | 17 ++ 10 files changed, 432 insertions(+), 34 deletions(-) create mode 100644 code/core/src/core-server/utils/summarizeStats.test.ts create mode 100644 code/core/src/core-server/utils/summarizeStats.ts create mode 100644 code/renderers/react/template/stories/csf1.stories.tsx create mode 100644 code/renderers/react/template/stories/csf2.stories.tsx diff --git a/code/core/src/core-server/utils/StoryIndexGenerator.ts b/code/core/src/core-server/utils/StoryIndexGenerator.ts index dfcf2d4a5f51..8f6e1fe0c0d1 100644 --- a/code/core/src/core-server/utils/StoryIndexGenerator.ts +++ b/code/core/src/core-server/utils/StoryIndexGenerator.ts @@ -1,3 +1,4 @@ +/* eslint-disable no-underscore-dangle */ import path from 'node:path'; import chalk from 'chalk'; import fs from 'fs-extra'; @@ -17,6 +18,7 @@ import type { StoryIndex, Indexer, StorybookConfigRaw, + IndexInputStats, } from '@storybook/core/types'; import { userOrAutoTitleFromSpecifier, sortStoriesV7 } from '@storybook/core/preview-api'; import { commonGlobOptions, normalizeStoryPath } from '@storybook/core/common'; @@ -26,14 +28,18 @@ import { storyNameFromExport, toId, combineTags } from '@storybook/csf'; import { dedent } from 'ts-dedent'; import { autoName } from './autoName'; import { IndexingError, MultipleIndexingError } from './IndexingError'; +import { addStats, summarizeStats, type IndexStatsSummary } from './summarizeStats'; +import { Input } from '../../components/components/form/form.stories'; // Extended type to keep track of the csf meta id so we know the component id when referencing docs in `extractDocs` -type StoryIndexEntryWithMetaId = StoryIndexEntry & { metaId?: string }; +type StoryIndexEntryWithExtra = StoryIndexEntry & { + extra: { metaId?: string; stats: IndexInputStats }; +}; /** A .mdx file will produce a docs entry */ type DocsCacheEntry = DocsIndexEntry; /** A *.stories.* file will produce a list of stories and possibly a docs entry */ type StoriesCacheEntry = { - entries: (StoryIndexEntryWithMetaId | DocsIndexEntry)[]; + entries: (StoryIndexEntryWithExtra | DocsIndexEntry)[]; dependents: Path[]; type: 'stories'; }; @@ -103,6 +109,9 @@ export class StoryIndexGenerator { // - the preview changes [not yet implemented] private lastIndex?: StoryIndex | null; + // Cache the last value stats calculation, mirroring lastIndex + private lastStats?: IndexStatsSummary; + // Same as the above but for the error case private lastError?: Error | null; @@ -221,7 +230,7 @@ export class StoryIndexGenerator { projectTags, }: { projectTags?: Tag[]; - }): Promise<(IndexEntry | ErrorEntry)[]> { + }): Promise<{ entries: (IndexEntry | ErrorEntry)[]; stats: IndexStatsSummary }> { // First process all the story files. Then, in a second pass, // process the docs files. The reason for this is that the docs // files may use the `` syntax, which requires @@ -236,7 +245,8 @@ export class StoryIndexGenerator { this.extractDocs(specifier, absolutePath, projectTags) ); - return this.specifiers.flatMap((specifier) => { + const statsSummary = {} as IndexStatsSummary; + const entries = this.specifiers.flatMap((specifier) => { const cache = this.specifierToCache.get(specifier); invariant( cache, @@ -251,12 +261,17 @@ export class StoryIndexGenerator { return entry.entries.map((item) => { if (item.type === 'docs') return item; + + addStats(item.extra.stats, statsSummary); + // Drop the meta id as it isn't part of the index, we just used it for record keeping in `extractDocs` - const { metaId, ...existing } = item; + const { extra, ...existing } = item; return existing; }); }); }); + + return { entries, stats: statsSummary }; } findDependencies(absoluteImports: Path[]) { @@ -340,7 +355,7 @@ export class StoryIndexGenerator { ]); } - const entries: ((StoryIndexEntryWithMetaId | DocsCacheEntry) & { tags: Tag[] })[] = + const entries: ((StoryIndexEntryWithExtra | DocsCacheEntry) & { tags: Tag[] })[] = indexInputs.map((input) => { const name = input.name ?? storyNameFromExport(input.exportName); const componentPath = @@ -348,14 +363,16 @@ export class StoryIndexGenerator { this.resolveComponentPath(input.rawComponentPath, absolutePath, matchPath); const title = input.title ?? defaultMakeTitle(); - // eslint-disable-next-line no-underscore-dangle const id = input.__id ?? toId(input.metaId ?? title, storyNameFromExport(input.exportName)); const tags = combineTags(...projectTags, ...(input.tags ?? [])); return { type: 'story', id, - metaId: input.metaId, + extra: { + metaId: input.metaId, + stats: input.__stats ?? {}, + }, name, title, importPath, @@ -428,12 +445,12 @@ export class StoryIndexGenerator { // Also, if `result.of` is set, it means that we're using the `` syntax, // so find the `title` defined the file that `meta` points to. - let csfEntry: StoryIndexEntryWithMetaId | undefined; + let csfEntry: StoryIndexEntryWithExtra | undefined; if (result.of) { const absoluteOf = makeAbsolute(result.of, normalizedPath, this.options.workingDir); dependencies.forEach((dep) => { if (dep.entries.length > 0) { - const first = dep.entries.find((e) => e.type !== 'docs') as StoryIndexEntryWithMetaId; + const first = dep.entries.find((e) => e.type !== 'docs') as StoryIndexEntryWithExtra; if ( path @@ -475,7 +492,7 @@ export class StoryIndexGenerator { result.name || (csfEntry ? autoName(importPath, csfEntry.importPath, defaultName) : defaultName); - const id = toId(csfEntry?.metaId || title, name); + const id = toId(csfEntry?.extra.metaId || title, name); const tags = combineTags( ...projectTags, @@ -598,7 +615,12 @@ export class StoryIndexGenerator { } async getIndex() { - if (this.lastIndex) return this.lastIndex; + return (await this.getIndexAndStats()).storyIndex; + } + + async getIndexAndStats(): Promise<{ storyIndex: StoryIndex; stats: IndexStatsSummary }> { + if (this.lastIndex && this.lastStats) + return { storyIndex: this.lastIndex, stats: this.lastStats }; if (this.lastError) throw this.lastError; const previewCode = await this.getPreviewCode(); @@ -606,7 +628,7 @@ export class StoryIndexGenerator { // Extract any entries that are currently missing // Pull out each file's stories into a list of stories, to be composed and sorted - const storiesList = await this.ensureExtracted({ projectTags }); + const { entries: storiesList, stats } = await this.ensureExtracted({ projectTags }); try { const errorEntries = storiesList.filter((entry) => entry.type === 'error'); @@ -635,12 +657,13 @@ export class StoryIndexGenerator { previewCode && getStorySortParameter(previewCode) ); + this.lastStats = stats; this.lastIndex = { v: 5, entries: sorted, }; - return this.lastIndex; + return { storyIndex: this.lastIndex, stats: this.lastStats }; } catch (err) { this.lastError = err == null || err instanceof Error ? err : undefined; invariant(this.lastError); diff --git a/code/core/src/core-server/utils/__tests__/index-extraction.test.ts b/code/core/src/core-server/utils/__tests__/index-extraction.test.ts index 01a880479b99..6fbbdeb41c6c 100644 --- a/code/core/src/core-server/utils/__tests__/index-extraction.test.ts +++ b/code/core/src/core-server/utils/__tests__/index-extraction.test.ts @@ -62,9 +62,12 @@ describe('story extraction', () => { "entries": [ { "componentPath": undefined, + "extra": { + "metaId": "a", + "stats": {}, + }, "id": "a--story-one", "importPath": "./src/A.stories.js", - "metaId": "a", "name": "Story One", "tags": [ "story-tag-from-indexer", @@ -74,9 +77,12 @@ describe('story extraction', () => { }, { "componentPath": undefined, + "extra": { + "metaId": "custom-id", + "stats": {}, + }, "id": "some-fully-custom-id", "importPath": "./src/A.stories.js", - "metaId": "custom-id", "name": "Another Story Name", "tags": [ "story-tag-from-indexer", @@ -118,9 +124,12 @@ describe('story extraction', () => { "entries": [ { "componentPath": undefined, + "extra": { + "metaId": undefined, + "stats": {}, + }, "id": "f--story-one", "importPath": "./src/first-nested/deeply/F.stories.js", - "metaId": undefined, "name": "Story One", "tags": [], "title": "F", @@ -164,9 +173,12 @@ describe('story extraction', () => { "entries": [ { "componentPath": undefined, + "extra": { + "metaId": "a", + "stats": {}, + }, "id": "a--story-one", "importPath": "./src/first-nested/deeply/F.stories.js", - "metaId": "a", "name": "Story One", "tags": [ "story-tag-from-indexer", @@ -212,9 +224,12 @@ describe('story extraction', () => { "entries": [ { "componentPath": undefined, + "extra": { + "metaId": "a", + "stats": {}, + }, "id": "a--story-one", "importPath": "./src/A.stories.js", - "metaId": "a", "name": "Story One", "tags": [ "story-tag-from-indexer", @@ -278,9 +293,12 @@ describe('story extraction', () => { "entries": [ { "componentPath": undefined, + "extra": { + "metaId": undefined, + "stats": {}, + }, "id": "a--story-one", "importPath": "./src/A.stories.js", - "metaId": undefined, "name": "Story One", "tags": [ "story-tag-from-indexer", @@ -290,9 +308,12 @@ describe('story extraction', () => { }, { "componentPath": undefined, + "extra": { + "metaId": undefined, + "stats": {}, + }, "id": "custom-title--story-two", "importPath": "./src/A.stories.js", - "metaId": undefined, "name": "Custom Name For Second Story", "tags": [ "story-tag-from-indexer", @@ -302,9 +323,12 @@ describe('story extraction', () => { }, { "componentPath": undefined, + "extra": { + "metaId": "custom-meta-id", + "stats": {}, + }, "id": "custom-meta-id--story-three", "importPath": "./src/A.stories.js", - "metaId": "custom-meta-id", "name": "Story Three", "tags": [ "story-tag-from-indexer", @@ -347,9 +371,12 @@ describe('story extraction', () => { "entries": [ { "componentPath": undefined, + "extra": { + "metaId": undefined, + "stats": {}, + }, "id": "a--story-one", "importPath": "./src/A.stories.js", - "metaId": undefined, "name": "Story One", "tags": [ "story-tag-from-indexer", @@ -397,9 +424,12 @@ describe('docs entries from story extraction', () => { "entries": [ { "componentPath": undefined, + "extra": { + "metaId": undefined, + "stats": {}, + }, "id": "a--story-one", "importPath": "./src/A.stories.js", - "metaId": undefined, "name": "Story One", "tags": [ "story-tag-from-indexer", @@ -457,9 +487,12 @@ describe('docs entries from story extraction', () => { }, { "componentPath": undefined, + "extra": { + "metaId": undefined, + "stats": {}, + }, "id": "a--story-one", "importPath": "./src/A.stories.js", - "metaId": undefined, "name": "Story One", "tags": [ "autodocs", @@ -506,9 +539,12 @@ describe('docs entries from story extraction', () => { "entries": [ { "componentPath": undefined, + "extra": { + "metaId": undefined, + "stats": {}, + }, "id": "a--story-one", "importPath": "./src/A.stories.js", - "metaId": undefined, "name": "Story One", "tags": [ "autodocs", diff --git a/code/core/src/core-server/utils/doTelemetry.ts b/code/core/src/core-server/utils/doTelemetry.ts index b7d039119c3b..be9479cf3348 100644 --- a/code/core/src/core-server/utils/doTelemetry.ts +++ b/code/core/src/core-server/utils/doTelemetry.ts @@ -15,9 +15,9 @@ export async function doTelemetry( ) { if (!core?.disableTelemetry) { initializedStoryIndexGenerator.then(async (generator) => { - let storyIndex: StoryIndex | undefined; + let indexAndStats; try { - storyIndex = await generator?.getIndex(); + indexAndStats = await generator?.getIndexAndStats(); } catch (err) { // If we fail to get the index, treat it as a recoverable error, but send it up to telemetry // as if we crashed. In the future we will revisit this to send a distinct error @@ -36,10 +36,11 @@ export async function doTelemetry( const payload = { precedingUpgrade: await getPrecedingUpgrade(), }; - if (storyIndex) { + if (indexAndStats) { Object.assign(payload, { versionStatus: versionUpdates && versionCheck ? versionStatus(versionCheck) : 'disabled', - storyIndex: summarizeIndex(storyIndex), + storyIndex: summarizeIndex(indexAndStats.storyIndex), + storyStats: indexAndStats.stats, }); } telemetry('dev', payload, { configDir: options.configDir }); diff --git a/code/core/src/core-server/utils/summarizeStats.test.ts b/code/core/src/core-server/utils/summarizeStats.test.ts new file mode 100644 index 000000000000..e1e342e94260 --- /dev/null +++ b/code/core/src/core-server/utils/summarizeStats.test.ts @@ -0,0 +1,18 @@ +import { it, expect } from 'vitest'; +import { summarizeStats } from './summarizeStats'; + +it('should summarize stats', () => { + const stats = [ + { play: true, render: true, storyFn: false }, + { play: true, render: false, storyFn: false }, + { play: false, render: false, storyFn: false }, + ]; + const result = summarizeStats(stats); + expect(result).toMatchInlineSnapshot(` + { + "play": 2, + "render": 1, + "storyFn": 0, + } + `); +}); diff --git a/code/core/src/core-server/utils/summarizeStats.ts b/code/core/src/core-server/utils/summarizeStats.ts new file mode 100644 index 000000000000..bdd0ef026ce5 --- /dev/null +++ b/code/core/src/core-server/utils/summarizeStats.ts @@ -0,0 +1,18 @@ +import type { IndexInputStats } from '@storybook/core/types'; + +export type IndexStatsSummary = Record; + +export const addStats = (stat: IndexInputStats, acc: IndexStatsSummary) => { + Object.entries(stat).forEach(([key, value]) => { + const statsKey = key as keyof IndexInputStats; + if (!acc[statsKey]) acc[statsKey] = 0; + acc[statsKey] += value ? 1 : 0; + }); +}; + +export const summarizeStats = (stats: IndexInputStats[]): IndexStatsSummary => { + return stats.reduce((acc, stat) => { + addStats(stat, acc); + return acc; + }, {} as IndexStatsSummary); +}; diff --git a/code/core/src/csf-tools/CsfFile.test.ts b/code/core/src/csf-tools/CsfFile.test.ts index 4c87576d2474..0567154942ca 100644 --- a/code/core/src/csf-tools/CsfFile.test.ts +++ b/code/core/src/csf-tools/CsfFile.test.ts @@ -42,11 +42,19 @@ describe('CsfFile', () => { parameters: __isArgsStory: false __id: foo-bar--a + __stats: + play: false + render: false + storyFn: true - id: foo-bar--b name: B parameters: __isArgsStory: true __id: foo-bar--b + __stats: + play: false + render: false + storyFn: true `); }); @@ -69,10 +77,18 @@ describe('CsfFile', () => { name: A parameters: __id: foo-bar--a + __stats: + play: false + render: false + storyFn: false - id: foo-bar--b name: B parameters: __id: foo-bar--b + __stats: + play: false + render: false + storyFn: false `); }); @@ -94,6 +110,10 @@ describe('CsfFile', () => { parameters: __isArgsStory: false __id: foo-bar--basic + __stats: + play: false + render: false + storyFn: true `); }); @@ -116,6 +136,10 @@ describe('CsfFile', () => { stories: - id: foo-bar--a name: A + __stats: + play: false + render: false + storyFn: true `); }); @@ -136,6 +160,10 @@ describe('CsfFile', () => { stories: - id: foo-bar--include-a name: Include A + __stats: + play: false + render: false + storyFn: true `); }); @@ -154,6 +182,10 @@ describe('CsfFile', () => { stories: - id: foo-bar--a name: Some story + __stats: + play: false + render: false + storyFn: true `); }); @@ -173,8 +205,16 @@ describe('CsfFile', () => { stories: - id: default-title--a name: A + __stats: + play: false + render: false + storyFn: true - id: default-title--b name: B + __stats: + play: false + render: false + storyFn: true `); }); @@ -194,8 +234,16 @@ describe('CsfFile', () => { stories: - id: custom-id--a name: A + __stats: + play: false + render: false + storyFn: true - id: custom-id--b name: B + __stats: + play: false + render: false + storyFn: true `); }); @@ -215,8 +263,16 @@ describe('CsfFile', () => { stories: - id: custom-meta-id--just-custom-meta-id name: Just Custom Meta Id + __stats: + play: false + render: false + storyFn: false - id: custom-id name: Custom Paremeters Id + __stats: + play: false + render: false + storyFn: false `); }); @@ -237,8 +293,16 @@ describe('CsfFile', () => { stories: - id: foo-bar-baz--a name: A + __stats: + play: false + render: false + storyFn: true - id: foo-bar-baz--b name: B + __stats: + play: false + render: false + storyFn: true `); }); @@ -263,11 +327,19 @@ describe('CsfFile', () => { parameters: __isArgsStory: true __id: foo-bar--a + __stats: + play: false + render: false + storyFn: false - id: foo-bar--b name: B parameters: __isArgsStory: true __id: foo-bar--b + __stats: + play: false + render: false + storyFn: false `); }); @@ -292,11 +364,19 @@ describe('CsfFile', () => { parameters: __isArgsStory: true __id: foo-bar--a + __stats: + play: false + render: false + storyFn: false - id: foo-bar--b name: B parameters: __isArgsStory: true __id: foo-bar--b + __stats: + play: false + render: false + storyFn: false `); }); @@ -318,8 +398,16 @@ describe('CsfFile', () => { stories: - id: foo-bar-baz--a name: A + __stats: + play: false + render: false + storyFn: true - id: foo-bar-baz--b name: B + __stats: + play: false + render: false + storyFn: true `); }); @@ -341,8 +429,16 @@ describe('CsfFile', () => { stories: - id: foo-bar-baz--a name: A + __stats: + play: false + render: false + storyFn: true - id: foo-bar-baz--b name: B + __stats: + play: false + render: false + storyFn: true `); }); @@ -362,8 +458,16 @@ describe('CsfFile', () => { stories: - id: default-title--a name: A + __stats: + play: false + render: false + storyFn: true - id: default-title--b name: B + __stats: + play: false + render: false + storyFn: true `); }); @@ -387,6 +491,10 @@ describe('CsfFile', () => { parameters: __isArgsStory: true __id: foo-bar--a + __stats: + play: false + render: false + storyFn: true `); }); @@ -409,6 +517,10 @@ describe('CsfFile', () => { parameters: __isArgsStory: false __id: foo-bar--a + __stats: + play: false + render: false + storyFn: true `); }); @@ -432,6 +544,10 @@ describe('CsfFile', () => { __isArgsStory: false __id: foo-bar--page docsOnly: true + __stats: + play: false + render: false + storyFn: true `); }); @@ -460,6 +576,10 @@ describe('CsfFile', () => { __isArgsStory: false __id: foo-bar--page docsOnly: true + __stats: + play: false + render: false + storyFn: true `); }); @@ -483,11 +603,19 @@ describe('CsfFile', () => { parameters: __isArgsStory: false __id: foo-bar--a + __stats: + play: false + render: false + storyFn: true - id: foo-bar--b name: B parameters: __isArgsStory: true __id: foo-bar--b + __stats: + play: false + render: false + storyFn: true `); }); @@ -506,8 +634,16 @@ describe('CsfFile', () => { stories: - id: foo-bar--a name: A + __stats: + play: false + render: false + storyFn: false - id: foo-bar--b name: B + __stats: + play: false + render: false + storyFn: false `); }); @@ -531,11 +667,19 @@ describe('CsfFile', () => { parameters: __isArgsStory: true __id: foo-bar--b + __stats: + play: false + render: false + storyFn: true - id: foo-bar--a name: A parameters: __isArgsStory: false __id: foo-bar--a + __stats: + play: false + render: false + storyFn: true `); }); @@ -560,6 +704,10 @@ describe('CsfFile', () => { name: A parameters: __id: foo-bar--a + __stats: + play: false + render: false + storyFn: true `); }); @@ -631,8 +779,16 @@ describe('CsfFile', () => { stories: - id: default-title--a name: A + __stats: + play: false + render: false + storyFn: true - id: default-title--b name: B + __stats: + play: false + render: false + storyFn: true `); }); @@ -677,8 +833,16 @@ describe('CsfFile', () => { stories: - id: foo-bar--a name: A + __stats: + play: false + render: false + storyFn: true - id: foo-bar--b name: B + __stats: + play: false + render: false + storyFn: true `); }); }); @@ -747,6 +911,10 @@ describe('CsfFile', () => { parameters: __isArgsStory: false __id: foo-bar--a + __stats: + play: false + render: true + storyFn: false `); }); @@ -770,6 +938,10 @@ describe('CsfFile', () => { parameters: __isArgsStory: true __id: foo-bar--a + __stats: + play: false + render: true + storyFn: false `); }); @@ -791,6 +963,10 @@ describe('CsfFile', () => { parameters: __isArgsStory: true __id: foo-bar--a + __stats: + play: false + render: false + storyFn: false `); }); @@ -814,6 +990,10 @@ describe('CsfFile', () => { parameters: __isArgsStory: true __id: foo-bar--a + __stats: + play: false + render: false + storyFn: false `); }); @@ -886,6 +1066,10 @@ describe('CsfFile', () => { stories: - id: foo-bar--a name: A + __stats: + play: false + render: false + storyFn: true tags: - 'Y' `); @@ -910,6 +1094,10 @@ describe('CsfFile', () => { stories: - id: foo-bar--a name: A + __stats: + play: false + render: true + storyFn: false tags: - 'Y' `); @@ -936,6 +1124,10 @@ describe('CsfFile', () => { stories: - id: foo-bar--a name: A + __stats: + play: false + render: true + storyFn: false tags: - 'Y' `); @@ -987,6 +1179,10 @@ describe('CsfFile', () => { stories: - id: foo-bar--a name: A + __stats: + play: true + render: false + storyFn: true tags: - 'Y' - play-fn @@ -1013,6 +1209,10 @@ describe('CsfFile', () => { stories: - id: foo-bar--a name: A + __stats: + play: true + render: true + storyFn: false tags: - 'Y' - play-fn @@ -1039,6 +1239,10 @@ describe('CsfFile', () => { stories: - id: foo-bar--a name: A + __stats: + play: true + render: true + storyFn: false tags: - 'Y' `); @@ -1062,6 +1266,10 @@ describe('CsfFile', () => { stories: - id: foo-bar--a name: A + __stats: + play: true + render: false + storyFn: true tags: - 'Y' `); @@ -1103,6 +1311,10 @@ describe('CsfFile', () => { - story-tag - play-fn __id: component-id--a + __stats: + play: true + render: false + storyFn: false - type: story importPath: foo/bar.stories.js exportName: B @@ -1114,6 +1326,10 @@ describe('CsfFile', () => { - story-tag - play-fn __id: component-id--b + __stats: + play: true + render: false + storyFn: false `); }); @@ -1143,6 +1359,10 @@ describe('CsfFile', () => { tags: - component-tag __id: custom-story-id + __stats: + play: false + render: false + storyFn: false `); }); @@ -1177,6 +1397,10 @@ describe('CsfFile', () => { - story-tag-dup - inherit-tag-dup __id: custom-foo-title--a + __stats: + play: false + render: false + storyFn: false `); }); @@ -1226,6 +1450,10 @@ describe('CsfFile', () => { title: custom foo title tags: [] __id: custom-foo-title--a + __stats: + play: false + render: true + storyFn: false `); }); @@ -1254,6 +1482,10 @@ describe('CsfFile', () => { title: custom foo title tags: [] __id: custom-foo-title--a + __stats: + play: false + render: true + storyFn: false `); }); @@ -1282,6 +1514,10 @@ describe('CsfFile', () => { title: custom foo title tags: [] __id: custom-foo-title--a + __stats: + play: false + render: true + storyFn: false `); }); @@ -1310,6 +1546,10 @@ describe('CsfFile', () => { title: custom foo title tags: [] __id: custom-foo-title--a + __stats: + play: false + render: true + storyFn: false `); }); }); diff --git a/code/core/src/csf-tools/CsfFile.ts b/code/core/src/csf-tools/CsfFile.ts index 2ab3bfa27408..9e492598dae4 100644 --- a/code/core/src/csf-tools/CsfFile.ts +++ b/code/core/src/csf-tools/CsfFile.ts @@ -9,12 +9,18 @@ import bt from '@babel/traverse'; import * as recast from 'recast'; import { toId, isExportStory, storyNameFromExport } from '@storybook/csf'; -import type { ComponentAnnotations, StoryAnnotations, Tag } from '@storybook/core/types'; +import type { + Tag, + StoryAnnotations, + ComponentAnnotations, + IndexedCSFFile, + IndexInput, + IndexInputStats, +} from '@storybook/core/types'; import type { Options } from 'recast'; import { babelParse } from './babelParse'; import { findVarInitialization } from './findVarInitialization'; import type { PrintResultType } from './PrintResultType'; -import type { IndexInput, IndexedCSFFile } from '@storybook/core/types'; // @ts-expect-error (needed due to it's use of `exports.default`) const traverse = (bt.default || bt) as typeof bt; @@ -136,6 +142,7 @@ export interface StaticMeta export interface StaticStory extends Pick { id: string; + __stats: IndexInputStats; } export class CsfFile { @@ -392,6 +399,7 @@ export class CsfFile { id: 'FIXME', name, parameters, + __stats: {}, }; } }); @@ -422,7 +430,12 @@ export class CsfFile { } } else { self._storyAnnotations[exportName] = {}; - self._stories[exportName] = { id: 'FIXME', name: exportName, parameters: {} }; + self._stories[exportName] = { + id: 'FIXME', + name: exportName, + parameters: {}, + __stats: {}, + }; } } }); @@ -520,7 +533,8 @@ export class CsfFile { parameters.docsOnly = true; } acc[key] = { ...story, id, parameters }; - const { tags, play } = self._storyAnnotations[key]; + const storyAnnotations = self._storyAnnotations[key]; + const { tags, play } = storyAnnotations; if (tags) { const node = t.isIdentifier(tags) ? findVarInitialization(tags.name, this._ast.program) @@ -530,6 +544,15 @@ export class CsfFile { if (play) { acc[key].tags = [...(acc[key].tags || []), 'play-fn']; } + ['play', 'render'].forEach((annotation) => { + acc[key].__stats[annotation as keyof IndexInputStats] = + !!storyAnnotations[annotation] || !!self._metaAnnotations[annotation]; + }); + const storyExport = self.getStoryExport(key); + acc[key].__stats.storyFn = !!( + t.isArrowFunctionExpression(storyExport) || t.isFunctionDeclaration(storyExport) + ); + return acc; }, {} as Record @@ -589,6 +612,7 @@ export class CsfFile { metaId: this.meta?.id, tags, __id: story.id, + __stats: story.__stats, }; }); } diff --git a/code/core/src/types/modules/indexer.ts b/code/core/src/types/modules/indexer.ts index ceb3bf915e51..251a252daa0e 100644 --- a/code/core/src/types/modules/indexer.ts +++ b/code/core/src/types/modules/indexer.ts @@ -87,6 +87,12 @@ export type DocsIndexEntry = BaseIndexEntry & { export type IndexEntry = StoryIndexEntry | DocsIndexEntry; +export interface IndexInputStats { + play?: boolean; + render?: boolean; + storyFn?: boolean; +} + /** * The base input for indexing a story or docs entry. */ @@ -115,6 +121,10 @@ export type BaseIndexInput = { * Only use this if you need to override the auto-generated id. */ __id?: StoryId; + /** + * Stats about language feature usage that the indexer can optionally report + */ + __stats?: IndexInputStats; }; /** diff --git a/code/renderers/react/template/stories/csf1.stories.tsx b/code/renderers/react/template/stories/csf1.stories.tsx new file mode 100644 index 000000000000..63a53672b396 --- /dev/null +++ b/code/renderers/react/template/stories/csf1.stories.tsx @@ -0,0 +1,11 @@ +import React from 'react'; + +export default { + component: {}, + parameters: { + chromatic: { disable: true }, + }, +}; + +export const Hello1 = () =>
Hello1
; +export const Hello2 = () =>
Hello2
; diff --git a/code/renderers/react/template/stories/csf2.stories.tsx b/code/renderers/react/template/stories/csf2.stories.tsx new file mode 100644 index 000000000000..6b78e54e36e2 --- /dev/null +++ b/code/renderers/react/template/stories/csf2.stories.tsx @@ -0,0 +1,17 @@ +import React from 'react'; + +export default { + component: {}, + parameters: { + chromatic: { disable: true }, + }, +}; + +const Template = ({ label }: { label: string }) =>
{label}
; +Template.args = { label: 'Hello' }; + +export const Hello1 = Template.bind({}); + +export const Hello2 = Template.bind({}); + +export const Hello3 = Template.bind({}); From 047b67c4d215f98a3773a7b2f0ec923b18ee7c9e Mon Sep 17 00:00:00 2001 From: Michael Shilman Date: Fri, 19 Jul 2024 19:15:13 +0800 Subject: [PATCH 3/4] Add StoryIndexGenerator stats tests --- .../utils/StoryIndexGenerator.test.ts | 197 ++++++++++++++++-- .../first-nested/deeply/Features.stories.jsx | 21 ++ 2 files changed, 197 insertions(+), 21 deletions(-) create mode 100644 code/core/src/core-server/utils/__mockdata__/src/first-nested/deeply/Features.stories.jsx diff --git a/code/core/src/core-server/utils/StoryIndexGenerator.test.ts b/code/core/src/core-server/utils/StoryIndexGenerator.test.ts index 2d2b8d2b19c4..e38eb8e0e037 100644 --- a/code/core/src/core-server/utils/StoryIndexGenerator.test.ts +++ b/code/core/src/core-server/utils/StoryIndexGenerator.test.ts @@ -68,7 +68,8 @@ describe('StoryIndexGenerator', () => { const generator = new StoryIndexGenerator([specifier], options); await generator.initialize(); - expect(await generator.getIndex()).toMatchInlineSnapshot(` + const { storyIndex, stats } = await generator.getIndexAndStats(); + expect(storyIndex).toMatchInlineSnapshot(` { "entries": { "a--story-one": { @@ -89,6 +90,14 @@ describe('StoryIndexGenerator', () => { "v": 5, } `); + + expect(stats).toMatchInlineSnapshot(` + { + "play": 0, + "render": 0, + "storyFn": 0, + } + `); }); }); describe('single file .story specifier', () => { @@ -101,7 +110,8 @@ describe('StoryIndexGenerator', () => { const generator = new StoryIndexGenerator([specifier], options); await generator.initialize(); - expect(await generator.getIndex()).toMatchInlineSnapshot(` + const { storyIndex } = await generator.getIndexAndStats(); + expect(storyIndex).toMatchInlineSnapshot(` { "entries": { "f--story-one": { @@ -133,7 +143,8 @@ describe('StoryIndexGenerator', () => { const generator = new StoryIndexGenerator([specifier], options); await generator.initialize(); - expect(await generator.getIndex()).toMatchInlineSnapshot(` + const { storyIndex } = await generator.getIndexAndStats(); + expect(storyIndex).toMatchInlineSnapshot(` { "entries": { "stories--story-one": { @@ -165,7 +176,8 @@ describe('StoryIndexGenerator', () => { const generator = new StoryIndexGenerator([specifier], options); await generator.initialize(); - expect(await generator.getIndex()).toMatchInlineSnapshot(` + const { storyIndex } = await generator.getIndexAndStats(); + expect(storyIndex).toMatchInlineSnapshot(` { "entries": { "componentpath-extension--story-one": { @@ -245,7 +257,8 @@ describe('StoryIndexGenerator', () => { const generator = new StoryIndexGenerator([specifier], options); await generator.initialize(); - expect(await generator.getIndex()).toMatchInlineSnapshot(` + const { storyIndex, stats } = await generator.getIndexAndStats(); + expect(storyIndex).toMatchInlineSnapshot(` { "entries": { "a--story-one": { @@ -336,6 +349,56 @@ describe('StoryIndexGenerator', () => { "title": "first-nested/deeply/F", "type": "story", }, + "first-nested-deeply-features--with-play": { + "componentPath": undefined, + "id": "first-nested-deeply-features--with-play", + "importPath": "./src/first-nested/deeply/Features.stories.jsx", + "name": "With Play", + "tags": [ + "dev", + "test", + "play-fn", + ], + "title": "first-nested/deeply/Features", + "type": "story", + }, + "first-nested-deeply-features--with-render": { + "componentPath": undefined, + "id": "first-nested-deeply-features--with-render", + "importPath": "./src/first-nested/deeply/Features.stories.jsx", + "name": "With Render", + "tags": [ + "dev", + "test", + ], + "title": "first-nested/deeply/Features", + "type": "story", + }, + "first-nested-deeply-features--with-story-fn": { + "componentPath": undefined, + "id": "first-nested-deeply-features--with-story-fn", + "importPath": "./src/first-nested/deeply/Features.stories.jsx", + "name": "With Story Fn", + "tags": [ + "dev", + "test", + ], + "title": "first-nested/deeply/Features", + "type": "story", + }, + "first-nested-deeply-features--with-test": { + "componentPath": undefined, + "id": "first-nested-deeply-features--with-test", + "importPath": "./src/first-nested/deeply/Features.stories.jsx", + "name": "With Test", + "tags": [ + "dev", + "test", + "play-fn", + ], + "title": "first-nested/deeply/Features", + "type": "story", + }, "h--story-one": { "componentPath": undefined, "id": "h--story-one", @@ -378,6 +441,14 @@ describe('StoryIndexGenerator', () => { "v": 5, } `); + + expect(stats).toMatchInlineSnapshot(` + { + "play": 2, + "render": 1, + "storyFn": 1, + } + `); }); }); @@ -395,7 +466,8 @@ describe('StoryIndexGenerator', () => { const generator = new StoryIndexGenerator([specifier], autodocsOptions); await generator.initialize(); - expect(await generator.getIndex()).toMatchInlineSnapshot(` + const { storyIndex, stats } = await generator.getIndexAndStats(); + expect(storyIndex).toMatchInlineSnapshot(` { "entries": { "a--story-one": { @@ -512,6 +584,56 @@ describe('StoryIndexGenerator', () => { "title": "first-nested/deeply/F", "type": "story", }, + "first-nested-deeply-features--with-play": { + "componentPath": undefined, + "id": "first-nested-deeply-features--with-play", + "importPath": "./src/first-nested/deeply/Features.stories.jsx", + "name": "With Play", + "tags": [ + "dev", + "test", + "play-fn", + ], + "title": "first-nested/deeply/Features", + "type": "story", + }, + "first-nested-deeply-features--with-render": { + "componentPath": undefined, + "id": "first-nested-deeply-features--with-render", + "importPath": "./src/first-nested/deeply/Features.stories.jsx", + "name": "With Render", + "tags": [ + "dev", + "test", + ], + "title": "first-nested/deeply/Features", + "type": "story", + }, + "first-nested-deeply-features--with-story-fn": { + "componentPath": undefined, + "id": "first-nested-deeply-features--with-story-fn", + "importPath": "./src/first-nested/deeply/Features.stories.jsx", + "name": "With Story Fn", + "tags": [ + "dev", + "test", + ], + "title": "first-nested/deeply/Features", + "type": "story", + }, + "first-nested-deeply-features--with-test": { + "componentPath": undefined, + "id": "first-nested-deeply-features--with-test", + "importPath": "./src/first-nested/deeply/Features.stories.jsx", + "name": "With Test", + "tags": [ + "dev", + "test", + "play-fn", + ], + "title": "first-nested/deeply/Features", + "type": "story", + }, "h--docs": { "id": "h--docs", "importPath": "./src/H.stories.mjs", @@ -567,6 +689,14 @@ describe('StoryIndexGenerator', () => { "v": 5, } `); + + expect(stats).toMatchInlineSnapshot(` + { + "play": 2, + "render": 1, + "storyFn": 1, + } + `); }); const autodocsTrueOptions = { @@ -603,6 +733,11 @@ describe('StoryIndexGenerator', () => { "componentpath-package--story-one", "first-nested-deeply-f--docs", "first-nested-deeply-f--story-one", + "first-nested-deeply-features--docs", + "first-nested-deeply-features--with-play", + "first-nested-deeply-features--with-story-fn", + "first-nested-deeply-features--with-render", + "first-nested-deeply-features--with-test", "nested-button--docs", "nested-button--story-one", "second-nested-g--docs", @@ -639,6 +774,11 @@ describe('StoryIndexGenerator', () => { "componentpath-package--story-one", "first-nested-deeply-f--docs", "first-nested-deeply-f--story-one", + "first-nested-deeply-features--docs", + "first-nested-deeply-features--with-play", + "first-nested-deeply-features--with-story-fn", + "first-nested-deeply-features--with-render", + "first-nested-deeply-features--with-test", "nested-button--docs", "nested-button--story-one", "second-nested-g--docs", @@ -730,7 +870,8 @@ describe('StoryIndexGenerator', () => { const generator = new StoryIndexGenerator([csfSpecifier, docsSpecifier], autodocsOptions); await generator.initialize(); - expect(await generator.getIndex()).toMatchInlineSnapshot(` + const { storyIndex } = await generator.getIndexAndStats(); + expect(storyIndex).toMatchInlineSnapshot(` { "entries": { "b--docs": { @@ -794,7 +935,8 @@ describe('StoryIndexGenerator', () => { const generator = new StoryIndexGenerator([csfSpecifier, docsSpecifier], autodocsOptions); await generator.initialize(); - expect(await generator.getIndex()).toMatchInlineSnapshot(` + const { storyIndex } = await generator.getIndexAndStats(); + expect(storyIndex).toMatchInlineSnapshot(` { "entries": { "b--docs": { @@ -862,7 +1004,8 @@ describe('StoryIndexGenerator', () => { ); await generator.initialize(); - expect(await generator.getIndex()).toMatchInlineSnapshot(` + const { storyIndex } = await generator.getIndexAndStats(); + expect(storyIndex).toMatchInlineSnapshot(` { "entries": { "a--docs": { @@ -919,7 +1062,8 @@ describe('StoryIndexGenerator', () => { generator.getProjectTags = () => ['dev', 'test', 'autodocs']; await generator.initialize(); - expect(await generator.getIndex()).toMatchInlineSnapshot(` + const { storyIndex } = await generator.getIndexAndStats(); + expect(storyIndex).toMatchInlineSnapshot(` { "entries": { "a--docs": { @@ -970,7 +1114,8 @@ describe('StoryIndexGenerator', () => { const generator = new StoryIndexGenerator([specifier], autodocsOptions); await generator.initialize(); - expect(await generator.getIndex()).toMatchInlineSnapshot(` + const { storyIndex } = await generator.getIndexAndStats(); + expect(storyIndex).toMatchInlineSnapshot(` { "entries": { "duplicate-a--docs": { @@ -1030,7 +1175,8 @@ describe('StoryIndexGenerator', () => { const generator = new StoryIndexGenerator([csfSpecifier], autodocsOptions); await generator.initialize(); - expect(await generator.getIndex()).toMatchInlineSnapshot(` + const { storyIndex } = await generator.getIndexAndStats(); + expect(storyIndex).toMatchInlineSnapshot(` { "entries": {}, "v": 5, @@ -1047,7 +1193,8 @@ describe('StoryIndexGenerator', () => { const generator = new StoryIndexGenerator([csfSpecifier], autodocsOptions); await generator.initialize(); - expect(await generator.getIndex()).toMatchInlineSnapshot(` + const { storyIndex } = await generator.getIndexAndStats(); + expect(storyIndex).toMatchInlineSnapshot(` { "entries": { "my-component-a--docs": { @@ -1088,7 +1235,8 @@ describe('StoryIndexGenerator', () => { const generator = new StoryIndexGenerator([storiesSpecifier, docsSpecifier], options); await generator.initialize(); - expect(await generator.getIndex()).toMatchInlineSnapshot(` + const { storyIndex } = await generator.getIndexAndStats(); + expect(storyIndex).toMatchInlineSnapshot(` { "entries": { "a--metaof": { @@ -1222,7 +1370,8 @@ describe('StoryIndexGenerator', () => { }); await generator.initialize(); - expect(await generator.getIndex()).toMatchInlineSnapshot(` + const { storyIndex } = await generator.getIndexAndStats(); + expect(storyIndex).toMatchInlineSnapshot(` { "entries": { "a--metaof": { @@ -1328,7 +1477,8 @@ describe('StoryIndexGenerator', () => { options ); await generator.initialize(); - expect(await generator.getIndex()).toMatchInlineSnapshot(` + const { storyIndex } = await generator.getIndexAndStats(); + expect(storyIndex).toMatchInlineSnapshot(` { "entries": { "a--story-one": { @@ -1395,7 +1545,8 @@ describe('StoryIndexGenerator', () => { const generator = new StoryIndexGenerator([csfSpecifier, docsSpecifier], options); await generator.initialize(); - expect(await generator.getIndex()).toMatchInlineSnapshot(` + const { storyIndex } = await generator.getIndexAndStats(); + expect(storyIndex).toMatchInlineSnapshot(` { "entries": { "my-component-b--docs": { @@ -1603,6 +1754,10 @@ describe('StoryIndexGenerator', () => { "componentpath-noextension--story-one", "componentpath-package--story-one", "first-nested-deeply-f--story-one", + "first-nested-deeply-features--with-play", + "first-nested-deeply-features--with-story-fn", + "first-nested-deeply-features--with-render", + "first-nested-deeply-features--with-test", ] `); }); @@ -1621,7 +1776,7 @@ describe('StoryIndexGenerator', () => { const generator = new StoryIndexGenerator([specifier], options); await generator.initialize(); await generator.getIndex(); - expect(readCsfMock).toHaveBeenCalledTimes(10); + expect(readCsfMock).toHaveBeenCalledTimes(11); readCsfMock.mockClear(); await generator.getIndex(); @@ -1679,7 +1834,7 @@ describe('StoryIndexGenerator', () => { const generator = new StoryIndexGenerator([specifier], options); await generator.initialize(); await generator.getIndex(); - expect(readCsfMock).toHaveBeenCalledTimes(10); + expect(readCsfMock).toHaveBeenCalledTimes(11); generator.invalidate(specifier, './src/B.stories.ts', false); @@ -1764,7 +1919,7 @@ describe('StoryIndexGenerator', () => { const generator = new StoryIndexGenerator([specifier], options); await generator.initialize(); await generator.getIndex(); - expect(readCsfMock).toHaveBeenCalledTimes(10); + expect(readCsfMock).toHaveBeenCalledTimes(11); generator.invalidate(specifier, './src/B.stories.ts', true); @@ -1803,7 +1958,7 @@ describe('StoryIndexGenerator', () => { const generator = new StoryIndexGenerator([specifier], options); await generator.initialize(); await generator.getIndex(); - expect(readCsfMock).toHaveBeenCalledTimes(10); + expect(readCsfMock).toHaveBeenCalledTimes(11); generator.invalidate(specifier, './src/B.stories.ts', true); diff --git a/code/core/src/core-server/utils/__mockdata__/src/first-nested/deeply/Features.stories.jsx b/code/core/src/core-server/utils/__mockdata__/src/first-nested/deeply/Features.stories.jsx new file mode 100644 index 000000000000..d5de09d42f79 --- /dev/null +++ b/code/core/src/core-server/utils/__mockdata__/src/first-nested/deeply/Features.stories.jsx @@ -0,0 +1,21 @@ +const component = {}; +export default { + component, +}; + +export const WithPlay = { + play: async () => {}, +}; + +export const WithStoryFn = () => {}; + +export const WithRender = { + render: () => {}, +}; + +export const WithTest = { + beforeEach: async () => {}, + play: async ({ mount }) => { + await mount(); + }, +}; From aba435b85e969f888f2032bb2106cef85efe24a5 Mon Sep 17 00:00:00 2001 From: Michael Shilman Date: Fri, 19 Jul 2024 20:23:39 +0800 Subject: [PATCH 4/4] Track loaders and PR cleanup --- code/core/package.json | 2 +- .../utils/StoryIndexGenerator.test.ts | 30 +++++++++ .../core-server/utils/StoryIndexGenerator.ts | 5 +- .../first-nested/deeply/Features.stories.jsx | 6 ++ .../core-server/utils/stories-json.test.ts | 57 +++++++++++++++++ code/core/src/csf-tools/CsfFile.test.ts | 61 +++++++++++++++++++ code/core/src/csf-tools/CsfFile.ts | 2 +- code/core/src/types/modules/indexer.ts | 1 + code/yarn.lock | 10 +-- 9 files changed, 164 insertions(+), 10 deletions(-) diff --git a/code/core/package.json b/code/core/package.json index 3b545da0b3e0..a3c2d9ac89f3 100644 --- a/code/core/package.json +++ b/code/core/package.json @@ -285,7 +285,7 @@ "@radix-ui/react-dialog": "^1.0.5", "@radix-ui/react-scroll-area": "^1.0.5", "@radix-ui/react-slot": "^1.0.2", - "@storybook/docs-mdx": "4.0.0-next.0", + "@storybook/docs-mdx": "4.0.0-next.1", "@storybook/global": "^5.0.0", "@storybook/icons": "^1.2.5", "@tanstack/react-virtual": "^3.3.0", diff --git a/code/core/src/core-server/utils/StoryIndexGenerator.test.ts b/code/core/src/core-server/utils/StoryIndexGenerator.test.ts index e38eb8e0e037..c22842552fec 100644 --- a/code/core/src/core-server/utils/StoryIndexGenerator.test.ts +++ b/code/core/src/core-server/utils/StoryIndexGenerator.test.ts @@ -93,6 +93,7 @@ describe('StoryIndexGenerator', () => { expect(stats).toMatchInlineSnapshot(` { + "loaders": 0, "play": 0, "render": 0, "storyFn": 0, @@ -349,6 +350,18 @@ describe('StoryIndexGenerator', () => { "title": "first-nested/deeply/F", "type": "story", }, + "first-nested-deeply-features--with-csf-1": { + "componentPath": undefined, + "id": "first-nested-deeply-features--with-csf-1", + "importPath": "./src/first-nested/deeply/Features.stories.jsx", + "name": "With CSF 1", + "tags": [ + "dev", + "test", + ], + "title": "first-nested/deeply/Features", + "type": "story", + }, "first-nested-deeply-features--with-play": { "componentPath": undefined, "id": "first-nested-deeply-features--with-play", @@ -444,6 +457,7 @@ describe('StoryIndexGenerator', () => { expect(stats).toMatchInlineSnapshot(` { + "loaders": 1, "play": 2, "render": 1, "storyFn": 1, @@ -584,6 +598,18 @@ describe('StoryIndexGenerator', () => { "title": "first-nested/deeply/F", "type": "story", }, + "first-nested-deeply-features--with-csf-1": { + "componentPath": undefined, + "id": "first-nested-deeply-features--with-csf-1", + "importPath": "./src/first-nested/deeply/Features.stories.jsx", + "name": "With CSF 1", + "tags": [ + "dev", + "test", + ], + "title": "first-nested/deeply/Features", + "type": "story", + }, "first-nested-deeply-features--with-play": { "componentPath": undefined, "id": "first-nested-deeply-features--with-play", @@ -692,6 +718,7 @@ describe('StoryIndexGenerator', () => { expect(stats).toMatchInlineSnapshot(` { + "loaders": 1, "play": 2, "render": 1, "storyFn": 1, @@ -738,6 +765,7 @@ describe('StoryIndexGenerator', () => { "first-nested-deeply-features--with-story-fn", "first-nested-deeply-features--with-render", "first-nested-deeply-features--with-test", + "first-nested-deeply-features--with-csf-1", "nested-button--docs", "nested-button--story-one", "second-nested-g--docs", @@ -779,6 +807,7 @@ describe('StoryIndexGenerator', () => { "first-nested-deeply-features--with-story-fn", "first-nested-deeply-features--with-render", "first-nested-deeply-features--with-test", + "first-nested-deeply-features--with-csf-1", "nested-button--docs", "nested-button--story-one", "second-nested-g--docs", @@ -1758,6 +1787,7 @@ describe('StoryIndexGenerator', () => { "first-nested-deeply-features--with-story-fn", "first-nested-deeply-features--with-render", "first-nested-deeply-features--with-test", + "first-nested-deeply-features--with-csf-1", ] `); }); diff --git a/code/core/src/core-server/utils/StoryIndexGenerator.ts b/code/core/src/core-server/utils/StoryIndexGenerator.ts index 8f6e1fe0c0d1..64aa9744e00e 100644 --- a/code/core/src/core-server/utils/StoryIndexGenerator.ts +++ b/code/core/src/core-server/utils/StoryIndexGenerator.ts @@ -28,8 +28,7 @@ import { storyNameFromExport, toId, combineTags } from '@storybook/csf'; import { dedent } from 'ts-dedent'; import { autoName } from './autoName'; import { IndexingError, MultipleIndexingError } from './IndexingError'; -import { addStats, summarizeStats, type IndexStatsSummary } from './summarizeStats'; -import { Input } from '../../components/components/form/form.stories'; +import { addStats, type IndexStatsSummary } from './summarizeStats'; // Extended type to keep track of the csf meta id so we know the component id when referencing docs in `extractDocs` type StoryIndexEntryWithExtra = StoryIndexEntry & { @@ -264,7 +263,7 @@ export class StoryIndexGenerator { addStats(item.extra.stats, statsSummary); - // Drop the meta id as it isn't part of the index, we just used it for record keeping in `extractDocs` + // Drop extra data used for internal bookkeeping const { extra, ...existing } = item; return existing; }); diff --git a/code/core/src/core-server/utils/__mockdata__/src/first-nested/deeply/Features.stories.jsx b/code/core/src/core-server/utils/__mockdata__/src/first-nested/deeply/Features.stories.jsx index d5de09d42f79..90d96b590b6a 100644 --- a/code/core/src/core-server/utils/__mockdata__/src/first-nested/deeply/Features.stories.jsx +++ b/code/core/src/core-server/utils/__mockdata__/src/first-nested/deeply/Features.stories.jsx @@ -19,3 +19,9 @@ export const WithTest = { await mount(); }, }; + +export const WithCSF1 = { + parameters: {}, + decorators: [], + loaders: [], +}; diff --git a/code/core/src/core-server/utils/stories-json.test.ts b/code/core/src/core-server/utils/stories-json.test.ts index 84b201ab62f1..a4ad0fa780b7 100644 --- a/code/core/src/core-server/utils/stories-json.test.ts +++ b/code/core/src/core-server/utils/stories-json.test.ts @@ -263,6 +263,63 @@ describe('useStoriesJson', () => { "title": "first-nested/deeply/F", "type": "story", }, + "first-nested-deeply-features--with-csf-1": { + "id": "first-nested-deeply-features--with-csf-1", + "importPath": "./src/first-nested/deeply/Features.stories.jsx", + "name": "With CSF 1", + "tags": [ + "dev", + "test", + ], + "title": "first-nested/deeply/Features", + "type": "story", + }, + "first-nested-deeply-features--with-play": { + "id": "first-nested-deeply-features--with-play", + "importPath": "./src/first-nested/deeply/Features.stories.jsx", + "name": "With Play", + "tags": [ + "dev", + "test", + "play-fn", + ], + "title": "first-nested/deeply/Features", + "type": "story", + }, + "first-nested-deeply-features--with-render": { + "id": "first-nested-deeply-features--with-render", + "importPath": "./src/first-nested/deeply/Features.stories.jsx", + "name": "With Render", + "tags": [ + "dev", + "test", + ], + "title": "first-nested/deeply/Features", + "type": "story", + }, + "first-nested-deeply-features--with-story-fn": { + "id": "first-nested-deeply-features--with-story-fn", + "importPath": "./src/first-nested/deeply/Features.stories.jsx", + "name": "With Story Fn", + "tags": [ + "dev", + "test", + ], + "title": "first-nested/deeply/Features", + "type": "story", + }, + "first-nested-deeply-features--with-test": { + "id": "first-nested-deeply-features--with-test", + "importPath": "./src/first-nested/deeply/Features.stories.jsx", + "name": "With Test", + "tags": [ + "dev", + "test", + "play-fn", + ], + "title": "first-nested/deeply/Features", + "type": "story", + }, "h--story-one": { "id": "h--story-one", "importPath": "./src/H.stories.mjs", diff --git a/code/core/src/csf-tools/CsfFile.test.ts b/code/core/src/csf-tools/CsfFile.test.ts index 0567154942ca..390bcbb9e8ac 100644 --- a/code/core/src/csf-tools/CsfFile.test.ts +++ b/code/core/src/csf-tools/CsfFile.test.ts @@ -45,6 +45,7 @@ describe('CsfFile', () => { __stats: play: false render: false + loaders: false storyFn: true - id: foo-bar--b name: B @@ -54,6 +55,7 @@ describe('CsfFile', () => { __stats: play: false render: false + loaders: false storyFn: true `); }); @@ -80,6 +82,7 @@ describe('CsfFile', () => { __stats: play: false render: false + loaders: false storyFn: false - id: foo-bar--b name: B @@ -88,6 +91,7 @@ describe('CsfFile', () => { __stats: play: false render: false + loaders: false storyFn: false `); }); @@ -113,6 +117,7 @@ describe('CsfFile', () => { __stats: play: false render: false + loaders: false storyFn: true `); }); @@ -139,6 +144,7 @@ describe('CsfFile', () => { __stats: play: false render: false + loaders: false storyFn: true `); }); @@ -163,6 +169,7 @@ describe('CsfFile', () => { __stats: play: false render: false + loaders: false storyFn: true `); }); @@ -185,6 +192,7 @@ describe('CsfFile', () => { __stats: play: false render: false + loaders: false storyFn: true `); }); @@ -208,12 +216,14 @@ describe('CsfFile', () => { __stats: play: false render: false + loaders: false storyFn: true - id: default-title--b name: B __stats: play: false render: false + loaders: false storyFn: true `); }); @@ -237,12 +247,14 @@ describe('CsfFile', () => { __stats: play: false render: false + loaders: false storyFn: true - id: custom-id--b name: B __stats: play: false render: false + loaders: false storyFn: true `); }); @@ -266,12 +278,14 @@ describe('CsfFile', () => { __stats: play: false render: false + loaders: false storyFn: false - id: custom-id name: Custom Paremeters Id __stats: play: false render: false + loaders: false storyFn: false `); }); @@ -296,12 +310,14 @@ describe('CsfFile', () => { __stats: play: false render: false + loaders: false storyFn: true - id: foo-bar-baz--b name: B __stats: play: false render: false + loaders: false storyFn: true `); }); @@ -330,6 +346,7 @@ describe('CsfFile', () => { __stats: play: false render: false + loaders: false storyFn: false - id: foo-bar--b name: B @@ -339,6 +356,7 @@ describe('CsfFile', () => { __stats: play: false render: false + loaders: false storyFn: false `); }); @@ -367,6 +385,7 @@ describe('CsfFile', () => { __stats: play: false render: false + loaders: false storyFn: false - id: foo-bar--b name: B @@ -376,6 +395,7 @@ describe('CsfFile', () => { __stats: play: false render: false + loaders: false storyFn: false `); }); @@ -401,12 +421,14 @@ describe('CsfFile', () => { __stats: play: false render: false + loaders: false storyFn: true - id: foo-bar-baz--b name: B __stats: play: false render: false + loaders: false storyFn: true `); }); @@ -432,12 +454,14 @@ describe('CsfFile', () => { __stats: play: false render: false + loaders: false storyFn: true - id: foo-bar-baz--b name: B __stats: play: false render: false + loaders: false storyFn: true `); }); @@ -461,12 +485,14 @@ describe('CsfFile', () => { __stats: play: false render: false + loaders: false storyFn: true - id: default-title--b name: B __stats: play: false render: false + loaders: false storyFn: true `); }); @@ -494,6 +520,7 @@ describe('CsfFile', () => { __stats: play: false render: false + loaders: false storyFn: true `); }); @@ -520,6 +547,7 @@ describe('CsfFile', () => { __stats: play: false render: false + loaders: false storyFn: true `); }); @@ -547,6 +575,7 @@ describe('CsfFile', () => { __stats: play: false render: false + loaders: false storyFn: true `); }); @@ -579,6 +608,7 @@ describe('CsfFile', () => { __stats: play: false render: false + loaders: false storyFn: true `); }); @@ -606,6 +636,7 @@ describe('CsfFile', () => { __stats: play: false render: false + loaders: false storyFn: true - id: foo-bar--b name: B @@ -615,6 +646,7 @@ describe('CsfFile', () => { __stats: play: false render: false + loaders: false storyFn: true `); }); @@ -637,12 +669,14 @@ describe('CsfFile', () => { __stats: play: false render: false + loaders: false storyFn: false - id: foo-bar--b name: B __stats: play: false render: false + loaders: false storyFn: false `); }); @@ -670,6 +704,7 @@ describe('CsfFile', () => { __stats: play: false render: false + loaders: false storyFn: true - id: foo-bar--a name: A @@ -679,6 +714,7 @@ describe('CsfFile', () => { __stats: play: false render: false + loaders: false storyFn: true `); }); @@ -707,6 +743,7 @@ describe('CsfFile', () => { __stats: play: false render: false + loaders: false storyFn: true `); }); @@ -782,12 +819,14 @@ describe('CsfFile', () => { __stats: play: false render: false + loaders: false storyFn: true - id: default-title--b name: B __stats: play: false render: false + loaders: false storyFn: true `); }); @@ -836,12 +875,14 @@ describe('CsfFile', () => { __stats: play: false render: false + loaders: false storyFn: true - id: foo-bar--b name: B __stats: play: false render: false + loaders: false storyFn: true `); }); @@ -914,6 +955,7 @@ describe('CsfFile', () => { __stats: play: false render: true + loaders: false storyFn: false `); }); @@ -941,6 +983,7 @@ describe('CsfFile', () => { __stats: play: false render: true + loaders: false storyFn: false `); }); @@ -966,6 +1009,7 @@ describe('CsfFile', () => { __stats: play: false render: false + loaders: false storyFn: false `); }); @@ -993,6 +1037,7 @@ describe('CsfFile', () => { __stats: play: false render: false + loaders: false storyFn: false `); }); @@ -1069,6 +1114,7 @@ describe('CsfFile', () => { __stats: play: false render: false + loaders: false storyFn: true tags: - 'Y' @@ -1097,6 +1143,7 @@ describe('CsfFile', () => { __stats: play: false render: true + loaders: false storyFn: false tags: - 'Y' @@ -1127,6 +1174,7 @@ describe('CsfFile', () => { __stats: play: false render: true + loaders: false storyFn: false tags: - 'Y' @@ -1182,6 +1230,7 @@ describe('CsfFile', () => { __stats: play: true render: false + loaders: false storyFn: true tags: - 'Y' @@ -1212,6 +1261,7 @@ describe('CsfFile', () => { __stats: play: true render: true + loaders: false storyFn: false tags: - 'Y' @@ -1226,6 +1276,7 @@ describe('CsfFile', () => { export default { title: 'foo/bar', play: () => {}, tags: ['X'] }; export const A = { render: () => {}, + loaders: [], tags: ['Y'], }; ` @@ -1242,6 +1293,7 @@ describe('CsfFile', () => { __stats: play: true render: true + loaders: true storyFn: false tags: - 'Y' @@ -1269,6 +1321,7 @@ describe('CsfFile', () => { __stats: play: true render: false + loaders: false storyFn: true tags: - 'Y' @@ -1314,6 +1367,7 @@ describe('CsfFile', () => { __stats: play: true render: false + loaders: false storyFn: false - type: story importPath: foo/bar.stories.js @@ -1329,6 +1383,7 @@ describe('CsfFile', () => { __stats: play: true render: false + loaders: false storyFn: false `); }); @@ -1362,6 +1417,7 @@ describe('CsfFile', () => { __stats: play: false render: false + loaders: false storyFn: false `); }); @@ -1400,6 +1456,7 @@ describe('CsfFile', () => { __stats: play: false render: false + loaders: false storyFn: false `); }); @@ -1453,6 +1510,7 @@ describe('CsfFile', () => { __stats: play: false render: true + loaders: false storyFn: false `); }); @@ -1485,6 +1543,7 @@ describe('CsfFile', () => { __stats: play: false render: true + loaders: false storyFn: false `); }); @@ -1517,6 +1576,7 @@ describe('CsfFile', () => { __stats: play: false render: true + loaders: false storyFn: false `); }); @@ -1549,6 +1609,7 @@ describe('CsfFile', () => { __stats: play: false render: true + loaders: false storyFn: false `); }); diff --git a/code/core/src/csf-tools/CsfFile.ts b/code/core/src/csf-tools/CsfFile.ts index 9e492598dae4..3792a3903a51 100644 --- a/code/core/src/csf-tools/CsfFile.ts +++ b/code/core/src/csf-tools/CsfFile.ts @@ -544,7 +544,7 @@ export class CsfFile { if (play) { acc[key].tags = [...(acc[key].tags || []), 'play-fn']; } - ['play', 'render'].forEach((annotation) => { + ['play', 'render', 'loaders'].forEach((annotation) => { acc[key].__stats[annotation as keyof IndexInputStats] = !!storyAnnotations[annotation] || !!self._metaAnnotations[annotation]; }); diff --git a/code/core/src/types/modules/indexer.ts b/code/core/src/types/modules/indexer.ts index 251a252daa0e..ce20f88de187 100644 --- a/code/core/src/types/modules/indexer.ts +++ b/code/core/src/types/modules/indexer.ts @@ -88,6 +88,7 @@ export type DocsIndexEntry = BaseIndexEntry & { export type IndexEntry = StoryIndexEntry | DocsIndexEntry; export interface IndexInputStats { + loaders?: boolean; play?: boolean; render?: boolean; storyFn?: boolean; diff --git a/code/yarn.lock b/code/yarn.lock index 7c563aac8e27..ed97ee265727 100644 --- a/code/yarn.lock +++ b/code/yarn.lock @@ -5762,7 +5762,7 @@ __metadata: "@radix-ui/react-scroll-area": "npm:^1.0.5" "@radix-ui/react-slot": "npm:^1.0.2" "@storybook/csf": "npm:0.1.11" - "@storybook/docs-mdx": "npm:4.0.0-next.0" + "@storybook/docs-mdx": "npm:4.0.0-next.1" "@storybook/global": "npm:^5.0.0" "@storybook/icons": "npm:^1.2.5" "@tanstack/react-virtual": "npm:^3.3.0" @@ -5915,12 +5915,12 @@ __metadata: languageName: node linkType: hard -"@storybook/docs-mdx@npm:4.0.0-next.0": - version: 4.0.0-next.0 - resolution: "@storybook/docs-mdx@npm:4.0.0-next.0" +"@storybook/docs-mdx@npm:4.0.0-next.1": + version: 4.0.0-next.1 + resolution: "@storybook/docs-mdx@npm:4.0.0-next.1" dependencies: acorn: "npm:^8.12.1" - checksum: 10c0/6253361e4e3c6c716c4f4c8cc30c082bcdab66b35b30183f6574d94720d875e28927916be8bda0bff4987090c3e50d348ca898160b1812a90c5afa845400414e + checksum: 10c0/8779279014a0a48c00d5884d310b3ca7828a49057c7403371e4eaf0fd053d8c93a412084cbbd6e5ea65e509e27f96752e8de7dadacdfa89198158b8b10deabdc languageName: node linkType: hard