From 3d7f249ab406d2cd152d0c6f9a225b050ed423d8 Mon Sep 17 00:00:00 2001 From: Yann Braga Date: Wed, 19 Jan 2022 10:44:31 +0100 Subject: [PATCH 01/31] feat: add testing utilities in @storybook/store --- lib/store/src/StoryStore.ts | 4 +- lib/store/src/csf/index.ts | 5 + .../src/{ => csf}/normalizeInputTypes.test.ts | 0 .../src/{ => csf}/normalizeInputTypes.ts | 0 .../src/csf/normalizeProjectAnnotations.ts | 22 +++++ .../src/{ => csf}/normalizeStory.test.ts | 0 lib/store/src/{ => csf}/normalizeStory.ts | 2 +- lib/store/src/{ => csf}/prepareStory.test.ts | 2 +- lib/store/src/{ => csf}/prepareStory.ts | 10 +- .../src/{ => csf}/processCSFFile.test.ts | 0 lib/store/src/{ => csf}/processCSFFile.ts | 22 ++--- lib/store/src/csf/testing-utils/index.ts | 99 +++++++++++++++++++ lib/store/src/index.ts | 2 +- 13 files changed, 143 insertions(+), 25 deletions(-) create mode 100644 lib/store/src/csf/index.ts rename lib/store/src/{ => csf}/normalizeInputTypes.test.ts (100%) rename lib/store/src/{ => csf}/normalizeInputTypes.ts (100%) create mode 100644 lib/store/src/csf/normalizeProjectAnnotations.ts rename lib/store/src/{ => csf}/normalizeStory.test.ts (100%) rename lib/store/src/{ => csf}/normalizeStory.ts (97%) rename lib/store/src/{ => csf}/prepareStory.test.ts (99%) rename lib/store/src/{ => csf}/prepareStory.ts (96%) rename lib/store/src/{ => csf}/processCSFFile.test.ts (100%) rename lib/store/src/{ => csf}/processCSFFile.ts (74%) create mode 100644 lib/store/src/csf/testing-utils/index.ts diff --git a/lib/store/src/StoryStore.ts b/lib/store/src/StoryStore.ts index ddfeb57c14c4..c1a7e79cf527 100644 --- a/lib/store/src/StoryStore.ts +++ b/lib/store/src/StoryStore.ts @@ -17,8 +17,7 @@ import { SynchronousPromise } from 'synchronous-promise'; import { StoryIndexStore } from './StoryIndexStore'; import { ArgsStore } from './ArgsStore'; import { GlobalsStore } from './GlobalsStore'; -import { processCSFFile } from './processCSFFile'; -import { prepareStory } from './prepareStory'; +import { normalizeInputTypes, processCSFFile, prepareStory } from './csf'; import { CSFFile, ModuleImportFn, @@ -32,7 +31,6 @@ import { V2CompatIndexEntry, } from './types'; import { HooksContext } from './hooks'; -import { normalizeInputTypes } from './normalizeInputTypes'; import { inferArgTypes } from './inferArgTypes'; import { inferControls } from './inferControls'; diff --git a/lib/store/src/csf/index.ts b/lib/store/src/csf/index.ts new file mode 100644 index 000000000000..4e7870ccd0b2 --- /dev/null +++ b/lib/store/src/csf/index.ts @@ -0,0 +1,5 @@ +export * from './normalizeInputTypes'; +export * from './normalizeStory'; +export * from './processCSFFile'; +export * from './prepareStory'; +export * from './testing-utils'; diff --git a/lib/store/src/normalizeInputTypes.test.ts b/lib/store/src/csf/normalizeInputTypes.test.ts similarity index 100% rename from lib/store/src/normalizeInputTypes.test.ts rename to lib/store/src/csf/normalizeInputTypes.test.ts diff --git a/lib/store/src/normalizeInputTypes.ts b/lib/store/src/csf/normalizeInputTypes.ts similarity index 100% rename from lib/store/src/normalizeInputTypes.ts rename to lib/store/src/csf/normalizeInputTypes.ts diff --git a/lib/store/src/csf/normalizeProjectAnnotations.ts b/lib/store/src/csf/normalizeProjectAnnotations.ts new file mode 100644 index 000000000000..dd3eba963475 --- /dev/null +++ b/lib/store/src/csf/normalizeProjectAnnotations.ts @@ -0,0 +1,22 @@ +import { sanitize, AnyFramework } from '@storybook/csf'; + +import { ModuleExports, NormalizedComponentAnnotations } from '../types'; +import { normalizeInputTypes } from './normalizeInputTypes'; + +export function normalizeProjectAnnotations( + defaultExport: ModuleExports['default'], + title: string = defaultExport.title, + importPath?: string +): NormalizedComponentAnnotations { + const { id, argTypes } = defaultExport; + return { + id: sanitize(id || title), + ...defaultExport, + title, + ...(argTypes && { argTypes: normalizeInputTypes(argTypes) }), + parameters: { + fileName: importPath, + ...defaultExport.parameters, + }, + }; +} diff --git a/lib/store/src/normalizeStory.test.ts b/lib/store/src/csf/normalizeStory.test.ts similarity index 100% rename from lib/store/src/normalizeStory.test.ts rename to lib/store/src/csf/normalizeStory.test.ts diff --git a/lib/store/src/normalizeStory.ts b/lib/store/src/csf/normalizeStory.ts similarity index 97% rename from lib/store/src/normalizeStory.ts rename to lib/store/src/csf/normalizeStory.ts index b0784d5a3c6d..1d0f3b93c071 100644 --- a/lib/store/src/normalizeStory.ts +++ b/lib/store/src/csf/normalizeStory.ts @@ -11,7 +11,7 @@ import { import dedent from 'ts-dedent'; import { logger } from '@storybook/client-logger'; import deprecate from 'util-deprecate'; -import { NormalizedStoryAnnotations } from './types'; +import { NormalizedStoryAnnotations } from '../types'; import { normalizeInputTypes } from './normalizeInputTypes'; const deprecatedStoryAnnotation = dedent` diff --git a/lib/store/src/prepareStory.test.ts b/lib/store/src/csf/prepareStory.test.ts similarity index 99% rename from lib/store/src/prepareStory.test.ts rename to lib/store/src/csf/prepareStory.test.ts index 4dd1c7b379c9..1400ed1ea49a 100644 --- a/lib/store/src/prepareStory.test.ts +++ b/lib/store/src/csf/prepareStory.test.ts @@ -7,7 +7,7 @@ import { SBScalarType, StoryContext, } from '@storybook/csf'; -import { NO_TARGET_NAME } from './args'; +import { NO_TARGET_NAME } from '../args'; import { prepareStory } from './prepareStory'; jest.mock('global', () => ({ diff --git a/lib/store/src/prepareStory.ts b/lib/store/src/csf/prepareStory.ts similarity index 96% rename from lib/store/src/prepareStory.ts rename to lib/store/src/csf/prepareStory.ts index 6547cf959810..16d5919aa57e 100644 --- a/lib/store/src/prepareStory.ts +++ b/lib/store/src/csf/prepareStory.ts @@ -18,11 +18,11 @@ import { Story, NormalizedStoryAnnotations, NormalizedProjectAnnotations, -} from './types'; -import { combineParameters } from './parameters'; -import { applyHooks } from './hooks'; -import { defaultDecorateStory } from './decorators'; -import { groupArgsByTarget, NO_TARGET_NAME } from './args'; +} from '../types'; +import { combineParameters } from '../parameters'; +import { applyHooks } from '../hooks'; +import { defaultDecorateStory } from '../decorators'; +import { groupArgsByTarget, NO_TARGET_NAME } from '../args'; const argTypeDefaultValueWarning = deprecate( () => {}, diff --git a/lib/store/src/processCSFFile.test.ts b/lib/store/src/csf/processCSFFile.test.ts similarity index 100% rename from lib/store/src/processCSFFile.test.ts rename to lib/store/src/csf/processCSFFile.test.ts diff --git a/lib/store/src/processCSFFile.ts b/lib/store/src/csf/processCSFFile.ts similarity index 74% rename from lib/store/src/processCSFFile.ts rename to lib/store/src/csf/processCSFFile.ts index dbb5e44a4193..0e6d3207553d 100644 --- a/lib/store/src/processCSFFile.ts +++ b/lib/store/src/csf/processCSFFile.ts @@ -1,10 +1,10 @@ -import { isExportStory, sanitize, Parameters, AnyFramework, ComponentTitle } from '@storybook/csf'; +import { isExportStory, Parameters, AnyFramework, ComponentTitle } from '@storybook/csf'; import { logger } from '@storybook/client-logger'; -import { ModuleExports, CSFFile, NormalizedComponentAnnotations } from './types'; +import { ModuleExports, CSFFile, NormalizedComponentAnnotations } from '../types'; import { normalizeStory } from './normalizeStory'; -import { normalizeInputTypes } from './normalizeInputTypes'; -import { Path } from '.'; +import { Path } from '..'; +import { normalizeProjectAnnotations } from './normalizeProjectAnnotations'; const checkGlobals = (parameters: Parameters) => { const { globals, globalTypes } = parameters; @@ -40,17 +40,11 @@ export function processCSFFile( ): CSFFile { const { default: defaultExport, __namedExportsOrder, ...namedExports } = moduleExports; - const { id, argTypes } = defaultExport; - const meta: NormalizedComponentAnnotations = { - id: sanitize(id || title), - ...defaultExport, + const meta: NormalizedComponentAnnotations = normalizeProjectAnnotations( + defaultExport, title, - ...(argTypes && { argTypes: normalizeInputTypes(argTypes) }), - parameters: { - fileName: importPath, - ...defaultExport.parameters, - }, - }; + importPath + ); checkDisallowedParameters(meta.parameters); const csfFile: CSFFile = { meta, stories: {} }; diff --git a/lib/store/src/csf/testing-utils/index.ts b/lib/store/src/csf/testing-utils/index.ts new file mode 100644 index 000000000000..7ebeece296be --- /dev/null +++ b/lib/store/src/csf/testing-utils/index.ts @@ -0,0 +1,99 @@ +import { + isExportStory, + AnyFramework, + AnnotatedStoryFn, + ComponentAnnotations, + Args, +} from '@storybook/csf'; + +import { prepareStory } from '../prepareStory'; +import { normalizeStory } from '../normalizeStory'; +import { normalizeProjectAnnotations } from '../normalizeProjectAnnotations'; +import { HooksContext } from '../../hooks'; +import { NormalizedProjectAnnotations } from '../..'; + +if (process.env.NODE_ENV === 'test') { + // eslint-disable-next-line global-require + const { default: addons, mockChannel } = require('@storybook/addons'); + addons.setChannel(mockChannel()); +} + +export type StoryFile = { default: any; __esModule?: boolean; __namedExportsOrder?: any }; + +type Entries = { + [K in keyof T]: [K, T[K]]; +}[keyof T]; +export function objectEntries(t: T): Entries[] { + return Object.entries(t) as any; +} + +export function composeStory< + TFramework extends AnyFramework = AnyFramework, + TArgs extends Args = Args +>( + story: AnnotatedStoryFn, + meta: ComponentAnnotations, + globalConfig: NormalizedProjectAnnotations = {} +) { + if (story === undefined) { + throw new Error('Expected a story but received undefined.'); + } + + const normalizedMeta = normalizeProjectAnnotations(meta); + + const normalizedStory = normalizeStory(story.name, story, normalizedMeta); + + const preparedStory = prepareStory(normalizedStory, normalizedMeta, globalConfig); + + const defaultGlobals = Object.entries(globalConfig.globalTypes || {}).reduce( + (acc, [arg, { defaultValue }]) => { + if (defaultValue) { + acc[arg] = defaultValue; + } + return acc; + }, + {} as Record + ); + + const composedStory = (extraArgs: Partial) => { + const context = { + ...preparedStory, + hooks: new HooksContext(), + globals: defaultGlobals, + args: { ...preparedStory.initialArgs, ...extraArgs }, + } as any; + + return preparedStory.unboundStoryFn(context); + }; + + composedStory.storyName = story.storyName || story.name; + composedStory.args = preparedStory.initialArgs; + composedStory.play = preparedStory.playFunction; + composedStory.parameters = preparedStory.parameters; + + return composedStory; +} + +export function composeStories( + storiesImport: TModule, + globalConfig: NormalizedProjectAnnotations, + composeStoryFn: typeof composeStory +) { + const { default: meta, __esModule, __namedExportsOrder, ...stories } = storiesImport; + const composedStories = objectEntries(stories).reduce((storiesMap, [key, _story]) => { + if (!isExportStory(key as string, meta)) { + return storiesMap; + } + + const storyName = String(key); + const story = _story as any; + story.storyName = storyName; + + const result = Object.assign(storiesMap, { + [key]: composeStoryFn(story, meta, globalConfig), + }); + return result; + }, {}); + + return composedStories; +} diff --git a/lib/store/src/index.ts b/lib/store/src/index.ts index ab63007918c8..f3bca023e57b 100644 --- a/lib/store/src/index.ts +++ b/lib/store/src/index.ts @@ -1,12 +1,12 @@ export { StoryStore } from './StoryStore'; export { combineParameters } from './parameters'; export { filterArgTypes } from './filterArgTypes'; -export { normalizeInputTypes } from './normalizeInputTypes'; export type { PropDescriptor } from './filterArgTypes'; export { inferControls } from './inferControls'; export * from './types'; +export * from './csf'; export * from './hooks'; export * from './decorators'; export * from './args'; From a3b599dbe263690f79357c6dbfb4bcb896d22223 Mon Sep 17 00:00:00 2001 From: Yann Braga Date: Wed, 19 Jan 2022 10:44:47 +0100 Subject: [PATCH 02/31] feat: add testing utilities in @storybook/react --- app/react/src/client/index.ts | 1 + app/react/src/client/testing/index.ts | 57 +++++++++++++++++++++++++++ app/react/src/client/testing/types.ts | 43 ++++++++++++++++++++ 3 files changed, 101 insertions(+) create mode 100644 app/react/src/client/testing/index.ts create mode 100644 app/react/src/client/testing/types.ts diff --git a/app/react/src/client/index.ts b/app/react/src/client/index.ts index 54de76c85858..141f7e7f43fc 100644 --- a/app/react/src/client/index.ts +++ b/app/react/src/client/index.ts @@ -9,6 +9,7 @@ export { raw, forceReRender, } from './preview'; +export * from './testing'; export * from './preview/types-6-3'; diff --git a/app/react/src/client/testing/index.ts b/app/react/src/client/testing/index.ts new file mode 100644 index 000000000000..7ce12904ea0e --- /dev/null +++ b/app/react/src/client/testing/index.ts @@ -0,0 +1,57 @@ +import { + composeStory as originalComposeStory, + composeStories as originalComposeStories, +} from '@storybook/store'; +// eslint-disable-next-line import/no-extraneous-dependencies +import { composeConfigs } from '@storybook/preview-web'; +import type { AnnotatedStoryFn } from '@storybook/csf'; +import { render } from '../preview/render'; + +import type { Meta, ReactFramework } from '../preview/types-6-0'; +import type { StoriesWithPartialProps, GlobalConfig, StoryFile } from './types'; + +const defaultGlobalConfig: GlobalConfig = { + render, +}; + +let globalStorybookConfig = { + ...defaultGlobalConfig, +}; + +/** Function that sets the globalConfig of your storybook. The global config is the preview module of your .storybook folder. + * + * It should be run a single time, so that your global config (e.g. decorators) is applied to your stories when using `composeStories` or `composeStory`. + * + * Example: + *```jsx + * // setup.js (for jest) + * import { setGlobalConfig } from '@storybook/testing-react'; + * import * as globalStorybookConfig from './.storybook/preview'; + * + * setGlobalConfig(globalStorybookConfig); + *``` + * + * @param config - e.g. (import * as globalConfig from '../.storybook/preview') + */ +export function setGlobalConfig(config: GlobalConfig) { + globalStorybookConfig = composeConfigs([defaultGlobalConfig, config]) as GlobalConfig; +} + +export function composeStory( + story: AnnotatedStoryFn, + meta: Meta, + globalConfig: GlobalConfig = globalStorybookConfig +) { + const projectAnnotations = { ...defaultGlobalConfig, ...globalConfig }; + + return originalComposeStory(story, meta, projectAnnotations); +} + +export function composeStories( + storiesImport: TModule, + globalConfig?: GlobalConfig +) { + const composedStories = originalComposeStories(storiesImport, globalConfig, composeStory); + + return (composedStories as unknown) as Omit, keyof StoryFile>; +} diff --git a/app/react/src/client/testing/types.ts b/app/react/src/client/testing/types.ts new file mode 100644 index 000000000000..574495b01776 --- /dev/null +++ b/app/react/src/client/testing/types.ts @@ -0,0 +1,43 @@ +import { NormalizedProjectAnnotations } from '@storybook/store'; +import type { + StoryFn as OriginalStoryFn, + StoryObj, + Meta, + Args, + StoryContext, + ReactFramework, +} from '../preview/types-6-0'; + +/** + * Object representing the preview.ts module + * + * Used in storybook testing utilities. + * @see [Unit testing with Storybook](https://storybook.js.org/docs/react/workflows/unit-testing) + */ +export type GlobalConfig = NormalizedProjectAnnotations; + +export type StoryFile = { default: Meta; __esModule?: boolean; __namedExportsOrder?: any }; + +export type TestingStory = StoryFn | StoryObj; + +export type TestingStoryPlayContext = Partial> & + Pick; + +export type TestingStoryPlayFn = ( + context: TestingStoryPlayContext +) => Promise | void; + +export type StoryFn = OriginalStoryFn & { play: TestingStoryPlayFn }; + +/** + * T represents the whole es module of a stories file. K of T means named exports (basically the Story type) + * 1. pick the keys K of T that have properties that are Story + * 2. infer the actual prop type for each Story + * 3. reconstruct Story with Partial. Story -> Story> + */ +export type StoriesWithPartialProps = any; +// { +// [K in keyof T as T[K] extends TestingStory ? K : never]: T[K] extends TestingStory +// ? StoryFn> +// : unknown; +// }; From f9f5c02697029c38726a914d1aab910cd06271b3 Mon Sep 17 00:00:00 2001 From: Yann Braga Date: Wed, 19 Jan 2022 10:45:13 +0100 Subject: [PATCH 03/31] chore: add tests for testing utilities in cra-ts-essentials --- .../.storybook/{main.js => main.ts} | 0 .../.storybook/{preview.js => preview.tsx} | 17 +- examples/cra-ts-essentials/package.json | 7 +- examples/cra-ts-essentials/src/setupTests.ts | 4 + .../components/AccountForm.stories.tsx | 109 ++++ .../components/AccountForm.test.tsx | 25 + .../testing-react/components/AccountForm.tsx | 552 ++++++++++++++++++ .../components/Button.stories.tsx | 87 +++ .../testing-react/components/Button.test.tsx | 94 +++ .../testing-react/components/Button.tsx | 43 ++ .../__snapshots__/internals.test.tsx.snap | 127 ++++ .../testing-react/components/button.css | 30 + .../components/internals.test.tsx | 112 ++++ examples/cra-ts-essentials/tsconfig.json | 14 +- .../.storybook/{preview.js => preview.tsx} | 0 examples/react-ts/package.json | 1 + examples/react-ts/src/AccountForm.stories.tsx | 55 +- examples/react-ts/src/AccountForm.test.tsx | 24 + yarn.lock | 23 +- 19 files changed, 1294 insertions(+), 30 deletions(-) rename examples/cra-ts-essentials/.storybook/{main.js => main.ts} (100%) rename examples/cra-ts-essentials/.storybook/{preview.js => preview.tsx} (55%) create mode 100644 examples/cra-ts-essentials/src/setupTests.ts create mode 100644 examples/cra-ts-essentials/src/stories/testing-react/components/AccountForm.stories.tsx create mode 100644 examples/cra-ts-essentials/src/stories/testing-react/components/AccountForm.test.tsx create mode 100644 examples/cra-ts-essentials/src/stories/testing-react/components/AccountForm.tsx create mode 100644 examples/cra-ts-essentials/src/stories/testing-react/components/Button.stories.tsx create mode 100644 examples/cra-ts-essentials/src/stories/testing-react/components/Button.test.tsx create mode 100644 examples/cra-ts-essentials/src/stories/testing-react/components/Button.tsx create mode 100644 examples/cra-ts-essentials/src/stories/testing-react/components/__snapshots__/internals.test.tsx.snap create mode 100644 examples/cra-ts-essentials/src/stories/testing-react/components/button.css create mode 100644 examples/cra-ts-essentials/src/stories/testing-react/components/internals.test.tsx rename examples/react-ts/.storybook/{preview.js => preview.tsx} (100%) create mode 100644 examples/react-ts/src/AccountForm.test.tsx diff --git a/examples/cra-ts-essentials/.storybook/main.js b/examples/cra-ts-essentials/.storybook/main.ts similarity index 100% rename from examples/cra-ts-essentials/.storybook/main.js rename to examples/cra-ts-essentials/.storybook/main.ts diff --git a/examples/cra-ts-essentials/.storybook/preview.js b/examples/cra-ts-essentials/.storybook/preview.tsx similarity index 55% rename from examples/cra-ts-essentials/.storybook/preview.js rename to examples/cra-ts-essentials/.storybook/preview.tsx index 305c8eb0be14..54093cc18b6c 100644 --- a/examples/cra-ts-essentials/.storybook/preview.js +++ b/examples/cra-ts-essentials/.storybook/preview.tsx @@ -1,14 +1,25 @@ import React from 'react'; +import type { DecoratorFn } from '@storybook/react'; +import { ThemeProvider, convert, themes } from '@storybook/theming'; -export const decorators = [ - (StoryFn, { globals: { locale = 'en' } }) => ( +export const decorators: DecoratorFn[] = [ + (StoryFn, { globals: { locale } }) => ( <> -
{locale}
+
Locale: {locale}
), + (StoryFn) => ( + + + + ), ]; +export const parameters = { + actions: { argTypesRegex: '^on[A-Z].*' }, +}; + export const globalTypes = { locale: { name: 'Locale', diff --git a/examples/cra-ts-essentials/package.json b/examples/cra-ts-essentials/package.json index e00f7dc18c19..4c0059e96b10 100644 --- a/examples/cra-ts-essentials/package.json +++ b/examples/cra-ts-essentials/package.json @@ -8,7 +8,7 @@ "eject": "react-scripts eject", "start": "react-scripts start", "storybook": "start-storybook -p 9009 --no-manager-cache", - "test": "react-scripts test" + "test": "SKIP_PREFLIGHT_CHECK=true react-scripts test" }, "browserslist": { "production": [ @@ -27,6 +27,7 @@ "@types/node": "^14.14.20 || ^16.0.0", "@types/react": "^16.14.2", "@types/react-dom": "16.9.10", + "formik": "2.2.9", "global": "^4.4.0", "react": "16.14.0", "react-dom": "16.14.0", @@ -38,8 +39,12 @@ "@storybook/addon-ie11": "0.0.7--canary.5e87b64.0", "@storybook/addons": "6.5.0-alpha.23", "@storybook/builder-webpack4": "6.5.0-alpha.23", + "@storybook/components": "6.5.0-alpha.23", + "@storybook/jest": "^0.0.5", "@storybook/preset-create-react-app": "^3.1.6", "@storybook/react": "6.5.0-alpha.23", + "@storybook/testing-library": "^0.0.7", + "@storybook/theming": "6.5.0-alpha.23", "webpack": "4" }, "storybook": { diff --git a/examples/cra-ts-essentials/src/setupTests.ts b/examples/cra-ts-essentials/src/setupTests.ts new file mode 100644 index 000000000000..c3f66482a560 --- /dev/null +++ b/examples/cra-ts-essentials/src/setupTests.ts @@ -0,0 +1,4 @@ +import { setGlobalConfig } from '@storybook/react'; +import * as globalStorybookConfig from '../.storybook/preview'; + +setGlobalConfig(globalStorybookConfig); diff --git a/examples/cra-ts-essentials/src/stories/testing-react/components/AccountForm.stories.tsx b/examples/cra-ts-essentials/src/stories/testing-react/components/AccountForm.stories.tsx new file mode 100644 index 000000000000..f3cab5e6d2f5 --- /dev/null +++ b/examples/cra-ts-essentials/src/stories/testing-react/components/AccountForm.stories.tsx @@ -0,0 +1,109 @@ +/* eslint-disable storybook/await-interactions */ +import React from 'react'; +import type { ComponentMeta, ComponentStoryObj } from '@storybook/react'; +import { userEvent, within } from '@storybook/testing-library'; + +import { AccountForm, AccountFormProps } from './AccountForm'; + +export default { + title: 'CSF3/AccountForm', + component: AccountForm, + parameters: { + layout: 'centered', + }, +} as ComponentMeta; + +type Story = ComponentStoryObj; + +export const Standard: Story = { + args: { passwordVerification: false }, +}; + +export const StandardEmailFilled: Story = { + ...Standard, + play: async ({ canvasElement }) => { + const canvas = within(canvasElement); + await userEvent.type(canvas.getByTestId('email'), 'michael@chromatic.com'); + }, +}; + +export const StandardEmailFailed: Story = { + ...Standard, + play: async ({ canvasElement }) => { + const canvas = within(canvasElement); + await userEvent.type(canvas.getByTestId('email'), 'michael@chromatic.com.com@com'); + await userEvent.type(canvas.getByTestId('password1'), 'testpasswordthatwontfail'); + await userEvent.click(canvas.getByTestId('submit')); + }, +}; + +export const StandardPasswordFailed: Story = { + ...Standard, + play: async (context) => { + const canvas = within(context.canvasElement); + await StandardEmailFilled.play!(context); + await userEvent.type(canvas.getByTestId('password1'), 'asdf'); + await userEvent.click(canvas.getByTestId('submit')); + }, +}; + +export const StandardFailHover: Story = { + ...StandardPasswordFailed, + play: async (context) => { + const canvas = within(context.canvasElement); + await StandardPasswordFailed.play!(context); + await sleep(100); + await userEvent.hover(canvas.getByTestId('password-error-info')); + }, +}; + +export const Verification: Story = { + args: { passwordVerification: true }, +}; + +export const VerificationPasssword1: Story = { + ...Verification, + play: async (context) => { + const canvas = within(context.canvasElement); + await StandardEmailFilled.play!(context); + await userEvent.type(canvas.getByTestId('password1'), 'asdfasdf'); + await userEvent.click(canvas.getByTestId('submit')); + }, +}; + +export const VerificationPasswordMismatch: Story = { + ...Verification, + play: async (context) => { + const canvas = within(context.canvasElement); + await StandardEmailFilled.play!(context); + await userEvent.type(canvas.getByTestId('password1'), 'asdfasdf'); + await userEvent.type(canvas.getByTestId('password2'), 'asdf1234'); + await userEvent.click(canvas.getByTestId('submit')); + }, +}; + +const sleep = (ms: number) => new Promise((r) => setTimeout(r, ms)); + +export const VerificationSuccess: Story = { + ...Verification, + play: async (context) => { + const canvas = within(context.canvasElement); + await StandardEmailFilled.play!(context); + await sleep(1000); + await userEvent.type(canvas.getByTestId('password1'), 'asdfasdf', { delay: 50 }); + await sleep(1000); + await userEvent.type(canvas.getByTestId('password2'), 'asdfasdf', { delay: 50 }); + await sleep(1000); + await userEvent.click(canvas.getByTestId('submit')); + }, +}; + +export const StandardWithRenderFunction: Story = { + ...Standard, + render: (args: AccountFormProps) => ( +
+

This uses a custom render

+ +
+ ), +}; diff --git a/examples/cra-ts-essentials/src/stories/testing-react/components/AccountForm.test.tsx b/examples/cra-ts-essentials/src/stories/testing-react/components/AccountForm.test.tsx new file mode 100644 index 000000000000..a15fe4224779 --- /dev/null +++ b/examples/cra-ts-essentials/src/stories/testing-react/components/AccountForm.test.tsx @@ -0,0 +1,25 @@ +import React from 'react'; +import { render, screen } from '@testing-library/react'; + +import { composeStories, composeStory } from '@storybook/react'; + +import * as stories from './AccountForm.stories'; + +const { Standard } = composeStories(stories); + +test('renders form', async () => { + await render(); + expect(screen.getByTestId('email')).not.toBe(null); +}); + +test('fills input from play function', async () => { + // @ts-ignore + const StandardEmailFilled = composeStory(stories.StandardEmailFilled, stories.default); + const { container } = await render(); + + // @ts-ignore + await StandardEmailFilled.play({ canvasElement: container }); + + const emailInput = screen.getByTestId('email') as HTMLInputElement; + expect(emailInput.value).toBe('michael@chromatic.com'); +}); diff --git a/examples/cra-ts-essentials/src/stories/testing-react/components/AccountForm.tsx b/examples/cra-ts-essentials/src/stories/testing-react/components/AccountForm.tsx new file mode 100644 index 000000000000..b646a174143b --- /dev/null +++ b/examples/cra-ts-essentials/src/stories/testing-react/components/AccountForm.tsx @@ -0,0 +1,552 @@ +import React, { FC, HTMLAttributes, useCallback, useState } from 'react'; +import { keyframes, styled } from '@storybook/theming'; +import { + ErrorMessage, + Field as FormikInput, + Form as FormikForm, + Formik, + FormikProps, +} from 'formik'; +import { Icons, WithTooltip } from '@storybook/components'; + +const errorMap = { + email: { + required: { + normal: 'Please enter your email address', + tooltip: + 'We do require an email address and a password as a minimum in order to be able to create an account for you to log in with', + }, + format: { + normal: 'Please enter a correctly formatted email address', + tooltip: + 'Your email address is formatted incorrectly and is not correct - please double check for misspelling', + }, + }, + password: { + required: { + normal: 'Please enter a password', + tooltip: 'A password is requried to create an account', + }, + length: { + normal: 'Please enter a password of minimum 6 characters', + tooltip: + 'For security reasons we enforce a password length of minimum 6 characters - but have no other requirements', + }, + }, + verifiedPassword: { + required: { + normal: 'Please verify your password', + tooltip: + 'Verification of your password is required to ensure no errors in the spelling of the password', + }, + match: { + normal: 'Your passwords do not match', + tooltip: + 'Your verification password has to match your password to make sure you have not misspelled', + }, + }, +}; + +// https://emailregex.com/ +const email99RegExp = new RegExp( + // eslint-disable-next-line no-useless-escape + /^(([^<>()\[\]\\.,;:\s@"]+(\.[^<>()\[\]\\.,;:\s@"]+)*)|(".+"))@((\[[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}])|(([a-zA-Z\-0-9]+\.)+[a-zA-Z]{2,}))$/ +); + +export interface AccountFormResponse { + success: boolean; +} + +export interface AccountFormValues { + email: string; + password: string; +} + +interface FormValues extends AccountFormValues { + verifiedPassword: string; +} + +interface FormErrors { + email?: string; + emailTooltip?: string; + password?: string; + passwordTooltip?: string; + verifiedPassword?: string; + verifiedPasswordTooltip?: string; +} + +export type AccountFormProps = { + passwordVerification?: boolean; + onSubmit?: (values: AccountFormValues) => void; + onTransactionStart?: (values: AccountFormValues) => void; + onTransactionEnd?: (values: AccountFormResponse) => void; +}; + +export const AccountForm: FC = ({ + passwordVerification, + onSubmit, + onTransactionStart, + onTransactionEnd, +}) => { + const [state, setState] = useState({ + transacting: false, + transactionSuccess: false, + transactionFailure: false, + }); + + const handleFormSubmit = useCallback( + async ({ email, password }: FormValues, { setSubmitting, resetForm }) => { + if (onSubmit) { + onSubmit({ email, password }); + } + + if (onTransactionStart) { + onTransactionStart({ email, password }); + } + + setSubmitting(true); + + setState({ + ...state, + transacting: true, + }); + + await new Promise((r) => setTimeout(r, 2100)); + + const success = Math.random() < 1; + + if (onTransactionEnd) { + onTransactionEnd({ success }); + } + + setSubmitting(false); + resetForm({ values: { email: '', password: '', verifiedPassword: '' } }); + + setState({ + ...state, + transacting: false, + transactionSuccess: success === true, + transactionFailure: success === false, + }); + }, + [setState, onTransactionEnd, onTransactionStart] + ); + + return ( + + + + Storybook icon + + + + + + + + <title>Storybook + + + + + + {!state.transactionSuccess && !state.transactionFailure && ( + Create an account to join the Storybook community + )} + + {state.transactionSuccess && !state.transactionFailure && ( + +

+ Everything is perfect. Your account is ready and we should probably get you started! +

+

So why don't you get started then?

+ { + setState({ + transacting: false, + transactionSuccess: false, + transactionFailure: false, + }); + }} + > + Go back + +
+ )} + {state.transactionFailure && !state.transactionSuccess && ( + +

What a mess, this API is not working

+

+ Someone should probably have a stern talking to about this, but it won't be me - coz + I'm gonna head out into the nice weather +

+ { + setState({ + transacting: false, + transactionSuccess: false, + transactionFailure: false, + }); + }} + > + Go back + +
+ )} + {!state.transactionSuccess && !state.transactionFailure && ( + { + const errors: FormErrors = {}; + + if (!email) { + errors.email = errorMap.email.required.normal; + errors.emailTooltip = errorMap.email.required.tooltip; + } else { + const validEmail = email.match(email99RegExp); + + if (validEmail === null) { + errors.email = errorMap.email.format.normal; + errors.emailTooltip = errorMap.email.format.tooltip; + } + } + + if (!password) { + errors.password = errorMap.password.required.normal; + errors.passwordTooltip = errorMap.password.required.tooltip; + } else if (password.length < 6) { + errors.password = errorMap.password.length.normal; + errors.passwordTooltip = errorMap.password.length.tooltip; + } + + if (passwordVerification && !verifiedPassword) { + errors.verifiedPassword = errorMap.verifiedPassword.required.normal; + errors.verifiedPasswordTooltip = errorMap.verifiedPassword.required.tooltip; + } else if (passwordVerification && password !== verifiedPassword) { + errors.verifiedPassword = errorMap.verifiedPassword.match.normal; + errors.verifiedPasswordTooltip = errorMap.verifiedPassword.match.tooltip; + } + + return errors; + }} + > + {({ errors: _errors, isSubmitting, dirty }: FormikProps) => { + const errors = _errors as FormErrors; + + return ( +
+ + + + {({ field }: { field: HTMLAttributes }) => ( + <> + + {errors.email && ( + {errors.emailTooltip}} + > + + + + + + )} + + )} + + + + + + {({ field }: { field: HTMLAttributes }) => ( + + )} + + {errors.password && ( + {errors.passwordTooltip}}> + + + + + + )} + + {passwordVerification && ( + + + + {({ field }: { field: HTMLAttributes }) => ( + + )} + + {errors.verifiedPassword && ( + {errors.verifiedPasswordTooltip}} + > + + + + + + )} + + )} + + + Create Account + + + Reset + + +
+ ); + }} +
+ )} +
+
+ ); +}; + +const Wrapper = styled.section(({ theme }) => ({ + fontFamily: theme.typography.fonts.base, + display: 'flex', + flexDirection: 'column', + alignItems: 'center', + width: 450, + padding: 32, + backgroundColor: theme.background.content, + borderRadius: 7, +})); + +const Brand = styled.div({ + display: 'flex', + alignItems: 'center', + justifyContent: 'center', +}); + +const Title = styled.svg({ + height: 40, + zIndex: 1, + left: -32, + position: 'relative', +}); + +const logoAnimation = keyframes({ + '0': { + transform: 'rotateY(0deg)', + transformOrigin: '50% 5% 0', + }, + '100%': { + transform: 'rotateY(360deg)', + transformOrigin: '50% 5% 0', + }, +}); + +interface LogoProps { + transacting: boolean; +} + +const Logo = styled.svg( + ({ transacting }) => + transacting && { + animation: `${logoAnimation} 1250ms both infinite`, + }, + { height: 40, zIndex: 10, marginLeft: 32 } +); + +const Introduction = styled.p({ + marginTop: 20, + textAlign: 'center', +}); + +const Content = styled.div({ + display: 'flex', + alignItems: 'flex-start', + justifyContent: 'center', + width: 350, + minHeight: 189, + marginTop: 8, +}); + +const Presentation = styled.div({ + textAlign: 'center', +}); + +const Form = styled(FormikForm)({ + width: '100%', + alignSelf: 'flex-start', + '&[aria-disabled="true"]': { + opacity: 0.6, + }, +}); + +const FieldWrapper = styled.div({ + display: 'flex', + flexDirection: 'column', + justifyContent: 'stretch', + marginBottom: 10, +}); + +const Label = styled.label({ + fontSize: 13, + fontWeight: 500, + marginBottom: 6, +}); + +const Input = styled.input(({ theme }) => ({ + fontSize: 14, + color: theme.color.defaultText, + padding: '10px 15px', + borderRadius: 4, + appearance: 'none', + outline: 'none', + border: '0 none', + boxShadow: 'rgb(0 0 0 / 10%) 0px 0px 0px 1px inset', + '&:focus': { + boxShadow: 'rgb(30 167 253) 0px 0px 0px 1px inset', + }, + '&:active': { + boxShadow: 'rgb(30 167 253) 0px 0px 0px 1px inset', + }, + '&[aria-invalid="true"]': { + boxShadow: 'rgb(255 68 0) 0px 0px 0px 1px inset', + }, +})); + +const ErrorWrapper = styled.div({ + display: 'flex', + alignItems: 'flex-start', + fontSize: 11, + marginTop: 6, + cursor: 'help', +}); + +const ErrorIcon = styled(Icons)(({ theme }) => ({ + fill: theme.color.defaultText, + opacity: 0.8, + marginRight: 6, + marginLeft: 2, + marginTop: 1, +})); + +const ErrorTooltip = styled.div(({ theme }) => ({ + fontFamily: theme.typography.fonts.base, + fontSize: 13, + padding: 8, + maxWidth: 350, +})); + +const Actions = styled.div({ + alignSelf: 'stretch', + display: 'flex', + justifyContent: 'space-between', + marginTop: 24, +}); + +const Error = styled(ErrorMessage)({}); + +interface ButtonProps { + dirty?: boolean; +} + +const Button = styled.button({ + backgroundColor: 'transparent', + border: '0 none', + outline: 'none', + appearance: 'none', + fontWeight: 500, + fontSize: 12, + flexBasis: '50%', + cursor: 'pointer', + padding: '11px 16px', + borderRadius: 4, + textTransform: 'uppercase', + '&:focus': { + textDecoration: 'underline', + fontWeight: 700, + }, + '&:active': { + textDecoration: 'underline', + fontWeight: 700, + }, + '&[aria-disabled="true"]': { + cursor: 'default', + }, +}); + +const Submit = styled(Button)(({ theme, dirty }) => ({ + marginRight: 8, + backgroundColor: theme.color.secondary, + color: theme.color.inverseText, + opacity: dirty ? 1 : 0.6, + boxShadow: 'rgb(30 167 253 / 10%) 0 0 0 1px inset', +})); + +const Reset = styled(Button)(({ theme }) => ({ + marginLeft: 8, + boxShadow: 'rgb(30 167 253) 0 0 0 1px inset', + color: theme.color.secondary, +})); diff --git a/examples/cra-ts-essentials/src/stories/testing-react/components/Button.stories.tsx b/examples/cra-ts-essentials/src/stories/testing-react/components/Button.stories.tsx new file mode 100644 index 000000000000..cea434dba36c --- /dev/null +++ b/examples/cra-ts-essentials/src/stories/testing-react/components/Button.stories.tsx @@ -0,0 +1,87 @@ +/* eslint-disable storybook/use-storybook-testing-library */ +import React from 'react'; +import { StoryFn as CSF2Story, StoryObj as CSF3Story, Meta } from '@storybook/react'; +import { screen } from '@testing-library/react'; +import userEvent from '@testing-library/user-event'; + +import { Button, ButtonProps } from './Button'; + +export default { + title: 'Example/Button', + component: Button, + argTypes: { + backgroundColor: { control: 'color' }, + label: { defaultValue: 'Button' }, + }, +} as Meta; + +const Template: CSF2Story = (args) => ; +}; +StoryWithLocale.storyName = 'WithLocale'; + +export const StoryWithParamsAndDecorator: CSF2Story = (args) => { + return + ); +}; diff --git a/examples/cra-ts-essentials/src/stories/testing-react/components/__snapshots__/internals.test.tsx.snap b/examples/cra-ts-essentials/src/stories/testing-react/components/__snapshots__/internals.test.tsx.snap new file mode 100644 index 000000000000..68cf24c20432 --- /dev/null +++ b/examples/cra-ts-essentials/src/stories/testing-react/components/__snapshots__/internals.test.tsx.snap @@ -0,0 +1,127 @@ +// Jest Snapshot v1, https://goo.gl/fbAQLP + +exports[`Renders CSF3Button story 1`] = ` + +
+
+ Locale: + en +
+ +
+ +`; + +exports[`Renders CSF3ButtonWithRender story 1`] = ` + +
+
+ Locale: + en +
+
+

