simplify. remove tick
This commit is contained in:
@@ -1 +1,2 @@
|
|||||||
.cpcache/
|
.cpcache/
|
||||||
|
CLAUDE.local.md
|
||||||
|
|||||||
@@ -38,7 +38,7 @@ View (hiccup) → Render (ANSI string) → Terminal (raw mode I/O)
|
|||||||
|
|
||||||
### Core Modules
|
### Core Modules
|
||||||
|
|
||||||
- **tui.core** - Main runtime with core.async. Manages the event loop, executes commands (quit, tick, batch, seq), handles input via goroutines.
|
- **tui.core** - Main runtime with core.async. Manages the event loop, executes commands (quit, after, batch, seq), handles input via goroutines.
|
||||||
- **tui.render** - Converts hiccup (`[:col [:text "hi"]]`) to ANSI strings. Handles `:text`, `:row`, `:col`, `:box`, `:space` elements.
|
- **tui.render** - Converts hiccup (`[:col [:text "hi"]]`) to ANSI strings. Handles `:text`, `:row`, `:col`, `:box`, `:space` elements.
|
||||||
- **tui.terminal** - Platform abstraction: raw mode via `stty`, reads from `/dev/tty`, renders by printing ANSI.
|
- **tui.terminal** - Platform abstraction: raw mode via `stty`, reads from `/dev/tty`, renders by printing ANSI.
|
||||||
- **tui.input** - Parses raw bytes into key messages (`[:key {:char \a}]`, `[:key :up]`, `[:key {:ctrl true :char \c}]`).
|
- **tui.input** - Parses raw bytes into key messages (`[:key {:char \a}]`, `[:key :up]`, `[:key {:ctrl true :char \c}]`).
|
||||||
@@ -56,7 +56,7 @@ View (hiccup) → Render (ANSI string) → Terminal (raw mode I/O)
|
|||||||
### Command Types
|
### Command Types
|
||||||
|
|
||||||
- `tui/quit` - Exit
|
- `tui/quit` - Exit
|
||||||
- `(tui/tick ms)` - Send `:tick` after delay
|
- `(tui/after ms msg)` - Send `msg` after delay (for timers, animations)
|
||||||
- `(tui/batch cmd1 cmd2)` - Parallel execution
|
- `(tui/batch cmd1 cmd2)` - Parallel execution
|
||||||
- `(tui/sequentially cmd1 cmd2)` - Sequential execution
|
- `(tui/sequentially cmd1 cmd2)` - Sequential execution
|
||||||
- `(fn [] msg)` - Custom async returning a message
|
- `(fn [] msg)` - Custom async returning a message
|
||||||
|
|||||||
@@ -32,10 +32,10 @@
|
|||||||
(tui/key= msg [:ctrl \c]))
|
(tui/key= msg [:ctrl \c]))
|
||||||
[model tui/quit]
|
[model tui/quit]
|
||||||
|
|
||||||
;; Tick - advance frame
|
;; Spinner frame - advance animation
|
||||||
(= (first msg) :tick)
|
(= msg :spinner-frame)
|
||||||
(if (:loading model)
|
(if (:loading model)
|
||||||
[(update model :frame inc) (tui/tick 80)]
|
[(update model :frame inc) (tui/after 80 :spinner-frame)]
|
||||||
[model nil])
|
[model nil])
|
||||||
|
|
||||||
;; Space - simulate completion
|
;; Space - simulate completion
|
||||||
@@ -53,7 +53,7 @@
|
|||||||
;; r - restart
|
;; r - restart
|
||||||
(tui/key= msg "r")
|
(tui/key= msg "r")
|
||||||
[(assoc model :loading true :frame 0 :message "Loading...")
|
[(assoc model :loading true :frame 0 :message "Loading...")
|
||||||
(tui/tick 80)]
|
(tui/after 80 :spinner-frame)]
|
||||||
|
|
||||||
:else
|
:else
|
||||||
[model nil]))
|
[model nil]))
|
||||||
@@ -86,5 +86,5 @@
|
|||||||
(tui/run {:init initial-model
|
(tui/run {:init initial-model
|
||||||
:update update-model
|
:update update-model
|
||||||
:view view
|
:view view
|
||||||
:init-cmd (tui/tick 80)})
|
:init-cmd (tui/after 80 :spinner-frame)})
|
||||||
(println "Spinner demo finished."))
|
(println "Spinner demo finished."))
|
||||||
|
|||||||
+6
-6
@@ -17,27 +17,27 @@
|
|||||||
(tui/key= msg [:ctrl \c]))
|
(tui/key= msg [:ctrl \c]))
|
||||||
[model tui/quit]
|
[model tui/quit]
|
||||||
|
|
||||||
;; Tick - decrement timer
|
;; Timer tick - decrement timer
|
||||||
(= (first msg) :tick)
|
(= msg :timer-tick)
|
||||||
(if (:running model)
|
(if (:running model)
|
||||||
(let [new-seconds (dec (:seconds model))]
|
(let [new-seconds (dec (:seconds model))]
|
||||||
(if (<= new-seconds 0)
|
(if (<= new-seconds 0)
|
||||||
;; Timer done
|
;; Timer done
|
||||||
[(assoc model :seconds 0 :done true :running false) nil]
|
[(assoc model :seconds 0 :done true :running false) nil]
|
||||||
;; Continue countdown
|
;; Continue countdown
|
||||||
[(assoc model :seconds new-seconds) (tui/tick 1000)]))
|
[(assoc model :seconds new-seconds) (tui/after 1000 :timer-tick)]))
|
||||||
[model nil])
|
[model nil])
|
||||||
|
|
||||||
;; Space - pause/resume
|
;; Space - pause/resume
|
||||||
(tui/key= msg " ")
|
(tui/key= msg " ")
|
||||||
(let [new-running (not (:running model))]
|
(let [new-running (not (:running model))]
|
||||||
[(assoc model :running new-running)
|
[(assoc model :running new-running)
|
||||||
(when new-running (tui/tick 1000))])
|
(when new-running (tui/after 1000 :timer-tick))])
|
||||||
|
|
||||||
;; r - reset
|
;; r - reset
|
||||||
(tui/key= msg "r")
|
(tui/key= msg "r")
|
||||||
[(assoc model :seconds 10 :done false :running true)
|
[(assoc model :seconds 10 :done false :running true)
|
||||||
(tui/tick 1000)]
|
(tui/after 1000 :timer-tick)]
|
||||||
|
|
||||||
:else
|
:else
|
||||||
[model nil]))
|
[model nil]))
|
||||||
@@ -76,6 +76,6 @@
|
|||||||
(let [final-model (tui/run {:init initial-model
|
(let [final-model (tui/run {:init initial-model
|
||||||
:update update-model
|
:update update-model
|
||||||
:view view
|
:view view
|
||||||
:init-cmd (tui/tick 1000)})]
|
:init-cmd (tui/after 1000 :timer-tick)})]
|
||||||
(when (:done final-model)
|
(when (:done final-model)
|
||||||
(println "Timer completed!"))))
|
(println "Timer completed!"))))
|
||||||
|
|||||||
+11
-12
@@ -9,7 +9,6 @@
|
|||||||
;; === Command Types ===
|
;; === Command Types ===
|
||||||
;; nil - no-op
|
;; nil - no-op
|
||||||
;; [:quit] - exit program
|
;; [:quit] - exit program
|
||||||
;; [:tick ms] - send :tick message after ms
|
|
||||||
;; [:batch cmd1 cmd2 ...] - run commands in parallel
|
;; [:batch cmd1 cmd2 ...] - run commands in parallel
|
||||||
;; [:seq cmd1 cmd2 ...] - run commands sequentially
|
;; [:seq cmd1 cmd2 ...] - run commands sequentially
|
||||||
;; (fn [] msg) - arbitrary async function returning message
|
;; (fn [] msg) - arbitrary async function returning message
|
||||||
@@ -17,10 +16,17 @@
|
|||||||
;; === Built-in Commands ===
|
;; === Built-in Commands ===
|
||||||
(def quit [:quit])
|
(def quit [:quit])
|
||||||
|
|
||||||
(defn tick
|
(defn after
|
||||||
"Send a :tick message after ms milliseconds."
|
"Returns a command that sends msg after ms milliseconds.
|
||||||
[ms]
|
Use this for timers, animations, or delayed actions.
|
||||||
[:tick ms])
|
|
||||||
|
Example:
|
||||||
|
(after 1000 [:timer-tick])
|
||||||
|
(after 80 [:spinner-frame {:id 1}])"
|
||||||
|
[ms msg]
|
||||||
|
(fn []
|
||||||
|
(Thread/sleep ms)
|
||||||
|
msg))
|
||||||
|
|
||||||
(defn batch
|
(defn batch
|
||||||
"Run multiple commands in parallel."
|
"Run multiple commands in parallel."
|
||||||
@@ -47,13 +53,6 @@
|
|||||||
(= cmd [:quit])
|
(= cmd [:quit])
|
||||||
(put! msg-chan [:quit])
|
(put! msg-chan [:quit])
|
||||||
|
|
||||||
;; Tick command
|
|
||||||
(and (vector? cmd) (= (first cmd) :tick))
|
|
||||||
(let [ms (second cmd)]
|
|
||||||
(go
|
|
||||||
(<! (timeout ms))
|
|
||||||
(>! msg-chan [:tick (System/currentTimeMillis)])))
|
|
||||||
|
|
||||||
;; Batch - run all in parallel
|
;; Batch - run all in parallel
|
||||||
(and (vector? cmd) (= (first cmd) :batch))
|
(and (vector? cmd) (= (first cmd) :batch))
|
||||||
(doseq [c (rest cmd)]
|
(doseq [c (rest cmd)]
|
||||||
|
|||||||
+39
-26
@@ -102,40 +102,53 @@
|
|||||||
(is (vector? tui/quit))
|
(is (vector? tui/quit))
|
||||||
(is (= :quit (first tui/quit)))))
|
(is (= :quit (first tui/quit)))))
|
||||||
|
|
||||||
(deftest tick-command-test
|
(deftest after-command-test
|
||||||
(testing "from timer: tick with 1000ms"
|
(testing "from timer: after creates function"
|
||||||
(is (= [:tick 1000] (tui/tick 1000))))
|
(let [cmd (tui/after 1000 :timer-tick)]
|
||||||
|
(is (fn? cmd))))
|
||||||
|
|
||||||
(testing "from spinner: tick with 80ms"
|
(testing "from spinner: after creates function"
|
||||||
(is (= [:tick 80] (tui/tick 80))))
|
(let [cmd (tui/after 80 :spinner-frame)]
|
||||||
|
(is (fn? cmd))))
|
||||||
|
|
||||||
(testing "tick with various intervals"
|
(testing "after with zero delay returns message immediately"
|
||||||
(are [ms] (= [:tick ms] (tui/tick ms))
|
(is (= :timer-tick ((tui/after 0 :timer-tick))))
|
||||||
0 1 10 100 500 1000 5000 60000)))
|
(is (= [:my-tick {:id 1}] ((tui/after 0 [:my-tick {:id 1}]))))))
|
||||||
|
|
||||||
(deftest batch-command-test
|
(deftest batch-command-test
|
||||||
(testing "batch two commands"
|
(testing "batch two commands"
|
||||||
(let [cmd (tui/batch (tui/tick 100) tui/quit)]
|
(let [cmd (tui/batch (tui/after 100 :tick1) tui/quit)]
|
||||||
(is (= [:batch [:tick 100] [:quit]] cmd))))
|
(is (= :batch (first cmd)))
|
||||||
|
(is (= 3 (count cmd)))
|
||||||
|
(is (fn? (second cmd)))
|
||||||
|
(is (= [:quit] (last cmd)))))
|
||||||
|
|
||||||
(testing "batch three commands"
|
(testing "batch three commands"
|
||||||
(let [cmd (tui/batch (tui/tick 50) (tui/tick 100) tui/quit)]
|
(let [cmd (tui/batch (tui/after 50 :t1) (tui/after 100 :t2) tui/quit)]
|
||||||
(is (= [:batch [:tick 50] [:tick 100] [:quit]] cmd))))
|
(is (= :batch (first cmd)))
|
||||||
|
(is (= 4 (count cmd)))))
|
||||||
|
|
||||||
(testing "batch filters nil"
|
(testing "batch filters nil"
|
||||||
(is (= [:batch [:tick 100]] (tui/batch nil (tui/tick 100) nil)))
|
(let [cmd (tui/batch nil (tui/send-msg :msg1) nil)]
|
||||||
|
(is (= :batch (first cmd)))
|
||||||
|
(is (= 2 (count cmd))))
|
||||||
(is (= [:batch] (tui/batch nil nil nil))))
|
(is (= [:batch] (tui/batch nil nil nil))))
|
||||||
|
|
||||||
(testing "batch with single command"
|
(testing "batch with single command"
|
||||||
(is (= [:batch tui/quit] (tui/batch tui/quit)))))
|
(is (= [:batch [:quit]] (tui/batch tui/quit)))))
|
||||||
|
|
||||||
(deftest sequentially-command-test
|
(deftest sequentially-command-test
|
||||||
(testing "sequentially two commands"
|
(testing "sequentially two commands"
|
||||||
(let [cmd (tui/sequentially (tui/tick 100) tui/quit)]
|
(let [cmd (tui/sequentially (tui/after 100 :tick1) tui/quit)]
|
||||||
(is (= [:seq [:tick 100] [:quit]] cmd))))
|
(is (= :seq (first cmd)))
|
||||||
|
(is (= 3 (count cmd)))
|
||||||
|
(is (fn? (second cmd)))
|
||||||
|
(is (= [:quit] (last cmd)))))
|
||||||
|
|
||||||
(testing "sequentially filters nil"
|
(testing "sequentially filters nil"
|
||||||
(is (= [:seq [:tick 100]] (tui/sequentially nil (tui/tick 100) nil))))
|
(let [cmd (tui/sequentially nil (tui/send-msg :msg1) nil)]
|
||||||
|
(is (= :seq (first cmd)))
|
||||||
|
(is (= 2 (count cmd)))))
|
||||||
|
|
||||||
(testing "sequentially with functions"
|
(testing "sequentially with functions"
|
||||||
(let [f (fn [] :msg)
|
(let [f (fn [] :msg)
|
||||||
@@ -231,31 +244,31 @@
|
|||||||
(testing "timer tick handling pattern"
|
(testing "timer tick handling pattern"
|
||||||
(let [update-fn (fn [{:keys [seconds running] :as model} msg]
|
(let [update-fn (fn [{:keys [seconds running] :as model} msg]
|
||||||
(cond
|
(cond
|
||||||
(= (first msg) :tick)
|
(= msg :timer-tick)
|
||||||
(if running
|
(if running
|
||||||
(let [new-seconds (dec seconds)]
|
(let [new-seconds (dec seconds)]
|
||||||
(if (<= new-seconds 0)
|
(if (<= new-seconds 0)
|
||||||
[(assoc model :seconds 0 :done true :running false) nil]
|
[(assoc model :seconds 0 :done true :running false) nil]
|
||||||
[(assoc model :seconds new-seconds) (tui/tick 1000)]))
|
[(assoc model :seconds new-seconds) (tui/after 1000 :timer-tick)]))
|
||||||
[model nil])
|
[model nil])
|
||||||
|
|
||||||
(tui/key= msg " ")
|
(tui/key= msg " ")
|
||||||
(let [new-running (not running)]
|
(let [new-running (not running)]
|
||||||
[(assoc model :running new-running)
|
[(assoc model :running new-running)
|
||||||
(when new-running (tui/tick 1000))])
|
(when new-running (tui/after 1000 :timer-tick))])
|
||||||
|
|
||||||
:else
|
:else
|
||||||
[model nil]))]
|
[model nil]))]
|
||||||
|
|
||||||
;; Test tick countdown
|
;; Test tick countdown
|
||||||
(let [m0 {:seconds 3 :running true :done false}
|
(let [m0 {:seconds 3 :running true :done false}
|
||||||
[m1 c1] (update-fn m0 [:tick 123])
|
[m1 c1] (update-fn m0 :timer-tick)
|
||||||
[m2 c2] (update-fn m1 [:tick 123])
|
[m2 c2] (update-fn m1 :timer-tick)
|
||||||
[m3 c3] (update-fn m2 [:tick 123])]
|
[m3 c3] (update-fn m2 :timer-tick)]
|
||||||
(is (= 2 (:seconds m1)))
|
(is (= 2 (:seconds m1)))
|
||||||
(is (= [:tick 1000] c1))
|
(is (fn? c1))
|
||||||
(is (= 1 (:seconds m2)))
|
(is (= 1 (:seconds m2)))
|
||||||
(is (= [:tick 1000] c2))
|
(is (fn? c2))
|
||||||
(is (= 0 (:seconds m3)))
|
(is (= 0 (:seconds m3)))
|
||||||
(is (:done m3))
|
(is (:done m3))
|
||||||
(is (not (:running m3)))
|
(is (not (:running m3)))
|
||||||
@@ -268,7 +281,7 @@
|
|||||||
(is (not (:running m1)))
|
(is (not (:running m1)))
|
||||||
(is (nil? c1))
|
(is (nil? c1))
|
||||||
(is (:running m2))
|
(is (:running m2))
|
||||||
(is (= [:tick 1000] c2))))))
|
(is (fn? c2))))))
|
||||||
|
|
||||||
(deftest list-selection-update-pattern-test
|
(deftest list-selection-update-pattern-test
|
||||||
(testing "cursor navigation with bounds"
|
(testing "cursor navigation with bounds"
|
||||||
|
|||||||
+36
-14
@@ -12,24 +12,46 @@
|
|||||||
(testing "quit command is correct vector"
|
(testing "quit command is correct vector"
|
||||||
(is (= [:quit] tui/quit))))
|
(is (= [:quit] tui/quit))))
|
||||||
|
|
||||||
(deftest tick-command-test
|
(deftest after-command-test
|
||||||
(testing "tick creates correct command"
|
(testing "after creates a function command"
|
||||||
(is (= [:tick 100] (tui/tick 100)))
|
(let [cmd (tui/after 0 :my-tick)]
|
||||||
(is (= [:tick 1000] (tui/tick 1000)))))
|
(is (fn? cmd))
|
||||||
|
(is (= :my-tick (cmd)))))
|
||||||
|
|
||||||
|
(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)))))
|
||||||
|
|
||||||
|
(testing "after with non-zero delay creates function"
|
||||||
|
;; Don't invoke - these would sleep
|
||||||
|
(is (fn? (tui/after 100 :tick)))
|
||||||
|
(is (fn? (tui/after 1000 :tick)))))
|
||||||
|
|
||||||
(deftest batch-command-test
|
(deftest batch-command-test
|
||||||
(testing "batch combines commands"
|
(testing "batch combines commands"
|
||||||
(is (= [:batch [:tick 100] [:quit]] (tui/batch (tui/tick 100) tui/quit))))
|
(let [cmd (tui/batch (tui/send-msg :msg1) tui/quit)]
|
||||||
|
(is (vector? cmd))
|
||||||
|
(is (= :batch (first cmd)))
|
||||||
|
(is (= 3 (count cmd))) ; [:batch fn [:quit]]
|
||||||
|
(is (= [:quit] (last cmd)))))
|
||||||
|
|
||||||
(testing "batch filters nil commands"
|
(testing "batch filters nil commands"
|
||||||
(is (= [:batch [:tick 100]] (tui/batch nil (tui/tick 100) nil)))))
|
(let [cmd (tui/batch nil (tui/send-msg :msg1) nil)]
|
||||||
|
(is (= :batch (first cmd)))
|
||||||
|
(is (= 2 (count cmd))))))
|
||||||
|
|
||||||
(deftest sequentially-command-test
|
(deftest sequentially-command-test
|
||||||
(testing "sequentially creates seq command"
|
(testing "sequentially creates seq command"
|
||||||
(is (= [:seq [:tick 100] [:quit]] (tui/sequentially (tui/tick 100) tui/quit))))
|
(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)))))
|
||||||
|
|
||||||
(testing "sequentially filters nil commands"
|
(testing "sequentially filters nil commands"
|
||||||
(is (= [:seq [:tick 100]] (tui/sequentially nil (tui/tick 100) nil)))))
|
(let [cmd (tui/sequentially nil (tui/send-msg :msg1) nil)]
|
||||||
|
(is (= :seq (first cmd)))
|
||||||
|
(is (= 2 (count cmd))))))
|
||||||
|
|
||||||
(deftest send-msg-command-test
|
(deftest send-msg-command-test
|
||||||
(testing "send-msg creates function that returns message"
|
(testing "send-msg creates function that returns message"
|
||||||
@@ -102,10 +124,11 @@
|
|||||||
(is (= [:quit] result)))
|
(is (= [:quit] result)))
|
||||||
(close! msg-chan))))
|
(close! msg-chan))))
|
||||||
|
|
||||||
(deftest execute-tick-command-test
|
(deftest execute-after-command-test
|
||||||
(testing "tick command sends :tick message after delay"
|
(testing "after command sends message after delay"
|
||||||
(let [msg-chan (chan 1)]
|
(let [msg-chan (chan 1)
|
||||||
(#'tui/execute-cmd! [:tick 50] msg-chan)
|
cmd (tui/after 50 :delayed-msg)]
|
||||||
|
(#'tui/execute-cmd! cmd msg-chan)
|
||||||
;; Should not receive immediately
|
;; Should not receive immediately
|
||||||
(let [immediate (alt!!
|
(let [immediate (alt!!
|
||||||
msg-chan ([v] v)
|
msg-chan ([v] v)
|
||||||
@@ -115,8 +138,7 @@
|
|||||||
(let [delayed (alt!!
|
(let [delayed (alt!!
|
||||||
msg-chan ([v] v)
|
msg-chan ([v] v)
|
||||||
(timeout 200) :timeout)]
|
(timeout 200) :timeout)]
|
||||||
(is (vector? delayed))
|
(is (= :delayed-msg delayed)))
|
||||||
(is (= :tick (first delayed))))
|
|
||||||
(close! msg-chan))))
|
(close! msg-chan))))
|
||||||
|
|
||||||
(deftest execute-function-command-test
|
(deftest execute-function-command-test
|
||||||
|
|||||||
@@ -195,7 +195,7 @@
|
|||||||
(is (= [:batch] (tui/batch))))
|
(is (= [:batch] (tui/batch))))
|
||||||
|
|
||||||
(testing "batch with many commands"
|
(testing "batch with many commands"
|
||||||
(let [cmd (tui/batch (tui/tick 1) (tui/tick 2) (tui/tick 3) (tui/tick 4) (tui/tick 5))]
|
(let [cmd (tui/batch (tui/after 1 :t1) (tui/after 2 :t2) (tui/after 3 :t3) (tui/after 4 :t4) (tui/after 5 :t5))]
|
||||||
(is (= 6 (count cmd))) ; :batch + 5 commands
|
(is (= 6 (count cmd))) ; :batch + 5 commands
|
||||||
(is (= :batch (first cmd))))))
|
(is (= :batch (first cmd))))))
|
||||||
|
|
||||||
@@ -209,12 +209,22 @@
|
|||||||
(testing "sequentially with no arguments"
|
(testing "sequentially with no arguments"
|
||||||
(is (= [:seq] (tui/sequentially)))))
|
(is (= [:seq] (tui/sequentially)))))
|
||||||
|
|
||||||
(deftest tick-edge-cases-test
|
(deftest after-edge-cases-test
|
||||||
(testing "tick with zero"
|
(testing "after with zero delay"
|
||||||
(is (= [:tick 0] (tui/tick 0))))
|
(let [cmd (tui/after 0 :immediate)]
|
||||||
|
(is (fn? cmd))
|
||||||
|
;; Zero delay executes immediately
|
||||||
|
(is (= :immediate (cmd)))))
|
||||||
|
|
||||||
(testing "tick with very large value"
|
(testing "after with various delays creates function"
|
||||||
(is (= [:tick 999999999] (tui/tick 999999999)))))
|
;; Don't invoke - just verify the function is created correctly
|
||||||
|
(is (fn? (tui/after 1 :t1)))
|
||||||
|
(is (fn? (tui/after 1000 :t2)))
|
||||||
|
(is (fn? (tui/after 999999999 :t3))))
|
||||||
|
|
||||||
|
(testing "after with complex message"
|
||||||
|
(let [cmd (tui/after 0 [:tick {:id 1 :data [1 2 3]}])]
|
||||||
|
(is (= [:tick {:id 1 :data [1 2 3]}] (cmd))))))
|
||||||
|
|
||||||
(deftest send-msg-edge-cases-test
|
(deftest send-msg-edge-cases-test
|
||||||
(testing "send-msg with nil"
|
(testing "send-msg with nil"
|
||||||
|
|||||||
+17
-17
@@ -127,29 +127,29 @@
|
|||||||
(testing "timer tick decrements and reaches zero"
|
(testing "timer tick decrements and reaches zero"
|
||||||
(let [update-fn (fn [{:keys [seconds running] :as model} msg]
|
(let [update-fn (fn [{:keys [seconds running] :as model} msg]
|
||||||
(cond
|
(cond
|
||||||
(= (first msg) :tick)
|
(= msg :timer-tick)
|
||||||
(if running
|
(if running
|
||||||
(let [new-seconds (dec seconds)]
|
(let [new-seconds (dec seconds)]
|
||||||
(if (<= new-seconds 0)
|
(if (<= new-seconds 0)
|
||||||
[(assoc model :seconds 0 :done true :running false) nil]
|
[(assoc model :seconds 0 :done true :running false) nil]
|
||||||
[(assoc model :seconds new-seconds) (tui/tick 1000)]))
|
[(assoc model :seconds new-seconds) (tui/after 1000 :timer-tick)]))
|
||||||
[model nil])
|
[model nil])
|
||||||
:else [model nil]))]
|
:else [model nil]))]
|
||||||
|
|
||||||
;; Normal tick
|
;; Normal tick
|
||||||
(let [[m1 c1] (update-fn {:seconds 10 :running true :done false} [:tick 123])]
|
(let [[m1 c1] (update-fn {:seconds 10 :running true :done false} :timer-tick)]
|
||||||
(is (= 9 (:seconds m1)))
|
(is (= 9 (:seconds m1)))
|
||||||
(is (= [:tick 1000] c1)))
|
(is (fn? c1)))
|
||||||
|
|
||||||
;; Tick to zero
|
;; Tick to zero
|
||||||
(let [[m1 c1] (update-fn {:seconds 1 :running true :done false} [:tick 123])]
|
(let [[m1 c1] (update-fn {:seconds 1 :running true :done false} :timer-tick)]
|
||||||
(is (= 0 (:seconds m1)))
|
(is (= 0 (:seconds m1)))
|
||||||
(is (true? (:done m1)))
|
(is (true? (:done m1)))
|
||||||
(is (false? (:running m1)))
|
(is (false? (:running m1)))
|
||||||
(is (nil? c1)))
|
(is (nil? c1)))
|
||||||
|
|
||||||
;; Tick when paused does nothing
|
;; Tick when paused does nothing
|
||||||
(let [[m1 c1] (update-fn {:seconds 5 :running false :done false} [:tick 123])]
|
(let [[m1 c1] (update-fn {:seconds 5 :running false :done false} :timer-tick)]
|
||||||
(is (= 5 (:seconds m1)))
|
(is (= 5 (:seconds m1)))
|
||||||
(is (nil? c1))))))
|
(is (nil? c1))))))
|
||||||
|
|
||||||
@@ -159,7 +159,7 @@
|
|||||||
(if (tui/key= msg " ")
|
(if (tui/key= msg " ")
|
||||||
(let [new-running (not running)]
|
(let [new-running (not running)]
|
||||||
[(assoc model :running new-running)
|
[(assoc model :running new-running)
|
||||||
(when new-running (tui/tick 1000))])
|
(when new-running (tui/after 1000 :timer-tick))])
|
||||||
[model nil]))]
|
[model nil]))]
|
||||||
|
|
||||||
;; Pause (running -> not running)
|
;; Pause (running -> not running)
|
||||||
@@ -170,21 +170,21 @@
|
|||||||
;; Resume (not running -> running)
|
;; Resume (not running -> running)
|
||||||
(let [[m1 c1] (update-fn {:seconds 5 :running false} [:key {:char \space}])]
|
(let [[m1 c1] (update-fn {:seconds 5 :running false} [:key {:char \space}])]
|
||||||
(is (true? (:running m1)))
|
(is (true? (:running m1)))
|
||||||
(is (= [:tick 1000] c1))))))
|
(is (fn? c1))))))
|
||||||
|
|
||||||
(deftest timer-reset-test
|
(deftest timer-reset-test
|
||||||
(testing "timer reset restores initial state"
|
(testing "timer reset restores initial state"
|
||||||
(let [update-fn (fn [model msg]
|
(let [update-fn (fn [model msg]
|
||||||
(if (tui/key= msg "r")
|
(if (tui/key= msg "r")
|
||||||
[(assoc model :seconds 10 :done false :running true)
|
[(assoc model :seconds 10 :done false :running true)
|
||||||
(tui/tick 1000)]
|
(tui/after 1000 :timer-tick)]
|
||||||
[model nil]))]
|
[model nil]))]
|
||||||
|
|
||||||
(let [[m1 c1] (update-fn {:seconds 0 :done true :running false} [:key {:char \r}])]
|
(let [[m1 c1] (update-fn {:seconds 0 :done true :running false} [:key {:char \r}])]
|
||||||
(is (= 10 (:seconds m1)))
|
(is (= 10 (:seconds m1)))
|
||||||
(is (false? (:done m1)))
|
(is (false? (:done m1)))
|
||||||
(is (true? (:running m1)))
|
(is (true? (:running m1)))
|
||||||
(is (= [:tick 1000] c1))))))
|
(is (fn? c1))))))
|
||||||
|
|
||||||
(deftest timer-view-color-logic-test
|
(deftest timer-view-color-logic-test
|
||||||
(testing "timer view shows correct colors"
|
(testing "timer view shows correct colors"
|
||||||
@@ -240,19 +240,19 @@
|
|||||||
(deftest spinner-tick-advances-frame-test
|
(deftest spinner-tick-advances-frame-test
|
||||||
(testing "spinner tick advances frame when loading"
|
(testing "spinner tick advances frame when loading"
|
||||||
(let [update-fn (fn [model msg]
|
(let [update-fn (fn [model msg]
|
||||||
(if (= (first msg) :tick)
|
(if (= msg :spinner-frame)
|
||||||
(if (:loading model)
|
(if (:loading model)
|
||||||
[(update model :frame inc) (tui/tick 80)]
|
[(update model :frame inc) (tui/after 80 :spinner-frame)]
|
||||||
[model nil])
|
[model nil])
|
||||||
[model nil]))]
|
[model nil]))]
|
||||||
|
|
||||||
;; Tick advances frame when loading
|
;; Tick advances frame when loading
|
||||||
(let [[m1 c1] (update-fn {:frame 0 :loading true} [:tick 123])]
|
(let [[m1 c1] (update-fn {:frame 0 :loading true} :spinner-frame)]
|
||||||
(is (= 1 (:frame m1)))
|
(is (= 1 (:frame m1)))
|
||||||
(is (= [:tick 80] c1)))
|
(is (fn? c1)))
|
||||||
|
|
||||||
;; Tick does nothing when not loading
|
;; Tick does nothing when not loading
|
||||||
(let [[m1 c1] (update-fn {:frame 5 :loading false} [:tick 123])]
|
(let [[m1 c1] (update-fn {:frame 5 :loading false} :spinner-frame)]
|
||||||
(is (= 5 (:frame m1)))
|
(is (= 5 (:frame m1)))
|
||||||
(is (nil? c1))))))
|
(is (nil? c1))))))
|
||||||
|
|
||||||
@@ -293,14 +293,14 @@
|
|||||||
(let [update-fn (fn [model msg]
|
(let [update-fn (fn [model msg]
|
||||||
(if (tui/key= msg "r")
|
(if (tui/key= msg "r")
|
||||||
[(assoc model :loading true :frame 0 :message "Loading...")
|
[(assoc model :loading true :frame 0 :message "Loading...")
|
||||||
(tui/tick 80)]
|
(tui/after 80 :spinner-frame)]
|
||||||
[model nil]))]
|
[model nil]))]
|
||||||
|
|
||||||
(let [[m1 c1] (update-fn {:loading false :frame 100 :message "Done!"} [:key {:char \r}])]
|
(let [[m1 c1] (update-fn {:loading false :frame 100 :message "Done!"} [:key {:char \r}])]
|
||||||
(is (true? (:loading m1)))
|
(is (true? (:loading m1)))
|
||||||
(is (= 0 (:frame m1)))
|
(is (= 0 (:frame m1)))
|
||||||
(is (= "Loading..." (:message m1)))
|
(is (= "Loading..." (:message m1)))
|
||||||
(is (= [:tick 80] c1))))))
|
(is (fn? c1))))))
|
||||||
|
|
||||||
;; =============================================================================
|
;; =============================================================================
|
||||||
;; LIST SELECTION EXAMPLE TESTS
|
;; LIST SELECTION EXAMPLE TESTS
|
||||||
|
|||||||
Reference in New Issue
Block a user