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