signals.sx, engine.sx, orchestration.sx, boot.sx, router.sx, deps.sx, forms.sx, page-helpers.sx, adapters, boundary files → web/ Web tests → web/tests/ Test runners updated with _SPEC_TESTS and _WEB_TESTS paths. All 89 tests pass (20 signal + 43 CEK + 26 CEK reactive). Both bootstrappers build fully (5993 Python lines, 387KB JS). Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
328 lines
12 KiB
Plaintext
328 lines
12 KiB
Plaintext
;; ==========================================================================
|
|
;; test-deps.sx — Tests for component dependency analysis (deps.sx)
|
|
;;
|
|
;; Requires: test-framework.sx loaded first.
|
|
;; Platform functions: scan-refs, transitive-deps, components-needed,
|
|
;; component-pure?, scan-io-refs, transitive-io-refs,
|
|
;; scan-components-from-source, test-env
|
|
;; (loaded from bootstrapped output by test runners)
|
|
;; ==========================================================================
|
|
|
|
|
|
;; --------------------------------------------------------------------------
|
|
;; Test component definitions — these exist in the test env for dep analysis
|
|
;; --------------------------------------------------------------------------
|
|
|
|
(defcomp ~dep-leaf ()
|
|
(span "leaf"))
|
|
|
|
(defcomp ~dep-branch ()
|
|
(div (~dep-leaf)))
|
|
|
|
(defcomp ~dep-trunk ()
|
|
(div (~dep-branch) (~dep-leaf)))
|
|
|
|
(defcomp ~dep-conditional (&key show?)
|
|
(if show?
|
|
(~dep-leaf)
|
|
(~dep-branch)))
|
|
|
|
(defcomp ~dep-nested-cond (&key mode)
|
|
(cond
|
|
(= mode "a") (~dep-leaf)
|
|
(= mode "b") (~dep-branch)
|
|
:else (~dep-trunk)))
|
|
|
|
(defcomp ~dep-island ()
|
|
(div "no deps"))
|
|
|
|
;; Islands with dependencies — defisland bodies must be scanned
|
|
(defisland ~dep-island-with-child ()
|
|
(div (~dep-leaf) "island content"))
|
|
|
|
(defisland ~dep-island-with-chain ()
|
|
(div (~dep-branch) "deep island"))
|
|
|
|
|
|
;; --------------------------------------------------------------------------
|
|
;; 1. scan-refs — finds component references in AST nodes
|
|
;; --------------------------------------------------------------------------
|
|
|
|
(defsuite "scan-refs"
|
|
|
|
(deftest "empty for string literal"
|
|
(assert-equal (list) (scan-refs "hello")))
|
|
|
|
(deftest "empty for number"
|
|
(assert-equal (list) (scan-refs 42)))
|
|
|
|
(deftest "finds component symbol"
|
|
(let ((refs (scan-refs (quote (~dep-leaf)))))
|
|
(assert-contains "~dep-leaf" refs)))
|
|
|
|
(deftest "finds in nested list"
|
|
(let ((refs (scan-refs (quote (div (span (~dep-leaf)))))))
|
|
(assert-contains "~dep-leaf" refs)))
|
|
|
|
(deftest "finds multiple refs"
|
|
(let ((refs (scan-refs (quote (div (~dep-leaf) (~dep-branch))))))
|
|
(assert-contains "~dep-leaf" refs)
|
|
(assert-contains "~dep-branch" refs)))
|
|
|
|
(deftest "deduplicates"
|
|
(let ((refs (scan-refs (quote (div (~dep-leaf) (~dep-leaf))))))
|
|
(assert-equal 1 (len refs))))
|
|
|
|
(deftest "walks if branches"
|
|
(let ((refs (scan-refs (quote (if true (~dep-leaf) (~dep-branch))))))
|
|
(assert-contains "~dep-leaf" refs)
|
|
(assert-contains "~dep-branch" refs)))
|
|
|
|
(deftest "walks cond branches"
|
|
(let ((refs (scan-refs (quote (cond (= x 1) (~dep-leaf) :else (~dep-trunk))))))
|
|
(assert-contains "~dep-leaf" refs)
|
|
(assert-contains "~dep-trunk" refs)))
|
|
|
|
(deftest "ignores non-component symbols"
|
|
(let ((refs (scan-refs (quote (div class "foo")))))
|
|
(assert-equal 0 (len refs)))))
|
|
|
|
|
|
;; --------------------------------------------------------------------------
|
|
;; 2. scan-components-from-source — regex-based source string scanning
|
|
;; --------------------------------------------------------------------------
|
|
|
|
(defsuite "scan-components-from-source"
|
|
|
|
(deftest "finds single component"
|
|
(let ((refs (scan-components-from-source "(~dep-leaf)")))
|
|
(assert-contains "~dep-leaf" refs)))
|
|
|
|
(deftest "finds multiple components"
|
|
(let ((refs (scan-components-from-source "(div (~dep-leaf) (~dep-branch))")))
|
|
(assert-contains "~dep-leaf" refs)
|
|
(assert-contains "~dep-branch" refs)))
|
|
|
|
(deftest "no false positives on plain text"
|
|
(let ((refs (scan-components-from-source "(div \"hello world\")")))
|
|
(assert-equal 0 (len refs))))
|
|
|
|
(deftest "handles hyphenated names"
|
|
(let ((refs (scan-components-from-source "(~my-component :key val)")))
|
|
(assert-contains "~my-component" refs))))
|
|
|
|
|
|
;; --------------------------------------------------------------------------
|
|
;; 3. transitive-deps — transitive dependency closure
|
|
;; --------------------------------------------------------------------------
|
|
|
|
(defsuite "transitive-deps"
|
|
|
|
(deftest "leaf has no deps"
|
|
(let ((deps (transitive-deps "~dep-leaf" (test-env))))
|
|
(assert-equal 0 (len deps))))
|
|
|
|
(deftest "direct dependency"
|
|
(let ((deps (transitive-deps "~dep-branch" (test-env))))
|
|
(assert-contains "~dep-leaf" deps)))
|
|
|
|
(deftest "transitive closure"
|
|
(let ((deps (transitive-deps "~dep-trunk" (test-env))))
|
|
(assert-contains "~dep-branch" deps)
|
|
(assert-contains "~dep-leaf" deps)))
|
|
|
|
(deftest "excludes self"
|
|
(let ((deps (transitive-deps "~dep-trunk" (test-env))))
|
|
(assert-false (contains? deps "~dep-trunk"))))
|
|
|
|
(deftest "walks conditional branches"
|
|
(let ((deps (transitive-deps "~dep-conditional" (test-env))))
|
|
(assert-contains "~dep-leaf" deps)
|
|
(assert-contains "~dep-branch" deps)))
|
|
|
|
(deftest "walks all cond branches"
|
|
(let ((deps (transitive-deps "~dep-nested-cond" (test-env))))
|
|
(assert-contains "~dep-leaf" deps)
|
|
(assert-contains "~dep-branch" deps)
|
|
(assert-contains "~dep-trunk" deps)))
|
|
|
|
(deftest "island has no deps"
|
|
(let ((deps (transitive-deps "~dep-island" (test-env))))
|
|
(assert-equal 0 (len deps))))
|
|
|
|
(deftest "accepts name without tilde"
|
|
(let ((deps (transitive-deps "dep-branch" (test-env))))
|
|
(assert-contains "~dep-leaf" deps)))
|
|
|
|
(deftest "island direct dep scanned"
|
|
(let ((deps (transitive-deps "~dep-island-with-child" (test-env))))
|
|
(assert-contains "~dep-leaf" deps)))
|
|
|
|
(deftest "island transitive deps scanned"
|
|
(let ((deps (transitive-deps "~dep-island-with-chain" (test-env))))
|
|
(assert-contains "~dep-branch" deps)
|
|
(assert-contains "~dep-leaf" deps))))
|
|
|
|
|
|
;; --------------------------------------------------------------------------
|
|
;; 4. components-needed — page bundle computation
|
|
;; --------------------------------------------------------------------------
|
|
|
|
(defsuite "components-needed"
|
|
|
|
(deftest "finds direct and transitive"
|
|
(let ((needed (components-needed "(~dep-trunk)" (test-env))))
|
|
(assert-contains "~dep-trunk" needed)
|
|
(assert-contains "~dep-branch" needed)
|
|
(assert-contains "~dep-leaf" needed)))
|
|
|
|
(deftest "deduplicates"
|
|
(let ((needed (components-needed "(div (~dep-leaf) (~dep-leaf))" (test-env))))
|
|
;; ~dep-leaf should appear only once
|
|
(assert-true (contains? needed "~dep-leaf"))))
|
|
|
|
(deftest "handles leaf page"
|
|
(let ((needed (components-needed "(~dep-island)" (test-env))))
|
|
(assert-contains "~dep-island" needed)
|
|
(assert-equal 1 (len needed))))
|
|
|
|
(deftest "handles multiple top-level components"
|
|
(let ((needed (components-needed "(div (~dep-leaf) (~dep-island))" (test-env))))
|
|
(assert-contains "~dep-leaf" needed)
|
|
(assert-contains "~dep-island" needed)))
|
|
|
|
(deftest "island deps included in page bundle"
|
|
(let ((needed (components-needed "(~dep-island-with-chain)" (test-env))))
|
|
(assert-contains "~dep-island-with-chain" needed)
|
|
(assert-contains "~dep-branch" needed)
|
|
(assert-contains "~dep-leaf" needed))))
|
|
|
|
|
|
;; --------------------------------------------------------------------------
|
|
;; 5. IO detection — scan-io-refs, component-pure?
|
|
;; --------------------------------------------------------------------------
|
|
|
|
;; Define components that reference "io" functions for testing
|
|
(defcomp ~dep-pure ()
|
|
(div (~dep-leaf) "static"))
|
|
|
|
(defcomp ~dep-io ()
|
|
(div (fetch-data "/api")))
|
|
|
|
(defcomp ~dep-io-indirect ()
|
|
(div (~dep-io)))
|
|
|
|
(defsuite "scan-io-refs"
|
|
|
|
(deftest "no IO in pure AST"
|
|
(let ((refs (scan-io-refs (quote (div "hello" (span "world"))) (list "fetch-data"))))
|
|
(assert-equal 0 (len refs))))
|
|
|
|
(deftest "finds IO reference"
|
|
(let ((refs (scan-io-refs (quote (div (fetch-data "/api"))) (list "fetch-data"))))
|
|
(assert-contains "fetch-data" refs)))
|
|
|
|
(deftest "multiple IO refs"
|
|
(let ((refs (scan-io-refs (quote (div (fetch-data "/a") (query-db "x"))) (list "fetch-data" "query-db"))))
|
|
(assert-contains "fetch-data" refs)
|
|
(assert-contains "query-db" refs)))
|
|
|
|
(deftest "ignores non-IO symbols"
|
|
(let ((refs (scan-io-refs (quote (div (map str items))) (list "fetch-data"))))
|
|
(assert-equal 0 (len refs)))))
|
|
|
|
|
|
(defsuite "component-pure?"
|
|
|
|
(deftest "pure component is pure"
|
|
(assert-true (component-pure? "~dep-pure" (test-env) (list "fetch-data"))))
|
|
|
|
(deftest "IO component is not pure"
|
|
(assert-false (component-pure? "~dep-io" (test-env) (list "fetch-data"))))
|
|
|
|
(deftest "indirect IO is not pure"
|
|
(assert-false (component-pure? "~dep-io-indirect" (test-env) (list "fetch-data"))))
|
|
|
|
(deftest "leaf component is pure"
|
|
(assert-true (component-pure? "~dep-leaf" (test-env) (list "fetch-data")))))
|
|
|
|
|
|
;; --------------------------------------------------------------------------
|
|
;; 6. render-target — boundary decision with affinity
|
|
;; --------------------------------------------------------------------------
|
|
|
|
;; Components with explicit affinity annotations
|
|
(defcomp ~dep-force-client (&key x)
|
|
:affinity :client
|
|
(div (fetch-data "/api") x))
|
|
|
|
(defcomp ~dep-force-server (&key x)
|
|
:affinity :server
|
|
(div x))
|
|
|
|
(defcomp ~dep-auto-pure (&key x)
|
|
(div x))
|
|
|
|
(defcomp ~dep-auto-io (&key x)
|
|
(div (fetch-data "/api")))
|
|
|
|
(defsuite "render-target"
|
|
|
|
(deftest "pure auto component targets client"
|
|
(assert-equal "client" (render-target "~dep-auto-pure" (test-env) (list "fetch-data"))))
|
|
|
|
(deftest "IO auto component targets server"
|
|
(assert-equal "server" (render-target "~dep-auto-io" (test-env) (list "fetch-data"))))
|
|
|
|
(deftest "affinity client overrides IO to client"
|
|
(assert-equal "client" (render-target "~dep-force-client" (test-env) (list "fetch-data"))))
|
|
|
|
(deftest "affinity server overrides pure to server"
|
|
(assert-equal "server" (render-target "~dep-force-server" (test-env) (list "fetch-data"))))
|
|
|
|
(deftest "leaf component targets client"
|
|
(assert-equal "client" (render-target "~dep-leaf" (test-env) (list "fetch-data"))))
|
|
|
|
(deftest "unknown name targets server"
|
|
(assert-equal "server" (render-target "~nonexistent" (test-env) (list "fetch-data")))))
|
|
|
|
|
|
;; --------------------------------------------------------------------------
|
|
;; 7. page-render-plan — per-page boundary plan
|
|
;; --------------------------------------------------------------------------
|
|
|
|
;; A page component that uses both pure and IO components
|
|
(defcomp ~plan-page (&key data)
|
|
(div
|
|
(~dep-auto-pure :x "hello")
|
|
(~dep-auto-io :x data)
|
|
(~dep-force-client :x "interactive")))
|
|
|
|
(defsuite "page-render-plan"
|
|
|
|
(deftest "plan classifies components correctly"
|
|
(let ((plan (page-render-plan "(~plan-page :data d)" (test-env) (list "fetch-data"))))
|
|
;; ~plan-page has transitive IO deps (via ~dep-auto-io) so targets server
|
|
(assert-equal "server" (dict-get (get plan :components) "~plan-page"))
|
|
(assert-equal "client" (dict-get (get plan :components) "~dep-auto-pure"))
|
|
(assert-equal "server" (dict-get (get plan :components) "~dep-auto-io"))
|
|
(assert-equal "client" (dict-get (get plan :components) "~dep-force-client"))))
|
|
|
|
(deftest "plan server list contains IO components"
|
|
(let ((plan (page-render-plan "(~plan-page :data d)" (test-env) (list "fetch-data"))))
|
|
(assert-true (contains? (get plan :server) "~dep-auto-io"))))
|
|
|
|
(deftest "plan client list contains pure components"
|
|
(let ((plan (page-render-plan "(~plan-page :data d)" (test-env) (list "fetch-data"))))
|
|
(assert-true (contains? (get plan :client) "~dep-auto-pure"))
|
|
(assert-true (contains? (get plan :client) "~dep-force-client"))))
|
|
|
|
(deftest "plan collects IO deps from server components"
|
|
(let ((plan (page-render-plan "(~plan-page :data d)" (test-env) (list "fetch-data"))))
|
|
(assert-true (contains? (get plan :io-deps) "fetch-data"))))
|
|
|
|
(deftest "pure-only page has empty server list"
|
|
(let ((plan (page-render-plan "(~dep-auto-pure :x 1)" (test-env) (list "fetch-data"))))
|
|
(assert-equal 0 (len (get plan :server)))
|
|
(assert-true (> (len (get plan :client)) 0)))))
|