Spaces:
Running on Zero
Running on Zero
File size: 28,088 Bytes
46f1a78 27c0524 46f1a78 27c0524 46f1a78 27c0524 46f1a78 27c0524 46f1a78 27c0524 46f1a78 27c0524 46f1a78 27c0524 46f1a78 27c0524 46f1a78 27c0524 46f1a78 27c0524 46f1a78 c201e66 46f1a78 bc0c291 05562d4 bc0c291 98d7a3e c201e66 3f2deae c201e66 f0ac64a b656fb9 f0ac64a b656fb9 f0ac64a b656fb9 f0ac64a c425891 | 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 279 280 281 282 283 284 285 286 287 288 289 290 291 292 293 294 295 296 297 298 299 300 301 302 303 304 305 306 307 308 309 310 311 312 313 314 315 316 317 318 319 320 321 322 323 324 325 326 327 328 329 330 331 332 333 334 335 336 337 338 339 340 341 342 343 344 345 346 347 348 349 350 351 352 353 354 355 356 357 358 359 360 361 362 363 364 365 366 367 368 369 370 371 372 373 374 375 376 377 378 379 380 381 382 383 384 385 386 387 388 389 390 391 392 393 394 395 396 397 398 399 400 401 402 403 404 405 406 407 408 409 410 411 412 413 414 415 416 417 418 419 420 421 422 423 424 425 426 427 428 429 430 431 432 433 434 435 436 437 438 439 440 441 442 443 444 445 446 447 448 449 450 451 452 453 454 455 456 457 458 459 460 461 462 463 464 465 466 467 468 469 470 471 472 473 474 475 476 477 478 479 480 481 482 483 484 485 486 487 488 489 490 491 492 493 494 495 496 497 498 499 500 501 502 503 504 505 506 507 508 509 510 511 512 513 514 515 516 517 518 519 520 521 522 523 524 525 526 527 528 529 530 531 532 533 534 535 536 537 538 539 540 541 542 543 544 545 546 547 548 549 550 551 552 553 554 555 556 557 558 559 560 561 562 563 564 565 566 567 568 569 570 571 572 573 574 575 576 577 578 579 580 581 582 583 584 585 586 587 588 589 590 591 592 593 594 595 596 597 598 599 600 601 602 603 604 605 606 607 608 609 610 611 612 613 614 615 616 617 618 619 620 621 622 623 624 625 626 627 628 629 630 631 632 633 634 635 636 637 638 639 640 641 642 643 644 645 646 647 648 649 650 651 652 653 654 655 656 657 658 659 660 661 662 663 664 665 666 667 668 669 670 671 672 673 674 675 676 677 678 679 680 681 682 683 684 685 686 687 688 689 690 691 692 693 694 695 696 697 698 699 700 701 702 703 704 705 706 707 708 709 710 711 712 713 714 715 716 717 718 719 720 721 722 723 724 725 726 727 728 729 730 731 732 733 734 735 736 737 738 739 740 741 742 743 744 745 746 747 748 749 750 751 752 753 754 755 756 757 758 759 760 761 762 763 764 765 766 767 768 769 770 771 772 773 774 775 776 777 778 779 780 | """Phase 3 acceptance: the UI renders, and every displayed number is traceable.
These run against the real cached store when it is present, and skip cleanly
when it is not (a fresh clone with no `.cache/store` yet).
"""
from __future__ import annotations
import re
import sys
from pathlib import Path
import pandas as pd
import plotly.graph_objects as go
import pytest
sys.path.insert(0, str(Path(__file__).resolve().parent.parent))
import app as bitapp
from src import charts, comparisons, config, runtime, strategies
from src.ui import components as C
from src.ui.format import money, num, pct
from src.runtime import RunRequest
def _store_ready() -> bool:
try:
m = runtime.get_store().load_manifest()
return len(m.prices) > 0
except Exception:
return False
pytestmark = pytest.mark.skipif(not _store_ready(),
reason="no cached signal store available")
@pytest.fixture(scope="module")
def rec():
return runtime.execute(RunRequest(
strategy="SMA Crossover", asset="BTC-USD", timeframe="1d", date_range="3Y",
))
@pytest.fixture(scope="module")
def three_runs():
reqs = [
RunRequest(strategy="SMA Crossover", asset="BTC-USD", timeframe="1d", date_range="3Y"),
RunRequest(strategy="RSI Mean Reversion", asset="ETH-USD", timeframe="1d", date_range="3Y"),
RunRequest(strategy="Buy & Hold (benchmark)", asset="SPY", timeframe="1d", date_range="3Y"),
]
return [runtime.execute(r) for r in reqs]
# --------------------------------------------------------------------------
# App construction
# --------------------------------------------------------------------------
def test_app_object_exists():
assert bitapp.demo is not None
def test_theme_css_carries_the_design_tokens():
from src.ui import theme
css = theme.full_css()
for token in ("--bg-canvas", "--accent-amber", "--fin-up", "--font-styrene"):
assert token in css, f"{token} missing from theme CSS"
assert "#161512" in css # stone-950 canvas
assert "#af9209" in css # accent amber
def test_disclaimer_is_present_in_the_footer():
assert "not indicative of future results" in C.footer()
assert "Not financial advice" in C.footer() or \
"not a licensed investment adviser" in C.footer()
def test_empty_state_offers_the_worked_example():
assert "No run loaded" in C.empty_state()
assert "worked example" in C.empty_state()
def test_glossary_covers_every_design_term():
terms = {t for t, _ in bitapp.GLOSSARY}
assert {"SHARPE", "SORTINO", "MAX DRAWDOWN", "PROFIT FACTOR",
"R-MULTIPLE", "MAE / MFE", "WALK-FORWARD", "OOS"} <= terms
# --------------------------------------------------------------------------
# The stat band must match engine output exactly
# --------------------------------------------------------------------------
def test_stat_band_values_match_engine_metrics(rec):
html = C.stat_band(rec)
m = rec.result.metrics_all
assert pct(m.total_return) in html
assert pct(m.cagr) in html
assert num(m.sharpe) in html
assert num(m.sortino) in html
assert pct(m.max_drawdown) in html
assert num(m.profit_factor) in html
assert f">{m.trade_count}<" in html
def test_stat_band_shows_is_and_oos_for_every_stat(rec):
html = C.stat_band(rec)
assert html.count("IS ") >= 9
assert html.count("· OOS") >= 9
assert num(rec.result.metrics_oos.sharpe) in html
assert num(rec.result.metrics_is.sharpe) in html
def test_stat_band_reports_costs_actually_paid(rec):
html = C.stat_band(rec)
assert money(rec.result.costs_paid) in html
assert rec.result.costs_paid > 0, "costs default to ON, so this must be positive"
def test_empty_segment_renders_an_em_dash_not_a_zero():
"""A segment with no bars must not read as 0.00."""
short = runtime.execute(RunRequest(
strategy="SMA Crossover", asset="BTC-USD", timeframe="1h", date_range="1Y"))
if short.result.metrics_oos.bars:
pytest.skip("this range did produce OOS windows")
html = C.stat_band(short)
assert "· OOS —" in html
assert "no out-of-sample windows" in html
def test_trade_table_rows_match_the_engine_trade_list(rec):
df = bitapp.trades_frame(rec)
assert len(df) == len(rec.result.trades)
if len(df):
assert df["Net"].iloc[0] == pytest.approx(
round(float(rec.result.trades["net_pnl"].iloc[0]), 2))
assert set(df["Segment"]) <= {"IS", "OOS", "holdout"}
assert (df["Costs"] >= 0).all()
def test_report_quotes_the_same_numbers_as_the_stat_band(rec):
md = bitapp.report_markdown(rec)
assert pct(rec.result.metrics_all.total_return) in md
assert money(rec.result.costs_paid) in md
assert rec.run_id in md
def test_costs_off_is_called_out_as_not_real():
off = runtime.execute(RunRequest(
strategy="SMA Crossover", asset="BTC-USD", timeframe="1d",
date_range="3Y", costs_on=False))
_, _, _, _, _, _, _, _, note = bitapp.build_overview(off, log_scale=False, cvd=False)
assert "COSTS ARE OFF" in note
assert off.result.costs_paid == 0.0
# --------------------------------------------------------------------------
# Charts
# --------------------------------------------------------------------------
def test_overview_builds_every_figure(rec):
figs = bitapp.build_overview(rec, log_scale=False, cvd=False)
assert len(figs) == 9
for f in figs[:8]:
assert isinstance(f, go.Figure)
assert isinstance(figs[8], str)
def test_log_scale_and_colorblind_variants_render(rec):
for log_s in (False, True):
for cb in (False, True):
figs = bitapp.build_overview(rec, log_scale=log_s, cvd=cb)
assert isinstance(figs[0], go.Figure)
def test_equity_chart_marks_the_holdout_band(rec):
fig = charts.equity_curve(rec.result.equity, rec.result.benchmark_equity,
plan=rec.result.plan)
if rec.result.plan.holdout_start is not None:
texts = [str(a.text) for a in fig.layout.annotations]
assert any("HOLDOUT" in t for t in texts)
def test_charts_survive_empty_inputs():
empty = pd.Series(dtype="float64")
assert isinstance(charts.equity_curve(empty), go.Figure)
assert isinstance(charts.underwater_chart(empty), go.Figure)
assert isinstance(charts.pnl_histogram(pd.DataFrame()), go.Figure)
assert isinstance(charts.mae_mfe_scatter(pd.DataFrame()), go.Figure)
assert isinstance(charts.walk_forward_bars([]), go.Figure)
assert isinstance(charts.monte_carlo_cone(None), go.Figure)
def test_colorblind_palette_differs_from_default():
assert charts.up_color(cvd=True) != charts.up_color(cvd=False)
assert charts.down_color(cvd=True) != charts.down_color(cvd=False)
# --------------------------------------------------------------------------
# Comparison tab with three runs
# --------------------------------------------------------------------------
def test_comparison_renders_three_runs(three_runs):
curves = {r.label[:28]: r.result.equity for r in three_runs}
rets = {r.label[:28]: r.result.equity.pct_change().dropna() for r in three_runs}
assert len(curves) == 3
assert isinstance(charts.overlaid_returns(curves), go.Figure)
assert isinstance(charts.small_multiples(curves), go.Figure)
corr = charts.correlation_matrix(rets)
assert isinstance(corr, go.Figure)
assert len(corr.data[0].z) == 3
def test_return_overlay_caps_the_series_it_will_draw():
"""The old six-run session picker was replaced by the catalog view; the
readability cap now lives in the chart itself."""
import numpy as np
idx = pd.date_range("2024-01-01", periods=30, freq="D", tz="UTC")
curves = {f"s{i}": pd.Series(np.linspace(0, 1, 30), index=idx) for i in range(50)}
assert len(charts.multi_return_overlay(curves, max_series=6).data) == 6
def test_precomputed_heatmap_loads_from_the_store():
heat = comparisons.load_table(runtime.get_store(), comparisons.HEATMAP)
if heat.empty:
pytest.skip("comparison tables not generated yet")
assert {"asset", "strategy", "timeframe", "oos_sharpe"} <= set(heat.columns)
assert isinstance(charts.strategy_timeframe_heatmap(heat), go.Figure)
def test_regime_breakdown_covers_the_named_regimes(rec):
df = runtime.regime_breakdown(rec)
if df.empty:
pytest.skip("no regime variation in this window")
assert set(df["regime"]) <= {"BULL", "BEAR", "CHOP"}
# --------------------------------------------------------------------------
# Coverage map & share links
# --------------------------------------------------------------------------
def test_coverage_map_renders_from_the_live_manifest():
df = runtime.coverage_frame()
assert not df.empty
assert {"Model", "Asset", "TF", "Coverage", "Rows", "Real?"} <= set(df.columns)
def test_placeholder_slices_are_labelled_in_the_coverage_map():
df = runtime.coverage_frame()
assert set(df["Real?"]) <= {"real", "PLACEHOLDER"}
def test_share_link_round_trips():
req = RunRequest(strategy="RSI Mean Reversion", asset="ETH-USD",
timeframe="1h", date_range="1Y", params={"rsi_period": 21})
again = RunRequest.decode(req.encode())
assert again.strategy == req.strategy
assert again.asset == req.asset
assert again.params["rsi_period"] == 21
@pytest.mark.parametrize("payload", [
'{"strategy":"__import__(\'os\').system","asset":"BTC-USD"}',
'{"strategy":"SMA Crossover","asset":"../../etc/passwd"}',
'{"strategy":"SMA Crossover","asset":"BTC-USD","timeframe":"99y"}',
'{"strategy":"SMA Crossover","asset":"BTC-USD","validation_mode":"eval"}',
])
def test_hostile_share_links_are_rejected(payload):
import base64
token = base64.urlsafe_b64encode(payload.encode()).decode().rstrip("=")
with pytest.raises(ValueError):
RunRequest.decode(token)
def test_unavailable_presets_are_listed_but_refuse_to_run():
assert "Custom (code)" in strategies.PRESETS
assert not strategies.PRESETS["Custom (code)"].available
with pytest.raises(runtime.RunError, match="never executes untrusted code"):
runtime.execute(RunRequest(strategy="Custom (code)", asset="BTC-USD"))
# --------------------------------------------------------------------------
# Performance budget
# --------------------------------------------------------------------------
def test_default_three_year_daily_run_is_under_two_seconds():
runtime.cache_clear()
req = RunRequest(strategy="SMA Crossover", asset="BTC-USD",
timeframe="1d", date_range="3Y")
runtime.execute(req) # warm the slice cache
import time
t0 = time.perf_counter()
runtime.execute(req)
assert time.perf_counter() - t0 < 2.0
# --------------------------------------------------------------------------
# Design-system fidelity
#
# The original bug this guards: base.css was vendored into assets/ but never
# loaded, so the app silently inherited Gradio's rounded, roomy defaults and
# only approximated the design.
# --------------------------------------------------------------------------
def test_every_global_stylesheet_the_design_system_declares_is_loaded():
"""The DS manifest lists six globalCssPaths; all of them must reach the page."""
from src.ui import theme
css = theme.full_css()
# colors / typography / spacing / base, each identified by a rule only it has
markers = {
"colors.css": "--accent-amber:#af9209",
"typography.css": "--text-2xs:8px",
"spacing.css": "--space-4:16px",
"base.css": "border-radius:0 !important",
}
for name, marker in markers.items():
assert marker in css, f"{name} is not loaded into the page"
def test_base_css_square_corner_reset_is_present():
from src.ui import theme
assert "border-radius:0 !important" in theme.full_css()
# The app's own shell must not reintroduce a rounded default on top of the
# reset. base.css legitimately defines opt-in `.bit-rounded*` utilities, so
# only our shell layer is checked here.
assert "border-radius" not in theme.SHELL_CSS
assert "border-radius" not in theme.GRADIO_RESET
def test_base_css_scrollbar_and_selection_rules_survive():
from src.ui import theme
css = theme.full_css()
assert "::-webkit-scrollbar" in css
assert "::selection" in css
def test_data_surfaces_stay_selectable_despite_base_css():
"""base.css sets user-select:none globally; tables must opt back in."""
from src.ui import theme
css = theme.full_css()
assert "user-select: text" in css
assert ".bit-table" in css.split("user-select: text")[0][-400:] or \
"table, table *" in css
def test_gradio_layout_spacing_is_configured_not_left_default():
"""Gradio defaults to layout_gap *spacing_xxl and block_padding *spacing_xl."""
from src.ui import theme
t = theme.bit_theme()
assert t.layout_gap == "8px"
assert t.block_padding == "0px"
assert t.form_gap_width == "0px"
def test_gradio_radius_is_squared_off():
from src.ui import theme
t = theme.bit_theme()
for attr in ("block_radius", "input_radius", "button_large_radius",
"button_small_radius", "container_radius"):
assert getattr(t, attr) == "0px", f"{attr} is not square"
def test_type_scale_matches_the_designs_dominant_sizes():
"""The design's workhorse is --text-xs (10px) with --text-2xs (8px) micro."""
from src.ui import theme
t = theme.bit_theme()
assert t.body_text_size == "10px"
assert t.block_label_text_size == "8px"
def test_no_hardcoded_pixel_font_sizes_outside_the_theme():
"""Component markup must size through tokens, never literal px."""
import re
from pathlib import Path
root = Path(__file__).resolve().parent.parent
for rel in ("src/ui/components.py", "src/ui/compare_tab.py", "app.py"):
text = (root / rel).read_text()
assert not re.search(r"font-size:\s*\d", text), \
f"{rel} hardcodes a pixel font-size instead of using a token"
def test_every_bit_class_used_in_markup_has_a_css_rule():
"""A class with no rule renders unstyled and silently breaks the design."""
import re
from pathlib import Path
from src.ui import theme
root = Path(__file__).resolve().parent.parent
used = set()
for rel in ("src/ui/components.py", "src/ui/compare_tab.py", "app.py"):
text = (root / rel).read_text()
# Only real class attributes -- a bare `bit-…` token can appear in a
# comment or an asset filename and is not a class the page uses.
for attr in (re.findall(r'class="([^"]*)"', text)
+ re.findall(r"elem_classes=[\"']([^\"']+)", text)):
for cls in attr.split():
if not cls.startswith("bit-"):
continue
# Class names are built in f-strings, e.g. `bit-chip{cls}` or
# `bit-podium-{rank}`. Keep the static prefix; a name that is
# only a prefix (ends in `-`) has variant rules instead.
static = cls.split("{", 1)[0]
if static and not static.endswith("-"):
used.add(static)
defined = set(re.findall(r"\.(bit-[a-z0-9-]+)", theme.full_css()))
missing = sorted(c for c in used if c not in defined and not c.endswith("-"))
assert not missing, f"classes used in markup but never styled: {missing}"
def test_fonts_are_served_for_every_weight_the_design_uses():
from src.ui import theme
css = theme.full_css()
assert css.count("@font-face") >= 5
assert "Styrene A" in css and "Mac Minecraft" in css
def test_app_constructs_without_any_hugging_face_credentials(monkeypatch):
"""Reading and backtesting are open to anyone, so the app must build with
no token at all. Gradio's off-Space OAuth mock calls whoami and raises
without one, which previously made the whole app unconstructable."""
import importlib
for var in ("HF_TOKEN", "HF_WRITE_TOKEN", "HUGGING_FACE_HUB_TOKEN", "SPACE_ID"):
monkeypatch.delenv(var, raising=False)
import app as fresh
importlib.reload(fresh)
assert fresh.demo is not None
# --------------------------------------------------------------------------
# Design-markup rendering (shell) and the click bridge
# --------------------------------------------------------------------------
def test_left_panel_is_design_markup_not_gradio_components():
"""Every control in the left panel must be a real button or input."""
from src.ui import shell
st = bitapp.default_state()
html = bitapp.render_left(st)
assert 'width:286px' in html, "zone width is not the design's 286px"
assert html.count("<button") >= 12, "controls are not real buttons"
assert "data-bit=" in html, "buttons are not wired to the bridge"
# Gradio's own control DOM must not appear here.
assert "<fieldset" not in html
assert 'type="radio"' not in html
def test_left_panel_uses_the_designs_exact_spacing():
html = bitapp.render_left(bitapp.default_state())
for value in ("padding:9px 12px", # panel header
"padding:0 12px 12px", # section body
"gap:10px", # section body gap
"padding:3px 7px", # chips
"padding:8px 12px"): # accordion header
assert value in html, f"design spacing {value!r} missing"
def test_top_bar_uses_the_real_mark_and_design_header_treatment():
from src.ui import shell
html = shell.top_bar(context="BTC-USD", status="RUN COMPLETE", tone="ok")
assert "M50 10H90V90H50Z" in html, "not the design's mark"
assert "padding:8px 12px" in html
assert "margin-top:3px" in html, "optical alignment on the title is missing"
assert "mono-data" in html, "design-system utility class not used"
def test_selected_chip_is_the_only_active_one():
st = bitapp.default_state()
st["timeframe"] = "1h"
html = bitapp.render_left(st)
actives = re.findall(
r'<button data-bit="(tf:[^"]+)"[^>]*background:var\(--accent-amber\)', html)
assert actives == ["tf:1h"]
def test_html_entities_are_escaped_exactly_once():
html = bitapp.render_left(bitapp.default_state())
assert "&amp;" not in html, "double-escaped entity"
assert "Universe & Data" in html
@pytest.mark.parametrize("action,key,expected", [
("tf:15m", "timeframe", "15m"),
("asset:ETH-USD", "asset", "ETH-USD"),
("range:1Y", "range", "1Y"),
("validation:holdout", "validation_mode", "holdout"),
("sizing:vol_target", "sizing_mode", "vol_target"),
("slippage:volume_scaled", "slippage_model", "volume_scaled"),
])
def test_actions_fold_into_state(action, key, expected):
st, ran = bitapp.apply_action(bitapp.default_state(), f"{action}|nonce")
assert st[key] == expected
assert ran is False
def test_run_and_example_actions_request_a_run():
_, ran = bitapp.apply_action(bitapp.default_state(), "run:|n")
assert ran is True
st, ran = bitapp.apply_action(bitapp.default_state(), "example:|n")
assert ran is True and st["strategy"] == "Chronos Forecast Follower"
def test_accordion_action_toggles():
st = bitapp.default_state()
before = st["acc"]["costs"]
st, _ = bitapp.apply_action(st, "acc:costs|n")
assert st["acc"]["costs"] is not before
def test_numeric_param_commits_through_the_bridge():
st, _ = bitapp.apply_action(bitapp.default_state(), "param:commission_bps=25|n")
assert st["commission_bps"] == 25.0
def test_strategy_param_commits_and_survives_preset_defaults():
st = bitapp.default_state()
st, _ = bitapp.apply_action(st, "param:fast_ma=33|n")
assert st["params"]["fast_ma"] == 33.0
@pytest.mark.parametrize("bad", [
"param:commission_bps=nonsense", "param:commission_bps=", "topn:abc",
"tf:99y", "asset:../etc/passwd", "strategy:__import__",
])
def test_malformed_actions_leave_state_untouched(bad):
before = bitapp.default_state()
after, ran = bitapp.apply_action(bitapp.default_state(), f"{bad}|n")
assert ran is False
assert after["timeframe"] == before["timeframe"]
assert after["asset"] == before["asset"]
assert after["strategy"] == before["strategy"]
assert after["commission_bps"] == before["commission_bps"]
def test_unknown_action_keys_are_dropped():
from src.ui.bridge import parse_action
assert parse_action("evil:rm -rf|n") is None
assert parse_action("os.system:x|n") is None
def test_bridge_survives_values_containing_spaces():
"""Preset names contain spaces; the nonce separator must not collide."""
from src.ui.bridge import emit, parse_action
raw = emit("strategy", "Buy & Hold (benchmark)")
assert parse_action(raw + "|nonce").value == "Buy & Hold (benchmark)"
def test_tables_render_as_markup_not_dataframes():
df = pd.DataFrame({"A": [1, 2], "B": ["x", "y"]})
html = bitapp.render_table(df)
assert "<table" in html and "pixel-text" in html
assert "bit-selectable" in html, "table text must stay selectable"
def test_empty_table_says_so():
assert "no rows" in bitapp.render_table(pd.DataFrame(), empty="no rows")
def test_zone_widths_are_pinned_in_css():
from src.ui import theme
css = theme.full_css()
assert "flex:0 0 286px" in css, "left aside is not pinned to the design width"
# The right tray was removed; its rules should not linger.
assert "bit-zone-right" not in css
def test_bridge_target_is_hidden_but_present():
from src.ui import theme
css = theme.full_css()
assert "#bit-action" in css
assert "opacity:0" in css.split("#bit-action")[1][:220]
def test_gradio_content_wrapper_is_stretched_full_bleed():
"""Gradio centres the app in a padded `.contain`, which cost 208px of a
1680px viewport. The design is a full-bleed dashboard."""
from src.ui import theme
import re
css = theme.full_css()
# Match the rule whose selector list starts with `.contain`, not the
# `.container` rule -- one is a prefix of the other.
m = re.search(r"\.gradio-container \.contain,(.*?)\{(.*?)\}", css, re.S)
assert m, "no full-bleed rule for Gradio's .contain wrapper"
body = m.group(2)
assert "max-width:100% !important" in body
assert "padding:0 !important" in body
# --------------------------------------------------------------------------
# Regressions from the header/nav restructure
# --------------------------------------------------------------------------
def test_header_carries_nav_login_and_glossary():
from src.ui import shell
html = shell.top_bar(tab="backtest", glossary=bitapp.GLOSSARY, on_space=True)
assert 'data-bit="tab:compare"' in html
assert 'data-bit="tab:backtest"' in html
assert "bit-help" in html, "glossary tooltip missing from the header"
def test_signed_in_header_shows_the_handle():
from src.ui import shell
assert "@alice" in shell.top_bar(user="alice", on_space=True)
def test_sign_in_uses_a_real_login_button_not_a_handrolled_link():
"""Gradio only mounts /login/huggingface when it sees a LoginButton in the
app. A hand-rolled anchor points at a route that returns 404."""
import inspect
from src.ui import shell
src = inspect.getsource(bitapp.build_app)
assert "gr.LoginButton" in src, "no LoginButton, so OAuth routes never mount"
# and the markup must not fake one
assert "/login/huggingface" not in shell.top_bar(on_space=True)
def test_off_space_header_explains_sign_in_is_unavailable():
from src.ui import shell
assert "SIGN IN" in shell.top_bar(on_space=False)
def test_context_chip_only_appears_on_the_backtest_tab():
"""On Compare, a run context chip would describe something not on screen."""
st = bitapp.default_state()
st["tab"] = "compare"
class FakeRec:
run_id = "abcd1234"
elapsed_s = 1.0
# The chip is the only place the asset and timeframe appear together.
# "WALK-FORWARD" alone is ambiguous -- it is also a glossary term.
compare_header = bitapp.render_top(st, FakeRec())
assert "BTC-USD · 1D" not in compare_header
st["tab"] = "backtest"
assert "BTC-USD · 1D" in bitapp.render_top(st, FakeRec())
def test_tab_action_switches_views():
st, _ = bitapp.apply_action(bitapp.default_state(), "tab:backtest|n1")
assert st["tab"] == "backtest"
st, _ = bitapp.apply_action(st, "tab:compare|n2")
assert st["tab"] == "compare"
def test_unknown_tab_value_is_ignored():
st, _ = bitapp.apply_action(bitapp.default_state(), "tab:../admin|n")
assert st["tab"] == "compare"
def test_the_same_click_cannot_run_twice():
"""Two event bindings deliver one click; the nonce must de-duplicate it."""
st = bitapp.default_state()
st, first = bitapp.apply_action(st, "run:|nonce-a")
_, replay = bitapp.apply_action(st, "run:|nonce-a")
assert first is True and replay is False
def test_a_fresh_click_still_runs_after_a_deduped_one():
st = bitapp.default_state()
st, _ = bitapp.apply_action(st, "run:|nonce-a")
_, again = bitapp.apply_action(st, "run:|nonce-b")
assert again is True
def test_running_switches_to_the_backtest_tab():
st = bitapp.default_state()
assert st["tab"] == "compare"
st, should_run = bitapp.apply_action(st, "example:|n")
assert should_run is True
def test_right_sidebar_is_gone():
import inspect
src = inspect.getsource(bitapp.build_app)
assert "bit-zone-right" not in src
assert "right_panel" not in src
def test_bridge_elements_are_rendered_not_visible_false():
"""`visible=False` removes an element from the DOM, which left the click
bridge with nothing to write into."""
import inspect
src = inspect.getsource(bitapp.build_app)
box = src[src.index("action_box = gr.Textbox"):]
assert "visible=True" in box[:260]
trig = src[src.index("action_trigger = gr.Button"):]
assert "visible=True" in trig[:200]
def test_bridge_js_is_loaded_without_outputs():
"""Gradio treats a `js=` return value as the output values, so the bridge
installer must not share a load call that has outputs."""
import inspect
src = inspect.getsource(bitapp.build_app)
assert "demo.load(fn=None, inputs=None, outputs=None, js=BRIDGE_LOAD_JS)" in src
def test_no_oauth_annotated_load_handler():
"""A `demo.load` handler that takes `gr.OAuthProfile` asks for an
authenticated session on every page render. An unauthenticated visitor is
sent to sign in, returns, fires load again, and is sent back -- an infinite
redirect. The profile belongs on user-initiated handlers only."""
import inspect
import re
src = inspect.getsource(bitapp.build_app)
# Strip comments so the explanation of this rule does not trip it.
code = "\n".join(ln for ln in src.splitlines()
if not ln.lstrip().startswith("#"))
assert "OAuthProfile" not in code, \
"an OAuth-annotated handler is registered in build_app"
for call in re.findall(r"demo\.load\((.*?)\)\n", code, re.S):
assert "profile" not in call, f"load handler takes a profile: {call[:80]}"
def test_profile_is_read_only_on_user_initiated_handlers():
"""Where the profile *is* needed, it must hang off a click, not a load."""
import inspect
from src import extension
for fn in (extension.extend_ui, extension.add_model_ui):
params = inspect.signature(fn).parameters
assert "profile" in params, f"{fn.__name__} should receive the profile"
def test_login_button_is_the_only_sign_in_mechanism():
import inspect
from src.ui import shell
src = inspect.getsource(bitapp.build_app)
assert "gr.LoginButton" in src
# The header must not also hand-roll a link to the OAuth route.
assert "/login/huggingface" not in shell.top_bar(on_space=True)
|