Skip to content

Commit fe56d31

Browse files
wesbillmanCarl
andauthored
test(desktop): stabilize unread and audio release smoke fixtures (#7821)
## 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>
1 parent 312cf67 commit fe56d31

9 files changed

Lines changed: 294 additions & 54 deletions

‎desktop/playwright.config.ts‎

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,7 @@ export default defineConfig({
2020
name: "smoke",
2121
testMatch: [
2222
"**/smoke.spec.ts",
23+
"**/mock-subscription-readiness.spec.ts",
2324
"**/owned-agent-discovery.spec.ts",
2425
"**/thread-head-stale-edit.spec.ts",
2526
"**/sidebar-offcanvas-rail.spec.ts",

‎desktop/src/testing/e2eBridge.ts‎

Lines changed: 38 additions & 49 deletions
Original file line numberDiff line numberDiff line change
@@ -30,8 +30,13 @@ import {
3030
subscribeControlResults,
3131
} from "@/features/agents/observerRelayStore";
3232
import { switchManagedAgentModel } from "@/shared/api/agentControl";
33+
import { getAudioMediaLoadSchedulerSnapshot } from "@/features/messages/lib/audioMediaLoadScheduler";
3334
import { mockSearchHitMatches } from "./e2eBridgeSearch.ts";
3435
import { selectMockHistory } from "./e2eBridgeHistory.ts";
36+
import {
37+
createMockSubscription,
38+
hasMockSubscription,
39+
} from "./e2eBridgeSubscriptions.ts";
3540
export { mockSearchHitMatches };
3641
import type { ConnectionState } from "@/shared/api/relayClientShared";
3742
import type {
@@ -1085,14 +1090,7 @@ type MockManagedAgentRuntimeRow = {
10851090
type WsHandler = (message: unknown) => void;
10861091
const GLOBAL_MOCK_SUBSCRIPTION = "*";
10871092

1088-
type MockSubscription = {
1089-
channelIds: string[];
1090-
kinds: number[] | null;
1091-
/** `#p` values from the REQ filters, if any — lets specs assert an
1092-
* owner-scoped live subscription (e.g. the observer-archive `24200`
1093-
* reconciliation gate) independently of channel-scoped ones. */
1094-
ownerPubkeys: string[];
1095-
};
1093+
type MockSubscription = ReturnType<typeof createMockSubscription>;
10961094

10971095
type MockFilter = {
10981096
"#a"?: string[];
@@ -1275,6 +1273,7 @@ declare global {
12751273
__BUZZ_E2E_HAS_MOCK_LIVE_SUBSCRIPTION__?: (input: {
12761274
channelName: string;
12771275
kind?: number;
1276+
exactChannel?: boolean;
12781277
}) => boolean;
12791278
__BUZZ_E2E_HAS_MOCK_OWNER_KIND_SUBSCRIPTION__?: (input: {
12801279
ownerPubkey: string;
@@ -1609,6 +1608,8 @@ declare global {
16091608
__BUZZ_E2E_LINK_PREVIEW_UPLOAD_STARTS__?: number;
16101609
/** Hold renderer-owned media fetches until their cancellation command. */
16111610
__BUZZ_E2E_HOLD_MEDIA_FETCHES__?: boolean;
1611+
/** Real scheduler ownership, including work not yet admitted to native fetch. */
1612+
__BUZZ_E2E_AUDIO_LOAD_STATE__?: typeof getAudioMediaLoadSchedulerSnapshot;
16121613
/** Exact active/peak native media-fetch ownership for scheduler tests. */
16131614
__BUZZ_E2E_MEDIA_FETCH_STATE__?: { active: number; peak: number };
16141615
/** Object-URL lifecycle counters installed by the audio E2E regression. */
@@ -5040,22 +5041,19 @@ function emitMockGlobalEvent(event: RelayEvent) {
50405041
}
50415042
}
50425043

5043-
function hasMockLiveSubscription(channelId: string, kind?: number) {
5044-
for (const socket of mockSockets.values()) {
5045-
for (const subscription of socket.subscriptions.values()) {
5046-
if (
5047-
(subscription.channelIds.includes(channelId) ||
5048-
subscription.channelIds.includes(GLOBAL_MOCK_SUBSCRIPTION)) &&
5049-
(kind === undefined ||
5050-
!subscription.kinds ||
5051-
subscription.kinds.includes(kind))
5052-
) {
5053-
return true;
5054-
}
5055-
}
5056-
}
5057-
5058-
return false;
5044+
function hasMockLiveSubscription(
5045+
channelId: string,
5046+
kind?: number,
5047+
exactChannel = false,
5048+
) {
5049+
return [...mockSockets.values()].some((socket) =>
5050+
hasMockSubscription(
5051+
socket.subscriptions.values(),
5052+
channelId,
5053+
kind,
5054+
exactChannel,
5055+
),
5056+
);
50595057
}
50605058

50615059
/**
@@ -10862,43 +10860,29 @@ function sendToMockSocket(args: {
1086210860
}
1086310861

1086410862
if (subId.startsWith("live-")) {
10865-
// Collect channel IDs from all filters in the REQ
10866-
const channelIds = new Set<string>();
10867-
const kinds = new Set<number>();
10868-
const ownerPubkeys = new Set<string>();
10869-
for (const f of filters) {
10870-
for (const channelId of f["#h"] ?? []) channelIds.add(channelId);
10871-
for (const kind of f.kinds ?? []) {
10872-
kinds.add(kind);
10873-
}
10874-
for (const p of f["#p"] ?? []) {
10875-
ownerPubkeys.add(p);
10876-
}
10877-
}
10863+
const subscription = createMockSubscription(filters);
1087810864
const onlyChannelId =
10879-
channelIds.size === 1
10880-
? (channelIds.values().next().value as string)
10865+
subscription.channelIds.length === 1 &&
10866+
subscription.channelIds[0] !== GLOBAL_MOCK_SUBSCRIPTION
10867+
? subscription.channelIds[0]
1088110868
: undefined;
1088210869
if (
1088310870
getConfig()?.mock?.closeChannelLiveSubscriptionOnce &&
1088410871
!mockClosedChannelLiveSubscription &&
1088510872
onlyChannelId &&
10886-
kinds.has(KIND_CHANNEL_THREAD_SUMMARY)
10873+
subscription.kinds?.includes(KIND_CHANNEL_THREAD_SUMMARY)
1088710874
) {
1088810875
mockClosedChannelLiveSubscription = true;
1088910876
sendWsText(socket.handler, ["CLOSED", subId, "rate-limited"]);
1089010877
return;
1089110878
}
10892-
socket.subscriptions.set(subId, {
10893-
channelIds:
10894-
channelIds.size > 0 ? [...channelIds] : [GLOBAL_MOCK_SUBSCRIPTION],
10895-
kinds: kinds.size > 0 ? [...kinds] : null,
10896-
ownerPubkeys: [...ownerPubkeys],
10897-
});
10879+
socket.subscriptions.set(subId, subscription);
1089810880
// Live requests still replay stored matches; pacing can admit them after
1089910881
// a publish. Ephemeral/global fixtures are not channel history.
1090010882
const history = new Map(
10901-
[...channelIds].map((id) => [id, getMockMessageStore(id)]),
10883+
subscription.channelIds
10884+
.filter((id) => id !== GLOBAL_MOCK_SUBSCRIPTION)
10885+
.map((id) => [id, getMockMessageStore(id)]),
1090210886
);
1090310887
for (const event of selectMockHistory(history, filters)) {
1090410888
sendWsText(socket.handler, ["EVENT", subId, event]);
@@ -11362,6 +11346,7 @@ export function maybeInstallE2eTauriMocks() {
1136211346
cancelledMediaFetchIds = new Set<string>();
1136311347
mockMediaFetchControllers = new Map<string, AbortController>();
1136411348
window.__BUZZ_E2E_LINK_PREVIEW_UPLOAD_STARTS__ = 0;
11349+
window.__BUZZ_E2E_AUDIO_LOAD_STATE__ = getAudioMediaLoadSchedulerSnapshot;
1136511350
window.__BUZZ_E2E_MEDIA_FETCH_STATE__ = { active: 0, peak: 0 };
1136611351
window.__BUZZ_E2E_RELEASE_LINK_PREVIEW_METADATA__ = () => {
1136711352
const queued = deferredLinkPreviewMetadataQueue.splice(0);
@@ -11618,15 +11603,19 @@ export function maybeInstallE2eTauriMocks() {
1161811603
createdAt,
1161911604
);
1162011605
};
11621-
window.__BUZZ_E2E_HAS_MOCK_LIVE_SUBSCRIPTION__ = ({ channelName, kind }) => {
11606+
window.__BUZZ_E2E_HAS_MOCK_LIVE_SUBSCRIPTION__ = ({
11607+
channelName,
11608+
kind,
11609+
exactChannel,
11610+
}) => {
1162211611
const channel = mockChannels.find(
1162311612
(candidate) => candidate.name === channelName,
1162411613
);
1162511614
if (!channel) {
1162611615
throw new Error(`Mock channel ${channelName} not found.`);
1162711616
}
1162811617

11629-
return hasMockLiveSubscription(channel.id, kind);
11618+
return hasMockLiveSubscription(channel.id, kind, exactChannel);
1163011619
};
1163111620
window.__BUZZ_E2E_HAS_MOCK_OWNER_KIND_SUBSCRIPTION__ = ({
1163211621
ownerPubkey,
Lines changed: 68 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,68 @@
1+
import assert from "node:assert/strict";
2+
import test from "node:test";
3+
import {
4+
createMockSubscription,
5+
hasMockSubscription,
6+
} from "./e2eBridgeSubscriptions.ts";
7+
8+
const ready = (filters, channel = "channel", kind = 9, exact = true) =>
9+
hasMockSubscription([createMockSubscription(filters)], channel, kind, exact);
10+
11+
test("channel readiness rejects global-only, wrong-channel and wrong-kind REQs", () => {
12+
for (const filter of [
13+
{ kinds: [9] },
14+
{ "#h": [], kinds: [9] },
15+
{ "#h": ["other"], kinds: [9] },
16+
{ "#h": ["channel"], kinds: [30078] },
17+
])
18+
assert.equal(ready([filter]), false);
19+
assert.equal(hasMockSubscription([], "channel", 9, true), false);
20+
for (const kinds of [[9], [7, 9], undefined]) {
21+
assert.equal(ready([{ "#h": ["channel"], kinds }]), true);
22+
}
23+
const emptyKinds = [
24+
createMockSubscription([{ "#h": ["channel"], kinds: [] }]),
25+
];
26+
assert.equal(hasMockSubscription(emptyKinds, "channel", 9, true), false);
27+
assert.equal(
28+
hasMockSubscription(emptyKinds, "channel", undefined, true),
29+
false,
30+
);
31+
assert.equal(
32+
ready([
33+
{ "#h": ["channel"], kinds: [] },
34+
{ "#h": ["channel"], kinds: [9] },
35+
]),
36+
true,
37+
);
38+
});
39+
40+
test("REQ storage preserves channel/kind correlation across OR filters", () => {
41+
for (const unrelated of [{ kinds: [9] }, { "#h": ["other"], kinds: [9] }]) {
42+
const filters = [{ "#h": ["channel"], kinds: [30078] }, unrelated];
43+
const stored = createMockSubscription(filters);
44+
assert.deepEqual(stored.filters, filters);
45+
assert.equal(hasMockSubscription([stored], "channel", 9, true), false);
46+
assert.equal(hasMockSubscription([stored], "channel", 30078, true), true);
47+
assert.equal(ready([...filters, { "#h": ["channel"], kinds: [9] }]), true);
48+
}
49+
});
50+
51+
test("legacy readiness and explicit global queries retain their semantics", () => {
52+
const global = [createMockSubscription([{ kinds: [30078] }])];
53+
assert.equal(hasMockSubscription(global, "channel"), true);
54+
assert.equal(hasMockSubscription(global, "channel", 9), false);
55+
assert.equal(hasMockSubscription(global, "channel", 30078), true);
56+
assert.equal(hasMockSubscription(global, "*", 30078), true);
57+
assert.equal(hasMockSubscription(global, "*", 9), false);
58+
assert.equal(hasMockSubscription(global, "channel", 30078, true), false);
59+
assert.equal(
60+
ready(
61+
[{ "#h": ["channel"], kinds: [30078] }, { kinds: [9] }],
62+
"channel",
63+
9,
64+
false,
65+
),
66+
true,
67+
);
68+
});
Lines changed: 50 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,50 @@
1+
type SubscriptionFilter = {
2+
"#h"?: readonly string[];
3+
"#p"?: readonly string[];
4+
kinds?: readonly number[];
5+
};
6+
7+
/** Preserve raw REQ correlation alongside legacy mock delivery projections. */
8+
export function createMockSubscription(filters: readonly SubscriptionFilter[]) {
9+
const channelIds = [
10+
...new Set(filters.flatMap((filter) => filter["#h"] ?? [])),
11+
];
12+
const kinds = [...new Set(filters.flatMap((filter) => filter.kinds ?? []))];
13+
return {
14+
filters,
15+
channelIds: channelIds.length ? channelIds : ["*"],
16+
kinds: kinds.length ? kinds : null,
17+
ownerPubkeys: [...new Set(filters.flatMap((filter) => filter["#p"] ?? []))],
18+
};
19+
}
20+
21+
/** Test readiness must distinguish a channel consumer from unrelated global REQs. */
22+
export function hasMockSubscription(
23+
subscriptions: Iterable<ReturnType<typeof createMockSubscription>>,
24+
channelId: string,
25+
kind?: number,
26+
exactChannel = false,
27+
): boolean {
28+
for (const subscription of subscriptions) {
29+
if (exactChannel) {
30+
if (
31+
subscription.filters.some(
32+
(filter) =>
33+
filter["#h"]?.includes(channelId) &&
34+
(filter.kinds === undefined ||
35+
(filter.kinds.length > 0 &&
36+
(kind === undefined || filter.kinds.includes(kind)))),
37+
)
38+
)
39+
return true;
40+
} else if (
41+
(subscription.channelIds.includes(channelId) ||
42+
subscription.channelIds.includes("*")) &&
43+
(kind === undefined ||
44+
!subscription.kinds ||
45+
subscription.kinds.includes(kind))
46+
)
47+
return true;
48+
}
49+
return false;
50+
}

‎desktop/tests/e2e/channel-activity-popover.spec.ts‎

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -28,10 +28,14 @@ async function waitForMockLiveSubscription(page: Page, channelName: string) {
2828
window as Window & {
2929
__BUZZ_E2E_HAS_MOCK_LIVE_SUBSCRIPTION__?: (input: {
3030
channelName: string;
31+
kind: number;
32+
exactChannel: boolean;
3133
}) => boolean;
3234
}
3335
).__BUZZ_E2E_HAS_MOCK_LIVE_SUBSCRIPTION__?.({
3436
channelName: name,
37+
kind: 9,
38+
exactChannel: true,
3539
}) ?? false,
3640
channelName,
3741
),
@@ -184,6 +188,7 @@ async function seedChannelActivity(
184188
await page.getByTestId("channel-random").click();
185189
await expect(page.getByTestId("chat-title")).toHaveText("random");
186190

191+
await waitForMockLiveSubscription(page, "general");
187192
const unreadAt = Math.floor(Date.now() / 1000) + 60;
188193
await emitMockMessage(
189194
page,

‎desktop/tests/e2e/channel-mute.spec.ts‎

Lines changed: 8 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -38,10 +38,15 @@ async function waitForMockLiveSubscription(
3838
window as Window & {
3939
__BUZZ_E2E_HAS_MOCK_LIVE_SUBSCRIPTION__?: (input: {
4040
channelName: string;
41+
kind: number;
42+
exactChannel: boolean;
4143
}) => boolean;
4244
}
43-
).__BUZZ_E2E_HAS_MOCK_LIVE_SUBSCRIPTION__?.({ channelName: ch }) ??
44-
false,
45+
).__BUZZ_E2E_HAS_MOCK_LIVE_SUBSCRIPTION__?.({
46+
channelName: ch,
47+
kind: 9,
48+
exactChannel: true,
49+
}) ?? false,
4550
{ ch: channelName },
4651
);
4752
})
@@ -124,6 +129,7 @@ test.describe("channel muting", () => {
124129

125130
await page.getByTestId("channel-random").click();
126131
await expect(page.getByTestId("chat-title")).toHaveText("random");
132+
await waitForMockLiveSubscription(page, "engineering");
127133

128134
await page.evaluate(
129135
({ pubkey, mockPubkey }) => {

‎desktop/tests/e2e/inbox-reactions.spec.ts‎

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -122,6 +122,16 @@ test("inbox reaction on a thread-reply mention persists after refetch", async ({
122122

123123
const selectedMessage = page.getByTestId("home-inbox-selected-message");
124124

125+
// Detail renders from history before the paced background consumer is ready.
126+
// Exercise live delivery only once this channel actually requests kind 7.
127+
await page.waitForFunction(() =>
128+
window.__BUZZ_E2E_HAS_MOCK_LIVE_SUBSCRIPTION__?.({
129+
channelName: "general",
130+
kind: 7,
131+
exactChannel: true,
132+
}),
133+
);
134+
125135
// Deliver a live reaction with the real add_reaction wire shape: `e` target,
126136
// no `h` channel tag. The Inbox must render it without waiting for another
127137
// message or a context refetch.

0 commit comments

Comments
 (0)