Skip to content

Commit 432d365

Browse files
committed
fix(material-experimental/mdc-chips): align test harnesses with the non-MDC version
Aligns the test harnesses of the MDC-based chips module with the ones from the non-MDC version. Overview of the changes: * `MatChipGridHarness` - `getTextInput` was renamed to `getInput` to match the non-MDC list harness. Also adds a few utility methods to check whether the grid is disabled, required etc. * `MatChipHarness` - adds support for the same set of filters when querying for the harness. Also adds a method to get the removal button and disabled state. * `MatChipInputHarness` - adds a bunch of utility methods that we have on the other input-related harnesses. Also implemets the same set of harness filters as the non-MDC harness. * `MatChipListboxHarness` - Renames `getOptions` to `getChips` for consistency and removes the `getSelected` method in favor of filtering selected chips through the harness predicate. Also adds some utility methods for selecting chips, getting the disabled and required states etc. * `MatChipOptionHarness` - implements harness filters and adds APIs for selecting/deselecting. * `MatChipRemoveHarness` - new harness which is identical to the non-MDC `MatChipRemoveHarness`. * `MatChipSetHarness` - supports filtering through the `getChips` method. These changes also include tests for all the new functionality and minor cleanups around the `mdc-chips/testing` package.
1 parent 94bd0d4 commit 432d365

17 files changed

+453
-127
lines changed

src/material-experimental/mdc-chips/testing/BUILD.bazel

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,7 @@ ng_test_library(
2323
"//src/cdk/testing",
2424
"//src/cdk/testing/testbed",
2525
"//src/material-experimental/mdc-chips",
26+
"@npm//@angular/forms",
2627
],
2728
)
2829

Lines changed: 47 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -1,17 +1,19 @@
11
import {HarnessLoader} from '@angular/cdk/testing';
22
import {TestbedHarnessEnvironment} from '@angular/cdk/testing/testbed';
3+
import {FormControl, ReactiveFormsModule, Validators} from '@angular/forms';
34
import {Component} from '@angular/core';
45
import {ComponentFixture, TestBed} from '@angular/core/testing';
56
import {MatChipsModule} from '../index';
67
import {MatChipGridHarness} from './chip-grid-harness';
78

8-
let fixture: ComponentFixture<ChipGridHarnessTest>;
9-
let loader: HarnessLoader;
109

