Fusion de la tête du chantier (924a40d : lots B2 et F) dans le lot D

Les deux côtés gardés partout. Aux coutures :
- une objection (redit / contredit) porte sur deux articles, jamais sur un module ;
  un ordre ne se corrige pas, ni par son auteur ni par un autre ; une coquille du
  titre suit le chemin de F (l'auteur du titre, un administrateur pour le titre
  de départ, ou 3 jours), et documents.title suit à l'application ;
- votable() couvre les modules ; leurs totaux passent par shown_tally, à l'écran
  et dans document.json ;
- « Modules à voter » ne compte plus une version retenue par une objection (test
  vu rouge) ; la liste du groupe la compte toujours parmi les propositions en cours ;
- le panier : une fonction pour les articles et les modules, avec les versions
  retenues de F ; un nouvel article se propose dans une section, à sa fin, et peut
  répondre à une objection ;
- migrations 0007 (modules) puis 0008 (objections) ;
- C25 et C26 supportent un groupe d'essai où une voix suffit (dès 16 membres).

Boucle : fmt, clippy -D warnings, cargo test (227), svelte-check 0, vitest 55,
build, e2e 35 / 35 (C1–C33 et M1).

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
Yvv
2026-10-31 09:41:30 +01:00
co-authored by Claude Opus 5.5
82 changed files with 8285 additions and 677 deletions
+28
View File
@@ -210,6 +210,34 @@ async fn an_adopted_module_leaves_the_count_and_a_vote_to_reconfirm_does_not_cou
assert_eq!(decisions(&mut yvv, "essai").await["docs"][0]["withoutMyVote"], 41);
}
#[tokio::test]
async fn a_version_held_by_an_objection_is_not_to_vote_while_it_is_held() {
let mut yvv = Client::new().await;
yvv.login("Yvv", true).await;
let doc = the_doc(&mut yvv, "essai").await;
// Two articles of a single open version each: one of them is held (ADR 0010).
let (i2, k1) = (version(&doc, "I2", "0"), version(&doc, "K1", "0"));
let (s, v) = yvv
.call(
"POST",
&format!("/api/proposals/{i2}/objections"),
Some(json!({ "otherId": k1, "kind": "contredit", "reason": "Les deux disent l'inverse." })),
)
.await;
assert_eq!(s, StatusCode::OK, "{v}");
// Nobody can vote on it while it is held: one module fewer to vote, for everyone.
let d = decisions(&mut yvv, "essai").await;
assert_eq!((d["docs"][0]["toVote"].as_u64(), d["docs"][0]["withoutMyVote"].as_u64()), (Some(40), Some(40)));
// The list of the group still counts it among the proposals in progress: it is open.
let g = yvv.get("/api/groups/essai").await;
let open = doc["units"]
.as_array()
.expect("units")
.iter()
.map(|u| u["proposals"].as_array().expect("p").iter().filter(|p| p["status"] == "open").count() as u64);
assert_eq!(g["docs"][0]["open"].as_u64(), Some(open.sum::<u64>()));
}
#[tokio::test]
async fn where_one_does_not_vote_there_is_no_count_of_ones_own() {
let mut admin = Client::new().await;