"""Validate the exact embedded graph-browser HTML, CSS, and JavaScript.""" from __future__ import annotations import os import shutil import subprocess import sys import tempfile from pathlib import Path from typing import cast from docforge.graph_projection import ( GraphViewRequestV1, build_graph_projection_package, build_graph_view_plan, ) from docforge.index import ProjectIndex from docforge.project import Project from docforge.rendering import RenderService from docforge_renderers.graph import ( PORTABLE_GRAPH_CSS, PORTABLE_GRAPH_JAVASCRIPT, PortableGraphHtmlRenderer, ) ROOT = Path(__file__).resolve().parents[1] ASSET_ROOT = ROOT / "src" / "docforge" / "assets" def _graph_browser_html() -> str: return (ASSET_ROOT / "graph.html").read_text(encoding="utf-8") def _rendered_manual_html() -> str: with tempfile.TemporaryDirectory() as directory: fixture = ROOT / "tests" / "fixtures" / "alpha" project_root = Path(directory) / "alpha" shutil.copytree(fixture, project_root) project = Project.open(project_root) ProjectIndex(project).build() result = RenderService(project).render("manual") output = result.get("output") if not isinstance(output, dict): raise RuntimeError("Manual render did not return output identity") relative_path = cast(dict[str, object], output).get("path") if not isinstance(relative_path, str): raise RuntimeError("Manual render did not return an output path") return (project_root / relative_path).read_text(encoding="utf-8") def _portable_graph_html() -> str: project = Project.open(ROOT / "tests" / "fixtures" / "alpha") plan = build_graph_view_plan( project.load(), GraphViewRequestV1( view_id="web-quality", title="Portable graph quality fixture", root_node_id="guide.workflow", depth=2, max_nodes=20, max_edges=40, max_work=1_000, ), False, ) package = build_graph_projection_package( plan, renderer_id=PortableGraphHtmlRenderer.renderer_id, renderer_version=PortableGraphHtmlRenderer.renderer_version, max_output_bytes=1_000_000, ) return PortableGraphHtmlRenderer().render(package).artifacts[0].content.decode("utf-8") def _tool(name: str) -> str: suffix = ".cmd" if os.name == "nt" else "" path = ROOT / "node_modules" / ".bin" / f"{name}{suffix}" if not path.is_file(): raise RuntimeError(f"Missing web validator {name}; run npm ci") return str(path) def _run(label: str, command: list[str], content: str) -> bool: print(f"Checking {label}...") result = subprocess.run( command, cwd=ROOT, input=content, text=True, check=False, ) return result.returncode == 0 def main() -> int: html = _graph_browser_html() checks = ( ( "HTML", [ _tool("html-validate"), "--stdin", "--stdin-filename=graph-browser.html", "--max-warnings=0", ], html, ), ( "rendered manual HTML", [ _tool("html-validate"), "--stdin", "--stdin-filename=rendered-manual.html", "--max-warnings=0", ], _rendered_manual_html(), ), ( "portable graph HTML", [ _tool("html-validate"), "--stdin", "--stdin-filename=portable-graph.html", "--max-warnings=0", ], _portable_graph_html(), ), ( "CSS", [ _tool("stylelint"), "--stdin", "--stdin-filename=graph-browser.css", "--max-warnings=0", ], (ASSET_ROOT / "graph.css").read_text(encoding="utf-8"), ), ( "JavaScript", [ _tool("eslint"), "--stdin", "--stdin-filename=graph-browser.js", "--max-warnings=0", ], (ASSET_ROOT / "graph.js").read_text(encoding="utf-8"), ), ( "portable graph CSS", [ _tool("stylelint"), "--stdin", "--stdin-filename=portable-graph.css", "--max-warnings=0", ], PORTABLE_GRAPH_CSS, ), ( "portable graph JavaScript", [ _tool("eslint"), "--stdin", "--stdin-filename=portable-graph.js", "--max-warnings=0", ], PORTABLE_GRAPH_JAVASCRIPT, ), ) results = [_run(label, command, content) for label, command, content in checks] return 0 if all(results) else 1 if __name__ == "__main__": sys.exit(main())