mirror of
https://github.com/penpot/penpot.git
synced 2026-06-01 13:10:21 +00:00
501 lines
23 KiB
Clojure
501 lines
23 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]
|
||
[app.common.types.container :as ctn]
|
||
[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-nested-component-from-different-library-preserves-children
|
||
;; Regression test for the cross-library find-main-container bug:
|
||
;; avatar-img lives in library1, avatar (which nests avatar-img) lives in library2,
|
||
;; and card (which nests avatar) lives in the current file.
|
||
;; Resetting overrides on avatar used to delete avatar-img because find-main-container
|
||
;; only searched within the single library argument (library2) and could not find
|
||
;; the avatar-img component which belongs to library1.
|
||
(let [;; ==== Setup
|
||
;; library1: avatar-img-component (simple component with a child rect)
|
||
library1
|
||
(-> (thf/sample-file :library1 :is-shared true)
|
||
(tho/add-simple-component :avatar-img-component
|
||
:avatar-img-main
|
||
:avatar-img-child
|
||
:child-params {:fills (ths/sample-fills-color :fill-color "#abcdef")}))
|
||
|
||
;; library2: avatar-component (nests avatar-img from library1)
|
||
library2
|
||
(-> (thf/sample-file :library2 :is-shared true)
|
||
(tho/add-frame :avatar-main)
|
||
(thc/instantiate-component :avatar-img-component
|
||
:avatar-img-nested-head
|
||
:library library1
|
||
:parent-label :avatar-main
|
||
:children-labels [:avatar-img-nested-child])
|
||
(thc/make-component :avatar-component :avatar-main))
|
||
|
||
;; file: card-component (nests avatar from library2) + a copy of card
|
||
file
|
||
(-> (thf/sample-file :file)
|
||
(tho/add-frame :card-main)
|
||
(thc/instantiate-component :avatar-component
|
||
:avatar-in-card-main
|
||
:library library2
|
||
:parent-label :card-main
|
||
:children-labels [:avatar-img-in-card-main])
|
||
(thc/make-component :card-component :card-main)
|
||
(thc/instantiate-component :card-component
|
||
:card-copy
|
||
:children-labels [:avatar-in-card-copy]))
|
||
|
||
page (thf/current-page file)
|
||
avatar-in-card-copy (ths/get-shape file :avatar-in-card-copy)
|
||
|
||
;; ==== Action: override a fill on the avatar copy, then reset
|
||
changes
|
||
(cls/generate-update-shapes (pcb/empty-changes nil (:id page))
|
||
#{(:id avatar-in-card-copy)}
|
||
(fn [shape]
|
||
(assoc shape :fills (ths/sample-fills-color :fill-color "#fabada")))
|
||
(:objects page)
|
||
{})
|
||
|
||
file-mdf (thf/apply-changes file changes)
|
||
container-mdf (ctn/get-container (:data file-mdf) :page (:id page))
|
||
|
||
changes
|
||
(cll/generate-reset-component (pcb/empty-changes)
|
||
file-mdf
|
||
{(:id file-mdf) file-mdf
|
||
(:id library1) library1
|
||
(:id library2) library2}
|
||
container-mdf
|
||
(:id avatar-in-card-copy))
|
||
|
||
file' (thf/apply-changes file-mdf changes :validate? false)
|
||
|
||
;; ==== Get
|
||
avatar-in-card-copy' (ths/get-shape file' :avatar-in-card-copy)
|
||
;; avatar-img nested head must still exist inside avatar-in-card-copy after reset
|
||
avatar-img-copy-id (first (:shapes avatar-in-card-copy'))
|
||
avatar-img-copy' (when avatar-img-copy-id
|
||
(ths/get-shape-by-id file' avatar-img-copy-id))]
|
||
|
||
;; ==== Check
|
||
(t/is (some? avatar-in-card-copy') "avatar copy must still exist after reset")
|
||
(t/is (some? avatar-img-copy-id) "avatar-img copy must not be deleted from avatar's children")
|
||
(t/is (some? avatar-img-copy') "avatar-img copy shape must be present in objects")))
|
||
|
||
(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")))) |