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,18 @@
{
"extends": "../../.jshintrc",
"predef": [
"assert",
"suite",
"testRequire",
"test",
"setup",
"teardown",
"suiteSetup",
"suiteTeardown",
"require",
"requireApp",
"sinon",
"mocha",
"loadBodyHTML"
]
}

View file

@ -0,0 +1,229 @@
'use strict';
suite('AlarmDatabase Test Suite', function() {
var alarmDatabase;
suiteSetup(function(done) {
require(['alarm', 'alarm_database'], function(alarm, alarm_database) {
alarmDatabase = alarm_database;
done();
});
});
suite('from the string version', function() {
var alarmConversions = [
[{ normalAlarmId: 7,
snoozeAlarmId: 42,
label: 'TEST 1',
hour: 5,
minute: 3,
enabled: true,
repeat: '1010011',
sound: 'ac_awake.opus',
vibrate: '1',
snooze: 5 },
{ registeredAlarms: { normal: 7, snooze: 42 },
repeat: {
'0': true,
'2': true,
'5': true,
'6': true
},
label: 'TEST 1',
hour: 5,
minute: 3,
vibrate: true,
sound: 'ac_awake.opus',
snooze: 5 } ],
[{ normalAlarmId: 7,
snoozeAlarmId: 42,
label: 'TEST 2',
hour: 15,
minute: 43,
enabled: true,
repeat: '1111111',
vibrate: '0',
sound: '0',
snooze: 5 },
{ registeredAlarms: { normal: 7, snooze: 42 },
repeat: {
'0': true,
'1': true,
'2': true,
'3': true,
'4': true,
'5': true,
'6': true
},
label: 'TEST 2',
hour: 15,
minute: 43,
vibrate: false,
sound: null,
snooze: 5 } ],
[{ normalAlarmId: 7,
snoozeAlarmId: 42,
label: 'TEST 3',
hour: 15,
minute: 43,
enabled: true,
repeat: '0000000',
sound: 'OUTDATED_SOUND_FILE.opus',
snooze: 5 },
{ registeredAlarms: { normal: 7, snooze: 42 },
repeat: { },
label: 'TEST 3',
hour: 15,
minute: 43,
vibrate: false,
sound: 'ac_awake.opus',
snooze: 5 } ],
[{ normalAlarmId: 7,
snoozeAlarmId: 42,
label: 'TEST 4',
hour: 15,
minute: 43,
enabled: true,
repeat: '0101100',
sound: 'ac_cycle.opus',
snooze: 5 },
{ registeredAlarms: { normal: 7, snooze: 42 },
repeat: {
'1': true,
'3': true,
'4': true
},
label: 'TEST 4',
hour: 15,
vibrate: false,
minute: 43,
sound: 'ac_cycle.opus',
snooze: 5 } ]
];
test('convert old alarms to new alarms', function() {
alarmConversions.forEach(function(item) {
var [oldAlarm, newAlarm] = item;
assert.deepEqual(
alarmDatabase.normalizeAlarmRecord(oldAlarm), newAlarm);
});
});
});
suite('from the hardcoded days version', function() {
var alarmConversions = [
[{ registeredAlarms: { normal: 7, snooze: 42 },
repeat: {
'sunday': true,
'tuesday': true,
'friday': true,
'saturday': true
},
label: 'TEST 1',
hour: 5,
minute: 3,
vibrate: true,
sound: 'ac_awake.opus',
snooze: 5 },
{ registeredAlarms: { normal: 7, snooze: 42 },
repeat: {
'0': true,
'2': true,
'5': true,
'6': true
},
label: 'TEST 1',
hour: 5,
minute: 3,
vibrate: true,
sound: 'ac_awake.opus',
snooze: 5 } ],
[{ registeredAlarms: { normal: 7, snooze: 42 },
repeat: {
'sunday': true,
'monday': true,
'tuesday': true,
'wednesday': true,
'thursday': true,
'friday': true,
'saturday': true
},
label: 'TEST 2',
hour: 15,
minute: 43,
vibrate: false,
sound: null,
snooze: 5 },
{ registeredAlarms: { normal: 7, snooze: 42 },
repeat: {
'0': true,
'1': true,
'2': true,
'3': true,
'4': true,
'5': true,
'6': true
},
label: 'TEST 2',
hour: 15,
minute: 43,
vibrate: false,
sound: null,
snooze: 5 } ],
[{ registeredAlarms: { normal: 7, snooze: 42 },
repeat: { },
label: 'TEST 3',
hour: 15,
minute: 43,
vibrate: false,
sound: 'ac_awake.opus',
snooze: 5 },
{ registeredAlarms: { normal: 7, snooze: 42 },
repeat: { },
label: 'TEST 3',
hour: 15,
minute: 43,
vibrate: false,
sound: 'ac_awake.opus',
snooze: 5 } ],
[{ registeredAlarms: { normal: 7, snooze: 42 },
repeat: {
'monday': true,
'wednesday': true,
'thursday': true
},
label: 'TEST 4',
hour: 15,
vibrate: false,
minute: 43,
sound: 'ac_cycle.opus',
snooze: 5 },
{ registeredAlarms: { normal: 7, snooze: 42 },
repeat: {
'1': true,
'3': true,
'4': true
},
label: 'TEST 4',
hour: 15,
vibrate: false,
minute: 43,
sound: 'ac_cycle.opus',
snooze: 5 } ]
];
test('convert old alarms to new alarms', function() {
alarmConversions.forEach(function(item) {
var [oldAlarm, newAlarm] = item;
assert.deepEqual(
alarmDatabase.normalizeAlarmRecord(oldAlarm), newAlarm);
});
});
});
});

View file

@ -0,0 +1,102 @@
'use strict';
/* global MockIntlHelper */
suite('Alarm Test', function() {
var Alarm;
var activeAlarm;
var alarmDatabase;
suiteSetup(function(done) {
window.IntlHelper = MockIntlHelper;
require(['alarm', 'alarm_database', 'panels/alarm/active_alarm'],
function(alarm, alarm_database, ActiveAlarm) {
Alarm = alarm;
activeAlarm = new ActiveAlarm();
alarmDatabase = alarm_database;
done();
}
);
});
suite('Date handling', function() {
//var now = new Date(1398387324081); // thursday
var now = new Date(Date.UTC(2014, 3, 24)); // thursday
setup(function() {
this.alarm = new Alarm({
hour: 4,
minute: 20,
repeat: {
monday: true, wednesday: true, friday: true
}
});
});
test('basic properties and serialization', function() {
assert.deepEqual(this.alarm.toJSON(), {
id: null,
registeredAlarms: {},
repeat: { monday: true, wednesday: true, friday: true },
hour: 4,
minute: 20,
label: '',
sound: 'ac_awake.opus',
vibrate: true,
snooze: 10
});
});
// Bug 1146263
test.skip('alarm is enabled when snoozed', function(done) {
this.alarm.schedule('snooze').then(function() {
assert.ok(this.alarm.isEnabled());
done();
}.bind(this));
});
// Bug 1146263
test.skip('schedule saves alarm', function(done) {
var spy = sinon.spy(alarmDatabase, 'put');
this.alarm.schedule('normal').then(function() {
spy.restore();
assert.ok(spy.called);
done();
});
});
// Bug 1146263
test.skip('cancel saves alarm', function(done) {
var spy = sinon.spy(alarmDatabase, 'put');
this.alarm.cancel().then(function() {
spy.restore();
assert.ok(spy.called);
done();
});
});
test('getNextAlarmFireTime', function() {
// Today is thursday; the alarm fires first on Friday:
var when = this.alarm.getNextAlarmFireTime(now);
assert.equal(when.getDay(), 5); // Friday
// After Friday, it should fire on Monday at the same time:
var secondAlarm = this.alarm.getNextAlarmFireTime(when);
assert.equal(secondAlarm.getHours(), when.getHours()); // at the same time
assert.equal(secondAlarm.getMinutes(), when.getMinutes());
assert.equal(secondAlarm.getDay(), 1); // on Monday
// And again on Wednesday
var thirdAlarm = this.alarm.getNextAlarmFireTime(secondAlarm);
assert.equal(thirdAlarm.getHours(), secondAlarm.getHours());
assert.equal(thirdAlarm.getMinutes(), secondAlarm.getMinutes());
assert.equal(thirdAlarm.getDay(), 3); // on Wednesday
});
test('getNextSnoozeFireTime', function() {
assert.equal(this.alarm.getNextSnoozeFireTime(now).getTime(),
now.getTime() + (10 * 60 * 1000));
});
});
});

View file

@ -0,0 +1,60 @@
'use strict';
suite('AsyncQueue', function() {
var AsyncQueue;
suiteSetup(function(done) {
require(['async_queue'], function(async_queue) {
AsyncQueue = async_queue;
done();
});
});
test('runs operations serially', function(done) {
// Construct the string "hello", one character at a time. We
// assign each task different durations to ensure that no matter
// how long each operation takes, the result remains in the same
// order as the tasks were added.
var result = '';
var queue = new AsyncQueue();
var clock = this.sinon.useFakeTimers();
function addTask(timeout, character) {
queue.push(function(taskFinished) {
setTimeout(function() {
result += character;
taskFinished();
}, timeout);
clock.tick(10);
});
}
addTask(50, 'h');
addTask(10, 'e');
addTask(0, 'l');
addTask(30, 'l');
addTask(20, 'o');
queue.push(function() {
assert.equal(result, 'hello');
clock.restore();
done();
});
clock.tick(100);
});
test('continues after queue is empty', function(done) {
var queue = new AsyncQueue();
queue.push(function(taskFinished) {
taskFinished();
setTimeout(function() {
queue.push(function() {
done();
});
}, 0);
});
});
});

View file

@ -0,0 +1,65 @@
suite('Audio Manager', function() {
'use strict';
var AudioManager;
var navigator_mozSettings = navigator.mozSettings;
suiteSetup(function(done) {
require(['mocks/mock_navigator_mozsettings'], function(mockSettings) {
navigator.mozSettings = mockSettings;
require(['audio_manager'], function(_AudioManager) {
AudioManager = _AudioManager;
done();
});
});
});
suiteTeardown(function() {
navigator.mozSettings = navigator_mozSettings;
});
function assertVolumeEqual(actual, expected, message) {
assert.equal(AudioManager.floatToSystemVolume(actual),
AudioManager.floatToSystemVolume(expected));
}
setup(function() {
this.audio = AudioManager.createAudioPlayer();
});
teardown(function() {
this.audio = null;
});
test('AudioPlayer lazy load', function() {
assert.equal(this.audio._audio, null);
});
test('AudioPlayer does not throw when uninitialized', function() {
this.audio.pause();
});
test('AudioPlayer loads sound with correct settings', function() {
this.audio.playRingtone('some_ringtone.opus');
assert.equal(this.audio._audio.loop, true);
assert.ok(/some_ringtone.opus$/.test(this.audio._audio.src),
'audio.src contains the ringtone');
assertVolumeEqual(this.audio._audio.volume, AudioManager.getAlarmVolume());
});
test('Volume can be set and read from storage', function(done) {
// use a callback to check asynchronously
AudioManager.setAlarmVolume(0.5, function() {
assertVolumeEqual(AudioManager.getAlarmVolume(), 0.5);
done();
});
// also check synchronously
assertVolumeEqual(AudioManager.getAlarmVolume(), 0.5);
});
test('Setting global alarm volume changes AudioPlayer', function() {
AudioManager.setAlarmVolume(0.2);
this.audio.playRingtone('some_ringtone.opus');
assertVolumeEqual(this.audio._audio.volume, 0.2);
});
});

View file

@ -0,0 +1,81 @@
suite('Banner', function() {
'use strict';
/* global MockIntlHelper, l10nAssert */
var Banner;
suiteSetup(function(done) {
window.IntlHelper = MockIntlHelper;
window.IntlHelper.define('digit-nopadding', 'number', {
style: 'decimal',
useGrouping: false
});
// The timestamp for "Tue Jul 16 2013 06:00:00" according to the local
// system's time zone
this.sixAm = new Date(2013, 5, 16, 6).getTime();
// +12 minutes +20 seconds
this.minutes = this.sixAm + 740000;
// +10 hours +12 minutes +20 seconds
this.hours = this.minutes + (10 * 60 * 60 * 1000);
// +2 day +10 hours +12 minutes +20 seconds
this.days = this.hours + (48 * 60 * 60 * 1000);
// Instantiate the Banner once with an element
this.noteElem = document.createElement('div');
require(['banner/main'], function(banner) {
Banner = banner;
this.banner = new Banner(this.noteElem);
done();
}.bind(this));
});
setup(function() {
this.clock = this.sinon.useFakeTimers(this.sixAm);
});
suite('#show', function() {
test('should make notification visible ', function() {
var visible;
this.banner.show(this.hours);
visible = this.noteElem.className.split(/\s+/).some(function(elem) {
return elem === 'visible';
});
assert.ok(visible);
});
});
suite('#render', function() {
test('minutes ahead', function() {
// build banner
this.banner.show(this.minutes);
// Check l10n arguments
l10nAssert(this.banner.notice.querySelector('p'),
'countdown_lessThanAnHour',
{minutes: 12});
});
test('hours ahead', function() {
// build banner
this.banner.show(this.hours);
// Check l10n arguments
l10nAssert(this.banner.notice.querySelector('p'),
'countdown_moreThanAnHour',
{minutes: 12, hour: 10});
});
test('days ahead', function() {
// build banner
this.banner.show(this.days);
// Check l10n arguments
l10nAssert(this.banner.notice.querySelector('p'),
'countdown_moreThanADay',
{days: 2, hour: 10});
});
});
});

View file

