Skip to content

Commit b42650c

Browse files
committed
feat(material/chips): add test harness support for edit input
Adds the ability to start editing a chip, change its value, and finish editing it through test harnesses. Fixes #26419.
1 parent 62bd54e commit b42650c

File tree

6 files changed

+136
-4
lines changed

6 files changed

+136
-4
lines changed
Lines changed: 48 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,48 @@
1+
/**
2+
* @license
3+
* Copyright Google LLC All Rights Reserved.
4+
*
5+
* Use of this source code is governed by an MIT-style license that can be
6+
* found in the LICENSE file at https://angular.io/license
7+
*/
8+
9+
import {
10+
ComponentHarness,
11+
ComponentHarnessConstructor,
12+
HarnessPredicate,
13+
} from '@angular/cdk/testing';
14+
import {ChipEditInputHarnessFilters} from './chip-harness-filters';
15+
16+
/** Harness for interacting with an editable chip's input in tests. */
17+
export class MatChipEditInputHarness extends ComponentHarness {
18+
static hostSelector = '.mat-chip-edit-input';
19+
20+
/**
21+
* Gets a `HarnessPredicate` that can be used to search for a chip edit input with specific
22+
* attributes.
23+
* @param options Options for filtering which input instances are considered a match.
24+
* @return a `HarnessPredicate` configured with the given options.
25+
*/
26+
static with<T extends MatChipEditInputHarness>(
27+
this: ComponentHarnessConstructor<T>,
28+
options: ChipEditInputHarnessFilters = {},
29+
): HarnessPredicate<T> {
30+
return new HarnessPredicate(this, options);
31+
}
32+
33+
/** Sets the value of the input. */
34+
async setValue(value: string): Promise<void> {
35+
const host = await this.host();
36+
37+
// @breaking-change 16.0.0 Remove this null check once `setContenteditableValue`
38+
// becomes a required method.
39+
if (!host.setContenteditableValue) {
40+
throw new Error(
41+
'Cannot set chip edit input value, because test ' +
42+
'element does not implement the `setContenteditableValue` method.',
43+
);
44+
}
45+
46+
return host.setContenteditableValue(value);
47+
}
48+
}

src/material/chips/testing/chip-grid-harness.spec.ts

Lines changed: 47 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
import {HarnessLoader} from '@angular/cdk/testing';
1+
import {HarnessLoader, parallel} from '@angular/cdk/testing';
22
import {TestbedHarnessEnvironment} from '@angular/cdk/testing/testbed';
33
import {FormControl, ReactiveFormsModule, Validators} from '@angular/forms';
44
import {Component} from '@angular/core';
@@ -78,12 +78,55 @@ describe('MatChipGridHarness', () => {
7878

7979
expect(await harness.isInvalid()).toBe(true);
8080
});
81+
82+
it('should get whether a chip is editable', async () => {
83+
const grid = await loader.getHarness(MatChipGridHarness);
84+
const chips = await grid.getRows();
85+
fixture.componentInstance.firstChipEditable = true;
86+
87+
expect(await parallel(() => chips.map(chip => chip.isEditable()))).toEqual([
88+
true,
89+
false,
90+
false,
91+
]);
92+
});
93+
94+
it('should throw when trying to edit a chip that is not editable', async () => {
95+
const grid = await loader.getHarness(MatChipGridHarness);
96+
const chip = (await grid.getRows())[0];
97+
let error: string | null = null;
98+
fixture.componentInstance.firstChipEditable = false;
99+
100+
try {
101+
await chip.startEditing();
102+
} catch (e: any) {
103+
error = e.message;
104+
}
105+
106+
expect(error).toBe('Cannot begin editing a chip that is not editable.');
107+
});
108+
109+
it('should be able to edit a chip row', async () => {
110+
const grid = await loader.getHarness(MatChipGridHarness);
111+
const chip = (await grid.getRows())[0];
112+
fixture.componentInstance.firstChipEditable = true;
113+
114+
await chip.startEditing();
115+
await (await chip.getEditInput()).setValue('new value');
116+
await chip.finishEditing();
117+
118+
expect(fixture.componentInstance.editSpy).toHaveBeenCalledWith(
119+
jasmine.objectContaining({
120+
value: 'new value',
121+
}),
122+
);
123+
});
81124
});
82125