+ I am a custom render function +

+ +
+
+ +`; + +exports[`Renders InputFieldFilled story 1`] = ` + +
+
+ Locale: + en +
+ +
+ +`; + +exports[`Renders Primary story 1`] = ` + +
+
+ Locale: + en +
+ +
+ +`; + +exports[`Renders Secondary story 1`] = ` + +
+
+ Locale: + en +
+ +
+ +`; + +exports[`Renders StoryWithLocale story 1`] = ` + +
+
+ Locale: + en +
+ +
+ +`; + +exports[`Renders StoryWithParamsAndDecorator story 1`] = ` + +
+
+ Locale: + en +
+ +
+ +`; diff --git a/examples/cra-ts-essentials/src/stories/testing-react/components/button.css b/examples/cra-ts-essentials/src/stories/testing-react/components/button.css new file mode 100644 index 000000000000..dc91dc76370b --- /dev/null +++ b/examples/cra-ts-essentials/src/stories/testing-react/components/button.css @@ -0,0 +1,30 @@ +.storybook-button { + font-family: 'Nunito Sans', 'Helvetica Neue', Helvetica, Arial, sans-serif; + font-weight: 700; + border: 0; + border-radius: 3em; + cursor: pointer; + display: inline-block; + line-height: 1; +} +.storybook-button--primary { + color: white; + background-color: #1ea7fd; +} +.storybook-button--secondary { + color: #333; + background-color: transparent; + box-shadow: rgba(0, 0, 0, 0.15) 0px 0px 0px 1px inset; +} +.storybook-button--small { + font-size: 12px; + padding: 10px 16px; +} +.storybook-button--medium { + font-size: 14px; + padding: 11px 20px; +} +.storybook-button--large { + font-size: 16px; + padding: 12px 24px; +} diff --git a/examples/cra-ts-essentials/src/stories/testing-react/components/internals.test.tsx b/examples/cra-ts-essentials/src/stories/testing-react/components/internals.test.tsx new file mode 100644 index 000000000000..e209c67651aa --- /dev/null +++ b/examples/cra-ts-essentials/src/stories/testing-react/components/internals.test.tsx @@ -0,0 +1,112 @@ +/* eslint-disable @typescript-eslint/no-non-null-assertion */ +import React from 'react'; +import addons from '@storybook/addons'; +import { render, screen } from '@testing-library/react'; + +import { composeStories, composeStory } from '@storybook/react'; + +import * as stories from './Button.stories'; + +import * as globalConfig from '../../../../.storybook/preview'; + +const { StoryWithParamsAndDecorator } = composeStories(stories); + +test('returns composed args including default values from argtypes', () => { + expect(StoryWithParamsAndDecorator.args).toEqual({ + ...stories.StoryWithParamsAndDecorator.args, + label: stories.default!.argTypes!.label!.defaultValue, + }); +}); + +test('returns composed parameters from story', () => { + expect(StoryWithParamsAndDecorator.parameters).toEqual( + expect.objectContaining({ + ...stories.StoryWithParamsAndDecorator.parameters, + ...globalConfig.parameters, + }) + ); +}); + +// common in addons that need to communicate between manager and preview +test('should pass with decorators that need addons channel', () => { + // @ts-ignore + const PrimaryWithChannels = composeStory(stories.Primary, stories.default, { + decorators: [ + (StoryFn: any) => { + addons.getChannel(); + return ; + }, + ], + }); + render(Hello world); + const buttonElement = screen.getByText(/Hello world/i); + expect(buttonElement).not.toBeNull(); +}); + +describe('Unsupported formats', () => { + test('should throw error StoryFn.story notation', () => { + const UnsupportedStory = () =>
hello world
; + UnsupportedStory.story = { parameters: {} }; + + const UnsupportedStoryModule: any = { + default: {}, + UnsupportedStory, + }; + + expect(() => { + composeStories(UnsupportedStoryModule); + }).toThrow(); + }); + + test('should throw error with non component stories', () => { + const UnsupportedStoryModule: any = { + default: {}, + UnsupportedStory: 123, + }; + + expect(() => { + composeStories(UnsupportedStoryModule); + }).toThrow(); + }); +}); + +describe('non-story exports', () => { + test('should filter non-story exports with excludeStories', () => { + const StoryModuleWithNonStoryExports = { + default: { + title: 'Some/Component', + excludeStories: /.*Data/, + }, + LegitimateStory: () =>
hello world
, + mockData: {}, + }; + + const result = composeStories(StoryModuleWithNonStoryExports); + expect(Object.keys(result)).not.toContain('mockData'); + }); + + test('should filter non-story exports with includeStories', () => { + const StoryModuleWithNonStoryExports = { + default: { + title: 'Some/Component', + includeStories: /.*Story/, + }, + LegitimateStory: () =>
hello world
, + mockData: {}, + }; + + const result = composeStories(StoryModuleWithNonStoryExports); + expect(Object.keys(result)).not.toContain('mockData'); + }); +}); + +// Batch snapshot testing +const testCases = Object.values(composeStories(stories)).map((Story) => [ + // The ! is necessary in Typescript only, as the property is part of a partial type + Story.storyName!, + Story, +]); +test.each(testCases)('Renders %s story', async (_storyName, Story) => { + const tree = await render(); + expect(tree.baseElement).toMatchSnapshot(); +}); diff --git a/examples/cra-ts-essentials/tsconfig.json b/examples/cra-ts-essentials/tsconfig.json index 450e0014a4ea..4e81ac32da5f 100644 --- a/examples/cra-ts-essentials/tsconfig.json +++ b/examples/cra-ts-essentials/tsconfig.json @@ -6,7 +6,7 @@ "experimentalDecorators": true, "emitDecoratorMetadata": true, "jsx": "react", - "module": "commonjs", + "module": "esnext", "skipLibCheck": true, "allowSyntheticDefaultImports": true, "esModuleInterop": true, @@ -18,9 +18,17 @@ "lib": [ "es2017", "dom" - ] + ], + "allowJs": true, + "strict": true, + "forceConsistentCasingInFileNames": true, + "noFallthroughCasesInSwitch": true, + "moduleResolution": "node", + "resolveJsonModule": true, + "isolatedModules": true, + "noEmit": true }, "include": [ "src" ] -} \ No newline at end of file +} diff --git a/examples/react-ts/.storybook/preview.js b/examples/react-ts/.storybook/preview.tsx similarity index 100% rename from examples/react-ts/.storybook/preview.js rename to examples/react-ts/.storybook/preview.tsx diff --git a/examples/react-ts/package.json b/examples/react-ts/package.json index 731f86269da0..f839df256c25 100644 --- a/examples/react-ts/package.json +++ b/examples/react-ts/package.json @@ -26,6 +26,7 @@ "@storybook/react": "6.5.0-alpha.23", "@storybook/theming": "6.5.0-alpha.23", "@testing-library/dom": "^7.31.2", + "@testing-library/react": "12.1.2", "@testing-library/user-event": "^13.1.9", "@types/babel__preset-env": "^7", "@types/react": "^16.14.2", diff --git a/examples/react-ts/src/AccountForm.stories.tsx b/examples/react-ts/src/AccountForm.stories.tsx index d670d64ee45a..1a4806881f06 100644 --- a/examples/react-ts/src/AccountForm.stories.tsx +++ b/examples/react-ts/src/AccountForm.stories.tsx @@ -1,11 +1,12 @@ +/* eslint-disable @typescript-eslint/no-non-null-assertion */ /* eslint-disable storybook/await-interactions */ /* eslint-disable storybook/use-storybook-testing-library */ // @TODO: use addon-interactions and remove the rule disable above import React from 'react'; import { ComponentStoryObj, ComponentMeta } from '@storybook/react'; -import { screen } from '@testing-library/dom'; +import { screen } from '@testing-library/react'; import userEvent from '@testing-library/user-event'; -import { AccountForm } from './AccountForm'; +import { AccountForm, AccountFormProps } from './AccountForm'; export default { // Title not needed due to CSF3 auto-title @@ -20,17 +21,19 @@ export default { // Standard.args = { passwordVerification: false }; // Standard.play = () => userEvent.type(screen.getByTestId('email'), 'michael@chromatic.com'); -export const Standard: ComponentStoryObj = { +type Story = ComponentStoryObj; + +export const Standard: Story = { // render: (args: AccountFormProps) => , args: { passwordVerification: false }, }; -export const StandardEmailFilled = { +export const StandardEmailFilled: Story = { ...Standard, play: () => userEvent.type(screen.getByTestId('email'), 'michael@chromatic.com'), }; -export const StandardEmailFailed = { +export const StandardEmailFailed: Story = { ...Standard, play: async () => { await userEvent.type(screen.getByTestId('email'), 'michael@chromatic.com.com@com'); @@ -39,41 +42,41 @@ export const StandardEmailFailed = { }, }; -export const StandardPasswordFailed = { +export const StandardPasswordFailed: Story = { ...Standard, - play: async () => { - await StandardEmailFilled.play(); + play: async (context) => { + await StandardEmailFilled.play!(context); await userEvent.type(screen.getByTestId('password1'), 'asdf'); await userEvent.click(screen.getByTestId('submit')); }, }; -export const StandardFailHover = { +export const StandardFailHover: Story = { ...StandardPasswordFailed, - play: async () => { - await StandardPasswordFailed.play(); + play: async (context) => { + await StandardPasswordFailed.play!(context); await sleep(100); await userEvent.hover(screen.getByTestId('password-error-info')); }, }; -export const Verification: ComponentStoryObj = { +export const Verification: Story = { args: { passwordVerification: true }, }; -export const VerificationPasssword1 = { +export const VerificationPasssword1: Story = { ...Verification, - play: async () => { - await StandardEmailFilled.play(); + play: async (context) => { + await StandardEmailFilled.play!(context); await userEvent.type(screen.getByTestId('password1'), 'asdfasdf'); await userEvent.click(screen.getByTestId('submit')); }, }; -export const VerificationPasswordMismatch = { +export const VerificationPasswordMismatch: Story = { ...Verification, - play: async () => { - await StandardEmailFilled.play(); + play: async (context) => { + await StandardEmailFilled.play!(context); await userEvent.type(screen.getByTestId('password1'), 'asdfasdf'); await userEvent.type(screen.getByTestId('password2'), 'asdf1234'); await userEvent.click(screen.getByTestId('submit')); @@ -82,10 +85,10 @@ export const VerificationPasswordMismatch = { const sleep = (ms: number) => new Promise((r) => setTimeout(r, ms)); -export const VerificationSuccess = { +export const VerificationSuccess: Story = { ...Verification, - play: async () => { - await StandardEmailFilled.play(); + play: async (context) => { + await StandardEmailFilled.play!(context); await sleep(1000); await userEvent.type(screen.getByTestId('password1'), 'asdfasdf', { delay: 50 }); await sleep(1000); @@ -94,3 +97,13 @@ export const VerificationSuccess = { await userEvent.click(screen.getByTestId('submit')); }, }; + +export const StandardWithRenderFunction: Story = { + ...Standard, + render: (args: AccountFormProps) => ( +
+

This uses a custom render

+ +
+ ), +}; diff --git a/examples/react-ts/src/AccountForm.test.tsx b/examples/react-ts/src/AccountForm.test.tsx new file mode 100644 index 000000000000..863fabe75e06 --- /dev/null +++ b/examples/react-ts/src/AccountForm.test.tsx @@ -0,0 +1,24 @@ +import React from 'react'; +import { render, screen } from '@testing-library/react'; + +import { composeStories, composeStory } from '@storybook/react'; + +import * as stories from './AccountForm.stories'; + +const { Standard } = composeStories(stories); + +test('renders form', async () => { + await render(); +}); + +test('fills input from play function', async () => { + // @ts-ignore + const StandardEmailFilled = composeStory(stories.StandardEmailFilled, stories.default); + const { container } = await render(); + + // @ts-ignore + await StandardEmailFilled.play({ canvasElement: container }); + + const emailInput = screen.getByTestId('email') as HTMLInputElement; + expect(emailInput.value).toBe('michael@chromatic.com'); +}); diff --git a/yarn.lock b/yarn.lock index 1c885aabd8ff..3161316ab6d9 100644 --- a/yarn.lock +++ b/yarn.lock @@ -7766,6 +7766,7 @@ __metadata: "@storybook/react": 6.5.0-alpha.23 "@storybook/theming": 6.5.0-alpha.23 "@testing-library/dom": ^7.31.2 + "@testing-library/react": 12.1.2 "@testing-library/user-event": ^13.1.9 "@types/babel__preset-env": ^7 "@types/react": ^16.14.2 @@ -9093,7 +9094,7 @@ __metadata: languageName: node linkType: hard -"@testing-library/dom@npm:^8.3.0": +"@testing-library/dom@npm:^8.0.0, @testing-library/dom@npm:^8.3.0": version: 8.11.2 resolution: "@testing-library/dom@npm:8.11.2" dependencies: @@ -9126,6 +9127,19 @@ __metadata: languageName: node linkType: hard +"@testing-library/react@npm:12.1.2": + version: 12.1.2 + resolution: "@testing-library/react@npm:12.1.2" + dependencies: + "@babel/runtime": ^7.12.5 + "@testing-library/dom": ^8.0.0 + peerDependencies: + react: "*" + react-dom: "*" + checksum: c8579252f5f0a23df368253108bbe5b4f26abb9ed5f514746ba6b2ce1a6d09592900526ef6284466af959b50fbb7afa1f37eb2ff629fc91abe70dade3da6cc9a + languageName: node + linkType: hard + "@testing-library/react@npm:^11.2.2": version: 11.2.7 resolution: "@testing-library/react@npm:11.2.7" @@ -17648,12 +17662,17 @@ __metadata: "@storybook/addon-ie11": 0.0.7--canary.5e87b64.0 "@storybook/addons": 6.5.0-alpha.23 "@storybook/builder-webpack4": 6.5.0-alpha.23 + "@storybook/components": 6.5.0-alpha.23 + "@storybook/jest": ^0.0.5 "@storybook/preset-create-react-app": ^3.1.6 "@storybook/react": 6.5.0-alpha.23 + "@storybook/testing-library": ^0.0.7 + "@storybook/theming": 6.5.0-alpha.23 "@types/jest": ^26.0.16 "@types/node": ^14.14.20 || ^16.0.0 "@types/react": ^16.14.2 "@types/react-dom": 16.9.10 + formik: 2.2.9 global: ^4.4.0 react: 16.14.0 react-dom: 16.14.0 @@ -23207,7 +23226,7 @@ __metadata: languageName: node linkType: hard -"formik@npm:^2.2.9": +"formik@npm:2.2.9, formik@npm:^2.2.9": version: 2.2.9 resolution: "formik@npm:2.2.9" dependencies: From d9ac7cb0302c2a4e8f81d28754fca5579ecc0c8c Mon Sep 17 00:00:00 2001 From: Yann Braga Date: Wed, 19 Jan 2022 17:17:04 +0100 Subject: [PATCH 04/31] fix storyStore tests --- lib/store/src/StoryStore.test.ts | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/lib/store/src/StoryStore.test.ts b/lib/store/src/StoryStore.test.ts index 8b882b373f9b..ce28c254f1ae 100644 --- a/lib/store/src/StoryStore.test.ts +++ b/lib/store/src/StoryStore.test.ts @@ -1,18 +1,18 @@ import { AnyFramework, ProjectAnnotations } from '@storybook/csf'; import global from 'global'; -import { prepareStory } from './prepareStory'; -import { processCSFFile } from './processCSFFile'; +import { prepareStory } from './csf/prepareStory'; +import { processCSFFile } from './csf/processCSFFile'; import { StoryStore } from './StoryStore'; import { StoryIndex } from './types'; import { HooksContext } from './hooks'; // Spy on prepareStory/processCSFFile -jest.mock('./prepareStory', () => ({ - prepareStory: jest.fn(jest.requireActual('./prepareStory').prepareStory), +jest.mock('./csf/prepareStory', () => ({ + prepareStory: jest.fn(jest.requireActual('./csf/prepareStory').prepareStory), })); -jest.mock('./processCSFFile', () => ({ - processCSFFile: jest.fn(jest.requireActual('./processCSFFile').processCSFFile), +jest.mock('./csf/processCSFFile', () => ({ + processCSFFile: jest.fn(jest.requireActual('./csf/processCSFFile').processCSFFile), })); jest.mock('global', () => ({ From 9ccbaacafad5b38985638df7a5fb86e582a3fa55 Mon Sep 17 00:00:00 2001 From: Yann Braga Date: Wed, 19 Jan 2022 17:17:57 +0100 Subject: [PATCH 05/31] improve testing utility types --- app/react/src/client/testing/types.ts | 21 +++++++++++---------- lib/store/src/csf/testing-utils/index.ts | 22 ++++++++++------------ 2 files changed, 21 insertions(+), 22 deletions(-) diff --git a/app/react/src/client/testing/types.ts b/app/react/src/client/testing/types.ts index 574495b01776..ac8d58ce7775 100644 --- a/app/react/src/client/testing/types.ts +++ b/app/react/src/client/testing/types.ts @@ -16,9 +16,7 @@ import type { */ export type GlobalConfig = NormalizedProjectAnnotations; -export type StoryFile = { default: Meta; __esModule?: boolean; __namedExportsOrder?: any }; - -export type TestingStory = StoryFn | StoryObj; +export type StoryFile = { default: Meta; __esModule?: boolean; __namedExportsOrder?: any }; export type TestingStoryPlayContext = Partial> & Pick; @@ -29,15 +27,18 @@ export type TestingStoryPlayFn = ( export type StoryFn = OriginalStoryFn & { play: TestingStoryPlayFn }; +export type TestingStory = StoryFn | StoryObj; + /** - * T represents the whole es module of a stories file. K of T means named exports (basically the Story type) + * T represents the whole ES module of a stories file. K of T means named exports (basically the Story type) * 1. pick the keys K of T that have properties that are Story * 2. infer the actual prop type for each Story * 3. reconstruct Story with Partial. Story -> Story> */ -export type StoriesWithPartialProps = any; -// { -// [K in keyof T as T[K] extends TestingStory ? K : never]: T[K] extends TestingStory -// ? StoryFn> -// : unknown; -// }; +export type StoriesWithPartialProps = { + // @TODO once we can use Typescript 4.0 do this to exclude nonStory exports: + // replace [K in keyof TModule] with [K in keyof TModule as TModule[K] extends TestingStory ? K : never] + [K in keyof TModule]: TModule[K] extends TestingStory + ? StoryFn> + : unknown; +}; diff --git a/lib/store/src/csf/testing-utils/index.ts b/lib/store/src/csf/testing-utils/index.ts index 7ebeece296be..bcbcb1f943be 100644 --- a/lib/store/src/csf/testing-utils/index.ts +++ b/lib/store/src/csf/testing-utils/index.ts @@ -4,6 +4,7 @@ import { AnnotatedStoryFn, ComponentAnnotations, Args, + StoryContext, } from '@storybook/csf'; import { prepareStory } from '../prepareStory'; @@ -18,14 +19,11 @@ if (process.env.NODE_ENV === 'test') { addons.setChannel(mockChannel()); } -export type StoryFile = { default: any; __esModule?: boolean; __namedExportsOrder?: any }; - -type Entries = { - [K in keyof T]: [K, T[K]]; -}[keyof T]; -export function objectEntries(t: T): Entries[] { - return Object.entries(t) as any; -} +export type StoryFile = { + default: Record; + __esModule?: boolean; + __namedExportsOrder?: string[]; +}; export function composeStory< TFramework extends AnyFramework = AnyFramework, @@ -56,14 +54,14 @@ export function composeStory< ); const composedStory = (extraArgs: Partial) => { - const context = { + const context: Partial = { ...preparedStory, hooks: new HooksContext(), globals: defaultGlobals, args: { ...preparedStory.initialArgs, ...extraArgs }, - } as any; + }; - return preparedStory.unboundStoryFn(context); + return preparedStory.unboundStoryFn(context as StoryContext); }; composedStory.storyName = story.storyName || story.name; @@ -80,7 +78,7 @@ export function composeStories( composeStoryFn: typeof composeStory ) { const { default: meta, __esModule, __namedExportsOrder, ...stories } = storiesImport; - const composedStories = objectEntries(stories).reduce((storiesMap, [key, _story]) => { + const composedStories = Object.entries(stories).reduce((storiesMap, [key, _story]) => { if (!isExportStory(key as string, meta)) { return storiesMap; } From bef9e354825e7f60d98524ba840d485dfdcd165f Mon Sep 17 00:00:00 2001 From: Yann Braga Date: Wed, 19 Jan 2022 17:18:26 +0100 Subject: [PATCH 06/31] test(store): add tests for testing utils --- lib/store/src/csf/testing-utils/index.test.ts | 67 +++++++++++++++++++ 1 file changed, 67 insertions(+) create mode 100644 lib/store/src/csf/testing-utils/index.test.ts diff --git a/lib/store/src/csf/testing-utils/index.test.ts b/lib/store/src/csf/testing-utils/index.test.ts new file mode 100644 index 000000000000..2eccf6c0b58b --- /dev/null +++ b/lib/store/src/csf/testing-utils/index.test.ts @@ -0,0 +1,67 @@ +import { composeStory, composeStories } from '.'; + +describe('composeStory', () => { + const meta = { + title: 'Button', + parameters: { + firstAddon: true, + }, + args: { + label: 'Hello World', + primary: true, + }, + }; + + test('should return story with composed args and parameters', () => { + const Story = () => {}; + Story.args = { primary: true }; + Story.parameters = { + parameters: { + secondAddon: true, + }, + }; + + const composedStory = composeStory(Story, meta); + expect(composedStory.args).toEqual({ ...Story.args, ...meta.args }); + expect(composedStory.parameters).toEqual( + // why is this erroring in TS? + expect.objectContaining({ ...Story.parameters, ...meta.parameters }) + ); + }); + + test('should throw an error if Story is undefined', () => { + expect(() => { + composeStory(undefined, meta); + }).toThrow(); + }); +}); + +describe('composeStories', () => { + test('should call composeStoryFn with stories', () => { + const composeConfigFn = jest.fn((v) => v); + const module = { + default: { + title: 'Button', + }, + StoryOne: () => {}, + StoryTwo: () => {}, + }; + const globalConfig = {}; + composeStories(module, globalConfig, composeConfigFn); + expect(composeConfigFn).toHaveBeenCalledWith(module.StoryOne, module.default, globalConfig); + expect(composeConfigFn).toHaveBeenCalledWith(module.StoryTwo, module.default, globalConfig); + }); + + test('should not call composeStoryFn for non-story exports', () => { + const composeConfigFn = jest.fn((v) => v); + const module = { + default: { + title: 'Button', + excludeStories: /Data/, + }, + mockData: {}, + }; + composeStories(module, {}, composeConfigFn); + expect(composeConfigFn).not.toHaveBeenCalled(); + }); +}); From 7571c0e55e58552b0baf2e6c95e31c911a807892 Mon Sep 17 00:00:00 2001 From: Yann Braga Date: Wed, 19 Jan 2022 17:33:50 +0100 Subject: [PATCH 07/31] chore: make example stories CSF version more explicit in cra-ts-essentials --- .../components/Button.stories.tsx | 34 ++++----- .../testing-react/components/Button.test.tsx | 28 ++++--- .../__snapshots__/internals.test.tsx.snap | 76 +++++++++---------- .../components/internals.test.tsx | 14 ++-- lib/store/src/csf/testing-utils/index.ts | 1 + 5 files changed, 76 insertions(+), 77 deletions(-) diff --git a/examples/cra-ts-essentials/src/stories/testing-react/components/Button.stories.tsx b/examples/cra-ts-essentials/src/stories/testing-react/components/Button.stories.tsx index cea434dba36c..bd5f81fb0d5e 100644 --- a/examples/cra-ts-essentials/src/stories/testing-react/components/Button.stories.tsx +++ b/examples/cra-ts-essentials/src/stories/testing-react/components/Button.stories.tsx @@ -17,16 +17,8 @@ export default { const Template: CSF2Story = (args) => ; }; -StoryWithLocale.storyName = 'WithLocale'; +CSF2StoryWithLocale.storyName = 'WithLocale'; -export const StoryWithParamsAndDecorator: CSF2Story = (args) => { +export const CSF2StoryWithParamsAndDecorator: CSF2Story = (args) => { return `; -exports[`Renders CSF3ButtonWithRender story 1`] = ` +exports[`Renders CSF2StoryWithLocale story 1`] = `
Locale: en
-
-

