(ns tui.core-test "Integration tests for the TUI engine. Tests the update loop, event 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"))))) ;; ============================================================================= ;; UPDATE FUNCTION CONTRACT TESTS ;; ============================================================================= (deftest update-function-contract-test (testing "update function returns {:model ... :events ...}" (let [update-fn (fn [{:keys [model event]}] (cond (ev/key= event \q) {:model model :events [(ev/quit)]} (ev/key= event :up) {:model (update model :n inc)} :else {:model model})) model {:n 0}] ;; Test quit returns event (let [{:keys [model events]} (update-fn {:model model :event {:type :key :key \q}})] (is (= {:n 0} model)) (is (= [{:type :quit}] events))) ;; Test up returns updated model (let [{:keys [model events]} (update-fn {:model model :event {:type :key :key :up}})] (is (= {:n 1} model)) (is (nil? events))) ;; Test unknown key returns model unchanged (let [{:keys [model events]} (update-fn {:model model :event {:type :key :key \x}})] (is (= {:n 0} model)) (is (nil? events)))))) ;; ============================================================================= ;; EVENT EXECUTION TESTS ;; ============================================================================= (deftest execute-quit-event-test (testing "quit event puts {:type :quit} on channel" (let [msg-chan (chan 1)] (#'tui/execute-event! {:type :quit} msg-chan) (let [result (alt!! msg-chan ([v] v) (timeout 100) :timeout)] (is (= {:type :quit} result))) (close! msg-chan)))) (deftest execute-delayed-event-test (testing "delayed-event sends message after delay" (let [msg-chan (chan 1) event (ev/delayed-event 50 {:type :delayed-msg})] (#'tui/execute-event! event 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 (= {:type :delayed-msg} delayed))) (close! msg-chan)))) (deftest execute-batch-event-test (testing "batch executes multiple events" (let [msg-chan (chan 10)] (#'tui/execute-event! {:type :batch :events [{:type :msg1} {:type :msg2}]} msg-chan) ;; Give time for dispatch (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 (= #{{:type :msg1} {:type :msg2}} (set results)))) (close! msg-chan)))) (deftest execute-unknown-event-test (testing "unknown event type is dispatched to update" (let [msg-chan (chan 1)] (#'tui/execute-event! {:type :custom-app-event :data 42} msg-chan) (let [result (alt!! msg-chan ([v] v) (timeout 100) :timeout)] (is (= {:type :custom-app-event :data 42} result))) (close! msg-chan)))) (deftest execute-nil-event-test (testing "nil event does nothing" (let [msg-chan (chan 1)] (#'tui/execute-event! 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 [{:keys [model event]}] {:model model}) :view (fn [m size] [:text "test"])) (is (map? test-app)) (is (= {:count 0} (:init test-app))) (is (fn? (:update test-app))) (is (fn? (:view test-app)))))