We use cookies to give you the best experience on our website. If you continue to browse, then you agree to our privacy policy and cookie policy. Image for the cookie policy date

ejs-(tree)grid and karma/jasmine unittest: how to wait for grid data?

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) at at 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) at at 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)


Other approach: doesn't work either

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();
});
// adding this makes the test fail with a timeout exception
   await fixture.isStable();
});

... but I put a log output into checkThatNumberOfCssElementsExist, and it simply doesn't get called.

How do I make this work??


13 Replies 1 reply marked as answer

PS Pon Selva Jeganathan Syncfusion Team December 20, 2022 04:42 PM UTC

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: 1name: 'Employee 1'parentId: null },

      { id: 2name: 'Employee 2'parentId: 1 },

      { id: 3name: 'Employee 3'parentId: 2 }

    ]);

    fixture.detectChanges();

 

    const treegridElement = fixture.nativeElement.querySelector('e-treegrid');

    treegridElement.addEventListener('dataBound', () => {

      const data = treegridElement.dataSource;

      expect(data).toEqual([

        { id: 1name: 'Employee 1'parentId: null },

        { id: 2name: 'Employee 2'parentId: 1 },

        { id: 3name: '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



EB Ewa Baumgarten replied to Pon Selva Jeganathan January 11, 2023 08:41 AM UTC

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:


Solution:

    // The done function argument will force the test to wait until it's called - time out otherwise.
    it(`should check grid data elements`, (done: Function) => {
      expect(component.gridDataSource).not.toBeDefined();
      expect(component.waitingForUnitTest).toBeTrue();              // the ngIf flag
      expect(component.treeGrid).not.toBeDefined();
      expect(fixture.nativeElement.querySelector('ejs-treegrid')).toBeNull();

      const gridDataSource = component['generateGridDataSource']();
     
      // force-trigger grid load
      component.waitingForUnitTest = false;
      fixture.detectChanges();

      // both the html element and the component's view child exist now:
      expect(fixture.nativeElement.querySelector('ejs-treegrid')).not.toBeNull();
      expect(component.treeGrid).toBeDefined();

      // since gridData isn't set yet, dataBound hasn't fired yet. Register the event listener before binding:
      component.treeGrid.addEventListener('dataBound', () => {
      // Unfortunately, there is no dedicated event afterRendered or something like that.
      // However, fortunately it happens one frame after onDataBound.
      setTimeout(() => {
        const cssElements = fixture.debugElement.queryAll(By.css('.e-gridcontent .icon-edit'))
        expect(cssElements.length).toBe(31);
        // Tell the test that it's done
        done();
      });
    });

      // Now that the listener is registered, bind the grid data source and refresh the HTML view.
      component.gridDataSource = gridDataSource;
      fixture.detectChanges();

    }, 15000); // Default timeout is too short



Marked as answer

SG Suganya Gopinath Syncfusion Team January 12, 2023 01:20 PM UTC

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.



EB Ewa Baumgarten July 3, 2023 07:17 AM UTC

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 it
    expect(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.




PS Pon Selva Jeganathan Syncfusion Team July 4, 2023 02:13 PM UTC

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.


  1. Complete code example.
  2. Share code example of setTimeout function.
  3. Script error details. (If face any)
  4. Video demo of the issue.
  5. Whether you have faced the issue on enabling the columnTemplate or any other scenario.
  6. If possible, try to reproduce the reported issue in the provided sample or share a reproducible sample with us.


The provided information will be helpful to proceed further.


Kindly get back to us for further assistance.


Regards,

Pon selva




EB Ewa Baumgarten July 6, 2023 02:01 PM UTC

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.



PS Pon Selva Jeganathan Syncfusion Team July 7, 2023 02:05 PM UTC

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 componentAppComponent;

  let fixtureComponentFixture<AppComponent>;

  let data;

 

  beforeEach(async(() => {

    fixture = TestBed.configureTestingModule({

      imports: [TreeGridModuleTranslateTestingModule],

      declarations: [AppComponentDeviceIconComponentIconButtonComponent],

    }).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,


  1. Share code example of getStatus function.
  2. Script error details. (If face any)
  3. Video demo of the issue.
  4. Please share a runnable sample that reproduces the issue


The provided information will be helpful to proceed further.



EB Ewa Baumgarten July 11, 2023 07:40 AM UTC

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



PS Pon Selva Jeganathan Syncfusion Team July 13, 2023 05:26 PM UTC

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 componentAppComponent;

  let fixtureComponentFixture<AppComponent>;

  let data;

…..

 

 

 it(`should check grid data elements`, (doneFunction=> {

    expect(component.tree).toBeDefined();

    expect(fixture.nativeElement?.querySelector('ejs-treegrid')).not.toBeNull();

 

    component.tree.addEventListener('dataBound', (_dataany=> {

      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`, (doneFunction=> {

    expect(component.tree).toBeDefined();

    console.log(fixture.nativeElement?.querySelector('ejs-treegrid'));

 

    expect(fixture.nativeElement?.querySelector('ejs-treegrid')).not.toBeNull();

 

    component.tree.addEventListener('dataBound', (_dataany=> {

      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,


  1. Script error details. (If face any)
  2. Video demo of the issue.
  3. Please share a runnable sample that reproduces the issue


The provided information will be helpful to proceed further.




EB Ewa Baumgarten July 19, 2023 10:25 AM UTC

Hello Pon Selva Jeganathan,


thank you for your answer and the help and sorry for not getting back to you for so long. 

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. However, we've moved the issue to a future sprint, so that I will get back to you in a few weeks...

For now, I have a different question:
For future versions, would it be possible to add an event for unit testing that only fires if a component has received data and is fully initialized? That way, I could simply subscribe to (e.g.) onFullyRendered(), which would do a lot for reliability and testing robustness.

Thank you for your continued support.


PS Pon Selva Jeganathan Syncfusion Team July 20, 2023 04:43 PM UTC

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,


  1. Script error details. (If face any)
  2. Video demo of the issue.
  3. Please share a simple runnable sample that reproduces the issue or replicate the issue in previously shared sample


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.



EB Ewa Baumgarten July 28, 2023 12:04 PM UTC

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






SM Shek Mohammed Asiq Abdul Jabbar Syncfusion Team July 31, 2023 10:29 AM UTC

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.


Loader.
Up arrow icon