0
 @ViewChild('scroller')
  scroller!: CdkVirtualScrollViewport;

  constructor(private ngZone: NgZone) { }

  ngAfterViewInit(): void {
    this.unsub = this.scroller.elementScrolled().pipe(
      map(() => this.scroller.measureScrollOffset('bottom')),
      pairwise(),
      filter(([y1, y2]) => (y2 < y1 && y2 < 140)),
      throttleTime(200)
    ).subscribe(() => {
      this.ngZone.run(() => {
        (this.maxItems > this.listItems.length) && this.fetchMore();
      });
    })
  }
<cdk-virtual-scroll-viewport class="example-viewport" #scroller itemSize="72">
  <table mat-table [dataSource]="dataSource" class="mat-elevation-z8">

    <ng-container matColumnDef="name">
      <th mat-header-cell *matHeaderCellDef> Name </th>
      <td mat-cell *matCellDef="let element"> {{element.name}} </td>
    </ng-container>
  
    <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; sticky: true"></tr>
    <tr mat-row *matRowDef="let row; columns: displayedColumns;"></tr>

  </table>
</cdk-virtual-scroll-viewport>

test case:

  it('should check \'ngAfterViewInit\'', () => {
    component.ngAfterViewInit();
    changeDetectorRef.detectChanges();
    spyOn(virtualScrollViewport, 'elementScrolled').and.callThrough();
    expect(virtualScrollViewport.elementScrolled).toHaveBeenCalled();
  });

