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,305 @@
/* global suiteTemplate, MockMozIntl */
define(function(require) {
'use strict';
require('/shared/elements/gaia-header/dist/gaia-header.js');
require('/shared/test/unit/mocks/mock_moz_intl.js');
var AccountTemplate = require('templates/account');
var AdvancedSettings = require('views/advanced_settings');
var Factory = require('test/support/factory');
var core = require('core');
var waitFor = require('test/support/wait_for');
require('dom!advanced_settings');
suite('Views.AdvancedSettings', function() {
var subject;
var template;
var accountStore;
var fixtures;
var settings;
var triggerEvent;
suiteSetup(function() {
triggerEvent = testSupport.calendar.triggerEvent;
window.mozIntl = MockMozIntl;
});
setup(function() {
fixtures = {
a: Factory('account', {
_id: 'a',
providerType: 'Caldav'
}),
b: Factory('account', {
_id: 'b',
providerType: 'Caldav'
}),
// expected not to be displayed.
notRendered: Factory('account', {
_id: 'c',
providerType: 'Local'
})
};
});
var db;
suiteTemplate('advanced-settings', {
id: 'advanced-settings-view'
});
setup(function(done) {
db = core.db;
template = AccountTemplate;
subject = new AdvancedSettings();
accountStore = core.storeFactory.get('Account');
settings = core.storeFactory.get('Setting');
core.db.open(done);
});
setup(function(done) {
var trans = db.transaction('accounts', 'readwrite');
for (var key in fixtures) {
accountStore.persist(fixtures[key], trans);
}
trans.oncomplete = function() {
done();
};
trans.onerror = function(e) {
done(e);
};
});
teardown(function(done) {
testSupport.calendar.clearStore(
core.db,
['accounts'],
function() {
core.db.close();
done();
}
);
});
test('#accountList', function() {
assert.ok(subject.accountList);
});
test('#syncFrequency', function() {
var freq = subject.syncFrequency;
assert.ok(freq);
});
test('#element', function() {
var el = document.querySelector(subject.selectors.element);
assert.ok(el);
assert.equal(subject.element, el);
});
suite('#_initEvents', function() {
var children;
setup(function(done) {
children = subject.accountList.children;
subject._initEvents();
waitFor(() => children.length === 2, done);
});
suite('syncFrequency', function() {
var expected;
setup(function(done) {
settings.getValue('syncFrequency', function(err, value) {
expected = String(value);
done(err);
});
});
test('selected value should match store', function(done) {
assert.ok(expected, 'expected');
waitFor(() => subject.syncFrequency.value === expected, done);
});
});
suite('account with error', function() {
var withError;
setup(function(done) {
withError = Factory('account', {
_id: 'with-error',
providerType: 'Caldav',
error: {}
});
accountStore.persist(withError);
waitFor(() => children.length === 3, done);
});
teardown(function(done) {
accountStore.remove(withError._id);
waitFor(() => children.length === 2, done);
});
test('adds error class', function() {
assert.ok(subject.accountList.querySelector('.error'));
});
test('| update', function(done) {
accountStore.persist(Factory('account', {
_id: 'with-error',
providerType: 'Caldav',
error: undefined
}));
waitFor(() => {
return subject.accountList.querySelector('.error') == null;
}, done);
});
test('| remove', function(done) {
accountStore.remove(withError._id);
waitFor(() => children.length === 2, done);
});
});
suite('local provider', function() {
setup(function(done) {
accountStore.persist(Factory('account', {
providerType: 'Local'
}), done);
});
test('does not add account', function() {
assert.lengthOf(children, 2);
});
});
});
suite('#handleSettingUiChange', function() {
var calledWith;
var originalSet;
setup(function() {
subject._initEvents();
calledWith = 'notcalled';
originalSet = settings.set;
settings.set = function(name, value) {
if (name === 'syncFrequency') {
calledWith = value;
}
};
});
teardown(function() {
settings.set = originalSet;
});
function change(name, value) {
var el = subject[name];
el.value = value;
triggerEvent(el, 'change');
}
suite('syncFrequency', function() {
test('null', function() {
change('syncFrequency', 'null');
assert.equal(calledWith, null);
});
test('numeric', function() {
change('syncFrequency', '30');
assert.equal(calledWith, 30);
});
});
});
suite('on syncFrequencyChange', function() {
var select;
suite('syncFrequency', function() {
setup(function() {
select = subject.syncFrequency;
subject._initEvents();
});
test('numeric 15', function(done) {
select.value = 'null';
settings.emit('syncFrequencyChange', 15);
waitFor(() => select.value === '15', done);
});
test('null', function(done) {
select.value = '15';
settings.emit('syncFrequencyChange', null);
waitFor(() => select.value === 'null', done);
});
});
});
suite('#render', function() {
var list;
var expectedEventAlarm = -300;
var expectedAllDayAlarm = 32400;
setup(function(done) {
var pending = 2;
settings.set('standardAlarmDefault', expectedEventAlarm, next);
settings.set('alldayAlarmDefault', expectedAllDayAlarm, next);
function next() {
if (!(--pending)) {
done();
}
}
});
// stage error
setup(function(done) {
fixtures.b.error = {};
accountStore.persist(fixtures.b, done);
});
setup(function(done) {
list = subject.accountList;
subject.render().then(done);
});
test('alarm select populated', function() {
assert.equal(
subject.standardAlarmLabel.querySelectorAll('select').length,
1
);
assert.equal(
subject.alldayAlarmLabel.querySelectorAll('select').length,
1
);
});
test('alarms set to stored value', function() {
assert.equal(
subject.standardAlarm.value, expectedEventAlarm,
'event alarm set to stored value'
);
assert.equal(
subject.alldayAlarm.value, expectedAllDayAlarm,
'event alarm set to stored value'
);
});
});
});
});

View file

@ -0,0 +1,149 @@
define(function(require) {
'use strict';
var AccountTemplate = require('templates/account');
var CreateAccount = require('views/create_account');
var Presets = require('common/presets');
var core = require('core');
suite('Views.CreateAccount', function() {
var subject;
var template;
var storeFactory;
teardown(function() {
var el = document.getElementById('test');
el.parentNode.removeChild(el);
});
setup(function(done) {
var div = document.createElement('div');
div.id = 'test';
div.innerHTML = [
'<div id="create-account-view">',
'<gaia-header id="create-account-header" action="back">',
'<h1>Add an account</h1>',
'</gaia-header>',
'<ul id="create-account-presets"></ul>',
'</div>'
].join('');
document.body.appendChild(div);
storeFactory = core.storeFactory;
template = AccountTemplate;
subject = new CreateAccount();
core.db.open(done);
});
teardown(function(done) {
subject.destroy();
testSupport.calendar.clearStore(
core.db,
['accounts'],
function() {
core.db.close();
done();
}
);
});
test('#element', function() {
var el = document.querySelector(subject.selectors.element);
assert.ok(el);
assert.equal(subject.element, el);
});
test('#accounts', function() {
assert.equal(
subject.accounts,
document.querySelector(subject.selectors.accounts)
);
});
suite('#_initEvents', function() {
setup(function(done) {
subject.render = done;
subject._initEvents();
});
test('when an account is added', function(done) {
var store = storeFactory.get('Account');
subject.render = done;
store.emit('add');
});
test('when an account is removed', function(done) {
var store = storeFactory.get('Account');
subject.render = done;
store.emit('remove');
});
});
suite('#render', function() {
setup(function() {
subject.accounts.innerHTML = '__MARKER__';
});
suite('default calendar presets', function() {
var presets;
setup(function(done) {
presets = Object.keys(Presets);
subject.render().then(done).catch(done);
});
test('each preset is displayed', function() {
var html = subject.accounts.innerHTML;
assert.ok(html);
presets.forEach(function(val) {
assert.include(
html,
val
);
});
});
});
suite('with preset marked for single use', function() {
setup(function(done) {
subject.presets = {
'one': {
singleUse: true,
providerType: 'local'
},
'two': {
singleUse: true,
providerType: 'local'
}
};
var accountStore = storeFactory.get('Account');
accountStore.persist({ preset: 'one' }, function() {
subject.render().then(done).catch(done);
});
});
test('hides used single use presets', function() {
assert.ok(
subject.accounts.innerHTML.indexOf('__MARKER__') === -1
);
var html = subject.accounts.innerHTML;
assert.ok(html);
var hasLocal = (html.indexOf('local') !== -1);
assert.isFalse(
hasLocal,
'single use presets should not be displayed'
);
});
});
});
});
});

View file

