Public Quart microservice that runs pytest against shared/tests/ and shared/sexp/tests/, serving an HTMX-powered sexp-rendered dashboard with pass/fail/running status, auto-refresh polling, and re-run button. No database — results stored in memory. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
89 lines
5.0 KiB
Plaintext
89 lines
5.0 KiB
Plaintext
;; Test dashboard components
|
|
|
|
(defcomp ~test-status-badge (&key status)
|
|
(span :class (str "inline-flex items-center rounded-full border px-3 py-1 text-sm font-medium "
|
|
(if (= status "running") "border-amber-300 bg-amber-50 text-amber-700 animate-pulse"
|
|
(if (= status "passed") "border-emerald-300 bg-emerald-50 text-emerald-700"
|
|
(if (= status "failed") "border-rose-300 bg-rose-50 text-rose-700"
|
|
"border-stone-300 bg-stone-50 text-stone-700"))))
|
|
status))
|
|
|
|
(defcomp ~test-run-button (&key running csrf)
|
|
(form :method "POST" :action "/run" :class "inline"
|
|
(input :type "hidden" :name "csrf_token" :value csrf)
|
|
(button :type "submit"
|
|
:class (str "rounded bg-stone-800 px-4 py-2 text-sm font-medium text-white hover:bg-stone-700 "
|
|
"disabled:opacity-50 disabled:cursor-not-allowed transition-colors")
|
|
:disabled (if running "true" nil)
|
|
(if running "Running..." "Run Tests"))))
|
|
|
|
(defcomp ~test-summary (&key status passed failed errors skipped total duration last-run running csrf)
|
|
(div :class "space-y-4"
|
|
(div :class "flex items-center justify-between flex-wrap gap-3"
|
|
(div :class "flex items-center gap-3"
|
|
(h2 :class "text-lg font-semibold text-stone-800" "Test Results")
|
|
(when status (~test-status-badge :status status)))
|
|
(~test-run-button :running running :csrf csrf))
|
|
(when status
|
|
(div :class "grid grid-cols-2 sm:grid-cols-3 md:grid-cols-6 gap-3"
|
|
(div :class "rounded border border-stone-200 bg-white p-3 text-center"
|
|
(div :class "text-2xl font-bold text-stone-800" total)
|
|
(div :class "text-xs text-stone-500" "Total"))
|
|
(div :class "rounded border border-emerald-200 bg-emerald-50 p-3 text-center"
|
|
(div :class "text-2xl font-bold text-emerald-700" passed)
|
|
(div :class "text-xs text-emerald-600" "Passed"))
|
|
(div :class "rounded border border-rose-200 bg-rose-50 p-3 text-center"
|
|
(div :class "text-2xl font-bold text-rose-700" failed)
|
|
(div :class "text-xs text-rose-600" "Failed"))
|
|
(div :class "rounded border border-orange-200 bg-orange-50 p-3 text-center"
|
|
(div :class "text-2xl font-bold text-orange-700" errors)
|
|
(div :class "text-xs text-orange-600" "Errors"))
|
|
(div :class "rounded border border-sky-200 bg-sky-50 p-3 text-center"
|
|
(div :class "text-2xl font-bold text-sky-700" skipped)
|
|
(div :class "text-xs text-sky-600" "Skipped"))
|
|
(div :class "rounded border border-stone-200 bg-white p-3 text-center"
|
|
(div :class "text-2xl font-bold text-stone-800" (str duration "s"))
|
|
(div :class "text-xs text-stone-500" "Duration")))
|
|
(div :class "text-xs text-stone-400" (str "Last run: " last-run)))))
|
|
|
|
(defcomp ~test-row (&key nodeid outcome duration longrepr)
|
|
(tr :class (str "border-b border-stone-100 "
|
|
(if (= outcome "passed") "bg-white"
|
|
(if (= outcome "failed") "bg-rose-50"
|
|
(if (= outcome "skipped") "bg-sky-50"
|
|
"bg-orange-50"))))
|
|
(td :class "px-3 py-2 text-xs font-mono text-stone-700 max-w-0 truncate" :title nodeid nodeid)
|
|
(td :class "px-3 py-2 text-center"
|
|
(span :class (str "inline-flex items-center rounded-full border px-2 py-0.5 text-[11px] font-medium "
|
|
(if (= outcome "passed") "border-emerald-300 bg-emerald-50 text-emerald-700"
|
|
(if (= outcome "failed") "border-rose-300 bg-rose-50 text-rose-700"
|
|
(if (= outcome "skipped") "border-sky-300 bg-sky-50 text-sky-700"
|
|
"border-orange-300 bg-orange-50 text-orange-700"))))
|
|
outcome))
|
|
(td :class "px-3 py-2 text-right text-xs text-stone-500 tabular-nums" (str duration "s"))
|
|
(td :class "px-3 py-2 text-xs text-rose-600 font-mono max-w-xs truncate" :title longrepr
|
|
(when longrepr longrepr))))
|
|
|
|
(defcomp ~test-results-table (&key rows-html has-failures)
|
|
(div :class "overflow-x-auto rounded border border-stone-200 bg-white"
|
|
(table :class "w-full text-left"
|
|
(thead
|
|
(tr :class "border-b border-stone-200 bg-stone-50"
|
|
(th :class "px-3 py-2 text-xs font-medium text-stone-600" "Test")
|
|
(th :class "px-3 py-2 text-xs font-medium text-stone-600 text-center w-24" "Status")
|
|
(th :class "px-3 py-2 text-xs font-medium text-stone-600 text-right w-20" "Time")
|
|
(th :class "px-3 py-2 text-xs font-medium text-stone-600 w-48" "Error")))
|
|
(tbody rows-html))))
|
|
|
|
(defcomp ~test-running-indicator ()
|
|
(div :class "flex items-center justify-center py-12 text-stone-500"
|
|
(div :class "flex items-center gap-3"
|
|
(div :class "animate-spin h-6 w-6 border-2 border-stone-300 border-t-stone-600 rounded-full")
|
|
(span :class "text-sm" "Running tests..."))))
|
|
|
|
(defcomp ~test-no-results ()
|
|
(div :class "flex items-center justify-center py-12 text-stone-400"
|
|
(div :class "text-center"
|
|
(div :class "text-4xl mb-2" "?")
|
|
(div :class "text-sm" "No test results yet. Click \"Run Tests\" to start."))))
|