// Auto-generated from test.sx — SX spec self-tests for JavaScript. // // DO NOT EDIT. Regenerate with: // python shared/sx/ref/bootstrap_test_js.py --output shared/sx/tests/test_sx_spec.js // // Run: // node shared/sx/tests/test_sx_spec.js // --- Load sx-browser.js (bootstrapped from spec) --- Object.defineProperty(globalThis, "document", { value: undefined, writable: true }); var _sxPath = require("path").resolve(__dirname, "../../static/scripts/sx-browser.js"); var Sx = require(_sxPath); // --- Inject spec primitives into env --- // sx-browser.js has assert but is missing some spec primitives. // We inject them into the test env since PRIMITIVES is not exposed. var NIL = Sx.NIL; function _isNil(x) { return x === NIL || x === null || x === undefined; } function _deepEqual(a, b) { if (a === b) return true; if (_isNil(a) && _isNil(b)) return true; if (typeof a !== typeof b) return false; if (Array.isArray(a) && Array.isArray(b)) { if (a.length !== b.length) return false; for (var i = 0; i < a.length; i++) if (!_deepEqual(a[i], b[i])) return false; return true; } if (a && typeof a === "object" && b && typeof b === "object") { var ka = Object.keys(a), kb = Object.keys(b); if (ka.length !== kb.length) return false; for (var j = 0; j < ka.length; j++) if (!_deepEqual(a[ka[j]], b[ka[j]])) return false; return true; } return false; } // Primitives injected into every test env var _envPrimitives = { "equal?": function(a, b) { return _deepEqual(a, b); }, "eq?": function(a, b) { return a === b; }, "boolean?": function(x) { return typeof x === "boolean"; }, "string-length": function(s) { return String(s).length; }, "substring": function(s, start, end) { return String(s).slice(start, end); }, "string-contains?": function(s, needle) { return String(s).indexOf(needle) !== -1; }, "upcase": function(s) { return String(s).toUpperCase(); }, "downcase": function(s) { return String(s).toLowerCase(); }, "reverse": function(c) { return c ? c.slice().reverse() : []; }, "flatten": function(c) { var r = []; for (var i = 0; i < (c||[]).length; i++) { if (Array.isArray(c[i])) for (var j = 0; j < c[i].length; j++) r.push(c[i][j]); else r.push(c[i]); } return r; }, "has-key?": function(d, k) { return d && typeof d === "object" && k in d; }, // Fix append to concatenate when x is a list "append": function(c, x) { return Array.isArray(x) ? (c||[]).concat(x) : (c||[]).concat([x]); }, }; // --- Test infrastructure --- var _passed = 0, _failed = 0, _errors = []; var _testNum = 0; function _makeEnv() { var env = {}; // Copy injected primitives into env for (var k in _envPrimitives) env[k] = _envPrimitives[k]; var src = '(define assert-equal (fn (expected actual) (assert (equal? expected actual) (str "Expected " (str expected) " but got " (str actual)))))\n(define assert-not-equal (fn (a b) (assert (not (equal? a b)) (str "Expected values to differ but both are " (str a)))))\n(define assert-true (fn (val) (assert val (str "Expected truthy but got " (str val)))))\n(define assert-false (fn (val) (assert (not val) (str "Expected falsy but got " (str val)))))\n(define assert-nil (fn (val) (assert (nil? val) (str "Expected nil but got " (str val)))))\n(define assert-type (fn (expected-type val) (let ((actual-type (if (nil? val) "nil" (if (boolean? val) "boolean" (if (number? val) "number" (if (string? val) "string" (if (list? val) "list" (if (dict? val) "dict" "unknown")))))))) (assert (= expected-type actual-type) (str "Expected type " expected-type " but got " actual-type)))))\n(define assert-length (fn (expected-len col) (assert (= (len col) expected-len) (str "Expected length " expected-len " but got " (len col)))))\n(define assert-contains (fn (item col) (assert (some (fn (x) (equal? x item)) col) (str "Expected collection to contain " (str item)))))\n(define assert-throws (fn (thunk) (platform-assert-throws thunk)))'; var exprs = Sx.parseAll(src); for (var i = 0; i < exprs.length; i++) Sx.eval(exprs[i], env); return env; } function _run(name, sxSource) { _testNum++; try { var env = _makeEnv(); var exprs = Sx.parseAll(sxSource); for (var i = 0; i < exprs.length; i++) Sx.eval(exprs[i], env); _passed++; console.log("ok " + _testNum + " - " + name); } catch (e) { _failed++; _errors.push({ name: name, error: e.message || String(e) }); console.log("not ok " + _testNum + " - " + name); console.log(" # " + (e.message || String(e))); } } console.log("TAP version 13"); console.log("1..81"); // --- literals --- _run('literals > numbers are numbers', '(do (assert-type "number" 42) (assert-type "number" 3.14) (assert-type "number" -1))'); _run('literals > strings are strings', '(do (assert-type "string" "hello") (assert-type "string" ""))'); _run('literals > booleans are booleans', '(do (assert-type "boolean" true) (assert-type "boolean" false))'); _run('literals > nil is nil', '(do (assert-type "nil" nil) (assert-nil nil))'); _run('literals > lists are lists', '(do (assert-type "list" (list 1 2 3)) (assert-type "list" (list)))'); _run('literals > dicts are dicts', '(assert-type "dict" {:a 1 :b 2})'); // --- arithmetic --- _run('arithmetic > addition', '(do (assert-equal 3 (+ 1 2)) (assert-equal 0 (+ 0 0)) (assert-equal -1 (+ 1 -2)) (assert-equal 10 (+ 1 2 3 4)))'); _run('arithmetic > subtraction', '(do (assert-equal 1 (- 3 2)) (assert-equal -1 (- 2 3)))'); _run('arithmetic > multiplication', '(do (assert-equal 6 (* 2 3)) (assert-equal 0 (* 0 100)) (assert-equal 24 (* 1 2 3 4)))'); _run('arithmetic > division', '(do (assert-equal 2 (/ 6 3)) (assert-equal 2.5 (/ 5 2)))'); _run('arithmetic > modulo', '(do (assert-equal 1 (mod 7 3)) (assert-equal 0 (mod 6 3)))'); // --- comparison --- _run('comparison > equality', '(do (assert-true (= 1 1)) (assert-false (= 1 2)) (assert-true (= "a" "a")) (assert-false (= "a" "b")))'); _run('comparison > deep equality', '(do (assert-true (equal? (list 1 2 3) (list 1 2 3))) (assert-false (equal? (list 1 2) (list 1 3))) (assert-true (equal? {:a 1} {:a 1})) (assert-false (equal? {:a 1} {:a 2})))'); _run('comparison > ordering', '(do (assert-true (< 1 2)) (assert-false (< 2 1)) (assert-true (> 2 1)) (assert-true (<= 1 1)) (assert-true (<= 1 2)) (assert-true (>= 2 2)) (assert-true (>= 3 2)))'); // --- strings --- _run('strings > str concatenation', '(do (assert-equal "abc" (str "a" "b" "c")) (assert-equal "hello world" (str "hello" " " "world")) (assert-equal "42" (str 42)) (assert-equal "" (str)))'); _run('strings > string-length', '(do (assert-equal 5 (string-length "hello")) (assert-equal 0 (string-length "")))'); _run('strings > substring', '(do (assert-equal "ell" (substring "hello" 1 4)) (assert-equal "hello" (substring "hello" 0 5)))'); _run('strings > string-contains?', '(do (assert-true (string-contains? "hello world" "world")) (assert-false (string-contains? "hello" "xyz")))'); _run('strings > upcase and downcase', '(do (assert-equal "HELLO" (upcase "hello")) (assert-equal "hello" (downcase "HELLO")))'); _run('strings > trim', '(do (assert-equal "hello" (trim " hello ")) (assert-equal "hello" (trim "hello")))'); _run('strings > split and join', '(do (assert-equal (list "a" "b" "c") (split "a,b,c" ",")) (assert-equal "a-b-c" (join "-" (list "a" "b" "c"))))'); // --- lists --- _run('lists > constructors', '(do (assert-equal (list 1 2 3) (list 1 2 3)) (assert-equal (list) (list)) (assert-length 3 (list 1 2 3)))'); _run('lists > first and rest', '(do (assert-equal 1 (first (list 1 2 3))) (assert-equal (list 2 3) (rest (list 1 2 3))) (assert-nil (first (list))) (assert-equal (list) (rest (list))))'); _run('lists > nth', '(do (assert-equal 1 (nth (list 1 2 3) 0)) (assert-equal 2 (nth (list 1 2 3) 1)) (assert-equal 3 (nth (list 1 2 3) 2)))'); _run('lists > last', '(do (assert-equal 3 (last (list 1 2 3))) (assert-nil (last (list))))'); _run('lists > cons and append', '(do (assert-equal (list 0 1 2) (cons 0 (list 1 2))) (assert-equal (list 1 2 3 4) (append (list 1 2) (list 3 4))))'); _run('lists > reverse', '(do (assert-equal (list 3 2 1) (reverse (list 1 2 3))) (assert-equal (list) (reverse (list))))'); _run('lists > empty?', '(do (assert-true (empty? (list))) (assert-false (empty? (list 1))))'); _run('lists > len', '(do (assert-equal 0 (len (list))) (assert-equal 3 (len (list 1 2 3))))'); _run('lists > contains?', '(do (assert-true (contains? (list 1 2 3) 2)) (assert-false (contains? (list 1 2 3) 4)))'); _run('lists > flatten', '(assert-equal (list 1 2 3 4) (flatten (list (list 1 2) (list 3 4))))'); // --- dicts --- _run('dicts > dict literal', '(do (assert-type "dict" {:a 1 :b 2}) (assert-equal 1 (get {:a 1} "a")) (assert-equal 2 (get {:a 1 :b 2} "b")))'); _run('dicts > assoc', '(do (assert-equal {:a 1 :b 2} (assoc {:a 1} "b" 2)) (assert-equal {:a 99} (assoc {:a 1} "a" 99)))'); _run('dicts > dissoc', '(assert-equal {:b 2} (dissoc {:a 1 :b 2} "a"))'); _run('dicts > keys and vals', '(let ((d {:a 1 :b 2})) (assert-length 2 (keys d)) (assert-length 2 (vals d)) (assert-contains "a" (keys d)) (assert-contains "b" (keys d)))'); _run('dicts > has-key?', '(do (assert-true (has-key? {:a 1} "a")) (assert-false (has-key? {:a 1} "b")))'); _run('dicts > merge', '(do (assert-equal {:a 1 :b 2 :c 3} (merge {:a 1 :b 2} {:c 3})) (assert-equal {:a 99 :b 2} (merge {:a 1 :b 2} {:a 99})))'); // --- predicates --- _run('predicates > nil?', '(do (assert-true (nil? nil)) (assert-false (nil? 0)) (assert-false (nil? false)) (assert-false (nil? "")))'); _run('predicates > number?', '(do (assert-true (number? 42)) (assert-true (number? 3.14)) (assert-false (number? "42")))'); _run('predicates > string?', '(do (assert-true (string? "hello")) (assert-false (string? 42)))'); _run('predicates > list?', '(do (assert-true (list? (list 1 2))) (assert-false (list? "not a list")))'); _run('predicates > dict?', '(do (assert-true (dict? {:a 1})) (assert-false (dict? (list 1))))'); _run('predicates > boolean?', '(do (assert-true (boolean? true)) (assert-true (boolean? false)) (assert-false (boolean? nil)) (assert-false (boolean? 0)))'); _run('predicates > not', '(do (assert-true (not false)) (assert-true (not nil)) (assert-false (not true)) (assert-false (not 1)) (assert-false (not "x")))'); // --- special-forms --- _run('special-forms > if', '(do (assert-equal "yes" (if true "yes" "no")) (assert-equal "no" (if false "yes" "no")) (assert-equal "no" (if nil "yes" "no")) (assert-nil (if false "yes")))'); _run('special-forms > when', '(do (assert-equal "yes" (when true "yes")) (assert-nil (when false "yes")))'); _run('special-forms > cond', '(do (assert-equal "a" (cond true "a" :else "b")) (assert-equal "b" (cond false "a" :else "b")) (assert-equal "c" (cond false "a" false "b" :else "c")))'); _run('special-forms > and', '(do (assert-true (and true true)) (assert-false (and true false)) (assert-false (and false true)) (assert-equal 3 (and 1 2 3)))'); _run('special-forms > or', '(do (assert-equal 1 (or 1 2)) (assert-equal 2 (or false 2)) (assert-equal "fallback" (or nil false "fallback")) (assert-false (or false false)))'); _run('special-forms > let', '(do (assert-equal 3 (let ((x 1) (y 2)) (+ x y))) (assert-equal "hello world" (let ((a "hello") (b " world")) (str a b))))'); _run('special-forms > let clojure-style', '(assert-equal 3 (let (x 1 y 2) (+ x y)))'); _run('special-forms > do / begin', '(do (assert-equal 3 (do 1 2 3)) (assert-equal "last" (begin "first" "middle" "last")))'); _run('special-forms > define', '(do (define x 42) (assert-equal 42 x))'); _run('special-forms > set!', '(do (define x 1) (set! x 2) (assert-equal 2 x))'); // --- lambdas --- _run('lambdas > basic lambda', '(let ((add (fn (a b) (+ a b)))) (assert-equal 3 (add 1 2)))'); _run('lambdas > closure captures env', '(let ((x 10)) (let ((add-x (fn (y) (+ x y)))) (assert-equal 15 (add-x 5))))'); _run('lambdas > lambda as argument', '(assert-equal (list 2 4 6) (map (fn (x) (* x 2)) (list 1 2 3)))'); _run('lambdas > recursive lambda via define', '(do (define factorial (fn (n) (if (<= n 1) 1 (* n (factorial (- n 1)))))) (assert-equal 120 (factorial 5)))'); _run('lambdas > higher-order returns lambda', '(let ((make-adder (fn (n) (fn (x) (+ n x))))) (let ((add5 (make-adder 5))) (assert-equal 8 (add5 3))))'); // --- higher-order --- _run('higher-order > map', '(do (assert-equal (list 2 4 6) (map (fn (x) (* x 2)) (list 1 2 3))) (assert-equal (list) (map (fn (x) x) (list))))'); _run('higher-order > filter', '(do (assert-equal (list 2 4) (filter (fn (x) (= (mod x 2) 0)) (list 1 2 3 4))) (assert-equal (list) (filter (fn (x) false) (list 1 2 3))))'); _run('higher-order > reduce', '(do (assert-equal 10 (reduce (fn (acc x) (+ acc x)) 0 (list 1 2 3 4))) (assert-equal 0 (reduce (fn (acc x) (+ acc x)) 0 (list))))'); _run('higher-order > some', '(do (assert-true (some (fn (x) (> x 3)) (list 1 2 3 4 5))) (assert-false (some (fn (x) (> x 10)) (list 1 2 3))))'); _run('higher-order > every?', '(do (assert-true (every? (fn (x) (> x 0)) (list 1 2 3))) (assert-false (every? (fn (x) (> x 2)) (list 1 2 3))))'); _run('higher-order > map-indexed', '(assert-equal (list "0:a" "1:b" "2:c") (map-indexed (fn (i x) (str i ":" x)) (list "a" "b" "c")))'); // --- components --- _run('components > defcomp creates component', '(do (defcomp ~test-comp (&key title) (div title)) (assert-true (not (nil? ~test-comp))))'); _run('components > component renders with keyword args', '(do (defcomp ~greeting (&key name) (span (str "Hello, " name "!"))) (assert-true (not (nil? ~greeting))))'); _run('components > component with children', '(do (defcomp ~box (&key &rest children) (div :class "box" children)) (assert-true (not (nil? ~box))))'); _run('components > component with default via or', '(do (defcomp ~label (&key text) (span (or text "default"))) (assert-true (not (nil? ~label))))'); // --- macros --- _run('macros > defmacro creates macro', '(do (defmacro unless (cond &rest body) (quasiquote (if (not (unquote cond)) (do (splice-unquote body))))) (assert-equal "yes" (unless false "yes")) (assert-nil (unless true "no")))'); _run('macros > quasiquote and unquote', '(let ((x 42)) (assert-equal (list 1 42 3) (quasiquote (1 (unquote x) 3))))'); _run('macros > splice-unquote', '(let ((xs (list 2 3 4))) (assert-equal (list 1 2 3 4 5) (quasiquote (1 (splice-unquote xs) 5))))'); // --- threading --- _run('threading > thread-first', '(do (assert-equal 8 (-> 5 (+ 1) (+ 2))) (assert-equal "HELLO" (-> "hello" upcase)) (assert-equal "HELLO WORLD" (-> "hello" (str " world") upcase)))'); // --- truthiness --- _run('truthiness > truthy values', '(do (assert-true (if 1 true false)) (assert-true (if "x" true false)) (assert-true (if (list 1) true false)) (assert-true (if true true false)))'); _run('truthiness > falsy values', '(do (assert-false (if false true false)) (assert-false (if nil true false)))'); // --- edge-cases --- _run('edge-cases > nested let scoping', '(let ((x 1)) (let ((x 2)) (assert-equal 2 x)))'); _run('edge-cases > recursive map', '(assert-equal (list (list 2 4) (list 6 8)) (map (fn (sub) (map (fn (x) (* x 2)) sub)) (list (list 1 2) (list 3 4))))'); _run('edge-cases > keyword as value', '(do (assert-equal "class" :class) (assert-equal "id" :id))'); _run('edge-cases > dict with evaluated values', '(let ((x 42)) (assert-equal 42 (get {:val x} "val")))'); _run('edge-cases > nil propagation', '(do (assert-nil (get {:a 1} "missing")) (assert-equal "default" (or (get {:a 1} "missing") "default")))'); _run('edge-cases > empty operations', '(do (assert-equal (list) (map (fn (x) x) (list))) (assert-equal (list) (filter (fn (x) true) (list))) (assert-equal 0 (reduce (fn (acc x) (+ acc x)) 0 (list))) (assert-equal 0 (len (list))) (assert-equal "" (str)))'); // --- Summary --- console.log(""); console.log("# tests " + (_passed + _failed)); console.log("# pass " + _passed); if (_failed > 0) { console.log("# fail " + _failed); for (var ei = 0; ei < _errors.length; ei++) { console.log("# FAIL: " + _errors[ei].name + " — " + _errors[ei].error); } process.exit(1); }