//! What is left to vote in a document — the line of « Décisions ». //! //! A module is an article, the title of the document, or the order of a //! section (ADR 0009) — a unit. It stays **to vote** as long as no version //! of it was adopted by a vote: its version in force is a seed a minima not yet //! confirmed, or it has no version in force and at least one open version. //! Variants of an article count once. The order of a section of fewer than two //! articles has nothing to decide: never to vote. For a person who votes, a module is //! **without their vote** when they hold no valid vote (one not « to //! reconfirm ») on any of its versions. /// One article, as one person sees it. #[derive(Clone, Copy, Debug, Default, PartialEq, Eq)] pub struct Module { /// The version in force, if any: `Some(true)` when a vote adopted or /// confirmed it, `Some(false)` for a seed a minima. pub in_force: Option, /// Open versions of the article (proposals to replace, add or remove it). pub open: u32, /// The person holds a valid vote on one of its versions, in force or open. pub voted: bool, /// Nothing to decide: the order of a section of fewer than two articles /// (`order::is_moot`). Never to vote, and not shown. pub moot: bool, } impl Module { pub fn to_vote(&self) -> bool { if self.moot { return false; } match self.in_force { Some(adopted) => !adopted, None => self.open > 0, } } } #[derive(Clone, Copy, Debug, Default, PartialEq, Eq)] pub struct Pending { /// Modules not yet adopted by a vote. pub to_vote: u32, /// Among them, those where the person has no valid vote. pub without_my_vote: u32, } pub fn pending(modules: &[Module]) -> Pending { modules.iter().filter(|m| m.to_vote()).fold(Pending::default(), |acc, m| Pending { to_vote: acc.to_vote + 1, without_my_vote: acc.without_my_vote + u32::from(!m.voted), }) } #[cfg(test)] mod tests { use super::*; const SEED: Option = Some(false); const ADOPTED: Option = Some(true); fn m(in_force: Option, open: u32, voted: bool) -> Module { Module { in_force, open, voted, moot: false } } #[test] fn a_seed_a_minima_stays_to_vote_until_a_vote_confirms_it() { assert!(m(SEED, 0, false).to_vote()); assert!(m(SEED, 2, true).to_vote()); assert!(!m(ADOPTED, 0, false).to_vote()); } #[test] fn an_adopted_article_is_no_longer_to_vote_even_with_rivals() { assert!(!m(ADOPTED, 3, false).to_vote()); } #[test] fn an_empty_slot_is_to_vote_only_with_an_open_version() { assert!(m(None, 1, false).to_vote()); assert!(!m(None, 0, false).to_vote()); } #[test] fn variants_count_once_and_my_vote_on_any_version_counts() { // Seed of the certification act: 3 seeds a minima, 30 open articles (two with a variant). let mut modules = vec![m(SEED, 0, false); 3]; modules.extend(vec![m(None, 1, false); 28]); modules.extend(vec![m(None, 2, false); 2]); assert_eq!(pending(&modules), Pending { to_vote: 33, without_my_vote: 33 }); modules[0].voted = true; modules[31].voted = true; assert_eq!(pending(&modules), Pending { to_vote: 33, without_my_vote: 31 }); // Once adopted, an article leaves both counts, voted or not. modules[1].in_force = ADOPTED; modules[31].in_force = ADOPTED; assert_eq!(pending(&modules), Pending { to_vote: 31, without_my_vote: 30 }); } #[test] fn an_order_with_nothing_to_decide_is_never_to_vote() { let one_article = Module { moot: true, ..m(SEED, 1, false) }; assert!(!one_article.to_vote()); assert_eq!(pending(&[one_article]), Pending::default()); } #[test] fn the_modules_of_the_certification_act() { // 33 articles; the title of the document; the order of each of the 8 sections, // all seeds a minima — but « ordonnancement » holds one article: 41 to vote. let mut modules = vec![m(SEED, 0, false); 3]; modules.extend(vec![m(None, 1, false); 30]); modules.push(m(SEED, 0, false)); modules.extend(vec![m(SEED, 0, false); 7]); modules.push(Module { moot: true, ..m(SEED, 0, false) }); assert_eq!(pending(&modules), Pending { to_vote: 41, without_my_vote: 41 }); } #[test] fn nothing_to_vote_in_an_empty_document() { assert_eq!(pending(&[]), Pending::default()); } }