Split monolithic test.sx into composable test specs: - test-framework.sx: deftest/defsuite macros + assertion helpers - test-eval.sx: core evaluator + primitives (81 tests) - test-parser.sx: parser + serializer + round-trips (39 tests) - test-router.sx: route matching from router.sx (18 tests) - test-render.sx: HTML adapter rendering (23 tests) Runners auto-discover specs and test whatever bootstrapped code is available. Usage: `run.js eval parser router` or just `run.js`. Legacy mode (`--legacy`) still runs monolithic test.sx. Router tests use bootstrapped functions (sx_ref.py / sx-browser.js) because the hand-written evaluator's flat-dict env model doesn't support set! mutation across lambda closure boundaries. JS: 161/161. Python: 159/161 (2 parser escape bugs found). Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
126 lines
4.9 KiB
Plaintext
126 lines
4.9 KiB
Plaintext
;; ==========================================================================
|
|
;; test-router.sx — Tests for client-side route matching
|
|
;;
|
|
;; Requires: test-framework.sx loaded first.
|
|
;; Modules tested: router.sx
|
|
;;
|
|
;; No additional platform functions needed — router.sx is pure.
|
|
;; ==========================================================================
|
|
|
|
|
|
;; --------------------------------------------------------------------------
|
|
;; split-path-segments
|
|
;; --------------------------------------------------------------------------
|
|
|
|
(defsuite "split-path-segments"
|
|
(deftest "root path"
|
|
(assert-equal (list) (split-path-segments "/")))
|
|
|
|
(deftest "single segment"
|
|
(assert-equal (list "docs") (split-path-segments "/docs")))
|
|
|
|
(deftest "multiple segments"
|
|
(assert-equal (list "docs" "hello") (split-path-segments "/docs/hello")))
|
|
|
|
(deftest "trailing slash stripped"
|
|
(assert-equal (list "docs") (split-path-segments "/docs/")))
|
|
|
|
(deftest "deep path"
|
|
(assert-equal (list "a" "b" "c" "d") (split-path-segments "/a/b/c/d"))))
|
|
|
|
|
|
;; --------------------------------------------------------------------------
|
|
;; parse-route-pattern
|
|
;; --------------------------------------------------------------------------
|
|
|
|
(defsuite "parse-route-pattern"
|
|
(deftest "static pattern"
|
|
(let ((segs (parse-route-pattern "/docs/intro")))
|
|
(assert-length 2 segs)
|
|
(assert-equal "literal" (get (first segs) "type"))
|
|
(assert-equal "docs" (get (first segs) "value"))
|
|
(assert-equal "literal" (get (nth segs 1) "type"))
|
|
(assert-equal "intro" (get (nth segs 1) "value"))))
|
|
|
|
(deftest "pattern with param"
|
|
(let ((segs (parse-route-pattern "/docs/<slug>")))
|
|
(assert-length 2 segs)
|
|
(assert-equal "literal" (get (first segs) "type"))
|
|
(assert-equal "docs" (get (first segs) "value"))
|
|
(assert-equal "param" (get (nth segs 1) "type"))
|
|
(assert-equal "slug" (get (nth segs 1) "value"))))
|
|
|
|
(deftest "multiple params"
|
|
(let ((segs (parse-route-pattern "/users/<uid>/posts/<pid>")))
|
|
(assert-length 4 segs)
|
|
(assert-equal "param" (get (nth segs 1) "type"))
|
|
(assert-equal "uid" (get (nth segs 1) "value"))
|
|
(assert-equal "param" (get (nth segs 3) "type"))
|
|
(assert-equal "pid" (get (nth segs 3) "value"))))
|
|
|
|
(deftest "root pattern"
|
|
(assert-equal (list) (parse-route-pattern "/"))))
|
|
|
|
|
|
;; --------------------------------------------------------------------------
|
|
;; match-route
|
|
;; --------------------------------------------------------------------------
|
|
|
|
(defsuite "match-route"
|
|
(deftest "exact match returns empty params"
|
|
(let ((result (match-route "/docs/intro" "/docs/intro")))
|
|
(assert-true (not (nil? result)))
|
|
(assert-length 0 (keys result))))
|
|
|
|
(deftest "param match extracts value"
|
|
(let ((result (match-route "/docs/hello" "/docs/<slug>")))
|
|
(assert-true (not (nil? result)))
|
|
(assert-equal "hello" (get result "slug"))))
|
|
|
|
(deftest "no match returns nil"
|
|
(assert-nil (match-route "/docs/hello" "/essays/<slug>"))
|
|
(assert-nil (match-route "/docs" "/docs/<slug>")))
|
|
|
|
(deftest "segment count mismatch returns nil"
|
|
(assert-nil (match-route "/a/b/c" "/a/<b>"))
|
|
(assert-nil (match-route "/a" "/a/b")))
|
|
|
|
(deftest "root matches root"
|
|
(let ((result (match-route "/" "/")))
|
|
(assert-true (not (nil? result)))))
|
|
|
|
(deftest "multiple params extracted"
|
|
(let ((result (match-route "/users/42/posts/99" "/users/<uid>/posts/<pid>")))
|
|
(assert-true (not (nil? result)))
|
|
(assert-equal "42" (get result "uid"))
|
|
(assert-equal "99" (get result "pid")))))
|
|
|
|
|
|
;; --------------------------------------------------------------------------
|
|
;; find-matching-route
|
|
;; --------------------------------------------------------------------------
|
|
|
|
(defsuite "find-matching-route"
|
|
(deftest "finds first matching route"
|
|
(let ((routes (list
|
|
{:pattern "/docs/" :parsed (parse-route-pattern "/docs/") :name "docs-index"}
|
|
{:pattern "/docs/<slug>" :parsed (parse-route-pattern "/docs/<slug>") :name "docs-page"})))
|
|
(let ((result (find-matching-route "/docs/hello" routes)))
|
|
(assert-true (not (nil? result)))
|
|
(assert-equal "docs-page" (get result "name"))
|
|
(assert-equal "hello" (get (get result "params") "slug")))))
|
|
|
|
(deftest "returns nil for no match"
|
|
(let ((routes (list
|
|
{:pattern "/docs/<slug>" :parsed (parse-route-pattern "/docs/<slug>") :name "docs-page"})))
|
|
(assert-nil (find-matching-route "/essays/hello" routes))))
|
|
|
|
(deftest "matches exact routes before param routes"
|
|
(let ((routes (list
|
|
{:pattern "/docs/" :parsed (parse-route-pattern "/docs/") :name "docs-index"}
|
|
{:pattern "/docs/<slug>" :parsed (parse-route-pattern "/docs/<slug>") :name "docs-page"})))
|
|
;; /docs/ should match docs-index, not docs-page
|
|
(let ((result (find-matching-route "/docs/" routes)))
|
|
(assert-true (not (nil? result)))
|
|
(assert-equal "docs-index" (get result "name"))))))
|