michael@0: "use strict"; michael@0: michael@0: // Fix the environment to run Contacts tests michael@0: if (SpecialPowers.isMainProcess()) { michael@0: SpecialPowers.Cu.import("resource://gre/modules/ContactService.jsm"); michael@0: SpecialPowers.Cu.import("resource://gre/modules/PermissionPromptHelper.jsm"); michael@0: } michael@0: michael@0: SpecialPowers.addPermission("contacts-write", true, document); michael@0: SpecialPowers.addPermission("contacts-read", true, document); michael@0: SpecialPowers.addPermission("contacts-create", true, document); michael@0: michael@0: // Some helpful global vars michael@0: var isAndroid = (navigator.userAgent.indexOf("Android") !== -1); michael@0: michael@0: var defaultOptions = { michael@0: sortBy: "givenName", michael@0: }; michael@0: michael@0: var mozContacts = navigator.mozContacts; michael@0: michael@0: // To test sorting michael@0: var c1 = { michael@0: name: ["a a"], michael@0: familyName: ["a"], michael@0: givenName: ["a"], michael@0: }; michael@0: michael@0: var c2 = { michael@0: name: ["b b"], michael@0: familyName: ["b"], michael@0: givenName: ["b"], michael@0: }; michael@0: michael@0: var c3 = { michael@0: name: ["c c", "a a", "b b"], michael@0: familyName: ["c","a","b"], michael@0: givenName: ["c","a","b"], michael@0: }; michael@0: michael@0: var c4 = { michael@0: name: ["c c", "a a", "c c"], michael@0: familyName: ["c","a","c"], michael@0: givenName: ["c","a","c"], michael@0: }; michael@0: michael@0: var c5 = { michael@0: familyName: [], michael@0: givenName: [], michael@0: }; michael@0: michael@0: var c6 = { michael@0: name: ["e"], michael@0: familyName: ["e","e","e"], michael@0: givenName: ["e","e","e"], michael@0: }; michael@0: michael@0: var c7 = { michael@0: name: ["e"], michael@0: familyName: ["e","e","e"], michael@0: givenName: ["e","e","e"], michael@0: }; michael@0: michael@0: var c8 = { michael@0: name: ["e"], michael@0: familyName: ["e","e","e"], michael@0: givenName: ["e","e","e"], michael@0: }; michael@0: michael@0: var adr1 = { michael@0: type: ["work"], michael@0: streetAddress: "street 1", michael@0: locality: "locality 1", michael@0: region: "region 1", michael@0: postalCode: "postal code 1", michael@0: countryName: "country 1" michael@0: }; michael@0: michael@0: var adr2 = { michael@0: type: ["home, fax"], michael@0: streetAddress: "street2", michael@0: locality: "locality2", michael@0: region: "region2", michael@0: postalCode: "postal code2", michael@0: countryName: "country2" michael@0: }; michael@0: michael@0: var properties1 = { michael@0: // please keep capital letters at the start of these names michael@0: name: ["Test1 TestFamilyName", "Test2 Wagner"], michael@0: familyName: ["TestFamilyName","Wagner"], michael@0: givenName: ["Test1","Test2"], michael@0: phoneticFamilyName: ["TestphoneticFamilyName1","TestphoneticFamilyName2"], michael@0: phoneticGivenName: ["TestphoneticGivenName1","TestphoneticGivenName2"], michael@0: nickname: ["nicktest"], michael@0: tel: [{type: ["work"], value: "123456", carrier: "testCarrier"} , {type: ["home", "fax"], value: "+55 (31) 9876-3456"}, {type: ["home"], value: "+49 451 491934"}], michael@0: adr: [adr1], michael@0: email: [{type: ["work"], value: "x@y.com"}], michael@0: }; michael@0: michael@0: var properties2 = { michael@0: name: ["dummyHonorificPrefix dummyGivenName dummyFamilyName dummyHonorificSuffix", "dummyHonorificPrefix2"], michael@0: familyName: ["dummyFamilyName"], michael@0: givenName: ["dummyGivenName"], michael@0: phoneticFamilyName: ["dummyphoneticFamilyName"], michael@0: phoneticGivenName: ["dummyphoneticGivenName"], michael@0: honorificPrefix: ["dummyHonorificPrefix","dummyHonorificPrefix2"], michael@0: honorificSuffix: ["dummyHonorificSuffix"], michael@0: additionalName: ["dummyadditionalName"], michael@0: nickname: ["dummyNickname"], michael@0: tel: [{type: ["test"], value: "7932012345", carrier: "myCarrier", pref: 1},{type: ["home", "custom"], value: "7932012346", pref: 0}], michael@0: email: [{type: ["test"], value: "a@b.c"}, {value: "b@c.d", pref: 1}], michael@0: adr: [adr1, adr2], michael@0: impp: [{type: ["aim"], value:"im1", pref: 1}, {value: "im2"}], michael@0: org: ["org1", "org2"], michael@0: jobTitle: ["boss", "superboss"], michael@0: note: ["test note"], michael@0: category: ["cat1", "cat2"], michael@0: url: [{type: ["work", "work2"], value: "www.1.com", pref: 1}, {value:"www2.com"}], michael@0: bday: new Date("1980, 12, 01"), michael@0: anniversary: new Date("2000, 12, 01"), michael@0: sex: "male", michael@0: genderIdentity: "test", michael@0: key: ["ERPJ394GJJWEVJ0349GJ09W3H4FG0WFW80VHW3408GH30WGH348G3H"] michael@0: }; michael@0: michael@0: // To test sorting(CJK) michael@0: var c9 = { michael@0: phoneticFamilyName: ["a"], michael@0: phoneticGivenName: ["a"], michael@0: }; michael@0: michael@0: var c10 = { michael@0: phoneticFamilyName: ["b"], michael@0: phoneticGivenName: ["b"], michael@0: }; michael@0: michael@0: var c11 = { michael@0: phoneticFamilyName: ["c","a","b"], michael@0: phoneticGivenName: ["c","a","b"], michael@0: }; michael@0: michael@0: var c12 = { michael@0: phoneticFamilyName: ["c","a","c"], michael@0: phoneticGivenName: ["c","a","c"], michael@0: }; michael@0: michael@0: var c13 = { michael@0: phoneticFamilyName: [], michael@0: phoneticGivenName: [], michael@0: }; michael@0: michael@0: var c14 = { michael@0: phoneticFamilyName: ["e","e","e"], michael@0: phoneticGivenName: ["e","e","e"], michael@0: }; michael@0: michael@0: var c15 = { michael@0: phoneticFamilyName: ["e","e","e"], michael@0: phoneticGivenName: ["e","e","e"], michael@0: }; michael@0: michael@0: var c16 = { michael@0: phoneticFamilyName: ["e","e","e"], michael@0: phoneticGivenName: ["e","e","e"], michael@0: }; michael@0: michael@0: var properties3 = { michael@0: // please keep capital letters at the start of these names michael@0: name: ["Taro Yamada", "Ichiro Suzuki"], michael@0: familyName: ["Yamada","Suzuki"], michael@0: givenName: ["Taro","Ichiro"], michael@0: phoneticFamilyName: ["TestPhoneticFamilyYamada","TestPhoneticFamilySuzuki"], michael@0: phoneticGivenName: ["TestPhoneticGivenTaro","TestPhoneticGivenIchiro"], michael@0: nickname: ["phoneticNicktest"], michael@0: tel: [{type: ["work"], value: "123456", carrier: "testCarrier"} , {type: ["home", "fax"], value: "+55 (31) 9876-3456"}, {type: ["home"], value: "+49 451 491934"}], michael@0: adr: [adr1], michael@0: email: [{type: ["work"], value: "x@y.com"}], michael@0: }; michael@0: michael@0: var properties4 = { michael@0: name: ["dummyHonorificPrefix dummyTaro dummyYamada dummyHonorificSuffix", "dummyHonorificPrefix2"], michael@0: familyName: ["dummyYamada"], michael@0: givenName: ["dummyTaro"], michael@0: phoneticFamilyName: ["dummyTestPhoneticFamilyYamada"], michael@0: phoneticGivenName: ["dummyTestPhoneticGivenTaro"], michael@0: honorificPrefix: ["dummyPhoneticHonorificPrefix","dummyPhoneticHonorificPrefix2"], michael@0: honorificSuffix: ["dummyPhoneticHonorificSuffix"], michael@0: additionalName: ["dummyPhoneticAdditionalName"], michael@0: nickname: ["dummyPhoneticNickname"], michael@0: tel: [{type: ["test"], value: "7932012345", carrier: "myCarrier", pref: 1},{type: ["home", "custom"], value: "7932012346", pref: 0}], michael@0: email: [{type: ["test"], value: "a@b.c"}, {value: "b@c.d", pref: 1}], michael@0: adr: [adr1, adr2], michael@0: impp: [{type: ["aim"], value:"im1", pref: 1}, {value: "im2"}], michael@0: org: ["org1", "org2"], michael@0: jobTitle: ["boss", "superboss"], michael@0: note: ["test note"], michael@0: category: ["cat1", "cat2"], michael@0: url: [{type: ["work", "work2"], value: "www.1.com", pref: 1}, {value:"www2.com"}], michael@0: bday: new Date("1980, 12, 01"), michael@0: anniversary: new Date("2000, 12, 01"), michael@0: sex: "male", michael@0: genderIdentity: "test", michael@0: key: ["ERPJ394GJJWEVJ0349GJ09W3H4FG0WFW80VHW3408GH30WGH348G3H"] michael@0: }; michael@0: michael@0: var sample_id1; michael@0: var sample_id2; michael@0: michael@0: var createResult1; michael@0: var createResult2; michael@0: michael@0: var findResult1; michael@0: var findResult2; michael@0: michael@0: // DOMRequest helper functions michael@0: function onUnwantedSuccess() { michael@0: ok(false, "onUnwantedSuccess: shouldn't get here"); michael@0: } michael@0: michael@0: function onFailure() { michael@0: ok(false, "in on Failure!"); michael@0: next(); michael@0: } michael@0: michael@0: // Validation helper functions michael@0: function checkStr(str1, str2, msg) { michael@0: if (str1 ^ str2) { michael@0: ok(false, "Expected both strings to be either present or absent"); michael@0: return; michael@0: } michael@0: if (!str1 || str1 == "null") { michael@0: str1 = null; michael@0: } michael@0: if (!str2 || str2 == "null") { michael@0: str2 = null; michael@0: } michael@0: is(str1, str2, msg); michael@0: } michael@0: michael@0: function checkStrArray(str1, str2, msg) { michael@0: function normalize_falsy(v) { michael@0: if (!v || v == "null" || v == "undefined") { michael@0: return ""; michael@0: } michael@0: return v; michael@0: } michael@0: function optArray(val) { michael@0: return Array.isArray(val) ? val : [val]; michael@0: } michael@0: str1 = optArray(str1).map(normalize_falsy).filter(v => v != ""); michael@0: str2 = optArray(str2).map(normalize_falsy).filter(v => v != ""); michael@0: ise(JSON.stringify(str1), JSON.stringify(str2), msg); michael@0: } michael@0: michael@0: function checkPref(pref1, pref2) { michael@0: // If on Android treat one preference as 0 and the other as undefined as matching michael@0: if (isAndroid) { michael@0: if ((!pref1 && pref2 == undefined) || (pref1 == undefined && !pref2)) { michael@0: pref1 = false; michael@0: pref2 = false; michael@0: } michael@0: } michael@0: ise(!!pref1, !!pref2, "Same pref"); michael@0: } michael@0: michael@0: function checkAddress(adr1, adr2) { michael@0: if (adr1 ^ adr2) { michael@0: ok(false, "Expected both adrs to be either present or absent"); michael@0: return; michael@0: } michael@0: checkStrArray(adr1.type, adr2.type, "Same type"); michael@0: checkStr(adr1.streetAddress, adr2.streetAddress, "Same streetAddress"); michael@0: checkStr(adr1.locality, adr2.locality, "Same locality"); michael@0: checkStr(adr1.region, adr2.region, "Same region"); michael@0: checkStr(adr1.postalCode, adr2.postalCode, "Same postalCode"); michael@0: checkStr(adr1.countryName, adr2.countryName, "Same countryName"); michael@0: checkPref(adr1.pref, adr2.pref); michael@0: } michael@0: michael@0: function checkField(field1, field2) { michael@0: if (field1 ^ field2) { michael@0: ok(false, "Expected both fields to be either present or absent"); michael@0: return; michael@0: } michael@0: checkStrArray(field1.type, field2.type, "Same type"); michael@0: checkStr(field1.value, field2.value, "Same value"); michael@0: checkPref(field1.pref, field2.pref); michael@0: } michael@0: michael@0: function checkTel(tel1, tel2) { michael@0: if (tel1 ^ tel2) { michael@0: ok(false, "Expected both tels to be either present or absent"); michael@0: return; michael@0: } michael@0: checkField(tel1, tel2); michael@0: checkStr(tel1.carrier, tel2.carrier, "Same carrier"); michael@0: } michael@0: michael@0: function checkCategory(category1, category2) { michael@0: // Android adds contacts to the a default category. This should be removed from the michael@0: // results before comparing them michael@0: if (isAndroid) { michael@0: category1 = removeAndroidDefaultCategory(category1); michael@0: category2 = removeAndroidDefaultCategory(category2); michael@0: } michael@0: checkStrArray(category1, category2, "Same Category") michael@0: } michael@0: michael@0: function removeAndroidDefaultCategory(category) { michael@0: if (!category) { michael@0: return category; michael@0: } michael@0: michael@0: var result = []; michael@0: michael@0: for (var i of category) { michael@0: // Some devices may return the full group name (prefixed with "System Group: ") michael@0: if (i != "My Contacts" && i != "System Group: My Contacts") { michael@0: result.push(i); michael@0: } michael@0: } michael@0: michael@0: return result; michael@0: } michael@0: michael@0: function checkArrayField(array1, array2, func, msg) { michael@0: if (!!array1 ^ !!array2) { michael@0: ok(false, "Expected both arrays to be either present or absent: " + JSON.stringify(array1) + " vs. " + JSON.stringify(array2) + ". (" + msg + ")"); michael@0: return; michael@0: } michael@0: if (!array1 && !array2) { michael@0: ok(true, msg); michael@0: return; michael@0: } michael@0: ise(array1.length, array2.length, "Same length"); michael@0: for (var i = 0; i < array1.length; ++i) { michael@0: func(array1[i], array2[i], msg); michael@0: } michael@0: } michael@0: michael@0: function checkContacts(contact1, contact2) { michael@0: if (!!contact1 ^ !!contact2) { michael@0: ok(false, "Expected both contacts to be either present or absent"); michael@0: return; michael@0: } michael@0: checkStrArray(contact1.name, contact2.name, "Same name"); michael@0: checkStrArray(contact1.honorificPrefix, contact2.honorificPrefix, "Same honorificPrefix"); michael@0: checkStrArray(contact1.givenName, contact2.givenName, "Same givenName"); michael@0: checkStrArray(contact1.additionalName, contact2.additionalName, "Same additionalName"); michael@0: checkStrArray(contact1.familyName, contact2.familyName, "Same familyName"); michael@0: checkStrArray(contact1.phoneticFamilyName, contact2.phoneticFamilyName, "Same phoneticFamilyName"); michael@0: checkStrArray(contact1.phoneticGivenName, contact2.phoneticGivenName, "Same phoneticGivenName"); michael@0: checkStrArray(contact1.honorificSuffix, contact2.honorificSuffix, "Same honorificSuffix"); michael@0: checkStrArray(contact1.nickname, contact2.nickname, "Same nickname"); michael@0: checkCategory(contact1.category, contact2.category); michael@0: checkStrArray(contact1.org, contact2.org, "Same org"); michael@0: checkStrArray(contact1.jobTitle, contact2.jobTitle, "Same jobTitle"); michael@0: is(contact1.bday ? contact1.bday.valueOf() : null, contact2.bday ? contact2.bday.valueOf() : null, "Same birthday"); michael@0: checkStrArray(contact1.note, contact2.note, "Same note"); michael@0: is(contact1.anniversary ? contact1.anniversary.valueOf() : null , contact2.anniversary ? contact2.anniversary.valueOf() : null, "Same anniversary"); michael@0: checkStr(contact1.sex, contact2.sex, "Same sex"); michael@0: checkStr(contact1.genderIdentity, contact2.genderIdentity, "Same genderIdentity"); michael@0: checkStrArray(contact1.key, contact2.key, "Same key"); michael@0: michael@0: checkArrayField(contact1.adr, contact2.adr, checkAddress, "Same adr"); michael@0: checkArrayField(contact1.tel, contact2.tel, checkTel, "Same tel"); michael@0: checkArrayField(contact1.email, contact2.email, checkField, "Same email"); michael@0: checkArrayField(contact1.url, contact2.url, checkField, "Same url"); michael@0: checkArrayField(contact1.impp, contact2.impp, checkField, "Same impp"); michael@0: } michael@0: michael@0: function addContacts() { michael@0: ok(true, "Adding 40 contacts"); michael@0: let req; michael@0: for (let i = 0; i < 39; ++i) { michael@0: properties1.familyName[0] = "Testname" + (i < 10 ? "0" + i : i); michael@0: properties1.name = [properties1.givenName[0] + " " + properties1.familyName[0]]; michael@0: createResult1 = new mozContact(properties1); michael@0: req = mozContacts.save(createResult1); michael@0: req.onsuccess = function() { michael@0: ok(createResult1.id, "The contact now has an ID."); michael@0: }; michael@0: req.onerror = onFailure; michael@0: }; michael@0: properties1.familyName[0] = "Testname39"; michael@0: properties1.name = [properties1.givenName[0] + " Testname39"]; michael@0: createResult1 = new mozContact(properties1); michael@0: req = mozContacts.save(createResult1); michael@0: req.onsuccess = function() { michael@0: ok(createResult1.id, "The contact now has an ID."); michael@0: checkStrArray(createResult1.name, properties1.name, "Same Name"); michael@0: next(); michael@0: }; michael@0: req.onerror = onFailure; michael@0: } michael@0: michael@0: function getOne(msg) { michael@0: return function() { michael@0: ok(true, msg || "Retrieving one contact with getAll"); michael@0: let req = mozContacts.getAll({}); michael@0: michael@0: let count = 0; michael@0: req.onsuccess = function(event) { michael@0: ok(true, "on success"); michael@0: if (req.result) { michael@0: ok(true, "result is valid"); michael@0: count++; michael@0: req.continue(); michael@0: } else { michael@0: is(count, 1, "last contact - only one contact returned"); michael@0: next(); michael@0: } michael@0: }; michael@0: req.onerror = onFailure; michael@0: }; michael@0: } michael@0: michael@0: function getAll(msg) { michael@0: return function() { michael@0: ok(true, msg || "Retrieving 40 contacts with getAll"); michael@0: let req = mozContacts.getAll({ michael@0: sortBy: "familyName", michael@0: sortOrder: "ascending" michael@0: }); michael@0: let count = 0; michael@0: let result; michael@0: let props; michael@0: req.onsuccess = function(event) { michael@0: if (req.result) { michael@0: ok(true, "result is valid"); michael@0: result = req.result; michael@0: properties1.familyName[0] = "Testname" + (count < 10 ? "0" + count : count); michael@0: is(result.familyName[0], properties1.familyName[0], "Same familyName"); michael@0: count++; michael@0: req.continue(); michael@0: } else { michael@0: is(count, 40, "last contact - 40 contacts returned"); michael@0: next(); michael@0: } michael@0: }; michael@0: req.onerror = onFailure; michael@0: }; michael@0: } michael@0: michael@0: function clearTemps() { michael@0: sample_id1 = null; michael@0: sample_id2 = null; michael@0: createResult1 = null; michael@0: createResult2 = null; michael@0: findResult1 = null; michael@0: findResult2 = null; michael@0: } michael@0: michael@0: function clearDatabase() { michael@0: ok(true, "Deleting database"); michael@0: let req = mozContacts.clear() michael@0: req.onsuccess = function () { michael@0: ok(true, "Deleted the database"); michael@0: next(); michael@0: } michael@0: req.onerror = onFailure; michael@0: } michael@0: michael@0: function checkCount(count, msg, then) { michael@0: var request = mozContacts.getCount(); michael@0: request.onsuccess = function(e) { michael@0: is(e.target.result, count, msg); michael@0: then(); michael@0: }; michael@0: request.onerror = onFailure; michael@0: } michael@0: michael@0: // Helper functions to run tests michael@0: var index = 0; michael@0: michael@0: function next() { michael@0: info("Step " + index); michael@0: if (index >= steps.length) { michael@0: ok(false, "Shouldn't get here!"); michael@0: return; michael@0: } michael@0: try { michael@0: var i = index++; michael@0: steps[i](); michael@0: } catch(ex) { michael@0: ok(false, "Caught exception", ex); michael@0: } michael@0: } michael@0: michael@0: SimpleTest.waitForExplicitFinish(); michael@0: michael@0: function start_tests() { michael@0: // Skip tests on Android < 4.0 due to test failures on tbpl (see bugs 897924 & 888891) michael@0: let androidVersion = SpecialPowers.Cc['@mozilla.org/system-info;1'] michael@0: .getService(SpecialPowers.Ci.nsIPropertyBag2) michael@0: .getProperty('version'); michael@0: if (!isAndroid || androidVersion >= 14) { michael@0: next(); michael@0: } else { michael@0: ok(true, "Skip tests on Android < 4.0 (bugs 897924 & 888891"); michael@0: SimpleTest.finish(); michael@0: } michael@0: }