This commit is contained in:
2026-06-08 13:21:57 +02:00
parent 823130e3ac
commit 8bee76328b
23 changed files with 734 additions and 382 deletions
+7 -20
View File
@@ -1,25 +1,12 @@
#lang racket/base
(require rackunit
"../main.rkt"
(require "../demo/dom-exercises.rkt"
"jsmaker-test-framework.rkt")
(provide dom-tests)
(check-contains 'dom-query-selector "document.querySelector(\"p\")" exercise01)
(check-contains 'dom-inner-html "p.innerHTML" exercise01)
(check-contains 'dom-insert-adjacent-html "insertAdjacentHTML" exercise02)
(check-contains 'dom-text-content "textContent" exercise03)
(define dom-snippet
(js
(define title (send document getElementById "title"))
(set! (js-dot title innerHTML) "Hello")
(send title addEventListener "click" (lambda (evt) (return #t)))))
(define dom-tests
(test-suite
"DOM-like JavaScript generation"
(test-case "send and js-dot generate method calls and property assignment"
(check-js-contains? dom-snippet "document.getElementById(\"title\")")
(check-js-contains? dom-snippet "title.innerHTML = \"Hello\";")
(check-js-contains? dom-snippet "title.addEventListener"))))
(module+ test
(require rackunit/text-ui)
(run-tests dom-tests))
(module+ main
(test-summary 'jsmaker-dom-exercises))
+3 -2
View File
@@ -1,4 +1,5 @@
#lang racket/base
(require "jsmaker-test-framework.rkt")
(provide node-available? run-js/trimmed)
;; Kept as a compatibility source file for the package layout. js-maker 3 uses
;; the small optional Node runner in jsmaker-test-framework.rkt.
(provide)
+4 -16
View File
@@ -1,18 +1,6 @@
#lang racket/base
(require rackunit
"../main.rkt"
"jsmaker-test-framework.rkt")
(provide object-tests)
(define object-tests
(test-suite
"object construction regression tests"
(test-case "new and method calls"
(check-js-equal? (js (new Date)) "new Date();\n")
(check-js-equal? (js (send console log "ok")) "console.log(\"ok\");\n"))))
(module+ test
(require rackunit/text-ui)
(run-tests object-tests))
;; The old js-maker 2 hash tests covered a runtime library that is intentionally
;; not part of the compact js-maker 3 restart. See README.md for the retained
;; and removed test categories.
(provide)
+13 -15
View File
@@ -1,21 +1,19 @@
#lang racket/base
(require rackunit
"../main.rkt"
(require "../main.rkt"
"jsmaker-test-framework.rkt")
(provide list-tests)
(define list-program
(string-append
(js (define (makeList) (return (list 1 2 3))))
"\nconsole.log(JSON.stringify(makeList()));\n"))
(run-js-if-available 'list-runtime list-program "[1,2,3]")
(define list-tests
(test-suite
"list and quoted datum generation"
(test-case "list and cons"
(check-js-equal? (js (list 1 2 3)) "[1, 2, 3];\n")
(check-js-equal? (js (cons 1 (list 2 3))) "[1].concat([2, 3]);\n"))
(test-case "quoted data"
(check-js-equal? (js (quote alpha)) "\"alpha\";\n")
(check-js-equal? (js (quote (1 2 x))) "[1, 2, \"x\"];\n"))))
(define cons-program
(string-append
(js (define (prepend xs) (return (cons 1 xs))))
"\nconsole.log(JSON.stringify(prepend([2,3])));\n"))
(run-js-if-available 'cons-runtime cons-program "[1,2,3]")
(module+ test
(require rackunit/text-ui)
(run-tests list-tests))
(module+ main
(test-summary 'jsmaker-list-regression))
+41 -30
View File
@@ -1,51 +1,62 @@
#lang racket/base
(require rackunit
"../main.rkt"
(require "../main.rkt"
"jsmaker-test-framework.rkt")
(provide program-tests)
(define ordinary-let-program
(string-append
(js (define (ordinary-let x)
(js (define (ordinaryLet x)
(let ([x 1] [y x])
(return y))))
"console.log(ordinary_let(99));\n"))
"\nconsole.log(JSON.stringify(ordinaryLet(99)));\n"))
(run-js-if-available 'ordinary-let-runtime ordinary-let-program "99")
(define sequential-let-program
(define let-star-program
(string-append
(js (define (sequential-let x)
(js (define (sequentialLet x)
(let* ([x 1] [y x])
(return y))))
"console.log(sequential_let(99));\n"))
"\nconsole.log(JSON.stringify(sequentialLet(99)));\n"))
(run-js-if-available 'let-star-runtime let-star-program "1")
(define named-let-program
(string-append
(js (define (sum-to n)
(js (define (sumTo n)
(let loop ([i 0] [acc 0])
(if (> i n)
(return acc)
(loop (+ i 1) (+ acc i))))))
"console.log(sum_to(10));\n"))
"\nconsole.log(JSON.stringify(sumTo(10)));\n"))
(run-js-if-available 'named-let-runtime named-let-program "55")
(define program-tests
(test-suite
"generated JavaScript program behavior"
(test-case "ordinary let uses parallel Racket binding semantics"
(check-js-contains? ordinary-let-program "const")
(check-js-contains? ordinary-let-program "let x")
(when (node-available?)
(check-equal? (run-js/trimmed ordinary-let-program) "99")))
(test-case "let* uses sequential binding semantics"
(when (node-available?)
(check-equal? (run-js/trimmed sequential-let-program) "1")))
(test-case "named let compiles to a while loop with parallel updates"
(check-js-contains? named-let-program "while (true)")
(check-js-contains? named-let-program "continue;")
(when (node-available?)
(check-equal? (run-js/trimmed named-let-program) "55")))))
(define ref-program
(string-append
(js (define (at xs i)
(return (js-ref xs i))))
"\nconsole.log(JSON.stringify(at([10,20,30],1)));\n"))
(run-js-if-available 'ref-runtime ref-program "20")
(module+ test
(require rackunit/text-ui)
(run-tests program-tests))
(define ref-set-program
(string-append
(js (define (put xs i value)
(set! (js-ref xs i) value)
(return xs)))
"\nconsole.log(JSON.stringify(put([1,2,3],1,9)));\n"))
(run-js-if-available 'ref-set-runtime ref-set-program "[1,9,3]")
(define ref-string-key-program
(string-append
(js (define (nameOf obj)
(return (js-ref obj "name"))))
"\nconsole.log(JSON.stringify(nameOf({name:\"Ada\"})));\n"))
(run-js-if-available 'ref-string-key-runtime ref-string-key-program "\"Ada\"")
(define lambda-program
(string-append
(js (define (makeAdder x)
(return (lambda (y) (return (+ x y))))))
"\nconsole.log(JSON.stringify(makeAdder(2)(3)));\n"))
(run-js-if-available 'lambda-runtime lambda-program "5")
(module+ main
(test-summary 'jsmaker-program-regression))
+4 -17
View File
@@ -1,19 +1,6 @@
#lang racket/base
(require rackunit
"../main.rkt"
"jsmaker-test-framework.rkt")
(provide regexp-tests)
(define regexp-tests
(test-suite
"string escaping regression tests"
(test-case "strings are JavaScript escaped"
(check-js-equal? (js "a\"b") "\"a\\\"b\";\n")
(check-js-equal? (js "a\\b") "\"a\\\\b\";\n")
(check-js-equal? (js "a\nb") "\"a\\nb\";\n"))))
(module+ test
(require rackunit/text-ui)
(run-tests regexp-tests))
;; The old regexp tests depended on a JavaScript regexp runtime shim. The new
;; js-maker 3 core does not include that shim. See README.md for the retained
;; and removed test categories.
(provide)
+71 -28
View File
@@ -1,35 +1,78 @@
#lang racket/base
(require rackunit
racket/runtime-path
"../main.rkt"
(require "../main.rkt"
"jsmaker-test-framework.rkt")
(provide regression-tests)
(check-public-api)
(define-runtime-path main-module "../main.rkt")
(define simple-function
(js (define (add1 x) (return (+ x 1)))))
(check-contains 'simple-function "function add1(x)" simple-function)
(check-contains 'simple-function-return "return x + 1;" simple-function)
(define regression-tests
(test-suite
"core js macro output"
(test-case "the public API exports js only"
(check-exn exn:fail? (lambda () (dynamic-require main-module 'js1)))
(check-exn exn:fail? (lambda () (dynamic-require main-module 'js/expression))))
(test-case "arithmetic and boolean expressions can be emitted as statements"
(check-js-equal? (js (+ 1 2)) "1 + 2;\n")
(check-js-equal? (js (and a b)) "a && b;\n")
(check-js-equal? (js (not ready)) "!(ready);\n"))
(test-case "value and function definitions"
(check-js-equal? (js (define answer 42)) "let answer = 42;\n")
(check-js-contains?
(js (define (square x) (return (* x x))))
"function square(x)"))
(test-case "conditionals and begin blocks"
(define out (js (if (> x 0) (return x) (return 0))))
(check-js-contains? out "if (x > 0)")
(check-js-contains? out "return x;")
(check-js-contains? (js (begin (set! x 1) (return x))) "x = 1;"))))
(define escaped-string
(js (define (message) (return "regel 1\nregel 2 \"ok\""))))
(check-contains 'string-newline "regel 1\\nregel 2" escaped-string)
(check-contains 'string-quote "\\\"ok\\\"" escaped-string)
(module+ test
(require rackunit/text-ui)
(run-tests regression-tests))
(define list-program
(js (define (values) (return (list 1 "a" #t #f)))))
(check-contains 'list-literal "return [1, \"a\", true, false];" list-program)
(define cons-program
(js (define (prepend xs) (return (cons 1 xs)))))
(check-contains 'cons-generation "[1].concat(xs)" cons-program)
(define send-program
(js (define (unique xs) (return (send Array from (new Set xs))))))
(check-contains 'send-generation "Array.from(new Set(xs))" send-program)
(define dot-set-program
(js (define (setHtml el html) (set! (js-dot el innerHTML) html) (return (js-dot el innerHTML)))))
(check-contains 'dot-set "el.innerHTML = html;" dot-set-program)
(check-contains 'dot-return "return el.innerHTML;" dot-set-program)
(define ref-program
(js (define (at xs i) (return (js-ref xs i)))))
(check-contains 'ref-variable-index "return xs[i];" ref-program)
(define ref-string-key-program
(js (define (nameOf obj) (return (js-ref obj "name")))))
(check-contains 'ref-string-key "return obj[\"name\"];" ref-string-key-program)
(define ref-set-program
(js (define (put xs i value) (set! (js-ref xs i) value) (return xs))))
(check-contains 'ref-set "xs[i] = value;" ref-set-program)
(define ref-nested-program
(js (define (nested matrix r c) (return (js-ref matrix r c)))))
(check-contains 'ref-nested "return matrix[r][c];" ref-nested-program)
(define ordinary-let
(js (define (ordinaryLet x)
(let ([x 1] [y x])
(return y)))))
(check-matches 'ordinary-let-temp #rx"const .* = 1;" ordinary-let)
(check-contains 'ordinary-let-inner "{\nlet x =" ordinary-let)
(check-contains 'ordinary-let-inner-y "\nlet y =" ordinary-let)
(check-contains 'ordinary-let-return "return y;" ordinary-let)
(define let-star
(js (define (sequentialLet x)
(let* ([x 1] [y x])
(return y)))))
(check-contains 'let-star-x "let x = 1;" let-star)
(check-contains 'let-star-y "let y = x;" let-star)
(define named-let
(js (define (sumTo n)
(let loop ([i 0] [acc 0])
(if (> i n)
(return acc)
(loop (+ i 1) (+ acc i)))))))
(check-contains 'named-let-while "while (true)" named-let)
(check-contains 'named-let-continue "continue;" named-let)
(check-matches 'named-let-parallel-update #rx"const .* = i \\+ 1;" named-let)
(module+ main
(test-summary 'jsmaker-regression))
+6 -20
View File
@@ -1,28 +1,14 @@
#lang racket/base
(require rackunit
rackunit/text-ui
(require "jsmaker-test-framework.rkt"
"jsmaker-regression.rkt"
"jsmaker-program-regression.rkt"
"jsmaker-dom-exercises.rkt"
"jsmaker-list-regression.rkt"
"jsmaker-hash-regression.rkt"
"jsmaker-regexp-regression.rkt"
"jsmaker-usecases.rkt"
"jsmaker-hash-regression.rkt")
(define all-tests
(test-suite
"js-maker 3 regression suite"
regression-tests
program-tests
dom-tests
list-tests
regexp-tests
usecase-tests
object-tests))
"jsmaker-dom-exercises.rkt"
"jsmaker-usecases.rkt")
(module+ main
(void (run-tests all-tests)))
(module+ test
(void (run-tests all-tests)))
(test-summary 'jsmaker-regressions)
(displayln "js-maker regression suite completed."))
+107 -49
View File
@@ -1,57 +1,115 @@
#lang racket/base
(require rackunit
racket/string
racket/file
racket/system)
(require racket/file
racket/format
racket/list
racket/port
racket/runtime-path
racket/string)
(provide check-js-equal?
check-js-contains?
check-js-matches?
node-available?
run-js/trimmed)
(provide check-true
check-equal
check-matches
check-not-matches
check-contains
check-not-contains
check-public-api
run-js-if-available
note-dropped
test-summary)
(define-check (check-js-equal? actual expected)
(check-equal? actual expected))
(define checks-run 0)
(define checks-skipped 0)
(define-check (check-js-contains? actual needle)
(check-true (string-contains? actual needle)
(format "expected generated JavaScript to contain ~s, got:\n~a" needle actual)))
(define (bump!) (set! checks-run (add1 checks-run)))
(define (skip!) (set! checks-skipped (add1 checks-skipped)))
(define-check (check-js-matches? actual pattern)
(check-true (regexp-match? pattern actual)
(format "expected generated JavaScript to match ~s, got:\n~a" pattern actual)))
(define (fail name fmt . args)
(error name (apply format fmt args)))
(define (node-available?)
(and (find-executable-path "node") #t))
(define (check-true name value)
(bump!)
(unless value (fail name "check failed")))
(define (run-js/trimmed program)
(define node (find-executable-path "node"))
(unless node
(error 'run-js/trimmed "node is not available"))
(define source-path (make-temporary-file "js-maker-test-~a.js"))
(define out-path (make-temporary-file "js-maker-test-out-~a.txt"))
(define err-path (make-temporary-file "js-maker-test-err-~a.txt"))
(dynamic-wind
void
(lambda ()
(call-with-output-file source-path #:exists 'truncate
(lambda (out) (display program out)))
(define exit-code
(call-with-output-file out-path #:exists 'truncate
(lambda (out)
(call-with-output-file err-path #:exists 'truncate
(lambda (err)
(parameterize ([current-output-port out]
[current-error-port err])
(system*/exit-code node source-path)))))))
(define stdout (file->string out-path))
(define stderr (file->string err-path))
(unless (zero? exit-code)
(error 'run-js/trimmed
"node failed with exit code ~a\nstdout:\n~a\nstderr:\n~a\nprogram:\n~a"
exit-code stdout stderr program))
(string-trim stdout))
(lambda ()
(for ([path (list source-path out-path err-path)])
(with-handlers ([exn:fail? void]) (delete-file path))))))
(define (check-equal name actual expected)
(bump!)
(unless (equal? actual expected)
(fail name "expected ~s, got ~s" expected actual)))
(define (check-matches name rx text)
(bump!)
(unless (regexp-match? rx text)
(fail name "expected generated text to match ~s, got:\n~a" rx text)))
(define (check-not-matches name rx text)
(bump!)
(when (regexp-match? rx text)
(fail name "expected generated text not to match ~s, got:\n~a" rx text)))
(define (check-contains name needle text)
(bump!)
(unless (string-contains? text needle)
(fail name "expected generated text to contain ~s, got:\n~a" needle text)))
(define (check-not-contains name needle text)
(bump!)
(when (string-contains? text needle)
(fail name "expected generated text not to contain ~s, got:\n~a" needle text)))
(define-runtime-path main-path "../main.rkt")
(define (all-symbols v)
(cond [(symbol? v) (list v)]
[(pair? v) (append (all-symbols (car v)) (all-symbols (cdr v)))]
[else null]))
(define (check-public-api)
(define mp `(file ,(path->string main-path)))
(dynamic-require mp #f)
(define-values (value-exports syntax-exports) (module->exports mp))
(define exports (remove-duplicates (append (all-symbols value-exports)
(all-symbols syntax-exports))))
(check-true 'public-api-js (memq 'js exports))
(check-true 'public-api-no-js1 (not (memq 'js1 exports)))
(check-true 'public-api-no-js-ref (not (memq 'js-ref exports)))
(check-true 'public-api-no-js/expression (not (memq 'js/expression exports))))
(define (candidate-node)
(define env-node (getenv "JSMAKER_NODE"))
(cond [(and env-node (not (string=? env-node ""))) env-node]
[else (find-executable-path "node")]))
(define (subprocess-output executable file)
(define-values (proc stdout stdin stderr)
(subprocess #f #f #f executable file))
(close-output-port stdin)
(define out (port->string stdout))
(define err (port->string stderr))
(subprocess-wait proc)
(values (subprocess-status proc) out err))
(define (run-js-if-available name program expected-stdout)
(define node (candidate-node))
(cond
[node
(bump!)
(define file (make-temporary-file "jsmaker-~a.js"))
(call-with-output-file file #:exists 'replace
(lambda (out) (display program out)))
(define-values (status stdout stderr) (subprocess-output node file))
(delete-file file)
(unless (and (zero? status) (string=? (string-trim stdout) expected-stdout))
(fail name "JavaScript execution failed; status=~a stdout=~s stderr=~s program:\n~a"
status stdout stderr program))]
[else
(skip!)
(printf "NOTE: ~a skipped because node was not found.\n" name)]))
(define (note-dropped topic reason)
(printf "NOTE: dropped old ~a tests: ~a\n" topic reason))
(define (test-summary who)
(printf "~a: ~a checks passed" who checks-run)
(unless (zero? checks-skipped)
(printf ", ~a JavaScript execution checks skipped" checks-skipped))
(newline))
+4 -1
View File
@@ -1,2 +1,5 @@
#lang racket/base
(require "jsmaker-regressions.rkt")
;; Kept as a compatibility source file for the package layout. Tests are run
;; through testing/jsmaker-regressions.rkt.
(provide)
+8 -27
View File
@@ -1,32 +1,13 @@
#lang racket/base
(require rackunit
"../main.rkt"
(require "../demo/js-usecases.rkt"
"jsmaker-test-framework.rkt")
(provide usecase-tests)
(check-contains 'usecase-random "Math.floor" usecase-random-number)
(check-contains 'usecase-unique "new Set" usecase-unique-values)
(check-contains 'usecase-array-at "return xs[i];" usecase-array-at)
(check-contains 'usecase-named-let "while (true)" usecase-sum-to)
(check-contains 'usecase-dom "getElementById" usecase-set-html)
(define counter-program
(string-append
(js (define (make-counter start)
(let ([value start])
(return (lambda ()
(begin
(set! value (+ value 1))
(return value)))))))
"const c = make_counter(5);\n"
"console.log(c());\n"
"console.log(c());\n"))
(define usecase-tests
(test-suite
"small use cases"
(test-case "closures and set!"
(check-js-contains? counter-program "function make_counter(start)")
(check-js-contains? counter-program "value = value + 1;")
(when (node-available?)
(check-equal? (run-js/trimmed counter-program) "6\n7")))))
(module+ test
(require rackunit/text-ui)
(run-tests usecase-tests))
(module+ main
(test-summary 'jsmaker-usecases))