Skip to content

Commit

Permalink
Merge pull request #23520 from sookmax/prioritize-id-over-title-docs
Browse files Browse the repository at this point in the history
Autodocs: Prioritize `Meta.id` over `Meta.title` for ids of docs pages
  • Loading branch information
JReinhold committed Aug 1, 2023
2 parents 03a3590 + 9a38bdf commit bdbd335
Show file tree
Hide file tree
Showing 6 changed files with 139 additions and 9 deletions.
88 changes: 88 additions & 0 deletions code/lib/core-server/src/utils/StoryIndexGenerator.test.ts
Original file line number Diff line number Diff line change
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
Original file line number Diff line number Diff line change
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 @@ -229,7 +232,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 @@ -254,7 +263,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) => {
const title = userOrAutoTitleFromSpecifier(importPath, specifier, userTitle);
Expand All @@ -277,7 +286,16 @@ export class StoryIndexGenerator {
if (!parameters?.docsOnly) {
const tags = [...(storyTags || componentTags), 'story'];
invariant(csf.meta.title);
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,
});
}
});

Expand All @@ -292,7 +310,7 @@ export class StoryIndexGenerator {
const name = this.options.docs.defaultName;
if (!name) throw new Error('expected a defaultName property in options.docs');
invariant(csf.meta.title);
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 @@ -351,12 +369,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 | undefined;
let csfEntry: StoryIndexEntryWithMeta | 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 StoryIndexEntry;
const first = dep.entries.find((e) => e.type !== 'docs') as StoryIndexEntryWithMeta;

if (
path
Expand Down Expand Up @@ -398,7 +416,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
Original file line number Diff line number Diff line change
@@ -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 = {};
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
import * as Stories from './B.stories';

<Meta of={Stories} />

# Docs with of

hello docs
Original file line number Diff line number Diff line change
@@ -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
Original file line number Diff line number Diff line change
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

0 comments on commit bdbd335

Please sign in to comment.