File size: 3,729 Bytes
9734b71
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
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
from __future__ import annotations

import unittest
from pathlib import Path


class FrontendStaticContractTests(unittest.TestCase):
    @classmethod
    def setUpClass(cls) -> None:
        cls.app_js = (Path(__file__).resolve().parent.parent / "frontend" / "app.js").read_text(
            encoding="utf-8",
        )
        cls.index_html = (Path(__file__).resolve().parent.parent / "frontend" / "index.html").read_text(
            encoding="utf-8",
        )
        cls.workspace_js = (Path(__file__).resolve().parent.parent / "frontend" / "workspace.js").read_text(
            encoding="utf-8",
        )

    def test_app_js_does_not_use_inline_onclick_handlers(self) -> None:
        self.assertNotIn('onclick="', self.app_js)

    def test_explorer_interactions_use_data_attributes_and_global_compat_exports(self) -> None:
        self.assertIn('data-explorer-cat="', self.app_js)
        self.assertIn('data-symbol="${escapeHtml(s.symbol)}"', self.app_js)
        self.assertIn("window.selectExplorerCat = selectExplorerCat;", self.app_js)
        self.assertIn("window.explorerSelectSymbol = explorerSelectSymbol;", self.app_js)

    def test_market_toggle_dom_ref_is_explicit(self) -> None:
        self.assertIn("const toggleMarketBtn = document.getElementById('toggleMarketBtn');", self.app_js)

    def test_layout_switcher_reconciles_actual_workspace_state_before_noop(self) -> None:
        self.assertIn("function inferWorkspaceLayoutPreset()", self.app_js)
        self.assertIn("const currentPreset = syncWorkspaceLayoutPreset();", self.app_js)
        self.assertIn("const isPresetFullyApplied = (", self.app_js)

    def test_progressive_ai_forecast_contract_is_exposed(self) -> None:
        self.assertIn(
            "window.buildCombinedForecastPayloadForPane = buildCombinedForecastPayloadForPane;",
            self.app_js,
        )
        self.assertIn("async fetchForecastModel(symbol, interval, horizon, modelKey, signal)", self.workspace_js)
        self.assertIn("complete: false", self.workspace_js)

    def test_shared_chart_viewport_contract_is_centralized(self) -> None:
        self.assertIn("const CHART_RIGHT_OFFSET = 50;", self.app_js)
        self.assertIn("function buildSharedTimeScaleOptions(overrides = {})", self.app_js)
        self.assertIn("function getAllOpenChartInstances()", self.app_js)
        self.assertIn("function zoomAllCharts(direction)", self.app_js)
        self.assertIn("function panAllCharts(direction)", self.app_js)
        self.assertIn("function restoreOrFitChartViewport(chartKey, chartInstance, context)", self.app_js)
        self.assertIn("function registerChartViewportTracking(chartInstance, chartKey, getContext)", self.app_js)
        self.assertIn("function alignChartViewportToReservedSpace(chartKey, chartInstance, context, options = {})", self.app_js)
        self.assertIn("function resolveChartRightEdgeAnchor(chartKey)", self.app_js)
        self.assertIn("const zoomOutBtn = document.getElementById('zoomOutBtn');", self.app_js)
        self.assertIn("const zoomInBtn = document.getElementById('zoomInBtn');", self.app_js)
        self.assertIn("const panLeftBtn = document.getElementById('panLeftBtn');", self.app_js)
        self.assertIn("const panRightBtn = document.getElementById('panRightBtn');", self.app_js)
        self.assertIn("function setupChartNavigationCluster()", self.app_js)
        self.assertIn('id="zoomOutBtn"', self.index_html)
        self.assertIn('id="zoomInBtn"', self.index_html)
        self.assertIn('id="panLeftBtn"', self.index_html)
        self.assertIn('id="panRightBtn"', self.index_html)
        self.assertIn('.chart-nav-cluster', self.index_html)


if __name__ == "__main__":
    unittest.main()