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

View file

@ -0,0 +1,27 @@
'use strict';
/* jshint node: true, mocha: true */
/* global suiteSetup */
var helper = require('helper');
suite('Camera tests', function() {
suiteSetup(helper.cleanupWorkspace);
teardown(helper.cleanupWorkspace);
test('APP=camera make', function(done) {
helper.exec('APP=camera make', function(error, stdout, stderr) {
helper.checkError(error, stdout, stderr);
var zipPath = process.cwd() +
'/profile/webapps/camera.gaiamobile.org/application.zip';
var checkList = ['manifest.webapp'];
checkList.forEach(function(filePath) {
helper.checkFilePathInZip(zipPath, filePath);
});
done();
});
});
});

View file

@ -0,0 +1,18 @@
{
"extends": "../../../../.jshintrc",
"node": true,
"predef": [
"suite",
"test",
"setup",
"teardown",
"suiteSetup",
"suiteTeardown",
"marionette",
"marionetteScriptFinished",
"require",
"define",
"requireApp",
"sinon"
]
}

View file

@ -0,0 +1,36 @@
marionette('Capture', function() {
'use strict';
teardown(function() {
camera.close();
});
var assert = require('assert');
var client = marionette.client({
desiredCapabilities: { raisesAccessibilityExceptions: false }
});
var camera = new (require('./lib/camera'))(client);
setup(function() {
camera.launch();
});
test('capture a picture', function() {
camera.tapCapture();
camera.waitForThumbnail();
});
test('capture a video', function(done) {
camera.tapModeSwitch();
camera.waitForPreviewReady();
camera.tapCapture();
camera.waitForRecordingTimer();
// It records a 3 seconds video
setTimeout(function(){
camera.tapCapture();
camera.waitForThumbnail();
done();
}, 3000);
});
});

View file

@ -0,0 +1,41 @@
marionette('Hud', function() {
'use strict';
teardown(function() {
camera.close();
});
var assert = require('assert');
var client = marionette.client({
desiredCapabilities: { raisesAccessibilityExceptions: false }
});
var camera = new (require('./lib/camera'))(client);
setup(function() {
camera.launch();
});
test('toggle cameras', function() {
if (camera.frontCamera) {
camera.waitForHudReady();
camera.tapCameraToggle();
camera.waitForViewfinderDisabled();
camera.waitForViewfinderEnabled();
}
});
test('tap flash', function() {
if (camera.flash) {
camera.waitForHudReady();
camera.tapFlash();
camera.waitForNotification();
}
});
test('open settings menu', function() {
camera.waitForHudReady();
camera.tapSettings();
camera.waitForSettingsPanel();
});
});

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

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

View file

@ -0,0 +1,74 @@
marionette('Settings', function() {
'use strict';
// sadly must come before the client call because the client call creates
// a teardown which close the connection before this gets invoked if called
// after it.
teardown(function() {
camera.close();
});
var assert = require('assert');
var client = marionette.client({
desiredCapabilities: { raisesAccessibilityExceptions: false }
});
var camera = new (require('./lib/camera'))(client);
setup(function() {
camera.launch();
});
test('enables grid', function() {
camera.waitForHudReady();
camera.tapSettings();
camera.waitForSettingsPanel();
camera.tapGridSettings();
camera.waitForSettingOptionsPanel();
camera.tapOnOption();
camera.waitForGridOn();
});
test('disables grid', function() {
camera.waitForHudReady();
camera.tapSettings();
camera.waitForSettingsPanel();
camera.tapGridSettings();
camera.waitForSettingOptionsPanel();
camera.tapOffOption();
camera.waitForGridOff();
});
test('enables timer', function() {
camera.waitForHudReady();
camera.tapSettings();
camera.waitForSettingsPanel();
camera.tapSelfTimerSettings();
camera.waitForSettingOptionsPanel();
camera.tapSelfTimer2SecsOption();
camera.waitForNotification();
});
test('takes picture with 2 seconds timer', function() {
camera.waitForHudReady();
camera.tapSettings();
camera.waitForSettingsPanel();
camera.tapSelfTimerSettings();
camera.waitForSettingOptionsPanel();
camera.tapSelfTimer2SecsOption();
camera.waitForNotification();
camera.tapCapture();
camera.waitForCountDown();
camera.waitForThumbnail();
});
test('disables timer', function() {
camera.waitForHudReady();
camera.tapSettings();
camera.waitForSettingsPanel();
camera.tapSelfTimerSettings();
camera.waitForSettingOptionsPanel();
camera.tapOffOption();
camera.waitForNotification();
});
});

View file

@ -0,0 +1,28 @@
marionette('Zoom', function() {
'use strict';
teardown(function() {
camera.close();
});
var assert = require('assert');
var client = marionette.client({
desiredCapabilities: { raisesAccessibilityExceptions: false }
});
var camera = new (require('./lib/camera'))(client);
setup(function() {
camera.launch();
});
test('pinch to zoom', function() {
camera.pinch('body', {
x: 100,
y: 100,
distance: 10
});
camera.waitForZoomBarEnabled();
camera.waitForZoomBarDisabled();
});
});

View file

@ -0,0 +1,18 @@
{
"extends": "../../../../.jshintrc",
"predef": [
"assert",
"suite",
"test",
"setup",
"teardown",
"suiteSetup",
"suiteTeardown",
"require",
"requireApp",
"requirejs",
"sinon",
"mocha",
"loadBodyHTML"
]
}

View file

@ -0,0 +1,678 @@
/*jshint maxlen:false*/
suite('app', function() {
'use strict';
suiteSetup(function(done) {
var self = this;
requirejs([
'app',
'lib/camera/camera',
'view',
'lib/geo-location',
'lib/orientation',
'lib/setting',
'lib/pinch'
], function(App, Camera, View, GeoLocation, orientation, Setting, Pinch) {
self.App = App;
self.View = View;
self.Camera = Camera;
self.Geolocation = GeoLocation;
self.orientation = orientation;
self.Setting = Setting;
self.Pinch = Pinch;
done();
});
});
var mocks = {
doc: function() {
return {
addEventListener: sinon.spy(),
hidden: false
};
},
win: function() {
return {
addEventListener: sinon.spy(),
dispatchEvent: sinon.stub(),
location: {
hash: ''
}
};
}
};
setup(function() {
var self = this;
if (!navigator.mozCameras) {
navigator.mozCameras = {
getListOfCameras: function() { return []; },
getCamera: function() {},
release: function() {}
};
}
navigator.mozSettings = {
addObserver: function() {},
removeObserver: function() {}
};
var options = this.options = {
doc: mocks.doc(),
win: mocks.win(),
el: document.createElement('div'),
geolocation: sinon.createStubInstance(this.Geolocation),
orientation: this.orientation,
pinch: sinon.createStubInstance(this.Pinch),
activity: {},
camera: sinon.createStubInstance(this.Camera),
require: sinon.stub(),
settings: {
geolocation: sinon.createStubInstance(this.Setting),
spinnerTimeouts: sinon.createStubInstance(this.Setting),
keyDownEvents: sinon.createStubInstance(this.Setting),
countdown: sinon.createStubInstance(this.Setting)
},
views: {},
controllers: {
battery: sinon.spy(),
overlay: sinon.spy(),
hud: sinon.spy(),
countdown: sinon.spy(),
controls: sinon.spy(),
viewfinder: sinon.spy(),
camera: sinon.spy(),
settings: sinon.spy(),
activity: sinon.spy(),
storage: sinon.spy(),
// Lazy loaded
lazy: [
'controllers/preview-gallery',
'controllers/confirm',
'controllers/sounds'
]
}
};
this.loadingView = sinon.createStubInstance(this.View);
this.loadingView.appendTo.returns(this.loadingView);
options.LoadingView = function() { return self.loadingView; };
// Sandbox to put all our stubs in
this.sandbox = sinon.sandbox.create();
// Sinon take over setTimeout
this.clock = sinon.useFakeTimers();
// Stub out all methods
this.sandbox.stub(options.activity);
this.sandbox.stub(options.focusRing);
this.sandbox.stub(options.hud);
// Sometimes we have to spy on the prototype,
// this is because methods get bound and passed
// directly as callbacks. We set spys on prototype
// methods before any of this happens, so that the
// spy is always at the root of any call.
this.sandbox.spy(this.App.prototype, 'boot');
this.sandbox.spy(this.App.prototype, 'showSpinner');
this.sandbox.spy(this.App.prototype, 'clearSpinner');
// Aliases
this.settings = options.settings;
this.pinch = options.pinch;
// Create the app
this.app = new this.App(options);
this.sandbox.spy(this.app, 'on');
this.sandbox.spy(this.app, 'set');
this.sandbox.spy(this.app, 'once');
this.sandbox.spy(this.app, 'emit');
this.sandbox.spy(this.app, 'firer');
});
teardown(function() {
this.sandbox.restore();
this.clock.restore();
delete navigator.mozCameras;
});
suite('App()', function() {
test('Should store the injected dependencies on the instance', function() {
var options = this.options;
var app = this.app;
assert.ok(app.el === options.el);
assert.ok(app.inSecureMode === false);
assert.ok(app.geolocation === options.geolocation);
assert.ok(app.camera === options.camera);
assert.ok(app.storage === options.storage);
assert.ok(app.settings === options.settings);
assert.ok(app.sounds === options.sounds);
assert.ok(app.controllers === options.controllers);
});
test('Should detect secure mode', function() {
var options = this.options;
options.win.location.hash = '#secure';
// need to `new App()` again here (and disregard `this.app`)
// because we have changed the option mock.
var app = new this.App(options);
assert.ok(app.inSecureMode === true);
});
});
suite('App#boot()', function() {
setup(function() {
this.app.boot();
});
test('Should run each of the controllers,' +
'passing itself as first argument', function() {
var controllers = this.app.controllers;
var app = this.app;
assert.ok(controllers.overlay.calledWith(app));
assert.ok(controllers.battery.calledWith(app));
assert.ok(controllers.hud.calledWith(app));
assert.ok(controllers.controls.calledWith(app));
assert.ok(controllers.viewfinder.calledWith(app));
assert.ok(controllers.camera.calledWith(app));
});
test('Should bind to `visibilitychange` event', function() {
var call = this.app.doc.addEventListener.getCall(0);
assert.ok(call.args[0] === 'visibilitychange');
assert.ok(typeof call.args[1] === 'function');
});
test('Should bind to `beforeunload` event', function() {
var addEventListener = this.app.win.addEventListener;
assert.ok(addEventListener.calledWith('beforeunload', this.app.onBeforeUnload));
});
test('Should run the activity controller before controls or camera', function() {
var activity = this.app.controllers.activity;
var controls = this.app.controllers.controls;
var camera = this.app.controllers.camera;
assert.isTrue(activity.calledBefore(controls));
assert.isTrue(activity.calledBefore(camera));
});
test('Should watch location only once storage confirmed healthy', function() {
var geolocationWatch = this.app.geolocationWatch;
assert.ok(this.app.once.calledWith('storage:checked:healthy', geolocationWatch));
});
test('Should clear loading screen when camera is ready', function() {
var on = this.app.on.withArgs('ready');
var callback = on.args[0][1];
// Call the callback and make sure
// that `clearSpinner` was called.
callback();
sinon.assert.calledOnce(this.App.prototype.clearSpinner);
});
test('It calls `showSpinner`', function() {
sinon.assert.calledWith(this.App.prototype.showSpinner, 'requestingCamera');
});
suite('pinch', function() {
test('It listens to the pinch `changed` event', function() {
sinon.assert.called(this.pinch.on, 'changed');
});
test('It disables pinch while the preview-gallery is open', function() {
sinon.assert.called(this.app.on, 'previewgallery:opened', this.pinch.disable);
sinon.assert.called(this.app.on, 'previewgallery:closed', this.pinch.enable);
});
test('It disables pinch while settings is open', function() {
sinon.assert.called(this.app.on, 'settings:opened', this.pinch.disable);
sinon.assert.called(this.app.on, 'settings:closed', this.pinch.enable);
});
});
suite('App#geolocationWatch()', function() {
test('Should *not* watch location if in activity', function() {
this.app.hidden = false;
this.app.activity.pick = true;
this.app.geolocationWatch();
this.clock.tick(2000);
assert.isFalse(this.app.geolocation.watch.called);
});
test('Should *not* watch location if app hidden', function() {
this.app.hidden = true;
this.app.activity.pick = false;
this.app.geolocationWatch();
this.clock.tick(2000);
assert.isFalse(this.app.geolocation.watch.called);
});
});
suite('once(\'viewfinder:visible\')', function() {
setup(function() {
// Stop annoying logs
this.sandbox.stub(console, 'log');
this.spy = this.app.once.withArgs('viewfinder:visible');
this.callback = this.spy.args[0][1];
sinon.spy(this.app, 'loadLazyControllers');
sinon.stub(this.app, 'clearSpinner');
// Call the callback to test
this.callback();
});
test('Should fire a `criticalpathdone` event', function() {
assert.isTrue(this.app.emit.calledWith('criticalpathdone'));
});
test('Should flag `this.criticalPathDone`', function() {
assert.isTrue(this.app.criticalPathDone);
});
test('Should load non-critical controllers', function() {
sinon.assert.called(this.app.loadLazyControllers);
});
});
});
suite('App#bindEvents()', function() {
setup(function() {
this.app.firer.restore();
sinon.stub(this.app, 'firer');
this.app.firer
.withArgs('busy')
.returns('<busy-firer>');
this.app.firer
.withArgs('localized')
.returns('<localized-firer>');
this.sandbox.stub(this.app, 'onCountdown');
this.options.settings.countdown.selected.returns('off');
});
suite('default', function() {
setup(function() {
this.app.bindEvents();
});
test('Should listen for visibilitychange on document', function() {
sinon.assert.calledWith(this.app.doc.addEventListener, 'visibilitychange');
});
test('Should relay window \'localized\' event', function() {
sinon.assert.calledWith(this.app.doc.addEventListener, 'DOMRetranslated', '<localized-firer>');
});
test('It indicates the app is \'busy\' when the camera \'willchange\'', function() {
sinon.assert.calledWith(this.app.on, 'camera:willchange', '<busy-firer>');
});
test('Should listen for \'willchange\' to lazy load once', function() {
sinon.assert.calledWith(this.app.once, 'camera:willchange', this.app.onWillChange);
});
test('Should listen for \'activity\' to lazy load once', function() {
sinon.assert.calledWith(this.app.once, 'activity', this.app.onActivity);
});
test('Should listen for \'preview\' to lazy load once', function() {
sinon.assert.calledWith(this.app.once, 'preview', this.app.onPreview);
});
test('Should listen for \'newthumbnail\' to lazy load once', function() {
sinon.assert.calledWith(this.app.once, 'newthumbnail', this.app.onNewThumbnail);
});
test('Should listen for change:selected on countdown if disabled', function() {
sinon.assert.calledWith(this.options.settings.countdown.once, 'change:selected', this.app.onCountdown);
});
});
test('Should listen for change:selected on countdown if disabled', function() {
this.options.settings.countdown.selected.returns('2sec');
this.app.bindEvents();
sinon.assert.called(this.app.onCountdown);
});
});
suite('App#onVisibilityChange', function() {
test('Should update the `app.hidden` property', function() {
this.app.doc.hidden = true;
this.app.onVisibilityChange();
assert.equal(this.app.hidden, true);
this.app.doc.hidden = false;
this.app.onVisibilityChange();
assert.equal(this.app.hidden, false);
});
test('Should emit a \'visible\' event when the document is not hidden', function() {
this.app.doc.hidden = false;
this.app.onVisibilityChange();
assert.isTrue(this.app.emit.calledWith('visible'));
});
test('Should emit a \'hidden\' event when the document is hidden', function() {
this.app.doc.hidden = true;
this.app.onVisibilityChange();
assert.isTrue(this.app.emit.calledWith('hidden'));
});
});
suite('App#onHidden', function() {
setup(function() {
this.app.onHidden();
});
test('Should stop watching location', function() {
assert.ok(this.app.geolocation.stopWatching.called);
});
});
suite('App#onVisible()', function() {
setup(function() {
sinon.spy(this.app, 'geolocationWatch');
this.sandbox.spy(this.app.orientation, 'lock');
this.app.onVisible();
});
test('Should begin watching location again', function() {
sinon.assert.called(this.app.geolocationWatch);
});
test('It locks the orientation to portrait', function() {
sinon.assert.called(this.app.orientation.lock);
});
});
suite('App#onBusy()', function() {
setup(function() {
this.app.settings.spinnerTimeouts.get
.withArgs('takingPicture')
.returns(1500);
this.app.settings.spinnerTimeouts.get
.withArgs('requestingCamera')
.returns(600);
sinon.stub(this.app, 'showSpinner');
});
test('It calls showSpinner with type if busy type recongnised', function() {
this.app.onBusy('takingPicture');
sinon.assert.calledWith(this.app.showSpinner, 'takingPicture');
this.app.showSpinner.reset();
this.app.onBusy('requestingCamera');
sinon.assert.calledWith(this.app.showSpinner, 'requestingCamera');
this.app.showSpinner.reset();
});
test('Should not show loading screen if busy type not recongnised', function() {
this.app.onBusy('unknownType');
sinon.assert.notCalled(this.app.showSpinner);
});
});
suite('App#showSpinner()', function() {
setup(function() {
this.sandbox.spy(window, 'clearTimeout');
this.app.settings.spinnerTimeouts.get
.withArgs('takingPicture')
.returns(1500);
this.app.settings.spinnerTimeouts.get
.withArgs('requestingCamera')
.returns(600);
});
test('Should append a loading view to the app element and show', function() {
this.app.showSpinner('takingPicture');
this.clock.tick(1500);
sinon.assert.calledWith(this.app.views.loading.appendTo, this.app.el);
sinon.assert.called(this.app.views.loading.show);
});
test('Should clear any existing timeouts', function() {
this.sandbox.stub(window, 'setTimeout').returns('<timeout-id>');
this.app.showSpinner('requestingCamera');
this.app.showSpinner('requestingCamera');
sinon.assert.calledWith(window.clearTimeout, '<timeout-id>');
});
test('It should not require a type', function() {
this.app.showSpinner();
this.clock.tick(1);
sinon.assert.calledWith(this.app.views.loading.appendTo, this.app.el);
});
});
suite('App#clearSpinner()', function() {
setup(function() {
this.sandbox.spy(window, 'clearTimeout');
this.sandbox.stub(window, 'setTimeout').returns('<timeout-id>');
this.app.views.loading = sinon.createStubInstance(this.View);
this.app.views.loading.hide.callsArg(0);
});
test('Should clear loadingTimeout', function() {
this.app.views.loading = null;
this.app.showSpinner();
this.app.clearSpinner();
sinon.assert.calledWith(window.clearTimeout, '<timeout-id>');
});
test('Should hide, then destroy the view', function() {
var view = this.app.views.loading;
this.app.clearSpinner();
sinon.assert.called(view.hide);
assert.ok(view.destroy.calledAfter(view.hide));
});
test('Should clear reference to `app.views.loading`', function() {
this.app.clearSpinner();
assert.equal(this.app.views.loading, null);
});
});
suite('App#onActivity', function() {
setup(function() {
this.sandbox.stub(this.app, 'loadLazyController');
this.app.onActivity();
});
test('Should lazy load confirm controller', function() {
sinon.assert.calledWith(this.app.loadLazyController, 'controllers/confirm');
});
});
suite('App#onCountdown', function() {
setup(function() {
this.sandbox.stub(this.app, 'loadLazyController');
this.app.onCountdown();
});
test('Should lazy load countdown controller', function() {
sinon.assert.calledWith(this.app.loadLazyController, 'controllers/countdown');
});
});
suite('App#onWillChange', function() {
setup(function() {
this.sandbox.stub(this.app, 'loadLazyController');
this.app.onWillChange();
});
test('Should lazy load recording-timer controller', function() {
sinon.assert.calledWith(this.app.loadLazyController, 'controllers/recording-timer');
});
});
suite('App#onNewThumbnail', function() {
setup(function() {
this.sandbox.stub(this.app, 'loadLazyController');
this.app.onNewThumbnail();
});
test('Should lazy load recording-timer controller', function() {
sinon.assert.calledWith(this.app.loadLazyController, 'controllers/preview-gallery');
});
});
suite('App#onPreview', function() {
setup(function() {
this.sandbox.stub(this.app, 'loadLazyController').returns({
then: sinon.stub().callsArg(0)
});
this.app.onPreview();
});
test('Should lazy load preview gallery controller', function() {
sinon.assert.calledWith(this.app.loadLazyController, 'controllers/preview-gallery');
});
test('Should re-emit `preview`', function() {
sinon.assert.calledWith(this.app.emit, 'preview');
});
});
suite('App#loadLazyController()', function() {
setup(function() {
this.sandbox.stub(this.app, 'loadLazyControllers').returns('<promise>');
this.app.controllers.lazy = [];
});
test('Should add to lazy load controllers if critical path not reached', function() {
var promise = this.app.loadLazyController('<controller>');
assert.isTrue(this.app.controllers.lazy[0] === '<controller>');
assert.isTrue(promise === undefined);
});
test('Should lazy load immediately if critical path done', function() {
this.app.criticalPathDone = true;
var promise = this.app.loadLazyController('<controller>');
sinon.assert.called(this.app.loadLazyControllers);
assert.isTrue(promise === '<promise>');
assert.isTrue(this.app.dynamicLazy['<controller>'] === '<promise>');
});
test('Should return existing lazy load promise', function() {
this.app.criticalPathDone = true;
this.app.dynamicLazy['<controller>'] = '<existing-promise>';
var promise = this.app.loadLazyController('<controller>');
sinon.assert.notCalled(this.app.loadLazyControllers);
assert.isTrue(promise === '<existing-promise>');
});
});
suite('App#loadLazyControllers()', function() {
setup(function() {
this.fakeControllers = [ sinon.spy(), sinon.spy(), sinon.spy() ];
this.lazyPromise = this.app.loadLazyControllers(this.app.controllers.lazy);
this.callback = this.app.require.withArgs(this.app.controllers.lazy).args[0][1];
this.callback.apply(window, this.fakeControllers);
});
test('It requires the lazy controllers', function() {
sinon.assert.calledWith(this.app.require, this.app.controllers.lazy);
});
test('It runs each controller, passing in the App instance', function() {
var self = this;
this.fakeControllers.forEach(function(fakeController) {
sinon.assert.calledWith(fakeController, self.app);
});
});
test('It emits `lazyloaded`', function() {
sinon.assert.calledWith(this.app.emit, 'lazyloaded');
});
test('It fulfills the promise', function(done) {
this.lazyPromise.then(done);
});
});
suite('after critical path', function() {
setup(function() {
// Stop annoying logs
this.sandbox.stub(console, 'log');
this.app.boot();
this.app.emit('viewfinder:visible');
});
test('It loads lazy controllers', function() {
sinon.assert.calledWith(this.app.require, this.app.controllers.lazy);
});
test('It fires the \'loaded\' event only when:', function(done) {
var loadLazyControllersRequireCallback = this.app.require.args[0][1];
// 1. Lazy controllers have loaded
loadLazyControllersRequireCallback();
// 2. Storage has been checked
this.app.emit('storage:checked');
this.app.once('loaded', done);
});
});
suite('App#onKeyDown', function() {
setup(function() {
this.settings.keyDownEvents.get.withArgs('volumedown')
.returns('capture');
this.settings.keyDownEvents.get.withArgs('volumeup')
.returns('capture');
this.settings.keyDownEvents.get.withArgs('camera')
.returns('capture');
this.settings.keyDownEvents.get.withArgs('mozcamerafocusadjust')
.returns('focus');
});
test('`volumedown` key emits a `keydown:capture` event', function() {
this.app.onKeyDown({ key: 'volumedown' });
sinon.assert.calledWith(this.app.emit, 'keydown:capture');
});
test('`volumeup` key emits a `keydown:capture` event', function() {
this.app.onKeyDown({ key: 'volumeup' });
sinon.assert.calledWith(this.app.emit, 'keydown:capture');
});
test('`camera` key emits a `keydown:capture` event', function() {
this.app.onKeyDown({ key: 'camera' });
sinon.assert.calledWith(this.app.emit, 'keydown:capture');
});
test('`focus` key emits a `keydown:focus` event', function() {
this.app.onKeyDown({ key: 'mozcamerafocusadjust' });
sinon.assert.calledWith(this.app.emit, 'keydown:focus');
});
});
});

View file

@ -0,0 +1,295 @@
suite('controllers/activity', function() {
/*jshint maxlen:false*/
'use strict';
suiteSetup(function(done) {
var self = this;
requirejs([
'app',
'controllers/activity',
'lib/setting',
], function(App, ActivityController, Setting) {
self.ActivityController = ActivityController.ActivityController;
self.Setting = Setting;
self.App = App;
done();
});
});
setup(function() {
this.sandbox = sinon.sandbox.create();
this.app = sinon.createStubInstance(this.App);
this.app.win = { location: {} };
this.app.activity = {};
this.app.settings = {
mode: sinon.createStubInstance(this.Setting),
pictureSizes: sinon.createStubInstance(this.Setting),
recorderProfiles: sinon.createStubInstance(this.Setting),
activity: sinon.createStubInstance(this.Setting)
};
// Stub `mozSetMessageHandler`
navigator.mozSetMessageHandler = navigator.mozSetMessageHandler || function() {};
this.sandbox.stub(navigator, 'mozSetMessageHandler');
// Aliases
this.settings = this.app.settings;
this.activity = this.app.activity;
this.controller = new this.ActivityController(this.app);
});
teardown(function() {
this.sandbox.restore();
});
suite('ActivityController#configure()', function() {
test('Should should store the activity type on `app.activity`', function() {
this.controller.getName = sinon.stub().returns('pick');
this.controller.configure();
assert.equal(this.app.activity.pick, true);
});
});
suite('ActivityController#bindEvents()', function() {
setup(function() {
sinon.spy(this.controller, 'setupListener');
});
test('It binds a listener straight away if an activity name was found', function() {
this.controller.name = 'pick';
this.controller.bindEvents();
sinon.assert.calledWith(navigator.mozSetMessageHandler, 'activity');
});
test('It binds a listener straight after critical path if no activity name was found', function() {
this.controller.name = undefined;
this.controller.bindEvents();
sinon.assert.calledWith(this.app.once, 'criticalpathdone');
// Call the callback
this.app.once.withArgs('criticalpathdone').args[0][1]();
sinon.assert.calledWith(navigator.mozSetMessageHandler, 'activity');
});
});
suite('ActivityController#getName()', function() {
test('It returns the name after the #', function() {
this.app.win.location.hash = '#pick';
var name = this.controller.getName();
assert.equal(name, 'pick');
});
test('It returns undefined if no hash fragment is present', function() {
this.app.win.location.hash = '';
var name = this.controller.getName();
assert.equal(name, undefined);
});
});
suite('Activity#confgureMode()', function() {
test('Should get \'picture\' and \'video\' modes for SMS \'pick\' activity', function() {
var activity = {
'source': {
'data': {
'type': ['image/*', 'audio/*', 'video/*'],
'maxFileSizeBytes': 307200
},
'name': 'pick'
}
};
this.controller.configureMode(activity);
var modes = this.settings.mode.filterOptions.args[0][0];
assert.deepEqual(modes, ['picture', 'audio', 'video']);
});
test('Should get \'picture\' and \'video\' modes input[type="file"] \'pick\' activity', function() {
var activity = {
'source': {
'data': {
'type': [],
'nocrop': true
},
'name': 'pick'
}
};
this.controller.configureMode(activity);
var modes = this.settings.mode.filterOptions.args[0][0];
assert.deepEqual(modes, ['picture', 'video']);
});
test('Should get \'picture\' mode for input[type="file"] \'pick\' activity', function() {
var activity = {
'source': {
'data': {
'type': ['image/gif', 'image/jpeg', 'image/pjpeg',
'image/png', 'image/svg+xml', 'image/tiff',
'image/vnd.microsoft.icon'],
'nocrop': true
},
'name': 'pick'
}
};
this.controller.configureMode(activity);
var modes = this.settings.mode.filterOptions.args[0][0];
assert.deepEqual(modes, ['picture']);
});
test('Should be able to cope with String as well as Array', function() {
var activity = {
source: {
data: { type: 'image/jpeg' },
name: 'pick'
}
};
this.controller.configureMode(activity);
var modes = this.settings.mode.filterOptions.args[0][0];
assert.deepEqual(modes, ['picture']);
});
test('Should get [\'picture\', \'video\'] modes for Lockscreen/Gallery \'record\' activity', function() {
var activity = {
source: {
data: { type: 'photos' },
name: 'record'
}
};
this.controller.configureMode(activity);
var modes = this.settings.mode.filterOptions.args[0][0];
assert.deepEqual(modes, ['picture', 'video']);
});
test('Should get [\'video\', \'picture\'] modes for ' +
'Video \'record\' activity', function() {
var activity = {
source: {
data: { type: 'videos' },
name: 'record'
}
};
this.controller.configureMode(activity);
var modes = this.settings.mode.filterOptions.args[0][0];
assert.deepEqual(modes, ['video', 'picture']);
});
});
suite('ActivityController#onMessage()', function() {
setup(function() {
this.activity = {
source: {
name: 'pick',
data: {
type: 'image/jpeg',
maxFileSizeBytes: 100
},
}
};
sinon.spy(this.controller, 'configureMode');
sinon.stub(this.controller, 'getMaxPixelSize').returns('<max-pixel-size>');
this.controller.onMessage(this.activity);
});
test('Should emit app events', function() {
sinon.assert.calledWith(this.app.emit, 'activity');
sinon.assert.calledWith(this.app.emit, 'activity:pick');
});
test('Should configure mode', function() {
sinon.assert.called(this.controller.configureMode);
});
test('Should keep a reference to the activity', function() {
assert.equal(this.controller.activity, this.activity);
});
test('Should pass `data.maxFileSizeBytes`', function() {
var data = this.app.emit.withArgs('activity:pick').args[0][1];
assert.equal(data.maxFileSizeBytes, 100);
});
test('Should pass `data.name`', function() {
var data = this.app.emit.withArgs('activity:pick').args[0][1];
assert.equal(data.name, 'pick');
});
test('Should pass `data.maxPixelSize`', function() {
var data = this.app.emit.withArgs('activity:pick').args[0][1];
assert.equal(data.maxPixelSize, '<max-pixel-size>');
});
test('Should not do anything if name is unrecognised', function() {
this.app.emit.reset();
this.activity.source.name = 'unknown';
this.controller.onMessage(this.activity);
assert.isFalse(this.app.emit.calledWith('activity'));
assert.isFalse(this.app.emit.calledWith('activity:unknown'));
});
});
suite('ActivityController#getMaxPixelSize()', function() {
setup(function() {
this.activity = {
source: {
name: 'pick',
data: {
type: 'image/jpeg',
maxFileSizeBytes: 100,
width: 100,
height: 100
},
}
};
this.settings.activity.get
.withArgs('maxPickPixelSize')
.returns(480000);
this.settings.activity.get
.withArgs('maxPixelSizeScaleFactor')
.returns(2.5);
});
test('Should return pixel estimate of maxFileSizeBytes if supplied', function() {
var result = this.controller.getMaxPixelSize(this.activity);
assert.equal(result, 267);
});
test('Should return a pixel estimate based on width height if supplied', function() {
delete this.activity.source.data.maxFileSizeBytes;
var result = this.controller.getMaxPixelSize(this.activity);
assert.equal(result, 25000);
});
test('Should return `maxPickPixelSize` if neither supplied', function() {
delete this.activity.source.data.maxFileSizeBytes;
delete this.activity.source.data.width;
delete this.activity.source.data.height;
var result = this.controller.getMaxPixelSize(this.activity);
assert.equal(result, this.settings.activity.get('maxPickPixelSize'));
});
test('Should be able to cope with only one dimension being specified', function() {
delete this.activity.source.data.maxFileSizeBytes;
delete this.activity.source.data.height;
var result = this.controller.getMaxPixelSize(this.activity);
assert.equal(result, (1/3 * 100000));
delete this.activity.source.data.width;
this.activity.source.data.height = 100;
result = this.controller.getMaxPixelSize(this.activity);
assert.equal(result, (1/3 * 100000));
});
});
});

