Files
skribbl-gartic-color/test/socket-color.js
T

101 lines
4.1 KiB
JavaScript

// Color Together mode socket test.
const { io } = require("socket.io-client");
const URL = "http://localhost:3000";
const TIMEOUT_MS = 20000;
const results = [];
let exitCode = 0;
const pass = (n) => { results.push({ name: n, status: "pass" }); console.log("PASS:", n); };
const fail = (n, e) => { results.push({ name: n, status: "fail", error: String(e) }); console.log("FAIL:", n, "-", e); exitCode = 1; };
function waitFor(socket, event, predicate = () => true, timeout = 6000) {
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(s, event, payload) {
return new Promise((resolve, reject) => {
const t = setTimeout(() => reject(new Error(`ack timeout for ${event}`)), 5000);
s.emit(event, payload, (resp) => { clearTimeout(t); resolve(resp); });
});
}
const killer = setTimeout(() => { console.log("FAIL: Global timeout"); process.exit(1); }, TIMEOUT_MS);
killer.unref();
(async () => {
let a, b, c;
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("color: clients A,B connected");
const create = await emitAck(a, "room:create", {
nickname: "Alice", avatar: "🐱", mode: "color",
settings: { canvasType: "blank" },
});
if (!create || !create.ok) throw new Error("create failed: " + JSON.stringify(create));
const code = create.code;
pass("color: room created, code=" + code);
const j = await emitAck(b, "room:join", { code, nickname: "Bob", avatar: "🐶" });
if (!j || !j.ok) throw new Error("join failed");
pass("color: B joined");
// listen FIRST, then trigger
const stateAfterStartP = waitFor(a, "room:state", (s) => s.color !== null && s.phase === "playing", 4000);
const start = await emitAck(a, "game:start", {});
if (!start || !start.ok) throw new Error("game:start failed: " + JSON.stringify(start));
pass("color: game:start ok");
const stateAfterStart = await stateAfterStartP;
pass("color: room transitioned to playing with color state");
// A emits color:stroke; B should receive color:strokeBroadcast
const strokePayload = {
points: [{ x: 10, y: 10 }, { x: 20, y: 20 }],
color: "#FF0000",
size: 5,
tool: "brush",
};
const bRecv = waitFor(b, "color:strokeBroadcast", (d) => d && d.color === "#FF0000", 4000);
a.emit("color:stroke", strokePayload);
const got = await bRecv;
if (!got || !Array.isArray(got.points) || got.points.length !== 2) throw new Error("stroke broadcast malformed: " + JSON.stringify(got));
pass("color: B received color:strokeBroadcast with same payload");
// C joins mid-game and should receive color:state with the existing stroke
c = io(URL, { transports: ["websocket"], forceNew: true });
await new Promise((r) => c.on("connect", r));
const cStateP = waitFor(c, "color:state", (s) => s && Array.isArray(s.strokes), 5000);
const j2 = await emitAck(c, "room:join", { code, nickname: "Carol", avatar: "🦊" });
if (!j2 || !j2.ok) throw new Error("C join failed");
pass("color: C joined mid-game");
const cState = await cStateP;
if (!cState.strokes || cState.strokes.length < 1) throw new Error("color:state to new joiner missing strokes: " + JSON.stringify(cState));
pass("color: new joiner C received color:state with " + cState.strokes.length + " stroke(s)");
} catch (e) {
fail("color flow", e && e.message ? e.message : String(e));
} finally {
try { a && a.disconnect(); } catch (_) {}
try { b && b.disconnect(); } catch (_) {}
try { c && c.disconnect(); } catch (_) {}
clearTimeout(killer);
console.log("__RESULTS__ " + JSON.stringify(results));
setTimeout(() => process.exit(exitCode), 200);
}
})();