Closed IlCallo closed 5 years ago
The recommended way for testing inputs/outputs (thus, life-cycle hooks) is using a Host.
We have documented it briefly in the updated README, but we will add more docs with examples and cookbook articles when we have moved to a Gitbook wiki.
For now, I will close this issue. Feel free to open a new ticket if you have ideas or questions. ❤️
Hi, i have this code snippet which i'm testing an angular library which use the onPush change detection
The problem is i can't get the component to execute ngOnChange
hook with the SpectatorHost
implementation linked above.
Please see this snippet and any hints are very welcomed :)
In my component
@Component({
selector: "mat-advanced-table",
templateUrl: "./mat-advanced-table.component.html",
styleUrls: ["./mat-advanced-table.component.scss"],
changeDetection: ChangeDetectionStrategy.OnPush,
})
export class MatAdvancedTableComponent
implements OnInit, AfterContentInit, AfterViewInit, OnChanges {
@Input() columns: ColumnModel[] = [];
/**
* @description an array of keys the columns to hide
*/
@Input() hiddenColumns: string[] = [];
/**
* @description the data to be displayed conforming to the Columns model
*/
@Input() data: any[];
/**
* @description the table options as defined in NgxMatTableOptions
* see NgxMatTableOptionsDefaults for default values
*/
@Input()
public set options(v: Partial<NgxMatTableOptions>) {
this._options = v
? {
...NgxMatTableOptionsDefaults,
...v,
}
: NgxMatTableOptionsDefaults;
}
public get options(): Partial<NgxMatTableOptions> {
return this._options || NgxMatTableOptionsDefaults;
}
/**
* @description whether data is loading
*/
@Input() loading = false;
ngOnChanges(changes) {
[...]
this.buildColumns();// this method is the responsible for building the th of the table
}
// [..] Ommited implementation for brievety
@Component({ selector: "mat-host-component", template: `<mat-advanced-table [data]="data" [columns]="columns" [loading]="loading" [options]="options"
`, styles: [``], }) export class HostBaseComponent implements OnInit { constructor(protected matAdvancedService: MatAdvancedTableService) {} columns; hiddenColumns; data; loading = false; options: NgxMatTableOptions; transparentBg: boolean; rowNgClassFun;
ngOnInit(): void { this.columns = this.matAdvancedService.getColumnsOfType(MockClass); this.data = mockData; } }
*in my test file i used host component*
```typescript
describe("Basic Implementation", () => {
let spectator: SpectatorHost<MatAdvancedTableComponent>;
let component: MatAdvancedTableComponent;
let service: MatAdvancedTableService;
const createHost = createHostFactory({
component: MatAdvancedTableComponent,
imports: [MatAdvancedTableModule],
declareComponent: false,
});
beforeEach(() => {
spectator = createHost(
`<mat-advanced-table [data]="data" [columns]="columns" [loading]="loading"></mat-advanced-table>`,
{
hostProps: { data: [], columns: [], loading: false },
}
);
component = spectator.component;
service = spectator.get(MatAdvancedTableService);
});
const setupColumns = (typeClass) => {
spectator.setHostInput({ columns: service.getColumnsOfType(typeClass) });
spectator.detectChanges();
spectator.detectChanges();
};
const toggleLoadingData = (loading) => {
spectator.setHostInput({ data: loading ? [] : mockData });
spectator.setHostInput({ loading: loading });
spectator.detectChanges();
};
const setupData = (data = mockData) => {
spectator.setHostInput({ data });
spectator.detectChanges();
};
// Test cases
describe("creating the component with defaults", () => {
beforeEach(() => {
setupColumns(MockClass);
setupData([]);
});
it("Should create the component", () => {
expect(spectator.component).toBeTruthy();
});
it("Should contain a table", () => { // this works
expect(spectator.queryHost(`table`)).toBeTruthy();
});
it("Should contain a set of table header columns", () => { // this fails
expect(spectator.queryHostAll(`table thead th`)).toHaveLength(
component.columns.length
);
});
});
I'm running on Linux
with kernel Linux 5.3.x
using Node 10
"@angular/cli": "~7.0.5",
"@angular/compiler-cli": "~7.0.0",
"@angular/language-service": "~7.0.0",
"@ngneat/spectator": "^4.11.1",
"@qiwi/semantic-release-gh-pages-plugin": "^1.15.10",
"@types/jasmine": "~2.8.8",
"@types/jasminewd2": "~2.0.3",
"@types/node": "~8.9.4",
I worked on a similar issue today, so I'm leaving a comment to help anyone else who ends up here:
My specific use case is that I'm testing a structural directive, similar to ngIf, which uses ngOnChanges to detect changes.
SpectatorDirective.setHostInput()
calls ngOnChanges()
on the host component.
DomSpectator.setInput()
calls ngOnChanges()
on the directive or component under test.
Both calls directly set properties and call ngOnChanges(SimpleChanges)
; and like many Spectator APIs, both also call detectChanges()
afterwards.
As indicated above, it's best to test directives and OnPush components within a host component, and the host (either a regular spectator host, or a custom host component) should not be OnPush, because OnPush will prevent change detection of the child component. This requirement for using components to test directives and OnPush components is not specific to Spectator.
As said in https://github.com/NetanelBasal/spectator/issues/38, it would be a good idea to specify that to test code which uses ngOnChanges (and also components with changeDetection OnPush I think, given https://github.com/angular/angular/issues/12313) you are forced to manually call ngOnChanges (won't work for OnPush scenario I guess) or use a setup with a custom host component binding a property to the given template.
I can also provide an example that show how to do it and why, given that I had to produce it to wrap my head around how that mess works (talking about Angular, not Spectator).
I did this with Jest, but I guess it works with Jasmine as well.
If not interested, this issue will serve as reference I guess. Here the link to the Gist: https://gist.github.com/IlCallo/a003f4f0a12b4e0276d461d5ff8c2562