View file

@ -0,0 +1,235 @@
suite('controllers/battery', function() {
'use strict';
suiteSetup(function(done) {
var self = this;
requirejs([
'app',
'controllers/battery',
'lib/settings',
'lib/setting',
'views/notification'
], function(
App, BatteryController, Settings, Setting, NotificationView) {
self.BatteryController = BatteryController.BatteryController;
self.NotificationView = NotificationView;
self.Settings = Settings;
self.Setting = Setting;
self.App = App;
done();
});
});
setup(function() {
var levels = {
low: 15,
verylow: 10,
critical: 6,
shutdown: 5,
healthy: 100
};
this.app = sinon.createStubInstance(this.App);
this.app.settings = sinon.createStubInstance(this.Settings);
this.app.settings.battery = sinon.createStubInstance(this.Setting);
this.app.settings.battery.get.withArgs('levels').returns(levels);
this.app.l10n = { get: sinon.stub() };
this.app.localized = sinon.stub().returns(true);
this.app.views = {
notification: sinon.createStubInstance(this.NotificationView)
};
// Fake battery
this.app.battery = {
addEventListener: sinon.stub(),
level: 1,
charging: false
};
// Shortcuts
this.notification = this.app.views.notification;
// Keep reference of
// things we want to restore
this.backup = {
mozSettings: navigator.mozSettings
};
// Mock object that mimicks
// mozSettings get API. Inside
// tests set this.mozSettingsGetResult
// define the result of the mock call.
navigator.mozSettings = {
createLock: function() { return this; },
get: sinon.stub(),
set: sinon.stub(),
addObserver: sinon.stub()
};
navigator.mozSettings.get.withArgs('powersave.enabled').returns(
Promise.resolve({'powersave.enabled': false})
);
// Our test instance
this.controller = new this.BatteryController(this.app);
});
teardown(function() {
navigator.mozSettings = this.backup.mozSettings;
});
suite('BatteryController()', function() {
test('Should listen to the following events', function() {
assert.ok(this.app.on.calledWith('change:batteryStatus'));
assert.ok(this.app.on.calledWith('change:recording'));
assert.ok(this.app.battery.addEventListener.calledWith('levelchange'));
assert.ok(this.app.battery.addEventListener.calledWith('chargingchange'));
assert.ok(navigator.mozSettings.addObserver.calledWith('powersave.enabled'));
});
test('Should update the status initially', function() {
assert.ok(this.app.set.calledWith('batteryStatus', 'healthy'));
});
test('Should query power save state', function() {
assert.ok(navigator.mozSettings.get.calledWith('powersave.enabled'));
});
});
suite('BatteryController#updateStatus()', function() {
test('should call app.set with batteryStatus and battery charging' +
' on updateStatus', function() {
this.app.battery.charging = true;
this.controller.updateStatus();
assert.ok(this.app.set.calledWith('batteryStatus', 'charging'));
});
test('should call app.set with batteryStatus and low battery level' +
' on updateStatus', function() {
this.app.battery.level = 0.11;
this.controller.app.get
.withArgs('batteryStatus')
.returns('charging');
this.controller.updateStatus();
assert.ok(this.app.set.calledWith('batteryStatus', 'low'));
});
test('should call app.set with batteryStatus and verylow battery' +
' level on updateStatus', function() {
this.app.battery.level = 0.10;
this.controller.app.get
.withArgs('batteryStatus')
.returns('low');
this.controller.updateStatus();
assert.ok(this.app.set.calledWith('batteryStatus', 'verylow'));
});
test('should call app.set with batteryStatus and critical battery' +
' level on updateStatus', function() {
this.app.battery.level = 0.06;
this.controller.app.get
.withArgs('batteryStatus')
.returns('verylow');
this.controller.updateStatus();
assert.ok(this.app.set.calledWith('batteryStatus', 'critical'));
});
test('should call app.set with batteryStatus and battery shutdown' +
' level on updateStatus', function() {
this.app.battery.level = 0.05;
this.controller.app.get
.withArgs('batteryStatus')
.returns('critical');
this.controller.updateStatus();
assert.ok(this.app.set.calledWith('batteryStatus', 'shutdown'));
});
test('should call app.set with batteryStatus and battery healthy' +
' level on updateStatus', function() {
this.app.battery.level = 1;
this.controller.app.get
.withArgs('batteryStatus')
.returns('charging');
this.controller.updateStatus();
assert.ok(this.app.set.calledWith('batteryStatus', 'healthy'));
});
});
suite('BatteryController#onStatusChange()', function() {
test('Should display notification if required', function() {
this.controller.onStatusChange('low');
assert.isTrue(this.notification.display.called);
this.notification.display.reset();
this.controller.onStatusChange('verylow');
assert.isTrue(this.notification.display.called);
this.notification.display.reset();
this.controller.onStatusChange('critical');
assert.isTrue(this.notification.display.called);
this.notification.display.reset();
this.controller.onStatusChange('healthy');
assert.isFalse(this.notification.display.called);
this.notification.display.reset();
this.controller.onStatusChange('shutdown');
assert.isFalse(this.notification.display.called);
this.notification.display.reset();
});
test('Should always clear the last notification', function() {
this.controller.onStatusChange('low');
assert.isTrue(this.notification.clear.called);
});
});
suite('BatteryController#updatePowerSave()', function() {
setup(function() {
this.controller.powerSave = true;
this.controller.powerSaveEnabled = true;
this.app.get.withArgs('recording').returns(false);
});
test('Should emit `battery:powersave` if changed', function() {
this.controller.powerSave = false;
this.controller.updatePowerSave();
assert.isTrue(this.controller.powerSave);
assert.ok(this.app.emit.calledWith('battery:powersave', true));
});
test('Should disable if recording', function() {
this.app.get.withArgs('recording').returns(true);
this.controller.updatePowerSave();
assert.isFalse(this.controller.powerSave);
assert.ok(this.app.emit.calledWith('battery:powersave', false));
});
test('Should disable if power save disabled', function() {
this.controller.powerSaveEnabled = false;
this.controller.updatePowerSave();
assert.isFalse(this.controller.powerSave);
assert.ok(this.app.emit.calledWith('battery:powersave', false));
});
});
suite('BatteryController#onPowerSaveChange()', function() {
setup(function() {
sinon.stub(this.controller, 'updatePowerSave');
this.controller.onPowerSaveChange({settingValue: 'result'});
});
test('Should cache power save enabled state', function() {
assert.isTrue(this.controller.powerSaveEnabled === 'result');
});
test('Should call `updatePowerSave`', function() {
assert.ok(this.controller.updatePowerSave.called);
});
});
});

View file

@ -0,0 +1,762 @@
/*jshint maxlen:false*/
suite('controllers/camera', function() {
'use strict';
suiteSetup(function(done) {
var self = this;
requirejs([
'app',
'controllers/camera',
'lib/camera/camera',
'lib/settings',
'lib/setting',
'lib/geo-location'
], function(
App, CameraController, Camera, Settings, Setting, GeoLocation) {
self.CameraController = CameraController.CameraController;
self.GeoLocation = GeoLocation;
self.Settings = Settings;
self.Setting = Setting;
self.Camera = Camera;
self.App = App;
done();
});
});
setup(function() {
this.sandbox = sinon.sandbox.create();
this.app = sinon.createStubInstance(this.App);
this.app.camera = sinon.createStubInstance(this.Camera);
this.app.geolocation = sinon.createStubInstance(this.GeoLocation);
this.app.views = {
notification: {
display: sinon.spy()
}
};
// Activity
this.app.activity = {};
this.app.storage = {
getItem: sinon.stub(),
setItem: sinon.stub()
};
this.app.get.withArgs('batteryStatus').returns('healthy');
// Settings
this.app.settings = sinon.createStubInstance(this.Settings);
this.app.settings.cameras = sinon.createStubInstance(this.Setting);
this.app.settings.mode = sinon.createStubInstance(this.Setting);
this.app.settings.pictureSizes = sinon.createStubInstance(this.Setting);
this.app.settings.isoModes = sinon.createStubInstance(this.Setting);
this.app.settings.recorderProfiles = sinon.createStubInstance(this.Setting);
this.app.settings.flashModes = sinon.createStubInstance(this.Setting);
this.app.settings.whiteBalance = sinon.createStubInstance(this.Setting);
this.app.settings.hdr = sinon.createStubInstance(this.Setting);
this.app.settings.flashModesPicture = sinon.createStubInstance(this.Setting);
this.app.settings.countdown = sinon.createStubInstance(this.Setting);
// Aliases
this.settings = this.app.settings;
this.notification = this.app.views.notification;
this.storage = this.app.storage;
this.camera = this.app.camera;
this.camera.cameraList = ['back', 'front'];
// Test instance (some tests create there own)
this.controller = new this.CameraController(this.app);
});
teardown(function() {
this.sandbox.restore();
});
suite('CameraController()', function() {
setup(function() {
this.sandbox.stub(this.CameraController.prototype, 'shutdownCamera');
});
test('Should filter the `cameras` setting based on the camera list', function() {
sinon.assert.calledWith(this.settings.cameras.filterOptions, this.camera.cameraList);
});
test('Should load camera on app `visible`', function() {
assert.isTrue(this.app.on.calledWith('visible', this.controller.loadCamera));
});
test('Should teardown camera on app `hidden`', function() {
assert.isTrue(this.app.on.calledWith('hidden', this.controller.shutdownCamera));
});
test('Should relay focus change events', function() {
assert.isTrue(this.camera.on.calledWith('change:focus'));
assert.isTrue(this.app.firer.calledWith('camera:focusstatechanged'));
});
test('Should listen to storage:changed', function() {
assert.isTrue(this.app.on.calledWith('storage:changed'));
});
test('Should listen to \'configured\' event', function() {
assert.isTrue(this.camera.on.calledWith('configured'));
});
test('Should listen to the \'activity:pick\' event', function() {
sinon.assert.calledWith(this.app.on, 'activity:pick');
});
test('Should query battery status', function() {
sinon.assert.calledWith(this.app.get, 'batteryStatus');
assert.isFalse(this.controller.lowBattery);
});
});
suite('camera.on(\'configured\')', function() {
test('Should relay via app event', function() {
sinon.assert.calledWith(this.camera.on, 'configured');
sinon.assert.calledWith(this.app.firer, 'camera:configured');
});
});
suite('on(\'activity:pick\')', function() {
setup(function() {
var spy = this.app.on.withArgs('activity:pick');
this.callback = spy.args[0][1];
this.data = { maxFileSizeBytes: 100 };
this.callback(this.data);
});
test('Should set `maxFileSizeBytes` on camera', function() {
sinon.assert.calledWith(this.camera.set, 'maxFileSizeBytes', 100);
});
test('Should disable the camera boot config caching', function() {
this.camera.cacheConfig = true;
this.callback({});
assert.isFalse(this.camera.cacheConfig);
});
});
suite('CameraController#onSettingsConfigured()', function() {
setup(function() {
// Mock object that mimicks
// mozSettings get API. Inside
// tests set this.mozSettingsGetResult
// define the result of the mock call.
navigator.mozSettings = {
createLock: function() { return this; },
get: function(key) {
var mozSettings = this;
setTimeout(function() {
var result = {};
result[key] = 'the-result';
mozSettings.onsuccess({
target: {
result: result
}
});
});
return this;
}
};
this.app.settings.flashModes.selected.returns('on');
this.app.settings.isoModes.get = sinon.spy();
this.app.settings.isoModes.selected.returns({key: 'auto'});
this.app.settings.pictureSizes.selected.returns({ width: 480, height: 640 });
this.app.settings.recorderProfiles.selected.returns('cif');
this.app.settings.hdr.selected.returns('on');
// Returns `this` to allow chaining
this.app.camera.setRecorderProfile.returns(this.app.camera);
this.app.camera.setPictureSize.returns(this.app.camera);
this.app.camera.configureZoom.returns(this.app.camera);
this.controller = new this.CameraController(this.app);
this.controller.onSettingsConfigured();
});
test('Should set flashMode', function() {
assert.ok(this.camera.setFlashMode.called);
});
test('Should set hdr', function() {
assert.ok(this.camera.setHDR.calledWith('on'));
});
test('Should set recorderProfile', function() {
assert.ok(this.camera.setRecorderProfile.calledWith('cif'));
});
test('Should set pictureSize', function() {
var arg = this.app.camera.setPictureSize.args[0][0];
assert.ok(arg.width === 480);
assert.ok(arg.height === 640);
});
});
suite('CameraController#onRecordingChange', function() {
test('Should set `recording` to true if started', function() {
this.controller.onRecordingChange('started');
assert.ok(this.app.set.calledWith('recording', true));
});
test('Should set `recording` to true if stopped', function() {
this.controller.onRecordingChange('stopped');
assert.ok(this.app.set.calledWith('recording', false));
});
test('Should not set `recording` if starting or stopping', function() {
var self = this;
['error', 'starting', 'stopping'].forEach(function(recording) {
self.controller.onRecordingChange(recording);
sinon.assert.notCalled(self.app.set);
});
});
});
suite('CameraController#onCaptureKey', function() {
setup(function() {
this.app.busy = false;
this.controller.lowBattery = false;
});
test('`keydown:capture` triggers capture', function() {
var callback = this.app.on.withArgs('keydown:capture').args[0][1];
var event = { preventDefault: sinon.spy() };
callback(event);
sinon.assert.called(this.camera.capture);
});
test('It calls preventDefault if the capture call doesn\'t return false', function() {
var callback = this.app.on.withArgs('keydown:capture').args[0][1];
var event = { preventDefault: sinon.spy() };
this.camera.capture.returns(false);
callback(event);
sinon.assert.notCalled(event.preventDefault);
this.camera.capture.returns(undefined);
callback(event);
sinon.assert.called(event.preventDefault);
});
test('It doesnt capture if countdown is active', function() {
this.controller.countdown = 999;
var callback = this.app.on.withArgs('keydown:capture').args[0][1];
var event = { preventDefault: sinon.spy() };
callback(event);
sinon.assert.notCalled(this.camera.capture);
});
test('It doesnt capture if confirm overlay is shown', function() {
this.app.get.withArgs('confirmViewVisible').returns(true);
var callback = this.app.on.withArgs('keydown:capture').args[0][1];
var event = { preventDefault: sinon.spy() };
callback(event);
sinon.assert.notCalled(this.camera.capture);
});
test('It doesnt capture if busy', function() {
this.app.busy = true;
var callback = this.app.on.withArgs('keydown:capture').args[0][1];
var event = { preventDefault: sinon.spy() };
callback(event);
sinon.assert.notCalled(this.camera.capture);
});
});
suite('CameraController#onFocusKey', function() {
setup(function() {
this.app.busy = false;
this.camera.focus = {
focus: this.sinon.spy()
};
});
test('`keydown:focus` triggers focus', function() {
var callback = this.app.on.withArgs('keydown:focus').args[0][1];
var event = { preventDefault: sinon.spy() };
callback(event);
sinon.assert.called(this.camera.focus.focus);
});
test('It doesnt focus if busy', function() {
this.app.busy = true;
var callback = this.app.on.withArgs('keydown:focus').args[0][1];
var event = { preventDefault: sinon.spy() };
callback(event);
sinon.assert.notCalled(this.camera.focus.focus);
});
});
suite('CameraController#setMode()', function() {
test('It sets the flash mode', function() {
this.controller.setMode();
sinon.assert.called(this.notification.display);
sinon.assert.called(this.camera.setFlashMode);
});
test('It emits `camera:willchange` event', function() {
this.controller.setMode();
sinon.assert.calledWith(this.app.emit, 'camera:willchange');
});
test('It sets the mode once the viewfinder is hidden', function() {
this.controller.setMode('my-mode');
sinon.assert.calledWith(this.app.once, 'viewfinder:hidden');
// call the callback
this.app.once.withArgs('viewfinder:hidden').args[0][1]();
sinon.assert.calledWith(this.camera.setMode, 'my-mode');
});
test('It doesn\'t set the do anything if the mode didn\'t change', function() {
this.camera.isMode.returns(true);
this.controller.setMode('my-mode');
assert.isFalse(this.app.emit.calledWith('camera:willchange'));
});
});
suite('CameraController#setCamera()', function() {
test('It emits `camera:willchange` event', function() {
this.controller.setMode();
sinon.assert.calledWith(this.app.emit, 'camera:willchange');
});
test('It sets the camera once the viewfinder is hidden', function() {
this.controller.setCamera('back');
sinon.assert.calledWith(this.app.once, 'viewfinder:hidden');
// call the callback
this.app.once.withArgs('viewfinder:hidden').args[0][1]();
sinon.assert.calledWith(this.camera.setCamera, 'back');
});
});
suite('CameraController#updatePictureSize()', function() {
setup(function() {
this.settings.mode.selected
.withArgs('key')
.returns('picture');
this.settings.pictureSizes.selected
.withArgs('data')
.returns({ width: 400, height: 300 });
});
test('It emits `camera:willchange` event', function() {
this.controller.updatePictureSize();
sinon.assert.calledWith(this.app.emit, 'camera:willchange');
});
test('It sets the camera once the viewfinder is hidden', function() {
this.controller.updatePictureSize();
sinon.assert.calledWith(this.app.once, 'viewfinder:hidden');
// call the callback
this.app.once.withArgs('viewfinder:hidden').args[0][1]();
sinon.assert.calledWith(this.camera.setPictureSize, { width: 400, height: 300 });
});
test('It doesn\'t proceed if `pictureSize` didn\'t change', function() {
this.camera.isPictureSize.returns(true);
this.controller.updatePictureSize();
assert.isFalse(this.app.emit.calledWith('camera:willchange'));
});
suite('`video` mode', function() {
setup(function() {
this.settings.mode.selected
.withArgs('key')
.returns('video');
});
test('It doesn\'t emit `camera:willchange` if in `video` mode', function() {
this.controller.updatePictureSize();
assert.isFalse(this.app.emit.calledWith('camera:willchange'));
});
test('It calls `camera.setPictureSize`, but doesn\'t reconfigure camera', function() {
this.controller.updatePictureSize();
sinon.assert.calledWith(
this.camera.setPictureSize,
{ width: 400, height: 300 },
{ configure: false }
);
});
});
});
suite('CameraController#updateRecorderProfile()', function() {
setup(function() {
this.settings.mode.selected
.withArgs('key')
.returns('video');
this.settings.recorderProfiles.selected
.withArgs('key')
.returns('720p');
});
test('It emits `camera:willchange` event', function() {
this.controller.updateRecorderProfile();
sinon.assert.calledWith(this.app.emit, 'camera:willchange');
});
test('It sets the camera once the viewfinder is hidden', function() {
this.controller.updateRecorderProfile();
sinon.assert.calledWith(this.app.once, 'viewfinder:hidden');
// call the callback
this.app.once.withArgs('viewfinder:hidden').args[0][1]();
sinon.assert.calledWith(this.camera.setRecorderProfile, '720p');
});
test('It doesn\'t proceed if `recorderProfile` didn\'t change', function() {
this.camera.isRecorderProfile.returns(true);
this.controller.updateRecorderProfile();
assert.isFalse(this.app.emit.calledWith('camera:willchange'));
});
suite('`picture` mode', function() {
setup(function() {
this.settings.mode.selected
.withArgs('key')
.returns('picture');
});
test('It doesn\'t emit `camera:willchange` if in `video` mode', function() {
this.controller.updateRecorderProfile();
assert.isFalse(this.app.emit.calledWith('camera:willchange'));
});
test('It calls `camera.setRecorderProfile`, but doesn\'t reconfigure camera', function() {
this.controller.updateRecorderProfile();
sinon.assert.calledWith(
this.camera.setRecorderProfile,
'720p',
{ configure: false }
);
});
});
});
suite('CameraController#onHDRChange()', function() {
test('Should set flashModesPicture \'off\' when hdr is set to \'on\'',
function() {
var flashModesPicture = this.app.settings.flashModesPicture;
flashModesPicture.selected.withArgs('key').returns('on');
this.controller.onHDRChange('on');
assert.ok(flashModesPicture.select.calledWith('off'));
});
});
suite('CameraController#setHDR()', function() {
setup(function() {
this.settings.hdr.selected
.withArgs('key')
.returns('on');
});
test('Should call camera.setHDR with given value', function() {
this.controller.setHDR();
sinon.assert.calledWith(this.camera.setHDR, 'on');
});
test('Should not call camera.setHDR if `hdrDisabled`', function() {
this.controller.hdrDisabled = true;
this.controller.setHDR();
sinon.assert.notCalled(this.camera.setHDR);
});
});
suite('CameraController#onFlashModeChange()', function() {
setup(function() {
this.settings.hdr.selected
.withArgs('key')
.returns('on');
});
test('Should change hdr to off if flash is on', function() {
this.controller.hdrDisabled = false;
this.controller.onFlashModeChange('on');
assert.ok(this.settings.hdr.select.calledWith('off'));
});
test('Should not change HDR if flash is off', function() {
this.controller.hdrDisabled = false;
this.controller.onFlashModeChange('off');
assert.ok(!this.settings.hdr.select.called);
});
test('Should not change HDR if HDR is off', function() {
this.settings.hdr.selected.withArgs('key').returns('off');
this.controller.hdrDisabled = true;
this.controller.onFlashModeChange('auto');
assert.ok(!this.settings.hdr.select.called);
});
test('Should not change HDR if HDR is disabled', function() {
this.controller.hdrDisabled = true;
this.controller.onFlashModeChange('auto');
assert.ok(!this.settings.hdr.select.called);
});
});
suite('CameraController#capture()', function() {
setup(function() {
this.controller.galleryOpen = false;
this.controller.lowBattery = false;
sinon.spy(this.controller, 'startCountdown');
});
test('Should not start countdown if no countdown setting is set', function() {
this.app.settings.countdown.selected.returns(0);
this.app.get.withArgs('timerActive').returns(false);
this.app.get.withArgs('recording').returns(false);
this.controller.capture();
sinon.assert.notCalled(this.controller.startCountdown);
});
test('Should not start countdown if countdown is already active', function() {
this.app.settings.countdown.selected.returns(5);
this.controller.countdown = 999;
this.app.get.withArgs('recording').returns(false);
this.controller.capture();
sinon.assert.notCalled(this.controller.startCountdown);
});
test('Should not start countdown if recording', function() {
this.app.settings.countdown.selected.returns(5);
this.app.get.withArgs('timerActive').returns(false);
this.app.get.withArgs('recording').returns(true);
this.controller.capture();
sinon.assert.notCalled(this.controller.startCountdown);
});
test('Should otherwise start countdown', function() {
this.app.settings.countdown.selected.returns(5);
this.app.get.withArgs('timerActive').returns(false);
this.app.get.withArgs('recording').returns(false);
this.controller.capture();
sinon.assert.calledOnce(this.controller.startCountdown);
});
test('Should pass the current geolocation position', function() {
this.app.geolocation.position = 123;
this.controller.capture();
assert.ok(this.app.camera.capture.args[0][0].position === 123);
});
test('Should return false if low battery', function() {
this.controller.lowBattery = true;
assert.ok(this.controller.capture() === false);
});
test('Should return false if gallery open', function() {
this.controller.galleryOpen = true;
assert.ok(this.controller.capture() === false);
});
});
suite('CameraController#onBatteryStatusChange()', function() {
setup(function() {
this.controller.loadCamera = sinon.stub();
sinon.spy(this.controller, 'shutdownCamera');
this.controller.clearCountdown = sinon.stub();
});
test('Should call onBatteryStatuchange on \'change:batteryStatus\'',
function() {
this.app.on.calledWith('change:batteryStatus', this.onBatteryStatusChange);
});
test('Should load camera on `healthy` status', function() {
this.controller.onBatteryStatusChange('healthy');
assert.isTrue(this.controller.loadCamera.called);
});
test('Should shutdown camera on `shutdown` status', function() {
this.controller.onBatteryStatusChange('shutdown');
assert.isTrue(this.controller.shutdownCamera.called);
});
test('Should clear countdown on `shutdown` status', function() {
this.controller.onBatteryStatusChange('shutdown');
sinon.assert.calledOnce(this.controller.clearCountdown);
});
test('Should prevent capture on low battery', function() {
this.controller.onBatteryStatusChange('shutdown');
this.controller.capture();
assert.isFalse(this.camera.capture.called);
this.controller.onBatteryStatusChange('healthy');
this.controller.capture();
assert.isTrue(this.camera.capture.called);
});
});
suite('CameraController#startCountdown()', function() {
setup(function() {
this.app.settings.countdown.selected.withArgs('value').returns(5);
this.clock = this.sinon.useFakeTimers();
this.controller.lowBattery = false;
});
test('Emits a `countdown:started` event', function() {
this.controller.startCountdown();
sinon.assert.calledWith(this.app.emit, 'countdown:started', 5);
});
test('Emits a `countdown:tick` event each second', function() {
this.controller.startCountdown();
this.clock.tick(1000);
sinon.assert.calledWith(this.app.emit, 'countdown:tick', 4);
this.clock.tick(1000);
sinon.assert.calledWith(this.app.emit, 'countdown:tick', 3);
this.clock.tick(1000);
sinon.assert.calledWith(this.app.emit, 'countdown:tick', 2);
this.clock.tick(1000);
sinon.assert.calledWith(this.app.emit, 'countdown:tick', 1);
});
test('Emits a `countdown:ended` event once it reaches 0', function() {
this.controller.startCountdown();
this.clock.tick(5000);
sinon.assert.calledWith(this.app.emit, 'countdown:ended');
});
test('It calls camera.capture once ended', function() {
this.controller.startCountdown();
this.clock.tick(5000);
sinon.assert.calledOnce(this.camera.capture);
});
test('It clears the coundown when the app base el is clicked', function() {
var proto = this.controller.constructor.prototype;
var clearCountdown = this.sinon.spy(proto, 'clearCountdown');
this.app.on.reset();
var controller = new this.CameraController(this.app);
var callback = this.app.on.withArgs('click').args[0][1];
controller.startCountdown();
this.clock.tick(2000);
callback();
sinon.assert.calledOnce(clearCountdown);
});
});
suite('CameraController#loadCamera()', function() {
setup(function() {
this.app.hidden = false;
this.app.activity.pick = false;
this.app.isSharingActivity = sinon.stub();
this.app.isSharingActivity.returns(false);
this.app.showSpinner = sinon.stub();
this.controller.galleryOpen = false;
this.controller.lowBattery = false;
});
test('It doesn\'t load the camera if the battery is low', function() {
this.controller.lowBattery = true;
this.controller.loadCamera();
sinon.assert.notCalled(this.camera.load);
});
test('It does load the camera if the battery is not low', function() {
this.controller.loadCamera();
sinon.assert.called(this.camera.load);
});
test('It doesn\'t load the camera if gallery is open', function() {
this.controller.galleryOpen = true;
this.controller.loadCamera();
sinon.assert.notCalled(this.camera.load);
});
test('It does load the camera if gallery is closed', function() {
this.controller.loadCamera();
sinon.assert.called(this.camera.load);
});
test('It doesn\'t load the camera if app is hidden', function() {
this.app.hidden = true;
this.controller.loadCamera();
sinon.assert.notCalled(this.camera.load);
sinon.assert.notCalled(this.app.showSpinner);
});
test('It does load the camera if app is visible', function() {
this.controller.loadCamera();
sinon.assert.called(this.camera.load);
sinon.assert.notCalled(this.app.showSpinner);
});
test('It shows the spinner if loading and requested', function() {
this.controller.loadCamera(true);
sinon.assert.called(this.app.showSpinner);
});
});
suite('CameraController#shutdownCamera()', function() {
setup(function() {
this.controller.shutdownCamera();
});
test('Should stop shutdown the camera', function() {
assert.isTrue(this.camera.shutdown.called);
});
});
suite('CameraController#onStorageChanged()', function() {
test('Should stop recording if not available', function() {
this.controller.onStorageChanged('unavailable');
assert.isTrue(this.camera.stopRecording.called);
});
test('Should not stop recording if available', function() {
this.controller.onStorageChanged('available');
assert.isTrue(this.camera.stopRecording.notCalled);
});
});
suite('CameraController#onGalleryOpened()', function() {
test('Should store gallery open state', function() {
this.controller.shutdownCamera = sinon.stub();
this.controller.galleryOpen = false;
this.controller.onGalleryOpened();
assert.isTrue(this.controller.galleryOpen);
sinon.assert.called(this.controller.shutdownCamera);
});
});
suite('CameraController#onGalleryClosed()', function() {
test('It loads the camera', function() {
this.controller.loadCamera = sinon.stub();
this.controller.galleryOpen = true;
this.controller.onGalleryClosed();
assert.isFalse(this.controller.galleryOpen);
sinon.assert.calledWith(this.controller.loadCamera, true);
});
});
});

View file

@ -0,0 +1,88 @@
/*jshint maxlen:false*/
'use strict';
suite('controllers/confirm', function() {
suiteSetup(function(done) {
var modules = this.modules = {};
requirejs([
'controllers/confirm',
'views/confirm'
], function(Controller, ConfirmView) {
modules.controller = Controller;
modules.ConfirmView = ConfirmView;
done();
});
});
setup(function() {
var ConfirmView = this.modules.ConfirmView;
var Controller = this.modules.controller;
this.app = {
activity: {},
camera: { on: sinon.spy() },
ConfirmView: ConfirmView,
on: sinon.spy()
};
this.controller = new Controller(this.app);
this.sandbox = sinon.sandbox.create();
this.sandbox.spy(this.app, 'ConfirmView');
this.sandbox.stub(ConfirmView.prototype);
});
teardown(function() {
this.sandbox.restore();
});
suite('ConfirmController()', function() {
test('Should bind to app `newmedia` event', function() {
assert.ok(this.app.on.calledWith('newmedia'));
});
});
suite('ConfirmController#onRecordingChange()', function() {
setup(function() {
this.app.activity.pick = true;
this.controller.recording = true;
sinon.stub(this.controller, 'renderView');
});
test('Should set `recording` to true if starting', function() {
this.controller.recording = false;
this.controller.onRecordingChange('starting');
assert.isTrue(this.controller.recording);
});
test('Should set `recording` to false if error', function() {
this.controller.onRecordingChange('error');
assert.isFalse(this.controller.recording);
});
test('Should render view if stopped', function() {
this.controller.onRecordingChange('stopped');
sinon.assert.called(this.controller.renderView);
});
test('Should not do anything if recording failed and stopped', function() {
this.controller.recording = false;
this.controller.onRecordingChange('stopped');
sinon.assert.notCalled(this.controller.renderView);
});
test('Should not do anything if there is no active activity', function() {
this.app.activity.pick = false;
this.controller.onRecordingChange('stopped');
sinon.assert.notCalled(this.controller.renderView);
});
});
suite('ConfirmController#onNewMedia()', function() {
test('Should not do anything if there is no active activity', function() {
this.app.activity.pick = false;
this.controller.onNewMedia({});
assert.ok(this.app.ConfirmView.notCalled);
});
});
});

View file

