|
@@ -0,0 +1,107 @@
|
|
|
|
|
+(ns microtables-frontend.evaluation-test
|
|
|
|
|
+ (:require
|
|
|
|
|
+ [cljs.test :refer-macros [deftest testing is]]
|
|
|
|
|
+ [microtables-frontend.evaluation :as evaluation]
|
|
|
|
|
+ [microtables-frontend.utils.data :as data-utils]))
|
|
|
|
|
+
|
|
|
|
|
+(defn prepare-and-evaluate
|
|
|
|
|
+ "Wire up all references and evaluate all formula cells in a raw table-data map."
|
|
|
|
|
+ [raw-data]
|
|
|
|
|
+ (-> raw-data
|
|
|
|
|
+ (data-utils/walk-modify-data
|
|
|
|
|
+ (fn [_c _r datum]
|
|
|
|
|
+ (if (data-utils/formula? (:value datum))
|
|
|
|
|
+ (assoc datum :dirty true)
|
|
|
|
|
+ datum)))
|
|
|
|
|
+ data-utils/create-all-references
|
|
|
|
|
+ evaluation/create-all-back-references
|
|
|
|
|
+ evaluation/evaluate-all))
|
|
|
|
|
+
|
|
|
|
|
+;; --- Literal values ---
|
|
|
|
|
+
|
|
|
|
|
+(deftest literal-value-is-preserved
|
|
|
|
|
+ (let [data (prepare-and-evaluate {"A" {1 {:value "42"}}})]
|
|
|
|
|
+ (is (= "42" (get-in data ["A" 1 :value])))))
|
|
|
|
|
+
|
|
|
|
|
+;; --- Basic arithmetic ---
|
|
|
|
|
+
|
|
|
|
|
+(deftest pure-arithmetic-formula
|
|
|
|
|
+ (let [data (prepare-and-evaluate {"A" {1 {:value "=1+2"}}})]
|
|
|
|
|
+ (is (= 3 (get-in data ["A" 1 :display])))))
|
|
|
|
|
+
|
|
|
|
|
+(deftest arithmetic-multiplication
|
|
|
|
|
+ (let [data (prepare-and-evaluate {"A" {1 {:value "=3*4"}}})]
|
|
|
|
|
+ (is (= 12 (get-in data ["A" 1 :display])))))
|
|
|
|
|
+
|
|
|
|
|
+;; --- Cell references ---
|
|
|
|
|
+
|
|
|
|
|
+(deftest single-cell-reference
|
|
|
|
|
+ (let [data (prepare-and-evaluate {"A" {1 {:value "5"}}
|
|
|
|
|
+ "B" {1 {:value "=A1+3"}}})]
|
|
|
|
|
+ (is (= 8 (get-in data ["B" 1 :display])))))
|
|
|
|
|
+
|
|
|
|
|
+(deftest chained-references
|
|
|
|
|
+ (let [data (prepare-and-evaluate {"A" {1 {:value "2"}}
|
|
|
|
|
+ "B" {1 {:value "=A1*3"}}
|
|
|
|
|
+ "C" {1 {:value "=B1+1"}}})]
|
|
|
|
|
+ (is (= 7 (get-in data ["C" 1 :display])))))
|
|
|
|
|
+
|
|
|
|
|
+(deftest multi-cell-reference-in-formula
|
|
|
|
|
+ (let [data (prepare-and-evaluate {"A" {1 {:value "10"}}
|
|
|
|
|
+ "B" {1 {:value "20"}}
|
|
|
|
|
+ "C" {1 {:value "=A1+B1"}}})]
|
|
|
|
|
+ (is (= 30 (get-in data ["C" 1 :display])))))
|
|
|
|
|
+
|
|
|
|
|
+;; --- Range functions ---
|
|
|
|
|
+
|
|
|
|
|
+(deftest sum-contiguous-range
|
|
|
|
|
+ (let [data (prepare-and-evaluate {"A" {1 {:value "1"}
|
|
|
|
|
+ 2 {:value "2"}
|
|
|
|
|
+ 3 {:value "3"}}
|
|
|
|
|
+ "B" {1 {:value "=sum(A1:A3)"}}})]
|
|
|
|
|
+ (is (= 6 (get-in data ["B" 1 :display])))))
|
|
|
|
|
+
|
|
|
|
|
+(deftest average-range
|
|
|
|
|
+ (let [data (prepare-and-evaluate {"A" {1 {:value "2"}
|
|
|
|
|
+ 2 {:value "4"}
|
|
|
|
|
+ 3 {:value "6"}}
|
|
|
|
|
+ "B" {1 {:value "=average(A1:A3)"}}})]
|
|
|
|
|
+ (is (= 4 (get-in data ["B" 1 :display])))))
|
|
|
|
|
+
|
|
|
|
|
+(deftest sum-with-empty-cell-in-range
|
|
|
|
|
+ ; A2 is absent — treated as 0 and excluded from the range by preprocess-expression,
|
|
|
|
|
+ ; so sum(A1,A3) = 5 + 10 = 15
|
|
|
|
|
+ (let [data (prepare-and-evaluate {"A" {1 {:value "5"}
|
|
|
|
|
+ 3 {:value "10"}}
|
|
|
|
|
+ "B" {1 {:value "=sum(A1:A3)"}}})]
|
|
|
|
|
+ (is (= 15 (get-in data ["B" 1 :display])))))
|
|
|
|
|
+
|
|
|
|
|
+;; --- Cycle detection ---
|
|
|
|
|
+
|
|
|
|
|
+(deftest mutual-cycle-marked-as-error
|
|
|
|
|
+ (let [data (prepare-and-evaluate {"A" {1 {:value "=B1"}}
|
|
|
|
|
+ "B" {1 {:value "=A1"}}})]
|
|
|
|
|
+ (is (= :cycle-error (get-in data ["A" 1 :display])))
|
|
|
|
|
+ (is (= :cycle-error (get-in data ["B" 1 :display])))))
|
|
|
|
|
+
|
|
|
|
|
+(deftest self-reference-marked-as-cycle
|
|
|
|
|
+ (let [data (prepare-and-evaluate {"A" {1 {:value "=A1"}}})]
|
|
|
|
|
+ (is (= :cycle-error (get-in data ["A" 1 :display])))))
|
|
|
|
|
+
|
|
|
|
|
+(deftest three-cell-cycle
|
|
|
|
|
+ (let [data (prepare-and-evaluate {"A" {1 {:value "=C1"}}
|
|
|
|
|
+ "B" {1 {:value "=A1"}}
|
|
|
|
|
+ "C" {1 {:value "=B1"}}})]
|
|
|
|
|
+ (is (= :cycle-error (get-in data ["A" 1 :display])))))
|
|
|
|
|
+
|
|
|
|
|
+;; --- Error propagation ---
|
|
|
|
|
+
|
|
|
|
|
+(deftest calc-error-on-invalid-formula
|
|
|
|
|
+ (let [data (prepare-and-evaluate {"A" {1 {:value "=notafunction()"}}})]
|
|
|
|
|
+ (is (= :calc-error (get-in data ["A" 1 :display])))))
|
|
|
|
|
+
|
|
|
|
|
+;; --- Non-formula cell has no :display ---
|
|
|
|
|
+
|
|
|
|
|
+(deftest non-formula-has-no-display
|
|
|
|
|
+ (let [data (prepare-and-evaluate {"A" {1 {:value "hello"}}})]
|
|
|
|
|
+ (is (nil? (get-in data ["A" 1 :display])))))
|