| 12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667686970717273747576777879808182838485868788899091 |
- (ns microtables-frontend.events-test
- (:require
- [cljs.test :refer-macros [deftest testing is]]
- [microtables-frontend.evaluation :as evaluation]
- [microtables-frontend.utils.data :as data-utils]))
- ; Tests for the state-transition pipelines that event handlers compose.
- ; We test the pure functions directly rather than going through re-frame dispatch.
- ; The event handlers in events.cljs are thin wrappers around these functions.
- (defn initial-table
- "Build a fully initialised table-data map (references wired, all formulas evaluated)."
- [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))
- ;; --- Mirrors ::edit-cell-value then ::movement-leave-cell ---
- (deftest editing-value-then-leaving-triggers-downstream-reevaluation
- ; Start: A1=3, B1=A1*2 → B1 displays 6
- ; Edit A1 to 10, then leave → B1 should display 20
- (let [table (initial-table {"A" {1 {:value "3"}}
- "B" {1 {:value "=A1*2"}}})
- updated (-> table
- (data-utils/change-datum-value "A" 1 "10")
- (evaluation/reset-references "A" 1)
- (evaluation/evaluate-from-cell "A" 1))]
- (is (= 20 (get-in updated ["B" 1 :display])))))
- (deftest edit-cell-value-marks-dirty-without-evaluating
- ; change-datum-value (::edit-cell-value) only updates :value and marks :dirty;
- ; it does not produce a :display value.
- (let [table (initial-table {"A" {1 {:value "3"}}})
- updated (data-utils/change-datum-value table "A" 1 "=1+2")]
- (is (= "=1+2" (get-in updated ["A" 1 :value])))
- (is (= true (get-in updated ["A" 1 :dirty])))
- (is (nil? (get-in updated ["A" 1 :display])))))
- (deftest downstream-cells-marked-dirty-on-edit
- ; When A1 is edited, B1 (which depends on A1) should also be marked dirty.
- (let [table (initial-table {"A" {1 {:value "5"}}
- "B" {1 {:value "=A1+1"}}})
- updated (data-utils/change-datum-value table "A" 1 "99")]
- (is (= true (get-in updated ["B" 1 :dirty])))))
- ;; --- Mirrors ::movement-leave-cell reference graph update ---
- (deftest formula-change-removes-old-back-reference
- ; B1 starts referencing A1. When we change B1 to reference C1,
- ; A1's :inbound should no longer include B1.
- (let [table (initial-table {"A" {1 {:value "5"}}
- "B" {1 {:value "=A1"}}})
- updated (-> table
- (data-utils/change-datum-value "B" 1 "=C1+1")
- (evaluation/reset-references "B" 1)
- (evaluation/evaluate-from-cell "B" 1))]
- (is (not (some #(= % {:col "B" :row 1})
- (get-in updated ["A" 1 :inbound]))))))
- (deftest formula-change-adds-new-back-reference
- ; Same scenario: after B1 is changed to reference C1, C1's :inbound should include B1.
- (let [table (initial-table {"A" {1 {:value "5"}}
- "B" {1 {:value "=A1"}}})
- updated (-> table
- (data-utils/change-datum-value "B" 1 "=C1+1")
- (evaluation/reset-references "B" 1)
- (evaluation/evaluate-from-cell "B" 1))]
- (is (some #(= % {:col "B" :row 1})
- (get-in updated ["C" 1 :inbound])))))
- ;; --- Deeply nested dependency propagation ---
- (deftest deep-dependency-chain-reevaluated
- ; A1 → B1 → C1 → D1; editing A1 should propagate all the way to D1.
- (let [table (initial-table {"A" {1 {:value "1"}}
- "B" {1 {:value "=A1+1"}}
- "C" {1 {:value "=B1+1"}}
- "D" {1 {:value "=C1+1"}}})
- updated (-> table
- (data-utils/change-datum-value "A" 1 "10")
- (evaluation/reset-references "A" 1)
- (evaluation/evaluate-from-cell "A" 1))]
- (is (= 13 (get-in updated ["D" 1 :display])))))
|