diff --git a/code/addons/interactions/src/preset/preview.ts b/code/addons/interactions/src/preset/preview.ts index 41eadcf626f5..6995d3037159 100644 --- a/code/addons/interactions/src/preset/preview.ts +++ b/code/addons/interactions/src/preset/preview.ts @@ -59,3 +59,7 @@ export const { step: runStep } = instrument( { step: (label: StepLabel, play: PlayFunction, context: PlayFunctionContext) => play(context) }, { intercept: true } ); + +export const parameters = { + throwPlayFunctionExceptions: false, +}; diff --git a/code/lib/preview-web/src/PreviewWeb.mockdata.ts b/code/lib/preview-web/src/PreviewWeb.mockdata.ts index aae1136c168c..99d92dbf140b 100644 --- a/code/lib/preview-web/src/PreviewWeb.mockdata.ts +++ b/code/lib/preview-web/src/PreviewWeb.mockdata.ts @@ -64,7 +64,7 @@ export const projectAnnotations = { renderToDOM: jest.fn().mockReturnValue(teardownRenderToDOM), parameters: { docs: { renderer: () => docsRenderer } }, }; -export const getProjectAnnotations = () => projectAnnotations; +export const getProjectAnnotations = jest.fn(() => projectAnnotations as any); export const storyIndex: StoryIndex = { v: 4, diff --git a/code/lib/preview-web/src/PreviewWeb.test.ts b/code/lib/preview-web/src/PreviewWeb.test.ts index 2efa7ab9a64d..75fb1b8de23f 100644 --- a/code/lib/preview-web/src/PreviewWeb.test.ts +++ b/code/lib/preview-web/src/PreviewWeb.test.ts @@ -511,16 +511,13 @@ describe('PreviewWeb', () => { it('renders helpful message if renderToDOM is undefined', async () => { document.location.search = '?id=component-one--a'; + + getProjectAnnotations.mockReturnValueOnce({ + ...projectAnnotations, + renderToDOM: undefined, + }); const preview = new PreviewWeb(); - await expect( - preview.initialize({ - importFn, - getProjectAnnotations: () => ({ - ...getProjectAnnotations, - renderToDOM: undefined, - }), - }) - ).rejects.toThrow(); + await expect(preview.initialize({ importFn, getProjectAnnotations })).rejects.toThrow(); expect(preview.view.showErrorDisplay).toHaveBeenCalled(); expect((preview.view.showErrorDisplay as jest.Mock).mock.calls[0][0]) @@ -533,20 +530,56 @@ describe('PreviewWeb', () => { `); }); - it('emits but does not render exception if the play function throws', async () => { - const error = new Error('error'); - componentOneExports.a.play.mockImplementationOnce(() => { - throw error; + describe('when `throwPlayFunctionExceptions` is set', () => { + it('emits but does not render exception if the play function throws', async () => { + const error = new Error('error'); + componentOneExports.a.play.mockImplementationOnce(() => { + throw error; + }); + + getProjectAnnotations.mockReturnValueOnce({ + ...projectAnnotations, + parameters: { + ...projectAnnotations.parameters, + throwPlayFunctionExceptions: false, + }, + }); + + document.location.search = '?id=component-one--a'; + const preview = await createAndRenderPreview(); + + expect(mockChannel.emit).toHaveBeenCalledWith( + PLAY_FUNCTION_THREW_EXCEPTION, + serializeError(error) + ); + expect(preview.view.showErrorDisplay).not.toHaveBeenCalled(); + expect(mockChannel.emit).not.toHaveBeenCalledWith( + STORY_THREW_EXCEPTION, + serializeError(error) + ); }); + }); - document.location.search = '?id=component-one--a'; - const preview = await createAndRenderPreview(); + describe('when `throwPlayFunctionExceptions` is unset', () => { + it('emits AND renders exception if the play function throws', async () => { + const error = new Error('error'); + componentOneExports.a.play.mockImplementationOnce(() => { + throw error; + }); - expect(mockChannel.emit).toHaveBeenCalledWith( - PLAY_FUNCTION_THREW_EXCEPTION, - serializeError(error) - ); - expect(preview.view.showErrorDisplay).not.toHaveBeenCalled(); + document.location.search = '?id=component-one--a'; + const preview = await createAndRenderPreview(); + + expect(mockChannel.emit).toHaveBeenCalledWith( + PLAY_FUNCTION_THREW_EXCEPTION, + serializeError(error) + ); + expect(preview.view.showErrorDisplay).toHaveBeenCalled(); + expect(mockChannel.emit).toHaveBeenCalledWith( + STORY_THREW_EXCEPTION, + serializeError(error) + ); + }); }); it('renders exception if the story calls showException', async () => { @@ -3119,6 +3152,16 @@ describe('PreviewWeb', () => { }); }); + describe('with no selection', () => { + // eslint-disable-next-line jest/expect-expect + it('does not error', async () => { + const preview = await createAndRenderPreview(); + await preview.onGetProjectAnnotationsChanged({ + getProjectAnnotations: newGetProjectAnnotations, + }); + }); + }); + it('shows an error the new value throws', async () => { document.location.search = '?id=component-one--a'; const preview = await createAndRenderPreview(); diff --git a/code/lib/preview-web/src/PreviewWeb.tsx b/code/lib/preview-web/src/PreviewWeb.tsx index 49f07856b1b4..4c5d96eca8e9 100644 --- a/code/lib/preview-web/src/PreviewWeb.tsx +++ b/code/lib/preview-web/src/PreviewWeb.tsx @@ -181,7 +181,9 @@ export class PreviewWeb extends Preview implements Render { this.channel.emit(PLAY_FUNCTION_THREW_EXCEPTION, serializeError(error)); }); + if (this.story.parameters.throwPlayFunctionExceptions !== false) throw error; } this.disableKeyListeners = false; if (abortSignal.aborted) return;