mirror of
https://github.com/block/buzz.git
synced 2026-08-18 06:50:31 +02:00
## Why The Inbox mixed overlapping feed categories with personal work queues, so **All** was not actually comprehensive and several filters did not make it clear why an item appeared. Threads and DMs could produce one row per event instead of one row per conversation, drafts were hidden until selected, and reminders appeared through multiple competing presentations. This refactor makes the **Inbox** a focused, conversation-oriented place to catch up on work relevant to you. It is intentionally not a mirror of every unread event in every channel. ## What changed - Keep the destination named **Inbox** and use the standard Lucide bell icon. - Refocus **All** on DMs, mentions, thread replies, needs-action items, replies from agents the user owns or controls, due reminders, and active drafts. - Exclude generic top-level channel traffic and updates from agents the user does not own or control. - Group each thread or DM into one row, sorted by latest activity. - Resume an unread conversation at its oldest unread message while opening the full thread or DM in the detail pane. - Reuse the existing **New** divider at the unread boundary. - Make the detail title a direct link to the canonical conversation. - Give Reminders and Drafts the same list/detail interaction and location metadata as conversation rows. - Separate Reminders and Drafts from message filters with a subtle divider, without adding another labeled section. - Put reminder and draft counts beside their corresponding filter labels instead of on the generic filter button. - Preserve the selected conversation when switching filters if it remains valid; otherwise select a valid replacement without flashing stale detail. - Use filter-specific empty states and rename the options toggle to **Show unread only**. - Ship the focused behavior directly. The earlier experiment gate, Custom view, and default-view controls have been removed from this PR to keep the first pass focused. ## Filter model | Filter | What appears | | --- | --- | | **All** | One row per personally relevant conversation, plus due reminders and active drafts. Includes DMs, mentions, thread replies, explicit needs-action items, and replies from agents the current user owns or controls. Excludes generic top-level channel traffic, other agents' updates, and reminders that are not due yet. | | **Mentions** | Conversations containing a direct mention. Each conversation appears once and opens with full context. | | **Threads** | Conventional threaded replies, grouped to one row per thread. Broadcast replies are not treated as conventional thread replies. | | **Needs action** | Feed items explicitly classified as requiring action. | | **Agents** | Conversations whose representative response was authored by an agent the current user owns or controls, including top-level DM responses. If a human replies afterward, the conversation leaves this filter until an owned agent responds again. | | **Reminders** | All pending reminders, including upcoming reminders that stay out of **All** until they are due. | | **Drafts** | Active drafts, ordered by their last real edit time. | ## Grouping, ordering, and state - A thread or DM creates one Inbox row rather than one row per event. - An unread conversation resumes at its oldest unread message so intervening context is not skipped. - Conversation rows still sort by their latest activity. - The detail pane opens the full available conversation and shows the shared **New** divider before the first unread message. - Upcoming reminders appear only in **Reminders**. - When a reminder becomes due, it enters **All** at its trigger time. If its source conversation is already represented, the reminder state merges into that row instead of creating a duplicate; otherwise it appears as a standalone reminder row. - A due reminder can enrich a row in another relative filter when that conversation already qualifies for the filter. Reminder lifecycle remains separate from message read state. - Drafts appear in **All** by their last real edit time. Opening an unchanged draft does not move it to the top. - Reminder and draft rows show their location as `In #channel` or `In DM with <name>`. - **Show unread only** hides reminder and draft work queues because they do not share message unread semantics. ## Removed or narrowed - **Remove the old Activity filter.** It overlapped with All while still omitting items All now includes. - **Narrow Agents.** It no longer gathers every agent participating in a shared thread or subsequent human follow-ups. - **Remove duplicate reminder presentations.** The aggregate pending-reminders jump and duplicate generic feed rows are replaced by one list/detail model. - **Remove Custom and default-view settings from this pass.** They added considerable state and UI before the core model had been validated. - **Do not add section labels for Reminders and Drafts.** A divider communicates the distinction without creating another hierarchy in the menu. ## Risk assessment Medium implementation risk because this changes composition, grouping, ordering, read behavior, and personal queues in a primary desktop view. The implementation is scoped to the desktop UI and its local feed projection; it does not change relay schemas or public APIs. ## Testing - Desktop formatting, lint, file-size, text-size, and TypeScript checks passed. - Desktop unit suite: **3,663 passed, 0 failed**. - Desktop E2E production build passed. - Playwright smoke coverage across every spec touching this surface (`channels`, `smoke`, `profile`, `project-inbox`, `community-rail`, `integration`, `drafts-screenshots`): **118 passed, 0 failed**. - Full Playwright smoke project: **732 passed, 1 skipped**. Three local failures were investigated and cleared — `community-rail` keyboard reorder passed on re-run (flaky), while `relay-reconnect:97` and `video-attachment:223` are untouched by this commit (the only change to shared `tests/helpers/bridge.ts` is a comment) and pass in CI. - Unit coverage includes focused All matching, owned-agent filtering, conversation grouping, oldest-unread selection, selection stability, chronological reminder/draft composition, trigger-time reminder ordering, and duplicate reminder suppression. ## Update: July 27, 2026 The naming decision is settled: the surface stays **Inbox**. An earlier pass in this branch had renamed it to **Activity**; that rename has been reverted in `9c00d2d6e`, which is naming-only and changes no behavior. The revert covers file names, component/hook/type/constant identifiers, the sidebar label and tooltip, the `Inbox options` and `Filter inbox:` aria-labels, and the corresponding test names, test ids, and fixture ids. Three things were deliberately left as `activity`: - **The feed API contract** — the `activity` / `agent_activity` categories, the `feed.activity` and `feed.agentActivity` keys, and the `types=` query parameter. These are the server's names, not the surface's. - **Plain-noun usage** — empty states such as "No activity yet", plus `latestActivityAt` and `PROJECT_ACTIVITY_KINDS`. - **Pre-existing agent, project, and profile activity code**, which refers to a different concept entirely. The earlier experiment-gate approach has also been dropped, so `tests/helpers/bridge.ts` no longer claims that an Activity preview feature exists — `preview-features.json` has no such entry and the seed helper enables every desktop feature. Generated with Codex --------- Signed-off-by: Clay Delk <clay.delk@gmail.com> Co-authored-by: Claude Opus 5 (1M context) <noreply@anthropic.com>
572 lines
18 KiB
TypeScript
572 lines
18 KiB
TypeScript
import { expect, test, type Browser } from "@playwright/test";
|
|
|
|
import {
|
|
installRelayBridge,
|
|
openChannelBrowser,
|
|
openCreateChannelDialog,
|
|
TEST_IDENTITIES,
|
|
} from "../helpers/bridge";
|
|
import { openSettings } from "../helpers/settings";
|
|
import { assertRelaySeeded } from "../helpers/seed";
|
|
|
|
const isCi = Boolean(process.env.CI);
|
|
const relaySeedHookTimeoutMs = isCi ? 90_000 : 30_000;
|
|
|
|
async function createStream(
|
|
page: import("@playwright/test").Page,
|
|
channelName: string,
|
|
description?: string,
|
|
) {
|
|
await openCreateChannelDialog(page);
|
|
await page.getByTestId("create-channel-name").fill(channelName);
|
|
if (description !== undefined) {
|
|
await page.getByTestId("create-channel-description").fill(description);
|
|
}
|
|
await page.getByTestId("create-channel-submit").click();
|
|
|
|
await expect(page.getByTestId("stream-list")).toContainText(channelName);
|
|
await expect(page.getByTestId("chat-title")).toHaveText(channelName);
|
|
}
|
|
|
|
async function openChannelManagement(page: import("@playwright/test").Page) {
|
|
await page.getByTestId("channel-management-trigger").click();
|
|
await expect(page.getByTestId("channel-management-sheet")).toBeVisible();
|
|
}
|
|
|
|
async function openChannelEditDialog(page: import("@playwright/test").Page) {
|
|
await page.getByTestId("channel-management-edit").click();
|
|
await expect(
|
|
page.getByRole("dialog", {
|
|
name: /Edit (?:public|private) channel/,
|
|
}),
|
|
).toBeVisible();
|
|
}
|
|
|
|
async function closeChannelManagement(page: import("@playwright/test").Page) {
|
|
await page.getByTestId("auxiliary-panel-close").click();
|
|
await expect(page.getByTestId("channel-management-sheet")).not.toBeVisible();
|
|
}
|
|
|
|
async function assertDesktopNotificationsEnabled(
|
|
page: import("@playwright/test").Page,
|
|
) {
|
|
await openSettings(page, "notifications");
|
|
await expect(page.getByTestId("settings-notifications")).toBeVisible();
|
|
await expect(page.getByTestId("notifications-desktop-state")).toContainText(
|
|
"On",
|
|
);
|
|
await page.getByTestId("settings-back-to-app").click();
|
|
}
|
|
|
|
async function sendChannelMessage(
|
|
page: import("@playwright/test").Page,
|
|
{
|
|
channelName,
|
|
content,
|
|
kind,
|
|
mentionPubkeys,
|
|
}: {
|
|
channelName: string;
|
|
content: string;
|
|
kind?: number | null;
|
|
mentionPubkeys?: string[];
|
|
},
|
|
) {
|
|
await page.evaluate(
|
|
async ({
|
|
channelName: targetChannelName,
|
|
content,
|
|
kind,
|
|
mentionPubkeys,
|
|
}) => {
|
|
const tauriWindow = window as Window & {
|
|
__TAURI_INTERNALS__?: {
|
|
invoke: (
|
|
command: string,
|
|
payload?: Record<string, unknown>,
|
|
) => Promise<unknown>;
|
|
};
|
|
};
|
|
|
|
const invoke = tauriWindow.__TAURI_INTERNALS__?.invoke;
|
|
if (!invoke) {
|
|
throw new Error("Tauri invoke bridge is unavailable.");
|
|
}
|
|
|
|
const channels = (await invoke("get_channels")) as Array<{
|
|
id: string;
|
|
name: string;
|
|
}>;
|
|
const channel = channels.find(({ name }) => name === targetChannelName);
|
|
if (!channel) {
|
|
throw new Error(`Channel not found: ${targetChannelName}`);
|
|
}
|
|
|
|
await invoke("send_channel_message", {
|
|
channelId: channel.id,
|
|
content,
|
|
parentEventId: null,
|
|
mediaTags: null,
|
|
mentionPubkeys: mentionPubkeys ?? null,
|
|
kind: kind ?? null,
|
|
});
|
|
},
|
|
{ channelName, content, kind, mentionPubkeys },
|
|
);
|
|
}
|
|
|
|
async function joinChannel(
|
|
page: import("@playwright/test").Page,
|
|
channelName: string,
|
|
) {
|
|
await page.evaluate(async (targetChannelName) => {
|
|
const tauriWindow = window as Window & {
|
|
__TAURI_INTERNALS__?: {
|
|
invoke: (
|
|
command: string,
|
|
payload?: Record<string, unknown>,
|
|
) => Promise<unknown>;
|
|
};
|
|
};
|
|
|
|
const invoke = tauriWindow.__TAURI_INTERNALS__?.invoke;
|
|
if (!invoke) {
|
|
throw new Error("Tauri invoke bridge is unavailable.");
|
|
}
|
|
|
|
const channels = (await invoke("get_channels")) as Array<{
|
|
id: string;
|
|
name: string;
|
|
}>;
|
|
const channel = channels.find(({ name }) => name === targetChannelName);
|
|
if (!channel) {
|
|
throw new Error(`Channel not found: ${targetChannelName}`);
|
|
}
|
|
|
|
await invoke("join_channel", {
|
|
channelId: channel.id,
|
|
});
|
|
}, channelName);
|
|
}
|
|
|
|
async function getLoggedNotifications(page: import("@playwright/test").Page) {
|
|
return page.evaluate(() => {
|
|
const win = window as Window & {
|
|
__BUZZ_E2E_NOTIFICATIONS__?: Array<{
|
|
body: string | null;
|
|
title: string;
|
|
}>;
|
|
};
|
|
|
|
return win.__BUZZ_E2E_NOTIFICATIONS__ ?? [];
|
|
});
|
|
}
|
|
|
|
async function getLoggedNotificationCount(
|
|
page: import("@playwright/test").Page,
|
|
) {
|
|
return (await getLoggedNotifications(page)).length;
|
|
}
|
|
|
|
async function expectLoggedNotifications(
|
|
page: import("@playwright/test").Page,
|
|
expected: Array<{ body: string | null; title: string }>,
|
|
) {
|
|
await expect.poll(() => getLoggedNotifications(page)).toEqual(expected);
|
|
}
|
|
|
|
test.beforeAll(async () => {
|
|
test.setTimeout(relaySeedHookTimeoutMs);
|
|
await assertRelaySeeded();
|
|
});
|
|
|
|
test("create channel and verify in sidebar", async ({ page }) => {
|
|
const channelName = `integration-e2e-${Date.now()}`;
|
|
|
|
await installRelayBridge(page, "tyler");
|
|
await page.goto("/");
|
|
await openCreateChannelDialog(page);
|
|
await page.getByTestId("create-channel-name").fill(channelName);
|
|
await page.getByTestId("create-channel-submit").click();
|
|
|
|
await expect(page.getByTestId("stream-list")).toContainText(channelName);
|
|
await expect(page.getByTestId("chat-title")).toHaveText(channelName);
|
|
});
|
|
|
|
test("two users see the same channel", async ({
|
|
browser,
|
|
}: {
|
|
browser: Browser;
|
|
}) => {
|
|
const channelName = `shared-channel-${Date.now()}`;
|
|
const contextOne = await browser.newContext();
|
|
const contextTwo = await browser.newContext();
|
|
const pageOne = await contextOne.newPage();
|
|
const pageTwo = await contextTwo.newPage();
|
|
|
|
try {
|
|
await installRelayBridge(pageOne, "tyler");
|
|
await installRelayBridge(pageTwo, "alice");
|
|
|
|
await pageOne.goto("/");
|
|
await openCreateChannelDialog(pageOne);
|
|
await pageOne.getByTestId("create-channel-name").fill(channelName);
|
|
await pageOne.getByTestId("create-channel-submit").click();
|
|
await expect(pageOne.getByTestId("stream-list")).toContainText(channelName);
|
|
|
|
await pageTwo.goto("/");
|
|
await openChannelBrowser(pageTwo);
|
|
await expect(pageTwo.getByTestId("channel-browser-dialog")).toBeVisible();
|
|
await pageTwo
|
|
.getByTestId(`browse-channel-${channelName}`)
|
|
.getByRole("button", { name: "Join" })
|
|
.click();
|
|
await expect(pageTwo.getByTestId("stream-list")).toContainText(channelName);
|
|
} finally {
|
|
await contextOne.close();
|
|
await contextTwo.close();
|
|
}
|
|
});
|
|
|
|
test("message delivery across users", async ({
|
|
browser,
|
|
}: {
|
|
browser: Browser;
|
|
}) => {
|
|
const message = `Cross-user message ${Date.now()}`;
|
|
const contextOne = await browser.newContext();
|
|
const contextTwo = await browser.newContext();
|
|
const pageOne = await contextOne.newPage();
|
|
const pageTwo = await contextTwo.newPage();
|
|
|
|
try {
|
|
await installRelayBridge(pageOne, "tyler");
|
|
await installRelayBridge(pageTwo, "alice");
|
|
|
|
await pageOne.goto("/");
|
|
await pageTwo.goto("/");
|
|
|
|
await pageOne.getByTestId("channel-general").click();
|
|
await pageTwo.getByTestId("channel-general").click();
|
|
await expect(pageOne.getByTestId("chat-title")).toHaveText("general");
|
|
await expect(pageTwo.getByTestId("chat-title")).toHaveText("general");
|
|
|
|
await pageOne.getByTestId("message-input").fill(message);
|
|
await pageOne.getByTestId("send-message").click();
|
|
|
|
await expect(pageTwo.getByTestId("message-timeline")).toContainText(
|
|
message,
|
|
);
|
|
} finally {
|
|
await contextOne.close();
|
|
await contextTwo.close();
|
|
}
|
|
});
|
|
|
|
test("live mentions refetch the home feed without waiting for polling", async ({
|
|
browser,
|
|
}: {
|
|
browser: Browser;
|
|
}) => {
|
|
const stamp = Date.now();
|
|
const targetContext = await browser.newContext();
|
|
const senderContext = await browser.newContext();
|
|
const targetPage = await targetContext.newPage();
|
|
const senderPage = await senderContext.newPage();
|
|
|
|
try {
|
|
await installRelayBridge(targetPage, "tyler");
|
|
await installRelayBridge(senderPage, "alice");
|
|
|
|
await targetPage.goto("/");
|
|
await senderPage.goto("/");
|
|
await assertDesktopNotificationsEnabled(targetPage);
|
|
|
|
await targetPage.getByTestId("channel-general").click();
|
|
await expect(targetPage.getByTestId("chat-title")).toHaveText("general");
|
|
|
|
const message = `Heads up @tyler live mention ${stamp}`;
|
|
await sendChannelMessage(senderPage, {
|
|
channelName: "general",
|
|
content: message,
|
|
mentionPubkeys: [TEST_IDENTITIES.tyler.pubkey],
|
|
});
|
|
|
|
await expect(targetPage.getByTestId("message-timeline")).toContainText(
|
|
message,
|
|
);
|
|
|
|
await expectLoggedNotifications(targetPage, [
|
|
{
|
|
body: message,
|
|
title: "alice mentioned you in #general",
|
|
},
|
|
]);
|
|
|
|
// The Inbox feed should have been refetched live (the original purpose
|
|
// of this test). The home badge stays at 0 while the user is actively
|
|
// reading #general — reading in-channel advances the NIP-RS marker past
|
|
// the new mention — so the assertion that the refetch happened is the
|
|
// Inbox-list content, not the badge.
|
|
await targetPage
|
|
.getByTestId("app-sidebar")
|
|
.getByRole("button", { name: "Inbox" })
|
|
.click();
|
|
await expect(targetPage.getByTestId("home-inbox-list")).toBeVisible();
|
|
await expect(targetPage.getByTestId("home-inbox-list")).toContainText(
|
|
message,
|
|
);
|
|
await expect(targetPage.getByTestId("sidebar-home-count")).toHaveCount(0);
|
|
await expect.poll(() => getLoggedNotificationCount(targetPage)).toBe(1);
|
|
} finally {
|
|
await targetContext.close();
|
|
await senderContext.close();
|
|
}
|
|
});
|
|
|
|
test("live forum mentions refetch the home feed without waiting for polling", async ({
|
|
browser,
|
|
}: {
|
|
browser: Browser;
|
|
}) => {
|
|
const stamp = Date.now();
|
|
const targetContext = await browser.newContext();
|
|
const senderContext = await browser.newContext();
|
|
const targetPage = await targetContext.newPage();
|
|
const senderPage = await senderContext.newPage();
|
|
|
|
try {
|
|
await installRelayBridge(targetPage, "tyler");
|
|
await installRelayBridge(senderPage, "alice");
|
|
|
|
await targetPage.goto("/");
|
|
await senderPage.goto("/");
|
|
await assertDesktopNotificationsEnabled(targetPage);
|
|
|
|
await targetPage.getByTestId("channel-general").click();
|
|
await expect(targetPage.getByTestId("chat-title")).toHaveText("general");
|
|
await joinChannel(senderPage, "watercooler");
|
|
|
|
const message = `Forum ping @tyler ${stamp}`;
|
|
await sendChannelMessage(senderPage, {
|
|
channelName: "watercooler",
|
|
content: message,
|
|
kind: 45001,
|
|
mentionPubkeys: [TEST_IDENTITIES.tyler.pubkey],
|
|
});
|
|
|
|
await expect(targetPage.getByTestId("sidebar-home-count")).toHaveText("1");
|
|
|
|
await expectLoggedNotifications(targetPage, [
|
|
{
|
|
body: message,
|
|
title: "alice mentioned you in #watercooler",
|
|
},
|
|
]);
|
|
|
|
await targetPage
|
|
.getByTestId("app-sidebar")
|
|
.getByRole("button", { name: "Inbox" })
|
|
.click();
|
|
await expect(targetPage.getByTestId("home-inbox-list")).toBeVisible();
|
|
await expect(targetPage.getByTestId("home-inbox-list")).toBeVisible();
|
|
await expect(targetPage.getByTestId("home-inbox-list")).toContainText(
|
|
message,
|
|
);
|
|
await expect(targetPage.getByTestId("sidebar-home-count")).toHaveCount(0);
|
|
await expect.poll(() => getLoggedNotificationCount(targetPage)).toBe(1);
|
|
} finally {
|
|
await targetContext.close();
|
|
await senderContext.close();
|
|
}
|
|
});
|
|
|
|
test("DM channel appears in sidebar", async ({ page }) => {
|
|
await installRelayBridge(page, "tyler");
|
|
await page.goto("/");
|
|
|
|
await expect(page.getByTestId("dm-list")).toContainText("alice-tyler");
|
|
});
|
|
|
|
test("send message to DM", async ({ page }) => {
|
|
const message = `DM message ${Date.now()}`;
|
|
|
|
await installRelayBridge(page, "tyler");
|
|
await page.goto("/");
|
|
await page.getByTestId("channel-alice-tyler").click();
|
|
await expect(page.getByTestId("chat-title")).toHaveText("alice-tyler");
|
|
|
|
await page.getByTestId("message-input").fill(message);
|
|
await page.getByTestId("send-message").click();
|
|
|
|
await expect(page.getByTestId("message-timeline")).toContainText(message);
|
|
});
|
|
|
|
test("forum channel appears in sidebar", async ({ page }) => {
|
|
await installRelayBridge(page, "tyler");
|
|
await page.goto("/");
|
|
|
|
await expect(page.getByTestId("forum-list")).toContainText("watercooler");
|
|
});
|
|
|
|
test("create channel with description", async ({ page }) => {
|
|
const channelName = `desc-channel-${Date.now()}`;
|
|
const description = `Description for ${channelName}`;
|
|
|
|
await installRelayBridge(page, "tyler");
|
|
await page.goto("/");
|
|
await createStream(page, channelName, description);
|
|
|
|
await expect(page.getByTestId("chat-title")).toHaveAttribute(
|
|
"title",
|
|
description,
|
|
);
|
|
});
|
|
|
|
test("multiple channels independent", async ({ page }) => {
|
|
const channelA = `channel-a-${Date.now()}`;
|
|
const channelB = `channel-b-${Date.now()}`;
|
|
const messageA = `Message in A ${Date.now()}`;
|
|
|
|
await installRelayBridge(page, "tyler");
|
|
await page.goto("/");
|
|
|
|
await openCreateChannelDialog(page);
|
|
await page.getByTestId("create-channel-name").fill(channelA);
|
|
await page.getByTestId("create-channel-submit").click();
|
|
await expect(page.getByTestId("chat-title")).toHaveText(channelA);
|
|
|
|
await openCreateChannelDialog(page);
|
|
await page.getByTestId("create-channel-name").fill(channelB);
|
|
await page.getByTestId("create-channel-submit").click();
|
|
await expect(page.getByTestId("chat-title")).toHaveText(channelB);
|
|
|
|
// Navigate to channel A and send a message
|
|
await page.getByTestId(`channel-${channelA}`).click();
|
|
await expect(page.getByTestId("chat-title")).toHaveText(channelA);
|
|
await page.getByTestId("message-input").fill(messageA);
|
|
await page.getByTestId("send-message").click();
|
|
await expect(page.getByTestId("message-timeline")).toContainText(messageA);
|
|
|
|
// Switch to channel B — message from A should not appear
|
|
await page.getByTestId(`channel-${channelB}`).click();
|
|
await expect(page.getByTestId("chat-title")).toHaveText(channelB);
|
|
await expect(page.getByTestId("message-timeline")).not.toContainText(
|
|
messageA,
|
|
);
|
|
});
|
|
|
|
test("manage sheet updates channel details through the relay", async ({
|
|
page,
|
|
}) => {
|
|
const stamp = Date.now();
|
|
const initialName = `manage-integration-${stamp}`;
|
|
const renamedChannel = `manage-renamed-${stamp}`;
|
|
const initialDescription = `Initial description ${stamp}`;
|
|
const updatedDescription = `Updated description ${stamp}`;
|
|
|
|
await installRelayBridge(page, "tyler");
|
|
await page.goto("/");
|
|
await createStream(page, initialName, initialDescription);
|
|
|
|
await openChannelManagement(page);
|
|
await openChannelEditDialog(page);
|
|
const editDialog = page.getByRole("dialog", {
|
|
name: /Edit (?:public|private) channel/,
|
|
});
|
|
|
|
await editDialog.getByTestId("channel-management-name").fill(renamedChannel);
|
|
await editDialog
|
|
.getByTestId("channel-management-description")
|
|
.fill(updatedDescription);
|
|
await expect(editDialog.getByTestId("channel-management-topic")).toHaveCount(
|
|
0,
|
|
);
|
|
await expect(
|
|
editDialog.getByTestId("channel-management-purpose"),
|
|
).toHaveCount(0);
|
|
await editDialog.getByTestId("channel-management-save-changes").click();
|
|
await expect(editDialog).toHaveCount(0);
|
|
|
|
await expect(page.getByTestId("chat-title")).toHaveText(renamedChannel);
|
|
await expect(page.getByTestId("stream-list")).toContainText(renamedChannel);
|
|
|
|
await closeChannelManagement(page);
|
|
await page.reload();
|
|
|
|
await page.getByTestId(`channel-${renamedChannel}`).click();
|
|
await expect(page.getByTestId("chat-title")).toHaveText(renamedChannel);
|
|
await expect(page.getByTestId("chat-title")).toHaveAttribute(
|
|
"title",
|
|
updatedDescription,
|
|
);
|
|
|
|
await openChannelManagement(page);
|
|
await openChannelEditDialog(page);
|
|
const reopenedEditDialog = page.getByRole("dialog", {
|
|
name: /Edit (?:public|private) channel/,
|
|
});
|
|
|
|
await expect(
|
|
reopenedEditDialog.getByTestId("channel-management-name"),
|
|
).toHaveValue(renamedChannel);
|
|
await expect(
|
|
reopenedEditDialog.getByTestId("channel-management-description"),
|
|
).toHaveValue(updatedDescription);
|
|
await expect(
|
|
reopenedEditDialog.getByTestId("channel-management-topic"),
|
|
).toHaveCount(0);
|
|
await expect(
|
|
reopenedEditDialog.getByTestId("channel-management-purpose"),
|
|
).toHaveCount(0);
|
|
});
|
|
|
|
test("manage sheet archive and unarchive survives a reload through the relay", async ({
|
|
page,
|
|
}) => {
|
|
const channelName = `archive-integration-${Date.now()}`;
|
|
|
|
await installRelayBridge(page, "tyler");
|
|
await page.goto("/");
|
|
await createStream(page, channelName, "Archive integration channel");
|
|
|
|
await openChannelManagement(page);
|
|
await page.getByTestId("channel-management-archive").click();
|
|
await expect(page.getByTestId("channel-management-unarchive")).toBeVisible();
|
|
await closeChannelManagement(page);
|
|
|
|
await expect(page.getByTestId("stream-list")).not.toContainText(channelName);
|
|
await expect(page.getByTestId("message-input")).toHaveAttribute(
|
|
"contenteditable",
|
|
"false",
|
|
);
|
|
await expect(page.getByTestId("send-message")).toBeDisabled();
|
|
|
|
await page.reload();
|
|
|
|
await expect(page.getByTestId("stream-list")).not.toContainText(channelName);
|
|
await openChannelBrowser(page);
|
|
await expect(page.getByTestId("channel-browser-dialog")).toBeVisible();
|
|
await expect(page.getByTestId(`browse-channel-${channelName}`)).toContainText(
|
|
"archived",
|
|
);
|
|
await page.getByTestId(`browse-channel-${channelName}`).click();
|
|
await expect(page.getByTestId("channel-browser-dialog")).not.toBeVisible();
|
|
await expect(page.getByTestId("chat-title")).toHaveText(channelName);
|
|
await expect(page.getByTestId("message-input")).toHaveAttribute(
|
|
"contenteditable",
|
|
"false",
|
|
);
|
|
|
|
await openChannelManagement(page);
|
|
await page.getByTestId("channel-management-unarchive").click();
|
|
await expect(page.getByTestId("channel-management-archive")).toBeVisible();
|
|
await closeChannelManagement(page);
|
|
|
|
await expect(page.getByTestId("stream-list")).toContainText(channelName);
|
|
await expect(page.getByTestId("message-input")).toHaveAttribute(
|
|
"contenteditable",
|
|
"true",
|
|
);
|
|
});
|