facett-ops/tests/no_js_graphics.rs
//! **RED FIRST for the no-JavaScript-graphics rule.**
//!
//! Rickard's rule for this whole lane is one sentence — *"it must NOT use javascript
//! to do graphics, it must REUSE the RUST components, packaged in wasm"* — and a
//! sentence in a briefing enforces nothing. `facett_ops::jsguard` is the mechanical form,
//! and `pip::stage_package` runs it on the loader page before a wheel can be built.
//!
//! Every test below hands the scanner a page that DRAWS and asserts it is caught by
//! name. The most important one is `the_real_loader_page_passes`, paired with
//! `the_probe_exception_is_narrow`: the shipped page legitimately calls
//! `getContext`, inside the read-back probe that proves the renderer is WebGPU. A
//! guard that banned the substring would delete that proof and call itself green.
use facett_ops::jsguard;
/// The shipped loader page, read from the package source. Not a copy: a copy would
/// drift, and then this suite would be guarding a page nobody ships.
fn real_loader() -> String {
let path = std::path::Path::new(env!("CARGO_MANIFEST_DIR"))
.join("..")
.join("pkg-facet-wrapped/src/facett/assets/index.html");
std::fs::read_to_string(&path)
.unwrap_or_else(|e| panic!("the shipped loader must exist at {}: {e}", path.display()))
}
// ── the GREEN case: what we actually ship ──────────────────────────────────
#[test]
fn the_real_loader_page_passes() {
let html = real_loader();
let findings = jsguard::scan(&html);
assert!(
findings.is_empty(),
"the shipped loader must not draw, but the guard found:\n{}",
findings.iter().map(|f| f.to_string()).collect::<Vec<_>>().join("\n")
);
assert!(jsguard::check("loader", &html).is_ok());
}
#[test]
fn the_shipped_loader_really_does_contain_the_thing_the_exception_is_for() {
// If this ever fails, the exception below is guarding nothing and must go. A
// carve-out with no remaining reason is how a guard rots into a loophole.
let html = real_loader();
assert!(
html.contains("window.facet_canvas_api"),
"the read-back probe is gone — delete the getContext exception with it"
);
assert!(
html.contains("getContext(\"webgpu\")"),
"the probe no longer asks the canvas which API drew; that is the only \
independent evidence we have that this is WebGPU and not WebGL"
);
}
// ── RED: JavaScript painting on a canvas ───────────────────────────────────
#[test]
fn a_canvas_painter_is_refused_verb_by_verb() {
// Each verb on its own: a guard that only catches `fillRect` is a guard that
// catches the one example someone tested it with.
for verb in
["fillRect", "strokeRect", "beginPath", "lineTo", "fillText", "drawImage", "putImageData"]
{
let html = format!("<script>function draw(ctx) {{ ctx.{verb}(0,0,10,10); }}</script>");
let findings = jsguard::scan(&html);
assert_eq!(findings.len(), 1, "`{verb}` must be caught exactly once");
assert_eq!(findings[0].construct, verb);
assert!(jsguard::check("page", &html).is_err(), "`{verb}` must refuse the build");
}
}
// ── RED: the knut defect — a hand-built SVG renderer ───────────────────────
#[test]
fn a_hand_built_svg_renderer_is_refused() {
// This is the exact shape of `knut-pipelines/src/web.rs:293-405`: JS building
// SVG nodes for a DAG that the facett `pipeline` component already renders.
let html = r#"<script>
const n = document.createElementNS("http://www.w3.org/2000/svg", "rect");
svg.appendChild(n);
</script>"#;
let findings = jsguard::scan(html);
assert!(findings.iter().any(|f| f.construct == "createElementNS"));
assert!(jsguard::check("dag", html).is_err());
}
#[test]
fn an_inline_svg_element_is_refused() {
let html = "<div><svg width=\"400\" height=\"300\"></svg></div>";
let findings = jsguard::scan(html);
assert_eq!(findings.len(), 1);
assert_eq!(findings[0].construct, "<svg");
}
// ── RED: a charting library, which is a component by proxy ─────────────────
#[test]
fn a_charting_library_is_refused_by_name() {
// `py/panes/population_pane.py` renders through Plotly today — 165 lines of
// pandas + plotly that draw in the browser's JS. Same banned class, and this is
// the guard that will refuse it once that pane's page is the one being staged.
for lib in ["plotly", "echarts", "chart.js", "vega-lite"] {
let html = format!("<script src=\"https://cdn.example/{lib}\"></script>");
let findings = jsguard::scan(&html);
assert!(
findings.iter().any(|f| f.construct.eq_ignore_ascii_case(lib)),
"`{lib}` must be refused by name"
);
}
}
// ── RED: the exception is NARROW ───────────────────────────────────────────
#[test]
fn the_probe_exception_is_narrow() {
// Inside the probe: allowed, because it is asking which API drew.
let inside = r#"
window.facet_canvas_api = () => {
try { if (c.getContext("webgpu")) return "webgpu"; } catch (_) {}
return "none";
};
"#;
assert!(jsguard::scan(inside).is_empty(), "the read-back probe must stay legal");
// The same call, one function over: refused. This is a renderer acquiring a
// surface, and it is how JS graphics would come back in.
let outside = r#"
function render() {
const ctx = document.getElementById("c").getContext("2d");
ctx.fill();
}
"#;
let findings = jsguard::scan(outside);
assert!(
findings.iter().any(|f| f.construct == "getContext"),
"getContext outside the probe must be refused, got {findings:?}"
);
}
#[test]
fn the_probe_exception_closes_at_the_end_of_the_probe() {
// A page that opens the probe and then keeps going must NOT get a blanket pass
// for the rest of the file. If the region never closed, everything below one
// `window.facet_canvas_api` line would be exempt — which is exactly how a
// narrow exception becomes a hole.
let html = r#"
window.facet_canvas_api = () => {
try { if (c.getContext("webgpu")) return "webgpu"; } catch (_) {}
};
function sneak() {
const ctx = canvas.getContext("2d");
}
"#;
let findings = jsguard::scan(html);
assert_eq!(
findings.iter().filter(|f| f.construct == "getContext").count(),
1,
"exactly the one AFTER the probe must be caught, got {findings:?}"
);
assert!(findings[0].text.contains("2d"));
}
// ── the refusal has to be usable ───────────────────────────────────────────
#[test]
fn the_refusal_names_the_line_and_the_reason() {
let html = "<html>\n<body>\n<script>ctx.fillRect(0,0,1,1);</script>\n</body>\n</html>";
let err = jsguard::check("assets/index.html", html).unwrap_err();
assert!(err.contains("assets/index.html"), "must name the file: {err}");
assert!(err.contains("line 3"), "must name the line: {err}");
assert!(err.contains("fillRect"), "must name the construct: {err}");
assert!(err.contains("wasm"), "must say why: {err}");
}