@ -0,0 +1,210 @@
'use strict';
/* global MockIntlHelper */
suite('FormButton', function() {
var doc, input, formButton, FormButton, Constants, Utils;
suiteSetup(function(done) {
window.IntlHelper = MockIntlHelper;
require([
'form_button',
'constants',
'utils'
], function(formButton, constants, utils) {
FormButton = formButton;
Constants = constants;
Utils = utils;
done();
}
);
});
suite('basic functions', function() {
setup(function() {
doc = document.createElement('div');
doc.innerHTML = '<input type="time" id="time-input"/>';
input = doc.querySelector('#time-input');
formButton = new FormButton(input);
});
test('should insert a button element into the dom', function() {
var buttons = doc.querySelectorAll('button');
assert.equal(buttons.length, 1);
});
test('should hide the input', function() {
assert.isTrue(input.classList.contains('form-button-hide'));
});
test('clicking the button should focus the input', function(done) {
this.sinon.stub(input, 'focus', function() {
assert.ok(input.focus.called);
done();
});
formButton.button.click();
});
test('refresh should update the button text', function(done) {
formButton.input.value = '10:10';
formButton.refresh().then(() => {
assert.equal(formButton.button.textContent, '10:10');
}).then(done, done);
});
test('formatLabel should be overrideable', function() {
var formButton = new FormButton(input, {
formatLabel: function() {
return 'formatted label';
}
});
assert.equal(formButton.formatLabel(), 'formatted label');
});
test('tagName should set the element type of the button', function() {
var formButton = new FormButton(input, {
tagName: 'span'
});
assert.equal(formButton.button.nodeName, 'SPAN');
});
test('className should set the classes of the button', function() {
var formButton = new FormButton(input, {
className: 'foo bar'
});
assert.equal(formButton.button.className, 'foo bar');
});
test('id should set the id of the button', function() {
var formButton = new FormButton(input, {
id: 'my-button'
});
assert.equal(formButton.button.id, 'my-button');
});
});
suite('when the input property is an input element', function() {
setup(function() {
doc = document.createElement('div');
doc.innerHTML = '<input type="time" id="time-input"/>';
input = doc.querySelector('#time-input');
formButton = new FormButton(input);
});
test('the blur event should update the button text', function(done) {
formButton.input.value = '10:11';
formButton.input.dispatchEvent(new Event('blur'));
Promise.resolve().then(() => {
assert.equal(formButton.button.textContent, '10:11');
}).then(done, done);
});
test('getValue should return the current value', function() {
formButton.input.value = '10:12';
assert.equal(formButton.value, '10:12');
});
test('setValue should set the current value', function() {
formButton.value = '10:13';
assert.equal(formButton.input.value, '10:13');
});
test('isSelect should be false', function() {
assert.equal(formButton.isSelect, false);
});
});
suite('when the input property is a select element', function() {
setup(function() {
doc = document.createElement('div');
doc.innerHTML = ['<select id="vibrate-select"/>',
'<option value="1">On</option>',
'<option value="0">Off</option>',
'<option value="maybe">Maybe</option>',
'</select>'].join('');
input = doc.querySelector('#vibrate-select');
formButton = new FormButton(input);
});
test('the change event should update the button text', function(done) {
Utils.changeSelectByValue(formButton.input, '0');
formButton.input.dispatchEvent(new Event('change'));
Promise.resolve().then(() => {
assert.equal(formButton.button.textContent, '0');
}).then(done, done);
});
test('getValue should return the current value', function() {
Utils.changeSelectByValue(formButton.input, '1');
assert.equal(formButton.value, '1');
});
test('setValue should set the current value', function() {
formButton.value = 'maybe';
assert.equal(formButton.input.value, 'maybe');
});
test('isSelect should be true', function() {
assert.equal(formButton.isSelect, true);
});
});
suite('when the input property is a select multiple', function() {
setup(function() {
doc = document.createElement('div');
doc.innerHTML = ['<select id="repeat-select" multiple="true">',
'<option value="1">Monday</option>',
'<option value="2">Tuesday</option>',
'<option value="3">Wednesday</option>',
'<option value="4">Thursday</option>',
'<option value="5">Friday</option>',
'<option value="6">Saturday</option>',
'<option value="0">Sunday</option>',
'</select>'].join('');
input = doc.querySelector('#repeat-select');
formButton = new FormButton(input, {
formatLabel: function (value) {
return {raw: JSON.stringify(value)};
}
});
});
test('the change event should update the button text', function(done) {
Utils.changeSelectByValue(formButton.input, '5');
formButton.input.dispatchEvent(new Event('change'));
Promise.resolve().then(() => {
assert.equal(formButton.button.textContent, '{"5":true}');
}).then(done, done);
});
test('getValue should return the current value', function(done) {
Utils.changeSelectByValue(formButton.input, '2');
Promise.resolve().then(() => {
assert.deepEqual(formButton.value, { '2': true });
}).then(done, done);
});
test('setValue should set the current value', function() {
formButton.value = {'2': true, '4': true, '6': true};
var options = formButton.input.options;
assert.isFalse(options[0].selected); // monday
assert.isTrue(options[1].selected); // tuesday
assert.isFalse(options[2].selected); // wednesday
assert.isTrue(options[3].selected); // tursday
assert.isFalse(options[4].selected); // friday
assert.isTrue(options[5].selected); // saturday
assert.isFalse(options[6].selected); // sunday
});
test('isSelect should be true', function() {
assert.equal(formButton.isSelect, true);
});
});
});

View file

@ -0,0 +1,98 @@
'use strict';
var mozAlarmsId = 0;
function MockOSAlarm(date, respectTimezone, data, callback) {
this.date = date || new Date();
this.data = data || {};
this.callback = callback;
this.id = mozAlarmsId++;
this.respectTimezone = respectTimezone;
this.timeout = setTimeout(function() {
if ((typeof this.callback) === 'function') {
this.callback({
id: this.id,
respectTimezone: this.respectTimezone,
date: this.date,
data: this.data
});
}
}.bind(this), this.date.getTime() - Date.now());
}
function MockMozAlarmRequest(config) {
this.init(config || {});
}
MockMozAlarmRequest.prototype = {
init: function mar_init(config) {
setTimeout(function() {
this.fire(config.error, config.result);
}.bind(this), 0);
},
fire: function mar_call(error, result) {
if (!error) {
this.onsuccess && this.onsuccess({
target: {
result: result
}
});
} else {
this.onerror && this.onerror({
target: {
error: result
}
});
}
}
};
var messageHandlers = [];
navigator.mozSetMessageHandler = function(type, callback) {
if (type === 'alarm') {
messageHandlers.push(callback);
}
};
navigator.mozAlarms = {
alarms: [],
getAll: function() {
return new MockMozAlarmRequest({
result: this.alarms
});
},
add: function(date, timezone, data) {
if (!(timezone === 'ignoreTimezone' ||
timezone === 'honorTimezone')) {
throw new Error('invalid timezone argument');
}
var alarm = new MockOSAlarm(date, timezone, data, function(x) {
messageHandlers.forEach(function(callback) {
callback(x);
});
this.remove(x.id);
}.bind(this));
this.alarms.push(alarm);
return new MockMozAlarmRequest({
result: alarm.id
});
},
remove: function(id) {
for (var i = 0; i < this.alarms.length; i++) {
var alarm = this.alarms[i];
if (alarm && (alarm.id === id)) {
clearTimeout(alarm.timeout);
this.alarms.splice(i, 1);
return true;
}
}
return false;
}
};

View file

@ -0,0 +1,52 @@
define(function() {
'use strict';
function DateTimeFormat() {
this.mInitialized = true;
}
DateTimeFormat.prototype = {
localeFormat: function mockLocaleFormat(time, strFormat) {
return '' + time;
}
};
var testDefaults = {'en-US': {}};
var currentLanguage = 'en-US';
// callbacks to be fired onready
var onreadycbs = [];
return {
get: function get(key, params) {
var res = testDefaults[currentLanguage];
if (key in res) {
return res[key];
}
if (params) {
return key + JSON.stringify(params);
}
return key;
},
ready: function(cb) {
setTimeout(cb);
onreadycbs.push(cb);
},
setAttributes: function() {},
DateTimeFormat: DateTimeFormat,
// for unit tests
setResources: function(lang, res) {
testDefaults[lang] = res;
},
language: {
set code(lang) {
currentLanguage = lang;
for (var cb of onreadycbs) {
cb();
}
}
},
};
});

View file

@ -0,0 +1,45 @@
/* global define */
define(function() {
'use strict';
var allSettings = {};
var settingsObservers = {};
return {
createLock: function() {
return {
get: function(key) {
var ret = { onsuccess: null, result: {} };
setTimeout(function() {
ret.result[key] = allSettings[key];
ret.onsuccess && ret.onsuccess();
});
return ret;
},
set: function(settings) {
var ret = { onsuccess: null };
/* jshint loopfunc:true */
for (var key in settings) {
allSettings[key] = settings[key];
var listeners = settingsObservers[key];
if (listeners) {
listeners.forEach(function(cb) {
cb({ settingValue: settings[key] });
});
}
}
setTimeout(function() {
ret.onsuccess && ret.onsuccess();
});
return ret;
}
};
},
addObserver: function(key, cb) {
var listeners = settingsObservers[key] || [];
listeners.push(cb);
settingsObservers[key] = listeners;
},
};
});

View file

@ -0,0 +1,45 @@
define(function() {
'use strict';
function MockLock(type) {
this.type = type;
this.locked = true;
this.unlocks = 0;
}
MockLock.prototype = {
unlock: function() {
this.locked = false;
this.unlocks++;
}
};
function MockRequestWakeLock() {
this.issued = new Map();
}
MockRequestWakeLock.prototype = {
requestWakeLock: function mock_requestWakeLock(type) {
var lock = new MockLock(type);
this.issued.set(lock, true);
return lock;
},
getissued: function mock_getissued() {
var ret = [];
for (var el of this.issued) {
ret.push(el[0]);
}
return ret;
},
reset: function() {
this.issued.clear();
}
};
return MockRequestWakeLock;
});

View file

@ -0,0 +1,6 @@
'use strict';
define(function() {
return {
setAriaSelected: function() {}
};
});

View file

@ -0,0 +1,75 @@
/* global require, document, require, loadBodyHTML */
/* global MockIntlHelper, MockMozIntl */
suite('Ring Test', function() {
'use strict';
var RingView;
suiteSetup(function(done) {
window.IntlHelper = MockIntlHelper;
window.mozIntl = MockMozIntl;
require(['ring_view'],
function(_RingView) {
RingView = _RingView;
loadBodyHTML('/onring.html');
// Force document.hidden to be 'false' so that the alarm
// properly triggers visible callbacks in ring_view.js
Object.defineProperty(document, 'hidden', {value: false});
done();
});
});
[
{ vibrate: true, shouldVibrate: true },
{ vibrate: '1', shouldVibrate: true },
{ vibrate: false, shouldVibrate: false }
].forEach(function(testCase) {
var { vibrate, shouldVibrate } = testCase;
test('should ' + (shouldVibrate ? '' : 'not ') +
'vibrate when vibrate is set to ' + vibrate, function() {
var clock = this.sinon.useFakeTimers();
var mock = this.sinon.mock(navigator);
if (shouldVibrate) {
mock.expects('vibrate').atLeast(1);
} else {
mock.expects('vibrate').never();
}
var view = new RingView();
view.addAlert({
type: 'alarm',
vibrate: vibrate,
label: 'hi',
sound: null,
hour: 1,
minute: 1,
time: new Date(),
});
clock.tick(5000); // vibrate starts after a setInterval
mock.verify();
mock.restore();
});
});
test('should silence itself after a timeout', function() {
var clock = this.sinon.useFakeTimers();
var view = new RingView();
view.addAlert({
type: 'alarm',
vibrate: true,
label: 'hi',
sound: null,
hour: 1,
minute: 1,
time: new Date(),
});
var silenceSpy = this.sinon.spy(view, 'silence');
clock.tick(1000); // Wait one second... the alarm should still be ringing
assert.isFalse(silenceSpy.called);
clock.tick(10 * 60 * 1000); // After 10 minutes, it should be quiet.
assert.isTrue(silenceSpy.called);
});
});

View file

@ -0,0 +1,124 @@
'use strict';
suite('Panel', function() {
var Panel;
suiteSetup(function(done) {
require(['panel'], function(panel) {
Panel = panel;
done();
});
});
setup(function() {
this.element = document.createElement('div');
this.panel = new Panel(this.element);
});
suite('active', function() {
test('starts out active false', function() {
assert.isFalse(this.panel.active);
});
test('class not on element', function() {
assert.isFalse(this.element.classList.contains('active'));
});
suite('setting false', function() {
setup(function() {
this.panel.active = false;
});
test('class not on element', function() {
assert.isFalse(this.element.classList.contains('active'));
});
});
suite('setting true', function() {
setup(function() {
this.panel.active = true;
});
test('active is true', function() {
assert.isTrue(this.panel.active);
});
test('class on element', function() {
assert.isTrue(this.element.classList.contains('active'));
});
suite('setting true', function() {
setup(function() {
this.panel.active = true;
});
test('class on element', function() {
assert.isTrue(this.element.classList.contains('active'));
});
});
suite('setting false', function() {
setup(function() {
this.panel.active = false;
});
test('active is false', function() {
assert.isFalse(this.panel.active);
});
test('class not on element', function() {
assert.isFalse(this.element.classList.contains('active'));
});
});
});
});
suite('transition', function() {
setup(function() {
this.panel.transition = 'test-transition';
});
test('adds class', function() {
assert.isTrue(this.element.classList.contains('test-transition'));
});
suite('change transition', function() {
setup(function() {
this.panel.transition = 'test-transition-2';
});
test('removes old class', function() {
assert.isFalse(this.element.classList.contains('test-transition'));
});
test('adds new class', function() {
assert.isTrue(this.element.classList.contains('test-transition-2'));
});
});
suite('animationend event (not active)', function() {
setup(function() {
var event = new CustomEvent('animationend', { canBubble: true });
this.element.dispatchEvent(event);
});
test('sets transition to false', function() {
assert.isFalse(this.panel.transition);
});
test('removes class', function() {
assert.isFalse(this.element.classList.contains('test-transition'));
});
test('sets hidden', function() {
assert.isTrue(this.element.classList.contains('hidden'));
assert.isFalse(this.panel.visible);
});
});
suite('animationend event (active)', function() {
setup(function() {
this.panel.visible = true;
this.panel.active = true;
var event = new CustomEvent('animationend', { canBubble: true });
this.element.dispatchEvent(event);
});
test('sets transition to false', function() {
assert.isFalse(this.panel.transition);
});
test('removes class', function() {
assert.isFalse(this.element.classList.contains('test-transition'));
});
test('does not set hidden', function() {
assert.isFalse(this.element.classList.contains('hidden'));
assert.isTrue(this.panel.visible);
});
});
});
});

View file

