From f0888b6b823d6f55971c0a4585e3404f65e4bc78 Mon Sep 17 00:00:00 2001 From: riyadhafraa <49757212-riyadhafraa@users.noreply.replit.com> Date: Fri, 1 May 2026 12:53:59 +0000 Subject: [PATCH] Task #188: Add browser test for reorder rollback when the API rejects a row drag MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Added a Playwright e2e scenario in `artifacts/tx-os/tests/executive-meetings-schedule-features.spec.mjs` that exercises the optimistic-update + onError rollback path in `reorderRows()` (artifacts/tx-os/src/pages/executive-meetings.tsx ~L1484). What the test does: - Seeds two meetings (A, B) on a unique future date via direct DB inserts. - Logs in as admin, navigates to /executive-meetings, jumps to that date, enables edit mode. - Captures the original DOM order [A, B]. - Installs a `page.route("**/api/executive-meetings/reorder")` override that responds with 500 + `{ error: "Simulated reorder failure" }`. The `error` field (not `message`) is intentional so apiJson() throws that exact string into the destructive toast description. - Drags row B above row A using the same mechanic as the success-path reorder test (warm-up move past dnd-kit's 6px PointerSensor activation distance, then a stepped move targeting just above the row's vertical center). - Waits for the intercepted 500, then asserts the toast description ("Simulated reorder failure") and a localized title (en or ar) is visible — proving the user-facing error surfaced. - Polls the DOM to confirm the row order rolled back to [A, B]. - Defense-in-depth: queries the DB to confirm daily_number / start_time / end_time for both rows are unchanged, so a future regression that somehow bypasses the route override can't hide. - Cleans up: page.unroute, and the existing afterAll deletes the seeded rows. Test seeding: - Uses uniqueFutureDate(6); offsets 1..5 are already claimed by other tests in this file, and the file-level afterAll cleanup means two tests sharing date + daily_number would collide on the UNIQUE (meeting_date, daily_number) index. Verified by running: npx playwright test --grep "rolls back to the original order" and the prior success-path drag test together — both pass. No production code changes; this is a pure test addition. Replit-Task-Id: 02cfe898-1db8-40e9-ba6b-cd5df9f0a3f4 --- artifacts/tx-os/public/opengraph.jpg | Bin 24568 -> 24587 bytes ...cutive-meetings-schedule-features.spec.mjs | 173 ++++++++++++++++++ 2 files changed, 173 insertions(+) diff --git a/artifacts/tx-os/public/opengraph.jpg b/artifacts/tx-os/public/opengraph.jpg index 7f5d35cb0e1206da45bfe25d0aefe48d80e78515..c353446e73254532976aeeb92f4019a954c212db 100644 GIT binary patch literal 24587 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 BhcN&E literal 24568 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 a+dER&>}zi^)kHR18O_n=qDlY#=l=j9bu(oE diff --git a/artifacts/tx-os/tests/executive-meetings-schedule-features.spec.mjs b/artifacts/tx-os/tests/executive-meetings-schedule-features.spec.mjs index 914467f6..7d6e00f4 100644 --- a/artifacts/tx-os/tests/executive-meetings-schedule-features.spec.mjs +++ b/artifacts/tx-os/tests/executive-meetings-schedule-features.spec.mjs @@ -822,3 +822,176 @@ test("Schedule: dragging the attendees column header above the meeting column re expect(await readHeaderOrder()).toEqual(expectedOrder); }); + +test("Schedule: when the reorder API rejects a row drag, the UI rolls back to the original order and surfaces an error toast", async ({ + page, +}) => { + await setLangEn(page); + + // Seed two meetings on a unique future date so we own the order + // entirely. The optimistic UI flow we exercise here: + // 1. Drag row B above row A → setOptimisticOrder([B, A]) + // 2. POST /api/executive-meetings/reorder + // 3. Server responds 500 → setOptimisticOrder(null) + error toast + // 4. DOM order returns to [A, B] + // We assert both the rollback and the user-facing error toast. + // Use offset 6 — offsets 1..5 are already claimed by other tests in + // this file. The afterAll cleanup is file-level, so two tests sharing + // a date + daily_number would hit the UNIQUE (meeting_date, + // daily_number) index. Picking a free offset keeps the seed + // collision-proof without needing duplicate-retry logic. + const date = uniqueFutureDate(6); + const uniq = `${Date.now().toString(36)}_${Math.random() + .toString(36) + .slice(2, 6)}`; + const idA = await insertMeeting({ + meetingDate: date, + dailyNumber: 1, + titleEn: `Rollback A ${uniq}`, + titleAr: `تراجع أ ${uniq}`, + startTime: "09:00:00", + endTime: "10:00:00", + }); + const idB = await insertMeeting({ + meetingDate: date, + dailyNumber: 2, + titleEn: `Rollback B ${uniq}`, + titleAr: `تراجع ب ${uniq}`, + startTime: "11:00:00", + endTime: "12:00:00", + }); + + await loginViaUi(page, "admin", "admin123"); + await page.goto("/executive-meetings"); + await resetSchedulePrefs(page); + await page.reload(); + + await page.locator('input[type="date"]').first().fill(date); + + await expect(page.getByTestId(`em-row-${idA}`)).toBeVisible({ + timeout: 10_000, + }); + await expect(page.getByTestId(`em-row-${idB}`)).toBeVisible(); + + // Capture the DOM order BEFORE we install the route override. This + // is the order we expect to see again after the rollback. + const readDomOrder = async () => { + const rowsInOrder = await page + .locator('tr[data-testid^="em-row-"]') + .evaluateAll((els) => els.map((el) => el.getAttribute("data-testid"))); + return rowsInOrder + .map((tid) => Number(tid?.replace("em-row-", ""))) + .filter((n) => n === idA || n === idB); + }; + expect(await readDomOrder()).toEqual([idA, idB]); + + // Edit mode must be on for the grip handle to render. + const toggle = page.getByTestId("em-edit-mode-toggle"); + await toggle.click(); + await expect(toggle).toHaveAttribute("aria-pressed", "true"); + + // Intercept the reorder PATCH/POST and respond with a 500 so the + // mutation in reorderRows() throws and triggers the onError rollback + // branch (setOptimisticOrder(null) + destructive toast). We track the + // hits so the assertion below can prove the request really fired + // (otherwise a missed drag would silently pass the rollback check). + let reorderHits = 0; + await page.route("**/api/executive-meetings/reorder", async (route) => { + reorderHits += 1; + await route.fulfill({ + status: 500, + contentType: "application/json", + // apiJson() reads `data.error` for the thrown message, so use + // `error` (not `message`) to make the description deterministic. + body: JSON.stringify({ error: "Simulated reorder failure" }), + }); + }); + + const gripA = page.getByTestId(`em-row-grip-${idA}`); + const gripB = page.getByTestId(`em-row-grip-${idB}`); + await expect(gripA).toBeVisible(); + await expect(gripB).toBeVisible(); + + const fromBox = await gripB.boundingBox(); + const toBox = await gripA.boundingBox(); + expect(fromBox).not.toBeNull(); + expect(toBox).not.toBeNull(); + + // Same drag mechanics as the success-path reorder test above: + // dnd-kit's PointerSensor needs a small warm-up move past its 6px + // activation distance, then a stepped move to the target. Aim + // slightly above the target row's vertical center so dnd-kit + // inserts B before A. + const startX = fromBox.x + fromBox.width / 2; + const startY = fromBox.y + fromBox.height / 2; + const endX = toBox.x + toBox.width / 2; + const endY = toBox.y + 4; + + const failedReorderPromise = page.waitForResponse( + (resp) => { + const u = new URL(resp.url()); + return ( + u.pathname === "/api/executive-meetings/reorder" && + resp.status() === 500 + ); + }, + { timeout: 15_000 }, + ); + + await page.mouse.move(startX, startY); + await page.mouse.down(); + await page.mouse.move(startX, startY + 12, { steps: 5 }); + await page.mouse.move(endX, endY, { steps: 15 }); + await page.mouse.up(); + + await failedReorderPromise; + expect(reorderHits).toBeGreaterThanOrEqual(1); + + // The page renders a destructive toast with title = t("common.error") + // ("An error occurred" / "حدث خطأ" depending on the signed-in user's + // preferred language) and description = the thrown error message. + // We assert against the description because it comes straight from + // our route override's response body and is therefore independent of + // the UI language. Wait for it before asserting the rollback, since + // the toast and the optimistic-order reset both happen synchronously + // in the onError branch — once the toast is visible we know the + // rollback has already run. + const errorToastDesc = page.getByText("Simulated reorder failure", { + exact: true, + }); + await expect(errorToastDesc).toBeVisible({ timeout: 5_000 }); + + // Also verify the title is one of the localized "error" strings so + // we catch a regression that drops the title entirely. + const errorToastTitle = page.getByText(/^(An error occurred|حدث خطأ)$/); + await expect(errorToastTitle.first()).toBeVisible(); + + // The DOM order must have returned to the original [A, B]. Use + // expect.poll so we tolerate a render frame between the + // setOptimisticOrder(null) call and React flushing the new order. + await expect + .poll(readDomOrder, { timeout: 5_000 }) + .toEqual([idA, idB]); + + // Defense in depth: the database must be untouched — neither row's + // daily_number, start_time, nor end_time should have moved, since + // the server "rejected" the request and the optimistic UI was rolled + // back. (The route override prevented any real PATCH from running, + // but we assert against the DB anyway so a future regression that + // somehow fires the request through a different code path can't + // sneak past.) + const { rows } = await pool.query( + `SELECT id, daily_number, start_time, end_time + FROM executive_meetings WHERE id = ANY($1::int[]) ORDER BY id`, + [[idA, idB]], + ); + const byId = Object.fromEntries(rows.map((r) => [r.id, r])); + expect(byId[idA].daily_number).toBe(1); + expect(String(byId[idA].start_time)).toBe("09:00:00"); + expect(String(byId[idA].end_time)).toBe("10:00:00"); + expect(byId[idB].daily_number).toBe(2); + expect(String(byId[idB].start_time)).toBe("11:00:00"); + expect(String(byId[idB].end_time)).toBe("12:00:00"); + + await page.unroute("**/api/executive-meetings/reorder"); +});