test(e2e): expand coverage and harden cross-file isolation
New specs (4):
- accept-invite: pre-registrant flow happy path + cadence/preset UX
- admin-pre-registrants: list, filter, action gating, redirect
- admin-series: list, create, edit (delete skipped — button no-ops)
- admin-site-content: list whitelist, edit + roundtrip on /
Extended specs (6):
- join-flow: cadence ×12 math, guidance label, paid-tier success
- events: series-pass-required, member-savings gating
- admin-events: full CRUD via /admin/events/create?edit=<id>
- admin-members: add-member submit, status select, detail nav
- a11y: add /accept-invite, /member/account, /board, /admin/pre-registrants
- wave-slack-onboarding: 9 of 16 scaffold tests now passing
Cross-file isolation hardening:
- admin-events CRUD: refresh auth cookie (auth.spec.js logout test
bumps tokenVersion on the shared admin), wait for hydration
before form fill, search by unique title to dodge pagination.
- board: switch memberPage from shared admin to dedicated seeded
member to avoid the same tokenVersion race.
- wave-slack §6.4: create dedicated test member, filter by email
before clicking, removing the "first row" anchor.
Also fixed board heading drift ("Board" → "Bulletin Board").
This commit is contained in:
parent
03dfdab20e
commit
8dd55ccc09
11 changed files with 1077 additions and 89 deletions
|
|
@ -66,4 +66,68 @@ test.describe("Admin members page", () => {
|
|||
adminPage.getByPlaceholder("email@example.com"),
|
||||
).toBeVisible();
|
||||
});
|
||||
|
||||
test("create member, status select reflects STATUS_LABELS, change persists, detail page renders", async ({ adminPage }) => {
|
||||
const stamp = Date.now();
|
||||
const memberName = `E2E Member ${stamp}`;
|
||||
const memberEmail = `e2e-member-${stamp}@example.test`;
|
||||
|
||||
await adminPage.goto("/admin/members");
|
||||
await adminPage.waitForLoadState("networkidle");
|
||||
await expect(adminPage.locator("h1")).toHaveText("Members");
|
||||
|
||||
await adminPage.getByRole("button", { name: "Add Member" }).click();
|
||||
await adminPage.getByPlaceholder("Full name").fill(memberName);
|
||||
await adminPage.getByPlaceholder("email@example.com").fill(memberEmail);
|
||||
await adminPage.getByRole("button", { name: "Create Member" }).click();
|
||||
|
||||
// Verify the new member shows up via search
|
||||
const searchInput = adminPage.getByPlaceholder("Search members...");
|
||||
await expect(searchInput).toBeVisible({ timeout: 10000 });
|
||||
await searchInput.fill(memberEmail);
|
||||
|
||||
const memberRow = adminPage.locator("tr", { hasText: memberEmail });
|
||||
await expect(memberRow).toBeVisible({ timeout: 10000 });
|
||||
await expect(memberRow.getByText(memberName)).toBeVisible();
|
||||
|
||||
// Open the edit modal for this member, where the STATUS_LABELS-driven <select> lives
|
||||
await memberRow.getByRole("button", { name: "Edit" }).click();
|
||||
|
||||
const statusSelect = adminPage.locator(".modal select").filter({ hasText: "Active" });
|
||||
await expect(statusSelect).toBeVisible({ timeout: 10000 });
|
||||
|
||||
// STATUS_LABELS keys (values) and the rendered labels
|
||||
const expectedOptions = [
|
||||
{ value: "active", label: "Active" },
|
||||
{ value: "pending_payment", label: "Payment setup incomplete" },
|
||||
{ value: "suspended", label: "Paused" },
|
||||
{ value: "cancelled", label: "Closed" },
|
||||
];
|
||||
for (const { value, label } of expectedOptions) {
|
||||
const opt = statusSelect.locator(`option[value="${value}"]`);
|
||||
await expect(opt).toHaveCount(1);
|
||||
await expect(opt).toHaveText(label);
|
||||
}
|
||||
|
||||
// Change status to suspended and save
|
||||
await statusSelect.selectOption("suspended");
|
||||
await adminPage.getByRole("button", { name: "Save Changes" }).click();
|
||||
|
||||
// Modal closes; verify the row badge reflects the new status
|
||||
await expect(adminPage.locator(".modal")).toHaveCount(0, { timeout: 10000 });
|
||||
await expect(memberRow.getByText("Paused")).toBeVisible({ timeout: 10000 });
|
||||
|
||||
// Reload to confirm persistence
|
||||
await adminPage.reload();
|
||||
await adminPage.waitForLoadState("networkidle");
|
||||
await adminPage.getByPlaceholder("Search members...").fill(memberEmail);
|
||||
const reloadedRow = adminPage.locator("tr", { hasText: memberEmail });
|
||||
await expect(reloadedRow.getByText("Paused")).toBeVisible({ timeout: 10000 });
|
||||
|
||||
// Click the member name (link to detail page) and verify URL + heading
|
||||
await reloadedRow.getByRole("link", { name: memberName }).click();
|
||||
await adminPage.waitForURL(/\/admin\/members\/[a-f0-9]{24}$/, { timeout: 10000 });
|
||||
await expect(adminPage.locator("h1")).toHaveText(memberName);
|
||||
await expect(adminPage.locator(".member-email")).toHaveText(memberEmail);
|
||||
});
|
||||
});
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue