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