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:
OverSu 2026-09-30 23:13:03 +02:00
commit b38dea0597
25901 changed files with 9982925 additions and 369 deletions

1
gaia/dev_apps/test-agent/.gitignore vendored Executable file
View file

@ -0,0 +1 @@
config.json

View 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;
});

View 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));

View 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));

View 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>

View 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>

View 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));

View 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));

View 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);
}
}
}
}
};
}());

View 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;
}
});
});

View 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;
}

View 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
};
}());

View 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));

View 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));

File diff suppressed because one or more lines are too long

View 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);
}
});
})();

View 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;
};
});
}
}());

File diff suppressed because it is too large Load diff

View 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;
}
}

File diff suppressed because it is too large Load diff

File diff suppressed because it is too large Load diff

View 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 }

File diff suppressed because it is too large Load diff

View 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>

View 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"
}

Binary file not shown.

After

Width:  |  Height:  |  Size: 2 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.4 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.1 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.9 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.4 KiB

View 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>

View 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);
});
});

View 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');
}
});
});

View 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);
});
});

View 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
View 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);