Hyperscript conformance: 222 test fixtures from _hyperscript 0.9.14

Extract pure expression tests from the official _hyperscript test suite
and implement parser/compiler/runtime extensions to pass them.

Test infrastructure:
- 222 fixtures extracted from evalHyperScript calls (no DOM dependency)
- SX data format with eval-hs bridge and run-hs-fixture runner
- 24 suites covering expressions, comparisons, coercion, logic, etc.

Parser extensions (parser.sx):
- mod as infix arithmetic operator
- English comparison phrases (is less than, is greater than or equal to)
- is a/an Type typecheck syntax
- === / !== strict equality operators
- I as me synonym, am as is for comparisons
- does not exist/match/contain postfix
- some/every ... with quantifier expressions
- undefined keyword → nil

Compiler updates (compiler.sx):
- + emits hs-add (type-dispatching: string concat or numeric add)
- no emits hs-falsy? (HS truthiness: empty string is falsy)
- matches? emits hs-matches? (string regex in non-DOM context)
- New cases: not-in?, in?, type-check, strict-eq, some, every

Runtime additions (runtime.sx):
- hs-coerce: Int/Integer truncation via floor
- hs-add: string concat when either operand is string
- hs-falsy?: HS-compatible truthiness (nil, false, "" are falsy)
- hs-matches?: string pattern matching
- hs-type-check/hs-type-check!: lenient/strict type checking
- hs-strict-eq: type + value equality

Tokenizer (tokenizer.sx):
- Added keywords: I, am, does, some, mod, equal, equals, really,
  include, includes, contain, undefined, exist

Scorecard: 47/112 test groups passing. 0 non-HS regressions.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
2026-04-08 18:53:50 +00:00
parent 71d1ac9ce4
commit 2278443182
6 changed files with 1250 additions and 70 deletions

View File

@@ -57,10 +57,10 @@
(deftest
"addition passes through"
(let
((sx (hs-to-sx-from-source "set x to 1 + 2")))
((sx (hs-to-sx-from-source "1 + 2")))
(let
((val (nth sx 2)))
(assert= (quote +) (first val))
((val (first sx)))
(assert= (quote hs-add) (first val))
(assert= 1 (nth val 1))
(assert= 2 (nth val 2)))))
(deftest
@@ -287,4 +287,61 @@
"decrement attribute"
(let
((sx (hs-to-sx-from-source "decrement @count")))
(assert= (quote dom-set-attr) (first sx)))))
(assert= (quote dom-set-attr) (first sx)))))
(defsuite
"hs-live-demo-toggle"
(deftest
"toggle class on me compiles to single hs-on"
(let
((sx (hs-to-sx-from-source "on click toggle .bg-violet-600 on me then toggle .text-white on me")))
(assert= (quote hs-on) (first sx))
(assert= "click" (nth sx 2))
(let
((body (nth (nth sx 3) 2)))
(assert= (quote do) (first body))
(assert= 2 (len (rest body))))))
(deftest
"bounce: then chains wait and remove in same handler"
(let
((sx (hs-to-sx-from-source "on click add .animate-bounce to me then wait 1s then remove .animate-bounce from me")))
(assert= (quote hs-on) (first sx))
(assert= "click" (nth sx 2))
(let
((body (nth (nth sx 3) 2)))
(assert= (quote do) (first body))
(assert= 3 (len (rest body)))
(assert= (quote hs-wait) (first (nth body 2)))
(assert= 1000 (nth (nth body 2) 1)))))
(deftest
"count clicks: then chains increment and set in same handler"
(let
((sx (hs-to-sx-from-source "on click increment @data-count on me then set #click-counter's innerHTML to my @data-count")))
(assert= (quote hs-on) (first sx))
(assert= "click" (nth sx 2))
(let
((body (nth (nth sx 3) 2)))
(assert= (quote do) (first body))
(assert= 2 (len (rest body)))))))
(defsuite
"hs-wait-suspension"
(deftest
"wait in then chain keeps hs-wait (platform handles suspension)"
(let
((sx (hs-to-sx-from-source "on click add .bounce to me then wait 1s then remove .bounce from me")))
(assert= (quote hs-on) (first sx))
(let
((body (nth (nth sx 3) 2)))
(assert= (quote do) (first body))
(assert= 3 (len (rest body)))
(assert= (quote hs-wait) (first (nth body 2)))
(assert= 1000 (nth (nth body 2) 1)))))
(deftest
"wait preserves ms value in handler"
(let
((sx (hs-to-sx-from-source "on click add .a then wait 2s then add .b")))
(let
((body (nth (nth sx 3) 2)))
(assert= (quote hs-wait) (first (nth body 2)))
(assert= 2000 (nth (nth body 2) 1))))))

