-
Notifications
You must be signed in to change notification settings - Fork 125
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
fix: programmatically click product menu item on space/enter and add …
…keyboard arrow navigation (#3187) * programmatically click product menu item on space/enter * arrow key support for product switch * start tests * tests * address pr comments
- Loading branch information
1 parent
63296c4
commit 6d401b9
Showing
4 changed files
with
283 additions
and
10 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
126 changes: 119 additions & 7 deletions
126
libs/core/src/lib/product-switch/product-switch-body/product-switch-body.component.spec.ts
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,28 +1,140 @@ | ||
import { async, ComponentFixture, TestBed } from '@angular/core/testing'; | ||
|
||
import { ProductSwitchBodyComponent } from './product-switch-body.component'; | ||
import { ButtonModule, PopoverModule } from '@fundamental-ngx/core'; | ||
import { ButtonModule, PopoverModule, ProductSwitchItem } from '@fundamental-ngx/core'; | ||
import { DragAndDropModule } from '../../utils/drag-and-drop/drag-and-drop.module'; | ||
import { DragDropModule } from '@angular/cdk/drag-drop'; | ||
import { Component, DebugElement } from '@angular/core'; | ||
import { By } from '@angular/platform-browser'; | ||
import { createKeyboardEvent } from '../../utils/tests/event-objects'; | ||
import { DOWN_ARROW, ENTER, LEFT_ARROW, RIGHT_ARROW, UP_ARROW } from '@angular/cdk/keycodes'; | ||
|
||
@Component({ | ||
selector: 'fd-test-component', | ||
template: '<fd-product-switch-body [products]="list"> </fd-product-switch-body>' | ||
}) | ||
export class TestComponent { | ||
list: ProductSwitchItem[] = [ | ||
{ | ||
title: 'Home', | ||
subtitle: 'Central Home', | ||
icon: 'home', | ||
stickToPosition: true, | ||
disabledDragAndDrop: true | ||
}, | ||
{ | ||
title: 'Analytics Cloud', | ||
subtitle: 'Analytics Cloud', | ||
icon: 'business-objects-experience', | ||
selected: true | ||
}, | ||
{ | ||
title: 'Catalog', | ||
subtitle: 'Ariba', | ||
icon: 'contacts' | ||
}, | ||
{ | ||
title: 'Guided Buying', | ||
icon: 'credit-card' | ||
}, | ||
{ | ||
title: 'Strategic Procurement', | ||
icon: 'cart-3' | ||
} | ||
]; | ||
} | ||
|
||
describe('ProductSwitchBodyComponent', () => { | ||
let component: ProductSwitchBodyComponent; | ||
let fixture: ComponentFixture<ProductSwitchBodyComponent>; | ||
let fixture: ComponentFixture<TestComponent>, debugElement: DebugElement, element: HTMLElement; | ||
|
||
let component, componentInstance: ProductSwitchBodyComponent; | ||
|
||
beforeEach(async(() => { | ||
TestBed.configureTestingModule({ | ||
imports: [PopoverModule, ButtonModule, DragAndDropModule, DragDropModule], | ||
declarations: [ProductSwitchBodyComponent] | ||
}).compileComponents(); | ||
declarations: [ProductSwitchBodyComponent, TestComponent] | ||
}); | ||
})); | ||
|
||
beforeEach(() => { | ||
fixture = TestBed.createComponent(ProductSwitchBodyComponent); | ||
component = fixture.componentInstance; | ||
fixture = TestBed.createComponent(TestComponent); | ||
debugElement = fixture.debugElement; | ||
element = debugElement.nativeElement; | ||
fixture.detectChanges(); | ||
component = debugElement.query(By.directive(ProductSwitchBodyComponent)); | ||
componentInstance = component.injector.get(ProductSwitchBodyComponent); | ||
}); | ||
|
||
it('should create', () => { | ||
expect(component).toBeTruthy(); | ||
}); | ||
|
||
it('should handle keydown enter', () => { | ||
const el = fixture.debugElement.query(By.css('li')); | ||
spyOn(el.nativeElement, 'click'); | ||
el.nativeElement.focus(); | ||
const keyboardEvent = createKeyboardEvent('keydown', ENTER, 'Enter', el.nativeElement); | ||
spyOn(keyboardEvent, 'preventDefault'); | ||
el.nativeElement.dispatchEvent(keyboardEvent); | ||
|
||
expect(keyboardEvent.preventDefault).toHaveBeenCalled(); | ||
expect(el.nativeElement.click).toHaveBeenCalled(); | ||
}); | ||
|
||
it('should handle no list keydown arrow right', () => { | ||
spyOn(componentInstance, 'isListMode').and.returnValue(false); | ||
const el = fixture.debugElement.query(By.css('li')); | ||
const nextEl = el.nativeElement.nextElementSibling; | ||
el.nativeElement.focus(); | ||
const keyboardEvent = createKeyboardEvent('keydown', RIGHT_ARROW, 'ArrowRight', el.nativeElement); | ||
el.nativeElement.dispatchEvent(keyboardEvent); | ||
|
||
expect(document.activeElement).toBe(nextEl); | ||
}); | ||
|
||
it('should handle no list keydown arrow left', () => { | ||
spyOn(componentInstance, 'isListMode').and.returnValue(false); | ||
const el = fixture.debugElement.query(By.css('li')); | ||
const nextEl = el.nativeElement.nextElementSibling; | ||
nextEl.focus(); | ||
const keyboardEvent = createKeyboardEvent('keydown', LEFT_ARROW, 'ArrowLeft', nextEl); | ||
nextEl.dispatchEvent(keyboardEvent); | ||
|
||
expect(document.activeElement).toBe(el.nativeElement); | ||
}); | ||
|
||
it('should handle no list keydown arrow down', () => { | ||
spyOn(componentInstance, 'isListMode').and.returnValue(false); | ||
const el = fixture.debugElement.query(By.css('li')); | ||
const nextElDown = el.nativeElement.nextElementSibling.nextElementSibling.nextElementSibling; | ||
el.nativeElement.focus(); | ||
const keyboardEvent = createKeyboardEvent('keydown', DOWN_ARROW, 'ArrowDown', el.nativeElement); | ||
el.nativeElement.dispatchEvent(keyboardEvent); | ||
|
||
expect(document.activeElement).toBe(nextElDown); | ||
}); | ||
|
||
it('should handle no list keydown arrow up', () => { | ||
spyOn(componentInstance, 'isListMode').and.returnValue(false); | ||
const el = fixture.debugElement.query(By.css('li')); | ||
const nextElDown = el.nativeElement.nextElementSibling.nextElementSibling.nextElementSibling; | ||
nextElDown.focus(); | ||
const keyboardEvent = createKeyboardEvent('keydown', UP_ARROW, 'ArrowUp', nextElDown); | ||
nextElDown.dispatchEvent(keyboardEvent); | ||
|
||
expect(document.activeElement).toBe(el.nativeElement); | ||
}); | ||
|
||
it('should handle list arrow up/down', () => { | ||
spyOn(componentInstance, 'isListMode').and.returnValue(true); | ||
const el = fixture.debugElement.query(By.css('li')); | ||
const nextElDown = el.nativeElement.nextElementSibling; | ||
el.nativeElement.focus(); | ||
let keyboardEvent = createKeyboardEvent('keydown', DOWN_ARROW, 'ArrowDown', el.nativeElement); | ||
el.nativeElement.dispatchEvent(keyboardEvent); | ||
expect(document.activeElement).toBe(nextElDown); | ||
keyboardEvent = createKeyboardEvent('keydown', UP_ARROW, 'ArrowUp', nextElDown); | ||
nextElDown.dispatchEvent(keyboardEvent); | ||
expect(document.activeElement).toBe(el.nativeElement); | ||
}); | ||
}); |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,91 @@ | ||
/** | ||
* @license | ||
* Copyright Google LLC All Rights Reserved. | ||
* | ||
* Use of this source code is governed by an MIT-style license that can be | ||
* found in the LICENSE file at https://angular.io/license | ||
*/ | ||
|
||
import { ModifierKeys } from '@angular/cdk/testing'; | ||
|
||
/** | ||
* Dispatches a keydown event from an element. | ||
* @docs-private | ||
*/ | ||
export function createKeyboardEvent( | ||
type: string, | ||
keyCode: number = 0, | ||
key: string = '', | ||
target?: Element, | ||
modifiers: ModifierKeys = {} | ||
): KeyboardEvent { | ||
const event = document.createEvent('KeyboardEvent') as any; | ||
const originalPreventDefault = event.preventDefault; | ||
|
||
// Firefox does not support `initKeyboardEvent`, but supports `initKeyEvent`. | ||
if (event.initKeyEvent) { | ||
event.initKeyEvent( | ||
type, | ||
true, | ||
true, | ||
window, | ||
modifiers.control, | ||
modifiers.alt, | ||
modifiers.shift, | ||
modifiers.meta, | ||
keyCode | ||
); | ||
} else { | ||
// `initKeyboardEvent` expects to receive modifiers as a whitespace-delimited string | ||
// See https://developer.mozilla.org/en-US/docs/Web/API/KeyboardEvent/initKeyboardEvent | ||
let modifiersList = ''; | ||
|
||
if (modifiers.control) { | ||
modifiersList += 'Control '; | ||
} | ||
|
||
if (modifiers.alt) { | ||
modifiersList += 'Alt '; | ||
} | ||
|
||
if (modifiers.shift) { | ||
modifiersList += 'Shift '; | ||
} | ||
|
||
if (modifiers.meta) { | ||
modifiersList += 'Meta '; | ||
} | ||
|
||
event.initKeyboardEvent( | ||
type, | ||
true /* canBubble */, | ||
true /* cancelable */, | ||
window /* view */, | ||
0 /* char */, | ||
key /* key */, | ||
0 /* location */, | ||
modifiersList.trim() /* modifiersList */, | ||
false /* repeat */ | ||
); | ||
} | ||
|
||
// Webkit Browsers don't set the keyCode when calling the init function. | ||
// See related bug https://bugs.webkit.org/show_bug.cgi?id=16735 | ||
Object.defineProperties(event, { | ||
keyCode: { get: () => keyCode }, | ||
key: { get: () => key }, | ||
target: { get: () => target }, | ||
ctrlKey: { get: () => !!modifiers.control }, | ||
altKey: { get: () => !!modifiers.alt }, | ||
shiftKey: { get: () => !!modifiers.shift }, | ||
metaKey: { get: () => !!modifiers.meta } | ||
}); | ||
|
||
// IE won't set `defaultPrevented` on synthetic events so we need to do it manually. | ||
event.preventDefault = function (): Event { | ||
Object.defineProperty(event, 'defaultPrevented', { get: () => true }); | ||
return originalPreventDefault.apply(this, arguments); | ||
}; | ||
|
||
return event; | ||
} |