Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 5 additions & 1 deletion desktop/src/testing/e2eBridge.ts
Original file line number Diff line number Diff line change
Expand Up @@ -386,6 +386,8 @@ type E2eConfig = {
/** Reject successive mock `join_channel` calls, then resume. */
joinChannelErrors?: string[];
channelsReadDelayMs?: number;
/** Hold the boot `get_channels` read until the E2E release seam is invoked. */
deferFirstChannelsRead?: boolean;
/** Return not-modified for this many reads before resuming full payloads. */
channelsNotModifiedResponses?: number;
/** When true, a matching knownHash returns a not-modified channel payload. */
Expand Down Expand Up @@ -11911,7 +11913,9 @@ export function maybeInstallE2eTauriMocks() {
for (const release of held) release();
return held.length;
};
deferNextChannelsRead = false;
// Armed from config because the boot read fires before a spec can call the
// defer seam below.
deferNextChannelsRead = config.mock?.deferFirstChannelsRead ?? false;
deferredChannelsReadResolve = null;
window.__BUZZ_E2E_CHANNELS_READ_PENDING__ = 0;
window.__BUZZ_E2E_DEFER_NEXT_CHANNELS_READ__ = () => {
Expand Down
48 changes: 29 additions & 19 deletions desktop/tests/e2e/sidebar-snapshot.spec.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,10 @@
import { expect, test, type Page } from "@playwright/test";

import { installMockBridge } from "../helpers/bridge";
import {
installMockBridge,
pendingChannelsRead,
releaseChannelsRead,
} from "../helpers/bridge";

const RELAY_URL = "ws://localhost:3000";
const OTHER_RELAY_URL = "ws://localhost:3001";
Expand Down Expand Up @@ -317,17 +321,13 @@ test("matching not-modified preserves display mutations without persisting them"
exact: true,
});
});
await expect
.poll(() => page.evaluate(() => window.__BUZZ_E2E_CHANNELS_READ_PENDING__))
.toBe(1);
await expect.poll(() => pendingChannelsRead(page)).toBe(1);
await mutateDisplayedChannels(page, optimisticName);
await expect(
page.locator('[data-channel-id="optimistic-channel"]'),
).toBeVisible();

