Files
buzz/desktop/tests/e2e/channel-sort.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

176 lines
6.4 KiB
TypeScript
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
import { expect, test } from "@playwright/test";
import type { Page } from "@playwright/test";
import { waitForAnimations } from "../helpers/animations";
import { installMockBridge } from "../helpers/bridge";
const SHOTS = "test-results/channel-sort";
// Mock-mode current-user pubkey and relay (see e2eBridge DEFAULT_MOCK_PUBKEY /
// DEFAULT_RELAY_WS_URL). Sort preferences persist under the relay-scoped key
// buzz-channel-sort.v1:<pubkey>:<encoded-relay>.
const MOCK_PUBKEY = "deadbeef".repeat(8);
const MOCK_RELAY_ENCODED = encodeURIComponent("ws://localhost:3000");
const SORT_STORAGE_KEY = `buzz-channel-sort.v1:${MOCK_PUBKEY}:${MOCK_RELAY_ENCODED}`;
function seedSortState(page: Page, groups: Record<string, string>) {
return page.addInitScript(
({ key, groups }) => {
window.localStorage.setItem(key, JSON.stringify({ version: 1, groups }));
},
{ key: SORT_STORAGE_KEY, groups },
);
}
async function openApp(page: Page) {
await page.goto("/");
await page.getByTestId("channel-general").click();
await expect(page.getByTestId("chat-title")).toHaveText("general");
}
function streamNames(page: Page) {
return page
.getByTestId("stream-list")
.locator("[data-testid^='channel-']")
.evaluateAll((nodes) =>
nodes
.map((n) => n.getAttribute("data-testid") ?? "")
.filter(
(id) =>
!id.startsWith("channel-unread") &&
!id.startsWith("channel-working") &&
!id.startsWith("channel-dm-count"),
)
.map((id) => id.replace(/^channel-/, "")),
);
}
test.describe("per-group channel sort", () => {
test("01 — Channels group defaults to AZ", async ({ page }) => {
await installMockBridge(page);
await openApp(page);
const names = await streamNames(page);
const sorted = [...names].sort((a, b) => a.localeCompare(b));
expect(names).toEqual(sorted);
});
test("02 — sort trigger switches Channels to Recent and persists", async ({
page,
}) => {
await installMockBridge(page);
await openApp(page);
// Hover the Channels header to reveal the action cluster, then open the
// sort dropdown and choose Recent.
const streamList = page.getByTestId("stream-list");
await expect(streamList).toBeVisible();
await page.getByText("Channels", { exact: true }).hover();
const trigger = page.getByTestId("section-actions-channels");
await expect(trigger).toBeVisible();
await waitForAnimations(page);
await page.screenshot({ path: `${SHOTS}/01-channels-sort-ingress.png` });
await trigger.click();
// Sort is now a submenu flyout — open it before the radio items render.
await page.getByRole("menuitem", { name: "Sort" }).click();
await expect(
page.getByRole("menuitemradio", { name: "Recent" }),
).toBeVisible();
await waitForAnimations(page);
await page.screenshot({ path: `${SHOTS}/02-channels-sort-open.png` });
await page.getByRole("menuitemradio", { name: "Recent" }).click();
// Mock recency: all-replies (far future) > deep-history (1m) > general
// (5m) > agents (15m) > sales (30m) > engineering (42m) > design (120m),
// then no-activity channels alphabetically. The list is virtualized, so
// only assert on the rendered prefix.
await expect
.poll(async () => (await streamNames(page)).slice(0, 3))
.toEqual(["all-replies", "deep-history", "general"]);
const names = await streamNames(page);
const recencyOrder = [
"all-replies",
"deep-history",
"general",
"agents",
"sales",
"engineering",
"design",
];
const rendered = recencyOrder.filter((n) => names.includes(n));
expect(names.slice(0, rendered.length)).toEqual(rendered);
await waitForAnimations(page);
await page.screenshot({ path: `${SHOTS}/03-channels-recent.png` });
// Persisted for this identity.
const stored = await page.evaluate((key) => {
return JSON.parse(window.localStorage.getItem(key) ?? "null");
}, SORT_STORAGE_KEY);
expect(stored).toEqual({ version: 1, groups: { channels: "recent" } });
// Survives reload.
await page.reload();
await expect(page.getByTestId("stream-list")).toBeVisible();
await expect
.poll(async () => (await streamNames(page)).slice(0, 2))
.toEqual(["all-replies", "deep-history"]);
});
test("03 — group preferences are independent (seeded Channels=recent leaves Forums AZ)", async ({
page,
}) => {
await seedSortState(page, { channels: "recent" });
await installMockBridge(page);
await openApp(page);
// Channels reflects the seeded Recent order…
await expect
.poll(async () => (await streamNames(page)).slice(0, 2))
.toEqual(["all-replies", "deep-history"]);
// …while Forums (unset) stays alphabetical.
const forumNames = await page
.getByTestId("forum-list")
.locator("[data-testid^='channel-']")
.evaluateAll((nodes) =>
nodes
.map((n) => n.getAttribute("data-testid") ?? "")
.filter(
(id) =>
!id.startsWith("channel-unread") &&
!id.startsWith("channel-working"),
)
.map((id) => id.replace(/^channel-/, "")),
);
const sortedForums = [...forumNames].sort((a, b) => a.localeCompare(b));
expect(forumNames).toEqual(sortedForums);
await waitForAnimations(page);
await page.screenshot({ path: `${SHOTS}/04-independent-groups.png` });
});
test("04 — DM group has its own sort trigger", async ({ page }) => {
await installMockBridge(page);
await openApp(page);
const dmList = page.getByTestId("dm-list");
await expect(dmList).toBeVisible();
await page.getByText("Direct messages", { exact: true }).hover();
const trigger = page.getByTestId("section-actions-dms");
await expect(trigger).toBeVisible();
await trigger.click();
// Sort is now a submenu flyout — open it before the radio items render.
await page.getByRole("menuitem", { name: "Sort" }).click();
await expect(
page.getByRole("menuitemradio", { name: "AZ" }),
).toBeVisible();
await waitForAnimations(page);
await page.screenshot({ path: `${SHOTS}/05-dm-sort-open.png` });
await page.getByRole("menuitemradio", { name: "Recent" }).click();
const stored = await page.evaluate((key) => {
return JSON.parse(window.localStorage.getItem(key) ?? "null");
}, SORT_STORAGE_KEY);
expect(stored).toEqual({ version: 1, groups: { dms: "recent" } });
});
});