Files
rose-ash/web/test-deps.sx
giles 1a3d7b3d77
All checks were successful
Build and Deploy / build-and-deploy (push) Successful in 4m49s
Separate core spec from web framework
Three-layer architecture:
  spec/  — Core language (19 files): evaluator, parser, primitives,
           CEK machine, types, continuations. Host-independent.
  web/   — Web framework (20 files): signals, adapters, engine,
           orchestration, boot, router, CSSX. Built on core spec.
  sx/    — Application (sx-docs website). Built on web framework.

Split boundary.sx into boundary-core.sx (type-of, make-env, identical?)
and boundary-web.sx (IO primitives, signals, spreads, page helpers).

Bootstrappers search spec/ → web/ → shared/sx/ref/ for .sx files.
Original files remain in shared/sx/ref/ as fallback during transition.
All 63 tests pass.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-15 01:42:19 +00:00

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)))))