expect(
await page.evaluate(() => window.__BUZZ_E2E_RELEASE_CHANNELS_READ__?.()),
).toBe(1);
expect(await releaseChannelsRead(page)).toBe(1);
// Invocation logging only proves the request started. Wait for the query to
// consume its response before checking either display state or persistence.
await expect
Expand Down Expand Up @@ -358,12 +358,12 @@ test("matching not-modified preserves display mutations without persisting them"
test("first-ever boot without a snapshot sends null and shows loading", async ({
page,
}) => {
await installMockBridge(page, { channelsReadDelayMs: READ_DELAY_MS });
await installMockBridge(page, { deferFirstChannelsRead: true });
await page.goto("/");

await expect(page.getByTestId("sidebar-loading")).toBeVisible({
timeout: 500,
});
// The gate holds the boot read, so the loading frame cannot race the runner.
await expect(page.getByTestId("sidebar-loading")).toBeVisible();
await expect.poll(() => pendingChannelsRead(page)).toBe(1);
await expect(page.locator('[data-channel-id^="snapshot-"]')).toHaveCount(0);
await expect
.poll(() => getChannelsPayloads(page))
Expand All @@ -375,7 +375,10 @@ test("first-ever boot without a snapshot sends null and shows loading", async ({
presence: "absent",
serializedBytes: 0,
});

expect(await releaseChannelsRead(page)).toBe(1);
await expect(page.getByTestId("channel-general")).toBeVisible();
await expect(page.getByTestId("sidebar-loading")).toHaveCount(0);
await expect
.poll(() => getFullSidebarMeasure(page))
.toEqual({
Expand All @@ -391,12 +394,12 @@ test("a different identity's snapshot is ignored", async ({ page }) => {
keyOwnerPubkey: OWNER_PUBKEY,
ownerPubkey: STALE_COMMUNITY_PUBKEY,
});
await installMockBridge(page, { channelsReadDelayMs: READ_DELAY_MS });
await installMockBridge(page, { deferFirstChannelsRead: true });
await page.goto("/");

await expect(page.getByTestId("sidebar-loading")).toBeVisible({
timeout: 500,
});
// The gate holds the boot read, so the loading frame cannot race the runner.
await expect(page.getByTestId("sidebar-loading")).toBeVisible();
await expect.poll(() => pendingChannelsRead(page)).toBe(1);
await expect(page.locator('[data-channel-id^="snapshot-"]')).toHaveCount(0);
await expect
.poll(() => getChannelsPayloads(page))
Expand All @@ -407,7 +410,11 @@ test("a different identity's snapshot is ignored", async ({ page }) => {
channelCount: 0,
presence: "invalid",
});

expect(await releaseChannelsRead(page)).toBe(1);
await expect(page.getByTestId("channel-general")).toBeVisible();
await expect(page.getByTestId("sidebar-loading")).toHaveCount(0);
await expect(page.locator('[data-channel-id^="snapshot-"]')).toHaveCount(0);
});

test("display-only community pubkey cannot expose another identity's snapshot", async ({
Expand Down Expand Up @@ -447,14 +454,14 @@ test("partial hash/list write fails toward a full fetch", async ({ page }) => {
);
}, snapshotKey(RELAY_URL));
await installMockBridge(page, {
channelsReadDelayMs: READ_DELAY_MS,
deferFirstChannelsRead: true,
honorChannelsKnownHash: true,
});
await page.goto("/");

await expect(page.getByTestId("sidebar-loading")).toBeVisible({
timeout: 500,
});
// The gate holds the boot read, so the loading frame cannot race the runner.
await expect(page.getByTestId("sidebar-loading")).toBeVisible();
await expect.poll(() => pendingChannelsRead(page)).toBe(1);
await expect(page.locator('[data-channel-id^="snapshot-"]')).toHaveCount(0);
await expect
.poll(() => getChannelsPayloads(page))
Expand All @@ -465,7 +472,10 @@ test("partial hash/list write fails toward a full fetch", async ({ page }) => {
channelCount: 0,
presence: "invalid",
});

expect(await releaseChannelsRead(page)).toBe(1);
await expect(page.getByTestId("channel-general")).toBeVisible();
await expect(page.getByTestId("sidebar-loading")).toHaveCount(0);
await expect(page.locator('[data-channel-id^="snapshot-"]')).toHaveCount(0);
});

Expand Down
14 changes: 14 additions & 0 deletions desktop/tests/helpers/bridge.ts
Original file line number Diff line number Diff line change
Expand Up @@ -288,6 +288,8 @@ type MockBridgeOptions = {
/** Reject successive mock `join_channel` calls, then resume. */
joinChannelErrors?: string[];
channelsReadDelayMs?: number;
/** Hold the boot `get_channels` read until `releaseChannelsRead` runs. */
deferFirstChannelsRead?: boolean;
/** Return not-modified for this many reads before resuming full payloads. */
channelsNotModifiedResponses?: number;
/** When true, a matching knownHash returns a not-modified channel payload. */
Expand Down Expand Up @@ -1064,6 +1066,18 @@ export async function installRelayBridge(
});
}

/** Number of `get_channels` reads currently held. */
export async function pendingChannelsRead(page: Page): Promise<number> {
return page.evaluate(() => window.__BUZZ_E2E_CHANNELS_READ_PENDING__ ?? 0);
}

/** Release the `get_channels` read held by the mock bridge, if any. */
export async function releaseChannelsRead(page: Page): Promise<number> {
return page.evaluate(
() => window.__BUZZ_E2E_RELEASE_CHANNELS_READ__?.() ?? 0,
);
}

/** Release every `get_channel_members` read held by `deferChannelMembersReads`. */
export async function releaseChannelMembersReads(page: Page): Promise<number> {
return page.evaluate(
Expand Down
Loading