@ -0,0 +1,162 @@
suite('AlarmList', function() {
'use strict';
/** Disabled due to intermittent, see bug 1089543
var alarm, dom;
var alarmListPanel, Alarm, panel;
suiteSetup(function(done) {
// Account for potentially-slow file loading operations
this.timeout(60000);
require([
'panels/alarm/main',
'panels/alarm/alarm_list',
'alarm'
], function(AlarmPanel, AlarmListPanel, alarm) {
var div = document.createElement('div');
document.body.appendChild(div);
panel = new AlarmPanel(div);
alarmListPanel = new AlarmListPanel(document.createElement('div'));
Alarm = alarm;
done();
});
});
setup(function() {
alarm = new Alarm({
id: 42,
hour: 14,
minute: 32,
label: 'ALARM',
registeredAlarms: {
normal: 37
}
});
});
suite('render()', function() {
setup(function() {
dom = alarmListPanel.renderAlarm(alarm);
});
suite('markup contains correct information', function() {
test('id', function() {
assert.ok(dom.querySelector('[data-id="42"]'));
});
test('enabled', function() {
assert.ok(dom.querySelector('input').checked);
});
test('disabled', function() {
alarm = new Alarm({
hour: 14,
minute: 32
});
dom = alarmListPanel.renderAlarm(alarm);
assert.ok(!dom.querySelector('input').checked);
});
test('labeled', function() {
assert.equal(dom.querySelector('.label').textContent, 'ALARM');
});
test('unlabeled', function() {
alarm.label = '';
dom = alarmListPanel.renderAlarm(alarm);
assert.equal(dom.querySelector('.label').textContent, 'alarm');
});
test('repeat', function() {
alarm.repeat = { monday: true };
dom = alarmListPanel.renderAlarm(alarm);
assert.equal(
dom.querySelector('.repeat').textContent, 'weekday-1-short'
);
});
test('no repeat', function() {
alarm.label = '';
dom = alarmListPanel.renderAlarm(alarm);
assert.equal(dom.querySelector('.repeat').textContent, '');
});
test('repeat, with-repeat class', function() {
alarm.repeat = { monday: true };
dom = alarmListPanel.renderAlarm(alarm);
assert.isTrue(
dom.querySelector('.alarm-item').classList.contains('with-repeat')
);
});
test('no repeat, without with-repeat class', function() {
alarm.label = '';
dom = alarmListPanel.renderAlarm(alarm);
assert.isFalse(
dom.querySelector('.alarm-item').classList.contains('with-repeat')
);
});
});
});
suite('toggleAlarmEnableState', function() {
var origSetEnabled = Alarm.prototype.setEnabled;
setup(function() {
Alarm.prototype.setEnabled = sinon.spy(function(enabled) {
if (enabled) {
this.registeredAlarms.normal = 1;
} else {
delete this.registeredAlarms.normal;
}
return Promise.resolve();
});
this.sinon.spy(alarmListPanel, 'addOrUpdateAlarm');
});
teardown(function() {
Alarm.prototype.setEnabled = origSetEnabled;
});
test('refreshes the list when enabled changes to false', function(done) {
alarmListPanel.toggleAlarm(alarm, false, () => {
sinon.assert.calledWith(alarmListPanel.addOrUpdateAlarm, alarm);
assert.isFalse(alarm.isEnabled());
dom = alarmListPanel.renderAlarm(alarm);
assert.isFalse(dom.querySelector('input').checked);
done();
});
});
test('refreshes the list when enabled changes to true', function(done) {
alarmListPanel.toggleAlarm(alarm, true, () => {
sinon.assert.calledWith(alarmListPanel.addOrUpdateAlarm, alarm);
assert.isTrue(alarm.isEnabled());
dom = alarmListPanel.renderAlarm(alarm);
assert.isTrue(dom.querySelector('input').checked);
done();
});
});
test('toggling multiple times works correctly', function(done) {
alarmListPanel.toggleAlarm(alarm, true);
alarmListPanel.toggleAlarm(alarm, false);
alarmListPanel.toggleAlarm(alarm, true);
alarmListPanel.toggleAlarm(alarm, false);
alarmListPanel.toggleAlarm(alarm, true, () => {
sinon.assert.calledWith(alarmListPanel.addOrUpdateAlarm, alarm);
assert.isTrue(alarm.isEnabled());
dom = alarmListPanel.renderAlarm(alarm);
assert.isTrue(dom.querySelector('input').checked);
done();
});
});
});
*/
});

View file

@ -0,0 +1,395 @@
'use strict';
suite('ClockView', function() {
/** Disabled due to intermittent, see bug 1089543
var ClockView;
var asyncStorage;
var panel;
suiteSetup(function(done) {
// The timestamp for "Tue Jul 16 2013 06:00:00" according to the local
// system's time zone
this.sixAm = new Date(2013, 5, 16, 6).getTime();
require([
'panels/alarm/main',
'panels/alarm/clock_view',
'shared/js/async_storage'
], function(
AlarmPanel,
clockView,
mockAsyncStorage
) {
// Instantiate an Alarm Panel to ensure that elements are initialized
// properly
var div = document.createElement('div');
document.body.appendChild(div);
panel = new AlarmPanel(div);
ClockView = clockView;
asyncStorage = mockAsyncStorage;
done();
});
});
test('ClockView.isInitialized ', function() {
assert.ok(ClockView.isInitialized);
});
suite('updateDayDate', function() {
suiteSetup(function() {
ClockView.dayDate = document.getElementById('clock-day-date');
});
setup(function() {
this.clock = this.sinon.useFakeTimers(this.sixAm);
});
teardown(function() {
ClockView.dayDate.innerHTML = '';
});
test('date element is updated with the current time', function() {
ClockView.updateDayDate();
assert.equal(Date.parse(ClockView.dayDate.textContent), this.sixAm);
});
test('date element is not updated twice in the same day', function() {
ClockView.updateDayDate();
this.clock.tick(18 * 60 * 60 * 1000 - 1);
assert.equal(Date.parse(ClockView.dayDate.textContent), this.sixAm);
});
test('date element is updated each day', function() {
ClockView.updateDayDate();
this.clock.tick(18 * 60 * 60 * 1000);
assert.equal(Date.parse(ClockView.dayDate.textContent),
this.sixAm + 18 * 60 * 60 * 1000);
});
});
suite('updateDigitalClock', function() {
setup(function() {
this.clock = this.sinon.useFakeTimers(this.sixAm + 1000);
});
teardown(function() {
ClockView.time.innerHTML = '';
this.clock.restore();
});
test('time element is updated immediately',
function() {
ClockView.updateDigitalClock();
assert.equal(Date.parse(ClockView.time.innerHTML), this.sixAm + 1000);
});
test('time element is not updated twice in the same minute', function() {
ClockView.updateDigitalClock();
this.clock.tick(59 * 1000 - 1);
assert.equal(Date.parse(ClockView.time.innerHTML), this.sixAm + 1000);
});
test('time element is updated each minute', function() {
ClockView.updateDigitalClock();
this.clock.tick(59 * 1000);
assert.equal(Date.parse(ClockView.time.innerHTML),
this.sixAm + 60 * 1000);
});
});
suite('updateAnalogClock', function() {
suiteSetup(function() {
this.second = document.getElementById('secondhand');
this.minute = document.getElementById('minutehand');
this.hour = document.getElementById('hourhand');
});
setup(function() {
this.clock = this.sinon.useFakeTimers(this.sixAm + 1200);
});
function assertRotation(rotate, n) {
assert.ok(rotate.style.transform.indexOf('rotate(' + n + 'deg') !== -1,
'Expected rotate(' + n + 'deg) for clock hand');
}
test('second-, minute-, and hour- hands are updated immediately',
function() {
var rotate;
ClockView.updateAnalogClock();
rotate = this.second;
assert.ok(rotate, 'Second hand rotation element exists');
assertRotation(rotate, 6);
rotate = this.minute;
assert.ok(rotate, 'Minute hand rotation element exists');
assertRotation(rotate, 0);
rotate = this.hour;
assert.ok(rotate, 'Hour hand rotation element exists');
assertRotation(rotate, 180);
});
test('second-, minute-, and hour- hands are not updated twice in the ' +
'same second', function() {
var rotate;
ClockView.updateAnalogClock();
this.clock.tick(799);
rotate = this.second;
assert.ok(rotate, 'Second hand rotation element exists');
assertRotation(rotate, 6);
rotate = this.minute;
assert.ok(rotate, 'Minute hand rotation element exists');
assertRotation(rotate, 0);
rotate = this.hour;
assert.ok(rotate, 'Hour hand rotation element exists');
assertRotation(rotate, 180);
});
test('second-, minute-, and hour- hands are updated each second',
function() {
var rotate;
ClockView.updateAnalogClock();
this.clock.tick(800);
rotate = this.second;
assert.ok(rotate, 'Second hand rotation element exists');
assertRotation(rotate, 12);
rotate = this.minute;
assert.ok(rotate, 'Minute hand rotation element exists');
assertRotation(rotate, 0);
rotate = this.hour;
assert.ok(rotate, 'Hour hand rotation element exists');
assertRotation(rotate, 180);
});
});
suite('show', function() {
setup(function() {
ClockView.mode = 'analog';
window.location.hash = 'alarm-view';
});
teardown(function() {
ClockView.mode = 'analog';
});
test('show() [no mode, no settings, defer to default] ', function() {
this.sinon.spy(asyncStorage, 'setItem');
this.sinon.stub(asyncStorage, 'getItem', function() {
return null;
});
// Clear the locally stored "mode"
ClockView.mode = null;
ClockView.show();
// null => analog (default), asyncStorage.setItem should be called
assert.ok(asyncStorage.setItem.called);
// analog is the default, analog is visible
assert.ok(
ClockView.analog.classList.contains('visible')
);
// analog is the default, digital is not visible
assert.isFalse(
ClockView.digital.classList.contains('visible')
);
});
test('show() [no mode, defer to settings] ', function() {
this.sinon.spy(asyncStorage, 'setItem');
ClockView.show();
// Nothing changed, asyncStorage.setItem should not be called
assert.isFalse(asyncStorage.setItem.called);
// Nothing changed, analog is still visible
assert.ok(
ClockView.analog.classList.contains('visible')
);
// Nothing changed, digital is NOT visible
assert.isFalse(
ClockView.digital.classList.contains('visible')
);
});
test('show(analog) [no mode, defer to settings] ', function() {
this.sinon.spy(asyncStorage, 'setItem');
ClockView.show('analog');
// Nothing changed, asyncStorage.setItem should not be called
assert.isFalse(asyncStorage.setItem.called);
// Nothing changed, analog is still visible
assert.ok(
ClockView.analog.classList.contains('visible')
);
// Nothing changed, digital is NOT visible
assert.isFalse(
ClockView.digital.classList.contains('visible')
);
});
test('show(analog) ', function() {
ClockView.mode = 'digital';
this.sinon.spy(asyncStorage, 'setItem');
ClockView.show('analog');
// Nothing changed, asyncStorage.setItem should not be called
assert.ok(asyncStorage.setItem.called);
assert.equal(
asyncStorage.setItem.args[0][0],
'settings_clockoptions_mode'
);
assert.equal(
asyncStorage.setItem.args[0][1],
'analog'
);
// Nothing changed, analog is still visible
assert.ok(
ClockView.analog.classList.contains('visible')
);
// Nothing changed, digital is NOT visible
assert.isFalse(
ClockView.digital.classList.contains('visible')
);
});
test('show(digital) ', function() {
ClockView.mode = 'analog';
this.sinon.spy(asyncStorage, 'setItem');
ClockView.show('digital');
// Updated, asyncStorage.setItem IS called
assert.ok(asyncStorage.setItem.called);
assert.equal(
asyncStorage.setItem.args[0][0],
'settings_clockoptions_mode'
);
assert.equal(
asyncStorage.setItem.args[0][1],
'digital'
);
// Updated, analog is NOT visible
assert.isFalse(
ClockView.analog.classList.contains('visible')
);
// Updated, digital IS visible
assert.ok(
ClockView.digital.classList.contains('visible')
);
});
test('ClockView.analog.click() shows digital ', function(done) {
this.sinon.spy(asyncStorage, 'setItem');
this.sinon.stub(ClockView, 'show', function(mode) {
ClockView.show.restore();
assert.equal(mode, 'digital');
done();
});
ClockView.analog.click();
});
test('ClockView.digital.click() shows analog ', function(done) {
ClockView.mode = 'digital';
this.sinon.spy(asyncStorage, 'setItem');
this.sinon.stub(ClockView, 'show', function(mode) {
ClockView.show.restore();
assert.equal(mode, 'analog');
done();
});
ClockView.digital.click();
});
});
suite('resizeAnalogClock', function() {
suiteSetup(function() {
this.analogClockContainer = document.getElementById(
'analog-clock-container'
);
});
setup(function() {
this.getAlarmCountStub = this.sinon.stub(ClockView, 'getAlarmCount');
});
test('large size when no alarms in alarms list', function() {
this.getAlarmCountStub.returns(0);
ClockView.resizeAnalogClock();
assert.isTrue(this.analogClockContainer.classList.contains('large'));
});
test('large size when 1 alarm in alarms list', function() {
this.getAlarmCountStub.returns(1);
ClockView.resizeAnalogClock();
assert.isTrue(this.analogClockContainer.classList.contains('large'));
});
test('medium size when 2 alarms in alarms list', function() {
this.getAlarmCountStub.returns(2);
ClockView.resizeAnalogClock();
assert.isTrue(this.analogClockContainer.classList.contains('medium'));
});
test('small size when 3 or more alarms in alarms list', function() {
this.getAlarmCountStub.returns(3);
ClockView.resizeAnalogClock();
assert.isTrue(this.analogClockContainer.classList.contains('small'));
this.getAlarmCountStub.returns(4);
ClockView.resizeAnalogClock();
assert.isTrue(this.analogClockContainer.classList.contains('small'));
});
});
*/
});

View file

