Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Autodocs: Fix docs pages ignoring meta.id when calculating their ID #23520

Merged
merged 10 commits into from
Aug 1, 2023
88 changes: 88 additions & 0 deletions code/lib/core-server/src/utils/StoryIndexGenerator.test.ts
Expand Up @@ -748,6 +748,47 @@ describe('StoryIndexGenerator', () => {
}
`);
});

it('prioritizes using the component id over meta.title for generating its id, if provided. (autodocs)', async () => {
const csfSpecifier: NormalizedStoriesSpecifier = normalizeStoriesEntry(
'./docs-id-generation/A.stories.jsx',
options
);

const generator = new StoryIndexGenerator([csfSpecifier], autodocsOptions);
await generator.initialize();

expect(await generator.getIndex()).toMatchInlineSnapshot(`
Object {
"entries": Object {
"my-component-a--docs": Object {
"id": "my-component-a--docs",
"importPath": "./docs-id-generation/A.stories.jsx",
"name": "docs",
"storiesImports": Array [],
"tags": Array [
"autodocs",
"docs",
],
"title": "A",
"type": "docs",
},
"my-component-a--story-one": Object {
"id": "my-component-a--story-one",
"importPath": "./docs-id-generation/A.stories.jsx",
"name": "Story One",
"tags": Array [
"autodocs",
"story",
],
"title": "A",
"type": "story",
},
},
"v": 4,
}
`);
});
});

describe('docs specifier', () => {
Expand Down Expand Up @@ -1016,6 +1057,53 @@ describe('StoryIndexGenerator', () => {
}
`);
});

it('prioritizes using the component id over meta.title for generating its id, if provided. (mdx docs)', async () => {
const csfSpecifier: NormalizedStoriesSpecifier = normalizeStoriesEntry(
'./docs-id-generation/B.stories.jsx',
options
);

const docsSpecifier: NormalizedStoriesSpecifier = normalizeStoriesEntry(
'./docs-id-generation/B.docs.mdx',
options
);

const generator = new StoryIndexGenerator([csfSpecifier, docsSpecifier], options);
await generator.initialize();

expect(await generator.getIndex()).toMatchInlineSnapshot(`
Object {
"entries": Object {
"my-component-b--docs": Object {
"id": "my-component-b--docs",
"importPath": "./docs-id-generation/B.docs.mdx",
"name": "docs",
"storiesImports": Array [
"./docs-id-generation/B.stories.jsx",
],
"tags": Array [
"attached-mdx",
"docs",
],
"title": "B",
"type": "docs",
},
"my-component-b--story-one": Object {
"id": "my-component-b--story-one",
"importPath": "./docs-id-generation/B.stories.jsx",
"name": "Story One",
"tags": Array [
"story",
],
"title": "B",
"type": "story",
},
},
"v": 4,
}
`);
});
});

