Publish Vulpes Preview 15 sources for desktop, Android and Tundra

Share Gaia assets and adapters, keep private profiles and development documents local, and update both public READMEs.
This commit is contained in:
OverSu 2026-09-30 23:13:03 +02:00
commit b38dea0597
25901 changed files with 9982925 additions and 369 deletions

View file

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

View file

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

View file

@ -0,0 +1,241 @@
marionette('Alarm', function() {
'use strict';
var assert = require('assert');
var $ = require('./lib/mquery');
var utils = require('./lib/utils');
var actions = new (require('./lib/actions'))({
desiredCapabilities: { raisesAccessibilityExceptions: false }
});
var alarm = actions.alarm;
setup(function() {
actions.launch('alarm');
});
// PythonTests: functional/test_clock_switch_clock_type
test('Tapping the clock toggles between analog and digital', function() {
$('#analog-clock').tap();
assert.ok($('#digital-clock').displayed());
assert.ok($('#clock-day-date').displayed());
$('#digital-clock').tap();
assert.ok($('#analog-clock').displayed());
assert.ok($('#clock-day-date').displayed());
});
test('Deleting an alarm works between app launches', function() {
alarm.create();
alarm.openEditForm(0);
alarm.delete();
actions.restart();
assert.equal(alarm.list.length, 0);
});
// PythonTests: functional/test_clock_set_alarm
test('Blank "New Alarm" form mutates properly', function() {
alarm.openNewForm();
assert.equal($('#repeat-menu').text(), 'Never');
[
{ repeat: ['Monday'],
label: 'Mon' },
{ repeat: [],
label: 'Never' },
{ repeat: ['Monday', 'Tuesday'],
label: 'Mon, Tue' },
{ repeat: ['Sunday', 'Monday', 'Tuesday', 'Wednesday', 'Thursday',
'Friday', 'Saturday'],
label: 'Every day' },
{ repeat: ['Monday', 'Tuesday', 'Wednesday', 'Thursday', 'Friday'],
label: 'Weekdays' }
].forEach(function(item) {
$('#repeat-select').val(item.repeat);
assert.equal($('#repeat-menu').text(), item.label);
});
var selectedOptions = {
snooze: '15 minutes',
sound: 'Cycle',
repeat: 'Weekdays'
};
assert.notEqual($('#sound-menu').text(), selectedOptions.sound);
$('#sound-select').val(selectedOptions.sound);
assert.equal($('#sound-menu').text(), selectedOptions.sound);
assert.notEqual($('#snooze-menu').text(), selectedOptions.snooze);
$('#snooze-select').val(selectedOptions.snooze);
assert.equal($('#snooze-menu').text(), selectedOptions.snooze);
alarm.saveForm();
alarm.openEditForm(0);
assert.equal($('#sound-menu').text(), selectedOptions.sound);
assert.equal($('#snooze-menu').text(), selectedOptions.snooze);
assert.equal($('#repeat-menu').text(), selectedOptions.repeat);
// Ensure that the time button shows the value of the time
// <select>, since updating the select does not automatically
// change the button unless our code makes it do so.
utils.assertStringContainsTime($('#time-select + button').text(),
utils.stringToDate($('#time-select').val()));
});
test('Volume control saves immediately when changed', function() {
// Even if we abort the alarm_edit form without saving, the volume
// should be saved.
alarm.openNewForm();
alarm.volumeInput.val(1);
alarm.cancelForm();
alarm.openNewForm();
assert.equal(parseInt(alarm.volumeInput.val(), 10), 1);
alarm.volumeInput.val(0);
alarm.cancelForm();
alarm.openNewForm();
assert.equal(parseInt(alarm.volumeInput.val(), 10), 0);
});
// PythonTests: functional/test_clock_add_alarm_multiple_times
// PythonTests: functional/test_clock_create_new_alarm
test('Multiple saved alarms show the proper data', function() {
var i;
var numAlarms = 3;
var createdAlarms = [];
// Create these alarms in descending order (latest first)
for (i = 0; i < numAlarms; i++) {
createdAlarms.push(alarm.create('Alarm ' + i, i + 20));
}
var alarms = alarm.list;
assert.equal(alarms.length, numAlarms);
alarms.reverse();
for (i = 0; i < numAlarms; i++) {
assert.ok(alarms[i].name === 'Alarm ' + i, 'Alarm name matches');
utils.assertStringContainsTime(
alarms[i].timeString, createdAlarms[i].time);
}
});
test('System statusbar shows an icon when an alarm is active', function() {
assert.equal(alarm.statusIcon, false);
alarm.create();
assert.equal(alarm.statusIcon, true);
alarm.toggle(0); // turn it off
assert.equal(alarm.statusIcon, false);
alarm.toggle(0); // turn it on
assert.equal(alarm.statusIcon, true);
alarm.remove(0); // delete the alarm
assert.equal(alarm.statusIcon, false);
assert.equal(alarm.list.length, 0);
});
// PythonTests: functional/test_clock_delete_alarm
test('New alarm form shows "New Alarm" title after deletion', function() {
alarm.create();
alarm.remove(0);
alarm.openNewForm();
assert.ok($('.new-alarm-title').displayed());
});
test('Update alarm name and time', function() {
var originalName = 'coffee break';
var originalTime = alarm.create(originalName).time;
var changedName = 'quitting time';
var changedTime = new Date(originalTime + 1000 * 60);
alarm.openEditForm(0);
assert.equal(alarm.nameField.val(), originalName);
utils.assertStringContainsTime(alarm.timeField.val(), originalTime);
alarm.nameField.val(changedName);
alarm.timeField.val(new Date(changedTime));
alarm.saveForm();
$.client.waitFor(function() {
return alarm.list[0].name === changedName;
}.bind(this));
utils.assertStringContainsTime(alarm.list[0].timeString, changedTime);
// Assert that the banner has been displayed.
alarm.waitForBanner();
});
// PythonTests: functional/test_clock_create_new_alarm
test('Toggle alarm state', function() {
alarm.create();
alarm.toggle(0);
assert(!alarm.list[0].enabled, 'Alarm is disabled after toggling');
assert(!$('#banner-countdown').displayed(),
'Banner is not displayed after disabling an alarm');
alarm.toggle(0);
assert(alarm.list[0].enabled, 'Alarm is re-enabled after toggling');
});
test('After snoozing, alarm should be canceled.', function() {
// Create an alarm and make it fire.
alarm.create();
alarm.fire(0, new Date(), function() {
// Click the snooze button.
var el = $('#ring-button-snooze');
try {
el.click();
} catch(e) {
// Marionette throws an error because the frame closes while
// handling the click event. This is expected.
}
});
// Fire the snooze alarm.
alarm.fire(0, new Date(), function() {
// Click the "stop" button
var el = $('#ring-button-stop');
try {
el.click();
} catch(e) {
// Marionette throws an error because the frame closes while
// handling the click event. This is expected.
}
}, 'snooze');
// The alarm must disable itself after the snooze fires.
$.client.waitFor(function() {
return !alarm.list[0].enabled;
}.bind(this));
// Make sure we can see the analog clock again.
$('#analog-clock').waitToAppear();
});
test('Fire an alarm', function() {
alarm.create();
alarm.fire(0, new Date(), function() {
// Click the "stop" button
var el = $('#ring-button-stop');
try {
el.click();
} catch(e) {
// Marionette throws an error because the frame closes while
// handling the click event. This is expected.
}
});
// The alarm must disable itself since it is not repeating.
$.client.waitFor(function() {
return !alarm.list[0].enabled;
}.bind(this));
// Make sure we can see the analog clock again.
$('#analog-clock').waitToAppear();
});
});