@ -0,0 +1,270 @@
'use strict';
/** TEST TEMPORARILY DISABLED, SEE BUG 1033213 ****************************
suite('AlarmEditView', function() {
var Alarm, AlarmEdit, activeAlarm, alarmDatabase,
alarmListPanel, alarmEdit, panel;
suiteSetup(function(done) {
this.slow(25000);
require([
'alarm',
'panels/alarm/active_alarm',
'panels/alarm_edit/main',
'alarm_database',
'panels/alarm/main',
'panels/alarm/alarm_list',
], function(alarm, ActiveAlarm, alarmEdit, alarm_database, AlarmPanel,
AlarmListPanel) {
// Instantiate an Alarm Panel to ensure that elements are initialized
// properly
var div = document.createElement('div');
document.body.appendChild(div);
panel = new AlarmPanel(div);
Alarm = alarm;
activeAlarm = new ActiveAlarm();
AlarmEdit = alarmEdit;
alarmDatabase = alarm_database;
alarmListPanel = new AlarmListPanel(document.createElement('div'));
done();
});
});
setup(function() {
alarmEdit = new AlarmEdit(document.createElement('div'));
});
suite('Alarm persistence', function() {
setup(function(done) {
// Create an Alarm
var alarm = alarmEdit.alarm = new Alarm({
id: 42,
hour: 6,
minute: 34
});
alarm.repeat = {
monday: true, wednesday: true, friday: true
};
alarmEdit.element.dataset.id = alarm.id;
// shim the edit alarm view
alarmEdit.buttons.time.input = alarmEdit.selects.time;
alarmEdit.initTimeSelect();
this.sinon.stub(alarmEdit, 'getTimeSelect');
this.sinon.stub(alarmEdit, 'getSoundSelect');
this.sinon.stub(alarmEdit, 'getSnoozeSelect');
this.sinon.stub(alarmEdit, 'getRepeatSelect');
// Define the stubs to return the same values set in the
// default alarm object.
alarmEdit.getTimeSelect.returns({
hour: alarm.hour,
minute: alarm.minute
});
alarmEdit.getSoundSelect.returns(alarmEdit.alarm.sound);
alarmEdit.checkboxes.vibrate.checked = alarmEdit.alarm.vibrate;
alarmEdit.getSnoozeSelect.returns(alarmEdit.alarm.snooze);
alarmEdit.getRepeatSelect.returns(alarmEdit.alarm.repeat);
// Store to alarmsdb
alarmDatabase.put(alarm).then(done);
});
test('should save an alarm, no id', function(done) {
this.sinon.spy(alarmListPanel, 'addOrUpdateAlarm');
this.sinon.stub(alarmListPanel.banner, 'show');
alarmEdit.alarm = new Alarm({
hour: 5,
minute: 17,
repeat: {
monday: true, wednesday: true, friday: true
}
});
alarmEdit.element.dataset.id = '';
alarmEdit.save(function(err, alarm) {
assert.ok(!err);
// Refreshed AlarmList
assert.ok(alarmListPanel.addOrUpdateAlarm.called, 'addCalledOnce');
assert.ok(alarmListPanel.addOrUpdateAlarm.calledWithExactly(alarm));
// Rendered BannerBar
assert.ok(alarmListPanel.banner.show.called);
done();
});
});
test('should save an alarm, existing id', function(done) {
this.sinon.stub(alarmListPanel, 'addOrUpdateAlarm');
this.sinon.stub(alarmListPanel.banner, 'show');
alarmEdit.alarm = new Alarm({
id: 1,
hour: 5,
minute: 17,
repeat: {
monday: true, wednesday: true, friday: true
}
});
alarmEdit.element.dataset.id = alarmEdit.alarm.id;
alarmEdit.save(function(err, alarm) {
assert.ok(!err);
// Refreshed AlarmList
assert.ok(alarmListPanel.addOrUpdateAlarm.called);
assert.ok(alarmListPanel.addOrUpdateAlarm.calledWithExactly(alarm));
// Rendered BannerBar
assert.ok(alarmListPanel.banner.show.called);
done();
});
});
test('should delete an alarm', function(done) {
this.sinon.stub(alarmListPanel, 'removeAlarm');
alarmEdit.delete(function(err, alarm) {
assert.ok(!err, 'delete reported error');
assert.ok(alarmListPanel.removeAlarm.calledOnce);
done();
});
});
test('should add an alarm with sound, no vibrate', function(done) {
this.sinon.stub(alarmListPanel, 'addOrUpdateAlarm');
alarmEdit.checkboxes.vibrate.checked = false;
alarmEdit.alarm = new Alarm({
hour: 5,
minute: 17,
repeat: {
monday: true, wednesday: true, friday: true
}
});
alarmEdit.element.dataset.id = '';
alarmEdit.save(function(err, alarm) {
assert.ok(alarmListPanel.addOrUpdateAlarm.called);
done();
});
});
test('should update existing alarm with no sound, vibrate', function(done) {
this.sinon.stub(alarmListPanel, 'addOrUpdateAlarm');
// mock the view to turn sound on and vibrate off
alarmEdit.checkboxes.vibrate.checked = true;
alarmEdit.getSoundSelect.returns(null);
alarmEdit.save(function(err, alarm) {
assert.ok(alarm.id);
assert.ok(alarmListPanel.addOrUpdateAlarm.called);
alarmDatabase.get(alarm.id).then(function(alarm) {
assert.equal(alarm.vibrate, true);
assert.equal(alarm.sound, null);
done();
}).catch(done);
});
});
test('should update start of week for l10n, Monday first', function(done) {
var sunday = alarmEdit.element.querySelector('#repeat-select-sunday');
var parent = sunday.parentElement;
// Sunday gets moved to the end.
parent.appendChild(sunday);
mozL10n.language.code = 'en-US';
document.l10n.requestLanguages(['en-US']).then(() => {
assert.ok(!sunday.previousSibling, 'Sunday should be first (prev)');
assert.ok(sunday.nextSibling, 'Sunday should be first (next)');
done();
});
});
test('should update start of week for l10n, Sunday first', function(done) {
var sunday = alarmEdit.element.querySelector('#repeat-select-sunday');
var parent = sunday.parentElement;
// Sunday goes first.
parent.insertBefore(sunday, parent.firstChild);
document.l10n.requestLanguages(['fr']).then(() => {
assert.ok(sunday.previousSibling, 'Sunday should be last (prev)');
assert.ok(!sunday.nextSibling, 'Sunday should be last (next)');
done();
});
});
});
suite('initTimeSelect', function() {
var alarm;
setup(function() {
alarm = alarmEdit.alarm = new Alarm({});
});
suiteTeardown(function() {
alarmEdit.alarm = alarm;
});
test('0:0, should init time select with format of system time picker',
function() {
alarmEdit.alarm.hour = '0';
alarmEdit.alarm.minute = '0';
alarmEdit.initTimeSelect();
assert.equal(alarmEdit.selects.time.value, '00:00');
});
test('3:5, should init time select with format of system time picker',
function() {
alarmEdit.alarm.hour = '3';
alarmEdit.alarm.minute = '5';
alarmEdit.initTimeSelect();
assert.equal(alarmEdit.selects.time.value, '03:05');
});
test('9:25, should init time select with format of system time picker',
function() {
alarmEdit.alarm.hour = '9';
alarmEdit.alarm.minute = '25';
alarmEdit.initTimeSelect();
assert.equal(alarmEdit.selects.time.value, '09:25');
});
test('12:55, should init time select with format of system time picker',
function() {
alarmEdit.alarm.hour = '12';
alarmEdit.alarm.minute = '55';
alarmEdit.initTimeSelect();
assert.equal(alarmEdit.selects.time.value, '12:55');
});
test('15:5, should init time select with format of system time picker',
function() {
alarmEdit.alarm.hour = '15';
alarmEdit.alarm.minute = '5';
alarmEdit.initTimeSelect();
assert.equal(alarmEdit.selects.time.value, '15:05');
});
test('23:0, should init time select with format of system time picker',
function() {
alarmEdit.alarm.hour = '23';
alarmEdit.alarm.minute = '0';
alarmEdit.initTimeSelect();
assert.equal(alarmEdit.selects.time.value, '23:00');
});
});
});
*/

View file

@ -0,0 +1,347 @@
'use strict';
/* global MockL10n, MockMozIntl, MockIntlHelper */
suite('Stopwatch.Panel', function() {
var sevenMinSw, fourSecPausedSw, withLapsSw, runningSw;
var isHidden;
var panel;
var clock;
var Stopwatch;
suiteSetup(function(done) {
document.l10n = MockL10n;
window.mozIntl = MockMozIntl;
window.IntlHelper = MockIntlHelper;
var threeMin = 3 * 60 * 1000 + 130;
var sevenMin = 7 * 60 * 1000 + 170;
var fourSec = 4 * 1000 + 230;
var thirtyMins = 30 * 60 * 1000 + 270;
runningSw = function() {
return new Stopwatch({
startTime: Date.now(),
totalElapsed: 0,
state: Stopwatch.RUNNING,
laps: []
});
};
sevenMinSw = function() {
return new Stopwatch({
startTime: Date.now() - sevenMin,
totalElapsed: 0,
state: Stopwatch.RUNNING,
laps: []
});
};
fourSecPausedSw = function() {
return new Stopwatch({
startTime: Date.now(),
totalElapsed: fourSec,
state: Stopwatch.PAUSED,
laps: []
});
};
withLapsSw = function() {
var sw = new Stopwatch();
sw.start();
clock.tick(threeMin);
panel.update();
sw.lap();
clock.tick(fourSec);
panel.update();
sw.lap();
clock.tick(thirtyMins + threeMin * 3 + fourSec * 2);
panel.update();
return sw;
};
isHidden = function(element) {
return element.className.contains('hidden');
};
require([
'stopwatch', 'panels/stopwatch/main'
], function(stopwatch, stopwatchPanel) {
Stopwatch = stopwatch;
Stopwatch.Panel = stopwatchPanel;
panel = new Stopwatch.Panel(
document.createElement('div')
);
done();
}
);
});
setup(function() {
clock = this.sinon.useFakeTimers();
});
test('Default', function() {
assert.isFalse(isHidden(panel.nodes.start));
assert.isFalse(isHidden(panel.nodes.reset));
assert.isTrue(isHidden(panel.nodes.pause));
assert.isTrue(isHidden(panel.nodes.lap));
assert.isTrue(isHidden(panel.nodes.resume));
});
test('Seven Minute Running', function(done) {
var x = sevenMinSw();
panel.setStopwatch(x).then(() => {
assert.equal(panel.nodes.time.textContent, JSON.stringify({
value: 420170,
options: {
minUnit: 'millisecond',
maxUnit: 'minute'
}
}));
assert.isFalse(isHidden(panel.nodes.pause));
assert.isFalse(isHidden(panel.nodes.lap));
assert.isTrue(isHidden(panel.nodes.start));
assert.isTrue(isHidden(panel.nodes.reset));
assert.isTrue(isHidden(panel.nodes.resume));
}).then(() => {
clock.tick(3000);
// required due to requestAnimationFrame not being called
return panel.update();
}).then(() => {
assert.equal(panel.nodes.time.textContent, JSON.stringify({
value: 420170 + 3000,
options: {
minUnit: 'millisecond',
maxUnit: 'minute'
}
}));
}).then(done, done);
});
test('Pause a stopwatch', function(done) {
panel.setStopwatch(sevenMinSw());
panel.nodes.pause.click();
clock.tick(3000);
// required due to requestAnimationFrame not being called
panel.update().then(() => {
assert.equal(panel.nodes.time.textContent, JSON.stringify({
value: 420170,
options: {
minUnit: 'millisecond',
maxUnit: 'minute'
}
}));
}).then(done, done);
});
test('Four Second Paused', function(done) {
panel.setStopwatch(fourSecPausedSw());
// required due to requestAnimationFrame not being called
panel.update().then(() => {
assert.equal(panel.nodes.time.textContent, JSON.stringify({
value: 4230,
options: {
minUnit: 'millisecond',
maxUnit: 'minute'
}
}));
assert.isFalse(isHidden(panel.nodes.resume));
assert.isFalse(isHidden(panel.nodes.reset));
assert.isTrue(isHidden(panel.nodes.pause));
assert.isTrue(isHidden(panel.nodes.start));
assert.isTrue(isHidden(panel.nodes.lap));
clock.tick(3000);
// required due to requestAnimationFrame not being called
return panel.update();
}).then(() => {
assert.equal(panel.nodes.time.textContent, JSON.stringify({
value: 4230,
options: {
minUnit: 'millisecond',
maxUnit: 'minute'
}
}));
}).then(done, done);
});
test('Resume a stopwatch', function(done) {
panel.setStopwatch(fourSecPausedSw());
panel.nodes.resume.click();
clock.tick(3000);
// required due to requestAnimationFrame not being called
panel.update().then(() => {
assert.equal(panel.nodes.time.textContent, JSON.stringify({
value: 7230,
options: {
minUnit: 'millisecond',
maxUnit: 'minute'
}
}));
}).then(done, done);
});
test('Pre-existing laps', function(done) {
var tmp = withLapsSw();
panel.setStopwatch(tmp).then(() => {
var laps = panel.nodes.laps.querySelectorAll('li');
assert.equal(laps.length, 3);
assert.equal(laps[0].children[1].textContent, JSON.stringify({
value: 2349120,
options: {
minUnit: 'millisecond',
maxUnit: 'minute'
}
}));
assert.equal(laps[1].children[1].textContent, JSON.stringify({
value: 4230,
options: {
minUnit: 'millisecond',
maxUnit: 'minute'
}
}));
assert.equal(laps[2].children[1].textContent, JSON.stringify({
value: 180130,
options: {
minUnit: 'millisecond',
maxUnit: 'minute'
}
}));
}).then(done, done);
});
test('Add laps', function(done) {
function getLapInfo() {
var laps = panel.nodes.laps.querySelectorAll('li');
laps = Array.prototype.slice.call(laps);
return laps.map(function(e) {
return {
lapName: e.children[0],
lapTime: e.children[1].textContent.trim()
};
});
}
panel.setStopwatch(runningSw()).then(() => {
assert.equal(panel.nodes.time.textContent, JSON.stringify({
value: 0,
options: {
minUnit: 'millisecond',
maxUnit: 'minute'
}
}));
}).then(() => {
//Advance and click the lap button
clock.tick(3000);
// required due to requestAnimationFrame not being called
return panel.update();
}).then(() => {
assert.equal(panel.nodes.time.textContent, JSON.stringify({
value: 3000,
options: {
minUnit: 'millisecond',
maxUnit: 'minute'
}
}));
panel.nodes.lap.click();
}).then(() => {
var laps = getLapInfo();
assert.equal(laps.length, 2);
assert.equal(laps[1].lapTime, JSON.stringify({
value: 3000,
options: {
minUnit: 'millisecond',
maxUnit: 'minute'
}
}));
//Advance and add another lap
clock.tick(9000);
// required due to requestAnimationFrame not being called
return panel.update();
}).then(() => {
assert.equal(panel.nodes.time.textContent, JSON.stringify({
value: 9000 + 3000,
options: {
minUnit: 'millisecond',
maxUnit: 'minute'
}
}));
panel.nodes.lap.click();
}).then(() => {
var laps = getLapInfo();
assert.equal(laps.length, 3);
assert.equal(laps[1].lapTime, JSON.stringify({
value: 9000,
options: {
minUnit: 'millisecond',
maxUnit: 'minute'
}
}));
}).then(done, done);
});
test('Reset stopwatch', function(done) {
var laps;
panel.setStopwatch(runningSw()).then(() => {
assert.equal(panel.nodes.time.textContent, JSON.stringify({
value: 0,
options: {
minUnit: 'millisecond',
maxUnit: 'minute'
}
}));
//Advance and click the lap button (twice)
clock.tick(3000);
// required due to requestAnimationFrame not being called
panel.update();
panel.nodes.lap.click();
clock.tick(9000);
// required due to requestAnimationFrame not being called
panel.update();
panel.nodes.lap.click();
}).then(() => {
laps = panel.nodes.laps.querySelectorAll('li');
assert.equal(laps.length, 3);
//Reset
panel.nodes.pause.click();
panel.nodes.reset.click();
}).then(() => {
laps = panel.nodes.laps.querySelectorAll('li');
assert.equal(laps.length, 0);
assert.equal(panel.nodes.time.textContent, JSON.stringify({
value: 0,
options: {
minUnit: 'millisecond',
maxUnit: 'minute'
}
}));
}).then(done, done);
});
test('Stopwatch 100 minutes display', function(done) {
panel.setStopwatch(runningSw());
assert.ok(!panel.nodes.time.classList.contains('over-100-minutes'));
clock.tick(1000 * 60 * 105); // over 100 minutes
// required due to requestAnimationFrame not being called
panel.update().then(() => {
assert.ok(panel.nodes.time.classList.contains('over-100-minutes'));
}).then(done, done);
});
});

