From 33a0ca6722b4ba73951bd43f04de1a22cf0b54dd Mon Sep 17 00:00:00 2001 From: Kat <00-kat@proton.me> Date: Sat, 14 Mar 2026 09:55:21 +1100 Subject: [PATCH] cmd/pkgserver: add DOM reporter for JS tests --- cmd/pkgserver/ui.go | 8 ++++++ cmd/pkgserver/ui/static/test.scss | 24 ++++++++++++++++ cmd/pkgserver/ui/static/test.ts | 47 +++++++++++++++++++++++++++++-- cmd/pkgserver/ui/test.html | 24 ++++++++++++++++ cmd/pkgserver/ui_full.go | 2 +- 5 files changed, 102 insertions(+), 3 deletions(-) create mode 100644 cmd/pkgserver/ui/static/test.scss create mode 100644 cmd/pkgserver/ui/test.html diff --git a/cmd/pkgserver/ui.go b/cmd/pkgserver/ui.go index e4c42832..5b1c5106 100644 --- a/cmd/pkgserver/ui.go +++ b/cmd/pkgserver/ui.go @@ -25,14 +25,22 @@ func serveStaticContent(w http.ResponseWriter, r *http.Request) { http.ServeFileFS(w, r, content, "ui/static/favicon.ico") case "/static/index.js": http.ServeFileFS(w, r, content, "ui/static/index.js") + case "/static/test.js": + http.ServeFileFS(w, r, content, "ui/static/test.js") + case "/static/test.css": + http.ServeFileFS(w, r, content, "ui/static/test.css") default: http.NotFound(w, r) } } +func serveTester(w http.ResponseWriter, r *http.Request) { + http.ServeFileFS(w, r, content, "ui/test.html") +} func uiRoutes(mux *http.ServeMux) { mux.HandleFunc("GET /{$}", serveWebUI) mux.HandleFunc("GET /favicon.ico", serveStaticContent) mux.HandleFunc("GET /static/", serveStaticContent) + mux.HandleFunc("GET /test.html", serveTester) } diff --git a/cmd/pkgserver/ui/static/test.scss b/cmd/pkgserver/ui/static/test.scss new file mode 100644 index 00000000..28d7044d --- /dev/null +++ b/cmd/pkgserver/ui/static/test.scss @@ -0,0 +1,24 @@ +.root { + margin: 1rem 0; +} + +details.test-node { + margin-left: 1rem; + padding: 0.2rem 0.5rem; + border-left: 2px dashed black; + > summary { + cursor: pointer; + } + &.failure > summary::marker { + color: red; + } +} + +p.test-desc { + margin: 0 0 0 1rem; + padding: 2px 0; +} + +.italic { + font-style: italic; +} diff --git a/cmd/pkgserver/ui/static/test.ts b/cmd/pkgserver/ui/static/test.ts index cf1218b1..3abb177a 100644 --- a/cmd/pkgserver/ui/static/test.ts +++ b/cmd/pkgserver/ui/static/test.ts @@ -74,7 +74,50 @@ export class StreamReporter implements Reporter { } } -const r = new StreamReporter({ writeln: console.log }, true); +export class DOMReporter implements Reporter { + update(path: string[], result: TestResult) { + const counter = document.getElementById(result.success ? 'successes' : 'failures'); + counter.innerText = (Number(counter.innerText) + 1).toString(); + let parent = document.getElementById('root'); + for (const node of path) { + let child = null; + outer: for (const d of parent.children) { + for (const s of d.children) { + if (!(s instanceof HTMLElement)) continue; + if (s.tagName !== 'SUMMARY' || s.innerText !== node) continue; + child = d; + break outer; + } + } + if (child === null) { + child = document.createElement('details'); + child.className = 'test-node'; + const summary = document.createElement('summary'); + summary.appendChild(document.createTextNode(node)); + child.appendChild(summary); + parent.appendChild(child); + } + if (!result.success) { + child.open = true; + child.classList.add('failure'); + } + parent = child; + } + const p = document.createElement('p'); + p.classList.add('test-desc'); + if (result.output) { + const code = document.createElement('code'); + code.appendChild(document.createTextNode(result.output)); + p.appendChild(code); + } else { + p.classList.add('italic'); + p.appendChild(document.createTextNode('No output.')); + } + parent.appendChild(p); + } +} + +let r = typeof document !== 'undefined' ? new DOMReporter() : new StreamReporter({ writeln: console.log }); r.update(['alien', 'can walk'], { success: false, output: 'assertion failed' }); r.update(['alien', 'can speak'], { success: false, output: 'Uncaught ReferenceError: larynx is not defined' }); r.update(['alien', 'sleep'], { success: true, output: '' }); @@ -82,4 +125,4 @@ r.update(['Tetromino', 'generate', 'tessellates'], { success: false, output: 'as r.update(['Tetromino', 'solve', 'works'], { success: true, output: '' }); r.update(['discombobulate'], { success: false, output: 'hippopotamonstrosesquippedaliophobia' }); r.update(['recombobulate'], { success: true, output: '' }); -r.display(); +if (r instanceof StreamReporter) r.display(); diff --git a/cmd/pkgserver/ui/test.html b/cmd/pkgserver/ui/test.html new file mode 100644 index 00000000..20054688 --- /dev/null +++ b/cmd/pkgserver/ui/test.html @@ -0,0 +1,24 @@ + + +
+ + + + +