mirror of
https://github.com/storybookjs/storybook.git
synced 2025-04-09 00:19:13 +08:00
Implemented prepared context.
This commit is contained in:
parent
858aa9d49f
commit
466a547e6a
@ -59,6 +59,7 @@ describe('StoryRender', () => {
|
|||||||
applyLoaders: jest.fn(),
|
applyLoaders: jest.fn(),
|
||||||
unboundStoryFn: jest.fn(),
|
unboundStoryFn: jest.fn(),
|
||||||
playFunction: jest.fn(),
|
playFunction: jest.fn(),
|
||||||
|
prepareContext: jest.fn(),
|
||||||
};
|
};
|
||||||
|
|
||||||
const render = new StoryRender(
|
const render = new StoryRender(
|
||||||
@ -85,6 +86,7 @@ describe('StoryRender', () => {
|
|||||||
applyLoaders: jest.fn(),
|
applyLoaders: jest.fn(),
|
||||||
unboundStoryFn: jest.fn(),
|
unboundStoryFn: jest.fn(),
|
||||||
playFunction: jest.fn(),
|
playFunction: jest.fn(),
|
||||||
|
prepareContext: jest.fn(),
|
||||||
};
|
};
|
||||||
|
|
||||||
const render = new StoryRender(
|
const render = new StoryRender(
|
||||||
|
@ -154,8 +154,17 @@ export class StoryRender<TRenderer extends Renderer> implements Render<TRenderer
|
|||||||
if (!this.story) throw new Error('cannot render when not prepared');
|
if (!this.story) throw new Error('cannot render when not prepared');
|
||||||
if (!canvasElement) throw new Error('cannot render when canvasElement is unset');
|
if (!canvasElement) throw new Error('cannot render when canvasElement is unset');
|
||||||
|
|
||||||
const { id, componentId, title, name, tags, applyLoaders, unboundStoryFn, playFunction } =
|
const {
|
||||||
this.story;
|
id,
|
||||||
|
componentId,
|
||||||
|
title,
|
||||||
|
name,
|
||||||
|
tags,
|
||||||
|
applyLoaders,
|
||||||
|
unboundStoryFn,
|
||||||
|
playFunction,
|
||||||
|
prepareContext,
|
||||||
|
} = this.story;
|
||||||
|
|
||||||
if (forceRemount && !initial) {
|
if (forceRemount && !initial) {
|
||||||
// NOTE: we don't check the cancel actually worked here, so the previous
|
// NOTE: we don't check the cancel actually worked here, so the previous
|
||||||
@ -181,7 +190,7 @@ export class StoryRender<TRenderer extends Renderer> implements Render<TRenderer
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
const renderStoryContext: StoryContext<TRenderer> = {
|
const renderStoryContext: StoryContext<TRenderer> = prepareContext({
|
||||||
...loadedContext!,
|
...loadedContext!,
|
||||||
// By this stage, it is possible that new args/globals have been received for this story
|
// 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
|
// and we need to ensure we render it with the new values
|
||||||
@ -189,7 +198,7 @@ export class StoryRender<TRenderer extends Renderer> implements Render<TRenderer
|
|||||||
abortSignal,
|
abortSignal,
|
||||||
// We should consider parameterizing the story types with TRenderer['canvasElement'] in the future
|
// We should consider parameterizing the story types with TRenderer['canvasElement'] in the future
|
||||||
canvasElement: canvasElement as any,
|
canvasElement: canvasElement as any,
|
||||||
};
|
});
|
||||||
const renderContext: RenderContext<TRenderer> = {
|
const renderContext: RenderContext<TRenderer> = {
|
||||||
componentId,
|
componentId,
|
||||||
title,
|
title,
|
||||||
|
@ -468,8 +468,8 @@ describe('prepareStory', () => {
|
|||||||
{ render: renderMock }
|
{ render: renderMock }
|
||||||
);
|
);
|
||||||
|
|
||||||
const context = { args: story.initialArgs, ...story };
|
const context = story.prepareContext({ args: story.initialArgs, ...story } as any);
|
||||||
story.undecoratedStoryFn(context as any);
|
story.undecoratedStoryFn(context);
|
||||||
expect(renderMock).toHaveBeenCalledWith(
|
expect(renderMock).toHaveBeenCalledWith(
|
||||||
{ one: 'mapped', two: 2, three: 3 },
|
{ one: 'mapped', two: 2, three: 3 },
|
||||||
expect.objectContaining({ args: { one: 'mapped', two: 2, three: 3 } })
|
expect.objectContaining({ args: { one: 'mapped', two: 2, three: 3 } })
|
||||||
@ -529,6 +529,50 @@ describe('prepareStory', () => {
|
|||||||
|
|
||||||
hooks.clean();
|
hooks.clean();
|
||||||
});
|
});
|
||||||
|
|
||||||
|
it('prepared context is applied to decorators', () => {
|
||||||
|
const renderMock = jest.fn();
|
||||||
|
let ctx1;
|
||||||
|
let ctx2;
|
||||||
|
let ctx3;
|
||||||
|
|
||||||
|
const globalDecorator = jest.fn((fn, ctx) => {
|
||||||
|
ctx1 = ctx;
|
||||||
|
return fn();
|
||||||
|
});
|
||||||
|
const componentDecorator = jest.fn((fn, ctx) => {
|
||||||
|
ctx2 = ctx;
|
||||||
|
return fn();
|
||||||
|
});
|
||||||
|
const storyDecorator = jest.fn((fn, ctx) => {
|
||||||
|
ctx3 = ctx;
|
||||||
|
return fn();
|
||||||
|
});
|
||||||
|
const story = prepareStory(
|
||||||
|
{
|
||||||
|
id,
|
||||||
|
name,
|
||||||
|
argTypes: {
|
||||||
|
one: { name: 'one', type: { name: 'string' }, mapping: { 1: 'mapped-1' } },
|
||||||
|
},
|
||||||
|
args: { one: 1 },
|
||||||
|
decorators: [storyDecorator],
|
||||||
|
moduleExport,
|
||||||
|
},
|
||||||
|
{ id, title, decorators: [componentDecorator] },
|
||||||
|
{ render: renderMock, decorators: [globalDecorator] }
|
||||||
|
);
|
||||||
|
|
||||||
|
const hooks = new HooksContext();
|
||||||
|
const context = story.prepareContext({ args: story.initialArgs, hooks, ...story } as any);
|
||||||
|
story.unboundStoryFn(context);
|
||||||
|
|
||||||
|
expect(ctx1).toMatchObject({ args: { one: 'mapped-1' } });
|
||||||
|
expect(ctx2).toMatchObject({ args: { one: 'mapped-1' } });
|
||||||
|
expect(ctx3).toMatchObject({ args: { one: 'mapped-1' } });
|
||||||
|
|
||||||
|
hooks.clean();
|
||||||
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
describe('with `FEATURES.argTypeTargetsV7`', () => {
|
describe('with `FEATURES.argTypeTargetsV7`', () => {
|
||||||
@ -549,11 +593,12 @@ describe('prepareStory', () => {
|
|||||||
{ render: renderMock }
|
{ render: renderMock }
|
||||||
);
|
);
|
||||||
|
|
||||||
firstStory.unboundStoryFn({
|
const context = firstStory.prepareContext({
|
||||||
args: firstStory.initialArgs,
|
args: firstStory.initialArgs,
|
||||||
hooks: new HooksContext(),
|
hooks: new HooksContext(),
|
||||||
...firstStory,
|
...firstStory,
|
||||||
} as any);
|
} as any);
|
||||||
|
firstStory.unboundStoryFn(context);
|
||||||
expect(renderMock).toHaveBeenCalledWith(
|
expect(renderMock).toHaveBeenCalledWith(
|
||||||
{ a: 1 },
|
{ a: 1 },
|
||||||
expect.objectContaining({ args: { a: 1 }, allArgs: { a: 1, b: 2 } })
|
expect.objectContaining({ args: { a: 1 }, allArgs: { a: 1, b: 2 } })
|
||||||
@ -574,11 +619,12 @@ describe('prepareStory', () => {
|
|||||||
{ render: renderMock }
|
{ render: renderMock }
|
||||||
);
|
);
|
||||||
|
|
||||||
firstStory.unboundStoryFn({
|
const context = firstStory.prepareContext({
|
||||||
args: firstStory.initialArgs,
|
args: firstStory.initialArgs,
|
||||||
hooks: new HooksContext(),
|
hooks: new HooksContext(),
|
||||||
...firstStory,
|
...firstStory,
|
||||||
} as any);
|
} as any);
|
||||||
|
firstStory.unboundStoryFn(context);
|
||||||
expect(renderMock).toHaveBeenCalledWith(
|
expect(renderMock).toHaveBeenCalledWith(
|
||||||
{ a: 1 },
|
{ a: 1 },
|
||||||
expect.objectContaining({ args: { a: 1 }, allArgs: { a: 1, b: 2 } })
|
expect.objectContaining({ args: { a: 1 }, allArgs: { a: 1, b: 2 } })
|
||||||
@ -599,11 +645,12 @@ describe('prepareStory', () => {
|
|||||||
{ render: renderMock }
|
{ render: renderMock }
|
||||||
);
|
);
|
||||||
|
|
||||||
firstStory.unboundStoryFn({
|
const context = firstStory.prepareContext({
|
||||||
args: firstStory.initialArgs,
|
args: firstStory.initialArgs,
|
||||||
hooks: new HooksContext(),
|
hooks: new HooksContext(),
|
||||||
...firstStory,
|
...firstStory,
|
||||||
} as any);
|
} as any);
|
||||||
|
firstStory.unboundStoryFn(context);
|
||||||
expect(renderMock).toHaveBeenCalledWith(
|
expect(renderMock).toHaveBeenCalledWith(
|
||||||
{ a: 1 },
|
{ a: 1 },
|
||||||
expect.objectContaining({ argsByTarget: { [NO_TARGET_NAME]: { a: 1 }, foo: { b: 2 } } })
|
expect.objectContaining({ argsByTarget: { [NO_TARGET_NAME]: { a: 1 }, foo: { b: 2 } } })
|
||||||
@ -624,11 +671,12 @@ describe('prepareStory', () => {
|
|||||||
{ render: renderMock }
|
{ render: renderMock }
|
||||||
);
|
);
|
||||||
|
|
||||||
firstStory.unboundStoryFn({
|
const context = firstStory.prepareContext({
|
||||||
args: firstStory.initialArgs,
|
args: firstStory.initialArgs,
|
||||||
hooks: new HooksContext(),
|
hooks: new HooksContext(),
|
||||||
...firstStory,
|
...firstStory,
|
||||||
} as any);
|
} as any);
|
||||||
|
firstStory.unboundStoryFn(context);
|
||||||
expect(renderMock).toHaveBeenCalledWith(
|
expect(renderMock).toHaveBeenCalledWith(
|
||||||
{},
|
{},
|
||||||
expect.objectContaining({ argsByTarget: { foo: { b: 2 } } })
|
expect.objectContaining({ argsByTarget: { foo: { b: 2 } } })
|
||||||
@ -647,11 +695,12 @@ describe('prepareStory', () => {
|
|||||||
{ render: renderMock }
|
{ render: renderMock }
|
||||||
);
|
);
|
||||||
|
|
||||||
firstStory.unboundStoryFn({
|
const context = firstStory.prepareContext({
|
||||||
args: firstStory.initialArgs,
|
args: firstStory.initialArgs,
|
||||||
hooks: new HooksContext(),
|
hooks: new HooksContext(),
|
||||||
...firstStory,
|
...firstStory,
|
||||||
} as any);
|
} as any);
|
||||||
|
firstStory.unboundStoryFn(context);
|
||||||
expect(renderMock).toHaveBeenCalledWith({}, expect.objectContaining({ argsByTarget: {} }));
|
expect(renderMock).toHaveBeenCalledWith({}, expect.objectContaining({ argsByTarget: {} }));
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
@ -739,6 +788,7 @@ describe('prepareMeta', () => {
|
|||||||
unboundStoryFn,
|
unboundStoryFn,
|
||||||
undecoratedStoryFn,
|
undecoratedStoryFn,
|
||||||
playFunction,
|
playFunction,
|
||||||
|
prepareContext,
|
||||||
...expectedPreparedMeta
|
...expectedPreparedMeta
|
||||||
} = preparedStory;
|
} = preparedStory;
|
||||||
|
|
||||||
|
@ -71,23 +71,10 @@ export function prepareStory<TRenderer extends Renderer>(
|
|||||||
};
|
};
|
||||||
|
|
||||||
const undecoratedStoryFn: LegacyStoryFn<TRenderer> = (context: StoryContext<TRenderer>) => {
|
const undecoratedStoryFn: LegacyStoryFn<TRenderer> = (context: StoryContext<TRenderer>) => {
|
||||||
const mappedArgs = Object.entries(context.args).reduce((acc, [key, val]) => {
|
|
||||||
const mapping = context.argTypes[key]?.mapping;
|
|
||||||
acc[key] = mapping && val in mapping ? mapping[val] : val;
|
|
||||||
return acc;
|
|
||||||
}, {} as Args);
|
|
||||||
|
|
||||||
const includedArgs = Object.entries(mappedArgs).reduce((acc, [key, val]) => {
|
|
||||||
const argType = context.argTypes[key] || {};
|
|
||||||
if (includeConditionalArg(argType, mappedArgs, context.globals)) acc[key] = val;
|
|
||||||
return acc;
|
|
||||||
}, {} as Args);
|
|
||||||
|
|
||||||
const includedContext = { ...context, args: includedArgs };
|
|
||||||
const { passArgsFirst: renderTimePassArgsFirst = true } = context.parameters;
|
const { passArgsFirst: renderTimePassArgsFirst = true } = context.parameters;
|
||||||
return renderTimePassArgsFirst
|
return renderTimePassArgsFirst
|
||||||
? (render as ArgsStoryFn<TRenderer>)(includedContext.args, includedContext)
|
? (render as ArgsStoryFn<TRenderer>)(context.args, context)
|
||||||
: (render as LegacyStoryFn<TRenderer>)(includedContext);
|
: (render as LegacyStoryFn<TRenderer>)(context);
|
||||||
};
|
};
|
||||||
|
|
||||||
// Currently it is only possible to set these globally
|
// Currently it is only possible to set these globally
|
||||||
@ -109,8 +96,15 @@ export function prepareStory<TRenderer extends Renderer>(
|
|||||||
if (!render) throw new Error(`No render function available for storyId '${id}'`);
|
if (!render) throw new Error(`No render function available for storyId '${id}'`);
|
||||||
|
|
||||||
const decoratedStoryFn = applyHooks<TRenderer>(applyDecorators)(undecoratedStoryFn, decorators);
|
const decoratedStoryFn = applyHooks<TRenderer>(applyDecorators)(undecoratedStoryFn, decorators);
|
||||||
const unboundStoryFn = (context: StoryContext<TRenderer>) => {
|
const unboundStoryFn = (context: StoryContext<TRenderer>) => decoratedStoryFn(context);
|
||||||
|
|
||||||
|
// prepareContext is invoked at StoryRender.render()
|
||||||
|
// the context is prepared before invoking the render function, instead of here directly
|
||||||
|
// to ensure args don't loose there special properties set by the renderer
|
||||||
|
// eg. reactive proxies set by frameworks like SolidJS or Vue
|
||||||
|
const prepareContext = (context: StoryContext<TRenderer>) => {
|
||||||
let finalContext: StoryContext<TRenderer> = context;
|
let finalContext: StoryContext<TRenderer> = context;
|
||||||
|
|
||||||
if (global.FEATURES?.argTypeTargetsV7) {
|
if (global.FEATURES?.argTypeTargetsV7) {
|
||||||
const argsByTarget = groupArgsByTarget(context);
|
const argsByTarget = groupArgsByTarget(context);
|
||||||
finalContext = {
|
finalContext = {
|
||||||
@ -121,7 +115,19 @@ export function prepareStory<TRenderer extends Renderer>(
|
|||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
return decoratedStoryFn(finalContext);
|
const mappedArgs = Object.entries(finalContext.args).reduce((acc, [key, val]) => {
|
||||||
|
const mapping = finalContext.argTypes[key]?.mapping;
|
||||||
|
acc[key] = mapping && val in mapping ? mapping[val] : val;
|
||||||
|
return acc;
|
||||||
|
}, {} as Args);
|
||||||
|
|
||||||
|
const includedArgs = Object.entries(mappedArgs).reduce((acc, [key, val]) => {
|
||||||
|
const argType = finalContext.argTypes[key] || {};
|
||||||
|
if (includeConditionalArg(argType, mappedArgs, finalContext.globals)) acc[key] = val;
|
||||||
|
return acc;
|
||||||
|
}, {} as Args);
|
||||||
|
|
||||||
|
return { ...finalContext, args: includedArgs };
|
||||||
};
|
};
|
||||||
|
|
||||||
const play = storyAnnotations?.play || componentAnnotations.play;
|
const play = storyAnnotations?.play || componentAnnotations.play;
|
||||||
@ -150,6 +156,7 @@ export function prepareStory<TRenderer extends Renderer>(
|
|||||||
unboundStoryFn,
|
unboundStoryFn,
|
||||||
applyLoaders,
|
applyLoaders,
|
||||||
playFunction,
|
playFunction,
|
||||||
|
prepareContext,
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -84,6 +84,7 @@ export type PreparedStory<TRenderer extends Renderer = Renderer> =
|
|||||||
context: StoryContextForLoaders<TRenderer>
|
context: StoryContextForLoaders<TRenderer>
|
||||||
) => Promise<StoryContextForLoaders<TRenderer> & { loaded: StoryContext<TRenderer>['loaded'] }>;
|
) => Promise<StoryContextForLoaders<TRenderer> & { loaded: StoryContext<TRenderer>['loaded'] }>;
|
||||||
playFunction?: (context: StoryContext<TRenderer>) => Promise<void> | void;
|
playFunction?: (context: StoryContext<TRenderer>) => Promise<void> | void;
|
||||||
|
prepareContext: (context: StoryContext<TRenderer>) => StoryContext<TRenderer>;
|
||||||
};
|
};
|
||||||
|
|
||||||
export type PreparedMeta<TRenderer extends Renderer = Renderer> = Omit<
|
export type PreparedMeta<TRenderer extends Renderer = Renderer> = Omit<
|
||||||
|
Loading…
x
Reference in New Issue
Block a user