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.
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 { }
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}`);
}
<cdk-virtual-scroll-viewport itemSize="50" class="example-viewport">
<div *cdkVirtualFor="let item of items" class="example-item">{{ item }}</div>
</cdk-virtual-scroll-viewport>
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.
});
});
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.
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.
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.
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.
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);
}));
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.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With