v3 : interface d'une version retenue — « Contre » et « Retirer mon vote », jamais « Pour »

Sous l'article, une version retenue montre « Contre » et le retrait, avec la
phrase qui dit pourquoi. Dans le panier, elle se choisit en « contre » ou en
retrait ; « Tout pour » la saute et le dit, « Tout contre » la prend ; un « pour »
gardé d'avant l'objection quitte le panier (basket.kept). Les phrases qui disaient
« ne se vote plus » disent la règle d'Yvv, en fr, en et es. C28 ajusté.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
Yvv
2026-11-02 00:41:50 +01:00
co-authored by Claude Opus 5.5
parent 238c7db93f
commit 5d1f8652fc
11 changed files with 223 additions and 79 deletions
+24 -10
View File
@@ -1,6 +1,6 @@
import { describe, expect, it } from 'vitest';
import { clearScope, counts, fill, lines, sections, votableIds, waiting } from '../../src/lib/basket';
import type { Proposal, Unit } from '../../src/lib/types';
import { admits, clearScope, counts, fill, heldIn, kept, lines, sections, votableIds, waiting } from '../../src/lib/basket';
import type { Choice, Proposal, Unit } from '../../src/lib/types';
function prop(id: string, unitId: string, over: Partial<Proposal> = {}): Proposal {
return {
@@ -84,7 +84,7 @@ describe('the basket of a vote', () => {
});
it('the lines sent: a withdrawal is a null choice; a version gone from the document is left out', () => {
const known = new Set(['i1', 'e2', 'e4']);
const known = votableIds(sections([I1, E2, E4]));
expect(lines({ i1: 'for', e2: 'none', e4: 'against', gone: 'for' }, known)).toEqual([
{ proposalId: 'i1', choice: 'for' },
{ proposalId: 'e2', choice: null },
@@ -93,8 +93,9 @@ describe('the basket of a vote', () => {
expect(counts({ i1: 'for', e2: 'none', e4: 'against', e5: 'for' })).toEqual({ for: 2, against: 1, none: 1, total: 4 });
});
it('a version held back by an objection is shown apart: no grouped gesture fills it, nothing is sent for it', () => {
// E5 has the text in force and a rival held back; E6 is a new article, held back.
it('a version held back by an objection is shown apart: against or a withdrawal, never for', () => {
// Yvv, 2026-11-01: « Retirer ou "contre" ». E5 has the text in force and a rival
// held back; E6 is a new article, held back.
const e5 = unit('E5', 'technique', prop('e5', 'E5', { status: 'in_force' }), [
prop('e5r', 'E5', { blocked: true, votable: false }),
]);
@@ -103,15 +104,28 @@ describe('the basket of a vote', () => {
const [slotE5, slotE6] = s[0]?.slots ?? [];
expect(slotE5?.versions.map((p) => p.id)).toEqual(['e5']);
expect(slotE5?.held.map((p) => p.id)).toEqual(['e5r']);
// A held article stays in sight, with nothing to choose.
// A held article stays in sight, apart from the versions in competition.
expect(slotE6?.versions).toEqual([]);
expect(slotE6?.held.map((p) => p.id)).toEqual(['e6']);
const all = s.flatMap((x) => x.slots);
// The held rival does not compete now: the text in force is the only choice.
expect(fill(all, 'for', {})).toEqual({ e5: 'for' });
// The held rival does not compete now: the text in force is the only version to choose.
expect(waiting(all, {})).toEqual([]);
// A choice kept from before the objection is not sent.
expect(lines({ e5: 'for', e5r: 'for' }, votableIds(s))).toEqual([{ proposalId: 'e5', choice: 'for' }]);
// « Tout pour » skips the held versions (and says so: `heldIn`); « Tout contre » takes them.
expect(fill(all, 'for', {})).toEqual({ e5: 'for' });
expect(heldIn(all).map((p) => p.id)).toEqual(['e5r', 'e6']);
expect(fill(all, 'against', {})).toEqual({ e5: 'against', e5r: 'against', e6: 'against' });
// What may be chosen: anything on a free version; against or withdrawing on a held one.
const known = votableIds(s);
expect(['for', 'against', 'none'].map((c) => admits(known, 'e5', c as Choice))).toEqual([true, true, true]);
expect(['for', 'against', 'none'].map((c) => admits(known, 'e5r', c as Choice))).toEqual([false, true, true]);
expect(admits(known, 'gone', 'against')).toBe(false);
// A « for » kept from before the objection is neither kept nor sent; against and withdrawing are.
expect(kept({ e5: 'for', e5r: 'for', e6: 'against', gone: 'none' }, known)).toEqual({ e5: 'for', e6: 'against' });
expect(lines({ e5: 'for', e5r: 'for', e6: 'none' }, known)).toEqual([
{ proposalId: 'e5', choice: 'for' },
{ proposalId: 'e6', choice: null },
]);
expect(lines({ e5r: 'against' }, known)).toEqual([{ proposalId: 'e5r', choice: 'against' }]);
expect(clearScope(all, { e5: 'for', e5r: 'against', i1: 'for' })).toEqual({ i1: 'for' });
});
});