Skip to content

Commit

Permalink
Merge pull request #17633 from storybookjs/angular/fix-setter
Browse files Browse the repository at this point in the history
Angular: Fix multiple calls of Input setter
  • Loading branch information
shilman committed Mar 28, 2022
2 parents 94812d1 + 185b5e7 commit f09053c
Show file tree
Hide file tree
Showing 2 changed files with 41 additions and 29 deletions.
Expand Up @@ -14,6 +14,7 @@ describe('StorybookModule', () => {
template: `
<p id="input">{{ input }}</p>
<p id="inputBindingPropertyName">{{ localPropertyName }}</p>
<p id="setterCallNb">{{ setterCallNb }}</p>
<p id="localProperty">{{ localProperty }}</p>
<p id="localFunction">{{ localFunction() }}</p>
<p id="output" (click)="output.emit('outputEmitted')"></p>
Expand All @@ -27,6 +28,11 @@ describe('StorybookModule', () => {
@Input('inputBindingPropertyName')
public localPropertyName: string;

@Input()
public set setter(value: string) {
this.setterCallNb += 1;
}

@Output()
public output = new EventEmitter<string>();

Expand All @@ -36,6 +42,8 @@ describe('StorybookModule', () => {
public localProperty: string;

public localFunction = () => '';

public setterCallNb = 0;
}

it('should initialize inputs', async () => {
Expand Down Expand Up @@ -104,6 +112,7 @@ describe('StorybookModule', () => {
it('should change inputs if storyProps$ Subject emit', async () => {
const initialProps = {
input: 'input',
inputBindingPropertyName: '',
};
const storyProps$ = new BehaviorSubject<ICollection>(initialProps);

Expand Down Expand Up @@ -150,6 +159,7 @@ describe('StorybookModule', () => {
let expectedOutputValue;
let expectedOutputBindingValue;
const initialProps = {
input: '',
output: (value: string) => {
expectedOutputValue = value;
},
Expand Down Expand Up @@ -225,6 +235,34 @@ describe('StorybookModule', () => {
expect(fixture.nativeElement.querySelector('p').style.color).toEqual('black');
expect(fixture.nativeElement.querySelector('p#input').innerHTML).toEqual(newProps.input);
});

it('should call the Input() setter the right number of times', async () => {
const initialProps = {
setter: 'init',
};
const storyProps$ = new BehaviorSubject<ICollection>(initialProps);

const ngModule = getStorybookModuleMetadata(
{
storyFnAngular: { props: initialProps },
component: FooComponent,
targetSelector: 'my-selector',
},
storyProps$
);
const { fixture } = await configureTestingModule(ngModule);
fixture.detectChanges();

expect(fixture.nativeElement.querySelector('p#setterCallNb').innerHTML).toEqual('1');

const newProps = {
setter: 'new setter value',
};
storyProps$.next(newProps);
fixture.detectChanges();

expect(fixture.nativeElement.querySelector('p#setterCallNb').innerHTML).toEqual('2');
});
});

describe('with component without selector', () => {
Expand Down
Expand Up @@ -98,39 +98,13 @@ export const createStorybookWrapperComponent = (
this.storyComponentViewContainerRef.injector.get(ChangeDetectorRef).markForCheck();
this.changeDetectorRef.detectChanges();

// Once target component has been initialized, the storyProps$ observable keeps target component inputs up to date
// Once target component has been initialized, the storyProps$ observable keeps target component properties than are not Input|Output up to date
this.storyComponentPropsSubscription = this.storyProps$
.pipe(
skip(1),
map((props) => {
// removes component output in props
const outputsKeyToRemove = ngComponentInputsOutputs.outputs.map(
(o) => o.templateName
);
return Object.entries(props).reduce(
(prev, [key, value]) => ({
...prev,
...(!outputsKeyToRemove.includes(key) && {
[key]: value,
}),
}),
{} as ICollection
);
}),
map((props) => {
// In case a component uses an input with `bindingPropertyName` (ex: @Input('name'))
// find the value of the local propName in the component Inputs
// otherwise use the input key
return Object.entries(props).reduce((prev, [propKey, value]) => {
const input = ngComponentInputsOutputs.inputs.find(
(o) => o.templateName === propKey
);

return {
...prev,
...(input ? { [input.propName]: value } : { [propKey]: value }),
};
}, {} as ICollection);
const propsKeyToKeep = getNonInputsOutputsProps(ngComponentInputsOutputs, props);
return propsKeyToKeep.reduce((acc, p) => ({ ...acc, [p]: props[p] }), {});
})
)
.subscribe((props) => {
Expand Down

0 comments on commit f09053c

Please sign in to comment.