From 9483d9f73754ddbe8cb10f544e351863b2fda695 Mon Sep 17 00:00:00 2001 From: "alonso.torres" Date: Wed, 28 Oct 2026 01:10:31 +0100 Subject: [PATCH] :sparkles: Generate shadow, blur and corner edits Add drop/inner shadows, layer and background blur, and the three remaining corner radii to generated style edits. Preserve the existing input order and describe their authored groups independently of production metadata. Check distinct inputs, main propagation, preserved overrides, corrupt values and lost touched groups. Run component update, reset, detach and exact undo/redo histories in common and the async frontend. The input test fails six assertions before the addition. Focused common checks pass 4 tests / 90 assertions; the six frontend histories pass 204 assertions. Full common JVM/JS, frontend, backend and exporter suites and lint/format checks pass, including six 40-program properties at seed 712. F227's parity correction is covered separately. Golden data and production behavior are unchanged. Wider attribute/layout and source-copy generation, pending-work coverage and the five fresh 5,000-program gates remain open. AI-assisted-by: GPT-6 --- .../test_helpers/composable/comp/nodes.cljc | 3 +- .../common/test_helpers/composable/gen.cljc | 23 ++++++- .../test_helpers/composable/shadow.cljc | 4 +- .../logic/composable_gen_test.cljc | 68 +++++++++++++++++++ .../composable_tests/generated_test.cljs | 9 +++ 5 files changed, 104 insertions(+), 3 deletions(-) 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 5c66625ce6..9d47e02b80 100644 --- a/common/src/app/common/test_helpers/composable/comp/nodes.cljc +++ b/common/src/app/common/test_helpers/composable/comp/nodes.cljc @@ -91,7 +91,8 @@ ;; --------------------------------------------------------------------------- (def scalar-properties - #{:strokes :hidden :blocked :r1 :blend-mode :hide-fill-on-export :show-content + #{:strokes :shadow :blur :background-blur :hidden :blocked :r1 :r2 :r3 :r4 + :blend-mode :hide-fill-on-export :show-content :fixed-scroll :constraints-h :constraints-v :layout-item-margin :layout-gap :layout-padding :layout-align-items}) diff --git a/common/src/app/common/test_helpers/composable/gen.cljc b/common/src/app/common/test_helpers/composable/gen.cljc index 3aab239e14..95486a9f27 100644 --- a/common/src/app/common/test_helpers/composable/gen.cljc +++ b/common/src/app/common/test_helpers/composable/gen.cljc @@ -373,7 +373,28 @@ [:layout-item-margin [{:m1 0 :m2 0 :m3 0 :m4 0} {:m1 4 :m2 4 :m3 4 :m4 4}]] [:layout-gap [{:row-gap 0 :column-gap 0} {:row-gap 8 :column-gap 8}]] [:layout-padding [{:p1 0 :p2 0 :p3 0 :p4 0} {:p1 8 :p2 8 :p3 8 :p4 8}]] - [:layout-align-items [:start :center :end]]]) + [:layout-align-items [:start :center :end]] + [:shadow [[] [{:id #uuid "10000000-0000-0000-0000-000000000001" + :style :drop-shadow :color {:color "#000000" :opacity 0.5} + :offset-x 2 :offset-y 3 :blur 4 :spread 1 :hidden false}] + [{:id #uuid "10000000-0000-0000-0000-000000000001" + :style :inner-shadow :color {:color "#ff0000" :opacity 0.75} + :offset-x -2 :offset-y 4 :blur 8 :spread 2 :hidden false}]]] + [:blur [{:id #uuid "10000000-0000-0000-0000-000000000002" + :type :layer-blur :value 0 :hidden true} + {:id #uuid "10000000-0000-0000-0000-000000000002" + :type :layer-blur :value 4 :hidden false} + {:id #uuid "10000000-0000-0000-0000-000000000002" + :type :layer-blur :value 12 :hidden false}]] + [:background-blur [{:id #uuid "10000000-0000-0000-0000-000000000003" + :type :background-blur :value 0 :hidden true} + {:id #uuid "10000000-0000-0000-0000-000000000003" + :type :background-blur :value 6 :hidden false} + {:id #uuid "10000000-0000-0000-0000-000000000003" + :type :background-blur :value 16 :hidden false}]] + [:r2 [0 8 16]] + [:r3 [0 8 16]] + [:r4 [0 8 16]]]) (def text-style-edits "All twelve style units, independent of the production text group table." diff --git a/common/src/app/common/test_helpers/composable/shadow.cljc b/common/src/app/common/test_helpers/composable/shadow.cljc index 0c008e23a1..fc0ec16218 100644 --- a/common/src/app/common/test_helpers/composable/shadow.cljc +++ b/common/src/app/common/test_helpers/composable/shadow.cljc @@ -19,7 +19,9 @@ (def groups {:fills :fill-group :opacity :layer-effects-group :name :name-group :strokes :stroke-group :hidden :visibility-group :blocked :modifiable-group - :r1 :radius-group :blend-mode :layer-effects-group :hide-fill-on-export :fill-group + :r1 :radius-group :r2 :radius-group :r3 :radius-group :r4 :radius-group + :shadow :shadow-group :blur :blur-group :background-blur :blur-group + :blend-mode :layer-effects-group :hide-fill-on-export :fill-group :show-content :show-content :fixed-scroll :constraints-group :constraints-h :constraints-group :constraints-v :constraints-group :layout-item-margin :layout-item-margin :layout-gap :layout-gap diff --git a/common/test/common_tests/logic/composable_gen_test.cljc b/common/test/common_tests/logic/composable_gen_test.cljc index 0d06a59425..5018c66e93 100644 --- a/common/test/common_tests/logic/composable_gen_test.cljc +++ b/common/test/common_tests/logic/composable_gen_test.cljc @@ -85,6 +85,74 @@ (t/is (= :frame (get-in resolved [:shape :type])) (str "property " property ", scope " scope)))))) +(t/deftest generated-effect-and-corner-edits-have-distinct-inputs + (let [situation (cg/world [2 1 {}])] + (doseq [[index attr] [[14 :shadow] [15 :blur] [16 :background-blur] + [17 :r2] [18 :r3] [19 :r4]]] + (let [resolved (cg/resolve-op situation {} [:style 0 index 1 1])] + (t/is (= attr (:property resolved))) + (t/is (some? (:op resolved))) + (when (= attr (:property resolved)) + (t/is (not= (get (:shape resolved) attr) (:value resolved)))))))) + +(t/deftest effect-and-corner-overrides-reject-lost-values-or-groups + (doseq [[index attr group] [[14 :shadow :shadow-group] [15 :blur :blur-group] + [16 :background-blur :blur-group] [17 :r2 :radius-group] + [18 :r3 :radius-group] [19 :r4 :radius-group]]] + (let [initial (cg/initial-state [1 0 {}]) + resolved (cg/resolve-op (:situation initial) {} [:style 0 index 1 1]) + shape (:shape resolved) + expected (shadow/values (assoc shape attr (:value resolved)) group) + edited (-> initial (cg/step [:style 0 index 1 1]) cg/settle-state) + situation (:situation edited) + model (:model edited) + file (tm/file situation) + path [:data :pages-index (:id (thf/current-page file)) :objects (:id shape)] + invariant (fn [file] + (try (oracle/check (tm/with-file situation file) model) nil + (catch #?(:clj Exception :cljs js/Error) error + (:invariant (ex-data error)))))] + (t/is (= expected (get-in model [(:id shape) group]))) + (t/is (= :I9 (invariant (update-in file path + #(merge (dissoc % attr) (select-keys shape [attr])))))) + (t/is (= :I3 (invariant (update-in file (conj path :touched) disj group))))))) + +(t/deftest main-effect-and-corner-edits-reach-clean-copies-and-spare-overrides + (doseq [property (range 14 20)] + (let [initial (cg/initial-state [1 0 {}]) + situation (:situation initial) + resolved (cg/resolve-op situation {} [:style 0 property 1 1]) + copy (:shape resolved) + attr (:property resolved) + main-index (first (keep-indexed (fn [index entry] + (when (= (:shape-ref copy) (get-in entry [:shape :id])) index)) + (cg/candidates situation {} :edit 0))) + main-edit [:style main-index property 0 0] + copies (filter #(= (:shape-ref copy) (get-in % [:shape :shape-ref])) (cg/tree situation)) + inherited (-> initial (cg/step main-edit) cg/settle-state) + overridden (-> inherited (cg/step [:style 0 property 1 1]) cg/settle-state) + updated (-> overridden (cg/step (assoc main-edit 3 2)) cg/settle-state) + requested (:value (cg/resolve-op situation {} (assoc main-edit 3 2)))] + (t/is (some? main-index)) + (t/is (= 2 (count copies))) + (t/is (not= requested (:value resolved))) + (doseq [{:keys [shape]} copies] + (let [actual (tm/shape-by-id (:situation updated) (:id shape))] + (t/is (= (if (= (:id copy) (:id shape)) (:value resolved) requested) (get actual attr))) + (t/is (= (= (:id copy) (:id shape)) (boolean (seq (:touched actual))))))) + (t/is (map? (cg/run {:world [1 0 {}] + :ops [main-edit [:style 0 property 1 1] (assoc main-edit 3 2) + [:undo] [:redo]]})))))) + +(t/deftest generated-effects-and-corners-survive-component-operations + (doseq [property (range 14 20)] + (t/is (map? (cg/run {:world [2 1 {:variants true :rich-variants true}] + :ops [[:style 0 property 1 1] [:style 1 property 1 0] + [:undo] [:redo] [:update-main 0] + [:style 0 property 0 1] [:reset 0] + [:style 0 property 1 1] [:detach 0] + [:undo] [:redo]]}))))) + (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 8a6b69a191..9652362516 100644 --- a/frontend/test/frontend_tests/composable_tests/generated_test.cljs +++ b/frontend/test/frontend_tests/composable_tests/generated_test.cljs @@ -93,6 +93,15 @@ :ops [[:delete 9 9 1 2] [:text 5 9 1 3] [:style 3 1 1] [:restore 5 8 1 6] [:nest 5 6 0 2] [:undo] [:redo]]}))) +(t/deftest ^:async generated-effects-and-corners-survive-component-operations + (doseq [property (range 14 20)] + (await (replay {:world [2 1 {:variants true :rich-variants true}] + :ops [[:style 0 property 1 1] [:style 1 property 1 0] + [:undo] [:redo] [:update-main 0] + [:style 0 property 0 1] [:reset 0] + [:style 0 property 1 1] [:detach 0] + [:undo] [:redo]]})))) + (t/deftest ^:async swapping-a-copy-with-authored-position-constraints-stays-settled (await (replay {:world [2 1 {:library true :variants true :rich-variants true :text true :text-styles true :layout :flex :layout-copies true}]