From 1e0f10814ed65b32d09cf6943983b3210c3425a6 Mon Sep 17 00:00:00 2001 From: Andrey Antukh Date: Tue, 14 Apr 2026 19:04:04 +0000 Subject: [PATCH] :fire: Remove duplicate gradient helpers from app.common.colors The five functions interpolate-color, offset-spread, uniform-spread?, uniform-spread, and interpolate-gradient duplicated the canonical implementations in app.common.types.color. The copies in colors.cljc also contained two bugs: a division-by-zero in offset-spread when num=1, and a crash on nil idx in interpolate-gradient. All production callers already use app.common.types.color. The duplicate tests that exercised the old copies are removed; their coverage is absorbed into expanded tests under the types-* suite, including a new nil-idx guard test and a single-stop no-crash test. Signed-off-by: Andrey Antukh --- common/src/app/common/colors.cljc | 59 -------------- common/test/common_tests/colors_test.cljc | 94 ++++++++++++----------- 2 files changed, 50 insertions(+), 103 deletions(-) diff --git a/common/src/app/common/colors.cljc b/common/src/app/common/colors.cljc index e16acf94a3..ab7c7e2a76 100644 --- a/common/src/app/common/colors.cljc +++ b/common/src/app/common/colors.cljc @@ -487,62 +487,3 @@ b (+ (* bh 100) (* bv 10))] (compare a b))) -(defn interpolate-color - [c1 c2 offset] - (cond - (<= offset (:offset c1)) (assoc c1 :offset offset) - (>= offset (:offset c2)) (assoc c2 :offset offset) - - :else - (let [tr-offset (/ (- offset (:offset c1)) (- (:offset c2) (:offset c1))) - [r1 g1 b1] (hex->rgb (:color c1)) - [r2 g2 b2] (hex->rgb (:color c2)) - a1 (:opacity c1) - a2 (:opacity c2) - r (+ r1 (* (- r2 r1) tr-offset)) - g (+ g1 (* (- g2 g1) tr-offset)) - b (+ b1 (* (- b2 b1) tr-offset)) - a (+ a1 (* (- a2 a1) tr-offset))] - {:color (rgb->hex [r g b]) - :opacity a - :r r - :g g - :b b - :alpha a - :offset offset}))) - -(defn- offset-spread - [from to num] - (->> (range 0 num) - (map #(mth/precision (+ from (* (/ (- to from) (dec num)) %)) 2)))) - -(defn uniform-spread? - "Checks if the gradient stops are spread uniformly" - [stops] - (let [cs (count stops) - from (first stops) - to (last stops) - expect-vals (offset-spread (:offset from) (:offset to) cs) - - calculate-expected - (fn [expected-offset stop] - (and (mth/close? (:offset stop) expected-offset) - (let [ec (interpolate-color from to expected-offset)] - (and (= (:color ec) (:color stop)) - (= (:opacity ec) (:opacity stop))))))] - (->> (map calculate-expected expect-vals stops) - (every? true?)))) - -(defn uniform-spread - "Assign an uniform spread to the offset values for the gradient" - [from to num-stops] - (->> (offset-spread (:offset from) (:offset to) num-stops) - (mapv (fn [offset] - (interpolate-color from to offset))))) - -(defn interpolate-gradient - [stops offset] - (let [idx (d/index-of-pred stops #(<= offset (:offset %))) - start (if (= idx 0) (first stops) (get stops (dec idx))) - end (if (nil? idx) (last stops) (get stops idx))] - (interpolate-color start end offset))) diff --git a/common/test/common_tests/colors_test.cljc b/common/test/common_tests/colors_test.cljc index aa1edd450a..21f6af5bef 100644 --- a/common/test/common_tests/colors_test.cljc +++ b/common/test/common_tests/colors_test.cljc @@ -387,44 +387,41 @@ (t/is (= 0.25 (c/reduce-range 0.3 4))) (t/is (= 0.0 (c/reduce-range 0.0 10)))) -;; --- Gradient helpers +;; --- Gradient helpers (app.common.types.color) -(t/deftest ac-interpolate-color - (let [c1 {:color "#000000" :opacity 0.0 :offset 0.0} - c2 {:color "#ffffff" :opacity 1.0 :offset 1.0}] - ;; At c1's offset → c1 with updated offset - (let [result (c/interpolate-color c1 c2 0.0)] +(t/deftest types-interpolate-color + (t/testing "at c1 offset returns c1 color" + (let [c1 {:color "#000000" :opacity 0.0 :offset 0.0} + c2 {:color "#ffffff" :opacity 1.0 :offset 1.0} + result (colors/interpolate-color c1 c2 0.0)] (t/is (= "#000000" (:color result))) - (t/is (= 0.0 (:opacity result)))) - ;; At c2's offset → c2 with updated offset - (let [result (c/interpolate-color c1 c2 1.0)] + (t/is (= 0.0 (:opacity result))))) + (t/testing "at c2 offset returns c2 color" + (let [c1 {:color "#000000" :opacity 0.0 :offset 0.0} + c2 {:color "#ffffff" :opacity 1.0 :offset 1.0} + result (colors/interpolate-color c1 c2 1.0)] (t/is (= "#ffffff" (:color result))) - (t/is (= 1.0 (:opacity result)))) - ;; At midpoint → gray - (let [result (c/interpolate-color c1 c2 0.5)] + (t/is (= 1.0 (:opacity result))))) + (t/testing "at midpoint returns interpolated gray" + (let [c1 {:color "#000000" :opacity 0.0 :offset 0.0} + c2 {:color "#ffffff" :opacity 1.0 :offset 1.0} + result (colors/interpolate-color c1 c2 0.5)] (t/is (= "#7f7f7f" (:color result))) (t/is (mth/close? (:opacity result) 0.5))))) -(t/deftest ac-uniform-spread - (let [c1 {:color "#000000" :opacity 0.0 :offset 0.0} - c2 {:color "#ffffff" :opacity 1.0 :offset 1.0} - stops (c/uniform-spread c1 c2 3)] - (t/is (= 3 (count stops))) - (t/is (= 0.0 (:offset (first stops)))) - (t/is (mth/close? 0.5 (:offset (second stops)))) - (t/is (= 1.0 (:offset (last stops)))))) - -(t/deftest ac-uniform-spread? - (let [c1 {:color "#000000" :opacity 0.0 :offset 0.0} - c2 {:color "#ffffff" :opacity 1.0 :offset 1.0} - stops (c/uniform-spread c1 c2 3)] - ;; A uniformly spread result should pass the predicate - (t/is (true? (c/uniform-spread? stops)))) - ;; Manual non-uniform stops should not pass - (let [stops [{:color "#000000" :opacity 0.0 :offset 0.0} - {:color "#888888" :opacity 0.5 :offset 0.3} - {:color "#ffffff" :opacity 1.0 :offset 1.0}]] - (t/is (false? (c/uniform-spread? stops))))) +(t/deftest types-uniform-spread + (t/testing "produces correct count and offsets" + (let [c1 {:color "#000000" :opacity 0.0 :offset 0.0} + c2 {:color "#ffffff" :opacity 1.0 :offset 1.0} + stops (colors/uniform-spread c1 c2 3)] + (t/is (= 3 (count stops))) + (t/is (= 0.0 (:offset (first stops)))) + (t/is (mth/close? 0.5 (:offset (second stops)))) + (t/is (= 1.0 (:offset (last stops)))))) + (t/testing "single stop returns a vector of one element (no division by zero)" + (let [c1 {:color "#ff0000" :opacity 1.0 :offset 0.0} + stops (colors/uniform-spread c1 c1 1)] + (t/is (= 1 (count stops)))))) (t/deftest types-uniform-spread? (t/testing "uniformly spread stops are detected as uniform" @@ -447,16 +444,25 @@ {:color "#ffffff" :opacity 1.0 :offset 1.0}]] (t/is (false? (colors/uniform-spread? stops)))))) -(t/deftest ac-interpolate-gradient - (let [stops [{:color "#000000" :opacity 0.0 :offset 0.0} - {:color "#ffffff" :opacity 1.0 :offset 1.0}]] - ;; At start - (let [result (c/interpolate-gradient stops 0.0)] - (t/is (= "#000000" (:color result)))) - ;; At end - (let [result (c/interpolate-gradient stops 1.0)] - (t/is (= "#ffffff" (:color result)))) - ;; In the middle - (let [result (c/interpolate-gradient stops 0.5)] - (t/is (= "#7f7f7f" (:color result)))))) +(t/deftest types-interpolate-gradient + (t/testing "at start offset returns first stop color" + (let [stops [{:color "#000000" :opacity 0.0 :offset 0.0} + {:color "#ffffff" :opacity 1.0 :offset 1.0}] + result (colors/interpolate-gradient stops 0.0)] + (t/is (= "#000000" (:color result))))) + (t/testing "at end offset returns last stop color" + (let [stops [{:color "#000000" :opacity 0.0 :offset 0.0} + {:color "#ffffff" :opacity 1.0 :offset 1.0}] + result (colors/interpolate-gradient stops 1.0)] + (t/is (= "#ffffff" (:color result))))) + (t/testing "at midpoint returns interpolated gray" + (let [stops [{:color "#000000" :opacity 0.0 :offset 0.0} + {:color "#ffffff" :opacity 1.0 :offset 1.0}] + result (colors/interpolate-gradient stops 0.5)] + (t/is (= "#7f7f7f" (:color result))))) + (t/testing "offset beyond last stop returns last stop color (nil idx guard)" + (let [stops [{:color "#000000" :opacity 0.0 :offset 0.0} + {:color "#ffffff" :opacity 1.0 :offset 0.5}] + result (colors/interpolate-gradient stops 1.0)] + (t/is (= "#ffffff" (:color result))))))