Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

I can't test the rendered rows in the table. #103

Open
paologiarda opened this issue May 10, 2022 · 5 comments
Open

I can't test the rendered rows in the table. #103

paologiarda opened this issue May 10, 2022 · 5 comments

Comments

@paologiarda
Copy link

I try to run my unit tests with jest to check rendered rows in my table but no row are rendered.
this is test in my spec.ts file:
describe('ProjectsTableComponent', () => {

  let component: ProjectsTableComponent;

  let fixture: ComponentFixture;

  const testProjects: Project[] | null = [array containing two project];

  beforeEach(

    waitForAsync(() => {

      TestBed.configureTestingModule({

        imports: [

          CommonModule,

          BrowserModule,

          BrowserAnimationsModule,

          MaterialModule,

        ],

        declarations: [ProjectsTableComponent, BusinessUnitsBarComponent],

      }).compileComponents();

    })

  );

  beforeEach(() => {

    fixture = TestBed.createComponent(ProjectsTableComponent);

    component = fixture.componentInstance;

  });

  it('should create', () => {

    expect(component).toBeTruthy();

  });

  it('projects input should populate table', fakeAsync(() => {

    finishInit(fixture);

    component.projects = testProjects;

    fixture.detectChanges();

    flush();

    const compiled = fixture.debugElement.nativeElement;

    expect(compiled.querySelectorAll('.mat-row').length).toBeGreaterThanOrEqual(

      2

    );

  }));

});

/** Finish initializing the virtual scroll component at the beginning of a test. */

function finishInit(fixture: ComponentFixture) {

  // On the first cycle we render and measure the viewport.

  fixture.detectChanges();

  flush();

  // On the second cycle we render the items.

  fixture.detectChanges();

  flush();

  // Flush the initial fake scroll event.

  animationFrameScheduler.flush();

  flush();

  fixture.detectChanges();

}

my component.html

<!-- projectNumber Column -->
<ng-container matColumnDef="projectNumber">
  <th mat-header-cell *matHeaderCellDef mat-sort-header>
    <div class="forcePrimary">
      {{ getColumnLabel('projectNumber') }}
    </div>
  </th>
  <td mat-cell *matCellDef="let element">
    {{ element.projectNumber }}
  </td>
</ng-container>

<tr mat-header-row *matHeaderRowDef="displayedColumns; sticky: true"></tr>

<tr class="mat-row" *matNoDataRow>
  <td class="mat-cell" [attr.colspan]="displayedColumns.length">
    {{ noDataMessage }}
  </td>
</tr>

<tr
  style="cursor: pointer"
  mat-row
  *matRowDef="let row; columns: displayedColumns"
  (click)="selectedProject.emit(row)"
></tr>

my component.ts

export class ProjectsTableComponent implements OnInit {
columns!: TableColumn[];
displayedColumns!: string[];
dataSource = new TableVirtualScrollDataSource();
private sort!: MatSort;
noDataMessage = MatTableConstants.noDataMessage;
@ViewChild(MatSort, { static: false }) set matSort(ms: MatSort) {
this.sort = ms;
this.setDataSourceAttributes();
}
@input() set projects(val: Project[] | null) {
// eslint-disable-next-line @typescript-eslint/no-explicit-any
this.dataSource.data = val as any;
}
@output() selectedProject = new EventEmitter();
constructor() {}

ngOnInit(): void {
this.columns = [
{
fieldName: 'projectNumber',
label: 'Project Number',
},
];
this.displayedColumns = this.columns.map((x) => x.fieldName);
}

getColumnLabel(fieldName: string): string | null {
return this.columns.find((x) => x.fieldName === fieldName)?.label || null;
}

private setDataSourceAttributes() {
this.dataSource.sort = this.sort;
this.dataSource.sortingDataAccessor = MatTableConstants.pathDataAccessor;
}
}

@diprokon
Copy link
Owner

Hi!
Do you still have this problem?
Maybe, you need to set height for viewport container - it's a common issue

@Zoliqa
Copy link

Zoliqa commented Dec 15, 2022

