from __future__ import annotations import unittest from pathlib import Path ROOT = Path(__file__).resolve().parents[1] LOGIN = (ROOT / "frontend" / "login" / "index.html").read_text(encoding="utf-8") LOGIN_JS = (ROOT / "frontend" / "login" / "page.js").read_text(encoding="utf-8") AUTH = (ROOT / "frontend" / "shared" / "auth.css").read_text(encoding="utf-8") TOKENS = (ROOT / "frontend" / "shared" / "tokens.css").read_text(encoding="utf-8") class LoginPortalContractTests(unittest.TestCase): def test_confirmed_brand_skeleton_is_in_markup(self) -> None: for needle in ( 'class="login-brand-header"', 'class="login-brand-name"', "A股个人复盘工作台", 'class="login-brand-kicker"', "看懂情绪周期,把复盘变成下一次的先手。", 'class="login-brand-chart"', 'class="login-brand-stats"', "股市有风险,投资需谨慎", 'id="loginThemeToggle"', 'id="loginCard"', ): self.assertIn(needle, LOGIN) def test_login_tokens_own_the_confirmed_layout_metrics(self) -> None: for needle in ( "--login-brand-share: 34%;", "--login-brand-cap: 560px;", "--login-brand-wide-share: 29.2%;", "--login-card-width: 408px;", "--login-hero-size: 28px;", "--login-account-row-min: 72px;", ): self.assertIn(needle, TOKENS) self.assertIn("width: var(--login-brand-share);", AUTH) self.assertIn("width: var(--login-card-width);", AUTH) self.assertIn("justify-content: flex-end;", AUTH) self.assertIn("body.login-portal {", AUTH) self.assertIn("padding-bottom: 0;", AUTH) self.assertNotIn("padding: 0 0 var(--statusbar-height);", AUTH) def test_confirmed_mascots_fill_the_brand_gap(self) -> None: for needle in ( 'id="loginMascots"', 'aria-hidden="true"', 'class="login-mascot is-red"', 'class="login-mascot is-green"', 'class="login-mascot-back"', 'class="login-mascot-slit"', ): self.assertIn(needle, LOGIN) for needle in ( "--login-mascot-red: #e8605a;", "--login-mascot-green: #46be93;", "--login-mascot-height: clamp(150px, 15vw, 260px);", ): self.assertIn(needle, TOKENS) self.assertIn("flex: 1 1 auto;", AUTH) self.assertIn(".login-mascots {", AUTH) self.assertIn("prefers-reduced-motion: reduce", AUTH) self.assertIn('next === "password"', LOGIN_JS) self.assertIn("toggle-password", LOGIN_JS) self.assertIn("celebrateLogin", LOGIN_JS) def test_portal_keeps_account_switch_and_theme_hooks(self) -> None: self.assertIn("data-switch-id", LOGIN_JS) self.assertIn("data-resume-id", LOGIN_JS) self.assertIn('data-login-action="manage"', LOGIN_JS) self.assertIn('data-login-action="add"', LOGIN_JS) self.assertIn('data-login-action="resume"', LOGIN_JS) self.assertIn("继续使用", LOGIN_JS) self.assertIn("返回复盘", LOGIN_JS) self.assertIn("/api/auth/me", LOGIN_JS) self.assertIn("xiaobaiTheme", LOGIN_JS) self.assertNotIn("内网个人版", LOGIN) self.assertNotIn("192.168.200.11", LOGIN) self.assertNotIn("/api/heaven", LOGIN_JS) def test_current_account_row_stays_clickable_without_reswitching(self) -> None: self.assertIn("resumeCurrentAccount", LOGIN_JS) self.assertIn("当前会话已失效,请重新登录", LOGIN_JS) self.assertNotIn("!managing && !current ? \"is-switchable\"", LOGIN_JS) session = (ROOT / "frontend" / "shared" / "session.js").read_text(encoding="utf-8") self.assertIn('params.set("next", next)', session) self.assertIn(".login-account-action", AUTH)