View file

@ -0,0 +1,22 @@
marionette('Endurance', function() {
'use strict';
var assert = require('assert');
var actions = new (require('./lib/actions'))({
desiredCapabilities: { raisesAccessibilityExceptions: false }
});
setup(function() {
actions.launch('alarm');
});
// PythonTests: endurance/test_endurance_set_alarm
test('Set Alarm Endurance', function() {
var totalIterations = 3;
for (var iteration = 0; iteration < totalIterations; iteration++) {
actions.alarm.create('Iteration ' + iteration, iteration + 10);
}
assert.equal(actions.alarm.list.length, totalIterations);
});
});

View file

@ -0,0 +1,60 @@
marionette('Hour format', function() {
'use strict';
var assert = require('assert');
var $ = require('./lib/mquery');
var actions = new (require('./lib/actions'))({
desiredCapabilities: { raisesAccessibilityExceptions: false }
});
var alarm = actions.alarm;
setup(function() {
actions.launch('alarm');
});
suite('12 hour format', function() {
test('Fire an alarm', function() {
var alarmInfo = alarm.create();
alarm.fire(0, alarmInfo.time, function() {
var expectedDatetime = format12Datetime(alarmInfo.time);
assert.equal($('#ring-clock-time').text(), expectedDatetime);
});
});
});
suite('24 hour format', function() {
setup(function() {
// Set as 24 hour format.
$.client.settings.set('locale.hour12', false);
});
test('Fire an alarm', function() {
var alarmInfo = alarm.create();
alarm.fire(0, alarmInfo.time, function() {
var expectedDatetime = format24Datetime(alarmInfo.time);
assert.equal($('#ring-clock-time').text(), expectedDatetime);
});
});
});
function format12Datetime(date) {
var minutes = date.getMinutes();
minutes = minutes < 10 ? (0 + String(minutes)) : minutes;
var hours = date.getHours();
var amPm = hours >= 12 ? 'PM' : 'AM';
if (hours === 0) {
hours = 12;
} else if (hours > 12) {
hours = hours - 12;
}
return hours + ':' + minutes + ' ' + amPm;
}
function format24Datetime(date) {
var minutes = date.getMinutes();
minutes = minutes < 10 ? (0 + String(minutes)) : minutes;
var hours = date.getHours();
hours = hours < 10 ? '0' + String(hours) :hours;
return hours + ':' + minutes;
}
});