View File

@@ -0,0 +1,826 @@
;; _hyperscript conformance test fixtures
;; Auto-extracted from https://github.com/bigskysoftware/_hyperscript (v0.9.14)
;; 222 pure expression tests (no DOM dependency)
;; Generated: 2026-04-08T17:44:00.716Z
;; ── eval-hs: compile + evaluate hyperscript source ──────────────────
(define
eval-hs
(fn
(src &rest opts)
(let
((sx (hs-to-sx (hs-compile src)))
(ctx (if (> (len opts) 0) (first opts) nil)))
(if
(nil? ctx)
(eval-expr-cek
(list
(quote let)
(list
(list (quote me) nil)
(list (quote it) nil)
(list (quote result) nil))
sx))
(let
((defaults (list (list (quote me) nil) (list (quote it) nil) (list (quote result) nil)))
(overrides (list)))
(do
(when
(get ctx "me")
(set!
overrides
(cons (list (quote me) (get ctx "me")) overrides)))
(when
(get ctx "locals")
(for-each
(fn
(k)
(set!
overrides
(cons
(list (make-symbol k) (get (get ctx "locals") k))
overrides)))
(keys (get ctx "locals"))))
(eval-expr-cek
(list (quote let) defaults (list (quote let) overrides sx)))))))))
;; ── run-hs-fixture: evaluate one test case ────────────────────────────
(define run-hs-fixture
(fn (f)
(let ((src (get f "src"))
(expected (get f "expected"))
(ctx (if (or (get f "locals") (get f "me"))
{"locals" (get f "locals") "me" (get f "me")}
nil)))
(let ((result (if ctx (eval-hs src ctx) (eval-hs src))))
(assert= result expected src)))))
;; ── arrayIndex (1 fixtures) ──────────────────────────────
(defsuite "hs-compat-arrayIndex"
(deftest "can-create-an-array-literal"
(for-each run-hs-fixture
(list
{"src" "[1, 2, 3]" "expected" (list 1 2 3)}
)))
)
;; ── arrayLiteral (3 fixtures) ──────────────────────────────
(defsuite "hs-compat-arrayLiteral"
(deftest "empty-array-literals-work"
(for-each run-hs-fixture
(list
{"src" "[]" "expected" (list)}
)))
(deftest "one-element-array-literal-works"
(for-each run-hs-fixture
(list
{"src" "[true]" "expected" (list true)}
)))
(deftest "multi-element-array-literal-works"
(for-each run-hs-fixture
(list
{"src" "[true, false]" "expected" (list true false)}
)))
)
;; ── asExpression (19 fixtures) ──────────────────────────────
(defsuite "hs-compat-asExpression"
(deftest "converts-value-as-string"
(for-each run-hs-fixture
(list
{"src" "10 as String" "expected" "10"}
{"src" "true as String" "expected" "true"}
)))
(deftest "converts-value-as-int"
(for-each run-hs-fixture
(list
{"src" "'10' as Int" "expected" 10}
{"src" "'10.4' as Int" "expected" 10}
)))
(deftest "converts-value-as-float"
(for-each run-hs-fixture
(list
{"src" "'10' as Float" "expected" 10}
{"src" "'10.4' as Float" "expected" 10.4}
)))
(deftest "converts-value-as-fixed"
(for-each run-hs-fixture
(list
{"src" "'10.4' as Fixed" "expected" "10"}
{"src" "'10.4899' as Fixed:2" "expected" "10.49"}
)))
(deftest "converts-value-as-number"
(for-each run-hs-fixture
(list
{"src" "'10' as Number" "expected" 10}
{"src" "'10.4' as Number" "expected" 10.4}
)))
(deftest "converts-value-as-json"
(for-each run-hs-fixture
(list
{"src" "{foo:'bar'} as JSON" "expected" "{\"foo\":\"bar\"}"}
)))
(deftest "converts-string-as-object"
(for-each run-hs-fixture
(list
{"src" "'{\"foo\":\"bar\"}' as Object" "expected" "bar"}
)))
(deftest "can-use-the-an-modifier-if-you"
(for-each run-hs-fixture
(list
{"src" "'{\"foo\":\"bar\"}' as an Object" "expected" "bar"}
)))
(deftest "converts-value-as-object"
(for-each run-hs-fixture
(list
{"src" "x as Object" "expected" "bar"}
)))
(deftest "converts-a-complete-form-into-values"
(for-each run-hs-fixture
(list
{"src" "x as Values" "expected" "John"}
)))
(deftest "converts-numbers-things-"
(for-each run-hs-fixture
(list
{"src" "value as HTML" "expected" "123"}
)))
(deftest "converts-strings-into-fragments"
(for-each run-hs-fixture
(list
{"src" "value as Fragment" "expected" 1}
)))
(deftest "can-accept-custom-conversions"
(for-each run-hs-fixture
(list
{"src" "1 as Foo" "expected" "foo1"}
)))
(deftest "-"
(for-each run-hs-fixture
(list
{"src" "1 as Foo:Bar" "expected" "Bar1"}
)))
)
;; ── blockLiteral (4 fixtures) ──────────────────────────────
(defsuite "hs-compat-blockLiteral"
(deftest "basic-block-literals-work"
(for-each run-hs-fixture
(list
{"src" "\\\\-> true" "expected" true}
)))
(deftest "basic-identity-works"
(for-each run-hs-fixture
(list
{"src" "\\\\ x -> x" "expected" true}
)))
(deftest "basic-two-arg-identity-works"
(for-each run-hs-fixture
(list
{"src" "\\\\ x, y -> y" "expected" true}
)))
(deftest "can-map-an-array"
(for-each run-hs-fixture
(list
{"src" "['a', 'ab', 'abc'].map(\\\\ s -> s.length )" "expected" (list 1 2 3)}
)))
)
;; ── boolean (2 fixtures) ──────────────────────────────
(defsuite "hs-compat-boolean"
(deftest "true-boolean-literals-work"
(for-each run-hs-fixture
(list
{"src" "true" "expected" true}
)))
(deftest "false-boolean-literals-work"
(for-each run-hs-fixture
(list
{"src" "false" "expected" false}
)))
)
;; ── classRef (1 fixtures) ──────────────────────────────
(defsuite "hs-compat-classRef"
(deftest "basic-classref-works-w-no-match"
(for-each run-hs-fixture
(list
{"src" ".badClassThatDoesNotHaveAnyElements" "expected" 0}
)))
)
;; ── comparisonOperator (113 fixtures) ──────────────────────────────
(defsuite "hs-compat-comparisonOperator"
(deftest "less-than-works"
(for-each run-hs-fixture
(list
{"src" "1 < 2" "expected" true}
{"src" "2 < 1" "expected" false}
{"src" "2 < 2" "expected" false}
)))
(deftest "less-than-or-equal-works"
(for-each run-hs-fixture
(list
{"src" "1 <= 2" "expected" true}
{"src" "2 <= 1" "expected" false}
{"src" "2 <= 2" "expected" true}
)))
(deftest "greater-than-works"
(for-each run-hs-fixture
(list
{"src" "1 > 2" "expected" false}
{"src" "2 > 1" "expected" true}
{"src" "2 > 2" "expected" false}
)))
(deftest "greater-than-or-equal-works"
(for-each run-hs-fixture
(list
{"src" "1 >= 2" "expected" false}
{"src" "2 >= 1" "expected" true}
{"src" "2 >= 2" "expected" true}
)))
(deftest "equal-works"
(for-each run-hs-fixture
(list
{"src" "1 == 2" "expected" false}
{"src" "2 == 1" "expected" false}
{"src" "2 == 2" "expected" true}
)))
(deftest "triple-equal-works"
(for-each run-hs-fixture
(list
{"src" "1 === 2" "expected" false}
{"src" "2 === 1" "expected" false}
{"src" "2 === 2" "expected" true}
)))
(deftest "not-equal-works"
(for-each run-hs-fixture
(list
{"src" "1 != 2" "expected" true}
{"src" "2 != 1" "expected" true}
{"src" "2 != 2" "expected" false}
)))
(deftest "triple-not-equal-works"
(for-each run-hs-fixture
(list
{"src" "1 !== 2" "expected" true}
{"src" "2 !== 1" "expected" true}
{"src" "2 !== 2" "expected" false}
)))
(deftest "is-works"
(for-each run-hs-fixture
(list
{"src" "1 is 2" "expected" false}
{"src" "2 is 1" "expected" false}
{"src" "2 is 2" "expected" true}
)))
(deftest "equals-works"
(for-each run-hs-fixture
(list
{"src" "1 equals 2" "expected" false}
{"src" "2 equals 1" "expected" false}
{"src" "2 equals 2" "expected" true}
)))
(deftest "is-equal-to-works"
(for-each run-hs-fixture
(list
{"src" "1 is equal to 2" "expected" false}
{"src" "2 is equal to 1" "expected" false}
{"src" "2 is equal to 2" "expected" true}
)))
(deftest "is-really-equal-to-works"
(for-each run-hs-fixture
(list
{"src" "1 is really equal to 2" "expected" false}
{"src" "2 is really equal to 1" "expected" false}
{"src" "2 is really equal to '2'" "expected" false}
{"src" "2 is really equal to 2" "expected" true}
)))
(deftest "really-equals-works"
(for-each run-hs-fixture
(list
{"src" "1 really equals 2" "expected" false}
{"src" "2 really equals 1" "expected" false}
{"src" "2 really equals 2" "expected" true}
)))
(deftest "is-not-works"
(for-each run-hs-fixture
(list
{"src" "1 is not 2" "expected" true}
{"src" "2 is not 1" "expected" true}
{"src" "2 is not 2" "expected" false}
)))
(deftest "is-not-equal-to-works"
(for-each run-hs-fixture
(list
{"src" "1 is not equal to 2" "expected" true}
{"src" "2 is not equal to 1" "expected" true}
{"src" "2 is not equal to 2" "expected" false}
)))
(deftest "is-not-really-equal-to-works"
(for-each run-hs-fixture
(list
{"src" "1 is not really equal to 2" "expected" true}
{"src" "2 is not really equal to 1" "expected" true}
{"src" "2 is not really equal to '2'" "expected" true}
{"src" "2 is not really equal to 2" "expected" false}
)))
(deftest "is-in-works"
(for-each run-hs-fixture
(list
{"src" "1 is in [1, 2]" "expected" true}
{"src" "2 is in [1, 2]" "expected" true}
{"src" "3 is in [1, 2]" "expected" false}
{"src" "3 is in null" "expected" false}
)))
(deftest "is-not-in-works"
(for-each run-hs-fixture
(list
{"src" "1 is not in [1, 2]" "expected" false}
{"src" "2 is not in [1, 2]" "expected" false}
{"src" "3 is not in [1, 2]" "expected" true}
{"src" "3 is not in null" "expected" true}
)))
(deftest "i-am-in-works"
(for-each run-hs-fixture
(list
{"src" "I am in [1, 2]" "expected" true "me" 1}
{"src" "I am in [1, 2]" "expected" true "me" 2}
{"src" "I am in [1, 2]" "expected" false "me" 3}
{"src" "I am in null" "expected" false}
)))
(deftest "i-am-not-in-works"
(for-each run-hs-fixture
(list
{"src" "I am not in [1, 2]" "expected" false "me" 1}
{"src" "I am not in [1, 2]" "expected" false "me" 2}
{"src" "I am not in [1, 2]" "expected" true "me" 3}
{"src" "I am not in null" "expected" true}
)))
(deftest "match-works-w-strings"
(for-each run-hs-fixture
(list
{"src" "'a' matches '.*'" "expected" true}
{"src" "'a' matches 'b'" "expected" false}
)))
(deftest "does-not-match-works-w-strings"
(for-each run-hs-fixture
(list
{"src" "'a' does not match '.*'" "expected" false}
{"src" "'a' does not match 'b'" "expected" true}
)))
(deftest "is-empty-works"
(for-each run-hs-fixture
(list
{"src" "undefined is empty" "expected" true}
{"src" "'' is empty" "expected" true}
{"src" "[] is empty" "expected" true}
{"src" "'not empty' is empty" "expected" false}
{"src" "1000 is empty" "expected" false}
{"src" "[1,2,3] is empty" "expected" false}
{"src" ".aClassThatDoesNotExist is empty" "expected" true}
)))
(deftest "is-not-empty-works"
(for-each run-hs-fixture
(list
{"src" "undefined is not empty" "expected" false}
{"src" "'' is not empty" "expected" false}
{"src" "[] is not empty" "expected" false}
{"src" "'not empty' is not empty" "expected" true}
{"src" "1000 is not empty" "expected" true}
{"src" "[1,2,3] is not empty" "expected" true}
)))
(deftest "is-a-works"
(for-each run-hs-fixture
(list
{"src" "null is a String" "expected" true}
{"src" "null is a String!" "expected" false}
{"src" "'' is a String!" "expected" true}
)))
(deftest "is-not-a-works"
(for-each run-hs-fixture
(list
{"src" "null is not a String" "expected" false}
{"src" "null is not a String!" "expected" true}
{"src" "'' is not a String!" "expected" false}
)))
(deftest "is-an-works"
(for-each run-hs-fixture
(list
{"src" "null is an String" "expected" true}
{"src" "null is an String!" "expected" false}
{"src" "'' is an String!" "expected" true}
)))
(deftest "is-not-an-works"
(for-each run-hs-fixture
(list
{"src" "null is not an String" "expected" false}
{"src" "null is not an String!" "expected" true}
{"src" "'' is not an String!" "expected" false}
)))
(deftest "english-less-than-works"
(for-each run-hs-fixture
(list
{"src" "1 is less than 2" "expected" true}
{"src" "2 is less than 1" "expected" false}
{"src" "2 is less than 2" "expected" false}
)))
(deftest "english-less-than-or-equal-works"
(for-each run-hs-fixture
(list
{"src" "1 is less than or equal to 2" "expected" true}
{"src" "2 is less than or equal to 1" "expected" false}
{"src" "2 is less than or equal to 2" "expected" true}
)))
(deftest "english-greater-than-works"
(for-each run-hs-fixture
(list
{"src" "1 is greater than 2" "expected" false}
{"src" "2 is greater than 1" "expected" true}
{"src" "2 is greater than 2" "expected" false}
)))
(deftest "english-greater-than-or-equal-works"
(for-each run-hs-fixture
(list
{"src" "1 is greater than or equal to 2" "expected" false}
{"src" "2 is greater than or equal to 1" "expected" true}
{"src" "2 is greater than or equal to 2" "expected" true}
)))
(deftest "does-not-exist-works"
(for-each run-hs-fixture
(list
{"src" "undefined does not exist" "expected" true}
{"src" "null does not exist" "expected" true}
{"src" "#doesNotExist does not exist" "expected" true}
{"src" ".aClassThatDoesNotExist does not exist" "expected" true}
{"src" "<.aClassThatDoesNotExist/> does not exist" "expected" true}
{"src" "<body/> does not exist" "expected" false}
)))
)
;; ── cookies (9 fixtures) ──────────────────────────────
(defsuite "hs-compat-cookies"
(deftest "basic-set-cookie-values-work"
(for-each run-hs-fixture
(list
{"src" "cookies.foo" "expected" "bar"}
{"src" "set cookies.foo to 'bar'" "expected" "bar"}
{"src" "cookies.foo" "expected" "bar"}
)))
(deftest "update-cookie-values-work"
(for-each run-hs-fixture
(list
{"src" "set cookies.foo to 'bar'" "expected" "bar"}
{"src" "cookies.foo" "expected" "bar"}
{"src" "set cookies.foo to 'doh'" "expected" "doh"}
{"src" "cookies.foo" "expected" "doh"}
)))
(deftest "iterate-cookies-values-work"
(for-each run-hs-fixture
(list
{"src" "set cookies.foo to 'bar'" "expected" true}
{"src" "for x in cookies me.push(x.name) then you.push(x.value) end" "expected" true}
)))
)
;; ── in (4 fixtures) ──────────────────────────────
(defsuite "hs-compat-in"
(deftest "basic-no-query-return-values"
(for-each run-hs-fixture
(list
{"src" "1 in [1, 2, 3]" "expected" (list 1)}
{"src" "[1, 3] in [1, 2, 3]" "expected" (list 1 3)}
{"src" "[1, 3, 4] in [1, 2, 3]" "expected" (list 1 3)}
{"src" "[4, 5, 6] in [1, 2, 3]" "expected" (list)}
)))
)
;; ── logicalOperator (2 fixtures) ──────────────────────────────
(defsuite "hs-compat-logicalOperator"
(deftest "should-short-circuit-with-and-expression"
(for-each run-hs-fixture
(list
{"src" "func1() and func2()" "expected" false}
)))
(deftest "should-short-circuit-with-or-expression"
(for-each run-hs-fixture
(list
{"src" "func1() or func2()" "expected" true}
)))
)
;; ── mathOperator (8 fixtures) ──────────────────────────────
(defsuite "hs-compat-mathOperator"
(deftest "addition-works"
(for-each run-hs-fixture
(list
{"src" "1 + 1" "expected" 2}
)))
(deftest "string-concat-works"
(for-each run-hs-fixture
(list
{"src" "'a' + 'b'" "expected" "ab"}
)))
(deftest "subtraction-works"
(for-each run-hs-fixture
(list
{"src" "1 - 1" "expected" 0}
)))
(deftest "multiplication-works"
(for-each run-hs-fixture
(list
{"src" "1 * 2" "expected" 2}
)))
(deftest "division-works"
(for-each run-hs-fixture
(list
{"src" "1 / 2" "expected" 0.5}
)))
(deftest "mod-works"
(for-each run-hs-fixture
(list
{"src" "3 mod 2" "expected" 1}
)))
(deftest "addition-works-w-more-than-one-value"
(for-each run-hs-fixture
(list
{"src" "1 + 2 + 3" "expected" 6}
)))
(deftest "parenthesized-expressions-with-multiple-operators-work"
(for-each run-hs-fixture
(list
{"src" "1 + (2 * 3)" "expected" 7}
)))
)
;; ── no (5 fixtures) ──────────────────────────────
(defsuite "hs-compat-no"
(deftest "no-returns-true-for-null"
(for-each run-hs-fixture
(list
{"src" "no null" "expected" true}
)))
(deftest "no-returns-false-for-non-null"
(for-each run-hs-fixture
(list
{"src" "no 'thing'" "expected" false}
{"src" "no ['thing']" "expected" false}
)))
(deftest "no-returns-true-for-empty-array"
(for-each run-hs-fixture
(list
{"src" "no []" "expected" true}
)))
(deftest "no-returns-true-for-empty-selector"
(for-each run-hs-fixture
(list
{"src" "no .aClassThatDoesNotExist" "expected" true}
)))
)
;; ── not (3 fixtures) ──────────────────────────────
(defsuite "hs-compat-not"
(deftest "not-inverts-true"
(for-each run-hs-fixture
(list
{"src" "not true" "expected" false}
)))
(deftest "not-inverts-false"
(for-each run-hs-fixture
(list
{"src" "not false" "expected" true}
)))
(deftest "two-nots-make-a-true"
(for-each run-hs-fixture
(list
{"src" "not not true" "expected" true}
)))
)
;; ── numbers (4 fixtures) ──────────────────────────────
(defsuite "hs-compat-numbers"
(deftest "handles-numbers-properly"
(for-each run-hs-fixture
(list
{"src" "-1" "expected" -1}
{"src" "1" "expected" 1}
{"src" "1.1" "expected" 1.1}
{"src" "1234567890.1234567890" "expected" 1234567890.1234567}
)))
)
;; ── objectLiteral (3 fixtures) ──────────────────────────────
(defsuite "hs-compat-objectLiteral"
(deftest "empty-object-literals-work"
(for-each run-hs-fixture
(list
{"src" "{}" "expected" {}}
)))
(deftest "hyphens-work-in-object-literal-field-names"
(for-each run-hs-fixture
(list
{"src" "{-foo:true, bar-baz:false}" "expected" {"-foo" true "bar-baz" false}}
)))
(deftest "allows-trailing-commans"
(for-each run-hs-fixture
(list
{"src" "{foo:true, bar-baz:false,}" "expected" {"foo" true "bar-baz" false}}
)))
)
;; ── positionalExpression (2 fixtures) ──────────────────────────────
(defsuite "hs-compat-positionalExpression"
(deftest "first-works"
(for-each run-hs-fixture
(list
{"src" "the first of [1, 2, 3]" "expected" 1}
)))
(deftest "last-works"
(for-each run-hs-fixture
(list
{"src" "the last of [1, 2, 3]" "expected" 3}
)))
)
;; ── possessiveExpression (2 fixtures) ──────────────────────────────
(defsuite "hs-compat-possessiveExpression"
(deftest "can-access-basic-properties"
(for-each run-hs-fixture
(list
{"src" "foo's foo" "expected" "foo"}
)))
(deftest "can-access-its-properties"
(for-each run-hs-fixture
(list
{"src" "its foo" "expected" "foo"}
)))
)
;; ── propertyAccess (4 fixtures) ──────────────────────────────
(defsuite "hs-compat-propertyAccess"
(deftest "can-access-basic-properties"
(for-each run-hs-fixture
(list
{"src" "foo.foo" "expected" "foo"}
)))
(deftest "of-form-works"
(for-each run-hs-fixture
(list
{"src" "foo of foo" "expected" "foo"}
)))
(deftest "of-form-works-w-complex-left-side"
(for-each run-hs-fixture
(list
{"src" "bar.doh of foo" "expected" "foo"}
)))
(deftest "of-form-works-w-complex-right-side"
(for-each run-hs-fixture
(list
{"src" "doh of foo.bar" "expected" "foo"}
)))
)
;; ── queryRef (1 fixtures) ──────────────────────────────
(defsuite "hs-compat-queryRef"
(deftest "basic-queryref-works-w-no-match"
(for-each run-hs-fixture
(list
{"src" "<.badClassThatDoesNotHaveAnyElements/>" "expected" 0}
)))
)
;; ── some (6 fixtures) ──────────────────────────────
(defsuite "hs-compat-some"
(deftest "some-returns-false-for-null"
(for-each run-hs-fixture
(list
{"src" "some null" "expected" false}
)))
(deftest "some-returns-true-for-non-null"
(for-each run-hs-fixture
(list
{"src" "some 'thing'" "expected" true}
)))
(deftest "some-returns-false-for-empty-array"
(for-each run-hs-fixture
(list
{"src" "some []" "expected" false}
)))
(deftest "some-returns-false-for-empty-selector"
(for-each run-hs-fixture
(list
{"src" "some .aClassThatDoesNotExist" "expected" false}
)))
(deftest "some-returns-true-for-nonempty-selector"
(for-each run-hs-fixture
(list
{"src" "some <html/>" "expected" true}
)))
(deftest "some-returns-true-for-filled-array"
(for-each run-hs-fixture
(list
{"src" "some ['thing']" "expected" true}
)))
)
;; ── stringPostfix (10 fixtures) ──────────────────────────────
(defsuite "hs-compat-stringPostfix"
(deftest "handles-basic-postfix-strings-properly"
(for-each run-hs-fixture
(list
{"src" "1em" "expected" "1em"}
{"src" "1px" "expected" "1px"}
{"src" "-1px" "expected" "-1px"}
{"src" "100%" "expected" "100%"}
)))
(deftest "handles-basic-postfix-strings-with-spaces-properly"
(for-each run-hs-fixture
(list
{"src" "1 em" "expected" "1em"}
{"src" "1 px" "expected" "1px"}
{"src" "100 %" "expected" "100%"}
)))
(deftest "handles-expression-roots-properly"
(for-each run-hs-fixture
(list
{"src" "(0 + 1) em" "expected" "1em"}
{"src" "(0 + 1) px" "expected" "1px"}
{"src" "(100 + 0) %" "expected" "100%"}
)))
)
;; ── strings (11 fixtures) ──────────────────────────────
(defsuite "hs-compat-strings"
(deftest "handles-strings-properly"
(for-each run-hs-fixture
(list
{"src" "\"foo\"" "expected" "foo"}
{"src" "\"fo'o\"" "expected" "fo'o"}
{"src" "'foo'" "expected" "foo"}
)))
(deftest "string-templates-work-properly"
(for-each run-hs-fixture
(list
{"src" "`$1`" "expected" "1"}
)))
(deftest "string-templates-work-properly-w-braces"
(for-each run-hs-fixture
(list
{"src" "`${1 + 2}`" "expected" "3"}
)))
(deftest "string-templates-preserve-white-space"
(for-each run-hs-fixture
(list
{"src" "` ${1 + 2} ${1 + 2} `" "expected" " 3 3 "}
{"src" "`${1 + 2} ${1 + 2} `" "expected" "3 3 "}
{"src" "`${1 + 2}${1 + 2} `" "expected" "33 "}
{"src" "`${1 + 2} ${1 + 2}`" "expected" "3 3"}
)))
(deftest "should-handle-strings-with-tags-and-quotes"
(for-each run-hs-fixture
(list
{"src" "`<div age=\"${record.age}\" style=\"color:${record.favouriteColour}\">${record.name}</div>`" "expected" "<div age=\"21\" style=\"color:bleaux\">John Connor</div>"}
)))
(deftest "should-handle-back-slashes-in-non-template-content"
(for-each run-hs-fixture
(list
{"src" "`https://${foo}`" "expected" "https://bar" "locals" {"foo" "bar"}}
)))
)
;; ── symbol (1 fixtures) ──────────────────────────────
(defsuite "hs-compat-symbol"
(deftest "resolves-local-context-properly"
(for-each run-hs-fixture
(list
{"src" "foo" "expected" 42 "locals" {"foo" 42}}
)))
)
;; ── typecheck (4 fixtures) ──────────────────────────────
(defsuite "hs-compat-typecheck"
(deftest "can-do-basic-string-typecheck"
(for-each run-hs-fixture
(list
{"src" "'foo' : String" "expected" "foo"}
)))
(deftest "can-do-basic-non-string-typecheck-failure"
(for-each run-hs-fixture
(list
{"src" "true : String" "expected" 0}
)))
(deftest "can-do-basic-string-non-null-typecheck"
(for-each run-hs-fixture
(list
{"src" "'foo' : String!" "expected" "foo"}
)))
(deftest "null-causes-null-safe-string-check-to-fail"
(for-each run-hs-fixture
(list
{"src" "null : String!" "expected" 0}
)))
)
;; ── Summary ──────────────────────────────────────────────────────────
;; 24 suites, 112 tests, 222 fixtures