mirror of
https://github.com/block/buzz.git
synced 2026-10-27 12:40:30 +00:00
## Summary Repair the mock-backed desktop smoke fixtures blocking release PR #7817. No application unread logic, styling, audio component, scheduler, or release-tool changes. - Require an exact channel-scoped kind-9 subscription before injecting unread messages, including after navigation. The old readiness check could accept a global-only, wrong-channel, or wrong-kind consumer. The extracted predicate is used by the real mock bridge and has regression coverage for those false positives and legacy behavior. - Seed the 24 audio messages offscreen with distinct timestamps, then open the channel with all cards in the load margin. Same-second random event IDs previously reordered the settling timeline, causing different visible cards to start loads. This was viewport churn, not a component restart defect. - Expose the existing real audio scheduler snapshot through the E2E bridge. Assert 3 active / 21 queued before navigation and 0 active / 0 queued afterward. Preserve the peak-3 cap, exact 3 fetches / cancellations / releases, zero object URLs, and existing unread/bold assertions. No sleeps, retries, skipped tests, or relaxed limits were added. ## Validation - Before commit, all 30 tests in `channel-activity-popover.spec.ts`, `channel-mute.spec.ts`, and `voice-note.spec.ts` passed five repetitions with retries disabled: **150/150**. This was the working repair on base `312cf674f`, before final formatting/cleanup, not the entire smoke suite. - At committed head `1b126afbe75c2a2afbdfe13744f0938fa584764e`, normal pre-push hooks passed: **6,591/6,591 desktop unit tests**, desktop checks, TypeScript typechecking, differential file-size gate, and branch-skew check. Pre-commit Biome formatting passed; working tree is clean. - Independent sidebar and audio reviews found no substantive blocker. Their formatting findings are resolved in this commit. - Full repository `just ci`, native release packaging, and the entire Playwright smoke suite were not run locally. GitHub CI is the remaining broader gate; this PR is not a release certification. ## Release follow-up After this repair is merged and required checks pass, regenerate #7817 through the desktop release tooling and validate the new candidate's exact head. This PR does not merge or publish a release. Originating discussion: buzz://message?channel=3428ec3f-a58b-429b-afbc-dc6a77918ce8&id=3303565b14334696e9bbf433ef3ad1aea4307acc69834f7f685a412e81354493 Signed-off-by: Carl <32a2e2c9d428ee08902cab75d956da2c1d235a22d4766b0dd4138bf6e2e5db1d@buzz.block.builderlab.xyz> Co-authored-by: Carl <32a2e2c9d428ee08902cab75d956da2c1d235a22d4766b0dd4138bf6e2e5db1d@buzz.block.builderlab.xyz>
195 lines
7.1 KiB
TypeScript
195 lines
7.1 KiB
TypeScript
import { expect, test } from "@playwright/test";
|
|
|
|
import { installMockBridge, TEST_IDENTITIES } from "../helpers/bridge";
|
|
import type { RelayEvent } from "../../src/shared/api/types";
|
|
|
|
const GENERAL_CHANNEL_ID = "9a1657ac-f7aa-5db0-b632-d8bbeb6dfb50";
|
|
const SHOTS = "test-results/inbox-reactions";
|
|
|
|
type MockWindow = Window & {
|
|
__BUZZ_E2E_EMIT_MOCK_MESSAGE__?: (input: {
|
|
channelName: string;
|
|
content: string;
|
|
parentEventId?: string | null;
|
|
pubkey?: string;
|
|
mentionPubkeys?: string[];
|
|
/** 64-hex id — required for the message to be a valid reaction target. */
|
|
id?: string;
|
|
}) => RelayEvent;
|
|
__BUZZ_E2E_PUSH_MOCK_FEED_ITEM__?: (item: {
|
|
category: "mention" | "needs_action" | "activity" | "agent_activity";
|
|
channel_id: string | null;
|
|
channel_name: string;
|
|
content: string;
|
|
created_at: number;
|
|
id: string;
|
|
kind: number;
|
|
pubkey: string;
|
|
tags: string[][];
|
|
}) => unknown;
|
|
};
|
|
|
|
// Regression test: a reaction added from the Inbox detail pane must survive
|
|
// the post-toggle refetch. Inbox items are typically thread replies, which the
|
|
// server-assembled channel window does NOT carry reactions for — the Inbox
|
|
// must hydrate them by `#e` reference or the optimistic pill vanishes.
|
|
test("inbox reaction on a thread-reply mention persists after refetch", async ({
|
|
page,
|
|
}) => {
|
|
await installMockBridge(page);
|
|
await page.goto("/");
|
|
await expect(page.getByTestId("home-inbox-list")).toBeVisible();
|
|
await page.waitForFunction(() => {
|
|
const win = window as MockWindow;
|
|
return (
|
|
typeof win.__BUZZ_E2E_EMIT_MOCK_MESSAGE__ === "function" &&
|
|
typeof win.__BUZZ_E2E_PUSH_MOCK_FEED_ITEM__ === "function"
|
|
);
|
|
});
|
|
|
|
// Seed a thread: alice's top-level message, then alice's reply mentioning
|
|
// tyler (the current user). The reply is the inbox item — the shape Wes hit.
|
|
// A second, unrelated mention is seeded so the test can genuinely switch
|
|
// selection away and back.
|
|
const { replyEvent, otherEvent } = await page.evaluate(
|
|
({ channelId, currentPubkey, senderPubkey }) => {
|
|
const win = window as MockWindow;
|
|
const emitMessage = win.__BUZZ_E2E_EMIT_MOCK_MESSAGE__;
|
|
const pushFeedItem = win.__BUZZ_E2E_PUSH_MOCK_FEED_ITEM__;
|
|
if (!emitMessage || !pushFeedItem) {
|
|
throw new Error("Mock bridge helpers are not installed.");
|
|
}
|
|
|
|
const root = emitMessage({
|
|
channelName: "general",
|
|
content: "Thread root about the launch.",
|
|
pubkey: senderPubkey,
|
|
id: "a1".repeat(32),
|
|
});
|
|
const reply = emitMessage({
|
|
channelName: "general",
|
|
content: "Reply mentioning you — please react to this.",
|
|
parentEventId: root.id,
|
|
pubkey: senderPubkey,
|
|
mentionPubkeys: [currentPubkey],
|
|
id: "b2".repeat(32),
|
|
});
|
|
|
|
pushFeedItem({
|
|
id: reply.id,
|
|
kind: reply.kind,
|
|
pubkey: reply.pubkey,
|
|
content: reply.content,
|
|
created_at: reply.created_at,
|
|
channel_id: channelId,
|
|
channel_name: "general",
|
|
tags: reply.tags,
|
|
category: "mention",
|
|
});
|
|
|
|
const other = emitMessage({
|
|
channelName: "general",
|
|
content: "Unrelated mention for selection switching.",
|
|
pubkey: senderPubkey,
|
|
mentionPubkeys: [currentPubkey],
|
|
id: "c3".repeat(32),
|
|
});
|
|
pushFeedItem({
|
|
id: other.id,
|
|
kind: other.kind,
|
|
pubkey: other.pubkey,
|
|
content: other.content,
|
|
created_at: other.created_at,
|
|
channel_id: channelId,
|
|
channel_name: "general",
|
|
tags: other.tags,
|
|
category: "mention",
|
|
});
|
|
return { replyEvent: reply, otherEvent: other };
|
|
},
|
|
{
|
|
channelId: GENERAL_CHANNEL_ID,
|
|
currentPubkey: TEST_IDENTITIES.tyler.pubkey,
|
|
senderPubkey: TEST_IDENTITIES.alice.pubkey,
|
|
},
|
|
);
|
|
|
|
// Open the inbox item and react through Add reaction and the picker.
|
|
const item = page.getByTestId(`home-inbox-item-${replyEvent.id}`);
|
|
await item.click();
|
|
const detail = page.getByTestId("home-inbox-detail");
|
|
await expect(detail).toContainText("please react to this");
|
|
|
|
const selectedMessage = page.getByTestId("home-inbox-selected-message");
|
|
|
|
// Detail renders from history before the paced background consumer is ready.
|
|
// Exercise live delivery only once this channel actually requests kind 7.
|
|
await page.waitForFunction(() =>
|
|
window.__BUZZ_E2E_HAS_MOCK_LIVE_SUBSCRIPTION__?.({
|
|
channelName: "general",
|
|
kind: 7,
|
|
exactChannel: true,
|
|
}),
|
|
);
|
|
|
|
// Deliver a live reaction with the real add_reaction wire shape: `e` target,
|
|
// no `h` channel tag. The Inbox must render it without waiting for another
|
|
// message or a context refetch.
|
|
await page.evaluate(async (eventId) => {
|
|
const invoke = (
|
|
window as Window & {
|
|
__BUZZ_E2E_INVOKE_MOCK_COMMAND__?: (
|
|
command: string,
|
|
payload?: Record<string, unknown>,
|
|
) => Promise<unknown>;
|
|
}
|
|
).__BUZZ_E2E_INVOKE_MOCK_COMMAND__;
|
|
if (!invoke) throw new Error("Mock Tauri invoke bridge is unavailable.");
|
|
await invoke("add_reaction", { eventId, emoji: "❤️" });
|
|
}, replyEvent.id);
|
|
await expect(selectedMessage.getByLabel("Toggle ❤️ reaction")).toBeVisible();
|
|
|
|
await selectedMessage.hover();
|
|
const actionBar = page.getByTestId(`message-action-bar-${replyEvent.id}`);
|
|
const [actionBarBox, selectedMessageBox] = await Promise.all([
|
|
actionBar.boundingBox(),
|
|
selectedMessage.boundingBox(),
|
|
]);
|
|
expect(actionBarBox).not.toBeNull();
|
|
expect(selectedMessageBox).not.toBeNull();
|
|
if (!actionBarBox || !selectedMessageBox) {
|
|
throw new Error("Inbox message action bar bounds were unavailable.");
|
|
}
|
|
expect(actionBarBox.y).toBeGreaterThanOrEqual(selectedMessageBox.y);
|
|
|
|
await actionBar.getByRole("button", { name: "Open reactions" }).click();
|
|
const picker = page.locator("em-emoji-picker");
|
|
await expect(picker).toBeVisible();
|
|
await picker.locator("input[type='search']").fill("thumbs up");
|
|
await picker.getByRole("button", { name: "👍" }).first().click();
|
|
|
|
// The pill must appear AND persist: the post-toggle refetch replaces the
|
|
// optimistic state with fetched reaction events. Give the refetch time to
|
|
// land before asserting, then assert the pill is still there.
|
|
const reactions = selectedMessage.getByTestId("message-reactions");
|
|
await expect(reactions).toContainText("👍");
|
|
await page.waitForTimeout(1_500);
|
|
await expect(reactions).toContainText("👍");
|
|
await page.screenshot({ path: `${SHOTS}/01-pill-after-refetch.png` });
|
|
|
|
// Re-select the item (drops all optimistic state) — the reaction must
|
|
// come back purely from the fetched relay data. Select a genuinely
|
|
// different item first so the selection actually changes.
|
|
const otherItem = page.getByTestId(`home-inbox-item-${otherEvent.id}`);
|
|
await otherItem.click();
|
|
await expect(detail).toContainText("Unrelated mention");
|
|
await item.click();
|
|
await expect(detail).toContainText("please react to this");
|
|
await expect(
|
|
page
|
|
.getByTestId("home-inbox-selected-message")
|
|
.getByTestId("message-reactions"),
|
|
).toContainText("👍");
|
|
await page.screenshot({ path: `${SHOTS}/02-pill-after-reselect.png` });
|
|
});
|