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
17
gaia/apps/communications/contacts/test/unit/import/.jshintrc
Executable file
17
gaia/apps/communications/contacts/test/unit/import/.jshintrc
Executable file
|
|
@ -0,0 +1,17 @@
|
|||
{
|
||||
"extends": "../../../../../../.jshintrc",
|
||||
"predef": [
|
||||
"assert",
|
||||
"suite",
|
||||
"test",
|
||||
"setup",
|
||||
"teardown",
|
||||
"suiteSetup",
|
||||
"suiteTeardown",
|
||||
"require",
|
||||
"requireApp",
|
||||
"sinon",
|
||||
"mocha",
|
||||
"loadBodyHTML"
|
||||
]
|
||||
}
|
||||
|
|
@ -0,0 +1,65 @@
|
|||
'use strict';
|
||||
|
||||
/* globals MockNavigatorDatastore, MockDatastoreObj, ContactsDatastore */
|
||||
|
||||
require('/shared/js/contacts/contacts_datastore.js');
|
||||
require('/shared/test/unit/mocks/mock_navigator_datastore.js');
|
||||
|
||||
suite('Contacts Datastore', function() {
|
||||
var realDatastore;
|
||||
var datastoreOwner = 'app://gmailprovider.gaiamobile.org/manifest.webapp';
|
||||
|
||||
suiteSetup(function() {
|
||||
var datastore1 = new MockDatastoreObj('contacts', datastoreOwner, null);
|
||||
|
||||
MockNavigatorDatastore._datastores = [
|
||||
datastore1
|
||||
];
|
||||
|
||||
realDatastore = navigator.getDataStores;
|
||||
|
||||
navigator.getDataStores = MockNavigatorDatastore.getDataStores;
|
||||
});
|
||||
|
||||
suiteTeardown(function() {
|
||||
navigator.getDataStores = realDatastore;
|
||||
});
|
||||
|
||||
test('it provides the datastore', function(done) {
|
||||
var subject = new ContactsDatastore(datastoreOwner);
|
||||
|
||||
subject.getStore().then(function(store) {
|
||||
done(function() {
|
||||
assert.equal(store.owner, datastoreOwner);
|
||||
});
|
||||
}).catch (done);
|
||||
});
|
||||
|
||||
test('if the provider name is incorrect promise is rejected', function(done) {
|
||||
var subject = new ContactsDatastore('undefinedprovider');
|
||||
|
||||
subject.getStore().then(function(store) {
|
||||
done('failed');
|
||||
}).catch (function(error) {
|
||||
done(function() {
|
||||
assert.equal(error.name, 'DatastoreNotFound');
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
test('if navigator.getDatastores is not available promise is rejected',
|
||||
function(done) {
|
||||
navigator.getDataStores = null;
|
||||
|
||||
var subject = new ContactsDatastore(datastoreOwner);
|
||||
|
||||
subject.getStore().then(function(store) {
|
||||
done('failed');
|
||||
}).catch (function(error) {
|
||||
done(function() {
|
||||
assert.equal(error.name, 'DatastoreNotEnabled');
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
});
|
||||
17
gaia/apps/communications/contacts/test/unit/import/gmail/.jshintrc
Executable file
17
gaia/apps/communications/contacts/test/unit/import/gmail/.jshintrc
Executable file
|
|
@ -0,0 +1,17 @@
|
|||
{
|
||||
"extends": "../../../../../../../.jshintrc",
|
||||
"predef": [
|
||||
"assert",
|
||||
"suite",
|
||||
"test",
|
||||
"setup",
|
||||
"teardown",
|
||||
"suiteSetup",
|
||||
"suiteTeardown",
|
||||
"require",
|
||||
"requireApp",
|
||||
"sinon",
|
||||
"mocha",
|
||||
"loadBodyHTML"
|
||||
]
|
||||
}
|
||||
352
gaia/apps/communications/contacts/test/unit/import/gmail/gmail_connector_test.js
Executable file
352
gaia/apps/communications/contacts/test/unit/import/gmail/gmail_connector_test.js
Executable file
|
|
@ -0,0 +1,352 @@
|
|||
/* globals GmailConnector, MockGoogleEntry, MockGoogleEntryInvalidDate,
|
||||
MockGoogleEntryNoName, MockGoogleGroups, MockGoogleListing, MockRest,
|
||||
MockGoogleEntryNoFirstName */
|
||||
|
||||
'use strict';
|
||||
|
||||
require('/shared/js/contacts/import/gmail/gmail_connector.js');
|
||||
requireApp('communications/contacts/test/unit/import/gmail/mock_contact1.js');
|
||||
requireApp('communications/contacts/test/unit/import/gmail/mock_listing.js');
|
||||
requireApp('communications/contacts/test/unit/import/mock_rest.js');
|
||||
requireApp('communications/contacts/test/unit/import/gmail/mock_groups.js');
|
||||
|
||||
if (!window.Rest) {
|
||||
window.Rest = null;
|
||||
}
|
||||
|
||||
suite('Gmail Connector', function() {
|
||||
|
||||
var subject,
|
||||
END_POINT = 'https://www.google.com/m8/feeds/contacts/' +
|
||||
'default/full/?max-results=10000',
|
||||
GROUP_ID = 'https://www.google.com/m8/feeds/groups/' +
|
||||
'mepartoconmigo%40gmail.com/base/6';
|
||||
|
||||
suiteSetup(function() {
|
||||
subject = GmailConnector;
|
||||
window.Rest = MockRest;
|
||||
});
|
||||
|
||||
suiteTeardown(function() {
|
||||
window.Rest = null;
|
||||
});
|
||||
|
||||
suite('Parsing', function() {
|
||||
test('Check contacts listing from feed', function(done) {
|
||||
var callbacks = {
|
||||
'success': function onSuccess(result) {
|
||||
assert.isNotNull(result);
|
||||
assert.isNotNull(result.data);
|
||||
assert.lengthOf(result.data, 2);
|
||||
done();
|
||||
},
|
||||
'error': function onError(error) {
|
||||
assert.fail('We were no suppose to get an error',
|
||||
'We got an error while get');
|
||||
},
|
||||
'timeout': function onTimeout(to) {
|
||||
assert.fail('We were no suppose to get a timeout',
|
||||
'We got a timeout while get');
|
||||
}
|
||||
};
|
||||
|
||||
var contactsListUrl = END_POINT + '&group=' + GROUP_ID;
|
||||
var restConfigure = {
|
||||
'type': 'success',
|
||||
'https://www.google.com/m8/feeds/groups/default/full/': MockGoogleGroups
|
||||
};
|
||||
restConfigure[contactsListUrl] = MockGoogleListing;
|
||||
window.Rest.configure(restConfigure);
|
||||
|
||||
subject.listAllContacts('123456789', callbacks);
|
||||
});
|
||||
|
||||
test('Parse for display', function() {
|
||||
var contact = subject.gContactToJson(MockGoogleEntry);
|
||||
var result = subject.adaptDataForShowing(contact);
|
||||
|
||||
assert.isNotNull(result);
|
||||
assert.equal(result.uid,
|
||||
'http://www.google.com/m8/feeds/contacts/' +
|
||||
'mepartoconmigo%40gmail.com/base/2fc27a388c2bd974');
|
||||
assert.deepEqual(result.givenName, ['My']);
|
||||
assert.deepEqual(result.familyName, ['Contact']);
|
||||
assert.equal(result.email1, 'juan@palomo.es');
|
||||
});
|
||||
|
||||
test('Parse for display without name', function() {
|
||||
var contact = subject.gContactToJson(MockGoogleEntryNoName);
|
||||
var result = subject.adaptDataForShowing(contact);
|
||||
|
||||
assert.isNotNull(result);
|
||||
assert.equal(result.uid,
|
||||
'http://www.google.com/m8/feeds/contacts/' +
|
||||
'mepartoconmigo%40gmail.com/base/2fc27a388c2bd974');
|
||||
assert.deepEqual(result.givenName, 'juan@palomo.es');
|
||||
assert.equal(result.email1, 'juan@palomo.es');
|
||||
});
|
||||
|
||||
test('Parse for display with invalid birthday date', function() {
|
||||
var contact = subject.gContactToJson(MockGoogleEntryInvalidDate);
|
||||
var result = subject.adaptDataForShowing(contact);
|
||||
|
||||
assert.isNotNull(result);
|
||||
assert.equal(result.uid,
|
||||
'http://www.google.com/m8/feeds/contacts/' +
|
||||
'mepartoconmigo%40gmail.com/base/2fc27a388c2bd974');
|
||||
assert.isUndefined(result.bday);
|
||||
});
|
||||
|
||||
test('Parse for display with only last name', function() {
|
||||
var contact = subject.gContactToJson(MockGoogleEntryNoFirstName);
|
||||
var result = subject.adaptDataForShowing(contact);
|
||||
|
||||
assert.isNotNull(result);
|
||||
assert.equal(result.uid,
|
||||
'http://www.google.com/m8/feeds/contacts/' +
|
||||
'mepartoconmigo%40gmail.com/base/2fc27a388c2bd974');
|
||||
assert.isUndefined(result.bday);
|
||||
});
|
||||
|
||||
test('Parse for saving', function() {
|
||||
var contact = subject.gContactToJson(MockGoogleEntry);
|
||||
var result = subject.adaptDataForSaving(contact);
|
||||
|
||||
assert.isNotNull(result);
|
||||
assert.isNotNull(result.givenName);
|
||||
assert.lengthOf(result.givenName, 1);
|
||||
assert.equal(result.givenName[0], 'My');
|
||||
|
||||
assert.isNotNull(result.familyName);
|
||||
assert.lengthOf(result.familyName, 1);
|
||||
assert.equal(result.familyName[0], 'Contact');
|
||||
|
||||
assert.isNotNull(result.email);
|
||||
assert.lengthOf(result.email, 2);
|
||||
assert.equal('juan@palomo.es', result.email[0].value);
|
||||
assert.equal('home', result.email[0].type);
|
||||
assert.equal('workemail@email.com', result.email[1].value);
|
||||
assert.equal('work', result.email[1].type);
|
||||
|
||||
assert.isNotNull(result.adr);
|
||||
assert.lengthOf(result.adr, 1);
|
||||
assert.equal('This is the Street', result.adr[0].streetAddress);
|
||||
assert.equal('The City', result.adr[0].locality);
|
||||
assert.equal('The State', result.adr[0].region);
|
||||
assert.equal('The Zip', result.adr[0].postalCode);
|
||||
assert.equal('The Country', result.adr[0].countryName);
|
||||
|
||||
assert.isNotNull(result.tel);
|
||||
assert.lengthOf(result.tel, 1);
|
||||
assert.equal('+1555111333444222', result.tel[0].value);
|
||||
assert.equal('work', result.tel[0].type);
|
||||
|
||||
assert.isNotNull(result.org);
|
||||
assert.lengthOf(result.org, 1);
|
||||
assert.equal('The Company', result.org[0]);
|
||||
|
||||
assert.isNotNull(result.jobTitle);
|
||||
assert.lengthOf(result.jobTitle, 1);
|
||||
assert.equal('Title in Company', result.jobTitle[0]);
|
||||
|
||||
assert.isNotNull(result.bday);
|
||||
assert.deepEqual(new Date('1990-01-01'), result.bday);
|
||||
|
||||
assert.isNotNull(result.note);
|
||||
assert.lengthOf(result.note, 1);
|
||||
assert.equal('This is a Note', result.note[0]);
|
||||
|
||||
assert.isNotNull(result.category);
|
||||
assert.lengthOf(result.category, 1);
|
||||
assert.equal('gmail', result.category[0]);
|
||||
|
||||
assert.isNotNull(result.url);
|
||||
assert.lengthOf(result.url, 1);
|
||||
assert.lengthOf(result.url[0].type, 1);
|
||||
assert.equal('source', result.url[0].type[0]);
|
||||
assert.equal('urn:service:gmail:uid:' +
|
||||
'http://www.google.com/m8/feeds/contacts/' +
|
||||
'mepartoconmigo%40gmail.com/base/2fc27a388c2bd974',
|
||||
result.url[0].value);
|
||||
});
|
||||
|
||||
test('Parse for saving without name', function() {
|
||||
var contact = subject.gContactToJson(MockGoogleEntryNoName);
|
||||
var result = subject.adaptDataForSaving(contact);
|
||||
|
||||
assert.isNotNull(result);
|
||||
assert.isTrue(!result.givenName);
|
||||
assert.isTrue(!result.familyName);
|
||||
|
||||
assert.isNotNull(result.email);
|
||||
assert.lengthOf(result.email, 2);
|
||||
assert.equal('juan@palomo.es', result.email[0].value);
|
||||
assert.equal('home', result.email[0].type);
|
||||
assert.equal('workemail@email.com', result.email[1].value);
|
||||
assert.equal('work', result.email[1].type);
|
||||
|
||||
assert.isNotNull(result.adr);
|
||||
assert.lengthOf(result.adr, 1);
|
||||
assert.equal('This is the Street', result.adr[0].streetAddress);
|
||||
assert.equal('The City', result.adr[0].locality);
|
||||
assert.equal('The State', result.adr[0].region);
|
||||
assert.equal('The Zip', result.adr[0].postalCode);
|
||||
assert.equal('The Country', result.adr[0].countryName);
|
||||
|
||||
assert.isNotNull(result.tel);
|
||||
assert.lengthOf(result.tel, 1);
|
||||
assert.equal('+1555111333444222', result.tel[0].value);
|
||||
assert.equal('work', result.tel[0].type);
|
||||
|
||||
assert.isNotNull(result.org);
|
||||
assert.lengthOf(result.org, 1);
|
||||
assert.equal('The Company', result.org[0]);
|
||||
|
||||
assert.isNotNull(result.jobTitle);
|
||||
assert.lengthOf(result.jobTitle, 1);
|
||||
assert.equal('Title in Company', result.jobTitle[0]);
|
||||
|
||||
assert.isNotNull(result.bday);
|
||||
assert.deepEqual(new Date('1990-01-01'), result.bday);
|
||||
|
||||
assert.isNotNull(result.note);
|
||||
assert.lengthOf(result.note, 1);
|
||||
assert.equal('This is a Note', result.note[0]);
|
||||
|
||||
assert.isNotNull(result.category);
|
||||
assert.lengthOf(result.category, 1);
|
||||
assert.equal('gmail', result.category[0]);
|
||||
|
||||
assert.isNotNull(result.url);
|
||||
assert.lengthOf(result.url, 1);
|
||||
assert.lengthOf(result.url[0].type, 1);
|
||||
assert.equal('source', result.url[0].type[0]);
|
||||
assert.equal('urn:service:gmail:uid:' +
|
||||
'http://www.google.com/m8/feeds/contacts/' +
|
||||
'mepartoconmigo%40gmail.com/base/2fc27a388c2bd974',
|
||||
result.url[0].value);
|
||||
|
||||
});
|
||||
|
||||
test('Parse for saving with invalid birthday date', function() {
|
||||
var contact = subject.gContactToJson(MockGoogleEntryInvalidDate);
|
||||
var result = subject.adaptDataForSaving(contact);
|
||||
|
||||
assert.isNotNull(result);
|
||||
assert.isNotNull(result.givenName);
|
||||
assert.lengthOf(result.givenName, 1);
|
||||
assert.equal(result.givenName[0], 'My');
|
||||
|
||||
assert.isNotNull(result.familyName);
|
||||
assert.lengthOf(result.familyName, 1);
|
||||
assert.equal(result.familyName[0], 'Contact');
|
||||
|
||||
assert.isNotNull(result.email);
|
||||
assert.lengthOf(result.email, 2);
|
||||
assert.equal('juan@palomo.es', result.email[0].value);
|
||||
assert.equal('home', result.email[0].type);
|
||||
assert.equal('workemail@email.com', result.email[1].value);
|
||||
assert.equal('work', result.email[1].type);
|
||||
|
||||
assert.isNotNull(result.adr);
|
||||
assert.lengthOf(result.adr, 1);
|
||||
assert.equal('This is the Street', result.adr[0].streetAddress);
|
||||
assert.equal('The City', result.adr[0].locality);
|
||||
assert.equal('The State', result.adr[0].region);
|
||||
assert.equal('The Zip', result.adr[0].postalCode);
|
||||
assert.equal('The Country', result.adr[0].countryName);
|
||||
|
||||
assert.isNotNull(result.tel);
|
||||
assert.lengthOf(result.tel, 1);
|
||||
assert.equal('+1555111333444222', result.tel[0].value);
|
||||
assert.equal('work', result.tel[0].type);
|
||||
|
||||
assert.isNotNull(result.org);
|
||||
assert.lengthOf(result.org, 1);
|
||||
assert.equal('The Company', result.org[0]);
|
||||
|
||||
assert.isNotNull(result.jobTitle);
|
||||
assert.lengthOf(result.jobTitle, 1);
|
||||
assert.equal('Title in Company', result.jobTitle[0]);
|
||||
|
||||
assert.isUndefined(result.bday);
|
||||
|
||||
assert.isNotNull(result.note);
|
||||
assert.lengthOf(result.note, 1);
|
||||
assert.equal('This is a Note', result.note[0]);
|
||||
|
||||
assert.isNotNull(result.category);
|
||||
assert.lengthOf(result.category, 1);
|
||||
assert.equal('gmail', result.category[0]);
|
||||
|
||||
assert.isNotNull(result.url);
|
||||
assert.lengthOf(result.url, 1);
|
||||
assert.lengthOf(result.url[0].type, 1);
|
||||
assert.equal('source', result.url[0].type[0]);
|
||||
assert.equal('urn:service:gmail:uid:' +
|
||||
'http://www.google.com/m8/feeds/contacts/' +
|
||||
'mepartoconmigo%40gmail.com/base/2fc27a388c2bd974',
|
||||
result.url[0].value);
|
||||
});
|
||||
|
||||
test('Parse for saving with no first name', function() {
|
||||
var contact = subject.gContactToJson(MockGoogleEntryNoFirstName);
|
||||
var result = subject.adaptDataForSaving(contact);
|
||||
|
||||
assert.isNotNull(result);
|
||||
assert.isTrue(!result.givenName);
|
||||
|
||||
assert.isNotNull(result.familyName);
|
||||
assert.lengthOf(result.familyName, 1);
|
||||
assert.equal(result.familyName[0], 'Contact');
|
||||
|
||||
assert.isNotNull(result.email);
|
||||
assert.lengthOf(result.email, 2);
|
||||
assert.equal('juan@palomo.es', result.email[0].value);
|
||||
assert.equal('home', result.email[0].type);
|
||||
assert.equal('workemail@email.com', result.email[1].value);
|
||||
assert.equal('work', result.email[1].type);
|
||||
|
||||
assert.isNotNull(result.adr);
|
||||
assert.lengthOf(result.adr, 1);
|
||||
assert.equal('This is the Street', result.adr[0].streetAddress);
|
||||
assert.equal('The City', result.adr[0].locality);
|
||||
assert.equal('The State', result.adr[0].region);
|
||||
assert.equal('The Zip', result.adr[0].postalCode);
|
||||
assert.equal('The Country', result.adr[0].countryName);
|
||||
|
||||
assert.isNotNull(result.tel);
|
||||
assert.lengthOf(result.tel, 1);
|
||||
assert.equal('+1555111333444222', result.tel[0].value);
|
||||
assert.equal('work', result.tel[0].type);
|
||||
|
||||
assert.isNotNull(result.org);
|
||||
assert.lengthOf(result.org, 1);
|
||||
assert.equal('The Company', result.org[0]);
|
||||
|
||||
assert.isNotNull(result.jobTitle);
|
||||
assert.lengthOf(result.jobTitle, 1);
|
||||
assert.equal('Title in Company', result.jobTitle[0]);
|
||||
|
||||
assert.isNotNull(result.bday);
|
||||
assert.deepEqual(new Date('1990-01-01'), result.bday);
|
||||
|
||||
assert.isNotNull(result.note);
|
||||
assert.lengthOf(result.note, 1);
|
||||
assert.equal('This is a Note', result.note[0]);
|
||||
|
||||
assert.isNotNull(result.category);
|
||||
assert.lengthOf(result.category, 1);
|
||||
assert.equal('gmail', result.category[0]);
|
||||
|
||||
assert.isNotNull(result.url);
|
||||
assert.lengthOf(result.url, 1);
|
||||
assert.lengthOf(result.url[0].type, 1);
|
||||
assert.equal('source', result.url[0].type[0]);
|
||||
assert.equal('urn:service:gmail:uid:' +
|
||||
'http://www.google.com/m8/feeds/contacts/' +
|
||||
'mepartoconmigo%40gmail.com/base/2fc27a388c2bd974',
|
||||
result.url[0].value);
|
||||
});
|
||||
});
|
||||
});
|
||||
46
gaia/apps/communications/contacts/test/unit/import/gmail/mock_contact1.js
Executable file
46
gaia/apps/communications/contacts/test/unit/import/gmail/mock_contact1.js
Executable file
|
|
@ -0,0 +1,46 @@
|
|||
'use strict';
|
||||
|
||||
/* exported MockGoogleEntry, MockGoogleEntryNoName,
|
||||
MockGoogleEntryInvalidDate, MockGoogleEntryNoFirstName */
|
||||
|
||||
// refer to mock_contact1.xml for the entry XML we are getting.
|
||||
|
||||
var MockGoogleEntry = (function MockGoogleEntry() {
|
||||
var req = new XMLHttpRequest();
|
||||
req.open('GET', '/contacts/test/unit/import/gmail/mock_contact1.xml', false);
|
||||
req.send(null);
|
||||
|
||||
var entryBuffer = req.responseText;
|
||||
var parser = new DOMParser();
|
||||
return parser.parseFromString(entryBuffer, 'text/xml');
|
||||
})();
|
||||
|
||||
var MockGoogleEntryNoName = (function MockGoogleEntryNoName() {
|
||||
var req = new XMLHttpRequest();
|
||||
req.open('GET', '/contacts/test/unit/import/gmail/mock_contact2.xml', false);
|
||||
req.send(null);
|
||||
|
||||
var entryBuffer = req.responseText;
|
||||
var parser = new DOMParser();
|
||||
return parser.parseFromString(entryBuffer, 'text/xml');
|
||||
})();
|
||||
|
||||
var MockGoogleEntryInvalidDate = (function MockGoogleEntryWoName() {
|
||||
var req = new XMLHttpRequest();
|
||||
req.open('GET', '/contacts/test/unit/import/gmail/mock_contact3.xml', false);
|
||||
req.send(null);
|
||||
|
||||
var entryBuffer = req.responseText;
|
||||
var parser = new DOMParser();
|
||||
return parser.parseFromString(entryBuffer, 'text/xml');
|
||||
})();
|
||||
|
||||
var MockGoogleEntryNoFirstName = (function MockGoogleEntryNoFirstName() {
|
||||
var req = new XMLHttpRequest();
|
||||
req.open('GET', '/contacts/test/unit/import/gmail/mock_contact4.xml', false);
|
||||
req.send(null);
|
||||
|
||||
var entryBuffer = req.responseText;
|
||||
var parser = new DOMParser();
|
||||
return parser.parseFromString(entryBuffer, 'text/xml');
|
||||
})();
|
||||
41
gaia/apps/communications/contacts/test/unit/import/gmail/mock_contact1.xml
Executable file
41
gaia/apps/communications/contacts/test/unit/import/gmail/mock_contact1.xml
Executable file
|
|
@ -0,0 +1,41 @@
|
|||
<entry xmlns="http://www.w3.org/2005/Atom" xmlns:gContact="http://schemas.google.com/contact/2008" xmlns:batch="http://schemas.google.com/gdata/batch" xmlns:gd="http://schemas.google.com/g/2005" gd:etag=""RXg9fDVSLit7I2A9WhBSGUsPQgU."">
|
||||
<id>http://www.google.com/m8/feeds/contacts/mepartoconmigo%40gmail.com/base/2fc27a388c2bd974</id>
|
||||
<updated>2013-02-27T11:45:14.664Z</updated>
|
||||
<app:edited xmlns:app="http://www.w3.org/2007/app">2013-02-27T11:45:14.664Z</app:edited>
|
||||
<category scheme="http://schemas.google.com/g/2005#kind" term="http://schemas.google.com/contact/2008#contact" />
|
||||
<title>My Shiny Contact</title>
|
||||
<content>This is a Note</content>
|
||||
<link rel="http://schemas.google.com/contacts/2008/rel#photo" type="image/*" href="https://www.google.com/m8/feeds/photos/media/mepartoconmigo%40gmail.com/2fc27a388c2bd974" gd:etag=""LhMgGHAHWit7I2BuPF4FFCt_B0ZEGiYpXwg."" />
|
||||
<link rel="self" type="application/atom+xml" href="https://www.google.com/m8/feeds/contacts/mepartoconmigo%40gmail.com/full/2fc27a388c2bd974" />
|
||||
<link rel="edit" type="application/atom+xml" href="https://www.google.com/m8/feeds/contacts/mepartoconmigo%40gmail.com/full/2fc27a388c2bd974" />
|
||||
<gd:name>
|
||||
<gd:fullName>My Shiny Contact</gd:fullName>
|
||||
<gd:givenName>My</gd:givenName>
|
||||
<gd:additionalName>Shiny</gd:additionalName>
|
||||
<gd:familyName>Contact</gd:familyName>
|
||||
</gd:name>
|
||||
<gContact:birthday when="1990-01-01" />
|
||||
<gd:organization rel="http://schemas.google.com/g/2005#other">
|
||||
<gd:orgName>The Company</gd:orgName>
|
||||
<gd:orgTitle>Title in Company</gd:orgTitle>
|
||||
</gd:organization>
|
||||
<gd:email rel="http://schemas.google.com/g/2005#home" address="juan@palomo.es" primary="true" />
|
||||
<gd:email rel="http://schemas.google.com/g/2005#work" address="workemail@email.com" />
|
||||
<gd:im address="instantmessaging" protocol="http://schemas.google.com/g/2005#GOOGLE_TALK" rel="http://schemas.google.com/g/2005#other" />
|
||||
<gd:phoneNumber rel="http://schemas.google.com/g/2005#work">+1555111333444222</gd:phoneNumber>
|
||||
<gd:structuredPostalAddress rel="http://schemas.google.com/g/2005#home">
|
||||
<gd:formattedAddress>This is the Street
|
||||
The PO Box
|
||||
The Nieghborhood, The City, The State The Zip
|
||||
The Country</gd:formattedAddress>
|
||||
<gd:street>This is the Street</gd:street>
|
||||
<gd:neighborhood>The Nieghborhood</gd:neighborhood>
|
||||
<gd:pobox>The PO Box</gd:pobox>
|
||||
<gd:postcode>The Zip</gd:postcode>
|
||||
<gd:city>The City</gd:city>
|
||||
<gd:region>The State</gd:region>
|
||||
<gd:country>The Country</gd:country>
|
||||
</gd:structuredPostalAddress>
|
||||
<gContact:website href="http://www.myurl.com" rel="profile" />
|
||||
<gContact:groupMembershipInfo deleted="false" href="http://www.google.com/m8/feeds/groups/mepartoconmigo%40gmail.com/base/6" />
|
||||
</entry>
|
||||
35
gaia/apps/communications/contacts/test/unit/import/gmail/mock_contact2.xml
Executable file
35
gaia/apps/communications/contacts/test/unit/import/gmail/mock_contact2.xml
Executable file
|
|
@ -0,0 +1,35 @@
|
|||
<entry xmlns="http://www.w3.org/2005/Atom" xmlns:gContact="http://schemas.google.com/contact/2008" xmlns:batch="http://schemas.google.com/gdata/batch" xmlns:gd="http://schemas.google.com/g/2005" gd:etag=""RXg9fDVSLit7I2A9WhBSGUsPQgU."">
|
||||
<id>http://www.google.com/m8/feeds/contacts/mepartoconmigo%40gmail.com/base/2fc27a388c2bd974</id>
|
||||
<updated>2013-02-27T11:45:14.664Z</updated>
|
||||
<app:edited xmlns:app="http://www.w3.org/2007/app">2013-02-27T11:45:14.664Z</app:edited>
|
||||
<category scheme="http://schemas.google.com/g/2005#kind" term="http://schemas.google.com/contact/2008#contact" />
|
||||
<title>My Shiny Contact</title>
|
||||
<content>This is a Note</content>
|
||||
<link rel="http://schemas.google.com/contacts/2008/rel#photo" type="image/*" href="https://www.google.com/m8/feeds/photos/media/mepartoconmigo%40gmail.com/2fc27a388c2bd974" gd:etag=""LhMgGHAHWit7I2BuPF4FFCt_B0ZEGiYpXwg."" />
|
||||
<link rel="self" type="application/atom+xml" href="https://www.google.com/m8/feeds/contacts/mepartoconmigo%40gmail.com/full/2fc27a388c2bd974" />
|
||||
<link rel="edit" type="application/atom+xml" href="https://www.google.com/m8/feeds/contacts/mepartoconmigo%40gmail.com/full/2fc27a388c2bd974" />
|
||||
<gContact:birthday when="1990-01-01" />
|
||||
<gd:organization rel="http://schemas.google.com/g/2005#other">
|
||||
<gd:orgName>The Company</gd:orgName>
|
||||
<gd:orgTitle>Title in Company</gd:orgTitle>
|
||||
</gd:organization>
|
||||
<gd:email rel="http://schemas.google.com/g/2005#home" address="juan@palomo.es" primary="true" />
|
||||
<gd:email rel="http://schemas.google.com/g/2005#work" address="workemail@email.com" />
|
||||
<gd:im address="instantmessaging" protocol="http://schemas.google.com/g/2005#GOOGLE_TALK" rel="http://schemas.google.com/g/2005#other" />
|
||||
<gd:phoneNumber rel="http://schemas.google.com/g/2005#work">+1555111333444222</gd:phoneNumber>
|
||||
<gd:structuredPostalAddress rel="http://schemas.google.com/g/2005#home">
|
||||
<gd:formattedAddress>This is the Street
|
||||
The PO Box
|
||||
The Nieghborhood, The City, The State The Zip
|
||||
The Country</gd:formattedAddress>
|
||||
<gd:street>This is the Street</gd:street>
|
||||
<gd:neighborhood>The Nieghborhood</gd:neighborhood>
|
||||
<gd:pobox>The PO Box</gd:pobox>
|
||||
<gd:postcode>The Zip</gd:postcode>
|
||||
<gd:city>The City</gd:city>
|
||||
<gd:region>The State</gd:region>
|
||||
<gd:country>The Country</gd:country>
|
||||
</gd:structuredPostalAddress>
|
||||
<gContact:website href="http://www.myurl.com" rel="profile" />
|
||||
<gContact:groupMembershipInfo deleted="false" href="http://www.google.com/m8/feeds/groups/mepartoconmigo%40gmail.com/base/6" />
|
||||
</entry>
|
||||
41
gaia/apps/communications/contacts/test/unit/import/gmail/mock_contact3.xml
Executable file
41
gaia/apps/communications/contacts/test/unit/import/gmail/mock_contact3.xml
Executable file
|
|
@ -0,0 +1,41 @@
|
|||
<entry xmlns="http://www.w3.org/2005/Atom" xmlns:gContact="http://schemas.google.com/contact/2008" xmlns:batch="http://schemas.google.com/gdata/batch" xmlns:gd="http://schemas.google.com/g/2005" gd:etag=""RXg9fDVSLit7I2A9WhBSGUsPQgU."">
|
||||
<id>http://www.google.com/m8/feeds/contacts/mepartoconmigo%40gmail.com/base/2fc27a388c2bd974</id>
|
||||
<updated>2013-02-27T11:45:14.664Z</updated>
|
||||
<app:edited xmlns:app="http://www.w3.org/2007/app">2013-02-27T11:45:14.664Z</app:edited>
|
||||
<category scheme="http://schemas.google.com/g/2005#kind" term="http://schemas.google.com/contact/2008#contact" />
|
||||
<title>My Shiny Contact</title>
|
||||
<content>This is a Note</content>
|
||||
<link rel="http://schemas.google.com/contacts/2008/rel#photo" type="image/*" href="https://www.google.com/m8/feeds/photos/media/mepartoconmigo%40gmail.com/2fc27a388c2bd974" gd:etag=""LhMgGHAHWit7I2BuPF4FFCt_B0ZEGiYpXwg."" />
|
||||
<link rel="self" type="application/atom+xml" href="https://www.google.com/m8/feeds/contacts/mepartoconmigo%40gmail.com/full/2fc27a388c2bd974" />
|
||||
<link rel="edit" type="application/atom+xml" href="https://www.google.com/m8/feeds/contacts/mepartoconmigo%40gmail.com/full/2fc27a388c2bd974" />
|
||||
<gd:name>
|
||||
<gd:fullName>My Shiny Contact</gd:fullName>
|
||||
<gd:givenName>My</gd:givenName>
|
||||
<gd:additionalName>Shiny</gd:additionalName>
|
||||
<gd:familyName>Contact</gd:familyName>
|
||||
</gd:name>
|
||||
<gContact:birthday when="June 24" />
|
||||
<gd:organization rel="http://schemas.google.com/g/2005#other">
|
||||
<gd:orgName>The Company</gd:orgName>
|
||||
<gd:orgTitle>Title in Company</gd:orgTitle>
|
||||
</gd:organization>
|
||||
<gd:email rel="http://schemas.google.com/g/2005#home" address="juan@palomo.es" primary="true" />
|
||||
<gd:email rel="http://schemas.google.com/g/2005#work" address="workemail@email.com" />
|
||||
<gd:im address="instantmessaging" protocol="http://schemas.google.com/g/2005#GOOGLE_TALK" rel="http://schemas.google.com/g/2005#other" />
|
||||
<gd:phoneNumber rel="http://schemas.google.com/g/2005#work">+1555111333444222</gd:phoneNumber>
|
||||
<gd:structuredPostalAddress rel="http://schemas.google.com/g/2005#home">
|
||||
<gd:formattedAddress>This is the Street
|
||||
The PO Box
|
||||
The Nieghborhood, The City, The State The Zip
|
||||
The Country</gd:formattedAddress>
|
||||
<gd:street>This is the Street</gd:street>
|
||||
<gd:neighborhood>The Nieghborhood</gd:neighborhood>
|
||||
<gd:pobox>The PO Box</gd:pobox>
|
||||
<gd:postcode>The Zip</gd:postcode>
|
||||
<gd:city>The City</gd:city>
|
||||
<gd:region>The State</gd:region>
|
||||
<gd:country>The Country</gd:country>
|
||||
</gd:structuredPostalAddress>
|
||||
<gContact:website href="http://www.myurl.com" rel="profile" />
|
||||
<gContact:groupMembershipInfo deleted="false" href="http://www.google.com/m8/feeds/groups/mepartoconmigo%40gmail.com/base/6" />
|
||||
</entry>
|
||||
41
gaia/apps/communications/contacts/test/unit/import/gmail/mock_contact4.xml
Executable file
41
gaia/apps/communications/contacts/test/unit/import/gmail/mock_contact4.xml
Executable file
|
|
@ -0,0 +1,41 @@
|
|||
<entry xmlns="http://www.w3.org/2005/Atom" xmlns:gContact="http://schemas.google.com/contact/2008" xmlns:batch="http://schemas.google.com/gdata/batch" xmlns:gd="http://schemas.google.com/g/2005" gd:etag=""RXg9fDVSLit7I2A9WhBSGUsPQgU."">
|
||||
<id>http://www.google.com/m8/feeds/contacts/mepartoconmigo%40gmail.com/base/2fc27a388c2bd974</id>
|
||||
<updated>2013-02-27T11:45:14.664Z</updated>
|
||||
<app:edited xmlns:app="http://www.w3.org/2007/app">2013-02-27T11:45:14.664Z</app:edited>
|
||||
<category scheme="http://schemas.google.com/g/2005#kind" term="http://schemas.google.com/contact/2008#contact" />
|
||||
<title>My Shiny Contact</title>
|
||||
<content>This is a Note</content>
|
||||
<link rel="http://schemas.google.com/contacts/2008/rel#photo" type="image/*" href="https://www.google.com/m8/feeds/photos/media/mepartoconmigo%40gmail.com/2fc27a388c2bd974" gd:etag=""LhMgGHAHWit7I2BuPF4FFCt_B0ZEGiYpXwg."" />
|
||||
<link rel="self" type="application/atom+xml" href="https://www.google.com/m8/feeds/contacts/mepartoconmigo%40gmail.com/full/2fc27a388c2bd974" />
|
||||
<link rel="edit" type="application/atom+xml" href="https://www.google.com/m8/feeds/contacts/mepartoconmigo%40gmail.com/full/2fc27a388c2bd974" />
|
||||
<gd:name>
|
||||
<gd:fullName>Contact</gd:fullName>
|
||||
<gd:givenName></gd:givenName>
|
||||
<gd:additionalName></gd:additionalName>
|
||||
<gd:familyName>Contact</gd:familyName>
|
||||
</gd:name>
|
||||
<gContact:birthday when="1990-01-01" />
|
||||
<gd:organization rel="http://schemas.google.com/g/2005#other">
|
||||
<gd:orgName>The Company</gd:orgName>
|
||||
<gd:orgTitle>Title in Company</gd:orgTitle>
|
||||
</gd:organization>
|
||||
<gd:email rel="http://schemas.google.com/g/2005#home" address="juan@palomo.es" primary="true" />
|
||||
<gd:email rel="http://schemas.google.com/g/2005#work" address="workemail@email.com" />
|
||||
<gd:im address="instantmessaging" protocol="http://schemas.google.com/g/2005#GOOGLE_TALK" rel="http://schemas.google.com/g/2005#other" />
|
||||
<gd:phoneNumber rel="http://schemas.google.com/g/2005#work">+1555111333444222</gd:phoneNumber>
|
||||
<gd:structuredPostalAddress rel="http://schemas.google.com/g/2005#home">
|
||||
<gd:formattedAddress>This is the Street
|
||||
The PO Box
|
||||
The Nieghborhood, The City, The State The Zip
|
||||
The Country</gd:formattedAddress>
|
||||
<gd:street>This is the Street</gd:street>
|
||||
<gd:neighborhood>The Nieghborhood</gd:neighborhood>
|
||||
<gd:pobox>The PO Box</gd:pobox>
|
||||
<gd:postcode>The Zip</gd:postcode>
|
||||
<gd:city>The City</gd:city>
|
||||
<gd:region>The State</gd:region>
|
||||
<gd:country>The Country</gd:country>
|
||||
</gd:structuredPostalAddress>
|
||||
<gContact:website href="http://www.myurl.com" rel="profile" />
|
||||
<gContact:groupMembershipInfo deleted="false" href="http://www.google.com/m8/feeds/groups/mepartoconmigo%40gmail.com/base/6" />
|
||||
</entry>
|
||||
23
gaia/apps/communications/contacts/test/unit/import/gmail/mock_groups.js
Executable file
23
gaia/apps/communications/contacts/test/unit/import/gmail/mock_groups.js
Executable file
|
|
@ -0,0 +1,23 @@
|
|||
'use strict';
|
||||
|
||||
/* exported MockGoogleGroups */
|
||||
|
||||
/*
|
||||
|
||||
This mock represents the following google contacts response
|
||||
for groups of contacts. See mock_groups.xml.
|
||||
|
||||
*/
|
||||
|
||||
var MockGoogleGroups = (function MockGoogleGroups() {
|
||||
var req = new XMLHttpRequest();
|
||||
req.open('GET', '/contacts/test/unit/import/gmail/mock_groups.xml', false);
|
||||
req.send(null);
|
||||
|
||||
var entryBuffer = req.responseText;
|
||||
var parser = new DOMParser();
|
||||
var listing = parser.parseFromString(entryBuffer, 'text/xml');
|
||||
|
||||
return listing;
|
||||
|
||||
})();
|
||||
1
gaia/apps/communications/contacts/test/unit/import/gmail/mock_groups.xml
Executable file
1
gaia/apps/communications/contacts/test/unit/import/gmail/mock_groups.xml
Executable file
|
|
@ -0,0 +1 @@
|
|||
<?xml version='1.0' encoding='UTF-8'?><feed xmlns='http://www.w3.org/2005/Atom' xmlns:openSearch='http://a9.com/-/spec/opensearch/1.1/' xmlns:gContact='http://schemas.google.com/contact/2008' xmlns:batch='http://schemas.google.com/gdata/batch' xmlns:gd='http://schemas.google.com/g/2005' gd:etag='W/"DkUFSHgzeSt7I2A9WhBQGEs."'><id>mepartoconmigo@gmail.com</id><updated>2013-03-21T11:10:19.681Z</updated><category scheme='http://schemas.google.com/g/2005#kind' term='http://schemas.google.com/contact/2008#group'/><title>arcturus's Contact Groups</title><link rel='alternate' type='text/html' href='http://www.google.com/'/><link rel='http://schemas.google.com/g/2005#feed' type='application/atom+xml' href='https://www.google.com/m8/feeds/groups/mepartoconmigo%40gmail.com/full'/><link rel='http://schemas.google.com/g/2005#post' type='application/atom+xml' href='https://www.google.com/m8/feeds/groups/mepartoconmigo%40gmail.com/full'/><link rel='http://schemas.google.com/g/2005#batch' type='application/atom+xml' href='https://www.google.com/m8/feeds/groups/mepartoconmigo%40gmail.com/full/batch'/><link rel='self' type='application/atom+xml' href='https://www.google.com/m8/feeds/groups/mepartoconmigo%40gmail.com/full?max-results=25'/><author><name>arcturus</name><email>mepartoconmigo@gmail.com</email></author><generator version='1.0' uri='http://www.google.com/m8/feeds'>Contacts</generator><openSearch:totalResults>5</openSearch:totalResults><openSearch:startIndex>1</openSearch:startIndex><openSearch:itemsPerPage>25</openSearch:itemsPerPage><entry gd:etag='"YDwreyM."'><id>https://www.google.com/m8/feeds/groups/mepartoconmigo%40gmail.com/base/6</id><updated>1970-01-01T00:00:00.000Z</updated><category scheme='http://schemas.google.com/g/2005#kind' term='http://schemas.google.com/contact/2008#group'/><title>System Group: My Contacts</title><content>System Group: My Contacts</content><link rel='self' type='application/atom+xml' href='https://www.google.com/m8/feeds/groups/mepartoconmigo%40gmail.com/full/6'/><gContact:systemGroup id='Contacts'/></entry><entry gd:etag='"YDwreyM."'><id>https://www.google.com/m8/feeds/groups/mepartoconmigo%40gmail.com/base/d</id><updated>1970-01-01T00:00:00.000Z</updated><category scheme='http://schemas.google.com/g/2005#kind' term='http://schemas.google.com/contact/2008#group'/><title>System Group: Friends</title><content>System Group: Friends</content><link rel='self' type='application/atom+xml' href='https://www.google.com/m8/feeds/groups/mepartoconmigo%40gmail.com/full/d'/><gContact:systemGroup id='Friends'/></entry></feed>
|
||||
23
gaia/apps/communications/contacts/test/unit/import/gmail/mock_listing.js
Executable file
23
gaia/apps/communications/contacts/test/unit/import/gmail/mock_listing.js
Executable file
|
|
@ -0,0 +1,23 @@
|
|||
'use strict';
|
||||
|
||||
/* exported MockGoogleListing */
|
||||
|
||||
/*
|
||||
|
||||
This mock represents the following google contacts response
|
||||
for a list of contacts. See mock_listing.xml.
|
||||
|
||||
*/
|
||||
|
||||
var MockGoogleListing = (function MockGoogleListing() {
|
||||
var req = new XMLHttpRequest();
|
||||
req.open('GET', '/contacts/test/unit/import/gmail/mock_listing.xml', false);
|
||||
req.send(null);
|
||||
|
||||
var entryBuffer = req.responseText;
|
||||
var parser = new DOMParser();
|
||||
var listing = parser.parseFromString(entryBuffer, 'text/xml');
|
||||
|
||||
return listing;
|
||||
|
||||
})();
|
||||
50
gaia/apps/communications/contacts/test/unit/import/gmail/mock_listing.xml
Executable file
50
gaia/apps/communications/contacts/test/unit/import/gmail/mock_listing.xml
Executable file
|
|
@ -0,0 +1,50 @@
|
|||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<feed xmlns="http://www.w3.org/2005/Atom" xmlns:openSearch="http://a9.com/-/spec/opensearch/1.1/" xmlns:gContact="http://schemas.google.com/contact/2008" xmlns:batch="http://schemas.google.com/gdata/batch" xmlns:gd="http://schemas.google.com/g/2005" gd:etag="W/"DkENRHY6fyt7I2A9WhBSGUU."">
|
||||
<id>someemail@gmail.com</id>
|
||||
<updated>2013-02-27T17:04:55.817Z</updated>
|
||||
<category scheme="http://schemas.google.com/g/2005#kind" term="http://schemas.google.com/contact/2008#contact" />
|
||||
<title>someone's Contacts</title>
|
||||
<link rel="alternate" type="text/html" href="http://www.google.com/" />
|
||||
<link rel="http://schemas.google.com/g/2005#feed" type="application/atom+xml" href="https://www.google.com/m8/feeds/contacts/somegoogleemail%40gmail.com/full" />
|
||||
<link rel="http://schemas.google.com/g/2005#post" type="application/atom+xml" href="https://www.google.com/m8/feeds/contacts/somegoogleemail%40gmail.com/full" />
|
||||
<link rel="http://schemas.google.com/g/2005#batch" type="application/atom+xml" href="https://www.google.com/m8/feeds/contacts/somegoogleemail%40gmail.com/full/batch" />
|
||||
<link rel="self" type="application/atom+xml" href="https://www.google.com/m8/feeds/contacts/somegoogleemail%40gmail.com/full?max-results=25" />
|
||||
<link rel="next" type="application/atom+xml" href="https://www.google.com/m8/feeds/contacts/somegoogleemail%40gmail.com/full?start-index=26&max-results=25" />
|
||||
<author>
|
||||
<name>someone</name>
|
||||
<email>someemail@gmail.com</email>
|
||||
</author>
|
||||
<generator version="1.0" uri="http://www.google.com/m8/feeds">Contacts</generator>
|
||||
<openSearch:totalResults>2</openSearch:totalResults>
|
||||
<openSearch:startIndex>1</openSearch:startIndex>
|
||||
<openSearch:itemsPerPage>25</openSearch:itemsPerPage>
|
||||
<entry gd:etag=""RXs7ejVSLit7I2A9WxNRFE4IQAY."">
|
||||
<id>http://www.google.com/m8/feeds/contacts/somegoogleemail%40gmail.com/base/1</id>
|
||||
<updated>2009-09-08T17:58:44.502Z</updated>
|
||||
<app:edited xmlns:app="http://www.w3.org/2007/app">2009-09-08T17:58:44.502Z</app:edited>
|
||||
<category scheme="http://schemas.google.com/g/2005#kind" term="http://schemas.google.com/contact/2008#contact" />
|
||||
<title />
|
||||
<link rel="http://schemas.google.com/contacts/2008/rel#photo" type="image/*" href="https://www.google.com/m8/feeds/photos/media/somegoogleemail%40gmail.com/1" />
|
||||
<link rel="self" type="application/atom+xml" href="https://www.google.com/m8/feeds/contacts/somegoogleemail%40gmail.com/full/1" />
|
||||
<link rel="edit" type="application/atom+xml" href="https://www.google.com/m8/feeds/contacts/somegoogleemail%40gmail.com/full/1" />
|
||||
<gd:email rel="http://schemas.google.com/g/2005#other" address="another@gmail.com" primary="true" />
|
||||
<gd:email rel="http://schemas.google.com/g/2005#work" address="work@gmail.com" primary="true" />
|
||||
</entry>
|
||||
<entry gd:etag=""RHY4fTNRKyt7I2A9WhJbEkQDQgY."">
|
||||
<id>http://www.google.com/m8/feeds/contacts/somegoogleemail%40gmail.com/base/2</id>
|
||||
<updated>2012-09-22T07:38:45.835Z</updated>
|
||||
<app:edited xmlns:app="http://www.w3.org/2007/app">2012-09-22T07:38:45.835Z</app:edited>
|
||||
<category scheme="http://schemas.google.com/g/2005#kind" term="http://schemas.google.com/contact/2008#contact" />
|
||||
<title>John Doe</title>
|
||||
<gd:name>
|
||||
<gd:fullName>John Doe</gd:fullName>
|
||||
<gd:givenName>John</gd:givenName>
|
||||
<gd:additionalName></gd:additionalName>
|
||||
<gd:familyName>Doe</gd:familyName>
|
||||
</gd:name>
|
||||
<gContact:birthday when="1990-01-01" />
|
||||
<link rel="http://schemas.google.com/contacts/2008/rel#photo" type="image/*" href="https://www.google.com/m8/feeds/photos/media/somegoogleemail%40gmail.com/2" gd:etag=""UR5VJUMvbCt7I2BcOB46EypYQV4BJGx4YVc."" />
|
||||
<link rel="self" type="application/atom+xml" href="https://www.google.com/m8/feeds/contacts/somegoogleemail%40gmail.com/full/2" />
|
||||
<link rel="edit" type="application/atom+xml" href="https://www.google.com/m8/feeds/contacts/somegoogleemail%40gmail.com/full/2" />
|
||||
</entry>
|
||||
</feed>
|
||||
229
gaia/apps/communications/contacts/test/unit/import/import_contacts_test.js
Executable file
229
gaia/apps/communications/contacts/test/unit/import/import_contacts_test.js
Executable file
|
|
@ -0,0 +1,229 @@
|
|||
'use strict';
|
||||
|
||||
/* global contacts */
|
||||
/* global LazyLoader */
|
||||
/* global MyLocks */
|
||||
/* global MockWakeLock */
|
||||
/* global MockasyncStorage */
|
||||
/* global MockMozL10n */
|
||||
/* global MockSdCard */
|
||||
/* global MocksHelper */
|
||||
/* global MockUtils */
|
||||
/* global MockContactsIndexHtml */
|
||||
/* global MockNavigatorMozMobileConnections */
|
||||
/* global MockSimContactsImporter */
|
||||
/* global MockVCFReader */
|
||||
/* global MockMozContacts */
|
||||
/* global MockAdvancedTelemetryHelper */
|
||||
|
||||
require('/shared/js/lazy_loader.js');
|
||||
require('/shared/test/unit/mocks/mock_navigator_moz_mobile_connections.js');
|
||||
require('/shared/test/unit/mocks/mock_sdcard.js');
|
||||
require('/shared/test/unit/mocks/mock_vcard_parser.js');
|
||||
require('/shared/test/unit/mocks/mock_advanced_telemetry_helper.js');
|
||||
|
||||
requireApp('communications/contacts/services/contacts.js');
|
||||
requireApp('communications/contacts/test/unit/mock_contacts_index.html.js');
|
||||
|
||||
requireApp('communications/contacts/test/unit/mock_service_extensions.js');
|
||||
requireApp('communications/contacts/test/unit/mock_navigation.js');
|
||||
requireApp('communications/contacts/test/unit/mock_contacts.js');
|
||||
requireApp('communications/contacts/test/unit/mock_asyncstorage.js');
|
||||
requireApp('communications/contacts/test/unit/mock_fb.js');
|
||||
requireApp('communications/contacts/test/unit/mock_wakelock.js');
|
||||
requireApp('communications/contacts/test/unit/mock_l10n.js');
|
||||
requireApp('communications/contacts/test/unit/mock_event_listeners.js');
|
||||
requireApp('communications/contacts/test/unit/mock_sim_importer.js');
|
||||
|
||||
require('/shared/test/unit/mocks/mock_confirm_dialog.js');
|
||||
require('/shared/test/unit/mocks/mock_mozContacts.js');
|
||||
|
||||
requireApp('communications/contacts/js/views/settings.js');
|
||||
requireApp('communications/contacts/js/utilities/icc_handler.js');
|
||||
requireApp('communications/contacts/js/utilities/sim_dom_generator.js');
|
||||
requireApp('communications/contacts/js/navigation.js');
|
||||
|
||||
if (!window._) { window._ = null; }
|
||||
if (!window.utils) { window.utils = null; }
|
||||
if (!navigator.mozMobileConnections) { navigator.mozMobileConnections = null; }
|
||||
|
||||
var mocksHelperForContactImport = new MocksHelper([
|
||||
'ExtServices', 'Contacts', 'fb', 'asyncStorage', 'ConfirmDialog',
|
||||
'VCFReader', 'WakeLock', 'SimContactsImporter'
|
||||
]);
|
||||
mocksHelperForContactImport.init();
|
||||
|
||||
suite('Import contacts >', function() {
|
||||
var mocksHelper = mocksHelperForContactImport;
|
||||
|
||||
var real_,
|
||||
realUtils,
|
||||
realWakeLock,
|
||||
realMozMobileConnections,
|
||||
realMozContacts,
|
||||
realATH;
|
||||
|
||||
setup(function() {
|
||||
this.sinon.spy(window.utils.overlay, 'showMenu');
|
||||
});
|
||||
|
||||
teardown(function() {
|
||||
MockasyncStorage.clear();
|
||||
});
|
||||
|
||||
suiteSetup(function(done) {
|
||||
mocksHelper.suiteSetup();
|
||||
|
||||
realMozMobileConnections = navigator.mozMobileConnections;
|
||||
navigator.mozMobileConnections = MockNavigatorMozMobileConnections;
|
||||
|
||||
realWakeLock = navigator.requestWakeLock;
|
||||
navigator.requestWakeLock = MockWakeLock;
|
||||
|
||||
realMozContacts = navigator.mozContacts;
|
||||
navigator.mozContacts = MockMozContacts;
|
||||
|
||||
real_ = window._;
|
||||
window._ = MockMozL10n.get;
|
||||
|
||||
realUtils = window.utils;
|
||||
window.utils = MockUtils;
|
||||
window.utils.overlay = {
|
||||
show: function() {},
|
||||
showMenu: function() {}
|
||||
};
|
||||
|
||||
window.utils.status = {
|
||||
show: function() {}
|
||||
};
|
||||
|
||||
window.utils.misc = {
|
||||
getTimestamp: function(element, cb) {
|
||||
cb();
|
||||
},
|
||||
setTimestamp: function(time, cb) {
|
||||
cb();
|
||||
}
|
||||
};
|
||||
window.utils.sdcard = MockSdCard;
|
||||
window.utils.time = {
|
||||
pretty: function() {}
|
||||
};
|
||||
|
||||
document.body.innerHTML = MockContactsIndexHtml;
|
||||
contacts.Settings.init();
|
||||
|
||||
LazyLoader.load(['/shared/js/contacts/import/utilities/status.js',
|
||||
'/shared/js/advanced_telemetry_helper.js'], () => {
|
||||
LazyLoader.load('/contacts/js/utilities/telemetry.js', () => {
|
||||
realATH = window.AdvancedTelemetryHelper;
|
||||
window.AdvancedTelemetryHelper = MockAdvancedTelemetryHelper;
|
||||
done();
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
suiteTeardown(function() {
|
||||
navigator.mozMobileConnections = realMozMobileConnections;
|
||||
navigator.requestWakeLock = realWakeLock;
|
||||
navigator.mozContacts = realMozContacts;
|
||||
|
||||
window.utils = realUtils;
|
||||
window._ = real_;
|
||||
window.AdvancedTelemetryHelper = realATH;
|
||||
|
||||
mocksHelper.suiteTeardown();
|
||||
});
|
||||
|
||||
setup(function() {
|
||||
this.sinon.spy(window.utils.status, 'show');
|
||||
});
|
||||
|
||||
test('SD Import went well', function(done) {
|
||||
sinon.spy(window, 'AdvancedTelemetryHelper');
|
||||
contacts.Settings.importFromSDCard(function onImported() {
|
||||
assert.isTrue(window.utils.overlay.showMenu.called);
|
||||
assert.equal(window.utils.status.show.getCall(0).args.length, 2);
|
||||
assert.equal(false, MyLocks.cpu);
|
||||
window.TelemetryReady.then(() => {
|
||||
assert.isTrue(window.AdvancedTelemetryHelper.calledWithNew());
|
||||
var counter = window.AdvancedTelemetryHelper.getCall(0).returnValue;
|
||||
assert.equal(counter.name, 'telemetry_gaia_contacts_import_sd');
|
||||
assert.equal(window.AdvancedTelemetryHelper.HISTOGRAM_COUNT,
|
||||
counter.type);
|
||||
window.AdvancedTelemetryHelper.restore();
|
||||
done();
|
||||
});
|
||||
|
||||
});
|
||||
});
|
||||
|
||||
test('SD Import went well with duplicates found', function(done) {
|
||||
MockVCFReader.prototype.numDuplicated = 2;
|
||||
|
||||
contacts.Settings.importFromSDCard(function onImported() {
|
||||
assert.isTrue(window.utils.overlay.showMenu.called);
|
||||
|
||||
assert.isTrue(window.utils.status.show.called);
|
||||
assert.isTrue(window.utils.status.show.getCall(0).args[0] !== null);
|
||||
assert.isTrue(window.utils.status.show.getCall(0).args[1] !== null);
|
||||
|
||||
assert.equal(false, MyLocks.cpu);
|
||||
|
||||
delete MockVCFReader.prototype.numDuplicated;
|
||||
done();
|
||||
});
|
||||
});
|
||||
|
||||
test('SD Import with error cause no files to import', function(done) {
|
||||
// Simulate not finding any files
|
||||
MockSdCard.failOnRetrieveFiles = true;
|
||||
contacts.Settings.importFromSDCard(function onImported() {
|
||||
assert.isTrue(window.utils.overlay.showMenu.called);
|
||||
assert.isFalse(window.utils.status.show.called);
|
||||
assert.equal(false, MyLocks.cpu);
|
||||
// Restore the mock
|
||||
MockSdCard.failOnRetrieveFiles = false;
|
||||
done();
|
||||
});
|
||||
});
|
||||
|
||||
suite('SIM Import ', function() {
|
||||
suiteSetup(function() {
|
||||
contacts.Settings.init();
|
||||
});
|
||||
|
||||
test('If there are no Contacts to be imported a message appears',
|
||||
function(done) {
|
||||
MockSimContactsImporter.prototype.numImportedContacts = 0;
|
||||
MockSimContactsImporter.prototype.numDuplicated = 0;
|
||||
MockSimContactsImporter.prototype.number = 0;
|
||||
|
||||
contacts.Settings.importFromSIMCard('1234', function onImported() {
|
||||
assert.isTrue(window.utils.status.show.called);
|
||||
assert.isTrue(window.utils.status.show.getCall(0).args[0] !== null);
|
||||
assert.isTrue(window.utils.status.show.getCall(0).args[1] === null);
|
||||
|
||||
delete MockSimContactsImporter.prototype.numImportedContacts;
|
||||
done();
|
||||
});
|
||||
});
|
||||
|
||||
test('SIM Import went well with duplicates found', function(done) {
|
||||
MockSimContactsImporter.prototype.numDuplicated = 1;
|
||||
MockSimContactsImporter.prototype.numImportedContacts = 3;
|
||||
MockSimContactsImporter.prototype.number = 3;
|
||||
|
||||
contacts.Settings.importFromSIMCard('1234', function onImported() {
|
||||
assert.isTrue(window.utils.status.show.called);
|
||||
assert.isTrue(window.utils.status.show.getCall(0).args[0] !== null);
|
||||
assert.isTrue(window.utils.status.show.getCall(0).args[1] !== null);
|
||||
|
||||
assert.equal(false, MyLocks.cpu);
|
||||
|
||||
delete MockSimContactsImporter.prototype.numDuplicates;
|
||||
done();
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
156
gaia/apps/communications/contacts/test/unit/import/import_sim_contacts_test.js
Executable file
156
gaia/apps/communications/contacts/test/unit/import/import_sim_contacts_test.js
Executable file
|
|
@ -0,0 +1,156 @@
|
|||
'use strict';
|
||||
/* global SimContactsImporter */
|
||||
/* global MockMozContacts, MocksHelper, MockIccManager */
|
||||
|
||||
require('/shared/test/unit/mocks/mock_mozContacts.js');
|
||||
require('/shared/test/unit/mocks/mock_lazy_loader.js');
|
||||
require('/shared/test/unit/mocks/mock_iccmanager.js');
|
||||
require('/shared/js/text_normalizer.js');
|
||||
require('/shared/js/simple_phone_matcher.js');
|
||||
require('/shared/js/contacts/contacts_merger.js');
|
||||
require('/shared/js/contacts/utilities/image_thumbnail.js');
|
||||
require('/shared/js/contacts/merger_adapter.js');
|
||||
require('/shared/js/contacts/import/utilities/misc.js');
|
||||
require('/shared/js/contacts/import/utilities/import_sim_contacts.js');
|
||||
require('/shared/test/unit/mocks/mock_moz_contact.js');
|
||||
|
||||
var mocksHelperForImporter = new MocksHelper([
|
||||
'LazyLoader',
|
||||
'mozContact'
|
||||
]).init();
|
||||
|
||||
suite('Sim import >', function() {
|
||||
mocksHelperForImporter.attachTestHelpers();
|
||||
var realMozContacts,
|
||||
iccManager,
|
||||
icc,
|
||||
importer;
|
||||
|
||||
// List of contacts result for the method icc.readContacts
|
||||
var mRequestResult = {
|
||||
adn: [
|
||||
{id : 'a572247', tel : [{value : '900900900'}], name : ['Peter']},
|
||||
{id : 'b572248', tel : [{value : '192192192'}], name : ['Jason']}
|
||||
],
|
||||
sdn: [
|
||||
{id : '105721', tel : [{value : '190'}], name : ['Police'] },
|
||||
{id : '105722', tel : [{value : '192'}], name : ['Ambulance'] },
|
||||
{id : '105723', tel : [{value : '193'}], name : ['Fire'] }
|
||||
]
|
||||
};
|
||||
|
||||
suiteSetup(function(done) {
|
||||
realMozContacts = navigator.mozContacts;
|
||||
navigator.mozContacts = MockMozContacts;
|
||||
require('/shared/js/contacts/contacts_matcher.js', done);
|
||||
});
|
||||
|
||||
setup(function() {
|
||||
MockMozContacts.contacts = [];
|
||||
iccManager = new MockIccManager();
|
||||
icc = iccManager.getIccById('123');
|
||||
importer = new SimContactsImporter(icc);
|
||||
});
|
||||
|
||||
suiteTeardown(function() {
|
||||
navigator.mozContacts = realMozContacts;
|
||||
});
|
||||
|
||||
test('Calling with adn and sdn contacts', function(done) {
|
||||
iccManager.adnContacts = mRequestResult.adn;
|
||||
iccManager.sdnContacts = mRequestResult.sdn;
|
||||
iccManager.isAdnOnError = false;
|
||||
iccManager.isSdnOnError = false;
|
||||
|
||||
var totalContactsToImport = mRequestResult.adn.length +
|
||||
mRequestResult.sdn.length;
|
||||
|
||||
importer.onfinish = function() {
|
||||
var mozContactsLength = navigator.mozContacts.contacts.length;
|
||||
assert.equal(mozContactsLength, totalContactsToImport);
|
||||
done();
|
||||
};
|
||||
importer.start();
|
||||
});
|
||||
|
||||
test('Calling without adn contacts', function(done) {
|
||||
iccManager.adnContacts = [];
|
||||
iccManager.sdnContacts = mRequestResult.sdn;
|
||||
iccManager.isAdnOnError = false;
|
||||
iccManager.isSdnOnError = false;
|
||||
|
||||
var totalContactsToImport = mRequestResult.sdn.length;
|
||||
|
||||
importer.onfinish = function() {
|
||||
var mozContactsLength = navigator.mozContacts.contacts.length;
|
||||
assert.equal(mozContactsLength, totalContactsToImport);
|
||||
done();
|
||||
};
|
||||
importer.start();
|
||||
});
|
||||
|
||||
test('Calling without sdn contacts', function(done) {
|
||||
iccManager.adnContacts = mRequestResult.adn;
|
||||
iccManager.sdnContacts = [];
|
||||
iccManager.isAdnOnError = false;
|
||||
iccManager.isSdnOnError = false;
|
||||
|
||||
var totalContactsToImport = mRequestResult.adn.length;
|
||||
|
||||
importer.onfinish = function() {
|
||||
var mozContactsLength = navigator.mozContacts.contacts.length;
|
||||
assert.equal(mozContactsLength, totalContactsToImport);
|
||||
done();
|
||||
};
|
||||
importer.start();
|
||||
});
|
||||
|
||||
test('Request adn contacts fails', function(done) {
|
||||
iccManager.adnContacts = mRequestResult.adn;
|
||||
iccManager.sdnContacts = mRequestResult.sdn;
|
||||
iccManager.isAdnOnError = true;
|
||||
iccManager.isSdnOnError = false;
|
||||
|
||||
importer.onerror = function() {
|
||||
var mozContactsLength = navigator.mozContacts.contacts.length;
|
||||
assert.equal(mozContactsLength, 0);
|
||||
done();
|
||||
};
|
||||
importer.start();
|
||||
});
|
||||
|
||||
test('Request sdn contacts fails, only import adn', function(done) {
|
||||
iccManager.adnContacts = mRequestResult.adn;
|
||||
iccManager.sdnContacts = mRequestResult.sdn;
|
||||
iccManager.isAdnOnError = false;
|
||||
iccManager.isSdnOnError = true;
|
||||
|
||||
var adnContactsLength = mRequestResult.adn.length;
|
||||
|
||||
importer.onfinish = function() {
|
||||
var mozContactsLength = navigator.mozContacts.contacts.length;
|
||||
assert.equal(mozContactsLength, adnContactsLength);
|
||||
done();
|
||||
};
|
||||
|
||||
importer.start();
|
||||
});
|
||||
|
||||
test('Imported Contacts are saved with the iccManager provided id',
|
||||
function(done) {
|
||||
iccManager.adnContacts = [mRequestResult.adn[0]];
|
||||
|
||||
var realMozContact = window.mozContact;
|
||||
|
||||
window.mozContact = function(contact) {
|
||||
assert.equal(contact.id, mRequestResult.adn[0].id);
|
||||
};
|
||||
|
||||
importer.onfinish = function() {
|
||||
window.mozContact = realMozContact;
|
||||
done();
|
||||
};
|
||||
|
||||
importer.start();
|
||||
});
|
||||
});
|
||||
486
gaia/apps/communications/contacts/test/unit/import/import_test.js
Executable file
486
gaia/apps/communications/contacts/test/unit/import/import_test.js
Executable file
|
|
@ -0,0 +1,486 @@
|
|||
'use strict';
|
||||
|
||||
/* global utils, importer, MockAlphaScroll, MockImageLoader */
|
||||
/* global MockSearch, MockasyncStorage, MockOauthflow, MockImportHtml */
|
||||
/* global MockConnector, MockImportedContacts, MockCurtain, MockLazyLoader */
|
||||
/* global MockConfirmDialog */
|
||||
|
||||
require('/shared/js/text_normalizer.js');
|
||||
require('/shared/js/contacts/search.js');
|
||||
requireApp('communications/contacts/test/unit/mock_l10n.js');
|
||||
require('/shared/js/contacts/import/importer_ui.js');
|
||||
require('/shared/js/contacts/import/utilities/misc.js');
|
||||
require('/shared/js/contacts/utilities/dom.js');
|
||||
require('/shared/js/contacts/utilities/templates.js');
|
||||
require('/shared/test/unit/mocks/mock_lazy_loader.js');
|
||||
require('/shared/test/unit/mocks/mock_confirm_dialog.js');
|
||||
|
||||
requireApp('communications/contacts/test/unit/import/mock_import.html.js');
|
||||
requireApp('communications/contacts/test/unit/mock_asyncstorage.js');
|
||||
requireApp('communications/contacts/test/unit/mock_search.js');
|
||||
requireApp('communications/contacts/test/unit/mock_oauthflow.js');
|
||||
require('/shared/js/contacts/import/friends_list.js');
|
||||
requireApp('communications/contacts/test/unit/mock_contacts_shortcuts.js');
|
||||
requireApp('communications/contacts/test/unit/mock_utils.js');
|
||||
requireApp('communications/facebook/test/unit/mock_curtain.js');
|
||||
requireApp('communications/contacts/test/unit/import/mock_connector.js');
|
||||
requireApp(
|
||||
'communications/contacts/test/unit/import/mock_imported_contacts.js');
|
||||
|
||||
var realSearch,
|
||||
realImageLoader,
|
||||
realAlphaScroll,
|
||||
realAsyncStorage,
|
||||
realOauthflow,
|
||||
realCurtain,
|
||||
realLazyLoader,
|
||||
realConfirmDialog,
|
||||
realParentLazyLoader,
|
||||
groupsListChild, groupsList;
|
||||
|
||||
if (!window.asyncStorage) {
|
||||
window.asyncStorage = null;
|
||||
}
|
||||
|
||||
if (!window.ImageLoader) {
|
||||
window.ImageLoader = null;
|
||||
}
|
||||
|
||||
if (!window.contacts) {
|
||||
window.contacts = null;
|
||||
}
|
||||
|
||||
if (!window.onrendered) {
|
||||
window.onrendered = true;
|
||||
}
|
||||
|
||||
if (!window.oauthflow) {
|
||||
window.oauthflow = null;
|
||||
}
|
||||
|
||||
if (!window.LazyLoader) {
|
||||
window.LazyLoader = null;
|
||||
}
|
||||
|
||||
if (!window.ConfirmDialog) {
|
||||
window.ConfirmDialog = null;
|
||||
}
|
||||
|
||||
setup(function() {
|
||||
importer.reset();
|
||||
});
|
||||
|
||||
|
||||
|
||||
suite('Import Friends Test Suite', function() {
|
||||
suiteSetup(function() {
|
||||
realAlphaScroll = utils.AlphaScroll;
|
||||
utils.alphaScroll = MockAlphaScroll;
|
||||
|
||||
realCurtain = window.Curtain;
|
||||
window.Curtain = MockCurtain;
|
||||
|
||||
realImageLoader = window.ImageLoader;
|
||||
window.ImageLoader = MockImageLoader;
|
||||
|
||||
realSearch = window.Search;
|
||||
window.Search = MockSearch;
|
||||
|
||||
realAsyncStorage = window.asyncStorage;
|
||||
window.asyncStorage = MockasyncStorage;
|
||||
|
||||
realOauthflow = window.oauthflow;
|
||||
window.oauthflow = MockOauthflow;
|
||||
|
||||
realLazyLoader = window.LazyLoader;
|
||||
window.LazyLoader = MockLazyLoader;
|
||||
|
||||
realConfirmDialog = window.ConfirmDialog;
|
||||
window.ConfirmDialog = MockConfirmDialog;
|
||||
|
||||
realParentLazyLoader = window.parent.LazyLoader;
|
||||
window.parent.LazyLoader = MockLazyLoader;
|
||||
|
||||
document.body.innerHTML = MockImportHtml;
|
||||
|
||||
groupsList = document.body.querySelector('#groups-list');
|
||||
groupsListChild = groupsList.firstElementChild;
|
||||
|
||||
importer.ui.init();
|
||||
});
|
||||
|
||||
|
||||
test('Import UI. items created. not already present', function(done) {
|
||||
var contactsLoadedCalled = false;
|
||||
MockConnector.oncontactsloaded = function() {
|
||||
contactsLoadedCalled = true;
|
||||
};
|
||||
groupsList.innerHTML = '';
|
||||
groupsList.appendChild(groupsListChild);
|
||||
|
||||
importer.start('mock_token', MockConnector, '*', function() {
|
||||
assert.equal(document.querySelectorAll('#groups-list li').length,
|
||||
MockImportedContacts.data.length);
|
||||
|
||||
// MockAsyncStorage is ordering by first name
|
||||
assert.isNotNull(document.
|
||||
querySelector('section#group-P li[data-uuid="1xz"]'));
|
||||
assert.isNotNull(document.
|
||||
querySelector('section#group-A li[data-uuid="2abc"]'));
|
||||
assert.isNotNull(document.
|
||||
querySelector('section#group-Ψ li[data-uuid="3cde"]'));
|
||||
|
||||
assert.equal(document.querySelectorAll('section#group-G *').length, 0);
|
||||
|
||||
assert.equal(document.querySelector('input[name="1xz"]').checked, false);
|
||||
assert.equal(document.querySelector('input[name="2abc"]').checked, false);
|
||||
assert.equal(document.querySelector('input[name="3cde"]').checked, false);
|
||||
|
||||
assert.isTrue(document.getElementById('deselect-all').disabled);
|
||||
assert.isFalse(document.getElementById('select-all').disabled);
|
||||
assert.isTrue(document.getElementById('import-action').disabled);
|
||||
|
||||
if (contactsLoadedCalled) {
|
||||
done();
|
||||
}
|
||||
else {
|
||||
assert.fail('contactsLoaded not Called', 'contactsLoadedCalled');
|
||||
done();
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
|
||||
test('Import UI with some contacts already imported', function(done) {
|
||||
groupsList.innerHTML = '';
|
||||
groupsList.appendChild(groupsListChild);
|
||||
var listDeviceContacts = MockConnector.listDeviceContacts;
|
||||
|
||||
MockConnector.listDeviceContacts = function(callbacks) {
|
||||
callbacks.success([
|
||||
{
|
||||
uid: '1xz'
|
||||
}
|
||||
]);
|
||||
};
|
||||
|
||||
importer.start('mock_token', MockConnector, '*', function() {
|
||||
var check = document.querySelector(
|
||||
'li[data-uuid="1xz"] input[type="checkbox"]');
|
||||
|
||||
assert.isTrue(check.checked);
|
||||
|
||||
var otherCheck = document.querySelector(
|
||||
'li[data-uuid="2abc"] input[type="checkbox"]');
|
||||
|
||||
assert.isFalse(otherCheck.checked);
|
||||
|
||||
assert.isFalse(document.getElementById('deselect-all').disabled);
|
||||
assert.isFalse(document.getElementById('select-all').disabled);
|
||||
|
||||
done();
|
||||
});
|
||||
|
||||
MockConnector.listDeviceContacts = listDeviceContacts;
|
||||
});
|
||||
|
||||
test('Import UI with all contacts already imported', function(done) {
|
||||
groupsList.innerHTML = '';
|
||||
groupsList.appendChild(groupsListChild);
|
||||
var listDeviceContacts = MockConnector.listDeviceContacts;
|
||||
|
||||
MockConnector.listDeviceContacts = function(callbacks) {
|
||||
callbacks.success([
|
||||
{
|
||||
uid: '1xz'
|
||||
},
|
||||
{
|
||||
uid: '2abc'
|
||||
},
|
||||
{
|
||||
uid: '3cde'
|
||||
}
|
||||
]);
|
||||
};
|
||||
|
||||
importer.start('mock_token', MockConnector, '*', function() {
|
||||
var check = document.querySelector(
|
||||
'li[data-uuid="1xz"] input[type="checkbox"]');
|
||||
|
||||
assert.isTrue(check.checked);
|
||||
|
||||
var otherCheck = document.querySelector(
|
||||
'li[data-uuid="2abc"] input[type="checkbox"]');
|
||||
|
||||
assert.isTrue(otherCheck.checked);
|
||||
|
||||
var anotherCheck = document.querySelector(
|
||||
'li[data-uuid="3cde"] input[type="checkbox"]');
|
||||
|
||||
assert.isTrue(anotherCheck.checked);
|
||||
|
||||
assert.isTrue(document.getElementById('deselect-all').disabled === false);
|
||||
assert.isTrue(document.getElementById('select-all').disabled === true);
|
||||
|
||||
done();
|
||||
});
|
||||
|
||||
MockConnector.listDeviceContacts = listDeviceContacts;
|
||||
});
|
||||
|
||||
test('Import UI, Importing then deleting contacts from Outlook/Gmail ' +
|
||||
'returns the correct result', function(done) {
|
||||
groupsList.innerHTML = '';
|
||||
groupsList.appendChild(groupsListChild);
|
||||
var listDeviceContacts = MockConnector.listDeviceContacts;
|
||||
// Simulate contacts already imported from the connector
|
||||
MockConnector.listDeviceContacts = function(callbacks) {
|
||||
callbacks.success([
|
||||
{
|
||||
uid: '1xz'
|
||||
},
|
||||
{
|
||||
uid: '2abc'
|
||||
},
|
||||
{
|
||||
uid: '3cde'
|
||||
}
|
||||
]);
|
||||
};
|
||||
// Simulate the desktop deletion of all contacts
|
||||
this.sinon.stub(MockConnector, 'listAllContacts',
|
||||
function(access_token, callbacks) {
|
||||
callbacks.success({data: []});
|
||||
}
|
||||
);
|
||||
|
||||
var spy = sinon.spy(window.ConfirmDialog, 'show');
|
||||
|
||||
importer.start('mock_token', MockConnector, '*', function() {
|
||||
done(function() {
|
||||
assert.isTrue(spy.called);
|
||||
assert.isTrue(spy.calledWith(null, 'emptyAccount'));
|
||||
});
|
||||
});
|
||||
|
||||
spy.restore();
|
||||
MockConnector.listDeviceContacts = listDeviceContacts;
|
||||
});
|
||||
|
||||
test('Import UI with no contacts available to import', function(done) {
|
||||
groupsList.innerHTML = '';
|
||||
groupsList.appendChild(groupsListChild);
|
||||
|
||||
var listAllContacts = MockConnector.listAllContacts;
|
||||
|
||||
MockConnector.listAllContacts = function(access_token, callbacks) {
|
||||
callbacks.success({ data: []});
|
||||
};
|
||||
|
||||
var spy = sinon.spy(window.ConfirmDialog, 'show');
|
||||
|
||||
importer.start('mock_token', MockConnector, '*', function() {
|
||||
done(function() {
|
||||
assert.isTrue(spy.called);
|
||||
assert.isTrue(spy.calledWith(null, 'emptyAccount'));
|
||||
});
|
||||
});
|
||||
|
||||
spy.restore();
|
||||
MockConnector.listAllContacts = listAllContacts;
|
||||
});
|
||||
|
||||
suite('Import process', function() {
|
||||
var stubFriendListRenderer;
|
||||
var listDeviceContacts;
|
||||
var mockCurtainHide;
|
||||
var stubImporterImportAll;
|
||||
var stubParentPostMessage;
|
||||
|
||||
suiteSetup(function() {
|
||||
stubFriendListRenderer = sinon.stub(window.FriendListRenderer, 'render',
|
||||
function(contacts, cb) {
|
||||
cb();
|
||||
});
|
||||
|
||||
mockCurtainHide = MockCurtain.hide;
|
||||
MockCurtain.hide = function (cb) {cb();};
|
||||
|
||||
listDeviceContacts = MockConnector.listDeviceContacts;
|
||||
MockConnector.listDeviceContacts = function(callbacks) {
|
||||
callbacks.success([{uid: '1xz'}]);
|
||||
};
|
||||
stubImporterImportAll = sinon.stub(importer, 'importAll', function(cb) {
|
||||
cb();
|
||||
});
|
||||
});
|
||||
|
||||
setup(function() {
|
||||
groupsList.innerHTML = '';
|
||||
groupsList.appendChild(groupsListChild);
|
||||
});
|
||||
|
||||
test('Import UI is closed when process ends in FTU', function(done) {
|
||||
var stubGetContext = sinon.stub(importer, 'getContext', function() {
|
||||
return 'ftu';
|
||||
});
|
||||
|
||||
stubParentPostMessage = sinon.stub(window.parent, 'postMessage',
|
||||
function(msg) {
|
||||
if (msg.type === 'window_close') {
|
||||
done(function() {
|
||||
assert.ok('passed');
|
||||
stubGetContext.restore();
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
importer.start('mock_token', MockConnector, '*', function() {
|
||||
importer.setSelected({contact1: null});
|
||||
importer.ui.importAll();
|
||||
});
|
||||
});
|
||||
|
||||
test('Import UI is closed when process ends in contacts', function(done) {
|
||||
stubParentPostMessage = sinon.stub(window.parent, 'postMessage',
|
||||
function(msg) {
|
||||
if (msg.type === 'import_updated') {
|
||||
window.postMessage({type: 'contacts_loaded'}, '*');
|
||||
} else if (msg.type === 'window_close') {
|
||||
done(function() {
|
||||
assert.ok('passed');
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
importer.start('mock_token', MockConnector, window.location.origin,
|
||||
function() {
|
||||
importer.setSelected({contact1: null});
|
||||
importer.ui.importAll();
|
||||
});
|
||||
});
|
||||
|
||||
suite('Number of contacts available to import', function() {
|
||||
var fbContacts;
|
||||
var deviceContacts;
|
||||
var observer;
|
||||
|
||||
function setObserver(callback) {
|
||||
observer = new MutationObserver(callback);
|
||||
var observerTarget = document.getElementById('friends-msg');
|
||||
observer.observe(observerTarget, {attributes: true});
|
||||
|
||||
var MockFbConnector = {
|
||||
name: 'facebook',
|
||||
listAllContacts: function(token, cb) {
|
||||
cb.success({data: fbContacts.map(name => ({ name, uid: name }))});
|
||||
},
|
||||
listDeviceContacts: function(cb) {
|
||||
cb.success(deviceContacts.map(name => ({ name, uid: name })));
|
||||
},
|
||||
adaptDataForShowing: function(data) {
|
||||
return data;
|
||||
},
|
||||
getContactUid: function(data) {
|
||||
return data.uid;
|
||||
}
|
||||
};
|
||||
|
||||
window.importer.start('test_token', MockFbConnector);
|
||||
}
|
||||
|
||||
suiteSetup(function() {
|
||||
sinon.stub(window.Curtain, 'hide', function() {});
|
||||
});
|
||||
|
||||
suiteTeardown(function() {
|
||||
window.Curtain.hide.restore();
|
||||
});
|
||||
|
||||
teardown(function() {
|
||||
observer.disconnect();
|
||||
});
|
||||
|
||||
test('If fb contacts < device contacts, 0', function(done) {
|
||||
fbContacts = ['contact1', 'contact2'];
|
||||
deviceContacts = ['contact1', 'contact2', 'contact3'];
|
||||
|
||||
setObserver(function(mutations) {
|
||||
done(function() {
|
||||
var l10nAttrs =
|
||||
navigator.mozL10n.getAttributes(mutations[0].target);
|
||||
assert.deepEqual(l10nAttrs.args, { numFriends : 0 },
|
||||
'The number must be 0');
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
test('If fb contacts = device contacts, 0', function(done) {
|
||||
fbContacts = ['contact1', 'contact2', 'contact3'];
|
||||
deviceContacts = ['contact1', 'contact2', 'contact3'];
|
||||
|
||||
setObserver(function(mutations) {
|
||||
done(function() {
|
||||
var l10nAttrs =
|
||||
navigator.mozL10n.getAttributes(mutations[0].target);
|
||||
assert.deepEqual(l10nAttrs.args, { numFriends : 0 },
|
||||
'The number must be 0');
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
test('If fb contacts > device contacts, >0', function(done) {
|
||||
fbContacts = ['contact1', 'contact2', 'contact3'];
|
||||
deviceContacts = ['contact1', 'contact2'];
|
||||
|
||||
setObserver(function(mutations) {
|
||||
done(function() {
|
||||
var l10nAttrs =
|
||||
navigator.mozL10n.getAttributes(mutations[0].target);
|
||||
assert.deepEqual(l10nAttrs.args, { numFriends : 1 },
|
||||
'The number must be 1');
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
teardown(function() {
|
||||
stubParentPostMessage.restore();
|
||||
});
|
||||
|
||||
suiteTeardown(function() {
|
||||
MockConnector.listDeviceContacts = listDeviceContacts;
|
||||
stubImporterImportAll.restore();
|
||||
stubFriendListRenderer.restore();
|
||||
MockCurtain.hide = mockCurtainHide;
|
||||
});
|
||||
});
|
||||
|
||||
suiteTeardown(function() {
|
||||
utils.alphaScroll = realAlphaScroll;
|
||||
window.ImageLoader = realImageLoader;
|
||||
window.Search = realSearch;
|
||||
window.asyncStorage = realAsyncStorage;
|
||||
window.oauthflow = realOauthflow;
|
||||
window.curtain = realCurtain;
|
||||
window.LazyLoader = realLazyLoader;
|
||||
window.ConfirmDialog = realConfirmDialog;
|
||||
window.parent.LazyLoader = realParentLazyLoader;
|
||||
});
|
||||
|
||||
});
|
||||
|
||||
suite('HTML format', function() {
|
||||
test('Contact name is tagged with the right class', function() {
|
||||
function loadImportHTML() {
|
||||
var req = new XMLHttpRequest();
|
||||
req.open('GET', '/shared/pages/import/import.html', false);
|
||||
req.send(null);
|
||||
return req.responseText;
|
||||
}
|
||||
|
||||
document.body.innerHTML = loadImportHTML();
|
||||
assert.ok(document.getElementsByClassName('contact-text'), 'Some element' +
|
||||
'with class contact-text should exist for search highlighting to work');
|
||||
});
|
||||
});
|
||||
42
gaia/apps/communications/contacts/test/unit/import/mock_connector.js
Executable file
42
gaia/apps/communications/contacts/test/unit/import/mock_connector.js
Executable file
|
|
@ -0,0 +1,42 @@
|
|||
'use strict';
|
||||
|
||||
/* global MockImportedContacts */
|
||||
/* exported MockConnector */
|
||||
|
||||
var MockConnector = {
|
||||
listAllContacts: function(access_token, callbacks) {
|
||||
callbacks.success(MockImportedContacts);
|
||||
},
|
||||
|
||||
listDeviceContacts: function(callbacks) {
|
||||
callbacks.success([]);
|
||||
},
|
||||
|
||||
getImporter: function(contactsList, access_token) {
|
||||
},
|
||||
|
||||
getCleaner: function(contactsList, access_token) {
|
||||
},
|
||||
|
||||
adaptDataForShowing: function(source) {
|
||||
return source;
|
||||
},
|
||||
|
||||
adaptDataForSaving: function live2MozContact(liveContact) {
|
||||
},
|
||||
|
||||
getContactUid: function(deviceContact) {
|
||||
return deviceContact.uid || '-1';
|
||||
},
|
||||
|
||||
get name() {
|
||||
return 'mock';
|
||||
},
|
||||
|
||||
downloadContactPicture: function(contact, access_token, callbacks) {
|
||||
callbacks.success();
|
||||
},
|
||||
|
||||
startSync: function() {
|
||||
}
|
||||
};
|
||||
53
gaia/apps/communications/contacts/test/unit/import/mock_import.html
Executable file
53
gaia/apps/communications/contacts/test/unit/import/mock_import.html
Executable file
|
|
@ -0,0 +1,53 @@
|
|||
<section role="region" id="content" class="import">
|
||||
<gaia-header id="link-header" action="close">
|
||||
<h1 data-l10n-id="fbFriends">Facebook Friends</h1>
|
||||
<button id="import-action" data-l10n-id="import" disabled>
|
||||
Import
|
||||
</button>
|
||||
</gaia-header>
|
||||
|
||||
<section id="main">
|
||||
<section id="mainContent" data-state="selection">
|
||||
<form id="search-container" role="search" class="search">
|
||||
<p>
|
||||
<label for="search" id="search-start">
|
||||
<input type="search" name="search" class="textfield" placeholder="Search" data-l10n-id="search-contact">
|
||||
</label>
|
||||
</p>
|
||||
</form>
|
||||
|
||||
<section class="friends-msg">
|
||||
<output id="num-friends" hidden></output>
|
||||
<p id="friends-msg"></p>
|
||||
</section>
|
||||
<section id="groups-list" class="unbordered import-list" data-type="list">
|
||||
<section id="group-#group#" data-template>
|
||||
<header>#group#</header>
|
||||
<ol id="contacts-list-#group#">
|
||||
<li data-template data-uuid="#uid#" data-search="#search#" aria-disabled="false" class="block-item">
|
||||
<label class="pack-checkbox">
|
||||
<input type="checkbox" name="#uid#"></input>
|
||||
<span></span>
|
||||
</label>
|
||||
<aside class="pack-end">
|
||||
<img data-src="https://graph.facebook.com/#uid#/picture?type=square"></img>
|
||||
</aside>
|
||||
<p data-order-by="firstName"><strong>#givenName#</strong> #familyName#</p>
|
||||
<p data-order-by="lastName">#givenName# <strong>#familyName#</strong></p>
|
||||
<p>#email1#</p>
|
||||
</li>
|
||||
</ol>
|
||||
</section> <!-- group template content -->
|
||||
</section> <!-- groupsList -->
|
||||
|
||||
<form role="dialog" data-type="confirm" class="no-overlay">
|
||||
<menu id="select-all-wrapper">
|
||||
<button id="deselect-all" class="edit-button" data-l10n-id="deselectAll">
|
||||
Deselect all
|
||||
</button>
|
||||
<button id="select-all" class="edit-button" data-l10n-id="selectAll">
|
||||
Select all
|
||||
</button>
|
||||
</menu>
|
||||
</form>
|
||||
</section> <!-- mainContent -->
|
||||
11
gaia/apps/communications/contacts/test/unit/import/mock_import.html.js
Executable file
11
gaia/apps/communications/contacts/test/unit/import/mock_import.html.js
Executable file
|
|
@ -0,0 +1,11 @@
|
|||
'use strict';
|
||||
|
||||
/* exported MockImportHtml */
|
||||
|
||||
var MockImportHtml = (function MockGoogleListing() {
|
||||
var req = new XMLHttpRequest();
|
||||
req.open('GET', '/contacts/test/unit/import/mock_import.html', false);
|
||||
req.send(null);
|
||||
|
||||
return req.responseText;
|
||||
})();
|
||||
121
gaia/apps/communications/contacts/test/unit/import/mock_imported_contacts.js
Executable file
121
gaia/apps/communications/contacts/test/unit/import/mock_imported_contacts.js
Executable file
|
|
@ -0,0 +1,121 @@
|
|||
'use strict';
|
||||
|
||||
/* exported MockImportedContacts */
|
||||
|
||||
var MockImportedContacts = {
|
||||
data: [
|
||||
{
|
||||
'uid': '1xz',
|
||||
'updated': new Date(),
|
||||
'additionalName': [''],
|
||||
'adr': [
|
||||
{
|
||||
'countryName': 'Germany',
|
||||
'locality': 'Chemnitz',
|
||||
'postalCode': '09034',
|
||||
'streetAddress': 'Gotthardstrasse 22'
|
||||
}
|
||||
],
|
||||
'bday': '1978-12-20',
|
||||
'email': [
|
||||
{
|
||||
'type': 'Personal',
|
||||
'value': 'test@test.com'
|
||||
}
|
||||
],
|
||||
'familyName': ['Grillo'],
|
||||
'givenName': ['Pepito'],
|
||||
'jobTitle': [''],
|
||||
'name': [
|
||||
'Pepito Grillo'
|
||||
],
|
||||
'org': ['Test'],
|
||||
'tel': [
|
||||
{
|
||||
'value': '+346578888888',
|
||||
'type': 'Mobile',
|
||||
'carrier': 'TEF'
|
||||
}
|
||||
],
|
||||
'category': [
|
||||
'favorite'
|
||||
],
|
||||
'note': [
|
||||
'Note 1'
|
||||
],
|
||||
'photo': ['test.png']
|
||||
}
|
||||
,
|
||||
{
|
||||
'uid': '2abc',
|
||||
'updated': new Date(),
|
||||
'additionalName': [''],
|
||||
'adr': [
|
||||
{
|
||||
'countryName': 'Spain',
|
||||
'locality': 'Burgos',
|
||||
'postalCode': '09034',
|
||||
'streetAddress': 'El Espolón, 25'
|
||||
}
|
||||
],
|
||||
'bday': '1932-12-20',
|
||||
'email': [
|
||||
{
|
||||
'type': 'Personal',
|
||||
'value': 'test2@test2.com'
|
||||
}
|
||||
],
|
||||
'familyName': ['Gonzo'],
|
||||
'givenName': ['Alfonso'],
|
||||
'jobTitle': [''],
|
||||
'name': [
|
||||
'Alfonso Gonzo'
|
||||
],
|
||||
'org': ['Retired 2.0'],
|
||||
'tel': [
|
||||
{
|
||||
'value': '+34612456789',
|
||||
'type': 'Mobile',
|
||||
'carrier': 'TEF'
|
||||
}
|
||||
],
|
||||
'photo': ['test2.png']
|
||||
}
|
||||
,
|
||||
{
|
||||
'uid': '3cde',
|
||||
'updated': new Date(),
|
||||
'additionalName': [''],
|
||||
'adr': [
|
||||
{
|
||||
'countryName': 'Spain',
|
||||
'locality': 'Lugo',
|
||||
'postalCode': '19034',
|
||||
'streetAddress': 'Mallorca, 25'
|
||||
}
|
||||
],
|
||||
'bday': '1965-11-20',
|
||||
'email': [
|
||||
{
|
||||
'type': 'Personal',
|
||||
'value': 'test3@test3.com'
|
||||
}
|
||||
],
|
||||
'familyName': ['Lucca'],
|
||||
'givenName': ['Ψoca'],
|
||||
'jobTitle': [''],
|
||||
'name': [
|
||||
'Ψoca Lucca'
|
||||
],
|
||||
'org': ['Testing'],
|
||||
'tel': [
|
||||
{
|
||||
'value': '+34555666777',
|
||||
'type': 'Mobile',
|
||||
'carrier': 'TEF'
|
||||
}
|
||||
],
|
||||
'photo': ['test3.png']
|
||||
}
|
||||
]
|
||||
};
|
||||
48
gaia/apps/communications/contacts/test/unit/import/mock_rest.js
Executable file
48
gaia/apps/communications/contacts/test/unit/import/mock_rest.js
Executable file
|
|
@ -0,0 +1,48 @@
|
|||
'use strict';
|
||||
|
||||
/* exported MockRest */
|
||||
|
||||
/*
|
||||
Mocking the Rest utility class, it's configurable to behave
|
||||
differently via the configure method
|
||||
*/
|
||||
var MockRest = (function MockRest() {
|
||||
|
||||
var config = {
|
||||
// Values are success, error, timeout (the possible callbacks)
|
||||
'type': 'success'
|
||||
// Follow with a series of 'urls' and the values returned
|
||||
};
|
||||
|
||||
var configure = function configure(conf) {
|
||||
config = conf;
|
||||
};
|
||||
|
||||
var get = function get(url, callbacks, options) {
|
||||
if (!config[url]) {
|
||||
callbacks.error('No matching for url ' + url);
|
||||
return;
|
||||
}
|
||||
|
||||
switch (config.type) {
|
||||
case 'success':
|
||||
callbacks.success(config[url]);
|
||||
break;
|
||||
case 'error':
|
||||
callbacks.error(config[url]);
|
||||
break;
|
||||
case 'timeout':
|
||||
callbacks.timeout(config[url]);
|
||||
break;
|
||||
default:
|
||||
callbacks.timeout(config[url]);
|
||||
break;
|
||||
}
|
||||
};
|
||||
|
||||
return {
|
||||
'get': get,
|
||||
'configure': configure
|
||||
};
|
||||
|
||||
})();
|
||||
80
gaia/apps/communications/contacts/test/unit/import/redirect_test.js
Executable file
80
gaia/apps/communications/contacts/test/unit/import/redirect_test.js
Executable file
|
|
@ -0,0 +1,80 @@
|
|||
'use strict';
|
||||
|
||||
/* global Redirect */
|
||||
|
||||
require('/shared/pages/import/redirects/redirect.js');
|
||||
|
||||
|
||||
suite('OAuth redirect', function() {
|
||||
|
||||
var openerDefinition;
|
||||
|
||||
var myOpenerObject = {
|
||||
postMessage: function() {}
|
||||
};
|
||||
|
||||
suiteSetup(function() {
|
||||
openerDefinition = Object.getOwnPropertyDescriptor(window, 'opener');
|
||||
|
||||
Object.defineProperty(window, 'opener', {
|
||||
writable: true,
|
||||
enumerable: true,
|
||||
configurable: true,
|
||||
value: myOpenerObject
|
||||
});
|
||||
|
||||
sinon.stub(window, 'close', function() {
|
||||
|
||||
});
|
||||
});
|
||||
|
||||
setup(function() {
|
||||
window.close.reset();
|
||||
});
|
||||
|
||||
suiteTeardown(function() {
|
||||
Object.defineProperty(window, 'opener', openerDefinition);
|
||||
window.close.restore();
|
||||
});
|
||||
|
||||
suite('> Recirect with valid access token', function() {
|
||||
test('> Check token', function() {
|
||||
this.sinon.spy(window.opener, 'postMessage');
|
||||
document.location.hash = 'access_token=ok';
|
||||
Redirect.init();
|
||||
|
||||
sinon.assert.pass(window.close.calledOnce);
|
||||
sinon.assert.pass(myOpenerObject.postMessage.calledOnce);
|
||||
sinon.assert.calledWith(myOpenerObject.postMessage,
|
||||
{'access_token': 'ok'});
|
||||
});
|
||||
|
||||
test('> Check parameters', function() {
|
||||
this.sinon.spy(window.opener, 'postMessage');
|
||||
document.location.hash = 'access_token=ok¶m1=1¶m2=2';
|
||||
Redirect.init();
|
||||
|
||||
sinon.assert.pass(window.close.calledOnce);
|
||||
sinon.assert.pass(myOpenerObject.postMessage.calledOnce);
|
||||
sinon.assert.calledWith(myOpenerObject.postMessage,
|
||||
{
|
||||
'access_token': 'ok',
|
||||
'param1': '1',
|
||||
'param2': '2'
|
||||
}
|
||||
);
|
||||
});
|
||||
});
|
||||
|
||||
suite('> Redirect with invalid access token', function() {
|
||||
test('No access token', function() {
|
||||
this.sinon.spy(window.opener, 'postMessage');
|
||||
document.location.hash = 'invalid';
|
||||
Redirect.init();
|
||||
|
||||
sinon.assert.pass(window.close.calledOnce);
|
||||
sinon.assert.callCount(myOpenerObject.postMessage, 0);
|
||||
});
|
||||
});
|
||||
|
||||
});
|
||||
Loading…
Reference in a new issue