Spaces:
Running
Running
| """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. | |
| 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 "<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 "<script>" 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 "<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") | |
| 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 | |
| # -------------------------------------------------------------------------- | |
| 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}" | |