GUI -- Added keyBindings() and gestureNotes() to Key Service.
- Cleaned up fn.js and added contains(). - Unit tests added too. Change-Id: Id310675836e592af7a4a763f6624c0ee31adfbf5
Showing
4 changed files
with
202 additions
and
40 deletions
... | @@ -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 | }); | ... | ... |
-
Please register or login to post a comment