#479: Render upcoming-alert attendees as numbered tables per group
- DetailsPanel in upcoming-meeting-alert.tsx: replaced the per-group
`<ul class="list-disc">` with a real `<table class="w-full">`. Each
attendee row is `<tr>` with two `<td>`s: a narrow tabular-nums index
cell (`{idx + 1}.`, scope="row") and the cleaned name cell. The
group heading is rendered as `<caption>` (text-start, font-bold,
text-xs) so it spans both columns and is announced as the table
caption to AT. Each table also carries `aria-label={group.heading}`.
- Numbering restarts at 1 per group (each group is its own table); a
code comment notes the one-line tweak to use a continuous count.
- Preserved: `data-testid="alert-details-attendees"` wrapper,
`max-h-40 overflow-y-auto` scroll, empty-state, location/URL rows,
and the section header total ("الحضور (n)"). RTL/LTR work via
`text-start` and `pe-1`.
- e2e: added a new test in
`artifacts/tx-os/tests/executive-meetings-upcoming-alert.spec.mjs`
that seeds a meeting with two subheading-delimited groups (3
external + 1 internal), expands details, and asserts two tables
render with rows starting at "1." in each group.
Verification:
- `pnpm exec tsc --noEmit` clean.
- New attendees-table spec passes (1/1, 16.5s).
No server, schema, or i18n key changes; Tailwind utilities only.
This commit is contained in:
@@ -1228,3 +1228,61 @@ test("Cascade prompt UI: no followers => prompt skipped, direct submit with casc
|
||||
await page.unroute(/\/api\/executive-meetings\/\d+\/postpone-minutes$/);
|
||||
});
|
||||
|
||||
|
||||
test("Upcoming-meeting alert: details panel renders attendees as numbered tables per group", async ({
|
||||
page,
|
||||
}) => {
|
||||
// #479: each attendee group inside the expanded details panel is a
|
||||
// real <table> with a 1-based index column and the attendee name —
|
||||
// verifies the bulleted list was replaced and that index restarts
|
||||
// per group.
|
||||
await setLang(page, "en");
|
||||
const m = await insertImminentMeeting({
|
||||
titleAr: `${TEST_TAG} attlist AR`,
|
||||
titleEn: `${TEST_TAG} attlist EN`,
|
||||
deltaMins: 3,
|
||||
});
|
||||
// Two distinct groups so we can also assert numbering restarts at 1
|
||||
// for the second group.
|
||||
// Groups in the alert are delimited by `kind='subheading'` rows, not
|
||||
// by attendance_type — mirror the production data shape.
|
||||
const seedAttendees = [
|
||||
{ name: "External Group", attendanceType: "external", sortOrder: 1, kind: "subheading" },
|
||||
{ name: "External One", attendanceType: "external", sortOrder: 2, kind: "person" },
|
||||
{ name: "External Two", attendanceType: "external", sortOrder: 3, kind: "person" },
|
||||
{ name: "Internal Group", attendanceType: "internal", sortOrder: 4, kind: "subheading" },
|
||||
{ name: "Internal One", attendanceType: "internal", sortOrder: 5, kind: "person" },
|
||||
];
|
||||
for (const a of seedAttendees) {
|
||||
await pool.query(
|
||||
`INSERT INTO executive_meeting_attendees
|
||||
(meeting_id, name, attendance_type, sort_order, kind)
|
||||
VALUES ($1, $2, $3, $4, $5)`,
|
||||
[m.id, a.name, a.attendanceType, a.sortOrder, a.kind],
|
||||
);
|
||||
}
|
||||
|
||||
await loginViaUi(page, "admin", "admin123");
|
||||
await page.goto("/");
|
||||
await expect(page.getByTestId("upcoming-meeting-alert")).toBeVisible({
|
||||
timeout: 15_000,
|
||||
});
|
||||
await page.getByTestId("alert-details-toggle").click();
|
||||
const wrapper = page.getByTestId("alert-details-attendees");
|
||||
await expect(wrapper).toBeVisible();
|
||||
|
||||
const tables = wrapper.locator("table");
|
||||
await expect(tables).toHaveCount(2);
|
||||
|
||||
// First group: row 1 shows "1." next to the first external attendee.
|
||||
const firstGroupRows = tables.nth(0).locator("tbody tr");
|
||||
await expect(firstGroupRows.first()).toContainText("1.");
|
||||
await expect(firstGroupRows.first()).toContainText("External One");
|
||||
await expect(firstGroupRows.nth(1)).toContainText("2.");
|
||||
await expect(firstGroupRows.nth(1)).toContainText("External Two");
|
||||
|
||||
// Second group: numbering restarts at 1.
|
||||
const secondGroupRows = tables.nth(1).locator("tbody tr");
|
||||
await expect(secondGroupRows.first()).toContainText("1.");
|
||||
await expect(secondGroupRows.first()).toContainText("Internal One");
|
||||
});
|
||||
|
||||
Reference in New Issue
Block a user