fix: skribbl pre-pick label + gartic min 3 players + tests
This commit is contained in:
@@ -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);
|
||||
}
|
||||
})();
|
||||
Reference in New Issue
Block a user