Files
decision/web/tests/unit/opening.test.ts
T

131 lines
5.1 KiB
TypeScript
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
// Lot U3 — « Info ouverture doc de réf. » (Yvv, 2026-11-03: « un petit chapitre qui explicite les
// réglages initiaux de l'auteur, notamment Quorum et Inertie »): what the chapter says is read in
// the data of the document, never written in. The level a module opened at is the one before any
// change voted (a change of the seed's settings, an amendment, is the author's); a Proposition
// d'amorce stays one once it has entered into force (`seedFeatured`).
import { describe, expect, it } from 'vitest';
import { isAmorce, openedAt, openingLevels, quorumsOf } from '../../src/lib/opening';
import type { DocView, Inertia, InertiaChange, Proposal, Unit } from '../../src/lib/types';
function version(over: Partial<Proposal> = {}): Proposal {
return {
id: 'p',
unitId: 'u',
kind: 'text',
inertiaTarget: null,
title: 'Un article',
body: '',
status: 'open',
origin: 'seed',
label: null,
author: null,
mine: false,
revision: 1,
createdAt: '2026-11-03T00:00:00Z',
updatedAt: '2026-11-03T00:00:00Z',
submittedAt: null,
enteredAt: null,
confirmedAt: null,
seedMinimal: false,
seedProposal: false,
seedFeatured: false,
tally: { for: 0, against: 0 },
tallyPending: { for: 0, against: 0 },
tallySimulated: { for: 0, against: 0 },
evaluation: null,
votedAtInertia: 3,
blocked: false,
votable: true,
objections: 0,
myVote: null,
myVoteState: null,
myVoteStale: false,
myVoteChain: false,
myBallot: null,
translations: {},
...over,
};
}
let n = 0;
function unit(inertia: Inertia, over: Partial<Unit> = {}, versions: Partial<Proposal>[] = [{}]): Unit {
n += 1;
return {
id: `u${n}`,
code: `X${n}`,
section: 'technique',
position: n,
inertia,
inertiaChanges: [],
inForce: null,
proposals: versions.map((v) => version({ id: `p${n}`, ...v })),
...over,
};
}
const change = (from: Inertia, to: Inertia, cause: InertiaChange['cause']): InertiaChange => ({ from, to, cause, at: '2026-11-03T00:00:00Z' });
describe('the level a module opened at', () => {
it('is its level when nothing changed it', () => {
expect(openedAt(unit(3))).toBe(3);
});
it('is the author’s: a change of the seed’s settings (an amendment) is part of the opening', () => {
expect(openedAt(unit(2, { inertiaChanges: [change(3, 2, 'amendment')] }))).toBe(2);
});
it('is the one before the first change voted, whatever came after', () => {
expect(openedAt(unit(4, { inertiaChanges: [change(2, 4, 'vote')] }))).toBe(2);
expect(openedAt(unit(5, { inertiaChanges: [change(3, 2, 'amendment'), change(2, 4, 'vote'), change(4, 5, 'vote')] }))).toBe(2);
});
});
describe('a Proposition d’amorce', () => {
it('is the article the seed put forward, open or in force since', () => {
expect(isAmorce(unit(2, {}, [{ seedProposal: true, seedFeatured: true }]))).toBe(true);
expect(isAmorce(unit(2, { inForce: version({ status: 'in_force', seedFeatured: true, enteredAt: '2026-11-03T00:00:00Z' }) }, []))).toBe(true);
expect(isAmorce(unit(3))).toBe(false);
});
it('a server that does not say it yet makes no article an amorce', () => {
const old = version();
delete (old as Partial<Proposal>).seedFeatured;
expect(isAmorce(unit(3, { proposals: [old] }))).toBe(false);
});
});
describe('the levels at the opening, by kind of module', () => {
function view(units: Unit[], title: Unit | null, orders: Unit[]): Pick<DocView, 'units' | 'modules'> {
return { units, modules: { title, orders } };
}
it('the amorces, the title, the order of each section, the other articles of the seed', () => {
const v = view(
[
unit(2, {}, [{ seedProposal: true, seedFeatured: true }]),
unit(3, { inertiaChanges: [change(2, 3, 'vote')], inForce: version({ status: 'in_force', seedFeatured: true }) }, []),
unit(3),
unit(4, { inertiaChanges: [change(3, 4, 'vote')] }),
// A member's article is not a setting of the author.
unit(5, {}, [{ origin: 'member' }]),
],
unit(4, { kind: 'title' }),
[unit(2, { kind: 'order', section: 'introduction' }), unit(3, { kind: 'order', section: 'technique', inertiaChanges: [change(2, 3, 'vote')] })],
);
expect(openingLevels(v)).toEqual({ amorce: [2], title: [4], orders: [2], others: [3] });
});
it('several levels in one kind are all said, lowest first; a kind that is absent says none', () => {
const v = view([unit(4), unit(3), unit(4)], null, []);
expect(openingLevels(v)).toEqual({ amorce: [], title: [], orders: [], others: [3, 4] });
});
});
describe('the quorum of each level', () => {
it('said once when every level has the same', () => {
expect(quorumsOf([1, 1, 1, 1, 1])).toEqual({ same: 1, list: [1, 1, 1, 1, 1] });
});
it('listed from level 1 to level 5 otherwise', () => {
expect(quorumsOf([12, 12, 50, 50, 50])).toEqual({ same: null, list: [12, 12, 50, 50, 50] });
});
it('nothing when the server does not say them', () => {
expect(quorumsOf(undefined)).toBeNull();
expect(quorumsOf([])).toBeNull();
});
});