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
32 changes: 20 additions & 12 deletions desktop/src/testing/e2eBridge.ts
Original file line number Diff line number Diff line change
Expand Up @@ -1370,7 +1370,7 @@ type DeferredGetEvent = {
let deferredGetEventQueue: DeferredGetEvent[] = [];
let deferredObserverArchivePolicyQueue: Array<() => void> = [];
let deferNextChannelsRead = false;
let deferredChannelsReadQueue: Array<() => void> = [];
let deferredChannelsReadResolve: (() => void) | null = null;

const mockDisplayNames = new Map<string, string>([
[MOCK_IDENTITY_PUBKEY, DEFAULT_MOCK_IDENTITY.display_name],
Expand Down Expand Up @@ -9797,17 +9797,21 @@ export function maybeInstallE2eTauriMocks() {
return queued.length;
};
deferNextChannelsRead = false;
deferredChannelsReadQueue = [];
deferredChannelsReadResolve = null;
window.__BUZZ_E2E_CHANNELS_READ_PENDING__ = 0;
window.__BUZZ_E2E_DEFER_NEXT_CHANNELS_READ__ = () => {
if (deferredChannelsReadResolve) {
throw new Error("a channel read is already deferred");
}
deferNextChannelsRead = true;
};
window.__BUZZ_E2E_RELEASE_CHANNELS_READ__ = () => {
deferNextChannelsRead = false;
const queued = deferredChannelsReadQueue.splice(0);
const resolve = deferredChannelsReadResolve;
deferredChannelsReadResolve = null;
window.__BUZZ_E2E_CHANNELS_READ_PENDING__ = 0;
for (const resolve of queued) resolve();
return queued.length;
resolve?.();
return resolve ? 1 : 0;
};
window.__BUZZ_E2E_RELEASE_GET_EVENT__ = () => {
const queued = deferredGetEventQueue.splice(0);
Expand Down Expand Up @@ -11125,16 +11129,20 @@ export function maybeInstallE2eTauriMocks() {
payload as Parameters<typeof handleDiscoverManagedAgentPrereqs>[0],
activeConfig,
);
case "get_channels":
if (deferNextChannelsRead) {
deferNextChannelsRead = false;
case "get_channels": {
// Claim the one-shot before starting the read, then hold only that
// invocation's completion. Later reads pass through and cannot join it.
const deferred = deferNextChannelsRead;
deferNextChannelsRead = false;
const channels = handleGetChannels(activeConfig);
if (deferred) {
await new Promise<void>((resolve) => {
deferredChannelsReadQueue.push(resolve);
window.__BUZZ_E2E_CHANNELS_READ_PENDING__ =
deferredChannelsReadQueue.length;
deferredChannelsReadResolve = resolve;
window.__BUZZ_E2E_CHANNELS_READ_PENDING__ = 1;
});
}
return handleGetChannels(activeConfig);
return channels;
}
case "get_feed":
return handleGetFeed(
(payload as Parameters<typeof handleGetFeed>[0]) ?? {},
Expand Down
164 changes: 98 additions & 66 deletions desktop/tests/e2e/community-rail.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -507,26 +507,47 @@ test.describe("community rail", () => {
);
});
await page.evaluate(() => {
const deferNextChannelsRead = (
const config = (
window as Window & {
__BUZZ_E2E_DEFER_NEXT_CHANNELS_READ__?: () => void;
__BUZZ_E2E__?: {
mock?: {
channelsReadError?: string;
channelsReadErrors?: (string | null)[];
};
};
}
).__BUZZ_E2E_DEFER_NEXT_CHANNELS_READ__;
).__BUZZ_E2E__;
if (!config) throw new Error("missing E2E config");
config.mock = {
...config.mock,
channelsReadError: "temporary channel read failure",
channelsReadErrors: ["temporary channel read failure"],
};
});
await expect
.poll(() =>
page.evaluate(
() => window.__BUZZ_E2E__?.mock?.channelsReadErrors?.length ?? 0,
),
)
.toBe(1);
await page.evaluate(() => {
const testWindow = window as Window & {
__BUZZ_E2E_DEFER_NEXT_CHANNELS_READ__?: () => void;
__BUZZ_E2E_INVALIDATE_CHANNELS__?: () => Promise<void>;
};
const deferNextChannelsRead =
testWindow.__BUZZ_E2E_DEFER_NEXT_CHANNELS_READ__;
const invalidateChannels = testWindow.__BUZZ_E2E_INVALIDATE_CHANNELS__;
if (!deferNextChannelsRead) {
throw new Error("missing channel-read defer seam");
}
deferNextChannelsRead();
});

await page.evaluate(() => {
const invalidateChannels = (
window as Window & {
__BUZZ_E2E_INVALIDATE_CHANNELS__?: () => Promise<void>;
}
).__BUZZ_E2E_INVALIDATE_CHANNELS__;
if (!invalidateChannels) {
throw new Error("missing channel invalidation seam");
}
// Arm and trigger in one browser task so unrelated callbacks cannot
// claim the one-shot before the validation read starts.
deferNextChannelsRead();
void invalidateChannels();
});
await page.waitForFunction(
Expand All @@ -537,17 +558,24 @@ test.describe("community rail", () => {
}
).__BUZZ_E2E_CHANNELS_READ_PENDING__ === 1,
);
await page.evaluate(async () => {
const invoke = (
window as Window & {
__BUZZ_E2E_INVOKE_MOCK_COMMAND__?: (
command: string,
) => Promise<unknown>;
expect(
await page.evaluate(async () => {
const invoke = (
window as Window & {
__BUZZ_E2E_INVOKE_MOCK_COMMAND__?: (
command: string,
) => Promise<unknown>;
}
).__BUZZ_E2E_INVOKE_MOCK_COMMAND__;
if (!invoke) throw new Error("missing mock command seam");
try {
await invoke("get_channels");
return null;
} catch (error) {
return error instanceof Error ? error.message : String(error);
}
).__BUZZ_E2E_INVOKE_MOCK_COMMAND__;
if (!invoke) throw new Error("missing mock command seam");
await invoke("get_channels");
});
}),
).toBe("temporary channel read failure");
expect(
await page.evaluate(
() =>
Expand All @@ -571,36 +599,6 @@ test.describe("community rail", () => {
)
.toEqual({ kind: "channel", channelId: "general" });

await page.evaluate(() => {
const config = (
window as Window & {
__BUZZ_E2E__?: {
mock?: { channelsReadErrors?: (string | null)[] };
};
}
).__BUZZ_E2E__;
if (!config) throw new Error("missing E2E config");
config.mock = {
...config.mock,
channelsReadErrors: ["temporary channel read failure"],
};
});
await expect
.poll(() =>
page.evaluate(
() => window.__BUZZ_E2E__?.mock?.channelsReadErrors?.length ?? 0,
),
)
.toBe(1);
await page.evaluate(() => {
const deferNext = (
window as Window & {
__BUZZ_E2E_DEFER_NEXT_CHANNELS_READ__?: () => void;
}
).__BUZZ_E2E_DEFER_NEXT_CHANNELS_READ__;
if (!deferNext) throw new Error("missing channel-read defer seam");
deferNext();
});
const released = await page.evaluate(
() =>
(
Expand All @@ -610,20 +608,28 @@ test.describe("community rail", () => {
).__BUZZ_E2E_RELEASE_CHANNELS_READ__?.() ?? 0,
);
expect(released).toBe(1);
await page.evaluate(async () => {
const testWindow = window as Window & {
__BUZZ_E2E_INVOKE_MOCK_COMMAND__?: (
command: string,
) => Promise<unknown>;
__BUZZ_E2E_CHANNELS_READ_PENDING__?: number;
};
const invoke = testWindow.__BUZZ_E2E_INVOKE_MOCK_COMMAND__;
if (!invoke) throw new Error("missing mock command seam");
await invoke("get_channels");
if (testWindow.__BUZZ_E2E_CHANNELS_READ_PENDING__ !== 0) {
throw new Error("release left the channel-read latch armed");
}
});
expect(
await page.evaluate(async () => {
const testWindow = window as Window & {
__BUZZ_E2E_INVOKE_MOCK_COMMAND__?: (
command: string,
) => Promise<unknown>;
__BUZZ_E2E_CHANNELS_READ_PENDING__?: number;
};
const invoke = testWindow.__BUZZ_E2E_INVOKE_MOCK_COMMAND__;
if (!invoke) throw new Error("missing mock command seam");
let message: string | null = null;
try {
await invoke("get_channels");
} catch (error) {
message = error instanceof Error ? error.message : String(error);
}
return {
message,
pending: testWindow.__BUZZ_E2E_CHANNELS_READ_PENDING__,
};
}),
).toEqual({ message: "temporary channel read failure", pending: 0 });
await expect
.poll(() =>
page.evaluate(
Expand All @@ -642,6 +648,32 @@ test.describe("community rail", () => {
}, COMMUNITY_B.id),
)
.toEqual({ kind: "channel", channelId: "general" });
await page.evaluate(async () => {
const testWindow = window as Window & {
__BUZZ_E2E__?: {
mock?: { channelsReadError?: string };
};
__BUZZ_E2E_INVALIDATE_CHANNELS__?: () => Promise<void>;
};
const config = testWindow.__BUZZ_E2E__;
const invalidateChannels = testWindow.__BUZZ_E2E_INVALIDATE_CHANNELS__;
if (!config?.mock) throw new Error("missing E2E mock config");
if (!invalidateChannels) {
throw new Error("missing channel invalidation seam");
}
config.mock.channelsReadError = undefined;
await invalidateChannels();
});
await expect
.poll(() =>
page.evaluate((communityId) => {
const raw = window.localStorage.getItem(
"buzz-community-destinations",
);
return raw ? JSON.parse(raw)[communityId] : null;
}, COMMUNITY_B.id),
)
.toEqual({ kind: "home" });
});

test("does not restore a remembered destination on cold boot", async ({
Expand Down
Loading