mirror of
https://github.com/penpot/penpot.git
synced 2026-11-09 19:41:34 +00:00
Resetting a selected nested copy must compose its parent placement even when that parent remains a component copy. Red regression reproduced; reset suite passes 16 tests / 63 assertions. Benchmark vs Phase 1e: main 10/100/1000 0.51/0.64/0.65; levels 0.50; chain 0.57; library 0.53; many shapes 0.47; switch 0.59. No flags. AI-assisted-by: GPT-6
653 lines
31 KiB
Clojure
653 lines
31 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 SUBSIDIARY SL
|
||
|
||
(ns common-tests.logic.comp-reset-test
|
||
(:require
|
||
[app.common.files.changes-builder :as pcb]
|
||
[app.common.geom.shapes :as gsh]
|
||
[app.common.logic.libraries :as cll]
|
||
[app.common.logic.shapes :as cls]
|
||
[app.common.test-helpers.components :as thc]
|
||
[app.common.test-helpers.composable.comp.nodes :as n]
|
||
[app.common.test-helpers.composable.comp.runner :as r]
|
||
[app.common.test-helpers.composable.comp.setups :as setup]
|
||
[app.common.test-helpers.composable.core :as tm]
|
||
[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.normalize :as thn]
|
||
[app.common.test-helpers.shapes :as ths]
|
||
[app.common.types.container :as ctn]
|
||
[app.common.types.modifiers :as ctm]
|
||
[app.common.types.shape.layout :as ctl]
|
||
[clojure.test :as t]))
|
||
|
||
(t/use-fixtures :each thi/test-fixture)
|
||
|
||
(t/deftest reset-a-nested-grid-keeps-the-near-mains-manual-cell
|
||
(let [file (-> (thf/sample-file :file1)
|
||
(tho/add-grid-frame :inner-root)
|
||
(tho/add-rect :inner-child :parent-label :inner-root)
|
||
(tho/place-in-grid-columns :inner-root 2 {1 :inner-child})
|
||
(thc/make-component :inner :inner-root)
|
||
(tho/add-frame :outer-root)
|
||
(thc/instantiate-component :inner :nested :parent-label :outer-root
|
||
:children-labels [:nested-child])
|
||
(thc/make-component :outer :outer-root)
|
||
(thc/instantiate-component :outer :copy
|
||
:children-labels [:copy-nested :copy-child]))
|
||
initial (tm/make-situation file)
|
||
moved (-> initial (r/step (n/move-to-cell :nested :nested-child 1 2)) r/settle)
|
||
reset (-> moved (r/step (n/reset :copy-nested)) r/settle)
|
||
column (fn [s]
|
||
(let [head (tm/resolve-shape s :copy-nested)]
|
||
(:column (ctl/get-cell-by-shape-id head (first (:shapes head))))))]
|
||
(t/is (= 2 (column moved)))
|
||
(t/is (= 2 (column reset)))
|
||
(t/is (nil? (:touched (tm/resolve-shape reset :copy-nested))))))
|
||
|
||
(t/deftest reset-a-nested-copy-through-a-deleted-outer-main
|
||
(let [initial (setup/nested-component-with-copy)
|
||
edited (-> initial
|
||
(r/step (n/change-property :copy-instance :opacity 0.4)) r/settle)
|
||
deleted (-> edited (r/step (n/delete-component :main2-root)) r/settle)
|
||
reset (-> deleted (r/step (n/reset :copy-instance)) r/settle)
|
||
shape (tm/role-shape reset :copy-instance)
|
||
undone (r/step reset (n/undo))
|
||
redone (r/step undone (n/redo))]
|
||
(t/is (= (:opacity (tm/role-shape initial :main-instance)) (:opacity shape)))
|
||
(t/is (nil? (:touched shape)))
|
||
(t/is (= (thn/normalize-file (tm/file deleted)) (thn/normalize-file (tm/file undone))))
|
||
(t/is (= (thn/normalize-file (tm/file reset)) (thn/normalize-file (tm/file redone))))))
|
||
|
||
(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)}
|
||
{})
|
||
|
||
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"))))
|
||
|
||
(defn- margin-of
|
||
[file label]
|
||
(:layout-item-margin (ths/get-shape file label)))
|
||
|
||
(defn- nested-layout-board
|
||
"component1, a board with a `layout` whose child has the margin `margin`;
|
||
component2, which nests a copy of it; a copy of component2."
|
||
[layout margin]
|
||
(let [child {:parent-label :main1-root
|
||
:layout-item-margin margin
|
||
:layout-item-margin-type :simple}]
|
||
(-> (thf/sample-file :file1)
|
||
(cond-> (= layout :flex)
|
||
(-> (tho/add-frame :main1-root {:layout :flex :layout-flex-dir :row})
|
||
(tho/add-rect :main1-child child)))
|
||
(cond-> (= layout :grid)
|
||
(-> (tho/add-grid-frame :main1-root)
|
||
(tho/add-rect :main1-child child)
|
||
(tho/place-in-grid-columns :main1-root 1 {1 :main1-child})))
|
||
(thc/make-component :component1 :main1-root)
|
||
(tho/add-frame :main2-root)
|
||
(thc/instantiate-component :component1 :nested-head :parent-label :main2-root)
|
||
(thc/make-component :component2 :main2-root)
|
||
(thc/instantiate-component :component2 :copy2-root
|
||
:children-labels [:copy-nested-head :copy-nested-child]))))
|
||
|
||
(t/deftest test-reset-of-a-nested-copy-of-a-layout-board
|
||
;; a copy of component2 resets its nested copy of component1
|
||
(doseq [layout [:flex :grid]]
|
||
(let [margin {:m1 7 :m2 7 :m3 7 :m4 7}
|
||
file (nested-layout-board layout margin)
|
||
page (thf/current-page file)
|
||
changes (cll/generate-reset-component (pcb/empty-changes)
|
||
file
|
||
{(:id file) file}
|
||
(ctn/make-container page :page)
|
||
(thi/id :copy-nested-head))
|
||
file' (thf/apply-changes file changes)]
|
||
(t/testing (name layout)
|
||
(t/is (= margin (margin-of file :copy-nested-child)))
|
||
(t/is (= margin (margin-of file' :copy-nested-child)))))))
|
||
|
||
(t/deftest test-update-main-after-a-reset-of-a-nested-copy-keeps-the-main-overrides
|
||
;; component2's main overrides the fill of its nested copy of component1;
|
||
;; a copy of component2 resets its nested copy, then pushes to the main
|
||
(let [file (-> (thf/sample-file :file1)
|
||
(tho/add-nested-component :component1 :main1-root :main1-child
|
||
:component2 :main2-root :nested-head)
|
||
(thc/instantiate-component :component2 :copy2-root
|
||
:children-labels [:copy-nested-head :copy-nested-child]))
|
||
page (thf/current-page file)
|
||
nested (first (:shapes (ths/get-shape file :nested-head)))
|
||
changes (cls/generate-update-shapes (pcb/empty-changes nil (:id page))
|
||
#{nested}
|
||
#(assoc % :fills (ths/sample-fills-color :fill-color "#ff0000"))
|
||
(:objects page)
|
||
{})
|
||
file (thf/apply-changes file changes)
|
||
page (thf/current-page file)
|
||
reset (cll/generate-reset-component (pcb/empty-changes) file {(:id file) file}
|
||
(ctn/make-container page :page)
|
||
(thi/id :copy-nested-head))
|
||
file (thf/apply-changes file reset)
|
||
page (thf/current-page file)
|
||
container (ctn/make-container page :page)
|
||
update (-> (pcb/empty-changes)
|
||
(pcb/with-container container)
|
||
(cll/generate-sync-shape-inverse {(:id file) file} container (thi/id :copy2-root)))
|
||
file' (thf/apply-changes file update)]
|
||
(t/is (= #{:fill-group} (:touched (ths/get-shape-by-id file nested))))
|
||
(t/is (= #{:fill-group} (:touched (ths/get-shape-by-id file' nested))))))
|
||
|
||
|
||
(t/deftest reset-restores-the-main-rotation-and-undo-restores-the-override
|
||
(let [file (-> (thf/sample-file :file1)
|
||
(tho/add-simple-component-with-copy
|
||
:component1 :main-root :main-child :copy-root
|
||
:main-root-params {:width 100 :height 100}
|
||
:main-child-params {:width 40 :height 30}
|
||
:copy-root-params {:children-labels [:copy-child]}))
|
||
page (thf/current-page file)
|
||
root (ths/get-shape file :copy-root)
|
||
rotated (thf/apply-changes
|
||
file (n/transform-changes page root
|
||
(ctm/rotation-modifiers root (gsh/shape->center root) 45)))
|
||
page (thf/current-page rotated)
|
||
changes (cll/generate-reset-component
|
||
(pcb/empty-changes) rotated {(:id rotated) rotated}
|
||
(ctn/make-container page :page) (thi/id :copy-root))
|
||
reset (thf/apply-changes rotated changes)
|
||
undone (thf/apply-changes reset {:redo-changes (:undo-changes changes)})]
|
||
(t/is (= 0 (:rotation (ths/get-shape reset :copy-root))))
|
||
(t/is (= 0 (:rotation (ths/get-shape reset :copy-child))))
|
||
(t/is (nil? (:touched (ths/get-shape reset :copy-root))))
|
||
(t/is (= (thn/normalize-file rotated) (thn/normalize-file undone)))))
|
||
|
||
(t/deftest reset-a-nested-copy-keeps-its-rotated-parent-placement
|
||
(let [initial (-> (setup/nested-component-with-copy)
|
||
(r/step (n/rotate :copy-root 45)) r/settle)
|
||
head (tm/role-shape initial :copy-instance)
|
||
child (tm/shape-by-id initial (first (:shapes head)))
|
||
reset (-> initial (r/step (n/reset :copy-instance)) r/settle)
|
||
after (tm/shape-by-id reset (:id child))]
|
||
(t/is (gsh/close-attrs? :points (:points child) (:points after) 0.001))
|
||
(t/is (= (:rotation child) (:rotation after)))
|
||
(t/is (nil? (:touched (tm/role-shape reset :copy-instance))))))
|