feat(web): persist workspace presentation across browser hosts — #205

This commit is contained in:
2026-08-15 01:24:23 +02:00
parent 723e2987ef
commit 3b8ce66e33
6 changed files with 542 additions and 13 deletions

View File

@ -0,0 +1,62 @@
import { afterEach, describe, expect, it, vi } from "vitest";
import { createWebWorkspaceSyncBus } from "./webWorkspaceSync";
class FakeBroadcastChannel {
static channels = new Map<string, Set<FakeBroadcastChannel>>();
onmessage: ((event: MessageEvent) => void) | null = null;
constructor(private readonly name: string) {
const peers = FakeBroadcastChannel.channels.get(name) ?? new Set();
peers.add(this);
FakeBroadcastChannel.channels.set(name, peers);
}
postMessage(data: unknown): void {
for (const peer of FakeBroadcastChannel.channels.get(this.name) ?? []) {
if (peer !== this) {
peer.onmessage?.({ data } as MessageEvent);
}
}
}
close(): void {
FakeBroadcastChannel.channels.get(this.name)?.delete(this);
}
}
afterEach(() => {
vi.unstubAllGlobals();
FakeBroadcastChannel.channels.clear();
});
describe("web workspace sync bus (#205)", () => {
it("broadcasts focus changes to other browser hosts, not itself", () => {
vi.stubGlobal("BroadcastChannel", FakeBroadcastChannel);
const first = createWebWorkspaceSyncBus();
const second = createWebWorkspaceSyncBus();
const firstHandler = vi.fn();
const secondHandler = vi.fn();
first.subscribe(firstHandler);
second.subscribe(secondHandler);
first.publish({
type: "focus",
project: { id: "p-1", name: "Demo", root: "/srv/demo" },
activePanel: "tickets",
});
expect(firstHandler).not.toHaveBeenCalled();
expect(secondHandler).toHaveBeenCalledWith(
expect.objectContaining({
type: "focus",
sourceId: first.sourceId,
project: { id: "p-1", name: "Demo", root: "/srv/demo" },
activePanel: "tickets",
}),
);
first.close();
second.close();
});
});