9dfa06ffee
Docker image / Build (linux/amd64) (push) Has been cancelled
Docker image / Build (linux/arm64) (push) Has been cancelled
Docker image / Merge release multi-arch manifest (push) Has been cancelled
Docker image / Merge debug multi-arch manifest (push) Has been cancelled
Docker image / Build public push gateway (linux/amd64) (push) Has been cancelled
Docker image / Build public push gateway (linux/arm64) (push) Has been cancelled
Docker image / Publish public push gateway image (push) Has been cancelled
Sprig image / Build (linux/amd64) (push) Has been cancelled
Sprig image / Build (linux/arm64) (push) Has been cancelled
Sprig image / Merge multi-arch manifest (push) Has been cancelled
Harbor Buzz Orchestra / Python tests and lint (push) Has been cancelled
CI / Detect Changed Paths (push) Has been cancelled
CI / Rust Lint (push) Has been cancelled
CI / Unit Tests (push) Has been cancelled
CI / Desktop Core (push) Has been cancelled
CI / Desktop Smoke E2E (1) (push) Has been cancelled
CI / Desktop Smoke E2E (2) (push) Has been cancelled
CI / Desktop Smoke E2E (3) (push) Has been cancelled
CI / Desktop Smoke E2E (4) (push) Has been cancelled
CI / Desktop (push) Has been cancelled
CI / Desktop E2E Relay (push) Has been cancelled
CI / Desktop E2E Integration (1/2) (push) Has been cancelled
CI / Desktop E2E Integration (2/2) (push) Has been cancelled
CI / Desktop E2E Integration (push) Has been cancelled
CI / Backend Integration (relay e2e) (push) Has been cancelled
CI / Relay E2E (push) Has been cancelled
CI / Web (push) Has been cancelled
CI / Mobile (push) Has been cancelled
CI / Security (push) Has been cancelled
CI / Dead Token Reference Guard (push) Has been cancelled
CI / Server Cross-Compile (aarch64-unknown-linux-musl) (push) Has been cancelled
CI / Server Cross-Compile (x86_64-unknown-linux-musl) (push) Has been cancelled
CI / Windows Rust (x86_64-pc-windows-msvc) (push) Has been cancelled
CI / Desktop Build (macOS) (push) Has been cancelled
helm chart / lint + unittest + render matrix (push) Has been cancelled
helm chart / install on kind (gated) (push) Has been cancelled
helm chart / publish chart to GHCR (push) Has been cancelled
Mesh Lifecycle / Relay-Driven Mesh Lifecycle Smoke (push) Has been cancelled
Sprig / Build (aarch64-unknown-linux-musl) (push) Has been cancelled
Sprig / Build (x86_64-unknown-linux-musl) (push) Has been cancelled
Sprig / Publish rolling release (push) Has been cancelled
Sprig / Publish tagged release (push) Has been cancelled
Signed-off-by: cls_宁波本机 <908705107@qq.com>
145 lines
5.7 KiB
TypeScript
145 lines
5.7 KiB
TypeScript
import { expect, test } from "@playwright/test";
|
|
|
|
import { waitForAnimations } from "../helpers/animations";
|
|
import { installMockBridge } from "../helpers/bridge";
|
|
|
|
/**
|
|
* Regression suite for the "app freezes after setting a reminder" bug.
|
|
*
|
|
* The app used to bundle two copies of @radix-ui/react-dismissable-layer
|
|
* (react-menu resolved 1.1.11 while react-dialog resolved 1.1.15). Each copy
|
|
* keeps its own module-level `originalBodyPointerEvents`, so when a *modal*
|
|
* menu opened a *modal* dialog in the same React commit, the dialog's copy
|
|
* saved the menu's `pointer-events: none` as the "original" body style and
|
|
* restored it when the dialog closed — leaving `pointer-events: none` stuck
|
|
* on <body> and deadening the whole app to the mouse.
|
|
*
|
|
* The inbox row context menu hit this: its "Remind me later" item opens
|
|
* RemindMeLaterDialog synchronously. Fixed by deduplicating the layer via
|
|
* the pnpm override in pnpm-workspace.yaml — a single copy coordinates
|
|
* nested modal layers correctly, so the menu keeps its default modal
|
|
* behavior.
|
|
*
|
|
* Each test drives one reminder entry point through a full set-reminder
|
|
* cycle, then asserts <body> pointer-events is not stuck and that a real
|
|
* click still lands. The inbox right-click test fails if the dismissable
|
|
* layer ever duplicates again; the message-menu tests pin the already-safe
|
|
* paths.
|
|
*/
|
|
|
|
const GENERAL_MESSAGE_ROW = "message-row";
|
|
// The default mock feed's mention row (from alice, in #general) has a
|
|
// channel target, so its "Remind me later" context-menu item is enabled.
|
|
const INBOX_MENTION_ROW = "home-inbox-item-mock-feed-mention";
|
|
|
|
/**
|
|
* Assert the app survived the menu → dialog → close cycle.
|
|
*
|
|
* The stuck state is inline `pointer-events: none` left on <body>. The buggy
|
|
* restore runs when the dialog's dismissable layer unmounts — after its exit
|
|
* animation — so first wait for the dialog to leave the DOM, then check the
|
|
* body style, then prove clicks still land by navigating via the sidebar.
|
|
*/
|
|
async function expectAppAliveAfterDialogClose(
|
|
page: import("@playwright/test").Page,
|
|
) {
|
|
await expect(page.getByRole("dialog")).toHaveCount(0);
|
|
await waitForAnimations(page);
|
|
expect(await page.evaluate(() => document.body.style.pointerEvents)).not.toBe(
|
|
"none",
|
|
);
|
|
await page.getByTestId("channel-random").click();
|
|
await expect(page.getByTestId("chat-title")).toHaveText("random");
|
|
}
|
|
|
|
/** Open the Remind-me-later dialog from a message row's More-actions menu. */
|
|
async function openReminderDialogFromMessageMenu(
|
|
page: import("@playwright/test").Page,
|
|
) {
|
|
await page.goto("/");
|
|
await page.getByTestId("channel-general").click();
|
|
await expect(page.getByTestId("chat-title")).toHaveText("general");
|
|
|
|
const messageRow = page.getByTestId(GENERAL_MESSAGE_ROW).first();
|
|
await messageRow.hover();
|
|
await messageRow.getByRole("button", { name: "More actions" }).click();
|
|
|
|
const remindItem = page.getByRole("menuitem", { name: "Remind me later" });
|
|
await expect(remindItem).toBeVisible();
|
|
await waitForAnimations(page);
|
|
await remindItem.click();
|
|
|
|
const dialog = page.getByRole("dialog");
|
|
await expect(dialog).toBeVisible();
|
|
await waitForAnimations(page);
|
|
return dialog;
|
|
}
|
|
|
|
async function expectReminderSetToast(page: import("@playwright/test").Page) {
|
|
await expect(
|
|
page.locator("[data-sonner-toast]").filter({ hasText: "Reminder set" }),
|
|
).toBeVisible();
|
|
}
|
|
|
|
test.describe("reminder set → app stays clickable", () => {
|
|
test.beforeEach(async ({ page }) => {
|
|
await installMockBridge(page);
|
|
});
|
|
|
|
test("01 — message menu → time preset", async ({ page }) => {
|
|
const dialog = await openReminderDialogFromMessageMenu(page);
|
|
|
|
await dialog.getByRole("button", { name: "In 30 minutes" }).click();
|
|
|
|
await expectReminderSetToast(page);
|
|
await expectAppAliveAfterDialogClose(page);
|
|
});
|
|
|
|
test("02 — message menu → custom time via Set reminder footer button", async ({
|
|
page,
|
|
}) => {
|
|
const dialog = await openReminderDialogFromMessageMenu(page);
|
|
|
|
// The custom timestamp must be strictly in the future or the Set
|
|
// reminder button stays disabled — pick tomorrow at 09:00 local time.
|
|
const tomorrow = new Date(Date.now() + 24 * 60 * 60 * 1000);
|
|
const date = [
|
|
tomorrow.getFullYear(),
|
|
String(tomorrow.getMonth() + 1).padStart(2, "0"),
|
|
String(tomorrow.getDate()).padStart(2, "0"),
|
|
].join("-");
|
|
await dialog.getByLabel("Reminder date").fill(date);
|
|
await dialog.getByLabel("Reminder time").fill("09:00");
|
|
await dialog.getByRole("button", { name: "Set reminder" }).click();
|
|
|
|
await expectReminderSetToast(page);
|
|
await expectAppAliveAfterDialogClose(page);
|
|
});
|
|
|
|
// The regression path: only the inbox right-click stacks a modal
|
|
// ContextMenu under the modal dialog. Fails if two copies of the
|
|
// dismissable layer are ever bundled again — body pointer-events stays
|
|
// "none" after the dialog closes and the sidebar click below times out.
|
|
test("03 — inbox row right-click → Remind me later", async ({ page }) => {
|
|
await page.goto("/");
|
|
await expect(page.getByTestId("home-inbox")).toBeVisible();
|
|
|
|
const row = page.getByTestId(INBOX_MENTION_ROW);
|
|
await expect(row).toBeVisible();
|
|
await row.click({ button: "right" });
|
|
|
|
const remindItem = page.getByRole("menuitem", { name: "Remind me later" });
|
|
await expect(remindItem).toBeVisible();
|
|
await waitForAnimations(page);
|
|
await remindItem.click();
|
|
|
|
const dialog = page.getByRole("dialog");
|
|
await expect(dialog).toBeVisible();
|
|
await waitForAnimations(page);
|
|
await dialog.getByRole("button", { name: "In 30 minutes" }).click();
|
|
|
|
await expectReminderSetToast(page);
|
|
await expectAppAliveAfterDialogClose(page);
|
|
});
|
|
});
|