mirror of
https://github.com/penpot/penpot.git
synced 2026-10-29 05:50:35 +00:00
🐛 Compare layout source placement in backend test parity
A hidden copy follows its visible main child's layout-derived position. Only the frontend runs real reflow, so comparing that copy's raw position with the pure runner reported a false mismatch (F227). Compare untouched images against expected source placement for backend parity. Keep strict same-run comparisons and the numeric tolerances. Independent controls still reject image drift and authored geometry edits. The source/image translation control fails before the correction. Reduce the generated history from 41 operations to five and replay it in common and the async frontend, including exact undo and redo. Validation: 30 focused tests / 177 assertions; full common JVM/JS, frontend, backend and exporter suites; lint and formatting. All six generated properties pass 40 programs at seed 712. Golden data and production behavior are unchanged. AI-assisted-by: GPT-6
This commit is contained in:
parent
5fb7f55c95
commit
c79b09af24
@ -71,7 +71,7 @@
|
||||
(gpt/point (- half-width) half-height)])
|
||||
{::matrix matrix ::bounds bounds ::derived derived})))
|
||||
|
||||
(declare free-head-reference-origins)
|
||||
(declare get-derived-reference-origins)
|
||||
|
||||
(defn design-snapshot
|
||||
"Design inputs without derived placement, dimensions and bookkeeping.
|
||||
@ -80,7 +80,7 @@
|
||||
([situation {:keys [free-head-placement relative-free-head-placement]
|
||||
:or {free-head-placement true}}]
|
||||
(let [origins (when (and free-head-placement relative-free-head-placement)
|
||||
(free-head-reference-origins situation))]
|
||||
(get-derived-reference-origins situation))]
|
||||
(walk/postwalk
|
||||
(fn [value]
|
||||
(if (and (map? value) (contains? value :parent-id))
|
||||
@ -241,25 +241,29 @@
|
||||
(with-derived-geometry objects shape placed))))]
|
||||
(expected shape #{})))))
|
||||
|
||||
(defn- free-head-reference-origins
|
||||
"Backend parity permits a free head's placement to differ after reflow.
|
||||
Its untouched images compare offsets from their expected source frame,
|
||||
so inherited placement can differ while a drifted image still fails.
|
||||
(defn- get-derived-reference-origins
|
||||
"Backend parity permits source placement derived by reflow to differ.
|
||||
Untouched images compare offsets from their expected source frame,
|
||||
including hidden/absolute children of visible layout sources. Inherited
|
||||
placement can differ while a drifted image still fails.
|
||||
Strict same-run sync comparison never uses these origins."
|
||||
[situation]
|
||||
(into {}
|
||||
(for [[file-id file] (cf/all-files situation)
|
||||
page (vals (get-in file [:data :pages-index]))
|
||||
[id shape] (:objects page)
|
||||
:when (and (:component-id shape) (:shape-ref shape)
|
||||
(not (ctk/touched-group? shape :geometry-group))
|
||||
(> (count (ctn/get-parent-copy-heads (:objects page) shape)) 1))
|
||||
:when (and (:shape-ref shape)
|
||||
(not (ctk/touched-group? shape :geometry-group)))
|
||||
:let [libraries (cf/reference-libraries situation file-id id)
|
||||
source (ctf/find-ref-shape file page libraries shape
|
||||
:include-deleted? true :with-context? true)
|
||||
source-objects (get-in (meta source) [:container :objects])]
|
||||
:when (and (:component-id source) (:shape-ref source)
|
||||
(= 1 (count (ctn/get-parent-copy-heads source-objects source))))
|
||||
:when (or (and (:component-id shape)
|
||||
(> (count (ctn/get-parent-copy-heads (:objects page) shape)) 1)
|
||||
(:component-id source) (:shape-ref source)
|
||||
(= 1 (count (ctn/get-parent-copy-heads source-objects source))))
|
||||
(and (not (contains? (derived-attributes (:objects page) shape) :x))
|
||||
(contains? (derived-attributes source-objects source) :x)))
|
||||
:let [expected (expected-geometry file page libraries shape
|
||||
#(cf/reference-libraries situation file-id (:id %)))]]
|
||||
[id (gpt/point (:x expected) (:y expected))])))
|
||||
|
||||
@ -29,6 +29,11 @@
|
||||
|
||||
(t/use-fixtures :each thi/test-fixture)
|
||||
|
||||
(t/deftest nesting-a-copy-alongside-a-hidden-layout-child-stays-settled
|
||||
(t/is (map? (cg/run {:world [3 0 {:text true :text-styles true :layout :flex}]
|
||||
:ops [[:delete 9 9 1 2] [:text 5 9 1 3] [:style 3 1 1]
|
||||
[:restore 5 8 1 6] [:nest 5 6 0 2] [:undo] [:redo]]}))))
|
||||
|
||||
(t/deftest swapping-a-copy-with-authored-position-constraints-stays-settled
|
||||
(t/is (map? (cg/run {:world [2 1 {:library true :variants true :rich-variants true
|
||||
:text true :text-styles true :layout :flex :layout-copies true}]
|
||||
|
||||
@ -95,6 +95,36 @@
|
||||
(t/is (not (oracle/equivalent? (oracle/canonical initial strict) (oracle/canonical inherited strict))))
|
||||
(t/is (not (oracle/equivalent? (oracle/canonical initial) (oracle/canonical inherited))))))
|
||||
|
||||
(t/deftest backend-parity-follows-layout-sources-for-hidden-copies-without-hiding-drift
|
||||
(let [base (setup/simple-component-with-labeled-copy)
|
||||
file (-> (tm/file base)
|
||||
(ths/update-shape :main-root :layout :flex)
|
||||
(ths/update-shape :copy-root :layout :flex)
|
||||
(ths/update-shape :copy-root :touched nil))
|
||||
initial (-> (tm/with-file base file)
|
||||
(r/step (n/change-property :copy-child :hidden true)) r/settle)
|
||||
move (fn [situation labels]
|
||||
(let [file (tm/file situation)
|
||||
page (thf/current-page file)]
|
||||
(tm/with-file situation
|
||||
(thf/apply-changes file
|
||||
(cls/generate-update-shapes
|
||||
(pcb/empty-changes nil (:id page))
|
||||
(map thi/id labels)
|
||||
#(gsh/move % (gpt/point 30 0))
|
||||
(:objects page) {:ignore-touched true})))))
|
||||
inherited (move initial [:main-child :copy-child])
|
||||
drifted (move initial [:copy-child])
|
||||
authored (tm/with-file initial (ths/update-shape (tm/file initial) :copy-child :touched
|
||||
#{:visibility-group :geometry-group}))
|
||||
parity {:relative-free-head-placement true}
|
||||
strict {:free-head-placement false}]
|
||||
(t/is (oracle/equivalent? (oracle/canonical initial parity) (oracle/canonical inherited parity)))
|
||||
(t/is (not (oracle/equivalent? (oracle/canonical initial parity) (oracle/canonical drifted parity))))
|
||||
(t/is (not (oracle/equivalent? (oracle/canonical initial strict) (oracle/canonical inherited strict))))
|
||||
(t/is (not (oracle/equivalent? (oracle/canonical authored parity)
|
||||
(oracle/canonical (move authored [:main-child :copy-child]) parity))))))
|
||||
|
||||
(t/deftest oracle-rejects-unmarked-differences-in-all-scalar-groups
|
||||
(let [situation (setup/simple-component-with-labeled-copy)
|
||||
file (tm/file situation)]
|
||||
|
||||
@ -88,6 +88,11 @@
|
||||
rx/timer mock/timer-mock}
|
||||
(await (replay-program program)))))
|
||||
|
||||
(t/deftest ^:async nesting-a-copy-alongside-a-hidden-layout-child-stays-settled
|
||||
(await (replay {:world [3 0 {:text true :text-styles true :layout :flex}]
|
||||
:ops [[:delete 9 9 1 2] [:text 5 9 1 3] [:style 3 1 1]
|
||||
[:restore 5 8 1 6] [:nest 5 6 0 2] [:undo] [:redo]]})))
|
||||
|
||||
(t/deftest ^:async swapping-a-copy-with-authored-position-constraints-stays-settled
|
||||
(await (replay {:world [2 1 {:library true :variants true :rich-variants true
|
||||
:text true :text-styles true :layout :flex :layout-copies true}]
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user