@ -0,0 +1,475 @@
/* global MockMozIntl */
define(function(require) {
'use strict';
require('/shared/test/unit/mocks/mock_moz_intl.js');
var CurrentTime = require('views/current_time');
suite('Views.CurrentTime', function() {
var subject;
var container;
var timespan;
var realMozIntl;
suiteSetup(function() {
realMozIntl = window.mozIntl;
window.mozIntl = MockMozIntl;
});
setup(function() {
var startOfDay = new Date();
startOfDay.setHours(0, 0, 0, 0);
var endOfDay = new Date();
endOfDay.setHours(24, 0, 0, 0);
// mocks the Calendar.TimeSpan API needed by CurrentTime
timespan = {
start: startOfDay,
end: endOfDay
};
timespan.contains = timespan.containsNumeric = function(date) {
return date >= timespan.start && date <= timespan.end;
};
container = {
'appendChild': sinon.spy(),
'removeChild': sinon.spy()
};
subject = new CurrentTime({ container: container, timespan: timespan });
});
suiteTeardown(function() {
window.mozIntl = realMozIntl;
});
suite('#_create', function() {
setup(function() {
subject._create();
});
test('create and append element to DOM', function() {
assert.ok(subject.element, 'element');
assert.include(
subject.element.className,
'current-time',
'className'
);
assert.equal(subject.element.getAttribute('aria-hidden'), 'true');
assert.ok(
container.appendChild.calledWithExactly(subject.element),
'append to DOM'
);
});
});
suite('#activate', function() {
var contains;
setup(function() {
contains = sinon.stub(timespan, 'containsNumeric');
sinon.stub(subject, '_create');
sinon.stub(subject, '_tick');
sinon.stub(subject, '_maybeActivateInTheFuture');
subject.element = {
classList: {
add: sinon.stub()
}
};
});
teardown(function() {
contains.restore();
subject._create.restore();
subject._tick.restore();
subject._maybeActivateInTheFuture.restore();
delete subject.element;
});
test('create and setup tick if date is inside timespan', function() {
contains.returns(true);
subject.activate();
assert.ok(
timespan.containsNumeric.calledOnce,
'timespan.containsNumeric'
);
assert.ok(
!subject._maybeActivateInTheFuture.called,
'_maybeActivateInTheFuture'
);
assert.ok(
subject._create.calledOnce,
'_create'
);
assert.ok(
subject._tick.calledOnce,
'_tick'
);
assert.ok(
subject.element.classList.add.calledWithExactly('active'),
'add active class'
);
});
test('call _maybeActivateInTheFuture if outside timespan', function() {
contains.returns(false);
subject.activate();
assert.ok(
subject._maybeActivateInTheFuture.calledOnce
);
});
});
suite('#_maybeActivateInTheFuture', function() {
var clock;
var start;
var offset = 24 * 60 * 60 * 1000;
setup(function() {
clock = sinon.useFakeTimers();
start = timespan.start;
timespan.start = Date.now() + offset;
sinon.stub(subject, 'activate');
sinon.stub(subject, '_clearInterval');
});
teardown(function() {
timespan.start = start;
subject.activate.restore();
subject._clearInterval.restore();
clock.restore();
});
test('call activate after timeout', function() {
subject._maybeActivateInTheFuture();
assert.ok(
!subject.activate.called,
'only call activate after timeout'
);
assert.ok(
subject._clearInterval.calledOnce,
'clear previous timeout'
);
clock.tick(offset);
assert.ok(
subject.activate.calledOnce,
'call activate after timeout'
);
});
});
suite('#_render', function() {
var clock;
var date;
var element;
setup(function() {
date = new Date(2014, 4, 22, 5, 15);
clock = sinon.useFakeTimers(+date);
element = subject.element;
sinon.stub(subject, '_checkOverlap');
subject.element = {
textContent: null,
style: {
top: null
},
dataset: {
date: null,
l10nDateFormat: null
}
};
});
teardown(function() {
clock.restore();
subject.element = element;
subject._checkOverlap.restore();
});
test('should update position and time', function() {
subject._render();
assert.equal(subject.element.id, 'current-time-indicator');
assert.deepEqual(
subject.element,
{
textContent: '5:15 AM',
style: {
top: '21.875%'
},
id: 'current-time-indicator',
dataset: {
date: date,
l10nDateFormat: 'current-time'
}
}
);
assert.ok(
subject._checkOverlap.calledOnce,
'call _checkOverlap once'
);
assert.ok(
subject._checkOverlap.calledWithExactly(5),
'call _checkOverlap with current hour'
);
});
});
suite('#_tick', function() {
var clock;
setup(function() {
sinon.stub(subject, '_render');
sinon.stub(subject, '_clearInterval');
sinon.stub(subject, 'deactivate');
sinon.spy(subject, '_tick');
sinon.stub(timespan, 'contains');
clock = sinon.useFakeTimers();
});
teardown(function() {
subject._render.restore();
subject._clearInterval.restore();
subject.deactivate.restore();
subject._tick.restore();
timespan.contains.restore();
clock.restore();
});
test('inside range', function() {
timespan.contains.returns(true);
subject._tick();
assert.ok(
subject._clearInterval.calledOnce,
'_clearInterval'
);
assert.ok(
!subject.deactivate.called,
'deactivate'
);
assert.ok(
subject._render.calledOnce,
'_render'
);
// should call tick every minute
assert.ok(
subject._tick.calledOnce,
'_tick #1'
);
clock.tick(60000);
assert.ok(
subject._tick.calledTwice,
'_tick #2'
);
clock.tick(60000);
assert.ok(
subject._tick.calledThrice,
'_tick #3'
);
});
test('outside range', function() {
timespan.contains.returns(false);
subject._tick();
assert.ok(
subject._clearInterval.calledOnce,
'_clearInterval'
);
assert.ok(
subject.deactivate.calledOnce,
'deactivate'
);
assert.ok(
!subject._render.called,
'_render'
);
// should not call tick multiple times
assert.ok(
subject._tick.calledOnce,
'_tick #1'
);
clock.tick(60000);
assert.ok(
subject._tick.calledOnce,
'_tick #2'
);
clock.tick(60000);
assert.ok(
subject._tick.calledOnce,
'_tick #3'
);
});
});
suite('#_checkOverlap', function() {
var hour2;
function HourElement(top, right, left, bottom) {
this.getBoundingClientRect = function() {
return {
top: top,
right: right,
left: left,
bottom: bottom
};
};
this.classList = {
toggle: sinon.spy(),
remove: sinon.spy()
};
}
setup(function() {
hour2 = new HourElement(40, 50, 10, 60);
subject._container = {
querySelector: sinon.stub()
.withArgs('.hour-2 .display-hour')
.returns(hour2)
};
});
teardown(function() {
subject._container = container;
delete subject.element;
});
suite('> overlaps', function() {
setup(function() {
subject.element = {
getBoundingClientRect: function() {
return {
top: 55,
right: 300,
left: 0,
bottom: 65
};
}
};
subject._previousOverlap = new HourElement(10, 10, 30, 30);
});
teardown(function() {
delete subject.element;
delete subject._previousOverlap;
});
test('hide overlapping hour', function() {
subject._checkOverlap(2);
assert.ok(
hour2.classList.toggle.calledWithExactly('is-hidden', true),
'should hide second hour'
);
assert.strictEqual(
subject._previousOverlap,
hour2,
'update _previousOverlap'
);
});
});
suite('> doesn\'t overlap', function() {
setup(function() {
subject.element = {
getBoundingClientRect: function() {
return {
top: 105,
right: 300,
left: 0,
bottom: 115
};
}
};
subject._previousOverlap = hour2;
});
test('should not hide hour', function() {
subject._checkOverlap(2);
assert.ok(
hour2.classList.toggle.calledWithExactly('is-hidden', false),
'should not hide second hour'
);
assert.ok(
!hour2.classList.remove.called,
'should display second hour'
);
});
});
});
suite('#deactivate', function() {
suite('> with element', function() {
setup(function() {
sinon.stub(subject, '_clearInterval');
subject.element = {
classList: {
remove: sinon.stub()
}
};
});
teardown(function() {
subject._clearInterval.restore();
delete subject.element;
});
test('should clear the interval & remove active class', function() {
subject.deactivate();
assert.ok(subject._clearInterval.calledOnce, '_clearInterval');
assert.ok(
subject.element.classList.remove.calledWithExactly('active'),
'remove active class'
);
});
});
// element is only created after first activate call and we need to ensure
// deactivate still works even without an element
suite('> without element', function() {
setup(function() {
sinon.stub(subject, '_clearInterval');
});
teardown(function() {
subject._clearInterval.restore();
});
test('should clear the interval', function() {
assert.ok(!subject.element, 'no element');
subject.deactivate();
assert.ok(subject._clearInterval.calledOnce, '_clearInterval');
});
});
});
suite('#destroy', function() {
var el;
setup(function() {
el = subject.element = {
classList: {
remove: sinon.stub()
}
};
sinon.stub(subject, 'deactivate');
subject._previousOverlap = {};
});
teardown(function() {
subject.deactivate.restore();
});
test('stop timer, remove element', function() {
subject.destroy();
assert.ok(subject.deactivate.calledOnce, 'deactivate');
assert.ok(container.removeChild.calledWithExactly(el), 'removeChild');
});
});
});
});

View file

@ -0,0 +1,24 @@
define(function(require) {
'use strict';
var Errors = require('views/errors');
var core = require('core');
suite('Views.Errors', function() {
var subject, errorName;
setup(function() {
subject = new Errors();
subject.showErrors = function(list) {
errorName = list[0].name;
};
});
test('offline event', function() {
core.syncController.emit('offline');
assert.deepEqual(errorName, 'offline');
});
});
});

View file

@ -0,0 +1,331 @@
define(function(require) {
'use strict';
require('/shared/elements/gaia-header/dist/gaia-header.js');
var EventBase = require('views/event_base');
var EventModel = require('models/event');
var View = require('view');
var router = require('router');
suite('Views.EventBase', function() {
var core;
var subject;
var triggerEvent;
var last = router.last;
function hasClass(value) {
return subject.element.classList.contains(value);
}
suiteSetup(function() {
core = testSupport.calendar.core();
triggerEvent = testSupport.calendar.triggerEvent;
last = router.last;
});
teardown(function() {
var el = document.getElementById('test');
el.parentNode.removeChild(el);
router.last = last;
});
setup(function(done) {
var div = document.createElement('div');
div.id = 'test';
div.innerHTML = [
'<div id="event-test">',
'<gaia-header id="event-test-header" action="cancel">',
'<button class="primary">primary</button>',
'</gaia-header>',
'</div>'
].join('');
document.body.appendChild(div);
subject = new EventBase({
selectors: {
element: '#event-test',
header: '#event-test-header',
primaryButton: '#event-test .primary'
}
});
core.db.open(done);
});
// setup this.account / this.calendar
testSupport.calendar.accountEnvironment();
// setup this.event / this.busytime
testSupport.calendar.eventEnvironment();
teardown(function(done) {
testSupport.calendar.clearStore(
core.db,
['accounts', 'calendars', 'events', 'busytimes'],
function() {
core.db.close();
done();
}
);
});
test('initialization', function() {
assert.instanceOf(subject, View);
assert.instanceOf(subject, EventBase);
assert.ok(subject._els, 'has elements');
});
test('.primaryButton', function() {
assert.ok(subject.primaryButton);
});
test('.header', function() {
assert.ok(subject.header);
});
test('.fieldRoot', function() {
assert.ok(subject.fieldRoot);
assert.equal(subject.fieldRoot, subject.element);
});
test('.uiSelector', function() {
assert.ok(subject.uiSelector);
});
suite('#_useModel', function() {
var callsUpdateUI;
setup(function() {
callsUpdateUI = false;
subject._updateUI = function() {
callsUpdateUI = true;
};
});
test('readonly', function() {
var record = {
busytime: this.busytime,
event: this.event,
capabilities: { canUpdate: false }
};
subject._useModel(record);
assert.isTrue(hasClass(subject.READONLY), 'is readonly');
assert.isTrue(callsUpdateUI, 'updates ui');
});
test('normal', function() {
var record = {
busytime: this.busytime,
event: this.event,
calendar: { _id: this.event.calendarId },
capabilities: { canUpdate: true }
};
subject._useModel(record);
assert.equal(
subject.originalCalendar._id,
record.event.calendarId
);
assert.isTrue(callsUpdateUI, 'updates ui');
assert.isFalse(hasClass(subject.READONLY), 'is readonly');
});
});
suite('#dispatch', function() {
var classList;
setup(function() {
classList = subject.element.classList;
});
suite('create', function() {
var date = new Date();
date.setDate(date.getDate() + 1);
date.setMinutes(0);
date.setSeconds(0);
date.setMilliseconds(0);
var headerFontSize;
setup(function(done) {
headerFontSize = this.sinon.stub(subject.header, 'runFontFitSoon');
core.timeController.move(date);
subject.dispatch({ params: {} });
subject.ondispatch = done;
});
test('display', function() {
// Updates the header font size.
sinon.assert.calledOnce(headerFontSize);
// class details
assert.isTrue(classList.contains(subject.CREATE), 'has create class');
assert.isFalse(
classList.contains(subject.UPDATE),
'has update class'
);
// model
assert.instanceOf(
subject.event,
EventModel
);
// expected model time
var expectedStart = new Date(date.valueOf());
var expectedEnd = new Date(expectedStart.valueOf());
expectedEnd.setHours(expectedStart.getHours() + 1);
assert.deepEqual(subject.event.startDate, expectedStart);
assert.deepEqual(subject.event.endDate, expectedEnd);
});
});
test('/add returnTo', function() {
router.last = {
path: '/event/add/'
};
subject.dispatch({ params: {} });
assert.equal(subject.returnTo(), subject.DEFAULT_VIEW);
});
test('/advanced-settings returnTo', function() {
router.last = {
path: '/advanced-settings/'
};
subject.dispatch({ params: {} });
assert.strictEqual(subject.returnTo(), subject.DEFAULT_VIEW);
});
test('/day returnTo', function() {
router.last = {
path: '/day/'
};
subject.dispatch({ params: {} });
assert.strictEqual(subject.returnTo(), '/day/');
});
suite('update', function() {
setup(function(done) {
subject.ondispatch = done;
subject.dispatch({
params: { id: this.busytime._id }
});
assert.ok(
subject.element.classList.contains(subject.LOADING),
'is loading'
);
});
test('is done loading', function() {
assert.ok(
!subject.element.classList.contains(subject.LOADING)
);
});
test('existing model', function() {
assert.isFalse(classList.contains(subject.CREATE), 'has create class');
assert.isTrue(classList.contains(subject.UPDATE), 'has update class');
assert.deepEqual(
subject.busytime,
this.busytime,
'has correct busytime'
);
assert.deepEqual(
this.event,
subject.event.data,
'has correct event'
);
});
});
});
test('#returnTop', function(done) {
assert.ok(subject.returnTop);
subject._returnTo = '/foo';
subject._updateUI = function() {
assert.equal(subject._returnTo, '/foo');
assert.equal(subject._returnTop, '/foo');
};
subject.dispatch({params: {}});
subject._returnTo = '/bar';
subject._updateUI = function() {
assert.equal(subject._returnTo, '/bar');
assert.equal(subject._returnTop, '/foo');
};
subject.dispatch({params: {}});
subject.returnTop();
subject._returnTo = '/bar';
subject._updateUI = function() {
assert.equal(subject._returnTo, '/bar');
assert.equal(subject._returnTop, '/bar');
done();
};
subject.dispatch({params: {}});
});
suite('#_createModel', function() {
test('time is less then now', function() {
var model = subject._createModel(new Date(2012, 0, 1));
assert.hasProperties(
model,
{
startDate: new Date(2012, 0, 1, 8),
endDate: new Date(2012, 0, 1, 9)
}
);
});
test('time is today', function() {
var now = new Date();
var start = new Date(now.getTime());
start.setHours(0, 0, 0, 0);
// defaults to next hour
var realStart = new Date(now.getTime());
realStart.setHours(now.getHours() + 1, 0, 0, 0);
var end = new Date(now.getTime());
end.setHours(now.getHours() + 2, 0, 0, 0);
var model = subject._createModel(start);
assert.hasProperties(
model,
{ startDate: realStart, endDate: end }
);
});
test('time is greater then now', function() {
var now = new Date();
var year = now.getFullYear() + 1;
var start = new Date(year, 6, 23);
var model = subject._createModel(start);
assert.hasProperties(
model,
{
startDate: new Date(year, 6, 23, 8),
endDate: new Date(year, 6, 23, 9)
}
);
});
});
});
});