83126
@Component({
84127
template: `
85128
<mat-chip-grid [formControl]="control" [required]="required" #grid>
86-
<mat-chip-row>Chip A</mat-chip-row>
129+
<mat-chip-row [editable]="firstChipEditable" (edited)="editSpy($event)">Chip A</mat-chip-row>
87130
<mat-chip-row>Chip B</mat-chip-row>
88131
<mat-chip-row>Chip C</mat-chip-row>
89132
<input [matChipInputFor]="grid"/>
@@ -93,4 +136,6 @@ describe('MatChipGridHarness', () => {
93136
class ChipGridHarnessTest {
94137
control = new FormControl('value', [Validators.required]);
95138
required = false;
139+
firstChipEditable = false;
140+
editSpy = jasmine.createSpy('editSpy');
96141
}

src/material/chips/testing/chip-harness-filters.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -46,3 +46,5 @@ export interface ChipSetHarnessFilters extends BaseHarnessFilters {}
4646
export interface ChipRemoveHarnessFilters extends BaseHarnessFilters {}
4747

4848
export interface ChipAvatarHarnessFilters extends BaseHarnessFilters {}
49+
50+
export interface ChipEditInputHarnessFilters extends BaseHarnessFilters {}

src/material/chips/testing/chip-row-harness.ts

Lines changed: 23 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -6,9 +6,10 @@
66
* found in the LICENSE file at https://angular.io/license
77
*/
88

9+
import {TestKey} from '@angular/cdk/testing';
10+
import {MatChipEditInputHarness} from './chip-edit-input-harness';
911
import {MatChipHarness} from './chip-harness';
10-
11-
// TODO(crisbeto): add harness for the chip edit input inside the row.
12+
import {ChipEditInputHarnessFilters} from './chip-harness-filters';
1213

1314
/** Harness for interacting with a mat-chip-row in tests. */
1415
export class MatChipRowHarness extends MatChipHarness {
@@ -23,4 +24,24 @@ export class MatChipRowHarness extends MatChipHarness {
2324
async isEditing(): Promise<boolean> {
2425
return (await this.host()).hasClass('mat-mdc-chip-editing');
2526
}
27+
28+
/** Sets the chip row into an editing state, if it is editable. */
29+
async startEditing(): Promise<void> {
30+
if (!(await this.isEditable())) {
31+
throw new Error('Cannot begin editing a chip that is not editable.');
32+
}
33+
return (await this.host()).dispatchEvent('dblclick');
34+
}
35+
36+
/** Stops editing the chip, if it was in the editing state. */
37+
async finishEditing(): Promise<void> {
38+
if (await this.isEditing()) {
39+
await (await this.host()).sendKeys(TestKey.ENTER);
40+
}
41+
}
42+
43+
/** Gets the edit input inside the chip row. */
44+
async getEditInput(filter: ChipEditInputHarnessFilters = {}): Promise<MatChipEditInputHarness> {
45+
return this.locatorFor(MatChipEditInputHarness.with(filter))();
46+
}
2647
}

src/material/chips/testing/public-api.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -16,3 +16,4 @@ export * from './chip-listbox-harness';
1616
export * from './chip-grid-harness';
1717
export * from './chip-row-harness';
1818
export * from './chip-set-harness';
19+
export * from './chip-edit-input-harness';

tools/public_api_guard/material/chips-testing.md

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,10 @@ import { TestKey } from '@angular/cdk/testing';
1717
export interface ChipAvatarHarnessFilters extends BaseHarnessFilters {
1818
}
1919

20+
// @public (undocumented)
21+
export interface ChipEditInputHarnessFilters extends BaseHarnessFilters {
22+
}
23+
2024
// @public (undocumented)
2125
export interface ChipGridHarnessFilters extends BaseHarnessFilters {
2226
disabled?: boolean;
@@ -64,6 +68,14 @@ export class MatChipAvatarHarness extends ComponentHarness {
6468
static with<T extends MatChipAvatarHarness>(this: ComponentHarnessConstructor<T>, options?: ChipAvatarHarnessFilters): HarnessPredicate<T>;
6569
}
6670

71+
// @public
72+
export class MatChipEditInputHarness extends ComponentHarness {
73+
// (undocumented)
74+
static hostSelector: string;
75+
setValue(value: string): Promise<void>;
76+
static with<T extends MatChipEditInputHarness>(this: ComponentHarnessConstructor<T>, options?: ChipEditInputHarnessFilters): HarnessPredicate<T>;
77+
}
78+
6779
// @public
6880
export class MatChipGridHarness extends ComponentHarness {
6981
getInput(filter?: ChipInputHarnessFilters): Promise<MatChipInputHarness | null>;
@@ -140,10 +152,13 @@ export class MatChipRemoveHarness extends ComponentHarness {
140152

141153
// @public
142154
export class MatChipRowHarness extends MatChipHarness {
155+
finishEditing(): Promise<void>;
156+
getEditInput(filter?: ChipEditInputHarnessFilters): Promise<MatChipEditInputHarness>;
143157
// (undocumented)
144158
static hostSelector: string;
145159
isEditable(): Promise<boolean>;
146160
isEditing(): Promise<boolean>;
161+
startEditing(): Promise<void>;
147162
}
148163

149164
// @public

0 commit comments

Comments
 (0)