mirror of
https://github.com/penpot/penpot.git
synced 2026-04-25 11:18:36 +00:00
* 🔧 Validate only after propagation in tests * 💄 Enhance some component sync traces * 🔧 Add fake uuid generator for debugging * 🐛 Remove old feature of advancing references when reset changes Since long time ago, we only allow to reset changes in the top copy shape. In this case the near and the remote shapes are the same, so the advance-ref has no effect. * 🐛 Fix some bugs and add validations, repair and migrations Also added several utilities to debug and to create scripts that processes files * 🐛 Fix misplaced parenthesis passing propagate-fn to wrong function The :propagate-fn keyword argument was incorrectly placed inside the ths/get-shape call instead of being passed to tho/reset-overrides. This caused reset-overrides to never propagate component changes, making the test not validate what it intended. * 🐛 Accept and forward :include-deleted? in find-near-match Callers were passing :include-deleted? true but the parameter was not in the destructuring, so it was silently ignored and the function always hardcoded true. This made the API misleading and would cause incorrect behavior if called with :include-deleted? false. * 💄 Use set/union alias instead of fully-qualified clojure.set/union The namespace already requires [clojure.set :as set], so use the alias for consistency. * 🐛 Add tests for reset-overrides with and without propagate-fn Add two focused tests to comp_reset_test to cover the propagate-fn path in reset-overrides: - test-reset-with-propagation-updates-copies: verifies that resetting an override on a nested copy inside a main and supplying propagate-fn causes the canonical color to appear in all downstream copies. - test-reset-without-propagation-does-not-update-copies: regression guard for the misplaced-parenthesis bug; confirms that omitting propagate-fn leaves copies with the overridden value because the component sync never runs. --------- Co-authored-by: Andrey Antukh <niwi@niwi.nz>
421 lines
19 KiB
Clojure
421 lines
19 KiB
Clojure
;; This Source Code Form is subject to the terms of the Mozilla Public
|
||
;; License, v. 2.0. If a copy of the MPL was not distributed with this
|
||
;; file, You can obtain one at http://mozilla.org/MPL/2.0/.
|
||
;;
|
||
;; Copyright (c) KALEIDOS INC
|
||
|
||
(ns common-tests.logic.comp-reset-test
|
||
(:require
|
||
[app.common.files.changes-builder :as pcb]
|
||
[app.common.logic.libraries :as cll]
|
||
[app.common.logic.shapes :as cls]
|
||
[app.common.test-helpers.components :as thc]
|
||
[app.common.test-helpers.compositions :as tho]
|
||
[app.common.test-helpers.files :as thf]
|
||
[app.common.test-helpers.ids-map :as thi]
|
||
[app.common.test-helpers.shapes :as ths]
|
||
[clojure.test :as t]))
|
||
|
||
(t/use-fixtures :each thi/test-fixture)
|
||
|
||
(t/deftest test-reset-after-changing-attribute
|
||
(let [;; ==== Setup
|
||
file (-> (thf/sample-file :file1)
|
||
(tho/add-simple-component-with-copy :component1
|
||
:main-root
|
||
:main-child
|
||
:copy-root
|
||
:main-child-params {:fills (ths/sample-fills-color
|
||
:fill-color "#abcdef")}
|
||
:copy-root-params {:children-labels [:copy-child]}))
|
||
page (thf/current-page file)
|
||
copy-root (ths/get-shape file :copy-root)
|
||
copy-child (ths/get-shape file :copy-child)
|
||
|
||
;; ==== Action
|
||
changes (cls/generate-update-shapes (pcb/empty-changes nil (:id page))
|
||
#{(:id copy-child)}
|
||
(fn [shape]
|
||
(assoc shape :fills (ths/sample-fills-color :fill-color "#fabada")))
|
||
(:objects page)
|
||
{})
|
||
|
||
file-mdf (thf/apply-changes file changes)
|
||
page-mdf (thf/current-page file-mdf)
|
||
|
||
changes (cll/generate-reset-component (pcb/empty-changes)
|
||
file-mdf
|
||
{(:id file-mdf) file-mdf}
|
||
page-mdf
|
||
(:id copy-root))
|
||
|
||
file' (thf/apply-changes file changes)
|
||
|
||
;; ==== Get
|
||
copy-root' (ths/get-shape file' :copy-root)
|
||
copy-child' (ths/get-shape file' :copy-child)
|
||
fills' (:fills copy-child')
|
||
fill' (first fills')]
|
||
|
||
;; ==== Check
|
||
(t/is (some? copy-root'))
|
||
(t/is (some? copy-child'))
|
||
(t/is (= (count fills') 1))
|
||
(t/is (= (:fill-color fill') "#abcdef"))
|
||
(t/is (= (:fill-opacity fill') 1))
|
||
(t/is (= (:touched copy-root') nil))
|
||
(t/is (= (:touched copy-child') nil))))
|
||
|
||
(t/deftest test-reset-from-library
|
||
(let [;; ==== Setup
|
||
library (-> (thf/sample-file :library :is-shared true)
|
||
(tho/add-simple-component :component1 :main-root :main-child
|
||
:child-params {:fills (ths/sample-fills-color
|
||
:fill-color "#abcdef")}))
|
||
|
||
file (-> (thf/sample-file :file)
|
||
(thc/instantiate-component :component1 :copy-root
|
||
:library library
|
||
:children-labels [:copy-child]))
|
||
|
||
page (thf/current-page file)
|
||
copy-root (ths/get-shape file :copy-root)
|
||
copy-child (ths/get-shape file :copy-child)
|
||
|
||
;; ==== Action
|
||
changes (cls/generate-update-shapes (pcb/empty-changes nil (:id page))
|
||
#{(:id copy-child)}
|
||
(fn [shape]
|
||
(assoc shape :fills (ths/sample-fills-color :fill-color "#fabada")))
|
||
(:objects page)
|
||
{})
|
||
|
||
file-mdf (thf/apply-changes file changes)
|
||
page-mdf (thf/current-page file-mdf)
|
||
|
||
changes (cll/generate-reset-component (pcb/empty-changes)
|
||
file-mdf
|
||
{(:id file-mdf) file-mdf
|
||
(:id library) library}
|
||
page-mdf
|
||
(:id copy-root))
|
||
|
||
file' (thf/apply-changes file changes)
|
||
|
||
;; ==== Get
|
||
copy-root' (ths/get-shape file' :copy-root)
|
||
copy-child' (ths/get-shape file' :copy-child)
|
||
fills' (:fills copy-child')
|
||
fill' (first fills')]
|
||
|
||
;; ==== Check
|
||
(t/is (some? copy-root'))
|
||
(t/is (some? copy-child'))
|
||
(t/is (= (count fills') 1))
|
||
(t/is (= (:fill-color fill') "#abcdef"))
|
||
(t/is (= (:fill-opacity fill') 1))
|
||
(t/is (= (:touched copy-root') nil))
|
||
(t/is (= (:touched copy-child') nil))))
|
||
|
||
(t/deftest test-reset-after-adding-shape
|
||
(let [;; ==== Setup
|
||
file (-> (thf/sample-file :file1)
|
||
(tho/add-simple-component-with-copy :component1
|
||
:main-root
|
||
:main-child
|
||
:copy-root
|
||
:copy-root-params {:children-labels [:copy-child]})
|
||
(ths/add-sample-shape :free-shape))
|
||
|
||
page (thf/current-page file)
|
||
copy-root (ths/get-shape file :copy-root)
|
||
|
||
;; ==== Action
|
||
|
||
;; IMPORTANT: as modifying copies structure is now forbidden, this action
|
||
;; will not have any effect, and so the parent shape won't also be touched.
|
||
changes (cls/generate-relocate (-> (pcb/empty-changes nil)
|
||
(pcb/with-page-id (:id page))
|
||
(pcb/with-objects (:objects page)))
|
||
(thi/id :copy-root) ; parent-id
|
||
0 ; to-index
|
||
#{(thi/id :free-shape)}) ; ids
|
||
|
||
|
||
file-mdf (thf/apply-changes file changes)
|
||
page-mdf (thf/current-page file-mdf)
|
||
|
||
changes (cll/generate-reset-component (pcb/empty-changes)
|
||
file-mdf
|
||
{(:id file-mdf) file-mdf}
|
||
page-mdf
|
||
(:id copy-root))
|
||
|
||
file' (thf/apply-changes file changes)
|
||
|
||
;; ==== Get
|
||
copy-root' (ths/get-shape file' :copy-root)
|
||
copy-child' (ths/get-shape file' :copy-child)]
|
||
|
||
;; ==== Check
|
||
(t/is (some? copy-root'))
|
||
(t/is (some? copy-child'))
|
||
(t/is (= (:touched copy-root') nil))
|
||
(t/is (= (:touched copy-child') nil))))
|
||
|
||
(t/deftest test-reset-after-deleting-shape
|
||
(let [;; ==== Setup
|
||
file (-> (thf/sample-file :file1)
|
||
(tho/add-simple-component-with-copy :component1
|
||
:main-root
|
||
:main-child
|
||
:copy-root
|
||
:copy-root-params {:children-labels [:copy-child]}))
|
||
|
||
page (thf/current-page file)
|
||
copy-root (ths/get-shape file :copy-root)
|
||
copy-child (ths/get-shape file :copy-child)
|
||
|
||
;; ==== Action
|
||
|
||
;; IMPORTANT: as modifying copies structure is now forbidden, this action will not
|
||
;; delete the child shape, but hide it (thus setting the visibility group).
|
||
[_all-parents changes]
|
||
(cls/generate-delete-shapes (pcb/empty-changes)
|
||
file
|
||
page
|
||
(:objects page)
|
||
#{(:id copy-child)}
|
||
{:components-v2 true})
|
||
|
||
file-mdf (thf/apply-changes file changes)
|
||
page-mdf (thf/current-page file-mdf)
|
||
|
||
changes (cll/generate-reset-component (pcb/empty-changes)
|
||
file-mdf
|
||
{(:id file-mdf) file-mdf}
|
||
page-mdf
|
||
(:id copy-root))
|
||
|
||
file' (thf/apply-changes file changes)
|
||
|
||
;; ==== Get
|
||
copy-root' (ths/get-shape file' :copy-root)
|
||
copy-child' (ths/get-shape file' :copy-child)]
|
||
|
||
;; ==== Check
|
||
(t/is (some? copy-root'))
|
||
(t/is (some? copy-child'))
|
||
(t/is (= (:touched copy-root') nil))
|
||
(t/is (= (:touched copy-child') nil))))
|
||
|
||
(t/deftest test-reset-after-moving-shape
|
||
(let [;; ==== Setup
|
||
file (-> (thf/sample-file :file1)
|
||
(tho/add-component-with-many-children-and-copy :component1
|
||
:main-root
|
||
[:main-child1 :main-child2 :main-child3]
|
||
:copy-root
|
||
:copy-root-params {:children-labels [:copy-child]})
|
||
(ths/add-sample-shape :free-shape))
|
||
|
||
page (thf/current-page file)
|
||
copy-root (ths/get-shape file :copy-root)
|
||
copy-child1 (ths/get-shape file :copy-child)
|
||
|
||
;; ==== Action
|
||
|
||
;; IMPORTANT: as modifying copies structure is now forbidden, this action
|
||
;; will not have any effect, and so the parent shape won't also be touched.
|
||
changes (cls/generate-relocate (-> (pcb/empty-changes nil)
|
||
(pcb/with-page-id (:id page))
|
||
(pcb/with-objects (:objects page)))
|
||
(thi/id :copy-root) ; parent-id
|
||
2 ; to-index
|
||
#{(:id copy-child1)}) ; ids
|
||
|
||
|
||
file-mdf (thf/apply-changes file changes)
|
||
page-mdf (thf/current-page file-mdf)
|
||
|
||
changes (cll/generate-reset-component (pcb/empty-changes)
|
||
file-mdf
|
||
{(:id file-mdf) file-mdf}
|
||
page-mdf
|
||
(:id copy-root))
|
||
|
||
file' (thf/apply-changes file changes)
|
||
|
||
;; ==== Get
|
||
copy-root' (ths/get-shape file' :copy-root)
|
||
copy-child' (ths/get-shape file' :copy-child)]
|
||
|
||
;; ==== Check
|
||
(t/is (some? copy-root'))
|
||
(t/is (some? copy-child'))
|
||
(t/is (= (:touched copy-root') nil))
|
||
(t/is (= (:touched copy-child') nil))))
|
||
|
||
(t/deftest test-reset-after-changing-upper
|
||
(let [;; ==== Setup
|
||
file (-> (thf/sample-file :file1)
|
||
(tho/add-nested-component-with-copy :component1
|
||
:main1-root
|
||
:main1-child
|
||
:component2
|
||
:main2-root
|
||
:main2-nested-head
|
||
:copy2-root
|
||
:main2-root-params {:fills (ths/sample-fills-color
|
||
:fill-color "#abcdef")}))
|
||
page (thf/current-page file)
|
||
copy2-root (ths/get-shape file :copy2-root)
|
||
|
||
;; ==== Action
|
||
changes (cls/generate-update-shapes (pcb/empty-changes nil (:id page))
|
||
#{(:id copy2-root)}
|
||
(fn [shape]
|
||
(assoc shape :fills (ths/sample-fills-color :fill-color "#fabada")))
|
||
(:objects page)
|
||
{})
|
||
|
||
file-mdf (thf/apply-changes file changes)
|
||
page-mdf (thf/current-page file-mdf)
|
||
|
||
changes (cll/generate-reset-component (pcb/empty-changes)
|
||
file-mdf
|
||
{(:id file-mdf) file-mdf}
|
||
page-mdf
|
||
(:id copy2-root))
|
||
|
||
file' (thf/apply-changes file changes)
|
||
|
||
;; ==== Get
|
||
copy2-root' (ths/get-shape file' :copy2-root)
|
||
fills' (:fills copy2-root')
|
||
fill' (first fills')]
|
||
|
||
;; ==== Check
|
||
(t/is (some? copy2-root'))
|
||
(t/is (= (count fills') 1))
|
||
(t/is (= (:fill-color fill') "#abcdef"))
|
||
(t/is (= (:fill-opacity fill') 1))
|
||
(t/is (= (:touched copy2-root') nil))))
|
||
|
||
(t/deftest test-reset-after-changing-lower
|
||
(let [;; ==== Setup
|
||
file (-> (thf/sample-file :file1)
|
||
(tho/add-nested-component-with-copy :component1
|
||
:main1-root
|
||
:main1-child
|
||
:component2
|
||
:main2-root
|
||
:main2-nested-head
|
||
:copy2-root
|
||
:copy2-root-params {:children-labels [:copy2-child]}))
|
||
page (thf/current-page file)
|
||
copy2-root (ths/get-shape file :copy2-root)
|
||
copy2-child (ths/get-shape file :copy2-child)
|
||
|
||
;; ==== Action
|
||
changes (cls/generate-update-shapes (pcb/empty-changes nil (:id page))
|
||
#{(:id copy2-child)}
|
||
(fn [shape]
|
||
(assoc shape :fills (ths/sample-fills-color :fill-color "#fabada")))
|
||
(:objects page)
|
||
{})
|
||
|
||
file-mdf (thf/apply-changes file changes)
|
||
page-mdf (thf/current-page file-mdf)
|
||
|
||
changes (cll/generate-reset-component (pcb/empty-changes)
|
||
file-mdf
|
||
{(:id file-mdf) file-mdf}
|
||
page-mdf
|
||
(:id copy2-root))
|
||
|
||
file' (thf/apply-changes file changes)
|
||
|
||
;; ==== Get
|
||
copy2-root' (ths/get-shape file' :copy2-root)
|
||
copy2-child' (ths/get-shape file' :copy2-child)
|
||
fills' (:fills copy2-child')
|
||
fill' (first fills')]
|
||
|
||
;; ==== Check
|
||
(t/is (some? copy2-root'))
|
||
(t/is (some? copy2-child'))
|
||
(t/is (= (count fills') 1))
|
||
(t/is (= (:fill-color fill') "#FFFFFF"))
|
||
(t/is (= (:fill-opacity fill') 1))
|
||
(t/is (= (:touched copy2-root') nil))
|
||
(t/is (= (:touched copy2-child') nil))))
|
||
|
||
(t/deftest test-reset-with-propagation-updates-copies
|
||
;; When a nested copy inside a main component has an override and we
|
||
;; reset it passing a propagate-fn, the reset must be propagated to
|
||
;; all copies of that component so they reflect the canonical color.
|
||
(let [;; ==== Setup
|
||
file
|
||
(-> (thf/sample-file :file1)
|
||
;; component1: main1-root / main1-child (fill "#aabbcc")
|
||
;; component2: main2-root contains nested-head (instance of component1)
|
||
;; copy2-root: copy of component2
|
||
(tho/add-nested-component-with-copy
|
||
:component1 :main1-root :main1-child
|
||
:component2 :main2-root :nested-head
|
||
:copy2-root
|
||
:main1-child-params {:fills (ths/sample-fills-color :fill-color "#aabbcc")}
|
||
:copy2-root-params {:children-labels [:copy2-nested-head]}))
|
||
|
||
propagate-fn (fn [f]
|
||
(-> f
|
||
(tho/propagate-component-changes :component1)
|
||
(tho/propagate-component-changes :component2)))
|
||
|
||
;; ==== Action – override the nested-head color, then reset it with propagation
|
||
file'
|
||
(-> file
|
||
(tho/update-bottom-color :nested-head "#fabada" :propagate-fn propagate-fn)
|
||
(tho/reset-overrides (ths/get-shape file :nested-head) :propagate-fn propagate-fn))
|
||
|
||
;; ==== Get
|
||
copy2-bottom-color (tho/bottom-fill-color file' :copy2-root)]
|
||
|
||
;; ==== Check
|
||
;; After reset + propagation the copy should mirror the canonical color
|
||
(t/is (= copy2-bottom-color "#aabbcc"))))
|
||
|
||
(t/deftest test-reset-without-propagation-does-not-update-copies
|
||
;; This is the regression test for the misplaced-parenthesis bug: when
|
||
;; propagate-fn is NOT passed to reset-overrides the copies of the component
|
||
;; must still hold the overridden value because the component sync never ran.
|
||
(let [;; ==== Setup
|
||
file
|
||
(-> (thf/sample-file :file1)
|
||
(tho/add-nested-component-with-copy
|
||
:component1 :main1-root :main1-child
|
||
:component2 :main2-root :nested-head
|
||
:copy2-root
|
||
:main1-child-params {:fills (ths/sample-fills-color :fill-color "#aabbcc")}
|
||
:copy2-root-params {:children-labels [:copy2-nested-head]}))
|
||
|
||
propagate-fn (fn [f]
|
||
(-> f
|
||
(tho/propagate-component-changes :component1)
|
||
(tho/propagate-component-changes :component2)))
|
||
|
||
;; ==== Action – override the nested-head color, then reset WITHOUT propagation
|
||
file'
|
||
(-> file
|
||
(tho/update-bottom-color :nested-head "#fabada" :propagate-fn propagate-fn)
|
||
;; Reset without propagate-fn: the component definition is updated but
|
||
;; the change is never pushed to the copy.
|
||
(tho/reset-overrides (ths/get-shape file :nested-head)))
|
||
|
||
;; ==== Get
|
||
copy2-bottom-color (tho/bottom-fill-color file' :copy2-root)]
|
||
|
||
;; ==== Check
|
||
;; Without propagation the copy still reflects the overridden color
|
||
(t/is (= copy2-bottom-color "#fabada")))) |