
Angular Material MatSort 测试 Harness 完全指南MatSortHarness 与 MatSortHeaderHarness 的 API 与实战【免费下载链接】componentsComponent infrastructure and Material Design components for Angular项目地址: https://gitcode.com/GitHub_Trending/co/components本指南基于当前仓库中 goldens/material/sort/testing/index.api.md 这份 API Extractor 生成的公开 API 报告系统讲解 Angular MaterialmatSort/mat-sort-header组件测试工具Harness的完整公共接口、底层实现与实战用法。读完本文你将掌握如何通过MatSortHarness与MatSortHeaderHarness在组件测试中稳定地定位排序列、读取/触发排序状态、断言禁用与激活行为并理解这些能力背后依赖的 CDK Component Harness 架构与aria-sort语义。一、为什么需要 Sort 测试 HarnessmatSort指令与mat-sort-header组件用于为表格数据添加排序状态与可视化反馈详见 sort.md。在编写单元测试时如果直接通过 DOM 查询去定位排序表头代码会高度依赖组件内部模板结构与 CSS 类名一旦内部实现调整例如按钮包裹结构、图标投影方式变化测试就会大面积失效。Component Harness 是angular/cdk/testing提供的测试抽象层它把如何找到元素、如何读取状态、如何触发交互封装为稳定的公共 API测试代码只与 Harness 的公开方法打交道。MatSortHarness与MatSortHeaderHarness正是 Angular Material 为排序功能提供的这一层测试接口测试通过HarnessPredicate做精确过滤从而获得与实现解耦、可跨测试环境TestBed、Protractor、WebDriver 等复用的能力。二、公共 API 全景从 Golden 报告出发goldens/material/sort/testing/index.api.md记录了angular/material_sort_testing包的全部公开导出包括两个 Harness 类、两个过滤器接口以及它们依赖的angular/cdk/testing基础类型。2.1 MatSortHarnessimport { BaseHarnessFilters } from angular/cdk/testing; import { ComponentHarness } from angular/cdk/testing; import { HarnessPredicate } from angular/cdk/testing; // public export class MatSortHarness extends ComponentHarness { getActiveHeader(): PromiseMatSortHeaderHarness | null; getSortHeaders(filter?: SortHeaderHarnessFilters): PromiseMatSortHeaderHarness[]; static hostSelector: string; static with(options?: SortHarnessFilters): HarnessPredicateMatSortHarness; }MatSortHarness代表整个matSort容器即带有matSort指令的父元素职责是定位排序容器通过静态属性hostSelector实现值为.mat-sort见 sort-harness.ts找到宿主元素获取全部表头getSortHeaders(filter?)返回容器内所有或符合过滤条件的MatSortHeaderHarness实例获取当前激活排序列getActiveHeader()遍历所有表头返回第一个isActive()为真的表头没有任何列处于排序激活状态时返回null构造过滤器静态方法with(options?)返回HarnessPredicateMatSortHarness用于在测试中按条件筛选排序容器实例。2.2 MatSortHeaderHarness// public export class MatSortHeaderHarness extends ComponentHarness { click(): Promisevoid; getLabel(): Promisestring; getSortDirection(): PromiseSortDirection; static hostSelector: string; isActive(): Promiseboolean; isDisabled(): Promiseboolean; static with(options?: SortHeaderHarnessFilters): HarnessPredicateMatSortHeaderHarness; }MatSortHeaderHarness代表单个mat-sort-header提供对单个排序列的完整状态读取与交互能力方法返回值语义click()Promisevoid点击表头以切换排序方向仅在启用状态下有效见下方实现说明getLabel()Promisestring读取表头的文本标签即列标题文本getSortDirection()PromiseSortDirection读取当前排序方向取值为asc、desc或未排序isActive()Promiseboolean当前表头是否处于排序激活状态isDisabled()Promiseboolean当前表头是否被禁用static with(options?)HarnessPredicate...按标签文本或排序方向构造过滤条件static hostSelectorstring宿主选择器实现值为.mat-sort-headerSortDirection类型来自 sort.ts经 sort-header-harness.ts 引入与业务侧matSortChange事件中携带的方向保持一致。2.3 过滤器接口// public (undocumented) export interface SortHarnessFilters extends BaseHarnessFilters { } // public (undocumented) export interface SortHeaderHarnessFilters extends BaseHarnessFilters { label?: string | RegExp; sortDirection?: SortDirection; }SortHarnessFilters继承 CDK 的BaseHarnessFilters本身未新增字段用于未来扩展也用于限定MatSortHarness.with()的筛选维度SortHeaderHarnessFilters在基础过滤之上新增两个维度label按表头文本过滤支持精确字符串与正则表达式sortDirection按排序方向过滤asc/desc/。两个接口的定义位于 sort-harness-filters.ts完整导出链为 public-api.ts → index.ts。三、源码级实现剖析3.1 hostSelectorHarness 与 DOM 的契约Harness 通过静态hostSelector完成从逻辑查找到DOM 查找的映射// src/material/sort/testing/sort-harness.ts export class MatSortHarness extends ComponentHarness { static hostSelector .mat-sort; ... } // src/material/sort/testing/sort-header-harness.ts export class MatSortHeaderHarness extends ComponentHarness { static hostSelector .mat-sort-header; ... }这意味着宿主的matSort指令与mat-sort-header组件在渲染时会分别应用.mat-sort与.mat-sort-header类Harness 才能据此定位。从 sort-header.ts 可以看到宿主绑定中还维护了[attr.aria-sort]与[class.mat-sort-header-disabled]这两者恰好是下文两个核心状态读取方法的直接数据来源。3.2 getSortDirection()以 aria-sort 为准的状态读取MatSortHeaderHarness.getSortDirection()的底层实现是读取宿主元素的aria-sort属性并做语义映射sort-header-harness.tsasync getSortDirection(): PromiseSortDirection { const host await this.host(); const ariaSort await host.getAttribute(aria-sort); if (ariaSort ascending) { return asc; } else if (ariaSort descending) { return desc; } return ; }也就是说Harness 的排序状态完全依赖无障碍语义属性aria-sort取值ascending/descending未排序时组件不输出该属性或输出none。这既是实现事实也暗示了一个约束测试中断言排序方向时实际验证的是组件是否正确维护了aria-sort语义。这一点在业务组件自身的单元测试 sort.spec.ts 中同样得到验证——点击表头后aria-sort会在none→ascending→descending→none之间轮转。3.3 isActive() 与 isDisabled() 的实现async isActive(): Promiseboolean { return !!(await this.getSortDirection()); } async isDisabled(): Promiseboolean { return (await this.host()).hasClass(mat-sort-header-disabled); }isActive()是getSortDirection()的非空判断只要存在排序方向asc或desc即视为激活未排序返回falseisDisabled()检查宿主是否带有.mat-sort-header-disabled类与 sort-header.scss 中定义的禁用样式类一致绑定逻辑见 sort-header.ts。3.4 getLabel()读取列标题文本private _container this.locatorFor(.mat-sort-header-container); async getLabel(): Promisestring { return (await this._container()).text(); }标签文本取自表头内部.mat-sort-header-container容器的文本内容。在mat-table场景下由于默认使用列 id 作为表头 id见 sort.md 中 Using sort with the mat-table 一节getLabel()返回的文本通常即为列标题。3.5 click()触发排序交互async click(): Promisevoid { return (await this.host()).click(); }click()直接点击宿主元素。注意测试用例注释明确说明仅在表头启用时有效——当表头通过[disabled]或容器级matSortDisabled被禁用时mat-sort-header内部会忽略排序切换对应 sort.md 的 Disabling sorting 一节因此测试中应先通过isDisabled()判断可点击性。3.6 getActiveHeader()遍历查找激活列async getActiveHeader(): PromiseMatSortHeaderHarness | null { const headers await this.getSortHeaders(); for (let i 0; i headers.length; i) { if (await headers[i].isActive()) { return headers[i]; } } return null; }该方法线性遍历所有表头并返回第一个激活项由于排序语义上同一时刻只有一个激活列该实现是正确且高效的。未排序时返回null。四、实战在 TestBed 中驱动排序测试当前仓库自带完整可运行的单测示例 sort-harness.spec.ts其测试组件包含一个五列可排序的matSort表格Dessert / Calories / Fat / Carbs / Protein第三列 Fat 可通过 signal 动态禁用。以下结合该文件梳理标准用法。4.1 初始化 HarnessLoaderimport {HarnessLoader, parallel} from angular/cdk/testing; import {TestbedHarnessEnvironment} from angular/cdk/testing/testbed; import {MatSortModule, Sort} from ../../sort; import {MatSortHarness} from ./sort-harness; fixture TestBed.createComponent(SortHarnessTest); fixture.detectChanges(); loader TestbedHarnessEnvironment.loader(fixture);通过TestbedHarnessEnvironment.loader(fixture)创建HarnessLoader之后所有 Harness 查找都经由该 loader 完成。4.2 加载容器与表头const sort await loader.getHarness(MatSortHarness); // 找到唯一的 matSort 容器 const headers await sort.getSortHeaders(); // 获取全部 5 个表头当页面存在多个排序表格时可用loader.getAllHarnesses(MatSortHarness)获取全部容器或通过MatSortHarness.with()组合BaseHarnessFilters如ancestor、selector等精确锁定目标。4.3 用过滤器做定向断言按标签文本过滤精确字符串const headers await sort.getSortHeaders({label: Carbs}); expect(headers.length).toBe(1); expect(await headers[0].getLabel()).toBe(Carbs);按正则过滤const headers await sort.getSortHeaders({label: /^C/}); // 匹配 Calories 与 Carbs按排序方向过滤初始全部未排序时{sortDirection: }命中 5 个点击第一个表头后再查{sortDirection: asc}仅命中 1 个let headers await sort.getSortHeaders({sortDirection: }); expect(headers.length).toBe(5); await headers[0].click(); headers await sort.getSortHeaders({sortDirection: asc}); expect(headers.length).toBe(1);label与sortDirection两个过滤条件在MatSortHeaderHarness.with()中通过HarnessPredicate.stringMatches与getLabel()/getSortDirection()联动实现sort-header-harness.ts因此过滤器断言与直接方法断言基于同一套数据源结果天然一致。4.4 状态读取与交互断言禁用状态动态切换后重新检测变更const thirdHeader (await sort.getSortHeaders())[2]; expect(await thirdHeader.isDisabled()).toBe(false); fixture.componentInstance.disableThirdHeader.set(true); fixture.detectChanges(); expect(await thirdHeader.isDisabled()).toBe(true);激活状态与方向轮转expect(await secondHeader.isActive()).toBe(false); await secondHeader.click(); expect(await secondHeader.isActive()).toBe(true); expect(await secondHeader.getSortDirection()).toBe(asc); await secondHeader.click(); expect(await secondHeader.getSortDirection()).toBe(desc);激活列定位expect(await sort.getActiveHeader()).toBeNull(); // 初始无排序 await fifthHeader.click(); const activeHeader await sort.getActiveHeader(); expect(activeHeader).toBeTruthy(); expect(await activeHeader!.getLabel()).toBe(Protein);批量读取可用parallel(() headers.map(h h.getLabel()))并行执行异步调用避免串行等待这是 CDK Testing 推荐的性能模式测试文件 sort-harness.spec.ts 中即有应用。4.5 完整测试组件模板table matSort (matSortChange)sortData($event) tr th mat-sort-headernameDessert/th th mat-sort-headercaloriesCalories/th th mat-sort-headerfat [disabled]disableThirdHeader()Fat/th th mat-sort-headercarbsCarbs/th th mat-sort-headerproteinProtein/th /tr for (dessert of sortedData; track dessert) { tr td{{dessert.name}}/td td{{dessert.calories}}/td td{{dessert.fat}}/td td{{dessert.carbs}}/td td{{dessert.protein}}/td /tr } /tablemat-sort-header的id如name、calories用于标识排序列matSortChange事件携带{active, direction}供业务侧排序数据测试中通过sortData(sort: Sort)实现升/降序排列。在mat-table中若不给表头显式指定 id则默认使用列 id见 sort.md。五、构建与依赖关系MatSortHarness/MatSortHeaderHarness的构建配置见 testing/BUILD.bazeltesting目标编译全部非 spec 的 TS 文件其依赖仅为//src/cdk/testing提供ComponentHarness、HarnessPredicate、BaseHarnessFilters与//src/material/sort提供SortDirection及排序组件本体unit_tests_lib额外依赖angular/core、angular/platform-browser与//src/cdk/testing/testbed提供TestbedHarnessEnvironment。从依赖结构可以看出Sort 测试 Harness 是纯 CDK 抽象层的薄封装——不依赖任何具体测试运行器这也是它可以跨 TestBed / Protractor / WebDriver 环境复用的根本原因。六、使用建议与注意事项优先通过 Harness 断言而非直接查询 DOMgetSortDirection()等方法的输出绑定aria-sort语义天然与 sort.md 中强调的无障碍契约一致测试即文档区分禁用与未激活isDisabled()反映用户能否操作isActive()反映是否正在排序两者独立需要验证点击无效时应先用isDisabled()断言前置条件掌握方向轮转规律默认排序起点为asc点击后依次asc→desc→ 取消排序可通过matSortStartdesc全局反转、单表头startdesc局部反转或用matSortDisableClear/disableClear阻止取消排序sort.md Changing the sort order 一节——Harness 断言时应与这些业务配置保持一致过滤器优先使用label定位列标题在真实表格中具有唯一性用{label: Carbs}或正则/^C/比按数组下标取元素更稳健也能避免因列顺序调整导致的脆弱断言SortDirection类型三态asc/desc/空字符串表示未排序编写过滤器与断言时不要遗漏空态分支。七、小结MatSortHarness与MatSortHeaderHarness构成了 Angular Material 排序功能的官方测试入口前者负责容器级定位与激活列查询后者负责单列的标签读取、方向读取、禁用/激活判断与点击交互两者通过SortHarnessFilters/SortHeaderHarnessFilters实现精确过滤。其实现以aria-sort属性与.mat-sort-header-disabled类为数据源与组件运行时状态严格同源保证了测试断言与真实渲染行为的一致性。结合仓库中的 API 报告、Harness 源码、过滤器定义 与 完整测试套件你可以直接参照本节示例为任何使用matSort的表格编写稳健、可维护的排序行为测试。【免费下载链接】componentsComponent infrastructure and Material Design components for Angular项目地址: https://gitcode.com/GitHub_Trending/co/components创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考