blob: 5f4b349d75ed82fb0db39af60e97c1a942bef4a2 [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);
92 ks.bindQhs(qhs);
93 logServiceSpy = TestBed.get(LogService);
94 });
95
96 afterEach(() => {
97 d3.select('#ptest').remove();
98 });
99
100 it('should be created', () => {
101 expect(ks).toBeTruthy();
102 });
103
104 it('should define api functions', () => {
105 expect(fs.areFunctions(ks, [
106 'bindQhs', 'installOn', 'keyBindings', 'unbindKeys', 'dialogKeys',
107 'addSeq', 'remSeq', 'gestureNotes', 'enableKeys', 'enableGlobalKeys',
108 'checkNotGlobal', 'getKeyBindings',
109 'matchSeq', 'whatKey', 'textFieldInput', 'keyIn', 'qhlion', 'qhlionShowHide',
110 'qhlionHintEsc', 'qhlionHintT', 'setupGlobalKeys', 'quickHelp',
111 'escapeKey', 'toggleTheme', 'filterMaskedKeys', 'unexParam',
112 'setKeyBindings', 'bindDialogKeys', 'unbindDialogKeys'
113 ])).toBeTruthy();
114 });
115
116 function jsKeyDown(element, code: string, keyName: string) {
117 const ev = new KeyboardEvent('keydown',
118 { code: code, key: keyName });
119
120 // Chromium Hack
121 // if (navigator.userAgent.toLowerCase().indexOf('chrome') > -1) {
122 // Object.defineProperty(ev, 'keyCode', {
123 // get: () => { return this.keyCodeVal; }
124 // });
125 // Object.defineProperty(ev, 'which', {
126 // get: () => { return this.keyCodeVal; }
127 // });
128 // }
129
130 if (ev.code !== code.toString()) {
131 console.warn('keyCode mismatch ' + ev.code +
132 '(' + ev.which + ') -> ' + code);
133 }
134 element.dispatchEvent(ev);
135 }
136
137 // === Key binding related tests
138 it('should start with default key bindings', () => {
139 const state = ks.getKeyBindings();
140 const gk = state.globalKeys;
141 const mk = state.maskedKeys;
142 const vk = state.viewKeys;
143 const vf = state.viewFunction;
144
145 expect(gk.length).toEqual(4);
146 ['backSlash', 'slash', 'esc', 'T'].forEach((k) => {
147 expect(fs.contains(gk, k)).toBeTruthy();
148 });
149
150 expect(mk.length).toEqual(3);
151 ['backSlash', 'slash', 'T'].forEach((k) => {
152 expect(fs.contains(mk, k)).toBeTruthy();
153 });
154
155 expect(vk.length).toEqual(0);
156 expect(vf).toBeFalsy();
157 });
158
159 function bindTestKeys(withDescs?) {
160 const keys = ['A', '1', 'F5', 'equals'];
161 const kb = {};
162
163 function cb(view, key, code, ev) {
164 last.view = view;
165 last.key = key;
166 last.code = code;
167 last.ev = ev;
168 }
169
170 function bind(k) {
171 return withDescs ?
172 [(view, key, code, ev) => {cb(view, key, code, ev); }, 'desc for key ' + k] :
173 (view, key, code, ev) => {cb(view, key, code, ev); };
174 }
175
176 keys.forEach((k) => {
177 kb[k] = bind(k);
178 });
179
180 ks.keyBindings(kb);
181 }
182
183 function verifyCall(key, code) {
184 // TODO: update expectation, when view tokens are implemented
185 expect(last.view).toEqual(KeysToken.KEYEV);
186 last.view = null;
187
188 expect(last.key).toEqual(key);
189 last.key = null;
190
191 expect(last.code).toEqual(code);
192 last.code = null;
193
194 expect(last.ev).toBeTruthy();
195 last.ev = null;
196 }
197
198 function verifyNoCall() {
199 expect(last.view).toBeNull();
200 expect(last.key).toBeNull();
201 expect(last.code).toBeNull();
202 expect(last.ev).toBeNull();
203 }
204
205 function verifyTestKeys() {
206 jsKeyDown(elem, '65', 'A'); // 'A'
207 verifyCall('A', '65');
208 jsKeyDown(elem, '66', 'B'); // 'B'
209 verifyNoCall();
210
211 jsKeyDown(elem, '49', '1'); // '1'
212 verifyCall('1', '49');
213 jsKeyDown(elem, '50', '2'); // '2'
214 verifyNoCall();
215
216 jsKeyDown(elem, '116', 'F5'); // 'F5'
217 verifyCall('F5', '116');
218 jsKeyDown(elem, '117', 'F6'); // 'F6'
219 verifyNoCall();
220
221 jsKeyDown(elem, '187', '='); // 'equals'
222 verifyCall('equals', '187');
223 jsKeyDown(elem, '189', '-'); // 'dash'
224 verifyNoCall();
225
226 const vk = ks.getKeyBindings().viewKeys;
227
228 expect(vk.length).toEqual(4);
229 ['A', '1', 'F5', 'equals'].forEach((k) => {
230 expect(fs.contains(vk, k)).toBeTruthy();
231 });
232
233 expect(ks.getKeyBindings().viewFunction).toBeFalsy();
234 }
235
236 it('should allow specific key bindings', () => {
237 bindTestKeys();
238 verifyTestKeys();
239 });
240
241 it('should allow specific key bindings with descriptions', () => {
242 bindTestKeys(true);
243 verifyTestKeys();
244 });
245
246 it('should warn about masked keys', () => {
247 const k = {
248 'space': (token, key, code, ev) => cb(token, key, code, ev),
249 'T': (token, key, code, ev) => cb(token, key, code, ev)
250 };
251 let count = 0;
252
253 function cb(token, key, code, ev) {
254 count++;
255 // console.debug('count = ' + count, token, key, code);
256 }
257
258 ks.keyBindings(k);
259
260 expect(logServiceSpy.warn).toHaveBeenCalledWith('setKeyBindings()\n: Key "T" is reserved');
261
262 // the 'T' key should NOT invoke our callback
263 expect(count).toEqual(0);
264 jsKeyDown(elem, '84', 'T'); // 'T'
265 expect(count).toEqual(0);
266
267 // but the 'space' key SHOULD invoke our callback
268 jsKeyDown(elem, '32', ' '); // 'space'
269 expect(count).toEqual(1);
270 });
271
272 it('should block keys when disabled', () => {
273 let cbCount = 0;
274
275 function cb() { cbCount++; }
276
277 function pressA() { jsKeyDown(elem, '65', 'A'); } // 65 == 'A' keycode
278
279 ks.keyBindings({ A: () => cb() });
280
281 expect(cbCount).toBe(0);
282
283 pressA();
284 expect(cbCount).toBe(1);
285
286 ks.enableKeys(false);
287 pressA();
288 expect(cbCount).toBe(1);
289
290 ks.enableKeys(true);
291 pressA();
292 expect(cbCount).toBe(2);
293 });
294
295 // === Gesture notes related tests
296 it('should start with no notes', () => {
297 expect(ks.gestureNotes()).toEqual([]);
298 });
299
300 it('should allow us to add nodes', () => {
301 const notes = [
302 ['one', 'something about one'],
303 ['two', 'description of two']
304 ];
305 ks.gestureNotes(notes);
306
307 expect(ks.gestureNotes()).toEqual(notes);
308 });
309
310 it('should ignore non-arrays', () => {
311 ks.gestureNotes({foo: 4});
312 expect(ks.gestureNotes()).toEqual([]);
313 });
314
315 // Consider adding test to ensure array contains 2-tuples of strings
316});