Files
brain-of-reese/tests/unit/test_viewer_additive_toggle.py
T
ducoterra 6ba46e735a
Build and Push Containers / build-and-push-app (push) Successful in 2m3s
Build and Push Containers / build-and-push-db (push) Successful in 21s
phase: 163_pdf_strip_additive_selfheal
All gates verified. Final report:

**Phase 163 — final verification pass (all 5 tasks already complete; verification-only run)**

- Verified D1–D4 in code: `page_strip_mode` column + migration 0027, `DocContent` absent-never-null rule keyed off `page_count`; `_persist_pdf_pages` render → atomic tmp+`os.replace` writes → stale-tail-last + `strip_class` stamp; `_regate_pdf_strip` on both unchanged paths (mirror gate, heal, repair, fail-soft, one INFO line only when acting); viewer mode branch + Text/Pages toggle (Text default, `aria-pressed`, no innerHTML, lazy imgs).
- `uv run pytest --cov=app --cov-report=term-missing` → **3796 tests, exit 0, coverage 99%** (>90%)
- `uv run pytest tests/e2e/test_pdf_strip_additive_selfheal.py -v --no-cov` → **5 passed** in isolation
- `test_pdf_pages_in_sources + test_pictorial_pdf + test_garbage_pdf` → 10 passed; `test_document_viewer(_context) + test_image_documents` → 20 passed
- `uv run ruff check .` clean; `uv run pyright` → 0 errors; alembic **0027 downgrade→upgrade CLI round-trip clean** (+ integration pins)
- Completion criteria: additive view w/ toggle ✅ · primary DOM byte-identity ✅ · write-first order/mid-write-failure/atomic pins ✅ · heal + repair E2E ✅ · zero vision/LLM on fast path (mock-captured) ✅ · non-qualifying payload byte-identity ✅ · docs pass ✅
- **Documentation**: README.md + AGENTS.md reviewed in full — the phase's updates are accurate; all config-table defaults cross-checked against `app/config.py` (no drift), every page route, script, and E2E command referenced exists, every `.env.example` var documented — **verified consistent, no changes needed**.
- Notable: live `Letumdei` repair is owner-runtime post-deploy per D5 (next production Sync self-heals; no live changes made); the modified `.agents/screenshots/doc_modal_scrollbar_1280.png` is an E2E-regenerated artifact left for the pipeline commit.
- **Next pending phase:** `164_chat_error_honesty`
2026-10-07 13:57:30 -04:00

432 lines
18 KiB
Python

