mirror of
https://github.com/penpot/penpot.git
synced 2026-10-31 15:00:32 +00:00
🔧 Allow machine precision geometry noise in composable undo checks
Keep snapshots exact and compare only shape geometry within an absolute 1e-10 tolerance. Reflow recomputed 0.01 as 0.010000000000005116 after undo. Opacity, touched groups and other authored fields still compare exactly. Fix the false positive F130 and document the narrow exception. The regression failed before the fix. Focused comparison and structural undo checks pass 2 tests with 9 assertions, including negative controls. AI-assisted-by: GPT-6
This commit is contained in:
parent
90138f14c6
commit
59d06a1066
@ -93,3 +93,12 @@ For geometry-sensitive tests, read `mem:common/geometry-invariants` before posit
|
||||
## Debugging
|
||||
|
||||
Use `mem:common/component-debugging-recipes` for shape-tree dumps, undo/change inspection, and temporary live instrumentation recipes.
|
||||
|
||||
## Composable undo comparison
|
||||
|
||||
`comp/undo-check` keeps normalized snapshots exact, including `:touched` and
|
||||
`:remote-synced`. Comparison permits only absolute floating noise below 1e-10
|
||||
inside shape geometry: x/y, width/height, rotation, points, selrect and transform
|
||||
matrices. Reflow can recompute 0.01 as 0.010000000000005116 after undo (F130).
|
||||
This tolerance does not apply to opacity, other authored attrs or touched groups.
|
||||
The regression rejects a 1e-5 height change and a 5e-15 opacity change.
|
||||
|
||||
@ -34,6 +34,9 @@
|
||||
a pinned phase that does not fail fails the case, so the mark goes
|
||||
only with the fix."
|
||||
(:require
|
||||
[app.common.geom.matrix :as gmt]
|
||||
[app.common.geom.point :as gpt]
|
||||
[app.common.math :as mth]
|
||||
[app.common.test-helpers.composable.core :as tm]
|
||||
[app.common.test-helpers.ids-map :as thi]
|
||||
[app.common.test-helpers.normalize :as thn]
|
||||
@ -166,13 +169,43 @@
|
||||
(into (entities-signature :component (:components ed) (:components ad)
|
||||
#{} (constantly nil)))))))
|
||||
|
||||
(defn- same-state?
|
||||
"Reflow can recompute geometry at machine precision after undo. Keep
|
||||
snapshots exact, allowing only absolute geometry noise below 1e-10."
|
||||
([expected actual] (same-state? expected actual false))
|
||||
([expected actual geometry?]
|
||||
(or (= expected actual)
|
||||
(cond
|
||||
(and geometry? (number? expected) (number? actual))
|
||||
(mth/close? expected actual 1e-10)
|
||||
|
||||
(and geometry? (gmt/matrix? expected) (gmt/matrix? actual))
|
||||
(every? #(same-state? (get expected %) (get actual %) true) [:a :b :c :d :e :f])
|
||||
|
||||
(and geometry? (gpt/point? expected) (gpt/point? actual))
|
||||
(every? #(same-state? (get expected %) (get actual %) true) [:x :y])
|
||||
|
||||
(and (map? expected) (map? actual) (= (set (keys expected)) (set (keys actual))))
|
||||
(every? (fn [key]
|
||||
(same-state? (get expected key) (get actual key)
|
||||
(or geometry?
|
||||
(and (contains? expected :parent-id)
|
||||
(contains? #{:x :y :width :height :rotation :points
|
||||
:selrect :transform :transform-inverse} key)))))
|
||||
(keys expected))
|
||||
|
||||
(and (sequential? expected) (sequential? actual) (= (count expected) (count actual)))
|
||||
(every? true? (map #(same-state? %1 %2 geometry?) expected actual))
|
||||
|
||||
:else false))))
|
||||
|
||||
(defn compare-states
|
||||
"Record a failure of `phase` at `step` unless the normalized files
|
||||
`expected` and `actual` are equal. Phases: `:undo`, `:redo`,
|
||||
`:no-undo-entry` (a step that added no entry changed the file),
|
||||
`:variant-undo` and `:variant-redo`."
|
||||
[situation step phase expected actual]
|
||||
(if (= expected actual)
|
||||
(if (same-state? expected actual)
|
||||
situation
|
||||
(add-failure situation {:phase phase
|
||||
:step step
|
||||
|
||||
@ -21,6 +21,20 @@
|
||||
|
||||
(t/use-fixtures :each thi/test-fixture)
|
||||
|
||||
(t/deftest undo-comparison-allows-only-negligible-geometry-noise
|
||||
(let [situation (setup/simple-component-with-labeled-copy)
|
||||
file (tm/file situation)
|
||||
id (:id (tm/role-shape situation :copy-instance))
|
||||
path [:data :pages-index (:id (thf/current-page file)) :objects id]
|
||||
before (-> (uc/snapshot file)
|
||||
(assoc-in (conj path :height) 0.01)
|
||||
(assoc-in (conj path :opacity) 0.4))
|
||||
failures #(uc/failures (uc/compare-states {} nil :undo before %))]
|
||||
(t/is (empty? (failures (assoc-in before (conj path :height) 0.010000000000005116))))
|
||||
(t/is (seq (failures (assoc-in before (conj path :height) 0.01001))))
|
||||
(t/is (seq (failures (assoc-in before (conj path :opacity) (+ 0.4 5e-15)))))
|
||||
(t/is (seq (failures (assoc-in before (conj path :touched) #{:geometry-group}))))))
|
||||
|
||||
(t/deftest undo-main-child-structure-restores-remote-sync-state
|
||||
(let [initial (setup/nested-component-with-copy)
|
||||
updated (-> initial
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user