gestion_table_tournante_libre/src/application/csv.test.js
Mathieu Benoit 320b1b1687 [ADD] tags and goals: engine measures, CSV column, commands, bridge
Tags on people and goals on tables now reach every layer below the
screens. The engine measures rounds served, never-served people and
facilitators met, with a floor per goal mode at the diagnostic; the CSV
reads and writes an etiquettes column through the shared split and
spelling reconciliation; commands set tags, goals, their rule and the
two optional criteria, each refusal with cause and remedy; facilitators
and the stored priority order reach the engine through one bridge.
Without goals or the facilitator case, plans equal the reference prints.
Checked: node 4373, navigateur 885, node-long 113.

--- FR ---

[ADD] étiquettes et objectifs : mesures, colonne CSV, commandes, pont

Les étiquettes des personnes et les objectifs des tables atteignent toutes
les couches sous les écrans. Le moteur mesure les tours servis, les
personnes jamais servies et les animateurs rencontrés, avec un plancher
par mode au diagnostic ; le CSV lit et écrit une colonne d'étiquettes par
la découpe et la réconciliation communes ; des commandes posent étiquettes,
objectifs, règle et les deux critères facultatifs, chaque refus avec cause
et remède ; animateurs et ordre écrit passent au moteur par un seul pont.
Sans objectif ni case des animateurs, les plans égalent les empreintes.
Vérifié : node 4373, navigateur 885, node-long 113.

Assisted-by: Claude Opus 5.5
2026-10-11 15:03:13 -04:00

91 lines
4.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)
// Épreuves du relais CSV de l'application (§ 13.4, § 10.1) : l'interface
// atteint src/csv par ce module, qui réexporte ses fonctions telles quelles
// et lie le motif du CSV des refus au libellé français ; les textes des
// deux refus d'une ligne pour ses étiquettes. Les noms sont inventés.
import assert from 'node:assert/strict';
import { describe, test } from '../../test/lanceur.js';
import * as apercu from '../csv/apercu.js';
import * as colonnes from '../csv/colonnes.js';
import * as encodage from '../csv/encodage.js';
import * as exportCsv from '../csv/export.js';
import * as importCsv from '../csv/import.js';
import { decouper } from '../csv/lecture.js';
import * as normalisation from '../csv/normalisation.js';
import * as relais from './csv.js';
import { libelle } from './libelles.js';
describe('csv : le relais de src/csv pour l’interface (§ 13.4)', () => {
test('decoder, apercevoir, exporterParticipants, bilanRemplacement, cleNormalisee, CHAMPS et JOINTURE_ETIQUETTES sont ceux de src/csv', () => {
const attendus = [
['decoder', encodage.decoder],
['apercevoir', apercu.apercevoir],
['exporterParticipants', exportCsv.exporterParticipants],
['bilanRemplacement', importCsv.bilanRemplacement],
['cleNormalisee', normalisation.cleNormalisee],
];
assert.equal(attendus.length, 5);
for (const [nom, fonction] of attendus) {
assert.equal(typeof fonction, 'function', nom);
assert.equal(relais[nom], fonction, nom);
}
assert.equal(relais.CHAMPS, colonnes.CHAMPS);
assert.equal(relais.JOINTURE_ETIQUETTES, exportCsv.JOINTURE_ETIQUETTES);
assert.equal(relais.JOINTURE_ETIQUETTES, ', ');
assert.deepEqual(Object.keys(relais).sort(), [
'CHAMPS', 'JOINTURE_ETIQUETTES', 'apercevoir', 'bilanRemplacement', 'cleNormalisee', 'decoder',
'exporterParticipants', 'exporterRefus',
]);
});
test("exporterRefus écrit sous l'en-tête motif exactement libelle(code, details) d'une ligne refusée NOM_ABSENT", () => {
const texte = ['nom;prenom', 'Ombrelle;Iris', ';Théo', 'Pervenche;Ondine', ''].join('\r\n');
const lu = relais.apercevoir(texte);
assert.equal(lu.refusees.length, 1);
const [{ code, brut, ...details }] = lu.refusees;
assert.equal(code, 'NOM_ABSENT');
const { enregistrements } = decouper(relais.decoder(relais.exporterRefus(lu)).texte, ';');
assert.equal(enregistrements.length, 2);
const [entete, ligne] = enregistrements;
const rang = entete.indexOf('motif');
assert.ok(rang >= 0, entete.join(';'));
assert.equal(ligne[rang], libelle('NOM_ABSENT', details));
assert.equal(ligne[entete.indexOf('ligne')], '3');
assert.ok(brut.length > 0);
});
test('exporterRefus écrit les refus reçus en second argument, à la place de ceux de l’aperçu', () => {
const lu = relais.apercevoir(['nom;exclu', 'Ombrelle;peut-être', ''].join('\r\n'));
assert.equal(lu.refusees.length, 1);
const vide = decouper(relais.decoder(relais.exporterRefus(lu, [])).texte, ';').enregistrements;
assert.deepEqual(vide, [['nom', 'exclu', 'ligne', 'motif']]);
const [entete, ligne] = decouper(relais.decoder(relais.exporterRefus(lu, lu.refusees)).texte, ';').enregistrements;
const [{ code, brut, ...details }] = lu.refusees;
assert.equal(ligne[entete.indexOf('motif')], libelle(code, details));
assert.ok(brut.length > 0);
});
});
describe('csv : les textes des refus d’une ligne pour ses étiquettes (§ 10.1, A17)', () => {
test('ETIQUETTE_MAL_FORMEE cite la valeur et dit la raison : 40 caractères au plus, ou un caractère de contrôle ou invisible', () => {
const longue = libelle('ETIQUETTE_MAL_FORMEE', { ligne: 3, valeur: 'x'.repeat(41), raison: 'longueur' });
const invisible = libelle('ETIQUETTE_MAL_FORMEE', { ligne: 3, valeur: 'fin\tance', raison: 'controle' });
assert.match(longue, new RegExp(`« ${'x'.repeat(41)} »`));
assert.match(longue, /40/);
assert.match(invisible, /« fin\tance »/);
assert.match(invisible, /contrôle/);
assert.notEqual(longue.replace('x'.repeat(41), ''), invisible.replace('fin\tance', ''));
});
test('ETIQUETTES_TROP_NOMBREUSES dit le nombre lu et la borne de 20', () => {
const texte = libelle('ETIQUETTES_TROP_NOMBREUSES', { ligne: 3, valeur: null, nombre: 23 });
assert.match(texte, /23/);
assert.match(texte, /20/);
});
test('une raison inconnue lève RangeError plutôt que d’écrire un motif faux', () => {
assert.throws(() => libelle('ETIQUETTE_MAL_FORMEE', { ligne: 3, valeur: 'x', raison: 'autre' }), RangeError);
});
});