"""Unit: the phase-39 brand layer (BOR_APP_NAME → window.BOR_BRAND). No Python logic exists for this task — the behavior lives in ``frontend/assets/brand.js`` + the templates + the page scripts, and it is E2E-gated by the story suite (task 03). Like the other frontend-adjacent unit files, this module pins the JS markers the story depends on, so a silent regression in the brand layer is caught without a browser. """ from __future__ import annotations import re from pathlib import Path FRONTEND = Path(__file__).resolve().parents[2] / "frontend" ROOT = FRONTEND.parent BRAND_JS = FRONTEND / "assets" / "brand.js" APP_JS = FRONTEND / "assets" / "app.js" DOCUMENT_JS = FRONTEND / "assets" / "document.js" CONTAINERFILE = ROOT / "Containerfile" #: Every page in the app ships the brand layer (phase 39: "every visible #: brand string on every page resolves from one place"). HTML_PAGES = ( "index.html", "sources.html", "tuning.html", "document.html", "login.html", "git-sources.html", "history.html", # phase 50: the admin saved-chats page "shared.html", # phase 51: the anonymous shared-conversation page "doc-edit.html", # phase 59: the admin doc edit screen (flow page) ) def _text(path: Path) -> str: return path.read_text(encoding="utf-8") def test_brand_js_is_a_classic_script_with_synchronous_default() -> None: """brand.js is NOT a module (its top level must run at parse time, before the page's module scripts evaluate) and sets window.BOR_BRAND synchronously, BEFORE the /api/config fetch starts.""" js = _text(BRAND_JS) assert not re.search(r"^\s*import\s", js, re.M), ( "brand.js must be a classic script — no import statements" ) assert not re.search(r"^\s*export\s", js, re.M), ( "brand.js must be a classic script — no export statements" ) default_idx = js.find('window.BOR_BRAND = "Brain of Reese"') fetch_idx = js.find('fetch("/api/config"') assert 0 <= default_idx < fetch_idx, ( "the default name must be set at top level before the fetch" ) def test_brand_js_fetches_api_config_no_store() -> None: """The single source of the name is GET /api/config, never cached — a renamed app must not serve a stale name from the HTTP cache — and a fetch failure only warns (the loadHealth house style: the page never breaks).""" js = _text(BRAND_JS) assert 'fetch("/api/config", { cache: "no-store" })' in js assert "console.warn" in js def test_brand_js_defers_dom_application_until_ready() -> None: """The DOM passes run on DOMContentLoaded while parsing, otherwise immediately (the script sits at the end of
).""" js = _text(BRAND_JS) assert 'document.readyState === "loading"' in js assert 'document.addEventListener("DOMContentLoaded", applyBrand)' in js def test_brand_js_reskins_title_brand_text_prose_and_attributes() -> None: """The four DOM passes (phase 39 task 02): the document title, every .brand-text (bold split for "Brain of …" names, plain text otherwise — the name is HTML-escaped before innerHTML), a TreeWalker over the text nodes (script/style nodes rejected — the page source is never rewritten), and the aria-label / placeholder / meta content attributes.""" js = _text(BRAND_JS) assert "document.title.replaceAll" in js assert ".brand-text" in js assert 'name.startsWith("Brain of ")' in js assert "escapeHTML" in js, "the name must be escaped before innerHTML" assert "el.textContent = name" in js, "non-'Brain of ' names render plain" assert "document.createTreeWalker" in js assert "NodeFilter.SHOW_TEXT" in js assert 'tag === "SCRIPT" || tag === "STYLE"' in js assert "replaceAll(BRAND_LITERAL, name)" in js for marker in ('"aria-label"', '"placeholder"', "meta[content]"): assert marker in js, f"the attribute pass must cover {marker}" def test_brand_js_applies_phase_62_customization_from_the_same_fetch() -> None: """Phase 62 (owner-locked 2026-09-01, TODO L3): the SAME settled /api/config answer also drives the three customization keys — the #message-input placeholder, every .footer-text node, and the theme stylesheet link (inserted right after the styles.css link, guarded by #theme-override, degrading with a console.warn on 404 — A5). No second network call: the keys ride the existing boot fetch.""" js = _text(BRAND_JS) assert js.count('= fetch("/api/config"') == 1, ( "the three keys must ride the existing boot fetch — no new call" ) # 5. The composer placeholder (chat page only — the null guard # no-ops on every other page). assert 'document.querySelector("#message-input")' in js assert "input_placeholder" in js # 6. The footer line on all 9 pages (the phase-61 hook) — via # textContent: an operator string can't inject markup. assert 'document.querySelectorAll(".footer-text")' in js assert "footer_text" in js # 7. The theme link: /assets/themes/