Hello,
I want to run a unit test on a component rendering ejs-grid, and I would like to test my component's behaviour based on elements displayed in the grid. However, the grid doesn't refresh in a timely manner, and I haven't found a way to force it other than this one, which unfortunately doesn't cut it for me.
I wanted to ask if there was a way to enforce grid data loading and initialization for my unit test?
I've managed to get an ejs-treegrid to display data by declaring, but not initializing the dataSource-array, then binding the property before the *ngIf flag becomes true:
public gridDataSource?: ControllerOutputDto[];
ngOnInit() {this.updateData().subscribe((myData: someType[]) => {this.gridData = myData;
this.isLoading = false; // This will trigger lazy loading in view to render component
});}
<div class="col device-grid-container"*ngIf="!isLoading">
<ejs-treegrid
#treegrid
class="device-tree"
(collapsed)="onNodeCollapsed($event)"
(expanded)="onNodeExpanded($event)"
[treeColumnIndex]="0"
[dataSource]="gridDataSource"
>
</ejs-treegrid></div>
However, by the time that data is actually rendered, the unit test is already over. I have found this solution online as for how to wait for events and implemented it as follows:
TestBed.configureTestingModule({
imports: [
// ... a bunch of them
TreeGridAllModule, // also tried TreeGridModule
TreeViewAllModule, // also tried TreeViewModule
],
declarations: [MyComponent],
providers: [
// various
],
}).compileComponents();
fit(`should display edit button`, fakeAsync((done: any) => {
component.ngOnInit();
// tried for various orders and multitudes of detectChanges() and tick() with intervals
fixture.detectChanges();
tick();
expect(component.hasReadOnlyPermission).toBe(false);
spyOn(component.treeGrid, 'created').and.callFake((...args: any[]) => {
expect(component.treeGrid.created).toHaveBeenCalled();
done();
return args;
});
flush();
discardPeriodicTasks();
}));
Alas, it will lead to the following error:
TypeError: Cannot read properties of undefined (reading 'get')
at TreeGridComponent.removeEventListener (http://localhost:9876/_karma_webpack_/webpack:/node_modules/@syncfusion/ej2-angular-base/src/component-base.js:297:45) at TreeGridComponent.set [as created] (http://localhost:9876/_karma_webpack_/webpack:/node_modules/@syncfusion/ej2-base/dist/es6/ej2-base.es2015.js:5713:46) atat UserContext.apply (http://localhost:9876/_karma_webpack_/webpack:/src/app/devices/devices/devices.component.spec.ts:128:7) at UserContext.apply (http://localhost:9876/_karma_webpack_/webpack:/node_modules/zone.js/dist/zone-testing.js:2077:34) at _ZoneDelegate.invoke (http://localhost:9876/_karma_webpack_/webpack:/node_modules/zone.js/dist/zone.js:409:30) at ProxyZoneSpec.onInvoke (http://localhost:9876/_karma_webpack_/webpack:/node_modules/zone.js/dist/zone-testing.js:303:43) at _ZoneDelegate.invoke (http://localhost:9876/_karma_webpack_/webpack:/node_modules/zone.js/dist/zone.js:408:56)
Changing the spy will display a different error:
spyOn(TreeGridComponent.prototype, 'created').and.callFake((...args: any[]) => {
expect(TreeGridComponent.prototype.created).toHaveBeenCalled();
done();
return args;
});
TypeError: Cannot read properties of undefined (reading 'hasOwnProperty')
at TreeGrid.created (http://localhost:9876/_karma_webpack_/webpack:/node_modules/@syncfusion/ej2-base/dist/es6/ej2-base.es2015.js:5356:30) atat UserContext.apply (http://localhost:9876/_karma_webpack_/webpack:/src/app/devices/devices/devices.component.spec.ts:128:7) at UserContext.apply (http://localhost:9876/_karma_webpack_/webpack:/node_modules/zone.js/dist/zone-testing.js:2077:34) at _ZoneDelegate.invoke (http://localhost:9876/_karma_webpack_/webpack:/node_modules/zone.js/dist/zone.js:409:30) at ProxyZoneSpec.onInvoke (http://localhost:9876/_karma_webpack_/webpack:/node_modules/zone.js/dist/zone-testing.js:303:43) at _ZoneDelegate.invoke (http://localhost:9876/_karma_webpack_/webpack:/node_modules/zone.js/dist/zone.js:408:56) at Zone.run (http://localhost:9876/_karma_webpack_/webpack:/node_modules/zone.js/dist/zone.js:169:47)
component.html (other properties stay)
<ejs-treegrid (created)="onGridCreated">
component.ts:
public onGridCreated(): void {
// does nothing, is just here to make unittests work with grid}
component.spec.ts:
// beforeEach TestBed imports:
TreeGridAllModule,TreeViewAllModule
fit(`should display edit button`, (async (done: any) => { // rest of code not changed
spyOn(component, 'onGridCreated').and.callFake(() => {
expect(component.onGridCreated).toHaveBeenCalled();
checkThatNumberOfCssElementsExist('.edit-button', component.gridDataSource.length);
done();
});
await fixture.isStable();
});
... but I put a log output into checkThatNumberOfCssElementsExist, and it
simply doesn't get called.
Hi Ewa Baumgarten,
Based on your query, we suggest you use the dataBound event of the treegrid. This event is triggered when the data in the treegrid is bound to the component.
Please refer to the below code snippet,
….. it('should display data in ejs-treegrid when dataSource is initialized and ngIf flag is true', () => { component.dataSource = []; component.ngIfFlag = true; component.dataSource = new DataManager([ { id: 1, name: 'Employee 1', parentId: null }, { id: 2, name: 'Employee 2', parentId: 1 }, { id: 3, name: 'Employee 3', parentId: 2 } ]); fixture.detectChanges();
const treegridElement = fixture.nativeElement.querySelector('e-treegrid'); treegridElement.addEventListener('dataBound', () => { const data = treegridElement.dataSource; expect(data).toEqual([ { id: 1, name: 'Employee 1', parentId: null }, { id: 2, name: 'Employee 2', parentId: 1 }, { id: 3, name: 'Employee 3', parentId: 2 } ]); }); }); });
|
In the above code snippet, initializes the dataSource array with data and sets the *ngIf flag to true, then it subscribes to the dataBound event of the ejs-treegrid component and uses the DataManager to query the `dataSource
Please refer to the below API documentation,
https://ej2.syncfusion.com/documentation/api/treegrid/#databound
Hey,
Thank you for your answer. Unfortunately, it doesn't cut it yet. While toggling the grid via lazy loading allows me to control when the grid loads, your unit test will never even arrive at the expectations.
However, thanks to your input, I was able to figure something out that works. Documenting for posterity/Google:
Hi Ewa,
We are glad that the solution provided was little helpful. Thank you for sharing the solution that worked for you.
We are marking this thread as solved.
Regards,
Suganya Gopinath.
Hello,
after an update from ^20.1.50 to ^22.1.36, the unit tests are no longer working. I'm running the solution that's specified above, which has been working fine.
But with no changes in the test data, the grid renders in the unit test under 20.1.50:
... and only partially renders under 22.1.36:
... which is causing my test to fail, as I can't check the displayed table data anymore.
There is no error on the console, and the grid data is still the same (I'm actually checking against that):
// As detailed in the solution above, I'm deactivating the grid via *ngIf until I'm ready to initialize itexpect(component.gridDataSource).not.toBeDefined();expect(component.treeGrid).not.toBeDefined();expect(component.isLoading).toBeTrue();
const gridDataSource=component['generateGridDataSource']();
expect(component.gridDataSource).toBeDefined();
// This will cause the grid to actually render
component.waitingForUnitTest=false;
fixture.detectChanges();
expect(component.treeGrid).toBeDefined();
expect(fixture.nativeElement.querySelector('ejs-treegrid')).not.toBeNull();
component.treeGrid.addEventListener('dataBound', () => {
expect(component.treeGrid.dataSource).toEqual(gridDataSource);
setTimeout(() => {
// All the checks in here fail, as compared to the previous version
}, 1000);
});
As this is a showstopper for the update, quick help is appreciated.
Hi Ewa Baumgarten,
We have checked your query by preparing a sample based on your shared code example in latest version, but we were unable to replicate the issue at our end.
Please refer to the below sample,
https://stackblitz.com/edit/grid-refresh-test-nzhlm7
Still if you are facing issue, please provide the following information.
The provided information will be helpful to proceed further.
Kindly get back to us for further assistance.
Regards,
Pon selva
Hello Pon,
after removing the giant Syncfusion popup via altering the DOM (could you make it a bit smaller? Please?), I'm having one unit test fail:
https://stackblitz.com/edit/grid-refresh-test-cs8cth?file=src%2Fapp%2Fapp.module.ts
I figured that it would probably need more complex nested data and/or the translation pipe being involved, as simple plaintext fields show up fine. But I'm not even seeing a grid.
Hi Ewa Baumgarten,
Query: after removing the giant Syncfusion popup via altering the DOM (could you make it a bit smaller? Please?), I'm having one unit test fail: I figured that it would probably need more complex nested data and/or the translation pipe being involved, as simple plaintext fields show up fine. But I'm not even seeing a grid.
While running your sample, we are facing the console error and getStatus is not a function error as below screenshot:
We have solved this console error by defining the device and icon component in spec file.
Please refer to the below code example to resolve this console error,
App.component.spec.ts
import { DeviceIconComponent } from './device-icon/device-icon.component'; import { IconButtonComponent } from './icon-button/icon-button.component'; describe('AppComponent', () => { let component: AppComponent; let fixture: ComponentFixture<AppComponent>; let data;
beforeEach(async(() => { fixture = TestBed.configureTestingModule({ imports: [TreeGridModule, TranslateTestingModule], declarations: [AppComponent, DeviceIconComponent, IconButtonComponent], }).createComponent(AppComponent);
|
On further validation on your sample, we could see not the getStatus method definition. However, we still require more information to pinpoint the exact cause of the issue you are facing. Could you kindly provide us with the following details:
Kindly share the below details,
The provided information will be helpful to proceed further.
Hello,
I've stubbed in the app.component, and what I'm seeing under the Syncfusion popup is this now this:
I tried around with various variables, but the grid isn't rendering. At the current state, it's entering the done block, but the grid isn't displayed. I tried the hack from my original implementation with an ngIf flag, but that didn't help anything.
Any function called in the html is a small synchronous helper function. I just left them in because I figured that maybe they're the reason for the problem. I figured that if I could display the nested data, then we might
Hi Ewa Baumgarten,
Query: I tried around with various variables, but the grid isn't rendering. At the current state, it's entering the done block, but the grid isn't displayed. I tried the hack from my original implementation with an ngIf flag, but that didn't help anything.
While running your sample, we are facing the test case issue as below screenshot:
Based on your code example, we noticed that you are checking the condition using the dataBound event arguments value _data. However, the dataBound event only provides the args parameter by default, and it does not contain any direct access to the data source. To access the data source within the dataBound event, you can use the treegrid instance. To avoid this issue, we suggest you follow the below code example,
Your code: App.component.spec.ts
import { DeviceIconComponent } from './device-icon/device-icon.component'; import { IconButtonComponent } from './icon-button/icon-button.component'; describe('AppComponent', () => { let component: AppComponent; let fixture: ComponentFixture<AppComponent>; let data; …..
it(`should check grid data elements`, (done: Function) => { expect(component.tree).toBeDefined(); expect(fixture.nativeElement?.querySelector('ejs-treegrid')).not.toBeNull();
component.tree.addEventListener('dataBound', (_data: any) => { setTimeout(() => { expect( fixture.nativeElement.querySelector('ejs-treegrid') ).not.toBeNull(); const cssElements = fixture.debugElement.queryAll(By.css('#tree')); expect(cssElements.length).toBe(1); console.log(_data); expect(_data.length).toEqual(gridDataSource.length); done(); });
Modified code:
it(`should check grid data elements`, (done: Function) => { expect(component.tree).toBeDefined(); console.log(fixture.nativeElement?.querySelector('ejs-treegrid'));
expect(fixture.nativeElement?.querySelector('ejs-treegrid')).not.toBeNull();
component.tree.addEventListener('dataBound', (_data: any) => { setTimeout(() => { expect( fixture.nativeElement.querySelector('ejs-treegrid') ).not.toBeNull(); const cssElements = fixture.debugElement.queryAll(By.css('#tree')); expect(cssElements.length).toBe(1); console.log(_data);
expect(component.tree.dataSource.dataSource.json.length).toEqual( gridDataSource.length ); done(); }); });
|
After using this solution, the grid is rendered properly.
Please refer to the below sample,
https://stackblitz.com/edit/grid-refresh-test-iszkxm?file=src%2Fapp%2Fapp.component.spec.ts
Still if you are facing any issue, kindly share the below details,
The provided information will be helpful to proceed further.
Hello
Pon Selva Jeganathan,
Hi Ewa Baumgarten,
Thanks for the update.
Query:The working sample will probably be helpful in the future - unfortunately, I can't provide you with the full source code for reasons of confidentiality, but my local version is still not working in the unit test
If you are facing any issue in future, kindly share the below details,
The provided information will be helpful to proceed further.
Query: would it be possible to add an event for unit testing that only fires if a component has received data and is fully initialized?
Based on your query, we suggest you to use the dataBound event of the treegrid. This event is triggered when the data in the treegrid is bound to the component.
Refer to the below code example:
<ejs-treegrid [dataSource]='data' allowPaging='true' childMapping='subtasks' height='350' [treeColumnIndex]=…. (dataBound)='dataBound()' allowReordering='true' allowSorting='true'>
dataBound(): void { //Here checked the initally rendered if(this.treegrid.initalRender){ console.log('Tree Grid <b>dataBound</b> event called<hr>'); }
|
Refer to the below API documentation,
https://ej2.syncfusion.com/documentation/api/treegrid/#databound
Kindly get back to us for further assistance.
Updating syncfusion-grid to
22.2.5 made the issue simply go away, and everything works as it used to.
@Pon Selva Jeganathan: I'm using dataBound right now, but after your post I double-checked my test implementation, and after 22.2.5 it is no longer necessary to use the additional setTimeout.
Tha
Hi Ewa Baumgarten,
Thanks for your update. Hope that your reported problem has been resolved.
Please get back to us if you need any further assistance.