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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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