Skip to content

Commit 6f3df01

Browse files
authored
refactor(material-experimental/mdc-form-field): de-duplicate test harness logic (#21900)
De-duplicates the test harness logic between the MDC and non-MDC implementations.
1 parent ce9c905 commit 6f3df01

File tree

4 files changed

+143
-289
lines changed

4 files changed

+143
-289
lines changed

scripts/check-mdc-exports-config.ts

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -33,6 +33,10 @@ export const config = {
3333
'_MatDialogContainerBase',
3434
'_closeDialogVia',
3535
],
36+
'mdc-form-field/testing': [
37+
// Private base class that is only exported for MDC.
38+
'_MatFormFieldHarnessBase'
39+
],
3640
'mdc-menu': [
3741
// Private base class that is only exported for MDC.
3842
'_MatMenuBase'

src/material-experimental/mdc-form-field/testing/form-field-harness.ts

Lines changed: 14 additions & 195 deletions
Original file line numberDiff line numberDiff line change
@@ -6,16 +6,11 @@
66
* found in the LICENSE file at https://angular.io/license
77
*/
88

9+
import {HarnessPredicate} from '@angular/cdk/testing';
910
import {
10-
ComponentHarness,
11-
ComponentHarnessConstructor,
12-
HarnessPredicate,
13-
HarnessQuery,
14-
parallel,
15-
TestElement
16-
} from '@angular/cdk/testing';
17-
import {FormFieldHarnessFilters} from '@angular/material/form-field/testing';
18-
import {MatFormFieldControlHarness} from '@angular/material/form-field/testing/control';
11+
FormFieldHarnessFilters,
12+
_MatFormFieldHarnessBase,
13+
} from '@angular/material/form-field/testing';
1914
import {MatInputHarness} from '@angular/material-experimental/mdc-input/testing';
2015
import {MatSelectHarness} from '@angular/material-experimental/mdc-select/testing';
2116

@@ -25,7 +20,7 @@ import {MatSelectHarness} from '@angular/material-experimental/mdc-select/testin
2520
export type FormFieldControlHarness = MatInputHarness|MatSelectHarness;
2621

2722
/** Harness for interacting with a MDC-based form-field's in tests. */
28-
export class MatFormFieldHarness extends ComponentHarness {
23+
export class MatFormFieldHarness extends _MatFormFieldHarnessBase<FormFieldControlHarness> {
2924
static hostSelector = '.mat-mdc-form-field';
3025

3126
/**
@@ -36,25 +31,21 @@ export class MatFormFieldHarness extends ComponentHarness {
3631
*/
3732
static with(options: FormFieldHarnessFilters = {}): HarnessPredicate<MatFormFieldHarness> {
3833
return new HarnessPredicate(MatFormFieldHarness, options)
39-
.addOption(
40-
'floatingLabelText', options.floatingLabelText,
34+
.addOption('floatingLabelText', options.floatingLabelText,
4135
async (harness, text) => HarnessPredicate.stringMatches(await harness.getLabel(), text))
42-
.addOption(
43-
'hasErrors', options.hasErrors,
36+
.addOption('hasErrors', options.hasErrors,
4437
async (harness, hasErrors) => await harness.hasErrors() === hasErrors);
4538
}
4639

40+
protected _prefixContainer = this.locatorForOptional('.mat-mdc-form-field-prefix');
41+
protected _suffixContainer = this.locatorForOptional('.mat-mdc-form-field-suffix');
42+
protected _label = this.locatorForOptional('.mdc-floating-label');
43+
protected _errors = this.locatorForAll('.mat-mdc-form-field-error');
44+
protected _hints = this.locatorForAll('.mat-mdc-form-field-hint');
45+
protected _inputControl = this.locatorForOptional(MatInputHarness);
46+
protected _selectControl = this.locatorForOptional(MatSelectHarness);
4747
private _mdcTextField = this.locatorFor('.mat-mdc-text-field-wrapper');
4848

49-
private _prefixContainer = this.locatorForOptional('.mat-mdc-form-field-prefix');
50-
private _suffixContainer = this.locatorForOptional('.mat-mdc-form-field-suffix');
51-
private _label = this.locatorForOptional('.mdc-floating-label');
52-
private _errors = this.locatorForAll('.mat-mdc-form-field-error');
53-
private _hints = this.locatorForAll('.mat-mdc-form-field-hint');
54-
55-
private _inputControl = this.locatorForOptional(MatInputHarness);
56-
private _selectControl = this.locatorForOptional(MatSelectHarness);
57-
5849
/** Gets the appearance of the form-field. */
5950
async getAppearance(): Promise<'fill'|'outline'> {
6051
const textFieldEl = await this._mdcTextField();
@@ -64,186 +55,14 @@ export class MatFormFieldHarness extends ComponentHarness {
6455
return 'fill';
6556
}
6657

67-
/**
68-
* Gets the harness of the control that is bound to the form-field. Only
69-
* default controls such as "MatInputHarness" and "MatSelectHarness" are
70-
* supported.
71-
*/
72-
async getControl(): Promise<FormFieldControlHarness|null>;
73-
74-
/**
75-
* Gets the harness of the control that is bound to the form-field. Searches
76-
* for a control that matches the specified harness type.
77-
*/
78-
async getControl<X extends MatFormFieldControlHarness>(type: ComponentHarnessConstructor<X>):
79-
Promise<X|null>;
80-
81-
/**
82-
* Gets the harness of the control that is bound to the form-field. Searches
83-
* for a control that matches the specified harness predicate.
84-
*/
85-
async getControl<X extends MatFormFieldControlHarness>(type: HarnessPredicate<X>):
86-
Promise<X|null>;
87-
88-
// Implementation of the "getControl" method overload signatures.
89-
async getControl<X extends MatFormFieldControlHarness>(type?: HarnessQuery<X>) {
90-
if (type) {
91-
return this.locatorForOptional(type)();
92-
}
93-
const hostEl = await this.host();
94-
const [isInput, isSelect] = await parallel(() => [
95-
hostEl.hasClass('mat-mdc-form-field-type-mat-input'),
96-
hostEl.hasClass('mat-mdc-form-field-type-mat-select'),
97-
]);
98-
if (isInput) {
99-
return this._inputControl();
100-
} else if (isSelect) {
101-
return this._selectControl();
102-
}
103-
return null;
104-
}
105-
10658
/** Whether the form-field has a label. */
10759
async hasLabel(): Promise<boolean> {
10860
return (await this._label()) !== null;
10961
}
11062

111-
/** Gets the label of the form-field. */
112-
async getLabel(): Promise<string|null> {
113-
const labelEl = await this._label();
114-
return labelEl ? labelEl.text() : null;
115-
}
116-
117-
/** Whether the form-field has errors. */
118-
async hasErrors(): Promise<boolean> {
119-
return (await this.getTextErrors()).length > 0;
120-
}
121-
12263
/** Whether the label is currently floating. */
12364
async isLabelFloating(): Promise<boolean> {
12465
const labelEl = await this._label();
12566
return labelEl !== null ? await labelEl.hasClass('mdc-floating-label--float-above') : false;
12667
}
127-
128-
/** Whether the form-field is disabled. */
129-
async isDisabled(): Promise<boolean> {
130-
return (await this.host()).hasClass('mat-form-field-disabled');
131-
}
132-
133-
/** Whether the form-field is currently autofilled. */
134-
async isAutofilled(): Promise<boolean> {
135-
return (await this.host()).hasClass('mat-form-field-autofilled');
136-
}
137-
138-
/** Gets the theme color of the form-field. */
139-
async getThemeColor(): Promise<'primary'|'accent'|'warn'> {
140-
const hostEl = await this.host();
141-
const [isAccent, isWarn] =
142-
await parallel(() => [hostEl.hasClass('mat-accent'), hostEl.hasClass('mat-warn')]);
143-
if (isAccent) {
144-
return 'accent';
145-
} else if (isWarn) {
146-
return 'warn';
147-
}
148-
return 'primary';
149-
}
150-
151-
/** Gets error messages which are currently displayed in the form-field. */
152-
async getTextErrors(): Promise<string[]> {
153-
const errors = await this._errors();
154-
return parallel(() => errors.map(e => e.text()));
155-
}
156-
157-
/** Gets hint messages which are currently displayed in the form-field. */
158-
async getTextHints(): Promise<string[]> {
159-
const hints = await this._hints();
160-
return parallel(() => hints.map(e => e.text()));
161-
}
162-
163-
/**
164-
* Gets a reference to the container element which contains all projected
165-
* prefixes of the form-field.
166-
* @deprecated Use `getPrefixText` instead.
167-
* @breaking-change 11.0.0
168-
*/
169-
async getHarnessLoaderForPrefix(): Promise<TestElement|null> {
170-
return this._prefixContainer();
171-
}
172-
173-
/** Gets the text inside the prefix element. */
174-
async getPrefixText(): Promise<string> {
175-
const prefix = await this._prefixContainer();
176-
return prefix ? prefix.text() : '';
177-
}
178-
179-
/**
180-
* Gets a reference to the container element which contains all projected
181-
* suffixes of the form-field.
182-
* @deprecated Use `getSuffixText` instead.
183-
* @breaking-change 11.0.0
184-
*/
185-
async getHarnessLoaderForSuffix(): Promise<TestElement|null> {
186-
return this._suffixContainer();
187-
}
188-
189-
/** Gets the text inside the suffix element. */
190-
async getSuffixText(): Promise<string> {
191-
const suffix = await this._suffixContainer();
192-
return suffix ? suffix.text() : '';
193-
}
194-
195-
/**
196-
* Whether the form control has been touched. Returns "null"
197-
* if no form control is set up.
198-
*/
199-
async isControlTouched(): Promise<boolean|null> {
200-
if (!await this._hasFormControl()) {
201-
return null;
202-
}
203-
return (await this.host()).hasClass('ng-touched');
204-
}
205-
206-
/**
207-
* Whether the form control is dirty. Returns "null"
208-
* if no form control is set up.
209-
*/
210-
async isControlDirty(): Promise<boolean|null> {
211-
if (!await this._hasFormControl()) {
212-
return null;
213-
}
214-
return (await this.host()).hasClass('ng-dirty');
215-
}
216-
217-
/**
218-
* Whether the form control is valid. Returns "null"
219-
* if no form control is set up.
220-
*/
221-
async isControlValid(): Promise<boolean|null> {
222-
if (!await this._hasFormControl()) {
223-
return null;
224-
}
225-
return (await this.host()).hasClass('ng-valid');
226-
}
227-
228-
/**
229-
* Whether the form control is pending validation. Returns "null"
230-
* if no form control is set up.
231-
*/
232-
async isControlPending(): Promise<boolean|null> {
233-
if (!await this._hasFormControl()) {
234-
return null;
235-
}
236-
return (await this.host()).hasClass('ng-pending');
237-
}
238-
239-
/** Checks whether the form-field control has set up a form control. */
240-
private async _hasFormControl(): Promise<boolean> {
241-
const hostEl = await this.host();
242-
// If no form "NgControl" is bound to the form-field control, the form-field
243-
// is not able to forward any control status classes. Therefore if either the
244-
// "ng-touched" or "ng-untouched" class is set, we know that it has a form control
245-
const [isTouched, isUntouched] =
246-
await parallel(() => [hostEl.hasClass('ng-touched'), hostEl.hasClass('ng-untouched')]);
247-
return isTouched || isUntouched;
248-
}
24968
}

0 commit comments

Comments
 (0)