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,22 @@
{
"extends": "../../../../.jshintrc",
"predef": [
"CSSStyleSheet",
"IDBTransaction",
"assert",
"define",
"expect",
"require",
"requirejs",
"requireCommon",
"setup",
"suite",
"suiteGroup",
"suiteSetup",
"suiteTeardown",
"teardown",
"test",
"testSupport",
"sinon"
]
}

View file

@ -0,0 +1,33 @@
define(function(require) {
'use strict';
var mochaPromise = require('test/support/mocha_promise');
var threads = require('ext/threads');
suite('calendar service', function() {
var thread, client;
setup(function() {
thread = threads.create({
src: '/js/backend/calendar_worker.js',
type: 'worker'
});
client = threads.client('calendar', { thread: thread });
});
teardown(function() {
thread.destroy();
});
mochaPromise(test, 'calling service method', function() {
var call = client.method('echo', 'foo', 'bar', 'baz');
return expect(call).to.eventually.deep.equal([
'foo',
'bar',
'baz'
]);
});
});
});

View file

@ -0,0 +1,842 @@
define(function(require) {
'use strict';
/* global MockMozIntl */
require('/shared/test/unit/mocks/mock_moz_intl.js');
var Timespan = require('common/timespan');
var Calc = require('common/calc');
//Worth noting that these tests will fail
//in horrible ways outside of US timezone.
suite('calendar/calc', function() {
var subject, mocked = {};
setup(function() {
subject = Calc;
window.mozIntl = MockMozIntl;
});
teardown(function() {
var key;
for (key in mocked) {
if (mocked.hasOwnProperty(key)) {
subject[key] = mocked[key];
}
}
});
function mock(fn, value) {
if (!(fn in mocked)) {
mocked[fn] = subject[fn];
}
subject[fn] = function() {
return value;
};
}
suite('#isOnlyDate', function() {
function verify(date, message, isTrue=true) {
test(message + ' ' + date.toString() + ' === ' + isTrue, function() {
assert.equal(
subject.isOnlyDate(date),
isTrue,
message
);
});
}
verify(new Date(2012, 1), 'month');
verify(
new Date(2012, 1, 1),
'YYYY:DD:MM'
);
verify(
new Date(2012, 1, 1, 1),
'hour',
false
);
verify(
new Date(2012, 1, 1, 0, 1),
'minute',
false
);
verify(
new Date(2012, 1, 1, 0, 0, 1),
'second',
false
);
});
suite('#dayOfWeekFromStartDay', function() {
test('sunday', function() {
subject.startDay = 0;
var expected = [
['sun', 0, 0],
['mon', 1, 1],
['tue', 2, 2],
['wed', 3, 3],
['thu', 4, 4],
['fri', 5, 5],
['sat', 6, 6]
];
expected.forEach(function(line) {
var [name, date, numeric] = line;
assert.equal(
subject.dayOfWeekFromStartDay(date),
numeric,
name
);
});
});
test('Monday', function() {
subject.startDay = 1;
var expected = [
['mon', 1, 0],
['tue', 2, 1],
['wed', 3, 2],
['thu', 4, 3],
['fri', 5, 4],
['sat', 6, 5],
['sun', 0, 6]
];
expected.forEach(function(line) {
var [name, date, numeric] = line;
assert.equal(
subject.dayOfWeekFromStartDay(date),
numeric,
name
);
});
});
test('Saturday', function() {
subject.startDay = 6;
var expected = [
['sat', 6, 0],
['sun', 0, 1],
['mon', 1, 2],
['tue', 2, 3],
['wed', 3, 4],
['thu', 4, 5],
['fri', 5, 6]
];
expected.forEach(function(line) {
var [name, date, numeric] = line;
assert.equal(
subject.dayOfWeekFromStartDay(date),
numeric,
name
);
});
});
});
test('#dayOfWeekFromMonday', function() {
var expected = [
['mon', 1, 0],
['tue', 2, 1],
['wed', 3, 2],
['thu', 4, 3],
['fri', 5, 4],
['sat', 6, 5],
['sun', 0, 6]
];
expected.forEach(function(line) {
var [name, date, numeric] = line;
assert.equal(
subject.dayOfWeekFromMonday(date),
numeric,
name
);
});
});
suite('#dayOfWeek', function() {
var realStartDay;
var date = new Date(2012, 0, 1); // 2012-01-01 is a Sunday
suiteSetup(function() {
realStartDay = subject.startDay;
});
suiteTeardown(function() {
subject.startDay = realStartDay;
});
test('weekStartDay = 0', function() {
subject.startDay = 0; // week starts on Sunday => Sunday is day 0
assert.equal(
subject.dayOfWeek(date),
0
);
});
test('weekStartDay = 1', function() {
subject.startDay = 1; // week starts on Monday => Sunday is day 6
assert.equal(
subject.dayOfWeek(date),
6
);
});
test('weekStartDay = 6', function() {
subject.startDay = 6; // week starts on Saturday => Sunday is day 1
assert.equal(
subject.dayOfWeek(date),
1
);
});
});
suite('handle localization events', function() {
var firstDayOfTheWeek = 0;
suiteSetup(function() {
sinon.stub(window.mozIntl, 'calendarInfo', function(token) {
return Promise.resolve(firstDayOfTheWeek);
});
});
suiteTeardown(function() {
window.mozIntl.calendarInfo.restore();
});
test('firstDayOfTheWeek = 1', function(done) {
firstDayOfTheWeek = 1;
window.dispatchEvent(new window.Event('localized'));
Promise.resolve().then(() => {
assert.equal(subject.startDay, 1, 'week starts on Monday');
done();
});
});
test('firstDayOfTheWeek = 6', function(done) {
firstDayOfTheWeek = 6;
window.dispatchEvent(new window.Event('localized'));
Promise.resolve().then(() => {
assert.equal(subject.startDay, 6, 'week starts on Saturday');
done();
});
});
test('firstDayOfTheWeek = 0', function(done) {
firstDayOfTheWeek = 0;
window.dispatchEvent(new window.Event('localized'));
Promise.resolve().then(() => {
assert.equal(subject.startDay, 0, 'week starts on Sunday');
done();
});
});
});
suite('#spanOfMonth', function() {
function time(year, month, day, hour, min) {
var date = new Date(
year,
month || 0,
day || 1,
hour || 0,
min || 0
);
return v(date);
}
function v(date) {
return date.valueOf();
}
test('5 week month', function() {
var date = new Date(2012, 3, 1, 5, 1);
var range = subject.spanOfMonth(date);
assert.equal(
time(2012, 3, 1),
range.start
);
var end = new Date(2012, 4, 6);
end.setMilliseconds(-1);
assert.equal(
range.end,
v(end)
);
});
test('6 week month', function() {
var date = new Date(2012, 11, 1);
var range = subject.spanOfMonth(date);
assert.equal(
time(2012, 10, 25),
range.start
);
var end = new Date(2013, 0, 6);
end.setMilliseconds(-1);
assert.equal(
range.end,
v(end)
);
});
test('4 week month', function() {
var date = new Date(2009, 1, 1);
var range = subject.spanOfMonth(date);
var end = new Date(2009, 2, 1);
end.setMilliseconds(-1);
assert.equal(
range.end,
v(end)
);
});
});
suite('#dateToTransport', function() {
test('ICAL date', function() {
var date = new Date(2012, 0, 1, 11);
var utc = Date.UTC(
date.getFullYear(),
date.getMonth(),
date.getDate(),
date.getHours(),
date.getMinutes(),
date.getSeconds(),
date.getMilliseconds()
);
var expected = {
tzid: subject.FLOATING,
utc: utc,
offset: 0,
isDate: true
};
assert.deepEqual(
subject.dateToTransport(date, null, true),
expected
);
});
test('floating tz', function() {
var date = new Date(2012, 0, 1, 11, 1, 7);
var utc = Date.UTC(
date.getFullYear(),
date.getMonth(),
date.getDate(),
date.getHours(),
date.getMinutes(),
date.getSeconds(),
date.getMilliseconds()
);
var expected = {
tzid: subject.FLOATING,
utc: utc,
offset: 0
};
assert.deepEqual(
subject.dateToTransport(date, subject.FLOATING),
expected
);
});
test('utc', function() {
var date = new Date(2012, 0, 1, 11, 1);
var utc = Date.UTC(2012, 0, 1, 11, 1);
var offset = utc - date;
var expected = {
offset: offset,
utc: utc
};
assert.deepEqual(
subject.dateToTransport(date),
expected
);
});
});
suite('#getUTC', function() {
test('utc - conversion', function() {
var date = new Date(2012, 9, 1, 7, 11);
assert.notEqual(
date,
subject.getUTC(date)
);
});
});
suite('#dateFromTransport', function() {
test('utc - DST', function() {
// NOTE: I don't expect this test to fail
// in other timezones but it relies on
// hard coded DST info to verify that DST
// actually works in PST.
var date = new Date(2012, 9, 1, 7, 11);
var data = subject.dateToTransport(date);
assert.deepEqual(
subject.dateFromTransport(data),
date
);
});
test('utc - standard', function() {
var expected = new Date(2012, 0, 1, 0, 5);
var data = subject.dateToTransport(expected);
assert.deepEqual(
subject.dateFromTransport(data),
expected
);
});
test('floating', function() {
var expected = new Date(2012, 0, 8, 9, 10);
var data = subject.dateToTransport(
expected, subject.FLOATING
);
assert.deepEqual(
subject.dateFromTransport(data),
expected
);
});
});
suite('#getWeekStartDate', function() {
// we are testing for first week of Aug 2012
var expected = new Date(2012, 6, 29);
function matches(given) {
assert.equal(
subject.getWeekStartDate(given).valueOf(),
expected.valueOf()
);
}
test('when given middle', function() {
matches(new Date(2012, 7, 2));
});
test('when given start', function() {
matches(new Date(2012, 6, 29));
});
});
suite('#spanOfDay', function() {
var date = new Date(2012, 1, 1, 10, 33);
test('include time', function() {
var end = new Date(
date.getFullYear(),
date.getMonth(),
date.getDate() + 1
);
var out = subject.spanOfDay(date, true);
assert.deepEqual(out, new Timespan(date, end));
});
test('ignore time', function() {
var start = new Date(
date.getFullYear(),
date.getMonth(),
date.getDate()
);
var end = new Date(start.valueOf());
end.setDate(end.getDate() + 1);
var out = subject.spanOfDay(date);
assert.deepEqual(out, new Timespan(start, end));
});
});
test('#hourDiff', function() {
var start = new Date(2012, 0, 5);
var end = new Date(2012, 0, 7);
var expected = 48;
var out = subject.hourDiff(start, end);
assert.equal(out, expected);
});
suite('#daysBetween', function() {
//Nov 29th 2012
var start = new Date(2012, 10, 29, 2);
// Dec 2nd 2012
var end = new Date(2012, 11, 2, 5);
test('same day', function() {
var end = new Date(
start.getFullYear(),
start.getMonth(),
start.getDate(),
start.getHours() + 1
);
var expected = new Date(
start.getFullYear(),
start.getMonth(),
start.getDate()
);
assert.deepEqual(
subject.daysBetween(start, end),
[expected]
);
});
test('include time', function() {
var expected = [
start,
new Date(2012, 10, 30),
new Date(2012, 11, 1),
end
];
assert.deepEqual(
subject.daysBetween(start, end, true),
expected
);
});
test('exclude time', function() {
var expected = [
new Date(2012, 10, 29),
new Date(2012, 10, 30),
new Date(2012, 11, 1),
new Date(2012, 11, 2)
];
assert.deepEqual(
subject.daysBetween(start, end),
expected
);
});
});
suite('#getWeekEndDate', function() {
// we are testing for first week of Aug 2012
var expected = new Date(2012, 7, 4, 23, 59, 59, 999);
function matches(given) {
assert.equal(
subject.getWeekEndDate(given).valueOf(),
expected.valueOf()
);
}
test('when given middle', function() {
matches(new Date(2012, 7, 2));
});
test('when given start', function() {
matches(new Date(2012, 6, 29));
});
});
suite('#isSameDate', function() {
test('same day', function() {
assert.isTrue(subject.isSameDate(
new Date(2012, 1, 1, 8),
new Date(2012, 1, 1, 23)
));
});
test('same day different month', function() {
assert.isFalse(subject.isSameDate(
new Date(2012, 2, 1, 8),
new Date(2012, 1, 1, 8)
));
});
});
suite('#isToday', function() {
test('when given is today', function() {
var result = subject.isToday(new Date());
assert.isTrue(result, 'should be true when given today');
});
test('when given is not today', function() {
var now = new Date();
now.setDate(now.getDate() - 1);
var result = subject.isToday(now);
assert.isFalse(result, 'should be false when given is not today');
});
});
suite('#isPast', function() {
test('when date is passed', function() {
var date = new Date();
date.setTime(Date.now() - 1000);
var result = subject.isPast(date);
assert.isTrue(result, 'should be true when given is in the past');
});
test('when given is in the future', function() {
var date = new Date();
date.setTime(Date.now() + 100);
var result = subject.isPast(date);
assert.isFalse(result, 'should return false when date is in the future');
});
});
suite('#isFuture', function() {
test('when date is passed', function() {
var date = new Date();
date.setTime(Date.now() - 100);
var result = subject.isFuture(date);
assert.isFalse(result);
});
test('when given is in the future', function() {
var date = new Date(Date.now() + 100);
var result = subject.isFuture(date);
assert.isTrue(result);
});
});
suite('#dateFromId', function() {
var id,
date = new Date(2012, 7, 3);
suite('from day', function() {
setup(function() {
id = subject.getDayId(date);
});
test('id to date', function() {
assert.deepEqual(
subject.dateFromId(id),
date
);
});
});
suite('from month', function() {
setup(function() {
id = subject.getMonthId(date);
});
test('id to date', function() {
assert.deepEqual(
subject.dateFromId(id),
new Date(2012, 7, 1)
);
});
});
});
suite('#getWeeksDays', function() {
test('starting from monday', function() {
//starts on thursday
var start = new Date(2012, 10, 1);
//expected days by their getDayId
var expected = [
'd-2012-9-28',
'd-2012-9-29',
'd-2012-9-30',
'd-2012-9-31',
'd-2012-10-1',
'd-2012-10-2',
'd-2012-10-3'
];
var result = subject.getWeeksDays(start).map(
subject.getDayId
);
assert.deepEqual(result, expected);
});
});
test('#getDayId', function() {
var result = subject.getDayId(
new Date(2012, 3, 7)
);
assert.equal(result, 'd-2012-3-7');
});
test('#getMonthId', function() {
var result = subject.getMonthId(
new Date(2012, 3, 7)
);
assert.equal(result, 'm-2012-3');
});
suite('#relativeState', function() {
setup(function() {
mock('isToday', false);
});
test('when in the past', function() {
mock('isPast', true);
var state = subject.relativeState(
new Date(1991, 1, 1),
new Date(1991, 1, 1)
);
assert.equal(state, subject.PAST);
});
test('when in the future', function() {
mock('isPast', false);
var state = subject.relativeState(
new Date(1991, subject.today.getMonth(), 1),
new Date(1991, subject.today.getMonth(), 1)
);
assert.equal(state, subject.FUTURE);
});
test('when is in a different month in the past', function() {
mock('isPast', true);
var state = subject.relativeState(
new Date(1991, subject.today.getMonth() - 1, 1),
new Date(1991, subject.today.getMonth(), 1)
);
assert.include(state, subject.PAST);
assert.include(state, subject.OTHER_MONTH);
});
test('when is in a different month in the future', function() {
mock('isPast', false);
var state = subject.relativeState(
new Date(1991, subject.today.getMonth() + 1, 1),
new Date(1991, subject.today.getMonth(), 1)
);
assert.include(state, subject.FUTURE);
assert.include(state, subject.OTHER_MONTH);
});
test('when is today', function() {
mock('isToday', true);
var state = subject.relativeState(new Date(1991, 1, 1));
assert.equal(state, subject.PRESENT);
});
});
suite('#isAllDay', function() {
test('full day', function() {
assert.isTrue(subject.isAllDay(
new Date(2014, 9, 5),
new Date(2014, 9, 5),
new Date(2014, 9, 6)
));
});
test('not start of the day', function() {
assert.isFalse(subject.isAllDay(
new Date(2014, 9, 5),
new Date(2014, 9, 5, 5),
new Date(2014, 9, 6)
));
});
test('longer than a full day', function() {
assert.isTrue(subject.isAllDay(
new Date(2014, 9, 5),
new Date(2014, 9, 5),
new Date(2014, 9, 6, 5)
));
});
test('multiple days', function() {
assert.isTrue(subject.isAllDay(
new Date(2014, 9, 5),
new Date(2014, 9, 5),
new Date(2014, 9, 16)
));
assert.isTrue(subject.isAllDay(
new Date(2014, 9, 8),
new Date(2014, 9, 5),
new Date(2014, 9, 16)
));
});
test('same date', function() {
// yahoo uses same start/end dates for recurring all day events
assert.isTrue(subject.isAllDay(
new Date(2014, 9, 5),
new Date(2014, 9, 5),
new Date(2014, 9, 5)
));
});
});
test('#monthStart', function() {
assert.deepEqual(
Calc.monthStart(new Date(2014, 10, 1)),
new Date(2014, 10, 1),
'first day'
);
assert.deepEqual(
Calc.monthStart(new Date(2014, 10, 12)),
new Date(2014, 10, 1),
'middle'
);
});
});
});

View file

@ -0,0 +1,12 @@
define(function(require) {
'use strict';
var compare = require('common/compare');
test('compare', function() {
assert.equal(compare(0, 1), -1);
assert.equal(compare(1, 0), 1);
assert.equal(compare(10, 10), 0);
});
});

View file

@ -0,0 +1,25 @@
define(function(require) {
'use strict';
var CalendarError = require('common/error');
suite('errors', function() {
function verify(symbol) {
test(symbol, function() {
var detail = {};
assert.ok(CalendarError[symbol], 'CalendarError.' + symbol);
var err = new CalendarError[symbol](detail);
assert.equal(detail, err.detail, 'uses detail');
assert.ok(err.l10nID, 'has l10nID');
assert.ok(err.name, 'has name');
});
}
([
'Authentication',
'InvalidServer',
'ServerFailure'
]).forEach(verify);
});
});

View file

@ -0,0 +1,22 @@
define(function(require) {
'use strict';
var nextTick = require('common/next_tick');
test('nextTick', function(done) {
var inc = 0;
nextTick(() => {
assert.equal(inc, 1);
nextTick(() => {
assert.equal(inc, 2);
nextTick(done);
});
inc++;
});
inc = 1;
});
});

View file

@ -0,0 +1,63 @@
define(function(require) {
'use strict';
var object = require('common/object');
suite('Object', function() {
var subject;
setup(function() {
subject = object;
});
test('#filter', function() {
assert.deepEqual(
subject.filter(
{
'0': 'red',
'1': 'orange',
'2': 'yellow',
'3': 'green',
'4': 'blue',
'5': 'violet'
},
(key, value) => {
return value === 'red' || parseInt(key, 10) % 2 === 1;
}
),
[
'red',
'orange',
'green',
'violet'
]
);
});
test('#forEach', function() {
var input = { a: 'b', p: 'q', x: 'y' };
var result = {};
subject.forEach(input, (key, value) => {
result[key] = value;
});
assert.deepEqual(input, result);
});
test('#map', function() {
var input = { a: 'b', p: 'q', x: 'y' };
var result = subject.map(input, (key, value) => {
return value;
});
assert.deepEqual(result, ['b', 'q', 'y']);
});
test('#values', function() {
var input = { a: 'b', p: 'q', x: 'y' };
var result = subject.values(input);
assert.deepEqual(result, ['b', 'q', 'y']);
});
});
});

View file

@ -0,0 +1,10 @@
define(function(require) {
'use strict';
var presets = require('common/presets');
test('list', function() {
assert.instanceOf(presets, Object);
});
});

View file

@ -0,0 +1,146 @@
define(function(require) {
'use strict';
var promise = require('common/promise');
function errorOrFive(shouldError, callback) {
var error;
if (shouldError) {
error = new Error('You told me to error!');
} else {
error = null;
}
return callback(error, !shouldError && 5);
}
suite('denodeify', function() {
var subject;
setup(function() {
subject = promise.denodeify(errorOrFive);
});
test('node-style error', function(done) {
subject(true /* shouldError */, function(error) {
assert.instanceOf(error, Error);
assert.strictEqual(error.message, 'You told me to error!');
done();
});
});
test('node-style without error', function(done) {
subject(false /* shouldError */, function(error, result) {
assert.isNull(error);
assert.strictEqual(result, 5);
done();
});
});
test('promise with error', function(done) {
subject(true /* shouldError */)
.catch(function(error) {
assert.instanceOf(error, Error);
assert.strictEqual(error.message, 'You told me to error!');
done();
});
});
test('promise without error', function(done) {
subject(false /* shouldError */)
.then(function(value) {
assert.strictEqual(value, 5);
done();
});
});
test('no callback and no promise', function() {
// Should not hiccup.
subject(false /* shouldError */);
});
});
function SlowCalculator() {
this.currentValue = 0;
promise.denodeifyAll(this, [
'add',
'multiply'
]);
}
SlowCalculator.prototype = {
add: function(x, callback) {
var error = null;
if (typeof x !== 'number') {
error = new Error('Can only add numbers!');
}
this.currentValue += x;
setTimeout(function() {
callback(error, error || this.currentValue);
}.bind(this), 10);
},
multiply: function(x, callback) {
var error = null;
if (typeof x !== 'number') {
error = new Error('Can only multiply numbers!');
}
this.currentValue *= x;
setTimeout(function() {
callback(error, error || this.currentValue);
}.bind(this), 10);
}
};
suite('denodeifyAll', function() {
var subject;
setup(function() {
subject = new SlowCalculator();
});
test('node-style error', function(done) {
subject.multiply('lolcats', function(error) {
assert.instanceOf(error, Error);
assert.strictEqual(error.message, 'Can only multiply numbers!');
done();
});
});
test('node-style without error', function(done) {
subject.add(5, function(error, result) {
assert.isNull(error);
assert.strictEqual(result, 5);
done();
});
});
test('promise with error', function(done) {
subject.add(/\s+/).catch(function(error) {
assert.instanceOf(error, Error);
assert.strictEqual(error.message, 'Can only add numbers!');
done();
});
});
test('promise without error', function(done) {
subject.add(9)
.then(function(value) {
assert.strictEqual(value, 9);
return subject.multiply(8);
})
.then(function(value) {
assert.strictEqual(value, 72);
done();
});
});
test('no callback and no promise', function() {
// Should not hiccup.
subject.multiply(3.14);
});
});
});

View file

@ -0,0 +1,87 @@
/**
* Responder is actually an downstream of test-agent
* porting the handleEvent thing over here for now
* to eventually be moved back with the other tweaks.
*/
define(function(require) {
'use strict';
var Responder = require('common/responder');
suite('responder', function() {
var subject;
setup(function() {
subject = new Responder();
});
test('multi arg responder', function() {
var calledWith;
subject.on('test', function() {
calledWith = Array.slice(arguments);
});
subject.respond(['test', 'one', 'two', 'three']);
assert.deepEqual(calledWith, ['one', 'two', 'three']);
});
suite('handleEvent', function() {
test('object', function() {
var events = {};
var target = {
// mimic the actual eventTarget api's
handleEvent: function(e) {
var type = e.type;
if (!(type in events)) {
events[type] = [];
}
events[type].push(e.data);
}
};
subject.on('foo', target);
subject.on('foo', target);
subject.on('bar', target);
subject.emit('foo', 1);
subject.emit('bar', 1, 2);
assert.deepEqual(
events.foo,
[[1], [1]]
);
assert.deepEqual(
events.bar,
[[1, 2]]
);
});
});
test('#emitWhenListener', function(done) {
subject.emitWhenListener('foo', 'bar');
subject.emitWhenListener('foo', 'baz');
// Let the event loop tick before listening for the 'foo' topic.
setTimeout(() => {
var count = 0;
subject.on('foo', function onFoo(word) {
if (count === 0) {
assert.equal(word, 'bar');
count++;
return;
}
assert.equal(word, 'baz');
subject.off('foo', onFoo);
done();
});
}, 0);
});
});
});

View file

@ -0,0 +1,358 @@
define(function(require) {
'use strict';
var Calc = require('common/calc');
var Timespan = require('common/timespan');
suite('timespan', function() {
var subject;
var startDate;
var endDate;
setup(function() {
startDate = new Date(2012, 1, 1);
endDate = new Date(2012, 12, 1);
subject = new Timespan(
startDate,
endDate
);
});
test('initializer', function() {
assert.equal(
subject.start,
startDate.valueOf()
);
assert.equal(
subject.end,
endDate.valueOf()
);
});
test('#isEqual', function() {
var span = new Timespan(
new Date(2012, 1, 1),
new Date(2012, 1, 5)
);
var eqlSpan = new Timespan(
new Date(2012, 1, 1),
new Date(2012, 1, 5)
);
var notEqualSpan = new Timespan(
new Date(2012, 1, 1),
new Date(2012, 1, 6)
);
assert.isTrue(span.isEqual(eqlSpan));
assert.isFalse(span.isEqual(notEqualSpan));
});
test('#daysBetween', function() {
var range = new Timespan(
new Date(2012, 1, 1),
new Date(2012, 1, 3)
);
var dates = Calc.daysBetween(range);
assert.deepEqual(
dates[0],
new Date(2012, 1, 1)
);
assert.deepEqual(
dates[1],
new Date(2012, 1, 2)
);
assert.deepEqual(
dates[2],
new Date(2012, 1, 3)
);
});
suite('#trimOverlap', function() {
var before;
var subject;
var none;
setup(function() {
none = new Timespan(
new Date(2012, 5, 1),
new Date(2012, 5, 15)
);
before = new Timespan(
// July 1
new Date(2012, 6, 1),
// Aug 4th
new Date(2012, 7, 4)
);
subject = new Timespan(
// July 29th
new Date(2012, 6, 29),
// Aug 31
new Date(2012, 7, 31)
);
});
test('middle', function() {
var long = new Timespan(
new Date(2012, 0, 1),
new Date(2012, 0, 31)
);
var short = new Timespan(
new Date(2012, 0, 5),
new Date(2012, 0, 10)
);
assert.isNull(
long.trimOverlap(short),
'should return null subject contains given'
);
assert.isNull(
short.trimOverlap(long),
'should return null when given contains subject'
);
});
test('no overlap', function() {
var out = before.trimOverlap(none);
assert.deepEqual(out, none);
});
test('overlaps && input.end > subject.start', function() {
var output = subject.trimOverlap(
before
);
var start = new Date(2012, 6, 1);
var end = new Date(
2012, 6, 29
);
end.setMilliseconds(-1);
var expected = new Timespan(
start, end
);
assert.deepEqual(output, expected);
});
test('overlaps && subject.end > input.start', function() {
var output = before.trimOverlap(
subject
);
var start = new Date(2012, 7, 4);
start.setMilliseconds(
start.getMilliseconds() + 1
);
var end = new Date(
2012, 7, 31
);
var expected = new Timespan(
start, end
);
assert.deepEqual(output, expected);
});
});
suite('#overlaps', function() {
var dates;
suiteSetup(function() {
dates = {
'range: inside': {
assert: true,
value: new Timespan(
startDate,
endDate
)
},
'range: starts on end date': {
assert: false,
value: new Timespan(
endDate,
new Date(2015, 1, 1)
)
},
'range: outside before': {
assert: false,
value: new Timespan(
new Date(1991, 1, 1),
new Date(2000, 1, 1)
)
},
'range: outside after': {
assert: false,
value: new Timespan(
new Date(2015, 1, 1),
new Date(2016, 1, 1)
)
},
'range: greater': {
assert: true,
value: new Timespan(
new Date(2011, 12, 31),
new Date(2012, 12, 2)
)
},
'range overlap: start before': {
assert: true,
value: new Timespan(
new Date(2011, 1, 1),
new Date(2012, 1, 15)
)
},
'range overlap: end before': {
assert: true,
value: new Timespan(
new Date(2012, 1, 2),
new Date(2012, 11, 12)
)
}
};
});
test('overlaping times', function() {
var key;
for (key in dates) {
if (dates.hasOwnProperty(key)) {
var value = dates[key].value;
assert.equal(
subject.overlaps(value),
dates[key].assert,
key
);
}
}
});
test('overlaping times as dates', function() {
var key;
for (key in dates) {
if (dates.hasOwnProperty(key)) {
var value = dates[key].value;
var start = new Date(value.start);
var end = new Date(value.end);
assert.equal(
subject.overlaps(start, end),
dates[key].assert,
key
);
}
}
});
});
suite('#contains', function() {
var dates;
suiteSetup(function() {
dates = {
'day of': {
assert: true,
value: new Date(2012, 1, 1)
},
'one day after': {
assert: true,
value: new Date(2012, 1, 2)
},
'day of end range': {
assert: true,
value: new Date(2012, 12, 1)
},
'day before range': {
assert: false,
value: new Date(2011, 12, 31)
},
'day after range': {
assert: false,
value: new Date(2012, 12, 2)
}
};
});
test('timespan in range', function() {
var span = new Timespan(
new Date(2012, 1, 1),
new Date(2012, 1, 5)
);
assert.isTrue(
subject.contains(span)
);
assert.isFalse(
span.contains(subject)
);
});
function testWith(method, useNumeric) {
var key;
var prefix = '';
if (useNumeric) {
prefix += '[numeric] ';
}
prefix += method;
for (key in dates) {
if (dates.hasOwnProperty(key)) {
var value = dates[key].value.valueOf();
if (useNumeric) {
value = value.valueOf();
}
assert.equal(
subject.contains(value),
dates[key].assert,
prefix + key
);
}
}
}
test('numeric (contains)', function() {
testWith('contains', true);
});
test('numeric (containsNumeric)', function() {
testWith('containsNumeric', true);
});
test('date', function() {
testWith('contains', false);
});
});
});
});

View file

@ -0,0 +1,160 @@
define(function(require) {
'use strict';
var CalendarError = require('common/error');
var ErrorController = require('controllers/error');
var Factory = require('test/support/factory');
var Responder = require('common/responder');
var core = require('core');
var nextTick = require('common/next_tick');
var notification = require('notification');
suite('controllers/error', function() {
/**
* Because of uplifting difficulties I chose to copy/paste
* the following code rather then attempt to uplift related testing fixes...
* I may later live to regret this but given the short time we have until
* shipping v1.0.1 I have chosen to copy/paste.
*/
function mockRequestWakeLock(handler) {
var realApi;
function lockMock() {
return {
mAquired: false,
mIsUnlocked: false,
unlock: function() {
this.mIsUnlocked = true;
}
};
}
suiteSetup(function() {
realApi = navigator.requestWakeLock;
navigator.requestWakeLock = function(type) {
var lock = lockMock();
lock.type = type;
lock.mAquired = true;
handler && handler(lock);
return lock;
};
});
suiteTeardown(function() {
navigator.requestWakeLock = realApi;
});
}
var subject;
var detail;
setup(function(done) {
testSupport.calendar.core();
subject = new ErrorController();
core.db.open(done);
detail = {
account: Factory('account')
};
});
teardown(function(done) {
testSupport.calendar.clearStore(
core.db,
['accounts'],
function() {
core.db.close();
done();
}
);
});
test('initialization', function() {
assert.instanceOf(subject, Responder);
});
suite('default handling', function() {
test('authenticate', function(done) {
var callsAuth = false;
var error = new CalendarError.Authentication(detail);
subject.handleAuthenticate = function(account) {
assert.equal(account, detail.account, 'sends account');
callsAuth = true;
};
subject.once('error', function(givenErr) {
done(function() {
assert.ok(callsAuth);
assert.equal(error, givenErr, 'sends error');
});
});
subject.dispatch(error);
});
});
test('.accountErrorUrl', function() {
assert.ok(subject.accountErrorUrl);
});
suite('#handleAuthenticate', function() {
var sent;
var account;
var lock;
mockRequestWakeLock(function(_lock) {
lock = _lock;
});
var realSendApi;
suiteSetup(function() {
realSendApi = notification.sendNotification;
notification.sendNotification = function() {
sent = Array.slice(arguments);
return Promise.resolve();
};
});
suiteTeardown(function() {
notification.sendNotification = realSendApi;
});
setup(function() {
sent = null;
lock = null;
account = Factory('account', { _id: 'woot' });
});
test('with count 1', function(done) {
var expectedURL = subject.accountErrorUrl + account._id;
account.error = { count: 1 };
subject.handleAuthenticate(account, function() {
nextTick(function() {
done(function() {
assert.ok(lock.mIsUnlocked, 'unlocks');
assert.ok(sent, 'sends notification');
assert.equal(sent[2], expectedURL, 'sends to modify account');
});
});
});
assert.ok(lock, 'aquires lock');
});
test('with count 2', function(done) {
account.error = { count: 2 };
subject.handleAuthenticate(account, function() {
done(function() {
assert.ok(!lock, 'does not aquire lock');
assert.ok(!sent, 'does not resend error');
});
});
});
});
});
});

View file

@ -0,0 +1,122 @@
define(function(require) {
'use strict';
/* global MockMozIntl */
require('/shared/test/unit/mocks/mock_moz_intl.js');
var Factory = require('test/support/factory');
var mockRequestWakeLock = require('test/support/mock_request_wake_lock');
var controller = require('controllers/notifications');
var core = require('core');
var mochaPromise = require('test/support/mocha_promise');
var notification = require('notification');
var waitFor = require('test/support/wait_for');
suite('controllers/notifications', function() {
var alarm;
var db;
var sendNotification;
var storeFactory;
setup(function() {
window.mozIntl = MockMozIntl;
mockRequestWakeLock.setup();
sendNotification = sinon.spy(notification, 'sendNotification');
});
setup(function(done) {
storeFactory = core.storeFactory;
db = core.db;
db.open(done);
});
setup(function(done) {
var start = new Date();
var end = new Date();
end.setHours(end.getHours() + 1);
// Inject event, busytime, and alarm into db.
var event = Factory('event', {
_id: 'event-one',
remote: {
title: 'Birthday',
startDate: start,
endDate: end
}
});
event.description = null;
event.remote.description = null;
var busytime = Factory('busytime', {
eventId: 'event-one',
startDate: start,
endDate: end,
_id: 'busytime-one'
});
alarm = Factory('alarm', {
eventId: 'event-one',
busytimeId: 'busytime-one',
_id: 'alarm-one'
});
var eventStore = storeFactory.get('Event');
var busytimeStore = storeFactory.get('Busytime');
var alarmStore = storeFactory.get('Alarm');
Promise.all([
eventStore.persist(event),
busytimeStore.persist(busytime),
alarmStore.persist(alarm)
])
.then(() => done());
});
teardown(function() {
sendNotification.restore();
mockRequestWakeLock.teardown();
});
teardown(function(done) {
testSupport.calendar.clearStore(db, [
'events',
'busytimes',
'alarms'
], done);
});
teardown(function() {
db.close();
});
suite('#onAlarm', function() {
mochaPromise(test, 'cpu lock', function() {
controller.onAlarm(alarm);
return waitFor(function() {
var locks = mockRequestWakeLock.locks;
var lock = locks[0];
return lock && lock.type === 'cpu' && lock.unlocked;
});
});
mochaPromise(test, 'should send alarm notification', function() {
controller.onAlarm(alarm);
return waitFor(function() {
// Notably, even though our event description was null, we don't pass on
// 'null' to the notification body.
return sendNotification.calledWith(
{
id: 'alarm-started-notice',
args: { title: 'Birthday', distance: 'pretty date' }
},
{ raw: '' }
);
});
});
});
});
});

