This takes part of the work being done in #96 that was reverted but still useful. Note Tailwind and Fresh weren't upgraded because there's no security vulnerability in either, and I have found the new versions to be worse in performance. Thos will likely stay at those fixed versions going forward.
503 lines
9.8 KiB
TypeScript
503 lines
9.8 KiB
TypeScript
import { assertEquals, assertMatch } from '@std/assert';
|
|
|
|
import { generateVCard, getIdFromVCard, parseVCard, splitTextIntoVCards, updateVCard } from './contacts.ts';
|
|
|
|
Deno.test('that getIdFromVCard works', () => {
|
|
const tests: { input: string; expected?: string; shouldBeUUID?: boolean }[] = [
|
|
{
|
|
input: `BEGIN:VCARD
|
|
VERSION:4.0
|
|
UID:12345-abcde-67890
|
|
FN:John Doe
|
|
END:VCARD`,
|
|
expected: '12345-abcde-67890',
|
|
},
|
|
{
|
|
input: `BEGIN:VCARD
|
|
VERSION:3.0
|
|
FN:Jane Smith
|
|
UID:jane-smith-uuid
|
|
EMAIL:jane@example.com
|
|
END:VCARD`,
|
|
expected: 'jane-smith-uuid',
|
|
},
|
|
{
|
|
input: `BEGIN:VCARD
|
|
VERSION:4.0
|
|
FN:No UID Contact
|
|
EMAIL:nouid@example.com
|
|
END:VCARD`,
|
|
shouldBeUUID: true,
|
|
},
|
|
{
|
|
input: `BEGIN:VCARD
|
|
VERSION:4.0
|
|
UID: spaced-uid
|
|
FN:Spaced UID
|
|
END:VCARD`,
|
|
expected: 'spaced-uid',
|
|
},
|
|
];
|
|
|
|
for (const test of tests) {
|
|
const output = getIdFromVCard(test.input);
|
|
if (test.expected) {
|
|
assertEquals(output, test.expected);
|
|
} else if (test.shouldBeUUID) {
|
|
// Check that it's a valid UUID format
|
|
assertMatch(output, /^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i);
|
|
}
|
|
}
|
|
});
|
|
|
|
Deno.test('that splitTextIntoVCards works', () => {
|
|
const tests: { input: string; expected: string[] }[] = [
|
|
{
|
|
input: `BEGIN:VCARD
|
|
VERSION:4.0
|
|
UID:1
|
|
FN:John Doe
|
|
END:VCARD
|
|
BEGIN:VCARD
|
|
VERSION:4.0
|
|
UID:2
|
|
FN:Jane Smith
|
|
END:VCARD`,
|
|
expected: [
|
|
`BEGIN:VCARD
|
|
VERSION:4.0
|
|
UID:1
|
|
FN:John Doe
|
|
END:VCARD`,
|
|
`BEGIN:VCARD
|
|
VERSION:4.0
|
|
UID:2
|
|
FN:Jane Smith
|
|
END:VCARD`,
|
|
],
|
|
},
|
|
{
|
|
input: `BEGIN:VCARD
|
|
VERSION:3.0
|
|
FN:Single Contact
|
|
EMAIL:single@example.com
|
|
END:VCARD`,
|
|
expected: [
|
|
`BEGIN:VCARD
|
|
VERSION:3.0
|
|
FN:Single Contact
|
|
EMAIL:single@example.com
|
|
END:VCARD`,
|
|
],
|
|
},
|
|
{
|
|
input: '',
|
|
expected: [],
|
|
},
|
|
{
|
|
input: `BEGIN:VCARD
|
|
VERSION:4.0
|
|
FN:Incomplete Contact`,
|
|
expected: [],
|
|
},
|
|
];
|
|
|
|
for (const test of tests) {
|
|
const output = splitTextIntoVCards(test.input);
|
|
assertEquals(output, test.expected);
|
|
}
|
|
});
|
|
|
|
Deno.test('that generateVCard works', () => {
|
|
const tests: { input: { contactId: string; firstName: string; lastName?: string }; expected: string }[] = [
|
|
{
|
|
input: { contactId: 'test-123', firstName: 'John', lastName: 'Doe' },
|
|
expected: `BEGIN:VCARD
|
|
VERSION:4.0
|
|
N:Doe;John;
|
|
FN:John Doe
|
|
UID:test-123
|
|
END:VCARD`,
|
|
},
|
|
{
|
|
input: { contactId: 'single-name', firstName: 'Madonna' },
|
|
expected: `BEGIN:VCARD
|
|
VERSION:4.0
|
|
N:;Madonna;
|
|
FN:Madonna
|
|
UID:single-name
|
|
END:VCARD`,
|
|
},
|
|
{
|
|
input: { contactId: 'special-chars', firstName: 'John,Test', lastName: 'Doe\nSmith' },
|
|
expected: `BEGIN:VCARD
|
|
VERSION:4.0
|
|
N:Doe\\nSmith;John\\,Test;
|
|
FN:John\\,Test Doe\\nSmith
|
|
UID:special-chars
|
|
END:VCARD`,
|
|
},
|
|
];
|
|
|
|
for (const test of tests) {
|
|
const output = generateVCard(test.input.contactId, test.input.firstName, test.input.lastName);
|
|
assertEquals(output, test.expected);
|
|
}
|
|
});
|
|
|
|
Deno.test('that updateVCard works', () => {
|
|
const tests: {
|
|
input: {
|
|
vCard: string;
|
|
updates: {
|
|
firstName?: string;
|
|
lastName?: string;
|
|
email?: string;
|
|
phone?: string;
|
|
notes?: string;
|
|
};
|
|
};
|
|
expected: string;
|
|
}[] = [
|
|
{
|
|
input: {
|
|
vCard: `BEGIN:VCARD
|
|
VERSION:4.0
|
|
UID:test-123
|
|
N:Doe;John;
|
|
FN:John Doe
|
|
END:VCARD`,
|
|
updates: { firstName: 'Jane', lastName: 'Smith' },
|
|
},
|
|
expected: `BEGIN:VCARD
|
|
VERSION:4.0
|
|
UID:test-123
|
|
N:Smith;Jane;
|
|
FN:Jane Smith
|
|
END:VCARD`,
|
|
},
|
|
{
|
|
input: {
|
|
vCard: `BEGIN:VCARD
|
|
VERSION:4.0
|
|
UID:test-456
|
|
N:Doe;John;
|
|
FN:John Doe
|
|
EMAIL:old@example.com
|
|
TEL:+1234567890
|
|
END:VCARD`,
|
|
updates: { email: 'new@example.com', phone: '+9876543210' },
|
|
},
|
|
expected: `BEGIN:VCARD
|
|
VERSION:4.0
|
|
UID:test-456
|
|
N:Doe;John;
|
|
FN:John Doe
|
|
EMAIL:new@example.com
|
|
TEL:+9876543210
|
|
END:VCARD`,
|
|
},
|
|
{
|
|
input: {
|
|
vCard: `BEGIN:VCARD
|
|
VERSION:4.0
|
|
UID:test-789
|
|
N:Doe;John;
|
|
FN:John Doe
|
|
END:VCARD`,
|
|
updates: { email: 'added@example.com', phone: '+1111111111', notes: 'Test notes' },
|
|
},
|
|
expected: `BEGIN:VCARD
|
|
VERSION:4.0
|
|
UID:test-789
|
|
N:Doe;John;
|
|
FN:John Doe
|
|
EMAIL;TYPE=HOME:added@example.com
|
|
TEL;TYPE=HOME:+1111111111
|
|
NOTE:Test notes
|
|
END:VCARD`,
|
|
},
|
|
{
|
|
input: {
|
|
vCard: `BEGIN:VCARD
|
|
VERSION:4.0
|
|
UID:test-special
|
|
N:Doe;John;
|
|
FN:John Doe
|
|
NOTE:Old notes
|
|
END:VCARD`,
|
|
updates: { notes: 'New notes\nwith newlines, and commas' },
|
|
},
|
|
expected: `BEGIN:VCARD
|
|
VERSION:4.0
|
|
UID:test-special
|
|
N:Doe;John;
|
|
FN:John Doe
|
|
NOTE:New notes\\nwith newlines\\, and commas
|
|
END:VCARD`,
|
|
},
|
|
{
|
|
input: {
|
|
vCard: `BEGIN:VCARD
|
|
VERSION:4.0
|
|
UID:test-carriage
|
|
N:Doe;John;
|
|
FN:John Doe
|
|
END:VCARD`,
|
|
updates: { notes: 'Notes with\r\ncarriage returns' },
|
|
},
|
|
expected: `BEGIN:VCARD
|
|
VERSION:4.0
|
|
UID:test-carriage
|
|
N:Doe;John;
|
|
FN:John Doe
|
|
NOTE:Notes with\\ncarriage returns
|
|
END:VCARD`,
|
|
},
|
|
];
|
|
|
|
for (const test of tests) {
|
|
const output = updateVCard(test.input.vCard, test.input.updates);
|
|
assertEquals(output, test.expected);
|
|
}
|
|
});
|
|
|
|
Deno.test('that parseVCard works', () => {
|
|
const tests: {
|
|
input: string;
|
|
expected: Array<{
|
|
uid?: string;
|
|
firstName?: string;
|
|
lastName?: string;
|
|
middleNames?: string[];
|
|
title?: string;
|
|
email?: string;
|
|
phone?: string;
|
|
notes?: string;
|
|
}>;
|
|
}[] = [
|
|
{
|
|
input: `BEGIN:VCARD
|
|
VERSION:4.0
|
|
UID:test-123
|
|
N:Doe;John;Middle;Jr
|
|
FN:John Middle Doe Jr
|
|
EMAIL;TYPE=HOME:john@example.com
|
|
TEL;TYPE=HOME:+1234567890
|
|
NOTE:Test contact notes
|
|
END:VCARD`,
|
|
expected: [{
|
|
uid: 'test-123',
|
|
firstName: 'John',
|
|
lastName: 'Doe',
|
|
middleNames: ['Middle'],
|
|
title: 'Jr',
|
|
email: 'john@example.com',
|
|
phone: '+1234567890',
|
|
notes: 'Test contact notes',
|
|
}],
|
|
},
|
|
{
|
|
input: `BEGIN:VCARD
|
|
VERSION:3.0
|
|
UID:test-456
|
|
N:Smith;Jane;;
|
|
FN:Jane Smith
|
|
EMAIL:jane@example.com
|
|
TEL:+9876543210
|
|
END:VCARD`,
|
|
expected: [{
|
|
uid: 'test-456',
|
|
firstName: 'Jane',
|
|
lastName: 'Smith',
|
|
middleNames: [],
|
|
title: '',
|
|
email: 'jane@example.com',
|
|
phone: '+9876543210',
|
|
}],
|
|
},
|
|
{
|
|
input: `BEGIN:VCARD
|
|
VERSION:4.0
|
|
UID:multi-1
|
|
N:Doe;John;
|
|
FN:John Doe
|
|
END:VCARD
|
|
BEGIN:VCARD
|
|
VERSION:4.0
|
|
UID:multi-2
|
|
N:Smith;Jane;
|
|
FN:Jane Smith
|
|
EMAIL;PREF=1:jane@example.com
|
|
END:VCARD`,
|
|
expected: [
|
|
{
|
|
uid: 'multi-1',
|
|
firstName: 'John',
|
|
lastName: 'Doe',
|
|
middleNames: [],
|
|
title: '',
|
|
},
|
|
{
|
|
uid: 'multi-2',
|
|
firstName: 'Jane',
|
|
lastName: 'Smith',
|
|
middleNames: [],
|
|
title: '',
|
|
email: 'jane@example.com',
|
|
},
|
|
],
|
|
},
|
|
{
|
|
input: `BEGIN:VCARD
|
|
VERSION:4.0
|
|
UID:escaped-contact
|
|
N:Test;Contact;
|
|
FN:Contact Test
|
|
NOTE:Notes with\\nescaped newlines\\, and commas
|
|
END:VCARD`,
|
|
expected: [{
|
|
uid: 'escaped-contact',
|
|
firstName: 'Contact',
|
|
lastName: 'Test',
|
|
middleNames: [],
|
|
title: '',
|
|
notes: 'Notes with\nescaped newlines, and commas',
|
|
}],
|
|
},
|
|
{
|
|
input: `BEGIN:VCARD
|
|
VERSION:2.1
|
|
UID:version-21
|
|
N:Old;Format;
|
|
FN:Format Old
|
|
EMAIL:old@example.com
|
|
TEL:+1111111111
|
|
END:VCARD`,
|
|
expected: [{
|
|
uid: 'version-21',
|
|
firstName: 'Format',
|
|
lastName: 'Old',
|
|
middleNames: [],
|
|
title: '',
|
|
email: 'old@example.com',
|
|
phone: '+1111111111',
|
|
}],
|
|
},
|
|
{
|
|
input: `BEGIN:VCARD
|
|
VERSION:4.0
|
|
UID:email-variations
|
|
N:Test;Email;
|
|
FN:Email Test
|
|
EMAIL:direct@example.com
|
|
EMAIL;TYPE=WORK:work@example.com
|
|
TEL:+1234567890
|
|
TEL;TYPE=WORK:+9876543210
|
|
END:VCARD`,
|
|
expected: [{
|
|
uid: 'email-variations',
|
|
firstName: 'Email',
|
|
lastName: 'Test',
|
|
middleNames: [],
|
|
title: '',
|
|
email: 'direct@example.com', // Only first email is captured
|
|
phone: '+1234567890', // Only first phone is captured
|
|
}],
|
|
},
|
|
{
|
|
input: `BEGIN:VCARD
|
|
VERSION:5.0
|
|
UID:invalid-version
|
|
N:Invalid;Version;
|
|
FN:Version Invalid
|
|
END:VCARD`,
|
|
expected: [{
|
|
uid: 'invalid-version',
|
|
firstName: 'Version',
|
|
lastName: 'Invalid',
|
|
middleNames: [],
|
|
title: '',
|
|
}],
|
|
},
|
|
{
|
|
input: `BEGIN:VCARD
|
|
VERSION:4.0
|
|
UID:no-first-name
|
|
N:LastOnly;;
|
|
FN:LastOnly
|
|
END:VCARD`,
|
|
expected: [{ uid: 'no-first-name' }],
|
|
},
|
|
{
|
|
input: `BEGIN:VCARD
|
|
VERSION:4.0
|
|
UID:empty-uid
|
|
N:Test;Empty;
|
|
FN:Empty Test
|
|
END:VCARD`,
|
|
expected: [{
|
|
firstName: 'Empty',
|
|
lastName: 'Test',
|
|
middleNames: [],
|
|
title: '',
|
|
uid: 'empty-uid',
|
|
}],
|
|
},
|
|
];
|
|
|
|
for (const test of tests) {
|
|
const output = parseVCard(test.input);
|
|
assertEquals(output, test.expected);
|
|
}
|
|
});
|
|
|
|
Deno.test('that parseVCard handles edge cases', () => {
|
|
const edgeCases: { input: string; description: string; expected: any[] }[] = [
|
|
{
|
|
input: '',
|
|
description: 'empty string',
|
|
expected: [],
|
|
},
|
|
{
|
|
input: 'Not a vCard at all',
|
|
description: 'invalid format',
|
|
expected: [],
|
|
},
|
|
{
|
|
input: `BEGIN:VCARD
|
|
VERSION:4.0
|
|
UID:incomplete`,
|
|
description: 'incomplete vCard without END',
|
|
expected: [],
|
|
},
|
|
{
|
|
input: `BEGIN:VCARD
|
|
VERSION:4.0
|
|
UID:missing-required
|
|
FN:Missing Required Fields
|
|
END:VCARD`,
|
|
description: 'vCard without N field',
|
|
expected: [{ uid: 'missing-required' }],
|
|
},
|
|
{
|
|
input: `BEGIN:VCARD
|
|
VERSION:4.0
|
|
UID:empty-fields
|
|
N:;;;
|
|
FN:Empty Fields
|
|
EMAIL:
|
|
TEL:
|
|
NOTE:
|
|
END:VCARD`,
|
|
description: 'vCard with empty field values',
|
|
expected: [{ uid: 'empty-fields', notes: '' }],
|
|
},
|
|
];
|
|
|
|
for (const test of edgeCases) {
|
|
const output = parseVCard(test.input);
|
|
assertEquals(output, test.expected, `Failed for: ${test.description}`);
|
|
}
|
|
});
|