host/tests: Phase 0 — relate→delete row as an SX engine test (no browser)

Port the relate-picker's relate-delete behaviour from Playwright into an SX
harness test that drives the real engine (web/engine.sx + web/orchestration.sx)
against the OCaml runner's in-memory mock DOM. Builds the candidate row, runs
process-elements to bind the form's submit, mocks fetch-request to return the
host's empty 200, fires submit, and asserts the row is deleted in place — the
full fetch→swap→DOM-mutation loop in pure SX.

Mock-DOM completeness (run_tests.ml): NodeList.item(i) so dom-query-all can
iterate querySelectorAll results, and a DOMParser mock so the empty-body
sx-swap=delete path (handle-html-response → parseFromString) works as in a
browser.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
2026-06-29 17:40:02 +00:00
parent b0c0fdd4b1
commit 297bdc6096
2 changed files with 160 additions and 0 deletions

View File

@@ -2961,6 +2961,15 @@ let run_spec_tests env test_files =
| "setTimeout" -> (match rest with fn :: _ -> ignore (Sx_ref.cek_call fn (List [])); Nil | _ -> Nil)
| "clearTimeout" -> Nil
| _ -> Nil)
(* NodeList.item(i) — dom-query-all iterates the querySelectorAll result
(a bare List) via this method, exactly like a browser NodeList. *)
| (List _ | ListRef _) :: String "item" :: [idx] ->
let items = match args with
| List l :: _ -> l
| ListRef { contents = l } :: _ -> l
| _ -> [] in
let i = match idx with Number n -> int_of_float n | Integer n -> n | _ -> -1 in
if i >= 0 && i < List.length items then List.nth items i else Nil
| Dict d :: String "hasOwnProperty" :: [String k] ->
Bool (Hashtbl.mem d k)
| Dict d :: String m :: rest ->
@@ -3070,6 +3079,26 @@ let run_spec_tests env test_files =
(* console.log/debug/error — no-op in tests *)
Nil
else if mt = "domparser" then
(* DOMParser.parseFromString(text, "text/html") — returns a mock
document whose <body> is parsed from `text`. An empty string yields
a valid empty document (truthy), matching the browser: that's what
the engine's handle-html-response relies on for an empty-body
sx-swap="delete" response. *)
(match m with
| "parseFromString" ->
let text = match rest with String t :: _ -> t | _ -> "" in
let bd = match make_mock_element "body" with Dict d -> d | _ -> Hashtbl.create 0 in
Hashtbl.replace bd "tagName" (String "BODY");
Hashtbl.replace bd "nodeName" (String "BODY");
parse_html_into bd text;
Hashtbl.replace bd "innerHTML" (String text);
let doc = Hashtbl.create 4 in
Hashtbl.replace doc "__mock_type" (String "document");
Hashtbl.replace doc "body" (Dict bd);
Dict doc
| _ -> Nil)
else
(* Element methods *)
(match m with
@@ -3483,6 +3512,10 @@ let run_spec_tests env test_files =
Dict ev
| [String "Object"] ->
Dict (Hashtbl.create 4)
| [String "DOMParser"] ->
let d = Hashtbl.create 4 in
Hashtbl.replace d "__mock_type" (String "domparser");
Dict d
| _ -> Nil);
reg "host-callback" (fun args ->