import { describe, expect, it } from 'vitest'; import type { Proposal, Unit } from '../../src/lib/types'; import { articles, excerpts, level } from '../../src/lib/versions'; function prop(id: string, unitId: string, over: Partial = {}): Proposal { return { id, unitId, kind: 'text', inertiaTarget: null, title: unitId, body: `texte ${id}`, status: 'open', origin: 'seed', label: null, author: null, mine: false, revision: 1, createdAt: '', updatedAt: '', submittedAt: null, enteredAt: null, confirmedAt: null, seedMinimal: false, tally: { for: 0, against: 0 }, evaluation: { cast: 0, required: 50, reached: false, missing: 50, requiredRatio: 1, wellRated: false }, votedAtInertia: 'standard', blocked: false, signals: 0, myVote: null, myVoteStale: false, translations: {}, ...over, }; } function unit(id: string, proposals: Proposal[]): Unit { return { id, code: id, section: null, position: 0, inertia: 'standard', inForce: null, proposals }; } /** The reader's list: every open proposal, unit by unit, in document order. */ function items(...units: Unit[]) { return units.flatMap((u) => u.proposals.map((proposal) => ({ unit: u, proposal }))); } const tally = (f: number, against = 0) => ({ tally: { for: f, against } }); function at(xs: readonly T[], i: number): T { const x = xs[i]; if (x === undefined) throw new Error(`nothing at ${i}`); return x; } describe('the versions of an article', () => { it('one line per article, in document order, its versions inside', () => { const e2 = unit('E2', [prop('a', 'E2')]); const e3 = unit('E3', [prop('b1', 'E3', { label: '1.0.0' }), prop('b2', 'E3', { label: '0.4.0-draft' })]); const e4 = unit('E4', [prop('c', 'E4')]); const list = articles(items(e2, e3, e4)); expect(list.map((a) => a.unit.id)).toEqual(['E2', 'E3', 'E4']); expect(at(list, 1).versions.map((p) => p.id)).toEqual(['b1', 'b2']); }); it('the best version first: the highest water, then more for, then fewer against', () => { const u = unit('E3', [ prop('low', 'E3', tally(3)), prop('high', 'E3', tally(20, 4)), prop('same', 'E3', tally(20, 1)), ]); const a = at(articles(items(u)), 0); expect(a.versions.map((p) => p.id)).toEqual(['same', 'high', 'low']); expect(a.leads).toBe(true); }); it('equal versions keep their order, and none is said to lead', () => { const u = unit('E3', [prop('first', 'E3'), prop('second', 'E3')]); const a = at(articles(items(u)), 0); expect(a.versions.map((p) => p.id)).toEqual(['first', 'second']); expect(a.leads).toBe(false); }); it('a blocked version never comes first: it cannot enter into force', () => { const u = unit('E3', [prop('blocked', 'E3', { ...tally(40), blocked: true }), prop('free', 'E3', tally(2))]); expect(at(at(articles(items(u)), 0).versions, 0).id).toBe('free'); }); it('a filter keeps an article when one of its versions passes, with all its versions', () => { const wellRated = { cast: 9, required: 50, reached: false, missing: 41, requiredRatio: 1, wellRated: true }; const e3 = unit('E3', [prop('theirs', 'E3'), prop('mine', 'E3', { mine: true, evaluation: wellRated })]); const e4 = unit('E4', [prop('other', 'E4')]); for (const filter of ['mine', 'wellrated'] as const) { const list = articles(items(e3, e4), filter); expect(list.map((a) => a.unit.id), filter).toEqual(['E3']); expect(at(list, 0).versions).toHaveLength(2); } expect(articles(items(e3, e4), 'all')).toHaveLength(2); }); it('the water level is the share of the threshold reached', () => { expect(level(prop('x', 'E1', tally(25)))).toBe(0.5); expect(level(prop('x', 'E1', tally(80)))).toBe(1); expect(level(prop('x', 'E1', { evaluation: null }))).toBe(0); }); it('sibling versions are told apart where they part ways', () => { const a = 'Je me suis assuré de connaître suffisamment la personne qui gère cette clé publique. Connaître suffisamment ne signifie pas avoir vu.'; const b = 'Je me suis assuré de connaître suffisamment la personne qui gère cette clé publique : je sais la recontacter.'; expect(excerpts([a, b])).toEqual([ '… gère cette clé publique. Connaître suffisamment ne signifie pas avoir vu.', '… gère cette clé publique : je sais la recontacter.', ]); // A short common start reads well as it is. expect(excerpts(['Je certifie une personne.', 'Je certifie deux personnes.'])).toEqual([ 'Je certifie une personne.', 'Je certifie deux personnes.', ]); expect(excerpts(['Seule.'])).toEqual(['Seule.']); }); });