@ -0,0 +1,214 @@
suite('controllers/controls', function() {
/*jshint maxlen:false*/
'use strict';
suiteSetup(function(done) {
var self = this;
requirejs([
'app',
'lib/camera/camera',
'controllers/controls',
'views/controls',
'lib/settings',
'lib/setting'
], function(
App, Camera, ControlsController, ControlsView, Settings, Setting) {
self.App = App;
self.Camera = Camera;
self.ControlsController = ControlsController.ControlsController;
self.ControlsView = ControlsView;
self.Settings = Settings;
self.Setting = Setting;
done();
});
});
setup(function() {
this.app = sinon.createStubInstance(this.App);
this.app.camera = sinon.createStubInstance(this.Camera);
this.app.settings = sinon.createStubInstance(this.Settings);
this.app.settings.mode = sinon.createStubInstance(this.Setting);
this.app.views = { controls: sinon.createStubInstance(this.ControlsView) };
this.app.activity = { allowedTypes: {} };
// Fake available modes
this.app.settings.mode.get
.withArgs('options')
.returns([{ key: 'picture' }, { key: 'video' }]);
// Aliases
this.controls = this.app.views.controls;
this.view = this.app.views.controls;
this.settings = this.app.settings;
this.controller = new this.ControlsController(this.app);
this.state = {};
});
teardown(function() {
delete this.controller;
});
suite('ControlsController()', function() {
test('Should listen to the following events', function() {
assert.ok(this.app.on.calledWith('localized', this.view.localize));
assert.ok(this.app.on.calledWith('previewgallery:opened',
this.view.hide));
assert.ok(this.app.on.calledWith('previewgallery:closed',
this.view.show));
});
test('Should *not* show the cancel button when *not* within a \'pick\' activity', function() {
assert.isTrue(this.app.views.controls.set.calledWith('cancel', false));
});
test('Should show the cancel button when within activity', function() {
this.app.activity.pick = true;
this.state.controller = new this.ControlsController(this.app);
assert.isTrue(this.app.views.controls.set.calledWith('cancel', true));
});
test('It sets the mode to the value of the \'mode\' setting', function() {
// Test 'picture'
this.app.settings.mode.selected.returns('picture');
var controller = new this.ControlsController(this.app);
sinon.assert.calledWith(this.view.setMode, 'picture');
this.view.set.reset();
// Test 'video'
this.app.settings.mode.selected.returns('video');
controller = new this.ControlsController(this.app);
sinon.assert.calledWith(this.view.setMode, 'video');
this.view.set.reset();
});
test('Should call the preview when click on thumbnail', function() {
assert.ok(this.view.on.calledWith('click:thumbnail'));
});
test('Should remove the capture button highlight when shutter fires', function() {
sinon.assert.calledWith(this.app.on, 'camera:shutter');
// Call the callback
this.app.on.withArgs('camera:shutter').args[0][1]();
sinon.assert.calledWith(this.view.unset, 'capture-active');
});
test('Should disable the controls when the camera is busy', function() {
// Call the callback and check the view was disabled
this.app.on.withArgs('busy').args[0][1]();
sinon.assert.called(this.view.disable);
});
suite('app.once(\'loaded\')', function() {
setup(function() {
// Call the callback
this.app.once.withArgs('loaded').args[0][1]();
});
test('It enables the controls', function() {
sinon.assert.called(this.view.enable);
});
test('It \'restores\' the controls when the camera is \'ready\' from thereon after', function() {
sinon.assert.calledWith(this.app.on, 'ready', this.controller.restore);
});
});
test('Should hide the controls when the countdown is started', function() {
sinon.assert.calledWith(
this.app.on,
'countdown:started',
this.controller.onCountdownStarted
);
});
test('Should restore the controls when the countdown is cleared', function() {
sinon.assert.calledWith(
this.app.on,
'countdown:ended',
this.controller.onCountdownStopped
);
});
test('Should disable the view intitially until camera is ready', function() {
sinon.assert.called(this.view.disable);
});
});
suite('Set view screen reader visibility based on settings opened/closed',
function() {
test('settings opened -> hide view from screen reader', function() {
// Check the view is hidden from the screen reader.
this.app.on.withArgs('settings:opened').args[0][1]();
sinon.assert.calledWith(this.view.setScreenReaderVisible, false);
});
test('settings closed -> show view to screen reader', function() {
// Check the view is visible to the screen reader.
this.app.on.withArgs('settings:closed').args[0][1]();
sinon.assert.calledWith(this.view.setScreenReaderVisible, true);
});
});
suite('ControlsController#configureMode()', function() {
test('It\'s not switchable when only one mode is available', function() {
// Fake avaialable modes
this.app.settings.mode.get
.withArgs('options')
.returns([{ key: 'picture' }]);
this.controller.configureMode();
assert.isTrue(this.view.disable.calledWith('switch'));
});
});
suite('ControlsController#onCaptureClick', function() {
setup(function() {
sinon.spy(this.controller, 'captureHighlightOn');
this.controller.onCaptureClick();
});
test('Should highlight the capture button', function() {
assert.isTrue(this.controller.captureHighlightOn.calledOnce);
});
test('Should fire a \'capture\' event on the app', function() {
assert.isTrue(this.app.emit.calledWith('capture'));
});
});
suite('ControlsController#onCameraWillChange', function() {
test('When camera is reconfiguring it suspends the mode switch', function() {
this.controller.onCameraWillChange();
assert.isTrue(this.view.suspendModeSwitch.calledWith(true));
});
});
suite('ControlsController#onCameraConfigured', function() {
test('When camera is configured it unsuspends the mode switch', function() {
this.controller.onCameraConfigured();
assert.isTrue(this.view.suspendModeSwitch.calledWith(false));
});
});
suite('ControlsController#onRecordingChange', function() {
test('When recording view\'s setCaptureLabel should be called', function() {
[true, false].forEach(function(recording) {
this.controller.onRecordingChange(recording);
assert.isTrue(this.view.setCaptureLabel.calledWith(recording));
}, this);
});
});
suite('ControlsController.onViewModeChanged()', function() {
test('It switches to the next mode setting', function() {
this.controller.onViewModeChanged();
sinon.assert.notCalled(this.view.enable);
sinon.assert.called(this.settings.mode.next);
});
});
});

View file

@ -0,0 +1,242 @@
suite('controllers/hud', function() {
/*jshint maxlen:false*/
'use strict';
suiteSetup(function(done) {
var self = this;
requirejs([
'app',
'lib/camera/camera',
'controllers/hud',
'views/hud',
'views/controls',
'views/viewfinder',
'views/notification',
'lib/settings',
'lib/setting'
], function(
App, Camera, HudController, HudView, ControlsView,
ViewfinderView, NotificationView, Settings, Setting
) {
self.HudController = HudController.HudController;
self.NotificationView = NotificationView;
self.ViewfinderView = ViewfinderView;
self.ControlsView = ControlsView;
self.Settings = Settings;
self.Setting = Setting;
self.HudView = HudView;
self.Camera = Camera;
self.App = App;
done();
});
});
setup(function() {
this.app = sinon.createStubInstance(this.App);
this.app.camera = sinon.createStubInstance(this.Camera);
this.app.l10n = { get: sinon.spy(function(value) { return value; }) };
this.app.views = {
notification: sinon.createStubInstance(this.NotificationView),
viewfinder: sinon.createStubInstance(this.ViewfinderView),
controls: sinon.createStubInstance(this.ControlsView),
hud: sinon.createStubInstance(this.HudView)
};
// Stub 'cameras' setting
this.app.settings = {};
this.app.settings.cameras = sinon.createStubInstance(this.Setting);
this.app.settings.flashModes = sinon.createStubInstance(this.Setting);
this.app.settings.settingsMenu = sinon.createStubInstance(this.Setting);
this.app.settings.mode = sinon.createStubInstance(this.Setting);
this.app.settings.cameras.get.withArgs('options').returns([]);
// For convenience
this.notification = this.app.views.notification;
this.viewfinder = this.app.views.viewfinder;
this.controls = this.app.views.controls;
this.settings = this.app.settings;
this.camera = this.app.camera;
this.view = this.app.views.hud;
// Our test instance
this.controller = new this.HudController(this.app);
});
suite('HudController()', function() {
test('Should listen to the following events', function() {
assert.ok(this.app.settings.flashModes.on.calledWith('change:selected'));
assert.ok(this.app.settings.mode.on.calledWith('change:selected'));
assert.ok(this.app.settings.cameras.on.calledWith('change:selected'));
assert.ok(this.app.on.calledWith('settings:configured'));
assert.ok(this.app.on.calledWith('ready'));
assert.ok(this.app.on.calledWith('busy'));
assert.ok(this.app.on.calledWith('localized'));
assert.ok(this.app.on.calledWith('change:recording'));
assert.ok(this.app.on.calledWith('countdown:started'));
assert.ok(this.app.on.calledWith('countdown:ended'));
assert.ok(this.app.on.calledWith('settings:opened'));
assert.ok(this.app.on.calledWith('settings:closed'));
assert.ok(this.app.on.calledWith('previewgallery:opened',
this.view.hide));
assert.ok(this.app.on.calledWith('previewgallery:closed',
this.view.show));
});
test('Should update the flash support once settings are configured', function() {
sinon.assert.calledWith(this.app.on, 'settings:configured', this.controller.updateFlashSupport);
});
test('Should set \'camera\' to \'busy\' on view when busy', function() {
assert.ok(this.view.setter.calledWith('camera', 'busy'));
assert.ok(this.app.on.calledWith('busy'));
});
test('Should set \'camera\' to \'ready\' on view when ready', function() {
assert.ok(this.view.setter.calledWith('camera', 'ready'));
assert.ok(this.app.on.calledWith('ready'));
});
test('Should set \'countdown\' to \'active\' on view when started', function() {
assert.ok(this.view.setter.calledWith('countdown', 'active'));
assert.ok(this.app.on.calledWith('countdown:started'));
});
test('Should set \'countdown\' to \'inactive\' on view when countdown ended or cleared', function() {
assert.ok(this.view.setter.calledWith('countdown', 'inactive'));
assert.ok(this.app.on.calledWith('countdown:ended'));
});
test('Should set `recording` state on hud', function() {
assert.ok(this.view.setter.calledWith('recording'));
assert.ok(this.app.on.calledWith('change:recording'));
});
test('Should enable camera button depending on support', function() {
this.settings.cameras.get
.withArgs('options')
.returns(['back']);
sinon.assert.calledWith(this.view.enable, 'camera', false);
this.settings.cameras.get
.withArgs('options')
.returns(['back', 'front']);
this.controller = new this.HudController(this.app);
sinon.assert.calledWith(this.view.enable, 'camera', true);
});
test('Should disable flash initially until support is known', function() {
sinon.assert.calledWith(this.view.disable, 'flash');
});
});
suite('HudController#localize()', function() {
setup(function() {
this.controller.view = {
setFlashModeLabel: sinon.spy(),
setCameraLabel: sinon.spy(),
setMenuLabel: sinon.spy()
};
});
test('Should cycle to the next flash setting', function() {
this.controller.localize();
assert.ok(this.controller.view.setFlashModeLabel.called);
assert.ok(this.controller.view.setCameraLabel.called);
assert.ok(this.controller.view.setMenuLabel.called);
});
});
suite('HudController#onFlashClick()', function() {
setup(function() {
this.settings.hdr = {
selected: sinon.spy()
};
});
test('Should cycle to the next flash setting', function() {
this.controller.onFlashClick();
assert.ok(this.settings.flashModes.next.calledOnce);
});
test('Should set the new value on the hud view', function() {
this.settings.flashModes.selected.withArgs('key').returns('auto');
this.controller.onFlashClick();
assert.ok(this.view.set.calledWith('flashMode', 'auto'));
});
test('Should notify', function() {
sinon.spy(this.controller, 'notify');
this.controller.onFlashClick();
assert.ok(this.controller.notify.called);
});
});
suite('HudController#notify()', function() {
setup(function() {
this.settings.hdr = {
selected: sinon.spy()
};
});
test('Should display a notification', function() {
this.controller.onFlashClick();
assert.ok(this.notification.display.called);
});
});
suite('HudController#updateFlashMode()', function() {
setup(function() {
this.settings.flashModes = {
selected: sinon.spy()
};
});
test('Should update the flash mode', function() {
this.controller.updateFlashMode();
assert.ok(this.settings.flashModes.selected.called);
});
});
suite('HudController#updateCamera()', function() {
setup(function() {
this.settings.cameras = {
selected: sinon.spy()
};
});
test('Should update the camera', function() {
this.controller.updateCamera();
assert.ok(this.settings.cameras.selected.called);
});
});
suite('HudController#onCameraClick()', function() {
setup(function() {
this.callback = this.view.on.withArgs('click:camera').args[0][1];
});
test('Should clear notifications', function() {
this.callback();
sinon.assert.called(this.notification.clear);
});
test('Should call move to next available camera', function() {
this.callback();
sinon.assert.calledOnce(this.settings.cameras.next);
});
test('Should debounce (immediate) rapid button taps', function() {
this.callback();
sinon.assert.called(this.settings.cameras.next);
this.callback();
this.callback();
this.callback();
this.callback();
sinon.assert.calledOnce(this.settings.cameras.next);
});
});
});

View file

@ -0,0 +1,100 @@
suite('controllers/indicators', function() {
/*jshint maxlen:false*/
'use strict';
suiteSetup(function(done) {
var self = this;
requirejs([
'controllers/indicators',
'views/indicators',
'lib/setting',
'app'
], function(IndicatorsController, IndicatorsView, Setting, App) {
self.IndicatorsController = IndicatorsController.IndicatorsController;
self.IndicatorsView = IndicatorsView;
self.Setting = Setting;
self.App = App;
done();
});
});
setup(function() {
this.app = sinon.createStubInstance(this.App);
this.app.settings = {
hdr: sinon.createStubInstance(this.Setting),
mode: sinon.createStubInstance(this.Setting),
countdown: sinon.createStubInstance(this.Setting),
indicators: sinon.createStubInstance(this.Setting)
};
this.app.views = {
indicators: sinon.createStubInstance(this.IndicatorsView)
};
// Shortcuts
this.view = this.app.views.indicators;
this.settings = this.app.settings;
// Return values
this.settings.hdr.selected.returns('on');
this.settings.countdown.selected.returns('on');
this.app.get.withArgs('batteryStatus').returns('healthy');
// Create our test instance
this.controller = new this.IndicatorsController(this.app);
});
suite('IndicatorsController()', function() {
test('Should bind settings change events', function() {
assert.ok(this.app.settings.countdown.on.calledWith('change:selected'));
assert.ok(this.app.settings.mode.on.calledWith('change:selected'));
assert.ok(this.app.settings.hdr.on.calledWith('change:selected'));
assert.ok(this.app.on.calledWith('change:batteryStatus'));
assert.ok(this.app.on.calledWith('settings:configured'));
assert.ok(this.app.on.calledWith('settings:opened'));
assert.ok(this.app.on.calledWith('settings:closed'));
assert.ok(this.app.on.calledWith('previewgallery:opened',
this.view.hide));
assert.ok(this.app.on.calledWith('previewgallery:closed',
this.view.show));
});
test('It sets the battery status initially', function() {
sinon.assert.calledWith(this.view.set, 'battery', 'healthy');
});
test('It sets hdr status initially', function() {
sinon.assert.calledWith(this.view.set, 'hdr', 'on');
});
test('It sets countdown status initially', function() {
sinon.assert.calledWith(this.view.set, 'countdown', 'on');
});
test('It reconfigures when the settings change', function() {
sinon.assert.calledWith(this.app.on, 'settings:configured', this.controller.configure);
});
test('It binds view setter for aria-hidden true', function() {
sinon.assert.calledWith(this.view.setter, 'ariaHidden', true);
});
test('It binds view setter for aria-hidden false', function() {
sinon.assert.calledWith(this.view.setter, 'ariaHidden', false);
});
});
suite('IndicatorsController#configure()', function() {
test('Should set the current \'hdr\' and \'countdown\' keys' +
'on the view', function() {
var view = this.app.views.indicators;
this.app.settings.indicators.get.returns(this.enabledValues);
this.controller = new this.IndicatorsController(this.app);
this.controller.configure();
assert.ok(view.set.calledWith('countdown', 'on'));
assert.ok(view.set.calledWith('hdr', 'on'));
});
});
});

View file

@ -0,0 +1,127 @@
suite('controllers/media', function() {
'use strict';
suiteSetup(function(done) {
var self = this;
requirejs([
'app',
'controllers/media',
'lib/settings',
], function(App, MediaController, Settings) {
self.MediaController =
MediaController.MediaController;
self.Settings = Settings;
self.App = App;
done();
});
});
setup(function() {
this.app = sinon.createStubInstance(this.App);
this.app.settings = sinon.createStubInstance(this.Settings);
this.app.require = sinon.stub();
this.app.activity = {};
this.app.settings.previewGallery = {
get: sinon.spy()
};
// Our test instance
this.controller = new this.MediaController(this.app);
});
suite('MediaController()', function() {
setup(function() {
this.controller.settings = {
previewGallery: {
get: sinon.spy()
}
};
this.clock = sinon.useFakeTimers();
});
test('Should listen to the following events', function() {
assert.ok(this.app.on.calledWith('storage:itemdeleted'));
assert.ok(this.app.on.calledWith('storage:changed'));
assert.ok(this.app.on.calledWith('previewgallery:deletevideo'));
assert.ok(this.app.on.calledWith('previewgallery:deletepicture'));
assert.ok(this.app.on.calledWith('newmedia'));
assert.ok(this.app.on.calledWith('hidden'));
assert.ok(this.app.on.calledWith('lazyloaded'));
});
test('Check onNewMedia callback', function() {
var item = {
blob: {},
filepath: 'root/fileName',
isVideo: true
};
this.app.activity = {
active: false
};
this.controller.items.unshift = sinon.spy();
this.controller.updateThumbnail = sinon.spy();
this.controller.onNewMedia(item);
assert.ok(this.controller.items.unshift.called);
assert.ok(this.controller.updateThumbnail.called);
});
test('Should Check Storage Changed', function() {
var item = {
blob: {},
filepath: 'root/fileName',
isVideo: true
};
var data = {
path: 'root/fileName'
};
this.app.activity = {
active: false
};
this.controller.configure = sinon.spy();
this.controller.updateThumbnail = sinon.spy();
this.controller.onNewMedia(item);
this.controller.onStorageChanged('unavailable');
assert.ok(this.controller.configure.called);
assert.ok(this.controller.updateThumbnail.called);
});
test('Should Check the Item Deleted', function() {
var item = {
blob: {},
filepath: 'root/fileName',
isVideo: true
};
var data = {
path: 'root/fileName'
};
this.app.activity = {
active: false
};
this.controller.updateThumbnail = sinon.spy();
this.controller.updatePreviewGallery = sinon.spy();
this.controller.onNewMedia(item);
this.controller.onItemDeleted(data);
sinon.assert.calledWith(this.app.emit, 'media:deleted');
});
test('Should close the preview on blur if in \'secureMode\'', function() {
this.app.inSecureMode = true;
this.controller.closePreview = sinon.spy();
this.controller.configure = sinon.spy();
this.controller.updateThumbnail = sinon.spy();
this.controller.onHidden();
assert.ok(this.controller.configure.called);
assert.ok(this.controller.updateThumbnail.called);
sinon.assert.calledWith(this.app.emit, 'media:configured');
});
});
});

View file

@ -0,0 +1,141 @@
/*jshint maxlen:false*/
'use strict';
suite('controllers/overlay', function() {
suiteSetup(function(done) {
var self = this;
requirejs([
'app',
'controllers/overlay',
'views/overlay'
], function(App, controller, Overlay) {
self.Controller = controller;
self.Overlay = Overlay;
self.App = App;
done();
});
});
setup(function() {
this.app = sinon.createStubInstance(this.App);
this.app.activity = {
pick: false,
cancel: sinon.spy()
};
this.app.require = sinon.stub();
this.app.require.callsArgWith(1, this.Overlay);
this.controller = new this.Controller(this.app);
});
suite('OverlayController()', function() {
test('Should bind to events', function() {
assert.ok(this.app.on.calledWith('storage:changed'));
assert.ok(this.app.on.calledWith('change:batteryStatus'));
assert.ok(this.app.on.calledWith('camera:requesting'));
assert.ok(this.app.on.calledWith('camera:error'));
});
});
suite('OverlayController#onStorageChanged()', function() {
setup(function() {
sinon.stub(this.controller, 'updateOverlay');
});
test('Should *not* insert a new overlay', function() {
this.controller.onStorageChanged('available');
sinon.assert.calledWith(this.controller.updateOverlay, 'storage', false, 'available');
});
test('Should create overlay whenever the value is not \'available\'', function() {
this.controller.onStorageChanged('foo');
sinon.assert.calledWith(this.controller.updateOverlay, 'storage', true, 'foo');
});
});
suite('OverlayController#onBatteryChanged()', function() {
setup(function() {
sinon.stub(this.controller, 'updateOverlay');
});
test('Should create overlay if status is shutdown', function() {
this.controller.onBatteryChanged('shutdown');
sinon.assert.calledWith(this.controller.updateOverlay, 'battery', true, 'shutdown');
});
test('Should destroy overlay if previous is shutdown', function() {
this.controller.onBatteryChanged('foo');
sinon.assert.calledWith(this.controller.updateOverlay, 'battery', false, 'foo');
});
});
suite('OverlayController#onCameraRequesting()', function() {
setup(function() {
sinon.stub(this.controller, 'updateOverlay');
});
test('Should destroy overlay', function() {
this.controller.onCameraRequesting();
sinon.assert.calledWith(this.controller.updateOverlay, 'cameraError', false);
});
});
suite('OverlayController#onCameraError()', function() {
setup(function() {
sinon.stub(this.controller, 'updateOverlay');
});
test('Should create overlay', function() {
this.controller.onCameraError('foo');
sinon.assert.calledWith(this.controller.updateOverlay, 'cameraError', true, 'foo');
});
});
suite('OverlayController#updateOverlay()', function() {
setup(function() {
this.OverlayProto = this.Overlay.prototype;
sinon.spy(this.OverlayProto, 'initialize');
sinon.stub(this.OverlayProto, 'appendTo', function() { return this; });
});
teardown(function() {
this.OverlayProto.initialize.restore();
this.OverlayProto.appendTo.restore();
});
test('Should not insert overlay when key is unknown', function() {
this.controller.updateOverlay('storage', true, 'foobar');
assert.isFalse(this.OverlayProto.appendTo.called);
});
test('Should insert overlay when key is known', function() {
this.controller.updateOverlay('storage', true, 'unavailable');
assert.isTrue(this.OverlayProto.appendTo.called);
});
test('Should not be closable if type is \'request-fail\'', function() {
this.controller.updateOverlay('cameraError', true, 'request-fail');
assert.isFalse(this.OverlayProto.initialize.args[0][0].closable);
this.app.activity.pick = true;
this.controller.updateOverlay('cameraError', true, 'request-fail');
assert.isFalse(this.OverlayProto.initialize.args[0][0].closable);
});
test('Should not be closable if activity not pending', function() {
this.controller.updateOverlay('storage', true, 'unavailable');
assert.isFalse(this.OverlayProto.initialize.args[0][0].closable);
});
test('Should be closable only if activity is pending', function() {
this.app.activity.pick = true;
this.controller.updateOverlay('storage', true, 'unavailable');
assert.isTrue(this.OverlayProto.initialize.args[0][0].closable);
});
test('Should append the overlay to the body', function() {
this.controller.updateOverlay('storage', true, 'unavailable');
assert.isTrue(this.OverlayProto.appendTo.calledWith(document.body));
});
});
});

View file

@ -0,0 +1,294 @@
suite('controllers/preview-gallery', function() {
'use strict';
suiteSetup(function(done) {
var self = this;
requirejs([
'app',
'controllers/preview-gallery',
'lib/settings',
'views/preview-gallery',
'lib/storage',
'lib/orientation'
], function(
App,PreviewGalleryController, Settings, PreviewGalleryView,
Storage, orientation) {
self.PreviewGalleryController =
PreviewGalleryController.PreviewGalleryController;
self.Settings = Settings;
self.PreviewGalleryView = PreviewGalleryView;
self.Storage = Storage;
self.App = App;
self.orientation = orientation;
done();
});
});
setup(function() {
this.app = sinon.createStubInstance(this.App);
this.app.settings = sinon.createStubInstance(this.Settings);
this.app.require = sinon.stub();
this.app.activity = {};
// Fake dialog calls the
// 'delete' callback sync.
this.app.dialog = {
show: function(title, msg, cancel, del) { del.callback(); },
hide: sinon.stub()
};
// Our test instance
this.previewGalleryController = new this.PreviewGalleryController(this.app);
// For convenience
this.camera = this.app.camera;
this.previewGallery = this.previewGalleryController.view;
this.controller = this.previewGalleryController;
this.storage = this.app.storage;
this.dialog = this.app.dialog;
});
suite('PreviewGalleryController()', function() {
setup(function() {
var MozActivity = function() {};
if (!window.MozActivity) {
window.MozActivity = MozActivity;
}
sinon.stub(window, 'MozActivity');
this.previewGalleryController.storage = {
deleteImage: sinon.spy(),
deleteVideo: sinon.spy(),
on: sinon.spy()
};
this.previewGalleryController.settings = {
activity: {
get: sinon.spy()
},
};
this.previewGalleryController.resizeImageAndSave =
function(options, done) {
done(options.blob);
};
this.app.require.callsArgWith(1,
this.previewGalleryController.resizeImageAndSave);
this.clock = sinon.useFakeTimers();
});
teardown(function() {
window.MozActivity.restore();
});
test('Should listen to the following events', function() {
this.previewGalleryController.bindEvents();
assert.ok(this.app.on.calledWith('preview', this.controller.openPreview));
assert.ok(this.app.on.calledWith('hidden', this.controller.closePreview));
assert.ok(this.app.on.calledWith('media:configured', this.controller.updatePreviewGallery));
assert.ok(this.app.on.calledWith('media:deleted', this.controller.updatePreviewGallery));
});
test('Should open the gallery app when gallery button is pressed',
function() {
this.previewGalleryController.onGalleryButtonClick();
var arg = window.MozActivity.args[0][0];
assert.ok(arg.name === 'browse');
assert.ok(arg.data.type === 'photos');
});
test('Should shareCurrentItem whose type is image', function() {
var item = {
blob: new Blob(['empty-image'], {'type': 'image/jpeg'}),
filepath: 'root/folder1/folder2/fileName',
isImage: true
};
this.previewGalleryController.items = [item];
this.previewGalleryController.currentItemIndex = 0;
this.previewGalleryController.shareCurrentItem();
assert.ok(this.app.emit.calledWith('busy', 'resizingImage'));
// Get first argument, of first call
var arg = window.MozActivity.args[0][0];
assert.ok(arg.name === 'share');
assert.ok(arg.data.type === 'image/*');
assert.ok(arg.data.number === 1);
assert.ok(arg.data.blobs[0] === item.blob);
assert.ok(arg.data.filenames[0] === 'fileName');
assert.ok(arg.data.filepaths[0] === item.filepath);
});
test('Should shareCurrentItem whose type is video', function() {
var item = {
blob: {},
filepath: 'root/folder1/folder2/fileName',
isVideo: true
};
this.previewGalleryController.items = [item];
this.previewGalleryController.currentItemIndex = 0;
this.previewGalleryController.shareCurrentItem();
// Get first argument, of first call
var arg = window.MozActivity.args[0][0];
assert.ok(arg.name === 'share');
assert.ok(arg.data.type === 'video/*');
assert.ok(arg.data.number === 1);
assert.ok(arg.data.blobs[0] === item.blob);
assert.ok(arg.data.filenames[0] === 'fileName');
assert.ok(arg.data.filepaths[0] === item.filepath);
});
suite('PreviewGalleryController#deleteCurrentItem()', function() {
setup(function() {
});
test('Should deleteCurrentItem which is image', function() {
var item = {
blob: {},
filepath: 'root/fileName',
isVideo: false
};
this.previewGalleryController.items = [item];
this.previewGalleryController.currentItemIndex = 0;
this.previewGalleryController.deleteCurrentItem();
assert.ok(this.app.emit.calledWith('previewgallery:deletepicture', 'root/fileName'));
});
test('Should deleteCurrentItem which is video', function() {
var item = {
blob: {},
filepath: 'root/fileName',
isVideo: true
};
this.previewGalleryController.items = [item];
this.previewGalleryController.currentItemIndex = 0;
this.previewGalleryController.deleteCurrentItem();
assert.ok(this.app.emit.calledWith('previewgallery:deletevideo', 'root/fileName'));
});
});
test('Should go to next image on handleSwipe(\'left\')', function() {
this.previewGalleryController.items = [1,2,3];
this.previewGalleryController.currentItemIndex = 1;
this.previewGalleryController.previewItem = sinon.spy();
this.previewGalleryController.handleSwipe('left');
assert.ok(this.previewGalleryController.previewItem.called);
assert.equal(this.previewGalleryController.currentItemIndex, 2);
});
test('Should go to previous image on handleSwipe(\'right\')', function() {
this.previewGalleryController.items = [1,2,3];
this.previewGalleryController.currentItemIndex = 1;
this.previewGalleryController.previewItem = sinon.spy();
this.previewGalleryController.handleSwipe('right');
assert.ok(this.previewGalleryController.previewItem.called);
assert.equal(this.previewGalleryController.currentItemIndex, 0);
});
test('Should not go to next image if there is not one', function() {
this.previewGalleryController.items = [1,2];
this.previewGalleryController.currentItemIndex = 1;
this.previewGalleryController.previewItem = sinon.spy();
this.previewGalleryController.handleSwipe('left');
assert.isFalse(this.previewGalleryController.previewItem.called);
assert.equal(this.previewGalleryController.currentItemIndex, 1);
});
test('Should not go to previous image if there is not one', function() {
this.previewGalleryController.items = [1,2];
this.previewGalleryController.currentItemIndex = 0;
this.previewGalleryController.previewItem = sinon.spy();
this.previewGalleryController.handleSwipe('right');
assert.isFalse(this.previewGalleryController.previewItem.called);
assert.equal(this.previewGalleryController.currentItemIndex, 0);
});
});
suite('PreviewGalleryController#openPreview()', function() {
setup(function() {
this.app.require.callsArgWith(1, this.PreviewGalleryView, sinon.stub());
sinon.stub(this.controller, 'previewItem');
this.controller.settings = {
activity: {
get: sinon.spy()
},
previewGallery: {
get: sinon.spy()
}
};
});
test('Should call previewItem', function() {
this.controller.openPreview();
assert.isTrue(this.controller.previewItem.called);
});
// XXX: this is really a view test, but we don't have tests for the view yet
test('Should lock and unlock orientation when opening and closing view',
function() {
this.orientation.unlock = sinon.spy();
this.orientation.lock = sinon.spy();
this.previewGalleryController.previewItem = sinon.spy();
this.previewGalleryController.openPreview();
assert.isTrue(this.orientation.unlock.called);
assert.isTrue(this.previewGalleryController.previewItem.called);
assert.isFalse(this.orientation.lock.called);
this.previewGalleryController.closePreview();
assert.isTrue(this.orientation.lock.called);
});
// XXX: this is really a view test, but we don't have tests for the view yet
test('Should add and remove a resize handler when opening and closing view',
function() {
var add = sinon.spy(window, 'addEventListener').withArgs('resize');
var remove =
sinon.spy(window, 'removeEventListener').withArgs('resize');
this.previewGalleryController.previewItem = sinon.spy();
this.previewGalleryController.openPreview();
assert.ok(add.calledTwice); // twice because VideoPlayer does too
this.previewGalleryController.closePreview();
assert.equal(remove.callCount, 1);
});
});
suite('PreviewGalleryController#closePreview()', function() {
setup(function() {
this.controller.view = new this.PreviewGalleryView();
this.previewGalleryView = this.controller.view;
this.controller.view.close = sinon.spy();
this.controller.view.destroy = sinon.spy();
this.controller.closePreview();
});
test('Should set `previewGalleryOpen` to `false` on app', function() {
assert.isTrue(this.previewGalleryView.close.called);
assert.isTrue(this.previewGalleryView.destroy.called);
assert.isTrue(this.controller.view === null);
});
});
suite('PreviewGalleryController#onPreviewOptionClick()', function() {
setup(function() {
this.controller.view = new this.PreviewGalleryView();
this.controller.view.showOptionsMenu = sinon.spy();
this.controller.onOptionsClick();
});
test('Should open previewOption on onPreviewOptionClick', function() {
assert.isTrue(this.controller.view.showOptionsMenu.called);
});
});
});

