VulpesOS/gaia/apps/camera/test/unit/controllers/camera_test.js
OverSu b38dea0597 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.
2026-09-30 23:13:03 +02:00

762 lines
25 KiB
JavaScript
Executable file

/*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);
});
});
});