license import { parallel } from './change-detection' ; import { TestElement } from './test-element' ; /** An async function that returns a promise when called. */ export type AsyncFactoryFn < T > = ( ) => Promise < T > ; /** An async function that takes an item and returns a boolean promise */ export type AsyncPredicate < T > = ( item : T ) => Promise < boolean > ; /** An async function that takes an item and an option value and returns a boolean promise. */ export type AsyncOptionPredicate < T , O > = ( item : T , option : O ) => Promise < boolean > ; /**
 * A query for a `ComponentHarness`, which is expressed as either a `ComponentHarnessConstructor` or
 * a `HarnessPredicate`.
 */ export type HarnessQuery < T extends ComponentHarness > = ComponentHarnessConstructor < T > | HarnessPredicate < T > ; /**
 * The result type obtained when searching using a particular list of queries. This type depends on
 * the particular items being queried.
 * - If one of the queries is for a `ComponentHarnessConstructor<C1>`, it means that the result
 *   might be a harness of type `C1`
 * - If one of the queries is for a `HarnessPredicate<C2>`, it means that the result might be a
 *   harness of type `C2`
 * - If one of the queries is for a `string`, it means that the result might be a `TestElement`.
 *
 * Since we don't know for sure which query will match, the result type if the union of the types
 * for all possible results.
 *
 * e.g.
 * The type:
 * `LocatorFnResult&lt;[
 *   ComponentHarnessConstructor&lt;MyHarness&gt;,
 *   HarnessPredicate&lt;MyOtherHarness&gt;,
 *   string
 * ]&gt;`
 * is equivalent to:
 * `MyHarness | MyOtherHarness | TestElement`.
 */ export type LocatorFnResult < T extends ( HarnessQuery < any > | string ) [ ] > = { [ I in keyof T ] : T [ I ] extends new ( ... args ) => infer C ? C : T [ I ] extends { harnessType : new ( ... args ) => infer C ; } ? C : T [ I ] extends string ? TestElement : never ; } [ number ] ; /**
 * Interface used to load ComponentHarness objects. This interface is used by test authors to
 * instantiate `ComponentHarness`es.
 */ export interface HarnessLoader { param selector return throws getChildLoader ( selector ) : Promise < HarnessLoader > ; param selector return getAllChildLoaders ( selector ) : Promise < HarnessLoader [ ] > ; param query return throws getHarness < T extends ComponentHarness > ( query ) : Promise < T > ; param query return getAllHarnesses < T extends ComponentHarness > ( query ) : Promise < T [ ] > ; } /**
 * Interface used to create asynchronous locator functions used find elements and component
 * harnesses. This interface is used by `ComponentHarness` authors to create locator functions for
 * their `ComponentHarness` subclass.
 */ export interface LocatorFactory { /** Gets a locator factory rooted at the document root. */ documentRootLocatorFactory ( ) : LocatorFactory ; /** The root element of this `LocatorFactory` as a `TestElement`. */ rootElement : TestElement ; param queries return locatorFor < T extends ( HarnessQuery < any > | string ) [ ] > ( ... queries ) : AsyncFactoryFn < LocatorFnResult < T > > ; param queries return locatorForOptional < T extends ( HarnessQuery < any > | string ) [ ] > ( ... queries ) : AsyncFactoryFn < LocatorFnResult < T > | null > ; param queries return locatorForAll < T extends ( HarnessQuery < any > | string ) [ ] > ( ... queries ) : AsyncFactoryFn < LocatorFnResult < T > [ ] > ; return rootHarnessLoader ( ) : Promise < HarnessLoader > ; param selector return throws harnessLoaderFor ( selector ) : Promise < HarnessLoader > ; param selector return harnessLoaderForOptional ( selector ) : Promise < HarnessLoader | null > ; param selector return harnessLoaderForAll ( selector ) : Promise < HarnessLoader [ ] > ; /**
     * Flushes change detection and async tasks captured in the Angular zone.
     * In most cases it should not be necessary to call this manually. However, there may be some edge
     * cases where it is needed to fully flush animation events.
     */ forceStabilize ( ) : Promise < void > ; /**
     * Waits for all scheduled or running async tasks to complete. This allows harness
     * authors to wait for async tasks outside of the Angular zone.
     */ waitForTasksOutsideAngular ( ) : Promise < void > ; } /**
 * Base class for component harnesses that all component harness authors should extend. This base
 * component harness provides the basic ability to locate element and sub-component harness. It
 * should be inherited when defining user's own harness.
 */ export abstract class ComponentHarness { constructor ( protected readonly locatorFactory ) { } /** Gets a `Promise` for the `TestElement` representing the host element of the component. */ async host ( ) { return this . locatorFactory . rootElement ; } /**
     * Gets a `LocatorFactory` for the document root element. This factory can be used to create
     * locators for elements that a component creates outside of its own root element. (e.g. by
     * appending to document.body).
     */ protected documentRootLocatorFactory ( ) { return this . locatorFactory . documentRootLocatorFactory ( ) ; } param queries return protected locatorFor < T extends ( HarnessQuery < any > | string ) [ ] > ( ... queries ) { return this . locatorFactory . locatorFor ( ... queries ) ; } param queries return protected locatorForOptional < T extends ( HarnessQuery < any > | string ) [ ] > ( ... queries ) { return this . locatorFactory . locatorForOptional ( ... queries ) ; } param queries return protected locatorForAll < T extends ( HarnessQuery < any > | string ) [ ] > ( ... queries ) { return this . locatorFactory . locatorForAll ( ... queries ) ; } /**
     * Flushes change detection and async tasks in the Angular zone.
     * In most cases it should not be necessary to call this manually. However, there may be some edge
     * cases where it is needed to fully flush animation events.
     */ protected async forceStabilize ( ) { return this . locatorFactory . forceStabilize ( ) ; } /**
     * Waits for all scheduled or running async tasks to complete. This allows harness
     * authors to wait for async tasks outside of the Angular zone.
     */ protected async waitForTasksOutsideAngular ( ) { return this . locatorFactory . waitForTasksOutsideAngular ( ) ; } } /**
 * Base class for component harnesses that authors should extend if they anticipate that consumers
 * of the harness may want to access other harnesses within the `<ng-content>` of the component.
 */ export abstract class ContentContainerComponentHarness < S extends string = string > extends ComponentHarness implements HarnessLoader { async getChildLoader ( selector ) { return ( await this . getRootHarnessLoader ( ) ) . getChildLoader ( selector ) ; } async getAllChildLoaders ( selector ) { return ( await this . getRootHarnessLoader ( ) ) . getAllChildLoaders ( selector ) ; } async getHarness < T extends ComponentHarness > ( query ) { return ( await this . getRootHarnessLoader ( ) ) . getHarness ( query ) ; } async getAllHarnesses < T extends ComponentHarness > ( query ) { return ( await this . getRootHarnessLoader ( ) ) . getAllHarnesses ( query ) ; } /**
     * Gets the root harness loader from which to start
     * searching for content contained by this harness.
     */ protected async getRootHarnessLoader ( ) { return this . locatorFactory . rootHarnessLoader ( ) ; } } /** Constructor for a ComponentHarness subclass. */ export interface ComponentHarnessConstructor < T extends ComponentHarness > { new ( locatorFactory ) : T ; /**
     * `ComponentHarness` subclasses must specify a static `hostSelector` property that is used to
     * find the host element for the corresponding component. This property should match the selector
     * for the Angular component.
     */ hostSelector : string ; } /** A set of criteria that can be used to filter a list of `ComponentHarness` instances. */ export interface BaseHarnessFilters { /** Only find instances whose host element matches the given selector. */ selector ? : string ; /** Only find instances that are nested under an element with the given selector. */ ancestor ? : string ; } /**
 * A class used to associate a ComponentHarness class with predicates functions that can be used to
 * filter instances of the class.
 */ export class HarnessPredicate < T extends ComponentHarness > { private _predicates : AsyncPredicate < T > [ ] = [ ] ; private _descriptions : string [ ] = [ ] ; private _ancestor : string ; constructor ( public harnessType , options ) { this . _addBaseOptions ( options ) ; } param value param pattern return static async stringMatches ( value , pattern ) { value = await value ; if ( pattern === null ) { return value === null ; } else if ( value === null ) { return false ; } return typeof pattern === 'string' ? value === pattern : pattern . test ( value ) ; } param description param predicate return add ( description , predicate ) { this . _descriptions . push ( description ) ; this . _predicates . push ( predicate ) ; return this ; } param name param option param predicate return addOption < O > ( name , option , predicate ) { if ( option !== undefined ) { this . add ( `${ name } = ${ _valueAsString ( option ) }` , item => predicate ( item , option ) ) ; } return this ; } param harnesses return async filter ( harnesses ) { if ( harnesses . length === 0 ) { return [ ] ; } const results = await parallel ( ( ) => harnesses . map ( h => this . evaluate ( h ) ) ) ; return harnesses . filter ( ( _ , i ) => results [ i ] ) ; } param harness return async evaluate ( harness ) { const results = await parallel ( ( ) => this . _predicates . map ( p => p ( harness ) ) ) ; return results . reduce ( ( combined , current ) => combined && current , true ) ; } /** Gets a description of this predicate for use in error messages. */ getDescription ( ) { return this . _descriptions . join ( ', ' ) ; } /** Gets the selector used to find candidate elements. */ getSelector ( ) { if ( ! this . _ancestor ) { return ( this . harnessType . hostSelector || '' ) . trim ( ) ; } const [ ancestors , ancestorPlaceholders ] = _splitAndEscapeSelector ( this . _ancestor ) ; const [ selectors , selectorPlaceholders ] = _splitAndEscapeSelector ( this . harnessType . hostSelector || '' ) ; const result = [ ] ; ancestors . forEach ( escapedAncestor => { const ancestor = _restoreSelector ( escapedAncestor , ancestorPlaceholders ) ; return selectors . forEach ( escapedSelector => result . push ( `${ ancestor } ${ _restoreSelector ( escapedSelector , selectorPlaceholders ) }` ) ) ; } ) ; return result . join ( ', ' ) ; } /** Adds base options common to all harness types. */ private _addBaseOptions ( options ) { this . _ancestor = options . ancestor || '' ; if ( this . _ancestor ) { this . _descriptions . push ( `has ancestor matching selector "${ this . _ancestor }"` ) ; } const selector = options . selector ; if ( selector !== undefined ) { this . add ( `host matches selector "${ selector }"` , async ( item ) => { return ( await item . host ( ) ) . matchesSelector ( selector ) ; } ) ; } } } /** Represent a value as a string for the purpose of logging. */ function _valueAsString ( value ) { if ( value === undefined ) { return 'undefined' ; } try { const stringifiedValue = JSON . stringify ( value , ( _ , v ) => v instanceof RegExp ? `◬MAT_RE_ESCAPE◬${ v . toString ( ) . replace ( /"/g , '◬MAT_RE_ESCAPE◬' ) }◬MAT_RE_ESCAPE◬` : v ) ; return stringifiedValue . replace ( /"◬MAT_RE_ESCAPE◬|◬MAT_RE_ESCAPE◬"/g , '' ) . replace ( /◬MAT_RE_ESCAPE◬/g , '"' ) ; } catch { return '{...}' ; } } param selector returns function _splitAndEscapeSelector ( selector ) { const placeholders = [ ] ; const result = selector . replace ( /(["'][^["']*["'])/g , ( _ , keep ) => { const replaceBy = `__cdkPlaceholder-${ placeholders . length }__` ; placeholders . push ( keep ) ; return replaceBy ; } ) ; return [ result . split ( ',' ) . map ( part => part . trim ( ) ) , placeholders ] ; } /** Restores a selector whose content was escaped in `_splitAndEscapeSelector`. */ function _restoreSelector ( selector , placeholders ) { return selector . replace ( /__cdkPlaceholder-(\d+)__/g , ( _ , index ) => placeholders [ + index ] ) ; }