/// /** * Multi-user pattern test for the battleship multiplayer lobby. * * Unlike lobby.test.tsx (one runtime driving both slots from a single * identity), this runs ONE shared lobby instance across two worker-isolated * runtimes — so the PerUser slot assignment (`myName` / `myPlayerNumber`) * and cross-runtime propagation of the shared match state are actually * exercised: two real users land in two different slots. * * Joins go through the programmatic `joinWithName` stream (the headless seam * kept by the profile migration); the profile-wish UI path needs a browser. */ import { action, computed, multiUserTest, pattern } from "commonfabric"; import BattleshipLobby, { type LobbyOutput } from "./lobby.tsx"; interface Setup { lobby: LobbyOutput; } export const setup = pattern(() => ({ lobby: BattleshipLobby({}), })); export const alice = pattern<{ setup: Setup }>(({ setup }) => { const lobby = setup.lobby; const action_join = action(() => { lobby.joinWithName.send("Alice"); }); const assert_joined_slot_1 = computed(() => lobby.myName === "Alice" && lobby.myPlayerNumber === 1 && lobby.player1?.name === "Alice" ); const assert_game_started = computed(() => lobby.player2?.name === "Bob" && lobby.gameState.phase === "playing" && lobby.gameState.currentTurn === 1 ); // Bob joining must not clobber Alice's PerUser slot assignment. const assert_slot_unchanged = computed(() => lobby.myName === "Alice" && lobby.myPlayerNumber === 1 ); return { tests: [ { action: action_join }, { assertion: assert_joined_slot_1 }, { label: "alice-joined" }, { await: "bob-joined" }, { assertion: assert_game_started }, { assertion: assert_slot_unchanged }, ], }; }); export const bob = pattern<{ setup: Setup }>(({ setup }) => { const lobby = setup.lobby; const action_join = action(() => { lobby.joinWithName.send("Bob"); }); // Shared slot propagated from Alice's runtime. const assert_sees_alice = computed(() => lobby.player1?.name === "Alice"); // PerUser isolation: Alice's join must not leak into Bob's identity cells // (unwritten PerUser cells may read as undefined in a fresh runtime). const assert_not_joined_yet = computed(() => (lobby.myName ?? "") === "" && (lobby.myPlayerNumber ?? null) === null ); const assert_joined_slot_2 = computed(() => lobby.myName === "Bob" && lobby.myPlayerNumber === 2 && lobby.player2?.name === "Bob" ); const assert_game_started = computed(() => lobby.gameState.phase === "playing" && lobby.gameState.currentTurn === 1 ); return { tests: [ { await: "alice-joined" }, { assertion: assert_sees_alice }, { assertion: assert_not_joined_yet }, { action: action_join }, { assertion: assert_joined_slot_2 }, { assertion: assert_game_started }, { label: "bob-joined" }, ], }; }); export default multiUserTest({ setup, participants: { alice, bob } });