[ADD] event list: retained placement's chosen repeated meetings
The event list summed up the retained placement by its collisions alone; the chosen repeated meetings, the other figure the proposals table ranks on, was only visible after opening the event. Both now sit under one « Placement retenu » group header, read from a single measurement of the retained placement, with the same « — » cases and lazy loading. The column group span is tested, so assistive technology keeps both columns under that header. The user guide drops the stale redundancy target. Checked: node 5152, browser 1154, node-long 129; no capture changes. --- FR --- [ADD] liste des événements : rencontres répétées choisies du retenu La liste des événements résumait le retenu par ses seules collisions ; les rencontres répétées choisies, l'autre nombre que classe le tableau des propositions, ne se voyaient qu'en ouvrant l'événement. Les deux tiennent sous un même en-tête « Placement retenu », lues d'une seule mesure du retenu, avec les mêmes cas « — » et le même chargement différé. L'étendue du groupe de colonnes est éprouvée : les aides techniques gardent les deux colonnes sous cet en-tête. Le guide d'usage quitte la cible périmée. Vérifié : node 5152, navigateur 1154, node-long 129 ; aucune capture. Assisted-by: Claude Opus 5.5
This commit is contained in:
parent
4f9c550e44
commit
ffce294cec
12 changed files with 254 additions and 117 deletions
13
CHANGELOG.md
13
CHANGELOG.md
|
|
@ -166,12 +166,13 @@
|
|||
page de qualité.
|
||||
- Le guide d'usage montre la page de qualité, ses vues et ses cas limites,
|
||||
captures à l'appui.
|
||||
- Dans la liste des événements, la colonne du placement retenu montre ses
|
||||
« Collisions cumulées (paire et tour) », le même nombre que la colonne du
|
||||
tableau des propositions, au lieu de « Diff somme d'appartenance ». Le réglage
|
||||
« Cible de la somme de redondance », qui ne servait qu'à cette colonne, quitte
|
||||
la section des propositions : un fichier qui le porte s'ouvre sans message, et
|
||||
le perd au premier enregistrement.
|
||||
- Dans la liste des événements, le placement retenu montre ses « Collisions
|
||||
cumulées (paire et tour) » et ses « Rencontres répétées choisies (paires) »,
|
||||
les mêmes nombres que les colonnes du tableau des propositions, au lieu de
|
||||
« Diff somme d'appartenance ». Le réglage « Cible de la somme de redondance »,
|
||||
qui ne servait qu'à cette ancienne colonne, quitte la section des
|
||||
propositions : un fichier qui le porte s'ouvre sans message, et le perd au
|
||||
premier enregistrement.
|
||||
- La section « Propositions » offre « Garder les 10 meilleures » : les dix
|
||||
premières du classement restent, et celle d'où vient le placement retenu
|
||||
aussi, les autres s'effacent. Le nombre se règle dans le champ voisin. Une
|
||||
|
|
|
|||
|
|
@ -378,8 +378,9 @@ réservations imposent de ce que la recherche a choisi.
|
|||
proposition alors que le retenu a été ajusté à la main détruit ces ajustements :
|
||||
une fenêtre le dit avant.
|
||||
|
||||
La liste des événements résume ensuite le retenu par l’écart de sa somme de
|
||||
redondance d’appartenance à la cible de la soirée.
|
||||
La liste des événements résume ensuite le retenu par ses collisions cumulées
|
||||
(paire et tour) et ses rencontres répétées choisies (paires), les nombres du
|
||||
tableau des propositions.
|
||||
|
||||
Retenir n’est pas obligatoire, mais tout ce qui s’imprime sans placement retenu
|
||||
porte la mention BROUILLON (étape 12).
|
||||
|
|
|
|||
18
spec.md
18
spec.md
|
|
@ -201,7 +201,7 @@ du calcul qui a produit la proposition. Le logiciel sépare ce que le moteur a
|
|||
décidé de ce que les réservations imposent (§ 5.4). Retenir fait passer le plan
|
||||
à l'état *Retenu* (§ 9) ; les propositions non retenues restent disponibles. Le
|
||||
retenu se résume ensuite dans la liste des événements, par ses collisions
|
||||
cumulées (§ 5.4, § 8.1).
|
||||
cumulées et ses rencontres répétées choisies (§ 5.4, § 8.1).
|
||||
|
||||
**11. Ajuster à la main.** *Facultative, reprenable.* Le même geste qu'à
|
||||
l'étape 7 déplace maintenant une personne réelle dans le placement retenu. **Le
|
||||
|
|
@ -1950,12 +1950,16 @@ pastille de l'état et son compte —, ou ne les groupe pas, au choix ; ses
|
|||
en-têtes de colonnes se groupent en « Événement », « Placement retenu » et
|
||||
« Meilleure proposition du classement », et le nom de chaque ligne en est
|
||||
l'en-tête, la date et le chemin en petite ligne dessous. Sous le tableau, des
|
||||
cartes offrent la création, le dossier de travail et les démonstrations. Une
|
||||
colonne résume en plus le **placement retenu** : « Collisions cumulées (paire et
|
||||
tour) », les collisions cumulées du retenu (§ 5.4) — le nombre que la colonne du
|
||||
même intitulé du tableau des propositions porte sur la ligne du retenu. Elle se
|
||||
lit dans la même mesure que le tableau, sans rien écrire ; un événement sans
|
||||
retenu, ou dont le retenu est fautif ou en dérive (§ 8.9, § 9), y porte « — ».
|
||||
cartes offrent la création, le dossier de travail et les démonstrations. Deux
|
||||
colonnes résument en plus le **placement retenu**, sous l'en-tête du groupe qui
|
||||
les couvre toutes deux : « Collisions cumulées (paire et tour) », les collisions
|
||||
cumulées du retenu, puis « Rencontres répétées choisies (paires) », les paires
|
||||
réunies plus d'une fois que le placement a choisies, sans celles que les
|
||||
réservations imposent (§ 5.4) — dans chacune, le nombre que la colonne du même
|
||||
intitulé du tableau des propositions porte sur la ligne du retenu. Les deux se
|
||||
lisent dans une seule mesure du retenu, celle du tableau, sans rien écrire ; un
|
||||
événement sans retenu, ou dont le retenu est fautif ou en dérive (§ 8.9, § 9),
|
||||
porte « — » dans l'une et l'autre.
|
||||
Chaque ligne porte aussi la **« Meilleure proposition du classement »**, sous
|
||||
les libellés du bloc que la section de la génération montre en tête :
|
||||
l'identifiant de la proposition au rang 1 du classement (§ 5.7) — la même
|
||||
|
|
|
|||
|
|
@ -26,9 +26,10 @@
|
|||
// dans ses mesures, à part des choisies.
|
||||
//
|
||||
// resumeDuRetenu donne à la liste des événements les collisions cumulées
|
||||
// du retenu, lues dans la même mesure que sa ligne du tableau, sans le
|
||||
// diagnostic ni les plafonds : la liste en résume chaque événement du
|
||||
// dossier. premiereDuClassement est la seule définition de la proposition
|
||||
// et les rencontres répétées choisies du retenu, lues dans une seule
|
||||
// mesure, celle de sa ligne du tableau, sans le diagnostic ni les
|
||||
// plafonds : la liste en résume chaque événement du dossier.
|
||||
// premiereDuClassement est la seule définition de la proposition
|
||||
// au rang 1 : la section de la génération la lit dans sa table,
|
||||
// resumeDuMeilleur en donne l'identifiant à la liste, sur la même table
|
||||
// d'évaluation, diagnostic compris — le premier critère du classement lit
|
||||
|
|
@ -217,17 +218,23 @@ export function evaluer(charge, { plafondsAPriori = 'calcul' } = {}) {
|
|||
}
|
||||
|
||||
/**
|
||||
* Le résumé du retenu que montre la liste des événements (§ 5.4, § 8.1) :
|
||||
* collisionsCumulees, les occurrences (paire, tour) où deux personnes d'une
|
||||
* même appartenance partagent une table, celles de mesurer — la valeur que
|
||||
* la ligne du retenu d'evaluer porte, et que la colonne « Collisions
|
||||
* cumulées (paire et tour) » du tableau des propositions écrit. null sans
|
||||
* retenu, pour un retenu fautif ou en dérive, et quand la configuration ne
|
||||
* se normalise pas : rien ne s'y mesure, et la liste écrit « — ». Ne
|
||||
* modifie pas la charge.
|
||||
* Le résumé du retenu que montre la liste des événements (§ 5.4, § 8.1),
|
||||
* lu dans un seul appel à mesurer, la mesure que la ligne du retenu
|
||||
* d'evaluer porte :
|
||||
* - collisionsCumulees, les occurrences (paire, tour) où deux personnes
|
||||
* d'une même appartenance partagent une table — la valeur que la colonne
|
||||
* « Collisions cumulées (paire et tour) » du tableau des propositions
|
||||
* écrit ;
|
||||
* - rencontresRepetees, les paires réunies à plus d'un tour que le placement
|
||||
* a choisies, sans celles que les réservations imposent
|
||||
* (mesures.rencontresRepetees.choisies) — la valeur que la colonne
|
||||
* « Rencontres répétées choisies (paires) » écrit.
|
||||
* null sans retenu, pour un retenu fautif ou en dérive, et quand la
|
||||
* configuration ne se normalise pas : rien ne s'y mesure, et la liste écrit
|
||||
* « — » dans les deux colonnes. Ne modifie pas la charge.
|
||||
*
|
||||
* @param {import('../stockage/types.js').Charge} charge
|
||||
* @returns {{collisionsCumulees: number}|null}
|
||||
* @returns {{collisionsCumulees: number, rencontresRepetees: number}|null}
|
||||
*/
|
||||
export function resumeDuRetenu(charge) {
|
||||
const { retenu } = charge;
|
||||
|
|
@ -239,8 +246,8 @@ export function resumeDuRetenu(charge) {
|
|||
if (erreur instanceof ErreurConfiguration) return null;
|
||||
throw erreur;
|
||||
}
|
||||
const { collisionsCumulees } = mesurer(instance, indexerPlan(instance, planDepuisFichier(retenu)));
|
||||
return { collisionsCumulees };
|
||||
const { collisionsCumulees, rencontresRepetees } = mesurer(instance, indexerPlan(instance, planDepuisFichier(retenu)));
|
||||
return { collisionsCumulees, rencontresRepetees: rencontresRepetees.choisies };
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
|
|||
|
|
@ -484,23 +484,52 @@ describe("évaluation : une configuration qui ne se tient pas", () => {
|
|||
});
|
||||
});
|
||||
|
||||
describe('resumeDuRetenu : les collisions cumulées du retenu que lit la liste des événements (§ 5.4, § 8.1)', () => {
|
||||
describe('resumeDuRetenu : les collisions cumulées et les rencontres répétées choisies du retenu que lit la liste des événements (§ 5.4, § 8.1)', () => {
|
||||
// La petite démonstration, trois propositions générées, la deuxième retenue.
|
||||
function retenue() {
|
||||
const generees = generee(chargeDemo('petite-conflit'), { graine: 3, nombre: 3, arret: 300 });
|
||||
return executer(generees, 'retenirProposition', { id: generees.propositions[1].id });
|
||||
}
|
||||
|
||||
test("les seules collisions cumulées (paire et tour), celles de la ligne du retenu d'evaluer et de mesurer", () => {
|
||||
test("les collisions cumulées (paire et tour) et les rencontres répétées choisies, celles de la ligne du retenu d'evaluer et de mesurer, et rien d'autre", () => {
|
||||
const charge = figer(retenue());
|
||||
const resume = resumeDuRetenu(charge);
|
||||
const { mesures } = evaluer(charge).retenu;
|
||||
assert.ok(mesures.collisionsCumulees > 0, 'le retenu porte des collisions');
|
||||
assert.ok(mesures.rencontresRepetees.choisies > 0, 'le retenu porte des rencontres répétées choisies');
|
||||
assert.notEqual(mesures.collisionsCumulees, mesures.totalRedondance);
|
||||
assert.deepEqual(resume, { collisionsCumulees: mesures.collisionsCumulees });
|
||||
assert.notEqual(mesures.rencontresRepetees.choisies, mesures.collisionsCumulees);
|
||||
assert.deepEqual(resume, {
|
||||
collisionsCumulees: mesures.collisionsCumulees,
|
||||
rencontresRepetees: mesures.rencontresRepetees.choisies,
|
||||
});
|
||||
const instance = normaliser(configurationDe(charge));
|
||||
const attendues = mesurer(instance, indexerPlan(instance, planDepuisFichier(charge.retenu)));
|
||||
assert.equal(attendues.collisionsCumulees, mesures.collisionsCumulees);
|
||||
assert.deepEqual(attendues.rencontresRepetees, mesures.rencontresRepetees);
|
||||
});
|
||||
|
||||
test('les rencontres répétées du résumé sont les seules choisies : une paire réservée ensemble, imposée, n’y entre pas', () => {
|
||||
const charge = structuredClone(chargeDemo('petite'));
|
||||
charge.reservations = [1, 2].map((participant) => ({ participant, table: 1, siege: null, portee: 'tous', tour: null }));
|
||||
const avec = generee(charge, { graine: 6, nombre: 3, arret: 500 });
|
||||
for (const proposition of avec.propositions) {
|
||||
const retenue = figer(executer(avec, 'retenirProposition', { id: proposition.id }));
|
||||
const { mesures } = evaluer(retenue).retenu;
|
||||
assert.equal(mesures.rencontresRepetees.imposees, 1);
|
||||
assert.deepEqual(resumeDuRetenu(retenue), {
|
||||
collisionsCumulees: mesures.collisionsCumulees,
|
||||
rencontresRepetees: mesures.rencontresRepetees.choisies,
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
test('les deux valeurs sortent d’une seule mesure du retenu : un seul appel à mesurer, dont le résumé lit les deux', () => {
|
||||
const source = readFileSync(new URL('./evaluation.js', import.meta.url), 'utf8');
|
||||
const debut = source.indexOf('export function resumeDuRetenu(');
|
||||
assert.ok(debut >= 0);
|
||||
const corps = source.slice(debut, source.indexOf('\n}\n', debut));
|
||||
assert.equal(corps.match(/\bmesurer\(/g)?.length, 1);
|
||||
});
|
||||
|
||||
test('aucune cible : le résumé ne dépend pas d’un champ cibleRedondance resté dans les réglages, et le module n’en exporte plus', () => {
|
||||
|
|
|
|||
|
|
@ -639,7 +639,7 @@ describe('lister (§ 8.1, § 19.4)', () => {
|
|||
return serialiser(modifier(charge), { revision: 3, produitVersion: V1 });
|
||||
}
|
||||
|
||||
test('les mesures du retenu : ses seules collisions cumulées, celles de resumeDuRetenu ; null sans retenu ; rien d’écrit', async () => {
|
||||
test('les mesures du retenu : ses collisions cumulées et ses rencontres répétées choisies, celles de resumeDuRetenu ; null sans retenu ; rien d’écrit', async () => {
|
||||
const { fs, facade } = await deuxEvenements();
|
||||
fs.deposer('portable', `retenue${SUFFIXES.etat}`, texteRetenu());
|
||||
const avant = ecritures(fs);
|
||||
|
|
@ -649,7 +649,11 @@ describe('lister (§ 8.1, § 19.4)', () => {
|
|||
const charge = analyser(texteRetenu()).charge;
|
||||
const { mesures } = evaluer(charge).retenu;
|
||||
assert.ok(mesures.collisionsCumulees > 0, 'le retenu porte des collisions');
|
||||
assert.deepEqual(par.retenue.mesuresDuRetenu, { collisionsCumulees: mesures.collisionsCumulees });
|
||||
assert.ok(mesures.rencontresRepetees.choisies > 0, 'le retenu porte des rencontres répétées choisies');
|
||||
assert.deepEqual(par.retenue.mesuresDuRetenu, {
|
||||
collisionsCumulees: mesures.collisionsCumulees,
|
||||
rencontresRepetees: mesures.rencontresRepetees.choisies,
|
||||
});
|
||||
assert.deepEqual(par.retenue.mesuresDuRetenu, resumeDuRetenu(charge));
|
||||
assert.equal(par['soiree-a'].mesuresDuRetenu, null);
|
||||
assert.equal(par.petite.mesuresDuRetenu, null);
|
||||
|
|
|
|||
|
|
@ -1238,10 +1238,12 @@ describe('les mesures du retenu dans la liste (§ 5.4, § 8.1)', () => {
|
|||
return mesurer(instance, indexerPlan(instance, planDepuisFichier(charge.retenu)));
|
||||
}
|
||||
|
||||
test('un événement retenu : ses collisions cumulées (paire et tour), sous l’intitulé du tableau des propositions ; sans retenu : « — » ; rien d’écrit', async () => {
|
||||
test('un retenu : ses collisions cumulées et ses rencontres répétées choisies, sous les intitulés du tableau des propositions ; sans retenu, « — »', async () => {
|
||||
const charge = chargeRetenue();
|
||||
const mesures = mesuresDe(charge);
|
||||
expect(mesures.collisionsCumulees).toBeGreaterThan(0);
|
||||
expect(mesures.rencontresRepetees.choisies).toBeGreaterThan(0);
|
||||
expect(mesures.rencontresRepetees.choisies).not.toBe(mesures.collisionsCumulees);
|
||||
expect(mesures.collisionsCumulees).not.toBe(Math.abs(1000 - mesures.totalRedondance));
|
||||
const m = await monter({
|
||||
fichiers: [fichier(`retenue${SUFFIXES.etat}`, etat(charge)), fichier(`club${SUFFIXES.etat}`, texteRenomme('Club des Merles'))],
|
||||
|
|
@ -1252,27 +1254,39 @@ describe('les mesures du retenu dans la liste (§ 5.4, § 8.1)', () => {
|
|||
const retenue = ligneDe(m, 'retenue');
|
||||
expect(colonne(retenue, 'collisionsCumulees')).toBe(format.nombre(mesures.collisionsCumulees));
|
||||
expect(colonne(retenue, 'collisionsCumulees')).toBe(format.nombre(evaluer(charge).retenu.mesures.collisionsCumulees));
|
||||
expect(colonne(retenue, 'rencontresRepetees')).toBe(format.nombre(mesures.rencontresRepetees.choisies));
|
||||
expect(colonne(retenue, 'rencontresRepetees')).toBe(format.nombre(evaluer(charge).retenu.mesures.rencontresRepetees.choisies));
|
||||
const club = ligneDe(m, 'club');
|
||||
expect(colonne(club, 'collisionsCumulees')).toBe(TIRET);
|
||||
// L'intitulé est un en-tête de colonne, une seule fois pour toute la
|
||||
// liste, au texte de la colonne du tableau des propositions ; plus rien
|
||||
// ne nomme la différence à la cible.
|
||||
expect(tous(m, '[data-terme="collisionsCumulees"]').length).toBe(1);
|
||||
expect(terme(accueil(m), 'collisionsCumulees').closest('thead')).not.toBeNull();
|
||||
expect(terme(accueil(m), 'collisionsCumulees').textContent).toBe(t('propositions.colonne.collisionsCumulees'));
|
||||
expect(colonne(club, 'rencontresRepetees')).toBe(TIRET);
|
||||
// Chaque intitulé est un en-tête de colonne, une seule fois pour toute la
|
||||
// liste, au texte de la colonne du tableau des propositions, dans l'ordre
|
||||
// des valeurs ; plus rien ne nomme la différence à la cible.
|
||||
for (const nom of ['collisionsCumulees', 'rencontresRepetees']) {
|
||||
expect(tous(m, `[data-terme="${nom}"]`).length, nom).toBe(1);
|
||||
expect(terme(accueil(m), nom).closest('thead'), nom).not.toBeNull();
|
||||
expect(terme(accueil(m), nom).textContent, nom).toBe(t(`propositions.colonne.${nom}`));
|
||||
expect(visible(retenue.querySelector(`[data-colonne="${nom}"]`)), nom).toBe(true);
|
||||
}
|
||||
expect([...retenue.querySelectorAll('[data-colonne]')].map((cellule) => cellule.dataset.colonne).slice(-2)).toEqual([
|
||||
'collisionsCumulees',
|
||||
'rencontresRepetees',
|
||||
]);
|
||||
expect(tous(m, '[data-colonne="diffRedondance"], [data-terme="diffRedondance"]')).toEqual([]);
|
||||
expect(accueil(m).textContent).not.toContain("Diff somme d'appartenance");
|
||||
expect(visible(retenue.querySelector('[data-colonne="collisionsCumulees"]'))).toBe(true);
|
||||
expect(ecritures(m)).toBe(avant);
|
||||
});
|
||||
|
||||
test('chaque intitulé s’explique : le survol puis le focus ouvrent son infobulle, Échap et la sortie la ferment', async () => {
|
||||
const m = await monter({ fichiers: [fichier(`retenue${SUFFIXES.etat}`, etat(chargeRetenue()))] });
|
||||
await vi.waitFor(() => expect(lignes(m).length).toBe(1));
|
||||
await eprouverTermes(accueil(m), [['collisionsCumulees', 'accueil.explication.collisionsCumulees']]);
|
||||
await eprouverTermes(accueil(m), [
|
||||
['collisionsCumulees', 'accueil.explication.collisionsCumulees'],
|
||||
['rencontresRepetees', 'accueil.explication.rencontresRepetees'],
|
||||
]);
|
||||
});
|
||||
|
||||
test('un état écrit avec la cible de redondance d’un code antérieur : la ligne se lit sans signalement, ses collisions comme sans elle ; l’ouvrir n’écrit rien', async () => {
|
||||
test('un état écrit avec la cible de redondance d’un code antérieur : la ligne se lit sans signalement, ses mesures du retenu comme sans elle ; l’ouvrir n’écrit rien', async () => {
|
||||
const charge = chargeRetenue();
|
||||
const lu = JSON.parse(etat(charge));
|
||||
lu.charge.reglages.cibleRedondance = 25;
|
||||
|
|
@ -1282,6 +1296,7 @@ describe('les mesures du retenu dans la liste (§ 5.4, § 8.1)', () => {
|
|||
const ligne = ligneDe(m, 'retenue');
|
||||
expect(ligne.querySelector('[data-signalement]')).toBeNull();
|
||||
expect(colonne(ligne, 'collisionsCumulees')).toBe(format.nombre(mesuresDe(charge).collisionsCumulees));
|
||||
expect(colonne(ligne, 'rencontresRepetees')).toBe(format.nombre(mesuresDe(charge).rencontresRepetees.choisies));
|
||||
const avant = ecritures(m);
|
||||
await ouvrirLigne(m, 'retenue');
|
||||
await attendreEvenement(m);
|
||||
|
|
@ -1418,8 +1433,15 @@ describe('la meilleure proposition du classement dans la liste (§ 5.4, § 5.7,
|
|||
expect(tableau.parentElement.scrollWidth).toBeLessThanOrEqual(tableau.parentElement.clientWidth);
|
||||
expect(tableau.getBoundingClientRect().right).toBeLessThanOrEqual(carte.getBoundingClientRect().right);
|
||||
expect(carte.getBoundingClientRect().right).toBeLessThanOrEqual(innerWidth);
|
||||
// Chaque intitulé de colonne tient dans sa cellule, ceux du placement
|
||||
// retenu compris.
|
||||
for (const entete of tous(m, '[data-ecran="accueil"] thead th')) {
|
||||
expect(entete.scrollWidth, entete.textContent).toBeLessThanOrEqual(entete.clientWidth);
|
||||
expect(entete.getBoundingClientRect().right).toBeLessThanOrEqual(carte.getBoundingClientRect().right);
|
||||
}
|
||||
for (const ligne of lignes(m)) {
|
||||
expect(lu(ligne).id).toMatch(/\d/);
|
||||
for (const nom of ['collisionsCumulees', 'rencontresRepetees']) expect(colonne(ligne, nom), nom).toMatch(/\d/);
|
||||
for (const cellule of ligne.cells) {
|
||||
expect(cellule.scrollWidth, cellule.textContent).toBeLessThanOrEqual(cellule.clientWidth);
|
||||
expect(cellule.getBoundingClientRect().right).toBeLessThanOrEqual(innerWidth);
|
||||
|
|
@ -1446,15 +1468,16 @@ describe('le chargement des résumés de la liste (§ 8.1, § 14.5, § 19.2)', (
|
|||
return appliquerCommande('retenirProposition', charge, { id: charge.propositions[1].id }).charge;
|
||||
}
|
||||
|
||||
// Les deux valeurs de résumé d'une ligne, dans l'ordre du rendu.
|
||||
const CELLULES = ['[data-colonne="collisionsCumulees"]', '[data-meilleure-id]'];
|
||||
// Les trois valeurs de résumé d'une ligne, dans l'ordre du rendu : les
|
||||
// deux mesures du retenu, puis la meilleure proposition.
|
||||
const CELLULES = ['[data-colonne="collisionsCumulees"]', '[data-colonne="rencontresRepetees"]', '[data-meilleure-id]'];
|
||||
const cellules = (ligne) => CELLULES.map((selecteur) => ligne.querySelector(selecteur));
|
||||
// Ce que la ligne d'une charge écrit, par la table d'évaluation sollicitée
|
||||
// une seconde fois (§ 19.1).
|
||||
function attendues(charge) {
|
||||
const table = evaluer(charge);
|
||||
const [premiere] = table.propositions;
|
||||
return [table.retenu.mesures.collisionsCumulees, premiere.id].map((n) => format.nombre(n));
|
||||
return [table.retenu.mesures.collisionsCumulees, table.retenu.mesures.rencontresRepetees.choisies, premiere.id].map((n) => format.nombre(n));
|
||||
}
|
||||
// Chaque valeur de la ligne attend : la cellule se dit occupée, porte un
|
||||
// seul signe, muet pour l'accessibilité (aria-hidden, sans rôle ni nom),
|
||||
|
|
@ -1500,12 +1523,12 @@ describe('le chargement des résumés de la liste (§ 8.1, § 14.5, § 19.2)', (
|
|||
expect(colonne(ligne, 'participants')).toBe(format.nombre(charges[ligne.dataset.evenement].participants.length));
|
||||
enAttente(ligne);
|
||||
}
|
||||
expect(signes(m).length).toBe(4);
|
||||
expect(signes(m).length).toBe(6);
|
||||
const region = regionEtat(m);
|
||||
expect(region.textContent).toBe(t('accueil.chargement'));
|
||||
for (let image = 0; image < 3; image += 1) await uneImage();
|
||||
flushSync();
|
||||
expect(signes(m).length).toBe(4);
|
||||
expect(signes(m).length).toBe(6);
|
||||
m.horloge.avancer(0);
|
||||
await vi.waitFor(() => resolue(ligneDe(m, 'alpha'), attendues(charges.alpha)));
|
||||
enAttente(ligneDe(m, 'beta'));
|
||||
|
|
@ -1523,7 +1546,7 @@ describe('le chargement des résumés de la liste (§ 8.1, § 14.5, § 19.2)', (
|
|||
test('démontée avant le premier pas, l’application arrête le flux des résumés : aucun état ne se relit ensuite', async () => {
|
||||
const charges = { alpha: chargeResumee('evt-alpha'), beta: chargeResumee('evt-beta') };
|
||||
const m = await monter({ fichiers: Object.entries(charges).map(([base, charge]) => fichier(`${base}${SUFFIXES.etat}`, etat(charge))) });
|
||||
expect(signes(m).length).toBe(4);
|
||||
expect(signes(m).length).toBe(6);
|
||||
const relus = [];
|
||||
const lireTexte = m.fs.lireTexte;
|
||||
m.fs.lireTexte = (racine, chemin) => {
|
||||
|
|
@ -1540,7 +1563,7 @@ describe('le chargement des résumés de la liste (§ 8.1, § 14.5, § 19.2)', (
|
|||
expect(relus).toEqual([]);
|
||||
});
|
||||
|
||||
test('un état qui ne se relit pas pendant le chargement : « — » dans ses deux valeurs, et l’autre ligne résolue', async () => {
|
||||
test('un état qui ne se relit pas pendant le chargement : « — » dans ses trois valeurs, et l’autre ligne résolue', async () => {
|
||||
const charges = { alpha: chargeResumee('evt-alpha'), beta: chargeResumee('evt-beta') };
|
||||
const m = await monter({ fichiers: Object.entries(charges).map(([base, charge]) => fichier(`${base}${SUFFIXES.etat}`, etat(charge))) });
|
||||
const lireTexte = m.fs.lireTexte;
|
||||
|
|
@ -1549,7 +1572,7 @@ describe('le chargement des résumés de la liste (§ 8.1, § 14.5, § 19.2)', (
|
|||
? Promise.reject(new ErreurStockage('LECTURE', { chemin, cause: 'EIO' }))
|
||||
: lireTexte.call(m.fs, racine, chemin);
|
||||
await chargerResumes(m);
|
||||
resolue(ligneDe(m, 'alpha'), [TIRET, TIRET]);
|
||||
resolue(ligneDe(m, 'alpha'), [TIRET, TIRET, TIRET]);
|
||||
resolue(ligneDe(m, 'beta'), attendues(charges.beta));
|
||||
});
|
||||
|
||||
|
|
@ -1582,8 +1605,10 @@ describe('le chargement des résumés de la liste (§ 8.1, § 14.5, § 19.2)', (
|
|||
|
||||
test('le signe se voit : il tient 3:1 sur ce qui l’entoure dans les deux thèmes, sans atténuation', async () => {
|
||||
const m = await monter({ fichiers: [fichier(`alpha${SUFFIXES.etat}`, etat(chargeResumee('evt-alpha')))] });
|
||||
// Une ligne qui attend : un signe par valeur de résumé, un tracé par
|
||||
// signe.
|
||||
const traces = tous(m, '[data-chargement] path');
|
||||
expect(traces.length).toBe(2);
|
||||
expect(traces.length).toBe(3);
|
||||
for (const theme of ['clair', 'sombre']) {
|
||||
await sousTheme(theme, async () => {
|
||||
for (const trace of traces) {
|
||||
|
|
@ -1794,7 +1819,17 @@ describe('la liste en tableau : en-tête, tuiles, groupes et cartes (§ 8.1, §
|
|||
await chargerResumes(m);
|
||||
const entetes = tous(m, '[data-ecran="accueil"] thead th[scope="colgroup"]');
|
||||
expect(entetes.map(texte)).toEqual([t('accueil.entete.evenement'), t('accueil.entete.retenu'), t('generation.meilleure.titre')]);
|
||||
expect(entetes.map((th) => th.colSpan)).toEqual([4, 1, 1]);
|
||||
expect(entetes.map((th) => th.colSpan)).toEqual([4, 2, 1]);
|
||||
// Les groupes de colonnes, que lisent les techniques d'assistance pour
|
||||
// rattacher une cellule à son en-tête de groupe : un par en-tête, chacun
|
||||
// de la largeur de son en-tête, plus la colonne « Ouvrir », et leur
|
||||
// somme fait une ligne entière.
|
||||
const table = entetes[0].closest('table');
|
||||
const groupesDeColonnes = [...table.querySelectorAll(':scope > colgroup')].map((groupe) => groupe.span);
|
||||
expect(groupesDeColonnes).toEqual([4, 2, 1, 1]);
|
||||
expect(groupesDeColonnes.slice(0, 3)).toEqual(entetes.map((th) => th.colSpan));
|
||||
const largeurDeLigne = [...ligneDe(m, 'gala').cells].reduce((somme, cellule) => somme + cellule.colSpan, 0);
|
||||
expect(groupesDeColonnes.reduce((somme, span) => somme + span, 0)).toBe(largeurDeLigne);
|
||||
expect(fond(entetes[1])).toEqual(jeton('--couleur-etat-retenu-fond'));
|
||||
expect(fond(entetes[2])).toEqual(jeton('--couleur-etat-propose-fond'));
|
||||
// Le groupe porte le signe de son état, de la marque de l'état.
|
||||
|
|
@ -1807,7 +1842,11 @@ describe('la liste en tableau : en-tête, tuiles, groupes et cartes (§ 8.1, §
|
|||
};
|
||||
const ligne = ligneDe(m, 'gala');
|
||||
for (const nom of ['nom', 'etat', 'participants', 'modification']) expect(dans2(ligne.querySelector(`[data-colonne="${nom}"]`), entetes[0]), nom).toBe(true);
|
||||
expect(dans2(ligne.querySelector('[data-colonne="collisionsCumulees"]'), entetes[1])).toBe(true);
|
||||
for (const nom of ['collisionsCumulees', 'rencontresRepetees']) expect(dans2(ligne.querySelector(`[data-colonne="${nom}"]`), entetes[1]), nom).toBe(true);
|
||||
// Le groupe du retenu couvre ses deux colonnes, d'un bord à l'autre.
|
||||
const bornes = ['collisionsCumulees', 'rencontresRepetees'].map((nom) => ligne.querySelector(`[data-colonne="${nom}"]`).getBoundingClientRect());
|
||||
expect(Math.abs(bornes[0].left - entetes[1].getBoundingClientRect().left)).toBeLessThanOrEqual(0.5);
|
||||
expect(Math.abs(bornes[1].right - entetes[1].getBoundingClientRect().right)).toBeLessThanOrEqual(0.5);
|
||||
expect(dans2(ligne.querySelector('[data-meilleure-id]'), entetes[2])).toBe(true);
|
||||
// Le nom est l'en-tête de sa ligne ; la date et le chemin le suivent,
|
||||
// dessous, en plus petit.
|
||||
|
|
|
|||
|
|
@ -14,13 +14,14 @@
|
|||
//
|
||||
// Les en-têtes de colonnes sont groupés : l'événement — nom, état,
|
||||
// participants, dernière modification —, le placement retenu, à la teinte
|
||||
// de Retenu — ses collisions cumulées (COLONNES_DU_RETENU) —, la
|
||||
// meilleure proposition du classement, à la teinte de Proposé, sous le
|
||||
// titre et le libellé du bloc de la génération (MEILLEURE) — son
|
||||
// identifiant —, puis les actions. Chaque valeur est dans son propre
|
||||
// nœud : data-colonne, data-meilleure-id, « — » sans valeur. Le nom est
|
||||
// l'en-tête de sa ligne ; la date et le chemin le suivent en petite
|
||||
// ligne, chacun précédé de son intitulé, que
|
||||
// de Retenu — ses collisions cumulées et ses rencontres répétées choisies,
|
||||
// une colonne chacune (COLONNES_DU_RETENU), que l'en-tête du groupe
|
||||
// couvre —, la meilleure proposition du classement, à la teinte de
|
||||
// Proposé, sous le titre et le libellé du bloc de la génération
|
||||
// (MEILLEURE) — son identifiant —, puis les actions. Chaque valeur est
|
||||
// dans son propre nœud : data-colonne, data-meilleure-id, « — » sans
|
||||
// valeur. Le nom est l'en-tête de sa ligne ; la date et le chemin le
|
||||
// suivent en petite ligne, chacun précédé de son intitulé, que
|
||||
// lisent les technologies d'assistance et que l'œil ne voit pas, puis ses
|
||||
// signalements traduits — les bases qu'un doublon nomme suivent son
|
||||
// texte, chacune dans son propre nœud (data-doublon). L'état est une
|
||||
|
|
@ -33,7 +34,7 @@
|
|||
//
|
||||
// lignes : LigneListe[] de la façade ; resumes : les résumés reçus de
|
||||
// facade.resumer, par base — une ligne sans le sien attend, et chacune de
|
||||
// ses deux valeurs de résumé porte le signe du chargement (Chargement),
|
||||
// ses trois valeurs de résumé porte le signe du chargement (Chargement),
|
||||
// décoratif, dans une cellule qui se dit occupée (aria-busy), jusqu'à ce
|
||||
// qu'il arrive. La région d'état qui suit le tableau
|
||||
// (data-chargement-liste) existe tant que la liste a des lignes, et écrit
|
||||
|
|
@ -44,7 +45,7 @@
|
|||
// du dernier échec, ou null ; apresOperation() : à appeler quand une
|
||||
// opération a changé le dossier.
|
||||
//
|
||||
// L'intitulé de la mesure du retenu et le titre de la meilleure
|
||||
// L'intitulé de chaque mesure du retenu et le titre de la meilleure
|
||||
// proposition s'expliquent dans l'infobulle de la liste, une seule
|
||||
// (Infobulle, primitive) : chaque terme (data-terme, que pose le fragment termeExplique), un en-tête de colonne,
|
||||
// se focalise au clavier, son survol ou son focus ouvre l'explication, le
|
||||
|
|
@ -72,9 +73,9 @@
|
|||
const enChargement = $derived(lignes.some((ligne) => ligneAffichee(ligne, format, resumes.get(ligne.base)).enAttente));
|
||||
const groupes = $derived(groupesDeLaListe(lignes, vue.filtre, vue.groupement));
|
||||
// Le nombre de colonnes du tableau, que l'intitulé d'un groupe couvre :
|
||||
// les quatre de l'événement, celle du retenu, celle de la meilleure
|
||||
// les quatre de l'événement, les deux du retenu, celle de la meilleure
|
||||
// proposition, les actions.
|
||||
const COLONNES = 7;
|
||||
const COLONNES = 8;
|
||||
|
||||
// Le terme expliqué : {ancre, terme, cle, details} — son élément, son nom
|
||||
// et la clé de son explication — ; null sans infobulle.
|
||||
|
|
@ -136,13 +137,13 @@
|
|||
<div class="defilement">
|
||||
<table>
|
||||
<colgroup span="4"></colgroup>
|
||||
<colgroup></colgroup>
|
||||
<colgroup span={COLONNES_DU_RETENU.length}></colgroup>
|
||||
<colgroup></colgroup>
|
||||
<colgroup></colgroup>
|
||||
<thead>
|
||||
<tr>
|
||||
<th scope="colgroup" colspan="4" class="groupe">{t('accueil.entete.evenement')}</th>
|
||||
<th scope="colgroup" class="groupe retenu"
|
||||
<th scope="colgroup" colspan={COLONNES_DU_RETENU.length} class="groupe retenu"
|
||||
><span class="entete-groupe"
|
||||
><span class="signe"><Icone nom="coche" taille={14} /></span><span>{t('accueil.entete.retenu')}</span></span
|
||||
></th
|
||||
|
|
|
|||
|
|
@ -5,13 +5,13 @@
|
|||
// de la liste des événements affichent, sans arithmétique ni texte. Une
|
||||
// ligne de la liste (LigneListe de la façade, evenements.js) devient ses six
|
||||
// informations et ses signalements, mis en forme, puis, son résumé reçu, les
|
||||
// collisions cumulées de son retenu et l'identifiant de la meilleure
|
||||
// proposition de son classement ; les lignes de la liste, les tuiles de ses
|
||||
// filtres par état du plan, le résumé de son en-tête et les groupes de son
|
||||
// tableau ; une question du dossier de travail, ses réponses ; un état
|
||||
// illisible, ses secours ; un refus de la façade, le refus affiché sur la
|
||||
// liste. Chaque clé rendue est écrite ici en entier : aucun composant ne
|
||||
// compose une clé.
|
||||
// collisions cumulées et les rencontres répétées choisies de son retenu et
|
||||
// l'identifiant de la meilleure proposition de son classement ; les lignes
|
||||
// de la liste, les tuiles de ses filtres par état du plan, le résumé de son
|
||||
// en-tête et les groupes de son tableau ; une question du dossier de
|
||||
// travail, ses réponses ; un état illisible, ses secours ; un refus de la
|
||||
// façade, le refus affiché sur la liste. Chaque clé rendue est écrite ici
|
||||
// en entier : aucun composant ne compose une clé.
|
||||
|
||||
// Les états du plan (§ 9) et la clé de leur nom, dans l'ordre du § 9.
|
||||
const CLES_ETATS = new Map([
|
||||
|
|
@ -25,12 +25,15 @@ const CLES_ETATS = new Map([
|
|||
export const ETATS = Object.freeze([...CLES_ETATS.keys()]);
|
||||
|
||||
/**
|
||||
* La mesure du placement retenu qu'une ligne montre (§ 5.4, § 8.1) : ses
|
||||
* collisions cumulées, { colonne, cle, explication } — le nom de sa valeur
|
||||
* dans ligneAffichee et son data-colonne, la clé de son intitulé, celle de
|
||||
* la colonne du tableau des propositions qui porte la même mesure, et celle
|
||||
* de son explication, propre à la liste, que l'infobulle de l'intitulé
|
||||
* montre.
|
||||
* Les mesures du placement retenu qu'une ligne montre (§ 5.4, § 8.1), dans
|
||||
* l'ordre de leurs colonnes : ses collisions cumulées, puis ses rencontres
|
||||
* répétées choisies. Chacune est { colonne, cle, explication } — le nom de
|
||||
* sa valeur dans ligneAffichee et dans le résumé du retenu, qui est aussi son
|
||||
* data-colonne et son data-terme, la clé de son intitulé, celle de la
|
||||
* colonne du tableau des propositions qui porte la même mesure, et celle de
|
||||
* son explication, propre à la liste, que l'infobulle de l'intitulé montre.
|
||||
* L'en-tête du groupe « Placement retenu » couvre autant de colonnes que la
|
||||
* liste en compte.
|
||||
*/
|
||||
export const COLONNES_DU_RETENU = Object.freeze([
|
||||
Object.freeze({
|
||||
|
|
@ -38,6 +41,11 @@ export const COLONNES_DU_RETENU = Object.freeze([
|
|||
cle: 'propositions.colonne.collisionsCumulees',
|
||||
explication: 'accueil.explication.collisionsCumulees',
|
||||
}),
|
||||
Object.freeze({
|
||||
colonne: 'rencontresRepetees',
|
||||
cle: 'propositions.colonne.rencontresRepetees',
|
||||
explication: 'accueil.explication.rencontresRepetees',
|
||||
}),
|
||||
]);
|
||||
|
||||
/**
|
||||
|
|
@ -130,8 +138,9 @@ export function signalementsDe(ligne) {
|
|||
}
|
||||
|
||||
/**
|
||||
* Les six informations d'une ligne (§ 8.1), les collisions cumulées de son
|
||||
* retenu et l'identifiant de la meilleure proposition de son classement,
|
||||
* Les six informations d'une ligne (§ 8.1), les collisions cumulées et les
|
||||
* rencontres répétées choisies de son retenu et l'identifiant de la
|
||||
* meilleure proposition de son classement,
|
||||
* mis en forme, et ses signalements. nom : celui inscrit dans le fichier, ou la base quand l'état
|
||||
* ne se lit pas ; date, participants et dernière modification par format,
|
||||
* la valeur absente quand ils manquent — la dernière modification est
|
||||
|
|
@ -140,21 +149,23 @@ export function signalementsDe(ligne) {
|
|||
* clé de son nom, que le composant traduit ; chemin : tel que la façade
|
||||
* l'affiche, en entier.
|
||||
*
|
||||
* Les deux valeurs de résumé lisent resume, ce que facade.resumer a rendu
|
||||
* pour la ligne : collisionsCumulees, celles de mesuresDuRetenu, par format,
|
||||
* la valeur absente sans retenu mesurable — elles se lisent telles quelles,
|
||||
* jamais refaites ; meilleure.id, l'identifiant de meilleureDuClassement, par
|
||||
* format, la valeur absente sans proposition classée. Sans resume, la ligne
|
||||
* est enAttente : son résumé se calcule encore, et les deux valeurs valent la
|
||||
* valeur absente, que le composant remplace par le signe du chargement.
|
||||
* Les trois valeurs de résumé lisent resume, ce que facade.resumer a rendu
|
||||
* pour la ligne : collisionsCumulees et rencontresRepetees, celles de
|
||||
* mesuresDuRetenu, par format, la valeur absente sans retenu mesurable —
|
||||
* elles se lisent telles quelles, jamais refaites ; meilleure.id,
|
||||
* l'identifiant de meilleureDuClassement, par format, la valeur absente sans
|
||||
* proposition classée. Sans resume, la ligne est enAttente : son résumé se
|
||||
* calcule encore, et les trois valeurs valent la valeur absente, que le
|
||||
* composant remplace par le signe du chargement.
|
||||
*
|
||||
* @param {Object} ligne LigneListe (evenements.js)
|
||||
* @param {ReturnType<import('../../application/format.js').creerFormat>} format
|
||||
* @param {{mesuresDuRetenu: {collisionsCumulees: number}|null,
|
||||
* @param {{mesuresDuRetenu: {collisionsCumulees: number, rencontresRepetees: number}|null,
|
||||
* meilleureDuClassement: {id: number}|null}} [resume]
|
||||
* @returns {{nom: string, date: string, etat: string, participants: string,
|
||||
* modification: string, chemin: string, enAttente: boolean,
|
||||
* collisionsCumulees: string, meilleure: {id: string},
|
||||
* collisionsCumulees: string, rencontresRepetees: string,
|
||||
* meilleure: {id: string},
|
||||
* signalements: Array<{cle: string, details: Object, noms: string[]}>}}
|
||||
*/
|
||||
export function ligneAffichee(ligne, format, resume) {
|
||||
|
|
@ -167,6 +178,7 @@ export function ligneAffichee(ligne, format, resume) {
|
|||
chemin: ligne.chemin,
|
||||
enAttente: resume === undefined,
|
||||
collisionsCumulees: format.nombre(resume?.mesuresDuRetenu?.collisionsCumulees ?? null),
|
||||
rencontresRepetees: format.nombre(resume?.mesuresDuRetenu?.rencontresRepetees ?? null),
|
||||
meilleure: { id: format.nombre(resume?.meilleureDuClassement?.id ?? null) },
|
||||
signalements: signalementsDe(ligne),
|
||||
};
|
||||
|
|
|
|||
|
|
@ -88,6 +88,7 @@ describe('ligneAffichee : les six informations (§ 8.1)', () => {
|
|||
'modification',
|
||||
'nom',
|
||||
'participants',
|
||||
'rencontresRepetees',
|
||||
'signalements',
|
||||
]);
|
||||
assert.equal(affichee.nom, 'Bal des Grisailles');
|
||||
|
|
@ -335,59 +336,85 @@ describe('saisieDe : la saisie d’un nom pour copier ou renommer (§ 8.7)', ()
|
|||
});
|
||||
|
||||
describe('ligneAffichee : les mesures du retenu (§ 5.4, § 8.1)', () => {
|
||||
test('les collisions cumulées du retenu, celles de la façade, mises en forme, sans calcul ici', () => {
|
||||
for (const collisionsCumulees of [500, 7, 0]) {
|
||||
const affichee = ligneAffichee(ligne(), format, { ...AUCUN, mesuresDuRetenu: { collisionsCumulees } });
|
||||
test('les collisions cumulées et les rencontres répétées choisies du retenu, celles de la façade, mises en forme chacune à sa place, sans calcul ici', () => {
|
||||
for (const [collisionsCumulees, rencontresRepetees] of [
|
||||
[500, 3],
|
||||
[7, 12],
|
||||
[0, 0],
|
||||
]) {
|
||||
const affichee = ligneAffichee(ligne(), format, { ...AUCUN, mesuresDuRetenu: { collisionsCumulees, rencontresRepetees } });
|
||||
assert.equal(affichee.collisionsCumulees, format.nombre(collisionsCumulees), String(collisionsCumulees));
|
||||
assert.equal(affichee.rencontresRepetees, format.nombre(rencontresRepetees), String(rencontresRepetees));
|
||||
}
|
||||
});
|
||||
|
||||
test('sans retenu mesurable, et pour une ligne illisible, les collisions valent « — »', () => {
|
||||
test('sans retenu mesurable, et pour une ligne illisible, les collisions et les rencontres répétées valent « — »', () => {
|
||||
for (const champs of [{}, { nom: null, etat: null, participants: null, illisible: { raison: 'VIDE', chemin: null } }]) {
|
||||
const affichee = ligneAffichee(ligne(champs), format, AUCUN);
|
||||
assert.equal(affichee.collisionsCumulees, VIDE);
|
||||
assert.equal(affichee.rencontresRepetees, VIDE);
|
||||
}
|
||||
});
|
||||
|
||||
test('COLONNES_DU_RETENU : les seules collisions cumulées, sous l’intitulé de la colonne du tableau des propositions, avec l’explication propre à la liste ; plus rien de la différence à la cible', () => {
|
||||
test('COLONNES_DU_RETENU : les collisions cumulées puis les rencontres répétées choisies, chacune sous l’intitulé de sa colonne du tableau des propositions, avec son explication propre à la liste ; plus rien de la différence à la cible', () => {
|
||||
assert.deepEqual(COLONNES_DU_RETENU, [
|
||||
{
|
||||
colonne: 'collisionsCumulees',
|
||||
cle: 'propositions.colonne.collisionsCumulees',
|
||||
explication: 'accueil.explication.collisionsCumulees',
|
||||
},
|
||||
{
|
||||
colonne: 'rencontresRepetees',
|
||||
cle: 'propositions.colonne.rencontresRepetees',
|
||||
explication: 'accueil.explication.rencontresRepetees',
|
||||
},
|
||||
]);
|
||||
assert.ok(Object.isFrozen(COLONNES_DU_RETENU));
|
||||
assert.ok(Object.isFrozen(COLONNES_DU_RETENU[0]));
|
||||
for (const colonne of COLONNES_DU_RETENU) assert.ok(Object.isFrozen(colonne), colonne.colonne);
|
||||
assert.equal(t('propositions.colonne.collisionsCumulees'), 'Collisions cumulées (paire et tour)');
|
||||
assert.equal(t('propositions.colonne.rencontresRepetees'), 'Rencontres répétées choisies (paires)');
|
||||
const explication = t('accueil.explication.collisionsCumulees');
|
||||
for (const fragment of ['paire', 'tour', 'Collisions cumulées (paire et tour)', '\u{2014}']) {
|
||||
assert.ok(explication.includes(fragment), fragment);
|
||||
}
|
||||
for (const cle of ['accueil.colonne.diffRedondance', 'accueil.explication.diffRedondance', 'accueil.colonne.collisionsCumulees']) {
|
||||
// L'explication des rencontres répétées dit ce qu'elle compte, d'où elle
|
||||
// vient et quand elle vaut « — », et n'est pas celle des collisions.
|
||||
const repetees = t('accueil.explication.rencontresRepetees');
|
||||
for (const fragment of ['paires', "plus d'un tour", 'réservations', 'Rencontres répétées choisies (paires)', 'retenu', '\u{2014}']) {
|
||||
assert.ok(repetees.includes(fragment), fragment);
|
||||
}
|
||||
assert.notEqual(repetees, explication);
|
||||
for (const cle of [
|
||||
'accueil.colonne.diffRedondance',
|
||||
'accueil.explication.diffRedondance',
|
||||
'accueil.colonne.collisionsCumulees',
|
||||
'accueil.colonne.rencontresRepetees',
|
||||
]) {
|
||||
assert.throws(() => t(cle), RangeError, cle);
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
describe('ligneAffichee : les résumés en attente (§ 8.1)', () => {
|
||||
test('sans résumé reçu, la ligne est en attente et ses deux valeurs de résumé ne disent rien ; reçu, elle ne l’est plus', () => {
|
||||
test('sans résumé reçu, la ligne est en attente et ses trois valeurs de résumé ne disent rien ; reçu, elle ne l’est plus', () => {
|
||||
const attente = ligneAffichee(ligne(), format);
|
||||
assert.equal(attente.enAttente, true);
|
||||
assert.equal(attente.collisionsCumulees, VIDE);
|
||||
assert.equal(attente.rencontresRepetees, VIDE);
|
||||
assert.deepEqual(attente.meilleure, { id: VIDE });
|
||||
assert.equal(ligneAffichee(ligne(), format, AUCUN).enAttente, false);
|
||||
const recu = ligneAffichee(ligne(), format, {
|
||||
mesuresDuRetenu: { collisionsCumulees: 10 },
|
||||
mesuresDuRetenu: { collisionsCumulees: 10, rencontresRepetees: 4 },
|
||||
meilleureDuClassement: { id: 3 },
|
||||
});
|
||||
assert.equal(recu.enAttente, false);
|
||||
assert.equal(recu.collisionsCumulees, format.nombre(10));
|
||||
assert.equal(recu.rencontresRepetees, format.nombre(4));
|
||||
assert.deepEqual(recu.meilleure, { id: format.nombre(3) });
|
||||
});
|
||||
|
||||
test('les six informations ne dépendent pas des résumés : en attente ou non, elles sont les mêmes', () => {
|
||||
const sans = ({ enAttente, collisionsCumulees, meilleure, ...reste }) => reste;
|
||||
const sans = ({ enAttente, collisionsCumulees, rencontresRepetees, meilleure, ...reste }) => reste;
|
||||
assert.deepEqual(sans(ligneAffichee(ligne(), format)), sans(ligneAffichee(ligne(), format, AUCUN)));
|
||||
});
|
||||
});
|
||||
|
|
|
|||
|
|
@ -73,10 +73,10 @@ export const TABLE = new Map([
|
|||
['accueil.colonne.participants', 'Participants'],
|
||||
['accueil.colonne.modification', 'Dernière modification'],
|
||||
['accueil.colonne.chemin', 'Fichier'],
|
||||
// La mesure du placement retenu (§ 5.4, § 8.1) : son intitulé est celui de
|
||||
// la colonne du tableau des propositions qui porte la même mesure ;
|
||||
// l'explication que l'infobulle de l'intitulé donne dit ce que compte le
|
||||
// nombre, d'où il vient, et quand il vaut « — ».
|
||||
// Les mesures du placement retenu (§ 5.4, § 8.1) : l'intitulé de chacune
|
||||
// est celui de la colonne du tableau des propositions qui porte la même
|
||||
// mesure ; l'explication que l'infobulle de l'intitulé donne dit ce que
|
||||
// compte le nombre, d'où il vient, et quand il vaut « — ».
|
||||
[
|
||||
'accueil.explication.collisionsCumulees',
|
||||
'Les collisions du placement retenu. Chaque fois que deux personnes de la même appartenance partagent une ' +
|
||||
|
|
@ -85,6 +85,15 @@ export const TABLE = new Map([
|
|||
"Plus il est bas, mieux c'est. « \u{2014} » quand aucun placement n'est retenu, ou que le retenu ne décrit " +
|
||||
"plus la configuration, ou se contredit.",
|
||||
],
|
||||
[
|
||||
'accueil.explication.rencontresRepetees',
|
||||
"Les rencontres répétées du placement retenu : les paires de personnes réunies à la même table à plus d'un " +
|
||||
'tour que le placement a choisies, comptées une fois par paire : deux personnes ensemble à trois tours font 1. ' +
|
||||
"Une paire que les réservations réunissent à chacune de ses rencontres n'y entre pas. Le même nombre que " +
|
||||
'la colonne « Rencontres répétées choisies (paires) » du tableau des propositions, sur la ligne du retenu. ' +
|
||||
"Plus il est bas, mieux c'est. « \u{2014} » quand aucun placement n'est retenu, ou que le retenu ne décrit " +
|
||||
'plus la configuration, ou se contredit.',
|
||||
],
|
||||
|
||||
// La meilleure proposition du classement de chaque événement (§ 5.4,
|
||||
// § 5.7) : son titre et son identifiant s'écrivent sous les libellés du
|
||||
|
|
|
|||
|
|
@ -643,13 +643,14 @@ export const ECRANS = Object.freeze([
|
|||
mode: null,
|
||||
async atteindre(contexte) {
|
||||
// La liste paraît avant ses résumés : tant que l'horloge pilotée
|
||||
// n'avance pas, chaque valeur de résumé porte le signe du chargement.
|
||||
// Rien ici ne l'avance : les signes restent pendant les relevés des
|
||||
// deux thèmes, où le trait de chacun se mesure comme la marque
|
||||
// « chargement ».
|
||||
// n'avance pas, chaque valeur de résumé porte le signe du chargement :
|
||||
// trois par ligne, les deux mesures du retenu et la meilleure
|
||||
// proposition. Rien ici ne l'avance : les signes restent pendant les
|
||||
// relevés des deux thèmes, où le trait de chacun se mesure comme la
|
||||
// marque « chargement ».
|
||||
const m = await contexte.monter({ fichiers: [fichierEtat(chargePrincipale()), fichierEtat(chargeGelee(), 'gelee')] });
|
||||
await attendre('le signe du chargement dans chaque valeur de résumé', () =>
|
||||
[BASE, 'gelee'].every((base) => m.racine.querySelectorAll(`[data-evenement="${base}"] [aria-busy="true"] [data-chargement]`).length === 2),
|
||||
[BASE, 'gelee'].every((base) => m.racine.querySelectorAll(`[data-evenement="${base}"] [aria-busy="true"] [data-chargement]`).length === 3),
|
||||
);
|
||||
return m.racine;
|
||||
},
|
||||
|
|
@ -659,17 +660,19 @@ export const ECRANS = Object.freeze([
|
|||
mode: null,
|
||||
async atteindre(contexte) {
|
||||
// La principale, sa proposition retenue : la ligne montre les
|
||||
// collisions cumulées du retenu et la meilleure proposition du
|
||||
// classement, et le pointeur réel sur l'intitulé des collisions — un
|
||||
// en-tête de colonne du tableau, une fois pour la liste — ouvre son
|
||||
// infobulle.
|
||||
// collisions cumulées et les rencontres répétées choisies du retenu et
|
||||
// la meilleure proposition du classement, et le pointeur réel sur
|
||||
// l'intitulé des collisions — un en-tête de colonne du tableau, une
|
||||
// fois pour la liste — ouvre son infobulle.
|
||||
const principale = chargePrincipale();
|
||||
const charge = executer(principale, 'retenirProposition', { id: principale.propositions[0].id });
|
||||
const m = await contexte.monter({ fichiers: [fichierEtat(charge)] });
|
||||
const ligne = `[data-evenement="${BASE}"]`;
|
||||
await chargerResumes(m);
|
||||
await attendre('les collisions du retenu et la meilleure proposition', () =>
|
||||
['[data-colonne="collisionsCumulees"]', '[data-meilleure-id]'].every((valeur) => /\d/.test(m.racine.querySelector(`${ligne} ${valeur}`)?.textContent ?? '')),
|
||||
await attendre('les mesures du retenu et la meilleure proposition', () =>
|
||||
['[data-colonne="collisionsCumulees"]', '[data-colonne="rencontresRepetees"]', '[data-meilleure-id]'].every((valeur) =>
|
||||
/\d/.test(m.racine.querySelector(`${ligne} ${valeur}`)?.textContent ?? ''),
|
||||
),
|
||||
);
|
||||
const { x, y } = await centre(await element(m, '[data-ecran="accueil"] thead [data-terme="collisionsCumulees"]'));
|
||||
await pointer('bouger', x, y);
|
||||
|
|
|
|||
Loading…
Reference in a new issue