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
209 additions
and
47 deletions
| ... | @@ -22,20 +22,33 @@ | ... | @@ -22,20 +22,33 @@ |
| 22 | (function (onos) { | 22 | (function (onos) { |
| 23 | 'use strict'; | 23 | 'use strict'; |
| 24 | 24 | ||
| 25 | + function isF(f) { | ||
| 26 | + return $.isFunction(f) ? f : null; | ||
| 27 | + } | ||
| 28 | + | ||
| 29 | + function isA(a) { | ||
| 30 | + return $.isArray(a) ? a : null; | ||
| 31 | + } | ||
| 32 | + | ||
| 33 | + function isS(s) { | ||
| 34 | + return typeof s === 'string' ? s : null; | ||
| 35 | + } | ||
| 36 | + | ||
| 37 | + function isO(o) { | ||
| 38 | + return $.isPlainObject(o) ? o : null; | ||
| 39 | + } | ||
| 40 | + | ||
| 41 | + function contains(a, x) { | ||
| 42 | + return isA(a) && a.indexOf(x) > -1; | ||
| 43 | + } | ||
| 44 | + | ||
| 25 | onos.factory('FnService', [function () { | 45 | onos.factory('FnService', [function () { |
| 26 | return { | 46 | return { |
| 27 | - isF: function (f) { | 47 | + isF: isF, |
| 28 | - return $.isFunction(f) ? f : null; | 48 | + isA: isA, |
| 29 | - }, | 49 | + isS: isS, |
| 30 | - isA: function (a) { | 50 | + isO: isO, |
| 31 | - return $.isArray(a) ? a : null; | 51 | + contains: contains |
| 32 | - }, | ||
| 33 | - isS: function (s) { | ||
| 34 | - return typeof s === 'string' ? s : null; | ||
| 35 | - }, | ||
| 36 | - isO: function (o) { | ||
| 37 | - return $.isPlainObject(o) ? o : null; | ||
| 38 | - } | ||
| 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 () { | 102 | + }); |
| 97 | - expect(ks.whatKey(65)).toEqual('A'); | 103 | + |
| 98 | - }); | 104 | + expect(mk.length).toEqual(3); |
| 99 | - it('whatKey: 84', function () { | 105 | + ['backSlash', 'slash', 'T'].forEach(function (k) { |
| 100 | - expect(ks.whatKey(84)).toEqual('T'); | 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); |
| 104 | - }); | 110 | + expect(vf).toBeFalsy(); |
| 105 | - it('whatKey: 55', function () { | ||
| 106 | - expect(ks.whatKey(55)).toEqual('7'); | ||
| 107 | - }); | ||
| 108 | - it('whatKey: 112', function () { | ||
| 109 | - expect(ks.whatKey(112)).toEqual('F1'); | ||
| 110 | }); | 111 | }); |
| 111 | - it('whatKey: 123', function () { | 112 | + |
| 112 | - expect(ks.whatKey(123)).toEqual('F12'); | 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); | ||
| 130 | + }); | ||
| 131 | + | ||
| 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(); | ||
| 183 | + }); | ||
| 184 | + | ||
| 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