如何测试mat-select中的CDK虚拟滚动功能?
Angular MatSelect 结合 CdkVirtualScroll 的测试问题
场景代码
<mat-select hideSingleSelectionIndicator [formControl]="selectedItem" (openedChange)="onOpenedChange($event)" > <cdk-virtual-scroll-viewport [itemSize]="OPTION_HEIGHT" [style.height.px]="scrollViewportHeight()" > <mat-option *cdkVirtualFor="let item of items()" [value]="item.value"> {{ item.viewValue }} </mat-option> </cdk-virtual-scroll-viewport> </mat-select>
测试代码
it('should display the first options', async () => { const select = await loader.getHarness(MatSelectHarness); await select.open(); const options = await select.getOptions(); expect(options.length).toBe(10); // ❔ 为何默认是10? expect(await options[0].getText()).toBe('Item #0'); // ✔️ 通过 expect(await options[9].getText()).toBe('Item #9'); // ✔️ 通过 }); it('should be able to scroll options', async () => { const select = await loader.getHarness(MatSelectHarness); await select.open(); const scrollViewPort = fixture.componentInstance.scrollViewPort() as CdkVirtualScrollViewport; expect(scrollViewPort).toBeTruthy(); scrollViewPort.scrollable.scrollTo({ top: 8000 }); const options = await select.getOptions(); expect(options.length).toBe(10); expect(await options[0].getText()).toBe('Item #100'); // ❌ 失败 :( expect(await options[9].getText()).toBe('Item #109'); // ❌ 失败 :( });
环境说明:基于Angular Components示例修改,测试框架使用Jasmine,实际项目采用Jest >=29 + JSDom >=26
核心问题
- 如何触发滚动事件,使
await select.getOptions()能获取到非初始加载的选项? - 控制台为何会抛出“e.scrollTo is not a function”错误?
解决方案
问题2:错误原因与修复
scrollViewPort.scrollable是CdkScrollable实例,但JSDom环境下,底层DOM元素并不支持原生scrollTo方法,直接调用会触发环境兼容错误。
正确做法:使用CdkVirtualScrollViewport提供的scrollToIndex方法——这是虚拟滚动专属API,无需依赖浏览器原生滚动,更适配测试环境:
// 替换原scrollable.scrollTo调用 scrollViewPort.scrollToIndex(100); // 直接滚动到第100条数据的位置
问题1:触发滚动并获取更新后的选项
虚拟滚动的视图更新是异步的,滚动后需等待Angular变更检测完成,确保Harness能捕获到更新后的DOM元素。修改后的测试代码如下:
it('should be able to scroll options', async () => { const select = await loader.getHarness(MatSelectHarness); await select.open(); const scrollViewPort = fixture.componentInstance.scrollViewPort() as CdkVirtualScrollViewport; expect(scrollViewPort).toBeTruthy(); // 使用虚拟滚动专属API滚动到目标索引 scrollViewPort.scrollToIndex(100); // 触发变更检测并等待异步更新完成 fixture.detectChanges(); await fixture.whenStable(); const options = await select.getOptions(); expect(options.length).toBe(10); expect(await options[0].getText()).toBe('Item #100'); expect(await options[9].getText()).toBe('Item #109'); });
补充:默认仅显示10个选项的原因
这是CdkVirtualScrollViewport的性能优化机制——仅渲染当前视口可见范围的条目,加上少量上下缓冲条目。默认显示10条是因为scrollViewportHeight()计算的视口高度与itemSize的搭配,刚好能容纳10条数据(含缓冲)。
内容的提问来源于stack exchange,提问作者fservantdev
相关产品推荐
相关产品推荐

