py/tests/test_pane_guards.py
"""Guards: the facett-component panes must render through the wasm bridge.
These are anti-regression tests for the specific failure that produced this rewrite —
a Streamlit demo that *looked* like facett but was a parallel pydeck/plotly
re-implementation, which then drifted (a CARTO world basemap made a correctly-clipped
Sweden appear to include Africa).
The rule they encode:
A pane that shows a facett COMPONENT must embed the real component through
`facet_host` — this app's thin mount of the pip-installed `facett`
wheel. No pane may render domain data with a Python renderer.
(The bridge used to be a `facett_embed` package built out of the source tree. It is
gone: the components now ship as the `facett` WHEEL and are mounted via
`facett_demo.facet_host`, so the demo exercises what a user pip-installs.
These guards were left asserting the dead name and passed vacuously on nothing.)
"""
from __future__ import annotations
import ast
from pathlib import Path
import pytest
import facett_demo
PANES = Path(facett_demo.__file__).resolve().parent / "panes"
#: Panes that embed a facett component — these must NOT contain a second renderer.
#: EVERY pane that renders domain data belongs here. There is no exemption for
#: "it's only a table" or "it's only a chart": facett owns facett-table, facett-grid,
#: facett-stat, facett-plot, facett-plotters, facett-matrix, facett-icebergview and
#: facett-warehousedeck, and both korp and dwarves depend on them. The population pane
#: was exempted once and immediately became a third pandas/plotly lookalike.
COMPONENT_PANES = {
"map_pane.py": "osm2d",
"graph_pane.py": "graphnav",
"population_pane.py": "table",
"errcode_pane.py": "table",
}
#: Renderers that would constitute a lookalike re-implementation of a facett component.
BANNED_RENDERERS = {"pydeck", "plotly", "networkx", "altair", "folium", "matplotlib", "bokeh"}
#: Panes exempt from the renderer ban. Empty ON PURPOSE — see COMPONENT_PANES. If a
#: pane renders domain data, a facett component renders it. Python is allowed only the
#: gRPC plumbing, the layout, and the controls.
ANALYTICS_PANES: set[str] = set()
def _imported_modules(path: Path) -> set[str]:
"""Top-level module names imported by a source file."""
tree = ast.parse(path.read_text(), filename=str(path))
mods: set[str] = set()
for node in ast.walk(tree):
if isinstance(node, ast.Import):
for a in node.names:
mods.add(a.name.split(".")[0])
elif isinstance(node, ast.ImportFrom):
if node.module and node.level == 0:
mods.add(node.module.split(".")[0])
return mods
@pytest.mark.parametrize("filename", sorted(COMPONENT_PANES))
def test_component_pane_has_no_lookalike_renderer(filename: str):
"""A facett-component pane must not import a competing chart/map library."""
path = PANES / filename
assert path.exists(), f"{filename} missing"
offenders = _imported_modules(path) & BANNED_RENDERERS
assert not offenders, (
f"{filename} imports {sorted(offenders)} — that is a SECOND renderer competing "
"with the real facett component. Mount the component via `facet_host` instead."
)
@pytest.mark.parametrize("filename,component", sorted(COMPONENT_PANES.items()))
def test_component_pane_embeds_the_real_component(filename: str, component: str):
"""It must actually go through the wasm bridge, naming the registry component."""
src = (PANES / filename).read_text()
assert "facet_host" in src, f"{filename} must mount through `facet_host`"
assert "facett_component(" in src, f"{filename} must call facett_component()"
assert component in src, f"{filename} must embed the `{component}` component"
def test_no_pane_imports_the_removed_source_tree_bridge():
"""`facett_embed` is gone. A pane (or guard) still naming it is dead code."""
stale = [p.name for p in sorted(PANES.glob("*.py")) if "facett_embed" in p.read_text()]
assert not stale, f"{stale} reference the removed `facett_embed` bridge"
def test_the_shipped_wheel_exposes_the_data_driven_components_the_panes_need():
"""The INSTALLED wheel must offer every pane's component AS LIVE-DATA.
If this fails, the wasm bundle is missing or was built from a fixture-only
registry — the exact condition under which a lookalike gets hand-rolled. It is
asserted against the pip-installed package, not the source tree, because that
is what the demo actually mounts.
"""
fw = pytest.importorskip("facett")
names = {c["name"] for c in fw.manifest()}
assert names, "no manifest — run `cargo xtask wrap-wasm` && `python make_wheel.py` to build the wheel"
assert fw.build_info()["wasm_present"], "the installed wheel ships no wasm"
for component in set(COMPONENT_PANES.values()):
assert component in names, f"`{component}` missing from the facet-wrapped registry"
assert fw.is_data_driven(component), (
f"`{component}` is registered but NOT data-driven — it could only ever show a "
"demo fixture, which is what caused the pydeck/plotly lookalikes."
)
def test_no_pane_renders_domain_data_in_python():
"""The absolute rule, swept over EVERY pane — not just the ones we remembered.
Any pane importing a chart/map/table renderer, or calling Streamlit's own data
renderers, is drawing domain data in Python instead of mounting the facett
component that already exists for it.
"""
banned_calls = ("st.dataframe(", "st.table(", "st.plotly_chart(", "st.pydeck_chart(",
"st.altair_chart(", "st.bar_chart(", "st.line_chart(", "st.map(")
for path in sorted(PANES.glob("*_pane.py")):
if path.name in ANALYTICS_PANES:
continue
mods = _imported_modules(path)
offenders = mods & BANNED_RENDERERS
assert not offenders, f"{path.name} imports a second renderer: {sorted(offenders)}"
src = path.read_text()
used = [c for c in banned_calls if c in src]
assert not used, (
f"{path.name} renders domain data with Streamlit's own widgets {used} — "
"mount the facett component instead (table / stat-grid / plot / osm2d / graphnav)."
)