Your browser sucks, JavaScript disabled! Gunnar's console needs JavaScript and WebAssembly. Git over SSH on port 2222 does not.

nordisk/facett

xtask/tests/gallery_covers_the_manifest.rs

//! **The Streamlit gallery must show every component this build ships.**
//!
//! `py/facett_demo/demo_payloads.json` is the one payload table the gallery
//! renders from. The failure it exists to prevent is quiet: a component is added to
//! the registry, the wheel ships it, the manifest lists it — and the demo never
//! mounts it, because nobody remembered to write a payload. Nothing goes red. The
//! gallery just silently shows 26 of 27 surfaces, and the missing one is the new one.
//!
//! So this test asks the REGISTRY (not a checked-in manifest file, which could be
//! stale) and holds three properties:
//!
//! 1. every registered component is either in `payloads` or in `fixture_only`;
//! 2. the table names nothing that is not registered;
//! 3. **every payload actually BUILDS its component**, through the real adapter.
//!
//! (3) is the one that matters. A coverage check that only compares NAMES passes
//! happily on a payload the adapter rejects — and the gallery would then show the
//! loader's refusal panel for that component while this test stayed green. Names are
//! the identity value here; a built component is the applied output.

use std::collections::BTreeSet;
use std::path::PathBuf;

fn payload_table() -> serde_json::Value {
    let p = PathBuf::from(env!("CARGO_MANIFEST_DIR"))
        .parent()
        .unwrap()
        .join("py/facett_demo/demo_payloads.json");
    let text = std::fs::read_to_string(&p)
        .unwrap_or_else(|e| panic!("the gallery's payload table must exist at {}: {e}", p.display()));
    serde_json::from_str(&text)
        .unwrap_or_else(|e| panic!("{} is not valid JSON: {e}", p.display()))
}

fn payload_names(t: &serde_json::Value) -> BTreeSet<String> {
    t["payloads"].as_object().expect("payloads must be an object").keys().cloned().collect()
}

fn fixture_only(t: &serde_json::Value) -> BTreeSet<String> {
    t["fixture_only"]
        .as_array()
        .expect("fixture_only must be an array")
        .iter()
        .map(|v| v.as_str().expect("fixture_only entries are strings").to_string())
        .collect()
}

#[test]
fn every_registered_component_is_in_the_gallery() {
    let reg = facet_wrapped::Registry::with_builtins();
    let table = payload_table();
    let covered: BTreeSet<String> =
        payload_names(&table).union(&fixture_only(&table)).cloned().collect();

    let missing: Vec<&str> =
        reg.names().into_iter().filter(|n| !covered.contains(*n)).collect();
    assert!(
        missing.is_empty(),
        "these components are registered and shipped but the Streamlit gallery would never \
         mount them: {missing:?}. Add a payload to demo_payloads.json, or list the component \
         under `fixture_only` if it has no data seam yet."
    );
}

#[test]
fn the_gallery_names_nothing_that_does_not_exist() {
    // The other direction. A payload for a component that was renamed or removed is a
    // gallery entry that mounts nothing, and the loader answers "unknown component".
    let reg = facet_wrapped::Registry::with_builtins();
    let registered: BTreeSet<String> = reg.names().into_iter().map(String::from).collect();
    let table = payload_table();

    for name in payload_names(&table).union(&fixture_only(&table)) {
        assert!(
            registered.contains(name),
            "demo_payloads.json names `{name}`, which this build does not register"
        );
    }
}

#[test]
fn the_classification_matches_the_registry() {
    // `fixture_only` is not an opinion — the registry knows which entries accept data.
    // If the two disagree, either the gallery withholds a payload a component wants,
    // or it hands one to a component that will ignore it and render its own fixture.
    // The second is the dangerous direction: it looks exactly like success.
    let reg = facet_wrapped::Registry::with_builtins();
    let data_driven: BTreeSet<String> =
        reg.data_driven_names().into_iter().map(String::from).collect();
    let table = payload_table();

    for name in payload_names(&table) {
        assert!(
            data_driven.contains(&name),
            "the gallery has a payload for `{name}`, but the registry says it takes no data — \
             it would render its own FIXTURE while the gallery believed it was showing this \
             payload"
        );
    }
    for name in fixture_only(&table) {
        assert!(
            !data_driven.contains(&name),
            "`{name}` is listed fixture-only but the registry says it accepts data — the \
             gallery is withholding a payload from a component that wants one"
        );
    }
}

#[test]
fn every_gallery_payload_actually_builds_its_component() {
    // THE check. Names prove nothing: this hands each payload to the same adapter the
    // wasm calls and asserts the component is constructed. A payload the adapter
    // rejects shows the loader's refusal panel in the browser, and a name-only test
    // would call that green.
    let reg = facet_wrapped::Registry::with_builtins();
    let table = payload_table();
    let payloads = table["payloads"].as_object().unwrap();

    let mut built = 0usize;
    for (name, payload) in payloads {
        reg.check(name, payload).unwrap_or_else(|e| {
            panic!(
                "the gallery's payload for `{name}` is REFUSED by the real adapter: {e}\n\
                 payload: {}",
                serde_json::to_string(payload).unwrap_or_default()
            )
        });
        // ...and it must really construct, not merely pass a pre-flight.
        assert!(
            reg.build_with(name, payload).is_ok(),
            "`{name}` passed check() but build_with() failed"
        );
        built += 1;
    }
    assert_eq!(built, payloads.len());
    assert!(built >= 19, "expected at least 19 data-driven payloads, got {built}");
}

#[test]
fn the_gallery_covers_every_group_the_registry_declares() {
    // A gallery that renders 19 tables and no map would satisfy every count above.
    // Rickard's priority order is graphs, then the Spark viewer, then OSM 2D+3D — so
    // the demo must actually reach the map and graph families, not just the data one.
    let table = payload_table();
    let names = payload_names(&table);
    for must in ["osm2d", "osm3d", "graphnav", "graph3d", "pipeline"] {
        assert!(
            names.contains(must),
            "`{must}` is a priority surface for this lane and the gallery has no payload for it"
        );
    }
}