a1a3736cc4
When a job workspace opens, loads /status-suggestion and shows a
dismissible banner when a recent inbound email implies a status move
("This email looks like a move to Interview"). Applying it PATCHes the
status; nothing changes without the user's click.
- StatusSuggestion type + load-on-open effect + apply handler
- warning-toned banner shown above tab content on any tab
- EN/NB translations; README endpoint docs
- 2 frontend tests; full suite green (21 suites / 48 tests)
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
91 lines
3.5 KiB
TypeScript
91 lines
3.5 KiB
TypeScript
import React from 'react';
|
|
import '@testing-library/jest-dom';
|
|
import { fireEvent, render, screen, waitFor } from '@testing-library/react';
|
|
import { ConfirmProvider } from './confirm';
|
|
import { PromptProvider } from './prompt';
|
|
import { ToastProvider } from './toast';
|
|
import { I18nProvider } from './i18n/I18nProvider';
|
|
import JobDetailsDialog from './components/JobDetailsDialog';
|
|
import { api } from './api';
|
|
|
|
jest.setTimeout(15000);
|
|
|
|
jest.mock('./api', () => ({
|
|
api: {
|
|
get: jest.fn(),
|
|
post: jest.fn(() => Promise.resolve({ data: {} })),
|
|
put: jest.fn(() => Promise.resolve({ data: {} })),
|
|
patch: jest.fn(() => Promise.resolve({ data: {} })),
|
|
delete: jest.fn(() => Promise.resolve({ data: {} })),
|
|
interceptors: { request: { use: jest.fn() }, response: { use: jest.fn() } },
|
|
},
|
|
getApiErrorMessage: jest.fn(() => 'error'),
|
|
}));
|
|
|
|
const mockedApi = api as jest.Mocked<typeof api>;
|
|
|
|
function renderDialog() {
|
|
return render(
|
|
<ToastProvider>
|
|
<I18nProvider>
|
|
<ConfirmProvider>
|
|
<PromptProvider>
|
|
<JobDetailsDialog open jobId={42} onClose={() => {}} />
|
|
</PromptProvider>
|
|
</ConfirmProvider>
|
|
</I18nProvider>
|
|
</ToastProvider>,
|
|
);
|
|
}
|
|
|
|
beforeEach(() => {
|
|
mockedApi.get.mockImplementation((url: string) => {
|
|
if (url === '/jobapplications/42') {
|
|
return Promise.resolve({ data: { id: 42, jobTitle: 'Backend Developer', status: 'Applied', dateApplied: new Date().toISOString(), daysSince: 3, company: { name: 'Acme' } } } as any);
|
|
}
|
|
if (url === '/jobapplications/42/status-suggestion') {
|
|
return Promise.resolve({ data: { hasSuggestion: true, suggestedStatus: 'Interview', currentStatus: 'Applied', signal: 'schedule an interview', confidence: 'medium' } } as any);
|
|
}
|
|
if (url === '/auth/me') return Promise.resolve({ data: { roles: [] } } as any);
|
|
if (url === '/jobapplications/42/history') return Promise.resolve({ data: [] } as any);
|
|
if (url === '/attachments/42') return Promise.resolve({ data: [] } as any);
|
|
return Promise.resolve({ data: {} } as any);
|
|
});
|
|
});
|
|
|
|
afterEach(() => {
|
|
jest.clearAllMocks();
|
|
});
|
|
|
|
test('status suggestion banner appears and applies via PATCH', async () => {
|
|
renderDialog();
|
|
|
|
expect(await screen.findByText(/looks like a move to interview/i)).toBeInTheDocument();
|
|
|
|
fireEvent.click(await screen.findByRole('button', { name: /move to interview/i }));
|
|
|
|
await waitFor(() => {
|
|
expect(mockedApi.patch).toHaveBeenCalledWith('/jobapplications/42/status', { status: 'Interview' });
|
|
});
|
|
});
|
|
|
|
test('no banner when there is no suggestion', async () => {
|
|
mockedApi.get.mockImplementation((url: string) => {
|
|
if (url === '/jobapplications/42') {
|
|
return Promise.resolve({ data: { id: 42, jobTitle: 'Backend Developer', status: 'Applied', dateApplied: new Date().toISOString(), daysSince: 3, company: { name: 'Acme' } } } as any);
|
|
}
|
|
if (url === '/jobapplications/42/status-suggestion') {
|
|
return Promise.resolve({ data: { hasSuggestion: false } } as any);
|
|
}
|
|
if (url === '/auth/me') return Promise.resolve({ data: { roles: [] } } as any);
|
|
if (url === '/jobapplications/42/history') return Promise.resolve({ data: [] } as any);
|
|
if (url === '/attachments/42') return Promise.resolve({ data: [] } as any);
|
|
return Promise.resolve({ data: {} } as any);
|
|
});
|
|
|
|
renderDialog();
|
|
|
|
expect(await screen.findByText(/backend developer/i)).toBeInTheDocument();
|
|
expect(screen.queryByText(/looks like a move to/i)).not.toBeInTheDocument();
|
|
});
|