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:
parent
93e3a48004
commit
b38dea0597
25901 changed files with 9982925 additions and 369 deletions
18
gaia/apps/calendar/test/marionette/.jshintrc
Executable file
18
gaia/apps/calendar/test/marionette/.jshintrc
Executable file
|
|
@ -0,0 +1,18 @@
|
|||
{
|
||||
"extends": "../../../../.jshintrc",
|
||||
"node": true,
|
||||
"predef": [
|
||||
"suite",
|
||||
"test",
|
||||
"setup",
|
||||
"teardown",
|
||||
"suiteSetup",
|
||||
"suiteTeardown",
|
||||
"marionette",
|
||||
"marionetteScriptFinished",
|
||||
"require",
|
||||
"define",
|
||||
"requireApp",
|
||||
"sinon"
|
||||
]
|
||||
}
|
||||
134
gaia/apps/calendar/test/marionette/alarm_test.js
Executable file
134
gaia/apps/calendar/test/marionette/alarm_test.js
Executable file
|
|
@ -0,0 +1,134 @@
|
|||
'use strict';
|
||||
|
||||
var Calendar = require('./lib/calendar'),
|
||||
assert = require('chai').assert;
|
||||
|
||||
var SHARED_PATH = __dirname + '/../../../../shared/test/integration';
|
||||
|
||||
marionette('alarm', function() {
|
||||
var app;
|
||||
var client = marionette.client({
|
||||
profile: {
|
||||
prefs: {
|
||||
// we need to disable the keyboard to avoid intermittent failures on
|
||||
// Travis (transitions might take longer to run and block UI)
|
||||
'dom.mozInputMethod.enabled': false,
|
||||
// Do not require the B2G-desktop app window to have focus (as per the
|
||||
// system window manager) in order for it to do focus-related things.
|
||||
'focusmanager.testmode': true
|
||||
}
|
||||
},
|
||||
desiredCapabilities: { raisesAccessibilityExceptions: false }
|
||||
});
|
||||
|
||||
suite(' > with alarm mocks', function() {
|
||||
|
||||
setup(function() {
|
||||
app = new Calendar(client);
|
||||
client.contentScript.inject(SHARED_PATH +
|
||||
'/mock_navigator_mozalarms.js');
|
||||
client.contentScript.inject(SHARED_PATH +
|
||||
'/mock_navigator_moz_set_message_handler.js');
|
||||
app.launch();
|
||||
});
|
||||
|
||||
suite('create event with a single reminder', function() {
|
||||
setup(function() {
|
||||
createEventWithReminders(app, ['5 minutes before']);
|
||||
app.monthDay.scrollToEvent();
|
||||
});
|
||||
|
||||
test('should display reminder in read-only event view', function() {
|
||||
var reminders = getEventReminders(app);
|
||||
assert.equal(reminders.length, 1);
|
||||
assert.include(reminders, '5 minutes before');
|
||||
});
|
||||
|
||||
test('should add alarm to alarms api', function() {
|
||||
var alarms = getMozAlarms(client);
|
||||
assert.equal(alarms.length, 1);
|
||||
});
|
||||
});
|
||||
|
||||
suite('create an event with two valid reminders', function() {
|
||||
setup(function() {
|
||||
createEventWithReminders(app, ['5 minutes before',
|
||||
'15 minutes before']);
|
||||
app.monthDay.scrollToEvent();
|
||||
});
|
||||
|
||||
test('should display both reminders in read-only view', function() {
|
||||
var reminders = getEventReminders(app);
|
||||
assert.equal(reminders.length, 2);
|
||||
assert.include(reminders, '5 minutes before');
|
||||
assert.include(reminders, '15 minutes before');
|
||||
});
|
||||
|
||||
test('should only fire one alarm', function() {
|
||||
var alarms = getMozAlarms(client);
|
||||
assert.equal(alarms.length, 1);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
suite('> without alarm mocks', function() {
|
||||
setup(function() {
|
||||
app = new Calendar(client);
|
||||
app.launch();
|
||||
});
|
||||
|
||||
suite('create two events with simultaneous reminders', function() {
|
||||
setup(function() {
|
||||
var startTime = Date.now() + 5000; // 5 seconds in future
|
||||
app.createEvent({
|
||||
title: 'First Event',
|
||||
startDate: new Date(startTime),
|
||||
reminders: ['At time of event']
|
||||
});
|
||||
app.createEvent({
|
||||
title: 'Second Event',
|
||||
startDate: new Date(startTime),
|
||||
reminders: ['At time of event']
|
||||
});
|
||||
});
|
||||
|
||||
test('both events trigger notifications', function() {
|
||||
client.switchToFrame();
|
||||
client.waitFor(function() {
|
||||
var notificationSelector = '#desktop-notifications-container > div';
|
||||
return client.findElements(notificationSelector).length > 1;
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
function createEventWithReminders(app, reminders) {
|
||||
app.createEvent({
|
||||
title: 'Panic!',
|
||||
location: 'The Disco',
|
||||
startDate: new Date(),
|
||||
reminders: reminders
|
||||
});
|
||||
}
|
||||
|
||||
function getEventReminders(app) {
|
||||
var event = app.monthDay.events[0];
|
||||
event.click();
|
||||
|
||||
app.readEvent.waitForDisplay();
|
||||
return app.readEvent.alarms;
|
||||
}
|
||||
|
||||
function getMozAlarms(client) {
|
||||
var alarms;
|
||||
client.waitFor(function() {
|
||||
alarms = client.executeScript(function() {
|
||||
return window.wrappedJSObject.navigator.__mozFakeAlarms;
|
||||
});
|
||||
|
||||
return alarms && alarms.length;
|
||||
});
|
||||
|
||||
return alarms;
|
||||
}
|
||||
130
gaia/apps/calendar/test/marionette/caldav_test.js
Executable file
130
gaia/apps/calendar/test/marionette/caldav_test.js
Executable file
|
|
@ -0,0 +1,130 @@
|
|||
'use strict';
|
||||
|
||||
var Calendar = require('./lib/calendar'),
|
||||
Radicale = require('./lib/radicale'),
|
||||
assert = require('chai').assert,
|
||||
dateFormat = require('dateformat');
|
||||
|
||||
var ACCOUNT_USERNAME = 'firefox-os',
|
||||
TITLE = 'Go for a dream',
|
||||
DATE_PATTERN = 'yyyymmdd"T"HHMMssZ';
|
||||
|
||||
marionette('configure CalDAV accounts', function() {
|
||||
var client = marionette.client({
|
||||
profile: {
|
||||
prefs: {
|
||||
// we need to disable the keyboard to avoid intermittent failures on
|
||||
// Travis (transitions might take longer to run and block UI)
|
||||
'dom.mozInputMethod.enabled': false,
|
||||
// Do not require the B2G-desktop app window to have focus (as per the
|
||||
// system window manager) in order for it to do focus-related things.
|
||||
'focusmanager.testmode': true,
|
||||
}
|
||||
},
|
||||
desiredCapabilities: { raisesAccessibilityExceptions: false }
|
||||
});
|
||||
|
||||
var serverHelper = new Radicale(),
|
||||
app = null;
|
||||
|
||||
setup(function(done) {
|
||||
app = new Calendar(client);
|
||||
app.launch();
|
||||
|
||||
serverHelper.start(function(error, port) {
|
||||
if (error) {
|
||||
return done(error);
|
||||
}
|
||||
|
||||
var accountUrl = 'http://localhost:' + port + '/' + ACCOUNT_USERNAME,
|
||||
startDate = new Date(),
|
||||
endDate = new Date(),
|
||||
event = {};
|
||||
|
||||
app.setupAccount({
|
||||
accountType: 'caldav',
|
||||
user: ACCOUNT_USERNAME,
|
||||
fullUrl: accountUrl
|
||||
});
|
||||
|
||||
// Make sure we have a event item
|
||||
// in the top of event-list view in month view.
|
||||
startDate.setMinutes(0);
|
||||
endDate.setMinutes(59);
|
||||
event = {
|
||||
startDate: dateFormat(startDate, DATE_PATTERN),
|
||||
endDate: dateFormat(endDate, DATE_PATTERN),
|
||||
title: TITLE
|
||||
};
|
||||
serverHelper.addEvent(ACCOUNT_USERNAME, event);
|
||||
|
||||
app.sync();
|
||||
// Make sure we start the server before the setup is ended.
|
||||
done();
|
||||
});
|
||||
});
|
||||
|
||||
teardown(function(done) {
|
||||
// No matter how, we close the server in the end.
|
||||
try {
|
||||
serverHelper.removeAllEvents();
|
||||
} catch (error) {
|
||||
console.error(error.message);
|
||||
} finally {
|
||||
serverHelper.close(done);
|
||||
}
|
||||
});
|
||||
|
||||
test('should show a event', function() {
|
||||
var event = app.monthDay.events[0];
|
||||
|
||||
// Scroll so that the first one is in view and click it.
|
||||
app.monthDay.scrollToEvent(event);
|
||||
event.click();
|
||||
|
||||
assertEvent(ACCOUNT_USERNAME, TITLE);
|
||||
});
|
||||
|
||||
test('events from disabled calendars should not be displayed', function() {
|
||||
var events = app.monthDay.events;
|
||||
assert.equal(events.length, 1, 'at least one event');
|
||||
|
||||
app.openSettingsView();
|
||||
app.settings.toggleCalendar(ACCOUNT_USERNAME);
|
||||
app.closeSettingsView();
|
||||
|
||||
assert.deepEqual(app.monthDay.events, [], 'events should be removed');
|
||||
|
||||
var startDate = new Date();
|
||||
var endDate = new Date();
|
||||
endDate.setHours(endDate.getHours() + 1);
|
||||
var event = {
|
||||
startDate: dateFormat(startDate, DATE_PATTERN),
|
||||
endDate: dateFormat(endDate, DATE_PATTERN),
|
||||
title: TITLE
|
||||
};
|
||||
serverHelper.addEvent(ACCOUNT_USERNAME, event);
|
||||
|
||||
app.openSettingsView();
|
||||
app.settings.sync();
|
||||
app.closeSettingsView();
|
||||
|
||||
assert.deepEqual(app.monthDay.events, [], 'events should not be added');
|
||||
});
|
||||
|
||||
function assertEvent(username, title) {
|
||||
app.readEvent.waitForDisplay();
|
||||
|
||||
assert.deepEqual(
|
||||
app.readEvent.title,
|
||||
title,
|
||||
'event should have correct title'
|
||||
);
|
||||
// Make sure the event is created in the CalDAV username.
|
||||
assert.deepEqual(
|
||||
app.readEvent.calendar,
|
||||
username,
|
||||
'event should be created by the correct user'
|
||||
);
|
||||
}
|
||||
});
|
||||
257
gaia/apps/calendar/test/marionette/create_event_test.js
Executable file
257
gaia/apps/calendar/test/marionette/create_event_test.js
Executable file
|
|
@ -0,0 +1,257 @@
|
|||
'use strict';
|
||||
|
||||
var Calendar = require('./lib/calendar');
|
||||
var assert = require('chai').assert;
|
||||
|
||||
marionette('creating events', function() {
|
||||
var client = marionette.client({
|
||||
profile: {
|
||||
prefs: {
|
||||
// we need to disable the keyboard to avoid intermittent failures on
|
||||
// Travis (transitions might take longer to run and block UI)
|
||||
'dom.mozInputMethod.enabled': false,
|
||||
// Do not require the B2G-desktop app window to have focus (as per the
|
||||
// system window manager) in order for it to do focus-related things.
|
||||
'focusmanager.testmode': true,
|
||||
}
|
||||
},
|
||||
desiredCapabilities: { raisesAccessibilityExceptions: false }
|
||||
});
|
||||
|
||||
var app;
|
||||
|
||||
var startDate = new Date('Sep 08 1991 12:34:56');
|
||||
var startDateNextHour = new Date(startDate);
|
||||
startDateNextHour.setHours(startDate.getHours() + 1);
|
||||
var startDateNextDay = new Date(startDate);
|
||||
startDateNextDay.setDate(startDate.getDate() + 1);
|
||||
|
||||
var scenarios = [
|
||||
{
|
||||
name: 'one day',
|
||||
allDay: false,
|
||||
startDate: startDate,
|
||||
endDate: startDateNextHour,
|
||||
reminders: ['5 minutes before'],
|
||||
verifyMonthView: function() {
|
||||
var event = app.monthDay.events[0];
|
||||
assert.equal(event.startTime, '12:34 PM', 'start time');
|
||||
assert.equal(event.endTime, '1:34 PM', 'end time');
|
||||
},
|
||||
verifyReadEventView: function() {
|
||||
var readEvent = app.readEvent;
|
||||
assert.equal(
|
||||
readEvent.durationTime,
|
||||
'Sunday, September 08, 1991\n' +
|
||||
'from 12:34 PM to 1:34 PM',
|
||||
'duration time'
|
||||
);
|
||||
assert.equal(
|
||||
readEvent.reminders[0].text(),
|
||||
'5 minutes before',
|
||||
'reminder'
|
||||
);
|
||||
}
|
||||
},
|
||||
{
|
||||
name: 'multiple day',
|
||||
allDay: false,
|
||||
startDate: startDate,
|
||||
endDate: startDateNextDay,
|
||||
reminders: ['5 minutes before'],
|
||||
verifyMonthView: function() {
|
||||
var month = app.month;
|
||||
assert.lengthOf(month.squareDots(month.daySquares[7]), 1);
|
||||
assert.lengthOf(month.squareDots(month.daySquares[8]), 1);
|
||||
assert.equal(month.busyDots.length, 2, '2 busy dots');
|
||||
|
||||
var event = app.monthDay.events[0];
|
||||
assert.equal(event.startTime, '12:34 PM', 'start time');
|
||||
assert.equal(event.endTime, '12:34 PM', 'end time');
|
||||
},
|
||||
verifyReadEventView: function() {
|
||||
var readEvent = app.readEvent;
|
||||
assert.equal(
|
||||
readEvent.durationTime,
|
||||
'From 12:34 PM Sunday, September 08, 1991\n' +
|
||||
'to 12:34 PM Monday, September 09, 1991',
|
||||
'duration time'
|
||||
);
|
||||
assert.equal(
|
||||
readEvent.reminders[0].text(),
|
||||
'5 minutes before',
|
||||
'reminder'
|
||||
);
|
||||
}
|
||||
},
|
||||
{
|
||||
name: 'one all day',
|
||||
allDay: true,
|
||||
startDate: startDate,
|
||||
endDate: startDateNextHour,
|
||||
reminders: ['On day of event'],
|
||||
verifyMonthView: function() {
|
||||
var event = app.monthDay.events[0];
|
||||
assert.equal(event.allDay, 'All Day', 'event hour');
|
||||
},
|
||||
verifyReadEventView: function() {
|
||||
var readEvent = app.readEvent;
|
||||
assert.equal(
|
||||
readEvent.durationTime,
|
||||
'All day\n' +
|
||||
'Sunday, September 08, 1991',
|
||||
'duration time'
|
||||
);
|
||||
assert.equal(
|
||||
readEvent.reminders[0].text(),
|
||||
'On day of event',
|
||||
'reminder'
|
||||
);
|
||||
}
|
||||
},
|
||||
{
|
||||
name: 'multiple all day',
|
||||
allDay: true,
|
||||
startDate: startDate,
|
||||
endDate: startDateNextDay,
|
||||
reminders: ['On day of event'],
|
||||
verifyMonthView: function() {
|
||||
var month = app.month;
|
||||
assert.lengthOf(month.squareDots(month.daySquares[7]), 1);
|
||||
assert.lengthOf(month.squareDots(month.daySquares[8]), 1);
|
||||
assert.equal(month.busyDots.length, 2, '2 busy dots');
|
||||
assert.equal(
|
||||
app.monthDay.events[0].allDay,
|
||||
'All Day',
|
||||
'event hour'
|
||||
);
|
||||
},
|
||||
verifyReadEventView: function() {
|
||||
var readEvent = app.readEvent;
|
||||
assert.equal(
|
||||
readEvent.durationTime,
|
||||
'All day from Sunday, September 08, 1991\n' +
|
||||
'to Monday, September 09, 1991',
|
||||
'duration time'
|
||||
);
|
||||
assert.equal(
|
||||
readEvent.reminders[0].text(),
|
||||
'On day of event',
|
||||
'reminder'
|
||||
);
|
||||
}
|
||||
},
|
||||
{
|
||||
name: '24 hour format',
|
||||
allDay: false,
|
||||
startDate: startDate,
|
||||
endDate: startDateNextHour,
|
||||
reminders: ['5 minutes before'],
|
||||
verifyMonthView: function() {
|
||||
app.switch24HourTimeFormat();
|
||||
var event = app.monthDay.events[0];
|
||||
assert.equal(event.startTime, '12:34', 'start time');
|
||||
assert.equal(event.endTime, '13:34', 'end time');
|
||||
},
|
||||
verifyReadEventView: function() {
|
||||
app.switch24HourTimeFormat();
|
||||
var readEvent = app.readEvent;
|
||||
assert.equal(
|
||||
readEvent.durationTime,
|
||||
'Sunday, September 08, 1991\n' +
|
||||
'from 12:34 to 13:34',
|
||||
'duration time'
|
||||
);
|
||||
assert.equal(
|
||||
readEvent.reminders[0].text(),
|
||||
'5 minutes before',
|
||||
'reminder'
|
||||
);
|
||||
}
|
||||
}
|
||||
];
|
||||
|
||||
setup(function() {
|
||||
app = new Calendar(client);
|
||||
app.launch();
|
||||
});
|
||||
|
||||
scenarios.forEach(function(scenario) {
|
||||
suite('creating ' + scenario.name + ' event', function() {
|
||||
setup(function() {
|
||||
// we use default title/description/location to avoid duplication but
|
||||
// allow overriding if needed
|
||||
scenario.title = scenario.title ||
|
||||
'Puppy Bowl dogefortlongtextfotestloremipsumdolorsitamet';
|
||||
scenario.description = scenario.description ||
|
||||
'lorem ipsum dolor sit amet maecennas ullamcor';
|
||||
scenario.location = scenario.location ||
|
||||
'Animal Planet reallylongwordthatshouldnotoverflowbecausewewrap';
|
||||
|
||||
app.createEvent(scenario);
|
||||
|
||||
app.month.waitForDisplay();
|
||||
});
|
||||
|
||||
test('should display the created event in months day', function() {
|
||||
var event = app.monthDay.events[0];
|
||||
assert.equal(event.title, scenario.title, 'event title');
|
||||
assert.equal(event.address, scenario.location, 'event location');
|
||||
scenario.verifyMonthView();
|
||||
});
|
||||
|
||||
suite('opening event in read view', function() {
|
||||
setup(function() {
|
||||
var event = app.monthDay.events[0];
|
||||
// Scroll so that the first one is in view and click it.
|
||||
app.monthDay.scrollToEvent(event);
|
||||
event.click();
|
||||
// Wait until the read view is displayed.
|
||||
app.readEvent.waitForDisplay();
|
||||
});
|
||||
|
||||
test('should display the created event in read view', function() {
|
||||
var readEvent = app.readEvent;
|
||||
assert.equal(readEvent.title, scenario.title, 'event title');
|
||||
assert.equal(
|
||||
readEvent.description,
|
||||
scenario.description,
|
||||
'event description'
|
||||
);
|
||||
assert.equal(readEvent.location, scenario.location, 'location');
|
||||
assert.equal(readEvent.calendar, 'Offline calendar');
|
||||
assert.equal(readEvent.calendarColor, 'rgb(249, 124, 23)');
|
||||
scenario.verifyReadEventView();
|
||||
});
|
||||
|
||||
test('should not overflow title, location, or description',
|
||||
function() {
|
||||
var readEvent = app.readEvent;
|
||||
app.checkOverflow(readEvent.titleContainer, 'title');
|
||||
app.checkOverflow(readEvent.descriptionContainer, 'description');
|
||||
app.checkOverflow(readEvent.locationContainer, 'location');
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
test('only enable save button if title or location are set', function() {
|
||||
app.openModifyEventView();
|
||||
var editEvent = app.editEvent;
|
||||
client.waitFor(function() {
|
||||
return editEvent.saveButton.getAttribute('disabled') === 'true';
|
||||
});
|
||||
editEvent.title = 'foo';
|
||||
client.waitFor(function() {
|
||||
return editEvent.saveButton.getAttribute('disabled') === 'false';
|
||||
});
|
||||
editEvent.title = '';
|
||||
client.waitFor(function() {
|
||||
return editEvent.saveButton.getAttribute('disabled') === 'true';
|
||||
});
|
||||
editEvent.location = 'bar';
|
||||
client.waitFor(function() {
|
||||
return editEvent.saveButton.getAttribute('disabled') === 'false';
|
||||
});
|
||||
});
|
||||
});
|
||||
364
gaia/apps/calendar/test/marionette/day_view_test.js
Executable file
364
gaia/apps/calendar/test/marionette/day_view_test.js
Executable file
|
|
@ -0,0 +1,364 @@
|
|||
'use strict';
|
||||
|
||||
var Calendar = require('./lib/calendar'),
|
||||
assert = require('chai').assert;
|
||||
|
||||
marionette('day view', function() {
|
||||
var app;
|
||||
var day;
|
||||
var month;
|
||||
var client = marionette.client({
|
||||
profile: {
|
||||
prefs: {
|
||||
// we need to disable the keyboard to avoid intermittent failures on
|
||||
// Travis (transitions might take longer to run and block UI)
|
||||
'dom.mozInputMethod.enabled': false,
|
||||
// Do not require the B2G-desktop app window to have focus (as per the
|
||||
// system window manager) in order for it to do focus-related things.
|
||||
'focusmanager.testmode': true,
|
||||
}
|
||||
},
|
||||
desiredCapabilities: { raisesAccessibilityExceptions: false }
|
||||
});
|
||||
|
||||
setup(function() {
|
||||
app = new Calendar(client);
|
||||
app.launch();
|
||||
app.openDayView();
|
||||
day = app.day;
|
||||
month = app.month;
|
||||
});
|
||||
|
||||
test('header copy should not overflow', function() {
|
||||
// XXX: we don't use app.checkOverflow() because of Bug 971691
|
||||
// 20 chars is a "safe" limit if font-family is Fira Sans
|
||||
assert.operator(app.headerContent.text().length, '<', 21);
|
||||
});
|
||||
|
||||
suite('events longer than 2h', function() {
|
||||
setup(function() {
|
||||
app.createEvent({
|
||||
title: 'Lorem Ipsum',
|
||||
location: 'Dolor Amet',
|
||||
startHour: 0,
|
||||
duration: 3
|
||||
});
|
||||
day.waitForDisplay();
|
||||
day.waitForHourScrollEnd(0);
|
||||
});
|
||||
|
||||
test('click after first hour', function() {
|
||||
day.events[0].click();
|
||||
app.readEvent.waitForDisplay();
|
||||
|
||||
assert.equal(
|
||||
app.readEvent.title,
|
||||
'Lorem Ipsum',
|
||||
'title should match'
|
||||
);
|
||||
});
|
||||
|
||||
test('double tap after event end', function() {
|
||||
// we need to actually grab the event position + height to avoid issues
|
||||
// with DST (see Bug 981441)
|
||||
var event = day.events[0];
|
||||
var body = client.findElement('body');
|
||||
var position = event.location();
|
||||
var size = event.size();
|
||||
|
||||
app.actions
|
||||
.doubleTap(body, position.x + 20, position.y + size.height + 20)
|
||||
.perform();
|
||||
|
||||
// there is a delay between tap and view display
|
||||
app.editEvent.waitForDisplay();
|
||||
});
|
||||
});
|
||||
|
||||
test('all day', function() {
|
||||
assert(day.allDayIcon.displayed(), 'should display all day icon');
|
||||
});
|
||||
|
||||
test('event', function() {
|
||||
var eventData = {
|
||||
title: 'Test Day View',
|
||||
location: 'Somewhere',
|
||||
startHour: 1,
|
||||
reminders: ['5 minutes before']
|
||||
};
|
||||
app.createEvent(eventData);
|
||||
|
||||
var event = day.events[0];
|
||||
|
||||
assert.equal(
|
||||
event.title.text(), eventData.title, 'display event title'
|
||||
);
|
||||
|
||||
assert.equal(
|
||||
event.address.text(), eventData.location, 'display location'
|
||||
);
|
||||
|
||||
// calendar colors
|
||||
assert.match(
|
||||
event.cssProperty('background-color'),
|
||||
/rgba\(.+0.2\)/,
|
||||
'should set bg color'
|
||||
);
|
||||
|
||||
var borderColor = event.cssProperty('border-left-color');
|
||||
assert.ok(
|
||||
borderColor,
|
||||
'should set the border color'
|
||||
);
|
||||
|
||||
assert.ok(
|
||||
parseFloat(event.cssProperty('border-left-width')) > 0,
|
||||
'should have border'
|
||||
);
|
||||
|
||||
assert.equal(
|
||||
event.cssProperty('border-left-style'),
|
||||
'solid',
|
||||
'should have solid border'
|
||||
);
|
||||
|
||||
assert.equal(
|
||||
event.iconAlarm.cssProperty('color'),
|
||||
borderColor,
|
||||
'alarm icon color should match border color'
|
||||
);
|
||||
|
||||
});
|
||||
|
||||
suite('overlaps', function() {
|
||||
|
||||
function createEvent(title, location) {
|
||||
app.createEvent({
|
||||
title: title,
|
||||
location: location,
|
||||
startHour: 1
|
||||
});
|
||||
}
|
||||
|
||||
function eventsTitleAndLocationDisplayed() {
|
||||
return day.events.every(titleAndLocationDisplayed);
|
||||
}
|
||||
|
||||
function titleAndLocationDisplayed(event) {
|
||||
return checkDisplay(event.title) &&
|
||||
checkDisplay(event.address);
|
||||
}
|
||||
|
||||
function checkDisplay(el, name, i) {
|
||||
return el.text().length > 0 && el.displayed();
|
||||
}
|
||||
|
||||
test('event details display', function() {
|
||||
createEvent('1 Overlap', 'Somewhere');
|
||||
createEvent('2 Overlap', 'Somewhere Else');
|
||||
createEvent('3 Overlap', 'Here');
|
||||
createEvent('4 Overlap', 'There');
|
||||
|
||||
assert.ok(
|
||||
eventsTitleAndLocationDisplayed(),
|
||||
'details should be displayed if we have less than 5 events overlaps'
|
||||
);
|
||||
|
||||
createEvent('5 Overlap', 'Earth');
|
||||
|
||||
assert.isFalse(
|
||||
eventsTitleAndLocationDisplayed(),
|
||||
'details should be hidden if we have more than 4 events overlaps'
|
||||
);
|
||||
|
||||
createEvent('6 Overlap', 'Mars');
|
||||
createEvent('7 Overlap', 'Mind');
|
||||
|
||||
assert.isFalse(
|
||||
eventsTitleAndLocationDisplayed(),
|
||||
'details should be hidden if we have more than 4 events overlaps'
|
||||
);
|
||||
});
|
||||
|
||||
});
|
||||
|
||||
test('current-time', function() {
|
||||
day.waitForHourScrollEnd();
|
||||
|
||||
var currentTime = day.currentTime;
|
||||
|
||||
assert.ok(
|
||||
currentTime.displayed(),
|
||||
'current-time should be active'
|
||||
);
|
||||
|
||||
assert.ok(
|
||||
intersect(currentTime, day.currentHour),
|
||||
'current time should be inside current hour range'
|
||||
);
|
||||
|
||||
assert.ok(
|
||||
!day.currentDisplayHour.displayed(),
|
||||
'hour should be hidden if overlapping'
|
||||
);
|
||||
|
||||
function intersect(el1, el2) {
|
||||
var b1 = getBounds(el1);
|
||||
var b2 = getBounds(el2);
|
||||
|
||||
return (
|
||||
b1.left <= b2.right &&
|
||||
b2.left <= b1.right &&
|
||||
b1.top <= b2.bottom &&
|
||||
b2.top <= b1.bottom
|
||||
);
|
||||
}
|
||||
|
||||
function getBounds(element) {
|
||||
return element.scriptWith(function(el) {
|
||||
return el.getBoundingClientRect();
|
||||
});
|
||||
}
|
||||
|
||||
// it should hide the currentTime if current day is not visible
|
||||
app.swipeLeft();
|
||||
client.waitFor(function() {
|
||||
return !currentTime.displayed() && day.currentDisplayHour.displayed();
|
||||
});
|
||||
});
|
||||
|
||||
suite('animated scrolling', function() {
|
||||
setup(function() {
|
||||
day.waitForHourScrollEnd();
|
||||
});
|
||||
|
||||
test('today', function() {
|
||||
assert.equal(
|
||||
day.scrollTop,
|
||||
day.getDestinationScrollTop(new Date().getHours() - 1),
|
||||
'scroll to the previous hour of current time'
|
||||
);
|
||||
});
|
||||
|
||||
test('select next or previous day in the month', function() {
|
||||
var today = new Date();
|
||||
var selectedDay;
|
||||
|
||||
app.openMonthView();
|
||||
if (today.getDate() === 1 && today.getDay() === 0) {
|
||||
selectedDay = month.days[1];
|
||||
} else {
|
||||
selectedDay = month.days[0];
|
||||
}
|
||||
selectedDay.click();
|
||||
|
||||
app.openDayView();
|
||||
day.waitForHourScrollEnd();
|
||||
assert.equal(
|
||||
day.scrollTop,
|
||||
day.getDestinationScrollTop(8),
|
||||
'scroll to the 8AM element'
|
||||
);
|
||||
});
|
||||
|
||||
test('swipe to the next day', function() {
|
||||
var previousScrollTop = day.scrollTop;
|
||||
app.swipeLeft();
|
||||
|
||||
assert.equal(
|
||||
day.scrollTop,
|
||||
previousScrollTop,
|
||||
'same scrollTop'
|
||||
);
|
||||
});
|
||||
|
||||
test('swipe to the previous day', function() {
|
||||
var previousScrollTop = day.scrollTop;
|
||||
app.swipeRight();
|
||||
|
||||
assert.equal(
|
||||
day.scrollTop,
|
||||
previousScrollTop,
|
||||
'same scrollTop'
|
||||
);
|
||||
});
|
||||
|
||||
test('swipe to the today', function() {
|
||||
app.swipeRight();
|
||||
app.swipeLeft();
|
||||
|
||||
assert.equal(
|
||||
day.scrollTop,
|
||||
day.getDestinationScrollTop(new Date().getHours() - 1),
|
||||
'scroll to the 8AM element'
|
||||
);
|
||||
});
|
||||
});
|
||||
|
||||
suite('12/24 hour format', function() {
|
||||
test('default format: 12 hour', function() {
|
||||
assert.equal(day.sideBarHours[0].text(), '12 AM');
|
||||
assert.equal(day.sideBarHours[13].text(), '1 PM');
|
||||
assert.equal(day.sideBarHours[23].text(), '11 PM');
|
||||
|
||||
var now = new Date();
|
||||
var minutes = now.getMinutes();
|
||||
minutes = minutes < 10 ? (0 + String(minutes)) : minutes;
|
||||
var hours = now.getHours();
|
||||
var currentTime = (hours > 12 ? hours - 12 : hours ) + ':' + minutes;
|
||||
assert.equal(day.currentTime.text(), currentTime);
|
||||
});
|
||||
|
||||
test('switch to 24 hour format', function() {
|
||||
app.switch24HourTimeFormat();
|
||||
|
||||
// Settings changes are async, so we might need waitFor() the
|
||||
// UI components to update.
|
||||
client.waitFor(function() {
|
||||
return day.sideBarHours[0].text() === '0' &&
|
||||
day.sideBarHours[13].text() === '13' &&
|
||||
day.sideBarHours[23].text() === '23';
|
||||
});
|
||||
|
||||
var now = new Date();
|
||||
var currentTime = pad(now.getHours()) + ':' + pad(now.getMinutes());
|
||||
assert.equal(day.currentTime.text(), currentTime);
|
||||
});
|
||||
});
|
||||
|
||||
test('double tap all day + toggle all day', function() {
|
||||
day.waitForHourScrollEnd();
|
||||
|
||||
day.actions
|
||||
.doubleTap(day.activeAllDays[0], 150, 30)
|
||||
.perform();
|
||||
|
||||
var event = app.editEvent;
|
||||
event.waitForDisplay();
|
||||
assert.ok(event.allDay, 'is all day event');
|
||||
var oldStart = event.startTime;
|
||||
var oldEnd = event.endTime;
|
||||
event.allDay = false;
|
||||
event.title = 'Foo';
|
||||
client.waitFor(function() {
|
||||
return event.startTime !== oldStart && event.endTime !== oldEnd;
|
||||
});
|
||||
var now = new Date();
|
||||
var start = new Date(now.getTime());
|
||||
start.setHours(now.getHours() + 1, 0, 0, 0);
|
||||
var end = new Date(start.getTime());
|
||||
end.setHours(start.getHours() + 1, 0, 0, 0);
|
||||
var isToday = start.toISOString().slice(0, 10) === event.startDate;
|
||||
var expectedStart = isToday ? pad(start.getHours()) + ':00:00' : '08:00:00';
|
||||
var expectedEnd = isToday ? pad(end.getHours()) + ':00:00' : '09:00:00';
|
||||
assert.equal(event.startTime, expectedStart, 'startTime');
|
||||
assert.equal(event.endTime, expectedEnd, 'endTime');
|
||||
|
||||
event.save();
|
||||
day.waitForDisplay();
|
||||
});
|
||||
|
||||
function pad(n) {
|
||||
return n < 10 ? '0' + n : String(n);
|
||||
}
|
||||
});
|
||||
351
gaia/apps/calendar/test/marionette/lib/calendar.js
Executable file
351
gaia/apps/calendar/test/marionette/lib/calendar.js
Executable file
|
|
@ -0,0 +1,351 @@
|
|||
'use strict';
|
||||
|
||||
var AdvancedSettings = require('./views/advanced_settings'),
|
||||
CreateAccount = require('./views/create_account'),
|
||||
Day = require('./views/day'),
|
||||
EditEvent = require('./views/edit_event'),
|
||||
Marionette = require('marionette-client'),
|
||||
ModifyAccount = require('./views/modify_account'),
|
||||
Month = require('./views/month'),
|
||||
MonthDay = require('./views/month_day'),
|
||||
ReadEvent = require('./views/read_event'),
|
||||
Settings = require('./views/settings'),
|
||||
Week = require('./views/week');
|
||||
|
||||
function Calendar(client) {
|
||||
this.client = client.scope({ searchTimeout: 5000 });
|
||||
this.actions = new Marionette.Actions(this.client);
|
||||
|
||||
// Initialize our view remotes.
|
||||
this.advancedSettings = new AdvancedSettings(client);
|
||||
this.createAccount = new CreateAccount(client);
|
||||
this.day = new Day(client);
|
||||
this.editEvent = new EditEvent(client);
|
||||
this.modifyAccount = new ModifyAccount(client);
|
||||
this.month = new Month(client);
|
||||
this.monthDay = new MonthDay(client);
|
||||
this.readEvent = new ReadEvent(client);
|
||||
this.settings = new Settings(client);
|
||||
this.week = new Week(client);
|
||||
}
|
||||
module.exports = Calendar;
|
||||
|
||||
Calendar.ORIGIN = 'app://calendar.gaiamobile.org';
|
||||
|
||||
Calendar.prototype = {
|
||||
launch: function() {
|
||||
var client = this.client;
|
||||
|
||||
client.apps.launch(Calendar.ORIGIN);
|
||||
client.apps.switchToApp(Calendar.ORIGIN);
|
||||
|
||||
// Wait for the document body to know we're really 'launched'.
|
||||
this.client.helper.waitForElement('body');
|
||||
},
|
||||
|
||||
get addEventButton() {
|
||||
return this.client.findElement('#time-header a[href="/event/add/"]');
|
||||
},
|
||||
|
||||
get headerContent() {
|
||||
return this.client.findElement('#current-month-year');
|
||||
},
|
||||
|
||||
get timeHeader() {
|
||||
return this.client.findElement('#time-header');
|
||||
},
|
||||
|
||||
get themeColor() {
|
||||
return this.client.findElement('meta[name="theme-color"]')
|
||||
.getAttribute('content');
|
||||
},
|
||||
|
||||
openModifyEventView: function() {
|
||||
this.addEventButton.click();
|
||||
this.editEvent.waitForDisplay();
|
||||
return this;
|
||||
},
|
||||
|
||||
openSettingsView: function() {
|
||||
this._toggleSettingsView(true);
|
||||
return this;
|
||||
},
|
||||
|
||||
closeSettingsView: function() {
|
||||
this._toggleSettingsView(false);
|
||||
return this;
|
||||
},
|
||||
|
||||
_toggleSettingsView: function(isOpen) {
|
||||
var client = this.client;
|
||||
client.helper
|
||||
.waitForElement(this.timeHeader)
|
||||
.tap(25, 25);
|
||||
|
||||
// Wait for the animation to be complete before trying to click on
|
||||
// items in the drawer.
|
||||
var drawer = this.client.findElement('#settings .settings-drawer');
|
||||
|
||||
this.client.waitFor(function() {
|
||||
return drawer.getAttribute('data-animstate') === 'done';
|
||||
});
|
||||
|
||||
if (!isOpen) {
|
||||
var body = this.client.findElement('body');
|
||||
// Also wait for the UI to be in a non-settings state after
|
||||
// waiting for the animation to finish and the app UI to go
|
||||
// back to non-settings state.
|
||||
this.client.waitFor(function() {
|
||||
return body.getAttribute('data-path') !== '/settings/';
|
||||
});
|
||||
}
|
||||
return this;
|
||||
},
|
||||
|
||||
openAdvancedSettingsView: function() {
|
||||
this.openSettingsView();
|
||||
this.settings.setupAdvancedSettings();
|
||||
this.advancedSettings.waitForDisplay();
|
||||
return this;
|
||||
},
|
||||
|
||||
closeAdvancedSettingsView: function() {
|
||||
var advancedSettings = this.advancedSettings;
|
||||
advancedSettings.close();
|
||||
advancedSettings.waitForHide();
|
||||
},
|
||||
|
||||
openDayView: function() {
|
||||
this.client
|
||||
.findElement('#view-selector a[href="/day/"]')
|
||||
.click();
|
||||
this.day.waitForDisplay();
|
||||
return this;
|
||||
},
|
||||
|
||||
openMonthView: function() {
|
||||
this.client
|
||||
.findElement('#view-selector a[href="/month/"]')
|
||||
.click();
|
||||
this.month.waitForDisplay();
|
||||
return this;
|
||||
},
|
||||
|
||||
openWeekView: function() {
|
||||
this.client
|
||||
.findElement('#view-selector a[href="/week/"]')
|
||||
.click();
|
||||
this.week.waitForDisplay();
|
||||
return this;
|
||||
},
|
||||
|
||||
clickToday: function() {
|
||||
this.client
|
||||
.findElement('#view-selector a[href="#today"]')
|
||||
.click();
|
||||
return this;
|
||||
},
|
||||
|
||||
setupAccount: function(options) {
|
||||
this.openAdvancedSettingsView();
|
||||
this.advancedSettings.createAccount();
|
||||
this.createAccount.waitForDisplay();
|
||||
this.createAccount.chooseAccountType(options.accountType);
|
||||
|
||||
var modifyAccount = this.modifyAccount;
|
||||
modifyAccount.waitForDisplay();
|
||||
[
|
||||
'user',
|
||||
'password',
|
||||
'fullUrl'
|
||||
].forEach(function(key) {
|
||||
if (key in options) {
|
||||
modifyAccount[key] = options[key];
|
||||
}
|
||||
});
|
||||
|
||||
modifyAccount.save();
|
||||
modifyAccount.waitForHide();
|
||||
this.closeSettingsView();
|
||||
return this;
|
||||
},
|
||||
|
||||
teardownAccount: function(calendarName, user) {
|
||||
this.openAdvancedSettingsView();
|
||||
this.advancedSettings.clickAccount(calendarName, user);
|
||||
var modifyAccount = this.modifyAccount;
|
||||
modifyAccount.waitForDisplay();
|
||||
modifyAccount.delete();
|
||||
modifyAccount.waitForHide();
|
||||
this.closeAdvancedSettingsView();
|
||||
this.closeSettingsView();
|
||||
return this;
|
||||
},
|
||||
|
||||
sync: function() {
|
||||
this.openSettingsView();
|
||||
this.settings.sync();
|
||||
this.closeSettingsView();
|
||||
return this;
|
||||
},
|
||||
|
||||
/**
|
||||
* Create an event.
|
||||
*
|
||||
* Options:
|
||||
* (String) calendar - calendar name [defaults to "Offline calendar"].
|
||||
* (String) title - event title.
|
||||
* (String) location - event location.
|
||||
* (Date) startDate - event start date.
|
||||
* (Date) endDate - event end date.
|
||||
* (Number) startHour - shortcut for creating an event that starts today.
|
||||
* (Number) duration - length of event in hours.
|
||||
* (Boolean) allDay - whether this is an all day event.
|
||||
* (Array) reminders - array of strings like '5 minutes before'.
|
||||
*/
|
||||
createEvent: function(opts) {
|
||||
var startDate;
|
||||
if (opts.startDate) {
|
||||
startDate = opts.startDate;
|
||||
} else {
|
||||
startDate = new Date();
|
||||
// startHour can be zero!
|
||||
if (opts.startHour != null) {
|
||||
startDate.setHours(opts.startHour, 0, 0, 0);
|
||||
}
|
||||
}
|
||||
|
||||
var endDate;
|
||||
if (opts.endDate) {
|
||||
endDate = opts.endDate;
|
||||
} else {
|
||||
// 1h by default
|
||||
var duration = opts.duration || 1;
|
||||
endDate = new Date(startDate.getTime() + duration * 60 * 60 * 1000);
|
||||
}
|
||||
|
||||
this.openModifyEventView();
|
||||
var editEvent = this.editEvent;
|
||||
editEvent.title = opts.title;
|
||||
editEvent.location = opts.location || '';
|
||||
editEvent.description = opts.description || '';
|
||||
editEvent.calendar = opts.calendar || 'Offline calendar';
|
||||
editEvent.startDate = startDate;
|
||||
editEvent.endDate = endDate;
|
||||
if (opts.allDay) {
|
||||
editEvent.allDay = opts.allDay;
|
||||
} else {
|
||||
editEvent.startTime = startDate;
|
||||
editEvent.endTime = endDate;
|
||||
}
|
||||
// no reminders by default to avoid triggering notifications by mistake.
|
||||
// see: https://bugzil.la/1012507
|
||||
editEvent.reminders = opts.reminders == null ? [] : opts.reminders;
|
||||
editEvent.save();
|
||||
|
||||
editEvent.waitForHide();
|
||||
return this;
|
||||
},
|
||||
|
||||
/**
|
||||
* Tests if content is bigger than container. If something is wrong it
|
||||
* throws errors.
|
||||
*/
|
||||
checkOverflow: function(element, msg) {
|
||||
msg = msg ? msg + ': ' : '';
|
||||
|
||||
var wid = element.scriptWith(function(el) {
|
||||
return {
|
||||
content: el.scrollWidth,
|
||||
container: el.clientWidth
|
||||
};
|
||||
});
|
||||
if (!wid.content) {
|
||||
throw new Error(msg + 'invalid content width');
|
||||
}
|
||||
if (!wid.container) {
|
||||
throw new Error(msg + 'invalid container width');
|
||||
}
|
||||
// we use a buffer of 1px to account for potential rounding issues
|
||||
// see: Bug 959901
|
||||
if (Math.abs(wid.content - wid.container) > 1) {
|
||||
msg += 'content (' + wid.content + 'px) is wider than container (' +
|
||||
wid.container + 'px)';
|
||||
throw new Error(msg);
|
||||
}
|
||||
|
||||
return this;
|
||||
},
|
||||
|
||||
formatDate: function(date) {
|
||||
var month = date.getMonth() + 1,
|
||||
day = date.getDate(),
|
||||
year = date.getFullYear();
|
||||
if (month.toString().length === 1) {
|
||||
month = '0' + month;
|
||||
}
|
||||
if (day.toString().length === 1) {
|
||||
day = '0' + day;
|
||||
}
|
||||
|
||||
return [month, day, year].join('/');
|
||||
},
|
||||
|
||||
swipeLeft: function() {
|
||||
return this._swipe({ direction: 'left' });
|
||||
},
|
||||
|
||||
swipeRight: function() {
|
||||
return this._swipe({ direction: 'right' });
|
||||
},
|
||||
|
||||
/**
|
||||
* Options:
|
||||
* (String) direction is one of 'left', 'right'.
|
||||
*/
|
||||
_swipe: function(options) {
|
||||
var bodySize = this.client.executeScript(function() {
|
||||
return {
|
||||
height: document.body.clientHeight,
|
||||
width: document.body.clientWidth
|
||||
};
|
||||
});
|
||||
|
||||
// (x1, y1) is swipe start.
|
||||
// (x2, y2) is swipe end.
|
||||
var x1, x2, y1, y2;
|
||||
y1 = y2 = bodySize.height * 0.5;
|
||||
if (options.direction === 'left') {
|
||||
x1 = bodySize.width * 0.8;
|
||||
x2 = 0;
|
||||
} else if (options.direction === 'right') {
|
||||
x1 = bodySize.width * 0.2;
|
||||
x2 = bodySize.width;
|
||||
} else {
|
||||
throw new Error('swipe needs a direction');
|
||||
}
|
||||
|
||||
var body = this.client.findElement('body');
|
||||
this.actions
|
||||
.flick(body, x1, y1, x2, y2)
|
||||
.perform();
|
||||
return this;
|
||||
},
|
||||
|
||||
switch12HourTimeFormat: function() {
|
||||
this._switchTimeFormat(true);
|
||||
},
|
||||
|
||||
switch24HourTimeFormat: function() {
|
||||
this._switchTimeFormat(false);
|
||||
},
|
||||
|
||||
_switchTimeFormat: function(is12Hour) {
|
||||
var client = this.client;
|
||||
// Switch to System frame to get the permission
|
||||
// of writing settings values.
|
||||
client.switchToFrame();
|
||||
client.settings.set('locale.hour12', is12Hour);
|
||||
client.apps.switchToApp(Calendar.ORIGIN);
|
||||
}
|
||||
};
|
||||
159
gaia/apps/calendar/test/marionette/lib/radicale.js
Executable file
159
gaia/apps/calendar/test/marionette/lib/radicale.js
Executable file
|
|
@ -0,0 +1,159 @@
|
|||
'use strict';
|
||||
|
||||
var childProcess = require('child_process'),
|
||||
fs = require('fs'),
|
||||
path = require('path'),
|
||||
os = require('os'),
|
||||
ejs = require('ejs'),
|
||||
emptyPort = require('empty-port');
|
||||
|
||||
var CAL_ICS_TMP_PATH = __dirname + '/template/calendar.ics.ejs',
|
||||
CONFIG_TMP_PATH = __dirname + '/template/config.ejs',
|
||||
START_PORT = 50000;
|
||||
|
||||
/**
|
||||
* Radicale inherit CaldavServer.
|
||||
* It is a kind of CaldavServer implemented by pure Python.
|
||||
*
|
||||
* We install it from the gaia-marionette shell script file
|
||||
* during running the tests.
|
||||
*/
|
||||
function Radicale() {
|
||||
// The instance of the child process.
|
||||
this.childProcess = null;
|
||||
// The events stored in the server.
|
||||
this.events = [];
|
||||
// Set up dav directories.
|
||||
this.configFilePath = path.join(os.tmpdir(), 'config');
|
||||
this.fileSystemPath = path.join(os.tmpdir(), 'collections');
|
||||
}
|
||||
|
||||
Radicale.prototype = {
|
||||
/**
|
||||
* Server port.
|
||||
* @type {Number}
|
||||
*/
|
||||
port: null,
|
||||
|
||||
/**
|
||||
* The path to the config file.
|
||||
* @type {String}
|
||||
*/
|
||||
configFilePath: null,
|
||||
|
||||
/**
|
||||
* The path to the directory of the file system in server.
|
||||
* @type {String}
|
||||
*/
|
||||
fileSystemPath: null,
|
||||
|
||||
/**
|
||||
* Setup the config file for the Radicale server.
|
||||
*/
|
||||
_setup: function() {
|
||||
// Setup the Radicale config file,
|
||||
// and we create it at path /radicale/install/path/config in the disk.
|
||||
var configTemplate,
|
||||
config;
|
||||
|
||||
if (!fs.existsSync(this.fileSystemPath)) {
|
||||
fs.mkdirSync(this.fileSystemPath);
|
||||
}
|
||||
|
||||
configTemplate = fs.readFileSync(CONFIG_TMP_PATH, 'utf8'),
|
||||
config = ejs.render(configTemplate, {
|
||||
port: this.port,
|
||||
filesystemPath: this.fileSystemPath
|
||||
});
|
||||
|
||||
fs.writeFileSync(this.configFilePath, config);
|
||||
},
|
||||
|
||||
/**
|
||||
* Start the Radicale server with config file.
|
||||
*
|
||||
* @param {Object} options for the server.
|
||||
* @param {Function} callback with port param execute
|
||||
* after server is started.
|
||||
*/
|
||||
start: function(options, callback) {
|
||||
if (typeof options === 'function') {
|
||||
callback = options;
|
||||
options = {};
|
||||
}
|
||||
|
||||
if ('port' in options) {
|
||||
this.port = options.port;
|
||||
this._executeServer();
|
||||
return callback && callback(null, options.port);
|
||||
}
|
||||
|
||||
emptyPort({ startPort: START_PORT }, function(error, port) {
|
||||
if (error) {
|
||||
return callback && callback(error);
|
||||
}
|
||||
|
||||
this.port = port;
|
||||
this._executeServer();
|
||||
return callback && callback(null, port);
|
||||
}.bind(this));
|
||||
},
|
||||
|
||||
/**
|
||||
* Execute the server with terminal command.
|
||||
*/
|
||||
_executeServer: function() {
|
||||
this._setup();
|
||||
this.childProcess =
|
||||
childProcess.spawn('radicale', ['--config', this.configFilePath]);
|
||||
},
|
||||
|
||||
/**
|
||||
* Shutdown the Radicale server.
|
||||
*
|
||||
* @param {Function} callback execute after server is closed.
|
||||
*/
|
||||
close: function(callback) {
|
||||
this.childProcess.on('exit', function() {
|
||||
if (callback && typeof(callback) === 'function') {
|
||||
callback();
|
||||
}
|
||||
});
|
||||
this.childProcess.kill();
|
||||
},
|
||||
|
||||
/**
|
||||
* Add events in the CalDAV server.
|
||||
*
|
||||
* @param {String} username The user you would like to add for.
|
||||
* @param {Array|Object} event JSON objects, the structure is like
|
||||
* { startDate, endDate, title, location, description }.
|
||||
*/
|
||||
addEvent: function(username, event) {
|
||||
if (Array.isArray(event)) {
|
||||
this.events = this.events.concat(event);
|
||||
} else {
|
||||
this.events.push(event);
|
||||
}
|
||||
|
||||
var calendarIcsTemplate = fs.readFileSync(CAL_ICS_TMP_PATH, 'utf8'),
|
||||
calendarIcs = ejs.render(calendarIcsTemplate, { events: this.events });
|
||||
|
||||
fs.writeFileSync(
|
||||
path.join(this.fileSystemPath, username),
|
||||
calendarIcs
|
||||
);
|
||||
},
|
||||
|
||||
/**
|
||||
* Remove all events for all users.
|
||||
*/
|
||||
removeAllEvents: function() {
|
||||
this.events = [];
|
||||
fs.readdirSync(this.fileSystemPath).forEach(function(file) {
|
||||
fs.unlinkSync(path.join(this.fileSystemPath, file));
|
||||
}.bind(this));
|
||||
}
|
||||
};
|
||||
|
||||
module.exports = Radicale;
|
||||
13
gaia/apps/calendar/test/marionette/lib/template/calendar.ics.ejs
Executable file
13
gaia/apps/calendar/test/marionette/lib/template/calendar.ics.ejs
Executable file
|
|
@ -0,0 +1,13 @@
|
|||
BEGIN:VCALENDAR
|
||||
VERSION:2.0
|
||||
<% for (var i = 0; i < events.length; i++) { %>
|
||||
BEGIN:VEVENT
|
||||
UID:<%= i %>
|
||||
DTSTART:<%= events[i].startDate || '' %>
|
||||
DTEND:<%= events[i].endDate || '' %>
|
||||
SUMMARY:<%= events[i].title || '' %>
|
||||
LOCATION:<%= events[i].location || '' %>
|
||||
DESCRIPTION:<%= events[i].description || '' %>
|
||||
END:VEVENT
|
||||
<% } %>
|
||||
END:VCALENDAR
|
||||
10
gaia/apps/calendar/test/marionette/lib/template/config.ejs
Executable file
10
gaia/apps/calendar/test/marionette/lib/template/config.ejs
Executable file
|
|
@ -0,0 +1,10 @@
|
|||
[server]
|
||||
# CalDAV server hostnames separated by a comma
|
||||
hosts = 127.0.0.1:<%= port || 5232 %>
|
||||
|
||||
[storage]
|
||||
# Storage backend
|
||||
type = filesystem
|
||||
|
||||
# Folder for storing local collections, created if not present
|
||||
filesystem_folder = <%= filesystemPath || '' %>
|
||||
58
gaia/apps/calendar/test/marionette/lib/views/advanced_settings.js
Executable file
58
gaia/apps/calendar/test/marionette/lib/views/advanced_settings.js
Executable file
|
|
@ -0,0 +1,58 @@
|
|||
'use strict';
|
||||
|
||||
var View = require('./view');
|
||||
|
||||
function AdvancedSettings() {
|
||||
View.apply(this, arguments);
|
||||
}
|
||||
module.exports = AdvancedSettings;
|
||||
|
||||
AdvancedSettings.prototype = {
|
||||
__proto__: View.prototype,
|
||||
|
||||
selector: '#advanced-settings-view',
|
||||
|
||||
close: function() {
|
||||
this
|
||||
.findElement('a[href="/settings/"]')
|
||||
.click();
|
||||
},
|
||||
|
||||
createAccount: function() {
|
||||
this
|
||||
.findElement('[href="/select-preset/"]')
|
||||
.click();
|
||||
},
|
||||
|
||||
clickAccount: function(calendarName, user) {
|
||||
var account;
|
||||
this
|
||||
.findElements('.account-list > li')
|
||||
.some(function(element) {
|
||||
var text = element.text();
|
||||
if (text.indexOf(calendarName) !== -1 ||
|
||||
(user && text.indexOf(user) !== -1)) {
|
||||
account = element;
|
||||
return true;
|
||||
}
|
||||
});
|
||||
|
||||
if (!account) {
|
||||
throw new Error(
|
||||
'Could not find account for user ' + user +
|
||||
' or calendar ' + calendarName
|
||||
);
|
||||
}
|
||||
|
||||
account.click();
|
||||
},
|
||||
|
||||
waitForHide: function() {
|
||||
return this.client.waitFor(function() {
|
||||
var zIndex = this
|
||||
.getElement()
|
||||
.cssProperty('z-index');
|
||||
return zIndex === '-1';
|
||||
}.bind(this));
|
||||
}
|
||||
};
|
||||
25
gaia/apps/calendar/test/marionette/lib/views/create_account.js
Executable file
25
gaia/apps/calendar/test/marionette/lib/views/create_account.js
Executable file
|
|
@ -0,0 +1,25 @@
|
|||
'use strict';
|
||||
|
||||
var View = require('./view');
|
||||
|
||||
function CreateAccount() {
|
||||
View.apply(this, arguments);
|
||||
}
|
||||
module.exports = CreateAccount;
|
||||
|
||||
CreateAccount.prototype = {
|
||||
__proto__: View.prototype,
|
||||
|
||||
selector: '#create-account-view',
|
||||
|
||||
/**
|
||||
* @param {String} accountType is one of
|
||||
* 'google', 'yahoo', or 'caldav'.
|
||||
*/
|
||||
chooseAccountType: function(accountType) {
|
||||
var selector = '.' + accountType;
|
||||
this
|
||||
.findElement(selector)
|
||||
.click();
|
||||
}
|
||||
};
|
||||
25
gaia/apps/calendar/test/marionette/lib/views/day.js
Executable file
25
gaia/apps/calendar/test/marionette/lib/views/day.js
Executable file
|
|
@ -0,0 +1,25 @@
|
|||
'use strict';
|
||||
|
||||
var WeekView = require('./week'),
|
||||
DayEvent = require('./day_event');
|
||||
|
||||
function Day() {
|
||||
WeekView.apply(this, arguments);
|
||||
}
|
||||
module.exports = Day;
|
||||
|
||||
Day.prototype = {
|
||||
__proto__: WeekView.prototype,
|
||||
|
||||
selector: '#day-view',
|
||||
|
||||
get activeDay() {
|
||||
return this.findElement('.md__day[aria-hidden="false"]');
|
||||
},
|
||||
|
||||
get events() {
|
||||
return this.activeDay.findElements('.md__event').map(function(el) {
|
||||
return new DayEvent(this.client, el);
|
||||
}, this);
|
||||
}
|
||||
};
|
||||
28
gaia/apps/calendar/test/marionette/lib/views/day_event.js
Executable file
28
gaia/apps/calendar/test/marionette/lib/views/day_event.js
Executable file
|
|
@ -0,0 +1,28 @@
|
|||
'use strict';
|
||||
|
||||
var MarionetteElement = require('./marionette_element');
|
||||
|
||||
/**
|
||||
* Represents a single Day Event
|
||||
*/
|
||||
function DayEvent() {
|
||||
MarionetteElement.apply(this, arguments);
|
||||
}
|
||||
module.exports = DayEvent;
|
||||
|
||||
DayEvent.prototype = {
|
||||
__proto__: MarionetteElement.prototype,
|
||||
|
||||
get title() {
|
||||
return this.findElement('.md__event-title');
|
||||
},
|
||||
|
||||
// Marionette.Element already have a method location()
|
||||
get address() {
|
||||
return this.findElement('.md__event-location');
|
||||
},
|
||||
|
||||
get iconAlarm() {
|
||||
return this.findElement('.icon-calendar-alarm');
|
||||
}
|
||||
};
|
||||
159
gaia/apps/calendar/test/marionette/lib/views/edit_event.js
Executable file
159
gaia/apps/calendar/test/marionette/lib/views/edit_event.js
Executable file
|
|
@ -0,0 +1,159 @@
|
|||
'use strict';
|
||||
|
||||
var View = require('./view');
|
||||
|
||||
function EditEvent() {
|
||||
View.apply(this, arguments);
|
||||
}
|
||||
module.exports = EditEvent;
|
||||
|
||||
EditEvent.prototype = {
|
||||
__proto__: View.prototype,
|
||||
|
||||
selector: '#modify-event-view',
|
||||
|
||||
waitForDisplay: function() {
|
||||
// event details are loaded asynchronously, so we need to wait until the
|
||||
// "loading" class is removed from the view; this will avoid intermittent
|
||||
// failures on Travis (caused by race condition)
|
||||
var element = this.getElement();
|
||||
|
||||
var selectIsReady = function() {
|
||||
// the calendar list is also loaded asynchronously from the database, so
|
||||
// we need to wait until it's ready as well
|
||||
var select = element.findElement('select[name="calendarId"]');
|
||||
return select.getAttribute('className').indexOf('loading') === -1;
|
||||
};
|
||||
|
||||
this.client.waitFor(function() {
|
||||
return element.displayed() &&
|
||||
element.getAttribute('className').indexOf('loading') === -1 &&
|
||||
selectIsReady();
|
||||
});
|
||||
},
|
||||
|
||||
get form() {
|
||||
return this.findElement('form');
|
||||
},
|
||||
|
||||
get errors() {
|
||||
return this.form.findElement('.errors').text();
|
||||
},
|
||||
|
||||
set title(value) {
|
||||
this.setFormValue('title', value);
|
||||
},
|
||||
|
||||
set description(value) {
|
||||
this.setFormValue('description', value);
|
||||
},
|
||||
|
||||
set location(value) {
|
||||
this.setFormValue('location', value);
|
||||
},
|
||||
|
||||
set calendar(value) {
|
||||
var select = this.findElement('select[name="calendarId"]');
|
||||
this.client.helper.tapSelectOption(select, value);
|
||||
},
|
||||
|
||||
set startDate(value) {
|
||||
this.setFormValue('startDate', value);
|
||||
},
|
||||
|
||||
get startDate() {
|
||||
return this.getFormValue('startDate');
|
||||
},
|
||||
|
||||
set startTime(value) {
|
||||
this.setFormValue('startTime', value);
|
||||
},
|
||||
|
||||
get startTime() {
|
||||
return this.getFormValue('startTime');
|
||||
},
|
||||
|
||||
get startTimeLocale() {
|
||||
return this.findElement('#start-time-locale');
|
||||
},
|
||||
|
||||
set endDate(value) {
|
||||
this.setFormValue('endDate', value);
|
||||
},
|
||||
|
||||
get endDate() {
|
||||
return this.getFormValue('endDate');
|
||||
},
|
||||
|
||||
get endTimeLocale() {
|
||||
return this.findElement('#end-time-locale');
|
||||
},
|
||||
|
||||
set endTime(value) {
|
||||
this.setFormValue('endTime', value);
|
||||
},
|
||||
|
||||
get endTime() {
|
||||
return this.getFormValue('endTime');
|
||||
},
|
||||
|
||||
get allDayCheckbox() {
|
||||
return this.findElement('gaia-switch[name="allday"]');
|
||||
},
|
||||
|
||||
get allDay() {
|
||||
return this.allDayCheckbox.getAttribute('checked');
|
||||
},
|
||||
|
||||
set allDay(value) {
|
||||
if (value !== this.allDay) {
|
||||
this.allDayCheckbox.click();
|
||||
}
|
||||
},
|
||||
|
||||
set reminders(values) {
|
||||
if (!values || !values.length) {
|
||||
values = ['None'];
|
||||
}
|
||||
values.forEach(this.setReminderValue, this);
|
||||
},
|
||||
|
||||
setReminderValue: function(value, index) {
|
||||
// maximum amount of reminders is 5
|
||||
var nth = Math.min(index, 4) + 1;
|
||||
// we are using this complex selector, instead of querying for all the
|
||||
// available <select> elements, because client.helper.tapSelectOption
|
||||
// will wait for element to be displayed before trying to click on it; that
|
||||
// way we avoid potential race conditions
|
||||
var el = this.findElement('.alarms > *:nth-child(' + nth + ') select');
|
||||
this.client.helper.tapSelectOption(el, value);
|
||||
},
|
||||
|
||||
get reminders() {
|
||||
return this.findElements('[name="alarm[]"]').map(function(select) {
|
||||
return select.scriptWith(function(el) {
|
||||
return el.options[el.options.selectedIndex].textContent.trim();
|
||||
});
|
||||
});
|
||||
},
|
||||
|
||||
cancel: function() {
|
||||
this
|
||||
.findElement('.cancel')
|
||||
.click();
|
||||
},
|
||||
|
||||
delete: function() {
|
||||
this
|
||||
.findElement('.delete-record')
|
||||
.click();
|
||||
},
|
||||
|
||||
get saveButton() {
|
||||
return this.findElement('.save');
|
||||
},
|
||||
|
||||
save: function() {
|
||||
this.saveButton.click();
|
||||
}
|
||||
};
|
||||
22
gaia/apps/calendar/test/marionette/lib/views/marionette_element.js
Executable file
22
gaia/apps/calendar/test/marionette/lib/views/marionette_element.js
Executable file
|
|
@ -0,0 +1,22 @@
|
|||
'use strict';
|
||||
|
||||
var Marionette = require('marionette-client');
|
||||
|
||||
/**
|
||||
* This base class is used to inherit all methods from Marionette.Element, this
|
||||
* will allow us to call methods like `text()`, `click()`, etc in the View
|
||||
* context.
|
||||
*/
|
||||
function MarionetteElement(client, el) {
|
||||
this.client = client;
|
||||
this.element = el;
|
||||
}
|
||||
module.exports = MarionetteElement;
|
||||
|
||||
// Mix-in methods from Marionette.Element.prototype
|
||||
Object.keys(Marionette.Element.prototype).forEach(function(funcName) {
|
||||
var elementFn = Marionette.Element.prototype[funcName];
|
||||
MarionetteElement.prototype[funcName] = function() {
|
||||
return elementFn.apply(this.element, arguments);
|
||||
};
|
||||
});
|
||||
65
gaia/apps/calendar/test/marionette/lib/views/modify_account.js
Executable file
65
gaia/apps/calendar/test/marionette/lib/views/modify_account.js
Executable file
|
|
@ -0,0 +1,65 @@
|
|||
'use strict';
|
||||
|
||||
var View = require('./view');
|
||||
|
||||
function ModifyAccount() {
|
||||
View.apply(this, arguments);
|
||||
}
|
||||
module.exports = ModifyAccount;
|
||||
|
||||
ModifyAccount.prototype = {
|
||||
__proto__: View.prototype,
|
||||
|
||||
selector: '#modify-account-view',
|
||||
|
||||
get form() {
|
||||
return this.findElement('form');
|
||||
},
|
||||
|
||||
set user(value) {
|
||||
this.setFormValue('user', value);
|
||||
},
|
||||
|
||||
set password(value) {
|
||||
this.setFormValue('password', value);
|
||||
},
|
||||
|
||||
set fullUrl(value) {
|
||||
this.setFormValue('fullUrl', value);
|
||||
},
|
||||
|
||||
save: function() {
|
||||
this
|
||||
.findElement('.save')
|
||||
.click();
|
||||
},
|
||||
|
||||
delete: function() {
|
||||
this
|
||||
.findElement('.delete-record')
|
||||
.click();
|
||||
|
||||
this.client.waitFor(function() {
|
||||
var confirm = this.findElement('.delete-confirm');
|
||||
if (!confirm.displayed()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
confirm.click();
|
||||
return true;
|
||||
}.bind(this));
|
||||
},
|
||||
|
||||
/**
|
||||
* Workaround to wait for the modify account view to disappear.
|
||||
* Remove once https://bugzil.la/995563 is fixed.
|
||||
*/
|
||||
waitForHide: function() {
|
||||
return this.client.waitFor(function() {
|
||||
var zIndex = this
|
||||
.getElement()
|
||||
.cssProperty('z-index');
|
||||
return zIndex === '-1';
|
||||
}.bind(this));
|
||||
}
|
||||
};
|
||||
50
gaia/apps/calendar/test/marionette/lib/views/month.js
Executable file
50
gaia/apps/calendar/test/marionette/lib/views/month.js
Executable file
|
|
@ -0,0 +1,50 @@
|
|||
'use strict';
|
||||
|
||||
var View = require('./view');
|
||||
|
||||
function Month() {
|
||||
View.apply(this, arguments);
|
||||
}
|
||||
module.exports = Month;
|
||||
|
||||
Month.prototype = {
|
||||
__proto__: View.prototype,
|
||||
|
||||
selector: '#month-view',
|
||||
|
||||
get activeMonth() {
|
||||
return this.findElement('.month.active');
|
||||
},
|
||||
|
||||
get currentDay() {
|
||||
return this.activeMonth.findElement('.present > .day');
|
||||
},
|
||||
|
||||
get busyDots() {
|
||||
return this.activeMonth.findElements('.icon-calendar-dot');
|
||||
},
|
||||
|
||||
get days() {
|
||||
return this.activeMonth.findElements('.day');
|
||||
},
|
||||
|
||||
get weekdayHeaders() {
|
||||
return this
|
||||
.findElements('.month.active #month-days li')
|
||||
.map(function(li) {
|
||||
return li.text();
|
||||
});
|
||||
},
|
||||
|
||||
get daySquares() {
|
||||
return this.findElements('.month.active > ol li');
|
||||
},
|
||||
|
||||
get todaySquare() {
|
||||
return this.activeMonth.findElement('.present');
|
||||
},
|
||||
|
||||
squareDots: function(square) {
|
||||
return square.findElements('.icon-calendar-dot');
|
||||
}
|
||||
};
|
||||
47
gaia/apps/calendar/test/marionette/lib/views/month_day.js
Executable file
47
gaia/apps/calendar/test/marionette/lib/views/month_day.js
Executable file
|
|
@ -0,0 +1,47 @@
|
|||
'use strict';
|
||||
|
||||
var MonthDayEvent = require('./month_day_event'),
|
||||
View = require('./view');
|
||||
|
||||
function MonthDay() {
|
||||
View.apply(this, arguments);
|
||||
}
|
||||
module.exports = MonthDay;
|
||||
|
||||
MonthDay.prototype = {
|
||||
__proto__: View.prototype,
|
||||
|
||||
selector: '#month-day-agenda',
|
||||
|
||||
get container() {
|
||||
return this.findElement('#event-list');
|
||||
},
|
||||
|
||||
get events() {
|
||||
return this.findElements('.event').map(function(el) {
|
||||
return new MonthDayEvent(this.client, el);
|
||||
}, this);
|
||||
},
|
||||
|
||||
get date() {
|
||||
return this
|
||||
.findElement('#event-list-date')
|
||||
.text();
|
||||
},
|
||||
|
||||
/**
|
||||
* If event is not specified, we'll use today's first event.
|
||||
*/
|
||||
scrollToEvent: function(event) {
|
||||
if (!event) {
|
||||
event = this.events[0];
|
||||
} else if (typeof event === 'number') {
|
||||
event = this.events[event];
|
||||
}
|
||||
|
||||
event.scriptWith(function(element) {
|
||||
var container = document.getElementById('month-day-agenda');
|
||||
container.scrollTop = element.offsetTop;
|
||||
});
|
||||
}
|
||||
};
|
||||
60
gaia/apps/calendar/test/marionette/lib/views/month_day_event.js
Executable file
60
gaia/apps/calendar/test/marionette/lib/views/month_day_event.js
Executable file
|
|
@ -0,0 +1,60 @@
|
|||
'use strict';
|
||||
|
||||
var MarionetteElement = require('./marionette_element');
|
||||
|
||||
/**
|
||||
* Represents a single Day Event
|
||||
*/
|
||||
function MonthDayEvent() {
|
||||
MarionetteElement.apply(this, arguments);
|
||||
}
|
||||
module.exports = MonthDayEvent;
|
||||
|
||||
MonthDayEvent.prototype = {
|
||||
__proto__: MarionetteElement.prototype,
|
||||
|
||||
get container() {
|
||||
return this.findElement('.container');
|
||||
},
|
||||
|
||||
get title() {
|
||||
return this
|
||||
.findElement('h5')
|
||||
.text();
|
||||
},
|
||||
|
||||
// Marionette.Element already have a method location()
|
||||
get address() {
|
||||
return this
|
||||
.findElement('.location')
|
||||
.text();
|
||||
},
|
||||
|
||||
get allDay() {
|
||||
return this
|
||||
.findElement('.all-day')
|
||||
.text();
|
||||
},
|
||||
|
||||
get startTime() {
|
||||
return this
|
||||
.findElement('.start-time')
|
||||
.text();
|
||||
},
|
||||
|
||||
get endTime() {
|
||||
return this
|
||||
.findElement('.end-time')
|
||||
.text();
|
||||
},
|
||||
|
||||
get iconAlarm() {
|
||||
return this.findElement('.icon-calendar-alarm');
|
||||
},
|
||||
|
||||
hasAlarms: function() {
|
||||
return this.element.scriptWith(function(element) {
|
||||
return element.classList.contains('has-alarms');
|
||||
});
|
||||
}
|
||||
};
|
||||
103
gaia/apps/calendar/test/marionette/lib/views/read_event.js
Executable file
103
gaia/apps/calendar/test/marionette/lib/views/read_event.js
Executable file
|
|
@ -0,0 +1,103 @@
|
|||
'use strict';
|
||||
|
||||
var View = require('./view');
|
||||
|
||||
function ReadEvent() {
|
||||
View.apply(this, arguments);
|
||||
}
|
||||
module.exports = ReadEvent;
|
||||
|
||||
ReadEvent.prototype = {
|
||||
__proto__: View.prototype,
|
||||
|
||||
selector: '#event-view',
|
||||
|
||||
waitForDisplay: function() {
|
||||
// event details are loaded asynchronously, so we need to wait until the
|
||||
// "loading" class is removed from the view; this will avoid intermittent
|
||||
// failures on Travis (caused by race condition)
|
||||
var element = this.getElement();
|
||||
this.client.waitFor(function(){
|
||||
return element.displayed() &&
|
||||
element.getAttribute('className').indexOf('loading') === -1;
|
||||
});
|
||||
},
|
||||
|
||||
get alarms() {
|
||||
return this
|
||||
.findElements('.alarms > .content > div')
|
||||
.map(function(element) {
|
||||
return element.text();
|
||||
});
|
||||
},
|
||||
|
||||
get calendarColor() {
|
||||
return this
|
||||
.findElement('.current-calendar > .icon-calendar-dot')
|
||||
.cssProperty('color');
|
||||
},
|
||||
|
||||
get calendar() {
|
||||
return this
|
||||
.findElement('.current-calendar > .content')
|
||||
.text();
|
||||
},
|
||||
|
||||
get description() {
|
||||
return this
|
||||
.findElement('.description > .content')
|
||||
.text();
|
||||
},
|
||||
|
||||
get descriptionContainer() {
|
||||
return this.findElement('.description');
|
||||
},
|
||||
|
||||
get durationTime() {
|
||||
return this
|
||||
.findElement('.duration-time > .content')
|
||||
.text();
|
||||
},
|
||||
|
||||
get location() {
|
||||
return this
|
||||
.findElement('.location > .content')
|
||||
.text();
|
||||
},
|
||||
|
||||
get locationContainer() {
|
||||
return this.findElement('.location');
|
||||
},
|
||||
|
||||
get reminders() {
|
||||
return this.findElements('.alarms > .content > div');
|
||||
},
|
||||
|
||||
get title() {
|
||||
return this
|
||||
.findElement('.title .content')
|
||||
.text();
|
||||
},
|
||||
|
||||
get titleContainer() {
|
||||
return this.findElement('.title');
|
||||
},
|
||||
|
||||
get editable() {
|
||||
return this
|
||||
.findElement('.edit')
|
||||
.enabled();
|
||||
},
|
||||
|
||||
cancel: function() {
|
||||
return this
|
||||
.findElement('.cancel')
|
||||
.click();
|
||||
},
|
||||
|
||||
edit: function() {
|
||||
return this
|
||||
.findElement('.edit')
|
||||
.click();
|
||||
}
|
||||
};
|
||||
44
gaia/apps/calendar/test/marionette/lib/views/settings.js
Executable file
44
gaia/apps/calendar/test/marionette/lib/views/settings.js
Executable file
|
|
@ -0,0 +1,44 @@
|
|||
'use strict';
|
||||
|
||||
var View = require('./view');
|
||||
|
||||
function Settings() {
|
||||
View.apply(this, arguments);
|
||||
}
|
||||
module.exports = Settings;
|
||||
|
||||
Settings.prototype = {
|
||||
__proto__: View.prototype,
|
||||
|
||||
selector: '#settings',
|
||||
|
||||
setupAdvancedSettings: function() {
|
||||
this.client
|
||||
.findElement('li.settings')
|
||||
.click();
|
||||
},
|
||||
|
||||
sync: function() {
|
||||
this
|
||||
.findElement('[role="tablist"] .sync')
|
||||
.click();
|
||||
},
|
||||
|
||||
calendars: function() {
|
||||
return this
|
||||
.findElements('.calendars .name')
|
||||
.map(function(element) {
|
||||
return element.text();
|
||||
});
|
||||
},
|
||||
|
||||
toggleCalendar: function(name) {
|
||||
name = name || 'Offline calendar';
|
||||
this
|
||||
.findElements('gaia-checkbox label')
|
||||
.filter(function(element) {
|
||||
return element.text() === name;
|
||||
})[0]
|
||||
.click();
|
||||
}
|
||||
};
|
||||
83
gaia/apps/calendar/test/marionette/lib/views/view.js
Executable file
83
gaia/apps/calendar/test/marionette/lib/views/view.js
Executable file
|
|
@ -0,0 +1,83 @@
|
|||
'use strict';
|
||||
|
||||
var Marionette = require('marionette-client');
|
||||
|
||||
function View(client) {
|
||||
this.client = client.scope({ searchTimeout: 5000 });
|
||||
this.actions = new Marionette.Actions(client);
|
||||
}
|
||||
module.exports = View;
|
||||
|
||||
View.prototype = {
|
||||
/**
|
||||
* Namespaces a Marionette.Client#findElement call into this view.
|
||||
*/
|
||||
findElement: function(selector) {
|
||||
return this
|
||||
.getElement()
|
||||
.findElement(selector);
|
||||
},
|
||||
|
||||
findElements: function(selector) {
|
||||
return this
|
||||
.getElement()
|
||||
.findElements(selector);
|
||||
},
|
||||
|
||||
/**
|
||||
* Grab the root element associated with this view.
|
||||
*/
|
||||
getElement: function() {
|
||||
return this.client.findElement(this.selector);
|
||||
},
|
||||
|
||||
get element() {
|
||||
return this.getElement();
|
||||
},
|
||||
|
||||
/**
|
||||
* Whether or not the root element associated with
|
||||
* this view is displayed.
|
||||
*/
|
||||
displayed: function() {
|
||||
return this.getElement().displayed();
|
||||
},
|
||||
|
||||
waitForDisplay: function() {
|
||||
return this.client.waitFor(this.displayed.bind(this));
|
||||
},
|
||||
|
||||
waitForHide: function() {
|
||||
return this.client.waitFor(function() {
|
||||
return !this.displayed();
|
||||
}.bind(this));
|
||||
},
|
||||
|
||||
/* Form helpers */
|
||||
|
||||
setFormValue: function(name, value) {
|
||||
var formData = {};
|
||||
formData[name] = value;
|
||||
this.setFormData(formData);
|
||||
|
||||
// Send input event to trigger the listener method.
|
||||
// e.g. Set the start date to trigger doing auto change end date.
|
||||
this.form.findElement('[name="' + name + '"]')
|
||||
.scriptWith(function(el) {
|
||||
el.dispatchEvent(new Event('input'));
|
||||
el.dispatchEvent(new Event('change'));
|
||||
});
|
||||
this.form.scriptWith(function(el) {
|
||||
el.dispatchEvent(new Event('input'));
|
||||
el.dispatchEvent(new Event('change'));
|
||||
});
|
||||
},
|
||||
|
||||
setFormData: function(formData) {
|
||||
this.client.forms.fill(this.form, formData);
|
||||
},
|
||||
|
||||
getFormValue: function(name) {
|
||||
return this.findElement('[name="' + name + '"]').getAttribute('value');
|
||||
}
|
||||
};
|
||||
268
gaia/apps/calendar/test/marionette/lib/views/week.js
Executable file
268
gaia/apps/calendar/test/marionette/lib/views/week.js
Executable file
|
|
@ -0,0 +1,268 @@
|
|||
'use strict';
|
||||
|
||||
var View = require('./view');
|
||||
|
||||
function Week() {
|
||||
View.apply(this, arguments);
|
||||
}
|
||||
module.exports = Week;
|
||||
|
||||
Week.prototype = {
|
||||
__proto__: View.prototype,
|
||||
|
||||
selector: '#week-view',
|
||||
|
||||
get sideBarHours() {
|
||||
return this.findElements('.md__sidebar .md__hour .md__display-hour');
|
||||
},
|
||||
|
||||
get events() {
|
||||
return this.findElements('.md__event');
|
||||
},
|
||||
|
||||
get addEventButton() {
|
||||
return this.findElement('.md__add-event');
|
||||
},
|
||||
|
||||
get todayDates() {
|
||||
return this.findElements('.md__sticky .md__allday h1.is-today');
|
||||
},
|
||||
|
||||
get dayNames() {
|
||||
return this.findElements('.md__day-name').map(function(el) {
|
||||
return el.text();
|
||||
});
|
||||
},
|
||||
|
||||
get daysHolder() {
|
||||
return this.findElement('.md__days');
|
||||
},
|
||||
|
||||
get days() {
|
||||
return this.findElements('.md__day');
|
||||
},
|
||||
|
||||
get hours() {
|
||||
return this.findElements('.md__hour');
|
||||
},
|
||||
|
||||
get currentTime() {
|
||||
return this.findElement('.md__current-time');
|
||||
},
|
||||
|
||||
get currentHour() {
|
||||
var now = new Date();
|
||||
return this.findElement('.md__hour-'+ now.getHours());
|
||||
},
|
||||
|
||||
get currentDisplayHour() {
|
||||
return this.currentHour.findElement('.md__display-hour');
|
||||
},
|
||||
|
||||
get main() {
|
||||
return this.findElement('.md__main');
|
||||
},
|
||||
|
||||
get allDayIcon() {
|
||||
return this.findElement('.md__all-day');
|
||||
},
|
||||
|
||||
get activeAllDays() {
|
||||
return this.findElements('.md__allday[aria-hidden="false"]');
|
||||
},
|
||||
|
||||
get allDaysHolder() {
|
||||
return this.findElement('.md__alldays');
|
||||
},
|
||||
|
||||
get scrollTop() {
|
||||
return this.main.scriptWith(function(el) {
|
||||
return el.scrollTop;
|
||||
});
|
||||
},
|
||||
|
||||
/**
|
||||
* This method tries to find out where the hour square is located in the
|
||||
* window.
|
||||
*
|
||||
* @param {Number} hour The hour to locate.
|
||||
* @returns {Number} 0 if it's displayed, or the delta otherwise: negative
|
||||
* if it's above or positive if it's below.
|
||||
*/
|
||||
_getDeltaToFullyDisplayHour: function(hour) {
|
||||
var hourElt = this.hours[hour];
|
||||
var mainScroll = this.scrollTop;
|
||||
var hourOffset = hourElt.scriptWith(function(el) { return el.offsetTop; });
|
||||
var mainClientHeight = this.main.rect().height;
|
||||
var hourHeight = hourElt.rect().height;
|
||||
|
||||
if (hourOffset < mainScroll) {
|
||||
return hourOffset - mainScroll;
|
||||
}
|
||||
|
||||
var bottomHourOffset = hourOffset + hourHeight;
|
||||
var bottomScroll = mainScroll + mainClientHeight;
|
||||
if (bottomHourOffset > bottomScroll) {
|
||||
return bottomHourOffset - bottomScroll;
|
||||
}
|
||||
return 0;
|
||||
},
|
||||
|
||||
/**
|
||||
* This method waits until we don't scroll.
|
||||
*
|
||||
* Note this doesn't seem to work properly when the view is just displayed.
|
||||
* That's why waitForHourScrollEnd is still useful.
|
||||
*/
|
||||
waitForNoScroll: function() {
|
||||
var prevScrollTop = null;
|
||||
this.client.waitFor(function() {
|
||||
var currentScrollTop = this.scrollTop;
|
||||
if (prevScrollTop !== null && prevScrollTop === currentScrollTop) {
|
||||
return true;
|
||||
}
|
||||
prevScrollTop = currentScrollTop;
|
||||
return false;
|
||||
}.bind(this));
|
||||
},
|
||||
|
||||
/**
|
||||
* This method uses just the right amount of flicking to scroll page by page
|
||||
* until finding the right hour line.
|
||||
*
|
||||
* @param {Number} hour The hour to scroll to.
|
||||
*/
|
||||
scrollToHour: function(hour) {
|
||||
var mainRect = this.main.rect();
|
||||
var middleOfMain = mainRect.width / 2;
|
||||
|
||||
this.client.waitFor(function() {
|
||||
this.waitForNoScroll();
|
||||
|
||||
var delta = this._getDeltaToFullyDisplayHour(hour);
|
||||
if (delta === 0) {
|
||||
return true;
|
||||
}
|
||||
|
||||
var moveDelta = mainRect.height / 4;
|
||||
var beginPosition = 10;
|
||||
|
||||
// element is hidden above the visible part
|
||||
// we need to move it up
|
||||
if (delta < 0) {
|
||||
this.actions.flick(
|
||||
this.main,
|
||||
middleOfMain, beginPosition, middleOfMain, beginPosition + moveDelta
|
||||
).perform();
|
||||
}
|
||||
// element is hidden below the visible part.
|
||||
// we need to move it down
|
||||
if (delta > 0) {
|
||||
this.actions.flick(
|
||||
this.main,
|
||||
middleOfMain, beginPosition + moveDelta, middleOfMain, beginPosition
|
||||
).perform();
|
||||
}
|
||||
|
||||
return false;
|
||||
}.bind(this));
|
||||
},
|
||||
|
||||
/**
|
||||
* Tap the right square. This might throw if the square is not visible, so you
|
||||
* should use scrollToHour before calling this method.
|
||||
*
|
||||
* @param {Object} options
|
||||
* @param {Number} options.day The day to tap. This needs to be a visible day.
|
||||
* @param {Number} options.hour The hour to tap. It needs to be visible as
|
||||
* well.
|
||||
* @param {Number} [options.left] If present, it allows to tap at a specific
|
||||
* location in the square. It can be negative to tap at the left of the
|
||||
* button. Only one of `left`, `right` can be specified.
|
||||
* @param {Number} [options.right] If present, it allows to tap at a specific
|
||||
* location in the square. It can be negative to tap at the right of the
|
||||
* button.
|
||||
* @param {Number} [options.top] If present, it allows to tap at a specific
|
||||
* location in the square. It can be negative to tap at the top of the
|
||||
* button. Only one of `top`, `bottom` can be spcified.
|
||||
* @param {Number} [options.bottom] If present, it allows to tap at a specific
|
||||
* location in the square. It can be negative to tap at the bottom of the
|
||||
* button.
|
||||
*/
|
||||
tapDayHour: function(options) {
|
||||
var day = options.day;
|
||||
var hour = options.hour;
|
||||
var left = options.left;
|
||||
var right = options.right;
|
||||
var bottom = options.bottom;
|
||||
var top = options.top;
|
||||
|
||||
var dayElt = this.days[day];
|
||||
var hourElt = this.hours[hour];
|
||||
var dayEltRect = dayElt.rect();
|
||||
var hourEltRect = hourElt.rect();
|
||||
|
||||
if (left === undefined && right === undefined) {
|
||||
left = dayEltRect.width / 2;
|
||||
}
|
||||
|
||||
if (left === undefined) {
|
||||
left = dayEltRect.width - right;
|
||||
}
|
||||
|
||||
if (top === undefined && bottom === undefined) {
|
||||
top = hourEltRect.height / 2;
|
||||
}
|
||||
|
||||
if (top === undefined) {
|
||||
top = hourEltRect.height - bottom;
|
||||
}
|
||||
|
||||
top += hourEltRect.height * hour;
|
||||
|
||||
dayElt.tap(left, top);
|
||||
},
|
||||
|
||||
isShowingAddEventLink: function() {
|
||||
return this.element.scriptWith(function(el) {
|
||||
return !!el.querySelector('.md__add-event');
|
||||
});
|
||||
},
|
||||
|
||||
waitForHourScrollEnd: function(hour) {
|
||||
// if displaying current day it scrolls to current time, if not it scrolls
|
||||
// to 8AM; it should also scroll to the created event, that's why we allow
|
||||
// overriding the `hour`.
|
||||
if (hour == null) {
|
||||
hour = this.todayDates.length ?
|
||||
Math.max(new Date().getHours() - 1, 0) :
|
||||
8;
|
||||
}
|
||||
var expected = this.getDestinationScrollTop(hour);
|
||||
this._waitForScrollEnd(expected);
|
||||
},
|
||||
|
||||
_waitForScrollEnd: function(expected) {
|
||||
this.client.waitFor(function() {
|
||||
return this.scrollTop === expected;
|
||||
}.bind(this));
|
||||
this.client.waitFor(function() {
|
||||
return this.main.cssProperty('overflowY') !== 'hidden';
|
||||
}.bind(this));
|
||||
},
|
||||
|
||||
getDestinationScrollTop: function(hour) {
|
||||
hour = Math.max(hour, 0);
|
||||
var bottomScrollTop = this.main.scriptWith(function(el) {
|
||||
return el.scrollHeight - el.clientHeight;
|
||||
});
|
||||
var hourOffsetTop = this.hours[hour].scriptWith(function(el) {
|
||||
return el.offsetTop;
|
||||
});
|
||||
return Math.min(hourOffsetTop, bottomScrollTop);
|
||||
},
|
||||
|
||||
scrollToTop: function() {
|
||||
this.scrollToHour(0);
|
||||
}
|
||||
};
|
||||
145
gaia/apps/calendar/test/marionette/modify_event_view_test.js
Executable file
145
gaia/apps/calendar/test/marionette/modify_event_view_test.js
Executable file
|
|
@ -0,0 +1,145 @@
|
|||
'use strict';
|
||||
|
||||
var Calendar = require('./lib/calendar'),
|
||||
assert = require('chai').assert;
|
||||
|
||||
marionette('modify event view', function() {
|
||||
var app;
|
||||
var client = marionette.client({
|
||||
profile: {
|
||||
prefs: {
|
||||
// we need to disable the keyboard to avoid intermittent failures on
|
||||
// Travis (transitions might take longer to run and block UI)
|
||||
'dom.mozInputMethod.enabled': false,
|
||||
// Do not require the B2G-desktop app window to have focus (as per the
|
||||
// system window manager) in order for it to do focus-related things.
|
||||
'focusmanager.testmode': true
|
||||
}
|
||||
},
|
||||
desiredCapabilities: { raisesAccessibilityExceptions: false }
|
||||
});
|
||||
var editEvent;
|
||||
|
||||
setup(function() {
|
||||
app = new Calendar(client);
|
||||
editEvent = app.editEvent;
|
||||
app.launch();
|
||||
app.openModifyEventView();
|
||||
// we need a title or location otherwise event can't be saved
|
||||
editEvent.title = 'Reminder Test';
|
||||
});
|
||||
|
||||
suite('reminders', function() {
|
||||
test('default', function() {
|
||||
assert.deepEqual(editEvent.reminders, ['5 minutes before', 'None']);
|
||||
});
|
||||
|
||||
test('default allday', function() {
|
||||
editEvent.allDay = true;
|
||||
client.waitFor(function() {
|
||||
return editEvent.reminders[0] !== '5 minutes before';
|
||||
});
|
||||
assert.deepEqual(editEvent.reminders, ['On day of event', 'None']);
|
||||
});
|
||||
|
||||
test('set to none', function() {
|
||||
editEvent.setReminderValue('None', 0);
|
||||
client.waitFor(function() {
|
||||
return editEvent.reminders.length === 1;
|
||||
});
|
||||
assert.deepEqual(editEvent.reminders, ['None']);
|
||||
});
|
||||
|
||||
test('multiple reminders', function() {
|
||||
var reminders = [
|
||||
'At time of event',
|
||||
'5 minutes before',
|
||||
'15 minutes before',
|
||||
'30 minutes before',
|
||||
'1 hour before',
|
||||
'2 hours before',
|
||||
'1 day before'
|
||||
];
|
||||
editEvent.reminders = reminders;
|
||||
assert.deepEqual(
|
||||
editEvent.reminders,
|
||||
// maximum amount of reminders is 5, so it will override the last one
|
||||
reminders.slice(0, 4).concat(['1 day before'])
|
||||
);
|
||||
});
|
||||
|
||||
test('duplicates', function() {
|
||||
// it should only save a single reminder if user adds duplicates
|
||||
editEvent.reminders = [
|
||||
'15 minutes before',
|
||||
'15 minutes before',
|
||||
'15 minutes before',
|
||||
'15 minutes before'
|
||||
];
|
||||
editEvent.save();
|
||||
app.monthDay.waitForDisplay();
|
||||
app.monthDay.events[0].click();
|
||||
app.readEvent.waitForDisplay();
|
||||
app.readEvent.edit();
|
||||
editEvent.waitForDisplay();
|
||||
assert.deepEqual(editEvent.reminders, ['15 minutes before', 'None']);
|
||||
});
|
||||
});
|
||||
|
||||
suite('auto change end date and time', function() {
|
||||
var startDate = new Date('Sep 08 2014 12:34:00');
|
||||
var startDatePreviousHour = new Date(startDate);
|
||||
var startDatePreviousDay = new Date(startDate);
|
||||
var startDateNextHour = new Date(startDate);
|
||||
var startDateNextDay = new Date(startDate);
|
||||
|
||||
startDatePreviousHour.setHours(startDate.getHours() - 1);
|
||||
startDatePreviousDay.setDate(startDate.getDate() - 1);
|
||||
startDateNextHour.setHours(startDate.getHours() + 1);
|
||||
startDateNextDay.setDate(startDate.getDate() + 1);
|
||||
|
||||
setup(function() {
|
||||
editEvent.startDate = startDate;
|
||||
editEvent.startTime = startDate;
|
||||
editEvent.endDate = startDateNextDay;
|
||||
editEvent.endTime = startDateNextHour;
|
||||
});
|
||||
|
||||
test('end date should auto change to previous day', function() {
|
||||
editEvent.startDate = startDatePreviousDay;
|
||||
assert.equal(editEvent.endDate, '2014-09-08', 'change to previous day');
|
||||
});
|
||||
|
||||
test('end time should auto change to previous hour', function() {
|
||||
editEvent.startTime = startDatePreviousHour;
|
||||
assert.equal(editEvent.endTime, '12:34:00', 'change to previous hour');
|
||||
});
|
||||
|
||||
test('start datetime could not be after end datetime', function() {
|
||||
editEvent.endDate = startDatePreviousDay;
|
||||
assert.equal(
|
||||
editEvent.errors,
|
||||
'The event cannot end before its start date',
|
||||
'show the correct error message'
|
||||
);
|
||||
});
|
||||
|
||||
suite('12/24 hour format', function() {
|
||||
test('default format: 12 hour', function() {
|
||||
app.switch12HourTimeFormat();
|
||||
assert.equal(editEvent.startTimeLocale.text(), '12:34 PM',
|
||||
'check start time locale');
|
||||
assert.equal(editEvent.endTimeLocale.text(), '1:34 PM',
|
||||
'check end time locale');
|
||||
});
|
||||
|
||||
test('switch to 24 hour format', function() {
|
||||
app.switch24HourTimeFormat();
|
||||
assert.equal(editEvent.startTimeLocale.text(), '12:34',
|
||||
'check start time locale');
|
||||
assert.equal(editEvent.endTimeLocale.text(), '13:34',
|
||||
'check end time locale');
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
240
gaia/apps/calendar/test/marionette/month_view_test.js
Executable file
240
gaia/apps/calendar/test/marionette/month_view_test.js
Executable file
|
|
@ -0,0 +1,240 @@
|
|||
'use strict';
|
||||
|
||||
var Calendar = require('./lib/calendar'),
|
||||
assert = require('chai').assert;
|
||||
|
||||
marionette('month view', function() {
|
||||
var app;
|
||||
var client = marionette.client({
|
||||
profile: {
|
||||
prefs: {
|
||||
// we need to disable the keyboard to avoid intermittent failures on
|
||||
// Travis (transitions might take longer to run and block UI)
|
||||
'dom.mozInputMethod.enabled': false,
|
||||
// Do not require the B2G-desktop app window to have focus (as per the
|
||||
// system window manager) in order for it to do focus-related things.
|
||||
'focusmanager.testmode': true,
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
setup(function() {
|
||||
app = new Calendar(client);
|
||||
app.launch();
|
||||
});
|
||||
|
||||
test('#month-day-agenda scroll', function() {
|
||||
// Create a lot of events so we can scroll so fun!
|
||||
for (var i = 0; i < 5; i++) {
|
||||
app.createEvent({
|
||||
title: 'Launch the Hypermatter Reactor',
|
||||
location: 'Alderaan',
|
||||
startHour: i
|
||||
});
|
||||
}
|
||||
|
||||
app.openMonthView();
|
||||
var monthDay = app.monthDay.getElement();
|
||||
var container = app.monthDay.container;
|
||||
assert.equal(
|
||||
monthDay.getAttribute('scrollTop'),
|
||||
0,
|
||||
'scroll should start at zero'
|
||||
);
|
||||
|
||||
var pos = container.location();
|
||||
var x = pos.x + 30;
|
||||
var body = client.findElement('body');
|
||||
// fast vertical swipe, needs to happen on the body since we want
|
||||
// coordinates to be absolute
|
||||
app.actions
|
||||
.flick(body, x, pos.y + 10, x, 50)
|
||||
.perform();
|
||||
|
||||
// this will timeout if scroll did not change
|
||||
client.waitFor(function() {
|
||||
return monthDay.getAttribute('scrollTop') > 0;
|
||||
});
|
||||
});
|
||||
|
||||
test('week day headers should only have one letter', function() {
|
||||
var month = app.month;
|
||||
var weekdayHeaders = month.weekdayHeaders;
|
||||
assert.deepEqual(weekdayHeaders, ['S', 'M', 'T', 'W', 'T', 'F', 'S']);
|
||||
});
|
||||
|
||||
test('day number text should be centered', function() {
|
||||
var month = app.month;
|
||||
var daySquares = month.daySquares;
|
||||
var textAlign = daySquares[0].scriptWith(function(element) {
|
||||
return window.getComputedStyle(element).textAlign;
|
||||
});
|
||||
|
||||
assert.equal(textAlign, 'center');
|
||||
});
|
||||
|
||||
test('should gray out numbers outside current month', function() {
|
||||
function isOutsideMonthWithGrayText(square) {
|
||||
var gray = square.scriptWith(function(element) {
|
||||
return window.getComputedStyle(element).color;
|
||||
}) === 'rgb(189, 189, 189)';
|
||||
|
||||
var otherMonth = square
|
||||
.getAttribute('className')
|
||||
.indexOf('other-month') !== -1;
|
||||
return gray && otherMonth;
|
||||
}
|
||||
|
||||
// Either the first or last day chronologically must be
|
||||
// outside of the current month (except for in February!).
|
||||
|
||||
// Swipe to next month if it's February.
|
||||
var monthDay = app.monthDay;
|
||||
var date = monthDay.date;
|
||||
date = date.toLowerCase();
|
||||
if (date.indexOf('feb') !== -1) {
|
||||
// Swipe to another month.
|
||||
app.swipeLeft();
|
||||
}
|
||||
|
||||
var month = app.month;
|
||||
var daySquares = month.daySquares;
|
||||
assert.isTrue(daySquares.some(isOutsideMonthWithGrayText));
|
||||
});
|
||||
|
||||
test('should not change background outside current month', function() {
|
||||
function isOutsideMonthWithNonWhiteBackground(square) {
|
||||
var nonwhite = square.scriptWith(function(element) {
|
||||
return window.getComputedStyle(element).backgroundColor;
|
||||
}) !== 'rgb(255, 255, 255)';
|
||||
|
||||
var otherMonth = square
|
||||
.getAttribute('className')
|
||||
.indexOf('other-month') !== -1;
|
||||
return nonwhite && otherMonth;
|
||||
}
|
||||
|
||||
var month = app.month;
|
||||
var daySquares = month.daySquares;
|
||||
assert.isFalse(daySquares.some(isOutsideMonthWithNonWhiteBackground));
|
||||
});
|
||||
|
||||
test('should show event dots', function() {
|
||||
function createEvent(startHour) {
|
||||
app.createEvent({
|
||||
title: 'Launch the Hypermatter Reactor',
|
||||
location: 'Alderaan',
|
||||
startHour: startHour
|
||||
});
|
||||
}
|
||||
|
||||
var month = app.month;
|
||||
var todaySquare = month.todaySquare;
|
||||
|
||||
// 1 dot.
|
||||
createEvent(1 /* startHour */);
|
||||
assert.lengthOf(month.squareDots(todaySquare), 1);
|
||||
|
||||
// 2 dots.
|
||||
createEvent(2 /* startHour */);
|
||||
assert.lengthOf(month.squareDots(todaySquare), 2);
|
||||
|
||||
// 3 dots.
|
||||
createEvent(3 /* startHour */);
|
||||
assert.lengthOf(month.squareDots(todaySquare), 3);
|
||||
|
||||
// More dots?
|
||||
createEvent(4 /* startHour */);
|
||||
assert.lengthOf(month.squareDots(todaySquare), 3);
|
||||
});
|
||||
|
||||
test('should have today date in day events section', function() {
|
||||
var today = new Date();
|
||||
var expected =
|
||||
dayName(today.getDay()).toUpperCase() + ', ' +
|
||||
monthName(today.getMonth()).toUpperCase().substring(0, 3) + ' ' +
|
||||
today.getDate();
|
||||
var monthDay = app.monthDay;
|
||||
var actual = monthDay.date;
|
||||
assert.equal(actual, expected);
|
||||
});
|
||||
|
||||
test('today event', function() {
|
||||
app.createEvent({
|
||||
title: 'Launch the Hypermatter Reactor',
|
||||
location: 'Alderaan',
|
||||
startHour: 1
|
||||
});
|
||||
|
||||
app.openMonthView();
|
||||
|
||||
var monthDay = app.monthDay;
|
||||
var events = monthDay.events;
|
||||
var event = events[0];
|
||||
var text = event.text();
|
||||
|
||||
assert.include(text, '1:00 AM', 'should show start time');
|
||||
assert.include(text, 'Launch', 'should show title');
|
||||
assert.include(event.text(), 'Alderaan', 'should show location');
|
||||
});
|
||||
|
||||
test('double tap', function() {
|
||||
var month = app.month;
|
||||
month.actions.doubleTap(month.currentDay).perform();
|
||||
|
||||
var editEvent = app.editEvent;
|
||||
editEvent.waitForDisplay();
|
||||
var now = new Date();
|
||||
var start = new Date(now.getTime());
|
||||
start.setHours(now.getHours() + 1, 0, 0, 0);
|
||||
var end = new Date(start.getTime());
|
||||
end.setHours(start.getHours() + 1, 0, 0, 0);
|
||||
assert.equal(editEvent.startDate, toIso(start), 'startDate');
|
||||
assert.equal(editEvent.endDate, toIso(end), 'endDate');
|
||||
assert.equal(editEvent.startTime, toTime(start), 'startTime');
|
||||
assert.equal(editEvent.endTime, toTime(end), 'endTime');
|
||||
});
|
||||
});
|
||||
|
||||
function toIso(date) {
|
||||
return date.getFullYear() + '-' + pad(date.getMonth() + 1) + '-' +
|
||||
pad(date.getDate());
|
||||
}
|
||||
|
||||
function pad(num) {
|
||||
return num > 9 ? num : '0' + num;
|
||||
}
|
||||
|
||||
function toTime(date) {
|
||||
return pad(date.getHours()) + ':' + pad(date.getMinutes()) + ':' +
|
||||
pad(date.getSeconds());
|
||||
}
|
||||
|
||||
function dayName(num) {
|
||||
return [
|
||||
'Sunday',
|
||||
'Monday',
|
||||
'Tuesday',
|
||||
'Wednesday',
|
||||
'Thursday',
|
||||
'Friday',
|
||||
'Saturday'
|
||||
][num];
|
||||
}
|
||||
|
||||
function monthName(num) {
|
||||
return [
|
||||
'January',
|
||||
'February',
|
||||
'March',
|
||||
'April',
|
||||
'May',
|
||||
'June',
|
||||
'July',
|
||||
'August',
|
||||
'September',
|
||||
'October',
|
||||
'November',
|
||||
'December'
|
||||
][num];
|
||||
}
|
||||
241
gaia/apps/calendar/test/marionette/server_test.js
Executable file
241
gaia/apps/calendar/test/marionette/server_test.js
Executable file
|
|
@ -0,0 +1,241 @@
|
|||
/* global require, marionette, setup, suite, teardown, test */
|
||||
'use strict';
|
||||
|
||||
var Calendar = require('./lib/calendar'),
|
||||
Radicale = require('./lib/radicale'),
|
||||
assert = require('chai').assert,
|
||||
debug = require('common/debug')('marionette:server_test');
|
||||
|
||||
var calendarName = 'firefox-os';
|
||||
|
||||
marionette('interop basic', function() {
|
||||
var app, server;
|
||||
var client = marionette.client({
|
||||
desiredCapabilities: { raisesAccessibilityExceptions: false }
|
||||
});
|
||||
|
||||
var vanillaEvent = Object.freeze({
|
||||
title: 'Vanilla event',
|
||||
location: 'Baskin Robbins',
|
||||
description: 'What better a place for a vanilla event!',
|
||||
calendar: calendarName,
|
||||
startHour: 17,
|
||||
reminders: []
|
||||
});
|
||||
|
||||
var allDayEvent = Object.freeze({
|
||||
title: 'All day event',
|
||||
location: 'Planet Earth',
|
||||
description: 'Except for the mars rover!',
|
||||
calendar: calendarName,
|
||||
allDay: true,
|
||||
reminders: []
|
||||
});
|
||||
|
||||
var eventWithAlarms = Object.freeze({
|
||||
title: 'Event with alarms',
|
||||
location: 'Fire Station',
|
||||
description: 'Wee-Woo-Wee-Woo-Wee-Woo',
|
||||
calendar: calendarName,
|
||||
startHour: 10,
|
||||
reminders: ['5 minutes before', '1 hour before']
|
||||
});
|
||||
|
||||
var vanillaElement, allDayElement, elementWithAlarms;
|
||||
|
||||
function onServerUp(callback) {
|
||||
debug('Launch calendar.');
|
||||
app = new Calendar(client);
|
||||
app.launch();
|
||||
|
||||
app.setupAccount({
|
||||
accountType: 'caldav',
|
||||
user: calendarName,
|
||||
fullUrl: 'http://127.0.0.1:' + server.port + '/' + calendarName
|
||||
});
|
||||
|
||||
app.monthDay.events.forEach(function(event) {
|
||||
switch (event.title.trim()) {
|
||||
case vanillaEvent.title:
|
||||
vanillaElement = event;
|
||||
break;
|
||||
case allDayEvent.title:
|
||||
allDayElement = event;
|
||||
break;
|
||||
case eventWithAlarms.title:
|
||||
elementWithAlarms = event;
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
});
|
||||
|
||||
callback();
|
||||
}
|
||||
|
||||
suite('server', function() {
|
||||
setup(function(done) {
|
||||
// Start server if it's not already up.
|
||||
if (server) {
|
||||
return onServerUp(done);
|
||||
}
|
||||
|
||||
server = new Radicale();
|
||||
server.start(function() {
|
||||
onServerUp(done);
|
||||
});
|
||||
});
|
||||
|
||||
teardown(function() {
|
||||
debug('Delete account from calendar app.');
|
||||
app.teardownAccount(calendarName);
|
||||
// Now make sure that the account was actually deleted (see bug 1036753)
|
||||
var synced = getCalendars(app);
|
||||
assert.notInclude(synced, calendarName, 'calendar should be deleted');
|
||||
});
|
||||
|
||||
|
||||
/**
|
||||
* Major hack around suiteSetup not being fully supported
|
||||
* in marionette-js-runner.
|
||||
*/
|
||||
test('suiteSetup', function() {
|
||||
debug('Create test data.');
|
||||
[
|
||||
vanillaEvent,
|
||||
allDayEvent,
|
||||
eventWithAlarms
|
||||
].forEach(function(event) {
|
||||
app.createEvent(event);
|
||||
});
|
||||
|
||||
debug('Sync created data.');
|
||||
app.sync();
|
||||
});
|
||||
|
||||
|
||||
test('should get calendar', function() {
|
||||
var synced = getCalendars(app);
|
||||
assert.include(synced, calendarName, calendarName + ' should be synced');
|
||||
});
|
||||
|
||||
test('should get existing events', function() {
|
||||
var events = app.monthDay.events;
|
||||
[
|
||||
vanillaEvent,
|
||||
allDayEvent,
|
||||
eventWithAlarms
|
||||
].forEach(function(created) {
|
||||
debug('Looking for event titled ' + created.title + '.');
|
||||
assert.ok(
|
||||
events.some(function(event) {
|
||||
app.monthDay.scrollToEvent(event);
|
||||
debug('Found ' + event.title.trim());
|
||||
return event.title.trim() === created.title;
|
||||
})
|
||||
);
|
||||
});
|
||||
});
|
||||
|
||||
// TODO(gareth): Fix me!
|
||||
test.skip('event start times', function() {
|
||||
assert.strictEqual(
|
||||
vanillaElement.closestHour,
|
||||
vanillaEvent.startHour
|
||||
);
|
||||
assert.strictEqual(
|
||||
elementWithAlarms.closestHour,
|
||||
eventWithAlarms.startHour
|
||||
);
|
||||
// Make sure that we are inside an all day element.
|
||||
assert.ok(
|
||||
!!allDayElement.closestAllDay,
|
||||
'should recognize all day events'
|
||||
);
|
||||
});
|
||||
|
||||
// TODO(gareth): Fix me!
|
||||
test.skip('event descriptions', function() {
|
||||
[
|
||||
{ element: vanillaElement, event: vanillaEvent },
|
||||
{ element: allDayElement, event: allDayEvent },
|
||||
{ element: elementWithAlarms, event: eventWithAlarms }
|
||||
].forEach(function(obj) {
|
||||
var element = obj.element,
|
||||
event = obj.event;
|
||||
app.monthDay.scrollToEvent(element);
|
||||
element.click();
|
||||
app.readEvent.waitForDisplay();
|
||||
assert.strictEqual(
|
||||
app.readEvent.description.trim(),
|
||||
event.description
|
||||
);
|
||||
|
||||
app.readEvent.cancel();
|
||||
app.month.waitForDisplay();
|
||||
});
|
||||
});
|
||||
|
||||
test('event locations', function() {
|
||||
assert.strictEqual(
|
||||
vanillaElement.address.trim(),
|
||||
vanillaEvent.location
|
||||
);
|
||||
assert.strictEqual(
|
||||
allDayElement.address.trim(),
|
||||
allDayEvent.location
|
||||
);
|
||||
assert.strictEqual(
|
||||
elementWithAlarms.address.trim(),
|
||||
eventWithAlarms.location
|
||||
);
|
||||
});
|
||||
|
||||
test('event alarms', function() {
|
||||
assert.isFalse(vanillaElement.hasAlarms());
|
||||
assert.isFalse(allDayElement.hasAlarms());
|
||||
assert.isTrue(elementWithAlarms.hasAlarms());
|
||||
});
|
||||
|
||||
test('suiteTeardown', function(done) {
|
||||
// TODO(gareth): Should be able to delete test data
|
||||
// from within calendar app.
|
||||
debug('Delete test data.');
|
||||
/*
|
||||
var events = app.monthDay.events;
|
||||
events.forEach(function(event) {
|
||||
app.monthDay.scrollToEvent(event);
|
||||
event.click();
|
||||
app.readEvent.waitForDisplay();
|
||||
if (app.readEvent.editable) {
|
||||
app.readEvent.edit();
|
||||
app.editEvent.waitForDisplay();
|
||||
app.editEvent.delete();
|
||||
} else {
|
||||
app.readEvent.cancel();
|
||||
}
|
||||
|
||||
app.monthDay.waitForDisplay();
|
||||
});
|
||||
|
||||
debug('Sync deletions.');
|
||||
app.sync();
|
||||
*/
|
||||
try {
|
||||
server.removeAllEvents();
|
||||
} catch (error) {
|
||||
console.error(error.toString());
|
||||
} finally {
|
||||
server.close(done);
|
||||
}
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
function getCalendars(app) {
|
||||
debug('Reading list of calendars from settings view.');
|
||||
app.openSettingsView();
|
||||
var synced = app.settings.calendars();
|
||||
app.closeSettingsView();
|
||||
return synced;
|
||||
}
|
||||
32
gaia/apps/calendar/test/marionette/settings_test.js
Executable file
32
gaia/apps/calendar/test/marionette/settings_test.js
Executable file
|
|
@ -0,0 +1,32 @@
|
|||
'use strict';
|
||||
|
||||
var Calendar = require('./lib/calendar'),
|
||||
assert = require('chai').assert;
|
||||
|
||||
marionette('settings', function() {
|
||||
var app;
|
||||
var client = marionette.client({
|
||||
desiredCapabilities: { raisesAccessibilityExceptions: false }
|
||||
});
|
||||
|
||||
setup(function() {
|
||||
app = new Calendar(client);
|
||||
app.launch();
|
||||
});
|
||||
|
||||
test('theme color', function() {
|
||||
assert.equal(app.themeColor, 'var(--header-background)', 'index');
|
||||
app.openSettingsView();
|
||||
assert.equal(app.themeColor, 'var(--header-background)', 'settings');
|
||||
app.closeSettingsView();
|
||||
app.openDayView();
|
||||
assert.equal(app.themeColor, 'var(--header-background)', 'day view');
|
||||
app.openAdvancedSettingsView();
|
||||
assert.equal(app.themeColor, '#eeeeee', 'advanced settings');
|
||||
app.closeAdvancedSettingsView();
|
||||
assert.equal(app.themeColor, 'var(--header-background)', 'settings #2');
|
||||
app.closeSettingsView();
|
||||
assert.equal(app.themeColor, 'var(--header-background)', 'day view #2');
|
||||
});
|
||||
|
||||
});
|
||||
79
gaia/apps/calendar/test/marionette/today_test.js
Executable file
79
gaia/apps/calendar/test/marionette/today_test.js
Executable file
|
|
@ -0,0 +1,79 @@
|
|||
'use strict';
|
||||
|
||||
var Calendar = require('./lib/calendar'),
|
||||
assert = require('chai').assert,
|
||||
moment = require('moment');
|
||||
|
||||
var DATE_FORMAT = 'YYYY-MM-DD';
|
||||
|
||||
marionette('today', function() {
|
||||
var app;
|
||||
|
||||
var client = marionette.client({
|
||||
profile: {
|
||||
prefs: {
|
||||
// we need to disable the keyboard to avoid intermittent failures on
|
||||
// Travis (transitions might take longer to run and block UI)
|
||||
'dom.mozInputMethod.enabled': false,
|
||||
// Do not require the B2G-desktop app window to have focus (as per the
|
||||
// system window manager) in order for it to do focus-related things.
|
||||
'focusmanager.testmode': true,
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
setup(function() {
|
||||
app = new Calendar(client);
|
||||
app.launch();
|
||||
});
|
||||
|
||||
var scenarios = [
|
||||
{
|
||||
name: 'should show in header, highlighted day',
|
||||
setup: function() {
|
||||
// In the first scenario we do nothing
|
||||
// and expect for the current date to be
|
||||
// reflected in the month view.
|
||||
}
|
||||
},
|
||||
{
|
||||
name: 'should be show in header, highlighted day after tap today',
|
||||
setup: function() {
|
||||
// In the second scenario, we swipe to the next month
|
||||
// and then click on the today button. We also
|
||||
// expect for the current date to be reflected in the month view.
|
||||
app
|
||||
.swipeLeft()
|
||||
.clickToday();
|
||||
}
|
||||
}
|
||||
];
|
||||
|
||||
scenarios.forEach(function(scenario) {
|
||||
test(scenario.name, function() {
|
||||
var expectedDate = new Date();
|
||||
scenario.setup();
|
||||
|
||||
// Check to make sure the "present" day matches today's day.
|
||||
var day = app.month.currentDay;
|
||||
var currentDate = day.text();
|
||||
// Check to make sure the month feeding the date header is correct.
|
||||
var date = app
|
||||
.headerContent
|
||||
.getAttribute('data-date');
|
||||
|
||||
assert.equal(currentDate, expectedDate.getDate());
|
||||
assert.equal(
|
||||
moment(date).format(DATE_FORMAT),
|
||||
moment(expectedDate).format(DATE_FORMAT)
|
||||
);
|
||||
});
|
||||
});
|
||||
|
||||
test('should show correct date in the today icon', function() {
|
||||
var todayDate = client.findElement(
|
||||
'#view-selector a[href="#today"] .icon-calendar-today'
|
||||
);
|
||||
assert.equal(todayDate.text(), new Date().getDate());
|
||||
});
|
||||
});
|
||||
111
gaia/apps/calendar/test/marionette/toggle_calendar_test.js
Executable file
111
gaia/apps/calendar/test/marionette/toggle_calendar_test.js
Executable file
|
|
@ -0,0 +1,111 @@
|
|||
'use strict';
|
||||
|
||||
var Calendar = require('./lib/calendar'),
|
||||
assert = require('chai').assert;
|
||||
|
||||
marionette('toggle calendar', function() {
|
||||
var app;
|
||||
var client = marionette.client({
|
||||
profile: {
|
||||
prefs: {
|
||||
// we need to disable the keyboard to avoid intermittent failures on
|
||||
// Travis (transitions might take longer to run and block UI)
|
||||
'dom.mozInputMethod.enabled': false,
|
||||
// Do not require the B2G-desktop app window to have focus (as per the
|
||||
// system window manager) in order for it to do focus-related things.
|
||||
'focusmanager.testmode': true,
|
||||
}
|
||||
},
|
||||
desiredCapabilities: { raisesAccessibilityExceptions: false }
|
||||
});
|
||||
|
||||
setup(function() {
|
||||
app = new Calendar(client);
|
||||
app.launch();
|
||||
});
|
||||
|
||||
function toggleLocalCalendar() {
|
||||
app.openSettingsView();
|
||||
app.settings.toggleCalendar();
|
||||
app.closeSettingsView();
|
||||
}
|
||||
|
||||
suite('> regular event', function() {
|
||||
setup(function() {
|
||||
app.createEvent({
|
||||
title: 'Toggle Calendar Test',
|
||||
location: 'Some Place'
|
||||
});
|
||||
toggleLocalCalendar();
|
||||
});
|
||||
|
||||
suite('disable calendar', function() {
|
||||
test('month view', function() {
|
||||
client.waitFor(function() {
|
||||
return app.monthDay.events.length === 0;
|
||||
});
|
||||
assert.equal(app.month.busyDots.length, 0, 'no busy dots');
|
||||
});
|
||||
|
||||
test('week view', function() {
|
||||
app.openWeekView();
|
||||
client.waitFor(function() {
|
||||
return app.week.events.length === 0;
|
||||
});
|
||||
});
|
||||
|
||||
test('day view', function() {
|
||||
app.openDayView();
|
||||
client.waitFor(function() {
|
||||
return app.day.events.length === 0;
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
suite('enable calendar', function() {
|
||||
setup(toggleLocalCalendar);
|
||||
|
||||
test('month view', function() {
|
||||
client.waitFor(function() {
|
||||
return app.monthDay.events.length;
|
||||
});
|
||||
});
|
||||
|
||||
test('week view', function() {
|
||||
app.openWeekView();
|
||||
client.waitFor(function() {
|
||||
return app.week.events.length;
|
||||
});
|
||||
});
|
||||
|
||||
test('day view', function() {
|
||||
app.openDayView();
|
||||
client.waitFor(function() {
|
||||
return app.day.events.length;
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
suite('> all day event', function() {
|
||||
setup(function() {
|
||||
app.createEvent({
|
||||
title: 'Toggle Calendar Test',
|
||||
location: 'Some Place',
|
||||
allDay: true
|
||||
});
|
||||
toggleLocalCalendar();
|
||||
});
|
||||
|
||||
test('should not hide all day on day view', function() {
|
||||
app.openDayView();
|
||||
client.waitFor(function() {
|
||||
return app.day.events.length === 0;
|
||||
});
|
||||
assert.ok(
|
||||
app.day.allDaysHolder.displayed(),
|
||||
'all day should be displayed'
|
||||
);
|
||||
});
|
||||
});
|
||||
});
|
||||
431
gaia/apps/calendar/test/marionette/week_view_test.js
Executable file
431
gaia/apps/calendar/test/marionette/week_view_test.js
Executable file
|
|
@ -0,0 +1,431 @@
|
|||
'use strict';
|
||||
|
||||
var Calendar = require('./lib/calendar'),
|
||||
assert = require('chai').assert;
|
||||
|
||||
marionette('week view', function() {
|
||||
var app, week;
|
||||
|
||||
var client = marionette.client({
|
||||
profile: {
|
||||
prefs: {
|
||||
// we need to disable the keyboard to avoid intermittent failures on
|
||||
// Travis (transitions might take longer to run and block UI)
|
||||
'dom.mozInputMethod.enabled': false,
|
||||
// Do not require the B2G-desktop app window to have focus (as per the
|
||||
// system window manager) in order for it to do focus-related things.
|
||||
'focusmanager.testmode': true,
|
||||
}
|
||||
},
|
||||
desiredCapabilities: { raisesAccessibilityExceptions: false }
|
||||
});
|
||||
|
||||
setup(function() {
|
||||
app = new Calendar(client);
|
||||
app.launch();
|
||||
week = app.week;
|
||||
app.openWeekView();
|
||||
});
|
||||
|
||||
test('hours', function() {
|
||||
var hours = week.hours;
|
||||
var i = -1, h, hour;
|
||||
var currentHour = (new Date()).getHours();
|
||||
while (++i < 24) {
|
||||
h = i % 12 || 12;
|
||||
hour = hours[i];
|
||||
var text = h + '\n' + (i < 12 ? 'AM' : 'PM');
|
||||
// current hour is hidden because "current time" line overlaps it!
|
||||
if (i === currentHour) {
|
||||
text = '';
|
||||
}
|
||||
assert.strictEqual(hour.text(), text, 'hour text');
|
||||
}
|
||||
});
|
||||
|
||||
test('swipe should change date', function() {
|
||||
var prevText = app.headerContent.text();
|
||||
var swipeCount = 0;
|
||||
var headerCount = 0;
|
||||
var multiMonthCount = 0;
|
||||
// match the header on a multi week view, we just check for 2 dates since
|
||||
// month names will have different patterns on each locale
|
||||
var multiMonthPattern = /\d{4}.+\d{4}$/;
|
||||
|
||||
while (++swipeCount < 30) {
|
||||
var text = app.headerContent.text();
|
||||
|
||||
// we are not checking for real overflow since font is different on each
|
||||
// environment (Travis uses a wider font) which would make test to fail
|
||||
// https://groups.google.com/forum/#!topic/mozilla.dev.gaia/DrQzv7qexw4
|
||||
assert.operator(text.length, '<', 21, 'header should not overflow');
|
||||
|
||||
if (multiMonthPattern.test(text)) {
|
||||
multiMonthCount += 1;
|
||||
}
|
||||
|
||||
// jshint -W083
|
||||
// there was a bug during implementation that caused week view to add
|
||||
// duplicate dates to the DOM, checking if the days have a different
|
||||
// "name" should be enough to catch regressions
|
||||
var duplicateNames = week.dayNames.filter(function(name, i, arr) {
|
||||
return arr.indexOf(name, i + 1) !== -1;
|
||||
});
|
||||
assert.equal(duplicateNames.join(', '), '', 'duplicate dates');
|
||||
// jshint +W083
|
||||
|
||||
if (prevText !== text) {
|
||||
prevText = text;
|
||||
headerCount += 1;
|
||||
}
|
||||
|
||||
app.swipeLeft();
|
||||
}
|
||||
|
||||
assert.operator(headerCount, '>', 1, 'should update header at least 2x');
|
||||
assert.operator(multiMonthCount, '>', 0, 'header with multiple months');
|
||||
});
|
||||
|
||||
test('event + style + scroll + click', function() {
|
||||
// "better" to do all these checks at once because of performance
|
||||
|
||||
// we don't set the startHour because the view will scroll to current time
|
||||
// by default (since it's displaying today)
|
||||
var eventData = {
|
||||
title: 'Test Week View',
|
||||
location: 'Somewhere'
|
||||
};
|
||||
|
||||
app.createEvent(eventData);
|
||||
|
||||
// this is also enough to test if the view is scrolling to the proper
|
||||
// destination (will timeout if it doesn't end or click won't trigger on
|
||||
// proper element)
|
||||
week.waitForHourScrollEnd();
|
||||
|
||||
var event = week.events[0];
|
||||
|
||||
assert.strictEqual(
|
||||
event.text(),
|
||||
eventData.title,
|
||||
'display event title'
|
||||
);
|
||||
|
||||
assert.match(
|
||||
event.cssProperty('background-color'),
|
||||
/rgba\(.+0.2\)/,
|
||||
'should set bg color'
|
||||
);
|
||||
|
||||
assert.ok(
|
||||
event.cssProperty('border-left-color'),
|
||||
'should set the border color'
|
||||
);
|
||||
|
||||
assert.operator(
|
||||
parseFloat(event.cssProperty('border-left-width')), '>', 0,
|
||||
'should have border left width'
|
||||
);
|
||||
|
||||
assert.strictEqual(
|
||||
event.cssProperty('border-left-style'),
|
||||
'solid',
|
||||
'should have solid border'
|
||||
);
|
||||
|
||||
event.click();
|
||||
app.readEvent.waitForDisplay();
|
||||
assert.strictEqual(
|
||||
app.readEvent.title,
|
||||
eventData.title,
|
||||
'title'
|
||||
);
|
||||
assert.strictEqual(
|
||||
app.readEvent.location,
|
||||
eventData.location,
|
||||
'location'
|
||||
);
|
||||
});
|
||||
|
||||
test('overlaps', function() {
|
||||
function eventTitlesDisplayed() {
|
||||
return week.events.every(checkDisplay);
|
||||
}
|
||||
|
||||
function checkDisplay(el) {
|
||||
return el.text().length > 0 && el.displayed();
|
||||
}
|
||||
|
||||
app.createEvent({
|
||||
title: '1 Overlap',
|
||||
location: 'Somewhere'
|
||||
});
|
||||
|
||||
assert.ok(
|
||||
eventTitlesDisplayed(),
|
||||
'title should be displayed if no overlaps'
|
||||
);
|
||||
|
||||
app.createEvent({
|
||||
title: '2 Overlap',
|
||||
location: 'Somewhere Else'
|
||||
});
|
||||
|
||||
assert.isFalse(
|
||||
eventTitlesDisplayed(),
|
||||
'title should be hidden if we have overlaps'
|
||||
);
|
||||
|
||||
app.createEvent({
|
||||
title: '3 Overlap',
|
||||
location: 'Here'
|
||||
});
|
||||
|
||||
assert.isFalse(
|
||||
eventTitlesDisplayed(),
|
||||
'title should be hidden if we have overlaps'
|
||||
);
|
||||
});
|
||||
|
||||
test('today', function() {
|
||||
var todayDates = week.todayDates;
|
||||
assert.strictEqual(todayDates.length, 1, 'single date marked as today');
|
||||
|
||||
// checking for style is usually a bad idea but since the class name
|
||||
// doesn't guarantee that it's being applied properly we make sure to
|
||||
// test for it as well
|
||||
var today = todayDates[0];
|
||||
assert.strictEqual(
|
||||
today.cssProperty('font-weight'),
|
||||
'500',
|
||||
'font-weight'
|
||||
);
|
||||
assert.strictEqual(
|
||||
today.cssProperty('color'),
|
||||
'rgb(0, 142, 171)',
|
||||
'text color'
|
||||
);
|
||||
});
|
||||
|
||||
test('current-time', function() {
|
||||
week.waitForHourScrollEnd();
|
||||
|
||||
var currentTime = week.currentTime;
|
||||
|
||||
assert.ok(
|
||||
currentTime.displayed(),
|
||||
'current-time should be active'
|
||||
);
|
||||
|
||||
assert.ok(
|
||||
intersect(currentTime, week.currentHour),
|
||||
'current time should be inside current hour range'
|
||||
);
|
||||
|
||||
assert.ok(
|
||||
!week.currentDisplayHour.displayed(),
|
||||
'hour should be hidden if overlapping'
|
||||
);
|
||||
|
||||
function intersect(el1, el2) {
|
||||
var b1 = getBounds(el1);
|
||||
var b2 = getBounds(el2);
|
||||
|
||||
return (
|
||||
b1.left <= b2.right &&
|
||||
b2.left <= b1.right &&
|
||||
b1.top <= b2.bottom &&
|
||||
b2.top <= b1.bottom
|
||||
);
|
||||
}
|
||||
|
||||
function getBounds(element) {
|
||||
return element.scriptWith(function(el) {
|
||||
return el.getBoundingClientRect();
|
||||
});
|
||||
}
|
||||
|
||||
// it should hide the currentTime if current day is not visible
|
||||
app.swipeLeft();
|
||||
client.waitFor(function() {
|
||||
return !currentTime.displayed() && week.currentDisplayHour.displayed();
|
||||
});
|
||||
});
|
||||
|
||||
suite('double tap', function() {
|
||||
var dateIso, time, editEvent;
|
||||
|
||||
setup(function() {
|
||||
editEvent = app.editEvent;
|
||||
|
||||
week.waitForHourScrollEnd();
|
||||
|
||||
// we always click on the day that is in the middle of the screen
|
||||
var day = week.days[7].scriptWith(function(el) {
|
||||
return el.dataset.date;
|
||||
});
|
||||
dateIso = (new Date(day)).toISOString().slice(0, 10);
|
||||
|
||||
// we always click the 3rd hour from the top
|
||||
time = Math.floor((week.scrollTop + 100) / 50);
|
||||
});
|
||||
|
||||
test('fast', function() {
|
||||
week.actions
|
||||
.doubleTap(week.element, 200, 200)
|
||||
.perform();
|
||||
|
||||
assertEditEvent();
|
||||
});
|
||||
|
||||
test('slow + hide', function() {
|
||||
week.actions
|
||||
.tap(week.element, 200, 200)
|
||||
.perform();
|
||||
|
||||
client.waitFor(function() {
|
||||
return week.isShowingAddEventLink();
|
||||
});
|
||||
|
||||
// clicking on another hour should hide the link
|
||||
week.actions
|
||||
.tap(week.element, 200, 260)
|
||||
.perform();
|
||||
|
||||
client.waitFor(function() {
|
||||
return !week.isShowingAddEventLink();
|
||||
});
|
||||
|
||||
week.actions
|
||||
.tap(week.element, 200, 200)
|
||||
.wait(0.5)
|
||||
.tap(week.element, 200, 200)
|
||||
.perform();
|
||||
|
||||
assertEditEvent();
|
||||
});
|
||||
|
||||
function assertEditEvent() {
|
||||
editEvent.waitForDisplay();
|
||||
|
||||
assert.equal(editEvent.startDate, dateIso, 'startDate');
|
||||
assert.equal(editEvent.endDate, dateIso, 'endDate');
|
||||
|
||||
assert.equal(editEvent.startTime, pad(time) + ':00:00', 'startTime');
|
||||
assert.equal(editEvent.endTime, pad(time + 1) + ':00:00', 'endTime');
|
||||
}
|
||||
});
|
||||
|
||||
suite('event retargeting tests', function() {
|
||||
test('When tapping around an event we can create another event',
|
||||
function() {
|
||||
// When displaying the view, for some reason (likely APZC) scrollTop is
|
||||
// incorrect for some time. So we wait for the expected value using
|
||||
// waitForHourScrollEnd().
|
||||
// Also rarely we're not scrolled at first, and so this fails with a
|
||||
// timeout.
|
||||
week.waitForHourScrollEnd();
|
||||
|
||||
var testedHour = 6;
|
||||
// it's really the 3rd visible day as 5 invisible days are in the markup
|
||||
var testedDay = 7;
|
||||
|
||||
week.scrollToHour(testedHour);
|
||||
week.tapDayHour({ day: testedDay, hour: testedHour });
|
||||
week.actions.wait(1).perform();
|
||||
|
||||
assert.ok(
|
||||
week.isShowingAddEventLink(), 'The `add-event` link is displayed'
|
||||
);
|
||||
|
||||
// The "+" button should work with event retargeting.
|
||||
week.tapDayHour({ day: testedDay, hour: testedHour, bottom: -1 });
|
||||
// tapping using "-1" could theorically fail because scrollToHour does not
|
||||
// ensure we have some pixels after the element. Moreover this fails for
|
||||
// sure if testedHour is 23.
|
||||
|
||||
app.editEvent.waitForDisplay();
|
||||
app.editEvent.title = 'Test event';
|
||||
app.editEvent.save();
|
||||
|
||||
week.waitForDisplay();
|
||||
week.tapDayHour({ day: testedDay, hour: testedHour, bottom: -1 });
|
||||
|
||||
assert.ok(
|
||||
week.isShowingAddEventLink(), 'The `add-event` link is displayed'
|
||||
);
|
||||
});
|
||||
});
|
||||
|
||||
suite('12/24 hour format', function() {
|
||||
// Refer to http://bugzil.la/1061135.
|
||||
test.skip('default format: 12 hour', function() {
|
||||
assert.equal(week.sideBarHours[0].text(), '12\nAM');
|
||||
assert.equal(week.sideBarHours[13].text(), '1\nPM');
|
||||
assert.equal(week.sideBarHours[23].text(), '11\nPM');
|
||||
|
||||
var now = new Date();
|
||||
var minutes = now.getMinutes();
|
||||
minutes = minutes < 10 ? (0 + String(minutes)) : minutes;
|
||||
var currentTime = (now.getHours() % 12) + ':' + minutes;
|
||||
assert.equal(week.currentTime.text(), currentTime);
|
||||
});
|
||||
|
||||
test('switch to 24 hour format', function() {
|
||||
app.switch24HourTimeFormat();
|
||||
assert.equal(week.sideBarHours[0].text(), '0');
|
||||
assert.equal(week.sideBarHours[13].text(), '13');
|
||||
assert.equal(week.sideBarHours[23].text(), '23');
|
||||
|
||||
var now = new Date();
|
||||
var currentTime = pad(now.getHours()) + ':' + pad(now.getMinutes());
|
||||
assert.equal(week.currentTime.text(), currentTime);
|
||||
});
|
||||
});
|
||||
|
||||
test('delete', function() {
|
||||
app.createEvent({
|
||||
title: 'Foo',
|
||||
location: 'Bar'
|
||||
});
|
||||
client.waitFor(function() {
|
||||
return app.week.events.length === 1;
|
||||
}, { timeout: 2000 });
|
||||
|
||||
app.openMonthView();
|
||||
app.monthDay.events[0].click();
|
||||
app.readEvent.waitForDisplay();
|
||||
app.readEvent.edit();
|
||||
app.editEvent.waitForDisplay();
|
||||
app.editEvent.delete();
|
||||
app.month.waitForDisplay();
|
||||
app.openWeekView();
|
||||
|
||||
client.waitFor(function() {
|
||||
return app.week.events.length === 0;
|
||||
}, { timeout: 2000 });
|
||||
});
|
||||
|
||||
test('scroll to event', function() {
|
||||
week.waitForHourScrollEnd();
|
||||
week.scrollToTop();
|
||||
|
||||
var startDate = new Date();
|
||||
startDate.setHours(13, 0, 0, 0);
|
||||
|
||||
app.createEvent({
|
||||
title: 'Test Week View',
|
||||
location: 'Somewhere',
|
||||
startDate: startDate
|
||||
});
|
||||
|
||||
week.waitForDisplay();
|
||||
|
||||
// scroll to 1h before event
|
||||
week.waitForHourScrollEnd(12);
|
||||
});
|
||||
|
||||
function pad(n) {
|
||||
return n < 10 ? '0' + n : String(n);
|
||||
}
|
||||
});
|
||||
Loading…
Reference in a new issue