diff --git a/code/addons/actions/src/addArgs.ts b/code/addons/actions/src/addArgs.ts index db14aee0ce3d..5742bd8627c0 100644 --- a/code/addons/actions/src/addArgs.ts +++ b/code/addons/actions/src/addArgs.ts @@ -1,12 +1,7 @@ import type { ArgsEnhancer } from '@storybook/types'; -import { - addActionsFromArgTypes, - attachActionsToFunctionMocks, - inferActionsFromArgTypesRegex, -} from './addArgsHelpers'; +import { addActionsFromArgTypes, inferActionsFromArgTypesRegex } from './addArgsHelpers'; export const argsEnhancers: ArgsEnhancer[] = [ addActionsFromArgTypes, inferActionsFromArgTypesRegex, - attachActionsToFunctionMocks, ]; diff --git a/code/addons/actions/src/addArgsHelpers.ts b/code/addons/actions/src/addArgsHelpers.ts index cd4f67897a53..ab8fd3b36a0b 100644 --- a/code/addons/actions/src/addArgsHelpers.ts +++ b/code/addons/actions/src/addArgsHelpers.ts @@ -1,4 +1,3 @@ -/* eslint-disable no-underscore-dangle,no-param-reassign */ import type { Args, Renderer, ArgsEnhancer } from '@storybook/types'; import { action } from './runtime/action'; @@ -63,33 +62,3 @@ export const addActionsFromArgTypes: ArgsEnhancer = (context) => { return acc; }, {} as Args); }; - -export const attachActionsToFunctionMocks: ArgsEnhancer = (context) => { - const { - initialArgs, - argTypes, - parameters: { actions }, - } = context; - if (actions?.disable || !argTypes) { - return {}; - } - - const argTypesWithAction = Object.entries(initialArgs).filter( - ([, value]) => - typeof value === 'function' && - '_isMockFunction' in value && - value._isMockFunction && - !value._actionAttached - ); - - return argTypesWithAction.reduce((acc, [key, value]) => { - const previous = value.getMockImplementation(); - value.mockImplementation((...args: unknown[]) => { - action(key)(...args); - return previous?.(...args); - }); - // this enhancer is being called multiple times - value._actionAttached = true; - return acc; - }, {} as Args); -}; diff --git a/code/addons/actions/src/loaders.ts b/code/addons/actions/src/loaders.ts new file mode 100644 index 000000000000..99e51287a893 --- /dev/null +++ b/code/addons/actions/src/loaders.ts @@ -0,0 +1,30 @@ +/* eslint-disable no-underscore-dangle */ +import type { LoaderFunction } from '@storybook/types'; +import { action } from './runtime'; + +const attachActionsToFunctionMocks: LoaderFunction = (context) => { + const { + args, + parameters: { actions }, + } = context; + if (actions?.disable) return; + + Object.entries(args) + .filter( + ([, value]) => + typeof value === 'function' && '_isMockFunction' in value && value._isMockFunction + ) + .forEach(([key, value]) => { + const previous = value.getMockImplementation(); + if (previous?._actionAttached !== true) { + const implementation = (...params: unknown[]) => { + action(key)(...params); + return previous?.(...params); + }; + implementation._actionAttached = true; + value.mockImplementation(implementation); + } + }); +}; + +export const loaders: LoaderFunction[] = [attachActionsToFunctionMocks]; diff --git a/code/addons/actions/src/preview.ts b/code/addons/actions/src/preview.ts index 7a06751b46dd..bfafaa7faf4e 100644 --- a/code/addons/actions/src/preview.ts +++ b/code/addons/actions/src/preview.ts @@ -1 +1,2 @@ export * from './addArgs'; +export * from './loaders'; diff --git a/code/addons/links/package.json b/code/addons/links/package.json index 91039a15cdf4..a2b252eee6c2 100644 --- a/code/addons/links/package.json +++ b/code/addons/links/package.json @@ -63,7 +63,7 @@ "prep": "node --loader ../../../scripts/node_modules/esbuild-register/loader.js -r ../../../scripts/node_modules/esbuild-register/register.js ../../../scripts/prepare/addon-bundle.ts" }, "dependencies": { - "@storybook/csf": "^0.1.0", + "@storybook/csf": "^0.1.2", "@storybook/global": "^5.0.0", "ts-dedent": "^2.0.0" }, diff --git a/code/addons/storyshots-puppeteer/package.json b/code/addons/storyshots-puppeteer/package.json index 6ffb44476d17..4685d9a82237 100644 --- a/code/addons/storyshots-puppeteer/package.json +++ b/code/addons/storyshots-puppeteer/package.json @@ -37,7 +37,7 @@ }, "dependencies": { "@axe-core/puppeteer": "^4.2.0", - "@storybook/csf": "^0.1.0", + "@storybook/csf": "^0.1.2", "@storybook/node-logger": "workspace:*", "@storybook/types": "workspace:*", "@types/jest-image-snapshot": "^6.0.0", diff --git a/code/lib/codemod/package.json b/code/lib/codemod/package.json index ac98a974c4b3..f1914cd9d17e 100644 --- a/code/lib/codemod/package.json +++ b/code/lib/codemod/package.json @@ -55,7 +55,7 @@ "@babel/core": "^7.23.2", "@babel/preset-env": "^7.23.2", "@babel/types": "^7.23.0", - "@storybook/csf": "^0.1.0", + "@storybook/csf": "^0.1.2", "@storybook/csf-tools": "workspace:*", "@storybook/node-logger": "workspace:*", "@storybook/types": "workspace:*", diff --git a/code/lib/core-server/package.json b/code/lib/core-server/package.json index 830a25f85e89..a56640e1de4f 100644 --- a/code/lib/core-server/package.json +++ b/code/lib/core-server/package.json @@ -66,7 +66,7 @@ "@storybook/channels": "workspace:*", "@storybook/core-common": "workspace:*", "@storybook/core-events": "workspace:*", - "@storybook/csf": "^0.1.0", + "@storybook/csf": "^0.1.2", "@storybook/csf-tools": "workspace:*", "@storybook/docs-mdx": "^0.1.0", "@storybook/global": "^5.0.0", diff --git a/code/lib/csf-tools/package.json b/code/lib/csf-tools/package.json index 252055c7957e..9620c543e085 100644 --- a/code/lib/csf-tools/package.json +++ b/code/lib/csf-tools/package.json @@ -46,7 +46,7 @@ "@babel/parser": "^7.23.0", "@babel/traverse": "^7.23.2", "@babel/types": "^7.23.0", - "@storybook/csf": "^0.1.0", + "@storybook/csf": "^0.1.2", "@storybook/types": "workspace:*", "fs-extra": "^11.1.0", "recast": "^0.23.1", diff --git a/code/lib/manager-api/package.json b/code/lib/manager-api/package.json index f8f760e004cb..02a7cac6d7ad 100644 --- a/code/lib/manager-api/package.json +++ b/code/lib/manager-api/package.json @@ -46,7 +46,7 @@ "@storybook/channels": "workspace:*", "@storybook/client-logger": "workspace:*", "@storybook/core-events": "workspace:*", - "@storybook/csf": "^0.1.0", + "@storybook/csf": "^0.1.2", "@storybook/global": "^5.0.0", "@storybook/router": "workspace:*", "@storybook/theming": "workspace:*", diff --git a/code/lib/preview-api/package.json b/code/lib/preview-api/package.json index 5884953f4496..a43fecb7a446 100644 --- a/code/lib/preview-api/package.json +++ b/code/lib/preview-api/package.json @@ -71,7 +71,7 @@ "@storybook/channels": "workspace:*", "@storybook/client-logger": "workspace:*", "@storybook/core-events": "workspace:*", - "@storybook/csf": "^0.1.0", + "@storybook/csf": "^0.1.2", "@storybook/global": "^5.0.0", "@storybook/types": "workspace:*", "@types/qs": "^6.9.5", diff --git a/code/lib/preview-api/src/modules/client-api/ClientApi.ts b/code/lib/preview-api/src/modules/client-api/ClientApi.ts index 83a2ba0cd6b0..d68bf30b47cd 100644 --- a/code/lib/preview-api/src/modules/client-api/ClientApi.ts +++ b/code/lib/preview-api/src/modules/client-api/ClientApi.ts @@ -168,9 +168,11 @@ export class ClientApi { } }; - addStepRunner = (stepRunner: StepRunner) => { + addStepRunner = (stepRunner: StepRunner) => { this.facade.projectAnnotations.runStep = composeStepRunners( - [this.facade.projectAnnotations.runStep, stepRunner].filter(Boolean) as StepRunner[] + [this.facade.projectAnnotations.runStep, stepRunner].filter( + Boolean + ) as StepRunner[] ); }; @@ -297,7 +299,7 @@ export class ClientApi { this._addedExports[fileName] = { default: meta }; let counter = 0; - api.add = (storyName: string, storyFn: StoryFn, parameters: Parameters = {}) => { + api.add = (storyName: string, storyFn: StoryFn, parameters: Parameters = {}) => { hasAdded = true; if (typeof storyName !== 'string') { diff --git a/code/lib/preview-api/src/modules/preview-web/docs-context/DocsContext.ts b/code/lib/preview-api/src/modules/preview-web/docs-context/DocsContext.ts index 6796913596bd..40ea0ccbaa03 100644 --- a/code/lib/preview-api/src/modules/preview-web/docs-context/DocsContext.ts +++ b/code/lib/preview-api/src/modules/preview-web/docs-context/DocsContext.ts @@ -34,7 +34,7 @@ export class DocsContext implements DocsContextProps constructor( public channel: Channel, protected store: StoryStore, - public renderStoryToElement: DocsContextProps['renderStoryToElement'], + public renderStoryToElement: DocsContextProps['renderStoryToElement'], /** The CSF files known (via the index) to be refererenced by this docs file */ csfFiles: CSFFile[] ) { diff --git a/code/lib/preview-api/src/modules/preview-web/render/CsfDocsRender.ts b/code/lib/preview-api/src/modules/preview-web/render/CsfDocsRender.ts index a4fbb47b9e01..019d43d97893 100644 --- a/code/lib/preview-api/src/modules/preview-web/render/CsfDocsRender.ts +++ b/code/lib/preview-api/src/modules/preview-web/render/CsfDocsRender.ts @@ -95,7 +95,7 @@ export class CsfDocsRender implements Render['renderStoryToElement']) { if (!this.csfFiles) throw new Error('Cannot render docs before preparing'); const docsContext = new DocsContext( this.channel, @@ -112,7 +112,7 @@ export class CsfDocsRender implements Render['renderStoryToElement'] ) { if (!this.story || !this.csfFiles) throw new Error('Cannot render docs before preparing'); diff --git a/code/lib/preview-api/src/modules/preview-web/render/MdxDocsRender.ts b/code/lib/preview-api/src/modules/preview-web/render/MdxDocsRender.ts index ca81e01d438a..987dc7680192 100644 --- a/code/lib/preview-api/src/modules/preview-web/render/MdxDocsRender.ts +++ b/code/lib/preview-api/src/modules/preview-web/render/MdxDocsRender.ts @@ -79,7 +79,7 @@ export class MdxDocsRender implements Render['renderStoryToElement']) { if (!this.csfFiles) throw new Error('Cannot render docs before preparing'); // NOTE we do *not* attach any CSF file yet. We wait for `referenceMeta(..., true)` @@ -94,7 +94,7 @@ export class MdxDocsRender implements Render['renderStoryToElement'] ) { if (!this.exports || !this.csfFiles || !this.store.projectAnnotations) throw new Error('Cannot render docs before preparing'); diff --git a/code/lib/preview-api/src/modules/store/csf/normalizeArrays.ts b/code/lib/preview-api/src/modules/store/csf/normalizeArrays.ts new file mode 100644 index 000000000000..96344e6fac1d --- /dev/null +++ b/code/lib/preview-api/src/modules/store/csf/normalizeArrays.ts @@ -0,0 +1,4 @@ +export const normalizeArrays = (array: T[] | T | undefined): T[] => { + if (Array.isArray(array)) return array; + return array ? [array] : []; +}; diff --git a/code/lib/preview-api/src/modules/store/csf/normalizeProjectAnnotations.ts b/code/lib/preview-api/src/modules/store/csf/normalizeProjectAnnotations.ts index a93bf3584395..e2e6a88db31e 100644 --- a/code/lib/preview-api/src/modules/store/csf/normalizeProjectAnnotations.ts +++ b/code/lib/preview-api/src/modules/store/csf/normalizeProjectAnnotations.ts @@ -8,16 +8,21 @@ import type { import { inferArgTypes } from '../inferArgTypes'; import { inferControls } from '../inferControls'; import { normalizeInputTypes } from './normalizeInputTypes'; +import { normalizeArrays } from './normalizeArrays'; export function normalizeProjectAnnotations({ argTypes, globalTypes, argTypesEnhancers, + decorators, + loaders, ...annotations }: ProjectAnnotations): NormalizedProjectAnnotations { return { ...(argTypes && { argTypes: normalizeInputTypes(argTypes as ArgTypes) }), ...(globalTypes && { globalTypes: normalizeInputTypes(globalTypes) }), + decorators: normalizeArrays(decorators), + loaders: normalizeArrays(loaders), argTypesEnhancers: [ ...(argTypesEnhancers || []), inferArgTypes, diff --git a/code/lib/preview-api/src/modules/store/csf/normalizeStory.ts b/code/lib/preview-api/src/modules/store/csf/normalizeStory.ts index 6666a95217b5..9a96bc4a332e 100644 --- a/code/lib/preview-api/src/modules/store/csf/normalizeStory.ts +++ b/code/lib/preview-api/src/modules/store/csf/normalizeStory.ts @@ -13,6 +13,7 @@ import { dedent } from 'ts-dedent'; import { logger } from '@storybook/client-logger'; import deprecate from 'util-deprecate'; import { normalizeInputTypes } from './normalizeInputTypes'; +import { normalizeArrays } from './normalizeArrays'; const deprecatedStoryAnnotation = dedent` CSF .story annotations deprecated; annotate story functions directly: @@ -44,11 +45,15 @@ export function normalizeStory( storyObject.storyName || story?.name || exportName; - const decorators = [...(storyObject.decorators || []), ...(story?.decorators || [])]; + + const decorators = [ + ...normalizeArrays(storyObject.decorators), + ...normalizeArrays(story?.decorators), + ]; const parameters = { ...story?.parameters, ...storyObject.parameters }; const args = { ...story?.args, ...storyObject.args }; const argTypes = { ...(story?.argTypes as ArgTypes), ...(storyObject.argTypes as ArgTypes) }; - const loaders = [...(storyObject.loaders || []), ...(story?.loaders || [])]; + const loaders = [...normalizeArrays(storyObject.loaders), ...normalizeArrays(story?.loaders)]; const { render, play, tags = [] } = storyObject; // eslint-disable-next-line no-underscore-dangle diff --git a/code/lib/preview-api/src/modules/store/csf/prepareStory.test.ts b/code/lib/preview-api/src/modules/store/csf/prepareStory.test.ts index a7cb1d971e6d..b15c3a955659 100644 --- a/code/lib/preview-api/src/modules/store/csf/prepareStory.test.ts +++ b/code/lib/preview-api/src/modules/store/csf/prepareStory.test.ts @@ -343,9 +343,9 @@ describe('prepareStory', () => { ); const storyContext = { context: 'value' } as any; - const loadedContext = await applyLoaders(storyContext); + const loadedContext = await applyLoaders({ ...storyContext }); - expect(loader).toHaveBeenCalledWith(storyContext); + expect(loader).toHaveBeenCalledWith({ ...storyContext, loaded: {} }); expect(loadedContext).toEqual({ context: 'value', loaded: { foo: 7 }, diff --git a/code/lib/preview-api/src/modules/store/csf/prepareStory.ts b/code/lib/preview-api/src/modules/store/csf/prepareStory.ts index e4bfb1937114..311a4467c789 100644 --- a/code/lib/preview-api/src/modules/store/csf/prepareStory.ts +++ b/code/lib/preview-api/src/modules/store/csf/prepareStory.ts @@ -1,24 +1,25 @@ +/* eslint-disable no-restricted-syntax,no-await-in-loop,@typescript-eslint/no-loop-func,no-underscore-dangle */ import { global } from '@storybook/global'; import type { - Renderer, Args, ArgsStoryFn, LegacyStoryFn, - Parameters, - PlayFunction, - PlayFunctionContext, - StepLabel, + ModuleExport, NormalizedComponentAnnotations, NormalizedProjectAnnotations, NormalizedStoryAnnotations, + Parameters, + PlayFunction, + PlayFunctionContext, + PreparedMeta, PreparedStory, + Renderer, + StepLabel, StoryContext, StoryContextForEnhancers, StoryContextForLoaders, StrictArgTypes, - PreparedMeta, - ModuleExport, } from '@storybook/types'; import { includeConditionalArg } from '@storybook/csf'; @@ -26,6 +27,7 @@ import { applyHooks } from '../../addons'; import { combineParameters } from '../parameters'; import { defaultDecorateStory } from '../decorators'; import { groupArgsByTarget, UNTARGETED } from '../args'; +import { normalizeArrays } from './normalizeArrays'; // Combine all the metadata about a story (both direct and inherited from the component/global scope) // into a "renderable" story function, with all decorators applied, parameters passed as context etc @@ -48,15 +50,23 @@ export function prepareStory( projectAnnotations ); - const loaders = [ - ...(projectAnnotations.loaders || []), - ...(componentAnnotations.loaders || []), - ...(storyAnnotations?.loaders || []), - ]; - const applyLoaders = async (context: StoryContextForLoaders) => { - const loadResults = await Promise.all(loaders.map((loader) => loader(context))); - const loaded = Object.assign({}, ...loadResults); - return { ...context, loaded }; + const applyLoaders = async ( + context: StoryContextForLoaders + ): Promise & { loaded: StoryContext['loaded'] }> => { + let updatedContext = { ...context, loaded: {} }; + for (const loaders of [ + ...('__STORYBOOK_TEST_LOADERS__' in global && Array.isArray(global.__STORYBOOK_TEST_LOADERS__) + ? [global.__STORYBOOK_TEST_LOADERS__] + : []), + normalizeArrays(projectAnnotations.loaders), + normalizeArrays(componentAnnotations.loaders), + normalizeArrays(storyAnnotations.loaders), + ]) { + const loadResults = await Promise.all(loaders.map((loader) => loader(updatedContext))); + const loaded: Record = Object.assign({}, ...loadResults); + updatedContext = { ...updatedContext, loaded: { ...updatedContext.loaded, ...loaded } }; + } + return updatedContext; }; const undecoratedStoryFn: LegacyStoryFn = (context: StoryContext) => { @@ -70,9 +80,9 @@ export function prepareStory( const { applyDecorators = defaultDecorateStory, runStep } = projectAnnotations; const decorators = [ - ...(storyAnnotations?.decorators || []), - ...(componentAnnotations.decorators || []), - ...(projectAnnotations.decorators || []), + ...normalizeArrays(storyAnnotations?.decorators), + ...normalizeArrays(componentAnnotations?.decorators), + ...normalizeArrays(projectAnnotations?.decorators), ]; // The render function on annotations *has* to be an `ArgsStoryFn`, so when we normalize @@ -115,7 +125,6 @@ export function prepareStory( playFunction, }; } - export function prepareMeta( componentAnnotations: NormalizedComponentAnnotations, projectAnnotations: NormalizedProjectAnnotations, @@ -164,7 +173,6 @@ function preparePartialAnnotations( const { passArgsFirst = true } = parameters; - // eslint-disable-next-line no-underscore-dangle parameters.__isArgsStory = passArgsFirst && render && render.length > 0; } diff --git a/code/lib/preview-api/src/modules/store/csf/testing-utils/index.ts b/code/lib/preview-api/src/modules/store/csf/testing-utils/index.ts index 10468737e6dc..c9a3731f4bd6 100644 --- a/code/lib/preview-api/src/modules/store/csf/testing-utils/index.ts +++ b/code/lib/preview-api/src/modules/store/csf/testing-utils/index.ts @@ -82,7 +82,7 @@ export function composeStory)); }, { storyName, diff --git a/code/lib/preview-api/src/modules/store/sortStories.ts b/code/lib/preview-api/src/modules/store/sortStories.ts index 4335b6f72103..8fc6950fbd40 100644 --- a/code/lib/preview-api/src/modules/store/sortStories.ts +++ b/code/lib/preview-api/src/modules/store/sortStories.ts @@ -9,6 +9,7 @@ import type { Parameters, Path, PreparedStory, + Renderer, } from '@storybook/types'; import { storySort } from './storySort'; @@ -58,8 +59,8 @@ const toIndexEntry = (story: any): StoryIndexEntry => { return { id, title, name, importPath: parameters.fileName, type }; }; -export const sortStoriesV6 = ( - stories: [string, PreparedStory, Parameters, Parameters][], +export const sortStoriesV6 = ( + stories: [string, PreparedStory, Parameters, Parameters][], storySortParameter: Addon_StorySortParameter, fileNameOrder: Path[] ) => { diff --git a/code/lib/source-loader/package.json b/code/lib/source-loader/package.json index 98b44a0f9d62..9c93afba521f 100644 --- a/code/lib/source-loader/package.json +++ b/code/lib/source-loader/package.json @@ -45,7 +45,7 @@ "prep": "node --loader ../../../scripts/node_modules/esbuild-register/loader.js -r ../../../scripts/node_modules/esbuild-register/register.js ../../../scripts/prepare/bundle.ts" }, "dependencies": { - "@storybook/csf": "^0.1.0", + "@storybook/csf": "^0.1.2", "@storybook/types": "workspace:*", "estraverse": "^5.2.0", "lodash": "^4.17.21", diff --git a/code/lib/test/package.json b/code/lib/test/package.json index d75491d9cbb1..41c3d658bad1 100644 --- a/code/lib/test/package.json +++ b/code/lib/test/package.json @@ -19,7 +19,7 @@ "url": "https://opencollective.com/storybook" }, "license": "MIT", - "sideEffects": false, + "sideEffects": true, "exports": { ".": { "types": "./dist/index.d.ts", diff --git a/code/lib/test/src/index.ts b/code/lib/test/src/index.ts index 34d59391676e..600c68f1d3b4 100644 --- a/code/lib/test/src/index.ts +++ b/code/lib/test/src/index.ts @@ -1,18 +1,11 @@ import { instrument } from '@storybook/instrumenter'; -import * as spy from '@vitest/spy'; +import { type LoaderFunction } from '@storybook/csf'; import chai from 'chai'; -import { FORCE_REMOUNT, STORY_RENDER_PHASE_CHANGED } from '@storybook/core-events'; -import { addons } from '@storybook/preview-api'; +import { global } from '@storybook/global'; import { expect as rawExpect } from './expect'; +import { clearAllMocks, resetAllMocks, restoreAllMocks } from './spy'; -export * from '@vitest/spy'; - -const channel = addons.getChannel(); - -channel.on(FORCE_REMOUNT, () => spy.spies.forEach((mock) => mock.mockClear())); -channel.on(STORY_RENDER_PHASE_CHANGED, ({ newPhase }) => { - if (newPhase === 'loading') spy.spies.forEach((mock) => mock.mockClear()); -}); +export * from './spy'; export const { expect } = instrument( { expect: rawExpect }, @@ -32,3 +25,17 @@ export const { expect } = instrument( ); export * from './testing-library'; + +const resetAllMocksLoader: LoaderFunction = ({ parameters }) => { + if (parameters?.test?.mockReset === true) { + resetAllMocks(); + } else if (parameters?.test?.clearMocks === true) { + clearAllMocks(); + } else if (parameters?.test?.restoreMocks !== false) { + restoreAllMocks(); + } +}; + +// @ts-expect-error We are using this as a default Storybook loader, when the test package is used. This avoids the need for optional peer dependency workarounds. +// eslint-disable-next-line no-underscore-dangle +global.__STORYBOOK_TEST_LOADERS__ = [resetAllMocksLoader]; diff --git a/code/lib/test/src/spy.ts b/code/lib/test/src/spy.ts new file mode 100644 index 000000000000..8b0ce5f38e56 --- /dev/null +++ b/code/lib/test/src/spy.ts @@ -0,0 +1,66 @@ +import { + spyOn, + isMockFunction, + fn, + spies as mocks, + type MaybeMocked, + type MaybeMockedDeep, + type MaybePartiallyMocked, + type MaybePartiallyMockedDeep, +} from '@vitest/spy'; + +export type * from '@vitest/spy'; + +export { spyOn, isMockFunction, fn, mocks }; + +/** + * Calls [`.mockClear()`](https://vitest.dev/api/mock#mockclear) on every mocked function. This will only empty `.mock` state, it will not reset implementation. + * + * It is useful if you need to clean up mock between different assertions. + */ +export function clearAllMocks() { + mocks.forEach((spy) => spy.mockClear()); +} + +/** + * Calls [`.mockReset()`](https://vitest.dev/api/mock#mockreset) on every mocked function. This will empty `.mock` state, reset "once" implementations and force the base implementation to return `undefined` when invoked. + * + * This is useful when you want to completely reset a mock to the default state. + */ +export function resetAllMocks() { + mocks.forEach((spy) => spy.mockReset()); +} + +/** + * Calls [`.mockRestore()`](https://vitest.dev/api/mock#mockrestore) on every mocked function. This will restore all original implementations. + */ +export function restoreAllMocks() { + mocks.forEach((spy) => spy.mockRestore()); +} + +/** + * Type helper for TypeScript. Just returns the object that was passed. + * + * When `partial` is `true` it will expect a `Partial` as a return value. By default, this will only make TypeScript believe that + * the first level values are mocked. You can pass down `{ deep: true }` as a second argument to tell TypeScript that the whole object is mocked, if it actually is. + * + * @param item Anything that can be mocked + * @param deep If the object is deeply mocked + * @param options If the object is partially or deeply mocked + */ +export function mocked(item: T, deep?: false): MaybeMocked; +export function mocked(item: T, deep: true): MaybeMockedDeep; +export function mocked(item: T, options: { partial?: false; deep?: false }): MaybeMocked; +export function mocked(item: T, options: { partial?: false; deep: true }): MaybeMockedDeep; +export function mocked( + item: T, + options: { partial: true; deep?: false } +): MaybePartiallyMocked; +export function mocked( + item: T, + options: { partial: true; deep: true } +): MaybePartiallyMockedDeep; +export function mocked(item: T): MaybeMocked; +export function mocked(item: T, _options = {}): MaybeMocked { + return item as any; +} diff --git a/code/lib/types/package.json b/code/lib/types/package.json index 078a827837a5..e14551c1822c 100644 --- a/code/lib/types/package.json +++ b/code/lib/types/package.json @@ -50,7 +50,7 @@ "file-system-cache": "2.3.0" }, "devDependencies": { - "@storybook/csf": "^0.1.0", + "@storybook/csf": "^0.1.2", "@types/fs-extra": "^11.0.1", "@types/node": "^18.0.0", "typescript": "~4.9.3" diff --git a/code/lib/types/src/modules/story.ts b/code/lib/types/src/modules/story.ts index c1a5b9d6ffdd..cd4e9f1c9d16 100644 --- a/code/lib/types/src/modules/story.ts +++ b/code/lib/types/src/modules/story.ts @@ -1,4 +1,9 @@ -import type { Renderer, ProjectAnnotations as CsfProjectAnnotations } from '@storybook/csf'; +import type { + Renderer, + ProjectAnnotations as CsfProjectAnnotations, + DecoratorFunction, + LoaderFunction, +} from '@storybook/csf'; import type { ComponentAnnotations, @@ -42,23 +47,31 @@ export type ProjectAnnotations = CsfProjectAnnotatio renderToDOM?: RenderToCanvas; }; -export type NormalizedProjectAnnotations = - ProjectAnnotations & { - argTypes?: StrictArgTypes; - globalTypes?: StrictGlobalTypes; - }; +export type NormalizedProjectAnnotations = Omit< + ProjectAnnotations, + 'decorators' | 'loaders' +> & { + argTypes?: StrictArgTypes; + globalTypes?: StrictGlobalTypes; + decorators?: DecoratorFunction[]; + loaders?: LoaderFunction[]; +}; -export type NormalizedComponentAnnotations = - ComponentAnnotations & { - // Useful to guarantee that id & title exists - id: ComponentId; - title: ComponentTitle; - argTypes?: StrictArgTypes; - }; +export type NormalizedComponentAnnotations = Omit< + ComponentAnnotations, + 'decorators' | 'loaders' +> & { + // Useful to guarantee that id & title exists + id: ComponentId; + title: ComponentTitle; + argTypes?: StrictArgTypes; + decorators?: DecoratorFunction[]; + loaders?: LoaderFunction[]; +}; export type NormalizedStoryAnnotations = Omit< StoryAnnotations, - 'storyName' | 'story' + 'storyName' | 'story' | 'decorators' | 'loaders' > & { moduleExport: ModuleExport; // You cannot actually set id on story annotations, but we normalize it to be there for convience @@ -66,6 +79,8 @@ export type NormalizedStoryAnnotations = argTypes?: StrictArgTypes; name: StoryName; userStoryFn?: StoryFn; + decorators?: DecoratorFunction[]; + loaders?: LoaderFunction[]; }; export type CSFFile = { diff --git a/code/package.json b/code/package.json index a98045536f60..6998a74be8d6 100644 --- a/code/package.json +++ b/code/package.json @@ -140,7 +140,7 @@ "@storybook/core-events": "workspace:*", "@storybook/core-server": "workspace:*", "@storybook/core-webpack": "workspace:*", - "@storybook/csf": "^0.1.0", + "@storybook/csf": "^0.1.2", "@storybook/csf-plugin": "workspace:*", "@storybook/csf-tools": "workspace:*", "@storybook/docs-tools": "workspace:*", diff --git a/code/renderers/server/package.json b/code/renderers/server/package.json index ae2f96fe428f..c20511b1feff 100644 --- a/code/renderers/server/package.json +++ b/code/renderers/server/package.json @@ -47,7 +47,7 @@ }, "dependencies": { "@storybook/core-client": "workspace:*", - "@storybook/csf": "^0.1.0", + "@storybook/csf": "^0.1.2", "@storybook/csf-tools": "workspace:*", "@storybook/global": "^5.0.0", "@storybook/preview-api": "workspace:*", diff --git a/code/ui/blocks/package.json b/code/ui/blocks/package.json index b42af01f67db..b2dbcfb882f7 100644 --- a/code/ui/blocks/package.json +++ b/code/ui/blocks/package.json @@ -48,7 +48,7 @@ "@storybook/client-logger": "workspace:*", "@storybook/components": "workspace:*", "@storybook/core-events": "workspace:*", - "@storybook/csf": "^0.1.0", + "@storybook/csf": "^0.1.2", "@storybook/docs-tools": "workspace:*", "@storybook/global": "^5.0.0", "@storybook/manager-api": "workspace:*", diff --git a/code/ui/components/package.json b/code/ui/components/package.json index 13e69c16ff8b..9954c55392ef 100644 --- a/code/ui/components/package.json +++ b/code/ui/components/package.json @@ -62,7 +62,7 @@ "@radix-ui/react-select": "^1.2.2", "@radix-ui/react-toolbar": "^1.0.4", "@storybook/client-logger": "workspace:*", - "@storybook/csf": "^0.1.0", + "@storybook/csf": "^0.1.2", "@storybook/global": "^5.0.0", "@storybook/theming": "workspace:*", "@storybook/types": "workspace:*", diff --git a/code/yarn.lock b/code/yarn.lock index 760db61c9a4e..44f7f1428778 100644 --- a/code/yarn.lock +++ b/code/yarn.lock @@ -5655,7 +5655,7 @@ __metadata: dependencies: "@storybook/client-logger": "workspace:*" "@storybook/core-events": "workspace:*" - "@storybook/csf": "npm:^0.1.0" + "@storybook/csf": "npm:^0.1.2" "@storybook/global": "npm:^5.0.0" "@storybook/manager-api": "workspace:*" "@storybook/preview-api": "workspace:*" @@ -5724,7 +5724,7 @@ __metadata: resolution: "@storybook/addon-storyshots-puppeteer@workspace:addons/storyshots-puppeteer" dependencies: "@axe-core/puppeteer": "npm:^4.2.0" - "@storybook/csf": "npm:^0.1.0" + "@storybook/csf": "npm:^0.1.2" "@storybook/node-logger": "workspace:*" "@storybook/types": "workspace:*" "@types/jest-image-snapshot": "npm:^6.0.0" @@ -6033,7 +6033,7 @@ __metadata: "@storybook/client-logger": "workspace:*" "@storybook/components": "workspace:*" "@storybook/core-events": "workspace:*" - "@storybook/csf": "npm:^0.1.0" + "@storybook/csf": "npm:^0.1.2" "@storybook/docs-tools": "workspace:*" "@storybook/global": "npm:^5.0.0" "@storybook/manager-api": "workspace:*" @@ -6306,7 +6306,7 @@ __metadata: "@babel/core": "npm:^7.23.2" "@babel/preset-env": "npm:^7.23.2" "@babel/types": "npm:^7.23.0" - "@storybook/csf": "npm:^0.1.0" + "@storybook/csf": "npm:^0.1.2" "@storybook/csf-tools": "workspace:*" "@storybook/node-logger": "workspace:*" "@storybook/types": "workspace:*" @@ -6344,7 +6344,7 @@ __metadata: "@radix-ui/react-select": "npm:^1.2.2" "@radix-ui/react-toolbar": "npm:^1.0.4" "@storybook/client-logger": "workspace:*" - "@storybook/csf": "npm:^0.1.0" + "@storybook/csf": "npm:^0.1.2" "@storybook/global": "npm:^5.0.0" "@storybook/icons": "npm:^1.1.6" "@storybook/theming": "workspace:*" @@ -6434,7 +6434,7 @@ __metadata: "@storybook/channels": "workspace:*" "@storybook/core-common": "workspace:*" "@storybook/core-events": "workspace:*" - "@storybook/csf": "npm:^0.1.0" + "@storybook/csf": "npm:^0.1.2" "@storybook/csf-tools": "workspace:*" "@storybook/docs-mdx": "npm:^0.1.0" "@storybook/global": "npm:^5.0.0" @@ -6515,7 +6515,7 @@ __metadata: "@babel/parser": "npm:^7.23.0" "@babel/traverse": "npm:^7.23.2" "@babel/types": "npm:^7.23.0" - "@storybook/csf": "npm:^0.1.0" + "@storybook/csf": "npm:^0.1.2" "@storybook/types": "workspace:*" "@types/fs-extra": "npm:^11.0.1" "@types/js-yaml": "npm:^4.0.5" @@ -6536,12 +6536,12 @@ __metadata: languageName: node linkType: hard -"@storybook/csf@npm:^0.1.0": - version: 0.1.1 - resolution: "@storybook/csf@npm:0.1.1" +"@storybook/csf@npm:^0.1.2": + version: 0.1.2 + resolution: "@storybook/csf@npm:0.1.2" dependencies: type-fest: "npm:^2.19.0" - checksum: 999bb87fbbe047a559bbaa5baf2ed84872fcd5cdcae3c1169f8e4c641eefe8759d09a09034a78ed114032c0e5cf6301b7fa89e5e3ce60d75cf0bd5e33ec0a6e7 + checksum: b51a55292e5d2af8b1d135a28ecaa94f8860ddfedcb393adfa2cca1ee23853156066f737d8be1cb5412f572781aa525dc0b2f6e4a6f6ce805489f0149efe837c languageName: node linkType: hard @@ -6765,7 +6765,7 @@ __metadata: "@storybook/channels": "workspace:*" "@storybook/client-logger": "workspace:*" "@storybook/core-events": "workspace:*" - "@storybook/csf": "npm:^0.1.0" + "@storybook/csf": "npm:^0.1.2" "@storybook/global": "npm:^5.0.0" "@storybook/router": "workspace:*" "@storybook/theming": "workspace:*" @@ -7174,7 +7174,7 @@ __metadata: "@storybook/client-logger": "workspace:*" "@storybook/core-common": "workspace:*" "@storybook/core-events": "workspace:*" - "@storybook/csf": "npm:^0.1.0" + "@storybook/csf": "npm:^0.1.2" "@storybook/global": "npm:^5.0.0" "@storybook/types": "workspace:*" "@types/qs": "npm:^6.9.5" @@ -7383,7 +7383,7 @@ __metadata: "@storybook/core-events": "workspace:*" "@storybook/core-server": "workspace:*" "@storybook/core-webpack": "workspace:*" - "@storybook/csf": "npm:^0.1.0" + "@storybook/csf": "npm:^0.1.2" "@storybook/csf-plugin": "workspace:*" "@storybook/csf-tools": "workspace:*" "@storybook/docs-tools": "workspace:*" @@ -7556,7 +7556,7 @@ __metadata: resolution: "@storybook/server@workspace:renderers/server" dependencies: "@storybook/core-client": "workspace:*" - "@storybook/csf": "npm:^0.1.0" + "@storybook/csf": "npm:^0.1.2" "@storybook/csf-tools": "workspace:*" "@storybook/global": "npm:^5.0.0" "@storybook/preview-api": "workspace:*" @@ -7573,7 +7573,7 @@ __metadata: version: 0.0.0-use.local resolution: "@storybook/source-loader@workspace:lib/source-loader" dependencies: - "@storybook/csf": "npm:^0.1.0" + "@storybook/csf": "npm:^0.1.2" "@storybook/types": "workspace:*" estraverse: "npm:^5.2.0" jest-specific-snapshot: "npm:^8.0.0" @@ -7768,7 +7768,7 @@ __metadata: resolution: "@storybook/types@workspace:lib/types" dependencies: "@storybook/channels": "workspace:*" - "@storybook/csf": "npm:^0.1.0" + "@storybook/csf": "npm:^0.1.2" "@types/babel__core": "npm:^7.0.0" "@types/express": "npm:^4.7.0" "@types/fs-extra": "npm:^11.0.1"