feat(cv): clarify section editing scope (P5-10)
This commit is contained in:
@@ -462,6 +462,7 @@ test('custom entries can be added, edited, reordered and deleted with confirmati
|
||||
const title = screen.getByLabelText(/Section name for custom:/);
|
||||
fireEvent.change(title, { target: { value: 'Selected projects' } });
|
||||
fireEvent.click(screen.getByRole('button', { name: 'Expand Selected projects' }));
|
||||
expect(within(screen.getByRole('group', { name: 'Selected projects' })).getByText('CV-only content')).toBeInTheDocument();
|
||||
fireEvent.click(screen.getByRole('button', { name: 'Add entry' }));
|
||||
fireEvent.change(screen.getByLabelText('Entry 1'), { target: { value: 'First project' } });
|
||||
fireEvent.click(screen.getByRole('button', { name: 'Add entry' }));
|
||||
@@ -471,16 +472,20 @@ test('custom entries can be added, edited, reordered and deleted with confirmati
|
||||
expect(screen.getByLabelText('Entry 1')).toHaveValue('Second project');
|
||||
expect(screen.getByLabelText('Entry 2')).toHaveValue('First project');
|
||||
|
||||
fireEvent.click(screen.getByRole('button', { name: 'Delete custom entry 1' }));
|
||||
const deleteEntry = screen.getByRole('button', { name: 'Delete custom entry 1' });
|
||||
deleteEntry.focus();
|
||||
fireEvent.click(deleteEntry);
|
||||
let dialog = await screen.findByRole('dialog', { name: 'Delete custom entry' });
|
||||
fireEvent.click(within(dialog).getByRole('button', { name: 'Cancel' }));
|
||||
await waitFor(() => expect(screen.queryByRole('dialog')).not.toBeInTheDocument());
|
||||
expect(deleteEntry).toHaveFocus();
|
||||
expect(screen.getAllByLabelText(/^Entry /)).toHaveLength(2);
|
||||
fireEvent.click(screen.getByRole('button', { name: 'Delete custom entry 1' }));
|
||||
dialog = await screen.findByRole('dialog', { name: 'Delete custom entry' });
|
||||
fireEvent.click(within(dialog).getByRole('button', { name: 'Delete entry' }));
|
||||
await waitFor(() => expect(screen.getAllByLabelText(/^Entry /)).toHaveLength(1));
|
||||
await waitFor(() => expect(screen.queryByRole('dialog')).not.toBeInTheDocument());
|
||||
expect(screen.getByLabelText('Entry 1')).toHaveFocus();
|
||||
|
||||
await waitFor(() => expect(mockedApi.put).toHaveBeenLastCalledWith('/cv/variants/3', expect.objectContaining({
|
||||
settings: expect.objectContaining({
|
||||
@@ -500,6 +505,120 @@ test('custom entries can be added, edited, reordered and deleted with confirmati
|
||||
await waitFor(() => expect(screen.queryByLabelText(/Section name for custom:/)).not.toBeInTheDocument());
|
||||
});
|
||||
|
||||
const contentOutline = {
|
||||
sections: [{
|
||||
key: 'experience', title: 'Experience', kind: 'entries', bullets: [], tags: [], entries: [
|
||||
{ key: 'job-1', title: 'Engineer', subtitle: 'First Co', meta: '2021-01 – Present', bullets: ['Built APIs'], tags: [] },
|
||||
{ key: 'job-2', title: 'Lead', subtitle: 'Second Co', bullets: ['Led teams'], tags: [] },
|
||||
],
|
||||
}],
|
||||
};
|
||||
|
||||
test('[P5-10 PRES-C01 C07] source-backed entry edits identify their scope and save only this CV', async () => {
|
||||
routeGet(() => Promise.resolve({ data: variant }), contentOutline);
|
||||
renderAt(3);
|
||||
await openContentTab();
|
||||
fireEvent.click(screen.getByRole('button', { name: 'Expand Experience' }));
|
||||
const entry = screen.getByRole('group', { name: 'Engineer' });
|
||||
expect(entry).not.toHaveTextContent('2021-01 – Present');
|
||||
expect(within(entry).getAllByText('Using Career Profile')).toHaveLength(3);
|
||||
fireEvent.change(within(entry).getByLabelText('Title'), { target: { value: ' ' } });
|
||||
expect(screen.getByRole('group', { name: 'Engineer' })).toBe(entry);
|
||||
expect(within(entry).getByText('Blank uses Career Profile')).toBeInTheDocument();
|
||||
fireEvent.change(within(entry).getByLabelText('Title'), { target: { value: 'Platform Engineer' } });
|
||||
fireEvent.change(within(entry).getByLabelText('Subtitle'), { target: { value: 'CV-specific employer label' } });
|
||||
fireEvent.change(within(entry).getByLabelText('Bullet points (one per line)'), { target: { value: 'Reviewed CV bullet\nSecond CV bullet' } });
|
||||
expect(within(entry).getAllByText('Customized for this CV')).toHaveLength(3);
|
||||
fireEvent.click(screen.getByRole('button', { name: 'Save now' }));
|
||||
await screen.findByText('Saved');
|
||||
expect(mockedApi.put).toHaveBeenLastCalledWith('/cv/variants/3', expect.objectContaining({
|
||||
expectedVersion: 4, settings: expect.objectContaining({ overrides: {
|
||||
'job-1': { title: 'Platform Engineer', subtitle: 'CV-specific employer label', bullets: ['Reviewed CV bullet', 'Second CV bullet'] },
|
||||
} }),
|
||||
}));
|
||||
expect(mockedApi.put.mock.calls.every(([url]) => url === '/cv/variants/3')).toBe(true);
|
||||
expect(contentOutline.sections[0].entries[0]).toEqual({ key: 'job-1', title: 'Engineer', subtitle: 'First Co', meta: '2021-01 – Present', bullets: ['Built APIs'], tags: [] });
|
||||
});
|
||||
|
||||
test('[P5-10 PRES-C07 C10] resetting one entry field preserves omission, sibling override and order and supports undo', async () => {
|
||||
const settings = { ...variant.settings,
|
||||
sections: [{ key: 'experience', itemOrder: ['job-2', 'job-1'] }],
|
||||
overrides: {
|
||||
'job-1': { title: 'CV Engineer', subtitle: 'CV employer', bullets: ['CV bullet'], hidden: true },
|
||||
'job-2': { title: 'CV Lead', bullets: ['Sibling item override'] },
|
||||
},
|
||||
};
|
||||
routeGet(() => Promise.resolve({ data: { ...variant, settings } }), contentOutline);
|
||||
renderAt(3);
|
||||
await openContentTab();
|
||||
fireEvent.click(screen.getByRole('button', { name: 'Expand Experience' }));
|
||||
const entry = screen.getByRole('group', { name: 'CV Engineer' });
|
||||
fireEvent.click(within(entry).getByRole('button', { name: 'Use Career Profile: Title' }));
|
||||
expect(within(entry).getByLabelText('Title')).toHaveValue('Engineer');
|
||||
expect(within(entry).getByLabelText('Subtitle')).toHaveValue('CV employer');
|
||||
expect(within(entry).getByLabelText('Bullet points (one per line)')).toHaveValue('CV bullet');
|
||||
fireEvent.click(within(entry).getByRole('button', { name: 'Use Career Profile: Subtitle' }));
|
||||
expect(within(entry).getByLabelText('Subtitle')).toHaveValue('First Co');
|
||||
fireEvent.click(within(entry).getByRole('button', { name: 'Use Career Profile: Bullet points (one per line)' }));
|
||||
expect(within(entry).getByLabelText('Bullet points (one per line)')).toHaveValue('Built APIs');
|
||||
expect(within(entry).getAllByText('Using Career Profile')).toHaveLength(3);
|
||||
expect(within(entry).getByRole('button', { name: 'Show Engineer entry' })).toBeInTheDocument();
|
||||
fireEvent.click(screen.getByRole('button', { name: 'Undo' }));
|
||||
expect(within(entry).getByLabelText('Title')).toHaveValue('Engineer');
|
||||
expect(within(entry).getByLabelText('Subtitle')).toHaveValue('First Co');
|
||||
expect(within(entry).getByLabelText('Bullet points (one per line)')).toHaveValue('CV bullet');
|
||||
fireEvent.click(screen.getByRole('button', { name: 'Redo' }));
|
||||
fireEvent.click(screen.getByRole('button', { name: 'Save now' }));
|
||||
await screen.findByText('Saved');
|
||||
const saved = mockedApi.put.mock.calls[mockedApi.put.mock.calls.length - 1][1] as any;
|
||||
expect(saved.settings.overrides).toEqual({ 'job-1': { hidden: true }, 'job-2': settings.overrides['job-2'] });
|
||||
expect(saved.settings.sections[0].itemOrder).toEqual(['job-2', 'job-1']);
|
||||
});
|
||||
|
||||
test('[P5-10 PRES-C07 C10 E08] omitted entries remain editable and retain their draft after a failed save', async () => {
|
||||
routeGet(() => Promise.resolve({ data: { ...variant, settings: { ...variant.settings, overrides: { 'job-1': { hidden: true } } } } }), contentOutline);
|
||||
mockedApi.put.mockRejectedValueOnce(new Error('offline')).mockResolvedValueOnce({ data: { ...variant, version: 5 } } as any);
|
||||
renderAt(3);
|
||||
await openContentTab();
|
||||
fireEvent.click(screen.getByRole('button', { name: 'Expand Experience' }));
|
||||
const entry = screen.getByRole('group', { name: 'Engineer' });
|
||||
expect(within(entry).getByText('Omitted', { exact: true })).toBeInTheDocument();
|
||||
expect(within(entry).getByLabelText('Title')).toBeEnabled();
|
||||
expect(within(entry).getByLabelText('Subtitle')).toBeEnabled();
|
||||
const bullets = within(entry).getByLabelText('Bullet points (one per line)');
|
||||
fireEvent.change(bullets, { target: { value: 'Retained omitted-entry draft' } });
|
||||
fireEvent.click(screen.getByRole('button', { name: 'Save now' }));
|
||||
await screen.findByText('Save failed');
|
||||
expect(bullets).toHaveValue('Retained omitted-entry draft');
|
||||
expect(within(entry).getByRole('button', { name: 'Show Engineer entry' })).toBeInTheDocument();
|
||||
fireEvent.click(screen.getByRole('button', { name: 'Save now' }));
|
||||
await screen.findByText('Saved');
|
||||
expect(mockedApi.put).toHaveBeenLastCalledWith('/cv/variants/3', expect.objectContaining({
|
||||
settings: expect.objectContaining({ overrides: { 'job-1': { hidden: true, bullets: ['Retained omitted-entry draft'] } } }),
|
||||
}));
|
||||
});
|
||||
|
||||
test('[P5-10 PRES-C01 C07] section content reset inherits source without changing omission or presentation', async () => {
|
||||
routeGet(() => Promise.resolve({ data: { ...variant, settings: { ...variant.settings,
|
||||
sections: [{ key: 'skills', items: ['CV-specific skill'], hidden: true, presentation: 'bubble' }],
|
||||
} } }), { sections: [{ key: 'skills', title: 'Skills', kind: 'tags', tags: ['C#', 'Norsk'], bullets: [], entries: [] }] });
|
||||
renderAt(3);
|
||||
await openContentTab();
|
||||
fireEvent.click(screen.getByRole('button', { name: 'Expand Skills' }));
|
||||
const section = screen.getByRole('group', { name: 'Skills' });
|
||||
expect(within(section).getByLabelText('Items (one per line)')).toHaveValue('CV-specific skill');
|
||||
fireEvent.click(within(section).getByRole('button', { name: 'Use Career Profile content' }));
|
||||
expect(within(section).getByLabelText('Items (one per line)')).toHaveValue('C#\nNorsk');
|
||||
expect(within(section).getByRole('button', { name: 'Show Skills section' })).toBeInTheDocument();
|
||||
fireEvent.click(screen.getByRole('button', { name: 'Save now' }));
|
||||
await screen.findByText('Saved');
|
||||
const saved = mockedApi.put.mock.calls[mockedApi.put.mock.calls.length - 1][1] as any;
|
||||
const row = saved.settings.sections.find((item: any) => item.key === 'skills');
|
||||
expect(row).toEqual(expect.objectContaining({ key: 'skills', hidden: true, presentation: 'bubble' }));
|
||||
expect(row.items).toBeUndefined();
|
||||
expect(mockedApi.put.mock.calls.every(([url]) => url === '/cv/variants/3')).toBe(true);
|
||||
});
|
||||
|
||||
test('profile-backed sections expand, reorder, hide and persist variant-only overrides', async () => {
|
||||
routeGet(() => Promise.resolve({ data: variant } as any), {
|
||||
sections: [{
|
||||
|
||||
Reference in New Issue
Block a user