Files
Ustagammon/app/tests/router.test.ts
T

157 lines
5.9 KiB
TypeScript

// 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" }],
["#/parties/mh2k3x-a1b2c3/bilan", { screen: "summary", game: "mh2k3x-a1b2c3" }],
["#/parties/mh2k3x-a1b2c3/analyse", { screen: "analysis", game: "mh2k3x-a1b2c3", turn: null }],
["#/parties/mh2k3x-a1b2c3/analyse/7", { screen: "analysis", game: "mh2k3x-a1b2c3", turn: 7 }],
["#/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",
"#/parties/ab/autre",
"#/parties/ab/bilan/2",
"#/parties/ab/analyse/0",
"#/parties/ab/analyse/x",
"#/parties/ab/analyse/3/plus",
"#/parties/AB/bilan",
]) {
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);
});