bit-atlas / tests /test_ui.py
Bit-Trading-Company's picture
CI deploy local
d8733da verified
Raw
History Blame Contribute Delete
16.8 kB
"""UI tests: design fidelity, escaping, and the action bridge.
The fidelity tests here are the cheap half of the check -- they assert the
stylesheets reach the page and the design's own values are used. The other
half (measuring the rendered page in a browser) cannot run in CI and is done
by hand against the deployed Space.
"""
from __future__ import annotations
from pathlib import Path
import pytest
from src import atlas
from bit_ui import bridge, icons, theme
from src.ui import chrome, shell
from tests.conftest import make_row
# --------------------------------------------------------------------------
# The design system's stylesheets must actually reach the page
# --------------------------------------------------------------------------
#
# This is the guard the design-port notes ask for: it is easy to vendor the
# CSS files and then wire up only some of them, which leaves the app looking
# *approximately* right in a way that is hard to name. Each assertion is keyed
# on a rule only that file contains.
@pytest.mark.parametrize("marker,source", [
("--bg-canvas", "colors.css"),
("--text-2xs", "typography.css"),
("--space-", "spacing.css"),
("border-radius", "base.css"),
])
def test_every_design_stylesheet_reaches_the_page(marker, source):
css = theme.full_css()
assert marker in css, f"{source} did not reach the page (missing {marker})"
def test_fonts_are_declared():
css = theme.full_css()
assert "@font-face" in css
assert "Styrene A" in css
assert "Mac Minecraft" in css
def test_theme_uses_the_designs_dense_type_scale():
"""Gradio's 16px default would be a full step too large everywhere."""
built = theme.bit_theme()
assert built.body_text_size == "10px"
assert built.block_radius == "0px"
def test_bridge_transport_elements_are_hidden_not_removed():
"""`visible=False` would delete them from the DOM and break the bridge."""
css = theme.full_css()
assert "#bit-action" in css and "#bit-trigger" in css
assert "clip:rect(0,0,0,0)" in css.replace(" ", "")
# --------------------------------------------------------------------------
# Escaping -- model cards are written by strangers
# --------------------------------------------------------------------------
XSS = '<img src=x onerror="alert(1)">'
def test_red_flags_are_escaped_in_badges():
row = make_row("evil/model", red_flags=[XSS])
html = shell._badges(row)
assert "<img" not in html
assert "&lt;img" in html
def test_model_id_is_escaped_everywhere_it_appears(index):
import pandas as pd
nasty = make_row('evil/"><script>alert(1)</script>', red_flags=[XSS],
training_data_summary=XSS)
built = atlas.Index(pd.DataFrame([nasty]))
built.dataset_repo = "x/y"
state = atlas.default_state()
view = {"rows": built.rows, "hidden": 0, "tape": [], "trending": [],
"links": {}}
html = shell.page(built, state, view)
assert "<script>alert(1)</script>" not in html
assert "<img src=x" not in html
assert "&lt;script&gt;" in html
def test_drawer_escapes_summary_and_flags():
import pandas as pd
row = make_row("a/b", training_data_summary=XSS, red_flags=[XSS])
built = atlas.Index(pd.DataFrame([row]))
built.dataset_repo = "x/y"
html = shell.drawer(built.by_id["a/b"], built)
assert "<img" not in html
assert "&lt;img" in html
# --------------------------------------------------------------------------
# The bridge
# --------------------------------------------------------------------------
def test_emit_rejects_unknown_actions():
with pytest.raises(ValueError):
chrome.emit("definitely-not-an-action", "x")
def test_emit_rejects_values_containing_the_nonce_separator():
with pytest.raises(ValueError):
chrome.emit("sort", "a|b")
@pytest.mark.parametrize("raw", [
"", None, "no-colon", "notakey:value|n", "|||", 42,
])
def test_parse_action_drops_anything_unparseable(raw):
assert chrome.parse_action(raw) is None
def test_parse_action_strips_the_nonce():
action = chrome.parse_action("sort:Recently updated|abc123")
assert action.key == "sort"
assert action.value == "Recently updated"
def test_every_emitted_key_is_on_the_allow_list(index):
"""Whatever the shell renders must be something the bridge accepts."""
import re
state = atlas.default_state()
view = {"rows": index.rows, "hidden": 1, "tape": [], "trending": [],
"links": {}}
html = shell.page(index, state, view)
emitted = set(re.findall(r'data-bit="([^:"]+):', html))
assert emitted, "no actions rendered at all"
unknown = emitted - chrome.ALLOWED_KEYS
assert not unknown, f"shell emits actions the bridge rejects: {unknown}"
# --------------------------------------------------------------------------
# Icons
# --------------------------------------------------------------------------
def test_every_icon_the_nav_uses_is_vendored():
"""The nav is shared config now, so this guards bit-ui's default too."""
from bit_ui import nav as bit_nav
needed = {item["icon"] for group in bit_nav.default_document()["groups"]
for item in group["items"]}
missing = needed - set(icons.PATHS)
assert not missing, f"missing icons: {missing}"
def test_unknown_icon_renders_nothing_rather_than_raising():
assert icons.icon("no-such-icon") == ""
# --------------------------------------------------------------------------
# Honest rendering
# --------------------------------------------------------------------------
def test_no_sparkline_without_two_snapshots():
"""A trend we cannot know is an em dash, never an invented flat line."""
assert shell.sparkline(None) == ""
assert shell.sparkline([100]) == ""
assert shell.sparkline([100, 120]) != ""
def test_row_renders_a_dash_when_there_is_no_trend(index):
row = index.by_id["ProsusAI/finbert"]
assert index.trends == {}, "fixture should have no snapshots"
html = shell._table_row(row, index, selected=False)
assert "—" in html
assert "<path" not in html, "drew a sparkline with no data behind it"
def test_empty_index_says_so_rather_than_rendering_zeros():
import pandas as pd
built = atlas.Index(pd.DataFrame(columns=list(atlas.EMPTY_COLUMNS)))
built.dataset_repo = "x/y"
state = atlas.default_state()
view = {"rows": [], "hidden": 0, "tape": [], "trending": [], "links": {}}
html = shell.page(built, state, view)
assert "has not been built yet" in html
def test_trending_panel_admits_when_it_has_no_data(index):
html = shell.trending_panel(index, [])
assert "NO TREND YET" in html
# --------------------------------------------------------------------------
# The Backtest Lab hand-off
# --------------------------------------------------------------------------
@pytest.mark.parametrize("task,family,expected", [
("forecasting", "chronos", True),
("forecasting", "timesfm", True),
("forecasting", "", False), # forecasting, but not loadable there
("forecasting", "lstm", False),
("sentiment", "chronos", False), # loadable family, wrong task
("trading_signal", "", False),
])
def test_backtest_link_is_gated_on_task_and_adapter_family(task, family, expected):
"""Narrower than the design on purpose -- see the docstring in shell."""
taxonomy = atlas.FALLBACK_TAXONOMY
row = make_row("a/b", task=task, adapter_family=family)
assert shell.backtestable(row, taxonomy) is expected
def test_backtest_cta_only_renders_when_gated_in(index):
chronos = index.by_id["amazon/chronos-t5-small"]
finbert = index.by_id["ProsusAI/finbert"]
assert "Backtest this model" in shell.drawer(chronos, index)
assert "Backtest this model" not in shell.drawer(finbert, index)
# --------------------------------------------------------------------------
# No silent caps
# --------------------------------------------------------------------------
def test_counts_report_matches_not_the_rendered_slice(index):
"""A capped render must not understate how many models matched.
Reporting the slice size as the match count would tell the user their
filter is narrower than it is -- the most quietly misleading kind of bug.
"""
rows = index.rows[:2] # pretend the render was capped
html = shell.model_table(index, atlas.default_state(), rows, hidden_count=0,
matched=800, truncated=798)
assert "800 MATCHING" in html
assert "SHOWING FIRST 2 OF 800 MATCHING" in html
def test_uncapped_render_uses_the_designs_wording(index):
html = shell.model_table(index, atlas.default_state(), index.rows,
hidden_count=0, matched=len(index.rows), truncated=0)
assert "MAINTAINED" in html and "INDEXED" in html
assert "SHOWING FIRST" not in html
def test_build_view_carries_the_true_match_count(index, monkeypatch):
import app as module
monkeypatch.setattr(module, "INDEX", index)
monkeypatch.setattr(module, "MAX_ROWS", 2)
state = dict(atlas.default_state(), maintained_only=False)
view = module.build_view(index, state)
assert len(view["rows"]) == 2
assert view["matched"] == 5
assert view["truncated"] == 3
def test_header_result_count_reflects_matches_not_the_cap(index, monkeypatch):
import app as module
monkeypatch.setattr(module, "INDEX", index)
monkeypatch.setattr(module, "MAX_ROWS", 2)
state = dict(atlas.default_state(), maintained_only=False)
html = shell.page(index, state, module.build_view(index, state))
assert "5 / 5" in html, "the header count showed the cap, not the matches"
# --------------------------------------------------------------------------
# The drawer
# --------------------------------------------------------------------------
def test_clicking_inside_the_drawer_does_not_close_it(index):
"""The backdrop closes on click; the panel must not.
The bridge resolves a click with `closest('[data-bit]')`, so without a
marker on the panel every click inside it would walk up to the backdrop's
`close:` and dismiss the drawer -- including clicks on its own text.
A `noop:` marker stops the walk, and the bridge returns early on it.
"""
import re
html = shell.drawer(index.by_id["ProsusAI/finbert"], index)
backdrop, panel = html.split('role="dialog"', 1)
assert 'data-bit="close:"' in backdrop, "backdrop should close on click"
assert 'data-bit="noop:"' in panel[:400], (
"the drawer panel needs a noop marker or clicks inside it close it")
# The close button is nearer than the panel, so it still wins.
assert 'data-bit="close:"' in panel
def test_drawer_links_out_to_the_model_and_escapes_the_id(index):
html = shell.drawer(index.by_id["ProsusAI/finbert"], index)
assert "https://huggingface.co/ProsusAI/finbert" in html
assert 'rel="noopener noreferrer"' in html
def test_drawer_names_the_verification_state(index):
verified = shell.drawer(index.by_id["ProsusAI/finbert"], index)
assert "VERIFIED BY BIT TRADING" in verified
auto = shell.drawer(index.by_id["amazon/chronos-t5-small"], index)
assert "AUTO-INDEXED" in auto
def test_drawer_says_when_training_data_is_undocumented(index):
html = shell.drawer(index.by_id["fx-research/forex-lstm-eurusd"], index)
assert "does not describe the training data" in html
assert "undocumented" in html
# --------------------------------------------------------------------------
# Shared chrome (bit-ui)
# --------------------------------------------------------------------------
def test_page_has_no_forced_vh_anywhere(index):
"""The infinite-scroll guard, applied to the whole rendered page.
Inside HF's <iframe scrolling="no">, an element that forces height from vh
feeds back into the height the parent sets, and ratchets. This is the check
that keeps it fixed.
"""
from bit_ui import nav as bit_nav
state = dict(atlas.default_state(), sel="ProsusAI/finbert")
view = {"rows": index.rows, "matched": len(index.rows), "truncated": 0,
"hidden": 1, "tape": [], "trending": [], "links": {},
"nav": bit_nav.default_document(), "palette_groups": []}
assert theme.find_forced_vh(shell.page(index, state, view)) == []
def test_gradio_own_main_padding_is_reset():
"""Gradio ships a second <main> with padding:16px 32px around ours."""
css = chrome.full_css()
assert "main.fillable" in css
def test_sidebar_toggle_costs_no_round_trip(index):
"""Collapsing is a client-side attribute flip, not a server action.
Every action re-renders the whole page: 1.16 MB here, 95% of it the model
table. Putting that behind a purely visual toggle measured at 1.5-2.2s.
"""
from bit_ui import nav as bit_nav
view = {"rows": [], "matched": 0, "truncated": 0, "hidden": 0, "tape": [],
"trending": [], "links": {}, "nav": bit_nav.default_document(),
"palette_groups": []}
html = shell.page(index, atlas.default_state(), view)
assert "data-bit-sidebar-toggle" in html
assert "sidebar:toggle" not in html
assert "sidebar" not in chrome.ALLOWED_KEYS, (
"the sidebar action is back on the allow-list")
def test_header_and_sidebar_are_sticky(index):
from bit_ui import nav as bit_nav
css = chrome.full_css()
assert ".bit-header" in css and "position: sticky" in css
view = {"rows": [], "matched": 0, "truncated": 0, "hidden": 0, "tape": [],
"trending": [], "links": {}, "nav": bit_nav.default_document(),
"palette_groups": []}
assert 'class="bit-header"' in shell.page(index, atlas.default_state(), view)
def test_search_bar_is_live_not_enter_only(index):
"""The bug: fields sent `key=value`, which parse_action drops on the floor,
so the search box did nothing at all from the browser."""
html = shell.title_row(index, atlas.default_state(), shown=5)
assert 'data-bit-live="q"' in html
assert "⌘K" in html
def test_every_chrome_action_is_on_the_allow_list(index):
"""The sidebar and dialogs emit through this Space's emitter."""
import re
from bit_ui import dialogs as bit_dialogs
from bit_ui import nav as bit_nav
doc = bit_nav.default_document()
html = "".join([
shell.page(index, atlas.default_state(),
{"rows": [], "matched": 0, "truncated": 0, "hidden": 0,
"tape": [], "trending": [], "links": {}, "nav": doc,
"palette_groups": []}),
bit_dialogs.contact(doc, {"contact_form_open": True}, chrome.emit),
bit_dialogs.coming_soon(bit_nav.find(doc, "Dispatch"), doc, chrome.emit),
])
emitted = set(re.findall(r'data-bit="([^:"]+):', html))
emitted |= set(re.findall(r'data-bit-(?:input|live)="([^="]+)"', html))
unknown = emitted - chrome.ALLOWED_KEYS
assert not unknown, f"chrome emits actions the bridge rejects: {unknown}"
def test_the_app_serves_this_spaces_own_css_not_just_the_shared_css():
"""`theme.full_css()` is bit-ui's CSS only and silently drops the Atlas's.
This shipped once: the table had just been converted to CSS classes whose
rules live in ATLAS_CSS, and none of them reached the page. Everything
still rendered, just unstyled -- exactly the kind of break that looks like
a design regression rather than a wiring mistake.
"""
import app as module
css = module.chrome.full_css()
for marker in (".bit-row", ".bit-header", ".bit-cell", ".bit-tag"):
assert marker in css, f"{marker} is missing from the app's CSS"
source = (Path(module.__file__).read_text())
assert "css=chrome.full_css()" in source, (
"app.py is not serving this Space's CSS")
assert "css=theme.full_css()" not in source
def test_every_class_the_table_renders_has_a_rule():
"""A class with no rule is invisible styling debt."""
import re
import app as module
from bit_ui import nav as bit_nav
index = module.INDEX
css = module.chrome.full_css()
state = atlas.default_state()
view = {"rows": index.rows[:3], "matched": 3, "truncated": 0, "hidden": 0,
"tape": [], "trending": [], "links": {},
"nav": bit_nav.default_document(), "palette_groups": []}
html = shell.page(index, state, view)
rendered = set()
for attr in re.findall(r'class="([^"]+)"', html):
rendered.update(c for c in attr.split() if c.startswith("bit-"))
missing = sorted(c for c in rendered if f".{c}" not in css)
assert not missing, f"classes rendered with no CSS rule: {missing}"