View file

@ -0,0 +1,390 @@
'use strict';
/* global asyncStorage, MockL10n, MockMozIntl, MockIntlHelper */
suite('Timer.Panel', function() {
var clock, activeAlarm;
var isHidden;
var View, Timer, Utils;
suiteSetup(function(done) {
document.l10n = MockL10n;
window.mozIntl = MockMozIntl;
window.IntlHelper = MockIntlHelper;
window.IntlHelper.define('digit-nopadding', 'number', {
style: 'decimal',
useGrouping: false
});
isHidden = function(element) {
return element.className.contains('hidden');
};
require(['panels/alarm/active_alarm', 'timer', 'panels/timer/main',
'view', 'utils'],
function(ActiveAlarm, timer, timerPanel, view, utils) {
Timer = timer;
Timer.Panel = timerPanel;
View = view;
Utils = utils;
activeAlarm = new ActiveAlarm();
done();
});
});
setup(function() {
clock = this.sinon.useFakeTimers();
});
test('shape:prototype ', function() {
assert.ok(Timer.Panel);
assert.ok(Timer.Panel.prototype.showDialog);
assert.ok(Timer.Panel.prototype.hideDialog);
assert.ok(Timer.Panel.prototype.update);
assert.ok(Timer.Panel.prototype.onclick);
});
test('shape:instance ', function() {
var panel = new Timer.Panel(document.createElement('div'));
assert.ok(panel.nodes);
assert.isNull(panel.timer);
});
test('dialog ', function() {
var panel = new Timer.Panel(document.createElement('div'));
var dialog = View.instance(panel.nodes.dialog);
panel.showDialog();
assert.isTrue(dialog.visible);
panel.hideDialog();
assert.isFalse(dialog.visible);
});
test('update ', function(done) {
var panel = new Timer.Panel(document.createElement('div'));
// The timer panel should display rounded seconds.
var timer = { remaining: 10000 };
panel.timer = timer;
panel.update().then(() => {
assert.equal(panel.nodes.time.textContent, JSON.stringify({
value: 10000,
options: {
minUnit: 'second',
maxUnit: 'hour'
}
}));
}).then(done, done);
});
test('toggleButtons ', function() {
var panel = new Timer.Panel(document.createElement('div'));
var start = panel.nodes.start;
var pause = panel.nodes.pause;
panel.timer = {};
panel.timer.state = Timer.PAUSED;
panel.toggleButtons();
assert.isFalse(isHidden(start));
assert.isTrue(isHidden(pause));
panel.timer.state = Timer.STARTED;
panel.toggleButtons();
assert.isTrue(isHidden(start));
assert.isFalse(isHidden(pause));
});
test('Set timer state (paused)', function(done) {
var now = Date.now();
var oneHour = 60 * 60 * 1000;
var timer = new Timer({
startTime: now,
configuredDuration: oneHour
});
timer.start();
var panel = new Timer.Panel(document.createElement('div'));
panel.timer = timer;
panel.onvisibilitychange({ detail: { isVisible: true } });
panel.update().then(() => {
assert.equal(panel.nodes.time.textContent, JSON.stringify({
value: oneHour,
options: {
minUnit: 'second',
maxUnit: 'hour'
}
}));
clock.tick(5000);
return panel.update();
}).then(() => {
assert.equal(panel.nodes.time.textContent, JSON.stringify({
value: oneHour - 5000,
options: {
minUnit: 'second',
maxUnit: 'hour'
}
}));
panel.onclick({
target: panel.nodes.pause
});
panel.onTimerEvent({ type: 'timer-pause' });
return panel.update();
}).then(() => {
assert.isTrue(isHidden(panel.nodes.dialog));
assert.isTrue(isHidden(panel.nodes.pause));
assert.isFalse(isHidden(panel.nodes.start));
assert.isFalse(isHidden(panel.nodes.time));
assert.isFalse(isHidden(panel.nodes.cancel));
assert.equal(panel.nodes.time.textContent, JSON.stringify({
value: oneHour - 5000,
options: {
minUnit: 'second',
maxUnit: 'hour'
}
}));
clock.tick(5000);
return panel.update();
}).then(() => {
assert.equal(panel.nodes.time.textContent, JSON.stringify({
value: oneHour - 5000,
options: {
minUnit: 'second',
maxUnit: 'hour'
}
}));
}).then(done, done);
});
test('Set timer state (started)', function(done) {
var now = Date.now();
var oneHour = 60 * 60 * 1000;
var timer = new Timer({
configuredDuration: now + oneHour
});
timer.start();
var panel = new Timer.Panel(document.createElement('div'));
panel.timer = timer;
panel.onvisibilitychange({ detail: { isVisible: true } });
assert.isTrue(isHidden(panel.nodes.dialog));
assert.isTrue(isHidden(panel.nodes.start));
assert.isFalse(isHidden(panel.nodes.time));
assert.isFalse(isHidden(panel.nodes.pause));
assert.isFalse(isHidden(panel.nodes.cancel));
Promise.resolve().then(() => {
assert.equal(panel.nodes.time.textContent, JSON.stringify({
value: oneHour,
options: {
minUnit: 'second',
maxUnit: 'hour'
}
}));
clock.tick(5000);
return panel.update();
}).then(() => {
assert.equal(panel.nodes.time.textContent, JSON.stringify({
value: oneHour - 5000,
options: {
minUnit: 'second',
maxUnit: 'hour'
}
}));
}).then(done, done);
});
test('Create button is disabled when picker is set to 0:00', function() {
var panel = new Timer.Panel(document.createElement('div'));
var create = panel.nodes.create;
var nodes = panel.picker.nodes;
create = {
setAttribute: function() {},
removeAttribute: function() {}
};
this.sinon.spy(create, 'removeAttribute');
this.sinon.spy(create, 'setAttribute');
assert.isTrue(panel.nodes.create.disabled);
panel.picker.value = '3:00';
nodes.hours.dispatchEvent(
new CustomEvent('transitionend')
);
assert.isFalse(panel.nodes.create.disabled);
panel.picker.value = '0:00';
nodes.hours.dispatchEvent(
new CustomEvent('transitionend')
);
assert.isTrue(panel.nodes.create.disabled);
panel.picker.value = '0:15';
nodes.minutes.dispatchEvent(
new CustomEvent('transitionend')
);
assert.isFalse(panel.nodes.create.disabled);
panel.picker.value = '0:00';
nodes.minutes.dispatchEvent(
new CustomEvent('transitionend')
);
assert.isTrue(panel.nodes.create.disabled);
});
suite('Timer.Panel, Events', function() {
var panel;
setup(function() {
this.sinon.spy(Timer.Panel.prototype, 'onclick');
asyncStorage.getItem = function(key, callback) {
callback({duration: 5000});
};
panel = new Timer.Panel(document.createElement('div'));
this.sinon.spy(panel.timer, 'start');
this.sinon.spy(panel.timer, 'pause');
this.sinon.spy(panel.timer, 'cancel');
this.sinon.spy(panel.nodes.sound, 'focus');
this.sinon.spy(panel.timer, 'plus');
});
test('click: start ', function() {
var start = panel.nodes.start;
start.dispatchEvent(
new CustomEvent('click')
);
assert.ok(panel.onclick.called);
assert.ok(panel.timer.start.called);
});
test('click: pause ', function() {
var pause = panel.nodes.pause;
pause.dispatchEvent(
new CustomEvent('click')
);
assert.ok(panel.onclick.called);
assert.ok(panel.timer.pause.called);
});
test('click: cancel ', function() {
var cancel = panel.nodes.cancel;
var timer = panel.timer;
cancel.dispatchEvent(
new CustomEvent('click')
);
assert.ok(panel.onclick.called);
assert.ok(timer.cancel.called);
});
test('click: create', function() {
panel.picker.value = '1:00';
panel.picker.nodes.hours.dispatchEvent(
new CustomEvent('transitionend')
);
panel.nodes.create.dispatchEvent(
new CustomEvent('click')
);
assert.ok(panel.onclick.called);
// Duration from picker value
assert.equal(panel.timer.duration, 3600000);
});
test('click: plus ', function() {
var plus = panel.nodes.plus;
var timer = panel.timer;
plus.dispatchEvent(
new CustomEvent('click')
);
assert.ok(panel.onclick.called);
assert.ok(timer.plus.called);
});
test('blur: sound', function(done) {
var menu = panel.soundButton.button;
var sound = panel.nodes.sound;
Utils.changeSelectByValue(sound, 'ac_digicloud.opus');
sound.dispatchEvent(
new CustomEvent('blur')
);
Promise.resolve().then(() => {
assert.equal(menu.getAttribute('data-l10n-id'), 'ac_digicloud_opus');
}).then(done, done);
});
test('change: sound', function() {
var sound = panel.nodes.sound;
Utils.changeSelectByValue(sound, 'ac_digicloud.opus');
var mockAudio = {
pause: this.sinon.spy(),
play: this.sinon.spy(),
addEventListener: function() { },
load: function() { }
};
this.sinon.stub(window, 'Audio').returns(mockAudio);
sound.dispatchEvent(
new CustomEvent('change')
);
assert.isTrue(mockAudio.play.called);
assert.isTrue(mockAudio.loop);
assert.equal(mockAudio.mozAudioChannelType, 'alarm');
var expected = 'shared/resources/media/alarms/ac_digicloud.opus';
assert.equal(mockAudio.src, expected);
});
test('blur: pause playing alarm', function() {
var sound = panel.nodes.sound;
Utils.changeSelectByValue(sound, 'ac_digicloud.opus');
var mockAudio = {
pause: this.sinon.spy(),
play: this.sinon.spy(),
addEventListener: function() { },
load: function() { }
};
this.sinon.stub(window, 'Audio').returns(mockAudio);
sound.dispatchEvent(
new CustomEvent('change')
);
assert.isTrue(mockAudio.play.called);
assert.isTrue(mockAudio.pause.calledOnce);
sound.dispatchEvent(
new CustomEvent('blur')
);
assert.isTrue(mockAudio.pause.calledTwice);
});
});
});

View file

@ -0,0 +1,85 @@
'use strict';
/* global MockIntlHelper */
suite('Picker', function() {
var Picker, Spinner;
suiteSetup(function(done) {
window.IntlHelper = MockIntlHelper;
window.IntlHelper.define('digit-nopadding', 'number', {
style: 'decimal',
useGrouping: false
});
require(['picker/picker', 'picker/spinner'],
function(picker, spinner) {
Picker = picker;
Spinner = spinner;
done();
});
});
test('shape:prototype ', function() {
assert.ok(Picker);
assert.typeOf(Picker.prototype.reset, 'function');
assert.typeOf(Picker.prototype.value, 'null');
assert.isNull(Picker.prototype.value);
});
var picker;
setup(function() {
var pickerEl = document.createElement('div');
pickerEl.appendChild(document.createElement('div'))
.classList.add('picker-hours');
pickerEl.appendChild(document.createElement('div'))
.classList.add('picker-minutes');
picker = new Picker({
element: pickerEl,
pickers: {
hours: {
range: [0, 23]
},
minutes: {
range: [0, 59],
isPadded: true
}
}
});
});
test('shape:instance ', function() {
assert.typeOf(picker.nodes, 'object');
assert.typeOf(picker.spinners, 'object');
assert.typeOf(picker.pickers, 'Array');
assert.notEqual(typeof picker.nodes.hours, 'undefined');
assert.notEqual(typeof picker.nodes.minutes, 'undefined');
assert.notEqual(typeof picker.spinners.hours, 'undefined');
assert.notEqual(typeof picker.spinners.hours, 'undefined');
assert.deepEqual(picker.pickers, ['hours', 'minutes']);
});
test('values ', function() {
var spinners = picker.spinners;
assert.equal(spinners.hours.values[0], 0);
assert.equal(spinners.hours.values[23], 23);
assert.equal(spinners.hours.values[24], undefined);
assert.equal(spinners.minutes.values[0], 0);
assert.equal(spinners.minutes.values[59], 59);
assert.equal(spinners.minutes.values[60], undefined);
assert.equal(picker.value, '0:0');
});
test('get and set value ', function() {
picker.value = '1:59';
var spinners = picker.spinners;
assert.equal(spinners.hours.value, 1);
assert.equal(spinners.minutes.value, 59);
});
});

View file

