diff --git a/common/src/app/common/test_helpers/composable/comp/nodes.cljc b/common/src/app/common/test_helpers/composable/comp/nodes.cljc index 93322d78a2..e1b12558c9 100644 --- a/common/src/app/common/test_helpers/composable/comp/nodes.cljc +++ b/common/src/app/common/test_helpers/composable/comp/nodes.cljc @@ -96,7 +96,10 @@ :fixed-scroll :constraints-h :constraints-v :layout-item-margin :layout-gap :layout-padding :layout-align-items :layout-flex-dir :layout-wrap-type :layout-justify-content :layout-align-content - :layout-gap-type :layout-padding-type}) + :layout-gap-type :layout-padding-type :layout-justify-items + :layout-item-margin-type :layout-item-align-self :layout-item-h-sizing + :layout-item-v-sizing :layout-item-min-w :layout-item-max-w + :layout-item-min-h :layout-item-max-h :layout-item-absolute :layout-item-z-index}) (defn set-property "`shape` with `property` set to `value`. Public so the frontend interpreter @@ -140,7 +143,7 @@ (def ^:private layout-container-properties #{:layout-gap :layout-padding :layout-align-items :layout-flex-dir :layout-wrap-type :layout-justify-content :layout-align-content - :layout-gap-type :layout-padding-type}) + :layout-gap-type :layout-padding-type :layout-justify-items}) (defn- with-layout-reflow [changes id] @@ -186,7 +189,7 @@ value' (applied-value* property value) layout-id (cond (and (:layout shape) (contains? layout-container-properties property)) shape-id - (and (= :layout-item-margin property) + (and (contains? ctl/layout-child-attrs property) (:layout (get-in page [:objects (:parent-id shape)]))) (:parent-id shape)) changes (cond-> (cls/generate-update-shapes (pcb/empty-changes nil (:id page)) @@ -194,6 +197,11 @@ (fn [shape] (set-property shape property value')) (:objects page) {}) + (and (= :layout-item-absolute property) + (ctl/grid-layout? (get-in page [:objects layout-id]))) + (pcb/update-shapes [layout-id] ctl/assign-cells + {:with-objects? true :ignore-touched true}) + layout-id (-> (with-layout-reflow layout-id) (assoc ::layout-reflow? true)) diff --git a/common/src/app/common/test_helpers/composable/gen.cljc b/common/src/app/common/test_helpers/composable/gen.cljc index 912a0b6d2a..4ec308b1c7 100644 --- a/common/src/app/common/test_helpers/composable/gen.cljc +++ b/common/src/app/common/test_helpers/composable/gen.cljc @@ -87,6 +87,17 @@ (tm/apply-to (n/change-property parent :layout-padding {:p1 3 :p2 6 :p3 9 :p4 12})) (#(with-settled-layout % name))))) +(defn- with-layout-item-margins + [situation name] + (let [ids (:shapes (tm/shape-by-id situation ((n/main-head-of name) situation)))] + (-> (reduce (fn [s id] + (->> s + (tm/apply-to (n/change-property (constantly id) :layout-item-margin + {:m1 2 :m2 4 :m3 6 :m4 8})) + (tm/apply-to (n/change-property (constantly id) :layout-item-margin-type :multiple)))) + situation ids) + (with-settled-layout name)))) + (defn- with-outer-layout [situation name layout] (if-not layout @@ -201,6 +212,8 @@ (with-layout name (when (zero? index) (:layout opts))) (cond-> (and (zero? index) (:layout opts) (:layout-children opts)) (with-layout-children name)) + (cond-> (and (zero? index) (:layout opts) (:layout-item-margins opts)) + (with-layout-item-margins name)) (as-> s (reduce #(tm/apply-to %2 %1) s [(n/make-nested-component name depth)])) (with-outer-layout name (when (and (zero? index) (pos? depth) (:layout-copies opts)) (:layout opts))) @@ -432,7 +445,18 @@ [:layout-justify-content [:start :space-between :center :end :space-around :space-evenly]] [:layout-align-content [:stretch :center :end :start :space-between :space-around :space-evenly]] [:layout-gap-type [:multiple :simple]] - [:layout-padding-type [:simple :multiple]]]) + [:layout-padding-type [:simple :multiple]] + [:layout-justify-items [:start :center :end :stretch]] + [:layout-item-margin-type [:simple :multiple]] + [:layout-item-align-self [:start :center :end :stretch]] + [:layout-item-h-sizing [:fix :fill]] + [:layout-item-v-sizing [:fix :fill]] + [:layout-item-min-w [0 24 64]] + [:layout-item-max-w [256 96 64]] + [:layout-item-min-h [0 24 64]] + [:layout-item-max-h [256 96 64]] + [:layout-item-absolute [false true]] + [:layout-item-z-index [0 2 -1]]]) (def text-style-edits "All twelve style units, independent of the production text group table." @@ -480,7 +504,14 @@ :layout-gap-type :layout-padding-type :layout-justify-content :layout-align-content) (some? (:layout shape)) (:layout-flex-dir :layout-wrap-type) (= :flex (:layout shape)) - :layout-item-margin (some? (:layout (tm/shape-by-id situation (:parent-id shape)))) + :layout-justify-items (= :grid (:layout shape)) + :layout-item-align-self (= :flex (:layout (tm/shape-by-id situation (:parent-id shape)))) + (:layout-item-margin :layout-item-margin-type + :layout-item-h-sizing :layout-item-v-sizing + :layout-item-min-w :layout-item-max-w + :layout-item-min-h :layout-item-max-h + :layout-item-absolute :layout-item-z-index) + (some? (:layout (tm/shape-by-id situation (:parent-id shape)))) true)))) (and (= kind :edit) (= 2 (mod (or property 0) 3))) (remove (fn [{:keys [shape]}] @@ -637,7 +668,7 @@ (shadow/parent-orientation (all-objects situation) (all-objects settled)) (shadow/settle (all-objects settled))) all (into (set (synchronizable-components situation)) (r/pending-components situation)) - full-before (:situation (cf/sync-world situation all)) + full-before (r/settle situation all) full (:situation (cf/sync-world settled all)) opts {:free-head-placement false} settled-design (oracle/canonical settled opts) @@ -1023,8 +1054,9 @@ depth (gen/choose 0 2) layout (gen/elements [nil :flex :grid]) layout-children gen/boolean + layout-item-margins gen/boolean ops (operation-gen structure-kinds)] - {:world [components depth {:text true :text-styles true :layout layout :layout-children layout-children}] + {:world [components depth {:text true :text-styles true :layout layout :layout-children layout-children :layout-item-margins layout-item-margins}] :ops ops})) (def swap-kinds (into structure-kinds [:swap :switch])) @@ -1034,10 +1066,11 @@ differ? gen/boolean layout (gen/elements [nil :flex :grid]) layout-children gen/boolean + layout-item-margins gen/boolean ops (operation-gen swap-kinds)] {:world [components 2 {:variants true :variant-diff differ? :rich-variants true :text true :text-styles true :layout layout :layout-copies true - :layout-children layout-children}] + :layout-children layout-children :layout-item-margins layout-item-margins}] :ops ops})) (def library-kinds @@ -1047,9 +1080,10 @@ (gen/let [depth (gen/choose 0 2) layout (gen/elements [nil :flex :grid]) layout-children gen/boolean + layout-item-margins gen/boolean ops (operation-gen library-kinds)] {:world [2 depth {:library true :variants true :rich-variants true :text true :text-styles true - :layout layout :layout-copies true :layout-children layout-children}] + :layout layout :layout-copies true :layout-children layout-children :layout-item-margins layout-item-margins}] :ops ops})) (def stale-library-kinds @@ -1060,10 +1094,11 @@ library-depth (gen/choose 1 2) layout (gen/elements [nil :flex :grid]) layout-children gen/boolean + layout-item-margins gen/boolean ops (operation-gen stale-library-kinds)] {:world [2 depth {:library true :stale-libraries true :library-variants true :library-text true :library-depth library-depth :variants true :rich-variants true :text true :text-styles true - :layout layout :layout-copies true :layout-children layout-children}] + :layout layout :layout-copies true :layout-children layout-children :layout-item-margins layout-item-margins}] :ops ops})) (def dependency-kinds diff --git a/common/src/app/common/test_helpers/composable/shadow.cljc b/common/src/app/common/test_helpers/composable/shadow.cljc index 53385effff..cd9ea53862 100644 --- a/common/src/app/common/test_helpers/composable/shadow.cljc +++ b/common/src/app/common/test_helpers/composable/shadow.cljc @@ -28,7 +28,14 @@ :layout-padding :layout-padding :layout-align-items :layout-align-items :layout-flex-dir :layout-flex-dir :layout-wrap-type :layout-wrap-type :layout-justify-content :layout-justify-content :layout-align-content :layout-align-content - :layout-gap-type :layout-gap :layout-padding-type :layout-padding}) + :layout-gap-type :layout-gap :layout-padding-type :layout-padding + :layout-justify-items :layout-justify-items + :layout-item-margin-type :layout-item-margin + :layout-item-align-self :layout-item-align-self + :layout-item-h-sizing :layout-item-h-sizing :layout-item-v-sizing :layout-item-v-sizing + :layout-item-min-w :layout-item-min-w :layout-item-max-w :layout-item-max-w + :layout-item-min-h :layout-item-min-h :layout-item-max-h :layout-item-max-h + :layout-item-absolute :layout-item-absolute :layout-item-z-index :layout-item-z-index}) (defn attribute-applicable? "The clipping and background-export controls belong to frames." diff --git a/common/test/common_tests/logic/composable_gen_test.cljc b/common/test/common_tests/logic/composable_gen_test.cljc index 8cd59a3fc3..1df835ffaf 100644 --- a/common/test/common_tests/logic/composable_gen_test.cljc +++ b/common/test/common_tests/logic/composable_gen_test.cljc @@ -184,7 +184,51 @@ [22 :layout-justify-content :layout-justify-content #{:flex :grid}] [23 :layout-align-content :layout-align-content #{:flex :grid}] [24 :layout-gap-type :layout-gap #{:flex :grid}] - [25 :layout-padding-type :layout-padding #{:flex :grid}]]) + [25 :layout-padding-type :layout-padding #{:flex :grid}] + [26 :layout-justify-items :layout-justify-items #{:grid}]]) + +(def layout-item-cases + [[27 :layout-item-margin-type :layout-item-margin #{:flex :grid}] + [28 :layout-item-align-self :layout-item-align-self #{:flex}] + [29 :layout-item-h-sizing :layout-item-h-sizing #{:flex :grid}] + [30 :layout-item-v-sizing :layout-item-v-sizing #{:flex :grid}] + [31 :layout-item-min-w :layout-item-min-w #{:flex :grid}] + [32 :layout-item-max-w :layout-item-max-w #{:flex :grid}] + [33 :layout-item-min-h :layout-item-min-h #{:flex :grid}] + [34 :layout-item-max-h :layout-item-max-h #{:flex :grid}] + [35 :layout-item-absolute :layout-item-absolute #{:flex :grid}] + [36 :layout-item-z-index :layout-item-z-index #{:flex :grid}]]) + +(t/deftest remaining-layout-inputs-resolve-only-in-their-own-context + (doseq [layout [nil :flex :grid] + [property attr _ layouts] (into [[26 :layout-justify-items :layout-justify-items #{:grid}]] + layout-item-cases)] + (let [situation (cg/world [1 0 {:layout layout :layout-children true}]) + resolved (cg/resolve-op situation {} [:style 0 property 1 1]) + parent (tm/shape-by-id situation (get-in resolved [:shape :parent-id]))] + (t/is (= attr (:property resolved))) + (if (contains? layouts layout) + (do + (t/is (some? (:op resolved))) + (t/is (not= (get (:shape resolved) attr) (:value resolved))) + (t/is (= layout (if (= property 26) (get-in resolved [:shape :layout]) (:layout parent))))) + (do + (t/is (nil? (:op resolved))) + (t/is (= :skip (get-in resolved [:trace :status])))))))) + +(t/deftest asymmetric-item-margins-are-settled-before-copying + (doseq [layout [:flex :grid]] + (let [situation (cg/world [1 0 {:layout layout :layout-children true :layout-item-margins true}]) + main-id ((n/main-rect-of "random-0") situation) + images (filter #(or (= main-id (get-in % [:shape :id])) + (= main-id (get-in % [:shape :shape-ref]))) (cg/tree situation))] + (t/is (= 3 (count images))) + (doseq [{:keys [shape]} images] + (t/is (= {:m1 2 :m2 4 :m3 6 :m4 8} (:layout-item-margin shape))) + (t/is (= :multiple (:layout-item-margin-type shape)))) + (t/is (= :valid (try (oracle/check situation {}) :valid + (catch #?(:clj Exception :cljs js/Error) error + (select-keys (ex-data error) [:invariant :group])))))))) (t/deftest multi-child-layout-fixtures-start-settled-before-user-actions (doseq [layout [:flex :grid] depth [0 1]] @@ -309,6 +353,94 @@ (t/is (pos? (get-in result [:coverage :random kind :changed] 0)) (str kind " changes " property " in " layout)))))) +(t/deftest adding-a-flex-child-before-making-a-copy-child-absolute-stays-settled + (t/is (map? (cg/run {:world [1 0 {:layout :flex :layout-children true}] + :ops [[:add-child 0] [:style 2 35 1 1]]})))) + +(t/deftest full-settlement-comparison-still-rejects-absolute-child-drift + (let [initial (-> (cg/initial-state [1 0 {:layout :flex :layout-children true}]) + (cg/step [:add-child 0]) cg/settle-state) + id (get-in (cg/resolve-op (:situation initial) {} [:style 2 35 1 1]) [:shape :id]) + edited (cg/step initial [:style 2 35 1 1]) + settled (assoc edited :situation (r/settle (:situation edited)) :checkpoint nil) + settle r/settle + corrupt-full (fn [situation component-ids] + (let [result (settle situation component-ids) + file (tm/file result) + path [:data :pages-index (:id (thf/current-page file)) :objects id]] + (tm/with-file result (update-in file path gsh/move (gpt/point 0.25 0))))) + invariant (with-redefs [r/settle (fn + ([situation] (settle situation (r/pending-components situation))) + ([situation ids] (corrupt-full situation ids)))] + (try (cg/settle-state settled) nil + (catch #?(:clj Exception :cljs js/Error) error + (:invariant (ex-data error)))))] + (t/is (= :I6 invariant)))) + +(t/deftest layout-item-overrides-retain-values-and-companions + (doseq [[property attr group layouts] layout-item-cases + layout (sort layouts)] + (t/testing (str attr " in " layout) + (let [initial (cg/initial-state [1 0 {:layout layout :layout-children true :layout-item-margins true}]) + situation (:situation initial) + authored (if (= property 27) 0 1) + other (if (= property 27) 1 0) + resolved (cg/resolve-op situation {} [:style 0 property authored 1]) + shape (:shape resolved) + id (:id shape) + expected (if (= property 27) + {:layout-item-margin-type :simple :layout-item-margin {:m1 2 :m2 4 :m3 6 :m4 8}} + {attr (:value resolved)}) + edited (-> initial (cg/step [:style 0 property authored 1]) cg/settle-state) + after (:situation edited) + file (tm/file after) + path [:data :pages-index (:id (thf/current-page file)) :objects id] + invariant (fn [file] + (try (oracle/check (tm/with-file after file) (:model edited)) nil + (catch #?(:clj Exception :cljs js/Error) error + (:invariant (ex-data error))))) + reverted (cond-> (if (contains? shape attr) + (assoc-in file (conj path attr) (get shape attr)) + (update-in file path dissoc attr)) + (and (= property 35) (= layout :grid)) + (assoc-in (conj (pop path) (:parent-id shape) :layout-grid-cells) + (:layout-grid-cells (tm/shape-by-id situation (:parent-id shape))))) + main-index (first (keep (fn [index] + (when (= (:shape-ref shape) + (get-in (cg/resolve-op situation {} [:style index property other 0]) + [:shape :id])) index)) + (range (count (cg/tree situation))))) + updated (-> edited (cg/step [:style main-index property authored 0]) cg/settle-state + (cg/step [:style main-index property other 0]) cg/settle-state) + value (:value (cg/resolve-op situation {} [:style main-index property other 0])) + copies (filter #(= (:shape-ref shape) (get-in % [:shape :shape-ref])) (cg/tree situation))] + (t/is (= expected (get-in edited [:model id group]))) + (t/is (= #{group} (:touched (tm/shape-by-id after id)))) + (t/is (= (:layout-item-margin shape) (:layout-item-margin (tm/shape-by-id after id)))) + (t/is (= :I9 (invariant reverted))) + (t/is (= :I3 (invariant (update-in file (conj path :touched) disj group)))) + (t/is (some? main-index)) + (t/is (= 2 (count copies))) + (doseq [{copy :shape} copies] + (let [actual (tm/shape-by-id (:situation updated) (:id copy))] + (t/is (= (if (= id (:id copy)) (:value resolved) value) (get actual attr))) + (t/is (= (if (= id (:id copy)) #{group} #{}) (set (:touched actual)))))) + (t/is (= expected (get-in updated [:model id group]))))))) + +(t/deftest layout-item-histories-survive-component-operations + (doseq [[property _ _ layouts] layout-item-cases + layout (sort layouts)] + (let [authored (if (= property 27) 0 1) + other (if (= property 27) 1 0) + result (cg/run {:world [1 0 {:layout layout :layout-children true :layout-item-margins true}] + :ops [[:style 0 property authored 1] [:style 0 property authored 0] + [:style 0 property other 0] [:undo] [:redo] [:update-main 0] + [:style 0 property other 1] [:reset 0] + [:style 0 property other 1] [:detach 0] [:undo] [:redo]]})] + (doseq [kind [:style :update-main :reset :detach]] + (t/is (pos? (get-in result [:coverage :random kind :changed] 0)) + (str kind " changes " property " in " layout)))))) + (t/deftest text-style-edits-cover-every-style-unit (doseq [[unit group] (map-indexed vector [:text-content-family :text-content-weight :text-content-size diff --git a/frontend/test/frontend_tests/composable_tests/generated_test.cljs b/frontend/test/frontend_tests/composable_tests/generated_test.cljs index 8a889422c6..f60cbbc709 100644 --- a/frontend/test/frontend_tests/composable_tests/generated_test.cljs +++ b/frontend/test/frontend_tests/composable_tests/generated_test.cljs @@ -90,7 +90,7 @@ (t/deftest ^:async layout-container-histories-survive-component-operations (doseq [[property layouts] [[20 [:flex]] [21 [:flex]] [22 [:flex :grid]] [23 [:flex :grid]] - [24 [:flex :grid]] [25 [:flex :grid]]] + [24 [:flex :grid]] [25 [:flex :grid]] [26 [:grid]]] layout layouts] (await (replay {:world [1 0 {:layout layout :layout-children true}] :ops [[:style 0 property 1 1] [:style 0 property 1 0] @@ -98,6 +98,21 @@ [:style 0 property 0 1] [:reset 0] [:style 0 property 0 1] [:detach 0] [:undo] [:redo]]})))) +(t/deftest ^:async layout-item-histories-survive-component-operations + (doseq [property (range 27 37) + layout (if (= property 28) [:flex] [:flex :grid])] + (let [authored (if (= property 27) 0 1) + other (if (= property 27) 1 0)] + (await (replay {:world [1 0 {:layout layout :layout-children true :layout-item-margins true}] + :ops [[:style 0 property authored 1] [:style 0 property authored 0] + [:style 0 property other 0] [:undo] [:redo] [:update-main 0] + [:style 0 property other 1] [:reset 0] + [:style 0 property other 1] [:detach 0] [:undo] [:redo]]}))))) + +(t/deftest ^:async adding-a-flex-child-before-making-a-copy-child-absolute-stays-settled + (await (replay {:world [1 0 {:layout :flex :layout-children true}] + :ops [[:add-child 0] [:style 2 35 1 1] [:undo] [:redo]]}))) + (t/deftest ^:async nesting-a-copy-alongside-a-hidden-layout-child-stays-settled (await (replay {:world [3 0 {:text true :text-styles true :layout :flex}] :ops [[:delete 9 9 1 2] [:text 5 9 1 3] [:style 3 1 1] diff --git a/frontend/test/frontend_tests/composable_tests/interpreter.cljs b/frontend/test/frontend_tests/composable_tests/interpreter.cljs index 46b91614fc..01707c10de 100644 --- a/frontend/test/frontend_tests/composable_tests/interpreter.cljs +++ b/frontend/test/frontend_tests/composable_tests/interpreter.cljs @@ -150,13 +150,20 @@ [(dwsl/update-layout-child [id] (select-keys shape [:layout-item-margin :layout-item-margin-type]))]) + (:layout-item-margin-type :layout-item-align-self + :layout-item-h-sizing :layout-item-v-sizing + :layout-item-min-w :layout-item-max-w + :layout-item-min-h :layout-item-max-h + :layout-item-absolute :layout-item-z-index) + [(dwsl/update-layout-child [id] {property value})] + ;; the layers panel's rename, which renames a main's component too :name [(dw/end-rename-shape id value)] ;; Container controls use the sidebar transaction and reflow event. (:layout-flex-dir :layout-wrap-type :layout-justify-content - :layout-align-content :layout-gap-type :layout-padding-type) + :layout-align-content :layout-gap-type :layout-padding-type :layout-justify-items) [(dwsl/update-layout [id] {property value})] [(dwsh/update-shapes #{id} (fn [shape] (n/set-property shape property value)))]))