refactor(career): Phase 1 increment 2 — extract editor sections, hide duplicate CV concepts
UI-only. No change to APIs, save payloads, extraction behaviour, or data
models. The parent CareerProfilePage still owns loading, state, saving,
and all extraction/import actions; the new sections are presentational
(value + onChange, plus a getMetadata callback for review chips).
Extracted into src/views/career/CareerProfileSections.tsx:
PersonalInformation, ProfessionalSummary, Skills, Interests, Languages,
WorkExperience, Education, OtherSections. FieldReviewNote + confidenceTone
moved there verbatim and shared with the parent. CareerProfilePage went
from 1376 to ~1200 lines.
No Projects/Certifications sections were created -- the editor never had
them (they are not editable structured fields here). Inventing them would
add functionality, which this refactor avoids; noted for a product
decision later.
Hid the duplicate CV concepts behind an "Advanced CV tools" toggle,
collapsed by default: the CV Structure Overview parse block and the
Template-driven CV builder. Both stay mounted and functional (gated with
display:none), so no tested functionality is removed -- the real CV
Builder at /career/builder is the single generation surface. Future
removal plan documented.
Tests: added "editing a field in an extracted section updates parent
state and flows into save" (render -> edit -> PUT /career/profile
{profile,cvText}); existing parse/rewrite tests reveal the advanced tools
first. The increment-1 save-invariant test still pins the payload.
Verified: tsc clean, production build clean, 137 frontend tests pass.
Backend untouched.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
@@ -222,6 +222,9 @@ test('profile page loads persisted structured cv and can re-parse it', async ()
|
||||
expect(originalExtractionToggle).toHaveAttribute('aria-expanded', 'true');
|
||||
expect(await screen.findByLabelText(/profile cv \/ master resume text/i)).toHaveValue('Professional Summary\nBuilt backend systems');
|
||||
|
||||
// Structure overview is hidden from the default workflow (Phase 1 increment 2); reveal it.
|
||||
fireEvent.click(screen.getByRole('button', { name: /advanced cv tools/i }));
|
||||
|
||||
const analyzeButton = screen.getByRole('button', { name: /read sections/i });
|
||||
await waitFor(() => expect(analyzeButton).toBeEnabled());
|
||||
fireEvent.click(analyzeButton);
|
||||
@@ -251,6 +254,33 @@ test('saving the career profile PUTs the structured profile and cv text unchange
|
||||
});
|
||||
});
|
||||
|
||||
test('editing a field in an extracted section updates parent state and flows into save (Phase 1 increment 2)', async () => {
|
||||
renderPage();
|
||||
|
||||
// Renders existing data through the extracted PersonalInformationSection.
|
||||
const nameField = await screen.findByLabelText(/full name/i);
|
||||
expect(nameField).toHaveValue('Demo User');
|
||||
|
||||
// Editing the child field updates the parent's structuredCv (controlled input reflects it back).
|
||||
fireEvent.change(nameField, { target: { value: 'Edited Name' } });
|
||||
expect(nameField).toHaveValue('Edited Name');
|
||||
|
||||
const saveButton = screen.getByRole('button', { name: /save changes/i });
|
||||
await waitFor(() => expect(saveButton).toBeEnabled());
|
||||
fireEvent.click(saveButton);
|
||||
|
||||
// The edited value reaches the unchanged save path.
|
||||
await waitFor(() => {
|
||||
expect(mockedApi.put).toHaveBeenCalledWith(
|
||||
'/career/profile',
|
||||
expect.objectContaining({
|
||||
profile: expect.objectContaining({ contact: expect.objectContaining({ fullName: 'Edited Name' }) }),
|
||||
cvText: expect.any(String),
|
||||
}),
|
||||
);
|
||||
});
|
||||
});
|
||||
|
||||
test('profile page can reprocess from stored artifact history', async () => {
|
||||
renderPage();
|
||||
|
||||
@@ -269,6 +299,9 @@ test('profile page keeps raw extraction collapsed until expanded', async () => {
|
||||
expect(await screen.findByText(/cv ready/i)).toBeInTheDocument();
|
||||
expect(screen.getByText(/your career information stays front and center/i)).toBeInTheDocument();
|
||||
|
||||
// Reveal the advanced tools (Phase 1 increment 2) — the template-builder copy button lives there.
|
||||
fireEvent.click(screen.getByRole('button', { name: /advanced cv tools/i }));
|
||||
|
||||
const originalExtractionToggle = screen.getByRole('button', { name: /original import/i });
|
||||
expect(originalExtractionToggle).toHaveAttribute('aria-expanded', 'false');
|
||||
const copyButton = screen.getByRole('button', { name: /copy cv text/i });
|
||||
@@ -285,6 +318,9 @@ test('profile page keeps raw extraction collapsed until expanded', async () => {
|
||||
test('profile page rewrite tools use selected template and saved job context', async () => {
|
||||
renderPage();
|
||||
|
||||
// Template-driven CV builder is hidden from the default workflow (Phase 1 increment 2); reveal it.
|
||||
await screen.findByRole('button', { name: /advanced cv tools/i });
|
||||
fireEvent.click(screen.getByRole('button', { name: /advanced cv tools/i }));
|
||||
expect(await screen.findByText(/template-driven cv builder/i)).toBeInTheDocument();
|
||||
fireEvent.click(screen.getByText(/harvard/i));
|
||||
fireEvent.change(screen.getByLabelText(/prompt-based cv brief/i), { target: { value: 'Highlight backend platform ownership, distributed systems, and cross-team delivery.' } });
|
||||
|
||||
Reference in New Issue
Block a user