#!/usr/bin/env node // WASM kernel integration tests: env sync, globals, pages parsing, preventDefault const path = require('path'); const fs = require('fs'); require(path.join(__dirname, '../_build/default/browser/sx_browser.bc.js')); const K = globalThis.SxKernel; // Load compiler for evalVM support const compilerFiles = ['lib/bytecode.sx', 'lib/compiler.sx', 'lib/vm.sx']; for (const f of compilerFiles) { K.load(fs.readFileSync(path.join(__dirname, '../../..', f), 'utf8')); } let passed = 0, failed = 0; function test(name, fn) { try { const result = fn(); if (result === true) { passed++; } else { console.log(` FAIL: ${name} — got ${JSON.stringify(result)}`); failed++; } } catch (e) { console.log(` FAIL: ${name} — ${e.message || e}`); failed++; } } // ================================================================ // 1. Env binding / globals sync // ================================================================ test('define at top level visible to VM', () => { K.eval('(define _test-toplevel-1 42)'); return K.evalVM('_test-toplevel-1') === 42; }); test('define in begin visible to VM', () => { K.eval('(begin (define _test-begin-1 99))'); return K.evalVM('_test-begin-1') === 99; }); test('set! on global syncs to VM', () => { K.eval('(define _test-set-g 1)'); K.eval('(set! _test-set-g 55)'); return K.evalVM('_test-set-g') === 55; }); test('VM define syncs back to CEK', () => { K.evalVM('(define _test-vm-def 777)'); return K.eval('_test-vm-def') === 777; }); test('CEK and VM see same value after multiple updates', () => { K.eval('(define _test-ping 0)'); K.eval('(set! _test-ping 1)'); K.evalVM('(set! _test-ping 2)'); const cek = K.eval('_test-ping'); const vm = K.evalVM('_test-ping'); return cek === 2 && vm === 2; }); test('lambda defined at top level callable from VM', () => { K.eval('(define _test-top-fn (fn (x) (* x 10)))'); return K.evalVM('(_test-top-fn 3)') === 30; }); // ================================================================ // 2. Parse function (pages-sx format) // ================================================================ test('parse single dict', () => { const r = K.eval('(get (parse "{:name \\"home\\" :path \\"/\\"}") "name")'); return r === 'home'; }); test('parse multiple dicts returns list', () => { const r = K.eval('(len (parse "{:a 1}\\n{:b 2}\\n{:c 3}"))'); return r === 3; }); test('parse single expr unwraps', () => { return K.eval('(type-of (parse "42"))') === 'number'; }); test('parse multiple exprs returns list', () => { return K.eval('(type-of (parse "1 2 3"))') === 'list'; }); test('parse dict with content string', () => { const r = K.eval('(get (parse "{:name \\"test\\" :content \\"(div \\\\\\\"hello\\\\\\\")\\" :has-data false}") "content")'); return typeof r === 'string' && r.includes('div'); }); test('parse dict with path param pattern', () => { const r = K.eval('(get (parse "{:path \\"/docs/\\"}") "path")'); return r === '/docs/'; }); // ================================================================ // 3. Route pattern parsing (requires router.sx loaded) // ================================================================ // Load router module const routerSrc = fs.readFileSync(path.join(__dirname, '../../../web/router.sx'), 'utf8'); K.load(routerSrc); test('parse-route-pattern splits static path', () => { const r = K.eval('(len (parse-route-pattern "/docs/intro"))'); return r === 2; }); test('parse-route-pattern detects param segments', () => { const r = K.eval('(get (nth (parse-route-pattern "/docs/") 1) "type")'); return r === 'param'; }); test('parse-route-pattern detects literal segments', () => { const r = K.eval('(get (first (parse-route-pattern "/docs/")) "type")'); return r === 'literal'; }); test('find-matching-route matches static path', () => { K.eval('(define _test-routes (list (merge {:name "home" :path "/"} {:parsed (parse-route-pattern "/")})))'); const r = K.eval('(get (find-matching-route "/" _test-routes) "name")'); return r === 'home'; }); test('find-matching-route matches param path', () => { K.eval('(define _test-routes2 (list (merge {:name "doc" :path "/docs/"} {:parsed (parse-route-pattern "/docs/")})))'); const r = K.eval('(get (find-matching-route "/docs/intro" _test-routes2) "name")'); return r === 'doc'; }); test('find-matching-route returns nil for no match', () => { return K.eval('(nil? (find-matching-route "/unknown" _test-routes))') === true; }); // ================================================================ // 4. Click handler preventDefault pattern // ================================================================ // Register host FFI primitives (normally done by sx-platform.js) K.registerNative("host-global", function(args) { var name = args[0]; return (typeof name === 'string') ? globalThis[name] : undefined; }); K.registerNative("host-get", function(args) { var obj = args[0], key = args[1]; if (obj == null) return null; var v = obj[key]; return v === undefined ? null : v; }); K.registerNative("host-call", function(args) { var obj = args[0], method = args[1]; var callArgs = args.slice(2); if (obj == null || typeof obj[method] !== 'function') return null; try { return obj[method].apply(obj, callArgs); } catch(e) { return null; } }); K.registerNative("host-set!", function(args) { var obj = args[0], key = args[1], val = args[2]; if (obj != null) obj[key] = val; return null; }); test('host-call preventDefault on mock event', () => { let prevented = false; globalThis._testMockEvent = { preventDefault: () => { prevented = true; }, type: 'click', target: { tagName: 'A', getAttribute: () => '/test' } }; K.eval('(host-call (host-global "_testMockEvent") "preventDefault")'); delete globalThis._testMockEvent; return prevented === true; }); test('host-get reads property from JS object', () => { globalThis._testObj = { foo: 42 }; const r = K.eval('(host-get (host-global "_testObj") "foo")'); delete globalThis._testObj; return r === 42; }); test('host-set! writes property on JS object', () => { globalThis._testObj2 = { val: 0 }; K.eval('(host-set! (host-global "_testObj2") "val" 99)'); const r = globalThis._testObj2.val; delete globalThis._testObj2; return r === 99; }); test('click handler pattern: check target, prevent, navigate', () => { let prevented = false; let navigated = null; globalThis._testClickEvent = { preventDefault: () => { prevented = true; }, type: 'click', target: { tagName: 'A', href: '/about' } }; globalThis._testNavigate = (url) => { navigated = url; }; K.eval(` (let ((e (host-global "_testClickEvent"))) (let ((tag (host-get (host-get e "target") "tagName"))) (when (= tag "A") (host-call e "preventDefault") (host-call (host-global "_testNavigate") "call" nil (host-get (host-get e "target") "href"))))) `); delete globalThis._testClickEvent; delete globalThis._testNavigate; return prevented === true && navigated === '/about'; }); // ================================================================ // 5. Iterative cek_run — deep evaluation without stack overflow // ================================================================ test('deep recursion via foldl (100 iterations)', () => { const r = K.eval('(reduce + 0 (map (fn (x) x) (list ' + Array.from({length: 100}, (_, i) => i + 1).join(' ') + ')))'); return r === 5050; }); test('deeply nested let bindings', () => { // Build (let ((x0 0)) (let ((x1 (+ x0 1))) ... (let ((xN (+ xN-1 1))) xN))) let expr = 'x49'; for (let i = 49; i >= 0; i--) { const prev = i === 0 ? '0' : `(+ x${i-1} 1)`; expr = `(let ((x${i} ${prev})) ${expr})`; } return K.eval(expr) === 49; }); // ================================================================ // Results // ================================================================ console.log(`\n${passed} passed, ${failed} failed`); process.exit(failed > 0 ? 1 : 0);