diff options
Diffstat (limited to 'comm/mailnews/addrbook/test/unit/test_vCardProperties.js')
-rw-r--r-- | comm/mailnews/addrbook/test/unit/test_vCardProperties.js | 899 |
1 files changed, 899 insertions, 0 deletions
diff --git a/comm/mailnews/addrbook/test/unit/test_vCardProperties.js b/comm/mailnews/addrbook/test/unit/test_vCardProperties.js new file mode 100644 index 0000000000..cf2c28a634 --- /dev/null +++ b/comm/mailnews/addrbook/test/unit/test_vCardProperties.js @@ -0,0 +1,899 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, you can obtain one at http://mozilla.org/MPL/2.0/. */ + +/* Tests of VCardProperties and VCardPropertyEntry. */ + +var { AddrBookCard } = ChromeUtils.import( + "resource:///modules/AddrBookCard.jsm" +); +var { VCardProperties, VCardPropertyEntry } = ChromeUtils.import( + "resource:///modules/VCardUtils.jsm" +); + +function propertyEqual(actual, expected, message) { + let actualAsObject = { + name: actual.name, + params: actual.params, + type: actual.type, + value: actual.value, + }; + Assert.deepEqual(actualAsObject, expected, message); +} + +function propertyArrayEqual(actual, expected, message) { + Assert.deepEqual( + actual.map(a => { + return { + name: a.name, + params: a.params, + type: a.type, + value: a.value, + }; + }), + expected, + message + ); +} + +/** + * Tests that AddrBookCard supports vCard. + */ +add_task(function testAddrBookCard() { + let card = new AddrBookCard(); + Assert.equal(card.supportsVCard, true, "AddrBookCard supports vCard"); + Assert.ok(card.vCardProperties, "AddrBookCard has vCardProperties"); + Assert.equal(card.vCardProperties.constructor.name, "VCardProperties"); +}); + +/** + * Tests that nsAbCardProperty does not support vCard. + */ +add_task(function testABCardProperty() { + let card = Cc["@mozilla.org/addressbook/cardproperty;1"].createInstance( + Ci.nsIAbCard + ); + Assert.equal( + card.supportsVCard, + false, + "nsAbCardProperty does not support vCard" + ); + Assert.strictEqual( + card.vCardProperties, + null, + "nsAbCardProperty has no vCardProperties" + ); +}); + +/** + * Tests the `clone` and `equals` functions of VCardPropertyEntry, with a + * simple value type. + */ +add_task(function testPropertyEntrySingleValue() { + let entry = new VCardPropertyEntry("fn", {}, "text", "Juliet"); + let clone = entry.clone(); + + Assert.ok(entry.equals(entry), "original is equal to itself"); + Assert.ok(entry.equals(clone), "original is equal to cloned object"); + Assert.ok(clone.equals(entry), "cloned object is equal to original"); + Assert.ok(clone.equals(clone), "cloned object is equal to itself"); + + Assert.equal(clone.value, entry.value, "values are identical"); + + let other = new VCardPropertyEntry("n", {}, "text", "Romeo"); + Assert.ok(!entry.equals(other), "original is not equal to another object"); + Assert.ok(!other.equals(entry), "another object is not equal to original"); +}); + +/** + * Tests the `clone` and `equals` functions of VCardPropertyEntry, with a + * complex value type. + */ +add_task(function testPropertyEntryMultiValue() { + // A name entry for somebody named "Mr One Two Three Four Senior". + let entry = new VCardPropertyEntry("n", {}, "text", [ + "Four", + "One", + ["Two", "Three"], + "Mr", + "Senior", + ]); + let clone = entry.clone(); + + Assert.ok(entry.equals(entry), "original is equal to itself"); + Assert.ok(entry.equals(clone), "original is equal to cloned object"); + Assert.ok(clone.equals(entry), "cloned object is equal to original"); + Assert.ok(clone.equals(clone), "cloned object is equal to itself"); + + Assert.deepEqual(clone.value, entry.value, "values are identical"); + + Assert.notEqual( + clone.value, + entry.value, + "value arrays are separate objects" + ); + Assert.notEqual( + clone.value[2], + entry.value[2], + "subvalue arrays are separate objects" + ); + + // A name entry for somebody named "Mr One Two Three Four Junior". + let other = new VCardPropertyEntry("n", {}, "text", [ + "Four", + "One", + ["Two", "Three"], + "Mr", + "Junior", + ]); + Assert.ok(!entry.equals(other), "original is not equal to another object"); + Assert.ok(!other.equals(entry), "another object is not equal to original"); +}); + +/** + * Tests creating a VCardProperties from a vCard string, + * then recreating the vCard. + */ +add_task(function testFromToVCard() { + let inVCard = formatVCard` + BEGIN:VCARD + VERSION:3.0 + FN:Mike Test + N:Test;Mike;;; + EMAIL;PREF=1:mike@test.invalid + NICKNAME:Testing Mike + CATEGORIES:testers,quality control,QA + END:VCARD`; + let properties = VCardProperties.fromVCard(inVCard); + + Assert.equal(properties.entries.length, 6, "entry count"); + propertyEqual( + properties.getFirstEntry("version"), + { + name: "version", + params: {}, + type: "text", + value: "3.0", + }, + "version entry" + ); + propertyEqual( + properties.getFirstEntry("fn"), + { + name: "fn", + params: {}, + type: "text", + value: "Mike Test", + }, + "fn entry" + ); + propertyEqual( + properties.getFirstEntry("n"), + { + name: "n", + params: {}, + type: "text", + value: ["Test", "Mike", "", "", ""], + }, + "n entry" + ); + propertyEqual( + properties.getFirstEntry("email"), + { + name: "email", + params: { pref: 1 }, + type: "text", + value: "mike@test.invalid", + }, + "email entry" + ); + propertyEqual( + properties.getFirstEntry("nickname"), + { + name: "nickname", + params: {}, + type: "text", + value: "Testing Mike", + }, + "multivalue entry with one value" + ); + propertyEqual( + properties.getFirstEntry("categories"), + { + name: "categories", + params: {}, + type: "text", + value: ["testers", "quality control", "QA"], + }, + "multivalue entry with multiple values" + ); + + let outVCard = properties.toVCard(); + Assert.equal(outVCard, inVCard, "vCard reproduction"); +}); + +/** + * Tests creating a VCardProperties from a Map of old-style address book + * properties, then recreating the Map. + */ +add_task(function testFromToPropertyMap() { + let inProperties = [ + ["DisplayName", "Mike Test"], + ["LastName", "Test"], + ["FirstName", "Mike"], + ["PrimaryEmail", "mike@test.invalid"], + ["Custom1", "custom one"], + ["Custom2", "custom two"], + ["Custom3", "custom three"], + ["Custom4", "custom four"], + ]; + let properties = VCardProperties.fromPropertyMap( + new Map(inProperties), + "3.0" + ); + + Assert.equal(properties.entries.length, 8, "entry count"); + propertyEqual( + properties.getFirstEntry("version"), + { + name: "version", + params: {}, + type: "text", + value: "3.0", + }, + "version entry" + ); + propertyEqual( + properties.getFirstEntry("fn"), + { + name: "fn", + params: {}, + type: "text", + value: "Mike Test", + }, + "fn entry" + ); + propertyEqual( + properties.getFirstEntry("n"), + { + name: "n", + params: {}, + type: "text", + value: ["Test", "Mike", "", "", ""], + }, + "n entry" + ); + propertyEqual( + properties.getFirstEntry("email"), + { + name: "email", + params: { pref: 1 }, + type: "text", + value: "mike@test.invalid", + }, + "email entry" + ); + propertyEqual( + properties.getFirstEntry("x-custom1"), + { + name: "x-custom1", + params: {}, + type: "text", + value: "custom one", + }, + "custom1 entry" + ); + propertyEqual( + properties.getFirstEntry("x-custom2"), + { + name: "x-custom2", + params: {}, + type: "text", + value: "custom two", + }, + "custom2 entry" + ); + propertyEqual( + properties.getFirstEntry("x-custom3"), + { + name: "x-custom3", + params: {}, + type: "text", + value: "custom three", + }, + "custom3 entry" + ); + propertyEqual( + properties.getFirstEntry("x-custom4"), + { + name: "x-custom4", + params: {}, + type: "text", + value: "custom four", + }, + "custom4 entry" + ); + + let outProperties = properties.toPropertyMap(); + Assert.equal(outProperties.size, 8, "property count"); + for (let [key, value] of inProperties) { + Assert.equal(outProperties.get(key), value, `${key} property`); + } + + // Tests that `toPropertyMap` doesn't break multi-value entries, which could + // happen if `toAbCard` operates on the original entry instead of a clone. + properties = new VCardProperties(); + properties.addEntry( + new VCardPropertyEntry("org", {}, "text", ["one", "two", "three", "four"]) + ); + properties.toPropertyMap(); + Assert.deepEqual(properties.getFirstValue("org"), [ + "one", + "two", + "three", + "four", + ]); +}); + +/** + * Tests adding to and removing from VCardProperties using VCardPropertyEntry. + */ +add_task(function testEntryMethods() { + // Sanity check. + + let props = new VCardProperties(); + Assert.deepEqual(props.entries, [], "props has no entries"); + + // Add property entries. + + // Real VCardPropertyEntry objects. + let charlie = new VCardPropertyEntry( + "email", + { type: "home" }, + "text", + "charlie@invalid" + ); + let delta = new VCardPropertyEntry( + "email", + { type: "work" }, + "text", + "delta@invalid" + ); + + // Ordinary objects for Assert.deepEqual comparison. Use these objects to be + // sure of the values being tested. + let data = { + charlie: { + name: "email", + params: { type: "home" }, + type: "text", + value: "charlie@invalid", + }, + delta: { + name: "email", + params: { type: "work" }, + type: "text", + value: "delta@invalid", + }, + juliet: { + name: "email", + params: { type: "home" }, + type: "text", + value: "juliet@invalid", + }, + }; + + Assert.ok(props.addEntry(charlie)); + propertyArrayEqual( + props.getAllEntries("email"), + [data.charlie], + "props.email has one entry" + ); + Assert.deepEqual( + props.getAllValues("email"), + ["charlie@invalid"], + "props.email has one value" + ); + Assert.equal( + props.getFirstValue("email"), + "charlie@invalid", + "props.email has a first value" + ); + propertyArrayEqual(props.entries, [data.charlie], "props has one entry"); + + Assert.ok(props.addEntry(delta)); + propertyArrayEqual( + props.getAllEntries("email"), + [data.charlie, data.delta], + "props.email has two entries" + ); + Assert.deepEqual( + props.getAllValues("email"), + ["charlie@invalid", "delta@invalid"], + "props.email has two values" + ); + Assert.equal( + props.getFirstValue("email"), + "charlie@invalid", + "props.email has a first value" + ); + propertyArrayEqual( + props.entries, + [data.charlie, data.delta], + "props has two entries" + ); + + Assert.ok(!props.addEntry(charlie)); + propertyArrayEqual( + props.entries, + [data.charlie, data.delta], + "props still has two entries" + ); + + // Update a property entry. + + charlie.value = "juliet@invalid"; + propertyArrayEqual( + props.getAllEntries("email"), + [data.juliet, data.delta], + "props.email has two entries" + ); + Assert.deepEqual( + props.getAllValues("email"), + ["juliet@invalid", "delta@invalid"], + "props.email has two values" + ); + Assert.equal( + props.getFirstValue("email"), + "juliet@invalid", + "props.email has a first value" + ); + propertyArrayEqual( + props.entries, + [data.juliet, data.delta], + "props has two entries" + ); + + // Clone a property entry. + + let juliet = charlie.clone(); + Assert.notEqual( + juliet, + charlie, + "cloned VCardPropertyEntry is not the same object" + ); + propertyEqual( + juliet, + data.juliet, + "cloned VCardPropertyEntry has the same properties" + ); + + // Delete a property entry. + + Assert.ok(props.removeEntry(delta)); + propertyArrayEqual( + props.getAllEntries("email"), + [data.juliet], + "props.email has one entry" + ); + Assert.deepEqual( + props.getAllValues("email"), + ["juliet@invalid"], + "props.email has one value" + ); + Assert.equal( + props.getFirstValue("email"), + "juliet@invalid", + "props.email has a first value" + ); + propertyArrayEqual(props.entries, [data.juliet], "props has one entry"); + + // Delete a property entry using a clone of it. + + Assert.ok(props.removeEntry(juliet)); + propertyArrayEqual(props.entries, [], "all entries removed"); +}); + +/** + * Tests adding to and removing from VCardProperties using names and values. + * Uses the vCard 3 default entry types. + */ +add_task(function testValueMethods3() { + let props = new VCardProperties(); + + // Add a value. + + let first = props.addValue("tel", "1234567"); + propertyEqual(first, { + name: "tel", + params: {}, + type: "phone-number", + value: "1234567", + }); + propertyArrayEqual(props.entries, [ + { name: "tel", params: {}, type: "phone-number", value: "1234567" }, + ]); + + // Add a second value. + + let second = props.addValue("tel", "2345678"); + propertyEqual(second, { + name: "tel", + params: {}, + type: "phone-number", + value: "2345678", + }); + propertyArrayEqual(props.entries, [ + { name: "tel", params: {}, type: "phone-number", value: "1234567" }, + { name: "tel", params: {}, type: "phone-number", value: "2345678" }, + ]); + + // Add a value that already exists. The existing property should be returned. + + let secondCopy = props.addValue("tel", "2345678"); + Assert.equal(secondCopy, second); + propertyArrayEqual(props.entries, [ + { name: "tel", params: {}, type: "phone-number", value: "1234567" }, + { name: "tel", params: {}, type: "phone-number", value: "2345678" }, + ]); + + // Add a third value. + + let third = props.addValue("tel", "3456789"); + propertyEqual(third, { + name: "tel", + params: {}, + type: "phone-number", + value: "3456789", + }); + propertyArrayEqual(props.entries, [ + { name: "tel", params: {}, type: "phone-number", value: "1234567" }, + { name: "tel", params: {}, type: "phone-number", value: "2345678" }, + { name: "tel", params: {}, type: "phone-number", value: "3456789" }, + ]); + + // Remove the second value. + + props.removeValue("tel", "2345678"); + propertyArrayEqual(props.entries, [ + { name: "tel", params: {}, type: "phone-number", value: "1234567" }, + { name: "tel", params: {}, type: "phone-number", value: "3456789" }, + ]); + + // Remove a value that's already been removed. + + props.removeValue("tel", "2345678"); + propertyArrayEqual(props.entries, [ + { name: "tel", params: {}, type: "phone-number", value: "1234567" }, + { name: "tel", params: {}, type: "phone-number", value: "3456789" }, + ]); + + // Remove a value that never existed. + + props.removeValue("tel", "4567890"); + propertyArrayEqual(props.entries, [ + { name: "tel", params: {}, type: "phone-number", value: "1234567" }, + { name: "tel", params: {}, type: "phone-number", value: "3456789" }, + ]); + + // Remove the first value. + + props.removeValue("tel", "1234567"); + propertyArrayEqual(props.entries, [ + { name: "tel", params: {}, type: "phone-number", value: "3456789" }, + ]); + + // Remove the last value. + + props.removeValue("tel", "3456789"); + propertyArrayEqual(props.entries, []); +}); + +/** + * Tests adding to and removing from VCardProperties using names and values. + * Uses the vCard 4 default entry types. + */ +add_task(function testValueMethods4() { + let props = new VCardProperties("4.0"); + + // Add a value. + + let first = props.addValue("tel", "tel:1234567"); + propertyEqual(first, { + name: "tel", + params: {}, + type: "uri", + value: "tel:1234567", + }); + propertyArrayEqual(props.entries, [ + { name: "version", params: {}, type: "text", value: "4.0" }, + { name: "tel", params: {}, type: "uri", value: "tel:1234567" }, + ]); + + // Add a second value. + + let second = props.addValue("tel", "tel:2345678"); + propertyEqual(second, { + name: "tel", + params: {}, + type: "uri", + value: "tel:2345678", + }); + propertyArrayEqual(props.entries, [ + { name: "version", params: {}, type: "text", value: "4.0" }, + { name: "tel", params: {}, type: "uri", value: "tel:1234567" }, + { name: "tel", params: {}, type: "uri", value: "tel:2345678" }, + ]); + + // Add a value that already exists. The existing property should be returned. + + let secondCopy = props.addValue("tel", "tel:2345678"); + Assert.equal(secondCopy, second); + propertyArrayEqual(props.entries, [ + { name: "version", params: {}, type: "text", value: "4.0" }, + { name: "tel", params: {}, type: "uri", value: "tel:1234567" }, + { name: "tel", params: {}, type: "uri", value: "tel:2345678" }, + ]); + + // Add a third value. + + let third = props.addValue("tel", "tel:3456789"); + propertyEqual(third, { + name: "tel", + params: {}, + type: "uri", + value: "tel:3456789", + }); + propertyArrayEqual(props.entries, [ + { name: "version", params: {}, type: "text", value: "4.0" }, + { name: "tel", params: {}, type: "uri", value: "tel:1234567" }, + { name: "tel", params: {}, type: "uri", value: "tel:2345678" }, + { name: "tel", params: {}, type: "uri", value: "tel:3456789" }, + ]); + + // Remove the second value. + + props.removeValue("tel", "tel:2345678"); + propertyArrayEqual(props.entries, [ + { name: "version", params: {}, type: "text", value: "4.0" }, + { name: "tel", params: {}, type: "uri", value: "tel:1234567" }, + { name: "tel", params: {}, type: "uri", value: "tel:3456789" }, + ]); + + // Remove a value that's already been removed. + + props.removeValue("tel", "tel:2345678"); + propertyArrayEqual(props.entries, [ + { name: "version", params: {}, type: "text", value: "4.0" }, + { name: "tel", params: {}, type: "uri", value: "tel:1234567" }, + { name: "tel", params: {}, type: "uri", value: "tel:3456789" }, + ]); + + // Remove a value that never existed. + + props.removeValue("tel", "tel:4567890"); + propertyArrayEqual(props.entries, [ + { name: "version", params: {}, type: "text", value: "4.0" }, + { name: "tel", params: {}, type: "uri", value: "tel:1234567" }, + { name: "tel", params: {}, type: "uri", value: "tel:3456789" }, + ]); + + // Remove the first value. + + props.removeValue("tel", "tel:1234567"); + propertyArrayEqual(props.entries, [ + { name: "version", params: {}, type: "text", value: "4.0" }, + { name: "tel", params: {}, type: "uri", value: "tel:3456789" }, + ]); + + // Remove the last value. + + props.removeValue("tel", "tel:3456789"); + propertyArrayEqual(props.entries, [ + { name: "version", params: {}, type: "text", value: "4.0" }, + ]); +}); + +/** + * Tests retrieving entries and values in preference order. + */ +add_task(function testSortMethods() { + let props = new VCardProperties(); + props.addEntry(new VCardPropertyEntry("email", {}, "text", "third@invalid")); + props.addEntry( + new VCardPropertyEntry("email", { pref: 2 }, "text", "second@invalid") + ); + props.addEntry(new VCardPropertyEntry("email", {}, "text", "fourth@invalid")); + props.addEntry( + new VCardPropertyEntry("email", { pref: 1 }, "text", "first@invalid") + ); + + propertyArrayEqual(props.getAllEntriesSorted("email"), [ + { + name: "email", + params: { pref: 1 }, + type: "text", + value: "first@invalid", + }, + { + name: "email", + params: { pref: 2 }, + type: "text", + value: "second@invalid", + }, + { name: "email", params: {}, type: "text", value: "third@invalid" }, + { name: "email", params: {}, type: "text", value: "fourth@invalid" }, + ]); + + Assert.deepEqual(props.getAllValuesSorted("email"), [ + "first@invalid", + "second@invalid", + "third@invalid", + "fourth@invalid", + ]); +}); + +/** + * Tests the `clone` method of VCardProperties. + */ +add_task(function testClone() { + let properties = VCardProperties.fromVCard( + formatVCard` + BEGIN:VCARD + FN:this is a test + N:test;this;is,a;; + EMAIL;PREF=1;TYPE=WORK:test@invalid + EMAIL:test@test.invalid + END:VCARD` + ); + let clone = properties.clone(); + + Assert.deepEqual(clone.entries, properties.entries); + Assert.notEqual(clone.entries, properties.entries); + + for (let i = 0; i < 4; i++) { + Assert.deepEqual(clone.entries[i].value, properties.entries[i].value); + Assert.notEqual(clone.entries[i], properties.entries[i]); + Assert.ok(clone.entries[i].equals(properties.entries[i])); + } + + Assert.equal(clone.toVCard(), properties.toVCard()); +}); + +/** + * Tests that entries with a group prefix are correctly handled, and the + * `getGroupedEntries` method of VCardProperties. + */ +add_task(function testGroupEntries() { + let vCard = formatVCard` + BEGIN:VCARD + GROUP1.FN:test + GROUP1.X-FOO:bar + NOTE:this doesn't have a group + END:VCARD`; + + let properties = VCardProperties.fromVCard(vCard); + + let data = [ + { + name: "fn", + params: { + group: "group1", + }, + type: "text", + value: "test", + }, + { + name: "x-foo", + params: { + group: "group1", + }, + type: "unknown", + value: "bar", + }, + { + name: "note", + params: {}, + type: "text", + value: "this doesn't have a group", + }, + ]; + + propertyArrayEqual(properties.entries, data); + Assert.equal(properties.toVCard(), vCard); + propertyArrayEqual(properties.getGroupedEntries("group1"), data.slice(0, 2)); + + let clone = properties.clone(); + propertyArrayEqual(clone.entries, data); + Assert.equal(clone.toVCard(), vCard); + propertyArrayEqual(clone.getGroupedEntries("group1"), data.slice(0, 2)); +}); + +/** + * Tests that we correctly fix Google's bad escaping of colons in values, and + * other characters in URI values. + */ +add_task(function testGoogleEscaping() { + let vCard = formatVCard` + BEGIN:VCARD + VERSION:3.0 + N:test;en\\\\c\\:oding;;; + FN:en\\\\c\\:oding test + TITLE:title\\:title\\;title\\,title\\\\title\\\\\\:title\\\\\\;title\\\\\\,title\\\\\\\\ + TEL:tel\\:0123\\\\4567 + EMAIL:test\\\\test@invalid + NOTE:notes\\:\\nnotes\\;\\nnotes\\,\\nnotes\\\\ + URL:http\\://host/url\\:url\\;url\\,url\\\\url + END:VCARD`; + + let goodVCard = formatVCard` + BEGIN:VCARD + VERSION:3.0 + N:test;en\\\\c:oding;;; + FN:en\\\\c:oding test + TITLE:title:title\\;title\\,title\\\\title\\\\:title\\\\\\;title\\\\\\,title\\\\\\\\ + TEL:tel:01234567 + EMAIL:test\\\\test@invalid + NOTE:notes:\\nnotes\\;\\nnotes\\,\\nnotes\\\\ + URL:http://host/url:url;url,url\\url + END:VCARD`; + + let data = [ + { + name: "version", + params: {}, + type: "text", + value: "3.0", + }, + { + name: "n", + params: {}, + type: "text", + value: ["test", "en\\c:oding", "", "", ""], + }, + { + name: "fn", + params: {}, + type: "text", + value: "en\\c:oding test", + }, + { + name: "title", + params: {}, + type: "text", + value: "title:title;title,title\\title\\:title\\;title\\,title\\\\", + }, + { + name: "tel", + params: {}, + type: "phone-number", + value: "tel:01234567", + }, + { + name: "email", + params: {}, + type: "text", + value: "test\\test@invalid", + }, + { + name: "note", + params: {}, + type: "text", + value: "notes:\nnotes;\nnotes,\nnotes\\", + }, + { + name: "url", + params: {}, + type: "uri", + value: "http://host/url:url;url,url\\url", + }, + ]; + + let properties = VCardProperties.fromVCard(vCard, { isGoogleCardDAV: true }); + propertyArrayEqual(properties.entries, data); + Assert.equal(properties.toVCard(), goodVCard); + + let goodProperties = VCardProperties.fromVCard(goodVCard); + propertyArrayEqual(goodProperties.entries, data); + Assert.equal(goodProperties.toVCard(), goodVCard); +}); |