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
285
gaia/apps/camera/test/marionette/lib/camera.js
Executable file
285
gaia/apps/camera/test/marionette/lib/camera.js
Executable file
|
|
@ -0,0 +1,285 @@
|
|||
/* global marionette */
|
||||
'use strict';
|
||||
|
||||
function Camera(client) {
|
||||
this.client = client || marionette.client();
|
||||
this.$ = require('./vendor/mquery')(client);
|
||||
this.touches = [];
|
||||
}
|
||||
|
||||
Camera.prototype = {
|
||||
URL: 'app://camera.gaiamobile.org',
|
||||
|
||||
selectors : {
|
||||
controls : '.test-controls',
|
||||
viewfinder : '.viewfinder.js-viewfinder',
|
||||
recordingTimer: '.recording-timer',
|
||||
hud : '.hud',
|
||||
modeSwitch: '.test-switch',
|
||||
thumbnail: '.test-thumbnail',
|
||||
capture: '.test-capture',
|
||||
cameraToggle: '.test-camera-toggle',
|
||||
flash: '.test-flash-button',
|
||||
settingsToggle: '.test-settings-toggle',
|
||||
settingsPanel: '.settings',
|
||||
settingOptionsPanel: '.setting-options',
|
||||
onOption: '.setting-option[data-key=on]',
|
||||
offOption: '.setting-option[data-key=off]',
|
||||
gridSettings: '.test-grid-setting',
|
||||
selfTimerSettings: '.test-self-timer-setting',
|
||||
selfTimer2SecondsOption: '.setting-option[data-key=secs2]',
|
||||
selfTimerCountDown : '.countdown',
|
||||
notification: '.notification li',
|
||||
grid: '.viewfinder-grid',
|
||||
zoomBar: '.zoom-bar',
|
||||
},
|
||||
|
||||
launch: function() {
|
||||
this.client.apps.launch(this.URL);
|
||||
this.client.apps.switchToApp(this.URL);
|
||||
this.waitForPreviewReady();
|
||||
},
|
||||
|
||||
close: function() {
|
||||
this.client.apps.close(this.URL);
|
||||
},
|
||||
|
||||
tap: function(selector) {
|
||||
this.$(selector).tap();
|
||||
},
|
||||
|
||||
addOrReplaceTouch: function(identifier, x, y) {
|
||||
var touches = this.touches;
|
||||
for (var i = 0, length = touches.length; i < length; i++) {
|
||||
if (touches[i].identifier === identifier) {
|
||||
touches[i].x = x;
|
||||
touches[i].y = y;
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
touches.push({
|
||||
identifier: identifier,
|
||||
x: x,
|
||||
y: y
|
||||
});
|
||||
},
|
||||
|
||||
removeTouch: function(identifier) {
|
||||
var touches = this.touches;
|
||||
for (var i = 0, length = touches.length; i < length; i++) {
|
||||
if (touches[i].identifier === identifier) {
|
||||
touches.splice(i, 1);
|
||||
return;
|
||||
}
|
||||
}
|
||||
},
|
||||
|
||||
touch: function(selector, type, identifier, x, y) {
|
||||
var eventName = 'touch' + type;
|
||||
if (eventName === 'touchend') {
|
||||
this.removeTouch(identifier);
|
||||
} else {
|
||||
this.addOrReplaceTouch(identifier, x, y);
|
||||
}
|
||||
|
||||
var el = this.$(selector).waitToAppear().el;
|
||||
el.scriptWith(dispatchTouchEvent, [this.touches, eventName]);
|
||||
|
||||
function dispatchTouchEvent(el, touches, eventName) {
|
||||
var fakeTouches = [];
|
||||
touches.forEach(function(touch) {
|
||||
fakeTouches.push(
|
||||
document.createTouch(window, el, touch.identifier, touch.x, touch.y));
|
||||
});
|
||||
|
||||
var touchList = document.createTouchList(fakeTouches);
|
||||
var touchEvent = document.createEvent('TouchEvent');
|
||||
touchEvent.initTouchEvent(eventName, // name
|
||||
true, // bubbles
|
||||
true, // cancelable
|
||||
window, // view
|
||||
null, // detail
|
||||
false, // ctrlKey
|
||||
false, // altKey
|
||||
false, // shiftKey
|
||||
false, // metaKey
|
||||
touchList, // touches
|
||||
touchList, // targetTouches
|
||||
touchList); // changedTouches
|
||||
|
||||
el.dispatchEvent(touchEvent);
|
||||
}
|
||||
},
|
||||
|
||||
pinch: function(selector, options) {
|
||||
var x = options.x || 100;
|
||||
var y = options.y || 100;
|
||||
var distance = options.distance || 10;
|
||||
var halfDistance = Math.abs(distance) / 2;
|
||||
var inTouch1 = {
|
||||
x: x,
|
||||
y: y
|
||||
};
|
||||
var inTouch2 = {
|
||||
x: x + 1,
|
||||
y: y + 1
|
||||
};
|
||||
var outTouch1 = {
|
||||
x: x - halfDistance,
|
||||
y: y - halfDistance
|
||||
};
|
||||
var outTouch2 = {
|
||||
x: x + 1 + halfDistance,
|
||||
y: y + 1 + halfDistance
|
||||
};
|
||||
|
||||
var startTouches;
|
||||
var endTouches;
|
||||
|
||||
// Zoom in
|
||||
if (distance > 0) {
|
||||
startTouches = [inTouch1, inTouch2];
|
||||
endTouches = [outTouch1, outTouch2];
|
||||
}
|
||||
|
||||
// Zoom out
|
||||
else {
|
||||
startTouches = [outTouch1, outTouch2];
|
||||
endTouches = [inTouch1, inTouch2];
|
||||
}
|
||||
|
||||
this.touch(selector, 'start', 1, startTouches[0].x, startTouches[0].y);
|
||||
this.touch(selector, 'start', 2, startTouches[1].x, startTouches[1].y);
|
||||
this.touch(selector, 'move', 1, endTouches[0].x, endTouches[0].y);
|
||||
this.touch(selector, 'move', 2, endTouches[1].x, endTouches[1].y);
|
||||
this.touch(selector, 'end', 1);
|
||||
this.touch(selector, 'end', 2);
|
||||
},
|
||||
|
||||
waitFor: function(selector) {
|
||||
client.helper.waitForElement(selector);
|
||||
},
|
||||
|
||||
tapCapture: function() {
|
||||
this.tap(this.selectors.capture);
|
||||
},
|
||||
|
||||
tapModeSwitch: function() {
|
||||
this.tap(this.selectors.modeSwitch);
|
||||
},
|
||||
|
||||
tapSettings: function() {
|
||||
this.tap(this.selectors.settingsToggle);
|
||||
},
|
||||
|
||||
tapCameraToggle: function() {
|
||||
this.tap(this.selectors.cameraToggle);
|
||||
},
|
||||
|
||||
tapGridSettings: function() {
|
||||
this.tap(this.selectors.gridSettings);
|
||||
},
|
||||
|
||||
tapOnOption: function() {
|
||||
this.tap(this.selectors.onOption);
|
||||
},
|
||||
|
||||
tapOffOption: function() {
|
||||
this.tap(this.selectors.offOption);
|
||||
},
|
||||
|
||||
tapSelfTimerSettings: function() {
|
||||
this.tap(this.selectors.selfTimerSettings);
|
||||
},
|
||||
|
||||
tapSelfTimer2SecsOption: function() {
|
||||
this.tap(this.selectors.selfTimer2SecondsOption);
|
||||
},
|
||||
|
||||
tapFlash: function() {
|
||||
this.tap(this.selectors.flash);
|
||||
},
|
||||
|
||||
waitForSettingsPanel: function() {
|
||||
this.waitFor(this.selectors.settingsPanel);
|
||||
},
|
||||
|
||||
waitForSettingOptionsPanel: function() {
|
||||
this.waitFor(this.selectors.settingOptionsPanel);
|
||||
},
|
||||
|
||||
waitForThumbnail: function() {
|
||||
this.waitFor(this.selectors.thumbnail);
|
||||
},
|
||||
|
||||
waitForRecordingTimer: function() {
|
||||
this.waitFor(this.selectors.recordingTimer + '.visible');
|
||||
},
|
||||
|
||||
waitForNotification: function() {
|
||||
this.waitFor(this.selectors.notification);
|
||||
},
|
||||
|
||||
waitForHudReady: function() {
|
||||
this.waitFor(this.selectors.hud + '.visible[camera=ready]')
|
||||
},
|
||||
|
||||
waitForPreviewReady: function() {
|
||||
this.waitFor(this.selectors.viewfinder + '.visible');
|
||||
},
|
||||
|
||||
waitForControlsEnabled: function() {
|
||||
this.waitFor(this.selectors.controls + '.enabled')
|
||||
},
|
||||
|
||||
waitForGridOn: function() {
|
||||
this.waitFor(this.selectors.viewfinder + '[grid=on]');
|
||||
},
|
||||
|
||||
waitForGridOff: function() {
|
||||
this.waitFor(this.selectors.viewfinder + '[grid=off]');
|
||||
},
|
||||
|
||||
waitForViewfinderEnabled: function() {
|
||||
this.waitFor(this.selectors.viewfinder + '[enabled=true]');
|
||||
},
|
||||
|
||||
waitForViewfinderDisabled: function() {
|
||||
this.waitFor(this.selectors.viewfinder + '[enabled=false]');
|
||||
},
|
||||
|
||||
waitForCountDown: function() {
|
||||
this.waitFor(this.selectors.selfTimerCountDown + '.visible');
|
||||
},
|
||||
|
||||
waitForZoomBarEnabled: function() {
|
||||
this.waitFor(this.selectors.zoomBar + '.zooming');
|
||||
},
|
||||
|
||||
waitForZoomBarDisabled: function() {
|
||||
this.waitFor(this.selectors.zoomBar + ':not(.zooming)');
|
||||
},
|
||||
|
||||
get mode() {
|
||||
var controls = this.selectors.controls;
|
||||
if(this.$(controls + '[mode=picture]')[0]) {
|
||||
return 'picture';
|
||||
} else {
|
||||
return 'video';
|
||||
}
|
||||
},
|
||||
|
||||
get flash() {
|
||||
var hud = this.selectors.hud;
|
||||
return this.$(hud + '[flash-enabled=true]')[0];
|
||||
},
|
||||
|
||||
get frontCamera() {
|
||||
var hud = this.selectors.hud;
|
||||
return this.$(hud + '[camera-enabled=true]')[0];
|
||||
}
|
||||
|
||||
};
|
||||
|
||||
module.exports = Camera;
|
||||
274
gaia/apps/camera/test/marionette/lib/vendor/mquery.js
vendored
Executable file
274
gaia/apps/camera/test/marionette/lib/vendor/mquery.js
vendored
Executable file
|
|
@ -0,0 +1,274 @@
|
|||
'use strict';
|
||||
var Marionette = require('marionette-client');
|
||||
var assert = require('assert');
|
||||
|
||||
/**
|
||||
* mQuery allows you to query and modify the DOM through Marionette,
|
||||
* in a style resembling jQuery. While jQuery is often heckled for
|
||||
* being poor for designing large-scale apps, its quick DOM traversal
|
||||
* and manipulation is hard to beat. For integration tests, which tend
|
||||
* to require convenient, clear, mostly-read-only access to the DOM
|
||||
* with fewer architectural concerns, a jQuery-like API fits well.
|
||||
*
|
||||
* Typical use is something like this:
|
||||
*
|
||||
* var $ = require('mquery');
|
||||
*
|
||||
* $.client = marionette.client();
|
||||
*
|
||||
* console.log($('.my-element').text());
|
||||
* console.log($('#textbox').val());
|
||||
*
|
||||
* You can also set values in input fields like so:
|
||||
*
|
||||
* $('#textbox').val('foo');
|
||||
*
|
||||
* This also works with system <select> boxes and other inputs too.
|
||||
*
|
||||
* Architecturally, mQuery does something interesting: An item
|
||||
* returned from mQuery is _both_ an array of elements _and_ a single
|
||||
* Element at the same time. For instance, you can pass the result of
|
||||
* $('.foo') directly to Marionette calls that require a
|
||||
* Marionette.Element; similarly, since mQuery behaves like an array
|
||||
* as well, you can also use this anywhere you previously used
|
||||
* Marionette.findElements().
|
||||
*
|
||||
* While typical usage assigns $.client to your Marionette.Client
|
||||
* instance, you can instead pass { client: myClient } as an option to
|
||||
* mQuery if you'd prefer not to globally scope out your mQuery
|
||||
* instances.
|
||||
*
|
||||
* Read the code to see what functions are implemented; many of the
|
||||
* most commonly-used jQuery features are included. Add more if you
|
||||
* find them useful. While this initially contains some
|
||||
* half-implemented methods (e.g. ".css()" can only read values, not
|
||||
* set them), the eventual goal is to match jQuery's API where it
|
||||
* makes sense.
|
||||
*/
|
||||
var mQuery = function(selectorOrElement, opts) {
|
||||
assert(opts.client, 'marionette .client must be passed');
|
||||
|
||||
var parent = opts.parent || null;
|
||||
var client = opts.client;
|
||||
|
||||
if (!client) {
|
||||
throw new Error(
|
||||
'You must set mQuery.client to your Marionette client.');
|
||||
}
|
||||
|
||||
var elements;
|
||||
var selector;
|
||||
if (typeof selectorOrElement === 'string') {
|
||||
elements = (parent || client).findElements(selectorOrElement);
|
||||
selector = selectorOrElement;
|
||||
} else if (Array.isArray(selectorOrElement)) {
|
||||
elements = selectorOrElement;
|
||||
} else if (selectorOrElement instanceof ElementSet) {
|
||||
return selectorOrElement;
|
||||
} else if (selectorOrElement instanceof Marionette.Element) {
|
||||
elements = [selectorOrElement];
|
||||
} else {
|
||||
throw new Error('mQuery doesn\'t know how to handle ' + selectorOrElement);
|
||||
}
|
||||
|
||||
return new ElementSet(elements, {
|
||||
client: client,
|
||||
selector: selector
|
||||
});
|
||||
};
|
||||
|
||||
function mQueryFactory(client) {
|
||||
var opts = { client: client };
|
||||
|
||||
return function(selector) {
|
||||
return mQuery(selector, opts);
|
||||
}
|
||||
}
|
||||
|
||||
module.exports = mQueryFactory;
|
||||
|
||||
/**
|
||||
* ElementSet acts as a lightweight wrapper over Marionette.Element,
|
||||
* providing chainable, functional interface somewhat like jQuery.
|
||||
*/
|
||||
function ElementSet(elements, opts) {
|
||||
this.client = opts.client;
|
||||
this.selector = opts.selector;
|
||||
this.el = elements[0] || null;
|
||||
this.id = this.el && this.el.id; // Act like Marionette if we can.
|
||||
|
||||
for (var i = 0; i < elements.length; i++) {
|
||||
this.push(elements[i]);
|
||||
}
|
||||
}
|
||||
|
||||
// Behave like an Array of Marionette.Element instances.
|
||||
ElementSet.prototype = Object.create(Array.prototype);
|
||||
|
||||
// Behave like a single instance of Marionette.Element too.
|
||||
Object.keys(Marionette.Element.prototype).forEach(function(funcName) {
|
||||
var elementFn = Marionette.Element.prototype[funcName];
|
||||
ElementSet.prototype[funcName] = function() {
|
||||
return elementFn.apply(this.el, arguments);
|
||||
};
|
||||
});
|
||||
|
||||
// Behave like jQuery. (These are only getters, not setters though.)
|
||||
ElementSet.prototype.attr = ElementSet.prototype.getAttribute;
|
||||
ElementSet.prototype.css = ElementSet.prototype.cssProperty;
|
||||
|
||||
ElementSet.prototype.toString = function() {
|
||||
return '<ElementSet: ' + this.length + '>';
|
||||
};
|
||||
|
||||
// For the purposes of executeScript, behave like Marionette.Element.
|
||||
ElementSet.prototype.toJSON = function() {
|
||||
return { ELEMENT: this.id };
|
||||
};
|
||||
|
||||
// Chainable helper methods.
|
||||
|
||||
ElementSet.prototype.waitToAppear = function() {
|
||||
this.client.helper.waitForElement(this.el);
|
||||
return this;
|
||||
};
|
||||
|
||||
ElementSet.prototype.waitToDisappear = function() {
|
||||
this.client.helper.waitForElementToDisappear(this.el);
|
||||
return this;
|
||||
};
|
||||
|
||||
function makeChainable(proto, funcName) {
|
||||
var origFn = proto[funcName];
|
||||
proto[funcName] = function() {
|
||||
origFn.apply(this, arguments);
|
||||
return this;
|
||||
};
|
||||
}
|
||||
|
||||
makeChainable(ElementSet.prototype, 'clear');
|
||||
makeChainable(ElementSet.prototype, 'sendKeys');
|
||||
|
||||
ElementSet.prototype.tap = function() {
|
||||
this.waitToAppear();
|
||||
this.el.scriptWith(function(el) {
|
||||
el.scrollIntoView(false);
|
||||
});
|
||||
this.el.tap();
|
||||
return this;
|
||||
};
|
||||
|
||||
ElementSet.prototype.find = function(selector) {
|
||||
if (!this.el) {
|
||||
throw new Error(
|
||||
'Cannot call mQuery.find() when mQuery did not match any elements.');
|
||||
}
|
||||
return mQuery(selector, {
|
||||
client: this.client,
|
||||
parent: this.el
|
||||
});
|
||||
};
|
||||
|
||||
ElementSet.prototype.eq = function(idx) {
|
||||
return mQuery(this[idx], {
|
||||
client: this.client
|
||||
});
|
||||
};
|
||||
|
||||
ElementSet.prototype.height = function() {
|
||||
if (!this.el) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return this.el.size().height;
|
||||
};
|
||||
|
||||
ElementSet.prototype.width = function() {
|
||||
if (!this.el) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return this.el.size().width;
|
||||
};
|
||||
|
||||
ElementSet.prototype.parent = function() {
|
||||
if (!this.el) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return mQuery(this.client.executeScript(function(el) {
|
||||
return el.parentElement;
|
||||
}, [this.el]), {
|
||||
client: this.client
|
||||
});
|
||||
};
|
||||
|
||||
ElementSet.prototype.data = function(attr, value) {
|
||||
if (!this.el) {
|
||||
return null;
|
||||
}
|
||||
|
||||
if (arguments.length === 1) {
|
||||
return this.client.executeScript(function(el, attr) {
|
||||
return el.dataset[attr];
|
||||
}, [this.el, attr]);
|
||||
} else if (arguments.length === 2) {
|
||||
return this.client.executeScript(function(el, attr, value) {
|
||||
el.dataset[attr] = value;
|
||||
}, [this.el, attr, value]);
|
||||
} else {
|
||||
return this.client.executeScript(function(el) {
|
||||
return el.dataset;
|
||||
}, [this.el]);
|
||||
}
|
||||
};
|
||||
|
||||
ElementSet.prototype.val = function(value) {
|
||||
if (!this.el) {
|
||||
return null;
|
||||
}
|
||||
|
||||
var el = this.el;
|
||||
if (!arguments.length) {
|
||||
return el.getAttribute('value');
|
||||
} else {
|
||||
if (el.tagName().toLowerCase() === 'select') {
|
||||
// Save the origin so that we know how to switch back from the
|
||||
// system frame to the application frame.
|
||||
var origin = this.client.executeScript(function() {
|
||||
return window.location.origin;
|
||||
});
|
||||
|
||||
this.client.executeScript(function(el) {
|
||||
window.focus(); // Without this line, sometimes el.focus() does nothing.
|
||||
el.focus();
|
||||
}, [el]);
|
||||
|
||||
this.client.switchToFrame(); // Switch to the system frame.
|
||||
|
||||
var options = this.client.findElements(
|
||||
'.value-selector-select-option-popup li');
|
||||
var values = (!Array.isArray(value) ? [value] : value);
|
||||
options.forEach(function(option) {
|
||||
var itemText = option.findElement('label span').text();
|
||||
var shouldBeSelected = (values.indexOf(itemText) !== -1);
|
||||
var isSelected = (option.getAttribute('aria-selected') === 'true');
|
||||
if (shouldBeSelected !== isSelected) {
|
||||
option.scriptWith(function(el) {
|
||||
el.scrollIntoView(false);
|
||||
});
|
||||
option.tap();
|
||||
}
|
||||
});
|
||||
this.client.helper.waitForElement('.value-option-confirm').tap();
|
||||
this.client.apps.switchToApp(origin);
|
||||
} else {
|
||||
this.client.forms.fill(el, value);
|
||||
}
|
||||
el.scriptWith(function(el) {
|
||||
el.dispatchEvent(new CustomEvent('change'));
|
||||
});
|
||||
return this;
|
||||
}
|
||||
};
|
||||
|
||||
Loading…
Reference in a new issue