diff options
Diffstat (limited to 'accessible/tests/mochitest/jsat/jsatcommon.js')
-rw-r--r-- | accessible/tests/mochitest/jsat/jsatcommon.js | 739 |
1 files changed, 739 insertions, 0 deletions
diff --git a/accessible/tests/mochitest/jsat/jsatcommon.js b/accessible/tests/mochitest/jsat/jsatcommon.js new file mode 100644 index 000000000..aa7ee74e4 --- /dev/null +++ b/accessible/tests/mochitest/jsat/jsatcommon.js @@ -0,0 +1,739 @@ +// A common module to run tests on the AccessFu module + +'use strict'; + +/*global isDeeply, getMainChromeWindow, SimpleTest, SpecialPowers, Logger, + AccessFu, Utils, addMessageListener, currentTabDocument, currentBrowser*/ + +/** + * A global variable holding an array of test functions. + */ +var gTestFuncs = []; +/** + * A global Iterator for the array of test functions. + */ +var gIterator; + +Components.utils.import('resource://gre/modules/Services.jsm'); +Components.utils.import("resource://gre/modules/accessibility/Utils.jsm"); +Components.utils.import("resource://gre/modules/accessibility/EventManager.jsm"); +Components.utils.import("resource://gre/modules/accessibility/Gestures.jsm"); + +var AccessFuTest = { + + addFunc: function AccessFuTest_addFunc(aFunc) { + if (aFunc) { + gTestFuncs.push(aFunc); + } + }, + + _registerListener: function AccessFuTest__registerListener(aWaitForMessage, aListenerFunc) { + var listener = { + observe: function observe(aMessage) { + // Ignore unexpected messages. + if (!(aMessage instanceof Components.interfaces.nsIConsoleMessage)) { + return; + } + if (aMessage.message.indexOf(aWaitForMessage) < 0) { + return; + } + aListenerFunc.apply(listener); + } + }; + Services.console.registerListener(listener); + return listener; + }, + + on_log: function AccessFuTest_on_log(aWaitForMessage, aListenerFunc) { + return this._registerListener(aWaitForMessage, aListenerFunc); + }, + + off_log: function AccessFuTest_off_log(aListener) { + Services.console.unregisterListener(aListener); + }, + + once_log: function AccessFuTest_once_log(aWaitForMessage, aListenerFunc) { + return this._registerListener(aWaitForMessage, + function listenAndUnregister() { + Services.console.unregisterListener(this); + aListenerFunc(); + }); + }, + + _addObserver: function AccessFuTest__addObserver(aWaitForData, aListener) { + var listener = function listener(aSubject, aTopic, aData) { + var data = JSON.parse(aData)[1]; + // Ignore non-relevant outputs. + if (!data) { + return; + } + isDeeply(data.details, aWaitForData, "Data is correct"); + aListener.apply(listener); + }; + Services.obs.addObserver(listener, 'accessibility-output', false); + return listener; + }, + + on: function AccessFuTest_on(aWaitForData, aListener) { + return this._addObserver(aWaitForData, aListener); + }, + + off: function AccessFuTest_off(aListener) { + Services.obs.removeObserver(aListener, 'accessibility-output'); + }, + + once: function AccessFuTest_once(aWaitForData, aListener) { + return this._addObserver(aWaitForData, function observerAndRemove() { + Services.obs.removeObserver(this, 'accessibility-output'); + aListener(); + }); + }, + + _waitForExplicitFinish: false, + + waitForExplicitFinish: function AccessFuTest_waitForExplicitFinish() { + this._waitForExplicitFinish = true; + }, + + finish: function AccessFuTest_finish() { + // Disable the console service logging. + Logger.test = false; + Logger.logLevel = Logger.INFO; + // Reset Gesture Settings. + GestureSettings.dwellThreshold = this.dwellThreshold = + this.originalDwellThreshold; + GestureSettings.swipeMaxDuration = this.swipeMaxDuration = + this.originalSwipeMaxDuration; + GestureSettings.maxGestureResolveTimeout = + this.maxGestureResolveTimeout = + this.originalMaxGestureResolveTimeout; + // Finish through idle callback to let AccessFu._disable complete. + SimpleTest.executeSoon(function () { + AccessFu.detach(); + SimpleTest.finish(); + }); + }, + + nextTest: function AccessFuTest_nextTest() { + var result = gIterator.next(); + if (result.done) { + this.finish(); + return; + } + var testFunc = result.value; + testFunc(); + }, + + runTests: function AccessFuTest_runTests(aAdditionalPrefs) { + if (gTestFuncs.length === 0) { + ok(false, "No tests specified!"); + SimpleTest.finish(); + return; + } + + // Create an Iterator for gTestFuncs array. + gIterator = (function*() { + for (var testFunc of gTestFuncs) { + yield testFunc; + } + })(); + + // Start AccessFu and put it in stand-by. + Components.utils.import("resource://gre/modules/accessibility/AccessFu.jsm"); + + AccessFu.attach(getMainChromeWindow(window)); + + AccessFu.readyCallback = function readyCallback() { + // Enable logging to the console service. + Logger.test = true; + Logger.logLevel = Logger.DEBUG; + }; + + var prefs = [['accessibility.accessfu.notify_output', 1], + ['dom.mozSettings.enabled', true]]; + prefs.push.apply(prefs, aAdditionalPrefs); + + this.originalDwellThreshold = GestureSettings.dwellThreshold; + this.originalSwipeMaxDuration = GestureSettings.swipeMaxDuration; + this.originalMaxGestureResolveTimeout = + GestureSettings.maxGestureResolveTimeout; + // https://bugzilla.mozilla.org/show_bug.cgi?id=1001945 - sometimes + // SimpleTest.executeSoon timeout is bigger than the timer settings in + // GestureSettings that causes intermittents. + this.dwellThreshold = GestureSettings.dwellThreshold = + GestureSettings.dwellThreshold * 10; + this.swipeMaxDuration = GestureSettings.swipeMaxDuration = + GestureSettings.swipeMaxDuration * 10; + this.maxGestureResolveTimeout = GestureSettings.maxGestureResolveTimeout = + GestureSettings.maxGestureResolveTimeout * 10; + + SpecialPowers.pushPrefEnv({ 'set': prefs }, function () { + if (AccessFuTest._waitForExplicitFinish) { + // Run all test functions asynchronously. + AccessFuTest.nextTest(); + } else { + // Run all test functions synchronously. + gTestFuncs.forEach(testFunc => testFunc()); + AccessFuTest.finish(); + } + }); + } +}; + +function AccessFuContentTest(aFuncResultPairs) { + this.queue = aFuncResultPairs; +} + +AccessFuContentTest.prototype = { + expected: [], + currentAction: null, + actionNum: -1, + + start: function(aFinishedCallback) { + Logger.logLevel = Logger.DEBUG; + this.finishedCallback = aFinishedCallback; + var self = this; + + // Get top content message manager, and set it up. + this.mms = [Utils.getMessageManager(currentBrowser())]; + this.setupMessageManager(this.mms[0], function () { + // Get child message managers and set them up + var frames = currentTabDocument().querySelectorAll('iframe'); + if (frames.length === 0) { + self.pump(); + return; + } + + var toSetup = 0; + for (var i = 0; i < frames.length; i++ ) { + var mm = Utils.getMessageManager(frames[i]); + if (mm) { + toSetup++; + self.mms.push(mm); + self.setupMessageManager(mm, function () { + if (--toSetup === 0) { + // All message managers are loaded and ready to go. + self.pump(); + } + }); + } + } + }); + }, + + finish: function() { + Logger.logLevel = Logger.INFO; + for (var mm of this.mms) { + mm.sendAsyncMessage('AccessFu:Stop'); + mm.removeMessageListener('AccessFu:Present', this); + mm.removeMessageListener('AccessFu:Input', this); + mm.removeMessageListener('AccessFu:CursorCleared', this); + mm.removeMessageListener('AccessFu:Focused', this); + mm.removeMessageListener('AccessFu:AriaHidden', this); + mm.removeMessageListener('AccessFu:Ready', this); + mm.removeMessageListener('AccessFu:ContentStarted', this); + } + if (this.finishedCallback) { + this.finishedCallback(); + } + }, + + setupMessageManager: function (aMessageManager, aCallback) { + function contentScript() { + addMessageListener('AccessFuTest:Focus', function (aMessage) { + var elem = content.document.querySelector(aMessage.json.selector); + if (elem) { + if (aMessage.json.blur) { + elem.blur(); + } else { + elem.focus(); + } + } + }); + } + + aMessageManager.addMessageListener('AccessFu:Present', this); + aMessageManager.addMessageListener('AccessFu:Input', this); + aMessageManager.addMessageListener('AccessFu:CursorCleared', this); + aMessageManager.addMessageListener('AccessFu:Focused', this); + aMessageManager.addMessageListener('AccessFu:AriaHidden', this); + aMessageManager.addMessageListener('AccessFu:Ready', function () { + aMessageManager.addMessageListener('AccessFu:ContentStarted', aCallback); + aMessageManager.sendAsyncMessage('AccessFu:Start', + { buildApp: 'browser', + androidSdkVersion: Utils.AndroidSdkVersion, + logLevel: 'DEBUG', + inTest: true }); + }); + + aMessageManager.loadFrameScript( + 'chrome://global/content/accessibility/content-script.js', false); + aMessageManager.loadFrameScript( + 'data:,(' + contentScript.toString() + ')();', false); + }, + + pump: function() { + this.expected.shift(); + if (this.expected.length) { + return; + } + + var currentPair = this.queue.shift(); + + if (currentPair) { + this.actionNum++; + this.currentAction = currentPair[0]; + if (typeof this.currentAction === 'function') { + this.currentAction(this.mms[0]); + } else if (this.currentAction) { + this.mms[0].sendAsyncMessage(this.currentAction.name, + this.currentAction.json); + } + + this.expected = currentPair.slice(1, currentPair.length); + + if (!this.expected[0]) { + this.pump(); + } + } else { + this.finish(); + } + }, + + receiveMessage: function(aMessage) { + var expected = this.expected[0]; + + if (!expected) { + return; + } + + var actionsString = typeof this.currentAction === 'function' ? + this.currentAction.name + '()' : JSON.stringify(this.currentAction); + + if (typeof expected === 'string') { + ok(true, 'Got ' + expected + ' after ' + actionsString); + this.pump(); + } else if (expected.ignore && !expected.ignore(aMessage)) { + expected.is(aMessage.json, 'after ' + actionsString + + ' (' + this.actionNum + ')'); + expected.is_correct_focus(); + this.pump(); + } + } +}; + +// Common content messages + +var ContentMessages = { + simpleMoveFirst: { + name: 'AccessFu:MoveCursor', + json: { + action: 'moveFirst', + rule: 'Simple', + inputType: 'gesture', + origin: 'top' + } + }, + + simpleMoveLast: { + name: 'AccessFu:MoveCursor', + json: { + action: 'moveLast', + rule: 'Simple', + inputType: 'gesture', + origin: 'top' + } + }, + + simpleMoveNext: { + name: 'AccessFu:MoveCursor', + json: { + action: 'moveNext', + rule: 'Simple', + inputType: 'gesture', + origin: 'top' + } + }, + + simpleMovePrevious: { + name: 'AccessFu:MoveCursor', + json: { + action: 'movePrevious', + rule: 'Simple', + inputType: 'gesture', + origin: 'top' + } + }, + + clearCursor: { + name: 'AccessFu:ClearCursor', + json: { + origin: 'top' + } + }, + + moveOrAdjustUp: function moveOrAdjustUp(aRule) { + return { + name: 'AccessFu:MoveCursor', + json: { + origin: 'top', + action: 'movePrevious', + inputType: 'gesture', + rule: (aRule || 'Simple'), + adjustRange: true + } + } + }, + + moveOrAdjustDown: function moveOrAdjustUp(aRule) { + return { + name: 'AccessFu:MoveCursor', + json: { + origin: 'top', + action: 'moveNext', + inputType: 'gesture', + rule: (aRule || 'Simple'), + adjustRange: true + } + } + }, + + androidScrollForward: function adjustUp() { + return { + name: 'AccessFu:AndroidScroll', + json: { origin: 'top', direction: 'forward' } + }; + }, + + androidScrollBackward: function adjustDown() { + return { + name: 'AccessFu:AndroidScroll', + json: { origin: 'top', direction: 'backward' } + }; + }, + + focusSelector: function focusSelector(aSelector, aBlur) { + return { + name: 'AccessFuTest:Focus', + json: { + selector: aSelector, + blur: aBlur + } + }; + }, + + activateCurrent: function activateCurrent(aOffset) { + return { + name: 'AccessFu:Activate', + json: { + origin: 'top', + offset: aOffset + } + }; + }, + + moveNextBy: function moveNextBy(aGranularity) { + return { + name: 'AccessFu:MoveByGranularity', + json: { + direction: 'Next', + granularity: this._granularityMap[aGranularity] + } + }; + }, + + movePreviousBy: function movePreviousBy(aGranularity) { + return { + name: 'AccessFu:MoveByGranularity', + json: { + direction: 'Previous', + granularity: this._granularityMap[aGranularity] + } + }; + }, + + moveCaretNextBy: function moveCaretNextBy(aGranularity) { + return { + name: 'AccessFu:MoveCaret', + json: { + direction: 'Next', + granularity: this._granularityMap[aGranularity] + } + }; + }, + + moveCaretPreviousBy: function moveCaretPreviousBy(aGranularity) { + return { + name: 'AccessFu:MoveCaret', + json: { + direction: 'Previous', + granularity: this._granularityMap[aGranularity] + } + }; + }, + + _granularityMap: { + 'character': 1, // MOVEMENT_GRANULARITY_CHARACTER + 'word': 2, // MOVEMENT_GRANULARITY_WORD + 'paragraph': 8 // MOVEMENT_GRANULARITY_PARAGRAPH + } +}; + +function ExpectedMessage (aName, aOptions) { + this.name = aName; + this.options = aOptions || {}; + this.json = {}; +} + +ExpectedMessage.prototype.lazyCompare = function(aReceived, aExpected, aInfo) { + if (aExpected && !aReceived) { + return [false, 'Expected something but got nothing -- ' + aInfo]; + } + + var matches = true; + var delta = []; + for (var attr in aExpected) { + var expected = aExpected[attr]; + var received = aReceived[attr]; + if (typeof expected === 'object') { + var [childMatches, childDelta] = this.lazyCompare(received, expected); + if (!childMatches) { + delta.push(attr + ' [ ' + childDelta + ' ]'); + matches = false; + } + } else { + if (received !== expected) { + delta.push( + attr + ' [ expected ' + JSON.stringify(expected) + + ' got ' + JSON.stringify(received) + ' ]'); + matches = false; + } + } + } + + var msg = delta.length ? delta.join(' ') : 'Structures lazily match'; + return [matches, msg + ' -- ' + aInfo]; +}; + +ExpectedMessage.prototype.is = function(aReceived, aInfo) { + var checkFunc = this.options.todo ? 'todo' : 'ok'; + SimpleTest[checkFunc].apply( + SimpleTest, this.lazyCompare(aReceived, this.json, aInfo)); +}; + +ExpectedMessage.prototype.is_correct_focus = function(aInfo) { + if (!this.options.focused) { + return; + } + + var checkFunc = this.options.focused_todo ? 'todo_is' : 'is'; + var doc = currentTabDocument(); + SimpleTest[checkFunc].apply(SimpleTest, + [ doc.activeElement, doc.querySelector(this.options.focused), + 'Correct element is focused: ' + this.options.focused + ' -- ' + aInfo ]); +}; + +ExpectedMessage.prototype.ignore = function(aMessage) { + return aMessage.name !== this.name; +}; + +function ExpectedPresent(aB2g, aAndroid, aOptions) { + ExpectedMessage.call(this, 'AccessFu:Present', aOptions); + if (aB2g) { + this.json.b2g = aB2g; + } + + if (aAndroid) { + this.json.android = aAndroid; + } +} + +ExpectedPresent.prototype = Object.create(ExpectedMessage.prototype); + +ExpectedPresent.prototype.is = function(aReceived, aInfo) { + var received = this.extract_presenters(aReceived); + + for (var presenter of ['b2g', 'android']) { + if (!this.options['no_' + presenter]) { + var todo = this.options.todo || this.options[presenter + '_todo'] + SimpleTest[todo ? 'todo' : 'ok'].apply( + SimpleTest, this.lazyCompare(received[presenter], + this.json[presenter], aInfo + ' (' + presenter + ')')); + } + } +}; + +ExpectedPresent.prototype.extract_presenters = function(aReceived) { + var received = { count: 0 }; + for (var presenter of aReceived) { + if (presenter) { + received[presenter.type.toLowerCase()] = presenter.details; + received.count++; + } + } + + return received +}; + +ExpectedPresent.prototype.ignore = function(aMessage) { + if (ExpectedMessage.prototype.ignore.call(this, aMessage)) { + return true; + } + + var received = this.extract_presenters(aMessage.json); + return received.count === 0 || + (received.visual && received.visual.eventType === 'viewport-change') || + (received.android && + received.android[0].eventType === AndroidEvent.VIEW_SCROLLED); +}; + +function ExpectedCursorChange(aSpeech, aOptions) { + ExpectedPresent.call(this, { + eventType: 'vc-change', + data: aSpeech + }, [{ + eventType: 0x8000, // VIEW_ACCESSIBILITY_FOCUSED + }], aOptions); +} + +ExpectedCursorChange.prototype = Object.create(ExpectedPresent.prototype); + +function ExpectedCursorTextChange(aSpeech, aStartOffset, aEndOffset, aOptions) { + ExpectedPresent.call(this, { + eventType: 'vc-change', + data: aSpeech + }, [{ + eventType: AndroidEvent.VIEW_TEXT_TRAVERSED_AT_MOVEMENT_GRANULARITY, + fromIndex: aStartOffset, + toIndex: aEndOffset + }], aOptions); + + // bug 980509 + this.options.b2g_todo = true; +} + +ExpectedCursorTextChange.prototype = + Object.create(ExpectedCursorChange.prototype); + +function ExpectedClickAction(aOptions) { + ExpectedPresent.call(this, { + eventType: 'action', + data: [{ string: 'clickAction' }] + }, [{ + eventType: AndroidEvent.VIEW_CLICKED + }], aOptions); +} + +ExpectedClickAction.prototype = Object.create(ExpectedPresent.prototype); + +function ExpectedCheckAction(aChecked, aOptions) { + ExpectedPresent.call(this, { + eventType: 'action', + data: [{ string: aChecked ? 'checkAction' : 'uncheckAction' }] + }, [{ + eventType: AndroidEvent.VIEW_CLICKED, + checked: aChecked + }], aOptions); +} + +ExpectedCheckAction.prototype = Object.create(ExpectedPresent.prototype); + +function ExpectedSwitchAction(aSwitched, aOptions) { + ExpectedPresent.call(this, { + eventType: 'action', + data: [{ string: aSwitched ? 'onAction' : 'offAction' }] + }, [{ + eventType: AndroidEvent.VIEW_CLICKED, + checked: aSwitched + }], aOptions); +} + +ExpectedSwitchAction.prototype = Object.create(ExpectedPresent.prototype); + +function ExpectedNameChange(aName, aOptions) { + ExpectedPresent.call(this, { + eventType: 'name-change', + data: aName + }, null, aOptions); +} + +ExpectedNameChange.prototype = Object.create(ExpectedPresent.prototype); + +function ExpectedValueChange(aValue, aOptions) { + ExpectedPresent.call(this, { + eventType: 'value-change', + data: aValue + }, null, aOptions); +} + +ExpectedValueChange.prototype = Object.create(ExpectedPresent.prototype); + +function ExpectedTextChanged(aValue, aOptions) { + ExpectedPresent.call(this, { + eventType: 'text-change', + data: aValue + }, null, aOptions); +} + +ExpectedTextChanged.prototype = Object.create(ExpectedPresent.prototype); + +function ExpectedEditState(aEditState, aOptions) { + ExpectedMessage.call(this, 'AccessFu:Input', aOptions); + this.json = aEditState; +} + +ExpectedEditState.prototype = Object.create(ExpectedMessage.prototype); + +function ExpectedTextSelectionChanged(aStart, aEnd, aOptions) { + ExpectedPresent.call(this, null, [{ + eventType: AndroidEvent.VIEW_TEXT_SELECTION_CHANGED, + brailleOutput: { + selectionStart: aStart, + selectionEnd: aEnd + }}], aOptions); +} + +ExpectedTextSelectionChanged.prototype = + Object.create(ExpectedPresent.prototype); + +function ExpectedTextCaretChanged(aFrom, aTo, aOptions) { + ExpectedPresent.call(this, null, [{ + eventType: AndroidEvent.VIEW_TEXT_TRAVERSED_AT_MOVEMENT_GRANULARITY, + fromIndex: aFrom, + toIndex: aTo + }], aOptions); +} + +ExpectedTextCaretChanged.prototype = Object.create(ExpectedPresent.prototype); + +function ExpectedAnnouncement(aAnnouncement, aOptions) { + ExpectedPresent.call(this, null, [{ + eventType: AndroidEvent.ANNOUNCEMENT, + text: [ aAnnouncement], + addedCount: aAnnouncement.length + }], aOptions); +} + +ExpectedAnnouncement.prototype = Object.create(ExpectedPresent.prototype); + +function ExpectedNoMove(aOptions) { + ExpectedPresent.call(this, {eventType: 'no-move' }, null, aOptions); +} + +ExpectedNoMove.prototype = Object.create(ExpectedPresent.prototype); + +var AndroidEvent = { + VIEW_CLICKED: 0x01, + VIEW_LONG_CLICKED: 0x02, + VIEW_SELECTED: 0x04, + VIEW_FOCUSED: 0x08, + VIEW_TEXT_CHANGED: 0x10, + WINDOW_STATE_CHANGED: 0x20, + VIEW_HOVER_ENTER: 0x80, + VIEW_HOVER_EXIT: 0x100, + VIEW_SCROLLED: 0x1000, + VIEW_TEXT_SELECTION_CHANGED: 0x2000, + ANNOUNCEMENT: 0x4000, + VIEW_ACCESSIBILITY_FOCUSED: 0x8000, + VIEW_TEXT_TRAVERSED_AT_MOVEMENT_GRANULARITY: 0x20000 +}; |