diff --git a/src/material/chips/testing/chip-edit-input-harness.ts b/src/material/chips/testing/chip-edit-input-harness.ts new file mode 100644 index 000000000000..ca92e142de7e --- /dev/null +++ b/src/material/chips/testing/chip-edit-input-harness.ts @@ -0,0 +1,48 @@ +/** + * @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.io/license + */ + +import { + ComponentHarness, + ComponentHarnessConstructor, + HarnessPredicate, +} from '@angular/cdk/testing'; +import {ChipEditInputHarnessFilters} from './chip-harness-filters'; + +/** Harness for interacting with an editable chip's input in tests. */ +export class MatChipEditInputHarness extends ComponentHarness { + static hostSelector = '.mat-chip-edit-input'; + + /** + * Gets a `HarnessPredicate` that can be used to search for a chip edit input with specific + * attributes. + * @param options Options for filtering which input instances are considered a match. + * @return a `HarnessPredicate` configured with the given options. + */ + static with( + this: ComponentHarnessConstructor, + options: ChipEditInputHarnessFilters = {}, + ): HarnessPredicate { + return new HarnessPredicate(this, options); + } + + /** Sets the value of the input. */ + async setValue(value: string): Promise { + const host = await this.host(); + + // @breaking-change 16.0.0 Remove this null check once `setContenteditableValue` + // becomes a required method. + if (!host.setContenteditableValue) { + throw new Error( + 'Cannot set chip edit input value, because test ' + + 'element does not implement the `setContenteditableValue` method.', + ); + } + + return host.setContenteditableValue(value); + } +} diff --git a/src/material/chips/testing/chip-grid-harness.spec.ts b/src/material/chips/testing/chip-grid-harness.spec.ts index 989b72221ea8..d120d06aa747 100644 --- a/src/material/chips/testing/chip-grid-harness.spec.ts +++ b/src/material/chips/testing/chip-grid-harness.spec.ts @@ -1,4 +1,4 @@ -import {HarnessLoader} from '@angular/cdk/testing'; +import {HarnessLoader, parallel} from '@angular/cdk/testing'; import {TestbedHarnessEnvironment} from '@angular/cdk/testing/testbed'; import {FormControl, ReactiveFormsModule, Validators} from '@angular/forms'; import {Component} from '@angular/core'; @@ -78,12 +78,55 @@ describe('MatChipGridHarness', () => { expect(await harness.isInvalid()).toBe(true); }); + + it('should get whether a chip is editable', async () => { + const grid = await loader.getHarness(MatChipGridHarness); + const chips = await grid.getRows(); + fixture.componentInstance.firstChipEditable = true; + + expect(await parallel(() => chips.map(chip => chip.isEditable()))).toEqual([ + true, + false, + false, + ]); + }); + + it('should throw when trying to edit a chip that is not editable', async () => { + const grid = await loader.getHarness(MatChipGridHarness); + const chip = (await grid.getRows())[0]; + let error: string | null = null; + fixture.componentInstance.firstChipEditable = false; + + try { + await chip.startEditing(); + } catch (e: any) { + error = e.message; + } + + expect(error).toBe('Cannot begin editing a chip that is not editable.'); + }); + + it('should be able to edit a chip row', async () => { + const grid = await loader.getHarness(MatChipGridHarness); + const chip = (await grid.getRows())[0]; + fixture.componentInstance.firstChipEditable = true; + + await chip.startEditing(); + await (await chip.getEditInput()).setValue('new value'); + await chip.finishEditing(); + + expect(fixture.componentInstance.editSpy).toHaveBeenCalledWith( + jasmine.objectContaining({ + value: 'new value', + }), + ); + }); }); @Component({ template: ` - Chip A + Chip A Chip B Chip C @@ -93,4 +136,6 @@ describe('MatChipGridHarness', () => { class ChipGridHarnessTest { control = new FormControl('value', [Validators.required]); required = false; + firstChipEditable = false; + editSpy = jasmine.createSpy('editSpy'); } diff --git a/src/material/chips/testing/chip-harness-filters.ts b/src/material/chips/testing/chip-harness-filters.ts index a9ecdc25eb3e..fbdf354ceca2 100644 --- a/src/material/chips/testing/chip-harness-filters.ts +++ b/src/material/chips/testing/chip-harness-filters.ts @@ -46,3 +46,5 @@ export interface ChipSetHarnessFilters extends BaseHarnessFilters {} export interface ChipRemoveHarnessFilters extends BaseHarnessFilters {} export interface ChipAvatarHarnessFilters extends BaseHarnessFilters {} + +export interface ChipEditInputHarnessFilters extends BaseHarnessFilters {} diff --git a/src/material/chips/testing/chip-row-harness.ts b/src/material/chips/testing/chip-row-harness.ts index 973b27d7dd55..5f78d23e5db3 100644 --- a/src/material/chips/testing/chip-row-harness.ts +++ b/src/material/chips/testing/chip-row-harness.ts @@ -6,9 +6,10 @@ * found in the LICENSE file at https://angular.io/license */ +import {TestKey} from '@angular/cdk/testing'; +import {MatChipEditInputHarness} from './chip-edit-input-harness'; import {MatChipHarness} from './chip-harness'; - -// TODO(crisbeto): add harness for the chip edit input inside the row. +import {ChipEditInputHarnessFilters} from './chip-harness-filters'; /** Harness for interacting with a mat-chip-row in tests. */ export class MatChipRowHarness extends MatChipHarness { @@ -23,4 +24,24 @@ export class MatChipRowHarness extends MatChipHarness { async isEditing(): Promise { return (await this.host()).hasClass('mat-mdc-chip-editing'); } + + /** Sets the chip row into an editing state, if it is editable. */ + async startEditing(): Promise { + if (!(await this.isEditable())) { + throw new Error('Cannot begin editing a chip that is not editable.'); + } + return (await this.host()).dispatchEvent('dblclick'); + } + + /** Stops editing the chip, if it was in the editing state. */ + async finishEditing(): Promise { + if (await this.isEditing()) { + await (await this.host()).sendKeys(TestKey.ENTER); + } + } + + /** Gets the edit input inside the chip row. */ + async getEditInput(filter: ChipEditInputHarnessFilters = {}): Promise { + return this.locatorFor(MatChipEditInputHarness.with(filter))(); + } } diff --git a/src/material/chips/testing/public-api.ts b/src/material/chips/testing/public-api.ts index 0689ddb6c35a..b99eccc02162 100644 --- a/src/material/chips/testing/public-api.ts +++ b/src/material/chips/testing/public-api.ts @@ -16,3 +16,4 @@ export * from './chip-listbox-harness'; export * from './chip-grid-harness'; export * from './chip-row-harness'; export * from './chip-set-harness'; +export * from './chip-edit-input-harness'; diff --git a/tools/public_api_guard/material/chips-testing.md b/tools/public_api_guard/material/chips-testing.md index 902968435da1..bf2eb6175859 100644 --- a/tools/public_api_guard/material/chips-testing.md +++ b/tools/public_api_guard/material/chips-testing.md @@ -17,6 +17,10 @@ import { TestKey } from '@angular/cdk/testing'; export interface ChipAvatarHarnessFilters extends BaseHarnessFilters { } +// @public (undocumented) +export interface ChipEditInputHarnessFilters extends BaseHarnessFilters { +} + // @public (undocumented) export interface ChipGridHarnessFilters extends BaseHarnessFilters { disabled?: boolean; @@ -64,6 +68,14 @@ export class MatChipAvatarHarness extends ComponentHarness { static with(this: ComponentHarnessConstructor, options?: ChipAvatarHarnessFilters): HarnessPredicate; } +// @public +export class MatChipEditInputHarness extends ComponentHarness { + // (undocumented) + static hostSelector: string; + setValue(value: string): Promise; + static with(this: ComponentHarnessConstructor, options?: ChipEditInputHarnessFilters): HarnessPredicate; +} + // @public export class MatChipGridHarness extends ComponentHarness { getInput(filter?: ChipInputHarnessFilters): Promise; @@ -140,10 +152,13 @@ export class MatChipRemoveHarness extends ComponentHarness { // @public export class MatChipRowHarness extends MatChipHarness { + finishEditing(): Promise; + getEditInput(filter?: ChipEditInputHarnessFilters): Promise; // (undocumented) static hostSelector: string; isEditable(): Promise; isEditing(): Promise; + startEditing(): Promise; } // @public