"""Both records show the lesson → rule link in the web UI (milestone 440, #4635).
The frontend has no component test runner, so these read the source — and
each is written against something the backend owns, so a drift on either
side fails here rather than rendering a page that quietly reads wrong:
- the client's judgment and link-state unions are the service's and the
model's own values, so a renamed state cannot leave a branch that never
matches;
- the lesson page branches on every judgment, with the unjudged answer as
the fall-through, so "not yet judged" is stated rather than left blank;
- the write-level list the pages hide controls by is the server's
PERMISSION_RANK from editor up, which is the drift that had the snippet
page hiding its edit controls from shared editors (`"edit"` is not a level
the server sends).
"""
from __future__ import annotations
import re
from pathlib import Path
from scribe.models.lesson_rule_link import LINK_STATES
from scribe.services import lesson_rules as links_svc
from scribe.services.access import PERMISSION_RANK
FRONTEND = Path(__file__).resolve().parents[1] / "frontend" / "src"
def _read(rel: str) -> str:
return (FRONTEND / rel).read_text()
def _union(source: str, name: str) -> set[str]:
"""The string members of `export type
")] def test_the_panel_is_gated_on_the_judgment_being_attached(): """An absent `rule_judgment` is "the links could not be read" — never "no rule" — so the panel must not render a state for it.""" tpl = _template(_read("views/LessonDetailView.vue")) assert '' in src, rel scoped = src[src.index("")] assert ".rule-chip" not in scoped, f"{rel} re-spells the chip" def test_the_write_levels_are_the_servers(): src = _read("utils/permission.ts") m = re.search(r"WRITE_LEVELS[^=]*=\s*\[([^\]]*)\]", src) assert m, "WRITE_LEVELS not found" client = set(re.findall(r'"([^"]+)"', m.group(1))) server = {p for p, rank in PERMISSION_RANK.items() if rank >= PERMISSION_RANK["editor"]} assert client == server def test_no_page_spells_its_own_write_check(): """The helper exists so pages agree; a page comparing against a share level by hand is the copy that drifted to `"edit"`. Matched on the names only a share level uses — `admin` and `owner` are also user roles, and `role === "admin"` is not this.""" offenders = [ str(p.relative_to(FRONTEND)) for p in [*FRONTEND.rglob("*.vue"), *FRONTEND.rglob("*.ts")] if p != FRONTEND / "utils" / "permission.ts" and re.search(r'[!=]==\s*"(?:viewer|editor|edit)"', p.read_text()) ] assert offenders == []