gestion_table_tournante_libre/src/interface/generation/Evaluation.navigateur.test.js

134 lines
6.2 KiB
JavaScript
Raw Normal View History

// © 2026 TechnoLibre (http://www.technolibre.ca)
// License AGPL-3.0 or later (https://www.gnu.org/licenses/agpl)
// L'évaluation que partagent Génération et Propositions, dans un vrai
// moteur de rendu (§ 5.7, § 13.3) : tant que la charge de la séance ne
// change pas, revenir à l'une ou l'autre section ne mesure rien de nouveau ;
// une charge écrite se mesure une fois, et la meilleure proposition annoncée
// la suit. Une charge sans proposition n'a pas de table partagée : l'éditeur
// des priorités y lit l'état des critères par une évaluation à lui, une fois
// par charge écrite.
//
// Le module de l'évaluation est remplacé par ses propres fonctions,
// espionnées (vi.mock, spy) : evaluer garde son effet, et ses appels se
// comptent. Chaque épreuve monte App par monterApp, sur le système de
// fichiers simulé, avec l'exécuteur direct ; le rendu se lit par les
// attributs data-… ; les noms d'épreuve sont inventés.
//
// Le projet navigateur emploie expect, vi, et test et describe de Vitest
// (§ 14.8).
import { afterEach, beforeEach, describe, expect, test, vi } from 'vitest';
import { flushSync } from 'svelte';
import { evaluer, premiereDuClassement } from '../../application/evaluation.js';
import { creerFormat } from '../../application/format.js';
import { creerExecuteurDirect } from '../../application/generation.js';
import { TABLES_APPLICATION } from '../../application/libelles.js';
import { creerTraduction } from '../../application/traduction.js';
import { rechercher } from '../../moteur/recherche.js';
import { configurationDepuisCharge } from '../../stockage/document.js';
import { VERSION } from '../../version.genere.js';
import { fixerCadre } from '../../../test/navigateur/entrees.js';
import { monterApp } from '../../../test/navigateur/montage.js';
import { TABLES_INTERFACE } from '../libelles.js';
import { meilleureProposition } from './meilleure.js';
vi.mock('../../application/evaluation.js', { spy: true });
const { t } = creerTraduction([...TABLES_APPLICATION, ...TABLES_INTERFACE]);
const format = creerFormat(t);
// Les montages de l'épreuve, démontés après elle ; le collecteur d'erreurs
// n'a rien reçu pendant l'épreuve.
let montes = [];
async function monter(options) {
const m = await monterApp(options);
montes.push(m);
return m;
}
beforeEach(async () => {
await fixerCadre(1366, 700);
});
afterEach(() => {
const erreurs = montes.flatMap((m) => m.erreurs());
for (const m of montes.reverse()) m.demonter();
montes = [];
window.scrollTo(0, 0);
expect(erreurs, 'erreurs reçues par le collecteur').toEqual([]);
});
const dans = (m, selecteur) => m.racine.querySelector(selecteur);
const charge = (m) => m.seance.etat().charge;
const identifiant = (m) => dans(m, '[data-section="generation"] [data-meilleure] [data-meilleure-id]');
const lignes = (m) => m.racine.querySelectorAll('[data-section="propositions"] tbody tr[data-proposition]');
const appels = () => vi.mocked(evaluer).mock.calls.length;
// Ouvre la section nommée, son rendu à jour.
function aller(m, section) {
m.ui.allerA(section);
flushSync();
}
// Une génération du moteur de plus, enregistrée par la séance.
async function enregistrer(m, reglages) {
const configuration = configurationDepuisCharge(charge(m));
const propositions = rechercher(configuration, { historique: 1000, ...reglages });
await m.seance.executer('enregistrerGeneration', { propositions, configuration, produitVersion: VERSION.affichee });
flushSync();
}
// L'identifiant au rang 1 d'une table neuve de la charge courante, tel que
// le bloc l'écrit.
const rangUnAttendu = (m) => format.nombre(premiereDuClassement(evaluer(charge(m))).id);
describe('Génération et Propositions relisent une même évaluation (§ 5.7, § 13.3)', () => {
test('revenir à Génération, puis à Propositions, ne mesure rien de nouveau ; une écriture mesure une fois, et l’annonce suit la charge écrite', async () => {
const m = await monter({ executeur: creerExecuteurDirect() });
await m.facade.chargerDemonstration('petite-conflit');
await m.seance.passerEnEcriture();
await enregistrer(m, { graine: 5, nombre: 3, arret: 300 });
aller(m, 'participants');
vi.mocked(evaluer).mockClear();
aller(m, 'generation');
expect(appels(), '1re visite de Génération').toBe(1);
const premiere = identifiant(m).textContent;
aller(m, 'participants');
aller(m, 'generation');
expect(appels(), '2e visite de Génération').toBe(1);
aller(m, 'propositions');
expect(lignes(m).length).toBe(3);
expect(appels(), 'Propositions après Génération').toBe(1);
aller(m, 'generation');
expect(appels(), 'Génération après Propositions').toBe(1);
expect(identifiant(m).textContent).toBe(premiere);
// Le modèle relit la même table que les deux sections.
expect(format.nombre(meilleureProposition(charge(m)).id)).toBe(premiere);
expect(appels(), 'le modèle après les deux sections').toBe(1);
// Une écriture qui remplace les propositions : la charge écrite se
// mesure une fois, et l'annonce lit son rang 1, que l'ancienne charge
// ne portait pas — un identifiant ne se réattribue jamais.
const anciens = charge(m).propositions.map(({ id }) => id);
const avantEcriture = appels();
await m.seance.executer('effacerPropositions', {});
flushSync();
expect(appels(), 'la charge sans proposition, évaluée une fois pour l’état des critères').toBe(avantEcriture + 1);
aller(m, 'participants');
aller(m, 'generation');
expect(appels(), 'la charge sans proposition, revisitée').toBe(avantEcriture + 1);
const avantGeneration = appels();
await enregistrer(m, { graine: 9, nombre: 2, arret: 300 });
expect(appels(), 'la charge écrite, mesurée une fois').toBe(avantGeneration + 1);
const attendu = rangUnAttendu(m);
const apres = appels();
expect(identifiant(m).textContent).toBe(attendu);
expect(anciens.map((id) => format.nombre(id))).not.toContain(attendu);
aller(m, 'participants');
aller(m, 'generation');
expect(identifiant(m).textContent).toBe(attendu);
aller(m, 'propositions');
expect(lignes(m).length).toBe(2);
expect(appels(), 'les deux sections sur la charge écrite').toBe(apres);
});
});