| /* |
| * Copyright 2018-present Open Networking Foundation |
| * |
| * Licensed under the Apache License, Version 2.0 (the "License"); |
| * you may not use this file except in compliance with the License. |
| * You may obtain a copy of the License at |
| * |
| * http://www.apache.org/licenses/LICENSE-2.0 |
| * |
| * Unless required by applicable law or agreed to in writing, software |
| * distributed under the License is distributed on an "AS IS" BASIS, |
| * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
| * See the License for the specific language governing permissions and |
| * limitations under the License. |
| */ |
| import { TestBed, inject } from '@angular/core/testing'; |
| import {ActivatedRoute, Params} from '@angular/router'; |
| |
| import { KeysService, KeysToken } from './keys.service'; |
| import { FnService } from './fn.service'; |
| import { LogService } from '../log.service'; |
| import { NavService } from '../nav/nav.service'; |
| |
| import {of} from 'rxjs'; |
| import * as d3 from 'd3'; |
| |
| class MockActivatedRoute extends ActivatedRoute { |
| constructor(params: Params) { |
| super(); |
| this.queryParams = of(params); |
| } |
| } |
| |
| class MockNavService {} |
| |
| /* |
| ONOS GUI -- Key Handler Service - Unit Tests |
| */ |
| describe('KeysService', () => { |
| let ar: ActivatedRoute; |
| let fs: FnService; |
| let ks: KeysService; |
| let mockWindow: Window; |
| let logServiceSpy: jasmine.SpyObj<LogService>; |
| |
| const qhs: any = {}; |
| let d3Elem: any; |
| let elem: any; |
| let last: any; |
| |
| beforeEach(() => { |
| const logSpy = jasmine.createSpyObj('LogService', ['debug', 'warn', 'info']); |
| ar = new MockActivatedRoute({'debug': 'TestService'}); |
| mockWindow = <any>{ |
| innerWidth: 400, |
| innerHeight: 200, |
| navigator: { |
| userAgent: 'defaultUA' |
| }, |
| location: <any>{ |
| hostname: 'foo', |
| host: 'foo', |
| port: '80', |
| protocol: 'http', |
| search: { debug: 'true' }, |
| href: 'ws://foo:123/onos/ui/websock/path', |
| absUrl: 'ws://foo:123/onos/ui/websock/path' |
| } |
| }; |
| fs = new FnService(ar, logSpy, mockWindow); |
| |
| d3Elem = d3.select('body').append('p').attr('id', 'ptest'); |
| elem = d3Elem.node(); |
| last = { |
| view: null, |
| key: null, |
| code: null, |
| ev: null |
| }; |
| |
| TestBed.configureTestingModule({ |
| providers: [KeysService, |
| { provide: FnService, useValue: fs}, |
| { provide: LogService, useValue: logSpy }, |
| { provide: ActivatedRoute, useValue: ar }, |
| { provide: NavService, useClass: MockNavService}, |
| { provide: 'Window', useFactory: (() => mockWindow ) } |
| ] |
| }); |
| ks = TestBed.get(KeysService); |
| ks.installOn(d3Elem); |
| logServiceSpy = TestBed.get(LogService); |
| }); |
| |
| afterEach(() => { |
| d3.select('#ptest').remove(); |
| }); |
| |
| it('should be created', () => { |
| expect(ks).toBeTruthy(); |
| }); |
| |
| it('should define api functions', () => { |
| expect(fs.areFunctions(ks, [ |
| 'installOn', 'keyBindings', 'unbindKeys', 'dialogKeys', |
| 'addSeq', 'remSeq', 'gestureNotes', 'enableKeys', 'enableGlobalKeys', |
| 'checkNotGlobal', 'getKeyBindings', |
| 'matchSeq', 'whatKey', 'textFieldInput', 'keyIn', 'qhlion', 'qhlionShowHide', |
| 'qhlionHintEsc', 'qhlionHintT', 'setupGlobalKeys', 'quickHelp', |
| 'escapeKey', 'toggleTheme', 'filterMaskedKeys', 'unexParam', |
| 'setKeyBindings', 'bindDialogKeys', 'unbindDialogKeys' |
| ])).toBeTruthy(); |
| }); |
| |
| function jsKeyDown(element, code: string, keyName: string) { |
| const ev = new KeyboardEvent('keydown', |
| { code: code, key: keyName }); |
| |
| // Chromium Hack |
| // if (navigator.userAgent.toLowerCase().indexOf('chrome') > -1) { |
| // Object.defineProperty(ev, 'keyCode', { |
| // get: () => { return this.keyCodeVal; } |
| // }); |
| // Object.defineProperty(ev, 'which', { |
| // get: () => { return this.keyCodeVal; } |
| // }); |
| // } |
| |
| if (ev.code !== code.toString()) { |
| console.warn('keyCode mismatch ' + ev.code + |
| '(' + ev.toString() + ') -> ' + code); |
| } |
| element.dispatchEvent(ev); |
| } |
| |
| // === Key binding related tests |
| it('should start with default key bindings', () => { |
| const state = ks.getKeyBindings(); |
| const gk = state.globalKeys; |
| const mk = state.maskedKeys; |
| const vk = state.viewKeys; |
| const vf = state.viewFunction; |
| |
| expect(gk.length).toEqual(4); |
| ['backSlash', 'slash', 'esc', 'T'].forEach((k) => { |
| expect(fs.contains(gk, k)).toBeTruthy(); |
| }); |
| |
| expect(mk.length).toEqual(3); |
| ['backSlash', 'slash', 'T'].forEach((k) => { |
| expect(fs.contains(mk, k)).toBeTruthy(); |
| }); |
| |
| expect(vk.length).toEqual(0); |
| expect(vf).toBeFalsy(); |
| }); |
| |
| function bindTestKeys(withDescs?) { |
| const keys = ['A', '1', 'F5', 'equals']; |
| const kb = {}; |
| |
| function cb(view, key, code, ev) { |
| last.view = view; |
| last.key = key; |
| last.code = code; |
| last.ev = ev; |
| } |
| |
| function bind(k) { |
| return withDescs ? |
| [(view, key, code, ev) => {cb(view, key, code, ev); }, 'desc for key ' + k] : |
| (view, key, code, ev) => {cb(view, key, code, ev); }; |
| } |
| |
| keys.forEach((k) => { |
| kb[k] = bind(k); |
| }); |
| |
| ks.keyBindings(kb); |
| } |
| |
| function verifyCall(key, code) { |
| // TODO: update expectation, when view tokens are implemented |
| expect(last.view).toEqual(KeysToken.KEYEV); |
| last.view = null; |
| |
| expect(last.key).toEqual(key); |
| last.key = null; |
| |
| expect(last.code).toEqual(code); |
| last.code = null; |
| |
| expect(last.ev).toBeTruthy(); |
| last.ev = null; |
| } |
| |
| function verifyNoCall() { |
| expect(last.view).toBeNull(); |
| expect(last.key).toBeNull(); |
| expect(last.code).toBeNull(); |
| expect(last.ev).toBeNull(); |
| } |
| |
| function verifyTestKeys() { |
| jsKeyDown(elem, '65', 'A'); // 'A' |
| verifyCall('A', '65'); |
| jsKeyDown(elem, '66', 'B'); // 'B' |
| verifyNoCall(); |
| |
| jsKeyDown(elem, '49', '1'); // '1' |
| verifyCall('1', '49'); |
| jsKeyDown(elem, '50', '2'); // '2' |
| verifyNoCall(); |
| |
| jsKeyDown(elem, '116', 'F5'); // 'F5' |
| verifyCall('F5', '116'); |
| jsKeyDown(elem, '117', 'F6'); // 'F6' |
| verifyNoCall(); |
| |
| jsKeyDown(elem, '187', '='); // 'equals' |
| verifyCall('equals', '187'); |
| jsKeyDown(elem, '189', '-'); // 'dash' |
| verifyNoCall(); |
| |
| const vk = ks.getKeyBindings().viewKeys; |
| |
| expect(vk.length).toEqual(4); |
| ['A', '1', 'F5', 'equals'].forEach((k) => { |
| expect(fs.contains(vk, k)).toBeTruthy(); |
| }); |
| |
| expect(ks.getKeyBindings().viewFunction).toBeFalsy(); |
| } |
| |
| it('should allow specific key bindings', () => { |
| bindTestKeys(); |
| verifyTestKeys(); |
| }); |
| |
| it('should allow specific key bindings with descriptions', () => { |
| bindTestKeys(true); |
| verifyTestKeys(); |
| }); |
| |
| it('should warn about masked keys', () => { |
| const k = { |
| 'space': (token, key, code, ev) => cb(token, key, code, ev), |
| 'T': (token, key, code, ev) => cb(token, key, code, ev) |
| }; |
| let count = 0; |
| |
| function cb(token, key, code, ev) { |
| count++; |
| // console.debug('count = ' + count, token, key, code); |
| } |
| |
| ks.keyBindings(k); |
| |
| expect(logServiceSpy.warn).toHaveBeenCalledWith('setKeyBindings()\n: Key "T" is reserved'); |
| |
| // the 'T' key should NOT invoke our callback |
| expect(count).toEqual(0); |
| jsKeyDown(elem, '84', 'T'); // 'T' |
| expect(count).toEqual(0); |
| |
| // but the 'space' key SHOULD invoke our callback |
| jsKeyDown(elem, '32', ' '); // 'space' |
| expect(count).toEqual(1); |
| }); |
| |
| it('should block keys when disabled', () => { |
| let cbCount = 0; |
| |
| function cb() { cbCount++; } |
| |
| function pressA() { jsKeyDown(elem, '65', 'A'); } // 65 == 'A' keycode |
| |
| ks.keyBindings({ A: () => cb() }); |
| |
| expect(cbCount).toBe(0); |
| |
| pressA(); |
| expect(cbCount).toBe(1); |
| |
| ks.enableKeys(false); |
| pressA(); |
| expect(cbCount).toBe(1); |
| |
| ks.enableKeys(true); |
| pressA(); |
| expect(cbCount).toBe(2); |
| }); |
| |
| // === Gesture notes related tests |
| it('should start with no notes', () => { |
| expect(ks.gestureNotes()).toEqual([]); |
| }); |
| |
| it('should allow us to add nodes', () => { |
| const notes = [ |
| ['one', 'something about one'], |
| ['two', 'description of two'] |
| ]; |
| ks.gestureNotes(notes); |
| |
| expect(ks.gestureNotes()).toEqual(notes); |
| }); |
| |
| it('should ignore non-arrays', () => { |
| ks.gestureNotes({foo: 4}); |
| expect(ks.gestureNotes()).toEqual([]); |
| }); |
| |
| // Consider adding test to ensure array contains 2-tuples of strings |
| }); |