174 lines
5.8 KiB
Clojure
174 lines
5.8 KiB
Clojure
(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 >!! <!! timeout alt!! close!]]
|
|
[tui.core :as tui]
|
|
[tui.render :as render]))
|
|
|
|
;; === Command Tests ===
|
|
|
|
(deftest quit-command-test
|
|
(testing "quit command is correct vector"
|
|
(is (= [:quit] tui/quit))))
|
|
|
|
(deftest tick-command-test
|
|
(testing "tick creates correct command"
|
|
(is (= [:tick 100] (tui/tick 100)))
|
|
(is (= [:tick 1000] (tui/tick 1000)))))
|
|
|
|
(deftest batch-command-test
|
|
(testing "batch combines commands"
|
|
(is (= [:batch [:tick 100] [:quit]] (tui/batch (tui/tick 100) tui/quit))))
|
|
|
|
(testing "batch filters nil commands"
|
|
(is (= [:batch [:tick 100]] (tui/batch nil (tui/tick 100) nil)))))
|
|
|
|
(deftest sequentially-command-test
|
|
(testing "sequentially creates seq command"
|
|
(is (= [:seq [:tick 100] [:quit]] (tui/sequentially (tui/tick 100) tui/quit))))
|
|
|
|
(testing "sequentially filters nil commands"
|
|
(is (= [:seq [:tick 100]] (tui/sequentially nil (tui/tick 100) nil)))))
|
|
|
|
(deftest send-msg-command-test
|
|
(testing "send-msg creates function that returns message"
|
|
(let [cmd (tui/send-msg {:type :custom :data 42})]
|
|
(is (fn? cmd))
|
|
(is (= {:type :custom :data 42} (cmd))))))
|
|
|
|
;; === Key Matching Tests ===
|
|
|
|
(deftest key=-test
|
|
(testing "key= delegates to input/key-match?"
|
|
(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-str-test
|
|
(testing "key-str converts key to string"
|
|
(is (= "q" (tui/key-str [:key {:char \q}])))
|
|
(is (= "enter" (tui/key-str [:key :enter])))))
|
|
|
|
;; === Full Pipeline Tests ===
|
|
|
|
(deftest render-pipeline-test
|
|
(testing "model -> 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)))))
|