Bug 1773144 - Allow contacts API to handle properties again. r=darktrojan
Differential Revision: https://phabricator.services.mozilla.com/D148564
This commit is contained in:
Родитель
b1b12d0fd7
Коммит
a234f307a8
|
@ -6,10 +6,21 @@ var { Services } = ChromeUtils.import("resource://gre/modules/Services.jsm");
|
|||
var { MailServices } = ChromeUtils.import(
|
||||
"resource:///modules/MailServices.jsm"
|
||||
);
|
||||
|
||||
var { AddrBookDirectory } = ChromeUtils.import(
|
||||
"resource:///modules/AddrBookDirectory.jsm"
|
||||
);
|
||||
var { newUID } = ChromeUtils.import("resource:///modules/AddrBookUtils.jsm");
|
||||
var { XPCOMUtils } = ChromeUtils.import(
|
||||
"resource://gre/modules/XPCOMUtils.jsm"
|
||||
);
|
||||
|
||||
XPCOMUtils.defineLazyModuleGetters(this, {
|
||||
newUID: "resource:///modules/AddrBookUtils.jsm",
|
||||
AddrBookCard: "resource:///modules/AddrBookCard.jsm",
|
||||
BANISHED_PROPERTIES: "resource:///modules/VCardUtils.jsm",
|
||||
VCardProperties: "resource:///modules/VCardUtils.jsm",
|
||||
VCardUtils: "resource:///modules/VCardUtils.jsm",
|
||||
});
|
||||
|
||||
// nsIAbCard.idl contains a list of properties that Thunderbird uses. Extensions are not
|
||||
// restricted to using only these properties, but the following properties cannot
|
||||
|
@ -21,8 +32,90 @@ const hiddenProperties = [
|
|||
"PopularityIndex",
|
||||
"RecordKey",
|
||||
"UID",
|
||||
"_vCard",
|
||||
"vCard",
|
||||
];
|
||||
|
||||
/**
|
||||
* Gets the VCardProperties of the given card either directly or by reconstructing
|
||||
* from a set of flat standard properties.
|
||||
*
|
||||
* @param {nsIAbCard/AddrBookCard} card
|
||||
* @returns {VCardProperties}
|
||||
*/
|
||||
function vCardPropertiesFromCard(card) {
|
||||
if (card.supportsVCard) {
|
||||
return card.vCardProperties;
|
||||
}
|
||||
return VCardProperties.fromPropertyMap(
|
||||
new Map(Array.from(card.properties, p => [p.name, p.value]))
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a new AddrBookCard from a set of flat standard properties.
|
||||
*
|
||||
* @param {ContactProperties} properties - a key/value properties object
|
||||
* @param {string} uid - optional UID for the card
|
||||
* @returns {AddrBookCard}
|
||||
*/
|
||||
function flatPropertiesToAbCard(properties, uid) {
|
||||
// Do not use VCardUtils.propertyMapToVCard().
|
||||
let vCard = VCardProperties.fromPropertyMap(
|
||||
new Map(Object.entries(properties))
|
||||
).toVCard();
|
||||
return VCardUtils.vCardToAbCard(vCard, uid);
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks if the given property is a custom contact property, which can be exposed
|
||||
* to WebExtensions.
|
||||
*
|
||||
* @param {string} name - property name
|
||||
* @returns {boolean}
|
||||
*/
|
||||
function isCustomProperty(name) {
|
||||
return (
|
||||
!hiddenProperties.includes(name) &&
|
||||
!BANISHED_PROPERTIES.includes(name) &&
|
||||
name.match(/^\w+$/)
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Adds the provided originalProperties to the card, adjusted by the changes
|
||||
* given in updateProperties. All banished properties are skipped and the updated
|
||||
* properties must be valid according to isCustomProperty().
|
||||
*
|
||||
* @param {AddrBookCard} card - a card to receive the provided properties
|
||||
* @param {ContactProperties} updateProperties - a key/value object with properties
|
||||
* to update the provided originalProperties
|
||||
* @param {nsIProperties} originalProperties - properties to be cloned onto
|
||||
* the provided card
|
||||
*/
|
||||
function addProperties(card, updateProperties, originalProperties) {
|
||||
let updates = Object.entries(updateProperties).filter(e =>
|
||||
isCustomProperty(e[0])
|
||||
);
|
||||
let mergedProperties = originalProperties
|
||||
? new Map([
|
||||
...Array.from(originalProperties, p => [p.name, p.value]),
|
||||
...updates,
|
||||
])
|
||||
: new Map(updates);
|
||||
|
||||
for (let [name, value] of mergedProperties) {
|
||||
if (
|
||||
!BANISHED_PROPERTIES.includes(name) &&
|
||||
value != "" &&
|
||||
value != null &&
|
||||
value != undefined
|
||||
) {
|
||||
card.setProperty(name, value);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Address book that supports finding cards only for a search (like LDAP).
|
||||
* @implements {nsIAbDirectory}
|
||||
|
@ -92,16 +185,23 @@ class ExtSearchBook extends AddrBookDirectory {
|
|||
aSearchString,
|
||||
aQuery
|
||||
);
|
||||
for (let properties of results) {
|
||||
let card = Cc["@mozilla.org/addressbook/cardproperty;1"].createInstance(
|
||||
Ci.nsIAbCard
|
||||
);
|
||||
card.directoryUID = this.UID;
|
||||
for (let [name, value] of Object.entries(properties)) {
|
||||
if (!hiddenProperties.includes(name)) {
|
||||
card.setProperty(name, value);
|
||||
for (let resultData of results) {
|
||||
let card;
|
||||
// A specified vCard is winning over any individual standard property.
|
||||
if (resultData.vCard) {
|
||||
try {
|
||||
card = VCardUtils.vCardToAbCard(resultData.vCard);
|
||||
} catch (ex) {
|
||||
throw new ExtensionError(
|
||||
`Invalid vCard data: ${resultData.vCard}.`
|
||||
);
|
||||
}
|
||||
} else {
|
||||
card = flatPropertiesToAbCard(resultData);
|
||||
}
|
||||
// Add custom properties to the property bag.
|
||||
addProperties(card, resultData);
|
||||
card.directoryUID = this.UID;
|
||||
aListener.onSearchFoundCard(card);
|
||||
}
|
||||
aListener.onSearchFinished(Cr.NS_OK, isCompleteResult, null, "");
|
||||
|
@ -284,23 +384,24 @@ var addressBookCache = new (class extends EventEmitter {
|
|||
copy.remote = node.item.isRemote;
|
||||
break;
|
||||
case "contact": {
|
||||
let vCardProperties = vCardPropertiesFromCard(node.item);
|
||||
copy.properties = {};
|
||||
for (let property of node.item.properties) {
|
||||
if (!hiddenProperties.includes(property.name)) {
|
||||
switch (property.value) {
|
||||
case undefined:
|
||||
case null:
|
||||
case "":
|
||||
// If someone sets a property to one of these values,
|
||||
// the property will be deleted from the database.
|
||||
// However, the value still appears in the notification,
|
||||
// so we ignore it here.
|
||||
continue;
|
||||
}
|
||||
// WebExtensions complains if we use numbers.
|
||||
copy.properties[property.name] = "" + property.value;
|
||||
}
|
||||
|
||||
// Build a flat property list from vCardProperties.
|
||||
for (let [name, value] of vCardProperties.toPropertyMap()) {
|
||||
copy.properties[name] = "" + value;
|
||||
}
|
||||
|
||||
// Return all other exposed properties stored in the nodes property bag.
|
||||
for (let property of Array.from(node.item.properties).filter(e =>
|
||||
isCustomProperty(e.name)
|
||||
)) {
|
||||
copy.properties[property.name] = "" + property.value;
|
||||
}
|
||||
|
||||
// Add the vCard.
|
||||
copy.properties.vCard = vCardProperties.toVCard();
|
||||
|
||||
let parentNode;
|
||||
try {
|
||||
parentNode = this.findAddressBookById(node.parentId);
|
||||
|
@ -740,39 +841,49 @@ this.addressBook = class extends ExtensionAPI {
|
|||
false
|
||||
);
|
||||
},
|
||||
create(parentId, id, properties) {
|
||||
create(parentId, id, createData) {
|
||||
let parentNode = addressBookCache.findAddressBookById(parentId);
|
||||
if (parentNode.item.readOnly) {
|
||||
throw new ExtensionUtils.ExtensionError(
|
||||
"Cannot create a contact in a read-only address book"
|
||||
);
|
||||
}
|
||||
let card = Cc[
|
||||
"@mozilla.org/addressbook/cardproperty;1"
|
||||
].createInstance(Ci.nsIAbCard);
|
||||
for (let [name, value] of Object.entries(properties)) {
|
||||
if (!hiddenProperties.includes(name)) {
|
||||
card.setProperty(name, value);
|
||||
|
||||
let card;
|
||||
// A specified vCard is winning over any individual standard property.
|
||||
if (createData.vCard) {
|
||||
try {
|
||||
card = VCardUtils.vCardToAbCard(createData.vCard, id);
|
||||
} catch (ex) {
|
||||
throw new ExtensionError(
|
||||
`Invalid vCard data: ${createData.vCard}.`
|
||||
);
|
||||
}
|
||||
} else {
|
||||
card = flatPropertiesToAbCard(createData, id);
|
||||
}
|
||||
if (id) {
|
||||
// Add custom properties to the property bag.
|
||||
addProperties(card, createData);
|
||||
|
||||
// Check if the new card has an enforced UID.
|
||||
if (card.vCardProperties.getFirstValue("uid")) {
|
||||
let duplicateExists = false;
|
||||
try {
|
||||
// Second argument is only a hint, all address books are checked.
|
||||
addressBookCache.findContactById(id, parentId);
|
||||
addressBookCache.findContactById(card.UID, parentId);
|
||||
duplicateExists = true;
|
||||
} catch (ex) {
|
||||
// Do nothing. We want this to throw because no contact was found.
|
||||
}
|
||||
if (duplicateExists) {
|
||||
throw new ExtensionError(`Duplicate contact id: ${id}`);
|
||||
throw new ExtensionError(`Duplicate contact id: ${card.UID}`);
|
||||
}
|
||||
card.UID = id;
|
||||
}
|
||||
|
||||
let newCard = parentNode.item.addCard(card);
|
||||
return newCard.UID;
|
||||
},
|
||||
update(id, properties) {
|
||||
update(id, updateData) {
|
||||
let node = addressBookCache.findContactById(id);
|
||||
let parentNode = addressBookCache.findAddressBookById(node.parentId);
|
||||
if (parentNode.item.readOnly) {
|
||||
|
@ -781,12 +892,104 @@ this.addressBook = class extends ExtensionAPI {
|
|||
);
|
||||
}
|
||||
|
||||
for (let [name, value] of Object.entries(properties)) {
|
||||
if (!hiddenProperties.includes(name)) {
|
||||
node.item.setProperty(name, value);
|
||||
// A specified vCard is winning over any individual standard property.
|
||||
// While a vCard is replacing the entire contact, specified standard
|
||||
// properties only update single entries (setting a value to null
|
||||
// clears it / promotes the next value of the same kind).
|
||||
let card;
|
||||
if (updateData.vCard) {
|
||||
let vCardUID;
|
||||
try {
|
||||
card = new AddrBookCard();
|
||||
card.UID = node.item.UID;
|
||||
card.setProperty(
|
||||
"_vCard",
|
||||
VCardUtils.translateVCard21(updateData.vCard)
|
||||
);
|
||||
vCardUID = card.vCardProperties.getFirstValue("uid");
|
||||
} catch (ex) {
|
||||
throw new ExtensionError(
|
||||
`Invalid vCard data: ${updateData.vCard}.`
|
||||
);
|
||||
}
|
||||
if (vCardUID && vCardUID != node.item.UID) {
|
||||
throw new ExtensionError(
|
||||
`The card's UID ${node.item.UID} may not be changed: ${updateData.vCard}.`
|
||||
);
|
||||
}
|
||||
} else {
|
||||
// Get the current vCardProperties, build a propertyMap and create
|
||||
// vCardParsed which allows to identify all currently exposed entries
|
||||
// based on the typeName used in VCardUtils.jsm (e.g. adr.work).
|
||||
let vCardProperties = vCardPropertiesFromCard(node.item);
|
||||
let vCardParsed = VCardUtils._parse(vCardProperties.entries);
|
||||
let propertyMap = vCardProperties.toPropertyMap();
|
||||
|
||||
// Save the old exposed state.
|
||||
let oldProperties = VCardProperties.fromPropertyMap(propertyMap);
|
||||
let oldParsed = VCardUtils._parse(oldProperties.entries);
|
||||
// Update the propertyMap.
|
||||
for (let [name, value] of Object.entries(updateData)) {
|
||||
propertyMap.set(name, value);
|
||||
}
|
||||
// Save the new exposed state.
|
||||
let newProperties = VCardProperties.fromPropertyMap(propertyMap);
|
||||
let newParsed = VCardUtils._parse(newProperties.entries);
|
||||
|
||||
// Evaluate the differences and update the still existing entries,
|
||||
// mark removed items for deletion.
|
||||
let deleteLog = [];
|
||||
for (let typeName of oldParsed.keys()) {
|
||||
if (typeName == "version") {
|
||||
continue;
|
||||
}
|
||||
for (let idx = 0; idx < oldParsed.get(typeName).length; idx++) {
|
||||
if (
|
||||
newParsed.has(typeName) &&
|
||||
idx < newParsed.get(typeName).length
|
||||
) {
|
||||
let originalIndex = vCardParsed.get(typeName)[idx].index;
|
||||
let newEntryIndex = newParsed.get(typeName)[idx].index;
|
||||
vCardProperties.entries[originalIndex] =
|
||||
newProperties.entries[newEntryIndex];
|
||||
// Mark this item as handled.
|
||||
newParsed.get(typeName)[idx] = null;
|
||||
} else {
|
||||
deleteLog.push(vCardParsed.get(typeName)[idx].index);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Remove entries which have been marked for deletion.
|
||||
for (let deleteIndex of deleteLog.sort((a, b) => a < b)) {
|
||||
vCardProperties.entries.splice(deleteIndex, 1);
|
||||
}
|
||||
|
||||
// Add new entries.
|
||||
for (let typeName of newParsed.keys()) {
|
||||
if (typeName == "version") {
|
||||
continue;
|
||||
}
|
||||
for (let newEntry of newParsed.get(typeName)) {
|
||||
if (newEntry) {
|
||||
vCardProperties.addEntry(
|
||||
newProperties.entries[newEntry.index]
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Create a new card with the original UID from the updated vCardProperties.
|
||||
card = VCardUtils.vCardToAbCard(
|
||||
vCardProperties.toVCard(),
|
||||
node.item.UID
|
||||
);
|
||||
}
|
||||
parentNode.item.modifyCard(node.item);
|
||||
|
||||
// Clone original properties and update custom properties.
|
||||
addProperties(card, updateData, node.item.properties);
|
||||
|
||||
parentNode.item.modifyCard(card);
|
||||
},
|
||||
delete(id) {
|
||||
let node = addressBookCache.findContactById(id);
|
||||
|
@ -820,9 +1023,40 @@ this.addressBook = class extends ExtensionAPI {
|
|||
register: fire => {
|
||||
let listener = (event, node, changes) => {
|
||||
let filteredChanges = {};
|
||||
for (let [key, value] of Object.entries(changes)) {
|
||||
if (!hiddenProperties.includes(key) && key.match(/^\w+$/)) {
|
||||
filteredChanges[key] = value;
|
||||
// Find changes in flat properties stored in the vCard.
|
||||
if (changes.hasOwnProperty("_vCard")) {
|
||||
let oldVCardProperties = VCardProperties.fromVCard(
|
||||
changes._vCard.oldValue
|
||||
).toPropertyMap();
|
||||
let newVCardProperties = VCardProperties.fromVCard(
|
||||
changes._vCard.newValue
|
||||
).toPropertyMap();
|
||||
for (let [name, value] of oldVCardProperties) {
|
||||
if (newVCardProperties.get(name) != value) {
|
||||
filteredChanges[name] = {
|
||||
oldValue: value,
|
||||
newValue: newVCardProperties.get(name) ?? null,
|
||||
};
|
||||
}
|
||||
}
|
||||
for (let [name, value] of newVCardProperties) {
|
||||
if (
|
||||
!filteredChanges.hasOwnProperty(name) &&
|
||||
oldVCardProperties.get(name) != value
|
||||
) {
|
||||
filteredChanges[name] = {
|
||||
oldValue: oldVCardProperties.get(name) ?? null,
|
||||
newValue: value,
|
||||
};
|
||||
}
|
||||
}
|
||||
}
|
||||
for (let [name, value] of Object.entries(changes)) {
|
||||
if (
|
||||
!filteredChanges.hasOwnProperty(name) &&
|
||||
isCustomProperty(name)
|
||||
) {
|
||||
filteredChanges[name] = value;
|
||||
}
|
||||
}
|
||||
fire.sync(addressBookCache.convert(node), filteredChanges);
|
||||
|
|
|
@ -348,7 +348,7 @@
|
|||
{
|
||||
"id": "ContactProperties",
|
||||
"type": "object",
|
||||
"description": "A set of properties for a particular contact. For a complete list of properties that Thunderbird uses, see https://hg.mozilla.org/comm-central/file/tip/mailnews/addrbook/public/nsIAbCard.idl\nIt is also possible to store custom properties. The custom property name however may only use a to z, A to Z, 1 to 9 and underscores.",
|
||||
"description": "A set of individual properties for a particular contact and/or its vCard string in the <code>vCard</code> member. The individual properties may either be `legacy properties <https://searchfox.org/comm-central/rev/8a1ae67088acf237dab2fd704db18589e7bf119e/mailnews/addrbook/modules/VCardUtils.jsm#295-334>`__ or custom properties (name may use a to z, A to Z, 1 to 9 and underscores).\n**Note:** Using individual properties is deprecated, get/set the <code>vCard</code> member instead.",
|
||||
"patternProperties": {
|
||||
"^\\w+$": {
|
||||
"choices": [
|
||||
|
@ -525,12 +525,13 @@
|
|||
{
|
||||
"name": "id",
|
||||
"type": "string",
|
||||
"description": "Assigns the contact an id. If an existing contact has this id, an exception is thrown.",
|
||||
"description": "Assigns the contact an id. If an existing contact has this id, an exception is thrown. **Note:** Deprecated, the card's id should be specified in the vCard string instead.",
|
||||
"optional": true
|
||||
},
|
||||
{
|
||||
"name": "properties",
|
||||
"$ref": "ContactProperties"
|
||||
"$ref": "ContactProperties",
|
||||
"description": "The properties object for the new contact. If it includes a <code>vCard</code> member, all specified `legacy properties <https://searchfox.org/comm-central/rev/8a1ae67088acf237dab2fd704db18589e7bf119e/mailnews/addrbook/modules/VCardUtils.jsm#295-334>`__ are ignored and the new contact will be based on the provided vCard string. If a UID is specified in the vCard string, which is already used by another contact, an exception is thrown. **Note:** Using individual properties is deprecated, use the <code>vCard</code> member instead."
|
||||
},
|
||||
{
|
||||
"type": "function",
|
||||
|
@ -557,10 +558,11 @@
|
|||
},
|
||||
{
|
||||
"name": "properties",
|
||||
"$ref": "ContactProperties"
|
||||
"$ref": "ContactProperties",
|
||||
"description": "An object with properties to update the specified contact. Individual properties are removed, if they are set to <code>null</code>. If the provided object includes a <code>vCard</code> member, all specified `legacy properties <https://searchfox.org/comm-central/rev/8a1ae67088acf237dab2fd704db18589e7bf119e/mailnews/addrbook/modules/VCardUtils.jsm#295-334>`__ are ignored and the details of the contact will be replaced by the provided vCard. Changes to the UID will be ignored. **Note:** Using individual properties is deprecated, use the <code>vCard</code> member instead. "
|
||||
}
|
||||
],
|
||||
"description": "Edits the properties of a contact. To remove a property, specify it as <code>null</code>."
|
||||
"description": "Updates a contact."
|
||||
},
|
||||
{
|
||||
"name": "delete",
|
||||
|
|
|
@ -185,6 +185,8 @@ add_task(async function test_addressBooks() {
|
|||
newContactId = await browser.contacts.create(firstBookId, {
|
||||
FirstName: "first",
|
||||
LastName: "last",
|
||||
Notes: "Notes",
|
||||
SomethingCustom: "Custom property",
|
||||
});
|
||||
browser.test.assertEq(36, newContactId.length);
|
||||
checkEvents([
|
||||
|
@ -211,16 +213,43 @@ add_task(async function test_addressBooks() {
|
|||
browser.test.assertEq("contact", newContact.type);
|
||||
browser.test.assertEq(false, newContact.readOnly);
|
||||
browser.test.assertEq(false, newContact.remote);
|
||||
browser.test.assertEq(3, Object.keys(newContact.properties).length);
|
||||
browser.test.assertEq(5, Object.keys(newContact.properties).length);
|
||||
browser.test.assertEq("first", newContact.properties.FirstName);
|
||||
browser.test.assertEq("last", newContact.properties.LastName);
|
||||
browser.test.assertEq("Notes", newContact.properties.Notes);
|
||||
browser.test.assertEq(
|
||||
`BEGIN:VCARD\r\nVERSION:4.0\r\nN:last;first;;;\r\nUID:${newContactId}\r\nEND:VCARD\r\n`,
|
||||
newContact.properties._vCard
|
||||
"Custom property",
|
||||
newContact.properties.SomethingCustom
|
||||
);
|
||||
browser.test.assertEq(
|
||||
`BEGIN:VCARD\r\nVERSION:4.0\r\nNOTE:Notes\r\nN:last;first;;;\r\nUID:${newContactId}\r\nEND:VCARD\r\n`,
|
||||
newContact.properties.vCard
|
||||
);
|
||||
|
||||
// Changing the UID should throw.
|
||||
try {
|
||||
await browser.contacts.update(newContactId, {
|
||||
vCard: `BEGIN:VCARD\r\nVERSION:4.0\r\nN:;first;;;\r\nEMAIL;PREF=1:first@last\r\nUID:SomethingNew\r\nEND:VCARD\r\n`,
|
||||
});
|
||||
browser.test.fail(
|
||||
`Updating a contact with a vCard with a differnt UID should throw`
|
||||
);
|
||||
} catch (ex) {
|
||||
browser.test.assertEq(
|
||||
`The card's UID ${newContactId} may not be changed: BEGIN:VCARD\r\nVERSION:4.0\r\nN:;first;;;\r\nEMAIL;PREF=1:first@last\r\nUID:SomethingNew\r\nEND:VCARD\r\n.`,
|
||||
ex.message,
|
||||
`browser.contacts.update threw exception`
|
||||
);
|
||||
}
|
||||
|
||||
// If a vCard and legacy properties are given, vCard must win.
|
||||
await browser.contacts.update(newContactId, {
|
||||
_vCard: `BEGIN:VCARD\r\nVERSION:4.0\r\nN:;first;;;\r\nEMAIL;PREF=1:first@last\r\nUID:${newContactId}\r\nEND:VCARD\r\n`,
|
||||
vCard: `BEGIN:VCARD\r\nVERSION:4.0\r\nN:;first;;;\r\nEMAIL;PREF=1:first@last\r\nUID:${newContactId}\r\nEND:VCARD\r\n`,
|
||||
FirstName: "Superman",
|
||||
PrimaryEmail: "c.kent@dailyplanet.com",
|
||||
PreferDisplayName: "0",
|
||||
OtherCustom: "Yet another custom property",
|
||||
Notes: "Ignored Notes",
|
||||
});
|
||||
checkEvents([
|
||||
"contacts",
|
||||
|
@ -229,23 +258,150 @@ add_task(async function test_addressBooks() {
|
|||
{
|
||||
PrimaryEmail: { oldValue: null, newValue: "first@last" },
|
||||
LastName: { oldValue: "last", newValue: null },
|
||||
OtherCustom: {
|
||||
oldValue: null,
|
||||
newValue: "Yet another custom property",
|
||||
},
|
||||
PreferDisplayName: { oldValue: null, newValue: "0" },
|
||||
},
|
||||
]);
|
||||
|
||||
let updatedContact = await browser.contacts.get(newContactId);
|
||||
browser.test.assertEq(3, Object.keys(updatedContact.properties).length);
|
||||
browser.test.assertEq(6, Object.keys(updatedContact.properties).length);
|
||||
browser.test.assertEq("first", updatedContact.properties.FirstName);
|
||||
browser.test.assertEq(
|
||||
"first@last",
|
||||
updatedContact.properties.PrimaryEmail
|
||||
);
|
||||
browser.test.assertTrue(!("LastName" in updatedContact.properties));
|
||||
browser.test.assertTrue(!("Notes" in updatedContact.properties));
|
||||
browser.test.assertTrue(
|
||||
!("Notes" in updatedContact.properties),
|
||||
"The vCard is not specifying Notes and the specified Notes property should be ignored."
|
||||
);
|
||||
browser.test.assertEq(
|
||||
"Custom property",
|
||||
updatedContact.properties.SomethingCustom,
|
||||
"Untouched custom properties should not be changed by updating the vCard"
|
||||
);
|
||||
browser.test.assertEq(
|
||||
"Yet another custom property",
|
||||
updatedContact.properties.OtherCustom,
|
||||
"Custom properties should be added even while updating a vCard"
|
||||
);
|
||||
browser.test.assertEq(
|
||||
"0",
|
||||
updatedContact.properties.PreferDisplayName,
|
||||
"Setting non-banished properties parallel to a vCard should update"
|
||||
);
|
||||
browser.test.assertEq(
|
||||
`BEGIN:VCARD\r\nVERSION:4.0\r\nN:;first;;;\r\nEMAIL;PREF=1:first@last\r\nUID:${newContactId}\r\nEND:VCARD\r\n`,
|
||||
updatedContact.properties._vCard
|
||||
updatedContact.properties.vCard
|
||||
);
|
||||
|
||||
// Manually Remove properties.
|
||||
await browser.contacts.update(newContactId, {
|
||||
LastName: "lastname",
|
||||
PrimaryEmail: null,
|
||||
SecondEmail: "test@invalid.de",
|
||||
SomethingCustom: null,
|
||||
OtherCustom: null,
|
||||
});
|
||||
checkEvents([
|
||||
"contacts",
|
||||
"onUpdated",
|
||||
{ type: "contact", parentId: firstBookId, id: newContactId },
|
||||
{
|
||||
LastName: { oldValue: null, newValue: "lastname" },
|
||||
// It is how it is. Defining a 2nd email with no 1st, will make it the first.
|
||||
PrimaryEmail: { oldValue: "first@last", newValue: "test@invalid.de" },
|
||||
SomethingCustom: { oldValue: "Custom property", newValue: null },
|
||||
OtherCustom: {
|
||||
oldValue: "Yet another custom property",
|
||||
newValue: null,
|
||||
},
|
||||
},
|
||||
]);
|
||||
|
||||
updatedContact = await browser.contacts.get(newContactId);
|
||||
browser.test.assertEq(5, Object.keys(updatedContact.properties).length);
|
||||
// LastName and FirstName are stored in the same multi field property and changing LastName should not change FirstName.
|
||||
browser.test.assertEq("first", updatedContact.properties.FirstName);
|
||||
browser.test.assertEq("lastname", updatedContact.properties.LastName);
|
||||
browser.test.assertEq(
|
||||
"test@invalid.de",
|
||||
updatedContact.properties.PrimaryEmail
|
||||
);
|
||||
browser.test.assertTrue(
|
||||
!("SomethingCustom" in updatedContact.properties)
|
||||
);
|
||||
browser.test.assertTrue(!("OtherCustom" in updatedContact.properties));
|
||||
browser.test.assertEq(
|
||||
`BEGIN:VCARD\r\nVERSION:4.0\r\nN:lastname;first;;;\r\nEMAIL:test@invalid.de\r\nUID:${newContactId}\r\nEND:VCARD\r\n`,
|
||||
updatedContact.properties.vCard
|
||||
);
|
||||
|
||||
// Add an email address, going from 1 to 2.Also remove FirstName, LastName should stay.
|
||||
await browser.contacts.update(newContactId, {
|
||||
FirstName: null,
|
||||
PrimaryEmail: "new1@invalid.de",
|
||||
SecondEmail: "new2@invalid.de",
|
||||
});
|
||||
checkEvents([
|
||||
"contacts",
|
||||
"onUpdated",
|
||||
{ type: "contact", parentId: firstBookId, id: newContactId },
|
||||
{
|
||||
PrimaryEmail: {
|
||||
oldValue: "test@invalid.de",
|
||||
newValue: "new1@invalid.de",
|
||||
},
|
||||
SecondEmail: { oldValue: null, newValue: "new2@invalid.de" },
|
||||
FirstName: { oldValue: "first", newValue: null },
|
||||
},
|
||||
]);
|
||||
|
||||
updatedContact = await browser.contacts.get(newContactId);
|
||||
browser.test.assertEq(5, Object.keys(updatedContact.properties).length);
|
||||
browser.test.assertEq("lastname", updatedContact.properties.LastName);
|
||||
browser.test.assertEq(
|
||||
"new1@invalid.de",
|
||||
updatedContact.properties.PrimaryEmail
|
||||
);
|
||||
browser.test.assertEq(
|
||||
"new2@invalid.de",
|
||||
updatedContact.properties.SecondEmail
|
||||
);
|
||||
browser.test.assertEq(
|
||||
`BEGIN:VCARD\r\nVERSION:4.0\r\nN:lastname;;;;\r\nEMAIL;PREF=1:new1@invalid.de\r\nUID:${newContactId}\r\nEMAIL:new2@invalid.de\r\nEND:VCARD\r\n`,
|
||||
updatedContact.properties.vCard
|
||||
);
|
||||
|
||||
// Remove and email address, going from 2 to 1.
|
||||
await browser.contacts.update(newContactId, {
|
||||
SecondEmail: null,
|
||||
});
|
||||
checkEvents([
|
||||
"contacts",
|
||||
"onUpdated",
|
||||
{ type: "contact", parentId: firstBookId, id: newContactId },
|
||||
{
|
||||
SecondEmail: { oldValue: "new2@invalid.de", newValue: null },
|
||||
},
|
||||
]);
|
||||
|
||||
updatedContact = await browser.contacts.get(newContactId);
|
||||
browser.test.assertEq(4, Object.keys(updatedContact.properties).length);
|
||||
browser.test.assertEq("lastname", updatedContact.properties.LastName);
|
||||
browser.test.assertEq(
|
||||
"new1@invalid.de",
|
||||
updatedContact.properties.PrimaryEmail
|
||||
);
|
||||
browser.test.assertEq(
|
||||
`BEGIN:VCARD\r\nVERSION:4.0\r\nN:lastname;;;;\r\nEMAIL;PREF=1:new1@invalid.de\r\nUID:${newContactId}\r\nEND:VCARD\r\n`,
|
||||
updatedContact.properties.vCard
|
||||
);
|
||||
|
||||
// Set a fixed UID.
|
||||
let fixedContactId = await browser.contacts.create(
|
||||
firstBookId,
|
||||
"this is a test",
|
||||
|
|
|
@ -157,14 +157,27 @@ var VCardUtils = {
|
|||
// Strip the version.
|
||||
return vCard.replace(/(\r?\n)VERSION:2.1\r?\n/i, "$1");
|
||||
},
|
||||
vCardToAbCard(vCard) {
|
||||
/**
|
||||
* Return a new AddrBookCard from the provided vCard string.
|
||||
*
|
||||
* @param vCard - the vCard string
|
||||
* @param [uid] - an optional UID to be used for the new card, overriding any
|
||||
* UID specified in the vCard string.
|
||||
* @returns
|
||||
*/
|
||||
vCardToAbCard(vCard, uid) {
|
||||
vCard = this.translateVCard21(vCard);
|
||||
|
||||
let abCard = new AddrBookCard();
|
||||
abCard.setProperty("_vCard", vCard);
|
||||
let uid = abCard.vCardProperties.getFirstValue("uid");
|
||||
if (uid) {
|
||||
abCard.UID = uid;
|
||||
|
||||
let vCardUID = abCard.vCardProperties.getFirstValue("uid");
|
||||
if (uid || vCardUID) {
|
||||
abCard.UID = uid || vCardUID;
|
||||
if (abCard.UID != vCardUID) {
|
||||
abCard.vCardProperties.clearValues("uid");
|
||||
abCard.vCardProperties.addValue("uid", abCard.UID);
|
||||
}
|
||||
}
|
||||
|
||||
return abCard;
|
||||
|
|
Загрузка…
Ссылка в новой задаче