Skip to content

Commit

Permalink
Merge pull request #24948 from storybookjs/kasper/model-loaders-as-be…
Browse files Browse the repository at this point in the history
…fore-each

Test: Model loaders as before each and restore mocks properly
  • Loading branch information
kasperpeulen authored Nov 22, 2023
2 parents d2ef359 + 12960c7 commit c9afa39
Show file tree
Hide file tree
Showing 33 changed files with 237 additions and 129 deletions.
7 changes: 1 addition & 6 deletions code/addons/actions/src/addArgs.ts
Original file line number Diff line number Diff line change
@@ -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,
];
31 changes: 0 additions & 31 deletions code/addons/actions/src/addArgsHelpers.ts
Original file line number Diff line number Diff line change
@@ -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';

Expand Down Expand Up @@ -63,33 +62,3 @@ export const addActionsFromArgTypes: ArgsEnhancer<Renderer> = (context) => {
return acc;
}, {} as Args);
};

export const attachActionsToFunctionMocks: ArgsEnhancer<Renderer> = (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);
};
30 changes: 30 additions & 0 deletions code/addons/actions/src/loaders.ts
Original file line number Diff line number Diff line change
@@ -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];
1 change: 1 addition & 0 deletions code/addons/actions/src/preview.ts
Original file line number Diff line number Diff line change
@@ -1 +1,2 @@
export * from './addArgs';
export * from './loaders';
2 changes: 1 addition & 1 deletion code/addons/links/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -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"
},
Expand Down
2 changes: 1 addition & 1 deletion code/addons/storyshots-puppeteer/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -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",
Expand Down
2 changes: 1 addition & 1 deletion code/lib/codemod/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -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:*",
Expand Down
2 changes: 1 addition & 1 deletion code/lib/core-server/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -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",
Expand Down
2 changes: 1 addition & 1 deletion code/lib/csf-tools/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -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",
Expand Down
2 changes: 1 addition & 1 deletion code/lib/manager-api/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -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:*",
Expand Down
2 changes: 1 addition & 1 deletion code/lib/preview-api/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -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",
Expand Down
8 changes: 5 additions & 3 deletions code/lib/preview-api/src/modules/client-api/ClientApi.ts
Original file line number Diff line number Diff line change
Expand Up @@ -168,9 +168,11 @@ export class ClientApi<TRenderer extends Renderer> {
}
};

