update docs

This commit is contained in:
2026-01-22 10:50:26 -05:00
parent 95b53f7533
commit 0e40fe01d7
14 changed files with 57 additions and 508 deletions
-281
View File
@@ -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)))))))