@ -0,0 +1,396 @@
suite('Spinner', function() {
'use strict';
/* global MockIntlHelper, IntlHelper */
var Spinner, GestureDetector;
var _clientHeight;
var VALUE_ELEMENT_HEIGHT = 20;
suiteSetup(function(done) {
window.IntlHelper = MockIntlHelper;
IntlHelper.define('digit-nopadding', 'number', {
style: 'decimal',
useGrouping: false
});
// override native clientHeight
_clientHeight = Object.getOwnPropertyDescriptor(
Element.prototype, 'clientHeight');
Object.defineProperty(Element.prototype, 'clientHeight', {
configurable: true,
enumerable: true,
get: function clockTestClientHeightStub() {
if (this.classList.contains('picker-unit')) {
return VALUE_ELEMENT_HEIGHT;
}
return _clientHeight.get.call(this);
}
});
require([
'picker/spinner',
'shared/js/gesture_detector'
], function(spinner, GD) {
Spinner = spinner;
GestureDetector = GD;
done();
});
});
suiteTeardown(function() {
Object.defineProperty(Element.prototype, 'clientHeight', _clientHeight);
});
setup(function() {
VALUE_ELEMENT_HEIGHT = 20;
this.container = document.createElement('div');
this.container.classList.add('test-spinner-container');
this.element = document.createElement('div');
this.element.classList.add('test-spinner-element');
this.container.appendChild(this.element);
document.body.appendChild(this.container);
this.clock = this.sinon.useFakeTimers();
});
function checkTranslate(value) {
test('Checking translate is ' + value, function() {
assert.equal(this.element.style.transform,
'translateY(' + value + 'px)');
assert.equal(this.spinner.top, value);
});
}
function checkIndex(index) {
test('Checking index is ' + index, function() {
assert.equal(this.spinner.index, index);
assert.equal(this.spinner.value, this.spinner.values[index]);
});
}
function checkCreation() {
checkTranslate(0);
checkIndex(0);
}
suite('values: [0, 1, 2]', function() {
setup(function() {
this.spinner = new Spinner({
element: this.element,
values: [0, 1, 2]
});
});
checkCreation();
suite('select option 2', function() {
setup(function() {
this.spinner.select(2);
});
checkTranslate(-40);
checkIndex(2);
});
suite('select option 3 (out of range)', function() {
setup(function() {
this.spinner.select(3);
});
checkTranslate(-40);
checkIndex(2);
});
suite('set value 2', function() {
setup(function() {
this.spinner.value = 2;
});
checkTranslate(-40);
checkIndex(2);
});
suite('set value out of range', function() {
setup(function() {
this.spinner.value = 'nope';
});
checkTranslate(0);
checkIndex(0);
});
suite('touchstart', function() {
setup(function() {
this.event = new CustomEvent('touchstart', {
cancelable: true,
bubbles: true
});
this.event.changedTouches = [];
this.element.dispatchEvent(this.event);
});
test('default is prevented', function() {
assert.ok(this.event.defaultPrevented);
});
});
suite('pan down by 15px over 1 second', function() {
setup(function() {
var event;
event = new CustomEvent('pan', {
detail: {
position: {
clientY: 0,
timeStamp: 0
}
},
bubbles: true
});
this.container.dispatchEvent(event);
event = new CustomEvent('pan', {
detail: {
position: {
clientY: 15,
timeStamp: 1000
}
},
bubbles: true
});
this.container.dispatchEvent(event);
});
// top should still be at zero, can't go "down" in the spinner
checkTranslate(0);
});
suite('pan up by 15px over 1 second', function() {
setup(function() {
var event;
event = new CustomEvent('pan', {
detail: {
position: {
clientY: 15,
timeStamp: 0
}
},
bubbles: true
});
this.container.dispatchEvent(event);
event = new CustomEvent('pan', {
detail: {
position: {
clientY: 0,
timeStamp: 1000
}
},
bubbles: true
});
this.container.dispatchEvent(event);
});
checkTranslate(-15);
checkIndex(1);
suite('pan finished w/ low (therefore zero) momentum', function() {
setup(function() {
var event = new CustomEvent('swipe', {
bubbles: true
});
this.container.dispatchEvent(event);
});
checkTranslate(-20);
checkIndex(1);
test('animation-on', function() {
assert.ok(this.element.classList.contains('animation-on'));
});
});
});
});
suite('values: [0...30]', function() {
setup(function() {
this.spinner = new Spinner({
element: this.element,
values: [0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16,
17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27, 28, 29, 30]
});
});
checkCreation();
suite('pan up by 35px over 70ms', function() {
setup(function() {
var event;
event = new CustomEvent('pan', {
detail: {
position: {
clientY: 35,
timeStamp: 0
}
},
bubbles: true
});
this.container.dispatchEvent(event);
event = new CustomEvent('pan', {
detail: {
position: {
clientY: 0,
timeStamp: 70
}
},
bubbles: true
});
this.container.dispatchEvent(event);
});
checkTranslate(-35);
checkIndex(2);
suite('pan finished w/ momentum', function() {
setup(function() {
var event = new CustomEvent('swipe', {
bubbles: true
});
this.container.dispatchEvent(event);
});
checkTranslate(-200);
checkIndex(10);
test('animation-on', function() {
assert.ok(this.element.classList.contains('animation-on'));
});
});
suite('pan times out', function() {
setup(function() {
this.clock.tick(201);
});
checkTranslate(-40);
checkIndex(2);
test('animation-on', function() {
assert.ok(this.element.classList.contains('animation-on'));
});
});
});
suite('pan up by 100px over 1ms', function() {
setup(function() {
var event;
event = new CustomEvent('pan', {
detail: {
position: {
clientY: 100,
timeStamp: 0
}
},
bubbles: true
});
this.container.dispatchEvent(event);
event = new CustomEvent('pan', {
detail: {
position: {
clientY: 0,
timeStamp: 1
}
},
bubbles: true
});
this.container.dispatchEvent(event);
});
checkTranslate(-100);
checkIndex(5);
suite('pan finished w/ insane momentum', function() {
setup(function() {
var event = new CustomEvent('swipe', {
bubbles: true
});
this.container.dispatchEvent(event);
});
checkTranslate(-400);
checkIndex(20);
test('animation-on', function() {
assert.ok(this.element.classList.contains('animation-on'));
});
suite('start pan', function() {
setup(function() {
var event = new CustomEvent('pan', {
detail: {
position: {
clientY: 0,
timeStamp: 0
}
},
bubbles: true
});
this.container.dispatchEvent(event);
});
test('animation-on removed', function() {
assert.isFalse(
this.element.classList.contains('animation-on'));
});
suite('finish pan down 20px over 100ms', function() {
// this is a smaller scale pan, moving one unit per 100ms
setup(function() {
var event = new CustomEvent('pan', {
detail: {
position: {
clientY: 20,
timeStamp: 100
}
},
bubbles: true
});
this.container.dispatchEvent(event);
event = new CustomEvent('swipe', {
bubbles: true
});
this.container.dispatchEvent(event);
});
// total difference = 4, 1 moved + 3 inertia
checkTranslate(-320);
checkIndex(16);
});
});
});
suite('pan times out', function() {
setup(function() {
this.clock.tick(201);
});
checkTranslate(-100);
checkIndex(5);
test('animation-on', function() {
assert.ok(this.element.classList.contains('animation-on'));
});
});
});
});
suite('values: [0...30] - big elements', function() {
// results should have same index, as previous test,
// but double the px values for translates and motion because of bigger
// elements
setup(function() {
VALUE_ELEMENT_HEIGHT = 40;
this.spinner = new Spinner({
element: this.element,
values: [0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16,
17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27, 28, 29, 30]
});
});
checkCreation();
suite('pan up by 70px over 70ms', function() {
setup(function() {
var event;
event = new CustomEvent('pan', {
detail: {
position: {
clientY: 70,
timeStamp: 0
}
},
bubbles: true
});
this.container.dispatchEvent(event);
event = new CustomEvent('pan', {
detail: {
position: {
clientY: 0,
timeStamp: 70
}
},
bubbles: true
});
this.container.dispatchEvent(event);
});
checkTranslate(-70);
checkIndex(2);
suite('pan finished w/ momentum', function() {
setup(function() {
var event = new CustomEvent('swipe', {
bubbles: true
});
this.container.dispatchEvent(event);
});
checkTranslate(-400);
checkIndex(10);
test('animation-on', function() {
assert.ok(this.element.classList.contains('animation-on'));
});
});
});
});
});

View file

@ -0,0 +1,75 @@
'use strict';
suite('PostMessageProxy', function() {
var PostMessageProxy;
suiteSetup(function(done) {
require(['panels/alarm/post_message_proxy'], function(proxy) {
PostMessageProxy = proxy;
done();
});
});
test('sends and receives a message', function(done) {
var proxy = PostMessageProxy.create(window, 'me');
PostMessageProxy.receive('me', {
method: function(arg) {
assert.ok(arg === 'argument');
done();
}
});
proxy.method('argument');
});
test('passes a return value', function(done) {
var proxy = PostMessageProxy.create(window, 'me');
PostMessageProxy.receive('me', {
method: function(arg) {
return 'SOME RESULT';
}
});
proxy.method('argument').then(function(value) {
assert.equal(value, 'SOME RESULT');
done();
});
});
function MyError(message) {
this.name = 'MyError';
}
MyError.prototype = new Error();
MyError.prototype.constructor = MyError;
test('passes an exception', function(done) {
var proxy = PostMessageProxy.create(window, 'me');
PostMessageProxy.receive('me', {
method: function(arg) {
throw new MyError('foo');
}
});
proxy.method('argument').catch(function(error) {
assert.equal(error.name, 'MyError');
assert.ok(error.stack.length > 0);
done();
});
});
test('only receives messages for proper ID', function(done) {
PostMessageProxy.create(window, 'proxy1');
var proxy2 = PostMessageProxy.create(window, 'proxy2');
PostMessageProxy.receive('proxy1', {
callProxy: function(arg) {
assert.fail('should not have received this');
done();
}
});
PostMessageProxy.receive('proxy2', {
callProxy: function(arg) {
done();
}
});
proxy2.callProxy();
});
});

View file

@ -0,0 +1,32 @@
'use strict';
requireApp('clock/test/unit/mocks/mock_moz_alarm.js');
require('/shared/test/unit/load_body_html_helper.js');
require('/shared/test/unit/mocks/mock_intl_helper.js');
require('/shared/test/unit/mocks/mock_l20n.js');
require('/shared/test/unit/mocks/mock_moz_intl.js');
requireApp('clock/js/alameda.js', () => {
this.require = requirejs.config({
baseUrl: '/js',
paths: {
shared: '../shared',
mocks: '../test/unit/mocks'
},
urlArgs: 'cache_bust=' + Date.now(),
shim: {
'shared/js/template': {
exports: 'Template'
},
'shared/js/gesture_detector': {
exports: 'GestureDetector'
},
'shared/js/accessibility_helper': {
exports: 'AccessibilityHelper'
},
'shared/js/async_storage': {
exports: 'asyncStorage'
},
}
});
});

View file

@ -0,0 +1,309 @@
'use strict';
suite('Stopwatch', function() {
var Stopwatch;
var oneHour = 1 * 60 * 60 * 1000;
suiteSetup(function(done) {
require(['stopwatch'], function(stopwatch) {
Stopwatch = stopwatch;
this.sw = new Stopwatch();
done();
}.bind(this));
});
setup(function() {
this.clock = this.sinon.useFakeTimers();
});
teardown(function() {
this.clock.restore();
this.sw.reset();
});
suite('constructor', function() {
test('defaults', function() {
assert.isTrue(this.sw.getState() === Stopwatch.RESET);
assert.equal(this.sw.getElapsedTime(), 0);
assert.deepEqual(this.sw.getLaps(), []);
});
test('from object', function() {
var input = {
startTime: Date.now(),
totalElapsed: oneHour,
state: Stopwatch.RUNNING,
laps: [{time: Date.now(), duration: oneHour}]
};
this.sw = new Stopwatch(input);
assert.deepEqual(input, this.sw.toSerializable());
});
test('deep copy', function() {
var input = {
startTime: Date.now(),
totalElapsed: oneHour,
isStarted: true,
laps: [{time: Date.now(), duration: oneHour}]
};
this.sw = new Stopwatch(input);
var serialized = this.sw.toSerializable();
serialized.laps.push({ time: 0, duration: oneHour });
assert.notEqual(serialized.laps, input.laps, 'laps should not be equal');
});
});
suite('isRunning', function() {
test('before start', function() {
assert.isFalse(this.sw.getState() === Stopwatch.RUNNING);
assert.isFalse(this.sw.getState() === Stopwatch.PAUSED);
assert.isTrue(this.sw.getState() === Stopwatch.RESET);
});
test('start and elapse 1hr', function() {
this.sw.start();
this.clock.tick(oneHour);
assert.isTrue(this.sw.getState() === Stopwatch.RUNNING);
});
test('elapse 1hr and pause', function() {
this.sw.start();
this.clock.tick(oneHour);
this.sw.pause();
assert.isTrue(this.sw.getState() === Stopwatch.PAUSED);
});
test('pause and resume', function() {
this.sw.start();
this.clock.tick(oneHour);
this.sw.pause();
this.clock.tick(oneHour);
this.sw.start();
assert.isTrue(this.sw.getState() === Stopwatch.RUNNING);
});
test('pause and reset', function() {
this.sw.start();
this.clock.tick(oneHour);
this.sw.pause();
this.clock.tick(oneHour);
this.sw.reset();
assert.isTrue(this.sw.getState() === Stopwatch.RESET);
});
});
suite('getElapsedTime', function() {
test('before start', function() {
assert.equal(this.sw.getElapsedTime(), 0);
});
test('after start', function() {
this.sw.start();
this.clock.tick(oneHour);
assert.equal(this.sw.getElapsedTime(), oneHour);
});
});
suite('pause', function() {
setup(function() {
this.sw.start();
this.clock.tick(oneHour);
});
test('elapse 1hr & pause', function() {
this.sw.pause();
this.clock.tick(oneHour); // do not elapse this hour
assert.equal(this.sw.getElapsedTime(), oneHour);
});
test('pause & elapse 1 hr & resume', function() {
this.sw.pause();
this.clock.tick(oneHour); // do not elapse this hour
this.sw.start();
this.clock.tick(oneHour);
assert.equal(this.sw.getElapsedTime(), oneHour + oneHour);
});
});
suite('lap', function() {
test('before start', function() {
var l = this.sw.lap();
assert.equal(l, 0);
});
test('elapse 1hr & lap', function() {
this.sw.start();
this.clock.tick(oneHour);
var l = this.sw.lap();
assert.deepEqual(l, { duration: oneHour, time: oneHour });
});
test('3 times', function() {
this.sw.start();
this.clock.tick(oneHour);
var l1 = this.sw.lap();
this.clock.tick(oneHour);
var l2 = this.sw.lap();
this.clock.tick(oneHour);
var l3 = this.sw.lap();
assert.deepEqual(l1, { duration: oneHour, time: oneHour });
assert.deepEqual(l2, { duration: oneHour, time: 2 * oneHour });
assert.deepEqual(l3, { duration: oneHour, time: 3 * oneHour });
});
test('lap & pause & start & lap', function() {
this.sw.start();
this.clock.tick(oneHour);
var l1 = this.sw.lap();
this.sw.pause();
this.clock.tick(oneHour);
this.sw.start();
this.clock.tick(oneHour);
var l2 = this.sw.lap();
assert.deepEqual(l1, { duration: oneHour, time: oneHour });
assert.deepEqual(l2, { duration: oneHour, time: 2 * oneHour });
});
});
suite('getLaps', function() {
setup(function() {
this.sw.start();
this.clock.tick(oneHour);
});
test('after 3 laps', function() {
this.sw.lap();
this.clock.tick(oneHour);
this.sw.lap();
this.clock.tick(oneHour + oneHour);
this.sw.lap();
assert.equal(this.sw.getLaps()[0].duration, oneHour);
assert.equal(this.sw.getLaps()[1].duration, oneHour);
assert.equal(this.sw.getLaps()[2].duration, oneHour + oneHour);
});
});
suite('reset', function() {
setup(function() {
this.sw.start();
this.clock.tick(oneHour);
});
test('elapse 1hr & reset', function() {
this.sw.reset();
assert.equal(this.sw.getElapsedTime(), 0);
});
});
suite('toSerializable', function() {
test('default', function() {
var expected = {
startTime: 0,
totalElapsed: 0,
state: Stopwatch.RESET,
laps: []
};
var actual = this.sw.toSerializable();
assert.deepEqual(expected, actual);
});
suite('started', function() {
var d;
setup(function() {
d = Date.now();
this.sw.start();
this.clock.tick(oneHour);
});
test('deep copy', function() {
this.sw.pause();
var serialized = this.sw.toSerializable();
serialized.laps.push({ duration: 23, time: 0 });
assert.deepEqual(this.sw.getLaps(), []);
});
test('elapse 1hr', function() {
var expected = {
startTime: d,
totalElapsed: 0,
state: Stopwatch.RUNNING,
laps: []
};
var actual = this.sw.toSerializable();
assert.deepEqual(expected, actual);
});
test('elapse 1hr and pause', function() {
this.sw.pause();
var expected = {
startTime: d,
totalElapsed: oneHour,
state: Stopwatch.PAUSED,
laps: []
};
var actual = this.sw.toSerializable();
assert.deepEqual(expected, actual);
});
test('elapse 1hr and lap', function() {
this.sw.lap();
var expected = {
startTime: d,
totalElapsed: 0,
state: Stopwatch.RUNNING,
laps: [{time: Date.now(), duration: oneHour}]
};
var actual = this.sw.toSerializable();
assert.deepEqual(expected, actual);
});
test('pause and reset', function() {
this.sw.pause();
this.clock.tick(oneHour);
this.sw.reset();
var expected = {
startTime: 0,
totalElapsed: 0,
state: Stopwatch.RESET,
laps: []
};
var actual = this.sw.toSerializable();
assert.deepEqual(expected, actual);
});
test('pause and resume', function() {
this.sw.pause();
this.clock.tick(oneHour);
this.sw.start();
var expected = {
startTime: oneHour,
totalElapsed: oneHour,
state: Stopwatch.RUNNING,
laps: []
};
var actual = this.sw.toSerializable();
assert.deepEqual(expected, actual);
});
});
});
});

