Index: chrome/browser/resources/chromeos/chromevox/testing/mock_feedback.js |
diff --git a/chrome/browser/resources/chromeos/chromevox/testing/mock_feedback.js b/chrome/browser/resources/chromeos/chromevox/testing/mock_feedback.js |
new file mode 100644 |
index 0000000000000000000000000000000000000000..2a770344897e1a3f56c2ce68cb6c6e7604104669 |
--- /dev/null |
+++ b/chrome/browser/resources/chromeos/chromevox/testing/mock_feedback.js |
@@ -0,0 +1,328 @@ |
+// Copyright 2014 The Chromium Authors. All rights reserved. |
dmazzoni
2015/08/24 19:05:07
2015?
|
+// Use of this source code is governed by a BSD-style license that can be |
+// found in the LICENSE file. |
+ |
+/** |
+ * @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
|
+ * combined mock class for speech and braille feedback. A test that uses |
+ * this class may add expectations for speech utterances and braille display |
+ * content to be output. The |install| method sets appropriate mock classes |
+ * as the |cvox.ChromeVox.tts| and |cvox.ChromeVox.braille| objects, |
+ * respectively. Output sent to those objects will then be collected in |
+ * an internal queue. |
+ * |
+ * Expectaations can be added using the |expectSpeech| and |expectBraille| |
dmazzoni
2015/08/24 19:05:06
Expectaations -> Expectations
|
+ * methods. These methods take either strings or regular expressions to match |
+ * against. Strings must match a full utterance (or display content) exactly, |
+ * while a regular expression must match a substring (use anchor operators if |
+ * needed). |
+ * |
+ * Function calls may be inserted in the stream of expectations using the |
+ * |call| method. Such callbacks are called after all preceding expectations |
+ * have been met, and before any further expectations are matched. Callbacks |
+ * are called in the order they were added to the mock. |
+ * |
+ * The |replay| method starts processing any pending utterances and braille |
+ * display content and will try to match expectations as new feedback enters |
+ * the queue asynchronously. When all expectations have been met and callbacks |
+ * called, the finish callback, if any was provided to the constructor, is |
+ * called. The mock is lean, meaning that feedback that doesn't match |
+ * any expectations is silently ignored. |
+ * |
+ * NOTE: for asynchronous tests, the processing will never finish if there |
+ * are unmet expectations. To help debugging in such situations, the mock |
+ * will output its pending state if there are pending expectations and no |
+ * output is received within a few seconds. |
+ * |
+ * See mock_feedback_test.js for example usage of this class. |
+ */ |
+ |
+/** |
+ * Combined mock class for braille and speech output. |
+ * @param {function=} opt_finishedCallback Called when all expectaions have |
dmazzoni
2015/08/24 19:05:06
expectations
|
+ * been met. |
+ * @constructor |
+ */ |
+var MockFeedback = function(opt_finishedCallback) { |
+ /** |
+ * @type {function} |
+ * @private |
+ */ |
+ this.finishedCallback_ = opt_finishedCallback || null; |
+ /** |
+ * True when |replay| has been called and actions are being replayed. |
+ * @type {boolean} |
+ * @private |
+ */ |
+ this.replaying_ = false; |
+ /** |
+ * True when inside the |process| function to prevent nested calls. |
+ * @type {boolean} |
+ * @private |
+ */ |
+ this.inProcess_ = false; |
+ /** |
+ * Pending expectations and callbacks. |
+ * @type {Array<{perform: function(): boolean, describe: function(): string}>} |
+ * @private |
+ */ |
+ this.pendingActions_ = []; |
+ /** |
+ * Pending speech utterances. |
+ * @type {Array<{text: string, callback: (function|undefined)}>} |
+ * @private |
+ */ |
+ this.pendingUtterances_ = []; |
+ /** |
+ * Pending braille output. |
+ * @type {Array<{text: string, callback: (function|undefined)}>} |
+ * @private |
+ */ |
+ this.pendingBraille_ = []; |
+ /** |
+ * Handle for the timeout set for debug logging. |
+ * @type {number} |
+ * @private |
+ */ |
+ this.logTimeoutId_ = 0; |
+ /** |
+ * @type {cvox.NavBraille} |
+ * @private |
+ */ |
+ this.lastMatchedBraille_ = null; |
+}; |
+ |
+MockFeedback.prototype = { |
+ |
+ /** |
+ * Install mock objects as |cvox.CrhomeVox.tts| and |cvox.ChromeVox.braille| |
dmazzoni
2015/08/24 19:05:07
ChromeVox
|
+ * to collect feedback. |
+ */ |
+ install: function() { |
+ assertFalse(this.replaying_); |
+ function MockTts() {} |
dmazzoni
2015/08/24 19:05:07
I know this is legal syntax but it looks funny, li
|
+ MockTts.prototype = { |
+ __proto__: cvox.TtsInterface.prototype, |
+ speak: this.addUtterance_.bind(this) |
+ }; |
+ cvox.ChromeVox.tts = new MockTts(); |
+ function MockBraille() { |
+ } |
+ MockBraille.prototype = { |
+ __proto__: cvox.BrailleInterface.prototype, |
+ write: this.addBraille_.bind(this) |
+ }; |
+ cvox.ChromeVox.braille = new MockBraille(); |
+ }, |
+ |
+ /** |
+ * Adds an expectation for a spoken utterance. |
dmazzoni
2015/08/24 19:05:06
Or utterances?
|
+ * @param {...string} var_args |
dmazzoni
2015/08/24 19:05:06
Document this, i.e. clarify that each argument is
|
+ * @return {MockFeedback} |this| for chaining |
+ */ |
+ expectSpeech: function() { |
+ assertFalse(this.replaying_); |
+ Array.prototype.forEach.call(arguments, function(text) { |
+ this.pendingActions_.push({ |
+ perform: function() { |
+ return !!MockFeedback.matchAndConsume_( |
+ text, {}, this.pendingUtterances_); |
+ }.bind(this), |
+ describe: function() { return 'Speak \'' + text + '\''; } |
dmazzoni
2015/08/24 19:05:06
Could this be toString()?
|
+ }); |
+ }.bind(this)); |
+ return this; |
+ }, |
+ |
+ /** |
+ * Adds an expectation for braille output. |
+ * @param {string|RegExp} text |
+ * @param {Object=} opt_props Additional properties to match in the |
+ * |NavBraille| |
+ * @return {MockFeedback} |this| for chaining |
+ */ |
+ expectBraille: function(text, opt_props) { |
+ assertFalse(this.replaying_); |
+ var props = opt_props || {}; |
+ this.pendingActions_.push({ |
+ perform: function() { |
+ var match = MockFeedback.matchAndConsume_( |
+ text, props, this.pendingBraille_); |
+ if (match) |
+ this.lastMatchedBraille_ = match; |
+ return !!match; |
+ }.bind(this), |
+ describe: function() { |
+ return 'Braille \'' + text + '\' ' + JSON.stringify(props); |
+ } |
+ }); |
+ return this; |
+ }, |
+ |
+ /** |
+ * Arranges for a callback to be invoked when all expectations that were |
+ * added before this call have been met. Callbacks are called in the |
+ * order they are added. |
+ * @param {Function} callback |
+ * @return {MockFeedback} |this| for chaining |
+ */ |
+ call: function(callback) { |
+ assertFalse(this.replaying_); |
+ this.pendingActions_.push({ |
+ perform: function() { |
+ callback(); |
+ return true; |
+ }, |
+ describe: function() { |
+ return 'Callback'; |
+ } |
+ }); |
+ return this; |
+ }, |
+ |
+ /** |
+ * Processes any feedback that has been received so far and treis to |
+ * satisfy the registered expectations. Any feedback that is received |
+ * after this call (via the installed mock objects) is processed immediately. |
+ * When all expectations are satisfied and registered callbacks called, |
+ * the finish callbcak, if any, is called. |
+ * This function may only be called once. |
+ */ |
+ replay: function() { |
dmazzoni
2015/08/24 19:05:06
Do you think it would be possible to raise an exce
|
+ assertFalse(this.replaying_); |
+ this.replaying_ = true; |
+ this.process_(); |
+ }, |
+ |
+ /** |
+ * Returns the |NavBraille| that matched an expectation. This is |
+ * intended to be used by a callback to invoke braille commands that |
+ * depend on display contents. |
+ * @type {cvox.NavBraille} |
+ */ |
+ get lastMatchedBraille() { |
+ assertTrue(this.replaying_); |
+ return this.lastMatchedBraille_; |
+ }, |
+ |
+ /** |
+ * @param {string} textSTring |
dmazzoni
2015/08/24 19:05:07
textString
|
+ * @param {cvox.QueueMode} queueMode |
+ * @param {Object=} properties |
+ * @private |
+ */ |
+ addUtterance_: function(textString, queueMode, properties) { |
+ this.pendingUtterances_.push( |
+ {text: textString, |
+ callback: properties ? properties.endCallback : undefined}); |
dmazzoni
2015/08/24 19:05:07
Do you need to handle startCallback too?
If we wa
|
+ this.process_(); |
+ }, |
+ |
+ /** @private */ |
+ addBraille_: function(navBraille) { |
+ this.pendingBraille_.push(navBraille); |
+ this.process_(); |
+ }, |
+ |
+ /*** @private */ |
+ process_: function() { |
+ if (!this.replaying_ || this.inProcess_) |
+ return; |
+ try { |
+ this.inProcess_ = true; |
+ while (this.pendingActions_.length > 0) { |
+ var action = this.pendingActions_[0]; |
+ if (action.perform()) { |
+ this.pendingActions_.shift(); |
+ if (this.logTimeoutId_) { |
+ window.clearTimeout(this.logTimeoutId_); |
+ this.logTimeoutId_ = 0; |
+ } |
+ } else { |
+ break; |
+ } |
+ } |
+ if (this.pendingActions_.length == 0) { |
+ if (this.finishedCallback_) { |
+ this.finishedCallback_(); |
+ this.finishedCallback_ = null; |
+ } |
+ } else { |
+ // If there are pending actions and no matching feedback for a few |
+ // seconds, log the pending state to ease debugging. |
+ if (!this.logTimeoutId_) { |
+ this.logTimeoutId_ = window.setTimeout( |
+ this.logPendingState_.bind(this), 2000); |
+ } |
+ } |
+ } finally { |
+ this.inProcess_ = false; |
+ } |
+ }, |
+ |
+ /** @private */ |
+ logPendingState_: function() { |
+ if (this.pendingActions_.length > 0) |
+ console.log('Still waiting for ' + this.pendingActions_[0].describe()); |
+ function logPending(desc, list) { |
+ if (list.length > 0) |
+ console.log('Pending ' + desc + ':\n ' + |
+ list.map(function(i) { |
+ var ret = '\'' + i.text + '\''; |
+ if ('startIndex' in i) |
+ ret += ' startIndex=' + i.startIndex; |
+ if ('endIndex' in i) |
+ ret += ' endIndex=' + i.endIndex; |
+ return ret; |
+ }).join('\n ') + '\n '); |
+ } |
+ logPending('speech utterances', this.pendingUtterances_); |
+ logPending('braille', this.pendingBraille_); |
+ this.logTimeoutId_ = 0; |
+ }, |
+}; |
+ |
+/** |
+ * @param {string} text |
+ * @param {Object} props |
+ * @param {Array<{text: (string|RegExp), callback: (function|undefined)}>} |
+ * pending |
+ * @return {Object} |
+ * @private |
+ */ |
+MockFeedback.matchAndConsume_ = function(text, props, pending) { |
+ for (var i = 0, candidate; candidate = pending[i]; ++i) { |
dmazzoni
2015/08/24 19:05:07
This seems to allow additional utterances - for ex
|
+ var candidateText = candidate.text.toString(); |
+ if (text === candidateText || |
+ (text instanceof RegExp && text.test(candidateText))) { |
+ var matched = true; |
+ for (prop in props) { |
+ if (candidate[prop] !== props[prop]) { |
+ matched = false; |
+ break; |
+ } |
+ } |
+ if (matched) |
+ break; |
+ } |
+ } |
+ if (candidate) { |
+ var consumed = pending.splice(0, i + 1); |
+ consumed.forEach(function(item) { |
+ if (item.callback) |
+ item.callback(); |
+ }); |
+ } |
+ return candidate; |
+}; |
+ |
+/** |
+ * Returns its first argumennt. Moved out of the cosntructor to satisfy the |
dmazzoni
2015/08/24 19:05:06
constructor
|
+ * js linter. |
+ * @param {*} a |
+ * @return {*} |
+ * @private |
+ */ |
+MockFeedback.identityFunction_ = function(a) { |
+ return a; |
+}; |