fix: skribbl pre-pick label + gartic min 3 players + tests

This commit is contained in:
PM
2026-05-01 20:27:41 +00:00
parent 2a40097fad
commit 5c55e33710
20 changed files with 943 additions and 3 deletions
+148
View File
@@ -0,0 +1,148 @@
// E2E socket flow test for SKRIBBL mode.
const { io } = require("socket.io-client");
const URL = "http://localhost:3000";
const TIMEOUT_MS = 25000;
const results = [];
let exitCode = 0;
function pass(name) { results.push({ name, status: "pass" }); console.log("PASS:", name); }
function fail(name, err) { results.push({ name, status: "fail", error: String(err) }); console.log("FAIL:", name, "-", err); exitCode = 1; }
const wait = (ms) => new Promise(r => setTimeout(r, ms));
function waitFor(socket, event, predicate = () => true, timeout = 8000) {
return new Promise((resolve, reject) => {
const t = setTimeout(() => reject(new Error(`timeout waiting for ${event}`)), timeout);
const handler = (data) => {
if (predicate(data)) {
clearTimeout(t);
socket.off(event, handler);
resolve(data);
}
};
socket.on(event, handler);
});
}
function emitAck(socket, event, payload) {
return new Promise((resolve, reject) => {
const t = setTimeout(() => reject(new Error(`ack timeout for ${event}`)), 5000);
socket.emit(event, payload, (resp) => {
clearTimeout(t);
resolve(resp);
});
});
}
const killer = setTimeout(() => {
console.log("FAIL: Global timeout reached");
process.exit(1);
}, TIMEOUT_MS);
killer.unref();
(async () => {
let a, b;
try {
a = io(URL, { transports: ["websocket"], forceNew: true });
b = io(URL, { transports: ["websocket"], forceNew: true });
await Promise.all([
new Promise((r) => a.on("connect", r)),
new Promise((r) => b.on("connect", r)),
]);
pass("clients A and B connected");
// 1. A creates a skribbl room
const createResp = await emitAck(a, "room:create", {
nickname: "Alice", avatar: "🐱", mode: "skribbl",
settings: { rounds: 2, drawTimeSec: 60, language: "en" },
});
if (!createResp || !createResp.ok || !createResp.code) throw new Error("create failed: " + JSON.stringify(createResp));
const code = createResp.code;
pass("A: room:create -> ok, code=" + code);
// capture state from B's join
const aStateP = waitFor(a, "room:state", (s) => s.players.length >= 2, 5000);
const bStateP = waitFor(b, "room:state", (s) => s.players.length >= 2, 5000);
// 2. B joins
const joinResp = await emitAck(b, "room:join", { code, nickname: "Bob", avatar: "🐶" });
if (!joinResp || !joinResp.ok) throw new Error("join failed: " + JSON.stringify(joinResp));
pass("B: room:join -> ok");
// 3. Both clients receive room:state with both players
const aState = await aStateP;
const bState = await bStateP;
const aHasBoth = aState.players.find(p => p.nickname === "Alice") && aState.players.find(p => p.nickname === "Bob");
const bHasBoth = bState.players.find(p => p.nickname === "Alice") && bState.players.find(p => p.nickname === "Bob");
if (!aHasBoth || !bHasBoth) throw new Error("room:state missing players");
pass("Both clients receive room:state with both players");
// 4. Chat send/receive
const aChatP = waitFor(a, "chat:msg", (m) => m.text === "hello" && m.fromName === "Alice", 5000);
const bChatP = waitFor(b, "chat:msg", (m) => m.text === "hello" && m.fromName === "Alice", 5000);
a.emit("chat:send", { text: "hello" });
await Promise.all([aChatP, bChatP]);
pass("chat:send broadcasts to both clients");
// 5. Start the game
// Drawer (A is host & first in order) gets skribbl:wordChoices
const choicesP = waitFor(a, "skribbl:wordChoices", () => true, 5000);
const startResp = await emitAck(a, "game:start", {});
if (!startResp || !startResp.ok) throw new Error("start failed: " + JSON.stringify(startResp));
pass("game:start ack ok");
const choices = await choicesP;
if (!Array.isArray(choices) || choices.length < 1) throw new Error("invalid choices");
pass("drawer received skribbl:wordChoices: " + JSON.stringify(choices));
// 6. Drawer picks a word
const pickedWord = choices[0];
// Listen for roundStart on B
const bRoundStartP = waitFor(b, "skribbl:roundStart", () => true, 5000);
const aRoundStartP = waitFor(a, "skribbl:roundStart", () => true, 5000);
const pickResp = await emitAck(a, "skribbl:pickWord", { word: pickedWord });
if (!pickResp || !pickResp.ok) throw new Error("pickWord failed: " + JSON.stringify(pickResp));
pass("skribbl:pickWord ack ok, word=" + pickedWord);
const aRound = await aRoundStartP;
const bRound = await bRoundStartP;
if (!aRound || !bRound) throw new Error("roundStart missing");
pass("both clients received skribbl:roundStart");
// 7. Drawer emits strokes — other client receives them
const strokeP = waitFor(b, "skribbl:stroke", (s) => s && s.color === "#000", 3000);
a.emit("skribbl:stroke", { x: 10, y: 10, prevX: 5, prevY: 5, color: "#000", size: 4 });
a.emit("skribbl:stroke", { x: 20, y: 20, prevX: 10, prevY: 10, color: "#000", size: 4 });
const strokeReceived = await strokeP;
if (!strokeReceived) throw new Error("no stroke received on B");
pass("B received skribbl:stroke from A");
// 8. B guesses correctly
const aCorrectP = waitFor(a, "chat:msg", (m) => m.kind === "correct", 5000);
const bCorrectP = waitFor(b, "chat:msg", (m) => m.kind === "correct", 5000);
b.emit("chat:send", { text: pickedWord });
await Promise.all([aCorrectP, bCorrectP]);
pass("correct guess produces 'correct' chat message on both clients");
// verify state shows score updated
const stateAfterP = waitFor(a, "room:state", (s) => {
const bob = s.players.find(p => p.nickname === "Bob");
return bob && bob.score > 0;
}, 5000);
const stateAfter = await stateAfterP;
pass("room:state shows Bob score > 0 after correct guess: " + stateAfter.players.find(p=>p.nickname==="Bob").score);
// 9. Wait for round end
const roundEndP = waitFor(b, "skribbl:roundEnd", () => true, 8000);
const roundEnd = await roundEndP;
if (!roundEnd || !roundEnd.word) throw new Error("roundEnd missing word");
pass("skribbl:roundEnd fired, word=" + roundEnd.word);
} catch (e) {
fail("flow", e && e.message ? e.message : String(e));
} finally {
try { a && a.disconnect(); } catch (_) {}
try { b && b.disconnect(); } catch (_) {}
clearTimeout(killer);
// Print summary as JSON for parser
console.log("__RESULTS__ " + JSON.stringify(results));
setTimeout(() => process.exit(exitCode), 200);
}
})();