Tasks: TickTick history sync (export + idempotent import, start_at/time_zone); trackers ordered tasks, money, books with single-noun names
All checks were successful
Deploy production / deploy (push) Successful in 2m33s
All checks were successful
Deploy production / deploy (push) Successful in 2m33s
This commit is contained in:
80
src/lib/server/ticktick.test.ts
Normal file
80
src/lib/server/ticktick.test.ts
Normal file
@@ -0,0 +1,80 @@
|
||||
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 }
|
||||
]
|
||||
} as Parameters<typeof mapTask>[0]);
|
||||
expect(rows).toHaveLength(3);
|
||||
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');
|
||||
});
|
||||
});
|
||||
Reference in New Issue
Block a user