View file

@ -0,0 +1,39 @@
'use strict';
suite('Tabs', function() {
var Tabs;
suiteSetup(function(done) {
require(['tabs'], function(tabs) {
Tabs = tabs;
done();
});
});
setup(function() {
this.element = document.createElement('div');
this.element.innerHTML =
'<li aria-selected="true"><a href="#link-0"></a></li>' +
'<li><a href="#link-1"></a></li>' +
'<li><a href="#link-2"></a></li>' +
'<li><a href="#link-3"></a></li>';
this.links = this.element.querySelectorAll('a');
this.tabs = new Tabs(this.element);
});
suite('test click link 1', function() {
setup(function() {
this.links[0].click();
});
});
suite('test click link 2', function() {
setup(function() {
this.links[1].click();
});
test('test aria-selected set', function() {
['false', 'true', 'false'].forEach(function(value, index) {
assert.equal(this.links[index].getAttribute('aria-selected'), value);
}, this);
});
});
});

View file

@ -0,0 +1,201 @@
'use strict';
suite('Timer', function() {
/**** TEST DISABLED for intermittent failure; see bug 1153495.
var Timer;
var now, duration;
suiteSetup(function(done) {
require(['timer'], function(timer) {
Timer = timer;
done();
});
});
setup(function() {
now = Date.now();
duration = 5000;
this.clock = this.sinon.useFakeTimers(Date.now());
});
test('shape:static ', function() {
assert.equal(Timer.INITIAL, 0);
assert.equal(Timer.STARTED, 1);
assert.equal(Timer.PAUSED, 2);
});
function getTimer() {
return new Timer({
configuredDuration: duration
});
}
test('start new timer ', function() {
var timer = getTimer();
timer.start();
this.clock.tick(200);
assert.equal(timer.state, Timer.STARTED);
assert.equal(timer.remaining, duration - 200);
});
test('reactivate an un-paused timer ', function() {
var timer = getTimer();
timer.start();
this.clock.tick(200);
timer.start();
this.clock.tick(200);
timer.start();
assert.equal(timer.state, Timer.STARTED);
assert.equal(timer.remaining, duration - 400);
});
test('pause timer', function() {
var timer = getTimer();
timer.start();
this.clock.tick(200);
timer.pause();
assert.equal(timer.state, Timer.PAUSED);
});
test('reactivate a paused timer ', function() {
var timer = getTimer();
timer.start();
this.clock.tick(200);
timer.pause();
assert.equal(timer.state, Timer.PAUSED);
this.clock.tick(200);
timer.start();
assert.equal(timer.state, Timer.STARTED);
assert.equal(timer.remaining, duration - 200);
});
test('cancel ', function() {
var timer = getTimer();
timer.start();
this.clock.tick(200);
timer.cancel();
assert.equal(timer.state, Timer.INITIAL);
});
test('plus ', function() {
var timer = getTimer();
var originalDuration = timer.duration;
timer.plus(60);
assert.equal(timer.duration, originalDuration + (60 * 1000));
});
suite('commit', function() {
setup(function() {
this.addSpy = this.sinon.spy(navigator.mozAlarms, 'add');
this.removeSpy = this.sinon.spy(navigator.mozAlarms, 'remove');
this.timer = getTimer();
});
suite('timers without alarms', function() {
test('when in initial state', function(done) {
this.timer.commit(function() {
assert.equal(
this.addSpy.callCount, 0, 'does not create a new alarm'
);
assert.equal(this.removeSpy.callCount, 0, 'does not remove alarm');
done();
}.bind(this));
this.clock.tick(10);
});
test('when started', function(done) {
this.timer.start();
this.timer.commit(function() {
assert.equal(this.addSpy.callCount, 1, 'creates a new alarm');
assert.equal(this.removeSpy.callCount, 0, 'does not remove alarm');
done();
}.bind(this));
this.clock.tick(10);
});
});
suite('timers with previously-created alarms', function() {
setup(function(done) {
// Ensure that the timer is in a saved state and the spies are reset
this.timer.start();
this.timer.commit(function() {
this.addSpy.reset();
this.removeSpy.reset();
this.origTimerId = this.timer.id;
// Return timer to initial state
this.timer.cancel();
done();
}.bind(this));
this.clock.tick(10);
});
test('when in initial state', function(done) {
this.timer.commit(function() {
assert.equal(
this.addSpy.callCount, 0, 'does not create a new alarm'
);
assert.equal(
this.removeSpy.args[0][0], this.origTimerId, 'removes old alarm'
);
done();
}.bind(this));
this.clock.tick(10);
});
test('when started', function(done) {
this.timer.start();
this.timer.commit(function() {
assert.equal(
this.addSpy.callCount, 1, 'creates a new alarm'
);
assert.equal(
this.removeSpy.args[0][0], this.origTimerId, 'removes old alarm'
);
done();
}.bind(this));
this.clock.tick(10);
});
});
});
suite('Timer.remaining ', function() {
test('Initial time', function() {
var timer = getTimer();
timer.configuredDuration = 7200;
assert.equal(timer.remaining, 7200,
'Expected remaining time to equal configured time.');
});
test('Paused time', function() {
var timer = getTimer();
timer.start();
this.clock.tick(200);
timer.pause();
this.clock.tick(200);
assert.equal(timer.remaining, 4800,
'Expected paused time to be 4.8 seconds');
});
test('Started time', function() {
var timer = getTimer();
timer.start();
this.clock.tick(300);
assert.equal(timer.remaining, 4700,
'Expected started time to be 4.7 seconds');
});
});
*/
});

View file

