gestion_table_tournante_libre/src/application/empreintes.test.js

138 lines
6.4 KiB
JavaScript
Raw Normal View History

// © 2026 TechnoLibre (http://www.technolibre.ca)
// License AGPL-3.0 or later (https://www.gnu.org/licenses/agpl)
// Classement de référence (§ 5.7, § 14.10) : une charge de propositions
// engendrées par rechercher, enregistrées par la commande, s'évalue par
// evaluer sous les quatre cases cochées, plafond a priori calculé puis
// inconnu. L'ordre, les critères appliqués, le critère sauté et les grandeurs
// nommées de chaque proposition se comparent au fichier versionné
// test/fixtures/moteur/empreintes_defaut.json, que seule
// src/moteur/empreintes.test.js récrit, sous GTT_EMPREINTES=ecrire : cette
// épreuve-ci ne fait que relire, et refuse de se jouer sous ce mode. Les
// grandeurs de chaque proposition sont celles qu'une épreuve des seize
// combinaisons relit pour en déduire l'ordre attendu sous chacune.
//
// Un ordre figé ne garde un critère que s'il y décide quelque chose : la
// référence est tenue de faire de chaque critère appliqué, dans un de ses
// classements au moins, le premier qui départage deux propositions voisines,
// et un critère dont le retrait change l'ordre. Sans quoi un comparateur qui
// inverse ou oublie ce critère rend le même ordre que la référence.
import assert from 'node:assert/strict';
import { describe, test } from '../../test/lanceur.js';
import {
CASES,
CLASSEMENT,
chargeDeClassement,
classementLu,
empreintesDuClassement,
exigerVerification,
lireEmpreintes,
} from '../../test/empreintes_moteur.js';
import { evaluer } from './evaluation.js';
exigerVerification(process.env);
const REFERENCE = lireEmpreintes();
// Les deux classements de chaque charge, sous le nom de leur plafond a priori.
const PLAFONDS = Object.freeze(['connu', 'inconnu']);
// Pour chaque critère du classement, le nom de la grandeur nommée qu'il lit
// dans le fichier.
const GRANDEUR_DU_CRITERE = new Map([
['ecartAuPlafondAPrioriMax', 'ecartAuPlafondAPrioriMax'],
['excedentCollisions', 'excedentCollisions'],
['collisionsCumulees', 'collisionsCumulees'],
['rencontresRepetees', 'rencontresRepeteesChoisies'],
['redondance', 'redondance'],
]);
// La valeur d'un critère pour la proposition id, lue dans les grandeurs de
// la référence. Lève pour un critère sans grandeur, en le nommant.
function valeur(grandeurs, id, critere) {
const nom = GRANDEUR_DU_CRITERE.get(critere);
if (nom === undefined) throw new Error(`critère sans grandeur nommée : ${critere}`);
return grandeurs[String(id)][nom];
}
// Le premier critère de criteres sur lequel deux propositions diffèrent ;
// null quand elles sont égales sur tous.
function premierQuiDepartage(grandeurs, criteres, a, b) {
return criteres.find((critere) => valeur(grandeurs, a, critere) !== valeur(grandeurs, b, critere)) ?? null;
}
// Les identifiants ids rangés critère par critère dans l'ordre de criteres,
// chacun croissant, puis par identifiant croissant : le classement du § 5.7,
// refait sur les grandeurs du fichier.
function ordonner(grandeurs, criteres, ids) {
return [...ids].sort((a, b) => {
const critere = premierQuiDepartage(grandeurs, criteres, a, b);
return critere === null ? a - b : valeur(grandeurs, a, critere) - valeur(grandeurs, b, critere);
});
}
// Chaque classement de la référence, nommé par sa charge et son plafond a
// priori, avec les grandeurs de ses propositions.
function classementsDeReference() {
return CLASSEMENT.cles.flatMap((cle) =>
PLAFONDS.map((plafond) => ({
nom: `${cle}, plafond a priori ${plafond}`,
...REFERENCE.classements[cle][plafond],
grandeurs: REFERENCE.classements[cle].propositions,
})),
);
}
describe('classement de référence : evaluer sous les quatre cases cochées (§ 5.7, § 14.10)', () => {
test(`la référence porte ${CLASSEMENT.cles.join(' et ')}, ${CLASSEMENT.nombre} propositions chacune`, () => {
assert.deepEqual(Object.keys(REFERENCE.classements), CLASSEMENT.cles);
for (const cle of CLASSEMENT.cles) {
const { propositions } = REFERENCE.classements[cle];
assert.equal(Object.keys(propositions).length, CLASSEMENT.nombre, cle);
}
});
for (const cle of CLASSEMENT.cles) {
test(`${cle} : l’ordre et les critères appliqués, plafond a priori calculé puis inconnu, sont ceux de la référence`, () => {
const charge = chargeDeClassement(cle);
assert.deepEqual(CASES.map((nom) => charge.reglages[nom]), [true, true, true, true], `${cle} : les quatre cases cochées`);
const attendu = REFERENCE.classements[cle];
assert.deepEqual(classementLu(evaluer(charge).classement), attendu.connu, `${cle}, plafond a priori calculé`);
assert.deepEqual(
classementLu(evaluer(charge, { plafondsAPriori: null }).classement),
attendu.inconnu,
`${cle}, plafond a priori inconnu`,
);
});
test(`${cle} : les grandeurs nommées de chaque proposition sont celles de la référence`, () => {
assert.deepEqual(empreintesDuClassement(cle).propositions, REFERENCE.classements[cle].propositions, cle);
});
}
});
describe('classement de référence : chaque critère appliqué y décide (§ 5.7, § 14.10)', () => {
test('les grandeurs de la référence, rangées sous ses critères appliqués, redonnent son ordre', () => {
for (const { nom, ordre, criteresAppliques, grandeurs } of classementsDeReference()) {
assert.deepEqual(ordonner(grandeurs, criteresAppliques, ordre), ordre, nom);
}
});
test('chaque critère appliqué départage le premier deux propositions voisines, et son retrait change l’ordre', () => {
const classements = classementsDeReference();
const criteres = [...new Set(classements.flatMap(({ criteresAppliques }) => criteresAppliques))];
const sansEffet = [];
for (const critere of criteres) {
const departage = classements.some(({ ordre, criteresAppliques, grandeurs }) =>
ordre.slice(1).some((id, rang) => premierQuiDepartage(grandeurs, criteresAppliques, ordre[rang], id) === critere),
);
const compte = classements.some(({ ordre, criteresAppliques, grandeurs }) => {
const sans = criteresAppliques.filter((autre) => autre !== critere);
return criteresAppliques.includes(critere) && ordonner(grandeurs, sans, ordre).join() !== ordre.join();
});
if (!departage) sansEffet.push(`${critere} : ne départage le premier aucune paire voisine`);
if (!compte) sansEffet.push(`${critere} : son retrait ne change aucun ordre`);
}
assert.deepEqual(sansEffet, []);
});
});