View file

@ -0,0 +1,84 @@
'use strict';
suite('controllers/recording-timer', function() {
suiteSetup(function(done) {
var self = this;
requirejs([
'app',
'controllers/recording-timer',
'views/recording-timer'
], function(App, RecordingTimerController, RecordingTimerView) {
self.RecordingTimerController = RecordingTimerController.RecordingTimerController;
self.RecordingTimerView = RecordingTimerView;
self.App = App;
done();
});
});
setup(function() {
this.app = sinon.createStubInstance(this.App);
this.app.view = sinon.createStubInstance(this.RecordingTimerView);
this.app.el = {};
// Alias
this.view = this.app.view;
// Create out test instance
this.controller = new this.RecordingTimerController(this.app);
});
suite('RecordingTimerController()', function() {
test('Should respond to recording change', function() {
assert.isTrue(this.app.on.calledWith('change:recording'));
});
test('Should put the view in the DOM', function() {
assert.isTrue(this.view.appendTo.calledWith(this.app.el));
});
test('Should update the view value when the recording time changes', function() {
assert.isTrue(this.app.on.calledWith('camera:recorderTimeUpdate', this.view.value));
});
});
suite('RecordingTimerController#onRecordingChange()', function() {
test('Should hide the view if not recording', function() {
this.controller.onRecordingChange(false);
assert.isTrue(this.view.hide.called);
});
test('Should show and reset the view if recording', function() {
this.controller.onRecordingChange(true);
assert.isTrue(this.view.show.called);
assert.isTrue(this.view.value.calledWith(0));
});
});
suite('Accessibility Tests', function() {
var app,
view,
controller;
setup(function() {
app = sinon.createStubInstance(this.App);
app.view = new this.RecordingTimerView();
view = app.view;
controller = new this.RecordingTimerController(app);
});
test('Explicitly test for RecordingTimer visibility', function() {
// App is in its initialized state
assert.isFalse(view.el.classList.contains('visible'));
// Fire start recording event
controller.onRecordingChange(true);
assert.isTrue(view.el.classList.contains('visible'));
// Fire stop recording event
controller.onRecordingChange(false);
assert.isFalse(view.el.classList.contains('visible'));
});
});
});

View file

@ -0,0 +1,504 @@
suite('controllers/settings', function() {
/*jshint maxlen:false*/
'use strict';
suiteSetup(function(done) {
var self = this;
requirejs([
'controllers/settings',
'lib/settings',
'lib/setting',
'app',
'views/notification',
'views/settings'
], function(
SettingsController, Settings, Setting,
App, NotificationViews, SettingsView) {
self.SettingsController = SettingsController.SettingsController;
self.NotificationViews = NotificationViews;
self.SettingsView = SettingsView;
self.Settings = Settings;
self.Setting = Setting;
self.App = App;
done();
});
});
setup(function() {
var self = this;
this.app = sinon.createStubInstance(this.App);
this.app.require = sinon.stub();
this.app.require.callsArgWith(1, this.SettingsView);
// Settings
this.app.el = {};
this.app.activity = { active: false };
this.app.formatPictureSizes = sinon.stub();
this.app.formatRecorderProfiles = sinon.stub();
this.app.settings = sinon.createStubInstance(this.Settings);
this.app.settings.mode = sinon.createStubInstance(this.Setting);
this.app.settings.pictureSizes = sinon.createStubInstance(this.Setting);
this.app.settings.pictureSizesFront = sinon.createStubInstance(this.Setting);
this.app.settings.pictureSizesBack = sinon.createStubInstance(this.Setting);
this.app.settings.recorderProfiles = sinon.createStubInstance(this.Setting);
this.app.settings.recorderProfilesFront = sinon.createStubInstance(this.Setting);
this.app.settings.recorderProfilesBack = sinon.createStubInstance(this.Setting);
this.app.settings.flashModesPicture = sinon.createStubInstance(this.Setting);
this.app.settings.flashModesVideo = sinon.createStubInstance(this.Setting);
this.app.settings.hdr = sinon.createStubInstance(this.Setting);
this.app.views = {
notification: sinon.createStubInstance(this.NotificationViews)
};
// SettingsView mock
this.app.SettingsView = sinon.spy(function() {
var view = sinon.createStubInstance(self.SettingsView);
view.render.returns(view);
view.appendTo.returns(view);
view.on.returns(view);
view.fadeOut.callsArg(0);
return view;
});
document.l10n = {
formatValue: key => {
return {
then(fn) { fn('LOCALIZED'); }
};
}
};
// Shortcut
this.notification = this.app.views.notification;
this.settings = this.app.settings;
// Create test instance
this.controller = new this.SettingsController(this.app);
});
teardown(function() {
delete document.l10n;
});
suite('SettingsController()', function() {
test('Should setup aliases for `recorderProfiles`, `pictureSizes` and `flashModes`', function() {
sinon.assert.calledThrice(this.settings.alias);
});
test('Should toggle the settings menu on \'settings:toggle\'', function() {
assert.isTrue(this.app.on.calledWith('settings:toggle', this.controller.toggleSettings));
});
test('Should update the settings when the camera hardware changes', function() {
assert.isTrue(this.app.on.calledWith('camera:newcamera', this.controller.onNewCamera));
});
test('Should format pictreSize titles when the app is localized', function() {
assert.isTrue(this.app.on.calledWith('localized', this.controller.formatPictureSizeTitles));
});
test('Should listen to \'camera:newcamera\'', function() {
assert.isTrue(this.app.on.calledWith('camera:newcamera', this.controller.onNewCamera));
});
});
suite('SettingsController#configurePictureSizes()', function() {
setup(function() {
this.pictureSizes = [];
this.exclude = ['480x320'];
this.app.formatPictureSizes.returns('formatted');
this.settings.pictureSizes.get
.withArgs('exclude')
.returns(this.exclude);
this.settings.pictureSizes.current = sinon.stub()
.returns(this.settings.pictureSizesBack);
document.l10n = {
formatValue: function(id, args) {
return Promise.resolve();
}
};
this.settings.pictureSizes.get
.withArgs('options')
.returns([]);
// Run it
this.controller.configurePictureSizes({
pictureSize: { height: 1536, width: 2048 },
capabilities: {
pictureSizes: this.pictureSizes
}
});
});
test('Should format the raw pictureSizes list', function() {
assert.isTrue(this.app.formatPictureSizes.calledWith(this.pictureSizes));
});
test('Should reset pictureSize options with formatted list', function() {
assert.isTrue(this.settings.pictureSizes.resetOptions.calledWith('formatted'));
});
test('Should pass the raw recorderProfiles into formatRecorderProfiles', function() {
var arg1 = this.app.formatPictureSizes.args[0][0];
assert.equal(arg1, this.pictureSizes);
});
test('Should pass the `exclude`', function() {
var options = this.app.formatPictureSizes.args[0][1];
assert.equal(options.exclude, this.exclude);
});
});
suite('SettingsController#configureRecorderProfiles()', function() {
setup(function() {
this.recorderProfiles = {};
this.formatted = ['1080p', '720p', '480p'];
this.exclude = ['1080p'];
this.newCamera = {
recorderProfile: '720p',
pictureSize: { height: 1536, width: 2048 },
capabilities: {
recorderProfiles: this.recorderProfiles
}
};
this.app.formatRecorderProfiles
.returns(this.formatted);
this.settings.recorderProfiles.get
.withArgs('exclude')
.returns(this.exclude);
this.settings.recorderProfiles.current = sinon.stub()
.returns(this.settings.recorderProfilesBack);
// Run it
this.controller.configureRecorderProfiles(this.newCamera);
});
test('Should format the raw recorderProfiles list', function() {
assert.isTrue(this.app.formatRecorderProfiles.calledWith(this.recorderProfiles));
});
test('Should reset recorderProfiles options with formatted list', function() {
var resetOptions = this.settings.recorderProfiles.resetOptions;
assert.isTrue(resetOptions.calledWith(this.formatted));
});
test('Should pass the raw recorderProfiles into formatRecorderProfiles', function() {
var arg1 = this.app.formatRecorderProfiles.args[0][0];
assert.equal(arg1, this.recorderProfiles);
});
test('Should pass `exclude` option to formatRecorderProfiles', function() {
var options = this.app.formatRecorderProfiles.args[0][1];
assert.equal(options.exclude, this.exclude);
});
test('Should pick the last formatted recorderProfile if `maxFileSize` is defined', function() {
this.settings.recorderProfiles.resetOptions.reset();
this.settings.recorderProfiles.get
.withArgs('maxFileSizeBytes')
.returns(100);
this.controller.configureRecorderProfiles(this.newCamera);
var arg = this.settings.recorderProfiles.resetOptions.args[0][0];
assert.deepEqual(arg, ['480p']);
});
});
suite('SettingsController#onOptionTap()', function() {
setup(function() {
this.setting = sinon.createStubInstance(this.Setting);
sinon.stub(this.controller, 'closeSettings').callsArg(0);
sinon.stub(this.controller, 'notify');
});
test('Should select the given key on the given option', function() {
this.controller.onOptionTap('the-key', this.setting);
assert.isTrue(this.setting.select.calledWith('the-key'));
});
test('Should close the settings menu', function() {
this.controller.onOptionTap('the-key', this.setting);
assert.isTrue(this.controller.closeSettings.called);
});
test('It shows a notification after the settings have closed', function() {
this.controller.onOptionTap('the-key', this.setting);
assert.isTrue(this.controller.notify.calledAfter(this.controller.closeSettings));
});
});
suite('SettingsController#notify()', function() {
setup(function() {
this.setting = sinon.createStubInstance(this.Setting);
this.setting.selected.withArgs('title').returns('l10n-key');
});
test('Should display a notification', function() {
this.controller.notify(this.setting);
assert.isTrue(this.notification.display.called);
});
test('Should not display a notification if flagged `false`', function() {
this.setting.get.withArgs('notifications').returns(false);
this.controller.notify(this.setting);
sinon.assert.notCalled(this.notification.display);
});
});
suite('SettingsController#validMenuItem()', function() {
test('Should return `true` if supported', function() {
var output;
this.app.settings.hdr.supported.returns(true);
output = this.controller.validMenuItem({ key: 'hdr' });
assert.isTrue(output);
this.app.settings.hdr.supported.returns(false);
output = this.controller.validMenuItem({ key: 'hdr' });
assert.isFalse(output);
});
test('Should cope with undefined settings', function() {
assert.isFalse(this.controller.validMenuItem({ key: 'fake' }));
});
});
suite('SettingsController#onNewCamera()', function() {
setup(function() {
this.newCamera = {
recorderProfile: '720p',
pictureSize: { height: 1536, width: 2048 },
capabilities: {
hdr: ['on', 'off'],
pictureSizes: [],
recorderProfiles: [],
flashModes: []
}
};
this.settings.pictureSizes.current = sinon.stub()
.returns(this.settings.pictureSizesBack);
this.settings.recorderProfiles.current = sinon.stub()
.returns(this.settings.recorderProfilesBack);
this.app.settings.pictureSizes.get.returns([]);
this.app.formatRecorderProfiles
.returns(['1080p', '720p', '480p' ]);
});
test('Should filter both \'picture\' and \'video\' flashModes', function() {
var picture = this.settings.flashModesPicture;
var video = this.settings.flashModesVideo;
var capabilities = this.newCamera.capabilities;
this.controller.onNewCamera(this.newCamera);
assert.ok(picture.filterOptions.calledWith(capabilities.flashModes));
assert.ok(video.filterOptions.calledWith(capabilities.flashModes));
});
test('Should filter hdr', function() {
var capabilities = this.newCamera.capabilities;
this.controller.onNewCamera(this.newCamera);
assert.ok(this.settings.hdr.filterOptions.calledWith(capabilities.hdr));
});
test('Should reset pictureSizes and recorderProfiles options', function() {
var recorderProfiles = this.settings.recorderProfiles;
var pictureSizes = this.settings.pictureSizes;
this.controller.onNewCamera(this.newCamera);
assert.ok(recorderProfiles.resetOptions.called);
assert.ok(pictureSizes.resetOptions.called);
});
});
suite('SettingsController#toggleSettings()', function() {
setup(function() {
sinon.stub(this.controller, 'closeSettings');
sinon.stub(this.controller, 'openSettings');
});
test('Should close the settings if a `view` is present', function() {
this.controller.view = 'defined';
this.controller.toggleSettings();
assert.isTrue(this.controller.closeSettings.called);
});
test('Should open the settings if a `view` is not present', function() {
this.controller.view = undefined;
this.controller.toggleSettings();
assert.isTrue(this.controller.openSettings.called);
});
});
suite('SettingsController#openSettings()', function() {
setup(function() {
this.menuItems = [];
sinon.stub(this.controller, 'menuItems').returns(this.menuItems);
// Call it
this.controller.openSettings();
});
test('Should create a new SettingsView passing menu items', function() {
var options = this.app.SettingsView.args[0][0];
assert.isTrue(this.app.SettingsView.called);
assert.equal(options.items, this.menuItems);
});
test('Should render and appendTo app.el', function() {
assert.isTrue(this.controller.view.render.called);
assert.isTrue(this.controller.view.appendTo.calledWith(this.app.el));
});
test('Should bind to `click:close` and `click:option` events', function() {
assert.isTrue(this.controller.view.on.calledWith('click:close'));
assert.isTrue(this.controller.view.on.calledWith('click:option'));
});
test('Should emit a `settings:open` event', function() {
assert.isTrue(this.app.emit.calledWith('settings:opened'));
});
test('Should not do anything if a view already exists', function() {
this.app.SettingsView.reset();
this.controller.view = 'defined';
this.controller.openSettings();
assert.isFalse(this.app.SettingsView.called);
});
});
suite('SettingsController#closeSettings()', function() {
setup(function() {
// Open settings first so we have a view
sinon.stub(this.controller, 'menuItems').returns(this.menuItems);
this.controller.openSettings();
this.view = this.controller.view;
});
test('Should not do anything if no view exists', function() {
this.controller.view = undefined;
this.controller.closeSettings();
assert.isFalse(this.app.emit.calledWith('settings:closed'));
});
test('Should destroy the view', function() {
this.controller.closeSettings();
assert.isTrue(this.view.destroy.called);
});
test('Should null out `view`', function() {
this.controller.closeSettings();
assert.equal(this.controller.view, null);
});
});
suite('SettingsController#formatPictureSizeTitles()', function() {
setup(function(done) {
this.options = [
{
key: '400x300',
data: {
mp: 0,
width: 400,
height: 300,
aspect: '4:3'
}
},
{
key: '1600x900',
data: {
mp: 1,
width: 1600,
height: 900,
aspect: '16:9'
}
},
{
key: '3200x1800',
data: {
mp: 6,
width: 3200,
height: 1800,
aspect: '16:9'
}
}
];
this.settings.pictureSizes.get
.withArgs('options')
.returns(this.options);
document.l10n = {
formatValue: function(id, args) {
return Promise.resolve('MP');
}
};
// Call the test subject
this.controller.formatPictureSizeTitles().then(() => {
done();
});
});
test('Should include MP value for > 1MP', function() {
assert.isTrue(this.options[2].title.indexOf('6MP') > -1, this.options[2].title);
assert.isFalse(this.options[0].title.indexOf('MP') > -1, this.options[0].title);
});
test('Should include the resolution', function() {
assert.isTrue(this.options[0].title.indexOf('400x300') > -1);
});
test('Should use localized \'MP\' string', function() {
document.l10n = {
formatValue: function(id, args) {
return Promise.resolve('MP');
}
};
this.controller.formatPictureSizeTitles();
assert.isTrue(this.options[2].title.indexOf('MP') > -1, this.options[2].title);
});
});
suite('SettingsController#onPickActivity()', function() {
setup(function() {
this.onPickActivity = this.app.on.withArgs('activity:pick').args[0][1];
});
test('Should not save settings changes when in \'pick\' activity', function() {
this.onPickActivity({});
sinon.assert.called(this.settings.dontSave);
});
test('Should set maxPixelSize on pictureSizes settings', function() {
this.onPickActivity({ maxPixelSize: 100 });
sinon.assert.calledWith(this.settings.pictureSizesFront.set, 'maxPixelSize', 100);
sinon.assert.calledWith(this.settings.pictureSizesBack.set, 'maxPixelSize', 100);
});
test('Should set maxPixelSize on pictureSizes settings', function() {
this.onPickActivity({ maxFileSizeBytes: 100 });
sinon.assert.calledWith(this.settings.recorderProfilesFront.set, 'maxFileSizeBytes', 100);
sinon.assert.calledWith(this.settings.recorderProfilesBack.set, 'maxFileSizeBytes', 100);
});
});
});

View file

@ -0,0 +1,164 @@
suite('controllers/storage', function() {
'use strict';
suiteSetup(function(done) {
var self = this;
requirejs([
'controllers/storage',
'lib/setting',
'lib/storage',
'app'
], function(StorageController, Setting, Storage, App) {
self.StorageController = StorageController.StorageController;
self.Setting = Setting;
self.Storage = Storage;
self.App = App;
done();
});
});
setup(function() {
this.app = sinon.createStubInstance(this.App);
this.app.storage = sinon.createStubInstance(this.Storage);
this.app.storage.createVideoFilepath = 'test';
this.app.camera = {};
this.app.settings = {
pictureSizes: sinon.createStubInstance(this.Setting)
};
// Aliases
this.camera = this.app.camera;
this.settings = this.app.settings;
this.storage = this.app.storage;
this.settings.pictureSizes.selected
.withArgs('data')
.returns({ width: 400, height: 300 });
// Create a test instance
this.controller = new this.StorageController(this.app);
});
suite('StorageController()', function() {
test('Should set the camera `camera.createVideoFilepath`', function() {
assert.equal(this.camera.createVideoFilepath, 'test');
});
test('Should update maxFileSize when picture size changes', function() {
var on = this.settings.pictureSizes.on;
assert.isTrue(on.calledWith('change:selected', this.controller.updateMaxFileSize));
});
test('Should update maxFileSize when settings are configured', function() {
assert.isTrue(this.app.on.calledWith('settings:configured', this.controller.updateMaxFileSize));
});
test('Should delete a picture from storage when one is deleted from the preview gallery', function() {
assert.isTrue(this.app.on.calledWith('previewgallery:deletepicture', this.storage.deletePicture));
});
test('Should delete a video from storage when one is deleted from the preview gallery', function() {
assert.isTrue(this.app.on.calledWith('previewgallery:deletevideo', this.storage.deleteVideo));
});
test('Should media when the camera generates it', function() {
assert.isTrue(this.app.on.calledWith('camera:newimage', this.controller.storePicture));
assert.isTrue(this.app.on.calledWith('camera:newvideo', this.controller.storeVideo));
});
test('Should check storage when the app becomes visible', function() {
assert.isTrue(this.app.on.calledWith('visible', this.storage.check));
});
test('Should set the maxFileSize initially', function() {
assert.isTrue(this.storage.setMaxFileSize.calledOnce);
});
test('Should run an intitial storage check', function() {
this.storage.setMaxFileSize.restore();
this.controller = new this.StorageController(this.app);
assert.isTrue(this.storage.check.calledOnce);
});
});
suite('StorageController#storePicture()', function() {
setup(function() {
this.storage.addPicture.callsArgWith(1, null, '<filepath>', '<abspath>', '<file-blob>');
this.picture = { blob: '<memory-blob>' };
this.controller.storePicture(this.picture);
});
test('Should store the picture', function() {
assert.isTrue(this.storage.addPicture.calledWith('<memory-blob>'));
});
test('Should emit a `newmedia` app event', function() {
assert.isTrue(this.app.emit.calledWith('newmedia', this.picture));
});
test('Should switch the <memory-blob> with the <file-blob>', function() {
assert.equal(this.picture.blob, '<file-blob>');
});
test('Should store the <filepath>', function() {
assert.equal(this.picture.filepath, '<filepath>');
});
});
suite('StorageController#storeVideo()', function() {
setup(function() {
this.storage.addPicture.callsArgWith(2, null, '<filepath>', '<abspath>', '<file-blob>');
this.video = {
blob: '<file-blob>',
filepath: 'path/to/video.3gp',
poster: { blob: '<poster-memory-blob>' }
};
this.controller.storeVideo(this.video);
});
test('Should store the picture', function() {
assert.isTrue(this.storage.addPicture.calledWith('<poster-memory-blob>'));
});
test('Should store the poster image', function() {
var blob = this.storage.addPicture.args[0][0];
var options = this.storage.addPicture.args[0][1];
assert.equal(blob, '<poster-memory-blob>');
assert.equal(options.filepath, 'path/to/video.jpg');
});
test('Should emit a `newmedia` app event', function() {
assert.isTrue(this.app.emit.calledWith('newmedia', this.video));
});
test('Should switch the <poster-memory-blob> with the <file-blob>', function() {
assert.equal(this.video.poster.blob, '<file-blob>');
})
test('Should flag it as `isVideo`', function() {
assert.equal(this.video.isVideo, true);
});
});
suite('StorageController#updateMaxFileSize()', function() {
setup(function() {
// Reset calls in constructor
this.storage.setMaxFileSize.reset();
});
test('Should call `storage.setMaxFileSize` with picture file size estimate', function() {
this.settings.pictureSizes.selected.withArgs('data').returns({ width: 400, height: 300 });
this.controller.updateMaxFileSize();
var expected = (400 * 300 / 2) + 25000;
var bytes = this.storage.setMaxFileSize.args[0][0];
assert.equal(this.storage.setMaxFileSize.callCount, 1);
assert.equal(bytes, expected);
});
});
});

View file

@ -0,0 +1,360 @@
'use strict';
suite('controllers/viewfinder', function() {
suiteSetup(function(done) {
var self = this;
requirejs([
'app',
'lib/camera/camera',
'controllers/viewfinder',
'views/viewfinder',
'views/focus',
'views/faces',
'lib/settings',
'lib/setting'
], function(
App, Camera, ViewfinderController, ViewfinderView,
FocusView, FacesView, Settings, Setting) {
self.ViewfinderController = ViewfinderController.ViewfinderController;
self.ViewfinderView = ViewfinderView;
self.FocusView = FocusView;
self.FacesView = FacesView;
self.Settings = Settings;
self.Setting = Setting;
self.Camera = Camera;
self.App = App;
done();
});
});
setup(function() {
this.sandbox = sinon.sandbox.create();
// App
this.app = sinon.createStubInstance(this.App);
this.app.camera = sinon.createStubInstance(this.Camera);
this.app.activity = {};
this.app.settings = sinon.createStubInstance(this.Settings);
this.app.settings.grid = sinon.createStubInstance(this.Setting);
this.app.views = {
viewfinder: sinon.createStubInstance(this.ViewfinderView),
focus: sinon.createStubInstance(this.FocusView),
faces: sinon.createStubInstance(this.FacesView)
};
this.app.Pinch = sinon.stub();
this.app.Pinch.prototype.on = sinon.stub();
// Fake elements
this.app.views.viewfinder.els = { video: {} };
// Settings
this.app.settings = sinon.createStubInstance(this.Settings);
this.app.settings.grid = sinon.createStubInstance(this.Setting);
this.app.settings.viewfinder = sinon.createStubInstance(this.Setting);
this.app.settings.cameras = sinon.createStubInstance(this.Setting);
this.app.settings.zoom = sinon.createStubInstance(this.Setting);
// Default return values
this.app.settings.viewfinder.get.withArgs('scaleType').returns('fill');
this.app.settings.grid.selected.withArgs('key').returns('off');
// Test instance
this.controller = new this.ViewfinderController(this.app);
// Shortcuts
this.viewfinder = this.controller.views.viewfinder;
this.focusRing = this.controller.views.focus;
this.faces = this.controller.views.faces;
this.focus = this.controller.views.focus;
this.settings = this.app.settings;
this.camera = this.app.camera;
});
teardown(function() {
this.sandbox.restore();
});
suite('ViewfinderController()', function() {
test('Should stop the stream when the PreviewGallery is opened', function() {
assert.isTrue(this.app.on.calledWith('previewgallery:opened',
this.controller.onGalleryOpened));
});
test('Should start the stream when the PreviewGallery is closed', function() {
assert.isTrue(this.app.on.calledWith('previewgallery:closed',
this.controller.onGalleryClosed));
});
test('Should stop the stream preview is false', function() {
assert.isTrue(this.app.on.calledWith('camera:previewactive', this.controller.onPreviewActive));
});
test('Should hide the grid when the settings menu opened', function() {
assert.isTrue(this.app.on.calledWith('settings:opened',
this.controller.onSettingsOpened));
});
test('Should show the grid again when the settings menu is closed', function() {
assert.isTrue(this.app.on.calledWith('settings:closed',
this.controller.onSettingsClosed));
});
test('Should flash viewfinder shutter when camera shutter fires', function() {
assert.isTrue(this.app.on.calledWith('camera:shutter', this.viewfinder.shutter));
});
test('Should respond to `zoomchanged` event', function() {
assert.isTrue(this.camera.on.calledWith('zoomchanged'));
});
test('Should should set the foucsRing state when focus changes', function() {
assert.isTrue(this.app.on.calledWith('camera:focusstatechanged', this.focusRing.setFocusState));
});
test('Should set the scaleType on the view', function() {
assert.equal(this.viewfinder.scaleType, 'fill');
});
test('Should set grid on/off', function() {
assert.isTrue(this.viewfinder.set.calledWith('grid', 'off'));
this.app.settings.grid.selected.withArgs('key').returns('on');
this.viewfinder.set.reset();
this.controller = new this.ViewfinderController(this.app);
assert.isTrue(this.viewfinder.set.calledWith('grid', 'on'));
});
});
suite('ViewfinderController#createViews()', function() {
setup(function() {
this.controller.createViews();
});
test('It appends views to the viewfinder views', function() {
assert.isTrue(this.focus.appendTo.calledWith(this.viewfinder.el));
assert.isTrue(this.faces.appendTo.calledWith(this.viewfinder.el));
assert.isTrue(this.viewfinder.appendTo.calledWith(this.app.el));
});
});
suite('ViewfinderController#show()', function() {
setup(function() {
this.sandbox.stub(window, 'clearTimeout');
this.sandbox.stub(window, 'setTimeout').returns('<timeout-id>');
this.app.criticalPathDone = true;
});
test('It fades the viewfinder in straight away if the critical path is incomplete', function() {
this.app.criticalPathDone = false;
this.controller.show();
sinon.assert.called(this.viewfinder.fadeIn);
sinon.assert.notCalled(window.setTimeout);
});
test('It fades the viewfinder in after 280ms timeout to avoid flicker', function() {
this.controller.show();
sinon.assert.calledWith(window.setTimeout, this.viewfinder.fadeIn, 280);
});
test('It clears any existing timeouts to avoid multiple scheduled timeouts', function() {
this.controller.show();
this.controller.show();
sinon.assert.calledWith(window.clearTimeout, '<timeout-id>');
});
});
suite('ViewfinderController#hide()', function() {
setup(function() {
this.sandbox.stub(window, 'setTimeout').returns('<timeout-id>');
this.sandbox.stub(window, 'clearTimeout');
});
test('It fades out the viewfinder', function() {
this.controller.hide();
sinon.assert.called(this.viewfinder.fadeOut);
});
test('It clears any fadeTimeout hanging around', function() {
this.controller.show();
this.controller.hide();
sinon.assert.called(window.clearTimeout, '<timeout-id>');
});
});
suite('ViewfinderController#loadStream()', function() {
test('Should load preview stream into viewfinder video element', function() {
var video = this.viewfinder.els.video;
this.controller.loadStream();
assert.isTrue(this.camera.loadStreamInto.calledWith(video));
});
});
suite('ViewfinderController#configurePreview()', function() {
setup(function() {
this.settings.cameras.selected.withArgs('key').returns('back');
this.camera.getSensorAngle.returns(90);
this.camera.previewSize.returns({ width: 400, height: 300 });
});
test('Should pass current previewSize', function() {
this.controller.configurePreview();
var previewSize = this.viewfinder.updatePreview.args[0][0];
assert.deepEqual(previewSize, { width: 400, height: 300 });
});
test('Should pass the sensor angle', function() {
this.controller.configurePreview();
var arg = this.viewfinder.updatePreview.args[0][1];
assert.equal(arg, 90);
});
test('Should indicate if selected camera is front', function() {
var arg;
// Back
this.controller.configurePreview();
arg = this.viewfinder.updatePreview.args[0][2];
assert.equal(arg, false);
this.viewfinder.updatePreview.reset();
// Front
this.settings.cameras.selected.withArgs('key').returns('front');
this.controller.configurePreview();
arg = this.viewfinder.updatePreview.args[0][2];
assert.equal(arg, true);
});
});
suite('ViewfinderController#onZoomConfigured()', function() {
setup(function() {
this.camera.isZoomSupported.returns(true);
this.settings.zoom.enabled.returns(true);
this.camera.getMinimumZoom.returns(0);
this.camera.getMaximumZoom.returns(3);
});
test('Should call enableZoom on the viewfinder', function() {
this.controller.onZoomConfigured();
assert.isTrue(this.viewfinder.enableZoom.calledWith(0, 3));
});
test('Should disable zoom if camera doesn\'t support zoom', function() {
this.camera.isZoomSupported.returns(false);
this.controller.onZoomConfigured();
assert.isTrue(this.viewfinder.disableZoom.called);
});
test('Should disable zoom if zoom disabled in settings', function() {
this.settings.zoom.enabled.returns(false);
this.controller.onZoomConfigured();
assert.isTrue(this.viewfinder.disableZoom.called);
});
});
suite('ViewfinderController#onSettingsOpened()', function() {
setup(function() {
sinon.spy(this.controller, 'hideGrid');
this.controller.onSettingsOpened();
});
test('Hide grid and hide viewfinder view from the screen reader',
function() {
sinon.assert.called(this.controller.hideGrid);
assert.isTrue(this.viewfinder.set.calledWith('ariaHidden', true));
});
});
suite('ViewfinderController#onSettingsClosed()', function() {
setup(function() {
sinon.spy(this.controller, 'configureGrid');
this.controller.onSettingsClosed();
});
test('Configure grid and show viewfinder view to the screen reader',
function() {
sinon.assert.called(this.controller.configureGrid);
assert.isTrue(this.viewfinder.set.calledWith('ariaHidden', false));
});
});
suite('ViewfinderController#onGalleryOpened()', function() {
setup(function() {
this.controller.onGalleryOpened();
});
test('When gallery is open, the viewfinder view should be disabled and ' +
'hidden from screen reader', function() {
sinon.assert.called(this.viewfinder.disable);
assert.isTrue(this.viewfinder.set.calledWith('ariaHidden', true));
});
});
suite('ViewfinderController#onGalleryClosed()', function() {
setup(function() {
this.controller.onGalleryClosed();
});
test('When gallery is closed, the viewfinder view should be enabled and ' +
'visible to screen reader', function() {
sinon.assert.called(this.viewfinder.enable);
assert.isTrue(this.viewfinder.set.calledWith('ariaHidden', false));
});
});
suite('ViewfinderController#onPowerSave', function() {
test('When power save enabled, the viewfinder should disable screen ' +
'wakelocks', function() {
this.controller.onPowerSave(false);
assert.isTrue(this.viewfinder.els.video.mozUseScreenWakeLock);
});
test('When power save disabled, the viewfinder should enable screen ' +
'wakelocks', function() {
this.controller.onPowerSave(true);
assert.isFalse(this.viewfinder.els.video.mozUseScreenWakeLock);
});
});
suite('click:viewfinder', function() {
test('Should set the grid depending on the setting', function() {
this.app.settings.grid.selected.withArgs('key').returns('on');
this.controller = new this.ViewfinderController(this.app);
assert.isTrue(this.viewfinder.set.calledWith('grid', 'on'));
this.app.settings.grid.selected.withArgs('key').returns('off');
this.controller = new this.ViewfinderController(this.app);
assert.isTrue(this.viewfinder.set.calledWith('grid', 'off'));
});
test('Should set viewfinder fill depending on the setting', function() {
this.app.settings.viewfinder.get.withArgs('scaleType').returns('fill');
this.controller = new this.ViewfinderController(this.app);
assert.equal(this.viewfinder.scaleType, 'fill');
this.app.settings.viewfinder.get.withArgs('scaleType').returns('fit');
this.controller = new this.ViewfinderController(this.app);
assert.equal(this.viewfinder.scaleType, 'fit');
});
});
suite('ViewfinderController#calculateFaceCircle()', function() {
test('Draw circle from a square', function() {
var circle = this.controller.calculateFaceCircle({top: 50, left: 50, width: 10, height: 10});
assert.deepEqual(circle, {x: 50, y: 50, diameter: 10});
});
test('Draw circle from a 2:1 rectangle', function() {
var circle = this.controller.calculateFaceCircle({top: 100, left: 50, width: 20, height: 10});
assert.deepEqual(circle, {x: 50, y: 95, diameter: 20});
});
test('Draw circle from a 1:2 rectangle', function() {
var circle = this.controller.calculateFaceCircle({top: 100, left: 50, width: 10, height: 20});
assert.deepEqual(circle, {x: 45, y: 100, diameter: 20});
});
});
});

