facett-toast/tests/matrix.rs
//! **The functional matrix cell for facett-toast** — the pane emitted NO
//! testmatrix row of either aspect before this file (see
//! `.nornir/docs/test-matrix-inventory-history.md`, "~10 panes emit no cell").
//!
//! ONE call through the shared generic-`Facet` probe (L5) — no bespoke harness.
//! The toast stack is an OVERLAY (it paints into a ctx `Area`, not the inline
//! `Ui`), which is exactly why it had no render cell: the probe's `Facet::ui`
//! adapter forwards `ui.ctx()` so the overlay tessellates in the same frame.
use facett_toast::Toasts;
#[test]
fn toasts_probe_emits_render_and_input_cells() {
let mut view = Toasts::local();
let script = [
r#"{"push":{"text":"Nytt beslut","level":"Error","ttl":9.0,"case":"BL-13"}}"#,
r#"{"tick":5.0}"#, // ages the stack — the 4.0s Success toast expires
r#""clear""#,
];
let probe = facett_core::testmatrix::probe_and_emit("facett-toast", &mut view, &script);
assert!(probe.drew(), "the toast overlay must tessellate to vertices");
assert!(probe.cardinality() > 0, "state_json reports the stack");
assert!(probe.responded(), "update_json must drive state — a dead seam is a regression");
assert_eq!(probe.initial_state["count"], 4);
assert_eq!(probe.steps[0].state["count"], 5);
// EXACT expiry contract: tick(5.0) drops every toast whose ttl was ≤ 5.0
// (Sparat 4.0 and 3 nya ärenden 5.0) and keeps the three longer ones.
assert_eq!(probe.steps[1].state["count"], 3, "tick expires the short-lived toasts");
assert_eq!(probe.steps[2].state["count"], 0, "clear empties the stack");
}
/// RED-when-broken: `tick` must decay each toast by EXACTLY the elapsed time and
/// evict at zero — a loose "count went down" oracle would pass a decay that
/// dropped the wrong toasts.
#[test]
fn tick_decays_each_remaining_toast_by_the_exact_elapsed_time() {
let mut t = Toasts::local();
let before: Vec<f64> = t.state_json()["toasts"]
.as_array()
.unwrap()
.iter()
.map(|x| x["remaining"].as_f64().unwrap())
.collect();
assert_eq!(before, vec![4.0, 6.0, 8.0, 5.0]);
t.tick(3.5);
let after: Vec<f64> = t.state_json()["toasts"]
.as_array()
.unwrap()
.iter()
.map(|x| x["remaining"].as_f64().unwrap())
.collect();
assert_eq!(after.len(), 4, "nothing expires before its ttl");
for (b, a) in before.iter().zip(&after) {
assert!((b - a - 3.5).abs() < 1e-5, "each toast decays by exactly 3.5s: {b} → {a}");
}
t.tick(1.0); // the 4.0s toast now hits 0.5-1.0 < 0 → evicted
assert_eq!(t.len(), 3);
t.tick(100.0);
assert!(t.is_empty(), "everything expires eventually");
}