Task #491: center quick-actions surface + frame the selected row
The user reported that the small quick-actions popover (which holds the Postpone trigger) was hard to find — it floated next to the clicked row and they sometimes lost track of which meeting they were about to act on. They asked for the surface to appear in the center of the screen, and for the row they clicked to be clearly framed in a color while the surface is open. Changes: - Replaced the row's row-anchored Popover (PopoverAnchor + PopoverContent) with a centered Dialog (DialogContent) that Radix renders into a portal. The Dialog uses the existing `executiveMeetings.quickActions.label` translation key for its title (already present in both Arabic and English) and reuses #490's polished Postpone Button unchanged. Test ID stays `em-row-quick-${id}` so existing e2e specs continue to target the same surface. - Added a third inset-shadow ring (`inset 0 0 0 3px ${highlightColor}`) to the row's composed boxShadow stack while the row's quickOpen state is true. Listed first in the stack so it becomes the outermost frame and wins visually over the existing current-meeting / bulk-select rings. Uses the page-level highlightPrefs.color so the frame matches the user's chosen accent. Also exposes `data-quick-open="true"` on the <tr> for tests. - Updated the row-quick-actions e2e: the postpone-end-to-end test now asserts `role="dialog"` on the surface and the row carries `data-quick-open` while open and loses it after Postpone is clicked. Added a separate test for Escape closing the surface and clearing the row frame. Verified: 5/5 quick-actions tests pass, plus both drag specs (row-drag + iPad touch-reorder) still pass.
This commit is contained in:
@@ -1,9 +1,12 @@
|
||||
// #486 → #489: e2e for the Executive Meetings schedule row quick-
|
||||
// actions popover. After #489, the Move up / Move down buttons are
|
||||
// gone (the user drags the whole row instead — see
|
||||
// executive-meetings-row-drag.spec.mjs). The popover now exposes only
|
||||
// the Postpone action, and the gating + skip rules around interactive
|
||||
// row affordances stay the same.
|
||||
// #486 → #489 → #491: e2e for the Executive Meetings schedule row
|
||||
// quick-actions surface. After #489, the Move up / Move down buttons
|
||||
// are gone (the user drags the whole row instead — see
|
||||
// executive-meetings-row-drag.spec.mjs). After #491, the surface is a
|
||||
// centered Dialog (Radix portal) rather than a row-anchored Popover,
|
||||
// and the originating row carries a coloured frame while the surface
|
||||
// is open. The surface still exposes only the Postpone action, and
|
||||
// the gating + skip rules around interactive row affordances stay the
|
||||
// same.
|
||||
import { test, expect } from "@playwright/test";
|
||||
import pg from "pg";
|
||||
|
||||
@@ -131,7 +134,7 @@ test.afterAll(async () => {
|
||||
await pool.end();
|
||||
});
|
||||
|
||||
test("Postpone quick-action runs a 5-minute postpone end-to-end", async ({
|
||||
test("Postpone quick-action surface is a centered Dialog and runs a 5-minute postpone end-to-end", async ({
|
||||
page,
|
||||
}) => {
|
||||
const id = await insertMeeting({
|
||||
@@ -157,6 +160,20 @@ test("Postpone quick-action runs a 5-minute postpone end-to-end", async ({
|
||||
// it shows the coloured frame.
|
||||
await expect(surface).toHaveAttribute("role", "dialog");
|
||||
await expect(row).toHaveAttribute("data-quick-open", "true");
|
||||
// Geometric centering: the surface's bounding box should sit close
|
||||
// to the viewport center on both axes. We allow a generous 25%
|
||||
// tolerance so the assertion is robust against the design-system
|
||||
// Dialog primitive's exact padding/widths but still catches a
|
||||
// regression to row-anchored placement (which would land far off
|
||||
// center, especially on tall lists).
|
||||
const sBox = await surface.boundingBox();
|
||||
const vp = page.viewportSize();
|
||||
expect(sBox).not.toBeNull();
|
||||
expect(vp).not.toBeNull();
|
||||
const sCenterX = sBox.x + sBox.width / 2;
|
||||
const sCenterY = sBox.y + sBox.height / 2;
|
||||
expect(Math.abs(sCenterX - vp.width / 2)).toBeLessThan(vp.width * 0.25);
|
||||
expect(Math.abs(sCenterY - vp.height / 2)).toBeLessThan(vp.height * 0.25);
|
||||
await surface.locator(`[data-testid="em-row-quick-postpone-${id}"]`).click();
|
||||
// After clicking Postpone the quick-actions surface closes and the
|
||||
// row's selected-frame marker disappears.
|
||||
@@ -213,7 +230,44 @@ test("Escape closes the centered surface AND clears the selected-row frame", asy
|
||||
await expect(row).not.toHaveAttribute("data-quick-open", "true");
|
||||
});
|
||||
|
||||
test("popover only exposes Postpone — Move up/down buttons retired in #489", async ({
|
||||
test("RTL (Arabic): quick-actions surface centers and frames row with Arabic label", async ({
|
||||
page,
|
||||
}) => {
|
||||
// Force Arabic locale before navigation so the surface renders with
|
||||
// RTL direction and the Arabic Postpone label.
|
||||
await page.addInitScript(() => {
|
||||
try {
|
||||
window.localStorage.setItem("tx-lang", "ar");
|
||||
} catch {
|
||||
/* ignore */
|
||||
}
|
||||
});
|
||||
const date = pickFutureDate(36);
|
||||
const id = await insertMeeting({
|
||||
date,
|
||||
titleEn: "QA RTL Surface",
|
||||
startTime: "09:00:00",
|
||||
endTime: "09:30:00",
|
||||
});
|
||||
await loginViaUi(page, "admin", "admin123");
|
||||
await gotoTestDate(page, date);
|
||||
const row = page.locator(`[data-testid="em-row-${id}"]`);
|
||||
await expect(row).toBeVisible({ timeout: 10_000 });
|
||||
await row.click();
|
||||
const surface = page.locator(`[data-testid="em-row-quick-${id}"]`);
|
||||
await expect(surface).toBeVisible();
|
||||
// Surface mounts in RTL.
|
||||
await expect(surface).toHaveAttribute("dir", "rtl");
|
||||
// Row carries the selected-frame marker.
|
||||
await expect(row).toHaveAttribute("data-quick-open", "true");
|
||||
// Postpone button shows the Arabic label.
|
||||
const postponeBtn = surface.locator(
|
||||
`[data-testid="em-row-quick-postpone-${id}"]`,
|
||||
);
|
||||
await expect(postponeBtn).toContainText("تأجيل");
|
||||
});
|
||||
|
||||
test("quick-actions surface only exposes Postpone — Move up/down buttons retired in #489", async ({
|
||||
page,
|
||||
}) => {
|
||||
// Two rows so neighbours exist either way — confirms the popover
|
||||
@@ -261,7 +315,7 @@ test("popover only exposes Postpone — Move up/down buttons retired in #489", a
|
||||
).toHaveCount(0);
|
||||
});
|
||||
|
||||
test("clicking row affordances (time cell, row-actions) does NOT open the popover", async ({
|
||||
test("clicking row affordances (time cell, row-actions) does NOT open the quick-actions surface", async ({
|
||||
page,
|
||||
}) => {
|
||||
const id = await insertMeeting({
|
||||
@@ -305,7 +359,7 @@ test("clicking row affordances (time cell, row-actions) does NOT open the popove
|
||||
await expect(popover).toBeVisible({ timeout: 5_000 });
|
||||
});
|
||||
|
||||
test("non-mutator (executive_viewer) row click does NOT open quick-actions popover", async ({
|
||||
test("non-mutator (executive_viewer) row click does NOT open quick-actions surface", async ({
|
||||
page,
|
||||
}) => {
|
||||
const id = await insertMeeting({
|
||||
|
||||
Reference in New Issue
Block a user