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
73 lines
3.6 KiB
JavaScript
73 lines
3.6 KiB
JavaScript
// © 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);
|
||
});
|
||
});
|