@ -0,0 +1,610 @@
'use strict';
/* global MockIntlHelper, MockMozIntl */
suite('Time functions', function() {
var Utils;
var MockRequestWakeLock;
suiteSetup(function(done) {
window.IntlHelper = MockIntlHelper;
window.mozIntl = MockMozIntl;
require(['utils', 'mocks/mock_request_wake_lock'],
function(utils, _MockRequestWakeLock, l10n) {
Utils = utils;
MockRequestWakeLock = _MockRequestWakeLock;
done();
});
});
suite('#changeSelectByValue', function() {
var changeSelectByValue, selectDOM;
setup(function() {
changeSelectByValue = Utils.changeSelectByValue;
selectDOM = document.createElement('select');
selectDOM.innerHTML = ['<option value="a">A</option>',
'<option value="b">B</option>',
'<option value="c" selected>C</option>'
].join('');
});
test('correctly selects the specified element', function() {
changeSelectByValue(selectDOM, 'b');
assert.equal(selectDOM.selectedIndex, 1);
});
test('has no effect when specified element does not exist', function() {
changeSelectByValue(selectDOM, 'g');
assert.equal(selectDOM.selectedIndex, 2);
});
});
suite('#dateMath', function() {
suiteSetup(function() {
// The timestamp for 'Tue Jul 16 2013 06:00:00' GMT
this.sixAm = new Date(2013, 5, 16, 6).getTime();
// Set clock so calls to new Date() and Date.now() will not vary
// across test locales
this.dat = new Date(this.sixAm + 15120000);
this.allNeg = {
hours: -4,
minutes: -15,
seconds: -2
};
this.someNeg = {
hours: 4,
minutes: -15,
seconds: 2
};
this.overflow = {
hours: 4,
minutes: 0,
seconds: 902
};
this.skip = {
hours: 4,
seconds: 902
};
});
setup(function() {
this.clock = this.sinon.useFakeTimers(this.sixAm);
});
suite('toMS', function() {
test('returns correct millisecond value ', function() {
assert.equal(
Utils.dateMath.toMS(this.skip),
15302000
);
assert.equal(
Utils.dateMath.toMS(this.overflow),
15302000
);
});
test('any/all negative input result in negative output ', function() {
assert.ok(Utils.dateMath.toMS(this.someNeg) < 0);
assert.ok(Utils.dateMath.toMS(this.allNeg) < 0);
});
test('all positive input result in positive output ', function() {
assert.ok(Utils.dateMath.toMS(this.dat) > 0);
assert.ok(Utils.dateMath.toMS(this.skip) > 0);
});
test('accepts plural or singular unit names ', function() {
assert.equal(
Utils.dateMath.toMS(this.skip, {unitsPartial: ['hours', 'second']}),
15302000
);
});
});
suite('fromMS', function() {
suiteSetup(function() {
this.negtime = -140000;
this.hour = 1000 * 60 * 60;
this.fourish = 15120000;
});
test('returns a correctly formed object ', function() {
assert.deepEqual(
Utils.dateMath.fromMS(this.fourish, {
unitsPartial: ['hours', 'minutes']
}),
{
hours: 4,
minutes: 12
}
);
});
test('returns correct signs ', function() {
var pos, neg;
pos = Utils.dateMath.fromMS(this.hour);
neg = Utils.dateMath.fromMS(this.negtime);
assert.ok(Object.keys(pos).every(function(unit) {
return pos[unit] >= 0;
}));
assert.ok(Object.keys(neg).every(function(unit) {
return neg[unit] <= 0;
}));
});
test('returns desired granularity ', function() {
assert.deepEqual(
Utils.dateMath.fromMS(this.hour, {unitsPartial: ['minutes']}),
{
minutes: 60
}
);
assert.deepEqual(
Utils.dateMath.fromMS(this.fourish, {unitsPartial: ['minutes']}),
{
minutes: 252
}
);
});
test('accepts plural or singular unit names ', function() {
assert.deepEqual(
Utils.dateMath.fromMS(this.fourish, {
unitsPartial: ['hour', 'minute']
}),
{
hours: 4,
minutes: 12
}
);
});
});
});
suite('data tests', function() {
suite('compare tests', function() {
test('numbers', function() {
assert.equal(Utils.data.defaultCompare(42, 42), 0);
assert.equal(Utils.data.defaultCompare(0, 0), 0);
assert.equal(Utils.data.defaultCompare(0, 1), -1);
assert.equal(Utils.data.defaultCompare(1, 0), 1);
var e1, e2;
try {
Utils.data.defaultCompare(1, {});
} catch (err) {
e1 = err;
}
assert.ok(e1);
try {
Utils.data.defaultCompare({}, 1);
} catch (err) {
e2 = err;
}
assert.ok(e2);
});
test('strings', function() {
assert.equal(Utils.data.defaultCompare('abc', 'abc'), 0);
assert.equal(Utils.data.defaultCompare('abc', 'abd'), -1);
assert.equal(Utils.data.defaultCompare('abc', 'abb'), 1);
assert.equal(Utils.data.defaultCompare('abc', 'abcd'), -1);
assert.equal(Utils.data.defaultCompare('abc', 'ab'), 1);
var e;
try {
Utils.data.defaultCompare('abc', 1);
} catch (err) {
e = err;
}
assert.ok(e);
});
test('arrays', function() {
assert.equal(Utils.data.defaultCompare([1, 2, 3], [1, 2, 3]), 0);
assert.equal(Utils.data.defaultCompare([1, 2, 3], [1, 2, 4]), -1);
assert.equal(Utils.data.defaultCompare([1, 2, 3], [1, 2, 2]), 1);
var e;
try {
Utils.data.defaultCompare('abc', 1);
} catch (err) {
e = err;
}
assert.ok(e);
});
});
suite('binarySearch', function() {
var genMatchTest = function(arr, search, idx) {
test('length ' + arr.length + ', match for ' + JSON.stringify(search),
function() {
var res = Utils.data.binarySearch({ value: search }, arr,
Utils.data.keyedCompare('value'));
assert.deepEqual(res, {
match: true,
index: idx,
value: arr[idx]
});
});
};
var genNoMatchTest = function(arr, search, idx) {
test('length ' + arr.length + ', no match for ' +
JSON.stringify(search),
function() {
var res = Utils.data.binarySearch({ value: search }, arr,
Utils.data.keyedCompare('value'));
assert.deepEqual(res, {
match: false,
index: idx
});
});
};
var array14 = [
{value: 1}, {value: 11}, {value: 25},
{value: 28}, {value: 30}, {value: 36},
{value: 48}, {value: 54}, {value: 54},
{value: 59}, {value: 61}, {value: 82},
{value: 82}, {value: 85}
];
var array15 = [
{value: 1}, {value: 11}, {value: 25},
{value: 28}, {value: 30}, {value: 36},
{value: 48}, {value: 54}, {value: 54},
{value: 59}, {value: 61}, {value: 82},
{value: 82}, {value: 85}, {value: 98}
];
test('zero length', function() {
var res = Utils.data.binarySearch(34, []);
assert.deepEqual(res, { match: false, index: 0 });
});
test('length one, no match', function() {
var res = Utils.data.binarySearch(34, [5]);
assert.deepEqual(res, { match: false, index: 1 });
});
test('length one, match', function() {
var res = Utils.data.binarySearch(34, [34]);
assert.deepEqual(res, {
match: true,
index: 0,
value: 34
});
});
genMatchTest(array14, 1, 0);
genMatchTest(array14, 85, 13);
genNoMatchTest(array14, -1, 0);
genNoMatchTest(array14, 52, 7);
genNoMatchTest(array14, 101, 14);
genMatchTest(array15, 1, 0);
genMatchTest(array15, 98, 14);
genNoMatchTest(array15, -1, 0);
genNoMatchTest(array15, 52, 7);
genNoMatchTest(array15, 101, 15);
});
suite('sortedInsert', function() {
var array = [1, 8, 9, 19, 38, 42, 44, 56, 58, 64, 74, 82, 91];
test('zero length insert', function() {
var x = [];
var res = Utils.data.sortedInsert(3, x);
assert.deepEqual(x, [3]);
assert.equal(res, 0);
});
test('beginning insert', function() {
var x = array.slice();
var res = Utils.data.sortedInsert(-10, x);
assert.equal(x[0], -10);
assert.equal(x.length, array.length + 1);
assert.equal(res, 0);
});
test('middle insert', function() {
var x = array.slice();
var res = Utils.data.sortedInsert(43, x);
assert.equal(x[6], 43);
assert.equal(x.length, array.length + 1);
assert.equal(res, 6);
});
test('end insert', function() {
var x = array.slice();
var res = Utils.data.sortedInsert(99, x);
assert.equal(x[x.length - 1], 99);
assert.equal(x.length, array.length + 1);
assert.equal(res, array.length);
});
});
suite('sortedRemove', function() {
var array = [1, 8, 9, 19, 38, 42, 44, 56, 58, 64, 74, 82, 91];
test('zero length remove', function() {
var x = [];
var res = Utils.data.sortedRemove(3, x);
assert.equal(res, false);
});
test('length one remove', function() {
var x = [3];
var res = Utils.data.sortedRemove(3, x);
assert.deepEqual(x, []);
assert.equal(res, true);
});
test('beginning remove', function() {
var x = array.slice();
var res = Utils.data.sortedRemove(1, x);
assert.deepEqual(x, [8, 9, 19, 38, 42, 44, 56, 58, 64, 74, 82, 91]);
assert.equal(res, true);
});
test('middle remove', function() {
var x = array.slice();
var res = Utils.data.sortedRemove(58, x);
assert.deepEqual(x, [1, 8, 9, 19, 38, 42, 44, 56, 64, 74, 82, 91]);
assert.equal(res, true);
});
test('end remove', function() {
var x = array.slice();
var res = Utils.data.sortedRemove(91, x);
assert.deepEqual(x, [1, 8, 9, 19, 38, 42, 44, 56, 58, 64, 74, 82]);
assert.equal(res, true);
});
test('beginning multi remove', function() {
var x = [3, 3, 3, 3, 3, 8, 9, 19, 38, 42, 44, 56, 58, 64, 74, 82];
var res = Utils.data.sortedRemove(3, x, undefined, true);
assert.deepEqual(x, [8, 9, 19, 38, 42, 44, 56, 58, 64, 74, 82]);
assert.equal(res, true);
});
test('middle multi remove', function() {
var x = [1, 8, 9, 19, 21, 21, 21, 21, 38, 42, 44, 56, 58, 64, 74, 82];
var res = Utils.data.sortedRemove(21, x, undefined, true);
assert.deepEqual(x, [1, 8, 9, 19, 38, 42, 44, 56, 58, 64, 74, 82]);
assert.equal(res, true);
});
test('end multi remove', function() {
var x = [1, 8, 9, 19, 38, 42, 44, 56, 58, 64, 74, 82, 99, 99, 99];
var res = Utils.data.sortedRemove(99, x, undefined, true);
assert.deepEqual(x, [1, 8, 9, 19, 38, 42, 44, 56, 58, 64, 74, 82]);
assert.equal(res, true);
});
});
});
suite('extend tests', function() {
function hasOwn(obj, prop) {
return Object.prototype.hasOwnProperty.call(obj, prop);
}
function hasAny(obj, prop) {
return hasOwn(obj, prop) || (
obj !== Object.prototype && hasAny(obj.__proto__, prop));
}
function testObject() {
return Object.create(
Object.create({ grandparents: 4 },
{ parents: { value: 2 } }), {
me: { value: 1 }
});
}
test('basic extend', function() {
var x = {};
var y = Utils.extend(x, {a: 1, b: 2, c: 3});
assert.ok(x === y, 'x === y');
assert.ok(hasOwn(x, 'a') && x.a === 1);
assert.ok(hasOwn(x, 'b') && x.b === 2);
assert.ok(hasOwn(x, 'c') && x.c === 3);
});
test('extend only affects child instance', function() {
var x = testObject(), y;
y = Utils.extend(x, {favoriteColor: 'green'});
assert.ok(x === y, 'x === y');
assert.equal(x.favoriteColor, 'green');
assert.ok(hasOwn(x, 'favoriteColor'));
assert.ok(!hasAny(x.__proto__, 'favoriteColor'));
});
test('multiple extend', function() {
var x = Utils.extend({}, {
a: 42
}, {
a: 1,
b: 19,
c: 45
}, {
b: 199
});
assert.equal(x.a, 1);
assert.equal(x.b, 199);
assert.equal(x.c, 45);
});
});
suite('safeWakeLock tests', function() {
setup(function() {
this.mocklock = new MockRequestWakeLock();
this.sinon.stub(navigator, 'requestWakeLock',
this.mocklock.requestWakeLock.bind(this.mocklock));
this.sinon.useFakeTimers();
});
teardown(function() {
this.sinon.restore(navigator, 'requestWakeLock');
});
test('locks CPU', function() {
var callback = this.sinon.spy(function(unlock) {
assert.ok(navigator.requestWakeLock.calledOnce);
});
Utils.safeWakeLock({timeoutMs: 15000}, callback);
this.sinon.clock.tick(16000);
assert.ok(callback.calledOnce);
});
test('single unlock', function() {
Utils.safeWakeLock({timeoutMs: 15000}, function(unlock) {
unlock();
});
this.sinon.clock.tick(16000);
var locks = this.mocklock.getissued();
assert.equal(locks.length, 1);
assert.equal(locks[0].unlocks, 1);
});
test('no duplicate unlock', function() {
var here = 0;
Utils.safeWakeLock({timeoutMs: 15000}, function(unlock) {
setTimeout(function() {
here++;
unlock();
}, 16000);
here++;
});
this.sinon.clock.tick(17000);
var locks = this.mocklock.getissued();
assert.equal(locks.length, 1);
assert.equal(locks[0].unlocks, 1);
assert.equal(here, 2);
});
test('timeout unlock', function() {
var here = false;
Utils.safeWakeLock({timeoutMs: 15000}, function(unlock) {
here = true;
});
this.sinon.clock.tick(16000);
var locks = this.mocklock.getissued();
assert.equal(locks.length, 1);
assert.equal(locks[0].unlocks, 1);
assert.ok(here);
});
test('exception in callback still unlocks CPU', function() {
var here = false;
try {
Utils.safeWakeLock({timeoutMs: 15000}, function(unlock) {
here = true;
throw new Error('gotcha');
});
this.sinon.clock.tick(16000);
} catch (err) {
assert.equal(err.message, 'gotcha');
}
var locks = this.mocklock.getissued();
assert.equal(locks.length, 1);
assert.equal(locks[0].unlocks, 1);
assert.ok(here);
});
});
suite('async', function() {
test('generators', function() {
var spy = this.sinon.spy(function() {
assert.equal(arguments[0], null);
assert.equal(arguments.length, 1);
});
var gen = Utils.async.generator(spy);
var collection = [];
for (var i = 0; i < 100; i++) {
collection.push(gen());
}
assert.ok(!spy.called);
for (var k of collection) {
k();
}
assert.ok(spy.calledOnce);
});
test('generators error', function() {
var spy = this.sinon.spy(function() {
assert.equal(arguments[0], 1);
assert.equal(arguments.length, 1);
});
var gen = Utils.async.generator(spy);
var collection = [];
for (var i = 0; i < 100; i++) {
collection.push(gen());
}
assert.ok(!spy.called);
var j = 1;
for (var k of collection) {
k(j++);
}
assert.ok(spy.calledOnce);
});
test('namedParallel', function() {
var spy = this.sinon.spy(function() {
assert.equal(arguments[0], null);
assert.equal(arguments.length, 1);
});
var names = Utils.async.namedParallel([
'testa', 'testb', 'testc'
], spy);
assert.ok(!spy.called);
names.testa();
assert.ok(!spy.called);
names.testb();
assert.ok(!spy.called);
names.testc();
assert.ok(spy.calledOnce);
});
test('namedParallel error', function() {
var spy = this.sinon.spy(function() {
assert.equal(arguments[0], 'testing b');
assert.equal(arguments.length, 1);
});
var names = Utils.async.namedParallel([
'testa', 'testb', 'testc'
], spy);
assert.ok(!spy.called);
names.testa();
assert.ok(!spy.called);
names.testb('testing b');
assert.ok(spy.calledOnce);
names.testc();
assert.ok(spy.calledOnce);
});
});
suite('summarizeDaysOfWeek', function() {
test('weekends', function(done) {
Utils.summarizeDaysOfWeek({
'0': true, '6': true
}).then(result => {
assert.equal(result, 'weekends');
}).then(done, done);
});
test('never', function(done) {
Utils.summarizeDaysOfWeek({
}).then(result => {
assert.equal(result, 'never');
}).then(done, done);
});
test('weekdays', function(done) {
Utils.summarizeDaysOfWeek({
'1': true, '2': true, '3': true, '4': true, '5': true
}).then(result => {
assert.equal(result, 'weekdays');
}).then(done, done);
});
test('everyday', function(done) {
Utils.summarizeDaysOfWeek({
'0': true, '1': true, '2': true, '3': true,
'4': true, '5': true, '6': true
}).then(result => {
assert.equal(result, 'everyday');
}).then(done, done);
});
test('mon, wed, fri', function(done) {
Utils.summarizeDaysOfWeek({
'1': true, '3': true, '5': true
}).then(result => {
assert.deepEqual(result, {raw: 'Mon, Wed, Fri'});
}).then(done, done);
});
});
});

View file

@ -0,0 +1,106 @@
'use strict';
suite('View', function() {
var View;
suiteSetup(function(done) {
require(['view'], function(view) {
View = view;
done();
});
});
setup(function() {
this.element = document.createElement('div');
});
suite('View.instance', function() {
test('creates new View', function() {
var view = View.instance(this.element);
assert.ok(view instanceof View);
});
test('returns same View on second run', function() {
var view = View.instance(this.element);
assert.equal(View.instance(this.element), view);
});
suite('using subclass', function() {
setup(function() {
this.ctor = this.sinon.spy(function(element) {
View.call(this, element);
});
this.ctor.prototype = Object.create(View.prototype);
this.view = View.instance(this.element, this.ctor);
});
test('returns instance of ctor', function() {
assert.ok(this.view instanceof this.ctor);
});
test('called ctor', function() {
assert.ok(this.ctor.calledWith(this.element));
});
test('returns same view on second run', function() {
assert.equal(this.view, View.instance(this.element));
assert.equal(this.ctor.callCount, 1,
'did not call ctor again');
});
});
});
suite('tests on instances', function() {
setup(function() {
this.element.id = 'test-element';
this.view = new View(this.element);
this.visibleSpy = this.sinon.spy();
this.view.element.addEventListener('panel-visibilitychange',
this.visibleSpy);
});
test('has element', function() {
assert.equal(this.view.element, this.element);
});
test('has id', function() {
assert.equal(this.view.id, this.element.id);
});
test('starts visible', function() {
assert.isTrue(this.view.visible);
});
suite('set visible true', function() {
setup(function() {
this.view.visible = true;
});
test('event not called(changed to same state)', function() {
assert.equal(this.visibleSpy.callCount, 0);
});
});
suite('set visible false', function() {
setup(function() {
this.view.visible = false;
});
test('event called', function() {
assert.ok(this.visibleSpy.calledOnce);
});
test('added hidden class', function() {
assert.ok(this.element.classList.contains('hidden'));
});
suite('set visible false', function() {
setup(function() {
this.visibleSpy.reset();
this.view.visible = false;
});
test('event not called(changed to same state)', function() {
assert.equal(this.visibleSpy.callCount, 0);
});
});
suite('set visible true', function() {
setup(function() {
this.visibleSpy.reset();
this.view.visible = true;
});
test('event called', function() {
assert.ok(this.visibleSpy.calledOnce);
});
test('removed hidden class', function() {
assert.isFalse(this.element.classList.contains('hidden'));
});
});
});
});
});