diff --git a/apps/web/src/app/(portal)/settings/dashboard/calendar/calendar-settings.test.tsx b/apps/web/src/app/(portal)/settings/dashboard/calendar/calendar-settings.test.tsx new file mode 100644 index 0000000..29ede7e --- /dev/null +++ b/apps/web/src/app/(portal)/settings/dashboard/calendar/calendar-settings.test.tsx @@ -0,0 +1,146 @@ +import { cleanup, render, screen, waitFor, fireEvent } from '@testing-library/react'; +import { afterEach, describe, expect, it, vi } from 'vitest'; + +// Mock next-intl +vi.mock('next-intl', () => ({ + useTranslations: (ns: string) => (key: string) => { + const map: Record> = { + widgets: { + 'calendar.addSource': 'Add source', + 'calendar.sourceEmpty': 'No calendar sources configured. Add a source to display events.', + 'calendar.connectionSuccess': 'Connection successful', + 'calendar.connectionError': 'Connection failed. Please check the URL and credentials.', + }, + settings: { + categoryCalendar: 'Calendar', + sourceDeleteConfirm: 'Are you sure you want to delete this calendar source?', + sourceDeleteCta: 'Delete source', + cancel: 'Cancel', + }, + }; + return map[ns]?.[key] ?? key; + }, +})); + +// Mock calendar-api +const mockFetchSources = vi.fn(); +const mockUpdateSource = vi.fn(); +const mockAddSource = vi.fn(); +const mockDeleteSource = vi.fn(); +const mockTestSource = vi.fn(); + +vi.mock('@/lib/calendar-api', () => ({ + fetchSources: (...args: unknown[]) => mockFetchSources(...args), + updateSource: (...args: unknown[]) => mockUpdateSource(...args), + addSource: (...args: unknown[]) => mockAddSource(...args), + deleteSource: (...args: unknown[]) => mockDeleteSource(...args), + testSource: (...args: unknown[]) => mockTestSource(...args), +})); + +afterEach(() => { + cleanup(); + vi.restoreAllMocks(); +}); + +describe('CalendarSettingsPanel', () => { + it('lists two sources with name, type badge, and visibility toggle', async () => { + mockFetchSources.mockResolvedValue([ + { + id: 's1', + name: 'Work Calendar', + type: 'caldav', + isVisible: true, + color: '#ff0000', + lastSyncError: null, + }, + { + id: 's2', + name: 'Holidays', + type: 'ics', + isVisible: false, + color: '#00ff00', + lastSyncError: null, + }, + ]); + + const { CalendarSettingsPanel } = await import( + '@/components/settings/calendar-settings-panel' + ); + render(); + + await waitFor(() => { + expect(screen.getByText('Work Calendar')).toBeInTheDocument(); + }); + + expect(screen.getByText('Holidays')).toBeInTheDocument(); + + // Type badges + expect(screen.getByText('CalDAV')).toBeInTheDocument(); + expect(screen.getByText('ICS')).toBeInTheDocument(); + + // Visibility toggles (checkboxes or switches) + const toggles = screen.getAllByRole('switch'); + expect(toggles).toHaveLength(2); + }); + + it('calls updateSource with isVisible when toggling a source', async () => { + mockFetchSources.mockResolvedValue([ + { + id: 's1', + name: 'Work Calendar', + type: 'caldav', + isVisible: true, + color: '#ff0000', + lastSyncError: null, + }, + ]); + mockUpdateSource.mockResolvedValue({ + id: 's1', + name: 'Work Calendar', + type: 'caldav', + isVisible: false, + }); + + const { CalendarSettingsPanel } = await import( + '@/components/settings/calendar-settings-panel' + ); + render(); + + await waitFor(() => { + expect(screen.getByText('Work Calendar')).toBeInTheDocument(); + }); + + const toggle = screen.getByRole('switch'); + fireEvent.click(toggle); + + await waitFor(() => { + expect(mockUpdateSource).toHaveBeenCalledWith('s1', { isVisible: false }); + }); + }); + + it('requires name, type, and url before enabling add form submit', async () => { + mockFetchSources.mockResolvedValue([]); + + const { CalendarSettingsPanel } = await import( + '@/components/settings/calendar-settings-panel' + ); + render(); + + // Wait for empty state then click add button + await waitFor(() => { + expect( + screen.getByText('No calendar sources configured. Add a source to display events.'), + ).toBeInTheDocument(); + }); + + // Click the add source button + const addButton = screen.getByText('Add source'); + fireEvent.click(addButton); + + // Find the submit button in the form + await waitFor(() => { + const submitButton = screen.getByTestId('source-form-submit'); + expect(submitButton).toBeDisabled(); + }); + }); +});