Hi, I'm actually having the same problem. I've tried to set the height of the viewport container and also some suggestions found here https://stackoverflow.com/questions/53726484/angular-material-virtual-scroll-not-rendering-items-in-unit-tests but nothing seems to work.
My Code looks like this:

html:

<cdk-virtual-scroll-viewport
tvsItemSize="48"
style="height: 300px; width: 300px"

<table_
style="width: 100%; height: 300px"
mat-table
multiTemplateDataRows
[dataSource]="dataSource"
class="mat-elevation-z8"

<!--- Note that these columns can be defined in any order.
      The actual rendered columns are set as a property on the row definition" -->

<!-- Position Column -->
<ng-container matColumnDef="position">
  <th mat-header-cell *matHeaderCellDef>No.</th>
  <td mat-cell *matCellDef="let element">
    <div class="position">11{{ element.position }}</div>
  </td>
</ng-container>

<!-- Name Column -->
<ng-container matColumnDef="name">
  <th mat-header-cell *matHeaderCellDef>Name</th>
  <td mat-cell *matCellDef="let element">{{ element.name }}</td>
</ng-container>

<!-- Weight Column -->
<ng-container matColumnDef="weight">
  <th mat-header-cell *matHeaderCellDef>Weight</th>
  <td mat-cell *matCellDef="let element">{{ element.weight }}</td>
</ng-container>

<!-- Symbol Column -->
<ng-container matColumnDef="symbol">
  <th mat-header-cell *matHeaderCellDef>Symbol</th>
  <td mat-cell *matCellDef="let element">{{ element.symbol }}</td>
</ng-container>

<tr mat-header-row *matHeaderRowDef="displayedColumns"></tr>
<tr mat-row *matRowDef="let row; columns: displayedColumns"></tr>

<tr class="mat-row" *matNoDataRow>
  <td class="mat-cell" [attr.colspan]="4">No records found</td>
</tr>

</table_>

code:

export interface PeriodicElement {
name: string;
position: number;
weight: number;
symbol: string;
}

const ELEMENT_DATA: PeriodicElement[] = [...Array(1000).keys()].map((i) => ({
position: i + 1,
name: 'Hydrogen',
weight: 1.0079,
symbol: 'H'
}));

@component({
selector: 'nipex-test-table',
templateUrl: './test-table.component.html',
styleUrls: ['./test-table.component.scss']
})
export class TestTableComponent implements OnInit {
displayedColumns: string[] = ['position', 'name', 'weight', 'symbol'];
dataSource: any;

constructor() {}

ngOnInit(): void {
this.dataSource = new TableVirtualScrollDataSource(
ELEMENT_DATA
);
}
}

spec:

describe('TestTableComponent', () => {
let component: TestTableComponent;
let fixture: ComponentFixture;

const finishInit = () => {
// On the first cycle we render and measure the viewport.
fixture.detectChanges();
flush();

// On the second cycle we render the items.
fixture.detectChanges();
flush();

animationFrameScheduler.flush();
flush();
fixture.detectChanges();

};

beforeEach(async () => {
await TestBed.configureTestingModule({
declarations: [TestTableComponent],
imports: [SharedModule, NoopAnimationsModule],
schemas: [NO_ERRORS_SCHEMA],
providers: []
}).compileComponents();

fixture = TestBed.createComponent(TestTableComponent);
component = fixture.componentInstance;
// fixture.detectChanges();

});

it('should create', () => {
expect(component).toBeTruthy();
});

it('should render rows', fakeAsync(() => {
finishInit();

// fixture.autoDetectChanges(); // <---
// tick(500);

const el = fixture.nativeElement as HTMLElement;

console.log(el.outerHTML);

const rowEls = Array.from(el.querySelectorAll('tbody tr')) as HTMLElement[];

expect(rowEls.length).toBe(3);

}));
});

In the test, no rows are rendered. The "No record found" label is shown all the time.
Any suggestion would be highly appreciated.

@DamienBraillard
Copy link

I found a trick that is working to render (at least some) rows while testing. This at least works for angular applications (I use Angular 12 as for now).

In your test.ts file add the following setup function:

// Ensure that the cdk-virtual-scroll-viewport is not fully squashed so that at least one row is rendered
beforeAll(() => {
  const styleElement = document.createElement('style');

  // Adjust min-width and min-height to display the amount of items you need (with 100px, at least one row should be shown)
  styleElement.textContent = 'cdk-virtual-scroll-viewport { min-width: 100px !important; min-height: 100px !important; }';
  styleElement.id = 'custom-test-style-virtual-scroll-viewport';

  document.head.append(styleElement);
});

This adds an extra style when testing. It ensures that the cdk-virtual-scroll-viewport wrapping the table has a minimum size. This causes the virtual scroll to render items (at least one in my case).

Note that I use 100px as I only provide one item when testing. You might want to increase min-width and/or min-height if needed.

For reference, this is my full test.ts file:

// This file is required by karma.conf.js and loads recursively all the .spec and framework files
// @formatter:off
import 'zone.js/dist/zone-testing'; // Keep this first otherwise tests will fail (https://stackoverflow.com/questions/67414283/error-zone-testing-js-is-needed-for-the-fakeasync-test-helper-but-could-not-b/70000591#70000591)
import { getTestBed } from '@angular/core/testing';
import { BrowserDynamicTestingModule, platformBrowserDynamicTesting } from '@angular/platform-browser-dynamic/testing';
// @formatter:on

declare const require: {
  context(
    path: string,
    deep?: boolean,
    filter?: RegExp
  ): {
    keys(): string[];
    <T>(id: string): T;
  };
};

// Ensure that the cdk-virtual-scroll-viewport is not fully squashed so that at least one row is rendered
beforeAll(() => {
  const styleElement = document.createElement('style');

  // Adjust min-width and min-height to display the amount of items you need (with 100px, at least one row should be shown)
  styleElement.textContent = 'cdk-virtual-scroll-viewport { min-width: 100px !important; min-height: 100px !important; }';
  styleElement.id = 'custom-test-style-virtual-scroll-viewport';

  document.head.append(styleElement);
});

// First, initialize the Angular testing environment.
getTestBed().initTestEnvironment(BrowserDynamicTestingModule, platformBrowserDynamicTesting());
// Then we find all the tests.
const context = require.context('./', true, /\.spec\.ts$/);
// And load the modules.
context.keys().map(context);

Hope this helps.

@Zoliqa
Copy link

Zoliqa commented Jan 19, 2023

Hi Damien. Thanks for your suggestion. I've tried it out but without any success. There are still no rows rendered in my case. It seems to me that the height of the table is not set and that could be the issue.

@DamienBraillard
Copy link

DamienBraillard commented Jan 19, 2023

Hi Damien. Thanks for your suggestion. I've tried it out but without any success. There are still no rows rendered in my case. It seems to me that the height of the table is not set and that could be the issue.

Things you can try is to set the width and height of the <table> nested in the <cdk-virtual-scroll-viewport> to 100%.

Also, maybe try with the following setup CSS style:

div[id^=root] { width: 500px !important; height: 500px !important; overflow: scroll; opacity: 0; }

I had this as working global setup for tests before removing @angular\flex-layout and migrating ngx-datatable to material table.

beforeAll(() => {
  const styleElement = document.createElement('style');

  // Container of the tested component (A <div></div> with an id start starts with 'root'):
  //  - Set width to 500px x 500px
  //    This is particularly required for tables/grids that uses virtualization for their items.
  //    See: https://github.com/diprokon/ng-table-virtual-scroll/issues/103
  //  - Make it transparent and set overflow to scroll so that it does not screw up the test results display
  styleElement.textContent = 'div[id^=root] { width: 500px !important; height: 500px !important; overflow: scroll; opacity: 0; }';
  styleElement.id = 'custom-test-style-virtual-scroll-viewport';

  document.head.append(styleElement);
});

In the end, I had to find a style that ensured the size of the <cdk-virtual-scroll-viewport> element was not zero in any direction. To do so, I did put a breakpoint in one of the failing test, then I tried to see in chrome what CSS I could alter so that the row would render. That was tedious but it finally worked.

I really hope you find a solution. This has haunted me quite a bit.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

4 participants