View file

@ -0,0 +1,135 @@
'use strict';
suite('controllers/zoom-bar', function() {
suiteSetup(function(done) {
var self = this;
requirejs([
'app',
'lib/camera/camera',
'lib/settings',
'lib/setting',
'controllers/zoom-bar',
'views/zoom-bar'
], function(
App, Camera, Settings, Setting, ZoomBarController, ZoomBarView) {
self.ZoomBarController = ZoomBarController.ZoomBarController;
self.ZoomBarView = ZoomBarView;
self.Camera = Camera;
self.App = App;
self.Settings = Settings;
self.Setting = Setting;
done();
});
});
setup(function() {
this.sandbox = sinon.sandbox.create();
// App
this.app = sinon.createStubInstance(this.App);
this.app.camera = sinon.createStubInstance(this.Camera);
this.app.views = {
zoombar: sinon.createStubInstance(this.ZoomBarView)
};
// Settings
this.app.settings = sinon.createStubInstance(this.Settings);
this.app.settings.zoom = sinon.createStubInstance(this.Setting);
// Test instance
this.controller = new this.ZoomBarController(this.app);
// Shortcuts
this.zoombar = this.controller.view;
this.camera = this.app.camera;
});
teardown(function() {
this.sandbox.restore();
});
suite('ViewfinderController()', function() {
test('Should configure zoom when zoomconfigured event is emitted', function() {
assert.isTrue(this.camera.on.calledWith('zoomconfigured',
this.controller.onZoomConfigured));
});
test('Should set the zoom when zoomchanged event is emitted', function() {
assert.isTrue(this.camera.on.calledWith('zoomchanged',
this.controller.setZoom));
});
});
suite('ViewfinderController#createViews()', function() {
setup(function() {
this.controller.createView();
});
test('It appends view', function() {
assert.isTrue(this.zoombar.appendTo.calledWith(this.zoombar.el));
});
});
suite('ZoomBar#onChange()', function() {
setup(function() {
this.camera.getMaximumZoom.returns(10);
this.camera.getMinimumZoom.returns(1);
this.controller.onChange(5);
});
test('It calls setZoom with the correct value', function() {
assert.isTrue(this.camera.setZoom.calledWith(1.45));
});
});
suite('ZoomBar#onZoomConfigured()', function() {
setup(function() {
sinon.stub(this.controller, 'configureZoomBar');
sinon.stub(this.controller, 'setZoom');
this.controller.onZoomConfigured(5);
});
test('It sets zoom and hides', function() {
assert.isTrue(this.controller.configureZoomBar.calledOnce);
assert.isTrue(this.controller.setZoom.calledWith(5));
assert.isTrue(this.zoombar.hide.called);
});
});
suite('ZoomBar#configureZoomBar()', function() {
test('It disables zoom if not supported by camera', function() {
this.camera.isZoomSupported.returns(false);
this.app.settings.zoom.enabled.returns(true);
this.controller.configureZoomBar();
assert.isFalse(this.controller.enableZoom);
});
test('It enables zoom if supported by camera', function() {
this.camera.isZoomSupported.returns(true);
this.app.settings.zoom.enabled.returns(true);
this.controller.configureZoomBar();
assert.isTrue(this.controller.enableZoom);
});
});
suite('ZoomBar#setZoom()', function() {
setup(function() {
this.camera.getMaximumZoom.returns(11);
this.camera.getMinimumZoom.returns(1);
});
test('It sets the zoom with the correct value if enabled', function() {
this.controller.enableZoom = true;
this.controller.setZoom(4);
assert.isTrue(this.zoombar.setValue.calledWith(30));
});
test('It does not set the zoom if disabled', function() {
this.controller.enableZoom = false;
this.controller.setZoom(4);
assert.isFalse(this.zoombar.setValue.calledWith(30));
});
});
});

View file

@ -0,0 +1,658 @@
suite('lib/camera-coordinates', function() {
/*jshint maxlen:false*/
'use strict';
suiteSetup(function(done) {
var self = this;
requirejs(['lib/camera-coordinates'], function(cameraCoordinates) {
// alias
self.faceToCamera = cameraCoordinates.faceToCamera;
self.faceToPixels = cameraCoordinates.faceToPixels;
self.areaToCamera = cameraCoordinates.private.areaToCamera;
self.areaToPixels = cameraCoordinates.private.areaToPixels;
self.mirrorAreaCamera = cameraCoordinates.private.mirrorAreaCamera;
self.mirrorAreaPixels = cameraCoordinates.private.mirrorAreaPixels;
self.rotateArea = cameraCoordinates.private.rotateArea;
self.sizeToCamera = cameraCoordinates.private.sizeToCamera;
self.sizeToPixels = cameraCoordinates.private.sizeToPixels;
self.toCamera = cameraCoordinates.private.toCamera;
self.toPixels = cameraCoordinates.private.toPixels;
self.rotatePoint = cameraCoordinates.private.rotatePoint;
self.equalAreas = function(area1, area2) {
var equal = area1.top === area2.top &&
area1.left === area2.left &&
area1.bottom === area2.bottom &&
area1.right === area2.right &&
area1.width === area2.width &&
area1.height === area2.height;
if (!equal) {
console.log('Areas are not equal: ' +
JSON.stringify(area1) + ' /= ' + JSON.stringify(area2));
}
return equal;
};
done();
});
this.viewportWidth = 800;
this.viewportHeight = 600;
});
suite('#faceToCamera', function() {
suite('The face should be converted from pixel coordinates to camera and back to pixels', function() {
test('face is at the center of the viewport', function() {
var face = {
top: 200,
left: 300,
bottom: 400,
right: 600,
height: 200,
width: 300
};
var faceCameraCoordinates = this.faceToCamera(face, this.viewportWidth, this.viewportHeight, 0);
var facePixelCooordinates = this.faceToPixels(faceCameraCoordinates, this.viewportWidth, this.viewportHeight, 0);
assert.ok(this.equalAreas(face, facePixelCooordinates));
});
test('face is at the top left quadrant of the viewport', function() {
var face = {
top: 0,
left: 0,
bottom: 230,
right: 230,
height: 230,
width: 230
};
var faceCameraCoordinates = this.faceToCamera(face, this.viewportWidth, this.viewportHeight, 0);
var facePixelCooordinates = this.faceToPixels(faceCameraCoordinates, this.viewportWidth, this.viewportHeight, 0);
assert.ok(this.equalAreas(face, facePixelCooordinates));
});
test('face is at the top right quadrant of the viewport', function() {
var face = {
top: 50,
left: 500,
bottom: 80,
right: 530,
height: 30,
width: 30
};
var faceCameraCoordinates = this.faceToCamera(face, this.viewportWidth, this.viewportHeight, 0);
var facePixelCooordinates = this.faceToPixels(faceCameraCoordinates, this.viewportWidth, this.viewportHeight, 0);
assert.ok(this.equalAreas(face, facePixelCooordinates));
});
test('face is at the bottom left quadrant of the viewport', function() {
var face = {
top: 400,
left: 200,
bottom: 475,
right: 275,
height: 75,
width: 75
};
var faceCameraCoordinates = this.faceToCamera(face, this.viewportWidth, this.viewportHeight, 0);
var facePixelCooordinates = this.faceToPixels(faceCameraCoordinates, this.viewportWidth, this.viewportHeight, 0);
assert.ok(this.equalAreas(face, facePixelCooordinates));
});
test('face is at the bottom right quadrant of the viewport', function() {
var face = {
top: 400,
left: 500,
bottom: 500,
right: 600,
height: 100,
width: 100
};
var faceCameraCoordinates = this.faceToCamera(face, this.viewportWidth, this.viewportHeight, 0);
var facePixelCooordinates = this.faceToPixels(faceCameraCoordinates, this.viewportWidth, this.viewportHeight, 0);
assert.ok(this.equalAreas(face, facePixelCooordinates));
});
});
});
suite('#faceToPixels', function() {
suite('The face should be converted from camera to pixel coordinates and back to camera coordinates', function() {
test('face is at the center of the viewport', function() {
// var face = {
// top: -25,
// left: -25,
// bottom: 25,
// right: 25,
// height: 50,
// width: 50
// };
var face = {
top: -50,
left: -50,
bottom: 50,
right: 50,
height: 100,
width: 100
};
var facePixelCooordinates = this.faceToPixels(face, this.viewportWidth, this.viewportHeight, 0);
var faceCameraCoordinates = this.faceToCamera(facePixelCooordinates, this.viewportWidth, this.viewportHeight, 0);
assert.ok(this.equalAreas(face, faceCameraCoordinates));
});
test('face is at the top left quadrant of the viewport', function() {
var face = {
top: -1000,
left: -1000,
bottom: -950,
right: -950,
height: 50,
width: 50
};
var facePixelCooordinates = this.faceToPixels(face, this.viewportWidth, this.viewportHeight, 0);
var faceCameraCoordinates = this.faceToCamera(facePixelCooordinates, this.viewportWidth, this.viewportHeight, 0);
assert.ok(this.equalAreas(face, faceCameraCoordinates));
});
test('face is at the top right quadrant of the viewport', function() {
var face = {
top: -500,
left: 500,
bottom: -450,
right: 550,
height: 50,
width: 50
};
var facePixelCooordinates = this.faceToPixels(face, this.viewportWidth, this.viewportHeight, 0);
var faceCameraCoordinates = this.faceToCamera(facePixelCooordinates, this.viewportWidth, this.viewportHeight, 0);
assert.ok(this.equalAreas(face, faceCameraCoordinates));
});
test('face is at the bottom left quadrant of the viewport', function() {
var face = {
top: 500,
left: -500,
bottom: 550,
right: -450,
height: 50,
width: 50
};
var facePixelCooordinates = this.faceToPixels(face, this.viewportWidth, this.viewportHeight, 0);
var faceCameraCoordinates = this.faceToCamera(facePixelCooordinates, this.viewportWidth, this.viewportHeight, 0);
assert.ok(this.equalAreas(face, faceCameraCoordinates));
});
test('face is at the bottom right quadrant of the viewport', function() {
var face = {
top: 500,
left: 500,
bottom: 550,
right: 550,
height: 50,
width: 50
};
var facePixelCooordinates = this.faceToPixels(face, this.viewportWidth, this.viewportHeight, 0);
var faceCameraCoordinates = this.faceToCamera(facePixelCooordinates, this.viewportWidth, this.viewportHeight, 0);
assert.ok(this.equalAreas(face, faceCameraCoordinates));
});
});
});
suite('#areaToCamera', function() {
suite('The area should be converted from pixel coordinates to camera and back to pixels', function() {
test('area is at the center of the viewport', function() {
var area = {
top: 200,
left: 300,
bottom: 400,
right: 600,
height: 200,
width: 300
};
var areaCamera = this.areaToCamera(area, this.viewportWidth, this.viewportHeight);
var areaPixels = this.areaToPixels(areaCamera, this.viewportWidth, this.viewportHeight);
assert.ok(this.equalAreas(area, areaPixels));
});
test('area is at the top left quadrant of the viewport', function() {
var area = {
top: 0,
left: 0,
bottom: 230,
right: 230,
height: 230,
width: 230
};
var areaCamera = this.areaToCamera(area, this.viewportWidth, this.viewportHeight);
var areaPixels = this.areaToPixels(areaCamera, this.viewportWidth, this.viewportHeight);
assert.ok(this.equalAreas(area, areaPixels));
});
test('area is at the top right quadrant of the viewport', function() {
var area = {
top: 50,
left: 500,
bottom: 80,
right: 530,
height: 30,
width: 30
};
var areaCamera = this.areaToCamera(area, this.viewportWidth, this.viewportHeight);
var areaPixels = this.areaToPixels(areaCamera, this.viewportWidth, this.viewportHeight);
assert.ok(this.equalAreas(area, areaPixels));
});
test('area is at the bottom left quadrant of the viewport', function() {
var area = {
top: 400,
left: 200,
bottom: 475,
right: 275,
height: 75,
width: 75
};
var areaCamera = this.areaToCamera(area, this.viewportWidth, this.viewportHeight);
var areaPixels = this.areaToPixels(areaCamera, this.viewportWidth, this.viewportHeight);
assert.ok(this.equalAreas(area, areaPixels));
});
test('area is at the bottom right quadrant of the viewport', function() {
var area = {
top: 400,
left: 500,
bottom: 500,
right: 600,
height: 100,
width: 100
};
var areaCamera = this.areaToCamera(area, this.viewportWidth, this.viewportHeight);
var areaPixels = this.areaToPixels(areaCamera, this.viewportWidth, this.viewportHeight);
assert.ok(this.equalAreas(area, areaPixels));
});
});
});
suite('#areaToPixels', function() {
suite('The area should be converted from camera to pixel coordinates and back to camera coordinates', function() {
test('area is at the center of the viewport', function() {
var area = {
top: -50,
left: -50,
bottom: 50,
right: 50,
height: 100,
width: 100
};
var areaPixels = this.areaToPixels(area, this.viewportWidth, this.viewportHeight);
var areaCamera = this.areaToCamera(areaPixels, this.viewportWidth, this.viewportHeight);
assert.ok(this.equalAreas(area, areaCamera));
});
test('area is at the top left quadrant of the viewport', function() {
var area = {
top: -50,
left: -50,
bottom: 50,
right: 50,
height: 100,
width: 100
};
var areaPixels = this.areaToPixels(area, this.viewportWidth, this.viewportHeight);
var areaCamera = this.areaToCamera(areaPixels, this.viewportWidth, this.viewportHeight);
assert.ok(this.equalAreas(area, areaCamera));
});
test('area is at the top right quadrant of the viewport', function() {
var area = {
top: -50,
left: -50,
bottom: 50,
right: 50,
height: 100,
width: 100
};
var areaPixels = this.areaToPixels(area, this.viewportWidth, this.viewportHeight);
var areaCamera = this.areaToCamera(areaPixels, this.viewportWidth, this.viewportHeight);
assert.ok(this.equalAreas(area, areaCamera));
});
test('area is at the bottom left quadrant of the viewport', function() {
var area = {
top: -50,
left: -50,
bottom: 50,
right: 50,
height: 100,
width: 100
};
var areaPixels = this.areaToPixels(area, this.viewportWidth, this.viewportHeight);
var areaCamera = this.areaToCamera(areaPixels, this.viewportWidth, this.viewportHeight);
assert.ok(this.equalAreas(area, areaCamera));
});
test('area is at the bottom right quadrant of the viewport', function() {
var area = {
top: -50,
left: -50,
bottom: 50,
right: 50,
height: 100,
width: 100
};
var areaPixels = this.areaToPixels(area, this.viewportWidth, this.viewportHeight);
var areaCamera = this.areaToCamera(areaPixels, this.viewportWidth, this.viewportHeight);
assert.ok(this.equalAreas(area, areaCamera));
});
});
});
suite('#mirrorAreaCamera', function() {
suite('The area should be properly mirrored', function() {
test('area is at the center of the viewport', function() {
var area = {
top: -50,
left: -50,
bottom: 50,
right: 50,
height: 100,
width: 100
};
var mirroredArea = this.mirrorAreaCamera(area);
var doubledMirrorArea = this.mirrorAreaCamera(mirroredArea);
assert.ok(this.equalAreas(area, doubledMirrorArea));
});
test('area is at the top left quadrant of the viewport', function() {
var area = {
top: -100,
left: -100,
bottom: -50,
right: -50,
height: 50,
width: 50
};
var mirroredArea = this.mirrorAreaCamera(area);
var doubledMirrorArea = this.mirrorAreaCamera(mirroredArea);
assert.ok(this.equalAreas(area, doubledMirrorArea));
});
test('area is at the top right quadrant of the viewport', function() {
var area = {
top: -100,
left: 300,
bottom: -50,
right: 350,
height: 50,
width: 50
};
var mirroredArea = this.mirrorAreaCamera(area);
var doubledMirrorArea = this.mirrorAreaCamera(mirroredArea);
assert.ok(this.equalAreas(area, doubledMirrorArea));
});
test('area is at the bottom left quadrant of the viewport', function() {
var area = {
top: 100,
left: -200,
bottom: 200,
right: -100,
height: 100,
width: 100
};
var mirroredArea = this.mirrorAreaCamera(area);
var doubledMirrorArea = this.mirrorAreaCamera(mirroredArea);
assert.ok(this.equalAreas(area, doubledMirrorArea));
});
test('area is at the bottom right quadrant of the viewport', function() {
var area = {
top: 800,
left: 800,
bottom: 900,
right: 900,
height: 100,
width: 100
};
var mirroredArea = this.mirrorAreaCamera(area);
var doubledMirrorArea = this.mirrorAreaCamera(mirroredArea);
assert.ok(this.equalAreas(area, doubledMirrorArea));
});
});
});
suite('#mirrorAreaPixels', function() {
suite('The area should be properly mirrored', function() {
test('area is at the center of the viewport', function() {
var area = {
top: 100,
left: 300,
bottom: 200,
right: 400,
height: 100,
width: 100
};
var mirroredArea = this.mirrorAreaPixels(area, this.viewportWidth);
var doubledMirrorArea = this.mirrorAreaPixels(mirroredArea, this.viewportWidth);
assert.ok(this.equalAreas(area, doubledMirrorArea));
});
});
});
suite('#rotateArea', function() {
suite('The area should be properly rotated', function() {
test('area rotates 0 degrees', function() {
var area = {
top: -50,
left: -50,
bottom: 50,
right: 50,
height: 100,
width: 100
};
var expectedResult = {
top: -50,
left: -50,
bottom: 50,
right: 50,
height: 100,
width: 100
};
var rotatedArea = this.rotateArea(area, 0);
assert.ok(this.equalAreas(rotatedArea, expectedResult));
});
test('area rotates 90 degrees', function() {
var area = {
top: -50,
left: -50,
bottom: 50,
right: 50,
height: 100,
width: 100
};
var expectedResult = {
top: -50,
left: -50,
bottom: 50,
right: 50,
height: 100,
width: 100
};
var rotatedArea = this.rotateArea(area, 90);
assert.ok(this.equalAreas(rotatedArea, expectedResult));
});
test('area rotates 180 degrees', function() {
var area = {
top: -50,
left: -50,
bottom: 50,
right: 50,
height: 100,
width: 100
};
var expectedResult = {
top: -50,
left: -50,
bottom: 50,
right: 50,
height: 100,
width: 100
};
var rotatedArea = this.rotateArea(area, 180);
assert.ok(this.equalAreas(rotatedArea, expectedResult));
});
test('area rotates 270 degrees', function() {
var area = {
top: -50,
left: -50,
bottom: 50,
right: 50,
height: 100,
width: 100
};
var expectedResult = {
top: -50,
left: -50,
bottom: 50,
right: 50,
height: 100,
width: 100
};
var rotatedArea = this.rotateArea(area, 270);
assert.ok(this.equalAreas(rotatedArea, expectedResult));
});
});
});
suite('#sizeToCamera', function() {
test('The size should be converted from pixels to camera coordinates and back to pixels', function() {
var width = 50;
var height = 100;
var sizeCamera = this.sizeToCamera(width, height, this.viewportWidth, this.viewportHeight);
var sizePixels = this.sizeToPixels(sizeCamera.width, sizeCamera.height, this.viewportWidth, this.viewportHeight);
assert.ok(sizePixels.width === width);
assert.ok(sizePixels.height === height);
});
});
suite('#sizeToPixels', function() {
test('The size should be converted from camera coordinates to pixels and back to camera', function() {
var width = 50;
var height = 100;
var sizePixels = this.sizeToPixels(width, height, this.viewportWidth, this.viewportHeight);
var sizeCamera = this.sizeToCamera(sizePixels.width, sizePixels.height, this.viewportWidth, this.viewportHeight);
assert.ok(sizeCamera.width === width);
assert.ok(sizeCamera.height === height);
});
});
suite('#toCamera', function() {
test('The coordinate should be transformed from pixels to camera coordinates', function() {
var x = 50;
var y = 100;
var camera = this.toCamera(x, y, this.viewportWidth, this.viewportHeight);
var pixels = this.toPixels(camera.x, camera.y, this.viewportWidth, this.viewportHeight);
assert.ok(pixels.x === x);
assert.ok(pixels.y === y);
});
});
suite('#toPixels', function() {
test('The coordinate should be transformed from camera coordinates to pixels', function() {
var x = 50;
var y = 100;
var pixels = this.toPixels(x, y, this.viewportWidth, this.viewportHeight);
var camera = this.toCamera(pixels.x, pixels.y, this.viewportWidth, this.viewportHeight);
assert.ok(camera.x === x);
assert.ok(camera.y === y);
});
});
suite('#rotatePoint', function() {
test('The point coordinates should rotate 0 degrees', function() {
var x = 50;
var y = 100;
var point = this.rotatePoint(x, y, 0);
assert.ok(x === point.x);
assert.ok(y === point.y);
});
test('The point coordinates should rotate 90 degrees', function() {
var x = 760;
var y = 134;
var point = this.rotatePoint(x, y, 90);
assert.ok(x === point.y);
assert.ok(y === -point.x);
});
test('The point coordinates should rotate 180 degrees', function() {
var x = 34;
var y = 100;
var point = this.rotatePoint(x, y, 180);
assert.ok(x === -point.x);
assert.ok(y === -point.y);
});
test('The point coordinates should rotate 270 degrees', function() {
var x = 200;
var y = 200;
var point = this.rotatePoint(x, y, 270);
assert.ok(x === -point.y);
assert.ok(y === point.x);
});
test('Angle should be clamped to 0 degrees', function() {
var x = 0;
var y = 240;
var point1 = this.rotatePoint(x, y, 0);
var point2 = this.rotatePoint(x, y, -20);
assert.ok(point1.x === point2.x);
assert.ok(point1.y === point2.y);
});
test('Angle should be clamped to 90 degrees', function() {
var x = 123;
var y = -321;
var point1 = this.rotatePoint(x, y, 90);
var point2 = this.rotatePoint(x, y, -260);
assert.ok(point1.x === point2.x);
assert.ok(point1.y === point2.y);
});
test('Angle should be clamped to 90 degrees', function() {
var x = 123;
var y = 321;
var point1 = this.rotatePoint(x, y, 90);
var point2 = this.rotatePoint(x, y, 123);
assert.ok(point1.x === point2.x);
assert.ok(point1.y === point2.y);
});
test('Angle should be clamped to 180 degrees', function() {
var x = -100;
var y = 100;
var point1 = this.rotatePoint(x, y, 180);
var point2 = this.rotatePoint(x, y, 140);
assert.ok(point1.x === point2.x);
assert.ok(point1.y === point2.y);
});
test('Angle should be clamped to 270 degrees', function() {
var x = -500;
var y = -500;
var point1 = this.rotatePoint(x, y, 230);
var point2 = this.rotatePoint(x, y, 270);
assert.ok(point1.x === point2.x);
assert.ok(point1.y === point2.y);
});
});
});

View file

@ -0,0 +1,235 @@
/*jshint maxlen:false*/
/*global req*/
'use strict';
suite('utils/camera-utils', function() {
var CameraUtils;
suiteSetup(function(done) {
var self = this;
this.modules = {};
requirejs([
'lib/camera-utils'
], function(_CameraUtils) {
CameraUtils = self.modules.CameraUtils = _CameraUtils;
done();
});
});
setup(function() {
});
suite('scaleSizeToFitViewport', function() {
setup(function() {
});
test('Should scale image size to *FIT* viewport ' +
'preserving aspect ratio', function() {
var viewportSize, imageSize, outputSize;
// Case where image size exactly matches viewport size
viewportSize = {
width: 720,
height: 1280
};
imageSize = {
width: 720,
height: 1280
};
outputSize = CameraUtils.scaleSizeToFitViewport(viewportSize, imageSize);
assert.equal(outputSize.width, 720);
assert.equal(outputSize.height, 1280);
// Case where image size has larger width, smaller height than viewport
viewportSize = {
width: 720,
height: 1280
};
imageSize = {
width: 800,
height: 1280
};
outputSize = CameraUtils.scaleSizeToFitViewport(viewportSize, imageSize);
assert.equal(outputSize.width, 720);
assert.equal(outputSize.height, 1152);
// Case where image size has larger height, smaller width than viewport
viewportSize = {
width: 720,
height: 1280
};
imageSize = {
width: 720,
height: 1440
};
outputSize = CameraUtils.scaleSizeToFitViewport(viewportSize, imageSize);
assert.equal(outputSize.width, 640);
assert.equal(outputSize.height, 1280);
// Case where image size has larger width, larger height than viewport
// (different aspect ratio)
viewportSize = {
width: 600,
height: 800
};
imageSize = {
width: 720,
height: 1152
};
outputSize = CameraUtils.scaleSizeToFitViewport(viewportSize, imageSize);
assert.equal(outputSize.width, 500);
assert.equal(outputSize.height, 800);
// Case where image size has larger width, larger height than viewport
// (same aspect ratio)
viewportSize = {
width: 720,
height: 1280
};
imageSize = {
width: 1080,
height: 1920
};
outputSize = CameraUtils.scaleSizeToFitViewport(viewportSize, imageSize);
assert.equal(outputSize.width, 720);
assert.equal(outputSize.height, 1280);
// Case where image size has smaller width, smaller height than viewport
// (different aspect ratio)
viewportSize = {
width: 720,
height: 1152
};
imageSize = {
width: 600,
height: 800
};
outputSize = CameraUtils.scaleSizeToFitViewport(viewportSize, imageSize);
assert.equal(outputSize.width, 720);
assert.equal(outputSize.height, 960);
// Case where image size has smaller width, smaller height than viewport
// (same aspect ratio)
viewportSize = {
width: 1080,
height: 1920
};
imageSize = {
width: 720,
height: 1280
};
outputSize = CameraUtils.scaleSizeToFitViewport(viewportSize, imageSize);
assert.equal(outputSize.width, 1080);
assert.equal(outputSize.height, 1920);
});
});
suite('scaleSizeToFillViewport', function() {
setup(function() {});
test('Should scale image size to *FILL* viewport ' +
'preserving aspect ratio', function() {
var viewportSize, imageSize, outputSize;
// Case where image size exactly matches viewport size
viewportSize = {
width: 720,
height: 1280
};
imageSize = {
width: 720,
height: 1280
};
outputSize = CameraUtils.scaleSizeToFillViewport(viewportSize, imageSize);
assert.equal(outputSize.width, 720);
assert.equal(outputSize.height, 1280);
// Case where image size has larger width, smaller height than viewport
viewportSize = {
width: 720,
height: 1280
};
imageSize = {
width: 800,
height: 1280
};
outputSize = CameraUtils.scaleSizeToFillViewport(viewportSize, imageSize);
assert.equal(outputSize.width, 800);
assert.equal(outputSize.height, 1280);
// Case where image size has larger height, smaller width than viewport
viewportSize = {
width: 720,
height: 1280
};
imageSize = {
width: 720,
height: 1440
};
outputSize = CameraUtils.scaleSizeToFillViewport(viewportSize, imageSize);
assert.equal(outputSize.width, 720);
assert.equal(outputSize.height, 1440);
// Case where image size has larger width, larger height than viewport
// (different aspect ratio)
viewportSize = {
width: 600,
height: 800
};
imageSize = {
width: 720,
height: 1152
};
outputSize = CameraUtils.scaleSizeToFillViewport(viewportSize, imageSize);
assert.equal(outputSize.width, 600);
assert.equal(outputSize.height, 960);
// Case where image size has larger width, larger height than viewport
// (same aspect ratio)
viewportSize = {
width: 720,
height: 1280
};
imageSize = {
width: 1080,
height: 1920
};
outputSize = CameraUtils.scaleSizeToFillViewport(viewportSize, imageSize);
assert.equal(outputSize.width, 720);
assert.equal(outputSize.height, 1280);
// Case where image size has smaller width, smaller height than viewport
// (different aspect ratio)
viewportSize = {
width: 720,
height: 1152
};
imageSize = {
width: 600,
height: 800
};
outputSize = CameraUtils.scaleSizeToFillViewport(viewportSize, imageSize);
assert.equal(outputSize.width, 864);
assert.equal(outputSize.height, 1152);
// Case where image size has smaller width, smaller height than viewport
// (same aspect ratio)
viewportSize = {
width: 1080,
height: 1920
};
imageSize = {
width: 720,
height: 1280
};
outputSize = CameraUtils.scaleSizeToFillViewport(viewportSize, imageSize);
assert.equal(outputSize.width, 1080);
assert.equal(outputSize.height, 1920);
});
});
});

File diff suppressed because it is too large Load diff

View file

