Skip to content

Commit 2a6cd5e

Browse files
authored
feat: support run ngOnChanges hook for change bingins (#348) (#360)
1 parent 64d05f8 commit 2a6cd5e

File tree

8 files changed

+112
-10
lines changed

8 files changed

+112
-10
lines changed

README.md

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -161,7 +161,8 @@ const service = spectator.inject(QueryService, fromComponentInjector);
161161
```ts
162162
spectator.detectChanges();
163163
```
164-
- `setInput()` - Changes the value of an @Input() of the tested component:
164+
- `setInput()` - Changes the value of an @Input() of the tested component.
165+
Method runs `ngOnChanges` with `SimpleChanges` manually if it exists.
165166
```ts
166167
it('should...', () => {
167168
spectator.setInput('className', 'danger');

projects/spectator/src/lib/base/dom-spectator.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -96,7 +96,7 @@ export abstract class DomSpectator<I> extends BaseSpectator {
9696
public setInput<K extends keyof I>(input: Partial<I>): void;
9797
public setInput<K extends keyof I>(input: K, inputValue: I[K]): void;
9898
public setInput(input: any, value?: any): void {
99-
setProps(this.instance, input, value);
99+
setProps(this.instance, input, value, false);
100100
this.debugElement.injector.get(ChangeDetectorRef).detectChanges();
101101
}
102102

projects/spectator/src/lib/internals/query.ts

Lines changed: 26 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
import { DebugElement } from '@angular/core';
1+
import { DebugElement, SimpleChange, SimpleChanges } from '@angular/core';
22
import { By } from '@angular/platform-browser';
33

44
import { DOMSelector } from '../dom-selectors';
@@ -26,17 +26,38 @@ export function getChildren<R>(debugElementRoot: DebugElement): (directiveOrSele
2626
};
2727
}
2828

29-
export function setProps<T, K extends string | number | symbol, V>(instance: T, key: K, value: V): T & { [KEY in K]: V };
29+
export function setProps<T, K extends string | number | symbol, V>(
30+
instance: T,
31+
key: K,
32+
value: V,
33+
firstChange?: boolean
34+
): T & { [KEY in K]: V };
3035
export function setProps<T, KV>(instance: T, keyValues?: KV): T & KV;
31-
export function setProps(instance: any, keyOrKeyValues: any, value?: any): any {
36+
export function setProps(instance: any, keyOrKeyValues: any, value?: any, firstChange: boolean = true): any {
37+
const changes: SimpleChanges = {};
38+
39+
const update = (key: string, newValue: any): void => {
40+
if (instance[key] === newValue) {
41+
return;
42+
}
43+
44+
changes[key] = new SimpleChange(instance[key], newValue, firstChange);
45+
instance[key] = newValue;
46+
};
47+
3248
if (isString(keyOrKeyValues)) {
33-
instance[keyOrKeyValues] = value;
49+
update(keyOrKeyValues, value);
3450
} else {
3551
// tslint:disable-next-line:forin
3652
for (const p in keyOrKeyValues) {
37-
instance[p] = keyOrKeyValues[p];
53+
update(p, keyOrKeyValues[p]);
3854
}
3955
}
4056

57+
if (Object.keys(changes).length) {
58+
// tslint:disable-next-line:no-life-cycle-call
59+
instance.ngOnChanges?.(changes);
60+
}
61+
4162
return instance;
4263
}

projects/spectator/src/lib/spectator-directive/spectator-directive.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -36,7 +36,7 @@ export class SpectatorDirective<D, H = HostComponent> extends DomSpectator<D> {
3636
public setHostInput<K extends keyof H>(input: Partial<H>): void;
3737
public setHostInput<K extends keyof H>(input: K, inputValue: H[K]): void;
3838
public setHostInput(input: any, value?: any): void {
39-
setProps(this.hostComponent, input, value);
39+
setProps(this.hostComponent, input, value, false);
4040
this.detectChanges();
4141
}
4242
}

projects/spectator/src/lib/spectator-host/spectator-host.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -50,7 +50,7 @@ export class SpectatorHost<C, H = HostComponent> extends Spectator<C> {
5050
public setHostInput<K extends keyof H>(input: Partial<H>): void;
5151
public setHostInput<K extends keyof H>(input: K, inputValue: H[K]): void;
5252
public setHostInput(input: any, value?: any): void {
53-
setProps(this.hostComponent, input, value);
53+
setProps(this.hostComponent, input, value, false);
5454
this.detectChanges();
5555
}
5656
}

projects/spectator/src/lib/spectator-pipe/spectator-pipe.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -20,7 +20,7 @@ export class SpectatorPipe<P, H = HostComponent> extends BaseSpectator {
2020
public setHostInput<K extends keyof H>(input: Partial<H>): void;
2121
public setHostInput<K extends keyof H>(input: K, inputValue: H[K]): void;
2222
public setHostInput(input: any, value?: any): void {
23-
setProps(this.hostComponent, input, value);
23+
setProps(this.hostComponent, input, value, false);
2424
this.detectChanges();
2525
}
2626
}
Lines changed: 54 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,54 @@
1+
import { createComponentFactory, Spectator } from '@ngneat/spectator';
2+
import { CommonModule } from '@angular/common';
3+
4+
import { SimpleChangesComponent } from './simple-changes.component';
5+
6+
describe('SimpleChangesComponent', () => {
7+
let spectator: Spectator<SimpleChangesComponent>;
8+
9+
const createComponent = createComponentFactory({
10+
component: SimpleChangesComponent,
11+
imports: [CommonModule]
12+
});
13+
14+
it('should be sequence of calls is correct', () => {
15+
spectator = createComponent({
16+
props: { value: '1' }
17+
});
18+
19+
const { hooks } = spectator.component;
20+
21+
expect(hooks.length).toBe(2);
22+
expect(hooks[0]).toBe('ngOnChanges');
23+
expect(hooks[1]).toBe('ngOnInit');
24+
});
25+
26+
it('should be set first change value and next updates', () => {
27+
spectator = createComponent({ props: { value: '1' } });
28+
29+
const { changes } = spectator.component;
30+
31+
expect(changes.length).toBe(1, 'size after compile');
32+
expect(changes[0].currentValue).toBe('1', 'first change currentValue');
33+
expect(changes[0].previousValue).toBe(undefined, 'first change previousValue');
34+
expect(changes[0].firstChange).toBe(true, 'first change firstChange');
35+
36+
spectator.setInput({ value: '2' });
37+
38+
expect(changes.length).toBe(2, 'size after update');
39+
expect(changes[1].currentValue).toBe('2', 'after update currentValue');
40+
expect(changes[1].previousValue).toBe('1', 'after update previousValue');
41+
expect(changes[1].firstChange).toBe(false, 'after update firstChange');
42+
});
43+
44+
it('should not update when value is equal', () => {
45+
spectator = createComponent({ props: { value: '1' } });
46+
47+
const { changes } = spectator.component;
48+
49+
expect(changes.length).toBe(1, 'size after compile');
50+
51+
spectator.setInput({ value: '1' });
52+
expect(changes.length).toBe(1, 'not updated');
53+
});
54+
});
Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,26 @@
1+
import { Component, Input, OnChanges, OnInit, SimpleChange, SimpleChanges } from '@angular/core';
2+
3+
// tslint:disable:template-no-call-expression
4+
5+
@Component({
6+
selector: 'app-simple-changes',
7+
template: ``
8+
})
9+
export class SimpleChangesComponent implements OnInit, OnChanges {
10+
@Input() public value;
11+
12+
public hooks: string[] = [];
13+
public changes: SimpleChange[] = [];
14+
15+
public ngOnInit(): void {
16+
this.hooks.push('ngOnInit');
17+
}
18+
19+
public ngOnChanges(changes: SimpleChanges): void {
20+
this.hooks.push('ngOnChanges');
21+
22+
if ('value' in changes) {
23+
this.changes.push(changes.value);
24+
}
25+
}
26+
}

0 commit comments

Comments
 (0)