/* global window */ import { STORY_RENDERED, STORY_UNCHANGED, SET_INDEX } from '@storybook/core-events'; import type { Store_ModuleExports, Path, Loadable } from '@storybook/types'; import { setGlobalRender } from '@storybook/client-api'; import global from 'global'; import { waitForRender, waitForEvents, waitForQuiescence, emitter, mockChannel, } from './PreviewWeb.mockdata'; import { start as realStart } from './start'; jest.mock('global', () => ({ // @ts-expect-error (jest is not happy with this) ...jest.requireActual('global'), history: { replaceState: jest.fn() }, document: { location: { pathname: 'pathname', search: '?id=*', }, }, FEATURES: { breakingChangesV7: true, }, DOCS_OPTIONS: { enabled: true, }, })); jest.mock('@storybook/channel-postmessage', () => ({ createChannel: () => mockChannel })); jest.mock('react-dom'); // for the auto-title test jest.mock('@storybook/store', () => { const actualStore = jest.requireActual('@storybook/store'); return { ...actualStore, userOrAutoTitle: (importPath: Path, specifier: any, userTitle?: string) => userTitle || 'auto-title', }; }); jest.mock('@storybook/preview-web', () => { const actualPreviewWeb = jest.requireActual('@storybook/preview-web'); class OverloadPreviewWeb extends actualPreviewWeb.PreviewWeb { constructor() { super(); this.view = { ...Object.fromEntries( Object.getOwnPropertyNames(this.view.constructor.prototype).map((key) => [key, jest.fn()]) ), prepareForDocs: jest.fn().mockReturnValue('docs-root'), prepareForStory: jest.fn().mockReturnValue('story-root'), }; } } return { ...actualPreviewWeb, PreviewWeb: OverloadPreviewWeb, }; }); beforeEach(() => { mockChannel.emit.mockClear(); // Preview doesn't clean itself up as it isn't designed to ever be stopped :shrug: emitter.removeAllListeners(); }); const start: typeof realStart = (...args) => { const result = realStart(...args); const configure: typeof result['configure'] = ( framework: string, loadable: Loadable, m?: NodeModule, disableBackwardCompatibility = false ) => result.configure(framework, loadable, m, disableBackwardCompatibility); return { ...result, configure, }; }; afterEach(() => { // I'm not sure why this is required (it seems just afterEach is required really) mockChannel.emit.mockClear(); }); function makeRequireContext(importMap: Record) { const req = (path: Path) => importMap[path]; req.keys = () => Object.keys(importMap); return req; } describe('start', () => { beforeEach(() => { global.DOCS_OPTIONS = { enabled: false }; }); describe('when configure is called with storiesOf only', () => { it('loads and renders the first story correctly', async () => { const renderToDOM = jest.fn(); const { configure, clientApi } = start(renderToDOM); configure('test', () => { clientApi .storiesOf('Component A', { id: 'file1' } as NodeModule) .add('Story One', jest.fn()) .add('Story Two', jest.fn()); clientApi .storiesOf('Component B', { id: 'file2' } as NodeModule) .add('Story Three', jest.fn()); }); await waitForRender(); expect(mockChannel.emit.mock.calls.find((call: [string, any]) => call[0] === SET_INDEX)[1]) .toMatchInlineSnapshot(` Object { "entries": Object { "component-a--story-one": Object { "argTypes": Object {}, "args": Object {}, "componentId": "component-a", "id": "component-a--story-one", "importPath": "file1", "initialArgs": Object {}, "name": "Story One", "parameters": Object { "__id": "component-a--story-one", "__isArgsStory": false, "fileName": "file1", "framework": "test", }, "tags": Array [ "story", ], "title": "Component A", "type": "story", }, "component-a--story-two": Object { "argTypes": Object {}, "args": Object {}, "componentId": "component-a", "id": "component-a--story-two", "importPath": "file1", "initialArgs": Object {}, "name": "Story Two", "parameters": Object { "__id": "component-a--story-two", "__isArgsStory": false, "fileName": "file1", "framework": "test", }, "tags": Array [ "story", ], "title": "Component A", "type": "story", }, "component-b--story-three": Object { "argTypes": Object {}, "args": Object {}, "componentId": "component-b", "id": "component-b--story-three", "importPath": "file2", "initialArgs": Object {}, "name": "Story Three", "parameters": Object { "__id": "component-b--story-three", "__isArgsStory": false, "fileName": "file2", "framework": "test", }, "tags": Array [ "story", ], "title": "Component B", "type": "story", }, }, "v": 4, } `); await waitForRender(); expect(mockChannel.emit).toHaveBeenCalledWith(STORY_RENDERED, 'component-a--story-one'); expect(renderToDOM).toHaveBeenCalledWith( expect.objectContaining({ id: 'component-a--story-one', }), 'story-root' ); }); it('deals with stories with "default" name', async () => { const renderToDOM = jest.fn(); const { configure, clientApi } = start(renderToDOM); configure('test', () => { clientApi.storiesOf('Component A', { id: 'file1' } as NodeModule).add('default', jest.fn()); }); await waitForRender(); expect(mockChannel.emit).toHaveBeenCalledWith(STORY_RENDERED, 'component-a--default'); }); it('deals with stories with camel-cased names', async () => { const renderToDOM = jest.fn(); const { configure, clientApi } = start(renderToDOM); configure('test', () => { clientApi .storiesOf('Component A', { id: 'file1' } as NodeModule) .add('storyOne', jest.fn()); }); await waitForRender(); expect(mockChannel.emit).toHaveBeenCalledWith(STORY_RENDERED, 'component-a--storyone'); }); it('deals with stories with spaces in the name', async () => { const renderToDOM = jest.fn(); const { configure, clientApi } = start(renderToDOM); configure('test', () => { clientApi .storiesOf('Component A', { id: 'file1' } as NodeModule) .add('Story One', jest.fn()); }); await waitForRender(); expect(mockChannel.emit).toHaveBeenCalledWith(STORY_RENDERED, 'component-a--story-one'); }); // https://github.com/storybookjs/storybook/issues/16303 it('deals with stories with numeric names', async () => { const renderToDOM = jest.fn(); const { configure, clientApi } = start(renderToDOM); configure('test', () => { clientApi.storiesOf('Component A', { id: 'file1' } as NodeModule).add('story0', jest.fn()); }); await waitForRender(); expect(mockChannel.emit).toHaveBeenCalledWith(STORY_RENDERED, 'component-a--story0'); }); it('deals with storiesOf from the same file twice', async () => { const renderToDOM = jest.fn(); const { configure, clientApi } = start(renderToDOM); configure('test', () => { clientApi.storiesOf('Component A', { id: 'file1' } as NodeModule).add('default', jest.fn()); clientApi.storiesOf('Component B', { id: 'file1' } as NodeModule).add('default', jest.fn()); clientApi.storiesOf('Component C', { id: 'file1' } as NodeModule).add('default', jest.fn()); }); await waitForRender(); expect(mockChannel.emit).toHaveBeenCalledWith(STORY_RENDERED, 'component-a--default'); const storiesOfData = mockChannel.emit.mock.calls.find( (call: any[]) => call[0] === SET_INDEX )[1]; expect(Object.values(storiesOfData.entries).map((s: any) => s.parameters.fileName)).toEqual([ 'file1', 'file1-2', 'file1-3', ]); }); it('allows setting compomnent/args/argTypes via a parameter', async () => { const renderToDOM = jest.fn(({ storyFn }) => storyFn()); const { configure, clientApi } = start(renderToDOM); const component = {}; configure('test', () => { clientApi .storiesOf('Component A', { id: 'file1' } as NodeModule) .addParameters({ component, args: { a: 'a' }, argTypes: { a: { type: 'string' } }, }) .add('default', jest.fn(), { args: { b: 'b' }, argTypes: { b: { type: 'string' } }, }); }); await waitForRender(); expect(renderToDOM).toHaveBeenCalledWith( expect.objectContaining({ storyContext: expect.objectContaining({ component, args: { a: 'a', b: 'b' }, argTypes: { a: { name: 'a', type: { name: 'string' } }, b: { name: 'b', type: { name: 'string' } }, }, }), }), 'story-root' ); expect((window as any).IS_STORYBOOK).toBe(true); }); it('supports forceRerender()', async () => { const renderToDOM = jest.fn(({ storyFn }) => storyFn()); const { configure, clientApi, forceReRender } = start(renderToDOM); configure('test', () => { clientApi.storiesOf('Component A', { id: 'file1' } as NodeModule).add('default', jest.fn()); }); await waitForRender(); expect(mockChannel.emit).toHaveBeenCalledWith(STORY_RENDERED, 'component-a--default'); mockChannel.emit.mockClear(); forceReRender(); await waitForRender(); expect(mockChannel.emit).toHaveBeenCalledWith(STORY_RENDERED, 'component-a--default'); }); it('supports HMR when a story file changes', async () => { const renderToDOM = jest.fn(({ storyFn }) => storyFn()); const { configure, clientApi } = start(renderToDOM); let disposeCallback: () => void = () => {}; const module = { id: 'file1', hot: { accept: jest.fn(), dispose(cb: () => void) { disposeCallback = cb; }, }, }; const firstImplementation = jest.fn(); configure('test', () => { clientApi.storiesOf('Component A', module as any).add('default', firstImplementation); }); await waitForRender(); expect(mockChannel.emit).toHaveBeenCalledWith(STORY_RENDERED, 'component-a--default'); expect(firstImplementation).toHaveBeenCalled(); expect(module.hot.accept).toHaveBeenCalled(); expect(disposeCallback).toBeDefined(); mockChannel.emit.mockClear(); disposeCallback(); const secondImplementation = jest.fn(); clientApi.storiesOf('Component A', module as any).add('default', secondImplementation); await waitForRender(); expect(mockChannel.emit).toHaveBeenCalledWith(STORY_RENDERED, 'component-a--default'); expect(secondImplementation).toHaveBeenCalled(); }); it('re-emits SET_INDEX when a story is added', async () => { const renderToDOM = jest.fn(({ storyFn }) => storyFn()); const { configure, clientApi, forceReRender } = start(renderToDOM); let disposeCallback: () => void = () => {}; const module = { id: 'file1', hot: { accept: jest.fn(), dispose(cb: () => void) { disposeCallback = cb; }, }, }; configure('test', () => { clientApi.storiesOf('Component A', module as any).add('default', jest.fn()); }); await waitForRender(); mockChannel.emit.mockClear(); disposeCallback(); clientApi .storiesOf('Component A', module as any) .add('default', jest.fn()) .add('new', jest.fn()); await waitForEvents([SET_INDEX]); expect(mockChannel.emit.mock.calls.find((call: [string, any]) => call[0] === SET_INDEX)[1]) .toMatchInlineSnapshot(` Object { "entries": Object { "component-a--default": Object { "argTypes": Object {}, "args": Object {}, "componentId": "component-a", "id": "component-a--default", "importPath": "file1", "initialArgs": Object {}, "name": "default", "parameters": Object { "__id": "component-a--default", "__isArgsStory": false, "fileName": "file1", "framework": "test", }, "tags": Array [ "story", ], "title": "Component A", "type": "story", }, "component-a--new": Object { "argTypes": Object {}, "args": Object {}, "componentId": "component-a", "id": "component-a--new", "importPath": "file1", "initialArgs": Object {}, "name": "new", "parameters": Object { "__id": "component-a--new", "__isArgsStory": false, "fileName": "file1", "framework": "test", }, "tags": Array [ "story", ], "title": "Component A", "type": "story", }, }, "v": 4, } `); }); it('re-emits SET_INDEX when a story file is removed', async () => { const renderToDOM = jest.fn(({ storyFn }) => storyFn()); const { configure, clientApi } = start(renderToDOM); let disposeCallback: () => void = () => {}; const moduleB = { id: 'file2', hot: { accept: jest.fn(), dispose(cb: () => void) { disposeCallback = cb; }, }, }; configure('test', () => { clientApi.storiesOf('Component A', { id: 'file1' } as any).add('default', jest.fn()); clientApi.storiesOf('Component B', moduleB as any).add('default', jest.fn()); }); await waitForEvents([SET_INDEX]); expect(mockChannel.emit.mock.calls.find((call: [string, any]) => call[0] === SET_INDEX)[1]) .toMatchInlineSnapshot(` Object { "entries": Object { "component-a--default": Object { "argTypes": Object {}, "args": Object {}, "componentId": "component-a", "id": "component-a--default", "importPath": "file1", "initialArgs": Object {}, "name": "default", "parameters": Object { "__id": "component-a--default", "__isArgsStory": false, "fileName": "file1", "framework": "test", }, "tags": Array [ "story", ], "title": "Component A", "type": "story", }, "component-b--default": Object { "argTypes": Object {}, "args": Object {}, "componentId": "component-b", "id": "component-b--default", "importPath": "file2", "initialArgs": Object {}, "name": "default", "parameters": Object { "__id": "component-b--default", "__isArgsStory": false, "fileName": "file2", "framework": "test", }, "tags": Array [ "story", ], "title": "Component B", "type": "story", }, }, "v": 4, } `); mockChannel.emit.mockClear(); disposeCallback(); await waitForEvents([SET_INDEX]); expect(mockChannel.emit.mock.calls.find((call: [string, any]) => call[0] === SET_INDEX)[1]) .toMatchInlineSnapshot(` Object { "entries": Object { "component-a--default": Object { "argTypes": Object {}, "args": Object {}, "componentId": "component-a", "id": "component-a--default", "importPath": "file1", "initialArgs": Object {}, "name": "default", "parameters": Object { "__id": "component-a--default", "__isArgsStory": false, "fileName": "file1", "framework": "test", }, "tags": Array [ "story", ], "title": "Component A", "type": "story", }, }, "v": 4, } `); }); }); const componentCExports = { default: { title: 'Component C', tags: ['component-tag', 'docsPage'], }, StoryOne: { render: jest.fn(), tags: ['story-tag'], }, StoryTwo: jest.fn(), }; describe('when configure is called with CSF only', () => { it('loads and renders the first story correctly', async () => { const renderToDOM = jest.fn(); const { configure } = start(renderToDOM); configure('test', () => [componentCExports]); await waitForRender(); expect(mockChannel.emit.mock.calls.find((call: [string, any]) => call[0] === SET_INDEX)[1]) .toMatchInlineSnapshot(` Object { "entries": Object { "component-c--story-one": Object { "argTypes": Object {}, "args": Object {}, "id": "component-c--story-one", "importPath": "exports-map-0", "initialArgs": Object {}, "name": "Story One", "parameters": Object { "__isArgsStory": false, "fileName": "exports-map-0", "framework": "test", }, "tags": Array [ "story-tag", "story", ], "title": "Component C", "type": "story", }, "component-c--story-two": Object { "argTypes": Object {}, "args": Object {}, "id": "component-c--story-two", "importPath": "exports-map-0", "initialArgs": Object {}, "name": "Story Two", "parameters": Object { "__isArgsStory": false, "fileName": "exports-map-0", "framework": "test", }, "tags": Array [ "component-tag", "docsPage", "story", ], "title": "Component C", "type": "story", }, }, "v": 4, } `); await waitForRender(); expect(mockChannel.emit).toHaveBeenCalledWith(STORY_RENDERED, 'component-c--story-one'); expect(renderToDOM).toHaveBeenCalledWith( expect.objectContaining({ id: 'component-c--story-one', }), 'story-root' ); }); it('supports HMR when a story file changes', async () => { const renderToDOM = jest.fn(({ storyFn }) => storyFn()); let disposeCallback: (data: object) => void = () => {}; const module = { id: 'file1', hot: { data: {}, accept: jest.fn(), dispose(cb: () => void) { disposeCallback = cb; }, }, }; const { configure } = start(renderToDOM); configure('test', () => [componentCExports], module as any); await waitForRender(); expect(mockChannel.emit).toHaveBeenCalledWith(STORY_RENDERED, 'component-c--story-one'); expect(componentCExports.StoryOne.render).toHaveBeenCalled(); expect(module.hot.accept).toHaveBeenCalled(); expect(disposeCallback).toBeDefined(); mockChannel.emit.mockClear(); disposeCallback(module.hot.data); const secondImplementation = jest.fn(); configure( 'test', () => [{ ...componentCExports, StoryOne: secondImplementation }], module as any ); await waitForRender(); expect(mockChannel.emit).toHaveBeenCalledWith(STORY_RENDERED, 'component-c--story-one'); expect(secondImplementation).toHaveBeenCalled(); }); it('re-emits SET_INDEX when a story is added', async () => { const renderToDOM = jest.fn(({ storyFn }) => storyFn()); let disposeCallback: (data: object) => void = () => {}; const module = { id: 'file1', hot: { data: {}, accept: jest.fn(), dispose(cb: () => void) { disposeCallback = cb; }, }, }; const { configure } = start(renderToDOM); configure('test', () => [componentCExports], module as any); await waitForRender(); mockChannel.emit.mockClear(); disposeCallback(module.hot.data); configure('test', () => [{ ...componentCExports, StoryThree: jest.fn() }], module as any); await waitForEvents([SET_INDEX]); expect(mockChannel.emit.mock.calls.find((call: [string, any]) => call[0] === SET_INDEX)[1]) .toMatchInlineSnapshot(` Object { "entries": Object { "component-c--story-one": Object { "argTypes": Object {}, "args": Object {}, "id": "component-c--story-one", "importPath": "exports-map-0", "initialArgs": Object {}, "name": "Story One", "parameters": Object { "__isArgsStory": false, "fileName": "exports-map-0", "framework": "test", }, "tags": Array [ "story-tag", "story", ], "title": "Component C", "type": "story", }, "component-c--story-three": Object { "argTypes": Object {}, "args": Object {}, "id": "component-c--story-three", "importPath": "exports-map-0", "initialArgs": Object {}, "name": "Story Three", "parameters": Object { "__isArgsStory": false, "fileName": "exports-map-0", "framework": "test", }, "tags": Array [ "component-tag", "docsPage", "story", ], "title": "Component C", "type": "story", }, "component-c--story-two": Object { "argTypes": Object {}, "args": Object {}, "id": "component-c--story-two", "importPath": "exports-map-0", "initialArgs": Object {}, "name": "Story Two", "parameters": Object { "__isArgsStory": false, "fileName": "exports-map-0", "framework": "test", }, "tags": Array [ "component-tag", "docsPage", "story", ], "title": "Component C", "type": "story", }, }, "v": 4, } `); }); it('re-emits SET_INDEX when a story file is removed', async () => { const renderToDOM = jest.fn(({ storyFn }) => storyFn()); let disposeCallback: (data: object) => void = () => {}; const module = { id: 'file1', hot: { data: {}, accept: jest.fn(), dispose(cb: () => void) { disposeCallback = cb; }, }, }; const { configure } = start(renderToDOM); configure( 'test', () => [componentCExports, { default: { title: 'Component D' }, StoryFour: jest.fn() }], module as any ); await waitForEvents([SET_INDEX]); expect(mockChannel.emit.mock.calls.find((call: [string, any]) => call[0] === SET_INDEX)[1]) .toMatchInlineSnapshot(` Object { "entries": Object { "component-c--story-one": Object { "argTypes": Object {}, "args": Object {}, "id": "component-c--story-one", "importPath": "exports-map-0", "initialArgs": Object {}, "name": "Story One", "parameters": Object { "__isArgsStory": false, "fileName": "exports-map-0", "framework": "test", }, "tags": Array [ "story-tag", "story", ], "title": "Component C", "type": "story", }, "component-c--story-two": Object { "argTypes": Object {}, "args": Object {}, "id": "component-c--story-two", "importPath": "exports-map-0", "initialArgs": Object {}, "name": "Story Two", "parameters": Object { "__isArgsStory": false, "fileName": "exports-map-0", "framework": "test", }, "tags": Array [ "component-tag", "docsPage", "story", ], "title": "Component C", "type": "story", }, "component-d--story-four": Object { "argTypes": Object {}, "args": Object {}, "id": "component-d--story-four", "importPath": "exports-map-1", "initialArgs": Object {}, "name": "Story Four", "parameters": Object { "__isArgsStory": false, "fileName": "exports-map-1", "framework": "test", }, "tags": Array [ "story", ], "title": "Component D", "type": "story", }, }, "v": 4, } `); await waitForRender(); mockChannel.emit.mockClear(); disposeCallback(module.hot.data); configure('test', () => [componentCExports], module as any); await waitForEvents([SET_INDEX]); expect(mockChannel.emit.mock.calls.find((call: [string, any]) => call[0] === SET_INDEX)[1]) .toMatchInlineSnapshot(` Object { "entries": Object { "component-c--story-one": Object { "argTypes": Object {}, "args": Object {}, "id": "component-c--story-one", "importPath": "exports-map-0", "initialArgs": Object {}, "name": "Story One", "parameters": Object { "__isArgsStory": false, "fileName": "exports-map-0", "framework": "test", }, "tags": Array [ "story-tag", "story", ], "title": "Component C", "type": "story", }, "component-c--story-two": Object { "argTypes": Object {}, "args": Object {}, "id": "component-c--story-two", "importPath": "exports-map-0", "initialArgs": Object {}, "name": "Story Two", "parameters": Object { "__isArgsStory": false, "fileName": "exports-map-0", "framework": "test", }, "tags": Array [ "component-tag", "docsPage", "story", ], "title": "Component C", "type": "story", }, }, "v": 4, } `); await waitForEvents([STORY_UNCHANGED]); }); it('allows you to override the render function in project annotations', async () => { const renderToDOM = jest.fn(({ storyFn }) => storyFn()); const frameworkRender = jest.fn(); const { configure } = start(renderToDOM, { render: frameworkRender }); const projectRender = jest.fn(); setGlobalRender(projectRender); configure('test', () => { return [ { default: { title: 'Component A', component: jest.fn(), }, StoryOne: {}, }, ]; }); await waitForRender(); expect(mockChannel.emit).toHaveBeenCalledWith(STORY_RENDERED, 'component-a--story-one'); expect(frameworkRender).not.toHaveBeenCalled(); expect(projectRender).toHaveBeenCalled(); }); describe('docs', () => { beforeEach(() => { global.DOCS_OPTIONS = { enabled: true }; }); // NOTE: MDX files are only ever passed as CSF it('sends over docs only stories as entries', async () => { const renderToDOM = jest.fn(); const { configure } = start(renderToDOM); configure( 'test', makeRequireContext({ './Introduction.stories.mdx': { default: { title: 'Introduction' }, _Page: { name: 'Page', parameters: { docsOnly: true } }, }, }) ); await waitForEvents([SET_INDEX]); expect(mockChannel.emit.mock.calls.find((call: [string, any]) => call[0] === SET_INDEX)[1]) .toMatchInlineSnapshot(` Object { "entries": Object { "introduction": Object { "id": "introduction", "importPath": "./Introduction.stories.mdx", "name": undefined, "standalone": false, "storiesImports": Array [], "tags": Array [ "docs", ], "title": "Introduction", "type": "docs", }, }, "v": 4, } `); // Wait a second to let the docs "render" finish (and maybe throw) await waitForQuiescence(); }); }); }); describe('when configure is called with a combination', () => { it('loads and renders the first story correctly', async () => { const renderToDOM = jest.fn(); const { configure, clientApi } = start(renderToDOM); configure('test', () => { clientApi .storiesOf('Component A', { id: 'file1' } as NodeModule) .add('Story One', jest.fn()) .add('Story Two', jest.fn()); clientApi .storiesOf('Component B', { id: 'file2' } as NodeModule) .add('Story Three', jest.fn()); return [componentCExports]; }); await waitForRender(); expect(mockChannel.emit.mock.calls.find((call: [string, any]) => call[0] === SET_INDEX)[1]) .toMatchInlineSnapshot(` Object { "entries": Object { "component-a--story-one": Object { "argTypes": Object {}, "args": Object {}, "componentId": "component-a", "id": "component-a--story-one", "importPath": "file1", "initialArgs": Object {}, "name": "Story One", "parameters": Object { "__id": "component-a--story-one", "__isArgsStory": false, "fileName": "file1", "framework": "test", }, "tags": Array [ "story", ], "title": "Component A", "type": "story", }, "component-a--story-two": Object { "argTypes": Object {}, "args": Object {}, "componentId": "component-a", "id": "component-a--story-two", "importPath": "file1", "initialArgs": Object {}, "name": "Story Two", "parameters": Object { "__id": "component-a--story-two", "__isArgsStory": false, "fileName": "file1", "framework": "test", }, "tags": Array [ "story", ], "title": "Component A", "type": "story", }, "component-b--story-three": Object { "argTypes": Object {}, "args": Object {}, "componentId": "component-b", "id": "component-b--story-three", "importPath": "file2", "initialArgs": Object {}, "name": "Story Three", "parameters": Object { "__id": "component-b--story-three", "__isArgsStory": false, "fileName": "file2", "framework": "test", }, "tags": Array [ "story", ], "title": "Component B", "type": "story", }, "component-c--story-one": Object { "argTypes": Object {}, "args": Object {}, "id": "component-c--story-one", "importPath": "exports-map-0", "initialArgs": Object {}, "name": "Story One", "parameters": Object { "__isArgsStory": false, "fileName": "exports-map-0", "framework": "test", }, "tags": Array [ "story-tag", "story", ], "title": "Component C", "type": "story", }, "component-c--story-two": Object { "argTypes": Object {}, "args": Object {}, "id": "component-c--story-two", "importPath": "exports-map-0", "initialArgs": Object {}, "name": "Story Two", "parameters": Object { "__isArgsStory": false, "fileName": "exports-map-0", "framework": "test", }, "tags": Array [ "component-tag", "docsPage", "story", ], "title": "Component C", "type": "story", }, }, "v": 4, } `); await waitForRender(); expect(mockChannel.emit).toHaveBeenCalledWith(STORY_RENDERED, 'component-a--story-one'); expect(renderToDOM).toHaveBeenCalledWith( expect.objectContaining({ id: 'component-a--story-one', }), 'story-root' ); }); describe('docsPage', () => { beforeEach(() => { global.DOCS_OPTIONS = { enabled: true, docsPage: true, defaultName: 'Docs' }; }); it('adds stories for each component with docsPage tag', async () => { const renderToDOM = jest.fn(); const { configure, clientApi } = start(renderToDOM); configure('test', () => { clientApi .storiesOf('Component A', { id: 'file1' } as NodeModule) .add('Story One', jest.fn()) .add('Story Two', jest.fn()); clientApi .storiesOf('Component B', { id: 'file2' } as NodeModule) .addParameters({ tags: ['docsPage'] }) .add('Story Three', jest.fn()); return [componentCExports]; }); await waitForRender(); expect(mockChannel.emit.mock.calls.find((call: [string, any]) => call[0] === SET_INDEX)[1]) .toMatchInlineSnapshot(` Object { "entries": Object { "component-a--story-one": Object { "argTypes": Object {}, "args": Object {}, "componentId": "component-a", "id": "component-a--story-one", "importPath": "file1", "initialArgs": Object {}, "name": "Story One", "parameters": Object { "__id": "component-a--story-one", "__isArgsStory": false, "fileName": "file1", "framework": "test", }, "tags": Array [ "story", ], "title": "Component A", "type": "story", }, "component-a--story-two": Object { "argTypes": Object {}, "args": Object {}, "componentId": "component-a", "id": "component-a--story-two", "importPath": "file1", "initialArgs": Object {}, "name": "Story Two", "parameters": Object { "__id": "component-a--story-two", "__isArgsStory": false, "fileName": "file1", "framework": "test", }, "tags": Array [ "story", ], "title": "Component A", "type": "story", }, "component-b--docs": Object { "componentId": "component-b", "id": "component-b--docs", "importPath": "file2", "name": "Docs", "standalone": false, "storiesImports": Array [], "tags": Array [ "docsPage", "docs", ], "title": "Component B", "type": "docs", }, "component-b--story-three": Object { "argTypes": Object {}, "args": Object {}, "componentId": "component-b", "id": "component-b--story-three", "importPath": "file2", "initialArgs": Object {}, "name": "Story Three", "parameters": Object { "__id": "component-b--story-three", "__isArgsStory": false, "fileName": "file2", "framework": "test", }, "tags": Array [ "docsPage", "story", ], "title": "Component B", "type": "story", }, "component-c--docs": Object { "id": "component-c--docs", "importPath": "exports-map-0", "name": "Docs", "standalone": false, "storiesImports": Array [], "tags": Array [ "component-tag", "docsPage", "docs", ], "title": "Component C", "type": "docs", }, "component-c--story-one": Object { "argTypes": Object {}, "args": Object {}, "id": "component-c--story-one", "importPath": "exports-map-0", "initialArgs": Object {}, "name": "Story One", "parameters": Object { "__isArgsStory": false, "fileName": "exports-map-0", "framework": "test", }, "tags": Array [ "story-tag", "story", ], "title": "Component C", "type": "story", }, "component-c--story-two": Object { "argTypes": Object {}, "args": Object {}, "id": "component-c--story-two", "importPath": "exports-map-0", "initialArgs": Object {}, "name": "Story Two", "parameters": Object { "__isArgsStory": false, "fileName": "exports-map-0", "framework": "test", }, "tags": Array [ "component-tag", "docsPage", "story", ], "title": "Component C", "type": "story", }, }, "v": 4, } `); }); }); }); describe('auto-title', () => { const componentDExports = { default: { component: 'Component D', }, StoryOne: jest.fn(), }; it('loads and renders the first story correctly', async () => { const renderToDOM = jest.fn(); const { configure } = start(renderToDOM); configure('test', () => [componentDExports]); await waitForEvents([SET_INDEX]); expect(mockChannel.emit.mock.calls.find((call: [string, any]) => call[0] === SET_INDEX)[1]) .toMatchInlineSnapshot(` Object { "entries": Object { "auto-title--story-one": Object { "argTypes": Object {}, "args": Object {}, "id": "auto-title--story-one", "importPath": "exports-map-0", "initialArgs": Object {}, "name": "Story One", "parameters": Object { "__isArgsStory": false, "fileName": "exports-map-0", "framework": "test", }, "tags": Array [ "story", ], "title": "auto-title", "type": "story", }, }, "v": 4, } `); await waitForRender(); }); }); });