@ -0,0 +1,677 @@
suite('lib/camera/focus', function() {
'use strict';
suiteSetup(function(done) {
var self = this;
requirejs(['lib/camera/focus'], function(Focus) {
self.Focus = Focus;
done();
});
});
setup(function() {
this.sandbox = sinon.sandbox.create();
// Fake mozCamera
this.mozCamera = {
_listeners: {},
capabilities: {
focusModes : ["auto", "infinity", "normal",
"macro", "continuous-picture", "continuous-video" ],
},
autoFocus: sinon.stub().returns({
then: function(onSuccess, onError) {
onSuccess(true);
}
}),
setFocusAreas: sinon.stub(),
setMeteringAreas: sinon.stub(),
stopContinuousFocus: sinon.stub(),
resumeContinuousFocus: sinon.stub(),
startFaceDetection: sinon.spy(),
stopFaceDetection: sinon.spy(),
addEventListener: function(eventname, listener) {
this._listeners[eventname] = listener;
},
removeEventListener: function(eventname, listener) {
if (this._listeners.hasOwnProperty(eventname)) {
if (this._listeners[eventname] === listener) {
delete this._listeners[eventname];
}
}
}
};
this.focus = new this.Focus({});
this.focus.mozCamera = this.mozCamera;
});
suite('Focus#configure()', function() {
setup(function() {
this.focus.configureFocusModes = sinon.spy();
});
test('mozCamera should be assigned \'auto\' mode by default', function() {
this.focus.configure(this.mozCamera);
assert.ok(this.focus.configureFocusModes.called);
assert.equal(this.mozCamera.focusMode, 'auto');
});
test('user preferences override defaults', function() {
this.focus.continuousAutoFocus = false;
this.focus.configure(this.mozCamera, 'continuous-picture');
assert.ok(this.focus.configureFocusModes.called);
assert.equal(this.mozCamera.focusMode, 'auto');
});
test('continuous-picture selected on picture mode if user pref is set to CAF', function() {
this.focus.continuousAutoFocus = true;
this.focus.configure(this.mozCamera, 'picture');
assert.ok(this.focus.configureFocusModes.called);
assert.equal(this.mozCamera.focusMode, 'continuous-picture');
});
test('continuous-video selected on video mode if user pref is set to CAF', function() {
this.focus.continuousAutoFocus = true;
this.focus.configure(this.mozCamera, 'video');
assert.ok(this.focus.configureFocusModes.called);
assert.equal(this.mozCamera.focusMode, 'continuous-video');
});
test('if not valid focus mode is passed the first available is picked', function() {
this.focus.configure(this.mozCamera, 'invalid-focus-mode');
assert.ok(this.focus.configureFocusModes.called);
assert.equal(this.mozCamera.focusMode, 'auto');
});
});
suite('Focus#getMode', function() {
setup(function() {
this.mozCamera.focusMode = 'auto';
});
test('it returns the current focus mode', function() {
this.focus.startFaceDetection();
assert.ok(this.focus.getMode() === 'auto');
});
test('it returns the suspended focus mode', function() {
this.mozCamera.focusMode = 'continuous-picture';
assert.ok(this.focus.getMode() === 'continuous-picture');
});
});
suite('Focus#configureFocusModes()', function() {
setup(function() {
var userPreferences = {
touchFocus: true,
faceDetection: false
};
this.focus = new this.Focus(userPreferences);
this.focus.mozCamera = this.mozCamera;
this.sandbox.stub(this.focus, 'isTouchFocusSupported');
this.sandbox.stub(this.focus, 'isFaceDetectionSupported');
this.sandbox.stub(this.focus, 'startFaceDetection');
this.focus.isTouchFocusSupported.returns(true);
this.focus.isFaceDetectionSupported.returns(true);
});
teardown(function() {
this.sandbox.restore();
});
test('continous autofocus is enabled by default if supported', function() {
this.focus.configureFocusModes();
assert.equal(this.focus.continuousAutoFocus, true);
});
test('continous autofocus is disabled if user preferences desable it', function() {
this.focus.userPreferences.continuousAutoFocus = false;
this.focus.configureFocusModes();
assert.equal(this.focus.continuousAutoFocus, false);
});
test('touch to focus is enabled by default if available', function() {
this.focus.configureFocusModes();
assert.equal(this.focus.touchFocus, true);
});
test('touch to focus is disabled if user preferences disable it', function() {
this.focus.userPreferences.touchFocus = false;
this.focus.configureFocusModes();
assert.equal(this.focus.touchFocus, false);
});
test('touch to focus is disabled if hardware doesn\'t support it', function() {
this.focus.isTouchFocusSupported.returns(false);
this.focus.configureFocusModes();
assert.equal(this.focus.touchFocus, false);
});
test('touch to focus disabled if hardware doesn\'t support it and even if user preferences enable it', function() {
this.focus.userPreferences.touchFocus = true;
this.focus.isTouchFocusSupported.returns(false);
this.focus.configureFocusModes('picture');
assert.equal(this.focus.touchFocus, false);
});
test('face detection is disabled if not supported by HW', function() {
this.focus.userPreferences.faceDetection = true;
this.focus.isFaceDetectionSupported.returns(false);
this.focus.configureFocusModes('picture');
assert.ok(!this.focus.faceDetection);
});
test('face detection is disabled if disabled in user preferences', function() {
this.focus.userPreferences.faceDetection = false;
this.focus.isFaceDetectionSupported.returns(true);
this.focus.configureFocusModes('picture');
assert.ok(!this.focus.faceDetection);
});
test('face detection is disabled in video mode', function() {
this.focus.userPreferences.faceDetection = true;
this.focus.isFaceDetectionSupported.returns(true);
this.focus.configureFocusModes('video');
assert.ok(!this.focus.faceDetection);
});
test('face detection is enabled in picture mode if supported by HW and enabled in user preferences', function() {
this.focus.userPreferences.faceDetection = true;
this.focus.isFaceDetectionSupported.returns(true);
this.focus.configureFocusModes('picture');
assert.ok(this.focus.faceDetection);
});
});
suite('Focus#startFaceDetection', function() {
setup(function() {
this.focus.faceDetection = true;
});
test('mozCamera startFaceDetection is called to start face detection if face detection enabled', function() {
this.focus.startFaceDetection();
assert.ok(this.mozCamera.startFaceDetection.called);
});
test('mozCamera startFaceDetection is not called to start face detection if face detection disabled', function() {
this.focus.faceDetection = false;
this.focus.startFaceDetection();
assert.ok(!this.mozCamera.startFaceDetection.called);
});
});
suite('Focus#clearFaceDetection', function() {
setup(function() {
this.focus.faceDetection = true;
this.sandbox.spy(this.focus, 'focusOnLargestFace');
});
teardown(function() {
this.sandbox.restore();
});
test('focusOnLargestFace is called to start face detection if face detection enabled', function() {
this.focus.clearFaceDetection();
assert.ok(this.focus.focusOnLargestFace.calledWith([]));
});
test('focusOnLargestFace is called with empty list when clearing faces', function() {
this.focus.faceDetection = false;
this.focus.clearFaceDetection();
assert.ok(this.focus.focusOnLargestFace.calledWith([]));
});
});
suite('Focus#stopFaceDetection', function() {
setup(function() {
this.focus.faceDetection = true;
this.focus.mozCamera = this.mozCamera;
this.sandbox.spy(this.focus, 'stopFaceDetection');
this.sandbox.spy(this.focus, 'clearFaceDetection');
});
teardown(function() {
this.sandbox.restore();
});
test('mozCamera stopFaceDetection is called to stop face detection', function() {
this.focus.stopFaceDetection();
assert.ok(this.mozCamera.stopFaceDetection.called);
});
test('mozCamera stopFaceDetection is not called if face detection is not available', function() {
this.mozCamera.stopFaceDetection = false;
this.focus.stopFaceDetection();
assert.ok(!this.mozCamera.stopFaceDetection.called);
assert.ok(this.focus.clearFaceDetection.called);
});
});
suite('Focus#suspendFaceDetection', function() {
setup(function() {
this.clock = sinon.useFakeTimers();
this.focus.faceDetection = true;
this.focus.faceDetectionSuspended = undefined;
this.focus.faceFocused = true;
});
teardown(function() {
this.clock.restore();
});
test('mozCamera suspendFaceDetection resets timers', function() {
this.focus.suspendFaceDetection(500, 300);
this.clock.tick(500);
assert.ok(this.focus.faceDetectionSuspended);
this.clock.tick(300);
assert.ok(!this.focus.faceFocused);
});
});
suite('Focus#resumeContinuousFocus', function() {
setup(function() {
this.focus.mozCamera = this.mozCamera;
this.focus.previousMode = 'auto';
this.mozCamera.focusMode = 'continuous-picture';
});
test('mozCamera resumeContinuousFocus is called', function() {
this.focus.resumeContinuousFocus();
assert.ok(!this.focus.suspendedMode);
assert.ok(this.focus.mozCamera.resumeContinuousFocus.called);
});
});
suite('Focus#onfocus', function() {
setup(function() {
this.sandbox.spy(this.focus, 'updateFocusState');
});
teardown(function() {
this.sandbox.restore();
});
test('should call updateFocusState', function() {
this.focus.configureFocusModes();
var listener = this.mozCamera._listeners['focus'];
assert.ok(listener);
listener({ newState: 'focusing' });
assert.ok(this.focus.updateFocusState.calledWith('focusing'));
listener({ newState: 'focused' });
assert.ok(this.focus.updateFocusState.calledWith('focused'));
listener({ newState: 'unfocused' });
assert.ok(this.focus.updateFocusState.calledWith('fail'));
});
});
suite('Focus#updateFocusState', function() {
setup(function() {
this.sandbox.spy(this.focus, 'onAutoFocusChanged');
});
teardown(function() {
this.sandbox.restore();
});
test('should call onAutoFocusChanged', function() {
this.focus.focusState = 'focused';
this.focus.updateFocusState('focusing');
assert.ok(this.focus.onAutoFocusChanged.calledWith('focusing'));
this.focus.updateFocusState('focused');
assert.ok(this.focus.onAutoFocusChanged.calledWith('focused'));
this.focus.updateFocusState('focusing');
assert.ok(this.focus.onAutoFocusChanged.calledWith('focusing'));
this.focus.updateFocusState('fail');
assert.ok(this.focus.onAutoFocusChanged.calledWith('fail'));
});
test('should not call onAutoFocusChanged', function() {
this.focus.focusState = 'focused';
this.focus.updateFocusState('focused');
assert.ok(!this.focus.onAutoFocusChanged.called);
this.focus.updateFocusState('fail');
assert.ok(!this.focus.onAutoFocusChanged.called);
this.focus.focusState = 'focusing';
this.focus.updateFocusState('focusing');
assert.ok(!this.focus.onAutoFocusChanged.called);
});
});
suite('Focus#suspendContinuousFocus', function() {
setup(function() {
this.clock = sinon.useFakeTimers();
this.focus.mozCamera = this.mozCamera;
this.sandbox.spy(this.focus, 'resumeContinuousFocus');
this.focus.continuousModeTimer = undefined;
});
teardown(function() {
this.sandbox.restore();
this.clock.restore();
});
test('mozCamera resumeContinuousFocus is called', function() {
this.focus.suspendContinuousFocus(500);
assert.ok(this.focus.continuousModeTimer !== undefined);
this.clock.tick(500);
assert.ok(this.focus.resumeContinuousFocus.called);
});
});
suite('Focus#focusOnLargestFace', function() {
setup(function() {
this.sandbox.spy(this.focus, 'onFacesDetected');
});
teardown(function() {
this.sandbox.restore();
});
test('it should not focus on any face if face detection is suspended', function() {
this.focus.touchFocus = true;
this.focus.faceDetectionSuspended = true;
this.focus.focusOnLargestFace([{
id: 3,
score: 80,
bounds: {
height: 300,
width: 300
}
}]);
assert.ok(this.focus.onFacesDetected.calledWith([]));
});
test('it should focus on the singleton face', function() {
this.focus.touchFocus = true;
this.focus.faceDetectionSuspended = false;
var face = {
id: 3,
score: 80,
bounds: {
height: 300,
width: 300
}
};
this.focus.focusOnLargestFace([face]);
assert.ok(
Object.is(this.focus.onFacesDetected.firstCall.args[0][0], face)
);
});
test('it should focus on the singleton face (event)', function() {
this.focus.touchFocus = true;
this.focus.faceDetectionSuspended = false;
var face = {
id: 3,
score: 80,
bounds: {
height: 300,
width: 300
}
};
var event = { faces: [face] };
this.focus.handleFaceDetectionEvent(event);
assert.ok(
Object.is(this.focus.onFacesDetected.firstCall.args[0][0], face)
);
});
});
suite('Focus#focus()', function() {
setup(function() {
this.focus.focusMode = 'auto';
this.focus.onAutoFocusChanged = sinon.spy();
});
test('mozCamera autoFocus is called if focus mode is auto', function() {
this.mozCamera.focusMode = 'auto';
this.focus.focus(function() {});
assert.ok(this.focus.onAutoFocusChanged.calledWith('focusing'));
assert.ok(this.mozCamera.autoFocus.called);
});
test('mozCamera autoFocus is called if focus mode is continuous-picture', function() {
this.mozCamera.focusMode = 'continuous-picture';
this.focus.focus(function() {});
assert.ok(this.focus.onAutoFocusChanged.calledWith('focusing'));
assert.ok(this.mozCamera.autoFocus.called);
});
test('mozCamera autoFocus is not called if focus mode is neither auto nor continuous-picture', function() {
this.mozCamera.focusMode = 'infinity';
this.focus.focus(function() {});
assert.ok(!this.focus.onAutoFocusChanged.called);
assert.ok(!this.mozCamera.autoFocus.called);
});
test('mozCamera autoFocus callback is called with error and focus state switches to false', function() {
var onFocused = sinon.spy();
this.mozCamera.autoFocus = sinon.stub();
this.mozCamera.focusMode = 'auto';
this.mozCamera.autoFocus.returns({
then: function(resolve, reject) {
resolve(false);
}
});
this.focus.focus(onFocused);
assert.ok(this.focus.onAutoFocusChanged.calledWith('focusing'));
assert.ok(this.mozCamera.autoFocus.called);
assert.ok(onFocused.calledWith('failed'));
assert.ok(!this.focus.focused);
});
test('mozCamera autoFocus callback is called with success and focus state switches to true', function() {
var onFocused = sinon.spy();
this.mozCamera.autoFocus = sinon.stub();
this.mozCamera.focusMode = 'auto';
this.mozCamera.autoFocus.returns({
then: function(resolve, reject) {
resolve(true);
}
});
this.focus.focus(onFocused);
assert.ok(this.focus.onAutoFocusChanged.calledWith('focusing'));
assert.ok(this.mozCamera.autoFocus.called);
assert.ok(onFocused.calledWith('focused'));
assert.ok(this.focus.focused);
});
test('with focus mode different than auto or continuous-picture callback is called with no arguments and focus state doesn\'t change', function(done) {
var onFocused = function(result) {
assert.equal(result, undefined);
done();
};
var previousFocusState;
this.focus.focused = true;
previousFocusState = this.focus.focused;
this.mozCamera.autoFocus = sinon.stub();
this.mozCamera.focusMode = 'infinity';
this.focus.focus(onFocused);
assert.ok(!this.focus.onAutoFocusChanged.called);
assert.ok(!this.mozCamera.autoFocus.called);
assert.ok(previousFocusState === this.focus.focused);
});
test('Should call the focus callback with interrupted state if autofocus is interrupted', function() {
var onFocused = sinon.spy();
this.mozCamera.autoFocus = sinon.stub();
this.mozCamera.autoFocus.returns({
then: function(resolve, reject) {
var error = new Error();
error.name = 'NS_ERROR_IN_PROGRESS';
reject(error);
}
});
this.mozCamera.focusMode = 'auto';
this.focus.focused = true;
this.focus.focus(onFocused);
assert.ok(onFocused.calledWith('interrupted'));
assert.ok(this.focus.focused === false);
});
});
suite('Focus#pause()', function() {
setup(function() {
this.focus.stopContinuousFocus = sinon.spy();
this.focus.stopFaceDetection = sinon.spy();
});
test('Should do nothing if already paused', function() {
this.focus.paused = true;
this.focus.pause();
assert.ok(!this.focus.stopContinuousFocus.called);
assert.ok(!this.focus.stopFaceDetection.called);
assert.ok(this.focus.paused);
});
test('Should stop face detection and continuous focus', function() {
this.focus.paused = false;
this.focus.pause();
assert.ok(this.focus.stopContinuousFocus.called);
assert.ok(this.focus.stopFaceDetection.called);
assert.ok(this.focus.paused);
});
});
suite('Focus#reset()', function() {
test('metering areas and focus areas are reset if touch focus enabled', function() {
this.focus.touchFocus = true;
this.focus.resetFocusAreas();
assert.ok(this.focus.mozCamera.setFocusAreas.calledWith([]));
assert.ok(this.focus.mozCamera.setMeteringAreas.calledWith([]));
});
test('metering areas and focus areas are not reset if touch focus disabled', function() {
this.focus.touchFocus = false;
this.focus.resetFocusAreas();
assert.ok(!this.focus.mozCamera.setFocusAreas.called);
assert.ok(!this.focus.mozCamera.setMeteringAreas.called);
});
});
suite('Focus#resume()', function() {
setup(function() {
this.focus.resumeContinuousFocus = sinon.spy();
});
test('should do nothing if not paused', function() {
this.focus.paused = false;
this.focus.resume();
assert.ok(!this.focus.resumeContinuousFocus.called);
assert.ok(!this.focus.paused);
});
test('resumeContinuousFocus called if focus mode is continuous-picture', function() {
this.focus.paused = true;
this.focus.mozCamera.focusMode = 'continuous-picture'
this.focus.resume();
assert.ok(this.focus.resumeContinuousFocus.called);
assert.ok(!this.focus.paused);
});
});
suite('Focus#isTouchFocusSupported()', function() {
setup(function() {
this.focus.mozCamera.capabilities = {};
});
test('it returns false if touch to focus not supported by hardware', function() {
this.focus.mozCamera.capabilities.maxFocusAreas = 0;
this.focus.mozCamera.capabilities.maxMeteringAreas = 0;
assert.ok(!this.focus.isTouchFocusSupported());
});
test('it returns true if touch to focus supported by hardware', function() {
this.focus.mozCamera.capabilities.maxFocusAreas = 4;
this.focus.mozCamera.capabilities.maxMeteringAreas = 4;
assert.ok(this.focus.isTouchFocusSupported());
});
});
suite('Focus#isFaceDetectionSupported()', function() {
test('it returns true if hardware supports face detection', function() {
this.focus.mozCamera.capabilities = {
maxDetectedFaces: 5
};
this.focus.mozCamera.startFaceDetection = sinon.stub();
assert.ok(this.focus.isFaceDetectionSupported() === true);
});
test('it returns false if hardware doesn\'t support face detection', function() {
this.focus.mozCamera.capabilities = {
maxDetectedFaces: 0
};
this.focus.mozCamera.startFaceDetection = undefined;
assert.ok(this.focus.isFaceDetectionSupported() === false);
});
});
suite('Focus#updateFocusArea()', function() {
setup(function() {
this.focus.focus = sinon.spy();
this.focus.stopContinuousFocus = sinon.spy();
this.focus.suspendFaceDetection = sinon.spy();
this.focus.onAutoFocusChanged = sinon.spy();
});
test('it returns without doing anything if touch to focus disabled', function() {
this.focus.touchFocus = false;
this.focus.updateFocusArea();
assert.ok(!this.focus.stopContinuousFocus.called);
assert.ok(!this.focus.suspendFaceDetection.calledWith(10000));
assert.ok(!this.focus.mozCamera.setFocusAreas.called);
assert.ok(!this.focus.mozCamera.setMeteringAreas.called);
assert.ok(!this.focus.focus.called);
assert.ok(!this.focus.onAutoFocusChanged.called);
});
test('it updates focus area if touch to focus is enabled', function() {
this.focus.touchFocus = true;
this.focus.updateFocusArea();
assert.ok(this.focus.onAutoFocusChanged.calledWith('focusing'));
assert.ok(this.focus.stopContinuousFocus.called);
assert.ok(this.focus.suspendFaceDetection.calledWith(10000));
assert.ok(this.focus.mozCamera.setFocusAreas.called);
assert.ok(this.focus.mozCamera.setMeteringAreas.called);
assert.ok(this.focus.focus.called);
});
test('Should call focus callback with fail state if updating the focus area fails', function() {
this.focus.touchFocus = true;
var onFocused = sinon.spy();
this.focus.updateFocusArea(null, onFocused);
assert.ok(this.focus.focus.called);
this.focus.focus.callArgWith(0, 'failed');
assert.ok(onFocused.calledWith('failed'));
});
});
suite('Focus#resumeCamera', function() {
setup(function() {
this.sandbox.spy(this.focus, 'onAutoFocusChanged');
});
teardown(function() {
this.sandbox.restore();
});
test('should call onAutoFocusChanged after pause', function() {
this.focus.focusState = 'focusing';
this.focus.pause();
this.focus.updateFocusState('focusing');
assert.ok(this.focus.onAutoFocusChanged.calledWith('focusing'));
});
});
});

View file

@ -0,0 +1,43 @@
require('/shared/js/format.js');
suite('Format Timer Unit Tests', function() {
/*global asyncStorage*/
'use strict';
var subject;
suiteSetup(function(done) {
requirejs([
'lib/dcf'
], function(dcf) {
subject = dcf;
done();
});
});
suite('#createDCFFilename', function() {
setup(function() {
this.sandbox = sinon.sandbox.create();
var stub = this.sandbox.stub(asyncStorage, 'getItem');
subject.init();
// Trigger the callback
stub.getCall(0).args[1]({
file: 'moz',
dir: '1'
});
});
test('=> pads the filename', function() {
var result;
var storage = {
get: function(filename) {
result = filename;
return {};
}
};
subject.createDCFFilename(storage, 'image');
assert.equal(result, 'DCIM/1MZLLA/IMG_0moz.jpg');
});
});
});

View file

@ -0,0 +1,64 @@
suite('lib/picture-sizes/format-picture-sizes', function() {
/*jshint maxlen:false*/
'use strict';
suiteSetup(function(done) {
var self = this;
requirejs(['lib/format-picture-sizes'], function(formatPictureSizes) {
self.formatPictureSizes = formatPictureSizes;
done();
});
});
setup(function() {
this.sizes = [
{
height: 1536,
width: 2048
},
{
height: 1200,
width: 1600
},
{
height: 240,
width: 320
},
{
height: 480,
width: 640
},
{
height: 288,
width: 352
}
];
this.options = this.formatPictureSizes(this.sizes);
});
test('Should be sorted by resolution', function() {
assert.equal(this.options[0].key, '2048x1536');
assert.equal(this.options[4].key, '320x240');
});
test('Should exlude given keys', function() {
var options = this.formatPictureSizes(this.sizes, { exclude: { keys: ['2048x1536'] }});
var found = options.some(function(size) { return size.key === '2048x1536'; });
assert.isFalse(found);
});
test('Should exlude given aspects', function() {
var options = this.formatPictureSizes(this.sizes, { exclude: { aspects: ['11:9'] }});
var found = options.some(function(size) { return size.data.aspect === '11:9'; });
assert.isFalse(found);
});
test('Should not include sizes above the `maxPixelSize` option', function() {
var maxPixelSize = 307200;
var options = this.formatPictureSizes(this.sizes, { maxPixelSize: maxPixelSize });
var someGreater = options.some(function(item) { return item.pixelSize > maxPixelSize; });
assert.isFalse(someGreater, 'should be none greater than: ' + maxPixelSize);
});
});

View file

@ -0,0 +1,155 @@
suite('lib/format-recorder-profiles', function() {
/*jshint maxlen:false*/
'use strict';
suiteSetup(function(done) {
var self = this;
requirejs(['lib/format-recorder-profiles'], function(formatRecorderProfiles) {
self.formatRecorderProfiles = formatRecorderProfiles;
done();
});
});
setup(function() {
this.profiles = {
'low': {
format: '3gp',
video: {
codec: 'mpeg4sp',
bitrate: 128000,
framerate: 15,
width: 320,
height: 240
},
audio: {
codec: 'amrnb',
bitrate: 12200,
samplerate: 8000,
channels: 1
}
},
'high': {
format: 'mp4',
video: {
codec: 'h264',
bitrate: 12000000,
framerate: 30,
width: 1920,
height: 1080
},
audio: {
codec: 'aac',
bitrate: 96000,
samplerate: 48000,
channels: 1
}
},
'cif': {
format: 'mp4',
video: {
codec: 'h264',
bitrate: 1200000,
framerate: 30,
width: 352,
height: 288
},
audio: {
codec: 'aac',
bitrate: 96000,
samplerate: 48000,
channels: 1
},
key: 'cif'
},
'480p': {
format: 'mp4',
video: {
codec: 'h264',
bitrate: 5000000,
framerate: 30,
width: 720,
height: 480
},
audio: {
codec: 'aac',
bitrate: 96000,
samplerate: 48000,
channels: 1
}
},
'720p': {
format: 'mp4',
video: {
codec: 'h264',
bitrate: 8000000,
framerate: 30,
width: 1280,
height: 720
},
audio: {
codec: 'aac',
bitrate: 96000,
samplerate: 48000,
channels: 1
}
},
'1080p': {
format: 'mp4',
video: {
codec: 'h264',
bitrate: 12000000,
framerate: 30,
width: 1920,
height: 1080
},
audio: {
codec: 'aac',
bitrate: 96000,
samplerate: 48000,
channels: 1
}
}
};
this.profiles.default = this.profiles['720p'];
this.result = this.formatRecorderProfiles(this.profiles, {
exclude: ['high', 'low', 'default']
});
});
test('Should have formatted title string', function() {
var items = this.result;
assert.equal(items[0].title, '1080p 1920x1080 16:9');
assert.equal(items[1].title, '720p 1280x720 16:9');
assert.equal(items[2].title, '480p 720x480 3:2');
assert.equal(items[3].title, 'cif 352x288 11:9');
});
test('Should be sorted by pixel count', function() {
var items = this.result;
var first = items[0];
var last = items[items.length - 1];
assert.equal(first.key, '1080p');
assert.equal(last.key, 'cif');
});
test('Should exclude given keys', function() {
var exclude = ['default', 'low', 'high', 'other'];
var items = this.formatRecorderProfiles(this.profiles, { exclude: exclude });
var found = items.some(function(item) { return exclude.indexOf(item.key) > -1; });
assert.isFalse(found);
assert.equal(items.length, 4);
});
test('Should store the raw profile', function() {
assert.equal(this.result[0].raw, this.profiles['1080p']);
});
test('Should store the `key`', function() {
assert.equal(this.result[0].key, '1080p');
});
});

View file

@ -0,0 +1,27 @@
/*
Formattimer Test.
*/
'use strict';
require('/shared/js/format.js');
suite('Format Timer Unit Tests', function() {
var subject;
suiteSetup(function(done) {
requirejs([
'lib/format-timer'
], function(formattimer) {
subject = formattimer;
done();
});
});
suite('#exports', function() {
test('=> pads minutes and seconds', function() {
assert.equal(subject(0), '00:00');
assert.equal(subject(60000), '01:00');
assert.equal(subject(1000000), '16:40');
});
});
});

View file

@ -0,0 +1,74 @@
/*jshint maxlen:false*/
'use strict';
suite('geolocation', function() {
var GeoLocation;
suiteSetup(function(done) {
requirejs(['lib/geo-location'], function(geolocation) {
GeoLocation = geolocation;
done();
});
});
setup(function() {
this.geolocation = new GeoLocation();
sinon.stub(navigator.geolocation, 'watchPosition');
sinon.stub(navigator.geolocation, 'clearWatch');
});
teardown(function() {
navigator.geolocation.watchPosition.restore();
navigator.geolocation.clearWatch.restore();
});
suite('GeoLocation#watch()', function() {
test('Should begin watching geolocation position if' +
'no watcher currently in place', function() {
this.geolocation.watch();
assert.isTrue(navigator.geolocation.watchPosition.calledOnce);
});
test('Should begin watching geolocation position if' +
'no watcher currently in place', function() {
this.geolocation.watcher = 1;
this.geolocation.watch();
assert.isFalse(navigator.geolocation.watchPosition.called);
});
});
suite('GeoLocation#stopWatching()', function() {
test('Should call navigator.geolocation.clearWatch passing' +
'a reference to the watcher', function() {
this.geolocation.watcher = 1;
this.geolocation.stopWatching();
assert.isTrue(navigator.geolocation.clearWatch.calledWith(1));
});
test('Should unset the watcher', function() {
this.geolocation.watcher = 1;
this.geolocation.stopWatching();
assert.ok(!this.geolocation.watcher);
});
});
suite('GeoLocation#setPosition()', function() {
test('Should store passed geolocation object on instance', function() {
this.geolocation.setPosition({
timestamp: 1234,
coords: {
latitude: 1,
longitude: 2,
altitude: 3
}
});
var position = this.geolocation.position;
assert.ok(position.timestamp === 1234);
assert.ok(position.latitude === 1);
assert.ok(position.longitude === 2);
assert.ok(position.altitude === 3);
});
});
});

View file

@ -0,0 +1,191 @@
suite('lib/pinch', function() {
'use strict';
suiteSetup(function(done) {
var self = this;
requirejs([
'lib/pinch'
], function(Pinch) {
self.Pinch = Pinch;
done();
});
});
setup(function() {
this.el = {
addEventListener: sinon.spy(),
removeEventListener: sinon.spy()
};
this.pinch = new this.Pinch(this.el);
this.pinch.emit = sinon.spy();
});
suite('Pinch()', function() {
test('Should attach to specified element', function() {
assert.isTrue(this.el.addEventListener.called);
});
});
suite('Pinch#attach()', function() {
test('Should attach touchstart to specified element', function() {
this.pinch.attach(this.el);
assert.isTrue(this.el.addEventListener.calledWith('touchstart',
this.pinch.onTouchStart));
});
});
suite('Pinch#detach()', function() {
test('Should detach touchstart from specified element', function() {
this.pinch.detach(this.el);
assert.isTrue(this.el.removeEventListener.calledWith('touchstart',
this.pinch.onTouchStart));
});
});
suite('Pinch#onTouchStart()', function() {
setup(function() {
this.pinch.isPinching = false;
});
test('Should NOT emit `started` for one touch', function() {
this.pinch.onTouchStart({
touches: [{}]
});
assert.isFalse(this.pinch.emit.calledWith('started'));
});
test('Should emit `started` for two touches', function() {
this.pinch.onTouchStart({
touches: [{}]
});
this.pinch.onTouchStart({
touches: [{}, {}]
});
assert.isTrue(this.pinch.isPinching);
assert.isTrue(this.pinch.emit.calledWith('started'));
});
test('Should remember last two touches for two touches', function() {
var touchA = { identifier: 0 };
var touchB = { identifier: 1 };
this.pinch.onTouchStart({
touches: [touchA, touchB]
});
assert.equal(this.pinch.lastTouchA, touchA);
assert.equal(this.pinch.lastTouchB, touchB);
});
});
suite('Pinch#onTouchMove()', function() {
setup(function() {
this.pinch.isPinching = false;
});
test('Should NOT emit `change` for one touch', function() {
this.pinch.onTouchStart({
touches: [{}]
});
this.pinch.onTouchMove({
touches: [{}]
});
assert.isFalse(this.pinch.emit.calledWith('changed'));
});
test('Should emit `change` for two touches', function() {
this.pinch.onTouchStart({
touches: [{}]
});
this.pinch.onTouchStart({
touches: [{}, {}]
});
this.pinch.onTouchMove({
touches: [{}, {}]
});
assert.isTrue(this.pinch.emit.calledWith('changed'));
});
test('Should track touch "A" for two touches', function() {
var touchA1 = { identifier: 0 };
var touchA2 = { identifier: 0 };
this.pinch.onTouchStart({
touches: [touchA1, {}]
});
assert.equal(this.pinch.lastTouchA, touchA1);
this.pinch.onTouchMove({
touches: [touchA2, {}]
});
assert.equal(this.pinch.lastTouchA, touchA2);
});
test('Should track touch "B" for two touches', function() {
var touchB1 = { identifier: 1 };
var touchB2 = { identifier: 1 };
this.pinch.onTouchStart({
touches: [{}, touchB1]
});
assert.equal(this.pinch.lastTouchB, touchB1);
this.pinch.onTouchMove({
touches: [{}, touchB2]
});
assert.equal(this.pinch.lastTouchB, touchB2);
});
test('Should calculate delta for two touches', function() {
var touchA1 = { identifier: 0, pageX: -1, pageY: -1 };
var touchA2 = { identifier: 0, pageX: -2, pageY: -2 };
var touchB1 = { identifier: 1, pageX: 1, pageY: 1 };
var touchB2 = { identifier: 1, pageX: 2, pageY: 2 };
this.pinch.onTouchStart({
touches: [touchA1, touchB1]
});
this.pinch.onTouchMove({
touches: [touchA2, touchB2]
});
assert.equal(this.pinch.emit.args[1][1].toFixed(4), '2.8284');
});
});
suite('Pinch#onTouchEnd()', function() {
setup(function() {
this.pinch.isPinching = false;
});
test('Should NOT emit `ended` for one touch', function() {
this.pinch.onTouchStart({
touches: [{}]
});
this.pinch.onTouchEnd({
touches: []
});
assert.isFalse(this.pinch.emit.calledWith('ended'));
});
test('Should emit `ended` for two touches', function() {
this.pinch.onTouchStart({
touches: [{}, {}]
});
this.pinch.onTouchEnd({
touches: [{}]
});
assert.isFalse(this.pinch.isPinching);
assert.isTrue(this.pinch.emit.calledWith('ended'));
});
});
});

