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