"""Execute the browser transition against a small graph/DOM harness.""" import shutil import subprocess import pytest from railiance_fabric.graph_explorer_ui import graph_explorer_page def test_graph_switch_replaces_data_and_retains_previous_graph_on_failure(tmp_path): if not shutil.which("node"): pytest.skip("Node.js is required for the UI transition test") page = graph_explorer_page() transition = page.split(" let graphRequest = 0;", 1)[1].split( " const handleFilterChange", 1 )[0] script = r''' const assert = require("node:assert/strict"); let graphMode = "full", includeResidualsParam = false, activeMode = "full"; let selected, selectedAnchor, selectedZoneId, focusSet, manualOverrides, filterRules; let editingRuleId, currentProfileId, allEdgeTypes; const layerColors = {}, allNodeTypes = [], nodeTypeLabels = {}; const collapsedZoneSnapshots = new Map(), zoneContainerState = new Map(), zoneSummaries = new Map(); const modeSelect = {}, profileSelect = {}, nodeTypeFilter = {}, edgeTypeFilter = {}; const popup = {style: {}}, profileSummary = {}; const controls = {}; const document = {getElementById(id) {return controls[id] ||= {style: {}};}}; let data = [{data: {id: "old"}}], urls = [], updated = 0; const cy = {elements: () => ({remove() {data = [];}}), add(elements) {data.push(...elements);}}; const renderChecklist = () => {}, refreshRuleBuilder = () => {}, renderRules = () => {}; const syncFilterSummaries = () => {}, applyFilters = () => {}, runLayout = () => {}; const updateUrlState = () => {updated++;}, showDetails = () => {}; const updateProfileControls = () => {}, updateProfileSummary = () => {}; const applyViewState = () => {}; let response = {ok: true, json: async () => ({elements: [{data: {id: "workplan:a"}}]})}; let fetch = async (url) => {urls.push(url); return response;}; let graphRequest = 0; ''' script += transition + r''' (async () => { await switchGraph("coordination", true); assert.equal(data[0].data.id, "workplan:a"); assert.equal(modeSelect.value, "coordination"); assert.equal(controls["include-residuals-wrap"].style.display, ""); assert.match(urls[0], /include_residuals=true/); response = {ok: false, status: 503}; await switchGraph("full"); assert.equal(data[0].data.id, "workplan:a"); assert.equal(modeSelect.value, "coordination"); assert.match(profileSummary.textContent, /503/); response = {ok: true, json: async () => ({elements: []})}; await switchGraph("by-access-zone"); assert.deepEqual(data, []); assert.equal(activeMode, "by-access-zone"); assert.equal(graphMode, "full"); assert.equal(controls["include-residuals-wrap"].style.display, "none"); let resolveOld; fetch = () => new Promise(resolve => {resolveOld = resolve;}); const oldRequest = switchGraph("coordination"); fetch = async () => ({ok: true, json: async () => ({elements: [{data: {id: "latest"}}]})}); await switchGraph("full"); resolveOld({ok: true, json: async () => ({elements: [{data: {id: "stale"}}]})}); await oldRequest; assert.equal(data[0].data.id, "latest"); assert.equal(updated, 3); })().catch(error => {console.error(error); process.exitCode = 1;}); ''' path = tmp_path / "switch.cjs" path.write_text(script) subprocess.run(["node", str(path)], check=True, capture_output=True, text=True)