Skip to content

Commit

Permalink
Merge pull request #25537 from storybookjs/shilman/add-indexer-metatags
Browse files Browse the repository at this point in the history
Indexer: Add metaTags and make autodocs inherit them
  • Loading branch information
shilman authored Jan 12, 2024
2 parents 63b9583 + 185e342 commit d9caeb8
Show file tree
Hide file tree
Showing 11 changed files with 124 additions and 23 deletions.
62 changes: 62 additions & 0 deletions code/e2e-tests/tags.spec.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,62 @@
import { test, expect } from '@playwright/test';
import { SbPage } from './util';

const storybookUrl = process.env.STORYBOOK_URL || 'http://localhost:8001';

test.describe('tags', () => {
test.beforeEach(async ({ page }) => {
await page.goto(storybookUrl);
await new SbPage(page).waitUntilLoaded();
});

test('should correctly filter dev-only, docs-only, test-only stories', async ({ page }) => {
const sbPage = new SbPage(page);

await sbPage.navigateToStory('lib/preview-api/tags', 'docs');

// Sidebar should include dev-only and exclude docs-only and test-only
const devOnlyEntry = await page.locator('#lib-preview-api-tags--dev-only').all();
expect(devOnlyEntry.length).toBe(1);

const docsOnlyEntry = await page.locator('#lib-preview-api-tags--docs-only').all();
expect(docsOnlyEntry.length).toBe(0);

const testOnlyEntry = await page.locator('#lib-preview-api-tags--test-only').all();
expect(testOnlyEntry.length).toBe(0);

// Autodocs should include docs-only and exclude dev-only and test-only
const root = sbPage.previewRoot();

const devOnlyAnchor = await root.locator('#anchor--lib-preview-api-tags--dev-only').all();
expect(devOnlyAnchor.length).toBe(0);

const docsOnlyAnchor = await root.locator('#anchor--lib-preview-api-tags--docs-only').all();
expect(docsOnlyAnchor.length).toBe(1);

const testOnlyAnchor = await root.locator('#anchor--lib-preview-api-tags--test-only').all();
expect(testOnlyAnchor.length).toBe(0);
});

test('should correctly filter out test-only autodocs pages', async ({ page }) => {
const sbPage = new SbPage(page);

await sbPage.selectToolbar('#lib-preview-api');

// Sidebar should exclude test-only stories and their docs
const componentEntry = await page.locator('#lib-preview-api-test-only-tag').all();
expect(componentEntry.length).toBe(0);

// Even though test-only autodocs not sidebar, it is still in the preview
// Even though the test-only story is filtered out of the stories, it is still the primary story (should it be?)
await sbPage.deepLinkToStory(storybookUrl, 'lib/preview-api/test-only-tag', 'docs');
await sbPage.waitUntilLoaded();
const docsButton = await sbPage.previewRoot().locator('button', { hasText: 'Button' });
await expect(docsButton).toBeVisible();

// Even though test-only story not sidebar, it is still in the preview
await sbPage.deepLinkToStory(storybookUrl, 'lib/preview-api/test-only-tag', 'default');
await sbPage.waitUntilLoaded();
const storyButton = await sbPage.previewRoot().locator('button', { hasText: 'Button' });
await expect(storyButton).toBeVisible();
});
});
3 changes: 3 additions & 0 deletions code/e2e-tests/util.ts
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,9 @@ export class SbPage {
const storyLinkId = `${titleId}--${storyId}`;
const viewMode = name === 'docs' ? 'docs' : 'story';
await this.page.goto(`${baseURL}/?path=/${viewMode}/${storyLinkId}`);

await this.page.waitForURL((url) => url.search.includes(`path=/${viewMode}/${storyLinkId}`));
await this.previewRoot();
}

