update examples. fix bugs

This commit is contained in:
2026-02-03 12:53:52 -05:00
parent 9150c90ad1
commit 426a0c4715
15 changed files with 867 additions and 1148 deletions
+82 -183
View File
@@ -1,90 +1,88 @@
(ns tui.core-test
"Integration tests for the TUI engine.
Tests the update loop, command handling, and full render pipeline."
Tests the update loop, event handling, and full render pipeline."
(:require [clojure.test :refer [deftest testing is]]
[clojure.core.async :as async :refer [chan >!! <!! timeout alt!! close!]]
[tui.core :as tui]
[tui.events :as ev]
[tui.render :as render]))
;; === Legacy Command Tests (Backward Compatibility) ===
;; =============================================================================
;; EVENT CONSTRUCTOR TESTS
;; =============================================================================
(deftest quit-command-test
(testing "quit command is correct vector (legacy)"
(is (= [:quit] tui/quit))))
(deftest quit-event-test
(testing "tui/quit creates quit event"
(is (= {:type :quit} (tui/quit)))))
(deftest after-command-test
(testing "after creates a function command (legacy)"
(let [cmd (tui/after 0 :my-tick)]
(is (fn? cmd))
(is (= :my-tick (cmd)))))
(deftest delayed-event-test
(testing "tui/delayed-event creates delayed-event event"
(let [event (tui/delayed-event 1000 {:type :timer-tick})]
(is (= :delayed-event (:type event)))
(is (= 1000 (:ms event)))
(is (= {:type :timer-tick} (:event event))))))
(testing "after can send any message type"
(is (= [:timer-tick {:id 1}] ((tui/after 0 [:timer-tick {:id 1}]))))
(is (= :simple-msg ((tui/after 0 :simple-msg)))))
(deftest batch-event-test
(testing "tui/batch creates batch event"
(let [event (tui/batch {:type :msg1} {:type :msg2})]
(is (= :batch (:type event)))
(is (= 2 (count (:events event))))))
(testing "after with non-zero delay creates function"
(is (fn? (tui/after 100 :tick)))
(is (fn? (tui/after 1000 :tick)))))
(testing "batch filters nil"
(let [event (tui/batch nil {:type :msg1} nil)]
(is (= 1 (count (:events event)))))
(is (nil? (tui/batch nil nil nil)))))
(deftest batch-command-test
(testing "batch combines commands (legacy)"
(let [cmd (tui/batch (tui/send-msg :msg1) tui/quit)]
(is (vector? cmd))
(is (= :batch (first cmd)))
(is (= 3 (count cmd)))
(is (= [:quit] (last cmd)))))
(deftest sequential-event-test
(testing "tui/sequential creates sequential event"
(let [event (tui/sequential {:type :msg1} {:type :msg2})]
(is (= :sequential (:type event)))
(is (= 2 (count (:events event))))))
(testing "batch filters nil commands"
(let [cmd (tui/batch nil (tui/send-msg :msg1) nil)]
(is (= :batch (first cmd)))
(is (= 2 (count cmd))))))
(testing "sequential filters nil"
(let [event (tui/sequential nil {:type :msg1} nil)]
(is (= 1 (count (:events event)))))))
(deftest sequentially-command-test
(testing "sequentially creates seq command (legacy)"
(let [cmd (tui/sequentially (tui/send-msg :msg1) tui/quit)]
(is (vector? cmd))
(is (= :seq (first cmd)))
(is (= 3 (count cmd)))
(is (= [:quit] (last cmd)))))
(deftest shell-event-test
(testing "tui/shell creates shell event"
(let [event (tui/shell ["git" "status"] {:type :git-result})]
(is (= :shell (:type event)))
(is (= ["git" "status"] (:cmd event)))
(is (= {:type :git-result} (:event event))))))
(testing "sequentially filters nil commands"
(let [cmd (tui/sequentially nil (tui/send-msg :msg1) nil)]
(is (= :seq (first cmd)))
(is (= 2 (count cmd))))))
(deftest debounce-event-test
(testing "tui/debounce creates debounce event"
(let [event (tui/debounce :search 300 {:type :do-search})]
(is (= :debounce (:type event)))
(is (= :search (:id event)))
(is (= 300 (:ms event))))))
(deftest send-msg-command-test
(testing "send-msg creates function that returns message (legacy)"
(let [cmd (tui/send-msg {:type :custom :data 42})]
(is (fn? cmd))
(is (= {:type :custom :data 42} (cmd))))))
;; =============================================================================
;; KEY MATCHING TESTS
;; =============================================================================
;; === Legacy Key Matching Tests ===
(deftest key=-test
(testing "key= matches characters"
(is (tui/key= {:type :key :key \q} \q))
(is (not (tui/key= {:type :key :key \a} \q))))
(deftest key=-legacy-test
(testing "key= works with legacy format"
(is (tui/key= [:key {:char \q}] "q"))
(is (tui/key= [:key :enter] :enter))
(is (tui/key= [:key {:ctrl true :char \c}] [:ctrl \c]))
(is (not (tui/key= [:key {:char \a}] "b")))))
(deftest key=-new-format-test
(testing "key= works with new format"
(is (tui/key= {:type :key :key \q} "q"))
(testing "key= matches special keys"
(is (tui/key= {:type :key :key :enter} :enter))
(is (tui/key= {:type :key :key \c :modifiers #{:ctrl}} [:ctrl \c]))
(is (not (tui/key= {:type :key :key \a} "b")))))
(is (tui/key= {:type :key :key :up} :up))
(is (tui/key= {:type :key :key :escape} :escape)))
(deftest key-str-test
(testing "key-str converts key to string (legacy)"
(is (= "q" (tui/key-str [:key {:char \q}])))
(is (= "enter" (tui/key-str [:key :enter]))))
(testing "key= matches modifiers"
(is (tui/key= {:type :key :key \c :modifiers #{:ctrl}} \c #{:ctrl}))
(is (not (tui/key= {:type :key :key \c :modifiers #{:ctrl}} \c)))
(is (not (tui/key= {:type :key :key \c} \c #{:ctrl}))))
(testing "key-str converts key to string (new format)"
(is (= "q" (tui/key-str {:type :key :key \q})))
(is (= "enter" (tui/key-str {:type :key :key :enter})))))
(testing "key= doesn't match non-key events"
(is (not (tui/key= {:type :timer-tick} \q)))
(is (not (tui/key= {:type :quit} :enter)))))
;; === Full Pipeline Tests ===
;; =============================================================================
;; RENDER PIPELINE TESTS
;; =============================================================================
(deftest render-pipeline-test
(testing "model -> view -> render produces valid output"
@@ -98,10 +96,12 @@
(is (clojure.string/includes? rendered "Counter"))
(is (clojure.string/includes? rendered "Count: 5")))))
;; === New API Update Function Tests ===
;; =============================================================================
;; UPDATE FUNCTION CONTRACT TESTS
;; =============================================================================
(deftest new-update-function-contract-test
(testing "new update function returns {:model ... :events ...}"
(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)]}
@@ -124,33 +124,9 @@
(is (= {:n 0} model))
(is (nil? events))))))
;; === Legacy Update Function Tests ===
(deftest legacy-update-function-contract-test
(testing "legacy 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))))))
;; === Event Execution Tests ===
;; =============================================================================
;; EVENT EXECUTION TESTS
;; =============================================================================
(deftest execute-quit-event-test
(testing "quit event puts {:type :quit} on channel"
@@ -162,10 +138,10 @@
(is (= {:type :quit} result)))
(close! msg-chan))))
(deftest execute-delay-event-test
(testing "delay event sends message after delay"
(deftest execute-delayed-event-test
(testing "delayed-event sends message after delay"
(let [msg-chan (chan 1)
event (ev/delay 50 {:type :delayed-msg})]
event (ev/delayed-event 50 {:type :delayed-msg})]
(#'tui/execute-event! event msg-chan)
;; Should not receive immediately
(let [immediate (alt!!
@@ -218,94 +194,17 @@
(is (= :timeout result)))
(close! msg-chan))))
;; === Legacy Command Execution Tests ===
(deftest execute-quit-command-legacy-test
(testing "quit command puts {:type :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 (= {:type :quit} result)))
(close! msg-chan))))
(deftest execute-after-command-legacy-test
(testing "after command sends message after delay"
(let [msg-chan (chan 1)
cmd (tui/after 50 :delayed-msg)]
(#'tui/execute-cmd! cmd 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 (= :delayed-msg delayed)))
(close! msg-chan))))
(deftest execute-function-command-legacy-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-legacy-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-legacy-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 ===
;; =============================================================================
;; DEFAPP MACRO TESTS
;; =============================================================================
(deftest defapp-macro-test
(testing "defapp creates app map (legacy)"
(tui/defapp test-app-legacy
:init {:count 0}
:update (fn [m msg] [m nil])
:view (fn [m] [:text "test"]))
(is (map? test-app-legacy))
(is (= {:count 0} (:init test-app-legacy)))
(is (fn? (:update test-app-legacy)))
(is (fn? (:view test-app-legacy))))
(testing "defapp creates app map (new)"
(tui/defapp test-app-new
(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-new))
(is (= {:count 0} (:init test-app-new)))
(is (fn? (:update test-app-new)))
(is (fn? (:view test-app-new)))))
(is (map? test-app))
(is (= {:count 0} (:init test-app)))
(is (fn? (:update test-app)))
(is (fn? (:view test-app)))))