addStepRunner = (stepRunner: StepRunner) => {
addStepRunner = (stepRunner: StepRunner<TRenderer>) => {
this.facade.projectAnnotations.runStep = composeStepRunners(
[this.facade.projectAnnotations.runStep, stepRunner].filter(Boolean) as StepRunner[]
[this.facade.projectAnnotations.runStep, stepRunner].filter(
Boolean
) as StepRunner<TRenderer>[]
);
};

Expand Down Expand Up @@ -297,7 +299,7 @@ export class ClientApi<TRenderer extends Renderer> {
this._addedExports[fileName] = { default: meta };

let counter = 0;
api.add = (storyName: string, storyFn: StoryFn<TRenderer>, parameters: Parameters = {}) => {
api.add = (storyName: string, storyFn: StoryFn, parameters: Parameters = {}) => {
hasAdded = true;

if (typeof storyName !== 'string') {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -34,7 +34,7 @@ export class DocsContext<TRenderer extends Renderer> implements DocsContextProps
constructor(
public channel: Channel,
protected store: StoryStore<TRenderer>,
public renderStoryToElement: DocsContextProps['renderStoryToElement'],
public renderStoryToElement: DocsContextProps<TRenderer>['renderStoryToElement'],
/** The CSF files known (via the index) to be refererenced by this docs file */
csfFiles: CSFFile<TRenderer>[]
) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -95,7 +95,7 @@ export class CsfDocsRender<TRenderer extends Renderer> implements Render<TRender
);
}

docsContext(renderStoryToElement: DocsContextProps['renderStoryToElement']) {
docsContext(renderStoryToElement: DocsContextProps<TRenderer>['renderStoryToElement']) {
if (!this.csfFiles) throw new Error('Cannot render docs before preparing');
const docsContext = new DocsContext<TRenderer>(
this.channel,
Expand All @@ -112,7 +112,7 @@ export class CsfDocsRender<TRenderer extends Renderer> implements Render<TRender

async renderToElement(
canvasElement: TRenderer['canvasElement'],
renderStoryToElement: DocsContextProps['renderStoryToElement']
renderStoryToElement: DocsContextProps<TRenderer>['renderStoryToElement']
) {
if (!this.story || !this.csfFiles) throw new Error('Cannot render docs before preparing');

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -79,7 +79,7 @@ export class MdxDocsRender<TRenderer extends Renderer> implements Render<TRender
);
}

docsContext(renderStoryToElement: DocsContextProps['renderStoryToElement']) {
docsContext(renderStoryToElement: DocsContextProps<TRenderer>['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)`
Expand All @@ -94,7 +94,7 @@ export class MdxDocsRender<TRenderer extends Renderer> implements Render<TRender

async renderToElement(
canvasElement: TRenderer['canvasElement'],
renderStoryToElement: DocsContextProps['renderStoryToElement']
renderStoryToElement: DocsContextProps<TRenderer>['renderStoryToElement']
) {
if (!this.exports || !this.csfFiles || !this.store.projectAnnotations)
throw new Error('Cannot render docs before preparing');
Expand Down
4 changes: 4 additions & 0 deletions code/lib/preview-api/src/modules/store/csf/normalizeArrays.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
export const normalizeArrays = <T>(array: T[] | T | undefined): T[] => {
if (Array.isArray(array)) return array;
return array ? [array] : [];
};
Original file line number Diff line number Diff line change
Expand Up @@ -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<TRenderer extends Renderer>({
argTypes,
globalTypes,
argTypesEnhancers,
decorators,
loaders,
...annotations
}: ProjectAnnotations<TRenderer>): NormalizedProjectAnnotations<TRenderer> {
return {
...(argTypes && { argTypes: normalizeInputTypes(argTypes as ArgTypes) }),
...(globalTypes && { globalTypes: normalizeInputTypes(globalTypes) }),
decorators: normalizeArrays(decorators),
loaders: normalizeArrays(loaders),
argTypesEnhancers: [
...(argTypesEnhancers || []),
inferArgTypes,
Expand Down
9 changes: 7 additions & 2 deletions code/lib/preview-api/src/modules/store/csf/normalizeStory.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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:
Expand Down Expand Up @@ -44,11 +45,15 @@ export function normalizeStory<TRenderer extends Renderer>(
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
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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 },
Expand Down
50 changes: 29 additions & 21 deletions code/lib/preview-api/src/modules/store/csf/prepareStory.ts
Original file line number Diff line number Diff line change
@@ -1,31 +1,33 @@
/* 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';

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
Expand All @@ -48,15 +50,23 @@ export function prepareStory<TRenderer extends Renderer>(
projectAnnotations
);

const loaders = [
...(projectAnnotations.loaders || []),
...(componentAnnotations.loaders || []),
...(storyAnnotations?.loaders || []),
];
const applyLoaders = async (context: StoryContextForLoaders<TRenderer>) => {
const loadResults = await Promise.all(loaders.map((loader) => loader(context)));
const loaded = Object.assign({}, ...loadResults);
return { ...context, loaded };
const applyLoaders = async (
context: StoryContextForLoaders<TRenderer>
): Promise<StoryContextForLoaders<TRenderer> & { loaded: StoryContext<TRenderer>['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<string, any> = Object.assign({}, ...loadResults);
updatedContext = { ...updatedContext, loaded: { ...updatedContext.loaded, ...loaded } };
}
return updatedContext;
};

const undecoratedStoryFn: LegacyStoryFn<TRenderer> = (context: StoryContext<TRenderer>) => {
Expand All @@ -70,9 +80,9 @@ export function prepareStory<TRenderer extends Renderer>(
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
Expand Down Expand Up @@ -115,7 +125,6 @@ export function prepareStory<TRenderer extends Renderer>(
playFunction,
};
}

export function prepareMeta<TRenderer extends Renderer>(
componentAnnotations: NormalizedComponentAnnotations<TRenderer>,
projectAnnotations: NormalizedProjectAnnotations<TRenderer>,
Expand Down Expand Up @@ -164,7 +173,6 @@ function preparePartialAnnotations<TRenderer extends Renderer>(

const { passArgsFirst = true } = parameters;

// eslint-disable-next-line no-underscore-dangle
parameters.__isArgsStory = passArgsFirst && render && render.length > 0;
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -82,7 +82,7 @@ export function composeStory<TRenderer extends Renderer = Renderer, TArgs extend
args: { ...story.initialArgs, ...extraArgs },
};

return story.unboundStoryFn(prepareContext(context as StoryContext));
return story.unboundStoryFn(prepareContext(context as StoryContext<TRenderer>));
},
{
storyName,
Expand Down
Loading

0 comments on commit c9afa39

Please sign in to comment.