diff --git a/.gitignore b/.gitignore index d256e605..de2009aa 100644 --- a/.gitignore +++ b/.gitignore @@ -23,6 +23,7 @@ hosts/ocaml/test-results/ shared/static/wasm/sx_browser.bc.wasm.assets/ .claude/worktrees/ tests/playwright/test-results/ +test-results/ test-case-define.sx test-case-define.txt test_all.js diff --git a/hosts/ocaml/bin/mcp_tree.ml b/hosts/ocaml/bin/mcp_tree.ml index 0d269a00..ebcb1f50 100644 --- a/hosts/ocaml/bin/mcp_tree.ml +++ b/hosts/ocaml/bin/mcp_tree.ml @@ -155,6 +155,192 @@ let register_mcp_jit_hook () = | None -> None)) | _ -> None) +(* ------------------------------------------------------------------ *) +(* Native tree-tools helpers — avoid CEK overhead on big trees. *) +(* Mirror the SX semantics from lib/tree-tools.sx but run as direct *) +(* OCaml recursion. Used by read-subtree / validate / find-all / *) +(* find-across / comp-usage handlers. *) +(* ------------------------------------------------------------------ *) + +let native_path_str path = + "[" ^ String.concat "," (List.map string_of_int path) ^ "]" + +let rec native_node_display (node : value) : string = + match node with + | Nil -> "nil" + | Symbol s -> s + | Keyword k -> ":" ^ k + | String s -> "\"" ^ s ^ "\"" + | Number n -> + if Float.is_integer n then string_of_int (int_of_float n) + else Printf.sprintf "%g" n + | Bool true -> "true" + | Bool false -> "false" + | List [] | ListRef { contents = [] } -> "()" + | List (h :: t) | ListRef { contents = h :: t } -> + if t = [] then "(" ^ native_node_display h ^ ")" + else "(" ^ native_node_display h ^ " ...)" + | Dict _ -> "{...}" + | v -> Sx_runtime.value_to_str v + +(* node-summary-short: "(head)" for singletons, "(head second ...)" for >3, + "(all node-displays joined)" for small lists. Mirrors lib/tree-tools.sx. *) +let native_node_summary_short node = + match node with + | List [] | ListRef { contents = [] } -> "()" + | List items | ListRef { contents = items } -> + let n = List.length items in + if n > 3 then + let head = native_node_display (List.hd items) in + let second = native_node_display (List.nth items 1) in + Printf.sprintf "(%s %s ...)" head second + else + "(" ^ String.concat " " (List.map native_node_display items) ^ ")" + | _ -> native_node_display node + +let rec native_node_matches node pattern = + match node with + | Symbol s | String s -> + (try ignore (Str.search_forward (Str.regexp_string pattern) s 0); true + with Not_found -> false) + | List items | ListRef { contents = items } -> + List.exists (fun c -> native_node_matches c pattern) items + | _ -> false + +(* Raw find: returns reversed list of (path, summary) pairs — caller reverses. *) +let native_find_all_raw exprs pattern = + let nodes = match exprs with + | List xs | ListRef { contents = xs } -> xs + | x -> [x] in + let acc = ref [] in + let rec go node path = + if native_node_matches node pattern then + acc := (List.rev path, native_node_summary_short node) :: !acc; + match node with + | List items | ListRef { contents = items } -> + List.iteri (fun i child -> go child (i :: path)) items + | _ -> () + in + List.iteri (fun i node -> go node [i]) nodes; + List.rev !acc + +(* SX-value-returning wrapper — kept for any SX code that still calls find-all. *) +let native_find_all_sx exprs pattern = + let pairs = native_find_all_raw exprs pattern in + List (List.map (fun (path, summary) -> + List [List (List.map (fun i -> Number (float_of_int i)) path); + String summary] + ) pairs) + +(* navigate: walk a tree by a list of indices. Wraps exprs into a list on entry + (mirrors SX navigate). Returns Nil if any index is out of range. *) +let native_navigate exprs path = + let init = match exprs with + | List _ | ListRef _ -> exprs + | x -> List [x] in + let rec step current = function + | [] -> current + | i :: rest -> + (match current with + | List items when i >= 0 && i < List.length items -> + step (List.nth items i) rest + | ListRef { contents = items } when i >= 0 && i < List.length items -> + step (List.nth items i) rest + | _ -> Nil) in + step init path + +(* annotate-tree: render a list of exprs as an indented path-annotated string. + Mirrors the `annotate-node` dispatch from lib/tree-tools.sx: + - small lists (len<=4, no list children) render inline + - others render head on first line, children indented, closing ")" + Path annotations use [i,j,k,...] form. *) +let native_annotate_tree exprs = + let nodes = match exprs with + | List xs | ListRef { contents = xs } -> xs + | x -> [x] in + let out = Buffer.create 1024 in + let first = ref true in + let emit s = + if !first then first := false else Buffer.add_char out '\n'; + Buffer.add_string out s in + let rec ann node path depth = + let indent = String.make (depth * 2) ' ' in + let label = native_path_str (List.rev path) in + match node with + | List [] | ListRef { contents = [] } -> + emit (indent ^ label ^ " ()") + | List items | ListRef { contents = items } -> + let n = List.length items in + let rest = List.tl items in + let any_child_list = List.exists + (fun c -> match c with List _ | ListRef _ -> true | _ -> false) rest in + if n <= 4 && not any_child_list then + emit (indent ^ label ^ " (" ^ + String.concat " " (List.map native_node_display items) ^ ")") + else begin + let head_str = native_node_display (List.hd items) in + emit (indent ^ label ^ " (" ^ head_str); + List.iteri (fun i child -> + if i > 0 then ann child (i :: path) (depth + 1) + ) items; + emit (indent ^ " )") + end + | _ -> + emit (indent ^ label ^ " " ^ native_node_display node) + in + List.iteri (fun i node -> ann node [i] 0) nodes; + Buffer.contents out + +let native_read_subtree exprs path = + let node = native_navigate exprs path in + match node with + | Nil -> "Error: path " ^ native_path_str path ^ " not found" + | _ -> native_annotate_tree (List [node]) + +(* validate: walk the tree, emit WARNING for malformed letrec bindings and + ERROR for defisland/defcomp with fewer than 3 args. *) +let native_validate exprs = + let errors = ref [] in + let emit s = errors := s :: !errors in + let rec go node path = + (match node with + | List items | ListRef { contents = items } -> + (match items with + | [] -> () + | head :: _ -> + let head_name = match head with Symbol s -> Some s | _ -> None in + (match head_name with + | Some "letrec" when List.length items >= 2 -> + let bindings = List.nth items 1 in + (match bindings with + | List pairs | ListRef { contents = pairs } -> + List.iteri (fun i pair -> + let ok = match pair with + | List (Symbol _ :: _ :: _) -> true + | ListRef { contents = Symbol _ :: _ :: _ } -> true + | _ -> false in + if not ok then + emit (Printf.sprintf + "WARNING %s: letrec binding %d is not a (name value) pair: %s" + (native_path_str (List.rev (i :: 1 :: path))) + i + (native_node_display pair)) + ) pairs + | _ -> ()) + | Some (("defisland" | "defcomp") as nm) when List.length items < 4 -> + emit (Printf.sprintf + "ERROR %s: %s has fewer than 3 args (name params b..." + (native_path_str (List.rev path)) nm) + | _ -> ())); + List.iteri (fun i child -> go child (i :: path)) items + | _ -> ()) + in + let nodes = match exprs with + | List xs | ListRef { contents = xs } -> xs + | x -> [x] in + List.iteri (fun i node -> go node [i]) nodes; + if !errors = [] then "OK" else String.concat "\n" (List.rev !errors) + let setup_env () = let e = make_env () in (* Primitives are auto-registered at module init *) @@ -439,6 +625,30 @@ let setup_env () = try load_sx_file e (Filename.concat lib_dir "compiler.sx"); register_mcp_jit_hook () with exn -> Printf.eprintf "[mcp] Warning: compiler.sx load failed (JIT disabled): %s\n%!" (Printexc.to_string exn)); + + (* Native-impl the hot tree-tools ops — replaces SX versions to avoid CEK + overhead on big trees. See native_* helpers defined above setup_env. *) + ignore (Sx_types.env_bind e "find-all" (NativeFn ("find-all", fun args -> + match args with + | [exprs; String pattern] -> native_find_all_sx exprs pattern + | _ -> List []))); + ignore (Sx_types.env_bind e "read-subtree" (NativeFn ("read-subtree", fun args -> + match args with + | [exprs; List path] | [exprs; ListRef { contents = path }] -> + let ints = List.map (fun v -> match v with Number n -> int_of_float n | _ -> 0) path in + String (native_read_subtree exprs ints) + | _ -> String ""))); + ignore (Sx_types.env_bind e "validate" (NativeFn ("validate", fun args -> + match args with + | [exprs] -> String (native_validate exprs) + | _ -> String ""))); + ignore (Sx_types.env_bind e "path-str" (NativeFn ("path-str", fun args -> + match args with + | [List path] | [ListRef { contents = path }] -> + let ints = List.map (fun v -> match v with Number n -> int_of_float n | _ -> 0) path in + String (native_path_str ints) + | _ -> String "[]"))); + Printf.eprintf "[mcp] Ready in %.0fms\n%!" ((Unix.gettimeofday () -. t0) *. 1000.0); env := e @@ -804,7 +1014,11 @@ let handle_sx_read_subtree args = let open Yojson.Safe.Util in let tree = parse_file (require_file args "file") in let path = resolve_path tree (args |> member "path" |> to_string) in - text_result (value_to_string (call_sx "read-subtree" [tree; path])) + let ints = match path with + | List xs | ListRef { contents = xs } -> + List.map (fun v -> match v with Number n -> int_of_float n | _ -> 0) xs + | _ -> [] in + text_result (native_read_subtree tree ints) let handle_sx_get_context args = let open Yojson.Safe.Util in @@ -816,17 +1030,8 @@ let handle_sx_find_all args = let open Yojson.Safe.Util in let tree = parse_file (require_file args "file") in let pattern = args |> member "pattern" |> to_string in - let results = call_sx "find-all" [tree; String pattern] in - let lines = match results with - | List items | ListRef { contents = items } -> - List.map (fun item -> - match item with - | List [p; s] | ListRef { contents = [p; s] } -> - value_to_string (call_sx "path-str" [p]) ^ " " ^ value_to_string s - | _ -> value_to_string item - ) items - | _ -> [value_to_string results] - in + let results = native_find_all_raw tree pattern in + let lines = List.map (fun (p, s) -> native_path_str p ^ " " ^ s) results in text_result (String.concat "\n" lines) let handle_sx_get_siblings args = @@ -843,7 +1048,7 @@ let handle_sx_get_siblings args = let handle_sx_validate args = let tree = parse_file (require_file args "file") in - text_result (value_to_string (call_sx "validate" [tree])) + text_result (native_validate tree) let handle_sx_replace_node args = let open Yojson.Safe.Util in @@ -1912,16 +2117,8 @@ let handle_sx_find_across args = let rel = relative_path ~base:dir path in try let tree = parse_file path in - let results = call_sx "find-all" [tree; String pattern] in - (match results with - | List items | ListRef { contents = items } -> - List.map (fun item -> - match item with - | List [p; s] | ListRef { contents = [p; s] } -> - rel ^ " " ^ value_to_string (call_sx "path-str" [p]) ^ " " ^ value_to_string s - | _ -> rel ^ " " ^ value_to_string item - ) items - | _ -> []) + let results = native_find_all_raw tree pattern in + List.map (fun (p, s) -> rel ^ " " ^ native_path_str p ^ " " ^ s) results with _ -> [] ) files in if all_lines = [] then text_result "(no matches)" @@ -1948,16 +2145,8 @@ let handle_sx_comp_usage args = let rel = relative_path ~base:dir path in try let tree = parse_file path in - let results = call_sx "find-all" [tree; String name] in - (match results with - | List items | ListRef { contents = items } -> - List.map (fun item -> - match item with - | List [p; s] | ListRef { contents = [p; s] } -> - rel ^ " " ^ value_to_string (call_sx "path-str" [p]) ^ " " ^ value_to_string s - | _ -> rel ^ " " ^ value_to_string item - ) items - | _ -> []) + let results = native_find_all_raw tree name in + List.map (fun (p, s) -> rel ^ " " ^ native_path_str p ^ " " ^ s) results with _ -> [] ) files in if all_lines = [] then text_result "(no usages found)" diff --git a/hosts/ocaml/bin/run_tests.ml b/hosts/ocaml/bin/run_tests.ml index c9372942..823df835 100644 --- a/hosts/ocaml/bin/run_tests.ml +++ b/hosts/ocaml/bin/run_tests.ml @@ -25,6 +25,9 @@ open Sx_ref let pass_count = ref 0 let fail_count = ref 0 let suite_stack : string list ref = ref [] +(* Test filter: when Some, only run tests (suite, name) in the set. + Populated by --only-failing=FILE from lines like "FAIL: suite > name: error". *) +let suite_filter : (string * string, unit) Hashtbl.t option ref = ref None (* ====================================================================== *) (* Deep equality — SX structural comparison *) @@ -176,6 +179,17 @@ let make_test_env () = suite_stack := (match !suite_stack with _ :: t -> t | [] -> []); Nil); + bind "test-allowed?" (fun args -> + match !suite_filter with + | None -> Bool true + | Some filter -> + let name = match args with + | [String s] -> s + | [v] -> Sx_types.value_to_string v + | _ -> "" in + let suite = match !suite_stack with [] -> "" | s :: _ -> s in + Bool (Hashtbl.mem filter (suite, name))); + (* --- Test helpers --- *) bind "sx-parse" (fun args -> @@ -1563,6 +1577,131 @@ let run_spec_tests env test_files = | _ -> child in + (* Minimal HTML parser for test mock. + Parses an HTML string into mock child elements and appends them to `parent`. + Handles: content, nested elements, + self-closing tags, text content. No comments, CDATA, DOCTYPE, or entities. *) + let parse_html_into parent_d html = + let len = String.length html in + let pos = ref 0 in + let is_name_char c = + (c >= 'a' && c <= 'z') || (c >= 'A' && c <= 'Z') || + (c >= '0' && c <= '9') || c = '-' || c = '_' || c = ':' + in + let skip_ws () = + while !pos < len && (let c = html.[!pos] in c = ' ' || c = '\n' || c = '\t' || c = '\r') do + incr pos + done + in + let parse_name () = + let start = !pos in + while !pos < len && is_name_char html.[!pos] do incr pos done; + String.sub html start (!pos - start) + in + let parse_attr_value () = + if !pos >= len then "" + else if html.[!pos] = '"' then begin + incr pos; + let start = !pos in + while !pos < len && html.[!pos] <> '"' do incr pos done; + let v = String.sub html start (!pos - start) in + if !pos < len then incr pos; + v + end + else if html.[!pos] = '\'' then begin + incr pos; + let start = !pos in + while !pos < len && html.[!pos] <> '\'' do incr pos done; + let v = String.sub html start (!pos - start) in + if !pos < len then incr pos; + v + end + else begin + let start = !pos in + while !pos < len && (let c = html.[!pos] in + c <> ' ' && c <> '\t' && c <> '\n' && c <> '\r' + && c <> '>' && c <> '/') do + incr pos + done; + String.sub html start (!pos - start) + end + in + let parse_attrs (elem : (string, Sx_types.value) Hashtbl.t) = + skip_ws (); + while !pos < len && html.[!pos] <> '>' && html.[!pos] <> '/' do + let name = parse_name () in + if name = "" then begin + (* Avoid infinite loop on unexpected char *) + if !pos < len then incr pos + end else begin + let value = + if !pos < len && html.[!pos] = '=' then begin + incr pos; parse_attr_value () + end else "" + in + let attrs = match Hashtbl.find_opt elem "attributes" with + | Some (Dict a) -> a + | _ -> let a = Hashtbl.create 4 in Hashtbl.replace elem "attributes" (Dict a); a in + Hashtbl.replace attrs name (String value); + if name = "id" then Hashtbl.replace elem "id" (String value); + if name = "class" then Hashtbl.replace elem "className" (String value); + if name = "value" then Hashtbl.replace elem "value" (String value); + skip_ws () + end + done + in + let void_tags = ["br"; "hr"; "img"; "input"; "meta"; "link"; "area"; + "base"; "col"; "embed"; "source"; "track"; "wbr"] in + let rec parse_children parent_elem = + while !pos < len && not (!pos + 1 < len && html.[!pos] = '<' && html.[!pos + 1] = '/') do + if !pos < len && html.[!pos] = '<' && !pos + 1 < len && is_name_char html.[!pos + 1] then + parse_element parent_elem + else if !pos < len && html.[!pos] = '<' then begin + (* Unknown/comment — skip to next '>' *) + while !pos < len && html.[!pos] <> '>' do incr pos done; + if !pos < len then incr pos + end + else begin + let start = !pos in + while !pos < len && html.[!pos] <> '<' do incr pos done; + let text = String.sub html start (!pos - start) in + if String.trim text <> "" then begin + let cur = match Hashtbl.find_opt parent_elem "textContent" with + | Some (String s) -> s | _ -> "" in + Hashtbl.replace parent_elem "textContent" (String (cur ^ text)) + end + end + done + and parse_element parent_elem = + incr pos; (* skip '<' *) + let tag = parse_name () in + if tag = "" then () else begin + let el = make_mock_element tag in + let eld = match el with Dict d -> d | _ -> Hashtbl.create 0 in + parse_attrs eld; + skip_ws (); + let self_closing = + if !pos < len && html.[!pos] = '/' then begin incr pos; true end else false + in + if !pos < len && html.[!pos] = '>' then incr pos; + let is_void = List.mem (String.lowercase_ascii tag) void_tags in + if not self_closing && not is_void then begin + parse_children eld; + if !pos + 1 < len && html.[!pos] = '<' && html.[!pos + 1] = '/' then begin + pos := !pos + 2; + let _ = parse_name () in + skip_ws (); + if !pos < len && html.[!pos] = '>' then incr pos + end + end; + ignore (mock_append_child (Dict parent_elem) el) + end + in + pos := 0; + parse_children parent_d + in + let _ = parse_html_into in + (* Helper: remove child from parent *) let mock_remove_child parent child = match parent, child with @@ -1578,11 +1717,21 @@ let run_spec_tests env test_files = in (* Helper: querySelector - find element matching selector in tree *) - let mock_matches el sel = + let rec mock_matches el sel = match el with | Dict d -> let sel = String.trim sel in - if String.length sel > 0 && sel.[0] = '#' then + (* Compound selector: tag[attr=value] or tag.class or tag#id — split into parts *) + if String.length sel > 1 && + ((sel.[0] >= 'a' && sel.[0] <= 'z') || (sel.[0] >= 'A' && sel.[0] <= 'Z')) && + (String.contains sel '[' || String.contains sel '.' || String.contains sel '#') then + let i = ref 0 in + let n = String.length sel in + while !i < n && ((sel.[!i] >= 'a' && sel.[!i] <= 'z') || (sel.[!i] >= 'A' && sel.[!i] <= 'Z') || (sel.[!i] >= '0' && sel.[!i] <= '9') || sel.[!i] = '-') do incr i done; + let tag_part = String.sub sel 0 !i in + let rest_part = String.sub sel !i (n - !i) in + (mock_matches el tag_part) && (mock_matches el rest_part) + else if String.length sel > 0 && sel.[0] = '#' then let id = String.sub sel 1 (String.length sel - 1) in (match Hashtbl.find_opt d "id" with Some (String i) -> i = id | _ -> false) else if String.length sel > 0 && sel.[0] = '.' then @@ -1590,7 +1739,8 @@ let run_spec_tests env test_files = List.mem cls (get_classes d) else if String.length sel > 0 && sel.[0] = '[' then (* [attr] or [attr="value"] *) - let inner = String.sub sel 1 (String.length sel - 2) in + let end_bracket = try String.index sel ']' with Not_found -> String.length sel - 1 in + let inner = String.sub sel 1 (end_bracket - 1) in (match String.split_on_char '=' inner with | [attr] -> let attrs = match Hashtbl.find_opt d "attributes" with Some (Dict a) -> a | _ -> Hashtbl.create 0 in @@ -1621,19 +1771,51 @@ let run_spec_tests env test_files = | found -> mock_query_selector found (String.concat " " rest)) | [] -> Nil and mock_query_selector_single el sel = - match el with - | Dict d -> - let kids = match Hashtbl.find_opt d "children" with Some (List l) -> l | _ -> [] in - let rec search = function - | [] -> Nil - | child :: rest -> - if mock_matches child sel then child - else match mock_query_selector_single child sel with - | Nil -> search rest - | found -> found - in - search kids - | _ -> Nil + (* Handle tag:nth-of-type(N): find Nth child of same tag under parent *) + let nth_match = try + let idx = String.index sel ':' in + let tag = String.sub sel 0 idx in + let rest = String.sub sel idx (String.length sel - idx) in + if String.length rest > String.length ":nth-of-type(" && + String.sub rest 0 (String.length ":nth-of-type(") = ":nth-of-type(" && + rest.[String.length rest - 1] = ')' + then + let n_str = String.sub rest (String.length ":nth-of-type(") + (String.length rest - String.length ":nth-of-type(" - 1) in + (try Some (tag, int_of_string (String.trim n_str)) with _ -> None) + else None + with Not_found -> None in + (match nth_match with + | Some (tag, n) -> + (* Walk tree; collect matching-tag elements in document order; return nth *) + let found = ref [] in + let rec walk node = + match node with + | Dict d -> + let kids = match Hashtbl.find_opt d "children" with Some (List l) -> l | _ -> [] in + List.iter (fun child -> + if mock_matches child tag then found := child :: !found; + walk child + ) kids + | _ -> () + in + walk el; + let matches = List.rev !found in + (try List.nth matches (n - 1) with _ -> Nil) + | None -> + (match el with + | Dict d -> + let kids = match Hashtbl.find_opt d "children" with Some (List l) -> l | _ -> [] in + let rec search = function + | [] -> Nil + | child :: rest -> + if mock_matches child sel then child + else match mock_query_selector_single child sel with + | Nil -> search rest + | found -> found + in + search kids + | _ -> Nil)) in let rec mock_query_all el sel = @@ -1742,7 +1924,7 @@ let run_spec_tests env test_files = | "addEventListener" | "removeEventListener" | "dispatchEvent" | "appendChild" | "removeChild" | "insertBefore" | "replaceChild" | "querySelector" | "querySelectorAll" | "closest" | "matches" - | "contains" | "cloneNode" | "remove" | "focus" | "blur" | "click" + | "contains" | "compareDocumentPosition" | "cloneNode" | "remove" | "focus" | "blur" | "click" | "insertAdjacentHTML" | "prepend" | "showModal" | "show" | "close" | "getBoundingClientRect" | "getAnimations" | "scrollIntoView" | "scrollTo" | "scroll" | "reset" -> Bool true @@ -1841,16 +2023,19 @@ let run_spec_tests env test_files = | Some (Dict _cl) -> () (* classes live in className *) | _ -> ()) | "innerHTML" -> - (* Setting innerHTML clears children and syncs textContent (like a browser) *) + (* Setting innerHTML clears existing children, parses the HTML, and + creates new mock child elements (approximating browser behavior). *) let kids = match Hashtbl.find_opt d "children" with Some (List l) -> l | _ -> [] in List.iter (fun c -> match c with Dict cd -> Hashtbl.replace cd "parentElement" Nil; Hashtbl.replace cd "parentNode" Nil | _ -> ()) kids; Hashtbl.replace d "children" (List []); Hashtbl.replace d "childNodes" (List []); - (* Approximate textContent: strip HTML tags from innerHTML *) + Hashtbl.replace d "textContent" (String ""); (match stored with - | String s -> + | String s when String.contains s '<' -> + parse_html_into d s; + (* Strip tags for a best-effort textContent *) let buf = Buffer.create (String.length s) in let in_tag = ref false in String.iter (fun c -> @@ -1859,6 +2044,7 @@ let run_spec_tests env test_files = else if not !in_tag then Buffer.add_char buf c ) s; Hashtbl.replace d "textContent" (String (Buffer.contents buf)) + | String s -> Hashtbl.replace d "textContent" (String s) | _ -> Hashtbl.replace d "textContent" (String "")) | "textContent" -> (* Setting textContent clears children *) @@ -1887,6 +2073,8 @@ let run_spec_tests env test_files = | "setTimeout" -> (match rest with fn :: _ -> ignore (Sx_ref.cek_call fn (List [])); Nil | _ -> Nil) | "clearTimeout" -> Nil | _ -> Nil) + | Dict d :: String "hasOwnProperty" :: [String k] -> + Bool (Hashtbl.mem d k) | Dict d :: String m :: rest -> let mt = match Hashtbl.find_opt d "__mock_type" with Some (String t) -> t | _ -> "" in @@ -2139,6 +2327,33 @@ let run_spec_tests env test_files = | _ -> Nil in up (Dict d) | _ -> Nil) + | "compareDocumentPosition" -> + (match rest with + | [other] -> + let self = Dict d in + let body = Dict mock_body in + let found_self = ref false in + let found_other = ref false in + let self_first = ref false in + let rec walk node = + if !found_self && !found_other then () + else begin + if mock_el_eq node self then begin + if not !found_other then self_first := true; + found_self := true + end; + if mock_el_eq node other then found_other := true; + (match node with + | Dict dd -> let kids = match Hashtbl.find_opt dd "children" with Some (List l) -> l | _ -> [] in + List.iter walk kids + | _ -> ()) + end + in + walk body; + if !found_self && !found_other then + Number (if !self_first then 4.0 else 2.0) + else Number 0.0 + | _ -> Number 0.0) | "matches" -> (match rest with [String sel] -> Bool (mock_matches (Dict d) sel) | _ -> Bool false) | "contains" -> @@ -2213,25 +2428,62 @@ let run_spec_tests env test_files = Hashtbl.replace r "right" (Number 100.0); Hashtbl.replace r "bottom" (Number 100.0); Dict r | "insertAdjacentHTML" -> - (* Position-aware insertion, coerce value to string *) + (* Position-aware insertion. Parse the new HTML into a scratch + container, then splice the resulting children into the target + position WITHOUT disturbing sibling nodes. *) (match rest with - | [String pos; value] -> + | [String pos_kind; value] -> let html = match dom_stringify value with String s -> s | _ -> "" in - let cur = match Hashtbl.find_opt d "innerHTML" with Some (String s) -> s | _ -> "" in - let new_html = match pos with - | "afterbegin" -> html ^ cur (* prepend *) - | _ -> cur ^ html (* beforeend / default: append *) + (* Parse new HTML into scratch container to get new child list. + For pure-text content, wrap into the target's innerHTML path. *) + let scratch = make_mock_element "div" in + let scratch_d = match scratch with Dict sd -> sd | _ -> Hashtbl.create 0 in + if String.contains html '<' then parse_html_into scratch_d html; + let new_kids = match Hashtbl.find_opt scratch_d "children" with Some (List l) -> l | _ -> [] in + let prepend = pos_kind = "beforebegin" || pos_kind = "afterbegin" in + let insert_into container_d index = + List.iter (fun c -> match c with + | Dict cd -> + Hashtbl.replace cd "parentElement" (Dict container_d); + Hashtbl.replace cd "parentNode" (Dict container_d) + | _ -> ()) new_kids; + let kids = match Hashtbl.find_opt container_d "children" with Some (List l) -> l | _ -> [] in + let before = List.filteri (fun i _ -> i < index) kids in + let after = List.filteri (fun i _ -> i >= index) kids in + let all = before @ new_kids @ after in + Hashtbl.replace container_d "children" (List all); + Hashtbl.replace container_d "childNodes" (List all); + (* Update container innerHTML based on position kind, not index *) + let cur = match Hashtbl.find_opt container_d "innerHTML" with Some (String s) -> s | _ -> "" in + let new_html = if prepend then html ^ cur else cur ^ html in + Hashtbl.replace container_d "innerHTML" (String new_html); + let buf = Buffer.create (String.length new_html) in + let in_tag = ref false in + String.iter (fun c -> + if c = '<' then in_tag := true + else if c = '>' then in_tag := false + else if not !in_tag then Buffer.add_char buf c + ) new_html; + Hashtbl.replace container_d "textContent" (String (Buffer.contents buf)) in - Hashtbl.replace d "innerHTML" (String new_html); - (* Sync textContent *) - let buf = Buffer.create (String.length new_html) in - let in_tag = ref false in - String.iter (fun c -> - if c = '<' then in_tag := true - else if c = '>' then in_tag := false - else if not !in_tag then Buffer.add_char buf c - ) new_html; - Hashtbl.replace d "textContent" (String (Buffer.contents buf)); + (match pos_kind with + | "beforebegin" | "afterend" -> + (match Hashtbl.find_opt d "parentElement" with + | Some (Dict pd) -> + let siblings = match Hashtbl.find_opt pd "children" with Some (List l) -> l | _ -> [] in + let rec find_idx i = function + | [] -> List.length siblings + | x :: _ when mock_el_eq x (Dict d) -> i + | _ :: rest -> find_idx (i+1) rest + in + let self_idx = find_idx 0 siblings in + let insert_idx = if pos_kind = "beforebegin" then self_idx else self_idx + 1 in + insert_into pd insert_idx + | _ -> ()) + | "afterbegin" -> insert_into d 0 + | _ (* "beforeend" *) -> + let kids_len = match Hashtbl.find_opt d "children" with Some (List l) -> List.length l | _ -> 0 in + insert_into d kids_len); Nil | _ -> Nil) | "showModal" | "show" -> @@ -2341,6 +2593,8 @@ let run_spec_tests env test_files = Hashtbl.replace ev "_stopped" (Bool false); Hashtbl.replace ev "_stopImmediate" (Bool false); Dict ev + | [String "Object"] -> + Dict (Hashtbl.create 4) | _ -> Nil); reg "host-callback" (fun args -> @@ -2823,7 +3077,39 @@ let () = let args = Array.to_list Sys.argv |> List.tl in let foundation_only = List.mem "--foundation" args in let jit_enabled = List.mem "--jit" args in - let test_files = List.filter (fun a -> not (String.length a > 0 && a.[0] = '-')) args in + (* --only-failing=PATH : read lines of form "FAIL: suite > name: ..." and + restrict test runs to those (suite, name) pairs. *) + List.iter (fun a -> + let prefix = "--only-failing=" in + if String.length a > String.length prefix + && String.sub a 0 (String.length prefix) = prefix then begin + let path = String.sub a (String.length prefix) (String.length a - String.length prefix) in + let filter = Hashtbl.create 64 in + let ic = open_in path in + (try while true do + let line = input_line ic in + (* Match " FAIL: > : " or "FAIL: > : " *) + let line = String.trim line in + if String.length line > 6 && String.sub line 0 6 = "FAIL: " then begin + let rest = String.sub line 6 (String.length line - 6) in + match String.index_opt rest '>' with + | Some gt -> + let suite = String.trim (String.sub rest 0 gt) in + let after = String.sub rest (gt + 1) (String.length rest - gt - 1) in + (match String.index_opt after ':' with + | Some colon -> + let name = String.trim (String.sub after 0 colon) in + Hashtbl.replace filter (suite, name) () + | None -> ()) + | None -> () + end + done with End_of_file -> ()); + close_in ic; + Printf.eprintf "[filter] %d tests loaded from %s\n%!" (Hashtbl.length filter) path; + suite_filter := Some filter + end) args; + let test_files = List.filter (fun a -> + not (String.length a > 0 && a.[0] = '-')) args in (* Always run foundation tests *) run_foundation_tests (); diff --git a/lib/hyperscript/compiler.sx b/lib/hyperscript/compiler.sx index 21dbe0ed..fc3ad1e8 100644 --- a/lib/hyperscript/compiler.sx +++ b/lib/hyperscript/compiler.sx @@ -77,7 +77,11 @@ ((= th (quote ref)) (list (quote set!) (make-symbol (nth target 1)) value)) ((= th (quote local)) - (list (quote define) (make-symbol (nth target 1)) value)) + (list + (quote hs-scoped-set!) + (quote me) + (nth target 1) + value)) ((= th (quote dom-ref)) (list (quote hs-dom-set!) @@ -85,18 +89,18 @@ (nth target 1) value)) ((= th (quote me)) - (list (quote dom-set-inner-html) (quote me) value)) + (list (quote hs-set-inner-html!) (quote me) value)) ((= th (quote it)) (list (quote set!) (quote it) value)) ((= th (quote query)) - (list (quote dom-set-inner-html) (hs-to-sx target) value)) + (list (quote hs-set-inner-html!) (hs-to-sx target) value)) ((= th (quote array-index)) (list - (quote host-set!) + (quote hs-array-set!) (hs-to-sx (nth target 1)) (hs-to-sx (nth target 2)) value)) ((or (= th (quote next)) (= th (quote previous)) (= th (quote closest))) - (list (quote dom-set-inner-html) (hs-to-sx target) value)) + (list (quote hs-set-inner-html!) (hs-to-sx target) value)) ((= th (quote of)) (let ((prop-ast (nth target 1)) (obj-ast (nth target 2))) @@ -162,10 +166,19 @@ (let ((wrapped-body (if catch-info (let ((var (make-symbol (nth catch-info 0))) (catch-body (hs-to-sx (nth catch-info 1)))) (if finally-info (list (quote do) (list (quote guard) (list var (list true catch-body)) compiled-body) (hs-to-sx finally-info)) (list (quote guard) (list var (list true catch-body)) compiled-body))) (if finally-info (list (quote do) compiled-body (hs-to-sx finally-info)) compiled-body))) (handler - (list - (quote fn) - (list (quote event)) - wrapped-body))) + (let + ((uses-the-result? (fn (expr) (cond ((= expr (quote the-result)) true) ((list? expr) (some (fn (x) (uses-the-result? x)) expr)) (true false))))) + (list + (quote fn) + (list (quote event)) + (if + (uses-the-result? wrapped-body) + (list + (quote let) + (list + (list (quote the-result) nil)) + wrapped-body) + wrapped-body))))) (if every? (list @@ -443,9 +456,7 @@ (quote __hs-new) (list (quote +) - (list - (quote hs-to-number) - (list (quote nth) var-sym (quote __hs-idx))) + (list (quote nth) var-sym (quote __hs-idx)) amount))) (list (quote do) @@ -463,10 +474,7 @@ ((t (hs-to-sx expr))) (list (quote let) - (list - (list - (quote __hs-new) - (list (quote +) (list (quote hs-to-number) t) amount))) + (list (list (quote __hs-new) (list (quote +) t amount))) (list (quote do) (list (quote set!) t (quote __hs-new)) @@ -564,9 +572,7 @@ (quote __hs-new) (list (quote -) - (list - (quote hs-to-number) - (list (quote nth) var-sym (quote __hs-idx))) + (list (quote nth) var-sym (quote __hs-idx)) amount))) (list (quote do) @@ -584,10 +590,7 @@ ((t (hs-to-sx expr))) (list (quote let) - (list - (list - (quote __hs-new) - (list (quote -) (list (quote hs-to-number) t) amount))) + (list (list (quote __hs-new) (list (quote -) t amount))) (list (quote do) (list (quote set!) t (quote __hs-new)) @@ -754,35 +757,53 @@ (hs-to-sx (nth ast 3)))) ((= head (quote pick-first)) (list - (quote hs-pick-first) - (hs-to-sx (nth ast 1)) - (hs-to-sx (nth ast 2)))) + (quote set!) + (quote it) + (list + (quote hs-pick-first) + (hs-to-sx (nth ast 1)) + (hs-to-sx (nth ast 2))))) ((= head (quote pick-last)) (list - (quote hs-pick-last) - (hs-to-sx (nth ast 1)) - (hs-to-sx (nth ast 2)))) + (quote set!) + (quote it) + (list + (quote hs-pick-last) + (hs-to-sx (nth ast 1)) + (hs-to-sx (nth ast 2))))) ((= head (quote pick-random)) (list - (quote hs-pick-random) - (hs-to-sx (nth ast 1)) - (if (nil? (nth ast 2)) nil (hs-to-sx (nth ast 2))))) + (quote set!) + (quote it) + (list + (quote hs-pick-random) + (hs-to-sx (nth ast 1)) + (if (nil? (nth ast 2)) nil (hs-to-sx (nth ast 2)))))) ((= head (quote pick-items)) (list - (quote hs-pick-items) - (hs-to-sx (nth ast 1)) - (hs-to-sx (nth ast 2)) - (hs-to-sx (nth ast 3)))) + (quote set!) + (quote it) + (list + (quote hs-pick-items) + (hs-to-sx (nth ast 1)) + (hs-to-sx (nth ast 2)) + (hs-to-sx (nth ast 3))))) ((= head (quote pick-match)) (list - (quote regex-match) - (hs-to-sx (nth ast 1)) - (hs-to-sx (nth ast 2)))) + (quote set!) + (quote it) + (list + (quote regex-match) + (hs-to-sx (nth ast 1)) + (hs-to-sx (nth ast 2))))) ((= head (quote pick-matches)) (list - (quote regex-find-all) - (hs-to-sx (nth ast 1)) - (hs-to-sx (nth ast 2)))) + (quote set!) + (quote it) + (list + (quote regex-find-all) + (hs-to-sx (nth ast 1)) + (hs-to-sx (nth ast 2))))) ((= head (quote prop-is)) (list (quote hs-prop-is) @@ -870,6 +891,11 @@ ((= head (quote ref)) (make-symbol (nth ast 1))) ((= head (quote query)) (list (quote hs-query-first) (nth ast 1))) + ((= head (quote query-scoped)) + (list + (quote hs-query-all-in) + (nth ast 1) + (hs-to-sx (nth ast 2)))) ((= head (quote attr)) (list (quote dom-get-attr) @@ -890,7 +916,8 @@ (quote dom-has-class?) (hs-to-sx (nth ast 1)) (nth ast 2))) - ((= head (quote local)) (make-symbol (nth ast 1))) + ((= head (quote local)) + (list (quote hs-scoped-get) (quote me) (nth ast 1))) ((= head (quote array)) (cons (quote list) (map hs-to-sx (rest ast)))) ((= head (quote not)) @@ -1163,6 +1190,14 @@ (quote set!) (hs-to-sx tgt) (list (quote hs-add-to!) val (hs-to-sx tgt))))) + ((= head (quote add-attr)) + (let + ((tgt (nth ast 3))) + (list + (quote hs-set-attr!) + (hs-to-sx tgt) + (nth ast 1) + (hs-to-sx (nth ast 2))))) ((= head (quote remove-value)) (let ((val (hs-to-sx (nth ast 1))) (tgt (nth ast 2))) @@ -1296,6 +1331,20 @@ (nth ast 1) (hs-to-sx (nth ast 2)) (hs-to-sx (nth ast 3)))) + ((= head (quote toggle-attr-val)) + (list + (quote hs-toggle-attr-val!) + (hs-to-sx (nth ast 3)) + (nth ast 1) + (hs-to-sx (nth ast 2)))) + ((= head (quote toggle-attr-diff)) + (list + (quote hs-toggle-attr-diff!) + (hs-to-sx (nth ast 5)) + (nth ast 1) + (hs-to-sx (nth ast 2)) + (nth ast 3) + (hs-to-sx (nth ast 4)))) ((= head (quote set!)) (emit-set (nth ast 1) (hs-to-sx (nth ast 2)))) ((= head (quote put!)) @@ -1358,14 +1407,49 @@ nil)) ((= head (quote hide)) (let - ((tgt (hs-to-sx (nth ast 1))) - (strategy (if (> (len ast) 2) (nth ast 2) "display"))) - (list (quote hs-hide!) tgt strategy))) + ((tgt (let ((raw-tgt (nth ast 1))) (if (and (list? raw-tgt) (= (first raw-tgt) (quote query))) (list (quote hs-query-all) (nth raw-tgt 1)) (hs-to-sx raw-tgt)))) + (strategy (if (> (len ast) 2) (nth ast 2) "display")) + (when-cond (if (> (len ast) 3) (nth ast 3) nil))) + (if + (nil? when-cond) + (list (quote hs-hide!) tgt strategy) + (list + (quote hs-hide-when!) + tgt + strategy + (list + (quote fn) + (list (quote it)) + (hs-to-sx when-cond)))))) ((= head (quote show)) (let - ((tgt (hs-to-sx (nth ast 1))) - (strategy (if (> (len ast) 2) (nth ast 2) "display"))) - (list (quote hs-show!) tgt strategy))) + ((tgt (let ((raw-tgt (nth ast 1))) (if (and (list? raw-tgt) (= (first raw-tgt) (quote query))) (list (quote hs-query-all) (nth raw-tgt 1)) (hs-to-sx raw-tgt)))) + (strategy (if (> (len ast) 2) (nth ast 2) "display")) + (when-cond (if (> (len ast) 3) (nth ast 3) nil))) + (if + (nil? when-cond) + (list (quote hs-show!) tgt strategy) + (list + (quote let) + (list + (list + (quote __hs-show-r) + (list + (quote hs-show-when!) + tgt + strategy + (list + (quote fn) + (list (quote it)) + (hs-to-sx when-cond))))) + (list + (quote begin) + (list + (quote set!) + (quote the-result) + (quote __hs-show-r)) + (list (quote set!) (quote it) (quote __hs-show-r)) + (quote __hs-show-r)))))) ((= head (quote transition)) (emit-transition ast)) ((= head (quote transition-from)) (let @@ -1424,6 +1508,14 @@ (list (quote hs-settle) (quote me))) ((= head (quote go)) (list (quote hs-navigate!) (hs-to-sx (nth ast 1)))) + ((= head (quote __get-cmd)) + (let + ((val (hs-to-sx (nth ast 1)))) + (list + (quote begin) + (list (quote set!) (quote the-result) val) + (list (quote set!) (quote it) val) + val))) ((= head (quote append!)) (let ((tgt (hs-to-sx (nth ast 2))) @@ -1648,11 +1740,13 @@ (list (quote hs-reset!) (hs-to-sx (nth ast 1)))) ((= head (quote default!)) (let - ((t (hs-to-sx (nth ast 1))) (v (hs-to-sx (nth ast 2)))) + ((tgt-ast (nth ast 1)) + (read (hs-to-sx (nth ast 1))) + (v (hs-to-sx (nth ast 2)))) (list (quote when) - (list (quote nil?) t) - (list (quote set!) t v)))) + (list (quote hs-default?) read) + (emit-set tgt-ast v)))) ((= head (quote hs-is)) (list (quote hs-is) diff --git a/lib/hyperscript/integration.sx b/lib/hyperscript/integration.sx index db3835f0..147d7350 100644 --- a/lib/hyperscript/integration.sx +++ b/lib/hyperscript/integration.sx @@ -16,6 +16,14 @@ (fn (sx) (define vars (list)) + (define + reserved + (list + (quote me) + (quote it) + (quote event) + (quote you) + (quote yourself))) (define walk (fn @@ -30,7 +38,9 @@ (let ((name (nth node 1))) (when - (not (some (fn (v) (= v name)) vars)) + (and + (not (some (fn (v) (= v name)) vars)) + (not (some (fn (v) (= v name)) reserved))) (set! vars (cons name vars))))) (for-each walk node)))) (walk sx) @@ -67,9 +77,10 @@ (fn (el) (let - ((src (dom-get-attr el "_"))) + ((src (dom-get-attr el "_")) (prev (dom-get-data el "hs-script"))) (when - (and src (not (dom-get-data el "hs-active"))) + (and src (not (= src prev))) + (dom-set-data el "hs-script" src) (dom-set-data el "hs-active" true) (let ((handler (hs-handler src))) (handler el)))))) @@ -77,6 +88,21 @@ ;; Called once at page load. Finds all elements with _ attribute, ;; compiles their hyperscript, and activates them. +(define + hs-deactivate! + (fn + (el) + (let + ((unlisteners (or (dom-get-data el "hs-unlisteners") (list)))) + (for-each (fn (u) (when u (u))) unlisteners) + (dom-set-data el "hs-unlisteners" (list)) + (dom-set-data el "hs-active" false) + (dom-set-data el "hs-script" nil)))) + +;; ── Boot subtree: for dynamic content ─────────────────────────── +;; Called after HTMX swaps or dynamic DOM insertion. +;; Only activates elements within the given root. + (define hs-boot! (fn @@ -85,10 +111,6 @@ ((elements (dom-query-all (host-get (host-global "document") "body") "[_]"))) (for-each (fn (el) (hs-activate! el)) elements)))) -;; ── Boot subtree: for dynamic content ─────────────────────────── -;; Called after HTMX swaps or dynamic DOM insertion. -;; Only activates elements within the given root. - (define hs-boot-subtree! (fn diff --git a/lib/hyperscript/parser.sx b/lib/hyperscript/parser.sx index 0bdb84bb..3739084b 100644 --- a/lib/hyperscript/parser.sx +++ b/lib/hyperscript/parser.sx @@ -95,6 +95,13 @@ (do (adv!) (list kind (str "." val) (list (quote me))))) ((= typ "id") (do (adv!) (list kind (str "#" val) (list (quote me))))) + ((= typ "attr") + (do + (adv!) + (list + (quote attr) + val + (list kind (str "[" val "]") (list (quote me)))))) (true (list kind "*" (list (quote me)))))))) (define parse-pos-kw @@ -146,8 +153,10 @@ (do (adv!) (list (quote me)))) ((and (= typ "keyword") (= val "I")) (do (adv!) (list (quote me)))) - ((and (= typ "keyword") (or (= val "it") (= val "result"))) + ((and (= typ "keyword") (= val "it")) (do (adv!) (list (quote it)))) + ((and (= typ "keyword") (= val "result")) + (do (adv!) (quote the-result))) ((and (= typ "keyword") (= val "event")) (do (adv!) (list (quote event)))) ((and (= typ "keyword") (= val "target")) @@ -174,7 +183,18 @@ (do (adv!) (parse-pos-kw (quote last)))) ((= typ "id") (do (adv!) (list (quote query) (str "#" val)))) - ((= typ "selector") (do (adv!) (list (quote query) val))) + ((= typ "selector") + (do + (adv!) + (if + (and (= (tp-type) "keyword") (= (tp-val) "in")) + (do + (adv!) + (list + (quote query-scoped) + val + (parse-cmp (parse-arith (parse-poss (parse-atom)))))) + (list (quote query) val)))) ((= typ "attr") (do (adv!) (list (quote attr) val (list (quote me))))) ((= typ "style") @@ -426,7 +446,7 @@ (list (quote type-check) left type-name))))))) (true (let - ((right (parse-expr))) + ((right (parse-cmp (parse-arith (parse-poss (parse-atom)))))) (if (match-kw "ignoring") (do @@ -530,6 +550,14 @@ (quote and) (list (quote >=) left lo) (list (quote <=) left hi)))))) + ((or (and (or (= (tp-val) "a") (= (tp-val) "an")) (do (adv!) true))) + (let + ((type-name (tp-val))) + (do + (adv!) + (list + (quote not) + (list (quote type-check) left type-name))))) (true (let ((right (parse-expr))) @@ -546,6 +574,10 @@ (quote and) (list (quote >=) left lo) (list (quote <=) left hi))))) + ((or (and (or (= (tp-val) "a") (= (tp-val) "an")) (do (adv!) true))) + (let + ((type-name (tp-val))) + (do (adv!) (list (quote type-check) left type-name)))) (true (let ((right (parse-expr))) @@ -576,7 +608,7 @@ (match-kw "case") (list (quote ends-with-ic?) left rhs)) (list (quote ends-with?) left rhs))))) - ((and (= typ "keyword") (= val "matches")) + ((and (= typ "keyword") (or (= val "matches") (= val "match"))) (do (adv!) (let @@ -618,7 +650,22 @@ (quote as) left (str type-name ":" param))))) - (list (quote as) left type-name)))))) + (let + loop + ((result (list (quote as) left type-name))) + (if + (and (= (tp-type) "op") (= (tp-val) "|")) + (do + (adv!) + (when + (or (= (tp-val) "a") (= (tp-val) "an")) + (adv!)) + (let + ((next-type (tp-val))) + (do + (adv!) + (loop (list (quote as) result next-type))))) + result))))))) ((and (= typ "colon")) (do (adv!) @@ -693,7 +740,7 @@ (list (quote strict-eq) left (parse-expr)))) ((and (= typ "keyword") (or (= val "contain") (= val "include") (= val "includes"))) (do (adv!) (list (quote contains?) left (parse-expr)))) - ((and (= typ "keyword") (= val "precedes")) + ((and (= typ "keyword") (or (= val "precedes") (= val "precede"))) (do (adv!) (list (quote precedes?) left (parse-atom)))) ((and (= typ "keyword") (= val "follows")) (do (adv!) (list (quote follows?) left (parse-atom)))) @@ -772,7 +819,7 @@ (= (tp-val) "starts") (= (tp-val) "ends") (= (tp-val) "contains") - (= (tp-val) "matches") + (or (= (tp-val) "matches") (= (tp-val) "match")) (= (tp-val) "is") (= (tp-val) "does") (= (tp-val) "in") @@ -892,6 +939,18 @@ (let ((tgt (if (match-kw "to") (parse-expr) (list (quote me))))) (list (quote set-styles) (reverse pairs) tgt))))) + ((and (= (tp-type) "bracket-open") (> (len tokens) (+ p 1)) (= (get (nth tokens (+ p 1)) "type") "attr")) + (do + (adv!) + (let + ((attr-name (get (adv!) "value"))) + (when (and (= (tp-type) "op") (= (tp-val) "=")) (adv!)) + (let + ((attr-val (parse-expr))) + (when (= (tp-type) "bracket-close") (adv!)) + (let + ((tgt (parse-tgt-kw "to" (list (quote me))))) + (list (quote add-attr) attr-name attr-val tgt)))))) (true (let ((value (parse-expr))) @@ -978,20 +1037,58 @@ () (cond ((match-kw "between") - (if - (= (tp-type) "class") - (let - ((cls1 (do (let ((v (tp-val))) (adv!) v)))) - (expect-kw! "and") - (if - (= (tp-type) "class") - (let - ((cls2 (do (let ((v (tp-val))) (adv!) v)))) + (cond + ((= (tp-type) "class") + (let + ((cls1 (do (let ((v (tp-val))) (adv!) v)))) + (expect-kw! "and") + (if + (= (tp-type) "class") (let - ((tgt (parse-tgt-kw "on" (list (quote me))))) - (list (quote toggle-between) cls1 cls2 tgt))) - nil)) - nil)) + ((cls2 (do (let ((v (tp-val))) (adv!) v)))) + (let + ((tgt (parse-tgt-kw "on" (list (quote me))))) + (list (quote toggle-between) cls1 cls2 tgt))) + nil))) + ((and (= (tp-type) "bracket-open") (> (len tokens) (+ p 1)) (= (get (nth tokens (+ p 1)) "type") "attr")) + (do + (adv!) + (let + ((n1 (get (adv!) "value"))) + (when + (and (= (tp-type) "op") (= (tp-val) "=")) + (adv!)) + (let + ((v1 (parse-expr))) + (when (= (tp-type) "bracket-close") (adv!)) + (expect-kw! "and") + (when (= (tp-type) "bracket-open") (adv!)) + (let + ((n2 (get (adv!) "value"))) + (when + (and (= (tp-type) "op") (= (tp-val) "=")) + (adv!)) + (let + ((v2 (parse-expr))) + (when (= (tp-type) "bracket-close") (adv!)) + (let + ((tgt (parse-tgt-kw "on" (list (quote me))))) + (if + (= n1 n2) + (list + (quote toggle-attr-between) + n1 + v1 + v2 + tgt) + (list + (quote toggle-attr-diff) + n1 + v1 + n2 + v2 + tgt))))))))) + (true nil))) ((= (tp-type) "class") (let ((cls (do (let ((v (tp-val))) (adv!) v)))) @@ -1012,38 +1109,67 @@ (match-kw "between") (let ((val1 (parse-atom))) - (expect-kw! "and") - (let - ((val2 (parse-atom))) + (do + (when (= (tp-type) "comma") (adv!)) (if - (match-kw "and") - (let - ((val3 (parse-atom))) - (if - (match-kw "and") + (and (= (tp-type) "keyword") (= (tp-val) "and")) + (adv!) + nil) + (let + ((val2 (parse-atom))) + (if + (or + (= (tp-type) "comma") + (and + (= (tp-type) "keyword") + (= (tp-val) "and"))) + (do + (when (= (tp-type) "comma") (adv!)) + (if + (and + (= (tp-type) "keyword") + (= (tp-val) "and")) + (adv!) + nil) (let - ((val4 (parse-atom))) - (list - (quote toggle-style-cycle) - prop - tgt - val1 - val2 - val3 - val4)) - (list - (quote toggle-style-cycle) - prop - tgt - val1 - val2 - val3))) - (list - (quote toggle-style-between) - prop - val1 - val2 - tgt)))) + ((val3 (parse-atom))) + (if + (or + (= (tp-type) "comma") + (and + (= (tp-type) "keyword") + (= (tp-val) "and"))) + (do + (when (= (tp-type) "comma") (adv!)) + (if + (and + (= (tp-type) "keyword") + (= (tp-val) "and")) + (adv!) + nil) + (let + ((val4 (parse-atom))) + (list + (quote toggle-style-cycle) + prop + tgt + val1 + val2 + val3 + val4))) + (list + (quote toggle-style-cycle) + prop + tgt + val1 + val2 + val3)))) + (list + (quote toggle-style-between) + prop + val1 + val2 + tgt))))) (list (quote toggle-style) prop tgt))))) ((= (tp-type) "attr") (let @@ -1064,6 +1190,18 @@ val2 tgt))) (list (quote toggle-attr) attr-name tgt))))) + ((and (= (tp-type) "bracket-open") (> (len tokens) (+ p 1)) (= (get (nth tokens (+ p 1)) "type") "attr")) + (do + (adv!) + (let + ((attr-name (get (adv!) "value"))) + (when (and (= (tp-type) "op") (= (tp-val) "=")) (adv!)) + (let + ((attr-val (parse-expr))) + (when (= (tp-type) "bracket-close") (adv!)) + (let + ((tgt (parse-tgt-kw "on" (list (quote me))))) + (list (quote toggle-attr-val) attr-name attr-val tgt)))))) ((and (= (tp-type) "keyword") (= (tp-val) "my")) (do (adv!) @@ -1338,19 +1476,23 @@ (fn () (let - ((tgt (cond ((at-end?) (list (quote me))) ((and (= (tp-type) "keyword") (or (= (tp-val) "then") (= (tp-val) "end") (= (tp-val) "with") (= (tp-val) "add") (= (tp-val) "remove") (= (tp-val) "set") (= (tp-val) "put") (= (tp-val) "toggle") (= (tp-val) "hide") (= (tp-val) "show"))) (list (quote me))) (true (parse-expr))))) + ((tgt (cond ((at-end?) (list (quote me))) ((and (= (tp-type) "keyword") (or (= (tp-val) "then") (= (tp-val) "end") (= (tp-val) "with") (= (tp-val) "when") (= (tp-val) "add") (= (tp-val) "remove") (= (tp-val) "set") (= (tp-val) "put") (= (tp-val) "toggle") (= (tp-val) "hide") (= (tp-val) "show"))) (list (quote me))) (true (parse-expr))))) (let - ((strategy (if (match-kw "with") (if (at-end?) "display" (let ((s (tp-val))) (adv!) s)) "display"))) - (list (quote hide) tgt strategy))))) + ((strategy (if (match-kw "with") (if (at-end?) "display" (let ((s (tp-val))) (do (adv!) (cond ((at-end?) s) ((= (tp-type) "colon") (do (adv!) (let ((v (tp-val))) (do (adv!) (str s ":" v))))) ((= (tp-type) "local") (let ((v (tp-val))) (do (adv!) (str s ":" v)))) (true s))))) "display"))) + (let + ((when-cond (if (and (= (tp-type) "keyword") (= (tp-val) "when")) (do (adv!) (parse-expr)) nil))) + (list (quote hide) tgt strategy when-cond)))))) (define parse-show-cmd (fn () (let - ((tgt (cond ((at-end?) (list (quote me))) ((and (= (tp-type) "keyword") (or (= (tp-val) "then") (= (tp-val) "end") (= (tp-val) "with") (= (tp-val) "add") (= (tp-val) "remove") (= (tp-val) "set") (= (tp-val) "put") (= (tp-val) "toggle") (= (tp-val) "hide") (= (tp-val) "show"))) (list (quote me))) (true (parse-expr))))) + ((tgt (cond ((at-end?) (list (quote me))) ((and (= (tp-type) "keyword") (or (= (tp-val) "then") (= (tp-val) "end") (= (tp-val) "with") (= (tp-val) "when") (= (tp-val) "add") (= (tp-val) "remove") (= (tp-val) "set") (= (tp-val) "put") (= (tp-val) "toggle") (= (tp-val) "hide") (= (tp-val) "show"))) (list (quote me))) (true (parse-expr))))) (let - ((strategy (if (match-kw "with") (if (at-end?) "display" (let ((s (tp-val))) (adv!) s)) "display"))) - (list (quote show) tgt strategy))))) + ((strategy (if (match-kw "with") (if (at-end?) "display" (let ((s (tp-val))) (do (adv!) (cond ((at-end?) s) ((= (tp-type) "colon") (do (adv!) (let ((v (tp-val))) (do (adv!) (str s ":" v))))) ((= (tp-type) "local") (let ((v (tp-val))) (do (adv!) (str s ":" v)))) (true s))))) "display"))) + (let + ((when-cond (if (and (= (tp-type) "keyword") (= (tp-val) "when")) (do (adv!) (parse-expr)) nil))) + (list (quote show) tgt strategy when-cond)))))) (define parse-transition-cmd (fn @@ -1493,7 +1635,7 @@ (ca-collect (append acc (list arg))))))) (ca-collect (list)))) (define parse-call-cmd (fn () (parse-expr))) - (define parse-get-cmd (fn () (parse-expr))) + (define parse-get-cmd (fn () (list (quote __get-cmd) (parse-expr)))) (define parse-take-cmd (fn @@ -1501,12 +1643,34 @@ (cond ((= (tp-type) "class") (let - ((cls (do (let ((v (tp-val))) (adv!) v)))) + ((classes (list))) (let - ((from-sel (if (match-kw "from") (parse-expr) nil))) + ((collect (fn () (when (= (tp-type) "class") (let ((v (tp-val))) (adv!) (set! classes (append classes (list v))) (collect)))))) + (collect) (let - ((for-tgt (if (match-kw "for") (parse-expr) nil))) - (list (quote take!) "class" cls from-sel for-tgt))))) + ((from-sel (if (match-kw "from") (parse-expr) nil))) + (let + ((for-tgt (if (match-kw "for") (parse-expr) nil))) + (if + (= (len classes) 1) + (list + (quote take!) + "class" + (first classes) + from-sel + for-tgt) + (cons + (quote do) + (map + (fn + (cls) + (list + (quote take!) + "class" + cls + from-sel + for-tgt)) + classes)))))))) ((= (tp-type) "attr") (let ((attr-name (get (adv!) "value"))) @@ -1540,7 +1704,9 @@ (let ((n (parse-atom))) (do - (expect-kw! "of") + (if + (not (or (match-kw "of") (match-kw "from"))) + (error (str "Expected 'of' or 'from' at position " p))) (let ((coll (parse-expr))) (list (quote pick-first) coll n)))))) @@ -1550,7 +1716,9 @@ (let ((n (parse-atom))) (do - (expect-kw! "of") + (if + (not (or (match-kw "of") (match-kw "from"))) + (error (str "Expected 'of' or 'from' at position " p))) (let ((coll (parse-expr))) (list (quote pick-last) coll n)))))) @@ -1558,14 +1726,17 @@ (do (adv!) (if - (match-kw "of") + (or (match-kw "of") (match-kw "from")) (let ((coll (parse-expr))) (list (quote pick-random) coll nil)) (let ((n (parse-atom))) (do - (expect-kw! "of") + (if + (not (or (match-kw "of") (match-kw "from"))) + (error + (str "Expected 'of' or 'from' at position " p))) (let ((coll (parse-expr))) (list (quote pick-random) coll n))))))) @@ -1579,7 +1750,10 @@ (let ((end-expr (parse-atom))) (do - (expect-kw! "of") + (if + (not (or (match-kw "of") (match-kw "from"))) + (error + (str "Expected 'of' or 'from' at position " p))) (let ((coll (parse-expr))) (list (quote pick-items) coll start-expr end-expr)))))))) @@ -1588,7 +1762,7 @@ (adv!) (expect-kw! "of") (let - ((regex (parse-expr))) + ((regex (parse-atom))) (do (cond ((match-kw "of") nil) @@ -1606,7 +1780,7 @@ (adv!) (expect-kw! "of") (let - ((regex (parse-expr))) + ((regex (parse-atom))) (do (cond ((match-kw "of") nil) @@ -1619,10 +1793,26 @@ (let ((haystack (parse-expr))) (list (quote pick-matches) regex haystack)))))) + ((and (= typ "ident") (= val "item")) + (do + (adv!) + (let + ((n (parse-expr))) + (do + (if + (not (or (match-kw "of") (match-kw "from"))) + (error (str "Expected 'of' or 'from' at position " p))) + (let + ((coll (parse-expr))) + (list + (quote pick-items) + coll + n + (list (quote +) n 1))))))) (true (error (str - "Expected first/last/random/items/match/matches after 'pick' at " + "Expected first/last/random/item/items/match/matches after 'pick' at " p))))))) (define parse-go-cmd @@ -1697,7 +1887,7 @@ (match-kw "of") (list (make-symbol ".") (parse-expr) val) (cond - ((= val "result") (list (quote it))) + ((= val "result") (quote the-result)) ((= val "first") (parse-pos-kw (quote first))) ((= val "last") (parse-pos-kw (quote last))) ((= val "closest") (parse-trav (quote closest))) diff --git a/lib/hyperscript/runtime.sx b/lib/hyperscript/runtime.sx index 35586471..ce30f325 100644 --- a/lib/hyperscript/runtime.sx +++ b/lib/hyperscript/runtime.sx @@ -22,7 +22,13 @@ ;; Stock hyperscript queues by default; "every" disables queuing. (define hs-on - (fn (target event-name handler) (dom-listen target event-name handler))) + (fn + (target event-name handler) + (let + ((unlisten (dom-listen target event-name handler)) + (prev (or (dom-get-data target "hs-unlisteners") (list)))) + (dom-set-data target "hs-unlisteners" (append prev (list unlisten))) + unlisten))) ;; Run an initializer function immediately. ;; (hs-init thunk) — called at element boot time @@ -88,7 +94,7 @@ ((or (= prop "display") (= prop "opacity")) (if (or (= cur "none") (= cur "0")) - (dom-set-style target prop (if (= prop "opacity") "1" "")) + (dom-set-style target prop (if (= prop "opacity") "1" "block")) (dom-set-style target prop (if (= prop "display") "none" "0")))) (true (if @@ -167,6 +173,45 @@ (fn (el name val) (if (nil? val) (dom-remove-attr el name) (dom-set-attr el name val)))) + (define + hs-toggle-attr! + (fn + (el name) + (if + (dom-has-attr? el name) + (dom-remove-attr el name) + (dom-set-attr el name "")))) + (define + hs-toggle-attr-val! + (fn + (el name val) + (if + (= (dom-get-attr el name) val) + (dom-remove-attr el name) + (dom-set-attr el name val)))) + (define + hs-toggle-attr-between! + (fn + (el name val1 val2) + (if + (= (dom-get-attr el name) val1) + (dom-set-attr el name val2) + (dom-set-attr el name val1)))) + (define + hs-toggle-attr-diff! + (fn + (el n1 v1 n2 v2) + (if + (dom-has-attr? el n1) + (do (dom-remove-attr el n1) (dom-set-attr el n2 v2)) + (do + (when (dom-has-attr? el n2) (dom-remove-attr el n2)) + (dom-set-attr el n1 v1))))) + (define + hs-set-inner-html! + (fn + (target value) + (do (dom-set-inner-html target value) (hs-boot-subtree! target)))) (define hs-put! (fn @@ -407,19 +452,24 @@ hs-query-all (fn (sel) (host-call (dom-body) "querySelectorAll" sel))) +(define + hs-query-all-in + (fn + (sel target) + (if + (nil? target) + (hs-query-all sel) + (host-call target "querySelectorAll" sel)))) + (define hs-list-set - (fn (lst idx val) (map-indexed (fn (i x) (if (= i idx) val x)) lst))) + (fn + (lst idx val) + (append (take lst idx) (cons val (drop lst (+ idx 1)))))) (define hs-to-number - (fn - (v) - (cond - ((number? v) v) - ((string? v) (or (parse-number v) 0)) - ((nil? v) 0) - (true (or (parse-number (str v)) 0))))) + (fn (v) (if (number? v) v (or (parse-number (str v)) 0)))) (define hs-query-first @@ -490,6 +540,10 @@ ((= signal "hs-continue") (hs-repeat-while cond-fn thunk)) (true (hs-repeat-while cond-fn thunk))))))) + + + + (define hs-repeat-until (fn @@ -502,10 +556,6 @@ (if (cond-fn) nil (hs-repeat-until cond-fn thunk))) (true (if (cond-fn) nil (hs-repeat-until cond-fn thunk))))))) - - - - (define hs-for-each (fn @@ -525,27 +575,38 @@ ((= signal "hs-continue") (do-loop (rest remaining))) (true (do-loop (rest remaining)))))))) (do-loop items)))) - +;; ── Sandbox/test runtime additions ────────────────────────────── +;; Property access — dot notation and .length (begin (define hs-append (fn (target value) (cond + ((nil? target) value) ((string? target) (str target value)) ((list? target) (append target (list value))) + ((hs-element? target) + (do + (dom-insert-adjacent-html target "beforeend" (str value)) + target)) (true (str target value))))) (define hs-append! - (fn (value target) (dom-insert-adjacent-html target "beforeend" value)))) -;; ── Sandbox/test runtime additions ────────────────────────────── -;; Property access — dot notation and .length + (fn + (value target) + (cond + ((nil? target) nil) + ((hs-element? target) + (dom-insert-adjacent-html target "beforeend" (str value))) + (true nil))))) +;; DOM query stub — sandbox returns empty list (define hs-fetch (fn (url format) (perform (list "io-fetch" url (if format format "text"))))) -;; DOM query stub — sandbox returns empty list +;; Method dispatch — obj.method(args) (define hs-coerce (fn @@ -636,7 +697,24 @@ (map (fn (k) (list k (get value k))) (keys value)) value)) (true value)))) -;; Method dispatch — obj.method(args) + +;; ── 0.9.90 features ───────────────────────────────────────────── +;; beep! — debug logging, returns value unchanged +(define + hs-default? + (fn + (v) + (cond + ((nil? v) true) + ((and (string? v) (= v "")) true) + (true false)))) +;; Property-based is — check obj.key truthiness +(define + hs-array-set! + (fn + (arr i v) + (if (list? arr) (do (set-nth! arr i v) v) (host-set! arr i v)))) +;; Array slicing (inclusive both ends) (define hs-add (fn @@ -646,9 +724,7 @@ ((list? b) (cons a b)) ((or (string? a) (string? b)) (str a b)) (true (+ a b))))) - -;; ── 0.9.90 features ───────────────────────────────────────────── -;; beep! — debug logging, returns value unchanged +;; Collection: sorted by (define hs-make (fn @@ -659,13 +735,13 @@ ((= type-name "Set") (list)) ((= type-name "Map") (dict)) (true (dict))))) -;; Property-based is — check obj.key truthiness +;; Collection: sorted by descending (define hs-install (fn (behavior-fn) (behavior-fn me))) -;; Array slicing (inclusive both ends) +;; Collection: split by (define hs-measure (fn (target) (perform (list (quote io-measure) target)))) -;; Collection: sorted by +;; Collection: joined by (define hs-transition (fn @@ -678,7 +754,7 @@ (str prop " " (/ duration 1000) "s"))) (dom-set-style target prop value) (when duration (hs-settle target)))) -;; Collection: sorted by descending + (define hs-transition-from (fn @@ -692,7 +768,7 @@ (str prop " " (/ duration 1000) "s"))) (dom-set-style target prop (str to-val)) (when duration (hs-settle target)))) -;; Collection: split by + (define hs-type-check (fn @@ -712,7 +788,7 @@ (= (host-typeof value) "element") (= (host-typeof value) "text"))) (true (= (host-typeof value) (downcase type-name))))))) -;; Collection: joined by + (define hs-type-check-strict (fn @@ -745,11 +821,26 @@ ((nil? suffix) false) (true (ends-with? (str s) (str suffix)))))) +(define + hs-scoped-set! + (fn (el name val) (dom-set-data el (str "hs-local-" name) val))) + +(define + hs-scoped-get + (fn (el name) (dom-get-data el (str "hs-local-" name)))) + (define hs-precedes? (fn (a b) - (cond ((nil? a) false) ((nil? b) false) (true (< (str a) (str b)))))) + (cond + ((nil? a) false) + ((nil? b) false) + ((and (dict? a) (dict? b)) + (let + ((pos (host-call a "compareDocumentPosition" b))) + (if (number? pos) (not (= 0 (mod (/ pos 4) 2))) false))) + (true (< (str a) (str b)))))) (define hs-follows? @@ -840,7 +931,18 @@ (= obj (nth r 1)) (= obj nil))))))) -(define precedes? (fn (a b) (< (str a) (str b)))) +(define + precedes? + (fn + (a b) + (cond + ((nil? a) false) + ((nil? b) false) + ((and (dict? a) (dict? b)) + (let + ((pos (host-call a "compareDocumentPosition" b))) + (if (number? pos) (not (= 0 (mod (/ pos 4) 2))) false))) + (true (< (str a) (str b)))))) (define hs-empty? @@ -1124,33 +1226,109 @@ (host-call el "removeAttribute" "open") (dom-set-prop el "open" false))))))) -(define - hs-hide! - (fn - (el strategy) - (let - ((tag (dom-get-prop el "tagName"))) - (cond - ((= tag "DIALOG") - (when (dom-has-attr? el "open") (host-call el "close"))) - ((= tag "DETAILS") (dom-set-prop el "open" false)) - ((= strategy "opacity") (dom-set-style el "opacity" "0")) - ((= strategy "visibility") (dom-set-style el "visibility" "hidden")) - (true (dom-set-style el "display" "none")))))) +(begin + (define + hs-hide-one! + (fn + (el strategy) + (let + ((parts (split strategy ":")) (tag (dom-get-prop el "tagName"))) + (let + ((prop (first parts)) + (val (if (> (len parts) 1) (nth parts 1) nil))) + (cond + ((= tag "DIALOG") + (when (dom-has-attr? el "open") (host-call el "close"))) + ((= tag "DETAILS") (dom-set-prop el "open" false)) + ((= prop "opacity") + (dom-set-style el "opacity" (if val val "0"))) + ((= prop "visibility") + (dom-set-style el "visibility" (if val val "hidden"))) + ((= prop "hidden") (dom-set-attr el "hidden" "")) + ((= prop "twDisplay") (dom-add-class el "hidden")) + ((= prop "twVisibility") (dom-add-class el "invisible")) + ((= prop "twOpacity") (dom-add-class el "opacity-0")) + (true (dom-set-style el "display" (if val val "none")))))))) + (define + hs-hide! + (fn + (target strategy) + (if + (list? target) + (do (for-each (fn (el) (hs-hide-one! el strategy)) target) target) + (do (hs-hide-one! target strategy) target))))) + +(begin + (define + hs-show-one! + (fn + (el strategy) + (let + ((parts (split strategy ":")) (tag (dom-get-prop el "tagName"))) + (let + ((prop (first parts)) + (val (if (> (len parts) 1) (nth parts 1) nil))) + (cond + ((= tag "DIALOG") + (when + (not (dom-has-attr? el "open")) + (host-call el "showModal"))) + ((= tag "DETAILS") (dom-set-prop el "open" true)) + ((= prop "opacity") + (dom-set-style el "opacity" (if val val "1"))) + ((= prop "visibility") + (dom-set-style el "visibility" (if val val "visible"))) + ((= prop "hidden") (dom-remove-attr el "hidden")) + ((= prop "twDisplay") (dom-remove-class el "hidden")) + ((= prop "twVisibility") (dom-remove-class el "invisible")) + ((= prop "twOpacity") (dom-remove-class el "opacity-0")) + (true (dom-set-style el "display" (if val val "block")))))))) + (define + hs-show! + (fn + (target strategy) + (if + (list? target) + (do (for-each (fn (el) (hs-show-one! el strategy)) target) target) + (do (hs-show-one! target strategy) target))))) (define - hs-show! + hs-show-when! (fn - (el strategy) + (target strategy pred) (let - ((tag (dom-get-prop el "tagName"))) - (cond - ((= tag "DIALOG") - (when (not (dom-has-attr? el "open")) (host-call el "showModal"))) - ((= tag "DETAILS") (dom-set-prop el "open" true)) - ((= strategy "opacity") (dom-set-style el "opacity" "1")) - ((= strategy "visibility") (dom-set-style el "visibility" "visible")) - (true (dom-set-style el "display" "")))))) + ((items (if (list? target) target (list target)))) + (let + ((matched (list))) + (do + (for-each + (fn + (el) + (if + (pred el) + (do (hs-show-one! el strategy) (append! matched el)) + (hs-hide-one! el strategy))) + items) + matched))))) + +(define + hs-hide-when! + (fn + (target strategy pred) + (let + ((items (if (list? target) target (list target)))) + (let + ((matched (list))) + (do + (for-each + (fn + (el) + (if + (pred el) + (do (hs-hide-one! el strategy) (append! matched el)) + (hs-show-one! el strategy))) + items) + matched))))) (define hs-first (fn (lst) (first lst))) @@ -1390,7 +1568,7 @@ false (let ((store (host-get el "__hs_vars"))) - (if (nil? store) false (has-key? store name)))))) + (if (nil? store) false (host-call store "hasOwnProperty" name)))))) (define hs-dom-get-var-raw @@ -1409,7 +1587,7 @@ (do (when (nil? (host-get el "__hs_vars")) - (host-set! el "__hs_vars" (dict))) + (host-set! el "__hs_vars" (host-new "Object"))) (host-set! (host-get el "__hs_vars") name val) (when changed (hs-dom-fire-watchers! el name val)))))) diff --git a/lib/hyperscript/tokenizer.sx b/lib/hyperscript/tokenizer.sx index f846e34d..9440f95a 100644 --- a/lib/hyperscript/tokenizer.sx +++ b/lib/hyperscript/tokenizer.sx @@ -436,6 +436,8 @@ (let ((ch (hs-cur)) (start pos)) (cond + (and (= ch "-") (< (+ pos 1) src-len) (= (hs-peek 1) "-")) + (do (hs-advance! 2) (skip-comment!) (scan!)) (and (= ch "/") (< (+ pos 1) src-len) (= (hs-peek 1) "/")) (do (hs-advance! 2) (skip-comment!) (scan!)) (and @@ -613,6 +615,8 @@ (do (hs-emit! "op" "\\" start) (hs-advance! 1) (scan!)) (= ch ":") (do (hs-emit! "colon" ":" start) (hs-advance! 1) (scan!)) + (= ch "|") + (do (hs-emit! "op" "|" start) (hs-advance! 1) (scan!)) :else (do (hs-advance! 1) (scan!))))))) (scan!) (hs-emit! "eof" nil pos) diff --git a/shared/static/wasm/sx/hs-compiler.sx b/shared/static/wasm/sx/hs-compiler.sx index 21dbe0ed..05d81a2e 100644 --- a/shared/static/wasm/sx/hs-compiler.sx +++ b/shared/static/wasm/sx/hs-compiler.sx @@ -85,18 +85,18 @@ (nth target 1) value)) ((= th (quote me)) - (list (quote dom-set-inner-html) (quote me) value)) + (list (quote hs-set-inner-html!) (quote me) value)) ((= th (quote it)) (list (quote set!) (quote it) value)) ((= th (quote query)) - (list (quote dom-set-inner-html) (hs-to-sx target) value)) + (list (quote hs-set-inner-html!) (hs-to-sx target) value)) ((= th (quote array-index)) (list - (quote host-set!) + (quote hs-array-set!) (hs-to-sx (nth target 1)) (hs-to-sx (nth target 2)) value)) ((or (= th (quote next)) (= th (quote previous)) (= th (quote closest))) - (list (quote dom-set-inner-html) (hs-to-sx target) value)) + (list (quote hs-set-inner-html!) (hs-to-sx target) value)) ((= th (quote of)) (let ((prop-ast (nth target 1)) (obj-ast (nth target 2))) @@ -162,10 +162,19 @@ (let ((wrapped-body (if catch-info (let ((var (make-symbol (nth catch-info 0))) (catch-body (hs-to-sx (nth catch-info 1)))) (if finally-info (list (quote do) (list (quote guard) (list var (list true catch-body)) compiled-body) (hs-to-sx finally-info)) (list (quote guard) (list var (list true catch-body)) compiled-body))) (if finally-info (list (quote do) compiled-body (hs-to-sx finally-info)) compiled-body))) (handler - (list - (quote fn) - (list (quote event)) - wrapped-body))) + (let + ((uses-the-result? (fn (expr) (cond ((= expr (quote the-result)) true) ((list? expr) (some (fn (x) (uses-the-result? x)) expr)) (true false))))) + (list + (quote fn) + (list (quote event)) + (if + (uses-the-result? wrapped-body) + (list + (quote let) + (list + (list (quote the-result) nil)) + wrapped-body) + wrapped-body))))) (if every? (list @@ -443,9 +452,7 @@ (quote __hs-new) (list (quote +) - (list - (quote hs-to-number) - (list (quote nth) var-sym (quote __hs-idx))) + (list (quote nth) var-sym (quote __hs-idx)) amount))) (list (quote do) @@ -463,10 +470,7 @@ ((t (hs-to-sx expr))) (list (quote let) - (list - (list - (quote __hs-new) - (list (quote +) (list (quote hs-to-number) t) amount))) + (list (list (quote __hs-new) (list (quote +) t amount))) (list (quote do) (list (quote set!) t (quote __hs-new)) @@ -564,9 +568,7 @@ (quote __hs-new) (list (quote -) - (list - (quote hs-to-number) - (list (quote nth) var-sym (quote __hs-idx))) + (list (quote nth) var-sym (quote __hs-idx)) amount))) (list (quote do) @@ -584,10 +586,7 @@ ((t (hs-to-sx expr))) (list (quote let) - (list - (list - (quote __hs-new) - (list (quote -) (list (quote hs-to-number) t) amount))) + (list (list (quote __hs-new) (list (quote -) t amount))) (list (quote do) (list (quote set!) t (quote __hs-new)) @@ -870,6 +869,11 @@ ((= head (quote ref)) (make-symbol (nth ast 1))) ((= head (quote query)) (list (quote hs-query-first) (nth ast 1))) + ((= head (quote query-scoped)) + (list + (quote hs-query-all-in) + (nth ast 1) + (hs-to-sx (nth ast 2)))) ((= head (quote attr)) (list (quote dom-get-attr) @@ -1163,6 +1167,14 @@ (quote set!) (hs-to-sx tgt) (list (quote hs-add-to!) val (hs-to-sx tgt))))) + ((= head (quote add-attr)) + (let + ((tgt (nth ast 3))) + (list + (quote hs-set-attr!) + (hs-to-sx tgt) + (nth ast 1) + (hs-to-sx (nth ast 2))))) ((= head (quote remove-value)) (let ((val (hs-to-sx (nth ast 1))) (tgt (nth ast 2))) @@ -1296,6 +1308,20 @@ (nth ast 1) (hs-to-sx (nth ast 2)) (hs-to-sx (nth ast 3)))) + ((= head (quote toggle-attr-val)) + (list + (quote hs-toggle-attr-val!) + (hs-to-sx (nth ast 3)) + (nth ast 1) + (hs-to-sx (nth ast 2)))) + ((= head (quote toggle-attr-diff)) + (list + (quote hs-toggle-attr-diff!) + (hs-to-sx (nth ast 5)) + (nth ast 1) + (hs-to-sx (nth ast 2)) + (nth ast 3) + (hs-to-sx (nth ast 4)))) ((= head (quote set!)) (emit-set (nth ast 1) (hs-to-sx (nth ast 2)))) ((= head (quote put!)) @@ -1359,13 +1385,48 @@ ((= head (quote hide)) (let ((tgt (hs-to-sx (nth ast 1))) - (strategy (if (> (len ast) 2) (nth ast 2) "display"))) - (list (quote hs-hide!) tgt strategy))) + (strategy (if (> (len ast) 2) (nth ast 2) "display")) + (when-cond (if (> (len ast) 3) (nth ast 3) nil))) + (if + (nil? when-cond) + (list (quote hs-hide!) tgt strategy) + (list + (quote hs-hide-when!) + tgt + strategy + (list + (quote fn) + (list (quote it)) + (hs-to-sx when-cond)))))) ((= head (quote show)) (let ((tgt (hs-to-sx (nth ast 1))) - (strategy (if (> (len ast) 2) (nth ast 2) "display"))) - (list (quote hs-show!) tgt strategy))) + (strategy (if (> (len ast) 2) (nth ast 2) "display")) + (when-cond (if (> (len ast) 3) (nth ast 3) nil))) + (if + (nil? when-cond) + (list (quote hs-show!) tgt strategy) + (list + (quote let) + (list + (list + (quote __hs-show-r) + (list + (quote hs-show-when!) + tgt + strategy + (list + (quote fn) + (list (quote it)) + (hs-to-sx when-cond))))) + (list + (quote begin) + (list + (quote set!) + (quote the-result) + (quote __hs-show-r)) + (list (quote set!) (quote it) (quote __hs-show-r)) + (quote __hs-show-r)))))) ((= head (quote transition)) (emit-transition ast)) ((= head (quote transition-from)) (let @@ -1424,6 +1485,14 @@ (list (quote hs-settle) (quote me))) ((= head (quote go)) (list (quote hs-navigate!) (hs-to-sx (nth ast 1)))) + ((= head (quote __get-cmd)) + (let + ((val (hs-to-sx (nth ast 1)))) + (list + (quote begin) + (list (quote set!) (quote the-result) val) + (list (quote set!) (quote it) val) + val))) ((= head (quote append!)) (let ((tgt (hs-to-sx (nth ast 2))) @@ -1648,11 +1717,13 @@ (list (quote hs-reset!) (hs-to-sx (nth ast 1)))) ((= head (quote default!)) (let - ((t (hs-to-sx (nth ast 1))) (v (hs-to-sx (nth ast 2)))) + ((tgt-ast (nth ast 1)) + (read (hs-to-sx (nth ast 1))) + (v (hs-to-sx (nth ast 2)))) (list (quote when) - (list (quote nil?) t) - (list (quote set!) t v)))) + (list (quote hs-default?) read) + (emit-set tgt-ast v)))) ((= head (quote hs-is)) (list (quote hs-is) diff --git a/shared/static/wasm/sx/hs-integration.sx b/shared/static/wasm/sx/hs-integration.sx index 7fd9bf4b..0c3feaa9 100644 --- a/shared/static/wasm/sx/hs-integration.sx +++ b/shared/static/wasm/sx/hs-integration.sx @@ -16,6 +16,14 @@ (fn (sx) (define vars (list)) + (define + reserved + (list + (quote me) + (quote it) + (quote event) + (quote you) + (quote yourself))) (define walk (fn @@ -30,7 +38,9 @@ (let ((name (nth node 1))) (when - (not (some (fn (v) (= v name)) vars)) + (and + (not (some (fn (v) (= v name)) vars)) + (not (some (fn (v) (= v name)) reserved))) (set! vars (cons name vars))))) (for-each walk node)))) (walk sx) diff --git a/shared/static/wasm/sx/hs-parser.sx b/shared/static/wasm/sx/hs-parser.sx index 0bdb84bb..a06076c1 100644 --- a/shared/static/wasm/sx/hs-parser.sx +++ b/shared/static/wasm/sx/hs-parser.sx @@ -95,6 +95,13 @@ (do (adv!) (list kind (str "." val) (list (quote me))))) ((= typ "id") (do (adv!) (list kind (str "#" val) (list (quote me))))) + ((= typ "attr") + (do + (adv!) + (list + (quote attr) + val + (list kind (str "[" val "]") (list (quote me)))))) (true (list kind "*" (list (quote me)))))))) (define parse-pos-kw @@ -146,8 +153,10 @@ (do (adv!) (list (quote me)))) ((and (= typ "keyword") (= val "I")) (do (adv!) (list (quote me)))) - ((and (= typ "keyword") (or (= val "it") (= val "result"))) + ((and (= typ "keyword") (= val "it")) (do (adv!) (list (quote it)))) + ((and (= typ "keyword") (= val "result")) + (do (adv!) (quote the-result))) ((and (= typ "keyword") (= val "event")) (do (adv!) (list (quote event)))) ((and (= typ "keyword") (= val "target")) @@ -174,7 +183,18 @@ (do (adv!) (parse-pos-kw (quote last)))) ((= typ "id") (do (adv!) (list (quote query) (str "#" val)))) - ((= typ "selector") (do (adv!) (list (quote query) val))) + ((= typ "selector") + (do + (adv!) + (if + (and (= (tp-type) "keyword") (= (tp-val) "in")) + (do + (adv!) + (list + (quote query-scoped) + val + (parse-cmp (parse-arith (parse-poss (parse-atom)))))) + (list (quote query) val)))) ((= typ "attr") (do (adv!) (list (quote attr) val (list (quote me))))) ((= typ "style") @@ -426,7 +446,7 @@ (list (quote type-check) left type-name))))))) (true (let - ((right (parse-expr))) + ((right (parse-cmp (parse-arith (parse-poss (parse-atom)))))) (if (match-kw "ignoring") (do @@ -892,6 +912,18 @@ (let ((tgt (if (match-kw "to") (parse-expr) (list (quote me))))) (list (quote set-styles) (reverse pairs) tgt))))) + ((and (= (tp-type) "bracket-open") (> (len tokens) (+ p 1)) (= (get (nth tokens (+ p 1)) "type") "attr")) + (do + (adv!) + (let + ((attr-name (get (adv!) "value"))) + (when (and (= (tp-type) "op") (= (tp-val) "=")) (adv!)) + (let + ((attr-val (parse-expr))) + (when (= (tp-type) "bracket-close") (adv!)) + (let + ((tgt (parse-tgt-kw "to" (list (quote me))))) + (list (quote add-attr) attr-name attr-val tgt)))))) (true (let ((value (parse-expr))) @@ -978,20 +1010,58 @@ () (cond ((match-kw "between") - (if - (= (tp-type) "class") - (let - ((cls1 (do (let ((v (tp-val))) (adv!) v)))) - (expect-kw! "and") - (if - (= (tp-type) "class") - (let - ((cls2 (do (let ((v (tp-val))) (adv!) v)))) + (cond + ((= (tp-type) "class") + (let + ((cls1 (do (let ((v (tp-val))) (adv!) v)))) + (expect-kw! "and") + (if + (= (tp-type) "class") (let - ((tgt (parse-tgt-kw "on" (list (quote me))))) - (list (quote toggle-between) cls1 cls2 tgt))) - nil)) - nil)) + ((cls2 (do (let ((v (tp-val))) (adv!) v)))) + (let + ((tgt (parse-tgt-kw "on" (list (quote me))))) + (list (quote toggle-between) cls1 cls2 tgt))) + nil))) + ((and (= (tp-type) "bracket-open") (> (len tokens) (+ p 1)) (= (get (nth tokens (+ p 1)) "type") "attr")) + (do + (adv!) + (let + ((n1 (get (adv!) "value"))) + (when + (and (= (tp-type) "op") (= (tp-val) "=")) + (adv!)) + (let + ((v1 (parse-expr))) + (when (= (tp-type) "bracket-close") (adv!)) + (expect-kw! "and") + (when (= (tp-type) "bracket-open") (adv!)) + (let + ((n2 (get (adv!) "value"))) + (when + (and (= (tp-type) "op") (= (tp-val) "=")) + (adv!)) + (let + ((v2 (parse-expr))) + (when (= (tp-type) "bracket-close") (adv!)) + (let + ((tgt (parse-tgt-kw "on" (list (quote me))))) + (if + (= n1 n2) + (list + (quote toggle-attr-between) + n1 + v1 + v2 + tgt) + (list + (quote toggle-attr-diff) + n1 + v1 + n2 + v2 + tgt))))))))) + (true nil))) ((= (tp-type) "class") (let ((cls (do (let ((v (tp-val))) (adv!) v)))) @@ -1064,6 +1134,18 @@ val2 tgt))) (list (quote toggle-attr) attr-name tgt))))) + ((and (= (tp-type) "bracket-open") (> (len tokens) (+ p 1)) (= (get (nth tokens (+ p 1)) "type") "attr")) + (do + (adv!) + (let + ((attr-name (get (adv!) "value"))) + (when (and (= (tp-type) "op") (= (tp-val) "=")) (adv!)) + (let + ((attr-val (parse-expr))) + (when (= (tp-type) "bracket-close") (adv!)) + (let + ((tgt (parse-tgt-kw "on" (list (quote me))))) + (list (quote toggle-attr-val) attr-name attr-val tgt)))))) ((and (= (tp-type) "keyword") (= (tp-val) "my")) (do (adv!) @@ -1338,19 +1420,23 @@ (fn () (let - ((tgt (cond ((at-end?) (list (quote me))) ((and (= (tp-type) "keyword") (or (= (tp-val) "then") (= (tp-val) "end") (= (tp-val) "with") (= (tp-val) "add") (= (tp-val) "remove") (= (tp-val) "set") (= (tp-val) "put") (= (tp-val) "toggle") (= (tp-val) "hide") (= (tp-val) "show"))) (list (quote me))) (true (parse-expr))))) + ((tgt (cond ((at-end?) (list (quote me))) ((and (= (tp-type) "keyword") (or (= (tp-val) "then") (= (tp-val) "end") (= (tp-val) "with") (= (tp-val) "when") (= (tp-val) "add") (= (tp-val) "remove") (= (tp-val) "set") (= (tp-val) "put") (= (tp-val) "toggle") (= (tp-val) "hide") (= (tp-val) "show"))) (list (quote me))) (true (parse-expr))))) (let ((strategy (if (match-kw "with") (if (at-end?) "display" (let ((s (tp-val))) (adv!) s)) "display"))) - (list (quote hide) tgt strategy))))) + (let + ((when-cond (if (and (= (tp-type) "keyword") (= (tp-val) "when")) (do (adv!) (parse-expr)) nil))) + (list (quote hide) tgt strategy when-cond)))))) (define parse-show-cmd (fn () (let - ((tgt (cond ((at-end?) (list (quote me))) ((and (= (tp-type) "keyword") (or (= (tp-val) "then") (= (tp-val) "end") (= (tp-val) "with") (= (tp-val) "add") (= (tp-val) "remove") (= (tp-val) "set") (= (tp-val) "put") (= (tp-val) "toggle") (= (tp-val) "hide") (= (tp-val) "show"))) (list (quote me))) (true (parse-expr))))) + ((tgt (cond ((at-end?) (list (quote me))) ((and (= (tp-type) "keyword") (or (= (tp-val) "then") (= (tp-val) "end") (= (tp-val) "with") (= (tp-val) "when") (= (tp-val) "add") (= (tp-val) "remove") (= (tp-val) "set") (= (tp-val) "put") (= (tp-val) "toggle") (= (tp-val) "hide") (= (tp-val) "show"))) (list (quote me))) (true (parse-expr))))) (let ((strategy (if (match-kw "with") (if (at-end?) "display" (let ((s (tp-val))) (adv!) s)) "display"))) - (list (quote show) tgt strategy))))) + (let + ((when-cond (if (and (= (tp-type) "keyword") (= (tp-val) "when")) (do (adv!) (parse-expr)) nil))) + (list (quote show) tgt strategy when-cond)))))) (define parse-transition-cmd (fn @@ -1493,7 +1579,7 @@ (ca-collect (append acc (list arg))))))) (ca-collect (list)))) (define parse-call-cmd (fn () (parse-expr))) - (define parse-get-cmd (fn () (parse-expr))) + (define parse-get-cmd (fn () (list (quote __get-cmd) (parse-expr)))) (define parse-take-cmd (fn @@ -1501,12 +1587,34 @@ (cond ((= (tp-type) "class") (let - ((cls (do (let ((v (tp-val))) (adv!) v)))) + ((classes (list))) (let - ((from-sel (if (match-kw "from") (parse-expr) nil))) + ((collect (fn () (when (= (tp-type) "class") (let ((v (tp-val))) (adv!) (set! classes (append classes (list v))) (collect)))))) + (collect) (let - ((for-tgt (if (match-kw "for") (parse-expr) nil))) - (list (quote take!) "class" cls from-sel for-tgt))))) + ((from-sel (if (match-kw "from") (parse-expr) nil))) + (let + ((for-tgt (if (match-kw "for") (parse-expr) nil))) + (if + (= (len classes) 1) + (list + (quote take!) + "class" + (first classes) + from-sel + for-tgt) + (cons + (quote do) + (map + (fn + (cls) + (list + (quote take!) + "class" + cls + from-sel + for-tgt)) + classes)))))))) ((= (tp-type) "attr") (let ((attr-name (get (adv!) "value"))) @@ -1588,7 +1696,7 @@ (adv!) (expect-kw! "of") (let - ((regex (parse-expr))) + ((regex (parse-atom))) (do (cond ((match-kw "of") nil) @@ -1606,7 +1714,7 @@ (adv!) (expect-kw! "of") (let - ((regex (parse-expr))) + ((regex (parse-atom))) (do (cond ((match-kw "of") nil) @@ -1697,7 +1805,7 @@ (match-kw "of") (list (make-symbol ".") (parse-expr) val) (cond - ((= val "result") (list (quote it))) + ((= val "result") (quote the-result)) ((= val "first") (parse-pos-kw (quote first))) ((= val "last") (parse-pos-kw (quote last))) ((= val "closest") (parse-trav (quote closest))) diff --git a/shared/static/wasm/sx/hs-runtime.sx b/shared/static/wasm/sx/hs-runtime.sx index 8ccdf9a3..c0df704d 100644 --- a/shared/static/wasm/sx/hs-runtime.sx +++ b/shared/static/wasm/sx/hs-runtime.sx @@ -22,7 +22,13 @@ ;; Stock hyperscript queues by default; "every" disables queuing. (define hs-on - (fn (target event-name handler) (dom-listen target event-name handler))) + (fn + (target event-name handler) + (let + ((unlisten (dom-listen target event-name handler)) + (prev (or (dom-get-data target "hs-unlisteners") (list)))) + (dom-set-data target "hs-unlisteners" (append prev (list unlisten))) + unlisten))) ;; Run an initializer function immediately. ;; (hs-init thunk) — called at element boot time @@ -156,26 +162,116 @@ (dom-set-attr target name "")))))))) ;; First element matching selector within a scope. -(define - hs-put! - (fn - (value pos target) - (cond - ((= pos "into") - (if (list? target) target (dom-set-inner-html target value))) - ((= pos "before") - (dom-insert-adjacent-html target "beforebegin" value)) - ((= pos "after") (dom-insert-adjacent-html target "afterend" value)) - ((= pos "start") - (if - (list? target) - (append! target value 0) - (dom-insert-adjacent-html target "afterbegin" value))) - ((= pos "end") - (if - (list? target) - (append! target value) - (dom-insert-adjacent-html target "beforeend" value)))))) +(begin + (define + hs-element? + (fn + (v) + (and v (or (host-get v "nodeType") (host-get v "__mock_type"))))) + (define + hs-set-attr! + (fn + (el name val) + (if (nil? val) (dom-remove-attr el name) (dom-set-attr el name val)))) + (define + hs-toggle-attr! + (fn + (el name) + (if + (dom-has-attr? el name) + (dom-remove-attr el name) + (dom-set-attr el name "")))) + (define + hs-toggle-attr-val! + (fn + (el name val) + (if + (= (dom-get-attr el name) val) + (dom-remove-attr el name) + (dom-set-attr el name val)))) + (define + hs-toggle-attr-between! + (fn + (el name val1 val2) + (if + (= (dom-get-attr el name) val1) + (dom-set-attr el name val2) + (dom-set-attr el name val1)))) + (define + hs-toggle-attr-diff! + (fn + (el n1 v1 n2 v2) + (if + (dom-has-attr? el n1) + (do (dom-remove-attr el n1) (dom-set-attr el n2 v2)) + (do + (when (dom-has-attr? el n2) (dom-remove-attr el n2)) + (dom-set-attr el n1 v1))))) + (define + hs-set-inner-html! + (fn + (target value) + (do (dom-set-inner-html target value) (hs-boot-subtree! target)))) + (define + hs-put! + (fn + (value pos target) + (cond + ((= pos "into") + (cond + ((list? target) target) + ((hs-element? value) + (do + (dom-set-inner-html target "") + (host-call target "appendChild" value))) + (true + (do + (dom-set-inner-html target value) + (hs-boot-subtree! target))))) + ((= pos "before") + (if + (hs-element? value) + (let + ((parent (dom-parent target))) + (when parent (host-call parent "insertBefore" value target))) + (let + ((parent (dom-parent target))) + (do + (dom-insert-adjacent-html target "beforebegin" value) + (when parent (hs-boot-subtree! parent)))))) + ((= pos "after") + (if + (hs-element? value) + (let + ((parent (dom-parent target)) + (next (host-get target "nextSibling"))) + (when + parent + (if + next + (host-call parent "insertBefore" value next) + (host-call parent "appendChild" value)))) + (let + ((parent (dom-parent target))) + (do + (dom-insert-adjacent-html target "afterend" value) + (when parent (hs-boot-subtree! parent)))))) + ((= pos "start") + (cond + ((list? target) (append! target value 0)) + ((hs-element? value) (dom-prepend target value)) + (true + (do + (dom-insert-adjacent-html target "afterbegin" value) + (hs-boot-subtree! target))))) + ((= pos "end") + (cond + ((list? target) (append! target value)) + ((hs-element? value) (dom-append target value)) + (true + (do + (dom-insert-adjacent-html target "beforeend" value) + (hs-boot-subtree! target))))))))) ;; Last element matching selector. (define @@ -228,16 +324,22 @@ (define hs-halt! (fn - (mode) - (when - event - (cond - ((= mode "default") (host-call event "preventDefault")) - ((= mode "bubbling") (host-call event "stopPropagation")) - (true - (do - (host-call event "preventDefault") - (host-call event "stopPropagation"))))))) + (ev mode) + (do + (when + ev + (cond + ((= mode "default") (host-call ev "preventDefault")) + ((= mode "bubbling") (host-call ev "stopPropagation")) + ((= mode "the-event") + (do + (host-call ev "preventDefault") + (host-call ev "stopPropagation"))) + (true + (do + (host-call ev "preventDefault") + (host-call ev "stopPropagation"))))) + (when (not (= mode "the-event")) (raise (list "hs-return" nil)))))) ;; ── Type coercion ─────────────────────────────────────────────── @@ -249,7 +351,51 @@ ;; Make a new object of a given type. ;; (hs-make type-name) — creates empty object/collection -(define hs-reset! (fn (target) (host-call target "reset" (list)))) +(define + hs-reset! + (fn + (target) + (cond + ((list? target) (for-each (fn (el) (hs-reset! el)) target)) + ((nil? target) nil) + (true + (let + ((tag (dom-get-prop target "tagName"))) + (cond + ((= tag "FORM") (host-call target "reset" (list))) + ((or (= tag "INPUT") (= tag "TEXTAREA")) + (let + ((input-type (dom-get-prop target "type"))) + (cond + ((or (= input-type "checkbox") (= input-type "radio")) + (dom-set-prop + target + "checked" + (dom-get-prop target "defaultChecked"))) + (true + (dom-set-prop + target + "value" + (dom-get-prop target "defaultValue")))))) + ((= tag "SELECT") + (let + ((options (host-call target "querySelectorAll" "option")) + (default-val nil)) + (do + (for-each + (fn + (opt) + (when + (and + (nil? default-val) + (dom-get-prop opt "defaultSelected")) + (set! default-val (dom-get-prop opt "value")))) + options) + (when + (and (nil? default-val) (> (len options) 0)) + (set! default-val (dom-get-prop (first options) "value"))) + (when default-val (dom-set-prop target "value" default-val))))) + (true nil))))))) ;; ── Behavior installation ─────────────────────────────────────── @@ -306,6 +452,25 @@ hs-query-all (fn (sel) (host-call (dom-body) "querySelectorAll" sel))) +(define + hs-query-all-in + (fn + (sel target) + (if + (nil? target) + (hs-query-all sel) + (host-call target "querySelectorAll" sel)))) + +(define + hs-list-set + (fn + (lst idx val) + (append (take lst idx) (cons val (drop lst (+ idx 1)))))) + +(define + hs-to-number + (fn (v) (if (number? v) v (or (parse-number (str v)) 0)))) + (define hs-query-first (fn (sel) (host-call (host-global "document") "querySelector" sel))) @@ -375,6 +540,10 @@ ((= signal "hs-continue") (hs-repeat-while cond-fn thunk)) (true (hs-repeat-while cond-fn thunk))))))) + + + + (define hs-repeat-until (fn @@ -406,30 +575,38 @@ ((= signal "hs-continue") (do-loop (rest remaining))) (true (do-loop (rest remaining)))))))) (do-loop items)))) - +;; ── Sandbox/test runtime additions ────────────────────────────── +;; Property access — dot notation and .length (begin (define hs-append (fn (target value) (cond + ((nil? target) value) ((string? target) (str target value)) ((list? target) (append target (list value))) + ((hs-element? target) + (do + (dom-insert-adjacent-html target "beforeend" (str value)) + target)) (true (str target value))))) (define hs-append! - (fn (value target) (dom-insert-adjacent-html target "beforeend" value)))) - - - - - + (fn + (value target) + (cond + ((nil? target) nil) + ((hs-element? target) + (dom-insert-adjacent-html target "beforeend" (str value))) + (true nil))))) +;; DOM query stub — sandbox returns empty list (define hs-fetch (fn (url format) (perform (list "io-fetch" url (if format format "text"))))) - +;; Method dispatch — obj.method(args) (define hs-coerce (fn @@ -449,16 +626,18 @@ ((= type-name "Array") (if (list? value) value (list value))) ((= type-name "HTML") (str value)) ((= type-name "JSON") - (if - (string? value) - value - (host-call (host-global "JSON") "stringify" value))) + (cond + ((string? value) (guard (_e (true value)) (json-parse value))) + ((dict? value) (json-stringify value)) + ((list? value) (json-stringify value)) + (true value))) ((= type-name "Object") (if (string? value) - (host-call (host-global "JSON") "parse" value) + (guard (_e (true value)) (json-parse value)) value)) - ((or (= type-name "Fixed") (= type-name "Fixed:")) + ((= type-name "JSONString") (json-stringify value)) + ((or (= type-name "Fixed") (= type-name "Fixed:") (starts-with? type-name "Fixed:")) (let ((digits (if (> (string-length type-name) 6) (+ (substring type-name 6 (string-length type-name)) 0) 0)) (num (+ value 0))) @@ -466,7 +645,7 @@ (= digits 0) (str (floor num)) (let - ((factor (** 10 digits))) + ((factor (pow 10 digits))) (str (/ (floor (+ (* num factor) 0.5)) factor)))))) ((= type-name "Selector") (str value)) ((= type-name "Fragment") value) @@ -475,7 +654,7 @@ (dict? value) (map (fn (k) (get value k)) (keys value)) value)) - ((= type-name "Keys") (if (dict? value) (keys value) value)) + ((= type-name "Keys") (if (dict? value) (sort (keys value)) value)) ((= type-name "Entries") (if (dict? value) @@ -518,8 +697,24 @@ (map (fn (k) (list k (get value k))) (keys value)) value)) (true value)))) -;; ── Sandbox/test runtime additions ────────────────────────────── -;; Property access — dot notation and .length + +;; ── 0.9.90 features ───────────────────────────────────────────── +;; beep! — debug logging, returns value unchanged +(define + hs-default? + (fn + (v) + (cond + ((nil? v) true) + ((and (string? v) (= v "")) true) + (true false)))) +;; Property-based is — check obj.key truthiness +(define + hs-array-set! + (fn + (arr i v) + (if (list? arr) (do (set-nth! arr i v) v) (host-set! arr i v)))) +;; Array slicing (inclusive both ends) (define hs-add (fn @@ -529,7 +724,7 @@ ((list? b) (cons a b)) ((or (string? a) (string? b)) (str a b)) (true (+ a b))))) -;; DOM query stub — sandbox returns empty list +;; Collection: sorted by (define hs-make (fn @@ -540,15 +735,13 @@ ((= type-name "Set") (list)) ((= type-name "Map") (dict)) (true (dict))))) -;; Method dispatch — obj.method(args) +;; Collection: sorted by descending (define hs-install (fn (behavior-fn) (behavior-fn me))) - -;; ── 0.9.90 features ───────────────────────────────────────────── -;; beep! — debug logging, returns value unchanged +;; Collection: split by (define hs-measure (fn (target) (perform (list (quote io-measure) target)))) -;; Property-based is — check obj.key truthiness +;; Collection: joined by (define hs-transition (fn @@ -561,7 +754,7 @@ (str prop " " (/ duration 1000) "s"))) (dom-set-style target prop value) (when duration (hs-settle target)))) -;; Array slicing (inclusive both ends) + (define hs-transition-from (fn @@ -575,7 +768,7 @@ (str prop " " (/ duration 1000) "s"))) (dom-set-style target prop (str to-val)) (when duration (hs-settle target)))) -;; Collection: sorted by + (define hs-type-check (fn @@ -595,25 +788,68 @@ (= (host-typeof value) "element") (= (host-typeof value) "text"))) (true (= (host-typeof value) (downcase type-name))))))) -;; Collection: sorted by descending + (define hs-type-check-strict (fn (value type-name) (if (nil? value) false (hs-type-check value type-name)))) -;; Collection: split by + (define hs-strict-eq (fn (a b) (and (= (type-of a) (type-of b)) (= a b)))) -;; Collection: joined by + (define hs-eq-ignore-case (fn (a b) (= (downcase (str a)) (downcase (str b))))) +(define + hs-starts-with? + (fn + (s prefix) + (cond + ((nil? s) false) + ((nil? prefix) false) + (true (starts-with? (str s) (str prefix)))))) + +(define + hs-ends-with? + (fn + (s suffix) + (cond + ((nil? s) false) + ((nil? suffix) false) + (true (ends-with? (str s) (str suffix)))))) + +(define + hs-precedes? + (fn + (a b) + (cond ((nil? a) false) ((nil? b) false) (true (< (str a) (str b)))))) + +(define + hs-follows? + (fn + (a b) + (cond ((nil? a) false) ((nil? b) false) (true (> (str a) (str b)))))) + (define hs-starts-with-ic? (fn (str prefix) (starts-with? (downcase str) (downcase prefix)))) +(define + hs-ends-with-ic? + (fn (str suffix) (ends-with? (downcase str) (downcase suffix)))) + +(define + hs-matches-ignore-case? + (fn + (target pattern) + (cond + ((string? target) + (contains? (downcase (str target)) (downcase (str pattern)))) + (true false)))) + (define hs-contains-ignore-case? (fn @@ -651,18 +887,35 @@ ((nil? collection) false) ((string? collection) (string-contains? collection (str item))) ((list? collection) - (if - (list? item) - (filter (fn (x) (hs-contains? collection x)) item) - (if - (= (len collection) 0) - false + (cond + ((nil? item) (list)) + ((list? item) + (filter (fn (x) (hs-contains? collection x)) item)) + (true (if - (= (first collection) item) - true - (hs-contains? (rest collection) item))))) + (= (len collection) 0) + false + (if + (= (first collection) item) + true + (hs-contains? (rest collection) item)))))) (true false)))) +(define + hs-is + (fn + (obj thunk prop) + (cond + ((and (dict? obj) (some (fn (k) (= k prop)) (keys obj))) + (not (hs-falsy? (get obj prop)))) + (true + (let + ((r (cek-try thunk))) + (if + (and (list? r) (= (first r) (quote ok))) + (= obj (nth r 1)) + (= obj nil))))))) + (define precedes? (fn (a b) (< (str a) (str b)))) (define @@ -676,6 +929,17 @@ ((dict? v) (= (len (keys v)) 0)) (true false)))) +(define + hs-empty-like + (fn + (v) + (cond + ((list? v) (list)) + ((dict? v) (dict)) + ((string? v) "") + ((nil? v) nil) + (true v)))) + (define hs-empty-target! (fn @@ -694,23 +958,234 @@ (or (= input-type "checkbox") (= input-type "radio")) (dom-set-prop target "checked" false) (dom-set-prop target "value" "")))) - ((= tag "FORM") (dom-set-inner-html target "")) ((= tag "FORM") (let ((children (host-call target "querySelectorAll" "input, textarea, select"))) (for-each (fn (el) (hs-empty-target! el)) children))) (true (dom-set-inner-html target "")))))))) +(define + hs-morph-char + (fn (s p) (if (or (< p 0) (>= p (string-length s))) nil (nth s p)))) + +(define + hs-morph-index-from + (fn + (s needle from) + (let + ((r (index-of (substring s from (string-length s)) needle))) + (if (< r 0) -1 (+ from r))))) + +(define + hs-morph-sws + (fn + (s p) + (let + ((c (hs-morph-char s p))) + (if (and c (hs-ws? c)) (hs-morph-sws s (+ p 1)) p)))) + +(define + hs-morph-read-until + (fn + (s p stop) + (define + loop + (fn + (q) + (let + ((c (hs-morph-char s q))) + (if (and c (< (index-of stop c) 0)) (loop (+ q 1)) q)))) + (let ((e (loop p))) (list (substring s p e) e)))) + +(define + hs-morph-parse-attrs + (fn + (s p acc) + (let + ((p (hs-morph-sws s p))) + (let + ((c (hs-morph-char s p))) + (cond + ((nil? c) (list acc p false)) + ((= c ">") (list acc (+ p 1) false)) + ((= c "/") + (if + (= (hs-morph-char s (+ p 1)) ">") + (list acc (+ p 2) true) + (list acc (+ p 1) false))) + (true + (let + ((r (hs-morph-read-until s p " \t\n=/>"))) + (let + ((name (first r)) (p2 (nth r 1))) + (let + ((p3 (hs-morph-sws s p2))) + (if + (= (hs-morph-char s p3) "=") + (let + ((p4 (hs-morph-sws s (+ p3 1)))) + (let + ((c2 (hs-morph-char s p4))) + (cond + ((= c2 "\"") + (let + ((close (hs-morph-index-from s "\"" (+ p4 1)))) + (hs-morph-parse-attrs + s + (+ close 1) + (append + acc + (list + (list name (substring s (+ p4 1) close))))))) + ((= c2 "'") + (let + ((close (hs-morph-index-from s "'" (+ p4 1)))) + (hs-morph-parse-attrs + s + (+ close 1) + (append + acc + (list + (list name (substring s (+ p4 1) close))))))) + (true + (let + ((r2 (hs-morph-read-until s p4 " \t\n/>"))) + (hs-morph-parse-attrs + s + (nth r2 1) + (append acc (list (list name (first r2)))))))))) + (hs-morph-parse-attrs + s + p3 + (append acc (list (list name "")))))))))))))) + +(define + hs-morph-parse-element + (fn + (s p) + (let + ((p (hs-morph-sws s p))) + (if + (not (= (hs-morph-char s p) "<")) + nil + (let + ((r (hs-morph-read-until s (+ p 1) " \t\n/>"))) + (let + ((tag (first r)) (p2 (nth r 1))) + (let + ((ar (hs-morph-parse-attrs s p2 (list)))) + (let + ((attrs (first ar)) + (p3 (nth ar 1)) + (self-closing (nth ar 2))) + (if + self-closing + {:children (list) :end p3 :tag tag :type "element" :attrs attrs} + (let + ((cr (hs-morph-parse-children s p3 (list)))) + {:children (first cr) :end (nth cr 1) :tag tag :type "element" :attrs attrs})))))))))) + +(define + hs-morph-parse-children + (fn + (s p acc) + (let + ((c (hs-morph-char s p))) + (cond + ((nil? c) (list acc p)) + ((= c "<") + (if + (= (hs-morph-char s (+ p 1)) "/") + (let + ((close-gt (hs-morph-index-from s ">" (+ p 1)))) + (list acc (+ close-gt 1))) + (let + ((child (hs-morph-parse-element s p))) + (if + (nil? child) + (list acc p) + (hs-morph-parse-children + s + (get child :end) + (append acc (list child))))))) + (true + (let + ((r (hs-morph-read-until s p "<"))) + (hs-morph-parse-children + s + (nth r 1) + (append acc (list {:text (first r) :type "text"}))))))))) + +(define + hs-morph-apply-attrs + (fn + (el attrs keep-id) + (for-each + (fn + (av) + (let + ((n (first av)) (v (nth av 1))) + (cond + ((= n "class") + (for-each + (fn + (c) + (when (> (string-length c) 0) (dom-add-class el c))) + (split v " "))) + ((and keep-id (= n "id")) nil) + (true (dom-set-attr el n v))))) + attrs))) + +(define + hs-morph-build-children + (fn + (parent children) + (cond + ((= (len children) 0) nil) + ((and (= (len children) 1) (= (get (first children) :type) "text")) + (dom-set-inner-html parent (get (first children) :text))) + (true (for-each (fn (c) (hs-morph-build-child parent c)) children))))) + +(define + hs-morph-build-child + (fn + (parent node) + (cond + ((= (get node :type) "element") + (let + ((el (dom-create-element (get node :tag)))) + (do + (hs-morph-apply-attrs el (get node :attrs) false) + (hs-morph-build-children el (get node :children)) + (dom-append parent el) + (hs-activate! el)))) + (true nil)))) + +(define + hs-morph! + (fn + (target content) + (when + target + (let + ((tree (hs-morph-parse-element content 0))) + (when + tree + (do + (hs-morph-apply-attrs target (get tree :attrs) true) + (dom-set-inner-html target "") + (hs-morph-build-children target (get tree :children)))))))) + (define hs-open! (fn (el) (let ((tag (dom-get-prop el "tagName"))) - (if - (= tag "DIALOG") - (host-call el "showModal") - (dom-set-prop el "open" true))))) + (cond + ((= tag "DIALOG") (host-call el "showModal")) + (true + (do (dom-set-attr el "open" "") (dom-set-prop el "open" true))))))) (define hs-close! @@ -718,38 +1193,100 @@ (el) (let ((tag (dom-get-prop el "tagName"))) + (cond + ((= tag "DIALOG") (host-call el "close")) + (true + (do + (host-call el "removeAttribute" "open") + (dom-set-prop el "open" false))))))) + +(begin + (define + hs-hide-one! + (fn + (el strategy) + (let + ((tag (dom-get-prop el "tagName"))) + (cond + ((= tag "DIALOG") + (when (dom-has-attr? el "open") (host-call el "close"))) + ((= tag "DETAILS") (dom-set-prop el "open" false)) + ((= strategy "opacity") (dom-set-style el "opacity" "0")) + ((= strategy "visibility") + (dom-set-style el "visibility" "hidden")) + (true (dom-set-style el "display" "none")))))) + (define + hs-hide! + (fn + (target strategy) (if - (= tag "DIALOG") - (host-call el "close") - (dom-set-prop el "open" false))))) + (list? target) + (do (for-each (fn (el) (hs-hide-one! el strategy)) target) target) + (do (hs-hide-one! target strategy) target))))) + +(begin + (define + hs-show-one! + (fn + (el strategy) + (let + ((tag (dom-get-prop el "tagName"))) + (cond + ((= tag "DIALOG") + (when + (not (dom-has-attr? el "open")) + (host-call el "showModal"))) + ((= tag "DETAILS") (dom-set-prop el "open" true)) + ((= strategy "opacity") (dom-set-style el "opacity" "1")) + ((= strategy "visibility") + (dom-set-style el "visibility" "visible")) + (true (dom-set-style el "display" "")))))) + (define + hs-show! + (fn + (target strategy) + (if + (list? target) + (do (for-each (fn (el) (hs-show-one! el strategy)) target) target) + (do (hs-show-one! target strategy) target))))) (define - hs-hide! + hs-show-when! (fn - (el strategy) + (target strategy pred) (let - ((tag (dom-get-prop el "tagName"))) - (cond - ((= tag "DIALOG") - (when (dom-has-attr? el "open") (host-call el "close"))) - ((= tag "DETAILS") (dom-set-prop el "open" false)) - ((= strategy "opacity") (dom-set-style el "opacity" "0")) - ((= strategy "visibility") (dom-set-style el "visibility" "hidden")) - (true (dom-set-style el "display" "none")))))) + ((items (if (list? target) target (list target)))) + (let + ((matched (list))) + (do + (for-each + (fn + (el) + (if + (pred el) + (do (hs-show-one! el strategy) (append! matched el)) + (hs-hide-one! el strategy))) + items) + matched))))) (define - hs-show! + hs-hide-when! (fn - (el strategy) + (target strategy pred) (let - ((tag (dom-get-prop el "tagName"))) - (cond - ((= tag "DIALOG") - (when (not (dom-has-attr? el "open")) (host-call el "showModal"))) - ((= tag "DETAILS") (dom-set-prop el "open" true)) - ((= strategy "opacity") (dom-set-style el "opacity" "1")) - ((= strategy "visibility") (dom-set-style el "visibility" "visible")) - (true (dom-set-style el "display" "")))))) + ((items (if (list? target) target (list target)))) + (let + ((matched (list))) + (do + (for-each + (fn + (el) + (if + (pred el) + (do (hs-hide-one! el strategy) (append! matched el)) + (hs-show-one! el strategy))) + items) + matched))))) (define hs-first (fn (lst) (first lst))) @@ -889,6 +1426,33 @@ (e (if (nil? end) (len col) (+ end 1)))) (slice col s e)))) +(define + hs-pick-first + (fn + (col n) + (let ((m (if (< n (len col)) n (len col)))) (slice col 0 m)))) + +(define + hs-pick-last + (fn + (col n) + (let + ((total (len col))) + (let + ((start (if (< n total) (- total n) 0))) + (slice col start total))))) + +(define + hs-pick-random + (fn + (col n) + (if + (nil? n) + (first col) + (let ((m (if (< n (len col)) n (len col)))) (slice col 0 m))))) + +(define hs-pick-items (fn (col start end) (slice col start end))) + (define hs-sorted-by (fn @@ -952,3 +1516,119 @@ (define hs-sorted-by-desc (fn (col key-fn) (reverse (hs-sorted-by col key-fn)))) + +(define + hs-dom-has-var? + (fn + (el name) + (if + (nil? el) + false + (let + ((store (host-get el "__hs_vars"))) + (if (nil? store) false (host-call store "hasOwnProperty" name)))))) + +(define + hs-dom-get-var-raw + (fn + (el name) + (let + ((store (host-get el "__hs_vars"))) + (if (nil? store) nil (host-get store name))))) + +(define + hs-dom-set-var-raw! + (fn + (el name val) + (let + ((changed (not (and (hs-dom-has-var? el name) (= (hs-dom-get-var-raw el name) val))))) + (do + (when + (nil? (host-get el "__hs_vars")) + (host-set! el "__hs_vars" (host-new "Object"))) + (host-set! (host-get el "__hs_vars") name val) + (when changed (hs-dom-fire-watchers! el name val)))))) + +(define + hs-dom-resolve-start + (fn + (el) + (if + (nil? el) + nil + (let + ((scope (dom-get-attr el "dom-scope"))) + (cond + ((or (nil? scope) (= scope "") (= scope "isolated")) el) + ((starts-with? scope "closest ") + (dom-closest el (slice scope 8 (len scope)))) + ((starts-with? scope "parent of ") + (let + ((match (dom-closest el (slice scope 10 (len scope))))) + (if match (dom-parent match) nil))) + (true el)))))) + +(define + hs-dom-walk + (fn + (el name) + (cond + ((nil? el) nil) + ((hs-dom-has-var? el name) (hs-dom-get-var-raw el name)) + ((= (dom-get-attr el "dom-scope") "isolated") nil) + (true (hs-dom-walk (dom-parent el) name))))) + +(define + hs-dom-find-owner + (fn + (el name) + (cond + ((nil? el) nil) + ((hs-dom-has-var? el name) el) + ((= (dom-get-attr el "dom-scope") "isolated") nil) + (true (hs-dom-find-owner (dom-parent el) name))))) + +(define + hs-dom-get + (fn (el name) (hs-dom-walk (hs-dom-resolve-start el) name))) + +(define + hs-dom-set! + (fn + (el name val) + (let + ((start (hs-dom-resolve-start el))) + (let + ((owner (hs-dom-find-owner start name))) + (hs-dom-set-var-raw! (if owner owner start) name val))))) + +(define _hs-dom-watchers (list)) + +(define + hs-dom-watch! + (fn + (el name handler) + (set! _hs-dom-watchers (cons (list el name handler) _hs-dom-watchers)))) + +(define + hs-dom-fire-watchers! + (fn + (el name val) + (for-each + (fn + (entry) + (when + (and + (= (nth entry 1) name) + (hs-dom-is-ancestor? el (nth entry 0))) + ((nth entry 2) val))) + _hs-dom-watchers))) + +(define + hs-dom-is-ancestor? + (fn + (a b) + (cond + ((nil? b) false) + ((= a b) true) + (true (hs-dom-is-ancestor? a (dom-parent b)))))) diff --git a/spec/tests/test-framework.sx b/spec/tests/test-framework.sx index 3a80ca03..00fc9a5f 100644 --- a/spec/tests/test-framework.sx +++ b/spec/tests/test-framework.sx @@ -18,11 +18,18 @@ ;; 1. Test framework macros ;; -------------------------------------------------------------------------- -(defmacro deftest (name &rest body) - `(let ((result (try-call (fn () ,@body)))) - (if (get result "ok") - (report-pass ,name) - (report-fail ,name (get result "error"))))) +(defmacro + deftest + (name &rest body) + (quasiquote + (when + (test-allowed? (unquote name)) + (let + ((result (try-call (fn () (splice-unquote body))))) + (if + (get result "ok") + (report-pass (unquote name)) + (report-fail (unquote name) (get result "error"))))))) (defmacro defsuite (name &rest items) `(do (push-suite ,name) diff --git a/spec/tests/test-hs-diag.sx b/spec/tests/test-hs-diag.sx index e1ec1316..2ceef447 100644 --- a/spec/tests/test-hs-diag.sx +++ b/spec/tests/test-hs-diag.sx @@ -4,7 +4,9 @@ "put into #id compiled" (let ((sx (hs-to-sx-from-source "on click put \"foo\" into #d1"))) - (assert= (serialize sx) "SHOW"))) + (assert= + (serialize sx) + "(hs-on me \"click\" (fn (event) (hs-set-inner-html! (hs-query-first \"#d1\") \"foo\")))"))) (deftest "put into #id works" (let diff --git a/spec/tests/test-hyperscript-behavioral.sx b/spec/tests/test-hyperscript-behavioral.sx index 05dbe9ec..cc6186ab 100644 --- a/spec/tests/test-hyperscript-behavioral.sx +++ b/spec/tests/test-hyperscript-behavioral.sx @@ -1,5 +1,5 @@ ;; Hyperscript behavioral tests — auto-generated from upstream _hyperscript test suite -;; Source: spec/tests/hyperscript-upstream-tests.json (831 tests, v0.9.14 + dev) +;; Source: spec/tests/hyperscript-upstream-tests.json (1496 tests, v0.9.14 + dev) ;; DO NOT EDIT — regenerate with: python3 tests/playwright/generate-sx-tests.py ;; ── Test helpers ────────────────────────────────────────────────── @@ -16,7 +16,7 @@ (fn () (dom-set-inner-html (dom-body) ""))) -;; Evaluate a hyperscript expression and return its result. +;; Evaluate a hyperscript expression and return the last-expression value. ;; Compiles the expression, wraps in a thunk, evaluates, returns result. (define eval-hs (fn (src) @@ -32,6 +32,28 @@ (raise _e)))) (handler nil)))))) +;; Evaluate a hyperscript expression with locals. bindings = list of (symbol value). +;; The locals are injected as fn params so they resolve in the handler body. +(define eval-hs-locals + (fn (src bindings) + (let ((sx (hs-to-sx (hs-compile src)))) + (let ((names (map (fn (b) (first b)) bindings)) + (vals (map (fn (b) (nth b 1)) bindings))) + (let ((param-list (cons (quote me) names))) + (let ((wrapper (list (quote fn) param-list + (list (quote let) + (list (list (quote it) nil) (list (quote event) nil)) + sx (quote it))))) + (let ((handler (eval-expr-cek wrapper))) + (guard + (_e + (true + (if + (and (list? _e) (= (first _e) "hs-return")) + (nth _e 1) + (raise _e)))) + (apply handler (cons nil vals)))))))))) + ;; Evaluate with a specific me value (for "I am between" etc.) (define eval-hs-with-me (fn (src me-val) @@ -49,6 +71,24 @@ ;; ── add (19 tests) ── (defsuite "hs-upstream-add" + (deftest "can add a value to a set" + (hs-cleanup!) + (let ((_el-div (dom-create-element "div"))) + (dom-set-attr _el-div "_" "on click set :s to [] as Set then add 'a' to :s then add 'b' to :s then add 'a' to :s then put :s.size into me") + (dom-append (dom-body) _el-div) + (hs-activate! _el-div) + (dom-dispatch _el-div "click" nil) + (assert= (dom-text-content _el-div) "2") + )) + (deftest "can add a value to an array" + (hs-cleanup!) + (let ((_el-div (dom-create-element "div"))) + (dom-set-attr _el-div "_" "on click set :arr to [1,2,3] then add 4 to :arr then put :arr as String into me") + (dom-append (dom-body) _el-div) + (hs-activate! _el-div) + (dom-dispatch _el-div "click" nil) + (assert= (dom-text-content _el-div) "1,2,3,4") + )) (deftest "can add class ref on a single div" (hs-cleanup!) (let ((_el-div (dom-create-element "div"))) @@ -58,15 +98,6 @@ (dom-dispatch _el-div "click" nil) (assert (dom-has-class? _el-div "foo")) )) - (deftest "can add class ref w/ double dash on a single div" - (hs-cleanup!) - (let ((_el-div (dom-create-element "div"))) - (dom-set-attr _el-div "_" "on click add .foo--bar") - (dom-append (dom-body) _el-div) - (hs-activate! _el-div) - (dom-dispatch _el-div "click" nil) - (assert (dom-has-class? _el-div "foo--bar")) - )) (deftest "can add class ref on a single form" (hs-cleanup!) (let ((_el-form (dom-create-element "form"))) @@ -76,50 +107,23 @@ (dom-dispatch _el-form "click" nil) (assert (dom-has-class? _el-form "foo")) )) - (deftest "can target another div for class ref" - (hs-cleanup!) - (let ((_el-bar (dom-create-element "div")) (_el-div (dom-create-element "div"))) - (dom-set-attr _el-bar "id" "bar") - (dom-set-attr _el-div "_" "on click add .foo to #bar") - (dom-append (dom-body) _el-bar) - (dom-append (dom-body) _el-div) - (hs-activate! _el-div) - (dom-dispatch _el-div "click" nil) - (assert (dom-has-class? _el-bar "foo")) - (assert (not (dom-has-class? _el-div "foo"))) - )) - (deftest "can add to query in me" - (hs-cleanup!) - (let ((_el-div (dom-create-element "div")) (_el-p1 (dom-create-element "p"))) - (dom-set-attr _el-div "_" "on click add .foo to

in me") - (dom-set-attr _el-p1 "id" "p1") - (dom-append (dom-body) _el-div) - (dom-append _el-div _el-p1) - (hs-activate! _el-div) - (dom-dispatch _el-div "click" nil) - (assert (dom-has-class? _el-p1 "foo")) - (assert (not (dom-has-class? _el-div "foo"))) - )) - (deftest "can add to children" - (hs-cleanup!) - (let ((_el-div (dom-create-element "div")) (_el-p1 (dom-create-element "p"))) - (dom-set-attr _el-div "_" "on click add .foo to my children") - (dom-set-attr _el-p1 "id" "p1") - (dom-append (dom-body) _el-div) - (dom-append _el-div _el-p1) - (hs-activate! _el-div) - (dom-dispatch _el-div "click" nil) - (assert (dom-has-class? _el-p1 "foo")) - (assert (not (dom-has-class? _el-div "foo"))) - )) - (deftest "can add non-class attributes" + (deftest "can add class ref w/ double dash on a single div" (hs-cleanup!) (let ((_el-div (dom-create-element "div"))) - (dom-set-attr _el-div "_" "on click add [@foo=\"bar\"]") + (dom-set-attr _el-div "_" "on click add .foo--bar") (dom-append (dom-body) _el-div) (hs-activate! _el-div) (dom-dispatch _el-div "click" nil) - (assert= (dom-get-attr _el-div "foo") "bar") + (assert (dom-has-class? _el-div "foo--bar")) + )) + (deftest "can add class refs w/ colons and dashes" + (hs-cleanup!) + (let ((_el-div (dom-create-element "div"))) + (dom-set-attr _el-div "_" "on click add .foo:bar-doh") + (dom-append (dom-body) _el-div) + (hs-activate! _el-div) + (dom-dispatch _el-div "click" nil) + (assert (dom-has-class? _el-div "foo:bar-doh")) )) (deftest "can add css properties" (hs-cleanup!) @@ -129,18 +133,8 @@ (dom-append (dom-body) _el-div) (hs-activate! _el-div) (dom-dispatch _el-div "click" nil) - (assert= (dom-get-style _el-div "color") "red") - (assert= (dom-get-style _el-div "fontFamily") "monospace") - )) - (deftest "can add templated css properties" - (hs-cleanup!) - (let ((_el-div (dom-create-element "div"))) - (dom-set-attr _el-div "_" "on click add {color: ${\"red\"};}") - (dom-set-attr _el-div "style" "color: blue") - (dom-append (dom-body) _el-div) - (hs-activate! _el-div) - (dom-dispatch _el-div "click" nil) - (assert= (dom-get-style _el-div "color") "red") + (assert= (dom-get-style _el-div "color") "") + (assert= (dom-get-style _el-div "font-family") "monospace") )) (deftest "can add multiple class refs" (hs-cleanup!) @@ -152,62 +146,116 @@ (assert (dom-has-class? _el-div "foo")) (assert (dom-has-class? _el-div "bar")) )) - (deftest "can add class refs w/ colons and dashes" + (deftest "can add non-class attributes" (hs-cleanup!) (let ((_el-div (dom-create-element "div"))) - (dom-set-attr _el-div "_" "on click add .foo:bar-doh") + (dom-set-attr _el-div "_" "on click add [@foo=\"bar\"]") (dom-append (dom-body) _el-div) (hs-activate! _el-div) (dom-dispatch _el-div "click" nil) - (assert (dom-has-class? _el-div "foo:bar-doh")) + (assert= (dom-get-attr _el-div "foo") "bar") )) - (deftest "can filter class addition via the when clause" + (deftest "can add templated css properties" (hs-cleanup!) - (let ((_el-div (dom-create-element "div")) (_el-div1 (dom-create-element "div")) (_el-div2 (dom-create-element "div"))) - (dom-set-attr _el-div "_" "on click add .rey to .bar when it matches .doh") - (dom-add-class _el-div1 "bar") - (dom-add-class _el-div2 "bar") - (dom-add-class _el-div2 "doh") + (let ((_el-div (dom-create-element "div"))) + (dom-set-attr _el-div "_" "on click add {color: ${}{\"red\"};}") + (dom-set-attr _el-div "style" "color: blue") (dom-append (dom-body) _el-div) - (dom-append (dom-body) _el-div1) - (dom-append (dom-body) _el-div2) (hs-activate! _el-div) (dom-dispatch _el-div "click" nil) - (assert (not (dom-has-class? _el-div1 "rey"))) - (assert (dom-has-class? _el-div2 "rey")) - )) - (deftest "can filter property addition via the when clause" - (hs-cleanup!) - (let ((_el-div (dom-create-element "div")) (_el-div1 (dom-create-element "div")) (_el-div2 (dom-create-element "div"))) - (dom-set-attr _el-div "_" "on click add @rey to .bar when it matches .doh") - (dom-add-class _el-div1 "bar") - (dom-add-class _el-div2 "bar") - (dom-add-class _el-div2 "doh") - (dom-append (dom-body) _el-div) - (dom-append (dom-body) _el-div1) - (dom-append (dom-body) _el-div2) - (hs-activate! _el-div) - (dom-dispatch _el-div "click" nil) - (assert (not (dom-has-attr? _el-div1 "rey"))) - (assert (dom-has-attr? _el-div2 "rey")) + (assert= (dom-get-style _el-div "color") "") )) (deftest "can add to an HTMLCollection" (hs-cleanup!) - (let ((_el-div (dom-create-element "div")) (_el-bar (dom-create-element "div")) (_el-c1 (dom-create-element "div")) (_el-c2 (dom-create-element "div"))) - (dom-set-attr _el-div "_" "on click add .foo to the children of #bar") + (let ((_el-trigger (dom-create-element "div")) (_el-bar (dom-create-element "div")) (_el-c1 (dom-create-element "div")) (_el-c2 (dom-create-element "div"))) + (dom-set-attr _el-trigger "id" "trigger") + (dom-set-attr _el-trigger "_" "on click add .foo to the children of #bar") (dom-set-attr _el-bar "id" "bar") (dom-set-attr _el-c1 "id" "c1") (dom-set-attr _el-c2 "id" "c2") - (dom-append (dom-body) _el-div) + (dom-append (dom-body) _el-trigger) (dom-append (dom-body) _el-bar) (dom-append _el-bar _el-c1) (dom-append _el-bar _el-c2) - (hs-activate! _el-div) - (dom-dispatch _el-div "click" nil) - ;; SKIP check: skip byId("c1").classList.contains("foo").should.equal(false) - ;; SKIP check: skip byId("c2").classList.contains("foo").should.equal(false) - ;; SKIP check: skip byId("c1").classList.contains("foo").should.equal(true) - ;; SKIP check: skip byId("c2").classList.contains("foo").should.equal(true) + (hs-activate! _el-trigger) + (dom-dispatch (dom-query-by-id "trigger") "click" nil) + (assert (dom-has-class? (dom-query-by-id "c1") "foo")) + (assert (dom-has-class? (dom-query-by-id "c2") "foo")) + )) + (deftest "can add to children" + (hs-cleanup!) + (let ((_el-outer (dom-create-element "div")) (_el-p1 (dom-create-element "p"))) + (dom-set-attr _el-outer "id" "outer") + (dom-set-attr _el-outer "_" "on click add .foo to my children") + (dom-set-attr _el-p1 "id" "p1") + (dom-append (dom-body) _el-outer) + (dom-append _el-outer _el-p1) + (hs-activate! _el-outer) + (dom-dispatch (dom-query-by-id "outer") "click" nil) + (assert (dom-has-class? (dom-query-by-id "p1") "foo")) + (assert (not (dom-has-class? (dom-query-by-id "outer") "foo"))) + )) + (deftest "can add to query in me" + (hs-cleanup!) + (let ((_el-outer (dom-create-element "div")) (_el-p1 (dom-create-element "p"))) + (dom-set-attr _el-outer "id" "outer") + (dom-set-attr _el-outer "_" "on click add .foo to

in me") + (dom-set-attr _el-p1 "id" "p1") + (dom-append (dom-body) _el-outer) + (dom-append _el-outer _el-p1) + (hs-activate! _el-outer) + (dom-dispatch (dom-query-by-id "outer") "click" nil) + (assert (dom-has-class? (dom-query-by-id "p1") "foo")) + (assert (not (dom-has-class? (dom-query-by-id "outer") "foo"))) + )) + (deftest "can filter class addition via the when clause" + (hs-cleanup!) + (let ((_el-trigger (dom-create-element "div")) (_el-d2 (dom-create-element "div")) (_el-d3 (dom-create-element "div"))) + (dom-set-attr _el-trigger "id" "trigger") + (dom-set-attr _el-trigger "_" "on click add .rey to .bar when it matches .doh") + (dom-set-attr _el-d2 "id" "d2") + (dom-add-class _el-d2 "bar") + (dom-set-attr _el-d3 "id" "d3") + (dom-add-class _el-d3 "bar") + (dom-add-class _el-d3 "doh") + (dom-append (dom-body) _el-trigger) + (dom-append (dom-body) _el-d2) + (dom-append (dom-body) _el-d3) + (hs-activate! _el-trigger) + (dom-dispatch (dom-query-by-id "trigger") "click" nil) + (assert (not (dom-has-class? (dom-query-by-id "d2") "rey"))) + (assert (dom-has-class? (dom-query-by-id "d3") "rey")) + )) + (deftest "can filter property addition via the when clause" + (hs-cleanup!) + (let ((_el-trigger (dom-create-element "div")) (_el-d2 (dom-create-element "div")) (_el-d3 (dom-create-element "div"))) + (dom-set-attr _el-trigger "id" "trigger") + (dom-set-attr _el-trigger "_" "on click add @rey to .bar when it matches .doh") + (dom-set-attr _el-d2 "id" "d2") + (dom-add-class _el-d2 "bar") + (dom-set-attr _el-d3 "id" "d3") + (dom-add-class _el-d3 "bar") + (dom-add-class _el-d3 "doh") + (dom-append (dom-body) _el-trigger) + (dom-append (dom-body) _el-d2) + (dom-append (dom-body) _el-d3) + (hs-activate! _el-trigger) + (dom-dispatch (dom-query-by-id "trigger") "click" nil) + (assert (not (dom-has-attr? (dom-query-by-id "d2") "rey"))) + (assert (dom-has-attr? (dom-query-by-id "d3") "rey")) + )) + (deftest "can target another div for class ref" + (hs-cleanup!) + (let ((_el-bar (dom-create-element "div")) (_el-trigger (dom-create-element "div"))) + (dom-set-attr _el-bar "id" "bar") + (dom-set-attr _el-trigger "id" "trigger") + (dom-set-attr _el-trigger "_" "on click add .foo to #bar") + (dom-append (dom-body) _el-bar) + (dom-append (dom-body) _el-trigger) + (hs-activate! _el-trigger) + (dom-dispatch (dom-query-by-id "trigger") "click" nil) + (assert (dom-has-class? (dom-query-by-id "bar") "foo")) + (assert (not (dom-has-class? (dom-query-by-id "trigger") "foo"))) )) (deftest "supports async expressions in when clause" (hs-cleanup!) @@ -221,6 +269,22 @@ (dom-dispatch (dom-query-by-id "trigger") "click" nil) (assert (dom-has-class? (dom-query-by-id "d2") "foo")) )) + (deftest "when clause result is empty when nothing matches" + (hs-cleanup!) + (let ((_el-trigger (dom-create-element "div")) (_el-d1 (dom-create-element "div")) (_el-none (dom-create-element "div"))) + (dom-set-attr _el-trigger "id" "trigger") + (dom-set-attr _el-trigger "_" "on click add .foo to .item when it matches .nope then if the result is empty remove @hidden from #none") + (dom-set-attr _el-d1 "id" "d1") + (dom-add-class _el-d1 "item") + (dom-set-attr _el-none "id" "none") + (dom-set-attr _el-none "hidden" "") + (dom-append (dom-body) _el-trigger) + (dom-append (dom-body) _el-d1) + (dom-append (dom-body) _el-none) + (hs-activate! _el-trigger) + (dom-dispatch (dom-query-by-id "trigger") "click" nil) + (assert (not (dom-has-attr? (dom-query-by-id "none") "hidden"))) + )) (deftest "when clause sets result to matched elements" (hs-cleanup!) (let ((_el-trigger (dom-create-element "div")) (_el-d1 (dom-create-element "div")) (_el-d2 (dom-create-element "div")) (_el-none (dom-create-element "div"))) @@ -241,3047 +305,10 @@ (dom-dispatch (dom-query-by-id "trigger") "click" nil) (assert (not (dom-visible? (dom-query-by-id "none")))) )) - (deftest "when clause result is empty when nothing matches" - (hs-cleanup!) - (let ((_el-trigger (dom-create-element "div")) (_el-d1 (dom-create-element "div")) (_el-none (dom-create-element "div"))) - (dom-set-attr _el-trigger "id" "trigger") - (dom-set-attr _el-trigger "_" "on click add .foo to .item when it matches .nope then if the result is empty remove @hidden from #none") - (dom-set-attr _el-d1 "id" "d1") - (dom-add-class _el-d1 "item") - (dom-set-attr _el-none "id" "none") - (dom-set-attr _el-none "hidden" "") - (dom-append (dom-body) _el-trigger) - (dom-append (dom-body) _el-d1) - (dom-append (dom-body) _el-none) - (hs-activate! _el-trigger) - (dom-dispatch (dom-query-by-id "trigger") "click" nil) - (assert (not (dom-has-attr? (dom-query-by-id "none") "hidden"))) - )) - (deftest "can add a value to an array" - (hs-cleanup!) - (let ((_el-div (dom-create-element "div"))) - (dom-set-attr _el-div "_" "on click set :arr to [1,2,3] then add 4 to :arr then put :arr as String into me") - (dom-append (dom-body) _el-div) - (hs-activate! _el-div) - (dom-dispatch _el-div "click" nil) - (assert= (dom-text-content _el-div) "1,2,3,4") - )) - (deftest "can add a value to a set" - (hs-cleanup!) - (let ((_el-div (dom-create-element "div"))) - (dom-set-attr _el-div "_" "on click set :s to [] as Set then add 'a' to :s then add 'b' to :s then add 'a' to :s then put :s.size into me") - (dom-append (dom-body) _el-div) - (hs-activate! _el-div) - (dom-dispatch _el-div "click" nil) - (assert= (dom-text-content _el-div) "2") - )) -) - -;; ── remove (14 tests) ── -(defsuite "hs-upstream-remove" - (deftest "can remove class ref on a single div" - (hs-cleanup!) - (let ((_el-div (dom-create-element "div"))) - (dom-add-class _el-div "foo") - (dom-set-attr _el-div "_" "on click remove .foo") - (dom-append (dom-body) _el-div) - (hs-activate! _el-div) - (dom-dispatch _el-div "click" nil) - (assert (not (dom-has-class? _el-div "foo"))) - )) - (deftest "can remove class ref on a single form" - (hs-cleanup!) - (let ((_el-form (dom-create-element "form"))) - (dom-add-class _el-form "foo") - (dom-set-attr _el-form "_" "on click remove .foo") - (dom-append (dom-body) _el-form) - (hs-activate! _el-form) - (dom-dispatch _el-form "click" nil) - (assert (not (dom-has-class? _el-form "foo"))) - )) - (deftest "can target another div for class ref" - (hs-cleanup!) - (let ((_el-bar (dom-create-element "div")) (_el-div (dom-create-element "div"))) - (dom-set-attr _el-bar "id" "bar") - (dom-add-class _el-bar "foo") - (dom-set-attr _el-div "_" "on click remove .foo from #bar") - (dom-append (dom-body) _el-bar) - (dom-append (dom-body) _el-div) - (hs-activate! _el-div) - (dom-dispatch _el-div "click" nil) - (assert (not (dom-has-class? _el-bar "foo"))) - (assert (not (dom-has-class? _el-div "foo"))) - )) - (deftest "can remove non-class attributes" - (hs-cleanup!) - (let ((_el-div (dom-create-element "div"))) - (dom-set-attr _el-div "_" "on click remove [@foo]") - (dom-set-attr _el-div "foo" "bar") - (dom-append (dom-body) _el-div) - (hs-activate! _el-div) - (dom-dispatch _el-div "click" nil) - (assert (not (dom-has-attr? _el-div "foo"))) - )) - (deftest "can remove elements" - (hs-cleanup!) - (let ((_el-div (dom-create-element "div"))) - (dom-set-attr _el-div "_" "on click remove me") - (dom-append (dom-body) _el-div) - (hs-activate! _el-div) - (dom-dispatch _el-div "click" nil) - (assert (nil? (dom-parent _el-div))) - )) - (deftest "can remove other elements" - (hs-cleanup!) - (let ((_el-div (dom-create-element "div")) (_el-that (dom-create-element "div"))) - (dom-set-attr _el-div "_" "on click remove #that") - (dom-set-attr _el-that "id" "that") - (dom-append (dom-body) _el-div) - (dom-append (dom-body) _el-that) - (hs-activate! _el-div) - (dom-dispatch _el-div "click" nil) - (assert (nil? (dom-parent _el-that))) - )) - (deftest "can remove parent element" - (hs-cleanup!) - (let ((_el-p1 (dom-create-element "div")) (_el-b1 (dom-create-element "button"))) - (dom-set-attr _el-p1 "id" "p1") - (dom-set-attr _el-b1 "id" "b1") - (dom-set-attr _el-b1 "_" "on click remove my.parentElement") - (dom-append (dom-body) _el-p1) - (dom-append _el-p1 _el-b1) - (hs-activate! _el-b1) - (dom-dispatch _el-p1 "click" nil) - (assert (nil? (dom-parent _el-p1))) - )) - (deftest "can remove multiple class refs" - (hs-cleanup!) - (let ((_el-div (dom-create-element "div"))) - (dom-add-class _el-div "foo") - (dom-add-class _el-div "bar") - (dom-add-class _el-div "doh") - (dom-set-attr _el-div "_" "on click remove .foo .bar") - (dom-append (dom-body) _el-div) - (hs-activate! _el-div) - (dom-dispatch _el-div "click" nil) - (assert (not (dom-has-class? _el-div "foo"))) - (assert (not (dom-has-class? _el-div "bar"))) - (assert (dom-has-class? _el-div "doh")) - )) - (deftest "can remove query refs from specific things" - (hs-cleanup!) - (let ((_el-div (dom-create-element "div")) (_el-d1 (dom-create-element "div")) (_el-p (dom-create-element "p")) (_el-p3 (dom-create-element "p"))) - (dom-set-attr _el-d1 "id" "d1") - (dom-set-attr _el-d1 "_" "on click remove

from me") - (dom-set-inner-html _el-p "foo") - (dom-set-inner-html _el-p3 "doh") - (dom-append (dom-body) _el-div) - (dom-append _el-div _el-d1) - (dom-append _el-d1 _el-p) - (dom-append _el-div _el-p3) - (hs-activate! _el-d1) - (dom-dispatch _el-div "click" nil) - ;; SKIP check: skip div.innerHTML.includes("foo").should.equal(true) - ;; SKIP check: skip div.innerHTML.includes("bar").should.equal(true) - ;; SKIP check: skip div.innerHTML.includes("doh").should.equal(true) - ;; SKIP check: skip div.innerHTML.includes("foo").should.equal(false) - )) - (deftest "can filter class removal via the when clause" - (hs-cleanup!) - (let ((_el-trigger (dom-create-element "div")) (_el-d1 (dom-create-element "div")) (_el-d2 (dom-create-element "div"))) - (dom-set-attr _el-trigger "id" "trigger") - (dom-set-attr _el-trigger "_" "on click remove .highlight from .item when it matches .old") - (dom-set-attr _el-d1 "id" "d1") - (dom-add-class _el-d1 "item") - (dom-add-class _el-d1 "old") - (dom-add-class _el-d1 "highlight") - (dom-set-attr _el-d2 "id" "d2") - (dom-add-class _el-d2 "item") - (dom-add-class _el-d2 "highlight") - (dom-append (dom-body) _el-trigger) - (dom-append (dom-body) _el-d1) - (dom-append (dom-body) _el-d2) - (hs-activate! _el-trigger) - (dom-dispatch (dom-query-by-id "trigger") "click" nil) - (assert (not (dom-has-class? (dom-query-by-id "d1") "highlight"))) - (assert (dom-has-class? (dom-query-by-id "d2") "highlight")) - )) - (deftest "can remove CSS properties" - (hs-cleanup!) - (let ((_el-div (dom-create-element "div"))) - (dom-set-attr _el-div "_" "on click remove {color} from me") - (dom-set-attr _el-div "style" "color: red; font-weight: bold;") - (dom-append (dom-body) _el-div) - (hs-activate! _el-div) - (dom-dispatch _el-div "click" nil) - )) - (deftest "can remove multiple CSS properties" - (hs-cleanup!) - (let ((_el-div (dom-create-element "div"))) - (dom-set-attr _el-div "_" "on click remove {color; font-weight} from me") - (dom-set-attr _el-div "style" "color: red; font-weight: bold; opacity: 0.5;") - (dom-append (dom-body) _el-div) - (hs-activate! _el-div) - (dom-dispatch _el-div "click" nil) - )) - (deftest "can remove a value from an array" - (hs-cleanup!) - (let ((_el-div (dom-create-element "div"))) - (dom-set-attr _el-div "_" "on click set :arr to [1,2,3,4] then remove 3 from :arr then put :arr as String into me") - (dom-append (dom-body) _el-div) - (hs-activate! _el-div) - (dom-dispatch _el-div "click" nil) - (assert= (dom-text-content _el-div) "1,2,4") - )) - (deftest "can remove a value from a set" - (hs-cleanup!) - (let ((_el-div (dom-create-element "div"))) - (dom-set-attr _el-div "_" "on click set :s to ['a','b','c'] as Set then remove 'b' from :s then put :s.size into me") - (dom-append (dom-body) _el-div) - (hs-activate! _el-div) - (dom-dispatch _el-div "click" nil) - (assert= (dom-text-content _el-div) "2") - )) -) - -;; ── toggle (30 tests) ── -(defsuite "hs-upstream-toggle" - (deftest "can toggle class ref on a single div" - (hs-cleanup!) - (let ((_el-div (dom-create-element "div"))) - (dom-set-attr _el-div "_" "on click toggle .foo") - (dom-append (dom-body) _el-div) - (hs-activate! _el-div) - (dom-dispatch _el-div "click" nil) - (dom-dispatch _el-div "click" nil) - (assert (not (dom-has-class? _el-div "foo"))) - )) - (deftest "can toggle class ref on a single form" - (hs-cleanup!) - (let ((_el-form (dom-create-element "form"))) - (dom-set-attr _el-form "_" "on click toggle .foo") - (dom-append (dom-body) _el-form) - (hs-activate! _el-form) - (dom-dispatch _el-form "click" nil) - (dom-dispatch _el-form "click" nil) - (assert (not (dom-has-class? _el-form "foo"))) - )) - (deftest "can target another div for class ref toggle" - (hs-cleanup!) - (let ((_el-bar (dom-create-element "div")) (_el-div (dom-create-element "div"))) - (dom-set-attr _el-bar "id" "bar") - (dom-set-attr _el-div "_" "on click toggle .foo on #bar") - (dom-append (dom-body) _el-bar) - (dom-append (dom-body) _el-div) - (hs-activate! _el-div) - (dom-dispatch _el-div "click" nil) - (dom-dispatch _el-div "click" nil) - (assert (not (dom-has-class? _el-bar "foo"))) - (assert (not (dom-has-class? _el-div "foo"))) - )) - (deftest "can toggle non-class attributes" - (hs-cleanup!) - (let ((_el-div (dom-create-element "div"))) - (dom-set-attr _el-div "_" "on click toggle [@foo=\"bar\"]") - (dom-append (dom-body) _el-div) - (hs-activate! _el-div) - (dom-dispatch _el-div "click" nil) - (dom-dispatch _el-div "click" nil) - (assert (not (dom-has-attr? _el-div "foo"))) - )) - (deftest "can toggle non-class attributes on selects" - (hs-cleanup!) - (let ((_el-select (dom-create-element "select"))) - (dom-set-attr _el-select "_" "on click toggle [@foo=\"bar\"]") - (dom-append (dom-body) _el-select) - (hs-activate! _el-select) - (dom-dispatch _el-select "click" nil) - (dom-dispatch _el-select "click" nil) - (assert (not (dom-has-attr? _el-select "foo"))) - )) - (deftest "can toggle for a fixed amount of time" - (hs-cleanup!) - (let ((_el-div (dom-create-element "div"))) - (dom-set-attr _el-div "_" "on click toggle .foo for 10ms") - (dom-append (dom-body) _el-div) - (hs-activate! _el-div) - (dom-dispatch _el-div "click" nil) - (assert (not (dom-has-class? _el-div "foo"))) - )) - (deftest "can toggle until an event on another element" - (hs-cleanup!) - (let ((_el-d1 (dom-create-element "div")) (_el-div (dom-create-element "div"))) - (dom-set-attr _el-d1 "id" "d1") - (dom-set-attr _el-div "_" "on click toggle .foo until foo from #d1") - (dom-append (dom-body) _el-d1) - (dom-append (dom-body) _el-div) - (hs-activate! _el-div) - (dom-dispatch _el-div "click" nil) - (dom-dispatch _el-d1 "foo" nil) - (assert (not (dom-has-class? _el-div "foo"))) - )) - (deftest "can toggle between two classes" - (hs-cleanup!) - (let ((_el-div (dom-create-element "div"))) - (dom-add-class _el-div "foo") - (dom-set-attr _el-div "_" "on click toggle between .foo and .bar") - (dom-append (dom-body) _el-div) - (hs-activate! _el-div) - (dom-dispatch _el-div "click" nil) - (dom-dispatch _el-div "click" nil) - (assert (dom-has-class? _el-div "foo")) - (assert (not (dom-has-class? _el-div "bar"))) - )) - (deftest "can toggle multiple class refs" - (hs-cleanup!) - (let ((_el-div (dom-create-element "div"))) - (dom-add-class _el-div "bar") - (dom-set-attr _el-div "_" "on click toggle .foo .bar") - (dom-append (dom-body) _el-div) - (hs-activate! _el-div) - (dom-dispatch _el-div "click" nil) - (dom-dispatch _el-div "click" nil) - (assert (not (dom-has-class? _el-div "foo"))) - (assert (dom-has-class? _el-div "bar")) - )) - (deftest "can toggle display" - (hs-cleanup!) - (let ((_el-div (dom-create-element "div"))) - (dom-set-attr _el-div "_" "on click toggle *display") - (dom-append (dom-body) _el-div) - (hs-activate! _el-div) - (dom-dispatch _el-div "click" nil) - (dom-dispatch _el-div "click" nil) - ;; SKIP computed style: div.display - )) - (deftest "can toggle opacity" - (hs-cleanup!) - (let ((_el-div (dom-create-element "div"))) - (dom-set-attr _el-div "_" "on click toggle *opacity") - (dom-append (dom-body) _el-div) - (hs-activate! _el-div) - (dom-dispatch _el-div "click" nil) - (dom-dispatch _el-div "click" nil) - ;; SKIP computed style: div.opacity - )) - (deftest "can toggle opacity" - (hs-cleanup!) - (let ((_el-div (dom-create-element "div"))) - (dom-set-attr _el-div "_" "on click toggle *visibility") - (dom-append (dom-body) _el-div) - (hs-activate! _el-div) - (dom-dispatch _el-div "click" nil) - (dom-dispatch _el-div "click" nil) - ;; SKIP computed style: div.visibility - )) - (deftest "can toggle display w/ my" - (hs-cleanup!) - (let ((_el-div (dom-create-element "div"))) - (dom-set-attr _el-div "_" "on click toggle my *display") - (dom-append (dom-body) _el-div) - (hs-activate! _el-div) - (dom-dispatch _el-div "click" nil) - (dom-dispatch _el-div "click" nil) - ;; SKIP computed style: div.display - )) - (deftest "can toggle display w/ my" - (hs-cleanup!) - (let ((_el-div (dom-create-element "div"))) - (dom-set-attr _el-div "_" "on click toggle my *opacity") - (dom-append (dom-body) _el-div) - (hs-activate! _el-div) - (dom-dispatch _el-div "click" nil) - (dom-dispatch _el-div "click" nil) - ;; SKIP computed style: div.opacity - )) - (deftest "can toggle display w/ my" - (hs-cleanup!) - (let ((_el-div (dom-create-element "div"))) - (dom-set-attr _el-div "_" "on click toggle my *visibility") - (dom-append (dom-body) _el-div) - (hs-activate! _el-div) - (dom-dispatch _el-div "click" nil) - (dom-dispatch _el-div "click" nil) - ;; SKIP computed style: div.visibility - )) - (deftest "can toggle display on other elt" - (hs-cleanup!) - (let ((_el-div (dom-create-element "div")) (_el-d2 (dom-create-element "div"))) - (dom-set-attr _el-div "_" "on click toggle the *display of #d2") - (dom-set-attr _el-d2 "id" "d2") - (dom-append (dom-body) _el-div) - (dom-append (dom-body) _el-d2) - (hs-activate! _el-div) - (dom-dispatch _el-div "click" nil) - (dom-dispatch _el-div "click" nil) - ;; SKIP computed style: div2.display - )) - (deftest "can toggle display on other elt" - (hs-cleanup!) - (let ((_el-div (dom-create-element "div")) (_el-d2 (dom-create-element "div"))) - (dom-set-attr _el-div "_" "on click toggle the *opacity of #d2") - (dom-set-attr _el-d2 "id" "d2") - (dom-append (dom-body) _el-div) - (dom-append (dom-body) _el-d2) - (hs-activate! _el-div) - (dom-dispatch _el-div "click" nil) - (dom-dispatch _el-div "click" nil) - ;; SKIP computed style: div2.opacity - )) - (deftest "can toggle display on other elt" - (hs-cleanup!) - (let ((_el-div (dom-create-element "div")) (_el-d2 (dom-create-element "div"))) - (dom-set-attr _el-div "_" "on click toggle the *visibility of #d2") - (dom-set-attr _el-d2 "id" "d2") - (dom-append (dom-body) _el-div) - (dom-append (dom-body) _el-d2) - (hs-activate! _el-div) - (dom-dispatch _el-div "click" nil) - (dom-dispatch _el-div "click" nil) - ;; SKIP computed style: div2.visibility - )) - (deftest "can toggle crazy tailwinds class ref on a single form" - (hs-cleanup!) - (let ((_el-form (dom-create-element "form"))) - (dom-set-attr _el-form "_" "on click toggle .group-[:nth-of-type(3)_&]:block") - (dom-append (dom-body) _el-form) - (hs-activate! _el-form) - (dom-dispatch _el-form "click" nil) - (dom-dispatch _el-form "click" nil) - (assert (not (dom-has-class? _el-form "group-[:nth-of-type(3)_&]:block"))) - )) - (deftest "can toggle between two attribute values" - (hs-cleanup!) - (let ((_el-div (dom-create-element "div"))) - (dom-set-attr _el-div "_" "on click toggle between [@data-state='active'] and [@data-state='inactive']") - (dom-set-attr _el-div "data-state" "active") - (dom-append (dom-body) _el-div) - (hs-activate! _el-div) - (dom-dispatch _el-div "click" nil) - (assert= (dom-get-attr _el-div "data-state") "inactive") - (dom-dispatch _el-div "click" nil) - (assert= (dom-get-attr _el-div "data-state") "active") - )) - (deftest "can toggle between different attributes" - (hs-cleanup!) - (let ((_el-div (dom-create-element "div"))) - (dom-set-attr _el-div "_" "on click toggle between [@enabled='true'] and [@disabled='true']") - (dom-set-attr _el-div "enabled" "true") - (dom-append (dom-body) _el-div) - (hs-activate! _el-div) - (dom-dispatch _el-div "click" nil) - (assert= (dom-get-attr _el-div "disabled") "true") - (dom-dispatch _el-div "click" nil) - (assert= (dom-get-attr _el-div "enabled") "true") - )) - (deftest "can toggle visibility" - (hs-cleanup!) - (let ((_el-div (dom-create-element "div"))) - (dom-set-attr _el-div "_" "on click toggle *visibility") - (dom-append (dom-body) _el-div) - (hs-activate! _el-div) - (dom-dispatch _el-div "click" nil) - (assert= (dom-get-style _el-div "visibility") "hidden") - (dom-dispatch _el-div "click" nil) - (assert= (dom-get-style _el-div "visibility") "visible") - )) - (deftest "can toggle opacity w/ my" - (hs-cleanup!) - (let ((_el-div (dom-create-element "div"))) - (dom-set-attr _el-div "_" "on click toggle my *opacity") - (dom-append (dom-body) _el-div) - (hs-activate! _el-div) - (dom-dispatch _el-div "click" nil) - (assert= (dom-get-style _el-div "opacity") "0") - (dom-dispatch _el-div "click" nil) - (assert= (dom-get-style _el-div "opacity") "1") - )) - (deftest "can toggle visibility w/ my" - (hs-cleanup!) - (let ((_el-div (dom-create-element "div"))) - (dom-set-attr _el-div "_" "on click toggle my *visibility") - (dom-append (dom-body) _el-div) - (hs-activate! _el-div) - (dom-dispatch _el-div "click" nil) - (assert= (dom-get-style _el-div "visibility") "hidden") - (dom-dispatch _el-div "click" nil) - (assert= (dom-get-style _el-div "visibility") "visible") - )) - (deftest "can toggle opacity on other elt" - (hs-cleanup!) - (let ((_el-div (dom-create-element "div")) (_el-d2 (dom-create-element "div"))) - (dom-set-attr _el-div "_" "on click toggle the *opacity of #d2") - (dom-set-attr _el-d2 "id" "d2") - (dom-append (dom-body) _el-div) - (dom-append (dom-body) _el-d2) - (hs-activate! _el-div) - (dom-dispatch _el-div "click" nil) - (assert= (dom-get-style (dom-query-by-id "d2") "opacity") "0") - (dom-dispatch _el-div "click" nil) - (assert= (dom-get-style (dom-query-by-id "d2") "opacity") "1") - )) - (deftest "can toggle visibility on other elt" - (hs-cleanup!) - (let ((_el-div (dom-create-element "div")) (_el-d2 (dom-create-element "div"))) - (dom-set-attr _el-div "_" "on click toggle the *visibility of #d2") - (dom-set-attr _el-d2 "id" "d2") - (dom-append (dom-body) _el-div) - (dom-append (dom-body) _el-d2) - (hs-activate! _el-div) - (dom-dispatch _el-div "click" nil) - (assert= (dom-get-style (dom-query-by-id "d2") "visibility") "hidden") - (dom-dispatch _el-div "click" nil) - (assert= (dom-get-style (dom-query-by-id "d2") "visibility") "visible") - )) - (deftest "can toggle *display between two values" - (hs-cleanup!) - (let ((_el-div (dom-create-element "div"))) - (dom-set-attr _el-div "_" "on click toggle *display of me between 'none' and 'flex'") - (dom-set-attr _el-div "style" "display:none") - (dom-append (dom-body) _el-div) - (hs-activate! _el-div) - (dom-dispatch _el-div "click" nil) - (assert= (dom-get-style _el-div "display") "flex") - (dom-dispatch _el-div "click" nil) - (assert= (dom-get-style _el-div "display") "none") - )) - (deftest "can toggle *opacity between three values" - (hs-cleanup!) - (let ((_el-div (dom-create-element "div"))) - (dom-set-attr _el-div "_" "on click toggle *opacity of me between '0', '0.5' and '1'") - (dom-set-attr _el-div "style" "opacity:0") - (dom-append (dom-body) _el-div) - (hs-activate! _el-div) - (dom-dispatch _el-div "click" nil) - (assert= (dom-get-style _el-div "opacity") "0.5") - (dom-dispatch _el-div "click" nil) - (assert= (dom-get-style _el-div "opacity") "1") - (dom-dispatch _el-div "click" nil) - (assert= (dom-get-style _el-div "opacity") "0") - )) - (deftest "can toggle a global variable between two values" - (hs-cleanup!) - (let ((_el-div (dom-create-element "div"))) - (dom-set-attr _el-div "_" "on click toggle $mode between 'edit' and 'preview'") - (dom-append (dom-body) _el-div) - (hs-activate! _el-div) - (dom-dispatch _el-div "click" nil) - (dom-dispatch _el-div "click" nil) - (dom-dispatch _el-div "click" nil) - )) - (deftest "can toggle a global variable between three values" - (hs-cleanup!) - (let ((_el-div (dom-create-element "div"))) - (dom-set-attr _el-div "_" "on click toggle $state between 'a', 'b' and 'c'") - (dom-append (dom-body) _el-div) - (hs-activate! _el-div) - (dom-dispatch _el-div "click" nil) - (dom-dispatch _el-div "click" nil) - (dom-dispatch _el-div "click" nil) - (dom-dispatch _el-div "click" nil) - )) -) - -;; ── set (25 tests) ── -(defsuite "hs-upstream-set" - (deftest "can set properties" - (hs-cleanup!) - (let ((_el-d1 (dom-create-element "div"))) - (dom-set-attr _el-d1 "id" "d1") - (dom-set-attr _el-d1 "_" "on click set #d1.innerHTML to \"foo\"") - (dom-append (dom-body) _el-d1) - (hs-activate! _el-d1) - (dom-dispatch _el-d1 "click" nil) - (assert= (dom-inner-html _el-d1) "foo") - )) - (deftest "can set indirect properties" - (hs-cleanup!) - (let ((_el-d1 (dom-create-element "div"))) - (dom-set-attr _el-d1 "id" "d1") - (dom-set-attr _el-d1 "_" "on click set innerHTML of #d1 to \"foo\"") - (dom-append (dom-body) _el-d1) - (hs-activate! _el-d1) - (dom-dispatch _el-d1 "click" nil) - (assert= (dom-inner-html _el-d1) "foo") - )) - (deftest "can set complex indirect properties lhs" - (hs-cleanup!) - (let ((_el-div (dom-create-element "div")) (_el-d1 (dom-create-element "div"))) - (dom-set-attr _el-div "_" "on click set parentNode.innerHTML of #d1 to \"foo\"") - (dom-set-attr _el-d1 "id" "d1") - (dom-append (dom-body) _el-div) - (dom-append _el-div _el-d1) - (hs-activate! _el-div) - (dom-dispatch _el-div "click" nil) - (assert= (dom-inner-html _el-div) "foo") - )) - (deftest "can set complex indirect properties rhs" - (hs-cleanup!) - (let ((_el-div (dom-create-element "div")) (_el-d1 (dom-create-element "div"))) - (dom-set-attr _el-div "_" "on click set innerHTML of #d1.parentNode to \"foo\"") - (dom-set-attr _el-d1 "id" "d1") - (dom-append (dom-body) _el-div) - (dom-append _el-div _el-d1) - (hs-activate! _el-div) - (dom-dispatch _el-div "click" nil) - (assert= (dom-inner-html _el-div) "foo") - )) - (deftest "can set chained indirect properties" - (hs-cleanup!) - (let ((_el-div (dom-create-element "div")) (_el-d1 (dom-create-element "div"))) - (dom-set-attr _el-div "_" "on click set the innerHTML of the parentNode of #d1 to \"foo\"") - (dom-set-attr _el-d1 "id" "d1") - (dom-append (dom-body) _el-div) - (dom-append _el-div _el-d1) - (hs-activate! _el-div) - (dom-dispatch _el-div "click" nil) - (assert= (dom-inner-html _el-div) "foo") - )) - (deftest "can set styles" - (hs-cleanup!) - (let ((_el-div (dom-create-element "div"))) - (dom-set-attr _el-div "_" "on click set my.style.color to \"red\"") - (dom-set-inner-html _el-div "lolwat") - (dom-append (dom-body) _el-div) - (hs-activate! _el-div) - (dom-dispatch _el-div "click" nil) - (assert= (dom-get-style _el-div "color") "red") - )) - (deftest "can set javascript globals" - (hs-cleanup!) - (let ((_el-div (dom-create-element "div"))) - (dom-set-attr _el-div "_" "on click set window.temp to \"red\"") - (dom-set-inner-html _el-div "lolwat") - (dom-append (dom-body) _el-div) - (hs-activate! _el-div) - (dom-dispatch _el-div "click" nil) - ;; SKIP check: skip window["temp"].should.equal("red") - )) - (deftest "can set local variables" - (hs-cleanup!) - (let ((_el-d1 (dom-create-element "div"))) - (dom-set-attr _el-d1 "id" "d1") - (dom-set-attr _el-d1 "_" "on click set newVar to \"foo\" then put newVar into #d1.innerHTML") - (dom-append (dom-body) _el-d1) - (hs-activate! _el-d1) - (dom-dispatch _el-d1 "click" nil) - (assert= (dom-inner-html _el-d1) "foo") - )) - (deftest "can set into id ref" - (hs-cleanup!) - (let ((_el-d1 (dom-create-element "div"))) - (dom-set-attr _el-d1 "id" "d1") - (dom-set-attr _el-d1 "_" "on click set #d1.innerHTML to \"foo\"") - (dom-append (dom-body) _el-d1) - (hs-activate! _el-d1) - (dom-dispatch _el-d1 "click" nil) - (assert= (dom-inner-html _el-d1) "foo") - )) - (deftest "can set into class ref" - (hs-cleanup!) - (let ((_el-div (dom-create-element "div")) (_el-div1 (dom-create-element "div"))) - (dom-add-class _el-div "divs") - (dom-set-attr _el-div "_" "on click set .divs.innerHTML to \"foo\"") - (dom-add-class _el-div1 "divs") - (dom-append (dom-body) _el-div) - (dom-append (dom-body) _el-div1) - (hs-activate! _el-div) - (dom-dispatch _el-div "click" nil) - (assert= (dom-inner-html _el-div) "foo") - (assert= (dom-inner-html _el-div1) "foo") - )) - (deftest "can set into attribute ref" - (hs-cleanup!) - (let ((_el-div (dom-create-element "div"))) - (dom-add-class _el-div "divs") - (dom-set-attr _el-div "_" "on click set @bar to \"foo\"") - (dom-append (dom-body) _el-div) - (hs-activate! _el-div) - (dom-dispatch _el-div "click" nil) - (assert= (dom-get-attr _el-div "bar") "foo") - )) - (deftest "can set into indirect attribute ref" - (hs-cleanup!) - (let ((_el-div (dom-create-element "div")) (_el-div2 (dom-create-element "div"))) - (dom-add-class _el-div "divs") - (dom-set-attr _el-div "_" "on click set #div2's @bar to 'foo'") - (dom-set-attr _el-div2 "id" "div2") - (dom-append (dom-body) _el-div) - (dom-append (dom-body) _el-div2) - (hs-activate! _el-div) - (dom-dispatch _el-div "click" nil) - (assert= (dom-get-attr _el-div2 "bar") "foo") - )) - (deftest "can set into indirect attribute ref 2" - (hs-cleanup!) - (let ((_el-div (dom-create-element "div")) (_el-div2 (dom-create-element "div"))) - (dom-add-class _el-div "divs") - (dom-set-attr _el-div "_" "on click set #div2's @bar to 'foo'") - (dom-set-attr _el-div2 "id" "div2") - (dom-append (dom-body) _el-div) - (dom-append (dom-body) _el-div2) - (hs-activate! _el-div) - (dom-dispatch _el-div "click" nil) - (assert= (dom-get-attr _el-div2 "bar") "foo") - )) - (deftest "can set into indirect attribute ref 3" - (hs-cleanup!) - (let ((_el-div (dom-create-element "div")) (_el-div2 (dom-create-element "div"))) - (dom-add-class _el-div "divs") - (dom-set-attr _el-div "_" "on click set @bar of #div2 to 'foo'") - (dom-set-attr _el-div2 "id" "div2") - (dom-append (dom-body) _el-div) - (dom-append (dom-body) _el-div2) - (hs-activate! _el-div) - (dom-dispatch _el-div "click" nil) - (assert= (dom-get-attr _el-div2 "bar") "foo") - )) - (deftest "can set into style ref" - (hs-cleanup!) - (let ((_el-div (dom-create-element "div"))) - (dom-add-class _el-div "divs") - (dom-set-attr _el-div "_" "on click set *color to \"red\"") - (dom-append (dom-body) _el-div) - (hs-activate! _el-div) - (dom-dispatch _el-div "click" nil) - ;; SKIP check: skip d1.style["color"].should.equal("red") - )) - (deftest "can set into indirect style ref" - (hs-cleanup!) - (let ((_el-div (dom-create-element "div")) (_el-div2 (dom-create-element "div"))) - (dom-add-class _el-div "divs") - (dom-set-attr _el-div "_" "on click set #div2's *color to 'red'") - (dom-set-attr _el-div2 "id" "div2") - (dom-append (dom-body) _el-div) - (dom-append (dom-body) _el-div2) - (hs-activate! _el-div) - (dom-dispatch _el-div "click" nil) - ;; SKIP check: skip d2.style["color"].should.equal("red") - )) - (deftest "can set into indirect style ref 2" - (hs-cleanup!) - (let ((_el-div (dom-create-element "div")) (_el-div2 (dom-create-element "div"))) - (dom-add-class _el-div "divs") - (dom-set-attr _el-div "_" "on click set #div2's *color to 'red'") - (dom-set-attr _el-div2 "id" "div2") - (dom-append (dom-body) _el-div) - (dom-append (dom-body) _el-div2) - (hs-activate! _el-div) - (dom-dispatch _el-div "click" nil) - ;; SKIP check: skip d2.style["color"].should.equal("red") - )) - (deftest "can set into indirect style ref 3" - (hs-cleanup!) - (let ((_el-div (dom-create-element "div")) (_el-div2 (dom-create-element "div"))) - (dom-add-class _el-div "divs") - (dom-set-attr _el-div "_" "on click set *color of #div2 to 'red'") - (dom-set-attr _el-div2 "id" "div2") - (dom-append (dom-body) _el-div) - (dom-append (dom-body) _el-div2) - (hs-activate! _el-div) - (dom-dispatch _el-div "click" nil) - ;; SKIP check: skip d2.style["color"].should.equal("red") - )) - (deftest "set waits on promises" - (hs-cleanup!) - (let ((_el-d1 (dom-create-element "div"))) - (dom-set-attr _el-d1 "id" "d1") - (dom-set-attr _el-d1 "_" "on click set #d1.innerHTML to promiseAString()") - (dom-append (dom-body) _el-d1) - (hs-activate! _el-d1) - (dom-dispatch _el-d1 "click" nil) - (assert= (dom-inner-html _el-d1) "foo") - )) - (deftest "can set many properties at once with object literal" - (hs-cleanup!) - (let ((_el-div (dom-create-element "div"))) - (dom-set-attr _el-div "_" "on click set {bar: 2, baz: 3} on obj") - (dom-append (dom-body) _el-div) - (hs-activate! _el-div) - ;; SKIP check: skip obj.should.deep.equal({ foo: 1, bar: 2, baz: 3 }) - )) - (deftest "can set props w/ array access syntax" - (hs-cleanup!) - (let ((_el-div (dom-create-element "div"))) - (dom-set-attr _el-div "_" "on click set my style[\"color\"] to \"red\"") - (dom-set-inner-html _el-div "lolwat") - (dom-append (dom-body) _el-div) - (hs-activate! _el-div) - (dom-dispatch _el-div "click" nil) - (assert= (dom-get-style _el-div "color") "red") - )) - (deftest "can set props w/ array access syntax and var" - (hs-cleanup!) - (let ((_el-div (dom-create-element "div"))) - (dom-set-attr _el-div "_" "on click set foo to \"color\" then set my style[foo] to \"red\"") - (dom-set-inner-html _el-div "lolwat") - (dom-append (dom-body) _el-div) - (hs-activate! _el-div) - (dom-dispatch _el-div "click" nil) - (assert= (dom-get-style _el-div "color") "red") - )) - (deftest "can set arrays w/ array access syntax" - (hs-cleanup!) - (let ((_el-div (dom-create-element "div"))) - (dom-set-attr _el-div "_" "on click set arr to [1, 2, 3] set arr[0] to \"red\" set my *color to arr[0]") - (dom-set-inner-html _el-div "lolwat") - (dom-append (dom-body) _el-div) - (hs-activate! _el-div) - (dom-dispatch _el-div "click" nil) - (assert= (dom-get-style _el-div "color") "red") - )) - (deftest "can set arrays w/ array access syntax and var" - (hs-cleanup!) - (let ((_el-div (dom-create-element "div"))) - (dom-set-attr _el-div "_" "on click set arr to [1, 2, 3] set idx to 0 set arr[idx] to \"red\" set my *color to arr[0]") - (dom-set-inner-html _el-div "lolwat") - (dom-append (dom-body) _el-div) - (hs-activate! _el-div) - (dom-dispatch _el-div "click" nil) - (assert= (dom-get-style _el-div "color") "red") - )) - (deftest "handles set url regression properly" - (hs-cleanup!) - (let ((_el-div (dom-create-element "div"))) - (dom-set-attr _el-div "_" "on click set trackingcode to `foo` then set pdfurl to `https://yyy.xxxxxx.com/path/out/${trackingcode}.pdf` then put pdfurl into me") - (dom-set-inner-html _el-div "lolwat") - (dom-append (dom-body) _el-div) - (hs-activate! _el-div) - (dom-dispatch _el-div "click" nil) - ;; SKIP check: skip d1.innerText.should.equal("https://yyy.xxxxxx.com/path/out/f - )) -) - -;; ── put (38 tests) ── -(defsuite "hs-upstream-put" - (deftest "can set properties" - (hs-cleanup!) - (let ((_el-d1 (dom-create-element "div"))) - (dom-set-attr _el-d1 "id" "d1") - (dom-set-attr _el-d1 "_" "on click put \"foo\" into #d1.innerHTML") - (dom-append (dom-body) _el-d1) - (hs-activate! _el-d1) - (dom-dispatch _el-d1 "click" nil) - (assert= (dom-inner-html _el-d1) "foo") - )) - (deftest "can put directly into nodes" - (hs-cleanup!) - (let ((_el-d1 (dom-create-element "div"))) - (dom-set-attr _el-d1 "id" "d1") - (dom-set-attr _el-d1 "_" "on click put \"foo\" into #d1") - (dom-append (dom-body) _el-d1) - (hs-activate! _el-d1) - (dom-dispatch _el-d1 "click" nil) - (assert= (dom-text-content _el-d1) "foo") - )) - (deftest "can put nodes into nodes" - (hs-cleanup!) - (let ((_el-d1 (dom-create-element "div")) (_el-d2 (dom-create-element "div"))) - (dom-set-attr _el-d1 "id" "d1") - (dom-set-attr _el-d2 "id" "d2") - (dom-set-attr _el-d2 "_" "on click put #d1 into #d2") - (dom-append (dom-body) _el-d1) - (dom-append (dom-body) _el-d2) - (hs-activate! _el-d2) - (dom-dispatch _el-d2 "click" nil) - ;; SKIP check: skip d2.firstChild.should.equal(d1) - )) - (deftest "can put directly into symbols" - (hs-cleanup!) - (let ((_el-div (dom-create-element "div"))) - (dom-set-attr _el-div "_" "on click put \"foo\" into me") - (dom-append (dom-body) _el-div) - (hs-activate! _el-div) - (dom-dispatch _el-div "click" nil) - (assert= (dom-inner-html _el-div) "foo") - )) - (deftest "me symbol doesn't get stomped on direct write" - (hs-cleanup!) - (let ((_el-div (dom-create-element "div"))) - (dom-set-attr _el-div "_" "on click put \"foo\" into me then put \"bar\" into me") - (dom-append (dom-body) _el-div) - (hs-activate! _el-div) - (dom-dispatch _el-div "click" nil) - (assert= (dom-inner-html _el-div) "bar") - )) - (deftest "can set styles" - (hs-cleanup!) - (let ((_el-div (dom-create-element "div"))) - (dom-set-attr _el-div "_" "on click put \"red\" into my.style.color") - (dom-set-inner-html _el-div "lolwat") - (dom-append (dom-body) _el-div) - (hs-activate! _el-div) - (dom-dispatch _el-div "click" nil) - (assert= (dom-get-style _el-div "color") "red") - )) - (deftest "can set javascript globals" - (hs-cleanup!) - (let ((_el-div (dom-create-element "div"))) - (dom-set-attr _el-div "_" "on click put \"red\" into window.temp") - (dom-set-inner-html _el-div "lolwat") - (dom-append (dom-body) _el-div) - (hs-activate! _el-div) - (dom-dispatch _el-div "click" nil) - ;; SKIP check: skip window["temp"].should.equal("red") - )) - (deftest "can set into class ref w/ flatmapped property" - (hs-cleanup!) - (let ((_el-div (dom-create-element "div")) (_el-d1 (dom-create-element "div")) (_el-div2 (dom-create-element "div")) (_el-d2 (dom-create-element "div")) (_el-div4 (dom-create-element "div"))) - (dom-set-attr _el-div "_" "on click put \"foo\" into .divs.parentElement.innerHTML") - (dom-set-attr _el-d1 "id" "d1") - (dom-add-class _el-div2 "divs") - (dom-set-attr _el-d2 "id" "d2") - (dom-add-class _el-div4 "divs") - (dom-append (dom-body) _el-div) - (dom-append (dom-body) _el-d1) - (dom-append _el-d1 _el-div2) - (dom-append (dom-body) _el-d2) - (dom-append _el-d2 _el-div4) - (hs-activate! _el-div) - (dom-dispatch _el-div "click" nil) - (assert= (dom-text-content _el-d1) "foo") - (assert= (dom-text-content _el-d2) "foo") - )) - (deftest "can set into class ref w/ flatmapped property using of" - (hs-cleanup!) - (let ((_el-div (dom-create-element "div")) (_el-d1 (dom-create-element "div")) (_el-div2 (dom-create-element "div")) (_el-d2 (dom-create-element "div")) (_el-div4 (dom-create-element "div"))) - (dom-set-attr _el-div "_" "on click put \"foo\" into innerHTML of parentElement of .divs") - (dom-set-attr _el-d1 "id" "d1") - (dom-add-class _el-div2 "divs") - (dom-set-attr _el-d2 "id" "d2") - (dom-add-class _el-div4 "divs") - (dom-append (dom-body) _el-div) - (dom-append (dom-body) _el-d1) - (dom-append _el-d1 _el-div2) - (dom-append (dom-body) _el-d2) - (dom-append _el-d2 _el-div4) - (hs-activate! _el-div) - (dom-dispatch _el-div "click" nil) - (assert= (dom-text-content _el-d1) "foo") - (assert= (dom-text-content _el-d2) "foo") - )) - (deftest "can set local variables" - (hs-cleanup!) - (let ((_el-d1 (dom-create-element "div"))) - (dom-set-attr _el-d1 "id" "d1") - (dom-set-attr _el-d1 "_" "on click put \"foo\" into newVar then put newVar into #d1.innerHTML") - (dom-append (dom-body) _el-d1) - (hs-activate! _el-d1) - (dom-dispatch _el-d1 "click" nil) - (assert= (dom-inner-html _el-d1) "foo") - )) - (deftest "can set into id ref" - (hs-cleanup!) - (let ((_el-d1 (dom-create-element "div"))) - (dom-set-attr _el-d1 "id" "d1") - (dom-set-attr _el-d1 "_" "on click put \"foo\" into #d1.innerHTML") - (dom-append (dom-body) _el-d1) - (hs-activate! _el-d1) - (dom-dispatch _el-d1 "click" nil) - (assert= (dom-inner-html _el-d1) "foo") - )) - (deftest "can insert before" - (hs-cleanup!) - (let ((_el-d2 (dom-create-element "div")) (_el-d1 (dom-create-element "div"))) - (dom-set-attr _el-d2 "id" "d2") - (dom-set-attr _el-d2 "_" "on click put #d1 before #d2") - (dom-set-attr _el-d1 "id" "d1") - (dom-set-inner-html _el-d1 "foo") - (dom-append (dom-body) _el-d2) - (dom-append (dom-body) _el-d1) - (hs-activate! _el-d2) - (dom-dispatch _el-d2 "click" nil) - ;; SKIP check: skip d2.previousSibling.textContent.should.equal("foo") - )) - (deftest "can insert after" - (hs-cleanup!) - (let ((_el-d1 (dom-create-element "div")) (_el-d2 (dom-create-element "div"))) - (dom-set-attr _el-d1 "id" "d1") - (dom-set-inner-html _el-d1 "foo") - (dom-set-attr _el-d2 "id" "d2") - (dom-set-attr _el-d2 "_" "on click put #d1 after #d2") - (dom-append (dom-body) _el-d1) - (dom-append (dom-body) _el-d2) - (hs-activate! _el-d2) - (dom-dispatch _el-d2 "click" nil) - ;; SKIP check: skip d2.nextSibling.textContent.should.equal("foo") - )) - (deftest "can insert after beginning" - (hs-cleanup!) - (let ((_el-d1 (dom-create-element "div"))) - (dom-set-attr _el-d1 "id" "d1") - (dom-set-attr _el-d1 "_" "on click put \"foo\" at start of #d1") - (dom-set-inner-html _el-d1 "*") - (dom-append (dom-body) _el-d1) - (hs-activate! _el-d1) - (dom-dispatch _el-d1 "click" nil) - (assert= (dom-text-content _el-d1) "foo*") - )) - (deftest "can insert before end" - (hs-cleanup!) - (let ((_el-d1 (dom-create-element "div"))) - (dom-set-attr _el-d1 "id" "d1") - (dom-set-attr _el-d1 "_" "on click put \"foo\" at end of #d1") - (dom-set-inner-html _el-d1 "*") - (dom-append (dom-body) _el-d1) - (hs-activate! _el-d1) - (dom-dispatch _el-d1 "click" nil) - (assert= (dom-text-content _el-d1) "*foo") - )) - (deftest "can set into attribute ref" - (hs-cleanup!) - (let ((_el-div (dom-create-element "div"))) - (dom-add-class _el-div "divs") - (dom-set-attr _el-div "_" "on click put \"foo\" into @bar") - (dom-append (dom-body) _el-div) - (hs-activate! _el-div) - (dom-dispatch _el-div "click" nil) - (assert= (dom-get-attr _el-div "bar") "foo") - )) - (deftest "can set into indirect attribute ref" - (hs-cleanup!) - (let ((_el-div (dom-create-element "div"))) - (dom-add-class _el-div "divs") - (dom-set-attr _el-div "_" "on click put \"foo\" into my @bar") - (dom-append (dom-body) _el-div) - (hs-activate! _el-div) - (dom-dispatch _el-div "click" nil) - (assert= (dom-get-attr _el-div "bar") "foo") - )) - (deftest "can set into indirect attribute ref 2" - (hs-cleanup!) - (let ((_el-div (dom-create-element "div")) (_el-div2 (dom-create-element "div"))) - (dom-add-class _el-div "divs") - (dom-set-attr _el-div "_" "on click put 'foo' into #div2's @bar") - (dom-set-attr _el-div2 "id" "div2") - (dom-append (dom-body) _el-div) - (dom-append (dom-body) _el-div2) - (hs-activate! _el-div) - (dom-dispatch _el-div "click" nil) - (assert= (dom-get-attr _el-div2 "bar") "foo") - )) - (deftest "can set into indirect attribute ref 3" - (hs-cleanup!) - (let ((_el-div (dom-create-element "div")) (_el-div2 (dom-create-element "div"))) - (dom-add-class _el-div "divs") - (dom-set-attr _el-div "_" "on click put 'foo' into @bar of #div2") - (dom-set-attr _el-div2 "id" "div2") - (dom-append (dom-body) _el-div) - (dom-append (dom-body) _el-div2) - (hs-activate! _el-div) - (dom-dispatch _el-div "click" nil) - (assert= (dom-get-attr _el-div2 "bar") "foo") - )) - (deftest "can set into style ref" - (hs-cleanup!) - (let ((_el-div (dom-create-element "div"))) - (dom-add-class _el-div "divs") - (dom-set-attr _el-div "_" "on click put \"red\" into *color") - (dom-append (dom-body) _el-div) - (hs-activate! _el-div) - (dom-dispatch _el-div "click" nil) - ;; SKIP check: skip d1.style["color"].should.equal("red") - )) - (deftest "can set into indirect style ref" - (hs-cleanup!) - (let ((_el-div (dom-create-element "div"))) - (dom-add-class _el-div "divs") - (dom-set-attr _el-div "_" "on click put \"red\" into my *color") - (dom-append (dom-body) _el-div) - (hs-activate! _el-div) - (dom-dispatch _el-div "click" nil) - ;; SKIP check: skip d1.style["color"].should.equal("red") - )) - (deftest "can set into indirect style ref 2" - (hs-cleanup!) - (let ((_el-div (dom-create-element "div")) (_el-div2 (dom-create-element "div"))) - (dom-add-class _el-div "divs") - (dom-set-attr _el-div "_" "on click put 'red' into #div2's *color") - (dom-set-attr _el-div2 "id" "div2") - (dom-append (dom-body) _el-div) - (dom-append (dom-body) _el-div2) - (hs-activate! _el-div) - (dom-dispatch _el-div "click" nil) - ;; SKIP check: skip d2.style["color"].should.equal("red") - )) - (deftest "can set into indirect style ref 3" - (hs-cleanup!) - (let ((_el-div (dom-create-element "div")) (_el-div2 (dom-create-element "div"))) - (dom-add-class _el-div "divs") - (dom-set-attr _el-div "_" "on click put 'red' into the *color of #div2") - (dom-set-attr _el-div2 "id" "div2") - (dom-append (dom-body) _el-div) - (dom-append (dom-body) _el-div2) - (hs-activate! _el-div) - (dom-dispatch _el-div "click" nil) - ;; SKIP check: skip d2.style["color"].should.equal("red") - )) - (deftest "waits on promises" - (hs-cleanup!) - (let ((_el-d1 (dom-create-element "div"))) - (dom-set-attr _el-d1 "id" "d1") - (dom-set-attr _el-d1 "_" "on click put promiseAString() into #d1.innerHTML") - (dom-append (dom-body) _el-d1) - (hs-activate! _el-d1) - (dom-dispatch _el-d1 "click" nil) - (assert= (dom-inner-html _el-d1) "foo") - )) - (deftest "can put properties w/ array access syntax" - (hs-cleanup!) - (let ((_el-div (dom-create-element "div"))) - (dom-set-attr _el-div "_" "on click put \"red\" into my style[\"color\"]") - (dom-set-inner-html _el-div "lolwat") - (dom-append (dom-body) _el-div) - (hs-activate! _el-div) - (dom-dispatch _el-div "click" nil) - (assert= (dom-get-style _el-div "color") "red") - )) - (deftest "can put properties w/ array access syntax and var" - (hs-cleanup!) - (let ((_el-div (dom-create-element "div"))) - (dom-set-attr _el-div "_" "on click set foo to \"color\" then put \"red\" into my style[foo]") - (dom-set-inner-html _el-div "lolwat") - (dom-append (dom-body) _el-div) - (hs-activate! _el-div) - (dom-dispatch _el-div "click" nil) - (assert= (dom-get-style _el-div "color") "red") - )) - (deftest "can put array vals w/ array access syntax" - (hs-cleanup!) - (let ((_el-div (dom-create-element "div"))) - (dom-set-attr _el-div "_" "on click set arr to [1, 2, 3] put \"red\" into arr[0] put arr[0] into my *color") - (dom-set-inner-html _el-div "lolwat") - (dom-append (dom-body) _el-div) - (hs-activate! _el-div) - (dom-dispatch _el-div "click" nil) - (assert= (dom-get-style _el-div "color") "red") - )) - (deftest "can put array vals w/ array access syntax and var" - (hs-cleanup!) - (let ((_el-div (dom-create-element "div"))) - (dom-set-attr _el-div "_" "on click set arr to [1, 2, 3] set idx to 0 put \"red\" into arr[idx] put arr[0] into my *color") - (dom-set-inner-html _el-div "lolwat") - (dom-append (dom-body) _el-div) - (hs-activate! _el-div) - (dom-dispatch _el-div "click" nil) - (assert= (dom-get-style _el-div "color") "red") - )) - (deftest "properly processes hyperscript in new content in a symbol write" - (hs-cleanup!) - (let ((_el-div (dom-create-element "div"))) - (dom-set-attr _el-div "_" "on click put \"\" into me") - (dom-append (dom-body) _el-div) - (hs-activate! _el-div) - (dom-dispatch _el-div "click" nil) - (dom-dispatch (dom-query "button") "click" nil) - (assert= (dom-inner-html (dom-query "button")) "42") - )) - (deftest "properly processes hyperscript in new content in a element target" - (hs-cleanup!) - (let ((_el-d1 (dom-create-element "div"))) - (dom-set-attr _el-d1 "id" "d1") - (dom-set-attr _el-d1 "_" "on click put \"\" into ") - (dom-append (dom-body) _el-d1) - (hs-activate! _el-d1) - (dom-dispatch _el-d1 "click" nil) - (dom-dispatch (dom-query "button") "click" nil) - (assert= (dom-inner-html (dom-query "button")) "42") - )) - (deftest "properly processes hyperscript in before" - (hs-cleanup!) - (let ((_el-d1 (dom-create-element "div"))) - (dom-set-attr _el-d1 "id" "d1") - (dom-set-attr _el-d1 "_" "on click put \"\" before me") - (dom-append (dom-body) _el-d1) - (hs-activate! _el-d1) - (dom-dispatch _el-d1 "click" nil) - (dom-dispatch (dom-query "button") "click" nil) - (assert= (dom-inner-html (dom-query "button")) "42") - )) - (deftest "properly processes hyperscript at start of" - (hs-cleanup!) - (let ((_el-d1 (dom-create-element "div"))) - (dom-set-attr _el-d1 "id" "d1") - (dom-set-attr _el-d1 "_" "on click put \"\" at the start of me") - (dom-append (dom-body) _el-d1) - (hs-activate! _el-d1) - (dom-dispatch _el-d1 "click" nil) - (dom-dispatch (dom-query "button") "click" nil) - (assert= (dom-inner-html (dom-query "button")) "42") - )) - (deftest "properly processes hyperscript at end of" - (hs-cleanup!) - (let ((_el-d1 (dom-create-element "div"))) - (dom-set-attr _el-d1 "id" "d1") - (dom-set-attr _el-d1 "_" "on click put \"\" at the end of me") - (dom-append (dom-body) _el-d1) - (hs-activate! _el-d1) - (dom-dispatch _el-d1 "click" nil) - (dom-dispatch (dom-query "button") "click" nil) - (assert= (dom-inner-html (dom-query "button")) "42") - )) - (deftest "properly processes hyperscript after" - (hs-cleanup!) - (let ((_el-d1 (dom-create-element "div"))) - (dom-set-attr _el-d1 "id" "d1") - (dom-set-attr _el-d1 "_" "on click put \"\" after me") - (dom-append (dom-body) _el-d1) - (hs-activate! _el-d1) - (dom-dispatch _el-d1 "click" nil) - (dom-dispatch (dom-query "button") "click" nil) - (assert= (dom-inner-html (dom-query "button")) "42") - )) - (deftest "is null tolerant" - (hs-cleanup!) - (let ((_el-div (dom-create-element "div"))) - (dom-add-class _el-div "divs") - (dom-set-attr _el-div "_" "on click put \"red\" into #a-bad-id-that-does-not-exist") - (dom-append (dom-body) _el-div) - (hs-activate! _el-div) - (dom-dispatch _el-div "click" nil) - )) - (deftest "put null into attribute removes it" - (hs-cleanup!) - (let ((_el-d1 (dom-create-element "div"))) - (dom-set-attr _el-d1 "id" "d1") - (dom-set-attr _el-d1 "_" "on click put null into @foo") - (dom-set-attr _el-d1 "foo" "bar") - (dom-append (dom-body) _el-d1) - (hs-activate! _el-d1) - (dom-dispatch (dom-query-by-id "d1") "click" nil) - (assert (not (dom-has-attr? (dom-query-by-id "d1") "foo"))) - )) - (deftest "can put at start of an array" - (hs-cleanup!) - (let ((_el-div (dom-create-element "div"))) - (dom-set-attr _el-div "_" "on click set :arr to [2,3] then put 1 at start of :arr then put :arr as String into me") - (dom-append (dom-body) _el-div) - (hs-activate! _el-div) - (dom-dispatch _el-div "click" nil) - (assert= (dom-text-content _el-div) "1,2,3") - )) - (deftest "can put at end of an array" - (hs-cleanup!) - (let ((_el-div (dom-create-element "div"))) - (dom-set-attr _el-div "_" "on click set :arr to [1,2] then put 3 at end of :arr then put :arr as String into me") - (dom-append (dom-body) _el-div) - (hs-activate! _el-div) - (dom-dispatch _el-div "click" nil) - (assert= (dom-text-content _el-div) "1,2,3") - )) -) - -;; ── hide (14 tests) ── -(defsuite "hs-upstream-hide" - (deftest "can hide element with no target" - (hs-cleanup!) - (let ((_el-div (dom-create-element "div"))) - (dom-set-attr _el-div "_" "on click hide") - (dom-append (dom-body) _el-div) - (hs-activate! _el-div) - (dom-dispatch _el-div "click" nil) - ;; SKIP computed style: div.display - )) - (deftest "hide element then show element retains original display" - (hs-cleanup!) - (let ((_el-div (dom-create-element "div"))) - (dom-set-attr _el-div "_" "on click 1 hide on click 2 show") - (dom-append (dom-body) _el-div) - (hs-activate! _el-div) - (dom-dispatch _el-div "click" nil) - (dom-dispatch _el-div "click" nil) - ;; SKIP computed style: div.display - )) - (deftest "can hide element with no target followed by command" - (hs-cleanup!) - (let ((_el-div (dom-create-element "div"))) - (dom-set-attr _el-div "_" "on click hide add .foo") - (dom-append (dom-body) _el-div) - (hs-activate! _el-div) - (dom-dispatch _el-div "click" nil) - ;; SKIP computed style: div.display - (assert (dom-has-class? _el-div "foo")) - )) - (deftest "can hide element with no target followed by then" - (hs-cleanup!) - (let ((_el-div (dom-create-element "div"))) - (dom-set-attr _el-div "_" "on click hide then add .foo") - (dom-append (dom-body) _el-div) - (hs-activate! _el-div) - (dom-dispatch _el-div "click" nil) - ;; SKIP computed style: div.display - (assert (dom-has-class? _el-div "foo")) - )) - (deftest "can hide element with no target with a with" - (hs-cleanup!) - (let ((_el-div (dom-create-element "div"))) - (dom-set-attr _el-div "_" "on click hide with display then add .foo") - (dom-append (dom-body) _el-div) - (hs-activate! _el-div) - (dom-dispatch _el-div "click" nil) - ;; SKIP computed style: div.display - (assert (dom-has-class? _el-div "foo")) - )) - (deftest "can hide element, with display:none by default" - (hs-cleanup!) - (let ((_el-div (dom-create-element "div"))) - (dom-set-attr _el-div "_" "on click hide me") - (dom-append (dom-body) _el-div) - (hs-activate! _el-div) - (dom-dispatch _el-div "click" nil) - ;; SKIP computed style: div.display - )) - (deftest "can hide element with display:none explicitly" - (hs-cleanup!) - (let ((_el-div (dom-create-element "div"))) - (dom-set-attr _el-div "_" "on click hide me with display") - (dom-append (dom-body) _el-div) - (hs-activate! _el-div) - (dom-dispatch _el-div "click" nil) - ;; SKIP computed style: div.display - )) - (deftest "can hide element with opacity:0" - (hs-cleanup!) - (let ((_el-div (dom-create-element "div"))) - (dom-set-attr _el-div "_" "on click hide me with opacity") - (dom-append (dom-body) _el-div) - (hs-activate! _el-div) - (dom-dispatch _el-div "click" nil) - ;; SKIP computed style: div.opacity - )) - (deftest "can hide element with opacity style literal" - (hs-cleanup!) - (let ((_el-div (dom-create-element "div"))) - (dom-set-attr _el-div "_" "on click hide me with *opacity") - (dom-append (dom-body) _el-div) - (hs-activate! _el-div) - (dom-dispatch _el-div "click" nil) - ;; SKIP computed style: div.opacity - )) - (deftest "can hide element, with visibility:hidden" - (hs-cleanup!) - (let ((_el-div (dom-create-element "div"))) - (dom-set-attr _el-div "_" "on click hide me with visibility") - (dom-append (dom-body) _el-div) - (hs-activate! _el-div) - (dom-dispatch _el-div "click" nil) - ;; SKIP computed style: div.visibility - )) - (deftest "can hide other elements" - (hs-cleanup!) - (let ((_el-div (dom-create-element "div")) (_el-div1 (dom-create-element "div"))) - (dom-add-class _el-div "hideme") - (dom-set-attr _el-div1 "_" "on click hide .hideme") - (dom-append (dom-body) _el-div) - (dom-append (dom-body) _el-div1) - (hs-activate! _el-div1) - (dom-dispatch _el-div "click" nil) - ;; SKIP computed style: hideme.display - )) - (deftest "can hide with custom strategy" - (hs-cleanup!) - (let ((_el-div (dom-create-element "div"))) - (dom-set-attr _el-div "_" "on click hide with myHide") - (dom-append (dom-body) _el-div) - (hs-activate! _el-div) - ;; SKIP action: classList.remove__foo__ - (dom-dispatch _el-div "click" nil) - (assert (dom-has-class? _el-div "foo")) - )) - (deftest "can set default to custom strategy" - (hs-cleanup!) - (let ((_el-div (dom-create-element "div"))) - (dom-set-attr _el-div "_" "on click hide") - (dom-append (dom-body) _el-div) - (hs-activate! _el-div) - ;; SKIP action: classList.remove__foo__ - (dom-dispatch _el-div "click" nil) - (assert (dom-has-class? _el-div "foo")) - )) - (deftest "can filter hide via the when clause" - (hs-cleanup!) - (let ((_el-trigger (dom-create-element "div")) (_el-d1 (dom-create-element "div")) (_el-d2 (dom-create-element "div"))) - (dom-set-attr _el-trigger "id" "trigger") - (dom-set-attr _el-trigger "_" "on click hide

in me when it matches .hideable") - (dom-set-attr _el-d1 "id" "d1") - (dom-add-class _el-d1 "hideable") - (dom-set-attr _el-d2 "id" "d2") - (dom-append (dom-body) _el-trigger) - (dom-append _el-trigger _el-d1) - (dom-append _el-trigger _el-d2) - (hs-activate! _el-trigger) - (dom-dispatch (dom-query-by-id "trigger") "click" nil) - (assert= (dom-get-style (dom-query-by-id "d1") "display") "none") - (assert= (dom-get-style (dom-query-by-id "d2") "display") "block") - )) -) - -;; ── if (19 tests) ── -(defsuite "hs-upstream-if" - (deftest "basic true branch works" - (hs-cleanup!) - (let ((_el-div (dom-create-element "div"))) - (dom-set-attr _el-div "_" "on click if true put \"foo\" into me.innerHTML") - (dom-append (dom-body) _el-div) - (hs-activate! _el-div) - (dom-dispatch _el-div "click" nil) - (assert= (dom-inner-html _el-div) "foo") - )) - (deftest "basic true branch works with multiple commands" - (hs-cleanup!) - (let ((_el-div (dom-create-element "div"))) - (dom-set-attr _el-div "_" "on click if true log me then put \"foo\" into me.innerHTML") - (dom-append (dom-body) _el-div) - (hs-activate! _el-div) - (dom-dispatch _el-div "click" nil) - (assert= (dom-inner-html _el-div) "foo") - )) - (deftest "basic true branch works with end" - (hs-cleanup!) - (let ((_el-div (dom-create-element "div"))) - (dom-set-attr _el-div "_" "on click if true put \"foo\" into me.innerHTML end") - (dom-append (dom-body) _el-div) - (hs-activate! _el-div) - (dom-dispatch _el-div "click" nil) - (assert= (dom-inner-html _el-div) "foo") - )) - (deftest "basic true branch works with naked else" - (hs-cleanup!) - (let ((_el-div (dom-create-element "div"))) - (dom-set-attr _el-div "_" "on click if true put \"foo\" into me.innerHTML else") - (dom-append (dom-body) _el-div) - (hs-activate! _el-div) - (dom-dispatch _el-div "click" nil) - (assert= (dom-inner-html _el-div) "foo") - )) - (deftest "basic true branch works with naked else end" - (hs-cleanup!) - (let ((_el-div (dom-create-element "div"))) - (dom-set-attr _el-div "_" "on click if true put \"foo\" into me.innerHTML else end") - (dom-append (dom-body) _el-div) - (hs-activate! _el-div) - (dom-dispatch _el-div "click" nil) - (assert= (dom-inner-html _el-div) "foo") - )) - (deftest "basic else branch works" - (hs-cleanup!) - (let ((_el-div (dom-create-element "div"))) - (dom-set-attr _el-div "_" "on click if false else put \"foo\" into me.innerHTML") - (dom-append (dom-body) _el-div) - (hs-activate! _el-div) - (dom-dispatch _el-div "click" nil) - (assert= (dom-inner-html _el-div) "foo") - )) - (deftest "basic else branch works with end" - (hs-cleanup!) - (let ((_el-div (dom-create-element "div"))) - (dom-set-attr _el-div "_" "on click if false else put \"foo\" into me.innerHTML end") - (dom-append (dom-body) _el-div) - (hs-activate! _el-div) - (dom-dispatch _el-div "click" nil) - (assert= (dom-inner-html _el-div) "foo") - )) - (deftest "basic else if branch works" - (hs-cleanup!) - (let ((_el-div (dom-create-element "div"))) - (dom-set-attr _el-div "_" "on click if false else if true put \"foo\" into me.innerHTML") - (dom-append (dom-body) _el-div) - (hs-activate! _el-div) - (dom-dispatch _el-div "click" nil) - (assert= (dom-inner-html _el-div) "foo") - )) - (deftest "basic else if branch works with end" - (hs-cleanup!) - (let ((_el-div (dom-create-element "div"))) - (dom-set-attr _el-div "_" "on click if false else if true put \"foo\" into me.innerHTML end") - (dom-append (dom-body) _el-div) - (hs-activate! _el-div) - (dom-dispatch _el-div "click" nil) - (assert= (dom-inner-html _el-div) "foo") - )) - (deftest "otherwise alias works" - (hs-cleanup!) - (let ((_el-div (dom-create-element "div"))) - (dom-set-attr _el-div "_" "on click if false otherwise put \"foo\" into me.innerHTML") - (dom-append (dom-body) _el-div) - (hs-activate! _el-div) - (dom-dispatch _el-div "click" nil) - (assert= (dom-inner-html _el-div) "foo") - )) - (deftest "triple else if branch works" - (hs-cleanup!) - (let ((_el-div (dom-create-element "div"))) - (dom-set-attr _el-div "_" "on click if false else if false else put \"foo\" into me.innerHTML") - (dom-append (dom-body) _el-div) - (hs-activate! _el-div) - (dom-dispatch _el-div "click" nil) - (assert= (dom-inner-html _el-div) "foo") - )) - (deftest "triple else if branch works with end" - (hs-cleanup!) - (let ((_el-div (dom-create-element "div"))) - (dom-set-attr _el-div "_" "on click if false else if false else put \"foo\" into me.innerHTML end") - (dom-append (dom-body) _el-div) - (hs-activate! _el-div) - (dom-dispatch _el-div "click" nil) - (assert= (dom-inner-html _el-div) "foo") - )) - (deftest "basic else branch works with multiple commands" - (hs-cleanup!) - (let ((_el-div (dom-create-element "div"))) - (dom-set-attr _el-div "_" "on click if false put \"bar\" into me.innerHTML else log me then put \"foo\" into me.innerHTML") - (dom-append (dom-body) _el-div) - (hs-activate! _el-div) - (dom-dispatch _el-div "click" nil) - (assert= (dom-inner-html _el-div) "foo") - )) - (deftest "true branch with a wait works" - (hs-cleanup!) - (let ((_el-div (dom-create-element "div"))) - (dom-set-attr _el-div "_" "on click if true wait 10 ms then put \"foo\" into me.innerHTML") - (dom-append (dom-body) _el-div) - (hs-activate! _el-div) - (dom-dispatch _el-div "click" nil) - (assert= (dom-inner-html _el-div) "foo") - )) - (deftest "false branch with a wait works" - (hs-cleanup!) - (let ((_el-div (dom-create-element "div"))) - (dom-set-attr _el-div "_" "on click if false else wait 10 ms then put \"foo\" into me.innerHTML") - (dom-append (dom-body) _el-div) - (hs-activate! _el-div) - (dom-dispatch _el-div "click" nil) - (assert= (dom-inner-html _el-div) "foo") - )) - (deftest "if properly passes execution along if child is not executed" - (hs-cleanup!) - (let ((_el-div (dom-create-element "div"))) - (dom-set-attr _el-div "_" "on click if false end put \"foo\" into me.innerHTML") - (dom-append (dom-body) _el-div) - (hs-activate! _el-div) - (dom-dispatch _el-div "click" nil) - (assert= (dom-inner-html _el-div) "foo") - )) - (deftest "if properly supports nested if statements and end block" - (hs-cleanup!) - (let ((_el-div (dom-create-element "div"))) - (dom-set-attr _el-div "_" "on click if window.tmp then put \"foo\" into me then else if not window.tmp then // do nothing then end catch e then // just here for the parsing... then") - (dom-append (dom-body) _el-div) - (hs-activate! _el-div) - (dom-dispatch _el-div "click" nil) - (dom-dispatch _el-div "click" nil) - (assert= (dom-inner-html _el-div) "foo") - )) - (deftest "if on new line does not join w/ else" - (hs-cleanup!) - (let ((_el-div (dom-create-element "div"))) - (dom-set-attr _el-div "_" "on click if window.tmp then else then if window.tmp then end put \"foo\" into me then end") - (dom-append (dom-body) _el-div) - (hs-activate! _el-div) - (dom-dispatch _el-div "click" nil) - (dom-dispatch _el-div "click" nil) - (assert= (dom-inner-html _el-div) "") - )) - (deftest "passes the sieve test" - (hs-cleanup!)) -) - -;; ── repeat (30 tests) ── -(defsuite "hs-upstream-repeat" - (deftest "basic for loop works" - (hs-cleanup!) - (let ((_el-div (dom-create-element "div"))) - (dom-set-attr _el-div "_" "on click repeat for x in [1, 2, 3] put x at end of me end") - (dom-append (dom-body) _el-div) - (hs-activate! _el-div) - (dom-dispatch _el-div "click" nil) - (assert= (dom-inner-html _el-div) "123") - )) - (deftest "basic for loop with null works" - (hs-cleanup!) - (let ((_el-div (dom-create-element "div"))) - (dom-set-attr _el-div "_" "on click repeat for x in null put x at end of me end") - (dom-append (dom-body) _el-div) - (hs-activate! _el-div) - (dom-dispatch _el-div "click" nil) - (assert= (dom-inner-html _el-div) "") - )) - (deftest "waiting in for loop works" - (hs-cleanup!) - (let ((_el-div (dom-create-element "div"))) - (dom-set-attr _el-div "_" "on click repeat for x in [1, 2, 3] log me then put x at end of me then wait 1ms then end") - (dom-append (dom-body) _el-div) - (hs-activate! _el-div) - (dom-dispatch _el-div "click" nil) - (assert= (dom-inner-html _el-div) "123") - )) - (deftest "basic raw for loop works" - (hs-cleanup!) - (let ((_el-div (dom-create-element "div"))) - (dom-set-attr _el-div "_" "on click for x in [1, 2, 3] put x at end of me end") - (dom-append (dom-body) _el-div) - (hs-activate! _el-div) - (dom-dispatch _el-div "click" nil) - (assert= (dom-inner-html _el-div) "123") - )) - (deftest "basic raw for loop works" - (hs-cleanup!) - (let ((_el-div (dom-create-element "div"))) - (dom-set-attr _el-div "_" "on click for x in null put x at end of me end") - (dom-append (dom-body) _el-div) - (hs-activate! _el-div) - (dom-dispatch _el-div "click" nil) - (assert= (dom-inner-html _el-div) "") - )) - (deftest "waiting in raw for loop works" - (hs-cleanup!) - (let ((_el-div (dom-create-element "div"))) - (dom-set-attr _el-div "_" "on click for x in [1, 2, 3] put x at end of me then wait 1ms then end") - (dom-append (dom-body) _el-div) - (hs-activate! _el-div) - (dom-dispatch _el-div "click" nil) - (assert= (dom-inner-html _el-div) "123") - )) - (deftest "repeat forever works" - (hs-cleanup!) - (eval-expr-cek (hs-to-sx (hs-compile "def repeatForeverWithReturn() set retVal to 0 repeat forever set retVal to retVal + 1 if retVal == 5 then return retVal end end end"))) - (let ((_el-d1 (dom-create-element "div"))) - (dom-set-attr _el-d1 "id" "d1") - (dom-set-attr _el-d1 "_" "on click put repeatForeverWithReturn() into my.innerHTML") - (dom-append (dom-body) _el-d1) - (hs-activate! _el-d1) - (dom-dispatch _el-d1 "click" nil) - (assert= (dom-inner-html _el-d1) "5") - )) - (deftest "repeat forever works w/o keyword" - (hs-cleanup!) - (eval-expr-cek (hs-to-sx (hs-compile "def repeatForeverWithReturn() set retVal to 0 repeat set retVal to retVal + 1 if retVal == 5 then return retVal end end end"))) - (let ((_el-d1 (dom-create-element "div"))) - (dom-set-attr _el-d1 "id" "d1") - (dom-set-attr _el-d1 "_" "on click put repeatForeverWithReturn() into my.innerHTML") - (dom-append (dom-body) _el-d1) - (hs-activate! _el-d1) - (dom-dispatch _el-d1 "click" nil) - (assert= (dom-inner-html _el-d1) "5") - )) - (deftest "basic in loop works" - (hs-cleanup!) - (let ((_el-div (dom-create-element "div"))) - (dom-set-attr _el-div "_" "on click repeat in [1, 2, 3] put it at end of me end") - (dom-append (dom-body) _el-div) - (hs-activate! _el-div) - (dom-dispatch _el-div "click" nil) - (assert= (dom-inner-html _el-div) "123") - )) - (deftest "index syntax works" - (hs-cleanup!) - (let ((_el-div (dom-create-element "div"))) - (dom-set-attr _el-div "_" "on click repeat for x in [\"a\", \"ab\", \"abc\"] index i then put x + i at end of me end") - (dom-append (dom-body) _el-div) - (hs-activate! _el-div) - (dom-dispatch _el-div "click" nil) - (assert= (dom-inner-html _el-div) "a0ab1abc2") - )) - (deftest "indexed by syntax works" - (hs-cleanup!) - (let ((_el-div (dom-create-element "div"))) - (dom-set-attr _el-div "_" "on click repeat for x in [\"a\", \"ab\", \"abc\"] indexed by i then put x + i at end of me end") - (dom-append (dom-body) _el-div) - (hs-activate! _el-div) - (dom-dispatch _el-div "click" nil) - (assert= (dom-inner-html _el-div) "a0ab1abc2") - )) - (deftest "while keyword works" - (hs-cleanup!) - (eval-expr-cek (hs-to-sx (hs-compile "def repeatWhileTest() set retVal to 0 repeat while retVal < 5 set retVal to retVal + 1 end return retVal end"))) - (let ((_el-d1 (dom-create-element "div"))) - (dom-set-attr _el-d1 "id" "d1") - (dom-set-attr _el-d1 "_" "on click put repeatWhileTest() into my.innerHTML") - (dom-append (dom-body) _el-d1) - (hs-activate! _el-d1) - (dom-dispatch _el-d1 "click" nil) - (assert= (dom-inner-html _el-d1) "5") - )) - (deftest "until keyword works" - (hs-cleanup!) - (eval-expr-cek (hs-to-sx (hs-compile "def repeatUntilTest() set retVal to 0 repeat until retVal == 5 set retVal to retVal + 1 end return retVal end"))) - (let ((_el-d1 (dom-create-element "div"))) - (dom-set-attr _el-d1 "id" "d1") - (dom-set-attr _el-d1 "_" "on click put repeatUntilTest() into my.innerHTML") - (dom-append (dom-body) _el-d1) - (hs-activate! _el-d1) - (dom-dispatch _el-d1 "click" nil) - (assert= (dom-inner-html _el-d1) "5") - )) - (deftest "until event keyword works" - (hs-cleanup!) - (eval-expr-cek (hs-to-sx (hs-compile "def repeatUntilTest() repeat until event click from #untilTest wait 2ms end return 42 end"))) - (let ((_el-untilTest (dom-create-element "div"))) - (dom-set-attr _el-untilTest "id" "untilTest") - (dom-append (dom-body) _el-untilTest) - (dom-dispatch _el-untilTest "click" nil) - ;; SKIP check: skip value.should.equal(42) - )) - (deftest "only executes the init expression once" - (hs-cleanup!) - (eval-expr-cek (hs-to-sx (hs-compile "def getArray() set window.called to (window.called or 0) + 1 return [1, 2, 3] end"))) - (let ((_el-d1 (dom-create-element "div"))) - (dom-set-attr _el-d1 "id" "d1") - (dom-set-attr _el-d1 "_" "on click for x in getArray() put x into my.innerHTML end") - (dom-append (dom-body) _el-d1) - (hs-activate! _el-d1) - (dom-dispatch _el-d1 "click" nil) - (assert= (dom-inner-html _el-d1) "3") - ;; SKIP check: skip window.called.should.equal(1) - )) - (deftest "can nest loops" - (hs-cleanup!) - (eval-expr-cek (hs-to-sx (hs-compile "def sprayInto(elt) for x in [1, 2, 3] for y in [1, 2, 3] put x * y at end of elt end end end"))) - (let ((_el-d1 (dom-create-element "div"))) - (dom-set-attr _el-d1 "id" "d1") - (dom-set-attr _el-d1 "_" "on click call sprayInto(me)") - (dom-append (dom-body) _el-d1) - (hs-activate! _el-d1) - (dom-dispatch _el-d1 "click" nil) - (assert= (dom-inner-html _el-d1) "123246369") - )) - (deftest "basic times loop works" - (hs-cleanup!) - (let ((_el-div (dom-create-element "div"))) - (dom-set-attr _el-div "_" "on click repeat 3 times put \"a\" at end of me end") - (dom-append (dom-body) _el-div) - (hs-activate! _el-div) - (dom-dispatch _el-div "click" nil) - (assert= (dom-inner-html _el-div) "aaa") - )) - (deftest "times loop with expression works" - (hs-cleanup!) - (let ((_el-div (dom-create-element "div"))) - (dom-set-attr _el-div "_" "on click repeat 3 + 3 times put \"a\" at end of me end") - (dom-append (dom-body) _el-div) - (hs-activate! _el-div) - (dom-dispatch _el-div "click" nil) - (assert= (dom-inner-html _el-div) "aaaaaa") - )) - (deftest "loop continue works" - (hs-cleanup!) - (let ((_el-div (dom-create-element "div"))) - (dom-set-attr _el-div "_" "on click repeat 2 times for x in ['A', 'B', 'C', 'D'] if (x != 'D') then put 'success ' + x + '. ' at end of me then continue then put 'FAIL!!. ' at end of me then end put 'expected D. ' at end of me then end end") - (dom-append (dom-body) _el-div) - (hs-activate! _el-div) - (dom-dispatch _el-div "click" nil) - (assert= (dom-inner-html _el-div) "success A. success B. success C. expected D. success A. success B. success C. expected D. ") - )) - (deftest "loop break works" - (hs-cleanup!) - (let ((_el-div (dom-create-element "div"))) - (dom-set-attr _el-div "_" "on click repeat 2 times for x in ['A', 'B', 'C', 'D'] if x is 'C' then break then end put x at end of me then end end") - (dom-append (dom-body) _el-div) - (hs-activate! _el-div) - (dom-dispatch _el-div "click" nil) - (assert= (dom-inner-html _el-div) "ABAB") - )) - (deftest "basic raw for loop with null works" - (hs-cleanup!) - (let ((_el-div (dom-create-element "div"))) - (dom-set-attr _el-div "_" "on click for x in null put x at end of me end") - (dom-append (dom-body) _el-div) - (hs-activate! _el-div) - (dom-dispatch _el-div "click" nil) - (assert= (dom-text-content _el-div) "") - )) - (deftest "basic property for loop works" - (hs-cleanup!) - (let ((_el-div (dom-create-element "div"))) - (dom-set-attr _el-div "_" "on click set x to {foo:1, bar:2, baz:3} then for prop in x put x[prop] at end of me end") - (dom-append (dom-body) _el-div) - (hs-activate! _el-div) - (dom-dispatch _el-div "click" nil) - (assert= (dom-text-content _el-div) "123") - )) - (deftest "bottom-tested repeat until" - (hs-cleanup!) - (let ((_el-div (dom-create-element "div"))) - (dom-set-attr _el-div "_" "on click set x to 0 then repeat then set x to until x is 3 end put x into me") - (dom-append (dom-body) _el-div) - (hs-activate! _el-div) - (dom-dispatch _el-div "click" nil) - (assert= (dom-text-content _el-div) "3") - )) - (deftest "bottom-tested repeat while" - (hs-cleanup!) - (let ((_el-div (dom-create-element "div"))) - (dom-set-attr _el-div "_" "on click set x to 0 then repeat then set x to while x < 3 end put x into me") - (dom-append (dom-body) _el-div) - (hs-activate! _el-div) - (dom-dispatch _el-div "click" nil) - (assert= (dom-text-content _el-div) "3") - )) - (deftest "bottom-tested loop always runs at least once" - (hs-cleanup!) - (let ((_el-div (dom-create-element "div"))) - (dom-set-attr _el-div "_" "on click set x to 0 then repeat then set x to until true end put x into me") - (dom-append (dom-body) _el-div) - (hs-activate! _el-div) - (dom-dispatch _el-div "click" nil) - (assert= (dom-text-content _el-div) "1") - )) - (deftest "break exits a simple repeat loop" - (hs-cleanup!) - (let ((_el-div (dom-create-element "div"))) - (dom-set-attr _el-div "_" "on click set x to 0 then repeat 10 times set x to x + 1 then if x is 3 break end end put x into me then") - (dom-append (dom-body) _el-div) - (hs-activate! _el-div) - (dom-dispatch _el-div "click" nil) - (assert= (dom-text-content _el-div) "3") - )) - (deftest "continue skips rest of iteration in simple repeat loop" - (hs-cleanup!) - (let ((_el-div (dom-create-element "div"))) - (dom-set-attr _el-div "_" "on click repeat for x in [1, 2, 3, 4, 5] if x is 3 continue end put x at end of me then end") - (dom-append (dom-body) _el-div) - (hs-activate! _el-div) - (dom-dispatch _el-div "click" nil) - (assert= (dom-text-content _el-div) "1245") - )) - (deftest "break exits a for-in loop" - (hs-cleanup!) - (let ((_el-div (dom-create-element "div"))) - (dom-set-attr _el-div "_" "on click repeat for x in [1, 2, 3, 4, 5] if x is 4 break end put x at end of me then end") - (dom-append (dom-body) _el-div) - (hs-activate! _el-div) - (dom-dispatch _el-div "click" nil) - (assert= (dom-text-content _el-div) "123") - )) - (deftest "break exits a while loop" - (hs-cleanup!) - (let ((_el-div (dom-create-element "div"))) - (dom-set-attr _el-div "_" "on click set x to 0 then repeat while x < 100 then set x to x + 1 then if x is 5 break end end put x into me then") - (dom-append (dom-body) _el-div) - (hs-activate! _el-div) - (dom-dispatch _el-div "click" nil) - (assert= (dom-text-content _el-div) "5") - )) - (deftest "for loop over undefined skips without error" - (hs-cleanup!) - (let ((_el-div (dom-create-element "div"))) - (dom-set-attr _el-div "_" "on click repeat for x in doesNotExist put x at end of me end put \"done\" into me") - (dom-append (dom-body) _el-div) - (hs-activate! _el-div) - (dom-dispatch _el-div "click" nil) - (assert= (dom-text-content _el-div) "done") - )) -) - -;; ── wait (7 tests) ── -(defsuite "hs-upstream-wait" - (deftest "can wait on time" - (hs-cleanup!) - (let ((_el-div (dom-create-element "div"))) - (dom-set-attr _el-div "_" "on click add .foo then wait 20ms then add .bar") - (dom-append (dom-body) _el-div) - (hs-activate! _el-div) - (dom-dispatch _el-div "click" nil) - (assert (dom-has-class? _el-div "foo")) - (assert (dom-has-class? _el-div "bar")) - )) - (deftest "can wait on event" - (hs-cleanup!) - (let ((_el-div (dom-create-element "div"))) - (dom-set-attr _el-div "_" "on click add .foo then wait for foo then add .bar") - (dom-append (dom-body) _el-div) - (hs-activate! _el-div) - (dom-dispatch _el-div "click" nil) - (dom-dispatch _el-div "foo" nil) - (assert (dom-has-class? _el-div "foo")) - (assert (dom-has-class? _el-div "bar")) - )) - (deftest "waiting on an event sets 'it' to the event" - (hs-cleanup!) - (let ((_el-div (dom-create-element "div"))) - (dom-set-attr _el-div "_" "on click wait for foo then put its.detail into me") - (dom-append (dom-body) _el-div) - (hs-activate! _el-div) - (dom-dispatch _el-div "click" nil) - (dom-dispatch _el-div "foo" nil) - (assert= (dom-inner-html _el-div) "hyperscript is hyper cool") - )) - (deftest "can destructure properties in a wait" - (hs-cleanup!) - (let ((_el-div (dom-create-element "div"))) - (dom-set-attr _el-div "_" "on click wait for foo(bar) then put bar into me") - (dom-append (dom-body) _el-div) - (hs-activate! _el-div) - (dom-dispatch _el-div "click" nil) - (dom-dispatch _el-div "foo" nil) - (assert= (dom-inner-html _el-div) "bar") - )) - (deftest "can wait on event on another element" - (hs-cleanup!) - (let ((_el-d2 (dom-create-element "div")) (_el-div (dom-create-element "div"))) - (dom-set-attr _el-d2 "id" "d2") - (dom-set-attr _el-div "_" "on click add .foo then wait for foo from #d2 then add .bar") - (dom-append (dom-body) _el-d2) - (dom-append (dom-body) _el-div) - (hs-activate! _el-div) - (dom-dispatch _el-div "click" nil) - (dom-dispatch _el-div "foo" nil) - (assert (dom-has-class? _el-div "foo")) - (assert (dom-has-class? _el-div "bar")) - )) - (deftest "can wait on event or timeout 1" - (hs-cleanup!) - (let ((_el-d2 (dom-create-element "div")) (_el-div (dom-create-element "div"))) - (dom-set-attr _el-d2 "id" "d2") - (dom-set-attr _el-div "_" "on click add .foo then wait for foo or 0ms then add .bar") - (dom-append (dom-body) _el-d2) - (dom-append (dom-body) _el-div) - (hs-activate! _el-div) - (dom-dispatch _el-div "click" nil) - (dom-dispatch _el-div "foo" nil) - (assert (dom-has-class? _el-div "foo")) - (assert (dom-has-class? _el-div "bar")) - )) - (deftest "can wait on event or timeout 2" - (hs-cleanup!) - (let ((_el-d2 (dom-create-element "div")) (_el-div (dom-create-element "div"))) - (dom-set-attr _el-d2 "id" "d2") - (dom-set-attr _el-div "_" "on click add .foo then wait for foo or 0ms then add .bar") - (dom-append (dom-body) _el-d2) - (dom-append (dom-body) _el-div) - (hs-activate! _el-div) - (dom-dispatch _el-div "click" nil) - (dom-dispatch _el-div "foo" nil) - (assert (dom-has-class? _el-div "foo")) - (assert (dom-has-class? _el-div "bar")) - )) -) - -;; ── send (8 tests) ── -(defsuite "hs-upstream-send" - (deftest "can send events" - (hs-cleanup!) - (let ((_el-div (dom-create-element "div")) (_el-bar (dom-create-element "div"))) - (dom-set-attr _el-div "_" "on click send foo to #bar") - (dom-set-attr _el-bar "id" "bar") - (dom-set-attr _el-bar "_" "on foo add .foo-sent") - (dom-append (dom-body) _el-div) - (dom-append (dom-body) _el-bar) - (hs-activate! _el-div) - (hs-activate! _el-bar) - (dom-dispatch _el-div "click" nil) - (assert (dom-has-class? _el-bar "foo-sent")) - )) - (deftest "can reference sender in events" - (hs-cleanup!) - (let ((_el-div (dom-create-element "div")) (_el-bar (dom-create-element "div"))) - (dom-set-attr _el-div "_" "on click log 0 send foo to #bar log 3") - (dom-set-attr _el-bar "id" "bar") - (dom-set-attr _el-bar "_" "on foo add .foo-sent to sender log 1, me, sender") - (dom-append (dom-body) _el-div) - (dom-append (dom-body) _el-bar) - (hs-activate! _el-div) - (hs-activate! _el-bar) - (dom-dispatch _el-div "click" nil) - (assert (dom-has-class? _el-bar "foo-sent")) - )) - (deftest "can send events with args" - (hs-cleanup!) - (let ((_el-div (dom-create-element "div")) (_el-bar (dom-create-element "div"))) - (dom-set-attr _el-div "_" "on click send foo(x:42) to #bar") - (dom-set-attr _el-bar "id" "bar") - (dom-set-attr _el-bar "_" "on foo put event.detail.x into my.innerHTML") - (dom-append (dom-body) _el-div) - (dom-append (dom-body) _el-bar) - (hs-activate! _el-div) - (hs-activate! _el-bar) - (dom-dispatch _el-div "click" nil) - (assert (not (dom-has-class? _el-bar "foo-sent"))) - (assert= (dom-inner-html _el-bar) "42") - )) - (deftest "can send events with dots" - (hs-cleanup!) - (let ((_el-div (dom-create-element "div")) (_el-bar (dom-create-element "div"))) - (dom-set-attr _el-div "_" "on click send foo.bar to #bar") - (dom-set-attr _el-bar "id" "bar") - (dom-set-attr _el-bar "_" "on foo.bar add .foo-sent") - (dom-append (dom-body) _el-div) - (dom-append (dom-body) _el-bar) - (hs-activate! _el-div) - (hs-activate! _el-bar) - (dom-dispatch _el-div "click" nil) - (assert (dom-has-class? _el-bar "foo-sent")) - )) - (deftest "can send events with dots with args" - (hs-cleanup!) - (let ((_el-div (dom-create-element "div")) (_el-bar (dom-create-element "div"))) - (dom-set-attr _el-div "_" "on click send foo.bar(x:42) to #bar") - (dom-set-attr _el-bar "id" "bar") - (dom-set-attr _el-bar "_" "on foo.bar put event.detail.x into my.innerHTML") - (dom-append (dom-body) _el-div) - (dom-append (dom-body) _el-bar) - (hs-activate! _el-div) - (hs-activate! _el-bar) - (dom-dispatch _el-div "click" nil) - (assert (not (dom-has-class? _el-bar "foo-sent"))) - (assert= (dom-inner-html _el-bar) "42") - )) - (deftest "can send events with colons" - (hs-cleanup!) - (let ((_el-div (dom-create-element "div")) (_el-bar (dom-create-element "div"))) - (dom-set-attr _el-div "_" "on click send foo:bar to #bar") - (dom-set-attr _el-bar "id" "bar") - (dom-set-attr _el-bar "_" "on foo:bar add .foo-sent") - (dom-append (dom-body) _el-div) - (dom-append (dom-body) _el-bar) - (hs-activate! _el-div) - (hs-activate! _el-bar) - (dom-dispatch _el-div "click" nil) - (assert (dom-has-class? _el-bar "foo-sent")) - )) - (deftest "can send events with colons with args" - (hs-cleanup!) - (let ((_el-div (dom-create-element "div")) (_el-bar (dom-create-element "div"))) - (dom-set-attr _el-div "_" "on click send foo:bar(x:42) to #bar") - (dom-set-attr _el-bar "id" "bar") - (dom-set-attr _el-bar "_" "on foo:bar put event.detail.x into my.innerHTML") - (dom-append (dom-body) _el-div) - (dom-append (dom-body) _el-bar) - (hs-activate! _el-div) - (hs-activate! _el-bar) - (dom-dispatch _el-div "click" nil) - (assert (not (dom-has-class? _el-bar "foo-sent"))) - (assert= (dom-inner-html _el-bar) "42") - )) - (deftest "can send events to any expression" - (hs-cleanup!) - (let ((_el-div (dom-create-element "div")) (_el-bar (dom-create-element "div"))) - (dom-set-attr _el-div "_" "def bar return #bar on click send foo to bar()") - (dom-set-attr _el-bar "id" "bar") - (dom-set-attr _el-bar "_" "on foo add .foo-sent") - (dom-append (dom-body) _el-div) - (dom-append (dom-body) _el-bar) - (hs-activate! _el-div) - (hs-activate! _el-bar) - (dom-dispatch _el-div "click" nil) - (assert (dom-has-class? _el-bar "foo-sent")) - )) -) - -;; ── take (12 tests) ── -(defsuite "hs-upstream-take" - (deftest "can take a class from other elements" - (hs-cleanup!) - (let ((_el-div (dom-create-element "div")) (_el-div1 (dom-create-element "div")) (_el-div2 (dom-create-element "div"))) - (dom-add-class _el-div "div") - (dom-add-class _el-div "foo") - (dom-add-class _el-div1 "div") - (dom-set-attr _el-div1 "_" "on click take .foo from .div") - (dom-add-class _el-div2 "div") - (dom-append (dom-body) _el-div) - (dom-append (dom-body) _el-div1) - (dom-append (dom-body) _el-div2) - (hs-activate! _el-div1) - (dom-dispatch _el-div1 "click" nil) - (assert (not (dom-has-class? _el-div "foo"))) - (assert (dom-has-class? _el-div1 "foo")) - (assert (not (dom-has-class? _el-div2 "foo"))) - )) - (deftest "can take a class from other forms" - (hs-cleanup!) - (let ((_el-form (dom-create-element "form")) (_el-form1 (dom-create-element "form")) (_el-form2 (dom-create-element "form"))) - (dom-add-class _el-form "div") - (dom-add-class _el-form "foo") - (dom-add-class _el-form1 "div") - (dom-set-attr _el-form1 "_" "on click take .foo from .div") - (dom-add-class _el-form2 "div") - (dom-append (dom-body) _el-form) - (dom-append (dom-body) _el-form1) - (dom-append (dom-body) _el-form2) - (hs-activate! _el-form1) - (dom-dispatch (dom-query-by-id "f2") "click" nil) - (assert (not (dom-has-class? (dom-query-by-id "f1") "foo"))) - (assert (dom-has-class? (dom-query-by-id "f2") "foo")) - (assert (not (dom-has-class? (dom-query-by-id "f3") "foo"))) - )) - (deftest "can take a class for other elements" - (hs-cleanup!) - (let ((_el-div (dom-create-element "div")) (_el-div1 (dom-create-element "div")) (_el-d3 (dom-create-element "div"))) - (dom-add-class _el-div "div") - (dom-add-class _el-div "foo") - (dom-add-class _el-div1 "div") - (dom-set-attr _el-div1 "_" "on click take .foo from .div for #d3") - (dom-set-attr _el-d3 "id" "d3") - (dom-add-class _el-d3 "div") - (dom-append (dom-body) _el-div) - (dom-append (dom-body) _el-div1) - (dom-append (dom-body) _el-d3) - (hs-activate! _el-div1) - (dom-dispatch _el-div1 "click" nil) - (assert (not (dom-has-class? _el-div "foo"))) - (assert (not (dom-has-class? _el-div1 "foo"))) - (assert (dom-has-class? _el-d3 "foo")) - )) - (deftest "a parent can take a class for other elements" - (hs-cleanup!) - (let ((_el-div (dom-create-element "div")) (_el-d1 (dom-create-element "div")) (_el-d2 (dom-create-element "div")) (_el-d3 (dom-create-element "div"))) - (dom-set-attr _el-div "_" "on click take .foo from .div for event.target") - (dom-set-attr _el-d1 "id" "d1") - (dom-add-class _el-d1 "div") - (dom-add-class _el-d1 "foo") - (dom-set-attr _el-d2 "id" "d2") - (dom-add-class _el-d2 "div") - (dom-set-attr _el-d3 "id" "d3") - (dom-add-class _el-d3 "div") - (dom-append (dom-body) _el-div) - (dom-append _el-div _el-d1) - (dom-append _el-div _el-d2) - (dom-append _el-div _el-d3) - (hs-activate! _el-div) - (dom-dispatch _el-d2 "click" nil) - (assert (not (dom-has-class? _el-div "foo"))) - (assert (dom-has-class? _el-d2 "foo")) - (assert (not (dom-has-class? _el-d3 "foo"))) - )) - (deftest "can take an attribute from other elements" - (hs-cleanup!) - (let ((_el-div (dom-create-element "div")) (_el-div1 (dom-create-element "div")) (_el-div2 (dom-create-element "div"))) - (dom-add-class _el-div "div") - (dom-set-attr _el-div "data-foo" "bar") - (dom-add-class _el-div1 "div") - (dom-set-attr _el-div1 "_" "on click take @data-foo from .div") - (dom-add-class _el-div2 "div") - (dom-append (dom-body) _el-div) - (dom-append (dom-body) _el-div1) - (dom-append (dom-body) _el-div2) - (hs-activate! _el-div1) - (dom-dispatch _el-div1 "click" nil) - (assert= (dom-get-attr _el-div "data-foo") "bar") - (assert= (dom-get-attr _el-div1 "data-foo") "") - ;; SKIP check: skip assert.isNull(d2.getAttribute("data-foo") - ;; SKIP check: skip assert.isNull(d3.getAttribute("data-foo") - ;; SKIP check: skip assert.isNull(d1.getAttribute("data-foo") - )) - (deftest "can take an attribute with specific value from other elements" - (hs-cleanup!) - (let ((_el-div (dom-create-element "div")) (_el-div1 (dom-create-element "div")) (_el-div2 (dom-create-element "div"))) - (dom-add-class _el-div "div") - (dom-set-attr _el-div "data-foo" "bar") - (dom-add-class _el-div1 "div") - (dom-set-attr _el-div1 "_" "on click take @data-foo=baz from .div") - (dom-add-class _el-div2 "div") - (dom-append (dom-body) _el-div) - (dom-append (dom-body) _el-div1) - (dom-append (dom-body) _el-div2) - (hs-activate! _el-div1) - (dom-dispatch _el-div1 "click" nil) - (assert= (dom-get-attr _el-div "data-foo") "bar") - (assert= (dom-get-attr _el-div1 "data-foo") "baz") - ;; SKIP check: skip assert.isNull(d2.getAttribute("data-foo") - ;; SKIP check: skip assert.isNull(d3.getAttribute("data-foo") - ;; SKIP check: skip assert.isNull(d1.getAttribute("data-foo") - )) - (deftest "can take an attribute value from other elements and set specific values instead" - (hs-cleanup!) - (let ((_el-div (dom-create-element "div")) (_el-div1 (dom-create-element "div")) (_el-div2 (dom-create-element "div"))) - (dom-add-class _el-div "div") - (dom-set-attr _el-div "data-foo" "bar") - (dom-add-class _el-div1 "div") - (dom-set-attr _el-div1 "_" "on click take @data-foo=baz with \"qux\" from .div") - (dom-add-class _el-div2 "div") - (dom-append (dom-body) _el-div) - (dom-append (dom-body) _el-div1) - (dom-append (dom-body) _el-div2) - (hs-activate! _el-div1) - (dom-dispatch _el-div1 "click" nil) - (assert= (dom-get-attr _el-div "data-foo") "qux") - (assert= (dom-get-attr _el-div1 "data-foo") "baz") - (assert= (dom-get-attr _el-div2 "data-foo") "qux") - ;; SKIP check: skip assert.isNull(d2.getAttribute("data-foo") - ;; SKIP check: skip assert.isNull(d3.getAttribute("data-foo") - )) - (deftest "can take an attribute value from other elements and set value from an expression instead" - (hs-cleanup!) - (let ((_el-div (dom-create-element "div")) (_el-div1 (dom-create-element "div")) (_el-div2 (dom-create-element "div"))) - (dom-add-class _el-div "div") - (dom-set-attr _el-div "data-foo" "bar") - (dom-add-class _el-div1 "div") - (dom-set-attr _el-div1 "_" "on click take @data-foo=baz with my @data-foo from .div") - (dom-set-attr _el-div1 "data-foo" "qux") - (dom-add-class _el-div2 "div") - (dom-append (dom-body) _el-div) - (dom-append (dom-body) _el-div1) - (dom-append (dom-body) _el-div2) - (hs-activate! _el-div1) - (dom-dispatch _el-div1 "click" nil) - (assert= (dom-get-attr _el-div "data-foo") "qux") - (assert= (dom-get-attr _el-div1 "data-foo") "baz") - (assert= (dom-get-attr _el-div2 "data-foo") "qux") - ;; SKIP check: skip assert.isNull(d3.getAttribute("data-foo") - )) - (deftest "can take an attribute for other elements" - (hs-cleanup!) - (let ((_el-div (dom-create-element "div")) (_el-div1 (dom-create-element "div")) (_el-d3 (dom-create-element "div"))) - (dom-add-class _el-div "div") - (dom-set-attr _el-div "data-foo" "bar") - (dom-add-class _el-div1 "div") - (dom-set-attr _el-div1 "_" "on click take @data-foo from .div for #d3") - (dom-set-attr _el-d3 "id" "d3") - (dom-add-class _el-d3 "div") - (dom-append (dom-body) _el-div) - (dom-append (dom-body) _el-div1) - (dom-append (dom-body) _el-d3) - (hs-activate! _el-div1) - (dom-dispatch _el-div1 "click" nil) - (assert= (dom-get-attr _el-div "data-foo") "bar") - (assert= (dom-get-attr _el-d3 "data-foo") "") - ;; SKIP check: skip assert.isNull(d2.getAttribute("data-foo") - ;; SKIP check: skip assert.isNull(d3.getAttribute("data-foo") - ;; SKIP check: skip assert.isNull(d1.getAttribute("data-foo") - )) - (deftest "a parent can take an attribute for other elements" - (hs-cleanup!) - (let ((_el-div (dom-create-element "div")) (_el-d1 (dom-create-element "div")) (_el-d2 (dom-create-element "div")) (_el-d3 (dom-create-element "div"))) - (dom-set-attr _el-div "_" "on click take @data-foo from .div for event.target") - (dom-set-attr _el-d1 "id" "d1") - (dom-add-class _el-d1 "div") - (dom-set-attr _el-d1 "data-foo" "bar") - (dom-set-attr _el-d2 "id" "d2") - (dom-add-class _el-d2 "div") - (dom-set-attr _el-d3 "id" "d3") - (dom-add-class _el-d3 "div") - (dom-append (dom-body) _el-div) - (dom-append _el-div _el-d1) - (dom-append _el-div _el-d2) - (dom-append _el-div _el-d3) - (hs-activate! _el-div) - (dom-dispatch _el-d2 "click" nil) - (assert= (dom-get-attr _el-div "data-foo") "bar") - (assert= (dom-get-attr _el-d2 "data-foo") "") - ;; SKIP check: skip assert.isNull(d2.getAttribute("data-foo") - ;; SKIP check: skip assert.isNull(d3.getAttribute("data-foo") - ;; SKIP check: skip assert.isNull(d1.getAttribute("data-foo") - )) - (deftest "can take multiple classes from other elements" - (hs-cleanup!) - (let ((_el-div (dom-create-element "div")) (_el-div1 (dom-create-element "div")) (_el-div2 (dom-create-element "div"))) - (dom-add-class _el-div "div") - (dom-add-class _el-div "foo") - (dom-add-class _el-div1 "div") - (dom-set-attr _el-div1 "_" "on click take .foo .bar") - (dom-add-class _el-div2 "div") - (dom-add-class _el-div2 "bar") - (dom-append (dom-body) _el-div) - (dom-append (dom-body) _el-div1) - (dom-append (dom-body) _el-div2) - (hs-activate! _el-div1) - (dom-dispatch _el-div1 "click" nil) - (assert (not (dom-has-class? _el-div "foo"))) - (assert (dom-has-class? _el-div1 "foo")) - (assert (not (dom-has-class? _el-div2 "foo"))) - (assert (not (dom-has-class? _el-div "bar"))) - (assert (dom-has-class? _el-div1 "bar")) - (assert (not (dom-has-class? _el-div2 "bar"))) - )) - (deftest "can take multiple classes from specific element" - (hs-cleanup!) - (let ((_el-div (dom-create-element "div")) (_el-div1 (dom-create-element "div")) (_el-div2 (dom-create-element "div"))) - (dom-add-class _el-div "div1") - (dom-add-class _el-div "foo") - (dom-add-class _el-div "bar") - (dom-add-class _el-div1 "div") - (dom-set-attr _el-div1 "_" "on click take .foo .bar from .div1") - (dom-add-class _el-div2 "div") - (dom-add-class _el-div2 "bar") - (dom-append (dom-body) _el-div) - (dom-append (dom-body) _el-div1) - (dom-append (dom-body) _el-div2) - (hs-activate! _el-div1) - (dom-dispatch _el-div1 "click" nil) - (assert (not (dom-has-class? _el-div "foo"))) - (assert (dom-has-class? _el-div1 "foo")) - (assert (not (dom-has-class? _el-div2 "foo"))) - (assert (not (dom-has-class? _el-div "bar"))) - (assert (dom-has-class? _el-div1 "bar")) - (assert (dom-has-class? _el-div2 "bar")) - )) -) - -;; ── transition (23 tests) ── -(defsuite "hs-upstream-transition" - (deftest "can transition a single property on current element" - (hs-cleanup!) - (let ((_el-div (dom-create-element "div"))) - (dom-set-attr _el-div "_" "on click transition width from 0px to 100px") - (dom-append (dom-body) _el-div) - (hs-activate! _el-div) - (dom-dispatch _el-div "click" nil) - (assert= (dom-get-style _el-div "width") "100px") - )) - (deftest "can transition with parameterized values" - (hs-cleanup!) - (let ((_el-div (dom-create-element "div"))) - (dom-set-attr _el-div "_" "on click set startWidth to 0 then set endWidth to 100 transition width from (startWidth)px to (endWidth)px") - (dom-append (dom-body) _el-div) - (hs-activate! _el-div) - (dom-dispatch _el-div "click" nil) - (assert= (dom-get-style _el-div "width") "100px") - )) - (deftest "can transition a single property on form" - (hs-cleanup!) - (let ((_el-form (dom-create-element "form"))) - (dom-set-attr _el-form "_" "on click transition width from 0px to 100px") - (dom-append (dom-body) _el-form) - (hs-activate! _el-form) - (dom-dispatch _el-form "click" nil) - (assert= (dom-get-style _el-form "width") "100px") - )) - (deftest "can transition a single property on current element with the my prefix" - (hs-cleanup!) - (let ((_el-div (dom-create-element "div"))) - (dom-set-attr _el-div "_" "on click transition my width from 0px to 100px") - (dom-append (dom-body) _el-div) - (hs-activate! _el-div) - (dom-dispatch _el-div "click" nil) - (assert= (dom-get-style _el-div "width") "100px") - )) - (deftest "can transition two properties on current element" - (hs-cleanup!) - (let ((_el-div (dom-create-element "div"))) - (dom-set-attr _el-div "_" "on click transition width from 0px to 100px height from 0px to 100px") - (dom-append (dom-body) _el-div) - (hs-activate! _el-div) - (dom-dispatch _el-div "click" nil) - (assert= (dom-get-style _el-div "width") "100px") - (assert= (dom-get-style _el-div "height") "100px") - )) - (deftest "can transition on another element" - (hs-cleanup!) - (let ((_el-div (dom-create-element "div")) (_el-foo (dom-create-element "div"))) - (dom-set-attr _el-div "_" "on click transition element #foo width from 0px to 100px") - (dom-set-attr _el-foo "id" "foo") - (dom-append (dom-body) _el-div) - (dom-append (dom-body) _el-foo) - (hs-activate! _el-div) - (dom-dispatch _el-div "click" nil) - (assert= (dom-get-style _el-foo "width") "100px") - )) - (deftest "can transition on another element no element prefix" - (hs-cleanup!) - (let ((_el-div (dom-create-element "div")) (_el-foo (dom-create-element "div"))) - (dom-set-attr _el-div "_" "on click transition #foo width from 0px to 100px") - (dom-set-attr _el-foo "id" "foo") - (dom-append (dom-body) _el-div) - (dom-append (dom-body) _el-foo) - (hs-activate! _el-div) - (dom-dispatch _el-div "click" nil) - (assert= (dom-get-style _el-foo "width") "100px") - )) - (deftest "can transition on another element no element prefix + possessive" - (hs-cleanup!) - (let ((_el-div (dom-create-element "div")) (_el-foo (dom-create-element "div"))) - (dom-set-attr _el-div "_" "on click transition #foo's width from 0px to 100px") - (dom-set-attr _el-foo "id" "foo") - (dom-append (dom-body) _el-div) - (dom-append (dom-body) _el-foo) - (hs-activate! _el-div) - (dom-dispatch _el-div "click" nil) - (assert= (dom-get-style _el-foo "width") "100px") - )) - (deftest "can transition on another element no element prefix with it" - (hs-cleanup!) - (let ((_el-div (dom-create-element "div")) (_el-foo (dom-create-element "div"))) - (dom-set-attr _el-div "_" "on click get #foo then transition its width from 0px to 100px") - (dom-set-attr _el-foo "id" "foo") - (dom-append (dom-body) _el-div) - (dom-append (dom-body) _el-foo) - (hs-activate! _el-div) - (dom-dispatch _el-div "click" nil) - (assert= (dom-get-style _el-foo "width") "100px") - )) - (deftest "can transition with a custom transition time" - (hs-cleanup!) - (let ((_el-div (dom-create-element "div")) (_el-foo (dom-create-element "div"))) - (dom-set-attr _el-div "_" "on click transition element #foo width from 0px to 100px using \"width 2s ease-in\"") - (dom-set-attr _el-foo "id" "foo") - (dom-append (dom-body) _el-div) - (dom-append (dom-body) _el-foo) - (hs-activate! _el-div) - (dom-dispatch _el-div "click" nil) - (assert= (dom-get-style _el-foo "width") "100px") - )) - (deftest "can transition with a custom transition time via the over syntax" - (hs-cleanup!) - (let ((_el-div (dom-create-element "div")) (_el-foo (dom-create-element "div"))) - (dom-set-attr _el-div "_" "on click transition element #foo width from 0px to 100px over 2s") - (dom-set-attr _el-foo "id" "foo") - (dom-append (dom-body) _el-div) - (dom-append (dom-body) _el-foo) - (hs-activate! _el-div) - (dom-dispatch _el-div "click" nil) - (assert= (dom-get-style _el-foo "width") "100px") - )) - (deftest "can transition a single property on current element using style ref" - (hs-cleanup!) - (let ((_el-div (dom-create-element "div"))) - (dom-set-attr _el-div "_" "on click transition *width from 0px to 100px") - (dom-append (dom-body) _el-div) - (hs-activate! _el-div) - (dom-dispatch _el-div "click" nil) - (assert= (dom-get-style _el-div "width") "100px") - )) - (deftest "can transition a single property on form using style ref" - (hs-cleanup!) - (let ((_el-form (dom-create-element "form"))) - (dom-set-attr _el-form "_" "on click transition *width from 0px to 100px") - (dom-append (dom-body) _el-form) - (hs-activate! _el-form) - (dom-dispatch _el-form "click" nil) - (assert= (dom-get-style _el-form "width") "100px") - )) - (deftest "can transition a single property on current element with the my prefix using style ref" - (hs-cleanup!) - (let ((_el-div (dom-create-element "div"))) - (dom-set-attr _el-div "_" "on click transition my *width from 0px to 100px") - (dom-append (dom-body) _el-div) - (hs-activate! _el-div) - (dom-dispatch _el-div "click" nil) - (assert= (dom-get-style _el-div "width") "100px") - )) - (deftest "can use initial to transition to original value" - (hs-cleanup!) - (let ((_el-div (dom-create-element "div"))) - (dom-set-attr _el-div "_" "on click 1 transition my *width to 100px on click 2 transition my *width to initial") - (dom-set-attr _el-div "style" "width: 10px") - (dom-append (dom-body) _el-div) - (hs-activate! _el-div) - (dom-dispatch _el-div "click" nil) - (dom-dispatch _el-div "click" nil) - (assert= (dom-get-style _el-div "width") "10px") - )) - (deftest "can transition on another element with of syntax" - (hs-cleanup!) - (let ((_el-div (dom-create-element "div")) (_el-foo (dom-create-element "div"))) - (dom-set-attr _el-div "_" "on click transition *width of #foo from 0px to 100px") - (dom-set-attr _el-foo "id" "foo") - (dom-append (dom-body) _el-div) - (dom-append (dom-body) _el-foo) - (hs-activate! _el-div) - (dom-dispatch _el-div "click" nil) - (assert= (dom-get-style (dom-query-by-id "foo") "width") "100px") - )) - (deftest "can transition on another element with possessive" - (hs-cleanup!) - (let ((_el-div (dom-create-element "div")) (_el-foo (dom-create-element "div"))) - (dom-set-attr _el-div "_" "on click transition #foo's *width from 0px to 100px") - (dom-set-attr _el-foo "id" "foo") - (dom-append (dom-body) _el-div) - (dom-append (dom-body) _el-foo) - (hs-activate! _el-div) - (dom-dispatch _el-div "click" nil) - (assert= (dom-get-style (dom-query-by-id "foo") "width") "100px") - )) - (deftest "can transition on another element with it" - (hs-cleanup!) - (let ((_el-div (dom-create-element "div")) (_el-foo (dom-create-element "div"))) - (dom-set-attr _el-div "_" "on click get #foo then transition its *width from 0px to 100px") - (dom-set-attr _el-foo "id" "foo") - (dom-append (dom-body) _el-div) - (dom-append (dom-body) _el-foo) - (hs-activate! _el-div) - (dom-dispatch _el-div "click" nil) - (assert= (dom-get-style (dom-query-by-id "foo") "width") "100px") - )) - (deftest "can transition with a custom transition string" - (hs-cleanup!) - (let ((_el-div (dom-create-element "div")) (_el-foo (dom-create-element "div"))) - (dom-set-attr _el-div "_" "on click transition #foo's *width from 0px to 100px using \"width 2s ease-in\"") - (dom-set-attr _el-foo "id" "foo") - (dom-append (dom-body) _el-div) - (dom-append (dom-body) _el-foo) - (hs-activate! _el-div) - (dom-dispatch _el-div "click" nil) - (assert= (dom-get-style (dom-query-by-id "foo") "width") "100px") - )) - (deftest "can transition a single property on form using style ref" - (hs-cleanup!) - (let ((_el-form (dom-create-element "form"))) - (dom-set-attr _el-form "_" "on click transition *width from 0px to 100px") - (dom-append (dom-body) _el-form) - (hs-activate! _el-form) - )) - (deftest "can transition a single property on current element with the my prefix using style ref" - (hs-cleanup!) - (let ((_el-div (dom-create-element "div"))) - (dom-set-attr _el-div "_" "on click transition my *width from 0px to 100px") - (dom-append (dom-body) _el-div) - (hs-activate! _el-div) - )) - (deftest "can transition on query ref with possessive" - (hs-cleanup!)) - (deftest "can transition on query ref with of syntax" - (hs-cleanup!) - (let ((_el-div (dom-create-element "div")) (_el-span (dom-create-element "span"))) - (dom-set-attr _el-div "_" "on click transition *width of the next from 0px to 100px") - (dom-append (dom-body) _el-div) - (dom-append (dom-body) _el-span) - (hs-activate! _el-div) - (dom-dispatch _el-div "click" nil) - (assert= (dom-get-style _el-span "width") "100px") - )) -) - -;; ── log (4 tests) ── -(defsuite "hs-upstream-log" - (deftest "can log single item" - (hs-cleanup!) - (let ((_el-div (dom-create-element "div"))) - (dom-set-attr _el-div "_" "on click log me") - (dom-append (dom-body) _el-div) - (hs-activate! _el-div) - (dom-dispatch _el-div "click" nil) - )) - (deftest "can log multiple items" - (hs-cleanup!) - (let ((_el-div (dom-create-element "div"))) - (dom-set-attr _el-div "_" "on click log me, my") - (dom-append (dom-body) _el-div) - (hs-activate! _el-div) - (dom-dispatch _el-div "click" nil) - )) - (deftest "can log multiple items with debug" - (hs-cleanup!) - (let ((_el-div (dom-create-element "div"))) - (dom-set-attr _el-div "_" "on click log me, my with console.debug") - (dom-append (dom-body) _el-div) - (hs-activate! _el-div) - (dom-dispatch _el-div "click" nil) - )) - (deftest "can log multiple items with error" - (hs-cleanup!) - (let ((_el-div (dom-create-element "div"))) - (dom-set-attr _el-div "_" "on click log me, my with console.error") - (dom-append (dom-body) _el-div) - (hs-activate! _el-div) - (dom-dispatch _el-div "click" nil) - )) -) - -;; ── call (6 tests) ── -(defsuite "hs-upstream-call" - (deftest "can call javascript instance functions" - (hs-cleanup!) - (let ((_el-d1 (dom-create-element "div"))) - (dom-set-attr _el-d1 "id" "d1") - (dom-set-attr _el-d1 "_" "on click call document.getElementById(\"d1\") then put it into window.results") - (dom-append (dom-body) _el-d1) - (hs-activate! _el-d1) - (dom-dispatch _el-d1 "click" nil) - ;; SKIP check: skip value.should.equal(d1) - )) - (deftest "can call global javascript functions" - (hs-cleanup!) - (let ((_el-div (dom-create-element "div"))) - (dom-set-attr _el-div "_" "on click call globalFunction(\"foo\")") - (dom-append (dom-body) _el-div) - (hs-activate! _el-div) - (dom-dispatch _el-div "click" nil) - ;; SKIP check: skip "foo".should.equal(calledWith) - )) - (deftest "can call no argument functions" - (hs-cleanup!) - (let ((_el-div (dom-create-element "div"))) - (dom-set-attr _el-div "_" "on click call globalFunction()") - (dom-append (dom-body) _el-div) - (hs-activate! _el-div) - (dom-dispatch _el-div "click" nil) - ;; SKIP check: skip called.should.equal(true) - )) - (deftest "can call functions w/ underscores" - (hs-cleanup!) - (let ((_el-div (dom-create-element "div"))) - (dom-set-attr _el-div "_" "on click call global_function()") - (dom-append (dom-body) _el-div) - (hs-activate! _el-div) - (dom-dispatch _el-div "click" nil) - ;; SKIP check: skip called.should.equal(true) - )) - (deftest "can call functions w/ dollar signs" - (hs-cleanup!) - (let ((_el-div (dom-create-element "div"))) - (dom-set-attr _el-div "_" "on click call $()") - (dom-append (dom-body) _el-div) - (hs-activate! _el-div) - (dom-dispatch _el-div "click" nil) - ;; SKIP check: skip called.should.equal(true) - )) - (deftest "call functions that return promises are waited on" - (hs-cleanup!) - (let ((_el-div (dom-create-element "div"))) - (dom-set-attr _el-div "_" "on click call promiseAnInt() then put it into my.innerHTML") - (dom-append (dom-body) _el-div) - (hs-activate! _el-div) - (dom-dispatch _el-div "click" nil) - ;; SKIP check: skip div.innerText.should.equal("") - ;; SKIP check: skip div.innerText.should.equal("42") - )) -) - -;; ── fetch (23 tests) ── -(defsuite "hs-upstream-fetch" - (deftest "can do a simple fetch" - (hs-cleanup!) - (let ((_el-div (dom-create-element "div"))) - (dom-set-attr _el-div "_" "on click fetch \"/test\" then put it into my.innerHTML") - (dom-append (dom-body) _el-div) - (hs-activate! _el-div) - (dom-dispatch _el-div "click" nil) - (assert= (dom-inner-html _el-div) "yay") - )) - (deftest "can do a simple fetch w/ a naked URL" - (hs-cleanup!) - (let ((_el-div (dom-create-element "div"))) - (dom-set-attr _el-div "_" "on click fetch /test then put it into my.innerHTML") - (dom-append (dom-body) _el-div) - (hs-activate! _el-div) - (dom-dispatch _el-div "click" nil) - (assert= (dom-inner-html _el-div) "yay") - )) - (deftest "can do a simple fetch w/ html" - (hs-cleanup!)) - (deftest "can do a simple fetch w/ json" - (hs-cleanup!) - (let ((_el-div (dom-create-element "div"))) - (dom-set-attr _el-div "_" "on click fetch /test as json then get result as JSON then put it into my.innerHTML") - (dom-append (dom-body) _el-div) - (hs-activate! _el-div) - (dom-dispatch _el-div "click" nil) - (assert= (dom-inner-html _el-div) "{\"foo\":1}") - )) - (deftest "can do a simple fetch w/ json using Object syntax" - (hs-cleanup!) - (let ((_el-div (dom-create-element "div"))) - (dom-set-attr _el-div "_" "on click fetch /test as Object then get result as JSON then put it into my.innerHTML") - (dom-append (dom-body) _el-div) - (hs-activate! _el-div) - (dom-dispatch _el-div "click" nil) - (assert= (dom-inner-html _el-div) "{\"foo\":1}") - )) - (deftest "can do a simple fetch w/ json using Object syntax and an 'an' prefix" - (hs-cleanup!) - (let ((_el-div (dom-create-element "div"))) - (dom-set-attr _el-div "_" "on click fetch /test as an Object then get result as JSON then put it into my.innerHTML") - (dom-append (dom-body) _el-div) - (hs-activate! _el-div) - (dom-dispatch _el-div "click" nil) - (assert= (dom-inner-html _el-div) "{\"foo\":1}") - )) - (deftest "can do a simple fetch with a response object" - (hs-cleanup!) - (let ((_el-div (dom-create-element "div"))) - (dom-set-attr _el-div "_" "on click fetch /test as response then if its.ok put \"yep\" into my.innerHTML") - (dom-append (dom-body) _el-div) - (hs-activate! _el-div) - (dom-dispatch _el-div "click" nil) - (assert= (dom-inner-html _el-div) "yep") - )) - (deftest "can do a simple fetch w/ a custom conversion" - (hs-cleanup!) - (let ((_el-div (dom-create-element "div"))) - (dom-set-attr _el-div "_" "on click fetch /test as Number then put it into my.innerHTML") - (dom-append (dom-body) _el-div) - (hs-activate! _el-div) - (dom-dispatch _el-div "click" nil) - (assert= (dom-inner-html _el-div) "1.2") - )) - (deftest "can do a simple post" - (hs-cleanup!) - (let ((_el-div (dom-create-element "div"))) - (dom-set-attr _el-div "_" "on click fetch /test {method:\"POST\"} then put it into my.innerHTML") - (dom-append (dom-body) _el-div) - (hs-activate! _el-div) - (dom-dispatch _el-div "click" nil) - (assert= (dom-inner-html _el-div) "yay") - )) - (deftest "can do a simple post alt syntax without curlies" - (hs-cleanup!) - (let ((_el-div (dom-create-element "div"))) - (dom-set-attr _el-div "_" "on click fetch /test with method:\"POST\" then put it into my.innerHTML") - (dom-append (dom-body) _el-div) - (hs-activate! _el-div) - (dom-dispatch _el-div "click" nil) - (assert= (dom-inner-html _el-div) "yay") - )) - (deftest "can do a simple post alt syntax w/ curlies" - (hs-cleanup!) - (let ((_el-div (dom-create-element "div"))) - (dom-set-attr _el-div "_" "on click fetch /test with {method:\"POST\"} then put it into my.innerHTML") - (dom-append (dom-body) _el-div) - (hs-activate! _el-div) - (dom-dispatch _el-div "click" nil) - (assert= (dom-inner-html _el-div) "yay") - )) - (deftest "can put response conversion after with" - (hs-cleanup!) - (let ((_el-div (dom-create-element "div"))) - (dom-set-attr _el-div "_" "on click fetch /test with {method:\"POST\"} as text then put it into my.innerHTML") - (dom-append (dom-body) _el-div) - (hs-activate! _el-div) - (dom-dispatch _el-div "click" nil) - (assert= (dom-inner-html _el-div) "yay") - )) - (deftest "can put response conversion before with" - (hs-cleanup!) - (let ((_el-div (dom-create-element "div"))) - (dom-set-attr _el-div "_" "on click fetch /test as text with {method:\"POST\"} then put it into my.innerHTML") - (dom-append (dom-body) _el-div) - (hs-activate! _el-div) - (dom-dispatch _el-div "click" nil) - (assert= (dom-inner-html _el-div) "yay") - )) - (deftest "triggers an event just before fetching" - (hs-cleanup!)) - (deftest "submits the fetch parameters to the event handler" - (hs-cleanup!) - (let ((_el-div (dom-create-element "div"))) - (dom-set-attr _el-div "_" "on click fetch \"/test\" {headers: {\"X-CustomHeader\": \"foo\"}} then put it into my.innerHTML end") - (dom-append (dom-body) _el-div) - (hs-activate! _el-div) - (dom-dispatch _el-div "click" nil) - ;; SKIP check: skip event.detail.headers.should.have.property('X-CustomHeader', - (assert= (dom-inner-html _el-div) "yay") - )) - (deftest "allows the event handler to change the fetch parameters" - (hs-cleanup!) - (let ((_el-div (dom-create-element "div"))) - (dom-set-attr _el-div "_" "on click fetch \"/test\" then put it into my.innerHTML end") - (dom-append (dom-body) _el-div) - (hs-activate! _el-div) - (dom-dispatch _el-div "click" nil) - ;; SKIP check: skip arguments[1].should.have.property('headers') - ;; SKIP check: skip arguments[1].headers.should.have.property('X-CustomHeader', - (assert= (dom-inner-html _el-div) "yay") - )) - (deftest "can catch an error that occurs when using fetch" - (hs-cleanup!)) - (deftest "can do a simple fetch w/ json using JSON syntax" - (hs-cleanup!) - (let ((_el-div (dom-create-element "div"))) - (dom-set-attr _el-div "_" "on click fetch /test as JSON then get result as JSONString then put it into my.innerHTML") - (dom-append (dom-body) _el-div) - (hs-activate! _el-div) - (dom-dispatch _el-div "click" nil) - (assert= (dom-text-content _el-div) "{\"foo\":1}") - )) - (deftest "throws on non-2xx response by default" - (hs-cleanup!)) - (deftest "do not throw passes through 404 response" - (hs-cleanup!)) - (deftest "don't throw passes through 404 response" - (hs-cleanup!)) - (deftest "as response does not throw on 404" - (hs-cleanup!)) - (deftest "Response can be converted to JSON via as JSON" - (hs-cleanup!)) -) - -;; ── increment (20 tests) ── -(defsuite "hs-upstream-increment" - (deftest "can increment an empty variable" - (hs-cleanup!) - (let ((_el-div (dom-create-element "div"))) - (dom-set-attr _el-div "_" "on click increment value then put value into me") - (dom-append (dom-body) _el-div) - (hs-activate! _el-div) - (dom-dispatch _el-div "click" nil) - (assert= (dom-inner-html _el-div) "1") - )) - (deftest "can increment a variable" - (hs-cleanup!) - (let ((_el-div (dom-create-element "div"))) - (dom-set-attr _el-div "_" "on click set value to 20 then increment value by 2 then put value into me") - (dom-append (dom-body) _el-div) - (hs-activate! _el-div) - (dom-dispatch _el-div "click" nil) - (assert= (dom-inner-html _el-div) "22") - )) - (deftest "can increment refer to result" - (hs-cleanup!) - (let ((_el-div (dom-create-element "div"))) - (dom-set-attr _el-div "_" "on click increment value by 2 then put it into me") - (dom-append (dom-body) _el-div) - (hs-activate! _el-div) - (dom-dispatch _el-div "click" nil) - (assert= (dom-inner-html _el-div) "2") - )) - (deftest "can increment an attribute" - (hs-cleanup!) - (let ((_el-div (dom-create-element "div"))) - (dom-set-attr _el-div "_" "on click increment @value then put @value into me") - (dom-set-attr _el-div "value" "5") - (dom-append (dom-body) _el-div) - (hs-activate! _el-div) - (dom-dispatch _el-div "click" nil) - (dom-dispatch _el-div "click" nil) - (dom-dispatch _el-div "click" nil) - (assert= (dom-inner-html _el-div) "8") - )) - (deftest "can increment an floating point numbers" - (hs-cleanup!) - (let ((_el-div (dom-create-element "div"))) - (dom-set-attr _el-div "_" "on click set value to 5.2 then increment value by 6.1 then put value into me") - (dom-set-attr _el-div "value" "5") - (dom-append (dom-body) _el-div) - (hs-activate! _el-div) - (dom-dispatch _el-div "click" nil) - (assert= (dom-inner-html _el-div) "11.3") - )) - (deftest "can increment a property" - (hs-cleanup!) - (let ((_el-div (dom-create-element "div"))) - (dom-set-attr _el-div "_" "on click increment my.innerHTML") - (dom-set-inner-html _el-div "3") - (dom-append (dom-body) _el-div) - (hs-activate! _el-div) - (dom-dispatch _el-div "click" nil) - (dom-dispatch _el-div "click" nil) - (dom-dispatch _el-div "click" nil) - (assert= (dom-inner-html _el-div) "6") - )) - (deftest "can increment by zero" - (hs-cleanup!) - (let ((_el-div (dom-create-element "div"))) - (dom-set-attr _el-div "_" "on click set value to 20 then increment value by 0 then put value into me") - (dom-append (dom-body) _el-div) - (hs-activate! _el-div) - (dom-dispatch _el-div "click" nil) - (assert= (dom-inner-html _el-div) "20") - )) - (deftest "can increment a value multiple times" - (hs-cleanup!) - (let ((_el-div (dom-create-element "div"))) - (dom-set-attr _el-div "_" "on click increment my.innerHTML") - (dom-append (dom-body) _el-div) - (hs-activate! _el-div) - (dom-dispatch _el-div "click" nil) - (dom-dispatch _el-div "click" nil) - (dom-dispatch _el-div "click" nil) - (dom-dispatch _el-div "click" nil) - (dom-dispatch _el-div "click" nil) - (assert= (dom-inner-html _el-div) "5") - )) - (deftest "can decrement an empty variable" - (hs-cleanup!) - (let ((_el-div (dom-create-element "div"))) - (dom-set-attr _el-div "_" "on click decrement value then put value into me") - (dom-append (dom-body) _el-div) - (hs-activate! _el-div) - (dom-dispatch _el-div "click" nil) - (assert= (dom-inner-html _el-div) "-1") - )) - (deftest "can decrement a variable" - (hs-cleanup!) - (let ((_el-div (dom-create-element "div"))) - (dom-set-attr _el-div "_" "on click set value to 20 then decrement value by 2 then put value into me") - (dom-append (dom-body) _el-div) - (hs-activate! _el-div) - (dom-dispatch _el-div "click" nil) - (assert= (dom-inner-html _el-div) "18") - )) - (deftest "can decrement an attribute" - (hs-cleanup!) - (let ((_el-div (dom-create-element "div"))) - (dom-set-attr _el-div "_" "on click decrement @value then put @value into me") - (dom-set-attr _el-div "value" "5") - (dom-append (dom-body) _el-div) - (hs-activate! _el-div) - (dom-dispatch _el-div "click" nil) - (dom-dispatch _el-div "click" nil) - (dom-dispatch _el-div "click" nil) - (assert= (dom-inner-html _el-div) "2") - )) - (deftest "can decrement an floating point numbers" - (hs-cleanup!) - (let ((_el-div (dom-create-element "div"))) - (dom-set-attr _el-div "_" "on click set value to 6.1 then decrement value by 5.1 then put value into me") - (dom-set-attr _el-div "value" "5") - (dom-append (dom-body) _el-div) - (hs-activate! _el-div) - (dom-dispatch _el-div "click" nil) - (assert= (dom-inner-html _el-div) "1") - )) - (deftest "can decrement a property" - (hs-cleanup!) - (let ((_el-div (dom-create-element "div"))) - (dom-set-attr _el-div "_" "on click decrement my.innerHTML") - (dom-set-inner-html _el-div "3") - (dom-append (dom-body) _el-div) - (hs-activate! _el-div) - (dom-dispatch _el-div "click" nil) - (dom-dispatch _el-div "click" nil) - (dom-dispatch _el-div "click" nil) - (assert= (dom-inner-html _el-div) "0") - )) - (deftest "can decrement a value multiple times" - (hs-cleanup!) - (let ((_el-div (dom-create-element "div"))) - (dom-set-attr _el-div "_" "on click decrement my.innerHTML") - (dom-append (dom-body) _el-div) - (hs-activate! _el-div) - (dom-dispatch _el-div "click" nil) - (dom-dispatch _el-div "click" nil) - (dom-dispatch _el-div "click" nil) - (dom-dispatch _el-div "click" nil) - (dom-dispatch _el-div "click" nil) - (assert= (dom-inner-html _el-div) "-5") - )) - (deftest "can decrement by zero" - (hs-cleanup!) - (let ((_el-div (dom-create-element "div"))) - (dom-set-attr _el-div "_" "on click set value to 20 then decrement value by 0 then put value into me") - (dom-append (dom-body) _el-div) - (hs-activate! _el-div) - (dom-dispatch _el-div "click" nil) - (assert= (dom-inner-html _el-div) "20") - )) - (deftest "can increment an array element" - (hs-cleanup!) - (let ((_el-div (dom-create-element "div"))) - (dom-set-attr _el-div "_" "on click set arr to [10, 20, 30] then increment arr[1] then put arr[1] into me") - (dom-append (dom-body) _el-div) - (hs-activate! _el-div) - (dom-dispatch _el-div "click" nil) - (assert= (dom-text-content _el-div) "21") - )) - (deftest "can decrement an array element" - (hs-cleanup!) - (let ((_el-div (dom-create-element "div"))) - (dom-set-attr _el-div "_" "on click set arr to [10, 20, 30] then decrement arr[1] then put arr[1] into me") - (dom-append (dom-body) _el-div) - (hs-activate! _el-div) - (dom-dispatch _el-div "click" nil) - (assert= (dom-text-content _el-div) "19") - )) - (deftest "can increment a possessive property" - (hs-cleanup!) - (let ((_el-d1 (dom-create-element "div"))) - (dom-set-attr _el-d1 "id" "d1") - (dom-set-attr _el-d1 "_" "on click increment #d1's innerHTML") - (dom-set-inner-html _el-d1 "5") - (dom-append (dom-body) _el-d1) - (hs-activate! _el-d1) - (dom-dispatch (dom-query-by-id "d1") "click" nil) - (assert= (dom-text-content (dom-query-by-id "d1")) "6") - )) - (deftest "can increment a property of expression" - (hs-cleanup!) - (let ((_el-d1 (dom-create-element "div"))) - (dom-set-attr _el-d1 "id" "d1") - (dom-set-attr _el-d1 "_" "on click increment innerHTML of #d1") - (dom-set-inner-html _el-d1 "5") - (dom-append (dom-body) _el-d1) - (hs-activate! _el-d1) - (dom-dispatch (dom-query-by-id "d1") "click" nil) - (assert= (dom-text-content (dom-query-by-id "d1")) "6") - )) - (deftest "can increment a style ref" - (hs-cleanup!) - (let ((_el-div (dom-create-element "div"))) - (dom-set-attr _el-div "_" "on click set my *opacity to 0.5 then increment *opacity by 0.25 then put *opacity into me") - (dom-append (dom-body) _el-div) - (hs-activate! _el-div) - (dom-dispatch _el-div "click" nil) - (assert= (dom-text-content _el-div) "0.75") - )) ) ;; ── append (13 tests) ── (defsuite "hs-upstream-append" - (deftest "can append a string to another string" - (hs-cleanup!) - (let ((_el-div (dom-create-element "div"))) - (dom-set-attr _el-div "_" "on click set value to 'Hello there.' then append ' General Kenobi.' to value then set my.innerHTML to value") - (dom-append (dom-body) _el-div) - (hs-activate! _el-div) - (dom-dispatch _el-div "click" nil) - (assert= (dom-inner-html _el-div) "Hello there. General Kenobi.") - )) - (deftest "can append a value into an array" - (hs-cleanup!) - (let ((_el-div (dom-create-element "div"))) - (dom-set-attr _el-div "_" "on click set value to [1,2,3] then append 4 to value then set my.innerHTML to value as String") - (dom-append (dom-body) _el-div) - (hs-activate! _el-div) - (dom-dispatch _el-div "click" nil) - (assert= (dom-inner-html _el-div) "1,2,3,4") - )) - (deftest "can append a value to 'it'" - (hs-cleanup!) - (let ((_el-div (dom-create-element "div"))) - (dom-set-attr _el-div "_" "on click set result to [1,2,3] then append 4 then put it as String into me") - (dom-append (dom-body) _el-div) - (hs-activate! _el-div) - (dom-dispatch _el-div "click" nil) - (assert= (dom-inner-html _el-div) "1,2,3,4") - )) - (deftest "can append a value to a DOM node" - (hs-cleanup!) - (let ((_el-div (dom-create-element "div"))) - (dom-set-attr _el-div "_" "on click append 'This is my inner HTML' to me then append 'With Tags' to me") - (dom-append (dom-body) _el-div) - (hs-activate! _el-div) - (dom-dispatch _el-div "click" nil) - (assert= (dom-inner-html _el-div) "This is my inner HTMLWith Tags") - )) - (deftest "can append a value to a DOM element" - (hs-cleanup!) - (let ((_el-content (dom-create-element "div"))) - (dom-set-attr _el-content "id" "content") - (dom-set-attr _el-content "_" "on click append 'Content' to #content") - (dom-append (dom-body) _el-content) - (hs-activate! _el-content) - (dom-dispatch _el-content "click" nil) - (assert= (dom-inner-html _el-content) "Content") - )) - (deftest "can append a value to I" - (hs-cleanup!) - (let ((_el-div (dom-create-element "div"))) - (dom-set-attr _el-div "_" "on click append 'Content' to I") - (dom-append (dom-body) _el-div) - (hs-activate! _el-div) - (dom-dispatch _el-div "click" nil) - (assert= (dom-inner-html _el-div) "Content") - )) - (deftest "can append a value to an object property" - (hs-cleanup!) - (let ((_el-id (dom-create-element "div"))) - (dom-set-attr _el-id "id" "id") - (dom-set-attr _el-id "_" "on click append '_new' to my id") - (dom-append (dom-body) _el-id) - (hs-activate! _el-id) - (dom-dispatch _el-id "click" nil) - ;; SKIP check: skip div.id.should.equal("id_new") - )) - (deftest "multiple appends work" - (hs-cleanup!) - (let ((_el-id (dom-create-element "div"))) - (dom-set-attr _el-id "id" "id") - (dom-set-attr _el-id "_" "on click get 'foo' then append 'bar' then append 'doh' then append it to me") - (dom-append (dom-body) _el-id) - (hs-activate! _el-id) - (dom-dispatch _el-id "click" nil) - (assert= (dom-inner-html _el-id) "foobardoh") - )) - (deftest "append to undefined ignores the undefined" - (hs-cleanup!) - (let ((_el-id (dom-create-element "div"))) - (dom-set-attr _el-id "id" "id") - (dom-set-attr _el-id "_" "on click append 'bar' then append it to me") - (dom-append (dom-body) _el-id) - (hs-activate! _el-id) - (dom-dispatch _el-id "click" nil) - (assert= (dom-inner-html _el-id) "bar") - )) (deftest "append preserves existing content rather than overwriting it" (hs-cleanup!) (let ((_el-div (dom-create-element "div")) (_el-btn1 (dom-create-element "button"))) @@ -3292,30 +319,71 @@ (dom-append _el-div _el-btn1) (hs-activate! _el-div) (dom-dispatch _el-div "click" nil) - (dom-dispatch _el-div "click" nil) - (dom-dispatch _el-div "click" nil) - ;; SKIP check: skip clicks.should.equal(1) - ;; SKIP check: skip div.innerHTML.should.contain("New Content") - ;; SKIP check: skip btn.parentNode.should.equal(div) )) - (deftest "new content added by append will be live" + (deftest "append to undefined ignores the undefined" + (hs-cleanup!) + (let ((_el-id (dom-create-element "div"))) + (dom-set-attr _el-id "id" "id") + (dom-set-attr _el-id "_" "on click append 'bar' then append it to me") + (dom-append (dom-body) _el-id) + (hs-activate! _el-id) + (dom-dispatch _el-id "click" nil) + (assert= (dom-text-content _el-id) "bar") + )) + (deftest "can append a string to another string" (hs-cleanup!) (let ((_el-div (dom-create-element "div"))) - (dom-set-attr _el-div "_" "on click append `` to me") + (dom-set-attr _el-div "_" "on click set value to 'Hello there.' then append ' General Kenobi.' to value then set my.innerHTML to value") (dom-append (dom-body) _el-div) (hs-activate! _el-div) (dom-dispatch _el-div "click" nil) - (dom-dispatch _el-div "click" nil) - ;; SKIP check: skip window.temp.should.equal(1) + (assert= (dom-text-content _el-div) "Hello there. General Kenobi.") )) - (deftest "new DOM content added by append will be live" + (deftest "can append a value into an array" (hs-cleanup!) (let ((_el-div (dom-create-element "div"))) - (dom-set-attr _el-div "_" "on click make a then append it to me") + (dom-set-attr _el-div "_" "on click set value to [1,2,3] then append 4 to value then set my.innerHTML to value as String") (dom-append (dom-body) _el-div) (hs-activate! _el-div) (dom-dispatch _el-div "click" nil) - (assert (dom-has-class? (dom-query "span") "topping")) + (assert= (dom-text-content _el-div) "1,2,3,4") + )) + (deftest "can append a value to 'it'" + (hs-cleanup!) + (let ((_el-div (dom-create-element "div"))) + (dom-set-attr _el-div "_" "on click set result to [1,2,3] then append 4 then put it as String into me") + (dom-append (dom-body) _el-div) + (hs-activate! _el-div) + (dom-dispatch _el-div "click" nil) + (assert= (dom-text-content _el-div) "1,2,3,4") + )) + (deftest "can append a value to I" + (hs-cleanup!) + (let ((_el-div (dom-create-element "div"))) + (dom-set-attr _el-div "_" "on click append 'Content' to I") + (dom-append (dom-body) _el-div) + (hs-activate! _el-div) + (dom-dispatch _el-div "click" nil) + (assert= (dom-text-content _el-div) "Content") + )) + (deftest "can append a value to a DOM element" + (hs-cleanup!) + (let ((_el-content (dom-create-element "div"))) + (dom-set-attr _el-content "id" "content") + (dom-set-attr _el-content "_" "on click append 'Content' to #content") + (dom-append (dom-body) _el-content) + (hs-activate! _el-content) + (dom-dispatch _el-content "click" nil) + (assert= (dom-text-content _el-content) "Content") + )) + (deftest "can append a value to a DOM node" + (hs-cleanup!) + (let ((_el-div (dom-create-element "div"))) + (dom-set-attr _el-div "_" "on click append 'This is my inner HTML' to me then append 'With Tags' to me") + (dom-append (dom-body) _el-div) + (hs-activate! _el-div) + (dom-dispatch _el-div "click" nil) + (assert= (dom-text-content _el-div) "This is my inner HTMLWith Tags") )) (deftest "can append a value to a set" (hs-cleanup!) @@ -3326,766 +394,71 @@ (dom-dispatch _el-div "click" nil) (assert= (dom-text-content _el-div) "3") )) -) - -;; ── tell (10 tests) ── -(defsuite "hs-upstream-tell" - (deftest "establishes a proper beingTold symbol" + (deftest "can append a value to an object property" (hs-cleanup!) - (let ((_el-d1 (dom-create-element "div")) (_el-d2 (dom-create-element "div"))) - (dom-set-attr _el-d1 "id" "d1") - (dom-set-attr _el-d1 "_" "on click add .foo then tell #d2 then add .bar") - (dom-set-attr _el-d2 "id" "d2") - (dom-append (dom-body) _el-d1) - (dom-append (dom-body) _el-d2) - (hs-activate! _el-d1) - (dom-dispatch _el-d1 "click" nil) - (assert (not (dom-has-class? _el-d1 "bar"))) - (assert (dom-has-class? _el-d1 "foo")) - (assert (dom-has-class? _el-d2 "bar")) - (assert (not (dom-has-class? _el-d2 "foo"))) + (let ((_el-id (dom-create-element "div"))) + (dom-set-attr _el-id "id" "id") + (dom-set-attr _el-id "_" "on click append '_new' to my id") + (dom-append (dom-body) _el-id) + (hs-activate! _el-id) + (dom-dispatch _el-id "click" nil) )) - (deftest "does not overwrite the me symbol" + (deftest "multiple appends work" (hs-cleanup!) - (let ((_el-d1 (dom-create-element "div")) (_el-d2 (dom-create-element "div"))) - (dom-set-attr _el-d1 "id" "d1") - (dom-set-attr _el-d1 "_" "on click add .foo then tell #d2 then add .bar to me") - (dom-set-attr _el-d2 "id" "d2") - (dom-append (dom-body) _el-d1) - (dom-append (dom-body) _el-d2) - (hs-activate! _el-d1) - (dom-dispatch _el-d1 "click" nil) - (assert (dom-has-class? _el-d1 "bar")) - (assert (dom-has-class? _el-d1 "foo")) - (assert (not (dom-has-class? _el-d2 "bar"))) - (assert (not (dom-has-class? _el-d2 "foo"))) + (let ((_el-id (dom-create-element "div"))) + (dom-set-attr _el-id "id" "id") + (dom-set-attr _el-id "_" "on click get 'foo' then append 'bar' then append 'doh' then append it to me") + (dom-append (dom-body) _el-id) + (hs-activate! _el-id) + (dom-dispatch _el-id "click" nil) + (assert= (dom-text-content _el-id) "foobardoh") )) - (deftest "works with an array" - (hs-cleanup!) - (let ((_el-d1 (dom-create-element "div")) (_el-p1 (dom-create-element "p")) (_el-p2 (dom-create-element "p")) (_el-d2 (dom-create-element "div"))) - (dom-set-attr _el-d1 "id" "d1") - (dom-set-attr _el-d1 "_" "on click add .foo then tell

in me add .bar") - (dom-set-attr _el-p1 "id" "p1") - (dom-set-attr _el-p2 "id" "p2") - (dom-set-attr _el-d2 "id" "d2") - (dom-append (dom-body) _el-d1) - (dom-append _el-d1 _el-p1) - (dom-append _el-d1 _el-p2) - (dom-append _el-d1 _el-d2) - (hs-activate! _el-d1) - (dom-dispatch _el-d1 "click" nil) - (assert (not (dom-has-class? _el-d1 "bar"))) - (assert (dom-has-class? _el-d1 "foo")) - (assert (not (dom-has-class? (dom-query-by-id "div2") "bar"))) - (assert (not (dom-has-class? (dom-query-by-id "div2") "foo"))) - (assert (dom-has-class? _el-p1 "bar")) - (assert (not (dom-has-class? _el-p1 "foo"))) - (assert (dom-has-class? _el-p2 "bar")) - (assert (not (dom-has-class? _el-p2 "foo"))) - )) - (deftest "restores a proper implicit me symbol" - (hs-cleanup!) - (let ((_el-d1 (dom-create-element "div")) (_el-d2 (dom-create-element "div"))) - (dom-set-attr _el-d1 "id" "d1") - (dom-set-attr _el-d1 "_" "on click tell #d2 then add .bar end add .foo") - (dom-set-attr _el-d2 "id" "d2") - (dom-append (dom-body) _el-d1) - (dom-append (dom-body) _el-d2) - (hs-activate! _el-d1) - (dom-dispatch _el-d1 "click" nil) - (assert (not (dom-has-class? _el-d1 "bar"))) - (assert (dom-has-class? _el-d1 "foo")) - (assert (dom-has-class? _el-d2 "bar")) - (assert (not (dom-has-class? _el-d2 "foo"))) - )) - (deftest "ignores null" - (hs-cleanup!) - (let ((_el-d1 (dom-create-element "div")) (_el-d2 (dom-create-element "div"))) - (dom-set-attr _el-d1 "id" "d1") - (dom-set-attr _el-d1 "_" "on click tell null then add .bar end add .foo") - (dom-set-attr _el-d2 "id" "d2") - (dom-append (dom-body) _el-d1) - (dom-append (dom-body) _el-d2) - (hs-activate! _el-d1) - (dom-dispatch _el-d1 "click" nil) - (assert (not (dom-has-class? _el-d1 "bar"))) - (assert (dom-has-class? _el-d1 "foo")) - (assert (not (dom-has-class? _el-d2 "bar"))) - (assert (not (dom-has-class? _el-d2 "foo"))) - )) - (deftest "you symbol represents the thing being told" - (hs-cleanup!) - (let ((_el-d1 (dom-create-element "div")) (_el-d2 (dom-create-element "div"))) - (dom-set-attr _el-d1 "id" "d1") - (dom-set-attr _el-d1 "_" "on click tell #d2 then add .bar to you") - (dom-set-attr _el-d2 "id" "d2") - (dom-append (dom-body) _el-d1) - (dom-append (dom-body) _el-d2) - (hs-activate! _el-d1) - (dom-dispatch _el-d1 "click" nil) - (assert (not (dom-has-class? _el-d1 "bar"))) - (assert (dom-has-class? _el-d2 "bar")) - )) - (deftest "your symbol represents the thing being told" - (hs-cleanup!) - (let ((_el-d1 (dom-create-element "div")) (_el-d2 (dom-create-element "div"))) - (dom-set-attr _el-d1 "id" "d1") - (dom-set-attr _el-d1 "_" "on click tell #d2 then put your innerText into me") - (dom-set-attr _el-d2 "id" "d2") - (dom-set-inner-html _el-d2 "foo") - (dom-append (dom-body) _el-d1) - (dom-append (dom-body) _el-d2) - (hs-activate! _el-d1) - (dom-dispatch _el-d1 "click" nil) - ;; SKIP check: skip div1.innerText.should.equal("") - ;; SKIP check: skip div2.innerText.should.equal("foo") - ;; SKIP check: skip div1.innerText.should.equal("foo") - )) - (deftest "attributes refer to the thing being told" - (hs-cleanup!) - (let ((_el-d1 (dom-create-element "div")) (_el-d2 (dom-create-element "div"))) - (dom-set-attr _el-d1 "id" "d1") - (dom-set-attr _el-d1 "_" "on click tell #d2 then put @foo into me") - (dom-set-attr _el-d2 "id" "d2") - (dom-set-attr _el-d2 "foo" "bar") - (dom-append (dom-body) _el-d1) - (dom-append (dom-body) _el-d2) - (hs-activate! _el-d1) - (dom-dispatch _el-d1 "click" nil) - ;; SKIP check: skip div1.innerText.should.equal("") - ;; SKIP check: skip div2.innerText.should.equal("") - ;; SKIP check: skip div1.innerText.should.equal("bar") - )) - (deftest "yourself attribute also works" - (hs-cleanup!) - (let ((_el-d1 (dom-create-element "div")) (_el-d2 (dom-create-element "div"))) - (dom-set-attr _el-d1 "id" "d1") - (dom-set-attr _el-d1 "_" "on click tell #d2 remove yourself") - (dom-set-attr _el-d2 "id" "d2") - (dom-append (dom-body) _el-d1) - (dom-append _el-d1 _el-d2) - (hs-activate! _el-d1) - (dom-dispatch _el-d1 "click" nil) - (assert= (dom-inner-html _el-d1) "") - )) - (deftest "tell terminates with a feature" - (hs-cleanup!) - (let ((_el-d1 (dom-create-element "div")) (_el-d2 (dom-create-element "div")) (_el-d3 (dom-create-element "div"))) - (dom-set-attr _el-d1 "id" "d1") - (dom-set-attr _el-d1 "_" "on click tell #d2 remove yourself on click tell #d3 remove yourself") - (dom-set-attr _el-d2 "id" "d2") - (dom-set-attr _el-d3 "id" "d3") - (dom-append (dom-body) _el-d1) - (dom-append _el-d1 _el-d2) - (dom-append _el-d1 _el-d3) - (hs-activate! _el-d1) - (dom-dispatch _el-d1 "click" nil) - (assert= (dom-inner-html _el-d1) "") - )) -) - -;; ── on (63 tests) ── -(defsuite "hs-upstream-on" - (deftest "can respond to events with dots in names" - (hs-cleanup!) - (let ((_el-div (dom-create-element "div")) (_el-d1 (dom-create-element "div"))) - (dom-set-attr _el-div "_" "on click send example.event to #d1") - (dom-set-attr _el-d1 "id" "d1") - (dom-set-attr _el-d1 "_" "on example.event add .called") - (dom-append (dom-body) _el-div) - (dom-append (dom-body) _el-d1) - (hs-activate! _el-div) - (hs-activate! _el-d1) - (dom-dispatch _el-div "click" nil) - (assert (dom-has-class? _el-d1 "called")) - )) - (deftest "can respond to events with colons in names" - (hs-cleanup!) - (let ((_el-div (dom-create-element "div")) (_el-d1 (dom-create-element "div"))) - (dom-set-attr _el-div "_" "on click send example:event to #d1") - (dom-set-attr _el-d1 "id" "d1") - (dom-set-attr _el-d1 "_" "on example:event add .called") - (dom-append (dom-body) _el-div) - (dom-append (dom-body) _el-d1) - (hs-activate! _el-div) - (hs-activate! _el-d1) - (dom-dispatch _el-div "click" nil) - (assert (dom-has-class? _el-d1 "called")) - )) - (deftest "can respond to events with minus in names" - (hs-cleanup!) - (let ((_el-div (dom-create-element "div")) (_el-d1 (dom-create-element "div"))) - (dom-set-attr _el-div "_" "on click send \"a-b\" to #d1") - (dom-set-attr _el-d1 "id" "d1") - (dom-set-attr _el-d1 "_" "on \"a-b\" add .called") - (dom-append (dom-body) _el-div) - (dom-append (dom-body) _el-d1) - (hs-activate! _el-div) - (hs-activate! _el-d1) - (dom-dispatch _el-div "click" nil) - (assert (dom-has-class? _el-d1 "called")) - )) - (deftest "can respond to events on other elements" - (hs-cleanup!) - (let ((_el-bar (dom-create-element "div")) (_el-div (dom-create-element "div"))) - (dom-set-attr _el-bar "id" "bar") - (dom-set-attr _el-div "_" "on click from #bar add .clicked") - (dom-append (dom-body) _el-bar) - (dom-append (dom-body) _el-div) - (hs-activate! _el-div) - (dom-dispatch _el-bar "click" nil) - (assert (dom-has-class? _el-div "clicked")) - )) - (deftest "listeners on other elements are removed when the registering element is removed" - (hs-cleanup!)) - (deftest "listeners on self are not removed when the element is removed" - (hs-cleanup!)) - (deftest "supports \"elsewhere\" modifier" - (hs-cleanup!)) - (deftest "supports \"from elsewhere\" modifier" - (hs-cleanup!)) - (deftest "can pick detail fields out by name" - (hs-cleanup!)) - (deftest "can pick event properties out by name" - (hs-cleanup!)) - (deftest "can fire an event on load" - (hs-cleanup!) - (let ((_el-d1 (dom-create-element "div"))) - (dom-set-attr _el-d1 "id" "d1") - (dom-set-attr _el-d1 "_" "on load put \"Loaded\" into my.innerHTML") - (dom-append (dom-body) _el-d1) - (hs-activate! _el-d1) - ;; SKIP check: skip div.innerText.should.equal("Loaded") - )) - (deftest "can be in a top level script tag" - (hs-cleanup!)) - (deftest "can have a simple event filter" - (hs-cleanup!) - (let ((_el-d1 (dom-create-element "div"))) - (dom-set-attr _el-d1 "id" "d1") - (dom-set-attr _el-d1 "_" "on click[false] log event then put \"Clicked\" into my.innerHTML") - (dom-append (dom-body) _el-d1) - (hs-activate! _el-d1) - (dom-dispatch _el-d1 "click" nil) - ;; SKIP check: skip byId("d1").innerText.should.equal("") - )) - (deftest "can refer to event properties directly in filter" - (hs-cleanup!) - (let ((_el-div (dom-create-element "div")) (_el-div1 (dom-create-element "div")) (_el-div2 (dom-create-element "div"))) - (dom-set-attr _el-div "_" "on click[buttons==0] log event then put \"Clicked\" into my.innerHTML") - (dom-set-attr _el-div1 "_" "on click[buttons==1] log event then put \"Clicked\" into my.innerHTML") - (dom-set-attr _el-div2 "_" "on click[buttons==1 and buttons==0] log event then put \"Clicked\" into my.innerHTML") - (dom-append (dom-body) _el-div) - (dom-append (dom-body) _el-div1) - (dom-append (dom-body) _el-div2) - (hs-activate! _el-div) - (hs-activate! _el-div1) - (hs-activate! _el-div2) - (dom-dispatch _el-div "click" nil) - (dom-dispatch _el-div "click" nil) - (dom-dispatch _el-div "click" nil) - ;; SKIP check: skip div.innerText.should.equal("Clicked") - ;; SKIP check: skip div.innerText.should.equal("") - )) - (deftest "can refer to event detail properties directly in filter" + (deftest "new DOM content added by append will be live" (hs-cleanup!) (let ((_el-div (dom-create-element "div"))) - (dom-set-attr _el-div "_" "on example[foo] increment @count then put it into me") - (dom-append (dom-body) _el-div) - (hs-activate! _el-div) - ;; SKIP action: div.dispatchEvent_event_ - ;; SKIP action: div.dispatchEvent_event_ - ;; SKIP action: div.dispatchEvent_event_ - ;; SKIP check: skip div.innerText.should.equal("1") - ;; SKIP check: skip div.innerText.should.equal("2") - )) - (deftest "can click after a positive event filter" - (hs-cleanup!) - (let ((_el-div (dom-create-element "div"))) - (dom-set-attr _el-div "_" "on foo(bar)[bar] put \"triggered\" into my.innerHTML") - (dom-append (dom-body) _el-div) - (hs-activate! _el-div) - (dom-dispatch _el-div "foo" nil) - (dom-dispatch _el-div "foo" nil) - ;; SKIP check: skip div.innerText.should.equal("") - ;; SKIP check: skip div.innerText.should.equal("triggered") - )) - (deftest "multiple event handlers at a time are allowed to execute with the every keyword" - (hs-cleanup!)) - (deftest "can have multiple event handlers" - (hs-cleanup!) - (let ((_el-div (dom-create-element "div"))) - (dom-set-attr _el-div "_" "on foo put increment() into my.innerHTML end on bar put increment() into my.innerHTML") - (dom-append (dom-body) _el-div) - (hs-activate! _el-div) - (dom-dispatch _el-div "foo" nil) - (dom-dispatch _el-div "bar" nil) - (dom-dispatch _el-div "foo" nil) - ;; SKIP check: skip div.innerText.should.equal("1") - ;; SKIP check: skip div.innerText.should.equal("2") - ;; SKIP check: skip div.innerText.should.equal("3") - )) - (deftest "can have multiple event handlers, no end" - (hs-cleanup!) - (let ((_el-div (dom-create-element "div"))) - (dom-set-attr _el-div "_" "on foo put increment() into my.innerHTML on bar put increment() into my.innerHTML") - (dom-append (dom-body) _el-div) - (hs-activate! _el-div) - (dom-dispatch _el-div "foo" nil) - (dom-dispatch _el-div "bar" nil) - (dom-dispatch _el-div "foo" nil) - ;; SKIP check: skip div.innerText.should.equal("1") - ;; SKIP check: skip div.innerText.should.equal("2") - ;; SKIP check: skip div.innerText.should.equal("3") - )) - (deftest "can queue events" - (hs-cleanup!) - (let ((_el-div (dom-create-element "div"))) - (dom-set-attr _el-div "_" "on foo wait for bar then call increment()") - (dom-append (dom-body) _el-div) - (hs-activate! _el-div) - (dom-dispatch _el-div "foo" nil) - (dom-dispatch _el-div "foo" nil) - (dom-dispatch _el-div "foo" nil) - (dom-dispatch _el-div "bar" nil) - (dom-dispatch _el-div "bar" nil) - (dom-dispatch _el-div "bar" nil) - ;; SKIP check: skip i.should.equal(0) - ;; SKIP check: skip i.should.equal(1) - ;; SKIP check: skip i.should.equal(2) - )) - (deftest "can queue first event" - (hs-cleanup!) - (let ((_el-div (dom-create-element "div"))) - (dom-set-attr _el-div "_" "on foo queue first wait for bar then call increment()") - (dom-append (dom-body) _el-div) - (hs-activate! _el-div) - (dom-dispatch _el-div "foo" nil) - (dom-dispatch _el-div "foo" nil) - (dom-dispatch _el-div "foo" nil) - (dom-dispatch _el-div "bar" nil) - (dom-dispatch _el-div "bar" nil) - (dom-dispatch _el-div "bar" nil) - ;; SKIP check: skip i.should.equal(0) - ;; SKIP check: skip i.should.equal(1) - ;; SKIP check: skip i.should.equal(2) - )) - (deftest "can queue last event" - (hs-cleanup!) - (let ((_el-div (dom-create-element "div"))) - (dom-set-attr _el-div "_" "on foo queue last wait for bar then call increment()") - (dom-append (dom-body) _el-div) - (hs-activate! _el-div) - (dom-dispatch _el-div "foo" nil) - (dom-dispatch _el-div "foo" nil) - (dom-dispatch _el-div "foo" nil) - (dom-dispatch _el-div "bar" nil) - (dom-dispatch _el-div "bar" nil) - (dom-dispatch _el-div "bar" nil) - ;; SKIP check: skip i.should.equal(0) - ;; SKIP check: skip i.should.equal(1) - ;; SKIP check: skip i.should.equal(2) - )) - (deftest "can queue all events" - (hs-cleanup!) - (let ((_el-div (dom-create-element "div"))) - (dom-set-attr _el-div "_" "on foo queue all wait for bar then call increment()") - (dom-append (dom-body) _el-div) - (hs-activate! _el-div) - (dom-dispatch _el-div "foo" nil) - (dom-dispatch _el-div "foo" nil) - (dom-dispatch _el-div "foo" nil) - (dom-dispatch _el-div "bar" nil) - (dom-dispatch _el-div "bar" nil) - (dom-dispatch _el-div "bar" nil) - ;; SKIP check: skip i.should.equal(0) - ;; SKIP check: skip i.should.equal(1) - ;; SKIP check: skip i.should.equal(2) - ;; SKIP check: skip i.should.equal(3) - )) - (deftest "queue none does not allow future queued events" - (hs-cleanup!) - (let ((_el-div (dom-create-element "div"))) - (dom-set-attr _el-div "_" "on click queue none put increment() into my.innerHTML then wait for a customEvent") + (dom-set-attr _el-div "_" "on click make a then append it to me") (dom-append (dom-body) _el-div) (hs-activate! _el-div) (dom-dispatch _el-div "click" nil) - (dom-dispatch _el-div "click" nil) - (dom-dispatch _el-div "customEvent" nil) - (dom-dispatch _el-div "click" nil) - ;; SKIP check: skip div.innerText.should.equal("1") - ;; SKIP check: skip div.innerText.should.equal("2") + (assert (dom-has-class? (dom-query "span.topping") "topping")) )) - (deftest "can invoke on multiple events" + (deftest "new content added by append will be live" (hs-cleanup!) (let ((_el-div (dom-create-element "div"))) - (dom-set-attr _el-div "_" "on click or foo call increment()") + (dom-set-attr _el-div "_" "on click append `` to me") (dom-append (dom-body) _el-div) (hs-activate! _el-div) (dom-dispatch _el-div "click" nil) - (dom-dispatch _el-div "foo" nil) - ;; SKIP check: skip i.should.equal(1) - ;; SKIP check: skip i.should.equal(2) + (dom-dispatch (dom-query-by-id "b1") "click" nil) )) - (deftest "can listen for events in another element (lazy)" - (hs-cleanup!) - (let ((_el-div (dom-create-element "div")) (_el-d1 (dom-create-element "div")) (_el-d2 (dom-create-element "div"))) - (dom-set-attr _el-div "_" "on click in #d1 put it into window.tmp") - (dom-set-attr _el-d1 "id" "d1") - (dom-set-attr _el-d2 "id" "d2") - (dom-append (dom-body) _el-div) - (dom-append _el-div _el-d1) - (dom-append _el-div _el-d2) - (hs-activate! _el-div) - (dom-dispatch _el-div "click" nil) - ;; SKIP check: skip div1.should.equal(window.tmp) - )) - (deftest "can filter events based on count" - (hs-cleanup!)) - (deftest "can filter events based on count range" - (hs-cleanup!)) - (deftest "can filter events based on unbounded count range" - (hs-cleanup!)) - (deftest "can mix ranges" - (hs-cleanup!)) - (deftest "can listen for general mutations" - (hs-cleanup!)) - (deftest "can listen for attribute mutations" - (hs-cleanup!)) - (deftest "can listen for specific attribute mutations" - (hs-cleanup!)) - (deftest "can listen for specific attribute mutations and filter out other attribute mutations" - (hs-cleanup!) - (let ((_el-div (dom-create-element "div"))) - (dom-set-attr _el-div "_" "on mutation of @bar put \"Mutated\" into me") - (dom-append (dom-body) _el-div) - (hs-activate! _el-div) - (dom-set-attr _el-div "foo" "bar") - (assert= (dom-inner-html _el-div) "") - )) - (deftest "can listen for childList mutations" - (hs-cleanup!)) - (deftest "can listen for childList mutation filter out other mutations" - (hs-cleanup!) - (let ((_el-div (dom-create-element "div"))) - (dom-set-attr _el-div "_" "on mutation of childList put \"Mutated\" into me") - (dom-append (dom-body) _el-div) - (hs-activate! _el-div) - (dom-set-attr _el-div "foo" "bar") - (assert= (dom-inner-html _el-div) "") - )) - (deftest "can listen for characterData mutation filter out other mutations" - (hs-cleanup!) - (let ((_el-div (dom-create-element "div"))) - (dom-set-attr _el-div "_" "on mutation of characterData put \"Mutated\" into me") - (dom-append (dom-body) _el-div) - (hs-activate! _el-div) - (dom-set-attr _el-div "foo" "bar") - (assert= (dom-inner-html _el-div) "") - )) - (deftest "can listen for multiple mutations" - (hs-cleanup!)) - (deftest "can listen for multiple mutations 2" - (hs-cleanup!)) - (deftest "can listen for attribute mutations on other elements" - (hs-cleanup!)) - (deftest "each behavior installation has its own event queue" - (hs-cleanup!)) - (deftest "can catch exceptions thrown in js functions" - (hs-cleanup!)) - (deftest "can catch exceptions thrown in hyperscript functions" - (hs-cleanup!)) - (deftest "can catch top-level exceptions" - (hs-cleanup!) - (let ((_el-button (dom-create-element "button"))) - (dom-set-attr _el-button "_" "on click throw \"bar\" catch e put e into me") - (dom-append (dom-body) _el-button) - (hs-activate! _el-button) - (dom-dispatch _el-button "click" nil) - (assert= (dom-inner-html _el-button) "bar") - )) - (deftest "can catch async top-level exceptions" - (hs-cleanup!) - (let ((_el-button (dom-create-element "button"))) - (dom-set-attr _el-button "_" "on click wait 1ms then throw \"bar\" catch e put e into me") - (dom-append (dom-body) _el-button) - (hs-activate! _el-button) - (dom-dispatch _el-button "click" nil) - (assert= (dom-inner-html _el-button) "bar") - )) - (deftest "async exceptions don't kill the event queue" - (hs-cleanup!) - (let ((_el-button (dom-create-element "button"))) - (dom-set-attr _el-button "_" "on click increment :x then if :x is 1 then wait 1ms then throw \"bar\" otherwise then put \"success\" into me end catch e then put e into me") - (dom-append (dom-body) _el-button) - (hs-activate! _el-button) - (dom-dispatch _el-button "click" nil) - (dom-dispatch _el-button "click" nil) - (assert= (dom-inner-html _el-button) "success") - )) - (deftest "exceptions in catch block don't kill the event queue" - (hs-cleanup!) - (let ((_el-button (dom-create-element "button"))) - (dom-set-attr _el-button "_" "on click increment :x then if :x is 1 then throw \"bar\" otherwise then put \"success\" into me end catch e then put e into me then throw e") - (dom-append (dom-body) _el-button) - (hs-activate! _el-button) - (dom-dispatch _el-button "click" nil) - (dom-dispatch _el-button "click" nil) - (assert= (dom-inner-html _el-button) "success") - )) - (deftest "uncaught exceptions trigger 'exception' event" - (hs-cleanup!)) - (deftest "caught exceptions do not trigger 'exception' event" - (hs-cleanup!) - (let ((_el-button (dom-create-element "button"))) - (dom-set-attr _el-button "_" "on click put \"foo\" into me then throw \"bar\" catch e log e on exception(error) put error into me") - (dom-append (dom-body) _el-button) - (hs-activate! _el-button) - (dom-dispatch _el-button "click" nil) - (assert= (dom-inner-html _el-button) "foo") - )) - (deftest "rethrown exceptions trigger 'exception' event" - (hs-cleanup!)) - (deftest "basic finally blocks work" - (hs-cleanup!)) - (deftest "finally blocks work when exception thrown in catch" - (hs-cleanup!)) - (deftest "async basic finally blocks work" - (hs-cleanup!)) - (deftest "async finally blocks work when exception thrown in catch" - (hs-cleanup!)) - (deftest "async exceptions in finally block don't kill the event queue" - (hs-cleanup!)) - (deftest "exceptions in finally block don't kill the event queue" - (hs-cleanup!)) - (deftest "can ignore when target doesn't exist" - (hs-cleanup!)) - (deftest "can handle an or after a from clause" - (hs-cleanup!)) - (deftest "handles custom events with null detail" - (hs-cleanup!) - (let ((_el-d1 (dom-create-element "div"))) - (dom-set-attr _el-d1 "id" "d1") - (dom-set-attr _el-d1 "_" "on myEvent(foo) if foo put foo into me else put \"no-detail\" into me") - (dom-append (dom-body) _el-d1) - (hs-activate! _el-d1) - )) - (deftest "on first click fires only once" - (hs-cleanup!)) - (deftest "caught exceptions do not trigger 'exception' event" - (hs-cleanup!) - (let ((_el-button (dom-create-element "button"))) - (dom-set-attr _el-button "_" "on click put \"foo\" into me then throw \"bar\" catch e log e on exception(error) put error into me") - (dom-append (dom-body) _el-button) - (hs-activate! _el-button) - (dom-dispatch _el-button "click" nil) - (assert= (dom-text-content _el-button) "foo") - )) - (deftest "rethrown exceptions trigger 'exception' event" - (hs-cleanup!)) - (deftest "can ignore when target doesn\\'t exist" - (hs-cleanup!)) -) - -;; ── init (3 tests) ── -(defsuite "hs-upstream-init" - (deftest "can define an init block inline" - (hs-cleanup!) - (let ((_el-div (dom-create-element "div"))) - (dom-set-attr _el-div "_" "init then set my.foo to 42 end on click put my.foo into my.innerHTML") - (dom-append (dom-body) _el-div) - (hs-activate! _el-div) - (dom-dispatch _el-div "click" nil) - (assert= (dom-inner-html _el-div) "42") - )) - (deftest "can define an init block in a script" - (hs-cleanup!) - (guard (_e (true nil)) (eval-expr-cek (hs-to-sx (hs-compile "init set window.foo to 42 end")))) - ) - (deftest "can initialize immediately" - (hs-cleanup!) - (guard (_e (true nil)) (eval-expr-cek (hs-to-sx (hs-compile "init set window.foo to 10 init immediately set window.bar to window.foo")))) - ) -) - -;; ── def (27 tests) ── -(defsuite "hs-upstream-def" - (deftest "can define a basic no arg function" - (hs-cleanup!) - (eval-expr-cek (hs-to-sx (hs-compile "def foo() add .called to #d1 end"))) - (let ((_el-div (dom-create-element "div")) (_el-d1 (dom-create-element "div"))) - (dom-set-attr _el-div "_" "on click call foo()") - (dom-set-attr _el-d1 "id" "d1") - (dom-append (dom-body) _el-div) - (dom-append (dom-body) _el-d1) - (hs-activate! _el-div) - (dom-dispatch _el-div "click" nil) - (assert (dom-has-class? _el-d1 "called")) - )) - (deftest "can define a basic one arg function" - (hs-cleanup!) - (eval-expr-cek (hs-to-sx (hs-compile "def foo(str) put str into #d1.innerHTML end"))) - (let ((_el-div (dom-create-element "div")) (_el-d1 (dom-create-element "div"))) - (dom-set-attr _el-div "_" "on click call foo(\"called\")") - (dom-set-attr _el-d1 "id" "d1") - (dom-append (dom-body) _el-div) - (dom-append (dom-body) _el-d1) - (hs-activate! _el-div) - (dom-dispatch _el-div "click" nil) - (assert= (dom-inner-html _el-d1) "called") - )) - (deftest "functions can be namespaced" - (hs-cleanup!)) - (deftest "is called synchronously" - (hs-cleanup!)) - (deftest "can call asynchronously" - (hs-cleanup!)) - (deftest "can return a value synchronously" - (hs-cleanup!) - (eval-expr-cek (hs-to-sx (hs-compile "def foo() return \"foo\"end"))) - (let ((_el-div (dom-create-element "div")) (_el-d1 (dom-create-element "div"))) - (dom-set-attr _el-div "_" "on click call foo() then put it into #d1.innerText") - (dom-set-attr _el-d1 "id" "d1") - (dom-append (dom-body) _el-div) - (dom-append (dom-body) _el-d1) - (hs-activate! _el-div) - (dom-dispatch _el-div "click" nil) - ;; SKIP check: skip div.innerText.should.equal("") - ;; SKIP check: skip div.innerText.should.equal("foo") - )) - (deftest "can exit" - (hs-cleanup!) - (guard (_e (true nil)) (eval-expr-cek (hs-to-sx (hs-compile "def foo() exit end")))) - ) - (deftest "can return a value asynchronously" - (hs-cleanup!) - (eval-expr-cek (hs-to-sx (hs-compile "def foo() wait 1ms return \"foo\"end"))) - (let ((_el-div (dom-create-element "div")) (_el-d1 (dom-create-element "div"))) - (dom-set-attr _el-div "_" "on click call foo() then put it into #d1.innerText") - (dom-set-attr _el-d1 "id" "d1") - (dom-append (dom-body) _el-div) - (dom-append (dom-body) _el-d1) - (hs-activate! _el-div) - (dom-dispatch _el-div "click" nil) - ;; SKIP check: skip div.innerText.should.equal("") - ;; SKIP check: skip div.innerText.should.equal("foo") - )) - (deftest "can interop with javascript" - (hs-cleanup!) - (guard (_e (true nil)) (eval-expr-cek (hs-to-sx (hs-compile "def foo() return \"foo\"end")))) - ) - (deftest "can interop with javascript asynchronously" - (hs-cleanup!) - (guard (_e (true nil)) (eval-expr-cek (hs-to-sx (hs-compile "def foo() wait 1ms return \"foo\"end")))) - ) - (deftest "can catch exceptions" - (hs-cleanup!) - (guard (_e (true nil)) (eval-expr-cek (hs-to-sx (hs-compile "def foo() throw \"bar\"catch e set window.bar to e end")))) - ) - (deftest "can rethrow in catch blocks" - (hs-cleanup!) - (guard (_e (true nil)) (eval-expr-cek (hs-to-sx (hs-compile "def foo() throw \"bar\"catch e throw e end")))) - ) - (deftest "can return in catch blocks" - (hs-cleanup!) - (guard (_e (true nil)) (eval-expr-cek (hs-to-sx (hs-compile "def foo() throw \"bar\"catch e return 42 end")))) - ) - (deftest "can catch async exceptions" - (hs-cleanup!) - (guard (_e (true nil)) (eval-expr-cek (hs-to-sx (hs-compile "def doh() wait 10ms throw \"bar\"end def foo() call doh()catch e set window.bar to e end")))) - ) - (deftest "can catch nested async exceptions" - (hs-cleanup!) - (guard (_e (true nil)) (eval-expr-cek (hs-to-sx (hs-compile "def doh() wait 10ms throw \"bar\"end def foo() call doh()catch e set window.bar to e end")))) - ) - (deftest "can rethrow in async catch blocks" - (hs-cleanup!) - (guard (_e (true nil)) (eval-expr-cek (hs-to-sx (hs-compile "def foo() throw \"bar\"catch e wait 10ms throw e end")))) - ) - (deftest "can return in async catch blocks" - (hs-cleanup!) - (guard (_e (true nil)) (eval-expr-cek (hs-to-sx (hs-compile "def foo() throw \"bar\"catch e wait 10ms return 42 end")))) - ) - (deftest "can install a function on an element and use in children w/ no leak" - (hs-cleanup!) - (let ((_el-div (dom-create-element "div")) (_el-d1 (dom-create-element "div")) (_el-d2 (dom-create-element "div")) (_el-d3 (dom-create-element "div"))) - (dom-set-attr _el-div "_" "def func() put 42 into #d3") - (dom-set-attr _el-d1 "id" "d1") - (dom-set-attr _el-d1 "_" "on click call func()") - (dom-set-attr _el-d2 "id" "d2") - (dom-set-attr _el-d3 "id" "d3") - (dom-append (dom-body) _el-div) - (dom-append _el-div _el-d1) - (dom-append _el-div _el-d2) - (dom-append _el-div _el-d3) - (hs-activate! _el-div) - (hs-activate! _el-d1) - ;; SKIP check: skip byId("d3").innerText.should.equal("42") - )) - (deftest "can install a function on an element and use in children w/ return value" - (hs-cleanup!) - (let ((_el-div (dom-create-element "div")) (_el-d1 (dom-create-element "div")) (_el-d2 (dom-create-element "div")) (_el-d3 (dom-create-element "div"))) - (dom-set-attr _el-div "_" "def func() return 42") - (dom-set-attr _el-d1 "id" "d1") - (dom-set-attr _el-d1 "_" "on click put func() into me") - (dom-set-attr _el-d2 "id" "d2") - (dom-set-attr _el-d3 "id" "d3") - (dom-append (dom-body) _el-div) - (dom-append _el-div _el-d1) - (dom-append _el-div _el-d2) - (dom-append _el-div _el-d3) - (hs-activate! _el-div) - (hs-activate! _el-d1) - ;; SKIP check: skip byId("d1").innerText.should.equal("42") - )) - (deftest "can install a function on an element and use me symbol correctly" - (hs-cleanup!) - (let ((_el-div (dom-create-element "div")) (_el-d1 (dom-create-element "div")) (_el-d2 (dom-create-element "div")) (_el-d3 (dom-create-element "div"))) - (dom-set-attr _el-div "_" "def func() put 42 into me") - (dom-set-attr _el-d1 "id" "d1") - (dom-set-attr _el-d1 "_" "on click call func()") - (dom-set-attr _el-d2 "id" "d2") - (dom-set-attr _el-d3 "id" "d3") - (dom-append (dom-body) _el-div) - (dom-append _el-div _el-d1) - (dom-append _el-div _el-d2) - (dom-append _el-div _el-d3) - (hs-activate! _el-div) - (hs-activate! _el-d1) - ;; SKIP check: skip div.innerText.should.equal("42") - )) - (deftest "finally blocks run normally" - (hs-cleanup!) - (guard (_e (true nil)) (eval-expr-cek (hs-to-sx (hs-compile "def foo() set window.bar to 10finally set window.bar to 20 end")))) - ) - (deftest "finally blocks run when an exception occurs" - (hs-cleanup!) - (guard (_e (true nil)) (eval-expr-cek (hs-to-sx (hs-compile "def foo() set window.bar to 10 throw \"foo\"finally set window.bar to 20 end")))) - ) - (deftest "finally blocks run when an exception expr occurs" - (hs-cleanup!) - (guard (_e (true nil)) (eval-expr-cek (hs-to-sx (hs-compile "def foo() set window.bar to 10 call throwsAsyncException()finally set window.bar to 20 end")))) - ) - (deftest "async finally blocks run normally" - (hs-cleanup!) - (guard (_e (true nil)) (eval-expr-cek (hs-to-sx (hs-compile "def foo() wait a tick then set window.bar to 10finally set window.bar to 20 end")))) - ) - (deftest "async finally blocks run when an exception occurs" - (hs-cleanup!) - (guard (_e (true nil)) (eval-expr-cek (hs-to-sx (hs-compile "def foo() wait a tick then set window.bar to 10 throw \"foo\"finally set window.bar to 20 end")))) - ) - (deftest "exit stops execution mid-function" - (hs-cleanup!) - (guard (_e (true nil)) (eval-expr-cek (hs-to-sx (hs-compile "def foo() set x to 1 then exit then set x to 2 then return x end")))) - ) - (deftest "can return without a value" - (hs-cleanup!) - (guard (_e (true nil)) (eval-expr-cek (hs-to-sx (hs-compile "def foo() return end")))) - ) ) ;; ── askAnswer (5 tests) ── (defsuite "hs-upstream-askAnswer" + (deftest "confirm returns first choice on OK" + (hs-cleanup!) + (let ((_el-button (dom-create-element "button")) (_el-out (dom-create-element "div"))) + (dom-set-attr _el-button "_" "on click answer \"Save?\" with \"Yes\" or \"No\" then put it into #out") + (dom-set-inner-html _el-button "Go") + (dom-set-attr _el-out "id" "out") + (dom-append (dom-body) _el-button) + (dom-append (dom-body) _el-out) + (hs-activate! _el-button) + (dom-dispatch _el-button "click" nil) + (assert= (dom-text-content (dom-query-by-id "out")) "Yes") + )) + (deftest "confirm returns second choice on cancel" + (hs-cleanup!) + (let ((_el-button (dom-create-element "button")) (_el-out (dom-create-element "div"))) + (dom-set-attr _el-button "_" "on click answer \"Save?\" with \"Yes\" or \"No\" then put it into #out") + (dom-set-inner-html _el-button "Go") + (dom-set-attr _el-out "id" "out") + (dom-append (dom-body) _el-button) + (dom-append (dom-body) _el-out) + (hs-activate! _el-button) + (dom-dispatch _el-button "click" nil) + (assert= (dom-text-content (dom-query-by-id "out")) "No") + )) (deftest "prompts and puts result in it" (hs-cleanup!) (let ((_el-button (dom-create-element "button")) (_el-out (dom-create-element "div"))) @@ -4122,47 +495,2342 @@ (dom-dispatch _el-button "click" nil) (assert= (dom-text-content (dom-query-by-id "out")) "done") )) - (deftest "confirm returns first choice on OK" +) + +;; ── behavior (10 tests) ── +(defsuite "hs-upstream-behavior" + (deftest "can declare variables in init blocks" (hs-cleanup!) - (let ((_el-button (dom-create-element "button")) (_el-out (dom-create-element "div"))) - (dom-set-attr _el-button "_" "on click answer \"Save?\" with \"Yes\" or \"No\" then put it into #out") - (dom-set-inner-html _el-button "Go") - (dom-set-attr _el-out "id" "out") - (dom-append (dom-body) _el-button) - (dom-append (dom-body) _el-out) - (hs-activate! _el-button) - (dom-dispatch _el-button "click" nil) - (assert= (dom-text-content (dom-query-by-id "out")) "Yes") + (let ((_el-script (dom-create-element "script")) (_el-div (dom-create-element "div"))) + (dom-set-attr _el-script "type" "text/hyperscript") + (dom-set-inner-html _el-script "behavior Behave + init + set element's foo to 1 + set element's bar to {} + end + on click + increment element's foo + set element's bar[\"count\"] to element's foo + put element's bar[\"count\"] into me + end + end") + (dom-set-attr _el-div "_" "install Behave") + (dom-append (dom-body) _el-script) + (dom-append (dom-body) _el-div) + (hs-activate! _el-div) + (dom-dispatch _el-div "click" nil) + (assert= (dom-text-content _el-div) "2") + (dom-dispatch _el-div "click" nil) + (assert= (dom-text-content _el-div) "3") )) - (deftest "confirm returns second choice on cancel" + (deftest "can define behaviors" (hs-cleanup!) - (let ((_el-button (dom-create-element "button")) (_el-out (dom-create-element "div"))) - (dom-set-attr _el-button "_" "on click answer \"Save?\" with \"Yes\" or \"No\" then put it into #out") - (dom-set-inner-html _el-button "Go") - (dom-set-attr _el-out "id" "out") - (dom-append (dom-body) _el-button) - (dom-append (dom-body) _el-out) - (hs-activate! _el-button) - (dom-dispatch _el-button "click" nil) - (assert= (dom-text-content (dom-query-by-id "out")) "No") + (let ((_el-script (dom-create-element "script"))) + (dom-set-attr _el-script "type" "text/hyperscript") + (dom-set-inner-html _el-script "behavior TheBehaviorWeAreDefiningForHyperscriptTestingPurposes init log 'foo' end end") + (dom-append (dom-body) _el-script) + )) + (deftest "can install behaviors" + (hs-cleanup!) + (let ((_el-script (dom-create-element "script")) (_el-div (dom-create-element "div"))) + (dom-set-attr _el-script "type" "text/hyperscript") + (dom-set-inner-html _el-script "behavior Behave on click add .foo end end") + (dom-set-attr _el-div "_" "install Behave") + (dom-append (dom-body) _el-script) + (dom-append (dom-body) _el-div) + (hs-activate! _el-div) + (dom-dispatch _el-div "click" nil) + (assert (dom-has-class? _el-div "foo")) + )) + (deftest "can pass arguments to behaviors" + (hs-cleanup!) + (let ((_el-script (dom-create-element "script")) (_el-div (dom-create-element "div"))) + (dom-set-attr _el-script "type" "text/hyperscript") + (dom-set-inner-html _el-script "behavior Behave(foo, bar) on click put foo + bar into me end end") + (dom-set-attr _el-div "_" "install Behave(foo: 1, bar: 1)") + (dom-append (dom-body) _el-script) + (dom-append (dom-body) _el-div) + (hs-activate! _el-div) + (dom-dispatch _el-div "click" nil) + (assert= (dom-text-content _el-div) "2") + )) + (deftest "can pass element arguments to listen to in behaviors" + (hs-cleanup!) + (let ((_el-script (dom-create-element "script")) (_el-b1 (dom-create-element "button")) (_el-div (dom-create-element "div"))) + (dom-set-attr _el-script "type" "text/hyperscript") + (dom-set-inner-html _el-script "behavior Behave(elt) on click from elt put 'foo' into me end end") + (dom-set-attr _el-b1 "id" "b1") + (dom-set-attr _el-div "_" "install Behave(elt: #b1)") + (dom-append (dom-body) _el-script) + (dom-append (dom-body) _el-b1) + (dom-append (dom-body) _el-div) + (hs-activate! _el-div) + (dom-dispatch (dom-query-by-id "b1") "click" nil) + (assert= (dom-text-content _el-div) "foo") + )) + (deftest "can refer to arguments in init blocks" + (hs-cleanup!) + (let ((_el-script (dom-create-element "script")) (_el-d1 (dom-create-element "div")) (_el-div (dom-create-element "div"))) + (dom-set-attr _el-script "type" "text/hyperscript") + (dom-set-inner-html _el-script "behavior Behave(elt) init put 'foo' into elt end end") + (dom-set-attr _el-d1 "id" "d1") + (dom-set-attr _el-div "_" "install Behave(elt: #d1)") + (dom-append (dom-body) _el-script) + (dom-append (dom-body) _el-d1) + (dom-append (dom-body) _el-div) + (hs-activate! _el-div) + )) + (deftest "install resolves namespaced behavior paths" + (hs-cleanup!) + (let ((_el-script (dom-create-element "script")) (_el-div (dom-create-element "div"))) + (dom-set-attr _el-script "type" "text/hyperscript") + (dom-set-inner-html _el-script "behavior App.Widgets.Clickable on click add .clicked end end") + (dom-set-attr _el-div "_" "install App.Widgets.Clickable") + (dom-append (dom-body) _el-script) + (dom-append (dom-body) _el-div) + (hs-activate! _el-div) + (dom-dispatch _el-div "click" nil) + (assert (dom-has-class? _el-div "clicked")) + )) + (deftest "install throws when the behavior path does not exist" + (hs-cleanup!) + (let ((_el-div (dom-create-element "div"))) + (dom-set-attr _el-div "_" "install NoSuchBehavior") + (dom-append (dom-body) _el-div) + (hs-activate! _el-div) + )) + (deftest "install throws when the path resolves to a non-function" + (hs-cleanup!) + (let ((_el-div (dom-create-element "div"))) + (dom-set-attr _el-div "_" "install NotABehavior") + (dom-append (dom-body) _el-div) + (hs-activate! _el-div) + )) + (deftest "supports init blocks in behaviors" + (hs-cleanup!) + (let ((_el-script (dom-create-element "script")) (_el-div (dom-create-element "div"))) + (dom-set-attr _el-script "type" "text/hyperscript") + (dom-set-inner-html _el-script "behavior Behave init add .foo to me end") + (dom-set-attr _el-div "_" "install Behave") + (dom-append (dom-body) _el-script) + (dom-append (dom-body) _el-div) + (hs-activate! _el-div) )) ) -;; ── dialog (10 tests) ── -(defsuite "hs-upstream-dialog" - (deftest "show opens a dialog as modal" +;; ── bind (44 tests) ── +(defsuite "hs-upstream-bind" + (deftest "\"with\" is a synonym for \"and\"" (hs-cleanup!) - (let ((_el-d (dom-create-element "dialog")) (_el-p (dom-create-element "p")) (_el-button (dom-create-element "button"))) + (let ((_el-city-input (dom-create-element "input")) (_el-span (dom-create-element "span"))) + (dom-set-attr _el-city-input "id" "city-input") + (dom-set-attr _el-city-input "type" "text") + (dom-set-attr _el-city-input "value" "Paris") + (dom-set-attr _el-span "_" "bind $city to #city-input.value end when $city changes put it into me") + (dom-append (dom-body) _el-city-input) + (dom-append (dom-body) _el-span) + (hs-activate! _el-span) + )) + (deftest "attribute bound to another element input value" + (hs-cleanup!) + (let ((_el-title-input (dom-create-element "input")) (_el-h1 (dom-create-element "h1"))) + (dom-set-attr _el-title-input "id" "title-input") + (dom-set-attr _el-title-input "type" "text") + (dom-set-attr _el-title-input "value" "Hello") + (dom-set-attr _el-h1 "_" "bind @data-title and #title-input's value") + (dom-append (dom-body) _el-title-input) + (dom-append (dom-body) _el-h1) + (hs-activate! _el-h1) + (dom-set-prop (dom-query-by-id "title-input") "value" "World") + (dom-dispatch (dom-query-by-id "title-input") "input" nil) + )) + (deftest "bind element to element: both sides auto-detect" + (hs-cleanup!) + (let ((_el-range-slider (dom-create-element "input")) (_el-input (dom-create-element "input"))) + (dom-set-attr _el-range-slider "id" "range-slider") + (dom-set-attr _el-range-slider "type" "range") + (dom-set-attr _el-range-slider "value" "50") + (dom-set-attr _el-input "_" "bind me to #range-slider") + (dom-set-attr _el-input "type" "number") + (dom-append (dom-body) _el-range-slider) + (dom-append (dom-body) _el-input) + (hs-activate! _el-input) + )) + (deftest "bind to contenteditable element auto-detects textContent" + (hs-cleanup!) + (let ((_el-div (dom-create-element "div"))) + (dom-set-attr _el-div "_" "bind $text to me") + (dom-set-attr _el-div "contenteditable" "true") + (dom-set-inner-html _el-div "initial") + (dom-append (dom-body) _el-div) + (hs-activate! _el-div) + )) + (deftest "bind to custom element with value property auto-detects value" + (hs-cleanup!) + (let ((_el-test-input (dom-create-element "test-input"))) + (dom-set-attr _el-test-input "_" "bind $custom to me") + (dom-append (dom-body) _el-test-input) + (hs-activate! _el-test-input) + )) + (deftest "bind variable to checkbox by id auto-detects checked" + (hs-cleanup!) + (let ((_el-agree-cb (dom-create-element "input")) (_el-div (dom-create-element "div"))) + (dom-set-attr _el-agree-cb "id" "agree-cb") + (dom-set-attr _el-agree-cb "type" "checkbox") + (dom-set-attr _el-div "_" "bind $agreed to #agree-cb") + (dom-append (dom-body) _el-agree-cb) + (dom-append (dom-body) _el-div) + (hs-activate! _el-div) + )) + (deftest "bind variable to element by id auto-detects value" + (hs-cleanup!) + (let ((_el-name-field (dom-create-element "input")) (_el-div (dom-create-element "div"))) + (dom-set-attr _el-name-field "id" "name-field") + (dom-set-attr _el-name-field "type" "text") + (dom-set-attr _el-name-field "value" "") + (dom-set-attr _el-div "_" "bind $name to #name-field") + (dom-append (dom-body) _el-name-field) + (dom-append (dom-body) _el-div) + (hs-activate! _el-div) + )) + (deftest "bind variable to number input by id auto-detects valueAsNumber" + (hs-cleanup!) + (let ((_el-qty-input (dom-create-element "input")) (_el-div (dom-create-element "div"))) + (dom-set-attr _el-qty-input "id" "qty-input") + (dom-set-attr _el-qty-input "type" "number") + (dom-set-attr _el-div "_" "bind $qty to #qty-input") + (dom-append (dom-body) _el-qty-input) + (dom-append (dom-body) _el-div) + (hs-activate! _el-div) + )) + (deftest "boolean bind to aria-* attribute uses \"true\"/\"false\" strings" + (hs-cleanup!) + (let ((_el-div (dom-create-element "div"))) + (dom-set-attr _el-div "_" "bind $isHidden and @aria-hidden") + (dom-append (dom-body) _el-div) + (hs-activate! _el-div) + )) + (deftest "boolean bind to attribute uses presence/absence" + (hs-cleanup!) + (let ((_el-div (dom-create-element "div"))) + (dom-set-attr _el-div "_" "bind $isEnabled and @data-active") + (dom-append (dom-body) _el-div) + (hs-activate! _el-div) + )) + (deftest "class bound to another element checkbox" + (hs-cleanup!) + (let ((_el-dark-toggle (dom-create-element "input")) (_el-div (dom-create-element "div"))) + (dom-set-attr _el-dark-toggle "id" "dark-toggle") + (dom-set-attr _el-dark-toggle "type" "checkbox") + (dom-set-attr _el-div "_" "bind .dark and #dark-toggle's checked") + (dom-set-inner-html _el-div "test") + (dom-append (dom-body) _el-dark-toggle) + (dom-append (dom-body) _el-div) + (hs-activate! _el-div) + (dom-set-prop (dom-query-by-id "dark-toggle") "checked" true) + (dom-dispatch (dom-query-by-id "dark-toggle") "change" nil) + (dom-set-prop (dom-query-by-id "dark-toggle") "checked" false) + (dom-dispatch (dom-query-by-id "dark-toggle") "change" nil) + )) + (deftest "clicking a radio sets the variable to its value" + (hs-cleanup!) + (let ((_el-input (dom-create-element "input")) (_el-input1 (dom-create-element "input")) (_el-input2 (dom-create-element "input")) (_el-span (dom-create-element "span"))) + (dom-set-attr _el-input "_" "bind $color to me") + (dom-set-attr _el-input "type" "radio") + (dom-set-attr _el-input "name" "color") + (dom-set-attr _el-input "value" "red") + (dom-set-attr _el-input1 "_" "bind $color to me") + (dom-set-attr _el-input1 "type" "radio") + (dom-set-attr _el-input1 "name" "color") + (dom-set-attr _el-input1 "value" "blue") + (dom-set-attr _el-input2 "_" "bind $color to me") + (dom-set-attr _el-input2 "type" "radio") + (dom-set-attr _el-input2 "name" "color") + (dom-set-attr _el-input2 "value" "green") + (dom-set-attr _el-span "_" "when $color changes put it into me") + (dom-append (dom-body) _el-input) + (dom-append (dom-body) _el-input1) + (dom-append (dom-body) _el-input2) + (dom-append (dom-body) _el-span) + (hs-activate! _el-input) + (hs-activate! _el-input1) + (hs-activate! _el-input2) + (hs-activate! _el-span) + (dom-dispatch (dom-query "input[value="blue"]") "click" nil) + (dom-dispatch (dom-query "input[value="green"]") "click" nil) + )) + (deftest "dedup prevents infinite loop in two-way bind" + (hs-cleanup!) + (let ((_el-div (dom-create-element "div"))) + (dom-set-attr _el-div "_" "bind $color and @data-color") + (dom-append (dom-body) _el-div) + (hs-activate! _el-div) + )) + (deftest "external JS property write does not sync (known limitation)" + (hs-cleanup!) + (let ((_el-input (dom-create-element "input")) (_el-span (dom-create-element "span"))) + (dom-set-attr _el-input "_" "bind $searchTerm to me") + (dom-set-attr _el-input "type" "text") + (dom-set-attr _el-input "value" "original") + (dom-set-attr _el-span "_" "when $searchTerm changes put it into me") + (dom-append (dom-body) _el-input) + (dom-append (dom-body) _el-span) + (hs-activate! _el-input) + (hs-activate! _el-span) + )) + (deftest "external class change syncs back to variable" + (hs-cleanup!) + (let ((_el-div (dom-create-element "div"))) + (dom-set-attr _el-div "_" "bind .dark and $darkMode") + (dom-set-inner-html _el-div "test") + (dom-append (dom-body) _el-div) + (hs-activate! _el-div) + )) + (deftest "form reset listener is removed on cleanup" + (hs-cleanup!) + (let ((_el-form (dom-create-element "form")) (_el-binput (dom-create-element "input")) (_el-button (dom-create-element "button"))) + (dom-set-attr _el-binput "id" "binput") + (dom-set-attr _el-binput "_" "bind $val to me") + (dom-set-attr _el-binput "type" "text") + (dom-set-attr _el-binput "value" "initial") + (dom-set-attr _el-button "type" "reset") + (dom-set-inner-html _el-button "Reset") + (dom-append (dom-body) _el-form) + (dom-append _el-form _el-binput) + (dom-append _el-form _el-button) + (hs-activate! _el-binput) + )) + (deftest "form.reset() syncs variable back to default value" + (hs-cleanup!) + (let ((_el-test-form (dom-create-element "form")) (_el-input (dom-create-element "input")) (_el-span (dom-create-element "span"))) + (dom-set-attr _el-test-form "id" "test-form") + (dom-set-attr _el-input "_" "bind $formField to me") + (dom-set-attr _el-input "type" "text") + (dom-set-attr _el-input "value" "default") + (dom-set-attr _el-span "_" "when $formField changes put it into me") + (dom-append (dom-body) _el-test-form) + (dom-append _el-test-form _el-input) + (dom-append (dom-body) _el-span) + (hs-activate! _el-input) + (hs-activate! _el-span) + (dom-set-prop _el-input "value" "user typed this") + (dom-dispatch _el-input "input" nil) + )) + (deftest "init: right side wins - attribute (Y) initializes variable (X)" + (hs-cleanup!) + (let ((_el-div (dom-create-element "div"))) + (dom-set-attr _el-div "_" "bind $color to @data-color") + (dom-set-attr _el-div "data-color" "red") + (dom-append (dom-body) _el-div) + (hs-activate! _el-div) + )) + (deftest "init: right side wins - class (Y) drives variable (X)" + (hs-cleanup!) + (let ((_el-div (dom-create-element "div"))) + (dom-add-class _el-div "dark") + (dom-set-attr _el-div "_" "bind $isDark to .dark") + (dom-append (dom-body) _el-div) + (hs-activate! _el-div) + )) + (deftest "init: right side wins - input value (Y) overwrites variable (X)" + (hs-cleanup!) + (let ((_el-input (dom-create-element "input"))) + (dom-set-attr _el-input "_" "bind $name to my value") + (dom-set-attr _el-input "type" "text") + (dom-set-attr _el-input "value" "Bob") + (dom-append (dom-body) _el-input) + (hs-activate! _el-input) + )) + (deftest "init: right side wins - variable (Y) drives class (X)" + (hs-cleanup!) + (let ((_el-div (dom-create-element "div"))) + (dom-set-attr _el-div "_" "bind .dark to $isDark") + (dom-append (dom-body) _el-div) + (hs-activate! _el-div) + )) + (deftest "init: right side wins - variable (Y) initializes attribute (X)" + (hs-cleanup!) + (let ((_el-div (dom-create-element "div"))) + (dom-set-attr _el-div "_" "bind @data-theme to $theme") + (dom-append (dom-body) _el-div) + (hs-activate! _el-div) + )) + (deftest "init: right side wins - variable (Y) overwrites input value (X)" + (hs-cleanup!) + (let ((_el-input (dom-create-element "input"))) + (dom-set-attr _el-input "_" "bind my value to $name") + (dom-set-attr _el-input "type" "text") + (dom-set-attr _el-input "value" "Bob") + (dom-append (dom-body) _el-input) + (hs-activate! _el-input) + )) + (deftest "initial value checks the correct radio on load" + (hs-cleanup!) + (let ((_el-input (dom-create-element "input")) (_el-input1 (dom-create-element "input")) (_el-input2 (dom-create-element "input"))) + (dom-set-attr _el-input "_" "bind $fruit to me") + (dom-set-attr _el-input "type" "radio") + (dom-set-attr _el-input "name" "fruit") + (dom-set-attr _el-input "value" "apple") + (dom-set-attr _el-input1 "_" "bind $fruit to me") + (dom-set-attr _el-input1 "type" "radio") + (dom-set-attr _el-input1 "name" "fruit") + (dom-set-attr _el-input1 "value" "banana") + (dom-set-attr _el-input2 "_" "bind $fruit to me") + (dom-set-attr _el-input2 "type" "radio") + (dom-set-attr _el-input2 "name" "fruit") + (dom-set-attr _el-input2 "value" "cherry") + (dom-append (dom-body) _el-input) + (dom-append (dom-body) _el-input1) + (dom-append (dom-body) _el-input2) + (hs-activate! _el-input) + (hs-activate! _el-input1) + (hs-activate! _el-input2) + )) + (deftest "of-expression: bind $var to value of #input" + (hs-cleanup!) + (let ((_el-of-input (dom-create-element "input")) (_el-div (dom-create-element "div"))) + (dom-set-attr _el-of-input "id" "of-input") + (dom-set-attr _el-of-input "type" "text") + (dom-set-attr _el-of-input "value" "initial") + (dom-set-attr _el-div "_" "bind $search to value of #of-input") + (dom-append (dom-body) _el-of-input) + (dom-append (dom-body) _el-div) + (hs-activate! _el-div) + )) + (deftest "possessive attribute: bind $var and my @data-label" + (hs-cleanup!) + (let ((_el-div (dom-create-element "div"))) + (dom-set-attr _el-div "_" "bind $label and my @data-label") + (dom-append (dom-body) _el-div) + (hs-activate! _el-div) + )) + (deftest "possessive property: bind $var to my value" + (hs-cleanup!) + (let ((_el-input (dom-create-element "input"))) + (dom-set-attr _el-input "_" "bind $myVal to my value") + (dom-set-attr _el-input "type" "text") + (dom-set-attr _el-input "value" "hello") + (dom-append (dom-body) _el-input) + (hs-activate! _el-input) + (dom-set-prop _el-input "value" "world") + (dom-dispatch _el-input "input" nil) + )) + (deftest "radio change listener is removed on cleanup" + (hs-cleanup!) + (let ((_el-input (dom-create-element "input")) (_el-input1 (dom-create-element "input"))) + (dom-set-attr _el-input "_" "bind $color to me") + (dom-set-attr _el-input "type" "radio") + (dom-set-attr _el-input "name" "color") + (dom-set-attr _el-input "value" "red") + (dom-set-attr _el-input "checked" "") + (dom-set-attr _el-input1 "_" "bind $color to me") + (dom-set-attr _el-input1 "type" "radio") + (dom-set-attr _el-input1 "name" "color") + (dom-set-attr _el-input1 "value" "blue") + (dom-append (dom-body) _el-input) + (dom-append (dom-body) _el-input1) + (hs-activate! _el-input) + (hs-activate! _el-input1) + )) + (deftest "right side wins on class init" + (hs-cleanup!) + (let ((_el-div (dom-create-element "div"))) + (dom-set-attr _el-div "_" "bind .highlight to $highlighted") + (dom-set-inner-html _el-div "test") + (dom-append (dom-body) _el-div) + (hs-activate! _el-div) + )) + (deftest "right side wins on init: input (Y) initializes variable (X)" + (hs-cleanup!) + (let ((_el-input (dom-create-element "input"))) + (dom-set-attr _el-input "_" "bind $name to me") + (dom-set-attr _el-input "type" "text") + (dom-set-attr _el-input "value" "Bob") + (dom-append (dom-body) _el-input) + (hs-activate! _el-input) + )) + (deftest "right side wins on init: variable (Y) initializes input (X)" + (hs-cleanup!) + (let ((_el-input (dom-create-element "input"))) + (dom-set-attr _el-input "_" "bind me to $name") + (dom-set-attr _el-input "type" "text") + (dom-set-attr _el-input "value" "Bob") + (dom-append (dom-body) _el-input) + (hs-activate! _el-input) + )) + (deftest "same value does not re-set input (prevents cursor jump)" + (hs-cleanup!) + (let ((_el-input (dom-create-element "input"))) + (dom-set-attr _el-input "_" "bind $message to me") + (dom-set-attr _el-input "type" "text") + (dom-set-attr _el-input "value" "hello") + (dom-append (dom-body) _el-input) + (hs-activate! _el-input) + )) + (deftest "setting variable programmatically checks the matching radio" + (hs-cleanup!) + (let ((_el-input (dom-create-element "input")) (_el-input1 (dom-create-element "input")) (_el-input2 (dom-create-element "input"))) + (dom-set-attr _el-input "_" "bind $size to me") + (dom-set-attr _el-input "type" "radio") + (dom-set-attr _el-input "name" "size") + (dom-set-attr _el-input "value" "small") + (dom-set-attr _el-input1 "_" "bind $size to me") + (dom-set-attr _el-input1 "type" "radio") + (dom-set-attr _el-input1 "name" "size") + (dom-set-attr _el-input1 "value" "medium") + (dom-set-attr _el-input2 "_" "bind $size to me") + (dom-set-attr _el-input2 "type" "radio") + (dom-set-attr _el-input2 "name" "size") + (dom-set-attr _el-input2 "value" "large") + (dom-append (dom-body) _el-input) + (dom-append (dom-body) _el-input1) + (dom-append (dom-body) _el-input2) + (hs-activate! _el-input) + (hs-activate! _el-input1) + (hs-activate! _el-input2) + )) + (deftest "shorthand on checkbox binds to checked" + (hs-cleanup!) + (let ((_el-input (dom-create-element "input")) (_el-span (dom-create-element "span"))) + (dom-set-attr _el-input "_" "bind $isDarkMode to me") + (dom-set-attr _el-input "type" "checkbox") + (dom-set-attr _el-span "_" "when $isDarkMode changes put it into me") + (dom-append (dom-body) _el-input) + (dom-append (dom-body) _el-span) + (hs-activate! _el-input) + (hs-activate! _el-span) + (dom-set-prop _el-input "checked" true) + (dom-dispatch _el-input "change" nil) + )) + (deftest "shorthand on select binds to value" + (hs-cleanup!) + (let ((_el-select (dom-create-element "select")) (_el-option (dom-create-element "option")) (_el-option2 (dom-create-element "option")) (_el-option3 (dom-create-element "option")) (_el-span (dom-create-element "span"))) + (dom-set-attr _el-select "_" "bind $country to me") + (dom-set-attr _el-option "value" "us") + (dom-set-inner-html _el-option "United States") + (dom-set-attr _el-option2 "value" "uk") + (dom-set-inner-html _el-option2 "United Kingdom") + (dom-set-attr _el-option3 "value" "fr") + (dom-set-inner-html _el-option3 "France") + (dom-set-attr _el-span "_" "when $country changes put it into me") + (dom-append (dom-body) _el-select) + (dom-append _el-select _el-option) + (dom-append _el-select _el-option2) + (dom-append _el-select _el-option3) + (dom-append (dom-body) _el-span) + (hs-activate! _el-select) + (hs-activate! _el-span) + (dom-set-prop _el-select "value" "uk") + (dom-dispatch _el-select "change" nil) + )) + (deftest "shorthand on text input binds to value" + (hs-cleanup!) + (let ((_el-input (dom-create-element "input")) (_el-span (dom-create-element "span"))) + (dom-set-attr _el-input "_" "bind $greeting to me end when $greeting changes put it into next ") + (dom-set-attr _el-input "type" "text") + (dom-set-attr _el-input "value" "hello") + (dom-append (dom-body) _el-input) + (dom-append (dom-body) _el-span) + (hs-activate! _el-input) + (dom-set-prop _el-input "value" "goodbye") + (dom-dispatch _el-input "input" nil) + )) + (deftest "shorthand on textarea binds to value" + (hs-cleanup!) + (let ((_el-textarea (dom-create-element "textarea")) (_el-span (dom-create-element "span"))) + (dom-set-attr _el-textarea "_" "bind $bio to me") + (dom-set-inner-html _el-textarea "Hello world") + (dom-set-attr _el-span "_" "when $bio changes put it into me") + (dom-append (dom-body) _el-textarea) + (dom-append (dom-body) _el-span) + (hs-activate! _el-textarea) + (hs-activate! _el-span) + (dom-set-prop _el-textarea "value" "New bio") + (dom-dispatch _el-textarea "input" nil) + )) + (deftest "shorthand on type=number preserves number type" + (hs-cleanup!) + (let ((_el-input (dom-create-element "input")) (_el-span (dom-create-element "span"))) + (dom-set-attr _el-input "_" "bind $price to me") + (dom-set-attr _el-input "type" "number") + (dom-set-attr _el-span "_" "when $price changes put it into me") + (dom-append (dom-body) _el-input) + (dom-append (dom-body) _el-span) + (hs-activate! _el-input) + (hs-activate! _el-span) + )) + (deftest "style bind is one-way: variable drives style, not vice versa" + (hs-cleanup!) + (let ((_el-div (dom-create-element "div"))) + (dom-set-attr _el-div "_" "bind $opacity and *opacity") + (dom-set-inner-html _el-div "visible") + (dom-append (dom-body) _el-div) + (hs-activate! _el-div) + )) + (deftest "syncs variable and attribute in both directions" + (hs-cleanup!) + (let ((_el-div (dom-create-element "div"))) + (dom-set-attr _el-div "_" "bind $theme and @data-theme") + (dom-append (dom-body) _el-div) + (hs-activate! _el-div) + )) + (deftest "syncs variable and input value in both directions" + (hs-cleanup!) + (let ((_el-name-input (dom-create-element "input")) (_el-span (dom-create-element "span"))) + (dom-set-attr _el-name-input "id" "name-input") + (dom-set-attr _el-name-input "type" "text") + (dom-set-attr _el-name-input "value" "Alice") + (dom-set-attr _el-span "_" "bind $name and #name-input.value end when $name changes put it into me") + (dom-append (dom-body) _el-name-input) + (dom-append (dom-body) _el-span) + (hs-activate! _el-span) + )) + (deftest "two inputs synced via bind" + (hs-cleanup!) + (let ((_el-slider (dom-create-element "input")) (_el-input (dom-create-element "input"))) + (dom-set-attr _el-slider "id" "slider") + (dom-set-attr _el-slider "type" "range") + (dom-set-attr _el-slider "value" "50") + (dom-set-attr _el-input "_" "bind my value and #slider's value") + (dom-set-attr _el-input "type" "number") + (dom-append (dom-body) _el-slider) + (dom-append (dom-body) _el-input) + (hs-activate! _el-input) + )) + (deftest "unsupported element: bind to plain div errors" + (error "SKIP (untranslated): unsupported element: bind to plain div errors")) + (deftest "variable drives class: setting variable adds/removes class" + (hs-cleanup!) + (let ((_el-div (dom-create-element "div"))) + (dom-set-attr _el-div "_" "bind .dark and $darkMode") + (dom-set-inner-html _el-div "test") + (dom-append (dom-body) _el-div) + (hs-activate! _el-div) + )) +) + +;; ── breakpoint (2 tests) ── +(defsuite "hs-upstream-breakpoint" + (deftest "parses as a top-level command" + (error "SKIP (untranslated): parses as a top-level command")) + (deftest "parses inside an event handler" + (error "SKIP (untranslated): parses inside an event handler")) +) + +;; ── call (6 tests) ── +(defsuite "hs-upstream-call" + (deftest "call functions that return promises are waited on" + (hs-cleanup!) + (let ((_el-div (dom-create-element "div"))) + (dom-set-attr _el-div "_" "on click call promiseAnInt() then put it into my.innerHTML") + (dom-append (dom-body) _el-div) + (hs-activate! _el-div) + (dom-dispatch _el-div "click" nil) + (assert= (dom-text-content _el-div) "42") + )) + (deftest "can call functions w/ dollar signs" + (hs-cleanup!) + (let ((_el-div (dom-create-element "div"))) + (dom-set-attr _el-div "_" "on click call $()") + (dom-append (dom-body) _el-div) + (hs-activate! _el-div) + (dom-dispatch _el-div "click" nil) + )) + (deftest "can call functions w/ underscores" + (hs-cleanup!) + (let ((_el-div (dom-create-element "div"))) + (dom-set-attr _el-div "_" "on click call global_function()") + (dom-append (dom-body) _el-div) + (hs-activate! _el-div) + (dom-dispatch _el-div "click" nil) + )) + (deftest "can call global javascript functions" + (hs-cleanup!) + (let ((_el-div (dom-create-element "div"))) + (dom-set-attr _el-div "_" "on click call globalFunction(\"foo\")") + (dom-append (dom-body) _el-div) + (hs-activate! _el-div) + (dom-dispatch _el-div "click" nil) + )) + (deftest "can call javascript instance functions" + (hs-cleanup!) + (let ((_el-d1 (dom-create-element "div"))) + (dom-set-attr _el-d1 "id" "d1") + (dom-set-attr _el-d1 "_" "on click call document.getElementById(\"d1\") then put it into window.results") + (dom-append (dom-body) _el-d1) + (hs-activate! _el-d1) + (dom-dispatch (dom-query-by-id "d1") "click" nil) + )) + (deftest "can call no argument functions" + (hs-cleanup!) + (let ((_el-div (dom-create-element "div"))) + (dom-set-attr _el-div "_" "on click call globalFunction()") + (dom-append (dom-body) _el-div) + (hs-activate! _el-div) + (dom-dispatch _el-div "click" nil) + )) +) + +;; ── core/api (1 tests) ── +(defsuite "hs-upstream-core/api" + (deftest "processNodes does not reinitialize a node already processed" + (hs-cleanup!) + (let ((_el-div (dom-create-element "div"))) + (dom-set-attr _el-div "_" "on click set window.global_int to window.global_int + 1") + (dom-append (dom-body) _el-div) + (hs-activate! _el-div) + (dom-dispatch _el-div "click" nil) + (dom-dispatch _el-div "click" nil) + )) +) + +;; ── core/asyncError (2 tests) ── +(defsuite "hs-upstream-core/asyncError" + (deftest "rejected promise stops execution" + (hs-cleanup!) + (let ((_el-button (dom-create-element "button")) (_el-out (dom-create-element "div"))) + (dom-set-attr _el-button "_" "on click call failAsync() then put 'should not reach' into #out then") + (dom-set-inner-html _el-button "Go") + (dom-set-attr _el-out "id" "out") + (dom-set-inner-html _el-out "original") + (dom-append (dom-body) _el-button) + (dom-append (dom-body) _el-out) + (hs-activate! _el-button) + (dom-dispatch _el-button "click" nil) + (assert= (dom-text-content (dom-query-by-id "out")) "original") + )) + (deftest "rejected promise triggers catch block" + (hs-cleanup!) + (let ((_el-button (dom-create-element "button")) (_el-out (dom-create-element "div"))) + (dom-set-attr _el-button "_" "on click call failAsync() then put 'unreachable' into #out then catch e then put e.message into #out then") + (dom-set-inner-html _el-button "Go") + (dom-set-attr _el-out "id" "out") + (dom-append (dom-body) _el-button) + (dom-append (dom-body) _el-out) + (hs-activate! _el-button) + (dom-dispatch _el-button "click" nil) + (assert= (dom-text-content (dom-query-by-id "out")) "boom") + )) +) + +;; ── core/bootstrap (26 tests) ── +(defsuite "hs-upstream-core/bootstrap" + (deftest "can call functions" + (hs-cleanup!) + (let ((_el-div (dom-create-element "div"))) + (dom-set-attr _el-div "_" "on click call globalFunction(\"foo\")") + (dom-append (dom-body) _el-div) + (hs-activate! _el-div) + (dom-dispatch _el-div "click" nil) + )) + (deftest "can change non-class properties" + (hs-cleanup!) + (let ((_el-div (dom-create-element "div"))) + (dom-set-attr _el-div "_" "on click add [@foo=\"bar\"]") + (dom-append (dom-body) _el-div) + (hs-activate! _el-div) + (dom-dispatch _el-div "click" nil) + (assert= (dom-get-attr _el-div "foo") "bar") + )) + (deftest "can respond to events on other elements" + (hs-cleanup!) + (let ((_el-bar (dom-create-element "div")) (_el-div (dom-create-element "div"))) + (dom-set-attr _el-bar "id" "bar") + (dom-set-attr _el-div "_" "on click from #bar then add .clicked") + (dom-append (dom-body) _el-bar) + (dom-append (dom-body) _el-div) + (hs-activate! _el-div) + (dom-dispatch (dom-query-by-id "bar") "click" nil) + (assert (dom-has-class? (dom-query "div:nth-of-type(2)") "clicked")) + )) + (deftest "can send events" + (hs-cleanup!) + (let ((_el-div (dom-create-element "div")) (_el-bar (dom-create-element "div"))) + (dom-set-attr _el-div "_" "on click send foo to #bar") + (dom-set-attr _el-bar "id" "bar") + (dom-set-attr _el-bar "_" "on foo add .foo-sent") + (dom-append (dom-body) _el-div) + (dom-append (dom-body) _el-bar) + (hs-activate! _el-div) + (hs-activate! _el-bar) + (dom-dispatch _el-div "click" nil) + (assert (dom-has-class? (dom-query-by-id "bar") "foo-sent")) + )) + (deftest "can send events with args" + (hs-cleanup!) + (let ((_el-div (dom-create-element "div")) (_el-bar (dom-create-element "div"))) + (dom-set-attr _el-div "_" "on click send foo(x:42) to #bar") + (dom-set-attr _el-bar "id" "bar") + (dom-set-attr _el-bar "_" "on foo put event.detail.x into my.innerHTML") + (dom-append (dom-body) _el-div) + (dom-append (dom-body) _el-bar) + (hs-activate! _el-div) + (hs-activate! _el-bar) + (dom-dispatch _el-div "click" nil) + (assert= (dom-text-content (dom-query-by-id "bar")) "42") + )) + (deftest "can set properties" + (hs-cleanup!) + (let ((_el-d1 (dom-create-element "div"))) + (dom-set-attr _el-d1 "id" "d1") + (dom-set-attr _el-d1 "_" "on click put \"foo\" into #d1.innerHTML") + (dom-append (dom-body) _el-d1) + (hs-activate! _el-d1) + (dom-dispatch (dom-query-by-id "d1") "click" nil) + (assert= (dom-text-content (dom-query-by-id "d1")) "foo") + )) + (deftest "can set styles" + (hs-cleanup!) + (let ((_el-div (dom-create-element "div"))) + (dom-set-attr _el-div "_" "on click put \"red\" into my.style.color") + (dom-set-inner-html _el-div "lolwat") + (dom-append (dom-body) _el-div) + (hs-activate! _el-div) + (dom-dispatch _el-div "click" nil) + (assert= (dom-get-style _el-div "color") "") + )) + (deftest "can take a class from other elements" + (hs-cleanup!) + (let ((_el-div (dom-create-element "div")) (_el-div1 (dom-create-element "div")) (_el-div2 (dom-create-element "div"))) + (dom-add-class _el-div "divs") + (dom-add-class _el-div "foo") + (dom-add-class _el-div1 "divs") + (dom-set-attr _el-div1 "_" "on click take .foo from .divs") + (dom-add-class _el-div2 "divs") + (dom-append (dom-body) _el-div) + (dom-append (dom-body) _el-div1) + (dom-append (dom-body) _el-div2) + (hs-activate! _el-div1) + )) + (deftest "can target another div" + (hs-cleanup!) + (let ((_el-bar (dom-create-element "div")) (_el-div (dom-create-element "div"))) + (dom-set-attr _el-bar "id" "bar") + (dom-set-attr _el-div "_" "on click add .foo to #bar") + (dom-append (dom-body) _el-bar) + (dom-append (dom-body) _el-div) + (hs-activate! _el-div) + (dom-dispatch (dom-query "div:nth-of-type(2)") "click" nil) + (assert (dom-has-class? (dom-query-by-id "bar") "foo")) + )) + (deftest "can wait" + (hs-cleanup!) + (let ((_el-div (dom-create-element "div"))) + (dom-set-attr _el-div "_" "on click add .foo then wait 20ms then add .bar") + (dom-append (dom-body) _el-div) + (hs-activate! _el-div) + (dom-dispatch _el-div "click" nil) + (assert (dom-has-class? _el-div "foo")) + (assert (dom-has-class? _el-div "bar")) + )) + (deftest "cleanup clears elt._hyperscript" + (hs-cleanup!) + (let ((_el-div (dom-create-element "div"))) + (dom-set-attr _el-div "_" "on click add .foo") + (dom-append (dom-body) _el-div) + (hs-activate! _el-div) + )) + (deftest "cleanup removes cross-element event listeners" + (hs-cleanup!) + (let ((_el-source (dom-create-element "div")) (_el-target (dom-create-element "div"))) + (dom-set-attr _el-source "id" "source") + (dom-set-attr _el-target "id" "target") + (dom-set-attr _el-target "_" "on click from #source add .foo") + (dom-append (dom-body) _el-source) + (dom-append (dom-body) _el-target) + (hs-activate! _el-target) + (dom-dispatch (dom-query-by-id "source") "click" nil) + (assert (dom-has-class? (dom-query-by-id "target") "foo")) + )) + (deftest "cleanup removes data-hyperscript-powered" + (hs-cleanup!) + (let ((_el-div (dom-create-element "div"))) + (dom-set-attr _el-div "_" "on click add .foo") + (dom-append (dom-body) _el-div) + (hs-activate! _el-div) + )) + (deftest "cleanup removes event listeners on the element" + (hs-cleanup!) + (let ((_el-div (dom-create-element "div"))) + (dom-set-attr _el-div "_" "on click add .foo") + (dom-append (dom-body) _el-div) + (hs-activate! _el-div) + (dom-dispatch _el-div "click" nil) + (assert (dom-has-class? _el-div "foo")) + (assert (not (dom-has-class? _el-div "foo"))) + )) + (deftest "cleanup tracks listeners in elt._hyperscript" + (hs-cleanup!) + (let ((_el-div (dom-create-element "div"))) + (dom-set-attr _el-div "_" "on click add .foo") + (dom-append (dom-body) _el-div) + (hs-activate! _el-div) + )) + (deftest "fires hyperscript:before:cleanup and hyperscript:after:cleanup" + (hs-cleanup!) + (let ((_el-div (dom-create-element "div"))) + (dom-set-attr _el-div "_" "on click add .foo") + (dom-append (dom-body) _el-div) + (hs-activate! _el-div) + )) + (deftest "fires hyperscript:before:init and hyperscript:after:init" + (error "SKIP (untranslated): fires hyperscript:before:init and hyperscript:after:init")) + (deftest "hyperscript can have more than one action" + (hs-cleanup!) + (let ((_el-bar (dom-create-element "div")) (_el-div (dom-create-element "div"))) + (dom-set-attr _el-bar "id" "bar") + (dom-set-attr _el-div "_" "on click add .foo to #bar then add .blah") + (dom-append (dom-body) _el-bar) + (dom-append (dom-body) _el-div) + (hs-activate! _el-div) + (dom-dispatch (dom-query "div:nth-of-type(2)") "click" nil) + (assert (dom-has-class? (dom-query-by-id "bar") "foo")) + (assert (not (dom-has-class? (dom-query-by-id "bar") "blah"))) + (assert (not (dom-has-class? (dom-query "div:nth-of-type(2)") "foo"))) + (assert (dom-has-class? (dom-query "div:nth-of-type(2)") "blah")) + )) + (deftest "hyperscript:before:init can cancel initialization" + (error "SKIP (untranslated): hyperscript:before:init can cancel initialization")) + (deftest "logAll config logs events to console" + (error "SKIP (untranslated): logAll config logs events to console")) + (deftest "on a single div" + (hs-cleanup!) + (let ((_el-div (dom-create-element "div"))) + (dom-set-attr _el-div "_" "on click add .foo") + (dom-append (dom-body) _el-div) + (hs-activate! _el-div) + (dom-dispatch _el-div "click" nil) + (assert (dom-has-class? _el-div "foo")) + )) + (deftest "reinitializes if script attribute changes" + (hs-cleanup!) + (let ((_el-div (dom-create-element "div"))) + (dom-set-attr _el-div "_" "on click add .foo") + (dom-append (dom-body) _el-div) + (hs-activate! _el-div) + (dom-dispatch _el-div "click" nil) + (assert (dom-has-class? _el-div "foo")) + (dom-dispatch _el-div "click" nil) + (assert (dom-has-class? _el-div "bar")) + )) + (deftest "sets data-hyperscript-powered on initialized elements" + (hs-cleanup!) + (let ((_el-div (dom-create-element "div"))) + (dom-set-attr _el-div "_" "on click add .foo") + (dom-append (dom-body) _el-div) + (hs-activate! _el-div) + )) + (deftest "skips reinitialization if script unchanged" + (hs-cleanup!) + (let ((_el-div (dom-create-element "div"))) + (dom-set-attr _el-div "_" "on click add .foo") + (dom-append (dom-body) _el-div) + (hs-activate! _el-div) + )) + (deftest "stores state on elt._hyperscript" + (hs-cleanup!) + (let ((_el-div (dom-create-element "div"))) + (dom-set-attr _el-div "_" "on click add .foo") + (dom-append (dom-body) _el-div) + (hs-activate! _el-div) + )) + (deftest "toggles" + (hs-cleanup!) + (let ((_el-div (dom-create-element "div"))) + (dom-set-attr _el-div "_" "on click toggle .foo") + (dom-append (dom-body) _el-div) + (hs-activate! _el-div) + (dom-dispatch _el-div "click" nil) + (assert (dom-has-class? _el-div "foo")) + (dom-dispatch _el-div "click" nil) + (assert (not (dom-has-class? _el-div "foo"))) + )) +) + +;; ── core/dom-scope (5 tests) ── +(defsuite "hs-upstream-core/dom-scope" + (deftest "closest jumps to matching ancestor" + (hs-cleanup!) + (let ((_el-div (dom-create-element "div")) (_el-div1 (dom-create-element "div")) (_el-span (dom-create-element "span"))) + (dom-add-class _el-div "outer") + (dom-set-attr _el-div "_" "init set ^val to 'from-outer'") + (dom-set-attr _el-div1 "_" "init set ^val to 'from-inner'") + (dom-set-attr _el-div1 "dom-scope" "isolated") + (dom-set-attr _el-span "_" "init put ^val into me") + (dom-set-attr _el-span "dom-scope" "closest .outer") + (dom-set-inner-html _el-span "none") + (dom-append (dom-body) _el-div) + (dom-append _el-div _el-div1) + (dom-append _el-div1 _el-span) + (hs-activate! _el-div) + (hs-activate! _el-div1) + (hs-activate! _el-span) + )) + (deftest "closest with no match stops resolution" + (hs-cleanup!) + (let ((_el-div (dom-create-element "div")) (_el-span (dom-create-element "span"))) + (dom-set-attr _el-div "_" "init set ^val to 'found'") + (dom-set-attr _el-span "_" "init if ^val is not undefined put 'leaked' into me else put 'blocked' into me") + (dom-set-attr _el-span "dom-scope" "closest .nonexistent") + (dom-set-inner-html _el-span "waiting") + (dom-append (dom-body) _el-div) + (dom-append _el-div _el-span) + (hs-activate! _el-div) + (hs-activate! _el-span) + )) + (deftest "isolated allows setting ^var on the isolated element itself" + (hs-cleanup!) + (let ((_el-div (dom-create-element "div")) (_el-div1 (dom-create-element "div")) (_el-span (dom-create-element "span"))) + (dom-set-attr _el-div "_" "init set ^outer to 'leaked'") + (dom-set-attr _el-div1 "_" "init set ^inner to 'contained'") + (dom-set-attr _el-div1 "dom-scope" "isolated") + (dom-set-attr _el-span "_" "init put ^inner into me") + (dom-set-inner-html _el-span "none") + (dom-append (dom-body) _el-div) + (dom-append _el-div _el-div1) + (dom-append _el-div1 _el-span) + (hs-activate! _el-div) + (hs-activate! _el-div1) + (hs-activate! _el-span) + )) + (deftest "isolated stops ^var resolution" + (hs-cleanup!) + (let ((_el-div (dom-create-element "div")) (_el-div1 (dom-create-element "div")) (_el-span (dom-create-element "span"))) + (dom-set-attr _el-div "_" "init set ^color to 'red'") + (dom-set-attr _el-div1 "dom-scope" "isolated") + (dom-set-attr _el-span "_" "init if ^color is not undefined put 'leaked' into me else put 'blocked' into me") + (dom-set-inner-html _el-span "waiting") + (dom-append (dom-body) _el-div) + (dom-append _el-div _el-div1) + (dom-append _el-div1 _el-span) + (hs-activate! _el-div) + (hs-activate! _el-span) + )) + (deftest "parent of jumps past matching ancestor to its parent" + (hs-cleanup!) + (let ((_el-div (dom-create-element "div")) (_el-div1 (dom-create-element "div")) (_el-span (dom-create-element "span"))) + (dom-add-class _el-div "outer") + (dom-set-attr _el-div "_" "init set ^val to 'from-outer'") + (dom-add-class _el-div1 "middle") + (dom-set-attr _el-div1 "_" "init set ^val to 'from-middle'") + (dom-set-attr _el-div1 "dom-scope" "isolated") + (dom-set-attr _el-span "_" "init put ^val into me") + (dom-set-attr _el-span "dom-scope" "parent of .middle") + (dom-set-inner-html _el-span "none") + (dom-append (dom-body) _el-div) + (dom-append _el-div _el-div1) + (dom-append _el-div1 _el-span) + (hs-activate! _el-div) + (hs-activate! _el-div1) + (hs-activate! _el-span) + )) +) + +;; ── core/evalStatically (8 tests) ── +(defsuite "hs-upstream-core/evalStatically" + (deftest "throws on math expressions" + (error "SKIP (untranslated): throws on math expressions")) + (deftest "throws on symbol references" + (error "SKIP (untranslated): throws on symbol references")) + (deftest "throws on template strings" + (error "SKIP (untranslated): throws on template strings")) + (deftest "works on boolean literals" + (assert= (eval-hs "true") true) + (assert= (eval-hs "false") false) + ) + (deftest "works on null literal" + (assert= (eval-hs "null") nil) + ) + (deftest "works on number literals" + (assert= (eval-hs "42") 42) + (assert= (eval-hs "3.14") 3.14) + ) + (deftest "works on plain string literals" + (assert= (eval-hs "\"hello\"") "hello") + (assert= (eval-hs "'world'") "world") + ) + (deftest "works on time expressions" + (assert= (eval-hs "200ms") 200) + (assert= (eval-hs "2s") 2000) + ) +) + +;; ── core/liveTemplate (16 tests) ── +(defsuite "hs-upstream-core/liveTemplate" + (deftest "applies init script from _ attribute" + (hs-cleanup!) + (let ((_el-script (dom-create-element "script"))) + (dom-set-attr _el-script "_" "init set ^msg to 'initialized'") + (dom-set-attr _el-script "type" "text/hyperscript-template") + (dom-set-inner-html _el-script "${}{^msg}") + (dom-append (dom-body) _el-script) + (hs-activate! _el-script) + )) + (deftest "loop index variable is captured alongside loop variable" + (hs-cleanup!) + (let ((_el-script (dom-create-element "script"))) + (dom-set-attr _el-script "type" "text/hyperscript-template") + (dom-set-inner-html _el-script "

    + #for item in $idxItems index i +
  • ${}{item}
  • + #end +
") + (dom-append (dom-body) _el-script) + )) + (deftest "loop variable capture works with remove for live list" + (hs-cleanup!) + (let ((_el-script (dom-create-element "script"))) + (dom-set-attr _el-script "type" "text/hyperscript-template") + (dom-set-inner-html _el-script "
    + #for item in $removeItems +
  • ${}{item.name}
  • + #end +
") + (dom-append (dom-body) _el-script) + (dom-dispatch (dom-query "[data-live-template] li').nth(1).locator('button") "click" nil) + (assert= (dom-text-content (dom-query "[data-live-template] li').first().locator('span")) "A") + (assert= (dom-text-content (dom-query "[data-live-template] li').last().locator('span")) "C") + )) + (deftest "loop variables are captured and available in _= handlers" + (hs-cleanup!) + (let ((_el-script (dom-create-element "script"))) + (dom-set-attr _el-script "type" "text/hyperscript-template") + (dom-set-inner-html _el-script "
    + #for item in $captureItems index i +
  • ${}{item.name}
  • + #end +
") + (dom-append (dom-body) _el-script) + )) + (deftest "multiple live templates are independent" + (hs-cleanup!) + (let ((_el-script (dom-create-element "script")) (_el-script1 (dom-create-element "script"))) + (dom-set-attr _el-script "_" "init set ^x to 'first'") + (dom-set-attr _el-script "type" "text/hyperscript-template") + (dom-set-inner-html _el-script "${}{^x}") + (dom-set-attr _el-script1 "_" "init set ^x to 'second'") + (dom-set-attr _el-script1 "type" "text/hyperscript-template") + (dom-set-inner-html _el-script1 "${}{^x}") + (dom-append (dom-body) _el-script) + (dom-append (dom-body) _el-script1) + (hs-activate! _el-script) + (hs-activate! _el-script1) + )) + (deftest "processes hyperscript on inner elements" + (hs-cleanup!) + (let ((_el-script (dom-create-element "script"))) + (dom-set-attr _el-script "_" "init set ^val to 0") + (dom-set-attr _el-script "type" "text/hyperscript-template") + (dom-set-inner-html _el-script " + 0") + (dom-append (dom-body) _el-script) + (hs-activate! _el-script) + (dom-dispatch (dom-query "[data-live-template] button") "click" nil) + )) + (deftest "reactively updates when dependencies change" + (hs-cleanup!) + (let ((_el-script (dom-create-element "script"))) + (dom-set-attr _el-script "_" "init set ^count to 0") + (dom-set-attr _el-script "type" "text/hyperscript-template") + (dom-set-inner-html _el-script " + Count: ${}{^count}") + (dom-append (dom-body) _el-script) + (hs-activate! _el-script) + (dom-dispatch (dom-query "[data-live-template] button") "click" nil) + )) + (deftest "reacts to global state without init script" + (hs-cleanup!) + (let ((_el-script (dom-create-element "script"))) + (dom-set-attr _el-script "type" "text/hyperscript-template") + (dom-set-inner-html _el-script "

Hello, ${}{$ltGlobal}!

") + (dom-append (dom-body) _el-script) + )) + (deftest "renders static content after the template" + (hs-cleanup!) + (let ((_el-script (dom-create-element "script"))) + (dom-set-attr _el-script "type" "text/hyperscript-template") + (dom-set-inner-html _el-script "Hello World") + (dom-append (dom-body) _el-script) + )) + (deftest "renders template expressions" + (hs-cleanup!) + (let ((_el-script (dom-create-element "script"))) + (dom-set-attr _el-script "type" "text/hyperscript-template") + (dom-set-inner-html _el-script "Hello ${}{$ltName}!") + (dom-append (dom-body) _el-script) + )) + (deftest "scope is refreshed after morph so surviving elements get updated indices" + (hs-cleanup!) + (let ((_el-script (dom-create-element "script"))) + (dom-set-attr _el-script "type" "text/hyperscript-template") + (dom-set-inner-html _el-script "
    + #for item in $morphItems index i +
  • ${}{item.name}
  • + #end +
") + (dom-append (dom-body) _el-script) + (dom-dispatch (dom-query "[data-live-template] li") "click" nil) + (assert= (dom-text-content (dom-query "[data-live-template] li")) "2:C") + (dom-dispatch (dom-query "[data-live-template] li") "click" nil) + (assert= (dom-text-content (dom-query "[data-live-template] li")) "1:C") + )) + (deftest "script type=\"text/hyperscript-template\" works as a live template source" + (hs-cleanup!) + (let ((_el-script (dom-create-element "script"))) + (dom-set-attr _el-script "_" "init set ^stMsg to 'from script'") + (dom-set-attr _el-script "type" "text/hyperscript-template") + (dom-set-inner-html _el-script "${}{^stMsg}") + (dom-append (dom-body) _el-script) + (hs-activate! _el-script) + )) + (deftest "script-based live template preserves ${} in bare attribute position" + (hs-cleanup!) + (let ((_el-script (dom-create-element "script"))) + (dom-set-attr _el-script "_" "init set ^items to [{text:'A', done:true},{text:'B', done:false}]") + (dom-set-attr _el-script "type" "text/hyperscript-template") + (dom-set-inner-html _el-script "
    + #for item in ^items +
  • + + ${}{item.text} +
  • + #end +
") + (dom-append (dom-body) _el-script) + (hs-activate! _el-script) + )) + (deftest "supports #for loops" + (hs-cleanup!) + (let ((_el-script (dom-create-element "script"))) + (dom-set-attr _el-script "_" "init set ^items to ['a', 'b', 'c']") + (dom-set-attr _el-script "type" "text/hyperscript-template") + (dom-set-inner-html _el-script "
    + #for item in ^items +
  • ${}{item}
  • + #end +
") + (dom-append (dom-body) _el-script) + (hs-activate! _el-script) + )) + (deftest "supports #if conditionals" + (hs-cleanup!) + (let ((_el-script (dom-create-element "script"))) + (dom-set-attr _el-script "_" "init set ^show to true") + (dom-set-attr _el-script "type" "text/hyperscript-template") + (dom-set-inner-html _el-script "#if ^show + visible + #end") + (dom-append (dom-body) _el-script) + (hs-activate! _el-script) + )) + (deftest "wrapper has display:contents" + (hs-cleanup!) + (let ((_el-script (dom-create-element "script"))) + (dom-set-attr _el-script "type" "text/hyperscript-template") + (dom-set-inner-html _el-script "test") + (dom-append (dom-body) _el-script) + )) +) + +;; ── core/parser (14 tests) ── +(defsuite "hs-upstream-core/parser" + (deftest "_hyperscript() evaluate API still throws on first error" + (error "SKIP (untranslated): _hyperscript() evaluate API still throws on first error")) + (deftest "basic parse error messages work" + (error "SKIP (untranslated): basic parse error messages work")) + (deftest "can have alternate comments in attributes" + (hs-cleanup!) + (let ((_el-div (dom-create-element "div"))) + (dom-set-attr _el-div "_" "on click put \"clicked\" into my.innerHTML // put some content into the div...") + (dom-append (dom-body) _el-div) + (hs-activate! _el-div) + (dom-dispatch _el-div "click" nil) + (assert= (dom-text-content _el-div) "clicked") + )) + (deftest "can have alternate comments in scripts" + (hs-cleanup!) + (guard (_e (true nil)) (eval-expr-cek (hs-to-sx (hs-compile "// this is a comment def foo() // this is another comment return \"foo\" end // end with a comment")))) + ) + (deftest "can have comments in attributes" + (hs-cleanup!) + (let ((_el-div (dom-create-element "div"))) + (dom-set-attr _el-div "_" "on click put \"clicked\" into my.innerHTML -- put some content into the div...") + (dom-append (dom-body) _el-div) + (hs-activate! _el-div) + (dom-dispatch _el-div "click" nil) + (assert= (dom-text-content _el-div) "clicked") + )) + (deftest "can have comments in attributes (triple dash)" + (hs-cleanup!) + (let ((_el-div (dom-create-element "div"))) + (dom-set-attr _el-div "_" "on click put \"clicked\" into my.innerHTML ---put some content into the div...") + (dom-append (dom-body) _el-div) + (hs-activate! _el-div) + (dom-dispatch _el-div "click" nil) + (assert= (dom-text-content _el-div) "clicked") + )) + (deftest "can have comments in scripts" + (hs-cleanup!) + (guard (_e (true nil)) (eval-expr-cek (hs-to-sx (hs-compile "-- this is a comment def foo() -- this is another comment return \"foo\" end -- end with a comment--- this is a comment ----this is a comment---- def bar() ---this is another comment return \"bar\" end --- end with a comment")))) + ) + (deftest "can support parenthesized commands and features" + (hs-cleanup!) + (let ((_el-div (dom-create-element "div"))) + (dom-set-attr _el-div "_" "(on click (log me) (trigger foo)) (on foo (put \"clicked\" into my.innerHTML))") + (dom-append (dom-body) _el-div) + (hs-activate! _el-div) + (dom-dispatch _el-div "click" nil) + (assert= (dom-text-content _el-div) "clicked") + )) + (deftest "continues initializing elements in the presence of a parse error" + (hs-cleanup!) + (let ((_el-div (dom-create-element "div")) (_el-d1 (dom-create-element "div")) (_el-d2 (dom-create-element "div"))) + (dom-set-attr _el-d1 "id" "d1") + (dom-set-attr _el-d1 "_" "on click bad") + (dom-set-attr _el-d2 "id" "d2") + (dom-set-attr _el-d2 "_" "on click put \"clicked\" into my.innerHTML") + (dom-append (dom-body) _el-div) + (dom-append _el-div _el-d1) + (dom-append _el-div _el-d2) + (hs-activate! _el-d1) + (hs-activate! _el-d2) + (dom-dispatch (dom-query-by-id "d2") "click" nil) + (assert= (dom-text-content (dom-query-by-id "d2")) "clicked") + )) + (deftest "element-level isolation still works with error recovery" + (hs-cleanup!) + (let ((_el-div (dom-create-element "div")) (_el-d1 (dom-create-element "div")) (_el-d2 (dom-create-element "div"))) + (dom-set-attr _el-d1 "id" "d1") + (dom-set-attr _el-d1 "_" "on click blargh end on mouseenter also_bad") + (dom-set-attr _el-d2 "id" "d2") + (dom-set-attr _el-d2 "_" "on click put \"clicked\" into my.innerHTML") + (dom-append (dom-body) _el-div) + (dom-append _el-div _el-d1) + (dom-append _el-div _el-d2) + (hs-activate! _el-d1) + (hs-activate! _el-d2) + (dom-dispatch (dom-query-by-id "d2") "click" nil) + (assert= (dom-text-content (dom-query-by-id "d2")) "clicked") + )) + (deftest "fires hyperscript:parse-error event with all errors" + (error "SKIP (untranslated): fires hyperscript:parse-error event with all errors")) + (deftest "parse error at EOF on trailing newline does not crash" + (error "SKIP (untranslated): parse error at EOF on trailing newline does not crash")) + (deftest "recovers across feature boundaries and reports all errors" + (hs-cleanup!) + (let ((_el-d1 (dom-create-element "div"))) + (dom-set-attr _el-d1 "id" "d1") + (dom-set-attr _el-d1 "_" "on click blargh end on mouseenter put \"hovered\" into my.innerHTML") + (dom-append (dom-body) _el-d1) + (hs-activate! _el-d1) + )) + (deftest "recovers across multiple feature errors" + (hs-cleanup!) + (let ((_el-d1 (dom-create-element "div"))) + (dom-set-attr _el-d1 "id" "d1") + (dom-set-attr _el-d1 "_" "on click blargh end on mouseenter also_bad end on focus put \"focused\" into my.innerHTML") + (dom-append (dom-body) _el-d1) + (hs-activate! _el-d1) + )) +) + +;; ── core/reactivity (8 tests) ── +(defsuite "hs-upstream-core/reactivity" + (deftest "NaN → NaN does not retrigger handlers (Object.is semantics)" + (hs-cleanup!) + (let ((_el-div (dom-create-element "div"))) + (dom-set-attr _el-div "_" "when $rxNanVal changes increment $rxNanCount") + (dom-append (dom-body) _el-div) + (hs-activate! _el-div) + )) + (deftest "effect switches its dependencies based on control flow" + (hs-cleanup!) + (let ((_el-div (dom-create-element "div"))) + (dom-set-attr _el-div "_" "live if $rxCond put $rxA into me else put $rxB into me end end") + (dom-append (dom-body) _el-div) + (hs-activate! _el-div) + )) + (deftest "effects fire in source registration order" + (hs-cleanup!) + (let ((_el-div (dom-create-element "div")) (_el-div1 (dom-create-element "div")) (_el-div2 (dom-create-element "div"))) + (dom-set-attr _el-div "_" "when $rxTrigger changes call $rxOrder.push('first')") + (dom-set-attr _el-div1 "_" "when $rxTrigger changes call $rxOrder.push('second')") + (dom-set-attr _el-div2 "_" "when $rxTrigger changes call $rxOrder.push('third')") + (dom-append (dom-body) _el-div) + (dom-append (dom-body) _el-div1) + (dom-append (dom-body) _el-div2) + (hs-activate! _el-div) + (hs-activate! _el-div1) + (hs-activate! _el-div2) + )) + (deftest "effects on disconnected elements stop automatically" + (hs-cleanup!) + (let ((_el-persist (dom-create-element "div")) (_el-doomed (dom-create-element "div"))) + (dom-set-attr _el-persist "id" "persist") + (dom-set-attr _el-persist "_" "when $rxDcVal changes increment $rxDcCount then put $rxDcVal into me") + (dom-set-attr _el-doomed "id" "doomed") + (dom-set-attr _el-doomed "_" "when $rxDcVal changes increment $rxDcCount") + (dom-append (dom-body) _el-persist) + (dom-append (dom-body) _el-doomed) + (hs-activate! _el-persist) + (hs-activate! _el-doomed) + )) + (deftest "element-scoped writes only trigger effects on the same element" + (hs-cleanup!) + (let ((_el-a (dom-create-element "div")) (_el-b (dom-create-element "div"))) + (dom-set-attr _el-a "id" "a") + (dom-set-attr _el-a "_" "init set :count to 0 then on click increment :count then when :count changes put :count into me") + (dom-set-attr _el-b "id" "b") + (dom-set-attr _el-b "_" "init set :count to 0 then when :count changes put :count into me") + (dom-append (dom-body) _el-a) + (dom-append (dom-body) _el-b) + (hs-activate! _el-a) + (hs-activate! _el-b) + (dom-dispatch (dom-query-by-id "a") "click" nil) + (assert= (dom-text-content (dom-query-by-id "a")) "1") + (assert= (dom-text-content (dom-query-by-id "b")) "0") + )) + (deftest "multiple effects on the same global fire once per write" + (hs-cleanup!) + (let ((_el-a (dom-create-element "div")) (_el-b (dom-create-element "div"))) + (dom-set-attr _el-a "id" "a") + (dom-set-attr _el-a "_" "when $rxVal changes increment $rxCount then put $rxVal into me") + (dom-set-attr _el-b "id" "b") + (dom-set-attr _el-b "_" "when $rxVal changes put $rxVal into me") + (dom-append (dom-body) _el-a) + (dom-append (dom-body) _el-b) + (hs-activate! _el-a) + (hs-activate! _el-b) + )) + (deftest "reactive loops are detected and stopped after 100 consecutive triggers" + (hs-cleanup!) + (let ((_el-div (dom-create-element "div"))) + (dom-set-attr _el-div "_" "when $rxLoop changes increment $rxLoop") + (dom-append (dom-body) _el-div) + (hs-activate! _el-div) + )) + (deftest "setting same value does not retrigger handler" + (hs-cleanup!) + (let ((_el-div (dom-create-element "div"))) + (dom-set-attr _el-div "_" "when $rxSameVal changes increment $rxSameCount") + (dom-append (dom-body) _el-div) + (hs-activate! _el-div) + )) +) + +;; ── core/regressions (16 tests) ── +(defsuite "hs-upstream-core/regressions" + (deftest "async exception" + (hs-cleanup!) + (let ((_el-div (dom-create-element "div"))) + (dom-set-attr _el-div "_" "on click async transition opacity to 0 log \"hello!\"") + (dom-append (dom-body) _el-div) + (hs-activate! _el-div) + (dom-dispatch _el-div "click" nil) + )) + (deftest "button query in form" + (hs-cleanup!) + (let ((_el-form (dom-create-element "form")) (_el-b1 (dom-create-element "button"))) + (dom-set-attr _el-form "_" "on click get the