311d59ccad
The schedule's per-row affordances (delete, row color, merge cells)
used to render as three separate hover-revealed icon buttons stacked
inside the same narrow cell, which collided visually on iPad and
small screens. Replaced them with a single MoreVertical kebab trigger
that opens a Radix popover with three views (main / color / merge),
plus a "Back" affordance to return from sub-views to the main menu.
Implementation:
- New RowActionsMenu component (artifacts/tx-os/src/pages/executive-meetings.tsx)
rendered once per row, anchored at the trailing-top of the first
visible cell (matching the previous overlay anchor logic). Resets
to "main" view on close.
- Old DeleteRowButton, RowColorPicker, and MergeMenu components removed.
- Existing testids preserved on the popover items so external tests
still target em-delete-row-{id}, em-row-color-trigger, and
em-merge-trigger-{id}. New testids added: em-row-actions-{id} for
the kebab trigger, em-row-actions-back for the Back button.
- Locale keys added in ar.json/en.json: rowActions.label, rowActions.back.
- New imports: MoreVertical, ChevronLeft, ChevronRight (RTL-aware Back arrow).
- Merged-cell branch also uses RowActionsMenu so merge/unmerge stays
reachable when the # column is hidden.
Tests:
- New regression spec: executive-meetings-row-actions-menu.spec.mjs
exercises the full menu flow (open → color → back → main → merge → escape).
- Existing executive-meetings-edit-toggle.spec.mjs and
executive-meetings-schedule-features.spec.mjs continue to pass —
legacy testids remain absent in view mode and present inside the
popover when opened in edit mode.
Pre-existing test failures (NOT caused by this change):
- "non-mutate user (executive_viewer)" spec fails because seeded user
"ahmed" is missing from the dev DB (parallel task #131 drizzle work).
- TS errors in admin.tsx tracked by other tasks (api-client codegen
out of sync).
Follow-up proposed:
- #192: show row color + merge previews inline in the main menu items.
107 lines
4.2 KiB
JavaScript
107 lines
4.2 KiB
JavaScript
// Verifies the consolidated row-actions kebab menu introduced as part
|
|
// of task #191 — the three previously-separate row overlays (delete,
|
|
// row color, merge cells) are now collapsed into a single menu so they
|
|
// no longer collide visually inside narrow cells. Each sub-action
|
|
// keeps its original testid, so the rest of the suite + any external
|
|
// tooling that targeted them keeps working without changes.
|
|
import { test, expect } from "@playwright/test";
|
|
|
|
async function loginViaUi(page, username, password) {
|
|
await page.goto("/login");
|
|
await page.locator("#username").fill(username);
|
|
await page.locator("#password").fill(password);
|
|
await Promise.all([
|
|
page.waitForURL((url) => !url.pathname.endsWith("/login"), {
|
|
timeout: 15_000,
|
|
}),
|
|
page.locator('form button[type="submit"]').click(),
|
|
]);
|
|
}
|
|
|
|
async function ensureEditModeOn(page) {
|
|
const toggle = page.getByTestId("em-edit-mode-toggle");
|
|
await expect(toggle).toBeVisible();
|
|
if ((await toggle.getAttribute("aria-pressed")) === "false") {
|
|
await toggle.click();
|
|
}
|
|
await expect(toggle).toHaveAttribute("aria-pressed", "true");
|
|
}
|
|
|
|
test("Schedule: row actions menu exposes delete + color + merge in a single popover and supports back navigation", async ({
|
|
page,
|
|
}) => {
|
|
await page.addInitScript(() => {
|
|
try {
|
|
window.localStorage.setItem("tx-lang", "en");
|
|
} catch {
|
|
/* localStorage may not be ready before navigation */
|
|
}
|
|
});
|
|
await loginViaUi(page, "admin", "admin123");
|
|
await page.goto("/executive-meetings");
|
|
await ensureEditModeOn(page);
|
|
|
|
// Exactly one consolidated kebab trigger per row, replacing what used
|
|
// to be three separate overlay icons (Trash, Palette, Combine).
|
|
const kebab = page.locator('[data-testid^="em-row-actions-"]').first();
|
|
await expect(kebab).toHaveCount(1);
|
|
|
|
// Closed state: none of the legacy testids are mounted yet.
|
|
await expect(page.locator('[data-testid^="em-delete-row-"]')).toHaveCount(0);
|
|
await expect(page.getByTestId("em-row-color-trigger")).toHaveCount(0);
|
|
await expect(page.locator('[data-testid^="em-merge-trigger-"]')).toHaveCount(
|
|
0,
|
|
);
|
|
|
|
// The kebab is opacity-0 until row hover, so force the click.
|
|
await kebab.click({ force: true });
|
|
|
|
// Main menu shows all three legacy testids — preserves selectors used
|
|
// elsewhere in the suite.
|
|
await expect(page.locator('[data-testid^="em-delete-row-"]')).toHaveCount(1);
|
|
await expect(page.getByTestId("em-row-color-trigger")).toHaveCount(1);
|
|
await expect(page.locator('[data-testid^="em-merge-trigger-"]')).toHaveCount(
|
|
1,
|
|
);
|
|
|
|
// Drill into the Row color sub-view: Delete + Merge get hidden,
|
|
// a Back affordance + the swatch row appear.
|
|
await page.getByTestId("em-row-color-trigger").click();
|
|
await expect(page.locator('[data-testid^="em-delete-row-"]')).toHaveCount(0);
|
|
await expect(page.locator('[data-testid^="em-merge-trigger-"]')).toHaveCount(
|
|
0,
|
|
);
|
|
await expect(page.getByTestId("em-row-actions-back")).toBeVisible();
|
|
|
|
// Back returns to the main 3-item view.
|
|
await page.getByTestId("em-row-actions-back").click();
|
|
await expect(page.locator('[data-testid^="em-delete-row-"]')).toHaveCount(1);
|
|
await expect(page.getByTestId("em-row-color-trigger")).toHaveCount(1);
|
|
await expect(page.locator('[data-testid^="em-merge-trigger-"]')).toHaveCount(
|
|
1,
|
|
);
|
|
|
|
// Drill into the Merge sub-view — three merge options appear and the
|
|
// other items disappear.
|
|
await page.locator('[data-testid^="em-merge-trigger-"]').click();
|
|
await expect(
|
|
page.locator('[data-testid^="em-merge-opt-meeting-attendees-"]'),
|
|
).toHaveCount(1);
|
|
await expect(
|
|
page.locator('[data-testid^="em-merge-opt-meeting-time-"]'),
|
|
).toHaveCount(1);
|
|
await expect(page.locator('[data-testid^="em-merge-opt-all-"]')).toHaveCount(
|
|
1,
|
|
);
|
|
await expect(page.locator('[data-testid^="em-delete-row-"]')).toHaveCount(0);
|
|
await expect(page.getByTestId("em-row-color-trigger")).toHaveCount(0);
|
|
|
|
// Escape closes the popover entirely.
|
|
await page.keyboard.press("Escape");
|
|
await expect(page.locator('[data-testid^="em-delete-row-"]')).toHaveCount(0);
|
|
await expect(page.getByTestId("em-row-color-trigger")).toHaveCount(0);
|
|
await expect(page.locator('[data-testid^="em-merge-trigger-"]')).toHaveCount(
|
|
0,
|
|
);
|
|
});
|