Skip to content

feat(material/chips): add test harnesses for selectable chips #20875

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
15 changes: 12 additions & 3 deletions src/material/chips/testing/chip-harness-filters.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,21 +7,30 @@
*/
import {BaseHarnessFilters} from '@angular/cdk/testing';

/** A set of criteria that can be used to filter a list of `MatChipHarness` instances. */
/** A set of criteria that can be used to filter a list of chip instances. */
export interface ChipHarnessFilters extends BaseHarnessFilters {
/** Only find instances whose text matches the given value. */
text?: string | RegExp;
/**
* Only find chip instances whose selected state matches the given value.
* @deprecated Will be moved into separate selection-specific harness.
* @deprecated Use `MatChipOptionHarness` together with `ChipOptionHarnessFilters`.
* @breaking-change 12.0.0
*/
selected?: boolean;
}

/** A set of criteria that can be used to filter a list of `MatChipListHarness` instances. */
/** A set of criteria that can be used to filter a list of selectable chip instances. */
export interface ChipOptionHarnessFilters extends ChipHarnessFilters {
/** Only find chip instances whose selected state matches the given value. */
selected?: boolean;
}

/** A set of criteria that can be used to filter chip list instances. */
export interface ChipListHarnessFilters extends BaseHarnessFilters {}

/** A set of criteria that can be used to filter selectable chip list instances. */
export interface ChipListboxHarnessFilters extends BaseHarnessFilters {}

