Simon Hunt

GUI -- Added keyBindings() and gestureNotes() to Key Service.

- Cleaned up fn.js and added contains().
- Unit tests added too.

Change-Id: Id310675836e592af7a4a763f6624c0ee31adfbf5
...@@ -22,20 +22,33 @@ ...@@ -22,20 +22,33 @@
22 (function (onos) { 22 (function (onos) {
23 'use strict'; 23 'use strict';
24 24
25 - onos.factory('FnService', [function () { 25 + function isF(f) {
26 - return {
27 - isF: function (f) {
28 return $.isFunction(f) ? f : null; 26 return $.isFunction(f) ? f : null;
29 - }, 27 + }
30 - isA: function (a) { 28 +
29 + function isA(a) {
31 return $.isArray(a) ? a : null; 30 return $.isArray(a) ? a : null;
32 - }, 31 + }
33 - isS: function (s) { 32 +
33 + function isS(s) {
34 return typeof s === 'string' ? s : null; 34 return typeof s === 'string' ? s : null;
35 - }, 35 + }
36 - isO: function (o) { 36 +
37 + function isO(o) {
37 return $.isPlainObject(o) ? o : null; 38 return $.isPlainObject(o) ? o : null;
38 } 39 }
40 +
41 + function contains(a, x) {
42 + return isA(a) && a.indexOf(x) > -1;
43 + }
44 +
45 + onos.factory('FnService', [function () {
46 + return {
47 + isF: isF,
48 + isA: isA,
49 + isS: isS,
50 + isO: isO,
51 + contains: contains
39 }; 52 };
40 }]); 53 }]);
41 54
......
...@@ -144,6 +144,45 @@ ...@@ -144,6 +144,45 @@
144 return true; 144 return true;
145 } 145 }
146 146
147 + function setKeyBindings(keyArg) {
148 + var viewKeys,
149 + masked = [];
150 +
151 + if (f.isF(keyArg)) {
152 + // set general key handler callback
153 + keyHandler.viewFn = keyArg;
154 + } else {
155 + // set specific key filter map
156 + viewKeys = d3.map(keyArg).keys();
157 + viewKeys.forEach(function (key) {
158 + if (keyHandler.maskedKeys[key]) {
159 + masked.push(' Key "' + key + '" is reserved');
160 + }
161 + });
162 +
163 + if (masked.length) {
164 + // TODO: use alert service
165 + window.alert('WARNING...\n\nsetKeys():\n' + masked.join('\n'));
166 + }
167 + keyHandler.viewKeys = keyArg;
168 + }
169 + }
170 +
171 + function getKeyBindings() {
172 + var gkeys = d3.map(keyHandler.globalKeys).keys(),
173 + masked = d3.map(keyHandler.maskedKeys).keys(),
174 + vkeys = d3.map(keyHandler.viewKeys).keys(),
175 + vfn = !!f.isF(keyHandler.viewFn);
176 +
177 + return {
178 + globalKeys: gkeys,
179 + maskedKeys: masked,
180 + viewKeys: vkeys,
181 + viewFunction: vfn
182 + };
183 + }
184 +
185 + // TODO: inject alert service
147 onos.factory('KeyService', ['FnService', function (fs) { 186 onos.factory('KeyService', ['FnService', function (fs) {
148 f = fs; 187 f = fs;
149 return { 188 return {
...@@ -154,7 +193,20 @@ ...@@ -154,7 +193,20 @@
154 theme: function () { 193 theme: function () {
155 return theme; 194 return theme;
156 }, 195 },
157 - whatKey: whatKey 196 + keyBindings: function (x) {
197 + if (x === undefined) {
198 + return getKeyBindings();
199 + } else {
200 + setKeyBindings(x);
201 + }
202 + },
203 + gestureNotes: function (g) {
204 + if (g === undefined) {
205 + return keyHandler.viewGestures;
206 + } else {
207 + keyHandler.viewGestures = f.isA(g) || [];
208 + }
209 + }
158 }; 210 };
159 }]); 211 }]);
160 212
......
...@@ -26,7 +26,8 @@ describe('factory: fw/lib/fn.js', function() { ...@@ -26,7 +26,8 @@ describe('factory: fw/lib/fn.js', function() {
26 someObject = { foo: 'bar'}, 26 someObject = { foo: 'bar'},
27 someNumber = 42, 27 someNumber = 42,
28 someString = 'xyyzy', 28 someString = 'xyyzy',
29 - someDate = new Date(); 29 + someDate = new Date(),
30 + stringArray = ['foo', 'bar'];
30 31
31 beforeEach(module('onosApp')); 32 beforeEach(module('onosApp'));
32 33
...@@ -149,4 +150,20 @@ describe('factory: fw/lib/fn.js', function() { ...@@ -149,4 +150,20 @@ describe('factory: fw/lib/fn.js', function() {
149 it('isO(): the reference for object', function () { 150 it('isO(): the reference for object', function () {
150 expect(fs.isO(someObject)).toBe(someObject); 151 expect(fs.isO(someObject)).toBe(someObject);
151 }); 152 });
153 +
154 + // === Tests for contains()
155 + it('contains(): false for improper args', function () {
156 + expect(fs.contains()).toBeFalsy();
157 + });
158 + it('contains(): false for non-array', function () {
159 + expect(fs.contains(null, 1)).toBeFalsy();
160 + });
161 + it ('contains(): true for contained item', function () {
162 + expect(fs.contains(someArray, 1)).toBeTruthy();
163 + expect(fs.contains(stringArray, 'bar')).toBeTruthy();
164 + });
165 + it ('contains(): false for non-contained item', function () {
166 + expect(fs.contains(someArray, 109)).toBeFalsy();
167 + expect(fs.contains(stringArray, 'zonko')).toBeFalsy();
168 + });
152 }); 169 });
......
...@@ -20,9 +20,7 @@ ...@@ -20,9 +20,7 @@
20 @author Simon Hunt 20 @author Simon Hunt
21 */ 21 */
22 describe('factory: fw/lib/keys.js', function() { 22 describe('factory: fw/lib/keys.js', function() {
23 - var ks, 23 + var ks, fs, d3Elem, elem, last;
24 - fs,
25 - d3Elem;
26 24
27 beforeEach(module('onosApp')); 25 beforeEach(module('onosApp'));
28 26
...@@ -30,7 +28,14 @@ describe('factory: fw/lib/keys.js', function() { ...@@ -30,7 +28,14 @@ describe('factory: fw/lib/keys.js', function() {
30 ks = KeyService; 28 ks = KeyService;
31 fs = FnService; 29 fs = FnService;
32 d3Elem = d3.select('body').append('p').attr('id', 'ptest'); 30 d3Elem = d3.select('body').append('p').attr('id', 'ptest');
31 + elem = d3Elem.node();
33 ks.installOn(d3Elem); 32 ks.installOn(d3Elem);
33 + last = {
34 + view: null,
35 + key: null,
36 + code: null,
37 + ev: null
38 + };
34 })); 39 }));
35 40
36 afterEach(function () { 41 afterEach(function () {
...@@ -74,45 +79,120 @@ describe('factory: fw/lib/keys.js', function() { ...@@ -74,45 +79,120 @@ describe('factory: fw/lib/keys.js', function() {
74 element.dispatchEvent(ev); 79 element.dispatchEvent(ev);
75 } 80 }
76 81
82 + // === Theme related tests
77 it('should start in light theme', function () { 83 it('should start in light theme', function () {
78 expect(ks.theme()).toEqual('light'); 84 expect(ks.theme()).toEqual('light');
79 }); 85 });
80 it('should toggle to dark theme', function () { 86 it('should toggle to dark theme', function () {
81 - jsKeyDown(d3Elem.node(), 84); // 'T' 87 + jsKeyDown(elem, 84); // 'T'
82 expect(ks.theme()).toEqual('dark'); 88 expect(ks.theme()).toEqual('dark');
83 }); 89 });
84 90
85 - // key code lookups 91 + // === Key binding related tests
86 - // NOTE: should be injecting keydown events, rather than exposing whatKey() 92 + it('should start with default key bindings', function () {
87 - it('whatKey: 13', function () { 93 + var state = ks.keyBindings(),
88 - expect(ks.whatKey(13)).toEqual('enter'); 94 + gk = state.globalKeys,
89 - }); 95 + mk = state.maskedKeys,
90 - it('whatKey: 16', function () { 96 + vk = state.viewKeys,
91 - expect(ks.whatKey(16)).toEqual('shift'); 97 + vf = state.viewFunction;
92 - }); 98 +
93 - it('whatKey: 40', function () { 99 + expect(gk.length).toEqual(4);
94 - expect(ks.whatKey(40)).toEqual('downArrow'); 100 + ['backSlash', 'slash', 'esc', 'T'].forEach(function (k) {
95 - }); 101 + expect(fs.contains(gk, k)).toBeTruthy();
96 - it('whatKey: 65', function () {
97 - expect(ks.whatKey(65)).toEqual('A');
98 }); 102 });
99 - it('whatKey: 84', function () { 103 +
100 - expect(ks.whatKey(84)).toEqual('T'); 104 + expect(mk.length).toEqual(3);
105 + ['backSlash', 'slash', 'T'].forEach(function (k) {
106 + expect(fs.contains(mk, k)).toBeTruthy();
101 }); 107 });
102 - it('whatKey: 49', function () { 108 +
103 - expect(ks.whatKey(49)).toEqual('1'); 109 + expect(vk.length).toEqual(0);
110 + expect(vf).toBeFalsy();
104 }); 111 });
105 - it('whatKey: 55', function () { 112 +
106 - expect(ks.whatKey(55)).toEqual('7'); 113 + function bindTestKeys(withDescs) {
114 + var keys = ['A', '1', 'F5', 'equals'],
115 + kb = {};
116 +
117 + function cb(view, key, code, ev) {
118 + last.view = view;
119 + last.key = key;
120 + last.code = code;
121 + last.ev = ev;
122 + }
123 +
124 + function bind(k) {
125 + return withDescs ? [cb, 'desc for key ' + k] : cb;
126 + }
127 +
128 + keys.forEach(function (k) {
129 + kb[k] = bind(k);
107 }); 130 });
108 - it('whatKey: 112', function () { 131 +
109 - expect(ks.whatKey(112)).toEqual('F1'); 132 + ks.keyBindings(kb);
133 + }
134 +
135 + function verifyCall(key, code) {
136 + // TODO: update expectation, when view tokens are implemented
137 + expect(last.view).toEqual('NotYetAViewToken');
138 + last.view = null;
139 +
140 + expect(last.key).toEqual(key);
141 + last.key = null;
142 +
143 + expect(last.code).toEqual(code);
144 + last.code = null;
145 +
146 + expect(last.ev).toBeTruthy();
147 + last.ev = null;
148 + }
149 +
150 + function verifyNoCall() {
151 + expect(last.view).toBeNull();
152 + expect(last.key).toBeNull();
153 + expect(last.code).toBeNull();
154 + expect(last.ev).toBeNull();
155 + }
156 +
157 + function verifyTestKeys() {
158 + jsKeyDown(elem, 65); // 'A'
159 + verifyCall('A', 65);
160 + jsKeyDown(elem, 66); // 'B'
161 + verifyNoCall();
162 +
163 + jsKeyDown(elem, 49); // '1'
164 + verifyCall('1', 49);
165 + jsKeyDown(elem, 50); // '2'
166 + verifyNoCall();
167 +
168 + jsKeyDown(elem, 116); // 'F5'
169 + verifyCall('F5', 116);
170 + jsKeyDown(elem, 117); // 'F6'
171 + verifyNoCall();
172 +
173 + jsKeyDown(elem, 187); // 'equals'
174 + verifyCall('equals', 187);
175 + jsKeyDown(elem, 189); // 'dash'
176 + verifyNoCall();
177 +
178 + var vk = ks.keyBindings().viewKeys;
179 +
180 + expect(vk.length).toEqual(4);
181 + ['A', '1', 'F5', 'equals'].forEach(function (k) {
182 + expect(fs.contains(vk, k)).toBeTruthy();
110 }); 183 });
111 - it('whatKey: 123', function () { 184 +
112 - expect(ks.whatKey(123)).toEqual('F12'); 185 + expect(ks.keyBindings().viewFunction).toBeFalsy();
186 + }
187 +
188 + it('should allow specific key bindings', function () {
189 + bindTestKeys();
190 + verifyTestKeys();
113 }); 191 });
114 - it('whatKey: 1', function () { 192 +
115 - expect(ks.whatKey(1)).toEqual('.'); 193 + it('should allow specific key bindings with descriptions', function () {
194 + bindTestKeys(true);
195 + verifyTestKeys();
116 }); 196 });
117 197
118 }); 198 });
......