/** * @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, ContentContainerComponentHarness, HarnessPredicate, parallel, } from '@angular/cdk/testing'; import {BaseListItemHarnessFilters, SubheaderHarnessFilters} from './list-harness-filters'; const iconSelector = '.mat-mdc-list-item-icon'; const avatarSelector = '.mat-mdc-list-item-avatar'; /** * Gets a `HarnessPredicate` that applies the given `BaseListItemHarnessFilters` to the given * list item harness. * @template H The type of list item harness to create a predicate for. * @param harnessType A constructor for a list item harness. * @param options An instance of `BaseListItemHarnessFilters` to apply. * @return A `HarnessPredicate` for the given harness type with the given options applied. */ export function getListItemPredicate( harnessType: ComponentHarnessConstructor, options: BaseListItemHarnessFilters, ): HarnessPredicate { return new HarnessPredicate(harnessType, options) .addOption('text', options.text, (harness, text) => HarnessPredicate.stringMatches(harness.getText(), text), ) .addOption('fullText', options.fullText, (harness, fullText) => HarnessPredicate.stringMatches(harness.getFullText(), fullText), ) .addOption('title', options.title, (harness, title) => HarnessPredicate.stringMatches(harness.getTitle(), title), ) .addOption('secondaryText', options.secondaryText, (harness, secondaryText) => HarnessPredicate.stringMatches(harness.getSecondaryText(), secondaryText), ) .addOption('tertiaryText', options.tertiaryText, (harness, tertiaryText) => HarnessPredicate.stringMatches(harness.getTertiaryText(), tertiaryText), ); } /** Harness for interacting with a list subheader. */ export class MatSubheaderHarness extends ComponentHarness { static hostSelector = '.mat-mdc-subheader'; static with(options: SubheaderHarnessFilters = {}): HarnessPredicate { return new HarnessPredicate(MatSubheaderHarness, options).addOption( 'text', options.text, (harness, text) => HarnessPredicate.stringMatches(harness.getText(), text), ); } /** Gets the full text content of the list item (including text from any font icons). */ async getText(): Promise { return (await this.host()).text(); } } /** Selectors for the various list item sections that may contain user content. */ export enum MatListItemSection { CONTENT = '.mdc-list-item__content', } /** Enum describing the possible variants of a list item. */ export enum MatListItemType { ONE_LINE_ITEM, TWO_LINE_ITEM, THREE_LINE_ITEM, } /** * Shared behavior among the harnesses for the various `MatListItem` flavors. * @docs-private */ export abstract class MatListItemHarnessBase extends ContentContainerComponentHarness { private _lines = this.locatorForAll('.mat-mdc-list-item-line'); private _primaryText = this.locatorFor('.mdc-list-item__primary-text'); private _avatar = this.locatorForOptional('.mat-mdc-list-item-avatar'); private _icon = this.locatorForOptional('.mat-mdc-list-item-icon'); private _unscopedTextContent = this.locatorFor('.mat-mdc-list-item-unscoped-content'); /** Gets the type of the list item, currently describing how many lines there are. */ async getType(): Promise { const host = await this.host(); const [isOneLine, isTwoLine] = await parallel(() => [ host.hasClass('mdc-list-item--with-one-line'), host.hasClass('mdc-list-item--with-two-lines'), ]); if (isOneLine) { return MatListItemType.ONE_LINE_ITEM; } else if (isTwoLine) { return MatListItemType.TWO_LINE_ITEM; } else { return MatListItemType.THREE_LINE_ITEM; } } /** * Gets the full text content of the list item, excluding text * from icons and avatars. * * @deprecated Use the `getFullText` method instead. * @breaking-change 16.0.0 */ async getText(): Promise { return this.getFullText(); } /** * Gets the full text content of the list item, excluding text * from icons and avatars. */ async getFullText(): Promise { return (await this.host()).text({exclude: `${iconSelector}, ${avatarSelector}`}); } /** Gets the title of the list item. */ async getTitle(): Promise { return (await this._primaryText()).text(); } /** Whether the list item is disabled. */ async isDisabled(): Promise { return (await this.host()).hasClass('mdc-list-item--disabled'); } /** * Gets the secondary line text of the list item. Null if the list item * does not have a secondary line. */ async getSecondaryText(): Promise { const type = await this.getType(); if (type === MatListItemType.ONE_LINE_ITEM) { return null; } const [lines, unscopedTextContent] = await parallel(() => [ this._lines(), this._unscopedTextContent(), ]); // If there is no explicit line for the secondary text, the unscoped text content // is rendered as the secondary text (with potential text wrapping enabled). if (lines.length >= 1) { return lines[0].text(); } else { return unscopedTextContent.text(); } } /** * Gets the tertiary line text of the list item. Null if the list item * does not have a tertiary line. */ async getTertiaryText(): Promise { const type = await this.getType(); if (type !== MatListItemType.THREE_LINE_ITEM) { return null; } const [lines, unscopedTextContent] = await parallel(() => [ this._lines(), this._unscopedTextContent(), ]); // First we check if there is an explicit line for the tertiary text. If so, we return it. // If there is at least an explicit secondary line though, then we know that the unscoped // text content corresponds to the tertiary line. If there are no explicit lines at all, // we know that the unscoped text content from the secondary text just wraps into the third // line, but there *no* actual dedicated tertiary text. if (lines.length === 2) { return lines[1].text(); } else if (lines.length === 1) { return unscopedTextContent.text(); } return null; } /** Whether this list item has an avatar. */ async hasAvatar(): Promise { return !!(await this._avatar()); } /** Whether this list item has an icon. */ async hasIcon(): Promise { return !!(await this._icon()); } }