View file

@ -0,0 +1,100 @@
/* global marionette */
'use strict';
var $ = require('./mquery');
var StopwatchActions = require('./stopwatch_actions');
var TimerActions = require('./timer_actions');
var AlarmActions = require('./alarm_actions');
function ClockAppActions(options) {
this._client = marionette.client({
profile: {
prefs: {
// we need to disable the keyboard to avoid intermittent failures on
// Travis (transitions might take longer to run and block UI).
'dom.mozInputMethod.enabled': false,
// NOTE: We used to require 'focusmanager.testmode=true' for
// B2G-Desktop, but Mulet and Device tests do not require it.
}
},
desiredCapabilities: options && options.desiredCapabilities
});
this.stopwatch = new StopwatchActions(this._client);
this.timer = new TimerActions(this._client);
this.alarm = new AlarmActions(this._client, this);
}
module.exports = ClockAppActions;
ClockAppActions.prototype = {
origin: 'app://clock.gaiamobile.org',
launch: function(tab) {
$.client = this._client;
this._client.apps.launch(this.origin);
this._client.apps.switchToApp(this.origin);
this._client.helper.waitForElement('#clock-view .visible');
if (tab && tab !== 'alarm') {
this.openTab(tab);
}
// Wait until everything has loaded.
this._client.waitFor(function() {
return !!this.currentPanelId;
}.bind(this));
},
restart: function(tab) {
this._client.apps.close(this.origin);
this.launch(tab);
},
get currentPanelId() {
var activePanel = $('.panel.active');
return activePanel && activePanel.data('panelId');
},
tapAndTransition: function(selector) {
var previousPanel = this.currentPanelId;
if (typeof selector === 'string') {
selector = this._client.findElement(selector);
}
selector.tap();
this._client.switchToShadowRoot();
var self = this;
this._client.waitFor(function() {
var searchTimeout = self._client.searchTimeout;
self._client.setSearchTimeout(0);
var test = this.currentPanelId &&
(previousPanel !== this.currentPanelId) &&
!$('.slide-in-right, .slide-in-left')[0];
self._client.setSearchTimeout(searchTimeout);
return test;
}.bind(this));
},
// Application Navigation
openTab: function(tabName) {
// Tap the button to open the tab
this.tapAndTransition({
alarm: '#alarm-tab',
timer: '#timer-tab',
stopwatch: '#stopwatch-tab'
}[tabName]);
$('[data-panel-id=' + tabName + ']')
.waitToAppear();
},
isButtonUsable: function(btn) {
return $(btn).attr('disabled') === 'false' && $(btn).displayed();
}
};

View file