View file

@ -0,0 +1,153 @@
/**
* TODO(gareth): It would be really wonderful for the interactions
* with the account store to be real (not stubbed) but there
* is currently some bizarreness around testSupport.calendar.dbFixtures
* which needs to be addressed first.
*/
define(function(require) {
'use strict';
var createDOMPromise = require('common/create_dom_promise');
var mockAlarms = require('test/support/mock_alarms');
var periodicSync = require('controllers/periodic_sync');
suite('controllers/periodic_sync', function() {
var db, storeFactory;
setup(function(done) {
mockAlarms.setup();
var core = testSupport.calendar.core();
db = core.db;
storeFactory = core.storeFactory;
db.open(done);
});
teardown(function() {
mockAlarms.teardown();
db.close();
});
test('mozAlarms mock', function(done) {
mockAlarms.add(new Date(), 'ignoreTimezone', { type: 'sync' });
createDOMPromise(mockAlarms.getAll())
.should
.eventually
.have
.length(1)
.notify(done);
});
suite('periodic sync', function() {
var settings;
setup(function(done) {
settings = storeFactory.get('Setting');
settings.set('syncFrequency', 1).then(() => done());
});
setup(function(done) {
periodicSync.observe().then(() => done());
});
teardown(function() {
settings._clearCache();
});
teardown(function(done) {
testSupport.calendar.clearStore(db, [ 'settings' ], done);
});
teardown(function() {
periodicSync.unobserve();
});
suite('without initial caldav calendar', function() {
setup(function() {
var accounts = storeFactory.get('Account');
sinon.stub(accounts, 'syncableAccounts').returns(Promise.resolve([]));
});
teardown(function() {
var accounts = storeFactory.get('Account');
accounts.syncableAccounts.restore();
});
test('should not schedule sync initially', function(done) {
createDOMPromise(mockAlarms.getAll())
.should
.eventually
.have
.length(0)
.notify(done);
});
});
suite('with initial caldav calendar', function() {
setup(function() {
var accounts = storeFactory.get('Account');
sinon.stub(accounts, 'syncableAccounts').returns(Promise.resolve([
{ _id: 'one', providerType: 'Caldav' }
]));
});
setup(function(done) {
periodicSync.events.once('schedule', () => done());
var accounts = storeFactory.get('Account');
accounts.emit('persist');
});
teardown(function() {
var accounts = storeFactory.get('Account');
accounts.syncableAccounts.restore();
});
test('should schedule sync initially', function(done) {
createDOMPromise(mockAlarms.getAll())
.should
.eventually
.have
.length(1)
.notify(done);
});
test('sync interval change revokes previous alarm', function(done) {
var first;
periodicSync.events.once('schedule', () => {
var getAll = createDOMPromise(mockAlarms.getAll());
getAll.then(result => {
assert.lengthOf(result, 1);
var alarm = result[0];
assert.notStrictEqual(alarm.alarmId, first);
done();
});
});
var getAll = createDOMPromise(mockAlarms.getAll());
getAll.then(result => {
var alarm = result[0];
first = alarm.alarmId;
var settings = storeFactory.get('Setting');
settings.set('syncFrequency', 2);
});
});
test('removing last caldav account should stop sync', function(done) {
periodicSync.events.on('pause', () => {
var getAll = createDOMPromise(mockAlarms.getAll());
getAll.then(result => {
assert.lengthOf(result, 0);
done();
});
});
var accounts = storeFactory.get('Account');
accounts.syncableAccounts.restore();
sinon.stub(accounts, 'syncableAccounts').returns(Promise.resolve([]));
accounts.emit('remove');
});
});
});
});
});

View file

@ -0,0 +1,305 @@
define(function(require) {
'use strict';
var Factory = require('test/support/factory');
var RecurringEvents = require('controllers/recurring_events');
var Responder = require('common/responder');
var core = require('core');
var nextTick = require('common/next_tick');
suite('Controllers.RecurringEvents', function() {
var db;
var storeFactory;
var subject;
var syncController;
var timeController;
setup(function(done) {
db = core.db;
storeFactory = core.storeFactory;
syncController = core.syncController;
timeController = core.timeController;
subject = new RecurringEvents();
db.open(done);
});
teardown(function(done) {
subject.unobserve();
testSupport.calendar.clearStore(
db,
['accounts'],
function() {
done(function() {
db.close();
});
}
);
});
test('initialization', function() {
assert.instanceOf(subject, Responder);
});
test('#observe', function() {
var date = new Date(2012, 1, 1);
timeController.move(date);
var expandCall;
subject.queueExpand = function(date) {
expandCall = date;
};
subject.observe();
assert.deepEqual(expandCall, date, 'expands initial time');
});
suite('controller events', function() {
var date = new Date(2012, 1, 1);
setup(function(done) {
subject.observe();
subject.waitBeforeMove = 10;
timeController.move(date);
subject.once('expandComplete', done);
});
test('syncComplete', function(done) {
subject.queueExpand = function(date) {
done(function() {
assert.deepEqual(timeController.position, date);
});
};
syncController.emit('syncComplete');
});
/*
// 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('monthChange', function(done) {
var expectedDate = new Date(2012, 10, 1);
// modify wait before move for faster tests.
subject.queueExpand = function(date) {
done(function() {
assert.deepEqual(expectedDate, date);
});
};
timeController.move(new Date(2012, 8, 1));
timeController.move(new Date(2012, 9, 1));
Calendar.nextTick(function() {
timeController.move(expectedDate);
});
});
*/
});
suite('#queueExpand', function() {
var expandCalls;
var expandStartEvents;
setup(function() {
expandCalls = [];
subject.expand = function(date, cb) {
expandCalls.push([date, cb]);
};
expandStartEvents = 0;
subject.on('expandStart', function() {
expandStartEvents++;
});
});
test('multiple expansions', function(done) {
var dates = [];
subject.once('expandComplete', function() {
done(function() {
assert.equal(expandStartEvents, 1, 'calls expand once');
assert.deepEqual(
dates,
[
new Date(2012, 1, 1),
new Date(2012, 8, 1),
new Date(2012, 10, 7)
]
);
});
});
subject.expand = function(date, cb) {
dates.push(date);
nextTick(cb);
};
// should actually trigger because its the first
// item in the queue...
subject.queueExpand(new Date(2012, 1, 1));
nextTick(function() {
subject.queueExpand(new Date(2012, 7, 7));
});
// should be skipped because the next is greater and
// we are still processing the first item in th queue.
subject.queueExpand(new Date(2012, 2, 7));
// queued
subject.queueExpand(new Date(2012, 8, 1));
// should be skipped its less then others
subject.queueExpand(new Date(2012, 1, 2));
nextTick(function() {
// after the second expansion this fires
// so should the final expansion this tests
// some complicated async ordering.
subject.queueExpand(new Date(2012, 10, 7));
});
});
test('single expand', function(done) {
var date = new Date(2012, 1, 7);
subject.once('expandComplete', function() {
done(function() {
assert.ok(!subject.pending);
assert.equal(expandStartEvents, 1);
});
});
subject.queueExpand(date);
assert.ok(subject.pending);
assert.lengthOf(expandCalls, 1);
// verify right date is being expanded.
assert.deepEqual(expandCalls[0][0], date);
// trigger callback
expandCalls[0][1]();
});
});
suite('#expand', function() {
var account;
var provider;
var expandDate = new Date(2012, 1, 1);
var expectedDate;
setup(function() {
expectedDate = new Date(expandDate.valueOf());
expectedDate.setDate(expectedDate.getDate() + subject.paddingInDays);
});
function setupProvider(type, id) {
setup(function(done) {
account = Factory('account', {
providerType: type,
_id: id || type
});
provider = core.providerFactory.get(type);
storeFactory.get('Account').persist(account, done);
});
}
test('with no accounts', function(done) {
subject.expand(expandDate, done);
});
suite('provider that cannot expand', function() {
setupProvider('Local');
test('will not expand', function(done) {
provider.ensureRecurrencesExpanded = function() {
throw new Error('local should not expand');
};
subject.expand(expandDate, done);
});
});
suite('provider that can expand', function() {
// two caldav accounts to catch duplicate busytimes error (Bug 1018833)
setupProvider('Caldav', 1);
setupProvider('Caldav', 2);
// custom helper to allow each test
// to inject specific logic while sharing the
// spy/mock easily... override spyHandler to use.
var spyHandler;
// number of times ensureRecurrencesExpanded was called.
var calledEnsure;
setup(function() {
spyHandler = null;
calledEnsure = 0;
provider.ensureRecurrencesExpanded = function(date, cb) {
calledEnsure++;
// we use the same date for all tests
assert.deepEqual(date, expectedDate);
if (!spyHandler) {
setTimeout(cb, 0, null, false);
} else {
spyHandler(cb);
}
};
});
test('with no expansions required', function(done) {
subject.expand(expandDate, function() {
done(function() {
assert.equal(calledEnsure, 1, 'calls ensure');
});
});
});
test('multiple expansions required', function(done) {
// expected number of times
var requiredTries = 3;
spyHandler = function(cb) {
if (calledEnsure === requiredTries) {
// did not expand
setTimeout(cb, 0, null, false);
} else {
// did expand
setTimeout(cb, 0, null, true);
}
};
subject.expand(expandDate, function() {
done(function() {
assert.equal(calledEnsure, requiredTries, 'number of tries');
});
});
});
test('expand beyond maximum', function(done) {
spyHandler = function(cb) {
nextTick(cb.bind(this, null, true));
};
subject.expand(expandDate, function() {
done(function() {
assert.equal(calledEnsure, subject.maximumExpansions);
});
});
});
});
});
});
});

View file

@ -0,0 +1,47 @@
define(function(require) {
'use strict';
var Manager = require('worker/manager');
var ServiceController = require('controllers/service');
suite('Controllers.Service', function() {
var subject;
setup(function() {
subject = new ServiceController();
});
test('initialize', function() {
assert.instanceOf(subject, Manager);
});
test('#start', function() {
subject.start();
assert.ok(subject._ensureActiveWorker('caldav'));
});
/*
// 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('caldav worker', function(done) {
subject.start();
subject.request('caldav', 'noop', function(data) {
done(function() {
assert.deepEqual(data, { ready: true });
});
});
});
*/
teardown(function() {
subject.workers.forEach(function(worker) {
worker.instance.terminate();
worker.instance = null;
});
});
});
});

View file

@ -0,0 +1,307 @@
define(function(require) {
'use strict';
var CalendarError = require('common/error');
var Factory = require('test/support/factory');
var SyncController = require('controllers/sync');
var core = require('core');
var nextTick = require('common/next_tick');
suite('Controllers.Sync', function() {
var account;
var calendar;
var event;
var subject;
var db;
var accModel;
setup(function(done) {
db = core.db;
subject = new SyncController();
var storeFactory = core.storeFactory;
calendar = storeFactory.get('Calendar');
account = storeFactory.get('Account');
event = storeFactory.get('Event');
accModel = Factory('account', {
_id: 'one'
});
db.open(function(err) {
if (err) {
done(err);
return;
}
done();
});
});
teardown(function(done) {
testSupport.calendar.clearStore(
db,
['accounts', 'calendars', 'events', 'busytimes'],
done
);
});
teardown(function() {
db.close();
});
test('initialization', function() {
assert.equal(subject.pending, 0);
});
test('sync all, no accounts', function(done) {
subject.once('syncComplete', done);
subject.all();
});
test('sync all, no accounts with callback', function(done) {
subject.all(done);
});
suite('#all', function() {
var list = [];
setup(function(done) {
var trans = db.transaction('accounts', 'readwrite');
list.push(Factory('account'));
list.push(Factory('account'));
account.persist(list[0], trans);
account.persist(list[1], trans);
trans.oncomplete = function() {
done();
};
});
test('sync account', function(done) {
var calledModels = [];
var pending = 2;
function complete() {
list.forEach(function(item, idx) {
assert.hasProperties(item, calledModels[idx]);
});
}
subject.account = function(model) {
calledModels.push(model);
if (!--pending) {
done(complete);
}
};
subject.all();
});
});
suite('individual sync operations', function() {
var calendars;
var accountSyncCall;
var calendarSyncCalls;
var events;
function assertEmit(event) {
assert.isTrue(
(event in events),
'has emitted ' + event
);
}
function assertDoesNotEmit(event) {
assert.isFalse(
(event in events),
'emitted ' + event
);
}
var handler = {
handleEvent: function(event) {
events[event.type] = event.data;
}
};
setup(function(done) {
var trans = db.transaction(
['accounts', 'calendars'],
'readwrite'
);
trans.oncomplete = function() {
done();
};
// setup events
events = {};
subject.on('syncStart', handler);
subject.on('syncComplete', handler);
// setup mocks
calendarSyncCalls = [];
account.sync = function() {
accountSyncCall = Array.slice(arguments);
};
calendar.sync = function() {
calendarSyncCalls.push(Array.slice(arguments));
};
// setup db
calendars = [];
account.persist(accModel, trans);
var numberOfCalendars = 2;
while (numberOfCalendars--) {
var item = Factory('calendar', {
accountId: accModel._id
});
calendars.push(item);
calendar.persist(item, trans);
}
});
suite('#account', function() {
var err;
setup(function() {
err = new CalendarError();
account.sync = function() {
var args = Array.slice(arguments);
var cb = args.pop();
nextTick(cb.bind(this, err));
};
});
teardown(function() {
delete account.sync;
delete core.errorController.dispatch;
});
test('error without a callback', function(done) {
core.errorController.dispatch = function(given) {
done(function() {
assert.equal(subject.pending, 0);
assert.equal(err, given);
});
};
assert.equal(subject.pending, 0);
subject.account(accModel);
assert.equal(subject.pending, 1);
});
test('error with a callback', function(done) {
subject.account(accModel, function(givenErr) {
done(function() {
assert.equal(givenErr, err, 'sends error');
assert.equal(subject.pending, 0, 'pending');
});
});
});
test('success', function(done) {
var pendingCalendarSync = 2;
// verify we sync accounts
account.sync = function(account, callback) {
assert.equal(account._id, accModel._id);
assertDoesNotEmit('syncComplete');
callback();
};
var lastCalendar;
// verify we sync calendars
calendar.sync = function(acc, calendar, callback) {
assert.equal(accModel._id, acc._id);
assert.equal(calendar.accountId, acc._id);
nextTick(function() {
callback();
if (!--pendingCalendarSync) {
assert.notEqual(lastCalendar._id, calendar._id);
} else {
lastCalendar = calendar;
assertDoesNotEmit('syncComplete');
}
});
};
subject.account(accModel, function() {
done(function() {
assertEmit('syncComplete');
});
});
});
});
suite('#calendar', function() {
test('multiple in progress', function() {
subject.calendar(accModel, calendars[0]);
assertEmit('syncStart');
delete events.syncStart;
subject.calendar(accModel, calendars[1]);
assertDoesNotEmit('syncStart');
var firstSync = calendarSyncCalls.shift();
firstSync[firstSync.length - 1]();
// because there are two pending...
assertDoesNotEmit('syncComplete');
var secondSync = calendarSyncCalls.shift();
secondSync[secondSync.length - 1]();
// now both are fully completed.
assertEmit('syncComplete');
});
test('success', function(done) {
subject.calendar(accModel, calendars[0], function() {
assertEmit('syncComplete');
done();
});
assertEmit('syncStart');
assert.lengthOf(calendarSyncCalls, 1, 'emits syncComplete');
var sync = calendarSyncCalls[0];
assert.deepEqual(
sync.slice(0, 2),
[accModel, calendars[0]]
);
assertDoesNotEmit('syncComplete');
sync[sync.length - 1]();
});
test('failure', function(done) {
var sentErr = new Error();
subject.calendar(accModel, calendars[0], function(err) {
assert.equal(err, sentErr);
assertEmit('syncComplete');
done();
});
var sync = calendarSyncCalls.shift();
assert.ok(sync);
sync[sync.length - 1](sentErr);
});
});
});
});
});

View file

@ -0,0 +1,256 @@
define(function(require) {
'use strict';
var Responder = require('common/responder');
var TimeController = require('controllers/time');
var core = require('core');
window.page = window.page || {};
suite('Controllers.Time', function() {
var subject;
var busytimeStore;
var db;
setup(function(done) {
subject = new TimeController();
subject.calendarStore = {
shouldDisplayCalendar: function(id) {
return true;
},
on: function() {}
};
busytimeStore = core.storeFactory.get('Busytime');
db = core.db;
db.open(function() {
done();
});
});
teardown(function(done) {
testSupport.calendar.clearStore(
core.db,
['events', 'busytimes', 'alarms'],
done
);
});
teardown(function() {
core.db.close();
});
test('initialize', function() {
assert.instanceOf(subject, Responder);
});
suite('#handleEvent', function() {
test('switching between days', function() {
function type() {
return subject.mostRecentDayType;
}
subject.selectedDay = new Date(2012, 1, 5);
assert.deepEqual(
subject.mostRecentDay,
subject.selectedDay,
'mostRecentDay - selected day'
);
assert.equal(
type(),
'selectedDay',
'"selectedDay" change should update type'
);
subject.move(new Date(2012, 1, 10));
assert.equal(
type(), 'day',
'move - sets most recent type'
);
assert.deepEqual(
subject.mostRecentDay,
subject.position,
'mostRecentDay - day'
);
// back & forth
subject.move(new Date(2012, 1, 15));
assert.equal(type(), 'day');
subject.selectedDay = new Date(2012, 1, 20);
assert.equal(type(), 'selectedDay');
});
});
test('#scale', function() {
var calledWith;
subject.on('scaleChange', function() {
calledWith = Array.slice(arguments);
});
subject.scale = 'year';
assert.deepEqual(calledWith, ['year', null]);
calledWith = null;
subject.scale = 'year';
assert.isNull(calledWith, 'should not trigger change when value is same');
subject.scale = 'day';
assert.deepEqual(
calledWith,
['day', 'year']
);
});
test('#moveToMostRecentDay', function() {
var date = new Date();
var calledMove;
subject.move(date);
subject.move = function() {
TimeController.prototype.move.apply(this, arguments);
calledMove = arguments;
};
subject.selectedDay = new Date(2012, 1, 1);
subject.moveToMostRecentDay();
assert.equal(
calledMove[0],
subject.selectedDay,
'should move to selected day'
);
calledMove = null;
subject.moveToMostRecentDay();
assert.ok(!calledMove, 'should not move when "day" was last changed');
});
test('#selectedDay', function() {
var calledWith;
subject.on('selectedDayChange', function() {
calledWith = arguments;
});
var date = new Date(2012, 1, 1);
subject.selectedDay = date;
assert.equal(calledWith[0], date);
assert.equal(subject.selectedDay, date);
calledWith = null;
// try and set it again with same object...
subject.selectedDay = new Date(2012, 1, 1);
assert.isNull(calledWith, 'should not fire event when day is same');
var newDate = new Date(2012, 1, 2);
subject.selectedDay = newDate;
assert.equal(subject.selectedDay, newDate);
assert.equal(calledWith[0], newDate);
assert.equal(calledWith[1], date);
});
suite('#move', function() {
var events;
var date;
function clearEvents() {
events = {
year: [],
month: [],
day: []
};
}
setup(function() {
clearEvents();
date = new Date(2012, 5, 5);
var handle = {
handleEvent: function(e) {
var name = e.type.replace('Change', '');
events[name].push(e.data[0]);
}
};
subject.on('yearChange', handle);
subject.on('monthChange', handle);
subject.on('dayChange', handle);
subject.move(date);
});
function fires(type, value) {
var eventValue = events[type][0];
assert.deepEqual(
eventValue, value, 'fires: ' + type + ' change'
);
assert.deepEqual(
subject[type],
value,
'should update .' + type
);
}
function doesNotFire(type) {
assert.deepEqual(
events[type],
[]
);
}
test('initial move', function() {
fires('year', new Date(2012, 0, 1));
fires('month', new Date(2012, 5, 1));
fires('day', new Date(2012, 5, 5));
});
test('move day', function() {
clearEvents();
subject.move(new Date(2012, 5, 6));
doesNotFire('year');
doesNotFire('month');
fires('day', new Date(2012, 5, 6));
});
test('move month', function() {
clearEvents();
subject.move(new Date(2012, 8, 6));
doesNotFire('year');
fires('month', new Date(2012, 8, 1));
fires('day', new Date(2012, 8, 6));
});
test('move into the past', function() {
clearEvents();
subject.move(new Date(2011, 5, 4));
fires('year', new Date(2011, 0, 1));
fires('month', new Date(2011, 5, 1));
fires('day', new Date(2011, 5, 4));
});
});
});
});

View file

@ -0,0 +1,355 @@
define(function(require) {
'use strict';
var Factory = require('test/support/factory');
var core = require('core');
var dayObserver = require('day_observer');
suite('day_observer', function() {
var busyToday1, busyToday2, busyTomorrow, busyTodayAllday;
var event1, event2, event3, event4;
var calendar, hiddenCalendar;
var calendarColor = '#00aacc';
var hiddenCalendarColor = '#BADA55';
var busytimeStore, calendarStore, eventStore;
var subject;
var today;
var tomorrow;
var yesterday;
var oneHour = 60 * 60 * 1000;
suiteSetup(function(done) {
subject = dayObserver;
var storeFactory = core.storeFactory;
eventStore = storeFactory.get('Event');
busytimeStore = storeFactory.get('Busytime');
calendarStore = storeFactory.get('Calendar');
today = new Date();
today.setHours(8, 0, 0, 0);
// Set the date to the 15th, so it's in the middle of the month.
// This helps avoid bug 1128172.
var DAY = 15;
today.setDate(DAY);
yesterday = new Date();
yesterday.setHours(8, 0, 0, 0);
yesterday.setDate(DAY - 1);
tomorrow = new Date();
tomorrow.setHours(8, 0, 0, 0);
tomorrow.setDate(DAY + 1);
core.db.open(done);
});
suiteSetup(function(done) {
calendarStore.all((err) => {
done(err);
});
});
// we add the events to the DB before calling subject.init() on purpose, just
// to simulate the app start
suiteSetup(function(done) {
calendar = Factory('calendar', {
localDisplayed: true,
color: calendarColor
});
hiddenCalendar = Factory('calendar', {
localDisplayed: false,
color: hiddenCalendarColor
});
var calendarId = calendar._id;
event1 = Factory('event', {
_id: 'foo',
calendarId: calendarId
});
event2 = Factory('event', {
_id: 'bar',
calendarId: calendarId
});
event3 = Factory('event', {
_id: 'baz',
calendarId: calendarId
});
event4 = Factory('event', {
_id: 'bag',
calendarId: calendarId
});
busyToday1 = Factory('busytime', {
calendarId: calendarId,
eventId: event1._id,
startDate: today
});
// tests events that spans thru multiple days
busyToday2 = Factory('busytime', {
calendarId: calendarId,
eventId: event2._id,
startDate: new Date(today.getTime() + oneHour),
endDate: tomorrow
});
busyTomorrow = Factory('busytime', {
calendarId: calendarId,
eventId: event3._id,
startDate: tomorrow
});
var todayStart = new Date(today.getTime());
todayStart.setHours(0, 0, 0, 0);
var tomorrowStart = new Date(todayStart.getTime());
tomorrowStart.setHours(24);
busyTodayAllday = Factory('busytime', {
calendarId: calendarId,
eventId: event4._id,
startDate: todayStart,
endDate: tomorrowStart
});
// events should be persisted before the busytimes
Promise.all([
calendarStore.persist(calendar),
calendarStore.persist(hiddenCalendar),
eventStore.persist(event1),
eventStore.persist(event2),
eventStore.persist(event3),
eventStore.persist(event4)
]).then(function() {
Promise.all([
busytimeStore.persist(busyToday1),
busytimeStore.persist(busyToday2),
busytimeStore.persist(busyTomorrow),
busytimeStore.persist(busyTodayAllday)
]).then(function() {
subject.init();
done();
});
});
});
suiteTeardown(function(done) {
testSupport.calendar.clearStore(
core.db,
['events', 'busytimes', 'calendars'],
done
);
});
suiteTeardown(function() {
core.db.close();
});
teardown(function() {
subject.removeAllListeners();
});
suite('empty cache', function() {
test('#on: today', function(done) {
subject.on(today, function(records) {
assert.equal(records.amount, 0);
assert.deepEqual(records.basic, []);
assert.deepEqual(records.allday, []);
done();
});
});
test('#findAssociated', function(done) {
subject.findAssociated(busyTomorrow._id).then(function(record) {
assert.deepEqual(record.busytime, busyTomorrow);
assert.deepEqual(record.event, event3);
done();
});
});
});
suite('cached', function() {
suiteSetup(function(done) {
// make sure we wait enough time until all records are loaded from DB
function onRecords(records) {
if (records.amount > 0) {
subject.off(today, onRecords);
done();
}
}
// ensures 'monthChange' triggers the load of busytimes, we are not
// calling `move()` because this is more flexible and less error prone
core.timeController.emit('monthChange', today);
subject.on(today, onRecords);
});
test('#on: today', function(done) {
subject.on(today, function(records) {
assert.equal(records.amount, 3);
assert.deepEqual(records.basic, [
{
event: event1,
busytime: busyToday1,
color: calendarColor
},
{
event: event2,
busytime: busyToday2,
color: calendarColor
}
]);
assert.deepEqual(records.allday, [
{
event: event4,
busytime: busyTodayAllday,
color: calendarColor
}
]);
done();
});
});
test('#on: tomorrow', function(done) {
subject.on(tomorrow, function(records) {
assert.equal(records.amount, 2);
assert.deepEqual(records.basic, [
{
event: event2,
busytime: busyToday2,
color: calendarColor
},
{
event: event3,
busytime: busyTomorrow,
color: calendarColor
}
]);
assert.deepEqual(records.allday, []);
done();
});
});
test('#on: yesterday', function(done) {
subject.on(yesterday, function(records) {
assert.equal(records.amount, 0);
assert.deepEqual(records.basic, []);
assert.deepEqual(records.allday, []);
done();
});
});
test('#on: yesterday + persist + remove + visibility', function(done) {
/* disabled due to intermittent failures see bug 1128275 */
/* jshint -W027 */
return done();
var count = 0;
var busyYesterday = Factory('busytime', {
calendarId: calendar._id,
eventId: event3._id,
startDate: yesterday
});
var busyYesterday2 = Factory('busytime', {
calendarId: calendar._id,
eventId: event4._id,
startDate: new Date(yesterday.getTime() + oneHour)
});
// events from hidden calendars should not be displayed
var busyYesterdayHidden = Factory('busytime', {
calendarId: hiddenCalendar._id,
eventId: event4._id,
startDate: new Date(yesterday.getTime() + oneHour)
});
subject.on(yesterday, function(records) {
count += 1;
// it should call it once without the new events to make sure our views
// are "eventually" in sync
if (count === 1) {
assert.equal(records.amount, 0, 'first');
assert.deepEqual(records.basic, []);
assert.deepEqual(records.allday, []);
return;
}
if (count === 2) {
if (records.amount === 1) {
// busytimeStore.persist is async and might take longer than
// a single dispatch to display all the busytimes, so we make sure
// we only bump the count if it really updated the value to match
// the expected result. that is enough to prove that UI will
// "eventually" reflect the correct amount of items. (Bug 1115083)
count -= 1;
return;
}
assert.equal(records.amount, 2, 'after persist');
assert.deepEqual(records.basic, [
{
event: event3,
busytime: busyYesterday,
color: calendarColor
},
{
event: event4,
busytime: busyYesterday2,
color: calendarColor
}
]);
assert.deepEqual(records.allday, []);
// remove busytime
busytimeStore.remove(busyYesterday2._id);
return;
}
if (count === 3) {
assert.equal(records.amount, 1, 'after remove');
assert.deepEqual(records.basic, [
{
event: event3,
busytime: busyYesterday,
color: calendarColor
}
]);
assert.deepEqual(records.allday, []);
// toggle calendar visibility
calendarStore.persist(Factory('calendar', {
_id: hiddenCalendar._id,
localDisplayed: true
}));
return;
}
assert.equal(records.amount, 2, 'after calendar visible');
assert.deepEqual(records.basic, [
{
event: event3,
busytime: busyYesterday,
color: calendarColor
},
{
event: event4,
busytime: busyYesterdayHidden,
color: calendarColor
}
]);
assert.deepEqual(records.allday, []);
done();
});
busytimeStore.persist(busyYesterday);
busytimeStore.persist(busyYesterday2);
busytimeStore.persist(busyYesterdayHidden);
});
test('#findAssociated', function(done) {
subject.findAssociated(busyToday1._id).then(function(record) {
assert.deepEqual(record.busytime, busyToday1);
assert.deepEqual(record.event, event1);
done();
});
});
});
});
});

View file

@ -0,0 +1,529 @@
define(function(require) {
'use strict';
var Db = require('db');
var Factory = require('test/support/factory');
var Responder = require('common/responder');
var core = require('core');
suite('db', function() {
var subject;
var storeFactory;
setup(function(done) {
var db = core.db;
db.deleteDatabase(function(err, success) {
assert.ok(!err, 'should not have an error when deleting db');
assert.ok(success, 'should be able to delete the db');
done();
});
});
teardown(function() {
subject.close();
});
setup(function() {
subject = core.db;
storeFactory = core.storeFactory;
});
test('initialization', function() {
// create test db
assert.ok(subject.name);
assert.include(subject.name, 'test');
assert.ok(subject.store);
assert.instanceOf(subject, Responder);
assert.isTrue(Object.isFrozen(subject.store));
});
suite('#transaction', function() {
setup(done => subject.open(done));
test('result', function(done) {
var trans = subject.transaction(['events'], 'readonly');
assert.equal(trans.mode, 'readonly');
trans.onabort = function() {
done();
};
trans.abort();
});
});
suite('#open', function() {
suite('on version change', function() {
suite('#setupDefaults', function() {
var accountStore;
var calendarStore;
var storeLoads;
setup(function() {
storeLoads = {};
});
setup(function(done) {
accountStore = storeFactory.get('Account');
calendarStore = storeFactory.get('Calendar');
subject.load(done);
});
['Calendar', 'Account'].forEach(function(storeName) {
setup(function(done) {
var store = storeFactory.get(storeName);
var humanName = storeName.toLowerCase() + 's';
store.all(function(err, all) {
storeLoads[humanName] = all;
done();
});
});
});
test('default account', function() {
var list = Object.keys(storeLoads.accounts);
assert.lengthOf(list, 1);
var item = storeLoads.accounts[list[0]];
assert.ok(item);
assert.equal(item.providerType, 'Local', 'provider');
assert.equal(item.preset, 'local', 'preset');
});
test('default calendar', function() {
var list = Object.keys(storeLoads.calendars);
assert.lengthOf(list, 1);
var item = storeLoads.calendars[list[0]];
assert.ok(item);
assert.equal(item.remote.name, 'Offline calendar');
var account = storeLoads.accounts[item.accountId];
assert.ok(account, 'has account');
assert.equal(account.providerType, 'Local');
});
});
test('creation of stores', function(done) {
var finishedOpen = false;
assert.ok(!subject.connection, 'connection should be closed');
subject.on('open', function onopen() {
subject.off('open', onopen);
if (!finishedOpen) {
done(new Error(
'fired callback/event out of order ' +
'callback should fire then events'
));
} else {
done(function() {
// check that each store now exists
var stores = subject.connection.objectStoreNames;
var actualStore;
for (actualStore in subject.store) {
assert.ok(
(stores.contains(actualStore)),
actualStore + ' was not created'
);
}
});
subject.close();
}
});
subject.open(function() {
assert.ok(subject.connection, 'has connection');
assert.ok(subject.isOpen, 'is open');
assert.ok(subject.version, 'has version');
assert.equal(subject.oldVersion, 0, 'upgraded from 0');
assert.isTrue(subject.hasUpgraded, 'has upgraded');
assert.equal(subject.connection.name, subject.name);
finishedOpen = true;
});
});
suite('Bug 887698', function() {
/**
* @type {Calendar.Store}
*/
var busytimeStore, calendarStore, eventStore;
/**
* Keys for test events.
* @const {number}
*/
var EVENT_ONE_ID, EVENT_TWO_ID, EVENT_THREE_ID;
/**
* Keys for test busytimes.
* @const {number}
*/
var BUSYTIME_ONE_ID, BUSYTIME_TWO_ID, BUSYTIME_THREE_ID;
/**
* The version immediately before we fix the corrupt calendarIds.
* @const {number}
*/
var OLD_VERSION;
var LOCAL_CALENDAR;
var NEW_VERSION;
setup(function(done) {
// 13 is the version for v1.0.1
OLD_VERSION = 13;
// 15 is the version for v1.1
NEW_VERSION = 15;
LOCAL_CALENDAR = 'local-first';
// These need their calendarId fixed.
EVENT_ONE_ID = 'evt1';
BUSYTIME_ONE_ID = 'bt1';
// These are fine as is.
EVENT_TWO_ID = 'evt2';
BUSYTIME_TWO_ID = 'bt2';
// These need to be deleted.
EVENT_THREE_ID = 'evt3';
BUSYTIME_THREE_ID = 'bt3';
busytimeStore = storeFactory.get('Busytime');
calendarStore = storeFactory.get('Calendar');
eventStore = storeFactory.get('Event');
subject.open(OLD_VERSION, function() {
var trans = null;
calendarStore.persist(Factory('calendar',
{ _id: LOCAL_CALENDAR }), trans);
[
Factory('event', {
calendarId: LOCAL_CALENDAR,
_id: EVENT_ONE_ID
}),
Factory('event', {
calendarId: 2,
_id: EVENT_TWO_ID
}),
Factory('event', {
calendarId: '3',
_id: EVENT_THREE_ID
})
].forEach(function(obj) {
eventStore.persist(obj, trans);
});
[
Factory('busytime', {
eventId: EVENT_ONE_ID,
calendarId: LOCAL_CALENDAR,
_id: BUSYTIME_ONE_ID
}),
Factory('busytime', {
eventId: EVENT_TWO_ID,
calendarId: 2,
_id: BUSYTIME_TWO_ID
}),
Factory('busytime', {
eventId: EVENT_THREE_ID,
calendarId: '3',
_id: BUSYTIME_THREE_ID
})
].forEach(function(obj) {
busytimeStore.persist(obj, trans);
});
trans = subject.transaction(
['busytimes', 'calendars', 'events'],
'readwrite'
);
trans.oncomplete = function() {
subject.close();
done();
};
});
});
setup(function(done) {
subject.open(NEW_VERSION, done);
});
teardown(function() {
if (subject.isOpen) {
subject.close();
}
});
test('should turn event str calendarId into int', function(done) {
var trans = subject.transaction(['events'], 'readwrite');
var store = trans.objectStore('events');
store.get(EVENT_ONE_ID).onsuccess = function(evt) {
assert.strictEqual(evt.target.result.calendarId, LOCAL_CALENDAR);
done();
};
});
test('should not modify event int calendarId', function(done) {
var trans = subject.transaction(['events'], 'readwrite');
var store = trans.objectStore('events');
store.get(EVENT_TWO_ID).onsuccess = function(evt) {
assert.strictEqual(evt.target.result.calendarId, 2);
done();
};
});
test('should delete event if its calendar died', function(done) {
var trans = subject.transaction(['events'], 'readwrite');
var store = trans.objectStore('events');
store.get(EVENT_THREE_ID).onsuccess = function(evt) {
assert.strictEqual(evt.target.result, undefined);
done();
};
});
test('should turn busytime str calendarId into int', function(done) {
var trans = subject.transaction(['busytimes'], 'readwrite');
var store = trans.objectStore('busytimes');
store.get(BUSYTIME_ONE_ID).onsuccess = function(evt) {
assert.strictEqual(evt.target.result.calendarId, LOCAL_CALENDAR);
done();
};
});
test('should not modify busytime int calendarId', function(done) {
var trans = subject.transaction(['busytimes'], 'readwrite');
var store = trans.objectStore('busytimes');
store.get(BUSYTIME_TWO_ID).onsuccess = function(evt) {
assert.strictEqual(evt.target.result.calendarId, 2);
done();
};
});
test('should delete busytime if its calendar died', function(done) {
var trans = subject.transaction(['busytimes'], 'readwrite');
var store = trans.objectStore('busytimes');
store.get(BUSYTIME_THREE_ID).onsuccess = function(evt) {
assert.strictEqual(evt.target.result, undefined);
done();
};
});
});
suite('Bug 851003', function() {
/*jshint -W027 */
/** bug 912087: renable once tests pass consistently */
return;
/**
* @type {Calendar.Store}
*/
var busytimeStore, calendarStore, eventStore;
/**
* Keys for test events.
* @const {number}
*/
var EVENT_ONE_ID, EVENT_TWO_ID, EVENT_THREE_ID;
/**
* Keys for test busytimes.
* @const {number}
*/
var BUSYTIME_ONE_ID, BUSYTIME_TWO_ID, BUSYTIME_THREE_ID;
/**
* The version immediately before we fix the corrupt calendarIds.
* @const {number}
*/
var OLD_VERSION;
setup(function(done) {
OLD_VERSION = 14;
// These need their calendarId fixed.
EVENT_ONE_ID = 'evt1';
BUSYTIME_ONE_ID = 'bt1';
// These are fine as is.
EVENT_TWO_ID = 'evt2';
BUSYTIME_TWO_ID = 'bt2';
// These need to be deleted.
EVENT_THREE_ID = 'evt3';
BUSYTIME_THREE_ID = 'bt3';
busytimeStore = storeFactory.get('Busytime');
calendarStore = storeFactory.get('Calendar');
eventStore = storeFactory.get('Event');
subject.open(OLD_VERSION, function() {
var trans = null;
calendarStore.persist(Factory('calendar', { _id: 1 }), trans);
[
Factory('event', {
calendarId: '1',
_id: EVENT_ONE_ID
}),
Factory('event', {
calendarId: 2,
_id: EVENT_TWO_ID
}),
Factory('event', {
calendarId: '3',
_id: EVENT_THREE_ID
})
].forEach(function(obj) {
eventStore.persist(obj, trans);
});
[
Factory('busytime', {
eventId: EVENT_ONE_ID,
calendarId: '1',
_id: BUSYTIME_ONE_ID
}),
Factory('busytime', {
eventId: EVENT_TWO_ID,
calendarId: 2,
_id: BUSYTIME_TWO_ID
}),
Factory('busytime', {
eventId: EVENT_THREE_ID,
calendarId: '3',
_id: BUSYTIME_THREE_ID
})
].forEach(function(obj) {
busytimeStore.persist(obj, trans);
});
trans = subject.transaction(
['busytimes', 'calendars', 'events'],
'readwrite'
);
trans.oncomplete = function() {
subject.close();
done();
};
});
});
teardown(function() {
if (subject.isOpen) {
subject.close();
}
});
test('should turn event str calendarId into int', function(done) {
subject.open(OLD_VERSION + 1, function() {
var trans = subject.transaction(['events'], 'readwrite');
var store = trans.objectStore('events');
store.get(EVENT_ONE_ID).onsuccess = function(evt) {
assert.strictEqual(evt.target.result.calendarId, 1);
done();
};
});
});
test('should not modify event int calendarId', function(done) {
subject.open(OLD_VERSION + 1, function() {
var trans = subject.transaction(['events'], 'readwrite');
var store = trans.objectStore('events');
store.get(EVENT_TWO_ID).onsuccess = function(evt) {
assert.strictEqual(evt.target.result.calendarId, 2);
done();
};
});
});
test('should delete event if its calendar died', function(done) {
subject.open(OLD_VERSION + 1, function() {
var trans = subject.transaction(['events'], 'readwrite');
var store = trans.objectStore('events');
store.get(EVENT_THREE_ID).onsuccess = function(evt) {
assert.strictEqual(evt.target.result, undefined);
done();
};
});
});
test('should turn busytime str calendarId into int', function(done) {
subject.open(OLD_VERSION + 1, function() {
var trans = subject.transaction(['busytimes'], 'readwrite');
var store = trans.objectStore('busytimes');
store.get(BUSYTIME_ONE_ID).onsuccess = function(evt) {
assert.strictEqual(evt.target.result.calendarId, 1);
done();
};
});
});
test('should not modify busytime int calendarId', function(done) {
subject.open(OLD_VERSION + 1, function() {
var trans = subject.transaction(['busytimes'], 'readwrite');
var store = trans.objectStore('busytimes');
store.get(BUSYTIME_TWO_ID).onsuccess = function(evt) {
assert.strictEqual(evt.target.result.calendarId, 2);
done();
};
});
});
test('should delete busytime if its calendar died', function(done) {
subject.open(OLD_VERSION + 1, function() {
var trans = subject.transaction(['busytimes'], 'readwrite');
var store = trans.objectStore('busytimes');
store.get(BUSYTIME_THREE_ID).onsuccess = function(evt) {
assert.strictEqual(evt.target.result, undefined);
done();
};
});
});
});
});
suite('after version change', function() {
teardown(function() {
subject.close();
});
setup(function(done) {
// make sure db is open
subject.open(function() {
done();
});
});
test('open', function(done) {
// close it
subject.close();
subject = new Db(subject.name);
subject.open(function() {
done();
});
});
});
});
});
});