View file

@ -0,0 +1,754 @@
define(function(require) {
'use strict';
var AccountModel = require('models/account');
var Factory = require('test/support/factory');
var FakePage = require('test/support/fake_page');
var ModifyAccount = require('views/modify_account');
var OAuthWindow = require('oauth_window');
var Presets = require('common/presets');
var core = require('core');
var nextTick = require('common/next_tick');
var router = require('router');
require('/shared/elements/gaia-header/dist/gaia-header.js');
require('dom!modify_event');
require('dom!show_event');
suite('Views.ModifyAccount', function() {
var subject;
var account;
var triggerEvent;
var storeFactory;
var mozApp = {};
var MockOAuth = function(server, params) {
this.server = server;
this.params = params;
this.open = function() {
this.isOpen = true;
};
this.close = function() {
this.isOpen = false;
};
};
var RealOAuth;
var realMozApps;
function setupOauth() {
realMozApps = navigator.mozApps;
RealOAuth = OAuthWindow;
OAuthWindow = MockOAuth;
navigator.mozApps = {
getSelf: function() {
var req = {};
nextTick(function() {
if (req.onsuccess) {
req.onsuccess({
target: {
result: mozApp
}
});
}
});
return req;
}
};
}
function teardownOauth() {
OAuthWindow = RealOAuth;
navigator.mozApps = realMozApps;
}
suiteSetup(function() {
triggerEvent = testSupport.calendar.triggerEvent;
});
function hasClass(value) {
return subject.element.classList.contains(value);
}
function getField(name) {
return subject.fields[name];
}
function fieldValue(name) {
var field = getField(name);
return field.value;
}
teardown(function() {
var el = document.getElementById('test');
el.parentNode.removeChild(el);
});
// template
setup(function() {
var div = document.createElement('div');
div.id = 'test';
div.innerHTML = [
'<div id="modify-account-view">',
'<gaia-header id="modify-account-header" action="back">',
'<h1>Account</h1>',
'<button class="save">Save</button>',
'</gaia-header>',
'<button class="save">save</button>',
'<button class="cancel">cancel</button>',
'<button class="delete-cancel">cancel</button>',
'<section role="status">',
'<div class="errors"></div>',
'</section>',
'<form class="modify-account-form">',
'<input name="user" />',
'<input name="password" />',
'<input name="fullUrl" />',
'</form>',
'<a role="button" class="delete-record">',
'<button class="delete-confirm">',
'<a class="force-oauth2"></a>',
'</div>',
'<section id="oauth2">',
'<gaia-header id="oauth-header" action="back">',
'<h1 class="oauth-browser-title"> </h1>',
'</gaia-header>',
'<div class="browser-container"></div>',
'</section>'
].join('');
document.body.appendChild(div);
});
// db
setup(function(done) {
account = Factory('account', { _id: 1 });
// assumes account is in a "modify" state
subject = new ModifyAccount({
model: account
});
storeFactory = core.storeFactory;
core.db.open(function() {
storeFactory.get('Account').persist(account, done);
});
});
teardown(function(done) {
testSupport.calendar.clearStore(
core.db,
['accounts'],
function() {
core.db.close();
done();
}
);
});
test('#authenticationType', function() {
assert.equal(subject.authenticationType, 'basic');
});
test('#oauth2Window', function() {
assert.ok(subject.oauth2Window);
});
test('#oauth2SignIn', function() {
assert.ok(subject.oauth2SignIn);
});
test('#deleteButton', function() {
assert.ok(subject.deleteButton);
});
test('#deleteRecordButton', function() {
assert.ok(subject.deleteRecordButton);
});
test('#saveButton', function() {
assert.ok(subject.saveButton);
});
test('#errors', function() {
assert.ok(subject.errors);
});
test('#form', function() {
assert.ok(subject.form);
});
suite('#showErrors', function() {
var handler;
var offline;
var sentErr;
var show;
var showErrorCall;
setup(function() {
handler = core.bridge.createAccount;
sentErr = new Error();
core.bridge.createAccount = function() {
throw sentErr;
};
show = subject.showErrors;
subject.showErrors = function() {
showErrorCall = Array.slice(arguments);
};
offline = subject.isOffline;
subject.isOffline = function() {
return false;
};
});
teardown(function() {
core.bridge.createAccount = handler;
subject.isOffline = offline;
subject.showErrors = show;
});
test('authorizeError', function() {
subject.save(null);
assert.deepEqual(
showErrorCall,
[sentErr]
);
});
});
test('#fields', function() {
var result = subject.fields;
function hasName(field) {
var value = result[field].getAttribute('name');
assert.equal(value, field);
}
hasName('user');
hasName('password');
hasName('fullUrl');
});
suite('#deleteRecord', function() {
var model;
var show;
setup(function() {
// assign model to simulate
// a record that has been dispatched
model = Factory('account');
model._id = 'myaccount';
subject.model = model;
show = router.show;
});
teardown(function() {
router.show = show;
});
test('with existing model', function() {
var calledShow;
var calledRemove;
var store = storeFactory.get('Account');
// we don't really need to redirect
// in the test just confirm that it does
router.show = function() {
calledShow = Array.slice(arguments);
};
// again fake model so we do a fake remove
store.remove = function() {
calledRemove = Array.slice(arguments);
};
subject.render();
triggerEvent(subject.deleteButton, 'click');
assert.ok(calledRemove, 'called remove');
assert.equal(calledRemove[0], model._id, 'removes right id');
assert.deepEqual(
calledShow,
['/advanced-settings/']
);
});
test('#deleteRecordButton click', function() {
this.sinon.stub(subject, 'hideHeaderAndForm');
subject.render();
triggerEvent(subject.deleteRecordButton, 'click');
assert.isTrue(subject.hideHeaderAndForm.called);
});
test('#hideHeaderAndForm + #dispatch', function() {
assert.isFalse(subject.element.classList.contains(
subject.removeDialogClass));
subject.hideHeaderAndForm();
assert.isTrue(subject.element.classList.contains(
subject.removeDialogClass));
subject.dispatch({ params: { preset: 'local' } });
assert.isFalse(subject.element.classList.contains(
subject.removeDialogClass));
});
test('#deleteRecordButton click', function() {
this.sinon.stub(subject, 'cancelDelete');
subject.render();
triggerEvent(subject.cancelDeleteButton, 'click');
assert.isTrue(subject.cancelDelete.called);
});
test('#cancelDelete', function() {
subject.element.classList.add(subject.removeDialogClass);
this.sinon.stub(subject, 'cancel');
subject.cancelDelete();
assert.isTrue(subject.cancel.called);
assert.isFalse(subject.element.classList.contains(
subject.removeDialogClass));
});
});
suite('#save', function() {
var calledWith;
var create;
var offline;
setup(function() {
calledWith = null;
subject.completeUrl = '/settings';
FakePage.shown = null;
create = core.bridge.createAccount;
core.bridge.createAccount = function() {
calledWith = arguments;
return Promise.resolve();
};
offline = subject.isOffline;
subject.isOffline = function() {
return false;
};
});
teardown(function() {
core.bridge.createAccount = create;
subject.isOffline = offline;
});
test('clears errors', function() {
subject.errors.textContent = 'foo';
subject.save();
assert.equal(subject.errors.textContent, '', 'clears text');
});
test('with updateModel option', function() {
subject.fields.user.value = 'iupdatedu';
subject.save({ updateModel: true });
assert.equal(subject.model.user, 'iupdatedu');
});
test('on success', function(done) {
subject.save().then(() => {
done(() => {
assert.equal(calledWith[0], subject.model, 'model');
assert.equal(
FakePage.shown,
subject.completeUrl,
'redirects to complete url'
);
assert.isFalse(
hasClass(subject.progressClass),
'disabled progress class'
);
});
}).catch(done);
});
suite('on failure', function() {
setup(function() {
core.bridge.createAccount = function() {
calledWith = arguments;
return Promise.reject();
};
});
test('failure', function(done) {
subject.save().catch(() => {
done(() => {
assert.ok(calledWith, 'sends request');
assert.equal(calledWith[0], subject.model);
assert.ok(
!hasClass(subject.progressClass),
'hides progress'
);
assert.notEqual(
FakePage.shown,
subject.completeUrl,
'does not redirect on complete'
);
});
});
});
});
});
test('#_createModel', function() {
var preset = 'local';
subject.model = null;
var model = subject._createModel(preset);
assert.instanceOf(model, AccountModel);
assert.equal(
model.providerType,
Presets.local.providerType
);
});
test('#updateForm', function() {
account.user = 'james';
//we never display the password.
account.password = 'baz';
account.fullUrl = 'http://google.com/path/';
subject.updateForm();
assert.equal(fieldValue('user'), 'james');
assert.equal(fieldValue('password'), '');
assert.equal(fieldValue('fullUrl'), 'http://google.com/path/');
});
test('#updateModel', function() {
var fields = subject.fields;
fields.user.value = 'user';
fields.password.value = 'pass';
fields.fullUrl.value = 'http://google.com/foo/';
subject.updateModel();
assert.equal(account.user, 'user');
assert.equal(account.password, 'pass');
assert.equal(account.fullUrl, 'http://google.com/foo/');
});
suite('#dispatch', function() {
test('new', function(done) {
subject.dispatch({
params: { preset: 'local' }
}).then(() => {
done(function() {
assert.instanceOf(
subject.model,
AccountModel,
'creates model'
);
assert.hasProperties(
subject.model,
Presets.local.options,
'uses preset options'
);
assert.equal(subject.preset, Presets.local);
assert.equal(subject.completeUrl, '/settings/');
});
});
});
test('existing', function(done) {
var destroyed;
subject.model = {};
subject.destroy = function() {
destroyed = true;
};
subject.dispatch({
// send as string to emulate real conditions
params: { id: String(account._id) }
}).then(() => {
done(function() {
assert.ok(destroyed, 'should destroy previous state');
assert.equal(subject.completeUrl, '/settings/');
assert.hasProperties(
account,
subject.model,
'loads account'
);
});
});
});
});
suite('#render', function() {
suite('with error', function() {
setup(function() {
account.error = {};
subject.render();
});
test('has .error class', function() {
assert.isTrue(hasClass('error'));
});
});
suite('normal flow', function() {
var create;
setup(function() {
create = core.bridge.createAccount;
account.user = 'foo';
subject.fields.password.value = 'foo';
subject.render();
});
teardown(function() {
core.bridge.createAccount = create;
});
test('save button', function(done) {
subject.fields.user.value = 'updated';
core.bridge.createAccount = function(model) {
done(function() {
assert.equal(
model.user,
subject.fields.user.value,
'updates fields'
);
});
};
triggerEvent(subject.saveButton, 'click');
});
test('type', function(done) {
assert.ok(subject.type);
done();
});
test('update', function(done) {
assert.equal(subject.fields.user.value, 'foo');
done();
});
test('clear password', function() {
assert.equal(subject.fields.password.value, '');
});
test('type class', function() {
assert.isFalse(hasClass('error'));
assert.isTrue(hasClass(subject.type));
assert.isTrue(hasClass('preset-' + account.preset));
assert.isTrue(hasClass('provider-' + account.providerType));
assert.isTrue(hasClass('auth-' + subject.authenticationType));
});
});
suite('oauth flow', function() {
var callsSave;
suiteSetup(setupOauth);
suiteTeardown(teardownOauth);
var clearsCookies;
mozApp = {
clearBrowserData: function() {
var req = {};
nextTick(function() {
clearsCookies = true;
req.onsuccess && req.onsuccess();
});
return req;
}
};
setup(function(done) {
clearsCookies = false;
subject.save = function() {
callsSave = true;
};
// Oauth flows are only for new accounts
subject.model = {};
subject.preset = Presets.google;
subject.render();
var realFlow = subject._redirectToOAuthFlow;
subject._redirectToOAuthFlow = function() {
realFlow.apply(this, arguments);
done();
};
});
test('clears cookies', function() {
assert.ok(clearsCookies, 'cookies where cleared');
});
test('authenticationType', function() {
assert.equal(
subject.authenticationType,
subject.preset.authenticationType,
'sets authentication type to preset'
);
});
test('class names', function() {
assert.isTrue(hasClass('auth-' + subject.authenticationType));
});
test('oauth dialog comples with error', function(done) {
subject.cancel = done;
assert.ok(subject._oauthDialog, 'has dialog');
subject._oauthDialog.oncomplete({ error: 'access_denied' });
});
test('oauth flow is a success', function() {
var code = 'xxx';
assert.ok(subject._oauthDialog, 'has dialog');
subject._oauthDialog.oncomplete({ code: code });
assert.equal(subject.model.oauth.code, code, 'sets code');
assert.ok(callsSave);
});
});
suite('modify oauth account', function() {
suiteSetup(setupOauth);
suiteTeardown(teardownOauth);
setup(function() {
subject.preset = Presets.google;
subject.render();
});
test('oauth flow is not triggered', function() {
assert.equal(subject._oauthDialog, undefined, 'does not have dialog');
assert.ok(subject.fields.user.disabled);
});
test('force sign in', function() {
// stub out real oauth flow window to prevent failures
subject._redirectToOAuthFlow = function() {};
assert.ok(
!subject.oauth2Window.classList.contains('active'),
'is inactive before click'
);
triggerEvent(subject.oauth2SignIn, 'click');
assert.ok(
subject.oauth2Window.classList.contains('active'), 'shows oauth2'
);
});
});
});
suite('#destroy', function() {
suite('normal flow', function() {
setup(function() {
subject.model.error = {};
subject.render();
subject.destroy();
});
test('save button', function() {
var called;
subject._persistForm = function() {
called = true;
};
triggerEvent(subject.saveButton, 'click');
assert.ok(!called);
});
test('fields', function() {
assert.equal(subject._fields, null);
assert.equal(subject.fields.user.disabled, false,
're-enable username field');
});
test('type class', function() {
assert.isFalse(hasClass(subject.type));
assert.isFalse(hasClass('error'));
assert.isFalse(hasClass('preset-' + account.preset));
assert.isFalse(hasClass('provider-' + account.providerType));
});
});
suite('oauth2 edit flow', function() {
setup(function() {
subject.preset = Presets.google;
subject.model._id = 1;
assert.equal(subject.authenticationType, 'oauth2');
subject.render();
subject.destroy();
});
test('should disable force display of oauth2', function() {
triggerEvent(subject.oauth2SignIn, 'click');
assert.ok(
!subject.oauth2Window.classList.contains('active'),
'is ignored after destroy'
);
});
});
suite('submit form', function() {
var create;
setup(function() {
create = core.bridge.createAccount;
account.user = 'foo';
subject.fields.password.value = 'foo';
subject.render();
});
teardown(function() {
core.bridge.createAccount = create;
});
test('default is prevented', function(done) {
subject.element.addEventListener('submit', function(e) {
assert.ok(e.defaultPrevented);
done();
});
core.bridge.createAccount = function(model) {};
triggerEvent(subject.form, 'submit');
});
});
});
});
});

