forked from yvv/decision
Fusion de la tête du chantier (924a40d : lots B2 et F) dans le lot D
Les deux côtés gardés partout. Aux coutures : - une objection (redit / contredit) porte sur deux articles, jamais sur un module ; un ordre ne se corrige pas, ni par son auteur ni par un autre ; une coquille du titre suit le chemin de F (l'auteur du titre, un administrateur pour le titre de départ, ou 3 jours), et documents.title suit à l'application ; - votable() couvre les modules ; leurs totaux passent par shown_tally, à l'écran et dans document.json ; - « Modules à voter » ne compte plus une version retenue par une objection (test vu rouge) ; la liste du groupe la compte toujours parmi les propositions en cours ; - le panier : une fonction pour les articles et les modules, avec les versions retenues de F ; un nouvel article se propose dans une section, à sa fin, et peut répondre à une objection ; - migrations 0007 (modules) puis 0008 (objections) ; - C25 et C26 supportent un groupe d'essai où une voix suffit (dès 16 membres). Boucle : fmt, clippy -D warnings, cargo test (227), svelte-check 0, vitest 55, build, e2e 35 / 35 (C1–C33 et M1). Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
+35
-19
@@ -1,4 +1,4 @@
|
||||
import { devices, expect, test, type Page } from '@playwright/test';
|
||||
import { devices, expect, test, type Browser, type Page } from '@playwright/test';
|
||||
import { ALICE, DOC, person, shot, signIn, TEST_DOC, wallet } from './helpers';
|
||||
|
||||
// Lot D — the modules (Yvv, 2026-10-30; ADR 0009): at the end of each section, the
|
||||
@@ -17,6 +17,31 @@ async function voteFor(page: Page, row: string) {
|
||||
await page.locator('ul.deep section.fold').getByRole('button', { name: 'Pour', exact: true }).click();
|
||||
}
|
||||
|
||||
/** Whether a version of a module of the test group is in force now. */
|
||||
async function inForce(page: Page, proposalId: string): Promise<boolean> {
|
||||
const g = await (await page.request.get('/api/groups/essai')).json();
|
||||
const d = await (await page.request.get(`/api/docs/${g.docs[0].id}`)).json();
|
||||
return [d.modules.title, ...d.modules.orders].some((u) => u?.inForce?.id === proposalId);
|
||||
}
|
||||
|
||||
/**
|
||||
* The voices that bring a module version of the test group into force: hers, then a
|
||||
* second one if hers was not enough. Every dev sign-in joins the test group: from 16
|
||||
* members B^W vanishes and, C being 1, one voice may suffice (ADR 0007). Returns the
|
||||
* number of voices for it.
|
||||
*/
|
||||
async function secondVoice(page: Page, browser: Browser, proposalId: string, row: string, news: string): Promise<number> {
|
||||
if (await inForce(page, proposalId)) {
|
||||
await expect(page.getByText(news)).toBeVisible();
|
||||
return 1;
|
||||
}
|
||||
const yvv = await person(browser, 'Yvv');
|
||||
await yvv.goto(TEST_DOC);
|
||||
await voteFor(yvv, row);
|
||||
await expect(yvv.getByText(news)).toBeVisible();
|
||||
return 2;
|
||||
}
|
||||
|
||||
test('C25 — l’ordre des articles : en fin de section, proposé en cliquant, au clavier, voté, en vigueur', async ({ page, browser }) => {
|
||||
await signIn(page, 'Chiara');
|
||||
await page.goto(TEST_DOC);
|
||||
@@ -31,17 +56,14 @@ test('C25 — l’ordre des articles : en fin de section, proposé en cliquant,
|
||||
await order.scrollIntoViewIfNeeded();
|
||||
await shot(page, 'c25-1-ordre-lecture');
|
||||
|
||||
// Its vote unfolds under it, like an article's; the mark tells the real number of votes.
|
||||
// Its vote unfolds under it, like an article's, the list whole (the mark's real number of
|
||||
// votes is seen in Monnaie libre, below: here one voice may confirm a seed).
|
||||
const fond = page.getByRole('group', { name: 'Ordre des articles · Engagements fondamentaux' });
|
||||
await fond.hover();
|
||||
await fond.getByRole('button', { name: 'Le vote' }).click();
|
||||
const fondVote = page.getByRole('region', { name: 'Le vote de « Ordre des articles · Engagements fondamentaux »' });
|
||||
await expect(fondVote.getByText('inertie 2 / 5 · basse', { exact: true })).toBeVisible();
|
||||
await expect(fondVote.getByRole('list', { name: 'Ordre en vigueur' }).locator('li')).toHaveText(['Engagements fondamentaux', 'Unicité du compte', 'Réciprocité']);
|
||||
await fondVote.getByRole('button', { name: 'Pour', exact: true }).click();
|
||||
await expect(fond.getByText('seed a minima · 1 vote')).toBeVisible();
|
||||
await fondVote.getByRole('button', { name: 'Retirer mon vote' }).click();
|
||||
await expect(fond.getByText('seed a minima · 0 vote')).toBeVisible();
|
||||
await fond.getByRole('button', { name: 'Le vote' }).click();
|
||||
|
||||
// Another order: the article, then its new place.
|
||||
@@ -82,25 +104,22 @@ test('C25 — l’ordre des articles : en fin de section, proposé en cliquant,
|
||||
await expect(fold.locator('ol.order li')).toHaveText([K2, K1]);
|
||||
await expect(page.locator('ul.deep li.section', { hasText: 'Conclusion' })).toBeVisible();
|
||||
await expect(page.locator('ul.deep button.row', { hasText: 'Ordre des articles · Conclusion' })).toContainText(`${K2} · ${K1}`);
|
||||
const pid = ((await fold.getAttribute('id')) ?? '').replace(/^vote-/, '');
|
||||
await fold.getByRole('button', { name: 'Pour', exact: true }).click();
|
||||
await expect(page.getByText('Vote enregistré.')).toBeVisible();
|
||||
await shot(page, 'c25-3-ordre-vote');
|
||||
// Into force (her voice, or a second one): the section reads in its new order.
|
||||
const voices = await secondVoice(page, browser, pid, 'Ordre des articles · Conclusion', 'Le nouvel ordre de « Conclusion » est entré en vigueur.');
|
||||
|
||||
// In the basket too: at the end of its section.
|
||||
await page.getByRole('button', { name: /^Voter en lot/ }).click();
|
||||
const basket = panel.getByRole('region', { name: 'Conclusion' });
|
||||
await expect(basket.locator('li.slot').last()).toContainText('Ordre des articles · Conclusion');
|
||||
await panel.getByRole('button', { name: 'Fermer le panneau' }).click();
|
||||
|
||||
// A second voice brings it into force: the section reads in its new order.
|
||||
const yvv = await person(browser, 'Yvv');
|
||||
await yvv.goto(TEST_DOC);
|
||||
await voteFor(yvv, 'Ordre des articles · Conclusion');
|
||||
await expect(yvv.getByText('Le nouvel ordre de « Conclusion » est entré en vigueur.')).toBeVisible();
|
||||
await page.reload();
|
||||
await expect(order.locator(':scope > ol li')).toHaveText([`1 ${K2}`, `2 ${K1}`]);
|
||||
// In force now: its vote stays unfolded, under the module, the list whole.
|
||||
await expect(order.locator('section.fold')).toContainText('2 pour · 0 contre');
|
||||
await expect(order.locator('section.fold')).toContainText(`${voices} pour · 0 contre`);
|
||||
await order.scrollIntoViewIfNeeded();
|
||||
await shot(page, 'c25-3b-ordre-en-vigueur');
|
||||
await expect(order).toContainText(/adopté le/);
|
||||
@@ -131,13 +150,10 @@ test('C26 — le titre du document : proposé et voté en tête, ses traductions
|
||||
const fold = page.locator('ul.deep section.fold');
|
||||
await expect(fold).toHaveAttribute('aria-label', `Le vote de « ${next} »`);
|
||||
await expect(page.locator('ul.deep button.row', { hasText: next })).toContainText('Titre du document');
|
||||
const pid = ((await fold.getAttribute('id')) ?? '').replace(/^vote-/, '');
|
||||
await fold.getByRole('button', { name: 'Pour', exact: true }).click();
|
||||
await expect(page.getByText('Vote enregistré.')).toBeVisible();
|
||||
|
||||
const yvv = await person(browser, 'Yvv');
|
||||
await yvv.goto(TEST_DOC);
|
||||
await voteFor(yvv, next);
|
||||
await expect(yvv.getByText(`Le titre « ${next} » est entré en vigueur.`)).toBeVisible();
|
||||
const voices = await secondVoice(page, browser, pid, next, `Le titre « ${next} » est entré en vigueur.`);
|
||||
|
||||
// The document changes title; its translations stay editorial, to review.
|
||||
await page.reload();
|
||||
@@ -148,7 +164,7 @@ test('C26 — le titre du document : proposé et voté en tête, ses traductions
|
||||
const vote = bar.locator('section.fold');
|
||||
await expect(bar.getByRole('button', { name: 'Le vote' })).toHaveAttribute('aria-expanded', 'true');
|
||||
await expect(vote.getByText('inertie 4 / 5 · haute', { exact: true })).toBeVisible();
|
||||
await expect(vote.getByText('2 pour · 0 contre')).toBeVisible();
|
||||
await expect(vote.getByText(`${voices} pour · 0 contre`)).toBeVisible();
|
||||
await shot(page, 'c26-2-titre-vote');
|
||||
await bar.getByRole('button', { name: 'Le vote' }).click();
|
||||
await page.getByRole('radiogroup', { name: 'Langue du texte' }).getByRole('radio', { name: 'EN' }).click();
|
||||
|
||||
Reference in New Issue
Block a user