Add new certificateProvider extension API.
[chromium-blink-merge.git] / chrome / browser / resources / chromeos / chromevox / testing / mock_feedback.js
blob795f268bc20ceb8bea6010ff034b15cc067df925
1 // Copyright 2015 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.
5 /**
6  * @fileoverview This file contains the |MockFeedback| class which is a
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  * Expectations can be added using the |expectSpeech| and |expectBraille|
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.
30  *
31  * This mock class is lean, meaning that feedback that doesn't match
32  * any expectations is silently ignored.
33  *
34  * NOTE: for asynchronous tests, the processing will never finish if there
35  * are unmet expectations.  To help debugging in such situations, the mock
36  * will output its pending state if there are pending expectations and no
37  * output is received within a few seconds.
38  *
39  * See mock_feedback_test.js for example usage of this class.
40  */
42 /**
43  * Combined mock class for braille and speech output.
44  * @param {function=} opt_finishedCallback Called when all expectations have
45  *     been met.
46  * @constructor
47  */
48 var MockFeedback = function(opt_finishedCallback) {
49   /**
50    * @type {function}
51    * @private
52    */
53   this.finishedCallback_ = opt_finishedCallback || null;
54   /**
55    * True when |replay| has been called and actions are being replayed.
56    * @type {boolean}
57    * @private
58    */
59   this.replaying_ = false;
60   /**
61    * True when inside the |process| function to prevent nested calls.
62    * @type {boolean}
63    * @private
64    */
65   this.inProcess_ = false;
66   /**
67    * Pending expectations and callbacks.
68    * @type {Array<{perform: function(): boolean, toString: function(): string}>}
69    * @private
70    */
71   this.pendingActions_ = [];
72   /**
73    * Pending speech utterances.
74    * @type {Array<{text: string, callback: (function|undefined)}>}
75    * @private
76    */
77   this.pendingUtterances_ = [];
78   /**
79    * Pending braille output.
80    * @type {Array<{text: string, callback: (function|undefined)}>}
81    * @private
82    */
83   this.pendingBraille_ = [];
84   /**
85    * Handle for the timeout set for debug logging.
86    * @type {number}
87    * @private
88    */
89   this.logTimeoutId_ = 0;
90   /**
91    * @type {cvox.NavBraille}
92    * @private
93    */
94   this.lastMatchedBraille_ = null;
97 MockFeedback.prototype = {
99   /**
100    * Install mock objects as |cvox.ChromeVox.tts| and |cvox.ChromeVox.braille|
101    * to collect feedback.
102    */
103   install: function() {
104     assertFalse(this.replaying_);
106     var MockTts = function() {};
107     MockTts.prototype = {
108       __proto__: cvox.TtsInterface.prototype,
109       speak: this.addUtterance_.bind(this)
110     };
112     cvox.ChromeVox.tts = new MockTts();
114     var MockBraille = function() {};
115     MockBraille.prototype = {
116       __proto__: cvox.BrailleInterface.prototype,
117       write: this.addBraille_.bind(this)
118     };
120     cvox.ChromeVox.braille = new MockBraille();
121   },
123   /**
124    * Adds an expectation for one or more spoken utterances.
125    * @param {...(string|RegExp)} var_args One or more utterance to add as
126    *     expectations.
127    * @return {MockFeedback} |this| for chaining
128    */
129   expectSpeech: function() {
130     assertFalse(this.replaying_);
131     Array.prototype.forEach.call(arguments, function(text) {
132       this.pendingActions_.push({
133         perform: function() {
134           return !!MockFeedback.matchAndConsume_(
135               text, {}, this.pendingUtterances_);
136         }.bind(this),
137         toString: function() { return 'Speak \'' + text + '\''; }
138       });
139     }.bind(this));
140     return this;
141   },
143   /**
144    * Adds an expectation for braille output.
145    * @param {string|RegExp} text
146    * @param {Object=} opt_props Additional properties to match in the
147    *     |NavBraille|
148    * @return {MockFeedback} |this| for chaining
149    */
150   expectBraille: function(text, opt_props) {
151     assertFalse(this.replaying_);
152     var props = opt_props || {};
153     this.pendingActions_.push({
154       perform: function() {
155         var match = MockFeedback.matchAndConsume_(
156             text, props, this.pendingBraille_);
157         if (match)
158           this.lastMatchedBraille_ = match;
159         return !!match;
160       }.bind(this),
161       toString: function() {
162         return 'Braille \'' + text + '\' ' + JSON.stringify(props);
163       }
164     });
165     return this;
166   },
168   /**
169    * Arranges for a callback to be invoked when all expectations that were
170    * added before this call have been met.  Callbacks are called in the
171    * order they are added.
172    * @param {Function} callback
173    * @return {MockFeedback} |this| for chaining
174    */
175   call: function(callback) {
176     assertFalse(this.replaying_);
177     this.pendingActions_.push({
178       perform: function() {
179         callback();
180         return true;
181       },
182       toString: function() {
183         return 'Callback';
184       }
185     });
186     return this;
187   },
189   /**
190    * Processes any feedback that has been received so far and treis to
191    * satisfy the registered expectations.  Any feedback that is received
192    * after this call (via the installed mock objects) is processed immediately.
193    * When all expectations are satisfied and registered callbacks called,
194    * the finish callbcak, if any, is called.
195    * This function may only be called once.
196    */
197   replay: function() {
198     assertFalse(this.replaying_);
199     this.replaying_ = true;
200     this.process_();
201   },
203   /**
204    * Returns the |NavBraille| that matched an expectation.  This is
205    * intended to be used by a callback to invoke braille commands that
206    * depend on display contents.
207    * @type {cvox.NavBraille}
208    */
209   get lastMatchedBraille() {
210     assertTrue(this.replaying_);
211     return this.lastMatchedBraille_;
212   },
214   /**
215    * @param {string} textString
216    * @param {cvox.QueueMode} queueMode
217    * @param {Object=} properties
218    * @private
219    */
220   addUtterance_: function(textString, queueMode, properties) {
221     var callback;
222     if (properties && (properties.startCallback || properties.endCallback)) {
223       var startCallback = properties.startCallback;
224       var endCallback = properties.endCallback;
225       callback = function() {
226         startCallback && startCallback();
227         endCallback && endCallback();
228       };
229     }
230     this.pendingUtterances_.push(
231         {text: textString,
232          callback: callback});
233     this.process_();
234   },
236   /** @private */
237   addBraille_: function(navBraille) {
238     this.pendingBraille_.push(navBraille);
239     this.process_();
240   },
242   /*** @private */
243   process_: function() {
244     if (!this.replaying_ || this.inProcess_)
245       return;
246     try {
247       this.inProcess_ = true;
248       while (this.pendingActions_.length > 0) {
249         var action = this.pendingActions_[0];
250         if (action.perform()) {
251           this.pendingActions_.shift();
252           if (this.logTimeoutId_) {
253             window.clearTimeout(this.logTimeoutId_);
254             this.logTimeoutId_ = 0;
255           }
256         } else {
257           break;
258         }
259       }
260       if (this.pendingActions_.length == 0) {
261         if (this.finishedCallback_) {
262           this.finishedCallback_();
263           this.finishedCallback_ = null;
264         }
265       } else {
266         // If there are pending actions and no matching feedback for a few
267         // seconds, log the pending state to ease debugging.
268         if (!this.logTimeoutId_) {
269           this.logTimeoutId_ = window.setTimeout(
270               this.logPendingState_.bind(this), 2000);
271         }
272       }
273     } finally {
274       this.inProcess_ = false;
275     }
276   },
278   /** @private */
279   logPendingState_: function() {
280     if (this.pendingActions_.length > 0)
281       console.log('Still waiting for ' + this.pendingActions_[0].toString());
282     function logPending(desc, list) {
283       if (list.length > 0)
284         console.log('Pending ' + desc + ':\n  ' +
285             list.map(function(i) {
286               var ret = '\'' + i.text + '\'';
287               if ('startIndex' in i)
288                 ret += ' startIndex=' + i.startIndex;
289               if ('endIndex' in i)
290                 ret += ' endIndex=' + i.endIndex;
291               return ret;
292             }).join('\n  ') + '\n  ');
293     }
294     logPending('speech utterances', this.pendingUtterances_);
295     logPending('braille', this.pendingBraille_);
296     this.logTimeoutId_ = 0;
297   },
301  * @param {string} text
302  * @param {Object} props
303  * @param {Array<{text: (string|RegExp), callback: (function|undefined)}>}
304  *     pending
305  * @return {Object}
306  * @private
307  */
308 MockFeedback.matchAndConsume_ = function(text, props, pending) {
309   for (var i = 0, candidate; candidate = pending[i]; ++i) {
310     var candidateText = candidate.text.toString();
311     if (text === candidateText ||
312         (text instanceof RegExp && text.test(candidateText))) {
313       var matched = true;
314       for (prop in props) {
315         if (candidate[prop] !== props[prop]) {
316           matched = false;
317           break;
318         }
319       }
320       if (matched)
321         break;
322     }
323   }
324   if (candidate) {
325     var consumed = pending.splice(0, i + 1);
326     consumed.forEach(function(item) {
327       if (item.callback)
328         item.callback();
329     });
330   }
331   return candidate;