// © 2026 TechnoLibre (http://www.technolibre.ca) // License AGPL-3.0 or later (https://www.gnu.org/licenses/agpl) // Épreuves du dépôt d'une personne glissée (§ 5.8, § 5.9, § 7.1, § 7.3, // § 14.10), sans DOM : la commande se lit sur la cible de l'état des places // (EtatPlaces.cible, A5), jamais sur la présence d'un retenu ; la place se // désigne par la géométrie des tables. Les charges d'épreuve se construisent // par les commandes de l'application et l'état des places se calcule par // etatPlaces : la même implémentation que celle du plan (§ 19.1). Les noms // sont inventés. import assert from 'node:assert/strict'; import { describe, test } from '../../../test/lanceur.js'; import { appliquerCommande } from '../../application/commandes.js'; import { etatPlaces } from '../../application/places.js'; import { geometrieTable } from '../../geometrie/tables.js'; import { creerCharge } from '../../stockage/document.js'; import { commandeDeDepot, deposer } from './depot.js'; const NOMS = [ ['Ombrelle', 'Iris'], ['Pervenche', 'Théo'], ['Lacasse', 'Ondine'], ['Grisaille', null], ['Tourmaline', 'Basile'], ['Arrosoir', 'Capucine'], ['Cerfeuil', 'Aurèle'], ]; // Sept personnes, trois rondes de trois places, deux tours, et une // proposition à sièges non attribués : au tour 1, la table 1 est pleine // (1, 2, 3), la table 2 assied 4 et 5, la table 3 assied 6 et 7 ; au // tour 2, 1, 4, 6 ; 2, 5, 7 ; 3. function chargeDepot() { const charge = creerCharge({ id: 'evt-depot', nom: 'Club des Merles', siegesParDefaut: 3, tours: 2 }); charge.participants = NOMS.map(([nom, prenom], rang) => ({ id: rang + 1, nom, prenom, appartenance: null, courriel: null, titrePressenti: null, notes: null, exclu: false, })); charge.tables = [1, 2, 3].map((id) => ({ id, numero: id, sieges: null, forme: 'ronde', position: { x: 300 * (id - 1), y: 0 } })); charge.prochainsIds = { participant: 8, table: 4, proposition: 2 }; charge.propositions = [ { id: 1, graine: 1, arret: 40, historique: 1000, produitVersion: 'epreuve', siegesAttribues: false, tables: [1, 2, 3], capacites: [3, 3, 3], tours: 2, participants: [1, 2, 3, 4, 5, 6, 7], placement: [ { sieges: [[1, 2, 3], [4, 5], [6, 7]], reserve: [] }, { sieges: [[1, 4, 6], [2, 5, 7], [3]], reserve: [] }, ], }, ]; charge.evenement.etat = 'propose'; return charge; } const executer = (charge, nom, args) => appliquerCommande(nom, charge, args).charge; const retenue = (charge) => executer(charge, 'retenirProposition', { id: 1 }); // Le dépôt de la personne assise à depuis, lu sur l'état des places du tour. function depot(etat, depuis, cible, surLeSol = false) { const table = etat.tables.find(({ id }) => id === depuis.table); const personne = table.chaises[depuis.siege - 1].occupant; assert.notEqual(personne, null, `une personne est assise à ${depuis.table}:${depuis.siege}`); return commandeDeDepot({ etatPlaces: etat, tour: etat.tour, personne, depuis, cible, surLeSol }); } describe('commandeDeDepot sous la cible « reservations » : avant la retenue (§ 5.8)', () => { const avant = () => etatPlaces(chargeDepot(), { tour: 1, proposition: 1 }); test('l’état des places le dit : une proposition montrée avant la retenue a pour cible les réservations', () => { const etat = avant(); assert.equal(etat.source, 'proposition'); assert.equal(etat.cible, 'reservations'); }); test('une personne non réservée sur un siège libre → reserver au tour affiché, portée « tour »', () => { assert.deepEqual(depot(avant(), { table: 2, siege: 1 }, { table: 3, siege: 3 }), { commande: 'reserver', arguments: { participant: 4, table: 3, siege: 3, portee: 'tour', tour: 1 }, }); }); test('sur un siège qu’occupe une personne non réservée de la proposition → reserver, comme sur un siège libre', () => { assert.deepEqual(depot(avant(), { table: 2, siege: 1 }, { table: 3, siege: 1 }), { commande: 'reserver', arguments: { participant: 4, table: 3, siege: 1, portee: 'tour', tour: 1 }, }); }); test('une personne réservée → deplacerReservation, la réservation désignée par sa table, sa portée et son tour', () => { const charge = executer(chargeDepot(), 'reserver', { participant: 1, table: 1, siege: 1, portee: 'tour', tour: 1 }); const etat = etatPlaces(charge, { tour: 1, proposition: 1 }); assert.deepEqual(depot(etat, { table: 1, siege: 1 }, { table: 3, siege: 3 }), { commande: 'deplacerReservation', arguments: { participant: 1, de: { table: 1, portee: 'tour', tour: 1 }, vers: { table: 3, siege: 3 } }, }); const tous = executer(chargeDepot(), 'reserver', { participant: 1, table: 1, siege: 1, portee: 'tous', tour: null }); assert.deepEqual(depot(etatPlaces(tous, { tour: 2, proposition: 1 }), { table: 1, siege: 1 }, { table: 2, siege: 3 }), { commande: 'deplacerReservation', arguments: { participant: 1, de: { table: 1, portee: 'tous', tour: null }, vers: { table: 2, siege: 3 } }, }); }); test('sur le sol → retirerReservation pour une personne réservée ; null pour une personne qui ne l’est pas', () => { const charge = executer(chargeDepot(), 'reserver', { participant: 1, table: 1, siege: 1, portee: 'tour', tour: 1 }); const etat = etatPlaces(charge, { tour: 1, proposition: 1 }); assert.deepEqual(depot(etat, { table: 1, siege: 1 }, null, true), { commande: 'retirerReservation', arguments: { participant: 1, table: 1, portee: 'tour', tour: 1 }, }); assert.equal(depot(etat, { table: 2, siege: 1 }, null, true), null); }); test('régime table → le premier siège qu’aucune réservation ne tient ; table pleine de réservations → la même commande, siege: null', () => { assert.deepEqual(depot(avant(), { table: 2, siege: 1 }, { table: 3 }), { commande: 'reserver', arguments: { participant: 4, table: 3, siege: 1, portee: 'tour', tour: 1 }, }); let charge = chargeDepot(); for (const [participant, siege] of [[1, 1], [2, 2], [3, 3]]) { charge = executer(charge, 'reserver', { participant, table: 1, siege, portee: 'tour', tour: 1 }); } assert.deepEqual(depot(etatPlaces(charge, { tour: 1, proposition: 1 }), { table: 2, siege: 1 }, { table: 1 }), { commande: 'reserver', arguments: { participant: 4, table: 1, siege: null, portee: 'tour', tour: 1 }, }); }); test('un tour que le retenu ne porte pas : la cible est « reservations », la commande reserver, quoiqu’un retenu existe', () => { const charge = executer(retenue(chargeDepot()), 'changerTours', { tours: '3' }); const etat = etatPlaces(charge, { tour: 3 }); assert.notEqual(charge.retenu, null); assert.equal(etat.cible, 'reservations'); const personne = etat.reserve[0]; assert.equal(personne, 1); const rendu = commandeDeDepot({ etatPlaces: etat, tour: 3, personne, depuis: null, cible: { table: 2, siege: 2 }, surLeSol: false }); assert.deepEqual(rendu, { commande: 'reserver', arguments: { participant: 1, table: 2, siege: 2, portee: 'tour', tour: 3 } }); }); }); describe('commandeDeDepot sous la cible « retenu » (§ 5.8, § 7.1)', () => { const apres = () => etatPlaces(retenue(chargeDepot()), { tour: 1 }); test('siège libre → deplacerDansRetenu ; siège occupé → echangerDansRetenu ; sol → retirerDuTour', () => { const etat = apres(); assert.equal(etat.cible, 'retenu'); assert.deepEqual(depot(etat, { table: 2, siege: 1 }, { table: 3, siege: 3 }), { commande: 'deplacerDansRetenu', arguments: { tour: 1, participant: 4, table: 3, siege: 3 }, }); assert.deepEqual(depot(etat, { table: 2, siege: 1 }, { table: 3, siege: 1 }), { commande: 'echangerDansRetenu', arguments: { tour: 1, a: 4, b: 6 }, }); assert.deepEqual(depot(etat, { table: 2, siege: 1 }, null, true), { commande: 'retirerDuTour', arguments: { tour: 1, participant: 4 }, }); }); test('régime table → le premier siège libre au tour affiché ; table pleine → la même commande, siege: null', () => { const etat = apres(); assert.deepEqual(depot(etat, { table: 2, siege: 1 }, { table: 3 }), { commande: 'deplacerDansRetenu', arguments: { tour: 1, participant: 4, table: 3, siege: 3 }, }); assert.deepEqual(depot(etat, { table: 2, siege: 1 }, { table: 1 }), { commande: 'deplacerDansRetenu', arguments: { tour: 1, participant: 4, table: 1, siege: null }, }); }); test('la commande rendue s’exécute : la personne change de table au tour affiché seulement', () => { const charge = retenue(chargeDepot()); const etat = etatPlaces(charge, { tour: 1 }); const { commande, arguments: args } = depot(etat, { table: 2, siege: 1 }, { table: 3, siege: 3 }); const suivante = executer(charge, commande, args); assert.deepEqual(suivante.retenu.placement[0].sieges, [[1, 2, 3], [5], [4, 6, 7]]); assert.deepEqual(suivante.retenu.placement[1], charge.retenu.placement[1]); }); }); describe('commandeDeDepot : ce qui ne désigne rien, et la cible null (contrat, « Source »)', () => { test('entre deux chaises, ou sur le plateau : null, la personne revient', () => { for (const etat of [etatPlaces(chargeDepot(), { tour: 1, proposition: 1 }), etatPlaces(retenue(chargeDepot()), { tour: 1 })]) { assert.equal(depot(etat, { table: 2, siege: 1 }, null, false), null); } }); test('relâchée sur sa propre chaise : null', () => { assert.equal(depot(etatPlaces(retenue(chargeDepot()), { tour: 1 }), { table: 2, siege: 1 }, { table: 2, siege: 1 }), null); }); test('une proposition montrée après la retenue → { refus } PROPOSITION_MONTREE, remède afficherTour, sur un siège, une table comme sur le sol', () => { const etat = etatPlaces(retenue(chargeDepot()), { tour: 1, proposition: 1 }); assert.equal(etat.cible, null); const attendu = { refus: { code: 'PROPOSITION_MONTREE', details: { proposition: 1, remede: { geste: 'afficherTour', tour: 1, proposition: null } } }, }; assert.deepEqual(depot(etat, { table: 2, siege: 1 }, { table: 3, siege: 3 }), attendu); assert.deepEqual(depot(etat, { table: 2, siege: 1 }, { table: 3 }), attendu); assert.deepEqual(depot(etat, { table: 2, siege: 1 }, null, true), attendu); }); test('un retenu fautif → { refus } RETENU_FAUTIF', () => { const charge = retenue(chargeDepot()); charge.retenu.placement[0].sieges[1].push(1); const etat = etatPlaces(charge, { tour: 1 }); assert.equal(etat.cible, null); assert.equal(etat.refus.code, 'RETENU_FAUTIF'); const rendu = commandeDeDepot({ etatPlaces: etat, tour: 1, personne: 4, depuis: { table: 2, siege: 1 }, cible: { table: 3, siege: 3 }, surLeSol: false }); assert.deepEqual(rendu, { refus: etat.refus }); }); }); describe('deposer : la place se désigne par la géométrie (§ 7.3, § 14.10)', () => { const geometries = (etat) => etat.tables.map((table) => geometrieTable(table, table.capacite)); test('au centre d’une chaise libre d’une autre table → la commande nomme cette table et ce siège, et la place est ce siège', () => { const etat = etatPlaces(retenue(chargeDepot()), { tour: 1 }); const g = geometries(etat); assert.equal(g.length, 3); const chaise = g[2].chaises[2]; const rendu = deposer({ etatPlaces: etat, tour: 1, personne: 4, depuis: { table: 2, siege: 1 }, point: { x: chaise.x, y: chaise.y }, geometries: g, k: 1 }); assert.deepEqual(rendu, { resultat: { commande: 'deplacerDansRetenu', arguments: { tour: 1, participant: 4, table: 3, siege: 3 } }, place: { table: 3, siege: 3 }, }); }); test('sur le plateau : rien, la place est celle d’où vient la personne ; loin des tables : le sol', () => { const etat = etatPlaces(retenue(chargeDepot()), { tour: 1 }); const g = geometries(etat); const commun = { etatPlaces: etat, tour: 1, personne: 4, depuis: { table: 2, siege: 1 }, geometries: g, k: 1 }; assert.deepEqual(deposer({ ...commun, point: g[2].centre }), { resultat: null, place: { table: 2, siege: 1 } }); assert.deepEqual(deposer({ ...commun, point: { x: 150, y: 400 } }), { resultat: { commande: 'retirerDuTour', arguments: { tour: 1, participant: 4 } }, place: { table: 2, siege: 1 }, }); }); test('au zoom où la table est la cible, le point au centre désigne la table, et la place est la table', () => { const etat = etatPlaces(retenue(chargeDepot()), { tour: 1 }); const g = geometries(etat); const rendu = deposer({ etatPlaces: etat, tour: 1, personne: 4, depuis: { table: 2, siege: 1 }, point: g[0].centre, geometries: g, k: 0.1 }); assert.deepEqual(rendu, { resultat: { commande: 'deplacerDansRetenu', arguments: { tour: 1, participant: 4, table: 1, siege: null } }, place: { table: 1 }, }); }); });