Files
website/src/lib/server/ticktick.test.ts
Stan Runge 1754ea9316
All checks were successful
Deploy production / deploy (push) Successful in 2m34s
Tasks: ignore placeholder checklist completion dates from TickTick
2026-10-24 03:10:53 +00:00

84 lines
3.0 KiB
TypeScript

import { describe, expect, it } from 'vitest';
import {
mapTask,
moreComplete,
normTitle,
slugify,
ttDate,
ttDue,
ttPriority,
ttRecurrence,
ttReminder,
ttStatus
} from './ticktick';
describe('ticktick mapping', () => {
it('parses TickTick timestamps', () => {
expect(ttDate('2024-06-11T17:30:36.000+0000')?.toISOString()).toBe('2024-06-11T17:30:36.000Z');
expect(ttDate('')).toBeNull();
});
it('keeps the calendar date of all-day dues and converts timed dues to Amsterdam', () => {
expect(ttDue('2025-03-11T23:00:00.000+0000', true, 'Europe/Amsterdam')).toEqual({
dueDate: '2025-03-12',
dueTime: null
});
expect(ttDue('2026-05-04T04:00:00.000+0000', false, 'Europe/Berlin')).toEqual({
dueDate: '2026-05-04',
dueTime: '06:00'
});
expect(ttDue(undefined, true, undefined)).toEqual({ dueDate: null, dueTime: null });
});
it('maps priority, status, recurrence and reminders', () => {
expect([5, 3, 1, 0, undefined].map(ttPriority)).toEqual([3, 2, 1, 0, 0]);
expect([0, 2, -1].map(ttStatus)).toEqual(['todo', 'done', 'dropped']);
expect(ttRecurrence('RRULE:FREQ=WEEKLY;INTERVAL=1')).toBe('FREQ=WEEKLY;INTERVAL=1');
const due = new Date('2026-01-01T10:00:00Z');
expect(ttReminder(['TRIGGER:-PT60M', 'TRIGGER:PT0S'], due)?.toISOString()).toBe(
'2026-01-01T09:00:00.000Z'
);
expect(ttReminder(['TRIGGER:P0DT9H0M0S'], due)?.toISOString()).toBe('2026-01-01T19:00:00.000Z');
});
it('prefers the more complete status', () => {
expect(moreComplete('done', 'todo')).toBe('done');
expect(moreComplete('todo', 'done')).toBe('done');
expect(moreComplete('in_progress', 'todo')).toBe('in_progress');
});
it('turns checklist items into subtasks', () => {
const rows = mapTask({
id: 't1',
projectId: 'inbox1',
title: ' Groceries ',
kind: 'CHECKLIST',
desc: 'weekly',
status: 2,
priority: 5,
createdTime: '2025-01-01T10:00:00.000+0000',
modifiedTime: '2025-01-02T10:00:00.000+0000',
completedTime: '2025-01-02T09:00:00.000+0000',
items: [
{ id: 'i1', title: 'milk', status: 1, completedTime: '2025-01-02T08:00:00.000+0000' },
{ id: 'i2', title: 'eggs', status: 0 },
{ id: 'i3', title: 'bread', status: 1, completedTime: 978307200000 }
]
} as Parameters<typeof mapTask>[0]);
expect(rows).toHaveLength(4);
// Placeholder completion (2001) falls back to the task's completion.
expect(rows[3].completedAt?.toISOString()).toBe('2025-01-02T09:00:00.000Z');
expect(rows[0]).toMatchObject({
title: 'Groceries',
notes: 'weekly',
status: 'done',
priority: 3
});
expect(rows[0].completedAt?.toISOString()).toBe('2025-01-02T09:00:00.000Z');
expect(rows[1]).toMatchObject({ externalId: 't1:i1', parentExternalId: 't1', status: 'done' });
// Unchecked item of a completed task follows the task.
expect(rows[2]).toMatchObject({ externalId: 't1:i2', status: 'done' });
expect(rows[2].completedAt?.toISOString()).toBe('2025-01-02T09:00:00.000Z');
});
it('slugs and normalises titles', () => {
expect(slugify('Stuhee')).toBe('stuhee');
expect(normTitle('Book haircut!')).toBe('book haircut');
});
});