398 lines
17 KiB
TypeScript
398 lines
17 KiB
TypeScript
|
|
import {Component, signal} from '@angular/core';
|
||
|
|
import {ComponentFixture, TestBed} from '@angular/core/testing';
|
||
|
|
import {ComponentHarness, HarnessLoader, HarnessPredicate, parallel} from '@angular/cdk/testing';
|
||
|
|
import {createFakeEvent, dispatchFakeEvent} from '@angular/cdk/testing/private';
|
||
|
|
import {TestbedHarnessEnvironment} from '@angular/cdk/testing/testbed';
|
||
|
|
import {FormControl, ReactiveFormsModule, Validators} from '@angular/forms';
|
||
|
|
import {NoopAnimationsModule} from '@angular/platform-browser/animations';
|
||
|
|
import {
|
||
|
|
MatError,
|
||
|
|
MatFormField,
|
||
|
|
MatHint,
|
||
|
|
MatLabel,
|
||
|
|
MatPrefix,
|
||
|
|
MatSuffix,
|
||
|
|
} from '@angular/material/form-field';
|
||
|
|
import {MatAutocomplete, MatAutocompleteTrigger} from '@angular/material/autocomplete';
|
||
|
|
import {MatInput} from '@angular/material/input';
|
||
|
|
import {MatSelect} from '@angular/material/select';
|
||
|
|
import {MatNativeDateModule, MatOption} from '@angular/material/core';
|
||
|
|
import {
|
||
|
|
MatDateRangeInput,
|
||
|
|
MatDateRangePicker,
|
||
|
|
MatDatepicker,
|
||
|
|
MatDatepickerInput,
|
||
|
|
MatDatepickerModule,
|
||
|
|
MatEndDate,
|
||
|
|
MatStartDate,
|
||
|
|
} from '@angular/material/datepicker';
|
||
|
|
import {MatInputHarness} from '@angular/material/input/testing';
|
||
|
|
import {MatSelectHarness} from '@angular/material/select/testing';
|
||
|
|
import {
|
||
|
|
MatDateRangeInputHarness,
|
||
|
|
MatDatepickerInputHarness,
|
||
|
|
} from '@angular/material/datepicker/testing';
|
||
|
|
import {MatFormFieldHarness} from './form-field-harness';
|
||
|
|
import {MatErrorHarness} from './error-harness';
|
||
|
|
|
||
|
|
describe('MatFormFieldHarness', () => {
|
||
|
|
let fixture: ComponentFixture<FormFieldHarnessTest>;
|
||
|
|
let loader: HarnessLoader;
|
||
|
|
|
||
|
|
beforeEach(() => {
|
||
|
|
TestBed.configureTestingModule({
|
||
|
|
imports: [
|
||
|
|
NoopAnimationsModule,
|
||
|
|
MatNativeDateModule,
|
||
|
|
FormFieldHarnessTest,
|
||
|
|
MatDatepickerModule,
|
||
|
|
],
|
||
|
|
});
|
||
|
|
|
||
|
|
fixture = TestBed.createComponent(FormFieldHarnessTest);
|
||
|
|
fixture.detectChanges();
|
||
|
|
loader = TestbedHarnessEnvironment.loader(fixture);
|
||
|
|
});
|
||
|
|
|
||
|
|
it('should be able to load harnesses', async () => {
|
||
|
|
const formFields = await loader.getAllHarnesses(MatFormFieldHarness);
|
||
|
|
expect(formFields.length).toBe(7);
|
||
|
|
});
|
||
|
|
|
||
|
|
it('should be able to load form-field that matches specific selector', async () => {
|
||
|
|
const formFieldMatches = await loader.getAllHarnesses(
|
||
|
|
MatFormFieldHarness.with({
|
||
|
|
selector: '#first-form-field',
|
||
|
|
}),
|
||
|
|
);
|
||
|
|
expect(formFieldMatches.length).toBe(1);
|
||
|
|
});
|
||
|
|
|
||
|
|
it('should be able to get appearance of form-field', async () => {
|
||
|
|
const formFields = await loader.getAllHarnesses(MatFormFieldHarness);
|
||
|
|
expect(await formFields[0].getAppearance()).toBe('fill');
|
||
|
|
expect(await formFields[1].getAppearance()).toBe('fill');
|
||
|
|
expect(await formFields[2].getAppearance()).toBe('fill');
|
||
|
|
expect(await formFields[3].getAppearance()).toBe('outline');
|
||
|
|
expect(await formFields[4].getAppearance()).toBe('fill');
|
||
|
|
});
|
||
|
|
|
||
|
|
it('should be able to get control of form-field', async () => {
|
||
|
|
const formFields = await loader.getAllHarnesses(MatFormFieldHarness);
|
||
|
|
expect((await formFields[0].getControl()) instanceof MatInputHarness).toBe(true);
|
||
|
|
expect((await formFields[1].getControl()) instanceof MatInputHarness).toBe(true);
|
||
|
|
expect((await formFields[2].getControl()) instanceof MatSelectHarness).toBe(true);
|
||
|
|
expect((await formFields[3].getControl()) instanceof MatInputHarness).toBe(true);
|
||
|
|
expect((await formFields[4].getControl()) instanceof MatInputHarness).toBe(true);
|
||
|
|
expect((await formFields[5].getControl()) instanceof MatDatepickerInputHarness).toBe(true);
|
||
|
|
expect((await formFields[6].getControl()) instanceof MatDateRangeInputHarness).toBe(true);
|
||
|
|
});
|
||
|
|
|
||
|
|
it('should be able to get custom control of form-field', async () => {
|
||
|
|
const formFields = await loader.getAllHarnesses(MatFormFieldHarness);
|
||
|
|
expect(await formFields[0].getControl(CustomControlHarness)).toBe(null);
|
||
|
|
expect(
|
||
|
|
(await formFields[1].getControl(CustomControlHarness)) instanceof CustomControlHarness,
|
||
|
|
).toBe(true);
|
||
|
|
expect(await formFields[2].getControl(CustomControlHarness)).toBe(null);
|
||
|
|
expect(await formFields[3].getControl(CustomControlHarness)).toBe(null);
|
||
|
|
expect(await formFields[4].getControl(CustomControlHarness)).toBe(null);
|
||
|
|
});
|
||
|
|
|
||
|
|
it('should be able to get custom control of form-field using a predicate', async () => {
|
||
|
|
const predicate = new HarnessPredicate(CustomControlHarness, {});
|
||
|
|
const formFields = await loader.getAllHarnesses(MatFormFieldHarness);
|
||
|
|
expect(await formFields[0].getControl(predicate)).toBe(null);
|
||
|
|
expect((await formFields[1].getControl(predicate)) instanceof CustomControlHarness).toBe(true);
|
||
|
|
expect(await formFields[2].getControl(predicate)).toBe(null);
|
||
|
|
expect(await formFields[3].getControl(predicate)).toBe(null);
|
||
|
|
expect(await formFields[4].getControl(predicate)).toBe(null);
|
||
|
|
});
|
||
|
|
|
||
|
|
it('should be able to check whether form-field has label', async () => {
|
||
|
|
const formFields = await loader.getAllHarnesses(MatFormFieldHarness);
|
||
|
|
expect(await formFields[0].hasLabel()).toBe(false);
|
||
|
|
expect(await formFields[1].hasLabel()).toBe(false);
|
||
|
|
expect(await formFields[2].hasLabel()).toBe(true);
|
||
|
|
expect(await formFields[3].hasLabel()).toBe(true);
|
||
|
|
expect(await formFields[4].hasLabel()).toBe(true);
|
||
|
|
});
|
||
|
|
|
||
|
|
it('should be able to check whether label is floating', async () => {
|
||
|
|
const formFields = await loader.getAllHarnesses(MatFormFieldHarness);
|
||
|
|
expect(await formFields[0].isLabelFloating()).toBe(false);
|
||
|
|
expect(await formFields[1].isLabelFloating()).toBe(false);
|
||
|
|
expect(await formFields[2].isLabelFloating()).toBe(false);
|
||
|
|
expect(await formFields[3].isLabelFloating()).toBe(true);
|
||
|
|
expect(await formFields[4].isLabelFloating()).toBe(false);
|
||
|
|
|
||
|
|
fixture.componentInstance.shouldLabelFloat.set('always');
|
||
|
|
expect(await formFields[4].isLabelFloating()).toBe(true);
|
||
|
|
});
|
||
|
|
|
||
|
|
it('should be able to check whether form-field is disabled', async () => {
|
||
|
|
const formFields = await loader.getAllHarnesses(MatFormFieldHarness);
|
||
|
|
expect(await formFields[0].isDisabled()).toBe(false);
|
||
|
|
expect(await formFields[1].isDisabled()).toBe(false);
|
||
|
|
expect(await formFields[2].isDisabled()).toBe(false);
|
||
|
|
expect(await formFields[3].isDisabled()).toBe(false);
|
||
|
|
expect(await formFields[4].isDisabled()).toBe(false);
|
||
|
|
|
||
|
|
fixture.componentInstance.isDisabled.set(true);
|
||
|
|
expect(await formFields[0].isDisabled()).toBe(true);
|
||
|
|
expect(await formFields[1].isDisabled()).toBe(false);
|
||
|
|
expect(await formFields[2].isDisabled()).toBe(true);
|
||
|
|
expect(await formFields[3].isDisabled()).toBe(false);
|
||
|
|
expect(await formFields[4].isDisabled()).toBe(false);
|
||
|
|
});
|
||
|
|
|
||
|
|
it('should be able to check whether form-field is auto-filled', async () => {
|
||
|
|
const formFields = await loader.getAllHarnesses(MatFormFieldHarness);
|
||
|
|
expect(await formFields[0].isAutofilled()).toBe(false);
|
||
|
|
expect(await formFields[1].isAutofilled()).toBe(false);
|
||
|
|
expect(await formFields[2].isAutofilled()).toBe(false);
|
||
|
|
expect(await formFields[3].isAutofilled()).toBe(false);
|
||
|
|
expect(await formFields[4].isAutofilled()).toBe(false);
|
||
|
|
|
||
|
|
const autofillTriggerEvent: any = createFakeEvent('animationstart');
|
||
|
|
autofillTriggerEvent.animationName = 'cdk-text-field-autofill-start';
|
||
|
|
|
||
|
|
// Dispatch an "animationstart" event on the input to trigger the
|
||
|
|
// autofill monitor.
|
||
|
|
fixture.nativeElement
|
||
|
|
.querySelector('#first-form-field input')
|
||
|
|
.dispatchEvent(autofillTriggerEvent);
|
||
|
|
|
||
|
|
expect(await formFields[0].isAutofilled()).toBe(true);
|
||
|
|
expect(await formFields[1].isAutofilled()).toBe(false);
|
||
|
|
expect(await formFields[2].isAutofilled()).toBe(false);
|
||
|
|
expect(await formFields[3].isAutofilled()).toBe(false);
|
||
|
|
expect(await formFields[4].isAutofilled()).toBe(false);
|
||
|
|
});
|
||
|
|
|
||
|
|
it('should be able to get theme color of form-field', async () => {
|
||
|
|
const formFields = await loader.getAllHarnesses(MatFormFieldHarness);
|
||
|
|
expect(await formFields[0].getThemeColor()).toBe('primary');
|
||
|
|
expect(await formFields[1].getThemeColor()).toBe('warn');
|
||
|
|
expect(await formFields[2].getThemeColor()).toBe('accent');
|
||
|
|
expect(await formFields[3].getThemeColor()).toBe('primary');
|
||
|
|
expect(await formFields[4].getThemeColor()).toBe('primary');
|
||
|
|
});
|
||
|
|
|
||
|
|
it('should be able to get label of form-field', async () => {
|
||
|
|
const formFields = await loader.getAllHarnesses(MatFormFieldHarness);
|
||
|
|
expect(await formFields[0].getLabel()).toBe(null);
|
||
|
|
expect(await formFields[1].getLabel()).toBe(null);
|
||
|
|
expect(await formFields[2].getLabel()).toBe('Label');
|
||
|
|
expect(await formFields[3].getLabel()).toBe('autocomplete_label');
|
||
|
|
expect(await formFields[4].getLabel()).toBe('Label');
|
||
|
|
});
|
||
|
|
|
||
|
|
it('should be able to get error messages of form-field', async () => {
|
||
|
|
const formFields = await loader.getAllHarnesses(MatFormFieldHarness);
|
||
|
|
expect(await formFields[1].getTextErrors()).toEqual([]);
|
||
|
|
|
||
|
|
fixture.componentInstance.requiredControl.setValue('');
|
||
|
|
dispatchFakeEvent(fixture.nativeElement.querySelector('#with-errors input'), 'blur');
|
||
|
|
expect(await formFields[1].getTextErrors()).toEqual(['Error 1', 'Error 2']);
|
||
|
|
});
|
||
|
|
|
||
|
|
it('should be able to get form-field by validity', async () => {
|
||
|
|
let invalid = await loader.getAllHarnesses(MatFormFieldHarness.with({isValid: false}));
|
||
|
|
expect(invalid.length).toBe(0);
|
||
|
|
|
||
|
|
fixture.componentInstance.requiredControl.setValue('');
|
||
|
|
dispatchFakeEvent(fixture.nativeElement.querySelector('#with-errors input'), 'blur');
|
||
|
|
|
||
|
|
invalid = await loader.getAllHarnesses(MatFormFieldHarness.with({isValid: false}));
|
||
|
|
expect(invalid.length).toBe(1);
|
||
|
|
});
|
||
|
|
|
||
|
|
it('should be able to get error harnesses from the form-field harness', async () => {
|
||
|
|
const formFields = await loader.getAllHarnesses(MatFormFieldHarness);
|
||
|
|
expect(await formFields[1].getErrors()).toEqual([]);
|
||
|
|
|
||
|
|
fixture.componentInstance.requiredControl.setValue('');
|
||
|
|
dispatchFakeEvent(fixture.nativeElement.querySelector('#with-errors input'), 'blur');
|
||
|
|
|
||
|
|
const formFieldErrorHarnesses = await formFields[1].getErrors();
|
||
|
|
expect(formFieldErrorHarnesses.length).toBe(2);
|
||
|
|
expect(await formFieldErrorHarnesses[0].getText()).toBe('Error 1');
|
||
|
|
expect(await formFieldErrorHarnesses[1].getText()).toBe('Error 2');
|
||
|
|
const error1Harnesses = await formFields[1].getErrors({text: 'Error 1'});
|
||
|
|
expect(error1Harnesses.length).toBe(1);
|
||
|
|
expect(await error1Harnesses[0].getText()).toBe('Error 1');
|
||
|
|
});
|
||
|
|
|
||
|
|
it('should be able to directly load error harnesses', async () => {
|
||
|
|
const formFields = await loader.getAllHarnesses(MatFormFieldHarness);
|
||
|
|
expect(await formFields[1].getErrors()).toEqual([]);
|
||
|
|
|
||
|
|
fixture.componentInstance.requiredControl.setValue('');
|
||
|
|
dispatchFakeEvent(fixture.nativeElement.querySelector('#with-errors input'), 'blur');
|
||
|
|
|
||
|
|
const errorHarnesses = await loader.getAllHarnesses(MatErrorHarness);
|
||
|
|
expect(errorHarnesses.length).toBe(2);
|
||
|
|
expect(await errorHarnesses[0].getText()).toBe('Error 1');
|
||
|
|
expect(await errorHarnesses[1].getText()).toBe('Error 2');
|
||
|
|
const error1Harnesses = await loader.getAllHarnesses(MatErrorHarness.with({text: 'Error 1'}));
|
||
|
|
expect(error1Harnesses.length).toBe(1);
|
||
|
|
expect(await error1Harnesses[0].getText()).toBe('Error 1');
|
||
|
|
});
|
||
|
|
|
||
|
|
it('should be able to get hint messages of form-field', async () => {
|
||
|
|
const formFields = await loader.getAllHarnesses(MatFormFieldHarness);
|
||
|
|
expect(await formFields[1].getTextHints()).toEqual(['Hint 1', 'Hint 2']);
|
||
|
|
|
||
|
|
fixture.componentInstance.requiredControl.setValue('');
|
||
|
|
dispatchFakeEvent(fixture.nativeElement.querySelector('#with-errors input'), 'blur');
|
||
|
|
expect(await formFields[1].getTextHints()).toEqual([]);
|
||
|
|
});
|
||
|
|
|
||
|
|
it('should be able to get the prefix text of a form-field', async () => {
|
||
|
|
const formFields = await loader.getAllHarnesses(MatFormFieldHarness);
|
||
|
|
const prefixTexts = await parallel(() => formFields.map(f => f.getPrefixText()));
|
||
|
|
expect(prefixTexts).toEqual(['prefix_textprefix_text_2', '', '', '', '', '', '']);
|
||
|
|
});
|
||
|
|
|
||
|
|
it('should be able to get the suffix text of a form-field', async () => {
|
||
|
|
const formFields = await loader.getAllHarnesses(MatFormFieldHarness);
|
||
|
|
const suffixTexts = await parallel(() => formFields.map(f => f.getSuffixText()));
|
||
|
|
expect(suffixTexts).toEqual(['suffix_text', '', '', '', '', '', '']);
|
||
|
|
});
|
||
|
|
|
||
|
|
it('should be able to check if form field has been touched', async () => {
|
||
|
|
const formFields = await loader.getAllHarnesses(MatFormFieldHarness);
|
||
|
|
expect(await formFields[0].isControlTouched()).toBe(null);
|
||
|
|
expect(await formFields[1].isControlTouched()).toBe(false);
|
||
|
|
|
||
|
|
fixture.componentInstance.requiredControl.setValue('');
|
||
|
|
dispatchFakeEvent(fixture.nativeElement.querySelector('#with-errors input'), 'blur');
|
||
|
|
expect(await formFields[1].isControlTouched()).toBe(true);
|
||
|
|
});
|
||
|
|
|
||
|
|
it('should be able to check if form field is invalid', async () => {
|
||
|
|
const formFields = await loader.getAllHarnesses(MatFormFieldHarness);
|
||
|
|
expect(await formFields[0].isControlValid()).toBe(null);
|
||
|
|
expect(await formFields[1].isControlValid()).toBe(true);
|
||
|
|
|
||
|
|
fixture.componentInstance.requiredControl.setValue('');
|
||
|
|
expect(await formFields[1].isControlValid()).toBe(false);
|
||
|
|
});
|
||
|
|
|
||
|
|
it('should be able to check if form field is dirty', async () => {
|
||
|
|
const formFields = await loader.getAllHarnesses(MatFormFieldHarness);
|
||
|
|
expect(await formFields[0].isControlDirty()).toBe(null);
|
||
|
|
expect(await formFields[1].isControlDirty()).toBe(false);
|
||
|
|
|
||
|
|
fixture.componentInstance.requiredControl.setValue('new value');
|
||
|
|
dispatchFakeEvent(fixture.nativeElement.querySelector('#with-errors input'), 'input');
|
||
|
|
expect(await formFields[1].isControlDirty()).toBe(true);
|
||
|
|
});
|
||
|
|
|
||
|
|
it('should be able to check if form field is pending async validation', async () => {
|
||
|
|
const formFields = await loader.getAllHarnesses(MatFormFieldHarness);
|
||
|
|
expect(await formFields[0].isControlPending()).toBe(null);
|
||
|
|
expect(await formFields[1].isControlPending()).toBe(false);
|
||
|
|
|
||
|
|
fixture.componentInstance.setupAsyncValidator();
|
||
|
|
fixture.componentInstance.requiredControl.setValue('');
|
||
|
|
expect(await formFields[1].isControlPending()).toBe(true);
|
||
|
|
});
|
||
|
|
});
|
||
|
|
|
||
|
|
@Component({
|
||
|
|
template: `
|
||
|
|
<mat-form-field id="first-form-field" [floatLabel]="shouldLabelFloat()">
|
||
|
|
<span matTextPrefix>prefix_text</span>
|
||
|
|
<span matTextPrefix>prefix_text_2</span>
|
||
|
|
<input matInput value="Sushi" name="favorite-food" placeholder="With placeholder"
|
||
|
|
[disabled]="isDisabled()">
|
||
|
|
<span matTextSuffix>suffix_text</span>
|
||
|
|
</mat-form-field>
|
||
|
|
|
||
|
|
<mat-form-field appearance="fill" color="warn" id="with-errors">
|
||
|
|
<span class="custom-control">Custom control harness</span>
|
||
|
|
<input matInput [formControl]="requiredControl">
|
||
|
|
|
||
|
|
<mat-error>Error 1</mat-error>
|
||
|
|
<div matError>Error 2</div>
|
||
|
|
<mat-hint align="start">Hint 1</mat-hint>
|
||
|
|
<mat-hint align="end">Hint 2</mat-hint>
|
||
|
|
</mat-form-field>
|
||
|
|
|
||
|
|
<mat-form-field appearance="fill" color="accent">
|
||
|
|
<mat-label>Label</mat-label>
|
||
|
|
<mat-select [disabled]="isDisabled()">
|
||
|
|
<mat-option>First</mat-option>
|
||
|
|
</mat-select>
|
||
|
|
</mat-form-field>
|
||
|
|
|
||
|
|
<mat-form-field floatLabel="always" appearance="outline" color="primary">
|
||
|
|
<mat-label>autocomplete_label</mat-label>
|
||
|
|
<input type="text" matInput [matAutocomplete]="auto">
|
||
|
|
</mat-form-field>
|
||
|
|
|
||
|
|
<mat-autocomplete #auto="matAutocomplete">
|
||
|
|
<mat-option>autocomplete_option</mat-option>
|
||
|
|
</mat-autocomplete>
|
||
|
|
|
||
|
|
<mat-form-field id="last-form-field" [floatLabel]="shouldLabelFloat()">
|
||
|
|
<mat-label>Label</mat-label>
|
||
|
|
<input matInput>
|
||
|
|
</mat-form-field>
|
||
|
|
|
||
|
|
<mat-form-field>
|
||
|
|
<mat-label>Date</mat-label>
|
||
|
|
<input matInput [matDatepicker]="datepicker">
|
||
|
|
<mat-datepicker #datepicker></mat-datepicker>
|
||
|
|
</mat-form-field>
|
||
|
|
|
||
|
|
<mat-form-field>
|
||
|
|
<mat-label>Date range</mat-label>
|
||
|
|
<mat-date-range-input [rangePicker]="rangePicker">
|
||
|
|
<input matStartDate placeholder="Start date"/>
|
||
|
|
<input matEndDate placeholder="End date"/>
|
||
|
|
</mat-date-range-input>
|
||
|
|
<mat-date-range-picker #rangePicker></mat-date-range-picker>
|
||
|
|
</mat-form-field>
|
||
|
|
`,
|
||
|
|
standalone: true,
|
||
|
|
imports: [
|
||
|
|
ReactiveFormsModule,
|
||
|
|
MatNativeDateModule,
|
||
|
|
MatAutocomplete,
|
||
|
|
MatAutocompleteTrigger,
|
||
|
|
MatDatepicker,
|
||
|
|
MatDatepickerInput,
|
||
|
|
MatDateRangePicker,
|
||
|
|
MatDateRangeInput,
|
||
|
|
MatEndDate,
|
||
|
|
MatError,
|
||
|
|
MatFormField,
|
||
|
|
MatHint,
|
||
|
|
MatInput,
|
||
|
|
MatLabel,
|
||
|
|
MatPrefix,
|
||
|
|
MatSelect,
|
||
|
|
MatStartDate,
|
||
|
|
MatSuffix,
|
||
|
|
MatOption,
|
||
|
|
],
|
||
|
|
})
|
||
|
|
class FormFieldHarnessTest {
|
||
|
|
requiredControl = new FormControl('Initial value', [Validators.required]);
|
||
|
|
shouldLabelFloat = signal<'always' | 'auto'>('auto');
|
||
|
|
hasLabel = false;
|
||
|
|
isDisabled = signal(false);
|
||
|
|
|
||
|
|
setupAsyncValidator() {
|
||
|
|
this.requiredControl.setValidators(() => null);
|
||
|
|
this.requiredControl.setAsyncValidators(() => new Promise(res => setTimeout(res, 10000)));
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
class CustomControlHarness extends ComponentHarness {
|
||
|
|
static hostSelector = '.custom-control';
|
||
|
|
}
|