refactor: establish standalone application boundary
This commit is contained in:
@@ -2,39 +2,581 @@ 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"
|
||||
LEGACY_STYLESHEETS = (
|
||||
|
||||
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 = (STATIC / "index.html").read_text(encoding="utf-8")
|
||||
cls.html = assembled_frontend_document()
|
||||
cls.tokens = TOKENS.read_text(encoding="utf-8")
|
||||
|
||||
def test_token_layer_loads_before_application_styles(self) -> None:
|
||||
expected_order = (
|
||||
"/shared/tokens.css",
|
||||
"/styles/styles.css",
|
||||
"/styles/renovation.css",
|
||||
"/styles/redesign-v2.css",
|
||||
"/styles/design-system.css",
|
||||
"/styles/theme.css",
|
||||
"/pages/heaven/page.css",
|
||||
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"
|
||||
)
|
||||
positions = [self.html.index(path) for path in expected_order]
|
||||
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 (
|
||||
@@ -57,13 +599,12 @@ class CssGovernanceTests(unittest.TestCase):
|
||||
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 in LEGACY_STYLESHEETS:
|
||||
stylesheet = (STATIC / filename).read_text(encoding="utf-8")
|
||||
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 = (STATIC / "pages" / "heaven" / "page.css").read_text(encoding="utf-8")
|
||||
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:
|
||||
|
||||
Reference in New Issue
Block a user