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..128d5551ce2505e034e0e05658d9e3f054547bdf |
--- /dev/null |
+++ b/chrome/browser/resources/chromeos/chromevox/testing/mock_feedback.js |
@@ -0,0 +1,233 @@ |
+// Copyright 2014 The Chromium Authors. All rights reserved. |
+// Use of this source code is governed by a BSD-style license that can be |
+// found in the LICENSE file. |
+ |
+/** |
+ * Combined mock class for braille and speech output. |
+ * Allows a test to set up expectations for text to be sent to tts and |
+ * the braille display with intermingled callbacks to drive the test. |
+ * @param {Function?} opt_finishedCallback Called when all expectaions have |
+ * been met. |
+ * @constructor |
+ */ |
+var MockFeedback = function(opt_finishedCallback) { |
+ /** |
+ * @type {!Function} |
+ * @private |
+ */ |
+ this.finishedCallback_ = opt_finishedCallback || function() {}; |
+ /** |
+ * True when |go| has been called and actions are being replayed. |
+ * @type {boolean} |
+ * @private |
+ */ |
+ this.going_ = 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.logTimeout_ = 0; |
+}; |
+ |
+MockFeedback.prototype = { |
+ |
+ /** |
+ * Install mock objects as |cvox.CrhomeVox.tts| and |cvox.ChromeVox.braille| |
+ * to collect feedback. |
+ */ |
+ install: function() { |
+ assertFalse(this.going_); |
+ function MockTts() {} |
+ 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. |
+ * @param {string} text |
+ * @return {MockFeedback} |this| for chaining |
+ */ |
+ expectSpeech: function(text) { |
+ assertFalse(this.going_); |
+ this.pendingActions_.push({ |
+ perform: MockFeedback.matchAndConsume_.bind( |
+ null, text, this.pendingUtterances_), |
+ describe: function() { return 'Speak ' + text; } |
+ }); |
+ return this; |
+ }, |
+ |
+ /** |
+ * Adds an expectation for braille output. |
+ * @param {string} text |
+ * @return {MockFeedback} |this| for chaining |
+ */ |
+ expectBraille: function(text) { |
+ assertFalse(this.going_); |
+ this.pendingActions_.push({ |
+ perform: MockFeedback.matchAndConsume_.bind( |
+ null, text, this.pendingBraille_), |
+ describe: function() { return 'Braille ' + text; } |
+ }); |
+ 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.going_); |
+ 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. |
+ */ |
+ go: function() { |
+ assertFalse(this.going_); |
+ this.going_ = true; |
+ this.process_(); |
+ }, |
+ |
+ /** |
+ * @param {string} textSTring |
+ * @param {cvox.QueueMode} queueMode |
+ * @param {Object=} properties |
+ * @private |
+ */ |
+ addUtterance_: function(textString, queueMode, properties) { |
+ this.pendingUtterances_.push( |
+ {text: textString, |
+ callback: properties ? properties.endCallback : undefined}); |
+ this.process_(); |
+ }, |
+ |
+ /** @private */ |
+ addBraille_: function(navBraille) { |
+ this.pendingBraille_.push({text: navBraille.text.toString()}); |
+ this.process_(); |
+ }, |
+ |
+ /*** @private */ |
+ process_: function() { |
+ if (!this.going_ || 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.logTimeout_) { |
+ window.clearTimeout(this.logTimeout_); |
+ this.debugTimeout = 0; |
+ } |
+ } else { |
+ break; |
+ } |
+ } |
+ if (this.pendingActions_.length == 0) { |
+ this.finishedCallback_(); |
+ } else { |
+ // If there are pending actions and no feedback for a few seconds, |
+ // log the pending state to ease debugging. |
+ if (!this.logTimeout_) { |
+ this.logTimeout_ = 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) {return i.text;}).join('\n ') + '\n '); |
+ } |
+ logPending('speech utterances', this.pendingUtterances_); |
+ logPending('braille', this.pendingBraille_); |
+ this.logTimeout_ = 0; |
+ }, |
+}; |
+ |
+/** |
+ * @param {string} text |
+ * @param {Array<{text: string|RegExp, callback: Function|undefined}>} pending |
+ * @return {boolean} |
+ * @private |
+ */ |
+MockFeedback.matchAndConsume_ = function(text, pending) { |
+ for (var i = 0, candidate; candidate = pending[i]; ++i) { |
+ if (text === candidate.text || |
+ (text instanceof RegExp && text.test(candidate.text))) |
+ break; |
+ } |
+ if (candidate) { |
+ var consumed = pending.splice(0, i + 1); |
+ consumed.forEach(function(item) { |
+ if (item.callback) |
+ item.callback(); |
+ }); |
+ return true; |
+ } |
+ return false; |
+}; |