Best JavaScript code snippet using storybook-root
PreviewWeb.test.ts
Source:PreviewWeb.test.ts
1/// <reference types="@types/jest" />;2import { jest, jest as mockJest, it, describe, beforeEach, afterEach, expect } from '@jest/globals';3import global from 'global';4import merge from 'lodash/merge';5import {6 CONFIG_ERROR,7 CURRENT_STORY_WAS_SET,8 DOCS_RENDERED,9 FORCE_REMOUNT,10 FORCE_RE_RENDER,11 GLOBALS_UPDATED,12 IGNORED_EXCEPTION,13 PREVIEW_KEYDOWN,14 RESET_STORY_ARGS,15 SET_CURRENT_STORY,16 SET_GLOBALS,17 STORY_ARGS_UPDATED,18 STORY_CHANGED,19 STORY_ERRORED,20 STORY_MISSING,21 STORY_PREPARED,22 STORY_RENDERED,23 STORY_SPECIFIED,24 STORY_THREW_EXCEPTION,25 PLAY_FUNCTION_THREW_EXCEPTION,26 STORY_UNCHANGED,27 UPDATE_GLOBALS,28 UPDATE_STORY_ARGS,29} from '@storybook/core-events';30import { logger } from '@storybook/client-logger';31import { addons, mockChannel as createMockChannel } from '@storybook/addons';32import type { AnyFramework } from '@storybook/csf';33import type { ModuleImportFn, WebProjectAnnotations } from '@storybook/store';34import { mocked } from 'ts-jest/utils';35import { PreviewWeb } from './PreviewWeb';36import {37 componentOneExports,38 componentTwoExports,39 importFn,40 projectAnnotations,41 getProjectAnnotations,42 storyIndex,43 emitter,44 mockChannel,45 waitForEvents,46 waitForRender,47 waitForQuiescence,48 waitForRenderPhase,49 docsRenderer,50 standaloneDocsExports,51 teardownRenderToDOM,52} from './PreviewWeb.mockdata';53import { WebView } from './WebView';54const { history, document } = global;55const mockStoryIndex = jest.fn(() => storyIndex);56let mockFetchResult;57jest.mock('global', () => ({58 ...(mockJest.requireActual('global') as any),59 history: { replaceState: mockJest.fn() },60 document: {61 location: {62 pathname: 'pathname',63 search: '?id=*',64 },65 },66 window: {67 location: {68 reload: mockJest.fn(),69 },70 },71 FEATURES: {72 storyStoreV7: true,73 breakingChangesV7: true,74 // xxx75 },76 fetch: async () => mockFetchResult,77}));78jest.mock('@storybook/client-logger');79jest.mock('react-dom');80jest.mock('./WebView');81const serializeError = (error: Error) => {82 const { name = 'Error', message = String(error), stack } = error;83 return { name, message, stack };84};85const createGate = (): [Promise<any | undefined>, (_?: any) => void] => {86 let openGate = (_?: any) => {};87 const gate = new Promise<any | undefined>((resolve) => {88 openGate = resolve;89 });90 return [gate, openGate];91};92// SET_CURRENT_STORY does some stuff in promises, then waits for93// a timer, so we need to first setImmediate (to get past the resolution), then run the timers94// Probably jest modern timers do this but they aren't working for some bizzarre reason.95async function waitForSetCurrentStory() {96 await new Promise((r) => setImmediate(r));97 jest.runAllTimers();98}99async function createAndRenderPreview({100 importFn: inputImportFn = importFn,101 getProjectAnnotations: inputGetProjectAnnotations = getProjectAnnotations,102}: {103 importFn?: ModuleImportFn;104 getProjectAnnotations?: () => WebProjectAnnotations<AnyFramework>;105} = {}) {106 const preview = new PreviewWeb();107 await preview.initialize({108 importFn: inputImportFn,109 getProjectAnnotations: inputGetProjectAnnotations,110 });111 await waitForRender();112 return preview;113}114beforeEach(() => {115 document.location.search = '';116 mockChannel.emit.mockClear();117 emitter.removeAllListeners();118 componentOneExports.default.loaders[0].mockReset().mockImplementation(async () => ({ l: 7 }));119 componentOneExports.a.play.mockReset();120 teardownRenderToDOM.mockReset();121 projectAnnotations.renderToDOM.mockReset().mockReturnValue(teardownRenderToDOM);122 projectAnnotations.render.mockClear();123 projectAnnotations.decorators[0].mockClear();124 docsRenderer.render.mockClear();125 (logger.warn as jest.Mock<typeof logger.warn>).mockClear();126 mockStoryIndex.mockReset().mockReturnValue(storyIndex);127 addons.setChannel(mockChannel as any);128 addons.setServerChannel(createMockChannel());129 mockFetchResult = { status: 200, json: mockStoryIndex, text: () => 'error text' };130 mocked(WebView.prototype).prepareForDocs.mockReturnValue('docs-element' as any);131 mocked(WebView.prototype).prepareForStory.mockReturnValue('story-element' as any);132});133describe('PreviewWeb', () => {134 describe('initialize', () => {135 it('shows an error if getProjectAnnotations throws', async () => {136 const err = new Error('meta error');137 const preview = new PreviewWeb();138 await expect(139 preview.initialize({140 importFn,141 getProjectAnnotations: () => {142 throw err;143 },144 })145 ).rejects.toThrow(err);146 expect(preview.view.showErrorDisplay).toHaveBeenCalled();147 expect(mockChannel.emit).toHaveBeenCalledWith(CONFIG_ERROR, err);148 });149 it('shows an error if the stories.json endpoint 500s', async () => {150 const err = new Error('sort error');151 mockFetchResult = { status: 500, text: async () => err.toString() };152 const preview = new PreviewWeb();153 await expect(preview.initialize({ importFn, getProjectAnnotations })).rejects.toThrow(154 'sort error'155 );156 expect(preview.view.showErrorDisplay).toHaveBeenCalled();157 expect(mockChannel.emit).toHaveBeenCalledWith(CONFIG_ERROR, expect.any(Error));158 });159 it('sets globals from the URL', async () => {160 document.location.search = '?id=*&globals=a:c';161 const preview = await createAndRenderPreview();162 expect(preview.storyStore.globals!.get()).toEqual({ a: 'c' });163 });164 it('emits the SET_GLOBALS event', async () => {165 await createAndRenderPreview();166 expect(mockChannel.emit).toHaveBeenCalledWith(SET_GLOBALS, {167 globals: { a: 'b' },168 globalTypes: {},169 });170 });171 it('SET_GLOBALS sets globals and types even when undefined', async () => {172 await createAndRenderPreview({ getProjectAnnotations: () => ({ renderToDOM: jest.fn() }) });173 expect(mockChannel.emit).toHaveBeenCalledWith(SET_GLOBALS, {174 globals: {},175 globalTypes: {},176 });177 });178 it('emits the SET_GLOBALS event from the URL', async () => {179 document.location.search = '?id=*&globals=a:c';180 await createAndRenderPreview();181 expect(mockChannel.emit).toHaveBeenCalledWith(SET_GLOBALS, {182 globals: { a: 'c' },183 globalTypes: {},184 });185 });186 it('sets args from the URL', async () => {187 document.location.search = '?id=component-one--a&args=foo:url';188 const preview = await createAndRenderPreview();189 expect(preview.storyStore.args.get('component-one--a')).toEqual({190 foo: 'url',191 });192 });193 it('updates args from the URL', async () => {194 document.location.search = '?id=component-one--a&args=foo:url';195 await createAndRenderPreview();196 expect(mockChannel.emit).toHaveBeenCalledWith(STORY_ARGS_UPDATED, {197 storyId: 'component-one--a',198 args: { foo: 'url' },199 });200 });201 it('allows async getProjectAnnotations', async () => {202 const preview = new PreviewWeb();203 await preview.initialize({204 importFn,205 getProjectAnnotations: async () => {206 return getProjectAnnotations();207 },208 });209 expect(preview.storyStore.globals!.get()).toEqual({ a: 'b' });210 });211 });212 describe('initial selection', () => {213 it('selects the story specified in the URL', async () => {214 document.location.search = '?id=component-one--a';215 const preview = await createAndRenderPreview();216 expect(preview.urlStore.selection).toEqual({217 storyId: 'component-one--a',218 viewMode: 'story',219 });220 expect(history.replaceState).toHaveBeenCalledWith(221 {},222 '',223 'pathname?id=component-one--a&viewMode=story'224 );225 });226 it('emits the STORY_SPECIFIED event', async () => {227 document.location.search = '?id=component-one--a';228 await createAndRenderPreview();229 expect(mockChannel.emit).toHaveBeenCalledWith(STORY_SPECIFIED, {230 storyId: 'component-one--a',231 viewMode: 'story',232 });233 });234 it('emits the CURRENT_STORY_WAS_SET event', async () => {235 document.location.search = '?id=component-one--a';236 await createAndRenderPreview();237 expect(mockChannel.emit).toHaveBeenCalledWith(CURRENT_STORY_WAS_SET, {238 storyId: 'component-one--a',239 viewMode: 'story',240 });241 });242 describe('when the first entry is a docs entry', () => {243 it('emits the STORY_SPECIFIED event with viewMode=docs', async () => {244 document.location.search = '?id=*';245 await createAndRenderPreview();246 expect(mockChannel.emit).toHaveBeenCalledWith(STORY_SPECIFIED, {247 storyId: 'component-one--docs',248 viewMode: 'docs',249 });250 });251 });252 describe('if the story specified does not exist', () => {253 it('renders a loading error', async () => {254 document.location.search = '?id=random';255 const preview = await createAndRenderPreview();256 expect(preview.view.showErrorDisplay).toHaveBeenCalled();257 expect(mockChannel.emit).toHaveBeenCalledWith(STORY_MISSING, 'random');258 });259 it('tries again with a specifier if CSF file changes', async () => {260 document.location.search = '?id=component-one--missing';261 const preview = await createAndRenderPreview();262 expect(preview.view.showErrorDisplay).toHaveBeenCalled();263 expect(mockChannel.emit).toHaveBeenCalledWith(STORY_MISSING, 'component-one--missing');264 mockChannel.emit.mockClear();265 const newComponentOneExports = merge({}, componentOneExports, {266 d: { args: { foo: 'd' }, play: jest.fn() },267 });268 const newImportFn = jest.fn(async (path) => {269 return path === './src/ComponentOne.stories.js'270 ? newComponentOneExports271 : componentTwoExports;272 });273 preview.onStoriesChanged({274 importFn: newImportFn,275 storyIndex: {276 v: 4,277 entries: {278 ...storyIndex.entries,279 'component-one--missing': {280 type: 'story',281 id: 'component-one--missing',282 title: 'Component One',283 name: 'D',284 importPath: './src/ComponentOne.stories.js',285 },286 },287 },288 });289 await waitForRender();290 expect(mockChannel.emit).toHaveBeenCalledWith(STORY_SPECIFIED, {291 storyId: 'component-one--missing',292 viewMode: 'story',293 });294 });295 describe('after selection changes', () => {296 beforeEach(() => {297 jest.useFakeTimers();298 });299 afterEach(() => {300 jest.useRealTimers();301 });302 it('DOES NOT try again if CSF file changes', async () => {303 document.location.search = '?id=component-one--missing';304 const preview = await createAndRenderPreview();305 expect(preview.view.showErrorDisplay).toHaveBeenCalled();306 expect(mockChannel.emit).toHaveBeenCalledWith(STORY_MISSING, 'component-one--missing');307 emitter.emit(SET_CURRENT_STORY, {308 storyId: 'component-one--b',309 viewMode: 'story',310 });311 await waitForSetCurrentStory();312 const newComponentOneExports = merge({}, componentOneExports, {313 d: { args: { foo: 'd' }, play: jest.fn() },314 });315 const newImportFn = jest.fn(async (path) => {316 return path === './src/ComponentOne.stories.js'317 ? newComponentOneExports318 : componentTwoExports;319 });320 preview.onStoriesChanged({321 importFn: newImportFn,322 storyIndex: {323 v: 4,324 entries: {325 ...storyIndex.entries,326 'component-one--missing': {327 type: 'story',328 id: 'component-one--missing',329 title: 'Component One',330 name: 'D',331 importPath: './src/ComponentOne.stories.js',332 },333 },334 },335 });336 expect(mockChannel.emit).not.toHaveBeenCalledWith(STORY_SPECIFIED, {337 storyId: 'component-one--missing',338 viewMode: 'story',339 });340 });341 });342 });343 it('renders missing if no selection', async () => {344 const preview = await createAndRenderPreview();345 expect(preview.view.showNoPreview).toHaveBeenCalled();346 expect(mockChannel.emit).toHaveBeenCalledWith(STORY_MISSING);347 });348 describe('story entries', () => {349 it('calls view.prepareForStory', async () => {350 document.location.search = '?id=component-one--a';351 const preview = await createAndRenderPreview();352 expect(preview.view.prepareForStory).toHaveBeenCalledWith(353 expect.objectContaining({354 id: 'component-one--a',355 })356 );357 });358 it('emits STORY_PREPARED', async () => {359 document.location.search = '?id=component-one--a';360 await createAndRenderPreview();361 expect(mockChannel.emit).toHaveBeenCalledWith(STORY_PREPARED, {362 id: 'component-one--a',363 parameters: {364 __isArgsStory: false,365 docs: expect.any(Object),366 fileName: './src/ComponentOne.stories.js',367 },368 initialArgs: { foo: 'a' },369 argTypes: { foo: { name: 'foo', type: { name: 'string' } } },370 args: { foo: 'a' },371 });372 });373 it('applies loaders with story context', async () => {374 document.location.search = '?id=component-one--a';375 await createAndRenderPreview();376 expect(componentOneExports.default.loaders[0]).toHaveBeenCalledWith(377 expect.objectContaining({378 id: 'component-one--a',379 parameters: {380 __isArgsStory: false,381 docs: expect.any(Object),382 fileName: './src/ComponentOne.stories.js',383 },384 initialArgs: { foo: 'a' },385 argTypes: { foo: { name: 'foo', type: { name: 'string' } } },386 args: { foo: 'a' },387 })388 );389 });390 it('passes loaded context to renderToDOM', async () => {391 document.location.search = '?id=component-one--a';392 await createAndRenderPreview();393 expect(projectAnnotations.renderToDOM).toHaveBeenCalledWith(394 expect.objectContaining({395 forceRemount: true,396 storyContext: expect.objectContaining({397 id: 'component-one--a',398 parameters: {399 __isArgsStory: false,400 docs: expect.any(Object),401 fileName: './src/ComponentOne.stories.js',402 },403 globals: { a: 'b' },404 initialArgs: { foo: 'a' },405 argTypes: { foo: { name: 'foo', type: { name: 'string' } } },406 args: { foo: 'a' },407 loaded: { l: 7 },408 }),409 }),410 'story-element'411 );412 });413 it('renders exception if a loader throws', async () => {414 const error = new Error('error');415 componentOneExports.default.loaders[0].mockImplementationOnce(() => {416 throw error;417 });418 document.location.search = '?id=component-one--a';419 const preview = await createAndRenderPreview();420 expect(mockChannel.emit).toHaveBeenCalledWith(STORY_THREW_EXCEPTION, serializeError(error));421 expect(preview.view.showErrorDisplay).toHaveBeenCalledWith(error);422 });423 it('renders exception if renderToDOM throws', async () => {424 const error = new Error('error');425 projectAnnotations.renderToDOM.mockImplementation(() => {426 throw error;427 });428 document.location.search = '?id=component-one--a';429 const preview = await createAndRenderPreview();430 expect(mockChannel.emit).toHaveBeenCalledWith(STORY_THREW_EXCEPTION, serializeError(error));431 expect(preview.view.showErrorDisplay).toHaveBeenCalledWith(error);432 });433 it('renders helpful message if renderToDOM is undefined', async () => {434 document.location.search = '?id=component-one--a';435 const preview = new PreviewWeb();436 await expect(437 preview.initialize({438 importFn,439 getProjectAnnotations: () => ({440 ...getProjectAnnotations,441 renderToDOM: undefined,442 }),443 })444 ).rejects.toThrow();445 expect(preview.view.showErrorDisplay).toHaveBeenCalled();446 expect((preview.view.showErrorDisplay as jest.Mock).mock.calls[0][0])447 .toMatchInlineSnapshot(`448 [Error: Expected your framework's preset to export a \`renderToDOM\` field.449 Perhaps it needs to be upgraded for Storybook 6.4?450 More info: https://github.com/storybookjs/storybook/blob/next/MIGRATION.md#mainjs-framework-field]451 `);452 });453 it('emits but does not render exception if the play function throws', async () => {454 const error = new Error('error');455 componentOneExports.a.play.mockImplementationOnce(() => {456 throw error;457 });458 document.location.search = '?id=component-one--a';459 const preview = await createAndRenderPreview();460 expect(mockChannel.emit).toHaveBeenCalledWith(461 PLAY_FUNCTION_THREW_EXCEPTION,462 serializeError(error)463 );464 expect(preview.view.showErrorDisplay).not.toHaveBeenCalled();465 });466 it('renders exception if the story calls showException', async () => {467 const error = new Error('error');468 projectAnnotations.renderToDOM.mockImplementation((context) =>469 context.showException(error)470 );471 document.location.search = '?id=component-one--a';472 const preview = await createAndRenderPreview();473 expect(mockChannel.emit).toHaveBeenCalledWith(STORY_THREW_EXCEPTION, serializeError(error));474 expect(preview.view.showErrorDisplay).toHaveBeenCalledWith(error);475 });476 it('renders error if the story calls showError', async () => {477 const error = { title: 'title', description: 'description' };478 projectAnnotations.renderToDOM.mockImplementation((context) => context.showError(error));479 document.location.search = '?id=component-one--a';480 const preview = await createAndRenderPreview();481 expect(mockChannel.emit).toHaveBeenCalledWith(STORY_ERRORED, error);482 expect(preview.view.showErrorDisplay).toHaveBeenCalledWith({483 message: error.title,484 stack: error.description,485 });486 });487 it('executes playFunction', async () => {488 document.location.search = '?id=component-one--a';489 await createAndRenderPreview();490 expect(componentOneExports.a.play).toHaveBeenCalled();491 });492 it('emits STORY_RENDERED', async () => {493 document.location.search = '?id=component-one--a';494 await createAndRenderPreview();495 expect(mockChannel.emit).toHaveBeenCalledWith(STORY_RENDERED, 'component-one--a');496 });497 it('does not show error display if the render function throws IGNORED_EXCEPTION', async () => {498 document.location.search = '?id=component-one--a';499 projectAnnotations.renderToDOM.mockImplementation(() => {500 throw IGNORED_EXCEPTION;501 });502 const preview = new PreviewWeb();503 await preview.initialize({ importFn, getProjectAnnotations });504 await waitForRender();505 expect(mockChannel.emit).toHaveBeenCalledWith(506 STORY_THREW_EXCEPTION,507 serializeError(IGNORED_EXCEPTION)508 );509 expect(preview.view.showErrorDisplay).not.toHaveBeenCalled();510 });511 });512 describe('template docs entries', () => {513 it('always renders in docs viewMode', async () => {514 document.location.search = '?id=component-one--docs';515 await createAndRenderPreview();516 expect(mockChannel.emit).toHaveBeenCalledWith(DOCS_RENDERED, 'component-one--docs');517 });518 it('calls view.prepareForDocs', async () => {519 document.location.search = '?id=component-one--docs&viewMode=docs';520 const preview = await createAndRenderPreview();521 expect(preview.view.prepareForDocs).toHaveBeenCalled();522 });523 it('renders with docs parameters from the first story', async () => {524 document.location.search = '?id=component-one--docs&viewMode=docs';525 await createAndRenderPreview();526 expect(docsRenderer.render).toHaveBeenCalledWith(527 expect.any(Object),528 expect.objectContaining({529 page: componentOneExports.default.parameters.docs.page,530 renderer: projectAnnotations.parameters.docs.renderer,531 }),532 'docs-element',533 expect.any(Function)534 );535 });536 it('loads imports of the docs entry', async () => {537 document.location.search = '?id=component-one--docs&viewMode=docs';538 await createAndRenderPreview();539 expect(importFn).toHaveBeenCalledWith('./src/ExtraComponentOne.stories.js');540 });541 it('renders with componentStories loaded from both story files', async () => {542 document.location.search = '?id=component-one--docs&viewMode=docs';543 await createAndRenderPreview();544 const context = docsRenderer.render.mock.calls[0][0];545 expect(context.componentStories().map((s) => s.id)).toEqual([546 'component-one--a',547 'component-one--b',548 'component-one--e',549 ]);550 });551 it('emits DOCS_RENDERED', async () => {552 document.location.search = '?id=component-one--docs&viewMode=docs';553 await createAndRenderPreview();554 expect(mockChannel.emit).toHaveBeenCalledWith(DOCS_RENDERED, 'component-one--docs');555 });556 });557 describe('standalone docs entries', () => {558 it('always renders in docs viewMode', async () => {559 document.location.search = '?id=introduction--docs';560 await createAndRenderPreview();561 expect(mockChannel.emit).toHaveBeenCalledWith(DOCS_RENDERED, 'introduction--docs');562 });563 it('calls view.prepareForDocs', async () => {564 document.location.search = '?id=component-one--docs&viewMode=docs';565 const preview = await createAndRenderPreview();566 expect(preview.view.prepareForDocs).toHaveBeenCalled();567 });568 it('renders with the generated docs parameters', async () => {569 document.location.search = '?id=introduction--docs&viewMode=docs';570 await createAndRenderPreview();571 expect(docsRenderer.render).toHaveBeenCalledWith(572 expect.any(Object),573 expect.objectContaining({574 page: standaloneDocsExports.default,575 renderer: projectAnnotations.parameters.docs.renderer,576 }),577 'docs-element',578 expect.any(Function)579 );580 });581 it('loads imports of the docs entry', async () => {582 document.location.search = '?id=introduction--docs';583 await createAndRenderPreview();584 expect(importFn).toHaveBeenCalledWith('./src/ComponentTwo.stories.js');585 });586 it('emits DOCS_RENDERED', async () => {587 document.location.search = '?id=component-one--docs&viewMode=docs';588 await createAndRenderPreview();589 expect(mockChannel.emit).toHaveBeenCalledWith(DOCS_RENDERED, 'component-one--docs');590 });591 });592 });593 describe('onUpdateGlobals', () => {594 it('emits GLOBALS_UPDATED', async () => {595 document.location.search = '?id=component-one--a';596 await createAndRenderPreview();597 emitter.emit(UPDATE_GLOBALS, { globals: { foo: 'bar' } });598 await waitForEvents([GLOBALS_UPDATED]);599 expect(mockChannel.emit).toHaveBeenCalledWith(GLOBALS_UPDATED, {600 globals: { a: 'b', foo: 'bar' },601 initialGlobals: { a: 'b' },602 });603 });604 it('sets new globals on the store', async () => {605 document.location.search = '?id=component-one--a';606 const preview = await createAndRenderPreview();607 emitter.emit(UPDATE_GLOBALS, { globals: { foo: 'bar' } });608 expect(preview.storyStore.globals!.get()).toEqual({ a: 'b', foo: 'bar' });609 });610 it('passes new globals in context to renderToDOM', async () => {611 document.location.search = '?id=component-one--a';612 const preview = await createAndRenderPreview();613 mockChannel.emit.mockClear();614 projectAnnotations.renderToDOM.mockClear();615 emitter.emit(UPDATE_GLOBALS, { globals: { foo: 'bar' } });616 await waitForRender();617 expect(projectAnnotations.renderToDOM).toHaveBeenCalledWith(618 expect.objectContaining({619 forceRemount: false,620 storyContext: expect.objectContaining({621 globals: { a: 'b', foo: 'bar' },622 }),623 }),624 'story-element'625 );626 });627 it('emits STORY_RENDERED', async () => {628 document.location.search = '?id=component-one--a';629 await createAndRenderPreview();630 mockChannel.emit.mockClear();631 emitter.emit(UPDATE_GLOBALS, { globals: { foo: 'bar' } });632 await waitForRender();633 expect(mockChannel.emit).toHaveBeenCalledWith(STORY_RENDERED, 'component-one--a');634 });635 describe('in docs mode', () => {636 it('re-renders the docs container', async () => {637 document.location.search = '?id=component-one--docs&viewMode=docs';638 await createAndRenderPreview();639 mockChannel.emit.mockClear();640 docsRenderer.render.mockClear();641 emitter.emit(UPDATE_GLOBALS, { globals: { foo: 'bar' } });642 await waitForEvents([GLOBALS_UPDATED]);643 expect(docsRenderer.render).toHaveBeenCalled();644 });645 });646 });647 describe('onUpdateArgs', () => {648 it('emits STORY_ARGS_UPDATED', async () => {649 document.location.search = '?id=component-one--a';650 await createAndRenderPreview();651 emitter.emit(UPDATE_STORY_ARGS, {652 storyId: 'component-one--a',653 updatedArgs: { new: 'arg' },654 });655 await waitForEvents([STORY_ARGS_UPDATED]);656 expect(mockChannel.emit).toHaveBeenCalledWith(STORY_ARGS_UPDATED, {657 storyId: 'component-one--a',658 args: { foo: 'a', new: 'arg' },659 });660 });661 it('sets new args on the store', async () => {662 document.location.search = '?id=component-one--a';663 const preview = await createAndRenderPreview();664 emitter.emit(UPDATE_STORY_ARGS, {665 storyId: 'component-one--a',666 updatedArgs: { new: 'arg' },667 });668 expect(preview.storyStore.args.get('component-one--a')).toEqual({669 foo: 'a',670 new: 'arg',671 });672 });673 it('passes new args in context to renderToDOM', async () => {674 document.location.search = '?id=component-one--a';675 await createAndRenderPreview();676 mockChannel.emit.mockClear();677 projectAnnotations.renderToDOM.mockClear();678 emitter.emit(UPDATE_STORY_ARGS, {679 storyId: 'component-one--a',680 updatedArgs: { new: 'arg' },681 });682 await waitForRender();683 expect(projectAnnotations.renderToDOM).toHaveBeenCalledWith(684 expect.objectContaining({685 forceRemount: false,686 storyContext: expect.objectContaining({687 initialArgs: { foo: 'a' },688 args: { foo: 'a', new: 'arg' },689 }),690 }),691 'story-element'692 );693 });694 it('emits STORY_RENDERED', async () => {695 document.location.search = '?id=component-one--a';696 await createAndRenderPreview();697 mockChannel.emit.mockClear();698 emitter.emit(UPDATE_STORY_ARGS, {699 storyId: 'component-one--a',700 updatedArgs: { new: 'arg' },701 });702 await waitForRender();703 expect(mockChannel.emit).toHaveBeenCalledWith(STORY_RENDERED, 'component-one--a');704 });705 describe('while story is still rendering', () => {706 it('runs loaders again', async () => {707 const [gate, openGate] = createGate();708 document.location.search = '?id=component-one--a';709 componentOneExports.default.loaders[0].mockImplementationOnce(async () => gate);710 await new PreviewWeb().initialize({ importFn, getProjectAnnotations });711 await waitForRenderPhase('loading');712 expect(componentOneExports.default.loaders[0]).toHaveBeenCalledWith(713 expect.objectContaining({714 args: { foo: 'a' },715 })716 );717 componentOneExports.default.loaders[0].mockClear();718 emitter.emit(UPDATE_STORY_ARGS, {719 storyId: 'component-one--a',720 updatedArgs: { new: 'arg' },721 });722 await waitForRender();723 expect(componentOneExports.default.loaders[0]).toHaveBeenCalledWith(724 expect.objectContaining({725 args: { foo: 'a', new: 'arg' },726 })727 );728 // Story gets rendered with updated args729 expect(projectAnnotations.renderToDOM).toHaveBeenCalledTimes(1);730 expect(projectAnnotations.renderToDOM).toHaveBeenCalledWith(731 expect.objectContaining({732 forceRemount: true, // Wasn't yet rendered so we need to force remount733 storyContext: expect.objectContaining({734 loaded: { l: 7 }, // This is the value returned by the *second* loader call735 args: { foo: 'a', new: 'arg' },736 }),737 }),738 'story-element'739 );740 // Now let the first loader call resolve741 mockChannel.emit.mockClear();742 projectAnnotations.renderToDOM.mockClear();743 openGate({ l: 8 });744 await waitForRender();745 // Now the first call comes through, but picks up the new args746 // Note this isn't a particularly realistic case (the second loader being quicker than the first)747 expect(projectAnnotations.renderToDOM).toHaveBeenCalledTimes(1);748 expect(projectAnnotations.renderToDOM).toHaveBeenCalledWith(749 expect.objectContaining({750 storyContext: expect.objectContaining({751 loaded: { l: 8 },752 args: { foo: 'a', new: 'arg' },753 }),754 }),755 'story-element'756 );757 });758 it('renders a second time if renderToDOM is running', async () => {759 const [gate, openGate] = createGate();760 document.location.search = '?id=component-one--a';761 projectAnnotations.renderToDOM.mockImplementation(async () => gate);762 await new PreviewWeb().initialize({ importFn, getProjectAnnotations });763 await waitForRenderPhase('rendering');764 emitter.emit(UPDATE_STORY_ARGS, {765 storyId: 'component-one--a',766 updatedArgs: { new: 'arg' },767 });768 // Now let the renderToDOM call resolve769 openGate();770 await waitForRender();771 expect(projectAnnotations.renderToDOM).toHaveBeenCalledTimes(2);772 expect(projectAnnotations.renderToDOM).toHaveBeenCalledWith(773 expect.objectContaining({774 forceRemount: true,775 storyContext: expect.objectContaining({776 loaded: { l: 7 },777 args: { foo: 'a' },778 }),779 }),780 'story-element'781 );782 expect(projectAnnotations.renderToDOM).toHaveBeenCalledWith(783 expect.objectContaining({784 forceRemount: false,785 storyContext: expect.objectContaining({786 loaded: { l: 7 },787 args: { foo: 'a', new: 'arg' },788 }),789 }),790 'story-element'791 );792 });793 it('works if it is called directly from inside non async renderToDOM', async () => {794 document.location.search = '?id=component-one--a';795 projectAnnotations.renderToDOM.mockImplementation(() => {796 emitter.emit(UPDATE_STORY_ARGS, {797 storyId: 'component-one--a',798 updatedArgs: { new: 'arg' },799 });800 });801 await createAndRenderPreview();802 expect(projectAnnotations.renderToDOM).toHaveBeenCalledTimes(2);803 expect(projectAnnotations.renderToDOM).toHaveBeenCalledWith(804 expect.objectContaining({805 forceRemount: true,806 storyContext: expect.objectContaining({807 loaded: { l: 7 },808 args: { foo: 'a' },809 }),810 }),811 'story-element'812 );813 expect(projectAnnotations.renderToDOM).toHaveBeenCalledWith(814 expect.objectContaining({815 forceRemount: false,816 storyContext: expect.objectContaining({817 loaded: { l: 7 },818 args: { foo: 'a', new: 'arg' },819 }),820 }),821 'story-element'822 );823 });824 it('calls renderToDOM again if play function is running', async () => {825 const [gate, openGate] = createGate();826 componentOneExports.a.play.mockImplementationOnce(async () => gate);827 const renderToDOMCalled = new Promise((resolve) => {828 projectAnnotations.renderToDOM.mockImplementation(() => {829 resolve(null);830 });831 });832 document.location.search = '?id=component-one--a';833 await new PreviewWeb().initialize({ importFn, getProjectAnnotations });834 await waitForRenderPhase('playing');835 await renderToDOMCalled;836 // Story gets rendered with original args837 expect(projectAnnotations.renderToDOM).toHaveBeenCalledWith(838 expect.objectContaining({839 forceRemount: true,840 storyContext: expect.objectContaining({841 loaded: { l: 7 },842 args: { foo: 'a' },843 }),844 }),845 'story-element'846 );847 emitter.emit(UPDATE_STORY_ARGS, {848 storyId: 'component-one--a',849 updatedArgs: { new: 'arg' },850 });851 // The second call should emit STORY_RENDERED852 await waitForRender();853 // Story gets rendered with updated args854 expect(projectAnnotations.renderToDOM).toHaveBeenCalledWith(855 expect.objectContaining({856 forceRemount: false,857 storyContext: expect.objectContaining({858 loaded: { l: 7 },859 args: { foo: 'a', new: 'arg' },860 }),861 }),862 'story-element'863 );864 // Now let the playFunction call resolve865 openGate();866 });867 });868 describe('in docs mode', () => {869 it('does not re-render the docs container', async () => {870 document.location.search = '?id=component-one--docs&viewMode=docs';871 await createAndRenderPreview();872 docsRenderer.render.mockClear();873 mockChannel.emit.mockClear();874 emitter.emit(UPDATE_STORY_ARGS, {875 storyId: 'component-one--a',876 updatedArgs: { new: 'arg' },877 });878 await waitForEvents([STORY_ARGS_UPDATED]);879 expect(docsRenderer.render).not.toHaveBeenCalled();880 });881 describe('when renderStoryToElement was called', () => {882 it('re-renders the story', async () => {883 document.location.search = '?id=component-one--docs&viewMode=docs';884 const preview = await createAndRenderPreview();885 await waitForRender();886 mockChannel.emit.mockClear();887 const story = await preview.storyStore.loadStory({ storyId: 'component-one--a' });888 preview.renderStoryToElement(story, 'story-element' as any);889 await waitForRender();890 expect(projectAnnotations.renderToDOM).toHaveBeenCalledWith(891 expect.objectContaining({892 storyContext: expect.objectContaining({893 args: { foo: 'a' },894 }),895 }),896 'story-element'897 );898 docsRenderer.render.mockClear();899 mockChannel.emit.mockClear();900 emitter.emit(UPDATE_STORY_ARGS, {901 storyId: 'component-one--a',902 updatedArgs: { new: 'arg' },903 });904 await waitForRender();905 expect(projectAnnotations.renderToDOM).toHaveBeenCalledWith(906 expect.objectContaining({907 storyContext: expect.objectContaining({908 args: { foo: 'a', new: 'arg' },909 }),910 }),911 'story-element'912 );913 });914 });915 });916 });917 describe('onPreloadStories', () => {918 it('loads stories', async () => {919 document.location.search = '?id=component-one--docs&viewMode=docs';920 const preview = await createAndRenderPreview();921 await waitForRender();922 importFn.mockClear();923 await preview.onPreloadStories({ ids: ['component-two--c'] });924 expect(importFn).toHaveBeenCalledWith('./src/ComponentTwo.stories.js');925 });926 it('loads legacy docs entries', async () => {927 document.location.search = '?id=component-one--docs&viewMode=docs';928 const preview = await createAndRenderPreview();929 await waitForRender();930 importFn.mockClear();931 await preview.onPreloadStories({ ids: ['component-one--docs'] });932 expect(importFn).toHaveBeenCalledWith('./src/ComponentOne.stories.js');933 });934 it('loads modern docs entries', async () => {935 document.location.search = '?id=component-one--docs&viewMode=docs';936 const preview = await createAndRenderPreview();937 await waitForRender();938 importFn.mockClear();939 await preview.onPreloadStories({ ids: ['introduction--docs'] });940 expect(importFn).toHaveBeenCalledWith('./src/Introduction.mdx');941 });942 it('loads imports of modern docs entries', async () => {943 document.location.search = '?id=component-one--docs&viewMode=docs';944 const preview = await createAndRenderPreview();945 await waitForRender();946 importFn.mockClear();947 await preview.onPreloadStories({ ids: ['introduction--docs'] });948 expect(importFn).toHaveBeenCalledWith('./src/ComponentTwo.stories.js');949 });950 });951 describe('onResetArgs', () => {952 it('emits STORY_ARGS_UPDATED', async () => {953 document.location.search = '?id=component-one--a';954 await createAndRenderPreview();955 mockChannel.emit.mockClear();956 emitter.emit(UPDATE_STORY_ARGS, {957 storyId: 'component-one--a',958 updatedArgs: { foo: 'new' },959 });960 await waitForEvents([STORY_ARGS_UPDATED]);961 expect(mockChannel.emit).toHaveBeenCalledWith(STORY_ARGS_UPDATED, {962 storyId: 'component-one--a',963 args: { foo: 'new' },964 });965 mockChannel.emit.mockClear();966 emitter.emit(RESET_STORY_ARGS, {967 storyId: 'component-one--a',968 argNames: ['foo'],969 });970 await waitForEvents([STORY_ARGS_UPDATED]);971 expect(mockChannel.emit).toHaveBeenCalledWith(STORY_ARGS_UPDATED, {972 storyId: 'component-one--a',973 args: { foo: 'a' },974 });975 });976 it('resets a single arg', async () => {977 document.location.search = '?id=component-one--a';978 const preview = await createAndRenderPreview();979 const onUpdateArgsSpy = jest.spyOn(preview, 'onUpdateArgs');980 mockChannel.emit.mockClear();981 emitter.emit(UPDATE_STORY_ARGS, {982 storyId: 'component-one--a',983 updatedArgs: { foo: 'new', new: 'value' },984 });985 await waitForEvents([STORY_ARGS_UPDATED]);986 mockChannel.emit.mockClear();987 emitter.emit(RESET_STORY_ARGS, {988 storyId: 'component-one--a',989 argNames: ['foo'],990 });991 await waitForRender();992 expect(projectAnnotations.renderToDOM).toHaveBeenCalledWith(993 expect.objectContaining({994 forceRemount: false,995 storyContext: expect.objectContaining({996 initialArgs: { foo: 'a' },997 args: { foo: 'a', new: 'value' },998 }),999 }),1000 'story-element'1001 );1002 await waitForEvents([STORY_ARGS_UPDATED]);1003 expect(mockChannel.emit).toHaveBeenCalledWith(STORY_ARGS_UPDATED, {1004 storyId: 'component-one--a',1005 args: { foo: 'a', new: 'value' },1006 });1007 expect(onUpdateArgsSpy).toHaveBeenCalledWith({1008 storyId: 'component-one--a',1009 updatedArgs: { foo: 'a' },1010 });1011 });1012 it('resets all args after one is updated', async () => {1013 document.location.search = '?id=component-one--a';1014 const preview = await createAndRenderPreview();1015 const onUpdateArgsSpy = jest.spyOn(preview, 'onUpdateArgs');1016 emitter.emit(UPDATE_STORY_ARGS, {1017 storyId: 'component-one--a',1018 updatedArgs: { foo: 'new' },1019 });1020 await waitForEvents([STORY_ARGS_UPDATED]);1021 mockChannel.emit.mockClear();1022 emitter.emit(RESET_STORY_ARGS, {1023 storyId: 'component-one--a',1024 });1025 await waitForRender();1026 expect(projectAnnotations.renderToDOM).toHaveBeenCalledWith(1027 expect.objectContaining({1028 forceRemount: false,1029 storyContext: expect.objectContaining({1030 initialArgs: { foo: 'a' },1031 args: { foo: 'a' },1032 }),1033 }),1034 'story-element'1035 );1036 await waitForEvents([STORY_ARGS_UPDATED]);1037 expect(mockChannel.emit).toHaveBeenCalledWith(STORY_ARGS_UPDATED, {1038 storyId: 'component-one--a',1039 args: { foo: 'a' },1040 });1041 expect(onUpdateArgsSpy).toHaveBeenCalledWith({1042 storyId: 'component-one--a',1043 updatedArgs: { foo: 'a' },1044 });1045 });1046 it('resets all args', async () => {1047 document.location.search = '?id=component-one--a';1048 const preview = await createAndRenderPreview();1049 const onUpdateArgsSpy = jest.spyOn(preview, 'onUpdateArgs');1050 emitter.emit(UPDATE_STORY_ARGS, {1051 storyId: 'component-one--a',1052 updatedArgs: { foo: 'new', new: 'value' },1053 });1054 await waitForEvents([STORY_ARGS_UPDATED]);1055 mockChannel.emit.mockClear();1056 emitter.emit(RESET_STORY_ARGS, {1057 storyId: 'component-one--a',1058 });1059 await waitForRender();1060 expect(projectAnnotations.renderToDOM).toHaveBeenCalledWith(1061 expect.objectContaining({1062 forceRemount: false,1063 storyContext: expect.objectContaining({1064 initialArgs: { foo: 'a' },1065 args: { foo: 'a' },1066 }),1067 }),1068 'story-element'1069 );1070 await waitForEvents([STORY_ARGS_UPDATED]);1071 expect(mockChannel.emit).toHaveBeenCalledWith(STORY_ARGS_UPDATED, {1072 storyId: 'component-one--a',1073 args: { foo: 'a' },1074 });1075 expect(onUpdateArgsSpy).toHaveBeenCalledWith({1076 storyId: 'component-one--a',1077 updatedArgs: { foo: 'a', new: undefined },1078 });1079 });1080 it('resets all args when one arg is undefined', async () => {1081 document.location.search = '?id=component-one--a';1082 const preview = await createAndRenderPreview();1083 const onUpdateArgsSpy = jest.spyOn(preview, 'onUpdateArgs');1084 emitter.emit(UPDATE_STORY_ARGS, {1085 storyId: 'component-one--a',1086 updatedArgs: { foo: undefined },1087 });1088 await waitForEvents([STORY_ARGS_UPDATED]);1089 mockChannel.emit.mockClear();1090 emitter.emit(RESET_STORY_ARGS, {1091 storyId: 'component-one--a',1092 });1093 await waitForRender();1094 expect(projectAnnotations.renderToDOM).toHaveBeenCalledWith(1095 expect.objectContaining({1096 forceRemount: false,1097 storyContext: expect.objectContaining({1098 initialArgs: { foo: 'a' },1099 args: { foo: 'a' },1100 }),1101 }),1102 'story-element'1103 );1104 await waitForEvents([STORY_ARGS_UPDATED]);1105 expect(mockChannel.emit).toHaveBeenCalledWith(STORY_ARGS_UPDATED, {1106 storyId: 'component-one--a',1107 args: { foo: 'a' },1108 });1109 expect(onUpdateArgsSpy).toHaveBeenCalledWith({1110 storyId: 'component-one--a',1111 updatedArgs: { foo: 'a' },1112 });1113 });1114 });1115 describe('on FORCE_RE_RENDER', () => {1116 it('rerenders the story with the same args', async () => {1117 document.location.search = '?id=component-one--a';1118 await createAndRenderPreview();1119 mockChannel.emit.mockClear();1120 projectAnnotations.renderToDOM.mockClear();1121 emitter.emit(FORCE_RE_RENDER);1122 await waitForRender();1123 expect(projectAnnotations.renderToDOM).toHaveBeenCalledWith(1124 expect.objectContaining({ forceRemount: false }),1125 'story-element'1126 );1127 });1128 });1129 describe('on FORCE_REMOUNT', () => {1130 beforeEach(() => {1131 jest.useFakeTimers();1132 });1133 afterEach(() => {1134 jest.useRealTimers();1135 });1136 it('remounts the story with the same args', async () => {1137 document.location.search = '?id=component-one--a';1138 await createAndRenderPreview();1139 mockChannel.emit.mockClear();1140 projectAnnotations.renderToDOM.mockClear();1141 emitter.emit(FORCE_REMOUNT, { storyId: 'component-one--a' });1142 await waitForRender();1143 expect(projectAnnotations.renderToDOM).toHaveBeenCalledWith(1144 expect.objectContaining({ forceRemount: true }),1145 'story-element'1146 );1147 });1148 it('aborts render function for initial story', async () => {1149 const [gate, openGate] = createGate();1150 document.location.search = '?id=component-one--a';1151 projectAnnotations.renderToDOM.mockImplementation(async () => gate);1152 await new PreviewWeb().initialize({ importFn, getProjectAnnotations });1153 await waitForRenderPhase('rendering');1154 expect(projectAnnotations.renderToDOM).toHaveBeenCalledWith(1155 expect.objectContaining({1156 forceRemount: true,1157 storyContext: expect.objectContaining({1158 id: 'component-one--a',1159 loaded: { l: 7 },1160 }),1161 }),1162 'story-element'1163 );1164 mockChannel.emit.mockClear();1165 emitter.emit(FORCE_REMOUNT, { storyId: 'component-one--a' });1166 await waitForSetCurrentStory();1167 // Now let the renderToDOM call resolve1168 openGate();1169 await waitForRenderPhase('aborted');1170 await waitForSetCurrentStory();1171 await waitForRenderPhase('rendering');1172 expect(projectAnnotations.renderToDOM).toHaveBeenCalledTimes(2);1173 await waitForRenderPhase('playing');1174 expect(componentOneExports.a.play).toHaveBeenCalledTimes(1);1175 await waitForRenderPhase('completed');1176 expect(mockChannel.emit).toHaveBeenCalledWith(STORY_RENDERED, 'component-one--a');1177 await waitForQuiescence();1178 });1179 });1180 describe('onSetCurrentStory', () => {1181 beforeEach(() => {1182 jest.useFakeTimers();1183 });1184 afterEach(() => {1185 jest.useRealTimers();1186 });1187 it('updates URL', async () => {1188 document.location.search = '?id=component-one--a';1189 await createAndRenderPreview();1190 emitter.emit(SET_CURRENT_STORY, {1191 storyId: 'component-one--b',1192 viewMode: 'story',1193 });1194 await waitForSetCurrentStory();1195 expect(history.replaceState).toHaveBeenCalledWith(1196 {},1197 '',1198 'pathname?id=component-one--b&viewMode=story'1199 );1200 });1201 it('emits CURRENT_STORY_WAS_SET', async () => {1202 document.location.search = '?id=component-one--a';1203 await createAndRenderPreview();1204 emitter.emit(SET_CURRENT_STORY, {1205 storyId: 'component-one--b',1206 viewMode: 'story',1207 });1208 await waitForSetCurrentStory();1209 expect(mockChannel.emit).toHaveBeenCalledWith(CURRENT_STORY_WAS_SET, {1210 storyId: 'component-one--b',1211 viewMode: 'story',1212 });1213 });1214 it('renders loading error if the story specified does not exist', async () => {1215 document.location.search = '?id=component-one--a';1216 const preview = await createAndRenderPreview();1217 emitter.emit(SET_CURRENT_STORY, {1218 storyId: 'random',1219 viewMode: 'story',1220 });1221 await waitForSetCurrentStory();1222 await waitForEvents([STORY_MISSING]);1223 expect(preview.view.showErrorDisplay).toHaveBeenCalled();1224 expect(mockChannel.emit).toHaveBeenCalledWith(STORY_MISSING, 'random');1225 });1226 describe('if called before the preview is initialized', () => {1227 it('still renders the selected story, once ready', async () => {1228 document.location.search = '';1229 // We intentionally are *not* awaiting here1230 new PreviewWeb().initialize({ importFn, getProjectAnnotations });1231 emitter.emit(SET_CURRENT_STORY, {1232 storyId: 'component-one--b',1233 viewMode: 'story',1234 });1235 await waitForEvents([STORY_RENDERED]);1236 expect(mockChannel.emit).toHaveBeenCalledWith(CURRENT_STORY_WAS_SET, {1237 storyId: 'component-one--b',1238 viewMode: 'story',1239 });1240 expect(history.replaceState).toHaveBeenCalledWith(1241 {},1242 '',1243 'pathname?id=component-one--b&viewMode=story'1244 );1245 expect(mockChannel.emit).not.toHaveBeenCalledWith(STORY_MISSING, 'component-one--b');1246 expect(mockChannel.emit).toHaveBeenCalledWith(STORY_RENDERED, 'component-one--b');1247 });1248 });1249 describe('if called on a storybook without selection', () => {1250 it('sets viewMode to story by default', async () => {1251 await createAndRenderPreview();1252 emitter.emit(SET_CURRENT_STORY, {1253 storyId: 'component-one--b',1254 });1255 await waitForSetCurrentStory();1256 expect(history.replaceState).toHaveBeenCalledWith(1257 {},1258 '',1259 'pathname?id=component-one--b&viewMode=story'1260 );1261 });1262 });1263 describe('if the selection is unchanged', () => {1264 it('emits STORY_UNCHANGED', async () => {1265 document.location.search = '?id=component-one--a';1266 await createAndRenderPreview();1267 emitter.emit(SET_CURRENT_STORY, {1268 storyId: 'component-one--a',1269 viewMode: 'story',1270 });1271 await waitForSetCurrentStory();1272 await waitForEvents([STORY_UNCHANGED]);1273 expect(mockChannel.emit).toHaveBeenCalledWith(STORY_UNCHANGED, 'component-one--a');1274 });1275 it('does NOT call renderToDOM', async () => {1276 document.location.search = '?id=component-one--a';1277 await createAndRenderPreview();1278 projectAnnotations.renderToDOM.mockClear();1279 emitter.emit(SET_CURRENT_STORY, {1280 storyId: 'component-one--a',1281 viewMode: 'story',1282 });1283 await waitForSetCurrentStory();1284 // The renderToDOM would have been async so we need to wait a tick.1285 await waitForQuiescence();1286 expect(projectAnnotations.renderToDOM).not.toHaveBeenCalled();1287 });1288 it('does NOT call renderToDOMs teardown', async () => {1289 document.location.search = '?id=component-one--a';1290 await createAndRenderPreview();1291 projectAnnotations.renderToDOM.mockClear();1292 emitter.emit(SET_CURRENT_STORY, {1293 storyId: 'component-one--a',1294 viewMode: 'story',1295 });1296 await waitForSetCurrentStory();1297 expect(teardownRenderToDOM).not.toHaveBeenCalled();1298 });1299 describe('while preparing', () => {1300 // For https://github.com/storybookjs/storybook/issues/172141301 it('does NOT render a second time in story mode', async () => {1302 document.location.search = '?id=component-one--a';1303 const [gate, openGate] = createGate();1304 const [importedGate, openImportedGate] = createGate();1305 importFn1306 .mockImplementationOnce(async (...args) => {1307 await gate;1308 return importFn(...args);1309 })1310 .mockImplementationOnce(async (...args) => {1311 // The second time we `import()` we open the "imported" gate1312 openImportedGate();1313 await gate;1314 return importFn(...args);1315 });1316 const preview = new PreviewWeb();1317 // We can't wait for the initialize function, as it waits for `renderSelection()`1318 // which prepares, but it does emit `CURRENT_STORY_WAS_SET` right before that1319 preview.initialize({ importFn, getProjectAnnotations });1320 await waitForEvents([CURRENT_STORY_WAS_SET]);1321 mockChannel.emit.mockClear();1322 projectAnnotations.renderToDOM.mockClear();1323 emitter.emit(SET_CURRENT_STORY, {1324 storyId: 'component-one--a',1325 viewMode: 'story',1326 });1327 await importedGate;1328 // We are blocking import so this won't render yet1329 expect(projectAnnotations.renderToDOM).not.toHaveBeenCalled();1330 mockChannel.emit.mockClear();1331 openGate();1332 await waitForRender();1333 // We should only render *once*1334 expect(projectAnnotations.renderToDOM).toHaveBeenCalledTimes(1);1335 // We should not show an error either1336 expect(preview.view.showErrorDisplay).not.toHaveBeenCalled();1337 });1338 // For https://github.com/storybookjs/storybook/issues/190151339 it('does NOT render a second time in template docs mode', async () => {1340 document.location.search = '?id=component-one--docs&viewMode=docs';1341 const [gate, openGate] = createGate();1342 const [importedGate, openImportedGate] = createGate();1343 importFn1344 .mockImplementationOnce(async (...args) => {1345 await gate;1346 return importFn(...args);1347 })1348 .mockImplementationOnce(async (...args) => {1349 // The second time we `import()` we open the "imported" gate1350 openImportedGate();1351 await gate;1352 return importFn(...args);1353 });1354 const preview = new PreviewWeb();1355 // We can't wait for the initialize function, as it waits for `renderSelection()`1356 // which prepares, but it does emit `CURRENT_STORY_WAS_SET` right before that1357 preview.initialize({ importFn, getProjectAnnotations });1358 await waitForEvents([CURRENT_STORY_WAS_SET]);1359 mockChannel.emit.mockClear();1360 projectAnnotations.renderToDOM.mockClear();1361 emitter.emit(SET_CURRENT_STORY, {1362 storyId: 'component-one--docs',1363 viewMode: 'docs',1364 });1365 await importedGate;1366 // We are blocking import so this won't render yet1367 expect(docsRenderer.render).not.toHaveBeenCalled();1368 mockChannel.emit.mockClear();1369 openGate();1370 await waitForRender();1371 // We should only render *once*1372 expect(docsRenderer.render).toHaveBeenCalledTimes(1);1373 // We should not show an error either1374 expect(preview.view.showErrorDisplay).not.toHaveBeenCalled();1375 });1376 it('does NOT render a second time in standalone docs mode', async () => {1377 document.location.search = '?id=introduction--docs&viewMode=docs';1378 const [gate, openGate] = createGate();1379 const [importedGate, openImportedGate] = createGate();1380 importFn1381 .mockImplementationOnce(async (...args) => {1382 await gate;1383 return importFn(...args);1384 })1385 .mockImplementationOnce(async (...args) => {1386 // The second time we `import()` we open the "imported" gate1387 openImportedGate();1388 await gate;1389 return importFn(...args);1390 });1391 const preview = new PreviewWeb();1392 // We can't wait for the initialize function, as it waits for `renderSelection()`1393 // which prepares, but it does emit `CURRENT_STORY_WAS_SET` right before that1394 preview.initialize({ importFn, getProjectAnnotations });1395 await waitForEvents([CURRENT_STORY_WAS_SET]);1396 mockChannel.emit.mockClear();1397 projectAnnotations.renderToDOM.mockClear();1398 emitter.emit(SET_CURRENT_STORY, {1399 storyId: 'introduction--docs',1400 viewMode: 'docs',1401 });1402 await importedGate;1403 // We are blocking import so this won't render yet1404 expect(docsRenderer.render).not.toHaveBeenCalled();1405 mockChannel.emit.mockClear();1406 openGate();1407 await waitForRender();1408 // We should only render *once*1409 expect(docsRenderer.render).toHaveBeenCalledTimes(1);1410 // We should not show an error either1411 expect(preview.view.showErrorDisplay).not.toHaveBeenCalled();1412 });1413 });1414 });1415 describe('when changing story in story viewMode', () => {1416 it('calls renderToDOMs teardown', async () => {1417 document.location.search = '?id=component-one--a';1418 await createAndRenderPreview();1419 projectAnnotations.renderToDOM.mockClear();1420 emitter.emit(SET_CURRENT_STORY, {1421 storyId: 'component-one--b',1422 viewMode: 'story',1423 });1424 await waitForSetCurrentStory();1425 expect(teardownRenderToDOM).toHaveBeenCalled();1426 });1427 it('updates URL', async () => {1428 document.location.search = '?id=component-one--a';1429 await createAndRenderPreview();1430 emitter.emit(SET_CURRENT_STORY, {1431 storyId: 'component-one--b',1432 viewMode: 'story',1433 });1434 await waitForSetCurrentStory();1435 expect(history.replaceState).toHaveBeenCalledWith(1436 {},1437 '',1438 'pathname?id=component-one--b&viewMode=story'1439 );1440 });1441 it('renders preparing state', async () => {1442 document.location.search = '?id=component-one--a';1443 const preview = await createAndRenderPreview();1444 emitter.emit(SET_CURRENT_STORY, {1445 storyId: 'component-one--b',1446 viewMode: 'story',1447 });1448 await waitForSetCurrentStory();1449 expect(preview.view.showPreparingStory).toHaveBeenCalled();1450 });1451 it('emits STORY_CHANGED', async () => {1452 document.location.search = '?id=component-one--a';1453 await createAndRenderPreview();1454 mockChannel.emit.mockClear();1455 emitter.emit(SET_CURRENT_STORY, {1456 storyId: 'component-one--b',1457 viewMode: 'story',1458 });1459 await waitForSetCurrentStory();1460 await waitForEvents([STORY_CHANGED]);1461 expect(mockChannel.emit).toHaveBeenCalledWith(STORY_CHANGED, 'component-one--b');1462 });1463 it('emits STORY_PREPARED', async () => {1464 document.location.search = '?id=component-one--a';1465 await createAndRenderPreview();1466 mockChannel.emit.mockClear();1467 emitter.emit(SET_CURRENT_STORY, {1468 storyId: 'component-one--b',1469 viewMode: 'story',1470 });1471 await waitForSetCurrentStory();1472 await waitForEvents([STORY_PREPARED]);1473 expect(mockChannel.emit).toHaveBeenCalledWith(STORY_PREPARED, {1474 id: 'component-one--b',1475 parameters: {1476 __isArgsStory: false,1477 docs: expect.any(Object),1478 fileName: './src/ComponentOne.stories.js',1479 },1480 initialArgs: { foo: 'b' },1481 argTypes: { foo: { name: 'foo', type: { name: 'string' } } },1482 args: { foo: 'b' },1483 });1484 });1485 it('applies loaders with story context', async () => {1486 document.location.search = '?id=component-one--a';1487 await createAndRenderPreview();1488 mockChannel.emit.mockClear();1489 emitter.emit(SET_CURRENT_STORY, {1490 storyId: 'component-one--b',1491 viewMode: 'story',1492 });1493 await waitForSetCurrentStory();1494 await waitForRender();1495 expect(componentOneExports.default.loaders[0]).toHaveBeenCalledWith(1496 expect.objectContaining({1497 id: 'component-one--b',1498 parameters: {1499 __isArgsStory: false,1500 docs: expect.any(Object),1501 fileName: './src/ComponentOne.stories.js',1502 },1503 initialArgs: { foo: 'b' },1504 argTypes: { foo: { name: 'foo', type: { name: 'string' } } },1505 args: { foo: 'b' },1506 })1507 );1508 });1509 it('passes loaded context to renderToDOM', async () => {1510 document.location.search = '?id=component-one--a';1511 await createAndRenderPreview();1512 mockChannel.emit.mockClear();1513 emitter.emit(SET_CURRENT_STORY, {1514 storyId: 'component-one--b',1515 viewMode: 'story',1516 });1517 await waitForSetCurrentStory();1518 await waitForRender();1519 expect(projectAnnotations.renderToDOM).toHaveBeenCalledWith(1520 expect.objectContaining({1521 forceRemount: true,1522 storyContext: expect.objectContaining({1523 id: 'component-one--b',1524 parameters: {1525 __isArgsStory: false,1526 docs: expect.any(Object),1527 fileName: './src/ComponentOne.stories.js',1528 },1529 globals: { a: 'b' },1530 initialArgs: { foo: 'b' },1531 argTypes: { foo: { name: 'foo', type: { name: 'string' } } },1532 args: { foo: 'b' },1533 loaded: { l: 7 },1534 }),1535 }),1536 'story-element'1537 );1538 });1539 it('renders exception if renderToDOM throws', async () => {1540 document.location.search = '?id=component-one--a';1541 const preview = await createAndRenderPreview();1542 const error = new Error('error');1543 projectAnnotations.renderToDOM.mockImplementation(() => {1544 throw error;1545 });1546 mockChannel.emit.mockClear();1547 emitter.emit(SET_CURRENT_STORY, {1548 storyId: 'component-one--b',1549 viewMode: 'story',1550 });1551 await waitForSetCurrentStory();1552 await waitForRender();1553 expect(mockChannel.emit).toHaveBeenCalledWith(STORY_THREW_EXCEPTION, serializeError(error));1554 expect(preview.view.showErrorDisplay).toHaveBeenCalledWith(error);1555 });1556 it('renders error if the story calls showError', async () => {1557 document.location.search = '?id=component-one--a';1558 const preview = await createAndRenderPreview();1559 const error = { title: 'title', description: 'description' };1560 projectAnnotations.renderToDOM.mockImplementation((context) => context.showError(error));1561 mockChannel.emit.mockClear();1562 emitter.emit(SET_CURRENT_STORY, {1563 storyId: 'component-one--b',1564 viewMode: 'story',1565 });1566 await waitForSetCurrentStory();1567 await waitForRender();1568 expect(mockChannel.emit).toHaveBeenCalledWith(STORY_ERRORED, error);1569 expect(preview.view.showErrorDisplay).toHaveBeenCalledWith({1570 message: error.title,1571 stack: error.description,1572 });1573 });1574 it('renders exception if the story calls showException', async () => {1575 document.location.search = '?id=component-one--a';1576 const preview = await createAndRenderPreview();1577 const error = new Error('error');1578 projectAnnotations.renderToDOM.mockImplementation((context) =>1579 context.showException(error)1580 );1581 mockChannel.emit.mockClear();1582 emitter.emit(SET_CURRENT_STORY, {1583 storyId: 'component-one--b',1584 viewMode: 'story',1585 });1586 await waitForSetCurrentStory();1587 await waitForRender();1588 expect(mockChannel.emit).toHaveBeenCalledWith(STORY_THREW_EXCEPTION, serializeError(error));1589 expect(preview.view.showErrorDisplay).toHaveBeenCalledWith(error);1590 });1591 it('executes playFunction', async () => {1592 document.location.search = '?id=component-one--a';1593 await createAndRenderPreview();1594 mockChannel.emit.mockClear();1595 emitter.emit(SET_CURRENT_STORY, {1596 storyId: 'component-one--b',1597 viewMode: 'story',1598 });1599 await waitForSetCurrentStory();1600 await waitForRender();1601 expect(componentOneExports.b.play).toHaveBeenCalled();1602 });1603 it('emits STORY_RENDERED', async () => {1604 document.location.search = '?id=component-one--a';1605 await createAndRenderPreview();1606 mockChannel.emit.mockClear();1607 emitter.emit(SET_CURRENT_STORY, {1608 storyId: 'component-one--b',1609 viewMode: 'story',1610 });1611 await waitForSetCurrentStory();1612 await waitForRender();1613 expect(mockChannel.emit).toHaveBeenCalledWith(STORY_RENDERED, 'component-one--b');1614 });1615 it('retains any arg changes', async () => {1616 document.location.search = '?id=component-one--a';1617 const preview = await createAndRenderPreview();1618 mockChannel.emit.mockClear();1619 emitter.emit(UPDATE_STORY_ARGS, {1620 storyId: 'component-one--a',1621 updatedArgs: { foo: 'updated' },1622 });1623 await waitForRender();1624 expect(preview.storyStore.args.get('component-one--a')).toEqual({1625 foo: 'updated',1626 });1627 mockChannel.emit.mockClear();1628 emitter.emit(SET_CURRENT_STORY, {1629 storyId: 'component-one--b',1630 viewMode: 'story',1631 });1632 await waitForSetCurrentStory();1633 await waitForRender();1634 expect(preview.storyStore.args.get('component-one--a')).toEqual({1635 foo: 'updated',1636 });1637 mockChannel.emit.mockClear();1638 emitter.emit(SET_CURRENT_STORY, {1639 storyId: 'component-one--a',1640 viewMode: 'story',1641 });1642 await waitForSetCurrentStory();1643 await waitForRender();1644 expect(preview.storyStore.args.get('component-one--a')).toEqual({1645 foo: 'updated',1646 });1647 });1648 describe('while story is still rendering', () => {1649 it('stops initial story after loaders if running', async () => {1650 const [gate, openGate] = createGate();1651 componentOneExports.default.loaders[0].mockImplementationOnce(async () => gate);1652 document.location.search = '?id=component-one--a';1653 await new PreviewWeb().initialize({ importFn, getProjectAnnotations });1654 await waitForRenderPhase('loading');1655 emitter.emit(SET_CURRENT_STORY, {1656 storyId: 'component-one--b',1657 viewMode: 'story',1658 });1659 await waitForSetCurrentStory();1660 await waitForRender();1661 // Now let the loader resolve1662 openGate({ l: 8 });1663 await waitForRender();1664 // Story gets rendered with updated args1665 expect(projectAnnotations.renderToDOM).toHaveBeenCalledTimes(1);1666 expect(projectAnnotations.renderToDOM).toHaveBeenCalledWith(1667 expect.objectContaining({1668 forceRemount: true,1669 storyContext: expect.objectContaining({1670 id: 'component-one--b',1671 loaded: { l: 7 },1672 }),1673 }),1674 'story-element'1675 );1676 });1677 it('aborts render for initial story', async () => {1678 const [gate, openGate] = createGate();1679 document.location.search = '?id=component-one--a';1680 projectAnnotations.renderToDOM.mockImplementation(async () => gate);1681 await new PreviewWeb().initialize({ importFn, getProjectAnnotations });1682 await waitForRenderPhase('rendering');1683 mockChannel.emit.mockClear();1684 emitter.emit(SET_CURRENT_STORY, {1685 storyId: 'component-one--b',1686 viewMode: 'story',1687 });1688 await waitForSetCurrentStory();1689 // Now let the renderToDOM call resolve1690 openGate();1691 await waitForRenderPhase('aborted');1692 await waitForSetCurrentStory();1693 await waitForRenderPhase('rendering');1694 expect(projectAnnotations.renderToDOM).toHaveBeenCalledTimes(2);1695 await waitForRenderPhase('playing');1696 expect(componentOneExports.a.play).not.toHaveBeenCalled();1697 expect(componentOneExports.b.play).toHaveBeenCalled();1698 await waitForRenderPhase('completed');1699 expect(mockChannel.emit).not.toHaveBeenCalledWith(STORY_RENDERED, 'component-one--a');1700 expect(mockChannel.emit).toHaveBeenCalledWith(STORY_RENDERED, 'component-one--b');1701 await waitForQuiescence();1702 });1703 it('aborts play function for initial story', async () => {1704 const [gate, openGate] = createGate();1705 componentOneExports.a.play.mockImplementationOnce(async () => gate);1706 document.location.search = '?id=component-one--a';1707 await new PreviewWeb().initialize({ importFn, getProjectAnnotations });1708 await waitForRenderPhase('playing');1709 expect(projectAnnotations.renderToDOM).toHaveBeenCalledWith(1710 expect.objectContaining({1711 forceRemount: true,1712 storyContext: expect.objectContaining({1713 id: 'component-one--a',1714 loaded: { l: 7 },1715 }),1716 }),1717 'story-element'1718 );1719 mockChannel.emit.mockClear();1720 emitter.emit(SET_CURRENT_STORY, {1721 storyId: 'component-one--b',1722 viewMode: 'story',1723 });1724 await waitForSetCurrentStory();1725 // Now let the playFunction call resolve1726 openGate();1727 await waitForRenderPhase('aborted');1728 await waitForSetCurrentStory();1729 await waitForRenderPhase('rendering');1730 expect(mockChannel.emit).toHaveBeenCalledWith(STORY_CHANGED, 'component-one--b');1731 expect(projectAnnotations.renderToDOM).toHaveBeenCalledWith(1732 expect.objectContaining({1733 forceRemount: true,1734 storyContext: expect.objectContaining({1735 id: 'component-one--b',1736 loaded: { l: 7 },1737 }),1738 }),1739 'story-element'1740 );1741 await waitForRenderPhase('playing');1742 await waitForRenderPhase('completed');1743 expect(mockChannel.emit).toHaveBeenCalledWith(STORY_RENDERED, 'component-one--b');1744 // Final story rendered is not emitted for the first story1745 await waitForQuiescence();1746 expect(mockChannel.emit).not.toHaveBeenCalledWith(STORY_RENDERED, 'component-one--a');1747 });1748 it('reloads page if playFunction fails to abort in time', async () => {1749 const [gate] = createGate();1750 componentOneExports.a.play.mockImplementationOnce(async () => gate);1751 document.location.search = '?id=component-one--a';1752 await new PreviewWeb().initialize({ importFn, getProjectAnnotations });1753 await waitForRenderPhase('playing');1754 expect(projectAnnotations.renderToDOM).toHaveBeenCalledWith(1755 expect.objectContaining({1756 forceRemount: true,1757 storyContext: expect.objectContaining({1758 id: 'component-one--a',1759 loaded: { l: 7 },1760 }),1761 }),1762 'story-element'1763 );1764 mockChannel.emit.mockClear();1765 emitter.emit(SET_CURRENT_STORY, {1766 storyId: 'component-one--b',1767 viewMode: 'story',1768 });1769 // Wait three ticks without resolving the play function1770 await waitForSetCurrentStory();1771 await waitForSetCurrentStory();1772 await waitForSetCurrentStory();1773 expect(global.window.location.reload).toHaveBeenCalled();1774 expect(mockChannel.emit).not.toHaveBeenCalledWith(STORY_CHANGED, 'component-one--b');1775 expect(projectAnnotations.renderToDOM).not.toHaveBeenCalledWith(1776 expect.objectContaining({1777 storyContext: expect.objectContaining({ id: 'component-one--b' }),1778 }),1779 undefined1780 );1781 });1782 });1783 });1784 describe('when changing from story viewMode to docs', () => {1785 it('calls renderToDOMs teardown', async () => {1786 document.location.search = '?id=component-one--a';1787 await createAndRenderPreview();1788 emitter.emit(SET_CURRENT_STORY, {1789 storyId: 'component-one--docs',1790 viewMode: 'docs',1791 });1792 await waitForSetCurrentStory();1793 expect(teardownRenderToDOM).toHaveBeenCalled();1794 });1795 it('updates URL', async () => {1796 document.location.search = '?id=component-one--a';1797 await createAndRenderPreview();1798 emitter.emit(SET_CURRENT_STORY, {1799 storyId: 'component-one--docs',1800 viewMode: 'docs',1801 });1802 await waitForSetCurrentStory();1803 expect(history.replaceState).toHaveBeenCalledWith(1804 {},1805 '',1806 'pathname?id=component-one--docs&viewMode=docs'1807 );1808 });1809 it('renders preparing state', async () => {1810 document.location.search = '?id=component-one--a';1811 const preview = await createAndRenderPreview();1812 emitter.emit(SET_CURRENT_STORY, {1813 storyId: 'component-one--docs',1814 viewMode: 'docs',1815 });1816 await waitForSetCurrentStory();1817 expect(preview.view.showPreparingDocs).toHaveBeenCalled();1818 });1819 it('emits STORY_CHANGED', async () => {1820 document.location.search = '?id=component-one--a';1821 await createAndRenderPreview();1822 mockChannel.emit.mockClear();1823 emitter.emit(SET_CURRENT_STORY, {1824 storyId: 'component-one--docs',1825 viewMode: 'docs',1826 });1827 await waitForSetCurrentStory();1828 await waitForEvents([STORY_CHANGED]);1829 expect(mockChannel.emit).toHaveBeenCalledWith(STORY_CHANGED, 'component-one--docs');1830 });1831 it('calls view.prepareForDocs', async () => {1832 document.location.search = '?id=component-one--a';1833 const preview = await createAndRenderPreview();1834 mockChannel.emit.mockClear();1835 emitter.emit(SET_CURRENT_STORY, {1836 storyId: 'component-one--docs',1837 viewMode: 'docs',1838 });1839 await waitForSetCurrentStory();1840 await waitForRender();1841 expect(preview.view.prepareForDocs).toHaveBeenCalled();1842 });1843 it('render the docs container with the correct context, template render', async () => {1844 document.location.search = '?id=component-one--a';1845 await createAndRenderPreview();1846 mockChannel.emit.mockClear();1847 emitter.emit(SET_CURRENT_STORY, {1848 storyId: 'component-one--docs',1849 viewMode: 'docs',1850 });1851 await waitForSetCurrentStory();1852 await waitForRender();1853 expect(docsRenderer.render).toHaveBeenCalled();1854 expect(docsRenderer.render.mock.calls[0][0].storyById()).toMatchObject({1855 id: 'component-one--a',1856 });1857 });1858 it('emits DOCS_RENDERED', async () => {1859 document.location.search = '?id=component-one--a';1860 await createAndRenderPreview();1861 mockChannel.emit.mockClear();1862 emitter.emit(SET_CURRENT_STORY, {1863 storyId: 'component-one--docs',1864 viewMode: 'docs',1865 });1866 await waitForSetCurrentStory();1867 await waitForRender();1868 expect(mockChannel.emit).toHaveBeenCalledWith(DOCS_RENDERED, 'component-one--docs');1869 });1870 });1871 describe('when changing from docs viewMode to story', () => {1872 it('updates URL', async () => {1873 document.location.search = '?id=component-one--docs&viewMode=docs';1874 await createAndRenderPreview();1875 emitter.emit(SET_CURRENT_STORY, {1876 storyId: 'component-one--a',1877 viewMode: 'story',1878 });1879 await waitForSetCurrentStory();1880 expect(history.replaceState).toHaveBeenCalledWith(1881 {},1882 '',1883 'pathname?id=component-one--a&viewMode=story'1884 );1885 });1886 it('unmounts docs', async () => {1887 document.location.search = '?id=component-one--docs&viewMode=docs';1888 await createAndRenderPreview();1889 mockChannel.emit.mockClear();1890 emitter.emit(SET_CURRENT_STORY, {1891 storyId: 'component-one--a',1892 viewMode: 'story',1893 });1894 await waitForSetCurrentStory();1895 await waitForRender();1896 expect(docsRenderer.unmount).toHaveBeenCalled();1897 });1898 // NOTE: I am not sure this entirely makes sense but this is the behaviour from 6.31899 it('emits STORY_CHANGED', async () => {1900 document.location.search = '?id=component-one--docs&viewMode=docs';1901 await createAndRenderPreview();1902 mockChannel.emit.mockClear();1903 emitter.emit(SET_CURRENT_STORY, {1904 storyId: 'component-one--a',1905 viewMode: 'story',1906 });1907 await waitForSetCurrentStory();1908 await waitForEvents([STORY_CHANGED]);1909 expect(mockChannel.emit).toHaveBeenCalledWith(STORY_CHANGED, 'component-one--a');1910 });1911 it('calls view.prepareForStory', async () => {1912 document.location.search = '?id=component-one--docs&viewMode=docs';1913 const preview = await createAndRenderPreview();1914 mockChannel.emit.mockClear();1915 emitter.emit(SET_CURRENT_STORY, {1916 storyId: 'component-one--a',1917 viewMode: 'story',1918 });1919 await waitForSetCurrentStory();1920 await waitForRender();1921 expect(preview.view.prepareForStory).toHaveBeenCalledWith(1922 expect.objectContaining({1923 id: 'component-one--a',1924 })1925 );1926 });1927 it('emits STORY_PREPARED', async () => {1928 document.location.search = '?id=component-one--docs&viewMode=docs';1929 await createAndRenderPreview();1930 mockChannel.emit.mockClear();1931 emitter.emit(SET_CURRENT_STORY, {1932 storyId: 'component-one--a',1933 viewMode: 'story',1934 });1935 await waitForSetCurrentStory();1936 await waitForEvents([STORY_PREPARED]);1937 expect(mockChannel.emit).toHaveBeenCalledWith(STORY_PREPARED, {1938 id: 'component-one--a',1939 parameters: {1940 __isArgsStory: false,1941 docs: expect.any(Object),1942 fileName: './src/ComponentOne.stories.js',1943 },1944 initialArgs: { foo: 'a' },1945 argTypes: { foo: { name: 'foo', type: { name: 'string' } } },1946 args: { foo: 'a' },1947 });1948 });1949 it('applies loaders with story context', async () => {1950 document.location.search = '?id=component-one--docs&viewMode=docs';1951 await createAndRenderPreview();1952 mockChannel.emit.mockClear();1953 emitter.emit(SET_CURRENT_STORY, {1954 storyId: 'component-one--a',1955 viewMode: 'story',1956 });1957 await waitForSetCurrentStory();1958 await waitForRender();1959 expect(componentOneExports.default.loaders[0]).toHaveBeenCalledWith(1960 expect.objectContaining({1961 id: 'component-one--a',1962 parameters: {1963 __isArgsStory: false,1964 docs: expect.any(Object),1965 fileName: './src/ComponentOne.stories.js',1966 },1967 initialArgs: { foo: 'a' },1968 argTypes: { foo: { name: 'foo', type: { name: 'string' } } },1969 args: { foo: 'a' },1970 })1971 );1972 });1973 it('passes loaded context to renderToDOM', async () => {1974 document.location.search = '?id=component-one--docs&viewMode=docs';1975 await createAndRenderPreview();1976 mockChannel.emit.mockClear();1977 emitter.emit(SET_CURRENT_STORY, {1978 storyId: 'component-one--a',1979 viewMode: 'story',1980 });1981 await waitForSetCurrentStory();1982 await waitForRender();1983 expect(projectAnnotations.renderToDOM).toHaveBeenCalledWith(1984 expect.objectContaining({1985 forceRemount: true,1986 storyContext: expect.objectContaining({1987 id: 'component-one--a',1988 parameters: {1989 __isArgsStory: false,1990 docs: expect.any(Object),1991 fileName: './src/ComponentOne.stories.js',1992 },1993 globals: { a: 'b' },1994 initialArgs: { foo: 'a' },1995 argTypes: { foo: { name: 'foo', type: { name: 'string' } } },1996 args: { foo: 'a' },1997 loaded: { l: 7 },1998 }),1999 }),2000 'story-element'2001 );2002 });2003 it('renders exception if renderToDOM throws', async () => {2004 document.location.search = '?id=component-one--docs&viewMode=docs';2005 const preview = await createAndRenderPreview();2006 const error = new Error('error');2007 projectAnnotations.renderToDOM.mockImplementation(() => {2008 throw error;2009 });2010 mockChannel.emit.mockClear();2011 emitter.emit(SET_CURRENT_STORY, {2012 storyId: 'component-one--a',2013 viewMode: 'story',2014 });2015 await waitForSetCurrentStory();2016 await waitForRender();2017 expect(mockChannel.emit).toHaveBeenCalledWith(STORY_THREW_EXCEPTION, serializeError(error));2018 expect(preview.view.showErrorDisplay).toHaveBeenCalledWith(error);2019 });2020 it('renders error if the story calls showError', async () => {2021 const error = { title: 'title', description: 'description' };2022 projectAnnotations.renderToDOM.mockImplementation((context) => context.showError(error));2023 document.location.search = '?id=component-one--docs&viewMode=docs';2024 const preview = await createAndRenderPreview();2025 mockChannel.emit.mockClear();2026 emitter.emit(SET_CURRENT_STORY, {2027 storyId: 'component-one--a',2028 viewMode: 'story',2029 });2030 await waitForSetCurrentStory();2031 await waitForRender();2032 expect(mockChannel.emit).toHaveBeenCalledWith(STORY_ERRORED, error);2033 expect(preview.view.showErrorDisplay).toHaveBeenCalledWith({2034 message: error.title,2035 stack: error.description,2036 });2037 });2038 it('renders exception if the story calls showException', async () => {2039 const error = new Error('error');2040 projectAnnotations.renderToDOM.mockImplementation((context) =>2041 context.showException(error)2042 );2043 document.location.search = '?id=component-one--docs&viewMode=docs';2044 const preview = await createAndRenderPreview();2045 mockChannel.emit.mockClear();2046 emitter.emit(SET_CURRENT_STORY, {2047 storyId: 'component-one--a',2048 viewMode: 'story',2049 });2050 await waitForSetCurrentStory();2051 await waitForRender();2052 expect(mockChannel.emit).toHaveBeenCalledWith(STORY_THREW_EXCEPTION, serializeError(error));2053 expect(preview.view.showErrorDisplay).toHaveBeenCalledWith(error);2054 });2055 it('executes playFunction', async () => {2056 document.location.search = '?id=component-one--docs&viewMode=docs';2057 await createAndRenderPreview();2058 mockChannel.emit.mockClear();2059 emitter.emit(SET_CURRENT_STORY, {2060 storyId: 'component-one--a',2061 viewMode: 'story',2062 });2063 await waitForSetCurrentStory();2064 await waitForRender();2065 expect(componentOneExports.a.play).toHaveBeenCalled();2066 });2067 it('emits STORY_RENDERED', async () => {2068 document.location.search = '?id=component-one--docs&viewMode=docs';2069 await createAndRenderPreview();2070 mockChannel.emit.mockClear();2071 emitter.emit(SET_CURRENT_STORY, {2072 storyId: 'component-one--a',2073 viewMode: 'story',2074 });2075 await waitForSetCurrentStory();2076 await waitForRender();2077 expect(mockChannel.emit).toHaveBeenCalledWith(STORY_RENDERED, 'component-one--a');2078 });2079 });2080 });2081 describe('onStoriesChanged', () => {2082 describe('if stories.json endpoint 500s initially', () => {2083 it('recovers and renders the story', async () => {2084 document.location.search = '?id=component-one--a';2085 const err = new Error('sort error');2086 mockFetchResult = { status: 500, text: async () => err.toString() };2087 const preview = new PreviewWeb();2088 await expect(preview.initialize({ importFn, getProjectAnnotations })).rejects.toThrow(2089 'sort error'2090 );2091 expect(preview.view.showErrorDisplay).toHaveBeenCalled();2092 expect(mockChannel.emit).toHaveBeenCalledWith(CONFIG_ERROR, expect.any(Error));2093 mockChannel.emit.mockClear();2094 mockFetchResult = { status: 200, json: mockStoryIndex, text: () => 'error text' };2095 preview.onStoryIndexChanged();2096 await waitForRender();2097 expect(mockChannel.emit).toHaveBeenCalledWith(STORY_RENDERED, 'component-one--a');2098 });2099 it('sets story args from the URL', async () => {2100 document.location.search = '?id=component-one--a&args=foo:url';2101 const err = new Error('sort error');2102 mockFetchResult = { status: 500, text: async () => err.toString() };2103 const preview = new PreviewWeb();2104 await expect(preview.initialize({ importFn, getProjectAnnotations })).rejects.toThrow(2105 'sort error'2106 );2107 expect(preview.view.showErrorDisplay).toHaveBeenCalled();2108 expect(mockChannel.emit).toHaveBeenCalledWith(CONFIG_ERROR, expect.any(Error));2109 mockChannel.emit.mockClear();2110 mockFetchResult = { status: 200, json: mockStoryIndex, text: () => 'error text' };2111 preview.onStoryIndexChanged();2112 await waitForRender();2113 expect(preview.storyStore.args.get('component-one--a')).toEqual({2114 foo: 'url',2115 });2116 });2117 });2118 describe('when the current story changes', () => {2119 const newComponentOneExports = merge({}, componentOneExports, {2120 a: { args: { foo: 'edited' } },2121 });2122 const newImportFn = jest.fn(async (path) => {2123 return path === './src/ComponentOne.stories.js'2124 ? newComponentOneExports2125 : componentTwoExports;2126 });2127 it('calls renderToDOMs teardown', async () => {2128 document.location.search = '?id=component-one--a';2129 const preview = await createAndRenderPreview();2130 mockChannel.emit.mockClear();2131 preview.onStoriesChanged({ importFn: newImportFn });2132 await waitForRender();2133 expect(teardownRenderToDOM).toHaveBeenCalled();2134 });2135 it('does not emit STORY_UNCHANGED', async () => {2136 document.location.search = '?id=component-one--a';2137 const preview = await createAndRenderPreview();2138 mockChannel.emit.mockClear();2139 preview.onStoriesChanged({ importFn: newImportFn });2140 await waitForRender();2141 expect(mockChannel.emit).not.toHaveBeenCalledWith(STORY_UNCHANGED, 'component-one--a');2142 });2143 it('does not emit STORY_CHANGED', async () => {2144 document.location.search = '?id=component-one--a';2145 const preview = await createAndRenderPreview();2146 mockChannel.emit.mockClear();2147 preview.onStoriesChanged({ importFn: newImportFn });2148 await waitForRender();2149 expect(mockChannel.emit).not.toHaveBeenCalledWith(STORY_CHANGED, 'component-one--a');2150 });2151 it('emits STORY_PREPARED with new annotations', async () => {2152 document.location.search = '?id=component-one--a';2153 const preview = await createAndRenderPreview();2154 mockChannel.emit.mockClear();2155 preview.onStoriesChanged({ importFn: newImportFn });2156 await waitForRender();2157 expect(mockChannel.emit).toHaveBeenCalledWith(STORY_PREPARED, {2158 id: 'component-one--a',2159 parameters: {2160 __isArgsStory: false,2161 docs: expect.any(Object),2162 fileName: './src/ComponentOne.stories.js',2163 },2164 initialArgs: { foo: 'edited' },2165 argTypes: { foo: { name: 'foo', type: { name: 'string' } } },2166 args: { foo: 'edited' },2167 });2168 });2169 it('emits STORY_ARGS_UPDATED with new args', async () => {2170 document.location.search = '?id=component-one--a';2171 const preview = await createAndRenderPreview();2172 mockChannel.emit.mockClear();2173 preview.onStoriesChanged({ importFn: newImportFn });2174 await waitForRender();2175 expect(mockChannel.emit).toHaveBeenCalledWith(STORY_ARGS_UPDATED, {2176 storyId: 'component-one--a',2177 args: { foo: 'edited' },2178 });2179 });2180 it('applies loaders with story context', async () => {2181 document.location.search = '?id=component-one--a';2182 const preview = await createAndRenderPreview();2183 mockChannel.emit.mockClear();2184 componentOneExports.default.loaders[0].mockClear();2185 preview.onStoriesChanged({ importFn: newImportFn });2186 await waitForRender();2187 expect(componentOneExports.default.loaders[0]).toHaveBeenCalledWith(2188 expect.objectContaining({2189 id: 'component-one--a',2190 parameters: {2191 __isArgsStory: false,2192 docs: expect.any(Object),2193 fileName: './src/ComponentOne.stories.js',2194 },2195 initialArgs: { foo: 'edited' },2196 argTypes: { foo: { name: 'foo', type: { name: 'string' } } },2197 args: { foo: 'edited' },2198 })2199 );2200 });2201 it('passes loaded context to renderToDOM', async () => {2202 document.location.search = '?id=component-one--a';2203 const preview = await createAndRenderPreview();2204 mockChannel.emit.mockClear();2205 projectAnnotations.renderToDOM.mockClear();2206 preview.onStoriesChanged({ importFn: newImportFn });2207 await waitForRender();2208 expect(projectAnnotations.renderToDOM).toHaveBeenCalledWith(2209 expect.objectContaining({2210 forceRemount: true,2211 storyContext: expect.objectContaining({2212 id: 'component-one--a',2213 parameters: {2214 __isArgsStory: false,2215 docs: expect.any(Object),2216 fileName: './src/ComponentOne.stories.js',2217 },2218 globals: { a: 'b' },2219 initialArgs: { foo: 'edited' },2220 argTypes: { foo: { name: 'foo', type: { name: 'string' } } },2221 args: { foo: 'edited' },2222 loaded: { l: 7 },2223 }),2224 }),2225 'story-element'2226 );2227 });2228 it('retains the same delta to the args', async () => {2229 document.location.search = '?id=component-one--a';2230 const preview = await createAndRenderPreview();2231 mockChannel.emit.mockClear();2232 emitter.emit(UPDATE_STORY_ARGS, {2233 storyId: 'component-one--a',2234 updatedArgs: { foo: 'updated' },2235 });2236 await waitForRender();2237 mockChannel.emit.mockClear();2238 projectAnnotations.renderToDOM.mockClear();2239 preview.onStoriesChanged({ importFn: newImportFn });2240 await waitForRender();2241 expect(projectAnnotations.renderToDOM).toHaveBeenCalledWith(2242 expect.objectContaining({2243 forceRemount: true,2244 storyContext: expect.objectContaining({2245 id: 'component-one--a',2246 args: { foo: 'updated' },2247 }),2248 }),2249 'story-element'2250 );2251 });2252 it('renders exception if renderToDOM throws', async () => {2253 document.location.search = '?id=component-one--a';2254 const preview = await createAndRenderPreview();2255 const error = new Error('error');2256 projectAnnotations.renderToDOM.mockImplementation(() => {2257 throw error;2258 });2259 mockChannel.emit.mockClear();2260 preview.onStoriesChanged({ importFn: newImportFn });2261 await waitForRender();2262 expect(mockChannel.emit).toHaveBeenCalledWith(STORY_THREW_EXCEPTION, serializeError(error));2263 expect(preview.view.showErrorDisplay).toHaveBeenCalledWith(error);2264 });2265 it('renders error if the story calls showError', async () => {2266 document.location.search = '?id=component-one--a';2267 const preview = await createAndRenderPreview();2268 const error = { title: 'title', description: 'description' };2269 projectAnnotations.renderToDOM.mockImplementation((context) => context.showError(error));2270 mockChannel.emit.mockClear();2271 preview.onStoriesChanged({ importFn: newImportFn });2272 await waitForRender();2273 expect(mockChannel.emit).toHaveBeenCalledWith(STORY_ERRORED, error);2274 expect(preview.view.showErrorDisplay).toHaveBeenCalledWith({2275 message: error.title,2276 stack: error.description,2277 });2278 });2279 it('renders exception if the story calls showException', async () => {2280 document.location.search = '?id=component-one--a';2281 const preview = await createAndRenderPreview();2282 const error = new Error('error');2283 projectAnnotations.renderToDOM.mockImplementation((context) =>2284 context.showException(error)2285 );2286 mockChannel.emit.mockClear();2287 preview.onStoriesChanged({ importFn: newImportFn });2288 await waitForRender();2289 expect(mockChannel.emit).toHaveBeenCalledWith(STORY_THREW_EXCEPTION, serializeError(error));2290 expect(preview.view.showErrorDisplay).toHaveBeenCalledWith(error);2291 });2292 it('executes playFunction', async () => {2293 document.location.search = '?id=component-one--a';2294 const preview = await createAndRenderPreview();2295 mockChannel.emit.mockClear();2296 componentOneExports.a.play.mockClear();2297 preview.onStoriesChanged({ importFn: newImportFn });2298 await waitForRender();2299 expect(componentOneExports.a.play).toHaveBeenCalled();2300 });2301 it('emits STORY_RENDERED', async () => {2302 document.location.search = '?id=component-one--a';2303 const preview = await createAndRenderPreview();2304 mockChannel.emit.mockClear();2305 preview.onStoriesChanged({ importFn: newImportFn });2306 await waitForRender();2307 expect(mockChannel.emit).toHaveBeenCalledWith(STORY_RENDERED, 'component-one--a');2308 });2309 });2310 describe('when the current story changes importPath', () => {2311 const newImportFn = jest.fn(async (path) => ({ ...componentOneExports }));2312 const newStoryIndex = {2313 v: 4,2314 entries: {2315 ...storyIndex.entries,2316 'component-one--a': {2317 ...storyIndex.entries['component-one--a'],2318 importPath: './src/ComponentOne-new.stories.js',2319 },2320 },2321 };2322 beforeEach(() => {2323 newImportFn.mockClear();2324 });2325 it('re-imports the component', async () => {2326 document.location.search = '?id=component-one--a';2327 const preview = await createAndRenderPreview();2328 mockChannel.emit.mockClear();2329 preview.onStoriesChanged({ importFn: newImportFn, storyIndex: newStoryIndex });2330 await waitForRender();2331 expect(newImportFn).toHaveBeenCalledWith('./src/ComponentOne-new.stories.js');2332 });2333 describe('if it was previously rendered', () => {2334 beforeEach(() => {2335 jest.useFakeTimers();2336 });2337 afterEach(() => {2338 jest.useRealTimers();2339 });2340 it('is reloaded when it is re-selected', async () => {2341 document.location.search = '?id=component-one--a';2342 const preview = await createAndRenderPreview();2343 mockChannel.emit.mockClear();2344 emitter.emit(SET_CURRENT_STORY, {2345 storyId: 'component-one--b',2346 viewMode: 'story',2347 });2348 await waitForSetCurrentStory();2349 await waitForRender();2350 preview.onStoriesChanged({ importFn: newImportFn, storyIndex: newStoryIndex });2351 mockChannel.emit.mockClear();2352 emitter.emit(SET_CURRENT_STORY, {2353 storyId: 'component-one--a',2354 viewMode: 'story',2355 });2356 await waitForSetCurrentStory();2357 await waitForRender();2358 expect(newImportFn).toHaveBeenCalledWith('./src/ComponentOne-new.stories.js');2359 });2360 });2361 });2362 describe('when the current story has not changed', () => {2363 const newComponentTwoExports = { ...componentTwoExports };2364 const newImportFn = jest.fn(async (path) => {2365 return path === './src/ComponentOne.stories.js'2366 ? componentOneExports2367 : newComponentTwoExports;2368 });2369 it('does NOT call renderToDOMs teardown', async () => {2370 document.location.search = '?id=component-one--a';2371 const preview = await createAndRenderPreview();2372 mockChannel.emit.mockClear();2373 preview.onStoriesChanged({ importFn: newImportFn });2374 await waitForEvents([STORY_UNCHANGED]);2375 expect(teardownRenderToDOM).not.toHaveBeenCalled();2376 });2377 it('emits STORY_UNCHANGED', async () => {2378 document.location.search = '?id=component-one--a';2379 const preview = await createAndRenderPreview();2380 mockChannel.emit.mockClear();2381 preview.onStoriesChanged({ importFn: newImportFn });2382 await waitForEvents([STORY_UNCHANGED]);2383 expect(mockChannel.emit).toHaveBeenCalledWith(STORY_UNCHANGED, 'component-one--a');2384 expect(mockChannel.emit).not.toHaveBeenCalledWith(STORY_CHANGED, 'component-one--a');2385 });2386 it('clears preparing state', async () => {2387 document.location.search = '?id=component-one--a';2388 const preview = await createAndRenderPreview();2389 (preview.view.showMain as jest.Mock).mockClear();2390 mockChannel.emit.mockClear();2391 preview.onStoriesChanged({ importFn: newImportFn });2392 await waitForEvents([STORY_UNCHANGED]);2393 expect(preview.view.showMain).toHaveBeenCalled();2394 });2395 it('does not re-render the story', async () => {2396 document.location.search = '?id=component-one--a';2397 const preview = await createAndRenderPreview();2398 mockChannel.emit.mockClear();2399 projectAnnotations.renderToDOM.mockClear();2400 preview.onStoriesChanged({ importFn: newImportFn });2401 await waitForQuiescence();2402 expect(projectAnnotations.renderToDOM).not.toHaveBeenCalled();2403 expect(mockChannel.emit).not.toHaveBeenCalledWith(STORY_RENDERED, 'component-one--a');2404 });2405 });2406 describe('when another (not current) story changes', () => {2407 beforeEach(() => {2408 jest.useFakeTimers();2409 });2410 afterEach(() => {2411 jest.useRealTimers();2412 });2413 const newComponentOneExports = merge({}, componentOneExports, {2414 a: { args: { bar: 'edited' }, argTypes: { bar: { type: { name: 'string' } } } },2415 });2416 const newImportFn = jest.fn(async (path) => {2417 return path === './src/ComponentOne.stories.js'2418 ? newComponentOneExports2419 : componentTwoExports;2420 });2421 it('retains the same delta to the args', async () => {2422 // Start at Story A2423 document.location.search = '?id=component-one--a';2424 const preview = await createAndRenderPreview();2425 // Change A's args2426 mockChannel.emit.mockClear();2427 emitter.emit(UPDATE_STORY_ARGS, {2428 storyId: 'component-one--a',2429 updatedArgs: { foo: 'updated' },2430 });2431 await waitForRender();2432 // Change to story B2433 mockChannel.emit.mockClear();2434 emitter.emit(SET_CURRENT_STORY, {2435 storyId: 'component-one--b',2436 viewMode: 'story',2437 });2438 await waitForSetCurrentStory();2439 await waitForRender();2440 expect(preview.storyStore.args.get('component-one--a')).toEqual({2441 foo: 'updated',2442 });2443 // Update story A's args via HMR2444 mockChannel.emit.mockClear();2445 projectAnnotations.renderToDOM.mockClear();2446 preview.onStoriesChanged({ importFn: newImportFn });2447 await waitForRender();2448 // Change back to Story A2449 mockChannel.emit.mockClear();2450 emitter.emit(SET_CURRENT_STORY, {2451 storyId: 'component-one--a',2452 viewMode: 'story',2453 });2454 await waitForSetCurrentStory();2455 await waitForRender();2456 expect(preview.storyStore.args.get('component-one--a')).toEqual({2457 foo: 'updated',2458 bar: 'edited',2459 });2460 expect(projectAnnotations.renderToDOM).toHaveBeenCalledWith(2461 expect.objectContaining({2462 forceRemount: true,2463 storyContext: expect.objectContaining({2464 id: 'component-one--a',2465 args: { foo: 'updated', bar: 'edited' },2466 }),2467 }),2468 'story-element'2469 );2470 });2471 });2472 describe('if the story no longer exists', () => {2473 const { a, ...componentOneExportsWithoutA } = componentOneExports;2474 const newImportFn = jest.fn(async (path) => {2475 return path === './src/ComponentOne.stories.js'2476 ? componentOneExportsWithoutA2477 : componentTwoExports;2478 });2479 const newStoryIndex = {2480 v: 4,2481 entries: {2482 'component-one--b': storyIndex.entries['component-one--b'],2483 },2484 };2485 it('calls renderToDOMs teardown', async () => {2486 document.location.search = '?id=component-one--a';2487 const preview = await createAndRenderPreview();2488 mockChannel.emit.mockClear();2489 preview.onStoriesChanged({ importFn: newImportFn, storyIndex: newStoryIndex });2490 await waitForEvents([STORY_MISSING]);2491 expect(teardownRenderToDOM).toHaveBeenCalled();2492 });2493 it('renders loading error', async () => {2494 document.location.search = '?id=component-one--a';2495 const preview = await createAndRenderPreview();2496 mockChannel.emit.mockClear();2497 preview.onStoriesChanged({ importFn: newImportFn, storyIndex: newStoryIndex });2498 await waitForEvents([STORY_MISSING]);2499 expect(preview.view.showErrorDisplay).toHaveBeenCalled();2500 expect(mockChannel.emit).toHaveBeenCalledWith(STORY_MISSING, 'component-one--a');2501 });2502 it('does not re-render the story', async () => {2503 document.location.search = '?id=component-one--a';2504 const preview = await createAndRenderPreview();2505 mockChannel.emit.mockClear();2506 projectAnnotations.renderToDOM.mockClear();2507 preview.onStoriesChanged({ importFn: newImportFn, storyIndex: newStoryIndex });2508 await waitForQuiescence();2509 expect(projectAnnotations.renderToDOM).not.toHaveBeenCalled();2510 expect(mockChannel.emit).not.toHaveBeenCalledWith(STORY_RENDERED, 'component-one--a');2511 });2512 it('re-renders the story if it is readded', async () => {2513 document.location.search = '?id=component-one--a';2514 const preview = await createAndRenderPreview();2515 mockChannel.emit.mockClear();2516 preview.onStoriesChanged({ importFn: newImportFn, storyIndex: newStoryIndex });2517 await waitForEvents([STORY_MISSING]);2518 mockChannel.emit.mockClear();2519 preview.onStoriesChanged({ importFn, storyIndex });2520 await waitForRender();2521 expect(mockChannel.emit).toHaveBeenCalledWith(STORY_RENDERED, 'component-one--a');2522 });2523 });2524 describe('when a standalone docs file changes', () => {2525 const newStandaloneDocsExports = { default: jest.fn() };2526 const newImportFn = jest.fn(async (path) => {2527 return path === './src/Introduction.mdx' ? newStandaloneDocsExports : importFn(path);2528 });2529 it('renders with the generated docs parameters', async () => {2530 document.location.search = '?id=introduction--docs&viewMode=docs';2531 const preview = await createAndRenderPreview();2532 mockChannel.emit.mockClear();2533 docsRenderer.render.mockClear();2534 preview.onStoriesChanged({ importFn: newImportFn });2535 await waitForRender();2536 expect(docsRenderer.render).toHaveBeenCalledWith(2537 expect.any(Object),2538 expect.objectContaining({2539 page: newStandaloneDocsExports.default,2540 renderer: projectAnnotations.parameters.docs.renderer,2541 }),2542 'docs-element',2543 expect.any(Function)2544 );2545 });2546 it('emits DOCS_RENDERED', async () => {2547 document.location.search = '?id=introduction--docs&viewMode=docs';2548 const preview = await createAndRenderPreview();2549 mockChannel.emit.mockClear();2550 preview.onStoriesChanged({ importFn: newImportFn });2551 await waitForRender();2552 expect(mockChannel.emit).toHaveBeenCalledWith(DOCS_RENDERED, 'introduction--docs');2553 });2554 });2555 });2556 describe('onGetProjectAnnotationsChanged', () => {2557 describe('if initial getProjectAnnotations threw', () => {2558 it('recovers and renders the story', async () => {2559 document.location.search = '?id=component-one--a';2560 const err = new Error('meta error');2561 const preview = new PreviewWeb();2562 await expect(2563 preview.initialize({2564 importFn,2565 getProjectAnnotations: () => {2566 throw err;2567 },2568 })2569 ).rejects.toThrow(err);2570 preview.onGetProjectAnnotationsChanged({ getProjectAnnotations });2571 await waitForRender();2572 expect(mockChannel.emit).toHaveBeenCalledWith(STORY_RENDERED, 'component-one--a');2573 });2574 it('sets globals from the URL', async () => {2575 document.location.search = '?id=*&globals=a:c';2576 const err = new Error('meta error');2577 const preview = new PreviewWeb();2578 await expect(2579 preview.initialize({2580 importFn,2581 getProjectAnnotations: () => {2582 throw err;2583 },2584 })2585 ).rejects.toThrow(err);2586 preview.onGetProjectAnnotationsChanged({ getProjectAnnotations });2587 await waitForRender();2588 expect(preview.storyStore.globals!.get()).toEqual({ a: 'c' });2589 });2590 });2591 it('shows an error the new value throws', async () => {2592 document.location.search = '?id=component-one--a';2593 const preview = await createAndRenderPreview();2594 mockChannel.emit.mockClear();2595 const err = new Error('error getting meta');2596 await expect(2597 preview.onGetProjectAnnotationsChanged({2598 getProjectAnnotations: () => {2599 throw err;2600 },2601 })2602 ).rejects.toThrow(err);2603 expect(preview.view.showErrorDisplay).toHaveBeenCalled();2604 expect(mockChannel.emit).toHaveBeenCalledWith(CONFIG_ERROR, err);2605 });2606 const newGlobalDecorator = jest.fn((s) => s());2607 const newGetProjectAnnotations = () => {2608 return {2609 ...projectAnnotations,2610 args: { global: 'added' },2611 globals: { a: 'edited' },2612 decorators: [newGlobalDecorator],2613 };2614 };2615 it('updates globals to their new values', async () => {2616 document.location.search = '?id=component-one--a';2617 const preview = await createAndRenderPreview();2618 mockChannel.emit.mockClear();2619 preview.onGetProjectAnnotationsChanged({ getProjectAnnotations: newGetProjectAnnotations });2620 await waitForRender();2621 expect(preview.storyStore.globals!.get()).toEqual({ a: 'edited' });2622 });2623 it('emits SET_GLOBALS with new values', async () => {2624 document.location.search = '?id=component-one--a';2625 const preview = await createAndRenderPreview();2626 mockChannel.emit.mockClear();2627 preview.onGetProjectAnnotationsChanged({ getProjectAnnotations: newGetProjectAnnotations });2628 await waitForRender();2629 await waitForEvents([SET_GLOBALS]);2630 expect(mockChannel.emit).toHaveBeenCalledWith(SET_GLOBALS, {2631 globals: { a: 'edited' },2632 globalTypes: {},2633 });2634 });2635 it('updates args to their new values', async () => {2636 document.location.search = '?id=component-one--a';2637 const preview = await createAndRenderPreview();2638 mockChannel.emit.mockClear();2639 preview.onGetProjectAnnotationsChanged({ getProjectAnnotations: newGetProjectAnnotations });2640 await waitForRender();2641 expect(preview.storyStore.args.get('component-one--a')).toEqual({2642 foo: 'a',2643 global: 'added',2644 });2645 });2646 it('emits SET_STORY_ARGS with new values', async () => {2647 document.location.search = '?id=component-one--a';2648 const preview = await createAndRenderPreview();2649 mockChannel.emit.mockClear();2650 preview.onGetProjectAnnotationsChanged({ getProjectAnnotations: newGetProjectAnnotations });2651 await waitForRender();2652 expect(mockChannel.emit).toHaveBeenCalledWith(STORY_ARGS_UPDATED, {2653 storyId: 'component-one--a',2654 args: { foo: 'a', global: 'added' },2655 });2656 });2657 it('calls renderToDOMs teardown', async () => {2658 document.location.search = '?id=component-one--a';2659 const preview = await createAndRenderPreview();2660 projectAnnotations.renderToDOM.mockClear();2661 mockChannel.emit.mockClear();2662 preview.onGetProjectAnnotationsChanged({ getProjectAnnotations: newGetProjectAnnotations });2663 await waitForRender();2664 expect(teardownRenderToDOM).toHaveBeenCalled();2665 });2666 it('rerenders the current story with new global meta-generated context', async () => {2667 document.location.search = '?id=component-one--a';2668 const preview = await createAndRenderPreview();2669 projectAnnotations.renderToDOM.mockClear();2670 mockChannel.emit.mockClear();2671 preview.onGetProjectAnnotationsChanged({ getProjectAnnotations: newGetProjectAnnotations });2672 await waitForRender();2673 expect(projectAnnotations.renderToDOM).toHaveBeenCalledWith(2674 expect.objectContaining({2675 storyContext: expect.objectContaining({2676 args: { foo: 'a', global: 'added' },2677 globals: { a: 'edited' },2678 }),2679 }),2680 'story-element'2681 );2682 });2683 });2684 describe('onKeydown', () => {2685 it('emits PREVIEW_KEYDOWN for regular elements', async () => {2686 document.location.search = '?id=component-one--docs&viewMode=docs';2687 const preview = await createAndRenderPreview();2688 preview.onKeydown({2689 target: { tagName: 'div', getAttribute: jest.fn().mockReturnValue(null) },2690 } as any);2691 expect(mockChannel.emit).toHaveBeenCalledWith(PREVIEW_KEYDOWN, expect.objectContaining({}));2692 });2693 it('does not emit PREVIEW_KEYDOWN for input elements', async () => {2694 document.location.search = '?id=component-one--docs&viewMode=docs';2695 const preview = await createAndRenderPreview();2696 preview.onKeydown({2697 target: { tagName: 'input', getAttribute: jest.fn().mockReturnValue(null) },2698 } as any);2699 expect(mockChannel.emit).not.toHaveBeenCalledWith(2700 PREVIEW_KEYDOWN,2701 expect.objectContaining({})2702 );2703 });2704 });2705 describe('extract', () => {2706 // NOTE: if you are using storyStoreV6, and your `preview.js` throws, we do not currently2707 // detect it (as we do not wrap the import of `preview.js` in a `try/catch`). The net effect2708 // of that is that the `PreviewWeb`/`StoryStore` end up in an uninitalized state.2709 it('throws an error if the preview is uninitialized', async () => {2710 const preview = new PreviewWeb();2711 await expect(preview.extract()).rejects.toThrow(/Failed to initialize/);2712 });2713 it('throws an error if preview.js throws', async () => {2714 const err = new Error('meta error');2715 const preview = new PreviewWeb();2716 await expect(2717 preview.initialize({2718 importFn,2719 getProjectAnnotations: () => {2720 throw err;2721 },2722 })2723 ).rejects.toThrow(err);2724 await expect(preview.extract()).rejects.toThrow(err);2725 });2726 it('shows an error if the stories.json endpoint 500s', async () => {2727 const err = new Error('sort error');2728 mockFetchResult = { status: 500, text: async () => err.toString() };2729 const preview = new PreviewWeb();2730 await expect(preview.initialize({ importFn, getProjectAnnotations })).rejects.toThrow(2731 'sort error'2732 );2733 await expect(preview.extract()).rejects.toThrow('sort error');2734 });2735 it('waits for stories to be cached', async () => {2736 const [gate, openGate] = createGate();2737 const gatedImportFn = async (path) => {2738 await gate;2739 return importFn(path);2740 };2741 const preview = await createAndRenderPreview({ importFn: gatedImportFn });2742 let extracted = false;2743 preview.extract().then(() => {2744 extracted = true;2745 });2746 expect(extracted).toBe(false);2747 openGate();2748 await new Promise((r) => setTimeout(r, 0)); // Let the promise resolve2749 expect(extracted).toBe(true);2750 expect(await preview.extract()).toMatchInlineSnapshot(`2751 Object {2752 "component-one--a": Object {2753 "argTypes": Object {2754 "foo": Object {2755 "name": "foo",2756 "type": Object {2757 "name": "string",2758 },2759 },2760 },2761 "args": Object {2762 "foo": "a",2763 },2764 "component": undefined,2765 "componentId": "component-one",2766 "id": "component-one--a",2767 "initialArgs": Object {2768 "foo": "a",2769 },2770 "kind": "Component One",2771 "name": "A",2772 "parameters": Object {2773 "__isArgsStory": false,2774 "docs": Object {2775 "container": [MockFunction],2776 "page": [MockFunction],2777 "renderer": [Function],2778 },2779 "fileName": "./src/ComponentOne.stories.js",2780 },2781 "story": "A",2782 "subcomponents": undefined,2783 "title": "Component One",2784 },2785 "component-one--b": Object {2786 "argTypes": Object {2787 "foo": Object {2788 "name": "foo",2789 "type": Object {2790 "name": "string",2791 },2792 },2793 },2794 "args": Object {2795 "foo": "b",2796 },2797 "component": undefined,2798 "componentId": "component-one",2799 "id": "component-one--b",2800 "initialArgs": Object {2801 "foo": "b",2802 },2803 "kind": "Component One",2804 "name": "B",2805 "parameters": Object {2806 "__isArgsStory": false,2807 "docs": Object {2808 "container": [MockFunction],2809 "page": [MockFunction],2810 "renderer": [Function],2811 },2812 "fileName": "./src/ComponentOne.stories.js",2813 },2814 "story": "B",2815 "subcomponents": undefined,2816 "title": "Component One",2817 },2818 "component-one--e": Object {2819 "argTypes": Object {},2820 "args": Object {},2821 "component": undefined,2822 "componentId": "component-one",2823 "id": "component-one--e",2824 "initialArgs": Object {},2825 "kind": "Component One",2826 "name": "E",2827 "parameters": Object {2828 "__isArgsStory": false,2829 "docs": Object {2830 "page": [MockFunction],2831 "renderer": [Function],2832 },2833 "fileName": "./src/ExtraComponentOne.stories.js",2834 },2835 "playFunction": undefined,2836 "story": "E",2837 "subcomponents": undefined,2838 "title": "Component One",2839 },2840 "component-two--c": Object {2841 "argTypes": Object {2842 "foo": Object {2843 "name": "foo",2844 "type": Object {2845 "name": "string",2846 },2847 },2848 },2849 "args": Object {2850 "foo": "c",2851 },2852 "component": undefined,2853 "componentId": "component-two",2854 "id": "component-two--c",2855 "initialArgs": Object {2856 "foo": "c",2857 },2858 "kind": "Component Two",2859 "name": "C",2860 "parameters": Object {2861 "__isArgsStory": false,2862 "docs": Object {2863 "renderer": [Function],2864 },2865 "fileName": "./src/ComponentTwo.stories.js",2866 },2867 "playFunction": undefined,2868 "story": "C",2869 "subcomponents": undefined,2870 "title": "Component Two",2871 },2872 }2873 `);2874 });2875 });...
Using AI Code Generation
1import { onUpdateArgsSpy } from 'storybook-root';2import { onUpdateArgsSpy } from 'storybook-root';3import { onUpdateArgsSpy } from 'storybook-root';4import { onUpdateArgsSpy } from 'storybook-root';5import { onUpdateArgsSpy } from 'storybook-root';6import { onUpdateArgsSpy } from 'storybook-root';7import { onUpdateArgsSpy } from 'storybook-root';8import { onUpdateArgsSpy } from 'storybook-root';9import { onUpdateArgsSpy } from 'storybook-root';10import { onUpdateArgsSpy } from 'storybook-root';11import { onUpdateArgsSpy } from 'storybook-root';12import { onUpdateArgsSpy } from 'storybook-root';13import { onUpdateArgsSpy } from 'storybook-root';14import { onUpdateArgsSpy } from 'storybook-root';15import { onUpdateArgsSpy } from 'storybook-root';16import { onUpdateArgsSpy } from 'storybook-root';17import { onUpdateArgsSpy } from 'storybook-root';18import { onUpdateArgsSpy } from 'storybook-root';19import { onUpdateArgsSpy } from 'storybook-root';20import { onUpdateArgsSpy } from
Using AI Code Generation
1import { onUpdateArgsSpy } from 'storybook-root';2import { updateArgs } from 'storybook-root';3import { updateArgs } from 'storybook-root';4import { updateArgs } from 'storybook-root';5import { updateArgs } from 'storybook-root';6import { onUpdateArgsSpy } from 'storybook-root';7import { updateArgs } from 'storybook-root';8import { updateArgs } from 'storybook-root';9import { updateArgs } from 'storybook-root';10import { updateArgs } from 'storybook-root';11import { onUpdateArgsSpy } from 'storybook-root';12import { updateArgs } from 'storybook-root';13import { updateArgs } from 'storybook-root';14import { updateArgs } from 'storybook-root';15import { updateArgs } from 'storybook-root';16import { onUpdateArgsSpy } from 'storybook-root';17import { updateArgs } from 'storybook-root';18import { updateArgs } from 'storybook-root';19import { updateArgs } from 'storybook-root';20import { updateArgs } from 'storybook-root';
Using AI Code Generation
1import { onUpdateArgsSpy } from 'storybook-root';2onUpdateArgsSpy('arg1', 'arg2');3import { onStoryChangeSpy } from 'storybook-root';4onStoryChangeSpy('storyId');5import { onStoryArgsChangeSpy } from 'storybook-root';6onStoryArgsChangeSpy('storyId');7import { onStoryRenderSpy } from 'storybook-root';8onStoryRenderSpy('storyId');9import { onStorySortSpy } from 'storybook-root';10onStorySortSpy('storyId');11import { onForceReRenderSpy } from 'storybook-root';12onForceReRenderSpy();13import { onSetOptionsSpy } from 'storybook-root';14onSetOptionsSpy('options');15import { onGetCurrentStoryDataSpy } from 'storybook-root';16onGetCurrentStoryDataSpy();17import { onGetStorybookUIParamsSpy } from 'storybook-root';18onGetStorybookUIParamsSpy();19import { onGetStorybookUIPropsSpy } from 'storybook-root';20onGetStorybookUIPropsSpy();21import { onGetDecoratedSpy } from 'storybook-root';22onGetDecoratedSpy();23import { onGetPreviewErrorSpy } from 'storybook-root';24onGetPreviewErrorSpy();
Using AI Code Generation
1import { onUpdateArgsSpy } from 'storybook-root';2const updateArgs = (args) => {3 onUpdateArgsSpy(args);4};5export const MyStory = Template.bind({});6MyStory.args = {7};8MyStory.parameters = {9 actions: {10 },11};12import { addons } from '@storybook/addons';13const UPDATE_ARGS_EVENT = 'storybook/actions/updateArgs';14export const onUpdateArgsSpy = (args) => {15 const channel = addons.getChannel();16 channel.emit(UPDATE_ARGS_EVENT, args);17};18import { addons } from '@storybook/addons';19import { UPDATE_ARGS_EVENT } from 'storybook-root';20addons.getChannel().addListener(UPDATE_ARGS_EVENT, (args) => {21});
Using AI Code Generation
1import { onUpdateArgsSpy } from 'storybook-root';2export const test = () => {3 onUpdateArgsSpy(() => {4 console.log('onUpdateArgsSpy called');5 });6};7import { addons } from '@storybook/addons';8import { STORY_ARGS_UPDATED } from '@storybook/core-events';9export const onUpdateArgsSpy = (callback) => {10 addons.getChannel().on(STORY_ARGS_UPDATED, (data) => {11 callback(data);12 });13};
Using AI Code Generation
1import { onUpdateArgsSpy } from 'storybook-root';2onUpdateArgsSpy('story-id', { property: value });3import { onUpdateArgsSpy } from 'storybook-root';4 (Story, context) => {5 const { id, args } = context;6 onUpdateArgsSpy(id, args);7 return Story(context);8 }9import { stories } from 'storybook-root';10 (Story, context) => {11 const { id, args } = context;12 const story = stories.find(story => story.id === id);13 if (story) {14 story.args = args;15 }16 return Story(context);17 }
Learn to execute automation testing from scratch with LambdaTest Learning Hub. Right from setting up the prerequisites to run your first automation test, to following best practices and diving deeper into advanced test scenarios. LambdaTest Learning Hubs compile a list of step-by-step guides to help you be proficient with different test automation frameworks i.e. Selenium, Cypress, TestNG etc.
You could also refer to video tutorials over LambdaTest YouTube channel to get step by step demonstration from industry experts.
Get 100 minutes of automation test minutes FREE!!