[FIX] event: page never scrolls, broken-support remedies always shown
At 1366 x 700 an open event that shows warnings no longer scrolls the page: the warnings scroll within their zone and the plan keeps at least 75 % of its height. A broken support never shrinks, so its three remedies stay in view, the only way to write the event elsewhere (spec 8.6). Tests now prove the working-folder path and duplicate nodes are visible, the guard of « Remplacer » in import, and which header a refused re-alignment shows. Checked: 2692 node, 499 browser tests, from the index alone. --- FR --- [FIX] événement : sans défilement, remèdes du support rompu visibles À 1366 x 700, un événement ouvert qui montre des avertissements ne fait plus défiler la page : les avertissements défilent dans leur zone et le plan garde au moins 75 % de sa hauteur. Un support rompu ne rétrécit jamais : ses trois remèdes, seul moyen d'écrire l'événement ailleurs (§ 8.6), restent visibles. Les épreuves prouvent que le chemin et les doublons du dossier sont visibles, la garde de « Remplacer » à l'import, et l'en-tête que montre un réalignement refusé. Vérifié : 2692 node, 499. Assisted-by: Claude Opus 5.5
This commit is contained in:
parent
a2812cddb9
commit
8186b06713
8 changed files with 245 additions and 18 deletions
|
|
@ -460,7 +460,11 @@ describe('signalements et récupération (§ 8.1, § 8.8)', () => {
|
|||
[b, 'Merles A'],
|
||||
]) {
|
||||
expect(element.textContent).toContain(t('accueil.signalement.doublon', { autres: 1 }));
|
||||
expect(element.querySelector(`[data-doublon="${CSS.escape(autre)}"]`)?.textContent).toBe(autre);
|
||||
const doublon = element.querySelector(`[data-doublon="${CSS.escape(autre)}"]`);
|
||||
expect(doublon?.textContent).toBe(autre);
|
||||
// La base nommée se lit à l'écran, pas seulement dans l'arbre.
|
||||
expect(visible(doublon), autre).toBe(true);
|
||||
expect(atteint(doublon), autre).toBe(true);
|
||||
const noeuds = noeudsDeTexte(element).filter((texte) => texte.includes('Merles'));
|
||||
expect(noeuds).toEqual([autre]);
|
||||
}
|
||||
|
|
@ -678,7 +682,11 @@ describe('le dossier de travail (§ 8.5, § 8.6)', () => {
|
|||
const cheminAutre = `${SOUS_APPDATA}\\data`;
|
||||
expect(proposition.textContent).toContain(t('accueil.autre.proposition', { evenements: 2 }));
|
||||
// Le chemin est une donnée : son propre nœud de texte, à part de la phrase.
|
||||
expect(dans(m, '[data-autre-chemin]')?.textContent).toBe(cheminAutre);
|
||||
const chemin = dans(m, '[data-autre-chemin]');
|
||||
expect(chemin?.textContent).toBe(cheminAutre);
|
||||
// Le chemin se lit à l'écran, pas seulement dans l'arbre.
|
||||
expect(visible(chemin)).toBe(true);
|
||||
expect(atteint(chemin)).toBe(true);
|
||||
expect(noeudsDeTexte(proposition).filter((texte) => texte.includes('\\'))).toEqual([cheminAutre]);
|
||||
expect(proposition.textContent).toContain(t('accueil.autre.originaux'));
|
||||
// En tête : avant la création, les démonstrations et toute ligne.
|
||||
|
|
@ -802,6 +810,10 @@ describe('les opérations de fichiers (§ 8.7)', () => {
|
|||
]) {
|
||||
expect(signalement(base).textContent).toContain(t('accueil.signalement.doublon', { autres: 1 }));
|
||||
expect(noeudsDeTexte(signalement(base)).filter((texte) => texte.includes('Gala'))).toEqual([autre]);
|
||||
const doublon = signalement(base).querySelector(`[data-doublon="${CSS.escape(autre)}"]`);
|
||||
expect(doublon?.textContent).toBe(autre);
|
||||
expect(visible(doublon), autre).toBe(true);
|
||||
expect(atteint(doublon), autre).toBe(true);
|
||||
}
|
||||
});
|
||||
|
||||
|
|
|
|||
|
|
@ -74,7 +74,6 @@
|
|||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: var(--espace-2);
|
||||
margin-bottom: var(--espace-3);
|
||||
}
|
||||
|
||||
.avertissement {
|
||||
|
|
|
|||
|
|
@ -8,10 +8,16 @@
|
|||
// à droite, la place fixe, le libellé de mode, puis « Défaire » et
|
||||
// « Refaire » (§ 8.3). Sous la barre vient la tête, tete, quand elle est
|
||||
// donnée — l'en-tête de l'événement et la navigation des sections —, puis
|
||||
// la surface, children, qui défile seule. La barre ouvre le cadre : rien
|
||||
// ne la précède dans l'écran de l'événement, si bien qu'un refus posé dans
|
||||
// la tête, ou un nom qui s'étend sur plusieurs lignes, allonge la tête
|
||||
// sans déplacer la place fixe (§ 8.4).
|
||||
// les annonces, annonces, quand il y en a — support rompu et
|
||||
// avertissements —, puis la surface, children. La barre ouvre le cadre :
|
||||
// rien ne la précède dans l'écran de l'événement, si bien qu'un refus posé
|
||||
// dans la tête, ou un nom qui s'étend sur plusieurs lignes, allonge la tête
|
||||
// sans déplacer la place fixe (§ 8.4). Tête, annonces et surface défilent
|
||||
// chacune dans sa zone : des avertissements nombreux ne poussent pas la
|
||||
// surface hors du cadre, ni le cadre hors de la fenêtre. La surface porte
|
||||
// le plan, que l'opérateur regarde : elle réclame les trois quarts du
|
||||
// cadre avant que les annonces ne dépassent leur première ligne, sauf
|
||||
// devant un support rompu, dont les issues restent en vue.
|
||||
//
|
||||
// La place fixe porte la commande que rend commandeFixe (modele.js) :
|
||||
// « Modifier », « Débloquer » — confirmé en place, par la Commande d'I2 —,
|
||||
|
|
@ -29,7 +35,7 @@
|
|||
import Refus from '../primitives/Refus.svelte';
|
||||
import { cadreDeMode, commandeFixe, repriseOfferte } from './modele.js';
|
||||
|
||||
let { children, tete = null } = $props();
|
||||
let { children, tete = null, annonces = null } = $props();
|
||||
|
||||
const t = getContext('t');
|
||||
const seance = getContext('seance');
|
||||
|
|
@ -103,6 +109,11 @@
|
|||
{@render tete()}
|
||||
</div>
|
||||
{/if}
|
||||
{#if annonces !== null}
|
||||
<div class="annonces" class:rompu={$seance.supportRompu !== null} data-annonces>
|
||||
{@render annonces()}
|
||||
</div>
|
||||
{/if}
|
||||
<div class="surface">
|
||||
{@render children()}
|
||||
</div>
|
||||
|
|
@ -204,8 +215,37 @@
|
|||
border-bottom: 1px solid var(--couleur-page-filet);
|
||||
}
|
||||
|
||||
/* Les annonces ne prennent jamais plus du quart du cadre, et cèdent
|
||||
d'abord leur hauteur à la surface : leur facteur de rétrécissement,
|
||||
mille fois celui de la surface, leur fait absorber presque tout le
|
||||
manque de place, jusqu'à leur hauteur minimale : un avertissement
|
||||
d'une ligne en entier, et le haut du suivant, qui dit que la zone en
|
||||
porte d'autres. Au-delà de leur hauteur, elles défilent dans leur
|
||||
zone. */
|
||||
.annonces {
|
||||
display: flex;
|
||||
flex: 0 1000 auto;
|
||||
flex-direction: column;
|
||||
gap: var(--espace-2);
|
||||
min-height: 3rem;
|
||||
max-height: 25%;
|
||||
overflow-y: auto;
|
||||
padding: var(--espace-2) var(--espace-3);
|
||||
border-bottom: 1px solid var(--couleur-page-filet);
|
||||
}
|
||||
|
||||
/* Le support rompu, en tête des annonces, porte les seules issues de
|
||||
l'opérateur : tant qu'il est là, les annonces ne cèdent rien à la
|
||||
surface, et gardent leur hauteur jusqu'au quart du cadre ; les
|
||||
avertissements qui le suivent défilent sous lui. */
|
||||
.annonces.rompu {
|
||||
flex-shrink: 0;
|
||||
}
|
||||
|
||||
/* La surface part des trois quarts du cadre, s'étend à ce qui reste, et
|
||||
ne rétrécit, après les annonces, que pour la barre et la tête. */
|
||||
.surface {
|
||||
flex: 1 1 auto;
|
||||
flex: 1 1 75%;
|
||||
min-height: 0;
|
||||
overflow: auto;
|
||||
padding: var(--espace-3);
|
||||
|
|
|
|||
|
|
@ -30,7 +30,7 @@ import { FORMAT, analyser, configurationDepuisCharge, etatDeduit } from '../../s
|
|||
import { lireJournal } from '../../stockage/journal.js';
|
||||
import { SUFFIXES } from '../../stockage/noms.js';
|
||||
import { VERSION } from '../../version.genere.js';
|
||||
import { fixerCadre } from '../../../test/navigateur/entrees.js';
|
||||
import { fixerCadre, molette } from '../../../test/navigateur/entrees.js';
|
||||
import { monterApp } from '../../../test/navigateur/montage.js';
|
||||
import { visible } from '../../../test/navigateur/rendu.js';
|
||||
import { TABLES_INTERFACE } from '../libelles.js';
|
||||
|
|
@ -230,6 +230,11 @@ describe('un nom long ou de glyphes larges (point d’attention 3)', () => {
|
|||
expect(titre.textContent).toBe(nom);
|
||||
expect(document.documentElement.scrollWidth, motif).toBeLessThanOrEqual(window.innerWidth);
|
||||
expect(titre.getBoundingClientRect().right, motif).toBeLessThanOrEqual(window.innerWidth);
|
||||
// Ni le titre ni la tête qui le porte ne débordent : la tête défile en
|
||||
// hauteur, et un nom qui ne passerait pas à la ligne s'étendrait en
|
||||
// largeur sous elle sans élargir la page.
|
||||
const tete = titre.closest('header').parentElement;
|
||||
expect([titre.scrollWidth, tete.scrollWidth], motif).toEqual([titre.clientWidth, tete.clientWidth]);
|
||||
expect(coin(fixe(m, 'modifier')), motif).toEqual(place);
|
||||
expect(visible(fixe(m, 'modifier')), motif).toBe(true);
|
||||
await m.seance.fermer();
|
||||
|
|
@ -578,6 +583,125 @@ describe('les annonces d’ouverture (§ 8.9, § 9)', () => {
|
|||
});
|
||||
});
|
||||
|
||||
describe('l’écran tient dans la fenêtre, avertissements compris (§ 8.4)', () => {
|
||||
// Ferme l'événement garni et généré de base, l'abîme, puis le rouvre : une
|
||||
// proposition de longueur fausse, une périmée, et une entrée du journal
|
||||
// abîmée lui donnent trois avertissements à l'ouverture, au moins.
|
||||
async function rouvrirAbime(m, base) {
|
||||
await m.seance.fermer();
|
||||
reecrireEtat(m, base, (charge) => {
|
||||
charge.propositions[0].placement.pop();
|
||||
charge.participants[0].exclu = true;
|
||||
});
|
||||
const chemin = `${base}${SUFFIXES.journal}`;
|
||||
const lignes = m.fs.contenu('documents', chemin).split('\n');
|
||||
const rang = lignes.findIndex((ligne) => ligne.startsWith('{"type":"entree"') && JSON.parse(ligne).revision === 4);
|
||||
expect(rang).toBeGreaterThan(0);
|
||||
lignes[rang] = '{"type":"entree", abîmée';
|
||||
m.fs.deposer('documents', chemin, lignes.join('\n'));
|
||||
await m.seance.ouvrir(base);
|
||||
flushSync();
|
||||
await new Promise((resoudre) => requestAnimationFrame(resoudre));
|
||||
}
|
||||
|
||||
test('à 1 366 × 700, trois avertissements et plus : la page ne défile pas, le plan garde sa hauteur ; les avertissements défilent à la molette, sans rien déplacer', async () => {
|
||||
const m = await neuf();
|
||||
await garnir(m);
|
||||
await generer(m, { retenir: false });
|
||||
const base = baseDe(m);
|
||||
// La hauteur du dessin du plan sans aucune annonce : la mesure de
|
||||
// référence de ce que les avertissements peuvent lui prendre.
|
||||
m.ui.allerA('plan');
|
||||
flushSync();
|
||||
expect(dans(m, '[data-annonces]')).toBeNull();
|
||||
const hauteurSeule = dans(m, '[data-plan]').getBoundingClientRect().height;
|
||||
expect(hauteurSeule).toBeGreaterThan(0);
|
||||
await rouvrirAbime(m, base);
|
||||
expect(m.ui.etat().section).toBe('plan');
|
||||
const montres = tous(m, '[data-avertissement]');
|
||||
expect(montres.length).toBeGreaterThanOrEqual(3);
|
||||
const boite = (element) => element.getBoundingClientRect().toJSON();
|
||||
// La page ne défile pas : le cadre tient dans la fenêtre, et la section
|
||||
// du plan dans le cadre, sans que la surface qui la porte ait à défiler.
|
||||
expect(document.documentElement.scrollHeight).toBeLessThanOrEqual(innerHeight);
|
||||
expect(cadre(m).getBoundingClientRect().bottom).toBeLessThanOrEqual(innerHeight);
|
||||
const section = dans(m, '[data-section]');
|
||||
const porteur = section.parentElement;
|
||||
expect(section.getBoundingClientRect().bottom).toBeLessThanOrEqual(porteur.getBoundingClientRect().bottom);
|
||||
expect(porteur.scrollHeight).toBeLessThanOrEqual(porteur.clientHeight);
|
||||
expect(visible(dans(m, '[data-plan]'))).toBe(true);
|
||||
// Le plan reste la surface de travail : les annonces ne prennent pas
|
||||
// plus du quart de la hauteur de son dessin.
|
||||
expect(dans(m, '[data-plan]').getBoundingClientRect().height).toBeGreaterThanOrEqual(hauteurSeule * 0.75);
|
||||
// Les avertissements débordent leur zone, qui défile seule, à la
|
||||
// molette, comme sous la main de l'opérateur : le dernier, amené dans la
|
||||
// zone, se voit, et ni la page, ni la place fixe, ni la section n'ont
|
||||
// bougé.
|
||||
const zone = montres[0].closest('[data-annonces]');
|
||||
expect(zone).not.toBeNull();
|
||||
for (const montre of montres) expect(zone.contains(montre)).toBe(true);
|
||||
expect(zone.scrollHeight).toBeGreaterThan(zone.clientHeight);
|
||||
expect(['auto', 'scroll']).toContain(getComputedStyle(zone).overflowY);
|
||||
expect(visible(montres[0])).toBe(true);
|
||||
const place = coin(fixe(m, 'modifier'));
|
||||
const avant = boite(section);
|
||||
const surZone = zone.getBoundingClientRect();
|
||||
await molette(surZone.left + surZone.width / 2, surZone.top + surZone.height / 2, zone.scrollHeight);
|
||||
await attendre(() => expect(visible(montres.at(-1))).toBe(true));
|
||||
expect(zone.scrollTop).toBeGreaterThan(0);
|
||||
expect(window.scrollY).toBe(0);
|
||||
expect(coin(fixe(m, 'modifier'))).toEqual(place);
|
||||
expect(boite(section)).toEqual(avant);
|
||||
// Sur chaque section, la page reste sans défilement, et la zone garde
|
||||
// sa boîte.
|
||||
const zoneAvant = boite(zone);
|
||||
for (const nom of SECTIONS) {
|
||||
m.ui.allerA(nom);
|
||||
flushSync();
|
||||
expect(document.documentElement.scrollHeight, nom).toBeLessThanOrEqual(innerHeight);
|
||||
expect(boite(dans(m, '[data-annonces]')), nom).toEqual(zoneAvant);
|
||||
}
|
||||
});
|
||||
|
||||
test('à 1 366 × 700, un support rompu et trois avertissements et plus : chaque issue du support rompu se voit sans défiler, la page ne défile pas, et les avertissements défilent sous lui', async () => {
|
||||
const m = await neuf();
|
||||
await garnir(m);
|
||||
await generer(m, { retenir: false });
|
||||
await rouvrirAbime(m, baseDe(m));
|
||||
expect(tous(m, '[data-avertissement]').length).toBeGreaterThanOrEqual(3);
|
||||
fixe(m, 'modifier').click();
|
||||
await attendre(() => expect(m.seance.etat().mode).toBe('ecriture'));
|
||||
m.fs.pannes.ecriture('documents');
|
||||
await expect(m.seance.executer('ajouterParticipant', { champs: { nom: NOMS[0] } })).rejects.toMatchObject({ code: 'ECRITURE' });
|
||||
flushSync();
|
||||
await new Promise((resoudre) => requestAnimationFrame(resoudre));
|
||||
expect(m.seance.etat().supportRompu).not.toBeNull();
|
||||
const montres = tous(m, '[data-avertissement]');
|
||||
expect(montres.length).toBeGreaterThanOrEqual(3);
|
||||
expect(document.documentElement.scrollHeight).toBeLessThanOrEqual(innerHeight);
|
||||
expect(cadre(m).getBoundingClientRect().bottom).toBeLessThanOrEqual(innerHeight);
|
||||
// Les issues du support rompu, seule sortie de l'opérateur, se voient
|
||||
// sans que rien ne défile.
|
||||
const message = dans(m, '[data-support-rompu]');
|
||||
for (const nom of ['choisirDossier', 'abandonner']) {
|
||||
const issue = message.querySelector(`[data-commande="${nom}"]`);
|
||||
expect(issue, nom).not.toBeNull();
|
||||
expect(visible(issue), nom).toBe(true);
|
||||
}
|
||||
expect(visible(fixe(m, 'lecture'))).toBe(true);
|
||||
expect(visible(dans(m, '[data-plan]'))).toBe(true);
|
||||
// Les avertissements restent dans la zone, qui défile seule : le
|
||||
// dernier, amené à la molette, se voit, et la page n'a pas bougé.
|
||||
const zone = message.closest('[data-annonces]');
|
||||
expect(zone).not.toBeNull();
|
||||
for (const montre of montres) expect(zone.contains(montre)).toBe(true);
|
||||
const surZone = zone.getBoundingClientRect();
|
||||
await molette(surZone.left + surZone.width / 2, surZone.top + surZone.height / 2, zone.scrollHeight);
|
||||
await attendre(() => expect(visible(montres.at(-1))).toBe(true));
|
||||
expect(window.scrollY).toBe(0);
|
||||
});
|
||||
});
|
||||
|
||||
describe('le journal (§ 8.6, § 8.2)', () => {
|
||||
test('cent entrées, la 37ᵉ abîmée : JOURNAL_ECARTEES {n: 64} s’affiche, son texte porte « 64 »', async () => {
|
||||
const m = await neuf();
|
||||
|
|
@ -742,6 +866,14 @@ describe('le support rompu (§ 8.6, § 8.2)', () => {
|
|||
const message = (m) => dans(m, '[data-support-rompu]');
|
||||
const commandeListe = (m) => dans(m, '[data-bandeau] [data-commande="liste"]');
|
||||
|
||||
// Le message offre exactement les issues noms, et chacune se voit au cadre
|
||||
// de l'épreuve : c'est la seule sortie de l'opérateur d'un support rompu.
|
||||
function issuesVisibles(m, noms) {
|
||||
const offertes = [...message(m).querySelectorAll('[data-commande]')].map((issue) => issue.dataset.commande);
|
||||
expect(offertes).toEqual(noms);
|
||||
for (const nom of noms) expect(visible(message(m).querySelector(`[data-commande="${nom}"]`)), nom).toBe(true);
|
||||
}
|
||||
|
||||
// L'événement ouvert en écriture, puis le support de racine refuse
|
||||
// l'écriture d'un geste.
|
||||
async function rompre(m, racine) {
|
||||
|
|
@ -776,7 +908,7 @@ describe('le support rompu (§ 8.6, § 8.2)', () => {
|
|||
expect(visible(fixe(m, 'lecture'))).toBe(true);
|
||||
expect(visible(dans(m, '[data-refus="liste"]'))).toBe(true);
|
||||
expect(m.seance.etat().evenement.base).toBe(base);
|
||||
expect(message(m).querySelector('[data-commande="choisirDossier"]')).not.toBeNull();
|
||||
issuesVisibles(m, ['ecrireDocuments', 'choisirDossier', 'abandonner']);
|
||||
message(m).querySelector('[data-commande="ecrireDocuments"]').click();
|
||||
await attendre(() => expect(m.seance.etat().supportRompu).toBeNull());
|
||||
const nouvelle = baseDe(m);
|
||||
|
|
@ -798,7 +930,7 @@ describe('le support rompu (§ 8.6, § 8.2)', () => {
|
|||
const m = await neuf();
|
||||
await rompre(m, 'documents');
|
||||
expect(m.seance.etat().supportRompu.ailleurs).toBeNull();
|
||||
expect(message(m).querySelector('[data-commande="ecrireDocuments"]')).toBeNull();
|
||||
issuesVisibles(m, ['choisirDossier', 'abandonner']);
|
||||
const choisir = message(m).querySelector('[data-commande="choisirDossier"]');
|
||||
expect(choisir.textContent).toBe(t('communs.commande.choisirDossier'));
|
||||
choisir.click();
|
||||
|
|
@ -810,6 +942,7 @@ describe('le support rompu (§ 8.6, § 8.2)', () => {
|
|||
test('non publié, « Fermer sans écrire ailleurs », confirmé en place : la liste revient, sans aucune écriture', async () => {
|
||||
const m = await neuf();
|
||||
await rompre(m, 'documents');
|
||||
issuesVisibles(m, ['choisirDossier', 'abandonner']);
|
||||
const avant = m.trace.length;
|
||||
const abandonner = () => message(m).querySelector('[data-commande="abandonner"]');
|
||||
expect(abandonner().textContent).toBe(t('evenement.supportRompu.abandonner'));
|
||||
|
|
@ -826,6 +959,7 @@ describe('le support rompu (§ 8.6, § 8.2)', () => {
|
|||
test('non publié, un dossier choisi : état et journal y sont écrits, le geste suivant aussi, et la liste le suit', async () => {
|
||||
const m = await neuf({ systeme: { dossiersChoisis: ['D:\\Secours'] } });
|
||||
await rompre(m, 'documents');
|
||||
issuesVisibles(m, ['choisirDossier', 'abandonner']);
|
||||
message(m).querySelector('[data-commande="choisirDossier"]').click();
|
||||
await attendre(() => expect(m.seance.etat().supportRompu).toBeNull());
|
||||
expect(m.seance.etat().racine).toEqual({ id: 'choisi-1', chemin: 'D:\\Secours' });
|
||||
|
|
|
|||
|
|
@ -18,6 +18,12 @@
|
|||
// globale ci-dessous donne pour cela une hauteur à l'ascendance de l'écran
|
||||
// — le conteneur de l'application et son <main> — tant qu'il porte cet
|
||||
// écran, et à lui seul.
|
||||
//
|
||||
// Le support rompu et les avertissements se posent dans la zone des
|
||||
// annonces du cadre, qui défile seule, et non au-dessus de la section : la
|
||||
// section garde la hauteur de la surface, plan compris, quel que soit le
|
||||
// nombre des avertissements, et le support rompu, en tête de la zone,
|
||||
// garde ses issues en vue (CadreMode.svelte).
|
||||
import { getContext } from 'svelte';
|
||||
import Diagnostic from '../diagnostic/Diagnostic.svelte';
|
||||
import SectionGeneration from '../generation/SectionGeneration.svelte';
|
||||
|
|
@ -30,6 +36,7 @@
|
|||
import CadreMode from './CadreMode.svelte';
|
||||
import EnteteEvenement from './EnteteEvenement.svelte';
|
||||
import SupportRompu from './SupportRompu.svelte';
|
||||
import { avertissementsOrdonnes } from './modele.js';
|
||||
import { SECTIONS, cleDeSection } from './sections.js';
|
||||
|
||||
const t = getContext('t');
|
||||
|
|
@ -48,6 +55,9 @@
|
|||
]);
|
||||
|
||||
const Section = $derived(COMPOSANTS.get($ui.section));
|
||||
// La zone des annonces n'existe que lorsqu'elle a quelque chose à dire :
|
||||
// sans annonce, le cadre ne lui garde ni hauteur ni trait.
|
||||
const annoncer = $derived($seance.supportRompu !== null || avertissementsOrdonnes($seance).length > 0);
|
||||
</script>
|
||||
|
||||
{#snippet tete()}
|
||||
|
|
@ -65,12 +75,15 @@
|
|||
</nav>
|
||||
{/snippet}
|
||||
|
||||
{#snippet annonces()}
|
||||
{#if $seance.supportRompu !== null}
|
||||
<SupportRompu />
|
||||
{/if}
|
||||
<Avertissements />
|
||||
{/snippet}
|
||||
|
||||
<div class="evenement" data-ecran="evenement">
|
||||
<CadreMode tete={$ui.pleinEcran ? null : tete}>
|
||||
{#if $seance.supportRompu !== null}
|
||||
<SupportRompu />
|
||||
{/if}
|
||||
<Avertissements />
|
||||
<CadreMode tete={$ui.pleinEcran ? null : tete} annonces={annoncer ? annonces : null}>
|
||||
<Section />
|
||||
</CadreMode>
|
||||
</div>
|
||||
|
|
|
|||
|
|
@ -52,7 +52,6 @@
|
|||
flex-direction: column;
|
||||
align-items: flex-start;
|
||||
gap: var(--espace-2);
|
||||
margin-bottom: var(--espace-3);
|
||||
padding: var(--espace-2) var(--espace-3);
|
||||
border-left: 3px solid var(--couleur-refus);
|
||||
color: var(--couleur-refus);
|
||||
|
|
|
|||
|
|
@ -731,6 +731,30 @@ describe('importer selon l’état (§ 10.1, § 9)', () => {
|
|||
expect(pose.querySelector('[data-commande="modifier"]').textContent).toBe(t('communs.remede.modifier'));
|
||||
expect(ecritures(e)).toBe(avant);
|
||||
});
|
||||
|
||||
test('sous « Remplacer », bloqué puis en lecture : le refus à côté de la commande, avec son remède, aucune fenêtre, rien d’écrit', async () => {
|
||||
const cas = [
|
||||
['LECTURE_SEULE', 'debloquer', { prepare: (seance) => seance.executer('changerEtat', { etat: 'bloque' }) }],
|
||||
['MODE_LECTURE', 'modifier', { ecriture: false }],
|
||||
];
|
||||
for (const [code, remede, options] of cas) {
|
||||
const e = await ouvrir({ participants: QUATRE(), ...options });
|
||||
expect(e.m.seance.etat().refus?.code, code).toBe(code);
|
||||
const avant = ecritures(e);
|
||||
await choisir(e, 'tardif.csv', enUtf8(FICHIER));
|
||||
choisirMode(e, 'remplacer');
|
||||
commande(e, 'importer').click();
|
||||
await vi.waitFor(() => expect(dans(e, `[data-refus="${code}"]`), code).not.toBeNull());
|
||||
const pose = dans(e, `[data-refus="${code}"]`);
|
||||
expect(commande(e, 'importer').parentElement.contains(pose), code).toBe(true);
|
||||
expect(pose.querySelector(`[data-commande="${remede}"]`)?.textContent, code).toBe(t(`communs.remede.${remede}`));
|
||||
expect(vu(pose), code).toBe(true);
|
||||
expect(dans(e, 'dialog'), code).toBeNull();
|
||||
expect(imports(e), code).toEqual([]);
|
||||
expect(ecritures(e), code).toBe(avant);
|
||||
expect(participantsDe(e).length, code).toBe(4);
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
describe('le collage en bloc passe par le même analyseur (§ 10.2, § 14.10)', () => {
|
||||
|
|
|
|||
|
|
@ -354,6 +354,12 @@ describe('changer le défaut (§ 6.1, § 6.2, § 19.3 06)', () => {
|
|||
expect(fenetre()).toBeNull();
|
||||
const refus = enonce(m).querySelector('[data-refus-realigner]');
|
||||
expect(refus).not.toBeNull();
|
||||
// L'en-tête est celui du réalignement refusé, qui laisse « Appliquer »
|
||||
// offert, et non celui d'un défaut que rien n'applique.
|
||||
const tete = refus.querySelector('[data-phrase]');
|
||||
expect(tete?.textContent).toBe(t('tables.defaut.realignementRefuse', { avant: 8, apres: 10 }));
|
||||
expect(visible(tete)).toBe(true);
|
||||
expect(refus.textContent).not.toContain(t('tables.defaut.refusee', { avant: 8, apres: 10 }));
|
||||
expect(refus.textContent).toContain('Table 2');
|
||||
expect(refus.textContent).toContain('11 réservations');
|
||||
expect(ecritures(m)).toBe(avant);
|
||||
|
|
|
|||
Loading…
Reference in a new issue