|
| 1 | +;; Tests for sort function |
| 2 | +;; Ensures Glojure behavior matches Clojure |
| 3 | + |
| 4 | +(ns glojure.test-glojure.sort |
| 5 | + (:require [glojure.test :refer :all] |
| 6 | + [glojure.string :as s])) |
| 7 | + |
| 8 | +(deftest test-sort-basic |
| 9 | + (testing "Basic sort functionality" |
| 10 | + ;; Numbers |
| 11 | + (is (= '(1 2 3 4 5) (sort [3 1 4 2 5]))) |
| 12 | + (is (= '(1 1 3 4 5) (sort [3 1 4 1 5]))) ; duplicates preserved |
| 13 | + (is (= '(1.0 2.5 3 4.7) (sort [4.7 1.0 3 2.5]))) ; mixed numeric types |
| 14 | + |
| 15 | + ;; Strings |
| 16 | + (is (= '("apple" "banana" "cherry") (sort ["cherry" "apple" "banana"]))) |
| 17 | + (is (= '("" "a" "ab" "b") (sort ["b" "a" "" "ab"]))) ; empty string sorts first |
| 18 | + |
| 19 | + ;; Keywords |
| 20 | + (is (= '(:a :b :c) (sort [:c :a :b]))) |
| 21 | + (is (= '(:a/x :b/x :c/x) (sort [:c/x :a/x :b/x]))) ; namespaced keywords |
| 22 | + |
| 23 | + ;; Symbols |
| 24 | + (is (= '(a b c) (sort '[c a b]))) |
| 25 | + (is (= '(a/x b/x c/x) (sort '[c/x a/x b/x]))) ; namespaced symbols |
| 26 | + |
| 27 | + ;; Empty collection |
| 28 | + (is (= '() (sort []))) |
| 29 | + (is (= '() (sort '()))) |
| 30 | + (is (= '() (sort nil))) ; nil returns empty seq |
| 31 | + |
| 32 | + ;; Single element |
| 33 | + (is (= '(42) (sort [42]))) |
| 34 | + |
| 35 | + ;; Already sorted |
| 36 | + (is (= '(1 2 3) (sort [1 2 3]))) |
| 37 | + |
| 38 | + ;; Reverse sorted |
| 39 | + (is (= '(1 2 3) (sort [3 2 1]))))) |
| 40 | + |
| 41 | +(deftest test-sort-with-comparator |
| 42 | + (testing "Sort with custom comparator" |
| 43 | + ;; Reverse sort |
| 44 | + (is (= '(5 4 3 2 1) (sort (fn [a b] (compare b a)) [3 1 4 5 2]))) |
| 45 | + (is (= '(5 4 3 2 1) (sort > [3 1 4 5 2]))) ; using > as comparator |
| 46 | + |
| 47 | + ;; Case-insensitive string sort |
| 48 | + (is (= '("apple" "Banana" "cherry") |
| 49 | + (sort (fn [a b] (compare (s/lower-case a) (s/lower-case b))) |
| 50 | + ["cherry" "apple" "Banana"]))) |
| 51 | + |
| 52 | + ;; Sort by string length |
| 53 | + (is (= '("a" "bb" "ccc" "dddd") |
| 54 | + (sort (fn [a b] (compare (count a) (count b))) |
| 55 | + ["ccc" "a" "dddd" "bb"]))) |
| 56 | + |
| 57 | + ;; Sort maps by a specific key |
| 58 | + (let [data [{:name "John" :age 30} |
| 59 | + {:name "Jane" :age 25} |
| 60 | + {:name "Bob" :age 35}]] |
| 61 | + (is (= [{:name "Jane" :age 25} |
| 62 | + {:name "John" :age 30} |
| 63 | + {:name "Bob" :age 35}] |
| 64 | + (sort (fn [a b] (compare (:age a) (:age b))) data)))))) |
| 65 | + |
| 66 | +(deftest test-sort-nil-handling |
| 67 | + (testing "Nil handling in sort" |
| 68 | + ;; nil sorts before everything |
| 69 | + (is (= '(nil 1 2 3) (sort [3 nil 1 2]))) |
| 70 | + (is (= '(nil nil 1 2) (sort [2 nil 1 nil]))) |
| 71 | + (is (= '(nil "a" "b") (sort ["b" nil "a"]))) |
| 72 | + |
| 73 | + ;; With custom comparator that handles nil |
| 74 | + (is (= '(3 2 1 nil) |
| 75 | + (sort (fn [a b] |
| 76 | + (cond |
| 77 | + (nil? a) 1 ; nil goes to end |
| 78 | + (nil? b) -1 |
| 79 | + :else (compare b a))) |
| 80 | + [nil 1 2 3]))))) |
| 81 | + |
| 82 | +(deftest test-sort-different-collection-types |
| 83 | + (testing "Sort works on different collection types" |
| 84 | + ;; Vector |
| 85 | + (is (= '(1 2 3) (sort [3 1 2]))) |
| 86 | + |
| 87 | + ;; List |
| 88 | + (is (= '(1 2 3) (sort '(3 1 2)))) |
| 89 | + |
| 90 | + ;; Set |
| 91 | + (is (= '(1 2 3) (sort #{3 1 2}))) |
| 92 | + |
| 93 | + ;; Map entries (sorted as vectors) |
| 94 | + (let [result (sort {:b 2 :a 1 :c 3})] |
| 95 | + (is (= 3 (count result))) |
| 96 | + (is (every? vector? result)) |
| 97 | + (is (= :a (first (first result))))) |
| 98 | + |
| 99 | + ;; String (converts to character sequence) |
| 100 | + (is (= '(\a \b \c \d) (sort "dcba"))) |
| 101 | + |
| 102 | + ;; Range |
| 103 | + (is (= '(0 1 2 3 4) (sort (reverse (range 5))))))) |
| 104 | + |
| 105 | +(deftest test-sort-stability |
| 106 | + (testing "Sort is stable" |
| 107 | + ;; Create items that compare equal but are distinguishable |
| 108 | + (let [items [{:id 1 :value 1} |
| 109 | + {:id 2 :value 2} |
| 110 | + {:id 3 :value 1} |
| 111 | + {:id 4 :value 2} |
| 112 | + {:id 5 :value 1}] |
| 113 | + sorted (sort (fn [a b] (compare (:value a) (:value b))) items)] |
| 114 | + ;; Items with same value should maintain relative order |
| 115 | + (is (= [{:id 1 :value 1} |
| 116 | + {:id 3 :value 1} |
| 117 | + {:id 5 :value 1} |
| 118 | + {:id 2 :value 2} |
| 119 | + {:id 4 :value 2}] |
| 120 | + sorted))))) |
| 121 | + |
| 122 | +(deftest test-sort-metadata-preservation |
| 123 | + (testing "Sort preserves metadata" |
| 124 | + (let [coll ^{:my-meta true} [3 1 2] |
| 125 | + sorted (sort coll)] |
| 126 | + (is (= '(1 2 3) sorted)) |
| 127 | + (is (= true (:my-meta (meta sorted))))))) |
| 128 | + |
| 129 | +(deftest test-sort-edge-cases |
| 130 | + (testing "Sort edge cases" |
| 131 | + ;; Large collection |
| 132 | + (let [large (repeatedly 1000 #(rand-int 100)) |
| 133 | + sorted (sort large)] |
| 134 | + (is (= 1000 (count sorted))) |
| 135 | + (is (apply <= sorted))) ; verify it's actually sorted |
| 136 | + |
| 137 | + ;; All equal elements |
| 138 | + (is (= '(1 1 1 1) (sort [1 1 1 1]))) |
| 139 | + |
| 140 | + ;; Mixed positive/negative numbers |
| 141 | + (is (= '(-3 -1 0 1 3) (sort [1 -1 3 0 -3]))))) |
| 142 | + |
| 143 | +(deftest test-sort-error-cases |
| 144 | + (testing "Sort error cases" |
| 145 | + ;; Invalid comparator (not a function) |
| 146 | + (is (thrown? go/any (sort "not-a-function" [1 2 3]))) |
| 147 | + |
| 148 | + ;; Comparator returns non-numeric |
| 149 | + (is (thrown? go/any |
| 150 | + (sort (fn [a b] "not-a-number") [1 2 3]))) |
| 151 | + |
| 152 | + ;; Uncomparable types (this might throw or might have undefined behavior) |
| 153 | + ;; Clojure's behavior here is to throw ClassCastException |
| 154 | + (is (thrown? go/any |
| 155 | + (sort [1 "a" :b]))))) |
| 156 | + |
| 157 | +(deftest test-compare-function |
| 158 | + (testing "Compare function behavior" |
| 159 | + ;; Numbers |
| 160 | + (is (= -1 (compare 1 2))) |
| 161 | + (is (= 0 (compare 2 2))) |
| 162 | + (is (= 1 (compare 3 2))) |
| 163 | + (is (= -1 (compare 1.5 2))) |
| 164 | + (is (= 1 (compare 2.5 2))) |
| 165 | + |
| 166 | + ;; Strings |
| 167 | + (is (= -1 (compare "a" "b"))) |
| 168 | + (is (= 0 (compare "hello" "hello"))) |
| 169 | + (is (= 1 (compare "z" "a"))) |
| 170 | + |
| 171 | + ;; Keywords |
| 172 | + (is (= -1 (compare :a :b))) |
| 173 | + (is (= 0 (compare :x :x))) |
| 174 | + |
| 175 | + ;; Symbols |
| 176 | + (is (= -1 (compare 'a 'b))) |
| 177 | + (is (= 0 (compare 'x 'x))) |
| 178 | + |
| 179 | + ;; nil handling |
| 180 | + (is (= -1 (compare nil 1))) |
| 181 | + (is (= -1 (compare nil "a"))) |
| 182 | + (is (= -1 (compare nil :a))) |
| 183 | + (is (= 0 (compare nil nil))) |
| 184 | + (is (= 1 (compare 1 nil))) |
| 185 | + |
| 186 | + ;; Different numeric types |
| 187 | + (is (= 0 (compare 1 1.0))) |
| 188 | + (is (= 0 (compare 1.0 1))))) |
| 189 | + |
| 190 | +(deftest test-sort-maps-behavior |
| 191 | + (testing "Sorting maps produces map entries" |
| 192 | + (let [m {:b 2 :a 1 :c 3} |
| 193 | + sorted (sort m)] |
| 194 | + ;; Each element should be a map entry (vector of [k v]) |
| 195 | + (is (every? vector? sorted)) |
| 196 | + (is (every? #(= 2 (count %)) sorted)) |
| 197 | + ;; Should be sorted by key |
| 198 | + (is (= [[:a 1] [:b 2] [:c 3]] sorted))))) |
| 199 | + |
| 200 | +;; Run tests |
| 201 | +(run-tests) |
0 commit comments