describe('errors', () => {
Expand Down
34 changes: 26 additions & 8 deletions code/lib/core-server/src/utils/StoryIndexGenerator.ts
Expand Up @@ -19,6 +19,7 @@ import type {
V3CompatIndexEntry,
StoryId,
StoryName,
IndexedCSFFile,
} from '@storybook/types';
import { userOrAutoTitleFromSpecifier, sortStoriesV7 } from '@storybook/preview-api';
import { commonGlobOptions, normalizeStoryPath } from '@storybook/core-common';
Expand All @@ -30,11 +31,13 @@ import dedent from 'ts-dedent';
import { autoName } from './autoName';
import { IndexingError, MultipleIndexingError } from './IndexingError';

// Extended type to keep track of the csf meta so we know the component id when referencing docs in `extractDocs`
type StoryIndexEntryWithMeta = StoryIndexEntry & { meta?: IndexedCSFFile['meta'] };
/** 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: (StoryIndexEntry | DocsIndexEntry)[];
entries: (StoryIndexEntryWithMeta | DocsIndexEntry)[];
dependents: Path[];
type: 'stories';
};
Expand Down Expand Up @@ -217,7 +220,13 @@ export class StoryIndexGenerator {
if (!entry) return [];
if (entry.type === 'docs') return [entry];
if (entry.type === 'error') return [entry];
return entry.entries;

return entry.entries.map((item) => {
if (item.type === 'docs') return item;
// Drop the meta as it isn't part of the index, we just used it for record keeping in `extractDocs`
const { meta, ...existing } = item;
return existing;
});
});
});
}
Expand All @@ -242,7 +251,7 @@ export class StoryIndexGenerator {

async extractStories(specifier: NormalizedStoriesSpecifier, absolutePath: Path) {
const relativePath = path.relative(this.options.workingDir, absolutePath);
const entries = [] as IndexEntry[];
const entries = [] as (StoryIndexEntryWithMeta | DocsIndexEntry)[];
const importPath = slash(normalizeStoryPath(relativePath));
const makeTitle = (userTitle?: string) => {
return userOrAutoTitleFromSpecifier(importPath, specifier, userTitle);
Expand All @@ -260,7 +269,16 @@ export class StoryIndexGenerator {
csf.stories.forEach(({ id, name, tags: storyTags, parameters }) => {
if (!parameters?.docsOnly) {
const tags = [...(storyTags || componentTags), 'story'];
entries.push({ id, title: csf.meta.title, name, importPath, tags, type: 'story' });
entries.push({
id,
title: csf.meta.title,
name,
importPath,
tags,
type: 'story',
// We need to keep track of the csf meta so we know the component id when referencing docs below in `extractDocs`
meta: csf.meta,
sookmax marked this conversation as resolved.
Show resolved Hide resolved
});
}
});

Expand All @@ -273,7 +291,7 @@ export class StoryIndexGenerator {
// b) we have docs page enabled for this file
if (componentTags.includes(STORIES_MDX_TAG) || autodocsOptedIn) {
const name = this.options.docs.defaultName;
const id = toId(csf.meta.title, name);
const id = toId(csf.meta.id || csf.meta.title, name);
entries.unshift({
id,
title: csf.meta.title,
Expand Down Expand Up @@ -332,12 +350,12 @@ export class StoryIndexGenerator {

// Also, if `result.of` is set, it means that we're using the `<Meta of={XStories} />` syntax,
// so find the `title` defined the file that `meta` points to.
let csfEntry: StoryIndexEntry;
let csfEntry: StoryIndexEntryWithMeta;
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 StoryIndexEntry;
const first = dep.entries.find((e) => e.type !== 'docs') as StoryIndexEntryWithMeta;

if (
path
Expand Down Expand Up @@ -373,7 +391,7 @@ export class StoryIndexGenerator {
const name =
result.name ||
(csfEntry ? autoName(importPath, csfEntry.importPath, defaultName) : defaultName);
const id = toId(title, name);
const id = toId(csfEntry?.meta?.id || title, name);

const docsEntry: DocsCacheEntry = {
id,
Expand Down
@@ -0,0 +1,9 @@
// Stories for a component with meta.id
const component = {};
export default {
id: 'my-component-A',
component,
tags: ['autodocs'],
};

export const StoryOne = {};
@@ -0,0 +1,7 @@
import * as Stories from './B.stories';

<Meta of={Stories} />

# Docs with of

hello docs
@@ -0,0 +1,8 @@
// Stories for a component with meta.id
const component = {};
export default {
id: 'my-component-B',
component,
};

export const StoryOne = {};
2 changes: 1 addition & 1 deletion code/lib/types/src/modules/storyIndex.ts
Expand Up @@ -38,7 +38,7 @@ export interface IndexedStory {
parameters?: Parameters;
}
export interface IndexedCSFFile {
meta: { title?: string; tags?: Tag[] };
meta: { id?: string; title?: string; tags?: Tag[] };
stories: IndexedStory[];
}

Expand Down