update docs
This commit is contained in:
@@ -1,281 +0,0 @@
|
||||
(ns tui.simple-test
|
||||
"Unit tests for the simple (synchronous) TUI runtime."
|
||||
(:require [clojure.test :refer [deftest testing is]]
|
||||
[tui.simple :as simple]
|
||||
[tui.render :as render]))
|
||||
|
||||
;; =============================================================================
|
||||
;; QUIT COMMAND TESTS
|
||||
;; =============================================================================
|
||||
|
||||
(deftest quit-command-test
|
||||
(testing "quit command is correct vector"
|
||||
(is (= [:quit] simple/quit))
|
||||
(is (vector? simple/quit))
|
||||
(is (= :quit (first simple/quit)))))
|
||||
|
||||
;; =============================================================================
|
||||
;; KEY MATCHING TESTS (same API as tui.core)
|
||||
;; =============================================================================
|
||||
|
||||
(deftest key=-character-keys-test
|
||||
(testing "matches single character keys"
|
||||
(is (simple/key= [:key {:char \q}] "q"))
|
||||
(is (simple/key= [:key {:char \a}] "a"))
|
||||
(is (simple/key= [:key {:char \space}] " ")))
|
||||
|
||||
(testing "does not match different characters"
|
||||
(is (not (simple/key= [:key {:char \q}] "a")))
|
||||
(is (not (simple/key= [:key {:char \x}] "y")))))
|
||||
|
||||
(deftest key=-special-keys-test
|
||||
(testing "matches special keys by keyword"
|
||||
(is (simple/key= [:key :enter] :enter))
|
||||
(is (simple/key= [:key :escape] :escape))
|
||||
(is (simple/key= [:key :up] :up))
|
||||
(is (simple/key= [:key :down] :down))
|
||||
(is (simple/key= [:key :left] :left))
|
||||
(is (simple/key= [:key :right] :right))
|
||||
(is (simple/key= [:key :tab] :tab))
|
||||
(is (simple/key= [:key :backspace] :backspace))))
|
||||
|
||||
(deftest key=-ctrl-combos-test
|
||||
(testing "matches ctrl+char combinations"
|
||||
(is (simple/key= [:key {:ctrl true :char \c}] [:ctrl \c]))
|
||||
(is (simple/key= [:key {:ctrl true :char \x}] [:ctrl \x])))
|
||||
|
||||
(testing "ctrl combo does not match plain char"
|
||||
(is (not (simple/key= [:key {:ctrl true :char \c}] "c")))
|
||||
(is (not (simple/key= [:key {:char \c}] [:ctrl \c])))))
|
||||
|
||||
(deftest key=-alt-combos-test
|
||||
(testing "matches alt+char combinations"
|
||||
(is (simple/key= [:key {:alt true :char \x}] [:alt \x])))
|
||||
|
||||
(testing "alt combo does not match plain char"
|
||||
(is (not (simple/key= [:key {:alt true :char \x}] "x")))))
|
||||
|
||||
(deftest key=-non-key-messages-test
|
||||
(testing "returns nil for non-key messages"
|
||||
(is (nil? (simple/key= [:tick 123] "q")))
|
||||
(is (nil? (simple/key= [:quit] :enter)))
|
||||
(is (nil? (simple/key= nil "a")))))
|
||||
|
||||
;; =============================================================================
|
||||
;; KEY-STR TESTS
|
||||
;; =============================================================================
|
||||
|
||||
(deftest key-str-test
|
||||
(testing "converts character keys to strings"
|
||||
(is (= "q" (simple/key-str [:key {:char \q}])))
|
||||
(is (= " " (simple/key-str [:key {:char \space}]))))
|
||||
|
||||
(testing "converts special keys to strings"
|
||||
(is (= "enter" (simple/key-str [:key :enter])))
|
||||
(is (= "escape" (simple/key-str [:key :escape])))
|
||||
(is (= "up" (simple/key-str [:key :up]))))
|
||||
|
||||
(testing "converts modifier keys to strings"
|
||||
(is (= "ctrl+c" (simple/key-str [:key {:ctrl true :char \c}])))
|
||||
(is (= "alt+x" (simple/key-str [:key {:alt true :char \x}]))))
|
||||
|
||||
(testing "returns nil for non-key messages"
|
||||
(is (nil? (simple/key-str [:tick 123])))
|
||||
(is (nil? (simple/key-str nil)))))
|
||||
|
||||
;; =============================================================================
|
||||
;; RENDER RE-EXPORT TESTS
|
||||
;; =============================================================================
|
||||
|
||||
(deftest render-reexport-test
|
||||
(testing "simple/render is the same as render/render"
|
||||
(is (= (render/render [:text "hello"])
|
||||
(simple/render [:text "hello"])))
|
||||
(is (= (render/render [:col "a" "b"])
|
||||
(simple/render [:col "a" "b"])))))
|
||||
|
||||
;; =============================================================================
|
||||
;; UPDATE FUNCTION CONTRACT TESTS (same as tui.core)
|
||||
;; =============================================================================
|
||||
|
||||
(deftest simple-update-contract-test
|
||||
(testing "update function returns [model cmd] tuple"
|
||||
(let [update-fn (fn [model msg]
|
||||
(cond
|
||||
(simple/key= msg "q") [model simple/quit]
|
||||
(simple/key= msg :up) [(update model :n inc) nil]
|
||||
:else [model nil]))
|
||||
model {:n 0}]
|
||||
|
||||
;; Quit returns command
|
||||
(let [[new-model cmd] (update-fn model [:key {:char \q}])]
|
||||
(is (= model new-model))
|
||||
(is (= [:quit] cmd)))
|
||||
|
||||
;; Up returns updated model
|
||||
(let [[new-model cmd] (update-fn model [:key :up])]
|
||||
(is (= {:n 1} new-model))
|
||||
(is (nil? cmd)))
|
||||
|
||||
;; Unknown key returns model unchanged
|
||||
(let [[new-model cmd] (update-fn model [:key {:char \x}])]
|
||||
(is (= model new-model))
|
||||
(is (nil? cmd))))))
|
||||
|
||||
;; =============================================================================
|
||||
;; COUNTER PATTERN TESTS (from counter example, works with simple runtime)
|
||||
;; =============================================================================
|
||||
|
||||
(deftest simple-counter-pattern-test
|
||||
(testing "counter update pattern without async commands"
|
||||
(let [update-fn (fn [{:keys [count] :as model} msg]
|
||||
(cond
|
||||
(or (simple/key= msg "q")
|
||||
(simple/key= msg [:ctrl \c]))
|
||||
[model simple/quit]
|
||||
|
||||
(or (simple/key= msg :up)
|
||||
(simple/key= msg "k"))
|
||||
[(update model :count inc) nil]
|
||||
|
||||
(or (simple/key= msg :down)
|
||||
(simple/key= msg "j"))
|
||||
[(update model :count dec) nil]
|
||||
|
||||
(simple/key= msg "r")
|
||||
[(assoc model :count 0) nil]
|
||||
|
||||
:else
|
||||
[model nil]))]
|
||||
|
||||
;; Test increment with up arrow
|
||||
(let [[m1 _] (update-fn {:count 0} [:key :up])]
|
||||
(is (= 1 (:count m1))))
|
||||
|
||||
;; Test increment with k
|
||||
(let [[m1 _] (update-fn {:count 0} [:key {:char \k}])]
|
||||
(is (= 1 (:count m1))))
|
||||
|
||||
;; Test decrement
|
||||
(let [[m1 _] (update-fn {:count 5} [:key :down])]
|
||||
(is (= 4 (:count m1))))
|
||||
|
||||
;; Test reset
|
||||
(let [[m1 _] (update-fn {:count 42} [:key {:char \r}])]
|
||||
(is (= 0 (:count m1))))
|
||||
|
||||
;; Test quit with q
|
||||
(let [[_ cmd] (update-fn {:count 0} [:key {:char \q}])]
|
||||
(is (= simple/quit cmd)))
|
||||
|
||||
;; Test quit with ctrl+c
|
||||
(let [[_ cmd] (update-fn {:count 0} [:key {:ctrl true :char \c}])]
|
||||
(is (= simple/quit cmd))))))
|
||||
|
||||
;; =============================================================================
|
||||
;; LIST SELECTION PATTERN TESTS (works with simple runtime)
|
||||
;; =============================================================================
|
||||
|
||||
(deftest simple-list-selection-pattern-test
|
||||
(testing "list selection with cursor navigation"
|
||||
(let [items ["Pizza" "Sushi" "Tacos" "Burger"]
|
||||
update-fn (fn [{:keys [cursor items] :as model} msg]
|
||||
(cond
|
||||
(or (simple/key= msg :up)
|
||||
(simple/key= msg "k"))
|
||||
[(update model :cursor #(max 0 (dec %))) nil]
|
||||
|
||||
(or (simple/key= msg :down)
|
||||
(simple/key= msg "j"))
|
||||
[(update model :cursor #(min (dec (count items)) (inc %))) nil]
|
||||
|
||||
(simple/key= msg " ")
|
||||
[(update model :selected
|
||||
#(if (contains? % cursor)
|
||||
(disj % cursor)
|
||||
(conj % cursor)))
|
||||
nil]
|
||||
|
||||
(simple/key= msg :enter)
|
||||
[(assoc model :submitted true) simple/quit]
|
||||
|
||||
:else
|
||||
[model nil]))]
|
||||
|
||||
;; Test cursor bounds - can't go below 0
|
||||
(let [[m1 _] (update-fn {:cursor 0 :items items :selected #{}} [:key :up])]
|
||||
(is (= 0 (:cursor m1))))
|
||||
|
||||
;; Test cursor bounds - can't go above max
|
||||
(let [[m1 _] (update-fn {:cursor 3 :items items :selected #{}} [:key :down])]
|
||||
(is (= 3 (:cursor m1))))
|
||||
|
||||
;; Test toggle selection
|
||||
(let [m0 {:cursor 1 :items items :selected #{}}
|
||||
[m1 _] (update-fn m0 [:key {:char \space}])
|
||||
[m2 _] (update-fn m1 [:key {:char \space}])]
|
||||
(is (= #{1} (:selected m1)))
|
||||
(is (= #{} (:selected m2))))
|
||||
|
||||
;; Test submission
|
||||
(let [[m1 cmd] (update-fn {:cursor 0 :items items :selected #{0 2} :submitted false}
|
||||
[:key :enter])]
|
||||
(is (:submitted m1))
|
||||
(is (= simple/quit cmd))))))
|
||||
|
||||
;; =============================================================================
|
||||
;; VIEWS STATE MACHINE TESTS (works with simple runtime)
|
||||
;; =============================================================================
|
||||
|
||||
(deftest simple-views-state-machine-test
|
||||
(testing "view state transitions"
|
||||
(let [items [{:name "Profile" :desc "Profile settings"}
|
||||
{:name "Settings" :desc "App preferences"}]
|
||||
update-fn (fn [{:keys [view cursor items] :as model} msg]
|
||||
(case view
|
||||
:menu
|
||||
(cond
|
||||
(simple/key= msg :enter)
|
||||
[(assoc model :view :detail :selected (nth items cursor)) nil]
|
||||
(simple/key= msg "q")
|
||||
[model simple/quit]
|
||||
:else [model nil])
|
||||
|
||||
:detail
|
||||
(cond
|
||||
(or (simple/key= msg :escape)
|
||||
(simple/key= msg "b"))
|
||||
[(assoc model :view :menu :selected nil) nil]
|
||||
(simple/key= msg "q")
|
||||
[(assoc model :view :confirm) nil]
|
||||
:else [model nil])
|
||||
|
||||
:confirm
|
||||
(cond
|
||||
(simple/key= msg "y")
|
||||
[model simple/quit]
|
||||
(simple/key= msg "n")
|
||||
[(assoc model :view :detail) nil]
|
||||
:else [model nil])))]
|
||||
|
||||
;; Menu -> Detail
|
||||
(let [[m1 _] (update-fn {:view :menu :cursor 0 :items items} [:key :enter])]
|
||||
(is (= :detail (:view m1)))
|
||||
(is (= "Profile" (:name (:selected m1)))))
|
||||
|
||||
;; Detail -> Menu (back)
|
||||
(let [[m1 _] (update-fn {:view :detail :selected (first items)} [:key :escape])]
|
||||
(is (= :menu (:view m1)))
|
||||
(is (nil? (:selected m1))))
|
||||
|
||||
;; Detail -> Confirm (quit attempt)
|
||||
(let [[m1 _] (update-fn {:view :detail} [:key {:char \q}])]
|
||||
(is (= :confirm (:view m1))))
|
||||
|
||||
;; Confirm -> Quit (yes)
|
||||
(let [[_ cmd] (update-fn {:view :confirm} [:key {:char \y}])]
|
||||
(is (= simple/quit cmd)))
|
||||
|
||||
;; Confirm -> Detail (no)
|
||||
(let [[m1 _] (update-fn {:view :confirm} [:key {:char \n}])]
|
||||
(is (= :detail (:view m1)))))))
|
||||
Reference in New Issue
Block a user