1110
describe('MatChipGridHarness', () => {
11+
let fixture: ComponentFixture<ChipGridHarnessTest>;
12+
let loader: HarnessLoader;
13+
1214
beforeEach(async () => {
1315
await TestBed.configureTestingModule({
14-
imports: [MatChipsModule],
16+
imports: [MatChipsModule, ReactiveFormsModule],
1517
declarations: [ChipGridHarnessTest],
1618
}).compileComponents();
1719

@@ -26,27 +28,59 @@ describe('MatChipGridHarness', () => {
2628
});
2729

2830
it('should get correct number of rows', async () => {
29-
const harnesses = await loader.getAllHarnesses(MatChipGridHarness);
30-
const rows = await harnesses[0].getRows();
31+
const harness = await loader.getHarness(MatChipGridHarness);
32+
const rows = await harness.getRows();
3133
expect(rows.length).toBe(3);
3234
});
3335

3436
it('should get the chip input harness', async () => {
35-
const harnesses = await loader.getAllHarnesses(MatChipGridHarness);
36-
const input = await harnesses[0].getTextInput();
37+
const harness = await loader.getHarness(MatChipGridHarness);
38+
const input = await harness.getInput();
3739
expect(input).not.toBe(null);
3840
});
41+
42+
it('should get whether the grid is disabled', async () => {
43+
const harness = await loader.getHarness(MatChipGridHarness);
44+
expect(await harness.isDisabled()).toBe(false);
45+
46+
fixture.componentInstance.control.disable();
47+
expect(await harness.isDisabled()).toBe(true);
48+
});
49+
50+
it('should get whether the grid is required', async () => {
51+
const harness = await loader.getHarness(MatChipGridHarness);
52+
expect(await harness.isRequired()).toBe(false);
53+
54+
fixture.componentInstance.required = true;
55+
expect(await harness.isRequired()).toBe(true);
56+
});
57+
58+
it('should get whether the grid is invalid', async () => {
59+
const harness = await loader.getHarness(MatChipGridHarness);
60+
expect(await harness.isInvalid()).toBe(false);
61+
62+
// Mark the control as touched since the default error
63+
// state matcher only activates after a control is touched.
64+
fixture.componentInstance.control.markAsTouched();
65+
fixture.componentInstance.control.setValue(undefined);
66+
67+
expect(await harness.isInvalid()).toBe(true);
68+
});
69+
3970
});
4071

4172
@Component({
4273
template: `
43-
<mat-chip-grid #grid>
44-
<mat-chip-row> Chip A </mat-chip-row>
45-
<mat-chip-row> Chip B </mat-chip-row>
46-
<mat-chip-row> Chip C </mat-chip-row>
47-
<input [matChipInputFor]="grid" />
74+
<mat-chip-grid [formControl]="control" [required]="required" #grid>
75+
<mat-chip-row>Chip A</mat-chip-row>
76+
<mat-chip-row>Chip B</mat-chip-row>
77+
<mat-chip-row>Chip C</mat-chip-row>
78+
<input [matChipInputFor]="grid"/>
4879
</mat-chip-grid>
4980
`
5081
})
51-
class ChipGridHarnessTest {}
82+
class ChipGridHarnessTest {
83+
control = new FormControl('value', [Validators.required]);
84+
required = false;
85+
}
5286

src/material-experimental/mdc-chips/testing/chip-grid-harness.ts

Lines changed: 23 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,11 @@
77
*/
88

99
import {ComponentHarness, HarnessPredicate} from '@angular/cdk/testing';
10-
import {ChipGridHarnessFilters} from './chip-harness-filters';
10+
import {
11+
ChipGridHarnessFilters,
12+
ChipInputHarnessFilters,
13+
ChipRowHarnessFilters,
14+
} from './chip-harness-filters';
1115
import {MatChipInputHarness} from './chip-input-harness';
1216
import {MatChipRowHarness} from './chip-row-harness';
1317

@@ -22,16 +26,28 @@ export class MatChipGridHarness extends ComponentHarness {
2226
return new HarnessPredicate(MatChipGridHarness, options);
2327
}
2428

25-
private _rows = this.locatorForAll(MatChipRowHarness);
26-
private _input = this.locatorFor(MatChipInputHarness);
29+
/** Gets whether the chip grid is disabled. */
30+
async isDisabled(): Promise<boolean> {
31+
return await (await this.host()).getAttribute('aria-disabled') === 'true';
32+
}
33+
34+
/** Gets whether the chip grid is required. */
35+
async isRequired(): Promise<boolean> {
36+
return await (await this.host()).hasClass('mat-mdc-chip-list-required');
37+
}
38+
39+
/** Gets whether the chip grid is invalid. */
40+
async isInvalid(): Promise<boolean> {
41+
return await (await this.host()).getAttribute('aria-invalid') === 'true';
42+
}
2743

2844
/** Gets promise of the harnesses for the chip rows. */
29-
async getRows(): Promise<MatChipRowHarness[]> {
30-
return await this._rows();
45+
getRows(filter: ChipRowHarnessFilters = {}): Promise<MatChipRowHarness[]> {
46+
return this.locatorForAll(MatChipRowHarness.with(filter))();
3147
}
3248

3349
/** Gets promise of the chip text input harness. */
34-
async getTextInput(): Promise<MatChipInputHarness|null> {
35-
return await this._input();
50+
getInput(filter: ChipInputHarnessFilters = {}): Promise<MatChipInputHarness|null> {
51+
return this.locatorFor(MatChipInputHarness.with(filter))();
3652
}
3753
}

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

Lines changed: 17 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -8,17 +8,26 @@
88

99
import {BaseHarnessFilters} from '@angular/cdk/testing';
1010

11-
// TODO(mmalerba): Add additional options that make sense for each harness type.
12-
13-
export interface ChipGridHarnessFilters extends BaseHarnessFilters {}
14-
15-
export interface ChipHarnessFilters extends BaseHarnessFilters {}
16-
17-
export interface ChipInputHarnessFilters extends BaseHarnessFilters {}
11+
export interface ChipHarnessFilters extends BaseHarnessFilters {
12+
/** Only find instances whose text matches the given value. */
13+
text?: string | RegExp;
14+
}
15+
16+
export interface ChipInputHarnessFilters extends BaseHarnessFilters {
17+
/** Filters based on the value of the input. */
18+
value?: string | RegExp;
19+
/** Filters based on the placeholder text of the input. */
20+
placeholder?: string | RegExp;
21+
}
1822

1923
export interface ChipListboxHarnessFilters extends BaseHarnessFilters {}
2024

21-
export interface ChipOptionHarnessFilters extends ChipHarnessFilters {}
25+
export interface ChipOptionHarnessFilters extends ChipHarnessFilters {
26+
/** Only find chip instances whose selected state matches the given value. */
27+
selected?: boolean;
28+
}
29+
30+
export interface ChipGridHarnessFilters extends BaseHarnessFilters {}
2231

2332
export interface ChipRowHarnessFilters extends ChipHarnessFilters {}
2433

src/material-experimental/mdc-chips/testing/chip-harness.spec.ts

Lines changed: 20 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -5,10 +5,11 @@ import {ComponentFixture, TestBed} from '@angular/core/testing';
55
import {MatChipsModule} from '../index';
66
import {MatChipHarness} from './chip-harness';
77

8-
let fixture: ComponentFixture<ChipHarnessTest>;
9-
let loader: HarnessLoader;
108

119
describe('MatChipHarness', () => {
10+
let fixture: ComponentFixture<ChipHarnessTest>;
11+
let loader: HarnessLoader;
12+
1213
beforeEach(async () => {
1314
await TestBed.configureTestingModule({
1415
imports: [MatChipsModule],
@@ -34,22 +35,37 @@ describe('MatChipHarness', () => {
3435
expect(await harnesses[4].getText()).toBe('Chip Row');
3536
});
3637

37-
it('should be able to remove a mat-chip-row', async () => {
38+
it('should be able to remove a chip', async () => {
3839
const removeChipSpy = spyOn(fixture.componentInstance, 'removeChip');
3940

4041
const harnesses = await loader.getAllHarnesses(MatChipHarness);
4142
await harnesses[4].remove();
4243

4344
expect(removeChipSpy).toHaveBeenCalledTimes(1);
4445
});
46+
47+
it('should get the disabled state of a chip', async () => {
48+
const harnesses = await loader.getAllHarnesses(MatChipHarness);
49+
const disabledStates = await Promise.all(harnesses.map(harness => harness.isDisabled()));
50+
expect(disabledStates).toEqual([false, false, false, true, false]);
51+
});
52+
53+
it('should get the remove button of a chip', async () => {
54+
const harness = await loader.getHarness(MatChipHarness.with({selector: '.has-remove-button'}));
55+
expect(await harness.getRemoveButton()).toBeTruthy();
56+
});
57+
4558
});
4659

4760
@Component({
4861
template: `
4962
<mat-basic-chip>Basic Chip</mat-basic-chip>
5063
<mat-chip>Chip <span matChipTrailingIcon>trailing_icon</span></mat-chip>
5164
<mat-chip><mat-chip-avatar>B</mat-chip-avatar>Chip with avatar</mat-chip>
52-
<mat-chip disabled>Disabled Chip <span matChipRemove>remove_icon</span></mat-chip>
65+
<mat-chip
66+
class="has-remove-button"
67+
disabled>Disabled Chip <span matChipRemove>remove_icon</span>
68+
</mat-chip>
5369
<mat-chip-row (removed)="removeChip()">Chip Row</mat-chip-row>
5470
`
5571
})

src/material-experimental/mdc-chips/testing/chip-harness.ts

Lines changed: 22 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,8 @@
77
*/
88

99
import {ComponentHarness, HarnessPredicate, TestKey} from '@angular/cdk/testing';
10-
import {ChipHarnessFilters} from './chip-harness-filters';
10+
import {ChipHarnessFilters, ChipRemoveHarnessFilters} from './chip-harness-filters';
11+
import {MatChipRemoveHarness} from './chip-remove-harness';
1112

1213
/** Harness for interacting with a mat-chip in tests. */
1314
export class MatChipHarness extends ComponentHarness {
@@ -20,8 +21,10 @@ export class MatChipHarness extends ComponentHarness {
2021
// methods. See https://github.com/microsoft/TypeScript/issues/5863
2122
static with<T extends typeof MatChipHarness>(this: T, options: ChipHarnessFilters = {}):
2223
HarnessPredicate<InstanceType<T>> {
23-
return new HarnessPredicate(MatChipHarness, options) as
24-
unknown as HarnessPredicate<InstanceType<T>>;
24+
return new HarnessPredicate(MatChipHarness, options)
25+
.addOption('text', options.text, (harness, label) => {
26+
return HarnessPredicate.stringMatches(harness.getText(), label);
27+
}) as unknown as HarnessPredicate<InstanceType<T>>;
2528
}
2629

2730
/** Gets a promise for the text content the option. */
@@ -31,10 +34,25 @@ export class MatChipHarness extends ComponentHarness {
3134
});
3235
}
3336

37+
/** Whether the chip is disabled. */
38+
async isDisabled(): Promise<boolean> {
39+
return (await this.host()).hasClass('mat-mdc-chip-disabled');
40+
}
41+
3442
/** Delete a chip from the set. */
3543
async remove(): Promise<void> {
3644
const hostEl = await this.host();
37-
await hostEl.sendKeys!(TestKey.DELETE);
45+
await hostEl.sendKeys(TestKey.DELETE);
46+
47+
// @breaking-change 12.0.0 Remove non-null assertion from `dispatchEvent`.
3848
await hostEl.dispatchEvent!('transitionend', {propertyName: 'width'});
3949
}
50+
51+
/**
52+
* Gets the remove button inside of a chip.
53+
* @param filter Optionally filters which chips are included.
54+
*/
55+
async getRemoveButton(filter: ChipRemoveHarnessFilters = {}): Promise<MatChipRemoveHarness> {
56+
return this.locatorFor(MatChipRemoveHarness.with(filter))();
57+
}
4058
}

src/material-experimental/mdc-chips/testing/chip-input-harness.spec.ts

Lines changed: 40 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -5,10 +5,11 @@ import {ComponentFixture, TestBed} from '@angular/core/testing';
55
import {MatChipsModule} from '../index';
66
import {MatChipInputHarness} from './chip-input-harness';
77

8-
let fixture: ComponentFixture<ChipInputHarnessTest>;
9-
let loader: HarnessLoader;
108

119
describe('MatChipInputHarness', () => {
10+
let fixture: ComponentFixture<ChipInputHarnessTest>;
11+
let loader: HarnessLoader;
12+
1213
beforeEach(async () => {
1314
await TestBed.configureTestingModule({
1415
imports: [MatChipsModule],
@@ -30,18 +31,53 @@ describe('MatChipInputHarness', () => {
3031
expect(await harnesses[0].isDisabled()).toBe(false);
3132
expect(await harnesses[1].isDisabled()).toBe(true);
3233
});
34+
35+
it('should get whether the input is required', async () => {
36+
const harness = await loader.getHarness(MatChipInputHarness);
37+
expect(await harness.isRequired()).toBe(false);
38+
39+
fixture.componentInstance.required = true;
40+
expect(await harness.isRequired()).toBe(true);
41+
});
42+
43+
it('should get whether the input placeholder', async () => {
44+
const harness = await loader.getHarness(MatChipInputHarness);
45+
expect(await harness.getPlaceholder()).toBe('Placeholder');
46+
});
47+
48+
it('should get and set the input value', async () => {
49+
const harness = await loader.getHarness(MatChipInputHarness);
50+
expect(await harness.getValue()).toBe('');
51+
52+
await harness.setValue('value');
53+
expect(await harness.getValue()).toBe('value');
54+
});
55+
56+
it('should control the input focus state', async () => {
57+
const harness = await loader.getHarness(MatChipInputHarness);
58+
expect(await harness.isFocused()).toBe(false);
59+
60+
await harness.focus();
61+
expect(await harness.isFocused()).toBe(true);
62+
63+
await harness.blur();
64+
expect(await harness.isFocused()).toBe(false);
65+
});
66+
3367
});
3468

3569
@Component({
3670
template: `
3771
<mat-chip-grid #grid1>
38-
<input [matChipInputFor]="grid1" />
72+
<input [matChipInputFor]="grid1" [required]="required" placeholder="Placeholder" />
3973
</mat-chip-grid>
4074
4175
<mat-chip-grid #grid2>
4276
<input [matChipInputFor]="grid2" disabled />
4377
</mat-chip-grid>
4478
`
4579
})
46-
class ChipInputHarnessTest {}
80+
class ChipInputHarnessTest {
81+
required = false;
82+
}
4783

0 commit comments

Comments
 (0)