(ns tui.ansi-test "Unit tests for ANSI escape codes and string utilities." (:require [clojure.test :refer [deftest testing is]] [clojure.string :as str] [tui.ansi :as ansi])) ;; === Style Tests === (deftest style-foreground-test (testing "applies foreground colors" (let [result (ansi/style "text" :fg :red)] (is (str/includes? result "31m")) (is (str/includes? result "text")) (is (str/ends-with? result ansi/reset)))) (testing "applies bright foreground colors" (let [result (ansi/style "text" :fg :bright-red)] (is (str/includes? result "91m")))) (testing "applies gray/grey alias" (let [gray (ansi/style "text" :fg :gray) grey (ansi/style "text" :fg :grey)] (is (= gray grey)) (is (str/includes? gray "90m"))))) (deftest style-background-test (testing "applies background colors" (let [result (ansi/style "text" :bg :blue)] (is (str/includes? result "44m")) (is (str/includes? result "text"))))) (deftest style-attributes-test (testing "applies bold" (let [result (ansi/style "text" :bold true)] (is (str/includes? result "1m")))) (testing "applies dim" (let [result (ansi/style "text" :dim true)] (is (str/includes? result "2m")))) (testing "applies italic" (let [result (ansi/style "text" :italic true)] (is (str/includes? result "3m")))) (testing "applies underline" (let [result (ansi/style "text" :underline true)] (is (str/includes? result "4m")))) (testing "applies inverse" (let [result (ansi/style "text" :inverse true)] (is (str/includes? result "7m")))) (testing "applies strikethrough" (let [result (ansi/style "text" :strike true)] (is (str/includes? result "9m"))))) (deftest style-combined-test (testing "combines multiple styles" (let [result (ansi/style "text" :fg :red :bold true :underline true)] (is (str/includes? result "31")) ; Red (is (str/includes? result "1")) ; Bold (is (str/includes? result "4"))))) ; Underline (deftest style-no-styles-test (testing "returns plain text when no styles" (is (= "text" (ansi/style "text"))))) ;; === Color Helper Tests === (deftest fg-helper-test (testing "fg helper applies foreground color" (let [result (ansi/fg :green "text")] (is (str/includes? result "32m")) (is (str/includes? result "text"))))) (deftest bg-helper-test (testing "bg helper applies background color" (let [result (ansi/bg :yellow "text")] (is (str/includes? result "43m")) (is (str/includes? result "text"))))) ;; === 256 Color Tests === (deftest fg-256-test (testing "applies 256-color foreground" (let [result (ansi/fg-256 196 "text")] (is (str/includes? result "38;5;196m")) (is (str/includes? result "text"))))) (deftest bg-256-test (testing "applies 256-color background" (let [result (ansi/bg-256 21 "text")] (is (str/includes? result "48;5;21m")) (is (str/includes? result "text"))))) ;; === True Color Tests === (deftest fg-rgb-test (testing "applies RGB foreground" (let [result (ansi/fg-rgb 255 128 64 "text")] (is (str/includes? result "38;2;255;128;64m")) (is (str/includes? result "text"))))) (deftest bg-rgb-test (testing "applies RGB background" (let [result (ansi/bg-rgb 0 128 255 "text")] (is (str/includes? result "48;2;0;128;255m")) (is (str/includes? result "text"))))) ;; === String Utility Tests === (deftest visible-length-test (testing "returns length of plain text" (is (= 5 (ansi/visible-length "hello"))) (is (= 0 (ansi/visible-length "")))) (testing "excludes ANSI codes from length" (let [styled (ansi/style "hello" :fg :red :bold true)] (is (= 5 (ansi/visible-length styled))))) (testing "handles multiple ANSI sequences" (let [text (str (ansi/fg :red "red") (ansi/fg :blue "blue"))] (is (= 7 (ansi/visible-length text)))))) (deftest pad-right-test (testing "pads string to width" (is (= "hi " (ansi/pad-right "hi" 5))) (is (= "hello" (ansi/pad-right "hello" 5)))) (testing "does not truncate if longer" (is (= "hello" (ansi/pad-right "hello" 3)))) (testing "handles styled text" (let [styled (ansi/style "hi" :fg :red) padded (ansi/pad-right styled 5)] (is (= 5 (ansi/visible-length padded)))))) (deftest pad-left-test (testing "pads string on left" (is (= " hi" (ansi/pad-left "hi" 5))) (is (= "hello" (ansi/pad-left "hello" 5)))) (testing "does not truncate if longer" (is (= "hello" (ansi/pad-left "hello" 3))))) (deftest pad-center-test (testing "centers string" (is (= " hi " (ansi/pad-center "hi" 5))) (is (= " hi " (ansi/pad-center "hi" 6)))) (testing "handles odd padding" (is (= " x " (ansi/pad-center "x" 3))))) (deftest truncate-test (testing "truncates long strings" (is (= "hel\u2026" (ansi/truncate "hello" 4))) (is (= "h\u2026" (ansi/truncate "hello" 2)))) (testing "does not truncate short strings" (is (= "hi" (ansi/truncate "hi" 5))) (is (= "hello" (ansi/truncate "hello" 5))))) ;; === Box Characters Tests === (deftest box-chars-test (testing "all border styles have required characters" (doseq [[style chars] ansi/box-chars] (is (contains? chars :tl) (str style " missing :tl")) (is (contains? chars :tr) (str style " missing :tr")) (is (contains? chars :bl) (str style " missing :bl")) (is (contains? chars :br) (str style " missing :br")) (is (contains? chars :h) (str style " missing :h")) (is (contains? chars :v) (str style " missing :v"))))) ;; === Escape Sequence Constants Tests === (deftest escape-sequences-test (testing "escape sequences are strings" (is (string? ansi/clear-screen)) (is (string? ansi/clear-line)) (is (string? ansi/cursor-home)) (is (string? ansi/hide-cursor)) (is (string? ansi/show-cursor)) (is (string? ansi/enter-alt-screen)) (is (string? ansi/exit-alt-screen))) (testing "escape sequences start with ESC" (is (str/starts-with? ansi/clear-screen "\u001b")) (is (str/starts-with? ansi/cursor-home "\u001b")))) (deftest cursor-movement-test (testing "cursor-to generates correct sequence" (is (= "\u001b[5;10H" (ansi/cursor-to 5 10)))) (testing "cursor movement functions" (is (= "\u001b[3A" (ansi/cursor-up 3))) (is (= "\u001b[2B" (ansi/cursor-down 2))) (is (= "\u001b[4C" (ansi/cursor-forward 4))) (is (= "\u001b[1D" (ansi/cursor-back 1)))))