[ADD] quality page views: header, profiles, memberships, pairs, goals
The Qualité tab now draws its five views from the placement's detail.
A numeric header with each figure's percentage and worst case; the
profile as counts with the diversity bound dotted, and as percentages
on a second chart; membership collisions and a table grid behind a
three-position selector, seats by default; repeated pairs with matrix
and a card section; goals per round on servable couples and
facilitators over the evening and per round; the course of the search.
Each chart has its twin table and keyboard access.
Checked: node 5083, navigateur 1102, node-long 128; captures stable.
--- FR ---
[ADD] vues de la qualité : en-tête, profils, appartenances, paires
L'onglet Qualité dessine désormais ses cinq vues à partir du détail du
placement : un en-tête chiffré avec le pourcentage et le pire cas de
chaque chiffre ; le profil en comptes, borne de diversité en pointillé,
et en pourcentages sur un second graphique ; les collisions et la grille
des tables derrière un sélecteur à trois positions, les sièges par
défaut ; les paires répétées, leur matrice et la fiche ; les objectifs
par tour et les animateurs sur la soirée et par tour ; la course. Chaque
graphique a son tableau jumeau et se parcourt au clavier.
Vérifié : node 5083, navigateur 1102, longues 128 ; captures stables.
Assisted-by: Claude Opus 5.5
2026-10-12 11:26:02 -04:00
// © 2026 TechnoLibre (http://www.technolibre.ca)
// License AGPL-3.0 or later (https://www.gnu.org/licenses/agpl)
// La rubrique « Rencontres » de la fiche dans un vrai moteur de rendu
// (§ 12.5, B8, B14, D33) : la ligne de la personne dans le placement que le
// plan montre, sous la ligne de source, les plus revues d'abord, égale à sa
// ligne de la matrice de la page de qualité ; une phrase pour une source qui
// ne se mesure pas et pour une personne exclue ; absente sans placement,
// sans phrase ; vivante en lecture et sur un plan bloqué. Chaque épreuve
// monte App par monterApp, au cadre de 1 366 × 700, ouvre la section des
// participants et la fiche, et lit le rendu par le texte et les attributs
// data-…, jamais par une classe ; la dernière monte la fiche seule, sur
// une séance en lecture simulée, et change la proposition demandée au
// plan. Les noms d'épreuve sont inventés.
import { afterEach , beforeEach , describe , expect , test } from 'vitest' ;
import { flushSync , mount , tick , unmount } from 'svelte' ;
import { writable } from 'svelte/store' ;
import { appliquerCommande } from '../../application/commandes.js' ;
import { rencontresDePersonne } from '../../application/evaluation.js' ;
import { TABLES _APPLICATION } from '../../application/libelles.js' ;
import { creerTraduction } from '../../application/traduction.js' ;
import { rechercher } from '../../moteur/recherche.js' ;
import { serialiser } from '../../stockage/canonique.js' ;
import { configurationDepuisCharge , creerCharge } from '../../stockage/document.js' ;
import { SUFFIXES } from '../../stockage/noms.js' ;
import { VERSION } from '../../version.genere.js' ;
[IMP] quality page: deferred charts, cached text, hash-free gestures
Opening the quality tab measured text and forced layout per chart, and every
gesture rehashed each placement because a cloned load lost its fingerprints.
A gesture now inherits the fingerprint of an unchanged placement; charts far
from view draw on approach, holding their drawn height and their legend, so
a link or a scroll lands where the fully drawn page puts it.
Checked, 200 propositions: tab opening 84.5 -> 27.1 ms (limit 50), 4x CPU
370.7 -> 113.9 ms, 1,973 -> 485 elements, a gesture back at its prior cost.
Every memePlacement mutant fails its own test; 44 captures byte-identical.
--- FR ---
[IMP] page qualité : graphiques différés, texte en cache, gestes légers
L'onglet Qualité mesurait le texte et forçait la mise en page à chaque
graphique, et chaque geste rehachait chaque placement, la charge clonée
perdant ses empreintes. Un geste hérite de l'empreinte d'un placement
inchangé ; un graphique loin de la vue se dessine à l'approche, sa hauteur
et sa légende déjà posées : un lien ou un défilement arrive où le met la
page entièrement dessinée.
Vérifié, 200 propositions : ouverture 84,5 -> 27,1 ms (limite 50), CPU 4x
370,7 -> 113,9 ms, 1 973 -> 485 éléments, un geste revenu à son coût.
Chaque mutant de memePlacement fait échouer son test ; 44 captures identiques.
Assisted-by: Claude Opus 5.5
2026-10-12 22:31:46 -04:00
import { dessinerGraphiques , fixerCadre } from '../../../test/navigateur/entrees.js' ;
[ADD] quality page views: header, profiles, memberships, pairs, goals
The Qualité tab now draws its five views from the placement's detail.
A numeric header with each figure's percentage and worst case; the
profile as counts with the diversity bound dotted, and as percentages
on a second chart; membership collisions and a table grid behind a
three-position selector, seats by default; repeated pairs with matrix
and a card section; goals per round on servable couples and
facilitators over the evening and per round; the course of the search.
Each chart has its twin table and keyboard access.
Checked: node 5083, navigateur 1102, node-long 128; captures stable.
--- FR ---
[ADD] vues de la qualité : en-tête, profils, appartenances, paires
L'onglet Qualité dessine désormais ses cinq vues à partir du détail du
placement : un en-tête chiffré avec le pourcentage et le pire cas de
chaque chiffre ; le profil en comptes, borne de diversité en pointillé,
et en pourcentages sur un second graphique ; les collisions et la grille
des tables derrière un sélecteur à trois positions, les sièges par
défaut ; les paires répétées, leur matrice et la fiche ; les objectifs
par tour et les animateurs sur la soirée et par tour ; la course. Chaque
graphique a son tableau jumeau et se parcourt au clavier.
Vérifié : node 5083, navigateur 1102, longues 128 ; captures stables.
Assisted-by: Claude Opus 5.5
2026-10-12 11:26:02 -04:00
import { monterApp } from '../../../test/navigateur/montage.js' ;
import { creerEtatUi } from '../etat_ui.js' ;
import { TABLES _INTERFACE } from '../libelles.js' ;
import Fiche from './Fiche.svelte' ;
const { t } = creerTraduction ( [ ... TABLES _APPLICATION , ... TABLES _INTERFACE ] ) ;
const V1 = VERSION . affichee ;
const BASE = 'soiree' ;
// Douze personnes inventées, quatre tables de trois numérotées 11 à 14, six
// tours ; les deux premières réservées ensemble à la table 11 à chaque tour.
const NOMS = [
[ 'Ombrelle' , 'Iris' ] ,
[ 'Pervenche' , 'Théo' ] ,
[ 'Lacasse' , 'Ondine' ] ,
[ 'Tourteau' , 'Basile' ] ,
[ 'Quenouille' , 'Maëlle' ] ,
[ 'Varech' , 'Sidonie' ] ,
[ 'Bruyère' , 'Aurèle' ] ,
[ 'Embrun' , 'Clovis' ] ,
[ 'Sablière' , 'Noé' ] ,
[ 'Goémon' , 'Perrine' ] ,
[ 'Ajonc' , 'Tancrède' ] ,
[ 'Fougeraie' , 'Ysaline' ] ,
] ;
function chargeDouze ( ) {
const charge = creerCharge ( { id : 'evt-fiche' , nom : 'Atelier Ressac' , siegesParDefaut : 3 , tours : 6 } ) ;
charge . participants = NOMS . map ( ( [ nom , prenom ] , rang ) => ( {
id : rang + 1 ,
nom ,
prenom ,
appartenance : rang % 2 === 0 ? 'Coopérative Aubier' : 'Atelier Ressac' ,
courriel : null ,
titrePressenti : null ,
notes : null ,
exclu : false ,
} ) ) ;
charge . tables = [ 1 , 2 , 3 , 4 ] . map ( ( id ) => ( { id , numero : 10 + id , sieges : null , forme : 'ronde' , position : { x : 300 * id , y : 0 } } ) ) ;
charge . reservations = [ 1 , 2 ] . map ( ( participant ) => ( { participant , table : 1 , siege : null , portee : 'tous' , tour : null } ) ) ;
charge . prochainsIds = { participant : 13 , table : 5 , proposition : 1 } ;
return charge ;
}
function generee ( charge ) {
const configuration = configurationDepuisCharge ( charge ) ;
const propositions = rechercher ( configuration , { graine : 5 , arret : 60 , nombre : 2 } ) ;
return appliquerCommande ( 'enregistrerGeneration' , charge , { propositions , configuration , produitVersion : V1 } ) . charge ;
}
const executer = ( charge , nom , args ) => appliquerCommande ( nom , charge , args ) . charge ;
let montes = [ ] ;
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 ( [ ] ) ;
} ) ;
async function attendre ( assertion ) {
for ( let essai = 0 ; ; essai += 1 ) {
flushSync ( ) ;
try {
return assertion ( ) ;
} catch ( erreur ) {
if ( essai >= 200 ) throw erreur ;
[IMP] quality page: deferred charts, cached text, hash-free gestures
Opening the quality tab measured text and forced layout per chart, and every
gesture rehashed each placement because a cloned load lost its fingerprints.
A gesture now inherits the fingerprint of an unchanged placement; charts far
from view draw on approach, holding their drawn height and their legend, so
a link or a scroll lands where the fully drawn page puts it.
Checked, 200 propositions: tab opening 84.5 -> 27.1 ms (limit 50), 4x CPU
370.7 -> 113.9 ms, 1,973 -> 485 elements, a gesture back at its prior cost.
Every memePlacement mutant fails its own test; 44 captures byte-identical.
--- FR ---
[IMP] page qualité : graphiques différés, texte en cache, gestes légers
L'onglet Qualité mesurait le texte et forçait la mise en page à chaque
graphique, et chaque geste rehachait chaque placement, la charge clonée
perdant ses empreintes. Un geste hérite de l'empreinte d'un placement
inchangé ; un graphique loin de la vue se dessine à l'approche, sa hauteur
et sa légende déjà posées : un lien ou un défilement arrive où le met la
page entièrement dessinée.
Vérifié, 200 propositions : ouverture 84,5 -> 27,1 ms (limite 50), CPU 4x
370,7 -> 113,9 ms, 1 973 -> 485 éléments, un geste revenu à son coût.
Chaque mutant de memePlacement fait échouer son test ; 44 captures identiques.
Assisted-by: Claude Opus 5.5
2026-10-12 22:31:46 -04:00
// Un graphique de la page de qualité que l'assertion lit attend
// peut-être d'approcher de la zone visible : chacun se dessine, puis
// elle se relit.
await dessinerGraphiques ( document . body ) ;
[ADD] quality page views: header, profiles, memberships, pairs, goals
The Qualité tab now draws its five views from the placement's detail.
A numeric header with each figure's percentage and worst case; the
profile as counts with the diversity bound dotted, and as percentages
on a second chart; membership collisions and a table grid behind a
three-position selector, seats by default; repeated pairs with matrix
and a card section; goals per round on servable couples and
facilitators over the evening and per round; the course of the search.
Each chart has its twin table and keyboard access.
Checked: node 5083, navigateur 1102, node-long 128; captures stable.
--- FR ---
[ADD] vues de la qualité : en-tête, profils, appartenances, paires
L'onglet Qualité dessine désormais ses cinq vues à partir du détail du
placement : un en-tête chiffré avec le pourcentage et le pire cas de
chaque chiffre ; le profil en comptes, borne de diversité en pointillé,
et en pourcentages sur un second graphique ; les collisions et la grille
des tables derrière un sélecteur à trois positions, les sièges par
défaut ; les paires répétées, leur matrice et la fiche ; les objectifs
par tour et les animateurs sur la soirée et par tour ; la course. Chaque
graphique a son tableau jumeau et se parcourt au clavier.
Vérifié : node 5083, navigateur 1102, longues 128 ; captures stables.
Assisted-by: Claude Opus 5.5
2026-10-12 11:26:02 -04:00
await new Promise ( ( resoudre ) => requestAnimationFrame ( resoudre ) ) ;
}
}
}
// La charge posée en fichier d'état, ouverte — en écriture sauf mode
// 'lecture' —, la proposition montrer demandée au plan, la section des
// participants et la fiche de la personne ouvertes.
async function ouvrirFiche ( charge , personne , { mode = 'ecriture' , montrer = null } = { } ) {
const texte = serialiser ( charge , { revision : 1 , produitVersion : V1 } ) ;
const m = await monterApp ( { fichiers : [ { racine : 'documents' , chemin : ` ${ BASE } ${ SUFFIXES . etat } ` , texte } ] } ) ;
montes . push ( m ) ;
await m . seance . ouvrir ( BASE ) ;
if ( mode === 'ecriture' ) await m . seance . passerEnEcriture ( ) ;
if ( montrer !== null ) m . ui . montrerProposition ( montrer ) ;
m . ui . allerA ( 'participants' ) ;
m . ui . ouvrirFiche ( personne ) ;
flushSync ( ) ;
const fiche = await attendre ( ( ) => {
const trouvee = m . racine . querySelector ( ` [data-fiche=" ${ personne } "] ` ) ;
expect ( trouvee ) . not . toBeNull ( ) ;
return trouvee ;
} ) ;
return { m , fiche } ;
}
const texteDe = ( element ) => element . textContent . replace ( /\s+/g , ' ' ) . trim ( ) ;
const rubrique = ( fiche ) => fiche . querySelector ( '[data-rubrique-rencontres]' ) ;
// Les lignes de la rubrique : [nom, fois, tours], le nom lu dans le premier
// élément de l'intitulé de ligne, le reste dans le texte de chaque cellule.
const lignesDe = ( section ) =>
[ ... section . querySelectorAll ( 'tbody tr' ) ] . map ( ( ligne ) => {
const [ intitule , ... cellules ] = ligne . querySelectorAll ( 'th, td' ) ;
return [ texteDe ( intitule . firstElementChild ) , ... cellules . map ( texteDe ) ] ;
} ) ;
// Ce que l'intitulé d'une ligne écrit après le nom, un texte par élément :
// l'identifiant d'un homonyme, puis l'appartenance.
const secondairesDe = ( ligne ) => [ ... ligne . querySelector ( 'th' ) . children ] . slice ( 1 ) . map ( texteDe ) ;
describe ( 'la rubrique « Rencontres » de la fiche (§ 12.5, D33)' , ( ) => {
const lue = generee ( chargeDouze ( ) ) ;
const id = lue . propositions [ 0 ] . id ;
test ( 'sous la ligne de source, les personnes rencontrées dans la proposition montrée, les plus revues d’ abord, leurs tours et tables écrits' , async ( ) => {
const { fiche } = await ouvrirFiche ( lue , 1 , { montrer : id } ) ;
const section = rubrique ( fiche ) ;
expect ( section ) . not . toBeNull ( ) ;
expect ( texteDe ( section . querySelector ( 'h4' ) ) ) . toBe ( t ( 'participants.fiche.rencontres.titre' ) ) ;
expect ( texteDe ( section ) ) . toContain (
` ${ t ( 'participants.fiche.rencontres.intitule' ) } ${ t ( 'participants.fiche.rencontres.proposition' , { proposition : id } )} ` ,
) ;
const attendues = rencontresDePersonne ( lue , id , 1 ) . lignes . map ( ( { nom , fois , tours } ) => [
nom ,
String ( fois ) ,
tours . map ( ( { tour , table } ) => t ( 'participants.fiche.rencontres.tour' , { tour : tour + 1 , table : 10 + table } ) ) . join ( ' ; ' ) ,
] ) ;
expect ( lignesDe ( section ) ) . toEqual ( attendues ) ;
expect ( lignesDe ( section ) [ 0 ] . slice ( 0 , 2 ) ) . toEqual ( [ 'Théo Pervenche' , '6' ] ) ;
} ) ;
test ( 'la ligne de la fiche égale celle de la matrice de la page de qualité, pour chaque personne' , async ( ) => {
const { m } = await ouvrirFiche ( lue , 1 , { montrer : id } ) ;
m . ui . allerA ( 'qualite' ) ;
const grille = await attendre ( ( ) => {
const trouvee = m . racine . querySelector ( '[data-vue-qualite="paires"] [data-graphique="matrice"]' ) ;
expect ( trouvee ? . querySelectorAll ( '[data-case]' ) ) . toHaveLength ( 144 ) ;
return trouvee ;
} ) ;
// Les intitulés de ligne, « rang. nom », dans l'ordre de la matrice.
const ordre = [ ... grille . querySelectorAll ( 'text' ) ] . map ( ( texte ) => texte . textContent ) . filter ( ( texte ) => / ^ \ d + \ . / . test ( texte ) ) ;
const nomDe = ( intitule ) => intitule . replace ( /^\d+\. / , '' ) ;
const matrice = ordre . map ( ( _ , i ) =>
ordre
. map ( ( intitule , j ) => [ nomDe ( intitule ) , texteDe ( grille . querySelector ( ` [data-case=" ${ i } : ${ j } "] ` ) ) ] )
. filter ( ( [ , fois ] ) => fois !== '' && fois !== '0' ) ,
) ;
for ( const [ i , intitule ] of ordre . entries ( ) ) {
const personne = lue . participants . find ( ( { nom , prenom } ) => ` ${ prenom } ${ nom } ` === nomDe ( intitule ) ) . id ;
m . ui . allerA ( 'participants' ) ;
m . ui . ouvrirFiche ( personne ) ;
const section = await attendre ( ( ) => {
const trouvee = rubrique ( m . racine . querySelector ( ` [data-fiche=" ${ personne } "] ` ) ) ;
expect ( trouvee ) . not . toBeNull ( ) ;
return trouvee ;
} ) ;
const deLaFiche = lignesDe ( section ) . map ( ( [ nom , fois ] ) => [ nom , fois ] ) ;
const parNom = ( a , b ) => ( a [ 0 ] < b [ 0 ] ? - 1 : a [ 0 ] > b [ 0 ] ? 1 : 0 ) ;
expect ( [ ... deLaFiche ] . sort ( parNom ) , intitule ) . toEqual ( [ ... matrice [ i ] ] . sort ( parNom ) ) ;
}
} ) ;
test ( 'sans proposition montrée, le retenu ; vivante en lecture et sur un plan bloqué, sans écriture' , async ( ) => {
const retenue = executer ( lue , 'retenirProposition' , { id : lue . propositions [ 1 ] . id } ) ;
for ( const charge of [ retenue , executer ( retenue , 'changerEtat' , { etat : 'bloque' } ) ] ) {
const { m , fiche } = await ouvrirFiche ( charge , 3 , { mode : 'lecture' } ) ;
const ecritures = m . trace . length ;
const section = rubrique ( fiche ) ;
expect ( section ) . not . toBeNull ( ) ;
expect ( texteDe ( section ) ) . toContain ( ` ${ t ( 'participants.fiche.rencontres.intitule' ) } ${ t ( 'participants.fiche.rencontres.retenu' ) } ` ) ;
expect ( lignesDe ( section ) . length ) . toBe ( rencontresDePersonne ( charge , 'retenu' , 3 ) . lignes . length ) ;
expect ( section . querySelector ( 'button, [data-refus]' ) ) . toBeNull ( ) ;
expect ( m . trace . length ) . toBe ( ecritures ) ;
}
} ) ;
test ( 'une proposition demandée au plan l’ emporte sur le retenu : la fiche mesure la même source que la page de qualité' , async ( ) => {
const retenue = executer ( lue , 'retenirProposition' , { id : lue . propositions [ 1 ] . id } ) ;
expect ( id ) . not . toBe ( lue . propositions [ 1 ] . id ) ;
const { fiche } = await ouvrirFiche ( retenue , 3 , { montrer : id } ) ;
const section = rubrique ( fiche ) ;
expect ( texteDe ( section ) ) . toContain (
` ${ t ( 'participants.fiche.rencontres.intitule' ) } ${ t ( 'participants.fiche.rencontres.proposition' , { proposition : id } )} ` ,
) ;
expect ( texteDe ( section ) ) . not . toContain ( t ( 'participants.fiche.rencontres.retenu' ) ) ;
const attendues = rencontresDePersonne ( retenue , id , 3 ) . lignes . map ( ( { nom , fois } ) => [ nom , String ( fois ) ] ) ;
expect ( lignesDe ( section ) . map ( ( [ nom , fois ] ) => [ nom , fois ] ) ) . toEqual ( attendues ) ;
} ) ;
test ( 'une personne exclue : la phrase qui le dit, sans ligne ; une source en dérive : la phrase du placement non mesurable' , async ( ) => {
const exclue = structuredClone ( lue ) ;
exclue . participants [ 2 ] . exclu = true ;
const { fiche } = await ouvrirFiche ( exclue , 3 , { montrer : id } ) ;
expect ( texteDe ( rubrique ( fiche ) ) ) . toContain ( t ( 'participants.fiche.rencontres.exclue' ) ) ;
expect ( rubrique ( fiche ) . querySelector ( 'table' ) ) . toBeNull ( ) ;
const { fiche : autre } = await ouvrirFiche ( exclue , 4 , { montrer : id } ) ;
expect ( texteDe ( rubrique ( autre ) ) ) . toContain ( t ( 'participants.fiche.rencontres.nonMesurable' ) ) ;
expect ( rubrique ( autre ) . querySelector ( 'table' ) ) . toBeNull ( ) ;
} ) ;
test ( 'deux homonymes rencontrés : chaque ligne écrit, après le nom, l’ identifiant puis l’ appartenance, et les deux lignes diffèrent' , async ( ) => {
// La personne 2, réservée avec la personne 1, prend son nom : qui
// rencontre l'une rencontre l'autre.
const homonymes = structuredClone ( lue ) ;
Object . assign ( homonymes . participants [ 1 ] , { nom : 'Ombrelle' , prenom : 'Iris' } ) ;
const temoin = [ 3 , 4 , 5 , 6 , 7 , 8 , 9 , 10 , 11 , 12 ] . find ( ( personne ) =>
rencontresDePersonne ( homonymes , id , personne ) . lignes . some ( ( ligne ) => ligne . id === 1 ) ,
) ;
const { fiche } = await ouvrirFiche ( homonymes , temoin , { montrer : id } ) ;
const rangees = [ ... rubrique ( fiche ) . querySelectorAll ( 'tbody tr' ) ] ;
const deLaPersonne = ( personne ) =>
rangees [ rencontresDePersonne ( homonymes , id , temoin ) . lignes . findIndex ( ( ligne ) => ligne . id === personne ) ] ;
const [ une , autre ] = [ 1 , 2 ] . map ( deLaPersonne ) ;
expect ( texteDe ( une . querySelector ( 'th' ) . firstElementChild ) ) . toBe ( 'Iris Ombrelle' ) ;
expect ( texteDe ( autre . querySelector ( 'th' ) . firstElementChild ) ) . toBe ( 'Iris Ombrelle' ) ;
expect ( secondairesDe ( une ) ) . toEqual ( [ t ( 'qualite.paires.homonyme' , { id : 1 } ) , 'Coopérative Aubier' ] ) ;
expect ( secondairesDe ( autre ) ) . toEqual ( [ t ( 'qualite.paires.homonyme' , { id : 2 } ) , 'Atelier Ressac' ] ) ;
expect ( texteDe ( une ) ) . not . toBe ( texteDe ( autre ) ) ;
// Les autres : le nom, puis l'appartenance seule.
for ( const rangee of rangees . filter ( ( r ) => r !== une && r !== autre ) ) {
expect ( secondairesDe ( rangee ) ) . toHaveLength ( 1 ) ;
expect ( secondairesDe ( rangee ) [ 0 ] ) . not . toMatch ( /n°/ ) ;
}
} ) ;
test ( 'sans placement — ni proposition ni retenu —, la rubrique est absente, sans phrase' , async ( ) => {
const { fiche } = await ouvrirFiche ( chargeDouze ( ) , 1 ) ;
expect ( rubrique ( fiche ) ) . toBeNull ( ) ;
for ( const cle of [ 'titre' , 'nonMesurable' , 'exclue' , 'aucune' ] ) {
expect ( texteDe ( fiche ) ) . not . toContain ( t ( ` participants.fiche.rencontres. ${ cle } ` ) ) ;
}
} ) ;
} ) ;
describe ( 'la rubrique suit le placement que le plan montre (§ 12.5, D25)' , ( ) => {
test ( 'la fiche montée seule : une autre proposition demandée au plan change la source et les lignes ; aucune, et sans retenu, la rubrique disparaît' , async ( ) => {
const lue = generee ( chargeDouze ( ) ) ;
const [ premiere , seconde ] = lue . propositions . map ( ( { id } ) => id ) ;
const etat = { mode : 'lecture' , evenement : { etat : 'brouillon' , revision : 1 } , charge : lue } ;
const magasin = writable ( etat ) ;
const seance = {
subscribe : magasin . subscribe ,
etat : ( ) => etat ,
surAnnulation : ( ) => ( ) => { } ,
debuterGeste ( ) { } ,
finirGeste ( ) { } ,
async executer ( ) {
throw new Error ( 'la rubrique n’ écrit rien' ) ;
} ,
} ;
const ui = creerEtatUi ( ) ;
ui . montrerProposition ( premiere ) ;
const cible = document . createElement ( 'div' ) ;
document . body . prepend ( cible ) ;
const composant = mount ( Fiche , {
target : cible ,
props : { participant : lue . participants [ 4 ] , charge : lue } ,
context : new Map ( [
[ 't' , t ] ,
[ 'ui' , ui ] ,
[ 'seance' , seance ] ,
] ) ,
} ) ;
try {
flushSync ( ) ;
const lignesAttendues = ( id ) => rencontresDePersonne ( lue , id , 5 ) . lignes . map ( ( { nom , fois } ) => [ nom , String ( fois ) ] ) ;
const source = ( id ) => ` ${ t ( 'participants.fiche.rencontres.intitule' ) } ${ t ( 'participants.fiche.rencontres.proposition' , { proposition : id } )} ` ;
expect ( texteDe ( rubrique ( cible ) ) ) . toContain ( source ( premiere ) ) ;
expect ( lignesDe ( rubrique ( cible ) ) . map ( ( [ nom , fois ] ) => [ nom , fois ] ) ) . toEqual ( lignesAttendues ( premiere ) ) ;
ui . montrerProposition ( seconde ) ;
await tick ( ) ;
flushSync ( ) ;
expect ( texteDe ( rubrique ( cible ) ) ) . toContain ( source ( seconde ) ) ;
expect ( lignesDe ( rubrique ( cible ) ) . map ( ( [ nom , fois ] ) => [ nom , fois ] ) ) . toEqual ( lignesAttendues ( seconde ) ) ;
ui . montrerProposition ( null ) ;
await tick ( ) ;
flushSync ( ) ;
expect ( rubrique ( cible ) ) . toBeNull ( ) ;
} finally {
unmount ( composant ) ;
cible . remove ( ) ;
}
} ) ;
} ) ;