facett-releaseview/tests/vault.rs
//! **RED-when-broken proofs for the CodeVault hero** (feature `graph3d`) — the
//! `Planes3D` mode rendered through the shared facett-graph3d CodeVault, plus the
//! headless `TriBadge2D` fallback that must survive with the feature on.
//!
//! Run: `cargo test -p facett-releaseview --features graph3d`.
#![cfg(feature = "graph3d")]
use std::cell::RefCell;
use std::rc::Rc;
use egui_kittest::Harness;
use egui_kittest::kittest::{By, Queryable};
use facett_core::Facet;
use facett_releaseview::model::{CrateConvergence, DepEdge, ReleaseConvergence, ReleaseEvent};
use facett_releaseview::vault::{convergence_to_codegraph, vault_state_json};
use facett_releaseview::{ReleaseView, ViewMode};
/// A 4-crate constellation starting DIVERGED: two need-release (red), one bump-drift
/// (amber), one converged (green).
fn sample() -> ReleaseConvergence {
ReleaseConvergence::new("nordisk release-3")
.with_node(CrateConvergence::new("skade", "0.5.1", "0.5.1", "0.5.1")) // converged
.with_node(CrateConvergence::new("dwarves", "0.1.4", "0.1.4", "0.1.3")) // needs-release
.with_node(CrateConvergence::new("holger", "0.6.9", "0.6.9", "0.6.8")) // needs-release
.with_node(CrateConvergence::new("nornir", "0.5.3", "0.5.4", "0.5.4")) // bump-drift
.with_edge(DepEdge::new(1, 0))
.with_edge(DepEdge::new(2, 1))
.with_edge(DepEdge::new(3, 2))
}
#[test]
fn planes3d_renders_through_the_codevault_and_reports_collapse() {
// The hero path: Planes3D under `graph3d` lowers the model onto the shared
// CodeVault and renders it headless (CPU projection — no GPU). The CodeVault's own
// a11y ("Code Vault — N boxes …") proves the reused renderer drew it, not the CPU
// parallelogram; state_json.vault carries the convergence-collapse as data.
let view = Rc::new(RefCell::new(ReleaseView::from_model(sample()).with_mode(ViewMode::Planes3D).with_clock(0.0)));
let ui_view = view.clone();
let mut harness = Harness::builder().with_size(egui::vec2(1000.0, 720.0)).build_ui(move |ui| {
ui_view.borrow_mut().ui(ui);
});
harness.run();
harness.run();
// The CodeVault renderer is the one drawing (its a11y image label).
assert!(harness.query_all(By::new().label_contains("Code Vault")).count() >= 1, "the shared CodeVault must render the 3-plane hero");
let sj = view.borrow().state_json();
assert_eq!(sj["mode"], "3-plane");
let vault = &sj["vault"];
assert_eq!(vault["renderer"], "codevault");
assert_eq!(vault["crate_count"], 4);
// The sample has two needs-release (red) crates + a bump-drift → separated planes.
assert_eq!(vault["collapsed"], false, "a diverged release has separated planes");
assert_eq!(vault["glitch_count"], 2, "the two needs-release crates throb red");
assert!(vault["max_abs_z"].as_f64().unwrap() > 0.0, "at least one plane is pushed off z=0");
// dwarves (crates.io behind) has its separated box on the CRATES_IO plane.
let dwarves = vault["crates"].as_array().unwrap().iter().find(|c| c["krate"] == "dwarves").unwrap();
let lag = dwarves["laggards"].as_array().unwrap();
assert!(lag.iter().any(|l| l["plane"] == "cratesio"), "dwarves is separated on the crates.io plane");
assert!(lag.iter().any(|l| l["z"].as_f64().unwrap().abs() > 0.0), "the laggard is off z=0");
}
#[test]
fn all_agree_collapses_the_planes_into_one() {
// Convergence complete ⇒ every crate is a single box on the merged plane; the
// state_json reports collapsed=true and convergence 1.0. RED-when-broken: if a
// converged crate spuriously split, collapsed flips false.
let converged = ReleaseConvergence::new("done")
.with_node(CrateConvergence::new("a", "1.0.0", "1.0.0", "1.0.0"))
.with_node(CrateConvergence::new("b", "2.0.0", "2.0.0", "2.0.0"))
.with_edge(DepEdge::new(1, 0));
let v = ReleaseView::from_model(converged).with_mode(ViewMode::Planes3D).with_clock(0.0);
let sj = v.state_json();
assert_eq!(sj["converged"], true);
let vault = &sj["vault"];
assert_eq!(vault["collapsed"], true, "all-agree collapses every box onto the merged plane");
assert_eq!(vault["box_count"], 2, "one box per converged crate");
assert_eq!(vault["glitch_count"], 0, "nothing is throbbing when converged");
assert!((vault["converged_fraction"].as_f64().unwrap() - 1.0).abs() < 1e-9);
}
#[test]
fn tribadge_fallback_still_renders_headless_with_graph3d_on() {
// The cheap 2D fallback must keep working even when the GPU hero feature is
// compiled in (headless / no-GPU / wasm reach for this path).
let view = Rc::new(RefCell::new(ReleaseView::from_model(sample()).with_mode(ViewMode::TriBadge2D).with_clock(0.0)));
let ui_view = view.clone();
let mut harness = Harness::builder().with_size(egui::vec2(1000.0, 720.0)).build_ui(move |ui| {
ui_view.borrow_mut().ui(ui);
});
harness.run();
harness.run();
for krate in ["skade", "dwarves", "holger", "nornir"] {
assert!(harness.query_all(By::new().label_contains(krate)).count() >= 1, "tri-badge fallback shows `{krate}`");
}
// It is NOT the CodeVault (the fallback stays the flat CPU DAG).
assert_eq!(harness.query_all(By::new().label_contains("Code Vault")).count(), 0, "TriBadge2D is the CPU fallback, not the vault");
assert_eq!(view.borrow().state_json()["mode"], "tri-badge");
}
#[test]
fn live_cascade_collapses_the_vault_scene() {
// The animation IS the convergence: as publish events land, the separated planes
// collapse. Assert the box tally + collapse flag follow the model node-by-node.
let mut v = ReleaseView::from_model(sample());
let before = vault_state_json(v.model(), 1.0);
assert_eq!(before["collapsed"], false);
let before_boxes = before["box_count"].as_u64().unwrap();
v.apply_event(ReleaseEvent::Published { krate: "dwarves".into(), version: "0.1.4".into() });
v.apply_event(ReleaseEvent::Published { krate: "holger".into(), version: "0.6.9".into() });
v.apply_event(ReleaseEvent::LocalBumped { krate: "nornir".into(), version: "0.5.4".into() });
let after = vault_state_json(v.model(), 1.0);
assert_eq!(after["collapsed"], true, "the whole scene collapses once every crate converges");
assert_eq!(after["glitch_count"], 0);
assert!(after["box_count"].as_u64().unwrap() < before_boxes, "laggard boxes merge away as crates converge");
// The mapping itself agrees: no laggards remain.
let (_, positions, meta) = convergence_to_codegraph(v.model(), 1.0);
assert!(meta.iter().all(|m| m.target), "no separated planes left after convergence");
assert!(positions.iter().all(|p| p[2].abs() < 1e-4), "every box on the merged plane");
}