The error trace what I got when mocking

        Error: Error: cdk-virtual-scroll-viewport requires the "itemSize" property to be set.
            at new CdkVirtualScrollViewport (http://localhost:9876/_karma_webpack_/node_modules/@angular/cdk/__ivy_ngcc__/fesm2015/scrolling.js:1264:1)
            at new MockCdkVirtualScrollViewport (http://localhost:9876/_karma_webpack_/main.js:27773:9)
            at Object.factory (http://localhost:9876/_karma_webpack_/node_modules/@angular/core/__ivy_ngcc__/fesm2015/core.js:17371:1)
            at R3Injector.hydrate (http://localhost:9876/_karma_webpack_/node_modules/@angular/core/__ivy_ngcc__/fesm2015/core.js:17247:42)
            at R3Injector.get (http://localhost:9876/_karma_webpack_/node_modules/@angular/core/__ivy_ngcc__/fesm2015/core.js:16997:1)
            at NgModuleRef$1.get (http://localhost:9876/_karma_webpack_/node_modules/@angular/core/__ivy_ngcc__/fesm2015/core.js:36383:1)
            at TestBedRender3.inject (http://localhost:9876/_karma_webpack_/node_modules/@angular/core/__ivy_ngcc__/fesm2015/testing.js:3227:1)
            at Function.inject (http://localhost:9876/_karma_webpack_/node_modules/@angular/core/__ivy_ngcc__/fesm2015/testing.js:3110:1)
            at UserContext.<anonymous> (http://localhost:9876/_karma_webpack_/src/app/components/gridview/gridview.component.spec.ts:112:37)
halfer
  • 19,824
  • 17
  • 99
  • 186
Anand Raja
  • 2,676
  • 1
  • 30
  • 35

2 Answers2

2

A possible implementation of the mock class could be like this:

import { CdkVirtualScrollViewport } from '@angular/cdk/scrolling';
import { Subject } from 'rxjs';

export class MockCdkVirtualScrollViewport implements CdkVirtualScrollViewport {
  scrolledIndexChange: Subject<number> = new Subject<number>();
  elementScrolled: Subject<Event> = new Subject<Event>();
  setRenderedRange(): void { }
  getRenderedRange(): any { }
  // add any other properties and methods that are needed for your test
}

You can use it in your tests like so:

import { ComponentFixture, TestBed } from '@angular/core/testing';
import { Component } from '@angular/core';
import { CdkVirtualScrollViewport } from '@angular/cdk/scrolling';
import { MockCdkVirtualScrollViewport } from './mock-cdk-virtual-scroll-viewport';

describe('YourComponent', () => {
  let component: YourComponent;
  let fixture: ComponentFixture<YourComponent>;
  let virtualScrollViewport: CdkVirtualScrollViewport;

  beforeEach(async () => {
    // configure the test module with the mock class
    await TestBed.configureTestingModule({
      declarations: [ YourComponent ],
      providers: [
        { provide: CdkVirtualScrollViewport, useClass: MockCdkVirtualScrollViewport }
      ]
    }).compileComponents();

    // create an instance of the component and the mock class
    fixture = TestBed.createComponent(YourComponent);
    component = fixture.componentInstance;
    virtualScrollViewport = TestBed.inject(CdkVirtualScrollViewport);
  });

  it('should do something', () => {
    // use the methods and properties of the mock class in your test
    // for example you can call the `setRenderedRange()` method and check that it works as expected
  });
});
sfelli
  • 176
  • 5
  • Could you please give a detail answer @SeF – Anand Raja Jan 18 '23 at 09:13
  • 1
    You can use the mock in the beforeEach section by add this in the providers array like so { provide: CdkVirtualScrollViewport, useClass: MockCdkVirtualScrollViewport } – sfelli Jan 18 '23 at 10:02
  • Hi @SeF Currently I'm getting the following error `Error: Error: cdk-virtual-scroll-viewport requires the "itemSize" property to be set.` My code: ` component.ngAfterViewInit(); changeDetectorRef.detectChanges(); spyOn(virtualScrollViewport, 'elementScrolled').and.callThrough(); expect(virtualScrollViewport.elementScrolled).toHaveBeenCalled(); ` – Anand Raja Jan 18 '23 at 12:17
  • To fix this error, you will need to set the "itemSize" property on the "cdk-virtual-scroll-viewport" component in your code to the appropriate value. – sfelli Jan 18 '23 at 12:49
  • `cdk-virtual-scroll-viewport` is the materal component from `ScrollingModule`. How to set `itemSize` here. I tried to add `itemSize` as property to `MockCdkVirtualScrollViewport` class. But it also didn't work. Could you please help me with this to fix the error – Anand Raja Jan 19 '23 at 05:24
  • The itemSize goes in the html like so
    {{ item }}
    – sfelli Jan 19 '23 at 08:12
  • yeah I've used it in HTML, but how to include the `itemSize` in test case.... that's where I get error – Anand Raja Jan 19 '23 at 08:14
  • In a unit test for a component that uses the element, you can set the itemSize property by directly manipulating the component's instance.like this: fixture.componentInstance.itemSize = 50; fixture.detectChanges(); – sfelli Jan 19 '23 at 10:30
  • I've added the test case and error what I got for the reference. `itemSize` is not the property of that consuming component. So, unable to do like this `fixture.componentInstance.itemSize = 50;`. I tried to bind the `itemSize` as property binding (`@Input`) inside `MockCdkVirtualScrollViewport` component. Nothing works.... could you please guide me further. – Anand Raja Jan 19 '23 at 11:08
  • Found any way to fix? – Anand Raja Jan 20 '23 at 05:37
  • You can try to set a static itemSize value in the MockCdkVirtualScrollViewport – sfelli Jan 20 '23 at 13:58
  • I tried that also `static itemSize = 130;`. But still the same error comes.... – Anand Raja Jan 21 '23 at 08:00
  • If you create a StackBlitz I can help you to fix this – sfelli Jan 23 '23 at 09:10
  • Please find the StackBlitz link https://stackblitz.com/edit/angular-ivy-fdcaaj?file=src%2Fapp%2Fapp.component.ts – Anand Raja Jan 24 '23 at 07:34
  • The spec file is empty ! – sfelli Jan 24 '23 at 08:52
  • I've added the test case now. Please have a look at them. – Anand Raja Jan 24 '23 at 10:51
  • I think it's related to you angular version you have the angular 7 I suppose – sfelli Jan 24 '23 at 13:09
  • No... this stackBlitz is using angular version 15... I tried angular 9(in my local)... Angular 15 needed to added `override` to override while mocking... all are same... same error only comes in both the versions – Anand Raja Jan 24 '23 at 13:20
  • I see, I let you know if I fix this – sfelli Jan 24 '23 at 13:29
  • Let us [continue this discussion in chat](https://chat.stackoverflow.com/rooms/251365/discussion-between-sfelli-and-anand-raja). – sfelli Jan 24 '23 at 15:17
1

I had the same issue. Initially added CdkVirtualScrollViewport to the providers list and with or without a mock it asked for "itemSize" to be set.

In the end removing CdkVirtualScrollViewport from the providers list and just adding ScrollingModule to the imports list worked. Just the single line.

Corné
  • 125
  • 1
  • 2
  • 14