Skip to content

Commit

Permalink
feat(material/chips): add test harness support for edit input
Browse files Browse the repository at this point in the history
Adds the ability to start editing a chip, change its value, and finish editing it through test harnesses.

Fixes #26419.
  • Loading branch information
crisbeto committed Jan 18, 2023
1 parent 62bd54e commit b42650c
Show file tree
Hide file tree
Showing 6 changed files with 136 additions and 4 deletions.
48 changes: 48 additions & 0 deletions src/material/chips/testing/chip-edit-input-harness.ts
Original file line number Diff line number Diff line change
@@ -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<T extends MatChipEditInputHarness>(
this: ComponentHarnessConstructor<T>,
options: ChipEditInputHarnessFilters = {},
): HarnessPredicate<T> {
return new HarnessPredicate(this, options);
}

/** Sets the value of the input. */
async setValue(value: string): Promise<void> {
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);
}
}
49 changes: 47 additions & 2 deletions src/material/chips/testing/chip-grid-harness.spec.ts
Original file line number Diff line number Diff line change
@@ -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';
Expand Down Expand Up @@ -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: `
<mat-chip-grid [formControl]="control" [required]="required" #grid>
<mat-chip-row>Chip A</mat-chip-row>
<mat-chip-row [editable]="firstChipEditable" (edited)="editSpy($event)">Chip A</mat-chip-row>
<mat-chip-row>Chip B</mat-chip-row>
<mat-chip-row>Chip C</mat-chip-row>
<input [matChipInputFor]="grid"/>
Expand All @@ -93,4 +136,6 @@ describe('MatChipGridHarness', () => {
class ChipGridHarnessTest {
control = new FormControl('value', [Validators.required]);
required = false;
firstChipEditable = false;
editSpy = jasmine.createSpy('editSpy');
}
2 changes: 2 additions & 0 deletions src/material/chips/testing/chip-harness-filters.ts
Original file line number Diff line number Diff line change
Expand Up @@ -46,3 +46,5 @@ export interface ChipSetHarnessFilters extends BaseHarnessFilters {}
export interface ChipRemoveHarnessFilters extends BaseHarnessFilters {}

export interface ChipAvatarHarnessFilters extends BaseHarnessFilters {}

export interface ChipEditInputHarnessFilters extends BaseHarnessFilters {}
25 changes: 23 additions & 2 deletions src/material/chips/testing/chip-row-harness.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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 {
Expand All @@ -23,4 +24,24 @@ export class MatChipRowHarness extends MatChipHarness {
async isEditing(): Promise<boolean> {
return (await this.host()).hasClass('mat-mdc-chip-editing');
}

/** Sets the chip row into an editing state, if it is editable. */
async startEditing(): Promise<void> {
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<void> {
if (await this.isEditing()) {
await (await this.host()).sendKeys(TestKey.ENTER);
}
}

/** Gets the edit input inside the chip row. */
async getEditInput(filter: ChipEditInputHarnessFilters = {}): Promise<MatChipEditInputHarness> {
return this.locatorFor(MatChipEditInputHarness.with(filter))();
}
}
1 change: 1 addition & 0 deletions src/material/chips/testing/public-api.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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';
15 changes: 15 additions & 0 deletions tools/public_api_guard/material/chips-testing.md
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down Expand Up @@ -64,6 +68,14 @@ export class MatChipAvatarHarness extends ComponentHarness {
static with<T extends MatChipAvatarHarness>(this: ComponentHarnessConstructor<T>, options?: ChipAvatarHarnessFilters): HarnessPredicate<T>;
}

// @public
export class MatChipEditInputHarness extends ComponentHarness {
// (undocumented)
static hostSelector: string;
setValue(value: string): Promise<void>;
static with<T extends MatChipEditInputHarness>(this: ComponentHarnessConstructor<T>, options?: ChipEditInputHarnessFilters): HarnessPredicate<T>;
}

// @public
export class MatChipGridHarness extends ComponentHarness {
getInput(filter?: ChipInputHarnessFilters): Promise<MatChipInputHarness | null>;
Expand Down Expand Up @@ -140,10 +152,13 @@ export class MatChipRemoveHarness extends ComponentHarness {

// @public
export class MatChipRowHarness extends MatChipHarness {
finishEditing(): Promise<void>;
getEditInput(filter?: ChipEditInputHarnessFilters): Promise<MatChipEditInputHarness>;
// (undocumented)
static hostSelector: string;
isEditable(): Promise<boolean>;
isEditing(): Promise<boolean>;
startEditing(): Promise<void>;
}

// @public
Expand Down

0 comments on commit b42650c

Please sign in to comment.