181 lines
7.1 KiB
JavaScript
181 lines
7.1 KiB
JavaScript
|
|
// © 2026 TechnoLibre (http://www.technolibre.ca)
|
||
|
|
// License AGPL-3.0 or later (https://www.gnu.org/licenses/agpl)
|
||
|
|
|
||
|
|
// Épreuves de l'association des colonnes (§ 10.1) : les champs du CSV, la
|
||
|
|
// reconnaissance d'un en-tête et de ses synonymes, et l'association par
|
||
|
|
// en-tête, jamais par position, qui laisse sans colonne un champ que deux
|
||
|
|
// colonnes revendiquent tant qu'un choix ne tranche pas.
|
||
|
|
import assert from 'node:assert/strict';
|
||
|
|
import { describe, test } from '../../test/lanceur.js';
|
||
|
|
import { CHAMPS, associer, reconnaitre } from './colonnes.js';
|
||
|
|
|
||
|
|
describe('CHAMPS', () => {
|
||
|
|
test('les sept champs du § 10.1, dans leur ordre, figés', () => {
|
||
|
|
assert.deepEqual(CHAMPS, ['nom', 'prenom', 'appartenance', 'courriel', 'titre_pressenti', 'exclu', 'notes']);
|
||
|
|
assert.ok(Object.isFrozen(CHAMPS));
|
||
|
|
});
|
||
|
|
});
|
||
|
|
|
||
|
|
describe('reconnaitre (§ 10.1)', () => {
|
||
|
|
test('chaque champ par son propre nom', () => {
|
||
|
|
assert.ok(CHAMPS.length > 0, 'aucun champ');
|
||
|
|
for (const champ of CHAMPS) assert.equal(reconnaitre(champ), champ);
|
||
|
|
});
|
||
|
|
|
||
|
|
test('les synonymes : organisation, entreprise et équipe ; titre et rôle', () => {
|
||
|
|
assert.deepEqual(
|
||
|
|
['organisation', 'entreprise', 'équipe', 'titre', 'rôle'].map((entete) => reconnaitre(entete)),
|
||
|
|
['appartenance', 'appartenance', 'appartenance', 'titre_pressenti', 'titre_pressenti'],
|
||
|
|
);
|
||
|
|
});
|
||
|
|
|
||
|
|
test('sans égard à la casse, aux accents ni aux espaces', () => {
|
||
|
|
const cas = [
|
||
|
|
['NOM', 'nom'],
|
||
|
|
[' Prénom ', 'prenom'],
|
||
|
|
['Équipe', 'appartenance'],
|
||
|
|
['titre', 'titre_pressenti'],
|
||
|
|
['Rôle', 'titre_pressenti'],
|
||
|
|
['E X C L U', 'exclu'],
|
||
|
|
['Titre_Pressenti', 'titre_pressenti'],
|
||
|
|
['titre pressenti', 'titre_pressenti'],
|
||
|
|
['COURRIEL', 'courriel'],
|
||
|
|
['Notes ', 'notes'],
|
||
|
|
['ORGANISATION', 'appartenance'],
|
||
|
|
['Entre-prise', 'appartenance'],
|
||
|
|
['Equipe', 'appartenance'],
|
||
|
|
];
|
||
|
|
assert.deepEqual(
|
||
|
|
cas.map(([entete]) => reconnaitre(entete)),
|
||
|
|
cas.map(([, champ]) => champ),
|
||
|
|
);
|
||
|
|
});
|
||
|
|
|
||
|
|
test('un en-tête inconnu rend null, dont « ligne » et « motif » du CSV des refus', () => {
|
||
|
|
const inconnus = [
|
||
|
|
'ligne', 'motif', '', ' ', 'nom de famille', 'société', 'prenoms', 'courriels',
|
||
|
|
'organisations', 'titres', 'appartenances', 'exclus', 'note', 'titre.pressenti',
|
||
|
|
];
|
||
|
|
assert.deepEqual(
|
||
|
|
inconnus.map((entete) => reconnaitre(entete)),
|
||
|
|
inconnus.map(() => null),
|
||
|
|
);
|
||
|
|
});
|
||
|
|
});
|
||
|
|
|
||
|
|
describe('associer : par en-tête, jamais par position (§ 10.1)', () => {
|
||
|
|
test('chaque champ reconnu reçoit le rang de sa colonne, à partir de 0', () => {
|
||
|
|
assert.deepEqual(associer(['nom', 'prenom', 'appartenance', 'courriel', 'titre_pressenti', 'exclu', 'notes']), {
|
||
|
|
colonnes: { nom: 0, prenom: 1, appartenance: 2, courriel: 3, titre_pressenti: 4, exclu: 5, notes: 6 },
|
||
|
|
ambigus: [],
|
||
|
|
nonReconnues: [],
|
||
|
|
});
|
||
|
|
});
|
||
|
|
|
||
|
|
test('des colonnes déplacées donnent à chaque champ la même valeur', () => {
|
||
|
|
const entetes = ['nom', 'prenom', 'appartenance', 'courriel'];
|
||
|
|
const enregistrement = ['Ombrelle', 'Iris', 'Club des Merles', 'iris@exemple.test'];
|
||
|
|
// La colonne i du fichier déplacé est la colonne deplacement[i] de l'autre.
|
||
|
|
const deplacement = [3, 2, 0, 1];
|
||
|
|
const entetesDeplaces = deplacement.map((rang) => entetes[rang]);
|
||
|
|
const enregistrementDeplace = deplacement.map((rang) => enregistrement[rang]);
|
||
|
|
// [champ, valeur] de chaque champ associé, dans l'ordre de CHAMPS.
|
||
|
|
const lire = ({ colonnes }, champs) =>
|
||
|
|
CHAMPS.filter((champ) => colonnes[champ] !== undefined).map((champ) => [champ, champs[colonnes[champ]]]);
|
||
|
|
|
||
|
|
const association = associer(entetes);
|
||
|
|
const associationDeplacee = associer(entetesDeplaces);
|
||
|
|
assert.deepEqual(associationDeplacee.colonnes, { nom: 2, prenom: 3, appartenance: 1, courriel: 0 });
|
||
|
|
assert.deepEqual(lire(associationDeplacee, enregistrementDeplace), lire(association, enregistrement));
|
||
|
|
assert.deepEqual(lire(association, enregistrement), [
|
||
|
|
['nom', 'Ombrelle'],
|
||
|
|
['prenom', 'Iris'],
|
||
|
|
['appartenance', 'Club des Merles'],
|
||
|
|
['courriel', 'iris@exemple.test'],
|
||
|
|
]);
|
||
|
|
});
|
||
|
|
|
||
|
|
test("colonnes suit l'ordre de CHAMPS, quel que soit celui du fichier", () => {
|
||
|
|
assert.equal(
|
||
|
|
JSON.stringify(associer(['notes', 'exclu', 'courriel', 'nom']).colonnes),
|
||
|
|
'{"nom":3,"courriel":2,"exclu":1,"notes":0}',
|
||
|
|
);
|
||
|
|
});
|
||
|
|
|
||
|
|
test("organisation et entreprise ensemble : ni l'une ni l'autre, une ambiguïté sur appartenance", () => {
|
||
|
|
assert.deepEqual(associer(['nom', 'organisation', 'entreprise']), {
|
||
|
|
colonnes: { nom: 0 },
|
||
|
|
ambigus: [{ champ: 'appartenance', rangs: [1, 2] }],
|
||
|
|
nonReconnues: [],
|
||
|
|
});
|
||
|
|
});
|
||
|
|
|
||
|
|
test("le choix tranche l'ambiguïté, pour l'une comme pour l'autre colonne", () => {
|
||
|
|
const entetes = ['nom', 'organisation', 'entreprise'];
|
||
|
|
assert.deepEqual(associer(entetes, { appartenance: 2 }), {
|
||
|
|
colonnes: { nom: 0, appartenance: 2 },
|
||
|
|
ambigus: [],
|
||
|
|
nonReconnues: [],
|
||
|
|
});
|
||
|
|
assert.deepEqual(associer(entetes, { appartenance: 1 }).colonnes, { nom: 0, appartenance: 1 });
|
||
|
|
});
|
||
|
|
|
||
|
|
test('des colonnes déplacées donnent la même ambiguïté, aux rangs déplacés', () => {
|
||
|
|
const entetes = ['entreprise', 'nom', 'organisation'];
|
||
|
|
assert.deepEqual(associer(entetes), {
|
||
|
|
colonnes: { nom: 1 },
|
||
|
|
ambigus: [{ champ: 'appartenance', rangs: [0, 2] }],
|
||
|
|
nonReconnues: [],
|
||
|
|
});
|
||
|
|
assert.deepEqual(associer(entetes, { appartenance: 0 }).colonnes, { nom: 1, appartenance: 0 });
|
||
|
|
});
|
||
|
|
|
||
|
|
test("un choix qui ne désigne pas l'une des colonnes en conflit est sans effet", () => {
|
||
|
|
const entetes = ['nom', 'organisation', 'entreprise', 'inconnue'];
|
||
|
|
const sansChoix = associer(entetes);
|
||
|
|
assert.deepEqual(sansChoix.ambigus, [{ champ: 'appartenance', rangs: [1, 2] }]);
|
||
|
|
const choix = [
|
||
|
|
{ appartenance: 0 },
|
||
|
|
{ appartenance: 3 },
|
||
|
|
{ appartenance: 7 },
|
||
|
|
{ appartenance: '2' },
|
||
|
|
{ appartenance: null },
|
||
|
|
{ nom: 1 },
|
||
|
|
{ prenom: 3 },
|
||
|
|
{ inconnu: 1 },
|
||
|
|
];
|
||
|
|
for (const un of choix) assert.deepEqual(associer(entetes, un), sansChoix, JSON.stringify(un));
|
||
|
|
});
|
||
|
|
|
||
|
|
test("deux colonnes nom : ni l'une ni l'autre", () => {
|
||
|
|
assert.deepEqual(associer(['Nom', 'prenom', 'NOM']), {
|
||
|
|
colonnes: { prenom: 1 },
|
||
|
|
ambigus: [{ champ: 'nom', rangs: [0, 2] }],
|
||
|
|
nonReconnues: [],
|
||
|
|
});
|
||
|
|
});
|
||
|
|
|
||
|
|
test("plusieurs conflits : les ambiguïtés dans l'ordre de CHAMPS, les rangs croissants", () => {
|
||
|
|
assert.deepEqual(associer(['titre', 'équipe', 'nom', 'rôle', 'organisation', 'entreprise']), {
|
||
|
|
colonnes: { nom: 2 },
|
||
|
|
ambigus: [
|
||
|
|
{ champ: 'appartenance', rangs: [1, 4, 5] },
|
||
|
|
{ champ: 'titre_pressenti', rangs: [0, 3] },
|
||
|
|
],
|
||
|
|
nonReconnues: [],
|
||
|
|
});
|
||
|
|
});
|
||
|
|
|
||
|
|
test('les colonnes non reconnues, en-tête vide compris, par rang croissant', () => {
|
||
|
|
assert.deepEqual(associer(['x', 'nom', '', 'ligne', ' ']), {
|
||
|
|
colonnes: { nom: 1 },
|
||
|
|
ambigus: [],
|
||
|
|
nonReconnues: [0, 2, 3, 4],
|
||
|
|
});
|
||
|
|
});
|
||
|
|
|
||
|
|
test('aucune colonne : rien à associer', () => {
|
||
|
|
assert.deepEqual(associer([]), { colonnes: {}, ambigus: [], nonReconnues: [] });
|
||
|
|
});
|
||
|
|
});
|