diff --git a/spec/tests/test-hyperscript-behavioral.sx b/spec/tests/test-hyperscript-behavioral.sx index 96f8296a..e1c0aab7 100644 --- a/spec/tests/test-hyperscript-behavioral.sx +++ b/spec/tests/test-hyperscript-behavioral.sx @@ -6696,9 +6696,11 @@ ;; ── collectionExpressions (22 tests) ── (defsuite "hs-upstream-collectionExpressions" (deftest "filters an array by condition" - (error "NOT IMPLEMENTED: test HTML could not be parsed into SX")) + (assert= (list "a" "c") (eval-hs "set arr to [{name: \"a\", active: true}, {name: \"b\", active: false}, {name: \"c\", active: true}] then return arr where its active")) + ) (deftest "filters with comparison" - (error "NOT IMPLEMENTED: test HTML could not be parsed into SX")) + (assert= (list 4 5) (eval-hs "set arr to [1, 2, 3, 4, 5] then return arr where it > 3")) + ) (deftest "works with DOM elements" (hs-cleanup!) (let ((_el-list (dom-create-element "ul")) (_el-button (dom-create-element "button")) (_el-out (dom-create-element "div"))) @@ -6713,23 +6715,32 @@ (assert= "AC" (dom-text-content (dom-query-by-id "out"))) )) (deftest "sorts by a property" - (error "NOT IMPLEMENTED: test HTML could not be parsed into SX")) + (assert= (list "Alice" "Bob" "Charlie") (eval-hs "set arr to [{name: \"Charlie\"}, {name: \"Alice\"}, {name: \"Bob\"}] then return arr sorted by its name")) + ) (deftest "sorts descending" - (error "NOT IMPLEMENTED: test HTML could not be parsed into SX")) + (assert= (list 3 2 1) (eval-hs "set arr to [3, 1, 2] then return arr sorted by it descending")) + ) (deftest "sorts numbers by a computed key" - (error "NOT IMPLEMENTED: test HTML could not be parsed into SX")) + (assert= (list "a" "c" "b") (eval-hs "set arr to [{name: \"b\", age: 30}, {name: \"a\", age: 20}, {name: \"c\", age: 25}] then return arr sorted by its age")) + ) (deftest "maps to a property" - (error "NOT IMPLEMENTED: test HTML could not be parsed into SX")) + (assert= (list "Alice" "Bob") (eval-hs "set arr to [{name: \"Alice\"}, {name: \"Bob\"}] then return arr mapped to its name")) + ) (deftest "maps with an expression" - (error "NOT IMPLEMENTED: test HTML could not be parsed into SX")) + (assert= (list 2 4 6) (eval-hs "set arr to [1, 2, 3] then return arr mapped to (it * 2)")) + ) (deftest "where then mapped to" - (error "NOT IMPLEMENTED: test HTML could not be parsed into SX")) + (assert= (list "Alice" "Charlie") (eval-hs "set arr to [{name: \"Alice\", active: true}, {name: \"Bob\", active: false}, {name: \"Charlie\", active: true}] then return arr where its active mapped to its name")) + ) (deftest "sorted by then mapped to" - (error "NOT IMPLEMENTED: test HTML could not be parsed into SX")) + (assert= (list "Alice" "Charlie") (eval-hs "set arr to [{name: \"Charlie\", age: 30}, {name: \"Alice\", age: 20}] then return arr sorted by its age mapped to its name")) + ) (deftest "where then sorted by then mapped to" - (error "NOT IMPLEMENTED: test HTML could not be parsed into SX")) + (assert= (list "Bob" "Charlie") (eval-hs "set arr to [{name: \"Charlie\", active: true, age: 30}, {name: \"Alice\", active: false, age: 20}, {name: \"Bob\", active: true, age: 25}] then return arr where its active sorted by its age mapped to its name")) + ) (deftest "the result inside where refers to previous command result, not current element" - (error "NOT IMPLEMENTED: test HTML could not be parsed into SX")) + (assert= (list 4 5) (eval-hs "get 3 then set arr to [1, 2, 3, 4, 5] then return arr where it > the result")) + ) (deftest "where binds after in without parens" (hs-cleanup!) (let ((_el-container (dom-create-element "div"))) @@ -6810,19 +6821,26 @@ ;; ── splitJoin (7 tests) ── (defsuite "hs-upstream-splitJoin" (deftest "splits a string by delimiter" - (error "NOT IMPLEMENTED: test HTML could not be parsed into SX")) + (assert= (list "a" "b" "c") (eval-hs "return \"a,b,c\" split by \",\"")) + ) (deftest "splits by whitespace" - (error "NOT IMPLEMENTED: test HTML could not be parsed into SX")) + (assert= (list "hello" "world") (eval-hs "return \"hello world\" split by \" \"")) + ) (deftest "joins an array with delimiter" - (error "NOT IMPLEMENTED: test HTML could not be parsed into SX")) + (assert= "a, b, c" (eval-hs "return [\"a\", \"b\", \"c\"] joined by \", \"")) + ) (deftest "joins with empty string" - (error "NOT IMPLEMENTED: test HTML could not be parsed into SX")) + (assert= "xyz" (eval-hs "return [\"x\", \"y\", \"z\"] joined by \"\"")) + ) (deftest "split then where then joined" - (error "NOT IMPLEMENTED: test HTML could not be parsed into SX")) + (assert= "a-b-c" (eval-hs "return \"a,,b,,c\" split by \",\" where it is not \"\" joined by \"-\"")) + ) (deftest "split then sorted then joined" - (error "NOT IMPLEMENTED: test HTML could not be parsed into SX")) + (assert= "apple, banana, cherry" (eval-hs "return \"banana,apple,cherry\" split by \",\" sorted by it joined by \", \"")) + ) (deftest "split then mapped then joined" - (error "NOT IMPLEMENTED: test HTML could not be parsed into SX")) + (assert= "5,5" (eval-hs "return \"hello world\" split by \" \" mapped to its length joined by \",\"")) + ) ) ;; ── component (19 tests) ── @@ -7380,17 +7398,23 @@ (deftest "converts value as Boolean" (assert= true (eval-hs "1 as Boolean")) (assert= false (eval-hs "0 as Boolean")) + (assert= false (eval-hs "'' as Boolean")) + (assert= true (eval-hs "'hello' as Boolean")) ) (deftest "can use the a modifier if you like" (error "NOT IMPLEMENTED: test HTML could not be parsed into SX")) (deftest "parses string as JSON to object" - (error "NOT IMPLEMENTED: test HTML could not be parsed into SX")) + (assert= "bar" (eval-hs "'{\"foo\":\"bar\"}' as JSON")) + ) (deftest "converts value as JSONString" - (error "NOT IMPLEMENTED: test HTML could not be parsed into SX")) + (assert= "{"foo":"bar"}" (eval-hs "{foo:'bar'} as JSONString")) + ) (deftest "pipe operator chains conversions" - (error "NOT IMPLEMENTED: test HTML could not be parsed into SX")) + (assert= "bar" (eval-hs "{foo:'bar'} as JSONString | JSON")) + ) (deftest "can use the an modifier if you'd like" - (error "NOT IMPLEMENTED: test HTML could not be parsed into SX")) + (assert= "bar" (eval-hs "'{\"foo\":\"bar\"}' as an Object")) + ) (deftest "collects duplicate text inputs into an array" (error "NOT IMPLEMENTED: test HTML could not be parsed into SX")) (deftest "converts multiple selects with programmatically changed selections" @@ -7485,33 +7509,69 @@ (assert= "yes" (dom-text-content (dom-query-by-id "d1"))) )) (deftest "is ignoring case works" - (error "NOT IMPLEMENTED: test HTML could not be parsed into SX")) + (assert= true (eval-hs "'Hello' is 'hello' ignoring case")) + (assert= true (eval-hs "'Hello' is 'HELLO' ignoring case")) + (assert= false (eval-hs "'Hello' is 'world' ignoring case")) + ) (deftest "is not ignoring case works" - (error "NOT IMPLEMENTED: test HTML could not be parsed into SX")) + (assert= true (eval-hs "'Hello' is not 'world' ignoring case")) + (assert= false (eval-hs "'Hello' is not 'hello' ignoring case")) + ) (deftest "contains ignoring case works" - (error "NOT IMPLEMENTED: test HTML could not be parsed into SX")) + (assert= true (eval-hs "'Hello World' contains 'hello' ignoring case")) + (assert= true (eval-hs "'Hello World' contains 'WORLD' ignoring case")) + (assert= false (eval-hs "'Hello World' contains 'missing' ignoring case")) + ) (deftest "matches ignoring case works" - (error "NOT IMPLEMENTED: test HTML could not be parsed into SX")) + (assert= true (eval-hs "'Hello' matches 'hello' ignoring case")) + (assert= true (eval-hs "'Hello' matches 'HELLO' ignoring case")) + ) (deftest "starts with works" - (error "NOT IMPLEMENTED: test HTML could not be parsed into SX")) + (assert= true (eval-hs "'hello world' starts with 'hello'")) + (assert= false (eval-hs "'hello world' starts with 'world'")) + (assert= true (eval-hs "'hello' starts with 'hello'")) + (assert= false (eval-hs "'' starts with 'x'")) + ) (deftest "ends with works" - (error "NOT IMPLEMENTED: test HTML could not be parsed into SX")) + (assert= true (eval-hs "'hello world' ends with 'world'")) + (assert= false (eval-hs "'hello world' ends with 'hello'")) + (assert= true (eval-hs "'hello' ends with 'hello'")) + (assert= false (eval-hs "'' ends with 'x'")) + ) (deftest "does not start with works" - (error "NOT IMPLEMENTED: test HTML could not be parsed into SX")) + (assert= false (eval-hs "'hello world' does not start with 'hello'")) + (assert= true (eval-hs "'hello world' does not start with 'world'")) + ) (deftest "does not end with works" - (error "NOT IMPLEMENTED: test HTML could not be parsed into SX")) + (assert= false (eval-hs "'hello world' does not end with 'world'")) + (assert= true (eval-hs "'hello world' does not end with 'hello'")) + ) (deftest "starts with null is false" - (error "NOT IMPLEMENTED: test HTML could not be parsed into SX")) + (assert= false (eval-hs "null starts with 'x'")) + (assert= true (eval-hs "null does not start with 'x'")) + ) (deftest "ends with null is false" - (error "NOT IMPLEMENTED: test HTML could not be parsed into SX")) + (assert= false (eval-hs "null ends with 'x'")) + (assert= true (eval-hs "null does not end with 'x'")) + ) (deftest "starts with ignoring case works" - (error "NOT IMPLEMENTED: test HTML could not be parsed into SX")) + (assert= true (eval-hs "'Hello World' starts with 'hello' ignoring case")) + (assert= true (eval-hs "'Hello World' starts with 'HELLO' ignoring case")) + (assert= false (eval-hs "'Hello World' starts with 'world' ignoring case")) + ) (deftest "ends with ignoring case works" - (error "NOT IMPLEMENTED: test HTML could not be parsed into SX")) + (assert= true (eval-hs "'Hello World' ends with 'world' ignoring case")) + (assert= true (eval-hs "'Hello World' ends with 'WORLD' ignoring case")) + (assert= false (eval-hs "'Hello World' ends with 'hello' ignoring case")) + ) (deftest "starts with coerces to string" - (error "NOT IMPLEMENTED: test HTML could not be parsed into SX")) + (assert= true (eval-hs "123 starts with '12'")) + (assert= false (eval-hs "123 starts with '23'")) + ) (deftest "ends with coerces to string" - (error "NOT IMPLEMENTED: test HTML could not be parsed into SX")) + (assert= true (eval-hs "123 ends with '23'")) + (assert= false (eval-hs "123 ends with '12'")) + ) (deftest "is between works" (assert= true (eval-hs "5 is between 1 and 10")) (assert= true (eval-hs "1 is between 1 and 10")) @@ -7527,7 +7587,9 @@ (assert= false (eval-hs "10 is not between 1 and 10")) ) (deftest "between works with strings" - (error "NOT IMPLEMENTED: test HTML could not be parsed into SX")) + (assert= true (eval-hs "'b' is between 'a' and 'c'")) + (assert= false (eval-hs "'d' is between 'a' and 'c'")) + ) (deftest "I am between works" (error "NOT IMPLEMENTED: test HTML could not be parsed into SX")) (deftest "I am not between works" @@ -7585,8 +7647,10 @@ )) (deftest "is really works without equal to" (assert= true (eval-hs "2 is really 2")) + (assert= false (eval-hs "2 is really '2'")) ) (deftest "is not really works without equal to" + (assert= true (eval-hs "2 is not really '2'")) (assert= false (eval-hs "2 is not really 2")) ) (deftest "is equal works without to" @@ -7667,8 +7731,7 @@ ;; ── in (1 tests) ── (defsuite "hs-upstream-in" (deftest "null value in array returns empty" - ;; toEqual: [] - ) + (error "NOT IMPLEMENTED: test HTML could not be parsed into SX")) ) ;; ── logicalOperator (3 tests) ── @@ -7684,25 +7747,23 @@ ;; ── mathOperator (5 tests) ── (defsuite "hs-upstream-mathOperator" (deftest "array + array concats" - ;; toEqual: [1, 2, 3, 4] - ) - (deftest "array + single value appends" - ;; toEqual: [1, 2, 3] - ) - (deftest "array + array does not mutate original" (error "NOT IMPLEMENTED: test HTML could not be parsed into SX")) + (deftest "array + single value appends" + (error "NOT IMPLEMENTED: test HTML could not be parsed into SX")) + (deftest "array + array does not mutate original" + (assert= (list 1 2) (eval-hs "set a to [1, 2] then set b to a + [3] then return a")) + ) (deftest "array concat chains" - ;; toEqual: [1, 2, 3] - ) + (error "NOT IMPLEMENTED: test HTML could not be parsed into SX")) (deftest "empty array + array works" - ;; toEqual: [1, 2] - ) + (error "NOT IMPLEMENTED: test HTML could not be parsed into SX")) ) ;; ── no (5 tests) ── (defsuite "hs-upstream-no" (deftest "no returns false for non-empty array" - (error "NOT IMPLEMENTED: test HTML could not be parsed into SX")) + (assert= false (eval-hs "no ['thing']")) + ) (deftest "no with where filters then checks emptiness" (assert= true (eval-hs "no [1, 2, 3] where it > 5")) ) @@ -7710,7 +7771,8 @@ (assert= false (eval-hs "no [1, 2, 3] where it > 1")) ) (deftest "no with where and is not" - (error "NOT IMPLEMENTED: test HTML could not be parsed into SX")) + (assert= false (eval-hs "no [1, 2, 3] where it is not 2")) + ) (deftest "no with where on DOM elements" (hs-cleanup!) (let ((_el-box (dom-create-element "div")) (_el-button (dom-create-element "button")) (_el-out (dom-create-element "div"))) @@ -7728,8 +7790,7 @@ ;; ── objectLiteral (1 tests) ── (defsuite "hs-upstream-objectLiteral" (deftest "allows trailing commas" - ;; toEqual: { "foo": true, "bar-baz": false } - ) + (error "NOT IMPLEMENTED: test HTML could not be parsed into SX")) ) ;; ── queryRef (1 tests) ── diff --git a/test-results/.last-run.json b/test-results/.last-run.json index cbcc1fba..ad15d7fb 100644 --- a/test-results/.last-run.json +++ b/test-results/.last-run.json @@ -1,4 +1,6 @@ { - "status": "passed", - "failedTests": [] + "status": "failed", + "failedTests": [ + "1c2c0c67218972a8b3e8-5ab09db25e2a06899363" + ] } \ No newline at end of file diff --git a/tests/playwright/generate-sx-tests.py b/tests/playwright/generate-sx-tests.py index c0d50ac8..beb9edfc 100644 --- a/tests/playwright/generate-sx-tests.py +++ b/tests/playwright/generate-sx-tests.py @@ -508,37 +508,116 @@ def generate_test_pw(test, elements, var_names, idx): return '\n'.join(lines) +def js_val_to_sx(val): + """Convert a JS literal value to SX.""" + val = val.strip() + if val == 'true': return 'true' + if val == 'false': return 'false' + if val in ('null', 'undefined'): return 'nil' + if val.startswith('"') or val.startswith("'"): + return '"' + val.strip("\"'") + '"' + # Arrays: [1, 2, 3] → (list 1 2 3) + if val.startswith('[') and val.endswith(']'): + inner = val[1:-1].strip() + if not inner: + return '(list)' + items = [js_val_to_sx(x.strip()) for x in split_top_level(inner)] + return '(list ' + ' '.join(items) + ')' + try: + float(val) + return val + except ValueError: + return f'"{val}"' + + +def split_top_level(s): + """Split a string by commas, respecting brackets/quotes.""" + parts = [] + depth = 0 + current = [] + in_str = None + for ch in s: + if in_str: + current.append(ch) + if ch == in_str: + in_str = None + elif ch in ('"', "'"): + in_str = ch + current.append(ch) + elif ch in ('(', '[', '{'): + depth += 1 + current.append(ch) + elif ch in (')', ']', '}'): + depth -= 1 + current.append(ch) + elif ch == ',' and depth == 0: + parts.append(''.join(current)) + current = [] + else: + current.append(ch) + if current: + parts.append(''.join(current)) + return parts + + +def extract_hs_expr(raw): + """Clean a HS expression extracted from run() call.""" + # Remove surrounding whitespace and newlines + expr = raw.strip().replace('\n', ' ').replace('\t', ' ') + # Collapse multiple spaces + expr = re.sub(r'\s+', ' ', expr) + # Escape quotes for SX string + expr = expr.replace('\\', '').replace('"', '\\"') + return expr + + def generate_eval_only_test(test, idx): """Generate SX deftest for no-HTML tests using eval-hs. - Parses body field for run("expr").toBe(val) / expect(run("expr")).toBe(val) patterns.""" + Handles patterns: + - run("expr").toBe(val) + - expect(run("expr")).toBe(val) + - var result = await run(`expr`); expect(result).toBe(val) + - run("expr").toEqual([...]) + - run("expr").toThrow() + """ body = test.get('body', '') lines = [] - lines.append(f' (deftest "{test["name"]}"') + safe_name = test["name"].replace('"', "'") + lines.append(f' (deftest "{safe_name}"') - # Extract run("expr").toBe(val) or expect(await run("expr")).toBe(val) patterns assertions = [] - for m in re.finditer(r'(?:expect\()?(?:await\s+)?run\(["\x27]([^"\x27]+)["\x27]\)\)?\.toBe\(([^)]+)\)', body): - hs_expr = m.group(1).replace('\\', '').replace('"', '\\"') - expected = m.group(2).strip() - # Convert JS values to SX - if expected == 'true': expected_sx = 'true' - elif expected == 'false': expected_sx = 'false' - elif expected == 'null' or expected == 'undefined': expected_sx = 'nil' - elif expected.startswith('"') or expected.startswith("'"): - expected_sx = '"' + expected.strip("\"'") + '"' - else: - try: - float(expected) - expected_sx = expected - except ValueError: - expected_sx = f'"{expected}"' + + # Pattern 1: Inline — run("expr").toBe(val) or expect(run("expr")).toBe(val) + for m in re.finditer( + r'(?:expect\()?(?:await\s+)?run\((["\x27`])(.+?)\1\)\)?\.toBe\(([^)]+)\)', + body, re.DOTALL + ): + hs_expr = extract_hs_expr(m.group(2)) + expected_sx = js_val_to_sx(m.group(3)) assertions.append(f' (assert= {expected_sx} (eval-hs "{hs_expr}"))') - # Also handle toEqual patterns - for m in re.finditer(r'(?:expect\()?(?:await\s+)?run\(["\x27]([^"\x27]+)["\x27]\)\)?\.toEqual\(([^)]+)\)', body): - hs_expr = m.group(1).replace('\\', '').replace('"', '\\"') - expected = m.group(2).strip() - assertions.append(f' ;; toEqual: {expected[:40]}') + # Pattern 2: Two-line — var result = await run(`expr`); expect(result).toBe(val) + if not assertions: + run_match = re.search( + r'(?:var|let|const)\s+\w+\s*=\s*(?:await\s+)?run\((["\x27`])(.+?)\1\)', + body, re.DOTALL + ) + if run_match: + hs_expr = extract_hs_expr(run_match.group(2)) + for m in re.finditer(r'\.toBe\(([^)]+)\)', body): + expected_sx = js_val_to_sx(m.group(1)) + assertions.append(f' (assert= {expected_sx} (eval-hs "{hs_expr}"))') + for m in re.finditer(r'\.toEqual\((\[.*?\])\)', body, re.DOTALL): + expected_sx = js_val_to_sx(m.group(1)) + assertions.append(f' (assert= {expected_sx} (eval-hs "{hs_expr}"))') + + # Pattern 3: toThrow — expect(() => run("expr")).toThrow() + for m in re.finditer( + r'run\((["\x27`])(.+?)\1\).*?\.toThrow\(\)', + body, re.DOTALL + ): + hs_expr = extract_hs_expr(m.group(2)) + assertions.append(f' (assert-throws (eval-hs "{hs_expr}"))') if not assertions: return None # Can't convert this body pattern