File diff suppressed because it is too large Load diff

View file

@ -0,0 +1,262 @@
define(function(require) {
'use strict';
var MonthDayAgenda = require('views/month_day_agenda');
var core = require('core');
var template = require('templates/month_day_agenda');
var intl = require('intl');
suite('Views.MonthDayAgenda', function() {
var subject;
suiteSetup(function() {
intl.init();
});
teardown(function() {
var el = document.getElementById('test');
el.parentNode.removeChild(el);
core.db.close();
});
setup(function(done) {
var div = document.createElement('div');
div.id = 'test';
div.innerHTML = [
'<div id="month-day-agenda">',
'<div id="event-list-date"></div>',
'<div id="empty-message"></div>',
'<div class="day-events"></div>',
'</div>'
].join(' ');
document.body.appendChild(div);
subject = new MonthDayAgenda();
core.db.open(done);
});
suite('#changeDate', function() {
var currentDate;
setup(function() {
currentDate = document.getElementById('event-list-date');
sinon.spy(core.bridge, 'observeDay');
});
teardown(function() {
core.bridge.observeDay.restore();
});
test('> date', function() {
var now = new Date();
var format = 'months-day-view-header-format';
subject.changeDate(now);
assert.equal(
currentDate.getAttribute('data-l10n-date-format'),
format,
'should set the currentDate textContent');
assert.equal(currentDate.dataset.date, now.toString());
assert.deepEqual(currentDate.dataset.l10nDateFormat, format);
assert.ok(
core.bridge.observeDay.calledWith(now),
'should listen current day'
);
});
test('> null', function() {
var format = 'months-day-view-header-format';
var oldDate = new Date(2012, 8, 1);
subject.date = oldDate;
var oldObserver = { cancel: sinon.spy() };
subject._observer = oldObserver;
subject.changeDate(null);
assert.notDeepEqual(subject.date, oldDate, 'date changed');
assert.equal(
currentDate.getAttribute('data-l10n-date-format'),
format,
'should set the currentDate textContent');
assert.deepEqual(
new Date(currentDate.dataset.date),
new Date(subject.date)
);
assert.deepEqual(currentDate.dataset.l10nDateFormat, format);
assert.ok(
oldObserver.cancel.called,
'should remove previous listener'
);
assert.ok(
core.bridge.observeDay.calledWith(subject.date),
'should listen current day'
);
});
});
suite('#_render', function() {
var events = document.createElement('div');
var emptyMessage = {};
setup(function() {
sinon.stub(subject, '_findElement')
.withArgs('events').returns(events)
.withArgs('emptyMessage').returns(emptyMessage);
subject.date = new Date(2014, 9, 22);
emptyMessage.classList = {
toggle: sinon.spy()
};
sinon.spy(template.event, 'render');
});
teardown(function() {
subject._findElement.restore();
template.event.render.restore();
});
test('> no record', function() {
subject._render({ basic: [], allday: [], amount: 0 });
assert.deepEqual(subject.events.innerHTML, '', 'should clear events');
assert.ok(
emptyMessage.classList.toggle.calledWith('active', true),
'should display empty message'
);
});
test('> with records', function() {
subject._render({
amount: 2,
basic: [
{
event: {
calendarId: '3',
remote: {
title: 'Lorem',
location: 'Ipsum',
alarms: []
}
},
busytime: {
_id: '3-lorem',
startDate: new Date(2014, 9, 22, 1),
endDate: new Date(2014, 9, 22, 3)
},
color: '#BADA55'
},
{
event: {
calendarId: '7',
remote: {
title: 'Foo',
location: 'Bar',
alarms: [1, 2]
}
},
busytime: {
_id: '7-busy-foo',
startDate: new Date(2014, 9, 22, 13),
endDate: new Date(2014, 9, 22, 14)
},
color: '#00FFCC'
}
],
allday: []
});
// should pass proper arguments to template
// output is already tested by template unit test
sinon.assert.calledWithMatch(template.event.render, {
hasAlarms: true,
busytimeId: '7-busy-foo',
color: '#00FFCC',
title: 'Foo',
location: 'Bar',
startTime: new Date(2014, 9, 22, 13),
endTime: new Date(2014, 9, 22, 14),
isAllDay: false
});
});
});
suite('#handleEvent', function() {
/*
// This test is currently failing and has been temporarily disabled as per
// Bug 838993. It should be fixed and re-enabled as soon as possible as per
// Bug 840489.
// This test appears to make incorrect assumptions about localization details
// (it does not fail on systems configured for US English).
test('selectedDayChange', function() {
var date = new Date(2012, 1, 1);
var calledWith;
// start with different date...
subject.date = new Date(2012, 1, 27);
// initialize events
subject.render();
subject.changeDate = function() {
Calendar.Views.MonthsDay.prototype.changeDate.apply(
this, arguments
);
calledWith = arguments;
};
subject.controller.selectedDay = date;
assert.equal(
calledWith[0],
date,
'should change date in view when controller changes'
);
assert.deepEqual(
subject.date,
date
);
var html = subject.header.outerHTML;
assert.ok(html);
assert.include(html, date.toLocaleFormat('%A'));
});
*/
});
/*
// This test is currently failing and has been temporarily disabled as per
// Bug 838993. It should be fixed and re-enabled as soon as possible as per
// Bug 840489.
// This test appears to make incorrect assumptions about localization details
// (it does not fail on systems configured for US English).
test('#render', function() {
var date = new Date();
var span = Calendar.Calc.spanOfDay(date);
subject.render();
assert.deepEqual(subject.timespan, span);
assert.ok(subject.allDayElement, 'has all day');
assert.ok(subject.events, 'has events');
var html = subject.header.outerHTML;
assert.ok(html);
assert.include(html, date.toLocaleFormat('%A'));
});
*/
});
});

View file

@ -0,0 +1,104 @@
define(function(require) {
'use strict';
var MonthDay = require('views/month_day');
suite('Views.MonthDay', function() {
var container;
var subject;
setup(function() {
container = document.createElement('div');
subject = new MonthDay({
container: container,
date: new Date(2014, 6, 23),
month: new Date(2014, 6, 1)
});
});
teardown(function() {
subject.destroy();
container.innerHTML = '';
});
suite('#create', function() {
test('same month', function() {
subject.create();
var id = 'month-view-day-d-2014-6-23';
var li = subject.element;
assert.equal(li.getAttribute('role'), 'gridcell');
assert.equal(li.tabindex, '0');
assert.equal(
li.getAttribute('aria-describedby'),
`${id}-busy-indicator ${id}-description`
);
assert.equal(li.dataset.date, 'd-2014-6-23');
var day = li.querySelector('.day');
assert.equal(day.getAttribute('role'), 'button');
assert.equal(day.textContent.trim(), '23');
var busy = li.querySelector('.busy-indicator');
assert.equal(busy.id, `${id}-busy-indicator`);
assert.equal(busy.getAttribute('aria-hidden'), 'true');
var description = li.querySelector(`#${id}-description`);
assert.equal(description.getAttribute('aria-hidden'), 'true');
assert.equal(description.getAttribute('data-l10n-id'), '');
});
test('past-other-month', function() {
subject.date = new Date(2014, 5, 30);
subject.create();
assert.include(
subject.element.outerHTML,
'data-l10n-id="past-other-month-description"'
);
});
/*
// Test disabled: https://bugzilla.mozilla.org/show_bug.cgi?id=1179085
test('future-other-month', function() {
var future = new Date();
future.setFullYear(future.getFullYear() + 1);
subject.date = future;
subject.create();
assert.include(
subject.element.outerHTML,
'data-l10n-id="future-other-month-description"'
);
});
*/
});
suite('#_updateBusyCount', function() {
var busy;
setup(function() {
subject.create();
busy = subject.element.querySelector('.busy-indicator');
});
test('add', function() {
subject._updateBusyCount({ amount: 2 });
assert.lengthOf(busy.childNodes, 2);
subject._updateBusyCount({ amount: 5 });
assert.lengthOf(busy.childNodes, 3);
});
test('remove', function() {
subject._updateBusyCount({ amount: 5 });
assert.lengthOf(busy.childNodes, 3);
subject._updateBusyCount({ amount: 3 });
assert.lengthOf(busy.childNodes, 3);
subject._updateBusyCount({ amount: 2 });
assert.lengthOf(busy.childNodes, 2);
subject._updateBusyCount({ amount: 0 });
assert.lengthOf(busy.childNodes, 0);
});
});
});
});

