Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
25 changes: 15 additions & 10 deletions code/frameworks/angular-vite/src/client/decorateStory.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,8 +6,7 @@ import { componentWrapperDecorator } from './decorators.ts';
import decorateStory from './decorateStory.ts';
import type { AngularRenderer } from './types.ts';

// TODO: Fix. Test is infinitely running.
describe.skip('decorateStory', () => {
describe('decorateStory', () => {
describe('angular behavior', () => {
it('should use componentWrapperDecorator with args', () => {
const decorators: DecoratorFunction<AngularRenderer>[] = [
Expand Down Expand Up @@ -37,8 +36,11 @@ describe.skip('decorateStory', () => {
grandparentInput: 'grandparent input',
parentOutput: expect.any(Function),
},
template:
'<great-grandparent><grandparent [grandparentInput]="grandparentInput"><parent [parentInput]="parentInput" (parentOutput)="parentOutput($event)"></child></parent></grandparent></great-grandparent>',
template: `<great-grandparent><grandparent [grandparentInput]="grandparentInput"><parent
[parentInput]="parentInput"
(parentOutput)="parentOutput($event)">
</child>
</parent></grandparent></great-grandparent>`,
userDefinedTemplate: true,
});
});
Expand Down Expand Up @@ -76,8 +78,11 @@ describe.skip('decorateStory', () => {
grandparentInput: 'Grandparent input',
sameInput: 'Story input',
},
template:
'<great-grandparent><grandparent [grandparentInput]="grandparentInput"><parent [parentInput]="parentInput" (parentOutput)="parentOutput($event)"></child></parent></grandparent></great-grandparent>',
template: `<great-grandparent><grandparent [grandparentInput]="grandparentInput"><parent
[parentInput]="parentInput"
(parentOutput)="parentOutput($event)">
</child>
</parent></grandparent></great-grandparent>`,
userDefinedTemplate: true,
});
});
Expand Down Expand Up @@ -207,7 +212,7 @@ describe.skip('decorateStory', () => {
});
});

it('should only keeps args with a control or an action in argTypes', () => {
it('passes every arg to the story, including ones carrying no control', () => {
const decorated = decorateStory(
(context: StoryContext) => ({
template: `Args available in the story : ${Object.keys(context.args).join()}`,
Expand All @@ -222,17 +227,17 @@ describe.skip('decorateStory', () => {
argTypes: {
withControl: { control: { type: 'object' }, name: 'withControl' },
withAction: { action: 'onClick', name: 'withAction' },
toRemove: { name: 'toRemove' },
plain: { name: 'plain' },
},
args: {
withControl: 'withControl',
withAction: () => ({}),
toRemove: 'toRemove',
plain: 'plain',
},
})
)
).toEqual({
template: 'Args available in the story : withControl,withAction',
template: 'Args available in the story : withControl,withAction,plain',
userDefinedTemplate: true,
});
});
Expand Down
49 changes: 1 addition & 48 deletions code/frameworks/angular-vite/src/client/decorateStory.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,14 +2,13 @@ import { sanitizeStoryContextUpdate } from 'storybook/preview-api';
import type { DecoratorFunction, LegacyStoryFn, StoryContext } from 'storybook/internal/types';

import { computesTemplateFromComponent } from './renderer/ComputesTemplateFromComponent.ts';
import { getComponentInputsOutputs } from './renderer/utils/NgComponentAnalyzer.ts';
import type { AngularRenderer } from './types.ts';

export default function decorateStory(
mainStoryFn: LegacyStoryFn<AngularRenderer>,
decorators: DecoratorFunction<AngularRenderer>[]
): LegacyStoryFn<AngularRenderer> {
const returnDecorators = [cleanArgsDecorator, ...decorators].reduce(
const returnDecorators = decorators.reduce(
(previousStoryFn: LegacyStoryFn<AngularRenderer>, decorator) =>
(context: StoryContext<AngularRenderer>) => {
const decoratedStory = decorator((update) => {
Expand Down Expand Up @@ -50,49 +49,3 @@ const prepareMain = (
function hasNoTemplate(template: string | null | undefined): template is undefined {
return template === null || template === undefined;
}

const cleanArgsDecorator: DecoratorFunction<AngularRenderer> = (storyFn, context) => {
if (!context.argTypes || !context.args) {
return storyFn();
}

// When no argTypes are defined for the story (e.g. compodoc metadata is
// unavailable, or the class name was renamed by a bundler so the compodoc
// lookup fails) we have no signal to distinguish "real" component inputs
// from other args. Pass them through unchanged rather than stripping every
// arg the user explicitly set.
//
// Server docgen leaves the same gap: it holds the component metadata and
// infers controls where the UI reads them, so nothing here carries a control
// and a public property that is not an @Input would lose the value its story set.
if (Object.keys(context.argTypes).length === 0 || globalThis.FEATURES?.experimentalDocgenServer) {
return storyFn();
}

// Without compodoc-derived argTypes the decorator-extracted control/action
// signal disappears for component inputs/outputs, so fall back to Angular's
// own runtime metadata: any arg whose name matches a component @Input/@Output
// (incl. signal inputs/outputs) must be passed through even if its argType is
// missing/incomplete.
const componentIO = context.component
? getComponentInputsOutputs(context.component)
: { inputs: [], outputs: [] };
const componentBindings = new Set([
...componentIO.inputs.map((i) => i.templateName),
...componentIO.outputs.map((o) => o.templateName),
]);

const argsToClean = context.args;

context.args = Object.entries(argsToClean).reduce((obj, [key, arg]) => {
const argType = context.argTypes[key];

// Keep args declared as component inputs/outputs OR with a control/action.
if (argType?.action || argType?.control || componentBindings.has(key)) {
return { ...obj, [key]: arg };
}
return obj;
}, {});

return storyFn();
};
Loading