"""Unit: the document modal's history-entry lifecycle (phase 144, task 02). Back while the ``#doc-modal`` document viewer is open must CLOSE the modal — not navigate the view behind it (TODO.md L6). The mechanics live in ``frontend/assets/document-modal.js``: the open owns ONE state-only history entry (``{ modal: "doc", source, path }`` — the second arg ``""`` keeps the URL where it is, so the router's pathname-only popstate is a no-op and the Sources drill re-adopts the state it was already showing); Back closes the modal (remembering the landed state as ``lastPrev``); a button/Escape/backdrop close removes the entry IN PLACE (``replaceState(entry.prev)`` — the next Back behaves as if the modal never opened); Forward onto the entry re-opens the same document (the browser owns its entries — the phase-99 D2 convention). The browser behavior itself is E2E-gated by the phase-144 story suite (``tests/e2e/test_modal_back_button.py`` — task 03 drives the real history via ``page.goBack()``/``page.goForward()``); like ``test_remove_confirm_modal.py`` (the house pattern for a frontend module whose real behavior is E2E-gated), this module pins the source-level contract a silent regression would break: * a second push on the forward re-open (the adopt branch must assign ``entry`` WITHOUT a ``pushState``); * a ``replaceState`` on a non-modal current entry (the close's in-place removal is guarded by ``!closingViaBack`` AND ``history.state.modal === "doc"`` — the fail-safe); * a double-registered popstate listener; * a ``history.back()``/``history.go()`` introduction (the browser owns its entries — the closed set of ``history.*`` sites is the one push, the two replaces, and the read-only ``history.state`` accesses); * a lost ``lastPrev`` write (it must land BEFORE the ``closingViaBack``-wrapped close in the back branch); * a fallback-path push (the ``window.open`` no-skeleton fallback's early ``return`` precedes the first ``history.`` use in the open). Phase 153 (task 01) — the "Full page" escape hatch carries the origin ``back``; the module's added pins: * a second ``currentBack`` declaration / a reset that does not land at the top of EVERY open (before the no-skeleton guard — the fallback path reads the stored value); * ``fullPageUrl`` appending ``&back=`` unconditionally (a null/absent ``back`` must keep the URL byte-identical — the Sources path); * the ``openDocumentModal`` ``back`` default drifting from ``null`` (the Sources table's 3-arg call must stay byte-identical); * a ``fullPageUrl`` call site dropping the stored ``back`` (the no-skeleton fallback and the anchor must both read ``currentBack``). Phase 153 (task 03) — unit pins for the URL contract (additions on top of task 01's source pins; the pre-phase contract is the load-bearing byte-identity): * ``fullPageUrl`` behavior under node — ``back = null`` and an ABSENT third arg are byte-identical to each other AND to the exact pre-phase string (no ``&back=`` anywhere — the Sources path); ``back = "/"`` appends exactly ``&back=%2F`` to that same string; * ``openDocumentModal`` behavior under node — the no-skeleton ``window.open`` fallback carries the stored ``back`` (a chat open's ``"/"`` lands in the URL, an omitted ``back`` keeps it byte- identical) and the ``#doc-modal-open`` ("Full page") anchor carries it on a skeleton open (chat → ``&back=%2F``; a Sources-style open → byte-identical, no param). """ 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" MODAL_JS = FRONTEND / "assets" / "document-modal.js" HAVE_NODE = shutil.which("node") is not None 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 def _js() -> str: return MODAL_JS.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_remove_confirm_modal.py helper — ``export function`` matches the plain ``function name(`` search).""" start = js.find(f"function {name}(") assert start != -1, f"{name}() must exist in document-modal.js" depth = 0 for i in range(js.find("{", start), len(js)): if js[i] == "{": depth += 1 elif js[i] == "}": depth -= 1 if depth == 0: return js[start : i + 1] raise AssertionError(f"unbalanced braces in {name}()") def _block(source: str, from_i: int) -> str: """The ``{ … }`` block opening at the first ``{`` at/after ``from_i`` — balanced-brace walk, braces included.""" brace = source.find("{", from_i) assert brace != -1, "no block opener found" depth = 0 for i in range(brace, len(source)): if source[i] == "{": depth += 1 elif source[i] == "}": depth -= 1 if depth == 0: return source[brace : i + 1] raise AssertionError("unbalanced braces in the entry/popstate block") def _popstate_handler(js: str) -> str: """The module-top-level popstate handler's ``{ … }`` body.""" i = js.find('window.addEventListener("popstate"') assert i != -1, "the popstate listener must be registered at module top level" arrow = js.find("=> {", i) assert arrow != -1, "the popstate listener must be an arrow function" return _block(js, arrow) # ---------- the module state: one declaration, no second writer site ---------- def test_history_state_is_declared_exactly_once() -> None: """REGRESSION: a duplicated module-level declaration / second writer site for the entry state. ``entry`` / ``lastPrev`` / ``closingViaBack`` are each declared by EXACTLY ONE ``let`` (the single writer sites — open/close, the popstate handler, the popstate handler — are pinned by the per-branch tests below).""" js = _js() assert js.count("let entry = null;") == 1 assert len(re.findall(r"let\s+entry\s*=", js)) == 1 assert js.count("let lastPrev = null;") == 1 assert len(re.findall(r"let\s+lastPrev\s*=", js)) == 1 assert js.count("let closingViaBack = false;") == 1 assert len(re.findall(r"let\s+closingViaBack\s*=", js)) == 1 # ---------- the open: one state-only entry, adopt vs push vs replace ---------- def test_open_pushes_exactly_one_state_only_entry() -> None: """REGRESSION: a double push (two entries per open) or a URL-MOVING push. The push is exactly ONE ``history.pushState({ modal: "doc", source, path }, "")`` — the ``""`` second arg is what keeps the URL where it is (state-only; the router's pathname-only popstate stays a no-op).""" open_body = _fn(_js(), "openDocumentModal") assert open_body.count("history.pushState(") == 1, ( "exactly one pushState in the open — a second push stacks a dead history step" ) assert 'history.pushState({ modal: "doc", source, path }, "")' in open_body, ( "the push must be state-only: the state key modal: \"doc\" + the " "doc name, and the \"\" second arg (the URL must not move)" ) def test_open_entry_chain_adopts_on_forward_and_replaces_on_defensive_reopen() -> None: """REGRESSION: a second push on the forward re-open. The chain is ``if (entry) → replaceState IN PLACE (keep entry, update source/path — the defensive re-open-while-open never stacks) → else if the current entry is already a modal entry → ADOPT (assign ``entry = { prev: lastPrev, source, path }`` WITHOUT any pushState — the browser already sits on the entry) → else push. The push lives ONLY in the final else.""" open_body = _fn(_js(), "openDocumentModal") st_i = open_body.find("const st = history.state;") def_i = open_body.find("if (entry) {", st_i) adopt_i = open_body.find('else if (st && st.modal === "doc") {', def_i) else_i = open_body.find("} else {", adopt_i) push_i = open_body.find('history.pushState({ modal: "doc", source, path }, "");', else_i) assert -1 < st_i < def_i < adopt_i < else_i < push_i, ( "chain order: const st → if (entry) → else if (adopt) → else → push" ) # Defensive re-open: replaceState in place, entry KEPT (updated). defensive = open_body[def_i:adopt_i] assert "entry.source = source;" in defensive assert "entry.path = path;" in defensive assert 'history.replaceState({ modal: "doc", source, path }, "")' in defensive assert "pushState" not in defensive, "a defensive re-open must never push" # Forward re-open: ADOPT — assign entry from lastPrev, no push. adopt = open_body[adopt_i:else_i] assert "entry = { prev: lastPrev, source, path };" in adopt, ( "the adopt must take lastPrev (the below-entry remembered at the back-close)" ) assert "pushState" not in adopt, ( "a second push on the forward re-open stacks a dead history step" ) # Normal open: push with prev = the entry we sat on, in the final else. assert "entry = { prev: st, source, path };" in open_body[else_i:push_i] assert open_body.count("history.pushState(") == 1 def test_window_open_fallback_precedes_the_entry_logic() -> None: """REGRESSION: a fallback-path push. A page WITHOUT the modal skeleton falls back to ``window.open(fullPageUrl…)`` in a new tab — that path must not touch the history AT ALL: the fallback's early ``return`` precedes the first ``history.`` use in the open.""" open_body = _fn(_js(), "openDocumentModal") guard_i = open_body.find("if (!modalEl || !titleEl || !metaEl || !contentEl) {") open_i = open_body.find( 'window.open(fullPageUrl(source, path, currentBack), "_blank", "noopener");' ) ret_i = open_body.find("return;", open_i) first_hist_i = open_body.find("history.") assert -1 < guard_i < open_i < ret_i < first_hist_i, ( "the window.open fallback must early-return before any history use" ) # ---------- the close: in-place removal, guarded; entry always nulled ---------- def test_close_removes_the_entry_in_place_and_only_on_a_modal_entry() -> None: """REGRESSION: a ``replaceState`` on the wrong entry. The in-place removal ``history.replaceState(entry.prev, "")`` is guarded by BOTH ``!closingViaBack`` (a back close already moved us off the entry — the browser owns that pop) AND the current entry still being a modal entry (the fail-safe: a non-modal current entry is NEVER overwritten). Exactly one replaceState in the close.""" close_body = _fn(_js(), "closeDocumentModal") assert close_body.count("history.replaceState(") == 1 guard = 'if (entry && !closingViaBack && history.state && history.state.modal === "doc") {' guard_i = close_body.find(guard) assert guard_i != -1, ( "the close's in-place removal must be guarded by !closingViaBack AND " "history.state.modal === \"doc\"" ) # The single replace sits INSIDE the guard block. block = _block(close_body, guard_i) assert 'history.replaceState(entry.prev, "");' in block, ( "the removal must restore entry.prev (byte-for-byte view state)" ) def test_close_nulls_the_entry_unconditionally() -> None: """REGRESSION: a lost ``entry = null`` — the adopt's memory surviving a close (a later popstate would then close/adopt against a stale entry). ``entry = null`` runs OUTSIDE the guarded if, between the removal and the hide, on EVERY close (back or not).""" close_body = _fn(_js(), "closeDocumentModal") guard_i = close_body.find('if (entry && !closingViaBack && history.state') brace_i = close_body.find("{", guard_i) block = _block(close_body, guard_i) assert "entry = null" not in block, "entry = null must not sit inside the guard" null_i = close_body.find("entry = null;", brace_i + len(block)) hide_i = close_body.find("modalEl.hidden = true;", null_i) assert -1 < null_i < hide_i, "entry = null must run (before the hide) on every close" # ---------- the popstate listener: once, mutually exclusive branches ---------- def test_popstate_listener_registered_exactly_once() -> None: """REGRESSION: a double-registered popstate listener (two closes / two re-opens per navigation). The module registers EXACTLY ONE ``window.addEventListener("popstate"`` at top level.""" js = _js() assert js.count('window.addEventListener("popstate"') == 1 def test_popstate_forward_branch_reopens_and_returns() -> None: """REGRESSION: the forward branch falling THROUGH into the back-close branch. Forward = the modal is closed and we landed on a modal entry — re-open the doc the entry names (trigger null — focus goes to the content target) and RETURN before the back branch (the two branches are mutually exclusive).""" handler = _popstate_handler(_js()) const_i = handler.find("const st = event.state;") flag_i = handler.find('const isModalEntry = st && st.modal === "doc";') fwd_i = handler.find("if (isModalEntry && modalEl && modalEl.hidden) {") reopen_i = handler.find("openDocumentModal(st.source, st.path, null);", fwd_i) ret_i = handler.find("return;", reopen_i) back_i = handler.find("if (!isModalEntry && modalEl && !modalEl.hidden && entry) {", ret_i) assert -1 < const_i < flag_i < fwd_i < reopen_i < ret_i < back_i, ( "forward: land on a modal entry with the modal closed → re-open the " "named doc (null trigger) → RETURN before the back-close branch" ) def test_popstate_back_branch_remembers_lastprev_then_closes_guarded() -> None: """REGRESSION: a lost ``lastPrev`` write (the forward re-open's adopt would then restore ``prev: null`` and corrupt the entry below). Back = the modal is open and we landed on a NON-modal state: write ``lastPrev = st`` FIRST, then the close wrapped in ``closingViaBack = true`` … ``= false`` (keeps the close from also replaceState-ing the entry the browser already popped). The flag and the below-entry have their single writer sites here.""" js = _js() handler = _popstate_handler(js) back_i = handler.find("if (!isModalEntry && modalEl && !modalEl.hidden && entry) {") assert back_i != -1, ( "the back branch (non-modal land + modal open + entry) is gone" ) lastprev_i = handler.find("lastPrev = st;", back_i) setflag_i = handler.find("closingViaBack = true;", lastprev_i) close_i = handler.find("closeDocumentModal();", setflag_i) resetflag_i = handler.find("closingViaBack = false;", close_i) assert -1 < lastprev_i < setflag_i < close_i < resetflag_i, ( "back: lastPrev = st → closingViaBack = true → close → = false" ) # The single writer sites (no second writer anywhere in the module). assert js.count("lastPrev = st;") == 1 assert js.count("closingViaBack = true;") == 1 assert handler.count("closingViaBack = false;") == 1 def test_popstate_passes_through_everything_else() -> None: """REGRESSION: a handler branch firing on entries that are not the modal's (the router's view entries, the drill's ``kb`` entries) or on a modal entry with the modal still open. The forward branch requires ``isModalEntry`` (and the modal hidden) and the back branch requires ``entry`` (falsy when the modal is closed) — and AFTER the back branch closes the handler does NOTHING (the tail is empty): every other popstate falls through untouched.""" handler = _popstate_handler(_js()) back_i = handler.find("if (!isModalEntry && modalEl && !modalEl.hidden && entry) {") assert back_i != -1, "the back branch (non-modal land + modal open + entry) is gone" brace_i = handler.find("{", back_i) back_block = _block(handler, back_i) tail = handler[brace_i + len(back_block) :] # The only thing after the back branch is the handler's own closing # brace — no code runs on any other popstate (the pass-through). assert tail.strip() in ("", "}"), ( "no code after the back branch — the pass-through must be a no-op" ) # ---------- the no-leak guarantee: the closed set of history.* sites ---------- def test_history_call_sites_are_the_closed_set() -> None: """REGRESSION: a ``history.back()``/``history.go()`` introduction, or ANY untracked ``history.*`` call site. The browser owns its entries (the phase-99 D2 convention): the module's ONLY ``history.`` sites are the one ``pushState`` (the open), the two ``replaceState`` (the defensive re-open, the non-back close), and the read-only ``history.state`` accesses.""" js = _js() sites = re.findall(r"history\.\w+", js) assert sorted(set(sites)) == [ "history.pushState", "history.replaceState", "history.state", ], f"an untracked history.* site slipped in: {sorted(set(sites))}" assert sites.count("history.pushState") == 1 assert sites.count("history.replaceState") == 2 assert "history.back(" not in js, "the browser owns its entries — no history.back()" assert "history.go(" not in js, "the browser owns its entries — no history.go()" # ---------- the pre-phase contract a regression could plausibly break ---------- def test_pre_phase_contract_pins_still_hold() -> None: """The phase-26 contracts the entry logic sits beside (a regression in the rework must not break them): the Escape capture closes via ``closeDocumentModal()``; the per-open keydown capture is added on open and removed on close (document-level, capture phase); the ``fetchSeq`` stale-guard lines survive (both checkpoints in the fetch chain + the close bump); the trigger-focus restore in the close (best-effort, contained in the DOM).""" js = _js() keydown = _fn(js, "onModalKeydown") esc_i = keydown.find('if (e.key === "Escape") {') prevent_i = keydown.find("e.preventDefault();", esc_i) close_i = keydown.find("closeDocumentModal();", prevent_i) assert -1 < esc_i < prevent_i < close_i, "Escape: prevent + closeDocumentModal()" open_body = _fn(js, "openDocumentModal") close_body = _fn(js, "closeDocumentModal") assert ( 'document.addEventListener("keydown", onModalKeydown, true);' in open_body ), "the per-open capture is added on open (document-level, capture)" assert ( 'document.removeEventListener("keydown", onModalKeydown, true);' in close_body ), "the per-open capture is removed on close" # The fetchSeq stale guard: the sequence, both checkpoints, the # catch checkpoint, and the close bump (any in-flight fetch is # stale after a close). assert open_body.count("const seq = ++fetchSeq;") == 1 assert open_body.count("if (seq !== fetchSeq) return;") == 2, ( "both render checkpoints (pre- and post-json) must stay stale-guarded" ) assert "if (seq === fetchSeq) renderNotFound(path);" in open_body assert "fetchSeq += 1;" in close_body # The trigger-focus restore (best-effort: contained in the DOM). assert ( "triggerEl && typeof triggerEl.focus === \"function\" && document.contains(triggerEl)" in close_body ) assert "triggerEl.focus({ preventScroll: true });" in close_body assert "triggerEl = null;" in close_body # ---------- phase 153 (task 01): the "Full page" hatch carries the origin ---------- def test_currentback_declared_exactly_once_and_reset_at_the_top_of_every_open() -> None: """REGRESSION: a second ``currentBack`` writer, or a reset that does not land at the top of EVERY open. ``currentBack`` is declared by EXACTLY ONE ``let``, and the open resets it (``currentBack = back;``) BEFORE the no-skeleton guard — so even the ``window.open`` fallback path (which early-returns there) reads the stored value of THIS open.""" js = _js() assert js.count("let currentBack = null;") == 1 assert len(re.findall(r"let\s+currentBack\s*=", js)) == 1 open_body = _fn(js, "openDocumentModal") reset_i = open_body.find("currentBack = back;") guard_i = open_body.find("if (!modalEl || !titleEl || !metaEl || !contentEl) {") assert -1 < reset_i < guard_i, ( "the reset must land at the TOP of every open — before the " "no-skeleton guard (the fallback path reads the stored value)" ) # Exactly one reset site (no second writer anywhere in the module). assert js.count("currentBack = back;") == 1 def test_fullpageurl_appends_encoded_back_only_when_truthy() -> None: """REGRESSION: the ``back`` param leaking into a null/absent open (the Sources path must stay byte-identical — the viewer's /sources.html default) or the encoding drifting from the app.js::documentUrl contract. The signature is ``fullPageUrl(source, path, back = null)`` and the ONLY writer after the base URL is the guarded ``if (back)`` append — percent-encoded, exactly the way documentUrl documents it.""" js = _js() assert "function fullPageUrl(source, path, back = null)" in js fn = _fn(js, "fullPageUrl") assert 'if (back) url += "&back=" + encodeURIComponent(back);' in fn # The base URL is built first; the guarded append is the only # ``&back=`` writer (a null back bakes no param into the URL). base_i = fn.find('"/document.html?source=" + encodeURIComponent(') back_i = fn.find('"&back=" + encodeURIComponent(back)', base_i) assert -1 < base_i < back_i, "the back append must follow the base URL" base_part = fn.split('"&back="')[0] assert "back=" not in base_part, ( "no &back= may be baked into the base URL (null back = no param)" ) def test_open_document_modal_defaults_back_to_null() -> None: """REGRESSION: a non-null default for the ``back`` argument. ``openDocumentModal(source, path, trigger = null, back = null)`` — a call WITHOUT ``back`` (the Sources table's ``openDocumentModal(d.source, d.path, link)``) must leave the "Full page" URL byte-identical to pre-phase (the viewer's /sources.html default — the byte-identity is the contract).""" js = _js() assert ( "export function openDocumentModal(source, path, trigger = null, back = null)" in js ) def test_fallback_and_anchor_both_read_the_stored_back() -> None: """REGRESSION: a call site passing ``back`` positionally from a caller, or dropping the third argument (a dropped arg loses the origin — the chat path would land on "/sources.html"). BOTH the no-skeleton ``window.open`` fallback and the ``#doc-modal-open`` ("Full page") anchor read ``currentBack``: exactly three ``fullPageUrl(`` sites in the module (one definition + two call sites), and no call site without the stored variable.""" js = _js() assert 'window.open(fullPageUrl(source, path, currentBack), "_blank", "noopener");' in js assert "openEl.href = fullPageUrl(source, path, currentBack);" in js assert "fullPageUrl(source, path)" not in js, "a dropped third argument loses the back" assert "fullPageUrl(source, path, back)" not in js, ("call sites must read the stored variable") assert js.count("fullPageUrl(") == 3, "1 definition + 2 call sites" # ---------- phase 153 (task 03): the URL contract, behavior-pinned under node ---------- @pytest.mark.skipif(not HAVE_NODE, reason="node not available") def test_fullpageurl_null_back_is_byte_identical_to_the_absent_and_pre_phase() -> None: """The URL contract's load-bearing byte-identity, executed under node (the real function, not a re-implementation): * ``back = null`` and an ABSENT third argument produce the same string, and that string is byte-identical to the EXACT pre-phase string (no ``&back=`` anywhere — the Sources path must never gain the param); * ``back = "/"`` (a chat open) appends exactly ``&back=%2F`` to that same pre-phase string (the encoding pinned byte-for-byte). A refactor that bakes a ``back=`` into the base, reorders the param, or re-encodes the values breaks one of the exact strings.""" fn = _fn(_js(), "fullPageUrl") out = _run_node( fn + "\nconsole.log(fullPageUrl('H omelab', 'a/b.md'));" + "\nconsole.log(fullPageUrl('H omelab', 'a/b.md', null));" + "\nconsole.log(fullPageUrl('H omelab', 'a/b.md', '/'));" ) absent, null_back, chat = out.splitlines() pre_phase = "/document.html?source=H%20omelab&path=a%2Fb.md" assert null_back == absent, "back = null must be byte-identical to an absent back" assert absent == pre_phase, ( "a null/absent back must keep the URL byte-identical to pre-phase" ) assert "back=" not in absent, "no &back= may be present for a null/absent back" assert chat == pre_phase + "&back=%2F", ( "back = '/' must append exactly &back=%2F to the pre-phase string" ) @pytest.mark.skipif(not HAVE_NODE, reason="node not available") def test_open_document_modal_no_skeleton_fallback_carries_the_stored_back() -> None: """The no-skeleton ``window.open`` fallback, executed under node (the real ``openDocumentModal`` with the module state stubbed): a chat open (``back = "/"``) lands in the URL exactly (``%2F``), and a Sources-style open (``back`` omitted — the 3-arg call) keeps the URL byte-identical (no ``&back=`` at all). The fallback is the ``window.open(url, "_blank", "noopener")`` shape, and the early ``return`` means neither open touches the history.""" fn = _fn(_js(), "openDocumentModal") url_fn = _fn(_js(), "fullPageUrl") script = ( "let currentBack = null;\n" "const modalEl = null, titleEl = null, metaEl = null, contentEl = null, openEl = null;\n" "const opened = [];\n" "globalThis.window = { open: (u, t, f) => opened.push([u, t, f]) };\n" + url_fn + "\n" + fn + "\nopenDocumentModal('H omelab', 'a/b c.md', null, '/');\n" + "\nopenDocumentModal('S2', 'p2.md', null);\n" + "\nconsole.log(JSON.stringify(opened));\n" ) out = json.loads(_run_node(script)) assert out == [ [ "/document.html?source=H%20omelab&path=a%2Fb%20c.md&back=%2F", "_blank", "noopener", ], # the omitted back (the Sources table's 3-arg call) stays byte-identical ["/document.html?source=S2&path=p2.md", "_blank", "noopener"], ] @pytest.mark.skipif(not HAVE_NODE, reason="node not available") def test_open_document_modal_full_page_anchor_carries_the_stored_back() -> None: """The ``#doc-modal-open`` ("Full page") anchor on a skeleton open, executed under node (the real ``openDocumentModal`` with the modal elements + history + fetch chain stubbed): a chat open (``back = "/"``) sets the anchor's ``href`` to the pre-phase string + exactly ``&back=%2F``; a follow-up Sources-style open (``back`` omitted — the defensive re-open-while-open branch, which updates the SAME anchor) resets it to the byte-identical no-param string. The open's first push is a single state-only entry (the phase-144 contract stands); the defensive re-open replaces in place (no second push).""" fn = _fn(_js(), "openDocumentModal") url_fn = _fn(_js(), "fullPageUrl") script = ( "let currentBack = null;\n" "let triggerEl = null;\n" "let fetchSeq = 0;\n" "let entry = null;\n" "let lastPrev = null;\n" "let closingViaBack = false;\n" "const modalEl = { hidden: true };\n" "const titleEl = {}, metaEl = { replaceChildren() {} };\n" "const contentEl = { replaceChildren() {}, focus() {} };\n" "const anchor = { href: '', hidden: true };\n" "const openEl = anchor;\n" "const hist = [];\n" "globalThis.history = { state: null," " pushState(s, t) { hist.push(['push', s.modal === 'doc', s.source, s.path, t]); }," " replaceState(s, t) { hist.push(['replace', s.modal === 'doc', s.source, s.path]); } };\n" "globalThis.document = { addEventListener() {}, removeEventListener() {} };\n" "globalThis.fetch = () => Promise.resolve({ ok: true, json: async () => ({}) });\n" "function onModalKeydown() {}\n" "function showLoading() {}\n" "function setDesc() {}\n" "function renderNotFound() {}\n" "function wireCopyPath() {}\n" "function renderDocument() {}\n" 'function contentUrl(s, p) { return ""; }\n' + url_fn + "\n" + fn + "\nopenDocumentModal('H omelab', 'a/b c.md', null, '/');\n" + "\nconst chatHref = anchor.href;\n" + "\nopenDocumentModal('S2', 'p2.md', null);\n" + "\nconsole.log(JSON.stringify({ chatHref, sourcesHref: anchor.href," + " visible: !modalEl.hidden, hist }));\n" ) out = json.loads(_run_node(script)) pre_phase = "/document.html?source=H%20omelab&path=a%2Fb%20c.md" assert out["chatHref"] == pre_phase + "&back=%2F", ( "a chat open must set the Full-page anchor to the pre-phase string + exactly &back=%2F" ) assert out["sourcesHref"] == "/document.html?source=S2&path=p2.md", ( "a Sources-style open (back omitted) must reset the anchor to the byte-" "identical no-param string" ) assert out["visible"] is True # phase 144 stands: one state-only push, then the defensive # re-open replaces IN PLACE (no second push). assert out["hist"] == [ ["push", True, "H omelab", "a/b c.md", ""], ["replace", True, "S2", "p2.md"], ]