/** * electionResult — simple plurality (BLUEPRINT-V2.md Δ28, « Modalités » #4). * * Contract under test: the caller passes the LAST ACTIVE votes only * (supersedesVoteId chains resolved by the store) — one vote per voter. * Blank vote = no choicePersonId: counts for participation, never for * designation. The engine NEVER breaks a tie. */ import { describe, expect, it } from 'vitest' import type { Vote } from '../../app/types/domain' import { electionResult } from '../../app/engine/threshold' let seq = 0 /** Build a minimal last-active Vote; omit choicePersonId for a blank. */ function vote(choicePersonId?: string): Vote { seq++ return { id: `vote-${seq}`, collectiveId: 'col-1', createdAt: '2026-08-11T00:00:00Z', updatedAt: '2026-08-11T00:00:00Z', sessionId: 'session-1', voterId: `voter-${seq}`, choicePersonId, } } /** n votes for the same person (or n blanks when personId is undefined). */ function votes(n: number, personId?: string): Vote[] { return Array.from({ length: n }, () => vote(personId)) } describe('electionResult — clear plurality', () => { it('elects the most designated person; blanks count in participation only', () => { const ballot = [...votes(3, 'alice'), ...votes(2, 'bob'), ...votes(1)] const result = electionResult(ballot, { electionMinParticipants: 5 }) expect(result).toEqual({ outcome: 'elected', winnerId: 'alice', counts: { alice: 3, bob: 2 }, blanks: 1, participants: 6, }) }) it('a tie below the top does not prevent election', () => { const ballot = [...votes(3, 'alice'), ...votes(2, 'bob'), ...votes(2, 'carol')] const result = electionResult(ballot, {}) expect(result.outcome).toBe('elected') if (result.outcome === 'elected') { expect(result.winnerId).toBe('alice') expect(result.counts).toEqual({ alice: 3, bob: 2, carol: 2 }) } }) it('works without any quorum configured (electionMinParticipants undefined)', () => { const result = electionResult([vote('alice')], {}) expect(result.outcome).toBe('elected') }) }) describe('electionResult — tie: the engine NEVER breaks it', () => { it('two-way tie at the top => tie with sorted exAequoIds', () => { const ballot = [...votes(2, 'bob'), ...votes(2, 'alice'), ...votes(1, 'carol')] const result = electionResult(ballot, { electionMinParticipants: 3 }) expect(result).toEqual({ outcome: 'tie', exAequoIds: ['alice', 'bob'], // sorted — deterministic order, not a tie-break counts: { alice: 2, bob: 2, carol: 1 }, blanks: 0, participants: 5, }) }) it('three-way tie => all three ex aequo, sorted regardless of arrival order', () => { const ballot = [...votes(2, 'carol'), ...votes(2, 'alice'), ...votes(2, 'bob'), ...votes(1)] const result = electionResult(ballot, { electionMinParticipants: 4 }) expect(result.outcome).toBe('tie') if (result.outcome === 'tie') { expect(result.exAequoIds).toEqual(['alice', 'bob', 'carol']) expect(result.blanks).toBe(1) expect(result.participants).toBe(7) } }) it('never designates a winner among equals (no random, no first-come)', () => { // Same ballot tallied twice must yield the exact same tie. const ballot = [...votes(1, 'bob'), ...votes(1, 'alice')] const first = electionResult(ballot, {}) const second = electionResult(ballot, {}) expect(first).toEqual(second) expect(first.outcome).toBe('tie') }) }) describe('electionResult — rejections', () => { it('quorum not reached => rejected with reason quorum and required', () => { const ballot = [...votes(2, 'alice'), ...votes(1)] const result = electionResult(ballot, { electionMinParticipants: 5 }) expect(result).toEqual({ outcome: 'rejected', reason: 'quorum', participants: 3, required: 5, }) }) it('quorum reached but all blanks => rejected with reason no-designation', () => { const ballot = votes(4) const result = electionResult(ballot, { electionMinParticipants: 3 }) expect(result).toEqual({ outcome: 'rejected', reason: 'no-designation', participants: 4, required: 3, }) }) it('zero votes without quorum => rejected no-designation (required 0)', () => { const result = electionResult([], {}) expect(result).toEqual({ outcome: 'rejected', reason: 'no-designation', participants: 0, required: 0, }) }) }) describe('electionResult — blanks and participation', () => { it('quorum reached THANKS to blanks: blanks count for participation', () => { // 3 designations alone would miss the quorum of 5; 2 blanks complete it. const ballot = [...votes(3, 'alice'), ...votes(2)] const result = electionResult(ballot, { electionMinParticipants: 5 }) expect(result).toEqual({ outcome: 'elected', winnerId: 'alice', counts: { alice: 3 }, blanks: 2, participants: 5, }) }) it('exact quorum boundary: participants === required passes', () => { const ballot = [...votes(1, 'alice'), ...votes(1)] const result = electionResult(ballot, { electionMinParticipants: 2 }) expect(result.outcome).toBe('elected') }) it('blanks never appear in counts', () => { const ballot = [...votes(2, 'alice'), ...votes(3)] const result = electionResult(ballot, {}) if (result.outcome === 'elected') { expect(Object.keys(result.counts)).toEqual(['alice']) expect(result.blanks).toBe(3) } else { throw new Error(`expected elected, got ${result.outcome}`) } }) })