@ -0,0 +1,216 @@
'use strict';
/* global marionetteScriptFinished */
var utils = require('./utils');
var $ = require('./mquery');
function AlarmActions(client, actions) {
this._client = client;
this.actions = actions;
}
module.exports = AlarmActions;
AlarmActions.prototype = {
openNewForm: function() {
if ($('#banner-countdown').displayed()) {
this.waitForBanner();
}
this.actions.tapAndTransition('#alarm-new');
},
openEditForm: function(idx) {
if ($('#banner-countdown').displayed()) {
this.waitForBanner();
}
this.actions.tapAndTransition(
'.alarm-cell:nth-child(' + (idx + 1) + ') .alarm-item');
},
toggle: function(idx) {
var alarmWasEnabled = this.list[idx].enabled;
$('.alarm-cell:nth-child(' + (idx + 1) + ') .alarmEnable span')
.tap();
this._client.waitFor(function() {
return this.list[idx].enabled !== alarmWasEnabled;
}.bind(this));
if (!alarmWasEnabled) {
this.waitForBanner();
}
var numAlarmsEnabled = this.list.filter(function(alarm) {
return alarm.enabled;
}).length;
// If the number of alarms transitioned from 0 <=> 1, ensure the
// system status bar updates before continuing.
if (numAlarmsEnabled === 1) {
this._client.waitFor(function() {
return this.statusIcon;
}.bind(this));
} else if (numAlarmsEnabled === 0) {
this._client.waitFor(function() {
return !this.statusIcon;
}.bind(this));
}
},
cancelForm: function() {
var header = this._client.findElement('gaia-header');
this._client.switchToShadowRoot(header);
var button = this._client.findElement('.action-button');
this.actions.tapAndTransition(button);
},
delete: function() {
this.actions.tapAndTransition('#alarm-delete');
},
saveForm: function() {
this.actions.tapAndTransition('#alarm-done');
},
get nameField() {
return $('#edit-alarm [name="alarm.label"]');
},
get timeField() {
return $('#time-select');
},
get volumeInput() {
return $('#alarm-volume-input');
},
fire: function(idx, date, attentionHandler, type) {
// Trigger a fake 'alarm' event:
this._client.executeScript(function(idx, date, type) {
// String to Date object.
date = new Date(date);
var id = document.querySelector(
'.alarm-cell:nth-child(' + (idx + 1) + ') .alarm-item').dataset.id;
var alarm = new CustomEvent('test-alarm', {
detail: {
id: parseInt(id, 10),
type: type || 'normal',
date: date
}
});
window.dispatchEvent(alarm);
}, [idx, date, type]);
if (attentionHandler) {
// Switch to the Attention Screen
this._client.switchToFrame();
this._client.switchToFrame($('iframe[name="_blank"]').el);
// Wait for the onring page is ready.
this._client.waitFor(function() {
if (this._client.findElement('html')
.getAttribute('class')
.indexOf('ready') !== -1) {
return true;
}
}.bind(this));
attentionHandler();
// Now switch back to the system, then to the clock.
this._client.switchToFrame();
this._client.apps.switchToApp(this.actions.origin);
}
},
remove: function(idx) {
var count = this.list.length;
this.openEditForm(idx);
this.actions.tapAndTransition('#alarm-delete');
this._client.waitFor(function() {
return this.list.length === count - 1;
}.bind(this));
},
create: function(name, minutesFromNow) {
name = name || 'Alarm';
if (!minutesFromNow) {
minutesFromNow = 20;
}
var originalAlarmCount = this.list.length;
var now = Date.now();
this.openNewForm();
this.nameField.val(name);
this.timeField.val(new Date(now + minutesFromNow));
var time = new Date();
time.setHours.apply(time, this.timeField.val().split(':'));
this.saveForm();
var alarmData = null;
this._client.waitFor(function() {
// Verify that the alarm created is in the list of alarms.
this.list.forEach(function(alarm) {
if (alarm.name === name && alarm.enabled &&
utils.stringContainsTime(alarm.timeString, time)) {
alarmData = alarm;
}
});
return !!alarmData;
}.bind(this));
this._client.waitFor(function() {
return this.list.length === originalAlarmCount + 1;
}.bind(this));
$('[data-panel-id="alarm"]').waitToAppear();
this.waitForBanner();
return {
name: alarmData.name,
time: time,
timeString: alarmData.timeString,
enabled: alarmData.enabled
};
},
get list() {
// For now, do this all in one executeScript() call, because the
// rendering logic for alarms wipes out all elements at once. It
// could cause us to read inconsistent state from the DOM.
return this._client.executeScript(function() {
var domItems = document.querySelectorAll('.alarm-cell');
var alarms = [];
for (var i = 0; i < domItems.length; i++) {
var item = domItems[i];
alarms.push({
name: item.querySelector('.label').textContent,
timeString: item.querySelector('.time').textContent,
enabled: item.querySelector('.input-enable').checked
});
}
return alarms;
});
},
// Status Checks
get statusIcon() {
this._client.setScriptTimeout(2000);
return this._client.executeAsyncScript(function() {
var req = navigator.mozSettings.createLock().get('alarm.enabled');
req.onsuccess = function() {
marionetteScriptFinished(req.result['alarm.enabled']);
};
});
},
waitForBanner: function() {
$('#banner-countdown')
.tap()
.waitToDisappear();
}
};

