forked from yvv/decision
131 lines
5.1 KiB
TypeScript
131 lines
5.1 KiB
TypeScript
// 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();
|
||
});
|
||
});
|