View file

@ -0,0 +1,237 @@
define(function(require) {
'use strict';
var Calc = require('common/calc');
var EventMutations = require('event_mutations');
var Factory = require('test/support/factory');
var core = require('core');
suite('EventMutations', function() {
var subject;
var db;
var controller;
var eventStore;
var busytimeStore;
var alarmStore;
var componentStore;
var shouldDisplay;
var storeFactory;
setup(function(done) {
subject = EventMutations;
db = core.db;
controller = core.timeController;
shouldDisplay = controller._shouldDisplayBusytime;
controller._shouldDisplayBusytime = function() {
return true;
};
storeFactory = core.storeFactory;
eventStore = storeFactory.get('Event');
busytimeStore = storeFactory.get('Busytime');
alarmStore = storeFactory.get('Alarm');
componentStore = storeFactory.get('IcalComponent');
db.open(done);
});
teardown(function(done) {
controller._shouldDisplayBusytime = shouldDisplay;
testSupport.calendar.clearStore(
db,
[
'events',
'busytimes',
'alarms',
'icalComponents'
],
function() {
db.close();
done();
}
);
});
suite('#create', function() {
var event;
var component;
var mutation;
setup(function(done) {
event = Factory('event');
component = Factory('icalComponent', {
eventId: event._id
});
// Set the event to start and end in the past
event.remote.start = Calc.dateToTransport(
new Date(Date.now() - 2 * 60 * 60 * 1000)
);
// Ending one hour in the future
event.remote.end = Calc.dateToTransport(
new Date(Date.now() - 1 * 60 * 60 * 1000)
);
mutation = subject.create({
event: event,
icalComponent: component
});
mutation.commit(done);
});
test('event', function(done) {
eventStore.get(event._id, function(err, value) {
done(function() {
assert.ok(value);
});
});
});
test('busytime', function(done) {
var busytime = mutation.busytime;
assert.ok(busytime._id, 'has _id');
busytimeStore.get(busytime._id, function(err, value) {
done(function() {
assert.hasProperties(value, {
eventId: event._id,
calendarId: event.calendarId,
start: event.remote.start,
end: event.remote.end
});
});
});
});
test('alarms', function(done) {
var expectedAlarms = [];
var busyId = mutation.busytime._id;
alarmStore.findAllByBusytimeId(busyId, function(err, values) {
done(function() {
assert.equal(values.length, expectedAlarms.length);
});
});
});
test('icalComponent', function(done) {
componentStore.get(event._id, function(err, value) {
done(function() {
assert.deepEqual(value, component);
});
});
});
});
suite('#update', function() {
var event;
var component;
setup(function(done) {
event = Factory('event');
component = Factory('icalComponent', {
eventId: event._id
});
var create = subject.create({
event: event,
icalComponent: component
});
create.commit(done);
});
var mutation;
setup(function(done) {
event.remote.foo = true;
// Starting one hour in the past
event.remote.start = Calc.dateToTransport(
new Date(Date.now() - 1 * 60 * 60 * 1000)
);
// Ending one hour in the future
event.remote.end = Calc.dateToTransport(
new Date(Date.now() + 1 * 60 * 60 * 1000)
);
var futureTrigger = Date.now() - event.remote.start.utc + 5000;
event.remote.alarms = [
{action: 'DISPLAY', trigger: 60},
{action: 'DISPLAY', trigger: 300},
// Create an alarm in the future
{action: 'DISPLAY', trigger: futureTrigger}
];
component.data = { changed: true };
mutation = subject.update({
event: event,
icalComponent: component
});
mutation.commit(done);
});
test('event', function(done) {
eventStore.get(event._id, function(err, value) {
done(function() {
assert.hasProperties(
value.remote,
{
start: event.remote.start,
end: event.remote.end
}
);
});
});
});
test('busytime', function(done) {
busytimeStore.get(mutation.busytime._id, function(err, value) {
done(function() {
assert.hasProperties(value, {
eventId: event._id,
start: event.remote.start,
end: event.remote.end
});
});
});
});
test('alarms', function(done) {
var expectedAlarms = event.remote.alarms;
var busyId = mutation.busytime._id;
alarmStore.findAllByBusytimeId(busyId, function(err, values) {
done(function() {
assert.equal(values.length, expectedAlarms.length);
for (var i = 0; i < expectedAlarms.length; i++) {
assert.equal(
event.remote.start.utc + expectedAlarms[i].trigger * 1000,
values[i].trigger.utc
);
}
});
});
});
test('icalComponent', function(done) {
componentStore.get(event._id, function(err, value) {
done(function() {
assert.deepEqual(value, component);
});
});
});
});
});
});

View file

@ -0,0 +1,42 @@
BEGIN:VCALENDAR
VERSION:2.0
PRODID:Zimbra-Calendar-Provider
BEGIN:VTIMEZONE
TZID:America/Los_Angeles
BEGIN:STANDARD
DTSTART:19710101T020000
TZOFFSETTO:-0800
TZOFFSETFROM:-0700
RRULE:FREQ=YEARLY;WKST=MO;INTERVAL=1;BYMONTH=11;BYDAY=1SU
TZNAME:PST
END:STANDARD
BEGIN:DAYLIGHT
DTSTART:19710101T020000
TZOFFSETTO:-0700
TZOFFSETFROM:-0800
RRULE:FREQ=YEARLY;WKST=MO;INTERVAL=1;BYMONTH=3;BYDAY=2SU
TZNAME:PDT
END:DAYLIGHT
END:VTIMEZONE
BEGIN:VEVENT
UID:623c13c0-6c2b-45d6-a12b-c33ad61c4868
DESCRIPTION:IAM FOO
RRULE:FREQ=DAILY;BYDAY=TU
SUMMARY:Crazy Event Thingy!
LOCATION:PLACE
ORGANIZER;CN=James Lal:mailto:jlal@mozilla.com
DTSTART;TZID=America/Los_Angeles:20181002T100000
DTEND;TZID=America/Los_Angeles:20181002T103000
STATUS:CONFIRMED
CLASS:PUBLIC
TRANSP:OPAQUE
LAST-MODIFIED:20180912T171506Z
DTSTAMP:20180912T171506Z
SEQUENCE:0
BEGIN:VALARM
ACTION:DISPLAY
TRIGGER;RELATED=START:-PT5M
DESCRIPTION:Reminder
END:VALARM
END:VEVENT
END:VCALENDAR

View file

@ -0,0 +1,41 @@
BEGIN:VCALENDAR
VERSION:2.0
PRODID:Zimbra-Calendar-Provider
BEGIN:VTIMEZONE
TZID:America/Los_Angeles
BEGIN:STANDARD
DTSTART:19710101T020000
TZOFFSETTO:-0800
TZOFFSETFROM:-0700
RRULE:FREQ=YEARLY;WKST=MO;INTERVAL=1;BYMONTH=11;BYDAY=1SU
TZNAME:PST
END:STANDARD
BEGIN:DAYLIGHT
DTSTART:19710101T020000
TZOFFSETTO:-0700
TZOFFSETFROM:-0800
TZNAME:PDT
END:DAYLIGHT
END:VTIMEZONE
BEGIN:VEVENT
UID:623c13c0-6c2b-45d6-a12b-c33ad61c4868
DESCRIPTION:IAM FOO
RRULE:FREQ=MINUTELY;INTERVAL=45;UNTIL=20121005T100000
SUMMARY:Crazy Event Thingy!
LOCATION:PLACE
ORGANIZER;CN=James Lal:mailto:jlal@mozilla.com
DTSTART;TZID=America/Los_Angeles:20121002T100000
DTEND;TZID=America/Los_Angeles:20121002T103000
STATUS:CONFIRMED
CLASS:PUBLIC
TRANSP:OPAQUE
LAST-MODIFIED:20120912T171506Z
DTSTAMP:20120912T171506Z
SEQUENCE:0
BEGIN:VALARM
ACTION:DISPLAY
TRIGGER;RELATED=START:-PT5M
DESCRIPTION:Reminder
END:VALARM
END:VEVENT
END:VCALENDAR

View file

@ -0,0 +1,94 @@
BEGIN:VCALENDAR
VERSION:2.0
PRODID:Zimbra-Calendar-Provider
BEGIN:VTIMEZONE
TZID:America/Los_Angeles
BEGIN:STANDARD
DTSTART:19710101T020000
TZOFFSETTO:-0800
TZOFFSETFROM:-0700
RRULE:FREQ=YEARLY;WKST=MO;INTERVAL=1;BYMONTH=11;BYDAY=1SU
TZNAME:PST
END:STANDARD
BEGIN:DAYLIGHT
DTSTART:19710101T020000
TZOFFSETTO:-0700
TZOFFSETFROM:-0800
RRULE:FREQ=YEARLY;WKST=MO;INTERVAL=1;BYMONTH=3;BYDAY=2SU
TZNAME:PDT
END:DAYLIGHT
END:VTIMEZONE
BEGIN:VEVENT
UID:623c13c0-6c2b-45d6-a12b-c33ad61c4868
DESCRIPTION:IAM FOO
RRULE:FREQ=MONTHLY;INTERVAL=1;BYDAY=1TU
SUMMARY:Crazy Event Thingy!
ATTENDEE;CUTYPE=INDIVIDUAL;ROLE=REQ-PARTICIPANT;PARTSTAT=ACCEPTED;CN=Sahaja
Lal;X-NUM-GUESTS=0:mailto:calmozilla1@gmail.com
ATTENDEE;CUTYPE=INDIVIDUAL;ROLE=REQ-PARTICIPANT;PARTSTAT=NEEDS-ACTION;CN=ja
mes@lightsofapollo.com;X-NUM-GUESTS=0:mailto:james@lightsofapollo.com
ATTENDEE;CUTYPE=INDIVIDUAL;ROLE=REQ-PARTICIPANT;PARTSTAT=NEEDS-ACTION;CN=ia
m.revelation@gmail.com;X-NUM-GUESTS=0:mailto:iam.revelation@gmail.com
LOCATION:PLACE
ORGANIZER;CN=James Lal:mailto:jlal@mozilla.com
DTSTART;TZID=America/Los_Angeles:20121002T100000
DTEND;TZID=America/Los_Angeles:20121002T103000
STATUS:CONFIRMED
CLASS:PUBLIC
TRANSP:OPAQUE
LAST-MODIFIED:20120912T171506Z
DTSTAMP:20120912T171506Z
SEQUENCE:0
RDATE;TZID=America/Los_Angeles:20121110T100000
RDATE;TZID=America/Los_Angeles:20121105T100000
EXDATE;TZID=America/Los_Angeles:20130402T100000
EXDATE;TZID=America/Los_Angeles:20121204T100000
EXDATE;TZID=America/Los_Angeles:20130205T100000
BEGIN:VALARM
ACTION:DISPLAY
TRIGGER;RELATED=START:-PT5M
DESCRIPTION:Reminder
END:VALARM
END:VEVENT
BEGIN:VEVENT
UID:623c13c0-6c2b-45d6-a12b-c33ad61c4868
SUMMARY:Crazy Event Thingy!
DESCRIPTION:I HAZ CHANGED!
ORGANIZER;CN=James Lal:mailto:jlal@mozilla.com
DTSTART;TZID=America/Los_Angeles:20121002T150000
DTEND;TZID=America/Los_Angeles:20121002T153000
STATUS:CONFIRMED
CLASS:PUBLIC
TRANSP:OPAQUE
RECURRENCE-ID;TZID=America/Los_Angeles:20121002T100000
LAST-MODIFIED:20120912T171540Z
DTSTAMP:20120912T171540Z
SEQUENCE:1
BEGIN:VALARM
ACTION:DISPLAY
TRIGGER;RELATED=START:-PT5M
DESCRIPTION:Reminder
END:VALARM
END:VEVENT
BEGIN:VEVENT
UID:623c13c0-6c2b-45d6-a12b-c33ad61c4868
SUMMARY:Crazy Event Thingy!
ORGANIZER;CN=James Lal:mailto:jlal@mozilla.com
DTSTART;TZID=America/Los_Angeles:20121106T200000
DTEND;TZID=America/Los_Angeles:20121106T203000
STATUS:CONFIRMED
CLASS:PUBLIC
TRANSP:OPAQUE
RECURRENCE-ID;TZID=America/Los_Angeles:20121106T100000
LAST-MODIFIED:20120912T171820Z
DTSTAMP:20120912T171820Z
SEQUENCE:1
BEGIN:VALARM
ACTION:DISPLAY
TRIGGER;RELATED=START:-PT5M
DESCRIPTION:Reminder
END:VALARM
END:VEVENT
END:VCALENDAR

View file

@ -0,0 +1,116 @@
BEGIN:VCALENDAR
PRODID:-//Google Inc//Google Calendar 70.9054//EN
VERSION:2.0
CALSCALE:GREGORIAN
X-WR-CALNAME:kevingrandon@yahoo.com
X-WR-TIMEZONE:America/Los_Angeles
BEGIN:VTIMEZONE
TZID:America/Los_Angeles
X-LIC-LOCATION:America/Los_Angeles
BEGIN:DAYLIGHT
TZOFFSETFROM:-0800
TZOFFSETTO:-0700
TZNAME:PDT
DTSTART:19700308T020000
RRULE:FREQ=YEARLY;BYMONTH=3;BYDAY=2SU
END:DAYLIGHT
BEGIN:STANDARD
TZOFFSETFROM:-0700
TZOFFSETTO:-0800
TZNAME:PST
DTSTART:19701101T020000
RRULE:FREQ=YEARLY;BYMONTH=11;BYDAY=1SU
END:STANDARD
END:VTIMEZONE
BEGIN:VEVENT
DTSTART;TZID=America/Los_Angeles:20130311T070000
DTEND;TZID=America/Los_Angeles:20130311T093000
RRULE:FREQ=WEEKLY;COUNT=10;BYDAY=MO,WE,FR
DTSTAMP:20130315T195941Z
UID:1ptfstkbsqn5lfrrpc3igiun98@google.com
CREATED:20130315T195941Z
DESCRIPTION:
LAST-MODIFIED:20130315T195941Z
LOCATION:
SEQUENCE:0
STATUS:CONFIRMED
SUMMARY:Hello Event!
TRANSP:OPAQUE
BEGIN:VALARM
ACTION:DISPLAY
DESCRIPTION:This is an event reminder
TRIGGER;VALUE=DATE-TIME:20120620T233000Z
END:VALARM
BEGIN:VALARM
ACTION:EMAIL
DESCRIPTION:This is an event reminder
SUMMARY:Alarm notification
ATTENDEE:mailto:kgrandon@mozilla.com
TRIGGER:-P0DT0H10M0S
END:VALARM
BEGIN:VALARM
ACTION:DISPLAY
DESCRIPTION:This is an event reminder
TRIGGER:-P0DT0H30M0S
END:VALARM
END:VEVENT
BEGIN:VEVENT
DTSTART;TZID=America/Los_Angeles:20130313T100000
DTEND;TZID=America/Los_Angeles:20130313T123000
DTSTAMP:20130315T195946Z
UID:1ptfstkbsqn5lfrrpc3igiun98@google.com
RECURRENCE-ID;TZID=America/Los_Angeles:20130313T070000
CREATED:20130315T195941Z
DESCRIPTION:
LAST-MODIFIED:20130315T195946Z
LOCATION:
SEQUENCE:1
STATUS:CONFIRMED
SUMMARY:Hello Event!
TRANSP:OPAQUE
BEGIN:VALARM
ACTION:EMAIL
DESCRIPTION:This is an event reminder
SUMMARY:Alarm notification
ATTENDEE:mailto:kgrandon@mozilla.com
TRIGGER:-P0DT0H10M0S
END:VALARM
BEGIN:VALARM
ACTION:DISPLAY
DESCRIPTION:This is an event reminder
TRIGGER:-P0DT0H30M0S
END:VALARM
BEGIN:VALARM
ACTION:DISPLAY
DESCRIPTION:This is an event reminder
TRIGGER;VALUE=DATE-TIME:20120620T233000Z
END:VALARM
END:VEVENT
BEGIN:VEVENT
DTSTART;TZID=America/Los_Angeles:20130313T100000
DTEND;TZID=America/Los_Angeles:20130313T123000
DTSTAMP:20130315T195946Z
UID:1ptfstkbsqn5lfrrpc3igiun98@google.com
RECURRENCE-ID;TZID=America/Los_Angeles:20130313T070000
CREATED:20130315T195941Z
DESCRIPTION:
LAST-MODIFIED:20130315T195946Z
LOCATION:
SEQUENCE:1
STATUS:CONFIRMED
SUMMARY:Hello Event!
TRANSP:OPAQUE
BEGIN:VALARM
ACTION:EMAIL
DESCRIPTION:This is an event reminder
SUMMARY:Alarm notification
ATTENDEE:mailto:kgrandon@mozilla.com
TRIGGER:-P0DT0H10M0S
END:VALARM
BEGIN:VALARM
ACTION:DISPLAY
DESCRIPTION:This is an event reminder
TRIGGER:-P0DT0H30M0S
END:VALARM
END:VEVENT
END:VCALENDAR

View file

@ -0,0 +1,58 @@
BEGIN:VCALENDAR
PRODID:-//Google Inc//Google Calendar 70.9054//EN
VERSION:2.0
CALSCALE:GREGORIAN
X-WR-CALNAME:calmozilla1@gmail.com
X-WR-TIMEZONE:America/Los_Angeles
BEGIN:VTIMEZONE
TZID:America/Los_Angeles
X-LIC-LOCATION:America/Los_Angeles
BEGIN:DAYLIGHT
TZOFFSETFROM:-0800
TZOFFSETTO:-0700
TZNAME:PDT
DTSTART:19700308T020000
RRULE:FREQ=YEARLY;BYMONTH=3;BYDAY=2SU
END:DAYLIGHT
BEGIN:STANDARD
TZOFFSETFROM:-0700
TZOFFSETTO:-0800
TZNAME:PST
DTSTART:19701101T020000
RRULE:FREQ=YEARLY;BYMONTH=11;BYDAY=1SU
END:STANDARD
END:VTIMEZONE
BEGIN:VEVENT
DTSTART;TZID=America/Los_Angeles:20120630T060000
DTEND;TZID=America/Los_Angeles:20120630T070000
DTSTAMP:20120724T212411Z
UID:dn4vrfmfn5p05roahsopg57h48@google.com
CREATED:20120724T212411Z
DESCRIPTION:ICAL Description
LAST-MODIFIED:20120724T212411Z
LOCATION:My Loc
SEQUENCE:0
STATUS:CONFIRMED
SUMMARY:Summary Name
TRANSP:OPAQUE
BEGIN:VALARM
ACTION:EMAIL
DESCRIPTION:ICAL description
SUMMARY:Alarm notification
ATTENDEE:mailto:calmozilla1@gmail.com
TRIGGER:-P0DT0H30M0S
END:VALARM
BEGIN:VALARM
ACTION:DISPLAY
DESCRIPTION:This is an event reminder
TRIGGER:-P0DT0H30M0S
END:VALARM
BEGIN:VALARM
ACTION:DISPLAY
DESCRIPTION:This is an event reminder
TRIGGER;VALUE=DATE-TIME:20130312T233000Z
END:VALARM
END:VEVENT
END:VCALENDAR

View file

@ -0,0 +1,45 @@
addEventListener('message', function prepare(e) {
'use strict';
requirejs(['worker/thread'], (Thread) => {
var thread = new Thread(this);
this.console = new thread.console('W1');
thread.addRole('test');
thread.roles.test.on('relay', function() {
var args = Array.prototype.slice.call(arguments);
var callback = args.pop();
callback.apply(this, args);
});
thread.roles.test.on('error', function(callback) {
var err;
try {
throw new Error('message');
} catch (e) {
err = e;
}
callback(err);
});
thread.roles.test.on('stream', function(data, stream, callback) {
var args = Array.prototype.slice.call(arguments);
args.pop();
args.pop();
setTimeout(function() {
callback(args);
}, 0);
stream.emit('data', 1);
stream.emit('data', 2);
stream.emit('error', 'err');
});
thread.send('ready');
});
});

View file

@ -0,0 +1,52 @@
BEGIN:VCALENDAR
PRODID:-//Google Inc//Google Calendar 70.9054//EN
VERSION:2.0
CALSCALE:GREGORIAN
X-WR-CALNAME:calmozilla1@gmail.com
X-WR-TIMEZONE:America/Los_Angeles
BEGIN:VTIMEZONE
TZID:America/Los_Angeles
X-LIC-LOCATION:America/Los_Angeles
BEGIN:DAYLIGHT
TZOFFSETFROM:-0800
TZOFFSETTO:-0700
TZNAME:PDT
DTSTART:19700308T020000
RRULE:FREQ=YEARLY;BYMONTH=3;BYDAY=2SU
END:DAYLIGHT
BEGIN:STANDARD
TZOFFSETFROM:-0700
TZOFFSETTO:-0800
TZNAME:PST
DTSTART:19701101T020000
RRULE:FREQ=YEARLY;BYMONTH=11;BYDAY=1SU
END:STANDARD
END:VTIMEZONE
BEGIN:VEVENT
DTSTART;TZID=America/Los_Angeles:20120630T060000
DTEND;TZID=America/Los_Angeles:20120630T070000
DTSTAMP:20120724T212411Z
UID:dn4vrfmfn5p05roahsopg57h48@google.com
CREATED:20120724T212411Z
DESCRIPTION:ICAL Description
LAST-MODIFIED:20120724T212411Z
LOCATION:My Loc
SEQUENCE:0
STATUS:CONFIRMED
SUMMARY:Summary Name
TRANSP:OPAQUE
BEGIN:VALARM
ACTION:EMAIL
DESCRIPTION:ICAL description
SUMMARY:Alarm notification
ATTENDEE:mailto:calmozilla1@gmail.com
TRIGGER:-P0DT0H30M0S
END:VALARM
BEGIN:VALARM
ACTION:DISPLAY
DESCRIPTION:This is an event reminder
TRIGGER:-P0DT0H30M0S
END:VALARM
END:VEVENT
END:VCALENDAR

View file

@ -0,0 +1,34 @@
<div class="template" id="month-templates" style="display: none;">
<section class="busy">
<span class="busy-">&nbsp;</span>
</section>
<section class="currentMonth">
<span class="month"></span>
<span class="year"></span>
</section>
<section class="monthDaysHeader">
<header id="month-days" role="row">
<ol role="row">
</ol>
</header>
</section>
<section class="monthSection">
<section class="month">
</section>
</section>
<section class="sectionRow">
<ol class="row"></ol>
</section>
<section class="day">
<li class="day">
<span class="date"></span>
<div class="busy-indicator"></div>
</li>
</section>
</div>

View file

@ -0,0 +1,603 @@
define(function(require) {
'use strict';
var Factory = require('test/support/factory');
var IntervalTree = require('interval_tree');
var Timespan = require('common/timespan');
var binsearch = require('common/binsearch');
var compare = require('common/compare');
suite('interval_tree', function() {
var subject;
var items;
var list;
var expectedRange;
var span;
var Node;
function factory(id, start, end) {
return {
_id: id,
_startDateMS: start,
_endDateMS: end
};
}
suiteSetup(function() {
Node = IntervalTree.Node;
});
setup(function() {
IntervalTree.overlapTime = 0;
// we use this range as a baseline
// through the less complicated tests
expectedRange = {
start: 1200,
end: 1300
};
span = new Timespan(expectedRange.start, expectedRange.end);
// setup the basic list of items
items = {};
items.before = factory(1, 100, 800);
items.overlapBefore = factory(4, 1050, 1400);
items.middle = factory(3, 1250, 1280);
items.after = factory(2, 1500, 2000);
// should be in order of start times
list = [
items.before,
items.overlapBefore,
items.middle,
items.after
];
subject = new IntervalTree(list);
});
test('integration', function() {
var span = new Timespan(
100,
800
);
var result = subject.query(span);
assert.deepEqual(result, [items.before]);
subject.remove(items.before);
result = subject.query(span);
assert.deepEqual(result, []);
var added = factory(30, 400, 1200);
subject.add(added);
subject.add(factory(31, 1100, 1200));
assert.isFalse(subject.synced);
span.end = 1100;
assert.deepEqual(
subject.query(span),
[
added,
items.overlapBefore
]
);
});
test('initialization', function() {
assert.deepEqual(subject.items, list);
assert.ok(!subject.synced);
assert.ok(!subject.rootNode);
assert.deepEqual(subject.byId, {});
});
test('init without list', function() {
var subject = new IntervalTree();
assert.deepEqual(subject.items, []);
});
test('#build', function() {
subject.build();
var node = subject.rootNode;
assert.ok(subject.synced);
assert.ok(node);
subject.build();
assert.equal(
node, subject.rootNode,
'should not rebuild tree when in sync'
);
});
suite('#add', function() {
setup(function() {
// start from clean tree
subject.items = [];
subject.synced = true;
subject.add(items.after);
});
test('re-add item with same _id', function() {
var id = items.after._id;
var obj = { _id: id };
subject.add(obj);
assert.deepEqual(
subject.items,
[items.after]
);
});
test('first add', function() {
assert.deepEqual(
subject.items,
[items.after]
);
assert.isFalse(subject.synced);
assert.equal(
subject.byId[items.after._id],
items.after,
'should add to byId cache'
);
});
test('multiple adds', function() {
subject.add(items.overlapBefore);
subject.add(items.before);
assert.isFalse(subject.synced);
assert.deepEqual(
subject.items,
[
items.before,
items.overlapBefore,
items.after
]
);
});
test('yahoo recurring allday', function() {
var allday = factory(555, 1000, 1000);
subject.add(allday);
assert.deepEqual(subject.items, [allday, items.after]);
});
});
suite('#removeFutureIntervals', function() {
var list;
setup(function() {
subject.items = [];
list = {};
});
function add(name, start, end) {
setup(function() {
list[name] = factory(name, start, end);
subject.add(list[name]);
});
}
add('before', 10, 100);
add('on', 50, 100);
add('just after', 51, 100);
add('after', 60, 100);
test('when items need to be removed', function() {
assert.isFalse(subject.synced, 'not synced');
subject.synced = true;
subject.removeFutureIntervals(50);
var expectedIds = [
'before',
'on'
];
assert.ok(!subject.byId['just after']);
assert.ok(!subject.byId.after);
var ids = subject.items.map(function(item) {
return item._id;
});
assert.deepEqual(ids, expectedIds);
assert.isFalse(subject.synced, 'should be synced');
});
test('when last item needs to be removed', function() {
subject.removeFutureIntervals(59);
var expectedIds = [
'before',
'on',
'just after'
];
var ids = subject.items.map(function(item) {
return item._id;
});
assert.deepEqual(ids, expectedIds);
});
});
suite('#removePastIntervals', function() {
var list;
setup(function() {
subject.items = [];
list = {};
});
function add(name, start, end) {
setup(function() {
list[name] = factory(name, start, end);
subject.add(list[name]);
});
}
add('overlap all', 1, 1000);
add('ends before', 2, 50);
add('ends on 1', 3, 150);
add('ends on 2', 4, 150);
add('ends on 3', 5, 150);
add('ends just after', 6, 151);
add('ends after', 7, 200);
test('when items need to be removed', function() {
assert.isFalse(subject.synced, 'not synced');
subject.synced = true;
subject.removePastIntervals(150);
var expectedIds = [
'overlap all',
'ends just after',
'ends after'
];
assert.ok(!subject.byId['ends on 1']);
assert.ok(!subject.byId['ends on 2']);
assert.ok(!subject.byId['ends on 3']);
var ids = subject.items.map(function(item) {
assert.ok(subject.byId[item._id], 'should have: ' + item._id);
return item._id;
});
assert.deepEqual(ids, expectedIds);
assert.isFalse(subject.synced, 'should be synced');
});
});
suite('#remove', function() {
test('when removing nonexisting item', function() {
subject.synced = true;
var result = subject.remove({ start: 100 });
assert.isFalse(result, 'should return false when item is not removed');
assert.isTrue(subject.synced, 'should not remark sync to false');
});
test('item that does not share start', function() {
subject.synced = true;
var result = subject.remove(items.middle);
assert.isTrue(result, 'remove should return true');
assert.isFalse(subject.synced);
assert.deepEqual(
subject.items,
[
items.before,
items.overlapBefore,
items.after
]
);
});
suite('items that share start time', function() {
var middle;
var addedBefore;
var addedAfter;
setup(function() {
middle = items.before;
subject.items = [];
subject.synced = false;
addedBefore = factory(10, middle[subject.START], middle[subject.END]);
addedAfter = factory(12, middle[subject.START], middle[subject.END]);
// its going to shuffle
// each item is going to displace
// the next pushing it further in
// the array.
subject.add(addedBefore);
subject.add(middle);
subject.add(addedAfter);
assert.deepEqual(
subject.items,
[
addedAfter,
middle,
addedBefore
]
);
});
test('remove last added item', function() {
subject.remove(addedAfter);
assert.deepEqual(
subject.items,
[
middle,
addedBefore
]
);
assert.ok(!subject.byId[addedAfter._id]);
});
test('remove first added item', function() {
subject.remove(addedBefore);
assert.deepEqual(
subject.items,
[
addedAfter,
middle
]
);
});
test('remove middle item', function() {
subject.remove(middle);
assert.deepEqual(
subject.items,
[
addedAfter,
addedBefore
]
);
});
});
});
suite('Node', function() {
setup(function() {
subject.build();
subject = subject.rootNode;
});
suite('node alignment', function() {
test('base alignment', function() {
var left = subject.left;
var right = subject.right;
assert.isTrue(
(left.median < subject.median),
'lower values should be on the left'
);
assert.isTrue(
(right.median > subject.median),
'higher values should be on the right'
);
});
});
test('#traverse', function() {
var results = [];
subject.traverse(span, function(item, node) {
results.push(item);
assert.instanceOf(node, IntervalTree.Node);
});
assert.deepEqual(
results,
[items.overlapBefore, items.middle]
);
});
suite('#query', function() {
var sublist;
var id;
setup(function() {
id = 0;
sublist = [];
});
function compareDateMS(aObj, bObj) {
var a = aObj._startDateMS;
var b = bObj._startDateMS;
return compare(a, b);
}
function orderedAdd(item, arr) {
var idx = binsearch.insert(
arr,
item,
compareDateMS
);
arr.splice(idx, 0, item);
}
function add(start, end) {
var item = factory(id++, start, end);
orderedAdd(item, sublist);
return item;
}
test('large dataset with gaps', function() {
var expected = [];
var i = 0;
// create some out of range in lower bounds
for (i = 0; i < 1021; i++) {
add(i, i + 200);
}
// create two really huge spans
for (i = 0; i < 75; i++) {
orderedAdd(add(i, 5500), expected);
}
for (i = 0; i < 1099; i++) {
orderedAdd(add(5201 + i, 6000 + i), expected);
}
orderedAdd(add(7000, 7070), expected);
orderedAdd(add(8000, 8100), expected);
orderedAdd(add(9000, 9001), expected);
// middle values
orderedAdd(add(10200, 10500), expected);
orderedAdd(add(10300, 10500), expected);
orderedAdd(add(10400, 10500), expected);
orderedAdd(add(10000, 10800), expected);
// yahoo recurring all day events have same start/end dates
orderedAdd(add(10000, 10000), expected);
// create some out of range in upper bounds
for (i = 0; i < 1021; i++) {
add(i + 2500, i + 2505);
}
subject = new IntervalTree(sublist);
var result = subject.query(new Timespan(
5201,
11750
));
// this deep equality assertion is more expensive
// then the entire process of building
// the tree and finding the results...
assert.deepEqual(
result,
expected
);
});
test('basic start range query', function() {
var range = new Timespan(
80,
900
);
assert.deepEqual(
subject.query(range),
[items.before]
);
});
test('basic end range query', function() {
var range = new Timespan(
1600,
1800
);
assert.deepEqual(
subject.query(range),
[items.after]
);
});
test('basic middle query', function() {
var range = new Timespan(
expectedRange.start,
expectedRange.end
);
var results = subject.query(range);
assert.deepEqual(
results,
[
items.overlapBefore,
items.middle
]
);
});
});
});
suite('#createIndex / #index', function() {
var one;
var two;
var three;
setup(function() {
one = Factory('busytime', { eventId: 'xxx' });
two = Factory('busytime', { eventId: 'xxx' });
three = Factory('busytime', { eventId: 'xxx' });
subject.createIndex('eventId');
subject.add(one);
subject.add(two);
subject.add(three);
});
test('add', function() {
assert.deepEqual(
subject.index('eventId', one.eventId),
[one, two, three]
);
});
test('remove middle', function() {
subject.remove(two);
assert.deepEqual(
subject.index('eventId', one.eventId),
[one, three]
);
});
test('remove start', function() {
subject.remove(one);
assert.deepEqual(
subject.index('eventId', one.eventId),
[two, three]
);
});
test('remove end', function() {
subject.remove(three);
assert.deepEqual(
subject.index('eventId', one.eventId),
[one, two]
);
});
test('remove all', function() {
subject.remove(one);
subject.remove(two);
subject.remove(three);
assert.ok(!subject.index('eventId', one.eventId));
});
});
});
});

