fix: keep preview retry controls stationary during pointer activation
This commit is contained in:
@@ -192,7 +192,11 @@ for (const [mode, language] of [["light", "en"], ["dark", "nb"], ["light", "nb"]
|
||||
await expect(frame.getByText("Reviewed CV-only æøå", { exact: true })).toBeVisible();
|
||||
await page.getByText(t.cvEditorPreviewUnavailable, { exact: true }).evaluate(node => node.scrollIntoView({ block: "center" }));
|
||||
await capture(page, info, `error-${suffix}`);
|
||||
await page.getByRole("button", { name: t.cvEditorRetryPreview, exact: true }).click();
|
||||
// A held pointer must keep its target even if focus handlers schedule a layout frame.
|
||||
await page.getByRole("button", { name: t.cvEditorRetryPreview, exact: true }).hover();
|
||||
await page.mouse.down();
|
||||
await page.evaluate(() => new Promise<void>(resolve => requestAnimationFrame(() => requestAnimationFrame(() => resolve()))));
|
||||
await page.mouse.up();
|
||||
await expect(frame.getByText("Canonical engineer", { exact: true })).toBeVisible();
|
||||
await expect(page.getByText(t.cvEditorPreviewUnavailable, { exact: true })).not.toBeVisible();
|
||||
await page.getByRole("button", { name: t.cvEditorRedo, exact: true }).click();
|
||||
|
||||
@@ -181,8 +181,9 @@ test("[PRES-C06 C10 E08] stale editor retains and downloads its draft; reviewed
|
||||
await expect(headline).toBeVisible();
|
||||
const remote = await page.request.put(`${apiUrl}/cv/variants/${cv.id}`, { headers, data: { expectedVersion: cv.version, settings: { ...cv.settings, headline: "Remote reviewed edit" } } });
|
||||
expect(remote.status()).toBe(200);
|
||||
const rejectedSave = page.waitForResponse(response => response.url() === `${apiUrl}/cv/variants/${cv.id}` && response.request().method() === "PUT" && response.status() === 409);
|
||||
await headline.fill("Local recoverable draft æøå");
|
||||
await page.getByRole("button", { name: "Save now", exact: true }).click();
|
||||
expect((await rejectedSave).status()).toBe(409);
|
||||
await expect(page.getByText("CV changed elsewhere", { exact: true })).toBeVisible();
|
||||
await expect(headline).toHaveValue("Local recoverable draft æøå");
|
||||
const downloadPromise = page.waitForEvent("download");
|
||||
@@ -201,8 +202,9 @@ test("[PRES-C06 C10 E08] stale editor retains and downloads its draft; reviewed
|
||||
await page.getByRole("dialog").getByRole("button", { name: "Reload saved CV", exact: true }).click();
|
||||
await expect(page.getByRole("dialog")).toHaveCount(0);
|
||||
await expect(headline).toHaveValue("Remote reviewed edit");
|
||||
const reviewedSave = page.waitForResponse(response => response.url() === `${apiUrl}/cv/variants/${cv.id}` && response.request().method() === "PUT" && response.status() === 200);
|
||||
await headline.fill("Reviewed combined edit");
|
||||
await page.getByRole("button", { name: "Save now", exact: true }).click();
|
||||
expect((await reviewedSave).status()).toBe(200);
|
||||
await expect(page.getByText("Saved", { exact: true })).toBeVisible();
|
||||
expect((await (await page.request.get(`${apiUrl}/cv/variants/${cv.id}`)).json()).version).toBe(3);
|
||||
await page.getByRole("tab", { name: "History", exact: true }).click();
|
||||
|
||||
@@ -701,6 +701,26 @@ test('profile-backed sections expand, reorder, hide and persist variant-only ove
|
||||
expect(await screen.findByText('Saved')).toBeInTheDocument();
|
||||
});
|
||||
|
||||
test('focusing a preview retry control keeps its pointer target stationary', async () => {
|
||||
routeGet(() => Promise.resolve({ data: variant } as any));
|
||||
mockedApi.post.mockRejectedValueOnce(new Error('preview offline'));
|
||||
renderAt(3);
|
||||
const retry = await screen.findByRole('button', { name: 'Retry preview' });
|
||||
const region = screen.getByRole('region', { name: 'Live preview' });
|
||||
const scroll = jest.fn();
|
||||
region.parentElement!.scrollIntoView = scroll;
|
||||
await act(async () => {
|
||||
retry.focus();
|
||||
await new Promise<void>(resolve => requestAnimationFrame(() => resolve()));
|
||||
});
|
||||
expect(scroll).not.toHaveBeenCalled();
|
||||
await act(async () => {
|
||||
region.focus();
|
||||
await new Promise<void>(resolve => requestAnimationFrame(() => resolve()));
|
||||
});
|
||||
expect(scroll).toHaveBeenCalled();
|
||||
});
|
||||
|
||||
test('preview failure is visible and retryable without leaving the editor', async () => {
|
||||
routeGet(() => Promise.resolve({ data: variant } as any));
|
||||
mockedApi.post.mockRejectedValueOnce(new Error('preview offline')).mockResolvedValueOnce({ data: { themeId: 'modern', html: '<p>retry</p>', suggestedFileName: 'cv.pdf' } } as any);
|
||||
|
||||
@@ -625,6 +625,8 @@ export default function CvBuilderEditor() {
|
||||
|
||||
<Paper variant="outlined" onFocusCapture={(event) => {
|
||||
setMobilePane("preview");
|
||||
// Moving a pointer-focused control here can cancel its pending click.
|
||||
if (event.target !== scrollRef.current) return;
|
||||
const pane = event.currentTarget;
|
||||
requestAnimationFrame(() => {
|
||||
if (pane.contains(document.activeElement)) pane.scrollIntoView?.({ block: "start", inline: "nearest" });
|
||||
|
||||
Reference in New Issue
Block a user