9edcbfc5de
Make job/CV comparisons language-aware and filter recruitment noise. Improve responsive career navigation, shared spacing, dashboard priorities, settings, localized workspace controls, and portable browser tests.
139 lines
4.8 KiB
TypeScript
139 lines
4.8 KiB
TypeScript
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', () => () => <div>Import Export Stub</div>);
|
|
jest.mock('./components/BackupCard', () => () => <div>Backup Stub</div>);
|
|
|
|
const mockedApi = api as jest.Mocked<typeof api>;
|
|
|
|
function renderView() {
|
|
return render(
|
|
<MemoryRouter>
|
|
<ToastProvider>
|
|
<I18nProvider>
|
|
<SettingsView
|
|
pageSize={20}
|
|
onPageSizeChange={jest.fn()}
|
|
columns={{ status: true, dateApplied: true, daysSince: true, jobUrl: false }}
|
|
onColumnsChange={jest.fn()}
|
|
themeMode="dark"
|
|
onThemeModeChange={jest.fn()}
|
|
/>
|
|
</I18nProvider>
|
|
</ToastProvider>
|
|
</MemoryRouter>,
|
|
);
|
|
}
|
|
|
|
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);
|
|
}
|
|
if (url === '/notification-settings') return Promise.resolve({ data: { emailFollowUpRemindersEnabled: true } } 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();
|
|
expect(screen.getByLabelText(/settings section/i)).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.queryByLabelText(/email alerts for ghosted jobs/i)).not.toBeInTheDocument();
|
|
fireEvent.click(screen.getByLabelText(/email reminders for follow-ups/i));
|
|
fireEvent.click(screen.getByRole('button', { name: /save notification settings/i }));
|
|
expect(mockedApi.put).toHaveBeenCalledWith('/notification-settings', { emailFollowUpRemindersEnabled: false });
|
|
});
|
|
|
|
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,
|
|
});
|
|
});
|