View file

@ -0,0 +1,65 @@
define(function(require) {
'use strict';
var Account = require('models/account');
suite('Models.Account', function() {
var subject;
setup(function() {
subject = new Account({ user: 'foo', providerType: 'Local' });
});
test('initialization', function() {
assert.equal(subject.user, 'foo');
});
suite('fullUrl', function() {
test('get', function() {
subject.domain = 'http://google.com';
subject.entrypoint = '/foo';
assert.equal(subject.fullUrl, 'http://google.com/foo');
});
test('set', function() {
subject.fullUrl = 'http://google.com/foo/bar';
assert.equal(subject.domain, 'http://google.com');
assert.equal(subject.entrypoint, '/foo/bar');
});
});
suite('#toJSON', function() {
setup(function() {
subject._id = '1';
subject.entrypoint = 'url';
subject.domain = 'domain';
subject.password = 'pass';
subject.user = 'user';
subject.providerType = 'local';
subject.preset = 'google';
subject.calendarHome = '/foo/home';
subject.oauth = { code: 'xxx' };
});
test('output', function() {
var expected = {
_id: subject._id,
entrypoint: subject.entrypoint,
domain: subject.domain,
password: subject.password,
user: subject.user,
providerType: subject.providerType,
calendarHome: '/foo/home',
preset: 'google',
oauth: { code: 'xxx' },
error: undefined
};
assert.deepEqual(subject.toJSON(), expected);
});
});
});
});

View file

@ -0,0 +1,120 @@
define(function(require) {
'use strict';
var Calendar = require('models/calendar');
suite('Models.Calendar', function() {
var subject;
var remoteCalendar;
var CalendarModel;
suiteSetup(function() {
CalendarModel = Calendar;
});
setup(function() {
remoteCalendar = {
id: 'one',
syncToken: 'xxx'
};
subject = new Calendar({
name: 'foo',
remote: remoteCalendar
});
});
suite('initialization', function() {
test('when given a remote', function() {
var date = new Date();
subject = new CalendarModel({
remote: remoteCalendar,
lastEventSyncToken: '0',
lastEventSyncDate: date
});
assert.deepEqual(
subject.remote,
remoteCalendar
);
assert.equal(subject.lastEventSyncDate, date);
});
test('when given remote', function() {
subject = new CalendarModel({
remote: remoteCalendar
});
assert.deepEqual(subject.remote, remoteCalendar);
});
});
test('#updateRemote', function() {
remoteCalendar.id = 'foo';
subject.updateRemote(remoteCalendar);
assert.equal(subject.remote.id, 'foo');
});
suite('#eventSyncNeeded', function() {
test('when remote and local match', function() {
subject.remote.syncToken = 'foo';
subject.lastEventSyncToken = 'foo';
assert.isFalse(subject.eventSyncNeeded());
});
test('when remote and local differ', function() {
subject.remote.syncToken = 'foo';
subject.lastEventSyncToken = 'bar';
assert.isTrue(subject.eventSyncNeeded());
});
});
test('#toJSON', function() {
subject._id = '1';
var expected = {
error: undefined,
lastEventSyncToken: subject.lastEventSyncToken,
lastEventSyncDate: subject.lastEventSyncDate,
localDisplayed: subject.localDisplayed,
firstEventSyncDate: subject.firstEventSyncDate,
accountId: subject.accountId,
_id: subject._id,
remote: subject.remote
};
assert.deepEqual(subject.toJSON(), expected);
});
test('#name', function() {
subject.remote.name = 'foo';
assert.equal(subject.name, 'foo');
});
suite('#color', function() {
test('basic getter', function() {
subject.remote.color = '#ccc';
assert.equal(subject.color, '#ccc');
});
test('filter hex value', function() {
subject.remote.color = '#7BD148FF';
assert.equal(subject.color, '#7BD148');
});
});
test('#description', function() {
subject.remote.description = 'foo';
assert.equal(subject.description, 'foo');
});
});
});

View file

@ -0,0 +1,297 @@
define(function(require) {
'use strict';
var Calc = require('common/calc');
var Event = require('models/event');
var Factory = require('test/support/factory');
suite('Models.Event', function() {
var subject;
var rawEvent;
var remote;
var start;
var end;
var originalDates;
suiteSetup(function() {
start = new Date(2012, 0, 1);
end = new Date(2012, 0, 1, 12);
originalDates = {
start: start,
end: end
};
});
setup(function() {
rawEvent = Factory.create('event', {
remote: {
syncToken: '7ee',
startDate: start,
endDate: end,
start: Calc.dateToTransport(start),
end: Calc.dateToTransport(end)
}
});
remote = rawEvent.remote;
subject = new Event(rawEvent);
});
suite('initialization', function() {
test('from existing model', function() {
assert.equal(subject.data, rawEvent);
});
test('from existing "allday" model', function() {
var data = Factory.build('event', {
remote: {
startDate: new Date(2012, 1, 1),
endDate: new Date(2012, 1, 2)
}
});
var subject = new Event(data);
assert.isTrue(subject.isAllDay, 'is all day');
});
test('from existing model without .startDate/.endDate', function() {
var event = new Event();
var start = new Date(2012, 0, 1);
var end = new Date(2012, 0, 5);
event.startDate = start;
event.endDate = end;
var data = event.data;
delete data.remote.startDate;
delete data.remote.endDate;
var newEvent = new Event(data);
assert.deepEqual(newEvent.startDate, start);
assert.deepEqual(newEvent.endDate, end);
});
test('from new model', function() {
subject = new Event();
assert.ok(subject.data, 'has data');
assert.ok(subject.data.remote, 'has remote');
assert.instanceOf(
subject.startDate,
Date,
'pre-populates start date'
);
assert.instanceOf(
subject.endDate,
Date,
'pre-populates end date'
);
});
});
function remoteSetter(type) {
test('#' + type + ' setter/getter', function() {
var value = 'foo';
subject[type] = value;
assert.equal(rawEvent.remote[type], value, 'raw data ' + type);
assert.equal(subject[type], value, 'get ' + type);
});
}
// need a var for gjslint parse error?
var dateFields = ['start', 'end'];
dateFields.forEach(function(field) {
var remoteDateField = field + 'Date';
suite('#' + remoteDateField, function() {
test('initial value', function() {
assert.deepEqual(
originalDates[field],
subject[remoteDateField]
);
});
test('set value', function() {
var date = new Date(2012, 0, 1, 2);
var transport = Calc.dateToTransport(date);
subject[remoteDateField] = date;
assert.deepEqual(
remote[remoteDateField],
date,
remoteDateField
);
assert.deepEqual(
remote[field],
transport,
field
);
});
test('prevents non-date values when isAllDay', function() {
subject.isAllDay = true;
var date = new Date(2012, 1, 1, 1, 5);
var expected = new Date(2012, 1, 1);
var transport = Calc.dateToTransport(
expected, null, true
);
subject[remoteDateField] = date;
assert.deepEqual(
subject[remoteDateField],
expected,
'date'
);
assert.deepEqual(
subject.remote[field],
transport,
'trasport'
);
});
test('clears when is .allDay', function() {
subject.isAllDay = true;
assert.isTrue(
Calc.isOnlyDate(subject[remoteDateField]),
'is only date'
);
assert.isTrue(
subject.remote[field].isDate,
'is date'
);
});
});
});
test('#isAllDay', function() {
subject.isAllDay = true;
assert.isTrue(
Calc.isOnlyDate(subject.startDate),
'removes time from start'
);
assert.isTrue(
Calc.isOnlyDate(subject.endDate),
'removes time from end'
);
assert.isTrue(
subject.remote.start.isDate,
'sets start to date'
);
assert.isTrue(
subject.remote.end.isDate,
'sets end to date'
);
});
test('#calendarId', function() {
subject.calendarId = 7;
assert.equal(rawEvent.calendarId, 7);
assert.equal(subject.calendarId, 7);
});
test('#calendarId with string input', function() {
subject.calendarId = '7';
assert.strictEqual(rawEvent.calendarId, 7);
assert.strictEqual(subject.calendarId, 7);
});
test('#_id', function() {
rawEvent._id = 'foo';
assert.equal(subject._id, 'foo');
});
suite('#validationErrors', function() {
test('no errors', function() {
var event = new Event();
event.startDate = new Date(2012, 0, 1);
event.endDate = new Date(2012, 0, 2);
assert.ok(!event.validationErrors(), 'has no errors');
});
function hasError(event, type) {
var errors = event.validationErrors();
assert.lengthOf(errors, 1);
assert.deepEqual(
errors[0], {
name: type
},
'has error: "' + type + '"'
);
}
test('start date >(=) end date', function() {
var event = new Event();
event.startDate = new Date(2020, 0, 2);
event.endDate = new Date(2012, 0, 1);
// start date > end date
hasError(event, 'start-after-end');
// start date == end date
event.startDate = new Date(event.endDate.valueOf());
hasError(event, 'start-after-end');
});
});
suite('update attributes', function() {
var eventWithoutErrors;
var eventWithErrors;
var event;
var model;
setup(function() {
event = Factory.create('event', {
remote: {
syncToken: '7ee',
startDate: new Date(2019, 1, 2),
endDate: new Date(2020, 0, 2),
start: Calc.dateToTransport(start),
end: Calc.dateToTransport(end)
}
});
model = new Event(event);
eventWithoutErrors = {
startDate: new Date(2019, 1, 2),
endDate: new Date(2020, 1, 2)
};
eventWithErrors = {
startDate: new Date(2019, 1, 2),
endDate: new Date(2019, 0, 2)
};
});
test('does not update attributes', function() {
var errors = model.updateAttributes(eventWithErrors);
assert.ok(errors);
assert.deepEqual(errors[0].name, 'start-after-end');
});
test('will update attributes', function() {
var errors = model.updateAttributes(eventWithoutErrors);
assert.strictEqual(errors, true);
assert.deepEqual(model.endDate, eventWithoutErrors.endDate);
});
});
remoteSetter('syncToken');
remoteSetter('location');
remoteSetter('description');
remoteSetter('title');
remoteSetter('alarms');
});
});

View file

@ -0,0 +1,209 @@
define(function(require) {
'use strict';
var OAuthWindow = require('oauth_window');
var QueryString = require('querystring');
var View = require('view');
suite('OAuthWindow', function() {
var subject;
var url = 'https://foobar.com';
var element;
var params = {
client_id: 'foo',
redirect_uri: 'http://oauth.gaiamobile.org/foobar'
};
function emitLocationChange(url) {
var event = new CustomEvent('mozbrowserlocationchange', {
detail: url
});
subject.browserFrame.dispatchEvent(event);
}
setup(function() {
element = document.createElement('section');
element.innerHTML = [
'<section role="region">',
'<gaia-header id="oauth-header" action="cancel">',
'<h1 class="oauth-browser-title"></h1>',
'</gaia-header>',
'<div class="browser-container"></div>',
'</section>'
].join('');
subject = new OAuthWindow(
element,
url,
params
);
});
test('initialization', function() {
assert.deepEqual(subject.params, params);
assert.equal(subject.element, element);
assert.deepEqual(
subject.target,
url + '?' + QueryString.stringify(params),
'.target'
);
});
test('.browserContainer', function() {
assert.ok(subject.browserContainer, 'has container');
assert.equal(subject.browserContainer, element.querySelector(
subject.selectors.browserContainer
));
});
test('.browserTitle', function() {
assert.equal(
subject.browserTitle,
element.querySelector(subject.selectors.browserTitle)
);
});
test('.browserHeader', function() {
assert.equal(
subject.browserHeader,
element.querySelector(subject.selectors.browserHeader)
);
});
test('without redirect_uri', function() {
assert.throws(function() {
return new OAuthWindow(url, {});
});
});
suite('#open', function() {
setup(function() {
subject.open();
});
test('.isOpen', function() {
assert.isTrue(subject.isOpen);
});
test('is active', function() {
assert.ok(
subject.element.classList.contains(View.ACTIVE),
'is active'
);
});
test('has iframe', function() {
var iframe = element.querySelector(
'iframe'
);
assert.ok(
iframe.getAttribute('mozbrowser'),
'is mozbrowser'
);
assert.equal(
iframe.getAttribute('src'),
subject.target,
'is at url'
);
assert.equal(subject.browserFrame, iframe);
assert.equal(
subject.browserContainer.firstElementChild,
subject.browserFrame,
'is inside container'
);
});
test('url changed to something other then redirect_uri', function() {
var newUrl = 'http://foobar.com/bar/bar';
emitLocationChange(newUrl);
assert.equal(
subject.browserTitle.textContent,
newUrl,
'changes url'
);
});
test('click cancel button', function(done) {
var hasClosed;
subject.onabort = function() {
assert.ok(hasClosed, 'called close');
done();
};
subject.close = function() {
hasClosed = true;
};
testSupport.calendar.triggerEvent(
subject.browserHeader,
'action'
);
});
test('redirects to redirect_uri', function(done) {
var successParams = {
code: 'wow',
state: 'youwin'
};
var hasClosed;
subject.close = function() {
hasClosed = true;
};
subject.oncomplete = function(params) {
assert.ok(hasClosed, 'closed window');
assert.deepEqual(successParams, params, 'parsed params');
done();
};
emitLocationChange(
params.redirect_uri + '?' +
QueryString.stringify(successParams)
);
});
});
suite('#close', function() {
setup(function() {
subject.open();
});
test('removes iframe', function() {
assert.ok(subject.browserFrame, 'has frame');
subject.close();
assert.ok(!subject.browserFrame, 'removes frame');
assert.ok(!element.querySelector('iframe'), 'removes from dom');
assert.isFalse(subject.isOpen, 'isOpen');
assert.ok(
!element.classList.contains(View.ACTIVE),
'is inactive'
);
});
test('cleans up events', function() {
var triggedClose;
subject.close();
subject.close();
subject.close = function() {
triggedClose = true;
};
testSupport.calendar.triggerEvent(
subject.browserHeader,
'action'
);
assert.ok(!triggedClose, 'does not trigger close');
});
});
});
});

View file

@ -0,0 +1 @@
// TODO(gareth)

View file

@ -0,0 +1,31 @@
define(function(require) {
'use strict';
var Abstract = require('provider/abstract');
suite('Provider.Abstract', function() {
var subject;
setup(function() {
subject = new Abstract();
});
test('initializer', function() {
assert.ok(subject);
});
test('#eventCapabilities', function(done) {
// just trying to assert the api contract is correct.
subject.eventCapabilities({}, function(err, list) {
if (err) {
return done(err);
}
done(function() {
assert.instanceOf(list, Object);
});
});
});
});
});

View file

@ -0,0 +1,700 @@
define(function(require) {
'use strict';
var AccountModel = require('models/account');
var CalendarModel = require('models/calendar');
var CaldavPullEvents = require('provider/caldav_pull_events');
var CaldavService = require('service/caldav');
var Responder = require('common/responder');
var Factory = require('test/support/factory');
var ServiceSupport = require('test/service/helper');
var core = require('core');
suite('Provider.CaldavPullEvents', function() {
var fixtures;
var ical;
var subject;
var stream;
var db;
var service;
var storeFactory;
function createSubject(options) {
if (typeof(options) === 'undefined') {
options = Object.create(null);
}
if (!options.calendar) {
options.calendar = calendar;
}
if (!options.account) {
options.account = account;
}
stream = new Responder();
return new CaldavPullEvents(stream, options);
}
setup(function(done) {
ical = new ServiceSupport.Fixtures('ical');
ical.load('single_event');
ical.load('daily_event');
ical.load('recurring_event');
ical.onready = done;
fixtures = {};
ServiceSupport.setExpansionLimit(10);
service = new CaldavService(new Responder());
});
teardown(function() {
ServiceSupport.resetExpansionLimit();
});
['singleEvent', 'dailyEvent', 'recurringEvent'].forEach(function(item) {
setup(function(done) {
service.parseEvent(ical[item], function(err, event) {
fixtures[item] = service._formatEvent(
'abc', '/foobar.ics',
ical[item], event
);
done();
});
});
});
function serviceEvent(type) {
// poor mans clone
var json = JSON.stringify(
fixtures[type]
);
return JSON.parse(json);
}
setup(function(done) {
db = core.db;
storeFactory = core.storeFactory;
db.open(function(err) {
assert.ok(!err);
done();
});
});
var account;
setup(function(done) {
account = Factory.create('account');
storeFactory.get('Account').persist(account, done);
});
var calendar;
setup(function(done) {
calendar = Factory.create('calendar');
calendar.accountId = account._id;
calendar.remote.syncToken = 'not-same-as-other';
calendar.syncToken = 'neq';
storeFactory.get('Calendar').persist(calendar, done);
});
setup(function() {
subject = createSubject();
});
teardown(function(done) {
testSupport.calendar.clearStore(
db,
['accounts', 'calendars', 'icalComponents',
'events', 'busytimes', 'alarms'],
done
);
});
teardown(function() {
db.close();
});
test('initializer', function() {
var subject = createSubject();
assert.instanceOf(subject.account, AccountModel, '.account');
assert.instanceOf(subject.calendar, CalendarModel, '.calendar');
});
suite('#eventIdFromRemote', function() {
test('exception', function() {
var event = serviceEvent('singleEvent');
event.recurrenceId = { utc: 100 };
var id = calendar._id + '-' + event.id + '-100';
assert.deepEqual(
subject.eventIdFromRemote(event),
id
);
});
test('non exception', function() {
var event = serviceEvent('singleEvent');
var id = calendar._id + '-' + event.id;
assert.deepEqual(
subject.eventIdFromRemote(event),
id
);
});
});
suite('#formatEvent', function() {
test('recurring event', function() {
var event = serviceEvent('recurringEvent');
var exceptions = event.exceptions;
delete event.exceptions;
var primary = subject.formatEvent(
event
);
var exception = subject.formatEvent(
exceptions[0]
);
assert.ok(exception.remote, 'has remote');
assert.deepEqual(
exception.parentId,
primary._id,
'has parent id'
);
assert.equal(
exception._id,
primary._id + '-' + exception.remote.recurrenceId.utc,
'has exception id'
);
});
test('single event', function() {
var event = serviceEvent('singleEvent');
var remote = serviceEvent('singleEvent');
delete event.exceptions;
delete remote.exceptions;
var expected = {
_id: subject.eventIdFromRemote(remote),
calendarId: calendar._id,
remote: remote
};
assert.deepEqual(
subject.formatEvent(event),
expected
);
});
});
suite('#formatBusytime', function() {
var times = [];
var event;
function expand(name, limit) {
setup(function(done) {
times.length = 0;
event = serviceEvent(name);
var stream = new Responder();
stream.on('occurrence', function(item) {
times.push(item);
});
service.expandRecurringEvent(
ical[name],
{},
stream,
done
);
});
}
suite('without exceptions', function() {
expand('dailyEvent');
test('non-exception result', function() {
var time = times[1];
assert.isFalse(time.isException, 'is exception');
var result = subject.formatBusytime(time);
var modelCopy = Object.create(result);
modelCopy = storeFactory.get('Busytime').initRecord(modelCopy);
assert.hasProperties(
result,
modelCopy,
'is a model'
);
var eventId = result.eventId;
var calendarId = result.calendarId;
assert.ok(eventId, 'has event');
assert.ok(calendarId, 'has calendar');
assert.instanceOf(result.alarms, Array);
result.alarms.forEach(function(alarm) {
assert.equal(alarm.busytimeId, result._id);
assert.equal(alarm.eventId, eventId);
});
});
});
suite('with exceptions', function() {
expand('recurringEvent');
test('result', function() {
var time = times[2];
assert.isTrue(time.isException, 'is exception');
var eventId = subject.eventIdFromRemote(
time
);
var result = subject.formatBusytime(time);
assert.ok(result._id, 'has id');
assert.equal(result.calendarId, calendar._id);
assert.include(
result.eventId,
times[2].recurrenceId.utc,
'has utc time'
);
assert.equal(result.eventId, eventId);
});
});
});
suite('bug 809607', function() {
var accountStore;
var account;
setup(function(done) {
accountStore = storeFactory.get('Account');
account = Factory.create('account');
accountStore.persist(account, done);
});
var calendar;
setup(function(done) {
calendar = Factory.create('calendar');
calendar.accountId = account._id;
calendar.remote.syncToken = 'bug-809607';
calendar.syncToken = 'bug-809607';
storeFactory.get('Calendar').persist(calendar, done);
});
var subject;
var events;
var eventPersistCt;
var eventStore;
setup(function() {
subject = createSubject({account: account, calendar: calendar});
events = [];
for (var i = 0; i < 4; i++) {
events.push(serviceEvent('singleEvent'));
}
// Set up to count events persisted.
eventPersistCt = 0;
eventStore = storeFactory.get('Event');
eventStore.on('persist', function(id) {
eventPersistCt++;
});
});
test('account deletion during sync aborts processing', function(done) {
// Stream a couple of events before account removal
stream.emit('event', events[0]);
stream.emit('event', events[1]);
function afterRemove() {
// Stream a couple of events after account removal
stream.emit('event', events[2]);
stream.emit('event', events[3]);
// Attempt to commit the events
subject.commit(function() {
setTimeout(afterCommit, 0);
});
}
function afterCommit() {
// After account removal and commit, no events should have been saved.
assert.equal(eventPersistCt, 0);
done();
}
// Kick off the removal and subsequent steps...
accountStore.remove(account._id, function() {
setTimeout(afterRemove, 0);
});
});
test('abort during #commit also aborts transaction', function(done) {
// Stream some events...
for (var i = 0; i < events.length; i++) {
stream.emit('event', events[i]);
}
subject.commit(function() {
// No-op
});
// Detection of transaction abort is a test success.
subject._trans.onabort = function() {
// Restore the exception handler.
done();
};
// Queue up account removal...
accountStore.emit('remove', account._id);
});
});
suite('#commit', function() {
var removed = [];
var eventStore;
var componentStore;
var newEvent;
var newBusytime;
var alarm;
var addedComponent;
setup(function() {
removed.length = 0;
eventStore = storeFactory.get('Event');
componentStore = storeFactory.get('IcalComponent');
eventStore.remove = function(id) {
removed.push(id);
};
newEvent = serviceEvent('singleEvent');
newEvent = subject.formatEvent(newEvent);
addedComponent = {
eventId: newEvent._id,
ical: 'foo'
};
subject.icalQueue.push(addedComponent);
subject.eventQueue.push(newEvent);
subject.removeList = ['1'];
newBusytime = Factory('busytime', {
eventId: newEvent._id,
calendarId: newEvent.calendarId
});
subject.busytimeQueue.push(newBusytime);
alarm = Factory('alarm', {
startDate: new Date(),
eventId: newEvent._id,
busytimeId: newBusytime._id
});
subject.alarmQueue.push(alarm);
});
function commit(fn) {
setup(function(done) {
subject.commit(function() {
setTimeout(function() {
done();
}, 0);
});
});
}
suite('busytime/alarm', function() {
commit();
test('alarm', function(done) {
var trans = db.transaction('alarms');
var store = trans.objectStore('alarms');
var index = store.index('busytimeId');
index.get(alarm.busytimeId).onsuccess = function(e) {
done(function() {
var data = e.target.result;
assert.ok(data, 'has alarm');
assert.hasProperties(data, alarm, 'alarm matches');
});
};
});
test('busytimes', function(done) {
var id = newBusytime._id;
var trans = db.transaction('busytimes');
var store = trans.objectStore('busytimes');
store.get(id).onsuccess = function(e) {
done(function() {
var result = e.target.result;
assert.ok(result);
assert.hasProperties(result, {
start: newBusytime.start,
end: newBusytime.end,
eventId: newBusytime.eventId,
calendarId: newBusytime.calendarId
});
});
};
});
});
suite('without remove list', function() {
setup(function() {
subject.removeList = null;
});
test('result', function(done) {
subject.commit(done);
});
});
suite('event/component', function() {
commit();
test('component', function(done) {
componentStore.get(newEvent._id, function(err, record) {
if (err) {
done(err);
return;
}
done(function() {
assert.ok(record, 'has records');
assert.deepEqual(record, addedComponent);
});
});
});
test('event', function(done) {
eventStore.findByIds([newEvent._id], function(err, list) {
done(function() {
assert.lengthOf(Object.keys(list), 1, 'saved events');
assert.ok(list[newEvent._id], 'saved event id');
});
});
});
});
});
suite('#handleOccurrenceSync', function() {
var times = [];
var event;
setup(function(done) {
event = serviceEvent('dailyEvent');
var stream = new Responder();
stream.on('occurrence', function(item) {
times.push(item);
});
service.expandRecurringEvent(
ical.dailyEvent,
{},
stream,
done
);
});
function copy(idx) {
var json = JSON.stringify(times[idx]);
return JSON.parse(json);
}
test('single', function() {
var expected = subject.formatBusytime(
copy(0)
);
var alarms = expected.alarms;
delete expected.alarms;
assert.ok(alarms, 'has alarms');
stream.emit('occurrence', times[0]);
assert.lengthOf(subject.busytimeQueue, 1);
// ids are unique each time
expected._id = subject.busytimeQueue[0]._id;
assert.ok(expected._id, 'has id');
assert.hasProperties(
subject.busytimeQueue[0],
expected,
'queued'
);
assert.ok(!subject.busytimeQueue[0].alarms, 'removes alarms');
assert.deepEqual(
subject.alarmQueue.length, alarms.length,
'moves moves to alarm queue'
);
});
});
test('#handleMissingEvents', function() {
stream.emit('missingEvents', ['1', '2']);
assert.deepEqual(subject.removeList, ['1', '2']);
});
suite('#handleComponentSync', function() {
test('incomplete recurrence', function() {
var data = {
eventId: 'foo',
lastRecurrenceId: { year: 2012 },
ical: 'ical'
};
var expected = {};
for (var key in data) {
expected[key] = data[key];
}
expected.eventId = subject.eventIdFromRemote(
data
);
expected.calendarId = calendar._id;
stream.emit('component', data);
assert.lengthOf(subject.icalQueue, 1);
assert.deepEqual(
subject.icalQueue[0],
expected
);
});
test('complete recurrence', function() {
var data = {
lastRecurrenceId: false,
ical: 'ical'
};
stream.emit('component', data);
assert.lengthOf(subject.icalQueue, 1);
assert.ok(
!('lastRecurrenceId' in subject.icalQueue[0]),
'has not lastRecurrenceId'
);
});
});
suite('#handleEventSync', function() {
test('recurring', function() {
// control is needed to verify we have not mutated
// the results by using the same object during the test.
var control = serviceEvent('recurringEvent');
control = subject.formatEvent(control);
assert.lengthOf(control.remote.exceptions, 2);
var exceptions = control.remote.exceptions;
delete control.remote.exceptions;
exceptions = exceptions.map(subject.formatEvent, subject);
// we need to do this twice so this is a clean
// copy and the above can be mutated
var event = serviceEvent('recurringEvent');
stream.emit('event', event);
assert.lengthOf(subject.eventQueue, 3);
assert.lengthOf(subject.icalQueue, 1);
assert.hasProperties(
subject.icalQueue[0],
{
eventId: control._id,
data: control.remote.icalComponent
}
);
var order = [control].concat(exceptions);
// ensure event and all its exceptions are queued
// and in the correct order (event first exceptions after)
order.forEach(function(item, idx) {
assert.hasProperties(subject.eventQueue[idx].remote, {
id: item.remote.id,
eventId: item.remote.eventId,
recurrenceId: item.remote.recurrenceId
});
});
});
test('new event', function() {
var existing = serviceEvent('singleEvent');
existing = subject.formatEvent(existing);
existing.remote.syncToken = 'abx1';
subject = createSubject();
var newEvent = serviceEvent('singleEvent');
newEvent.syncToken = 'bbx1';
assert.notEqual(
newEvent.syncToken,
existing.remote.syncToken,
'sync tokens match'
);
stream.emit('event', newEvent);
assert.lengthOf(
subject.eventQueue,
1
);
});
test('event new', function() {
var event = serviceEvent('singleEvent');
stream.emit('event', event);
assert.deepEqual(
subject.eventQueue,
[subject.formatEvent(event)]
);
});
});
});
});

File diff suppressed because it is too large Load diff

View file

@ -0,0 +1,212 @@
define(function(require) {
'use strict';
var Abstract = require('provider/abstract');
var Factory = require('test/support/factory');
var Local = require('provider/local');
var core = require('core');
suite('provider/local', function() {
var subject;
var db;
var storeFactory;
setup(function(done) {
subject = new Local();
storeFactory = core.storeFactory;
db = core.db;
db.open(function(err) {
assert.ok(!err);
done();
});
});
teardown(function(done) {
testSupport.calendar.clearStore(
db,
['accounts', 'calendars', 'events', 'busytimes'],
done
);
});
teardown(function() {
db.close();
});
test('initialization', function() {
assert.instanceOf(subject, Abstract);
});
test('#getAccount', function(done) {
subject.getAccount({}, function(err, success) {
assert.ok(!err);
assert.deepEqual(success, {});
done();
});
});
/* disabled in Bug 838993, to be reenabled asap in Bug 840489.
* this happens when using a firefox launched with a french locale, we
* probably need to mock the l10n library instead of using it.
*
* 1) [calendar] provider/local #findCalendars:
* Error: expected 'Agenda hors-ligne' to equal 'Offline calendar'
*
test('#findCalendars', function(done) {
// local will always return the same
// calendar id
subject.findCalendars({}, function(err, list) {
done(function() {
var first = list['local-first'];
assert.equal(first.id, 'local-first');
assert.equal(first.name, 'Offline calendar');
});
});
});
*/
suite('mutations', function() {
var events;
var busytimes;
setup(function() {
events = storeFactory.get('Event');
busytimes = storeFactory.get('Busytime');
});
function find(eventId, done) {
var trans = db.transaction(
events._dependentStores,
'readwrite'
);
trans.oncomplete = function() {
done(busytime, event);
};
var event;
var busytime;
events.get(eventId, trans, function(err, record) {
event = record;
});
var index = trans.objectStore('busytimes').index('eventId');
index.get(eventId).onsuccess = function(e) {
busytime = e.target.result;
};
}
suite('#createEvent', function() {
var event;
function verify(done) {
subject.createEvent(event, function() {
find(event._id, function(busytime, event) {
done(function() {
assert.deepEqual(event, event);
});
});
});
}
test('without remote.id', function(done) {
event = Factory('event');
delete event.remote.id;
delete event._id;
verify(done);
assert.ok(event.remote.id, 'adds id');
});
test('with remote.id', function(done) {
event = Factory('event');
delete event._id;
var id = event.remote.id;
verify(done);
assert.equal(event.remote.id, id, 'id change');
});
});
suite('#updateEvent', function() {
//XXX: in the future we should skip the saving.
suite('update with same values', function() {
var event;
var busytime;
setup(function(done) {
event = Factory('event');
subject.createEvent(event, done);
});
setup(function(done) {
subject.updateEvent(event, busytime, function(err, busy, ev) {
event = ev;
busytime = busy;
done();
});
});
test('event', function(done) {
assert.ok(event);
events.count(function(err, count) {
done(function() {
assert.equal(count, 1);
});
});
});
test('busytime', function(done) {
assert.hasProperties(busytime, {
eventId: event._id, calendarId: event.calendarId
});
busytimes.count(function(err, count) {
done(function() {
assert.equal(count, 1);
});
});
});
});
});
suite('#deleteEvent', function() {
var event;
setup(function(done) {
event = Factory('event');
subject.createEvent(event, done);
});
setup(function(done) {
subject.deleteEvent(event, done);
});
test('busytime count', function(done) {
busytimes.count(function(err, count) {
done(function() {
assert.equal(count, 0);
});
});
});
test('event count', function(done) {
events.count(function(err, count) {
done(function() {
assert.equal(count, 0);
});
});
});
});
});
});
});

View file

