refactor: establish standalone application boundary

This commit is contained in:
leefer
2026-08-03 21:42:25 +08:00
parent 656f28a96d
commit d6def3af15
322 changed files with 73872 additions and 44656 deletions
+57 -47
View File
@@ -5,7 +5,10 @@ import unittest
from html.parser import HTMLParser
from pathlib import Path
from tests.preservation_helpers import reassembled_frontend_runtime
from tests.frontend_test_helpers import (
assembled_frontend_runtime,
assembled_frontend_document,
)
STATIC_DIR = Path(__file__).resolve().parents[1] / "frontend"
@@ -23,13 +26,21 @@ class IdCollector(HTMLParser):
class FrontendContractTests(unittest.TestCase):
@classmethod
def setUpClass(cls) -> None:
cls.html = (STATIC_DIR / "index.html").read_text(encoding="utf-8")
cls.script = reassembled_frontend_runtime()
cls.html = assembled_frontend_document()
cls.registry = (STATIC_DIR / "pages.config.js").read_text(encoding="utf-8")
cls.script = assembled_frontend_runtime()
cls.shell = (STATIC_DIR / "shared" / "shell.js").read_text(encoding="utf-8")
cls.ui_core = (STATIC_DIR / "shared" / "ui-core.js").read_text(encoding="utf-8")
cls.design_system = (STATIC_DIR / "styles" / "design-system.css").read_text(encoding="utf-8")
cls.theme = (STATIC_DIR / "styles" / "theme.css").read_text(encoding="utf-8")
cls.tokens = (STATIC_DIR / "shared" / "tokens.css").read_text(encoding="utf-8")
cls.auth_styles = (STATIC_DIR / "shared" / "auth.css").read_text(encoding="utf-8")
cls.feedback_styles = (STATIC_DIR / "shared" / "components" / "feedback.css").read_text(encoding="utf-8")
cls.sentiment_styles = (STATIC_DIR / "pages" / "sentiment" / "foundation.css").read_text(encoding="utf-8")
cls.screener_styles = (STATIC_DIR / "pages" / "screener" / "foundation.css").read_text(encoding="utf-8")
cls.mentor_styles = (STATIC_DIR / "pages" / "mentor" / "foundation.css").read_text(encoding="utf-8")
cls.heaven_styles = (STATIC_DIR / "pages" / "heaven" / "foundation.css").read_text(encoding="utf-8")
cls.review_styles = (STATIC_DIR / "pages" / "review" / "foundation.css").read_text(encoding="utf-8")
cls.theme_library_styles = (STATIC_DIR / "pages" / "themes" / "foundation.css").read_text(encoding="utf-8")
cls.base_styles = (STATIC_DIR / "shared" / "base.css").read_text(encoding="utf-8")
collector = IdCollector()
collector.feed(cls.html)
cls.ids = collector.ids
@@ -62,20 +73,21 @@ class FrontendContractTests(unittest.TestCase):
self.assertIn(f'data-view="{view_id}"', self.html)
for endpoint in ("/api/auction?", "/api/themes?", "/api/themes/detail?", "/api/popularity?"):
self.assertIn(endpoint, self.script)
screener_sources = "\n".join(
(
STATIC_DIR.parent
/ "backend"
/ "features"
/ "screener"
/ filename
).read_text(encoding="utf-8")
for filename in ("catalog.py", "factors.py")
)
for field in (
"auction_change", "auction_amount_million",
"auction_turnover_rate", "auction_volume_ratio",
):
self.assertIn(
field,
(
STATIC_DIR.parent
/ "backend"
/ "features"
/ "screener"
/ "engine.py"
).read_text(encoding="utf-8"),
)
self.assertIn(field, screener_sources)
def test_wencai_workspace_is_not_exposed_and_mentor_hides_internal_quality_score(self):
self.assertNotIn('id="wencaiView"', self.html)
@@ -160,23 +172,21 @@ class FrontendContractTests(unittest.TestCase):
self.assertIn("必需数据已完整,本日没有股票同时满足", self.script)
def test_dialogs_and_dark_table_hover_have_shared_safety_constraints(self):
redesign = (STATIC_DIR / "styles" / "redesign-v2.css").read_text(encoding="utf-8")
self.assertIn(".settings-dialog:not(.heaven-reading-dialog)[open] { margin: auto; }", redesign)
self.assertIn("max-height: min(760px, calc(100dvh - 28px));", redesign)
self.assertIn('#reviewWorkspaceView .data-table tbody tr:hover td', self.theme)
self.assertIn('#reviewWorkspaceView .data-table tbody td', self.theme)
self.assertIn('#screenerView .screener-result-frame tbody tr:hover td:last-child', self.theme)
self.assertIn(".settings-dialog:not(.heaven-reading-dialog)[open] {", self.auth_styles)
self.assertIn("margin: auto;", self.auth_styles)
self.assertIn("max-height: min(760px, -28px + 100dvh);", self.auth_styles)
self.assertIn('#reviewWorkspaceView .data-table tbody tr:hover td', self.review_styles)
self.assertIn('#reviewWorkspaceView .data-table tbody td', self.review_styles)
self.assertIn('#screenerView .screener-result-frame tbody tr:hover td:last-child', self.screener_styles)
def test_global_toast_has_one_owner_and_cannot_stretch_between_insets(self):
styles = (STATIC_DIR / "styles" / "styles.css").read_text(encoding="utf-8")
wentian = (STATIC_DIR / "pages" / "heaven" / "page.css").read_text(encoding="utf-8")
self.assertIn("#toast.toast {", styles)
self.assertIn("top: auto;", styles)
self.assertIn("left: auto;", styles)
self.assertIn("height: auto;", styles)
self.assertIn("#toast.toast[hidden] { display: none; }", styles)
self.assertNotIn(".toast{position:fixed", self.design_system)
self.assertNotRegex(wentian, r"(?m)^\.toast\s*\{")
self.assertIn("#toast.toast {", self.feedback_styles)
self.assertIn("inset: auto 18px 48px auto;", self.feedback_styles)
self.assertIn("width: max-content;", self.feedback_styles)
self.assertIn("height: auto;", self.feedback_styles)
self.assertIn("#toast.toast[hidden] {", self.feedback_styles)
self.assertIn("display: none;", self.feedback_styles)
self.assertNotRegex(self.heaven_styles, r"(?m)^\.toast\s*\{")
def test_public_knowledge_editors_are_hidden_for_non_admins(self):
self.assertIn('document.querySelector("#reasonForm").hidden = !isAdmin;', self.script)
@@ -185,8 +195,8 @@ class FrontendContractTests(unittest.TestCase):
def test_shared_ui_core_loads_before_application(self):
self.assertLess(
self.html.index('<script src="/shared/ui-core.js"'),
self.html.index('<script src="/app.js'),
self.registry.index('"/shared/ui-core.js"'),
self.registry.index('"/app.js'),
)
for function_name in (
"number", "clamp", "escapeHtml", "formatNumber", "formatTimestamp",
@@ -270,19 +280,19 @@ class FrontendContractTests(unittest.TestCase):
self.assertNotIn('context.fillStyle = "#6c7983";', chart)
def test_dark_mentor_tokens_and_sentiment_bottom_clearance_are_defined(self):
self.assertIn(":root[data-theme=\"dark\"] #mentorView {", self.theme)
self.assertIn("--mentor-ink: var(--text-primary);", self.theme)
self.assertIn("--mentor-sub: var(--text-secondary);", self.theme)
self.assertIn(":root[data-theme=\"dark\"] #mentorView .mentor-message {", self.theme)
self.assertIn("border-color: var(--line-soft);", self.theme)
self.assertIn("background: var(--surface-subtle);", self.theme)
self.assertIn("box-shadow: none;", self.theme)
self.assertIn("#sentimentCycleView .sentiment-history-frame {", self.theme)
self.assertIn("margin-bottom: var(--card-gap);", self.theme)
self.assertIn("padding-bottom: var(--card-gap);", self.theme)
self.assertIn(":root[data-theme=\"dark\"] #mentorView {", self.mentor_styles)
self.assertIn("--mentor-ink: var(--text-primary);", self.mentor_styles)
self.assertIn("--mentor-sub: var(--text-secondary);", self.mentor_styles)
self.assertIn(":root[data-theme=\"dark\"] #mentorView .mentor-message {", self.mentor_styles)
self.assertIn("border-color: var(--line-soft);", self.mentor_styles)
self.assertIn("background: var(--surface-subtle);", self.mentor_styles)
self.assertIn("box-shadow: none;", self.mentor_styles)
self.assertIn("#sentimentCycleView .sentiment-history-frame {", self.sentiment_styles)
self.assertIn("margin-bottom: var(--card-gap);", self.sentiment_styles)
self.assertIn("padding-bottom: var(--card-gap);", self.sentiment_styles)
self.assertIn("--sentiment-history-max-height: 510px;", self.tokens)
self.assertIn("max-height:var(--sentiment-history-max-height);", self.design_system)
self.assertIn("overflow:auto;", self.design_system)
self.assertIn("max-height: var(--sentiment-history-max-height);", self.sentiment_styles)
self.assertIn("overflow: auto;", self.sentiment_styles)
def test_theme_switch_is_atomic_and_theme_library_loading_surface_is_dark_safe(self):
self.assertIn('typeof document.startViewTransition === "function"', self.script)
@@ -290,9 +300,9 @@ class FrontendContractTests(unittest.TestCase):
self.assertIn('root.classList.remove("theme-switching")', self.script)
self.assertIn("clearThemeTransitionEffects();", self.script)
self.assertIn("redrawThemeSensitiveVisuals();", self.script)
self.assertIn(":root.theme-switching *", self.theme)
self.assertIn("::view-transition-old(root)", self.theme)
self.assertIn(".theme-detail-empty-v2,", self.theme)
self.assertIn(":root.theme-switching *", self.theme_library_styles)
self.assertIn("::view-transition-old(root)", self.base_styles)
self.assertIn(".theme-detail-empty-v2", self.theme_library_styles)
def test_membership_copy_includes_review_assistant_access(self):
self.assertIn("复盘助手仅对会员开放", self.html)