Chromium Code Reviews| OLD | NEW |
|---|---|
| (Empty) | |
| 1 // Copyright 2014 The Chromium Authors. All rights reserved. | |
|
dmazzoni
2015/08/24 19:05:07
2015?
| |
| 2 // Use of this source code is governed by a BSD-style license that can be | |
| 3 // found in the LICENSE file. | |
| 4 | |
| 5 /** | |
| 6 * @fileoverview This file contains the |MockFeedback| class which is a | |
|
dmazzoni
2015/08/24 19:05:06
Could you document what happens to initial speech
| |
| 7 * combined mock class for speech and braille feedback. A test that uses | |
| 8 * this class may add expectations for speech utterances and braille display | |
| 9 * content to be output. The |install| method sets appropriate mock classes | |
| 10 * as the |cvox.ChromeVox.tts| and |cvox.ChromeVox.braille| objects, | |
| 11 * respectively. Output sent to those objects will then be collected in | |
| 12 * an internal queue. | |
| 13 * | |
| 14 * Expectaations can be added using the |expectSpeech| and |expectBraille| | |
|
dmazzoni
2015/08/24 19:05:06
Expectaations -> Expectations
| |
| 15 * methods. These methods take either strings or regular expressions to match | |
| 16 * against. Strings must match a full utterance (or display content) exactly, | |
| 17 * while a regular expression must match a substring (use anchor operators if | |
| 18 * needed). | |
| 19 * | |
| 20 * Function calls may be inserted in the stream of expectations using the | |
| 21 * |call| method. Such callbacks are called after all preceding expectations | |
| 22 * have been met, and before any further expectations are matched. Callbacks | |
| 23 * are called in the order they were added to the mock. | |
| 24 * | |
| 25 * The |replay| method starts processing any pending utterances and braille | |
| 26 * display content and will try to match expectations as new feedback enters | |
| 27 * the queue asynchronously. When all expectations have been met and callbacks | |
| 28 * called, the finish callback, if any was provided to the constructor, is | |
| 29 * called. The mock is lean, meaning that feedback that doesn't match | |
| 30 * any expectations is silently ignored. | |
| 31 * | |
| 32 * NOTE: for asynchronous tests, the processing will never finish if there | |
| 33 * are unmet expectations. To help debugging in such situations, the mock | |
| 34 * will output its pending state if there are pending expectations and no | |
| 35 * output is received within a few seconds. | |
| 36 * | |
| 37 * See mock_feedback_test.js for example usage of this class. | |
| 38 */ | |
| 39 | |
| 40 /** | |
| 41 * Combined mock class for braille and speech output. | |
| 42 * @param {function=} opt_finishedCallback Called when all expectaions have | |
|
dmazzoni
2015/08/24 19:05:06
expectations
| |
| 43 * been met. | |
| 44 * @constructor | |
| 45 */ | |
| 46 var MockFeedback = function(opt_finishedCallback) { | |
| 47 /** | |
| 48 * @type {function} | |
| 49 * @private | |
| 50 */ | |
| 51 this.finishedCallback_ = opt_finishedCallback || null; | |
| 52 /** | |
| 53 * True when |replay| has been called and actions are being replayed. | |
| 54 * @type {boolean} | |
| 55 * @private | |
| 56 */ | |
| 57 this.replaying_ = false; | |
| 58 /** | |
| 59 * True when inside the |process| function to prevent nested calls. | |
| 60 * @type {boolean} | |
| 61 * @private | |
| 62 */ | |
| 63 this.inProcess_ = false; | |
| 64 /** | |
| 65 * Pending expectations and callbacks. | |
| 66 * @type {Array<{perform: function(): boolean, describe: function(): string}>} | |
| 67 * @private | |
| 68 */ | |
| 69 this.pendingActions_ = []; | |
| 70 /** | |
| 71 * Pending speech utterances. | |
| 72 * @type {Array<{text: string, callback: (function|undefined)}>} | |
| 73 * @private | |
| 74 */ | |
| 75 this.pendingUtterances_ = []; | |
| 76 /** | |
| 77 * Pending braille output. | |
| 78 * @type {Array<{text: string, callback: (function|undefined)}>} | |
| 79 * @private | |
| 80 */ | |
| 81 this.pendingBraille_ = []; | |
| 82 /** | |
| 83 * Handle for the timeout set for debug logging. | |
| 84 * @type {number} | |
| 85 * @private | |
| 86 */ | |
| 87 this.logTimeoutId_ = 0; | |
| 88 /** | |
| 89 * @type {cvox.NavBraille} | |
| 90 * @private | |
| 91 */ | |
| 92 this.lastMatchedBraille_ = null; | |
| 93 }; | |
| 94 | |
| 95 MockFeedback.prototype = { | |
| 96 | |
| 97 /** | |
| 98 * Install mock objects as |cvox.CrhomeVox.tts| and |cvox.ChromeVox.braille| | |
|
dmazzoni
2015/08/24 19:05:07
ChromeVox
| |
| 99 * to collect feedback. | |
| 100 */ | |
| 101 install: function() { | |
| 102 assertFalse(this.replaying_); | |
| 103 function MockTts() {} | |
|
dmazzoni
2015/08/24 19:05:07
I know this is legal syntax but it looks funny, li
| |
| 104 MockTts.prototype = { | |
| 105 __proto__: cvox.TtsInterface.prototype, | |
| 106 speak: this.addUtterance_.bind(this) | |
| 107 }; | |
| 108 cvox.ChromeVox.tts = new MockTts(); | |
| 109 function MockBraille() { | |
| 110 } | |
| 111 MockBraille.prototype = { | |
| 112 __proto__: cvox.BrailleInterface.prototype, | |
| 113 write: this.addBraille_.bind(this) | |
| 114 }; | |
| 115 cvox.ChromeVox.braille = new MockBraille(); | |
| 116 }, | |
| 117 | |
| 118 /** | |
| 119 * Adds an expectation for a spoken utterance. | |
|
dmazzoni
2015/08/24 19:05:06
Or utterances?
| |
| 120 * @param {...string} var_args | |
|
dmazzoni
2015/08/24 19:05:06
Document this, i.e. clarify that each argument is
| |
| 121 * @return {MockFeedback} |this| for chaining | |
| 122 */ | |
| 123 expectSpeech: function() { | |
| 124 assertFalse(this.replaying_); | |
| 125 Array.prototype.forEach.call(arguments, function(text) { | |
| 126 this.pendingActions_.push({ | |
| 127 perform: function() { | |
| 128 return !!MockFeedback.matchAndConsume_( | |
| 129 text, {}, this.pendingUtterances_); | |
| 130 }.bind(this), | |
| 131 describe: function() { return 'Speak \'' + text + '\''; } | |
|
dmazzoni
2015/08/24 19:05:06
Could this be toString()?
| |
| 132 }); | |
| 133 }.bind(this)); | |
| 134 return this; | |
| 135 }, | |
| 136 | |
| 137 /** | |
| 138 * Adds an expectation for braille output. | |
| 139 * @param {string|RegExp} text | |
| 140 * @param {Object=} opt_props Additional properties to match in the | |
| 141 * |NavBraille| | |
| 142 * @return {MockFeedback} |this| for chaining | |
| 143 */ | |
| 144 expectBraille: function(text, opt_props) { | |
| 145 assertFalse(this.replaying_); | |
| 146 var props = opt_props || {}; | |
| 147 this.pendingActions_.push({ | |
| 148 perform: function() { | |
| 149 var match = MockFeedback.matchAndConsume_( | |
| 150 text, props, this.pendingBraille_); | |
| 151 if (match) | |
| 152 this.lastMatchedBraille_ = match; | |
| 153 return !!match; | |
| 154 }.bind(this), | |
| 155 describe: function() { | |
| 156 return 'Braille \'' + text + '\' ' + JSON.stringify(props); | |
| 157 } | |
| 158 }); | |
| 159 return this; | |
| 160 }, | |
| 161 | |
| 162 /** | |
| 163 * Arranges for a callback to be invoked when all expectations that were | |
| 164 * added before this call have been met. Callbacks are called in the | |
| 165 * order they are added. | |
| 166 * @param {Function} callback | |
| 167 * @return {MockFeedback} |this| for chaining | |
| 168 */ | |
| 169 call: function(callback) { | |
| 170 assertFalse(this.replaying_); | |
| 171 this.pendingActions_.push({ | |
| 172 perform: function() { | |
| 173 callback(); | |
| 174 return true; | |
| 175 }, | |
| 176 describe: function() { | |
| 177 return 'Callback'; | |
| 178 } | |
| 179 }); | |
| 180 return this; | |
| 181 }, | |
| 182 | |
| 183 /** | |
| 184 * Processes any feedback that has been received so far and treis to | |
| 185 * satisfy the registered expectations. Any feedback that is received | |
| 186 * after this call (via the installed mock objects) is processed immediately. | |
| 187 * When all expectations are satisfied and registered callbacks called, | |
| 188 * the finish callbcak, if any, is called. | |
| 189 * This function may only be called once. | |
| 190 */ | |
| 191 replay: function() { | |
|
dmazzoni
2015/08/24 19:05:06
Do you think it would be possible to raise an exce
| |
| 192 assertFalse(this.replaying_); | |
| 193 this.replaying_ = true; | |
| 194 this.process_(); | |
| 195 }, | |
| 196 | |
| 197 /** | |
| 198 * Returns the |NavBraille| that matched an expectation. This is | |
| 199 * intended to be used by a callback to invoke braille commands that | |
| 200 * depend on display contents. | |
| 201 * @type {cvox.NavBraille} | |
| 202 */ | |
| 203 get lastMatchedBraille() { | |
| 204 assertTrue(this.replaying_); | |
| 205 return this.lastMatchedBraille_; | |
| 206 }, | |
| 207 | |
| 208 /** | |
| 209 * @param {string} textSTring | |
|
dmazzoni
2015/08/24 19:05:07
textString
| |
| 210 * @param {cvox.QueueMode} queueMode | |
| 211 * @param {Object=} properties | |
| 212 * @private | |
| 213 */ | |
| 214 addUtterance_: function(textString, queueMode, properties) { | |
| 215 this.pendingUtterances_.push( | |
| 216 {text: textString, | |
| 217 callback: properties ? properties.endCallback : undefined}); | |
|
dmazzoni
2015/08/24 19:05:07
Do you need to handle startCallback too?
If we wa
| |
| 218 this.process_(); | |
| 219 }, | |
| 220 | |
| 221 /** @private */ | |
| 222 addBraille_: function(navBraille) { | |
| 223 this.pendingBraille_.push(navBraille); | |
| 224 this.process_(); | |
| 225 }, | |
| 226 | |
| 227 /*** @private */ | |
| 228 process_: function() { | |
| 229 if (!this.replaying_ || this.inProcess_) | |
| 230 return; | |
| 231 try { | |
| 232 this.inProcess_ = true; | |
| 233 while (this.pendingActions_.length > 0) { | |
| 234 var action = this.pendingActions_[0]; | |
| 235 if (action.perform()) { | |
| 236 this.pendingActions_.shift(); | |
| 237 if (this.logTimeoutId_) { | |
| 238 window.clearTimeout(this.logTimeoutId_); | |
| 239 this.logTimeoutId_ = 0; | |
| 240 } | |
| 241 } else { | |
| 242 break; | |
| 243 } | |
| 244 } | |
| 245 if (this.pendingActions_.length == 0) { | |
| 246 if (this.finishedCallback_) { | |
| 247 this.finishedCallback_(); | |
| 248 this.finishedCallback_ = null; | |
| 249 } | |
| 250 } else { | |
| 251 // If there are pending actions and no matching feedback for a few | |
| 252 // seconds, log the pending state to ease debugging. | |
| 253 if (!this.logTimeoutId_) { | |
| 254 this.logTimeoutId_ = window.setTimeout( | |
| 255 this.logPendingState_.bind(this), 2000); | |
| 256 } | |
| 257 } | |
| 258 } finally { | |
| 259 this.inProcess_ = false; | |
| 260 } | |
| 261 }, | |
| 262 | |
| 263 /** @private */ | |
| 264 logPendingState_: function() { | |
| 265 if (this.pendingActions_.length > 0) | |
| 266 console.log('Still waiting for ' + this.pendingActions_[0].describe()); | |
| 267 function logPending(desc, list) { | |
| 268 if (list.length > 0) | |
| 269 console.log('Pending ' + desc + ':\n ' + | |
| 270 list.map(function(i) { | |
| 271 var ret = '\'' + i.text + '\''; | |
| 272 if ('startIndex' in i) | |
| 273 ret += ' startIndex=' + i.startIndex; | |
| 274 if ('endIndex' in i) | |
| 275 ret += ' endIndex=' + i.endIndex; | |
| 276 return ret; | |
| 277 }).join('\n ') + '\n '); | |
| 278 } | |
| 279 logPending('speech utterances', this.pendingUtterances_); | |
| 280 logPending('braille', this.pendingBraille_); | |
| 281 this.logTimeoutId_ = 0; | |
| 282 }, | |
| 283 }; | |
| 284 | |
| 285 /** | |
| 286 * @param {string} text | |
| 287 * @param {Object} props | |
| 288 * @param {Array<{text: (string|RegExp), callback: (function|undefined)}>} | |
| 289 * pending | |
| 290 * @return {Object} | |
| 291 * @private | |
| 292 */ | |
| 293 MockFeedback.matchAndConsume_ = function(text, props, pending) { | |
| 294 for (var i = 0, candidate; candidate = pending[i]; ++i) { | |
|
dmazzoni
2015/08/24 19:05:07
This seems to allow additional utterances - for ex
| |
| 295 var candidateText = candidate.text.toString(); | |
| 296 if (text === candidateText || | |
| 297 (text instanceof RegExp && text.test(candidateText))) { | |
| 298 var matched = true; | |
| 299 for (prop in props) { | |
| 300 if (candidate[prop] !== props[prop]) { | |
| 301 matched = false; | |
| 302 break; | |
| 303 } | |
| 304 } | |
| 305 if (matched) | |
| 306 break; | |
| 307 } | |
| 308 } | |
| 309 if (candidate) { | |
| 310 var consumed = pending.splice(0, i + 1); | |
| 311 consumed.forEach(function(item) { | |
| 312 if (item.callback) | |
| 313 item.callback(); | |
| 314 }); | |
| 315 } | |
| 316 return candidate; | |
| 317 }; | |
| 318 | |
| 319 /** | |
| 320 * Returns its first argumennt. Moved out of the cosntructor to satisfy the | |
|
dmazzoni
2015/08/24 19:05:06
constructor
| |
| 321 * js linter. | |
| 322 * @param {*} a | |
| 323 * @return {*} | |
| 324 * @private | |
| 325 */ | |
| 326 MockFeedback.identityFunction_ = function(a) { | |
| 327 return a; | |
| 328 }; | |
| OLD | NEW |