149 lines
6.4 KiB
JavaScript
149 lines
6.4 KiB
JavaScript
// 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);
|
|
}
|
|
})();
|