;; ========================================================================== ;; test-forms.sx — Tests for web-platform definition forms ;; ;; Requires: test-framework.sx, forms.sx loaded. ;; Tests defpage, streaming functions, and the multi-stream data protocol. ;; ;; These tests were previously in spec/tests/test-eval.sx but belong here ;; because they test web-specific forms, not the core evaluator. ;; ========================================================================== ;; -------------------------------------------------------------------------- ;; defpage — page definition form ;; -------------------------------------------------------------------------- (defsuite "defpage" (deftest "basic defpage returns page-def" (let ((p (defpage test-basic :path "/test" :auth :public :content (div "hello")))) (assert-true (not (nil? p))) (assert-equal "test-basic" (get p "name")) (assert-equal "/test" (get p "path")) (assert-equal "public" (get p "auth")))) (deftest "defpage content expr is unevaluated AST" (let ((p (defpage test-content :path "/c" :auth :public :content (~my-comp :title "hi")))) (assert-true (not (nil? (get p "content")))))) (deftest "defpage with :stream" (let ((p (defpage test-stream :path "/s" :auth :public :stream true :content (div "x")))) (assert-equal true (get p "stream")))) (deftest "defpage with :shell" (let ((p (defpage test-shell :path "/sh" :auth :public :stream true :shell (~my-layout (~suspense :id "data" :fallback (div "loading..."))) :content (~my-streamed :data data-val)))) (assert-true (not (nil? (get p "shell")))) (assert-true (not (nil? (get p "content")))))) (deftest "defpage with :fallback" (let ((p (defpage test-fallback :path "/f" :auth :public :stream true :fallback (div :class "skeleton" "loading") :content (div "done")))) (assert-true (not (nil? (get p "fallback")))))) (deftest "defpage with :data" (let ((p (defpage test-data :path "/d" :auth :public :data (fetch-items) :content (~items-list :items items)))) (assert-true (not (nil? (get p "data")))))) (deftest "defpage missing fields are nil" (let ((p (defpage test-minimal :path "/m" :auth :public :content (div "x")))) (assert-nil (get p "data")) (assert-nil (get p "filter")) (assert-nil (get p "aside")) (assert-nil (get p "menu")) (assert-nil (get p "shell")) (assert-nil (get p "fallback")) (assert-equal false (get p "stream"))))) ;; -------------------------------------------------------------------------- ;; Multi-stream data protocol ;; -------------------------------------------------------------------------- (defsuite "stream-chunk-id" (deftest "extracts stream-id from chunk" (assert-equal "my-slot" (stream-chunk-id {"stream-id" "my-slot" "x" 1}))) (deftest "defaults to stream-content when missing" (assert-equal "stream-content" (stream-chunk-id {"x" 1 "y" 2})))) (defsuite "stream-chunk-bindings" (deftest "removes stream-id from chunk" (let ((bindings (stream-chunk-bindings {"stream-id" "slot" "name" "alice" "age" 30}))) (assert-equal "alice" (get bindings "name")) (assert-equal 30 (get bindings "age")) (assert-nil (get bindings "stream-id")))) (deftest "returns all keys when no stream-id" (let ((bindings (stream-chunk-bindings {"a" 1 "b" 2}))) (assert-equal 1 (get bindings "a")) (assert-equal 2 (get bindings "b"))))) (defsuite "normalize-binding-key" (deftest "converts underscores to hyphens" (assert-equal "my-key" (normalize-binding-key "my_key"))) (deftest "leaves hyphens unchanged" (assert-equal "my-key" (normalize-binding-key "my-key"))) (deftest "handles multiple underscores" (assert-equal "a-b-c" (normalize-binding-key "a_b_c")))) (defsuite "bind-stream-chunk" (deftest "creates fresh env with bindings" (let ((base {"existing" 42}) (chunk {"stream-id" "slot" "user-name" "bob" "count" 5}) (env (bind-stream-chunk chunk base))) ;; Base env bindings are preserved (assert-equal 42 (get env "existing")) ;; Chunk bindings are added (stream-id removed) (assert-equal "bob" (get env "user-name")) (assert-equal 5 (get env "count")) ;; stream-id is not in env (assert-nil (get env "stream-id")))) (deftest "isolates env from base — bindings don't leak to base" (let ((base {"x" 1}) (chunk {"stream-id" "s" "y" 2}) (env (bind-stream-chunk chunk base))) ;; Chunk bindings should not appear in base (assert-nil (get base "y")) ;; Base bindings should be in derived env (assert-equal 1 (get env "x"))))) (defsuite "validate-stream-data" (deftest "valid: list of dicts" (assert-true (validate-stream-data (list {"stream-id" "a" "x" 1} {"stream-id" "b" "y" 2})))) (deftest "valid: empty list" (assert-true (validate-stream-data (list)))) (deftest "invalid: single dict (not a list)" (assert-equal false (validate-stream-data {"x" 1}))) (deftest "invalid: list containing non-dict" (assert-equal false (validate-stream-data (list {"x" 1} "oops" {"y" 2}))))) ;; -------------------------------------------------------------------------- ;; Multi-stream end-to-end scenarios ;; -------------------------------------------------------------------------- (defsuite "multi-stream routing" (deftest "stream-chunk-id routes different chunks to different slots" (let ((chunks (list {"stream-id" "stream-fast" "msg" "quick"} {"stream-id" "stream-medium" "msg" "steady"} {"stream-id" "stream-slow" "msg" "slow"})) (ids (map stream-chunk-id chunks))) (assert-equal "stream-fast" (nth ids 0)) (assert-equal "stream-medium" (nth ids 1)) (assert-equal "stream-slow" (nth ids 2)))) (deftest "bind-stream-chunk creates isolated envs per chunk" (let ((base {"layout" "main"}) (chunk-a {"stream-id" "a" "title" "First" "count" 1}) (chunk-b {"stream-id" "b" "title" "Second" "count" 2}) (env-a (bind-stream-chunk chunk-a base)) (env-b (bind-stream-chunk chunk-b base))) ;; Each env has its own bindings (assert-equal "First" (get env-a "title")) (assert-equal "Second" (get env-b "title")) (assert-equal 1 (get env-a "count")) (assert-equal 2 (get env-b "count")) ;; Both share base (assert-equal "main" (get env-a "layout")) (assert-equal "main" (get env-b "layout")) ;; Neither leaks into base (assert-nil (get base "title")))) (deftest "normalize-binding-key applied to chunk keys" (let ((chunk {"stream-id" "s" "user_name" "alice" "item_count" 3}) (bindings (stream-chunk-bindings chunk))) ;; Keys with underscores need normalizing for SX env (assert-equal "alice" (get bindings "user_name")) ;; normalize-binding-key converts them (assert-equal "user-name" (normalize-binding-key "user_name")) (assert-equal "item-count" (normalize-binding-key "item_count")))) (deftest "defpage stream flag defaults to false" (let ((p (defpage test-no-stream :path "/ns" :auth :public :content (div "x")))) (assert-equal false (get p "stream")))) (deftest "defpage stream true recorded in page-def" (let ((p (defpage test-with-stream :path "/ws" :auth :public :stream true :shell (~layout (~suspense :id "data")) :content (~chunk :val val)))) (assert-equal true (get p "stream")) (assert-true (not (nil? (get p "shell")))))))