"""Regression coverage for issue #4793 DOM recycle hardening.""" import json import shutil import subprocess import tempfile from pathlib import Path import pytest REPO_ROOT = Path(__file__).parent.parent.resolve() UI_JS_PATH = REPO_ROOT / "static" / "ui.js" NODE = shutil.which("node") pytestmark = pytest.mark.skipif(NODE is None, reason="node not on PATH") def _run_node(source: str) -> dict: with tempfile.NamedTemporaryFile( "w", suffix=".cjs", encoding="utf-8", dir=REPO_ROOT, delete=False ) as script: script.write(source) script_path = Path(script.name) try: result = subprocess.run( [NODE, str(script_path)], cwd=str(REPO_ROOT), capture_output=True, text=True, timeout=30, ) finally: script_path.unlink(missing_ok=True) if result.returncode != 0: raise RuntimeError(result.stderr) return json.loads(result.stdout.strip()) def _js_prefix() -> str: return f""" const fs = require('fs'); const src = fs.readFileSync({json.dumps(str(UI_JS_PATH))}, 'utf8'); function extractBetween(startMarker, endMarker, startFrom = 0) {{ const start = src.indexOf(startMarker, startFrom); if (start < 0) throw new Error(startMarker + ' not found'); const end = src.indexOf(endMarker, start); if (end < 0) throw new Error(endMarker + ' not found'); return src.slice(start, end); }} function extractBody(startMarker, endMarker, startFrom = 0) {{ const start = src.indexOf(startMarker, startFrom); if (start < 0) throw new Error(startMarker + ' not found'); const end = src.indexOf(endMarker, start); if (end < 0) throw new Error(endMarker + ' not found'); return src.slice(start + startMarker.length, end); }} """ def test_recycled_assistant_turn_reuse_clears_live_anchor_attrs(): script = _js_prefix() + """ const recycleChunk = extractBetween("if(!currentAssistantTurn){", "const seg=document.createElement('div');"); const resetListChunk = extractBetween("const _recycleResetAttrs=[", "let _scrollbarDragActive=false;") .replace("const _recycleResetAttrs=", "var _recycleResetAttrs="); const removedAttrs = []; const role = { replacement: null, set outerHTML(value) { this.replacement = value; }, get outerHTML() { return this.replacement; }, }; const recycled = { dataset: { recycleKey: 'old-key', role: 'old-role', sessionId: 'old-session' }, classList: { contains(name) { return name === 'assistant-turn'; } }, removeAttribute(name) { removedAttrs.push(name); }, querySelector(selector) { return selector === '.msg-role.assistant' ? role : null; }, }; eval(resetListChunk); const _recycleStash = new Map([[7, recycled]]); const _msgNodeRecycleEnabled = true; let currentAssistantTurn = null; const rawIdx = 7; const blocks = { innerHTML: 'keep me' }; const _assistantTurnBlocks = () => blocks; const inner = { appendChild() {} }; const tsTitle = 'Turn title'; const m = { _turnTps: 42 }; const S = { session: { session_id: 'sess-1' } }; const _assistantRoleHtml = (title, tps) => `role:${title}:${tps}`; const isTpsDisplayEnabled = () => true; const _formatTurnTps = value => `tps:${value}`; const latestRenderedAssistantRawIdx = rawIdx; function _setLatestAssistantTurnLandmark() {} eval(recycleChunk); console.log(JSON.stringify({ removedAttrs, resetAttrs: _recycleResetAttrs, blocksInnerHTML: blocks.innerHTML, roleHtml: role.replacement, currentAssistantTurnIsRecycled: currentAssistantTurn === recycled, dataset: { role: currentAssistantTurn.dataset.role, sessionId: currentAssistantTurn.dataset.sessionId, recycleKey: String(currentAssistantTurn.dataset.recycleKey), }, })); """ result = _run_node(script) expected_attrs = { "data-transparent-turn-collapsed", "data-transparent-turn-toggle-bound", "data-anchor-scene-live-owner", "data-anchor-stream-id", "data-latest-assistant-response", "role", "aria-label", "data-live-assistant-turn", } assert set(result["resetAttrs"]) == expected_attrs assert len(result["resetAttrs"]) == len(expected_attrs) assert set(result["removedAttrs"]) == expected_attrs assert len(result["removedAttrs"]) == len(expected_attrs) assert result["currentAssistantTurnIsRecycled"] is True assert result["dataset"] == { "role": "assistant", "sessionId": "sess-1", "recycleKey": "7", } def test_recycled_assistant_turn_reuse_keeps_block_clear_and_role_refresh(): script = _js_prefix() + """ const recycleChunk = extractBetween("if(!currentAssistantTurn){", "const seg=document.createElement('div');"); const resetListChunk = extractBetween("const _recycleResetAttrs=[", "let _scrollbarDragActive=false;") .replace("const _recycleResetAttrs=", "var _recycleResetAttrs="); const removedAttrs = []; const role = { replacement: null, set outerHTML(value) { this.replacement = value; }, get outerHTML() { return this.replacement; }, }; const recycled = { dataset: { recycleKey: 'old-key' }, classList: { contains(name) { return name === 'assistant-turn'; } }, removeAttribute(name) { removedAttrs.push(name); }, querySelector(selector) { return selector === '.msg-role.assistant' ? role : null; }, }; eval(resetListChunk); const _recycleStash = new Map([[7, recycled]]); const _msgNodeRecycleEnabled = true; let currentAssistantTurn = null; const rawIdx = 7; const blocks = { innerHTML: 'keep me' }; const _assistantTurnBlocks = () => blocks; const inner = { appendChild() {} }; const tsTitle = 'Turn title'; const m = { _turnTps: 42 }; const S = { session: { session_id: 'sess-1' } }; const _assistantRoleHtml = (title, tps) => `role:${title}:${tps}`; const isTpsDisplayEnabled = () => true; const _formatTurnTps = value => `tps:${value}`; const latestRenderedAssistantRawIdx = rawIdx; function _setLatestAssistantTurnLandmark() {} eval(recycleChunk); console.log(JSON.stringify({ blocksInnerHTML: blocks.innerHTML, roleHtml: role.replacement, currentAssistantTurnIsRecycled: currentAssistantTurn === recycled, dataset: { role: currentAssistantTurn.dataset.role, sessionId: currentAssistantTurn.dataset.sessionId, recycleKey: String(currentAssistantTurn.dataset.recycleKey), }, })); """ result = _run_node(script) assert result["blocksInnerHTML"] == "" assert result["roleHtml"] == "role:Turn title:tps:42" assert result["currentAssistantTurnIsRecycled"] is True assert result["dataset"] == { "role": "assistant", "sessionId": "sess-1", "recycleKey": "7", } def test_messages_scrollbar_drag_ignores_bubbled_child_pointerdown(): script = _js_prefix() + """ const pointerdownBody = extractBody("el.addEventListener('pointerdown',(e)=>{", "},{passive:true});"); let _scrollbarDragActive = false; const el = { clientWidth: 120 }; eval(`function pointerdownHandler(e){${pointerdownBody}}`); pointerdownHandler({ target: { clientWidth: 120 }, offsetX: 999 }); const bubbledArmed = _scrollbarDragActive; _scrollbarDragActive = false; pointerdownHandler({ target: el, offsetX: 999 }); console.log(JSON.stringify({ bubbledArmed, directArmed: _scrollbarDragActive, })); """ result = _run_node(script) assert result["bubbledArmed"] is False assert result["directArmed"] is True def test_messages_scrollbar_drag_still_arms_for_direct_messages_press_and_rerenders_on_pointerup(): script = _js_prefix() + """ const pointerdownBody = extractBody("el.addEventListener('pointerdown',(e)=>{", "},{passive:true});"); const pointerupBody = extractBody("window.addEventListener('pointerup',()=>{", "},{passive:true});"); const rerenders = []; let _scrollbarDragActive = false; const el = { clientWidth: 120 }; function _scheduleMessageVirtualizedRender(force) { rerenders.push(force); } eval(`function pointerdownHandler(e){${pointerdownBody}}`); eval(`function pointerupHandler(){${pointerupBody}}`); pointerdownHandler({ target: el, offsetX: 999 }); const armed = _scrollbarDragActive; pointerupHandler(); console.log(JSON.stringify({ armed, afterPointerup: _scrollbarDragActive, rerenders, })); """ result = _run_node(script) assert result["armed"] is True assert result["afterPointerup"] is False assert result["rerenders"] == [True]