From e944cc5f0078c732369d1482477ff14bffe8fa6c Mon Sep 17 00:00:00 2001 From: Kasper Peulen Date: Tue, 25 Jun 2024 16:35:09 +0200 Subject: [PATCH 01/20] Consolidate loader, play and render context and add a self referencing context property --- code/addons/links/package.json | 2 +- code/lib/codemod/package.json | 2 +- code/lib/core-events/package.json | 2 +- code/lib/core-server/package.json | 2 +- code/lib/csf-tools/package.json | 2 +- code/lib/instrumenter/src/instrumenter.ts | 9 ++-- code/lib/manager-api/package.json | 2 +- code/lib/preview-api/package.json | 2 +- .../modules/preview-web/render/StoryRender.ts | 53 +++++++++---------- .../src/modules/store/csf/prepareStory.ts | 37 ++++--------- code/lib/source-loader/package.json | 2 +- code/lib/types/package.json | 2 +- code/lib/types/src/modules/story.ts | 16 +++--- code/package.json | 5 +- code/renderers/server/package.json | 2 +- code/ui/blocks/package.json | 2 +- code/ui/components/package.json | 2 +- code/yarn.lock | 44 +++++++-------- 18 files changed, 86 insertions(+), 102 deletions(-) diff --git a/code/addons/links/package.json b/code/addons/links/package.json index 3052208e2246..2be96a5f6d90 100644 --- a/code/addons/links/package.json +++ b/code/addons/links/package.json @@ -67,7 +67,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.8", + "@storybook/csf": "0.1.10--canary.d841bb4.0", "@storybook/global": "^5.0.0", "ts-dedent": "^2.0.0" }, diff --git a/code/lib/codemod/package.json b/code/lib/codemod/package.json index 97bc4d24046c..b70bc3016fcd 100644 --- a/code/lib/codemod/package.json +++ b/code/lib/codemod/package.json @@ -57,7 +57,7 @@ "@babel/core": "^7.24.4", "@babel/preset-env": "^7.24.4", "@babel/types": "^7.24.0", - "@storybook/csf": "^0.1.8", + "@storybook/csf": "0.1.10--canary.d841bb4.0", "@storybook/csf-tools": "workspace:*", "@storybook/node-logger": "workspace:*", "@storybook/types": "workspace:*", diff --git a/code/lib/core-events/package.json b/code/lib/core-events/package.json index 24fefcf9b258..d4bd5bf31d26 100644 --- a/code/lib/core-events/package.json +++ b/code/lib/core-events/package.json @@ -78,7 +78,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.8", + "@storybook/csf": "0.1.10--canary.d841bb4.0", "ts-dedent": "^2.0.0" }, "devDependencies": { diff --git a/code/lib/core-server/package.json b/code/lib/core-server/package.json index 0b4738c6e908..adec4bbecd9d 100644 --- a/code/lib/core-server/package.json +++ b/code/lib/core-server/package.json @@ -63,7 +63,7 @@ "@storybook/channels": "workspace:*", "@storybook/core-common": "workspace:*", "@storybook/core-events": "workspace:*", - "@storybook/csf": "^0.1.8", + "@storybook/csf": "0.1.10--canary.d841bb4.0", "@storybook/csf-tools": "workspace:*", "@storybook/docs-mdx": "3.1.0-next.0", "@storybook/global": "^5.0.0", diff --git a/code/lib/csf-tools/package.json b/code/lib/csf-tools/package.json index 026383952aeb..07718df5b847 100644 --- a/code/lib/csf-tools/package.json +++ b/code/lib/csf-tools/package.json @@ -46,7 +46,7 @@ "@babel/parser": "^7.24.4", "@babel/traverse": "^7.24.1", "@babel/types": "^7.24.0", - "@storybook/csf": "^0.1.8", + "@storybook/csf": "0.1.10--canary.d841bb4.0", "@storybook/types": "workspace:*", "fs-extra": "^11.1.0", "recast": "^0.23.5", diff --git a/code/lib/instrumenter/src/instrumenter.ts b/code/lib/instrumenter/src/instrumenter.ts index f0a11336666b..5170cb2483a3 100644 --- a/code/lib/instrumenter/src/instrumenter.ts +++ b/code/lib/instrumenter/src/instrumenter.ts @@ -412,12 +412,14 @@ export class Instrumenter { const { callRefsByResult, renderPhase } = this.getState(call.storyId); // Map complex values to a JSON-serializable representation. - const serializeValues = (value: any): any => { + // We use a depth, to avoid infinite recursion of self referencing values. + const serializeValues = (value: any, depth = 0): any => { if (callRefsByResult.has(value)) { return callRefsByResult.get(value); } if (value instanceof Array) { - return value.map(serializeValues); + if (depth > 10) return []; + return value.map((it) => serializeValues(it, ++depth)); } if (value instanceof Date) { return { __date__: { value: value.toISOString() } }; @@ -451,8 +453,9 @@ export class Instrumenter { return { __class__: { name: value.constructor.name } }; } if (Object.prototype.toString.call(value) === '[object Object]') { + if (depth > 10) return {}; return Object.fromEntries( - Object.entries(value).map(([key, val]) => [key, serializeValues(val)]) + Object.entries(value).map(([key, val]) => [key, serializeValues(val, ++depth)]) ); } return value; diff --git a/code/lib/manager-api/package.json b/code/lib/manager-api/package.json index 49270d11882d..36eef08b6735 100644 --- a/code/lib/manager-api/package.json +++ b/code/lib/manager-api/package.json @@ -47,7 +47,7 @@ "@storybook/channels": "workspace:*", "@storybook/client-logger": "workspace:*", "@storybook/core-events": "workspace:*", - "@storybook/csf": "^0.1.8", + "@storybook/csf": "0.1.10--canary.d841bb4.0", "@storybook/global": "^5.0.0", "@storybook/icons": "^1.2.5", "@storybook/router": "workspace:*", diff --git a/code/lib/preview-api/package.json b/code/lib/preview-api/package.json index 75415839d0d2..1256f3d16b11 100644 --- a/code/lib/preview-api/package.json +++ b/code/lib/preview-api/package.json @@ -47,7 +47,7 @@ "@storybook/channels": "workspace:*", "@storybook/client-logger": "workspace:*", "@storybook/core-events": "workspace:*", - "@storybook/csf": "^0.1.8", + "@storybook/csf": "0.1.10--canary.d841bb4.0", "@storybook/global": "^5.0.0", "@storybook/types": "workspace:*", "@types/qs": "^6.9.5", diff --git a/code/lib/preview-api/src/modules/preview-web/render/StoryRender.ts b/code/lib/preview-api/src/modules/preview-web/render/StoryRender.ts index 8ade66fc8942..028402596143 100644 --- a/code/lib/preview-api/src/modules/preview-web/render/StoryRender.ts +++ b/code/lib/preview-api/src/modules/preview-web/render/StoryRender.ts @@ -9,7 +9,6 @@ import type { StoryContextForLoaders, StoryId, StoryRenderOptions, - ViewMode, } from '@storybook/types'; import type { Channel } from '@storybook/channels'; import { @@ -71,7 +70,7 @@ export class StoryRender implements Render, private callbacks: RenderContextCallbacks, public id: StoryId, - public viewMode: ViewMode, + public viewMode: StoryContext['viewMode'], public renderOptions: StoryRenderOptions = { autoplay: true, forceInitialArgs: false }, story?: PreparedStory ) { @@ -165,6 +164,7 @@ export class StoryRender implements Render implements Render>; - await this.runPhase(abortSignal, 'loading', async () => { - loadedContext = await applyLoaders({ - ...this.storyContext(), - viewMode: this.viewMode, - // TODO add this to CSF - canvasElement, - } as unknown as StoryContextForLoaders); - }); - if (abortSignal.aborted) return; - - const renderStoryContext: StoryContext = { - ...loadedContext!, - // By this stage, it is possible that new args/globals have been received for this story - // and we need to ensure we render it with the new values - ...this.storyContext(), + const context: StoryContext = { + ...(this.storyContext() as StoryContextForLoaders), + viewMode: this.viewMode, abortSignal, - // We should consider parameterizing the story types with TRenderer['canvasElement'] in the future - canvasElement: canvasElement as any, + canvasElement, + loaded: {}, + step: (label, play) => runStep(label, play, context), }; - await this.runPhase(abortSignal, 'beforeEach', async () => { - const cleanupCallbacks = await applyBeforeEach(renderStoryContext); - this.store.addCleanupCallbacks(story, cleanupCallbacks); - }); - - if (abortSignal.aborted) return; + context.context = context; const renderContext: RenderContext = { componentId, @@ -226,10 +209,22 @@ export class StoryRender implements Render unboundStoryFn(renderStoryContext), + storyContext: context, + storyFn: () => unboundStoryFn(context), unboundStoryFn, }; + await this.runPhase(abortSignal, 'loading', async () => { + context.loaded = await applyLoaders(context); + }); + + if (abortSignal.aborted) return; + + await this.runPhase(abortSignal, 'beforeEach', async () => { + const cleanupCallbacks = await applyBeforeEach(context); + this.store.addCleanupCallbacks(story, cleanupCallbacks); + }); + + if (abortSignal.aborted) return; await this.runPhase(abortSignal, 'rendering', async () => { const teardown = await this.renderToScreen(renderContext, canvasElement); @@ -253,7 +248,7 @@ export class StoryRender implements Render { - await playFunction(renderContext.storyContext); + await playFunction(context); }); if (!ignoreUnhandledErrors && unhandledErrors.size > 0) { await this.runPhase(abortSignal, 'errored'); 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 ce99cef8f1e0..aa6e1d797dd0 100644 --- a/code/lib/preview-api/src/modules/store/csf/prepareStory.ts +++ b/code/lib/preview-api/src/modules/store/csf/prepareStory.ts @@ -1,4 +1,4 @@ -/* eslint-disable @typescript-eslint/no-loop-func,no-underscore-dangle */ +/* eslint-disable no-underscore-dangle */ import { global } from '@storybook/global'; import type { Args, @@ -8,12 +8,9 @@ import type { NormalizedProjectAnnotations, NormalizedStoryAnnotations, Parameters, - PlayFunction, - PlayFunctionContext, PreparedMeta, PreparedStory, Renderer, - StepLabel, StoryContext, StoryContextForEnhancers, StoryContextForLoaders, @@ -50,9 +47,9 @@ export function prepareStory( ); const applyLoaders = async ( - context: StoryContextForLoaders - ): Promise & { loaded: StoryContext['loaded'] }> => { - let updatedContext = { ...context, loaded: {} }; + context: StoryContext + ): Promise['loaded']> => { + const loaded = {}; for (const loaders of [ ...('__STORYBOOK_TEST_LOADERS__' in global && Array.isArray(global.__STORYBOOK_TEST_LOADERS__) ? [global.__STORYBOOK_TEST_LOADERS__] @@ -61,12 +58,11 @@ export function prepareStory( 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 } }; + if (context.abortSignal.aborted) return loaded; + const loadResults = await Promise.all(loaders.map((loader) => loader(context))); + Object.assign(loaded, ...loadResults); } - - return updatedContext; + return loaded; }; const applyBeforeEach = async (context: StoryContext): Promise => { @@ -76,6 +72,7 @@ export function prepareStory( ...normalizeArrays(componentAnnotations.beforeEach), ...normalizeArrays(storyAnnotations.beforeEach), ]) { + if (context.abortSignal.aborted) return cleanupCallbacks; const cleanup = await beforeEach(context); if (cleanup) cleanupCallbacks.push(cleanup); } @@ -106,20 +103,7 @@ export function prepareStory( const decoratedStoryFn = applyHooks(applyDecorators)(undecoratedStoryFn, decorators); const unboundStoryFn = (context: StoryContext) => decoratedStoryFn(context); - const play = storyAnnotations?.play || componentAnnotations.play; - - const playFunction = - play && - (async (storyContext: StoryContext) => { - const playFunctionContext: PlayFunctionContext = { - ...storyContext, - // eslint-disable-next-line @typescript-eslint/no-shadow - step: (label: StepLabel, play: PlayFunction) => - // TODO: We know runStep is defined, we need a proper normalized annotations type - runStep!(label, play, playFunctionContext), - }; - return play(playFunctionContext); - }); + const playFunction = storyAnnotations?.play ?? componentAnnotations?.play; return { ...partialAnnotations, @@ -133,6 +117,7 @@ export function prepareStory( applyLoaders, applyBeforeEach, playFunction, + runStep, }; } export function prepareMeta( diff --git a/code/lib/source-loader/package.json b/code/lib/source-loader/package.json index 94eb630d9aab..c9f90b9df49d 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.8", + "@storybook/csf": "0.1.10--canary.d841bb4.0", "@storybook/types": "workspace:*", "estraverse": "^5.2.0", "lodash": "^4.17.21", diff --git a/code/lib/types/package.json b/code/lib/types/package.json index 226acaedbbb1..81057ffcd97f 100644 --- a/code/lib/types/package.json +++ b/code/lib/types/package.json @@ -49,7 +49,7 @@ "file-system-cache": "2.3.0" }, "devDependencies": { - "@storybook/csf": "^0.1.8", + "@storybook/csf": "0.1.10--canary.d841bb4.0", "@types/fs-extra": "^11.0.1", "@types/node": "^18.0.0", "typescript": "^5.3.2" diff --git a/code/lib/types/src/modules/story.ts b/code/lib/types/src/modules/story.ts index 041aaed2e709..db994f5c40ec 100644 --- a/code/lib/types/src/modules/story.ts +++ b/code/lib/types/src/modules/story.ts @@ -4,6 +4,7 @@ import type { DecoratorFunction, LoaderFunction, CleanupCallback, + StepRunner, } from '@storybook/csf'; import type { @@ -16,7 +17,6 @@ import type { StoryAnnotations, StoryContext, StoryContextForEnhancers, - StoryContextForLoaders, StoryFn, StoryId, StoryIdentifier, @@ -41,12 +41,12 @@ export type RenderToCanvas = ( element: TRenderer['canvasElement'] ) => MaybePromise; -export type ProjectAnnotations = CsfProjectAnnotations & { +export interface ProjectAnnotations + extends CsfProjectAnnotations { renderToCanvas?: RenderToCanvas; - /* @deprecated use renderToCanvas */ renderToDOM?: RenderToCanvas; -}; +} type NamedExportsOrDefault = TExport | { default: TExport }; @@ -55,12 +55,13 @@ export type NamedOrDefaultProjectAnnotations = Omit< ProjectAnnotations, - 'decorators' | 'loaders' + 'decorators' | 'loaders' | 'runStep' > & { argTypes?: StrictArgTypes; globalTypes?: StrictGlobalTypes; decorators?: DecoratorFunction[]; loaders?: LoaderFunction[]; + runStep: StepRunner; }; export type NormalizedComponentAnnotations = Omit< @@ -101,11 +102,10 @@ export type PreparedStory = originalStoryFn: StoryFn; undecoratedStoryFn: LegacyStoryFn; unboundStoryFn: LegacyStoryFn; - applyLoaders: ( - context: StoryContextForLoaders - ) => Promise & { loaded: StoryContext['loaded'] }>; + applyLoaders: (context: StoryContext) => Promise['loaded']>; applyBeforeEach: (context: StoryContext) => Promise; playFunction?: (context: StoryContext) => Promise | void; + runStep: StepRunner; }; export type PreparedMeta = Omit< diff --git a/code/package.json b/code/package.json index 69553500b02d..f2dadf392dcc 100644 --- a/code/package.json +++ b/code/package.json @@ -126,7 +126,7 @@ "@storybook/core-events": "workspace:*", "@storybook/core-server": "workspace:*", "@storybook/core-webpack": "workspace:*", - "@storybook/csf": "^0.1.8", + "@storybook/csf": "0.1.10--canary.d841bb4.0", "@storybook/csf-plugin": "workspace:*", "@storybook/csf-tools": "workspace:*", "@storybook/docs-tools": "workspace:*", @@ -293,5 +293,6 @@ "Dependency Upgrades" ] ] - } + }, + "packageManager": "yarn@4.3.1" } diff --git a/code/renderers/server/package.json b/code/renderers/server/package.json index cdf3421a8b86..437d1d937941 100644 --- a/code/renderers/server/package.json +++ b/code/renderers/server/package.json @@ -46,7 +46,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.8", + "@storybook/csf": "0.1.10--canary.d841bb4.0", "@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 39f5d36c9149..d34881e2a9d9 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.8", + "@storybook/csf": "0.1.10--canary.d841bb4.0", "@storybook/docs-tools": "workspace:*", "@storybook/global": "^5.0.0", "@storybook/icons": "^1.2.5", diff --git a/code/ui/components/package.json b/code/ui/components/package.json index be82bb3941a0..52c5c8874920 100644 --- a/code/ui/components/package.json +++ b/code/ui/components/package.json @@ -62,7 +62,7 @@ "@radix-ui/react-dialog": "^1.0.5", "@radix-ui/react-slot": "^1.0.2", "@storybook/client-logger": "workspace:*", - "@storybook/csf": "^0.1.8", + "@storybook/csf": "0.1.10--canary.d841bb4.0", "@storybook/global": "^5.0.0", "@storybook/icons": "^1.2.5", "@storybook/theming": "workspace:*", diff --git a/code/yarn.lock b/code/yarn.lock index 993b919a3e57..3afc12623caf 100644 --- a/code/yarn.lock +++ b/code/yarn.lock @@ -5307,7 +5307,7 @@ __metadata: dependencies: "@storybook/client-logger": "workspace:*" "@storybook/core-events": "workspace:*" - "@storybook/csf": "npm:^0.1.8" + "@storybook/csf": "npm:0.1.10--canary.d841bb4.0" "@storybook/global": "npm:^5.0.0" "@storybook/manager-api": "workspace:*" "@storybook/preview-api": "workspace:*" @@ -5574,7 +5574,7 @@ __metadata: "@storybook/client-logger": "workspace:*" "@storybook/components": "workspace:*" "@storybook/core-events": "workspace:*" - "@storybook/csf": "npm:^0.1.8" + "@storybook/csf": "npm:0.1.10--canary.d841bb4.0" "@storybook/docs-tools": "workspace:*" "@storybook/global": "npm:^5.0.0" "@storybook/icons": "npm:^1.2.5" @@ -5804,7 +5804,7 @@ __metadata: "@babel/core": "npm:^7.24.4" "@babel/preset-env": "npm:^7.24.4" "@babel/types": "npm:^7.24.0" - "@storybook/csf": "npm:^0.1.8" + "@storybook/csf": "npm:0.1.10--canary.d841bb4.0" "@storybook/csf-tools": "workspace:*" "@storybook/node-logger": "workspace:*" "@storybook/types": "workspace:*" @@ -5841,7 +5841,7 @@ __metadata: "@radix-ui/react-scroll-area": "npm:^1.0.5" "@radix-ui/react-slot": "npm:^1.0.2" "@storybook/client-logger": "workspace:*" - "@storybook/csf": "npm:^0.1.8" + "@storybook/csf": "npm:0.1.10--canary.d841bb4.0" "@storybook/global": "npm:^5.0.0" "@storybook/icons": "npm:^1.2.5" "@storybook/test": "workspace:*" @@ -5924,7 +5924,7 @@ __metadata: version: 0.0.0-use.local resolution: "@storybook/core-events@workspace:lib/core-events" dependencies: - "@storybook/csf": "npm:^0.1.8" + "@storybook/csf": "npm:0.1.10--canary.d841bb4.0" chalk: "npm:^4.1.0" ts-dedent: "npm:^2.0.0" typescript: "npm:^5.3.2" @@ -5944,7 +5944,7 @@ __metadata: "@storybook/channels": "workspace:*" "@storybook/core-common": "workspace:*" "@storybook/core-events": "workspace:*" - "@storybook/csf": "npm:^0.1.8" + "@storybook/csf": "npm:0.1.10--canary.d841bb4.0" "@storybook/csf-tools": "workspace:*" "@storybook/docs-mdx": "npm:3.1.0-next.0" "@storybook/global": "npm:^5.0.0" @@ -6026,7 +6026,7 @@ __metadata: "@babel/parser": "npm:^7.24.4" "@babel/traverse": "npm:^7.24.1" "@babel/types": "npm:^7.24.0" - "@storybook/csf": "npm:^0.1.8" + "@storybook/csf": "npm:0.1.10--canary.d841bb4.0" "@storybook/types": "workspace:*" "@types/fs-extra": "npm:^11.0.1" "@types/js-yaml": "npm:^4.0.5" @@ -6038,6 +6038,15 @@ __metadata: languageName: unknown linkType: soft +"@storybook/csf@npm:0.1.10--canary.d841bb4.0": + version: 0.1.10--canary.d841bb4.0 + resolution: "@storybook/csf@npm:0.1.10--canary.d841bb4.0" + dependencies: + type-fest: "npm:^2.19.0" + checksum: 10c0/35a9b5cf88a3378fb362d0a1988265ec218aa98d54b82b56e78a4c1142b3beed6e2ee42bd8731bac009fd8133e085c92af43541028468865b51c0b1220ded338 + languageName: node + linkType: hard + "@storybook/csf@npm:^0.0.1": version: 0.0.1 resolution: "@storybook/csf@npm:0.0.1" @@ -6047,15 +6056,6 @@ __metadata: languageName: node linkType: hard -"@storybook/csf@npm:^0.1.8": - version: 0.1.8 - resolution: "@storybook/csf@npm:0.1.8" - dependencies: - type-fest: "npm:^2.19.0" - checksum: 10c0/4fd08bd65205395aba693f053706d5c880db5102543744c4e13768902a615df9a479ac03f80fe33297bd5457a0d0ef655cd33d41a0e379764dd6f0c6b9a7d88c - languageName: node - linkType: hard - "@storybook/docs-mdx@npm:3.1.0-next.0": version: 3.1.0-next.0 resolution: "@storybook/docs-mdx@npm:3.1.0-next.0" @@ -6225,7 +6225,7 @@ __metadata: "@storybook/channels": "workspace:*" "@storybook/client-logger": "workspace:*" "@storybook/core-events": "workspace:*" - "@storybook/csf": "npm:^0.1.8" + "@storybook/csf": "npm:0.1.10--canary.d841bb4.0" "@storybook/global": "npm:^5.0.0" "@storybook/icons": "npm:^1.2.5" "@storybook/router": "workspace:*" @@ -6565,7 +6565,7 @@ __metadata: "@storybook/client-logger": "workspace:*" "@storybook/core-common": "workspace:*" "@storybook/core-events": "workspace:*" - "@storybook/csf": "npm:^0.1.8" + "@storybook/csf": "npm:0.1.10--canary.d841bb4.0" "@storybook/global": "npm:^5.0.0" "@storybook/types": "workspace:*" "@types/qs": "npm:^6.9.5" @@ -6752,7 +6752,7 @@ __metadata: "@storybook/core-events": "workspace:*" "@storybook/core-server": "workspace:*" "@storybook/core-webpack": "workspace:*" - "@storybook/csf": "npm:^0.1.8" + "@storybook/csf": "npm:0.1.10--canary.d841bb4.0" "@storybook/csf-plugin": "workspace:*" "@storybook/csf-tools": "workspace:*" "@storybook/docs-tools": "workspace:*" @@ -6904,7 +6904,7 @@ __metadata: version: 0.0.0-use.local resolution: "@storybook/server@workspace:renderers/server" dependencies: - "@storybook/csf": "npm:^0.1.8" + "@storybook/csf": "npm:0.1.10--canary.d841bb4.0" "@storybook/csf-tools": "workspace:*" "@storybook/global": "npm:^5.0.0" "@storybook/preview-api": "workspace:*" @@ -6921,7 +6921,7 @@ __metadata: version: 0.0.0-use.local resolution: "@storybook/source-loader@workspace:lib/source-loader" dependencies: - "@storybook/csf": "npm:^0.1.8" + "@storybook/csf": "npm:0.1.10--canary.d841bb4.0" "@storybook/types": "workspace:*" estraverse: "npm:^5.2.0" lodash: "npm:^4.17.21" @@ -7091,7 +7091,7 @@ __metadata: resolution: "@storybook/types@workspace:lib/types" dependencies: "@storybook/channels": "workspace:*" - "@storybook/csf": "npm:^0.1.8" + "@storybook/csf": "npm:0.1.10--canary.d841bb4.0" "@types/express": "npm:^4.17.21" "@types/fs-extra": "npm:^11.0.1" "@types/node": "npm:^18.0.0" From f5d83841351ecff0be3d8944a5187e8d4466ac7f Mon Sep 17 00:00:00 2001 From: Kasper Peulen Date: Wed, 26 Jun 2024 10:05:29 +0200 Subject: [PATCH 02/20] Implement context consolidation for portable stories --- .../src/modules/store/csf/portable-stories.ts | 25 ++- .../modules/store/csf/prepareStory.test.ts | 153 ++++++++++++------ code/lib/types/src/modules/addons.ts | 4 +- code/lib/types/src/modules/composedStory.ts | 12 +- 4 files changed, 116 insertions(+), 78 deletions(-) diff --git a/code/lib/preview-api/src/modules/store/csf/portable-stories.ts b/code/lib/preview-api/src/modules/store/csf/portable-stories.ts index 75e8d16cce33..bb69130559dc 100644 --- a/code/lib/preview-api/src/modules/store/csf/portable-stories.ts +++ b/code/lib/preview-api/src/modules/store/csf/portable-stories.ts @@ -8,14 +8,12 @@ import type { ComponentAnnotations, LegacyStoryAnnotationsOrFn, NamedOrDefaultProjectAnnotations, - ComposedStoryPlayFn, ComposeStoryFn, Store_CSFExports, StoryContext, Parameters, ComposedStoryFn, StrictArgTypes, - PlayFunctionContext, ProjectAnnotations, } from '@storybook/types'; @@ -104,18 +102,20 @@ export function composeStory story.runStep(label, play, context), + canvasElement: globalThis?.document?.body, + context: null!, ...story, }; + context.context = context; + const playFunction = story.playFunction - ? async (extraContext: Partial>) => - story.playFunction!({ - ...context, - ...extraContext, - canvasElement: extraContext?.canvasElement ?? globalThis.document?.body, - }) + ? async (extraContext?: Partial>>) => { + Object.assign(context, extraContext); + return story.playFunction!(context); + } : undefined; let previousCleanupsDone = false; @@ -159,8 +159,7 @@ export function composeStory, parameters: story.parameters as Parameters, argTypes: story.argTypes as StrictArgTypes, - play: playFunction as ComposedStoryPlayFn | undefined, + play: playFunction, tags: story.tags, } ); 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 a574080558c5..5c3f8ca97f8a 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 @@ -1,10 +1,22 @@ -import { describe, beforeEach, it, expect, vi } from 'vitest'; +import { beforeEach, describe, expect, it, vi } from 'vitest'; import { global } from '@storybook/global'; -import type { Renderer, ArgsEnhancer, PlayFunctionContext, SBScalarType } from '@storybook/types'; +import type { + ArgsEnhancer, + NormalizedComponentAnnotations, + NormalizedStoryAnnotations, + PlayFunctionContext, + PreparedStory, + ProjectAnnotations, + Renderer, + SBScalarType, + StoryContext, +} from '@storybook/types'; import { addons, HooksContext } from '../../addons'; import { UNTARGETED } from '../args'; -import { prepareStory, prepareMeta, prepareContext } from './prepareStory'; +import { prepareMeta, prepareStory as realPrepareStory, prepareContext } from './prepareStory'; +import { composeConfigs } from './composeConfigs'; +import { normalizeProjectAnnotations } from './normalizeProjectAnnotations'; vi.mock('@storybook/global', async (importOriginal) => ({ global: { @@ -15,21 +27,42 @@ vi.mock('@storybook/global', async (importOriginal) => ({ const id = 'id'; const name = 'name'; const title = 'title'; -const render = (args: any) => {}; +const render = () => {}; const moduleExport = {}; const stringType: SBScalarType = { name: 'string' }; const numberType: SBScalarType = { name: 'number' }; const booleanType: SBScalarType = { name: 'boolean' }; +// Normalize the project annotations to mimick live behavior +export function prepareStory( + storyAnnotations: NormalizedStoryAnnotations, + componentAnnotations: NormalizedComponentAnnotations, + projectAnnotations: ProjectAnnotations +): PreparedStory { + return realPrepareStory( + storyAnnotations, + componentAnnotations, + normalizeProjectAnnotations(composeConfigs([projectAnnotations])) + ); +} // Extra fields that must be added to the story context after enhancers -const storyContextExtras = () => ({ - hooks: new HooksContext(), - viewMode: 'story' as const, - loaded: {}, - abortSignal: new AbortController().signal, - canvasElement: {}, -}); +const addExtraContext = ( + context: PreparedStory & Pick +): StoryContext => { + const extraContext = { + ...context, + hooks: new HooksContext(), + viewMode: 'story' as const, + loaded: {}, + abortSignal: new AbortController().signal, + canvasElement: {}, + step: vi.fn(), + context: undefined! as StoryContext, + }; + extraContext.context = extraContext; + return extraContext; +}; describe('prepareStory', () => { describe('tags', () => { @@ -93,7 +126,7 @@ describe('prepareStory', () => { ); expect(parameters).toEqual({ - __isArgsStory: true, + __isArgsStory: false, a: 'story', b: { name: 'component' }, c: { name: 'global' }, @@ -102,7 +135,11 @@ describe('prepareStory', () => { }); it('sets a value even if annotations do not have parameters', () => { - const { parameters } = prepareStory({ id, name, moduleExport }, { id, title }, { render }); + const { parameters } = prepareStory( + { id, name, moduleExport }, + { id, title }, + { render: (args: any) => {} } + ); expect(parameters).toEqual({ __isArgsStory: true }); }); @@ -322,6 +359,7 @@ describe('prepareStory', () => { }); describe('applyLoaders', () => { + const abortSignal = new AbortController().signal; it('awaits the result of a loader', async () => { const loader = vi.fn(async () => new Promise((r) => setTimeout(() => r({ foo: 7 }), 100))); const { applyLoaders } = prepareStory( @@ -330,14 +368,15 @@ describe('prepareStory', () => { { render } ); - const storyContext = { context: 'value' } as any; - const loadedContext = await applyLoaders({ ...storyContext }); + const storyContext = { abortSignal } as StoryContext; + const loaded = await applyLoaders(storyContext); - expect(loader).toHaveBeenCalledWith({ ...storyContext, loaded: {} }); - expect(loadedContext).toEqual({ - context: 'value', - loaded: { foo: 7 }, - }); + expect(loader).toHaveBeenCalledWith(storyContext); + expect(loaded).toMatchInlineSnapshot(` + { + "foo": 7, + } + `); }); it('loaders are composed in the right order', async () => { @@ -351,13 +390,13 @@ describe('prepareStory', () => { { render, loaders: [globalLoader] } ); - const storyContext = { context: 'value' } as any; - const loadedContext = await applyLoaders(storyContext); - - expect(loadedContext).toEqual({ - context: 'value', - loaded: { foo: 5, bar: 3, baz: 1 }, - }); + expect(await applyLoaders({ abortSignal } as StoryContext)).toMatchInlineSnapshot(` + { + "bar": 3, + "baz": 1, + "foo": 5, + } + `); }); it('later loaders override earlier loaders', async () => { @@ -372,13 +411,12 @@ describe('prepareStory', () => { { render } ); - const storyContext = { context: 'value' } as any; - const loadedContext = await applyLoaders(storyContext); - - expect(loadedContext).toEqual({ - context: 'value', - loaded: { foo: 3 }, - }); + expect(await applyLoaders({ abortSignal: abortSignal } as StoryContext)) + .toMatchInlineSnapshot(` + { + "foo": 3, + } + `); }); }); @@ -401,7 +439,7 @@ describe('prepareStory', () => { ); const context = prepareContext({ args: story.initialArgs, globals: {}, ...story }); - story.undecoratedStoryFn({ ...context, ...storyContextExtras() }); + story.undecoratedStoryFn(addExtraContext(context)); expect(renderMock).toHaveBeenCalledWith( { one: 'mapped', two: 2, three: 3 }, expect.objectContaining({ args: { one: 'mapped', two: 2, three: 3 } }) @@ -473,7 +511,7 @@ describe('prepareStory', () => { const hooks = new HooksContext(); const context = prepareContext({ args: story.initialArgs, globals: {}, ...story }); - story.unboundStoryFn({ ...context, ...storyContextExtras(), hooks }); + story.unboundStoryFn(addExtraContext(context)); expect(ctx1).toMatchObject({ unmappedArgs: { one: 1 }, args: { one: 'mapped-1' } }); expect(ctx2).toMatchObject({ unmappedArgs: { one: 1 }, args: { one: 'mapped-1' } }); @@ -548,7 +586,7 @@ describe('prepareStory', () => { ); const context = prepareContext({ args: firstStory.initialArgs, globals: {}, ...firstStory }); - firstStory.unboundStoryFn({ ...context, ...storyContextExtras() }); + firstStory.unboundStoryFn(addExtraContext(context)); expect(renderMock).toHaveBeenCalledWith( { a: 1 }, expect.objectContaining({ args: { a: 1 }, allArgs: { a: 1, b: 2 } }) @@ -570,7 +608,7 @@ describe('prepareStory', () => { ); const context = prepareContext({ args: firstStory.initialArgs, globals: {}, ...firstStory }); - firstStory.unboundStoryFn({ ...context, ...storyContextExtras() }); + firstStory.unboundStoryFn(addExtraContext(context)); expect(renderMock).toHaveBeenCalledWith( { a: 1 }, expect.objectContaining({ args: { a: 1 }, allArgs: { a: 1, b: 2 } }) @@ -592,7 +630,7 @@ describe('prepareStory', () => { ); const context = prepareContext({ args: firstStory.initialArgs, globals: {}, ...firstStory }); - firstStory.unboundStoryFn({ ...context, ...storyContextExtras() }); + firstStory.unboundStoryFn(addExtraContext(context)); expect(renderMock).toHaveBeenCalledWith( { a: 1 }, expect.objectContaining({ argsByTarget: { [UNTARGETED]: { a: 1 }, foo: { b: 2 } } }) @@ -614,7 +652,7 @@ describe('prepareStory', () => { ); const context = prepareContext({ args: firstStory.initialArgs, globals: {}, ...firstStory }); - firstStory.unboundStoryFn({ ...context, ...storyContextExtras() }); + firstStory.unboundStoryFn(addExtraContext(context)); expect(renderMock).toHaveBeenCalledWith( {}, expect.objectContaining({ argsByTarget: { foo: { b: 2 } } }) @@ -634,7 +672,7 @@ describe('prepareStory', () => { ); const context = prepareContext({ args: firstStory.initialArgs, globals: {}, ...firstStory }); - firstStory.unboundStoryFn({ ...context, ...storyContextExtras() }); + firstStory.unboundStoryFn(addExtraContext(context)); expect(renderMock).toHaveBeenCalledWith({}, expect.objectContaining({ argsByTarget: {} })); }); }); @@ -667,16 +705,19 @@ describe('playFunction', () => { await step('label', stepPlay); }); const runStep = vi.fn((label, p, c) => p(c)); - const { playFunction } = prepareStory( + const { playFunction, runStep: preparedRunStep } = prepareStory( { id, name, play, moduleExport }, { id, title }, { render, runStep } ); - await playFunction!({} as PlayFunctionContext); + const context: Partial = { + step: (label, playFn) => preparedRunStep(label, playFn, context as StoryContext), + }; + await playFunction!(context as PlayFunctionContext); expect(play).toHaveBeenCalled(); expect(stepPlay).toHaveBeenCalled(); - expect(runStep).toBeCalledWith('label', stepPlay, expect.any(Object)); + expect(runStep).toBeCalledWith('label', expect.any(Function), expect.any(Object)); }); }); @@ -689,7 +730,7 @@ describe('moduleExport', () => { }); describe('prepareMeta', () => { - it('returns the same as prepareStory', () => { + it('returns a similar object as prepareStory', () => { const meta = { id, title, @@ -710,8 +751,6 @@ describe('prepareMeta', () => { nested: { name: 'nested', type: booleanType, a: 'story' }, }, }; - const preparedStory = prepareStory({ id, name, moduleExport }, meta, { render }); - const preparedMeta = prepareMeta(meta, { render }, {}); // omitting the properties from preparedStory that are not in preparedMeta const { @@ -723,12 +762,20 @@ describe('prepareMeta', () => { unboundStoryFn, undecoratedStoryFn, playFunction, - // eslint-disable-next-line @typescript-eslint/naming-convention - parameters: { __isArgsStory, ...parameters }, - ...expectedPreparedMeta - } = preparedStory; + runStep, + ...preparedStory + } = prepareStory({ id, name, moduleExport }, meta, { render }); + + const { ...preparedMeta } = prepareMeta( + meta, + normalizeProjectAnnotations(composeConfigs([{ render }])), + {} + ); + + // prepareMeta doesn't explicitly set this parameter to false + // eslint-disable-next-line no-underscore-dangle + preparedMeta.parameters.__isArgsStory = false; - expect(preparedMeta).toMatchObject({ ...expectedPreparedMeta, parameters }); - expect(Object.keys(preparedMeta)).toHaveLength(Object.keys(expectedPreparedMeta).length + 1); + expect(preparedMeta).toEqual(preparedStory); }); }); diff --git a/code/lib/types/src/modules/addons.ts b/code/lib/types/src/modules/addons.ts index c1325e3f07ec..90011c5f1eec 100644 --- a/code/lib/types/src/modules/addons.ts +++ b/code/lib/types/src/modules/addons.ts @@ -83,11 +83,11 @@ export type Addon_StoryContext = StoryContextForFramework; export type Addon_StoryContextUpdate = Partial; -type Addon_ReturnTypeFramework = { +interface Addon_ReturnTypeFramework extends Renderer { component: any; storyResult: ReturnType; canvasElement: any; -}; +} export type Addon_PartialStoryFn = PartialStoryFnForFramework< Addon_ReturnTypeFramework >; diff --git a/code/lib/types/src/modules/composedStory.ts b/code/lib/types/src/modules/composedStory.ts index f4b49f24793a..b1cd46589c3a 100644 --- a/code/lib/types/src/modules/composedStory.ts +++ b/code/lib/types/src/modules/composedStory.ts @@ -1,9 +1,9 @@ /* eslint-disable @typescript-eslint/naming-convention */ import type { - PlayFunction, ProjectAnnotations, Renderer, + StoryContext, StoryId, StrictArgTypes, Tag, @@ -35,14 +35,6 @@ export type PartialArgsStoryFn = T extends (...args: infer P) => infer R - ? (...args: { [K in keyof P]?: Partial }) => R - : never; - -export type ComposedStoryPlayFn< - TRenderer extends Renderer = Renderer, - TArgs = Args, -> = MakeAllParametersOptional>>; /** * A story that got recomposed for portable stories, containing all the necessary data to be rendered in external environments */ @@ -52,7 +44,7 @@ export type ComposedStoryFn< > = PartialArgsStoryFn & { args: TArgs; id: StoryId; - play?: ComposedStoryPlayFn; + play?: (context?: Partial>>) => Promise; load: () => Promise; storyName: string; parameters: Parameters; From ba61879be536c786d56d288efbb52bae679d7534 Mon Sep 17 00:00:00 2001 From: Kasper Peulen Date: Wed, 26 Jun 2024 11:06:37 +0200 Subject: [PATCH 03/20] Add TODO for normalizeProjectAnnotations.ts --- code/addons/interactions/src/preview.ts | 10 +++++++--- .../modules/store/csf/normalizeProjectAnnotations.ts | 4 +++- .../src/modules/store/csf/prepareStory.test.ts | 5 ++--- .../src/modules/store/csf/stepRunners.test.ts | 10 +++++----- 4 files changed, 17 insertions(+), 12 deletions(-) diff --git a/code/addons/interactions/src/preview.ts b/code/addons/interactions/src/preview.ts index 5ef2520b6b71..aff3b3c59c50 100644 --- a/code/addons/interactions/src/preview.ts +++ b/code/addons/interactions/src/preview.ts @@ -1,10 +1,14 @@ -import type { PlayFunction, PlayFunctionContext, StepLabel } from '@storybook/types'; +import type { PlayFunction, StepLabel, StoryContext } from '@storybook/types'; import { instrument } from '@storybook/instrumenter'; export const { step: runStep } = instrument( { - step: (label: StepLabel, play: PlayFunction, context: PlayFunctionContext) => - play(context), + // It seems like the label is unused, but the instrumenter has access to it + // The context will be bounded later in StoryRender, so that the user can write just: + // await step("label", (context) => { + // // labeled step + // }); + step: (label: StepLabel, play: PlayFunction, context: StoryContext) => play(context), }, { intercept: true } ); 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 52ce9f31ab22..a741275746cf 100644 --- a/code/lib/preview-api/src/modules/store/csf/normalizeProjectAnnotations.ts +++ b/code/lib/preview-api/src/modules/store/csf/normalizeProjectAnnotations.ts @@ -13,6 +13,8 @@ import { normalizeInputTypes } from './normalizeInputTypes'; import { normalizeArrays } from './normalizeArrays'; import { combineParameters } from '../parameters'; +// TODO(kasperpeulen) Consolidate this function with composeConfigs +// As composeConfigs is the real normalizer, and always run before normalizeProjectAnnotations export function normalizeProjectAnnotations({ argTypes, globalTypes, @@ -48,6 +50,6 @@ export function normalizeProjectAnnotations({ inferControls, ], initialGlobals: combineParameters(initialGlobals, globals), - ...annotations, + ...(annotations as NormalizedProjectAnnotations), }; } 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 5c3f8ca97f8a..e2bb5550af62 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 @@ -4,7 +4,6 @@ import type { ArgsEnhancer, NormalizedComponentAnnotations, NormalizedStoryAnnotations, - PlayFunctionContext, PreparedStory, ProjectAnnotations, Renderer, @@ -691,7 +690,7 @@ describe('playFunction', () => { { render } ); - await playFunction!({} as PlayFunctionContext); + await playFunction?.({} as StoryContext); expect(play).toHaveBeenCalled(); expect(inner).toHaveBeenCalled(); }); @@ -714,7 +713,7 @@ describe('playFunction', () => { const context: Partial = { step: (label, playFn) => preparedRunStep(label, playFn, context as StoryContext), }; - await playFunction!(context as PlayFunctionContext); + await playFunction?.(context as StoryContext); expect(play).toHaveBeenCalled(); expect(stepPlay).toHaveBeenCalled(); expect(runStep).toBeCalledWith('label', expect.any(Function), expect.any(Object)); diff --git a/code/lib/preview-api/src/modules/store/csf/stepRunners.test.ts b/code/lib/preview-api/src/modules/store/csf/stepRunners.test.ts index 4858cfa7e995..e60976c2ac3f 100644 --- a/code/lib/preview-api/src/modules/store/csf/stepRunners.test.ts +++ b/code/lib/preview-api/src/modules/store/csf/stepRunners.test.ts @@ -1,5 +1,5 @@ import { describe, it, expect, vi } from 'vitest'; -import type { PlayFunctionContext, StepRunner } from '@storybook/types'; +import type { StoryContext, StepRunner } from '@storybook/types'; import { composeStepRunners } from './stepRunners'; describe('stepRunners', () => { @@ -21,10 +21,10 @@ describe('stepRunners', () => { const composed = composeStepRunners([firstStepRunner, secondStepRunner]); const playFnA = vi.fn(); - const playContextA = {} as PlayFunctionContext; + const playContextA = {} as StoryContext; await composed('a', playFnA, playContextA); const playFnB = vi.fn(); - const playContextB = {} as PlayFunctionContext; + const playContextB = {} as StoryContext; await composed('b', playFnB, playContextB); expect(playFnA).toHaveBeenCalledTimes(1); @@ -47,10 +47,10 @@ describe('stepRunners', () => { const composed = composeStepRunners([]); const playFnA = vi.fn(); - const playContextA = {} as PlayFunctionContext; + const playContextA = {} as StoryContext; await composed('a', playFnA, playContextA); const playFnB = vi.fn(); - const playContextB = {} as PlayFunctionContext; + const playContextB = {} as StoryContext; await composed('b', playFnB, playContextB); expect(playFnA).toHaveBeenCalledTimes(1); From 054f661b124965acdce09a5a03d1f375b153fdc3 Mon Sep 17 00:00:00 2001 From: Kasper Peulen Date: Wed, 26 Jun 2024 12:10:39 +0200 Subject: [PATCH 04/20] Add extra tests --- .../preview-web/PreviewWeb.mockdata.ts | 15 +++++++++++-- .../modules/preview-web/PreviewWeb.test.ts | 2 +- .../src/modules/store/StoryStore.test.ts | 18 ++++++++++------ .../modules/store/csf/prepareStory.test.ts | 2 +- .../template/stories/before-each.stories.ts | 21 +++++++++++++++++++ 5 files changed, 48 insertions(+), 10 deletions(-) diff --git a/code/lib/preview-api/src/modules/preview-web/PreviewWeb.mockdata.ts b/code/lib/preview-api/src/modules/preview-web/PreviewWeb.mockdata.ts index ded5833ed667..202e17f291ef 100644 --- a/code/lib/preview-api/src/modules/preview-web/PreviewWeb.mockdata.ts +++ b/code/lib/preview-api/src/modules/preview-web/PreviewWeb.mockdata.ts @@ -11,8 +11,15 @@ import { STORY_THREW_EXCEPTION, } from '@storybook/core-events'; -import type { ModuleImportFn, StoryIndex, TeardownRenderToCanvas } from '@storybook/types'; +import type { + ModuleImportFn, + ProjectAnnotations, + Renderer, + StoryIndex, + TeardownRenderToCanvas, +} from '@storybook/types'; import type { RenderPhase } from './render/StoryRender'; +import { composeConfigs } from '../store'; export const componentOneExports = { default: { @@ -65,7 +72,7 @@ export const docsRenderer = { unmount: vi.fn(), }; export const teardownrenderToCanvas: Mock<[TeardownRenderToCanvas]> = vi.fn(); -export const projectAnnotations = { +const rawProjectAnnotations = { initialGlobals: { a: 'b' }, globalTypes: {}, decorators: [vi.fn((s) => s())], @@ -73,6 +80,10 @@ export const projectAnnotations = { renderToCanvas: vi.fn().mockReturnValue(teardownrenderToCanvas), parameters: { docs: { renderer: () => docsRenderer } }, }; +export const projectAnnotations = composeConfigs([ + rawProjectAnnotations, +]) as ProjectAnnotations & typeof rawProjectAnnotations; + export const getProjectAnnotations = vi.fn(() => projectAnnotations as any); export const storyIndex: StoryIndex = { diff --git a/code/lib/preview-api/src/modules/preview-web/PreviewWeb.test.ts b/code/lib/preview-api/src/modules/preview-web/PreviewWeb.test.ts index 8d4a37436bdf..0c4650a37b9a 100644 --- a/code/lib/preview-api/src/modules/preview-web/PreviewWeb.test.ts +++ b/code/lib/preview-api/src/modules/preview-web/PreviewWeb.test.ts @@ -949,7 +949,7 @@ describe('PreviewWeb', () => { forceRemount: true, storyContext: expect.objectContaining({ loaded: { l: 8 }, // This is the value returned by the *first* loader call - args: { foo: 'a', new: 'arg', one: 'mapped-1' }, + args: { foo: 'a', one: 'mapped-1' }, }), }), 'story-element' diff --git a/code/lib/preview-api/src/modules/store/StoryStore.test.ts b/code/lib/preview-api/src/modules/store/StoryStore.test.ts index 51d7664b468d..b508cca90d3b 100644 --- a/code/lib/preview-api/src/modules/store/StoryStore.test.ts +++ b/code/lib/preview-api/src/modules/store/StoryStore.test.ts @@ -5,6 +5,7 @@ import { prepareStory } from './csf/prepareStory'; import { processCSFFile } from './csf/processCSFFile'; import { StoryStore } from './StoryStore'; import type { HooksContext } from './hooks'; +import { composeConfigs } from './csf/composeConfigs'; // Spy on prepareStory/processCSFFile vi.mock('./csf/prepareStory', async (importOriginal) => { @@ -41,12 +42,14 @@ const importFn = vi.fn(async (path) => { return path === './src/ComponentOne.stories.js' ? componentOneExports : componentTwoExports; }); -const projectAnnotations: ProjectAnnotations = { - globals: { a: 'b' }, - globalTypes: { a: { type: 'string' } }, - argTypes: { a: { type: 'string' } }, - render: vi.fn(), -}; +const projectAnnotations: ProjectAnnotations = composeConfigs([ + { + globals: { a: 'b' }, + globalTypes: { a: { type: 'string' } }, + argTypes: { a: { type: 'string' } }, + render: vi.fn(), + }, +]); const storyIndex: StoryIndex = { v: 5, @@ -660,6 +663,7 @@ describe('StoryStore', () => { "fileName": "./src/ComponentOne.stories.js", }, "playFunction": undefined, + "runStep": [Function], "story": "A", "storyFn": [Function], "subcomponents": undefined, @@ -707,6 +711,7 @@ describe('StoryStore', () => { "fileName": "./src/ComponentOne.stories.js", }, "playFunction": undefined, + "runStep": [Function], "story": "B", "storyFn": [Function], "subcomponents": undefined, @@ -754,6 +759,7 @@ describe('StoryStore', () => { "fileName": "./src/ComponentTwo.stories.js", }, "playFunction": undefined, + "runStep": [Function], "story": "C", "storyFn": [Function], "subcomponents": undefined, 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 e2bb5550af62..7818d2bd4752 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 @@ -765,7 +765,7 @@ describe('prepareMeta', () => { ...preparedStory } = prepareStory({ id, name, moduleExport }, meta, { render }); - const { ...preparedMeta } = prepareMeta( + const preparedMeta = prepareMeta( meta, normalizeProjectAnnotations(composeConfigs([{ render }])), {} diff --git a/code/lib/test/template/stories/before-each.stories.ts b/code/lib/test/template/stories/before-each.stories.ts index a6f613e7698c..da392737103c 100644 --- a/code/lib/test/template/stories/before-each.stories.ts +++ b/code/lib/test/template/stories/before-each.stories.ts @@ -1,3 +1,4 @@ +/* eslint-disable @typescript-eslint/naming-convention,storybook/prefer-pascal-case */ import { expect, mocked, getByRole, spyOn, userEvent } from '@storybook/test'; const meta = { @@ -42,3 +43,23 @@ export const BeforeEachOrder = { ]); }, }; + +export const before_each_and_loaders_can_extend_context = { + parameters: { chromatic: { disable: true } }, + loaders(context) { + context.foo = ['bar']; + }, + beforeEach(context) { + context.foo = [...context.foo, 'baz']; + }, + async play({ foo }) { + await expect(foo).toEqual(['bar', 'baz']); + }, +}; + +export const context_prop_is_available = { + parameters: { chromatic: { disable: true } }, + async play({ context, canvasElement }) { + await expect(context.canvasElement).toEqual(canvasElement); + }, +}; From 156966b51db64214b36c328d670f9a79861db7b9 Mon Sep 17 00:00:00 2001 From: Kasper Peulen Date: Wed, 26 Jun 2024 12:50:08 +0200 Subject: [PATCH 05/20] Update docs --- .../my-component-play-function-composition.md | 26 +++++++++---------- 1 file changed, 13 insertions(+), 13 deletions(-) diff --git a/docs/_snippets/my-component-play-function-composition.md b/docs/_snippets/my-component-play-function-composition.md index 6052d455a55d..d5f69d9d864a 100644 --- a/docs/_snippets/my-component-play-function-composition.md +++ b/docs/_snippets/my-component-play-function-composition.md @@ -33,12 +33,12 @@ export const SecondStory: Story = { }; export const CombinedStories: Story = { - play: async ({ canvasElement }) => { + play: async ({ context, canvasElement }) => { const canvas = within(canvasElement); // Runs the FirstStory and Second story play function before running this story's play function - await FirstStory.play({ canvasElement }); - await SecondStory.play({ canvasElement }); + await FirstStory.play(context); + await SecondStory.play(context); await userEvent.type(canvas.getByTestId('another-element'), 'random value'); }, }; @@ -74,8 +74,8 @@ export const SecondStory = { }; export const CombinedStories = { - play: async (context) => { - const canvas = within(context.canvasElement); + play: async ({ context, canvasElement }) => { + const canvas = within(canvasElement); // Runs the FirstStory and Second story play function before running this story's play function await FirstStory.play(context); @@ -121,12 +121,12 @@ export const SecondStory: Story = { }; export const CombinedStories: Story = { - play: async ({ canvasElement }) => { + play: async ({ context, canvasElement }) => { const canvas = within(canvasElement); // Runs the FirstStory and Second story play function before running this story's play function - await FirstStory.play({ canvasElement }); - await SecondStory.play({ canvasElement }); + await FirstStory.play(context); + await SecondStory.play(context); await userEvent.type(canvas.getByTestId('another-element'), 'random value'); }, }; @@ -168,7 +168,7 @@ export const SecondStory: Story = { }; export const CombinedStories: Story = { - play: async (context) => { + play: async ({ context, canvasElement }) => { const canvas = within(context.canvasElement); // Runs the FirstStory and Second story play function before running this story's play function @@ -207,8 +207,8 @@ export const SecondStory = { }; export const CombinedStories = { - play: async (context) => { - const canvas = within(context.canvasElement); + play: async ({ context, canvasElement }) => { + const canvas = within(canvasElement); // Runs the FirstStory and Second story play function before running this story's play function await FirstStory.play(context); @@ -250,8 +250,8 @@ export const SecondStory: Story = { }; export const CombinedStories: Story = { - play: async (context) => { - const canvas = within(context.canvasElement); + play: async ({ context, canvasElement }) => { + const canvas = within(canvasElement); // Runs the FirstStory and Second story play function before running this story's play function await FirstStory.play(context); From b765c989fe6938a4897dd1e8b0f56dece2e2fe00 Mon Sep 17 00:00:00 2001 From: Kasper Peulen Date: Wed, 26 Jun 2024 12:52:43 +0200 Subject: [PATCH 06/20] Update docs --- docs/_snippets/my-component-play-function-composition.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/_snippets/my-component-play-function-composition.md b/docs/_snippets/my-component-play-function-composition.md index d5f69d9d864a..e3239b113329 100644 --- a/docs/_snippets/my-component-play-function-composition.md +++ b/docs/_snippets/my-component-play-function-composition.md @@ -169,7 +169,7 @@ export const SecondStory: Story = { export const CombinedStories: Story = { play: async ({ context, canvasElement }) => { - const canvas = within(context.canvasElement); + const canvas = within(canvasElement); // Runs the FirstStory and Second story play function before running this story's play function await FirstStory.play(context); From fd11b1ac4eaa0e317c210e16c1f230b7033ab17e Mon Sep 17 00:00:00 2001 From: Kasper Peulen Date: Wed, 26 Jun 2024 13:01:47 +0200 Subject: [PATCH 07/20] add test step_and_canvas_element_can_be_used_in_loaders_and_before_each --- .../test/template/stories/before-each.stories.ts | 14 ++++++++++++++ 1 file changed, 14 insertions(+) diff --git a/code/lib/test/template/stories/before-each.stories.ts b/code/lib/test/template/stories/before-each.stories.ts index da392737103c..67a5c73a9fde 100644 --- a/code/lib/test/template/stories/before-each.stories.ts +++ b/code/lib/test/template/stories/before-each.stories.ts @@ -63,3 +63,17 @@ export const context_prop_is_available = { await expect(context.canvasElement).toEqual(canvasElement); }, }; + +export const step_and_canvas_element_can_be_used_in_loaders_and_before_each = { + parameters: { chromatic: { disable: true } }, + loaders({ step, canvasElement }) { + step('loaders', async () => { + await expect(canvasElement).toBeInTheDocument(); + }); + }, + beforeEach({ step, canvasElement }) { + step('before each', async () => { + await expect(canvasElement).toBeInTheDocument(); + }); + }, +}; From 1332415334fd925dfeeaa4ccdaf82d498d68862f Mon Sep 17 00:00:00 2001 From: Kasper Peulen Date: Wed, 26 Jun 2024 15:08:09 +0200 Subject: [PATCH 08/20] Add tests for circular references in instrumenter --- .../lib/instrumenter/src/instrumenter.test.ts | 109 ++++++++++++++++++ code/lib/instrumenter/src/instrumenter.ts | 4 +- 2 files changed, 111 insertions(+), 2 deletions(-) diff --git a/code/lib/instrumenter/src/instrumenter.test.ts b/code/lib/instrumenter/src/instrumenter.test.ts index bbd01e5f064f..c15391d35eb1 100644 --- a/code/lib/instrumenter/src/instrumenter.test.ts +++ b/code/lib/instrumenter/src/instrumenter.test.ts @@ -175,6 +175,115 @@ describe('Instrumenter', () => { ); }); + it('handles circular references', () => { + const { fn } = instrument({ fn: (...args: any) => {} }); + const obj = { key: 'value', obj: {}, array: [] as any[] }; + obj.obj = obj; + obj.array = [obj]; + + expect(() => fn(obj)).not.toThrow(); + + expect(callSpy.mock.calls[0][0].args).toMatchInlineSnapshot(` + [ + { + "array": [ + { + "array": [ + { + "array": "[Circular]", + "key": "value", + "obj": { + "array": "[Circular]", + "key": "value", + "obj": "[Circular]", + }, + }, + ], + "key": "value", + "obj": { + "array": [ + { + "array": "[Circular]", + "key": "value", + "obj": "[Circular]", + }, + ], + "key": "value", + "obj": { + "array": "[Circular]", + "key": "value", + "obj": { + "array": "[Circular]", + "key": "value", + "obj": "[Circular]", + }, + }, + }, + }, + ], + "key": "value", + "obj": { + "array": [ + { + "array": [ + { + "array": "[Circular]", + "key": "value", + "obj": "[Circular]", + }, + ], + "key": "value", + "obj": { + "array": "[Circular]", + "key": "value", + "obj": { + "array": "[Circular]", + "key": "value", + "obj": "[Circular]", + }, + }, + }, + ], + "key": "value", + "obj": { + "array": [ + { + "array": "[Circular]", + "key": "value", + "obj": { + "array": "[Circular]", + "key": "value", + "obj": "[Circular]", + }, + }, + ], + "key": "value", + "obj": { + "array": [ + { + "array": "[Circular]", + "key": "value", + "obj": "[Circular]", + }, + ], + "key": "value", + "obj": { + "array": "[Circular]", + "key": "value", + "obj": { + "array": "[Circular]", + "key": "value", + "obj": "[Circular]", + }, + }, + }, + }, + }, + }, + ] + `); + }); + it('provides metadata about the call in the event', () => { const { obj } = instrument({ obj: { fn: () => {} } }); obj.fn(); diff --git a/code/lib/instrumenter/src/instrumenter.ts b/code/lib/instrumenter/src/instrumenter.ts index 5170cb2483a3..3b6d6100107f 100644 --- a/code/lib/instrumenter/src/instrumenter.ts +++ b/code/lib/instrumenter/src/instrumenter.ts @@ -418,7 +418,7 @@ export class Instrumenter { return callRefsByResult.get(value); } if (value instanceof Array) { - if (depth > 10) return []; + if (depth > 10) return '[Circular]'; return value.map((it) => serializeValues(it, ++depth)); } if (value instanceof Date) { @@ -453,7 +453,7 @@ export class Instrumenter { return { __class__: { name: value.constructor.name } }; } if (Object.prototype.toString.call(value) === '[object Object]') { - if (depth > 10) return {}; + if (depth > 10) return '[Circular]'; return Object.fromEntries( Object.entries(value).map(([key, val]) => [key, serializeValues(val, ++depth)]) ); From b7e974ccc2d418a83253eae0509480f824faef1b Mon Sep 17 00:00:00 2001 From: Kasper Peulen Date: Wed, 26 Jun 2024 15:18:47 +0200 Subject: [PATCH 09/20] extract maximum depth --- code/lib/instrumenter/src/instrumenter.ts | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/code/lib/instrumenter/src/instrumenter.ts b/code/lib/instrumenter/src/instrumenter.ts index 3b6d6100107f..e2f3b77e8334 100644 --- a/code/lib/instrumenter/src/instrumenter.ts +++ b/code/lib/instrumenter/src/instrumenter.ts @@ -411,6 +411,7 @@ export class Instrumenter { invoke(fn: Function, object: Record, call: Call, options: Options) { const { callRefsByResult, renderPhase } = this.getState(call.storyId); + const maximumDepth = 10; // Map complex values to a JSON-serializable representation. // We use a depth, to avoid infinite recursion of self referencing values. const serializeValues = (value: any, depth = 0): any => { @@ -418,7 +419,7 @@ export class Instrumenter { return callRefsByResult.get(value); } if (value instanceof Array) { - if (depth > 10) return '[Circular]'; + if (depth > maximumDepth) return '[Circular]'; return value.map((it) => serializeValues(it, ++depth)); } if (value instanceof Date) { @@ -453,7 +454,7 @@ export class Instrumenter { return { __class__: { name: value.constructor.name } }; } if (Object.prototype.toString.call(value) === '[object Object]') { - if (depth > 10) return '[Circular]'; + if (depth > maximumDepth) return '[Circular]'; return Object.fromEntries( Object.entries(value).map(([key, val]) => [key, serializeValues(val, ++depth)]) ); From 99bc89cf0ba8c7ca1c80f298d11ece63a38a43e3 Mon Sep 17 00:00:00 2001 From: Kasper Peulen Date: Thu, 27 Jun 2024 09:32:59 +0200 Subject: [PATCH 10/20] Improve comment --- code/lib/preview-api/src/modules/preview-web/PreviewWeb.test.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/code/lib/preview-api/src/modules/preview-web/PreviewWeb.test.ts b/code/lib/preview-api/src/modules/preview-web/PreviewWeb.test.ts index 0c4650a37b9a..830450dd6a23 100644 --- a/code/lib/preview-api/src/modules/preview-web/PreviewWeb.test.ts +++ b/code/lib/preview-api/src/modules/preview-web/PreviewWeb.test.ts @@ -942,7 +942,7 @@ describe('PreviewWeb', () => { openBlockLoadersGate({ l: 8 }); await waitForRender(); - // Assert - renderToCanvas to be called the first time with initial args + // Assert - renderToCanvas to be called the first time with initial args and returned `loaded` value. expect(projectAnnotations.renderToCanvas).toHaveBeenCalledOnce(); expect(projectAnnotations.renderToCanvas).toHaveBeenCalledWith( expect.objectContaining({ From 2fa896ad784166effafca998199e2b5cef8b79b4 Mon Sep 17 00:00:00 2001 From: Kasper Peulen Date: Thu, 27 Jun 2024 10:44:38 +0200 Subject: [PATCH 11/20] Add TODO comments --- code/lib/instrumenter/src/instrumenter.ts | 6 ++++-- .../src/modules/store/csf/normalizeProjectAnnotations.ts | 2 ++ 2 files changed, 6 insertions(+), 2 deletions(-) diff --git a/code/lib/instrumenter/src/instrumenter.ts b/code/lib/instrumenter/src/instrumenter.ts index e2f3b77e8334..6d407545a189 100644 --- a/code/lib/instrumenter/src/instrumenter.ts +++ b/code/lib/instrumenter/src/instrumenter.ts @@ -411,8 +411,10 @@ export class Instrumenter { invoke(fn: Function, object: Record, call: Call, options: Options) { const { callRefsByResult, renderPhase } = this.getState(call.storyId); - const maximumDepth = 10; - // Map complex values to a JSON-serializable representation. + // TODO This function should not needed anymore, as the channel already serializes values with telejson + // Possibly we need to add HTMLElement support to telejson though + // Keeping this function here, as removing it means we need to refactor the deserializing that happens in addon-interactions + const maximumDepth = 25; // mimicks the max depth of telejson // We use a depth, to avoid infinite recursion of self referencing values. const serializeValues = (value: any, depth = 0): any => { if (callRefsByResult.has(value)) { 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 a741275746cf..37af21e7eca6 100644 --- a/code/lib/preview-api/src/modules/store/csf/normalizeProjectAnnotations.ts +++ b/code/lib/preview-api/src/modules/store/csf/normalizeProjectAnnotations.ts @@ -15,6 +15,8 @@ import { combineParameters } from '../parameters'; // TODO(kasperpeulen) Consolidate this function with composeConfigs // As composeConfigs is the real normalizer, and always run before normalizeProjectAnnotations +// tmeasday: Alternatively we could get rid of composeConfigs and just pass ProjectAnnotations[] around -- and do the composing here. +// That makes sense to me as it avoids the need for both WP + Vite to call composeConfigs at the right time. export function normalizeProjectAnnotations({ argTypes, globalTypes, From 8948af4962dd829520e5b03fbd8a4247eae84340 Mon Sep 17 00:00:00 2001 From: Kasper Peulen Date: Thu, 27 Jun 2024 11:39:21 +0200 Subject: [PATCH 12/20] Fix test, cleanup and backward support to deprecated StoryStory.fromId --- .../lib/instrumenter/src/instrumenter.test.ts | 100 ------------------ .../modules/preview-web/render/StoryRender.ts | 4 +- .../src/modules/store/StoryStore.ts | 12 ++- 3 files changed, 9 insertions(+), 107 deletions(-) diff --git a/code/lib/instrumenter/src/instrumenter.test.ts b/code/lib/instrumenter/src/instrumenter.test.ts index c15391d35eb1..e946c74c2394 100644 --- a/code/lib/instrumenter/src/instrumenter.test.ts +++ b/code/lib/instrumenter/src/instrumenter.test.ts @@ -182,106 +182,6 @@ describe('Instrumenter', () => { obj.array = [obj]; expect(() => fn(obj)).not.toThrow(); - - expect(callSpy.mock.calls[0][0].args).toMatchInlineSnapshot(` - [ - { - "array": [ - { - "array": [ - { - "array": "[Circular]", - "key": "value", - "obj": { - "array": "[Circular]", - "key": "value", - "obj": "[Circular]", - }, - }, - ], - "key": "value", - "obj": { - "array": [ - { - "array": "[Circular]", - "key": "value", - "obj": "[Circular]", - }, - ], - "key": "value", - "obj": { - "array": "[Circular]", - "key": "value", - "obj": { - "array": "[Circular]", - "key": "value", - "obj": "[Circular]", - }, - }, - }, - }, - ], - "key": "value", - "obj": { - "array": [ - { - "array": [ - { - "array": "[Circular]", - "key": "value", - "obj": "[Circular]", - }, - ], - "key": "value", - "obj": { - "array": "[Circular]", - "key": "value", - "obj": { - "array": "[Circular]", - "key": "value", - "obj": "[Circular]", - }, - }, - }, - ], - "key": "value", - "obj": { - "array": [ - { - "array": "[Circular]", - "key": "value", - "obj": { - "array": "[Circular]", - "key": "value", - "obj": "[Circular]", - }, - }, - ], - "key": "value", - "obj": { - "array": [ - { - "array": "[Circular]", - "key": "value", - "obj": "[Circular]", - }, - ], - "key": "value", - "obj": { - "array": "[Circular]", - "key": "value", - "obj": { - "array": "[Circular]", - "key": "value", - "obj": "[Circular]", - }, - }, - }, - }, - }, - }, - ] - `); }); it('provides metadata about the call in the event', () => { diff --git a/code/lib/preview-api/src/modules/preview-web/render/StoryRender.ts b/code/lib/preview-api/src/modules/preview-web/render/StoryRender.ts index 028402596143..d7a1abe740f3 100644 --- a/code/lib/preview-api/src/modules/preview-web/render/StoryRender.ts +++ b/code/lib/preview-api/src/modules/preview-web/render/StoryRender.ts @@ -6,7 +6,6 @@ import type { PreparedStory, TeardownRenderToCanvas, StoryContext, - StoryContextForLoaders, StoryId, StoryRenderOptions, } from '@storybook/types'; @@ -181,12 +180,13 @@ export class StoryRender implements Render = { - ...(this.storyContext() as StoryContextForLoaders), + ...this.storyContext(), viewMode: this.viewMode, abortSignal, canvasElement, loaded: {}, step: (label, play) => runStep(label, play, context), + context: null!, }; context.context = context; diff --git a/code/lib/preview-api/src/modules/store/StoryStore.ts b/code/lib/preview-api/src/modules/store/StoryStore.ts index 8e2c6e444f80..05702d9b2630 100644 --- a/code/lib/preview-api/src/modules/store/StoryStore.ts +++ b/code/lib/preview-api/src/modules/store/StoryStore.ts @@ -17,7 +17,6 @@ import type { V3CompatIndexEntry, StoryContext, StoryContextForEnhancers, - StoryContextForLoaders, StoryId, PreparedMeta, } from '@storybook/types'; @@ -226,10 +225,7 @@ export class StoryStore { // A prepared story does not include args, globals or hooks. These are stored in the story store // and updated separtely to the (immutable) story. - getStoryContext( - story: PreparedStory, - { forceInitialArgs = false } = {} - ): Omit { + getStoryContext(story: PreparedStory, { forceInitialArgs = false } = {}) { return prepareContext({ ...story, args: forceInitialArgs ? story.initialArgs : this.args.get(story.id), @@ -369,6 +365,12 @@ export class StoryStore { storyFn: (update) => { const context = { ...this.getStoryContext(story), + abortSignal: new AbortController().signal, + canvasElement: null!, + loaded: {}, + step: (label, play) => story.runStep(label, play, context), + context: null!, + canvas: {}, viewMode: 'story', } as StoryContext; From a795391375baccf68a4f57a21bbe5134c8ac6abd Mon Sep 17 00:00:00 2001 From: Kasper Peulen Date: Thu, 27 Jun 2024 14:05:58 +0200 Subject: [PATCH 13/20] Have proper support for circular references --- .../lib/instrumenter/src/instrumenter.test.ts | 12 ++++++++++++ code/lib/instrumenter/src/instrumenter.ts | 19 ++++++++++++------- 2 files changed, 24 insertions(+), 7 deletions(-) diff --git a/code/lib/instrumenter/src/instrumenter.test.ts b/code/lib/instrumenter/src/instrumenter.test.ts index e946c74c2394..6209f51b6f08 100644 --- a/code/lib/instrumenter/src/instrumenter.test.ts +++ b/code/lib/instrumenter/src/instrumenter.test.ts @@ -182,6 +182,18 @@ describe('Instrumenter', () => { obj.array = [obj]; expect(() => fn(obj)).not.toThrow(); + + expect(callSpy.mock.calls[0][0].args).toMatchInlineSnapshot(` + [ + { + "array": [ + "[Circular]", + ], + "key": "value", + "obj": "[Circular]", + }, + ] + `); }); it('provides metadata about the call in the event', () => { diff --git a/code/lib/instrumenter/src/instrumenter.ts b/code/lib/instrumenter/src/instrumenter.ts index 6d407545a189..641577d57f18 100644 --- a/code/lib/instrumenter/src/instrumenter.ts +++ b/code/lib/instrumenter/src/instrumenter.ts @@ -415,14 +415,17 @@ export class Instrumenter { // Possibly we need to add HTMLElement support to telejson though // Keeping this function here, as removing it means we need to refactor the deserializing that happens in addon-interactions const maximumDepth = 25; // mimicks the max depth of telejson - // We use a depth, to avoid infinite recursion of self referencing values. - const serializeValues = (value: any, depth = 0): any => { + const serializeValues = (value: any, depth: number, seen: unknown[]): any => { + if (seen.includes(value)) return '[Circular]'; + seen = [...seen, value]; + + if (depth > maximumDepth) return '...'; + if (callRefsByResult.has(value)) { return callRefsByResult.get(value); } if (value instanceof Array) { - if (depth > maximumDepth) return '[Circular]'; - return value.map((it) => serializeValues(it, ++depth)); + return value.map((it) => serializeValues(it, ++depth, seen)); } if (value instanceof Date) { return { __date__: { value: value.toISOString() } }; @@ -456,15 +459,17 @@ export class Instrumenter { return { __class__: { name: value.constructor.name } }; } if (Object.prototype.toString.call(value) === '[object Object]') { - if (depth > maximumDepth) return '[Circular]'; return Object.fromEntries( - Object.entries(value).map(([key, val]) => [key, serializeValues(val, ++depth)]) + Object.entries(value).map(([key, val]) => [key, serializeValues(val, ++depth, seen)]) ); } return value; }; - const info: Call = { ...call, args: call.args.map(serializeValues) }; + const info: Call = { + ...call, + args: call.args.map((arg) => serializeValues(arg, 0, [])), + }; // Mark any ancestor calls as "chained upon" so we won't attempt to defer it later. call.path.forEach((ref: any) => { From 2a9a3138aa17cac6ebbd04938867597d1ac28b88 Mon Sep 17 00:00:00 2001 From: Kasper Peulen Date: Thu, 27 Jun 2024 14:18:21 +0200 Subject: [PATCH 14/20] Adjust type of getStoryContext method in docs --- .../src/modules/preview-web/docs-context/DocsContext.ts | 4 ++-- code/lib/types/src/modules/docs.ts | 6 ++++-- 2 files changed, 6 insertions(+), 4 deletions(-) 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 b22ad4afe444..d299889bcaa1 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 @@ -4,7 +4,6 @@ import type { ModuleExport, ModuleExports, PreparedStory, - StoryContextForLoaders, StoryId, StoryName, ResolvedModuleExportType, @@ -232,8 +231,9 @@ export class DocsContext implements DocsContextProps getStoryContext = (story: PreparedStory) => { return { ...this.store.getStoryContext(story), + loaded: {}, viewMode: 'docs', - } as StoryContextForLoaders; + }; }; loadStory = (id: StoryId) => { diff --git a/code/lib/types/src/modules/docs.ts b/code/lib/types/src/modules/docs.ts index 4e8e1c2f3a7f..62184e057ff2 100644 --- a/code/lib/types/src/modules/docs.ts +++ b/code/lib/types/src/modules/docs.ts @@ -1,5 +1,5 @@ import type { Channel } from '@storybook/channels'; -import type { Renderer, StoryContextForLoaders, StoryId, StoryName, Parameters } from './csf'; +import type { Renderer, StoryContext, StoryId, StoryName, Parameters } from './csf'; import type { ModuleExport, ModuleExports, @@ -91,7 +91,9 @@ export interface DocsContextProps { /** * Get the story context of the referenced story. */ - getStoryContext: (story: PreparedStory) => StoryContextForLoaders; + getStoryContext: ( + story: PreparedStory + ) => Omit, 'abortSignal' | 'canvasElement' | 'step' | 'context'>; /** * Asyncronously load an arbitrary story by id. */ From e15a81de94f133d864721a5d49ce19e138408d9e Mon Sep 17 00:00:00 2001 From: Kasper Peulen Date: Thu, 27 Jun 2024 15:43:51 +0200 Subject: [PATCH 15/20] Fix type issue --- code/ui/blocks/src/blocks/Source.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/code/ui/blocks/src/blocks/Source.tsx b/code/ui/blocks/src/blocks/Source.tsx index 59906aa77c43..a59b4c023321 100644 --- a/code/ui/blocks/src/blocks/Source.tsx +++ b/code/ui/blocks/src/blocks/Source.tsx @@ -75,7 +75,7 @@ const getSnippet = ({ transformFromProps, }: { snippet: string; - storyContext: StoryContextForLoaders; + storyContext: ReturnType; typeFromProps: SourceType; transformFromProps?: SourceProps['transform']; }): string => { From f71def6ac2fd80fabd3c7068745945c9add2f004 Mon Sep 17 00:00:00 2001 From: Kasper Peulen Date: Thu, 27 Jun 2024 15:56:52 +0200 Subject: [PATCH 16/20] Fix other type issue --- code/ui/blocks/src/blocks/Source.tsx | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/code/ui/blocks/src/blocks/Source.tsx b/code/ui/blocks/src/blocks/Source.tsx index a59b4c023321..8635711dde79 100644 --- a/code/ui/blocks/src/blocks/Source.tsx +++ b/code/ui/blocks/src/blocks/Source.tsx @@ -5,7 +5,6 @@ import type { PreparedStory, ModuleExport, Args, - StoryContextForLoaders, } from '@storybook/types'; import { SourceType } from '@storybook/docs-tools'; @@ -24,7 +23,10 @@ type SourceParameters = SourceCodeProps & { /** * Transform the detected source for display */ - transform?: (code: string, storyContext: StoryContextForLoaders) => string; + transform?: ( + code: string, + storyContext: ReturnType + ) => string; /** * Internal: set by our CSF loader (`enrichCsf` in `@storybook/csf-tools`). */ From 00af650615168d4bd3f0bb21de761e866221a9ef Mon Sep 17 00:00:00 2001 From: Kasper Peulen Date: Thu, 27 Jun 2024 16:01:22 +0200 Subject: [PATCH 17/20] Fix other type issue --- code/frameworks/angular/src/client/decorators.test.ts | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/code/frameworks/angular/src/client/decorators.test.ts b/code/frameworks/angular/src/client/decorators.test.ts index 72a6f6836713..f31678fbd0c1 100644 --- a/code/frameworks/angular/src/client/decorators.test.ts +++ b/code/frameworks/angular/src/client/decorators.test.ts @@ -24,8 +24,12 @@ const defaultContext: Addon_StoryContext = { viewMode: 'story', abortSignal: undefined, canvasElement: undefined, + step: undefined, + context: undefined, }; +defaultContext.context = defaultContext; + class MockModule {} class MockModuleTwo {} class MockService {} From 9096e8993bf66b275823b356bb7f6f164af35dbb Mon Sep 17 00:00:00 2001 From: Kasper Peulen Date: Thu, 27 Jun 2024 16:03:11 +0200 Subject: [PATCH 18/20] Fix missing dev dep --- code/renderers/react/package.json | 1 + 1 file changed, 1 insertion(+) diff --git a/code/renderers/react/package.json b/code/renderers/react/package.json index ba8b41f1181d..f6ad0ace9b4c 100644 --- a/code/renderers/react/package.json +++ b/code/renderers/react/package.json @@ -88,6 +88,7 @@ }, "devDependencies": { "@storybook/test": "workspace:*", + "@storybook/blocks": "workspace:*", "@types/babel-plugin-react-docgen": "^4", "@types/semver": "^7.3.4", "@types/util-deprecate": "^1.0.0", From 053d9ee8fd206125a037b11bd4444b1cca5a00ff Mon Sep 17 00:00:00 2001 From: Kasper Peulen Date: Thu, 27 Jun 2024 16:15:20 +0200 Subject: [PATCH 19/20] Fix missing dev dep --- code/renderers/react/package.json | 2 +- code/yarn.lock | 1 + 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/code/renderers/react/package.json b/code/renderers/react/package.json index f6ad0ace9b4c..b540ba0198ea 100644 --- a/code/renderers/react/package.json +++ b/code/renderers/react/package.json @@ -87,8 +87,8 @@ "util-deprecate": "^1.0.2" }, "devDependencies": { - "@storybook/test": "workspace:*", "@storybook/blocks": "workspace:*", + "@storybook/test": "workspace:*", "@types/babel-plugin-react-docgen": "^4", "@types/semver": "^7.3.4", "@types/util-deprecate": "^1.0.0", diff --git a/code/yarn.lock b/code/yarn.lock index 3afc12623caf..cf80c8911920 100644 --- a/code/yarn.lock +++ b/code/yarn.lock @@ -6673,6 +6673,7 @@ __metadata: version: 0.0.0-use.local resolution: "@storybook/react@workspace:renderers/react" dependencies: + "@storybook/blocks": "workspace:*" "@storybook/client-logger": "workspace:*" "@storybook/docs-tools": "workspace:*" "@storybook/global": "npm:^5.0.0" From 6a91865a4f905e7b439e6a859397abf7a0b3483e Mon Sep 17 00:00:00 2001 From: Kasper Peulen Date: Thu, 27 Jun 2024 16:27:03 +0200 Subject: [PATCH 20/20] Prettier --- code/ui/blocks/src/blocks/Source.tsx | 7 +------ 1 file changed, 1 insertion(+), 6 deletions(-) diff --git a/code/ui/blocks/src/blocks/Source.tsx b/code/ui/blocks/src/blocks/Source.tsx index 8635711dde79..e5fa68e82636 100644 --- a/code/ui/blocks/src/blocks/Source.tsx +++ b/code/ui/blocks/src/blocks/Source.tsx @@ -1,11 +1,6 @@ import type { ComponentProps, FC } from 'react'; import React, { useContext } from 'react'; -import type { - StoryId, - PreparedStory, - ModuleExport, - Args, -} from '@storybook/types'; +import type { StoryId, PreparedStory, ModuleExport, Args } from '@storybook/types'; import { SourceType } from '@storybook/docs-tools'; import type { SourceCodeProps } from '../components/Source';