gecko-dev/accessible/tests/mochitest/jsat/jsatcommon.js

497 lines
13 KiB
JavaScript

// 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");
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.actions, aWaitForData, "Data is correct");
aListener.apply(listener);
};
Services.obs.addObserver(listener, 'accessfu-output', false);
return listener;
},
on: function AccessFuTest_on(aWaitForData, aListener) {
return this._addObserver(aWaitForData, aListener);
},
off: function AccessFuTest_off(aListener) {
Services.obs.removeObserver(aListener, 'accessfu-output');
},
once: function AccessFuTest_once(aWaitForData, aListener) {
return this._addObserver(aWaitForData, function observerAndRemove() {
Services.obs.removeObserver(this, 'accessfu-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;
// Finish through idle callback to let AccessFu._disable complete.
SimpleTest.executeSoon(function () {
AccessFu.detach();
SimpleTest.finish();
});
},
nextTest: function AccessFuTest_nextTest() {
var testFunc;
try {
// Get the next test function from the iterator. If none left,
// StopIteration exception is thrown.
testFunc = gIterator.next()[1];
} catch (ex) {
// StopIteration exception.
this.finish();
return;
}
testFunc();
},
runTests: function AccessFuTest_runTests() {
if (gTestFuncs.length === 0) {
ok(false, "No tests specified!");
SimpleTest.finish();
return;
}
// Create an Iterator for gTestFuncs array.
gIterator = Iterator(gTestFuncs); // jshint ignore:line
// 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;
};
SpecialPowers.pushPrefEnv({
'set': [['accessibility.accessfu.notify_output', 1],
['dom.mozSettings.enabled', true]]
}, function () {
if (AccessFuTest._waitForExplicitFinish) {
// Run all test functions asynchronously.
AccessFuTest.nextTest();
} else {
// Run all test functions synchronously.
[testFunc() for (testFunc of gTestFuncs)]; // jshint ignore:line
AccessFuTest.finish();
}
});
}
};
function AccessFuContentTest(aFuncResultPairs) {
this.queue = aFuncResultPairs;
}
AccessFuContentTest.prototype = {
currentPair: null,
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');
}
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:CursorCleared', this);
aMessageManager.addMessageListener('AccessFu:Ready', function () {
aMessageManager.addMessageListener('AccessFu:ContentStarted', aCallback);
aMessageManager.sendAsyncMessage('AccessFu:Start',
{ buildApp: 'browser',
androidSdkVersion: Utils.AndroidSdkVersion,
logLevel: 'DEBUG' });
});
aMessageManager.loadFrameScript(
'chrome://global/content/accessibility/content-script.js', false);
aMessageManager.loadFrameScript(
'data:,(' + contentScript.toString() + ')();', false);
},
pump: function() {
this.currentPair = this.queue.shift();
if (this.currentPair) {
if (this.currentPair[0] instanceof Function) {
this.currentPair[0](this.mms[0]);
} else if (this.currentPair[0]) {
this.mms[0].sendAsyncMessage(this.currentPair[0].name,
this.currentPair[0].json);
}
if (!this.currentPair[1]) {
this.pump();
}
} else {
this.finish();
}
},
receiveMessage: function(aMessage) {
if (!this.currentPair) {
return;
}
var expected = this.currentPair[1] || {};
// |expected| can simply be a name of a message, no more further testing.
if (aMessage.name === expected) {
ok(true, 'Received ' + expected);
this.pump();
return;
}
var speech = this.extractUtterance(aMessage.json);
var android = this.extractAndroid(aMessage.json, expected.android);
if ((speech && expected.speak) || (android && expected.android)) {
if (expected.speak) {
(SimpleTest[expected.speak_checkFunc] || is)(speech, expected.speak,
'"' + speech + '" spoken');
}
if (expected.android) {
var checkFunc = SimpleTest[expected.android_checkFunc] || ok;
checkFunc.apply(SimpleTest,
this.lazyCompare(android, expected.android));
}
this.pump();
}
},
lazyCompare: function lazyCompare(aReceived, aExpected) {
var matches = true;
var delta = [];
for (var attr in aExpected) {
var expected = aExpected[attr];
var received = aReceived !== undefined ? aReceived[attr] : null;
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 ' + expected + ' got ' + received + ' ]');
matches = false;
}
}
}
return [matches, delta.join(' ')];
},
extractUtterance: function(aData) {
if (!aData) {
return null;
}
for (var output of aData) {
if (output && output.type === 'Speech') {
for (var action of output.details.actions) {
if (action && action.method == 'speak') {
return action.data;
}
}
}
}
return null;
},
extractAndroid: function(aData, aExpectedEvents) {
for (var output of aData) {
if (output && output.type === 'Android') {
for (var i in output.details) {
// Only extract if event types match expected event types.
var exp = aExpectedEvents ? aExpectedEvents[i] : null;
if (!exp || (output.details[i].eventType !== exp.eventType)) {
return null;
}
}
return output.details;
}
}
return null;
}
};
// 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'
}
},
adjustRangeUp: {
name: 'AccessFu:AdjustRange',
json: {
origin: 'top',
direction: 'backward'
}
},
adjustRangeDown: {
name: 'AccessFu:AdjustRange',
json: {
origin: 'top',
direction: 'forward'
}
},
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
}
};
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
};