@ -0,0 +1,68 @@
define(function(require, exports, module) {
'use strict';
var Responder = require('common/responder');
function MockStream() {
Responder.call(this);
this._queue = [];
this._workingQueue = false;
}
module.exports = MockStream;
var emit = Responder.prototype.emit;
MockStream.prototype = {
__proto__: Responder.prototype,
emit: function() {
var args = Array.prototype.slice.call(arguments);
var self = this;
this._queue.push(function() {
emit.apply(self, args);
});
this._workQueue();
},
request: function(callback) {
if (this._requestCallback) {
throw new Error('request may only be called once.');
}
this._requestCallback = callback;
if (this.mockonrequest) {
this.mockonrequest();
}
},
_workQueue: function() {
if (!this._workingQueue && this._queue.length) {
var self = this;
var next = this._queue.shift();
this._workingQueue = true;
setTimeout(function() {
next();
self._workingQueue = false;
self._workQueue();
}, 0);
}
},
close: function() {
var self = this;
this._queue.push(function() {
self._requestCallback.apply(self, arguments);
});
this._workQueue();
}
};
});

View file

@ -0,0 +1,199 @@
define(function(require) {
'use strict';
var Factory = require('test/support/factory');
var nextTick = require('common/next_tick');
suite('provider/mock', function() {
var subject;
setup(function() {
var core = testSupport.calendar.core();
subject = core.providerFactory.get('Mock');
});
test('staged data events', function(done) {
var account = { user: 'user' };
var handle = subject.stageGetAccount(
'user',
null
);
var firedBefore = false;
handle.onbefore = function() {
firedBefore = true;
};
var firedAfter = false;
handle.onafter = function() {
firedAfter = true;
};
subject.getAccount(account, function() {
assert.ok(firedBefore, 'fires before event');
assert.ok(!firedAfter, 'after fires after callback');
nextTick(function() {
done(function() {
assert.ok(firedAfter, 'fires after');
});
});
});
assert.ok(!firedBefore, 'before is async');
});
suite('#getAccount', function() {
test('with staged account', function(done) {
var obj = {};
subject.stageGetAccount('user', null, obj);
subject.getAccount({ user: 'user' }, function(err, result) {
assert.ok(!err);
assert.equal(result, obj, 'uses staged object');
subject.getAccount({ user: 'user' }, function(err, result) {
assert.ok(!err);
assert.notEqual(obj, result, 'staged object is not resued');
done();
});
});
});
test('without mock signal', function(done) {
subject.getAccount({}, function(err, value) {
done(function() {
assert.ok(!err);
assert.deepEqual(value, {});
});
});
});
});
suite('#findCalendars', function() {
test('with staging', function(done) {
var list = {
one: Factory('remote.calendar', { id: 'one' })
};
subject.stageFindCalendars('user', null, list);
subject.findCalendars({ user: 'user' }, function(err, result) {
done(function() {
assert.ok(!err);
assert.deepEqual(list, result);
});
});
});
test('without staging', function(done) {
subject.findCalendars({}, function(err, list) {
done(function() {
assert.ok(!err);
assert.deepEqual(list, {});
});
});
});
});
suite('#syncEvents', function() {
var calendar;
var account;
setup(function() {
account = Factory('account');
calendar = Factory('calendar');
});
test('without staged data', function(done) {
subject.syncEvents({}, {}, done);
});
test('with staged data', function(done) {
var err = new Error('I win!');
subject.stageSyncEvents(
account.user,
calendar.remote.id,
err
);
subject.syncEvents(account, calendar, function(gotErr) {
done(function() {
assert.equal(err, gotErr);
});
});
});
});
suite('#calendarCapabilities', function() {
test('with staging data', function() {
subject.stageCalendarCapabilities(1, {
canDeleteEvent: false
});
assert.deepEqual(
subject.calendarCapabilities({ _id: 1 }),
{
canDeleteEvent: false,
canUpdateEvent: true,
canCreateEvent: true
}
);
});
test('without staging data', function() {
assert.deepEqual(
subject.calendarCapabilities({ _id: 1 }),
{
canDeleteEvent: true,
canUpdateEvent: true,
canCreateEvent: true
}
);
});
});
suite('#eventCapabilities', function() {
test('with staging data', function(done) {
subject.stageEventCapabilities(1, null, {
canDelete: false
});
subject.eventCapabilities({ _id: 1 }, function(err, result) {
if (err) {
return done(err);
}
done(function() {
assert.deepEqual(result, {
canCreate: true,
canDelete: false,
canUpdate: true
});
});
});
});
test('without staging data', function(done) {
subject.eventCapabilities({}, function(err, result) {
if (err) {
return done(err);
}
done(function() {
assert.deepEqual(result, {
canCreate: true,
canUpdate: true,
canDelete: true
});
});
});
});
});
});
});

View file

@ -0,0 +1,86 @@
define(function(require) {
'use strict';
var FakePage = require('test/support/fake_page');
var MockView = require('test/support/mock_view');
var router = require('router');
suite('router', function() {
var subject;
var page;
var View;
suiteSetup(function() {
View = MockView;
});
setup(function() {
page = FakePage;
subject = router;
subject.page = FakePage;
subject._activeObjects = [];
page.routes.length = 0;
});
test('initializer', function() {
assert.equal(subject.page, page, 'should have page');
assert.deepEqual(subject._activeObjects, [], 'should have active objects');
});
suite('#mangeObject', function() {
var object;
setup(function() {
object = {};
});
test('with onactive', function() {
var calledWith;
object.onactive = function() {
calledWith = arguments;
object.onactiveCalled = true;
};
subject.mangeObject(object, 'foo');
assert.ok(object.onactiveCalled);
assert.equal(subject._activeObjects[0], object);
assert.equal(calledWith[0], 'foo');
});
test('without onactive', function() {
subject.mangeObject(object);
assert.ok(!subject.__routerActive);
assert.equal(subject._activeObjects[0], object);
});
});
test('#clearObjects', function() {
var one = new View();
var two = new View();
two.active = one.active = true;
subject._activeObjects.push(one);
subject._activeObjects.push(two);
subject.clearObjects({});
assert.isFalse(one.active);
assert.isFalse(two.active);
});
var wrappedMethods = ['start', 'stop', 'show'];
wrappedMethods.forEach(function(method) {
test('#' + method, function() {
//hard to test because we are going
//to bind them to page to ensure the
//correct scope.
assert.ok(subject[method]);
});
});
});
});

File diff suppressed because it is too large Load diff

View file

@ -0,0 +1,101 @@
define(function(require, exports, module) {
'use strict';
var IcalRecurExpansion = require('service/ical_recur_expansion');
function request(file, cb) {
var xhr = new XMLHttpRequest();
xhr.open('GET', '/test/unit/' + file + '?' + Date.now(), true);
xhr.onreadystatechange = function() {
if (xhr.readyState === 4) {
if (xhr.status !== 200) {
cb(new Error('file not found or other error (' + file + ')', xhr));
} else {
cb(null, xhr.responseText);
}
}
};
xhr.send(null);
}
function camelize(string) {
var out = string.replace(/(?:^|[-_])(\w)/g, function(_, c) {
return c ? c.toUpperCase() : '';
});
return out[0].toLowerCase() + out.slice(1);
}
const ICAL_ROOT = 'fixtures/caldav/ical/';
var Handlers = {
/**
* Load and parse an ical sample
*
* @param {String} file location of ical file.
* @param {Function} node style callback: err, contents.
*/
ical: function icalLoader(file, callback) {
request(ICAL_ROOT + file + '.ics', function(err, text) {
if (err) {
callback(err);
return;
}
callback(null, text);
});
}
};
function Fixtures(type) {
var pending = 0;
var self = this;
var handler = Handlers[type];
function next() {
if (!(--pending)) {
if (self._error) {
throw self._error;
}
self.onready();
}
}
this.load = function loadFixture(file) {
pending++;
handler(file, function(err, contents) {
if (err) {
self._error = err;
} else {
self[camelize(file)] = contents;
}
next();
});
};
return this;
}
var Helper = {
Fixtures: Fixtures,
_originalExpansionLimit: null,
resetExpansionLimit: function() {
if (!Helper._originalExpansionLimit) {
IcalRecurExpansion.forEachLimit = Helper._originalExpansionLimit;
}
},
setExpansionLimit: function(newLimit) {
if (!Helper._originalExpansionLimit) {
Helper._originalExpansionLimit = IcalRecurExpansion.forEachLimit;
}
IcalRecurExpansion.forEachLimit = newLimit;
}
};
module.exports = Helper;
});

View file

@ -0,0 +1,275 @@
define(function(require) {
'use strict';
var CaldavService = require('service/caldav');
var ICAL = require('ext/ical');
var IcalRecurExpansion = require('service/ical_recur_expansion');
var Responder = require('common/responder');
var ServiceSupport = require('test/service/helper');
suite('service/ical_recur_expansion', function() {
var fixtures;
var subject;
var parseEvent;
var forEachLimit = 30;
suiteSetup(function() {
subject = IcalRecurExpansion;
// replace the maximum iterators with a smaller
// number suitable for tests.
ServiceSupport.setExpansionLimit(forEachLimit);
});
suiteTeardown(function() {
ServiceSupport.resetExpansionLimit();
});
// setup fixtures...
suiteSetup(function(done) {
fixtures = new ServiceSupport.Fixtures('ical');
fixtures.load('recurring_event');
fixtures.onready = done;
var service = new CaldavService(new Responder());
parseEvent = service.parseEvent.bind(service);
});
test('initialization', function() {
assert.ok(subject, 'exists');
});
suite('#forEach', function() {
var minDate;
var maxDate;
var inclusiveDates = [];
var iterator;
suiteSetup(function() {
// min will be the 5th date - 1 second
// max will be the 14th + 1 secound
// so 5-15 will be the range we expect.
parseEvent(fixtures.recurringEvent, function(err, event) {
var iter = event.iterator();
var num = 15;
while (--num) {
var last = iter.next();
if (num === 1) {
maxDate = last;
}
if (num === 10) {
minDate = last;
}
if (num <= 10) {
inclusiveDates.push(last.toJSDate());
}
}
minDate.second -= 1;
maxDate.second -= 1;
});
});
// create closed scope for sent/each for re-usability.
var verifyBounds = (function() {
var sent = [];
function each(item) {
sent.push(item.toJSDate());
}
function verifyMinMax() {
test('min & max', function() {
sent.length = 0;
var iter = subject.forEach(
event,
iterator,
each,
minDate,
maxDate
);
assert.deepEqual(
sent,
inclusiveDates
);
assert.deepEqual(
iter.last.toJSDate(),
inclusiveDates[inclusiveDates.length - 1]
);
});
}
function verifyMax() {
test('max', function() {
sent.length = 0;
var iter = subject.forEach(
event,
iterator,
each,
null,
maxDate
);
assert.deepEqual(
iter.last.toJSDate(),
inclusiveDates[inclusiveDates.length - 1],
'has last date'
);
});
}
function verifyMin() {
test('min', function() {
sent.length = 0;
subject.forEach(
event,
iterator,
each,
minDate
);
assert.deepEqual(
sent[0],
inclusiveDates[0]
);
assert.lengthOf(sent, forEachLimit, 'limit bounds');
});
test('min verify exclusive', function() {
sent.length = 0;
var exclusiveMin = minDate.clone();
exclusiveMin.second += 1;
subject.forEach(
event,
iterator,
each,
exclusiveMin
);
assert.deepEqual(
sent[0],
inclusiveDates[1]
);
assert.lengthOf(sent, forEachLimit, 'limit bounds');
});
}
return function verifyBounds() {
suite('bounds', function() {
verifyMax();
verifyMin();
verifyMinMax();
});
};
}());
var event;
setup(function(done) {
parseEvent(fixtures.recurringEvent, function(err, result) {
event = result;
done();
});
});
suite('with a pre-existing iterator', function() {
var last;
setup(function() {
iterator = event.iterator();
iterator.next();
last = iterator.next();
});
test('with a broken iterator', function() {
// round trip the data so we can clone it.
var iterStr = JSON.stringify(iterator);
var iterClone = new ICAL.RecurExpansion(
JSON.parse(iterStr)
);
var expected = [];
subject.forEach(event, iterClone, function(item) {
expected.push(item.toJSDate());
}, minDate, maxDate);
var sent = [];
subject.forEach(event, {}, function(item) {
sent.push(item.toJSDate());
}, minDate, maxDate);
assert.deepEqual(sent, expected);
});
test('resuming iteration', function() {
var expectedIter = event.iterator();
expectedIter.next();
expectedIter.next();
var expected = expectedIter.next();
var sent = [];
iterator = JSON.parse(
JSON.stringify(iterator)
);
subject.forEach(event, iterator, function(item) {
sent.push(item);
});
assert.deepEqual(
sent[0].toJSDate(),
expected.toJSDate()
);
assert.ok(
sent[0].compare(sent[1]) < 0,
'future instances are in the future'
);
});
verifyBounds();
});
suite('without an iterator', function() {
// ensure helpers have no iterators
setup(function() {
iterator = null;
});
test('without timeframe', function() {
var sent = [];
var iter = subject.forEach(event, null, function(last) {
sent.push(last);
});
assert.instanceOf(iter, ICAL.RecurExpansion);
assert.lengthOf(sent, forEachLimit);
// test sanity we need an infinite recur iterator
// or our tests simply suck...
assert.isFalse(iter.complete, 'should be incomplete');
});
verifyBounds();
});
});
});
});

View file

@ -0,0 +1,36 @@
(function(window) {
'use strict';
var getTestLoader = require('/js/ext/alameda.js').then(() => {
requirejs.config({
baseUrl: '/js',
paths: {
css: '/test/unit/support/css',
dom: '/test/unit/support/dom',
shared: '/shared/js',
sharedtest: '/shared/test/unit',
test: '/test/unit'
},
map: {
'*': {
'ext/page': 'test/support/fake_page'
}
},
shim: {
'ext/caldav': { exports: 'Caldav' },
'ext/ical': { exports: 'ICAL' },
'shared/gesture_detector': { exports: 'GestureDetector' },
'shared/notification_helper': { exports: 'NotificationHelper' },
'shared/intl_helper': { exports: 'IntlHelper' },
'sharedtest/mocks/mock_l10n': { exports: 'MockL10n' }
}
});
return new Promise(res => requirejs(['test/support/test_loader'], res));
});
window.testAgentRuntime.testLoader = function(path) {
return getTestLoader.then(testLoader => testLoader(path));
};
})(this);

View file

@ -0,0 +1,11 @@
define(function(require) {
'use strict';
var snakeCase = require('snake_case');
test('snake_case', function() {
assert.strictEqual(snakeCase('FooBarBaz'), 'foo_bar_baz');
assert.strictEqual(snakeCase('fooBarBaz'), 'foo_bar_baz');
});
});

View file

@ -0,0 +1,453 @@
define(function(require, exports, module) {
'use strict';
var Abstract = require('store/abstract');
var Factory = require('test/support/factory');
var Responder = require('common/responder');
var core = require('core');
suite('store/abstract', function() {
var subject;
var db;
setup(function(done) {
db = core.db;
subject = new Abstract(db);
// set _store to accounts so we can actually
// persist stuff.
subject._store = 'accounts';
db.open(function(err) {
assert.ok(!err);
done();
});
});
teardown(function(done) {
var trans = db.transaction(subject._store, 'readwrite');
var accounts = trans.objectStore(subject._store);
var res = accounts.clear();
res.onerror = function() {
done(new Error('could not wipe accounts db'));
};
res.onsuccess = function() {
done();
};
});
teardown(function() {
db.close();
});
test('initialization', function() {
assert.instanceOf(subject, Responder);
assert.deepEqual(subject._cached, {});
});
suite('#get', function() {
test('missing id', function(done) {
subject.get('FOO_NOT_HERE', function(err, record) {
done(function() {
assert.ok(!err);
assert.ok(!record);
});
});
});
suite('id present', function() {
var record;
setup(function(done) {
record = Factory('account', { _id: 'foo' });
subject.persist(record, done);
});
test('result', function(done) {
subject.get(record._id, function(err, result) {
done(function() {
assert.hasProperties(
record,
result,
'record matches persisted value'
);
});
});
});
});
});
suite('#persist', function() {
var events;
var id;
var object;
function watchEvent(event, done) {
subject.once(event, function() {
events[event] = arguments;
if (typeof(done) === 'function') {
done();
}
});
}
function checkEvent(event, id, object) {
var list = events[event];
assert.equal(list[0], id);
assert.equal(list[1], object);
}
suiteSetup(function() {
this.testData = {};
});
setup(function(done) {
object = this.testData.object;
events = {};
subject._addDependents = function() {};
if (this.testData.persist !== false) {
subject.persist(object, function(err, key) {
id = key;
});
watchEvent('add');
watchEvent('update');
watchEvent('persist', done);
} else {
done();
}
});
suite('with transaction', function() {
suiteSetup(function() {
this.testData.persist = false;
});
suiteTeardown(function() {
delete this.testData.persist;
});
test('result', function(done) {
var trans;
var obj = { name: 'foo' };
var callbackFired = false;
var transFired = false;
var pending = 2;
function next() {
pending--;
if (!pending) {
complete();
}
}
function complete() {
done(function() {
assert.isTrue(callbackFired);
assert.isTrue(transFired);
});
}
trans = core.db.transaction(
subject._store,
'readwrite'
);
trans.oncomplete = function() {
transFired = true;
next();
};
subject.persist(obj, trans, function() {
callbackFired = true;
next();
});
});
});
suite('update', function() {
var id = 'uniq';
suiteSetup(function() {
this.testData.persist = true;
this.testData.object = { providerType: 'local', _id: 'uniq' };
});
suiteTeardown(function() {
delete this.testData.persist;
delete this.testData.object;
});
test('update event', function() {
checkEvent('update', id, object);
});
test('persist event', function() {
checkEvent('persist', id, object);
});
test('db persistance', function(done) {
subject.get(id, function(err, result) {
if (err) {
done(err);
return;
}
done(function() {
assert.equal(object._id, id);
assert.equal(subject._cached[id], object);
assert.deepEqual(result.providerType, object.providerType);
});
});
});
});
suite('add', function() {
suiteSetup(function() {
this.testData.persist = true;
this.testData.object = { providerType: 'local' };
});
suiteTeardown(function() {
delete this.testData.persist;
delete this.testData.object;
});
test('add event', function() {
checkEvent('add', id, object);
});
test('persist event', function() {
checkEvent('persist', id, object);
});
test('db persistence', function(done) {
subject.get(id, function(err, result) {
if (err) {
done(err);
return;
}
done(function() {
assert.equal(object._id, id);
assert.equal(subject._cached[id], object);
assert.deepEqual(result.providerType, object.providerType);
});
});
});
});
});
suite('#remove', function() {
var id;
var removeEvent;
var callbackCalled = false;
var removeDepsCalled = false;
setup(function(done) {
subject.persist({ providerType: 'Local' }, function(err, key) {
if (err) {
done(new Error('could not add'));
} else {
id = key;
done();
}
});
});
setup(function(done) {
var preRemoveCalled = false;
callbackCalled = false;
removeDepsCalled = false;
subject._removeDependents = function() {
removeDepsCalled = arguments;
};
subject.once('preRemove', function(_id) {
assert.equal(id, _id, 'same id');
preRemoveCalled = true;
});
subject.remove(id, function() {
callbackCalled = true;
});
assert.ok(preRemoveCalled, 'removes event');
subject.once('remove', function() {
removeEvent = arguments;
// wait until next tick so other events
// have finished firing...
setTimeout(function() {
done();
}, 0);
});
});
test('event', function() {
assert.equal(removeEvent[0], id);
});
test('remove', function() {
assert.ok(callbackCalled);
assert.equal(removeDepsCalled[0], id);
assert.instanceOf(removeDepsCalled[1], IDBTransaction);
assert.ok(!subject._cached[id], 'should remove cached account');
});
});
suite('#count', function() {
setup(function(done) {
var trans = core.db.transaction(
subject._store,
'readwrite'
);
trans.oncomplete = function() {
done();
};
subject.persist(Factory('account'), trans);
subject.persist(Factory('account'), trans);
});
test('result', function(done) {
subject.count(function(err, number) {
done(function() {
assert.equal(number, 2);
});
});
});
});
suite('#all', function() {
var ids = [];
setup(function() {
ids.length = 0;
});
function add() {
setup(function(done) {
subject.persist({ providerType: 'Local' }, function(err, id) {
ids.push(id.toString());
done();
});
});
}
add();
add();
test('multiple all calls in parallel', function(done) {
var expected = 3;
var pending = expected;
var results = [];
function complete() {
assert.lengthOf(results, expected);
var idx = 1;
for (; idx < expected; idx++) {
for (var key in results[idx]) {
assert.equal(
results[0][key],
results[idx][key],
'objects are equal: ' + key
);
}
}
}
function next(err, list) {
if (err) {
return done(err);
}
var obj = Object.create(null);
for (var key in list) {
obj[key] = list[key];
}
results.push(obj);
if (!--pending) {
done(complete);
}
}
// load in parallel
subject.all(next);
subject.all(next);
subject.all(next);
});
test('results', function(done) {
var result;
function verify() {
var keys = Object.keys(result);
var key;
assert.deepEqual(
keys.sort(),
ids.sort()
);
for (key in result) {
var obj = result[key];
assert.ok(subject._cached[key]);
assert.ok(obj._id);
assert.equal(obj.providerType, 'Local');
}
}
subject.all(function(err, data) {
if (err) {
done(err);
}
result = data;
done(verify);
});
});
});
suite('#_objectData', function() {
test('with toJSON', function() {
var obj = {};
obj.toJSON = function() {
return 'foo';
};
assert.equal(subject._objectData(obj), 'foo');
});
test('without toJSON', function() {
var obj = Object.create(null);
obj.foo = '1';
assert.equal(subject._objectData(obj), obj);
});
});
});
});

View file

@ -0,0 +1,695 @@
define(function(require) {
'use strict';
var Abstract = require('store/abstract');
var AccountModel = require('models/account');
var CalendarError = require('common/error');
var CalendarModel = require('models/calendar');
var CalendarStore = require('store/calendar');
var Factory = require('test/support/factory');
suite('store/account', function() {
var subject;
var db;
var providerFactory;
var calendarStore;
setup(function(done) {
var core = testSupport.calendar.core();
db = core.db;
providerFactory = core.providerFactory;
subject = core.storeFactory.get('Account');
calendarStore = core.storeFactory.get('Calendar');
db.open(function(err) {
assert.ok(!err);
done();
});
});
teardown(function(done) {
subject._clearCache();
calendarStore._clearCache();
testSupport.calendar.clearStore(
db,
['accounts', 'calendars'],
done
);
});
teardown(function() {
db.close();
});
test('initialization', function() {
assert.instanceOf(subject, Abstract);
assert.deepEqual(subject._cached, {});
});
suite('#markWithError', function() {
var accounts = testSupport.calendar.dbFixtures(
'account',
'Account', {
one: { _id: 55, providerType: 'Mock' }
}
);
testSupport.calendar.dbFixtures(
'calendar',
'Calendar', {
one: { _id: 'one', accountId: 55 },
two: { _id: 'two', accountId: 55 }
}
);
var model;
setup(function() {
model = accounts.one;
});
suite('marking error', function() {
var error;
setup(function(done) {
error = new CalendarError.Authentication();
subject.markWithError(model, error, done);
});
function markedWithError(expectedCount) {
expectedCount = expectedCount || 1;
test('after erorr mark #' + expectedCount, function(done) {
subject.get(model._id, function(getErr, result) {
done(function() {
assert.ok(!getErr, 'is successful');
assert.equal(result.error.count, expectedCount, 'has count');
assert.equal(
result.error.name,
error.name,
'model is marked with error'
);
assert.instanceOf(
result.error.date,
Date,
'has date of occurrence'
);
});
});
});
}
markedWithError(1);
suite('second mark', function() {
setup(function(done) {
subject.markWithError(model, error, done);
});
markedWithError(2);
});
});
suite('dependant calendars', function() {
var err;
setup(function(done) {
err = new CalendarError.Authentication();
subject.markWithError(model, err, done);
});
function verifyCalendar(key) {
test('ensure calendar is marked: ' + key, function(done) {
calendarStore.get(key, function(getErr, result) {
done(function() {
assert.ok(result, 'has calendar');
assert.ok(result.error, 'sets error');
assert.equal(result.error.name, err.name, 'sets error');
});
});
});
}
verifyCalendar('one');
verifyCalendar('two');
});
});
suite('#availablePresets', function() {
var presetAccount;
var presets = {
'multiUse': {},
'singleUse': {
singleUse: true
}
};
test('without single use', function(done) {
subject.availablePresets({ 'foo': {} }, function(err, available) {
if (err) {
done();
}
done(function() {
assert.deepEqual(available, ['foo']);
});
});
});
test('when single use is available', function(done) {
subject.availablePresets(presets, function(err, available) {
if (err) {
done(err);
}
done(function() {
assert.deepEqual(available, ['multiUse', 'singleUse']);
});
});
});
suite('when single use is used', function() {
setup(function(done) {
// stage account
presetAccount = Factory('account', {
preset: 'singleUse'
});
subject.persist(presetAccount, done);
});
test('exclusion of single use preset', function(done) {
subject.availablePresets(presets, function(err, available) {
if (err) {
return done(err);
}
done(function() {
assert.deepEqual(available, ['multiUse']);
});
});
});
});
});
suite('#verifyAndPersist', function() {
var error;
var result;
var model;
var calledWith;
var modelParams;
var originalCaldav;
setup(function() {
error = null;
result = {};
modelParams = Factory.build('account', {
providerType: 'Caldav'
});
model = new AccountModel(modelParams);
originalCaldav = providerFactory.providers.Caldav;
providerFactory.providers.Caldav = {
getAccount: function(details, callback) {
calledWith = details;
setTimeout(function() {
callback(error, result);
}, 0);
}
};
});
teardown(function() {
providerFactory.providers.Caldav = originalCaldav;
});
suite('duplicate account failure', function() {
var existingAccount;
var existingParams = {
providerType: 'Caldav',
user: 'foobar',
fullUrl: 'http://google.com/foo'
};
function sendsDuplicateError(done) {
return function(err, id, model) {
done(function() {
assert.ok(!id, 'is not persisted');
assert.ok(err, 'sends error on duplicate account');
assert.equal(err.name, 'account-exist');
});
};
}
setup(function(done) {
existingAccount = Factory('account', existingParams);
subject.persist(existingAccount, done);
});
test('initial input is duplicate', function(done) {
var account = Factory('account', existingParams);
subject.verifyAndPersist(account, sendsDuplicateError(done));
});
test('input is updated to be duplicate', function(done) {
var account = Factory('account', existingParams);
account.user = '';
result = { user: existingParams.user };
subject.verifyAndPersist(account, sendsDuplicateError(done));
});
});
suite('existing account', function() {
// setup an account to modify
setup(function(done) {
model.error = {};
subject.persist(model, done);
});
// bug 870512
test('should be able to update', function(done) {
result = {};
model.password = 'new';
subject.verifyAndPersist(model, function(err, data) {
if (err) {
return done(err);
}
subject.get(model._id, function(getErr, result) {
if (getErr) {
return done(err);
}
done(function() {
assert.equal(result.password, 'new', 'updates pass');
assert.ok(!result.error, 'clears errors');
});
});
});
});
});
// mock out the provider
test('when verify fails', function(done) {
error = new Error('bad stuff');
subject.verifyAndPersist(model, function(err, data) {
done(function() {
assert.ok(!data);
assert.equal(err, error);
assert.deepEqual(calledWith, model.toJSON());
});
});
});
test('persist + url change', function(done) {
result = Factory('caldav.account');
subject.verifyAndPersist(model, function(err, id, data) {
done(function() {
assert.instanceOf(data, AccountModel);
assert.equal(data.domain, result.domain);
assert.equal(data.entrypoint, result.entrypoint);
assert.equal(data.calendarHome, result.calendarHome);
});
});
});
test('persist + oauth change', function(done) {
model.oauth = { code: 'xxx' };
result = Factory('caldav.account', {
oauth: { refresh_token: 'xxx' }
});
subject.verifyAndPersist(model, function(err, id, data) {
done(function() {
assert.equal(data.oauth, result.oauth);
});
});
});
test('persist no change', function(done) {
result = {};
subject.verifyAndPersist(model, function(err, id, data) {
done(function() {
assert.instanceOf(data, AccountModel);
assert.equal(data.domain, modelParams.domain);
assert.equal(data.calendarHome, modelParams.calendarHome);
});
});
});
});
suite('#remove', function() {
var model;
var calendars;
setup(function(done) {
calendars = {};
model = subject._createModel({ providerType: 'Local' });
subject.persist(model, done);
});
setup(function(done) {
assert.ok(model._id);
// we will eventually remove this
calendars[1] = new CalendarModel({
accountId: model._id,
remote: { id: 777 }
});
calendarStore.persist(calendars[1], done);
});
setup(function(done) {
calendars[2] = new CalendarModel({
accountId: 'some-other',
remote: { id: 666 }
});
// this is our control to ensure
// we are not removing extra stuff
calendarStore.persist(calendars[2], done);
});
suite('removal', function() {
var id;
setup(function(done) {
id = model._id;
subject.remove(id, done);
});
test('removes account', function(done) {
subject.get(id, function(err, result) {
done(function() {
assert.ok(!result, 'removes account');
});
});
});
test('removes associated calendars', function(done) {
calendarStore.all(function(err, calendars) {
done(function() {
assert.ok(calendars.accountId != id, 'removes calendars');
});
});
});
});
});
suite('#_createModel', function() {
test('with id', function() {
var result = subject._createModel({ providerType: 'Local' }, 'id');
assert.equal(result.providerType, 'Local');
assert.equal(result._id, 'id');
assert.instanceOf(result, AccountModel);
});
test('without id', function() {
var result = subject._createModel({ providerType: 'Local' });
assert.equal(result.providerType, 'Local');
assert.isFalse(('_id' in result));
});
});
suite('#syncableAccounts', function() {
var accounts = testSupport.calendar.dbFixtures(
'account',
'Account', {
nosync: { _id: 55, providerType: 'Local' },
sync: { _id: 56, providerType: 'Caldav' }
}
);
var results;
setup(function(done) {
subject.syncableAccounts(function(err, list) {
if (err) {
return done(err);
}
results = list;
done();
});
});
test('found accounts', function() {
assert.lengthOf(results, 1);
assert.equal(results[0]._id, accounts.sync._id);
});
suite('no syncable accounts', function() {
setup(function(done) {
subject.remove(accounts.sync._id, done);
});
test('result', function(done) {
subject.syncableAccounts(function(err, list) {
if (err) {
return done(err);
}
assert.equal(list.length, 0);
done();
});
});
});
});
suite('#sync: add, remove, update', function() {
var remote;
var events;
var account;
var cals;
var remoteCalledWith;
function watchEvent(eventName) {
calendarStore.on(eventName, function() {
if (!(eventName in events)) {
events[eventName] = [];
}
events[eventName].push(arguments);
});
}
setup(function() {
account = Factory.create('account', {
_id: 1,
providerType: 'Mock'
});
cals = {};
cals.add = Factory('calendar', {
accountId: account._id,
remote: { name: 'add' }
});
cals.add2 = Factory('calendar', {
accountId: account._id,
remote: { name: 'add2' }
});
cals.remove = Factory('calendar', {
accountId: account._id,
remote: { name: 'remove' }
});
cals.update = Factory('calendar', {
accountId: account._id,
// this color won't be used since it is not part of the palette (test
// case where user is updating the app and already have calendars
// stored in the DB)
remote: { name: 'update', color: '#00FFCC' },
error: {}
});
});
setup(function(done) {
calendarStore.persist(cals.update, done);
});
setup(function(done) {
calendarStore.persist(cals.remove, done);
});
setup(function(done) {
// clear cache
calendarStore._remoteByAccount = Object.create(null);
calendarStore._cached = Object.create(null);
calendarStore._usedColors = [];
// reload from db
calendarStore.all(done);
});
setup(function(done) {
events = {};
remoteCalledWith = null;
watchEvent('add');
watchEvent('update');
watchEvent('remove');
remote = {};
remote[cals.update.remote.id] = {
id: cals.update.remote.id,
name: 'update!',
description: 'new desc',
// this color will be ignored
color: '#F00'
};
remote[cals.add.remote.id] = {
id: cals.add.remote.id,
name: 'new item'
};
remote[cals.add2.remote.id] = {
id: cals.add2.remote.id,
name: 'add 2 calendar',
// this color will be ignored
color: '#0FC'
};
providerFactory.get('Mock').stageFindCalendars(
account.user,
null,
remote
);
subject.sync(account, function(err) {
if (err) {
return done(err);
}
done();
});
});
var syncResults;
setup(function(done) {
calendarStore.remotesByAccount(account._id, function(err, list) {
done(function() {
syncResults = list;
});
});
});
test('after sync', function() {
assert.equal(
Object.keys(syncResults).length, 3,
'should only have three records'
);
// EVENTS
assert.ok(events.remove[0][0]);
var updateObj = events.update[0][1].remote;
assert.equal(
updateObj.id,
cals.update.remote.id
);
var addObj = events.add[0][1].remote;
assert.equal(
addObj.id,
cals.add.remote.id
);
var add2Obj = events.add[1][1].remote;
assert.equal(
add2Obj.id,
cals.add2.remote.id
);
var remoteUpdate = syncResults[cals.update.remote.id];
var remoteAdd = syncResults[cals.add.remote.id];
var remoteAdd2 = syncResults[cals.add2.remote.id];
var palette = CalendarStore.REMOTE_COLORS;
// update
assert.instanceOf(
remoteUpdate,
CalendarModel,
'should update cache'
);
assert.ok(!remoteUpdate.error, 'removes error');
assert.equal(
remoteUpdate.remote.description,
'new desc',
'should update changed descripton'
);
assert.equal(
remoteUpdate.name,
'update!',
'should update changed name'
);
assert.equal(
remoteUpdate.color,
palette[0],
'should ignore color from remote and only use colors from palette'
);
// add
assert.instanceOf(
remoteAdd,
CalendarModel,
'should add new calendar'
);
assert.equal(
remoteAdd.name,
'new item',
'should use remote data when creating new calendar'
);
assert.equal(
remoteAdd.color,
palette[1],
'should add new color from palette'
);
// add 2
assert.instanceOf(
remoteAdd,
CalendarModel,
'should add new calendar'
);
assert.equal(
remoteAdd2.name,
'add 2 calendar',
'should use remote data when creating new calendar'
);
assert.equal(
remoteAdd2.color,
palette[2],
'should add new color from palette'
);
assert.notEqual(
remoteAdd.color,
remoteAdd2.color,
'each calendar should use a different color'
);
});
});
});
});

View file

