refactor: establish standalone application boundary
This commit is contained in:
@@ -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)
|
||||
|
||||
Reference in New Issue
Block a user