OLD | NEW |
(Empty) | |
| 1 // Copyright 2014 The Chromium Authors. All rights reserved. |
| 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 * Combined mock class for braille and speech output. |
| 7 * Allows a test to set up expectations for text to be sent to tts and |
| 8 * the braille display with intermingled callbacks to drive the test. |
| 9 * @param {Function?} opt_finishedCallback Called when all expectaions have |
| 10 * been met. |
| 11 * @constructor |
| 12 */ |
| 13 var MockFeedback = function(opt_finishedCallback) { |
| 14 /** |
| 15 * @type {!Function} |
| 16 * @private |
| 17 */ |
| 18 this.finishedCallback_ = opt_finishedCallback || function() {}; |
| 19 /** |
| 20 * True when |go| has been called and actions are being replayed. |
| 21 * @type {boolean} |
| 22 * @private |
| 23 */ |
| 24 this.going_ = false; |
| 25 /** |
| 26 * True when inside the |process| function to prevent nested calls. |
| 27 * @type {boolean} |
| 28 * @private |
| 29 */ |
| 30 this.inProcess_ = false; |
| 31 /** |
| 32 * Pending expectations and callbacks. |
| 33 * @type {Array<{perform: function(): boolean, describe: function(): string}>} |
| 34 * @private |
| 35 */ |
| 36 this.pendingActions_ = []; |
| 37 /** |
| 38 * Pending speech utterances. |
| 39 * @type {Array<{text: string, callback: Function|undefined}>} |
| 40 * @private |
| 41 */ |
| 42 this.pendingUtterances_ = []; |
| 43 /** |
| 44 * Pending braille output. |
| 45 * @type {Array<{text: string, callback: Function|undefined}>} |
| 46 * @private |
| 47 */ |
| 48 this.pendingBraille_ = []; |
| 49 /** |
| 50 * Handle for the timeout set for debug logging. |
| 51 * @type {number} |
| 52 * @private |
| 53 */ |
| 54 this.logTimeout_ = 0; |
| 55 }; |
| 56 |
| 57 MockFeedback.prototype = { |
| 58 |
| 59 /** |
| 60 * Install mock objects as |cvox.CrhomeVox.tts| and |cvox.ChromeVox.braille| |
| 61 * to collect feedback. |
| 62 */ |
| 63 install: function() { |
| 64 assertFalse(this.going_); |
| 65 function MockTts() {} |
| 66 MockTts.prototype = { |
| 67 __proto__: cvox.TtsInterface.prototype, |
| 68 speak: this.addUtterance_.bind(this) |
| 69 }; |
| 70 cvox.ChromeVox.tts = new MockTts(); |
| 71 function MockBraille() { |
| 72 } |
| 73 MockBraille.prototype = { |
| 74 __proto__: cvox.BrailleInterface.prototype, |
| 75 write: this.addBraille_.bind(this) |
| 76 }; |
| 77 cvox.ChromeVox.braille = new MockBraille(); |
| 78 }, |
| 79 |
| 80 /** |
| 81 * Adds an expectation for a spoken utterance. |
| 82 * @param {string} text |
| 83 * @return {MockFeedback} |this| for chaining |
| 84 */ |
| 85 expectSpeech: function(text) { |
| 86 assertFalse(this.going_); |
| 87 this.pendingActions_.push({ |
| 88 perform: MockFeedback.matchAndConsume_.bind( |
| 89 null, text, this.pendingUtterances_), |
| 90 describe: function() { return 'Speak ' + text; } |
| 91 }); |
| 92 return this; |
| 93 }, |
| 94 |
| 95 /** |
| 96 * Adds an expectation for braille output. |
| 97 * @param {string} text |
| 98 * @return {MockFeedback} |this| for chaining |
| 99 */ |
| 100 expectBraille: function(text) { |
| 101 assertFalse(this.going_); |
| 102 this.pendingActions_.push({ |
| 103 perform: MockFeedback.matchAndConsume_.bind( |
| 104 null, text, this.pendingBraille_), |
| 105 describe: function() { return 'Braille ' + text; } |
| 106 }); |
| 107 return this; |
| 108 }, |
| 109 |
| 110 /** |
| 111 * Arranges for a callback to be invoked when all expectations that were |
| 112 * added before this call have been met. Callbacks are called in the |
| 113 * order they are added. |
| 114 * @param {Function} callback |
| 115 * @return {MockFeedback} |this| for chaining |
| 116 */ |
| 117 call: function(callback) { |
| 118 assertFalse(this.going_); |
| 119 this.pendingActions_.push({ |
| 120 perform: function() { |
| 121 callback(); |
| 122 return true; |
| 123 }, |
| 124 describe: function() { |
| 125 return 'Callback'; |
| 126 } |
| 127 }); |
| 128 return this; |
| 129 }, |
| 130 |
| 131 /** |
| 132 * Processes any feedback that has been received so far and treis to |
| 133 * satisfy the registered expectations. Any feedback that is received |
| 134 * after this call (via the installed mock objects) is processed immediately. |
| 135 * When all expectations are satisfied and registered callbacks called, |
| 136 * the finish callbcak, if any, is called. |
| 137 * This function may only be called once. |
| 138 */ |
| 139 go: function() { |
| 140 assertFalse(this.going_); |
| 141 this.going_ = true; |
| 142 this.process_(); |
| 143 }, |
| 144 |
| 145 /** |
| 146 * @param {string} textSTring |
| 147 * @param {cvox.QueueMode} queueMode |
| 148 * @param {Object=} properties |
| 149 * @private |
| 150 */ |
| 151 addUtterance_: function(textString, queueMode, properties) { |
| 152 this.pendingUtterances_.push( |
| 153 {text: textString, |
| 154 callback: properties ? properties.endCallback : undefined}); |
| 155 this.process_(); |
| 156 }, |
| 157 |
| 158 /** @private */ |
| 159 addBraille_: function(navBraille) { |
| 160 this.pendingBraille_.push({text: navBraille.text.toString()}); |
| 161 this.process_(); |
| 162 }, |
| 163 |
| 164 /*** @private */ |
| 165 process_: function() { |
| 166 if (!this.going_ || this.inProcess_) |
| 167 return; |
| 168 try { |
| 169 this.inProcess_ = true; |
| 170 while (this.pendingActions_.length > 0) { |
| 171 var action = this.pendingActions_[0]; |
| 172 if (action.perform()) { |
| 173 this.pendingActions_.shift(); |
| 174 if (this.logTimeout_) { |
| 175 window.clearTimeout(this.logTimeout_); |
| 176 this.debugTimeout = 0; |
| 177 } |
| 178 } else { |
| 179 break; |
| 180 } |
| 181 } |
| 182 if (this.pendingActions_.length == 0) { |
| 183 this.finishedCallback_(); |
| 184 } else { |
| 185 // If there are pending actions and no feedback for a few seconds, |
| 186 // log the pending state to ease debugging. |
| 187 if (!this.logTimeout_) { |
| 188 this.logTimeout_ = window.setTimeout( |
| 189 this.logPendingState_.bind(this), 2000); |
| 190 } |
| 191 } |
| 192 } finally { |
| 193 this.inProcess_ = false; |
| 194 } |
| 195 }, |
| 196 |
| 197 /** @private */ |
| 198 logPendingState_: function() { |
| 199 if (this.pendingActions_.length > 0) |
| 200 console.log('Still waiting for ' + this.pendingActions_[0].describe()); |
| 201 function logPending(desc, list) { |
| 202 if (list.length > 0) |
| 203 console.log('Pending ' + desc + ':\n ' + |
| 204 list.map(function(i) {return i.text;}).join('\n ') + '\n '); |
| 205 } |
| 206 logPending('speech utterances', this.pendingUtterances_); |
| 207 logPending('braille', this.pendingBraille_); |
| 208 this.logTimeout_ = 0; |
| 209 }, |
| 210 }; |
| 211 |
| 212 /** |
| 213 * @param {string} text |
| 214 * @param {Array<{text: string|RegExp, callback: Function|undefined}>} pending |
| 215 * @return {boolean} |
| 216 * @private |
| 217 */ |
| 218 MockFeedback.matchAndConsume_ = function(text, pending) { |
| 219 for (var i = 0, candidate; candidate = pending[i]; ++i) { |
| 220 if (text === candidate.text || |
| 221 (text instanceof RegExp && text.test(candidate.text))) |
| 222 break; |
| 223 } |
| 224 if (candidate) { |
| 225 var consumed = pending.splice(0, i + 1); |
| 226 consumed.forEach(function(item) { |
| 227 if (item.callback) |
| 228 item.callback(); |
| 229 }); |
| 230 return true; |
| 231 } |
| 232 return false; |
| 233 }; |
OLD | NEW |