blob: 6f2f69a188151758522f00ff676dee59c7cfc49c [file] [log] [blame]
Sean Condonf4f54a12018-10-10 23:25:46 +01001/*
2 * Copyright 2018-present Open Networking Foundation
3 *
4 * Licensed under the Apache License, Version 2.0 (the "License");
5 * you may not use this file except in compliance with the License.
6 * You may obtain a copy of the License at
7 *
8 * http://www.apache.org/licenses/LICENSE-2.0
9 *
10 * Unless required by applicable law or agreed to in writing, software
11 * distributed under the License is distributed on an "AS IS" BASIS,
12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 * See the License for the specific language governing permissions and
14 * limitations under the License.
15 */
16import { TestBed, inject } from '@angular/core/testing';
17import {ActivatedRoute, Params} from '@angular/router';
18
19import { KeysService, KeysToken } from './keys.service';
20import { FnService } from './fn.service';
21import { LogService } from '../log.service';
22import { NavService } from '../nav/nav.service';
23
24import {of} from 'rxjs';
25import * as d3 from 'd3';
26
27class MockActivatedRoute extends ActivatedRoute {
28 constructor(params: Params) {
29 super();
30 this.queryParams = of(params);
31 }
32}
33
34class MockNavService {}
35
36/*
37 ONOS GUI -- Key Handler Service - Unit Tests
38 */
39describe('KeysService', () => {
40 let ar: ActivatedRoute;
41 let fs: FnService;
42 let ks: KeysService;
43 let mockWindow: Window;
44 let logServiceSpy: jasmine.SpyObj<LogService>;
45
46 const qhs: any = {};
47 let d3Elem: any;
48 let elem: any;
49 let last: any;
50
51 beforeEach(() => {
52 const logSpy = jasmine.createSpyObj('LogService', ['debug', 'warn', 'info']);
53 ar = new MockActivatedRoute({'debug': 'TestService'});
54 mockWindow = <any>{
55 innerWidth: 400,
56 innerHeight: 200,
57 navigator: {
58 userAgent: 'defaultUA'
59 },
60 location: <any>{
61 hostname: 'foo',
62 host: 'foo',
63 port: '80',
64 protocol: 'http',
65 search: { debug: 'true' },
66 href: 'ws://foo:123/onos/ui/websock/path',
67 absUrl: 'ws://foo:123/onos/ui/websock/path'
68 }
69 };
70 fs = new FnService(ar, logSpy, mockWindow);
71
72 d3Elem = d3.select('body').append('p').attr('id', 'ptest');
73 elem = d3Elem.node();
74 last = {
75 view: null,
76 key: null,
77 code: null,
78 ev: null
79 };
80
81 TestBed.configureTestingModule({
82 providers: [KeysService,
83 { provide: FnService, useValue: fs},
84 { provide: LogService, useValue: logSpy },
85 { provide: ActivatedRoute, useValue: ar },
86 { provide: NavService, useClass: MockNavService},
87 { provide: 'Window', useFactory: (() => mockWindow ) }
88 ]
89 });
90 ks = TestBed.get(KeysService);
91 ks.installOn(d3Elem);
Sean Condonf4f54a12018-10-10 23:25:46 +010092 logServiceSpy = TestBed.get(LogService);
93 });
94
95 afterEach(() => {
96 d3.select('#ptest').remove();
97 });
98
99 it('should be created', () => {
100 expect(ks).toBeTruthy();
101 });
102
103 it('should define api functions', () => {
104 expect(fs.areFunctions(ks, [
Sean Condonb2c483c2019-01-16 20:28:55 +0000105 'installOn', 'keyBindings', 'unbindKeys', 'dialogKeys',
Sean Condonf4f54a12018-10-10 23:25:46 +0100106 'addSeq', 'remSeq', 'gestureNotes', 'enableKeys', 'enableGlobalKeys',
107 'checkNotGlobal', 'getKeyBindings',
108 'matchSeq', 'whatKey', 'textFieldInput', 'keyIn', 'qhlion', 'qhlionShowHide',
109 'qhlionHintEsc', 'qhlionHintT', 'setupGlobalKeys', 'quickHelp',
110 'escapeKey', 'toggleTheme', 'filterMaskedKeys', 'unexParam',
111 'setKeyBindings', 'bindDialogKeys', 'unbindDialogKeys'
112 ])).toBeTruthy();
113 });
114
115 function jsKeyDown(element, code: string, keyName: string) {
116 const ev = new KeyboardEvent('keydown',
117 { code: code, key: keyName });
118
119 // Chromium Hack
120 // if (navigator.userAgent.toLowerCase().indexOf('chrome') > -1) {
121 // Object.defineProperty(ev, 'keyCode', {
122 // get: () => { return this.keyCodeVal; }
123 // });
124 // Object.defineProperty(ev, 'which', {
125 // get: () => { return this.keyCodeVal; }
126 // });
127 // }
128
129 if (ev.code !== code.toString()) {
130 console.warn('keyCode mismatch ' + ev.code +
Sean Condondfc6dba2019-11-09 11:50:23 +0000131 '(' + ev.toString() + ') -> ' + code);
Sean Condonf4f54a12018-10-10 23:25:46 +0100132 }
133 element.dispatchEvent(ev);
134 }
135
136 // === Key binding related tests
137 it('should start with default key bindings', () => {
138 const state = ks.getKeyBindings();
139 const gk = state.globalKeys;
140 const mk = state.maskedKeys;
141 const vk = state.viewKeys;
142 const vf = state.viewFunction;
143
144 expect(gk.length).toEqual(4);
145 ['backSlash', 'slash', 'esc', 'T'].forEach((k) => {
146 expect(fs.contains(gk, k)).toBeTruthy();
147 });
148
149 expect(mk.length).toEqual(3);
150 ['backSlash', 'slash', 'T'].forEach((k) => {
151 expect(fs.contains(mk, k)).toBeTruthy();
152 });
153
154 expect(vk.length).toEqual(0);
155 expect(vf).toBeFalsy();
156 });
157
158 function bindTestKeys(withDescs?) {
159 const keys = ['A', '1', 'F5', 'equals'];
160 const kb = {};
161
162 function cb(view, key, code, ev) {
163 last.view = view;
164 last.key = key;
165 last.code = code;
166 last.ev = ev;
167 }
168
169 function bind(k) {
170 return withDescs ?
171 [(view, key, code, ev) => {cb(view, key, code, ev); }, 'desc for key ' + k] :
172 (view, key, code, ev) => {cb(view, key, code, ev); };
173 }
174
175 keys.forEach((k) => {
176 kb[k] = bind(k);
177 });
178
179 ks.keyBindings(kb);
180 }
181
182 function verifyCall(key, code) {
183 // TODO: update expectation, when view tokens are implemented
184 expect(last.view).toEqual(KeysToken.KEYEV);
185 last.view = null;
186
187 expect(last.key).toEqual(key);
188 last.key = null;
189
190 expect(last.code).toEqual(code);
191 last.code = null;
192
193 expect(last.ev).toBeTruthy();
194 last.ev = null;
195 }
196
197 function verifyNoCall() {
198 expect(last.view).toBeNull();
199 expect(last.key).toBeNull();
200 expect(last.code).toBeNull();
201 expect(last.ev).toBeNull();
202 }
203
204 function verifyTestKeys() {
205 jsKeyDown(elem, '65', 'A'); // 'A'
206 verifyCall('A', '65');
207 jsKeyDown(elem, '66', 'B'); // 'B'
208 verifyNoCall();
209
210 jsKeyDown(elem, '49', '1'); // '1'
211 verifyCall('1', '49');
212 jsKeyDown(elem, '50', '2'); // '2'
213 verifyNoCall();
214
215 jsKeyDown(elem, '116', 'F5'); // 'F5'
216 verifyCall('F5', '116');
217 jsKeyDown(elem, '117', 'F6'); // 'F6'
218 verifyNoCall();
219
220 jsKeyDown(elem, '187', '='); // 'equals'
221 verifyCall('equals', '187');
222 jsKeyDown(elem, '189', '-'); // 'dash'
223 verifyNoCall();
224
225 const vk = ks.getKeyBindings().viewKeys;
226
227 expect(vk.length).toEqual(4);
228 ['A', '1', 'F5', 'equals'].forEach((k) => {
229 expect(fs.contains(vk, k)).toBeTruthy();
230 });
231
232 expect(ks.getKeyBindings().viewFunction).toBeFalsy();
233 }
234
235 it('should allow specific key bindings', () => {
236 bindTestKeys();
237 verifyTestKeys();
238 });
239
240 it('should allow specific key bindings with descriptions', () => {
241 bindTestKeys(true);
242 verifyTestKeys();
243 });
244
245 it('should warn about masked keys', () => {
246 const k = {
247 'space': (token, key, code, ev) => cb(token, key, code, ev),
248 'T': (token, key, code, ev) => cb(token, key, code, ev)
249 };
250 let count = 0;
251
252 function cb(token, key, code, ev) {
253 count++;
254 // console.debug('count = ' + count, token, key, code);
255 }
256
257 ks.keyBindings(k);
258
259 expect(logServiceSpy.warn).toHaveBeenCalledWith('setKeyBindings()\n: Key "T" is reserved');
260
261 // the 'T' key should NOT invoke our callback
262 expect(count).toEqual(0);
263 jsKeyDown(elem, '84', 'T'); // 'T'
264 expect(count).toEqual(0);
265
266 // but the 'space' key SHOULD invoke our callback
267 jsKeyDown(elem, '32', ' '); // 'space'
268 expect(count).toEqual(1);
269 });
270
271 it('should block keys when disabled', () => {
272 let cbCount = 0;
273
274 function cb() { cbCount++; }
275
276 function pressA() { jsKeyDown(elem, '65', 'A'); } // 65 == 'A' keycode
277
278 ks.keyBindings({ A: () => cb() });
279
280 expect(cbCount).toBe(0);
281
282 pressA();
283 expect(cbCount).toBe(1);
284
285 ks.enableKeys(false);
286 pressA();
287 expect(cbCount).toBe(1);
288
289 ks.enableKeys(true);
290 pressA();
291 expect(cbCount).toBe(2);
292 });
293
294 // === Gesture notes related tests
295 it('should start with no notes', () => {
296 expect(ks.gestureNotes()).toEqual([]);
297 });
298
299 it('should allow us to add nodes', () => {
300 const notes = [
301 ['one', 'something about one'],
302 ['two', 'description of two']
303 ];
304 ks.gestureNotes(notes);
305
306 expect(ks.gestureNotes()).toEqual(notes);
307 });
308
309 it('should ignore non-arrays', () => {
310 ks.gestureNotes({foo: 4});
311 expect(ks.gestureNotes()).toEqual([]);
312 });
313
314 // Consider adding test to ensure array contains 2-tuples of strings
315});