Files
xiaobai-review/tests/test_css_governance.py
T

624 lines
23 KiB
Python

from __future__ import annotations
import re
import unittest
from collections import defaultdict
from pathlib import Path
from tests.frontend_test_helpers import assembled_frontend_document
ROOT = Path(__file__).resolve().parents[1]
STATIC = ROOT / "frontend"
TOKENS = STATIC / "shared" / "tokens.css"
MODULE_STYLESHEETS = (
"shared/base.css",
"shared/shell.css",
"shared/auth.css",
"shared/components/controls.css",
"shared/components/navigation.css",
"shared/components/cards.css",
"shared/components/tables.css",
"shared/components/dialogs.css",
"shared/components/feedback.css",
"pages/market/foundation.css",
"pages/sentiment/foundation.css",
"pages/pools/foundation.css",
"pages/ladder/foundation.css",
"pages/rotation/foundation.css",
"pages/auction/foundation.css",
"pages/themes/foundation.css",
"pages/popularity/foundation.css",
"pages/dragon-tiger/foundation.css",
"pages/screener/foundation.css",
"pages/mentor/foundation.css",
"pages/heaven/foundation.css",
"pages/review/foundation.css",
)
RETIRED_STYLESHEETS = (
"styles/styles.css",
"styles/renovation.css",
"styles/redesign-v2.css",
"styles/design-system.css",
"styles/theme.css",
"pages/heaven/page.css",
)
PAGE_OWNERS = {
"sentimentCycleView": "pages/sentiment/foundation.css",
"limitPool": "pages/pools/foundation.css",
"brokenView": "pages/pools/foundation.css",
"downView": "pages/pools/foundation.css",
"yesterdayView": "pages/pools/foundation.css",
"performanceView": "pages/pools/foundation.css",
"ladderView": "pages/ladder/foundation.css",
"rotationView": "pages/rotation/foundation.css",
"auctionView": "pages/auction/foundation.css",
"themeLibraryView": "pages/themes/foundation.css",
"popularityView": "pages/popularity/foundation.css",
"dragonView": "pages/dragon-tiger/foundation.css",
"screenerView": "pages/screener/foundation.css",
"screenerTrackingView": "pages/screener/foundation.css",
"mentorView": "pages/mentor/foundation.css",
"heavenView": "pages/heaven/foundation.css",
"reviewWorkspaceView": "pages/review/foundation.css",
}
SHARED_ROOT_OWNERS = {
".app-header": "shared/shell.css",
".app-main": "shared/shell.css",
".module-nav": "shared/shell.css",
".overview-strip": "shared/shell.css",
".sidebar": "shared/shell.css",
".status-bar": "shared/shell.css",
".workspace-view": "shared/shell.css",
".button": "shared/components/controls.css",
".icon-button": "shared/components/controls.css",
".segment": "shared/components/navigation.css",
".data-table": "shared/components/tables.css",
".card": "shared/components/cards.css",
".dialog": "shared/components/dialogs.css",
".toast": "shared/components/feedback.css",
}
HISTORICAL_RUNTIME_CSS_BYTES = 861_341
HISTORICAL_SELECTOR_CONTEXT_RULES = 8_569
def normalize(value: str) -> str:
return re.sub(r"\s+", " ", re.sub(r"/\*.*?\*/", "", value, flags=re.DOTALL)).strip()
def normalize_context(value: str) -> str:
"""Normalize equivalent at-rule spellings before ownership checks."""
return re.sub(r"\s*([():,])\s*", r"\1", normalize(value))
def skip_space_and_comments(text: str, position: int) -> int:
while position < len(text):
if text[position].isspace():
position += 1
elif text.startswith("/*", position):
closing = text.find("*/", position + 2)
position = len(text) if closing < 0 else closing + 2
else:
break
return position
def find_delimiter(text: str, position: int) -> tuple[int, str]:
quote = ""
escaped = False
depth = 0
in_comment = False
while position < len(text):
char = text[position]
pair = text[position : position + 2]
if in_comment:
if pair == "*/":
in_comment = False
position += 2
continue
position += 1
continue
if quote:
if escaped:
escaped = False
elif char == "\\":
escaped = True
elif char == quote:
quote = ""
position += 1
continue
if pair == "/*":
in_comment = True
position += 2
continue
if char in "\"'":
quote = char
elif char in "([":
depth += 1
elif char in ")]":
depth = max(0, depth - 1)
elif depth == 0 and char in "{;":
return position, char
position += 1
return len(text), ""
def find_closing_brace(text: str, opening: int) -> int:
depth = 1
position = opening + 1
quote = ""
escaped = False
in_comment = False
while position < len(text):
char = text[position]
pair = text[position : position + 2]
if in_comment:
if pair == "*/":
in_comment = False
position += 2
continue
position += 1
continue
if quote:
if escaped:
escaped = False
elif char == "\\":
escaped = True
elif char == quote:
quote = ""
position += 1
continue
if pair == "/*":
in_comment = True
position += 2
continue
if char in "\"'":
quote = char
elif char == "{":
depth += 1
elif char == "}":
depth -= 1
if depth == 0:
return position
position += 1
raise AssertionError(f"unclosed CSS block at offset {opening}")
def split_selectors(prelude: str) -> list[str]:
selectors: list[str] = []
start = 0
quote = ""
escaped = False
depth = 0
for index, char in enumerate(prelude):
if quote:
if escaped:
escaped = False
elif char == "\\":
escaped = True
elif char == quote:
quote = ""
continue
if char in "\"'":
quote = char
elif char in "([":
depth += 1
elif char in ")]":
depth = max(0, depth - 1)
elif char == "," and depth == 0:
selectors.append(normalize(prelude[start:index]))
start = index + 1
selectors.append(normalize(prelude[start:]))
return [selector for selector in selectors if selector]
def stylesheet_rule_keys(text: str, contexts: tuple[str, ...] = ()) -> list[tuple[tuple[str, ...], str]]:
keys: list[tuple[tuple[str, ...], str]] = []
position = 0
while True:
position = skip_space_and_comments(text, position)
if position >= len(text):
return keys
delimiter, kind = find_delimiter(text, position)
prelude = normalize(text[position:delimiter])
if not kind:
raise AssertionError(f"unparsed CSS tail: {prelude[:80]}")
if kind == ";":
position = delimiter + 1
continue
closing = find_closing_brace(text, delimiter)
body = text[delimiter + 1 : closing]
lowered = prelude.lower()
if lowered.startswith(("@media", "@supports", "@container", "@layer", "@starting-style", "@scope")):
keys.extend(stylesheet_rule_keys(body, contexts + (normalize_context(prelude),)))
elif lowered.startswith(("@keyframes", "@-webkit-keyframes")):
keys.append((contexts, prelude))
elif not prelude.startswith("@"):
keys.extend((contexts, selector) for selector in split_selectors(prelude))
position = closing + 1
def declaration_properties(body: str) -> frozenset[str]:
properties: set[str] = set()
start = 0
position = 0
quote = ""
escaped = False
depth = 0
in_comment = False
while position <= len(body):
char = body[position] if position < len(body) else ";"
pair = body[position : position + 2]
if in_comment:
if pair == "*/":
in_comment = False
position += 2
continue
position += 1
continue
if quote:
if escaped:
escaped = False
elif char == "\\":
escaped = True
elif char == quote:
quote = ""
position += 1
continue
if pair == "/*":
in_comment = True
position += 2
continue
if char in "\"'":
quote = char
elif char in "([":
depth += 1
elif char in ")]":
depth = max(0, depth - 1)
elif char == ";" and depth == 0:
declaration = normalize(body[start:position])
match = re.match(r"^(--[-a-zA-Z0-9_]+|[-a-zA-Z][-_a-zA-Z0-9]*)\s*:", declaration)
if match:
properties.add(match.group(1).lower())
start = position + 1
position += 1
return frozenset(properties)
def stylesheet_rules(
text: str,
contexts: tuple[str, ...] = (),
) -> list[tuple[tuple[str, ...], str, frozenset[str]]]:
rules: list[tuple[tuple[str, ...], str, frozenset[str]]] = []
position = 0
while True:
position = skip_space_and_comments(text, position)
if position >= len(text):
return rules
delimiter, kind = find_delimiter(text, position)
prelude = normalize(text[position:delimiter])
if not kind:
raise AssertionError(f"unparsed CSS tail: {prelude[:80]}")
if kind == ";":
position = delimiter + 1
continue
closing = find_closing_brace(text, delimiter)
body = text[delimiter + 1 : closing]
lowered = prelude.lower()
if lowered.startswith(("@media", "@supports", "@container", "@layer", "@starting-style", "@scope")):
rules.extend(stylesheet_rules(body, contexts + (normalize_context(prelude),)))
elif not prelude.startswith("@"):
properties = declaration_properties(body)
rules.extend((contexts, selector, properties) for selector in split_selectors(prelude))
position = closing + 1
def remove_page_scope(selector: str, view_id: str) -> str:
prefixes = (
rf":where\(#{re.escape(view_id)}\)\s+",
rf"#{re.escape(view_id)}\s+",
rf"body\[data-active-view=[\"']{re.escape(view_id)}[\"']\]\s+",
)
semantic = selector
for prefix in prefixes:
semantic = re.sub(rf"^(?P<theme>:root(?:\[[^\]]+\])?\s+)?{prefix}", r"\g<theme>", semantic)
return normalize(semantic)
def find_matching_parenthesis(value: str, opening: int) -> int:
depth = 1
quote = ""
escaped = False
for position in range(opening + 1, len(value)):
char = value[position]
if quote:
if escaped:
escaped = False
elif char == "\\":
escaped = True
elif char == quote:
quote = ""
continue
if char in "\"'":
quote = char
elif char == "(":
depth += 1
elif char == ")":
depth -= 1
if depth == 0:
return position
raise AssertionError(f"unclosed selector function: {value}")
def prune_selector_consumers(
selector: str,
source: str,
dynamic_prefixes: set[str],
) -> tuple[str, bool, bool]:
output: list[str] = []
cursor = 0
changed = False
function_pattern = re.compile(r":(is|where|has|not)\(", re.IGNORECASE)
while match := function_pattern.search(selector, cursor):
opening = match.end() - 1
closing = find_matching_parenthesis(selector, opening)
output.append(selector[cursor : match.start()])
name = match.group(1).lower()
content = selector[opening + 1 : closing]
arguments = split_selectors(content)
retained: list[str] = []
nested_changed = False
for argument in arguments:
pruned, reachable, argument_changed = prune_selector_consumers(argument, source, dynamic_prefixes)
nested_changed |= argument_changed
if reachable:
retained.append(pruned)
else:
nested_changed = True
if name != "not" and not retained:
return selector, False, True
if name == "not" and not retained:
changed = True
elif nested_changed or len(retained) != len(arguments):
output.append(f":{name}({', '.join(retained)})")
changed = True
else:
output.append(selector[match.start() : closing + 1])
cursor = closing + 1
output.append(selector[cursor:])
pruned_selector = "".join(output)
tokens = {
match.group(2)
for match in re.finditer(r"([.#])([_a-zA-Z][-_a-zA-Z0-9]*)", pruned_selector)
}
reachable = all(
token in source or any(token.startswith(prefix) for prefix in dynamic_prefixes)
for token in tokens
)
return pruned_selector, reachable, changed or not reachable
class CssGovernanceTests(unittest.TestCase):
@classmethod
def setUpClass(cls) -> None:
cls.html = assembled_frontend_document()
cls.tokens = TOKENS.read_text(encoding="utf-8")
cls.modules = {
relative: (STATIC / relative).read_text(encoding="utf-8")
for relative in MODULE_STYLESHEETS
}
cls.rule_keys = {
relative: stylesheet_rule_keys(stylesheet)
for relative, stylesheet in cls.modules.items()
}
cls.rules = {
relative: stylesheet_rules(stylesheet)
for relative, stylesheet in cls.modules.items()
}
cls.runtime_source = "\n".join(
path.read_text(encoding="utf-8")
for path in STATIC.rglob("*")
if path.is_file()
and path.suffix in {".html", ".js", ".mjs"}
and "_css_core_before" not in path.parts
and path.name != "css-core-before.html"
)
cls.dynamic_class_prefixes = {
*re.findall(r"([_a-zA-Z][-_a-zA-Z0-9]*)\$\{", cls.runtime_source),
*re.findall(r'["\']([_a-zA-Z][-_a-zA-Z0-9]*)["\']\s*\+', cls.runtime_source),
}
def test_runtime_loads_only_the_canonical_stylesheet_stack(self) -> None:
expected = ("shared/tokens.css", *MODULE_STYLESHEETS)
positions = [self.html.index(f'/{path}') for path in expected]
self.assertEqual(positions, sorted(positions))
self.assertEqual(self.html.count('<link rel="stylesheet"'), len(expected))
for path in expected:
self.assertEqual(self.html.count(f'/{path}'), 1, path)
def test_historical_layers_and_patch_file_names_are_absent(self) -> None:
for relative in RETIRED_STYLESHEETS:
self.assertFalse((STATIC / relative).exists(), relative)
self.assertNotIn(f'/{relative}', self.html)
for name in ("legacy.css", "override.css", "fix.css"):
self.assertFalse(any(STATIC.rglob(name)), name)
def test_every_module_declares_its_owner_and_is_nonempty(self) -> None:
for relative, stylesheet in self.modules.items():
owner = Path(relative).parent.name if "/pages/" in f"/{relative}" else Path(relative).stem
if relative.startswith("shared/components/"):
owner = Path(relative).stem
self.assertTrue(
stylesheet.startswith(
f"/* Canonical CSS owner: {owner}. Historical layers consolidated 2026-08-02. */"
),
relative,
)
self.assertIn("{", stylesheet, relative)
def test_modules_do_not_contain_empty_declarations(self) -> None:
for relative, stylesheet in self.modules.items():
self.assertNotRegex(stylesheet, r"(?m)^\s*[-_a-zA-Z0-9]+:\s*;\s*$", relative)
def test_selector_and_context_have_exactly_one_owner(self) -> None:
owners: dict[tuple[tuple[str, ...], str], list[str]] = defaultdict(list)
for relative, keys in self.rule_keys.items():
for key in keys:
owners[key].append(relative)
duplicates = {
f"{' > '.join(contexts) or '<root>'} :: {selector}": paths
for (contexts, selector), paths in owners.items()
if len(paths) != 1
}
self.assertEqual(duplicates, {})
def test_page_ids_only_appear_in_their_owner_stylesheet(self) -> None:
for relative, stylesheet in self.modules.items():
for view_id, owner in PAGE_OWNERS.items():
if re.search(rf"(?:#{re.escape(view_id)}|data-active-view=[\"']{re.escape(view_id)})", stylesheet):
self.assertIn(relative, (owner, "shared/shell.css"), f"{view_id} leaked into {relative}")
def test_unscoped_shared_roots_stay_in_their_shared_owner(self) -> None:
for relative, keys in self.rule_keys.items():
for contexts, selector in keys:
owner = SHARED_ROOT_OWNERS.get(selector)
page_scoped = any(any(f"#{view_id}" in context for view_id in PAGE_OWNERS) for context in contexts)
if owner and not page_scoped:
self.assertEqual(relative, owner, f"{selector} leaked into {relative}")
def test_historical_shell_aliases_cannot_return(self) -> None:
class_tokens = {
token
for value in re.findall(r'class=["\']([^"\']*)["\']', self.html)
for token in value.split()
}
self.assertTrue({"app-header", "module-nav", "overview-strip", "status-bar"} <= class_tokens)
self.assertTrue({"topbar", "statusbar", "mktstrip", "brand", "logo"}.isdisjoint(class_tokens))
selector_text = "\n".join(
selector
for keys in self.rule_keys.values()
for _contexts, selector in keys
)
for alias in ("topbar", "statusbar", "mktstrip"):
self.assertNotRegex(selector_text, rf"(?<![-_a-zA-Z0-9])\.{alias}(?![-_a-zA-Z0-9])")
def test_special_shared_and_page_rules_have_one_owner(self) -> None:
exact_owners = {
".lucide": "shared/components/controls.css",
".visually-hidden": "shared/base.css",
}
for selector, expected_owner in exact_owners.items():
owners = [
relative
for relative, keys in self.rule_keys.items()
if any(candidate == selector for _contexts, candidate in keys)
]
self.assertEqual(owners, [expected_owner], selector)
for relative, keys in self.rule_keys.items():
for _contexts, selector in keys:
if ".wentian-v2-dialog" in selector:
self.assertEqual(relative, "pages/heaven/foundation.css", selector)
if re.search(r"\.admin-dialog\s+\.model-", selector):
self.assertEqual(relative, "shared/components/dialogs.css", selector)
def test_page_scope_variants_never_override_the_same_property(self) -> None:
conflicts: dict[str, list[str]] = defaultdict(list)
for view_id, owner in PAGE_OWNERS.items():
variants: dict[tuple[tuple[str, ...], str], list[tuple[str, frozenset[str]]]] = defaultdict(list)
for contexts, selector, properties in self.rules[owner]:
semantic = remove_page_scope(selector, view_id)
if semantic != selector:
variants[(contexts, semantic)].append((selector, properties))
for (contexts, semantic), rows in variants.items():
unique_selectors = {selector for selector, _properties in rows}
if len(unique_selectors) < 2:
continue
for index, (left_selector, left_properties) in enumerate(rows):
for right_selector, right_properties in rows[index + 1 :]:
if left_selector == right_selector:
continue
overlap = sorted(left_properties & right_properties)
if overlap:
context = " > ".join(contexts) or "<root>"
conflicts[owner].append(
f"{context} :: {semantic} :: {left_selector} <> {right_selector}: {overlap}"
)
details = "\n".join(
f"{owner}: {detail}"
for owner, owner_conflicts in conflicts.items()
for detail in owner_conflicts
)
self.assertFalse(conflicts, details)
def test_every_selector_has_a_runtime_consumer(self) -> None:
stale: list[str] = []
for relative, keys in self.rule_keys.items():
for contexts, selector in keys:
if selector.lower().startswith(("@keyframes", "@-webkit-keyframes")):
continue
_pruned, reachable, changed = prune_selector_consumers(
selector,
self.runtime_source,
self.dynamic_class_prefixes,
)
if not reachable or changed:
context = " > ".join(contexts) or "<root>"
stale.append(f"{relative}: {context} :: {selector}")
self.assertEqual(stale, [])
def test_runtime_css_is_smaller_and_has_fewer_rules(self) -> None:
total_bytes = sum((STATIC / path).stat().st_size for path in MODULE_STYLESHEETS)
total_rules = sum(len(keys) for keys in self.rule_keys.values())
self.assertLess(total_bytes, HISTORICAL_RUNTIME_CSS_BYTES)
self.assertLess(total_rules, HISTORICAL_SELECTOR_CONTEXT_RULES)
def test_token_file_has_three_layer_contract(self) -> None:
for heading in (
"/* Primitive tokens */",
"/* Semantic tokens */",
"/* Component tokens */",
"/* Compatibility aliases.",
):
self.assertIn(heading, self.tokens)
for variable in (
"--color-action:",
"--surface-canvas:",
"--text-primary:",
"--card-bg:",
"--control-height:",
"--sidebar-width:",
):
self.assertIn(variable, self.tokens)
def test_light_and_dark_semantics_share_one_owner(self) -> None:
self.assertIn(':root[data-theme="dark"] {', self.tokens)
global_root = re.compile(r'(?m)^:root(?:\[data-theme="dark"\])?\s*\{')
for filename, stylesheet in self.modules.items():
self.assertIsNone(global_root.search(stylesheet), filename)
def test_wentian_tokens_remain_isolated(self) -> None:
self.assertNotRegex(self.tokens, r"--wt-[a-z0-9-]+\s*:")
wentian = self.modules["pages/heaven/foundation.css"]
self.assertRegex(wentian, r"--wt-[a-z0-9-]+\s*:")
def test_compatibility_aliases_cover_historical_layers(self) -> None:
for variable in (
"--blue:",
"--up:",
"--xb-blue-500:",
"--r2-blue:",
"--chart-background:",
"--dragon-profile-list-width:",
):
self.assertIn(variable, self.tokens)
if __name__ == "__main__":
unittest.main()