From 483325a656e9884d4322d0ed38a9a518b9ea1f8f Mon Sep 17 00:00:00 2001 From: Valentin Palkovic Date: Wed, 4 Sep 2024 10:20:57 +0200 Subject: [PATCH 01/13] Portable Stories: Improve Handling of React Updates and Errors Co-authored-by: Yann Braga Co-authored-by: Jeppe Reinhold --- .../modules/store/csf/portable-stories.ts | 4 + .../react-dom-shim/src/preventActChecks.tsx | 17 -- code/lib/react-dom-shim/src/react-16.tsx | 6 +- code/lib/react-dom-shim/src/react-18.tsx | 23 +- code/renderers/react/package.json | 4 + .../react/src/__test__/Button.stories.tsx | 9 +- .../__test__/ComponentWithError.stories.tsx | 13 + .../react/src/__test__/ComponentWithError.tsx | 4 + .../portable-stories-legacy.test.tsx.snap | 34 +++ .../__test__/portable-stories-legacy.test.tsx | 6 +- .../src/__test__/portable-stories.test.tsx | 90 +++--- code/renderers/react/src/act-compat.ts | 65 +++++ code/renderers/react/src/portable-stories.tsx | 80 +++++- code/renderers/react/src/renderToCanvas.tsx | 7 +- code/vitest-setup.ts | 1 + code/yarn.lock | 269 +++++++++++++++++- 16 files changed, 557 insertions(+), 75 deletions(-) delete mode 100644 code/lib/react-dom-shim/src/preventActChecks.tsx create mode 100644 code/renderers/react/src/__test__/ComponentWithError.stories.tsx create mode 100644 code/renderers/react/src/__test__/ComponentWithError.tsx create mode 100644 code/renderers/react/src/act-compat.ts diff --git a/code/core/src/preview-api/modules/store/csf/portable-stories.ts b/code/core/src/preview-api/modules/store/csf/portable-stories.ts index 1525b6e3e6d8..7adc83196eb1 100644 --- a/code/core/src/preview-api/modules/store/csf/portable-stories.ts +++ b/code/core/src/preview-api/modules/store/csf/portable-stories.ts @@ -74,6 +74,10 @@ export function setProjectAnnotations( | NamedOrDefaultProjectAnnotations[] ): NormalizedProjectAnnotations { const annotations = Array.isArray(projectAnnotations) ? projectAnnotations : [projectAnnotations]; + if (globalThis.defaultProjectAnnotations) { + annotations.push(globalThis.defaultProjectAnnotations); + } + globalThis.globalProjectAnnotations = composeConfigs(annotations.map(extractAnnotation)); return globalThis.globalProjectAnnotations; diff --git a/code/lib/react-dom-shim/src/preventActChecks.tsx b/code/lib/react-dom-shim/src/preventActChecks.tsx deleted file mode 100644 index f35e2fb25dc5..000000000000 --- a/code/lib/react-dom-shim/src/preventActChecks.tsx +++ /dev/null @@ -1,17 +0,0 @@ -export {}; - -declare const globalThis: { - IS_REACT_ACT_ENVIRONMENT?: boolean; -}; - -// TODO(9.0): We should actually wrap all those lines in `act`, but that might be a breaking change. -// We should make that breaking change for SB 9.0 -export function preventActChecks(callback: () => void): void { - const originalActEnvironment = globalThis.IS_REACT_ACT_ENVIRONMENT; - globalThis.IS_REACT_ACT_ENVIRONMENT = false; - try { - callback(); - } finally { - globalThis.IS_REACT_ACT_ENVIRONMENT = originalActEnvironment; - } -} diff --git a/code/lib/react-dom-shim/src/react-16.tsx b/code/lib/react-dom-shim/src/react-16.tsx index a1e7b1e97009..8c7b2c8f5a67 100644 --- a/code/lib/react-dom-shim/src/react-16.tsx +++ b/code/lib/react-dom-shim/src/react-16.tsx @@ -2,14 +2,12 @@ import type { ReactElement } from 'react'; import * as ReactDOM from 'react-dom'; -import { preventActChecks } from './preventActChecks'; - export const renderElement = async (node: ReactElement, el: Element) => { return new Promise((resolve) => { - preventActChecks(() => void ReactDOM.render(node, el, () => resolve(null))); + ReactDOM.render(node, el, () => resolve(null)); }); }; export const unmountElement = (el: Element) => { - preventActChecks(() => void ReactDOM.unmountComponentAtNode(el)); + ReactDOM.unmountComponentAtNode(el); }; diff --git a/code/lib/react-dom-shim/src/react-18.tsx b/code/lib/react-dom-shim/src/react-18.tsx index 5eb72b20eb17..f3398fc65ff0 100644 --- a/code/lib/react-dom-shim/src/react-18.tsx +++ b/code/lib/react-dom-shim/src/react-18.tsx @@ -1,15 +1,21 @@ /* eslint-disable @typescript-eslint/no-unnecessary-type-constraint */ -import type { FC, ReactElement } from 'react'; +import type { ReactElement } from 'react'; import * as React from 'react'; import type { Root as ReactRoot, RootOptions } from 'react-dom/client'; import * as ReactDOM from 'react-dom/client'; -import { preventActChecks } from './preventActChecks'; - // A map of all rendered React 18 nodes const nodes = new Map(); -const WithCallback: FC<{ callback: () => void; children: ReactElement }> = ({ +declare const globalThis: { + IS_REACT_ACT_ENVIRONMENT: boolean; +}; + +function getIsReactActEnvironment() { + return globalThis.IS_REACT_ACT_ENVIRONMENT; +} + +const WithCallback: React.FC<{ callback: () => void; children: ReactElement }> = ({ callback, children, }) => { @@ -43,8 +49,13 @@ export const renderElement = async (node: ReactElement, el: Element, rootOptions // Create Root Element conditionally for new React 18 Root Api const root = await getReactRoot(el, rootOptions); + if (getIsReactActEnvironment()) { + root.render(node); + return; + } + const { promise, resolve } = Promise.withResolvers(); - preventActChecks(() => root.render({node})); + root.render({node}); return promise; }; @@ -52,7 +63,7 @@ export const unmountElement = (el: Element, shouldUseNewRootApi?: boolean) => { const root = nodes.get(el); if (root) { - preventActChecks(() => root.unmount()); + root.unmount(); nodes.delete(el); } }; diff --git a/code/renderers/react/package.json b/code/renderers/react/package.json index 4d370bbb8a1e..003466f1b182 100644 --- a/code/renderers/react/package.json +++ b/code/renderers/react/package.json @@ -94,12 +94,16 @@ "require-from-string": "^2.0.2" }, "peerDependencies": { + "@storybook/test": "workspace:*", "react": "^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0-beta", "react-dom": "^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0-beta", "storybook": "workspace:^", "typescript": ">= 4.2.x" }, "peerDependenciesMeta": { + "@storybook/test": { + "optional": true + }, "typescript": { "optional": true } diff --git a/code/renderers/react/src/__test__/Button.stories.tsx b/code/renderers/react/src/__test__/Button.stories.tsx index bde220fdf469..0e6e0d6e8c67 100644 --- a/code/renderers/react/src/__test__/Button.stories.tsx +++ b/code/renderers/react/src/__test__/Button.stories.tsx @@ -103,7 +103,6 @@ export const HooksStory: CSF3Story = { ); }, play: async ({ canvasElement, step }) => { - console.log('start of play function'); const canvas = within(canvasElement); await step('Step label', async () => { const inputEl = canvas.getByTestId('input'); @@ -112,8 +111,8 @@ export const HooksStory: CSF3Story = { await userEvent.type(inputEl, 'Hello world!'); await expect(inputEl).toHaveValue('Hello world!'); + await expect(buttonEl).toHaveTextContent('I am clicked'); }); - console.log('end of play function'); }, }; @@ -182,6 +181,12 @@ export const MountInPlayFunction: CSF3Story<{ mockFn: (val: string) => string }> }, }; +export const MountInPlayFunctionThrow: CSF3Story<{ mockFn: (val: string) => string }> = { + play: async () => { + throw new Error('Error thrown in play'); + }, +}; + export const WithActionArg: CSF3Story<{ someActionArg: HandlerFunction }> = { args: { someActionArg: action('some-action-arg'), diff --git a/code/renderers/react/src/__test__/ComponentWithError.stories.tsx b/code/renderers/react/src/__test__/ComponentWithError.stories.tsx new file mode 100644 index 000000000000..627055e2d965 --- /dev/null +++ b/code/renderers/react/src/__test__/ComponentWithError.stories.tsx @@ -0,0 +1,13 @@ +import type { Meta, StoryObj } from '..'; +import { ComponentWithError } from './ComponentWithError'; + +const meta = { + title: 'Example/ComponentWithError', + component: ComponentWithError as any, +} satisfies Meta; + +export default meta; + +type Story = StoryObj; + +export const ThrowsError: Story = {}; diff --git a/code/renderers/react/src/__test__/ComponentWithError.tsx b/code/renderers/react/src/__test__/ComponentWithError.tsx new file mode 100644 index 000000000000..37f667cb4f2c --- /dev/null +++ b/code/renderers/react/src/__test__/ComponentWithError.tsx @@ -0,0 +1,4 @@ +export function ComponentWithError() { + // eslint-disable-next-line local-rules/no-uncategorized-errors + throw new Error('Error in render'); +} diff --git a/code/renderers/react/src/__test__/__snapshots__/portable-stories-legacy.test.tsx.snap b/code/renderers/react/src/__test__/__snapshots__/portable-stories-legacy.test.tsx.snap index b4753327aaf1..b690349bed8d 100644 --- a/code/renderers/react/src/__test__/__snapshots__/portable-stories-legacy.test.tsx.snap +++ b/code/renderers/react/src/__test__/__snapshots__/portable-stories-legacy.test.tsx.snap @@ -147,6 +147,40 @@ exports[`Legacy Portable Stories API > Renders Modal story 1`] = ` `; +exports[`Legacy Portable Stories API > Renders MountInPlayFunction story 1`] = ` + +
+
+ loaded data +
+
+ mockFn return value +
+
+ +`; + +exports[`Legacy Portable Stories API > Renders MountInPlayFunctionThrow story 1`] = ` + +
+
+ loaded data +
+
+ mockFn return value +
+
+ +`; + exports[`Legacy Portable Stories API > Renders WithActionArg story 1`] = `
diff --git a/code/renderers/react/src/__test__/portable-stories-legacy.test.tsx b/code/renderers/react/src/__test__/portable-stories-legacy.test.tsx index 3c7321cdfe63..5567b1fd9fbc 100644 --- a/code/renderers/react/src/__test__/portable-stories-legacy.test.tsx +++ b/code/renderers/react/src/__test__/portable-stories-legacy.test.tsx @@ -200,7 +200,11 @@ describe('Legacy Portable Stories API', () => { it.each(testCases)('Renders %s story', async (_storyName, Story) => { cleanup(); - if (_storyName === 'CSF2StoryWithLocale' || _storyName === 'MountInPlayFunction') { + if ( + _storyName === 'CSF2StoryWithLocale' || + _storyName === 'MountInPlayFunction' || + _storyName === 'MountInPlayFunctionThrow' + ) { return; } diff --git a/code/renderers/react/src/__test__/portable-stories.test.tsx b/code/renderers/react/src/__test__/portable-stories.test.tsx index 90346edff991..94de89e093a5 100644 --- a/code/renderers/react/src/__test__/portable-stories.test.tsx +++ b/code/renderers/react/src/__test__/portable-stories.test.tsx @@ -2,7 +2,7 @@ /* eslint-disable import/namespace */ import { cleanup, render, screen } from '@testing-library/react'; -import { afterEach, describe, expect, it, vi } from 'vitest'; +import { afterEach, beforeAll, describe, expect, it, vi } from 'vitest'; import React from 'react'; @@ -16,23 +16,28 @@ import { expectTypeOf } from 'expect-type'; import { composeStories, composeStory, setProjectAnnotations } from '..'; import type { Button } from './Button'; -import * as stories from './Button.stories'; +import * as ButtonStories from './Button.stories'; +import * as ComponentWithErrorStories from './ComponentWithError.stories'; -setProjectAnnotations([]); +const HooksStory = composeStory(ButtonStories.HooksStory, ButtonStories.default); + +const projectAnnotations = setProjectAnnotations([]); // example with composeStories, returns an object with all stories composed with args/decorators -const { CSF3Primary, LoaderStory, MountInPlayFunction } = composeStories(stories); +const { CSF3Primary, LoaderStory, MountInPlayFunction, MountInPlayFunctionThrow } = + composeStories(ButtonStories); +const { ThrowsError } = composeStories(ComponentWithErrorStories); + +beforeAll(async () => { + await projectAnnotations.beforeAll?.(); +}); afterEach(() => { cleanup(); }); -declare const globalThis: { - IS_REACT_ACT_ENVIRONMENT?: boolean; -}; - // example with composeStory, returns a single story composed with args/decorators -const Secondary = composeStory(stories.CSF2Secondary, stories.default); +const Secondary = composeStory(ButtonStories.CSF2Secondary, ButtonStories.default); describe('renders', () => { it('renders primary button', () => { render(Hello world); @@ -60,6 +65,10 @@ describe('renders', () => { expect(buttonElement).not.toBeNull(); }); + it('should throw error when rendering a component with a render error', async () => { + await expect(() => ThrowsError.run()).rejects.toThrowError('Error in render'); + }); + it('should render component mounted in play function', async () => { await MountInPlayFunction.run(); @@ -67,6 +76,10 @@ describe('renders', () => { expect(screen.getByTestId('loaded-data').textContent).toEqual('loaded data'); }); + it('should throw an error in play function', () => { + expect(() => MountInPlayFunctionThrow.run()).rejects.toThrowError('Error thrown in play'); + }); + it('should call and compose loaders data', async () => { await LoaderStory.load(); const { getByTestId } = render(); @@ -78,10 +91,6 @@ describe('renders', () => { }); describe('projectAnnotations', () => { - afterEach(() => { - cleanup(); - }); - it('renders with default projectAnnotations', () => { setProjectAnnotations([ { @@ -91,7 +100,7 @@ describe('projectAnnotations', () => { }, }, ]); - const WithEnglishText = composeStory(stories.CSF2StoryWithLocale, stories.default); + const WithEnglishText = composeStory(ButtonStories.CSF2StoryWithLocale, ButtonStories.default); const { getByText } = render(); const buttonElement = getByText('Hello!'); expect(buttonElement).not.toBeNull(); @@ -99,24 +108,31 @@ describe('projectAnnotations', () => { }); it('renders with custom projectAnnotations via composeStory params', () => { - const WithPortugueseText = composeStory(stories.CSF2StoryWithLocale, stories.default, { - initialGlobals: { locale: 'pt' }, - }); + const WithPortugueseText = composeStory( + ButtonStories.CSF2StoryWithLocale, + ButtonStories.default, + { + initialGlobals: { locale: 'pt' }, + } + ); const { getByText } = render(); const buttonElement = getByText('Olá!'); expect(buttonElement).not.toBeNull(); }); it('has action arg from argTypes when addon-actions annotations are added', () => { - //@ts-expect-error our tsconfig.jsn#moduleResulution is set to 'node', which doesn't support this import - const Story = composeStory(stories.WithActionArgType, stories.default, addonActionsPreview); + const Story = composeStory( + ButtonStories.WithActionArgType, + ButtonStories.default, + addonActionsPreview + ); expect(Story.args.someActionArg).toHaveProperty('isAction', true); }); }); describe('CSF3', () => { it('renders with inferred globalRender', () => { - const Primary = composeStory(stories.CSF3Button, stories.default); + const Primary = composeStory(ButtonStories.CSF3Button, ButtonStories.default); render(Hello world); const buttonElement = screen.getByText(/Hello world/i); @@ -124,14 +140,17 @@ describe('CSF3', () => { }); it('renders with custom render function', () => { - const Primary = composeStory(stories.CSF3ButtonWithRender, stories.default); + const Primary = composeStory(ButtonStories.CSF3ButtonWithRender, ButtonStories.default); render(); expect(screen.getByTestId('custom-render')).not.toBeNull(); }); it('renders with play function without canvas element', async () => { - const CSF3InputFieldFilled = composeStory(stories.CSF3InputFieldFilled, stories.default); + const CSF3InputFieldFilled = composeStory( + ButtonStories.CSF3InputFieldFilled, + ButtonStories.default + ); await CSF3InputFieldFilled.run(); const input = screen.getByTestId('input') as HTMLInputElement; @@ -139,7 +158,10 @@ describe('CSF3', () => { }); it('renders with play function with canvas element', async () => { - const CSF3InputFieldFilled = composeStory(stories.CSF3InputFieldFilled, stories.default); + const CSF3InputFieldFilled = composeStory( + ButtonStories.CSF3InputFieldFilled, + ButtonStories.default + ); const div = document.createElement('div'); document.body.appendChild(div); @@ -153,21 +175,16 @@ describe('CSF3', () => { }); it('renders with hooks', async () => { - // TODO find out why act is not working here - globalThis.IS_REACT_ACT_ENVIRONMENT = false; - const HooksStory = composeStory(stories.HooksStory, stories.default); - await HooksStory.run(); const input = screen.getByTestId('input') as HTMLInputElement; expect(input.value).toEqual('Hello world!'); - globalThis.IS_REACT_ACT_ENVIRONMENT = true; }); }); // common in addons that need to communicate between manager and preview it('should pass with decorators that need addons channel', () => { - const PrimaryWithChannels = composeStory(stories.CSF3Primary, stories.default, { + const PrimaryWithChannels = composeStory(ButtonStories.CSF3Primary, ButtonStories.default, { decorators: [ (StoryFn: any) => { addons.getChannel(); @@ -186,27 +203,24 @@ describe('ComposeStories types', () => { type ComposeStoriesParam = Parameters[0]; expectTypeOf({ - ...stories, - default: stories.default as Meta, + ...ButtonStories, + default: ButtonStories.default as Meta, }).toMatchTypeOf(); expectTypeOf({ - ...stories, - default: stories.default satisfies Meta, + ...ButtonStories, + default: ButtonStories.default satisfies Meta, }).toMatchTypeOf(); }); }); -// Batch snapshot testing -const testCases = Object.values(composeStories(stories)).map( +const testCases = Object.values(composeStories(ButtonStories)).map( (Story) => [Story.storyName, Story] as [string, typeof Story] ); it.each(testCases)('Renders %s story', async (_storyName, Story) => { - if (_storyName === 'CSF2StoryWithLocale') { + if (_storyName === 'CSF2StoryWithLocale' || _storyName === 'MountInPlayFunctionThrow') { return; } - globalThis.IS_REACT_ACT_ENVIRONMENT = false; await Story.run(); - globalThis.IS_REACT_ACT_ENVIRONMENT = true; expect(document.body).toMatchSnapshot(); }); diff --git a/code/renderers/react/src/act-compat.ts b/code/renderers/react/src/act-compat.ts new file mode 100644 index 000000000000..afe1cc902316 --- /dev/null +++ b/code/renderers/react/src/act-compat.ts @@ -0,0 +1,65 @@ +// Copied from +// https://github.com/testing-library/react-testing-library/blob/3dcd8a9649e25054c0e650d95fca2317b7008576/src/act-compat.js +import * as React from 'react'; + +import * as DeprecatedReactTestUtils from 'react-dom/test-utils'; + +declare const globalThis: { + IS_REACT_ACT_ENVIRONMENT: boolean; +}; + +// @ts-expect-error act might not be available in some versions of React +const reactAct = typeof React.act === 'function' ? React.act : DeprecatedReactTestUtils.act; + +export function setReactActEnvironment(isReactActEnvironment: boolean) { + globalThis.IS_REACT_ACT_ENVIRONMENT = isReactActEnvironment; +} + +export function getReactActEnvironment() { + return globalThis.IS_REACT_ACT_ENVIRONMENT; +} + +function withGlobalActEnvironment(actImplementation: (callback: () => void) => Promise) { + return (callback: () => any) => { + const previousActEnvironment = getReactActEnvironment(); + setReactActEnvironment(true); + try { + // The return value of `act` is always a thenable. + let callbackNeedsToBeAwaited = false; + const actResult = actImplementation(() => { + const result = callback(); + if (result !== null && typeof result === 'object' && typeof result.then === 'function') { + callbackNeedsToBeAwaited = true; + } + return result; + }); + if (callbackNeedsToBeAwaited) { + const thenable: Promise = actResult; + return { + then: (resolve: (param: any) => void, reject: (param: any) => void) => { + thenable.then( + (returnValue) => { + setReactActEnvironment(previousActEnvironment); + resolve(returnValue); + }, + (error) => { + setReactActEnvironment(previousActEnvironment); + reject(error); + } + ); + }, + }; + } else { + setReactActEnvironment(previousActEnvironment); + return actResult; + } + } catch (error) { + // Can't be a `finally {}` block since we don't know if we have to immediately restore IS_REACT_ACT_ENVIRONMENT + // or if we have to await the callback first. + setReactActEnvironment(previousActEnvironment); + throw error; + } + }; +} + +export const act = withGlobalActEnvironment(reactAct); diff --git a/code/renderers/react/src/portable-stories.tsx b/code/renderers/react/src/portable-stories.tsx index 2ea196e85b4b..ced0bbd289e3 100644 --- a/code/renderers/react/src/portable-stories.tsx +++ b/code/renderers/react/src/portable-stories.tsx @@ -1,4 +1,4 @@ -import React from 'react'; +import * as React from 'react'; import { composeStories as originalComposeStories, @@ -17,6 +17,7 @@ import type { StoryAnnotationsOrFn, } from 'storybook/internal/types'; +import { act, getReactActEnvironment, setReactActEnvironment } from './act-compat'; import * as reactProjectAnnotations from './entry-preview'; import type { Meta } from './public-types'; import type { ReactRenderer } from './types'; @@ -54,9 +55,66 @@ export function setProjectAnnotations( // This will not be necessary once we have auto preset loading export const INTERNAL_DEFAULT_PROJECT_ANNOTATIONS: ProjectAnnotations = { ...reactProjectAnnotations, - renderToCanvas: (renderContext, canvasElement) => { + beforeAll: async function reactBeforeAll() { + try { + // copied from + // https://github.com/testing-library/react-testing-library/blob/3dcd8a9649e25054c0e650d95fca2317b7008576/src/pure.js + const { configure } = await import('@storybook/test'); + + configure({ + unstable_advanceTimersWrapper: (cb) => { + return act(cb); + }, + asyncWrapper: async (cb) => { + const previousActEnvironment = getReactActEnvironment(); + setReactActEnvironment(false); + try { + const result = await cb(); + // Drain microtask queue. + // Otherwise we'll restore the previous act() environment, before we resolve the `waitFor` call. + // The caller would have no chance to wrap the in-flight Promises in `act()` + await new Promise((resolve) => { + setTimeout(() => { + resolve(); + }, 0); + + if (jestFakeTimersAreEnabled()) { + // @ts-expect-error global jest + jest.advanceTimersByTime(0); + } + }); + + return result; + } finally { + setReactActEnvironment(previousActEnvironment); + } + }, + eventWrapper: (cb) => { + let result; + act(() => { + result = cb(); + }); + return result; + }, + }); + } catch (e) { + console.log(e); + // @storybook/test might not be available + } + }, + renderToCanvas: async (renderContext, canvasElement) => { if (renderContext.storyContext.testingLibraryRender == null) { - return reactProjectAnnotations.renderToCanvas(renderContext, canvasElement); + let unmount: () => void; + + await act(async () => { + unmount = await reactProjectAnnotations.renderToCanvas(renderContext, canvasElement); + }); + + return async () => { + await act(() => { + unmount(); + }); + }; } const { storyContext: { context, unboundStoryFn: Story, testingLibraryRender: render }, @@ -149,3 +207,19 @@ export function composeStories; } + +/** The function is used to configure jest's fake timers in environments where React's act is enabled */ +function jestFakeTimersAreEnabled() { + // @ts-expect-error global jest + if (typeof jest !== 'undefined' && jest !== null) { + return ( + // legacy timers + + // eslint-disable-next-line no-underscore-dangle + (setTimeout as any)._isMockFunction === true || // modern timers + Object.prototype.hasOwnProperty.call(setTimeout, 'clock') + ); + } + + return false; +} diff --git a/code/renderers/react/src/renderToCanvas.tsx b/code/renderers/react/src/renderToCanvas.tsx index f3a4231d078c..3ae6136f9582 100644 --- a/code/renderers/react/src/renderToCanvas.tsx +++ b/code/renderers/react/src/renderToCanvas.tsx @@ -5,6 +5,7 @@ import type { RenderContext } from 'storybook/internal/types'; import { global } from '@storybook/global'; +import { getReactActEnvironment } from './act-compat'; import type { ReactRenderer, StoryContext } from './types'; const { FRAMEWORK_OPTIONS } = global; @@ -57,7 +58,11 @@ export async function renderToCanvas( const { renderElement, unmountElement } = await import('@storybook/react-dom-shim'); const Story = unboundStoryFn as FC>; - const content = ( + const isActEnabled = getReactActEnvironment(); + + const content = isActEnabled ? ( + + ) : ( diff --git a/code/vitest-setup.ts b/code/vitest-setup.ts index 8edd64c36314..5eba16740d1d 100644 --- a/code/vitest-setup.ts +++ b/code/vitest-setup.ts @@ -7,6 +7,7 @@ const ignoreList = [ (error: any) => error.message.includes('":nth-child" is potentially unsafe'), (error: any) => error.message.includes('":first-child" is potentially unsafe'), (error: any) => error.message.match(/Browserslist: .* is outdated. Please run:/), + (error: any) => error.message.includes('Consider adding an error boundary'), (error: any) => error.message.includes('react-async-component-lifecycle-hooks') && error.stack.includes('addons/knobs/src/components/__tests__/Options.js'), diff --git a/code/yarn.lock b/code/yarn.lock index 9b8a426f6016..397b6cd54ff5 100644 --- a/code/yarn.lock +++ b/code/yarn.lock @@ -2490,7 +2490,7 @@ __metadata: languageName: node linkType: hard -"@emnapi/runtime@npm:^1.1.1": +"@emnapi/runtime@npm:^1.1.1, @emnapi/runtime@npm:^1.2.0": version: 1.2.0 resolution: "@emnapi/runtime@npm:1.2.0" dependencies: @@ -3424,6 +3424,18 @@ __metadata: languageName: node linkType: hard +"@img/sharp-darwin-arm64@npm:0.33.5": + version: 0.33.5 + resolution: "@img/sharp-darwin-arm64@npm:0.33.5" + dependencies: + "@img/sharp-libvips-darwin-arm64": "npm:1.0.4" + dependenciesMeta: + "@img/sharp-libvips-darwin-arm64": + optional: true + conditions: os=darwin & cpu=arm64 + languageName: node + linkType: hard + "@img/sharp-darwin-x64@npm:0.33.4": version: 0.33.4 resolution: "@img/sharp-darwin-x64@npm:0.33.4" @@ -3436,6 +3448,18 @@ __metadata: languageName: node linkType: hard +"@img/sharp-darwin-x64@npm:0.33.5": + version: 0.33.5 + resolution: "@img/sharp-darwin-x64@npm:0.33.5" + dependencies: + "@img/sharp-libvips-darwin-x64": "npm:1.0.4" + dependenciesMeta: + "@img/sharp-libvips-darwin-x64": + optional: true + conditions: os=darwin & cpu=x64 + languageName: node + linkType: hard + "@img/sharp-libvips-darwin-arm64@npm:1.0.2": version: 1.0.2 resolution: "@img/sharp-libvips-darwin-arm64@npm:1.0.2" @@ -3443,6 +3467,13 @@ __metadata: languageName: node linkType: hard +"@img/sharp-libvips-darwin-arm64@npm:1.0.4": + version: 1.0.4 + resolution: "@img/sharp-libvips-darwin-arm64@npm:1.0.4" + conditions: os=darwin & cpu=arm64 + languageName: node + linkType: hard + "@img/sharp-libvips-darwin-x64@npm:1.0.2": version: 1.0.2 resolution: "@img/sharp-libvips-darwin-x64@npm:1.0.2" @@ -3450,6 +3481,13 @@ __metadata: languageName: node linkType: hard +"@img/sharp-libvips-darwin-x64@npm:1.0.4": + version: 1.0.4 + resolution: "@img/sharp-libvips-darwin-x64@npm:1.0.4" + conditions: os=darwin & cpu=x64 + languageName: node + linkType: hard + "@img/sharp-libvips-linux-arm64@npm:1.0.2": version: 1.0.2 resolution: "@img/sharp-libvips-linux-arm64@npm:1.0.2" @@ -3457,6 +3495,13 @@ __metadata: languageName: node linkType: hard +"@img/sharp-libvips-linux-arm64@npm:1.0.4": + version: 1.0.4 + resolution: "@img/sharp-libvips-linux-arm64@npm:1.0.4" + conditions: os=linux & cpu=arm64 & libc=glibc + languageName: node + linkType: hard + "@img/sharp-libvips-linux-arm@npm:1.0.2": version: 1.0.2 resolution: "@img/sharp-libvips-linux-arm@npm:1.0.2" @@ -3464,6 +3509,13 @@ __metadata: languageName: node linkType: hard +"@img/sharp-libvips-linux-arm@npm:1.0.5": + version: 1.0.5 + resolution: "@img/sharp-libvips-linux-arm@npm:1.0.5" + conditions: os=linux & cpu=arm & libc=glibc + languageName: node + linkType: hard + "@img/sharp-libvips-linux-s390x@npm:1.0.2": version: 1.0.2 resolution: "@img/sharp-libvips-linux-s390x@npm:1.0.2" @@ -3471,6 +3523,13 @@ __metadata: languageName: node linkType: hard +"@img/sharp-libvips-linux-s390x@npm:1.0.4": + version: 1.0.4 + resolution: "@img/sharp-libvips-linux-s390x@npm:1.0.4" + conditions: os=linux & cpu=s390x & libc=glibc + languageName: node + linkType: hard + "@img/sharp-libvips-linux-x64@npm:1.0.2": version: 1.0.2 resolution: "@img/sharp-libvips-linux-x64@npm:1.0.2" @@ -3478,6 +3537,13 @@ __metadata: languageName: node linkType: hard +"@img/sharp-libvips-linux-x64@npm:1.0.4": + version: 1.0.4 + resolution: "@img/sharp-libvips-linux-x64@npm:1.0.4" + conditions: os=linux & cpu=x64 & libc=glibc + languageName: node + linkType: hard + "@img/sharp-libvips-linuxmusl-arm64@npm:1.0.2": version: 1.0.2 resolution: "@img/sharp-libvips-linuxmusl-arm64@npm:1.0.2" @@ -3485,6 +3551,13 @@ __metadata: languageName: node linkType: hard +"@img/sharp-libvips-linuxmusl-arm64@npm:1.0.4": + version: 1.0.4 + resolution: "@img/sharp-libvips-linuxmusl-arm64@npm:1.0.4" + conditions: os=linux & cpu=arm64 & libc=musl + languageName: node + linkType: hard + "@img/sharp-libvips-linuxmusl-x64@npm:1.0.2": version: 1.0.2 resolution: "@img/sharp-libvips-linuxmusl-x64@npm:1.0.2" @@ -3492,6 +3565,13 @@ __metadata: languageName: node linkType: hard +"@img/sharp-libvips-linuxmusl-x64@npm:1.0.4": + version: 1.0.4 + resolution: "@img/sharp-libvips-linuxmusl-x64@npm:1.0.4" + conditions: os=linux & cpu=x64 & libc=musl + languageName: node + linkType: hard + "@img/sharp-linux-arm64@npm:0.33.4": version: 0.33.4 resolution: "@img/sharp-linux-arm64@npm:0.33.4" @@ -3504,6 +3584,18 @@ __metadata: languageName: node linkType: hard +"@img/sharp-linux-arm64@npm:0.33.5": + version: 0.33.5 + resolution: "@img/sharp-linux-arm64@npm:0.33.5" + dependencies: + "@img/sharp-libvips-linux-arm64": "npm:1.0.4" + dependenciesMeta: + "@img/sharp-libvips-linux-arm64": + optional: true + conditions: os=linux & cpu=arm64 & libc=glibc + languageName: node + linkType: hard + "@img/sharp-linux-arm@npm:0.33.4": version: 0.33.4 resolution: "@img/sharp-linux-arm@npm:0.33.4" @@ -3516,6 +3608,18 @@ __metadata: languageName: node linkType: hard +"@img/sharp-linux-arm@npm:0.33.5": + version: 0.33.5 + resolution: "@img/sharp-linux-arm@npm:0.33.5" + dependencies: + "@img/sharp-libvips-linux-arm": "npm:1.0.5" + dependenciesMeta: + "@img/sharp-libvips-linux-arm": + optional: true + conditions: os=linux & cpu=arm & libc=glibc + languageName: node + linkType: hard + "@img/sharp-linux-s390x@npm:0.33.4": version: 0.33.4 resolution: "@img/sharp-linux-s390x@npm:0.33.4" @@ -3528,6 +3632,18 @@ __metadata: languageName: node linkType: hard +"@img/sharp-linux-s390x@npm:0.33.5": + version: 0.33.5 + resolution: "@img/sharp-linux-s390x@npm:0.33.5" + dependencies: + "@img/sharp-libvips-linux-s390x": "npm:1.0.4" + dependenciesMeta: + "@img/sharp-libvips-linux-s390x": + optional: true + conditions: os=linux & cpu=s390x & libc=glibc + languageName: node + linkType: hard + "@img/sharp-linux-x64@npm:0.33.4": version: 0.33.4 resolution: "@img/sharp-linux-x64@npm:0.33.4" @@ -3540,6 +3656,18 @@ __metadata: languageName: node linkType: hard +"@img/sharp-linux-x64@npm:0.33.5": + version: 0.33.5 + resolution: "@img/sharp-linux-x64@npm:0.33.5" + dependencies: + "@img/sharp-libvips-linux-x64": "npm:1.0.4" + dependenciesMeta: + "@img/sharp-libvips-linux-x64": + optional: true + conditions: os=linux & cpu=x64 & libc=glibc + languageName: node + linkType: hard + "@img/sharp-linuxmusl-arm64@npm:0.33.4": version: 0.33.4 resolution: "@img/sharp-linuxmusl-arm64@npm:0.33.4" @@ -3552,6 +3680,18 @@ __metadata: languageName: node linkType: hard +"@img/sharp-linuxmusl-arm64@npm:0.33.5": + version: 0.33.5 + resolution: "@img/sharp-linuxmusl-arm64@npm:0.33.5" + dependencies: + "@img/sharp-libvips-linuxmusl-arm64": "npm:1.0.4" + dependenciesMeta: + "@img/sharp-libvips-linuxmusl-arm64": + optional: true + conditions: os=linux & cpu=arm64 & libc=musl + languageName: node + linkType: hard + "@img/sharp-linuxmusl-x64@npm:0.33.4": version: 0.33.4 resolution: "@img/sharp-linuxmusl-x64@npm:0.33.4" @@ -3564,6 +3704,18 @@ __metadata: languageName: node linkType: hard +"@img/sharp-linuxmusl-x64@npm:0.33.5": + version: 0.33.5 + resolution: "@img/sharp-linuxmusl-x64@npm:0.33.5" + dependencies: + "@img/sharp-libvips-linuxmusl-x64": "npm:1.0.4" + dependenciesMeta: + "@img/sharp-libvips-linuxmusl-x64": + optional: true + conditions: os=linux & cpu=x64 & libc=musl + languageName: node + linkType: hard + "@img/sharp-wasm32@npm:0.33.4": version: 0.33.4 resolution: "@img/sharp-wasm32@npm:0.33.4" @@ -3573,6 +3725,15 @@ __metadata: languageName: node linkType: hard +"@img/sharp-wasm32@npm:0.33.5": + version: 0.33.5 + resolution: "@img/sharp-wasm32@npm:0.33.5" + dependencies: + "@emnapi/runtime": "npm:^1.2.0" + conditions: cpu=wasm32 + languageName: node + linkType: hard + "@img/sharp-win32-ia32@npm:0.33.4": version: 0.33.4 resolution: "@img/sharp-win32-ia32@npm:0.33.4" @@ -3580,6 +3741,13 @@ __metadata: languageName: node linkType: hard +"@img/sharp-win32-ia32@npm:0.33.5": + version: 0.33.5 + resolution: "@img/sharp-win32-ia32@npm:0.33.5" + conditions: os=win32 & cpu=ia32 + languageName: node + linkType: hard + "@img/sharp-win32-x64@npm:0.33.4": version: 0.33.4 resolution: "@img/sharp-win32-x64@npm:0.33.4" @@ -3587,6 +3755,13 @@ __metadata: languageName: node linkType: hard +"@img/sharp-win32-x64@npm:0.33.5": + version: 0.33.5 + resolution: "@img/sharp-win32-x64@npm:0.33.5" + conditions: os=win32 & cpu=x64 + languageName: node + linkType: hard + "@inquirer/confirm@npm:^3.0.0": version: 3.1.20 resolution: "@inquirer/confirm@npm:3.1.20" @@ -3903,13 +4078,20 @@ __metadata: languageName: node linkType: hard -"@next/env@npm:14.2.5, @next/env@npm:^14.2.5": +"@next/env@npm:14.2.5": version: 14.2.5 resolution: "@next/env@npm:14.2.5" checksum: 10c0/63d8b88ac450b3c37940a9e2119a63a1074aca89908574ade6157a8aa295275dcb3ac5f69e00883fc55d0f12963b73b74e87ba32a5768a489f9609c6be57b699 languageName: node linkType: hard +"@next/env@npm:^14.2.5": + version: 14.2.7 + resolution: "@next/env@npm:14.2.7" + checksum: 10c0/1cda023007acda4d47036a25fba0e039d9b2df9c3770651dc289207e0537506675546c02b5b574fe92bb1adc1c887d948d5cb630673aa572754278b82d150b7e + languageName: node + linkType: hard + "@next/swc-darwin-arm64@npm:14.2.5": version: 14.2.5 resolution: "@next/swc-darwin-arm64@npm:14.2.5" @@ -6668,11 +6850,14 @@ __metadata: type-fest: "npm:~2.19" util-deprecate: "npm:^1.0.2" peerDependencies: + "@storybook/test": "workspace:*" react: ^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0-beta react-dom: ^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0-beta storybook: "workspace:^" typescript: ">= 4.2.x" peerDependenciesMeta: + "@storybook/test": + optional: true typescript: optional: true languageName: unknown @@ -25319,6 +25504,15 @@ __metadata: languageName: node linkType: hard +"semver@npm:^7.6.3": + version: 7.6.3 + resolution: "semver@npm:7.6.3" + bin: + semver: bin/semver.js + checksum: 10c0/88f33e148b210c153873cb08cfe1e281d518aaa9a666d4d148add6560db5cd3c582f3a08ccb91f38d5f379ead256da9931234ed122057f40bb5766e65e58adaf + languageName: node + linkType: hard + "send@npm:0.18.0": version: 0.18.0 resolution: "send@npm:0.18.0" @@ -25470,7 +25664,7 @@ __metadata: languageName: node linkType: hard -"sharp@npm:^0.33.3, sharp@npm:^0.33.4": +"sharp@npm:^0.33.3": version: 0.33.4 resolution: "sharp@npm:0.33.4" dependencies: @@ -25539,6 +25733,75 @@ __metadata: languageName: node linkType: hard +"sharp@npm:^0.33.4": + version: 0.33.5 + resolution: "sharp@npm:0.33.5" + dependencies: + "@img/sharp-darwin-arm64": "npm:0.33.5" + "@img/sharp-darwin-x64": "npm:0.33.5" + "@img/sharp-libvips-darwin-arm64": "npm:1.0.4" + "@img/sharp-libvips-darwin-x64": "npm:1.0.4" + "@img/sharp-libvips-linux-arm": "npm:1.0.5" + "@img/sharp-libvips-linux-arm64": "npm:1.0.4" + "@img/sharp-libvips-linux-s390x": "npm:1.0.4" + "@img/sharp-libvips-linux-x64": "npm:1.0.4" + "@img/sharp-libvips-linuxmusl-arm64": "npm:1.0.4" + "@img/sharp-libvips-linuxmusl-x64": "npm:1.0.4" + "@img/sharp-linux-arm": "npm:0.33.5" + "@img/sharp-linux-arm64": "npm:0.33.5" + "@img/sharp-linux-s390x": "npm:0.33.5" + "@img/sharp-linux-x64": "npm:0.33.5" + "@img/sharp-linuxmusl-arm64": "npm:0.33.5" + "@img/sharp-linuxmusl-x64": "npm:0.33.5" + "@img/sharp-wasm32": "npm:0.33.5" + "@img/sharp-win32-ia32": "npm:0.33.5" + "@img/sharp-win32-x64": "npm:0.33.5" + color: "npm:^4.2.3" + detect-libc: "npm:^2.0.3" + semver: "npm:^7.6.3" + dependenciesMeta: + "@img/sharp-darwin-arm64": + optional: true + "@img/sharp-darwin-x64": + optional: true + "@img/sharp-libvips-darwin-arm64": + optional: true + "@img/sharp-libvips-darwin-x64": + optional: true + "@img/sharp-libvips-linux-arm": + optional: true + "@img/sharp-libvips-linux-arm64": + optional: true + "@img/sharp-libvips-linux-s390x": + optional: true + "@img/sharp-libvips-linux-x64": + optional: true + "@img/sharp-libvips-linuxmusl-arm64": + optional: true + "@img/sharp-libvips-linuxmusl-x64": + optional: true + "@img/sharp-linux-arm": + optional: true + "@img/sharp-linux-arm64": + optional: true + "@img/sharp-linux-s390x": + optional: true + "@img/sharp-linux-x64": + optional: true + "@img/sharp-linuxmusl-arm64": + optional: true + "@img/sharp-linuxmusl-x64": + optional: true + "@img/sharp-wasm32": + optional: true + "@img/sharp-win32-ia32": + optional: true + "@img/sharp-win32-x64": + optional: true + checksum: 10c0/6b81421ddfe6ee524d8d77e325c5e147fef22884e1c7b1656dfd89a88d7025894115da02d5f984261bf2e6daa16f98cadd1721c4ba408b4212b1d2a60f233484 + languageName: node + linkType: hard + "shebang-command@npm:^1.2.0": version: 1.2.0 resolution: "shebang-command@npm:1.2.0" From da21bfecb75a64abeb40a23fa01ec32daccbb40d Mon Sep 17 00:00:00 2001 From: Valentin Palkovic Date: Wed, 4 Sep 2024 13:00:05 +0200 Subject: [PATCH 02/13] Next.js: Update webpack configuration to support react-dom/test-utils --- code/frameworks/nextjs/src/config/webpack.ts | 10 +++++++++- 1 file changed, 9 insertions(+), 1 deletion(-) diff --git a/code/frameworks/nextjs/src/config/webpack.ts b/code/frameworks/nextjs/src/config/webpack.ts index 3860207e124e..3e0d758c1514 100644 --- a/code/frameworks/nextjs/src/config/webpack.ts +++ b/code/frameworks/nextjs/src/config/webpack.ts @@ -25,9 +25,17 @@ export const configureConfig = async ({ if (tryResolve('next/dist/compiled/react')) { addScopedAlias(baseConfig, 'react', 'next/dist/compiled/react'); } + if (tryResolve('next/dist/compiled/react-dom/cjs/react-dom-test-utils.production.js')) { + addScopedAlias( + baseConfig, + 'react-dom/test-utils', + 'next/dist/compiled/react-dom/cjs/react-dom-test-utils.production.js' + ); + } if (tryResolve('next/dist/compiled/react-dom')) { - addScopedAlias(baseConfig, 'react-dom', 'next/dist/compiled/react-dom'); + addScopedAlias(baseConfig, 'react-dom$', 'next/dist/compiled/react-dom'); } + setupRuntimeConfig(baseConfig, nextConfig); return nextConfig; From da2aee4928b7cc5ba104df99ec9a62873a4b3b3b Mon Sep 17 00:00:00 2001 From: Jeppe Reinhold Date: Thu, 5 Sep 2024 09:12:51 +0200 Subject: [PATCH 03/13] name decorators for easier debugging --- code/core/template/stories/preview.ts | 16 +- code/frameworks/sveltekit/src/preview.ts | 222 +++++++++++------------ 2 files changed, 119 insertions(+), 119 deletions(-) diff --git a/code/core/template/stories/preview.ts b/code/core/template/stories/preview.ts index 4cd4c64abff5..bba2716864bc 100644 --- a/code/core/template/stories/preview.ts +++ b/code/core/template/stories/preview.ts @@ -30,14 +30,14 @@ export const parameters = { export const loaders = [async () => ({ projectValue: 2 })]; -export const decorators = [ - (storyFn: PartialStoryFn, context: StoryContext) => { - if (context.parameters.useProjectDecorator) { - return storyFn({ args: { ...context.args, text: `project ${context.args.text}` } }); - } - return storyFn(); - }, -]; +const testProjectDecorator = (storyFn: PartialStoryFn, context: StoryContext) => { + if (context.parameters.useProjectDecorator) { + return storyFn({ args: { ...context.args, text: `project ${context.args.text}` } }); + } + return storyFn(); +}; + +export const decorators = [testProjectDecorator]; export const initialGlobals = { foo: 'fooValue', diff --git a/code/frameworks/sveltekit/src/preview.ts b/code/frameworks/sveltekit/src/preview.ts index f93c06862c29..c93cbde37a55 100644 --- a/code/frameworks/sveltekit/src/preview.ts +++ b/code/frameworks/sveltekit/src/preview.ts @@ -15,125 +15,125 @@ const normalizeHrefConfig = (hrefConfig: HrefConfig): NormalizedHrefConfig => { return hrefConfig; }; -export const decorators: Decorator[] = [ - (Story, ctx) => { - const svelteKitParameters: SvelteKitParameters = ctx.parameters?.sveltekit_experimental ?? {}; - setPage(svelteKitParameters?.stores?.page); - setNavigating(svelteKitParameters?.stores?.navigating); - setUpdated(svelteKitParameters?.stores?.updated); - setAfterNavigateArgument(svelteKitParameters?.navigation?.afterNavigate); +const svelteKitMocksDecorator = (Story, ctx) => { + const svelteKitParameters: SvelteKitParameters = ctx.parameters?.sveltekit_experimental ?? {}; + setPage(svelteKitParameters?.stores?.page); + setNavigating(svelteKitParameters?.stores?.navigating); + setUpdated(svelteKitParameters?.stores?.updated); + setAfterNavigateArgument(svelteKitParameters?.navigation?.afterNavigate); - onMount(() => { - const globalClickListener = (e: MouseEvent) => { - // we add a global click event listener and we check if there's a link in the composedPath - const path = e.composedPath(); - const element = path.findLast((el) => el instanceof HTMLElement && el.tagName === 'A'); - if (element && element instanceof HTMLAnchorElement) { - // if the element is an a-tag we get the href of the element - // and compare it to the hrefs-parameter set by the user - const to = element.getAttribute('href'); - if (!to) { - return; - } - e.preventDefault(); - const defaultActionCallback = () => action('navigate')(to, e); - if (!svelteKitParameters.hrefs) { - defaultActionCallback(); - return; - } - - let callDefaultCallback = true; - // we loop over every href set by the user and check if the href matches - // if it does we call the callback provided by the user and disable the default callback - Object.entries(svelteKitParameters.hrefs).forEach(([href, hrefConfig]) => { - const { callback, asRegex } = normalizeHrefConfig(hrefConfig); - const isMatch = asRegex ? new RegExp(href).test(to) : to === href; - if (isMatch) { - callDefaultCallback = false; - callback?.(to, e); - } - }); - if (callDefaultCallback) { - defaultActionCallback(); - } + onMount(() => { + const globalClickListener = (e: MouseEvent) => { + // we add a global click event listener and we check if there's a link in the composedPath + const path = e.composedPath(); + const element = path.findLast((el) => el instanceof HTMLElement && el.tagName === 'A'); + if (element && element instanceof HTMLAnchorElement) { + // if the element is an a-tag we get the href of the element + // and compare it to the hrefs-parameter set by the user + const to = element.getAttribute('href'); + if (!to) { + return; + } + e.preventDefault(); + const defaultActionCallback = () => action('navigate')(to, e); + if (!svelteKitParameters.hrefs) { + defaultActionCallback(); + return; } - }; - - /** - * Function that create and add listeners for the event that are emitted by the mocked - * functions. The event name is based on the function name - * - * Eg. storybook:goto, storybook:invalidateAll - * - * @param baseModule The base module where the function lives (navigation|forms) - * @param functions The list of functions in that module that emit events - * @param {boolean} [defaultToAction] The list of functions in that module that emit events - * @returns A function to remove all the listener added - */ - function createListeners( - baseModule: keyof SvelteKitParameters, - functions: string[], - defaultToAction?: boolean - ) { - // the array of every added listener, we can use this in the return function - // to clean them - const toRemove: Array<{ - eventType: string; - listener: (event: { detail: any[] }) => void; - }> = []; - functions.forEach((func) => { - // we loop over every function and check if the user actually passed - // a function in sveltekit_experimental[baseModule][func] eg. sveltekit_experimental.navigation.goto - const hasFunction = - (svelteKitParameters as any)[baseModule]?.[func] && - (svelteKitParameters as any)[baseModule][func] instanceof Function; - // if we default to an action we still add the listener (this will be the case for goto, invalidate, invalidateAll) - if (hasFunction || defaultToAction) { - // we create the listener that will just get the detail array from the custom element - // and call the user provided function spreading this args in...this will basically call - // the function that the user provide with the same arguments the function is invoked to - // eg. if it calls goto("/my-route") inside the component the function sveltekit_experimental.navigation.goto - // it provided to storybook will be called with "/my-route" - const listener = ({ detail = [] as any[] }) => { - const args = Array.isArray(detail) ? detail : []; - // if it has a function in the parameters we call that function - // otherwise we invoke the action - const fnToCall = hasFunction - ? (svelteKitParameters as any)[baseModule][func] - : action(func); - fnToCall(...args); - }; - const eventType = `storybook:${func}`; - toRemove.push({ eventType, listener }); - // add the listener to window - (window.addEventListener as any)(eventType, listener); + let callDefaultCallback = true; + // we loop over every href set by the user and check if the href matches + // if it does we call the callback provided by the user and disable the default callback + Object.entries(svelteKitParameters.hrefs).forEach(([href, hrefConfig]) => { + const { callback, asRegex } = normalizeHrefConfig(hrefConfig); + const isMatch = asRegex ? new RegExp(href).test(to) : to === href; + if (isMatch) { + callDefaultCallback = false; + callback?.(to, e); } }); - return () => { - // loop over every listener added and remove them - toRemove.forEach(({ eventType, listener }) => { - // @ts-expect-error apparently you can't remove a custom listener to the window with TS - window.removeEventListener(eventType, listener); - }); - }; + if (callDefaultCallback) { + defaultActionCallback(); + } } + }; - const removeNavigationListeners = createListeners( - 'navigation', - ['goto', 'invalidate', 'invalidateAll', 'pushState', 'replaceState'], - true - ); - const removeFormsListeners = createListeners('forms', ['enhance']); - window.addEventListener('click', globalClickListener); + /** + * Function that create and add listeners for the event that are emitted by the mocked + * functions. The event name is based on the function name + * + * Eg. storybook:goto, storybook:invalidateAll + * + * @param baseModule The base module where the function lives (navigation|forms) + * @param functions The list of functions in that module that emit events + * @param {boolean} [defaultToAction] The list of functions in that module that emit events + * @returns A function to remove all the listener added + */ + function createListeners( + baseModule: keyof SvelteKitParameters, + functions: string[], + defaultToAction?: boolean + ) { + // the array of every added listener, we can use this in the return function + // to clean them + const toRemove: Array<{ + eventType: string; + listener: (event: { detail: any[] }) => void; + }> = []; + functions.forEach((func) => { + // we loop over every function and check if the user actually passed + // a function in sveltekit_experimental[baseModule][func] eg. sveltekit_experimental.navigation.goto + const hasFunction = + (svelteKitParameters as any)[baseModule]?.[func] && + (svelteKitParameters as any)[baseModule][func] instanceof Function; + // if we default to an action we still add the listener (this will be the case for goto, invalidate, invalidateAll) + if (hasFunction || defaultToAction) { + // we create the listener that will just get the detail array from the custom element + // and call the user provided function spreading this args in...this will basically call + // the function that the user provide with the same arguments the function is invoked to + // eg. if it calls goto("/my-route") inside the component the function sveltekit_experimental.navigation.goto + // it provided to storybook will be called with "/my-route" + const listener = ({ detail = [] as any[] }) => { + const args = Array.isArray(detail) ? detail : []; + // if it has a function in the parameters we call that function + // otherwise we invoke the action + const fnToCall = hasFunction + ? (svelteKitParameters as any)[baseModule][func] + : action(func); + fnToCall(...args); + }; + const eventType = `storybook:${func}`; + toRemove.push({ eventType, listener }); + // add the listener to window + (window.addEventListener as any)(eventType, listener); + } + }); return () => { - window.removeEventListener('click', globalClickListener); - removeNavigationListeners(); - removeFormsListeners(); + // loop over every listener added and remove them + toRemove.forEach(({ eventType, listener }) => { + // @ts-expect-error apparently you can't remove a custom listener to the window with TS + window.removeEventListener(eventType, listener); + }); }; - }); + } + + const removeNavigationListeners = createListeners( + 'navigation', + ['goto', 'invalidate', 'invalidateAll', 'pushState', 'replaceState'], + true + ); + const removeFormsListeners = createListeners('forms', ['enhance']); + window.addEventListener('click', globalClickListener); + + return () => { + window.removeEventListener('click', globalClickListener); + removeNavigationListeners(); + removeFormsListeners(); + }; + }); + + return Story(); +}; - return Story(); - }, -]; +export const decorators: Decorator[] = [svelteKitMocksDecorator]; From 4f1b73d8ff7c5c49323912b9e420385cce45946c Mon Sep 17 00:00:00 2001 From: Jeppe Reinhold Date: Thu, 5 Sep 2024 09:13:27 +0200 Subject: [PATCH 04/13] fix duplicate default annotations --- .../modules/store/csf/portable-stories.ts | 16 +++++++++++----- 1 file changed, 11 insertions(+), 5 deletions(-) diff --git a/code/core/src/preview-api/modules/store/csf/portable-stories.ts b/code/core/src/preview-api/modules/store/csf/portable-stories.ts index 7adc83196eb1..1133e2e87a84 100644 --- a/code/core/src/preview-api/modules/store/csf/portable-stories.ts +++ b/code/core/src/preview-api/modules/store/csf/portable-stories.ts @@ -74,13 +74,19 @@ export function setProjectAnnotations( | NamedOrDefaultProjectAnnotations[] ): NormalizedProjectAnnotations { const annotations = Array.isArray(projectAnnotations) ? projectAnnotations : [projectAnnotations]; - if (globalThis.defaultProjectAnnotations) { - annotations.push(globalThis.defaultProjectAnnotations); - } - globalThis.globalProjectAnnotations = composeConfigs(annotations.map(extractAnnotation)); - return globalThis.globalProjectAnnotations; + /* + We must return the composition of default and global annotations here + To ensure that the user has the full project annotations, eg. when running + + const projectAnnotations = setProjectAnnotations(...); + beforeAll(projectAnnotations.beforeAll) + */ + return composeConfigs([ + globalThis.defaultProjectAnnotations, + globalThis.globalProjectAnnotations, + ]); } const cleanups: CleanupCallback[] = []; From 8a7d8eee7ce16030111e009779b9fb15a70099c0 Mon Sep 17 00:00:00 2001 From: Jeppe Reinhold Date: Thu, 5 Sep 2024 10:53:11 +0200 Subject: [PATCH 05/13] fix decorator type --- code/frameworks/sveltekit/src/preview.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/code/frameworks/sveltekit/src/preview.ts b/code/frameworks/sveltekit/src/preview.ts index c93cbde37a55..6eb8a816dd12 100644 --- a/code/frameworks/sveltekit/src/preview.ts +++ b/code/frameworks/sveltekit/src/preview.ts @@ -15,7 +15,7 @@ const normalizeHrefConfig = (hrefConfig: HrefConfig): NormalizedHrefConfig => { return hrefConfig; }; -const svelteKitMocksDecorator = (Story, ctx) => { +const svelteKitMocksDecorator: Decorator = (Story, ctx) => { const svelteKitParameters: SvelteKitParameters = ctx.parameters?.sveltekit_experimental ?? {}; setPage(svelteKitParameters?.stores?.page); setNavigating(svelteKitParameters?.stores?.navigating); From e4697f9d782700879ed2b4706965fcba8ff7a988 Mon Sep 17 00:00:00 2001 From: Jeppe Reinhold Date: Thu, 5 Sep 2024 13:25:27 +0200 Subject: [PATCH 06/13] fix composing undefined defaultProjectAnnotations --- .../src/preview-api/modules/store/csf/portable-stories.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/code/core/src/preview-api/modules/store/csf/portable-stories.ts b/code/core/src/preview-api/modules/store/csf/portable-stories.ts index 1133e2e87a84..fd50bd57c2dd 100644 --- a/code/core/src/preview-api/modules/store/csf/portable-stories.ts +++ b/code/core/src/preview-api/modules/store/csf/portable-stories.ts @@ -84,8 +84,8 @@ export function setProjectAnnotations( beforeAll(projectAnnotations.beforeAll) */ return composeConfigs([ - globalThis.defaultProjectAnnotations, - globalThis.globalProjectAnnotations, + globalThis.defaultProjectAnnotations ?? {}, + globalThis.globalProjectAnnotations ?? {}, ]); } From 758aaa1497d8ded23bc2f1c2e3662cf597ced881 Mon Sep 17 00:00:00 2001 From: Valentin Palkovic Date: Thu, 5 Sep 2024 13:29:09 +0200 Subject: [PATCH 07/13] Next.js-Vite: Update vite-plugin-storybook-nextjs --- code/frameworks/experimental-nextjs-vite/package.json | 2 +- code/yarn.lock | 10 +++++----- 2 files changed, 6 insertions(+), 6 deletions(-) diff --git a/code/frameworks/experimental-nextjs-vite/package.json b/code/frameworks/experimental-nextjs-vite/package.json index c0a70eaecffd..83e8bfc35993 100644 --- a/code/frameworks/experimental-nextjs-vite/package.json +++ b/code/frameworks/experimental-nextjs-vite/package.json @@ -99,7 +99,7 @@ "@storybook/react": "workspace:*", "@storybook/test": "workspace:*", "styled-jsx": "5.1.6", - "vite-plugin-storybook-nextjs": "^1.0.10" + "vite-plugin-storybook-nextjs": "^1.0.11" }, "devDependencies": { "@types/node": "^18.0.0", diff --git a/code/yarn.lock b/code/yarn.lock index c3b25a45ce54..b7dcbf2eaa77 100644 --- a/code/yarn.lock +++ b/code/yarn.lock @@ -6340,7 +6340,7 @@ __metadata: sharp: "npm:^0.33.3" styled-jsx: "npm:5.1.6" typescript: "npm:^5.3.2" - vite-plugin-storybook-nextjs: "npm:^1.0.10" + vite-plugin-storybook-nextjs: "npm:^1.0.11" peerDependencies: "@storybook/test": "workspace:*" next: ^14.1.0 @@ -28697,9 +28697,9 @@ __metadata: languageName: node linkType: hard -"vite-plugin-storybook-nextjs@npm:^1.0.10": - version: 1.0.10 - resolution: "vite-plugin-storybook-nextjs@npm:1.0.10" +"vite-plugin-storybook-nextjs@npm:^1.0.11": + version: 1.0.11 + resolution: "vite-plugin-storybook-nextjs@npm:1.0.11" dependencies: "@next/env": "npm:^14.2.5" image-size: "npm:^1.1.1" @@ -28715,7 +28715,7 @@ __metadata: dependenciesMeta: sharp: optional: true - checksum: 10c0/e0e373ef94e1761b871b2cc846c205a846901d93c7e61f9d9ee3c69740681e42e6403a7d61109c59f2d98d5829476c3e6d4e9d5a329c4bd51e758b763fa8ea9e + checksum: 10c0/9652b76c13a682b688d9a4f617b1a66263f25f395a99af8e258bedef4f3b3ce1c856ec1ff66cc0359d6aedc96adee9750fd6b0432514dd575ad7896cd1de70df languageName: node linkType: hard From 714913d6443c524666ef81de37d4e59709712b33 Mon Sep 17 00:00:00 2001 From: Valentin Palkovic Date: Thu, 5 Sep 2024 15:33:36 +0200 Subject: [PATCH 08/13] Next.js: Fix react-dom/test-utils aliasing --- code/frameworks/nextjs/src/config/webpack.ts | 15 ++++++++++++++- code/renderers/react/src/act-compat.ts | 8 ++++++-- 2 files changed, 20 insertions(+), 3 deletions(-) diff --git a/code/frameworks/nextjs/src/config/webpack.ts b/code/frameworks/nextjs/src/config/webpack.ts index 3e0d758c1514..57e7caa47bbe 100644 --- a/code/frameworks/nextjs/src/config/webpack.ts +++ b/code/frameworks/nextjs/src/config/webpack.ts @@ -20,6 +20,9 @@ export const configureConfig = async ({ nextConfigPath?: string; }): Promise => { const nextConfig = await resolveNextConfig({ nextConfigPath }); + baseConfig.resolve ??= {}; + baseConfig.resolve.alias ??= {}; + const aliasConfig = baseConfig.resolve.alias; addScopedAlias(baseConfig, 'next/config'); if (tryResolve('next/dist/compiled/react')) { @@ -31,9 +34,19 @@ export const configureConfig = async ({ 'react-dom/test-utils', 'next/dist/compiled/react-dom/cjs/react-dom-test-utils.production.js' ); + } else { + const name = 'react-dom/test-utils'; + if (Array.isArray(aliasConfig)) { + aliasConfig.push({ + name, + alias: name, + }); + } else { + aliasConfig[name] = name; + } } if (tryResolve('next/dist/compiled/react-dom')) { - addScopedAlias(baseConfig, 'react-dom$', 'next/dist/compiled/react-dom'); + addScopedAlias(baseConfig, 'react-dom', 'next/dist/compiled/react-dom'); } setupRuntimeConfig(baseConfig, nextConfig); diff --git a/code/renderers/react/src/act-compat.ts b/code/renderers/react/src/act-compat.ts index afe1cc902316..31b4fb72e54f 100644 --- a/code/renderers/react/src/act-compat.ts +++ b/code/renderers/react/src/act-compat.ts @@ -8,8 +8,12 @@ declare const globalThis: { IS_REACT_ACT_ENVIRONMENT: boolean; }; -// @ts-expect-error act might not be available in some versions of React -const reactAct = typeof React.act === 'function' ? React.act : DeprecatedReactTestUtils.act; +const reactAct = + // @ts-expect-error act might not be available in some versions of React + typeof React.act === 'function' + ? // @ts-expect-error act might not be available in some versions of React + React.act + : DeprecatedReactTestUtils.act ?? (async (cb: () => Promise | void) => cb()); export function setReactActEnvironment(isReactActEnvironment: boolean) { globalThis.IS_REACT_ACT_ENVIRONMENT = isReactActEnvironment; From ea1a533a4b4e0017fb1a93baaf76c51be25fe66b Mon Sep 17 00:00:00 2001 From: Valentin Palkovic Date: Fri, 6 Sep 2024 11:46:32 +0200 Subject: [PATCH 09/13] Fix Webpack aliasing --- code/frameworks/nextjs/src/config/webpack.ts | 31 ++++++++++---------- code/frameworks/nextjs/src/utils.ts | 20 ++++++++----- code/renderers/react/src/act-compat.ts | 5 +--- 3 files changed, 28 insertions(+), 28 deletions(-) diff --git a/code/frameworks/nextjs/src/config/webpack.ts b/code/frameworks/nextjs/src/config/webpack.ts index 57e7caa47bbe..a0ea2d47bded 100644 --- a/code/frameworks/nextjs/src/config/webpack.ts +++ b/code/frameworks/nextjs/src/config/webpack.ts @@ -2,7 +2,7 @@ import type { NextConfig } from 'next'; import type { Configuration as WebpackConfig } from 'webpack'; import { DefinePlugin } from 'webpack'; -import { addScopedAlias, resolveNextConfig } from '../utils'; +import { addScopedAlias, resolveNextConfig, setAlias } from '../utils'; const tryResolve = (path: string) => { try { @@ -20,33 +20,32 @@ export const configureConfig = async ({ nextConfigPath?: string; }): Promise => { const nextConfig = await resolveNextConfig({ nextConfigPath }); - baseConfig.resolve ??= {}; - baseConfig.resolve.alias ??= {}; - const aliasConfig = baseConfig.resolve.alias; addScopedAlias(baseConfig, 'next/config'); + + // @ts-expect-error We know that alias is an object + if (baseConfig.resolve?.alias?.['react-dom']) { + // Removing the alias to react-dom to avoid conflicts with the alias we are setting + // because the react-dom alias is an exact match and we need to alias separate parts of react-dom + // in different places + // @ts-expect-error We know that alias is an object + delete baseConfig.resolve.alias?.['react-dom']; + } + if (tryResolve('next/dist/compiled/react')) { addScopedAlias(baseConfig, 'react', 'next/dist/compiled/react'); } if (tryResolve('next/dist/compiled/react-dom/cjs/react-dom-test-utils.production.js')) { - addScopedAlias( + setAlias( baseConfig, 'react-dom/test-utils', 'next/dist/compiled/react-dom/cjs/react-dom-test-utils.production.js' ); - } else { - const name = 'react-dom/test-utils'; - if (Array.isArray(aliasConfig)) { - aliasConfig.push({ - name, - alias: name, - }); - } else { - aliasConfig[name] = name; - } } if (tryResolve('next/dist/compiled/react-dom')) { - addScopedAlias(baseConfig, 'react-dom', 'next/dist/compiled/react-dom'); + setAlias(baseConfig, 'react-dom$', 'next/dist/compiled/react-dom'); + setAlias(baseConfig, 'react-dom/client', 'next/dist/compiled/react-dom/client'); + setAlias(baseConfig, 'react-dom/server', 'next/dist/compiled/react-dom/server'); } setupRuntimeConfig(baseConfig, nextConfig); diff --git a/code/frameworks/nextjs/src/utils.ts b/code/frameworks/nextjs/src/utils.ts index 9c8abc6c88c8..198917513166 100644 --- a/code/frameworks/nextjs/src/utils.ts +++ b/code/frameworks/nextjs/src/utils.ts @@ -27,23 +27,27 @@ export const resolveNextConfig = async ({ return loadConfig(PHASE_DEVELOPMENT_SERVER, dir, undefined); }; -// This is to help the addon in development -// Without it, webpack resolves packages in its node_modules instead of the example's node_modules -export const addScopedAlias = (baseConfig: WebpackConfig, name: string, alias?: string): void => { +export function setAlias(baseConfig: WebpackConfig, name: string, alias: string) { baseConfig.resolve ??= {}; baseConfig.resolve.alias ??= {}; const aliasConfig = baseConfig.resolve.alias; - const scopedAlias = scopedResolve(`${alias ?? name}`); - if (Array.isArray(aliasConfig)) { aliasConfig.push({ name, - alias: scopedAlias, + alias, }); } else { - aliasConfig[name] = scopedAlias; + aliasConfig[name] = alias; } +} + +// This is to help the addon in development +// Without it, webpack resolves packages in its node_modules instead of the example's node_modules +export const addScopedAlias = (baseConfig: WebpackConfig, name: string, alias?: string): void => { + const scopedAlias = scopedResolve(`${alias ?? name}`); + + setAlias(baseConfig, name, scopedAlias); }; /** @@ -64,7 +68,7 @@ export const scopedResolve = (id: string): string => { let scopedModulePath; try { - // TODO: Remove in next major release (SB 8.0) and use the statement in the catch block per default instead + // TODO: Remove in next major release (SB 9.0) and use the statement in the catch block per default instead scopedModulePath = require.resolve(id, { paths: [resolve()] }); } catch (e) { scopedModulePath = require.resolve(id); diff --git a/code/renderers/react/src/act-compat.ts b/code/renderers/react/src/act-compat.ts index 31b4fb72e54f..3eab722d3bb1 100644 --- a/code/renderers/react/src/act-compat.ts +++ b/code/renderers/react/src/act-compat.ts @@ -10,10 +10,7 @@ declare const globalThis: { const reactAct = // @ts-expect-error act might not be available in some versions of React - typeof React.act === 'function' - ? // @ts-expect-error act might not be available in some versions of React - React.act - : DeprecatedReactTestUtils.act ?? (async (cb: () => Promise | void) => cb()); + typeof React.act === 'function' ? React.act : DeprecatedReactTestUtils.act; export function setReactActEnvironment(isReactActEnvironment: boolean) { globalThis.IS_REACT_ACT_ENVIRONMENT = isReactActEnvironment; From c24f22182edb47bf7da8fa4230b0b37cc054db62 Mon Sep 17 00:00:00 2001 From: Valentin Palkovic Date: Fri, 6 Sep 2024 15:21:04 +0200 Subject: [PATCH 10/13] Remove console.log --- code/renderers/react/src/portable-stories.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/code/renderers/react/src/portable-stories.tsx b/code/renderers/react/src/portable-stories.tsx index ced0bbd289e3..bc031a04717e 100644 --- a/code/renderers/react/src/portable-stories.tsx +++ b/code/renderers/react/src/portable-stories.tsx @@ -98,7 +98,7 @@ export const INTERNAL_DEFAULT_PROJECT_ANNOTATIONS: ProjectAnnotations Date: Mon, 9 Sep 2024 10:10:56 +0200 Subject: [PATCH 11/13] fix lock-file changes --- code/yarn.lock | 257 +------------------------------------------------ 1 file changed, 3 insertions(+), 254 deletions(-) diff --git a/code/yarn.lock b/code/yarn.lock index 2e596fc708a3..52f251440bb3 100644 --- a/code/yarn.lock +++ b/code/yarn.lock @@ -2490,7 +2490,7 @@ __metadata: languageName: node linkType: hard -"@emnapi/runtime@npm:^1.1.1, @emnapi/runtime@npm:^1.2.0": +"@emnapi/runtime@npm:^1.1.1": version: 1.2.0 resolution: "@emnapi/runtime@npm:1.2.0" dependencies: @@ -3424,18 +3424,6 @@ __metadata: languageName: node linkType: hard -"@img/sharp-darwin-arm64@npm:0.33.5": - version: 0.33.5 - resolution: "@img/sharp-darwin-arm64@npm:0.33.5" - dependencies: - "@img/sharp-libvips-darwin-arm64": "npm:1.0.4" - dependenciesMeta: - "@img/sharp-libvips-darwin-arm64": - optional: true - conditions: os=darwin & cpu=arm64 - languageName: node - linkType: hard - "@img/sharp-darwin-x64@npm:0.33.4": version: 0.33.4 resolution: "@img/sharp-darwin-x64@npm:0.33.4" @@ -3448,18 +3436,6 @@ __metadata: languageName: node linkType: hard -"@img/sharp-darwin-x64@npm:0.33.5": - version: 0.33.5 - resolution: "@img/sharp-darwin-x64@npm:0.33.5" - dependencies: - "@img/sharp-libvips-darwin-x64": "npm:1.0.4" - dependenciesMeta: - "@img/sharp-libvips-darwin-x64": - optional: true - conditions: os=darwin & cpu=x64 - languageName: node - linkType: hard - "@img/sharp-libvips-darwin-arm64@npm:1.0.2": version: 1.0.2 resolution: "@img/sharp-libvips-darwin-arm64@npm:1.0.2" @@ -3467,13 +3443,6 @@ __metadata: languageName: node linkType: hard -"@img/sharp-libvips-darwin-arm64@npm:1.0.4": - version: 1.0.4 - resolution: "@img/sharp-libvips-darwin-arm64@npm:1.0.4" - conditions: os=darwin & cpu=arm64 - languageName: node - linkType: hard - "@img/sharp-libvips-darwin-x64@npm:1.0.2": version: 1.0.2 resolution: "@img/sharp-libvips-darwin-x64@npm:1.0.2" @@ -3481,13 +3450,6 @@ __metadata: languageName: node linkType: hard -"@img/sharp-libvips-darwin-x64@npm:1.0.4": - version: 1.0.4 - resolution: "@img/sharp-libvips-darwin-x64@npm:1.0.4" - conditions: os=darwin & cpu=x64 - languageName: node - linkType: hard - "@img/sharp-libvips-linux-arm64@npm:1.0.2": version: 1.0.2 resolution: "@img/sharp-libvips-linux-arm64@npm:1.0.2" @@ -3495,13 +3457,6 @@ __metadata: languageName: node linkType: hard -"@img/sharp-libvips-linux-arm64@npm:1.0.4": - version: 1.0.4 - resolution: "@img/sharp-libvips-linux-arm64@npm:1.0.4" - conditions: os=linux & cpu=arm64 & libc=glibc - languageName: node - linkType: hard - "@img/sharp-libvips-linux-arm@npm:1.0.2": version: 1.0.2 resolution: "@img/sharp-libvips-linux-arm@npm:1.0.2" @@ -3509,13 +3464,6 @@ __metadata: languageName: node linkType: hard -"@img/sharp-libvips-linux-arm@npm:1.0.5": - version: 1.0.5 - resolution: "@img/sharp-libvips-linux-arm@npm:1.0.5" - conditions: os=linux & cpu=arm & libc=glibc - languageName: node - linkType: hard - "@img/sharp-libvips-linux-s390x@npm:1.0.2": version: 1.0.2 resolution: "@img/sharp-libvips-linux-s390x@npm:1.0.2" @@ -3523,13 +3471,6 @@ __metadata: languageName: node linkType: hard -"@img/sharp-libvips-linux-s390x@npm:1.0.4": - version: 1.0.4 - resolution: "@img/sharp-libvips-linux-s390x@npm:1.0.4" - conditions: os=linux & cpu=s390x & libc=glibc - languageName: node - linkType: hard - "@img/sharp-libvips-linux-x64@npm:1.0.2": version: 1.0.2 resolution: "@img/sharp-libvips-linux-x64@npm:1.0.2" @@ -3537,13 +3478,6 @@ __metadata: languageName: node linkType: hard -"@img/sharp-libvips-linux-x64@npm:1.0.4": - version: 1.0.4 - resolution: "@img/sharp-libvips-linux-x64@npm:1.0.4" - conditions: os=linux & cpu=x64 & libc=glibc - languageName: node - linkType: hard - "@img/sharp-libvips-linuxmusl-arm64@npm:1.0.2": version: 1.0.2 resolution: "@img/sharp-libvips-linuxmusl-arm64@npm:1.0.2" @@ -3551,13 +3485,6 @@ __metadata: languageName: node linkType: hard -"@img/sharp-libvips-linuxmusl-arm64@npm:1.0.4": - version: 1.0.4 - resolution: "@img/sharp-libvips-linuxmusl-arm64@npm:1.0.4" - conditions: os=linux & cpu=arm64 & libc=musl - languageName: node - linkType: hard - "@img/sharp-libvips-linuxmusl-x64@npm:1.0.2": version: 1.0.2 resolution: "@img/sharp-libvips-linuxmusl-x64@npm:1.0.2" @@ -3565,13 +3492,6 @@ __metadata: languageName: node linkType: hard -"@img/sharp-libvips-linuxmusl-x64@npm:1.0.4": - version: 1.0.4 - resolution: "@img/sharp-libvips-linuxmusl-x64@npm:1.0.4" - conditions: os=linux & cpu=x64 & libc=musl - languageName: node - linkType: hard - "@img/sharp-linux-arm64@npm:0.33.4": version: 0.33.4 resolution: "@img/sharp-linux-arm64@npm:0.33.4" @@ -3584,18 +3504,6 @@ __metadata: languageName: node linkType: hard -"@img/sharp-linux-arm64@npm:0.33.5": - version: 0.33.5 - resolution: "@img/sharp-linux-arm64@npm:0.33.5" - dependencies: - "@img/sharp-libvips-linux-arm64": "npm:1.0.4" - dependenciesMeta: - "@img/sharp-libvips-linux-arm64": - optional: true - conditions: os=linux & cpu=arm64 & libc=glibc - languageName: node - linkType: hard - "@img/sharp-linux-arm@npm:0.33.4": version: 0.33.4 resolution: "@img/sharp-linux-arm@npm:0.33.4" @@ -3608,18 +3516,6 @@ __metadata: languageName: node linkType: hard -"@img/sharp-linux-arm@npm:0.33.5": - version: 0.33.5 - resolution: "@img/sharp-linux-arm@npm:0.33.5" - dependencies: - "@img/sharp-libvips-linux-arm": "npm:1.0.5" - dependenciesMeta: - "@img/sharp-libvips-linux-arm": - optional: true - conditions: os=linux & cpu=arm & libc=glibc - languageName: node - linkType: hard - "@img/sharp-linux-s390x@npm:0.33.4": version: 0.33.4 resolution: "@img/sharp-linux-s390x@npm:0.33.4" @@ -3632,18 +3528,6 @@ __metadata: languageName: node linkType: hard -"@img/sharp-linux-s390x@npm:0.33.5": - version: 0.33.5 - resolution: "@img/sharp-linux-s390x@npm:0.33.5" - dependencies: - "@img/sharp-libvips-linux-s390x": "npm:1.0.4" - dependenciesMeta: - "@img/sharp-libvips-linux-s390x": - optional: true - conditions: os=linux & cpu=s390x & libc=glibc - languageName: node - linkType: hard - "@img/sharp-linux-x64@npm:0.33.4": version: 0.33.4 resolution: "@img/sharp-linux-x64@npm:0.33.4" @@ -3656,18 +3540,6 @@ __metadata: languageName: node linkType: hard -"@img/sharp-linux-x64@npm:0.33.5": - version: 0.33.5 - resolution: "@img/sharp-linux-x64@npm:0.33.5" - dependencies: - "@img/sharp-libvips-linux-x64": "npm:1.0.4" - dependenciesMeta: - "@img/sharp-libvips-linux-x64": - optional: true - conditions: os=linux & cpu=x64 & libc=glibc - languageName: node - linkType: hard - "@img/sharp-linuxmusl-arm64@npm:0.33.4": version: 0.33.4 resolution: "@img/sharp-linuxmusl-arm64@npm:0.33.4" @@ -3680,18 +3552,6 @@ __metadata: languageName: node linkType: hard -"@img/sharp-linuxmusl-arm64@npm:0.33.5": - version: 0.33.5 - resolution: "@img/sharp-linuxmusl-arm64@npm:0.33.5" - dependencies: - "@img/sharp-libvips-linuxmusl-arm64": "npm:1.0.4" - dependenciesMeta: - "@img/sharp-libvips-linuxmusl-arm64": - optional: true - conditions: os=linux & cpu=arm64 & libc=musl - languageName: node - linkType: hard - "@img/sharp-linuxmusl-x64@npm:0.33.4": version: 0.33.4 resolution: "@img/sharp-linuxmusl-x64@npm:0.33.4" @@ -3704,18 +3564,6 @@ __metadata: languageName: node linkType: hard -"@img/sharp-linuxmusl-x64@npm:0.33.5": - version: 0.33.5 - resolution: "@img/sharp-linuxmusl-x64@npm:0.33.5" - dependencies: - "@img/sharp-libvips-linuxmusl-x64": "npm:1.0.4" - dependenciesMeta: - "@img/sharp-libvips-linuxmusl-x64": - optional: true - conditions: os=linux & cpu=x64 & libc=musl - languageName: node - linkType: hard - "@img/sharp-wasm32@npm:0.33.4": version: 0.33.4 resolution: "@img/sharp-wasm32@npm:0.33.4" @@ -3725,15 +3573,6 @@ __metadata: languageName: node linkType: hard -"@img/sharp-wasm32@npm:0.33.5": - version: 0.33.5 - resolution: "@img/sharp-wasm32@npm:0.33.5" - dependencies: - "@emnapi/runtime": "npm:^1.2.0" - conditions: cpu=wasm32 - languageName: node - linkType: hard - "@img/sharp-win32-ia32@npm:0.33.4": version: 0.33.4 resolution: "@img/sharp-win32-ia32@npm:0.33.4" @@ -3741,13 +3580,6 @@ __metadata: languageName: node linkType: hard -"@img/sharp-win32-ia32@npm:0.33.5": - version: 0.33.5 - resolution: "@img/sharp-win32-ia32@npm:0.33.5" - conditions: os=win32 & cpu=ia32 - languageName: node - linkType: hard - "@img/sharp-win32-x64@npm:0.33.4": version: 0.33.4 resolution: "@img/sharp-win32-x64@npm:0.33.4" @@ -3755,13 +3587,6 @@ __metadata: languageName: node linkType: hard -"@img/sharp-win32-x64@npm:0.33.5": - version: 0.33.5 - resolution: "@img/sharp-win32-x64@npm:0.33.5" - conditions: os=win32 & cpu=x64 - languageName: node - linkType: hard - "@inquirer/confirm@npm:^3.0.0": version: 3.1.20 resolution: "@inquirer/confirm@npm:3.1.20" @@ -4078,20 +3903,13 @@ __metadata: languageName: node linkType: hard -"@next/env@npm:14.2.5": +"@next/env@npm:14.2.5, @next/env@npm:^14.2.5": version: 14.2.5 resolution: "@next/env@npm:14.2.5" checksum: 10c0/63d8b88ac450b3c37940a9e2119a63a1074aca89908574ade6157a8aa295275dcb3ac5f69e00883fc55d0f12963b73b74e87ba32a5768a489f9609c6be57b699 languageName: node linkType: hard -"@next/env@npm:^14.2.5": - version: 14.2.7 - resolution: "@next/env@npm:14.2.7" - checksum: 10c0/1cda023007acda4d47036a25fba0e039d9b2df9c3770651dc289207e0537506675546c02b5b574fe92bb1adc1c887d948d5cb630673aa572754278b82d150b7e - languageName: node - linkType: hard - "@next/swc-darwin-arm64@npm:14.2.5": version: 14.2.5 resolution: "@next/swc-darwin-arm64@npm:14.2.5" @@ -25822,7 +25640,7 @@ __metadata: languageName: node linkType: hard -"sharp@npm:^0.33.3": +"sharp@npm:^0.33.3, sharp@npm:^0.33.4": version: 0.33.4 resolution: "sharp@npm:0.33.4" dependencies: @@ -25891,75 +25709,6 @@ __metadata: languageName: node linkType: hard -"sharp@npm:^0.33.4": - version: 0.33.5 - resolution: "sharp@npm:0.33.5" - dependencies: - "@img/sharp-darwin-arm64": "npm:0.33.5" - "@img/sharp-darwin-x64": "npm:0.33.5" - "@img/sharp-libvips-darwin-arm64": "npm:1.0.4" - "@img/sharp-libvips-darwin-x64": "npm:1.0.4" - "@img/sharp-libvips-linux-arm": "npm:1.0.5" - "@img/sharp-libvips-linux-arm64": "npm:1.0.4" - "@img/sharp-libvips-linux-s390x": "npm:1.0.4" - "@img/sharp-libvips-linux-x64": "npm:1.0.4" - "@img/sharp-libvips-linuxmusl-arm64": "npm:1.0.4" - "@img/sharp-libvips-linuxmusl-x64": "npm:1.0.4" - "@img/sharp-linux-arm": "npm:0.33.5" - "@img/sharp-linux-arm64": "npm:0.33.5" - "@img/sharp-linux-s390x": "npm:0.33.5" - "@img/sharp-linux-x64": "npm:0.33.5" - "@img/sharp-linuxmusl-arm64": "npm:0.33.5" - "@img/sharp-linuxmusl-x64": "npm:0.33.5" - "@img/sharp-wasm32": "npm:0.33.5" - "@img/sharp-win32-ia32": "npm:0.33.5" - "@img/sharp-win32-x64": "npm:0.33.5" - color: "npm:^4.2.3" - detect-libc: "npm:^2.0.3" - semver: "npm:^7.6.3" - dependenciesMeta: - "@img/sharp-darwin-arm64": - optional: true - "@img/sharp-darwin-x64": - optional: true - "@img/sharp-libvips-darwin-arm64": - optional: true - "@img/sharp-libvips-darwin-x64": - optional: true - "@img/sharp-libvips-linux-arm": - optional: true - "@img/sharp-libvips-linux-arm64": - optional: true - "@img/sharp-libvips-linux-s390x": - optional: true - "@img/sharp-libvips-linux-x64": - optional: true - "@img/sharp-libvips-linuxmusl-arm64": - optional: true - "@img/sharp-libvips-linuxmusl-x64": - optional: true - "@img/sharp-linux-arm": - optional: true - "@img/sharp-linux-arm64": - optional: true - "@img/sharp-linux-s390x": - optional: true - "@img/sharp-linux-x64": - optional: true - "@img/sharp-linuxmusl-arm64": - optional: true - "@img/sharp-linuxmusl-x64": - optional: true - "@img/sharp-wasm32": - optional: true - "@img/sharp-win32-ia32": - optional: true - "@img/sharp-win32-x64": - optional: true - checksum: 10c0/6b81421ddfe6ee524d8d77e325c5e147fef22884e1c7b1656dfd89a88d7025894115da02d5f984261bf2e6daa16f98cadd1721c4ba408b4212b1d2a60f233484 - languageName: node - linkType: hard - "shebang-command@npm:^1.2.0": version: 1.2.0 resolution: "shebang-command@npm:1.2.0" From ceb8387e24f9b401bd3fc693dbb0f5b220a389d0 Mon Sep 17 00:00:00 2001 From: Kasper Peulen Date: Wed, 11 Sep 2024 11:25:00 +0200 Subject: [PATCH 12/13] Add comment explaining asyncWrapper --- code/renderers/react/src/portable-stories.tsx | 2 ++ 1 file changed, 2 insertions(+) diff --git a/code/renderers/react/src/portable-stories.tsx b/code/renderers/react/src/portable-stories.tsx index bc031a04717e..7b906c9f4bde 100644 --- a/code/renderers/react/src/portable-stories.tsx +++ b/code/renderers/react/src/portable-stories.tsx @@ -65,6 +65,8 @@ export const INTERNAL_DEFAULT_PROJECT_ANNOTATIONS: ProjectAnnotations { return act(cb); }, + // For more context about why we need disable act warnings in waitFor: + // https://github.com/reactwg/react-18/discussions/102 asyncWrapper: async (cb) => { const previousActEnvironment = getReactActEnvironment(); setReactActEnvironment(false); From 46aa6e01ff4a4421a9899cc6f18edf2ff8fdee97 Mon Sep 17 00:00:00 2001 From: Kasper Peulen Date: Wed, 11 Sep 2024 11:26:34 +0200 Subject: [PATCH 13/13] Fix bug where @storybook/test is not imported but canvas is used directly from context --- code/addons/interactions/src/preview.ts | 3 +++ 1 file changed, 3 insertions(+) diff --git a/code/addons/interactions/src/preview.ts b/code/addons/interactions/src/preview.ts index 6abc30f63b93..482b6933279f 100644 --- a/code/addons/interactions/src/preview.ts +++ b/code/addons/interactions/src/preview.ts @@ -1,6 +1,9 @@ import type { PlayFunction, StepLabel, StoryContext } from 'storybook/internal/types'; import { instrument } from '@storybook/instrumenter'; +// This makes sure that storybook test loaders are always loaded when addon-interactions is used +// For 9.0 we want to merge storybook/test and addon-interactions into one addon. +import '@storybook/test'; export const { step: runStep } = instrument( {