// Tests des liens de position (app/src/domain/game/position-link.ts, ADR // 0013) sur le vrai moteur WebAssembly : Match ID écrit puis relu, videau // vu du joueur au trait, identifiants illisibles. // // Copyright (C) 2026 les auteurs d'Ustagammon. // Licence : GPLv3 ou ultérieure (voir LICENSE). import assert from "node:assert/strict"; import { test } from "node:test"; import type { EngineCube } from "../src/domain/game/engine.js"; import { Game } from "../src/domain/game/game.js"; import { START_POSITION } from "../src/domain/game/position.js"; import { linkOf, readPosition, setupOf, UnreadableLinkError, } from "../src/domain/game/position-link.js"; import { fixedDice } from "../src/domain/game/roll.js"; import { parseRoute, routeHash } from "../src/ui/router.js"; import { loadNodeEngine } from "./support/node-engine.js"; test("le moteur relit le Match ID qu'il écrit, pour chaque état du videau", async () => { const engine = await loadNodeEngine(); const cases: [EngineCube, -1 | 0 | 1][] = [ [{ value: 1, owner: "centered" }, -1], [{ value: 2, owner: "onRoll" }, 0], [{ value: 4, owner: "opponent" }, 1], ]; for (const [cube, owner] of cases) { const id = await engine.encodeMatch(cube); assert.equal(id.length, 12); const match = await engine.decodeMatch(id); assert.equal(match.matchLength, 0); assert.equal(match.playerOnRoll, 0); assert.equal(match.cubeValue, cube.value); assert.equal(match.cubeOwner, owner); } }); test("sans Match ID : partie libre, videau au centre", async () => { const engine = await loadNodeEngine(); const shared = await readPosition(engine, { position: START_POSITION, matchId: null }); assert.deepEqual(shared.cube, { value: 1, owner: null }); assert.equal(shared.match, null); assert.equal(shared.result, null); assert.equal(shared.sides.player.pipCount, 167); assert.equal(shared.sides.computer.pipCount, 167); assert.deepEqual(setupOf(shared), { position: START_POSITION, firstTurn: "player", cube: { value: 1, owner: null }, }); }); test("des identifiants illisibles donnent une erreur claire", async () => { const engine = await loadNodeEngine(); for (const link of [ { position: "abc", matchId: null }, { position: "4HPwATDgc/ABM*", matchId: null }, { position: START_POSITION, matchId: "pas un id" }, { position: START_POSITION, matchId: "" }, ]) { await assert.rejects(readPosition(engine, link), UnreadableLinkError, JSON.stringify(link)); } }); test("le lien d'une partie rouvre sa position, son videau compris, par l'adresse", async () => { const engine = await loadNodeEngine(); // Videau chez l'ordinateur, à 2 : une partie commencée ainsi. const game = Game.start(engine, { dice: fixedDice([3, 1]), setup: { position: "jGfkASjg8wcBMA", firstTurn: "player", cube: { value: 2, owner: "computer" }, }, }); const link = await linkOf(engine, game.state); const route = parseRoute( routeHash({ screen: "position", position: link.position, match: link.matchId }), ); assert.ok(route.screen === "position"); const shared = await readPosition(engine, { position: route.position, matchId: route.match }); assert.deepEqual(shared.sides, await game.displayedBoard()); assert.deepEqual(shared.cube, { value: 2, owner: "computer" }); }); test("le lien pris pendant le tour de l'ordinateur le met au trait, en bas", async () => { const engine = await loadNodeEngine(); // Jet d'ouverture 1-6 : l'ordinateur commence. const game = Game.start(engine, { dice: fixedDice([1, 6]) }); assert.equal(game.state.turn, "computer"); const link = await linkOf(engine, game.state); assert.equal(link.position, game.state.position); const shared = await readPosition(engine, link); const board = await engine.board(game.state.position, { value: 1, owner: "centered" }); assert.deepEqual(shared.sides, { player: board.onRoll, computer: board.opponent }); });