"""Unit: the additive PDF strip view (phase 163, task 04 — D4, LOCKED A1/A6).
Frontend source-contract pins + node behavioral runs (the
``test_document_viewer.py`` house pattern) over ``document.js``'s
``renderDocument`` — the ONE shared core (Sources modal AND
/document.html):
* the MODE branch — ``page_urls`` + ``page_strip_mode === "additive"``
falls through to the text-doc rendering (Summary panel + content,
the pre-148 branch reused) wrapped in a ``.doc-views`` container
with the ``.doc-view-toggle`` group above it (Text pressed by
default, LOCKED A6); ``page_urls`` with the mode ABSENT or
``"primary"`` keeps the phase-148 strip-only branch VERBATIM (append
the strip to ``contentEl`` + return — no toggle, no wrapper, no
Summary panel); falsy ``page_urls`` is untouched;
* the toggle semantics — ``role="group"`` + ``aria-label="Document
view"``, two ``<button type="button">`` labeled ``Text`` / ``Pages``
(fixed strings, no doc-derived text), ``aria-pressed`` true/false
initial state, and a click mutating ONLY the two ``aria-pressed``
states + the wrapper's ``data-view`` attribute (CSS-driven show/
hide — the strip node stays mounted, no re-render, no refetch);
* the never-build-HTML rule — createElement + properties/textContent
only in the new code path (the module's ONE innerHTML stays the
escape-first markdown render); the strip imgs keep
``loading = "lazy"`` (the hidden strip never fetches);
* the ``.doc-view-toggle`` CSS — the house ghost-pill idiom, the
pressed state as text + style (never color alone, B5), ≥44px
targets, and the ``data-view`` show/hide contract.
"""
from __future__ import annotations
import json
import re
import shutil
import subprocess
from pathlib import Path
import pytest
FRONTEND = Path(__file__).resolve().parents[2] / "frontend"
DOCUMENT_JS = FRONTEND / "assets" / "document.js"
STYLES_CSS = FRONTEND / "assets" / "styles.css"
HAVE_NODE = shutil.which("node") is not None
def _read(path: Path) -> str:
return path.read_text(encoding="utf-8")
def _fn(js: str, name: str) -> str:
"""The source of a top-level function via balanced-brace counting
(the ``test_document_viewer.py`` pattern)."""
start = js.find(f"function {name}(")
assert start != -1, f"{name}() must exist in document.js"
i = js.find("(", start)
depth = 0
close = i
while i < len(js):
if js[i] == "(":
depth += 1
elif js[i] == ")":
depth -= 1
if depth == 0:
close = i
break
i += 1
brace = js.find("{", close)
depth = 0
for j in range(brace, len(js)):
if js[j] == "{":
depth += 1
elif js[j] == "}":
depth -= 1
if depth == 0:
return js[start : j + 1]
raise AssertionError(f"unbalanced braces in {name}()")
def _code(js: str) -> str:
"""The source with comments stripped (the never-build-HTML pins)."""
return re.sub(r"//.*?$|/\*.*?\*/", "", js, flags=re.S | re.M)
def _run_node(script: str) -> str:
proc = subprocess.run(
["node", "-e", script], capture_output=True, text=True, timeout=60
)
assert proc.returncode == 0, f"node failed: {proc.stderr}"
return proc.stdout
# ---------------------------------------------------------------------------
# Source pins — the renderDocument mode branch
# ---------------------------------------------------------------------------
def test_render_document_branches_on_strip_mode() -> None:
"""The mode branch (LOCKED A2 — the tolerant read): the additive
flag keys on the EXACT ``doc.page_strip_mode === "additive"``
string (absent / "primary" / anything else ⇒ primary), and the
primary/absent strip branch stays the phase-148 shape — append
``docPagesStrip`` to ``contentEl`` and RETURN — evaluated BEFORE
any wrapper/toggle construction."""
render = _fn(_read(DOCUMENT_JS), "renderDocument")
assert 'doc.page_strip_mode === "additive"' in render, (
"only the exact additive string switches on the toggle"
)
gate_i = render.find('const additive = stripEligible && doc.page_strip_mode === "additive";')
primary_i = render.find("if (stripEligible && !additive) {")
strip_i = render.find("contentEl.appendChild(docPagesStrip(doc, pageUrls));")
return_i = render.find("return;", strip_i)
wrapper_i = render.find('views.className = "doc-views";')
assert -1 < gate_i < primary_i < strip_i < return_i < wrapper_i, (
"the primary/absent branch (strip into contentEl + return) precedes "
"the additive wrapper — its DOM stays the phase-148 shape verbatim"
)
# The wrapper + toggle: ONE .doc-views container, the toggle BEFORE
# it, both into contentEl; the text view lands in the wrapper.
append_i = render.find("contentEl.append(docViewToggle(views), views);")
assert wrapper_i < append_i, "the wrapper is built, then appended after its toggle"
assert 'views.setAttribute("data-view", "text");' in render, (
"LOCKED A6 — Text is the default view"
)
# The strip mounts ONCE inside the wrapper, after the text view —
# never rebuilt on a flip (no docPagesStrip call outside the two
# branches).
assert render.count("docPagesStrip(doc, pageUrls)") == 2
mount_i = render.rfind("views.appendChild(docPagesStrip(doc, pageUrls));")
assert mount_i > render.find('slot.appendChild(pre);') or mount_i > render.rfind(
"slot.appendChild(wrap);"
), "the strip mounts AFTER the text view inside the wrapper"
# The non-additive path is untouched: slot === contentEl (the
# pre-148 appends keep their exact shape through the slot var).
assert "let slot = contentEl;" in render
def test_primary_absent_branch_builds_no_toggle() -> None:
"""The primary/absent branch is toggle-free by construction: the
strip-branch block (from the ``pageUrls`` read to its ``return``)
contains NO ``doc-view-toggle`` / ``doc-views`` construction — the
strip-only DOM stays byte-identical to pre-phase-163."""
render = _fn(_read(DOCUMENT_JS), "renderDocument")
primary_start = render.find("if (stripEligible && !additive)")
branch = render[render.find("const pageUrls =") : render.find("return;", primary_start)]
assert "doc-view-toggle" not in branch
assert "doc-views" not in branch
assert branch.count("appendChild") == 1, (
"the primary branch is exactly the phase-148 append + return"
)
def test_toggle_never_builds_html_and_labels_are_fixed() -> None:
"""The never-build-HTML rule: the toggle builders use
createElement + properties/textContent only — no innerHTML /
outerHTML — and the labels are the FIXED strings ``Text`` /
``Pages`` (no doc-derived text inside the toggle). The module's
ONE innerHTML (the escape-first markdown render) survives."""
js = _read(DOCUMENT_JS)
toggle = _fn(js, "docViewToggle")
btn = _fn(js, "viewToggleButton")
for body in (toggle, btn):
code = _code(body)
assert "innerHTML" not in code and "outerHTML" not in code
assert 'document.createElement("div")' in toggle
assert 'group.setAttribute("role", "group")' in toggle
assert 'group.setAttribute("aria-label", "Document view")' in toggle
assert 'document.createElement("button")' in btn
assert 'btn.type = "button"' in btn
assert 'btn.className = "doc-view-btn"' in btn
assert 'viewToggleButton("Text")' in toggle
assert 'viewToggleButton("Pages")' in toggle
# The module-wide XSS contract: exactly one innerHTML, the
# renderMarkdown line.
whole = _code(js)
assert whole.count("innerHTML") == 1
assert "outerHTML" not in whole
def test_strip_imgs_stay_lazy() -> None:
"""The phase-148 lazy-img pin re-runs green: the strip builder is
UNMODIFIED — every page ``<img>`` keeps ``loading = "lazy"`` (the
additive default view never fetches the strip)."""
strip = _fn(_read(DOCUMENT_JS), "docPagesStrip")
assert 'img.loading = "lazy";' in strip
def test_view_toggle_css() -> None:
"""styles.css: the ``.doc-view-toggle`` group (the chat-column
reading width, the pill pair adjacent) + the ``.doc-view-btn``
ghost pill (≥44px target, ink-soft text — AA) + the PRESSED state
(``[aria-pressed="true"]`` — brand fill + font-weight 700: text +
style, never color alone, B5) + the CSS-driven view swap
(``.doc-views .doc-pages`` hidden by default; ``[data-view=
"pages"]`` hides the Summary panel / .doc-md / .doc-raw /
.doc-image and shows the strip as the grid). Provenance comment
above the block (house style)."""
css = _read(STYLES_CSS)
toggle_i = css.find(".doc-view-toggle {")
assert toggle_i != -1, "styles.css must style .doc-view-toggle"
comment_start = css.rfind("/*", 0, toggle_i)
assert -1 < comment_start < toggle_i and "phase 163" in css[comment_start:toggle_i]
btn_i = css.find(".doc-view-btn {")
pressed_i = css.find('.doc-view-btn[aria-pressed="true"]')
assert toggle_i < btn_i < pressed_i
btn_rule = css[btn_i : css.find("}", btn_i)]
for decl in ("min-height: 44px", "color: var(--ink-soft)", "border: 1px solid var(--line)"):
assert decl in btn_rule
pressed_rule = css[pressed_i : css.find("}", pressed_i)]
assert "background: var(--brand)" in pressed_rule
assert "font-weight: 700" in pressed_rule, (
"the pressed state is text weight + fill — never color alone (B5)"
)
# The CSS-driven swap: hidden strip by default; pages hides the
# text view and shows the strip.
assert '.doc-views .doc-pages { display: none; }' in css
for sel in (".doc-summary", ".doc-md", ".doc-raw", ".doc-image"):
assert f'.doc-views[data-view="pages"] {sel}' in css
assert '.doc-views[data-view="pages"] .doc-pages { display: grid; }' in css
# ---------------------------------------------------------------------------
# Node behavioral runs — the real renderDocument over a minimal fake DOM
# ---------------------------------------------------------------------------
_FAKE_DOM = """
function makeEl(tag) {
return {
tag, className: "", textContent: "", attrs: {}, children: [], handlers: {},
setAttribute(k, v) { this.attrs[k] = String(v); },
getAttribute(k) { return k in this.attrs ? this.attrs[k] : null; },
appendChild(c) { this.children.push(c); return c; },
append(...cs) { this.children.push(...cs); },
replaceChildren(...cs) { this.children = cs; },
addEventListener(ev, fn) { this.handlers[ev] = fn; },
click() { this.handlers.click(); },
};
}
globalThis.document = { createElement: (t) => makeEl(t) };
function fmtDate(x) { return "DATE:" + x; }
function metaBadge(cls, text, title) {
const s = makeEl("span");
s.className = cls;
s.textContent = text;
if (title !== undefined) s.setAttribute("title", title);
return s;
}
function copyPathButton() { return makeEl("button"); }
function originAnchor() { return makeEl("a"); }
async function docAdminReady() { return false; }
function wireDateEdit() {}
function wireSummaryEdit() {}
function renderMarkdown(s) { return s; }
function docImageBlock() { return makeEl("div"); }
function describe(el) {
return {
tag: el.tag,
className: el.className,
attrs: el.attrs,
text: el.textContent,
children: el.children.map(describe),
};
}
"""
def _render_script() -> str:
js = _read(DOCUMENT_JS)
parts = [
_fn(js, n)
for n in ("renderDocument", "docViewToggle", "viewToggleButton", "docPagesStrip")
]
return _FAKE_DOM + "\n".join(parts)
@pytest.mark.skipif(not HAVE_NODE, reason="node not available")
def test_additive_doc_renders_text_views_and_toggle() -> None:
"""Behavioral (node, fake DOM): an ADDITIVE doc renders the TEXT
doc exactly as a non-qualifying one — Summary panel + content —
inside ``.doc-views`` (data-view="text"), with the toggle group
FIRST in the content slot: role=group, aria-label="Document
view", the two ``doc-view-btn`` buttons labeled Text/Pages, Text
aria-pressed="true" (LOCKED A6), and the strip MOUNTED inside the
wrapper (figures + lazy imgs) after the text view."""
out = _run_node(
_render_script()
+ """
const doc = {
title: "Letumdei", path: "book.pdf", source: "Library", format: "pdf",
summary: "A book.", content: "BODY TEXT", created_at: "c", indexed_at: "i",
chunks: 9, page_urls: ["/api/documents/x/pages/1", "/api/documents/x/pages/2"],
page_strip_mode: "additive",
};
const contentEl = makeEl("div");
renderDocument(doc, { titleEl: makeEl("h1"), metaEl: makeEl("div"), contentEl });
console.log(JSON.stringify(describe(contentEl)));
"""
)
content = json.loads(out)
assert [c["className"] for c in content["children"]] == [
"doc-view-toggle",
"doc-views",
], "the toggle group sits above the .doc-views wrapper"
toggle, views = content["children"]
assert toggle["attrs"]["role"] == "group"
assert toggle["attrs"]["aria-label"] == "Document view"
labels = [c["text"] for c in toggle["children"]]
assert labels == ["Text", "Pages"]
assert [c["attrs"]["aria-pressed"] for c in toggle["children"]] == ["true", "false"], (
"Text pressed by default (LOCKED A6)"
)
for c in toggle["children"]:
assert c["tag"] == "button"
assert views["attrs"]["data-view"] == "text"
assert [c["className"] for c in views["children"]] == [
"doc-summary",
"doc-raw",
"doc-pages",
], "additive = Summary panel + text content + the mounted strip"
# pre.textContent — a text node, never parsed as HTML
assert views["children"][1]["text"] == "BODY TEXT"
strip = views["children"][2]
assert len(strip["children"]) == 2, "one figure per page_urls entry"
@pytest.mark.skipif(not HAVE_NODE, reason="node not available")
def test_primary_and_absent_modes_keep_the_strip_only_dom() -> None:
"""Behavioral: mode "primary" AND an absent mode (the pre-163
stamped row — the live Letumdei pre-sync shape) keep the phase-148
DOM byte-identically: the content slot holds EXACTLY the
``.doc-pages`` strip — no wrapper, no toggle, no Summary panel,
no text block."""
out = _run_node(
_render_script()
+ """
const results = [];
for (const mode of ["primary", undefined]) {
const doc = {
title: "Manual", path: "m.pdf", source: "Library", format: "pdf",
summary: "A manual.", content: "SHOULD NOT SHOW", created_at: "c",
indexed_at: "i", chunks: 4,
page_urls: ["/api/documents/y/pages/1"],
};
if (mode !== undefined) doc.page_strip_mode = mode;
const contentEl = makeEl("div");
renderDocument(doc, { titleEl: makeEl("h1"), metaEl: makeEl("div"), contentEl });
results.push(describe(contentEl));
}
console.log(JSON.stringify(results));
"""
)
primary, absent = json.loads(out)
for content in (primary, absent):
assert len(content["children"]) == 1
assert content["children"][0]["className"] == "doc-pages"
classes = json.dumps(content)
assert "doc-view-toggle" not in classes
assert "doc-views" not in classes
assert "doc-summary" not in classes
assert "doc-raw" not in classes, "the strip replaces the text block (primary class)"
@pytest.mark.skipif(not HAVE_NODE, reason="node not available")
def test_text_doc_dom_is_untouched() -> None:
"""Behavioral: a doc with no page_urls is UNTOUCHED — Summary
panel + content appended straight to the content slot, no
wrapper, no toggle (the byte-identity property for every
non-PDF)."""
out = _run_node(
_render_script()
+ """
const doc = {
title: "Note", path: "n.pdf", source: "Library", format: "pdf",
summary: "S.", content: "TEXT", created_at: "c", indexed_at: "i", chunks: 1,
};
const contentEl = makeEl("div");
renderDocument(doc, { titleEl: makeEl("h1"), metaEl: makeEl("div"), contentEl });
console.log(JSON.stringify(describe(contentEl)));
"""
)
content = json.loads(out)
assert [c["className"] for c in content["children"]] == ["doc-summary", "doc-raw"]
@pytest.mark.skipif(not HAVE_NODE, reason="node not available")
def test_toggle_flips_view_without_rerender_or_refetch() -> None:
"""Behavioral: the toggle handler mutates ONLY the two
aria-pressed states + the wrapper's data-view. Clicking Pages
flips data-view to "pages"; clicking back to Text reverses; the
strip node STAYS MOUNTED the whole time (same object, no
re-append — the flip-flop fetches nothing) and the wrapper's
children never change count."""
out = _run_node(
_render_script()
+ """
const doc = {
title: "Book", path: "b.pdf", source: "Library", format: "pdf",
summary: "S.", content: "TEXT", created_at: "c", indexed_at: "i", chunks: 2,
page_urls: ["/api/documents/z/pages/1", "/api/documents/z/pages/2"],
page_strip_mode: "additive",
};
const contentEl = makeEl("div");
renderDocument(doc, { titleEl: makeEl("h1"), metaEl: makeEl("div"), contentEl });
const toggle = contentEl.children[0];
const views = contentEl.children[1];
const [textBtn, pagesBtn] = toggle.children;
const stripBefore = views.children[2];
const countBefore = views.children.length;
pagesBtn.click();
const afterPages = {
view: views.getAttribute("data-view"),
pressed: toggle.children.map((b) => b.getAttribute("aria-pressed")),
};
textBtn.click();
const afterText = {
view: views.getAttribute("data-view"),
pressed: toggle.children.map((b) => b.getAttribute("aria-pressed")),
stripSame: views.children[2] === stripBefore,
countSame: views.children.length === countBefore,
};
console.log(JSON.stringify({ afterPages, afterText }));
"""
)
res = json.loads(out)
assert res["afterPages"] == {"view": "pages", "pressed": ["false", "true"]}
assert res["afterText"] == {
"view": "text",
"pressed": ["true", "false"],
"stripSame": True,
"countSame": True,
}, "the strip node persists across flips — no re-render, no refetch"