- I am a custom render function -

- -
+
`; -exports[`Renders InputFieldFilled story 1`] = ` +exports[`Renders CSF2StoryWithParamsAndDecorator story 1`] = `
Locale: en
- +
`; -exports[`Renders Primary story 1`] = ` +exports[`Renders CSF3Button story 1`] = `
@@ -63,7 +61,7 @@ exports[`Renders Primary story 1`] = ` en
+
+

+ I am a custom render function +

+ +
`; -exports[`Renders StoryWithLocale story 1`] = ` +exports[`Renders CSF3InputFieldFilled story 1`] = `
Locale: en
- +
`; -exports[`Renders StoryWithParamsAndDecorator story 1`] = ` +exports[`Renders CSF3Primary story 1`] = `
@@ -116,7 +116,7 @@ exports[`Renders StoryWithParamsAndDecorator story 1`] = ` en
-
- -`; - exports[`Renders CSF2StoryWithParamsAndDecorator story 1`] = `
@@ -125,3 +108,20 @@ exports[`Renders CSF3Primary story 1`] = `
`; + +exports[`Renders WithLocale story 1`] = ` + +
+
+ Locale: + en +
+ +
+ +`; diff --git a/lib/store/src/csf/testing-utils/index.ts b/lib/store/src/csf/testing-utils/index.ts index 4769d355f7de..b6f7adc3c0d4 100644 --- a/lib/store/src/csf/testing-utils/index.ts +++ b/lib/store/src/csf/testing-utils/index.ts @@ -14,7 +14,7 @@ import { normalizeStory } from '../normalizeStory'; import { HooksContext } from '../../hooks'; import { normalizeComponentAnnotations } from '../normalizeComponentAnnotations'; import { getValuesFromArgTypes, normalizeProjectAnnotations } from '..'; -import type { CSFExports, TestingStoryPlayFn } from './types'; +import type { CSFExports, TestingStoryPlayFn, TestingStory } from './types'; export * from './types'; @@ -80,23 +80,35 @@ export function composeStory< return composedStory; } +const getStoryName = (story: TestingStory) => { + if (story.storyName) { + return story.storyName; + } + + if (typeof story !== 'function' && story.name) { + return story.name; + } + + return undefined; +}; + export function composeStories( storiesImport: TModule, globalConfig: ProjectAnnotations, composeStoryFn: typeof composeStory ) { const { default: meta, __esModule, __namedExportsOrder, ...stories } = storiesImport; - const composedStories = Object.entries(stories).reduce((storiesMap, [key, _story]) => { - if (!isExportStory(key as string, meta)) { + const composedStories = Object.entries(stories).reduce((storiesMap, [exportsName, _story]) => { + if (!isExportStory(exportsName as string, meta)) { return storiesMap; } - const storyName = String(key); - const story = _story as any; - story.storyName = storyName; + const story = _story as TestingStory; + // Ensure story name is already set, else use exportsName as fallback. This is important for scenarios like snapshot testing + story.storyName = getStoryName(story) || String(exportsName); const result = Object.assign(storiesMap, { - [key]: composeStoryFn(story, meta, globalConfig), + [exportsName]: composeStoryFn(story, meta, globalConfig), }); return result; }, {}); From 7b7a841842068642ac9c0c1fbb77fee5bad3e54a Mon Sep 17 00:00:00 2001 From: Yann Braga Date: Wed, 26 Jan 2022 09:48:53 +0100 Subject: [PATCH 16/31] feat: support exportsName in composeStory - also rename parameters for better consistency --- app/react/src/client/testing/index.ts | 44 ++++++----- lib/store/src/csf/testing-utils/index.ts | 98 +++++++++++++----------- 2 files changed, 79 insertions(+), 63 deletions(-) diff --git a/app/react/src/client/testing/index.ts b/app/react/src/client/testing/index.ts index 00e28ba7a256..93a5a1b74414 100644 --- a/app/react/src/client/testing/index.ts +++ b/app/react/src/client/testing/index.ts @@ -18,24 +18,25 @@ import type { StoriesWithPartialProps, TestingStory } from './types'; *```jsx * // setup.js (for jest) * import { setGlobalConfig } from '@storybook/react'; - * import * as globalStorybookConfig from './.storybook/preview'; + * import * as projectAnnotations from './.storybook/preview'; * - * setGlobalConfig(globalStorybookConfig); + * setGlobalConfig(projectAnnotations); *``` * - * @param config - e.g. (import * as globalConfig from '../.storybook/preview') + * @param projectAnnotations - e.g. (import * as projectAnnotations from '../.storybook/preview') */ -export function setGlobalConfig(config: ProjectAnnotations) { - originalSetGlobalConfig(config); +export function setGlobalConfig(projectAnnotations: ProjectAnnotations) { + originalSetGlobalConfig(projectAnnotations); } -const defaultGlobalConfig: ProjectAnnotations = { +// This will not be necessary once we have auto preset loading +const defaultProjectAnnotations: ProjectAnnotations = { render, }; /** * Function that will receive a story along with meta (e.g. a default export from a .stories file) - * and optionally a globalConfig e.g. (import * from '../.storybook/preview) + * and optionally projectAnnotations e.g. (import * from '../.storybook/preview) * and will return a composed component that has all args/parameters/decorators/etc combined and applied to it. * * @@ -56,25 +57,28 @@ const defaultGlobalConfig: ProjectAnnotations = { *``` * * @param story - * @param meta - e.g. (import Meta from './Button.stories') - * @param [globalConfig] - e.g. (import * as globalConfig from '../.storybook/preview') this can be applied automatically if you use `setGlobalConfig` in your setup files. + * @param componentAnnotations - e.g. (import Meta from './Button.stories') + * @param [projectAnnotations] - e.g. (import * as projectAnnotations from '../.storybook/preview') this can be applied automatically if you use `setGlobalConfig` in your setup files. + * @param [exportsName] - in case your story does not contain a name and you want it to have a name. */ export function composeStory( story: TestingStory, - meta: Meta, - globalConfig?: ProjectAnnotations + componentAnnotations: Meta, + projectAnnotations?: ProjectAnnotations, + exportsName?: string ) { return originalComposeStory( story, - meta, - globalConfig, - defaultGlobalConfig + componentAnnotations, + projectAnnotations, + defaultProjectAnnotations, + exportsName ); } /** * Function that will receive a stories import (e.g. `import * as stories from './Button.stories'`) - * and optionally a globalConfig (e.g. `import * from '../.storybook/preview`) + * and optionally projectAnnotations (e.g. `import * from '../.storybook/preview`) * and will return an object containing all the stories passed, but now as a composed component that has all args/parameters/decorators/etc combined and applied to it. * * @@ -94,14 +98,14 @@ export function composeStory( * }); *``` * - * @param storiesImport - e.g. (import * as stories from './Button.stories') - * @param [globalConfig] - e.g. (import * as globalConfig from '../.storybook/preview') this can be applied automatically if you use `setGlobalConfig` in your setup files. + * @param csfExports - e.g. (import * as stories from './Button.stories') + * @param [projectAnnotations] - e.g. (import * as projectAnnotations from '../.storybook/preview') this can be applied automatically if you use `setGlobalConfig` in your setup files. */ export function composeStories>( - storiesImport: TModule, - globalConfig?: ProjectAnnotations + csfExports: TModule, + projectAnnotations?: ProjectAnnotations ) { - const composedStories = originalComposeStories(storiesImport, globalConfig, composeStory); + const composedStories = originalComposeStories(csfExports, projectAnnotations, composeStory); return composedStories as unknown as Omit, keyof CSFExports>; } diff --git a/lib/store/src/csf/testing-utils/index.ts b/lib/store/src/csf/testing-utils/index.ts index b6f7adc3c0d4..6e64fd054014 100644 --- a/lib/store/src/csf/testing-utils/index.ts +++ b/lib/store/src/csf/testing-utils/index.ts @@ -7,6 +7,7 @@ import { ProjectAnnotations, Args, StoryContext, + Parameters, } from '@storybook/csf'; import { prepareStory } from '../prepareStory'; @@ -14,7 +15,7 @@ import { normalizeStory } from '../normalizeStory'; import { HooksContext } from '../../hooks'; import { normalizeComponentAnnotations } from '../normalizeComponentAnnotations'; import { getValuesFromArgTypes, normalizeProjectAnnotations } from '..'; -import type { CSFExports, TestingStoryPlayFn, TestingStory } from './types'; +import type { CSFExports, TestingStoryPlayFn } from './types'; export * from './types'; @@ -24,91 +25,102 @@ if (process.env.NODE_ENV === 'test') { addons.setChannel(mockChannel()); } -let GLOBAL_STORYBOOK_CONFIG = {}; +let GLOBAL_STORYBOOK_PROJECT_ANNOTATIONS = {}; export function setGlobalConfig( - config: ProjectAnnotations + projectAnnotations: ProjectAnnotations ) { - GLOBAL_STORYBOOK_CONFIG = config; + GLOBAL_STORYBOOK_PROJECT_ANNOTATIONS = projectAnnotations; +} + +interface ComposeStory { + ( + storyAnnotations: AnnotatedStoryFn | StoryAnnotations, + componentAnnotations: ComponentAnnotations, + projectAnnotations: ProjectAnnotations, + exportsName?: string + ): { + (extraArgs: Partial): TFramework['storyResult']; + storyName: string; + args: Args; + play: TestingStoryPlayFn; + parameters: Parameters; + }; } export function composeStory< TFramework extends AnyFramework = AnyFramework, TArgs extends Args = Args >( - story: AnnotatedStoryFn | StoryAnnotations, - meta: ComponentAnnotations, - globalConfig: ProjectAnnotations = GLOBAL_STORYBOOK_CONFIG, - defaultConfig: ProjectAnnotations = {} + storyAnnotations: AnnotatedStoryFn | StoryAnnotations, + componentAnnotations: ComponentAnnotations, + projectAnnotations: ProjectAnnotations = GLOBAL_STORYBOOK_PROJECT_ANNOTATIONS, + defaultConfig: ProjectAnnotations = {}, + exportsName?: string ) { - if (story === undefined) { + if (storyAnnotations === undefined) { throw new Error('Expected a story but received undefined.'); } - const projectAnnotations = { ...defaultConfig, ...globalConfig }; + const normalizedComponentAnnotations = normalizeComponentAnnotations(componentAnnotations); - const normalizedMeta = normalizeComponentAnnotations(meta); + const storyExportsName = + exportsName || + storyAnnotations.storyName || + storyAnnotations.story?.name || + storyAnnotations.name; - const normalizedStory = normalizeStory(story.name, story, normalizedMeta); + const normalizedStory = normalizeStory( + storyExportsName, + storyAnnotations, + normalizedComponentAnnotations + ); - const normalizedProjectAnnotations = normalizeProjectAnnotations(projectAnnotations); + const normalizedProjectAnnotations = normalizeProjectAnnotations({ + ...projectAnnotations, + ...defaultConfig, + }); - const preparedStory = prepareStory( + const story = prepareStory( normalizedStory, - normalizedMeta, + normalizedComponentAnnotations, normalizedProjectAnnotations ); - const defaultGlobals = getValuesFromArgTypes(globalConfig.globalTypes); + const defaultGlobals = getValuesFromArgTypes(projectAnnotations.globalTypes); const composedStory = (extraArgs: Partial) => { const context: Partial = { - ...preparedStory, + ...story, hooks: new HooksContext(), globals: defaultGlobals, - args: { ...preparedStory.initialArgs, ...extraArgs }, + args: { ...story.initialArgs, ...extraArgs }, }; - return preparedStory.unboundStoryFn(context as StoryContext); + return story.unboundStoryFn(context as StoryContext); }; - composedStory.storyName = story.storyName || story.name; - composedStory.args = preparedStory.initialArgs; - composedStory.play = preparedStory.playFunction as TestingStoryPlayFn; - composedStory.parameters = preparedStory.parameters; + composedStory.storyName = storyAnnotations.storyName || storyAnnotations.name; + composedStory.args = story.initialArgs; + composedStory.play = story.playFunction as TestingStoryPlayFn; + composedStory.parameters = story.parameters; return composedStory; } -const getStoryName = (story: TestingStory) => { - if (story.storyName) { - return story.storyName; - } - - if (typeof story !== 'function' && story.name) { - return story.name; - } - - return undefined; -}; - export function composeStories( storiesImport: TModule, globalConfig: ProjectAnnotations, - composeStoryFn: typeof composeStory + composeStoryFn: ComposeStory ) { const { default: meta, __esModule, __namedExportsOrder, ...stories } = storiesImport; - const composedStories = Object.entries(stories).reduce((storiesMap, [exportsName, _story]) => { - if (!isExportStory(exportsName as string, meta)) { + const composedStories = Object.entries(stories).reduce((storiesMap, [exportsName, story]) => { + if (!isExportStory(exportsName, meta)) { return storiesMap; } - const story = _story as TestingStory; - // Ensure story name is already set, else use exportsName as fallback. This is important for scenarios like snapshot testing - story.storyName = getStoryName(story) || String(exportsName); - const result = Object.assign(storiesMap, { - [exportsName]: composeStoryFn(story, meta, globalConfig), + [exportsName]: composeStoryFn(story, meta, globalConfig, exportsName), }); return result; }, {}); From a647db320b2547c1d4b2145dfb318d6836fad0b0 Mon Sep 17 00:00:00 2001 From: Yann Braga Date: Wed, 26 Jan 2022 09:53:18 +0100 Subject: [PATCH 17/31] test: update snapshots --- .../__snapshots__/internals.test.tsx.snap | 50 +++++++++---------- 1 file changed, 25 insertions(+), 25 deletions(-) diff --git a/examples/cra-ts-essentials/src/stories/testing-react/components/__snapshots__/internals.test.tsx.snap b/examples/cra-ts-essentials/src/stories/testing-react/components/__snapshots__/internals.test.tsx.snap index dc838a933064..ff8ed9b7e781 100644 --- a/examples/cra-ts-essentials/src/stories/testing-react/components/__snapshots__/internals.test.tsx.snap +++ b/examples/cra-ts-essentials/src/stories/testing-react/components/__snapshots__/internals.test.tsx.snap @@ -1,6 +1,6 @@ // Jest Snapshot v1, https://goo.gl/fbAQLP -exports[`Renders CSF2Secondary story 1`] = ` +exports[`Renders CSF2StoryWithParamsAndDecorator story 1`] = `
@@ -12,13 +12,30 @@ exports[`Renders CSF2Secondary story 1`] = ` label="Button" type="button" > - Children coming from story args! + foo
`; -exports[`Renders CSF2StoryWithParamsAndDecorator story 1`] = ` +exports[`Renders WithLocale story 1`] = ` + +
+
+ Locale: + en +
+ +
+ +`; + +exports[`Renders bound Template story 1`] = `
@@ -30,13 +47,13 @@ exports[`Renders CSF2StoryWithParamsAndDecorator story 1`] = ` label="Button" type="button" > - foo + Children coming from story args!
`; -exports[`Renders CSF3Button story 1`] = ` +exports[`Renders undefined story 1`] = `
@@ -54,7 +71,7 @@ exports[`Renders CSF3Button story 1`] = ` `; -exports[`Renders CSF3ButtonWithRender story 1`] = ` +exports[`Renders undefined story 2`] = `
@@ -79,7 +96,7 @@ exports[`Renders CSF3ButtonWithRender story 1`] = ` `; -exports[`Renders CSF3InputFieldFilled story 1`] = ` +exports[`Renders undefined story 3`] = `
@@ -91,7 +108,7 @@ exports[`Renders CSF3InputFieldFilled story 1`] = ` `; -exports[`Renders CSF3Primary story 1`] = ` +exports[`Renders undefined story 4`] = `
@@ -108,20 +125,3 @@ exports[`Renders CSF3Primary story 1`] = `
`; - -exports[`Renders WithLocale story 1`] = ` - -
-
- Locale: - en -
- -
- -`; From a4ea8486c79836641523e362093ce7c95594af6f Mon Sep 17 00:00:00 2001 From: Yann Braga Date: Wed, 23 Mar 2022 17:23:50 +0100 Subject: [PATCH 18/31] allow setGlobalConfig to accept array of configurations and compose them --- app/react/src/client/testing/index.ts | 4 +- .../src/__snapshots__/storyshots.test.ts.snap | 373 ++++++++++++++++++ lib/store/package.json | 1 + lib/store/src/csf/testing-utils/index.test.ts | 16 +- lib/store/src/csf/testing-utils/index.ts | 7 +- yarn.lock | 198 +++++++++- 6 files changed, 591 insertions(+), 8 deletions(-) diff --git a/app/react/src/client/testing/index.ts b/app/react/src/client/testing/index.ts index 93a5a1b74414..1667ba9fd3e9 100644 --- a/app/react/src/client/testing/index.ts +++ b/app/react/src/client/testing/index.ts @@ -25,7 +25,9 @@ import type { StoriesWithPartialProps, TestingStory } from './types'; * * @param projectAnnotations - e.g. (import * as projectAnnotations from '../.storybook/preview') */ -export function setGlobalConfig(projectAnnotations: ProjectAnnotations) { +export function setGlobalConfig( + projectAnnotations: ProjectAnnotations | ProjectAnnotations[] +) { originalSetGlobalConfig(projectAnnotations); } diff --git a/examples/react-ts/src/__snapshots__/storyshots.test.ts.snap b/examples/react-ts/src/__snapshots__/storyshots.test.ts.snap index 3926326cf9aa..e667fd935d97 100644 --- a/examples/react-ts/src/__snapshots__/storyshots.test.ts.snap +++ b/examples/react-ts/src/__snapshots__/storyshots.test.ts.snap @@ -1840,6 +1840,379 @@ exports[`Storyshots Demo/AccountForm Standard Password Failed 1`] = ` `; +exports[`Storyshots Demo/AccountForm Standard With Render Function 1`] = ` +.emotion-15 { + font-family: "Nunito Sans",-apple-system,".SFNSText-Regular","San Francisco",BlinkMacSystemFont,"Segoe UI","Helvetica Neue",Helvetica,Arial,sans-serif; + display: -webkit-box; + display: -webkit-flex; + display: -ms-flexbox; + display: flex; + -webkit-flex-direction: column; + -ms-flex-direction: column; + flex-direction: column; + -webkit-align-items: center; + -webkit-box-align: center; + -ms-flex-align: center; + align-items: center; + width: 450px; + padding: 32px; + background-color: #FFFFFF; + border-radius: 7px; +} + +.emotion-2 { + display: -webkit-box; + display: -webkit-flex; + display: -ms-flexbox; + display: flex; + -webkit-align-items: center; + -webkit-box-align: center; + -ms-flex-align: center; + align-items: center; + -webkit-box-pack: center; + -webkit-justify-content: center; + -ms-flex-pack: center; + justify-content: center; +} + +.emotion-0 { + height: 40px; + z-index: 10; + margin-left: 32px; +} + +.emotion-1 { + height: 40px; + z-index: 1; + left: -32px; + position: relative; +} + +.emotion-3 { + margin-top: 20px; + text-align: center; +} + +.emotion-14 { + display: -webkit-box; + display: -webkit-flex; + display: -ms-flexbox; + display: flex; + -webkit-align-items: flex-start; + -webkit-box-align: flex-start; + -ms-flex-align: flex-start; + align-items: flex-start; + -webkit-box-pack: center; + -webkit-justify-content: center; + -ms-flex-pack: center; + justify-content: center; + width: 350px; + min-height: 189px; + margin-top: 8px; +} + +.emotion-13 { + width: 100%; + -webkit-align-self: flex-start; + -ms-flex-item-align: start; + align-self: flex-start; +} + +.emotion-13[aria-disabled="true"] { + opacity: 0.6; +} + +.emotion-6 { + display: -webkit-box; + display: -webkit-flex; + display: -ms-flexbox; + display: flex; + -webkit-flex-direction: column; + -ms-flex-direction: column; + flex-direction: column; + -webkit-box-pack: stretch; + -webkit-justify-content: stretch; + -ms-flex-pack: stretch; + justify-content: stretch; + margin-bottom: 10px; +} + +.emotion-4 { + font-size: 13px; + font-weight: 500; + margin-bottom: 6px; +} + +.emotion-5 { + font-size: 14px; + color: #333333; + padding: 10px 15px; + border-radius: 4px; + -webkit-appearance: none; + -moz-appearance: none; + appearance: none; + outline: none; + border: 0 none; + box-shadow: rgb(0 0 0 / 10%) 0px 0px 0px 1px inset; +} + +.emotion-5:focus { + box-shadow: rgb(30 167 253) 0px 0px 0px 1px inset; +} + +.emotion-5:active { + box-shadow: rgb(30 167 253) 0px 0px 0px 1px inset; +} + +.emotion-5[aria-invalid="true"] { + box-shadow: rgb(255 68 0) 0px 0px 0px 1px inset; +} + +.emotion-12 { + -webkit-align-self: stretch; + -ms-flex-item-align: stretch; + align-self: stretch; + display: -webkit-box; + display: -webkit-flex; + display: -ms-flexbox; + display: flex; + -webkit-box-pack: justify; + -webkit-justify-content: space-between; + -ms-flex-pack: justify; + justify-content: space-between; + margin-top: 24px; +} + +.emotion-10 { + background-color: transparent; + border: 0 none; + outline: none; + -webkit-appearance: none; + -moz-appearance: none; + appearance: none; + font-weight: 500; + font-size: 12px; + -webkit-flex-basis: 50%; + -ms-flex-preferred-size: 50%; + flex-basis: 50%; + cursor: pointer; + padding: 11px 16px; + border-radius: 4px; + text-transform: uppercase; + margin-right: 8px; + background-color: #1EA7FD; + color: #FFFFFF; + opacity: 0.6; + box-shadow: rgb(30 167 253 / 10%) 0 0 0 1px inset; +} + +.emotion-10:focus { + -webkit-text-decoration: underline; + text-decoration: underline; + font-weight: 700; +} + +.emotion-10:active { + -webkit-text-decoration: underline; + text-decoration: underline; + font-weight: 700; +} + +.emotion-10[aria-disabled="true"] { + cursor: default; +} + +.emotion-11 { + background-color: transparent; + border: 0 none; + outline: none; + -webkit-appearance: none; + -moz-appearance: none; + appearance: none; + font-weight: 500; + font-size: 12px; + -webkit-flex-basis: 50%; + -ms-flex-preferred-size: 50%; + flex-basis: 50%; + cursor: pointer; + padding: 11px 16px; + border-radius: 4px; + text-transform: uppercase; + margin-left: 8px; + box-shadow: rgb(30 167 253) 0 0 0 1px inset; + color: #1EA7FD; +} + +.emotion-11:focus { + -webkit-text-decoration: underline; + text-decoration: underline; + font-weight: 700; +} + +.emotion-11:active { + -webkit-text-decoration: underline; + text-decoration: underline; + font-weight: 700; +} + +.emotion-11[aria-disabled="true"] { + cursor: default; +} + +
+

+ This uses a custom render +

+
+
+ + + Storybook icon + + + + + + + + + + Storybook + + + + + +
+

+ Create an account to join the Storybook community +

+
+
+
+ + +
+
+ + +
+
+ + +
+
+
+
+
+`; + exports[`Storyshots Demo/AccountForm Verification 1`] = ` .emotion-18 { font-family: "Nunito Sans",-apple-system,".SFNSText-Regular","San Francisco",BlinkMacSystemFont,"Segoe UI","Helvetica Neue",Helvetica,Arial,sans-serif; diff --git a/lib/store/package.json b/lib/store/package.json index 7f4cd3a42189..a3cbb92c543f 100644 --- a/lib/store/package.json +++ b/lib/store/package.json @@ -44,6 +44,7 @@ "@storybook/client-logger": "6.5.0-alpha.50", "@storybook/core-events": "6.5.0-alpha.50", "@storybook/csf": "0.0.2--canary.507502b.0", + "@storybook/preview-web": "6.5.0-alpha.50", "core-js": "^3.8.2", "fast-deep-equal": "^3.1.3", "global": "^4.4.0", diff --git a/lib/store/src/csf/testing-utils/index.test.ts b/lib/store/src/csf/testing-utils/index.test.ts index 2eccf6c0b58b..14e2e6deab76 100644 --- a/lib/store/src/csf/testing-utils/index.test.ts +++ b/lib/store/src/csf/testing-utils/index.test.ts @@ -1,5 +1,6 @@ import { composeStory, composeStories } from '.'; +// Most integration tests for this functionality are located under examples/cra-ts-essentials describe('composeStory', () => { const meta = { title: 'Button', @@ -24,7 +25,6 @@ describe('composeStory', () => { const composedStory = composeStory(Story, meta); expect(composedStory.args).toEqual({ ...Story.args, ...meta.args }); expect(composedStory.parameters).toEqual( - // why is this erroring in TS? expect.objectContaining({ ...Story.parameters, ...meta.parameters }) ); }); @@ -48,8 +48,18 @@ describe('composeStories', () => { }; const globalConfig = {}; composeStories(module, globalConfig, composeConfigFn); - expect(composeConfigFn).toHaveBeenCalledWith(module.StoryOne, module.default, globalConfig); - expect(composeConfigFn).toHaveBeenCalledWith(module.StoryTwo, module.default, globalConfig); + expect(composeConfigFn).toHaveBeenCalledWith( + module.StoryOne, + module.default, + globalConfig, + 'StoryOne' + ); + expect(composeConfigFn).toHaveBeenCalledWith( + module.StoryTwo, + module.default, + globalConfig, + 'StoryTwo' + ); }); test('should not call composeStoryFn for non-story exports', () => { diff --git a/lib/store/src/csf/testing-utils/index.ts b/lib/store/src/csf/testing-utils/index.ts index 6e64fd054014..a4d6c2ae9352 100644 --- a/lib/store/src/csf/testing-utils/index.ts +++ b/lib/store/src/csf/testing-utils/index.ts @@ -9,6 +9,7 @@ import { StoryContext, Parameters, } from '@storybook/csf'; +import { composeConfigs } from '@storybook/preview-web'; import { prepareStory } from '../prepareStory'; import { normalizeStory } from '../normalizeStory'; @@ -28,9 +29,11 @@ if (process.env.NODE_ENV === 'test') { let GLOBAL_STORYBOOK_PROJECT_ANNOTATIONS = {}; export function setGlobalConfig( - projectAnnotations: ProjectAnnotations + projectAnnotations: ProjectAnnotations | ProjectAnnotations[] ) { - GLOBAL_STORYBOOK_PROJECT_ANNOTATIONS = projectAnnotations; + GLOBAL_STORYBOOK_PROJECT_ANNOTATIONS = Array.isArray(projectAnnotations) + ? composeConfigs(projectAnnotations) + : projectAnnotations; } interface ComposeStory { diff --git a/yarn.lock b/yarn.lock index a8b13ff23145..aa9a93829908 100644 --- a/yarn.lock +++ b/yarn.lock @@ -6833,6 +6833,28 @@ __metadata: languageName: node linkType: hard +"@storybook/addons@npm:6.4.19": + version: 6.4.19 + resolution: "@storybook/addons@npm:6.4.19" + dependencies: + "@storybook/api": 6.4.19 + "@storybook/channels": 6.4.19 + "@storybook/client-logger": 6.4.19 + "@storybook/core-events": 6.4.19 + "@storybook/csf": 0.0.2--canary.87bc651.0 + "@storybook/router": 6.4.19 + "@storybook/theming": 6.4.19 + "@types/webpack-env": ^1.16.0 + core-js: ^3.8.2 + global: ^4.4.0 + regenerator-runtime: ^0.13.7 + peerDependencies: + react: ^16.8.0 || ^17.0.0 + react-dom: ^16.8.0 || ^17.0.0 + checksum: f309622784a965ff7fd2d16f344397eeebaab0006406b6372129f43390a4015c83c16e1e6d32c6ad360517523ec2221ed4f8cac3853984d60f4ab2a3705f2a45 + languageName: node + linkType: hard + "@storybook/angular@6.5.0-alpha.50, @storybook/angular@workspace:*, @storybook/angular@workspace:app/angular": version: 0.0.0-use.local resolution: "@storybook/angular@workspace:app/angular" @@ -6983,6 +7005,34 @@ __metadata: languageName: node linkType: hard +"@storybook/api@npm:6.4.19": + version: 6.4.19 + resolution: "@storybook/api@npm:6.4.19" + dependencies: + "@storybook/channels": 6.4.19 + "@storybook/client-logger": 6.4.19 + "@storybook/core-events": 6.4.19 + "@storybook/csf": 0.0.2--canary.87bc651.0 + "@storybook/router": 6.4.19 + "@storybook/semver": ^7.3.2 + "@storybook/theming": 6.4.19 + core-js: ^3.8.2 + fast-deep-equal: ^3.1.3 + global: ^4.4.0 + lodash: ^4.17.21 + memoizerific: ^1.11.3 + regenerator-runtime: ^0.13.7 + store2: ^2.12.0 + telejson: ^5.3.2 + ts-dedent: ^2.0.0 + util-deprecate: ^1.0.2 + peerDependencies: + react: ^16.8.0 || ^17.0.0 + react-dom: ^16.8.0 || ^17.0.0 + checksum: 77fae2be5269d45d3d30d8364ca35b7b95ffaebe5ae1780275f97c2ebb1b6d9e3fa2a260e82587bbe141feeebac5054e44add36fec19cefe6d058e01ce4810ee + languageName: node + linkType: hard + "@storybook/babel-plugin-require-context-hook@npm:1.0.1": version: 1.0.1 resolution: "@storybook/babel-plugin-require-context-hook@npm:1.0.1" @@ -7125,6 +7175,21 @@ __metadata: languageName: unknown linkType: soft +"@storybook/channel-postmessage@npm:6.4.19": + version: 6.4.19 + resolution: "@storybook/channel-postmessage@npm:6.4.19" + dependencies: + "@storybook/channels": 6.4.19 + "@storybook/client-logger": 6.4.19 + "@storybook/core-events": 6.4.19 + core-js: ^3.8.2 + global: ^4.4.0 + qs: ^6.10.0 + telejson: ^5.3.2 + checksum: ed1dfd996456ccd23246b415db1de228814651ed56692e76ee69f9b0308a6b1b0355d3a3cc7afc3603c2850d3e27ec420e574f55a93e16b08f275eb7e0d8de82 + languageName: node + linkType: hard + "@storybook/channel-websocket@6.5.0-alpha.50, @storybook/channel-websocket@workspace:*, @storybook/channel-websocket@workspace:lib/channel-websocket": version: 0.0.0-use.local resolution: "@storybook/channel-websocket@workspace:lib/channel-websocket" @@ -7158,6 +7223,17 @@ __metadata: languageName: node linkType: hard +"@storybook/channels@npm:6.4.19": + version: 6.4.19 + resolution: "@storybook/channels@npm:6.4.19" + dependencies: + core-js: ^3.8.2 + ts-dedent: ^2.0.0 + util-deprecate: ^1.0.2 + checksum: 14700d23d848f9c06e87650d4d11d764012baa6405b9c7eedb04a9f162660f49233a572f9c6a89c9bbc49ba378895ecf151dfe792fb8e6482a9a0de002d80e4e + languageName: node + linkType: hard + "@storybook/cli@6.5.0-alpha.50, @storybook/cli@workspace:*, @storybook/cli@workspace:lib/cli": version: 0.0.0-use.local resolution: "@storybook/cli@workspace:lib/cli" @@ -7254,6 +7330,16 @@ __metadata: languageName: node linkType: hard +"@storybook/client-logger@npm:6.4.19": + version: 6.4.19 + resolution: "@storybook/client-logger@npm:6.4.19" + dependencies: + core-js: ^3.8.2 + global: ^4.4.0 + checksum: 06e6e463ea05560280fc34faeddfd86475064b1a2eb04c1a1da230f4d761d0aebfbad5ae2978412edece7bac01f47ff09f9620a77936ffe6c9c7e20438a17acf + languageName: node + linkType: hard + "@storybook/codemod@6.5.0-alpha.50, @storybook/codemod@workspace:*, @storybook/codemod@workspace:lib/codemod": version: 0.0.0-use.local resolution: "@storybook/codemod@workspace:lib/codemod" @@ -7434,6 +7520,15 @@ __metadata: languageName: node linkType: hard +"@storybook/core-events@npm:6.4.19": + version: 6.4.19 + resolution: "@storybook/core-events@npm:6.4.19" + dependencies: + core-js: ^3.8.2 + checksum: 574d1fc3adc23ec2a12246898e5f27eaad0592db664f9691dc855dd4ceddcb248d180a389e5c98d69324de2675c818d9cbae67a58d5ed5b6ff4d0e2be0346711 + languageName: node + linkType: hard + "@storybook/core-server@6.5.0-alpha.50, @storybook/core-server@workspace:lib/core-server": version: 0.0.0-use.local resolution: "@storybook/core-server@workspace:lib/core-server" @@ -8076,6 +8171,33 @@ __metadata: languageName: unknown linkType: soft +"@storybook/preview-web@npm:": + version: 6.4.19 + resolution: "@storybook/preview-web@npm:6.4.19" + dependencies: + "@storybook/addons": 6.4.19 + "@storybook/channel-postmessage": 6.4.19 + "@storybook/client-logger": 6.4.19 + "@storybook/core-events": 6.4.19 + "@storybook/csf": 0.0.2--canary.87bc651.0 + "@storybook/store": 6.4.19 + ansi-to-html: ^0.6.11 + core-js: ^3.8.2 + global: ^4.4.0 + lodash: ^4.17.21 + qs: ^6.10.0 + regenerator-runtime: ^0.13.7 + synchronous-promise: ^2.0.15 + ts-dedent: ^2.0.0 + unfetch: ^4.2.0 + util-deprecate: ^1.0.2 + peerDependencies: + react: ^16.8.0 || ^17.0.0 + react-dom: ^16.8.0 || ^17.0.0 + checksum: 7da30e8dd1cf4ec0fa05c6cf655a1a80bf7df22fc94590126aba4d9dbe3822d5b9bb00f3fad2d7bf0ebd323ef45c9da7f4eaee16e2bcc497e9c4be946c0bb00d + languageName: node + linkType: hard + "@storybook/react-docgen-typescript-plugin@npm:1.0.2-canary.6.9d540b91e815f8fc2f8829189deb00553559ff63.0": version: 1.0.2-canary.6.9d540b91e815f8fc2f8829189deb00553559ff63.0 resolution: "@storybook/react-docgen-typescript-plugin@npm:1.0.2-canary.6.9d540b91e815f8fc2f8829189deb00553559ff63.0" @@ -8452,6 +8574,28 @@ __metadata: languageName: node linkType: hard +"@storybook/router@npm:6.4.19": + version: 6.4.19 + resolution: "@storybook/router@npm:6.4.19" + dependencies: + "@storybook/client-logger": 6.4.19 + core-js: ^3.8.2 + fast-deep-equal: ^3.1.3 + global: ^4.4.0 + history: 5.0.0 + lodash: ^4.17.21 + memoizerific: ^1.11.3 + qs: ^6.10.0 + react-router: ^6.0.0 + react-router-dom: ^6.0.0 + ts-dedent: ^2.0.0 + peerDependencies: + react: ^16.8.0 || ^17.0.0 + react-dom: ^16.8.0 || ^17.0.0 + checksum: f998c4752f0f4b7b7243e6ef271e14e6e1bc65f67109940ee4a17393b4c1c0ee50e41ff9f83e6c83d8902cd08fee3a9bb6f225cef868bbbecd968dec00b1e026 + languageName: node + linkType: hard + "@storybook/semver@npm:^7.3.2": version: 7.3.2 resolution: "@storybook/semver@npm:7.3.2" @@ -8525,6 +8669,7 @@ __metadata: "@storybook/client-logger": 6.5.0-alpha.50 "@storybook/core-events": 6.5.0-alpha.50 "@storybook/csf": 0.0.2--canary.507502b.0 + "@storybook/preview-web": "" core-js: ^3.8.2 fast-deep-equal: ^3.1.3 global: ^4.4.0 @@ -8542,6 +8687,32 @@ __metadata: languageName: unknown linkType: soft +"@storybook/store@npm:6.4.19": + version: 6.4.19 + resolution: "@storybook/store@npm:6.4.19" + dependencies: + "@storybook/addons": 6.4.19 + "@storybook/client-logger": 6.4.19 + "@storybook/core-events": 6.4.19 + "@storybook/csf": 0.0.2--canary.87bc651.0 + core-js: ^3.8.2 + fast-deep-equal: ^3.1.3 + global: ^4.4.0 + lodash: ^4.17.21 + memoizerific: ^1.11.3 + regenerator-runtime: ^0.13.7 + slash: ^3.0.0 + stable: ^0.1.8 + synchronous-promise: ^2.0.15 + ts-dedent: ^2.0.0 + util-deprecate: ^1.0.2 + peerDependencies: + react: ^16.8.0 || ^17.0.0 + react-dom: ^16.8.0 || ^17.0.0 + checksum: f194b0943ff10bbceea5135f60d7c8da48420bd32e6ce547be76bf6173e45821deaa1ef4292d1159cf80bc7bd12d0c3dd1145af0e8525d33a408a59e7c25222b + languageName: node + linkType: hard + "@storybook/svelte@6.5.0-alpha.50, @storybook/svelte@workspace:*, @storybook/svelte@workspace:app/svelte": version: 0.0.0-use.local resolution: "@storybook/svelte@workspace:app/svelte" @@ -8652,6 +8823,29 @@ __metadata: languageName: node linkType: hard +"@storybook/theming@npm:6.4.19": + version: 6.4.19 + resolution: "@storybook/theming@npm:6.4.19" + dependencies: + "@emotion/core": ^10.1.1 + "@emotion/is-prop-valid": ^0.8.6 + "@emotion/styled": ^10.0.27 + "@storybook/client-logger": 6.4.19 + core-js: ^3.8.2 + deep-object-diff: ^1.1.0 + emotion-theming: ^10.0.27 + global: ^4.4.0 + memoizerific: ^1.11.3 + polished: ^4.0.5 + resolve-from: ^5.0.0 + ts-dedent: ^2.0.0 + peerDependencies: + react: ^16.8.0 || ^17.0.0 + react-dom: ^16.8.0 || ^17.0.0 + checksum: b28ba28536f3c958a29b07ff451310eb2d03c2d7c0edf98b6033c1de227aea15391d585b82d4cb9b6f670a47872b03b3beb870eee1aec93c7bd27e01e70cc087 + languageName: node + linkType: hard + "@storybook/ui@6.5.0-alpha.50, @storybook/ui@workspace:*, @storybook/ui@workspace:lib/ui": version: 0.0.0-use.local resolution: "@storybook/ui@workspace:lib/ui" @@ -37933,7 +38127,7 @@ __metadata: languageName: node linkType: hard -"react-router-dom@npm:^6.0.0-beta.8": +"react-router-dom@npm:^6.0.0, react-router-dom@npm:^6.0.0-beta.8": version: 6.2.2 resolution: "react-router-dom@npm:6.2.2" dependencies: @@ -37957,7 +38151,7 @@ __metadata: languageName: node linkType: hard -"react-router@npm:6.2.2, react-router@npm:^6.0.0-beta.8": +"react-router@npm:6.2.2, react-router@npm:^6.0.0, react-router@npm:^6.0.0-beta.8": version: 6.2.2 resolution: "react-router@npm:6.2.2" dependencies: From c693b4b532c820721b7b2e088492649a692d6c95 Mon Sep 17 00:00:00 2001 From: Yann Braga Date: Mon, 28 Mar 2022 12:24:23 +0200 Subject: [PATCH 19/31] remove unnecessary mock --- lib/store/src/csf/testing-utils/index.ts | 6 ------ 1 file changed, 6 deletions(-) diff --git a/lib/store/src/csf/testing-utils/index.ts b/lib/store/src/csf/testing-utils/index.ts index a4d6c2ae9352..16d335e204df 100644 --- a/lib/store/src/csf/testing-utils/index.ts +++ b/lib/store/src/csf/testing-utils/index.ts @@ -20,12 +20,6 @@ import type { CSFExports, TestingStoryPlayFn } from './types'; export * from './types'; -if (process.env.NODE_ENV === 'test') { - // eslint-disable-next-line global-require - const { default: addons, mockChannel } = require('@storybook/addons'); - addons.setChannel(mockChannel()); -} - let GLOBAL_STORYBOOK_PROJECT_ANNOTATIONS = {}; export function setGlobalConfig( From bcf7e4d03a1e58a1862379adbfd98d1be67194e4 Mon Sep 17 00:00:00 2001 From: Yann Braga Date: Mon, 28 Mar 2022 18:24:15 +0200 Subject: [PATCH 20/31] Move composeConfigs logic from preview-web to store --- app/react/src/client/testing/index.ts | 10 +- app/react/src/client/testing/types.ts | 22 -- examples/react-ts/src/AccountForm.test.tsx | 3 +- .../virtualModuleModernEntry.js.handlebars | 3 +- .../virtualModuleModernEntry.js.handlebars | 3 +- lib/core-client/src/preview/start.ts | 3 +- lib/preview-web/src/PreviewWeb.test.ts | 3 +- lib/preview-web/src/PreviewWeb.tsx | 3 +- lib/preview-web/src/index.ts | 1 - lib/preview-web/src/types.ts | 8 +- lib/store/package.json | 1 - .../src/csf}/composeConfigs.test.ts | 0 .../src => store/src/csf}/composeConfigs.ts | 28 ++- lib/store/src/csf/index.ts | 1 + lib/store/src/csf/testing-utils/index.ts | 18 +- lib/store/src/csf/testing-utils/types.ts | 22 +- lib/store/src/types.ts | 5 + yarn.lock | 198 +----------------- 18 files changed, 75 insertions(+), 257 deletions(-) delete mode 100644 app/react/src/client/testing/types.ts rename lib/{preview-web/src => store/src/csf}/composeConfigs.test.ts (100%) rename lib/{preview-web/src => store/src/csf}/composeConfigs.ts (65%) diff --git a/app/react/src/client/testing/index.ts b/app/react/src/client/testing/index.ts index 1667ba9fd3e9..28c7a71fabd9 100644 --- a/app/react/src/client/testing/index.ts +++ b/app/react/src/client/testing/index.ts @@ -3,12 +3,13 @@ import { composeStories as originalComposeStories, setGlobalConfig as originalSetGlobalConfig, CSFExports, + ComposedStory, + StoriesWithPartialProps, } from '@storybook/store'; import { ProjectAnnotations, Args } from '@storybook/csf'; import { render } from '../preview/render'; import type { Meta, ReactFramework } from '../preview/types-6-0'; -import type { StoriesWithPartialProps, TestingStory } from './types'; /** Function that sets the globalConfig of your storybook. The global config is the preview module of your .storybook folder. * @@ -64,7 +65,7 @@ const defaultProjectAnnotations: ProjectAnnotations = { * @param [exportsName] - in case your story does not contain a name and you want it to have a name. */ export function composeStory( - story: TestingStory, + story: ComposedStory, componentAnnotations: Meta, projectAnnotations?: ProjectAnnotations, exportsName?: string @@ -109,5 +110,8 @@ export function composeStories>( ) { const composedStories = originalComposeStories(csfExports, projectAnnotations, composeStory); - return composedStories as unknown as Omit, keyof CSFExports>; + return composedStories as unknown as Omit< + StoriesWithPartialProps, + keyof CSFExports + >; } diff --git a/app/react/src/client/testing/types.ts b/app/react/src/client/testing/types.ts deleted file mode 100644 index f20307e9578d..000000000000 --- a/app/react/src/client/testing/types.ts +++ /dev/null @@ -1,22 +0,0 @@ -import type { - StoryFn as OriginalStoryFn, - TestingStory as OriginalTestingStory, -} from '@storybook/store'; -import type { ReactFramework, Args } from '../preview/types-6-0'; - -export type TestingStory = OriginalTestingStory; -export type StoryFn = OriginalStoryFn; - -/** - * T represents the whole ES module of a stories file. K of T means named exports (basically the Story type) - * 1. pick the keys K of T that have properties that are Story - * 2. infer the actual prop type for each Story - * 3. reconstruct Story with Partial. Story -> Story> - */ -export type StoriesWithPartialProps = { - // @TODO once we can use Typescript 4.0 do this to exclude nonStory exports: - // replace [K in keyof TModule] with [K in keyof TModule as TModule[K] extends TestingStory ? K : never] - [K in keyof TModule]: TModule[K] extends TestingStory - ? StoryFn> - : unknown; -}; diff --git a/examples/react-ts/src/AccountForm.test.tsx b/examples/react-ts/src/AccountForm.test.tsx index 863fabe75e06..4dd5f7bef315 100644 --- a/examples/react-ts/src/AccountForm.test.tsx +++ b/examples/react-ts/src/AccountForm.test.tsx @@ -9,14 +9,13 @@ const { Standard } = composeStories(stories); test('renders form', async () => { await render(); + expect(screen.getByTestId('email')).not.toBe(null); }); test('fills input from play function', async () => { - // @ts-ignore const StandardEmailFilled = composeStory(stories.StandardEmailFilled, stories.default); const { container } = await render(); - // @ts-ignore await StandardEmailFilled.play({ canvasElement: container }); const emailInput = screen.getByTestId('email') as HTMLInputElement; diff --git a/lib/builder-webpack4/templates/virtualModuleModernEntry.js.handlebars b/lib/builder-webpack4/templates/virtualModuleModernEntry.js.handlebars index dfa3f306fa61..9aa88d6d276a 100644 --- a/lib/builder-webpack4/templates/virtualModuleModernEntry.js.handlebars +++ b/lib/builder-webpack4/templates/virtualModuleModernEntry.js.handlebars @@ -1,6 +1,7 @@ import global from 'global'; -import { composeConfigs, PreviewWeb } from '@storybook/preview-web'; +import { PreviewWeb } from '@storybook/preview-web'; +import { composeConfigs} from '@storybook/store'; import { ClientApi } from '@storybook/client-api'; import { addons } from '@storybook/addons'; import createPostMessageChannel from '@storybook/channel-postmessage'; diff --git a/lib/builder-webpack5/templates/virtualModuleModernEntry.js.handlebars b/lib/builder-webpack5/templates/virtualModuleModernEntry.js.handlebars index c7b3025c4b7a..30df5e4c4248 100644 --- a/lib/builder-webpack5/templates/virtualModuleModernEntry.js.handlebars +++ b/lib/builder-webpack5/templates/virtualModuleModernEntry.js.handlebars @@ -1,6 +1,7 @@ import global from 'global'; -import { composeConfigs, PreviewWeb } from '@storybook/preview-web'; +import { PreviewWeb } from '@storybook/preview-web'; +import { composeConfigs } from '@storybook/store'; import { ClientApi } from '@storybook/client-api'; import { addons } from '@storybook/addons'; import createPostMessageChannel from '@storybook/channel-postmessage'; diff --git a/lib/core-client/src/preview/start.ts b/lib/core-client/src/preview/start.ts index 8ce0ec1810a2..5c121dce2b6e 100644 --- a/lib/core-client/src/preview/start.ts +++ b/lib/core-client/src/preview/start.ts @@ -2,12 +2,11 @@ import global from 'global'; import deprecate from 'util-deprecate'; import { ClientApi } from '@storybook/client-api'; import { PreviewWeb } from '@storybook/preview-web'; -import type { WebProjectAnnotations } from '@storybook/preview-web'; import type { AnyFramework, ArgsStoryFn } from '@storybook/csf'; import createChannel from '@storybook/channel-postmessage'; import { addons } from '@storybook/addons'; import Events from '@storybook/core-events'; -import type { Path } from '@storybook/store'; +import type { Path, WebProjectAnnotations } from '@storybook/store'; import { Loadable } from './types'; import { executeLoadableForChanges } from './executeLoadable'; diff --git a/lib/preview-web/src/PreviewWeb.test.ts b/lib/preview-web/src/PreviewWeb.test.ts index b42e479ba2ee..f5436f57b518 100644 --- a/lib/preview-web/src/PreviewWeb.test.ts +++ b/lib/preview-web/src/PreviewWeb.test.ts @@ -5,7 +5,7 @@ import Events, { IGNORED_EXCEPTION } from '@storybook/core-events'; import { logger } from '@storybook/client-logger'; import { addons, mockChannel as createMockChannel } from '@storybook/addons'; import type { AnyFramework } from '@storybook/csf'; -import type { ModuleImportFn } from '@storybook/store'; +import type { ModuleImportFn, WebProjectAnnotations } from '@storybook/store'; import { PreviewWeb } from './PreviewWeb'; import { @@ -22,7 +22,6 @@ import { waitForQuiescence, waitForRenderPhase, } from './PreviewWeb.mockdata'; -import type { WebProjectAnnotations } from './types'; jest.mock('./WebView'); const { history, document } = global; diff --git a/lib/preview-web/src/PreviewWeb.tsx b/lib/preview-web/src/PreviewWeb.tsx index d164eaf63850..a38ee0f2740f 100644 --- a/lib/preview-web/src/PreviewWeb.tsx +++ b/lib/preview-web/src/PreviewWeb.tsx @@ -13,10 +13,9 @@ import { StoryStore, StorySpecifier, StoryIndex, + WebProjectAnnotations, } from '@storybook/store'; -import { WebProjectAnnotations } from './types'; - import { UrlStore } from './UrlStore'; import { WebView } from './WebView'; import { PREPARE_ABORTED, StoryRender } from './StoryRender'; diff --git a/lib/preview-web/src/index.ts b/lib/preview-web/src/index.ts index 78f8cd589748..e87ee32cd5ea 100644 --- a/lib/preview-web/src/index.ts +++ b/lib/preview-web/src/index.ts @@ -1,6 +1,5 @@ export { PreviewWeb } from './PreviewWeb'; -export { composeConfigs } from './composeConfigs'; export { simulatePageLoad, simulateDOMContentLoaded } from './simulate-pageload'; export * from './types'; diff --git a/lib/preview-web/src/types.ts b/lib/preview-web/src/types.ts index fc4817e30fc6..c068a57a8150 100644 --- a/lib/preview-web/src/types.ts +++ b/lib/preview-web/src/types.ts @@ -2,20 +2,14 @@ import type { StoryId, StoryName, AnyFramework, - ProjectAnnotations, StoryContextForLoaders, ComponentTitle, Args, Globals, } from '@storybook/csf'; -import type { RenderContext, Story } from '@storybook/store'; +import type { Story } from '@storybook/store'; import { PreviewWeb } from './PreviewWeb'; -export type WebProjectAnnotations = - ProjectAnnotations & { - renderToDOM?: (context: RenderContext, element: Element) => Promise | void; - }; - export interface DocsContextProps { id: StoryId; title: ComponentTitle; diff --git a/lib/store/package.json b/lib/store/package.json index a3cbb92c543f..7f4cd3a42189 100644 --- a/lib/store/package.json +++ b/lib/store/package.json @@ -44,7 +44,6 @@ "@storybook/client-logger": "6.5.0-alpha.50", "@storybook/core-events": "6.5.0-alpha.50", "@storybook/csf": "0.0.2--canary.507502b.0", - "@storybook/preview-web": "6.5.0-alpha.50", "core-js": "^3.8.2", "fast-deep-equal": "^3.1.3", "global": "^4.4.0", diff --git a/lib/preview-web/src/composeConfigs.test.ts b/lib/store/src/csf/composeConfigs.test.ts similarity index 100% rename from lib/preview-web/src/composeConfigs.test.ts rename to lib/store/src/csf/composeConfigs.test.ts diff --git a/lib/preview-web/src/composeConfigs.ts b/lib/store/src/csf/composeConfigs.ts similarity index 65% rename from lib/preview-web/src/composeConfigs.ts rename to lib/store/src/csf/composeConfigs.ts index bc426aabc2a7..a8e60cbd3876 100644 --- a/lib/preview-web/src/composeConfigs.ts +++ b/lib/store/src/csf/composeConfigs.ts @@ -1,21 +1,33 @@ import type { AnyFramework } from '@storybook/csf'; -import { combineParameters } from '@storybook/store'; -import type { ModuleExports } from '@storybook/store'; -import type { WebProjectAnnotations } from './types'; -function getField(moduleExportList: ModuleExports[], field: string): any[] { +import { combineParameters } from '../parameters'; +import type { ModuleExports, WebProjectAnnotations } from '../types'; + +export function getField( + moduleExportList: ModuleExports[], + field: string +): TFieldType | TFieldType[] { return moduleExportList.map((xs) => xs[field]).filter(Boolean); } -function getArrayField(moduleExportList: ModuleExports[], field: string): any[] { - return getField(moduleExportList, field).reduce((a, b) => [...a, ...b], []); +export function getArrayField( + moduleExportList: ModuleExports[], + field: string +): TFieldType[] { + return getField(moduleExportList, field).reduce((a: any, b: any) => [...a, ...b], []); } -function getObjectField(moduleExportList: ModuleExports[], field: string): Record { +export function getObjectField>( + moduleExportList: ModuleExports[], + field: string +): TFieldType { return Object.assign({}, ...getField(moduleExportList, field)); } -function getSingletonField(moduleExportList: ModuleExports[], field: string): any { +export function getSingletonField( + moduleExportList: ModuleExports[], + field: string +): TFieldType { return getField(moduleExportList, field).pop(); } diff --git a/lib/store/src/csf/index.ts b/lib/store/src/csf/index.ts index 215f85a9f71a..00cdb73c4794 100644 --- a/lib/store/src/csf/index.ts +++ b/lib/store/src/csf/index.ts @@ -5,4 +5,5 @@ export * from './prepareStory'; export * from './normalizeComponentAnnotations'; export * from './normalizeProjectAnnotations'; export * from './getValuesFromArgTypes'; +export * from './composeConfigs'; export * from './testing-utils'; diff --git a/lib/store/src/csf/testing-utils/index.ts b/lib/store/src/csf/testing-utils/index.ts index 16d335e204df..dcdfd7debafa 100644 --- a/lib/store/src/csf/testing-utils/index.ts +++ b/lib/store/src/csf/testing-utils/index.ts @@ -9,14 +9,15 @@ import { StoryContext, Parameters, } from '@storybook/csf'; -import { composeConfigs } from '@storybook/preview-web'; +import { composeConfigs } from '../composeConfigs'; import { prepareStory } from '../prepareStory'; import { normalizeStory } from '../normalizeStory'; import { HooksContext } from '../../hooks'; import { normalizeComponentAnnotations } from '../normalizeComponentAnnotations'; -import { getValuesFromArgTypes, normalizeProjectAnnotations } from '..'; -import type { CSFExports, TestingStoryPlayFn } from './types'; +import { getValuesFromArgTypes } from '../getValuesFromArgTypes'; +import { normalizeProjectAnnotations } from '../normalizeProjectAnnotations'; +import type { CSFExports, ComposedStoryPlayFn } from './types'; export * from './types'; @@ -24,6 +25,13 @@ let GLOBAL_STORYBOOK_PROJECT_ANNOTATIONS = {}; export function setGlobalConfig( projectAnnotations: ProjectAnnotations | ProjectAnnotations[] +) { + // deprecate this + setProjectAnnotations(projectAnnotations); +} + +export function setProjectAnnotations( + projectAnnotations: ProjectAnnotations | ProjectAnnotations[] ) { GLOBAL_STORYBOOK_PROJECT_ANNOTATIONS = Array.isArray(projectAnnotations) ? composeConfigs(projectAnnotations) @@ -40,7 +48,7 @@ interface ComposeStory): TFramework['storyResult']; storyName: string; args: Args; - play: TestingStoryPlayFn; + play: ComposedStoryPlayFn; parameters: Parameters; }; } @@ -99,7 +107,7 @@ export function composeStory< composedStory.storyName = storyAnnotations.storyName || storyAnnotations.name; composedStory.args = story.initialArgs; - composedStory.play = story.playFunction as TestingStoryPlayFn; + composedStory.play = story.playFunction as ComposedStoryPlayFn; composedStory.parameters = story.parameters; return composedStory; diff --git a/lib/store/src/csf/testing-utils/types.ts b/lib/store/src/csf/testing-utils/types.ts index 4e0f5542582a..498e091aedac 100644 --- a/lib/store/src/csf/testing-utils/types.ts +++ b/lib/store/src/csf/testing-utils/types.ts @@ -13,13 +13,27 @@ export type CSFExports = { __namedExportsOrder?: string[]; }; -export type TestingStoryPlayContext = Partial & Pick; +export type ComposedStoryPlayContext = Partial & Pick; -export type TestingStoryPlayFn = (context: TestingStoryPlayContext) => Promise | void; +export type ComposedStoryPlayFn = (context: ComposedStoryPlayContext) => Promise | void; export type StoryFn = - AnnotatedStoryFn & { play: TestingStoryPlayFn }; + AnnotatedStoryFn & { play: ComposedStoryPlayFn }; -export type TestingStory = +export type ComposedStory = | StoryFn | StoryAnnotations; + +/** + * T represents the whole ES module of a stories file. K of T means named exports (basically the Story type) + * 1. pick the keys K of T that have properties that are Story + * 2. infer the actual prop type for each Story + * 3. reconstruct Story with Partial. Story -> Story> + */ +export type StoriesWithPartialProps = { + // @TODO once we can use Typescript 4.0 do this to exclude nonStory exports: + // replace [K in keyof TModule] with [K in keyof TModule as TModule[K] extends ComposedStory ? K : never] + [K in keyof TModule]: TModule[K] extends ComposedStory + ? AnnotatedStoryFn> + : unknown; +}; diff --git a/lib/store/src/types.ts b/lib/store/src/types.ts index 98d39ec2b566..cd92d6c16061 100644 --- a/lib/store/src/types.ts +++ b/lib/store/src/types.ts @@ -29,6 +29,11 @@ export type ModuleExports = Record; type PromiseLike = Promise | SynchronousPromise; export type ModuleImportFn = (path: Path) => PromiseLike; +export type WebProjectAnnotations = + ProjectAnnotations & { + renderToDOM?: (context: RenderContext, element: Element) => Promise | void; + }; + export type NormalizedProjectAnnotations = ProjectAnnotations & { argTypes?: StrictArgTypes; diff --git a/yarn.lock b/yarn.lock index aa9a93829908..a8b13ff23145 100644 --- a/yarn.lock +++ b/yarn.lock @@ -6833,28 +6833,6 @@ __metadata: languageName: node linkType: hard -"@storybook/addons@npm:6.4.19": - version: 6.4.19 - resolution: "@storybook/addons@npm:6.4.19" - dependencies: - "@storybook/api": 6.4.19 - "@storybook/channels": 6.4.19 - "@storybook/client-logger": 6.4.19 - "@storybook/core-events": 6.4.19 - "@storybook/csf": 0.0.2--canary.87bc651.0 - "@storybook/router": 6.4.19 - "@storybook/theming": 6.4.19 - "@types/webpack-env": ^1.16.0 - core-js: ^3.8.2 - global: ^4.4.0 - regenerator-runtime: ^0.13.7 - peerDependencies: - react: ^16.8.0 || ^17.0.0 - react-dom: ^16.8.0 || ^17.0.0 - checksum: f309622784a965ff7fd2d16f344397eeebaab0006406b6372129f43390a4015c83c16e1e6d32c6ad360517523ec2221ed4f8cac3853984d60f4ab2a3705f2a45 - languageName: node - linkType: hard - "@storybook/angular@6.5.0-alpha.50, @storybook/angular@workspace:*, @storybook/angular@workspace:app/angular": version: 0.0.0-use.local resolution: "@storybook/angular@workspace:app/angular" @@ -7005,34 +6983,6 @@ __metadata: languageName: node linkType: hard -"@storybook/api@npm:6.4.19": - version: 6.4.19 - resolution: "@storybook/api@npm:6.4.19" - dependencies: - "@storybook/channels": 6.4.19 - "@storybook/client-logger": 6.4.19 - "@storybook/core-events": 6.4.19 - "@storybook/csf": 0.0.2--canary.87bc651.0 - "@storybook/router": 6.4.19 - "@storybook/semver": ^7.3.2 - "@storybook/theming": 6.4.19 - core-js: ^3.8.2 - fast-deep-equal: ^3.1.3 - global: ^4.4.0 - lodash: ^4.17.21 - memoizerific: ^1.11.3 - regenerator-runtime: ^0.13.7 - store2: ^2.12.0 - telejson: ^5.3.2 - ts-dedent: ^2.0.0 - util-deprecate: ^1.0.2 - peerDependencies: - react: ^16.8.0 || ^17.0.0 - react-dom: ^16.8.0 || ^17.0.0 - checksum: 77fae2be5269d45d3d30d8364ca35b7b95ffaebe5ae1780275f97c2ebb1b6d9e3fa2a260e82587bbe141feeebac5054e44add36fec19cefe6d058e01ce4810ee - languageName: node - linkType: hard - "@storybook/babel-plugin-require-context-hook@npm:1.0.1": version: 1.0.1 resolution: "@storybook/babel-plugin-require-context-hook@npm:1.0.1" @@ -7175,21 +7125,6 @@ __metadata: languageName: unknown linkType: soft -"@storybook/channel-postmessage@npm:6.4.19": - version: 6.4.19 - resolution: "@storybook/channel-postmessage@npm:6.4.19" - dependencies: - "@storybook/channels": 6.4.19 - "@storybook/client-logger": 6.4.19 - "@storybook/core-events": 6.4.19 - core-js: ^3.8.2 - global: ^4.4.0 - qs: ^6.10.0 - telejson: ^5.3.2 - checksum: ed1dfd996456ccd23246b415db1de228814651ed56692e76ee69f9b0308a6b1b0355d3a3cc7afc3603c2850d3e27ec420e574f55a93e16b08f275eb7e0d8de82 - languageName: node - linkType: hard - "@storybook/channel-websocket@6.5.0-alpha.50, @storybook/channel-websocket@workspace:*, @storybook/channel-websocket@workspace:lib/channel-websocket": version: 0.0.0-use.local resolution: "@storybook/channel-websocket@workspace:lib/channel-websocket" @@ -7223,17 +7158,6 @@ __metadata: languageName: node linkType: hard -"@storybook/channels@npm:6.4.19": - version: 6.4.19 - resolution: "@storybook/channels@npm:6.4.19" - dependencies: - core-js: ^3.8.2 - ts-dedent: ^2.0.0 - util-deprecate: ^1.0.2 - checksum: 14700d23d848f9c06e87650d4d11d764012baa6405b9c7eedb04a9f162660f49233a572f9c6a89c9bbc49ba378895ecf151dfe792fb8e6482a9a0de002d80e4e - languageName: node - linkType: hard - "@storybook/cli@6.5.0-alpha.50, @storybook/cli@workspace:*, @storybook/cli@workspace:lib/cli": version: 0.0.0-use.local resolution: "@storybook/cli@workspace:lib/cli" @@ -7330,16 +7254,6 @@ __metadata: languageName: node linkType: hard -"@storybook/client-logger@npm:6.4.19": - version: 6.4.19 - resolution: "@storybook/client-logger@npm:6.4.19" - dependencies: - core-js: ^3.8.2 - global: ^4.4.0 - checksum: 06e6e463ea05560280fc34faeddfd86475064b1a2eb04c1a1da230f4d761d0aebfbad5ae2978412edece7bac01f47ff09f9620a77936ffe6c9c7e20438a17acf - languageName: node - linkType: hard - "@storybook/codemod@6.5.0-alpha.50, @storybook/codemod@workspace:*, @storybook/codemod@workspace:lib/codemod": version: 0.0.0-use.local resolution: "@storybook/codemod@workspace:lib/codemod" @@ -7520,15 +7434,6 @@ __metadata: languageName: node linkType: hard -"@storybook/core-events@npm:6.4.19": - version: 6.4.19 - resolution: "@storybook/core-events@npm:6.4.19" - dependencies: - core-js: ^3.8.2 - checksum: 574d1fc3adc23ec2a12246898e5f27eaad0592db664f9691dc855dd4ceddcb248d180a389e5c98d69324de2675c818d9cbae67a58d5ed5b6ff4d0e2be0346711 - languageName: node - linkType: hard - "@storybook/core-server@6.5.0-alpha.50, @storybook/core-server@workspace:lib/core-server": version: 0.0.0-use.local resolution: "@storybook/core-server@workspace:lib/core-server" @@ -8171,33 +8076,6 @@ __metadata: languageName: unknown linkType: soft -"@storybook/preview-web@npm:": - version: 6.4.19 - resolution: "@storybook/preview-web@npm:6.4.19" - dependencies: - "@storybook/addons": 6.4.19 - "@storybook/channel-postmessage": 6.4.19 - "@storybook/client-logger": 6.4.19 - "@storybook/core-events": 6.4.19 - "@storybook/csf": 0.0.2--canary.87bc651.0 - "@storybook/store": 6.4.19 - ansi-to-html: ^0.6.11 - core-js: ^3.8.2 - global: ^4.4.0 - lodash: ^4.17.21 - qs: ^6.10.0 - regenerator-runtime: ^0.13.7 - synchronous-promise: ^2.0.15 - ts-dedent: ^2.0.0 - unfetch: ^4.2.0 - util-deprecate: ^1.0.2 - peerDependencies: - react: ^16.8.0 || ^17.0.0 - react-dom: ^16.8.0 || ^17.0.0 - checksum: 7da30e8dd1cf4ec0fa05c6cf655a1a80bf7df22fc94590126aba4d9dbe3822d5b9bb00f3fad2d7bf0ebd323ef45c9da7f4eaee16e2bcc497e9c4be946c0bb00d - languageName: node - linkType: hard - "@storybook/react-docgen-typescript-plugin@npm:1.0.2-canary.6.9d540b91e815f8fc2f8829189deb00553559ff63.0": version: 1.0.2-canary.6.9d540b91e815f8fc2f8829189deb00553559ff63.0 resolution: "@storybook/react-docgen-typescript-plugin@npm:1.0.2-canary.6.9d540b91e815f8fc2f8829189deb00553559ff63.0" @@ -8574,28 +8452,6 @@ __metadata: languageName: node linkType: hard -"@storybook/router@npm:6.4.19": - version: 6.4.19 - resolution: "@storybook/router@npm:6.4.19" - dependencies: - "@storybook/client-logger": 6.4.19 - core-js: ^3.8.2 - fast-deep-equal: ^3.1.3 - global: ^4.4.0 - history: 5.0.0 - lodash: ^4.17.21 - memoizerific: ^1.11.3 - qs: ^6.10.0 - react-router: ^6.0.0 - react-router-dom: ^6.0.0 - ts-dedent: ^2.0.0 - peerDependencies: - react: ^16.8.0 || ^17.0.0 - react-dom: ^16.8.0 || ^17.0.0 - checksum: f998c4752f0f4b7b7243e6ef271e14e6e1bc65f67109940ee4a17393b4c1c0ee50e41ff9f83e6c83d8902cd08fee3a9bb6f225cef868bbbecd968dec00b1e026 - languageName: node - linkType: hard - "@storybook/semver@npm:^7.3.2": version: 7.3.2 resolution: "@storybook/semver@npm:7.3.2" @@ -8669,7 +8525,6 @@ __metadata: "@storybook/client-logger": 6.5.0-alpha.50 "@storybook/core-events": 6.5.0-alpha.50 "@storybook/csf": 0.0.2--canary.507502b.0 - "@storybook/preview-web": "" core-js: ^3.8.2 fast-deep-equal: ^3.1.3 global: ^4.4.0 @@ -8687,32 +8542,6 @@ __metadata: languageName: unknown linkType: soft -"@storybook/store@npm:6.4.19": - version: 6.4.19 - resolution: "@storybook/store@npm:6.4.19" - dependencies: - "@storybook/addons": 6.4.19 - "@storybook/client-logger": 6.4.19 - "@storybook/core-events": 6.4.19 - "@storybook/csf": 0.0.2--canary.87bc651.0 - core-js: ^3.8.2 - fast-deep-equal: ^3.1.3 - global: ^4.4.0 - lodash: ^4.17.21 - memoizerific: ^1.11.3 - regenerator-runtime: ^0.13.7 - slash: ^3.0.0 - stable: ^0.1.8 - synchronous-promise: ^2.0.15 - ts-dedent: ^2.0.0 - util-deprecate: ^1.0.2 - peerDependencies: - react: ^16.8.0 || ^17.0.0 - react-dom: ^16.8.0 || ^17.0.0 - checksum: f194b0943ff10bbceea5135f60d7c8da48420bd32e6ce547be76bf6173e45821deaa1ef4292d1159cf80bc7bd12d0c3dd1145af0e8525d33a408a59e7c25222b - languageName: node - linkType: hard - "@storybook/svelte@6.5.0-alpha.50, @storybook/svelte@workspace:*, @storybook/svelte@workspace:app/svelte": version: 0.0.0-use.local resolution: "@storybook/svelte@workspace:app/svelte" @@ -8823,29 +8652,6 @@ __metadata: languageName: node linkType: hard -"@storybook/theming@npm:6.4.19": - version: 6.4.19 - resolution: "@storybook/theming@npm:6.4.19" - dependencies: - "@emotion/core": ^10.1.1 - "@emotion/is-prop-valid": ^0.8.6 - "@emotion/styled": ^10.0.27 - "@storybook/client-logger": 6.4.19 - core-js: ^3.8.2 - deep-object-diff: ^1.1.0 - emotion-theming: ^10.0.27 - global: ^4.4.0 - memoizerific: ^1.11.3 - polished: ^4.0.5 - resolve-from: ^5.0.0 - ts-dedent: ^2.0.0 - peerDependencies: - react: ^16.8.0 || ^17.0.0 - react-dom: ^16.8.0 || ^17.0.0 - checksum: b28ba28536f3c958a29b07ff451310eb2d03c2d7c0edf98b6033c1de227aea15391d585b82d4cb9b6f670a47872b03b3beb870eee1aec93c7bd27e01e70cc087 - languageName: node - linkType: hard - "@storybook/ui@6.5.0-alpha.50, @storybook/ui@workspace:*, @storybook/ui@workspace:lib/ui": version: 0.0.0-use.local resolution: "@storybook/ui@workspace:lib/ui" @@ -38127,7 +37933,7 @@ __metadata: languageName: node linkType: hard -"react-router-dom@npm:^6.0.0, react-router-dom@npm:^6.0.0-beta.8": +"react-router-dom@npm:^6.0.0-beta.8": version: 6.2.2 resolution: "react-router-dom@npm:6.2.2" dependencies: @@ -38151,7 +37957,7 @@ __metadata: languageName: node linkType: hard -"react-router@npm:6.2.2, react-router@npm:^6.0.0, react-router@npm:^6.0.0-beta.8": +"react-router@npm:6.2.2, react-router@npm:^6.0.0-beta.8": version: 6.2.2 resolution: "react-router@npm:6.2.2" dependencies: From 7755bd3c847baff84531e32bec7c0b2fe587f6bf Mon Sep 17 00:00:00 2001 From: Yann Braga Date: Mon, 28 Mar 2022 18:35:48 +0200 Subject: [PATCH 21/31] update test and snapshots --- examples/react-ts/src/AccountForm.test.tsx | 23 ------------------- .../cra-ts-essentials_preview-dev-posix | 2 +- .../cra-ts-essentials_preview-prod-posix | 2 +- lib/store/src/csf/testing-utils/index.test.ts | 2 +- 4 files changed, 3 insertions(+), 26 deletions(-) delete mode 100644 examples/react-ts/src/AccountForm.test.tsx diff --git a/examples/react-ts/src/AccountForm.test.tsx b/examples/react-ts/src/AccountForm.test.tsx deleted file mode 100644 index 4dd5f7bef315..000000000000 --- a/examples/react-ts/src/AccountForm.test.tsx +++ /dev/null @@ -1,23 +0,0 @@ -import React from 'react'; -import { render, screen } from '@testing-library/react'; - -import { composeStories, composeStory } from '@storybook/react'; - -import * as stories from './AccountForm.stories'; - -const { Standard } = composeStories(stories); - -test('renders form', async () => { - await render(); - expect(screen.getByTestId('email')).not.toBe(null); -}); - -test('fills input from play function', async () => { - const StandardEmailFilled = composeStory(stories.StandardEmailFilled, stories.default); - const { container } = await render(); - - await StandardEmailFilled.play({ canvasElement: container }); - - const emailInput = screen.getByTestId('email') as HTMLInputElement; - expect(emailInput.value).toBe('michael@chromatic.com'); -}); diff --git a/lib/core-server/src/__snapshots__/cra-ts-essentials_preview-dev-posix b/lib/core-server/src/__snapshots__/cra-ts-essentials_preview-dev-posix index c0f5cd0feded..13630dcfcad0 100644 --- a/lib/core-server/src/__snapshots__/cra-ts-essentials_preview-dev-posix +++ b/lib/core-server/src/__snapshots__/cra-ts-essentials_preview-dev-posix @@ -17,7 +17,7 @@ Object { "ROOT/addons/backgrounds/dist/esm/preset/addParameter.js-generated-config-entry.js", "ROOT/addons/measure/dist/esm/preset/addDecorator.js-generated-config-entry.js", "ROOT/addons/outline/dist/esm/preset/addDecorator.js-generated-config-entry.js", - "ROOT/examples/cra-ts-essentials/.storybook/preview.js-generated-config-entry.js", + "ROOT/examples/cra-ts-essentials/.storybook/preview.tsx-generated-config-entry.js", "ROOT/generated-stories-entry.js", ], "keys": Array [ diff --git a/lib/core-server/src/__snapshots__/cra-ts-essentials_preview-prod-posix b/lib/core-server/src/__snapshots__/cra-ts-essentials_preview-prod-posix index e6371f7c088e..9f07b584334b 100644 --- a/lib/core-server/src/__snapshots__/cra-ts-essentials_preview-prod-posix +++ b/lib/core-server/src/__snapshots__/cra-ts-essentials_preview-prod-posix @@ -16,7 +16,7 @@ Object { "ROOT/addons/backgrounds/dist/esm/preset/addParameter.js-generated-config-entry.js", "ROOT/addons/measure/dist/esm/preset/addDecorator.js-generated-config-entry.js", "ROOT/addons/outline/dist/esm/preset/addDecorator.js-generated-config-entry.js", - "ROOT/examples/cra-ts-essentials/.storybook/preview.js-generated-config-entry.js", + "ROOT/examples/cra-ts-essentials/.storybook/preview.tsx-generated-config-entry.js", "ROOT/generated-stories-entry.js", ], "keys": Array [ diff --git a/lib/store/src/csf/testing-utils/index.test.ts b/lib/store/src/csf/testing-utils/index.test.ts index 14e2e6deab76..514b82b1c0d5 100644 --- a/lib/store/src/csf/testing-utils/index.test.ts +++ b/lib/store/src/csf/testing-utils/index.test.ts @@ -1,4 +1,4 @@ -import { composeStory, composeStories } from '.'; +import { composeStory, composeStories } from './index'; // Most integration tests for this functionality are located under examples/cra-ts-essentials describe('composeStory', () => { From 26767e663df9395423fbc5cd89c78379b25e4108 Mon Sep 17 00:00:00 2001 From: Yann Braga Date: Mon, 28 Mar 2022 18:51:19 +0200 Subject: [PATCH 22/31] use exports name as story name as fallback --- .../__snapshots__/internals.test.tsx.snap | 18 +++++++++--------- .../components/internals.test.tsx | 15 ++------------- lib/store/src/csf/testing-utils/index.ts | 6 +++--- 3 files changed, 14 insertions(+), 25 deletions(-) diff --git a/examples/cra-ts-essentials/src/stories/testing-react/components/__snapshots__/internals.test.tsx.snap b/examples/cra-ts-essentials/src/stories/testing-react/components/__snapshots__/internals.test.tsx.snap index ff8ed9b7e781..dd0a3d4183fb 100644 --- a/examples/cra-ts-essentials/src/stories/testing-react/components/__snapshots__/internals.test.tsx.snap +++ b/examples/cra-ts-essentials/src/stories/testing-react/components/__snapshots__/internals.test.tsx.snap @@ -1,6 +1,6 @@ // Jest Snapshot v1, https://goo.gl/fbAQLP -exports[`Renders CSF2StoryWithParamsAndDecorator story 1`] = ` +exports[`Renders CSF2Secondary story 1`] = `
@@ -12,13 +12,13 @@ exports[`Renders CSF2StoryWithParamsAndDecorator story 1`] = ` label="Button" type="button" > - foo + Children coming from story args!
`; -exports[`Renders WithLocale story 1`] = ` +exports[`Renders CSF2StoryWithLocale story 1`] = `
@@ -35,7 +35,7 @@ exports[`Renders WithLocale story 1`] = ` `; -exports[`Renders bound Template story 1`] = ` +exports[`Renders CSF2StoryWithParamsAndDecorator story 1`] = `
@@ -47,13 +47,13 @@ exports[`Renders bound Template story 1`] = ` label="Button" type="button" > - Children coming from story args! + foo
`; -exports[`Renders undefined story 1`] = ` +exports[`Renders CSF3Button story 1`] = `
@@ -71,7 +71,7 @@ exports[`Renders undefined story 1`] = ` `; -exports[`Renders undefined story 2`] = ` +exports[`Renders CSF3ButtonWithRender story 1`] = `
@@ -96,7 +96,7 @@ exports[`Renders undefined story 2`] = ` `; -exports[`Renders undefined story 3`] = ` +exports[`Renders CSF3InputFieldFilled story 1`] = `
@@ -108,7 +108,7 @@ exports[`Renders undefined story 3`] = ` `; -exports[`Renders undefined story 4`] = ` +exports[`Renders CSF3Primary story 1`] = `
diff --git a/examples/cra-ts-essentials/src/stories/testing-react/components/internals.test.tsx b/examples/cra-ts-essentials/src/stories/testing-react/components/internals.test.tsx index d03cc4e1bdf6..5fbdae1697d0 100644 --- a/examples/cra-ts-essentials/src/stories/testing-react/components/internals.test.tsx +++ b/examples/cra-ts-essentials/src/stories/testing-react/components/internals.test.tsx @@ -43,24 +43,13 @@ test('should pass with decorators that need addons channel', () => { }); describe('Unsupported formats', () => { - test('should throw error StoryFn.story notation', () => { + test('should throw error if story is undefined', () => { const UnsupportedStory = () =>
hello world
; UnsupportedStory.story = { parameters: {} }; const UnsupportedStoryModule: any = { default: {}, - UnsupportedStory, - }; - - expect(() => { - composeStories(UnsupportedStoryModule); - }).toThrow(); - }); - - test('should throw error with non component stories', () => { - const UnsupportedStoryModule: any = { - default: {}, - UnsupportedStory: 123, + UnsupportedStory: undefined, }; expect(() => { diff --git a/lib/store/src/csf/testing-utils/index.ts b/lib/store/src/csf/testing-utils/index.ts index dcdfd7debafa..97bad14707fb 100644 --- a/lib/store/src/csf/testing-utils/index.ts +++ b/lib/store/src/csf/testing-utils/index.ts @@ -69,14 +69,14 @@ export function composeStory< const normalizedComponentAnnotations = normalizeComponentAnnotations(componentAnnotations); - const storyExportsName = + const storyName = exportsName || storyAnnotations.storyName || storyAnnotations.story?.name || storyAnnotations.name; const normalizedStory = normalizeStory( - storyExportsName, + storyName, storyAnnotations, normalizedComponentAnnotations ); @@ -105,7 +105,7 @@ export function composeStory< return story.unboundStoryFn(context as StoryContext); }; - composedStory.storyName = storyAnnotations.storyName || storyAnnotations.name; + composedStory.storyName = storyName; composedStory.args = story.initialArgs; composedStory.play = story.playFunction as ComposedStoryPlayFn; composedStory.parameters = story.parameters; From ab0e576bbd28a83e01a5e278e6147e3a67a6e313 Mon Sep 17 00:00:00 2001 From: Yann Braga Date: Mon, 28 Mar 2022 18:51:39 +0200 Subject: [PATCH 23/31] add temporary fix for auto title --- lib/store/src/csf/testing-utils/index.ts | 3 +++ 1 file changed, 3 insertions(+) diff --git a/lib/store/src/csf/testing-utils/index.ts b/lib/store/src/csf/testing-utils/index.ts index 97bad14707fb..da2f7e3b8af7 100644 --- a/lib/store/src/csf/testing-utils/index.ts +++ b/lib/store/src/csf/testing-utils/index.ts @@ -67,6 +67,9 @@ export function composeStory< throw new Error('Expected a story but received undefined.'); } + // @TODO: Support auto title + // eslint-disable-next-line no-param-reassign + componentAnnotations.title = componentAnnotations.title ?? 'ComposedStory'; const normalizedComponentAnnotations = normalizeComponentAnnotations(componentAnnotations); const storyName = From af4e1ab8cf85046173023de1776fe751df39036f Mon Sep 17 00:00:00 2001 From: Yann Braga Date: Mon, 28 Mar 2022 22:26:04 +0200 Subject: [PATCH 24/31] update yarn lock --- yarn.lock | 55 ++++++++----------------------------------------------- 1 file changed, 8 insertions(+), 47 deletions(-) diff --git a/yarn.lock b/yarn.lock index 90f91bb22929..e86d609b2187 100644 --- a/yarn.lock +++ b/yarn.lock @@ -1,3 +1,6 @@ +# This file is generated by running "yarn install" inside your project. +# Manual changes might be lost - proceed with caution! + __metadata: version: 5 cacheKey: 8c0 @@ -7230,16 +7233,7 @@ __metadata: languageName: unknown linkType: soft -"@storybook/client-logger@6.5.0-alpha.50, @storybook/client-logger@^6.4.0 || >=6.5.0-0, @storybook/client-logger@workspace:*, @storybook/client-logger@workspace:lib/client-logger": - version: 0.0.0-use.local - resolution: "@storybook/client-logger@workspace:lib/client-logger" - dependencies: - core-js: ^3.8.2 - global: ^4.4.0 - languageName: unknown - linkType: soft - -"@storybook/client-logger@6.5.0-alpha.51, @storybook/client-logger@workspace:*, @storybook/client-logger@workspace:lib/client-logger": +"@storybook/client-logger@6.5.0-alpha.51, @storybook/client-logger@^6.4.0 || >=6.5.0-0, @storybook/client-logger@workspace:*, @storybook/client-logger@workspace:lib/client-logger": version: 0.0.0-use.local resolution: "@storybook/client-logger@workspace:lib/client-logger" dependencies: @@ -7757,19 +7751,7 @@ __metadata: languageName: unknown linkType: soft -"@storybook/instrumenter@6.5.0-alpha.50, @storybook/instrumenter@^6.4.0 || >=6.5.0-0, @storybook/instrumenter@workspace:*, @storybook/instrumenter@workspace:lib/instrumenter": - version: 0.0.0-use.local - resolution: "@storybook/instrumenter@workspace:lib/instrumenter" - dependencies: - "@storybook/addons": 6.5.0-alpha.51 - "@storybook/client-logger": 6.5.0-alpha.51 - "@storybook/core-events": 6.5.0-alpha.51 - core-js: ^3.8.2 - global: ^4.4.0 - languageName: unknown - linkType: soft - -"@storybook/instrumenter@6.5.0-alpha.51, @storybook/instrumenter@workspace:*, @storybook/instrumenter@workspace:lib/instrumenter": +"@storybook/instrumenter@6.5.0-alpha.51, @storybook/instrumenter@^6.4.0 || >=6.5.0-0, @storybook/instrumenter@workspace:*, @storybook/instrumenter@workspace:lib/instrumenter": version: 0.0.0-use.local resolution: "@storybook/instrumenter@workspace:lib/instrumenter" dependencies: @@ -8622,30 +8604,6 @@ __metadata: languageName: node linkType: hard -"@storybook/theming@6.5.0-alpha.50, @storybook/theming@workspace:*, @storybook/theming@workspace:lib/theming": - version: 0.0.0-use.local - resolution: "@storybook/theming@workspace:lib/theming" - dependencies: - "@emotion/core": ^10.3.1 - "@emotion/is-prop-valid": ^0.8.6 - "@emotion/styled": ^10.0.27 - "@storybook/client-logger": 6.5.0-alpha.51 - "@types/node": ^14.14.20 || ^16.0.0 - core-js: ^3.8.2 - deep-object-diff: ^1.1.0 - emotion-theming: ^10.0.27 - global: ^4.4.0 - memoizerific: ^1.11.3 - polished: ^4.0.5 - regenerator-runtime: ^0.13.7 - ts-dedent: ^2.0.0 - ts-node: ^10.4.0 - peerDependencies: - react: ^16.8.0 || ^17.0.0 - react-dom: ^16.8.0 || ^17.0.0 - languageName: unknown - linkType: soft - "@storybook/theming@6.5.0-alpha.51, @storybook/theming@workspace:*, @storybook/theming@workspace:lib/theming": version: 0.0.0-use.local resolution: "@storybook/theming@workspace:lib/theming" @@ -17846,8 +17804,11 @@ __metadata: "@storybook/addon-ie11": 0.0.7--canary.5e87b64.0 "@storybook/addons": 6.5.0-alpha.51 "@storybook/builder-webpack4": 6.5.0-alpha.51 + "@storybook/components": 6.5.0-alpha.51 "@storybook/preset-create-react-app": ^3.1.6 "@storybook/react": 6.5.0-alpha.51 + "@storybook/testing-library": ^0.0.9 + "@storybook/theming": 6.5.0-alpha.51 "@types/jest": ^26.0.16 "@types/node": ^14.14.20 || ^16.0.0 "@types/react": ^16.14.23 From 72bcdf1f3a094cfd32734945f053973dad2dfc35 Mon Sep 17 00:00:00 2001 From: Michael Shilman Date: Tue, 29 Mar 2022 08:51:34 +0800 Subject: [PATCH 25/31] Replace setGlobalConfig with setProjectAnnotations --- app/react/src/client/testing/index.ts | 24 ++++++++++++++----- examples/cra-ts-essentials/src/setupTests.ts | 6 ++--- .../components/AccountForm.stories.tsx | 1 - lib/store/src/csf/testing-utils/index.ts | 7 ------ 4 files changed, 21 insertions(+), 17 deletions(-) diff --git a/app/react/src/client/testing/index.ts b/app/react/src/client/testing/index.ts index 28c7a71fabd9..8099a10b9ff8 100644 --- a/app/react/src/client/testing/index.ts +++ b/app/react/src/client/testing/index.ts @@ -1,12 +1,13 @@ import { composeStory as originalComposeStory, composeStories as originalComposeStories, - setGlobalConfig as originalSetGlobalConfig, + setProjectAnnotations as originalSetProjectAnnotations, CSFExports, ComposedStory, StoriesWithPartialProps, } from '@storybook/store'; import { ProjectAnnotations, Args } from '@storybook/csf'; +import { once } from '@storybook/client-logger'; import { render } from '../preview/render'; import type { Meta, ReactFramework } from '../preview/types-6-0'; @@ -18,18 +19,29 @@ import type { Meta, ReactFramework } from '../preview/types-6-0'; * Example: *```jsx * // setup.js (for jest) - * import { setGlobalConfig } from '@storybook/react'; + * import { setProjectAnnotations } from '@storybook/react'; * import * as projectAnnotations from './.storybook/preview'; * - * setGlobalConfig(projectAnnotations); + * setProjectAnnotations(projectAnnotations); *``` * * @param projectAnnotations - e.g. (import * as projectAnnotations from '../.storybook/preview') */ +export function setProjectAnnotations( + projectAnnotations: ProjectAnnotations | ProjectAnnotations[] +) { + originalSetProjectAnnotations(projectAnnotations); +} + +/** Preserved for users migrating from `@storybook/testing-react`. + * + * @deprecated Use setProjectAnnotations instead + */ export function setGlobalConfig( projectAnnotations: ProjectAnnotations | ProjectAnnotations[] ) { - originalSetGlobalConfig(projectAnnotations); + once.warn(`setGlobalConfig is deprecated. Use setProjectAnnotations instead.`); + setProjectAnnotations(projectAnnotations); } // This will not be necessary once we have auto preset loading @@ -61,7 +73,7 @@ const defaultProjectAnnotations: ProjectAnnotations = { * * @param story * @param componentAnnotations - e.g. (import Meta from './Button.stories') - * @param [projectAnnotations] - e.g. (import * as projectAnnotations from '../.storybook/preview') this can be applied automatically if you use `setGlobalConfig` in your setup files. + * @param [projectAnnotations] - e.g. (import * as projectAnnotations from '../.storybook/preview') this can be applied automatically if you use `setProjectAnnotations` in your setup files. * @param [exportsName] - in case your story does not contain a name and you want it to have a name. */ export function composeStory( @@ -102,7 +114,7 @@ export function composeStory( *``` * * @param csfExports - e.g. (import * as stories from './Button.stories') - * @param [projectAnnotations] - e.g. (import * as projectAnnotations from '../.storybook/preview') this can be applied automatically if you use `setGlobalConfig` in your setup files. + * @param [projectAnnotations] - e.g. (import * as projectAnnotations from '../.storybook/preview') this can be applied automatically if you use `setProjectAnnotations` in your setup files. */ export function composeStories>( csfExports: TModule, diff --git a/examples/cra-ts-essentials/src/setupTests.ts b/examples/cra-ts-essentials/src/setupTests.ts index c3f66482a560..e65a5923ef0f 100644 --- a/examples/cra-ts-essentials/src/setupTests.ts +++ b/examples/cra-ts-essentials/src/setupTests.ts @@ -1,4 +1,4 @@ -import { setGlobalConfig } from '@storybook/react'; -import * as globalStorybookConfig from '../.storybook/preview'; +import { setProjectAnnotations } from '@storybook/react'; +import * as projectAnnotations from '../.storybook/preview'; -setGlobalConfig(globalStorybookConfig); +setProjectAnnotations(projectAnnotations); diff --git a/examples/cra-ts-essentials/src/stories/testing-react/components/AccountForm.stories.tsx b/examples/cra-ts-essentials/src/stories/testing-react/components/AccountForm.stories.tsx index f3cab5e6d2f5..5b6731f1a922 100644 --- a/examples/cra-ts-essentials/src/stories/testing-react/components/AccountForm.stories.tsx +++ b/examples/cra-ts-essentials/src/stories/testing-react/components/AccountForm.stories.tsx @@ -1,4 +1,3 @@ -/* eslint-disable storybook/await-interactions */ import React from 'react'; import type { ComponentMeta, ComponentStoryObj } from '@storybook/react'; import { userEvent, within } from '@storybook/testing-library'; diff --git a/lib/store/src/csf/testing-utils/index.ts b/lib/store/src/csf/testing-utils/index.ts index da2f7e3b8af7..04a7f93594bb 100644 --- a/lib/store/src/csf/testing-utils/index.ts +++ b/lib/store/src/csf/testing-utils/index.ts @@ -23,13 +23,6 @@ export * from './types'; let GLOBAL_STORYBOOK_PROJECT_ANNOTATIONS = {}; -export function setGlobalConfig( - projectAnnotations: ProjectAnnotations | ProjectAnnotations[] -) { - // deprecate this - setProjectAnnotations(projectAnnotations); -} - export function setProjectAnnotations( projectAnnotations: ProjectAnnotations | ProjectAnnotations[] ) { From 6a8ec8a836424fcd2e35125c60cb2a65ab127ea4 Mon Sep 17 00:00:00 2001 From: Yann Braga Date: Tue, 29 Mar 2022 08:38:57 +0200 Subject: [PATCH 26/31] fix build errors in cra-ts-essentials --- examples/cra-ts-essentials/.storybook/main.ts | 10 +++++++--- examples/cra-ts-essentials/package.json | 4 ++-- .../testing-react/components/AccountForm.stories.tsx | 4 ++-- 3 files changed, 11 insertions(+), 7 deletions(-) diff --git a/examples/cra-ts-essentials/.storybook/main.ts b/examples/cra-ts-essentials/.storybook/main.ts index 97a4e0a2b0ff..4ec79b0ac3f9 100644 --- a/examples/cra-ts-essentials/.storybook/main.ts +++ b/examples/cra-ts-essentials/.storybook/main.ts @@ -1,6 +1,8 @@ +import type { StorybookConfig } from '@storybook/react/types'; + const path = require('path'); -module.exports = { +const mainConfig: StorybookConfig = { stories: ['../src/**/*.stories.@(tsx|mdx)'], addons: [ '@storybook/preset-create-react-app', @@ -13,9 +15,9 @@ module.exports = { }, ], logLevel: 'debug', - webpackFinal: (config) => { + webpackFinal: async (config) => { // add monorepo root as a valid directory to import modules from - config.resolve.plugins.forEach((p) => { + config.resolve?.plugins?.forEach((p: any) => { if (Array.isArray(p.appSrcs)) { p.appSrcs.push(path.join(__dirname, '..', '..', '..')); } @@ -30,3 +32,5 @@ module.exports = { buildStoriesJson: true, }, }; + +module.exports = mainConfig; diff --git a/examples/cra-ts-essentials/package.json b/examples/cra-ts-essentials/package.json index 639d18ac5c98..179fee8e51e1 100644 --- a/examples/cra-ts-essentials/package.json +++ b/examples/cra-ts-essentials/package.json @@ -23,6 +23,8 @@ ] }, "dependencies": { + "@storybook/components": "6.5.0-alpha.51", + "@storybook/theming": "6.5.0-alpha.51", "@types/jest": "^26.0.16", "@types/node": "^14.14.20 || ^16.0.0", "@types/react": "^16.14.23", @@ -39,11 +41,9 @@ "@storybook/addon-ie11": "0.0.7--canary.5e87b64.0", "@storybook/addons": "6.5.0-alpha.51", "@storybook/builder-webpack4": "6.5.0-alpha.51", - "@storybook/components": "6.5.0-alpha.51", "@storybook/preset-create-react-app": "^3.1.6", "@storybook/react": "6.5.0-alpha.51", "@storybook/testing-library": "^0.0.9", - "@storybook/theming": "6.5.0-alpha.51", "webpack": "4" }, "storybook": { diff --git a/examples/cra-ts-essentials/src/stories/testing-react/components/AccountForm.stories.tsx b/examples/cra-ts-essentials/src/stories/testing-react/components/AccountForm.stories.tsx index 5b6731f1a922..c2953c144d5a 100644 --- a/examples/cra-ts-essentials/src/stories/testing-react/components/AccountForm.stories.tsx +++ b/examples/cra-ts-essentials/src/stories/testing-react/components/AccountForm.stories.tsx @@ -14,6 +14,8 @@ export default { type Story = ComponentStoryObj; +const sleep = (ms: number) => new Promise((r) => setTimeout(r, ms)); + export const Standard: Story = { args: { passwordVerification: false }, }; @@ -81,8 +83,6 @@ export const VerificationPasswordMismatch: Story = { }, }; -const sleep = (ms: number) => new Promise((r) => setTimeout(r, ms)); - export const VerificationSuccess: Story = { ...Verification, play: async (context) => { From f20128e6505b503cb4d291c256e378c0796db913 Mon Sep 17 00:00:00 2001 From: Yann Braga Date: Tue, 29 Mar 2022 08:40:16 +0200 Subject: [PATCH 27/31] Update lib/builder-webpack4/templates/virtualModuleModernEntry.js.handlebars Co-authored-by: Tom Coleman --- .../templates/virtualModuleModernEntry.js.handlebars | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/builder-webpack4/templates/virtualModuleModernEntry.js.handlebars b/lib/builder-webpack4/templates/virtualModuleModernEntry.js.handlebars index 9aa88d6d276a..73e29368f16d 100644 --- a/lib/builder-webpack4/templates/virtualModuleModernEntry.js.handlebars +++ b/lib/builder-webpack4/templates/virtualModuleModernEntry.js.handlebars @@ -1,7 +1,7 @@ import global from 'global'; import { PreviewWeb } from '@storybook/preview-web'; -import { composeConfigs} from '@storybook/store'; +import { composeConfigs } from '@storybook/store'; import { ClientApi } from '@storybook/client-api'; import { addons } from '@storybook/addons'; import createPostMessageChannel from '@storybook/channel-postmessage'; From 8a290f8ce20872f3676331423e9b0b18d23fd550 Mon Sep 17 00:00:00 2001 From: Yann Braga Date: Tue, 29 Mar 2022 09:40:23 +0200 Subject: [PATCH 28/31] fix typescript issues --- .../cra-ts-essentials/src/stories/0-Welcome.stories.tsx | 7 +++++-- .../cra-ts-essentials/src/stories/1-Button.stories.tsx | 5 +++-- .../testing-react/components/AccountForm.stories.tsx | 1 + .../stories/testing-react/components/Button.stories.tsx | 4 ++-- 4 files changed, 11 insertions(+), 6 deletions(-) diff --git a/examples/cra-ts-essentials/src/stories/0-Welcome.stories.tsx b/examples/cra-ts-essentials/src/stories/0-Welcome.stories.tsx index 9920a416e1ff..ec39dbbe4813 100644 --- a/examples/cra-ts-essentials/src/stories/0-Welcome.stories.tsx +++ b/examples/cra-ts-essentials/src/stories/0-Welcome.stories.tsx @@ -1,12 +1,15 @@ import React from 'react'; import { linkTo } from '@storybook/addon-links'; import { Welcome } from '@storybook/react/demo'; +import type { ComponentMeta, ComponentStory } from '@storybook/react'; export default { title: 'Welcome', component: Welcome, -}; +} as ComponentMeta; -export const ToStorybook = () => ; +export const ToStorybook: ComponentStory = () => ( + +); ToStorybook.storyName = 'to Storybook'; diff --git a/examples/cra-ts-essentials/src/stories/1-Button.stories.tsx b/examples/cra-ts-essentials/src/stories/1-Button.stories.tsx index e952509a9012..c3d83e4544a7 100644 --- a/examples/cra-ts-essentials/src/stories/1-Button.stories.tsx +++ b/examples/cra-ts-essentials/src/stories/1-Button.stories.tsx @@ -1,13 +1,14 @@ import React from 'react'; import { Button } from '@storybook/react/demo'; +import type { ComponentStory, ComponentMeta } from '@storybook/react'; export default { title: 'Button', component: Button, argTypes: { onClick: { action: 'clicked' } }, -}; +} as ComponentMeta; -const Template = (args: any) =>