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: