events_test.cljs 4.0 KB

12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667686970717273747576777879808182838485868788899091
  1. (ns microtables-frontend.events-test
  2. (:require
  3. [cljs.test :refer-macros [deftest testing is]]
  4. [microtables-frontend.evaluation :as evaluation]
  5. [microtables-frontend.utils.data :as data-utils]))
  6. ; Tests for the state-transition pipelines that event handlers compose.
  7. ; We test the pure functions directly rather than going through re-frame dispatch.
  8. ; The event handlers in events.cljs are thin wrappers around these functions.
  9. (defn initial-table
  10. "Build a fully initialised table-data map (references wired, all formulas evaluated)."
  11. [raw-data]
  12. (-> raw-data
  13. (data-utils/walk-modify-data
  14. (fn [_c _r datum]
  15. (if (data-utils/formula? (:value datum))
  16. (assoc datum :dirty true)
  17. datum)))
  18. data-utils/create-all-references
  19. evaluation/create-all-back-references
  20. evaluation/evaluate-all))
  21. ;; --- Mirrors ::edit-cell-value then ::movement-leave-cell ---
  22. (deftest editing-value-then-leaving-triggers-downstream-reevaluation
  23. ; Start: A1=3, B1=A1*2 → B1 displays 6
  24. ; Edit A1 to 10, then leave → B1 should display 20
  25. (let [table (initial-table {"A" {1 {:value "3"}}
  26. "B" {1 {:value "=A1*2"}}})
  27. updated (-> table
  28. (data-utils/change-datum-value "A" 1 "10")
  29. (evaluation/reset-references "A" 1)
  30. (evaluation/evaluate-from-cell "A" 1))]
  31. (is (= 20 (get-in updated ["B" 1 :display])))))
  32. (deftest edit-cell-value-marks-dirty-without-evaluating
  33. ; change-datum-value (::edit-cell-value) only updates :value and marks :dirty;
  34. ; it does not produce a :display value.
  35. (let [table (initial-table {"A" {1 {:value "3"}}})
  36. updated (data-utils/change-datum-value table "A" 1 "=1+2")]
  37. (is (= "=1+2" (get-in updated ["A" 1 :value])))
  38. (is (= true (get-in updated ["A" 1 :dirty])))
  39. (is (nil? (get-in updated ["A" 1 :display])))))
  40. (deftest downstream-cells-marked-dirty-on-edit
  41. ; When A1 is edited, B1 (which depends on A1) should also be marked dirty.
  42. (let [table (initial-table {"A" {1 {:value "5"}}
  43. "B" {1 {:value "=A1+1"}}})
  44. updated (data-utils/change-datum-value table "A" 1 "99")]
  45. (is (= true (get-in updated ["B" 1 :dirty])))))
  46. ;; --- Mirrors ::movement-leave-cell reference graph update ---
  47. (deftest formula-change-removes-old-back-reference
  48. ; B1 starts referencing A1. When we change B1 to reference C1,
  49. ; A1's :inbound should no longer include B1.
  50. (let [table (initial-table {"A" {1 {:value "5"}}
  51. "B" {1 {:value "=A1"}}})
  52. updated (-> table
  53. (data-utils/change-datum-value "B" 1 "=C1+1")
  54. (evaluation/reset-references "B" 1)
  55. (evaluation/evaluate-from-cell "B" 1))]
  56. (is (not (some #(= % {:col "B" :row 1})
  57. (get-in updated ["A" 1 :inbound]))))))
  58. (deftest formula-change-adds-new-back-reference
  59. ; Same scenario: after B1 is changed to reference C1, C1's :inbound should include B1.
  60. (let [table (initial-table {"A" {1 {:value "5"}}
  61. "B" {1 {:value "=A1"}}})
  62. updated (-> table
  63. (data-utils/change-datum-value "B" 1 "=C1+1")
  64. (evaluation/reset-references "B" 1)
  65. (evaluation/evaluate-from-cell "B" 1))]
  66. (is (some #(= % {:col "B" :row 1})
  67. (get-in updated ["C" 1 :inbound])))))
  68. ;; --- Deeply nested dependency propagation ---
  69. (deftest deep-dependency-chain-reevaluated
  70. ; A1 → B1 → C1 → D1; editing A1 should propagate all the way to D1.
  71. (let [table (initial-table {"A" {1 {:value "1"}}
  72. "B" {1 {:value "=A1+1"}}
  73. "C" {1 {:value "=B1+1"}}
  74. "D" {1 {:value "=C1+1"}}})
  75. updated (-> table
  76. (data-utils/change-datum-value "A" 1 "10")
  77. (evaluation/reset-references "A" 1)
  78. (evaluation/evaluate-from-cell "A" 1))]
  79. (is (= 13 (get-in updated ["D" 1 :display])))))