/** A set of criteria that can be used to filter a list of `MatChipListInputHarness` instances. */
export interface ChipInputHarnessFilters extends BaseHarnessFilters {
/** Filters based on the value of the input. */
Expand Down
10 changes: 5 additions & 5 deletions src/material/chips/testing/chip-harness.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ import {ComponentHarness, HarnessPredicate, TestKey} from '@angular/cdk/testing'
import {ChipHarnessFilters, ChipRemoveHarnessFilters} from './chip-harness-filters';
import {MatChipRemoveHarness} from './chip-remove-harness';

/** Harness for interacting with a standard Angular Material chip in tests. */
/** Harness for interacting with a standard selectable Angular Material chip in tests. */
export class MatChipHarness extends ComponentHarness {
/** The selector for the host element of a `MatChip` instance. */
static hostSelector = '.mat-chip';
Expand Down Expand Up @@ -38,7 +38,7 @@ export class MatChipHarness extends ComponentHarness {

/**
* Whether the chip is selected.
* @deprecated Will be moved into separate selection-specific harness.
* @deprecated Use `MatChipOptionHarness.isSelected` instead.
* @breaking-change 12.0.0
*/
async isSelected(): Promise<boolean> {
Expand All @@ -52,7 +52,7 @@ export class MatChipHarness extends ComponentHarness {

/**
* Selects the given chip. Only applies if it's selectable.
* @deprecated Will be moved into separate selection-specific harness.
* @deprecated Use `MatChipOptionHarness.select` instead.
* @breaking-change 12.0.0
*/
async select(): Promise<void> {
Expand All @@ -63,7 +63,7 @@ export class MatChipHarness extends ComponentHarness {

/**
* Deselects the given chip. Only applies if it's selectable.
* @deprecated Will be moved into separate selection-specific harness.
* @deprecated Use `MatChipOptionHarness.deselect` instead.
* @breaking-change 12.0.0
*/
async deselect(): Promise<void> {
Expand All @@ -74,7 +74,7 @@ export class MatChipHarness extends ComponentHarness {

/**
* Toggles the selected state of the given chip. Only applies if it's selectable.
* @deprecated Will be moved into separate selection-specific harness.
* @deprecated Use `MatChipOptionHarness.toggle` instead.
* @breaking-change 12.0.0
*/
async toggle(): Promise<void> {
Expand Down
6 changes: 4 additions & 2 deletions src/material/chips/testing/chip-list-harness.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,8 +4,10 @@ import {MatChipListHarness} from './chip-list-harness';
import {MatChipHarness} from './chip-harness';
import {MatChipInputHarness} from './chip-input-harness';
import {MatChipRemoveHarness} from './chip-remove-harness';
import {MatChipListboxHarness} from './chip-listbox-harness';
import {MatChipOptionHarness} from './chip-option-harness';

describe('Non-MDC-based MatChipListHarness', () => {
runHarnessTests(MatChipsModule, MatChipListHarness, MatChipHarness, MatChipInputHarness,
MatChipRemoveHarness);
runHarnessTests(MatChipsModule, MatChipListHarness, MatChipListboxHarness, MatChipHarness,
MatChipOptionHarness, MatChipInputHarness, MatChipRemoveHarness);
});
37 changes: 20 additions & 17 deletions src/material/chips/testing/chip-list-harness.ts
Original file line number Diff line number Diff line change
Expand Up @@ -15,21 +15,8 @@ import {
ChipInputHarnessFilters,
} from './chip-harness-filters';

/** Harness for interacting with a standard chip list in tests. */
export class MatChipListHarness extends ComponentHarness {
/** The selector for the host element of a `MatChipList` instance. */
static hostSelector = '.mat-chip-list';

/**
* Gets a `HarnessPredicate` that can be used to search for a `MatChipListHarness` that meets
* certain criteria.
* @param options Options for filtering which chip list instances are considered a match.
* @return a `HarnessPredicate` configured with the given options.
*/
static with(options: ChipListHarnessFilters = {}): HarnessPredicate<MatChipListHarness> {
return new HarnessPredicate(MatChipListHarness, options);
}

/** Base class for chip list harnesses. */
export abstract class _MatChipListHarnessBase extends ComponentHarness {
/** Gets whether the chip list is disabled. */
async isDisabled(): Promise<boolean> {
return await (await this.host()).getAttribute('aria-disabled') === 'true';
Expand All @@ -55,6 +42,22 @@ export class MatChipListHarness extends ComponentHarness {
const orientation = await (await this.host()).getAttribute('aria-orientation');
return orientation === 'vertical' ? 'vertical' : 'horizontal';
}
}

/** Harness for interacting with a standard chip list in tests. */
export class MatChipListHarness extends _MatChipListHarnessBase {
/** The selector for the host element of a `MatChipList` instance. */
static hostSelector = '.mat-chip-list';

/**
* Gets a `HarnessPredicate` that can be used to search for a `MatChipListHarness` that meets
* certain criteria.
* @param options Options for filtering which chip list instances are considered a match.
* @return a `HarnessPredicate` configured with the given options.
*/
static with(options: ChipListHarnessFilters = {}): HarnessPredicate<MatChipListHarness> {
return new HarnessPredicate(MatChipListHarness, options);
}

/**
* Gets the list of chips inside the chip list.
Expand All @@ -68,13 +71,13 @@ export class MatChipListHarness extends ComponentHarness {
* Selects a chip inside the chip list.
* @param filter An optional filter to apply to the child chips.
* All the chips matching the filter will be selected.
* @deprecated Will be moved into separate selection-specific harness.
* @deprecated Use `MatChipListboxHarness.selectChips` instead.
* @breaking-change 12.0.0
*/
async selectChips(filter: ChipHarnessFilters = {}): Promise<void> {
const chips = await this.getChips(filter);
if (!chips.length) {
throw Error(`Cannot find mat-chip matching filter ${JSON.stringify(filter)}`);
throw Error(`Cannot find chip matching filter ${JSON.stringify(filter)}`);
}
await Promise.all(chips.map(chip => chip.select()));
}
Expand Down
53 changes: 53 additions & 0 deletions src/material/chips/testing/chip-listbox-harness.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,53 @@
/**
* @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 {HarnessPredicate} from '@angular/cdk/testing';
import {MatChipOptionHarness} from './chip-option-harness';
import {
ChipListboxHarnessFilters,
ChipOptionHarnessFilters,
} from './chip-harness-filters';
import {_MatChipListHarnessBase} from './chip-list-harness';

/** Harness for interacting with a standard selectable chip list in tests. */
export class MatChipListboxHarness extends _MatChipListHarnessBase {
/** The selector for the host element of a `MatChipList` instance. */
static hostSelector = '.mat-chip-list';

/**
* Gets a `HarnessPredicate` that can be used to search for a `MatChipListHarness` that meets
* certain criteria.
* @param options Options for filtering which chip list instances are considered a match.
* @return a `HarnessPredicate` configured with the given options.
*/
static with(options: ChipListboxHarnessFilters = {}):
HarnessPredicate<MatChipListboxHarness> {
return new HarnessPredicate(MatChipListboxHarness, options);
}

/**
* Gets the list of chips inside the chip list.
* @param filter Optionally filters which chips are included.
*/
async getChips(filter: ChipOptionHarnessFilters = {}): Promise<MatChipOptionHarness[]> {
return this.locatorForAll(MatChipOptionHarness.with(filter))();
}

/**
* Selects a chip inside the chip list.
* @param filter An optional filter to apply to the child chips.
* All the chips matching the filter will be selected.
*/
async selectChips(filter: ChipOptionHarnessFilters = {}): Promise<void> {
const chips = await this.getChips(filter);
if (!chips.length) {
throw Error(`Cannot find chip matching filter ${JSON.stringify(filter)}`);
}
await Promise.all(chips.map(chip => chip.select()));
}
}
55 changes: 55 additions & 0 deletions src/material/chips/testing/chip-option-harness.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,55 @@
/**
* @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 {HarnessPredicate} from '@angular/cdk/testing';
import {MatChipHarness} from './chip-harness';
import {ChipOptionHarnessFilters} from './chip-harness-filters';

export class MatChipOptionHarness extends MatChipHarness {
/** The selector for the host element of a selectable chip instance. */
static hostSelector = '.mat-chip';

/**
* Gets a `HarnessPredicate` that can be used to search for a `MatChipOptionHarness`
* that meets certain criteria.
* @param options Options for filtering which chip instances are considered a match.
* @return a `HarnessPredicate` configured with the given options.
*/
static with(options: ChipOptionHarnessFilters = {}):
HarnessPredicate<MatChipOptionHarness> {
return new HarnessPredicate(MatChipOptionHarness, options)
.addOption('text', options.text,
(harness, label) => HarnessPredicate.stringMatches(harness.getText(), label))
.addOption('selected', options.selected,
async (harness, selected) => (await harness.isSelected()) === selected);
}

/** Whether the chip is selected. */
async isSelected(): Promise<boolean> {
return (await this.host()).hasClass('mat-chip-selected');
}

/** Selects the given chip. Only applies if it's selectable. */
async select(): Promise<void> {
if (!(await this.isSelected())) {
await this.toggle();
}
}

/** Deselects the given chip. Only applies if it's selectable. */
async deselect(): Promise<void> {
if (await this.isSelected()) {
await this.toggle();
}
}

/** Toggles the selected state of the given chip. */
async toggle(): Promise<void> {
return (await this.host()).sendKeys(' ');
}
}
4 changes: 3 additions & 1 deletion src/material/chips/testing/public-api.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,8 @@

export * from './chip-harness';
export * from './chip-harness-filters';
export * from './chip-list-harness';
export {MatChipListHarness} from './chip-list-harness';
export * from './chip-input-harness';
export * from './chip-remove-harness';
export * from './chip-option-harness';
export * from './chip-listbox-harness';
16 changes: 10 additions & 6 deletions src/material/chips/testing/shared.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,12 +9,16 @@ import {MatChipListHarness} from './chip-list-harness';
import {MatChipHarness} from './chip-harness';
import {MatChipInputHarness} from './chip-input-harness';
import {MatChipRemoveHarness} from './chip-remove-harness';
import {MatChipOptionHarness} from './chip-option-harness';
import {MatChipListboxHarness} from './chip-listbox-harness';

/** Shared tests to run on both the original and MDC-based chips. */
export function runHarnessTests(
chipsModule: typeof MatChipsModule,
chipListHarness: typeof MatChipListHarness,
listboxHarness: typeof MatChipListboxHarness,
chipHarness: typeof MatChipHarness,
chipOptionHarness: typeof MatChipOptionHarness,
chipInputHarness: typeof MatChipInputHarness,
chipRemoveHarness: typeof MatChipRemoveHarness) {
let fixture: ComponentFixture<ChipsHarnessTest>;
Expand Down Expand Up @@ -124,7 +128,7 @@ export function runHarnessTests(
});

it('should be able to get the selected chips in a list', async () => {
const chipList = await loader.getHarness(chipListHarness);
const chipList = await loader.getHarness(listboxHarness);
const chips = await chipList.getChips();

expect((await chipList.getChips({selected: true})).length).toBe(0);
Expand All @@ -135,7 +139,7 @@ export function runHarnessTests(
});

it('should be able to select chips based on a filter', async () => {
const chipList = await loader.getHarness(chipListHarness);
const chipList = await loader.getHarness(listboxHarness);
fixture.componentInstance.isMultiple = true;

expect((await chipList.getChips({selected: true})).length).toBe(0);
Expand Down Expand Up @@ -166,22 +170,22 @@ export function runHarnessTests(
});

it('should be able to select a chip', async () => {
const chip = await loader.getHarness(chipHarness);
const chip = await loader.getHarness(chipOptionHarness);
expect(await chip.isSelected()).toBe(false);
await chip.select();
expect(await chip.isSelected()).toBe(true);
});

it('should be able to deselect a chip', async () => {
const chip = await loader.getHarness(chipHarness);
const chip = await loader.getHarness(chipOptionHarness);
await chip.select();
expect(await chip.isSelected()).toBe(true);
await chip.deselect();
expect(await chip.isSelected()).toBe(false);
});

it('should be able to toggle the selected state of a chip', async () => {
const chip = await loader.getHarness(chipHarness);
const chip = await loader.getHarness(chipOptionHarness);
expect(await chip.isSelected()).toBe(false);
await chip.toggle();
expect(await chip.isSelected()).toBe(true);
Expand All @@ -202,7 +206,7 @@ export function runHarnessTests(
});

it('should get the selected text of a chip', async () => {
const chips = await loader.getAllHarnesses(chipHarness);
const chips = await loader.getAllHarnesses(chipOptionHarness);
expect(await Promise.all(chips.map(chip => chip.isSelected()))).toEqual([
false,
false,
Expand Down
30 changes: 24 additions & 6 deletions tools/public_api_guard/material/chips/testing.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,9 +8,16 @@ export interface ChipInputHarnessFilters extends BaseHarnessFilters {
value?: string | RegExp;
}

export interface ChipListboxHarnessFilters extends BaseHarnessFilters {
}

export interface ChipListHarnessFilters extends BaseHarnessFilters {
}

export interface ChipOptionHarnessFilters extends ChipHarnessFilters {
selected?: boolean;
}

export interface ChipRemoveHarnessFilters extends BaseHarnessFilters {
}

Expand Down Expand Up @@ -41,19 +48,30 @@ export declare class MatChipInputHarness extends ComponentHarness {
static with(options?: ChipInputHarnessFilters): HarnessPredicate<MatChipInputHarness>;
}

export declare class MatChipListHarness extends ComponentHarness {
export declare class MatChipListboxHarness extends _MatChipListHarnessBase {
getChips(filter?: ChipOptionHarnessFilters): Promise<MatChipOptionHarness[]>;
selectChips(filter?: ChipOptionHarnessFilters): Promise<void>;
static hostSelector: string;
static with(options?: ChipListboxHarnessFilters): HarnessPredicate<MatChipListboxHarness>;
}

export declare class MatChipListHarness extends _MatChipListHarnessBase {
getChips(filter?: ChipHarnessFilters): Promise<MatChipHarness[]>;
getInput(filter?: ChipInputHarnessFilters): Promise<MatChipInputHarness>;
getOrientation(): Promise<'horizontal' | 'vertical'>;
isDisabled(): Promise<boolean>;
isInvalid(): Promise<boolean>;
isMultiple(): Promise<boolean>;
isRequired(): Promise<boolean>;
selectChips(filter?: ChipHarnessFilters): Promise<void>;
static hostSelector: string;
static with(options?: ChipListHarnessFilters): HarnessPredicate<MatChipListHarness>;
}

export declare class MatChipOptionHarness extends MatChipHarness {
deselect(): Promise<void>;
isSelected(): Promise<boolean>;
select(): Promise<void>;
toggle(): Promise<void>;
static hostSelector: string;
static with(options?: ChipOptionHarnessFilters): HarnessPredicate<MatChipOptionHarness>;
}

export declare class MatChipRemoveHarness extends ComponentHarness {
click(): Promise<void>;
static hostSelector: string;
Expand Down