From 20ed51348abfc27cf524e620d5840d4cc188666d Mon Sep 17 00:00:00 2001 From: Dave Roberts Date: Sat, 22 Nov 2025 17:05:43 -0600 Subject: [PATCH 1/3] Add tests for `sort-by` --- test/clojure/core_test/sort_by.cljc | 151 ++++++++++++++++++++++++++++ 1 file changed, 151 insertions(+) create mode 100644 test/clojure/core_test/sort_by.cljc diff --git a/test/clojure/core_test/sort_by.cljc b/test/clojure/core_test/sort_by.cljc new file mode 100644 index 00000000..69c9552c --- /dev/null +++ b/test/clojure/core_test/sort_by.cljc @@ -0,0 +1,151 @@ +(ns clojure.core-test.sort-by + (:require [clojure.test :as t :refer [deftest testing is are]] + [clojure.core-test.portability #?(:cljs :refer-macros :default :refer) [when-var-exists]])) + +(when-var-exists sort-by + ;; Data for simple tests + (def simple-vec-maps [{:a 2 :b 9} + {:a 1 :b 10} + {:a 4 :b 7} + {:a 3 :b 8}]) + + ;; Data to test stable sorting + (def stable-data [{:a 1 :b 11} + {:a 1 :b 21} + {:a 1 :b 39} + {:a 1 :b 2} + {:a 4 :b 5} + {:a 2 :b 101} + {:a 2 :b 71} + {:a 2 :b 28} + {:a 2 :b 99} + {:a 4 :b 210} + {:a 3 :b 113} + {:a 3 :b 412} + {:a 3 :b 135} + {:a 3 :b 314} + {:a 4 :b 215}]) + + (deftest test-sort-by + + ;; We're not testing `compare` so keep it simple + ;; (sort ...) = (sort-by identity ...) + + ;; comparator defaults to compare + ;; must implement java.util.Comparator + + (testing "sort-by artity-2" + (testing "key-fn is `identity`" + (are [expected x] (= expected (sort x) (sort-by identity x)) + ;; cases from `sort` tests, since (sort ...) = (sort-by identity ...) + '() nil + '(1 2 3 4) [3 1 2 4] + '(nil 2 3 4) [3 nil 2 4] + '(1 2 3 4) '(3 1 2 4) + '(1 2 3 4) #{3 1 2 4} + '([:a 1] [:b 2] [:c 3]) {:c 3 :b 2 :a 1} + '([1] [2] [3] [4]) [[3] [1] [2] [4]] + '("a" "b" "c" "d") ["b" "a" "c" "d"] + '(\c \e \j \l \o \r \u) "clojure")) + (testing "key-fn is keyword" + (is (= (seq [{:a 4 :b 7} + {:a 3 :b 8} + {:a 2 :b 9} + {:a 1 :b 10}]) + (sort-by :b simple-vec-maps))) + (is (= (seq [{:a 1 :b 10} + {:a 2 :b 9} + {:a 3 :b 8} + {:a 4 :b 7}]) + (sort-by :a simple-vec-maps))) + ;; test more complex key-fn and stability of sort-by + ;; :a + :b = 11 for all elements of simple-vec-maps + (is (= (seq simple-vec-maps) + (sort-by #(+ (:a %) (:b %)) simple-vec-maps))) + ;; If the key-fn returns the same value all the time we also + ;; expect no change. For instance, when the key-fn returns + ;; `nil` constantly because we use the wrong keyword or we + ;; navigate the structure of the element incorrectly. + (is (= (seq simple-vec-maps) + (sort-by :c simple-vec-maps))))) + + (testing "sort-by artity-3" + ;; Use `compare` + (is (= (seq [{:a 1 :b 10} + {:a 2 :b 9} + {:a 3 :b 8} + {:a 4 :b 7}]) + (sort-by :a compare simple-vec-maps))) + ;; Reverse `compare` + (is (= (seq [{:a 4 :b 7} + {:a 3 :b 8} + {:a 2 :b 9} + {:a 1 :b 10}]) + (sort-by :a #(compare %2 %1) simple-vec-maps))) + (is (= (seq [{:a 1 :b 10} + {:a 2 :b 9} + {:a 3 :b 8} + {:a 4 :b 7}]) + (sort-by :a < simple-vec-maps))) + (is (= (seq [{:a 4 :b 7} + {:a 3 :b 8} + {:a 2 :b 9} + {:a 1 :b 10}]) + (sort-by :a > simple-vec-maps))) + + ;; Use `compare` + (is (= (seq [{:a 4 :b 7} + {:a 3 :b 8} + {:a 2 :b 9} + {:a 1 :b 10}]) + (sort-by :b compare simple-vec-maps))) + ;; Reverse `compare` + (is (= (seq [{:a 1 :b 10} + {:a 2 :b 9} + {:a 3 :b 8} + {:a 4 :b 7}]) + (sort-by :b #(compare %2 %1) simple-vec-maps))) + (is (= (seq [{:a 4 :b 7} + {:a 3 :b 8} + {:a 2 :b 9} + {:a 1 :b 10}]) + (sort-by :b < simple-vec-maps))) + (is (= (seq [{:a 1 :b 10} + {:a 2 :b 9} + {:a 3 :b 8} + {:a 4 :b 7}]) + (sort-by :b > simple-vec-maps)))) + + (testing "negative cases" + ;; key-fn is not a fn + (is (thrown? #?(:cljs :default, :default Exception) (sort-by nil simple-vec-maps))) + (is (thrown? #?(:cljs :default, :default Exception) (sort-by [] simple-vec-maps))) + ;; comparator is not a fn + (is (thrown? #?(:cljs :default, :default Exception) (sort-by :a nil simple-vec-maps))) + (is (thrown? #?(:cljs :default, :default Exception) (sort-by :a [] simple-vec-maps))) + ;; collection is not a collection + (is (thrown? #?(:cljs :default, :default Exception) (sort-by :a 1))) + (is (thrown? #?(:cljs :default, :default Exception) (sort-by :a true)))) + + (testing "stable sort" + ;; sort first by :b and then by :a results in runs of :a in + ;; order with all the :b's associated with a given :a being in + ;; order + (is (= (seq [{:a 1, :b 2} + {:a 1, :b 11} + {:a 1, :b 21} + {:a 1, :b 39} + {:a 2, :b 28} + {:a 2, :b 71} + {:a 2, :b 99} + {:a 2, :b 101} + {:a 3, :b 113} + {:a 3, :b 135} + {:a 3, :b 314} + {:a 3, :b 412} + {:a 4, :b 5} + {:a 4, :b 210} + {:a 4, :b 215}]) + (->> stable-data + (sort-by :b) + (sort-by :a))))))) From c5d675c5ca59f5bdb1d7659cee2191062fc94116 Mon Sep 17 00:00:00 2001 From: Dave Roberts Date: Sat, 22 Nov 2025 20:48:51 -0600 Subject: [PATCH 2/3] Remove some notes in comments --- test/clojure/core_test/sort_by.cljc | 7 ------- 1 file changed, 7 deletions(-) diff --git a/test/clojure/core_test/sort_by.cljc b/test/clojure/core_test/sort_by.cljc index 69c9552c..310984c0 100644 --- a/test/clojure/core_test/sort_by.cljc +++ b/test/clojure/core_test/sort_by.cljc @@ -27,13 +27,6 @@ {:a 4 :b 215}]) (deftest test-sort-by - - ;; We're not testing `compare` so keep it simple - ;; (sort ...) = (sort-by identity ...) - - ;; comparator defaults to compare - ;; must implement java.util.Comparator - (testing "sort-by artity-2" (testing "key-fn is `identity`" (are [expected x] (= expected (sort x) (sort-by identity x)) From 99bf25de33e05e97dc91343560841ac04b495fd3 Mon Sep 17 00:00:00 2001 From: Dave Roberts Date: Sat, 29 Nov 2025 21:43:29 -0600 Subject: [PATCH 3/3] Tweak comment --- test/clojure/core_test/sort_by.cljc | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/test/clojure/core_test/sort_by.cljc b/test/clojure/core_test/sort_by.cljc index 310984c0..c42d57c4 100644 --- a/test/clojure/core_test/sort_by.cljc +++ b/test/clojure/core_test/sort_by.cljc @@ -9,7 +9,7 @@ {:a 4 :b 7} {:a 3 :b 8}]) - ;; Data to test stable sorting + ;; Data for stable sorting (def stable-data [{:a 1 :b 11} {:a 1 :b 21} {:a 1 :b 39}