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 |