133 lines
5.9 KiB
TypeScript
133 lines
5.9 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(),
|
|
put: jest.fn(),
|
|
patch: jest.fn(),
|
|
delete: jest.fn(),
|
|
interceptors: { request: { use: jest.fn() }, response: { use: jest.fn() } },
|
|
},
|
|
}));
|
|
|
|
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(() => {
|
|
Object.assign(navigator, {
|
|
clipboard: {
|
|
writeText: jest.fn().mockResolvedValue(undefined),
|
|
},
|
|
});
|
|
|
|
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', recruiterEmail: 'recruiter@acme.test' },
|
|
tailoredCvText: 'Saved CV',
|
|
coverLetterText: 'Saved cover letter',
|
|
recruiterMessageDraft: 'Saved recruiter message',
|
|
notes: 'Original notes\n\n<<<APPLICATION_ANSWER_DRAFT>>>\nSaved application answer\n<<<END_APPLICATION_ANSWER_DRAFT>>>',
|
|
shortSummary: 'summary'
|
|
} } as any);
|
|
}
|
|
if (url === '/auth/me') {
|
|
return Promise.resolve({ data: { roles: [], profileCvText: 'Master CV text' } } as any);
|
|
}
|
|
if (url === '/jobapplications/42/history') {
|
|
return Promise.resolve({ data: [] } as any);
|
|
}
|
|
if (url === '/attachments/42') {
|
|
return Promise.resolve({ data: [{ id: 9, fileName: 'resume.pdf', uploadDate: new Date().toISOString(), fileType: 'application/pdf', fileSize: 1234, purpose: 'resume', useForAi: true }] } as any);
|
|
}
|
|
if (url === '/jobapplications/42/candidate-fit') {
|
|
return Promise.resolve({ data: { matchSummary: 'Strong fit summary', fitLevel: 'Strong match', matchScore: 84, strengths: ['.NET'], gaps: ['Kubernetes'], mention: [], avoid: [], cvImprovements: [], missingKeywords: [], interviewPrep: [], tailoredPitch: 'Pitch', guidance: { cv: [], coverLetter: [], interview: [], recruiterMessage: [] } } } as any);
|
|
}
|
|
if (url === '/jobapplications/42/focus-plan') {
|
|
return Promise.resolve({ data: { strategicSummary: 'Lead with backend delivery and measurable outcomes.', immediatePriorities: ['Highlight .NET ownership'], cvBulletIdeas: [], proofPointsToLeadWith: [], coverLetterAngles: [], followUpApproach: [] } } as any);
|
|
}
|
|
return Promise.resolve({ data: [] } as any);
|
|
});
|
|
mockedApi.post.mockResolvedValue({ data: { tailoredCvText: 'Generated CV', coverLetterDraft: 'Draft letter', applicationAnswerDraft: 'Draft answer', recruiterMessageDraft: 'Recruiter hello', keyPoints: ['Lead with .NET'], attachmentSignals: [], attachmentFilesUsed: [], coverLetterVariants: ['Variant A'], recruiterMessageVariants: ['Variant B'] } } as any);
|
|
mockedApi.put.mockResolvedValue({ data: {} } as any);
|
|
});
|
|
|
|
afterEach(() => {
|
|
jest.clearAllMocks();
|
|
});
|
|
|
|
test('application package workspace reflects saved job material, generated drafts, and save state', async () => {
|
|
renderDialog();
|
|
|
|
fireEvent.click(await screen.findByRole('tab', { name: /tailored cv/i }));
|
|
|
|
expect(await screen.findByDisplayValue('Saved CV')).toBeInTheDocument();
|
|
expect(await screen.findByDisplayValue('Saved cover letter')).toBeInTheDocument();
|
|
expect(await screen.findByDisplayValue('Saved application answer')).toBeInTheDocument();
|
|
expect(await screen.findByDisplayValue('Saved recruiter message')).toBeInTheDocument();
|
|
expect(await screen.findByText(/saved working material/i)).toBeInTheDocument();
|
|
|
|
fireEvent.click(screen.getByRole('button', { name: /generate application package/i }));
|
|
|
|
expect(await screen.findByDisplayValue('Generated CV')).toBeInTheDocument();
|
|
const coverLetter = await screen.findByDisplayValue('Draft letter');
|
|
const applicationAnswer = await screen.findByDisplayValue('Draft answer');
|
|
const recruiterMessage = await screen.findByDisplayValue('Recruiter hello');
|
|
|
|
fireEvent.change(coverLetter, { target: { value: 'Edited cover letter' } });
|
|
fireEvent.change(applicationAnswer, { target: { value: 'Edited answer' } });
|
|
fireEvent.change(recruiterMessage, { target: { value: 'Edited recruiter note' } });
|
|
|
|
fireEvent.click(screen.getByRole('button', { name: /save package to job/i }));
|
|
|
|
await waitFor(() => {
|
|
expect(mockedApi.put).toHaveBeenCalledWith('/jobapplications/42/tailored-cv', { tailoredCvText: 'Generated CV' });
|
|
expect(mockedApi.put).toHaveBeenCalledWith('/jobapplications/42/application-drafts', {
|
|
coverLetterText: 'Edited cover letter',
|
|
notes: 'Original notes\n\n<<<APPLICATION_ANSWER_DRAFT>>>\nEdited answer\n<<<END_APPLICATION_ANSWER_DRAFT>>>',
|
|
recruiterMessageDraft: 'Edited recruiter note',
|
|
});
|
|
});
|
|
|
|
expect(await screen.findAllByText(/saved to job/i)).not.toHaveLength(0);
|
|
});
|
|
|
|
test('strategy snapshot can be generated from overview', async () => {
|
|
renderDialog();
|
|
|
|
expect(await screen.findByRole('button', { name: /generate strategy snapshot/i })).toBeInTheDocument();
|
|
fireEvent.click(screen.getByRole('button', { name: /generate strategy snapshot/i }));
|
|
|
|
expect(await screen.findByText(/lead with backend delivery and measurable outcomes/i)).toBeInTheDocument();
|
|
expect(await screen.findByText(/highlight \.net ownership/i)).toBeInTheDocument();
|
|
});
|