@ -0,0 +1,438 @@
define(function(require) {
'use strict';
var Abstract = require('store/abstract');
var Calc = require('common/calc');
var Factory = require('test/support/factory');
var Responder = require('common/responder');
var notificationsController = require('controllers/notifications');
suite('store/alarm', function() {
var core;
var subject;
var db;
setup(function(done) {
core = testSupport.calendar.core();
db = core.db;
subject = core.storeFactory.get('Alarm');
db.open(done);
});
teardown(function(done) {
testSupport.calendar.clearStore(
core.db,
['alarms'],
done
);
});
teardown(function() {
db.close();
});
suite('#findAllByBusytimeId', function() {
suite('existing', function() {
var alarms;
var busytimeId = 'xfoo';
var busytime = { _id: busytimeId };
setup(function(done) {
alarms = [];
var trans = db.transaction('alarms', 'readwrite');
for (var i = 0; i < 3; i++) {
var alarm = Factory('alarm', { busytimeId: busytimeId });
alarms.push(alarm);
subject.persist(alarm, trans);
}
trans.oncomplete = function() {
done();
};
trans.onerror = function(e) {
done(e.target.error);
};
});
test('result', function(done) {
subject.findAllByBusytimeId(busytime._id, function(err, result) {
done(function() {
assert.ok(!err);
assert.equal(result.length, alarms.length);
});
});
});
});
/*
Error: expected false to be truthy (for the second assert)
this works when executing only this file but not when executing all
calendar tests.
Disabled in Bug 838993, to be enabled asap in Bug 840489
test('missing', function(done) {
subject.findAllByBusytimeId('foo', function(err, result) {
try {
assert.ok(!err);
assert.ok(!result);
done();
} catch (e) {
done(e);
}
});
});
*/
});
suite('#_objectData', function() {
var alarm;
var time = new Date(2018, 0, 1);
setup(function(done) {
alarm = Factory('alarm', { _id: 'a1', startDate: time });
subject.persist(alarm, done);
});
test('get', function(done) {
subject.get('a1', function(err, result) {
done(function() {
assert.ok(result.startDate);
assert.deepEqual(
result.trigger,
result.startDate,
'pending trigger should be same date as trigger'
);
});
});
});
});
suite('#_addDependents', function() {
var worksQueue = 0;
var auto;
var originalWork;
setup(function() {
worksQueue = 0;
auto = subject.autoQueue;
originalWork = subject.workQueue;
subject.autoQueue = true;
subject.workQueue = function() {
worksQueue++;
};
});
teardown(function() {
subject.autoQueue = auto;
subject.workQueue = originalWork;
});
test('after persist transaction', function(done) {
var trans = core.db.transaction('alarms', 'readwrite');
subject.persist({}, trans);
subject.persist({}, trans);
subject.persist({}, trans);
subject.persist({}, trans);
trans.addEventListener('complete', function() {
done(function() {
assert.equal(worksQueue, 1);
});
});
});
});
test('initialization', function() {
assert.instanceOf(subject, Abstract);
assert.deepEqual(subject._cached, {});
});
function add(date, busytime, event, floating) {
setup(function(done) {
var record = Factory('alarm', {
startDate: date,
busytimeId: busytime || 'busyId',
eventId: event || 'eventId'
});
if (floating) {
record.startDate.offset = 0;
record.startDate.tzid = Calc.FLOATING;
}
subject.persist(record, done);
});
}
suite('#workQueue', function() {
var getAllResults = [];
var added = [];
var lastId = 0;
var now = new Date(2018, 0, 1);
var realApi;
var mockApi = {
getAll: function() {
var req = new Responder();
setTimeout(function() {
req.result = getAllResults.concat([]);
var event = {
target: req
};
if (req.onsuccess) {
req.onsuccess(event);
}
req.emit('success', event);
}, 1);
return req;
},
add: function(date, tz, data) {
added.push(Array.prototype.slice.call(arguments));
var req = new Responder();
setTimeout(function() {
lastId++;
if (req.onsuccess) {
req.onsuccess(lastId);
}
req.emit('success', lastId);
}, 1);
return req;
}
};
suiteSetup(function() {
realApi = navigator.mozAlarms;
});
suiteTeardown(function() {
navigator.mozAlarms = realApi;
});
var allAlarms, handleAlarm, onAlarm;
setup(function() {
allAlarms = [];
handleAlarm = null;
added.length = 0;
getAllResults.length = 0;
navigator.mozAlarms = mockApi;
onAlarm = notificationsController.onAlarm;
notificationsController.onAlarm = function() {
handleAlarm = arguments;
allAlarms.push(handleAlarm);
};
});
teardown(function() {
// Restore notificationsController.onAlarm
notificationsController.onAlarm = onAlarm;
});
suite('without alarm api', function() {
test('should silently stop', function(done) {
navigator.mozAlarms = null;
subject.workQueue(function() {
done();
});
});
});
suite('alarm in the past', function() {
var now = new Date();
now.setMilliseconds(-1);
add(now, 'busyMe');
// Bug 857284 - We should only fire one alarm when there are multiple
// valid alarms for a single event.
var duplicate = new Date();
duplicate.setMilliseconds(-61);
add(duplicate, 'busyMe');
setup(function(done) {
subject.workQueue(() => done());
});
test('passing alarm directly to controller', function() {
var alarm = handleAlarm[0];
// verify right alarm is sent
assert.equal(alarm.busytimeId, 'busyMe');
// verify only one alarm sent
assert.lengthOf(allAlarms, 1);
});
});
suite('alarm in db and no alarm with 48 hours', function() {
add(new Date(2018, 0, 3), 3);
setup(function(done) {
getAllResults.push({
data: Factory('alarm', {
trigger: new Date(),
eventId: 'xx'
})
});
subject.workQueue(now, () => done());
});
test('after', function() {
assert.lengthOf(added, 0);
});
});
suite('unrelated alarm in db', function() {
add(new Date(2018, 0, 3), 3);
setup(function(done) {
getAllResults.push({
data: { _randomField: true }
});
subject.workQueue(now, () => done());
});
test('after complete', function() {
assert.lengthOf(added, 1);
assert.deepEqual(
added[0][0],
new Date(2018, 0, 3)
);
});
});
suite('no alarm in db and no alarm within 48 hours', function() {
add(new Date(2018, 0, 3), 3);
setup(function(done) {
subject.workQueue(now, () => done());
});
test('after complete', function() {
assert.lengthOf(added, 1);
assert.deepEqual(
added[0][0],
new Date(2018, 0, 3)
);
});
});
suite('initial add', function() {
// 5 hours from now floating time
add(new Date(2018, 0, 1, 5), 1, 1, true);
// 23 hours from now
add(new Date(2018, 0, 1, 23), 2);
// over 48 hours from now (yes this works)
// this record should be skipped and left
// in the database.
add(new Date(2018, 0, 3), 3);
setup(function(done) {
subject.workQueue(now, () => done());
});
/*
was disabled by Bug 838993
to be reenabled by Bug 840489
Error: has record: expected [ { _id: 1, busytimeId: 'foo' },
{ startDate: { utc: 1514782800000, offset: 0, tzid: 'floating' },
busytimeId: 1,
eventId: 1,
_id: 40 },
{ startDate: { utc: 1514847600000, offset: 3600000 },
busytimeId: 2,
eventId: 'eventId',
_id: 41 },
{ startDate: { utc: 1514937600000, offset: 3600000 },
busytimeId: 3,
eventId: 'eventId',
trigger: { utc: 1514937600000, offset: 3600000 },
_id: 42 } ] to have a length of 3 but got 4
note: this works when running only this file, but not when we run the
whole calendar tests
test('alarms', function(done) {
getAll(function(records) {
done(function() {
assert.length(records, 3, 'has record');
var hasTrigger = records.filter(function(item) {
return 'trigger' in item;
});
assert.length(hasTrigger, 1);
assert.equal(hasTrigger[0].busytimeId, 3);
});
});
});
*/
test('after complete', function() {
assert.lengthOf(added, 2);
assert.deepEqual(added[0][0], new Date(2018, 0, 1, 5));
assert.equal(
added[0][1], 'ignoreTimezone', 'floating time ignoreTimezone'
);
assert.hasProperties(added[0][2], {
busytimeId: 1
});
assert.deepEqual(added[1][0], new Date(2018, 0, 1, 23), 'abs trigger');
assert.equal(added[1][1], 'honorTimezone', 'abs time honorTimezone');
assert.hasProperties(added[1][2], {
busytimeId: 2
});
});
/*
Error: expected 4 to equal 3
this works when executing only this file but not when executing all
calendar tests.
Disabled in Bug 838993, to be enabled asap in Bug 840489
test('second work queue', function(done) {
added.length = 0;
subject.workQueue(function() {
// verify add
assert.equal(added[0][2].busytimeId, 3);
// verify we didn't delete the records
subject.count(function(err, len) {
done(function() {
assert.equal(len, 3);
});
});
});
});
*/
});
});
});
});

View file

@ -0,0 +1,286 @@
define(function(require) {
'use strict';
var Factory = require('test/support/factory');
var Timespan = require('common/timespan');
var core = require('core');
suite('store/busytime', function() {
var subject;
var db;
var id = 0;
setup(function(done) {
id = 0;
db = core.db;
subject = core.storeFactory.get('Busytime');
db.open(function(err) {
assert.ok(!err);
done();
});
});
teardown(function(done) {
testSupport.calendar.clearStore(
core.db,
done
);
});
teardown(function() {
core.db.close();
});
suite('#_removeDependents', function() {
suite('alarm store deps', function() {
var alarmStore;
var busytime;
function createTrans(done) {
var trans = core.db.transaction(
['busytimes', 'alarms'], 'readwrite'
);
if (done) {
trans.addEventListener('complete', function() {
done();
});
}
}
// create records
setup(function(done) {
var trans = createTrans(done);
alarmStore = core.storeFactory.get('Alarm');
busytime = Factory('busytime', { _id: 'foo' });
subject.persist(busytime, trans);
alarmStore.persist({ _id: 1, busytimeId: busytime._id }, trans);
});
test('count check', function(done) {
var pending = 2;
var alarmCount = 0;
var busytimeCount = 0;
function next() {
if (!(--pending)) {
done(function() {
assert.equal(busytimeCount, 1, 'busytime');
assert.equal(alarmCount, 1, 'alarm');
});
}
}
subject.count(function(err, value) {
busytimeCount = value;
next();
});
alarmStore.count(function(err, value) {
alarmCount = value;
next();
});
});
test('after delete', function(done) {
subject.remove(busytime._id, function() {
alarmStore.count(function(err, value) {
done(function() {
assert.equal(value, 0, 'removes alarm');
});
});
});
});
});
});
suite('#loadSpan', function() {
var list;
var span;
setup(function() {
list = Object.create(null);
span = new Timespan(
new Date(2012, 1, 5),
new Date(2012, 1, 10)
);
});
function add(name, start, end) {
setup(function(done) {
var item = list[name] = Factory('busytime', {
startDate: start,
endDate: end
});
delete item.startDate;
delete item.endDate;
subject.persist(item, done);
});
}
add('before long', new Date(2011, 1, 1), new Date(2011, 3, 1));
add('overlap', new Date(2012, 1, 1), new Date(2013, 1, 1));
add('starts before', new Date(2012, 1, 3), new Date(2012, 1, 6));
add('during', new Date(2012, 1, 5), new Date(2012, 1, 9));
add('ends after', new Date(2012, 1, 9), new Date(2012, 1, 11));
add('after', new Date(2012, 1, 12), new Date(2012, 1, 15));
var results;
var expectedIds;
function expected(name) {
expectedIds.push(
list[name]._id
);
}
setup(function(done) {
// because we just added events
// we need to remove them from the cache
subject._setupCache();
// build the list of expected
// busytimes to be returned by their
// event id
expectedIds = [];
// order is important we expect them
// to be sorted by start date
expected('overlap');
expected('starts before');
expected('during');
expected('ends after');
// load
subject.loadSpan(span, function(err, data) {
if (err) {
return done(err);
}
results = data;
// wait until next tick for event
// to fire...
setTimeout(done, 0, null);
});
});
test('load results', function() {
// verify correct data is returned;
var idMap = Object.create(null);
results.forEach(function(item) {
// verify startDate/endDate is present
var model = JSON.parse(JSON.stringify(item));
model = subject.initRecord(model);
assert.hasProperties(
item,
model,
'is model'
);
var id = item._id;
if (!(id in idMap)) {
idMap[id] = true;
}
});
var actualIds = Object.keys(idMap);
assert.deepEqual(
actualIds,
expectedIds,
'load event ids'
);
});
});
suite('#_createModel', function() {
var start = new Date(2012, 7, 1);
var end = new Date(2012, 7, 8);
var busy;
setup(function(done) {
busy = Factory('busytime', {
startDate: start,
endDate: end
});
subject.persist(busy, done);
});
test('db-round trip', function(done) {
subject.get(busy._id, function(err, record) {
done(function() {
assert.deepEqual(record.startDate, start, 'startDate');
assert.deepEqual(record.endDate, end, 'endDate');
});
});
});
});
suite('#removeEvent', function() {
var remove;
var keep;
var removeEvents = [];
setup(function(done) {
removeEvents.length = 0;
var trans = core.db.transaction(
'busytimes', 'readwrite'
);
remove = Factory('busytime', {
eventId: 'remove'
});
keep = Factory('busytime', {
eventId: 'keep'
});
subject.persist(remove, trans);
subject.persist(keep, trans);
trans.oncomplete = function() {
done();
};
});
setup(function(done) {
subject.on('remove', function(id) {
removeEvents.push(id);
});
subject.removeEvent('remove', done);
});
test('removed busytime', function(done) {
assert.deepEqual(
removeEvents,
[remove._id]
);
subject.get(remove._id, function(err, record) {
done(function() {
assert.ok(!record);
});
});
});
test('control - kept busytime', function(done) {
subject.get(keep._id, function(err, record) {
done(function() {
assert.ok(record);
});
});
});
});
});
});

View file

@ -0,0 +1,459 @@
define(function(require) {
'use strict';
var Abstract = require('store/abstract');
var AccountModel = require('models/account');
var CalendarError = require('common/error');
var CalendarModel = require('models/calendar');
var CalendarStore = require('store/calendar');
var Factory = require('test/support/factory');
var core = require('core');
var localCalendarId = require('common/constants').localCalendarId;
suite('store/calendar', function() {
var subject;
var db;
var model;
setup(function(done) {
db = core.db;
subject = core.storeFactory.get('Calendar');
model = Factory('calendar', {
_id: 1,
remote: { id: 'uuid' },
accountId: 'acc1'
});
db.open(function(err) {
assert.ok(!err);
done();
});
});
testSupport.calendar.accountEnvironment();
teardown(function(done) {
testSupport.calendar.clearStore(
db,
['accounts', 'calendars', 'events', 'busytimes'],
done
);
});
teardown(function() {
db.close();
});
test('initialization', function() {
assert.instanceOf(subject, Abstract);
assert.equal(subject._store, 'calendars');
});
suite('cache handling', function() {
setup(function() {
subject._addToCache(model);
});
test('#_addToCache', function() {
assert.equal(subject._cached[1], model);
});
test('#_removeFromCache', function() {
subject._removeFromCache(1);
assert.ok(!subject._cached[1]);
});
});
suite('#markWithError', function() {
var calendar;
setup(function(done) {
calendar = Factory('calendar');
subject.persist(calendar, done);
});
test('success', function(done) {
var err = new CalendarError.Authentication();
subject.markWithError(calendar, err, function(markErr) {
assert.ok(!markErr);
subject.get(calendar._id, function(getErr, result) {
done(function() {
assert.ok(result.error, 'has error');
assert.equal(result.error.name, err.name, 'set error');
});
});
});
});
});
suite('#persist', function() {
var stubUpdateColor;
setup(function() {
stubUpdateColor = sinon.stub(subject, '_updateCalendarColor');
});
teardown(function() {
stubUpdateColor.restore();
});
test('error case', function(done) {
var sample = Factory.create('calendar');
subject.persist(sample, function(err, data) {
sinon.assert.calledOnce(stubUpdateColor);
sinon.assert.calledWith(stubUpdateColor, sample);
done();
});
});
});
suite('#_createModel', function() {
var remote = {};
test('with id', function() {
var result = subject._createModel({
remote: remote
}, 'id');
assert.equal(result.remote, remote);
assert.equal(result._id, 'id');
assert.instanceOf(result, CalendarModel);
});
test('without id', function() {
var result = subject._createModel({
remote: remote
});
assert.equal(result.remote, remote);
assert.isFalse(('_id' in result));
});
});
suite('#remotesByAccount', function() {
var expected;
var models = testSupport.calendar.dbFixtures('calendar', 'Calendar', {
one: { accountId: 1, remote: { id: 'one' } },
two: { accountId: 1, remote: { id: 'two' } },
three: { accountId: 2, remote: { id: 'three' } }
});
setup(function(done) {
subject.persist(model, done);
});
function verify(accountId, done) {
subject.remotesByAccount(accountId, function(err, list) {
if (err) {
done(err);
}
done(function() {
var expectedIds = Object.keys(expected).sort();
assert.deepEqual(
Object.keys(list).sort(),
expectedIds,
'has same keys'
);
expectedIds.forEach(function(id) {
assert.hasProperties(
expected[id],
list[id],
id
);
});
});
});
}
test('one calendar', function(done) {
expected = {
three: models.three
};
verify(2, done);
});
test('no calendars', function(done) {
expected = {};
verify(3, done);
});
test('multiple calendars', function(done) {
expected = {
one: models.one,
two: models.two
};
verify(1, done);
});
});
suite('#sync - provider skip', function() {
var account, calendar;
setup(function() {
account = Factory('account', { providerType: 'Local' });
calendar = Factory('calendar', {
_id: 1,
lastEventSyncToken: null,
remote: { syncToken: 'synced' }
});
});
setup(function(done) {
core.storeFactory.get('Account').persist(account, done);
});
setup(function(done) {
subject.persist(calendar, done);
});
test('result', function(done) {
// should not sync because local cannot sync
subject.sync(account, calendar, function() {
done();
});
});
});
suite('#remove', function() {
var eventStore;
var stubRemoveColor;
var model;
var events;
setup(function(done) {
// setup fixtures
eventStore = core.storeFactory.get('Event');
events = {};
// transaction for initial creation of records.
var trans = core.db.transaction(
subject._dependentStores,
'readwrite'
);
// setup calendars
model = Factory('calendar', { accountId: 1 });
subject.persist(model, trans);
// setup events
events[1] = Factory('event', { calendarId: model._id });
events[2] = Factory('event', { calendarId: 'some-other' });
// we will eventually remove this record.
eventStore.persist(events[1], trans);
// this is our control to ensure
// we are not removing extra stuff
eventStore.persist(events[2], done);
trans.addEventListener('complete', function() {
done();
});
});
setup(function() {
stubRemoveColor = sinon.stub(subject, '_removeCalendarColorFromCache');
});
teardown(function() {
stubRemoveColor.restore();
});
setup(function(done) {
subject.remove(model._id, function() {
eventStore.count(function(err, count) {
assert.equal(count, 1);
sinon.assert.calledOnce(stubRemoveColor);
sinon.assert.calledWith(stubRemoveColor, model._id);
done();
});
});
});
test('after remove', function(done) {
eventStore.get(events[1]._id, function(err, result) {
done(function() {
assert.ok(!result);
});
});
});
});
suite('#ownersOf', function() {
test('given an id', function(done) {
var id = this.calendar._id;
subject.ownersOf(id, (err, owners) => {
done(() => {
assert.instanceOf(owners.account, AccountModel);
assert.instanceOf(owners.calendar, CalendarModel);
assert.equal(owners.account._id, this.account._id, 'account id');
assert.equal(owners.calendar._id, this.calendar._id, 'calendar id');
});
});
});
});
test('#providerFor', function(done) {
subject.providerFor(this.calendar, function(err, provider) {
done(function() {
assert.equal(provider, core.providerFactory.get('Mock'));
});
});
});
suite('#_updateCalendarColor', function(done) {
var palette = CalendarStore.REMOTE_COLORS;
function resetUsedColors() {
subject._usedColors.length = 0;
}
setup(resetUsedColors);
teardown(resetUsedColors);
test('> local calendar', function() {
var calendar = Factory('calendar', {
color: '#BADA55',
_id: localCalendarId
});
subject._updateCalendarColor(calendar);
assert.equal(
calendar.color,
CalendarStore.LOCAL_COLOR,
'should use local calendar color'
);
});
suite('> remote calendars', function() {
suite('> add', function() {
test('return first unused color from palette', function() {
assert.ok(palette.length, 'palette');
palette.forEach(function(color) {
assert.ok(
subject._usedColors.indexOf(color) === -1,
'should not repeat color ' + color
);
// it will ignore color from the remote and use color from palette
// instead
var calendar = { color: '#00FFCC' };
subject._updateCalendarColor(calendar);
assert.equal(
calendar.color,
color,
'color should match'
);
assert.ok(
subject._usedColors.indexOf(color) !== -1,
'should update the used colors ' + color
);
});
});
});
suite('> many calendars', function() {
setup(function() {
subject._usedColors = palette.slice(0, palette.length - 1);
});
test('loop colors if too many calendars', function() {
var calendar;
calendar = {};
subject._updateCalendarColor(calendar);
assert.equal(
calendar.color,
palette[palette.length - 1],
'last color'
);
calendar = {};
subject._updateCalendarColor(calendar);
assert.equal(
calendar.color,
palette[0],
'first color'
);
calendar = {};
subject._updateCalendarColor(calendar);
assert.equal(
calendar.color,
palette[1],
'second color'
);
});
});
suite('> update', function() {
var foo, bar;
setup(function() {
foo = { color: palette[3], _id: 'foo' };
bar = { color: '#F00', _id: 'bar' };
subject._cached.foo = foo;
subject._cached.bar = bar;
subject._usedColors = [foo.color, bar.color];
});
teardown(function() {
delete subject._cached.foo;
delete subject._cached.bar;
});
teardown(resetUsedColors);
test('keep previous color if from palette', function() {
subject._updateCalendarColor(foo);
assert.equal(
foo.color,
palette[3],
'should keep same color'
);
assert.deepEqual(
subject._usedColors,
[bar.color, foo.color],
'should keep _usedColors in sync'
);
});
test('override color if not from palette', function() {
// this test simulates an update on the color scheme and/or old
// calendars (stored in the DB before the 2.0 visual refresh)
subject._updateCalendarColor(bar);
assert.equal(
bar.color,
palette[0],
'should use first color from palette'
);
assert.deepEqual(
subject._usedColors,
[foo.color, bar.color],
'should keep _usedColors in sync'
);
});
});
});
});
});
});

View file

@ -0,0 +1,330 @@
define(function(require) {
'use strict';
var Abstract = require('store/abstract');
var AccountModel = require('models/account');
var Calc = require('common/calc');
var CalendarModel = require('models/calendar');
var Factory = require('test/support/factory');
var core = require('core');
suite('store/event', function() {
var subject;
var db;
var id = 0;
function event(date) {
if (typeof(date) === 'undefined') {
date = new Date();
}
return Factory('event', { remote: { startDate: date, _id: ++id } });
}
setup(function(done) {
id = 0;
db = core.db;
subject = core.storeFactory.get('Event');
db.open(function(err) {
assert.ok(!err);
done();
});
});
testSupport.calendar.accountEnvironment();
teardown(function(done) {
testSupport.calendar.clearStore(
core.db,
[
'accounts', 'calendars', 'events',
'busytimes', 'icalComponents'
],
function() {
db.close();
done();
}
);
});
test('initialization', function() {
assert.instanceOf(subject, Abstract);
assert.equal(subject._store, 'events');
});
test('#_createModel', function() {
var input = Factory.build('event');
var output = subject._createModel(input, 1);
assert.equal(output._id, 1);
assert.equal(output.name, output.name);
assert.deepEqual(
output.remote.startDate,
Calc.dateFromTransport(output.remote.start),
'startDate'
);
assert.deepEqual(
output.remote.endDate,
Calc.dateFromTransport(output.remote.end),
'endDate'
);
});
suite('#(x)For', function() {
var event;
setup(function(done) {
event = Factory('event', {
calendarId: this.calendar._id
});
subject.persist(event, done);
});
test('#ownersOf', function(done) {
subject.ownersOf(event, (err, owners) => {
done(() => {
assert.instanceOf(owners.account, AccountModel);
assert.instanceOf(owners.calendar, CalendarModel);
assert.equal(owners.account._id, this.account._id, 'account id');
assert.equal(owners.calendar._id, this.calendar._id, 'calendar id');
});
});
});
test('#providerFor', function(done) {
subject.providerFor(event, function(err, provider) {
assert.equal(
provider,
core.providerFactory.get('Mock')
);
done();
});
});
});
suite('#eventsForCalendar', function() {
var inCal;
var outCal;
setup(function(done) {
inCal = Factory('event', {
calendarId: 1
});
subject.persist(inCal, done);
});
setup(function(done) {
outCal = Factory('event', {
calendarId: 2
});
subject.persist(outCal, done);
});
test('result', function(done) {
subject.eventsForCalendar(1, function(err, result) {
done(function() {
assert.ok(!err);
assert.deepEqual(
result,
[inCal]
);
});
});
});
});
suite('#findByIds', function() {
var events = {};
function persist() {
setup(function(done) {
var item = event();
events[item._id] = item;
subject.persist(item, done);
});
}
persist();
persist();
persist();
persist();
test('result', function(done) {
var ids = Object.keys(events);
ids.push('random-not-here');
subject.findByIds(ids, function(err, items) {
done(function() {
assert.equal(
Object.keys(items).length,
4,
'should find all items'
);
for (var id in events) {
assert.deepEqual(
items[id],
events[id],
'should find event with id ' + id
);
}
});
});
});
});
suite('#remove', function() {
//TODO: busytime removal tests?
//
suite('remove ical component', function() {
var component;
var event;
var componentStore;
setup(function(done) {
componentStore = core.storeFactory.get('IcalComponent');
event = Factory('event');
component = Factory('icalComponent', {
eventId: event._id
});
var trans = core.db.transaction(
['events', 'icalComponents'],
'readwrite'
);
trans.oncomplete = function() {
done();
};
subject.persist(event, trans);
componentStore.persist(component, trans);
});
setup(function(done) {
subject.remove(event._id, done);
});
test('after removing the event', function(done) {
componentStore.get(event._id, function(err, result) {
done(function() {
assert.ok(!result, 'removes component');
});
});
});
});
suite('parent items /w children', function() {
var parentId = 'parentStuff';
var childId = 'child';
var id = 'foobar1';
setup(function(done) {
var item = Factory('event', {
_id: parentId
});
subject.persist(item, done);
});
setup(function(done) {
subject.persist(
Factory('event', { _id: childId, parentId: id }),
done
);
});
setup(function(done) {
subject.remove(id, done);
});
test('removes parent event', function(done) {
subject.get(parentId, function() {
done(function(err, result) {
assert.ok(!err);
assert.ok(!result);
});
});
});
test('removes child event', function(done) {
subject.get(childId, function() {
done(function(err, result) {
assert.ok(!err);
assert.ok(!result);
});
});
});
});
});
suite('#removeByIndex', function() {
var busytime;
var byCalendar = {};
setup(function() {
byCalendar = {};
});
function persistEvent(calendarId) {
setup(function(done) {
var event = Factory('event', {
calendarId: calendarId
});
if (!(calendarId in byCalendar)) {
byCalendar[calendarId] = [];
}
byCalendar[calendarId].push(event._id);
subject.persist(event, done);
});
}
persistEvent(1);
persistEvent(1);
persistEvent(2);
setup(function(done) {
busytime = core.storeFactory.get('Busytime');
subject.get(byCalendar[2][0], function(err, result) {
done(function() {
assert.ok(result, 'has control event');
});
});
});
test('removed all events for 1', function(done) {
subject.removeByIndex('calendarId', 1, function() {
assert.ok(
!busytime._byEventId[byCalendar[1][0]],
'should remove events from busytime'
);
assert.ok(
!busytime._byEventId[byCalendar[1][1]],
'should remove events from busytime'
);
subject.get(byCalendar[2][0], function(err, result) {
done(function() {
assert.ok(!err);
assert.ok(result, 'should not remove control');
});
});
});
});
});
});
});

View file

@ -0,0 +1,94 @@
define(function(require) {
'use strict';
var Abstract = require('store/abstract');
var Factory = require('test/support/factory');
var core = require('core');
suite('store/ical_component', function() {
var subject;
var db;
setup(function(done) {
db = core.db;
subject = core.storeFactory.get('IcalComponent');
db.open(function(err) {
assert.ok(!err);
done();
});
});
teardown(function(done) {
testSupport.calendar.clearStore(
core.db,
['icalComponents'],
done
);
});
teardown(function() {
db.close();
});
test('initialization', function() {
assert.instanceOf(subject, Abstract);
assert.deepEqual(subject._cached, {});
});
suite('#findRecurrencesBefore', function() {
var max = new Date(2012, 3, 1);
var after;
var expected;
setup(function(done) {
var trans = db.transaction('icalComponents', 'readwrite');
trans.oncomplete = function() {
done();
};
expected = [
new Date(2012, 1, 1),
new Date(2012, 2, 1),
new Date(2012, 3, 0)
];
after = [
new Date(2012, 3, 1, 1),
new Date(2012, 4, 1)
];
var id = 0;
function persistList(list, update) {
list.forEach(function(item, idx) {
item = Factory('icalComponent', {
eventId: ++id,
lastRecurrenceId: item
});
if (update) {
list[idx] = item;
}
subject.persist(item, trans);
});
}
persistList(expected, true);
persistList(after);
});
test('found', function(done) {
subject.findRecurrencesBefore(max, function(err, list) {
done(function() {
assert.lengthOf(list, expected.length);
assert.deepEqual(list, expected);
});
});
});
});
});
});

View file

@ -0,0 +1,138 @@
define(function(require) {
'use strict';
var Abstract = require('store/abstract');
var core = require('core');
suite('store/setting', function() {
var subject;
var db;
setup(function(done) {
db = core.db;
subject = core.storeFactory.get('Setting');
db.open(function(err) {
assert.ok(!err);
done();
});
});
teardown(function(done) {
testSupport.calendar.clearStore(
core.db,
['settings'],
done
);
});
teardown(function() {
db.close();
});
test('initialization', function() {
assert.instanceOf(subject, Abstract);
assert.deepEqual(subject._cached, {});
});
function get(id, callback) {
var trans = db.transaction('settings');
var store = trans.objectStore('settings');
store.get(id).onsuccess = function(e) {
callback(e.target.result);
};
}
suite('#set', function() {
var name = 'someFoo';
var calledEvent;
setup(function(done) {
subject.on(name + 'Change', function() {
calledEvent = Array.slice(arguments);
});
subject.set(name, 'first', done);
});
test('persistence', function(done) {
get(name, function(record) {
done(function() {
assert.equal(record.value, 'first', 'has correct value');
assert.instanceOf(record.createdAt, Date, 'updatedAt');
assert.instanceOf(record.updatedAt, Date, 'createdAt');
assert.deepEqual(
calledEvent,
['first', record],
'calls ' + name + 'Change'
);
});
});
});
});
suite('#getValue', function() {
test('with a default', function(done) {
subject.getValue('syncFrequency', function(err, value) {
done(function() {
assert.equal(value, subject.defaults.syncFrequency);
assert.ok(value);
});
});
});
test('with a zero default', function(done) {
subject.defaults.someZeroDefault = 0;
subject.getValue('someZeroDefault', function(err, value) {
done(function() {
assert.equal(value, subject.defaults.someZeroDefault);
assert.equal(value, 0);
});
});
});
suite('with value', function() {
setup(function(done) {
subject.set('syncFrequency', 200, done);
});
test('after set', function(done) {
subject.getValue('syncFrequency', function(err, value) {
// test cached version
subject.getValue('syncFrequency', function(err, cachedValue) {
done(function() {
assert.equal(value, 200, 'returns correct value');
assert.equal(value, cachedValue, 'cached value is equal');
});
});
});
});
});
});
suite('Bug #855782 - Settings were not being cached', function() {
test('getting, then seting a value will overwrite cache', function(done) {
var key = 'someFooBar';
// The cache needs to be empty to test this case
assert.ok(!subject._cached[key]);
subject.defaults[key] = 'bbq';
subject.getValue(key, function(err, value) {
assert.equal(value, subject.defaults[key]);
subject.set(key, 'ketchup', null, function(err, value) {
assert.notEqual(value, subject.defaults[key]);
subject.getValue(key, function(err, value) {
assert.equal(value, 'ketchup');
assert.notEqual(value, subject.defaults[key]);
done();
});
});
});
});
});
});
});

View file

@ -0,0 +1,139 @@
define(function(require, exports) {
'use strict';
var Db = require('db');
var Factory = require('test/support/factory');
var MockProvider = require('test/support/mock_provider');
var app = require('app');
var core = require('core');
require('test/support/factories/all');
exports._lastEnvId = 0;
exports.core = function() {
var pendingManager = app.pendingManager;
if (pendingManager) {
// hack to ensure clean tests
pendingManager.objects.forEach(item => pendingManager.unregister(item));
}
if (core.db && core.db.isOpen) {
core.db.close();
}
app.setupCore('b2g-test-calendar');
core.providerFactory.providers.Mock = new MockProvider();
return core;
};
exports.accountEnvironment = function(accOverrides, calOverrides) {
accOverrides = accOverrides || {};
accOverrides.providerType = accOverrides.providerType || 'Mock';
calOverrides = calOverrides || {};
setup(function(done) {
var id = ++exports._lastEnvId;
accOverrides._id = id;
calOverrides.accountId = id;
var trans = core.db.transaction(['accounts', 'calendars'], 'readwrite');
trans.oncomplete = () => done();
trans.onerror = event => done(event.target.error);
var account = this.account = Factory('account', accOverrides);
var calendar = this.calendar = Factory('calendar', calOverrides);
var storeFactory = core.storeFactory;
var accountStore = storeFactory.get('Account');
var calendarStore = storeFactory.get('Calendar');
accountStore.persist(account, trans);
calendarStore.persist(calendar, trans);
});
};
exports.eventEnvironment = function(busytimeOverrides, eventOverrides) {
eventOverrides = eventOverrides || {};
eventOverrides._id = eventOverrides._id || 'one';
busytimeOverrides = busytimeOverrides || {};
setup(function(done) {
var storeFactory = core.storeFactory;
var busytimeStore = storeFactory.get('Busytime');
var eventStore = storeFactory.get('Event');
var trans = core.db.transaction(eventStore._dependentStores, 'readwrite');
trans.oncomplete = () => done();
trans.onerror = event => done(event.target.error);
eventOverrides.calendarId = this.calendar._id;
var event = this.event = Factory('event', eventOverrides);
busytimeOverrides.calendarId = this.calendar._id;
busytimeOverrides.eventId = this.event._id || eventOverrides._id;
var busytime = this.busytime = Factory('busytime', busytimeOverrides);
eventStore.persist(event, trans);
busytimeStore.persist(busytime, trans);
});
};
exports.dbFixtures = function(factory, storeName, list) {
var object = Object.create(null);
setup(function(done) {
var db = core.db;
var store = core.storeFactory.get(storeName);
var trans = db.transaction(store._dependentStores, 'readwrite');
trans.oncomplete = () => done();
trans.onerror = event => done(event.target.error);
for (var key in list) {
store.persist(
(object[key] = Factory(factory, list[key])),
trans
);
}
});
return object;
};
exports.clearStore = function(db, name, callback) {
if (typeof(name) === 'function') {
callback = name;
name = Object.keys(Db.prototype.store);
}
var trans = db.transaction(name, 'readwrite');
trans.oncomplete = () => callback();
trans.onerror = () => callback(new Error('Could not wipe accounts db'));
name = [].concat(name);
name.forEach(storeName => {
var store = trans.objectStore(storeName);
store.clear();
});
};
exports.bench = function(iter, cb) {
var start = window.performance.now();
for (var i = 0; i < iter; i++) {
cb();
}
return window.performance.now() - start;
};
exports.vs = function(iter, cmds) {
var results = {};
for (var key in cmds) {
results[key] = exports.bench(iter, cmds[key]);
}
return results;
};
exports.triggerEvent = function(element, eventType) {
var event = document.createEvent('HTMLEvents');
event.initEvent(eventType, true, true);
element.dispatchEvent(event);
};
});

