When :href is provided, the button renders as <a> instead of <button>, enabling navigation links with full button styling. New :link variant renders as a minimal text link (underlined, accent color, no background/padding) — works as both <button> and <a>. CSS includes reset styles for a.btn (removes default link decoration) and preserves underline for a.btn-link.
259 lines
11 KiB
Clojure
259 lines
11 KiB
Clojure
(ns ui.form-test
|
|
(:require [clojure.test :refer [deftest is testing]]
|
|
[ui.form :as form]))
|
|
|
|
;; ── form-field ──────────────────────────────────────────────────────
|
|
|
|
(deftest form-field-class-list-test
|
|
(testing "default field"
|
|
(is (= ["form-field"] (form/form-field-class-list {}))))
|
|
(testing "with error"
|
|
(is (= ["form-field" "form-field--error"] (form/form-field-class-list {:error true})))))
|
|
|
|
(deftest form-field-classes-test
|
|
(testing "default"
|
|
(is (= "form-field" (form/form-field-classes {}))))
|
|
(testing "with error"
|
|
(is (= "form-field form-field--error" (form/form-field-classes {:error true})))))
|
|
|
|
(deftest form-field-component-test
|
|
(testing "renders with label and child"
|
|
(let [result (form/form-field {:label "Name"} [:input {:type "text"}])]
|
|
(is (= :div (first result)))
|
|
(is (= "form-field" (get-in result [1 :class])))
|
|
;; label
|
|
(is (= :label (first (nth result 2))))
|
|
(is (= "Name" (nth (nth result 2) 2)))
|
|
;; child input
|
|
(is (= :input (first (nth result 3))))))
|
|
|
|
(testing "renders hint text"
|
|
(let [result (form/form-field {:label "Password" :hint "At least 8 characters"} [:input])]
|
|
(is (some #(and (vector? %) (= :small (first %)) (= "form-hint" (get-in % [1 :class])))
|
|
result))))
|
|
|
|
(testing "renders error text"
|
|
(let [result (form/form-field {:label "Email" :error "Invalid email"} [:input])]
|
|
(is (= "form-field form-field--error" (get-in result [1 :class])))
|
|
(is (some #(and (vector? %) (= :small (first %)) (= "form-error" (get-in % [1 :class])))
|
|
result))))
|
|
|
|
(testing "no label renders without label element"
|
|
(let [result (form/form-field {} [:input])]
|
|
(is (= :input (first (nth result 2))))))
|
|
|
|
(testing "extra class appended"
|
|
(let [result (form/form-field {:class "extra"} [:input])]
|
|
(is (= "form-field extra" (get-in result [1 :class]))))))
|
|
|
|
;; ── form-input ──────────────────────────────────────────────────────
|
|
|
|
(deftest form-input-class-list-test
|
|
(testing "default"
|
|
(is (= ["form-input"] (form/form-input-class-list {}))))
|
|
(testing "with error"
|
|
(is (= ["form-input" "form-input--error"] (form/form-input-class-list {:error true})))))
|
|
|
|
(deftest form-input-component-test
|
|
(testing "basic text input"
|
|
(let [result (form/form-input {:type :text :placeholder "Name"})]
|
|
(is (= :input (first result)))
|
|
(is (= "form-input" (get-in result [1 :class])))
|
|
(is (= "text" (get-in result [1 :type])))
|
|
(is (= "Name" (get-in result [1 :placeholder])))))
|
|
|
|
(testing "email type"
|
|
(let [result (form/form-input {:type :email})]
|
|
(is (= "email" (get-in result [1 :type])))))
|
|
|
|
(testing "password type"
|
|
(let [result (form/form-input {:type :password})]
|
|
(is (= "password" (get-in result [1 :type])))))
|
|
|
|
(testing "date type"
|
|
(let [result (form/form-input {:type :date})]
|
|
(is (= "date" (get-in result [1 :type])))))
|
|
|
|
(testing "datetime-local type"
|
|
(let [result (form/form-input {:type :datetime-local})]
|
|
(is (= "datetime-local" (get-in result [1 :type])))))
|
|
|
|
(testing "default type is text"
|
|
(let [result (form/form-input {})]
|
|
(is (= "text" (get-in result [1 :type])))))
|
|
|
|
(testing "disabled"
|
|
(let [result (form/form-input {:disabled true})]
|
|
(is (true? (get-in result [1 :disabled])))))
|
|
|
|
(testing "with value"
|
|
(let [result (form/form-input {:value "hello"})]
|
|
(is (= "hello" (get-in result [1 :value])))))
|
|
|
|
(testing "error styling"
|
|
(let [result (form/form-input {:error true})]
|
|
(is (= "form-input form-input--error" (get-in result [1 :class]))))))
|
|
|
|
;; ── form-textarea ───────────────────────────────────────────────────
|
|
|
|
(deftest form-textarea-class-list-test
|
|
(testing "default"
|
|
(is (= ["form-textarea"] (form/form-textarea-class-list {}))))
|
|
(testing "with error"
|
|
(is (= ["form-textarea" "form-textarea--error"] (form/form-textarea-class-list {:error true})))))
|
|
|
|
(deftest form-textarea-component-test
|
|
(testing "basic textarea"
|
|
(let [result (form/form-textarea {:placeholder "Message"})]
|
|
(is (= :textarea (first result)))
|
|
(is (= "form-textarea" (get-in result [1 :class])))
|
|
(is (= "Message" (get-in result [1 :placeholder])))))
|
|
|
|
(testing "disabled"
|
|
(let [result (form/form-textarea {:disabled true})]
|
|
(is (true? (get-in result [1 :disabled])))))
|
|
|
|
(testing "with value"
|
|
(let [result (form/form-textarea {:value "hello"})]
|
|
(is (= "hello" (nth result 2))))))
|
|
|
|
;; ── form-select ─────────────────────────────────────────────────────
|
|
|
|
(deftest form-select-component-test
|
|
(testing "basic select with options"
|
|
(let [result (form/form-select {:options [{:value "a" :label "Option A"}
|
|
{:value "b" :label "Option B"}]})]
|
|
(is (= :select (first result)))
|
|
(is (= "form-select" (get-in result [1 :class])))
|
|
;; two options
|
|
(is (= 4 (count result)))))
|
|
|
|
(testing "with placeholder"
|
|
(let [result (form/form-select {:placeholder "Pick one"
|
|
:options [{:value "a" :label "A"}]})]
|
|
;; select + option A + placeholder option
|
|
(is (some #(and (vector? %) (= "Pick one" (nth % 2 nil)))
|
|
(rest (rest result))))))
|
|
|
|
(testing "disabled"
|
|
(let [result (form/form-select {:disabled true :options []})]
|
|
(is (true? (get-in result [1 :disabled])))))
|
|
|
|
(testing "string options"
|
|
(let [result (form/form-select {:options ["Foo" "Bar"]})]
|
|
(is (= 4 (count result))))))
|
|
|
|
;; ── form-checkbox ───────────────────────────────────────────────────
|
|
|
|
(deftest form-checkbox-component-test
|
|
(testing "basic checkbox"
|
|
(let [result (form/form-checkbox {:label "Agree"})]
|
|
(is (= :label (first result)))
|
|
(is (= "form-field form-field--inline" (get-in result [1 :class])))
|
|
;; has input checkbox
|
|
(let [input (nth result 2)]
|
|
(is (= :input (first input)))
|
|
(is (= "checkbox" (get-in input [1 :type])))
|
|
(is (= "form-checkbox" (get-in input [1 :class]))))
|
|
;; has label text
|
|
(is (= [:span "Agree"] (nth result 3)))))
|
|
|
|
(testing "checked"
|
|
(let [result (form/form-checkbox {:label "Agree" :checked true})
|
|
input (nth result 2)]
|
|
(is (true? (get-in input [1 :checked])))))
|
|
|
|
(testing "disabled"
|
|
(let [result (form/form-checkbox {:disabled true})
|
|
input (nth result 2)]
|
|
(is (true? (get-in input [1 :disabled]))))))
|
|
|
|
;; ── form-radio-group ────────────────────────────────────────────────
|
|
|
|
(deftest form-radio-group-component-test
|
|
(testing "basic radio group"
|
|
(let [result (form/form-radio-group {:label "Preference"
|
|
:radio-name "pref"
|
|
:options [{:value "a" :label "A"}
|
|
{:value "b" :label "B"}
|
|
{:value "c" :label "C"}]})]
|
|
(is (= :fieldset (first result)))
|
|
(is (= "form-fieldset form-fieldset--inline" (get-in result [1 :class])))
|
|
;; legend
|
|
(is (= :legend (first (nth result 2))))
|
|
(is (= "Preference" (nth (nth result 2) 2)))
|
|
;; three radio labels
|
|
(is (= 6 (count result))))) ; fieldset + attrs + legend + 3 radios
|
|
|
|
(testing "radio with selected value"
|
|
(let [result (form/form-radio-group {:radio-name "pref"
|
|
:radio-value "b"
|
|
:options [{:value "a" :label "A"}
|
|
{:value "b" :label "B"}]})
|
|
;; find the radio for "b"
|
|
radio-b (nth result 3) ; second radio (after attrs, no legend since no :label)
|
|
input-b (nth radio-b 1)]
|
|
(is (true? (get-in input-b [1 :checked])))))
|
|
|
|
(testing "string options"
|
|
(let [result (form/form-radio-group {:radio-name "x" :options ["X" "Y"]})]
|
|
;; 2 radios + attrs = 4 elements
|
|
(is (= 4 (count result))))))
|
|
|
|
;; ── form-file ───────────────────────────────────────────────────────
|
|
|
|
(deftest form-file-component-test
|
|
(testing "basic file input"
|
|
(let [result (form/form-file {})]
|
|
(is (= :input (first result)))
|
|
(is (= "file" (get-in result [1 :type])))
|
|
(is (= "form-file" (get-in result [1 :class])))))
|
|
|
|
(testing "with accept"
|
|
(let [result (form/form-file {:accept "image/*"})]
|
|
(is (= "image/*" (get-in result [1 :accept])))))
|
|
|
|
(testing "multiple"
|
|
(let [result (form/form-file {:multiple true})]
|
|
(is (true? (get-in result [1 :multiple])))))
|
|
|
|
(testing "disabled"
|
|
(let [result (form/form-file {:disabled true})]
|
|
(is (true? (get-in result [1 :disabled]))))))
|
|
|
|
;; ── form-range ──────────────────────────────────────────────────────
|
|
|
|
(deftest form-range-component-test
|
|
(testing "basic range"
|
|
(let [result (form/form-range {:min 0 :max 100 :value 50})]
|
|
(is (= :input (first result)))
|
|
(is (= "range" (get-in result [1 :type])))
|
|
(is (= "form-range" (get-in result [1 :class])))
|
|
(is (= 0 (get-in result [1 :min])))
|
|
(is (= 100 (get-in result [1 :max])))
|
|
(is (= 50 (get-in result [1 :value])))))
|
|
|
|
(testing "with step"
|
|
(let [result (form/form-range {:step 5})]
|
|
(is (= 5 (get-in result [1 :step])))))
|
|
|
|
(testing "disabled"
|
|
(let [result (form/form-range {:disabled true})]
|
|
(is (true? (get-in result [1 :disabled]))))))
|
|
|
|
;; ── form-group ──────────────────────────────────────────────────────
|
|
|
|
(deftest form-group-component-test
|
|
(testing "basic group"
|
|
(let [result (form/form-group {} [:input] [:button "Go"])]
|
|
(is (= :div (first result)))
|
|
(is (= "form-group" (get-in result [1 :class])))
|
|
(is (= 4 (count result))))))
|
|
|
|
(deftest form-group-addon-component-test
|
|
(testing "basic addon"
|
|
(let [result (form/form-group-addon {} "https://")]
|
|
(is (= :span (first result)))
|
|
(is (= "form-group-addon" (get-in result [1 :class])))
|
|
(is (= "https://" (nth result 2))))))
|