UNPKG

ipsos-components

Version:

Material Design components for Angular

413 lines (336 loc) 15.5 kB
import {TAB} from '@angular/cdk/keycodes'; import {dispatchFakeEvent, dispatchKeyboardEvent, dispatchMouseEvent} from '@angular/cdk/testing'; import {Component} from '@angular/core'; import {ComponentFixture, fakeAsync, 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'; describe('FocusMonitor', () => { let fixture: ComponentFixture<PlainButton>; let buttonElement: HTMLElement; let focusMonitor: FocusMonitor; let changeHandler: (origin: FocusOrigin) => void; beforeEach(() => { TestBed.configureTestingModule({ imports: [A11yModule], declarations: [ PlainButton, ], }).compileComponents(); }); beforeEach(inject([FocusMonitor], (fm: FocusMonitor) => { fixture = TestBed.createComponent(PlainButton); fixture.detectChanges(); buttonElement = fixture.debugElement.query(By.css('button')).nativeElement; focusMonitor = fm; changeHandler = jasmine.createSpy('focus origin change handler'); focusMonitor.monitor(buttonElement, false).subscribe(changeHandler); patchElementFocus(buttonElement); })); it('manually registered element should receive focus classes', fakeAsync(() => { buttonElement.focus(); fixture.detectChanges(); tick(); expect(buttonElement.classList.contains('cdk-focused')) .toBe(true, 'button should have cdk-focused class'); expect(changeHandler).toHaveBeenCalledTimes(1); })); it('should detect focus via keyboard', fakeAsync(() => { // Simulate focus via keyboard. dispatchKeyboardEvent(document, 'keydown', TAB); buttonElement.focus(); fixture.detectChanges(); tick(); expect(buttonElement.classList.length) .toBe(2, 'button should have exactly 2 focus classes'); expect(buttonElement.classList.contains('cdk-focused')) .toBe(true, 'button should have cdk-focused class'); expect(buttonElement.classList.contains('cdk-keyboard-focused')) .toBe(true, 'button should have cdk-keyboard-focused class'); expect(changeHandler).toHaveBeenCalledWith('keyboard'); })); it('should detect focus via mouse', fakeAsync(() => { // Simulate focus via mouse. dispatchMouseEvent(buttonElement, 'mousedown'); buttonElement.focus(); fixture.detectChanges(); tick(); expect(buttonElement.classList.length) .toBe(2, 'button should have exactly 2 focus classes'); expect(buttonElement.classList.contains('cdk-focused')) .toBe(true, 'button should have cdk-focused class'); expect(buttonElement.classList.contains('cdk-mouse-focused')) .toBe(true, 'button should have cdk-mouse-focused class'); expect(changeHandler).toHaveBeenCalledWith('mouse'); })); it('should detect focus via touch', fakeAsync(() => { // Simulate focus via touch. dispatchFakeEvent(buttonElement, 'touchstart'); buttonElement.focus(); fixture.detectChanges(); tick(TOUCH_BUFFER_MS); expect(buttonElement.classList.length) .toBe(2, 'button should have exactly 2 focus classes'); expect(buttonElement.classList.contains('cdk-focused')) .toBe(true, 'button should have cdk-focused class'); expect(buttonElement.classList.contains('cdk-touch-focused')) .toBe(true, 'button should have cdk-touch-focused class'); expect(changeHandler).toHaveBeenCalledWith('touch'); })); it('should detect programmatic focus', fakeAsync(() => { // Programmatically focus. buttonElement.focus(); fixture.detectChanges(); tick(); expect(buttonElement.classList.length) .toBe(2, 'button should have exactly 2 focus classes'); expect(buttonElement.classList.contains('cdk-focused')) .toBe(true, 'button should have cdk-focused class'); expect(buttonElement.classList.contains('cdk-program-focused')) .toBe(true, 'button should have cdk-program-focused class'); expect(changeHandler).toHaveBeenCalledWith('program'); })); it('focusVia keyboard should simulate keyboard focus', fakeAsync(() => { focusMonitor.focusVia(buttonElement, 'keyboard'); tick(); expect(buttonElement.classList.length) .toBe(2, 'button should have exactly 2 focus classes'); expect(buttonElement.classList.contains('cdk-focused')) .toBe(true, 'button should have cdk-focused class'); expect(buttonElement.classList.contains('cdk-keyboard-focused')) .toBe(true, 'button should have cdk-keyboard-focused class'); expect(changeHandler).toHaveBeenCalledWith('keyboard'); })); it('focusVia mouse should simulate mouse focus', fakeAsync(() => { focusMonitor.focusVia(buttonElement, 'mouse'); fixture.detectChanges(); tick(); expect(buttonElement.classList.length) .toBe(2, 'button should have exactly 2 focus classes'); expect(buttonElement.classList.contains('cdk-focused')) .toBe(true, 'button should have cdk-focused class'); expect(buttonElement.classList.contains('cdk-mouse-focused')) .toBe(true, 'button should have cdk-mouse-focused class'); expect(changeHandler).toHaveBeenCalledWith('mouse'); })); it('focusVia mouse should simulate mouse focus', fakeAsync(() => { focusMonitor.focusVia(buttonElement, 'touch'); fixture.detectChanges(); tick(); expect(buttonElement.classList.length) .toBe(2, 'button should have exactly 2 focus classes'); expect(buttonElement.classList.contains('cdk-focused')) .toBe(true, 'button should have cdk-focused class'); expect(buttonElement.classList.contains('cdk-touch-focused')) .toBe(true, 'button should have cdk-touch-focused class'); expect(changeHandler).toHaveBeenCalledWith('touch'); })); it('focusVia program should simulate programmatic focus', fakeAsync(() => { focusMonitor.focusVia(buttonElement, 'program'); fixture.detectChanges(); tick(); expect(buttonElement.classList.length) .toBe(2, 'button should have exactly 2 focus classes'); expect(buttonElement.classList.contains('cdk-focused')) .toBe(true, 'button should have cdk-focused class'); expect(buttonElement.classList.contains('cdk-program-focused')) .toBe(true, 'button should have cdk-program-focused class'); expect(changeHandler).toHaveBeenCalledWith('program'); })); it('should remove focus classes on blur', fakeAsync(() => { buttonElement.focus(); fixture.detectChanges(); tick(); expect(buttonElement.classList.length) .toBe(2, 'button should have exactly 2 focus classes'); expect(changeHandler).toHaveBeenCalledWith('program'); // Call `blur` directly because invoking `buttonElement.blur()` does not always trigger the // handler on IE11 on SauceLabs. focusMonitor._onBlur({} as any, buttonElement); fixture.detectChanges(); expect(buttonElement.classList.length) .toBe(0, 'button should not have any focus classes'); expect(changeHandler).toHaveBeenCalledWith(null); })); it('should remove classes on stopMonitoring', fakeAsync(() => { buttonElement.focus(); fixture.detectChanges(); tick(); expect(buttonElement.classList.length) .toBe(2, 'button should have exactly 2 focus classes'); focusMonitor.stopMonitoring(buttonElement); fixture.detectChanges(); expect(buttonElement.classList.length).toBe(0, 'button should not have any focus classes'); })); }); describe('cdkMonitorFocus', () => { beforeEach(() => { TestBed.configureTestingModule({ imports: [A11yModule], declarations: [ ButtonWithFocusClasses, ComplexComponentWithMonitorElementFocus, ComplexComponentWithMonitorSubtreeFocus, ], }).compileComponents(); }); describe('button with cdkMonitorElementFocus', () => { let fixture: ComponentFixture<ButtonWithFocusClasses>; let buttonElement: HTMLElement; beforeEach(() => { fixture = TestBed.createComponent(ButtonWithFocusClasses); fixture.detectChanges(); spyOn(fixture.componentInstance, 'focusChanged'); buttonElement = fixture.debugElement.query(By.css('button')).nativeElement; patchElementFocus(buttonElement); }); it('should initially not be focused', () => { expect(buttonElement.classList.length).toBe(0, 'button should not have focus classes'); }); it('should detect focus via keyboard', fakeAsync(() => { // Simulate focus via keyboard. dispatchKeyboardEvent(document, 'keydown', TAB); buttonElement.focus(); fixture.detectChanges(); tick(); expect(buttonElement.classList.length) .toBe(2, 'button should have exactly 2 focus classes'); expect(buttonElement.classList.contains('cdk-focused')) .toBe(true, 'button should have cdk-focused class'); expect(buttonElement.classList.contains('cdk-keyboard-focused')) .toBe(true, 'button should have cdk-keyboard-focused class'); expect(fixture.componentInstance.focusChanged).toHaveBeenCalledWith('keyboard'); })); it('should detect focus via mouse', fakeAsync(() => { // Simulate focus via mouse. dispatchMouseEvent(buttonElement, 'mousedown'); buttonElement.focus(); fixture.detectChanges(); tick(); expect(buttonElement.classList.length) .toBe(2, 'button should have exactly 2 focus classes'); expect(buttonElement.classList.contains('cdk-focused')) .toBe(true, 'button should have cdk-focused class'); expect(buttonElement.classList.contains('cdk-mouse-focused')) .toBe(true, 'button should have cdk-mouse-focused class'); expect(fixture.componentInstance.focusChanged).toHaveBeenCalledWith('mouse'); })); it('should detect focus via touch', fakeAsync(() => { // Simulate focus via touch. dispatchFakeEvent(buttonElement, 'touchstart'); buttonElement.focus(); fixture.detectChanges(); tick(TOUCH_BUFFER_MS); expect(buttonElement.classList.length) .toBe(2, 'button should have exactly 2 focus classes'); expect(buttonElement.classList.contains('cdk-focused')) .toBe(true, 'button should have cdk-focused class'); expect(buttonElement.classList.contains('cdk-touch-focused')) .toBe(true, 'button should have cdk-touch-focused class'); expect(fixture.componentInstance.focusChanged).toHaveBeenCalledWith('touch'); })); it('should detect programmatic focus', fakeAsync(() => { // Programmatically focus. buttonElement.focus(); fixture.detectChanges(); tick(); expect(buttonElement.classList.length) .toBe(2, 'button should have exactly 2 focus classes'); expect(buttonElement.classList.contains('cdk-focused')) .toBe(true, 'button should have cdk-focused class'); expect(buttonElement.classList.contains('cdk-program-focused')) .toBe(true, 'button should have cdk-program-focused class'); expect(fixture.componentInstance.focusChanged).toHaveBeenCalledWith('program'); })); it('should remove focus classes on blur', fakeAsync(() => { buttonElement.focus(); fixture.detectChanges(); tick(); expect(buttonElement.classList.length) .toBe(2, 'button should have exactly 2 focus classes'); expect(fixture.componentInstance.focusChanged).toHaveBeenCalledWith('program'); buttonElement.blur(); fixture.detectChanges(); expect(buttonElement.classList.length) .toBe(0, 'button should not have any focus classes'); expect(fixture.componentInstance.focusChanged).toHaveBeenCalledWith(null); })); }); describe('complex component with cdkMonitorElementFocus', () => { let fixture: ComponentFixture<ComplexComponentWithMonitorElementFocus>; let parentElement: HTMLElement; let childElement: HTMLElement; beforeEach(() => { fixture = TestBed.createComponent(ComplexComponentWithMonitorElementFocus); 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 focus classes on parent focus', fakeAsync(() => { parentElement.focus(); fixture.detectChanges(); tick(); expect(parentElement.classList.length).toBe(2, 'button should have exactly 2 focus classes'); })); it('should not add focus classes on child focus', fakeAsync(() => { childElement.focus(); fixture.detectChanges(); tick(); expect(parentElement.classList.length).toBe(0, 'button should not have any focus classes'); })); }); describe('complex component with cdkMonitorSubtreeFocus', () => { let fixture: ComponentFixture<ComplexComponentWithMonitorSubtreeFocus>; let parentElement: HTMLElement; let childElement: HTMLElement; beforeEach(() => { fixture = TestBed.createComponent(ComplexComponentWithMonitorSubtreeFocus); 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 focus classes on parent focus', fakeAsync(() => { parentElement.focus(); fixture.detectChanges(); tick(); expect(parentElement.classList.length).toBe(2, 'button should have exactly 2 focus classes'); })); it('should add focus classes on child focus', fakeAsync(() => { childElement.focus(); fixture.detectChanges(); tick(); expect(parentElement.classList.length).toBe(2, 'button should have exactly 2 focus classes'); })); }); }); @Component({ template: `<button>focus me!</button>` }) class PlainButton {} @Component({ template: `<button cdkMonitorElementFocus (cdkFocusChange)="focusChanged($event)"></button>` }) class ButtonWithFocusClasses { focusChanged(_origin: FocusOrigin) {} } @Component({ template: `<div tabindex="0" cdkMonitorElementFocus><button></button></div>` }) class ComplexComponentWithMonitorElementFocus {} @Component({ template: `<div tabindex="0" cdkMonitorSubtreeFocus><button></button></div>` }) class ComplexComponentWithMonitorSubtreeFocus {} /** * Patches an elements focus and blur methods to emit events consistently and predictably. * This is necessary, because some browsers, like IE11, will call the focus handlers asynchronously, * while others won't fire them at all if the browser window is not focused. */ function patchElementFocus(element: HTMLElement) { element.focus = () => dispatchFakeEvent(element, 'focus'); element.blur = () => dispatchFakeEvent(element, 'blur'); }