Files
buzz/desktop/tests/e2e/reminders.spec.ts
T
cls 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
feat: import Chinese-localized Buzz source snapshot
Signed-off-by: cls_宁波本机 <908705107@qq.com>
2026-08-13 18:34:25 +08:00

363 lines
12 KiB
TypeScript

import { expect, test } from "@playwright/test";
import { waitForAnimations } from "../helpers/animations";
import { installMockBridge } from "../helpers/bridge";
const MOCK_PUBKEY = "deadbeef".repeat(8);
// The inbox filter dropdown lives in the home pane, not the chat view. Land on
// home and wait for the inbox before reaching for the filter trigger.
async function gotoInboxHome(page: import("@playwright/test").Page) {
await page.goto("/");
await expect(page.getByTestId("home-inbox")).toBeVisible();
}
// Reminders is reached by opening the inbox filter dropdown and selecting the
// "Reminders" option — there is no standalone nav entry or view-mode slider.
async function openRemindersFilter(page: import("@playwright/test").Page) {
await page.getByTestId("inbox-filter-trigger").click();
await page.getByRole("menuitemradio", { name: "Reminders" }).click();
}
// The reminders query mounts (for the badge) before tests seed events, so a
// bare seed lands behind its cached empty result. Invalidate after seeding to
// force the refetch that picks up the mock events.
async function seedReminders(
page: import("@playwright/test").Page,
events: unknown[],
) {
await page.evaluate((seeded) => {
window.__BUZZ_E2E_SEED_MOCK_REMINDERS__?.(
seeded as Parameters<
NonNullable<typeof window.__BUZZ_E2E_SEED_MOCK_REMINDERS__>
>[0],
);
window.__BUZZ_E2E_QUERY_CLIENT__?.invalidateQueries({
queryKey: ["reminders"],
});
}, events);
}
function mockReminderEvent(opts: {
id: string;
dTag: string;
content: string;
notBefore: number;
createdAt?: number;
}) {
return {
id: opts.id,
pubkey: MOCK_PUBKEY,
created_at: opts.createdAt ?? Math.floor(Date.now() / 1000) - 300,
kind: 30300,
tags: [
["d", opts.dTag],
["not_before", String(opts.notBefore)],
],
content: opts.content,
sig: "mocksig".repeat(20).slice(0, 128),
};
}
test.describe("reminders", () => {
test.beforeEach(async ({ page }) => {
await installMockBridge(page);
});
test("01 — inbox filter dropdown shows Reminders option", async ({
page,
}) => {
await gotoInboxHome(page);
await page.getByTestId("inbox-filter-trigger").click();
const remindersOption = page.getByRole("menuitemradio", {
name: "Reminders",
});
await expect(remindersOption).toBeVisible();
await waitForAnimations(page);
});
test("02 — message action menu shows Remind me later", async ({ page }) => {
await page.goto("/");
await page.getByTestId("channel-general").click();
await expect(page.getByTestId("chat-title")).toHaveText("general");
const messageRow = page.getByTestId("message-row").first();
await messageRow.hover();
const moreActionsButton = messageRow.getByRole("button", {
name: "More actions",
});
await expect(moreActionsButton).toBeVisible();
await moreActionsButton.click();
const remindItem = page.getByRole("menuitem", {
name: "Remind me later",
});
await expect(remindItem).toBeVisible();
await waitForAnimations(page);
});
test("03 — Remind me later dialog with time presets", async ({ page }) => {
await page.goto("/");
await page.getByTestId("channel-general").click();
await expect(page.getByTestId("chat-title")).toHaveText("general");
const messageRow = page.getByTestId("message-row").first();
await messageRow.hover();
const moreActionsButton = messageRow.getByRole("button", {
name: "More actions",
});
await moreActionsButton.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 expect(dialog.getByText("Remind me later")).toBeVisible();
await expect(dialog.getByText("In 30 minutes")).toBeVisible();
await expect(dialog.getByText("Custom date & time")).toBeVisible();
await waitForAnimations(page);
});
test("04 — Reminders panel empty state", async ({ page }) => {
await gotoInboxHome(page);
await openRemindersFilter(page);
await expect(page.getByText("No reminders")).toBeVisible();
await waitForAnimations(page);
});
test("05 — Reminders panel with active pending reminder", async ({
page,
}) => {
await gotoInboxHome(page);
// Seed a pending reminder due in the future
const futureTimestamp = Math.floor(Date.now() / 1000) + 3600;
const reminderContent = JSON.stringify({
target: {
eventId: "mock-general-welcome",
channelId: "9a1657ac-f7aa-5db0-b632-d8bbeb6dfb50",
preview: "Welcome to #general",
authorPubkey: MOCK_PUBKEY,
},
note: "Follow up on this message",
status: "pending",
});
await seedReminders(page, [
mockReminderEvent({
id: "reminder-active-01",
dTag: "rem-active-01",
content: reminderContent,
notBefore: futureTimestamp,
}),
]);
await openRemindersFilter(page);
await expect(
page
.getByTestId("home-reminder-item-rem-active-01")
.getByText("Follow up on this message"),
).toBeVisible();
await waitForAnimations(page);
});
test("06 — Reminders panel with fired/overdue reminder", async ({ page }) => {
await gotoInboxHome(page);
// Seed a reminder that has already fired (notBefore in the past)
const pastTimestamp = Math.floor(Date.now() / 1000) - 7200;
const overdueContent = JSON.stringify({
target: {
eventId: "mock-general-alice",
channelId: "9a1657ac-f7aa-5db0-b632-d8bbeb6dfb50",
preview: "Hey team — checking in.",
authorPubkey:
"953d3363262e86b770419834c53d2446409db6d918a57f8f339d495d54ab001f",
},
note: "Reply to Alice",
status: "pending",
});
// Also seed a future reminder so both states are visible
const futureTimestamp = Math.floor(Date.now() / 1000) + 7200;
const activeContent = JSON.stringify({
target: {
eventId: "mock-general-welcome",
channelId: "9a1657ac-f7aa-5db0-b632-d8bbeb6dfb50",
preview: "Welcome to #general",
authorPubkey: MOCK_PUBKEY,
},
status: "pending",
});
await seedReminders(page, [
mockReminderEvent({
id: "reminder-overdue-01",
dTag: "rem-overdue-01",
content: overdueContent,
notBefore: pastTimestamp,
}),
mockReminderEvent({
id: "reminder-upcoming-01",
dTag: "rem-upcoming-01",
content: activeContent,
notBefore: futureTimestamp,
}),
]);
await openRemindersFilter(page);
await expect(
page
.getByTestId("home-reminder-item-rem-overdue-01")
.getByText("Reply to Alice"),
).toBeVisible();
await expect(
page
.getByTestId("home-reminder-item-rem-overdue-01")
.getByText("2h overdue"),
).toBeVisible();
await waitForAnimations(page);
});
});
// Phase 2 — author + source at a glance, and click-to-navigate. Both cases
// seed a reminder targeting Alice's seeded message in #general (event
// `mock-general-alice`, channel `9a1657ac-…`), so the author resolves to
// "alice" and the channel label to "general" from the live profile/channel
// queries — no "Unknown channel" fallback.
const GENERAL_CHANNEL_ID = "9a1657ac-f7aa-5db0-b632-d8bbeb6dfb50";
const ALICE_PUBKEY =
"953d3363262e86b770419834c53d2446409db6d918a57f8f339d495d54ab001f";
function aliceReminderContent() {
return JSON.stringify({
target: {
eventId: "mock-general-alice",
channelId: GENERAL_CHANNEL_ID,
preview: "Hey team — checking in.",
authorPubkey: ALICE_PUBKEY,
},
note: "Reply to Alice",
status: "pending",
});
}
// Nav badge — the surface unit tests can't prove: a due reminder driving the
// Inbox nav item's `(1)` count. The count is gated behind `homeBadgeEnabled`,
// so seed that setting on before installMockBridge (addInitScript runs at
// document start, ahead of the app reading localStorage).
const NOTIFICATION_SETTINGS_KEY = `buzz-notification-settings.v2:${MOCK_PUBKEY}`;
test.describe("reminders nav badge", () => {
test.beforeEach(async ({ page }) => {
await page.addInitScript(
({ key }) => {
localStorage.setItem(key, JSON.stringify({ homeBadgeEnabled: true }));
},
{ key: NOTIFICATION_SETTINGS_KEY },
);
await installMockBridge(page);
});
test("09 — Inbox nav badge counts a due reminder", async ({ page }) => {
await gotoInboxHome(page);
// One overdue pending reminder (notBefore in the past) is the sole badge
// contributor; the feed baseline is 0, so an exact "1" proves the reminder
// — not feed noise — drives the nav count.
const pastTimestamp = Math.floor(Date.now() / 1000) - 7200;
await seedReminders(page, [
mockReminderEvent({
id: "reminder-navbadge-01",
dTag: "rem-navbadge-01",
content: aliceReminderContent(),
notBefore: pastTimestamp,
}),
]);
await expect(page.getByTestId("sidebar-home-count")).toHaveText("1");
await waitForAnimations(page);
});
});
test.describe("reminders phase 2 — author, source, navigation", () => {
test.beforeEach(async ({ page }) => {
await installMockBridge(page);
});
test("07 — reminder row shows author and source channel", async ({
page,
}) => {
await gotoInboxHome(page);
const futureTimestamp = Math.floor(Date.now() / 1000) + 3600;
await seedReminders(page, [
mockReminderEvent({
id: "reminder-phase2-source-01",
dTag: "rem-phase2-source-01",
content: aliceReminderContent(),
notBefore: futureTimestamp,
}),
]);
await openRemindersFilter(page);
// Author + source line resolves from the live profile/channel queries.
// Scope to the reminders panel to avoid matching the "general" entry in
// the sidebar channel list.
const remindersPanel = page.getByTestId("home-inbox-reminders");
await expect(
remindersPanel.getByText("alice", { exact: true }),
).toBeVisible();
await expect(
remindersPanel.getByText("#general", { exact: true }),
).toBeVisible();
await expect(remindersPanel.getByText("Reply to Alice")).toBeVisible();
await waitForAnimations(page);
});
test("08 — clicking a reminder navigates to the message in context", async ({
page,
}) => {
await gotoInboxHome(page);
const futureTimestamp = Math.floor(Date.now() / 1000) + 3600;
await seedReminders(page, [
mockReminderEvent({
id: "reminder-phase2-nav-01",
dTag: "rem-phase2-nav-01",
content: aliceReminderContent(),
notBefore: futureTimestamp,
}),
]);
await openRemindersFilter(page);
// Selecting a reminder keeps the list visible and opens its detail pane.
// Navigation is an explicit action from that detail.
await page
.getByTestId("home-reminder-item-rem-phase2-nav-01")
.getByRole("button")
.first()
.click();
await page
.getByTestId("home-reminder-detail")
.getByRole("button", { name: "Open message" })
.click();
// Lands in the #general chat view with the target message in context.
await expect(page.getByTestId("chat-title")).toHaveText("general");
await expect(
page.getByTestId("message-timeline").getByText("Hey team — checking in."),
).toBeVisible();
await waitForAnimations(page);
});
});