View file

@ -0,0 +1,209 @@
define(function(require) {
'use strict';
var Calc = require('common/calc');
var Month = require('views/month');
var core = require('core');
requireCommon('test/synthetic_gestures.js');
suite('Views.Month', function() {
var subject,
controller,
triggerEvent;
suiteSetup(function(done) {
triggerEvent = testSupport.calendar.triggerEvent;
core.db.open(done);
});
suiteTeardown(function() {
core.db.close();
});
setup(function() {
var div = document.createElement('div');
div.id = 'test';
div.innerHTML = [
'<div id="current-month-year">',
'</div>',
'<div id="month-view">',
'</div>'
].join('');
document.body.appendChild(div);
controller = core.timeController;
controller.move(new Date());
subject = new Month();
});
teardown(function() {
subject.destroy();
var el = document.getElementById('test');
el.parentNode.removeChild(el);
});
test('initialization', function() {
assert.equal(subject.element, document.querySelector('#month-view'));
});
suite('events', function() {
setup(function() {
subject._initEvents();
});
test('dom: click', function() {
subject.onfirstseen();
var el = subject.element.querySelector('.month-day');
var date = Calc.dateFromId(el.dataset.date);
triggerEvent(el, 'click');
assert.deepEqual(
controller.selectedDay, date,
'tapping element should change selected date'
);
});
/*
// These tests are currently failing and have been temporarily disabled as per
// Bug 838993. They should be fixed and re-enabled as soon as possible as per
// Bug 840489.
test('dom: dbltap', function() {
var calledWith;
core.router.show = function(url) {
calledWith = url;
};
subject.render();
// find something with [data-date];
var el = subject.element.querySelector(
'[data-date]'
);
triggerEvent(el, 'dbltap');
assert.equal(
calledWith,
'/day/',
'double tapping on date should activate day view'
);
});
*/
test('controller: monthChange', function() {
var calledActivateTime = null;
subject.changeDate = function(month) {
calledActivateTime = month;
};
var date = new Date(2012, 1, 1);
controller.move(date);
assert.deepEqual(calledActivateTime, date);
});
});
test('#_onswipe', function() {
var date = new Date(2012, 4, 1);
var expected = new Date(2012, 3, 1);
subject.date = date;
subject._onswipe({
dy: 0,
dx: window.innerWidth / 5,
direction: 'right'
});
assert.deepEqual(
core.timeController.selectedDay,
expected,
'selects first day of previous month'
);
});
test('#_onwheel', function() {
var date = new Date(2012, 4, 10);
var expected = new Date(2012, 5, 1);
subject.date = date;
subject._onwheel({
deltaMode: window.WheelEvent.DOM_DELTA_PAGE,
DOM_DELTA_PAGE: window.WheelEvent.DOM_DELTA_PAGE,
deltaX: 1,
deltaY: 0
});
assert.deepEqual(
core.timeController.selectedDay,
expected,
'selects first day of month'
);
});
test('#_nextTime', function() {
subject.date = new Date(2012, 4, 1);
assert.deepEqual(
subject._nextTime(),
new Date(2012, 5, 1)
);
});
test('#_previousTime', function() {
subject.date = new Date(2012, 5, 1);
assert.deepEqual(
subject._previousTime(),
new Date(2012, 4, 1)
);
});
test('#changeDate', function() {
var base;
function dayIds() {
var months = subject.element.querySelectorAll('section.month');
return Array.map(months, el => el.dataset.date).join(', ');
}
base = new Date(2014, 10, 11);
subject.changeDate(base);
assert.ok(subject.currentFrame.active, 'currentFrame.active');
assert.deepEqual(
subject.currentFrame.date,
new Date(2014, 10, 1),
'base date should always be first day of the month'
);
assert.equal(
dayIds(),
'd-2014-10-1',
'1: single element at first call'
);
base = new Date(2015, 0, 1);
subject.changeDate(base);
assert.deepEqual(subject.currentFrame.date, base, '2: currentFrame.date');
assert.equal(
dayIds(),
'd-2014-10-1, d-2015-0-1',
'2: month elements should be sorted'
);
base = new Date(2014, 3, 1);
subject.changeDate(base);
assert.deepEqual(subject.currentFrame.date, base, '3: currentFrame.date');
assert.equal(
dayIds(),
'd-2014-3-1, d-2014-10-1, d-2015-0-1',
'3: month elements should be sorted'
);
});
});
});

View file

@ -0,0 +1,126 @@
define(function(require) {
'use strict';
var MultiDay = require('views/multi_day');
var core = require('core');
suite('Views.MultiDay', function() {
var subject;
var timeController;
setup(function() {
subject = new MultiDay();
subject.element = document.createElement('div');
subject.element.innerHTML = '<div class="md__sidebar"></div>';
subject._currentTime = {
refresh: this.sinon.spy()
};
timeController = core.timeController;
});
suite('#onactive', function() {
var stubOnFirstSeen;
var stubRender;
var stubResetScroll;
var stubScrollToHour;
setup(function() {
stubOnFirstSeen = sinon.stub(subject, 'onfirstseen');
stubRender = sinon.stub(subject, '_render');
stubResetScroll = sinon.stub(subject, '_resetScroll');
stubScrollToHour = sinon.stub(subject, '_scrollToHour');
});
teardown(function() {
stubOnFirstSeen.restore();
stubRender.restore();
stubResetScroll.restore();
stubScrollToHour.restore();
});
test('First time active', function() {
subject.onactive();
sinon.assert.calledOnce(stubOnFirstSeen);
sinon.assert.calledOnce(stubRender);
sinon.assert.calledOnce(stubResetScroll);
sinon.assert.calledOnce(stubScrollToHour);
});
test('Do not scroll when come back from other screen', function() {
subject.baseDate = timeController.position;
subject.seen = true;
subject.onactive();
assert.isFalse(stubOnFirstSeen.called);
assert.isFalse(stubResetScroll.called);
assert.isFalse(stubScrollToHour.called);
});
});
suite('localized and timeformatchange', function() {
var sidebar;
setup(function() {
navigator.mozHour12 = false;
sidebar = subject.sidebar;
subject._visibleRange = 123;
});
function assertTimeRefresh() {
// make sure we rebuild all hours during localize
var i = -1, date = new Date(0), hour;
while (++i < 24) {
date.setHours(i, 0, 0, 0);
hour = sidebar.querySelector('.md__hour-' + i);
assert.equal(hour.textContent, i, 'display hour');
assert.equal(
hour.querySelector('.md__display-hour').dataset.date,
date,
'date data'
);
}
// make sure we update the current time
assert.ok(
subject._currentTime.refresh.calledOnce,
'called refresh'
);
assert.equal(
subject._currentTime.timespan,
subject._visibleRange,
'current time timespan matches the _visibleRange'
);
}
test('localized', function() {
subject.handleEvent({type: 'localized'});
assertTimeRefresh();
});
test('timeformatchange', function() {
subject.handleEvent({type: 'timeformatchange'});
assertTimeRefresh();
});
});
test('#_updateBaseDateAfterScroll', function() {
// we need to make sure it's updating the timeController position and
// selectedDay after the drag so moving to day/month views have the
// expected output (highlight first day of the week view)
subject.baseDate = new Date(2014, 6, 23);
subject._updateBaseDateAfterScroll(-3);
var expected = (new Date(2014, 6, 20)).toISOString();
assert.equal(
timeController.position.toISOString(),
expected,
'position'
);
assert.equal(
timeController.selectedDay.toISOString(),
expected,
'selectedDay'
);
});
});
});

View file

@ -0,0 +1,515 @@
define(function(require) {
'use strict';
var CalendarTemplate = require('templates/calendar');
var Factory = require('test/support/factory');
var Settings = require('views/settings');
var View = require('view');
var core = require('core');
var nextTick = require('common/next_tick');
var suiteGroup = require('test/support/suite_group');
var waitFor = require('test/support/wait_for');
requireCommon('test/synthetic_gestures.js');
suiteGroup('views/settings', function() {
/* jshint -W027 */
return;
var subject;
var store;
var controller;
var storeFactory;
var syncController;
var template;
var triggerEvent;
var account;
var models;
function stageModels(list) {
var object = Object.create(null);
setup(function(done) {
account = Factory('account', { _id: 'testacc' });
var trans = core.db.transaction(
['calendars', 'accounts'], 'readwrite'
);
trans.oncomplete = function() {
done();
};
trans.onerror = function(e) {
done(e.target.error);
};
storeFactory.get('Account').persist(account, trans);
var model;
for (var key in list) {
list[key].accountId = account._id;
model = Factory('calendar', list[key]);
store.persist((object[key] = model), trans);
}
});
return object;
}
suiteSetup(function() {
triggerEvent = testSupport.calendar.triggerEvent;
syncController = core.syncController;
});
teardown(function() {
var el = document.getElementById('test');
el.parentNode.removeChild(el);
});
setup(function(done) {
var div = document.createElement('div');
div.id = 'test';
div.innerHTML = [
'<div id="settings">',
' <header>',
' <button data-l10n-id="back"',
' class="settings-back"></button>',
' </header>',
' <div class="settings-shield"></div>',
' <div class="settings-drawer-container">',
' <div class="settings-drawer">',
' <ol class="calendars">',
' </ol>',
' <ul role="tablist" class="bb-tablist" dir="ltr">',
' <li class="settings">',
' <span class="icon"',
' data-l10n-id="advanced-settings-short-icon-button">',
' </span>',
' </li>',
' <li class="sync update">',
' <span class="icon"',
' data-l10n-id="drawer-sync-icon-button"></span>',
' <span class="sync-progress" role="progressbar"></span>',
' </li>',
' </ul>',
' </div>',
' </div>',
'</div>'
].join('');
document.body.appendChild(div);
controller = core.timeController;
storeFactory = core.storeFactory;
store = storeFactory.get('Calendar');
template = CalendarTemplate;
subject = new Settings({
syncProgressTarget: div,
// normally this is higher in production but
// we don't need to wait that long in tests.
waitBeforePersist: 10
});
subject.calendarList = {
first: {
localDisplayed: true,
_id: 'first',
remote: {
name: 'first'
}
},
local: {
localDisplayed: true,
_id: 'local-first',
remote: {
name: 'this should not be used!!!!'
}
}
};
core.db.open(done);
models = stageModels({
displayed: {
localDisplayed: true,
_id: 1
},
hidden: {
localDisplayed: false,
_id: 'hidden'
}
});
});
teardown(function(done) {
testSupport.calendar.clearStore(
core.db,
['accounts', 'calendars'],
function() {
core.db.close();
done();
}
);
});
test('initialization', function() {
assert.instanceOf(subject, View);
assert.equal(
subject.element, document.querySelector('#settings')
);
});
test('#calendars', function() {
assert.ok(subject.calendars);
});
test('#syncButton', function() {
assert.ok(subject.syncButton);
});
test('#syncProgressTarget', function() {
assert.ok(subject.syncProgressTarget);
});
test('#syncProgress', function() {
assert.ok(subject.syncProgress);
});
suite('#_observeAccountStore', function() {
var accounts = testSupport.calendar.dbFixtures(
'account',
'Account',
{
sync: { _id: 'sync', providerType: 'Caldav' },
nosync: { _id: 'nosync', providerType: 'Local' }
}
);
var syncAccount;
var accountStore;
setup(function(done) {
accountStore = storeFactory.get('Account');
syncAccount = accounts.sync;
subject.render();
subject.onrender = done;
});
suite('remove', function() {
setup(function(done) {
accountStore.remove(accounts.sync._id).then(done);
});
test('should remove sync button', function(done) {
waitFor(() => subject.toolbar.classList.contains('noaccount'), done);
});
});
suite('add', function() {
setup(function(done) {
delete syncAccount._id;
accountStore.persist(syncAccount).then(done);
});
test('should show sync button', function(done) {
waitFor(() => !subject.toolbar.classList.contains('noaccount'), done);
});
});
});
suite('#_observeCalendarStore', function() {
var children;
setup(function(done) {
// we must wait until rendering completes
subject.onrender = function() {
children = subject.calendars.children;
nextTick(done);
};
subject.render();
});
suite('calendar update / error', function() {
var model;
var container;
setup(function() {
model = models.first;
container = children[0];
});
test('update with error / without error', function() {
model.error = {};
store.emit('update', model._id, model);
assert.ok(
container.classList.contains('error'),
'has error class'
);
delete model.error;
store.emit('update', model._id, model);
assert.ok(
!container.classList.contains('error'),
'removes error class'
);
});
test('normal flow', function() {
var check = children[0].querySelector(
'*[type="checkbox"]'
);
model.localDisplayed = false;
model.remote.name = 'foo';
store.emit('update', model._id, model);
assert.equal(children[0].textContent.trim(), 'foo');
assert.isFalse(
check.checked
);
});
test('toggle l10n', function() {
var check = children[1].querySelector('[type="checkbox"]');
assert.ok(
check.checked,
'should be checked'
);
var model = models.local;
model.localDisplayed = false;
store.emit('update', model._id, model);
assert.equal(children[1].textContent.trim(), 'Offline calendar');
assert.isFalse(
check.checked,
'should uncheck'
);
});
});
suite('add', function() {
function addModel() {
store.emit('add', 'two', model);
assert.equal(children.length, 3);
var container = children[2];
assert.equal(container.textContent.trim(), 'second');
return container;
}
var model;
setup(function() {
model = Factory('calendar', {
localDisplayed: false,
_id: 'two',
remote: { name: 'second' }
});
assert.equal(children.length, 2);
});
test('success', function() {
var container = addModel();
assert.ok(
!container.classList.contains('error'),
'does not add error'
);
});
test('add with error', function() {
model.error = {};
var container = addModel();
assert.ok(
container.classList.contains('error'),
'has error'
);
});
});
test('remove', function() {
assert.equal(children.length, 2);
store.emit('preRemove', models.first._id);
store.emit('remove', models.first._id);
assert.equal(children.length, 1);
});
});
suite('syncProgress', function() {
test('syncStart', function(done) {
syncController.on('syncStart', function () {
assert.ok(subject.syncProgress.classList.contains('syncing'));
assert.equal(subject.syncProgress.getAttribute('data-l10n-id'),
'sync-progress-syncing');
done();
});
syncController.emit('syncStart');
});
test('syncComplete', function(done) {
syncController.on('syncComplete', function () {
assert.equal(subject.syncProgress.getAttribute('data-l10n-id'),
'sync-progress-complete');
assert.notOk(subject.syncProgress.classList.contains('syncing'));
done();
});
syncController.emit('syncComplete');
});
});
suite('#_onCalendarDisplayToggle', function() {
var checkboxes;
setup(function(done) {
subject.render();
subject.onrender = function() {
checkboxes = {};
for (var id in models) {
checkboxes[id] = subject.calendars.querySelector(
'gaia-checkbox[value="' + models[id]._id + '"]'
);
}
done();
};
});
function checkAsync(id, value) {
nextTick(function() {
checkboxes[id].checked = !!value;
triggerEvent(checkboxes[id], 'change');
});
}
test('changing display state to false', function(done) {
// the goal is to trigger the change event
// multiple times but verify we only persist
// once...
assert.isTrue(
checkboxes.displayed.checked, 'begins checked'
);
// fired when calendar is persisted
subject.ondisplaypersist = function(calendar) {
done(function() {
assert.equal(calendar._id, models.displayed._id);
// verify we set it to false and checkbox is hidden.
assert.isFalse(calendar.localDisplayed);
assert.isFalse(checkboxes.displayed.checked);
});
};
checkAsync('displayed', false);
checkAsync('displayed', true);
checkAsync('displayed', false);
});
test('changing display to true', function(done) {
assert.isFalse(
checkboxes.hidden.checked,
'begins unchecked'
);
checkAsync('hidden', true);
subject.ondisplaypersist = function(calendar) {
done(function() {
assert.equal(calendar._id, models.hidden._id);
assert.isTrue(calendar.localDisplayed);
assert.isTrue(checkboxes.hidden.checked);
});
};
});
});
suite('#render', function() {
testSupport.calendar.dbFixtures(
'account',
'Account', {
one: {
_id: 'one',
providerType: 'Local'
},
two: {
_id: 'two',
providerType: 'Caldav'
}
}
);
var calendars = testSupport.calendar.dbFixtures(
'calendar',
'Calendar', {
one: {
accountId: 'one',
name: 'First',
localDisplayed: true,
_id: 1
},
two: {
accountId: 'two',
name: 'Second',
localDisplayed: false,
_id: 2,
error: {}
}
}
);
suite('calendars', function() {
var one;
var two;
var children;
setup(function(done) {
subject.onrender = function() {
children = subject.calendars.children;
one = children[0];
two = children[1];
done();
};
subject.render();
});
test('number of calendars', function() {
assert.equal(children.length, 2);
});
test('naming', function() {
assert.equal(one.textContent.trim(), calendars.one.name);
assert.equal(two.textContent.trim(), calendars.two.name);
});
test('localDisplayed on calendar', function() {
assert.isTrue(
one.querySelector('*[type="checkbox"]').checked
);
assert.isFalse(
two.querySelector('*[type="checkbox"]').checked
);
});
test('error on calendar', function() {
assert.ok(
two.classList.contains('error'),
'if error is present in model render shows it'
);
});
});
});
});
});

View file

