style(13-03): apply Prettier formatting across repo

Mechanical reformat — no logic changes. 398 files changed, 19125
insertions(+), 16457 deletions(-). Prettier 3.8.4 with .prettierrc
(singleQuote:true, semi:true, tabWidth:2, trailingComma:all,
printWidth:100). Isolated per D-13-08 for reviewability.
This commit is contained in:
Lucas Berger
2026-06-11 20:35:18 -04:00
parent 4bc0445173
commit 982438dc10
398 changed files with 19050 additions and 16382 deletions
+197 -180
View File
@@ -10,25 +10,25 @@
* - Calendar ctag/syncToken updated after sync
*/
import { describe, it, expect, vi, beforeEach } from 'vitest'
import { describe, it, expect, vi, beforeEach } from 'vitest';
import {
SAMPLE_VEVENT_TIMED,
SAMPLE_VEVENT_ALLDAY,
SAMPLE_VEVENT_RECURRING_TIMED,
SAMPLE_VEVENT_RECURRING_ALLDAY,
} from '../helpers/db.js'
} from '../helpers/db.js';
// Track calls for assertions
const mockOnDuplicateKeyUpdate = vi.fn().mockResolvedValue([{ insertId: 1 }])
const mockValues = vi.fn().mockReturnValue({ onDuplicateKeyUpdate: mockOnDuplicateKeyUpdate })
const mockInsert = vi.fn().mockReturnValue({ values: mockValues })
const mockLimit = vi.fn().mockResolvedValue([{ id: 42 }])
const mockWhere = vi.fn().mockReturnValue({ limit: mockLimit })
const mockFrom = vi.fn().mockReturnValue({ where: mockWhere })
const mockSelect = vi.fn().mockReturnValue({ from: mockFrom })
const mockOnDuplicateKeyUpdate = vi.fn().mockResolvedValue([{ insertId: 1 }]);
const mockValues = vi.fn().mockReturnValue({ onDuplicateKeyUpdate: mockOnDuplicateKeyUpdate });
const mockInsert = vi.fn().mockReturnValue({ values: mockValues });
const mockLimit = vi.fn().mockResolvedValue([{ id: 42 }]);
const mockWhere = vi.fn().mockReturnValue({ limit: mockLimit });
const mockFrom = vi.fn().mockReturnValue({ where: mockWhere });
const mockSelect = vi.fn().mockReturnValue({ from: mockFrom });
// Prune chain: db.delete(calendarEvents).where(...)
const mockDeleteWhere = vi.fn().mockResolvedValue([])
const mockDelete = vi.fn().mockReturnValue({ where: mockDeleteWhere })
const mockDeleteWhere = vi.fn().mockResolvedValue([]);
const mockDelete = vi.fn().mockReturnValue({ where: mockDeleteWhere });
// Mock the db singleton at module level (Vitest hoisting)
vi.mock('../../src/db/client.js', () => ({
@@ -37,334 +37,350 @@ vi.mock('../../src/db/client.js', () => ({
select: mockSelect,
delete: mockDelete,
},
}))
}));
describe('syncCalendar', () => {
beforeEach(() => {
vi.clearAllMocks()
vi.clearAllMocks();
// Reset mock implementations after clearAllMocks
mockOnDuplicateKeyUpdate.mockResolvedValue([{ insertId: 1 }])
mockValues.mockReturnValue({ onDuplicateKeyUpdate: mockOnDuplicateKeyUpdate })
mockInsert.mockReturnValue({ values: mockValues })
mockLimit.mockResolvedValue([{ id: 42 }])
mockWhere.mockReturnValue({ limit: mockLimit })
mockFrom.mockReturnValue({ where: mockWhere })
mockSelect.mockReturnValue({ from: mockFrom })
mockDeleteWhere.mockResolvedValue([])
mockDelete.mockReturnValue({ where: mockDeleteWhere })
})
mockOnDuplicateKeyUpdate.mockResolvedValue([{ insertId: 1 }]);
mockValues.mockReturnValue({ onDuplicateKeyUpdate: mockOnDuplicateKeyUpdate });
mockInsert.mockReturnValue({ values: mockValues });
mockLimit.mockResolvedValue([{ id: 42 }]);
mockWhere.mockReturnValue({ limit: mockLimit });
mockFrom.mockReturnValue({ where: mockWhere });
mockSelect.mockReturnValue({ from: mockFrom });
mockDeleteWhere.mockResolvedValue([]);
mockDelete.mockReturnValue({ where: mockDeleteWhere });
});
it('stores all-day events with dtstart_date (DATE) and dtstart_utc=NULL', async () => {
const { syncCalendar } = await import('../../src/broker/sync.js')
const { syncCalendar } = await import('../../src/broker/sync.js');
const mockClient = {
fetchCalendarObjects: vi.fn().mockResolvedValue([
{ data: SAMPLE_VEVENT_ALLDAY, etag: '"etag-allday"', url: '/cal/allday.ics' },
]),
}
fetchCalendarObjects: vi
.fn()
.mockResolvedValue([
{ data: SAMPLE_VEVENT_ALLDAY, etag: '"etag-allday"', url: '/cal/allday.ics' },
]),
};
const mockDavCal = {
url: 'https://caldav.fastmail.com/dav/calendars/user/test@fm.com/Default/',
displayName: 'Test Calendar',
ctag: 'ctag-v1',
syncToken: null,
}
};
await syncCalendar(mockClient as never, mockDavCal as never, 1)
await syncCalendar(mockClient as never, mockDavCal as never, 1);
// insert called twice: calendars + calendarEvents
expect(mockInsert).toHaveBeenCalledTimes(2)
expect(mockInsert).toHaveBeenCalledTimes(2);
// Event insert: second call's values arg
const eventValuesArg = mockValues.mock.calls[1][0]
expect(eventValuesArg.allDay).toBe(true)
expect(eventValuesArg.dtstartDate).toBeTruthy()
expect(eventValuesArg.dtstartUtc).toBeNull()
})
const eventValuesArg = mockValues.mock.calls[1][0];
expect(eventValuesArg.allDay).toBe(true);
expect(eventValuesArg.dtstartDate).toBeTruthy();
expect(eventValuesArg.dtstartUtc).toBeNull();
});
it('stores timed events with dtstart_utc (TIMESTAMP) and dtstart_date=NULL', async () => {
const { syncCalendar } = await import('../../src/broker/sync.js')
const { syncCalendar } = await import('../../src/broker/sync.js');
const mockClient = {
fetchCalendarObjects: vi.fn().mockResolvedValue([
{ data: SAMPLE_VEVENT_TIMED, etag: '"etag-timed"', url: '/cal/timed.ics' },
]),
}
fetchCalendarObjects: vi
.fn()
.mockResolvedValue([
{ data: SAMPLE_VEVENT_TIMED, etag: '"etag-timed"', url: '/cal/timed.ics' },
]),
};
const mockDavCal = {
url: 'https://caldav.fastmail.com/dav/calendars/user/test@fm.com/Default/',
displayName: 'Test Calendar',
ctag: 'ctag-v1',
syncToken: null,
}
};
await syncCalendar(mockClient as never, mockDavCal as never, 1)
await syncCalendar(mockClient as never, mockDavCal as never, 1);
expect(mockInsert).toHaveBeenCalledTimes(2)
const eventValuesArg = mockValues.mock.calls[1][0]
expect(eventValuesArg.allDay).toBe(false)
expect(eventValuesArg.dtstartUtc).toBeInstanceOf(Date)
expect(eventValuesArg.dtstartDate).toBeNull()
})
expect(mockInsert).toHaveBeenCalledTimes(2);
const eventValuesArg = mockValues.mock.calls[1][0];
expect(eventValuesArg.allDay).toBe(false);
expect(eventValuesArg.dtstartUtc).toBeInstanceOf(Date);
expect(eventValuesArg.dtstartDate).toBeNull();
});
it('sets allDay=true for all-day events, allDay=false for timed', async () => {
const { syncCalendar } = await import('../../src/broker/sync.js')
const { syncCalendar } = await import('../../src/broker/sync.js');
const mockClient = {
fetchCalendarObjects: vi.fn().mockResolvedValue([
{ data: SAMPLE_VEVENT_ALLDAY, etag: '"allday"', url: '/allday.ics' },
]),
}
fetchCalendarObjects: vi
.fn()
.mockResolvedValue([{ data: SAMPLE_VEVENT_ALLDAY, etag: '"allday"', url: '/allday.ics' }]),
};
const mockDavCal = {
url: 'https://caldav.fastmail.com/dav/calendars/user/test@fm.com/Test/',
displayName: 'Test',
ctag: null,
syncToken: null,
}
};
await syncCalendar(mockClient as never, mockDavCal as never, 1)
const eventArg = mockValues.mock.calls[1][0]
expect(eventArg.allDay).toBe(true)
})
await syncCalendar(mockClient as never, mockDavCal as never, 1);
const eventArg = mockValues.mock.calls[1][0];
expect(eventArg.allDay).toBe(true);
});
it('upserts on duplicate UID within the same calendar (onDuplicateKeyUpdate called for events)', async () => {
const { syncCalendar } = await import('../../src/broker/sync.js')
const { syncCalendar } = await import('../../src/broker/sync.js');
const mockClient = {
fetchCalendarObjects: vi.fn().mockResolvedValue([
{ data: SAMPLE_VEVENT_TIMED, etag: '"etag1"', url: '/timed.ics' },
]),
}
fetchCalendarObjects: vi
.fn()
.mockResolvedValue([{ data: SAMPLE_VEVENT_TIMED, etag: '"etag1"', url: '/timed.ics' }]),
};
const mockDavCal = {
url: 'https://caldav.fastmail.com/dav/calendars/user/test@fm.com/Default/',
displayName: 'Test',
ctag: 'v2',
syncToken: null,
}
};
await syncCalendar(mockClient as never, mockDavCal as never, 1)
await syncCalendar(mockClient as never, mockDavCal as never, 1);
// onDuplicateKeyUpdate must be called for both the calendar upsert and the event upsert
expect(mockOnDuplicateKeyUpdate).toHaveBeenCalledTimes(2)
})
expect(mockOnDuplicateKeyUpdate).toHaveBeenCalledTimes(2);
});
it('stores the raw VEVENT blob in rawVevent column', async () => {
const { syncCalendar } = await import('../../src/broker/sync.js')
const { syncCalendar } = await import('../../src/broker/sync.js');
const mockClient = {
fetchCalendarObjects: vi.fn().mockResolvedValue([
{ data: SAMPLE_VEVENT_TIMED, etag: '"etag"', url: '/timed.ics' },
]),
}
fetchCalendarObjects: vi
.fn()
.mockResolvedValue([{ data: SAMPLE_VEVENT_TIMED, etag: '"etag"', url: '/timed.ics' }]),
};
const mockDavCal = {
url: 'https://caldav.fastmail.com/dav/calendars/user/test@fm.com/Default/',
displayName: 'Test',
ctag: 'v1',
syncToken: null,
}
};
await syncCalendar(mockClient as never, mockDavCal as never, 1)
const eventArg = mockValues.mock.calls[1][0]
expect(eventArg.rawVevent).toBe(SAMPLE_VEVENT_TIMED)
})
await syncCalendar(mockClient as never, mockDavCal as never, 1);
const eventArg = mockValues.mock.calls[1][0];
expect(eventArg.rawVevent).toBe(SAMPLE_VEVENT_TIMED);
});
it('sets hasRrule=true for a VEVENT with RRULE (timed recurring)', async () => {
const { syncCalendar } = await import('../../src/broker/sync.js')
const { syncCalendar } = await import('../../src/broker/sync.js');
const mockClient = {
fetchCalendarObjects: vi.fn().mockResolvedValue([
{ data: SAMPLE_VEVENT_RECURRING_TIMED, etag: '"etag-rrule"', url: '/rrule.ics' },
]),
}
fetchCalendarObjects: vi
.fn()
.mockResolvedValue([
{ data: SAMPLE_VEVENT_RECURRING_TIMED, etag: '"etag-rrule"', url: '/rrule.ics' },
]),
};
const mockDavCal = {
url: 'https://caldav.fastmail.com/dav/calendars/user/test@fm.com/Default/',
displayName: 'Test Calendar',
ctag: 'ctag-v1',
syncToken: null,
}
};
await syncCalendar(mockClient as never, mockDavCal as never, 1)
await syncCalendar(mockClient as never, mockDavCal as never, 1);
expect(mockInsert).toHaveBeenCalledTimes(2)
const eventValuesArg = mockValues.mock.calls[1][0]
expect(eventValuesArg.hasRrule).toBe(true)
})
expect(mockInsert).toHaveBeenCalledTimes(2);
const eventValuesArg = mockValues.mock.calls[1][0];
expect(eventValuesArg.hasRrule).toBe(true);
});
it('sets hasRrule=true for an all-day VEVENT with RRULE (all-day recurring)', async () => {
const { syncCalendar } = await import('../../src/broker/sync.js')
const { syncCalendar } = await import('../../src/broker/sync.js');
const mockClient = {
fetchCalendarObjects: vi.fn().mockResolvedValue([
{ data: SAMPLE_VEVENT_RECURRING_ALLDAY, etag: '"etag-rrule-allday"', url: '/rrule-allday.ics' },
{
data: SAMPLE_VEVENT_RECURRING_ALLDAY,
etag: '"etag-rrule-allday"',
url: '/rrule-allday.ics',
},
]),
}
};
const mockDavCal = {
url: 'https://caldav.fastmail.com/dav/calendars/user/test@fm.com/Default/',
displayName: 'Test Calendar',
ctag: 'ctag-v1',
syncToken: null,
}
};
await syncCalendar(mockClient as never, mockDavCal as never, 1)
await syncCalendar(mockClient as never, mockDavCal as never, 1);
expect(mockInsert).toHaveBeenCalledTimes(2)
const eventValuesArg = mockValues.mock.calls[1][0]
expect(eventValuesArg.hasRrule).toBe(true)
})
expect(mockInsert).toHaveBeenCalledTimes(2);
const eventValuesArg = mockValues.mock.calls[1][0];
expect(eventValuesArg.hasRrule).toBe(true);
});
it('sets hasRrule=false for a non-recurring timed VEVENT', async () => {
const { syncCalendar } = await import('../../src/broker/sync.js')
const { syncCalendar } = await import('../../src/broker/sync.js');
const mockClient = {
fetchCalendarObjects: vi.fn().mockResolvedValue([
{ data: SAMPLE_VEVENT_TIMED, etag: '"etag-oneoff"', url: '/oneoff.ics' },
]),
}
fetchCalendarObjects: vi
.fn()
.mockResolvedValue([
{ data: SAMPLE_VEVENT_TIMED, etag: '"etag-oneoff"', url: '/oneoff.ics' },
]),
};
const mockDavCal = {
url: 'https://caldav.fastmail.com/dav/calendars/user/test@fm.com/Default/',
displayName: 'Test Calendar',
ctag: 'ctag-v1',
syncToken: null,
}
};
await syncCalendar(mockClient as never, mockDavCal as never, 1)
await syncCalendar(mockClient as never, mockDavCal as never, 1);
expect(mockInsert).toHaveBeenCalledTimes(2)
const eventValuesArg = mockValues.mock.calls[1][0]
expect(eventValuesArg.hasRrule).toBe(false)
})
expect(mockInsert).toHaveBeenCalledTimes(2);
const eventValuesArg = mockValues.mock.calls[1][0];
expect(eventValuesArg.hasRrule).toBe(false);
});
it('includes hasRrule in onDuplicateKeyUpdate set so re-syncs self-heal the flag', async () => {
const { syncCalendar } = await import('../../src/broker/sync.js')
const { syncCalendar } = await import('../../src/broker/sync.js');
const mockClient = {
fetchCalendarObjects: vi.fn().mockResolvedValue([
{ data: SAMPLE_VEVENT_RECURRING_TIMED, etag: '"etag-v2"', url: '/rrule.ics' },
]),
}
fetchCalendarObjects: vi
.fn()
.mockResolvedValue([
{ data: SAMPLE_VEVENT_RECURRING_TIMED, etag: '"etag-v2"', url: '/rrule.ics' },
]),
};
const mockDavCal = {
url: 'https://caldav.fastmail.com/dav/calendars/user/test@fm.com/Default/',
displayName: 'Test Calendar',
ctag: 'ctag-v2',
syncToken: null,
}
};
await syncCalendar(mockClient as never, mockDavCal as never, 1)
await syncCalendar(mockClient as never, mockDavCal as never, 1);
// The second onDuplicateKeyUpdate call is for the event upsert
const eventUpdateArg = mockOnDuplicateKeyUpdate.mock.calls[1][0]
expect(eventUpdateArg.set).toHaveProperty('hasRrule', true)
})
const eventUpdateArg = mockOnDuplicateKeyUpdate.mock.calls[1][0];
expect(eventUpdateArg.set).toHaveProperty('hasRrule', true);
});
it('BUG B: scopes the calendar-row select to (userId, url), not url alone', async () => {
// Capture the predicate passed to db.select().from(calendars).where(...).limit(1).
const capturedWhere: unknown[] = []
const capturedWhere: unknown[] = [];
mockWhere.mockImplementation((pred: unknown) => {
capturedWhere.push(pred)
return { limit: mockLimit }
})
capturedWhere.push(pred);
return { limit: mockLimit };
});
const { syncCalendar } = await import('../../src/broker/sync.js')
const mockClient = { fetchCalendarObjects: vi.fn().mockResolvedValue([]) }
const { syncCalendar } = await import('../../src/broker/sync.js');
const mockClient = { fetchCalendarObjects: vi.fn().mockResolvedValue([]) };
const mockDavCal = {
url: 'https://caldav.fastmail.com/dav/calendars/user/test@fm.com/Default/',
displayName: 'Test',
ctag: 'v1',
syncToken: null,
}
};
await syncCalendar(mockClient as never, mockDavCal as never, 99)
await syncCalendar(mockClient as never, mockDavCal as never, 99);
expect(capturedWhere.length).toBeGreaterThan(0)
expect(capturedWhere.length).toBeGreaterThan(0);
const serialized = JSON.stringify(capturedWhere[0], (_k, v) =>
typeof v === 'object' && v !== null && 'name' in (v as Record<string, unknown>)
? (v as { name?: unknown }).name
: v,
)
expect(serialized).toContain('user_id')
expect(serialized).toContain('url')
})
);
expect(serialized).toContain('user_id');
expect(serialized).toContain('url');
});
it('BUG B: calendar upsert is idempotent — onDuplicateKeyUpdate fires for the calendar row', async () => {
const { syncCalendar } = await import('../../src/broker/sync.js')
const mockClient = { fetchCalendarObjects: vi.fn().mockResolvedValue([]) }
const { syncCalendar } = await import('../../src/broker/sync.js');
const mockClient = { fetchCalendarObjects: vi.fn().mockResolvedValue([]) };
const mockDavCal = {
url: 'https://caldav.fastmail.com/dav/calendars/user/test@fm.com/Default/',
displayName: 'Test',
ctag: 'v1',
syncToken: null,
}
await syncCalendar(mockClient as never, mockDavCal as never, 1)
};
await syncCalendar(mockClient as never, mockDavCal as never, 1);
// The calendar insert (call 0) must use onDuplicateKeyUpdate so the (userId,url)
// unique key makes re-polls update-in-place instead of inserting duplicate rows.
expect(mockOnDuplicateKeyUpdate).toHaveBeenCalled()
const calUpdateArg = mockOnDuplicateKeyUpdate.mock.calls[0][0]
expect(calUpdateArg.set).toHaveProperty('ctag')
})
expect(mockOnDuplicateKeyUpdate).toHaveBeenCalled();
const calUpdateArg = mockOnDuplicateKeyUpdate.mock.calls[0][0];
expect(calUpdateArg.set).toHaveProperty('ctag');
});
it('updates the calendar ctag/syncToken after a successful sync', async () => {
const { syncCalendar } = await import('../../src/broker/sync.js')
const { syncCalendar } = await import('../../src/broker/sync.js');
const mockClient = {
fetchCalendarObjects: vi.fn().mockResolvedValue([]),
}
};
const mockDavCal = {
url: 'https://caldav.fastmail.com/dav/calendars/user/test@fm.com/Default/',
displayName: 'Test Calendar',
ctag: 'new-ctag-123',
syncToken: 'sync-token-abc',
}
};
await syncCalendar(mockClient as never, mockDavCal as never, 1)
await syncCalendar(mockClient as never, mockDavCal as never, 1);
// Calendar insert values must include the new ctag and syncToken
const calValuesArg = mockValues.mock.calls[0][0]
expect(calValuesArg.ctag).toBe('new-ctag-123')
expect(calValuesArg.syncToken).toBe('sync-token-abc')
})
const calValuesArg = mockValues.mock.calls[0][0];
expect(calValuesArg.ctag).toBe('new-ctag-123');
expect(calValuesArg.syncToken).toBe('sync-token-abc');
});
// Regression: deletes must be reconciled out of the cache. Before this fix,
// syncCalendar only upserted present events, so a deleted event lingered in
// calendar_events forever and the UI showed a ghost that "wouldn't delete".
it('prunes cached events whose uid is absent from the server (delete reconciliation)', async () => {
const { syncCalendar } = await import('../../src/broker/sync.js')
const { syncCalendar } = await import('../../src/broker/sync.js');
// Server returns ONE timed event; any other cached uid for this calendar must be pruned.
const mockClient = {
fetchCalendarObjects: vi.fn().mockResolvedValue([
{ data: SAMPLE_VEVENT_TIMED, etag: '"etag-timed"', url: '/cal/timed.ics' },
]),
}
fetchCalendarObjects: vi
.fn()
.mockResolvedValue([
{ data: SAMPLE_VEVENT_TIMED, etag: '"etag-timed"', url: '/cal/timed.ics' },
]),
};
const mockDavCal = {
url: 'https://caldav.fastmail.com/dav/calendars/user/test@fm.com/Default/',
displayName: 'Test Calendar',
ctag: 'ctag-v2',
syncToken: null,
}
};
await syncCalendar(mockClient as never, mockDavCal as never, 1)
await syncCalendar(mockClient as never, mockDavCal as never, 1);
// A prune DELETE must run, scoped by calendarId AND excluding the seen uid(s).
expect(mockDelete).toHaveBeenCalledTimes(1)
expect(mockDeleteWhere).toHaveBeenCalledTimes(1)
})
expect(mockDelete).toHaveBeenCalledTimes(1);
expect(mockDeleteWhere).toHaveBeenCalledTimes(1);
});
it('prunes the entire calendar cache when the server returns zero events', async () => {
const { syncCalendar } = await import('../../src/broker/sync.js')
const { syncCalendar } = await import('../../src/broker/sync.js');
const mockClient = {
fetchCalendarObjects: vi.fn().mockResolvedValue([]),
}
};
const mockDavCal = {
url: 'https://caldav.fastmail.com/dav/calendars/user/test@fm.com/Default/',
displayName: 'Test Calendar',
ctag: 'ctag-empty',
syncToken: null,
}
};
await syncCalendar(mockClient as never, mockDavCal as never, 1)
await syncCalendar(mockClient as never, mockDavCal as never, 1);
// Empty server result → prune-all DELETE (scoped to this calendar id only).
expect(mockDelete).toHaveBeenCalledTimes(1)
expect(mockDeleteWhere).toHaveBeenCalledTimes(1)
})
expect(mockDelete).toHaveBeenCalledTimes(1);
expect(mockDeleteWhere).toHaveBeenCalledTimes(1);
});
// NEW-WR-01: When the server returns zero events (whole-cache clear), the
// onChanges callback must still receive a 'delete' change for each cached row
@@ -372,7 +388,7 @@ describe('syncCalendar', () => {
// ran inside the seenUids.length > 0 branch, so bulk/clear deletions silently
// dropped all delete change events.
it('NEW-WR-01: emits delete change events for each cached row when server returns zero events', async () => {
const { syncCalendar } = await import('../../src/broker/sync.js')
const { syncCalendar } = await import('../../src/broker/sync.js');
// First mockWhere call: calendar row lookup (ends in .limit(1)) → { limit: mockLimit }
// Second mockWhere call: pending-delete pre-capture (direct await, no .limit()) →
@@ -383,32 +399,33 @@ describe('syncCalendar', () => {
// 1. db.select().from(calendars).where(...).limit(1) — calendar row lookup
// 2. db.select({uid,title}).from(calendarEvents).where(...) — pending-delete capture (awaited directly)
mockWhere
.mockReturnValueOnce({ limit: mockLimit }) // call 1: calendar row lookup
.mockResolvedValueOnce([ // call 2: pending-delete capture
.mockReturnValueOnce({ limit: mockLimit }) // call 1: calendar row lookup
.mockResolvedValueOnce([
// call 2: pending-delete capture
{ uid: 'uid-to-delete-1', title: 'Event A' },
{ uid: 'uid-to-delete-2', title: 'Event B' },
])
]);
const mockClient = {
fetchCalendarObjects: vi.fn().mockResolvedValue([]),
}
};
const mockDavCal = {
url: 'https://caldav.fastmail.com/dav/calendars/user/test@fm.com/Default/',
displayName: 'Test Calendar',
ctag: 'ctag-empty',
syncToken: null,
}
};
const collectedChanges: import('../../src/lib/eventChangeDispatcher.js').EventChange[] = []
const collectedChanges: import('../../src/lib/eventChangeDispatcher.js').EventChange[] = [];
const onChanges = (changes: import('../../src/lib/eventChangeDispatcher.js').EventChange[]) => {
collectedChanges.push(...changes)
}
collectedChanges.push(...changes);
};
await syncCalendar(mockClient as never, mockDavCal as never, 1, onChanges)
await syncCalendar(mockClient as never, mockDavCal as never, 1, onChanges);
// The callback must have received one delete change per cached row.
expect(collectedChanges).toHaveLength(2)
expect(collectedChanges[0]).toMatchObject({ uid: 'uid-to-delete-1', operation: 'delete' })
expect(collectedChanges[1]).toMatchObject({ uid: 'uid-to-delete-2', operation: 'delete' })
})
})
expect(collectedChanges).toHaveLength(2);
expect(collectedChanges[0]).toMatchObject({ uid: 'uid-to-delete-1', operation: 'delete' });
expect(collectedChanges[1]).toMatchObject({ uid: 'uid-to-delete-2', operation: 'delete' });
});
});