diff --git a/src/cdk/a11y/focus-monitor/focus-monitor.spec.ts b/src/cdk/a11y/focus-monitor/focus-monitor.spec.ts index fad90f55753..cb1414ca9b2 100644 --- a/src/cdk/a11y/focus-monitor/focus-monitor.spec.ts +++ b/src/cdk/a11y/focus-monitor/focus-monitor.spec.ts @@ -6,10 +6,10 @@ import { patchElementFocus, } from '@angular/cdk/testing'; import {Component} from '@angular/core'; -import {ComponentFixture, fakeAsync, inject, TestBed, tick} from '@angular/core/testing'; +import {ComponentFixture, fakeAsync, flush, inject, TestBed, tick} from '@angular/core/testing'; import {By} from '@angular/platform-browser'; -import {FocusMonitor, FocusOrigin, TOUCH_BUFFER_MS} from './focus-monitor'; import {A11yModule} from '../index'; +import {FocusMonitor, FocusOrigin, TOUCH_BUFFER_MS} from './focus-monitor'; describe('FocusMonitor', () => { @@ -224,6 +224,7 @@ describe('cdkMonitorFocus', () => { ButtonWithFocusClasses, ComplexComponentWithMonitorElementFocus, ComplexComponentWithMonitorSubtreeFocus, + ComplexComponentWithMonitorSubtreeFocusAnfMonitorElementFocus, ], }).compileComponents(); }); @@ -391,6 +392,36 @@ describe('cdkMonitorFocus', () => { expect(parentElement.classList.length).toBe(2, 'button should have exactly 2 focus classes'); })); }); + + describe('complex component with cdkMonitorSubtreeFocus and cdkMonitorElementFocus', () => { + let fixture: ComponentFixture; + let parentElement: HTMLElement; + let childElement: HTMLElement; + let focusMonitor: FocusMonitor; + + beforeEach(inject([FocusMonitor], (fm: FocusMonitor) => { + focusMonitor = fm; + fixture = + TestBed.createComponent(ComplexComponentWithMonitorSubtreeFocusAnfMonitorElementFocus); + fixture.detectChanges(); + + parentElement = fixture.debugElement.query(By.css('div')).nativeElement; + childElement = fixture.debugElement.query(By.css('button')).nativeElement; + + patchElementFocus(parentElement); + patchElementFocus(childElement); + })); + + it('should add keyboard focus classes on both elements when child is focused via keyboard', + fakeAsync(() => { + focusMonitor.focusVia(childElement, 'keyboard'); + fixture.detectChanges(); + flush(); + + expect(parentElement.classList).toContain('cdk-keyboard-focused'); + expect(childElement.classList).toContain('cdk-keyboard-focused'); + })); + }); }); @@ -418,3 +449,8 @@ class ComplexComponentWithMonitorElementFocus {} template: `
` }) class ComplexComponentWithMonitorSubtreeFocus {} + +@Component({ + template: `
` +}) +class ComplexComponentWithMonitorSubtreeFocusAnfMonitorElementFocus {} diff --git a/src/cdk/a11y/focus-monitor/focus-monitor.ts b/src/cdk/a11y/focus-monitor/focus-monitor.ts index 78df74ebcaa..7eb48efa055 100644 --- a/src/cdk/a11y/focus-monitor/focus-monitor.ts +++ b/src/cdk/a11y/focus-monitor/focus-monitor.ts @@ -18,7 +18,7 @@ import { Output, SkipSelf, } from '@angular/core'; -import {of as observableOf, Observable, Subject, Subscription} from 'rxjs'; +import {Observable, of as observableOf, Subject, Subscription} from 'rxjs'; // This is the value used by AngularJS Material. Through trial and error (on iPhone 6S) they found @@ -187,7 +187,7 @@ export class FocusMonitor implements OnDestroy { // focused element. let windowFocusListener = () => { this._windowFocused = true; - this._windowFocusTimeoutId = setTimeout(() => this._windowFocused = false, 0); + this._windowFocusTimeoutId = setTimeout(() => this._windowFocused = false); }; // Note: we listen to events in the capture phase so we can detect them even if the user stops @@ -246,7 +246,7 @@ export class FocusMonitor implements OnDestroy { private _setOriginForCurrentEventQueue(origin: FocusOrigin): void { this._ngZone.runOutsideAngular(() => { this._origin = origin; - this._originTimeoutId = setTimeout(() => this._origin = null, 0); + this._originTimeoutId = setTimeout(() => this._origin = null); }); } @@ -302,20 +302,20 @@ export class FocusMonitor implements OnDestroy { // 2) It was caused by a touch event, in which case we mark the origin as 'touch'. // 3) The element was programmatically focused, in which case we should mark the origin as // 'program'. - if (!this._origin) { + let origin = this._origin; + if (!origin) { if (this._windowFocused && this._lastFocusOrigin) { - this._origin = this._lastFocusOrigin; + origin = this._lastFocusOrigin; } else if (this._wasCausedByTouch(event)) { - this._origin = 'touch'; + origin = 'touch'; } else { - this._origin = 'program'; + origin = 'program'; } } - this._setClasses(element, this._origin); - elementInfo.subject.next(this._origin); - this._lastFocusOrigin = this._origin; - this._origin = null; + this._setClasses(element, origin); + elementInfo.subject.next(origin); + this._lastFocusOrigin = origin; } /** @@ -351,7 +351,6 @@ export class FocusMonitor implements OnDestroy { this._unregisterGlobalListeners = () => {}; } } - } diff --git a/src/demo-app/focus-origin/focus-origin-demo.html b/src/demo-app/focus-origin/focus-origin-demo.html index 7aa9e9a017a..968e7bb4327 100644 --- a/src/demo-app/focus-origin/focus-origin-demo.html +++ b/src/demo-app/focus-origin/focus-origin-demo.html @@ -19,3 +19,8 @@

