personal memory agent
0

Configure Feed

Select the types of activity you want to include in your feed.

solstone / tests / test_convey_app_bar.py
3.9 kB 115 lines
1# SPDX-License-Identifier: AGPL-3.0-only 2# Copyright (c) 2026 sol pbc 3 4from __future__ import annotations 5 6from pathlib import Path 7 8REPO_ROOT = Path(__file__).resolve().parents[1] 9APP_CSS_PATH = REPO_ROOT / "solstone" / "convey" / "static" / "app.css" 10 11 12def _class_specificity(selector: str) -> int: 13 return selector.count(".") 14 15 16def _rule_block(css: str, selector: str, start: int = 0) -> tuple[int, str]: 17 index = css.find(f"{selector} {{", start) 18 assert index != -1, f"{selector} rule was not found" 19 close = css.find("}", index) 20 assert close != -1, f"{selector} rule is not closed" 21 return index, css[index : close + 1] 22 23 24def _block_at(css: str, start: int) -> tuple[int, str]: 25 depth = 0 26 for index in range(start, len(css)): 27 char = css[index] 28 if char == "{": 29 depth += 1 30 elif char == "}": 31 depth -= 1 32 if depth == 0: 33 return start, css[start : index + 1] 34 raise AssertionError(f"block at {start} is not closed") 35 36 37def _media_blocks(css: str, query: str = "@media") -> list[tuple[int, str]]: 38 blocks: list[tuple[int, str]] = [] 39 start = css.find(query) 40 while start != -1: 41 blocks.append(_block_at(css, start)) 42 start = css.find(query, start + len(query)) 43 return blocks 44 45 46def _unconditional_app_bar_geometry_rule(css: str) -> tuple[int, str]: 47 media_spans = [ 48 (index, index + len(block)) for index, block in _media_blocks(css, "@media") 49 ] 50 start = css.find(".app-bar {") 51 while start != -1: 52 if not any( 53 span_start <= start < span_end for span_start, span_end in media_spans 54 ): 55 rule_index, rule = _rule_block(css, ".app-bar", start) 56 if "left:" in rule and "right:" in rule and "max-width:" in rule: 57 return rule_index, rule 58 start = css.find(".app-bar {", start + len(".app-bar {")) 59 raise AssertionError("unconditional .app-bar geometry rule was not found") 60 61 62def _mobile_app_bar_left_rules(css: str) -> list[tuple[int, str, str, int]]: 63 rules: list[tuple[int, str, str, int]] = [] 64 for block_index, block in _media_blocks(css, "@media (max-width: 768px)"): 65 rule_index = block.find(".app-bar {") 66 if rule_index == -1: 67 continue 68 _, rule = _rule_block(block, ".app-bar", rule_index) 69 if "left:" in rule: 70 rules.append((block_index + rule_index, rule, block, rule_index)) 71 return rules 72 73 74def test_desktop_app_bar_geometry_remains_unchanged(): 75 css = APP_CSS_PATH.read_text(encoding="utf-8") 76 _, base_rule = _unconditional_app_bar_geometry_rule(css) 77 78 assert "left: var(--menu-bar-width-minimal);" in base_rule 79 assert "right: 0;" in base_rule 80 assert "max-width: 1200px;" in base_rule 81 82 83def test_exactly_one_mobile_app_bar_rule_declares_left_with_comment(): 84 css = APP_CSS_PATH.read_text(encoding="utf-8") 85 rules = _mobile_app_bar_left_rules(css) 86 87 assert len(rules) == 1 88 89 _absolute_index, rule, block, rule_index = rules[0] 90 comment_index = block.rfind("/*", 0, rule_index) 91 comment_end = block.find("*/", comment_index) 92 93 assert comment_index != -1 94 assert comment_end != -1 95 assert comment_end < rule_index 96 assert comment_index < rule_index 97 assert "left: 8px;" in rule 98 assert "right: 8px;" in rule 99 assert "padding: 10px;" in rule 100 assert "gap: 10px;" in rule 101 102 103def test_later_mobile_app_bar_rule_wins_by_source_order_at_equal_specificity(): 104 css = APP_CSS_PATH.read_text(encoding="utf-8") 105 base_selector = ".app-bar" 106 mobile_selector = ".app-bar" 107 base_index, _base_rule = _unconditional_app_bar_geometry_rule(css) 108 rules = _mobile_app_bar_left_rules(css) 109 110 assert len(rules) == 1 111 112 mobile_index, _mobile_rule, _block, _block_rule_index = rules[0] 113 114 assert _class_specificity(base_selector) == _class_specificity(mobile_selector) 115 assert base_index < mobile_index