diff --git a/code/lib/store/template/stories/rendering.stories.ts b/code/lib/store/template/stories/rendering.stories.ts index 4b2181bb5ea6..8bca2f78f5c9 100644 --- a/code/lib/store/template/stories/rendering.stories.ts +++ b/code/lib/store/template/stories/rendering.stories.ts @@ -65,7 +65,5 @@ export const ChangeArgs = { await within(canvasElement).findByText(/New Text/); await expect(button).toHaveFocus(); - - await channel.emit(RESET_STORY_ARGS, { storyId: id }); }, }; diff --git a/code/renderers/svelte/package.json b/code/renderers/svelte/package.json index f83697472c40..46417e6e1bf3 100644 --- a/code/renderers/svelte/package.json +++ b/code/renderers/svelte/package.json @@ -54,6 +54,7 @@ "dependencies": { "@storybook/client-logger": "7.0.0-rc.6", "@storybook/core-client": "7.0.0-rc.6", + "@storybook/core-events": "7.0.0-rc.6", "@storybook/docs-tools": "7.0.0-rc.6", "@storybook/global": "^5.0.0", "@storybook/preview-api": "7.0.0-rc.6", diff --git a/code/renderers/svelte/src/render.ts b/code/renderers/svelte/src/render.ts index 777997c09598..cfde36047fa1 100644 --- a/code/renderers/svelte/src/render.ts +++ b/code/renderers/svelte/src/render.ts @@ -1,6 +1,7 @@ /* eslint-disable no-param-reassign */ import type { RenderContext, ArgsStoryFn } from '@storybook/types'; import type { SvelteComponentTyped } from 'svelte'; +import { RESET_STORY_ARGS } from '@storybook/core-events'; // ! DO NOT change this PreviewRender import to a relative path, it will break it. // ! A relative import will be compiled at build time, and Svelte will be unable to // ! render the component together with the user's Svelte components @@ -8,6 +9,7 @@ import type { SvelteComponentTyped } from 'svelte'; // ! with the same bundle as the user's Svelte components // eslint-disable-next-line import/no-extraneous-dependencies import PreviewRender from '@storybook/svelte/templates/PreviewRender.svelte'; +import { addons } from '@storybook/preview-api'; import type { SvelteRenderer } from './types'; @@ -24,6 +26,20 @@ function teardown(canvasElement: SvelteRenderer['canvasElement']) { componentsByDomElement.delete(canvasElement); } +/** + * This is a workaround for the issue that when resetting args, + * the story needs to be remounted completely to revert to the component's default props. + * This is because Svelte does not itself revert to defaults when a prop is undefined. + * See https://github.com/storybookjs/storybook/issues/21470#issuecomment-1467056479 + * + * We listen for the RESET_STORY_ARGS event and store the storyId to be reset + * We then use this in the renderToCanvas function to force remount the story + */ +const storyIdsToRemountFromResetArgsEvent = new Set(); +addons.getChannel().on(RESET_STORY_ARGS, ({ storyId }) => { + storyIdsToRemountFromResetArgsEvent.add(storyId); +}); + export function renderToCanvas( { storyFn, @@ -38,11 +54,18 @@ export function renderToCanvas( ) { const existingComponent = componentsByDomElement.get(canvasElement); - if (forceRemount) { + let remount = forceRemount; + + if (storyIdsToRemountFromResetArgsEvent.has(storyContext.id)) { + remount = true; + storyIdsToRemountFromResetArgsEvent.delete(storyContext.id); + } + + if (remount) { teardown(canvasElement); } - if (!existingComponent || forceRemount) { + if (!existingComponent || remount) { const createdComponent = new PreviewRender({ target: canvasElement, props: { diff --git a/code/renderers/svelte/template/stories/args.stories.js b/code/renderers/svelte/template/stories/args.stories.js new file mode 100644 index 000000000000..f94637bbe56a --- /dev/null +++ b/code/renderers/svelte/template/stories/args.stories.js @@ -0,0 +1,46 @@ +import { expect } from '@storybook/jest'; +import { within, userEvent, waitFor } from '@storybook/testing-library'; +import { + UPDATE_STORY_ARGS, + STORY_ARGS_UPDATED, + RESET_STORY_ARGS, + STORY_RENDERED, +} from '@storybook/core-events'; +import { addons } from '@storybook/preview-api'; +import ButtonView from './views/ButtonView.svelte'; + +export default { + component: ButtonView, +}; + +export const RemountOnResetStoryArgs = { + play: async ({ canvasElement, id }) => { + const canvas = await within(canvasElement); + const channel = addons.getChannel(); + + // Just to ensure the story is always in a clean state from the beginning, not really part of the test + await channel.emit(RESET_STORY_ARGS, { storyId: id }); + await new Promise((resolve) => { + channel.once(STORY_RENDERED, resolve); + }); + const button = await canvas.getByRole('button'); + await expect(button).toHaveTextContent('You clicked: 0'); + + await userEvent.click(button); + await expect(button).toHaveTextContent('You clicked: 1'); + + await channel.emit(UPDATE_STORY_ARGS, { storyId: id, updatedArgs: { text: 'Changed' } }); + await new Promise((resolve) => { + channel.once(STORY_RENDERED, resolve); + }); + await expect(button).toHaveTextContent('Changed: 1'); + + // expect that all state and args are reset after RESET_STORY_ARGS because Svelte needs to remount the component + // most other renderers would have 'You clicked: 1' here because they don't remount the component + // if this doesn't fully remount it would be 'undefined: 1' because undefined args are used as is in Svelte, and the state is kept + await channel.emit(RESET_STORY_ARGS, { storyId: id }); + await waitFor(async () => + expect(await within(canvasElement).getByRole('button')).toHaveTextContent('You clicked: 0') + ); + }, +}; diff --git a/code/yarn.lock b/code/yarn.lock index c08ac3f2d7ce..af02f66ee9e9 100644 --- a/code/yarn.lock +++ b/code/yarn.lock @@ -7312,6 +7312,7 @@ __metadata: dependencies: "@storybook/client-logger": 7.0.0-rc.6 "@storybook/core-client": 7.0.0-rc.6 + "@storybook/core-events": 7.0.0-rc.6 "@storybook/docs-tools": 7.0.0-rc.6 "@storybook/global": ^5.0.0 "@storybook/preview-api": 7.0.0-rc.6