feat(workspace): refine career workflows
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.
This commit is contained in:
@@ -78,7 +78,8 @@ function renderWith(Component: React.ComponentType) {
|
||||
|
||||
// These exercise the master-CV editing surface, which lives on /career (CareerProfilePage) after
|
||||
// the Phase 2.2 split. ProfilePage no longer carries it.
|
||||
function renderPage() {
|
||||
function renderPage(section: 'profile' | 'import' = 'profile') {
|
||||
window.history.replaceState({}, '', `/career?section=${section}`);
|
||||
return renderWith(CareerProfilePage);
|
||||
}
|
||||
void ProfilePage;
|
||||
@@ -184,6 +185,7 @@ beforeEach(() => {
|
||||
});
|
||||
mockedApi.put.mockResolvedValue({ data: {} } as any);
|
||||
window.localStorage.clear();
|
||||
window.history.replaceState({}, '', '/career');
|
||||
});
|
||||
|
||||
afterEach(() => {
|
||||
@@ -196,18 +198,19 @@ test('profile page loads persisted structured cv and can re-parse it', async ()
|
||||
expect(await screen.findByText(/cv ready/i)).toBeInTheDocument();
|
||||
expect(screen.getByText(/profile sections/i)).toBeInTheDocument();
|
||||
expect(screen.getAllByText(/career information/i).length).toBeGreaterThan(0);
|
||||
expect(screen.getByLabelText(/full name/i)).toHaveValue('Demo User');
|
||||
expect(screen.getByText(/high 92%/i)).toBeInTheDocument();
|
||||
expect(screen.getByText(/block-1/i)).toBeInTheDocument();
|
||||
|
||||
fireEvent.click(screen.getByRole('tab', { name: /import review/i }));
|
||||
expect(screen.getByText(/extraction history/i)).toBeInTheDocument();
|
||||
expect(screen.getByText(/resume.pdf/i)).toBeInTheDocument();
|
||||
expect(screen.getByText(/current run/i)).toBeInTheDocument();
|
||||
expect(screen.queryByText(/template-driven cv builder/i)).not.toBeInTheDocument();
|
||||
expect(screen.getByRole('link', { name: /open cv builder/i })).toHaveAttribute('href', '/career/builder');
|
||||
expect(screen.getAllByText(/original import/i).length).toBeGreaterThan(0);
|
||||
const originalExtractionToggle = screen.getByRole('button', { name: /original import/i });
|
||||
expect(originalExtractionToggle).toHaveAttribute('aria-expanded', 'false');
|
||||
expect(screen.getAllByText(/professional summary/i).length).toBeGreaterThan(0);
|
||||
expect(screen.getByLabelText(/full name/i)).toHaveValue('Demo User');
|
||||
expect(screen.getByText(/high 92%/i)).toBeInTheDocument();
|
||||
expect(screen.getByText(/block-1/i)).toBeInTheDocument();
|
||||
|
||||
fireEvent.click(originalExtractionToggle);
|
||||
expect(originalExtractionToggle).toHaveAttribute('aria-expanded', 'true');
|
||||
@@ -273,7 +276,7 @@ test('editing a field in an extracted section updates parent state and flows int
|
||||
});
|
||||
|
||||
test('profile page can reprocess from stored artifact history', async () => {
|
||||
renderPage();
|
||||
renderPage('import');
|
||||
|
||||
expect(await screen.findByText(/extraction history/i)).toBeInTheDocument();
|
||||
const reprocessButton = screen.getByRole('button', { name: /reprocess cv/i });
|
||||
@@ -354,7 +357,7 @@ test('profile page shows durable CV operation state and retries a failed run', a
|
||||
canRetry: true,
|
||||
},
|
||||
}];
|
||||
renderPage();
|
||||
renderPage('import');
|
||||
|
||||
expect(await screen.findByText('failed')).toBeInTheDocument();
|
||||
fireEvent.click(screen.getByRole('button', { name: /retry processing/i }));
|
||||
@@ -365,10 +368,9 @@ test('profile page shows durable CV operation state and retries a failed run', a
|
||||
});
|
||||
|
||||
test('profile page keeps raw extraction collapsed until expanded', async () => {
|
||||
renderPage();
|
||||
renderPage('import');
|
||||
|
||||
expect(await screen.findByText(/cv ready/i)).toBeInTheDocument();
|
||||
expect(screen.getByText(/your career information stays front and center/i)).toBeInTheDocument();
|
||||
expect(await screen.findByText(/your career information stays front and center/i)).toBeInTheDocument();
|
||||
|
||||
const originalExtractionToggle = screen.getByRole('button', { name: /original import/i });
|
||||
expect(originalExtractionToggle).toHaveAttribute('aria-expanded', 'false');
|
||||
@@ -385,7 +387,7 @@ test('saving the master profile (career) persists the structured profile via /ca
|
||||
// Phase 3: /career saves the master profile through the relational API. The payload carries the
|
||||
// structured profile object (not the legacy flat blob) and never identity fields.
|
||||
mockedApi.put.mockResolvedValue({ data: { profile: structuredCv, completeness: { percent: 70, missing: [], sections: [] }, cvText: '' } } as any);
|
||||
renderWith(CareerProfilePage);
|
||||
renderPage();
|
||||
|
||||
expect(await screen.findByText(/cv ready/i)).toBeInTheDocument();
|
||||
const fullNameInput = screen.getByLabelText(/full name/i);
|
||||
@@ -411,7 +413,25 @@ test('saving the master profile (career) persists the structured profile via /ca
|
||||
test('/career shows the profile completeness overview', async () => {
|
||||
renderWith(CareerProfilePage);
|
||||
expect(await screen.findByText(/profile completeness/i)).toBeInTheDocument();
|
||||
expect(screen.getByText(/70%/)).toBeInTheDocument();
|
||||
expect(await screen.findByText(/70%/)).toBeInTheDocument();
|
||||
});
|
||||
|
||||
test('/career separates overview, profile and import while preserving unsaved profile edits', async () => {
|
||||
renderWith(CareerProfilePage);
|
||||
await screen.findByText(/70%/);
|
||||
|
||||
fireEvent.click(screen.getByRole('link', { name: /edit career profile/i }));
|
||||
const nameField = await screen.findByLabelText(/full name/i);
|
||||
fireEvent.change(nameField, { target: { value: 'Unsaved Section Switch' } });
|
||||
|
||||
fireEvent.click(screen.getByRole('tab', { name: /import review/i }));
|
||||
expect(screen.getByRole('heading', { name: /master cv/i })).toBeInTheDocument();
|
||||
expect(window.location.search).toBe('?section=import');
|
||||
|
||||
fireEvent.click(screen.getByRole('tab', { name: /career profile/i }));
|
||||
expect(await screen.findByLabelText(/full name/i)).toHaveValue('Unsaved Section Switch');
|
||||
expect(screen.getByText('Unsaved changes')).toBeInTheDocument();
|
||||
expect(window.location.search).toBe('?section=profile');
|
||||
});
|
||||
|
||||
test('pending CV extraction can be reviewed and applied', async () => {
|
||||
@@ -426,7 +446,7 @@ test('pending CV extraction can be reviewed and applied', async () => {
|
||||
normalizerVersion: 'm005-s01',
|
||||
llmPromptVersion: 'm005-s01',
|
||||
}];
|
||||
renderPage();
|
||||
renderPage('import');
|
||||
|
||||
expect(await screen.findByText(/3 additions/i)).toBeInTheDocument();
|
||||
const lowConfidence = screen.getByRole('checkbox', { name: /include low-confidence languages: french/i });
|
||||
|
||||
Reference in New Issue
Block a user