gestion_table_tournante_libre/src/interface/appartenances.test.js
Mathieu Benoit 4d34039f47 [ADD] interface: visual foundations, pills, cards, icons, navy header
Screens need colour that separates information without ever carrying it
alone. Tokens gain state, membership and status tints whose marks hold
3:1 and texts 4.5:1 in both themes and under three dichromacies, and
every hue stays 30 degrees from the reserved conflict hues. Commands get
three levels; Pastille, Carte and an icon module join the primitives;
memberships map to six stable tints over sorted names. A pill label
wraps and never truncates, and keeps one line beside a nowrap neighbour.
Checked: node 3949; browser 767, only the walkthrough captures differ.

--- FR ---

[ADD] interface : fondations, pastilles, cartes, icônes, bandeau marine

Les écrans ont besoin d'une couleur qui distingue l'information sans
jamais la porter seule. Les jetons gagnent des teintes d'état,
d'appartenance et de statut dont les marques tiennent 3:1 et les textes
4,5:1 dans les deux thèmes et sous trois dichromaties, chaque teinte à
30 degrés des teintes de conflit réservées. Les commandes ont trois
niveaux ; Pastille, Carte et un module d'icônes rejoignent les
primitives ; les appartenances reçoivent six teintes stables sur les
noms triés. Un libellé de pastille se replie, jamais tronqué.
Vérifié : node 3949 ; navigateur 767, seules les captures du parcours.

Assisted-by: Claude Opus 5.5
2026-10-10 22:59:59 -04:00

73 lines
3.6 KiB
JavaScript
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

// © 2026 TechnoLibre (http://www.technolibre.ca)
// License AGPL-3.0 or later (https://www.gnu.org/licenses/agpl)
// L'attribution des teintes d'appartenance des listes : les appartenances
// distinctes de l'événement, rangées par le comparateur de noms du projet,
// reçoivent les rangs 1 à RANGS_APPARTENANCE dans l'ordre ; au-delà, null,
// la pastille neutre. L'attribution se calcule sur toute la liste, exclues
// comprises : un filtre, une exclusion ou l'ordre des lignes ne repeint
// rien. Les noms sont inventés.
import assert from 'node:assert/strict';
import { describe, test } from '../../test/lanceur.js';
import { RANGS_APPARTENANCE, teintesAppartenances } from './appartenances.js';
// Des participants d'épreuve, un par appartenance donnée, dans l'ordre reçu.
const personnes = (...appartenances) =>
appartenances.map((appartenance, rang) => ({ id: rang + 1, nom: `Personne ${rang + 1}`, appartenance, exclu: false }));
describe('teintesAppartenances', () => {
test('six rangs au plus', () => {
assert.equal(RANGS_APPARTENANCE, 6);
});
test('les appartenances distinctes, rangées par leur clé normalisée, reçoivent 1, 2, 3 ; une Map dans cet ordre', () => {
const teintes = teintesAppartenances(personnes('Ordre des Pluviers', 'Cercle des Aulnes', 'ordre des pluviers', 'Cercle des Aulnes', 'Bastion des Ifs'));
assert.ok(teintes instanceof Map);
assert.deepEqual([...teintes], [
['Bastion des Ifs', 1],
['Cercle des Aulnes', 2],
['Ordre des Pluviers', 3],
['ordre des pluviers', 4],
]);
});
test('casse et accents mis à part, l’ordre est celui de la clé ; deux orthographes de même clé se départagent par leurs points de code', () => {
const teintes = teintesAppartenances(personnes('Écluse des Saules', 'echo des Noues', 'Ecluse des Saules', 'Dune'));
assert.deepEqual([...teintes.keys()], ['Dune', 'echo des Noues', 'Ecluse des Saules', 'Écluse des Saules']);
});
test('au-delà de six appartenances, null ; une personne sans appartenance ne compte pas', () => {
const noms = ['Hêtraie', 'Gué', 'Futaie', 'Estran', 'Dolmen', 'Combe', 'Bocage', 'Aber'];
const teintes = teintesAppartenances(personnes(...noms, null, null));
assert.deepEqual([...teintes], [
['Aber', 1],
['Bocage', 2],
['Combe', 3],
['Dolmen', 4],
['Estran', 5],
['Futaie', 6],
['Gué', null],
['Hêtraie', null],
]);
assert.equal(teintes.has(null), false);
});
test('l’ordre des lignes, un filtre ou une exclusion ne changent aucun rang : l’attribution lit toute la liste', () => {
const liste = personnes('Combe', 'Aber', 'Bocage', 'Aber');
const reference = teintesAppartenances(liste);
assert.deepEqual([...teintesAppartenances([...liste].reverse())], [...reference]);
const exclue = liste.map((p) => (p.appartenance === 'Aber' ? { ...p, exclu: true } : p));
assert.deepEqual([...teintesAppartenances(exclue)], [...reference]);
});
test('une liste vide rend une Map vide ; la liste reçue n’est pas modifiée', () => {
assert.equal(teintesAppartenances([]).size, 0);
const liste = Object.freeze(personnes('Combe', 'Aber').map((p) => Object.freeze(p)));
assert.deepEqual([...teintesAppartenances(liste)], [['Aber', 1], ['Combe', 2]]);
});
test('une appartenance ni chaîne ni null lève TypeError en nommant la personne', () => {
assert.throws(() => teintesAppartenances([{ id: 7, appartenance: 12 }]), (erreur) => erreur instanceof TypeError && /7/.test(erreur.message));
assert.throws(() => teintesAppartenances([{ id: 8 }]), TypeError);
});
});