View file

@ -0,0 +1,265 @@
'use strict';
var Marionette = require('marionette-client');
/**
* mQuery allows you to query and modify the DOM through Marionette,
* in a style resembling jQuery. While jQuery is often heckled for
* being poor for designing large-scale apps, its quick DOM traversal
* and manipulation is hard to beat. For integration tests, which tend
* to require convenient, clear, mostly-read-only access to the DOM
* with fewer architectural concerns, a jQuery-like API fits well.
*
* Typical use is something like this:
*
* var $ = require('mquery');
*
* $.client = marionette.client();
*
* console.log($('.my-element').text());
* console.log($('#textbox').val());
*
* You can also set values in input fields like so:
*
* $('#textbox').val('foo');
*
* This also works with system <select> boxes and other inputs too.
*
* Architecturally, mQuery does something interesting: An item
* returned from mQuery is _both_ an array of elements _and_ a single
* Element at the same time. For instance, you can pass the result of
* $('.foo') directly to Marionette calls that require a
* Marionette.Element; similarly, since mQuery behaves like an array
* as well, you can also use this anywhere you previously used
* Marionette.findElements().
*
* While typical usage assigns $.client to your Marionette.Client
* instance, you can instead pass { client: myClient } as an option to
* mQuery if you'd prefer not to globally scope out your mQuery
* instances.
*
* Read the code to see what functions are implemented; many of the
* most commonly-used jQuery features are included. Add more if you
* find them useful. While this initially contains some
* half-implemented methods (e.g. ".css()" can only read values, not
* set them), the eventual goal is to match jQuery's API where it
* makes sense.
*/
var mQuery = function(selectorOrElement, opts) {
var client = (opts && opts.client) || mQuery.client;
var parent = (opts && opts.parent) || null;
if (!client) {
throw new Error(
'You must set mQuery.client to your Marionette client.');
}
var elements;
var selector;
if (typeof selectorOrElement === 'string') {
elements = (parent || mQuery.client).findElements(selectorOrElement);
selector = selectorOrElement;
} else if (Array.isArray(selectorOrElement)) {
elements = selectorOrElement;
} else if (selectorOrElement instanceof ElementSet) {
return selectorOrElement;
} else if (selectorOrElement instanceof Marionette.Element) {
elements = [selectorOrElement];
} else {
throw new Error('mQuery doesn\'t know how to handle ' + selectorOrElement);
}
return new ElementSet(elements, {
client: client,
selector: selector
});
};
module.exports = mQuery;
mQuery.client = null;
/**
* ElementSet acts as a lightweight wrapper over Marionette.Element,
* providing chainable, functional interface somewhat like jQuery.
*/
function ElementSet(elements, opts) {
this.client = opts.client;
this.selector = opts.selector;
this.el = elements[0] || null;
this.id = this.el && this.el.id; // Act like Marionette if we can.
for (var i = 0; i < elements.length; i++) {
this.push(elements[i]);
}
}
// Behave like an Array of Marionette.Element instances.
ElementSet.prototype = Object.create(Array.prototype);
// Behave like a single instance of Marionette.Element too.
Object.keys(Marionette.Element.prototype).forEach(function(funcName) {
var elementFn = Marionette.Element.prototype[funcName];
ElementSet.prototype[funcName] = function() {
return elementFn.apply(this.el, arguments);
};
});
// Behave like jQuery. (These are only getters, not setters though.)
ElementSet.prototype.attr = ElementSet.prototype.getAttribute;
ElementSet.prototype.css = ElementSet.prototype.cssProperty;
ElementSet.prototype.toString = function() {
return '<ElementSet: ' + this.length + '>';
};
// For the purposes of executeScript, behave like Marionette.Element.
ElementSet.prototype.toJSON = function() {
return { ELEMENT: this.id };
};
// Chainable helper methods.
ElementSet.prototype.waitToAppear = function() {
this.client.helper.waitForElement(this.el);
return this;
};
ElementSet.prototype.waitToDisappear = function() {
this.client.helper.waitForElementToDisappear(this.el);
return this;
};
function makeChainable(proto, funcName) {
var origFn = proto[funcName];
proto[funcName] = function() {
origFn.apply(this, arguments);
return this;
};
}
makeChainable(ElementSet.prototype, 'clear');
makeChainable(ElementSet.prototype, 'sendKeys');
ElementSet.prototype.tap = function() {
this.waitToAppear();
this.el.scriptWith(function(el) {
el.scrollIntoView(false);
});
this.el.tap();
return this;
};
ElementSet.prototype.find = function(selector) {
if (!this.el) {
throw new Error(
'Cannot call mQuery.find() when mQuery did not match any elements.');
}
return mQuery(selector, {
client: this.client,
parent: this.el
});
};
ElementSet.prototype.eq = function(idx) {
return mQuery(this[idx], {
client: this.client
});
};
ElementSet.prototype.height = function() {
if (!this.el) {
return null;
}
return this.el.size().height;
};
ElementSet.prototype.width = function() {
if (!this.el) {
return null;
}
return this.el.size().width;
};
ElementSet.prototype.parent = function() {
if (!this.el) {
return null;
}
return mQuery(this.client.executeScript(function(el) {
return el.parentElement;
}, [this.el]), {
client: this.client
});
};
ElementSet.prototype.data = function(attr, value) {
if (!this.el) {
return null;
}
if (arguments.length === 1) {
return this.client.executeScript(function(el, attr) {
return el.dataset[attr];
}, [this.el, attr]);
} else if (arguments.length === 2) {
return this.client.executeScript(function(el, attr, value) {
el.dataset[attr] = value;
}, [this.el, attr, value]);
} else {
return this.client.executeScript(function(el) {
return el.dataset;
}, [this.el]);
}
};
ElementSet.prototype.val = function(value) {
if (!this.el) {
return null;
}
var el = this.el;
if (!arguments.length) {
return el.getAttribute('value');
} else {
if (el.tagName().toLowerCase() === 'select') {
// Save the origin so that we know how to switch back from the
// system frame to the application frame.
var origin = this.client.executeScript(function() {
return window.location.origin;
});
this.client.executeScript(function(el) {
window.focus(); // Without this line, sometimes el.focus() does nothing.
el.focus();
}, [el]);
this.client.switchToFrame(); // Switch to the system frame.
var options = this.client.findElements(
'.value-selector-select-option-popup li');
var values = (!Array.isArray(value) ? [value] : value);
options.forEach(function(option) {
var itemText = option.findElement('label span').text();
var shouldBeSelected = (values.indexOf(itemText) !== -1);
var isSelected = (option.getAttribute('aria-selected') === 'true');
if (shouldBeSelected !== isSelected) {
option.scriptWith(function(el) {
el.scrollIntoView(false);
});
option.tap();
}
});
this.client.helper.waitForElement('.value-option-confirm').tap();
this.client.apps.switchToApp(origin);
} else {
this.client.forms.fill(el, value);
}
el.scriptWith(function(el) {
el.dispatchEvent(new CustomEvent('change'));
});
return this;
}
};

