events_test.cljs 4.6 KB

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