/** * @license * Copyright Google LLC All Rights Reserved. * * Use of this source code is governed by an MIT-style license that can be * found in the LICENSE file at https://angular.dev/license */ import { ComponentHarness, ComponentHarnessConstructor, HarnessPredicate, parallel, } from '@angular/cdk/testing'; import {DividerHarnessFilters, MatDividerHarness} from '@angular/material/divider/testing'; import {BaseListItemHarnessFilters, SubheaderHarnessFilters} from './list-harness-filters'; import {MatSubheaderHarness} from './list-item-harness-base'; /** Represents a section of a list falling under a specific header. */ export interface ListSection { /** The heading for this list section. `undefined` if there is no heading. */ heading?: string; /** The items in this list section. */ items: I[]; } /** * Shared behavior among the harnesses for the various `MatList` flavors. * @template T A constructor type for a list item harness type used by this list harness. * @template C The list item harness type that `T` constructs. * @template F The filter type used filter list item harness of type `C`. * @docs-private */ export abstract class MatListHarnessBase< T extends ComponentHarnessConstructor & {with: (options?: F) => HarnessPredicate}, C extends ComponentHarness, F extends BaseListItemHarnessFilters, > extends ComponentHarness { protected _itemHarness: T; /** * Gets a list of harnesses representing the items in this list. * @param filters Optional filters used to narrow which harnesses are included * @return The list of items matching the given filters. */ async getItems(filters?: F): Promise { return this.locatorForAll(this._itemHarness.with(filters))(); } /** * Gets a list of `ListSection` representing the list items grouped by subheaders. If the list has * no subheaders it is represented as a single `ListSection` with an undefined `heading` property. * @param filters Optional filters used to narrow which list item harnesses are included * @return The list of items matching the given filters, grouped into sections by subheader. */ async getItemsGroupedBySubheader(filters?: F): Promise[]> { type Section = {items: C[]; heading?: Promise}; const listSections: Section[] = []; let currentSection: Section = {items: []}; const itemsAndSubheaders = await this.getItemsWithSubheadersAndDividers({ item: filters, divider: false, }); for (const itemOrSubheader of itemsAndSubheaders) { if (itemOrSubheader instanceof MatSubheaderHarness) { if (currentSection.heading !== undefined || currentSection.items.length) { listSections.push(currentSection); } currentSection = {heading: itemOrSubheader.getText(), items: []}; } else { currentSection.items.push(itemOrSubheader); } } if ( currentSection.heading !== undefined || currentSection.items.length || !listSections.length ) { listSections.push(currentSection); } // Concurrently wait for all sections to resolve their heading if present. return parallel(() => listSections.map(async s => ({items: s.items, heading: await s.heading})), ); } /** * Gets a list of sub-lists representing the list items grouped by dividers. If the list has no * dividers it is represented as a list with a single sub-list. * @param filters Optional filters used to narrow which list item harnesses are included * @return The list of items matching the given filters, grouped into sub-lists by divider. */ async getItemsGroupedByDividers(filters?: F): Promise { const listSections: C[][] = [[]]; const itemsAndDividers = await this.getItemsWithSubheadersAndDividers({ item: filters, subheader: false, }); for (const itemOrDivider of itemsAndDividers) { if (itemOrDivider instanceof MatDividerHarness) { listSections.push([]); } else { listSections[listSections.length - 1].push(itemOrDivider); } } return listSections; } /** * Gets a list of harnesses representing all of the items, subheaders, and dividers * (in the order they appear in the list). Use `instanceof` to check which type of harness a given * item is. * @param filters Optional filters used to narrow which list items, subheaders, and dividers are * included. A value of `false` for the `item`, `subheader`, or `divider` properties indicates * that the respective harness type should be omitted completely. * @return The list of harnesses representing the items, subheaders, and dividers matching the * given filters. */ getItemsWithSubheadersAndDividers(filters: { item: false; subheader: false; divider: false; }): Promise<[]>; getItemsWithSubheadersAndDividers(filters: { item?: F | false; subheader: false; divider: false; }): Promise; getItemsWithSubheadersAndDividers(filters: { item: false; subheader?: SubheaderHarnessFilters | false; divider: false; }): Promise; getItemsWithSubheadersAndDividers(filters: { item: false; subheader: false; divider?: DividerHarnessFilters | false; }): Promise; getItemsWithSubheadersAndDividers(filters: { item?: F | false; subheader?: SubheaderHarnessFilters | false; divider: false; }): Promise<(C | MatSubheaderHarness)[]>; getItemsWithSubheadersAndDividers(filters: { item?: F | false; subheader: false; divider?: false | DividerHarnessFilters; }): Promise<(C | MatDividerHarness)[]>; getItemsWithSubheadersAndDividers(filters: { item: false; subheader?: false | SubheaderHarnessFilters; divider?: false | DividerHarnessFilters; }): Promise<(MatSubheaderHarness | MatDividerHarness)[]>; getItemsWithSubheadersAndDividers(filters?: { item?: F | false; subheader?: SubheaderHarnessFilters | false; divider?: DividerHarnessFilters | false; }): Promise<(C | MatSubheaderHarness | MatDividerHarness)[]>; async getItemsWithSubheadersAndDividers( filters: { item?: F | false; subheader?: SubheaderHarnessFilters | false; divider?: DividerHarnessFilters | false; } = {}, ): Promise<(C | MatSubheaderHarness | MatDividerHarness)[]> { const query = []; if (filters.item !== false) { query.push(this._itemHarness.with(filters.item || ({} as F))); } if (filters.subheader !== false) { query.push(MatSubheaderHarness.with(filters.subheader)); } if (filters.divider !== false) { query.push(MatDividerHarness.with(filters.divider)); } return this.locatorForAll(...query)(); } }