// Tests du routeur (app/src/ui/router.ts) : chaque adresse du tableau de // l'ADR 0013 (amendement du 2026-11-03) mène au bon écran, et chaque écran // redonne son adresse. // // 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 { isHelpScreen, parseRoute, type Route, routeHash, slug, stepsBack, } from "../src/ui/router.js"; const START = "4HPwATDgc/ABMA"; const MATCH = "cAkAAAAAAAAA"; /** Les adresses du tableau de l'ADR, et l'écran de chacune. */ const TABLE: [string, Route][] = [ ["#/", { screen: "game" }], ["#/accueil", { screen: "home" }], ["#/aide", { screen: "help", question: null }], ["#/aide/comment-jouer", { screen: "help", question: "comment-jouer" }], ["#/glossaire", { screen: "glossary", term: null }], ["#/glossaire/videau", { screen: "glossary", term: "videau" }], ["#/a-propos", { screen: "about" }], ["#/tutoriel", { screen: "tutorial", step: null, practice: false }], ["#/tutoriel/3", { screen: "tutorial", step: 3, practice: false }], ["#/tutoriel/3/a-vous", { screen: "tutorial", step: 3, practice: true }], ["#/tutoriel/fin", { screen: "tutorialEnd" }], ["#/installer", { screen: "install" }], ["#/parties", { screen: "games", game: null }], ["#/parties/mh2k3x-a1b2c3", { screen: "games", game: "mh2k3x-a1b2c3" }], ["#/menu", { screen: "menu" }], ["#/nouvelle-partie", { screen: "newGame" }], ["#/explication/12", { screen: "explanation", event: "12" }], [ "#/position/4HPwATDgc%2FABMA/cAkAAAAAAAAA", { screen: "position", position: START, match: MATCH }, ], ["#/position/4HPwATDgc%2FABMA", { screen: "position", position: START, match: null }], ]; test("chaque adresse du tableau de l'ADR 0013 mène à son écran, et inversement", () => { for (const [hash, route] of TABLE) { assert.deepEqual(parseRoute(hash), route, hash); assert.equal(routeHash(route), hash, hash); } }); test("une adresse vide ou « # » est la partie en cours ; une barre finale ne change rien", () => { assert.deepEqual(parseRoute(""), { screen: "game" }); assert.deepEqual(parseRoute("#"), { screen: "game" }); assert.deepEqual(parseRoute("#/aide/"), { screen: "help", question: null }); assert.deepEqual(parseRoute("#/glossaire/blot/"), { screen: "glossary", term: "blot" }); }); test("une adresse inconnue est reconnue comme telle", () => { for (const hash of [ "#/inconnu", "#aide", "#/aide/a/b", "#/aide/Majuscules", "#/menu/plus", "#/tutoriel/0", "#/tutoriel/x", "#/tutoriel/3/b", "#/tutoriel/3/a-vous/x", "#/tutoriel/fin/a-vous", "#/installer/x", "#/a-propos/x", "#/position", "#/position/%E0%A4%A", ]) { assert.equal(parseRoute(hash).screen, "unknown", hash); } assert.equal(routeHash(parseRoute("#/inconnu")), "#/inconnu"); }); test("un lien de position collé sans encodage (+ et / du base64) est compris", () => { assert.deepEqual(parseRoute(`#/position/${START}/${MATCH}`), { screen: "position", position: START, match: MATCH, }); assert.deepEqual(parseRoute("#/position/jGfkASjg8wcBMA/cAkAAAAAAAAA"), { screen: "position", position: "jGfkASjg8wcBMA", match: MATCH, }); // + reste un + (pas une espace), encodé ou non. const plus = "AAAA+AAAAAAAAA"; assert.equal( routeHash({ screen: "position", position: plus, match: null }), "#/position/AAAA%2BAAAAAAAAA", ); assert.deepEqual(parseRoute("#/position/AAAA%2BAAAAAAAAA"), { screen: "position", position: plus, match: null, }); assert.deepEqual(parseRoute(`#/position/${plus}`), { screen: "position", position: plus, match: null, }); }); test("un identifiant de position de mauvaise longueur arrive tel quel au moteur, qui jugera", () => { assert.deepEqual(parseRoute("#/position/abc"), { screen: "position", position: "abc", match: null, }); }); test("l'identifiant d'une question : le titre sans accents ni ponctuation", () => { assert.equal(slug("Jouer un coup, puis l'annuler"), "jouer-un-coup-puis-l-annuler"); assert.equal(slug(" Été : déjà vu ? "), "ete-deja-vu"); assert.equal( slug("Rejouer d'ici sans perdre l'original"), "rejouer-d-ici-sans-perdre-l-original", ); }); test("les écrans de l'aide : jouer, les mots du jeu, à propos", () => { assert.ok(isHelpScreen(parseRoute("#/aide/x"))); assert.ok(isHelpScreen(parseRoute("#/glossaire"))); assert.ok(isHelpScreen(parseRoute("#/a-propos"))); assert.ok(!isHelpScreen(parseRoute("#/menu"))); }); test("quitter un écran revient à la dernière adresse qui n'en est pas", () => { const help = (route: Route) => isHelpScreen(route); // Partie, aide, une question : « Retour » remonte de deux pas. assert.equal(stepsBack(["#/", "#/aide", "#/aide/q"], help), -2); // Le menu ouvert depuis l'aide : on revient à l'aide. const menu = (route: Route) => route.screen === "menu"; assert.equal(stepsBack(["#/", "#/aide", "#/menu"], menu), -1); // Ouvert directement dans un nouvel onglet : rien avant, on remplace. assert.equal(stepsBack(["#/aide/q"], help), null); assert.equal(stepsBack(["#/aide", "#/glossaire/blot"], help), null); });