@ -0,0 +1,504 @@
define(function(require) {
'use strict';
var Calc = require('common/calc');
var SingleDay = require('views/single_day');
var core = require('core');
suite('Views.SingleDay', function() {
var alldaysHolder;
var allDayIcon;
var date;
var dayId;
var daysHolder;
var subject;
function sanitize(str) {
return str.replace(/\s*\n\s*/gm, ' ');
}
setup(function(done) {
daysHolder = document.createElement('div');
alldaysHolder = document.createElement('div');
allDayIcon = document.createElement('span');
allDayIcon.id = 'all-day-icon';
date = new Date(2014, 6, 23);
dayId = Calc.getDayId(date);
subject = new SingleDay({
date: date,
daysHolder: daysHolder,
alldaysHolder: alldaysHolder,
hourHeight: 50,
allDayIcon: allDayIcon,
oneDayLabelFormat: 'event-one-day-duration'
});
this.sinon.spy(core.bridge, 'observeDay');
this.sinon.spy(window, 'addEventListener');
this.sinon.spy(window, 'removeEventListener');
this.sinon.spy(subject, 'onactive');
this.sinon.spy(subject, 'oninactive');
core.db.open(done);
});
teardown(function() {
core.bridge.observeDay.restore();
window.addEventListener.restore();
window.removeEventListener.restore();
subject.onactive.restore();
subject.oninactive.restore();
subject.destroy();
core.db.close();
});
test('#setup', function() {
subject.setup();
assert.ok(
subject.onactive.calledOnce,
'activated'
);
});
test('#onactive', function() {
subject.setup();
subject.onactive();
subject.onactive();
assert.ok(
core.bridge.observeDay.calledOnce,
'should add listener only once'
);
assert.ok(
core.bridge.observeDay.calledWithExactly(date),
'observing day events'
);
assert.ok(
window.addEventListener.calledWithExactly('localized', subject),
'observing localized event'
);
});
suite('#oninactive', function() {
var observer;
setup(function() {
subject._observer = observer = { cancel: sinon.spy() };
});
test('> when inactive', function() {
subject.oninactive();
subject.oninactive();
assert.ok(
!observer.cancel.called,
'should not execute if not active'
);
assert.ok(
!window.removeEventListener.called,
'localized'
);
});
test('> when active', function() {
subject._isActive = true;
subject.oninactive();
subject.oninactive();
assert.ok(
observer.cancel.calledOnce,
'should stop listening for date'
);
assert.ok(
window.removeEventListener.calledWithExactly('localized', subject),
'localized'
);
});
});
suite('#append', function() {
setup(function(done) {
subject.setup();
subject.append();
// render is async!
setTimeout(done);
});
test('> should render', function() {
var d = date.toString();
assert.equal(
daysHolder.innerHTML,
`<div class="md__day" data-date="${d}"></div>`,
'should add day node'
);
assert.equal(
sanitize(alldaysHolder.innerHTML),
sanitize(`<div class="md__allday" data-date="${d}">
<h1 class="md__day-name" aria-level="2"
id="md__day-name-${subject._instanceID}">23 Wed</h1>
<div aria-describedby="md__day-name-${subject._instanceID}"
data-l10n-id="create-all-day-event" role="button"
class="md__allday-events"></div>
</div>`)
);
});
});
test('#handleEvent', function() {
subject._dayName = { textContent: 'foo' };
subject.handleEvent({type: 'localized'});
assert.equal(subject._dayName.textContent, '23 Wed');
});
test('#destroy', function() {
// it should remove element from DOM and stop listening busytimes
subject.setup();
subject.append();
subject.destroy();
assert.equal(daysHolder.innerHTML, '');
assert.equal(alldaysHolder.innerHTML, '');
assert.ok(
subject.oninactive.calledOnce,
'should deactivate'
);
});
test('#_render', function() {
setup(function(done) {
subject.setup();
subject.append();
setTimeout(done);
});
test('> should render', function() {
var data = {
amount: 3,
basic: [
makeRecord('Dolor Amet', '4:00', '6:00', 1),
makeRecord('Lorem Ipsum', '5:00', '6:00')
],
allday: [
makeAlldayRecord('Curabitur')
]
};
var makeFirstEventID = makeID.bind(this, subject, data.basic[0]);
var makeSecondEventID = makeID.bind(this, subject, data.basic[1]);
var makeAllDayEventID = makeID.bind(this, subject, data.allday[0]);
// notice that we are testing the method indirectly (the important thing
// to check is if the view updates when we receive new data)
subject._observer.write(data);
assert.lengthOf(
daysHolder.querySelectorAll('.md__event'), 2, '0: events'
);
assert.lengthOf(
alldaysHolder.querySelectorAll('.md__event'), 1, '0: all'
);
// testing the whole markup is enough to check if position and overlaps
// are handled properly and also makes sure all the data is passed to the
// templates, a drawback is that if we change the markup we need to update
// the tests (might catch differences that are not regressions)
var days = daysHolder.querySelectorAll('.md__day');
assert.lengthOf(days, 1, 'single day');
assert.equal(days[0].dataset.date, date.toString(), '[data-date]');
var busytimes = days[0].querySelectorAll('.md__event');
assert.lengthOf(busytimes, 2, 'busytimes');
// busytime #1
var busy = busytimes[0];
// position/size is enough to test overlaps
assert.equal(busy.style.height, '99.9px', '1: height');
assert.equal(busy.style.top, '200px', '1: top');
assert.equal(busy.style.width, '50%', '1: width');
assert.equal(busy.style.left, '50%', '1: left');
assert.include(
busy.href, '/event/show/' + data.basic[0].busytime._id, '1: href');
var labels = busy.getAttribute('aria-labelledby');
assert.include(labels, makeFirstEventID('title'), '1: label title');
assert.include(labels, makeFirstEventID('location'), '1: label location');
assert.include(labels, makeFirstEventID('icon'), '1: label icon');
assert.include(labels, makeFirstEventID('description'), '1: label desc');
assert.include(busy.className, 'has-alarms', '1: class');
assert.include(busy.className, 'has-overlaps', '1: class');
var remote = data.basic[0].event.remote;
var title = busy.querySelector('#' + makeFirstEventID('title'));
assert.equal(title.textContent.trim(), remote.title, '1: title');
var location = busy.querySelector('#' + makeFirstEventID('location'));
assert.equal(location.textContent.trim(), remote.location, '1: location');
var icon = busy.querySelector('#' + makeFirstEventID('icon'));
assert.include(icon.className, 'icon-calendar-alarm', '1: icon');
assert.equal(icon.getAttribute('aria-hidden'), 'true', '1: icon hidden');
var description = busy.querySelector(
'#' + makeFirstEventID('description')
);
assert.equal(
description.getAttribute('aria-hidden'), 'true', '1: desc hidden');
assert.equal(
description.dataset.l10nId, 'event-one-day-duration', '1: desc l10nId'
);
assert.equal(
description.dataset.l10nArgs,
'{"startDate":"Wednesday, July 23, 2014","startTime":"04:00",' +
'"endDate":"Wednesday, July 23, 2014","endTime":"06:00"}',
'1: desc l10nArgs'
);
// busytime #2
busy = busytimes[1];
// position/size is enough to test overlaps
assert.equal(busy.style.height, '49.9px', '2: height');
assert.equal(busy.style.top, '250px', '2: top');
assert.equal(busy.style.width, '50%', '2: width');
assert.equal(busy.style.left, '0%', '2: left');
assert.include(
busy.href, '/event/show/' + data.basic[1].busytime._id, '2: href');
labels = busy.getAttribute('aria-labelledby');
assert.include(labels, makeSecondEventID('title'), '2: label title');
assert.include(
labels, makeSecondEventID('location'), '2: label location'
);
assert.include(labels, makeSecondEventID('description'), '2: label desc');
assert.include(busy.className, 'has-overlaps', '2: class');
remote = data.basic[1].event.remote;
title = busy.querySelector('#' + makeSecondEventID('title'));
assert.equal(title.textContent.trim(), remote.title, '2: title');
location = busy.querySelector('#' + makeSecondEventID('location'));
assert.equal(location.textContent.trim(), remote.location, '2: location');
icon = busy.querySelector('#' + makeSecondEventID('icon'));
assert.isNull(icon, '2: icon');
description = busy.querySelector('#' + makeSecondEventID('description'));
assert.equal(
description.getAttribute('aria-hidden'), 'true', '2: desc hidden');
assert.equal(
description.dataset.l10nId, 'event-one-day-duration', '2: desc l10nId'
);
assert.equal(
description.dataset.l10nArgs,
'{"startDate":"Wednesday, July 23, 2014","startTime":"05:00",' +
'"endDate":"Wednesday, July 23, 2014","endTime":"06:00"}',
'2: desc l10nArgs'
);
var id = subject._instanceID;
var dayName = alldaysHolder.querySelector('.md__day-name');
assert.equal(dayName.getAttribute('aria-level'), '2', '3: day level');
assert.equal(dayName.id, `md__day-name-${id}`, '3: day id');
assert.equal(dayName.textContent.trim(), '23 Wed', '3: day');
var wrapper = alldaysHolder.querySelector('.md__allday-events');
assert.equal(
wrapper.getAttribute('aria-labelledby'),
`all-day-icon md__day-name-${id}`,
'3: wrapper aria-labelledby'
);
assert.equal(
wrapper.getAttribute('aria-describedby'),
`md__day-name-${id}`,
'3: wrapper aria-describedby'
);
assert.equal(
wrapper.dataset.l10nId,
'create-all-day-event',
'3: wrapper data-l10n-id'
);
assert.equal(wrapper.getAttribute('role'), 'listbox', '3: wrapper role');
assert.equal(wrapper.childNodes.length, 1, '3: wrapper childNodes');
busy = wrapper.firstChild;
assert.equal(busy.getAttribute('role'), 'option', '4: busy role');
assert.include(busy.className, 'md__event', '4: busy md__event');
assert.include(busy.className, 'is-allday', '4: busy is-allday');
assert.equal(
busy.style.borderColor,
'rgb(0, 255, 204)',
'4: busy border color'
);
assert.equal(
busy.style.backgroundColor,
'rgba(0, 255, 204, 0.2)',
'4: busy background color'
);
assert.include(
busy.getAttribute('aria-labelledby'),
makeAllDayEventID('location'),
'4: busy location label'
);
assert.include(
busy.getAttribute('aria-labelledby'),
makeAllDayEventID('title'),
'4: busy title label'
);
assert.include(
busy.getAttribute('aria-labelledby'),
makeAllDayEventID('description'),
'4: busy description label'
);
remote = data.allday[0].event.remote;
title = busy.querySelector('.md__event-title');
assert.equal(
title.textContent.trim(),
remote.title,
'4: title'
);
assert.equal(
title.id,
makeAllDayEventID('title'),
'4: title id'
);
location = busy.querySelector('.md__event-location');
assert.equal(
location.textContent.trim(),
remote.location,
'4: location'
);
assert.equal(
location.id,
makeAllDayEventID('location'),
'4: location id'
);
description = busy.querySelector('#' + makeAllDayEventID('description'));
assert.equal(
description.getAttribute('aria-hidden'),
'true',
'4: description aria-hidden'
);
assert.equal(
description.getAttribute('data-l10n-id'),
'event-multiple-day-duration',
'4: description data-l10n-id'
);
assert.equal(
description.dataset.l10nArgs,
'{"startDate":"Wednesday, July 23, 2014","startTime":"00:00",' +
'"endDate":"Thursday, July 24, 2014","endTime":"00:00"}',
'4: description data-l10n-id'
);
data = {
amount: 2,
basic: [
makeRecord('Lorem Ipsum', '5:00', '6:00'),
makeRecord('Maecennas', '6:00', '17:00', 1)
],
allday: []
};
// we always send all the events for that day and redraw whole view
subject._observer.write(data);
// it's enough to check only the new event and the amount of busytimes
// inside each holder (just to ensure we are removing old elements)
busytimes = daysHolder.querySelectorAll('.md__event');
assert.lengthOf(busytimes, 2, '5: events');
assert.lengthOf(
alldaysHolder.querySelectorAll('.md__event'), 0, '5: all'
);
assert.equal(
busytimes[1].querySelector('.md__event-title').textContent.trim(),
data.basic[1].event.remote.title,
'5: event title'
);
});
test('> partial hour', function() {
subject._observer.write({
amount: 4,
basic: [
makeRecord('Lorem Ipsum', '5:00', '5:15'),
makeRecord('Maecennas', '6:00', '6:25'),
makeRecord('Dolor', '6:30', '7:00'),
makeRecord('Amet', '7:00', '7:50')
],
allday: []
});
assert.include(
daysHolder.innerHTML,
'is-partial is-partial-micro',
'smaller than 18min'
);
assert.include(
daysHolder.innerHTML,
'is-partial is-partial-tiny',
'between 18-30min'
);
assert.include(
daysHolder.innerHTML,
'is-partial is-partial-small',
'between 30-45min'
);
assert.include(
daysHolder.innerHTML,
'is-partial"',
'longer than 45min'
);
});
});
function makeID(subject, event, postfix) {
return ['md__event', event.busytime._id, postfix, subject._instanceID]
.join('-').replace(/\s+/g, '-');
}
function makeRecord(title, startTime, endTime, alarmsLength) {
var startDate = new Date(date);
var [startHour, startMinutes] = startTime.split(':');
startDate.setHours(startHour, startMinutes);
var endDate = new Date(date);
var [endHour, endMinutes] = endTime.split(':');
endDate.setHours(endHour, endMinutes);
return {
color: '#00ffcc',
busytime: {
_id: (title + '-' + startTime + '-' + endTime).replace(/[\s:]+/g, '-'),
startDate: startDate,
endDate: endDate
},
event: {
calendarId: 'local-first',
remote: {
title: title,
location: 'Mars',
alarms: new Array(alarmsLength || 0)
}
}
};
}
function makeAlldayRecord(title, alarmsLength) {
var record = makeRecord(title, '0:00', '0:00', alarmsLength);
var endDate = record.busytime.endDate;
endDate.setDate(endDate.getDate() + 1);
return record;
}
});
});

View file

@ -0,0 +1,138 @@
define(function(require) {
'use strict';
var SingleMonth = require('views/single_month');
suite('Views.SingleMonth', function() {
var core;
var subject;
var container;
setup(function() {
core = testSupport.calendar.core();
container = document.createElement('div');
subject = new SingleMonth({
date: new Date(2014, 6, 1),
container: container
});
});
teardown(function() {
subject.destroy();
container.innerHTML = '';
});
test('#create', function() {
subject.create();
var el = subject.element;
assert.equal(el.className, 'month');
assert.equal(el.getAttribute('role'), 'grid');
assert.equal(el.getAttribute('aria-labelledby'), 'current-month-year');
assert.equal(el.getAttribute('aria-readonly'), 'true');
var headers = el.querySelectorAll('li[role="columnheader"]');
Array.forEach(headers, (li, i) => {
assert.equal(li.dataset.l10nId, `weekday-${i}-single-char`);
});
var days = el.querySelectorAll('.day');
assert.lengthOf(days, 35);
assert.lengthOf(subject.days, 35);
// one ol is used for the table header, 5 for the weeks
assert.lengthOf(el.querySelectorAll('ol[role="row"]'), 6);
});
suite('activate/deactivate', function() {
var days;
var tc;
setup(function() {
tc = core.timeController;
core.timeController = {
on: sinon.spy(),
off: sinon.spy()
};
function makeDay() {
return { activate: sinon.spy(), deactivate: sinon.spy() };
}
days = subject.days;
subject.days = [ makeDay(), makeDay() ];
sinon.stub(subject, '_onSelectedDayChange');
sinon.spy(subject, 'oninactive');
});
teardown(function() {
core.timeController = tc;
subject.days = days;
subject._onSelectedDayChange.restore();
subject.oninactive.restore();
});
test('#activate', function() {
subject.activate();
sinon.assert.calledOnce(subject.days[0].activate, 'activate 1st day');
sinon.assert.calledOnce(subject.days[1].activate, 'activate 2nd day');
sinon.assert.calledWith(
core.timeController.on,
'selectedDayChange',
subject
);
// view might be disabled with a selected day, or view might be enabled
// with a preselected day
sinon.assert.calledOnce(subject._onSelectedDayChange);
});
test('#deactivate: not active', function() {
subject.deactivate();
sinon.assert.notCalled(subject.oninactive);
sinon.assert.notCalled(subject.days[0].deactivate);
sinon.assert.notCalled(core.timeController.off);
});
test('#deactivate: active', function() {
subject.activate();
subject.deactivate();
sinon.assert.calledOnce(subject.oninactive);
sinon.assert.calledOnce(subject.days[0].deactivate);
sinon.assert.calledWith(
core.timeController.off,
'selectedDayChange',
subject
);
});
});
test('selectedDayChange', function() {
subject.create();
// no days selected by default
var selected = subject.element.querySelectorAll('li.selected');
assert.lengthOf(selected, 0);
subject.handleEvent({
type: 'selectedDayChange',
data: [ new Date(2014, 6, 7) ]
});
selected = subject.element.querySelectorAll('li.selected');
assert.lengthOf(selected, 1);
assert.equal(selected[0].dataset.date, 'd-2014-6-7');
subject.handleEvent({
type: 'selectedDayChange',
data: [ new Date(2014, 6, 23) ]
});
selected = subject.element.querySelectorAll('li.selected');
assert.lengthOf(selected, 1);
assert.equal(selected[0].dataset.date, 'd-2014-6-23');
});
});
});

