Skip to content

Navigation Menu

Sign in
Appearance settings

Search code, repositories, users, issues, pull requests...

Provide feedback

We read every piece of feedback, and take your input very seriously.

Saved searches

Use saved searches to filter your results more quickly

Sign up
Appearance settings

Commit daa20ac

Browse files
feat: support ngOnChanges with correct simple change object within rerender
ref #365
1 parent 701dc5e commit daa20ac

File tree

3 files changed

+87
-8
lines changed

3 files changed

+87
-8
lines changed

‎projects/testing-library/src/lib/models.ts‎

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -55,7 +55,7 @@ export interface RenderResult<ComponentType, WrapperType = ComponentType> extend
5555
/**
5656
* @description
5757
* Re-render the same component with different properties.
58-
* This creates a new instance of the component.
58+
* Properties not passed in again are removed.
5959
*/
6060
rerender: (
6161
properties?: Pick<
@@ -64,11 +64,17 @@ export interface RenderResult<ComponentType, WrapperType = ComponentType> extend
6464
>,
6565
) => Promise<void>;
6666
/**
67+
* @deprecated
68+
* Use rerender instead and pass in all properties you don't want to change unchanged again.
69+
*
6770
* @description
6871
* Keeps the current fixture intact and invokes ngOnChanges with the updated properties.
6972
*/
7073
change: (changedProperties: Partial<ComponentType>) => void;
7174
/**
75+
* @deprecated
76+
* Use rerender instead and pass in all input properties you don't want to change unchanged again.
77+
*
7278
* @description
7379
* Keeps the current fixture intact, update the @Input properties and invoke ngOnChanges with the updated properties.
7480
*/

‎projects/testing-library/src/lib/testing-library.ts‎

Lines changed: 54 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -120,14 +120,38 @@ export async function render<SutType, WrapperType = SutType>(
120120

121121
await renderFixture(componentProperties, componentInputs, componentOutputs);
122122

123+
let renderedPropKeys = Object.keys(componentProperties);
124+
let renderedInputKeys = Object.keys(componentInputs);
125+
let renderedOutputKeys = Object.keys(componentOutputs);
123126
const rerender = async (
124127
properties?: Pick<RenderTemplateOptions<SutType>, 'componentProperties' | 'componentInputs' | 'componentOutputs'>,
125128
) => {
126-
await renderFixture(
127-
properties?.componentProperties ?? {},
128-
properties?.componentInputs ?? {},
129-
properties?.componentOutputs ?? {},
130-
);
129+
const newComponentInputs = properties?.componentInputs ?? {};
130+
for (const inputKey of renderedInputKeys) {
131+
if (!Object.prototype.hasOwnProperty.call(newComponentInputs, inputKey)) {
132+
delete (fixture.componentInstance as any)[inputKey];
133+
}
134+
}
135+
setComponentInputs(fixture, newComponentInputs);
136+
renderedInputKeys = Object.keys(newComponentInputs);
137+
138+
const newComponentOutputs = properties?.componentOutputs ?? {};
139+
for (const outputKey of renderedOutputKeys) {
140+
if (!Object.prototype.hasOwnProperty.call(newComponentOutputs, outputKey)) {
141+
delete (fixture.componentInstance as any)[outputKey];
142+
}
143+
}
144+
setComponentOutputs(fixture, newComponentOutputs);
145+
renderedOutputKeys = Object.keys(newComponentOutputs);
146+
147+
const newComponentProps = properties?.componentProperties ?? {};
148+
const changes = updateProps(fixture, renderedPropKeys, newComponentProps);
149+
if (hasOnChangesHook(fixture.componentInstance)) {
150+
fixture.componentInstance.ngOnChanges(changes);
151+
}
152+
renderedPropKeys = Object.keys(newComponentProps);
153+
154+
fixture.componentRef.injector.get(ChangeDetectorRef).detectChanges();
131155
};
132156

133157
const changeInput = (changedInputProperties: Partial<SutType>) => {
@@ -357,6 +381,31 @@ function getChangesObj(oldProps: Record<string, any> | null, newProps: Record<st
357381
);
358382
}
359383

384+
function updateProps<SutType>(
385+
fixture: ComponentFixture<SutType>,
386+
prevRenderedPropsKeys: string[],
387+
newProps: Record<string, any>,
388+
) {
389+
const componentInstance = fixture.componentInstance as Record<string, any>;
390+
const simpleChanges: SimpleChanges = {};
391+
392+
for (const key of prevRenderedPropsKeys) {
393+
if (!Object.prototype.hasOwnProperty.call(newProps, key)) {
394+
simpleChanges[key] = new SimpleChange(componentInstance[key], undefined, false);
395+
delete componentInstance[key];
396+
}
397+
}
398+
399+
for (const [key, value] of Object.entries(newProps)) {
400+
if (value !== componentInstance[key]) {
401+
simpleChanges[key] = new SimpleChange(componentInstance[key], value, false);
402+
}
403+
}
404+
setComponentProperties(fixture, newProps);
405+
406+
return simpleChanges;
407+
}
408+
360409
function addAutoDeclarations<SutType>(
361410
sut: Type<SutType> | string,
362411
{

‎projects/testing-library/tests/rerender.spec.ts‎

Lines changed: 26 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,15 +1,23 @@
1-
import { Component, Input } from '@angular/core';
1+
import { Component, Input,OnChanges,SimpleChanges } from '@angular/core';
22
import { render, screen } from '../src/public_api';
33

4+
let ngOnChangesSpy: jest.Mock;
45
@Component({
56
selector: 'atl-fixture',
67
template: ` {{ firstName }} {{ lastName }} `,
78
})
8-
class FixtureComponent {
9+
class FixtureComponent implementsOnChanges{
910
@Input() firstName = 'Sarah';
1011
@Input() lastName?: string;
12+
ngOnChanges(changes: SimpleChanges): void {
13+
ngOnChangesSpy(changes);
14+
}
1115
}
1216

17+
beforeEach(() => {
18+
ngOnChangesSpy = jest.fn();
19+
});
20+
1321
test('rerenders the component with updated props', async () => {
1422
const { rerender } = await render(FixtureComponent);
1523
expect(screen.getByText('Sarah')).toBeInTheDocument();
@@ -54,6 +62,22 @@ test('rerenders the component with updated props and resets other props', async
5462
const firstName2 = 'Chris';
5563
await rerender({ componentProperties: { firstName: firstName2 } });
5664

65+
expect(screen.getByText(`${firstName2}`)).toBeInTheDocument();
5766
expect(screen.queryByText(`${firstName2} ${lastName}`)).not.toBeInTheDocument();
5867
expect(screen.queryByText(`${firstName} ${lastName}`)).not.toBeInTheDocument();
68+
69+
expect(ngOnChangesSpy).toHaveBeenCalledTimes(2); // one time initially and one time for rerender
70+
const rerenderedChanges = ngOnChangesSpy.mock.calls[1][0] as SimpleChanges;
71+
expect(rerenderedChanges).toEqual({
72+
lastName: {
73+
previousValue: 'Peeters',
74+
currentValue: undefined,
75+
firstChange: false,
76+
},
77+
firstName: {
78+
previousValue: 'Mark',
79+
currentValue: 'Chris',
80+
firstChange: false,
81+
},
82+
});
5983
});

0 commit comments

Comments
(0)

AltStyle によって変換されたページ (->オリジナル) /