import React from 'react'; import '@testing-library/jest-dom'; import { fireEvent, render, screen } from '@testing-library/react'; import { MemoryRouter } from 'react-router-dom'; import SettingsView from './components/SettingsView'; import { I18nProvider } from './i18n/I18nProvider'; import { ToastProvider } from './toast'; import { api } from './api'; jest.mock('./api', () => ({ api: { get: jest.fn(), post: jest.fn(), put: jest.fn(), patch: jest.fn(), delete: jest.fn(), interceptors: { request: { use: jest.fn() }, response: { use: jest.fn() } }, }, getApiErrorMessage: (error: any, fallback?: string) => fallback || 'Request failed.', })); jest.mock('./components/ImportExportJobs', () => () =>
Import Export Stub
); jest.mock('./components/BackupCard', () => () =>
Backup Stub
); const mockedApi = api as jest.Mocked; function renderView() { return render( , ); } beforeEach(() => { mockedApi.get.mockImplementation((url: string) => { if (url === '/ai/settings') { return Promise.resolve({ data: { enabled: true, externalProcessingAllowed: false, externalProcessingAvailable: false, effectiveExternalProcessing: false, provider: 'local', }, } as any); } if (url === '/ai/usage') { return Promise.resolve({ data: { currentMonth: { calls: 0, estimatedTokens: 0 }, plan: 'free', monthlyCallLimit: 0, monthlyTokenLimit: 0, storageUsedBytes: 0, storageLimitBytes: 250_000_000, } } as any); } if (url === '/billing/status') { return Promise.resolve({ data: { enabled: false, canCheckout: false, canManage: false } } as any); } if (url === '/rules') { return Promise.resolve({ data: { id: 1, appliedFollowUpDays: 14, appliedGhostDays: 30, offerFollowUpDays: 7, offerGhostDays: 14, feedbackFollowUpDays: 7, feedbackGhostDays: 14, }, } as any); } return Promise.resolve({ data: {} } as any); }); window.localStorage.clear(); mockedApi.put.mockImplementation((url: string, body: any) => Promise.resolve({ data: { ...body, externalProcessingAvailable: false, effectiveExternalProcessing: false, provider: 'local', }, } as any)); }); afterEach(() => { jest.clearAllMocks(); }); test('settings view has no accent picker and uses one follow-up section, one notification section', async () => { renderView(); expect(screen.queryByText(/accent/i)).not.toBeInTheDocument(); fireEvent.click(screen.getByRole('tab', { name: /follow-ups/i })); expect(await screen.findByText(/follow-up rules by scenario/i)).toBeInTheDocument(); expect(screen.queryAllByText(/open reminders/i)).toHaveLength(0); fireEvent.click(screen.getByRole('tab', { name: /notifications/i })); expect(screen.getByText(/notification settings/i)).toBeInTheDocument(); // SMTP status now lives under Admin → System → Settings (the old "check system status" link was removed). expect(screen.getAllByText(/smtp delivery and test mail live under/i).length).toBe(1); expect(screen.getByLabelText(/email reminders for follow-ups/i)).toBeInTheDocument(); expect(screen.getByLabelText(/email alerts for ghosted jobs/i)).toBeInTheDocument(); }); test('AI privacy settings are server-backed and external processing is local-only by default', async () => { renderView(); expect(await screen.findByText(/AI privacy/i)).toBeInTheDocument(); const external = screen.getByLabelText(/allow approved external AI processing/i); expect(external).toBeDisabled(); fireEvent.click(screen.getByLabelText(/enable AI features for my account/i)); fireEvent.click(screen.getByRole('button', { name: /save AI privacy settings/i })); expect(mockedApi.put).toHaveBeenCalledWith('/ai/settings', { enabled: false, externalProcessingAllowed: false, }); });