85 lines
3.8 KiB
Python
85 lines
3.8 KiB
Python
"""Static contracts for the B-229 login desktop layout."""
|
|
|
|
from __future__ import annotations
|
|
|
|
from pathlib import Path
|
|
import re
|
|
import unittest
|
|
|
|
ROOT = Path(__file__).resolve().parents[1]
|
|
WEB = ROOT / "web"
|
|
|
|
|
|
class LoginFrontendContractTests(unittest.TestCase):
|
|
@classmethod
|
|
def setUpClass(cls) -> None:
|
|
cls.html = (WEB / "index.html").read_text(encoding="utf-8")
|
|
cls.css = (WEB / "styles.css").read_text(encoding="utf-8")
|
|
cls.js = (WEB / "app.js").read_text(encoding="utf-8")
|
|
|
|
def test_dual_zone_markup_and_auth_controls(self) -> None:
|
|
self.assertIn('class="entry-context"', self.html)
|
|
self.assertIn('class="entry-form-wrap"', self.html)
|
|
self.assertIn('id="loginForm"', self.html)
|
|
self.assertIn('id="togglePassword"', self.html)
|
|
self.assertIn('id="changePassword"', self.html)
|
|
self.assertIn('id="loginError"', self.html)
|
|
self.assertIn('name="username"', self.html)
|
|
self.assertIn('name="password"', self.html)
|
|
self.assertIn('name="new_password"', self.html)
|
|
self.assertIn('name="confirm_password"', self.html)
|
|
self.assertIn('value="admin"', self.html)
|
|
self.assertIn('value="company"', self.html)
|
|
self.assertIn("总账管理端", self.html)
|
|
self.assertIn("公司业务端", self.html)
|
|
self.assertIn("服务状态", self.html)
|
|
self.assertIn("当前账期", self.html)
|
|
self.assertNotIn("entry-card", self.html)
|
|
self.assertNotIn('value="group-admin"', self.html)
|
|
self.assertNotIn('value="demo123456"', self.html)
|
|
|
|
def test_login_css_lives_in_one_section(self) -> None:
|
|
self.assertIn("minmax(460px, 520px)", self.css)
|
|
self.assertIn("max-width: 1180px", self.css)
|
|
self.assertIn("margin: 0 auto", self.css)
|
|
self.assertIn("width: min(440px, 100%)", self.css)
|
|
self.assertIn("@media (max-width: 1023px)", self.css)
|
|
self.assertIn("@media (max-width: 720px)", self.css)
|
|
self.assertIn("minmax(400px, 440px)", self.css)
|
|
self.assertNotIn(".entry-card", self.css)
|
|
self.assertNotIn("0 0 60px rgba(55, 235, 137, 0.06)", self.css)
|
|
login_block = self.css.split("/* Login */", 1)[1]
|
|
after_login = login_block.split("/* ---- B-44", 1)[0] if "/* ---- B-44" in login_block else login_block
|
|
self.assertEqual(1, after_login.count(".entry-shell { width: 100%;"))
|
|
self.assertEqual(3, len(re.findall(r"\.entry-shell \{", after_login)))
|
|
later = self.css.split("/* ---- B-44 intercompany balances ---- */", 1)[-1]
|
|
self.assertNotIn(".entry-shell", later)
|
|
self.assertNotIn(".entry-form {", later)
|
|
self.assertNotIn(".role-switch {", later)
|
|
|
|
def test_login_error_and_keyboard_focus_follow_tokens(self) -> None:
|
|
self.assertIn(".entry-form #loginError { color: var(--color-danger); }", self.css)
|
|
self.assertIn(
|
|
".entry-form .field input:focus-visible { outline: 2px solid var(--color-primary-strong); outline-offset: 3px; }",
|
|
self.css,
|
|
)
|
|
|
|
def test_role_switch_reuses_segmented_rhythm(self) -> None:
|
|
self.assertIn(".role-switch { display: flex; gap: 4px; padding: 4px;", self.css)
|
|
self.assertIn("min-height: 36px", self.css)
|
|
self.assertIn('role="radiogroup"', self.html)
|
|
self.assertIn("class=\"sr-only\"", self.html)
|
|
|
|
def test_auth_javascript_untouched(self) -> None:
|
|
self.assertIn("function initEntry()", self.js)
|
|
self.assertIn('fetch("/api/login"', self.js)
|
|
self.assertIn('fetch("/api/password/change"', self.js)
|
|
self.assertIn("must_change_password", self.js)
|
|
self.assertIn("#togglePassword", self.js)
|
|
self.assertIn("进入总账管理端", self.js)
|
|
self.assertIn("进入公司业务端", self.js)
|
|
|
|
|
|
if __name__ == "__main__":
|
|
unittest.main()
|