gestion_table_tournante_libre/src/interface/etat_ui.test.js

154 lines
5.5 KiB
JavaScript
Raw Normal View History

[ADD] interface: application shell — banner, sections, theme, about The window showed only a banner; it now holds the application. App mounts the session, the facade and the generation, settles the working folder and lists its events before it reports ready, re-reading the list once a question is answered or a refused determination succeeds. The banner carries version, mode and the event list command; an open event shows its sections in their first form; the theme follows the system at each start and the operator's choice lasts the session; À propos names the build. Errors are collected for a report, and Electron removes the session's locks at exit. Checked: 2189 node, 193 browser, 60 node-long; the Electron start check. --- FR --- [ADD] interface : coquille — bandeau, sections, thème, À propos La fenêtre ne montrait qu'un bandeau ; elle tient désormais l'application. App monte la séance, la façade et la génération, règle le dossier de travail et liste ses événements avant de se dire prête, et relit la liste une fois une question répondue ou une détermination refusée réussie. Le bandeau porte version, mode et la commande de la liste ; un événement ouvert montre ses sections dans leur première forme ; le thème suit le système à chaque démarrage et le choix de l'opérateur vaut pour la séance ; À propos nomme la construction. Les erreurs se collectent pour un signalement, et Electron efface à la sortie les verrous de la séance. Vérifié : 2189 node, 193 navigateur, 60 node-long ; démarrage d'Electron. Assisted-by: Claude Opus 5.5
2026-10-07 07:22:05 -04:00
// © 2026 TechnoLibre (http://www.technolibre.ca)
// License AGPL-3.0 or later (https://www.gnu.org/licenses/agpl)
// L'état de navigation de l'interface : chaque transition, le contrat des
// magasins de Svelte, et l'absence de toute écriture — l'état ne connaît pas
// la séance, et regarder n'écrit jamais (§ 8.2, § 8.4).
import assert from 'node:assert/strict';
import { readFileSync } from 'node:fs';
import { describe, test } from '../../test/lanceur.js';
import { creerEtatUi } from './etat_ui.js';
import { SECTION_OUVERTURE, SECTIONS } from './evenement/sections.js';
const DEPART = {
section: SECTION_OUVERTURE,
apropos: false,
tour: 1,
miseEnEvidence: null,
proposition: null,
importEnAttente: null,
listes: true,
pleinEcran: false,
selection: null,
};
// Un état et le relevé de ce que reçoit son abonné.
function suivi() {
const ui = creerEtatUi();
const recus = [];
const desabonner = ui.subscribe((etat) => recus.push(etat));
return { ui, recus, desabonner };
}
describe('état de navigation : départ et magasin', () => {
test('le départ : la section d’ouverture, le tour 1, rien de choisi, les listes montrées', () => {
assert.deepEqual(creerEtatUi().etat(), DEPART);
});
test('subscribe rend l’état tout de suite, puis chaque nouvel état, figé ; désabonné, plus rien', () => {
const { ui, recus, desabonner } = suivi();
assert.deepEqual(recus, [DEPART]);
ui.allerA('tables');
assert.equal(recus.length, 2);
assert.equal(recus[1], ui.etat());
assert.ok(Object.isFrozen(recus[1]));
assert.throws(() => {
recus[1].section = 'plan';
}, TypeError);
desabonner();
ui.allerA('plan');
assert.equal(recus.length, 2);
});
test('l’état ne reçoit rien à sa création : il ne connaît ni la séance, ni la façade, ni un système de fichiers', () => {
assert.equal(creerEtatUi.length, 0);
const source = readFileSync(new URL('./etat_ui.js', import.meta.url), 'utf8');
// Le dernier mot de chaque ligne d'import : le module qu'elle charge.
const imports = source
.split('\n')
.filter((ligne) => ligne.startsWith('import '))
.map((ligne) => ligne.slice(ligne.lastIndexOf(' ') + 1));
assert.deepEqual(imports, ["'./evenement/sections.js';"]);
});
});
describe('état de navigation : chaque transition', () => {
test('allerA mène à chaque section ; hors de SECTIONS, RangeError sans rien changer', () => {
const ui = creerEtatUi();
assert.ok(SECTIONS.length > 0);
for (const section of SECTIONS) {
ui.allerA(section);
assert.deepEqual(ui.etat(), { ...DEPART, section });
}
const avant = ui.etat();
for (const faute of ['accueil', 'apropos', '', undefined, 'Plan', 'constructor']) {
assert.throws(() => ui.allerA(faute), RangeError, String(faute));
}
assert.equal(ui.etat(), avant);
});
test('reinitialiser rend l’état de départ, le panneau « À propos » excepté', () => {
const ui = creerEtatUi();
ui.allerA('generation');
ui.montrerTour(3);
ui.mettreEnEvidence(7);
ui.montrerProposition(2);
ui.ouvrirImport({ texte: 'nom\nOmbrelle', origine: 'liste.csv' });
ui.basculerListes();
ui.basculerPleinEcran();
ui.selectionner({ table: 1, siege: 2 });
ui.ouvrirApropos();
ui.reinitialiser();
assert.deepEqual(ui.etat(), { ...DEPART, apropos: true });
assert.equal(ui.etat().section, SECTION_OUVERTURE);
});
test('ouvrirApropos et fermerApropos', () => {
const ui = creerEtatUi();
ui.ouvrirApropos();
assert.deepEqual(ui.etat(), { ...DEPART, apropos: true });
ui.fermerApropos();
assert.deepEqual(ui.etat(), DEPART);
});
test('montrerTour prend un entier positif ; sinon RangeError', () => {
const ui = creerEtatUi();
ui.montrerTour(4);
assert.equal(ui.etat().tour, 4);
for (const faute of [0, -1, 1.5, Number.NaN, '2', null]) {
assert.throws(() => ui.montrerTour(faute), RangeError, String(faute));
}
assert.equal(ui.etat().tour, 4);
});
test('mettreEnEvidence, montrerProposition et selectionner posent puis retirent leur choix', () => {
const ui = creerEtatUi();
ui.mettreEnEvidence(12);
ui.montrerProposition(3);
ui.selectionner({ table: 2, siege: 1 });
assert.deepEqual(ui.etat(), { ...DEPART, miseEnEvidence: 12, proposition: 3, selection: { table: 2, siege: 1 } });
ui.mettreEnEvidence(null);
ui.montrerProposition(null);
ui.selectionner(null);
assert.deepEqual(ui.etat(), DEPART);
});
test('ouvrirImport garde le texte en attente et mène à la section import ; prendreImport le rend une fois', () => {
const ui = creerEtatUi();
const enAttente = { texte: 'nom;prénom\nPervenche;Théo', origine: 'invites.csv' };
ui.ouvrirImport(enAttente);
assert.deepEqual(ui.etat(), { ...DEPART, section: 'import', importEnAttente: enAttente });
assert.deepEqual(ui.prendreImport(), enAttente);
assert.equal(ui.etat().importEnAttente, null);
assert.equal(ui.prendreImport(), null);
});
test('basculerListes et basculerPleinEcran alternent', () => {
const ui = creerEtatUi();
ui.basculerListes();
ui.basculerPleinEcran();
assert.deepEqual(ui.etat(), { ...DEPART, listes: false, pleinEcran: true });
ui.basculerListes();
ui.basculerPleinEcran();
assert.deepEqual(ui.etat(), DEPART);
});
test('une transition qui ne change rien ne publie pas d’état neuf', () => {
const { ui, recus } = suivi();
ui.allerA(SECTION_OUVERTURE);
ui.fermerApropos();
ui.mettreEnEvidence(null);
assert.equal(recus.length, 1);
});
});