diff --git a/tests/playwright/generate-sx-tests.py b/tests/playwright/generate-sx-tests.py index 14fb2123..0fcae83e 100644 --- a/tests/playwright/generate-sx-tests.py +++ b/tests/playwright/generate-sx-tests.py @@ -1119,36 +1119,40 @@ def generate_eval_only_test(test, idx): if run_match: hs_expr = extract_hs_expr(run_match.group(2)) locals_str = run_match.group(3).strip() - # Parse locals: {key: val, ...} - local_bindings = [] + # Parse locals: {key: val, ...}. Collect (name, value-sx) pairs. + local_pairs = [] for lm in re.finditer(r'(\w+)\s*:\s*(.+?)(?:,\s*(?=\w+\s*:)|$)', locals_str): lname = lm.group(1) lval = js_val_to_sx(lm.group(2).strip().rstrip(',')) - local_bindings.append(f'({lname} {lval})') + local_pairs.append((lname, lval)) + # SX list of (symbol value) pairs for eval-hs-locals + locals_sx = '(list ' + ' '.join(f'(list (quote {n}) {v})' for n, v in local_pairs) + ')' if local_pairs else '(list)' - # Find expect().toBe() or .toEqual() + # Find expect().toBe() or .toEqual(). eval-hs/eval-hs-locals return + # the final value of `it` after the script runs, so assert on the + # return value directly — `it` is not in the outer SX scope. for m in re.finditer(r'expect\([^)]*\)\.toBe\(([^)]+)\)', body): expected_sx = js_val_to_sx(m.group(1)) - if local_bindings: - assertions.append(f' (let ({" ".join(local_bindings)}) (eval-hs "{hs_expr}") (assert= it {expected_sx}))') + if local_pairs: + assertions.append(f' (assert= (eval-hs-locals "{hs_expr}" {locals_sx}) {expected_sx})') else: assertions.append(f' (assert= (eval-hs "{hs_expr}") {expected_sx})') for m in re.finditer(r'expect\([^)]*\)\.toEqual\((\[.*?\])\)', body, re.DOTALL): expected_sx = js_val_to_sx(m.group(1)) - if local_bindings: - assertions.append(f' (let ({" ".join(local_bindings)}) (eval-hs "{hs_expr}") (assert= it {expected_sx}))') + if local_pairs: + assertions.append(f' (assert= (eval-hs-locals "{hs_expr}" {locals_sx}) {expected_sx})') else: assertions.append(f' (assert= (eval-hs "{hs_expr}") {expected_sx})') for m in re.finditer(r'expect\([^)]*\)\.toContain\(([^)]+)\)', body): expected_sx = js_val_to_sx(m.group(1)) - if local_bindings: - assertions.append(f' (let ({" ".join(local_bindings)}) (eval-hs "{hs_expr}") (assert (not (nil? it))))') + if local_pairs: + assertions.append(f' (assert (not (nil? (eval-hs-locals "{hs_expr}" {locals_sx}))))') else: assertions.append(f' (assert (not (nil? (eval-hs "{hs_expr}"))))') for m in re.finditer(r'expect\([^)]*\)\.toHaveLength\((\d+)\)', body): length = m.group(1) - if local_bindings: - assertions.append(f' (let ({" ".join(local_bindings)}) (eval-hs "{hs_expr}") (assert= (len it) {length}))') + if local_pairs: + assertions.append(f' (assert= (len (eval-hs-locals "{hs_expr}" {locals_sx})) {length})') else: assertions.append(f' (assert= (len (eval-hs "{hs_expr}")) {length})') @@ -1461,7 +1465,7 @@ output.append('(define hs-cleanup!') output.append(' (fn ()') output.append(' (dom-set-inner-html (dom-body) "")))') output.append('') -output.append(';; Evaluate a hyperscript expression and return its result.') +output.append(';; Evaluate a hyperscript expression and return the last-expression value.') output.append(';; Compiles the expression, wraps in a thunk, evaluates, returns result.') output.append('(define eval-hs') output.append(' (fn (src)') @@ -1477,6 +1481,28 @@ output.append(' (nth _e 1)') output.append(' (raise _e))))') output.append(' (handler nil))))))') output.append('') +output.append(';; Evaluate a hyperscript expression with locals. bindings = list of (symbol value).') +output.append(';; The locals are injected as fn params so they resolve in the handler body.') +output.append('(define eval-hs-locals') +output.append(' (fn (src bindings)') +output.append(' (let ((sx (hs-to-sx (hs-compile src))))') +output.append(' (let ((names (map (fn (b) (first b)) bindings))') +output.append(' (vals (map (fn (b) (nth b 1)) bindings)))') +output.append(' (let ((param-list (cons (quote me) names)))') +output.append(' (let ((wrapper (list (quote fn) param-list') +output.append(' (list (quote let)') +output.append(' (list (list (quote it) nil) (list (quote event) nil))') +output.append(' sx (quote it)))))') +output.append(' (let ((handler (eval-expr-cek wrapper)))') +output.append(' (guard') +output.append(' (_e') +output.append(' (true') +output.append(' (if') +output.append(' (and (list? _e) (= (first _e) "hs-return"))') +output.append(' (nth _e 1)') +output.append(' (raise _e))))') +output.append(' (apply handler (cons nil vals))))))))))') +output.append('') output.append(';; Evaluate with a specific me value (for "I am between" etc.)') output.append('(define eval-hs-with-me') output.append(' (fn (src me-val)')