mirror of
https://github.com/bewcloud/bewcloud.git
synced 2026-03-11 08:54:49 +00:00
Also upgrade all dependencies, make some unnecessary, and some other small tweaks. Because this has a big potential of unexpectedly breaking things, I'm doing a "breaking release", v3.
502 lines
9.8 KiB
TypeScript
502 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}`);
|
|
}
|
|
});
|