181 lines
5.6 KiB
TypeScript
181 lines
5.6 KiB
TypeScript
|
|
import {OverlayModule} from '@angular/cdk/overlay';
|
||
|
|
import {dispatchFakeEvent} from '@angular/cdk/testing/private';
|
||
|
|
import {
|
||
|
|
Component,
|
||
|
|
NgZone,
|
||
|
|
OnDestroy,
|
||
|
|
Provider,
|
||
|
|
QueryList,
|
||
|
|
Type,
|
||
|
|
ViewChild,
|
||
|
|
ViewChildren,
|
||
|
|
provideZoneChangeDetection,
|
||
|
|
} from '@angular/core';
|
||
|
|
import {ComponentFixture, TestBed, waitForAsync} from '@angular/core/testing';
|
||
|
|
import {FormControl, FormsModule, ReactiveFormsModule} from '@angular/forms';
|
||
|
|
import {NoopAnimationsModule} from '@angular/platform-browser/animations';
|
||
|
|
import {Subscription} from 'rxjs';
|
||
|
|
import {MatOption} from '../core';
|
||
|
|
import {MatFormField, MatFormFieldModule} from '../form-field';
|
||
|
|
import {MatInputModule} from '../input';
|
||
|
|
import {MatAutocomplete} from './autocomplete';
|
||
|
|
import {MatAutocompleteTrigger} from './autocomplete-trigger';
|
||
|
|
import {MatAutocompleteModule} from './module';
|
||
|
|
|
||
|
|
describe('MatAutocomplete Zone.js integration', () => {
|
||
|
|
// Creates a test component fixture.
|
||
|
|
function createComponent<T>(component: Type<T>, providers: Provider[] = []) {
|
||
|
|
TestBed.configureTestingModule({
|
||
|
|
imports: [
|
||
|
|
MatAutocompleteModule,
|
||
|
|
MatFormFieldModule,
|
||
|
|
MatInputModule,
|
||
|
|
FormsModule,
|
||
|
|
ReactiveFormsModule,
|
||
|
|
NoopAnimationsModule,
|
||
|
|
OverlayModule,
|
||
|
|
],
|
||
|
|
providers: [provideZoneChangeDetection(), ...providers],
|
||
|
|
declarations: [component],
|
||
|
|
});
|
||
|
|
|
||
|
|
return TestBed.createComponent<T>(component);
|
||
|
|
}
|
||
|
|
|
||
|
|
describe('panel toggling', () => {
|
||
|
|
let fixture: ComponentFixture<SimpleAutocomplete>;
|
||
|
|
|
||
|
|
beforeEach(() => {
|
||
|
|
fixture = createComponent(SimpleAutocomplete);
|
||
|
|
fixture.detectChanges();
|
||
|
|
});
|
||
|
|
|
||
|
|
it('should show the panel when the first open is after the initial zone stabilization', waitForAsync(() => {
|
||
|
|
// Note that we're running outside the Angular zone, in order to be able
|
||
|
|
// to test properly without the subscription from `_subscribeToClosingActions`
|
||
|
|
// giving us a false positive.
|
||
|
|
fixture.ngZone!.runOutsideAngular(() => {
|
||
|
|
fixture.componentInstance.trigger.openPanel();
|
||
|
|
|
||
|
|
Promise.resolve().then(() => {
|
||
|
|
expect(fixture.componentInstance.panel.showPanel)
|
||
|
|
.withContext(`Expected panel to be visible.`)
|
||
|
|
.toBe(true);
|
||
|
|
});
|
||
|
|
});
|
||
|
|
}));
|
||
|
|
});
|
||
|
|
|
||
|
|
it('should emit from `autocomplete.closed` after click outside inside the NgZone', waitForAsync(async () => {
|
||
|
|
const inZoneSpy = jasmine.createSpy('in zone spy');
|
||
|
|
|
||
|
|
const fixture = createComponent(SimpleAutocomplete);
|
||
|
|
fixture.detectChanges();
|
||
|
|
|
||
|
|
fixture.componentInstance.trigger.openPanel();
|
||
|
|
fixture.detectChanges();
|
||
|
|
await new Promise(r => setTimeout(r));
|
||
|
|
|
||
|
|
const subscription = fixture.componentInstance.trigger.autocomplete.closed.subscribe(() =>
|
||
|
|
inZoneSpy(NgZone.isInAngularZone()),
|
||
|
|
);
|
||
|
|
await new Promise(r => setTimeout(r));
|
||
|
|
|
||
|
|
dispatchFakeEvent(document, 'click');
|
||
|
|
|
||
|
|
expect(inZoneSpy).toHaveBeenCalledWith(true);
|
||
|
|
|
||
|
|
subscription.unsubscribe();
|
||
|
|
}));
|
||
|
|
});
|
||
|
|
|
||
|
|
const SIMPLE_AUTOCOMPLETE_TEMPLATE = `
|
||
|
|
<mat-form-field [floatLabel]="floatLabel" [style.width.px]="width" [color]="theme">
|
||
|
|
@if (hasLabel) {
|
||
|
|
<mat-label>State</mat-label>
|
||
|
|
}
|
||
|
|
<input
|
||
|
|
matInput
|
||
|
|
placeholder="State"
|
||
|
|
[matAutocomplete]="auto"
|
||
|
|
[matAutocompletePosition]="position"
|
||
|
|
[matAutocompleteDisabled]="autocompleteDisabled"
|
||
|
|
[formControl]="stateCtrl">
|
||
|
|
</mat-form-field>
|
||
|
|
<mat-autocomplete
|
||
|
|
#auto="matAutocomplete"
|
||
|
|
[class]="panelClass"
|
||
|
|
[displayWith]="displayFn"
|
||
|
|
[disableRipple]="disableRipple"
|
||
|
|
[requireSelection]="requireSelection"
|
||
|
|
[aria-label]="ariaLabel"
|
||
|
|
[aria-labelledby]="ariaLabelledby"
|
||
|
|
(opened)="openedSpy()"
|
||
|
|
(closed)="closedSpy()">
|
||
|
|
@for (state of filteredStates; track state) {
|
||
|
|
<mat-option
|
||
|
|
[value]="state"
|
||
|
|
[style.height.px]="state.height"
|
||
|
|
[disabled]="state.disabled">
|
||
|
|
<span>{{ state.code }}: {{ state.name }}</span>
|
||
|
|
</mat-option>
|
||
|
|
}
|
||
|
|
</mat-autocomplete>
|
||
|
|
`;
|
||
|
|
|
||
|
|
@Component({template: SIMPLE_AUTOCOMPLETE_TEMPLATE, standalone: false})
|
||
|
|
class SimpleAutocomplete implements OnDestroy {
|
||
|
|
stateCtrl = new FormControl<{name: string; code: string} | string | null>(null);
|
||
|
|
filteredStates: any[];
|
||
|
|
valueSub: Subscription;
|
||
|
|
floatLabel = 'auto';
|
||
|
|
position = 'auto';
|
||
|
|
width: number;
|
||
|
|
disableRipple = false;
|
||
|
|
autocompleteDisabled = false;
|
||
|
|
hasLabel = true;
|
||
|
|
requireSelection = false;
|
||
|
|
ariaLabel: string;
|
||
|
|
ariaLabelledby: string;
|
||
|
|
panelClass = 'class-one class-two';
|
||
|
|
theme: string;
|
||
|
|
openedSpy = jasmine.createSpy('autocomplete opened spy');
|
||
|
|
closedSpy = jasmine.createSpy('autocomplete closed spy');
|
||
|
|
|
||
|
|
@ViewChild(MatAutocompleteTrigger, {static: true}) trigger: MatAutocompleteTrigger;
|
||
|
|
@ViewChild(MatAutocomplete) panel: MatAutocomplete;
|
||
|
|
@ViewChild(MatFormField) formField: MatFormField;
|
||
|
|
@ViewChildren(MatOption) options: QueryList<MatOption>;
|
||
|
|
|
||
|
|
states: {code: string; name: string; height?: number; disabled?: boolean}[] = [
|
||
|
|
{code: 'AL', name: 'Alabama'},
|
||
|
|
{code: 'CA', name: 'California'},
|
||
|
|
{code: 'FL', name: 'Florida'},
|
||
|
|
{code: 'KS', name: 'Kansas'},
|
||
|
|
{code: 'MA', name: 'Massachusetts'},
|
||
|
|
{code: 'NY', name: 'New York'},
|
||
|
|
{code: 'OR', name: 'Oregon'},
|
||
|
|
{code: 'PA', name: 'Pennsylvania'},
|
||
|
|
{code: 'TN', name: 'Tennessee'},
|
||
|
|
{code: 'VA', name: 'Virginia'},
|
||
|
|
{code: 'WY', name: 'Wyoming'},
|
||
|
|
];
|
||
|
|
|
||
|
|
constructor() {
|
||
|
|
this.filteredStates = this.states;
|
||
|
|
this.valueSub = this.stateCtrl.valueChanges.subscribe(val => {
|
||
|
|
this.filteredStates = val
|
||
|
|
? this.states.filter(s => s.name.match(new RegExp(val as string, 'gi')))
|
||
|
|
: this.states;
|
||
|
|
});
|
||
|
|
}
|
||
|
|
|
||
|
|
displayFn(value: any): string {
|
||
|
|
return value ? value.name : value;
|
||
|
|
}
|
||
|
|
|
||
|
|
ngOnDestroy() {
|
||
|
|
this.valueSub.unsubscribe();
|
||
|
|
}
|
||
|
|
}
|