feat(cv): redesign document editing with revision-safe letters and writing tools
This commit is contained in:
@@ -58,6 +58,7 @@ function renderAt(id: number) {
|
||||
|
||||
beforeEach(() => {
|
||||
window.localStorage.removeItem('uiLanguage');
|
||||
global.ResizeObserver = class { observe() {} unobserve() {} disconnect() {} };
|
||||
jest.clearAllMocks();
|
||||
mockedApi.put.mockReset();
|
||||
mockedApi.put.mockResolvedValue({ data: { ...variant, version: variant.version + 1 } } as any);
|
||||
@@ -90,6 +91,66 @@ test('content sections follow the editing-region heading without mutating CV set
|
||||
expect(mockedApi.put).not.toHaveBeenCalled();
|
||||
});
|
||||
|
||||
test('settings history explains current Career Profile resolution without writing data', async () => {
|
||||
routeGet(() => Promise.resolve({ data: variant }));
|
||||
renderAt(3);
|
||||
await openContentTab();
|
||||
await act(async () => fireEvent.click(screen.getByRole('tab', { name: 'History' })));
|
||||
expect(screen.getByText(/These are CV settings revisions/)).toHaveTextContent('current Career Profile');
|
||||
expect(mockedApi.put).not.toHaveBeenCalled();
|
||||
});
|
||||
|
||||
test('PDF export locks immediately, rejects a newer unsaved draft, and keeps that draft recoverable', async () => {
|
||||
let complete!: (value: any) => void;
|
||||
mockedApi.put.mockImplementation(() => new Promise(resolve => { complete = resolve; }));
|
||||
routeGet(() => Promise.resolve({ data: variant }));
|
||||
renderAt(3);
|
||||
await openContentTab();
|
||||
const headline = screen.getByLabelText('Headline override');
|
||||
fireEvent.change(headline, { target: { value: 'First intended draft' } });
|
||||
fireEvent.click(screen.getByRole('button', { name: 'Download PDF' }));
|
||||
await waitFor(() => expect(mockedApi.put).toHaveBeenCalledTimes(1));
|
||||
expect(screen.getByRole('button', { name: /Exporting/ })).toBeDisabled();
|
||||
fireEvent.change(headline, { target: { value: 'Newer retained draft' } });
|
||||
await act(async () => complete({ data: { ...variant, version: 5 } }));
|
||||
await waitFor(() => expect(screen.getByRole('button', { name: 'Download PDF' })).toBeEnabled());
|
||||
expect(headline).toHaveValue('Newer retained draft');
|
||||
expect(mockedApi.post.mock.calls.some(([url]) => url.includes('/export-pdf'))).toBe(false);
|
||||
});
|
||||
|
||||
test('edits during a held PDF response prevent an older document download', async () => {
|
||||
let complete!: (value: any) => void;
|
||||
const createUrl = jest.fn(() => 'blob:test-pdf');
|
||||
Object.defineProperty(URL, 'createObjectURL', { configurable: true, value: createUrl });
|
||||
const click = jest.spyOn(HTMLAnchorElement.prototype, 'click').mockImplementation(() => {});
|
||||
mockedApi.post.mockImplementation((url: string) => url.endsWith('/export-pdf')
|
||||
? new Promise(resolve => { complete = resolve; })
|
||||
: Promise.resolve({ data: { html: '<p>preview</p>' } } as any));
|
||||
try {
|
||||
routeGet(() => Promise.resolve({ data: variant }));
|
||||
renderAt(3);
|
||||
await openContentTab();
|
||||
fireEvent.click(screen.getByRole('button', { name: 'Download PDF' }));
|
||||
await waitFor(() => expect(mockedApi.post).toHaveBeenCalledWith('/cv/variants/3/export-pdf', {}, { responseType: 'blob', params: { expectedVersion: 4 } }));
|
||||
fireEvent.change(screen.getByLabelText('Headline override'), { target: { value: 'Edit during PDF rendering' } });
|
||||
await act(async () => complete({ data: new Blob(['older document']) }));
|
||||
expect(createUrl).not.toHaveBeenCalled();
|
||||
expect(screen.getByLabelText('Headline override')).toHaveValue('Edit during PDF rendering');
|
||||
} finally { click.mockRestore(); Reflect.deleteProperty(URL, 'createObjectURL'); }
|
||||
});
|
||||
|
||||
test('moving a section to the first position announces its actual order and focuses its row', async () => {
|
||||
routeGet(() => Promise.resolve({ data: { ...variant, settings: { ...variant.settings,
|
||||
sections: [{ key: 'summary' }, { key: 'experience' }],
|
||||
} } }));
|
||||
renderAt(3);
|
||||
await openContentTab();
|
||||
const move = screen.getByRole('button', { name: 'Move Experience section up' });
|
||||
act(() => { move.focus(); fireEvent.click(move); });
|
||||
expect(screen.getByRole('status', { name: 'Section order' }).textContent).toMatch(/Experience: position 1 of/);
|
||||
await waitFor(() => expect(screen.getByRole('button', { name: 'Expand Experience' })).toHaveFocus());
|
||||
});
|
||||
|
||||
test('section navigation opens and focuses omitted/custom sections without changing their data', async () => {
|
||||
const settings = { ...variant.settings,
|
||||
sections: [{ key: 'skills', hidden: true }, { key: 'custom:sample', hidden: false }],
|
||||
@@ -448,6 +509,10 @@ test('choosing a template restores its own structure instead of inheriting the p
|
||||
fireEvent.click(screen.getByRole('tab', { name: 'Template' }));
|
||||
fireEvent.click(screen.getByRole('button', { name: 'All templates' }));
|
||||
fireEvent.click(screen.getByRole('button', { name: /Technical/ }));
|
||||
const candidate = await screen.findByRole('dialog', { name: /Preview design/ });
|
||||
const apply = within(candidate).getByRole('button', { name: 'Apply template' });
|
||||
await waitFor(() => expect(apply).toBeEnabled());
|
||||
fireEvent.click(apply);
|
||||
fireEvent.click(within(await screen.findByRole('dialog', { name: 'Apply template defaults?' })).getByRole('button', { name: 'Apply template' }));
|
||||
await waitFor(() => expect(screen.queryByRole('dialog')).not.toBeInTheDocument());
|
||||
fireEvent.click(screen.getByRole('button', { name: 'Save now' }));
|
||||
@@ -771,6 +836,12 @@ test('[P5-11 PRES-C06 C12] page count is announced only after the renderer has s
|
||||
expect(region).toHaveAttribute('aria-busy', 'false');
|
||||
expect(screen.getByText('3-page CV')).toBeInTheDocument();
|
||||
expect(region.querySelectorAll('[data-cv-page]')).toHaveLength(3);
|
||||
const second = region.querySelector('[data-cv-page="2"]') as HTMLElement;
|
||||
second.getBoundingClientRect = () => ({ top: 850 } as DOMRect);
|
||||
region.getBoundingClientRect = () => ({ top: 50 } as DOMRect);
|
||||
fireEvent.mouseDown(screen.getByRole('combobox', { name: 'Go to page' }));
|
||||
fireEvent.click(screen.getByRole('option', { name: 'Page 2 of 3' }));
|
||||
expect(region.scrollTop).toBe(800);
|
||||
});
|
||||
|
||||
test('[P5-11 PRES-C06 E09] overlapping preview successes and failures cannot replace the newest document', async () => {
|
||||
|
||||
Reference in New Issue
Block a user