View file

@ -0,0 +1,40 @@
'use strict';
/* global KeyEvent */
var $ = require('./mquery');
exports.getSpinnerValue = function(el) {
el = $(el);
var offsetTop = /translateY\(-?(\d+)/.exec(el.attr('style'))[1];
var pickerItems = el.find('.picker-unit');
var itemHeight = pickerItems[0].size().height;
var index = Math.floor(parseInt(offsetTop, 10) / itemHeight);
return parseInt(pickerItems[index].text(), 10);
};
exports.setSpinnerValue = function(el, value) {
el = $(el);
var currentValue = exports.getSpinnerValue(el);
var targetValue = parseInt(value, 10);
var delta = targetValue - currentValue;
el.parent().scriptWith(function(el, delta) {
var count = Math.abs(delta);
for (var i = 0; i < count; i++) {
var evt = new CustomEvent('keypress');
evt.keyCode = delta > 0 ? KeyEvent.DOM_VK_DOWN : KeyEvent.DOM_VK_UP;
el.dispatchEvent(evt);
}
}, [delta]);
exports.waitForSpinStop(el);
};
exports.waitForSpinStop = function(el) {
var currentY, lastY;
$.client.waitFor(function() {
lastY = currentY;
currentY = el.location().y;
return currentY === lastY;
}, { interval: 200 });
};

View file

@ -0,0 +1,71 @@
'use strict';
var utils = require('./utils');
var $ = require('./mquery');
function StopwatchActions(client) {
this._client = client;
}
module.exports = StopwatchActions;
StopwatchActions.prototype = {
get duration() {
return utils.extractDuration($('.stopwatch-time').text());
},
get laps() {
return this._client.executeScript(function() {
var domItems = document.querySelectorAll('.lap-cell');
var laps = [];
for (var i = 0; i < domItems.length; i++) {
var item = domItems[i];
laps.push({
name: item.querySelector('.lap-name').textContent,
durationString: item.querySelector('.lap-duration').textContent
});
}
return laps;
}).map(function(lap) {
lap.duration = utils.extractDuration(lap.durationString);
return lap;
});
},
start: function() {
$('.stopwatch-start').tap();
this.advanceTime();
return this;
},
lap: function() {
$('.stopwatch-lap').tap();
this.advanceTime();
return this;
},
pause: function() {
$('.stopwatch-pause').tap();
return this;
},
reset: function() {
$('.stopwatch-reset').tap();
return this;
},
setMaxLaps: function(num) {
$('[data-panel-id="stopwatch"]').data('maxLaps', num);
return this;
},
advanceTime: function(milliseconds) {
milliseconds = milliseconds || 1;
var expectedDuration = this.duration + milliseconds;
this._client.waitFor(function() {
return this.duration >= expectedDuration;
}.bind(this));
return this;
}
};

View file

@ -0,0 +1,57 @@
'use strict';
var utils = require('./utils');
var $ = require('./mquery');
var pickerUtils = require('./picker');
function TimerActions(client) {
this._client = client;
}
module.exports = TimerActions;
TimerActions.prototype = {
get hours() { return pickerUtils.getSpinnerValue('.picker-hours'); },
get minutes() { return pickerUtils.getSpinnerValue('.picker-minutes'); },
set hours(value) { pickerUtils.setSpinnerValue('.picker-hours', value); },
set minutes(value) { pickerUtils.setSpinnerValue('.picker-minutes', value); },
get countdown() {
return utils.extractDuration($('#timer-time').text());
},
get sound() {
return $('#timer-sound').val();
},
start: function() {
$('#timer-create').tap();
// Wait until the timer has moved from its default state of 0:00
this._client.waitFor(function() {
return this.countdown > 0;
}.bind(this));
return this;
},
pause: function() {
$('#timer-pause').tap();
return this;
},
resume: function() {
$('#timer-start').tap();
return this;
},
cancel: function() {
$('#timer-cancel').tap();
$('#timer-create').waitToAppear();
return this;
},
advanceTime: function(milliseconds) {
return this._client.helper.wait(milliseconds);
}
};

View file

@ -0,0 +1,85 @@
'use strict';
var assert = require('assert');
/**
* Extract a duration from any point in the given string. See the
* inline assertions below for expected usage.
*
* @return Duration found in milliseconds, or null if none found.
*/
exports.extractDuration = function(str) {
var match = /(?:(\d+):)?(\d+):(\d+)(?:[:.](\d)?(\d)?(\d)?)?/i.exec(str);
if (!match) { return null; }
var factors = [60 * 60 * 1000, 60 * 1000, 1000, 100, 10, 1];
return match.slice(1).reduce(function(sum, value, i) {
return sum + ((value || 0) * factors[i]);
}, 0);
};
assert.equal(exports.extractDuration('It took 0:00:00.000'), 0);
assert.equal(exports.extractDuration('It took 00:00.1'), 100);
assert.equal(exports.extractDuration('It took 1:00:00.010'), 3600010);
assert.equal(exports.extractDuration('It took 1:05:10.050'), 3910050);
assert.equal(exports.extractDuration('It took 30:22:22.55'), 109342550);
/**
* Extract a time from any point in the given string. See the inline
* assertions below for expected usage.
*
* @return An object with { hours: N, minutes: N } in 24-hour format.
*/
function extractTime(str) {
var match = /\b([0-9]+):([0-9]+)\s*(am|pm|a\.m\.|p\.m\.)?/i.exec(str);
var hr = parseInt(match[1], 10);
var min = parseInt(match[2], 10);
var hasAmPm = !!match[3];
if (hasAmPm && /p/i.test(match[3])) {
if (hr < 12) {
hr += 12;
}
} else {
if (hr === 12) {
hr = 0;
}
}
return { hours: hr, minutes: min, hasAmPm: hasAmPm };
}
exports.assertStringContainsTime = function(str, date) {
var time = extractTime(str);
if (time.hasAmPm) {
assert.equal(time.hours, date.getHours());
} else {
assert.equal(time.hours % 12, date.getHours() % 12);
}
assert.equal(time.minutes, date.getMinutes());
};
exports.stringContainsTime = function(str, date) {
try {
exports.assertStringContainsTime(str, date);
return true;
} catch (e) {
return false;
}
};
exports.stringToDate = function(str) {
var time = extractTime(str);
var date = new Date();
date.setHours(time.hours);
date.setMinutes(time.minutes);
return date;
};
assert.deepEqual(extractTime('It is 12:30 PM.'),
{ hours: 12, minutes: 30, hasAmPm: true });
assert.deepEqual(extractTime('It is 12:30 AM.'),
{ hours: 0, minutes: 30, hasAmPm: true });
assert.deepEqual(extractTime('It is 15:22.'),
{ hours: 15, minutes: 22, hasAmPm: false });
assert.deepEqual(extractTime('It is 0:00.'),
{ hours: 0, minutes: 0, hasAmPm: false });
assert.deepEqual(extractTime('It is 6:00 p.m..'),
{ hours: 18, minutes: 0, hasAmPm: true });

View file

@ -0,0 +1,81 @@
marionette('Stopwatch', function() {
'use strict';
var assert = require('assert');
var actions = new (require('./lib/actions'))();
var stopwatch = actions.stopwatch;
setup(function() {
actions.launch('stopwatch');
});
test('Stopwatch Resetting', function() {
stopwatch
.start()
.lap()
.lap()
.pause();
assert.equal(actions.stopwatch.laps.length, 3);
// During pause, the reset button should be usable.
assert.ok(actions.isButtonUsable('.stopwatch-reset'));
stopwatch.reset();
// After reset, start should be usable, reset should not.
assert.ok(!actions.isButtonUsable('.stopwatch-reset'));
assert.ok(actions.isButtonUsable('.stopwatch-start'));
// Everything should be gone.
assert.equal(stopwatch.duration, 0);
assert.equal(stopwatch.laps.length, 0);
});
test('Stopwatch Advancement', function() {
assert.equal(stopwatch.duration, 0);
stopwatch.start();
stopwatch.advanceTime(1300);
assert.ok(!actions.isButtonUsable('.stopwatch-start'));
assert.ok(stopwatch.duration > 1200);
});
test('Lap advancement and ordering', function() {
stopwatch
.start()
.lap();
assert.equal(stopwatch.laps.length, 2);
assert.ok(stopwatch.laps[0].duration > 0);
assert.ok(stopwatch.laps[1].duration > 0);
var originalLaps = stopwatch.laps;
stopwatch.advanceTime(1300);
stopwatch.lap();
var newLaps = stopwatch.laps;
// New laps should be inserted first.
assert.ok(newLaps[0].duration < newLaps[1].duration);
// Old laps should preserve their state.
assert.ok(newLaps[1].duration >= originalLaps[0].duration);
assert.equal(newLaps[2].duration, originalLaps[1].duration);
});
test('Max Laps', function() {
var MAX = 5;
stopwatch
.setMaxLaps(MAX)
.start();
for (var i = 0; i < MAX - 1; i++) {
stopwatch.lap();
}
assert.ok(!actions.isButtonUsable('.stopwatch-lap'));
});
});

View file

@ -0,0 +1,70 @@
marionette('Timer', function() {
'use strict';
var assert = require('assert');
var actions = new (require('./lib/actions'))();
var timer = actions.timer;
setup(function() {
actions.launch('timer');
});
test('Basic Timer Operation', function() {
var durationMs = (6 * 60 + 40) * 60 * 1000;
timer.hours = 6;
timer.minutes = 40;
timer.start();
timer.advanceTime(500);
// Make sure the default sound is properly selected.
assert.equal(timer.sound, 'ac_awake.opus');
assert.ok(timer.countdown > durationMs - 5000);
assert.ok(timer.countdown <= durationMs);
// Switch away from the timer tab and back.
// The timer should still show an appropriate countdown.
actions.openTab('alarm');
actions.openTab('timer');
assert.ok(timer.countdown > durationMs - 10000);
assert.ok(timer.countdown <= durationMs);
});
test('Timer resume after app termination', function() {
var durationMs = 60 * 60 * 1000;
timer.hours = 1;
timer.minutes = 0;
timer.start();
timer.advanceTime(500);
actions.restart('timer');
assert.ok(timer.countdown < durationMs);
});
test('Timer Pausing and Restarting', function() {
timer.hours = 0;
timer.minutes = 20;
timer.start();
timer.pause();
var pausedValue = timer.countdown;
timer.advanceTime(1200);
// The timer should not advance while paused.
assert.equal(timer.countdown, pausedValue);
timer.resume();
timer.advanceTime(1200);
// The timer should advance when unpaused.
assert.ok(timer.countdown < pausedValue);
});
});

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