View file

@ -0,0 +1,156 @@
suite('lib/setting-alias', function() {
/*jshint maxlen:false*/
'use strict';
suiteSetup(function(done) {
var self = this;
requirejs([
'lib/setting',
'lib/setting-alias',
], function(Setting, SettingAlias) {
self.Setting = Setting;
self.SettingAlias = SettingAlias;
done();
});
});
setup(function() {
var self = this;
// We use this object to store state
// as nested Suites and tests' context
// inherits from this context.
this.state = {};
this.state.value = 'a';
this.settings = {
a: new this.Setting({ key: 'a' }),
b: new this.Setting({ key: 'b' })
};
this.alias = new this.SettingAlias({
key: 'my-alias',
settings: this.settings,
get: function() {
return this.settings[self.state.value];
}
});
});
suite('SettingAlias()', function() {
test('It stores `key` and `settings` options', function() {
assert.equal(this.alias.key, 'my-alias');
assert.equal(this.alias.settings, this.settings);
});
});
suite('SettingAlias#current()', function() {
test('It returns the current setting', function() {
assert.equal(this.alias.current().key, 'a');
this.state.value = 'b';
assert.equal(this.alias.current().key, 'b');
});
});
suite('SettingAlias#on()', function() {
test('Should fire callback when *current* setting emits event', function() {
var callback = sinon.spy();
this.alias.on('event', callback);
this.settings.a.fire('event');
this.settings.b.fire('event');
sinon.assert.calledOnce(callback);
});
test('Should pass arguments to the callback', function() {
var callback = sinon.spy();
this.alias.on('event', callback);
this.settings.a.fire('event', 'a', 'b');
assert.ok(callback.calledWith('a', 'b'));
});
});
suite('SettingAlias#off()', function() {
test('it calls the callback once', function() {
var callback = sinon.spy();
this.alias.on('event', callback);
this.settings.a.fire('event');
sinon.assert.calledOnce(callback);
});
test('It only removes the listeners that match the callback given', function() {
var callback1 = sinon.spy();
var callback2 = sinon.stub();
this.alias.on('event', callback1);
this.alias.on('event', callback2);
this.settings.a.fire('event');
sinon.assert.calledOnce(callback1);
sinon.assert.calledOnce(callback2);
callback1.reset();
callback2.reset();
this.alias.off('event', callback1);
this.settings.a.fire('event');
sinon.assert.calledOnce(callback2);
sinon.assert.notCalled(callback1);
});
});
suite('SettingAlias#get()', function() {
test('It calls the underlying Setting\'s `.get()` method', function() {
assert.equal(this.alias.get('key'), 'a');
this.state.value = 'b';
assert.equal(this.alias.get('key'), 'b');
});
});
test('It forwards the setting mathod calls onto the current setting', function() {
sinon.stub(this.settings.a);
sinon.stub(this.settings.b);
this.alias.filterOptions();
this.alias.resetOptions();
this.alias.supported();
this.alias.selected();
this.alias.select();
this.alias.next();
this.alias.get();
this.alias.set();
sinon.assert.calledOnce(this.settings.a.filterOptions);
sinon.assert.calledOnce(this.settings.a.resetOptions);
sinon.assert.calledOnce(this.settings.a.supported);
sinon.assert.calledOnce(this.settings.a.selected);
sinon.assert.calledOnce(this.settings.a.select);
sinon.assert.calledOnce(this.settings.a.next);
sinon.assert.calledOnce(this.settings.a.get);
sinon.assert.calledOnce(this.settings.a.set);
// Change state
this.state.value = 'b';
this.alias.filterOptions();
this.alias.resetOptions();
this.alias.supported();
this.alias.selected();
this.alias.select();
this.alias.next();
this.alias.get();
this.alias.set();
sinon.assert.calledOnce(this.settings.b.filterOptions);
sinon.assert.calledOnce(this.settings.b.resetOptions);
sinon.assert.calledOnce(this.settings.b.supported);
sinon.assert.calledOnce(this.settings.b.selected);
sinon.assert.calledOnce(this.settings.b.select);
sinon.assert.calledOnce(this.settings.b.next);
sinon.assert.calledOnce(this.settings.b.get);
sinon.assert.calledOnce(this.settings.b.set);
});
});

View file

@ -0,0 +1,409 @@
suite('lib/setting', function() {
/*jshint maxlen:false*/
'use strict';
suiteSetup(function(done) {
var self = this;
requirejs(['lib/setting'], function(Setting) {
self.Setting = Setting;
done();
});
});
setup(function() {
this.storage = {
getItem: sinon.stub(),
setItem: sinon.stub()
};
});
suite('Setting()', function() {
setup(function() {
this.sandbox = sinon.sandbox.create();
});
teardown(function() {
this.sandbox.restore();
});
test('Should store the key on self', function() {
var setting = new this.Setting({ key: 'mykey', options: [] });
assert.ok(setting.key === 'mykey');
});
test('Should save when selection is change, only if marked as `persistent`', function() {
var on = this.sandbox.spy(this.Setting.prototype, 'on');
var settings1 = new this.Setting({
key: 'key',
persistent: true,
options: []
});
assert.ok(on.calledWith('change:selected', settings1.save));
on.reset();
var settings2 = new this.Setting({ key: 'key', options: [] });
assert.ok(!on.calledWith('change:selected', settings2.save));
});
test('Should default to selecting first option if ' +
'`selected` is undefined', function() {
var setting = new this.Setting({
key: 'key',
options: [
{ key: 'a' },
{ key: 'b' }
]
});
assert.ok(setting.get('selected') === 'a');
});
test('Should flag `options.defined` if options initially defined', function() {
var setting = new this.Setting({
key: 'key',
options: [
{ key: 'a' },
{ key: 'b' }
]
});
assert.isTrue(setting.options.defined);
setting = new this.Setting({
key: 'key',
options: []
});
assert.isFalse(setting.options.defined);
});
});
suite('Setting#select()', function() {
setup(function() {
this.setting = new this.Setting({
key: 'key',
options: [
{ key: 'a' },
{ key: 'b' }
]
});
});
test('Should select the first option if no key match', function() {
this.setting.select('b');
assert.ok(this.setting.get('selected') === 'b');
// Fallback to first
this.setting.select('c');
assert.ok(this.setting.get('selected') === 'a');
this.setting.select(3);
assert.ok(this.setting.get('selected') === 'a');
});
});
suite('Setting#resetOptions()', function() {
setup(function() {
this.setting = new this.Setting({
selected: 'b',
options: [
{ key: 'a' },
{ key: 'b' },
{ key: 'c' }
]
});
});
test('Should update the `selected` option to first if no new options match selected', function() {
this.setting.resetOptions([
{ key: 'd' },
{ key: 'e' },
{ key: 'f' }
]);
assert.equal(this.setting.selected('key'), 'd');
});
test('Should maintain the selected key if a key of new options matches', function() {
this.setting.resetOptions([
{ key: 'c' },
{ key: 'b' },
{ key: 'a' }
]);
assert.equal(this.setting.selected('key'), 'b');
});
test('Should maintain the order', function() {
var options;
this.setting.resetOptions([
{ key: 'a' },
{ key: 'b' },
{ key: 'c' }
]);
options = this.setting.get('options');
assert.equal(options[0].key, 'a');
assert.equal(options[1].key, 'b');
assert.equal(options[2].key, 'c');
this.setting.resetOptions([
{ key: 'c' },
{ key: 'b' },
{ key: 'a' }
]);
options = this.setting.get('options');
assert.equal(options[0].key, 'c');
assert.equal(options[1].key, 'b');
assert.equal(options[2].key, 'a');
});
test('Should not fire a `change` event', function() {
var spy = sinon.spy();
this.setting.on('change', spy);
this.setting.resetOptions([{ key: 'c' }]);
assert.isFalse(spy.called);
});
});
suite('Setting#filterOptions()', function() {
setup(function() {
this.setting = new this.Setting({
key: 'key',
selected: 'b',
options: [
{ key: 'a' },
{ key: 'b' },
{ key: 'c' }
]
});
});
test('Should filter down the available options', function() {
this.setting.filterOptions(['a', 'b']);
var available = this.setting.get('options');
var found = available.some(function(option) { return option.key === 'c'; });
assert.isFalse(found, 'c should not longer be present');
});
test('Should have no available options if argument is falsy', function() {
var available;
this.setting.filterOptions(null);
available = this.setting.get('options');
assert.equal(available.length, 0);
this.setting.filterOptions(undefined);
available = this.setting.get('options');
assert.equal(available.length, 0);
this.setting.filterOptions(false);
available = this.setting.get('options');
assert.equal(available.length, 0);
});
test('Should always filter the original config', function() {
var available;
this.setting.filterOptions(['a', 'b']);
available = this.setting.get('options');
assert.equal(available.length, 2);
assert.equal(available[0].key, 'a');
assert.equal(available[1].key, 'b');
this.setting.filterOptions(['c']);
available = this.setting.get('options');
assert.equal(available.length, 1);
assert.equal(available[0].key, 'c');
});
test('Should maintain `selected` if in filter list', function() {
this.setting.filterOptions(['c', 'b']);
assert.equal(this.setting.selected('key'), 'b');
});
test('Should update the `selected` match first if not in filter list', function() {
this.setting.filterOptions(['c', 'a']);
assert.equal(this.setting.selected('key'), 'a');
});
test('Should fire a `change` event', function() {
var spy = sinon.spy();
this.setting.on('change', spy);
this.setting.filterOptions(['c']);
assert.isTrue(spy.called);
});
test('Should cope with keys that aren\'t defined', function() {
this.setting.filterOptions(['a', 'c', 'jibberish']);
var options = this.setting.get('options');
assert.equal(options.length, 2);
assert.equal(options[0].key, 'a');
assert.equal(options[1].key, 'c');
});
});
suite('Setting#selected()', function() {
setup(function() {
this.setting = new this.Setting({
key: 'key',
options: [
{ key: 'a', title: 'i am a' },
{ key: 'b', title: 'i am b' },
{ key: 'c', title: 'i am c' }
]
});
});
test('Should return the currently selected option', function() {
var selected = this.setting.selected();
assert.ok(selected.title === 'i am a');
this.setting.select('b');
selected = this.setting.selected();
assert.ok(selected.title === 'i am b');
});
test('Should return the given key from the selected option', function() {
var title = this.setting.selected('title');
assert.ok(title === 'i am a');
this.setting.select('b');
var key = this.setting.selected('key');
assert.ok(key === 'b');
});
test('Should return `undefined` if no available options', function() {
this.setting.filterOptions([]);
assert.equal(this.setting.selected(), undefined);
assert.equal(this.setting.selected('key'), undefined);
});
});
suite('Setting#next()', function() {
setup(function() {
this.setting = new this.Setting({
key: 'key',
options: [
{ key: 'a' },
{ key: 'b' },
{ key: 'c' }
]
});
});
test('Should set the `selected` value to the next option', function() {
this.setting.next();
assert.ok(this.setting.selected().key === 'b');
this.setting.next();
assert.ok(this.setting.selected().key === 'c');
this.setting.next();
assert.ok(this.setting.selected().key === 'a');
});
});
suite('Setting#supported()', function() {
setup(function() {
this.config = {
title: 'My title',
options: [{ key: 'a' }, { key: 'b' }]
};
});
test('Should return false if disabled', function() {
var setting;
this.config.disabled = true;
setting = new this.Setting(this.config);
assert.isFalse(setting.supported());
this.config.disabled = false;
setting = new this.Setting(this.config);
assert.isTrue(setting.supported());
delete this.config.disabled;
setting = new this.Setting(this.config);
assert.isTrue(setting.supported());
});
test('Should return true if not disabled and has options', function() {
var setting;
setting = new this.Setting(this.config);
assert.isTrue(setting.supported());
this.config.options = [];
setting = new this.Setting(this.config);
assert.isFalse(setting.supported());
});
});
suite('Setting#fetch()', function() {
setup(function() {
this.setting = new this.Setting({
key: 'foo',
storage: this.storage
});
});
test('Should `select` silently the given key if one is in storage', function() {
sinon.spy(this.setting, 'selected');
this.setting.fetch();
assert.isFalse(this.setting.selected.called);
this.storage.getItem
.withArgs('setting:' + this.setting.key)
.returns('the-key');
this.setting.fetch();
assert.isFalse(this.setting.selected.calledWith('the-key'));
});
test('Should not fire a `change` event', function() {
var spy = sinon.spy();
this.storage.getItem
.withArgs('setting:' + this.setting.key)
.returns('the-key');
this.setting.on('change', spy);
this.setting.fetch();
assert.isFalse(spy.called);
});
});
suite('Setting#save()', function() {
setup(function() {
this.setting = new this.Setting({
key: 'foo',
storage: this.storage,
options: [
{ key: 'a' },
{ key: 'b' }
]
});
});
test('Should call `setItem` with the current selected option key', function() {
var key = 'setting:' + this.setting.key;
var value = this.setting.selected('key');
this.setting.save();
assert.isTrue(this.storage.setItem.calledWith(key, value));
});
});
});

View file

@ -0,0 +1,100 @@
suite('lib/settings', function() {
'use strict';
suiteSetup(function(done) {
var self = this;
requirejs([
'lib/settings',
'lib/setting'
], function(Settings, Setting) {
self.Settings = Settings;
self.Setting = Setting;
done();
});
});
setup(function() {
this.settings = new this.Settings({
setting1: {
some: 'stuff',
options: [
{ key: 'a' },
{ key: 'b' },
{ key: 'c' }
],
persistent: true
},
setting2: { some: 'stuff' },
setting3: { some: 'stuff' }
});
});
suite('Settings()', function() {
test('Should store each setting on self by key', function() {
assert.isTrue(this.settings.setting1 instanceof this.Setting);
assert.isTrue(this.settings.setting2 instanceof this.Setting);
assert.isTrue(this.settings.setting3 instanceof this.Setting);
});
});
suite('Settings#fetch()', function() {
setup(function() {
sinon.stub(this.settings.setting1, 'fetch');
sinon.stub(this.settings.setting2, 'fetch');
sinon.stub(this.settings.setting3, 'fetch');
});
test('Should call fetch() on each setting', function() {
this.settings.fetch();
assert.isTrue(this.settings.setting1.fetch.called);
assert.isTrue(this.settings.setting2.fetch.called);
assert.isTrue(this.settings.setting3.fetch.called);
});
});
suite('Settings#alias()', function() {
setup(function() {
sinon.spy(this.settings, 'SettingAlias');
this.settings.alias({
key: 'myAlias',
settings: {},
get: function() {}
});
});
teardown(function() {
this.settings.SettingAlias.restore();
});
test('Should call SettingAlias', function() {
assert.isTrue(this.settings.SettingAlias.called);
});
test('Should store the alias on self by key', function() {
assert.isDefined(this.settings.myAlias);
});
test('Should pass in the given key', function() {
var options = this.settings.SettingAlias.args[0][0];
assert.equal(options.key, 'myAlias');
});
});
suite('Settings#dontSave()', function() {
test('Should remove any save event handlers', function() {
var setting1 = this.settings.setting1;
var setting2 = this.settings.setting2;
var setting3 = this.settings.setting3;
sinon.spy(setting1, 'off');
sinon.spy(setting2, 'off');
sinon.spy(setting3, 'off');
this.settings.dontSave();
assert.isTrue(setting1.off.calledWith('change:selected', setting1.save));
assert.isTrue(setting2.off.calledWith('change:selected', setting2.save));
assert.isTrue(setting3.off.calledWith('change:selected', setting3.save));
});
});
});

View file

@ -0,0 +1,230 @@
require('/shared/test/unit/mocks/mock_audio.js')
var mocksHelperForSounds = new MocksHelper([
'Audio'
]).init();
suite('Sounds', function() {
/*jshint maxlen:false*/
'use strict';
/**
* Locals
*/
var Sounds;
var list = [
{
name: 'shutter',
url: 'resources/sounds/shutter.opus',
setting: 'camera.sound.enabled'
},
{
name: 'countdown',
url: 'resources/sounds/countdown.opus',
setting: 'camera.sound.enabled'
},
{
name: 'recordingStart',
url: 'resources/sounds/camcorder_start.opus',
setting: 'camera.sound.enabled'
},
{
name: 'recordingEnd',
url: 'resources/sounds/camcorder_end.opus',
setting: 'camera.sound.enabled'
}
];
mocksHelperForSounds.attachTestHelpers();
suiteSetup(function(done) {
requirejs(['lib/sounds'], function(sounds) {
Sounds = sounds;
done();
});
});
setup(function() {
this.sandbox = sinon.sandbox.create();
this.clock = sinon.useFakeTimers();
this.sounds = new Sounds();
// A sound to pass to APIs
this.mockSound = {
name: 'camera',
url: 'resources/sounds/shutter.opus',
setting: 'camera.sound.enabled'
};
// Keep reference of
// things we want to restore
this.backup = {
mozSettings: navigator.mozSettings
};
});
teardown(function() {
navigator.mozSettings = this.backup.mozSettings;
this.sandbox.restore();
this.clock.restore();
});
suite('Sounds()', function() {
test('Should have an empty items object', function() {
var items = this.sounds.items;
assert.ok(this.sounds.hasOwnProperty('items'));
assert.ok(typeof items === 'object');
assert.ok(Object.keys(items).length === 0);
});
});
suite('Sounds#add()', function() {
setup(function() {
// Stub `isEnabled` and call the callback with `true`
this.sandbox.stub(this.sounds, 'isEnabled');
this.sounds.isEnabled.callsArgWith(1, true);
// Simple stubs
this.sandbox.stub(this.sounds, 'setEnabled');
this.sandbox.stub(this.sounds, 'observeSetting');
});
test('Should add each of the sounds to the this.items hash', function() {
this.sounds.add(list[0]);
this.sounds.add(list[1]);
this.sounds.add(list[2]);
this.sounds.add(list[3]);
assert.ok(this.sounds.items.shutter);
assert.ok(this.sounds.items.countdown);
assert.ok(this.sounds.items.recordingStart);
assert.ok(this.sounds.items.recordingEnd);
});
test('Should call `isEnabled`', function() {
this.sounds.add(list[0]);
assert.ok(this.sounds.isEnabled.called);
});
test('Should call `setEnabled` with the result of `isEnabled`', function() {
var setEnabled = this.sounds.setEnabled;
this.sounds.add(list[0]);
assert.ok(setEnabled.calledWith(this.sounds.items.shutter, true));
});
});
suite('Sounds#isEnabled()', function() {
setup(function() {
var self = this;
// Mock object that mimicks
// mozSettings get API. Inside
// tests set this.mozSettingsGetResult
// define the result of the mock call.
navigator.mozSettings = {
createLock: function() { return this; },
get: function(key) {
var mozSettings = this;
setTimeout(function() {
var result = {};
result[key] = 'the-result';
mozSettings.onsuccess({
target: {
result: result
}
});
});
return this;
}
};
});
test('Should not error if navigator.mozSettings is undefined', function() {
navigator.mozSettings = undefined;
this.sounds.isEnabled(this.mockSound);
});
test('Should return the result from mozSettings API', function() {
this.sounds.isEnabled(this.mockSound, function(result) {
assert.ok(result === 'the-result');
});
// Move time forwards
// so the callback fires
this.clock.tick(1);
});
});
suite('Sounds#observeSetting()', function() {
setup(function() {
this.sandbox.stub(this.sounds, 'setEnabled');
navigator.mozSettings = {
addObserver: function(key, callback) {
this.callback = callback;
}
};
});
test('Should not error if navigator.mozSettings is undefined', function() {
navigator.mozSettings = undefined;
this.sounds.observeSetting(this.mockSound);
});
test('Should call setEnabled with the value passed' +
'to the observe callback', function() {
this.sounds.observeSetting(this.mockSound);
// Manually call the callback
// checking that `setEnabled` was
// called with the expected arguments
navigator.mozSettings.callback({ settingValue: true });
assert.ok(this.sounds.setEnabled.calledWith(this.mockSound, true));
});
});
suite('Sounds#createAudio()', function() {
test('Should return an Audio object with the given src', function() {
var url = this.mockSound.url;
var audio = this.sounds.createAudio(url);
assert.ok(audio instanceof window.Audio);
assert.ok(~audio.src.indexOf(url));
});
test('Should set the mozAudioChannel type', function() {
var audio = this.sounds.createAudio(this.mockSound.url);
assert.ok(audio.mozAudioChannelType === 'notification');
});
});
suite('Sounds#playSound()', function() {
setup(function() {
this.mockSound.audio = {
play: sinon.spy(),
cloneNode: function() { return this; }
};
});
test('Should *not* play the sound if it\'s not enabled', function() {
this.sounds.playSound(this.mockSound);
assert.ok(this.mockSound.audio.play.notCalled);
});
test('Should play the sound if it\'s enabled', function() {
this.mockSound.enabled = true;
this.sounds.playSound(this.mockSound);
assert.ok(this.mockSound.audio.play.called);
});
test('Should throw exception if sound is undefined', function() {
try {
this.sounds.playSound();
} catch (e) {
assert.ok('correctly threw exception');
return;
}
assert.ok(false);
});
});
});

View file

@ -0,0 +1,214 @@
suite('lib/storage', function() {
/*jshint maxlen:false*/
'use strict';
suiteSetup(function(done) {
var self = this;
requirejs([
'lib/storage'
], function(Storage) {
self.Storage = Storage;
done();
});
});
setup(function() {
this.clock = sinon.useFakeTimers();
this.sandbox = sinon.sandbox.create();
this.video = {};
this.video.addEventListener = sinon.spy();
this.video.delete = sinon.stub().returns(this.video);
this.picture = {};
this.picture.addEventListener = sinon.spy();
this.picture.removeEventListener = sinon.spy();
this.picture.delete = sinon.stub().returns(this.picture);
// Stub getDeviceStorage
if (!navigator.getDeviceStorage) { navigator.getDeviceStorage = function() {}; }
this.sandbox.stub(navigator, 'getDeviceStorage');
navigator.getDeviceStorage
.withArgs('pictures')
.returns(this.picture);
navigator.getDeviceStorage
.withArgs('videos')
.returns(this.video);
// Stub getDeviceStorages
if (!navigator.getDeviceStorages) { navigator.getDeviceStorages = function() {}; }
this.sandbox.stub(navigator, 'getDeviceStorages')
.withArgs('pictures').returns([this.picture])
.withArgs('videos').returns([this.video]);
// Stub mozSettings
navigator.mozSettings = {
addObserver: sinon.stub()
};
var options = {
dcf: {
init: sinon.spy(),
createDCFFilename: sinon.stub().callsArgWith(2, 'filename.file')
},
require: sinon.stub().callsArg(1)
};
// The test instance
this.storage = new this.Storage(options);
// Storage is a singleton. This forces reconfiguration for each suite
this.storage.configure();
});
teardown(function() {
this.clock.restore();
this.sandbox.restore();
});
suite('Storage()', function() {
test('Should listen for change events', function() {
assert.isTrue(this.picture.addEventListener.calledWith('change'));
assert.isTrue(navigator.mozSettings.addObserver.called);
});
test('Should set state when receiving change events with available reason', function() {
this.sandbox.stub(this.storage, 'setState');
this.sandbox.stub(this.storage, 'check');
this.storage.onStorageChange({
reason: 'available'
});
assert.isTrue(this.storage.setState.called);
assert.isTrue(this.storage.check.called);
});
test('Should set state when receiving change events with unavailable reason', function() {
this.sandbox.stub(this.storage, 'setState');
this.sandbox.stub(this.storage, 'check');
this.storage.onStorageChange({
reason: 'unavailable'
});
assert.isTrue(this.storage.setState.called);
assert.isTrue(this.storage.check.called);
});
test('Should set state when receiving change events with shared reason', function() {
this.sandbox.stub(this.storage, 'setState');
this.sandbox.stub(this.storage, 'check');
this.storage.onStorageChange({
reason: 'shared'
});
assert.isTrue(this.storage.setState.called);
assert.isTrue(this.storage.check.called);
});
test('Should not set state when receiving change events with unexpected reason', function() {
this.sandbox.stub(this.storage, 'setState');
this.sandbox.stub(this.storage, 'check');
this.storage.onStorageChange({
reason: 'foo'
});
assert.isFalse(this.storage.setState.called);
assert.isTrue(this.storage.check.called);
});
});
suite('Storage#addPicture()', function() {
setup(function() {
this.picture.addNamed = sinon.spy(function() { return this.addNamed.req; });
this.picture.addNamed.req = { result: '/path/to/picture.jpg' };
this.picture.get = sinon.spy(function() { return this.get.req; });
this.picture.get.req = { result: 'memory-backed-blob' };
this.callback = sinon.spy();
// Happy path
this.storage.addPicture('fake-blob', this.callback);
this.picture.addNamed.req.onsuccess({ target: this.picture.addNamed.req });
this.picture.get.req.onsuccess({ target: this.picture.get.req });
});
test('Should create a filename if one not given', function() {
assert.isTrue(this.storage.dcf.createDCFFilename.calledWith(this.picture, 'image'));
});
test('Should add the given blob to picture storage', function() {
assert.isTrue(this.picture.addNamed.calledWith('fake-blob', 'filename.file'));
});
test('Should refetch the memory-backed-blob', function() {
assert.isTrue(this.picture.get.calledWith('filename.file'));
});
test('Should callback passing the relative path, absolute path and memory-backed-blob', function() {
assert.isTrue(this.callback.calledWith(
null,
'filename.file',
'/path/to/picture.jpg',
'memory-backed-blob'));
});
});
suite('Settings#isSpace()', function() {
setup(function() {
var picture = this.picture;
var self = this;
this.event = { target: { result: 100 } };
this.picture.freeSpace = function() {
setTimeout(function() { picture.onsuccess(self.event); });
return picture;
};
});
test('Should return false if space < `maxFileSize` value', function() {
this.storage.maxFileSize = 50;
this.storage.isSpace(function(result) {
assert.isTrue(result);
});
this.clock.tick(1);
});
test('Should return true if space > `maxFileSize` value', function() {
this.storage.maxFileSize = 200;
this.storage.isSpace(function(result) {
assert.isFalse(result);
});
this.clock.tick(1);
});
});
suite('Settings#deletePicture()', function() {
test('Should call the deviceStorageAPI', function() {
this.storage.deletePicture('path/to/picture.jpg');
assert.isTrue(this.picture.delete.calledWith('path/to/picture.jpg'));
});
test('Should work when called out of context', function() {
var fn = this.storage.deletePicture;
fn('path/to/picture.jpg');
assert.isTrue(this.picture.delete.calledWith('path/to/picture.jpg'));
});
});
suite('Settings#deleteVideo()', function() {
test('Should call the deviceStorageAPI', function() {
this.storage.deleteVideo('path/video.3gp');
assert.isTrue(this.video.delete.calledWith('path/video.3gp'));
});
test('Should delete matching poster image', function() {
this.storage.deleteVideo('path/video.3gp');
assert.isTrue(this.picture.delete.calledWith('path/video.jpg'));
});
test('Should work when called out of context', function() {
var fn = this.storage.deleteVideo;
fn('path/video.3gp');
assert.isTrue(this.video.delete.calledWith('path/video.3gp'));
});
});
});

View file

@ -0,0 +1,67 @@
/*jshint maxlen:false*/
/*global req*/
'use strict';
suite('utils/string-utils', function() {
var StringUtils;
suiteSetup(function(done) {
var self = this;
this.modules = {};
requirejs([
'lib/string-utils'
], function(_StringUtils) {
StringUtils = self.modules.StringUtils = _StringUtils;
done();
});
});
setup(function() {
});
suite('toCamelCase', function() {
setup(function() {
});
test('Should convert hyphenated string to camel-case', function() {
var sourceStr = 'this-string-should-be-camel-cased';
var outputStr = StringUtils.toCamelCase(sourceStr);
assert.equal(outputStr, 'thisStringShouldBeCamelCased');
});
test('Should convert snake-case string to camel-case', function() {
var sourceStr = 'THIS_STRING_SHOULD_BE_CAMEL_CASED';
var outputStr = StringUtils.toCamelCase(sourceStr);
assert.equal(outputStr, 'thisStringShouldBeCamelCased');
});
});
suite('toHyphenate', function() {
setup(function() {
});
test('Should convert camel-case string to hyphenated', function() {
var sourceStr = 'thisStringShouldBeHyphenated';
var outputStr = StringUtils.toHyphenate(sourceStr);
assert.equal(outputStr, 'this-string-should-be-hyphenated');
});
});
suite('lastPathComponent', function() {
setup(function() {});
test('Should get the last path component', function() {
assert.equal(StringUtils.lastPathComponent('this/is/a/path'), 'path');
assert.equal(StringUtils.lastPathComponent('abc'), 'abc');
});
});
});

View file

@ -0,0 +1,12 @@
'use strict';
// Shim for non FXOS environments
if (!navigator.getDeviceStorage) {
navigator.getDeviceStorage = function() {};
}
// Load RequireJS and use same config as the app uses
requireApp('camera/bower_components/requirejs/index.js');
requireApp('camera/js/config/require.js');
require('/shared/test/unit/mocks/mocks_helper.js');

View file

