(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])))))