htmx demos working: activation, fetch, swap, OOB filtering, test runner page

- htmx-boot-subtree! wired into process-elements for auto-activation
- Fixed cond compilation bug in hx-verb-info (Clojure-style flat cond)
- Platform io-fetch upgraded: method/body/headers support, full response dict
- Replaced perform IO ops with browser primitives (set-timeout, browser-confirm, etc)
- SX→HTML rendering in hx-do-swap with OOB section filtering
- hx-collect-params: collects input name/value for all methods
- Handler naming: ex-{slug} convention, removed perform IO dependencies
- Test runner page at (test.(applications.(htmx))) with iframe-based runner
- Header "test" link on every page linking to test URL
- Page file restructure: 285 files moved to URL-matching paths (a/b/c/index.sx)
- page-functions.sx: ~100 component name references updated
- _test added to skip_dirs, test- file prefix convention for test files

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
2026-04-15 11:56:15 +00:00
parent 4f02f82f4e
commit 4aa49e42e8
16 changed files with 3201 additions and 1562 deletions

View File

@@ -0,0 +1,176 @@
;; Test runner page for the htmx demo
;; URL: /sx/(test.(applications.(htmx)))
(defcomp
()
(~docs/page
:title "Test: htmx demos"
(p
(~tw :tokens "text-stone-500 mb-4")
"Running tests against "
(a
:href "/sx/(applications.(htmx))"
(~tw :tokens "text-violet-600 underline")
"/sx/(applications.(htmx))"))
(div
(~tw :tokens "flex items-center gap-4 mb-4")
(button
:id "run-btn"
(~tw
:tokens "px-4 py-2 bg-violet-600 text-white rounded hover:bg-violet-700 transition-colors")
"Run All Tests")
(span :id "test-status" (~tw :tokens "text-sm text-stone-500") "Ready"))
(div :id "test-summary" (~tw :tokens "mb-4"))
(div
:id "test-list"
(~tw :tokens "space-y-2 mb-6")
(details
:class "sx-test-item"
:data-test "click-to-load"
(~tw :tokens "border border-stone-200 rounded-lg overflow-hidden")
(summary
(~tw
:tokens "px-4 py-2 cursor-pointer hover:bg-stone-50 flex items-center gap-2")
(span :data-role "test-icon" (~tw :tokens "font-bold") "○")
(span (~tw :tokens "font-medium") "click-to-load")
(span
(~tw :tokens "text-sm text-stone-400 ml-auto")
"Click button, verify content loads"))
(div
(~tw :tokens "px-4 py-3 bg-stone-50 border-t border-stone-200")
(pre
(~tw
:tokens "text-xs font-mono whitespace-pre-wrap text-stone-600")
"(deftest click-to-load\n :runner :playwright\n :url \"/sx/(applications.(htmx))\"\n (click \"button[hx-get]\")\n (wait 2000)\n (assert-text \"#click-result\" :contains \"Content loaded!\"))")))
(details
:class "sx-test-item"
:data-test "click-no-oob-leak"
(~tw :tokens "border border-stone-200 rounded-lg overflow-hidden")
(summary
(~tw
:tokens "px-4 py-2 cursor-pointer hover:bg-stone-50 flex items-center gap-2")
(span :data-role "test-icon" (~tw :tokens "font-bold") "○")
(span (~tw :tokens "font-medium") "click-no-oob-leak")
(span
(~tw :tokens "text-sm text-stone-400 ml-auto")
"OOB swap sections filtered from content"))
(div
(~tw :tokens "px-4 py-3 bg-stone-50 border-t border-stone-200")
(pre
(~tw
:tokens "text-xs font-mono whitespace-pre-wrap text-stone-600")
"(deftest click-no-oob-leak\n :runner :playwright\n :url \"/sx/(applications.(htmx))\"\n (click \"button[hx-get]\")\n (wait 2000)\n (assert-text \"#click-result\" :not-contains \"defcomp\"))")))
(details
:class "sx-test-item"
:data-test "search-debounce"
(~tw :tokens "border border-stone-200 rounded-lg overflow-hidden")
(summary
(~tw
:tokens "px-4 py-2 cursor-pointer hover:bg-stone-50 flex items-center gap-2")
(span :data-role "test-icon" (~tw :tokens "font-bold") "○")
(span (~tw :tokens "font-medium") "search-debounce")
(span
(~tw :tokens "text-sm text-stone-400 ml-auto")
"Type in search, results appear after debounce"))
(div
(~tw :tokens "px-4 py-3 bg-stone-50 border-t border-stone-200")
(pre
(~tw
:tokens "text-xs font-mono whitespace-pre-wrap text-stone-600")
"(deftest search-debounce\n :runner :playwright\n :url \"/sx/(applications.(htmx))\"\n (fill \"input[hx-get]\" \"hx-get\")\n (wait 1500)\n (assert-text \"#search-results\" :contains \"GET request\"))")))
(details
:class "sx-test-item"
:data-test "search-no-results"
(~tw :tokens "border border-stone-200 rounded-lg overflow-hidden")
(summary
(~tw
:tokens "px-4 py-2 cursor-pointer hover:bg-stone-50 flex items-center gap-2")
(span :data-role "test-icon" (~tw :tokens "font-bold") "○")
(span (~tw :tokens "font-medium") "search-no-results")
(span
(~tw :tokens "text-sm text-stone-400 ml-auto")
"Non-matching query shows no results"))
(div
(~tw :tokens "px-4 py-3 bg-stone-50 border-t border-stone-200")
(pre
(~tw
:tokens "text-xs font-mono whitespace-pre-wrap text-stone-600")
"(deftest search-no-results\n :runner :playwright\n :url \"/sx/(applications.(htmx))\"\n (fill \"input[hx-get]\" \"xyznonexistent\")\n (wait 1500)\n (assert-text \"#search-results\" :contains \"No results\"))")))
(details
:class "sx-test-item"
:data-test "tab-overview"
(~tw :tokens "border border-stone-200 rounded-lg overflow-hidden")
(summary
(~tw
:tokens "px-4 py-2 cursor-pointer hover:bg-stone-50 flex items-center gap-2")
(span :data-role "test-icon" (~tw :tokens "font-bold") "○")
(span (~tw :tokens "font-medium") "tab-overview")
(span
(~tw :tokens "text-sm text-stone-400 ml-auto")
"Click overview tab, verify content"))
(div
(~tw :tokens "px-4 py-3 bg-stone-50 border-t border-stone-200")
(pre
(~tw
:tokens "text-xs font-mono whitespace-pre-wrap text-stone-600")
"(deftest tab-overview\n :runner :playwright\n :url \"/sx/(applications.(htmx))\"\n (click \"button[hx-get*='tab=overview']\")\n (wait 2000)\n (assert-text \"#htmx-tab-content\" :contains \"htmx gives you access\"))")))
(details
:class "sx-test-item"
:data-test "tab-features"
(~tw :tokens "border border-stone-200 rounded-lg overflow-hidden")
(summary
(~tw
:tokens "px-4 py-2 cursor-pointer hover:bg-stone-50 flex items-center gap-2")
(span :data-role "test-icon" (~tw :tokens "font-bold") "○")
(span (~tw :tokens "font-medium") "tab-features")
(span
(~tw :tokens "text-sm text-stone-400 ml-auto")
"Click features tab, verify list"))
(div
(~tw :tokens "px-4 py-3 bg-stone-50 border-t border-stone-200")
(pre
(~tw
:tokens "text-xs font-mono whitespace-pre-wrap text-stone-600")
"(deftest tab-features\n :runner :playwright\n :url \"/sx/(applications.(htmx))\"\n (click \"button[hx-get*='tab=features']\")\n (wait 2000)\n (assert-text \"#htmx-tab-content\" :contains \"Any element\"))")))
(details
:class "sx-test-item"
:data-test "append-item"
(~tw :tokens "border border-stone-200 rounded-lg overflow-hidden")
(summary
(~tw
:tokens "px-4 py-2 cursor-pointer hover:bg-stone-50 flex items-center gap-2")
(span :data-role "test-icon" (~tw :tokens "font-bold") "○")
(span (~tw :tokens "font-medium") "append-item")
(span
(~tw :tokens "text-sm text-stone-400 ml-auto")
"POST appends new item to list"))
(div
(~tw :tokens "px-4 py-3 bg-stone-50 border-t border-stone-200")
(pre
(~tw
:tokens "text-xs font-mono whitespace-pre-wrap text-stone-600")
"(deftest append-item\n :runner :playwright\n :url \"/sx/(applications.(htmx))\"\n (click \"button[hx-post*='api.append']\")\n (wait 2000)\n (assert-count \"#item-list > *\" :gte 1))")))
(details
:class "sx-test-item"
:data-test "form-submit"
(~tw :tokens "border border-stone-200 rounded-lg overflow-hidden")
(summary
(~tw
:tokens "px-4 py-2 cursor-pointer hover:bg-stone-50 flex items-center gap-2")
(span :data-role "test-icon" (~tw :tokens "font-bold") "○")
(span (~tw :tokens "font-medium") "form-submit")
(span
(~tw :tokens "text-sm text-stone-400 ml-auto")
"POST form data, verify name in response"))
(div
(~tw :tokens "px-4 py-3 bg-stone-50 border-t border-stone-200")
(pre
(~tw
:tokens "text-xs font-mono whitespace-pre-wrap text-stone-600")
"(deftest form-submit\n :runner :playwright\n :url \"/sx/(applications.(htmx))\"\n (fill \"form[hx-post] input[name='name']\" \"Alice\")\n (fill \"form[hx-post] input[name='email']\" \"alice@test.com\")\n (click \"form[hx-post] button[type='submit']\")\n (wait 2000)\n (assert-text \"#form-result\" :contains \"Alice\"))"))))
(iframe
:id "test-iframe"
:src "/sx/(applications.(htmx))"
(~tw :tokens "w-full border border-stone-200 rounded-lg")
:style "height:600px")
(script :src "/static/scripts/sx-test-runner.js?v=7")))