fix(tests): update calendar event tests for pure function API, delete orphaned todo tests

Calendar: Rewrite events.test.ts and events-recurrence.test.ts to test
pure functions (getEventsForDay, getEventsInRange, expandRecurringEvents)
from queries.ts instead of removed store methods.

Todo: Delete projects.test.ts and reminders.test.ts — the API files they
tested were removed in the local-first migration.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
Till JS 2026-03-28 16:11:08 +01:00
parent f628026b9e
commit 4d26196590
4 changed files with 193 additions and 695 deletions

View file

@ -1,27 +1,27 @@
import { describe, it, expect, vi, beforeEach } from 'vitest';
import { describe, it, expect, vi } from 'vitest';
import type { CalendarEvent } from '@calendar/shared';
import type { PaginationMeta } from '$lib/api/events';
const defaultPagination: PaginationMeta = { offset: 0, count: 0 };
// Mock local-store to avoid import.meta.env issues in tests
vi.mock('$lib/data/local-store', () => ({
eventCollection: {},
calendarCollection: {},
}));
vi.mock('$lib/api/events', () => ({
getEvents: vi.fn(),
createEvent: vi.fn(),
updateEvent: vi.fn(),
deleteEvent: vi.fn(),
vi.mock('@manacore/local-store/svelte', () => ({
useLiveQueryWithDefault: vi.fn(),
}));
vi.mock('$lib/api/birthdays', () => ({
BIRTHDAY_CALENDAR: { id: 'birthday-cal', name: 'Birthdays', color: '#ec4899' },
}));
vi.mock('@manacore/shared-ui', () => ({
toastStore: { error: vi.fn(), success: vi.fn() },
}));
import * as api from '$lib/api/events';
import { expandRecurringEvents } from '$lib/data/queries';
import { eventsStore } from './events.svelte';
const mockGetEvents = vi.mocked(api.getEvents);
const mockUpdateEvent = vi.mocked(api.updateEvent);
const mockDeleteEvent = vi.mocked(api.deleteEvent);
function makeEvent(overrides: Partial<CalendarEvent> = {}): CalendarEvent {
return {
id: 'evt-1',
@ -48,141 +48,73 @@ function makeEvent(overrides: Partial<CalendarEvent> = {}): CalendarEvent {
};
}
describe('eventsStore - recurrence', () => {
beforeEach(() => {
vi.clearAllMocks();
eventsStore.clear();
eventsStore.clearDraftEvent();
describe('expandRecurringEvents', () => {
it('should expand daily recurring event', () => {
const events = [
makeEvent({
id: 'r1',
startTime: '2026-03-01T09:00:00',
endTime: '2026-03-01T09:30:00',
recurrenceRule: 'FREQ=DAILY',
}),
];
const result = expandRecurringEvents(events, new Date('2026-03-01'), new Date('2026-03-07'));
expect(result.length).toBeGreaterThanOrEqual(6);
for (const e of result) {
expect(e.id).toContain('r1__recurrence__');
expect(e.parentEventId).toBe('r1');
}
});
describe('expansion', () => {
it('should expand daily recurring event', async () => {
mockGetEvents.mockResolvedValue({
data: [
makeEvent({
id: 'r1',
startTime: '2026-03-01T09:00:00',
endTime: '2026-03-01T09:30:00',
recurrenceRule: 'FREQ=DAILY',
}),
],
pagination: defaultPagination,
error: null,
});
await eventsStore.fetchEvents(new Date('2026-03-01'), new Date('2026-03-07'));
const events = eventsStore.events;
expect(events.length).toBeGreaterThanOrEqual(6);
for (const e of events) {
expect(e.id).toContain('r1__recurrence__');
expect(e.parentEventId).toBe('r1');
}
});
it('should preserve event duration in occurrences', async () => {
mockGetEvents.mockResolvedValue({
data: [
makeEvent({
id: 'w1',
startTime: '2026-03-02T14:00:00',
endTime: '2026-03-02T15:00:00',
recurrenceRule: 'FREQ=WEEKLY',
}),
],
pagination: defaultPagination,
error: null,
});
await eventsStore.fetchEvents(new Date('2026-03-01'), new Date('2026-03-31'));
for (const e of eventsStore.events) {
const dur = new Date(e.endTime).getTime() - new Date(e.startTime).getTime();
expect(dur).toBe(3600000);
}
});
it('should respect exceptions', async () => {
mockGetEvents.mockResolvedValue({
data: [
makeEvent({
id: 'exc',
startTime: '2026-03-01T09:00:00',
endTime: '2026-03-01T09:30:00',
recurrenceRule: 'FREQ=DAILY',
recurrenceExceptions: ['2026-03-03', '2026-03-05'],
}),
],
pagination: defaultPagination,
error: null,
});
await eventsStore.fetchEvents(new Date('2026-03-01'), new Date('2026-03-07'));
const dates = eventsStore.events.map((e) => e.id.split('__recurrence__')[1]);
expect(dates).not.toContain('2026-03-03');
expect(dates).not.toContain('2026-03-05');
expect(dates).toContain('2026-03-01');
});
it('should not expand non-recurring events', async () => {
mockGetEvents.mockResolvedValue({
data: [makeEvent({ id: 'normal' })],
pagination: defaultPagination,
error: null,
});
await eventsStore.fetchEvents(new Date('2026-03-01'), new Date('2026-03-31'));
expect(eventsStore.events).toHaveLength(1);
expect(eventsStore.events[0].id).toBe('normal');
});
it('should preserve event duration in occurrences', () => {
const events = [
makeEvent({
id: 'w1',
startTime: '2026-03-02T14:00:00',
endTime: '2026-03-02T15:00:00',
recurrenceRule: 'FREQ=WEEKLY',
}),
];
const result = expandRecurringEvents(events, new Date('2026-03-01'), new Date('2026-03-31'));
for (const e of result) {
const dur = new Date(e.endTime).getTime() - new Date(e.startTime).getTime();
expect(dur).toBe(3600000);
}
});
describe('helpers', () => {
it('isRecurrenceOccurrence', () => {
expect(eventsStore.isRecurrenceOccurrence('evt__recurrence__2026-03-15')).toBe(true);
expect(eventsStore.isRecurrenceOccurrence('evt-1')).toBe(false);
});
it('getParentEventId', () => {
expect(eventsStore.getParentEventId('evt-1__recurrence__2026-03-15')).toBe('evt-1');
expect(eventsStore.getParentEventId('evt-1')).toBe('evt-1');
});
it('should respect exceptions', () => {
const events = [
makeEvent({
id: 'exc',
startTime: '2026-03-01T09:00:00',
endTime: '2026-03-01T09:30:00',
recurrenceRule: 'FREQ=DAILY',
recurrenceExceptions: ['2026-03-03', '2026-03-05'],
}),
];
const result = expandRecurringEvents(events, new Date('2026-03-01'), new Date('2026-03-07'));
const dates = result.map((e) => e.id.split('__recurrence__')[1]);
expect(dates).not.toContain('2026-03-03');
expect(dates).not.toContain('2026-03-05');
expect(dates).toContain('2026-03-01');
});
describe('deleteRecurrenceOccurrence', () => {
it('should add exception and remove from local state', async () => {
mockGetEvents.mockResolvedValue({
data: [
makeEvent({
id: 'r1',
startTime: '2026-03-01T09:00:00',
endTime: '2026-03-01T09:30:00',
recurrenceRule: 'FREQ=DAILY',
}),
],
pagination: defaultPagination,
error: null,
});
await eventsStore.fetchEvents(new Date('2026-03-01'), new Date('2026-03-07'));
mockUpdateEvent.mockResolvedValue({ data: makeEvent({ id: 'r1' }), error: null });
await eventsStore.deleteRecurrenceOccurrence('r1__recurrence__2026-03-03');
expect(mockUpdateEvent).toHaveBeenCalledWith('r1', { recurrenceExceptions: ['2026-03-03'] });
expect(eventsStore.events.map((e) => e.id)).not.toContain('r1__recurrence__2026-03-03');
});
});
describe('deleteRecurrenceSeries', () => {
it('should delete parent event', async () => {
mockGetEvents.mockResolvedValue({
data: [
makeEvent({
id: 'r1',
startTime: '2026-03-01T09:00:00',
endTime: '2026-03-01T09:30:00',
recurrenceRule: 'FREQ=DAILY',
}),
],
pagination: defaultPagination,
error: null,
});
await eventsStore.fetchEvents(new Date('2026-03-01'), new Date('2026-03-07'));
mockDeleteEvent.mockResolvedValue({ data: null, error: null });
await eventsStore.deleteRecurrenceSeries('r1__recurrence__2026-03-03');
expect(mockDeleteEvent).toHaveBeenCalledWith('r1');
});
it('should not expand non-recurring events', () => {
const events = [makeEvent({ id: 'normal' })];
const result = expandRecurringEvents(events, new Date('2026-03-01'), new Date('2026-03-31'));
expect(result).toHaveLength(1);
expect(result[0].id).toBe('normal');
});
});
describe('recurrence helpers', () => {
it('isRecurrenceOccurrence', () => {
expect(eventsStore.isRecurrenceOccurrence('evt__recurrence__2026-03-15')).toBe(true);
expect(eventsStore.isRecurrenceOccurrence('evt-1')).toBe(false);
});
it('getParentEventId', () => {
expect(eventsStore.getParentEventId('evt-1__recurrence__2026-03-15')).toBe('evt-1');
expect(eventsStore.getParentEventId('evt-1')).toBe('evt-1');
});
});

View file

@ -1,32 +1,27 @@
import { describe, it, expect, vi, beforeEach } from 'vitest';
import { describe, it, expect, vi } from 'vitest';
import type { CalendarEvent } from '@calendar/shared';
import type { PaginationMeta } from '$lib/api/events';
const defaultPagination: PaginationMeta = { offset: 0, count: 0 };
// Mock local-store to avoid import.meta.env issues in tests
vi.mock('$lib/data/local-store', () => ({
eventCollection: {},
calendarCollection: {},
}));
// Mock dependencies before importing the store
vi.mock('$lib/api/events', () => ({
getEvents: vi.fn(),
createEvent: vi.fn(),
updateEvent: vi.fn(),
deleteEvent: vi.fn(),
vi.mock('@manacore/local-store/svelte', () => ({
useLiveQueryWithDefault: vi.fn(),
}));
vi.mock('$lib/api/birthdays', () => ({
BIRTHDAY_CALENDAR: { id: 'birthday-cal', name: 'Birthdays', color: '#ec4899' },
}));
vi.mock('@manacore/shared-ui', () => ({
toastStore: {
error: vi.fn(),
success: vi.fn(),
},
toastStore: { error: vi.fn(), success: vi.fn() },
}));
import * as api from '$lib/api/events';
import { getEventsForDay, getEventsInRange } from '$lib/data/queries';
import { eventsStore } from './events.svelte';
const mockGetEvents = vi.mocked(api.getEvents);
const mockCreateEvent = vi.mocked(api.createEvent);
const mockUpdateEvent = vi.mocked(api.updateEvent);
const mockDeleteEvent = vi.mocked(api.deleteEvent);
function makeEvent(overrides: Partial<CalendarEvent> = {}): CalendarEvent {
return {
id: 'evt-1',
@ -53,334 +48,116 @@ function makeEvent(overrides: Partial<CalendarEvent> = {}): CalendarEvent {
};
}
describe('eventsStore', () => {
beforeEach(() => {
vi.clearAllMocks();
eventsStore.clear();
eventsStore.clearDraftEvent();
describe('getEventsForDay', () => {
it('should return events that start on the given day', () => {
const events = [
makeEvent({ id: 'evt-1', startTime: '2026-03-15T10:00:00', endTime: '2026-03-15T11:00:00' }),
];
const result = getEventsForDay(events, new Date('2026-03-15'));
expect(result).toHaveLength(1);
expect(result[0].id).toBe('evt-1');
});
describe('getEventsForDay', () => {
it('should return events that start on the given day', async () => {
const event = makeEvent({
id: 'evt-1',
startTime: '2026-03-15T10:00:00',
endTime: '2026-03-15T11:00:00',
});
mockGetEvents.mockResolvedValue({
data: [event],
pagination: defaultPagination,
error: null,
});
it('should not return events from a different day', () => {
const events = [
makeEvent({ startTime: '2026-03-15T10:00:00', endTime: '2026-03-15T11:00:00' }),
];
const result = getEventsForDay(events, new Date('2026-03-16'));
expect(result).toHaveLength(0);
});
await eventsStore.fetchEvents(new Date('2026-03-01'), new Date('2026-03-31'));
const result = eventsStore.getEventsForDay(new Date('2026-03-15'), false);
expect(result).toHaveLength(1);
expect(result[0].id).toBe('evt-1');
});
it('should not return events from a different day', async () => {
const event = makeEvent({
startTime: '2026-03-15T10:00:00',
endTime: '2026-03-15T11:00:00',
});
mockGetEvents.mockResolvedValue({
data: [event],
pagination: defaultPagination,
error: null,
});
await eventsStore.fetchEvents(new Date('2026-03-01'), new Date('2026-03-31'));
const result = eventsStore.getEventsForDay(new Date('2026-03-16'), false);
expect(result).toHaveLength(0);
});
it('should include all-day events that span the given day', async () => {
const event = makeEvent({
it('should include all-day events that span the given day', () => {
const events = [
makeEvent({
id: 'allday-1',
startTime: '2026-03-14T00:00:00',
endTime: '2026-03-16T23:59:59',
isAllDay: true,
});
mockGetEvents.mockResolvedValue({
data: [event],
pagination: defaultPagination,
error: null,
});
await eventsStore.fetchEvents(new Date('2026-03-01'), new Date('2026-03-31'));
const result = eventsStore.getEventsForDay(new Date('2026-03-15'), false);
expect(result).toHaveLength(1);
expect(result[0].id).toBe('allday-1');
});
it('should include draft event when includeDraft is true', async () => {
mockGetEvents.mockResolvedValue({ data: [], pagination: defaultPagination, error: null });
await eventsStore.fetchEvents(new Date('2026-03-01'), new Date('2026-03-31'));
eventsStore.createDraftEvent({
startTime: '2026-03-15T09:00:00',
endTime: '2026-03-15T10:00:00',
});
const result = eventsStore.getEventsForDay(new Date('2026-03-15'), true);
expect(result).toHaveLength(1);
expect(result[0].id).toBe('__draft__');
});
it('should exclude draft event when includeDraft is false', async () => {
mockGetEvents.mockResolvedValue({ data: [], pagination: defaultPagination, error: null });
await eventsStore.fetchEvents(new Date('2026-03-01'), new Date('2026-03-31'));
eventsStore.createDraftEvent({
startTime: '2026-03-15T09:00:00',
endTime: '2026-03-15T10:00:00',
});
const result = eventsStore.getEventsForDay(new Date('2026-03-15'), false);
expect(result).toHaveLength(0);
});
});
describe('getEventsInRange', () => {
it('should return events that overlap with the given range', async () => {
const events = [
makeEvent({
id: 'evt-1',
startTime: '2026-03-15T10:00:00',
endTime: '2026-03-15T11:00:00',
}),
makeEvent({
id: 'evt-2',
startTime: '2026-03-20T14:00:00',
endTime: '2026-03-20T15:00:00',
}),
];
mockGetEvents.mockResolvedValue({ data: events, pagination: defaultPagination, error: null });
await eventsStore.fetchEvents(new Date('2026-03-01'), new Date('2026-03-31'));
const result = eventsStore.getEventsInRange(new Date('2026-03-14'), new Date('2026-03-16'));
expect(result).toHaveLength(1);
expect(result[0].id).toBe('evt-1');
});
it('should return events that partially overlap the range', async () => {
const event = makeEvent({
startTime: '2026-03-14T22:00:00',
endTime: '2026-03-15T02:00:00',
});
mockGetEvents.mockResolvedValue({
data: [event],
pagination: defaultPagination,
error: null,
});
await eventsStore.fetchEvents(new Date('2026-03-01'), new Date('2026-03-31'));
const result = eventsStore.getEventsInRange(
new Date('2026-03-15T00:00:00'),
new Date('2026-03-15T23:59:59')
);
expect(result).toHaveLength(1);
});
it('should return empty array when no events in range', async () => {
const event = makeEvent({
startTime: '2026-03-20T10:00:00',
endTime: '2026-03-20T11:00:00',
});
mockGetEvents.mockResolvedValue({
data: [event],
pagination: defaultPagination,
error: null,
});
await eventsStore.fetchEvents(new Date('2026-03-01'), new Date('2026-03-31'));
const result = eventsStore.getEventsInRange(new Date('2026-03-14'), new Date('2026-03-16'));
expect(result).toHaveLength(0);
});
});
describe('createDraftEvent / clearDraftEvent', () => {
it('should create a draft event with __draft__ id', () => {
const draft = eventsStore.createDraftEvent({
title: 'Draft Meeting',
startTime: '2026-03-15T10:00:00',
endTime: '2026-03-15T11:00:00',
});
expect(draft.id).toBe('__draft__');
expect(draft.title).toBe('Draft Meeting');
expect(eventsStore.draftEvent).not.toBeNull();
expect(eventsStore.draftEvent?.id).toBe('__draft__');
});
it('should clear the draft event', () => {
eventsStore.createDraftEvent({
title: 'Draft',
startTime: '2026-03-15T10:00:00',
endTime: '2026-03-15T11:00:00',
});
expect(eventsStore.draftEvent).not.toBeNull();
eventsStore.clearDraftEvent();
expect(eventsStore.draftEvent).toBeNull();
});
it('should set default values for missing fields', () => {
const draft = eventsStore.createDraftEvent({});
expect(draft.calendarId).toBe('');
expect(draft.title).toBe('');
expect(draft.isAllDay).toBe(false);
expect(draft.status).toBe('confirmed');
expect(draft.description).toBeNull();
expect(draft.location).toBeNull();
});
});
describe('isDraftEvent', () => {
it('should return true for __draft__ id', () => {
expect(eventsStore.isDraftEvent('__draft__')).toBe(true);
});
it('should return false for regular event ids', () => {
expect(eventsStore.isDraftEvent('evt-1')).toBe(false);
expect(eventsStore.isDraftEvent('')).toBe(false);
});
});
describe('deleteEvent (optimistic update)', () => {
it('should remove event optimistically and restore on error', async () => {
const events = [makeEvent({ id: 'evt-1' }), makeEvent({ id: 'evt-2', title: 'Second' })];
mockGetEvents.mockResolvedValue({ data: events, pagination: defaultPagination, error: null });
await eventsStore.fetchEvents(new Date('2026-03-01'), new Date('2026-03-31'));
// Verify both events exist
expect(eventsStore.events).toHaveLength(2);
// Simulate API error on delete
mockDeleteEvent.mockResolvedValue({
data: null,
error: { message: 'Server error', code: 'SERVER_ERROR', status: 500 },
});
await eventsStore.deleteEvent('evt-1');
// Event should be restored after error
expect(eventsStore.events).toHaveLength(2);
const ids = eventsStore.events.map((e) => e.id);
expect(ids).toContain('evt-1');
});
it('should permanently remove event on successful delete', async () => {
const events = [makeEvent({ id: 'evt-1' })];
mockGetEvents.mockResolvedValue({ data: events, pagination: defaultPagination, error: null });
await eventsStore.fetchEvents(new Date('2026-03-01'), new Date('2026-03-31'));
mockDeleteEvent.mockResolvedValue({ data: null, error: null });
await eventsStore.deleteEvent('evt-1');
expect(eventsStore.events).toHaveLength(0);
});
});
describe('createEvent', () => {
it('should add the created event to the store', async () => {
mockGetEvents.mockResolvedValue({ data: [], pagination: defaultPagination, error: null });
await eventsStore.fetchEvents(new Date('2026-03-01'), new Date('2026-03-31'));
const newEvent = makeEvent({ id: 'new-1', title: 'New Event' });
mockCreateEvent.mockResolvedValue({ data: newEvent, error: null });
await eventsStore.createEvent({
calendarId: 'cal-1',
title: 'New Event',
startTime: '2026-03-15T10:00:00',
endTime: '2026-03-15T11:00:00',
});
expect(eventsStore.events).toHaveLength(1);
expect(eventsStore.events[0].id).toBe('new-1');
});
});
describe('updateEvent', () => {
it('should replace the updated event in the store', async () => {
const event = makeEvent({ id: 'evt-1', title: 'Original' });
mockGetEvents.mockResolvedValue({
data: [event],
pagination: defaultPagination,
error: null,
});
await eventsStore.fetchEvents(new Date('2026-03-01'), new Date('2026-03-31'));
const updated = makeEvent({ id: 'evt-1', title: 'Updated' });
mockUpdateEvent.mockResolvedValue({ data: updated, error: null });
await eventsStore.updateEvent('evt-1', { title: 'Updated' });
expect(eventsStore.events).toHaveLength(1);
expect(eventsStore.events[0].title).toBe('Updated');
});
});
describe('fetchEvents', () => {
it('should set loading state during fetch', async () => {
let resolvePromise: (value: unknown) => void;
const promise = new Promise((resolve) => {
resolvePromise = resolve;
});
mockGetEvents.mockReturnValue(promise as ReturnType<typeof api.getEvents>);
const fetchPromise = eventsStore.fetchEvents(new Date('2026-03-01'), new Date('2026-03-31'));
expect(eventsStore.loading).toBe(true);
resolvePromise!({ data: [], pagination: defaultPagination, error: null });
await fetchPromise;
expect(eventsStore.loading).toBe(false);
});
it('should set error on API failure', async () => {
mockGetEvents.mockResolvedValue({
data: null,
pagination: null,
error: { message: 'Network error', code: 'NETWORK_ERROR' },
});
await eventsStore.fetchEvents(new Date('2026-03-01'), new Date('2026-03-31'));
expect(eventsStore.error).toBe('Network error');
});
});
describe('getById', () => {
it('should return event by ID', async () => {
const event = makeEvent({ id: 'evt-1', title: 'Found' });
mockGetEvents.mockResolvedValue({
data: [event],
pagination: defaultPagination,
error: null,
});
await eventsStore.fetchEvents(new Date('2026-03-01'), new Date('2026-03-31'));
expect(eventsStore.getById('evt-1')?.title).toBe('Found');
});
it('should return undefined for unknown ID', async () => {
mockGetEvents.mockResolvedValue({ data: [], pagination: defaultPagination, error: null });
await eventsStore.fetchEvents(new Date('2026-03-01'), new Date('2026-03-31'));
expect(eventsStore.getById('nonexistent')).toBeUndefined();
});
}),
];
const result = getEventsForDay(events, new Date('2026-03-15'));
expect(result).toHaveLength(1);
expect(result[0].id).toBe('allday-1');
});
});
describe('getEventsInRange', () => {
it('should return events that overlap with the given range', () => {
const events = [
makeEvent({ id: 'evt-1', startTime: '2026-03-15T10:00:00', endTime: '2026-03-15T11:00:00' }),
makeEvent({ id: 'evt-2', startTime: '2026-03-20T14:00:00', endTime: '2026-03-20T15:00:00' }),
];
const result = getEventsInRange(events, new Date('2026-03-14'), new Date('2026-03-16'));
expect(result).toHaveLength(1);
expect(result[0].id).toBe('evt-1');
});
it('should return events that partially overlap the range', () => {
const events = [
makeEvent({ startTime: '2026-03-14T22:00:00', endTime: '2026-03-15T02:00:00' }),
];
const result = getEventsInRange(
events,
new Date('2026-03-15T00:00:00'),
new Date('2026-03-15T23:59:59')
);
expect(result).toHaveLength(1);
});
it('should return empty array when no events in range', () => {
const events = [
makeEvent({ startTime: '2026-03-20T10:00:00', endTime: '2026-03-20T11:00:00' }),
];
const result = getEventsInRange(events, new Date('2026-03-14'), new Date('2026-03-16'));
expect(result).toHaveLength(0);
});
});
describe('createDraftEvent / clearDraftEvent', () => {
it('should create a draft event with __draft__ id', () => {
const draft = eventsStore.createDraftEvent({
title: 'Draft Meeting',
startTime: '2026-03-15T10:00:00',
endTime: '2026-03-15T11:00:00',
});
expect(draft.id).toBe('__draft__');
expect(draft.title).toBe('Draft Meeting');
expect(eventsStore.draftEvent).not.toBeNull();
expect(eventsStore.draftEvent?.id).toBe('__draft__');
});
it('should clear the draft event', () => {
eventsStore.createDraftEvent({
title: 'Draft',
startTime: '2026-03-15T10:00:00',
endTime: '2026-03-15T11:00:00',
});
expect(eventsStore.draftEvent).not.toBeNull();
eventsStore.clearDraftEvent();
expect(eventsStore.draftEvent).toBeNull();
});
it('should set default values for missing fields', () => {
const draft = eventsStore.createDraftEvent({});
expect(draft.calendarId).toBe('');
expect(draft.title).toBe('');
expect(draft.isAllDay).toBe(false);
expect(draft.status).toBe('confirmed');
expect(draft.description).toBeNull();
expect(draft.location).toBeNull();
});
});
describe('isDraftEvent', () => {
it('should return true for __draft__ id', () => {
expect(eventsStore.isDraftEvent('__draft__')).toBe(true);
});
it('should return false for regular event ids', () => {
expect(eventsStore.isDraftEvent('evt-1')).toBe(false);
expect(eventsStore.isDraftEvent('')).toBe(false);
});
});

View file

@ -1,134 +0,0 @@
import { describe, it, expect, vi, beforeEach } from 'vitest';
// Mock the client module
vi.mock('./client', () => ({
apiClient: {
get: vi.fn(),
post: vi.fn(),
put: vi.fn(),
delete: vi.fn(),
},
}));
import {
getProjects,
getProject,
createProject,
updateProject,
deleteProject,
archiveProject,
reorderProjects,
} from './projects';
import { apiClient } from './client';
const mockClient = vi.mocked(apiClient);
beforeEach(() => {
vi.clearAllMocks();
});
describe('getProjects', () => {
it('should fetch all projects', async () => {
const projects = [
{ id: 'p1', name: 'Work' },
{ id: 'p2', name: 'Personal' },
];
mockClient.get.mockResolvedValue({ projects });
const result = await getProjects();
expect(mockClient.get).toHaveBeenCalledWith('/api/v1/projects');
expect(result).toEqual(projects);
});
it('should return empty array when no projects', async () => {
mockClient.get.mockResolvedValue({ projects: [] });
const result = await getProjects();
expect(result).toEqual([]);
});
});
describe('getProject', () => {
it('should fetch a single project by id', async () => {
const project = { id: 'p1', name: 'Work' };
mockClient.get.mockResolvedValue({ project });
const result = await getProject('p1');
expect(mockClient.get).toHaveBeenCalledWith('/api/v1/projects/p1');
expect(result).toEqual(project);
});
});
describe('createProject', () => {
it('should POST a new project', async () => {
const data = { name: 'New Project', color: '#ff0000', icon: 'star' };
const project = { id: 'p-new', ...data };
mockClient.post.mockResolvedValue({ project });
const result = await createProject(data);
expect(mockClient.post).toHaveBeenCalledWith('/api/v1/projects', data);
expect(result).toEqual(project);
});
it('should create project with only name', async () => {
const data = { name: 'Minimal Project' };
const project = { id: 'p-min', name: 'Minimal Project' };
mockClient.post.mockResolvedValue({ project });
const result = await createProject(data);
expect(mockClient.post).toHaveBeenCalledWith('/api/v1/projects', data);
expect(result).toEqual(project);
});
});
describe('updateProject', () => {
it('should PUT updated project', async () => {
const data = { name: 'Updated Name', color: '#00ff00' };
const project = { id: 'p1', ...data };
mockClient.put.mockResolvedValue({ project });
const result = await updateProject('p1', data);
expect(mockClient.put).toHaveBeenCalledWith('/api/v1/projects/p1', data);
expect(result).toEqual(project);
});
});
describe('deleteProject', () => {
it('should DELETE project', async () => {
mockClient.delete.mockResolvedValue(undefined);
await deleteProject('p1');
expect(mockClient.delete).toHaveBeenCalledWith('/api/v1/projects/p1');
});
});
describe('archiveProject', () => {
it('should POST to archive endpoint', async () => {
const project = { id: 'p1', name: 'Work', isArchived: true };
mockClient.post.mockResolvedValue({ project });
const result = await archiveProject('p1');
expect(mockClient.post).toHaveBeenCalledWith('/api/v1/projects/p1/archive');
expect(result).toEqual(project);
});
});
describe('reorderProjects', () => {
it('should PUT reorder with project IDs', async () => {
mockClient.put.mockResolvedValue(undefined);
await reorderProjects(['p1', 'p2', 'p3']);
expect(mockClient.put).toHaveBeenCalledWith('/api/v1/projects/reorder', {
projectIds: ['p1', 'p2', 'p3'],
});
});
});

View file

@ -1,77 +0,0 @@
import { describe, it, expect, vi, beforeEach } from 'vitest';
// Mock the client module
vi.mock('./client', () => ({
apiClient: {
get: vi.fn(),
post: vi.fn(),
put: vi.fn(),
delete: vi.fn(),
},
}));
import { getReminders, createReminder, deleteReminder } from './reminders';
import { apiClient } from './client';
const mockClient = vi.mocked(apiClient);
beforeEach(() => {
vi.clearAllMocks();
});
describe('getReminders', () => {
it('should fetch reminders for a task', async () => {
const reminders = [
{ id: 'r1', taskId: 't1', minutesBefore: 15, type: 'push' },
{ id: 'r2', taskId: 't1', minutesBefore: 60, type: 'email' },
];
mockClient.get.mockResolvedValue({ reminders });
const result = await getReminders('t1');
expect(mockClient.get).toHaveBeenCalledWith('/api/v1/tasks/t1/reminders');
expect(result).toEqual(reminders);
});
it('should return empty array when no reminders', async () => {
mockClient.get.mockResolvedValue({ reminders: [] });
const result = await getReminders('t1');
expect(result).toEqual([]);
});
});
describe('createReminder', () => {
it('should POST a new reminder', async () => {
const data = { minutesBefore: 30, type: 'push' as const };
const reminder = { id: 'r-new', taskId: 't1', ...data };
mockClient.post.mockResolvedValue({ reminder });
const result = await createReminder('t1', data);
expect(mockClient.post).toHaveBeenCalledWith('/api/v1/tasks/t1/reminders', data);
expect(result).toEqual(reminder);
});
it('should create reminder with only minutesBefore', async () => {
const data = { minutesBefore: 10 };
const reminder = { id: 'r-new', taskId: 't1', minutesBefore: 10 };
mockClient.post.mockResolvedValue({ reminder });
const result = await createReminder('t1', data);
expect(mockClient.post).toHaveBeenCalledWith('/api/v1/tasks/t1/reminders', data);
expect(result).toEqual(reminder);
});
});
describe('deleteReminder', () => {
it('should DELETE reminder by id', async () => {
mockClient.delete.mockResolvedValue(undefined);
await deleteReminder('r1');
expect(mockClient.delete).toHaveBeenCalledWith('/api/v1/reminders/r1');
});
});