gestion_table_tournante_libre/src/interface/APropos.navigateur.test.js
Mathieu Benoit c6082c3af2 [IMP] proposals, generation, badges, preview: cards and grouped headers
The proposal table had a dozen measure columns with no grouping. Five
group headers now gather neighbouring measures without changing any
column text; numbers align right in tabular figures, and the kept
placement row wears the Retenu tint and a « retenue » pill. Generation
settings and progress, badges and the paper choice become cards, each
paper a distinct choice; the preview toolbar groups its commands. The
printed paper is unchanged. Checked: node 172, browser 162.

--- FR ---

[IMP] propositions, génération, badges, aperçu : cartes, intitulés

Le tableau des propositions alignait une douzaine de mesures sans
groupe. Cinq intitulés de groupe réunissent les mesures voisines sans
changer le texte d'aucune colonne ; les nombres s'alignent à droite en
chiffres tabulaires, la ligne du retenu porte la teinte Retenu et une
pastille « retenue ». Réglages et avancement, badges et papier
deviennent des cartes, chaque papier un choix distinct ; la barre de
l'aperçu groupe ses commandes. Le papier imprimé ne change pas.
Vérifié : node 172, navigateur 162.

Assisted-by: Claude Opus 5.5
2026-10-11 00:58:07 -04:00

122 lines
5.4 KiB
JavaScript
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

// © 2026 TechnoLibre (http://www.technolibre.ca)
// License AGPL-3.0 or later (https://www.gnu.org/licenses/agpl)
// Le panneau « À propos » dans un vrai moteur de rendu (§ 18.6) : monté
// seul, sur un presse-papiers d'épreuve qui relève ce qu'on lui remet ou
// refuse. Le bloc copié est celui qu'on lui passe ; l'ouverture depuis le
// bandeau et la composition du bloc s'éprouvent dans App.navigateur.test.js.
//
// Le projet navigateur emploie expect, et test et describe de Vitest
// (§ 14.8).
import './jetons.css';
import { flushSync, mount, unmount } from 'svelte';
import { afterEach, describe, expect, test, vi } from 'vitest';
import { creerHorlogePilotee } from '../application/horloge.js';
import { TABLES_APPLICATION } from '../application/libelles.js';
import { creerTraduction } from '../application/traduction.js';
import { couleurDeJeton } from '../../test/navigateur/captures.js';
import { composantes, contraste, couleurTexte, fondPeintSous, sousTheme } from '../../test/navigateur/rendu.js';
import APropos from './APropos.svelte';
import { TABLES_INTERFACE } from './libelles.js';
const { t } = creerTraduction([...TABLES_APPLICATION, ...TABLES_INTERFACE]);
// Un bloc inventé, de la forme d'une ligne d'« À propos ».
const BLOC = 'Logiciel témoin \u{00B7} forme d\u{2019}épreuve \u{00B7} plateforme simulée';
const montes = [];
afterEach(() => {
for (const { composant, conteneur } of montes.splice(0)) {
unmount(composant);
conteneur.remove();
}
});
// Un presse-papiers d'épreuve : relève chaque copie, ou lève refus.
function pressePapiersDEpreuve({ refus = null } = {}) {
const copies = [];
return {
copies,
async copier(texte) {
if (refus !== null) throw refus;
copies.push(texte);
},
};
}
function monter(props) {
const conteneur = document.createElement('div');
document.body.prepend(conteneur);
const contexte = new Map([
['t', t],
['horloge', creerHorlogePilotee()],
]);
const composant = mount(APropos, { target: conteneur, props, context: contexte });
flushSync();
montes.push({ composant, conteneur });
return conteneur;
}
// La carte qui porte un élément : le plus proche ancêtre peint du fond de la
// carte, au rayon de 12 px ; null sans elle.
function carteDe(element) {
const fond = couleurDeJeton('--couleur-carte-fond').join();
for (let n = element.parentElement; n !== null; n = n.parentElement) {
const style = getComputedStyle(n);
if (composantes(style.backgroundColor).join() === fond && style.borderTopLeftRadius === '12px') return n;
}
return null;
}
describe('« À propos » : l’habillage (§ 18.6)', () => {
test('le titre au-dessus d’une carte qui porte le bloc, sur le fond sourd, et les commandes ; « Copier » est l’action principale, dans les deux thèmes', async () => {
const conteneur = monter({ bloc: BLOC, pressePapiers: pressePapiersDEpreuve(), fermer: () => {} });
const ecran = conteneur.querySelector('[data-ecran="apropos"]');
const titre = ecran.querySelector('h1');
expect(titre.textContent).toBe(t('coquille.apropos.titre'));
const bloc = ecran.querySelector('[data-bloc]');
expect(bloc.textContent).toBe(BLOC);
const carte = carteDe(bloc);
expect(carte, 'carte du bloc').not.toBeNull();
expect(ecran.contains(carte)).toBe(true);
expect(carte.contains(titre)).toBe(false);
expect(carte.contains(ecran.querySelector('[data-commande="copier"]'))).toBe(true);
expect(carte.contains(ecran.querySelector('[data-commande="fermerApropos"]'))).toBe(true);
for (const theme of ['clair', 'sombre']) {
await sousTheme(theme, async () => {
const [fond] = fondPeintSous(bloc);
expect(fond.join(), theme).toBe(couleurDeJeton('--couleur-carte-fond-sourd').join());
expect(contraste(couleurTexte(bloc), fond), theme).toBeGreaterThanOrEqual(4.5);
const copier = getComputedStyle(ecran.querySelector('[data-commande="copier"]'));
expect(composantes(copier.backgroundColor).join(), theme).toBe(couleurDeJeton('--couleur-action-fond').join());
});
}
});
test('« Copier » remet exactement le bloc affiché et le dit ; refusée, la copie le dit dans l’encre du refus', async () => {
const presse = pressePapiersDEpreuve();
const conteneur = monter({ bloc: BLOC, pressePapiers: presse, fermer: () => {} });
conteneur.querySelector('[data-commande="copier"]').click();
await vi.waitFor(() => expect(presse.copies).toEqual([BLOC]));
await vi.waitFor(() => {
flushSync();
expect(conteneur.querySelector('[role="status"]')?.textContent).toBe(t('coquille.apropos.copie'));
});
const refuse = monter({ bloc: BLOC, pressePapiers: pressePapiersDEpreuve({ refus: new Error('refusé') }), fermer: () => {} });
refuse.querySelector('[data-commande="copier"]').click();
await vi.waitFor(() => {
flushSync();
expect(refuse.querySelector('[data-refus="copier"]')?.textContent).toBe(t('coquille.apropos.copieRefusee'));
});
const message = refuse.querySelector('[data-refus="copier"]');
expect(couleurTexte(message).join()).toBe(couleurDeJeton('--couleur-refus').join());
});
test('« Fermer » appelle fermer une fois', () => {
const fermer = vi.fn();
const conteneur = monter({ bloc: BLOC, pressePapiers: pressePapiersDEpreuve(), fermer });
conteneur.querySelector('[data-commande="fermerApropos"]').click();
expect(fermer).toHaveBeenCalledTimes(1);
});
});