Publish Vulpes Preview 15 sources for desktop, Android and Tundra

Share Gaia assets and adapters, keep private profiles and development documents local, and update both public READMEs.
This commit is contained in:
OverSu 2026-09-30 23:13:03 +02:00
commit b38dea0597
25901 changed files with 9982925 additions and 369 deletions

View file

@ -0,0 +1,477 @@
'use strict';
/**
* Abstraction around contacts app.
* @constructor
* @param {Marionette.Client} client for operations.
*/
function Contacts(client) {
this.client = client;
this.client.setSearchTimeout(10000);
this.actions = this.client.loader.getActions();
}
/**
* @type String Origin of contacts app
*/
Contacts.URL = 'app://communications.gaiamobile.org';
Contacts.config = {
prefs: {
'device.storage.enabled': true,
'device.storage.testing': true,
'device.storage.prompt.testing': true
}
};
Contacts.Selectors = {
body: 'body',
bodyReady: 'body .view-body',
settingsButton: '#settings-button',
confirmHeader: '#confirmation-message h1',
confirmBody: '#confirmation-message p',
confirmDismiss: '#confirmation-message menu button',
confirmDelete: '#confirmation-message .danger',
details: '#view-contact-details',
detailsEditContact: '#edit-contact-button',
detailsTelLabelFirst: '#phone-details-template-0 h2',
detailsTelButtonFirst: '.button.icon-call[data-tel]',
detailsEmail: '#contact-detail-inner #email-details-template-0 div.item',
detailsAddress: '#contact-detail-inner #address-details-template-0 div.item',
detailsOrg: '#contact-detail-inner #org-title',
detailsNote: '#contact-detail-inner #note-details-template-0',
detailsFindDuplicate: '#contact-detail-inner #find-merge-button',
detailsFavoriteButton: '#toggle-favorite',
detailsContactName: '#contact-name-title',
detailsHeader: '#details-view-header',
detailsSocialLabel: '#contact-detail-inner #details-list #social-label',
detailsSocialTemplate: '#contact-detail-inner #details-list .social-actions',
detailsCoverImage: '#cover-img',
detailsShareButton: '#contact-detail-inner #share_button',
findDupsButton: '#details-list #find-merge-button',
duplicateFrame: 'iframe[src*="matching_contacts.html"]',
duplicateHeader: '#title',
duplicateClose: '#merge-close',
duplicateMerge: '#merge-action',
exportButton: '#exportContacts button',
exportSDCard: '#export-sd-option button',
form: '#view-contact-form',
formTitle: '#contact-form-title',
formCustomTag: '#custom-tag',
formCustomTagPage: '#view-select-tag',
formCustomTagDone: '#view-select-tag #settings-done',
formNew: '#add-contact-button',
formGivenName: '#givenName',
formOrg: '#org',
formFamilyName: '#familyName',
formSave: '#save-button',
formTel: '#contacts-form-phones input[type="tel"]',
formDelFirstTel: '#add-phone-0 .img-delete-button',
formTelLabelFirst: '#tel_type_0',
formTelNumberFirst: '#number_0',
formTelNumberSecond: '#number_1',
formEmailFirst: '#email_0',
formEmailSecond: '#email_1',
formPhotoButton: '#photo-button',
formAddNewTel: '#add-new-phone',
formAddNewEmail: '#add-new-email',
formHeader: '#contact-form-header',
formPhotoImg: '#thumbnail-photo',
deleteContact: '#delete-contact',
groupList: ' #groups-list',
contacts: '#groups-list .contact-item',
list: '#view-contacts-list',
listContactFirst: 'li:not([data-group="ice"]).contact-item',
listContactFirstText: 'li:not([data-group="ice"]).contact-item p',
contactsItems: 'li:not([data-group="ice"]).contact-item p',
contactListHeader: '#contacts-list-header',
contactsIframe: '#iframe-contacts',
searchLabel: '#search-start',
searchInput: '#search-contact',
searchCancel: '#cancel-search',
searchResultFirst: '#search-list .contact-item',
scrollbar: 'nav[data-type="scrollbar"]',
overlay: 'nav[data-type="scrollbar"] p',
settingsView: '#view-settings',
settingsClose: '#settings-close',
bulkDelete: '#bulkDelete',
orderSwitch: '#settingsOrder gaia-switch',
changeOrder: '#switch',
editForm: '#selectable-form',
editMenu: '#select-all-wrapper',
selectAction: '#select-action',
selectAllButton: '#select-all',
clearOrgButton: '#clear-org',
setIceButton: '#set-ice',
iceHeader: '#ice-header',
iceSettingsHeader: '#ice-settings-header',
iceSettings: '#ice-settings',
iceSwitch1: '#ice-contacts-1-switch',
iceInputSwitch1: '#ice-contacts-1-switch input[type="checkbox"]',
iceSwitch2: '#ice-contacts-2-switch',
iceInputSwitch2: '#ice-contacts-2-switch input[type="checkbox"]',
iceButton1: '#select-ice-contact-1',
iceButton2: '#select-ice-contact-2',
iceGroupOpen: '#section-group-ice',
iceContact: '#ice-group .contact-item',
importContacts: '#importContacts',
importSDCard: '#import-sd-option button',
importHeader: '#import-settings-header',
activityChooser: 'form[data-type="action"]',
buttonActivityChooser: 'form[data-type="action"] button',
actionMenu: '#action-menu',
actionMenuList: '#value-menu',
actionButton: '.action-button',
multipleSelectSave: '#save-button',
multipleSelectStatus: '#statusMsg p',
multipleSelectList: '#multiple-select-container',
systemMenu: 'form[data-z-index-level="action-menu"]',
galleryImage: '.thumbnail img',
galleryDone: '#crop-done-button',
header: '#edit-title',
noContacts: '#no-contacts'
};
Contacts.prototype = {
/**
* Launches contacts app and focuses on frame.
*/
launch: function() {
this.client.apps.launch(Contacts.URL, 'contacts');
this.switchToApp();
this.client.helper.waitForElement(Contacts.Selectors.bodyReady);
},
relaunch: function() {
this.close();
this.launch();
},
close: function() {
this.client.apps.close(Contacts.URL, 'contacts');
},
switchToApp: function() {
this.client.switchToFrame();
// switchToApp already waits for the app to be displayed
this.client.apps.switchToApp(Contacts.URL, 'contacts');
},
switchToCreateNewContactActivity: function() {
this._switchToActivity('views/form/form.html');
},
switchToMergeInterface: function() {
this._switchToActivity('views/matching/matching_contacts.html');
},
_switchToActivity: function(href) {
this.client.switchToFrame();
// switchToActivity already waits for the app to be displayed
this.client.apps.switchToActivity(Contacts.URL, '/contacts/' + href);
},
/**
* Returns a localized string from a properties file.
* @param {String} file to open.
* @param {String} key of the string to lookup.
*/
l10n: function(file, key) {
var ast = this.client.executeScript(function(file, key) {
var xhr = new XMLHttpRequest();
var data;
xhr.open('GET', file, false); // Intentional sync
xhr.onload = function(o) {
data = JSON.parse(xhr.response);
};
xhr.send(null);
return data;
}, [file, key]);
for (var i = 0; i < ast.length; i++) {
if (ast[i].$i === key) {
return ast[i].$v;
}
}
},
waitSlideLeft: function(elementKey) {
var element = this.client.findElement(Contacts.Selectors[elementKey]),
location;
var test = function() {
location = element.location();
return location.x <= 0;
};
this.client.waitFor(test);
},
waitForSlideDown: function(element) {
var bodyHeight = this.client.findElement(Contacts.Selectors.body).
size().height;
var test = function() {
return element.location().y >= bodyHeight;
};
this.client.waitFor(test);
},
waitForSlideUp: function(element) {
var test = function() {
return element.location().y <= 0;
};
this.client.waitFor(test);
},
waitForFadeIn: function(element) {
var test = function() {
var opacity = element.cssProperty('opacity');
var pointerEvents = element.cssProperty('pointer-events');
return opacity == 1 && pointerEvents == 'auto';
};
this.client.waitFor(test);
},
waitForFormShown: function() {
var form = this.client.helper.waitForElement(Contacts.Selectors.form),
location;
var test = function() {
location = form.location();
return location.y <= 0;
};
this.client.waitFor(test);
},
waitForFormTransition: function() {
var selectors = Contacts.Selectors;
var form = this.client.findElement(selectors.form);
try {
this.client.helper.waitForElementToDisappear(form);
} catch (e) {
// When the contact details pages was created in an activity and we close
// it, we might encounter errors like "GenericError: can't access dead
// object" or 'JavaScriptError: unload was called'. So we drop them as it
// makes the tests crash.
var isKnownError = e.message.indexOf(
'GenericError: can\'t access dead object') > -1 ||
e.message.indexOf('JavaScriptError: unload was called') > -1;
if (!isKnownError) {
throw e;
}
}
},
editContact: function() {
var selectors = Contacts.Selectors;
var edit = this.client.helper.waitForElement(selectors.detailsEditContact);
this.clickOn(edit);
this.waitForFadeIn(this.client.helper.waitForElement(selectors.form));
},
// Goes back to the Contact List view from a Details view
backToList: function() {
var selectors = Contacts.Selectors;
// Now we go back to the ICE settings and check that our ICE remains
this.waitForFadeIn(this.client.helper.waitForElement(selectors.details));
var header = this.client.helper.waitForElement(selectors.detailsHeader);
this.actions.wait(0.5).tap(header, 10, 10).perform();
this.waitSlideLeft('list');
},
enterContactDetails: function(details) {
var selectors = Contacts.Selectors;
details = details || {
givenName: 'Hello',
familyName: 'Contact',
org: 'Enterprise'
};
this.waitForFormShown();
for (var i in details) {
// Camelcase details to match form.* selectors.
var key = 'form' + i.charAt(0).toUpperCase() + i.slice(1);
this.client.findElement(selectors[key])
.sendKeys(details[i]);
}
this.client.findElement(selectors.formSave).click();
},
addContact: function(details) {
var selectors = Contacts.Selectors;
var addContact = this.client.findElement(selectors.formNew);
addContact.click();
this.enterContactDetails(details);
this.waitForFormTransition();
this.client.helper.waitForElement(selectors.list);
},
mergeContact: function(details) {
var selectors = Contacts.Selectors;
var addContact = this.client.findElement(selectors.formNew);
addContact.click();
this.enterContactDetails(details);
this.switchToMergeInterface();
var duplicateFrame = this.client.findElement(selectors.duplicateFrame);
var mergeAction =
this.client.helper.waitForElement(selectors.duplicateMerge);
this.clickOn(mergeAction);
this.switchTo();
this.waitForSlideDown(duplicateFrame);
},
addContactMultipleEmails: function(details) {
var selectors = Contacts.Selectors;
var addContact = this.client.findElement(selectors.formNew);
addContact.click();
this.client.helper.waitForElement(selectors.formAddNewEmail).click();
this.enterContactDetails(details);
this.waitForFormTransition();
this.client.helper.waitForElement(selectors.list);
},
tapContact: function(contactName) {
// There are currently no way to search on the text of an HTML Element
// That's why this function do the filter and check that no more than 1
// contact has the name we look up.
var elements = this.client.findElements(Contacts.Selectors.contacts);
var contactsWithName = elements.filter(function(el) {
var data = el.text();
return data && data.indexOf(contactName) > -1;
});
if (contactsWithName.length === 1) {
contactsWithName[0].tap();
} else if (contactsWithName.length > 1) {
throw new Error('There are more than 1 contact that contains "' +
contactName + '"');
} else {
throw new Error('"' + contactName + '" was not found');
}
},
getContactFirstName: function() {
var ci = this.client.findElement(Contacts.Selectors.contactsIframe);
this.client.switchToFrame(ci);
var contactInContactList = this.client.findElement(
Contacts.Selectors.listContactFirst
);
var contactName = contactInContactList.text();
return contactName;
},
tapSave: function() {
this.client.helper.waitForElement(Contacts.Selectors.formSave).click();
},
createNewContactAndReturnToDialer: function(contactName) {
this.client.helper.waitForElement(Contacts.Selectors.formGivenName);
this.client.helper.fillInputField(
Contacts.Selectors.formGivenName, contactName
);
this.client.helper.waitForElement(Contacts.Selectors.formSave).click();
var dialer = this.client.loader.getAppClass('dialer');
dialer.switchTo();
return dialer;
},
tapUpdate: function() {
this.tapSave();
},
get systemMenu() {
var selectors = Contacts.Selectors;
// Switch to the system app first.
this.client.switchToFrame();
return this.client.helper.waitForElement(selectors.systemMenu);
},
/**
* Helper method to simulate clicks on iFrames which is not currently
* working in the Marionette JS Runner.
* @param {Marionette.Element} element The element to simulate the click on.
**/
clickOn: function(element) {
element.scriptWith(function(elementEl) {
var event = new MouseEvent('click', {
'view': window,
'bubbles': true,
'cancelable': true
});
elementEl.dispatchEvent(event);
});
},
goToSettings: function() {
this.client.helper.waitForElement(
Contacts.Selectors.settingsButton).tap();
var settings = this.client.helper.waitForElement(
Contacts.Selectors.settingsView);
this.waitForFadeIn(settings);
},
getElementStyle: function(selector, type) {
return this.client.executeScript(function(selector, type) {
return document.querySelector(selector).style[type];
}, [selector, type]);
},
getNumberOfContacts: function() {
return this.contactsNames.length;
},
get contactsNames() {
try {
var contactElements = this.client.findElements(
Contacts.Selectors.contactsItems);
return contactElements.map((element) => {
return element.text();
});
} catch(e) {
return [];
}
}
};
module.exports = Contacts;