@ -0,0 +1,281 @@
suite('views/controls', function() {
/*jshint maxlen:false*/
'use strict';
suiteSetup(function(done) {
var self = this;
requirejs([
'views/controls',
'drag'
], function(ControlsView, Drag) {
self.ControlsView = ControlsView;
self.Drag = Drag;
self.style = loadCss('/style/controls.css', function() { done(); });
});
});
suiteTeardown(function() {
this.style.remove();
});
function loadCss(url, done) {
var link = document.createElement('link');
link.type = 'text/css';
link.rel = 'stylesheet';
link.href = url;
link.onload = done;
document.head.appendChild(link);
return link;
}
setup(function() {
var self = this;
this.sandbox = sinon.sandbox.create();
this.image = {
classList: {
add: function() {}
}
};
window.URL = window.URL || {};
window.URL.createObjectURL = window.URL.createObjectURL || function() {};
window.URL.revokeObjectURL = window.URL.revokeObjectURL || function() {};
this.sandbox.stub(window.URL, 'createObjectURL').returns('<object-url>');
this.sandbox.stub(window.URL, 'revokeObjectURL');
this.sandbox.stub(window, 'Image', function() { return self.image; });
this.sandbox.spy(this.ControlsView.prototype, 'onSwitchTapped');
this.drag = sinon.createStubInstance(this.Drag);
this.view = new this.ControlsView({ drag: this.drag });
this.view.els.image = undefined;
this.classes = this.view.el.classList;
this.sandbox.stub(this.view.els.thumbnail);
this.sandbox.spy(this.view, 'set');
this.sandbox.spy(this.view, 'unset');
this.sandbox.spy(this.view, 'emit');
});
teardown(function() {
this.sandbox.restore();
});
suite('ControlsView#setThumbnail()', function() {
test('Should set the thumbnail image src to the object url', function() {
this.view.els.image = this.image;
this.view.setThumbnail('<blob>');
assert.equal(this.view.els.image.src, '<object-url>');
});
test('Should revokeObjectURL if image is already set', function() {
this.view.setThumbnail('<blob-1>');
sinon.assert.notCalled(window.URL.revokeObjectURL);
this.view.setThumbnail('<blob-2>');
sinon.assert.calledWith(window.URL.revokeObjectURL, '<object-url>');
});
test('Should set thumbnail to `true`', function() {
this.view.setThumbnail('<blob>');
sinon.assert.calledWith(this.view.set, 'thumbnail', true);
});
test('Should create an Image and add it to the thmbnail container', function() {
this.view.setThumbnail('<blob>');
sinon.assert.called(window.Image);
sinon.assert.called(this.view.els.thumbnail.appendChild, this.image);
});
});
suite('ControlsView#set()', function() {
test('Should add the class if only one argument is given', function() {
this.view.set('key');
assert.isTrue(this.classes.contains('key'));
});
test('Should add a key/value class if a truthy value is given', function() {
this.view.set('key', 'val');
assert.isTrue(this.classes.contains('key-val'));
});
test('Should remove the previous class when the value is changed', function() {
this.view.set('key', 'val');
assert.isTrue(this.classes.contains('key-val'));
this.view.set('key', 'val2');
assert.isFalse(this.classes.contains('key-val1'));
assert.isTrue(this.classes.contains('key-val2'));
});
test('Should only accept string keys', function() {
this.view.set(false);
assert.isFalse(this.classes.contains('false'));
this.view.set(0);
assert.isFalse(this.classes.contains('0'));
});
test('Should remove the class for any falsy values', function() {
this.view.set('key');
assert.isTrue(this.classes.contains('key'));
this.view.set('key', undefined);
assert.isFalse(this.classes.contains('key'));
this.view.set('key');
assert.isTrue(this.classes.contains('key'));
this.view.set('key', 0);
assert.isFalse(this.classes.contains('key'));
this.view.set('key');
assert.isTrue(this.classes.contains('key'));
this.view.set('key', null);
assert.isFalse(this.classes.contains('key'));
this.view.set('key');
assert.isTrue(this.classes.contains('key'));
this.view.set('key', '');
assert.isFalse(this.classes.contains('key'));
});
});
suite('ControlsView#setScreenReaderVisible()', function() {
test('hide the view from screen reader', function() {
this.view.setScreenReaderVisible(false);
assert.equal(this.view.el.getAttribute('aria-hidden'), 'true');
});
test('show the view to the screen reader', function() {
this.view.el.setAttribute('aria-hidden', true);
this.view.setScreenReaderVisible(true);
assert.equal(this.view.el.getAttribute('aria-hidden'), 'false');
});
});
suite('ControlsView#enable()', function() {
test('Should add an enabled class if no value is given', function() {
this.view.enable();
assert.isTrue(this.classes.contains('enabled'));
});
test('Should add a key-enabled class if key is given', function() {
this.view.enable('key');
assert.isTrue(this.classes.contains('key-enabled'));
});
test('Should remove \'disabled\' class', function() {
this.classes.add('disabled');
this.view.enable();
assert.isFalse(this.classes.contains('disabled'));
});
});
suite('ControlsView#disabled()', function() {
test('Should add an \'disables\' class if no value is given', function() {
this.view.disable();
assert.isTrue(this.classes.contains('disabled'));
});
test('Should add a key-enabled class if key is given', function() {
this.view.disable('key');
assert.isTrue(this.classes.contains('key-disabled'));
});
test('Should remove \'enabled\' class', function() {
this.classes.add('enabled');
this.view.disable();
assert.isFalse(this.classes.contains('enabled'));
});
});
suite('ControlsView#onSwitchTapped()', function() {
setup(function() {
this.event = {
preventDefault: sinon.spy(),
stopPropagation: sinon.spy()
};
this.spy = this.ControlsView.prototype.onSwitchTapped;
this.view.enable();
});
test('It emits a `modechanged` event', function() {
this.view.onSwitchTapped(this.event);
sinon.assert.calledWith(this.view.emit, 'modechanged');
});
test('It prevents default to stop the event becoming a click', function() {
this.view.onSwitchTapped(this.event);
sinon.assert.called(this.event.preventDefault);
});
test('It is debounced to defend against button bashing', function() {
this.view.setupSwitch();
var callback = this.drag.on.withArgs('tapped').args[0][1];
callback(this.event);
callback(this.event);
callback(this.event);
callback(this.event);
sinon.assert.calledOnce(this.spy);
});
});
suite('ControlsView#onSwitchSnapped()', function() {
setup(function() {
this.view.set('mode', 'picture');
});
test('It fires \'modechanged\' when the switch changes position', function() {
// Didn't change
this.view.onSwitchSnapped({ x: 'left' });
assert.isFalse(this.view.emit.calledWith('modechanged'));
// Changed
this.view.onSwitchSnapped({ x: 'right' });
assert.isTrue(this.view.emit.calledWith('modechanged'));
});
});
suite('ControlsView#suspendModeSwitch()', function() {
test('It ignores user input on switch when suspended', function() {
this.view.suspendModeSwitch(true);
assert.ok(this.view.set.calledWith('switch-toggle-disabled'));
});
test('It accepts user input on switch when unsuspended', function() {
this.view.suspendModeSwitch(false);
assert.ok(this.view.unset.calledWith('switch-toggle-disabled'));
});
});
test('The switch should appear in the video position when set before the view is in the DOM', function() {
var view = new this.ControlsView();
view.setMode('video');
view.appendTo(document.body);
assert.equal(view.drag.handle.el.style.transform, 'translate(64px, 0px)');
assert.equal(view.els.switch.getAttribute('data-l10n-id'),
'video-mode-button');
});
test('ControlsView#localize', function() {
var view = new this.ControlsView();
view.localize();
for (var el in view.elsL10n) {
assert.equal(view.els[el].getAttribute('data-l10n-id'), view.elsL10n[el]);
}
assert.equal(view.els.switch.getAttribute('data-l10n-id'),
'picture-mode-button');
});
test('ControlsView#setCaptureLabel', function() {
var view = new this.ControlsView();
[true, false].forEach(function(recording) {
view.setCaptureLabel(recording);
assert.equal(view.els.capture.getAttribute('data-l10n-id'),
recording ? 'stop-capture-button' : 'capture-button');
});
});
});

View file

@ -0,0 +1,36 @@
suite('views/face', function() {
/*jshint maxlen:false*/
'use strict';
suiteSetup(function(done) {
var self = this;
requirejs(['views/face'], function(FaceView) {
self.FaceView = FaceView;
done();
});
});
setup(function() {
var self = this;
// Shortcuts
this.view = new this.FaceView();
});
suite('FaceView#setPosition()', function() {
test('Should set top left css values', function() {
this.view.setPosition(42, 101);
assert.equal(this.view.el.style.left, '42px');
assert.equal(this.view.el.style.top, '101px');
});
});
suite('FaceView#setDiameter()', function() {
test('Should set width height css values', function() {
this.view.setDiameter(50);
assert.equal(this.view.el.style.width, '50px');
assert.equal(this.view.el.style.height, '50px');
});
});
});

View file

@ -0,0 +1,75 @@
suite('views/faces', function() {
/*jshint maxlen:false*/
'use strict';
suiteSetup(function(done) {
var self = this;
requirejs(['views/faces'], function(FacesView) {
self.FacesView = FacesView;
done();
});
});
setup(function() {
var self = this;
this.FaceView = sinon.spy();
this.FaceView.prototype = {
FaceView: sinon.spy(),
hide: sinon.spy(),
appendTo: sinon.spy(),
setPosition: sinon.spy(),
setDiameter: sinon.spy(),
show: sinon.spy()
};
// Shortcuts
this.view = new this.FacesView({
FaceView: this.FaceView
});
});
suite('FacesView#createFaces()', function() {
test('Should create Face Views for the given number of faces', function() {
this.view.createFaces(10);
assert.ok(this.view.faces.length === 10);
this.view.createFaces(5);
assert.ok(this.view.faces.length === 15);
});
});
suite('FacesView#renderFace()', function() {
test('Should show and configure the face view based on the face object', function() {
var face = {
x: 320,
y: 400,
diameter: 300
};
var faceView = new this.FaceView();
this.view.renderFace(face, faceView);
assert.ok(faceView.setPosition.called);
assert.ok(faceView.setDiameter.called);
assert.ok(faceView.show.called);
});
});
suite('FacesView#hideFaces()', function() {
test('Should call hide once for each face view', function() {
var faceView = {
hide: sinon.spy()
};
this.view.faces = [faceView, faceView, faceView];
this.view.hideFaces();
assert.ok(faceView.hide.calledThrice);
});
});
suite('FacesView#clear()', function() {
test('Should remove faces DOM elements and reset faces', function() {
this.view.el.removeChild = sinon.spy();
this.view.faces = ['face', 'face', 'face'];
this.view.clear();
assert.ok(this.view.el.removeChild.calledThrice);
assert.ok(this.view.faces.length === 0);
});
});
});

View file

@ -0,0 +1,45 @@
suite('views/hud', function() {
'use strict';
var view;
suiteSetup(function(done) {
var self = this;
requirejs(['views/hud'], function(HudView) {
self.HudView = HudView;
done();
});
});
setup(function() {
view = new this.HudView();
});
test('HudView#setFlashMode()', function() {
[{ title: 'flash-on', icon: 'flash-on' },
{ title: 'flash-off', icon: 'flash-off' },
{ title: 'flash-auto', icon: 'flash-auto' }].forEach(function(mode) {
view.setFlashMode(mode);
assert.equal(view.els.flash.dataset.icon, mode.icon);
assert.equal(view.els.flash.getAttribute('data-l10n-id'),
mode.title + '-button');
});
});
test('HudView#setCamera()', function() {
[{ title: 'toggle-camera-rear', icon: 'toggle-camera-rear' },
{ title: 'toggle-camera-front', icon: 'toggle-camera-front' }].forEach(
function(mode) {
view.setCamera(mode);
assert.equal(view.els.camera.dataset.icon, mode.icon);
assert.equal(view.els.camera.getAttribute('data-l10n-id'),
mode.title + '-button');
});
});
test('HudView#setMenuLabel()', function() {
view.els.settings.removeAttribute('data-l10n-id');
view.setMenuLabel();
assert.equal(view.els.settings.getAttribute('data-l10n-id'), 'menu-button');
});
});

View file

@ -0,0 +1,178 @@
suite('views/notification', function() {
'use strict';
suiteSetup(function(done) {
var self = this;
requirejs(['views/notification'], function(NotificationView) {
self.NotificationView = NotificationView;
done();
});
});
setup(function() {
this.view = new this.NotificationView();
this.clock = sinon.useFakeTimers();
});
suite('NotificationView#display()', function() {
suite('Temporary', function() {
test('Should add the item to the view', function() {
assert.equal(this.view.el.getAttribute('role'), 'presentation');
this.view.display({ text: 'l10nId' });
var child = this.view.el.children[0];
assert.ok(child);
assert.equal(child.getAttribute('role'), 'status');
assert.equal(child.getAttribute('aria-live'), 'assertive');
});
test('Should add `text` as textContent', function() {
this.view.display({ text: 'l10nId' });
var child = this.view.el.children[0];
assert.equal(child.firstChild.getAttribute('data-l10n-id'), 'l10nId');
assert.equal(child.getAttribute('role'), 'status');
assert.equal(child.getAttribute('aria-live'), 'assertive');
});
test('Should assign className to the item element', function() {
this.view.display({ text: 'l10nId', className: 'bar' });
var child = this.view.el.children[0];
assert.isTrue(child.classList.contains('bar'));
assert.equal(child.getAttribute('role'), 'status');
assert.equal(child.getAttribute('aria-live'), 'assertive');
});
test('Should remove the last non-persistent item that is in the way', function() {
this.view.display({ text: 'l10nId' });
var first = this.view.el.children[0];
assert.ok(first);
assert.equal(first.firstChild.getAttribute('data-l10n-id'), 'l10nId');
this.view.display({ text: 'l10nId2' });
var second = this.view.el.children[0];
assert.ok(second);
assert.equal(first.parentNode, null, 'not in dom');
assert.equal(second.firstChild.getAttribute('data-l10n-id'),
'l10nId2');
});
test('Should auto clear notification after 3s', function() {
this.view.display({ text: 'l10nId' });
var first = this.view.el.children[0];
assert.ok(first);
assert.equal(first.firstChild.getAttribute('data-l10n-id'), 'l10nId');
this.clock.tick(1000);
assert.ok(first.parentNode, 'is in dom');
this.clock.tick(1000);
assert.ok(first.parentNode, 'is in dom');
this.clock.tick(1000);
assert.equal(first.parentNode, null, 'not in dom');
});
test('Should not mutate the passed object', function() {
var options = { text: 'l10nId' };
this.view.display(options);
assert.equal(Object.keys(options).length, 1);
assert.equal(options.text, 'l10nId');
});
});
suite('Persistent', function() {
test('Should remain in the DOM until cleared', function() {
var id = this.view.display({ text: 'l10nId', persistent: true });
var el = this.view.el.children[0];
assert.ok(el.parentNode, 'is in dom');
this.clock.tick(1000);
assert.ok(el.parentNode, 'is in dom');
this.clock.tick(3000);
assert.ok(el.parentNode, 'is in dom');
this.view.clear(id);
assert.ok(!el.parentNode, 'not in dom');
});
test('Should hide to show temporary notifications, then return', function() {
var els = {};
this.view.display({ text: 'l10nId', persistent: true, className: 'persistent' });
els.persistent = this.view.el.querySelector('.persistent');
assert.ok(els.persistent.parentNode, 'should be in DOM');
assert.isFalse(els.persistent.classList.contains('hidden'));
this.view.display({ text: 'l10nId', className: 'temporary' });
els.temporary = this.view.el.querySelector('.temporary');
assert.ok(els.temporary.parentNode, 'should be in DOM');
assert.isTrue(els.persistent.classList.contains('hidden'));
this.clock.tick(1000);
assert.isTrue(els.persistent.classList.contains('hidden'));
this.clock.tick(3000);
assert.ok(!els.temporary.parentNode);
assert.isFalse(els.persistent.classList.contains('hidden'));
});
test('Should replace first persistent item with second', function() {
var els = {};
this.view.display({ text: 'l10nId', persistent: true, className: 'persistent1' });
els.persistent1 = this.view.el.querySelector('.persistent1');
assert.ok(els.persistent1.parentNode, 'should be in DOM');
this.view.display({ text: 'l10nId', persistent: true, className: 'persistent2' });
els.persistent2 = this.view.el.querySelector('.persistent2');
assert.ok(els.persistent2.parentNode, 'should be in DOM');
assert.ok(!els.persistent1.parentNode, 'should not be in DOM');
});
});
});
suite('NotificationView#clear()', function() {
test('Should be able to clear notifications early', function() {
var id = this.view.display({ text: 'l10nId' });
var el = this.view.el.children[0];
assert.ok(el.parentNode);
this.view.clear(id);
assert.equal(el.parentNode, null);
});
test('Should not throw when attempting to clear a notification twice', function() {
var id = this.view.display({ text: 'l10nId' });
var el = this.view.el.children[0];
assert.ok(el.parentNode);
this.view.clear(id);
assert.equal(el.parentNode, null);
this.view.clear(id);
});
test('Should show hidden persistent notification', function() {
var persistent = this.view.display({ text: 'l10nId', persistent: true });
var els = {};
els.persistent = this.view.el.children[0];
assert.ok(els.persistent.parentNode, 'is in the dom');
var temporary = this.view.display({ text: 'l10nId' });
assert.isTrue(els.persistent.classList.contains('hidden'), 'is hidden');
this.view.clear(temporary);
assert.isFalse(els.persistent.classList.contains('hidden'), 'is not hidden');
});
});
});

View file

@ -0,0 +1,52 @@
/*jshint maxlen:false*/
'use strict';
suite('views/overlay', function() {
suiteSetup(function(done) {
var self = this;
requirejs([
'views/overlay'
], function(OverlayView) {
self.OverlayView = OverlayView;
done();
});
});
setup(function() {
this.view = new this.OverlayView({
type: 'unavailable',
closable: false
});
});
suite('OverlayView#getOverlayData()', function() {
test('Should return correct data for \'unavailable\'', function() {
var output = this.view.getData('unavailable');
assert.equal(output.title, 'nocard2-title');
assert.equal(output.body, 'nocard3-text');
});
test('Should return correct data for \'nospace\'', function() {
var output = this.view.getData('nospace');
assert.equal(output.title, 'nospace2-title');
assert.equal(output.body, 'nospace2-text');
});
test('Should return correct data for \'shared\'', function() {
var output = this.view.getData('shared');
assert.equal(output.title, 'pluggedin2-title');
assert.equal(output.body, 'pluggedin2-text');
});
test('Should return correct data for \'request-fail\'', function() {
var output = this.view.getData('request-fail');
assert.equal(output.title, 'camera-unavailable-title');
assert.equal(output.body, 'camera-unavailable-text');
});
test('Should return false for unknown key', function() {
var output = this.view.getData('something-not-known');
assert.equal(output, false);
});
});
});

View file

@ -0,0 +1,95 @@
suite('views/preview-gallery', function() {
'use strict';
suiteSetup(function(done) {
var self = this;
requirejs(['views/preview-gallery'], function(previewGalleryView) {
self.previewGalleryView = previewGalleryView;
done();
});
});
setup(function() {
this.view = new this.previewGalleryView();
this.clock = sinon.useFakeTimers();
});
suite('previewGalleryView#showOptionsMenu()', function() {
test('Should show the option menu', function() {
this.view.showOptionsMenu();
assert.equal(this.view.optionsMenuContainer.children[0].getAttribute(
'data-type'), 'action');
});
});
suite('previewGalleryView#onFrameWheel', function() {
var mockWheelRight = {
type: 'wheel', deltaMode: 2, DOM_DELTA_PAGE: 2, deltaX: 1
}, mockWheelLeft = {
type: 'wheel', deltaMode: 2, DOM_DELTA_PAGE: 2, deltaX: -1
}, mockWheelDown = {
type: 'wheel', deltaMode: 2, DOM_DELTA_PAGE: 2, deltaY: 1
};
setup(function() {
sinon.spy(this.view, 'emit');
sinon.stub(this.view, 'onVideoPaused');
});
test('wheel right', function() {
this.view.onFrameWheel(mockWheelRight);
assert.isTrue(this.view.emit.calledWith('swipe', 'left'));
sinon.assert.notCalled(this.view.onVideoPaused);
});
test('wheel left', function() {
this.view.onFrameWheel(mockWheelLeft);
assert.isTrue(this.view.emit.calledWith('swipe', 'right'));
sinon.assert.notCalled(this.view.onVideoPaused);
});
test('wheel right when video is playing', function() {
this.view.videoPlaying = true;
this.view.onFrameWheel(mockWheelRight);
assert.isTrue(this.view.emit.calledWith('swipe', 'left'));
sinon.assert.calledOnce(this.view.onVideoPaused);
});
test('wheel down', function() {
this.view.onFrameWheel(mockWheelDown);
sinon.assert.notCalled(this.view.emit);
});
});
suite('previewGalleryView#updateCountText', function() {
setup(function() {
this.view.render();
});
test('Accessible labelling', function() {
var total = 1, current = 3;
this.view.updateCountText(current, total);
assert.equal(this.view.els.mediaFrame.getAttribute('data-l10n-id'),
'media-frame');
assert.deepEqual(JSON.parse(this.view.els.mediaFrame.getAttribute(
'data-l10n-args')), { total: total, current: current });
});
});
suite('Options menu', function() {
test('showOptionsMenu', function() {
assert.isFalse(this.view.el.classList.contains('action-menu'));
this.view.showOptionsMenu();
assert.ok(this.view.el.classList.contains('action-menu'));
});
test('hideOptionsMenu', function() {
this.view.optionsMenuContainer = document.createElement('div');
this.view.el.appendChild(this.view.optionsMenuContainer);
this.view.el.classList.add('action-menu');
this.view.hideOptionsMenu();
assert.isFalse(this.view.el.classList.contains('action-menu'));
});
});
});

View file

@ -0,0 +1,58 @@
suite('views/setting-options', function() {
'use strict';
suiteSetup(function(done) {
var self = this;
requirejs([
'views/setting-options'
], function(SettingOptionsView) {
self.SettingOptionsView = SettingOptionsView;
done();
});
});
setup(function() {
this.sandbox = sinon.sandbox.create();
this.view = new this.SettingOptionsView({
model: { on: sinon.spy(), get: sinon.spy() },
l10n: {
formatValue: function(id) {
return Promise.resolve(id);
},
}
});
this.view.els.ul = document.createElement('ul');
});
teardown(function() {
this.sandbox.restore();
});
suite('SettingOptionsView#renderOption()', function() {
var option = {
key: 'key',
title: 'title'
};
test('localizable not selected', function() {
this.view.renderOption(true, option);
var rendered = this.view.els.key;
assert.equal(rendered.getAttribute('role'), 'option');
assert.equal(rendered.getAttribute('data-l10n-id'), 'title');
assert.isNull(rendered.getAttribute('aria-selected'));
});
test('not localizable selected', function() {
// Make current option selected.
this.view.selectedKey = option.key;
this.view.renderOption(false, option);
var rendered = this.view.els.key;
assert.equal(rendered.getAttribute('role'), 'option');
assert.equal(rendered.textContent, 'title');
assert.equal(rendered.getAttribute('aria-selected'), 'true');
});
});
});

View file

@ -0,0 +1,49 @@
suite('views/setting', function() {
'use strict';
suiteSetup(function(done) {
var self = this;
requirejs([
'views/setting'
], function(SettingView) {
self.SettingView = SettingView;
done();
});
});
setup(function() {
this.sandbox = sinon.sandbox.create();
this.model = {
on: sinon.spy(),
selected: sinon.stub().returns({ title: 'title' }),
get: sinon.stub().returns({ title: 'title', value: 'value' })
};
this.model.get.withArgs('title').returns('title');
this.view = new this.SettingView({ model: this.model });
});
teardown(function() {
this.sandbox.restore();
});
suite('SettingView#render()', function() {
setup(function() {
this.view.l10n = {
formatValue: function(id) {
return Promise.resolve(id);
},
setAttributes: sinon.spy()
};
this.view.render();
});
test('render setting accessibility', function(done) {
assert.equal(this.view.el.getAttribute('role'), 'option');
Promise.resolve().then(() => {
assert.isTrue(this.view.l10n.setAttributes.calledWith(this.view.el,
'setting-option-title', { value: 'title' }));
done();
});
});
});
});

View file

@ -0,0 +1,78 @@
suite('views/settings', function() {
'use strict';
suiteSetup(function(done) {
var self = this;
requirejs([
'views/settings',
'views/setting-options'
], function(SettingsView, OptionsView) {
self.SettingsView = SettingsView;
self.OptionsView = OptionsView;
done();
});
});
setup(function() {
var self = this;
this.sandbox = sinon.sandbox.create();
var OptionsView = sinon.spy(function() {
var view = sinon.createStubInstance(self.OptionsView);
view.render.returns(view);
view.appendTo.returns(view);
view.on.returns(view);
return view;
});
this.view = new this.SettingsView({ OptionsView: OptionsView });
});
teardown(function() {
this.sandbox.restore();
});
suite('SettingsView#onClick()', function() {
test('Should not let clicks propagate', function() {
var spy = sinon.spy();
this.view.onClick({ stopPropagation: spy });
assert.isTrue(spy.called);
});
});
suite('SettingsView#showSetting()', function() {
setup(function() {
this.model = {};
this.sandbox.spy(this.view, 'showPane');
this.view.showSetting(this.model);
});
test('Should create and append a new OptionsView', function() {
assert.isTrue(this.view.OptionsView.called);
assert.isTrue(this.view.optionsView.appendTo.called);
});
test('Should pass the model into the OptionsView', function() {
var options = this.view.OptionsView.args[0][0];
assert.equal(options.model, this.model);
});
test('Should show pane 2', function() {
assert.isTrue(this.view.showPane.calledWith(2));
});
});
suite('SettingsView#goBack()', function() {
test('Should destroy the options view (sync)', function() {
var options = this.view.optionsView = { destroy: sinon.spy() };
this.view.goBack();
assert.isTrue(options.destroy.called);
});
test('Should switch back to pane 1', function() {
sinon.spy(this.view, 'showPane');
this.view.goBack();
assert.isTrue(this.view.showPane.calledWith(1));
});
});
});

View file

@ -0,0 +1,39 @@
suite('views/countdown', function() {
'use strict';
suiteSetup(function(done) {
var self = this;
requirejs(['views/countdown'], function(CountdownView) {
self.CountdownView = CountdownView;
done();
});
});
setup(function() {
this.countdown = new this.CountdownView();
this.countdown.emit = sinon.spy();
});
suite('CountdownView#set()', function() {
test('Should update the text content of the counter', function() {
this.countdown.set(1000);
assert.ok(this.countdown.el.innerHTML.indexOf(1000) > -1);
});
});
suite('CountdownView#reset()', function() {
test('Should clear the countdown text', function() {
this.countdown.set(1000);
assert.isTrue(this.countdown.el.innerHTML.indexOf('1000') > -1);
this.countdown.reset();
assert.isFalse(this.countdown.el.innerHTML.indexOf('1000') > -1);
});
test('Should remove `immanent` class', function() {
this.countdown.setImmanent(true);
assert.isTrue(this.countdown.el.classList.contains('immanent'));
this.countdown.reset();
assert.isFalse(this.countdown.el.classList.contains('immanent'));
});
});
});

View file

@ -0,0 +1,184 @@
suite('views/viewfinder', function() {
'use strict';
var SENSOR_ANGLE = 90;
suiteSetup(function(done) {
var self = this;
requirejs(['views/viewfinder'], function(ViewfinderView) {
self.ViewfinderView = ViewfinderView;
done();
});
});
setup(function() {
this.viewfinder = new this.ViewfinderView();
this.viewfinder.width = 300;
this.viewfinder.height = 500;
});
suite('ViewfinderView#updatePreview()', function() {
setup(function() {
sinon.stub(this.viewfinder, 'updatePreviewMetrics');
});
test('Should prefer this.scaleType if defined', function() {
var preview = { width: 1600, height: 900 };
var scaleType;
this.viewfinder.scaleType = 'fit';
this.viewfinder.updatePreview(preview, SENSOR_ANGLE);
scaleType = this.viewfinder.updatePreviewMetrics.args[0][3];
assert.equal(scaleType, 'fit');
this.viewfinder.scaleType = 'foobar';
this.viewfinder.updatePreview(preview, SENSOR_ANGLE);
scaleType = this.viewfinder.updatePreviewMetrics.args[1][3];
assert.equal(scaleType, 'foobar');
});
test('Should choose \'fill\' scaleType if preview aspect > container aspect', function() {
var scaleType;
this.viewfinder.scaleType = undefined;
this.viewfinder.updatePreview({ width: 1600, height: 900 }, SENSOR_ANGLE);
scaleType = this.viewfinder.updatePreviewMetrics.args[0][3];
assert.equal(scaleType, 'fill');
this.viewfinder.updatePreview({ width: 400, height: 300 }, SENSOR_ANGLE);
scaleType = this.viewfinder.updatePreviewMetrics.args[1][3];
assert.equal(scaleType, 'fit');
});
test('Should add negative scale style if `mirror` argument is true', function() {
var preview = { width: 1600, height: 900 };
this.viewfinder.updatePreview(preview, SENSOR_ANGLE, true);
assert.isTrue(this.viewfinder.els.videoContainer.style.transform.indexOf('scale(-1') === -1);
this.viewfinder.updatePreview(preview, SENSOR_ANGLE, false);
assert.isFalse(this.viewfinder.els.videoContainer.style.transform.indexOf('scale(-1') !== -1);
});
});
suite('ViewfinderView#updatePreviewMetrics()', function() {
setup(function() {
this.viewfinder.container = {
width: this.viewfinder.height,
height: this.viewfinder.width
};
this.container = this.viewfinder.container;
});
test('Should set a \'scale-type\' attribute', function() {
var previewSize = { width: 400, height: 300 };
this.viewfinder.updatePreviewMetrics(previewSize, SENSOR_ANGLE, false, 'fill');
assert.equal(this.viewfinder.el.getAttribute('scale-type'), 'fill');
this.viewfinder.updatePreviewMetrics(previewSize, SENSOR_ANGLE, false, 'fit');
assert.equal(this.viewfinder.el.getAttribute('scale-type'), 'fit');
});
suite('\'fill\'', function() {
test('Should match frame width to container width when preview aspect > container aspect', function() {
var previewSize = { width: 1600, height: 900 };
// Run test-case
this.viewfinder.updatePreviewMetrics(previewSize, SENSOR_ANGLE, false, 'fill');
// Frame
var frame = {
width: parseInt(this.viewfinder.els.frame.style.width, 10),
height: parseInt(this.viewfinder.els.frame.style.height, 10)
};
// Video container
var video = {
width: parseInt(this.viewfinder.els.videoContainer.style.width, 10),
height: parseInt(this.viewfinder.els.videoContainer.style.height, 10)
};
assert.equal(frame.width, this.container.height);
assert.ok(frame.height > this.container.width);
assert.equal(video.height, this.container.height);
assert.ok(video.width > this.container.width);
});
test('Should match frame height to container height when preview aspect < container aspect', function() {
var previewSize = { width: 400, height: 300 };
// Run test-case
this.viewfinder.updatePreviewMetrics(previewSize, SENSOR_ANGLE, false, 'fill');
var frame = {
width: parseInt(this.viewfinder.els.frame.style.width, 10),
height: parseInt(this.viewfinder.els.frame.style.height, 10)
};
var video = {
width: parseInt(this.viewfinder.els.videoContainer.style.width, 10),
height: parseInt(this.viewfinder.els.videoContainer.style.height, 10)
};
assert.ok(frame.width > this.container.height);
assert.equal(frame.height, this.container.width);
assert.ok(video.height > this.container.height);
assert.equal(video.width, this.container.width);
});
});
suite('\'fit\'', function() {
test('Should match frame height to container height when preview aspect > container aspect', function() {
var previewSize = { width: 1600, height: 900 };
// Run test-case
this.viewfinder.updatePreviewMetrics(previewSize, SENSOR_ANGLE, false, 'fit');
var frame = {
width: parseInt(this.viewfinder.els.frame.style.width, 10),
height: parseInt(this.viewfinder.els.frame.style.height, 10)
};
var video = {
width: parseInt(this.viewfinder.els.videoContainer.style.width, 10),
height: parseInt(this.viewfinder.els.videoContainer.style.height, 10)
};
assert.equal(frame.height, this.container.width);
assert.ok(frame.width < this.container.height);
assert.equal(video.width, this.container.width);
assert.ok(video.height < this.container.height);
});
test('Should match frame width to container width when preview aspect < container aspect', function() {
var previewSize = { width: 400, height: 300 };
// Run test-case
this.viewfinder.updatePreviewMetrics(previewSize, SENSOR_ANGLE, false, 'fit');
var frame = {
width: parseInt(this.viewfinder.els.frame.style.width, 10),
height: parseInt(this.viewfinder.els.frame.style.height, 10)
};
var video = {
width: parseInt(this.viewfinder.els.videoContainer.style.width, 10),
height: parseInt(this.viewfinder.els.videoContainer.style.height, 10)
};
// Check width is equal and height is shorter
assert.equal(frame.width, this.container.height);
assert.ok(frame.height < this.container.width);
// Check container width is shorter and container height is equal
assert.ok(video.width < this.container.width);
assert.equal(video.height, this.container.height);
});
});
});
});