126 lines
5.7 KiB
JavaScript
126 lines
5.7 KiB
JavaScript
|
|
// © 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.
|
|||
|
|
//
|
|||
|
|
// 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', {});
|
|||
|
|
await enregistrer(m, { graine: 9, nombre: 2, arret: 300 });
|
|||
|
|
expect(appels(), 'la charge écrite, mesurée une fois').toBe(avantEcriture + 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);
|
|||
|
|
});
|
|||
|
|
});
|