View file

@ -0,0 +1,10 @@
define(function(require, exports) {
'use strict';
var nextTick = require('common/next_tick');
exports.load = function(id, require, onLoad) {
nextTick(() => onLoad());
};
});

View file

@ -0,0 +1,12 @@
/* global requireElements */
define(function(require, exports) {
'use strict';
var nextTick = require('common/next_tick');
exports.load = function(id, require, onLoad) {
requireElements('calendar/elements/' + id + '.html');
nextTick(() => onLoad());
};
});

View file

@ -0,0 +1,232 @@
define(function(require) {
'use strict';
/**
* Module dependencies
*/
var AccountModel = require('models/account');
var CalendarModel = require('models/calendar');
var Caldav = require('ext/caldav');
var Calc = require('common/calc');
var Factory = require('test/support/factory');
/**
* Module state
*/
var calendarId = 0;
function handleTransportDate(obj) {
// handle case of incoming start date
if (obj.startDate) {
obj.start = Calc.dateToTransport(obj.startDate);
}
if (obj.endDate) {
obj.end = Calc.dateToTransport(obj.endDate);
}
// handle case of defaults
if (!obj.start) {
obj.start = Calc.dateToTransport(
new Date()
);
}
if (obj.start) {
obj.startDate = Calc.dateFromTransport(obj.start);
}
if (!obj.end) {
obj.end = Calc.dateToTransport(
// 1 hour after start
new Date(obj.startDate.getTime() + 3600 * 1000)
);
}
if (obj.end) {
obj.endDate = Calc.dateFromTransport(obj.end);
}
}
Factory.define('remote.calendar', {
properties: {
id: '',
url: '/',
name: 'my calendar',
color: '#333',
description: 'description',
syncToken: '1',
// read, write, delete
privilegeSet: ['read', 'write-content', 'unbind']
},
onbuild: function(obj) {
var id = obj.id;
if (!id) {
id = obj.id = 'cuuid/' + calendarId++;
}
}
});
var eventId = 0;
Factory.define('remote.event', {
properties: {
location: 'location',
isRecurring: false,
alarms: [
{action: 'DISPLAY', trigger: 60000}
]
//XXX: raw data
},
onbuild: function(obj) {
var id = obj.id;
if (!id) {
id = obj.id = 'euuid/' + eventId++;
}
handleTransportDate(obj);
if (!obj.title) {
obj.title = 'title ' + id;
}
if (!obj.description) {
obj.description = 'description ' + id;
}
}
});
Factory.define('caldav.calendar', {
get object() {
return Caldav.Resources.Calendar;
},
properties: {
url: '/cal',
name: 'name',
color: '#333',
ctag: 'token',
description: '1',
privilegeSet: [
'read',
'write',
'write-content'
]
}
});
Factory.define('caldav.account', {
properties: {
user: 'user',
password: 'password',
domain: 'http://google.com',
entrypoint: '/dav/',
calendarHome: '/dav/my/foobar'
}
});
Factory.define('caldav.connection', {
extend: 'caldav.account',
get object() {
return Caldav.Connection;
}
});
Factory.define('account', {
get object() {
return AccountModel;
},
properties: {
providerType: 'Local',
user: 'user',
password: 'password',
domain: 'http://google.com',
entrypoint: '/',
preset: 'local'
}
});
Factory.define('calendar', {
get object() {
return CalendarModel;
},
properties: {
accountId: 1,
remote: Factory.get('remote.calendar')
},
oncreate: function(obj) {
if (!obj._id) {
obj._id = obj.accountId + '-' + obj.remote.id;
}
}
});
Factory.define('event', {
properties: {
calendarId: 1,
remote: Factory.get('remote.event')
},
oncreate: function(obj) {
if (!obj._id) {
obj._id = obj.calendarId + '-' + obj.remote.id;
}
}
});
var busytimeId = 0;
Factory.define('busytime', {
properties: {
calendarId: 'calendar-1',
eventId: 'event-1'
},
oncreate: function(obj) {
if (!obj._id) {
obj._id = obj.eventId + '-' + (++busytimeId);
}
handleTransportDate(obj);
if (obj.endDate === obj.startDate) {
obj.endDate.setHours(obj.endDate.getHours() + 2);
handleTransportDate(obj);
}
}
});
Factory.define('alarm', {
oncreate: function(obj) {
if (obj.startDate && obj.startDate instanceof Date) {
obj.startDate = Calc.dateToTransport(obj.startDate);
}
}
});
Factory.define('icalComponent', {
oncreate: function(obj) {
if (obj.lastRecurrenceId && obj.lastRecurrenceId instanceof Date) {
obj.lastRecurrenceId = Calc.dateToTransport(obj.lastRecurrenceId);
}
},
properties: {
ical: 'fooo!'
}
});
});

View file

@ -0,0 +1,209 @@
/**
* Test factory builder. Inspired by FactoryGirl (of ruby/rails fame).
* Tries very hard to respect getters in js objects
* as a result there is a performance hit so factories
* should not be used when building thousands of objects
* but should be fine for the 10-100 objects in small-large
* tests.
*/
define(function(require, exports, module) {
'use strict';
function propIsFactory(object, key) {
var descriptor = Object.getOwnPropertyDescriptor(
object, key
);
if (!descriptor) {
return false;
}
return (
descriptor.value &&
(descriptor.value instanceof Factory)
);
}
/**
* Copy a set of property descriptors from
* one object to another
*/
function copyProp(from, keys, to) {
var list = [].concat(keys);
list.forEach(function(key) {
if (!from.hasOwnProperty(key)) {
return;
}
Object.defineProperty(
to,
key,
Object.getOwnPropertyDescriptor(
from,
key
)
);
});
return to;
}
/**
* Copies all properties
* (in order from left to right to the final argument)
*/
function copy() {
var args = Array.prototype.slice.call(arguments);
var target = args.pop();
args.forEach(function(object) {
if (!object) {
return;
}
for (var key in object) {
if (object.hasOwnProperty(key)) {
Object.defineProperty(
target,
key,
Object.getOwnPropertyDescriptor(
object,
key
)
);
}
}
});
return target;
}
/* static api */
Factory._defined = Object.create(null);
Factory.get = function(name) {
return Factory._defined[name];
};
Factory.define = function(name, options) {
if (options.extend) {
var factory = Factory.get(options.extend);
Factory._defined[name] = factory.extend(
options
);
} else {
Factory._defined[name] = new Factory(
options
);
}
return Factory._defined[name];
};
Factory.create = function(name, opts) {
return Factory.get(name).create(opts);
};
Factory.build = function(name, opts) {
return Factory.get(name).build(opts);
};
function Factory(options) {
if (!(this instanceof Factory)) {
return Factory.create.apply(Factory, arguments);
}
copy(options, this);
return this;
}
module.exports = Factory;
Factory.prototype = {
parent: null,
object: null,
properties: {},
extend: function(options) {
var newFactory = {};
// we need to copy the prop
// rather then do an assignment for lazy-est
// possible evaluation of properties.
copyProp(
this,
['object', 'onbuild', 'oncreate'],
newFactory
);
copy(options, newFactory);
newFactory.properties = copy(
this.properties,
newFactory.properties,
{}
);
return new Factory(newFactory);
},
build: function(overrides, childFactoryMethod) {
if (typeof(overrides) === 'undefined') {
overrides = {};
}
if (typeof(childFactoryMethod) === 'undefined') {
childFactoryMethod = 'build';
}
var defaults = {};
var key;
var props = this.properties;
copy(props, overrides, defaults);
// expand factories
var factoryOverrides;
for (key in defaults) {
// when default property is a factory
if (propIsFactory(props, key)) {
factoryOverrides = undefined;
if (!propIsFactory(defaults, key)) {
// user overrides defaults
factoryOverrides = defaults[key];
}
defaults[key] = props[key][childFactoryMethod](
factoryOverrides
);
}
}
if (typeof(this.onbuild) === 'function') {
this.onbuild(defaults);
}
return defaults;
},
create: function(overrides) {
var result;
var constructor = this.object;
var attrs = this.build(overrides, 'create');
if (constructor) {
result = new constructor(attrs);
} else {
result = attrs;
}
if (typeof(this.oncreate) === 'function') {
this.oncreate(result);
}
return result;
}
};
});

View file

@ -0,0 +1,429 @@
define(function(require) {
'use strict';
var Factory = require('test/support/factory');
suite('factory', function() {
var subject;
var realDefined;
setup(function() {
realDefined = Factory._defined;
Factory._defined = Object.create(null);
});
teardown(function() {
Factory._defined = realDefined;
});
function Model(options) {
var key;
for (key in options) {
if (options.hasOwnProperty(key)) {
this[key] = options[key];
}
}
}
suite('hooks', function() {
setup(function() {
subject = new Factory({
onbuild: function(obj) {
obj.hitBuild = true;
},
oncreate: function(obj) {
obj.hitCreate = true;
},
properties: {
one: true
}
});
});
test('create', function() {
var out = subject.create();
assert.isTrue(out.hitBuild);
assert.isTrue(out.hitCreate);
});
test('build', function() {
var out = subject.build();
assert.isTrue(out.hitBuild);
assert.ok(!out.hitCreate);
});
});
suite('simple property factory', function() {
setup(function() {
subject = new Factory({
properties: {
get one() {
return 'cool';
},
two: 'foo'
}
});
});
test('initialization', function() {
assert.deepEqual(subject.properties, {
one: 'cool',
two: 'foo'
});
});
test('create - no overrides', function() {
var result = subject.create();
assert.deepEqual(
result,
subject.properties
);
});
test('create - overrides', function() {
var result = subject.create(
{ one: '1', three: '3' }
);
assert.deepEqual(
result, {
one: '1',
two: 'foo',
three: '3'
}
);
});
});
suite('with getters', function() {
var increment;
setup(function() {
increment = 0;
subject = new Factory({
properties: {
get increment() {
return increment++;
}
}
});
});
test('increment getter', function() {
subject.create();
subject.create();
subject.create();
var result = subject.create();
// This is probably something
// you would not want to do in real
// life but it provides that
// increments are clean.
assert.equal(result.increment, 0);
assert.equal(result.increment, 1);
assert.equal(result.increment, 2);
assert.equal(result.increment, 3);
});
});
suite('with constructors', function() {
setup(function() {
subject = new Factory({
object: Model,
properties: {
value: 'foo'
}
});
});
test('create - no overrides', function() {
var result = subject.create();
assert.instanceOf(result, Model);
assert.equal(result.value, 'foo');
});
test('create - with overrides', function() {
var result = subject.create(
{ value: 'val', other: true }
);
assert.instanceOf(result, Model);
assert.equal(result.value, 'val');
assert.equal(result.other, true);
});
test('build', function() {
var result = subject.build({ val: 1 });
assert.isFalse((
result instanceof Model
));
assert.equal(result.val, 1);
assert.equal(result.value, 'foo');
});
});
suite('with factory props', function() {
var childFactory;
setup(function() {
childFactory = new Factory({
object: Model,
properties: {
get child() {
return true;
}
}
});
subject = new Factory({
properties: {
parent: 'yes',
child: childFactory,
inertChild: childFactory
}
});
});
test('create', function() {
var result = subject.create({
parentVal: 1,
child: {
otherVal: 1
}
});
assert.instanceOf(result.child, Model);
assert.instanceOf(result.inertChild, Model);
assert.equal(result.parent, 'yes');
assert.equal(result.parentVal, 1);
assert.equal(result.inertChild.child, true);
assert.equal(result.child.child, true);
assert.equal(result.child.otherVal, 1);
});
test('build', function() {
var result = subject.build({
child: {
otherVal: 1
}
});
assert.isFalse((
result.child instanceof Model
));
assert.isFalse((
result.inertChild instanceof Model
));
});
test('#extend', function() {
var sub = subject.extend({
object: Model,
properties: {
foo: 'bar'
}
});
assert.ok(!subject.properties.foo);
assert.equal(sub.object, Model);
var result = sub.create({
other: 'fooz'
});
assert.instanceOf(result, Model);
assert.notEqual(
sub.properties,
subject.properties
);
assert.equal(result.other, 'fooz');
assert.instanceOf(result.child, Model);
assert.instanceOf(result.inertChild, Model);
assert.equal(result.child.child, true);
});
});
suite('static api', function() {
var defined;
setup(function() {
defined = Factory.define('one', {
object: Model,
properties: {
one: true
}
});
});
test('#define', function() {
assert.instanceOf(defined, Factory);
assert.equal(defined.object, Model);
assert.deepEqual(
defined.properties,
{ one: true }
);
assert.equal(Factory.get('one'), defined);
});
test('#define - /w extend', function() {
Factory.define('one.ext', {
extend: 'one',
properties: {
two: false
}
});
assert.instanceOf(
Factory.get('one.ext'),
Factory
);
var created = Factory.create('one.ext');
assert.equal(created.one, true);
assert.equal(created.two, false);
});
test('#create', function() {
var result = Factory.create('one', {
extra: true
});
assert.instanceOf(result, Model);
assert.equal(result.extra, true);
assert.equal(result.one, true);
});
test('#build', function() {
var result = Factory.build('one', {
extra: true
});
assert.isFalse((
result instanceof Model
));
assert.equal(result.extra, true);
assert.equal(result.one, true);
});
});
suite('acceptance', function() {
var result;
function Event() { Model.apply(this, arguments); }
function Cal() { Model.apply(this, arguments); }
function Account() { Model.apply(this, arguments); }
setup(function() {
Factory.define('event', {
object: Event,
properties: {
isEvent: true
}
});
Factory.define('calendar', {
object: Cal,
properties: {
isCalendar: true,
event: Factory.get('event')
}
});
Factory.define('account', {
object: Account,
properties: {
isAccount: true,
normalCal: Factory.get('calendar'),
extendCal: Factory.get('calendar').extend({
properties: {
isCrazy: true,
event: Factory.get('event').extend({
properties: {
isCrazy: true
}
})
}
})
}
});
});
function hasProps() {
// account
assert.isTrue(result.isAccount);
// normal cal
assert.isTrue(result.normalCal.isCalendar);
assert.ok(!result.normalCal.isCrazy);
// crazy cal
assert.isTrue(result.extendCal.isCrazy);
assert.isTrue(result.extendCal.isCalendar);
// normal event
assert.isTrue(result.normalCal.event.isEvent);
assert.ok(!result.normalCal.event.isCrazy);
// crazy calendar
assert.isTrue(result.extendCal.event.isEvent);
assert.isTrue(result.extendCal.event.isCrazy);
}
test('#create - no overrides', function() {
result = Factory.create('account');
assert.instanceOf(result, Account, 'result Account');
assert.instanceOf(result.normalCal, Cal, 'normalCal');
assert.instanceOf(result.extendCal, Cal, 'extendCal');
assert.instanceOf(result.normalCal.event, Event, 'normalCal.event');
assert.instanceOf(result.extendCal.event, Event, 'extendCal.event');
hasProps();
});
test('#build - no overrides', function() {
result = Factory.build('account');
assert.isFalse(
result instanceof Account
);
hasProps();
});
test('deep overrides', function() {
result = Factory('account', {
normalCal: {
foo: true,
event: {
nested: true
}
}
});
// we did not kill the defaults
hasProps();
assert.isTrue(result.normalCal.foo);
assert.isTrue(result.normalCal.event.nested);
});
});
});
});

View file

@ -0,0 +1,23 @@
define(function(require, exports, module) {
'use strict';
function page() {
page.routes.push(Array.prototype.slice.call(arguments));
}
module.exports = page;
page.routes = [];
page.show = function(item) {
this.shown = item;
};
page.start = function() {
this.started = true;
};
page.stop = function() {
this.started = false;
};
});

View file

@ -0,0 +1,28 @@
define(function(require, exports, module) {
'use strict';
module.exports = function mochaPromise(mochaFn, description, callback) {
if (typeof description === 'function') {
callback = description;
description = null;
}
function execute(done) {
var promise;
try {
promise = callback.call();
} catch (error) {
return done(error);
}
promise.then(() => done()).catch(done);
}
if (description) {
mochaFn(description, execute);
} else {
mochaFn(execute);
}
};
});

View file

@ -0,0 +1,54 @@
define(function(require, exports) {
'use strict';
var _id = 0;
var alarms = [];
var mozAlarms;
exports.setup = function() {
mozAlarms = navigator.mozAlarms;
navigator.mozAlarms = exports;
};
exports.teardown = function() {
removeAll();
navigator.mozAlarms = mozAlarms;
};
exports.add = function(date, honorTimezone, data) {
var alarmId = _id++;
alarms.push({
alarmId: alarmId,
date: date,
honorTimezone: honorTimezone,
data: data
});
return resolve(alarmId);
};
exports.getAll = function() {
return resolve(alarms);
};
exports.remove = function(alarmId) {
alarms = alarms.filter(alarm => alarm.alarmId !== alarmId);
};
function removeAll() {
alarms = [];
}
function resolve(result) {
var request = { onsuccess: null, onerror: null };
var requestContext = {};
requestContext.result = result;
setTimeout(() => {
return request.onsuccess &&
request.onsuccess.call(requestContext, result);
}, 10);
return request;
}
});

View file

@ -0,0 +1,213 @@
define(function(require, exports, module) {
'use strict';
var Parent = require('provider/abstract');
var nextTick = require('common/next_tick');
function accountGeneric(mockName) {
return function(account, callback) {
// next tick is scheduled before _resolveStage intentionally
nextTick(function() {
callback.apply(
null,
this._resolveStaged(mockName, account) || [null, {}]
);
}.bind(this));
};
}
function accountStaging(mockName) {
return function() {
var args = Array.slice(arguments);
var user = args.shift();
function matcher(object) {
return object.user === user;
}
return this._stage(mockName, matcher, args);
};
}
var stageEvents = {
onbefore: function() {},
onafter: function() {}
};
function Mock() {
Parent.apply(this, arguments);
// mock call storage
this._stageObjects = Object.create(null);
}
module.exports = Mock;
Mock.prototype = {
__proto__: Parent.prototype,
// opt-into sync by default
canSync: true,
_stage: function(name, matcher, args) {
if (!(this._stageObjects[name])) {
this._stageObjects[name] = [];
}
var handler = Object.create(stageEvents);
this._stageObjects[name].push({
matches: matcher,
args: args,
handler: handler
});
return handler;
},
/**
* Used to find staged objects.
* The found object must be saved as
* this method will also remove any found item.
*
* Stops at first found item that matches condition.
*
* var stagedValue = this._resolveStaged('type', input) || someDefault;
*
*/
_resolveStaged: function(name, input) {
var list = this._stageObjects[name];
if (!list) {
return null;
}
var i = 0;
var len = list.length;
for (; i < len; i++) {
if (list[i].matches(input)) {
// remove and return args
var item = list.splice(i, 1)[0];
var args = item.args;
var handler = item.handler;
handler.onbefore(args);
nextTick(handler.onafter.bind(null, args));
return args;
}
}
return null;
},
/**
* Stage a getAccount call.
*
* @param {String} user name to stage information for.
* @param {Object|Null} err given to callback.
* @param {Object|Null} object given to callback.
*/
stageGetAccount: accountStaging('getAccount'),
stageFindCalendars: accountStaging('findCalendars'),
stageSyncEvents: function(user, calendarRemoteId, err) {
err = err || null;
function matcher(args) {
return (
args[0].user === user &&
args[1].remote.id === calendarRemoteId
);
}
return this._stage('syncEvents', matcher, err);
},
stageEventCapabilities: function() {
var args = Array.slice(arguments);
var id = args.shift();
function matcher(event) {
return event._id === id;
}
return this._stage('eventCapabilities', matcher, args);
},
stageCalendarCapabilities: function() {
var args = Array.slice(arguments);
var id = args.shift();
function matcher(calendar) {
return calendar._id === id;
}
return this._stage('calendarCapabilities', matcher, args);
},
/**
* Stage findCalendars call.
*
*
* provider.stageFindCalendars(
* accountModel.user,
* err || null,
* {
* remoteId: { id: 'remoteId', ... },
* ...
* }
* );
*/
findCalendars: accountGeneric('findCalendars'),
getAccount: accountGeneric('getAccount'),
syncEvents: function(account, calendar, callback) {
// first so this tick is scheduled first
nextTick(function() {
callback(
this._resolveStaged('syncEvents', [account, calendar])
);
}.bind(this));
},
calendarCapabilities: function(calendar) {
var defaults = {
canUpdateEvent: true,
canCreateEvent: true,
canDeleteEvent: true
};
var args =
this._resolveStaged('calendarCapabilities', calendar) || [];
var overrides = args[0] || {};
for (var key in overrides) {
defaults[key] = overrides[key];
}
return defaults;
},
eventCapabilities: function(event, callback) {
var defaults = {
canUpdate: true,
canCreate: true,
canDelete: true
};
nextTick(function() {
var args = this._resolveStaged('eventCapabilities', event) || [];
var err = args[0] || null;
var overrides = args[1] || {};
for (var key in overrides) {
defaults[key] = overrides[key];
}
callback(err, defaults);
}.bind(this));
}
};
});

View file

@ -0,0 +1,33 @@
define(function(require, exports) {
'use strict';
var requestWakeLock;
exports.setup = function() {
requestWakeLock = navigator.requestWakeLock;
navigator.requestWakeLock = fakeRequestWakeLock;
};
exports.teardown = function() {
navigator.requestWakeLock = requestWakeLock;
locks = exports.locks = [];
};
var locks = exports.locks = [];
function fakeRequestWakeLock(type) {
var lock = createFakeLock(type);
locks.push(lock);
return lock;
}
function createFakeLock(type) {
return {
type: type,
unlocked: false,
unlock: function() {
this.unlocked = true;
}
};
}
});

View file

@ -0,0 +1,33 @@
define(function(require, exports, module) {
'use strict';
function View(options) {
var self = this;
var key;
if (typeof(options) === 'undefined') {
options = {};
}
for (key in options) {
if (options.hasOwnProperty(key)) {
this[key] = options[key];
}
}
this.called = 1;
this.args = arguments;
this.onactive = function() {
self.activeWith = arguments;
self.active = true;
};
this.oninactive = function() {
self.inactiveWith = arguments;
self.active = false;
};
}
module.exports = View;
});

View file

@ -0,0 +1,17 @@
define(function(require, exports, module) {
'use strict';
/** Deprecated! */
module.exports = function(name, callback) {
suite(name, function() {
suiteSetup(function(done) {
require([name], function() {
done();
});
});
callback();
});
};
});

View file

@ -0,0 +1,93 @@
define(function(require, exports, module) {
'use strict';
var asyncRequire = require('common/async_require');
var chai = require('ext/chai');
var chaiAsPromised = require('ext/chai-as-promised');
var co = require('ext/co');
module.exports = function testLoader(path) {
return co(function *() {
console.log(`Will load test ${path}`);
l10nLink('/locales/calendar.{locale}.properties');
l10nLink('/shared/locales/date/date.{locale}.properties');
l10nMeta('en-US', ['en-US']);
yield loadL10n();
var testSupport = yield asyncRequire('test/support/calendar');
testSupport.core();
// TODO(gaye): No more window/global stuff...
window.testSupport = window.testSupport || {};
window.testSupport.calendar = testSupport;
chai.use(chaiAsPromised);
extendChai();
window.assert = chai.assert;
window.expect = chai.expect;
window.should = chai.Should();
yield asyncRequire(path);
});
};
function *loadL10n() {
yield asyncRequire('shared/l10n');
// Massive hack to trick l10n to load
// TODO: upstream a fix to l10n.js
document.dispatchEvent(new Event('DOMContentLoaded'));
var readyState = navigator.mozL10n.readyState;
switch (readyState) {
case 'complete':
case 'interactive':
return Promise.resolve();
}
return new Promise(resolve => window.addEventListener('localized', resolve));
}
function l10nLink(href) {
var link = document.createElement('link');
link.setAttribute('href', href);
link.setAttribute('rel', 'localization');
document.head.appendChild(link);
}
function l10nMeta(defaultLanguage, availableLanguages) {
var metaDL = document.createElement('meta');
metaDL.setAttribute('name', 'defaultLanguage');
metaDL.setAttribute('content', defaultLanguage);
var metaAL = document.createElement('meta');
metaAL.setAttribute('name', 'availableLanguages');
metaAL.setAttribute('content', availableLanguages.join(', '));
document.head.appendChild(metaDL);
document.head.appendChild(metaAL);
}
function extendChai() {
// XXX: this is a lame way to do this
// in reality we need to fix the above upstream
// and leverage new chai 1x methods
chai.assert.hasProperties = function(given, props, msg) {
msg = (typeof(msg) === 'undefined') ? '' : msg + ': ';
if (props instanceof Array) {
props.forEach(function(prop) {
assert.ok(
(prop in given),
msg + 'given should have "' + prop + '" property'
);
});
} else {
for (var key in props) {
assert.deepEqual(
given[key],
props[key],
msg + ' property equality for (' + key + ') '
);
}
}
};
}
});

View file

@ -0,0 +1,17 @@
define(function(require, exports, module) {
'use strict';
var denodeify = require('common/promise').denodeify;
function waitFor(test, callback) {
var result = test();
if (result) {
return callback();
}
setTimeout(() => waitFor(test, callback), 200);
}
module.exports = denodeify(waitFor);
});

View file

@ -0,0 +1,322 @@
define(function(require) {
'use strict';
var Template = require('template');
var format = require('format');
suite('calendar/template', function() {
var subject,
tplStr = '%s foo %h',
support;
setup(function() {
support = testSupport.calendar;
});
suite('Template.create', function() {
var result;
setup(function() {
result = Template.create({
a: 'a',
b: 'b'
});
});
test('result', function() {
assert.instanceOf(result.a, Template, 'should create template for a');
assert.instanceOf(result.b, Template, 'should create template for b');
});
});
setup(function() {
subject = new Template(tplStr);
});
suite('initialization', function() {
test('should set .template', function() {
assert.equal(subject.template, tplStr);
});
});
test('#renderEach', function() {
var tpl,
result,
objects;
objects = [
{ x: 1, y: 2 },
{ x: 7, y: 7 }
];
tpl = new Template(function() { return this.h('x') + 'x' + this.h('y'); });
result = tpl.renderEach(objects);
assert.deepEqual(result, [
'1x2',
'7x7'
], 'should return array of rendered results');
result = tpl.renderEach(objects, '--');
assert.equal(
result,
'1x2--7x7',
'should return string joined by ","'
);
});
suite('#render', function() {
test('single placeholder no flag', function() {
var tpl = new Template(function() {
return 'z ' + this.h('a') + ' foo';
});
assert.equal(tpl.render({a: 'baz'}), 'z baz foo');
assert.equal(tpl.render({a: 'baz'}), 'z baz foo');
assert.equal(tpl.render({a: 'baz'}), 'z baz foo');
});
test('when input is not an object', function() {
var tpl = new Template(function() {
return 'foo ' + this.h('value') + '!'; });
var result = tpl.render(1);
assert.equal(result, 'foo 1!');
});
test('without placeholders', function() {
var tpl = new Template(function() { return 'foo bar'; });
assert.equal(tpl.render(), 'foo bar');
});
test('multiple placeholders', function() {
var tpl = new Template(function() {
return this.h('2') + ' ! ' + this.h('1');
});
assert.equal(tpl.render({1: '1', 2: '2'}), '2 ! 1');
});
test('keys with dashes', function() {
var tpl = new Template(function() {
return this.h('foo-bar');
});
assert.equal(tpl.render({'foo-bar': 'fo'}), 'fo');
});
test('html escape', function() {
var tpl, input, output;
tpl = new Template(function() {
return this.h('html');
});
input = '<div class="foo">\'zomg\'</div>';
output = tpl.render({html: input});
assert.equal(
output,
'&lt;div class=&quot;foo&quot;&gt;&#x27;zomg&#x27;&lt;/div&gt;'
);
assert.equal(
tpl.render({}),
''
);
});
test('without arguments', function() {
var tpl = new Template(function() { return 'foo ' + this.h('value'); });
assert.equal(tpl.render(), 'foo ');
});
test('with newlines in tpl', function() {
var tpl = new Template(function() { return '\nfoo ' + this.h('value'); });
assert.equal(tpl.render('bar'), '\nfoo bar');
});
test('with numeric, null and objects', function() {
var tpl = new Template(function() {
return 'foo ' + this.h('nullish') + this.h('undefinedish') +
this.h('zeroish') +' '+ this.h('falseish') + ' ' +
this.h('objectish');
});
// null, undefined and zero caused problems previously
assert.equal(tpl.render({
nullish: null,
undefinededish: undefined,
zeroish: 0,
falseish: false,
objectish: {
toString: function() {
// this should be escaped!!!
return '<a>complex</a>';
}
}
}), 'foo 0 false &lt;a&gt;complex&lt;/a&gt;');
});
test('no html escape', function() {
var tpl, input, output;
tpl = new Template(function() {
return this.s('html');
});
input = '<div class="foo">\'zomg\'</div>';
output = tpl.render({html: input});
assert.equal(
output,
input
);
});
test('bool handler', function() {
var tpl, output;
tpl = new Template(function() { return this.bool('one', 'selected'); });
output = tpl.render({ one: true });
assert.equal(output, 'selected');
output = tpl.render({ one: false });
assert.equal(output, '');
});
test('l10nId', function() {
var tpl = new Template(function() {
return this.l10nId('state');
});
assert.equal(tpl.render({state: 'past other-month'}), 'past-other-month');
});
suite('l10n', function() {
var realL10n;
var lookup = {
foo: 'FOO',
bar: 'BAR',
'field-one': 'first'
};
suiteSetup(function() {
realL10n = navigator.mozL10n;
navigator.mozL10n = {
get: function(name) {
return lookup[name];
}
};
});
suiteTeardown(function() {
if (realL10n) {
navigator.mozL10n = realL10n;
}
});
test('prefix', function() {
var tpl = new Template(function() {
return this.l10n('start', 'field-') + ' foo';
});
var result = tpl.render({
'start': 'one'
});
assert.equal(result, 'first foo');
});
test('simple', function() {
var tpl = new Template(function() {
return this.l10n('one') + ' ' + this.l10n('two');
});
var result = tpl.render({
one: 'foo',
two: 'bar'
});
assert.equal(result, 'FOO BAR');
});
});
});
suite('benchmarks', function() {
/*jshint -W027 */
test('tpl vs format', function() {
// XXX: Minor performance regression
// come back later and inline
// modifiers which should make
// templates quite a bit faster
return;
var tpl = 'My name is {first} {last}, Thats Mr {last}';
var template;
var results = support.vs(5000, {
compiled: function() {
template = template || new Template(tpl);
template.render({first: 'Sahaja', last: 'Lal'});
},
format: function() {
format(tpl, 'Sahaja', 'Lal');
}
});
assert.ok(
(results.compiled <= results.format),
'compiled template should be faster then format'
);
});
test('createElement vs template', function() {
var container = document.createElement('div'),
tpl;
container.id = 'containerTest';
document.body.appendChild(container);
var div = document.createElement('div'),
span = document.createElement('span');
div.appendChild(span);
support.vs(5000, {
html: function() {
var myDiv = div.cloneNode(true),
mySpan = myDiv.querySelector('span');
myDiv.className = 'dynamic';
mySpan.textContent = 'content';
mySpan.className = 'foo';
container.innerHTML = '';
container.appendChild(myDiv);
},
template: function() {
tpl = tpl || new Template(function() {
return '<div class="' + this.h('divClass') + '">' +
'<span class="' + this.h('spanClass') + '">' +
this.h('content') + '</span>' +
'</div>';
});
container.innerHTML = '';
container.innerHTML = tpl.render({
divClass: 'dynamic',
spanClass: 'foo',
content: 'content'
});
}
});
container.parentNode.removeChild(container);
});
});
});
});

View file

@ -0,0 +1,33 @@
define(function(require) {
'use strict';
var Account = require('templates/account');
suite('Templates.Account', function() {
var subject;
suiteSetup(function() {
subject = Account;
});
function renderHTML(type, options) {
return subject[type].render(options);
}
test('#provider', function() {
var output = renderHTML('provider', { name: 'yahoo' });
assert.include(output, 'yahoo');
assert.include(output, 'li class="yahoo" role="presentation"');
assert.include(output, 'a data-l10n-id="preset-yahoo" role="option"');
});
test('#account', function() {
var output = renderHTML('account', { id: '1' });
assert.include(output, 'li id="account-1" role="presentation"');
assert.include(output, 'a href="/update-account/1" role="option"');
});
});
});

View file

