Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Angular Material virtual scroll not rendering items in unit tests

I am using cdk-virtual-scroll-viewport + cdkVirtualFor in my component and it seems to work fine.

However in the unit test of that component the items don't get rendered.

I made a sample app based on this example and while the example works when you serve the app, the test I wrote fails.

app.module.ts

import { ScrollingModule } from '@angular/cdk/scrolling';
import { BrowserModule } from '@angular/platform-browser';
import { BrowserAnimationsModule } from '@angular/platform-browser/animations';
import { NgModule } from '@angular/core';

import { AppComponent } from './app.component';

@NgModule({
  declarations: [
    AppComponent,
  ],
  imports: [
    BrowserModule,
    BrowserAnimationsModule,
    ScrollingModule,
  ],
  providers: [],
  bootstrap: [AppComponent]
})
export class AppModule { }

app.component.ts

import { Component, ChangeDetectionStrategy } from '@angular/core';

@Component({
  selector: 'app-root',
  templateUrl: './app.component.html',
  styleUrls: ['./app.component.scss'],
  changeDetection: ChangeDetectionStrategy.OnPush,
})
export class AppComponent {
  items = Array.from({length: 100000}).map((_, i) => `Item #${i}`);
}

app.component.html

<cdk-virtual-scroll-viewport itemSize="50" class="example-viewport">
  <div *cdkVirtualFor="let item of items" class="example-item">{{ item }}</div>
</cdk-virtual-scroll-viewport>

app.component.spec.ts

import { TestBed, async } from '@angular/core/testing';
import { AppComponent } from './app.component';
import { ScrollingModule } from '@angular/cdk/scrolling';

describe('AppComponent', () => {
  beforeEach(async(() => {
    TestBed.configureTestingModule({
      declarations: [
        AppComponent
      ],
      imports: [
        ScrollingModule,
      ],
    }).compileComponents();
  }));

  it('should render at least 4 items', () => {
    const fixture = TestBed.createComponent(AppComponent);
    fixture.detectChanges();
    const compiled = fixture.debugElement.nativeElement as HTMLElement;
    expect(compiled.querySelectorAll('.example-item').length).toBeGreaterThanOrEqual(4); // <-- Error: Expected 0 to be greater than or equal 4.
  });
});
like image 816
moriesta Avatar asked Dec 11 '18 14:12

moriesta


People also ask

What is Virtual Scrolling in angular?

Introducing Virtual Scrolling in Angular. The ScrollingModule takes a large list of data and dynamically loads and unloads data from the DOM only when it comes into the users view. This means that as the user scrolls down a list more elements will appear, and those which have gone from the users view will be removed.

What are the disadvantages of virtual rendering in angular?

The problem with that is that so many elements in the DOM can cause slow initial rendering, laggy scrolling, and dirty checking on each one of them in the context of Angular can be expensive. The central concept behind virtual rendering is rendering only the visible items.

What is the scrolling module used for?

This means that as the user scrolls down a list more elements will appear, and those which have gone from the users view will be removed. Lets look at how you can use the Scrolling Module.

Why do we need a “load more” button in angular?

Perhaps you’ve used a “load more” button or paginination to limit the overload that would come with dumping a huge list on your users (l)app. Introducing Virtual Scrolling in Angular. The ScrollingModule takes a large list of data and dynamically loads and unloads data from the DOM only when it comes into the users view.


2 Answers

Changing the unit test (see in the question) to following resolved it:

it('should render at least 4 items', fakeAsync(() => { // <---
  const fixture = TestBed.createComponent(AppComponent);
  fixture.autoDetectChanges(); // <--- 
  tick(500); // <---
  const compiled = fixture.debugElement.nativeElement as HTMLElement;
  expect(compiled.querySelectorAll('.example-item').length).toBeGreaterThanOrEqual(4);
}));
like image 154
moriesta Avatar answered Sep 18 '22 12:09

moriesta


Somehow the solution from the previous answer didn't work for my case. Also the alternative solution proposed in github with flush() didn't work either.

So my final approach was to check how Google wrote their unit tests for the virtual viewport component. There I noticed that they used a function finishInit which is a local function in their unit test.

/** Finish initializing the virtual scroll component at the beginning of a test. */
function finishInit(fixture: ComponentFixture<any>) {
  // 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();
}

Copied it into my unit test and that seems to work in my case. It also kinda explains what is happening in the backend.

like image 32
cklam Avatar answered Sep 20 '22 12:09

cklam