/**
Expand Down
25 changes: 11 additions & 14 deletions code/lib/core-server/src/presets/common-manager.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,22 +3,19 @@ import { global } from '@storybook/global';

const STATIC_FILTER = 'static-filter';

const excludeTags = Object.entries(global.TAGS_OPTIONS ?? {}).reduce((acc, entry) => {
const [tag, option] = entry;
if ((option as any).excludeFromSidebar) {
acc[tag] = true;
}
return acc;
}, {} as Record<string, boolean>);

addons.register(STATIC_FILTER, (api) => {
// FIXME: this ensures the filter is applied after the first render
// to avoid a strange race condition in Webkit only.
const excludeTags = Object.entries(global.TAGS_OPTIONS ?? {}).reduce((acc, entry) => {
const [tag, option] = entry;
if ((option as any).excludeFromSidebar) {
acc[tag] = true;
}
return acc;
}, {} as Record<string, boolean>);

api.experimental_setFilter(STATIC_FILTER, (item) => {
const tags = item.tags || [];
// very strange behavior here. Auto-generated docs entries get
// the tags of the primary story by default, so if that story
// happens to be `docs-only`, then filtering it out of the sidebar
// ALSO filter out the sidebar entry, which is not what we want.
// Here we special case it, but there should be a better solution.
return tags.includes('docs') || tags.filter((tag) => excludeTags[tag]).length === 0;
return tags.filter((tag) => excludeTags[tag]).length === 0;
});
});
5 changes: 3 additions & 2 deletions code/lib/core-server/src/utils/StoryIndexGenerator.ts
Original file line number Diff line number Diff line change
Expand Up @@ -319,15 +319,15 @@ export class StoryIndexGenerator {
const name = this.options.docs.defaultName ?? 'Docs';
const { metaId } = indexInputs[0];
const { title } = entries[0];
const tags = indexInputs[0].tags || [];
const metaTags = indexInputs[0].metaTags || [];
const id = toId(metaId ?? title, name);
entries.unshift({
id,
title,
name,
importPath,
type: 'docs',
tags: [...tags, 'docs', ...(!hasAutodocsTag && !isStoriesMdx ? [AUTODOCS_TAG] : [])],
tags: [...metaTags, 'docs', ...(!hasAutodocsTag && !isStoriesMdx ? [AUTODOCS_TAG] : [])],
storiesImports: [],
});
}
Expand Down Expand Up @@ -438,6 +438,7 @@ export class StoryIndexGenerator {
importPath,
storiesImports: sortedDependencies.map((dep) => dep.entries[0].importPath),
type: 'docs',
// FIXME: update this to use the index entry's metaTags once we update this to run on `IndexInputs`
tags: [...(result.tags || []), csfEntry ? 'attached-mdx' : 'unattached-mdx', 'docs'],
};
return docsEntry;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -406,7 +406,6 @@ describe('docs entries from story extraction', () => {
"name": "docs",
"storiesImports": [],
"tags": [
"story-tag-from-indexer",
"docs",
"autodocs",
],
Expand Down Expand Up @@ -467,8 +466,6 @@ describe('docs entries from story extraction', () => {
"name": "docs",
"storiesImports": [],
"tags": [
"autodocs",
"story-tag-from-indexer",
"docs",
],
"title": "A",
Expand Down Expand Up @@ -578,8 +575,6 @@ describe('docs entries from story extraction', () => {
"name": "docs",
"storiesImports": [],
"tags": [
"stories-mdx",
"story-tag-from-indexer",
"docs",
],
"title": "A",
Expand Down
10 changes: 10 additions & 0 deletions code/lib/csf-tools/src/CsfFile.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -1098,6 +1098,8 @@ describe('CsfFile', () => {
- component-tag
- story-tag
- play-fn
metaTags: &ref_0
- component-tag
__id: component-id--a
- type: story
importPath: foo/bar.stories.js
Expand All @@ -1109,6 +1111,7 @@ describe('CsfFile', () => {
- component-tag
- story-tag
- play-fn
metaTags: *ref_0
__id: component-id--b
`);
});
Expand Down Expand Up @@ -1138,6 +1141,8 @@ describe('CsfFile', () => {
metaId: component-id
tags:
- component-tag
metaTags:
- component-tag
__id: custom-story-id
`);
});
Expand Down Expand Up @@ -1169,6 +1174,11 @@ describe('CsfFile', () => {
- inherit-tag-dup
- story-tag
- story-tag-dup
metaTags:
- component-tag
- component-tag-dup
- component-tag-dup
- inherit-tag-dup
__id: custom-foo-title--a
`);
});
Expand Down
1 change: 1 addition & 0 deletions code/lib/csf-tools/src/CsfFile.ts
Original file line number Diff line number Diff line change
Expand Up @@ -572,6 +572,7 @@ export class CsfFile {
title: this.meta?.title,
metaId: this.meta?.id,
tags,
metaTags: this.meta?.tags,
__id: story.id,
};
});
Expand Down
16 changes: 15 additions & 1 deletion code/lib/preview-api/template/stories/tags.stories.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,14 +5,15 @@ import { expect } from '@storybook/jest';

export default {
component: globalThis.Components.Pre,
tags: ['component-one', 'component-two'],
tags: ['component-one', 'component-two', 'autodocs'],
decorators: [
(storyFn: PartialStoryFn, context: StoryContext) => {
return storyFn({
args: { object: { tags: context.tags } },
});
},
],
parameters: { chromatic: { disable: true } },
};

export const Inheritance = {
Expand All @@ -23,4 +24,17 @@ export const Inheritance = {
tags: ['story-one', 'story-two', 'story'],
});
},
parameters: { chromatic: { disable: false } },
};

export const DocsOnly = {
tags: ['docs-only'],
};

export const TestOnly = {
tags: ['test-only'],
};

export const DevOnly = {
tags: ['dev-only'],
};
11 changes: 11 additions & 0 deletions code/lib/preview-api/template/stories/test-only-tag.stories.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
import { global as globalThis } from '@storybook/global';

export default {
component: globalThis.Components.Button,
tags: ['autodocs', 'test-only'],
parameters: { chromatic: { disable: true } },
};

export const Default = {
args: { label: 'Button' },
};
2 changes: 2 additions & 0 deletions code/lib/types/src/modules/indexer.ts
Original file line number Diff line number Diff line change
Expand Up @@ -107,6 +107,8 @@ export type BaseIndexInput = {
metaId?: MetaId;
/** Tags for filtering entries in Storybook and its tools. */
tags?: Tag[];
/** Tags from the meta for filtering entries in Storybook and its tools. */
metaTags?: Tag[];
/**
* The id of the entry, auto-generated from {@link title}/{@link metaId} and {@link exportName} if unspecified.
* If specified, the story in the CSF file _must_ have a matching id set at `parameters.__id`, to be correctly matched.
Expand Down
7 changes: 6 additions & 1 deletion code/ui/manager/src/runtime.ts
Original file line number Diff line number Diff line change
Expand Up @@ -55,4 +55,9 @@ class ReactProvider extends Provider {

const { document } = global;
const rootEl = document.getElementById('root');
renderStorybookUI(rootEl, new ReactProvider());

// We need to wait for the script tag containing the global objects
// to be run by Webkit before rendering the UI. This is fine in most browsers.
setTimeout(() => {
renderStorybookUI(rootEl, new ReactProvider());
}, 0);

0 comments on commit d9caeb8

Please sign in to comment.