div with subtree focus monitored

+ +
+

Parent div should get same focus origin as button when button is focused:

+ +
diff --git a/src/lib/button-toggle/button-toggle.spec.ts b/src/lib/button-toggle/button-toggle.spec.ts index 4e45385f230..c1c00fe08a0 100644 --- a/src/lib/button-toggle/button-toggle.spec.ts +++ b/src/lib/button-toggle/button-toggle.spec.ts @@ -1,13 +1,13 @@ -import {fakeAsync, tick, ComponentFixture, TestBed} from '@angular/core/testing'; import {dispatchMouseEvent} from '@angular/cdk/testing'; -import {NgModel, FormsModule, ReactiveFormsModule, FormControl} from '@angular/forms'; -import {Component, DebugElement, ViewChild, ViewChildren, QueryList} from '@angular/core'; +import {Component, DebugElement, QueryList, ViewChild, ViewChildren} from '@angular/core'; +import {ComponentFixture, fakeAsync, flush, TestBed, tick} from '@angular/core/testing'; +import {FormControl, FormsModule, NgModel, ReactiveFormsModule} from '@angular/forms'; import {By} from '@angular/platform-browser'; import { - MatButtonToggleGroup, - MatButtonToggleGroupMultiple, MatButtonToggle, MatButtonToggleChange, + MatButtonToggleGroup, + MatButtonToggleGroupMultiple, MatButtonToggleModule, } from './index'; @@ -574,13 +574,14 @@ describe('MatButtonToggle without forms', () => { it('should toggle when clicked', fakeAsync(() => { buttonToggleLabelElement.click(); - fixture.detectChanges(); + flush(); expect(buttonToggleInstance.checked).toBe(true); buttonToggleLabelElement.click(); fixture.detectChanges(); + flush(); expect(buttonToggleInstance.checked).toBe(false); })); diff --git a/src/lib/datepicker/datepicker.spec.ts b/src/lib/datepicker/datepicker.spec.ts index 1a846787c7b..eb984e927a3 100644 --- a/src/lib/datepicker/datepicker.spec.ts +++ b/src/lib/datepicker/datepicker.spec.ts @@ -1,11 +1,11 @@ import {ENTER, ESCAPE, RIGHT_ARROW, UP_ARROW} from '@angular/cdk/keycodes'; import {Overlay, OverlayContainer, ScrollDispatcher} from '@angular/cdk/overlay'; import { + createKeyboardEvent, + dispatchEvent, dispatchFakeEvent, dispatchKeyboardEvent, dispatchMouseEvent, - createKeyboardEvent, - dispatchEvent, } from '@angular/cdk/testing'; import {Component, FactoryProvider, Type, ValueProvider, ViewChild} from '@angular/core'; import {ComponentFixture, fakeAsync, flush, inject, TestBed} from '@angular/core/testing'; @@ -23,12 +23,12 @@ import { import {MatFormField, MatFormFieldModule} from '@angular/material/form-field'; import {By} from '@angular/platform-browser'; import {NoopAnimationsModule} from '@angular/platform-browser/animations'; +import {Subject} from 'rxjs'; import {MatInputModule} from '../input/index'; import {MatDatepicker} from './datepicker'; import {MatDatepickerInput} from './datepicker-input'; import {MatDatepickerToggle} from './datepicker-toggle'; import {MAT_DATEPICKER_SCROLL_STRATEGY, MatDatepickerIntl, MatDatepickerModule} from './index'; -import {Subject} from 'rxjs'; import {Directionality} from '@angular/cdk/bidi'; import {BrowserDynamicTestingModule} from '@angular/platform-browser-dynamic/testing'; @@ -308,6 +308,7 @@ describe('MatDatepicker', () => { testComponent.datepicker.open(); fixture.detectChanges(); + flush(); let ownedElementId = inputEl.getAttribute('aria-owns'); expect(ownedElementId).not.toBeNull(); @@ -945,6 +946,7 @@ describe('MatDatepicker', () => { testComponent.formField.color = 'primary'; testComponent.datepicker.open(); fixture.detectChanges(); + flush(); let contentEl = document.querySelector('.mat-datepicker-content')!; @@ -959,6 +961,7 @@ describe('MatDatepicker', () => { contentEl = document.querySelector('.mat-datepicker-content')!; fixture.detectChanges(); + flush(); expect(contentEl.classList).toContain('mat-warn'); expect(contentEl.classList).not.toContain('mat-primary'); @@ -969,6 +972,7 @@ describe('MatDatepicker', () => { testComponent.formField.color = 'warn'; testComponent.datepicker.open(); fixture.detectChanges(); + flush(); const contentEl = document.querySelector('.mat-datepicker-content')!; diff --git a/src/lib/slide-toggle/slide-toggle.spec.ts b/src/lib/slide-toggle/slide-toggle.spec.ts index 63a667fb89c..08a97ade0a7 100644 --- a/src/lib/slide-toggle/slide-toggle.spec.ts +++ b/src/lib/slide-toggle/slide-toggle.spec.ts @@ -1,18 +1,12 @@ -import {Component} from '@angular/core'; -import {By, HAMMER_GESTURE_CONFIG} from '@angular/platform-browser'; -import { - ComponentFixture, - TestBed, - fakeAsync, - tick, - flushMicrotasks, -} from '@angular/core/testing'; -import {NgModel, FormsModule, ReactiveFormsModule, FormControl} from '@angular/forms'; -import {MatSlideToggle, MatSlideToggleChange, MatSlideToggleModule} from './index'; -import {TestGestureConfig} from '../slider/test-gesture-config'; -import {dispatchFakeEvent} from '@angular/cdk/testing'; -import {defaultRippleAnimationConfig} from '@angular/material/core'; import {MutationObserverFactory} from '@angular/cdk/observers'; +import {dispatchFakeEvent} from '@angular/cdk/testing'; +import {Component} from '@angular/core'; +import {ComponentFixture, fakeAsync, flushMicrotasks, TestBed, tick} from '@angular/core/testing'; +import {FormControl, FormsModule, NgModel, ReactiveFormsModule} from '@angular/forms'; +import {defaultRippleAnimationConfig} from '@angular/material/core'; +import {By, HAMMER_GESTURE_CONFIG} from '@angular/platform-browser'; +import {TestGestureConfig} from '../slider/test-gesture-config'; +import {MatSlideToggle, MatSlideToggleChange, MatSlideToggleModule} from './index'; describe('MatSlideToggle without forms', () => { let gestureConfig: TestGestureConfig;