Skip to content

Commit 9d92927

Browse files
authored
fix(material-experimental/mdc-radio): de-duplicate test harness logic (#21532)
Changes the MDC-based radio harnesses to extend the base ones directly since the logic is identical, aside from some selectors.
1 parent acb3f33 commit 9d92927

File tree

7 files changed

+124
-278
lines changed

7 files changed

+124
-278
lines changed

scripts/check-mdc-exports-config.ts

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -42,6 +42,11 @@ export const config = {
4242
'_MatRadioGroupBase',
4343
'_MatRadioButtonBase',
4444
],
45+
'mdc-radio/testing': [
46+
// Private base classes that are only exported for MDC.
47+
'_MatRadioGroupHarnessBase',
48+
'_MatRadioButtonHarnessBase',
49+
],
4550
'mdc-select': [
4651
// Private base class that is only exported for MDC.
4752
'_MatSelectBase'

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

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,6 @@ ts_library(
1010
),
1111
module_name = "@angular/material-experimental/mdc-radio/testing",
1212
deps = [
13-
"//src/cdk/coercion",
1413
"//src/cdk/testing",
1514
"//src/material/radio/testing",
1615
],

src/material-experimental/mdc-radio/testing/public-api.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7,4 +7,4 @@
77
*/
88

99
export * from './radio-harness';
10-
export * from './radio-harness-filters';
10+
export {RadioButtonHarnessFilters, RadioGroupHarnessFilters} from '@angular/material/radio/testing';

src/material-experimental/mdc-radio/testing/radio-harness-filters.ts

Lines changed: 0 additions & 23 deletions
This file was deleted.

src/material-experimental/mdc-radio/testing/radio-harness.ts

Lines changed: 26 additions & 210 deletions
Original file line numberDiff line numberDiff line change
@@ -6,155 +6,39 @@
66
* found in the LICENSE file at https://angular.io/license
77
*/
88

9-
import {coerceBooleanProperty} from '@angular/cdk/coercion';
10-
import {ComponentHarness, HarnessPredicate} from '@angular/cdk/testing';
11-
import {RadioButtonHarnessFilters, RadioGroupHarnessFilters} from './radio-harness-filters';
9+
import {HarnessPredicate} from '@angular/cdk/testing';
10+
import {
11+
RadioButtonHarnessFilters,
12+
RadioGroupHarnessFilters,
13+
_MatRadioGroupHarnessBase,
14+
_MatRadioButtonHarnessBase,
15+
} from '@angular/material/radio/testing';
1216

1317
/** Harness for interacting with an MDC-based mat-radio-group in tests. */
14-
export class MatRadioGroupHarness extends ComponentHarness {
18+
export class MatRadioGroupHarness extends _MatRadioGroupHarnessBase<
19+
typeof MatRadioButtonHarness,
20+
MatRadioButtonHarness,
21+
RadioButtonHarnessFilters
22+
> {
1523
/** The selector for the host element of a `MatRadioGroup` instance. */
1624
static hostSelector = '.mat-mdc-radio-group';
25+
protected _buttonClass = MatRadioButtonHarness;
1726

1827
/**
1928
* Gets a `HarnessPredicate` that can be used to search for a `MatRadioGroupHarness` that meets
2029
* certain criteria.
2130
* @param options Options for filtering which radio group instances are considered a match.
2231
* @return a `HarnessPredicate` configured with the given options.
2332
*/
24-
static with(options: RadioGroupHarnessFilters = {}): HarnessPredicate<MatRadioGroupHarness> {
25-
return new HarnessPredicate(MatRadioGroupHarness, options)
33+
static with(options: RadioGroupHarnessFilters = {}):
34+
HarnessPredicate<MatRadioGroupHarness> {
35+
return new HarnessPredicate<MatRadioGroupHarness>(MatRadioGroupHarness, options)
2636
.addOption('name', options.name, this._checkRadioGroupName);
2737
}
28-
29-
/** Gets the name of the radio-group. */
30-
async getName(): Promise<string|null> {
31-
const hostName = await this._getGroupNameFromHost();
32-
// It's not possible to always determine the "name" of a radio-group by reading
33-
// the attribute. This is because the radio-group does not set the "name" as an
34-
// element attribute if the "name" value is set through a binding.
35-
if (hostName !== null) {
36-
return hostName;
37-
}
38-
// In case we couldn't determine the "name" of a radio-group by reading the
39-
// "name" attribute, we try to determine the "name" of the group by going
40-
// through all radio buttons.
41-
const radioNames = await this._getNamesFromRadioButtons();
42-
if (!radioNames.length) {
43-
return null;
44-
}
45-
if (!this._checkRadioNamesInGroupEqual(radioNames)) {
46-
throw Error('Radio buttons in radio-group have mismatching names.');
47-
}
48-
return radioNames[0]!;
49-
}
50-
51-
/** Gets the id of the radio-group. */
52-
async getId(): Promise<string|null> {
53-
return (await this.host()).getProperty('id');
54-
}
55-
56-
/** Gets the checked radio-button in a radio-group. */
57-
async getCheckedRadioButton(): Promise<MatRadioButtonHarness|null> {
58-
for (let radioButton of await this.getRadioButtons()) {
59-
if (await radioButton.isChecked()) {
60-
return radioButton;
61-
}
62-
}
63-
return null;
64-
}
65-
66-
/** Gets the checked value of the radio-group. */
67-
async getCheckedValue(): Promise<string|null> {
68-
const checkedRadio = await this.getCheckedRadioButton();
69-
if (!checkedRadio) {
70-
return null;
71-
}
72-
return checkedRadio.getValue();
73-
}
74-
75-
/**
76-
* Gets a list of radio buttons which are part of the radio-group.
77-
* @param filter Optionally filters which radio buttons are included.
78-
*/
79-
async getRadioButtons(filter: RadioButtonHarnessFilters = {}): Promise<MatRadioButtonHarness[]> {
80-
return this.locatorForAll(MatRadioButtonHarness.with(filter))();
81-
}
82-
83-
/**
84-
* Checks a radio button in this group.
85-
* @param filter An optional filter to apply to the child radio buttons. The first tab matching
86-
* the filter will be selected.
87-
*/
88-
async checkRadioButton(filter: RadioButtonHarnessFilters = {}): Promise<void> {
89-
const radioButtons = await this.getRadioButtons(filter);
90-
if (!radioButtons.length) {
91-
throw Error(`Could not find radio button matching ${JSON.stringify(filter)}`);
92-
}
93-
return radioButtons[0].check();
94-
}
95-
96-
/** Gets the name attribute of the host element. */
97-
private async _getGroupNameFromHost() {
98-
return (await this.host()).getAttribute('name');
99-
}
100-
101-
/** Gets a list of the name attributes of all child radio buttons. */
102-
private async _getNamesFromRadioButtons(): Promise<string[]> {
103-
const groupNames: string[] = [];
104-
for (let radio of await this.getRadioButtons()) {
105-
const radioName = await radio.getName();
106-
if (radioName !== null) {
107-
groupNames.push(radioName);
108-
}
109-
}
110-
return groupNames;
111-
}
112-
113-
/** Checks if the specified radio names are all equal. */
114-
private _checkRadioNamesInGroupEqual(radioNames: string[]): boolean {
115-
let groupName: string|null = null;
116-
for (let radioName of radioNames) {
117-
if (groupName === null) {
118-
groupName = radioName;
119-
} else if (groupName !== radioName) {
120-
return false;
121-
}
122-
}
123-
return true;
124-
}
125-
126-
/**
127-
* Checks if a radio-group harness has the given name. Throws if a radio-group with
128-
* matching name could be found but has mismatching radio-button names.
129-
*/
130-
private static async _checkRadioGroupName(harness: MatRadioGroupHarness, name: string) {
131-
// Check if there is a radio-group which has the "name" attribute set
132-
// to the expected group name. It's not possible to always determine
133-
// the "name" of a radio-group by reading the attribute. This is because
134-
// the radio-group does not set the "name" as an element attribute if the
135-
// "name" value is set through a binding.
136-
if (await harness._getGroupNameFromHost() === name) {
137-
return true;
138-
}
139-
// Check if there is a group with radio-buttons that all have the same
140-
// expected name. This implies that the group has the given name. It's
141-
// not possible to always determine the name of a radio-group through
142-
// the attribute because there is
143-
const radioNames = await harness._getNamesFromRadioButtons();
144-
if (radioNames.indexOf(name) === -1) {
145-
return false;
146-
}
147-
if (!harness._checkRadioNamesInGroupEqual(radioNames)) {
148-
throw Error(
149-
`The locator found a radio-group with name "${name}", but some ` +
150-
`radio-button's within the group have mismatching names, which is invalid.`);
151-
}
152-
return true;
153-
}
15438
}
15539

15640
/** Harness for interacting with an MDC-based mat-radio-button in tests. */
157-
export class MatRadioButtonHarness extends ComponentHarness {
41+
export class MatRadioButtonHarness extends _MatRadioButtonHarnessBase {
15842
/** The selector for the host element of a `MatRadioButton` instance. */
15943
static hostSelector = '.mat-mdc-radio-button';
16044

@@ -164,83 +48,15 @@ export class MatRadioButtonHarness extends ComponentHarness {
16448
* @param options Options for filtering which radio button instances are considered a match.
16549
* @return a `HarnessPredicate` configured with the given options.
16650
*/
167-
static with(options: RadioButtonHarnessFilters = {}): HarnessPredicate<MatRadioButtonHarness> {
168-
return new HarnessPredicate(MatRadioButtonHarness, options)
169-
.addOption(
170-
'label', options.label,
171-
(harness, label) => HarnessPredicate.stringMatches(harness.getLabelText(), label))
172-
.addOption(
173-
'name', options.name, async (harness, name) => (await harness.getName()) === name);
51+
static with(options: RadioButtonHarnessFilters = {}):
52+
HarnessPredicate<MatRadioButtonHarness> {
53+
return new HarnessPredicate<MatRadioButtonHarness>(MatRadioButtonHarness, options)
54+
.addOption('label', options.label,
55+
(harness, label) => HarnessPredicate.stringMatches(harness.getLabelText(), label))
56+
.addOption('name', options.name,
57+
async (harness, name) => (await harness.getName()) === name);
17458
}
17559

176-
private _label = this.locatorFor('label');
177-
private _input = this.locatorFor('input');
178-
179-
/** Whether the radio-button is checked. */
180-
async isChecked(): Promise<boolean> {
181-
const checked = (await this._input()).getProperty('checked');
182-
return coerceBooleanProperty(await checked);
183-
}
184-
185-
/** Whether the radio-button is disabled. */
186-
async isDisabled(): Promise<boolean> {
187-
const disabled = (await this._input()).getAttribute('disabled');
188-
return coerceBooleanProperty(await disabled);
189-
}
190-
191-
/** Whether the radio-button is required. */
192-
async isRequired(): Promise<boolean> {
193-
const required = (await this._input()).getAttribute('required');
194-
return coerceBooleanProperty(await required);
195-
}
196-
197-
/** Gets the radio-button's name. */
198-
async getName(): Promise<string|null> {
199-
return (await this._input()).getAttribute('name');
200-
}
201-
202-
/** Gets the radio-button's id. */
203-
async getId(): Promise<string|null> {
204-
return (await this.host()).getProperty('id');
205-
}
206-
207-
/**
208-
* Gets the value of the radio-button. The radio-button value will be converted to a string.
209-
*
210-
* Note: This means that for radio-button's with an object as a value `[object Object]` is
211-
* intentionally returned.
212-
*/
213-
async getValue(): Promise<string|null> {
214-
return (await this._input()).getProperty('value');
215-
}
216-
217-
/** Gets the radio-button's label text. */
218-
async getLabelText(): Promise<string> {
219-
return (await this._label()).text();
220-
}
221-
222-
/** Focuses the radio-button. */
223-
async focus(): Promise<void> {
224-
return (await this._input()).focus();
225-
}
226-
227-
/** Blurs the radio-button. */
228-
async blur(): Promise<void> {
229-
return (await this._input()).blur();
230-
}
231-
232-
/** Whether the radio-button is focused. */
233-
async isFocused(): Promise<boolean> {
234-
return (await this._input()).isFocused();
235-
}
236-
237-
/**
238-
* Puts the radio-button in a checked state by clicking it if it is currently unchecked,
239-
* or doing nothing if it is already checked.
240-
*/
241-
async check(): Promise<void> {
242-
if (!(await this.isChecked())) {
243-
return (await this._label()).click();
244-
}
245-
}
60+
protected _textLabel = this.locatorFor('label');
61+
protected _clickLabel = this._textLabel;
24662
}

0 commit comments

Comments
 (0)