(ns tui.core-test "Integration tests for the TUI engine. Tests the update loop, command handling, and full render pipeline." (:require [clojure.test :refer [deftest testing is]] [clojure.core.async :as async :refer [chan >!! view -> render produces valid output" (let [model {:count 5} view (fn [{:keys [count]}] [:col [:text {:bold true} "Counter"] [:text (str "Count: " count)]]) rendered (render/render (view model))] (is (string? rendered)) (is (clojure.string/includes? rendered "Counter")) (is (clojure.string/includes? rendered "Count: 5"))))) (deftest update-function-contract-test (testing "update function returns [model cmd] tuple" (let [update-fn (fn [model msg] (cond (tui/key= msg "q") [model tui/quit] (tui/key= msg :up) [(update model :n inc) nil] :else [model nil])) model {:n 0}] ;; Test quit returns command (let [[new-model cmd] (update-fn model [:key {:char \q}])] (is (= model new-model)) (is (= [:quit] cmd))) ;; Test up returns updated model (let [[new-model cmd] (update-fn model [:key :up])] (is (= {:n 1} new-model)) (is (nil? cmd))) ;; Test unknown key returns model unchanged (let [[new-model cmd] (update-fn model [:key {:char \x}])] (is (= model new-model)) (is (nil? cmd)))))) ;; === Command Execution Tests === ;; These test the internal command execution logic (deftest execute-quit-command-test (testing "quit command puts :quit on channel" (let [msg-chan (chan 1)] (#'tui/execute-cmd! [:quit] msg-chan) (let [result (alt!! msg-chan ([v] v) (timeout 100) :timeout)] (is (= [:quit] result))) (close! msg-chan)))) (deftest execute-tick-command-test (testing "tick command sends :tick message after delay" (let [msg-chan (chan 1)] (#'tui/execute-cmd! [:tick 50] msg-chan) ;; Should not receive immediately (let [immediate (alt!! msg-chan ([v] v) (timeout 10) :timeout)] (is (= :timeout immediate))) ;; Should receive after delay (let [delayed (alt!! msg-chan ([v] v) (timeout 200) :timeout)] (is (vector? delayed)) (is (= :tick (first delayed)))) (close! msg-chan)))) (deftest execute-function-command-test (testing "function command executes and sends result" (let [msg-chan (chan 1) cmd (fn [] {:custom :message})] (#'tui/execute-cmd! cmd msg-chan) (let [result (alt!! msg-chan ([v] v) (timeout 100) :timeout)] (is (= {:custom :message} result))) (close! msg-chan)))) (deftest execute-batch-command-test (testing "batch executes multiple commands" (let [msg-chan (chan 10)] (#'tui/execute-cmd! [:batch (fn [] :msg1) (fn [] :msg2)] msg-chan) ;; Give time for async execution (Thread/sleep 50) (let [results (loop [msgs []] (let [msg (alt!! msg-chan ([v] v) (timeout 10) nil)] (if msg (recur (conj msgs msg)) msgs)))] (is (= #{:msg1 :msg2} (set results)))) (close! msg-chan)))) (deftest execute-nil-command-test (testing "nil command does nothing" (let [msg-chan (chan 1)] (#'tui/execute-cmd! nil msg-chan) (let [result (alt!! msg-chan ([v] v) (timeout 50) :timeout)] (is (= :timeout result))) (close! msg-chan)))) ;; === Defapp Macro Tests === (deftest defapp-macro-test (testing "defapp creates app map" (tui/defapp test-app :init {:count 0} :update (fn [m msg] [m nil]) :view (fn [m] [:text "test"])) (is (map? test-app)) (is (= {:count 0} (:init test-app))) (is (fn? (:update test-app))) (is (fn? (:view test-app)))))