View file

@ -0,0 +1,170 @@
define(function(require) {
'use strict';
requireCommon('test/synthetic_gestures.js');
var intl = require('intl');
var TimeHeader = require('views/time_header');
var View = require('view');
var core = require('core');
suite('Views.TimeHeader', function() {
var subject;
var controller;
var date = new Date(2012, 0, 1);
var monthTitle;
suiteSetup(function() {
intl.init();
});
teardown(function() {
var el = document.getElementById('test');
el.parentNode.removeChild(el);
});
setup(function() {
var div = document.createElement('div');
div.id = 'test';
div.innerHTML = [
'<div id="wrapper"></div>',
'<gaia-header id="time-header" action="menu">',
'<h1></h1>',
'</gaia-header>'
].join('');
document.body.appendChild(div);
controller = core.timeController;
subject = new TimeHeader();
controller.move(date);
monthTitle = window.IntlHelper.get('multi-month-view-header-format').
format(date);
});
test('initialization', function() {
assert.instanceOf(subject, View);
assert.ok(subject.element);
assert.equal(
subject.element, document.querySelector('#time-header')
);
});
test('#title', function() {
assert.ok(subject.title);
});
test('#getScale', function() {
var out = subject.getScale('month');
assert.deepEqual(
out,
monthTitle
);
});
test('#getScale for day', function() {
controller.move(new Date(2012, 0, 30));
var compare = window.IntlHelper.get('day-view-header-format').
format(new Date(2012, 0, 30));
var out = subject.getScale('day');
assert.equal(out, compare);
// 20 chars seems to be the maximum with current layout (see bug 951423)
assert.operator(out.length, '<', 21,
'header should not have too many chars');
});
test('#getScale for week', function() {
controller.move(new Date(2012, 0, 15));
var out = subject.getScale('week');
var compare = window.IntlHelper.get('multi-month-view-header-format').
format(new Date(2012, 0, 30));
assert.equal(out, compare);
});
// When week starts in one month and ends in another we need a special format
test('#getScale for week - multiple months', function() {
controller.move(new Date(2012, 0, 30));
var out = subject.getScale('week');
var formatter = window.IntlHelper.get('multi-month-view-header-format');
var compare = formatter.format(new Date(2012, 0, 30));
compare += ' ' + formatter.format(new Date(2012, 1, 4));
assert.equal(out, compare);
});
test('#getScale for week - month ending on Wednesday', function() {
controller.move(new Date(2013, 6, 30));
var out = subject.getScale('week');
var formatter = window.IntlHelper.get('multi-month-view-header-format');
assert.equal(
out,
formatter.format(new Date(2013, 6, 28)) + ' ' +
formatter.format(new Date(2013, 7, 3))
);
});
test('#_updateTitle', function() {
subject._updateTitle();
assert.equal(
subject.title.dataset.date,
controller.position.toString(),
'sets element date'
);
assert.equal(
subject.title.dataset.l10nDateFormat,
subject.scales.month,
'sets element scale'
);
assert.equal(
subject.title.textContent,
subject.getScale('month')
);
});
suite('changing scales', function() {
var calledWith;
setup(function() {
controller.move(date);
controller.scale = 'year';
subject._updateTitle = function() {
calledWith = arguments;
};
// setup initial scale
subject.render();
calledWith = null;
});
test('initial change', function() {
controller.move(new Date(
date.getFullYear(),
1
));
assert.ok(!calledWith, 'dont re-render out of scale');
controller.move(new Date(2020, 1));
assert.ok(calledWith);
});
test('change scale', function() {
controller.scale = 'month';
assert.ok(calledWith);
calledWith = null;
controller.move(new Date(
date.getFullYear(),
date.getMonth() + 1
));
assert.ok(calledWith);
});
});
});
});

View file

@ -0,0 +1,322 @@
/* global suiteTemplate, MockMozIntl */
define(function(require) {
'use strict';
require('/shared/test/unit/mocks/mock_moz_intl.js');
var EventBase = require('views/event_base');
var View = require('view');
var ViewEvent = require('views/view_event');
var core = require('core');
var router = require('router');
var intl = require('intl');
require('dom!show_event');
suite('Views.ViewEvent', function() {
var subject;
var controller;
var event;
var account;
var calendar;
var busytime;
var provider;
var remote;
var eventStore;
var calendarStore;
var accountStore;
var realMozIntl;
function getEl(name) {
return subject.getEl(name);
}
function contentValue(name) {
var field = getEl(name).querySelector('.content');
return field.textContent;
}
var triggerEvent;
suiteSetup(function() {
realMozIntl = window.mozIntl;
window.mozIntl = MockMozIntl;
intl.init();
testSupport.calendar.core();
triggerEvent = testSupport.calendar.triggerEvent;
});
suiteTeardown(function() {
window.mozIntl = realMozIntl;
});
var realGo;
teardown(function() {
router.go = realGo;
});
suiteTemplate('show-event', {
id: 'event-view'
});
setup(function(done) {
realGo = router.go;
var storeFactory = core.storeFactory;
eventStore = storeFactory.get('Event');
accountStore = storeFactory.get('Account');
calendarStore = storeFactory.get('Calendar');
provider = core.providerFactory.get('Mock');
controller = core.timeController;
subject = new ViewEvent();
core.db.open(done);
});
teardown(function(done) {
testSupport.calendar.clearStore(
core.db,
['accounts', 'calendars', 'events', 'busytimes', 'alarms'],
function() {
core.db.close();
done();
}
);
});
testSupport.calendar.accountEnvironment();
testSupport.calendar.eventEnvironment(
// busytime
{
startDate: new Date(2012, 1, 1, 1),
endDate: new Date(2012, 1, 5, 1)
},
// event
{
startDate: new Date(2012, 1, 1, 1),
endDate: new Date(2012, 1, 5, 1)
}
);
setup(function() {
remote = this.event.remote;
event = this.event;
calendar = this.calendar;
account = this.account;
busytime = this.busytime;
});
test('initialization', function() {
assert.instanceOf(subject, View);
assert.instanceOf(subject, EventBase);
assert.equal(subject._changeToken, 0);
assert.ok(subject._els, 'has elements');
});
test('.primaryButton', function() {
assert.ok(subject.primaryButton);
});
test('.header', function() {
assert.ok(subject.header);
});
test('.fieldRoot', function() {
assert.ok(subject.fieldRoot);
assert.equal(subject.fieldRoot, subject.element);
});
test('#setContent', function() {
subject.setContent('title', '<b>text</b>');
assert.equal(
subject.getEl('title').querySelector('.content').innerHTML,
'&lt;b&gt;text&lt;/b&gt;'
);
subject.setContent('title', '<b>text</b>', 'innerHTML');
assert.equal(
subject.getEl('title').querySelector('.content').innerHTML,
'<b>text</b>'
);
});
test('#_getEl', function() {
var expected = subject.fieldRoot.querySelector('.title');
assert.ok(expected);
assert.equal(expected.tagName.toLowerCase(), 'li');
var result = subject.getEl('title');
assert.equal(result, expected);
assert.equal(result, subject.getEl('title'));
assert.equal(subject._els.title, expected);
});
suite('#_updateUI', function() {
var list;
setup(function() {
list = subject.element.classList;
});
function updatesValues(overrides, isAllDay, capabilities) {
capabilities = capabilities || { canUpdate: true };
var expected = {
title: remote.title,
location: remote.location,
currentCalendar: calendar.remote.name,
description: remote.description
};
var allDayHidden = [
'startTime',
'endTime'
];
if (overrides) {
for (var key in overrides) {
expected[key] = overrides[key];
}
}
function verify() {
if (capabilities.canCreateEvent) {
expected.calendarId = event.calendarId;
}
function replaceCaps($1) { return '-' + $1.toLowerCase(); }
for (var key in expected) {
// To dash-delimited
var fieldKey = key.replace(/([A-Z])/g, replaceCaps);
if (isAllDay && allDayHidden.indexOf(key) !== -1) {
assert.equal(
contentValue(fieldKey),
'',
'time element should be empty for all-day: "' + key + '"'
);
continue;
}
if (expected.hasOwnProperty(key)) {
assert.equal(
contentValue(fieldKey),
expected[key],
'should set "' + key + '"'
);
}
}
}
subject.onfirstseen();
subject._useModel({
busytime: busytime,
event: event,
calendar: calendar,
capabilities: capabilities
});
verify();
}
test('event view fields', function() {
updatesValues(null, null);
});
test('readonly', function() {
updatesValues(null, null, {
canUpdateEvent: false,
canCreateEvent: false
});
});
test('event description with html', function() {
event.remote.description = '<strong>hamburger</strong>';
updatesValues(
{ description: '<strong>hamburger</strong>' },
null
);
});
test('alarms are displayed', function() {
event.remote.alarms = [
{trigger: 0},
{trigger: -60}
];
subject.onfirstseen();
subject._useModel({
busytime: busytime,
event: event,
capabilities: { canUpdate: true }
});
var alarmChildren = getEl('alarms').querySelector('.content').children;
assert.equal(
alarmChildren.length,
2
);
assert.equal(
alarmChildren[0].textContent.trim(),
navigator.mozL10n.get('alarm-at-event-standard')
);
assert.equal(
alarmChildren[1].textContent.trim(),
navigator.mozL10n.get('minutes-before', {value: 1})
);
});
});
suite('navigation', function() {
test('cancel button step back', function(done) {
router.go = function(place) {
assert.equal(place, '/foo', 'redirects to proper location');
done();
};
subject._returnTop = '/foo';
triggerEvent(subject.header, 'action');
});
test('cancel button return top', function(done) {
router.go = function(place) {
assert.equal(place, '/bar', 'redirects to proper location');
done();
};
subject._returnTo = '/bar';
triggerEvent(subject.header, 'action');
});
test('edit button click', function(done) {
router.go = function(place) {
assert.equal(place, '/event/edit/funtime/', 'redirects to event page');
done();
};
subject.busytime = {
_id: 'funtime'
};
triggerEvent(subject.primaryButton, 'click');
});
});
});
});

View file

@ -0,0 +1,46 @@
define(function(require) {
'use strict';
var Calc = require('common/calc');
var Week = require('views/week');
suite('Views.Week', function() {
var subject;
setup(function() {
subject = new Week();
});
suite('#_calcBaseDate', function() {
test('Get same date if equal to base date', function() {
var date = new Date(1983, 8, 8);
subject.baseDate = date;
var baseDate = subject._calcBaseDate(date);
assert.isTrue(Calc.isSameDate(baseDate, date));
});
test('Get same date if it is Saturday', function() {
// It is Saturday.
var date = new Date(1983, 8, 10);
var baseDate = subject._calcBaseDate(date);
assert.isTrue(Calc.isSameDate(baseDate, date));
});
test('Get same date if it is Sunday', function() {
// It is Sunday.
var date = new Date(1983, 8, 11);
var baseDate = subject._calcBaseDate(date);
assert.isTrue(Calc.isSameDate(baseDate, date));
});
test('Get monday if date is between Mon to Fri', function() {
// It is Thursday.
var date = new Date(1983, 8, 8);
var baseDate = subject._calcBaseDate(date);
assert.equal(baseDate.getDay(), 1);
});
});
});
});