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
198
gaia/apps/sms/test/marionette/lib/messages.js
Executable file
198
gaia/apps/sms/test/marionette/lib/messages.js
Executable file
|
|
@ -0,0 +1,198 @@
|
|||
'use strict';
|
||||
|
||||
/* global module */
|
||||
var InboxAccessor = require('./views/inbox/accessors');
|
||||
var NewMessageAccessor = require('./views/new-message/accessors');
|
||||
var NewMessageView = require('./views/new-message/view');
|
||||
var ConversationAccessor = require('./views/conversation/accessors');
|
||||
var MenuAccessor = require('./views/shared/menu_accessors');
|
||||
var ComposerAccessor = require('./views/shared/composer_accessors');
|
||||
var ReportAccessor = require('./views/report/accessors');
|
||||
var ParticipantsAccessor = require('./views/participants/accessors');
|
||||
|
||||
(function(module) {
|
||||
|
||||
var ORIGIN_URL = 'app://sms.gaiamobile.org';
|
||||
var MANIFEST_URL = ORIGIN_URL + '/manifest.webapp';
|
||||
|
||||
// TODO Move these constants to marionette, see bug 1207516
|
||||
var Chars = {
|
||||
ENTER: '\ue007',
|
||||
BACKSPACE: '\ue003'
|
||||
};
|
||||
|
||||
var SELECTORS = Object.freeze({
|
||||
main: '#main-wrapper',
|
||||
appReady: 'body.js-app-ready',
|
||||
|
||||
Message: {
|
||||
content: '.message-content > p:first-child',
|
||||
vcardAttachment: '[data-attachment-type="vcard"]',
|
||||
fileName: '.file-name'
|
||||
}
|
||||
});
|
||||
|
||||
module.exports = {
|
||||
create: function(client) {
|
||||
var actions = client.loader.getActions();
|
||||
var newMessageView = new NewMessageView(client);
|
||||
|
||||
return {
|
||||
Selectors: SELECTORS,
|
||||
|
||||
manifestURL: MANIFEST_URL,
|
||||
|
||||
Composer: new ComposerAccessor(client),
|
||||
|
||||
NewMessage: new NewMessageAccessor(client),
|
||||
|
||||
Conversation: new ConversationAccessor(client),
|
||||
|
||||
Inbox: new InboxAccessor(client),
|
||||
|
||||
Menu: new MenuAccessor(client),
|
||||
|
||||
Report: new ReportAccessor(client),
|
||||
|
||||
Participants: new ParticipantsAccessor(client),
|
||||
|
||||
launch(doNotWaitForAppToBecomeReady) {
|
||||
client.switchToFrame();
|
||||
client.apps.launch(ORIGIN_URL);
|
||||
client.apps.switchToApp(ORIGIN_URL);
|
||||
|
||||
// In 99.99% cases we'd like to wait for the app to become fully ready
|
||||
// before we do anything in the tests, but sometimes we'd like to
|
||||
// verify that app is actionable before it's marked as ready, in this
|
||||
// case we just wait for the main app container to appear.
|
||||
if (!doNotWaitForAppToBecomeReady) {
|
||||
client.helper.waitForElement(SELECTORS.appReady);
|
||||
} else {
|
||||
client.helper.waitForElement(SELECTORS.main);
|
||||
}
|
||||
},
|
||||
|
||||
close: function() {
|
||||
client.apps.close(ORIGIN_URL);
|
||||
},
|
||||
|
||||
loadMocks: function() {
|
||||
client.loader.getMockManager('sms').inject([
|
||||
'test_storages',
|
||||
'test_blobs',
|
||||
'navigator_moz_icc_manager',
|
||||
'navigator_moz_mobile_message',
|
||||
'navigator_moz_contacts'
|
||||
]);
|
||||
},
|
||||
|
||||
/**
|
||||
* Sends system message to the Messages app using SystemMessageInternal
|
||||
* class available in chrome context. Should be replaced by marionette
|
||||
* apps built-in method or shared lib (see bug 1162165).
|
||||
* @param {string} name Name of the system message to send.
|
||||
* @param {Object} parameters Parameters object to pass with system
|
||||
* message.
|
||||
*/
|
||||
sendSystemMessage: function(name, parameters) {
|
||||
var chromeClient = client.scope({ context: 'chrome' });
|
||||
chromeClient.executeScript(function(manifestURL, name, parameters) {
|
||||
/* global Components, Services */
|
||||
var managerClass = Components.classes[
|
||||
'@mozilla.org/system-message-internal;1'
|
||||
];
|
||||
|
||||
var systemMessageManager = managerClass.getService(
|
||||
Components.interfaces.nsISystemMessagesInternal
|
||||
);
|
||||
|
||||
systemMessageManager.sendMessage(
|
||||
name,
|
||||
parameters,
|
||||
null, /* pageURI */
|
||||
Services.io.newURI(manifestURL, null, null)
|
||||
);
|
||||
}, [MANIFEST_URL, name, parameters]);
|
||||
},
|
||||
|
||||
switchTo: function() {
|
||||
client.switchToFrame();
|
||||
|
||||
client.apps.switchToApp(ORIGIN_URL);
|
||||
client.helper.waitForElement(SELECTORS.appReady);
|
||||
},
|
||||
|
||||
waitForAppToAppear: function() {
|
||||
client.switchToFrame();
|
||||
var frame = client.scope({ searchTimeout: 50 }).helper.waitForElement(
|
||||
'iframe[src*="' + ORIGIN_URL + '"]'
|
||||
);
|
||||
return frame;
|
||||
},
|
||||
|
||||
waitForAppToDisappear: function() {
|
||||
client.switchToFrame();
|
||||
client.scope({ searchTimeout: 50 }).helper.waitForElementToDisappear(
|
||||
'iframe[src*="' + ORIGIN_URL + '"]'
|
||||
);
|
||||
},
|
||||
|
||||
addRecipient: function(recipient) {
|
||||
newMessageView.addNewRecipient(recipient);
|
||||
},
|
||||
|
||||
getActivePanelName: function() {
|
||||
var cls = client.findElement('.panel-active').getAttribute('class');
|
||||
var matchResult = /\bpanel-([A-Z]\w+)\b/g.exec(cls);
|
||||
return matchResult && matchResult[1];
|
||||
},
|
||||
|
||||
getRecipient: function(number) {
|
||||
return client.helper.waitForElement(
|
||||
'#messages-recipients-list .recipient[data-number="' + number + '"]'
|
||||
);
|
||||
},
|
||||
|
||||
clearRecipient: function() {
|
||||
newMessageView.clearRecipients();
|
||||
},
|
||||
|
||||
send: function() {
|
||||
newMessageView.send();
|
||||
},
|
||||
|
||||
showSubject: function() {
|
||||
newMessageView.showSubject();
|
||||
},
|
||||
|
||||
hideSubject: function() {
|
||||
newMessageView.hideSubject();
|
||||
},
|
||||
|
||||
contextMenu: function(element) {
|
||||
actions.longPress(element, 1).perform();
|
||||
},
|
||||
|
||||
performHeaderAction: function() {
|
||||
client.switchToShadowRoot(this.NewMessage.header);
|
||||
this.NewMessage.headerActionButton.tap();
|
||||
client.switchToShadowRoot();
|
||||
},
|
||||
|
||||
performReportHeaderAction: function() {
|
||||
client.switchToShadowRoot(this.Report.header);
|
||||
this.Report.headerActionButton.tap();
|
||||
client.switchToShadowRoot();
|
||||
},
|
||||
|
||||
performGroupHeaderAction: function() {
|
||||
client.switchToShadowRoot(this.Participants.header);
|
||||
this.Participants.headerActionButton.tap();
|
||||
client.switchToShadowRoot();
|
||||
}
|
||||
};
|
||||
},
|
||||
|
||||
Chars: Chars
|
||||
};
|
||||
})(module);
|
||||
87
gaia/apps/sms/test/marionette/lib/messages_activity_caller.js
Executable file
87
gaia/apps/sms/test/marionette/lib/messages_activity_caller.js
Executable file
|
|
@ -0,0 +1,87 @@
|
|||
/* global module */
|
||||
(function(module) {
|
||||
'use strict';
|
||||
|
||||
var SELECTORS = Object.freeze({
|
||||
shareMenu: 'form[data-z-index-level="action-menu"]',
|
||||
shareImageButton: '#share-image',
|
||||
pickImageButton: '#pick-image',
|
||||
pickContactButton: '#pick-contact',
|
||||
sendMessageButton: '#send-message'
|
||||
});
|
||||
|
||||
var ORIGIN = 'messages.activity.gaiamobile.org';
|
||||
|
||||
module.exports = {
|
||||
ORIGIN: ORIGIN,
|
||||
|
||||
create: function(client) {
|
||||
var originURL = 'app://' + ORIGIN;
|
||||
|
||||
return {
|
||||
get shareMenu() {
|
||||
// Switch to the system app first.
|
||||
client.switchToFrame();
|
||||
return client.helper.waitForElement(SELECTORS.shareMenu);
|
||||
},
|
||||
|
||||
get shareWithMessagesButton() {
|
||||
var shareWithOptions = this.shareMenu.findElements('button');
|
||||
for (var i = 0; i < shareWithOptions.length; i++) {
|
||||
var shareWithOption = shareWithOptions[i];
|
||||
if (shareWithOption.text() === 'Messages') {
|
||||
return shareWithOption;
|
||||
}
|
||||
}
|
||||
},
|
||||
|
||||
launch: function() {
|
||||
client.switchToFrame();
|
||||
client.apps.launch(originURL);
|
||||
client.apps.switchToApp(originURL);
|
||||
},
|
||||
|
||||
switchTo: function() {
|
||||
client.switchToFrame();
|
||||
|
||||
client.apps.switchToApp(originURL);
|
||||
},
|
||||
|
||||
shareImage: function() {
|
||||
client.helper.waitForElement(SELECTORS.shareImageButton).tap();
|
||||
this.shareWithMessagesButton.tap();
|
||||
},
|
||||
|
||||
pickImage: function() {
|
||||
client.helper.waitForElement(SELECTORS.pickImageButton).tap();
|
||||
},
|
||||
|
||||
pickContact: function() {
|
||||
client.helper.waitForElement(SELECTORS.pickContactButton).tap();
|
||||
},
|
||||
|
||||
/**
|
||||
* Forces app to issue "websms/sms" new activity request with phone
|
||||
* number and body.
|
||||
* @param {{number: Number, body: string }} parameters Object that
|
||||
* contains parameters to pass to "websms/sms" new activity.
|
||||
*/
|
||||
sendMessage: function(parameters) {
|
||||
var button = client.helper.waitForElement(
|
||||
SELECTORS.sendMessageButton
|
||||
);
|
||||
|
||||
// We set button dataset attributes, that will be used by test
|
||||
// activity app as "websms/sms" activity parameters.
|
||||
button.scriptWith(function(element, parameters) {
|
||||
Object.keys(parameters).forEach(function(parameterKey) {
|
||||
element.dataset[parameterKey] = parameters[parameterKey];
|
||||
});
|
||||
}, [parameters]);
|
||||
|
||||
button.tap();
|
||||
}
|
||||
};
|
||||
}
|
||||
};
|
||||
})(module);
|
||||
56
gaia/apps/sms/test/marionette/lib/storage.js
Executable file
56
gaia/apps/sms/test/marionette/lib/storage.js
Executable file
|
|
@ -0,0 +1,56 @@
|
|||
/* global Components, module, Services */
|
||||
(function(module) {
|
||||
'use strict';
|
||||
|
||||
module.exports = {
|
||||
create: function(client) {
|
||||
return {
|
||||
/**
|
||||
* Sets pre-populated thread/message storage.
|
||||
* @param {Array.<Thread>?} threads List of the thread to pre-populate
|
||||
* storage with.
|
||||
* @param {number?} uniqueMessageIdCounter Start value for the unique
|
||||
* message id counter, it's used to avoid message "id" collision between
|
||||
* message ids from predefined store and message ids that can be
|
||||
* generated during test (e.g. send or receive new message).
|
||||
*/
|
||||
setMessagesStorage: function(threads, uniqueMessageIdCounter) {
|
||||
this.setStorage('messagesDB', {
|
||||
threads: threads || [],
|
||||
uniqueMessageIdCounter: uniqueMessageIdCounter || 0
|
||||
});
|
||||
},
|
||||
|
||||
/**
|
||||
* Sets pre-populated contacts storage.
|
||||
* @param {Array.<MozContact>?} contacts List of the contacts to
|
||||
* pre-populate storage with.
|
||||
*/
|
||||
setContactsStorage: function(contacts) {
|
||||
this.setStorage('contactsDB', {
|
||||
contacts: contacts || []
|
||||
});
|
||||
},
|
||||
|
||||
/**
|
||||
* Sets pre-populated custom storage.
|
||||
* @param {string} storageName Name of the custom storage.
|
||||
* @param {*} storage Custom storage data.
|
||||
* @private
|
||||
*/
|
||||
setStorage: function(storageName, storage) {
|
||||
client.scope({ context: 'chrome' }).executeScript(
|
||||
function(storage, storageName) {
|
||||
Services.obs.addObserver(function(window) {
|
||||
window.wrappedJSObject.TestStorages.set(
|
||||
storageName, Components.utils.cloneInto(storage, window)
|
||||
);
|
||||
}, 'test-storage-ready', false);
|
||||
},
|
||||
[storage, storageName]
|
||||
);
|
||||
}
|
||||
};
|
||||
}
|
||||
};
|
||||
})(module);
|
||||
32
gaia/apps/sms/test/marionette/lib/views/activity-chooser/view.js
Executable file
32
gaia/apps/sms/test/marionette/lib/views/activity-chooser/view.js
Executable file
|
|
@ -0,0 +1,32 @@
|
|||
'use strict';
|
||||
|
||||
/* global module */
|
||||
|
||||
var MenuAccessor = require('../shared/menu_accessors');
|
||||
|
||||
function ActivityChooserView(client) {
|
||||
this.client = client;
|
||||
this.menuAccessors = new MenuAccessor(client);
|
||||
}
|
||||
|
||||
ActivityChooserView.prototype = {
|
||||
choose: function(appName) {
|
||||
this.menuAccessors.selectSystemMenuOption(appName);
|
||||
|
||||
var activityApp = this.createAppViewByName(appName);
|
||||
activityApp.switchTo();
|
||||
|
||||
return activityApp;
|
||||
},
|
||||
|
||||
createAppViewByName: function(appName) {
|
||||
switch(appName) {
|
||||
case 'Messages Activity Caller':
|
||||
return require('../../messages_activity_caller.js').create(this.client);
|
||||
default:
|
||||
throw new Error('Unsupported app name: ' + appName);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
module.exports = ActivityChooserView;
|
||||
132
gaia/apps/sms/test/marionette/lib/views/conversation/accessors.js
Executable file
132
gaia/apps/sms/test/marionette/lib/views/conversation/accessors.js
Executable file
|
|
@ -0,0 +1,132 @@
|
|||
'use strict';
|
||||
|
||||
/* global module */
|
||||
|
||||
var SELECTORS = Object.freeze({
|
||||
main: '.panel-ConversationView',
|
||||
message: '.message',
|
||||
messageBubble: '.message .bubble',
|
||||
header: '#messages-header',
|
||||
headerTitle: '#messages-header-text',
|
||||
headerActionButton: '.action-button',
|
||||
container: '#messages-container',
|
||||
callButton: '#messages-call-number-button',
|
||||
// TODO: Remove the next selector once Message has its own accessors
|
||||
messageContent: '.message-content > p:first-child',
|
||||
createNewContactMenuOption: '.contact-prompt ' +
|
||||
'[data-l10n-id="createNewContact"]',
|
||||
addToExistingContactOption: '.contact-prompt ' +
|
||||
'[data-l10n-id="addToExistingContact"]',
|
||||
carrierHeaderPhoneNumber: '#contact-carrier .phone-number',
|
||||
optionsButton: '#messages-options-button'
|
||||
});
|
||||
|
||||
var EDIT_MODE_SELECTORS = Object.freeze({
|
||||
toggleSelectionButton: '#messages-check-uncheck-all-button',
|
||||
editHeaderTitle: '#messages-edit-mode'
|
||||
});
|
||||
|
||||
function ConversationAccessor(client) {
|
||||
this.client = client;
|
||||
this.actions = client.loader.getActions();
|
||||
}
|
||||
|
||||
ConversationAccessor.prototype = {
|
||||
get messages() {
|
||||
return this.client.findElements(SELECTORS.message);
|
||||
},
|
||||
|
||||
get message() {
|
||||
return this.client.helper.waitForElement(SELECTORS.messageBubble);
|
||||
},
|
||||
|
||||
get header() {
|
||||
return this.client.helper.waitForElement(SELECTORS.header);
|
||||
},
|
||||
|
||||
get headerTitle() {
|
||||
return this.client.helper.waitForElement(SELECTORS.headerTitle);
|
||||
},
|
||||
|
||||
get editHeaderTitle() {
|
||||
return this.client.helper.waitForElement(
|
||||
EDIT_MODE_SELECTORS.editHeaderTitle
|
||||
);
|
||||
},
|
||||
|
||||
get headerActionButton() {
|
||||
return this.client.helper.waitForElement(SELECTORS.headerActionButton);
|
||||
},
|
||||
|
||||
get carrierHeaderPhoneNumber() {
|
||||
return this.client.helper.waitForElement(
|
||||
SELECTORS.carrierHeaderPhoneNumber
|
||||
);
|
||||
},
|
||||
|
||||
get callButton() {
|
||||
return this.client.helper.waitForElement(SELECTORS.callButton);
|
||||
},
|
||||
|
||||
get optionsButton() {
|
||||
return this.client.helper.waitForElement(SELECTORS.optionsButton);
|
||||
},
|
||||
|
||||
get toggleSelectionButton() {
|
||||
return this.client.helper.waitForElement(
|
||||
EDIT_MODE_SELECTORS.toggleSelectionButton
|
||||
);
|
||||
},
|
||||
|
||||
get createNewContactOption() {
|
||||
return this.client.helper.waitForElement(
|
||||
SELECTORS.createNewContactMenuOption
|
||||
);
|
||||
},
|
||||
|
||||
get addToExistingContactOption() {
|
||||
return this.client.helper.waitForElement(
|
||||
SELECTORS.addToExistingContactOption
|
||||
);
|
||||
},
|
||||
|
||||
getMessageContent: function(message) {
|
||||
return this.client.helper.waitForElement(
|
||||
message.findElement(SELECTORS.messageContent)
|
||||
);
|
||||
},
|
||||
|
||||
findMessage: function(id) {
|
||||
return this.client.findElement(
|
||||
'.message[data-message-id="' + id + '"] .bubble'
|
||||
);
|
||||
},
|
||||
|
||||
scrollUp: function() {
|
||||
var conversationContainer = this.client.findElement(
|
||||
SELECTORS.container
|
||||
);
|
||||
|
||||
this.actions.flick(conversationContainer, 50, 50, 50, 350).perform();
|
||||
},
|
||||
|
||||
fakeScrollUpTo: function(scrollTop) {
|
||||
var conversationContainer = this.client.findElement(
|
||||
SELECTORS.container
|
||||
);
|
||||
|
||||
conversationContainer.scriptWith(function(container, scrollTop) {
|
||||
container.scrollTop = scrollTop;
|
||||
}, [scrollTop]);
|
||||
},
|
||||
|
||||
waitToAppear: function() {
|
||||
var conversationPanel = this.client.helper.waitForElement(SELECTORS.main);
|
||||
this.client.waitFor(function() {
|
||||
return conversationPanel.location().x === 0;
|
||||
});
|
||||
return conversationPanel;
|
||||
}
|
||||
};
|
||||
|
||||
module.exports = ConversationAccessor;
|
||||
72
gaia/apps/sms/test/marionette/lib/views/conversation/message_accessors.js
Executable file
72
gaia/apps/sms/test/marionette/lib/views/conversation/message_accessors.js
Executable file
|
|
@ -0,0 +1,72 @@
|
|||
'use strict';
|
||||
|
||||
/* global module */
|
||||
|
||||
var SELECTORS = Object.freeze({
|
||||
main: '.message[data-message-id="{id}"]',
|
||||
bubble: '.bubble',
|
||||
content: '.message-content-body',
|
||||
attachment: '.attachment-container',
|
||||
downloadButton: 'button.download'
|
||||
});
|
||||
|
||||
function MessageAccessor(client) {
|
||||
this.client = client.scope({ searchTimeout: 50 });
|
||||
this.actions = client.loader.getActions();
|
||||
}
|
||||
|
||||
MessageAccessor.prototype = {
|
||||
find: function(messageId) {
|
||||
return this.client.findElement(SELECTORS.main.replace('{id}', messageId));
|
||||
},
|
||||
|
||||
parse: function(messageNode) {
|
||||
// It's workaround for the case when message node reference is stale,
|
||||
// so when we try to access MarionetteElement from the node that's not
|
||||
// attached to the dom we'll get an exception.
|
||||
try {
|
||||
var messageClass = messageNode.getAttribute('class');
|
||||
var checkbox = messageNode.findElement('input[type=checkbox]');
|
||||
// Since most of the messages don't contain any attachments, let's make
|
||||
// it much faster.
|
||||
var attachments = this.client.scope({ searchTimeout: 50 }).findElements(
|
||||
SELECTORS.attachment, null /* default search method */, messageNode.id
|
||||
);
|
||||
|
||||
return {
|
||||
id: +messageNode.getAttribute('data-message-id'),
|
||||
type: messageClass.indexOf('mms') >= 0 ? 'mms' : 'sms',
|
||||
content: messageNode.findElement(SELECTORS.content).text(),
|
||||
attachments: attachments.map(function(attachment) {
|
||||
return {
|
||||
screenshot: function() {
|
||||
return this.client.screenshot({ element: attachment });
|
||||
}.bind(this),
|
||||
type: attachment.getAttribute('data-attachment-type')
|
||||
};
|
||||
}, this),
|
||||
isDownloaded: messageClass.indexOf('not-downloaded') < 0,
|
||||
isPending: messageClass.indexOf('pending') >= 0,
|
||||
isInEditMode: messageNode.findElement('.pack-checkbox').displayed(),
|
||||
isSelected: checkbox.getAttribute('checked') === 'true',
|
||||
isDisplayed: messageNode.displayed()
|
||||
};
|
||||
} catch(e) {
|
||||
console.warn('Message node is not available', e, e.stack);
|
||||
|
||||
return null;
|
||||
}
|
||||
},
|
||||
|
||||
download: function(messageId) {
|
||||
var messageNode = this.find(messageId);
|
||||
|
||||
if (this.parse(messageNode).isDownloaded) {
|
||||
throw new Error('Message has been already downloaded!');
|
||||
}
|
||||
|
||||
messageNode.findElement(SELECTORS.downloadButton).tap();
|
||||
}
|
||||
};
|
||||
|
||||
module.exports = MessageAccessor;
|
||||
287
gaia/apps/sms/test/marionette/lib/views/conversation/view.js
Executable file
287
gaia/apps/sms/test/marionette/lib/views/conversation/view.js
Executable file
|
|
@ -0,0 +1,287 @@
|
|||
'use strict';
|
||||
|
||||
/* global module */
|
||||
|
||||
var ConversationAccessor = require('./accessors');
|
||||
var MessageAccessor = require('./message_accessors');
|
||||
var ComposerAccessor = require('../shared/composer_accessors');
|
||||
var MenuAccessor = require('../shared/menu_accessors');
|
||||
var Tools = require('../shared/tools.js');
|
||||
|
||||
var appRoot = require('app-root-path');
|
||||
// TODO Change the path once requireFromApp becomes its own module
|
||||
var fromApp = require(appRoot + '/shared/test/integration/require_from_app');
|
||||
var Contacts = fromApp('contacts').require('lib/contacts');
|
||||
|
||||
function ConversationView(client) {
|
||||
this.client = client;
|
||||
this.accessors = new ConversationAccessor(client);
|
||||
this.messageAccessors = new MessageAccessor(client);
|
||||
this.composerAccessors = new ComposerAccessor(client);
|
||||
this.menuAccessors = new MenuAccessor(client);
|
||||
}
|
||||
|
||||
ConversationView.prototype = {
|
||||
// TODO Move these constants to marionette, see bug 1207516
|
||||
KEYS: {
|
||||
backspace: '\ue003'
|
||||
},
|
||||
|
||||
get headerTitle() {
|
||||
return this.accessors.headerTitle.text();
|
||||
},
|
||||
|
||||
get editHeaderTitle() {
|
||||
return this.accessors.editHeaderTitle.text();
|
||||
},
|
||||
|
||||
get toggleSelectionButtonTitle() {
|
||||
return this.accessors.toggleSelectionButton.text();
|
||||
},
|
||||
|
||||
/**
|
||||
* Returns the messages in this conversation, following an optional filter.
|
||||
* @param {Object} [filter] Optional filter
|
||||
* @param {Number} [filter.first] Will return the first n messages.
|
||||
* @returns {ParsedMessage[]} List of the messages found.
|
||||
*/
|
||||
messages: function(filter) {
|
||||
filter = filter || [];
|
||||
var messages = this.accessors.messages;
|
||||
|
||||
if (filter.first) {
|
||||
messages = messages.slice(0, filter.first);
|
||||
}
|
||||
|
||||
return messages.map(function(message) {
|
||||
return this.messageAccessors.parse(message);
|
||||
}, this);
|
||||
},
|
||||
|
||||
get carrierHeaderPhoneNumber() {
|
||||
return this.accessors.carrierHeaderPhoneNumber.text();
|
||||
},
|
||||
|
||||
findMessage: function(messageId) {
|
||||
var messageNode = this.messageAccessors.find(messageId);
|
||||
return messageNode && this.messageAccessors.parse(messageNode);
|
||||
},
|
||||
|
||||
waitForFullRendering: function(thread) {
|
||||
var lastId = thread.messages[thread.messages.length - 1].id;
|
||||
this.accessors.findMessage(lastId);
|
||||
},
|
||||
|
||||
downloadMessage: function(messageId) {
|
||||
this.messageAccessors.download(messageId);
|
||||
},
|
||||
|
||||
get attachments() {
|
||||
return this.composerAccessors.attachments;
|
||||
},
|
||||
|
||||
get messageText() {
|
||||
return this.composerAccessors.messageInput.text().trim();
|
||||
},
|
||||
|
||||
get subject() {
|
||||
return this.composerAccessors.subjectInput.text().trim();
|
||||
},
|
||||
|
||||
back: function() {
|
||||
this.client.switchToShadowRoot(this.accessors.header);
|
||||
this.accessors.headerActionButton.tap();
|
||||
this.client.switchToShadowRoot();
|
||||
},
|
||||
|
||||
callContact: function() {
|
||||
this.accessors.callButton.tap();
|
||||
|
||||
var Dialer = fromApp('dialer').require('lib/dialer');
|
||||
var dialer = new Dialer(this.client);
|
||||
dialer.switchTo();
|
||||
return dialer;
|
||||
},
|
||||
|
||||
openCreateNewContact: function() {
|
||||
this.accessors.headerTitle.tap();
|
||||
this.accessors.createNewContactOption.tap();
|
||||
var contacts = new Contacts(this.client);
|
||||
contacts.switchToCreateNewContactActivity();
|
||||
return contacts;
|
||||
},
|
||||
|
||||
openAddToExistingContact: function() {
|
||||
this.accessors.headerTitle.tap();
|
||||
this.accessors.addToExistingContactOption.tap();
|
||||
return this._switchToContactsApp();
|
||||
},
|
||||
|
||||
openParticipants: function() {
|
||||
this.accessors.headerTitle.tap();
|
||||
|
||||
var ParticipantsView = require('../participants/view');
|
||||
var participantsView = new ParticipantsView(this.client);
|
||||
participantsView.accessors.waitToAppear();
|
||||
return participantsView;
|
||||
},
|
||||
|
||||
openReport: function(messageId) {
|
||||
var message = this.accessors.findMessage(messageId);
|
||||
|
||||
// Show context menu on message bubble.
|
||||
this.client.loader.getActions().longPress(message, 1 /* sec */).perform();
|
||||
this.menuAccessors.selectAppMenuOption('View message report');
|
||||
|
||||
var ReportView = require('../report/view');
|
||||
var reportView = new ReportView(this.client, this);
|
||||
reportView.accessors.waitToAppear();
|
||||
return reportView;
|
||||
},
|
||||
|
||||
deleteMessage: function(messageId) {
|
||||
var lastMessageWillBeDeleted = this.messages().length === 1;
|
||||
|
||||
var message = this.accessors.findMessage(messageId);
|
||||
|
||||
// Show context menu on message bubble.
|
||||
this.client.loader.getActions().longPress(message, 1 /* sec */).perform();
|
||||
this.menuAccessors.selectAppMenuOption('Delete');
|
||||
|
||||
var dialogView = new (require('../dialog/view'))(this.client);
|
||||
dialogView.chooseAction('Delete');
|
||||
|
||||
// If deleted message was the last one, user will be returned back to Inbox.
|
||||
var currentView;
|
||||
if (lastMessageWillBeDeleted) {
|
||||
currentView = new (require('../inbox/view'))(this.client);
|
||||
currentView.accessors.waitToAppear();
|
||||
} else {
|
||||
currentView = this;
|
||||
}
|
||||
|
||||
return currentView;
|
||||
},
|
||||
|
||||
clearMessage: function() {
|
||||
var messageInput = this.composerAccessors.messageInput;
|
||||
messageInput.tap();
|
||||
while (messageInput.text() !== '') {
|
||||
messageInput.sendKeys(this.KEYS.backspace);
|
||||
}
|
||||
},
|
||||
|
||||
typeMessage: function(message) {
|
||||
this.composerAccessors.messageInput.sendKeys(message);
|
||||
},
|
||||
|
||||
addAttachment: function() {
|
||||
return this.composerAccessors.addAttachment();
|
||||
},
|
||||
|
||||
/**
|
||||
* Take a screenshot of an attachment thumbnail in the composer.
|
||||
* @param {Number} The attachment index
|
||||
* @return {String} The screenshot
|
||||
*/
|
||||
takeComposerAttachmentScreenshot: function(index) {
|
||||
return this.composerAccessors.takeAttachmentScreenshot(index);
|
||||
},
|
||||
|
||||
typeSubject: function(subject) {
|
||||
this.composerAccessors.subjectInput.sendKeys(subject);
|
||||
},
|
||||
|
||||
showOptions: function() {
|
||||
this.accessors.optionsButton.tap();
|
||||
},
|
||||
|
||||
showSubject: function() {
|
||||
this.showOptions();
|
||||
this.menuAccessors.selectAppMenuOption('Add subject');
|
||||
this.client.helper.waitForElement(this.composerAccessors.subjectInput);
|
||||
},
|
||||
|
||||
/**
|
||||
* Launch the "include someone else" action.
|
||||
* @returns {NewMessageView} the new message view we navigated to.
|
||||
*/
|
||||
includeSomeoneElse: function() {
|
||||
this.showOptions();
|
||||
this.menuAccessors.selectAppMenuOption('Include someone else');
|
||||
|
||||
var NewMessageView = require('../new-message/view');
|
||||
var newMessageView = new NewMessageView(this.client);
|
||||
newMessageView.accessors.waitToAppear();
|
||||
return newMessageView;
|
||||
},
|
||||
|
||||
hideSubject: function() {
|
||||
this.showOptions();
|
||||
this.menuAccessors.selectAppMenuOption('Remove subject');
|
||||
this.client.helper.waitForElementToDisappear(
|
||||
this.composerAccessors.subjectInput
|
||||
);
|
||||
},
|
||||
|
||||
enterEditMode: function() {
|
||||
this.showOptions();
|
||||
this.menuAccessors.selectAppMenuOption('Select Messages');
|
||||
},
|
||||
|
||||
exitEditMode: function() {
|
||||
this.back();
|
||||
},
|
||||
|
||||
tapOnMessage: function(messageId) {
|
||||
var messageNode = this.messageAccessors.find(messageId);
|
||||
messageNode.scriptWith(function(node) {
|
||||
node.scrollIntoView();
|
||||
});
|
||||
messageNode.tap();
|
||||
},
|
||||
|
||||
toggleMessagesSelection: function() {
|
||||
var header = this.accessors.header;
|
||||
// This is a workaround needed as the screen is moving app is
|
||||
// moving outside the screen on running tapOnMessage().
|
||||
// This is needed to get the header back in the frame
|
||||
header.scriptWith(function(node) {
|
||||
node.scrollIntoView();
|
||||
});
|
||||
this.accessors.toggleSelectionButton.tap();
|
||||
},
|
||||
|
||||
isSubjectVisible: function() {
|
||||
var subjectInput = this.composerAccessors.subjectInput;
|
||||
return subjectInput && this.composerAccessors.subjectInput.displayed();
|
||||
},
|
||||
|
||||
fakeScrollUpTo: function(scrollTop) {
|
||||
this.accessors.fakeScrollUpTo(scrollTop);
|
||||
},
|
||||
|
||||
assertMessageInputFocused: function(message) {
|
||||
Tools.assertElementFocused(this.composerAccessors.messageInput, message);
|
||||
},
|
||||
|
||||
backToInbox: function() {
|
||||
this.back();
|
||||
|
||||
var InboxView = require('../inbox/view');
|
||||
var inboxView = new InboxView(this.client);
|
||||
inboxView.accessors.waitToAppear();
|
||||
|
||||
return inboxView;
|
||||
},
|
||||
|
||||
_switchToContactsApp: function() {
|
||||
var contacts = new Contacts(this.client);
|
||||
contacts.switchToApp();
|
||||
return contacts;
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
|
||||
module.exports = ConversationView;
|
||||
44
gaia/apps/sms/test/marionette/lib/views/dialog/accessors.js
Executable file
44
gaia/apps/sms/test/marionette/lib/views/dialog/accessors.js
Executable file
|
|
@ -0,0 +1,44 @@
|
|||
'use strict';
|
||||
|
||||
/* global module */
|
||||
|
||||
var SELECTORS = Object.freeze({
|
||||
container: 'form[data-type=confirm][data-subtype=menu][role=dialog]',
|
||||
// The following rules should be scoped by the container.
|
||||
header: 'form h1',
|
||||
body: 'form p',
|
||||
button: 'form button'
|
||||
});
|
||||
|
||||
function DialogAccessor(client) {
|
||||
this.client = client;
|
||||
}
|
||||
|
||||
DialogAccessor.prototype = {
|
||||
get container() {
|
||||
return this.client.helper.waitForElement(SELECTORS.container);
|
||||
},
|
||||
|
||||
get buttons() {
|
||||
return this.container.findElements(SELECTORS.button);
|
||||
},
|
||||
|
||||
get header() {
|
||||
return this.container.findElement(SELECTORS.header);
|
||||
},
|
||||
|
||||
get body() {
|
||||
return this.container.findElement(SELECTORS.body);
|
||||
},
|
||||
|
||||
waitToAppear: function() {
|
||||
this.client.waitFor(function() {
|
||||
var body = this.client.findElement('body');
|
||||
|
||||
return this.container.getAttribute('class').indexOf('visible') >= 0 &&
|
||||
body.getAttribute('class').indexOf('dialog-animating') < 0;
|
||||
}.bind(this));
|
||||
}
|
||||
};
|
||||
|
||||
module.exports = DialogAccessor;
|
||||
35
gaia/apps/sms/test/marionette/lib/views/dialog/view.js
Executable file
35
gaia/apps/sms/test/marionette/lib/views/dialog/view.js
Executable file
|
|
@ -0,0 +1,35 @@
|
|||
'use strict';
|
||||
|
||||
/* global module */
|
||||
|
||||
var DialogAccessor = require('./accessors');
|
||||
|
||||
function DialogView(client) {
|
||||
this.client = client;
|
||||
this.accessors = new DialogAccessor(client);
|
||||
|
||||
this.accessors.waitToAppear();
|
||||
}
|
||||
|
||||
DialogView.prototype = {
|
||||
get header() {
|
||||
return this.accessors.header.text();
|
||||
},
|
||||
|
||||
get body() {
|
||||
return this.accessors.body.text();
|
||||
},
|
||||
|
||||
chooseAction: function(actionName) {
|
||||
var actionButtons = this.accessors.buttons;
|
||||
for (var i = 0; i < actionButtons.length; i++) {
|
||||
var actionButton = actionButtons[i];
|
||||
if (actionButton.text().toLowerCase() === actionName.toLowerCase()) {
|
||||
actionButton.tap();
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
module.exports = DialogView;
|
||||
74
gaia/apps/sms/test/marionette/lib/views/inbox/accessors.js
Executable file
74
gaia/apps/sms/test/marionette/lib/views/inbox/accessors.js
Executable file
|
|
@ -0,0 +1,74 @@
|
|||
'use strict';
|
||||
|
||||
/* global module */
|
||||
|
||||
var SELECTORS = Object.freeze({
|
||||
main: '.panel-InboxView',
|
||||
conversation: '.threadlist-item',
|
||||
smsConversation: '.threadlist-item[data-last-message-type="sms"]',
|
||||
mmsConversation: '.threadlist-item[data-last-message-type="mms"]',
|
||||
conversationTitle: '.threadlist-item-title',
|
||||
navigateToComposerHeaderButton: '#threads-composer-link'
|
||||
});
|
||||
|
||||
function InboxAccessor(client) {
|
||||
this.client = client;
|
||||
}
|
||||
|
||||
InboxAccessor.prototype = {
|
||||
get firstConversation() {
|
||||
return this.client.helper.waitForElement(SELECTORS.conversation);
|
||||
},
|
||||
|
||||
get smsConversation() {
|
||||
return this.client.helper.waitForElement(SELECTORS.smsConversation);
|
||||
},
|
||||
|
||||
get mmsConversation() {
|
||||
return this.client.helper.waitForElement(SELECTORS.mmsConversation);
|
||||
},
|
||||
|
||||
get conversations() {
|
||||
return this.client.findElements(SELECTORS.conversation);
|
||||
},
|
||||
|
||||
get createNewMessageButton() {
|
||||
return this.client.helper.waitForElement(
|
||||
SELECTORS.navigateToComposerHeaderButton
|
||||
);
|
||||
},
|
||||
|
||||
findConversation: function(id) {
|
||||
return this.client.findElement('#thread-' + id);
|
||||
},
|
||||
|
||||
hasConversation: function(id) {
|
||||
try {
|
||||
return !!this.client.scope({ searchTimeout: 100 }).findElement(
|
||||
'#thread-' + id
|
||||
);
|
||||
} catch(e) {
|
||||
return false;
|
||||
}
|
||||
},
|
||||
|
||||
getConversationTitle: function(conversation) {
|
||||
return conversation.findElement(SELECTORS.conversationTitle);
|
||||
},
|
||||
|
||||
waitToAppear: function() {
|
||||
return this.client.helper.waitForElement(SELECTORS.main);
|
||||
},
|
||||
|
||||
// Temporary solution to keep the current tests running.
|
||||
// TODO: bug 1167103
|
||||
navigateToComposer: function() {
|
||||
this.createNewMessageButton.tap();
|
||||
},
|
||||
|
||||
doubleTapOnFirstConversation: function() {
|
||||
this.client.loader.getActions().doubleTap(this.firstConversation).perform();
|
||||
}
|
||||
};
|
||||
|
||||
module.exports = InboxAccessor;
|
||||
76
gaia/apps/sms/test/marionette/lib/views/inbox/view.js
Executable file
76
gaia/apps/sms/test/marionette/lib/views/inbox/view.js
Executable file
|
|
@ -0,0 +1,76 @@
|
|||
'use strict';
|
||||
|
||||
/* global module */
|
||||
|
||||
var InboxAccessor = require('./accessors');
|
||||
|
||||
function InboxView(client) {
|
||||
this.client = client;
|
||||
this.accessors = new InboxAccessor(client);
|
||||
}
|
||||
|
||||
InboxView.prototype = {
|
||||
get conversations() {
|
||||
return this.accessors.conversations.reduce(function(list, conversation) {
|
||||
// It's workaround for the case when conversation node reference is stale,
|
||||
// so when we try to access MarionetteElement from the node that's not
|
||||
// attached to the dom we'll get an exception.
|
||||
try {
|
||||
var bodyText = conversation.findElement('.body-text');
|
||||
list.push({
|
||||
id: +conversation.getAttribute('data-thread-id'),
|
||||
lastMessageType: conversation.getAttribute('data-last-message-type'),
|
||||
title: this.accessors.getConversationTitle(conversation).text(),
|
||||
bodyText: bodyText.displayed() ? bodyText.text() : '',
|
||||
isDraft: !!conversation.getAttribute('data-draft-id'),
|
||||
hasDraft: conversation.getAttribute('class').indexOf('draft') !== -1
|
||||
});
|
||||
} catch(e) {
|
||||
console.error('Conversation node is not available', e);
|
||||
}
|
||||
|
||||
return list;
|
||||
}.bind(this), []);
|
||||
},
|
||||
|
||||
goToConversation: function(conversationId) {
|
||||
var conversation = this.accessors.findConversation(conversationId);
|
||||
|
||||
// We should retrieve all required info from the conversation node before we
|
||||
// tap on it, as in split view mode tap triggers document to change and
|
||||
// the node will be discarded.
|
||||
var isConversationLessDraft = !!conversation.getAttribute('data-draft-id');
|
||||
|
||||
conversation.tap();
|
||||
|
||||
return isConversationLessDraft ?
|
||||
this._createNewMessageView() :
|
||||
this._createConversationView();
|
||||
},
|
||||
|
||||
hasConversation: function(conversationId) {
|
||||
return this.accessors.hasConversation(conversationId);
|
||||
},
|
||||
|
||||
createNewMessage: function() {
|
||||
this.accessors.createNewMessageButton.tap();
|
||||
|
||||
return this._createNewMessageView();
|
||||
},
|
||||
|
||||
_createNewMessageView: function() {
|
||||
var NewMessageView = require('../new-message/view');
|
||||
var newMessageView = new NewMessageView(this.client);
|
||||
newMessageView.accessors.waitToAppear();
|
||||
return newMessageView;
|
||||
},
|
||||
|
||||
_createConversationView: function() {
|
||||
var ConversationView = require('../conversation/view');
|
||||
var conversationView = new ConversationView(this.client);
|
||||
conversationView.accessors.waitToAppear();
|
||||
return conversationView;
|
||||
}
|
||||
};
|
||||
|
||||
module.exports = InboxView;
|
||||
65
gaia/apps/sms/test/marionette/lib/views/new-message/accessors.js
Executable file
65
gaia/apps/sms/test/marionette/lib/views/new-message/accessors.js
Executable file
|
|
@ -0,0 +1,65 @@
|
|||
'use strict';
|
||||
|
||||
/* global module */
|
||||
|
||||
var SELECTORS = Object.freeze({
|
||||
main: '.panel-ConversationView',
|
||||
toField: '#messages-to-field',
|
||||
// last-child is a temporary workaround for bug 1097575
|
||||
recipientsInput: '#messages-to-field [contenteditable=true]:last-child',
|
||||
recipientsList: '#messages-recipients-list',
|
||||
recipients: '#messages-recipients-list .recipient[contenteditable=false]',
|
||||
pickContactButton: '#messages-contact-pick-button',
|
||||
header: '#messages-header',
|
||||
headerActionButton: '.action-button',
|
||||
optionsButton: '#messages-options-button',
|
||||
messageConvertNotice: '#messages-convert-notice'
|
||||
});
|
||||
|
||||
function NewMessageAccessor(client) {
|
||||
this.client = client;
|
||||
}
|
||||
|
||||
NewMessageAccessor.prototype = {
|
||||
get toField() {
|
||||
return this.client.helper.waitForElement(SELECTORS.toField);
|
||||
},
|
||||
|
||||
get recipientsInput() {
|
||||
return this.client.helper.waitForElement(SELECTORS.recipientsInput);
|
||||
},
|
||||
|
||||
get recipientsList() {
|
||||
return this.client.helper.waitForElement(SELECTORS.recipientsList);
|
||||
},
|
||||
|
||||
get recipients() {
|
||||
return this.client.findElements(SELECTORS.recipients);
|
||||
},
|
||||
|
||||
get pickContactButton() {
|
||||
return this.client.helper.waitForElement(SELECTORS.pickContactButton);
|
||||
},
|
||||
|
||||
get optionsButton() {
|
||||
return this.client.helper.waitForElement(SELECTORS.optionsButton);
|
||||
},
|
||||
|
||||
get header() {
|
||||
return this.client.helper.waitForElement(SELECTORS.header);
|
||||
},
|
||||
|
||||
get headerActionButton() {
|
||||
return this.client.helper.waitForElement(SELECTORS.headerActionButton);
|
||||
},
|
||||
|
||||
get conversionBanner() {
|
||||
return this.client.findElement(SELECTORS.messageConvertNotice);
|
||||
},
|
||||
|
||||
waitToAppear: function() {
|
||||
return this.client.helper.waitForElement(SELECTORS.main);
|
||||
}
|
||||
};
|
||||
|
||||
module.exports = NewMessageAccessor;
|
||||
207
gaia/apps/sms/test/marionette/lib/views/new-message/view.js
Executable file
207
gaia/apps/sms/test/marionette/lib/views/new-message/view.js
Executable file
|
|
@ -0,0 +1,207 @@
|
|||
'use strict';
|
||||
|
||||
/* global module */
|
||||
|
||||
var NewMessageAccessor = require('./accessors');
|
||||
var ComposerAccessor = require('../shared/composer_accessors');
|
||||
var MenuAccessor = require('../shared/menu_accessors');
|
||||
var Tools = require('../shared/tools');
|
||||
|
||||
var appRoot = require('app-root-path');
|
||||
// TODO Change the path once requireFromApp becomes its own module
|
||||
var fromApp = require(appRoot +
|
||||
'/shared/test/integration/require_from_app');
|
||||
|
||||
function NewMessageView(client) {
|
||||
this.client = client;
|
||||
|
||||
this.accessors = new NewMessageAccessor(client);
|
||||
this.composerAccessors = new ComposerAccessor(client);
|
||||
this.menuAccessors = new MenuAccessor(client);
|
||||
}
|
||||
|
||||
NewMessageView.prototype = {
|
||||
// TODO Move these constants to marionette, see bug 1207516
|
||||
KEYS: {
|
||||
backspace: '\ue003',
|
||||
enter: '\ue007'
|
||||
},
|
||||
|
||||
get recipients() {
|
||||
return this.accessors.recipients.map(function(recipient) {
|
||||
return recipient.text();
|
||||
});
|
||||
},
|
||||
|
||||
get recipientsPhoneNumbers() {
|
||||
return this.accessors.recipients.map(function(recipient) {
|
||||
return recipient.getAttribute('data-number');
|
||||
});
|
||||
},
|
||||
|
||||
get attachments() {
|
||||
return this.composerAccessors.attachments;
|
||||
},
|
||||
|
||||
get messageText() {
|
||||
return this.composerAccessors.messageInput.text().trim();
|
||||
},
|
||||
|
||||
get subject() {
|
||||
return this.composerAccessors.subjectInput.text().trim();
|
||||
},
|
||||
|
||||
addNewRecipient: function(recipient, separator) {
|
||||
separator = separator === undefined ? this.KEYS.enter : separator;
|
||||
|
||||
this.accessors.recipientsInput.sendKeys(recipient + separator);
|
||||
|
||||
// Since recipient.js re-renders recipients all the time (when new
|
||||
// recipient is added or old is removed) and it can happen several
|
||||
// times during single "add" or "remove" operation we should
|
||||
// wait until Recipients View is in a final state. The problem here is
|
||||
// that between "findElement" and "displayed" calls element can
|
||||
// actually be removed from DOM and re-created again that will lead to
|
||||
// "stale element" exception.
|
||||
var toField = this.accessors.toField;
|
||||
toField.scriptWith(observeElementStability);
|
||||
this.client.helper.waitFor(function() {
|
||||
return toField.scriptWith(function(el) {
|
||||
return !!el.dataset.__stable;
|
||||
});
|
||||
});
|
||||
},
|
||||
|
||||
clearRecipients: function() {
|
||||
var recipientsList = this.accessors.recipientsList;
|
||||
this.accessors.recipientsInput.tap(); // tapping the available space
|
||||
while (recipientsList.text() !== '') {
|
||||
// note: this works because in the app we handle the keys at the list
|
||||
// level, but we should rather send the keys to the focussed element.
|
||||
recipientsList.sendKeys(this.KEYS.backspace);
|
||||
}
|
||||
},
|
||||
|
||||
clearMessage: function() {
|
||||
var messageInput = this.composerAccessors.messageInput;
|
||||
messageInput.tap();
|
||||
while (messageInput.text() !== '') {
|
||||
messageInput.sendKeys(this.KEYS.backspace);
|
||||
}
|
||||
},
|
||||
|
||||
containsInvalidRecipients: function() {
|
||||
return this.accessors.recipients.some(function(recipient) {
|
||||
return recipient.getAttribute('class').indexOf('invalid') > -1;
|
||||
});
|
||||
},
|
||||
|
||||
openContactPicker: function() {
|
||||
this.accessors.pickContactButton.tap();
|
||||
var Contacts = fromApp('contacts').require('lib/contacts');
|
||||
var contacts = new Contacts(this.client);
|
||||
contacts.switchToApp();
|
||||
return contacts;
|
||||
},
|
||||
|
||||
typeMessage: function(message) {
|
||||
this.composerAccessors.messageInput.sendKeys(message);
|
||||
},
|
||||
|
||||
addAttachment: function() {
|
||||
return this.composerAccessors.addAttachment();
|
||||
},
|
||||
|
||||
/**
|
||||
* Take a screenshot of an attachment thumbnail in the composer.
|
||||
* @param {Number} the attachment index
|
||||
* @return {String} The screenshot
|
||||
*/
|
||||
takeComposerAttachmentScreenshot: function(index) {
|
||||
return this.composerAccessors.takeAttachmentScreenshot(index);
|
||||
},
|
||||
|
||||
typeSubject: function(subject) {
|
||||
this.composerAccessors.subjectInput.sendKeys(subject);
|
||||
},
|
||||
|
||||
showOptions: function() {
|
||||
this.accessors.optionsButton.tap();
|
||||
},
|
||||
|
||||
showSubject: function() {
|
||||
this.showOptions();
|
||||
this.menuAccessors.selectAppMenuOption('Add subject');
|
||||
this.client.helper.waitForElement(this.composerAccessors.subjectInput);
|
||||
},
|
||||
|
||||
hideSubject: function() {
|
||||
this.showOptions();
|
||||
this.menuAccessors.selectAppMenuOption('Remove subject');
|
||||
this.client.helper.waitForElementToDisappear(
|
||||
this.composerAccessors.subjectInput
|
||||
);
|
||||
},
|
||||
|
||||
send: function() {
|
||||
this.composerAccessors.send();
|
||||
|
||||
var ConversationView = require('../conversation/view');
|
||||
var conversationView = new ConversationView(this.client);
|
||||
conversationView.accessors.waitToAppear();
|
||||
|
||||
return conversationView;
|
||||
},
|
||||
|
||||
backToInbox: function() {
|
||||
this.client.switchToShadowRoot(this.accessors.header);
|
||||
this.accessors.headerActionButton.tap();
|
||||
this.client.switchToShadowRoot();
|
||||
|
||||
var InboxView = require('../inbox/view');
|
||||
var inboxView = new InboxView(this.client);
|
||||
inboxView.accessors.waitToAppear();
|
||||
|
||||
return inboxView;
|
||||
},
|
||||
|
||||
isSendButtonEnabled: function() {
|
||||
var sendButton = this.composerAccessors.sendButton;
|
||||
return sendButton.getAttribute('disabled') !== 'true';
|
||||
},
|
||||
|
||||
isSubjectVisible: function() {
|
||||
var subjectInput = this.composerAccessors.subjectInput;
|
||||
return subjectInput && this.composerAccessors.subjectInput.displayed();
|
||||
},
|
||||
|
||||
assertMessageInputFocused: function(message) {
|
||||
Tools.assertElementFocused(this.composerAccessors.messageInput, message);
|
||||
},
|
||||
|
||||
assertRecipientsInputFocused: function(message) {
|
||||
Tools.assertElementFocused(this.accessors.recipientsInput, message);
|
||||
}
|
||||
};
|
||||
|
||||
function observeElementStability(el) {
|
||||
delete el.dataset.__stable;
|
||||
|
||||
function markElementAsStable() {
|
||||
return setTimeout(function() {
|
||||
el.dataset.__stable = 'true';
|
||||
observer.disconnect();
|
||||
}, 1000);
|
||||
}
|
||||
|
||||
var timeout = markElementAsStable();
|
||||
var observer = new MutationObserver(function() {
|
||||
if (timeout) {
|
||||
clearTimeout(timeout);
|
||||
timeout = markElementAsStable();
|
||||
}
|
||||
});
|
||||
observer.observe(el, { childList: true, subtree: true });
|
||||
}
|
||||
|
||||
module.exports = NewMessageView;
|
||||
29
gaia/apps/sms/test/marionette/lib/views/participants/accessors.js
Executable file
29
gaia/apps/sms/test/marionette/lib/views/participants/accessors.js
Executable file
|
|
@ -0,0 +1,29 @@
|
|||
'use strict';
|
||||
|
||||
/* global module */
|
||||
|
||||
var SELECTORS = Object.freeze({
|
||||
main: '.panel-GroupView',
|
||||
header: '#information-group-header',
|
||||
headerActionButton: '.action-button'
|
||||
});
|
||||
|
||||
function ParticipantsAccessor(client) {
|
||||
this.client = client;
|
||||
}
|
||||
|
||||
ParticipantsAccessor.prototype = {
|
||||
get header() {
|
||||
return this.client.helper.waitForElement(SELECTORS.header);
|
||||
},
|
||||
|
||||
get headerActionButton() {
|
||||
return this.client.helper.waitForElement(SELECTORS.headerActionButton);
|
||||
},
|
||||
|
||||
waitToAppear: function() {
|
||||
return this.client.helper.waitForElement(SELECTORS.main);
|
||||
}
|
||||
};
|
||||
|
||||
module.exports = ParticipantsAccessor;
|
||||
20
gaia/apps/sms/test/marionette/lib/views/participants/view.js
Executable file
20
gaia/apps/sms/test/marionette/lib/views/participants/view.js
Executable file
|
|
@ -0,0 +1,20 @@
|
|||
'use strict';
|
||||
|
||||
/* global module */
|
||||
|
||||
var ParticipantsAccessor = require('./accessors');
|
||||
|
||||
function ParticipantsView(client) {
|
||||
this.client = client;
|
||||
this.accessors = new ParticipantsAccessor(client);
|
||||
}
|
||||
|
||||
ParticipantsView.prototype = {
|
||||
back: function() {
|
||||
this.client.switchToShadowRoot(this.accessors.header);
|
||||
this.accessors.headerActionButton.tap();
|
||||
this.client.switchToShadowRoot();
|
||||
}
|
||||
};
|
||||
|
||||
module.exports = ParticipantsView;
|
||||
29
gaia/apps/sms/test/marionette/lib/views/report/accessors.js
Executable file
29
gaia/apps/sms/test/marionette/lib/views/report/accessors.js
Executable file
|
|
@ -0,0 +1,29 @@
|
|||
'use strict';
|
||||
|
||||
/* global module */
|
||||
|
||||
var SELECTORS = Object.freeze({
|
||||
main: '.panel-ReportView',
|
||||
header: '#information-report-header',
|
||||
headerActionButton: '.action-button'
|
||||
});
|
||||
|
||||
function ReportAccessor(client) {
|
||||
this.client = client;
|
||||
}
|
||||
|
||||
ReportAccessor.prototype = {
|
||||
get header() {
|
||||
return this.client.helper.waitForElement(SELECTORS.header);
|
||||
},
|
||||
|
||||
get headerActionButton() {
|
||||
return this.client.helper.waitForElement(SELECTORS.headerActionButton);
|
||||
},
|
||||
|
||||
waitToAppear: function() {
|
||||
return this.client.helper.waitForElement(SELECTORS.main);
|
||||
}
|
||||
};
|
||||
|
||||
module.exports = ReportAccessor;
|
||||
23
gaia/apps/sms/test/marionette/lib/views/report/view.js
Executable file
23
gaia/apps/sms/test/marionette/lib/views/report/view.js
Executable file
|
|
@ -0,0 +1,23 @@
|
|||
'use strict';
|
||||
|
||||
/* global module */
|
||||
|
||||
var ReportAccessor = require('./accessors');
|
||||
|
||||
function ReportView(client, parentView) {
|
||||
this.client = client;
|
||||
this.parentView = parentView;
|
||||
this.accessors = new ReportAccessor(client);
|
||||
}
|
||||
|
||||
ReportView.prototype = {
|
||||
back: function() {
|
||||
this.client.switchToShadowRoot(this.accessors.header);
|
||||
this.accessors.headerActionButton.tap();
|
||||
this.client.switchToShadowRoot();
|
||||
this.parentView.accessors.waitToAppear();
|
||||
return this.parentView;
|
||||
}
|
||||
};
|
||||
|
||||
module.exports = ReportView;
|
||||
72
gaia/apps/sms/test/marionette/lib/views/shared/composer_accessors.js
Executable file
72
gaia/apps/sms/test/marionette/lib/views/shared/composer_accessors.js
Executable file
|
|
@ -0,0 +1,72 @@
|
|||
'use strict';
|
||||
|
||||
/* global module */
|
||||
|
||||
var SELECTORS = Object.freeze({
|
||||
messageInput: '#messages-input',
|
||||
subjectInput: '.subject-composer-input',
|
||||
sendButton: '#messages-send-button',
|
||||
attachButton: '#messages-attach-button',
|
||||
charCounter: '.message-counter',
|
||||
mmsLabel: '.mms-label',
|
||||
attachment: '#messages-input .attachment-container'
|
||||
});
|
||||
|
||||
function ComposerAccessor(client) {
|
||||
this.client = client;
|
||||
}
|
||||
|
||||
ComposerAccessor.prototype = {
|
||||
get messageInput() {
|
||||
return this.client.helper.waitForElement(SELECTORS.messageInput);
|
||||
},
|
||||
|
||||
get subjectInput() {
|
||||
return this.client.findElement(SELECTORS.subjectInput);
|
||||
},
|
||||
|
||||
get sendButton() {
|
||||
return this.client.helper.waitForElement(SELECTORS.sendButton);
|
||||
},
|
||||
|
||||
get attachButton() {
|
||||
return this.client.helper.waitForElement(SELECTORS.attachButton);
|
||||
},
|
||||
|
||||
get charCounter() {
|
||||
return this.client.findElement(SELECTORS.charCounter);
|
||||
},
|
||||
|
||||
get mmsLabel() {
|
||||
return this.client.findElement(SELECTORS.mmsLabel);
|
||||
},
|
||||
|
||||
get attachments() {
|
||||
return this.client.findElements(SELECTORS.attachment);
|
||||
},
|
||||
|
||||
addAttachment: function() {
|
||||
this.client.waitFor(function() {
|
||||
return this.attachButton.enabled();
|
||||
}.bind(this));
|
||||
|
||||
this.attachButton.tap();
|
||||
|
||||
var ActivityChooserView = require('../activity-chooser/view');
|
||||
return new ActivityChooserView(this.client);
|
||||
},
|
||||
|
||||
takeAttachmentScreenshot(index) {
|
||||
return this.client.screenshot({ element: this.attachments[index] });
|
||||
},
|
||||
|
||||
send: function() {
|
||||
this.client.waitFor(function() {
|
||||
return this.sendButton.enabled();
|
||||
}.bind(this));
|
||||
|
||||
this.sendButton.tap();
|
||||
}
|
||||
};
|
||||
|
||||
module.exports = ComposerAccessor;
|
||||
57
gaia/apps/sms/test/marionette/lib/views/shared/menu_accessors.js
Executable file
57
gaia/apps/sms/test/marionette/lib/views/shared/menu_accessors.js
Executable file
|
|
@ -0,0 +1,57 @@
|
|||
'use strict';
|
||||
|
||||
/* global module */
|
||||
|
||||
var SELECTORS = Object.freeze({
|
||||
optionMenu: 'body > form[data-type=action] menu',
|
||||
systemMenu: 'form[data-z-index-level="action-menu"]',
|
||||
contactPromptMenu: '.contact-prompt menu'
|
||||
});
|
||||
|
||||
function MenuAccessor(client) {
|
||||
this.client = client;
|
||||
}
|
||||
|
||||
MenuAccessor.prototype = {
|
||||
get systemMenu() {
|
||||
// Switch to the system app first.
|
||||
this.client.switchToFrame();
|
||||
return this.client.helper.waitForElement(SELECTORS.systemMenu);
|
||||
},
|
||||
|
||||
get optionMenu() {
|
||||
return this.client.helper.waitForElement(SELECTORS.optionMenu);
|
||||
},
|
||||
|
||||
get contactPromptMenu() {
|
||||
return this.client.helper.waitForElement(SELECTORS.contactPromptMenu);
|
||||
},
|
||||
|
||||
selectAppMenuOption: function(text) {
|
||||
this.selectMenuOption(this.optionMenu, text);
|
||||
},
|
||||
|
||||
selectSystemMenuOption: function(text) {
|
||||
this.selectMenuOption(this.systemMenu, text);
|
||||
},
|
||||
|
||||
selectContactPromptMenuOption: function(text) {
|
||||
this.selectMenuOption(this.contactPromptMenu, text);
|
||||
},
|
||||
|
||||
selectMenuOption: function(menuElement, text) {
|
||||
var menuOptions = menuElement.findElements('button');
|
||||
for (var i = 0; i < menuOptions.length; i++) {
|
||||
var menuOption = menuOptions[i];
|
||||
if (menuOption.text().toLowerCase() === text.toLowerCase()) {
|
||||
// XXX: Workaround until http://bugzil.la/912873 is fixed.
|
||||
// Wait for 750ms to let the element be clickable
|
||||
this.client.helper.wait(750);
|
||||
menuOption.tap();
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
module.exports = MenuAccessor;
|
||||
26
gaia/apps/sms/test/marionette/lib/views/shared/tools.js
Executable file
26
gaia/apps/sms/test/marionette/lib/views/shared/tools.js
Executable file
|
|
@ -0,0 +1,26 @@
|
|||
/* global module, require */
|
||||
'use strict';
|
||||
|
||||
var assert = require('chai').assert;
|
||||
|
||||
var ASSETS_DIR = './apps/sms/test/marionette/assets/';
|
||||
|
||||
module.exports = {
|
||||
assertElementFocused: function(element, message) {
|
||||
assert.isTrue(
|
||||
element.scriptWith(function(el) {
|
||||
return document.activeElement === el;
|
||||
}),
|
||||
message
|
||||
);
|
||||
},
|
||||
|
||||
/**
|
||||
* @param {String} assertName The file name, relative to the asset directory.
|
||||
* @returns {String} The file content in base64 form.
|
||||
*/
|
||||
loadAsset: function(assetName) {
|
||||
var fileName = ASSETS_DIR + assetName;
|
||||
return require('fs').readFileSync(fileName, { encoding: 'base64' });
|
||||
}
|
||||
};
|
||||
Loading…
Reference in a new issue