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
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);
|
||||
}
|
||||
};
|
||||
Loading…
Reference in a new issue