Publish Vulpes Preview 15 sources for desktop, Android and Tundra
Share Gaia assets and adapters, keep private profiles and development documents local, and update both public READMEs.
This commit is contained in:
parent
93e3a48004
commit
b38dea0597
25901 changed files with 9982925 additions and 369 deletions
1
gaia/dev_apps/test-agent/.gitignore
vendored
Executable file
1
gaia/dev_apps/test-agent/.gitignore
vendored
Executable file
|
|
@ -0,0 +1 @@
|
|||
config.json
|
||||
55
gaia/dev_apps/test-agent/agent.js
Executable file
55
gaia/dev_apps/test-agent/agent.js
Executable file
|
|
@ -0,0 +1,55 @@
|
|||
'use strict';
|
||||
(function(window){
|
||||
|
||||
var Loader = window.CommonResourceLoader = {},
|
||||
domain = document.location.host;
|
||||
|
||||
Loader.domain = document.location.protocol + '//' + domain;
|
||||
Loader.url = function(url){
|
||||
return this.domain + url;
|
||||
};
|
||||
|
||||
Loader.script = function(url){
|
||||
var script = document.createElement('script');
|
||||
script.type = 'application/javascript;version=1.8';
|
||||
script.async = false;
|
||||
script.src = this.url(url);
|
||||
|
||||
var promise = new Promise((resolve, reject) => {
|
||||
script.onload = resolve;
|
||||
script.onerror = reject;
|
||||
});
|
||||
|
||||
document.body.appendChild(script);
|
||||
return promise;
|
||||
};
|
||||
|
||||
Loader.stylesheet = function(url){
|
||||
var link = document.createElement('link');
|
||||
link.rel = 'stylesheet';
|
||||
link.href = this.url(url);
|
||||
|
||||
var promise = new Promise((resolve, reject) => {
|
||||
link.onload = resolve;
|
||||
link.onerror = reject;
|
||||
});
|
||||
|
||||
document.body.appendChild(link);
|
||||
return promise;
|
||||
};
|
||||
}(this));
|
||||
|
||||
|
||||
Promise.all([
|
||||
CommonResourceLoader.stylesheet('/common/vendor/test-agent/test-agent.css'),
|
||||
CommonResourceLoader.stylesheet('/common/vendor/mocha/mocha.css'),
|
||||
CommonResourceLoader.script('/common/vendor/mocha/mocha.js'),
|
||||
CommonResourceLoader.script('/common/vendor/sinon/sinon.js'),
|
||||
CommonResourceLoader.script('/common/vendor/blanket/blanket.js'),
|
||||
CommonResourceLoader.script('/common/vendor/test-agent/test-agent.js'),
|
||||
CommonResourceLoader.script('/common/test/test_url_resolver.js'),
|
||||
CommonResourceLoader.script('/common/test/agent.js')
|
||||
]).catch(function() {
|
||||
document.getElementById('loading-error-message').hidden = false;
|
||||
});
|
||||
|
||||
128
gaia/dev_apps/test-agent/common/test/agent.js
Executable file
128
gaia/dev_apps/test-agent/common/test/agent.js
Executable file
|
|
@ -0,0 +1,128 @@
|
|||
(function(window) {
|
||||
|
||||
/**
|
||||
* Keep awake utility to ensure device does
|
||||
* not go to sleep before the test completes.
|
||||
*/
|
||||
var KeepAwake = (function() {
|
||||
|
||||
var running = false,
|
||||
power = navigator.mozPower;
|
||||
|
||||
return {
|
||||
|
||||
_activate: function ka_activate() {
|
||||
if (running) {
|
||||
try {
|
||||
power.screenEnabled = true;
|
||||
} catch (e) {
|
||||
//during desktop we don't care if this
|
||||
//fails. In app context it will always work.
|
||||
}
|
||||
setTimeout(ka_activate, 800);
|
||||
}
|
||||
},
|
||||
|
||||
start: function ka_start() {
|
||||
running = true;
|
||||
this._activate();
|
||||
},
|
||||
|
||||
stop: function ka_stop() {
|
||||
running = false;
|
||||
}
|
||||
};
|
||||
}());
|
||||
|
||||
/**
|
||||
* For CI (and maybe for other uses)
|
||||
* we want to configure the agent
|
||||
* with url options.
|
||||
*
|
||||
* Options are taken from the hash rather
|
||||
* then the query string for future use
|
||||
* of offline cache.
|
||||
*
|
||||
* http://test-agent.gaiamobile.org/index.html#?key=value
|
||||
*/
|
||||
var AgentConfig = (function() {
|
||||
var params = window.location.hash,
|
||||
index = params.indexOf('?'),
|
||||
options = {
|
||||
websocketUrl: null
|
||||
};
|
||||
|
||||
if (index > -1) {
|
||||
params.slice(index + 1).split('&').forEach(function(pair) {
|
||||
var split = pair.split('=');
|
||||
if (split.length === 2) {
|
||||
options[split[0]] = split[1];
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
return options;
|
||||
}());
|
||||
|
||||
console.log(AgentConfig);
|
||||
|
||||
var worker = new TestAgent.BrowserWorker({
|
||||
/* this is where your tests will be loaded into */
|
||||
sandbox: './sandbox.html'
|
||||
});
|
||||
|
||||
worker.use(TestAgent.BrowserWorker.Config, {
|
||||
/* config file which lists all available tests for the ui */
|
||||
url: './config.json'
|
||||
});
|
||||
|
||||
worker.use(TestAgent.BrowserWorker.Websocket, {
|
||||
url: AgentConfig.websocketUrl
|
||||
});
|
||||
|
||||
worker.use(TestAgent.BrowserWorker.MultiDomainDriver, {
|
||||
groupTestsByDomain: function(test) {
|
||||
var parsed = TestUrlResolver.parse(test);
|
||||
|
||||
var env = parsed.host + '-' + parsed.url;
|
||||
|
||||
var result = {
|
||||
domain: parsed.domain + 'test/unit/_proxy.html',
|
||||
test: '/' + parsed.url,
|
||||
env: env
|
||||
};
|
||||
|
||||
return result;
|
||||
}
|
||||
});
|
||||
|
||||
worker.use(TestAgent.BrowserWorker.TestUi);
|
||||
worker.use(TestAgent.BrowserWorker.ErrorReporting);
|
||||
|
||||
worker.use(TestAgent.Common.MochaTestEvents, {
|
||||
defaultMochaReporter: 'HTML'
|
||||
});
|
||||
|
||||
worker.use(TestAgent.Common.BlanketReportCollector);
|
||||
|
||||
worker.on({
|
||||
|
||||
'open': function() {
|
||||
setTimeout(worker.ready.bind(worker), 0);
|
||||
},
|
||||
|
||||
'test runner': function() {
|
||||
KeepAwake.start();
|
||||
},
|
||||
|
||||
'test runner end': function() {
|
||||
KeepAwake.stop();
|
||||
}
|
||||
|
||||
});
|
||||
|
||||
worker.config();
|
||||
worker.start();
|
||||
|
||||
}(this));
|
||||
|
||||
71
gaia/dev_apps/test-agent/common/test/agent_proxy.js
Executable file
71
gaia/dev_apps/test-agent/common/test/agent_proxy.js
Executable file
|
|
@ -0,0 +1,71 @@
|
|||
(function(window) {
|
||||
|
||||
var worker = new TestAgent.BrowserWorker({
|
||||
/* this is where your tests will be loaded into */
|
||||
sandbox: '/test/unit/_sandbox.html'
|
||||
});
|
||||
|
||||
worker.use(TestAgent.BrowserWorker.PostMessage);
|
||||
|
||||
worker.use(TestAgent.BrowserWorker.MochaDriver, {
|
||||
|
||||
ui: 'tdd',
|
||||
/* path to mocha */
|
||||
mochaUrl: CommonResourceLoader.url('/common/vendor/mocha/mocha.js'),
|
||||
|
||||
/**
|
||||
* Important: will not cause failures if missing.
|
||||
* Allows each app to have its own setup.js file that will execute
|
||||
* before any tests are loaded so we can utilize helpers outside
|
||||
* of setup blocks. Any files require from setup.js should also
|
||||
* block loading of any tests...
|
||||
*/
|
||||
testHelperUrl: [
|
||||
CommonResourceLoader.url('/common/test/helper.js'),
|
||||
CommonResourceLoader.url('/common/test/sinon_helper.js'),
|
||||
'/test/unit/setup.js'
|
||||
],
|
||||
|
||||
reporter: null,
|
||||
|
||||
setup: {
|
||||
ignoreLeaks: true
|
||||
}
|
||||
|
||||
});
|
||||
|
||||
worker.use(TestAgent.BrowserWorker.BlanketDriver, {
|
||||
blanketUrl: CommonResourceLoader.url('/common/vendor/blanket/blanket.js'),
|
||||
defaultConfig: {
|
||||
'data-cover-only': 'js/',
|
||||
'data-cover-never': '["test/unit/","alameda.js","/shared/"]',
|
||||
'data-cover-flags': 'lazyload',
|
||||
'data-cover-adapter': CommonResourceLoader
|
||||
.url('/common/vendor/blanket/test-agent-blanket.js')
|
||||
}
|
||||
});
|
||||
|
||||
//enable let, yield, etc...
|
||||
worker.loader.type = 'application/javascript;version=1.8';
|
||||
|
||||
worker.on({
|
||||
|
||||
'sandbox': function() {
|
||||
},
|
||||
|
||||
'run tests': function() {
|
||||
},
|
||||
|
||||
'open': function() {
|
||||
console.log('socket open');
|
||||
},
|
||||
|
||||
'close': function() {
|
||||
console.log('lost client trying to reconnect');
|
||||
}
|
||||
|
||||
});
|
||||
|
||||
worker.start();
|
||||
|
||||
}(this));
|
||||
49
gaia/dev_apps/test-agent/common/test/boilerplate/_proxy.html
Executable file
49
gaia/dev_apps/test-agent/common/test/boilerplate/_proxy.html
Executable file
|
|
@ -0,0 +1,49 @@
|
|||
<!DOCTYPE HTML>
|
||||
<html>
|
||||
<head>
|
||||
<meta http-equiv="content-type" content="text/html; charset=utf-8" />
|
||||
<title>Serve the tests</title>
|
||||
|
||||
<script type="text/javascript" charset="utf-8">
|
||||
(function(window){
|
||||
var Loader = window.CommonResourceLoader = {},
|
||||
host = document.location.host,
|
||||
domain = host;
|
||||
|
||||
Loader.domain = document.location.protocol + '//' + domain;
|
||||
Loader.url = function(url){
|
||||
return this.domain + url;
|
||||
}
|
||||
|
||||
Loader.script = function(url, doc){
|
||||
doc = doc || document;
|
||||
doc.write('<script type="application/javascript;version=1.8" src="' + this.url(url) + '"><\/script>');
|
||||
return this;
|
||||
};
|
||||
}(this));
|
||||
</script>
|
||||
|
||||
<style type="text/css" media="all">
|
||||
html,body,iframe {
|
||||
height: 100%;
|
||||
width: 100%;
|
||||
}
|
||||
</style>
|
||||
|
||||
</head>
|
||||
<body>
|
||||
|
||||
<!-- Test Agent UI will be loaded in here -->
|
||||
<div id="test-agent-ui">
|
||||
</div>
|
||||
|
||||
<script type="text/javascript" charset="utf-8">
|
||||
CommonResourceLoader.
|
||||
script('/common/test/test_url_resolver.js').
|
||||
script('/common/vendor/test-agent/test-agent.js').
|
||||
script('/common/test/agent_proxy.js');
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
||||
|
||||
|
||||
27
gaia/dev_apps/test-agent/common/test/boilerplate/_sandbox.html
Executable file
27
gaia/dev_apps/test-agent/common/test/boilerplate/_sandbox.html
Executable file
|
|
@ -0,0 +1,27 @@
|
|||
<!DOCTYPE HTML>
|
||||
<html>
|
||||
<head>
|
||||
<meta http-equiv="content-type" content="text/html; charset=utf-8" />
|
||||
<title>Tests</title>
|
||||
<style type="text/css" media="all">
|
||||
iframe {
|
||||
border: none;
|
||||
padding: 0px;
|
||||
}
|
||||
</style>
|
||||
<script type="text/javascript" charset="utf-8">
|
||||
</script>
|
||||
</head>
|
||||
|
||||
<body>
|
||||
|
||||
<div id="mocha">
|
||||
</div>
|
||||
|
||||
<div id="test">
|
||||
</div>
|
||||
|
||||
</body>
|
||||
</html>
|
||||
|
||||
|
||||
133
gaia/dev_apps/test-agent/common/test/helper.js
Executable file
133
gaia/dev_apps/test-agent/common/test/helper.js
Executable file
|
|
@ -0,0 +1,133 @@
|
|||
// put stuff here to help your tests out...
|
||||
|
||||
(function(window) {
|
||||
// register the global
|
||||
window.navigator;
|
||||
|
||||
var htmlFragments;
|
||||
var requestedFragments = {};
|
||||
|
||||
var Common = window.parent.CommonResourceLoader,
|
||||
// mocha test methods we want to provide
|
||||
// yield support to.
|
||||
testMethods = [
|
||||
'suiteSetup',
|
||||
'setup',
|
||||
'test',
|
||||
'teardown',
|
||||
'suiteTeardown'
|
||||
];
|
||||
|
||||
// chai has no backtraces in ff
|
||||
// this patch will change the error
|
||||
// class used to provide real .stack.
|
||||
function patchChai(chai, util) {
|
||||
var Assertion = chai.Assertion;
|
||||
|
||||
function chaiAssert(expr, msg, negateMsg, expected, actual) {
|
||||
var ok = util.test(this, arguments);
|
||||
|
||||
if (!ok) {
|
||||
var msg = util.getMessage(this, arguments);
|
||||
throw new Error(
|
||||
// include custom message if available
|
||||
this.msg ? this.msg + ': ' + msg : msg
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
Assertion.prototype.assert = chaiAssert;
|
||||
}
|
||||
|
||||
window.requireApp = function(url, cb, options) {
|
||||
require(TestUrlResolver.resolve(url), cb, options);
|
||||
};
|
||||
|
||||
/**
|
||||
* Appends a templated node to the body for a suite
|
||||
* Removes the node at teardown.
|
||||
* @param {String} is the type of element.
|
||||
* @param {Object} attrs optional attributes.
|
||||
*/
|
||||
window.suiteTemplate = function(is, attrs) {
|
||||
|
||||
var testElement;
|
||||
|
||||
setup(function ta_template() {
|
||||
var foundElement = htmlFragments.querySelector('element[name="' + is + '"]');
|
||||
testElement = document.createElement(foundElement.getAttribute('extends') || 'div');
|
||||
var template = foundElement.querySelector('template');
|
||||
testElement.innerHTML = template.innerHTML;
|
||||
|
||||
attrs = attrs || {};
|
||||
for (var i in attrs) {
|
||||
testElement.setAttribute(i, attrs[i]);
|
||||
}
|
||||
|
||||
document.body.appendChild(testElement);
|
||||
});
|
||||
|
||||
teardown(function ta_teardown() {
|
||||
testElement.parentNode.removeChild(testElement);
|
||||
});
|
||||
};
|
||||
|
||||
window.requireElements = function(url) {
|
||||
|
||||
url = TestUrlResolver.resolve(url);
|
||||
|
||||
if (requestedFragments[url]) {
|
||||
return;
|
||||
}
|
||||
requestedFragments[url] = true;
|
||||
|
||||
var xhr = new XMLHttpRequest();
|
||||
xhr.open('GET', url, false /* intentional sync */);
|
||||
xhr.send();
|
||||
|
||||
if (!htmlFragments) {
|
||||
htmlFragments = document.createElement('div');
|
||||
}
|
||||
htmlFragments.innerHTML += xhr.responseText;
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Require a file from /common/ resources.
|
||||
*
|
||||
* Usage: requireCommon('vendor/mocha/mocha.js');
|
||||
*
|
||||
* @param {String} url relative location of file.
|
||||
* @param {Function} cb optional callback called
|
||||
* when resource has been loaded.
|
||||
*/
|
||||
window.requireCommon = function(url, cb) {
|
||||
require(Common.url('/common/' + url), cb);
|
||||
};
|
||||
|
||||
// template
|
||||
requireCommon('test/template.js');
|
||||
|
||||
// load chai
|
||||
window.requireCommon('vendor/chai/chai.js', function() {
|
||||
chai.config.includeStack = true;
|
||||
chai.use(patchChai);
|
||||
window.requireCommon('vendor/chai/chai-as-promised.js');
|
||||
window.assert = chai.assert;
|
||||
window.expect = chai.expect;
|
||||
window.should = chai.should();
|
||||
});
|
||||
|
||||
// mocha helpers
|
||||
window.requireCommon('test/mocha_task.js');
|
||||
window.requireCommon('test/mocha_generators.js', function() {
|
||||
testMethods.forEach(function(method) {
|
||||
testSupport.mochaGenerators.overload(method);
|
||||
});
|
||||
});
|
||||
|
||||
// url utilities
|
||||
window.requireCommon('test/test_url_resolver.js');
|
||||
|
||||
}(this));
|
||||
|
||||
89
gaia/dev_apps/test-agent/common/test/mocha_generators.js
Executable file
89
gaia/dev_apps/test-agent/common/test/mocha_generators.js
Executable file
|
|
@ -0,0 +1,89 @@
|
|||
(function(window) {
|
||||
var support;
|
||||
|
||||
if (typeof(window.testSupport) === 'undefined') {
|
||||
window.testSupport = {};
|
||||
}
|
||||
|
||||
support = window.testSupport;
|
||||
support.mochaGenerators = {
|
||||
|
||||
/**
|
||||
* Overloads a test function to be capable
|
||||
* of using generators/yield for control flow.
|
||||
*
|
||||
* test('yield', function(){
|
||||
* function sleep(time) {
|
||||
* setTimeout(time, MochaTask.next);
|
||||
* }
|
||||
*
|
||||
* yield sleep(5);
|
||||
*
|
||||
* //..
|
||||
*
|
||||
* });
|
||||
*
|
||||
*
|
||||
* @param {String} type name of mocha function.
|
||||
*/
|
||||
overload: function overload(type) {
|
||||
var orig = window[type];
|
||||
|
||||
/**
|
||||
* async tests fall off the stack
|
||||
* bring them back by providing the done fn
|
||||
*/
|
||||
function wrapDone(done) {
|
||||
return function(val) {
|
||||
if (typeof(val) === 'function') {
|
||||
try {
|
||||
val();
|
||||
return done();
|
||||
} catch (e) {
|
||||
return done(e);
|
||||
}
|
||||
} else {
|
||||
return done.apply(this, arguments);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
window[type] = function() {
|
||||
var args = Array.prototype.slice.call(arguments),
|
||||
cb = args.pop();
|
||||
|
||||
function wrapper(origDone) {
|
||||
var gen, useDone = cb.length > 0;
|
||||
var done = wrapDone(origDone);
|
||||
|
||||
if (useDone) {
|
||||
gen = cb.call(this, done);
|
||||
} else {
|
||||
gen = cb.call(this);
|
||||
}
|
||||
|
||||
if (gen && gen.next) {
|
||||
MochaTask.start(gen, function(e) {
|
||||
done();
|
||||
}, function(e) {
|
||||
done(e);
|
||||
});
|
||||
} else {
|
||||
if (!useDone) {
|
||||
done();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
wrapper.toString = function() {
|
||||
return cb.toString();
|
||||
}
|
||||
|
||||
args.push(wrapper);
|
||||
orig.apply(window, args);
|
||||
}
|
||||
}
|
||||
|
||||
};
|
||||
|
||||
}(this));
|
||||
126
gaia/dev_apps/test-agent/common/test/mocha_task.js
Executable file
126
gaia/dev_apps/test-agent/common/test/mocha_task.js
Executable file
|
|
@ -0,0 +1,126 @@
|
|||
/**
|
||||
* MochaTask manager.
|
||||
* Used to provide generator yields for mocha test.
|
||||
*/
|
||||
var MochaTask = (function() {
|
||||
var current, done, errorHandler;
|
||||
|
||||
function clearState() {
|
||||
done = null;
|
||||
current = null;
|
||||
errorHandler = null;
|
||||
}
|
||||
|
||||
return {
|
||||
|
||||
/**
|
||||
* Starts a task this will
|
||||
* effect the global state of MochaTask.
|
||||
*
|
||||
*
|
||||
* @param {Function} generator generator function.
|
||||
* @param {Function} success success callback.
|
||||
* @param {Function} error error callback receives an Error instance.
|
||||
*/
|
||||
start: function(generator, success, error) {
|
||||
current = generator;
|
||||
if (!current.next) {
|
||||
current = generator.call(this);
|
||||
}
|
||||
|
||||
done = success;
|
||||
errorHandler = error;
|
||||
|
||||
if (current && current.next) {
|
||||
current.next();
|
||||
}
|
||||
|
||||
return this;
|
||||
},
|
||||
|
||||
/**
|
||||
* Sends next value to the generator.
|
||||
* Function may be passed to functions
|
||||
* that normally require a callback.
|
||||
*
|
||||
* yield setTimeout(100, MochaTask.next)
|
||||
*
|
||||
* If next is called with a value it will be passed
|
||||
* to the generators send method so you can use it to
|
||||
* create this kind of code.
|
||||
*
|
||||
* var responseText = yield magicXhrMethod('GET', url, Mocha.next);
|
||||
*
|
||||
* @param {Object} value object to pass to generator.
|
||||
*/
|
||||
next: function(value) {
|
||||
//assign references so we
|
||||
//can clear state without messing
|
||||
//up execution order later.
|
||||
var complete = done,
|
||||
generator = current,
|
||||
handler = errorHandler;
|
||||
|
||||
try {
|
||||
generator.send(value);
|
||||
} catch (e) {
|
||||
if (e instanceof StopIteration) {
|
||||
clearState();
|
||||
|
||||
if (complete) {
|
||||
complete();
|
||||
}
|
||||
} else {
|
||||
clearState();
|
||||
|
||||
if (handler) {
|
||||
handler(e);
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
|
||||
nextNodeStyle: function(error, value) {
|
||||
//assign references so we
|
||||
//can clear state without messing
|
||||
//up execution order later.
|
||||
var complete = done,
|
||||
generator = current,
|
||||
handler = errorHandler;
|
||||
|
||||
if (error) {
|
||||
try {
|
||||
generator.throw(error);
|
||||
} catch (e) {
|
||||
if (!(e instanceof StopIteration)) {
|
||||
handler(e);
|
||||
} else {
|
||||
clearState();
|
||||
if (complete) {
|
||||
complete();
|
||||
}
|
||||
}
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
try {
|
||||
generator.send(value);
|
||||
} catch (e) {
|
||||
if (e instanceof StopIteration) {
|
||||
clearState();
|
||||
if (complete) {
|
||||
complete();
|
||||
}
|
||||
} else {
|
||||
clearState();
|
||||
if (handler) {
|
||||
handler(e);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
};
|
||||
|
||||
}());
|
||||
13
gaia/dev_apps/test-agent/common/test/sinon_helper.js
Executable file
13
gaia/dev_apps/test-agent/common/test/sinon_helper.js
Executable file
|
|
@ -0,0 +1,13 @@
|
|||
// load sinon.js
|
||||
window.requireCommon('vendor/sinon/sinon.js', function() {
|
||||
setup(function() {
|
||||
this.sinon = sinon.sandbox.create();
|
||||
});
|
||||
|
||||
teardown(function() {
|
||||
if (this.sinon) {
|
||||
this.sinon.restore();
|
||||
this.sinon = null;
|
||||
}
|
||||
});
|
||||
});
|
||||
49
gaia/dev_apps/test-agent/common/test/style/style.css
Executable file
49
gaia/dev_apps/test-agent/common/test/style/style.css
Executable file
|
|
@ -0,0 +1,49 @@
|
|||
* { -moz-box-sizing: border-box; -webkit-box-sizing: border-box; box-sizing: border-box; }
|
||||
|
||||
body,
|
||||
html {
|
||||
height: 100%;
|
||||
}
|
||||
|
||||
body {
|
||||
font: 14px/1.5 "Helvetica Neue", Helvetica, Arial, sans-serif;
|
||||
}
|
||||
|
||||
body > iframe {
|
||||
border: 1px solid #CCC;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
}
|
||||
|
||||
#test-agent-ui {
|
||||
margin-bottom: 5px;
|
||||
}
|
||||
|
||||
#test-agent-ui ul {
|
||||
padding: none;
|
||||
border: none;
|
||||
clear: both;
|
||||
margin-top: 10px;
|
||||
}
|
||||
|
||||
#test-agent-ui li {
|
||||
list-style: none;
|
||||
padding: 0px;
|
||||
margin: 0px;
|
||||
float: left;
|
||||
width: 33%;
|
||||
}
|
||||
|
||||
#test-agent-ui li.active {
|
||||
font-weight: bold;
|
||||
}
|
||||
|
||||
#test-agent-ui button {
|
||||
clear: both;
|
||||
font-size: 14px;
|
||||
width: 100%;
|
||||
padding: 5px;
|
||||
height: 30px;
|
||||
border: 1px solid #E2E2E2;
|
||||
background-color: #FAFAFA;
|
||||
}
|
||||
591
gaia/dev_apps/test-agent/common/test/synthetic_gestures.js
Executable file
591
gaia/dev_apps/test-agent/common/test/synthetic_gestures.js
Executable file
|
|
@ -0,0 +1,591 @@
|
|||
//
|
||||
// SyntheticGestures.js: test utilities for generating gesture events.
|
||||
//
|
||||
// This module defines a SyntheticGestures object whose properties are
|
||||
// utility functions that generate sequences of synthetic events. The event
|
||||
// sequences are intended to be recognized as gestures by GestureDetector.js
|
||||
// so this module is useful for writing tests of UI code that uses
|
||||
// GestureDetector.js.
|
||||
//
|
||||
// SyntheticGestures.tap(): emit touch events to generate a tap gesture
|
||||
// on the specified target element. GestureDetector.js should emit
|
||||
// a tap event in response. SyntheticGesture.mousetap() is the same
|
||||
// but synthesizes mouse events instead.
|
||||
//
|
||||
// SyntheticGestures.dbltap(): emit touch events to generate a double
|
||||
// tap gesture on the specified target element. GestureDetector.js
|
||||
// should emit two tap events and a dbltap event in response.
|
||||
// SyntheticGesture.mousedbltap() is the same but synthesizes mouse
|
||||
// events instead of touch events.
|
||||
//
|
||||
// SyntheticGestures.swipe(): emit touch events for a basic one finger
|
||||
// touch, move, lift geture over a specifed target, with given start
|
||||
// and end coordinates and gesture duration. GestureDetector.js should
|
||||
// emit a series of pan events and a final swipe event.
|
||||
// SyntheticGestures.mouseswipe() is similar but uses mouse events
|
||||
// instead of touch events.
|
||||
//
|
||||
// SyntheticGestures.hold(): emit touch events for a touch/hold/move/release
|
||||
// gesture. GestureDetector should emit a holdstart/holdmove/holdend
|
||||
// sequence of events in response. SyntheticGesture.mousehold() is
|
||||
// similar but uses mouse events instead.
|
||||
//
|
||||
// SyntheticGestures.pinch(): emit touch events for a 2-finger pinch
|
||||
// open or pinch close gesture with given staring points for each
|
||||
// finger and a given scale factor. GestureDetector.js should
|
||||
// respond with a sequence of transform events culminating in one
|
||||
// with something very close to the specified scale factor.
|
||||
// Two-finger gestures can't be simulated with a mouse, so there is no
|
||||
// mouse-only version of this gesture, and the pinch() function will
|
||||
// fail on platforms that do not support touch events.
|
||||
//
|
||||
// The coordinates passed to these functions are relative to the
|
||||
// coordinate system of the target element. If you pass numbers, they
|
||||
// are interpreted as pixels relative to upper-left corner of the
|
||||
// target. If you pass a string, then it is parsed to a number. If a
|
||||
// string-valued coordinate ends with '%', then it is interpreted as a
|
||||
// percent of the target element's width or height, otherwise it is
|
||||
// interpreted as a number of pixels. For the swipe(), mouseswipe(),
|
||||
// hold(), and mousehold() functions, if the second two coordinates
|
||||
// are strings that begin with "+" or "-" then they are interpreted
|
||||
// relative to the first two coordinates. These relative values can be
|
||||
// specifed as percentages or as pixels.
|
||||
//
|
||||
var SyntheticGestures = (function() {
|
||||
// Send move events about this often
|
||||
var EVENT_INTERVAL = 30; // milliseconds
|
||||
|
||||
// The current array of all pending touches
|
||||
var touches = [];
|
||||
// For assigning unique ids to all touches
|
||||
var nextTouchId = 1000;
|
||||
|
||||
// Emit a touch event of the specified type, using touch as the sole
|
||||
// member of the changedTouches property
|
||||
function emitTouchEvent(type, touch) {
|
||||
var target = touch.target;
|
||||
var doc = target.ownerDocument;
|
||||
var win = doc.defaultView;
|
||||
|
||||
// Create the three lists of touches
|
||||
|
||||
// All touches in the same document
|
||||
var documentTouches = doc.createTouchList(touches.filter(function(t) {
|
||||
return t.target.ownerDocument === doc;
|
||||
}));
|
||||
// All touches on the same target
|
||||
var targetTouches = doc.createTouchList(touches.filter(function(t) {
|
||||
return t.target === target;
|
||||
}));
|
||||
// Just this one touch that changed
|
||||
var changedTouches = doc.createTouchList(touch);
|
||||
|
||||
// Create the event object
|
||||
var event = document.createEvent('TouchEvent');
|
||||
event.initTouchEvent(type,
|
||||
true, // bubles
|
||||
true, // cancellable
|
||||
win,
|
||||
0, // detail
|
||||
false, false, false, false, // no modifier keys
|
||||
documentTouches,
|
||||
targetTouches,
|
||||
changedTouches);
|
||||
|
||||
// Now dispatch it
|
||||
target.dispatchEvent(event);
|
||||
}
|
||||
|
||||
//
|
||||
// Dispatch a touchstart, touchmove+, touchend sequence of events to
|
||||
// simulate a single-finger touch of the specified duration.
|
||||
//
|
||||
// The xt and yt arguments are functions that must return x and y
|
||||
// coordinates of the touch at any time t, 0 <= t <= duration.
|
||||
// Alternatively, xt and yt can be two-element arrays [from,to], and
|
||||
// the mouse coordinates will be interpolated between those
|
||||
// coordinates. The then argument is an optional callback that will
|
||||
// be invoked after the touchend event is sent. In either case, the
|
||||
// coordinates should be window coordinates, relative to the viewport
|
||||
//
|
||||
// touch() can be called multiple times concurrently to simulate
|
||||
// 2 and 3 finger gestures.
|
||||
//
|
||||
// This is a low-level function that the higher-level touch gesture
|
||||
// utilities are built on. Most testing code will not need to call it.
|
||||
//
|
||||
function touch(target, duration, xt, yt, then) {
|
||||
var doc = target.ownerDocument;
|
||||
var win = doc.defaultView;
|
||||
var touchId = nextTouchId++;
|
||||
|
||||
var x = xt;
|
||||
if (typeof xt !== 'function') {
|
||||
x = function(t) { return xt[0] + t / duration * (xt[1] - xt[0]); };
|
||||
}
|
||||
|
||||
var y = yt;
|
||||
if (typeof yt !== 'function') {
|
||||
y = function(t) { return yt[0] + t / duration * (yt[1] - yt[0]); };
|
||||
}
|
||||
|
||||
// viewport coordinates
|
||||
var clientX = Math.round(x(0)), clientY = Math.round(y(0));
|
||||
|
||||
// document coordinates
|
||||
var pageX = clientX + win.pageXOffset,
|
||||
pageY = clientY + win.pageYOffset;
|
||||
|
||||
// screen coordinates
|
||||
var screenX = clientX + win.mozInnerScreenX,
|
||||
screenY = clientY + win.mozInnerScreenY;
|
||||
|
||||
// Remember the coordinates
|
||||
var lastX = clientX, lastY = clientY;
|
||||
|
||||
// Create the touch object we'll be
|
||||
var touch = doc.createTouch(win, target, touchId,
|
||||
pageX, pageY,
|
||||
screenX, screenY,
|
||||
clientX, clientY);
|
||||
|
||||
// Add this new touch to the list of touches
|
||||
touches.push(touch);
|
||||
|
||||
// Send the start event
|
||||
emitTouchEvent('touchstart', touch);
|
||||
|
||||
var startTime = Date.now();
|
||||
|
||||
setTimeout(nextEvent, EVENT_INTERVAL);
|
||||
|
||||
function nextEvent() {
|
||||
// Figure out if this is the last of the touchmove events
|
||||
var time = Date.now();
|
||||
var dt = time - startTime;
|
||||
var last = dt + EVENT_INTERVAL / 2 > duration;
|
||||
|
||||
// Find our touch object in the touches[] array.
|
||||
// Note that its index may have changed since we pushed it
|
||||
var touchIndex = touches.indexOf(touch);
|
||||
|
||||
// If this is the last move event, make sure we move all the way
|
||||
if (last)
|
||||
dt = duration;
|
||||
|
||||
// New coordinates of the touch
|
||||
clientX = Math.round(x(dt));
|
||||
clientY = Math.round(y(dt));
|
||||
|
||||
// If we've moved, send a move event
|
||||
if (clientX !== lastX || clientY !== lastY) { // If we moved
|
||||
lastX = clientX;
|
||||
lastY = clientY;
|
||||
pageX = clientX + win.pageXOffset;
|
||||
pageY = clientY + win.pageYOffset;
|
||||
screenX = clientX + win.mozInnerScreenX;
|
||||
screenY = clientY + win.mozInnerScreenY;
|
||||
|
||||
// Since we moved, we've got to create a new Touch object
|
||||
// with the new coordinates
|
||||
touch = doc.createTouch(win, target, touchId,
|
||||
pageX, pageY,
|
||||
screenX, screenY,
|
||||
clientX, clientY);
|
||||
|
||||
// Replace the old touch object with the new one
|
||||
touches[touchIndex] = touch;
|
||||
|
||||
// And send the touchmove event
|
||||
emitTouchEvent('touchmove', touch);
|
||||
}
|
||||
|
||||
// If that was the last move, send the touchend event
|
||||
// and call the callback
|
||||
if (last) {
|
||||
touches.splice(touchIndex, 1);
|
||||
emitTouchEvent('touchend', touch);
|
||||
if (then)
|
||||
setTimeout(then, 0);
|
||||
}
|
||||
// Otherwise, schedule the next event
|
||||
else {
|
||||
setTimeout(nextEvent, EVENT_INTERVAL);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function coordinates(target, x0, y0, x1, y1) {
|
||||
var coords = {};
|
||||
var box = target.getBoundingClientRect();
|
||||
|
||||
var tx0 = typeof x0;
|
||||
var ty0 = typeof y0;
|
||||
var tx1 = typeof x1;
|
||||
var ty1 = typeof y1;
|
||||
|
||||
function percent(s, x) {
|
||||
s = s.trim();
|
||||
var f = parseFloat(s);
|
||||
if (s[s.length - 1] === '%')
|
||||
f = f * x / 100;
|
||||
return f;
|
||||
}
|
||||
|
||||
function relative(s, x) {
|
||||
var factor;
|
||||
if (s[0] === '+')
|
||||
factor = 1;
|
||||
else
|
||||
factor = -1;
|
||||
return factor * percent(s.substring(1), x);
|
||||
}
|
||||
|
||||
if (tx0 === 'number')
|
||||
coords.x0 = box.left + x0;
|
||||
else if (tx0 === 'string')
|
||||
coords.x0 = box.left + percent(x0, box.width);
|
||||
|
||||
if (tx1 === 'number')
|
||||
coords.x1 = box.left + x1;
|
||||
else if (tx1 === 'string') {
|
||||
x1 = x1.trim();
|
||||
if (x1[0] === '+' || x1[0] === '-')
|
||||
coords.x1 = coords.x0 + relative(x1, box.width);
|
||||
else
|
||||
coords.x1 = box.left + percent(x1, box.width);
|
||||
}
|
||||
|
||||
if (ty0 === 'number')
|
||||
coords.y0 = box.top + y0;
|
||||
else if (ty0 === 'string')
|
||||
coords.y0 = box.top + percent(y0, box.height);
|
||||
|
||||
if (ty1 === 'number')
|
||||
coords.y1 = box.top + y1;
|
||||
else if (ty1 === 'string') {
|
||||
y1 = y1.trim();
|
||||
if (y1[0] === '+' || y1[0] === '-')
|
||||
coords.y1 = coords.y0 + relative(y1, box.height);
|
||||
else
|
||||
coords.y1 = box.top + percent(y1, box.height);
|
||||
}
|
||||
|
||||
return coords;
|
||||
}
|
||||
|
||||
|
||||
|
||||
// Dispatch touchstart and touchend events over the specified target
|
||||
// and then invoke the then() callback.
|
||||
// x and y are the relative to the viewport.
|
||||
// If not specified then the center of the target is used. t is the
|
||||
// optional amount of time between touchstart and touchend event.
|
||||
function tap(target, then, x, y, t) {
|
||||
if (!SyntheticGestures.touchSupported) {
|
||||
console.warn('tap: touch events not supported; using mouse instead');
|
||||
return mousetap(target, then, x, y, t);
|
||||
}
|
||||
|
||||
if (x == null)
|
||||
x = '50%';
|
||||
if (y == null)
|
||||
y = '50%';
|
||||
|
||||
var c = coordinates(target, x, y);
|
||||
|
||||
touch(target, t || 50, [c.x0, c.x0], [c.y0, c.y0], then);
|
||||
}
|
||||
|
||||
// Dispatch a dbltap gesture. The arguments are like those to tap()
|
||||
// except that interval is the time between taps rather than the time between
|
||||
// touchstart and touchend
|
||||
function dbltap(target, then, x, y, interval) {
|
||||
if (!SyntheticGestures.touchSupported) {
|
||||
console.warn('dbltap: touch events not supported; using mouse instead');
|
||||
return mousedbltap(target, then, x, y, interval);
|
||||
}
|
||||
|
||||
if (x == null)
|
||||
x = '50%';
|
||||
if (y == null)
|
||||
y = '50%';
|
||||
var c = coordinates(target, x, y);
|
||||
|
||||
touch(target, 25, [c.x0, c.x0], [c.y0, c.y0], function() {
|
||||
// When the first tap is done, start a timer for interval ms.
|
||||
setTimeout(function() {
|
||||
// After interval ms, send the second tap
|
||||
touch(target, 25, [c.x0, c.x0], [c.y0, c.y0], then);
|
||||
}, interval || 50);
|
||||
});
|
||||
}
|
||||
|
||||
// Swipe smoothly from (x1, y1) to (x2, y2) over duration ms
|
||||
// then invoke the then() callback.
|
||||
function swipe(target, x1, y1, x2, y2, duration, then) {
|
||||
if (!SyntheticGestures.touchSupported) {
|
||||
console.warn('swipe: touch events not supported; using mouse instead');
|
||||
return mouseswipe(target, x1, y1, x2, y2, duration, then);
|
||||
}
|
||||
|
||||
var c = coordinates(target, x1, y1, x2, y2);
|
||||
touch(target, duration || 200, [c.x0, c.x1], [c.y0, c.y1], then);
|
||||
}
|
||||
|
||||
// Begin a touch at x1,y1 and hold it for holdtime ms,
|
||||
// then move smoothly to x2,y2 over movetime ms, and then invoke then().
|
||||
function hold(target, holdtime, x1, y1, x2, y2, movetime, then) {
|
||||
if (!SyntheticGestures.touchSupported) {
|
||||
console.warn('hold: touch events not supported; using mouse instead');
|
||||
return mousehold(target, holdtime, x1, y1, x2, y2, movetime, then);
|
||||
}
|
||||
|
||||
if (!movetime)
|
||||
movetime = 200;
|
||||
|
||||
var c = coordinates(target, x1, y1, x2, y2);
|
||||
|
||||
touch(target, holdtime + movetime,
|
||||
function(t) { // x coordinate a function of t
|
||||
if (t < holdtime)
|
||||
return c.x0;
|
||||
else
|
||||
return c.x0 + (t - holdtime) / movetime * (c.x1 - c.x0);
|
||||
},
|
||||
function(t) { // y coordinate a function of t
|
||||
if (t < holdtime)
|
||||
return c.y0;
|
||||
else
|
||||
return c.y0 + (t - holdtime) / movetime * (c.y1 - c.y0);
|
||||
},
|
||||
then);
|
||||
}
|
||||
|
||||
// Begin touches at (x1,y1) and (x2,y2) and then move them smoothly toward
|
||||
// or away from each other by the specified scale factor over duration ms.
|
||||
// Finally, invoke the then() callback
|
||||
function pinch(target, x1, y1, x2, y2, scale, duration, then) {
|
||||
if (!SyntheticGestures.touchSupported) {
|
||||
console.error('pinch: touch events not supported on this platform');
|
||||
return;
|
||||
}
|
||||
|
||||
var c1 = coordinates(target, x1, y1);
|
||||
var c2 = coordinates(target, x2, y2);
|
||||
x1 = c1.x0;
|
||||
y1 = c1.y0;
|
||||
x2 = c2.x0;
|
||||
y2 = c2.y0;
|
||||
|
||||
var xmid = (x1 + x2) / 2;
|
||||
var ymid = (y1 + y2) / 2;
|
||||
|
||||
var newx1 = Math.round(xmid + (x1 - xmid) * scale);
|
||||
var newy1 = Math.round(ymid + (y1 - ymid) * scale);
|
||||
var newx2 = Math.round(xmid + (x2 - xmid) * scale);
|
||||
var newy2 = Math.round(ymid + (y2 - ymid) * scale);
|
||||
|
||||
// Emit two concurrent series of touch events.a
|
||||
// The first one is simple:
|
||||
touch(target, duration, [x1, newx1], [y1, newy1]);
|
||||
|
||||
// The second touch moves twice as fast and then holds still and
|
||||
// lasts for an extra 200ms to ensure that both moves complete
|
||||
// before either finger lifts up. Hopefully this means we'll get
|
||||
// the full scale effect
|
||||
touch(target, duration + 200,
|
||||
function(t) {
|
||||
if (t < duration / 2)
|
||||
return x2 + t * 2 / duration * (newx2 - x2);
|
||||
else
|
||||
return newx2;
|
||||
},
|
||||
function(t) {
|
||||
if (t < duration / 2)
|
||||
return y2 + t * 2 / duration * (newy2 - y2);
|
||||
else
|
||||
return newy2;
|
||||
},
|
||||
then);
|
||||
}
|
||||
|
||||
//
|
||||
// Dispatch a mousedown, mousemove+, mouseup sequence of events over
|
||||
// duration ms to simulate a click-drag motion of the mouse or trackpad.
|
||||
//
|
||||
// The xt and yt arguments are [x0,x1], [y0,y1] or are functions
|
||||
// of t that specify the mouse coordinates at time t just as for the
|
||||
// touch() function.
|
||||
//
|
||||
// Unlike the touch() function, this drag() function takes a document
|
||||
// argument instead of an element argument, and always determines the
|
||||
// target of its events using document.elementFromPoint(). Callers
|
||||
// must ensure that xt and yt specify an initial point inside the
|
||||
// desired target element.
|
||||
//
|
||||
// This is a low-level function that the higher-level mouse gesture
|
||||
// utilities are built on. Most testing code will not need to call it.
|
||||
//
|
||||
function drag(doc, duration, xt, yt, then, detail, button) {
|
||||
var win = doc.defaultView;
|
||||
detail = detail || 1;
|
||||
button = button || 0;
|
||||
|
||||
var x = xt;
|
||||
if (typeof xt !== 'function') {
|
||||
x = function(t) { return xt[0] + t / duration * (xt[1] - xt[0]); };
|
||||
}
|
||||
|
||||
var y = yt;
|
||||
if (typeof yt !== 'function') {
|
||||
y = function(t) { return yt[0] + t / duration * (yt[1] - yt[0]); };
|
||||
}
|
||||
|
||||
// viewport coordinates
|
||||
var clientX = Math.round(x(0)), clientY = Math.round(y(0));
|
||||
|
||||
// Remember the coordinates
|
||||
var lastX = clientX, lastY = clientY;
|
||||
|
||||
// Send the initial mousedown event
|
||||
mouseEvent('mousedown', clientX, clientY);
|
||||
|
||||
// Now send a sequence of mousemove events followed by mouse up
|
||||
var startTime = Date.now();
|
||||
setTimeout(nextEvent, EVENT_INTERVAL);
|
||||
|
||||
// Send a mouse event of the specified type to whatever element is
|
||||
// at the specified coordinates in the viewport.
|
||||
function mouseEvent(type, clientX, clientY) {
|
||||
// Figure out what element the mouse would be over at (x,y)
|
||||
var target = doc.elementFromPoint(clientX, clientY);
|
||||
// Create an event
|
||||
var mousedown = doc.createEvent('MouseEvent');
|
||||
// Initialize it
|
||||
mousedown.initMouseEvent(type,
|
||||
true, true, // bubbles, cancellable
|
||||
win, detail, // window, click count
|
||||
clientX + win.mozInnerScreenX,
|
||||
clientY + win.mozInnerScreenY,
|
||||
clientX, clientY,
|
||||
false, false, false, false, // keyboard modifiers
|
||||
button, null); // mouse button, related target
|
||||
// And dispatch it on the target element
|
||||
target.dispatchEvent(mousedown);
|
||||
}
|
||||
|
||||
function nextEvent() {
|
||||
// Figure out if we've sent all of the mousemove events
|
||||
var time = Date.now();
|
||||
var dt = time - startTime;
|
||||
|
||||
// Is this the last move event we'll be sending?
|
||||
var last = dt + EVENT_INTERVAL / 2 > duration;
|
||||
|
||||
// If so make sure we move all the way
|
||||
if (last)
|
||||
dt = duration;
|
||||
|
||||
// New coordinates of the touch
|
||||
clientX = Math.round(x(dt));
|
||||
clientY = Math.round(y(dt));
|
||||
|
||||
// If we moved, send a move event
|
||||
if (clientX !== lastX || clientY !== lastY) { // If we moved
|
||||
lastX = clientX;
|
||||
lastY = clientY;
|
||||
mouseEvent('mousemove', clientX, clientY);
|
||||
}
|
||||
|
||||
// If this was the last move, send a mouse up and call the callback
|
||||
// Otherwise, schedule the next move event
|
||||
if (last) {
|
||||
mouseEvent('mouseup', lastX, lastY);
|
||||
if (then)
|
||||
setTimeout(then, 0);
|
||||
}
|
||||
else {
|
||||
setTimeout(nextEvent, EVENT_INTERVAL);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Send a mousedown/mouseup pair
|
||||
// XXX: will the browser automatically follow this with a click event?
|
||||
function mousetap(target, then, x, y, t) {
|
||||
if (x == null)
|
||||
x = '50%';
|
||||
if (y == null)
|
||||
y = '50%';
|
||||
var c = coordinates(target, x, y);
|
||||
|
||||
drag(target.ownerDocument, t || 50, [c.x0, c.x0], [c.y0, c.y0], then);
|
||||
}
|
||||
|
||||
// Dispatch a dbltap gesture. The arguments are like those to tap()
|
||||
// except that interval is the time between taps rather than the time between
|
||||
// touchstart and touchend
|
||||
function mousedbltap(target, then, x, y, interval) {
|
||||
if (x == null)
|
||||
x = '50%';
|
||||
if (y == null)
|
||||
y = '50%';
|
||||
var c = coordinates(target, x, y);
|
||||
|
||||
drag(target.ownerDocument, 25, [c.x0, c.x0], [c.y0, c.y0], function() {
|
||||
// When the first tap is done, start a timer for interval ms.
|
||||
setTimeout(function() {
|
||||
// After interval ms, send the second tap with the click count set to 2.
|
||||
drag(target.ownerDocument, 25, [c.x0, c.x0], [c.y0, c.y0], then, 2);
|
||||
}, interval || 50);
|
||||
});
|
||||
}
|
||||
|
||||
// Swipe smoothly with the mouse from (x1, y1) to (x2, y2) over duration ms
|
||||
// then invoke the then() callback.
|
||||
function mouseswipe(target, x1, y1, x2, y2, duration, then) {
|
||||
var c = coordinates(target, x1, y1, x2, y2);
|
||||
drag(target.ownerDocument, duration || 200,
|
||||
[c.x0, c.x1], [c.y0, c.y1], then);
|
||||
}
|
||||
|
||||
// Mousedown at x1,y1 and hold it for holdtime ms,
|
||||
// then move smoothly to x2,y2 over movetime ms, then mouse up,
|
||||
// and then invoke then().
|
||||
function mousehold(target, holdtime, x1, y1, x2, y2, movetime, then) {
|
||||
if (!movetime)
|
||||
movetime = 200;
|
||||
|
||||
var c = coordinates(target, x1, y1, x2, y2);
|
||||
|
||||
drag(target.ownerDocument, holdtime + movetime,
|
||||
function(t) { // x coordinate a function of t
|
||||
if (t < holdtime)
|
||||
return c.x0;
|
||||
else
|
||||
return c.x0 + (t - holdtime) / movetime * (c.x1 - c.x0);
|
||||
},
|
||||
function(t) { // y coordinate a function of t
|
||||
if (t < holdtime)
|
||||
return c.y0;
|
||||
else
|
||||
return c.y0 + (t - holdtime) / movetime * (c.y1 - c.y0);
|
||||
},
|
||||
then);
|
||||
}
|
||||
|
||||
return {
|
||||
// in the context of node, we don't even have document....
|
||||
touchSupported: (typeof document !== 'undefined' && typeof document.createTouch === 'function'),
|
||||
tap: tap,
|
||||
mousetap: mousetap,
|
||||
dbltap: dbltap,
|
||||
mousedbltap: mousedbltap,
|
||||
swipe: swipe,
|
||||
mouseswipe: mouseswipe,
|
||||
hold: hold,
|
||||
mousehold: mousehold,
|
||||
pinch: pinch // There is no mouse-based alternative to this
|
||||
};
|
||||
}());
|
||||
47
gaia/dev_apps/test-agent/common/test/template.js
Executable file
47
gaia/dev_apps/test-agent/common/test/template.js
Executable file
|
|
@ -0,0 +1,47 @@
|
|||
(function(window) {
|
||||
if (typeof(testSupport) === 'undefined') {
|
||||
testSupport = {};
|
||||
}
|
||||
|
||||
function template(url) {
|
||||
url = TestUrlResolver.resolve(url);
|
||||
|
||||
teardown(function() {
|
||||
var test = document.getElementById('test');
|
||||
if (test) {
|
||||
test.parentNode.removeChild(test);
|
||||
}
|
||||
});
|
||||
|
||||
setup(function(done) {
|
||||
var test = document.getElementById('test');
|
||||
if (!test) {
|
||||
test = document.createElement('div');
|
||||
test.id = 'test';
|
||||
document.body.appendChild(test);
|
||||
}
|
||||
|
||||
var xhr = new XMLHttpRequest();
|
||||
xhr.open('GET', url, true);
|
||||
xhr.onreadystatechange = function(event) {
|
||||
if (xhr.readyState === 4) {
|
||||
if (xhr.status === 200) {
|
||||
var wrapper = document.createElement('div');
|
||||
wrapper.innerHTML = xhr.responseText;
|
||||
test.appendChild(wrapper.firstChild);
|
||||
done();
|
||||
} else {
|
||||
done(new Error(
|
||||
'could not load template file "' + url + '"'
|
||||
));
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
xhr.send(null);
|
||||
});
|
||||
}
|
||||
|
||||
testSupport.template = template;
|
||||
|
||||
}(this));
|
||||
47
gaia/dev_apps/test-agent/common/test/test_url_resolver.js
Executable file
47
gaia/dev_apps/test-agent/common/test/test_url_resolver.js
Executable file
|
|
@ -0,0 +1,47 @@
|
|||
(function(window) {
|
||||
var TestUrlResolver = (function() {
|
||||
|
||||
let location = window.location,
|
||||
domainParts = window.location.host.split('.'),
|
||||
addSubdomain = false,
|
||||
prefix = null;
|
||||
|
||||
if (domainParts.length > 2) {
|
||||
//running from gaiamobile.org subdomain
|
||||
addSubdomain = true;
|
||||
} else {
|
||||
//running from localhost
|
||||
addSubdomain = false;
|
||||
}
|
||||
|
||||
return {
|
||||
PARSE_REGEX: /^(\/?)([\w\d-]+)\/(.*)/,
|
||||
|
||||
parse: function tur_parse(url) {
|
||||
if (addSubdomain) {
|
||||
let parsedUrl = this.PARSE_REGEX.exec(url);
|
||||
let domain = location.protocol + '//' + parsedUrl[2] + '.';
|
||||
|
||||
domain += domainParts.slice(1).join('.') + '/';
|
||||
|
||||
return {
|
||||
domain: domain,
|
||||
host: parsedUrl[2],
|
||||
url: parsedUrl[3]
|
||||
};
|
||||
|
||||
} else {
|
||||
throw new Error('you must run tests using real domains on localhost');
|
||||
}
|
||||
},
|
||||
|
||||
resolve: function tur_resolve(url) {
|
||||
var parts = this.parse(url);
|
||||
return parts.domain + parts.url;
|
||||
}
|
||||
};
|
||||
|
||||
}());
|
||||
|
||||
window.TestUrlResolver = TestUrlResolver;
|
||||
}(this));
|
||||
7346
gaia/dev_apps/test-agent/common/vendor/blanket/blanket.js
vendored
Executable file
7346
gaia/dev_apps/test-agent/common/vendor/blanket/blanket.js
vendored
Executable file
File diff suppressed because one or more lines are too long
72
gaia/dev_apps/test-agent/common/vendor/blanket/test-agent-blanket.js
vendored
Executable file
72
gaia/dev_apps/test-agent/common/vendor/blanket/test-agent-blanket.js
vendored
Executable file
|
|
@ -0,0 +1,72 @@
|
|||
(function() {
|
||||
|
||||
if (!mocha) {
|
||||
throw new Exception("mocha library does not exist in global namespace!");
|
||||
}
|
||||
|
||||
/*
|
||||
* Mocha Events:
|
||||
*
|
||||
* - `start` execution started
|
||||
* - `end` execution complete
|
||||
* - `suite` (suite) test suite execution started
|
||||
* - `suite end` (suite) all tests (and sub-suites) have finished
|
||||
* - `test` (test) test execution started
|
||||
* - `test end` (test) test completed
|
||||
* - `hook` (hook) hook execution started
|
||||
* - `hook end` (hook) hook complete
|
||||
* - `pass` (test) test passed
|
||||
* - `fail` (test, err) test failed
|
||||
*
|
||||
*/
|
||||
|
||||
var OriginalReporter = mocha._reporter;
|
||||
|
||||
var BlanketReporter = function(runner) {
|
||||
runner.on('start', function() {
|
||||
blanket.setupCoverage();
|
||||
});
|
||||
|
||||
runner.on('end', function() {
|
||||
blanket.onTestsDone();
|
||||
});
|
||||
|
||||
runner.on('suite', function() {
|
||||
blanket.onModuleStart();
|
||||
});
|
||||
|
||||
runner.on('test', function() {
|
||||
blanket.onTestStart();
|
||||
});
|
||||
|
||||
runner.on('test end', function(test) {
|
||||
blanket.onTestDone(test.parent.tests.length, test.state === 'passed');
|
||||
});
|
||||
|
||||
// NOTE: this is an instance of BlanketReporter
|
||||
OriginalReporter.apply(this, arguments);
|
||||
};
|
||||
|
||||
mocha.reporter(BlanketReporter);
|
||||
|
||||
var oldRun = mocha.run,
|
||||
oldCallback = null;
|
||||
|
||||
mocha.run = function(finishCallback) {
|
||||
oldCallback = finishCallback;
|
||||
console.log("waiting for blanket...");
|
||||
};
|
||||
|
||||
blanket.beforeStartTestRunner({
|
||||
callback: function() {
|
||||
if (!blanket.options("existingRequireJS")) {
|
||||
oldRun(oldCallback);
|
||||
}
|
||||
mocha.run = oldRun;
|
||||
},
|
||||
bindEvent: function(startEvent) {
|
||||
window.addEventListener("mocha-run", startEvent, false);
|
||||
}
|
||||
});
|
||||
|
||||
})();
|
||||
370
gaia/dev_apps/test-agent/common/vendor/chai/chai-as-promised.js
vendored
Executable file
370
gaia/dev_apps/test-agent/common/vendor/chai/chai-as-promised.js
vendored
Executable file
|
|
@ -0,0 +1,370 @@
|
|||
(function () {
|
||||
"use strict";
|
||||
|
||||
// Module systems magic dance.
|
||||
|
||||
/* istanbul ignore else */
|
||||
if (typeof require === "function" && typeof exports === "object" && typeof module === "object") {
|
||||
// NodeJS
|
||||
module.exports = chaiAsPromised;
|
||||
} else if (typeof define === "function" && define.amd) {
|
||||
// AMD
|
||||
define(function () {
|
||||
return chaiAsPromised;
|
||||
});
|
||||
} else {
|
||||
/*global self: false */
|
||||
|
||||
// Other environment (usually <script> tag): plug in to global chai instance directly.
|
||||
chai.use(chaiAsPromised);
|
||||
|
||||
// Expose as a property of the global object so that consumers can configure the `transferPromiseness` property.
|
||||
self.chaiAsPromised = chaiAsPromised;
|
||||
}
|
||||
|
||||
chaiAsPromised.transferPromiseness = function (assertion, promise) {
|
||||
assertion.then = promise.then.bind(promise);
|
||||
};
|
||||
|
||||
function chaiAsPromised(chai, utils) {
|
||||
var Assertion = chai.Assertion;
|
||||
var assert = chai.assert;
|
||||
|
||||
function isJQueryPromise(thenable) {
|
||||
return typeof thenable.always === "function" &&
|
||||
typeof thenable.done === "function" &&
|
||||
typeof thenable.fail === "function" &&
|
||||
typeof thenable.pipe === "function" &&
|
||||
typeof thenable.progress === "function" &&
|
||||
typeof thenable.state === "function";
|
||||
}
|
||||
|
||||
function assertIsAboutPromise(assertion) {
|
||||
if (typeof assertion._obj.then !== "function") {
|
||||
throw new TypeError(utils.inspect(assertion._obj) + " is not a thenable.");
|
||||
}
|
||||
if (isJQueryPromise(assertion._obj)) {
|
||||
throw new TypeError("Chai as Promised is incompatible with jQuery's thenables, sorry! Please use a " +
|
||||
"Promises/A+ compatible library (see http://promisesaplus.com/).");
|
||||
}
|
||||
}
|
||||
|
||||
function method(name, asserter) {
|
||||
utils.addMethod(Assertion.prototype, name, function () {
|
||||
assertIsAboutPromise(this);
|
||||
return asserter.apply(this, arguments);
|
||||
});
|
||||
}
|
||||
|
||||
function property(name, asserter) {
|
||||
utils.addProperty(Assertion.prototype, name, function () {
|
||||
assertIsAboutPromise(this);
|
||||
return asserter.apply(this, arguments);
|
||||
});
|
||||
}
|
||||
|
||||
function doNotify(promise, done) {
|
||||
promise.then(function () { done(); }, done);
|
||||
}
|
||||
|
||||
// These are for clarity and to bypass Chai refusing to allow `undefined` as actual when used with `assert`.
|
||||
function assertIfNegated(assertion, message, extra) {
|
||||
assertion.assert(true, null, message, extra.expected, extra.actual);
|
||||
}
|
||||
|
||||
function assertIfNotNegated(assertion, message, extra) {
|
||||
assertion.assert(false, message, null, extra.expected, extra.actual);
|
||||
}
|
||||
|
||||
function getBasePromise(assertion) {
|
||||
// We need to chain subsequent asserters on top of ones in the chain already (consider
|
||||
// `eventually.have.property("foo").that.equals("bar")`), only running them after the existing ones pass.
|
||||
// So the first base-promise is `assertion._obj`, but after that we use the assertions themselves, i.e.
|
||||
// previously derived promises, to chain off of.
|
||||
return typeof assertion.then === "function" ? assertion : assertion._obj;
|
||||
}
|
||||
|
||||
// Grab these first, before we modify `Assertion.prototype`.
|
||||
|
||||
var propertyNames = Object.getOwnPropertyNames(Assertion.prototype);
|
||||
|
||||
var propertyDescs = {};
|
||||
propertyNames.forEach(function (name) {
|
||||
propertyDescs[name] = Object.getOwnPropertyDescriptor(Assertion.prototype, name);
|
||||
});
|
||||
|
||||
property("fulfilled", function () {
|
||||
var that = this;
|
||||
var derivedPromise = getBasePromise(that).then(
|
||||
function (value) {
|
||||
that._obj = value;
|
||||
assertIfNegated(that,
|
||||
"expected promise not to be fulfilled but it was fulfilled with #{act}",
|
||||
{ actual: value });
|
||||
return value;
|
||||
},
|
||||
function (reason) {
|
||||
assertIfNotNegated(that,
|
||||
"expected promise to be fulfilled but it was rejected with #{act}",
|
||||
{ actual: reason });
|
||||
}
|
||||
);
|
||||
|
||||
chaiAsPromised.transferPromiseness(that, derivedPromise);
|
||||
});
|
||||
|
||||
property("rejected", function () {
|
||||
var that = this;
|
||||
var derivedPromise = getBasePromise(that).then(
|
||||
function (value) {
|
||||
that._obj = value;
|
||||
assertIfNotNegated(that,
|
||||
"expected promise to be rejected but it was fulfilled with #{act}",
|
||||
{ actual: value });
|
||||
return value;
|
||||
},
|
||||
function (reason) {
|
||||
assertIfNegated(that,
|
||||
"expected promise not to be rejected but it was rejected with #{act}",
|
||||
{ actual: reason });
|
||||
|
||||
// Return the reason, transforming this into a fulfillment, to allow further assertions, e.g.
|
||||
// `promise.should.be.rejected.and.eventually.equal("reason")`.
|
||||
return reason;
|
||||
}
|
||||
);
|
||||
|
||||
chaiAsPromised.transferPromiseness(that, derivedPromise);
|
||||
});
|
||||
|
||||
method("rejectedWith", function (Constructor, message) {
|
||||
var desiredReason = null;
|
||||
var constructorName = null;
|
||||
|
||||
if (Constructor instanceof RegExp || typeof Constructor === "string") {
|
||||
message = Constructor;
|
||||
Constructor = null;
|
||||
} else if (Constructor && Constructor instanceof Error) {
|
||||
desiredReason = Constructor;
|
||||
Constructor = null;
|
||||
message = null;
|
||||
} else if (typeof Constructor === "function") {
|
||||
constructorName = (new Constructor()).name;
|
||||
} else {
|
||||
Constructor = null;
|
||||
}
|
||||
|
||||
var that = this;
|
||||
var derivedPromise = getBasePromise(that).then(
|
||||
function (value) {
|
||||
var assertionMessage = null;
|
||||
var expected = null;
|
||||
|
||||
if (Constructor) {
|
||||
assertionMessage = "expected promise to be rejected with #{exp} but it was fulfilled with " +
|
||||
"#{act}";
|
||||
expected = constructorName;
|
||||
} else if (message) {
|
||||
var verb = message instanceof RegExp ? "matching" : "including";
|
||||
assertionMessage = "expected promise to be rejected with an error " + verb + " #{exp} but it " +
|
||||
"was fulfilled with #{act}";
|
||||
expected = message;
|
||||
} else if (desiredReason) {
|
||||
assertionMessage = "expected promise to be rejected with #{exp} but it was fulfilled with " +
|
||||
"#{act}";
|
||||
expected = desiredReason;
|
||||
}
|
||||
|
||||
that._obj = value;
|
||||
|
||||
assertIfNotNegated(that, assertionMessage, { expected: expected, actual: value });
|
||||
},
|
||||
function (reason) {
|
||||
if (Constructor) {
|
||||
that.assert(reason instanceof Constructor,
|
||||
"expected promise to be rejected with #{exp} but it was rejected with #{act}",
|
||||
"expected promise not to be rejected with #{exp} but it was rejected with #{act}",
|
||||
constructorName,
|
||||
reason);
|
||||
}
|
||||
|
||||
var reasonMessage = utils.type(reason) === "object" && "message" in reason ?
|
||||
reason.message :
|
||||
"" + reason;
|
||||
if (message && reasonMessage !== null && reasonMessage !== undefined) {
|
||||
if (message instanceof RegExp) {
|
||||
that.assert(message.test(reasonMessage),
|
||||
"expected promise to be rejected with an error matching #{exp} but got #{act}",
|
||||
"expected promise not to be rejected with an error matching #{exp}",
|
||||
message,
|
||||
reasonMessage);
|
||||
}
|
||||
if (typeof message === "string") {
|
||||
that.assert(reasonMessage.indexOf(message) !== -1,
|
||||
"expected promise to be rejected with an error including #{exp} but got #{act}",
|
||||
"expected promise not to be rejected with an error including #{exp}",
|
||||
message,
|
||||
reasonMessage);
|
||||
}
|
||||
}
|
||||
|
||||
if (desiredReason) {
|
||||
that.assert(reason === desiredReason,
|
||||
"expected promise to be rejected with #{exp} but it was rejected with #{act}",
|
||||
"expected promise not to be rejected with #{exp}",
|
||||
desiredReason,
|
||||
reason);
|
||||
}
|
||||
}
|
||||
);
|
||||
|
||||
chaiAsPromised.transferPromiseness(that, derivedPromise);
|
||||
});
|
||||
|
||||
property("eventually", function () {
|
||||
utils.flag(this, "eventually", true);
|
||||
});
|
||||
|
||||
method("notify", function (done) {
|
||||
doNotify(getBasePromise(this), done);
|
||||
});
|
||||
|
||||
method("become", function (value) {
|
||||
return this.eventually.deep.equal(value);
|
||||
});
|
||||
|
||||
////////
|
||||
// `eventually`
|
||||
|
||||
// We need to be careful not to trigger any getters, thus `Object.getOwnPropertyDescriptor` usage.
|
||||
var methodNames = propertyNames.filter(function (name) {
|
||||
return name !== "assert" && typeof propertyDescs[name].value === "function";
|
||||
});
|
||||
|
||||
methodNames.forEach(function (methodName) {
|
||||
Assertion.overwriteMethod(methodName, function (originalMethod) {
|
||||
return function () {
|
||||
doAsserterAsyncAndAddThen(originalMethod, this, arguments);
|
||||
};
|
||||
});
|
||||
});
|
||||
|
||||
var getterNames = propertyNames.filter(function (name) {
|
||||
return name !== "_obj" && typeof propertyDescs[name].get === "function";
|
||||
});
|
||||
|
||||
getterNames.forEach(function (getterName) {
|
||||
var propertyDesc = propertyDescs[getterName];
|
||||
|
||||
// Chainable methods are things like `an`, which can work both for `.should.be.an.instanceOf` and as
|
||||
// `should.be.an("object")`. We need to handle those specially.
|
||||
var isChainableMethod = false;
|
||||
try {
|
||||
isChainableMethod = typeof propertyDesc.get.call({}) === "function";
|
||||
} catch (e) { }
|
||||
|
||||
if (isChainableMethod) {
|
||||
Assertion.addChainableMethod(
|
||||
getterName,
|
||||
function () {
|
||||
var assertion = this;
|
||||
function originalMethod() {
|
||||
return propertyDesc.get.call(assertion).apply(assertion, arguments);
|
||||
}
|
||||
doAsserterAsyncAndAddThen(originalMethod, this, arguments);
|
||||
},
|
||||
function () {
|
||||
var originalGetter = propertyDesc.get;
|
||||
doAsserterAsyncAndAddThen(originalGetter, this);
|
||||
}
|
||||
);
|
||||
} else {
|
||||
Assertion.overwriteProperty(getterName, function (originalGetter) {
|
||||
return function () {
|
||||
doAsserterAsyncAndAddThen(originalGetter, this);
|
||||
};
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
function doAsserterAsyncAndAddThen(asserter, assertion, args) {
|
||||
// Since we're intercepting all methods/properties, we need to just pass through if they don't want
|
||||
// `eventually`, or if we've already fulfilled the promise (see below).
|
||||
if (!utils.flag(assertion, "eventually")) {
|
||||
return asserter.apply(assertion, args);
|
||||
}
|
||||
|
||||
var derivedPromise = getBasePromise(assertion).then(function (value) {
|
||||
// Set up the environment for the asserter to actually run: `_obj` should be the fulfillment value, and
|
||||
// now that we have the value, we're no longer in "eventually" mode, so we won't run any of this code,
|
||||
// just the base Chai code that we get to via the short-circuit above.
|
||||
assertion._obj = value;
|
||||
utils.flag(assertion, "eventually", false);
|
||||
asserter.apply(assertion, args);
|
||||
|
||||
// Because asserters, for example `property`, can change the value of `_obj` (i.e. change the "object"
|
||||
// flag), we need to communicate this value change to subsequent chained asserters. Since we build a
|
||||
// promise chain paralleling the asserter chain, we can use it to communicate such changes.
|
||||
return assertion._obj;
|
||||
});
|
||||
|
||||
chaiAsPromised.transferPromiseness(assertion, derivedPromise);
|
||||
}
|
||||
|
||||
///////
|
||||
// Now use the `Assertion` framework to build an `assert` interface.
|
||||
var originalAssertMethods = Object.getOwnPropertyNames(assert).filter(function (propName) {
|
||||
return typeof assert[propName] === "function";
|
||||
});
|
||||
|
||||
assert.isFulfilled = function (promise, message) {
|
||||
return (new Assertion(promise, message)).to.be.fulfilled;
|
||||
};
|
||||
|
||||
assert.isRejected = function (promise, toTestAgainst, message) {
|
||||
if (typeof toTestAgainst === "string") {
|
||||
message = toTestAgainst;
|
||||
toTestAgainst = undefined;
|
||||
}
|
||||
|
||||
var assertion = (new Assertion(promise, message));
|
||||
return toTestAgainst !== undefined ? assertion.to.be.rejectedWith(toTestAgainst) : assertion.to.be.rejected;
|
||||
};
|
||||
|
||||
assert.becomes = function (promise, value, message) {
|
||||
return assert.eventually.deepEqual(promise, value, message);
|
||||
};
|
||||
|
||||
assert.doesNotBecome = function (promise, value, message) {
|
||||
return assert.eventually.notDeepEqual(promise, value, message);
|
||||
};
|
||||
|
||||
assert.eventually = {};
|
||||
originalAssertMethods.forEach(function (assertMethodName) {
|
||||
assert.eventually[assertMethodName] = function (promise) {
|
||||
var otherArgs = Array.prototype.slice.call(arguments, 1);
|
||||
|
||||
var customRejectionHandler;
|
||||
var message = arguments[assert[assertMethodName].length - 1];
|
||||
if (typeof message === "string") {
|
||||
customRejectionHandler = function (reason) {
|
||||
throw new chai.AssertionError(message + "\n\nOriginal reason: " + utils.inspect(reason));
|
||||
};
|
||||
}
|
||||
|
||||
var returnedPromise = promise.then(
|
||||
function (fulfillmentValue) {
|
||||
return assert[assertMethodName].apply(assert, [fulfillmentValue].concat(otherArgs));
|
||||
},
|
||||
customRejectionHandler
|
||||
);
|
||||
|
||||
returnedPromise.notify = function (done) {
|
||||
doNotify(returnedPromise, done);
|
||||
};
|
||||
|
||||
return returnedPromise;
|
||||
};
|
||||
});
|
||||
}
|
||||
}());
|
||||
4783
gaia/dev_apps/test-agent/common/vendor/chai/chai.js
vendored
Executable file
4783
gaia/dev_apps/test-agent/common/vendor/chai/chai.js
vendored
Executable file
File diff suppressed because it is too large
Load diff
270
gaia/dev_apps/test-agent/common/vendor/mocha/mocha.css
vendored
Executable file
270
gaia/dev_apps/test-agent/common/vendor/mocha/mocha.css
vendored
Executable file
|
|
@ -0,0 +1,270 @@
|
|||
@charset "utf-8";
|
||||
|
||||
body {
|
||||
margin:0;
|
||||
}
|
||||
|
||||
#mocha {
|
||||
font: 20px/1.5 "Helvetica Neue", Helvetica, Arial, sans-serif;
|
||||
margin: 60px 50px;
|
||||
}
|
||||
|
||||
#mocha ul,
|
||||
#mocha li {
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
}
|
||||
|
||||
#mocha ul {
|
||||
list-style: none;
|
||||
}
|
||||
|
||||
#mocha h1,
|
||||
#mocha h2 {
|
||||
margin: 0;
|
||||
}
|
||||
|
||||
#mocha h1 {
|
||||
margin-top: 15px;
|
||||
font-size: 1em;
|
||||
font-weight: 200;
|
||||
}
|
||||
|
||||
#mocha h1 a {
|
||||
text-decoration: none;
|
||||
color: inherit;
|
||||
}
|
||||
|
||||
#mocha h1 a:hover {
|
||||
text-decoration: underline;
|
||||
}
|
||||
|
||||
#mocha .suite .suite h1 {
|
||||
margin-top: 0;
|
||||
font-size: .8em;
|
||||
}
|
||||
|
||||
#mocha .hidden {
|
||||
display: none;
|
||||
}
|
||||
|
||||
#mocha h2 {
|
||||
font-size: 12px;
|
||||
font-weight: normal;
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
#mocha .suite {
|
||||
margin-left: 15px;
|
||||
}
|
||||
|
||||
#mocha .test {
|
||||
margin-left: 15px;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
#mocha .test.pending:hover h2::after {
|
||||
content: '(pending)';
|
||||
font-family: arial, sans-serif;
|
||||
}
|
||||
|
||||
#mocha .test.pass.medium .duration {
|
||||
background: #c09853;
|
||||
}
|
||||
|
||||
#mocha .test.pass.slow .duration {
|
||||
background: #b94a48;
|
||||
}
|
||||
|
||||
#mocha .test.pass::before {
|
||||
content: '✓';
|
||||
font-size: 12px;
|
||||
display: block;
|
||||
float: left;
|
||||
margin-right: 5px;
|
||||
color: #00d6b2;
|
||||
}
|
||||
|
||||
#mocha .test.pass .duration {
|
||||
font-size: 9px;
|
||||
margin-left: 5px;
|
||||
padding: 2px 5px;
|
||||
color: #fff;
|
||||
-webkit-box-shadow: inset 0 1px 1px rgba(0,0,0,.2);
|
||||
-moz-box-shadow: inset 0 1px 1px rgba(0,0,0,.2);
|
||||
box-shadow: inset 0 1px 1px rgba(0,0,0,.2);
|
||||
-webkit-border-radius: 5px;
|
||||
-moz-border-radius: 5px;
|
||||
-ms-border-radius: 5px;
|
||||
-o-border-radius: 5px;
|
||||
border-radius: 5px;
|
||||
}
|
||||
|
||||
#mocha .test.pass.fast .duration {
|
||||
display: none;
|
||||
}
|
||||
|
||||
#mocha .test.pending {
|
||||
color: #0b97c4;
|
||||
}
|
||||
|
||||
#mocha .test.pending::before {
|
||||
content: '◦';
|
||||
color: #0b97c4;
|
||||
}
|
||||
|
||||
#mocha .test.fail {
|
||||
color: #c00;
|
||||
}
|
||||
|
||||
#mocha .test.fail pre {
|
||||
color: black;
|
||||
}
|
||||
|
||||
#mocha .test.fail::before {
|
||||
content: '✖';
|
||||
font-size: 12px;
|
||||
display: block;
|
||||
float: left;
|
||||
margin-right: 5px;
|
||||
color: #c00;
|
||||
}
|
||||
|
||||
#mocha .test pre.error {
|
||||
color: #c00;
|
||||
max-height: 300px;
|
||||
overflow: auto;
|
||||
}
|
||||
|
||||
/**
|
||||
* (1): approximate for browsers not supporting calc
|
||||
* (2): 42 = 2*15 + 2*10 + 2*1 (padding + margin + border)
|
||||
* ^^ seriously
|
||||
*/
|
||||
#mocha .test pre {
|
||||
display: block;
|
||||
float: left;
|
||||
clear: left;
|
||||
font: 12px/1.5 monaco, monospace;
|
||||
margin: 5px;
|
||||
padding: 15px;
|
||||
border: 1px solid #eee;
|
||||
max-width: 85%; /*(1)*/
|
||||
max-width: calc(100% - 42px); /*(2)*/
|
||||
word-wrap: break-word;
|
||||
border-bottom-color: #ddd;
|
||||
-webkit-border-radius: 3px;
|
||||
-webkit-box-shadow: 0 1px 3px #eee;
|
||||
-moz-border-radius: 3px;
|
||||
-moz-box-shadow: 0 1px 3px #eee;
|
||||
border-radius: 3px;
|
||||
}
|
||||
|
||||
#mocha .test h2 {
|
||||
position: relative;
|
||||
}
|
||||
|
||||
#mocha .test a.replay {
|
||||
position: absolute;
|
||||
top: 3px;
|
||||
right: 0;
|
||||
text-decoration: none;
|
||||
vertical-align: middle;
|
||||
display: block;
|
||||
width: 15px;
|
||||
height: 15px;
|
||||
line-height: 15px;
|
||||
text-align: center;
|
||||
background: #eee;
|
||||
font-size: 15px;
|
||||
-moz-border-radius: 15px;
|
||||
border-radius: 15px;
|
||||
-webkit-transition: opacity 200ms;
|
||||
-moz-transition: opacity 200ms;
|
||||
transition: opacity 200ms;
|
||||
opacity: 0.3;
|
||||
color: #888;
|
||||
}
|
||||
|
||||
#mocha .test:hover a.replay {
|
||||
opacity: 1;
|
||||
}
|
||||
|
||||
#mocha-report.pass .test.fail {
|
||||
display: none;
|
||||
}
|
||||
|
||||
#mocha-report.fail .test.pass {
|
||||
display: none;
|
||||
}
|
||||
|
||||
#mocha-report.pending .test.pass,
|
||||
#mocha-report.pending .test.fail {
|
||||
display: none;
|
||||
}
|
||||
#mocha-report.pending .test.pass.pending {
|
||||
display: block;
|
||||
}
|
||||
|
||||
#mocha-error {
|
||||
color: #c00;
|
||||
font-size: 1.5em;
|
||||
font-weight: 100;
|
||||
letter-spacing: 1px;
|
||||
}
|
||||
|
||||
#mocha-stats {
|
||||
position: fixed;
|
||||
top: 15px;
|
||||
right: 10px;
|
||||
font-size: 12px;
|
||||
margin: 0;
|
||||
color: #888;
|
||||
z-index: 1;
|
||||
}
|
||||
|
||||
#mocha-stats .progress {
|
||||
float: right;
|
||||
padding-top: 0;
|
||||
}
|
||||
|
||||
#mocha-stats em {
|
||||
color: black;
|
||||
}
|
||||
|
||||
#mocha-stats a {
|
||||
text-decoration: none;
|
||||
color: inherit;
|
||||
}
|
||||
|
||||
#mocha-stats a:hover {
|
||||
border-bottom: 1px solid #eee;
|
||||
}
|
||||
|
||||
#mocha-stats li {
|
||||
display: inline-block;
|
||||
margin: 0 5px;
|
||||
list-style: none;
|
||||
padding-top: 11px;
|
||||
}
|
||||
|
||||
#mocha-stats canvas {
|
||||
width: 40px;
|
||||
height: 40px;
|
||||
}
|
||||
|
||||
#mocha code .comment { color: #ddd; }
|
||||
#mocha code .init { color: #2f6fad; }
|
||||
#mocha code .string { color: #5890ad; }
|
||||
#mocha code .keyword { color: #8a6343; }
|
||||
#mocha code .number { color: #2f6fad; }
|
||||
|
||||
@media screen and (max-device-width: 480px) {
|
||||
#mocha {
|
||||
margin: 60px 0px;
|
||||
}
|
||||
|
||||
#mocha #stats {
|
||||
position: absolute;
|
||||
}
|
||||
}
|
||||
5012
gaia/dev_apps/test-agent/common/vendor/mocha/mocha.js
vendored
Executable file
5012
gaia/dev_apps/test-agent/common/vendor/mocha/mocha.js
vendored
Executable file
File diff suppressed because it is too large
Load diff
5753
gaia/dev_apps/test-agent/common/vendor/sinon/sinon.js
vendored
Executable file
5753
gaia/dev_apps/test-agent/common/vendor/sinon/sinon.js
vendored
Executable file
File diff suppressed because it is too large
Load diff
214
gaia/dev_apps/test-agent/common/vendor/test-agent/test-agent.css
vendored
Executable file
214
gaia/dev_apps/test-agent/common/vendor/test-agent/test-agent.css
vendored
Executable file
|
|
@ -0,0 +1,214 @@
|
|||
* { -moz-box-sizing: border-box; -webkit-box-sizing: border-box; box-sizing: border-box; }
|
||||
|
||||
body,
|
||||
html {
|
||||
height: 100%;
|
||||
padding: 0;
|
||||
margin: 0;
|
||||
}
|
||||
|
||||
body {
|
||||
font: 14px/1.5 "Helvetica Neue", Helvetica, Arial, sans-serif;
|
||||
}
|
||||
|
||||
body > iframe {
|
||||
visibility: hidden;
|
||||
}
|
||||
|
||||
button {
|
||||
border-radius: 0.3125rem;
|
||||
font-size: 1rem;
|
||||
}
|
||||
|
||||
button.active, button:active {
|
||||
border-left: 0.15em solid #B8B8B8;
|
||||
border-top: 0.15em solid #B8B8B8;
|
||||
border-right: 0.15em solid white;
|
||||
border-bottom: 0.15em solid white;
|
||||
background-color: #E0E0E0;
|
||||
}
|
||||
|
||||
#test-agent-ui {
|
||||
margin: 2em 2em 2em 1em;
|
||||
}
|
||||
|
||||
#test-agent-ui .hidden {
|
||||
display: none;
|
||||
}
|
||||
|
||||
#test-agent-ui > .error {
|
||||
margin: 15px 40px;
|
||||
}
|
||||
|
||||
#test-agent-ui > .error h1,
|
||||
#test-agent-ui > .error p {
|
||||
padding: 0px;
|
||||
margin: 0px;
|
||||
}
|
||||
|
||||
#test-agent-ui ul {
|
||||
padding: none;
|
||||
border: none;
|
||||
clear: both;
|
||||
overflow: hidden;
|
||||
margin: 4.5rem 0 0 0;
|
||||
padding: 0 0 1rem 0;
|
||||
|
||||
-webkit-column-width: 350px;
|
||||
-moz-column-width: 350px;
|
||||
column-width: 350px;
|
||||
-webkit-column-fill: balance;
|
||||
-moz-column-fill: balance;
|
||||
column-fill: balance;
|
||||
}
|
||||
|
||||
#test-agent-ui li {
|
||||
position: relative;
|
||||
|
||||
list-style: none;
|
||||
padding: 0 0 0 1.5rem;
|
||||
margin: 0;
|
||||
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
#test-agent-ui .active,
|
||||
#test-agent-ui li:not(.test-agent-list-head):hover {
|
||||
color: #00d;
|
||||
text-decoration: underline;
|
||||
}
|
||||
|
||||
#test-agent-ui .active:before {
|
||||
content: '*';
|
||||
position: absolute;
|
||||
display: block;
|
||||
left: .7rem;
|
||||
top: .2rem;
|
||||
width: 1rem;
|
||||
}
|
||||
|
||||
#test-agent-ui .working {
|
||||
font-style: italic;
|
||||
}
|
||||
|
||||
#test-agent-ui .working:after {
|
||||
content: '...';
|
||||
}
|
||||
|
||||
#test-agent-ui button {
|
||||
clear: both;
|
||||
font-size: 1.4rem;
|
||||
width: 100%;
|
||||
padding: .5rem;
|
||||
height: 3rem;
|
||||
border: 1px solid #E2E2E2;
|
||||
}
|
||||
|
||||
#test-agent-ui .test-agent-list-head {
|
||||
position: relative;
|
||||
|
||||
width: 100%;
|
||||
margin: 1rem 0;
|
||||
|
||||
font-weight: bolder;
|
||||
font-size: 1.2rem;
|
||||
background: #CCC;
|
||||
}
|
||||
|
||||
#test-agent-ui .test-agent-list-head-info {
|
||||
position: absolute;
|
||||
right: .5rem;
|
||||
|
||||
font-style: italic;
|
||||
font-weight: normal;
|
||||
}
|
||||
|
||||
#test-agent-ui .test-agent-list-head:hover {
|
||||
text-decoration: none;
|
||||
}
|
||||
|
||||
#test-agent-panel {
|
||||
position: fixed;
|
||||
top: 0;
|
||||
left: 0;
|
||||
padding: 1rem 5rem;
|
||||
background-color: rgba(250, 250, 250, 0.35);
|
||||
width: 100%;
|
||||
height: 4.5rem;
|
||||
z-index: 4;
|
||||
}
|
||||
|
||||
#test-agent-panel .options {
|
||||
margin-top: 0.2rem;
|
||||
margin-left: -0.3rem;
|
||||
}
|
||||
|
||||
#test-agent-panel .execute {
|
||||
margin-top: -2.5rem;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
@media only screen and (max-width: 720px) {
|
||||
#test-agent-ui {
|
||||
margin-left: 0;
|
||||
margin-right: 0;
|
||||
}
|
||||
|
||||
#test-agent-ui li {
|
||||
width: 100%;
|
||||
padding: .5rem 1rem .5rem 2rem;
|
||||
border-bottom: 1px solid #aaa;
|
||||
}
|
||||
|
||||
#test-agent-ui .active:before {
|
||||
top: .8rem;
|
||||
}
|
||||
|
||||
#test-agent-panel {
|
||||
height: 5.5rem;
|
||||
border-bottom: 1px solid #aaa;
|
||||
}
|
||||
|
||||
#test-agent-panel .execute button {
|
||||
margin-top: 2.8rem;
|
||||
}
|
||||
|
||||
#test-agent-panel .execute {
|
||||
margin-top: -4rem;
|
||||
}
|
||||
}
|
||||
|
||||
@media only screen and (max-width: 600px) {
|
||||
#test-agent-panel {
|
||||
height: 7rem;
|
||||
padding: 1rem 1.5rem;
|
||||
border-bottom: 1px solid #aaa;
|
||||
}
|
||||
|
||||
form {
|
||||
margin-top: 1.7rem;
|
||||
}
|
||||
|
||||
#test-agent-ui ul {
|
||||
margin: 5.9rem 0 0;
|
||||
}
|
||||
}
|
||||
|
||||
@media only screen and (max-width: 460px) {
|
||||
#test-agent-panel .execute {
|
||||
position: fixed;
|
||||
margin-left: 10.7rem;
|
||||
}
|
||||
}
|
||||
|
||||
#blanket-main * {
|
||||
-moz-box-sizing: initial;
|
||||
-webkit-box-sizing: initial;
|
||||
box-sizing: initial;
|
||||
}
|
||||
|
||||
code .comment { color: #ddd }
|
||||
code .init { color: #2F6FAD }
|
||||
code .string { color: #5890AD }
|
||||
code .keyword { color: #8A6343 }
|
||||
code .number { color: #2F6FAD }
|
||||
3548
gaia/dev_apps/test-agent/common/vendor/test-agent/test-agent.js
vendored
Executable file
3548
gaia/dev_apps/test-agent/common/vendor/test-agent/test-agent.js
vendored
Executable file
File diff suppressed because it is too large
Load diff
62
gaia/dev_apps/test-agent/index.html
Executable file
62
gaia/dev_apps/test-agent/index.html
Executable file
|
|
@ -0,0 +1,62 @@
|
|||
<!DOCTYPE HTML>
|
||||
<html>
|
||||
<head>
|
||||
<meta name="viewport" content="width=device-width, user-scalable=no, initial-scale=1">
|
||||
<meta http-equiv="content-type" content="text/html; charset=utf-8" />
|
||||
<title>Serve the tests</title>
|
||||
|
||||
<style type="text/css" media="all">
|
||||
html,body,iframe {
|
||||
height: 100%;
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
#loading-error-message {
|
||||
margin: .5em;
|
||||
padding: .5em 1em;
|
||||
|
||||
background: #fdd;
|
||||
border: 1px solid red;
|
||||
font-weight: bold;
|
||||
}
|
||||
</style>
|
||||
|
||||
</head>
|
||||
<body>
|
||||
|
||||
<div id="test-agent-panel">
|
||||
<form>
|
||||
<input type="text" name="search" id="test-agent-search-input" />
|
||||
<button type="button" id="test-agent-search-submit">Search</button>
|
||||
<div class="options">
|
||||
<label>
|
||||
<input type="checkbox" id="test-agent-coverage-checkbox" />
|
||||
Enable Coverage
|
||||
</label>
|
||||
</div>
|
||||
</form>
|
||||
<div class="execute">
|
||||
<button type="button" id="test-agent-execute-button">Execute</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div id="loading-error-message" hidden>
|
||||
One or more files are unavailable. To quickly fix the issue you can run
|
||||
<code>make update-common</code> from the Gaia directory. Note that <code>make
|
||||
test-agent-server</code> runs this too. Then, you can
|
||||
<a href='javascript:window.location.reload()'>try to turn it off and turn it
|
||||
on again</a>.
|
||||
</div>
|
||||
<!-- Test Agent UI will be loaded in here -->
|
||||
<div id="test-agent-ui">
|
||||
</div>
|
||||
|
||||
<div id="mocha">
|
||||
</div>
|
||||
|
||||
<script type="text/javascript" src="tools.js"></script>
|
||||
<script type="text/javascript" src="agent.js"></script>
|
||||
|
||||
</body>
|
||||
</html>
|
||||
|
||||
29
gaia/dev_apps/test-agent/manifest.webapp
Executable file
29
gaia/dev_apps/test-agent/manifest.webapp
Executable file
|
|
@ -0,0 +1,29 @@
|
|||
{
|
||||
"name": "Test Agent",
|
||||
"default_locale": "en-US",
|
||||
"description": "Gaia Test Agent",
|
||||
"launch_path": "/index.html",
|
||||
"permissions": {
|
||||
"power": {}
|
||||
},
|
||||
"locales": {
|
||||
"en-US": {
|
||||
"name": "Test Agent",
|
||||
"description": "Gaia Test Agent"
|
||||
}
|
||||
},
|
||||
"developer": {
|
||||
"url": "https://github.com/mozilla-b2g/gaia",
|
||||
"name": "The Gaia Team"
|
||||
},
|
||||
"icons": {
|
||||
"84": "/style/icons/test-agent_84.png",
|
||||
"126": "/style/icons/test-agent_126.png",
|
||||
"142": "/style/icons/test-agent_142.png",
|
||||
"189": "/style/icons/test-agent_189.png",
|
||||
"284": "/style/icons/test-agent_284.png"
|
||||
},
|
||||
"orientation": "portrait-primary"
|
||||
}
|
||||
|
||||
|
||||
BIN
gaia/dev_apps/test-agent/style/icons/test-agent_126.png
Executable file
BIN
gaia/dev_apps/test-agent/style/icons/test-agent_126.png
Executable file
Binary file not shown.
|
After Width: | Height: | Size: 2 KiB |
BIN
gaia/dev_apps/test-agent/style/icons/test-agent_142.png
Executable file
BIN
gaia/dev_apps/test-agent/style/icons/test-agent_142.png
Executable file
Binary file not shown.
|
After Width: | Height: | Size: 2.4 KiB |
BIN
gaia/dev_apps/test-agent/style/icons/test-agent_189.png
Executable file
BIN
gaia/dev_apps/test-agent/style/icons/test-agent_189.png
Executable file
Binary file not shown.
|
After Width: | Height: | Size: 3.1 KiB |
BIN
gaia/dev_apps/test-agent/style/icons/test-agent_284.png
Executable file
BIN
gaia/dev_apps/test-agent/style/icons/test-agent_284.png
Executable file
Binary file not shown.
|
After Width: | Height: | Size: 4.9 KiB |
BIN
gaia/dev_apps/test-agent/style/icons/test-agent_84.png
Executable file
BIN
gaia/dev_apps/test-agent/style/icons/test-agent_84.png
Executable file
Binary file not shown.
|
After Width: | Height: | Size: 1.4 KiB |
63
gaia/dev_apps/test-agent/test/index.html
Executable file
63
gaia/dev_apps/test-agent/test/index.html
Executable file
|
|
@ -0,0 +1,63 @@
|
|||
<!DOCTYPE HTML>
|
||||
<html>
|
||||
<head>
|
||||
<meta name="viewport" content="width=device-width, user-scalable=no, initial-scale=1">
|
||||
<meta http-equiv="content-type" content="text/html; charset=utf-8" />
|
||||
<title>Serve the tests</title>
|
||||
|
||||
<style type="text/css" media="all">
|
||||
html,body,iframe {
|
||||
height: 100%;
|
||||
width: 100%;
|
||||
}
|
||||
</style>
|
||||
|
||||
<script type="text/javascript" charset="utf-8">
|
||||
(function(window){
|
||||
var Loader = window.CommonResourceLoader = {},
|
||||
host = document.location.host,
|
||||
domain = host.replace(/(^[\w\d-]+\.)?([\w\d]+\.[a-z]+)/, '$2');
|
||||
|
||||
Loader.domain = document.location.protocol + '//' + domain;
|
||||
Loader.url = function(url){
|
||||
return this.domain + url;
|
||||
}
|
||||
|
||||
Loader.script = function(url, doc){
|
||||
doc = doc || document;
|
||||
doc.write('<script type="application/javascript;version=1.8" src="' + this.url(url) + '"><\/script>');
|
||||
return this;
|
||||
};
|
||||
|
||||
Loader.stylesheet = function(url, doc){
|
||||
doc = doc || document;
|
||||
doc.write('<link rel="stylesheet" type="text/css" href="' + this.url(url) + '">');
|
||||
return this;
|
||||
};
|
||||
}(this));
|
||||
|
||||
CommonResourceLoader
|
||||
.stylesheet('/common/vendor/test-agent/test-agent.css')
|
||||
.stylesheet('/common/vendor/mocha/mocha.css');
|
||||
</script>
|
||||
</head>
|
||||
<body>
|
||||
|
||||
<!-- Test Agent UI will be loaded in here -->
|
||||
<div id="test-agent-ui">
|
||||
</div>
|
||||
|
||||
<div id="mocha">
|
||||
</div>
|
||||
|
||||
<script type="text/javascript" charset="utf-8">
|
||||
CommonResourceLoader
|
||||
.script('/common/vendor/mocha/mocha.js')
|
||||
.script('/common/vendor/sinon/sinon.js')
|
||||
.script('/common/vendor/test-agent/test-agent.js')
|
||||
.script('/common/test/test-url-resolver.js')
|
||||
.script('/common/test/agent.js');
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
||||
|
||||
29
gaia/dev_apps/test-agent/test/unit/chai_test.js
Executable file
29
gaia/dev_apps/test-agent/test/unit/chai_test.js
Executable file
|
|
@ -0,0 +1,29 @@
|
|||
suite('chai', function() {
|
||||
|
||||
test('expect interface', function() {
|
||||
assert.isFunction(expect, 'expect enable');
|
||||
expect(true).to.be.ok;
|
||||
expect(false).to.not.be.ok;
|
||||
expect('test').to.have.property('length', 4);
|
||||
expect('asd').to.have.property('constructor', String);
|
||||
expect({ 'foo.bar': 'baz' }).to.have.property('foo.bar');
|
||||
});
|
||||
|
||||
test('should interface', function() {
|
||||
expect('test').to.have.property('should');
|
||||
true.should.be.ok;
|
||||
false.should.not.be.ok;
|
||||
'test'.should.have.property('length', 4);
|
||||
'asd'.should.have.property('constructor', String);
|
||||
({ 'foo.bar': 'baz' }).should.have.property('foo.bar');
|
||||
});
|
||||
|
||||
test('chai-as-promised plugin resolve', function(done) {
|
||||
assert.isFulfilled(Promise.resolve(), "Promise resolved").notify(done);
|
||||
});
|
||||
|
||||
test('chai-as-promised plugin reject', function(done) {
|
||||
assert.isRejected(Promise.reject(), "Promise rejected").notify(done);
|
||||
});
|
||||
|
||||
});
|
||||
36
gaia/dev_apps/test-agent/test/unit/mocha_task_test.js
Executable file
36
gaia/dev_apps/test-agent/test/unit/mocha_task_test.js
Executable file
|
|
@ -0,0 +1,36 @@
|
|||
suite('mocha task', function() {
|
||||
|
||||
test('simple generator', function() {
|
||||
var timePassed = false;
|
||||
|
||||
setTimeout(function() {
|
||||
timePassed = true;
|
||||
}, 10);
|
||||
|
||||
var value = yield setTimeout(function() {
|
||||
MochaTask.next('foo');
|
||||
}, 30);
|
||||
|
||||
assert.equal(value, 'foo');
|
||||
assert.ok(timePassed, 'time passed?');
|
||||
});
|
||||
|
||||
test('throwing an error', function() {
|
||||
function throwsSomething() {
|
||||
setTimeout(function() {
|
||||
MochaTask.nextNodeStyle(new Error('generator throws'));
|
||||
}, 10);
|
||||
}
|
||||
|
||||
var threw;
|
||||
try {
|
||||
yield throwsSomething();
|
||||
} catch (e) {
|
||||
threw = e;
|
||||
} finally {
|
||||
assert.ok(threw, 'throws the error');
|
||||
assert.ok(threw.message);
|
||||
assert.include(threw.message, 'generator throws');
|
||||
}
|
||||
});
|
||||
});
|
||||
10
gaia/dev_apps/test-agent/test/unit/sinon_test.js
Executable file
10
gaia/dev_apps/test-agent/test/unit/sinon_test.js
Executable file
|
|
@ -0,0 +1,10 @@
|
|||
|
||||
/**
|
||||
* @fileoverview Test that a very vanilla mocha test
|
||||
* (taken liberally from sinon docs) passes.
|
||||
*/
|
||||
suite('sinon', function() {
|
||||
test('should be defined', function() {
|
||||
assert.notStrictEqual(this.sinon, undefined);
|
||||
});
|
||||
});
|
||||
422
gaia/dev_apps/test-agent/test/unit/synthetic_gestures_test.js
Executable file
422
gaia/dev_apps/test-agent/test/unit/synthetic_gestures_test.js
Executable file
|
|
@ -0,0 +1,422 @@
|
|||
requireCommon('test/synthetic_gestures.js');
|
||||
|
||||
// Tests for the SyntheticGestures library which is used for
|
||||
// writing tests for the GestureDetector library.
|
||||
suite('SyntheticGestures', function() {
|
||||
|
||||
var touchDevice = (window.Touch !== undefined);
|
||||
|
||||
// Just make sure the object and its functions are defined
|
||||
suite('API', function() {
|
||||
test('SyntheticGestures object', function() {
|
||||
assert.ok(SyntheticGestures);
|
||||
});
|
||||
test('SyntheticGesture functions', function() {
|
||||
assert.typeOf(SyntheticGestures.tap, 'function');
|
||||
assert.typeOf(SyntheticGestures.mousetap, 'function');
|
||||
assert.typeOf(SyntheticGestures.dbltap, 'function');
|
||||
assert.typeOf(SyntheticGestures.mousedbltap, 'function');
|
||||
assert.typeOf(SyntheticGestures.swipe, 'function');
|
||||
assert.typeOf(SyntheticGestures.mouseswipe, 'function');
|
||||
assert.typeOf(SyntheticGestures.hold, 'function');
|
||||
assert.typeOf(SyntheticGestures.mousehold, 'function');
|
||||
assert.typeOf(SyntheticGestures.pinch, 'function');
|
||||
});
|
||||
});
|
||||
|
||||
// Test that the SyntheticGesture functions actually send out the
|
||||
// expected stream of synthetic events
|
||||
suite('eventstream', function() {
|
||||
var events; // save a sequence of events
|
||||
var element; // dummy element to generate events on
|
||||
|
||||
// Just push the event object into an array
|
||||
function saveevent(e) {
|
||||
if (!e.isTrusted) // only save the synthetic events
|
||||
events.push(e);
|
||||
}
|
||||
|
||||
// Return the sequence of events as a string of event types
|
||||
function eventseq() {
|
||||
return events.map(function(e) { return e.type }).join(' ');
|
||||
}
|
||||
|
||||
suiteSetup(function() {
|
||||
if (touchDevice) {
|
||||
document.addEventListener('touchstart', saveevent, true);
|
||||
document.addEventListener('touchmove', saveevent, true);
|
||||
document.addEventListener('touchend', saveevent, true);
|
||||
}
|
||||
document.addEventListener('mousedown', saveevent, true);
|
||||
document.addEventListener('mousemove', saveevent, true);
|
||||
document.addEventListener('mouseup', saveevent, true);
|
||||
});
|
||||
|
||||
suiteTeardown(function() {
|
||||
if (touchDevice) {
|
||||
document.removeEventListener('touchstart', saveevent, true);
|
||||
document.removeEventListener('touchmove', saveevent, true);
|
||||
document.removeEventListener('touchend', saveevent, true);
|
||||
}
|
||||
document.removeEventListener('mousedown', saveevent, true);
|
||||
document.removeEventListener('mousemove', saveevent, true);
|
||||
document.removeEventListener('mouseup', saveevent, true);
|
||||
});
|
||||
|
||||
setup(function() {
|
||||
events = [];
|
||||
element = document.createElement('div');
|
||||
element.setAttribute('style',
|
||||
'position:absolute;left:0px;top:0px;width:100%;height:100%');
|
||||
document.body.appendChild(element);
|
||||
});
|
||||
|
||||
teardown(function() {
|
||||
document.body.removeChild(element);
|
||||
});
|
||||
|
||||
if (touchDevice) {
|
||||
test('tap', function(done) {
|
||||
SyntheticGestures.tap(element, function() {
|
||||
done(function() {
|
||||
assert.equal(eventseq(), 'touchstart touchend');
|
||||
assert.equal(events[0].changedTouches[0].clientX, 10);
|
||||
assert.equal(events[0].changedTouches[0].clientY, 20);
|
||||
assert.equal(events[1].changedTouches[0].clientX, 10);
|
||||
assert.equal(events[1].changedTouches[0].clientY, 20);
|
||||
});
|
||||
}, 10, 20);
|
||||
});
|
||||
|
||||
test('dbltap', function(done) {
|
||||
SyntheticGestures.dbltap(element, function() {
|
||||
done(function() {
|
||||
assert.equal(eventseq(), 'touchstart touchend touchstart touchend');
|
||||
assert.equal(events[0].changedTouches[0].clientX, 10);
|
||||
assert.equal(events[0].changedTouches[0].clientY, 20);
|
||||
assert.equal(events[3].changedTouches[0].clientX, 10);
|
||||
assert.equal(events[3].changedTouches[0].clientY, 20);
|
||||
});
|
||||
}, 10, 20);
|
||||
});
|
||||
|
||||
test('swipe', function(done) {
|
||||
SyntheticGestures.swipe(element, 0, 0, 100, 100, 100, function() {
|
||||
done(function() {
|
||||
var firstEvent = events[0];
|
||||
var lastEvent = events[events.length - 1];
|
||||
assert.match(eventseq(), /touchstart (touchmove )+touchend/);
|
||||
assert.equal(firstEvent.changedTouches[0].clientX, 0);
|
||||
assert.equal(firstEvent.changedTouches[0].clientY, 0);
|
||||
assert.equal(lastEvent.changedTouches[0].clientX, 100);
|
||||
assert.equal(lastEvent.changedTouches[0].clientY, 100);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
test('hold', function(done) {
|
||||
SyntheticGestures.hold(element, 100, 0, 0, 100, 100, 100, function() {
|
||||
done(function() {
|
||||
var firstEvent = events[0];
|
||||
var lastEvent = events[events.length - 1];
|
||||
assert.match(eventseq(), /touchstart (touchmove )+touchend/);
|
||||
assert.equal(firstEvent.changedTouches[0].clientX, 0);
|
||||
assert.equal(firstEvent.changedTouches[0].clientY, 0);
|
||||
assert.equal(lastEvent.changedTouches[0].clientX, 100);
|
||||
assert.equal(lastEvent.changedTouches[0].clientY, 100);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
test('pinch (out)', function(done) {
|
||||
SyntheticGestures.pinch(element, 100, 100, 200, 100, 2,
|
||||
800, function() {
|
||||
done(function() {
|
||||
var expectedSequence =
|
||||
/touchstart touchstart (touchmove )+touchend touchend/;
|
||||
assert.match(eventseq(), expectedSequence);
|
||||
|
||||
assert.equal(events[0].touches.length, 1);
|
||||
assert.equal(events[1].touches.length, 2);
|
||||
assert.equal(events[1].targetTouches.length, 2);
|
||||
var e = events[events.length - 3]; // last touchmove event
|
||||
var t1 = e.targetTouches[0];
|
||||
var t2 = e.targetTouches[1];
|
||||
var dx = Math.abs(t1.clientX - t2.clientX);
|
||||
assert.equal(dx, 200); // 100px * 2
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
test('pinch (in)', function(done) {
|
||||
SyntheticGestures.pinch(element, 100, 100, 200, 100, .75,
|
||||
800, function() {
|
||||
done(function() {
|
||||
var expectedSequence =
|
||||
/touchstart touchstart (touchmove )+touchend touchend/;
|
||||
assert.match(eventseq(), expectedSequence);
|
||||
|
||||
assert.equal(events[0].touches.length, 1);
|
||||
assert.equal(events[1].touches.length, 2);
|
||||
assert.equal(events[1].targetTouches.length, 2);
|
||||
var e = events[events.length - 3]; // last touchmove event
|
||||
var t1 = e.targetTouches[0];
|
||||
var t2 = e.targetTouches[1];
|
||||
var dx = Math.abs(t1.clientX - t2.clientX);
|
||||
assert.equal(dx, 75); // 100px * .75
|
||||
});
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
test('mousetap', function(done) {
|
||||
SyntheticGestures.mousetap(element, function() {
|
||||
done(function() {
|
||||
assert.equal(eventseq(), 'mousedown mouseup');
|
||||
assert.equal(events[0].clientX, 10);
|
||||
assert.equal(events[0].clientY, 20);
|
||||
assert.equal(events[1].clientX, 10);
|
||||
assert.equal(events[1].clientY, 20);
|
||||
});
|
||||
}, 10, 20);
|
||||
});
|
||||
|
||||
test('mousedbltap', function(done) {
|
||||
SyntheticGestures.mousedbltap(element, function() {
|
||||
done(function() {
|
||||
assert.equal(eventseq(), 'mousedown mouseup mousedown mouseup');
|
||||
assert.equal(events[0].clientX, 10);
|
||||
assert.equal(events[0].clientY, 20);
|
||||
assert.equal(events[3].clientX, 10);
|
||||
assert.equal(events[3].clientY, 20);
|
||||
});
|
||||
},10, 20);
|
||||
});
|
||||
|
||||
test('mouseswipe', function(done) {
|
||||
SyntheticGestures.mouseswipe(element, 0, 0, 100, 100, 100, function() {
|
||||
done(function() {
|
||||
assert.match(eventseq(), /mousedown (mousemove )+mouseup/);
|
||||
assert.equal(events[0].clientX, 0);
|
||||
assert.equal(events[0].clientY, 0);
|
||||
assert.equal(events[events.length - 1].clientX, 100);
|
||||
assert.equal(events[events.length - 1].clientY, 100);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
test('mousehold', function(done) {
|
||||
SyntheticGestures.mousehold(element, 100, 0, 0, 100, 100,
|
||||
100, function() {
|
||||
done(function() {
|
||||
assert.match(eventseq(), /mousedown (mousemove )+mouseup/);
|
||||
assert.equal(events[0].clientX, 0);
|
||||
assert.equal(events[0].clientY, 0);
|
||||
assert.equal(events[events.length - 1].clientX, 100);
|
||||
assert.equal(events[events.length - 1].clientY, 100);
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
// Just like the above, but using relative coordinates
|
||||
suite('relative coordinates', function() {
|
||||
var events; // save a sequence of events
|
||||
var element; // dummy element to generate events on
|
||||
|
||||
// Just push the event object into an array
|
||||
function saveevent(e) {
|
||||
if (!e.isTrusted) // only save the synthetic events
|
||||
events.push(e);
|
||||
}
|
||||
|
||||
// Return the sequence of events as a string of event types
|
||||
function eventseq() {
|
||||
return events.map(function(e) { return e.type }).join(' ');
|
||||
}
|
||||
|
||||
suiteSetup(function() {
|
||||
if (touchDevice) {
|
||||
document.addEventListener('touchstart', saveevent, true);
|
||||
document.addEventListener('touchmove', saveevent, true);
|
||||
document.addEventListener('touchend', saveevent, true);
|
||||
}
|
||||
document.addEventListener('mousedown', saveevent, true);
|
||||
document.addEventListener('mousemove', saveevent, true);
|
||||
document.addEventListener('mouseup', saveevent, true);
|
||||
});
|
||||
|
||||
suiteTeardown(function() {
|
||||
if (touchDevice) {
|
||||
document.removeEventListener('touchstart', saveevent, true);
|
||||
document.removeEventListener('touchmove', saveevent, true);
|
||||
document.removeEventListener('touchend', saveevent, true);
|
||||
}
|
||||
document.removeEventListener('mousedown', saveevent, true);
|
||||
document.removeEventListener('mousemove', saveevent, true);
|
||||
document.removeEventListener('mouseup', saveevent, true);
|
||||
});
|
||||
|
||||
setup(function() {
|
||||
events = [];
|
||||
element = document.createElement('div');
|
||||
|
||||
var style = 'position:absolute;left:0px;top:0px;width:400px;height:400px';
|
||||
element.setAttribute('style', style);
|
||||
document.body.appendChild(element);
|
||||
});
|
||||
|
||||
teardown(function() {
|
||||
document.body.removeChild(element);
|
||||
});
|
||||
|
||||
if (touchDevice) {
|
||||
test('tap', function(done) {
|
||||
SyntheticGestures.tap(element, function() {
|
||||
done(function() {
|
||||
assert.equal(eventseq(), 'touchstart touchend');
|
||||
assert.equal(events[0].changedTouches[0].clientX, 40);
|
||||
assert.equal(events[0].changedTouches[0].clientY, 200);
|
||||
assert.equal(events[1].changedTouches[0].clientX, 40);
|
||||
assert.equal(events[1].changedTouches[0].clientY, 200);
|
||||
});
|
||||
}, '10%');
|
||||
});
|
||||
|
||||
test('dbltap', function(done) {
|
||||
SyntheticGestures.dbltap(element, function() {
|
||||
done(function() {
|
||||
assert.equal(eventseq(), 'touchstart touchend touchstart touchend');
|
||||
assert.equal(events[0].changedTouches[0].clientX, 40);
|
||||
assert.equal(events[0].changedTouches[0].clientY, 80);
|
||||
assert.equal(events[3].changedTouches[0].clientX, 40);
|
||||
assert.equal(events[3].changedTouches[0].clientY, 80);
|
||||
});
|
||||
}, '10%', '20%');
|
||||
});
|
||||
|
||||
test('swipe', function(done) {
|
||||
SyntheticGestures.swipe(element, '10%', '10%', '50%', '+10%',
|
||||
100, function() {
|
||||
done(function() {
|
||||
assert.match(eventseq(), /touchstart (touchmove )+touchend/);
|
||||
|
||||
var firstEvent = events[0];
|
||||
var lastEvent = events[events.length - 1];
|
||||
assert.equal(firstEvent.changedTouches[0].clientX, 40);
|
||||
assert.equal(firstEvent.changedTouches[0].clientY, 40);
|
||||
assert.equal(lastEvent.changedTouches[0].clientX, 200);
|
||||
assert.equal(lastEvent.changedTouches[0].clientY, 80);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
test('hold', function(done) {
|
||||
SyntheticGestures.hold(element, 100, '0%', '10%', '100%', '-1%',
|
||||
100, function() {
|
||||
done(function() {
|
||||
assert.match(eventseq(), /touchstart (touchmove )+touchend/);
|
||||
|
||||
var firstEvent = events[0];
|
||||
var lastEvent = events[events.length - 1];
|
||||
assert.equal(firstEvent.changedTouches[0].clientX, 0);
|
||||
assert.equal(firstEvent.changedTouches[0].clientY, 40);
|
||||
assert.equal(lastEvent.changedTouches[0].clientX, 400);
|
||||
assert.equal(lastEvent.changedTouches[0].clientY, 36);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
test('pinch (out)', function(done) {
|
||||
SyntheticGestures.pinch(element, '10%', '10%', '20%', '10%', 2,
|
||||
800, function() {
|
||||
done(function() {
|
||||
var expectedSeq =
|
||||
/touchstart touchstart (touchmove )+touchend touchend/;
|
||||
assert.match(eventseq(), expectedSeq);
|
||||
|
||||
assert.equal(events[0].touches.length, 1);
|
||||
assert.equal(events[1].touches.length, 2);
|
||||
assert.equal(events[1].targetTouches.length, 2);
|
||||
var e = events[events.length - 3]; // last touchmove event
|
||||
var t1 = e.targetTouches[0];
|
||||
var t2 = e.targetTouches[1];
|
||||
var dx = Math.abs(t1.clientX - t2.clientX);
|
||||
assert.equal(dx, 80); // 40px * 2
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
test('pinch (in)', function(done) {
|
||||
SyntheticGestures.pinch(element, '50%', 100, '0%', 100, .75,
|
||||
800, function() {
|
||||
done(function() {
|
||||
var expectedSeq =
|
||||
/touchstart touchstart (touchmove )+touchend touchend/;
|
||||
assert.match(eventseq(), expectedSeq);
|
||||
|
||||
assert.equal(events[0].touches.length, 1);
|
||||
assert.equal(events[1].touches.length, 2);
|
||||
assert.equal(events[1].targetTouches.length, 2);
|
||||
var e = events[events.length - 3]; // last touchmove event
|
||||
var t1 = e.targetTouches[0];
|
||||
var t2 = e.targetTouches[1];
|
||||
var dx = Math.abs(t1.clientX - t2.clientX);
|
||||
assert.equal(dx, 150); // 200px * .75
|
||||
});
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
test('mousetap', function(done) {
|
||||
SyntheticGestures.mousetap(element, function() {
|
||||
done(function() {
|
||||
assert.equal(eventseq(), 'mousedown mouseup');
|
||||
assert.equal(events[0].clientX, 200);
|
||||
assert.equal(events[0].clientY, 200);
|
||||
assert.equal(events[1].clientX, 200);
|
||||
assert.equal(events[1].clientY, 200);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
test('mousedbltap', function(done) {
|
||||
SyntheticGestures.mousedbltap(element, function() {
|
||||
done(function() {
|
||||
assert.equal(eventseq(), 'mousedown mouseup mousedown mouseup');
|
||||
assert.equal(events[0].clientX, 40);
|
||||
assert.equal(events[0].clientY, 80);
|
||||
assert.equal(events[3].clientX, 40);
|
||||
assert.equal(events[3].clientY, 80);
|
||||
});
|
||||
}, '10%', '20%');
|
||||
});
|
||||
|
||||
test('mouseswipe', function(done) {
|
||||
SyntheticGestures.mouseswipe(element, '50%', '50%', '-100', '-10%',
|
||||
100, function() {
|
||||
done(function() {
|
||||
assert.match(eventseq(), /mousedown (mousemove )+mouseup/);
|
||||
assert.equal(events[0].clientX, 200);
|
||||
assert.equal(events[0].clientY, 200);
|
||||
assert.equal(events[events.length - 1].clientX, 100);
|
||||
assert.equal(events[events.length - 1].clientY, 160);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
test('mousehold', function(done) {
|
||||
SyntheticGestures.mousehold(element, 100, '1%', '1', 100, '+1%',
|
||||
100, function() {
|
||||
done(function() {
|
||||
assert.match(eventseq(), /mousedown (mousemove )+mouseup/);
|
||||
assert.equal(events[0].clientX, 4);
|
||||
assert.equal(events[0].clientY, 1);
|
||||
assert.equal(events[events.length - 1].clientX, 100);
|
||||
assert.equal(events[events.length - 1].clientY, 5);
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
100
gaia/dev_apps/test-agent/tools.js
Executable file
100
gaia/dev_apps/test-agent/tools.js
Executable file
|
|
@ -0,0 +1,100 @@
|
|||
(function(exports) {
|
||||
|
||||
const KEY_ENTER = 13;
|
||||
|
||||
var SearchTool = function() {
|
||||
this.searchBox = document.getElementById('test-agent-search-input');
|
||||
this.button = document.getElementById('test-agent-search-submit');
|
||||
this.content = document.getElementById('test-agent-ui');
|
||||
|
||||
this.button.addEventListener('click', this._onFind.bind(this));
|
||||
this.searchBox.addEventListener('keypress', this._onQuery.bind(this));
|
||||
};
|
||||
|
||||
SearchTool.prototype._onQuery = function(evt) {
|
||||
if (KEY_ENTER === evt.which) {
|
||||
evt.preventDefault();
|
||||
var text = this.searchBox.value;
|
||||
this.searchBox.blur();
|
||||
this.content.focus();
|
||||
this.find(text);
|
||||
return false;
|
||||
}
|
||||
};
|
||||
|
||||
SearchTool.prototype._onFind = function(evt) {
|
||||
var text = this.searchBox.value;
|
||||
this.find(text);
|
||||
evt.preventDefault();
|
||||
};
|
||||
|
||||
SearchTool.prototype.find = function(text) {
|
||||
window.find(text, true, true, false);
|
||||
console.log('find', text);
|
||||
};
|
||||
|
||||
// Must be triggered only after all LI got attached.
|
||||
var CategoryGroup = function() {
|
||||
window.addEventListener('test-agent-list-done', (function() {
|
||||
this.gather().group().render();
|
||||
}).bind(this));
|
||||
};
|
||||
|
||||
CategoryGroup.prototype._getCategory = function(text) {
|
||||
return text.match(/^([^\/]*)\/(.*)$/);
|
||||
};
|
||||
|
||||
CategoryGroup.prototype.gather = function() {
|
||||
this.lis = document.querySelectorAll('#test-agent-ui li');
|
||||
return this;
|
||||
};
|
||||
|
||||
CategoryGroup.prototype.group = function() {
|
||||
var lis = this.lis;
|
||||
var result = {'groups': {}, 'categories': [], 'total': this.lis.length};
|
||||
for (var i = 0; i < lis.length; i++) {
|
||||
var li = lis[i];
|
||||
var text = li.textContent;
|
||||
[, cat, truncated] = this._getCategory(text);
|
||||
li.textContent = truncated;
|
||||
if (result.groups[cat]) {
|
||||
result.groups[cat].push(li);
|
||||
} else { // new group.
|
||||
result.groups[cat] = [li];
|
||||
result.categories.push(cat);
|
||||
}
|
||||
}
|
||||
this.grouped = result;
|
||||
return this;
|
||||
};
|
||||
|
||||
CategoryGroup.prototype.render = function() {
|
||||
var frame = document.querySelector('#test-agent-ui .test-list');
|
||||
for (var cat in this.grouped.groups) {
|
||||
var lis = this.grouped.groups[cat];
|
||||
var head = document.createElement('li');
|
||||
head.classList.add('test-agent-list-head');
|
||||
head.dataset.category = cat;
|
||||
head.textContent = '#' + cat;
|
||||
head.addEventListener('click', (function doGroupClick(evt) {
|
||||
this.addGroupTest(evt.target.dataset.category);
|
||||
}).bind(this));
|
||||
info = document.createElement('span');
|
||||
info.classList.add('test-agent-list-head-info');
|
||||
info.textContent = lis.length + '/' + this.grouped.total;
|
||||
head.appendChild(info);
|
||||
frame.insertBefore(head, lis[0]);
|
||||
}
|
||||
return this;
|
||||
};
|
||||
|
||||
CategoryGroup.prototype.addGroupTest = function(cat) {
|
||||
// Don't want to involve APIs studying so I just forward clicking events.
|
||||
this.grouped.groups[cat].forEach(function(li) {
|
||||
li.click();
|
||||
});
|
||||
};
|
||||
|
||||
exports.categoryGroup = new CategoryGroup();
|
||||
exports.searchTool = new SearchTool();
|
||||
})(window);
|
||||
Loading…
Reference in a new issue