mirror of
https://github.com/block/buzz.git
synced 2026-10-27 12:40:30 +00:00
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>
This commit is contained in:
@@ -20,6 +20,7 @@ export default defineConfig({
|
||||
name: "smoke",
|
||||
testMatch: [
|
||||
"**/smoke.spec.ts",
|
||||
"**/mock-subscription-readiness.spec.ts",
|
||||
"**/owned-agent-discovery.spec.ts",
|
||||
"**/thread-head-stale-edit.spec.ts",
|
||||
"**/sidebar-offcanvas-rail.spec.ts",
|
||||
|
||||
@@ -30,8 +30,13 @@ import {
|
||||
subscribeControlResults,
|
||||
} from "@/features/agents/observerRelayStore";
|
||||
import { switchManagedAgentModel } from "@/shared/api/agentControl";
|
||||
import { getAudioMediaLoadSchedulerSnapshot } from "@/features/messages/lib/audioMediaLoadScheduler";
|
||||
import { mockSearchHitMatches } from "./e2eBridgeSearch.ts";
|
||||
import { selectMockHistory } from "./e2eBridgeHistory.ts";
|
||||
import {
|
||||
createMockSubscription,
|
||||
hasMockSubscription,
|
||||
} from "./e2eBridgeSubscriptions.ts";
|
||||
export { mockSearchHitMatches };
|
||||
import type { ConnectionState } from "@/shared/api/relayClientShared";
|
||||
import type {
|
||||
@@ -1085,14 +1090,7 @@ type MockManagedAgentRuntimeRow = {
|
||||
type WsHandler = (message: unknown) => void;
|
||||
const GLOBAL_MOCK_SUBSCRIPTION = "*";
|
||||
|
||||
type MockSubscription = {
|
||||
channelIds: string[];
|
||||
kinds: number[] | null;
|
||||
/** `#p` values from the REQ filters, if any — lets specs assert an
|
||||
* owner-scoped live subscription (e.g. the observer-archive `24200`
|
||||
* reconciliation gate) independently of channel-scoped ones. */
|
||||
ownerPubkeys: string[];
|
||||
};
|
||||
type MockSubscription = ReturnType<typeof createMockSubscription>;
|
||||
|
||||
type MockFilter = {
|
||||
"#a"?: string[];
|
||||
@@ -1275,6 +1273,7 @@ declare global {
|
||||
__BUZZ_E2E_HAS_MOCK_LIVE_SUBSCRIPTION__?: (input: {
|
||||
channelName: string;
|
||||
kind?: number;
|
||||
exactChannel?: boolean;
|
||||
}) => boolean;
|
||||
__BUZZ_E2E_HAS_MOCK_OWNER_KIND_SUBSCRIPTION__?: (input: {
|
||||
ownerPubkey: string;
|
||||
@@ -1609,6 +1608,8 @@ declare global {
|
||||
__BUZZ_E2E_LINK_PREVIEW_UPLOAD_STARTS__?: number;
|
||||
/** Hold renderer-owned media fetches until their cancellation command. */
|
||||
__BUZZ_E2E_HOLD_MEDIA_FETCHES__?: boolean;
|
||||
/** Real scheduler ownership, including work not yet admitted to native fetch. */
|
||||
__BUZZ_E2E_AUDIO_LOAD_STATE__?: typeof getAudioMediaLoadSchedulerSnapshot;
|
||||
/** Exact active/peak native media-fetch ownership for scheduler tests. */
|
||||
__BUZZ_E2E_MEDIA_FETCH_STATE__?: { active: number; peak: number };
|
||||
/** Object-URL lifecycle counters installed by the audio E2E regression. */
|
||||
@@ -5040,22 +5041,19 @@ function emitMockGlobalEvent(event: RelayEvent) {
|
||||
}
|
||||
}
|
||||
|
||||
function hasMockLiveSubscription(channelId: string, kind?: number) {
|
||||
for (const socket of mockSockets.values()) {
|
||||
for (const subscription of socket.subscriptions.values()) {
|
||||
if (
|
||||
(subscription.channelIds.includes(channelId) ||
|
||||
subscription.channelIds.includes(GLOBAL_MOCK_SUBSCRIPTION)) &&
|
||||
(kind === undefined ||
|
||||
!subscription.kinds ||
|
||||
subscription.kinds.includes(kind))
|
||||
) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
function hasMockLiveSubscription(
|
||||
channelId: string,
|
||||
kind?: number,
|
||||
exactChannel = false,
|
||||
) {
|
||||
return [...mockSockets.values()].some((socket) =>
|
||||
hasMockSubscription(
|
||||
socket.subscriptions.values(),
|
||||
channelId,
|
||||
kind,
|
||||
exactChannel,
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -10862,43 +10860,29 @@ function sendToMockSocket(args: {
|
||||
}
|
||||
|
||||
if (subId.startsWith("live-")) {
|
||||
// Collect channel IDs from all filters in the REQ
|
||||
const channelIds = new Set<string>();
|
||||
const kinds = new Set<number>();
|
||||
const ownerPubkeys = new Set<string>();
|
||||
for (const f of filters) {
|
||||
for (const channelId of f["#h"] ?? []) channelIds.add(channelId);
|
||||
for (const kind of f.kinds ?? []) {
|
||||
kinds.add(kind);
|
||||
}
|
||||
for (const p of f["#p"] ?? []) {
|
||||
ownerPubkeys.add(p);
|
||||
}
|
||||
}
|
||||
const subscription = createMockSubscription(filters);
|
||||
const onlyChannelId =
|
||||
channelIds.size === 1
|
||||
? (channelIds.values().next().value as string)
|
||||
subscription.channelIds.length === 1 &&
|
||||
subscription.channelIds[0] !== GLOBAL_MOCK_SUBSCRIPTION
|
||||
? subscription.channelIds[0]
|
||||
: undefined;
|
||||
if (
|
||||
getConfig()?.mock?.closeChannelLiveSubscriptionOnce &&
|
||||
!mockClosedChannelLiveSubscription &&
|
||||
onlyChannelId &&
|
||||
kinds.has(KIND_CHANNEL_THREAD_SUMMARY)
|
||||
subscription.kinds?.includes(KIND_CHANNEL_THREAD_SUMMARY)
|
||||
) {
|
||||
mockClosedChannelLiveSubscription = true;
|
||||
sendWsText(socket.handler, ["CLOSED", subId, "rate-limited"]);
|
||||
return;
|
||||
}
|
||||
socket.subscriptions.set(subId, {
|
||||
channelIds:
|
||||
channelIds.size > 0 ? [...channelIds] : [GLOBAL_MOCK_SUBSCRIPTION],
|
||||
kinds: kinds.size > 0 ? [...kinds] : null,
|
||||
ownerPubkeys: [...ownerPubkeys],
|
||||
});
|
||||
socket.subscriptions.set(subId, subscription);
|
||||
// Live requests still replay stored matches; pacing can admit them after
|
||||
// a publish. Ephemeral/global fixtures are not channel history.
|
||||
const history = new Map(
|
||||
[...channelIds].map((id) => [id, getMockMessageStore(id)]),
|
||||
subscription.channelIds
|
||||
.filter((id) => id !== GLOBAL_MOCK_SUBSCRIPTION)
|
||||
.map((id) => [id, getMockMessageStore(id)]),
|
||||
);
|
||||
for (const event of selectMockHistory(history, filters)) {
|
||||
sendWsText(socket.handler, ["EVENT", subId, event]);
|
||||
@@ -11362,6 +11346,7 @@ export function maybeInstallE2eTauriMocks() {
|
||||
cancelledMediaFetchIds = new Set<string>();
|
||||
mockMediaFetchControllers = new Map<string, AbortController>();
|
||||
window.__BUZZ_E2E_LINK_PREVIEW_UPLOAD_STARTS__ = 0;
|
||||
window.__BUZZ_E2E_AUDIO_LOAD_STATE__ = getAudioMediaLoadSchedulerSnapshot;
|
||||
window.__BUZZ_E2E_MEDIA_FETCH_STATE__ = { active: 0, peak: 0 };
|
||||
window.__BUZZ_E2E_RELEASE_LINK_PREVIEW_METADATA__ = () => {
|
||||
const queued = deferredLinkPreviewMetadataQueue.splice(0);
|
||||
@@ -11618,7 +11603,11 @@ export function maybeInstallE2eTauriMocks() {
|
||||
createdAt,
|
||||
);
|
||||
};
|
||||
window.__BUZZ_E2E_HAS_MOCK_LIVE_SUBSCRIPTION__ = ({ channelName, kind }) => {
|
||||
window.__BUZZ_E2E_HAS_MOCK_LIVE_SUBSCRIPTION__ = ({
|
||||
channelName,
|
||||
kind,
|
||||
exactChannel,
|
||||
}) => {
|
||||
const channel = mockChannels.find(
|
||||
(candidate) => candidate.name === channelName,
|
||||
);
|
||||
@@ -11626,7 +11615,7 @@ export function maybeInstallE2eTauriMocks() {
|
||||
throw new Error(`Mock channel ${channelName} not found.`);
|
||||
}
|
||||
|
||||
return hasMockLiveSubscription(channel.id, kind);
|
||||
return hasMockLiveSubscription(channel.id, kind, exactChannel);
|
||||
};
|
||||
window.__BUZZ_E2E_HAS_MOCK_OWNER_KIND_SUBSCRIPTION__ = ({
|
||||
ownerPubkey,
|
||||
|
||||
68
desktop/src/testing/e2eBridgeSubscriptions.test.mjs
Normal file
68
desktop/src/testing/e2eBridgeSubscriptions.test.mjs
Normal file
@@ -0,0 +1,68 @@
|
||||
import assert from "node:assert/strict";
|
||||
import test from "node:test";
|
||||
import {
|
||||
createMockSubscription,
|
||||
hasMockSubscription,
|
||||
} from "./e2eBridgeSubscriptions.ts";
|
||||
|
||||
const ready = (filters, channel = "channel", kind = 9, exact = true) =>
|
||||
hasMockSubscription([createMockSubscription(filters)], channel, kind, exact);
|
||||
|
||||
test("channel readiness rejects global-only, wrong-channel and wrong-kind REQs", () => {
|
||||
for (const filter of [
|
||||
{ kinds: [9] },
|
||||
{ "#h": [], kinds: [9] },
|
||||
{ "#h": ["other"], kinds: [9] },
|
||||
{ "#h": ["channel"], kinds: [30078] },
|
||||
])
|
||||
assert.equal(ready([filter]), false);
|
||||
assert.equal(hasMockSubscription([], "channel", 9, true), false);
|
||||
for (const kinds of [[9], [7, 9], undefined]) {
|
||||
assert.equal(ready([{ "#h": ["channel"], kinds }]), true);
|
||||
}
|
||||
const emptyKinds = [
|
||||
createMockSubscription([{ "#h": ["channel"], kinds: [] }]),
|
||||
];
|
||||
assert.equal(hasMockSubscription(emptyKinds, "channel", 9, true), false);
|
||||
assert.equal(
|
||||
hasMockSubscription(emptyKinds, "channel", undefined, true),
|
||||
false,
|
||||
);
|
||||
assert.equal(
|
||||
ready([
|
||||
{ "#h": ["channel"], kinds: [] },
|
||||
{ "#h": ["channel"], kinds: [9] },
|
||||
]),
|
||||
true,
|
||||
);
|
||||
});
|
||||
|
||||
test("REQ storage preserves channel/kind correlation across OR filters", () => {
|
||||
for (const unrelated of [{ kinds: [9] }, { "#h": ["other"], kinds: [9] }]) {
|
||||
const filters = [{ "#h": ["channel"], kinds: [30078] }, unrelated];
|
||||
const stored = createMockSubscription(filters);
|
||||
assert.deepEqual(stored.filters, filters);
|
||||
assert.equal(hasMockSubscription([stored], "channel", 9, true), false);
|
||||
assert.equal(hasMockSubscription([stored], "channel", 30078, true), true);
|
||||
assert.equal(ready([...filters, { "#h": ["channel"], kinds: [9] }]), true);
|
||||
}
|
||||
});
|
||||
|
||||
test("legacy readiness and explicit global queries retain their semantics", () => {
|
||||
const global = [createMockSubscription([{ kinds: [30078] }])];
|
||||
assert.equal(hasMockSubscription(global, "channel"), true);
|
||||
assert.equal(hasMockSubscription(global, "channel", 9), false);
|
||||
assert.equal(hasMockSubscription(global, "channel", 30078), true);
|
||||
assert.equal(hasMockSubscription(global, "*", 30078), true);
|
||||
assert.equal(hasMockSubscription(global, "*", 9), false);
|
||||
assert.equal(hasMockSubscription(global, "channel", 30078, true), false);
|
||||
assert.equal(
|
||||
ready(
|
||||
[{ "#h": ["channel"], kinds: [30078] }, { kinds: [9] }],
|
||||
"channel",
|
||||
9,
|
||||
false,
|
||||
),
|
||||
true,
|
||||
);
|
||||
});
|
||||
50
desktop/src/testing/e2eBridgeSubscriptions.ts
Normal file
50
desktop/src/testing/e2eBridgeSubscriptions.ts
Normal file
@@ -0,0 +1,50 @@
|
||||
type SubscriptionFilter = {
|
||||
"#h"?: readonly string[];
|
||||
"#p"?: readonly string[];
|
||||
kinds?: readonly number[];
|
||||
};
|
||||
|
||||
/** Preserve raw REQ correlation alongside legacy mock delivery projections. */
|
||||
export function createMockSubscription(filters: readonly SubscriptionFilter[]) {
|
||||
const channelIds = [
|
||||
...new Set(filters.flatMap((filter) => filter["#h"] ?? [])),
|
||||
];
|
||||
const kinds = [...new Set(filters.flatMap((filter) => filter.kinds ?? []))];
|
||||
return {
|
||||
filters,
|
||||
channelIds: channelIds.length ? channelIds : ["*"],
|
||||
kinds: kinds.length ? kinds : null,
|
||||
ownerPubkeys: [...new Set(filters.flatMap((filter) => filter["#p"] ?? []))],
|
||||
};
|
||||
}
|
||||
|
||||
/** Test readiness must distinguish a channel consumer from unrelated global REQs. */
|
||||
export function hasMockSubscription(
|
||||
subscriptions: Iterable<ReturnType<typeof createMockSubscription>>,
|
||||
channelId: string,
|
||||
kind?: number,
|
||||
exactChannel = false,
|
||||
): boolean {
|
||||
for (const subscription of subscriptions) {
|
||||
if (exactChannel) {
|
||||
if (
|
||||
subscription.filters.some(
|
||||
(filter) =>
|
||||
filter["#h"]?.includes(channelId) &&
|
||||
(filter.kinds === undefined ||
|
||||
(filter.kinds.length > 0 &&
|
||||
(kind === undefined || filter.kinds.includes(kind)))),
|
||||
)
|
||||
)
|
||||
return true;
|
||||
} else if (
|
||||
(subscription.channelIds.includes(channelId) ||
|
||||
subscription.channelIds.includes("*")) &&
|
||||
(kind === undefined ||
|
||||
!subscription.kinds ||
|
||||
subscription.kinds.includes(kind))
|
||||
)
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
@@ -28,10 +28,14 @@ async function waitForMockLiveSubscription(page: Page, channelName: string) {
|
||||
window as Window & {
|
||||
__BUZZ_E2E_HAS_MOCK_LIVE_SUBSCRIPTION__?: (input: {
|
||||
channelName: string;
|
||||
kind: number;
|
||||
exactChannel: boolean;
|
||||
}) => boolean;
|
||||
}
|
||||
).__BUZZ_E2E_HAS_MOCK_LIVE_SUBSCRIPTION__?.({
|
||||
channelName: name,
|
||||
kind: 9,
|
||||
exactChannel: true,
|
||||
}) ?? false,
|
||||
channelName,
|
||||
),
|
||||
@@ -184,6 +188,7 @@ async function seedChannelActivity(
|
||||
await page.getByTestId("channel-random").click();
|
||||
await expect(page.getByTestId("chat-title")).toHaveText("random");
|
||||
|
||||
await waitForMockLiveSubscription(page, "general");
|
||||
const unreadAt = Math.floor(Date.now() / 1000) + 60;
|
||||
await emitMockMessage(
|
||||
page,
|
||||
|
||||
@@ -38,10 +38,15 @@ async function waitForMockLiveSubscription(
|
||||
window as Window & {
|
||||
__BUZZ_E2E_HAS_MOCK_LIVE_SUBSCRIPTION__?: (input: {
|
||||
channelName: string;
|
||||
kind: number;
|
||||
exactChannel: boolean;
|
||||
}) => boolean;
|
||||
}
|
||||
).__BUZZ_E2E_HAS_MOCK_LIVE_SUBSCRIPTION__?.({ channelName: ch }) ??
|
||||
false,
|
||||
).__BUZZ_E2E_HAS_MOCK_LIVE_SUBSCRIPTION__?.({
|
||||
channelName: ch,
|
||||
kind: 9,
|
||||
exactChannel: true,
|
||||
}) ?? false,
|
||||
{ ch: channelName },
|
||||
);
|
||||
})
|
||||
@@ -124,6 +129,7 @@ test.describe("channel muting", () => {
|
||||
|
||||
await page.getByTestId("channel-random").click();
|
||||
await expect(page.getByTestId("chat-title")).toHaveText("random");
|
||||
await waitForMockLiveSubscription(page, "engineering");
|
||||
|
||||
await page.evaluate(
|
||||
({ pubkey, mockPubkey }) => {
|
||||
|
||||
@@ -122,6 +122,16 @@ test("inbox reaction on a thread-reply mention persists after refetch", async ({
|
||||
|
||||
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.
|
||||
|
||||
93
desktop/tests/e2e/mock-subscription-readiness.spec.ts
Normal file
93
desktop/tests/e2e/mock-subscription-readiness.spec.ts
Normal file
@@ -0,0 +1,93 @@
|
||||
import { expect, test } from "@playwright/test";
|
||||
|
||||
import { installMockBridge } from "../helpers/bridge";
|
||||
|
||||
test("mock socket REQs preserve exact readiness semantics", async ({
|
||||
page,
|
||||
}) => {
|
||||
// Keep app-owned channel subscriptions out of the readiness result. The
|
||||
// bridge is installed, but locked boot never mounts the channel consumers.
|
||||
await installMockBridge(page, { identityLocked: true });
|
||||
await page.goto("/");
|
||||
await expect(page.getByTestId("keyring-locked")).toBeVisible();
|
||||
|
||||
const observed = await page.evaluate(async () => {
|
||||
const invoke = window.__BUZZ_E2E_INVOKE_MOCK_COMMAND__;
|
||||
const ready = window.__BUZZ_E2E_HAS_MOCK_LIVE_SUBSCRIPTION__;
|
||||
if (!invoke || !ready) throw new Error("Mock socket bridge is unavailable");
|
||||
const channel = "9a1657ac-f7aa-5db0-b632-d8bbeb6dfb50";
|
||||
const owner = "deadbeef".repeat(8);
|
||||
const frames: unknown[][] = [];
|
||||
const id = await invoke("plugin:websocket|connect", {
|
||||
onMessage: (batch: Array<{ type: string; data?: string }>) => {
|
||||
for (const frame of batch) {
|
||||
if (frame.type === "Text" && frame.data) {
|
||||
frames.push(JSON.parse(frame.data));
|
||||
}
|
||||
}
|
||||
},
|
||||
});
|
||||
const subId = "live-readiness-regression";
|
||||
const check = (kind: number | undefined = 9, exactChannel = true) =>
|
||||
ready({ channelName: "general", kind, exactChannel });
|
||||
const send = (message: unknown[]) =>
|
||||
invoke("plugin:websocket|send", {
|
||||
id,
|
||||
message: { type: "Text", data: JSON.stringify(message) },
|
||||
});
|
||||
const req = async (filters: Array<Record<string, unknown>>) => {
|
||||
frames.length = 0;
|
||||
await send(["REQ", subId, ...filters]);
|
||||
if (!frames.some((frame) => frame[0] === "EOSE" && frame[1] === subId)) {
|
||||
throw new Error(`REQ was not accepted: ${JSON.stringify(frames)}`);
|
||||
}
|
||||
return check();
|
||||
};
|
||||
|
||||
try {
|
||||
const before = check();
|
||||
const splitGlobal = await req([
|
||||
{ "#h": [channel], kinds: [30078] },
|
||||
{ kinds: [9] },
|
||||
]);
|
||||
const splitOtherChannel = await req([
|
||||
{ "#h": [channel], kinds: [30078] },
|
||||
{ "#h": ["other"], kinds: [9] },
|
||||
]);
|
||||
const sameFilter = await req([{ "#h": [channel], kinds: [9] }]);
|
||||
const emptyKinds = await req([
|
||||
{ "#h": [channel], "#p": [owner], kinds: [] },
|
||||
]);
|
||||
const omittedKinds = await req([{ "#h": [channel], "#p": [owner] }]);
|
||||
const globalOnly = await req([{ kinds: [9] }]);
|
||||
const legacyGlobal = check(9, false);
|
||||
await send(["CLOSE", subId]);
|
||||
const afterClose = check();
|
||||
return {
|
||||
before,
|
||||
splitGlobal,
|
||||
splitOtherChannel,
|
||||
sameFilter,
|
||||
emptyKinds,
|
||||
omittedKinds,
|
||||
globalOnly,
|
||||
legacyGlobal,
|
||||
afterClose,
|
||||
};
|
||||
} finally {
|
||||
await invoke("plugin:websocket|disconnect", { id });
|
||||
}
|
||||
});
|
||||
|
||||
expect(observed).toEqual({
|
||||
before: false,
|
||||
splitGlobal: false,
|
||||
splitOtherChannel: false,
|
||||
sameFilter: true,
|
||||
emptyKinds: false,
|
||||
omittedKinds: true,
|
||||
globalOnly: false,
|
||||
legacyGlobal: true,
|
||||
afterClose: false,
|
||||
});
|
||||
});
|
||||
@@ -23,10 +23,14 @@ async function waitForMockLiveSubscription(page: Page, channelName: string) {
|
||||
window as Window & {
|
||||
__BUZZ_E2E_HAS_MOCK_LIVE_SUBSCRIPTION__?: (input: {
|
||||
channelName: string;
|
||||
kind: number;
|
||||
exactChannel: boolean;
|
||||
}) => boolean;
|
||||
}
|
||||
).__BUZZ_E2E_HAS_MOCK_LIVE_SUBSCRIPTION__?.({
|
||||
channelName: currentChannelName,
|
||||
kind: 9,
|
||||
exactChannel: true,
|
||||
}) ?? false,
|
||||
channelName,
|
||||
),
|
||||
@@ -486,10 +490,14 @@ test("hard-caps audio work and cancels active and queued loads on unmount", asyn
|
||||
originalRevoke(url);
|
||||
};
|
||||
});
|
||||
await page.setViewportSize({ width: 1280, height: 400 });
|
||||
// Keep all 24 cards in the load margin so the test proves queued cancellation,
|
||||
// not just the three cards that happened to intersect a short viewport.
|
||||
await page.setViewportSize({ width: 1280, height: 3000 });
|
||||
await page.goto("/");
|
||||
await page.getByTestId("channel-general").click();
|
||||
await waitForMockLiveSubscription(page, "general");
|
||||
// Seed offscreen before opening the channel: live injection during initial
|
||||
// history/scroll settlement can unmount one visible set and load another.
|
||||
await page.getByTestId("channel-random").click();
|
||||
await expect(page.getByTestId("chat-title")).toHaveText("random");
|
||||
await page.evaluate(
|
||||
({ audioUrl }) => {
|
||||
const emit = (
|
||||
@@ -498,13 +506,16 @@ test("hard-caps audio work and cancels active and queued loads on unmount", asyn
|
||||
channelName: string;
|
||||
content: string;
|
||||
extraTags: string[][];
|
||||
createdAt: number;
|
||||
}) => unknown;
|
||||
}
|
||||
).__BUZZ_E2E_EMIT_MOCK_MESSAGE__;
|
||||
if (!emit) throw new Error("Mock message emitter is unavailable.");
|
||||
const createdAt = Math.floor(Date.now() / 1000);
|
||||
for (let index = 0; index < 24; index += 1) {
|
||||
emit({
|
||||
channelName: "general",
|
||||
createdAt: createdAt + index,
|
||||
content: `[voice-note-${index}.mp4](${audioUrl})`,
|
||||
extraTags: [
|
||||
[
|
||||
@@ -521,6 +532,7 @@ test("hard-caps audio work and cancels active and queued loads on unmount", asyn
|
||||
{ audioUrl: AUDIO_URL },
|
||||
);
|
||||
|
||||
await page.getByTestId("channel-general").click();
|
||||
const cards = page.getByTestId("audio-message-attachment");
|
||||
await expect(cards).toHaveCount(24);
|
||||
const readFetchCount = () =>
|
||||
@@ -537,6 +549,9 @@ test("hard-caps audio work and cancels active and queued loads on unmount", asyn
|
||||
),
|
||||
)
|
||||
.toEqual({ active: 3, peak: 3 });
|
||||
await expect
|
||||
.poll(() => page.evaluate(() => window.__BUZZ_E2E_AUDIO_LOAD_STATE__?.()))
|
||||
.toEqual({ active: 3, queued: 21 });
|
||||
expect(await readFetchCount()).toBe(3);
|
||||
expect(
|
||||
await page.evaluate(
|
||||
@@ -551,6 +566,9 @@ test("hard-caps audio work and cancels active and queued loads on unmount", asyn
|
||||
page.evaluate(() => window.__BUZZ_E2E_MEDIA_FETCH_STATE__?.active ?? -1),
|
||||
)
|
||||
.toBe(0);
|
||||
await expect
|
||||
.poll(() => page.evaluate(() => window.__BUZZ_E2E_AUDIO_LOAD_STATE__?.()))
|
||||
.toEqual({ active: 0, queued: 0 });
|
||||
const commandCounts = await page.evaluate(() => {
|
||||
const commands = window.__BUZZ_E2E_COMMANDS__ ?? [];
|
||||
return {
|
||||
|
||||
Reference in New Issue
Block a user