mirror of
https://github.com/penpot/penpot.git
synced 2026-11-05 17:40:39 +00:00
🐛 Keep variant sets intact under group, mask, board, and layout
This commit is contained in:
parent
b2c9ca1d23
commit
69fa682217
@ -426,6 +426,52 @@
|
||||
true
|
||||
(has-any-copy-parent? objects (:parent-id shape))))))
|
||||
|
||||
(defn get-variant-boundary-id
|
||||
"Id of the nearest variant main or variant container for this shape, or nil
|
||||
when the shape is outside any variant set.
|
||||
|
||||
Nested children resolve to their enclosing main (not the container), so two
|
||||
shapes under different variants are treated as different boundaries even
|
||||
when they share a set."
|
||||
[objects shape]
|
||||
(some (fn [s]
|
||||
(cond
|
||||
(ctk/is-variant? s) (:id s)
|
||||
(ctk/is-variant-container? s) (:id s)
|
||||
:else nil))
|
||||
(cfh/get-parents-with-self objects (:id shape))))
|
||||
|
||||
(defn crosses-variant-set-boundary?
|
||||
"True when the selection mixes shapes that belong to different variant
|
||||
boundaries — including outside vs in-set, two different variant mains
|
||||
(even in the same set), or two different variant containers.
|
||||
|
||||
Structure commands (group, mask, board-from-selection, layout) must not
|
||||
reparent across that boundary."
|
||||
[objects shapes]
|
||||
(let [ids (into #{} (map #(get-variant-boundary-id objects %)) shapes)]
|
||||
(> (count ids) 1)))
|
||||
|
||||
(defn ids-for-structure-op
|
||||
"Ids allowed for Group, Mask, and Create board from selection.
|
||||
|
||||
Drops copy-children and variant mains, refuses a cross-boundary or
|
||||
container selection, then removes descendants whose ancestor is
|
||||
still selected. Mains are dropped before loop-cleaning so a
|
||||
selected main does not swallow nested children. Layout does not
|
||||
use this helper."
|
||||
[objects ids]
|
||||
(let [shapes (->> ids
|
||||
(remove #(has-any-copy-parent? objects (get objects %)))
|
||||
(keep (d/getf objects)))]
|
||||
(if (or (crosses-variant-set-boundary? objects shapes)
|
||||
(some ctk/is-variant-container? shapes))
|
||||
[]
|
||||
(->> shapes
|
||||
(remove ctk/is-variant?)
|
||||
(map :id)
|
||||
(cfh/clean-loops objects)))))
|
||||
|
||||
(defn has-any-main?
|
||||
"Check if the shape is a main component or has any children or parent that is a main component."
|
||||
[objects shape]
|
||||
|
||||
@ -154,3 +154,140 @@
|
||||
shape-b (make-shape id-b :parent-id id-a)
|
||||
objects (objects-map shape-a shape-b)]
|
||||
(t/is (nil? (ctc/find-component-main objects shape-a false))))))
|
||||
|
||||
(t/deftest get-variant-boundary-id-recognizes-container-main-and-nested
|
||||
(let [page-id uuid/zero
|
||||
container-id (uuid/next)
|
||||
main-id (uuid/next)
|
||||
nested-id (uuid/next)
|
||||
outside-id (uuid/next)
|
||||
container (make-shape container-id
|
||||
:parent-id page-id
|
||||
:type :frame
|
||||
:is-variant-container true)
|
||||
main (make-shape main-id
|
||||
:parent-id container-id
|
||||
:type :frame
|
||||
:variant-id container-id)
|
||||
nested (make-shape nested-id :parent-id main-id)
|
||||
outside (make-shape outside-id :parent-id page-id)
|
||||
objects (objects-map container main nested outside)]
|
||||
(t/is (= main-id (ctc/get-variant-boundary-id objects nested)))
|
||||
(t/is (= main-id (ctc/get-variant-boundary-id objects main)))
|
||||
(t/is (= container-id (ctc/get-variant-boundary-id objects container)))
|
||||
(t/is (nil? (ctc/get-variant-boundary-id objects outside)))))
|
||||
|
||||
(t/deftest crosses-variant-set-boundary?
|
||||
(let [page-id uuid/zero
|
||||
container-id (uuid/next)
|
||||
main-id (uuid/next)
|
||||
main-2-id (uuid/next)
|
||||
nested-id (uuid/next)
|
||||
nested-2-id (uuid/next)
|
||||
outside-id (uuid/next)
|
||||
other-set-id (uuid/next)
|
||||
other-main-id (uuid/next)
|
||||
other-nest-id (uuid/next)
|
||||
container (make-shape container-id
|
||||
:parent-id page-id
|
||||
:type :frame
|
||||
:is-variant-container true)
|
||||
main (make-shape main-id
|
||||
:parent-id container-id
|
||||
:type :frame
|
||||
:variant-id container-id)
|
||||
main-2 (make-shape main-2-id
|
||||
:parent-id container-id
|
||||
:type :frame
|
||||
:variant-id container-id)
|
||||
nested (make-shape nested-id :parent-id main-id)
|
||||
nested-2 (make-shape nested-2-id :parent-id main-2-id)
|
||||
outside (make-shape outside-id :parent-id page-id)
|
||||
other-set (make-shape other-set-id
|
||||
:parent-id page-id
|
||||
:type :frame
|
||||
:is-variant-container true)
|
||||
other-main (make-shape other-main-id
|
||||
:parent-id other-set-id
|
||||
:type :frame
|
||||
:variant-id other-set-id)
|
||||
other-nested (make-shape other-nest-id :parent-id other-main-id)
|
||||
objects (objects-map container main main-2 nested nested-2
|
||||
outside other-set other-main other-nested)]
|
||||
(t/testing "nested child + outside crosses"
|
||||
(t/is (true? (ctc/crosses-variant-set-boundary?
|
||||
objects [nested outside]))))
|
||||
(t/testing "main + outside crosses"
|
||||
(t/is (true? (ctc/crosses-variant-set-boundary?
|
||||
objects [main outside]))))
|
||||
(t/testing "container + outside crosses"
|
||||
(t/is (true? (ctc/crosses-variant-set-boundary?
|
||||
objects [container outside]))))
|
||||
(t/testing "nested children of two variants in the same set cross"
|
||||
(t/is (true? (ctc/crosses-variant-set-boundary?
|
||||
objects [nested nested-2]))))
|
||||
(t/testing "nested children from two different sets cross"
|
||||
(t/is (true? (ctc/crosses-variant-set-boundary?
|
||||
objects [nested other-nested]))))
|
||||
(t/testing "two outside shapes do not cross"
|
||||
(let [outside-2 (make-shape (uuid/next) :parent-id page-id)]
|
||||
(t/is (false? (ctc/crosses-variant-set-boundary?
|
||||
(assoc objects (:id outside-2) outside-2)
|
||||
[outside outside-2])))))
|
||||
(t/testing "nested only does not cross"
|
||||
(t/is (false? (ctc/crosses-variant-set-boundary? objects [nested]))))
|
||||
(t/testing "main + nested under that main do not cross"
|
||||
(t/is (false? (ctc/crosses-variant-set-boundary?
|
||||
objects [main nested]))))
|
||||
(t/testing "two nested under the same main do not cross"
|
||||
(let [nested-b (make-shape (uuid/next) :parent-id main-id)]
|
||||
(t/is (false? (ctc/crosses-variant-set-boundary?
|
||||
(assoc objects (:id nested-b) nested-b)
|
||||
[nested nested-b])))))))
|
||||
|
||||
(t/deftest ids-for-structure-op
|
||||
(let [page-id uuid/zero
|
||||
container-id (uuid/next)
|
||||
main-id (uuid/next)
|
||||
main-2-id (uuid/next)
|
||||
nested-id (uuid/next)
|
||||
nested-2-id (uuid/next)
|
||||
outside-id (uuid/next)
|
||||
outside-2-id (uuid/next)
|
||||
container (make-shape container-id
|
||||
:parent-id page-id
|
||||
:type :frame
|
||||
:is-variant-container true)
|
||||
main (make-shape main-id
|
||||
:parent-id container-id
|
||||
:type :frame
|
||||
:variant-id container-id)
|
||||
main-2 (make-shape main-2-id
|
||||
:parent-id container-id
|
||||
:type :frame
|
||||
:variant-id container-id)
|
||||
nested (make-shape nested-id :parent-id main-id)
|
||||
nested-2 (make-shape nested-2-id :parent-id main-id)
|
||||
outside (make-shape outside-id :parent-id page-id)
|
||||
outside-2 (make-shape outside-2-id :parent-id page-id)
|
||||
objects (objects-map container main main-2 nested nested-2
|
||||
outside outside-2)]
|
||||
(t/testing "container-only is refused"
|
||||
(t/is (= [] (vec (ctc/ids-for-structure-op objects [container-id])))))
|
||||
(t/testing "nested + outside is refused"
|
||||
(t/is (= [] (vec (ctc/ids-for-structure-op objects [nested-id outside-id])))))
|
||||
(t/testing "main + outside is refused"
|
||||
(t/is (= [] (vec (ctc/ids-for-structure-op objects [main-id outside-id])))))
|
||||
(t/testing "two mains cross and are refused"
|
||||
(t/is (= [] (vec (ctc/ids-for-structure-op objects [main-id main-2-id])))))
|
||||
(t/testing "single main is dropped to empty"
|
||||
(t/is (= [] (vec (ctc/ids-for-structure-op objects [main-id])))))
|
||||
(t/testing "main + nested under that main keeps only the nested"
|
||||
(t/is (= [nested-id]
|
||||
(vec (ctc/ids-for-structure-op objects [main-id nested-id])))))
|
||||
(t/testing "two nested under one main are both kept"
|
||||
(t/is (= [nested-id nested-2-id]
|
||||
(vec (ctc/ids-for-structure-op objects [nested-id nested-2-id])))))
|
||||
(t/testing "two ordinary outside shapes are both kept"
|
||||
(t/is (= [outside-id outside-2-id]
|
||||
(vec (ctc/ids-for-structure-op objects [outside-id outside-2-id])))))))
|
||||
|
||||
@ -30,6 +30,14 @@
|
||||
reverse
|
||||
(map #(get objects %))))
|
||||
|
||||
(defn- shapes-for-group-or-mask
|
||||
"Shapes allowed for Group and Mask after the shared variant gates.
|
||||
|
||||
`ids-for-structure-op` drops mains before `clean-loops` so a selected
|
||||
variant main does not swallow nested children."
|
||||
[objects ids]
|
||||
(shapes-for-grouping objects (ctn/ids-for-structure-op objects ids)))
|
||||
|
||||
(defn- get-empty-groups-after-group-creation
|
||||
"An auxiliary function that finds and returns a set of ids that
|
||||
corresponds to groups that should be deleted after a group creation.
|
||||
@ -188,18 +196,11 @@
|
||||
(ptk/reify ::group-shapes
|
||||
ptk/WatchEvent
|
||||
(watch [it state _]
|
||||
(let [id (d/nilv id (uuid/next))
|
||||
page-id (:current-page-id state)
|
||||
objects (dsh/lookup-page-objects state page-id)
|
||||
|
||||
shapes
|
||||
(->> ids
|
||||
(cfh/clean-loops objects)
|
||||
(remove #(ctn/has-any-copy-parent? objects (get objects %)))
|
||||
(shapes-for-grouping objects)
|
||||
(remove ctk/is-variant?))
|
||||
|
||||
parents (into #{} (map :parent-id) shapes)]
|
||||
(let [id (d/nilv id (uuid/next))
|
||||
page-id (:current-page-id state)
|
||||
objects (dsh/lookup-page-objects state page-id)
|
||||
shapes (shapes-for-group-or-mask objects ids)
|
||||
parents (into #{} (map :parent-id) shapes)]
|
||||
(when-not (empty? shapes)
|
||||
(let [[group changes]
|
||||
(prepare-create-group (pcb/empty-changes it) id objects page-id shapes "Group" false)]
|
||||
@ -213,7 +214,9 @@
|
||||
(ptk/reify ::group-selected
|
||||
ptk/WatchEvent
|
||||
(watch [_ state _]
|
||||
(let [selected (dsh/lookup-selected state)]
|
||||
;; Raw ids: lookup-selected already clean-loops, which would drop
|
||||
;; nested children of a selected variant main before we can.
|
||||
(let [selected (dsh/get-selected-ids state)]
|
||||
(rx/of (group-shapes nil selected :change-selection? true))))))
|
||||
|
||||
(defn ungroup-shapes
|
||||
@ -287,11 +290,9 @@
|
||||
(watch [it state _]
|
||||
(let [page-id (:current-page-id state)
|
||||
objects (dsh/lookup-page-objects state page-id)
|
||||
selected (->> (or ids (dsh/lookup-selected state))
|
||||
(cfh/clean-loops objects)
|
||||
(remove #(ctn/has-any-copy-parent? objects (get objects %))))
|
||||
shapes (->> (shapes-for-grouping objects selected)
|
||||
(remove ctk/is-variant?))
|
||||
shapes (shapes-for-group-or-mask
|
||||
objects
|
||||
(or ids (dsh/get-selected-ids state)))
|
||||
first-shape (first shapes)]
|
||||
(when-not (empty? shapes)
|
||||
(let [;; If the selected shape is a group, we can use it. If not,
|
||||
|
||||
@ -17,6 +17,7 @@
|
||||
[app.common.logic.libraries :as cll]
|
||||
[app.common.types.color :as clr]
|
||||
[app.common.types.component :as ctc]
|
||||
[app.common.types.container :as ctn]
|
||||
[app.common.types.modifiers :as ctm]
|
||||
[app.common.types.shape.layout :as ctl]
|
||||
[app.common.uuid :as uuid]
|
||||
@ -216,13 +217,15 @@
|
||||
has-component? (some true? (map ctc/instance-root? selected-shapes))
|
||||
is-component? (and single? has-component?)
|
||||
has-variant? (some ctc/is-variant? selected-shapes)
|
||||
has-container? (some ctc/is-variant-container? selected-shapes)
|
||||
crosses-set? (ctn/crosses-variant-set-boundary? objects selected-shapes)
|
||||
|
||||
has-layout? (and single? (ctl/any-layout? (first selected-shapes)))
|
||||
|
||||
new-shape-id (uuid/next)
|
||||
undo-id (js/Symbol)]
|
||||
|
||||
(if has-variant?
|
||||
(if (or has-variant? has-container? crosses-set?)
|
||||
(rx/empty)
|
||||
(rx/concat
|
||||
(rx/of (dwu/start-undo-transaction undo-id))
|
||||
@ -294,12 +297,17 @@
|
||||
|
||||
undo-id (js/Symbol)]
|
||||
|
||||
(rx/of
|
||||
(dwu/start-undo-transaction undo-id)
|
||||
(if (and single? is-frame? (not has-layout?))
|
||||
(create-layout-from-id (first selected) type :from-frame? true)
|
||||
(create-layout-from-selection type))
|
||||
(dwu/commit-undo-transaction undo-id))))))
|
||||
;; Refuse before undo: container or cross-boundary. Leave single
|
||||
;; mains on the from-id branch (selection refuses has-variant?).
|
||||
(if (or (some ctc/is-variant-container? selected-shapes)
|
||||
(ctn/crosses-variant-set-boundary? objects selected-shapes))
|
||||
(rx/empty)
|
||||
(rx/of
|
||||
(dwu/start-undo-transaction undo-id)
|
||||
(if (and single? is-frame? (not has-layout?))
|
||||
(create-layout-from-id (first selected) type :from-frame? true)
|
||||
(create-layout-from-selection type))
|
||||
(dwu/commit-undo-transaction undo-id)))))))
|
||||
|
||||
(defn toggle-layout
|
||||
[type]
|
||||
|
||||
@ -476,16 +476,14 @@
|
||||
(ptk/reify ::create-artboard-from-selection
|
||||
ptk/WatchEvent
|
||||
(watch [_ state _]
|
||||
(let [page-id (:current-page-id state)
|
||||
objects (dsh/lookup-page-objects state page-id)
|
||||
selected (->> (dsh/lookup-selected state)
|
||||
(cfh/clean-loops objects)
|
||||
(remove #(ctn/has-any-copy-parent? objects (get objects %)))
|
||||
(remove #(->> %
|
||||
(get objects)
|
||||
(ctc/is-variant?))))]
|
||||
(let [page-id (:current-page-id state)
|
||||
objects (dsh/lookup-page-objects state page-id)
|
||||
selected (vec (ctn/ids-for-structure-op
|
||||
objects
|
||||
(dsh/get-selected-ids state)))]
|
||||
|
||||
(rx/of (create-artboard-from-shapes selected id parent-id index name delta)))))))
|
||||
(when (seq selected)
|
||||
(rx/of (create-artboard-from-shapes selected id parent-id index name delta))))))))
|
||||
|
||||
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
|
||||
;; Shape Flags
|
||||
|
||||
@ -398,6 +398,8 @@
|
||||
objects (deref refs/workspace-page-objects)
|
||||
any-in-copy? (some #(ctn/has-any-copy-parent? objects %) shapes)
|
||||
any-is-variant? (some ctk/is-variant? shapes)
|
||||
any-is-container? (some ctk/is-variant-container? shapes)
|
||||
crosses-set? (ctn/crosses-variant-set-boundary? objects shapes)
|
||||
|
||||
;; components can't be ungrouped
|
||||
has-frame? (->> shapes (d/seek #(and (cfh/frame-shape? %) (not (ctk/instance-head? %)) (not (ctk/is-variant-container? %)))))
|
||||
@ -416,7 +418,7 @@
|
||||
#(st/emit! (dwsh/create-artboard-from-selection))]
|
||||
|
||||
[:*
|
||||
(when (not (or any-in-copy? any-is-variant?))
|
||||
(when (not (or any-in-copy? any-is-variant? any-is-container? crosses-set?))
|
||||
[:*
|
||||
(when (or has-bool? has-group? has-mask? has-frame?)
|
||||
[:> menu-entry* {:title (tr "workspace.shape.menu.ungroup")
|
||||
@ -621,6 +623,10 @@
|
||||
(and single? (every? ctl/grid-layout? shapes))
|
||||
|
||||
any-is-variant? (some ctk/is-variant? shapes)
|
||||
;; shape-context-menu* already omits this menu for container-only;
|
||||
;; gate here too so a future caller cannot show Add flex/grid.
|
||||
any-is-container? (some ctk/is-variant-container? shapes)
|
||||
crosses-set? (ctn/crosses-variant-set-boundary? objects shapes)
|
||||
|
||||
on-add-layout
|
||||
(mf/use-fn
|
||||
@ -653,7 +659,9 @@
|
||||
:shortcut-key :toggle-layout-grid
|
||||
:on-click on-remove-layout}])]
|
||||
|
||||
(when (or single? (not any-is-variant?))
|
||||
(when (and (not crosses-set?)
|
||||
(not any-is-container?)
|
||||
(or single? (not any-is-variant?)))
|
||||
[:div
|
||||
[:> menu-separator* {}]
|
||||
[:> menu-entry* {:title (tr "workspace.shape.menu.add-flex")
|
||||
|
||||
@ -6,18 +6,18 @@
|
||||
(ns frontend-tests.logic.groups-test
|
||||
(:require
|
||||
[app.common.data :as d]
|
||||
[app.common.test-helpers.components :as cthc]
|
||||
[app.common.test-helpers.compositions :as ctho]
|
||||
[app.common.test-helpers.files :as cthf]
|
||||
[app.common.test-helpers.ids-map :as cthi]
|
||||
[app.common.test-helpers.shapes :as cths]
|
||||
[app.common.test-helpers.variants :as thv]
|
||||
[app.common.types.component :as ctk]
|
||||
[app.common.uuid :as uuid]
|
||||
[app.common.types.container :as ctn]
|
||||
[app.common.types.shape.layout :as ctl]
|
||||
[app.main.data.changes :as dch]
|
||||
[app.main.data.workspace :as dw]
|
||||
[app.main.data.workspace.groups :as dwgr]
|
||||
[app.main.data.workspace.selection :as dws]
|
||||
[app.main.data.workspace.shape-layout :as dwsl]
|
||||
[app.main.data.workspace.shapes :as dwsh]
|
||||
[beicon.v2.core :as rx]
|
||||
[cljs.test :as t :include-macros true]
|
||||
[frontend-tests.helpers.pages :as thp]
|
||||
@ -41,6 +41,24 @@
|
||||
[]
|
||||
(cths/add-sample-shape (variant-file) :rect))
|
||||
|
||||
(defn- variant-file-with-nested-and-outside
|
||||
[]
|
||||
(-> (variant-file)
|
||||
(cths/add-sample-shape :nested :parent-label :root1)
|
||||
(cths/add-sample-shape :outside)))
|
||||
|
||||
(defn- variant-file-with-two-nested
|
||||
[]
|
||||
(-> (variant-file)
|
||||
(cths/add-sample-shape :nested-a :parent-label :root1)
|
||||
(cths/add-sample-shape :nested-b :parent-label :root1)))
|
||||
|
||||
(defn- variant-file-with-nested-in-each-main
|
||||
[]
|
||||
(-> (variant-file)
|
||||
(cths/add-sample-shape :nested-a :parent-label :root1)
|
||||
(cths/add-sample-shape :nested-b :parent-label :root2)))
|
||||
|
||||
(defn- masked-groups
|
||||
[file]
|
||||
(->> (cthf/current-page file)
|
||||
@ -103,29 +121,46 @@
|
||||
;; Group has been created and is selected
|
||||
(t/is (= (get-in new-state [:workspace-local :selected]) #{group-id}))))))))
|
||||
|
||||
(t/deftest mask-group-leaves-variant-main-in-the-set
|
||||
(t/deftest mask-group-of-main-and-outside-does-not-commit
|
||||
(t/async
|
||||
done
|
||||
(let [file (variant-file-with-rect)
|
||||
rect (cths/get-shape file :rect)
|
||||
main (cths/get-shape file :root1)
|
||||
comp (cthc/get-component file :comp1)]
|
||||
(let [file (variant-file-with-rect)
|
||||
rect (cths/get-shape file :rect)
|
||||
main (cths/get-shape file :root1)
|
||||
commits (atom [])]
|
||||
(run file
|
||||
[(dwgr/mask-group [(cthi/id :root1) (cthi/id :rect)])]
|
||||
done
|
||||
(fn [state]
|
||||
(let [file' (ths/get-file-from-state state)
|
||||
main' (cths/get-shape file' :root1)
|
||||
rect' (cths/get-shape file' :rect)
|
||||
group (first (masked-groups file'))
|
||||
comp' (cthc/get-component file' :comp1)]
|
||||
(t/is (= (:parent-id main) (:parent-id main')))
|
||||
(t/is (= (:variant-id main) (:variant-id main')))
|
||||
(t/is (= (:variant-name main) (:variant-name main')))
|
||||
(t/is (= (:variant-id comp) (:variant-id comp')))
|
||||
(t/is (= (:id group) (:parent-id rect')))
|
||||
(t/is (= (:parent-id rect) (:parent-id group)))
|
||||
(t/is (= (:selrect rect) (:selrect group)))))))))
|
||||
(let [file' (ths/get-file-from-state state)]
|
||||
(t/is (= 0 (count @commits)))
|
||||
(t/is (empty? (masked-groups file')))
|
||||
(t/is (= (:parent-id main) (:parent-id (cths/get-shape file' :root1))))
|
||||
(t/is (= (:parent-id rect) (:parent-id (cths/get-shape file' :rect))))))
|
||||
:commits commits))))
|
||||
|
||||
(t/deftest mask-group-of-nested-and-outside-does-not-commit
|
||||
(t/async
|
||||
done
|
||||
(let [file (variant-file-with-nested-and-outside)
|
||||
nested (cths/get-shape file :nested)
|
||||
outside (cths/get-shape file :outside)
|
||||
commits (atom [])]
|
||||
(t/is (ctn/crosses-variant-set-boundary?
|
||||
(:objects (cthf/current-page file))
|
||||
[nested outside]))
|
||||
(run file
|
||||
[(dwgr/mask-group [(cthi/id :nested) (cthi/id :outside)])]
|
||||
done
|
||||
(fn [state]
|
||||
(let [file' (ths/get-file-from-state state)]
|
||||
(t/is (= 0 (count @commits)))
|
||||
(t/is (empty? (masked-groups file')))
|
||||
(t/is (= (:parent-id nested)
|
||||
(:parent-id (cths/get-shape file' :nested))))
|
||||
(t/is (= (:parent-id outside)
|
||||
(:parent-id (cths/get-shape file' :outside))))))
|
||||
:commits commits))))
|
||||
|
||||
(t/deftest mask-group-of-only-variant-mains-does-not-commit
|
||||
(t/async
|
||||
@ -178,24 +213,480 @@
|
||||
(t/is (= [group-id] (map :id (groups file'))))
|
||||
(t/is (= group-id (:parent-id (cths/get-shape file' :child))))))))))
|
||||
|
||||
(t/deftest group-shapes-leaves-variant-main-in-the-set
|
||||
(t/deftest group-shapes-of-main-and-outside-does-not-commit
|
||||
(t/async
|
||||
done
|
||||
(let [file (variant-file-with-rect)
|
||||
rect (cths/get-shape file :rect)]
|
||||
(let [file (variant-file-with-rect)
|
||||
rect (cths/get-shape file :rect)
|
||||
main (cths/get-shape file :root1)
|
||||
commits (atom [])]
|
||||
(run file
|
||||
[(dwgr/group-shapes nil [(cthi/id :root1) (cthi/id :rect)])]
|
||||
done
|
||||
(fn [state]
|
||||
(let [file' (ths/get-file-from-state state)
|
||||
board (cths/get-shape file' :variant)
|
||||
main (cths/get-shape file' :root1)
|
||||
rect' (cths/get-shape file' :rect)
|
||||
group (first (groups file'))]
|
||||
(t/is (= (:id board) (:parent-id main)))
|
||||
(let [file' (ths/get-file-from-state state)]
|
||||
(t/is (= 0 (count @commits)))
|
||||
(t/is (empty? (groups file')))
|
||||
(t/is (= (:parent-id main) (:parent-id (cths/get-shape file' :root1))))
|
||||
(t/is (= (:parent-id rect) (:parent-id (cths/get-shape file' :rect))))))
|
||||
:commits commits))))
|
||||
|
||||
(t/deftest group-shapes-of-nested-and-outside-does-not-commit
|
||||
(t/async
|
||||
done
|
||||
(let [file (variant-file-with-nested-and-outside)
|
||||
nested (cths/get-shape file :nested)
|
||||
outside (cths/get-shape file :outside)
|
||||
commits (atom [])]
|
||||
(run file
|
||||
[(dwgr/group-shapes nil [(cthi/id :nested) (cthi/id :outside)])]
|
||||
done
|
||||
(fn [state]
|
||||
(let [file' (ths/get-file-from-state state)]
|
||||
(t/is (= 0 (count @commits)))
|
||||
(t/is (empty? (groups file')))
|
||||
(t/is (= (:parent-id nested)
|
||||
(:parent-id (cths/get-shape file' :nested))))
|
||||
(t/is (= (:parent-id outside)
|
||||
(:parent-id (cths/get-shape file' :outside))))))
|
||||
:commits commits))))
|
||||
|
||||
(t/deftest group-shapes-of-two-nested-children-creates-a-group
|
||||
(t/async
|
||||
done
|
||||
(let [file (variant-file-with-two-nested)
|
||||
main (cths/get-shape file :root1)]
|
||||
(run file
|
||||
[(dwgr/group-shapes nil [(cthi/id :nested-a) (cthi/id :nested-b)])]
|
||||
done
|
||||
(fn [state]
|
||||
(let [file' (ths/get-file-from-state state)
|
||||
group (first (groups file'))]
|
||||
(t/is (some? group))
|
||||
(t/is (= (:id group) (:parent-id rect')))
|
||||
(t/is (= (:parent-id rect) (:parent-id group)))))))))
|
||||
(t/is (= (:id main) (:parent-id group)))
|
||||
(t/is (= (:id group) (:parent-id (cths/get-shape file' :nested-a))))
|
||||
(t/is (= (:id group) (:parent-id (cths/get-shape file' :nested-b))))))))))
|
||||
|
||||
(t/deftest group-shapes-of-main-and-nested-keeps-main-groups-nested
|
||||
(t/async
|
||||
done
|
||||
(let [file (-> (variant-file)
|
||||
(cths/add-sample-shape :nested :parent-label :root1))
|
||||
main (cths/get-shape file :root1)
|
||||
nested (cths/get-shape file :nested)
|
||||
commits (atom [])]
|
||||
(run file
|
||||
[(dwgr/group-shapes nil [(cthi/id :root1) (cthi/id :nested)])]
|
||||
done
|
||||
(fn [state]
|
||||
(let [file' (ths/get-file-from-state state)
|
||||
main' (cths/get-shape file' :root1)
|
||||
nested' (cths/get-shape file' :nested)
|
||||
group (first (groups file'))]
|
||||
(t/is (pos? (count @commits)))
|
||||
(t/is (some? group))
|
||||
(t/is (= (:parent-id main) (:parent-id main')))
|
||||
(t/is (= (:variant-id main) (:variant-id main')))
|
||||
(t/is (= (:id main') (:parent-id group)))
|
||||
(t/is (= (:id group) (:parent-id nested')))
|
||||
(t/is (= (:parent-id nested) (:id main)))))
|
||||
:commits commits))))
|
||||
|
||||
(t/deftest mask-group-of-main-and-nested-keeps-main-masks-nested
|
||||
(t/async
|
||||
done
|
||||
(let [file (-> (variant-file)
|
||||
(cths/add-sample-shape :nested :parent-label :root1))
|
||||
main (cths/get-shape file :root1)
|
||||
nested (cths/get-shape file :nested)
|
||||
commits (atom [])]
|
||||
(run file
|
||||
[(dwgr/mask-group [(cthi/id :root1) (cthi/id :nested)])]
|
||||
done
|
||||
(fn [state]
|
||||
(let [file' (ths/get-file-from-state state)
|
||||
main' (cths/get-shape file' :root1)
|
||||
nested' (cths/get-shape file' :nested)
|
||||
group (first (masked-groups file'))]
|
||||
(t/is (pos? (count @commits)))
|
||||
(t/is (some? group))
|
||||
(t/is (:masked-group group))
|
||||
(t/is (= (:parent-id main) (:parent-id main')))
|
||||
(t/is (= (:variant-id main) (:variant-id main')))
|
||||
(t/is (= (:id main') (:parent-id group)))
|
||||
(t/is (= (:id group) (:parent-id nested')))
|
||||
(t/is (= (:parent-id nested) (:id main)))))
|
||||
:commits commits))))
|
||||
|
||||
(t/deftest group-shapes-of-nested-in-different-variants-does-not-commit
|
||||
(t/async
|
||||
done
|
||||
(let [file (variant-file-with-nested-in-each-main)
|
||||
nested-a (cths/get-shape file :nested-a)
|
||||
nested-b (cths/get-shape file :nested-b)
|
||||
commits (atom [])]
|
||||
(t/is (ctn/crosses-variant-set-boundary?
|
||||
(:objects (cthf/current-page file))
|
||||
[nested-a nested-b]))
|
||||
(run file
|
||||
[(dwgr/group-shapes nil [(cthi/id :nested-a) (cthi/id :nested-b)])]
|
||||
done
|
||||
(fn [state]
|
||||
(let [file' (ths/get-file-from-state state)]
|
||||
(t/is (= 0 (count @commits)))
|
||||
(t/is (empty? (groups file')))
|
||||
(t/is (= (:parent-id nested-a)
|
||||
(:parent-id (cths/get-shape file' :nested-a))))
|
||||
(t/is (= (:parent-id nested-b)
|
||||
(:parent-id (cths/get-shape file' :nested-b))))))
|
||||
:commits commits))))
|
||||
|
||||
(t/deftest mask-group-of-nested-in-different-variants-does-not-commit
|
||||
(t/async
|
||||
done
|
||||
(let [file (variant-file-with-nested-in-each-main)
|
||||
nested-a (cths/get-shape file :nested-a)
|
||||
nested-b (cths/get-shape file :nested-b)
|
||||
commits (atom [])]
|
||||
(run file
|
||||
[(dwgr/mask-group [(cthi/id :nested-a) (cthi/id :nested-b)])]
|
||||
done
|
||||
(fn [state]
|
||||
(let [file' (ths/get-file-from-state state)]
|
||||
(t/is (= 0 (count @commits)))
|
||||
(t/is (empty? (masked-groups file')))
|
||||
(t/is (= (:parent-id nested-a)
|
||||
(:parent-id (cths/get-shape file' :nested-a))))
|
||||
(t/is (= (:parent-id nested-b)
|
||||
(:parent-id (cths/get-shape file' :nested-b))))))
|
||||
:commits commits))))
|
||||
|
||||
(t/deftest create-artboard-of-nested-and-outside-does-not-commit
|
||||
(t/async
|
||||
done
|
||||
(let [file (variant-file-with-nested-and-outside)
|
||||
nested (cths/get-shape file :nested)
|
||||
outside (cths/get-shape file :outside)
|
||||
commits (atom [])]
|
||||
(run file
|
||||
[(dws/select-shapes (d/ordered-set (cthi/id :nested) (cthi/id :outside)))
|
||||
(dwsh/create-artboard-from-selection)]
|
||||
done
|
||||
(fn [state]
|
||||
(let [file' (ths/get-file-from-state state)]
|
||||
(t/is (= 0 (count @commits)))
|
||||
(t/is (= (:parent-id nested)
|
||||
(:parent-id (cths/get-shape file' :nested))))
|
||||
(t/is (= (:parent-id outside)
|
||||
(:parent-id (cths/get-shape file' :outside))))))
|
||||
:commits commits))))
|
||||
|
||||
(t/deftest create-artboard-of-main-and-outside-does-not-commit
|
||||
(t/async
|
||||
done
|
||||
(let [file (variant-file-with-rect)
|
||||
rect (cths/get-shape file :rect)
|
||||
main (cths/get-shape file :root1)
|
||||
commits (atom [])]
|
||||
(run file
|
||||
[(dws/select-shapes (d/ordered-set (cthi/id :root1) (cthi/id :rect)))
|
||||
(dwsh/create-artboard-from-selection)]
|
||||
done
|
||||
(fn [state]
|
||||
(let [file' (ths/get-file-from-state state)]
|
||||
(t/is (= 0 (count @commits)))
|
||||
(t/is (= (:parent-id main) (:parent-id (cths/get-shape file' :root1))))
|
||||
(t/is (= (:parent-id rect) (:parent-id (cths/get-shape file' :rect))))))
|
||||
:commits commits))))
|
||||
|
||||
(t/deftest create-artboard-of-two-nested-children-commits
|
||||
(t/async
|
||||
done
|
||||
(let [file (variant-file-with-two-nested)
|
||||
main (cths/get-shape file :root1)
|
||||
commits (atom [])]
|
||||
(run file
|
||||
[(dws/select-shapes (d/ordered-set (cthi/id :nested-a) (cthi/id :nested-b)))
|
||||
(dwsh/create-artboard-from-selection)]
|
||||
done
|
||||
(fn [state]
|
||||
(let [file' (ths/get-file-from-state state)
|
||||
nested-a' (cths/get-shape file' :nested-a)
|
||||
nested-b' (cths/get-shape file' :nested-b)
|
||||
board (get (:objects (cthf/current-page file'))
|
||||
(:parent-id nested-a'))]
|
||||
(t/is (pos? (count @commits)))
|
||||
(t/is (= :frame (:type board)))
|
||||
(t/is (not (ctk/is-variant-container? board)))
|
||||
(t/is (= (:id main) (:parent-id board)))
|
||||
(t/is (= (:id board) (:parent-id nested-a')))
|
||||
(t/is (= (:id board) (:parent-id nested-b')))))
|
||||
:commits commits))))
|
||||
|
||||
(t/deftest create-artboard-of-main-and-nested-keeps-main-boards-nested
|
||||
(t/async
|
||||
done
|
||||
(let [file (-> (variant-file)
|
||||
(cths/add-sample-shape :nested :parent-label :root1))
|
||||
main (cths/get-shape file :root1)
|
||||
nested (cths/get-shape file :nested)
|
||||
commits (atom [])]
|
||||
(run file
|
||||
[(dws/select-shapes (d/ordered-set (cthi/id :root1) (cthi/id :nested)))
|
||||
(dwsh/create-artboard-from-selection)]
|
||||
done
|
||||
(fn [state]
|
||||
(let [file' (ths/get-file-from-state state)
|
||||
main' (cths/get-shape file' :root1)
|
||||
nested' (cths/get-shape file' :nested)
|
||||
board (get (:objects (cthf/current-page file'))
|
||||
(:parent-id nested'))]
|
||||
(t/is (pos? (count @commits)))
|
||||
(t/is (= :frame (:type board)))
|
||||
(t/is (not (ctk/is-variant-container? board)))
|
||||
(t/is (= (:parent-id main) (:parent-id main')))
|
||||
(t/is (= (:variant-id main) (:variant-id main')))
|
||||
(t/is (= (:id main') (:parent-id board)))
|
||||
(t/is (= (:id board) (:parent-id nested')))
|
||||
(t/is (= (:parent-id nested) (:id main)))))
|
||||
:commits commits))))
|
||||
|
||||
(t/deftest group-selected-of-main-and-nested-keeps-main-groups-nested
|
||||
(t/async
|
||||
done
|
||||
(let [file (-> (variant-file)
|
||||
(cths/add-sample-shape :nested :parent-label :root1))
|
||||
main (cths/get-shape file :root1)
|
||||
nested (cths/get-shape file :nested)
|
||||
commits (atom [])]
|
||||
(run file
|
||||
[(dws/select-shapes (d/ordered-set (cthi/id :root1) (cthi/id :nested)))
|
||||
(dwgr/group-selected)]
|
||||
done
|
||||
(fn [state]
|
||||
(let [file' (ths/get-file-from-state state)
|
||||
main' (cths/get-shape file' :root1)
|
||||
nested' (cths/get-shape file' :nested)
|
||||
group (first (groups file'))]
|
||||
(t/is (pos? (count @commits)))
|
||||
(t/is (some? group))
|
||||
(t/is (= (:parent-id main) (:parent-id main')))
|
||||
(t/is (= (:variant-id main) (:variant-id main')))
|
||||
(t/is (= (:id main') (:parent-id group)))
|
||||
(t/is (= (:id group) (:parent-id nested')))
|
||||
(t/is (= (:parent-id nested) (:id main)))))
|
||||
:commits commits))))
|
||||
|
||||
(t/deftest mask-group-from-selection-of-main-and-nested-keeps-main-masks-nested
|
||||
(t/async
|
||||
done
|
||||
(let [file (-> (variant-file)
|
||||
(cths/add-sample-shape :nested :parent-label :root1))
|
||||
main (cths/get-shape file :root1)
|
||||
nested (cths/get-shape file :nested)
|
||||
commits (atom [])]
|
||||
(run file
|
||||
[(dws/select-shapes (d/ordered-set (cthi/id :root1) (cthi/id :nested)))
|
||||
(dwgr/mask-group)]
|
||||
done
|
||||
(fn [state]
|
||||
(let [file' (ths/get-file-from-state state)
|
||||
main' (cths/get-shape file' :root1)
|
||||
nested' (cths/get-shape file' :nested)
|
||||
group (first (masked-groups file'))]
|
||||
(t/is (pos? (count @commits)))
|
||||
(t/is (some? group))
|
||||
(t/is (:masked-group group))
|
||||
(t/is (= (:parent-id main) (:parent-id main')))
|
||||
(t/is (= (:variant-id main) (:variant-id main')))
|
||||
(t/is (= (:id main') (:parent-id group)))
|
||||
(t/is (= (:id group) (:parent-id nested')))
|
||||
(t/is (= (:parent-id nested) (:id main)))))
|
||||
:commits commits))))
|
||||
|
||||
(t/deftest create-layout-of-nested-and-outside-does-not-commit
|
||||
(t/async
|
||||
done
|
||||
(let [file (variant-file-with-nested-and-outside)
|
||||
nested (cths/get-shape file :nested)
|
||||
outside (cths/get-shape file :outside)
|
||||
commits (atom [])]
|
||||
(run file
|
||||
[(dws/select-shapes (d/ordered-set (cthi/id :nested) (cthi/id :outside)))
|
||||
(dwsl/create-layout-from-selection :flex)]
|
||||
done
|
||||
(fn [state]
|
||||
(let [file' (ths/get-file-from-state state)]
|
||||
(t/is (= 0 (count @commits)))
|
||||
(t/is (= (:parent-id nested)
|
||||
(:parent-id (cths/get-shape file' :nested))))
|
||||
(t/is (= (:parent-id outside)
|
||||
(:parent-id (cths/get-shape file' :outside))))))
|
||||
:commits commits))))
|
||||
|
||||
(t/deftest create-layout-of-nested-in-different-variants-does-not-commit
|
||||
(t/async
|
||||
done
|
||||
(let [file (variant-file-with-nested-in-each-main)
|
||||
nested-a (cths/get-shape file :nested-a)
|
||||
nested-b (cths/get-shape file :nested-b)
|
||||
commits (atom [])]
|
||||
(run file
|
||||
[(dws/select-shapes (d/ordered-set (cthi/id :nested-a) (cthi/id :nested-b)))
|
||||
(dwsl/create-layout :flex)]
|
||||
done
|
||||
(fn [state]
|
||||
(let [file' (ths/get-file-from-state state)]
|
||||
(t/is (= 0 (count @commits)))
|
||||
(t/is (= (:parent-id nested-a)
|
||||
(:parent-id (cths/get-shape file' :nested-a))))
|
||||
(t/is (= (:parent-id nested-b)
|
||||
(:parent-id (cths/get-shape file' :nested-b))))))
|
||||
:commits commits))))
|
||||
|
||||
(t/deftest group-shapes-of-variant-container-only-does-not-commit
|
||||
(t/async
|
||||
done
|
||||
(let [file (variant-file)
|
||||
board (cths/get-shape file :variant)
|
||||
commits (atom [])]
|
||||
(run file
|
||||
[(dwgr/group-shapes nil [(cthi/id :variant)])]
|
||||
done
|
||||
(fn [state]
|
||||
(let [file' (ths/get-file-from-state state)]
|
||||
(t/is (= 0 (count @commits)))
|
||||
(t/is (empty? (groups file')))
|
||||
(t/is (= (:parent-id board)
|
||||
(:parent-id (cths/get-shape file' :variant))))
|
||||
(t/is (ctk/is-variant-container?
|
||||
(cths/get-shape file' :variant)))))
|
||||
:commits commits))))
|
||||
|
||||
(t/deftest mask-group-of-variant-container-only-does-not-commit
|
||||
(t/async
|
||||
done
|
||||
(let [file (variant-file)
|
||||
board (cths/get-shape file :variant)
|
||||
commits (atom [])]
|
||||
(run file
|
||||
[(dwgr/mask-group [(cthi/id :variant)])]
|
||||
done
|
||||
(fn [state]
|
||||
(let [file' (ths/get-file-from-state state)]
|
||||
(t/is (= 0 (count @commits)))
|
||||
(t/is (empty? (masked-groups file')))
|
||||
(t/is (= (:parent-id board)
|
||||
(:parent-id (cths/get-shape file' :variant))))
|
||||
(t/is (ctk/is-variant-container?
|
||||
(cths/get-shape file' :variant)))))
|
||||
:commits commits))))
|
||||
|
||||
(t/deftest create-artboard-of-variant-container-only-does-not-commit
|
||||
(t/async
|
||||
done
|
||||
(let [file (variant-file)
|
||||
board (cths/get-shape file :variant)
|
||||
commits (atom [])]
|
||||
(run file
|
||||
[(dws/select-shapes (d/ordered-set (cthi/id :variant)))
|
||||
(dwsh/create-artboard-from-selection)]
|
||||
done
|
||||
(fn [state]
|
||||
(let [file' (ths/get-file-from-state state)]
|
||||
(t/is (= 0 (count @commits)))
|
||||
(t/is (= (:parent-id board)
|
||||
(:parent-id (cths/get-shape file' :variant))))
|
||||
(t/is (ctk/is-variant-container?
|
||||
(cths/get-shape file' :variant)))))
|
||||
:commits commits))))
|
||||
|
||||
(t/deftest mask-group-of-two-nested-children-creates-a-mask
|
||||
(t/async
|
||||
done
|
||||
(let [file (variant-file-with-two-nested)
|
||||
main (cths/get-shape file :root1)]
|
||||
(run file
|
||||
[(dwgr/mask-group [(cthi/id :nested-a) (cthi/id :nested-b)])]
|
||||
done
|
||||
(fn [state]
|
||||
(let [file' (ths/get-file-from-state state)
|
||||
group (first (masked-groups file'))]
|
||||
(t/is (= 1 (count (masked-groups file'))))
|
||||
(t/is (:masked-group group))
|
||||
(t/is (= (:id main) (:parent-id group)))
|
||||
(t/is (= (:id group) (:parent-id (cths/get-shape file' :nested-a))))
|
||||
(t/is (= (:id group) (:parent-id (cths/get-shape file' :nested-b))))))))))
|
||||
|
||||
(t/deftest create-layout-of-variant-container-only-does-not-commit
|
||||
(t/async
|
||||
done
|
||||
(let [file (variant-file)
|
||||
board (cths/get-shape file :variant)
|
||||
commits (atom [])]
|
||||
(t/is (not (ctl/any-layout? board)))
|
||||
(run file
|
||||
[(dws/select-shapes (d/ordered-set (cthi/id :variant)))
|
||||
;; Pin the create-layout entry (from-id hole), not only
|
||||
;; create-layout-from-selection which already no-ops.
|
||||
(dwsl/create-layout :flex)]
|
||||
done
|
||||
(fn [state]
|
||||
(let [file' (ths/get-file-from-state state)
|
||||
board' (cths/get-shape file' :variant)]
|
||||
(t/is (= 0 (count @commits)))
|
||||
(t/is (not (ctl/any-layout? board')))
|
||||
(t/is (ctk/is-variant-container? board'))
|
||||
(t/is (= (:parent-id board) (:parent-id board')))))
|
||||
:commits commits))))
|
||||
|
||||
(t/deftest create-layout-of-variant-main-commits
|
||||
(t/async
|
||||
done
|
||||
(let [file (variant-file)
|
||||
main (cths/get-shape file :root1)
|
||||
board (cths/get-shape file :variant)
|
||||
commits (atom [])]
|
||||
(t/is (not (ctl/any-layout? main)))
|
||||
(run file
|
||||
[(dws/select-shapes (d/ordered-set (cthi/id :root1)))
|
||||
;; Locks create-layout → create-layout-from-id for a single main.
|
||||
(dwsl/create-layout :flex)]
|
||||
done
|
||||
(fn [state]
|
||||
(let [file' (ths/get-file-from-state state)
|
||||
main' (cths/get-shape file' :root1)]
|
||||
(t/is (pos? (count @commits)))
|
||||
(t/is (ctl/flex-layout? main'))
|
||||
(t/is (ctk/is-variant? main'))
|
||||
(t/is (= (:variant-id main) (:variant-id main')))
|
||||
(t/is (= (:id board) (:parent-id main')))))
|
||||
:commits commits))))
|
||||
|
||||
(t/deftest create-layout-of-one-nested-child-commits
|
||||
(t/async
|
||||
done
|
||||
(let [file (-> (variant-file)
|
||||
(cths/add-sample-shape :nested :parent-label :root1))
|
||||
nested (cths/get-shape file :nested)
|
||||
main (cths/get-shape file :root1)
|
||||
commits (atom [])]
|
||||
(run file
|
||||
[(dws/select-shapes (d/ordered-set (cthi/id :nested)))
|
||||
;; Same Shift+A / menu entry as the container refuse and main allow.
|
||||
(dwsl/create-layout :flex)]
|
||||
done
|
||||
(fn [state]
|
||||
(let [file' (ths/get-file-from-state state)
|
||||
nested' (cths/get-shape file' :nested)
|
||||
;; Layout wraps the child in a new board under the main.
|
||||
parent' (get (:objects (cthf/current-page file'))
|
||||
(:parent-id nested'))]
|
||||
(t/is (pos? (count @commits)))
|
||||
(t/is (not= (:parent-id nested) (:parent-id nested')))
|
||||
(t/is (= (:id main) (:parent-id parent')))
|
||||
(t/is (ctl/any-layout? parent'))))
|
||||
:commits commits))))
|
||||
|
||||
(t/deftest ungroup-shapes-leaves-a-variant-container
|
||||
(t/async
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user