@ -0,0 +1,178 @@
define(function(require) {
'use strict';
/* global MockMozIntl */
require('/shared/test/unit/mocks/mock_moz_intl.js');
var Alarm = require('templates/alarm');
suite('Templates.Alarm', function() {
var subject;
suiteSetup(function() {
window.mozIntl = MockMozIntl;
subject = Alarm;
});
function renderOption(value, layout, selected) {
return subject.option.render({
value: value,
layout: layout,
selected: selected
});
}
function renderDescription(layout, trigger) {
return subject.description.render({
layout: layout,
trigger: trigger
});
}
suite('description', function() {
test('minutes', function() {
var relativePartSpy = sinon.spy(window.mozIntl._gaia, 'relativePart');
renderDescription('allday', -600);
assert.isTrue(relativePartSpy.withArgs(-600 * 1000).calledOnce);
window.mozIntl._gaia.relativePart.restore();
assert.include(renderDescription('allday', -600), 'role="listitem"');
assert.include(renderDescription('standard', -600), 'role="listitem"');
});
test('hours', function() {
var relativePartSpy = sinon.spy(window.mozIntl._gaia, 'relativePart');
renderDescription('allday', -6000);
assert.isTrue(relativePartSpy.withArgs(-6000 * 1000).calledOnce);
window.mozIntl._gaia.relativePart.restore();
assert.include(renderDescription('allday', -6000), 'role="listitem"');
assert.include(renderDescription('standard', -6000), 'role="listitem"');
});
test('years', function() {
var relativePartSpy = sinon.spy(window.mozIntl._gaia, 'relativePart');
renderDescription('allday', -6000000);
assert.isTrue(relativePartSpy.withArgs(-6000000 * 1000).calledOnce);
window.mozIntl._gaia.relativePart.restore();
assert.include(renderDescription('allday', -6000000), 'role="listitem"');
assert.include(renderDescription('standard', -6000000),
'role="listitem"');
});
test('none', function() {
assert.include(renderDescription('allday', 'none'), 'None');
assert.include(renderDescription('allday', 'none'), 'role="listitem"');
assert.include(renderDescription('standard', 'none'), 'role="listitem"');
});
});
suite('#option', function() {
test('minutes', function() {
var relativePartSpy = sinon.spy(window.mozIntl._gaia, 'relativePart');
renderOption(-600);
assert.isTrue(relativePartSpy.withArgs(-600 * 1000).calledOnce);
window.mozIntl._gaia.relativePart.restore();
});
test('hours', function() {
// One hour ago
var relativePartSpy = sinon.spy(window.mozIntl._gaia, 'relativePart');
renderOption(-6000);
assert.isTrue(relativePartSpy.withArgs(-6000 * 1000).calledOnce);
window.mozIntl._gaia.relativePart.restore();
});
test('years', function() {
// Large differences are specified in weeks
var relativePartSpy = sinon.spy(window.mozIntl._gaia, 'relativePart');
renderOption(-6000000);
assert.isTrue(relativePartSpy.withArgs(-6000000 * 1000).calledOnce);
window.mozIntl._gaia.relativePart.restore();
});
test('none', function() {
assert.include(renderOption('none'), 'None');
});
test('single unit rendered', function() {
var relativePartSpy = sinon.spy(window.mozIntl._gaia, 'relativePart');
renderOption(-5400);
assert.isTrue(relativePartSpy.withArgs(-5400 * 1000).calledOnce);
window.mozIntl._gaia.relativePart.restore();
});
suite('selected', function() {
test('> not selected', function() {
assert.ok(
renderOption(-1800).indexOf('selected') === -1,
'not selected by default'
);
});
test('> selected', function() {
assert.include(renderOption(-1800, null, true), 'selected');
});
});
suite('all day event alarms', function() {
test('trigger include to system default\'s on day of event', function() {
var option = renderOption(32400, 'allday');
assert.include(option, 'On day of event');
assert.include(option, 'data-l10n-id="alarm-at-event-allday"');
});
test('trigger include to 9 hours before event', function() {
var relativePartSpy = sinon.spy(window.mozIntl._gaia, 'relativePart');
renderOption(-32400, 'allday');
assert.isTrue(relativePartSpy.withArgs(-32400 * 1000).calledOnce);
window.mozIntl._gaia.relativePart.restore();
});
test('trigger include to 1 day before', function() {
var relativePartSpy = sinon.spy(window.mozIntl._gaia, 'relativePart');
renderOption(-54000, 'allday');
assert.isTrue(relativePartSpy.withArgs(-86400000).calledOnce);
window.mozIntl._gaia.relativePart.restore();
});
test('trigger include to 1 day after', function() {
var relativePartSpy = sinon.spy(window.mozIntl._gaia, 'relativePart');
renderOption(86400, 'allday');
assert.isTrue(relativePartSpy.withArgs(86400 * 1000).calledOnce);
window.mozIntl._gaia.relativePart.restore();
});
test('trigger include to 2 days before', function() {
var relativePartSpy = sinon.spy(window.mozIntl._gaia, 'relativePart');
renderOption(-140400, 'allday');
assert.isTrue(relativePartSpy.withArgs(-172800000).calledOnce);
window.mozIntl._gaia.relativePart.restore();
});
test('trigger include to 1 week before', function() {
var relativePartSpy = sinon.spy(window.mozIntl._gaia, 'relativePart');
renderOption(-572400, 'allday');
assert.isTrue(relativePartSpy.withArgs(-604800000).calledOnce);
window.mozIntl._gaia.relativePart.restore();
});
test('trigger include to 2 weeks before', function() {
var relativePartSpy = sinon.spy(window.mozIntl._gaia, 'relativePart');
renderOption(-1177200, 'allday');
assert.isTrue(relativePartSpy.withArgs(-1209600000).calledOnce);
window.mozIntl._gaia.relativePart.restore();
});
test('trigger include to 30 minutes', function() {
var relativePartSpy = sinon.spy(window.mozIntl._gaia, 'relativePart');
renderOption(-1800, 'allday');
assert.isTrue(relativePartSpy.withArgs(-1800 * 1000).calledOnce);
window.mozIntl._gaia.relativePart.restore();
});
});
});
});
});

View file

@ -0,0 +1,74 @@
define(function(require) {
'use strict';
var Calendar = require('templates/calendar');
var localCalendarId = require('common/constants').localCalendarId;
suite('Templates.Calendar', function() {
var subject;
suiteSetup(function() {
subject = Calendar;
});
function renderHTML(type, options) {
return subject[type].render(options);
}
test('#item with local id', function() {
var model = {
localDisplayed: true,
_id: localCalendarId,
name: 'foo',
color: '#BADA55'
};
var output = renderHTML('item', model);
assert.ok(output);
assert.match(output, /calendar-local-first"\s+role="presentation"/);
assert.match(output,
/icon-calendar-dot"\s+style="color:#BADA55"\s+aria-hidden="true"/);
assert.include(output, 'calendar-local');
});
test('#item not local displayed', function() {
var model = {
localDisplayed: false,
_id: localCalendarId,
name: 'foo'
};
var output = renderHTML('item', model);
assert.ok(output);
assert.match(output, /gaia-checkbox/);
});
test('#item', function() {
var model = {
localDisplayed: true,
_id: 1,
name: 'cal name'
};
var output = renderHTML('item', model);
assert.ok(output);
assert.include(output, 'checked');
assert.include(output, model.name);
assert.include(output, 'calendar-1');
assert.ok(
output.indexOf('calendar-local') === -1,
'does not include calendar-local l10n id'
);
model.localDisplayed = false;
output = renderHTML('item', model);
var selected = output.indexOf('checked') !== -1;
assert.isFalse(selected);
assert.include(output, model.name);
});
});
});

View file

@ -0,0 +1,112 @@
define(function(require) {
'use strict';
var DurationTime = require('templates/duration_time');
suite('DurationTime', function() {
var subject;
suiteSetup(function() {
window.IntlHelper.define('shortTimeFormat', 'datetime', {
hour: 'numeric',
minute: 'numeric'
});
subject = DurationTime;
});
function renderDurationTime(options) {
return subject.durationTime.render(options);
}
suite('#durationTime', function() {
var container;
setup(function() {
container = document.createElement('div');
document.body.appendChild(container);
});
teardown(function() {
container.parentNode.removeChild(container);
});
test('one day', function() {
var startDate = new Date('1991-09-08T10:10:00');
var endDate = new Date('1991-09-08T17:17:00');
var durationDescription = renderDurationTime({
startDate: startDate,
endDate: endDate,
isAllDay: false
});
container.innerHTML = durationDescription;
var spans = container.querySelectorAll('span');
assert.include(
spans[0].dataset.date,
'Sun Sep 08 1991 10:10:00',
'start time'
);
assert.include(
spans[1].dataset.date,
'Sun Sep 08 1991 17:17:00',
'end time'
);
});
test('multiple day', function() {
var startDate = new Date('1991-09-08T10:10:00');
var endDate = new Date('1991-09-15T17:17:00');
var durationDescription = renderDurationTime({
startDate: startDate,
endDate: endDate,
isAllDay: false
});
container.innerHTML = durationDescription;
var spans = container.querySelectorAll('span');
assert.include(
spans[0].dataset.date,
'Sun Sep 08 1991 10:10:00',
'start time'
);
assert.include(
spans[1].dataset.date,
'Sun Sep 15 1991 17:17:00',
'end time'
);
});
test('one all day', function() {
var startDate = new Date('1991-09-08T00:00:00');
var endDate = new Date('1991-09-09T00:00:00');
var durationDescription = renderDurationTime({
startDate: startDate,
endDate: endDate,
isAllDay: true
});
assert.equal(
durationDescription,
'All day<br>' +
'Sunday, September 8, 1991'
);
});
test('multiple all day', function() {
var startDate = new Date('1991-09-08T00:00:00');
var endDate = new Date('1991-09-15T00:00:00');
var durationDescription = renderDurationTime({
startDate: startDate,
endDate: endDate,
isAllDay: true
});
assert.equal(
durationDescription,
'All day from Sunday, September 8, 1991<br>' +
'to Saturday, September 14, 1991'
);
});
});
});
});

View file

@ -0,0 +1,81 @@
define(function(require) {
'use strict';
var intl = require('intl');
var MonthDayAgenda = require('templates/month_day_agenda');
suite('templates/month_day_agenda', function() {
function renderHTML(type, options) {
return MonthDayAgenda[type].render(options);
}
suiteSetup(function() {
intl.init();
});
suite('#event', function() {
test('> regular event', function() {
var result = renderHTML('event', {
hasAlarms: true,
busytimeId: 55,
calendarId: 42,
title: 'Lorem Ipsum',
location: 'Dolor Sit Amet',
attendees: null,
startTime: new Date('October 13, 2014 12:34:00'),
endTime: new Date('October 13, 2014 16:56:00'),
isAllDay: false,
color: '#0000ff'
});
assert.include(result, 'has-alarms');
assert.include(result, 'href="/event/show/55/"');
assert.include(result,
'<h5 role="presentation" dir="auto">Lorem Ipsum</h5>');
assert.include(result, 'Dolor Sit Amet');
assert.include(result, '12:34');
assert.include(result, '16:56');
assert.include(result, 'role="option"');
assert.include(result, '<div class="gaia-icon icon-calendar-dot" ' +
'style="color:#0000ff"');
assert.match(result,
/icon-calendar-dot" style="color:#0000ff"\s+aria-hidden="true">/);
assert.match(result, /id="55-icon-calendar-alarm"\s+aria-hidden="true"/);
assert.include(result, 'aria-describedby="55-icon-calendar-alarm"');
});
test('> all day event', function() {
var result = renderHTML('event', {
hasAlarms: false,
busytimeId: 55,
calendarId: 42,
title: 'Lorem Ipsum',
location: 'Dolor Sit Amet',
attendees: null,
startTime: new Date('October 13, 2014 12:34:00'),
endTime: new Date('October 13, 2014 16:56:00'),
isAllDay: true,
color: '#ff0033'
});
assert.equal(result.indexOf('has-alarms'), -1, 'no alarm');
assert.include(result, 'href="/event/show/55/"');
assert.include(result,
'<h5 role="presentation" dir="auto">Lorem Ipsum</h5>');
assert.include(result, 'Dolor Sit Amet');
assert.include(result, 'data-l10n-id="hour-allday"');
assert.include(result, 'role="option"');
assert.include(result, '<div class="gaia-icon icon-calendar-dot" ' +
'style="color:#ff0033"');
assert.match(result,
/icon-calendar-dot" style="color:#ff0033"\s+aria-hidden="true">/);
assert.include(result, 'id="55-icon-calendar-alarm" aria-hidden="true"');
assert.include(result, 'aria-describedby="55-icon-calendar-alarm"');
assert.ok(result.indexOf('12:34') === -1, 'include start time');
assert.ok(result.indexOf('16:56') === -1, 'include end time');
});
});
});
});

View file

@ -0,0 +1,149 @@
define(function(require) {
'use strict';
var timeObserver = require('time_observer');
suite('time_observer', function() {
var subject;
var MINUTE = 60 * 1000;
var DAY = 24 * 60 * MINUTE;
suiteSetup(function() {
subject = timeObserver;
});
suite('minuteChange', function() {
var clock;
setup(function() {
clock = sinon.useFakeTimers((new Date(2015, 3, 15)).getTime());
});
teardown(function() {
subject._stop();
subject.removeAllListeners();
clock.restore();
});
test('#add', function() {
var prev = (new Date()).getMinutes();
var count = 0;
subject.on('minute', function() {
count += 1;
var cur = (new Date()).getMinutes();
assert.notEqual(prev, cur);
prev = cur;
});
// need to make sure that callback is only triggered when minute changes
clock.tick(MINUTE);
clock.tick(1000);
clock.tick(MINUTE);
clock.tick(20 * 1000);
clock.tick(MINUTE);
assert.equal(count, 3);
});
test('#once', function() {
var count = 0;
subject.once('minute', function() {
count += 1;
});
clock.tick(MINUTE);
clock.tick(MINUTE);
clock.tick(MINUTE);
assert.equal(count, 1);
});
test('#remove', function(done) {
var cb = function() {
assert.fail('should not be called');
};
subject.on('minute', cb);
subject.off('minute', cb);
clock.tick(MINUTE);
done();
});
});
suite('dayChange', function() {
var clock;
setup(function() {
clock = sinon.useFakeTimers((new Date(2015, 3, 15)).getTime());
});
teardown(function() {
subject._stop();
subject.removeAllListeners();
clock.restore();
});
test('#add', function() {
var prev = (new Date()).getDate();
var count = 0;
subject.on('day', function() {
count += 1;
var cur = (new Date()).getDate();
assert.notEqual(prev, cur);
prev = cur;
});
// need to make sure that callback is only triggered when day changes
clock.tick(MINUTE);
clock.tick(DAY);
clock.tick(MINUTE);
clock.tick(MINUTE);
clock.tick(DAY);
clock.tick(DAY);
assert.equal(count, 3);
});
test('#once', function() {
var count = 0;
subject.once('day', function() {
count += 1;
});
clock.tick(DAY);
clock.tick(DAY);
clock.tick(DAY);
assert.equal(count, 1);
});
test('#remove', function(done) {
var cb = function() {
assert.fail('should not be called');
};
subject.on('day', cb);
subject.off('day', cb);
clock.tick(DAY);
done();
});
});
suite('_toggleStatusOnVisibilityChange', function() {
var mock;
setup(function() {
mock = sinon.mock(subject);
});
teardown(function() {
mock.restore();
});
test('hidden', function() {
mock.expects('_stop').once();
mock.expects('_start').never();
mock.expects('_exec').never();
subject._toggleStatusOnVisibilityChange(true);
mock.verify();
});
test('visible', function() {
mock.expects('_stop').never();
mock.expects('_start').once();
mock.expects('_exec').once();
subject._toggleStatusOnVisibilityChange(false);
mock.verify();
});
});
});
});

View file

@ -0,0 +1,12 @@
define(function(require) {
'use strict';
var color = require('utils/color');
suite('utils/color', function() {
test('#hexToBackground', function() {
assert.equal(color.hexToBackground('#ff00aa'), 'rgba(255, 0, 170, 0.2)');
});
});
});

View file

@ -0,0 +1,414 @@
define(function(require) {
'use strict';
var Factory = require('test/support/factory');
var IntervalTree = require('interval_tree');
var Overlap = require('utils/overlap');
var Timespan = require('common/timespan');
suite('overlap', function() {
var forever;
var subject;
var baseDate = new Date(2012, 0, 1);
function createEl() {
return document.createElement('span');
}
// all time is based on the same
// day each overlap instance is expected
// to only cover a single date.
function time(hours=0, minutes=0, seconds=0) {
var date = new Date(baseDate.valueOf());
date.setHours(hours);
date.setMinutes(minutes);
date.setSeconds(seconds);
return date;
}
function record(start, end) {
var r = {
element: createEl(),
busytime: Factory('busytime', {
startDate: start,
endDate: end
})
};
r.element.id = r.busytime._id;
return r;
}
function addRecord(startHour, startMin, endHour, endMin) {
var rec = record(
time(startHour, startMin),
time(endHour, endMin)
);
subject.add(rec.busytime, rec.element);
return rec;
}
function addRecords(times) {
var records = [];
times.forEach(function(tm) {
records.push(addRecord.apply(this, tm));
});
return records;
}
function busytimesFromRecords(records) {
return records.map(function(r) {
return r.busytime;
});
}
function conflictSpansFromRecords(records) {
return records.map(function(r) {
return subject.getConflictSpan(r.busytime);
});
}
function elementsFromRecords(records) {
return records.map(function(r) {
return subject.getElement(r.busytime);
});
}
function widthAndLeftFromRecords(records) {
return records.map(function(r) {
var el = subject.getElement(r.busytime);
if (!el) {
return [null, null];
} else {
return [el.style.left, el.style.width];
}
});
}
function classNamesFromRecords(records) {
return elementsFromRecords(records)
.map(function(el) {
// make sure it is always in the same order
return el ? el.className.trim().split(/\s+/).sort().join(' ') : null;
});
}
setup(function() {
subject = new Overlap();
forever = new Timespan(0, Infinity);
});
teardown(function() {
subject.reset();
});
test('initialize', function() {
assert.instanceOf(subject.tree, IntervalTree);
assert.deepEqual(
subject.elements,
{}
);
});
suite('#add', function() {
setup(function() {
subject.reset();
});
test('single', function() {
var records = addRecords([
[1, 0, 2, 0]
]);
assert.deepEqual(
subject.tree.query(forever),
busytimesFromRecords(records)
);
});
test('2 non-conflicting', function() {
var records = addRecords([
[1, 0, 2, 0],
[2, 0, 3, 0]
]);
assert.deepEqual(
subject.tree.query(forever),
busytimesFromRecords(records)
);
assert.deepEqual(subject.conflicts, {});
});
test('2 in conflict', function() {
var records = addRecords([
[1, 0, 2, 0],
[1, 1, 2, 1]
]);
var cs1 = subject.getConflictSpan(records[0].busytime);
assert.ok(cs1, 'conflict found for record 0');
var cs2 = subject.getConflictSpan(records[1].busytime);
assert.ok(cs2, 'conflict found for record 1');
assert.equal(cs1, cs2, 'records share the same conflict span');
assert.equal(cs1.columns.length, 2);
});
test('3 in conflict', function() {
var records = addRecords([
[1, 0, 2, 0],
[1, 1, 2, 1],
[1, 2, 2, 2]
]);
var cs = conflictSpansFromRecords(records);
var cs0 = cs[0];
assert.deepEqual(cs, [cs0, cs0, cs0],
'records share the same conflict span');
assert.equal(cs0.columns.length, 3);
});
test('3 in staggered conflict', function() {
var records = addRecords([
[1, 0, 2, 0],
[1, 30, 2, 30],
[2, 0, 3, 0]
]);
var cs = conflictSpansFromRecords(records);
var cs0 = cs[0];
assert.deepEqual(cs, [cs0, cs0, cs0],
'records share the same conflict span');
assert.equal(cs0.columns.length, 2,
'staggered conflict results in 2 columns');
});
test('event add crossing spans should trigger merge', function() {
// Compose events such that there are 2 separate conflict spans:
var records = addRecords([
[1, 0, 2, 0],
[1, 1, 2, 1],
[3, 0, 3, 30],
[3, 15, 4, 1]
]);
// Ensure there are 2 separate conflict spans.
var cs = conflictSpansFromRecords(records);
assert.equal(cs[0], cs[1]);
assert.equal(cs[2], cs[3]);
assert.notEqual(cs[0], cs[2]);
assert.equal(cs[0].columns.length, 2);
assert.equal(cs[2].columns.length, 2);
// Add an event that partly crosses the 2 spans, triggering a merge.
records.push(addRecord(1, 30, 3, 15));
// There should be one span, now, encompassing all events.
cs = conflictSpansFromRecords(records);
cs.forEach(function(c) {
assert.equal(c, cs[0]);
assert.equal(c.columns.length, 3);
});
});
});
suite('#remove', function() {
setup(function() {
subject.reset();
});
test('span with a gap on event removal should split', function() {
// Compose events such that there are 2 separate conflict spans:
var records = addRecords([
[1, 0, 2, 0],
[1, 1, 2, 1],
// The gap goes here.
[3, 0, 3, 30],
[3, 15, 4, 1]
]);
// Add an event that crosses the 2 spans, triggering a merge.
var mergeRec = addRecord(1, 30, 3, 15);
// There should be one span, now, encompassing all events.
var cs = conflictSpansFromRecords(records);
cs.forEach(function(c) {
assert.equal(c, cs[0]);
assert.equal(c.columns.length, 3);
});
// Removing the event that joins 2 hunks should result in a split.
subject.remove(mergeRec.busytime);
assert.ok(!subject.tree.byId[mergeRec.busytime._id]);
// Ensure there are 2 separate conflict spans.
cs = conflictSpansFromRecords(records);
assert.notEqual(cs[0].id, cs[2].id);
assert.equal(cs[0], cs[1]);
assert.equal(cs[2], cs[3]);
// Columns in the new conflict spans should have collapsed to 2
assert.equal(cs[0].columns.length, 2);
assert.equal(cs[2].columns.length, 2);
});
test('span with no conflicts should self-destruct', function() {
// Non-conflicting events
var records = addRecords([
[1, 0, 2, 0],
[3, 0, 4, 0]
]);
// Cause a staggered conflict.
var conflict = addRecord(1, 30, 3, 30);
// Ensure the conflict exists as expected.
var cs = conflictSpansFromRecords(records);
assert.equal(cs[0], cs[1]);
assert.notEqual(cs[0], undefined);
// Remove the event that causes all the conflict.
subject.remove(conflict.busytime);
// Now, none of the records should be a part of a conflict.
cs = conflictSpansFromRecords(records);
assert.deepEqual(cs, [undefined, undefined]);
});
});
suite('#_updateLayout', function() {
setup(function() {
subject.reset();
});
test('layout updates triggered on #add and #remove', function() {
var records = addRecords([
[1, 0, 2, 0],
[1, 15, 2, 15],
[1, 30, 2, 30],
[3, 0, 4, 0],
[3, 30, 4, 30],
[6, 0, 7, 0]
]);
assert.deepEqual(widthAndLeftFromRecords(records), [
['33.3333%', '33.3333%'], ['0%', '33.3333%'], ['66.6667%', '33.3333%'],
['50%', '50%'], ['0%', '50%'], ['', '']
]);
assert.deepEqual(classNamesFromRecords(records), [
'has-overlaps', 'has-overlaps', 'has-overlaps', 'has-overlaps',
'has-overlaps', ''
]);
var big1 = addRecord(1, 45, 7, 45);
records.push(big1);
assert.deepEqual(widthAndLeftFromRecords(records), [
['25%', '25%'], ['0%', '25%'], ['50%', '25%'], ['25%', '25%'],
['0%', '25%'], ['0%', '25%'], ['75%', '25%']
]);
assert.deepEqual(classNamesFromRecords(records), [
'has-overlaps', 'has-overlaps', 'has-overlaps', 'has-overlaps',
'has-overlaps', 'has-overlaps', 'has-overlaps'
]);
var big2 = addRecord(3, 45, 6, 30);
records.push(big2);
assert.deepEqual(widthAndLeftFromRecords(records), [
['25%', '25%'], ['0%', '25%'], ['50%', '25%'], ['25%', '25%'],
['0%', '25%'], ['0%', '25%'], ['75%', '25%'], ['50%', '25%']
]);
assert.deepEqual(classNamesFromRecords(records), [
'has-overlaps', 'has-overlaps', 'has-overlaps', 'has-overlaps',
'has-overlaps', 'has-overlaps', 'has-overlaps', 'has-overlaps'
]);
var big3 = addRecord(1, 15, 3, 15);
records.push(big3);
assert.deepEqual(widthAndLeftFromRecords(records), [
['20%', '20%'], ['0%', '20%'], ['40%', '20%'], ['20%', '20%'],
['0%', '20%'], ['0%', '20%'], ['60%', '20%'], ['40%', '20%'],
['80%', '20%']
]);
assert.deepEqual(classNamesFromRecords(records), [
'has-overlaps many-overlaps', 'has-overlaps many-overlaps',
'has-overlaps many-overlaps', 'has-overlaps many-overlaps',
'has-overlaps many-overlaps', 'has-overlaps many-overlaps',
'has-overlaps many-overlaps', 'has-overlaps many-overlaps',
'has-overlaps many-overlaps'
]);
subject.remove(big1.busytime);
assert.deepEqual(widthAndLeftFromRecords(records), [
['25%', '25%'], ['0%', '25%'], ['50%', '25%'], ['25%', '25%'],
['0%', '25%'], ['0%', '25%'], [null, null], ['50%', '25%'],
['75%', '25%']
]);
assert.deepEqual(classNamesFromRecords(records), [
'has-overlaps', 'has-overlaps', 'has-overlaps', 'has-overlaps',
'has-overlaps', 'has-overlaps', null, 'has-overlaps', 'has-overlaps'
]);
subject.remove(big2.busytime);
assert.deepEqual(widthAndLeftFromRecords(records), [
['25%', '25%'], ['0%', '25%'], ['50%', '25%'], ['25%', '25%'],
['0%', '25%'], ['', ''], [null, null], [null, null], ['75%', '25%']
]);
assert.deepEqual(classNamesFromRecords(records), [
'has-overlaps', 'has-overlaps', 'has-overlaps', 'has-overlaps',
'has-overlaps', '', null, null, 'has-overlaps'
]);
subject.remove(big3.busytime);
assert.deepEqual(widthAndLeftFromRecords(records), [
['33.3333%', '33.3333%'], ['0%', '33.3333%'], ['66.6667%', '33.3333%'],
['0%', '50%'], ['50%', '50%'], ['', ''],
[null, null], [null, null], [null, null]
]);
assert.deepEqual(classNamesFromRecords(records), [
'has-overlaps', 'has-overlaps', 'has-overlaps', 'has-overlaps',
'has-overlaps', '', null, null, null
]);
});
test('bug 817763 - collapse columns on conflict delete', function() {
var records = addRecords([
[1, 0, 1, 15],
[1, 30, 2, 0]
]);
var to_delete = addRecord(1, 0, 2, 0);
records.push(to_delete);
assert.deepEqual(widthAndLeftFromRecords(records), [
['50%', '50%'], ['50%', '50%'], ['0%', '50%']
]);
assert.deepEqual(classNamesFromRecords(records), [
'has-overlaps', 'has-overlaps', 'has-overlaps'
]);
subject.remove(to_delete.busytime);
assert.deepEqual(widthAndLeftFromRecords(records), [
['', ''], ['', ''], [null, null]
]);
assert.deepEqual(classNamesFromRecords(records), [
'', '', null
]);
});
});
});
});

View file

@ -0,0 +1,62 @@
define(function(require) {
'use strict';
var URI = require('utils/uri');
suite('uri', function() {
suite('#getPort', function() {
test('with port', function() {
var port = URI.getPort('lolcats.com:1337');
assert.deepEqual(1337, port);
});
test('without port', function() {
var port = URI.getPort('https://lolcats.com/index.html');
assert.deepEqual(null, port);
});
test('with port and path', function() {
var port = URI.getPort('lolcats.com:1337/index.html');
assert.deepEqual(1337, port);
});
});
suite('#getScheme', function() {
test('http', function() {
var scheme = URI.getScheme('http://lolcats.com');
assert.deepEqual('http', scheme);
});
test('http with port', function() {
var scheme = URI.getScheme('http://lolcats.com:1337');
assert.deepEqual('http', scheme);
});
test('https', function() {
var scheme = URI.getScheme('https://lolcats.com');
assert.deepEqual('https', scheme);
});
test('https with port', function() {
var scheme = URI.getScheme('https://lolcats.com:443');
assert.deepEqual('https', scheme);
});
test('no scheme', function() {
var scheme = URI.getScheme('lolcats.com');
assert.deepEqual(null, scheme);
});
test('no scheme with port', function() {
var scheme = URI.getScheme('lolcats.com:1337');
assert.deepEqual(null, scheme);
});
test('unsupported scheme', function() {
var scheme = URI.getScheme('smb://lolcats.com');
assert.deepEqual(null, scheme);
});
});
});
});

View file

@ -0,0 +1,275 @@
define(function(require) {
'use strict';
var CalendarError = require('common/error');
var View = require('view');
suite('view', function() {
var el, subject;
setup(function() {
el = document.createElement('div');
el.id = 'view';
el.innerHTML = [
'<section role="status">',
'<div class="errors"></div>',
'</section>'
].join('');
document.body.appendChild(el);
subject = new View();
subject.selectors = {
element: '#view',
errors: 'section[role="status"] .errors',
status: 'section[role="status"]'
};
});
teardown(function() {
el.parentNode.removeChild(el);
el = null;
});
suite('initialization', function() {
test('string', function() {
subject = new View('#view');
assert.equal(subject.selectors.element, '#view');
});
test('object', function() {
subject = new View({ controller: 'a' });
assert.equal(subject.controller, 'a');
assert.ok(!subject.selectors);
});
});
test('#element', function() {
assert.equal(subject.element.id, el.id);
});
test('#calendarId', function() {
assert.equal(
subject.calendarId('1'),
'calendar-id-1'
);
assert.equal(
subject.calendarId({ calendarId: 1 }),
'calendar-id-1'
);
});
suite('clean css', function() {
test('#cssClean', function() {
var input = 'one/two/^three';
var output = subject.cssClean(input);
assert.equal(output, 'one-two--three');
});
});
suite('#_findElement', function() {
var expected;
setup(function() {
var thing = document.createElement('div');
thing.id = 'foo';
el.appendChild(thing);
});
setup(function() {
subject.selectors.myThing = '#foo';
expected = el.querySelector('#foo');
assert.ok(expected);
});
test('single lookup', function() {
var result = subject._findElement('myThing');
assert.equal(result, expected);
assert.equal(subject._myThingElement, expected);
});
test('query all', function() {
var result = subject._findElement('myThing', true);
assert.equal(result[0], expected);
assert.equal(subject._myThingElement[0], expected);
});
});
suite('#idForModel', function() {
test('string', function() {
assert.equal(
subject.idForModel('prefix-', 1),
'prefix-1'
);
});
test('object', function() {
assert.equal(
subject.idForModel('prefix-', { _id: 2 }),
'prefix-2'
);
});
});
suite('#delegate', function() {
var element;
var triggerEvent;
suiteSetup(function() {
triggerEvent = testSupport.calendar.triggerEvent;
});
setup(function() {
element = document.createElement('div');
element.id = 'test';
var html = '<ol>' +
'<li class="hit">hit</li>' +
'<li class="foo">foo</li>' +
'</ol>';
element.innerHTML = html;
document.body.appendChild(element);
});
teardown(function() {
element.parentNode.removeChild(element);
});
test('matches - fn', function(done) {
function next() {
if (!(--pending)) {
done();
}
}
var pending = 2;
function handleEvent(event, givenTarget) {
assert.ok(event);
assert.equal(givenTarget, target);
next();
}
var object = {
handleEvent: handleEvent
};
subject.delegate(element, 'click', 'li.hit', handleEvent);
subject.delegate(element, 'click', 'li.hit', object);
// we want to click hit
var target = element.querySelector('li.hit');
assert.ok(target);
triggerEvent(target, 'click');
});
test('miss - on element', function(done) {
subject.delegate(element, 'click', null, function() {
done(new Error('should not triger'));
});
setTimeout(function() {
done();
});
triggerEvent(element, 'click');
});
test('miss - on child', function(done) {
subject.delegate(element, 'click', null, function() {
done(new Error('should not triger'));
});
setTimeout(function() {
done();
});
var target = element.querySelector('li.foo');
assert.ok(target);
triggerEvent(target, 'click');
});
});
suite('#showErrors', function() {
test('successfully display', function() {
var errors = [{ name: 'error-default' }];
subject.showErrors(errors);
var list = subject.status.classList;
errors = subject.errors.textContent;
assert.ok(errors);
assert.include(errors, navigator.mozL10n.get('error-default'));
assert.ok(list.contains(subject.activeClass));
testSupport.calendar.triggerEvent(subject.status, 'animationend');
assert.ok(!list.contains(subject.activeClass));
});
/**
* Verifies that built in errors map to a l10n field.
*/
function verifyBuiltIn(error, expectedID) {
test('show built-in error: CalendarError.' + error, function() {
var err = new CalendarError[error]();
var msg = navigator.mozL10n.get(expectedID) || expectedID;
subject.showErrors(err);
assert.equal(subject.errors.textContent, msg);
subject.hideErrors();
});
}
verifyBuiltIn('Authentication', 'error-unauthenticated');
verifyBuiltIn('ServerFailure', 'error-internal-server-error');
verifyBuiltIn('ServerFailure', 'error-internal-server-error');
});
test('#onactive', function() {
var seen = 0;
var dispatched;
assert.isFalse(subject.seen);
subject.dispatch = function() {
dispatched = arguments;
};
subject.onfirstseen = function() {
seen += 1;
};
subject.onactive('foo');
assert.isTrue(subject.seen);
assert.equal(dispatched[0], 'foo');
assert.ok(el.classList.contains(subject.activeClass));
subject.onactive();
assert.equal(seen, 1);
});
test('#oninactive', function() {
subject.onactive();
subject.oninactive();
assert.isFalse(el.classList.contains(subject.activeClass));
});
});
});

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

View file

@ -0,0 +1,163 @@
define(function(require) {
'use strict';
var Manager = require('worker/manager');
/**
* This test should cover the basics of
* both the manager and the thread.
*/
suite('worker/manager', function() {
var subject;
function MockWorker(handler) {
return function WorkerLike(url) {
this.url = url;
this.onerror = this.onmessage = this.terminate = Math.min;
this.sent = [];
this.addEventListener = function(type, func) {
if (type == 'error') {
this.onerror = func;
} else if (type == 'message') {
this.onmessage = func;
}
};
this.respond = function(message) {
this.onmessage({data: message});
};
var self = this;
this.postMessage = function(msg) {
setTimeout(function() {handler.call(self, msg);}, 10);
};
};
}
setup(function() {
subject = new Manager();
subject.Worker = MockWorker(Math.min);
});
teardown(function() {
});
suite('#add', function() {
test('single role', function() {
subject.add('test', 'foo.js');
var result = subject._ensureActiveWorker('test').instance;
assert.instanceOf(result, subject.Worker);
assert.ok(result.url);
assert.include(result.url, 'foo.js');
});
test('multi role', function() {
subject.add(['one', 'two'], 'bar.js');
var resultOne = subject._ensureActiveWorker('one');
var resultTwo = subject._ensureActiveWorker('two');
assert.ok(resultOne);
assert.equal(resultOne, resultTwo);
assert.throws(function() {
subject._ensureActiveWorker('three');
});
});
});
suite('worker acceptance', function() {
function mockHandler(message) {
/*jshint validthis:true */
assert.equal(message[0], '_dispatch');
var data = message[1], self = this;
assert.equal(typeof data.id, 'number');
if (data.payload[0] == 'explode') {
self.onerror(new Error('BOOM'));
} else if (data.payload[0] == 'stream') {
self.respond([data.id + ' stream', 'data', 'data one']);
setTimeout(function() {
self.respond([data.id + ' stream', 'data', 'data two']);
setTimeout(function() {
self.respond([data.id + ' end', 'stream finito']);
}, 20);
}, 20);
} else if (data.payload[0] == 'stream/explode') {
self.respond([data.id + ' stream', 'data', 'data one']);
setTimeout(function() {
self.onerror(new Error('BOOM'));
}, 20);
} else {
self.respond([data.id + ' end', 'response']);
}
}
var obj = {prop: 'value'};
setup(function() {
subject.Worker = MockWorker(mockHandler);
subject.add(
'test', '/test/unit/fixtures/relay_worker.js'
);
});
test('#request', function(done) {
subject.request('test', 'foobar', obj, function(data) {
done(function() {
assert.deepEqual(data, 'response');
});
});
});
test('#request /w error object', function(done) {
subject.request('test', 'explode', function(err) {
done(function() {
assert.instanceOf(err, Error);
assert.equal(err.message, 'BOOM');
});
});
});
test('#stream', function(done) {
var stream = subject.stream('test', 'stream');
var dataReceived = [], errorReceived = null;
stream.on('data', function(data) { dataReceived.push(data); });
stream.on('error', function(error) { errorReceived = error; });
stream.request(function(data) {
done(function() {
assert.deepEqual(dataReceived, ['data one', 'data two']);
assert.equal(data, 'stream finito');
assert.equal(errorReceived, null);
});
});
// can only request stream once
assert.throws(function() {
stream.request();
});
});
test('#stream error', function(done) {
var stream = subject.stream('test', 'stream/explode');
var dataReceived = [], errorReceived = null;
stream.on('data', function(data) { dataReceived.push(data); });
stream.on('error', function(error) { errorReceived = error; });
stream.request(function(error) {
done(function() {
assert.deepEqual(dataReceived, ['data one']);
assert.instanceOf(error, Error);
assert.instanceOf(errorReceived, Error);
});
});
});
});
});
});