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 e1b12558c9..341d115403 100644 --- a/common/src/app/common/test_helpers/composable/comp/nodes.cljc +++ b/common/src/app/common/test_helpers/composable/comp/nodes.cljc @@ -221,6 +221,39 @@ [target property value] (tm/assign-id (->ChangeProperty target property value))) +(defrecord EditGrid [target action value] + IComponentOperation + (op-kind [_] :user) + + tm/IOperation + (apply-to [this situation] + (let [file (tm/file situation) + page (thf/current-page file) + id (tm/target-shape-id situation target) + edit (fn [shape] + (case action + :direction (let [from (or (:layout-grid-dir shape) :row)] + (if (= from value) + (assoc shape :layout-grid-dir value) + (ctl/reflow-grid-auto-items-for-direction shape from value))) + :cell (update shape :layout-grid-cells + (fn [cells] + (update-vals cells #(if (= [(:row value) (:column value)] [(:row %) (:column %)]) + (merge % (:props value)) %)))))) + changes (-> (cls/generate-update-shapes (pcb/empty-changes nil (:id page)) [id] + edit (:objects page) + {:skip-grid-reassignment? (= action :direction)}) + (with-layout-reflow id) + (assoc ::layout-reflow? true))] + (-> situation + (tm/with-file (thf/apply-changes file changes)) + (record-changes changes) + (tm/record-application this {:target target :action action :value value}))))) + +(defn edit-grid + [target action value] + (tm/assign-id (->EditGrid target action value))) + (defprotocol IPropertyCheck "The check dual of an operation that changes a property: whether a shape the test supplies holds the value the operation set." diff --git a/common/src/app/common/test_helpers/composable/gen.cljc b/common/src/app/common/test_helpers/composable/gen.cljc index 4ec308b1c7..7365d06f29 100644 --- a/common/src/app/common/test_helpers/composable/gen.cljc +++ b/common/src/app/common/test_helpers/composable/gen.cljc @@ -24,6 +24,7 @@ [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.composable.grid :as grid] [app.common.test-helpers.composable.oracle :as oracle] [app.common.test-helpers.composable.shadow :as shadow] [app.common.test-helpers.composable.variants :as variants] @@ -98,6 +99,23 @@ situation ids) (with-settled-layout name)))) +(defn- with-square-grid + [situation name] + (let [id ((n/main-head-of name) situation) + shape (tm/shape-by-id situation id) + children (vec (reverse (:shapes shape))) + cells (into {} (map-indexed (fn [index [row column]] + (let [id (uuid/next)] + [id {:id id :row row :column column :row-span 1 :column-span 1 + :position :auto :align-self :auto :justify-self :auto + :shapes (if-let [child (get children index)] [child] [])}])) + [[1 1] [1 2] [2 1] [2 2]])) + file (-> (tm/file situation) + (ths/update-shape-by-id (:id shape) :layout-grid-rows [{:type :fixed :value 60} {:type :fixed :value 60}]) + (ths/update-shape-by-id (:id shape) :layout-grid-columns [{:type :fixed :value 80} {:type :fixed :value 80}]) + (ths/update-shape-by-id (:id shape) :layout-grid-cells cells))] + (with-settled-layout (tm/with-file situation file) name))) + (defn- with-outer-layout [situation name layout] (if-not layout @@ -214,6 +232,8 @@ (with-layout-children name)) (cond-> (and (zero? index) (:layout opts) (:layout-item-margins opts)) (with-layout-item-margins name)) + (cond-> (and (zero? index) (= :grid (:layout opts)) (:grid-square opts)) + (with-square-grid 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))) @@ -473,6 +493,10 @@ [:direction ["ltr" "rtl"]] [:valign ["top" "bottom"]]]) +(def grid-cell-edits + [{:align-self :end} {:justify-self :center} {:align-self :center :justify-self :end} + {:align-self :auto :justify-self :auto}]) + (defn- edit-text-style [shape unit value] (let [[style values] (nth text-style-edits (mod (or unit 0) (count text-style-edits))) @@ -495,7 +519,10 @@ [style-property style-values] (nth style-edits (mod (or property 0) (count style-edits))) items (cond->> (if (contains? library-source-kinds kind) (library-candidates situation kind scope) - (candidates situation model (if (= kind :style) :edit kind) scope)) + (candidates situation model (if (#{:style :grid-cell :grid-direction} kind) :edit kind) scope)) + (#{:grid-cell :grid-direction} kind) + (filter #(and (= :grid (get-in % [:shape :layout])) + (seq (get-in % [:shape :layout-grid-cells])))) (= kind :style) (filter (fn [{:keys [shape]}] (and (shadow/attribute-applicable? shape style-property) @@ -543,6 +570,11 @@ (when target (case kind (:edit :style) (n/change-property target property value) + :grid-direction (n/edit-grid target :direction (nth [:row :column] (mod (or (nth tuple 3 nil) 0) 2))) + :grid-cell (let [cells (grid/get-cells (:shape selected)) + cell (nth cells (mod (or (nth tuple 2 nil) 0) (count cells))) + props (nth grid-cell-edits (mod (or (nth tuple 3 nil) 0) (count grid-cell-edits)))] + (n/edit-grid target :cell (assoc (select-keys cell [:row :column]) :props props))) (:transform :library-transform) (let [action (nth [:move :width :height :rotate :flip-x :flip-y] (mod (or (nth tuple 2 nil) 0) 6)) value (or (nth tuple 3 nil) 0)] @@ -747,6 +779,7 @@ id (:id shape)] (case kind (:edit :style) (shadow/edit model shape property value) + (:grid-cell :grid-direction) (grid/edit model shape (:action op) (:value op)) :reset (shadow/clear-subtree model objects id) :update-main (shadow/update-main model objects (all-objects after) id) :detach (shadow/detach model objects (all-objects after) id) @@ -842,6 +875,9 @@ (oracle/check-requested-geometry (:objects (thf/current-page (tm/file after))) (tm/shape-by-id after (get-in resolved [:shape :id])) (requested-geometry before resolved kind)))) + (when (#{:grid-cell :grid-direction} kind) + (oracle/check-requested-grid (tm/shape-by-id after (get-in resolved [:shape :id])) + (:shape resolved) (:action op) (:value op))) (when (#{:swap :switch} kind) (let [head (tm/shape-by-id after (get-in resolved [:shape :id]))] (when-not (= (get-in resolved [:destination :component-id]) (:component-id head)) diff --git a/common/src/app/common/test_helpers/composable/grid.cljc b/common/src/app/common/test_helpers/composable/grid.cljc new file mode 100644 index 0000000000..d118e42dff --- /dev/null +++ b/common/src/app/common/test_helpers/composable/grid.cljc @@ -0,0 +1,105 @@ +;; 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 app.common.test-helpers.composable.grid + "Independent cell expectations. No production grid assignment or direction + helper supplies expected values.") + +(defn get-cells + [shape] + (->> (vals (:layout-grid-cells shape)) + (sort-by (juxt :row :column)) + (mapv #(dissoc % :id)))) + +(defn remap-groups + [groups ids-map] + (cond-> groups + (:layout-grid-cells groups) + (update-in [:layout-grid-cells :layout-grid-cells] + (fn [cells] + (mapv #(update % :shapes (fn [ids] (mapv (fn [id] (get ids-map id id)) ids))) cells))))) + +(defn get-child-map + "Pair identities through refs, never through resulting cell membership." + [objects source target] + (let [source-ids (set (:shapes source)) + target-ids (set (:shapes target)) + find-ref (fn [ids id] + (loop [candidate id seen #{}] + (cond + (contains? ids candidate) candidate + (or (nil? candidate) (contains? seen candidate)) nil + :else (recur (:shape-ref (get objects candidate)) (conj seen candidate))))) + forward (into {} (keep #(when-let [id (find-ref target-ids %)] [% id])) (:shapes source)) + backward (into {} (keep #(when-let [id (find-ref source-ids %)] [id %])) (:shapes target))] + (merge forward backward))) + +(defn direction-cells + [shape direction] + (let [cells (get-cells shape) + order (fn [dir] (if (= dir :column) (juxt :column :row) (juxt :row :column))) + eligible (fn [cell] (and (= :auto (:position cell)) (= 1 (:row-span cell) (:column-span cell)))) + old (filter eligible (sort-by (order (:layout-grid-dir shape)) cells)) + new (filter eligible (sort-by (order direction) cells)) + members (vec (mapcat :shapes old)) + by-cell (into {} (map-indexed (fn [index cell] + [[(:row cell) (:column cell)] + (if-let [id (nth members index nil)] [id] [])]) new))] + (if (= (or (:layout-grid-dir shape) :row) direction) + cells + (mapv (fn [cell] + (if-let [ids (get by-cell [(:row cell) (:column cell)])] + (assoc cell :shapes ids) + cell)) cells)))) + +(defn get-intended-values + [shape action value] + (let [before (get-cells shape) + cells (case action + :direction (direction-cells shape value) + :cell (mapv #(if (= [(:row value) (:column value)] [(:row %) (:column %)]) + (merge % (:props value)) %) before))] + (cond-> {:layout-grid-cells cells} + (= action :direction) (assoc :layout-grid-dir value)))) + +(defn edit + [model shape action value] + (if (:shape-ref shape) + (reduce-kv (fn [model key value] + (if (= value (if (= key :layout-grid-cells) (get-cells shape) (get shape key))) + model + (assoc-in model [(:id shape) key] {key value}))) + model (get-intended-values shape action value)) + model)) + +(defn settle-cells + "Drop departed/absolute children; fill existing empty cells with participating + children in parent order. A gap, manual cell or span ends the auto prefix." + [cells shape objects] + (let [order (if (= :column (:layout-grid-dir shape)) (juxt :column :row) (juxt :row :column)) + participating (filterv #(let [child (get objects %)] + (not (or (:hidden child) (:layout-item-absolute child)))) (:shapes shape)) + members (set participating) + prefix (fn [cells] + (second + (reduce (fn [[ended result] cell] + (let [manual (or (= :manual (:position cell)) + (not= 1 (:row-span cell) (:column-span cell)) + (and ended (seq (:shapes cell)) (= :auto (:position cell))))] + [(or ended manual (empty? (:shapes cell))) + (conj result (cond-> cell + (and manual (= :auto (:position cell))) (assoc :position :manual)))])) + [false []] cells))) + cells (->> cells (sort-by order) + (mapv #(update % :shapes (fn [ids] (filterv members ids)))) prefix) + assigned (set (mapcat :shapes cells)) + missing (remove assigned participating) + [cells _] (reduce (fn [[result pending] cell] + (if (and (seq pending) (empty? (:shapes cell))) + [(conj result (assoc cell :position :auto :shapes [(first pending)])) (rest pending)] + [(conj result cell) pending])) + [[] missing] cells)] + (->> cells prefix (sort-by (juxt :row :column)) vec))) diff --git a/common/src/app/common/test_helpers/composable/oracle.cljc b/common/src/app/common/test_helpers/composable/oracle.cljc index cb4061a5c9..0fbae2938d 100644 --- a/common/src/app/common/test_helpers/composable/oracle.cljc +++ b/common/src/app/common/test_helpers/composable/oracle.cljc @@ -16,6 +16,7 @@ [app.common.test-helpers.composable.comp.undo-check :as uc] [app.common.test-helpers.composable.core :as tm] [app.common.test-helpers.composable.geometry :as geom] + [app.common.test-helpers.composable.grid :as grid] [app.common.test-helpers.composable.shadow :as shadow] [app.common.test-helpers.files :as thf] [app.common.test-helpers.normalize :as thn] @@ -33,6 +34,14 @@ (throw (ex-info (str "Component invariant " (name invariant) " failed") (assoc details :invariant invariant)))) +(defn check-requested-grid + [shape before action value] + (let [expected (grid/get-intended-values before action value) + actual (cond-> {:layout-grid-cells (grid/get-cells shape)} + (= action :direction) (assoc :layout-grid-dir (:layout-grid-dir shape)))] + (when-not (= expected actual) + (fail :I9 {:phase :requested-grid :expected expected :actual actual})))) + (defn snapshot [situation] (into {} (map (fn [[id file]] @@ -355,12 +364,14 @@ (->> cells end-auto-prefix (sort-by (juxt :row :column)) vec))) (defn- check-structure - [objects shape main] + [objects shape main authored] (let [refs (mapv #(get-in objects [% :shape-ref]) (:shapes shape))] (when-not (= (vec (:shapes main)) refs) (fail :I4 {:shape (:name shape) :group :structure :expected (:shapes main) :actual refs})) - (when (= :grid (:layout main)) + ;; Authored cells have already been checked against independent intent + ;; by I9. Child structure still follows the main in every case. + (when (and (= :grid (:layout main)) (not (contains? authored :layout-grid-cells))) (let [expected (expected-grid-cells objects shape main) actual (grid-cells shape)] (when-not (= expected actual) @@ -414,7 +425,7 @@ :expected value :actual (shadow/values shape group)}))) (when (and settled (:shape-ref shape)) (when-let [main (ctf/find-content-ref-shape file container libraries shape :include-deleted? true)] - (check-structure (:objects page) shape main) + (check-structure (:objects page) shape main expected) (doseq [group (keys shadow/attributes) :when (and (not= group :geometry-group) (not (contains? touched group))) :when (not (and (= group :name-group) (= :text (:type shape)) diff --git a/common/src/app/common/test_helpers/composable/shadow.cljc b/common/src/app/common/test_helpers/composable/shadow.cljc index cd9ea53862..c990a3aacb 100644 --- a/common/src/app/common/test_helpers/composable/shadow.cljc +++ b/common/src/app/common/test_helpers/composable/shadow.cljc @@ -12,6 +12,7 @@ [app.common.math :as mth] [app.common.test-helpers.composable.comp.nodes :as n] [app.common.test-helpers.composable.geometry :as geom] + [app.common.test-helpers.composable.grid :as grid] [app.common.test-helpers.texts :as thtx] [app.common.types.container :as ctn])) @@ -104,9 +105,11 @@ (defn values [shape group] - (if (contains? text-groups group) + (cond + (= group :layout-grid-cells) {:layout-grid-cells (grid/get-cells shape)} + (contains? text-groups group) {:content (:content shape)} - (into {} (map #(vector % (get shape %))) (get attributes group)))) + :else (into {} (map #(vector % (get shape %))) (get attributes group)))) (defn author-geometry [shape parent] @@ -161,7 +164,8 @@ (defn duplicate [model before source-id after copy-id] (letfn [(copy-groups [result source-id copy-id] - (let [groups (get model source-id) + (let [groups (grid/remap-groups (get model source-id) + (zipmap (:shapes (get before source-id)) (:shapes (get after copy-id)))) result (cond-> result (seq groups) (assoc copy-id groups))] (reduce (fn [result [source child]] (copy-groups result source child)) result (map vector (:shapes (get before source-id)) (:shapes (get after copy-id))))))] @@ -202,7 +206,7 @@ (loop [shape shape groups (get model (:id shape)) seen #{}] (let [ref (:shape-ref shape) source (get before ref) - inherited (get model ref) + inherited (grid/remap-groups (get model ref) (grid/get-child-map before source original)) inherited (cond-> inherited (:geometry-group inherited) (update :geometry-group in-orientation source original))] @@ -305,7 +309,7 @@ (assoc id {:swap-group {}}) (and (:shape-ref ref) (seq groups)) (update (:id ref) merge - (cond-> groups + (cond-> (grid/remap-groups groups (grid/get-child-map after shape ref)) (= (:component-id ref) (:component-id (get after (:shape-ref ref)))) (dissoc :swap-group))))] @@ -342,7 +346,10 @@ ordinary? (and shape position (nil? (:component-id shape)) (nil? (:component-id position))) - groups (cond-> groups ordinary? (dissoc :swap-group))] + groups (cond-> groups + ordinary? (dissoc :swap-group) + (:layout-grid-cells groups) + (update-in [:layout-grid-cells :layout-grid-cells] grid/settle-cells shape objects))] (if (seq groups) (assoc result id groups) (dissoc result id)))) model model)) diff --git a/common/test/common_tests/logic/composable_gen_test.cljc b/common/test/common_tests/logic/composable_gen_test.cljc index 1df835ffaf..ae1185c16e 100644 --- a/common/test/common_tests/logic/composable_gen_test.cljc +++ b/common/test/common_tests/logic/composable_gen_test.cljc @@ -29,6 +29,83 @@ (t/use-fixtures :each thi/test-fixture) +(t/deftest authored-grid-cell-values-are-checked-against-intent-instead-of-the-main + (let [situation (cg/world [1 0 {:layout :grid :layout-children true}]) + id ((n/nth-copy-head-of "random-0" 0) situation) + shape (tm/shape-by-id situation id) + cell-id (:id (first (sort-by (juxt :row :column) (vals (:layout-grid-cells shape))))) + intended (assoc-in shape [:layout-grid-cells cell-id :align-self] :end) + file (tm/file situation) + page (thf/current-page file) + changes (cls/generate-update-shapes (pcb/empty-changes nil (:id page)) [id] + (constantly intended) (:objects page) {}) + edited (tm/with-file situation (thf/apply-changes file changes)) + model {id {:layout-grid-cells (shadow/values intended :layout-grid-cells)}} + failure (try (oracle/check edited model) nil + (catch #?(:clj Exception :cljs js/Error) error (ex-data error)))] + (t/is (= #{:layout-grid-cells} (:touched (tm/shape-by-id edited id)))) + (t/is (nil? failure) (pr-str failure)))) + +(t/deftest grid-overrides-survive-component-histories + (doseq [ops [[[:grid-direction 0 0 1 1] [:grid-direction 0 0 1 0] + [:grid-direction 0 0 0 0] [:update-main 0] + [:grid-direction 0 0 0 1] [:reset 0] [:undo] [:redo]] + [[:grid-cell 0 0 0 1] [:grid-cell 0 1 1 0] [:grid-cell 0 0 2 0] + [:update-main 0] [:grid-cell 0 1 2 1] [:reset 0] [:undo] [:redo]] + [[:grid-cell 0 0 0 1] [:duplicate 0] [:undo] [:redo] [:update-main 0]] + [[:grid-direction 0 0 1 1] [:add-child 0] [:undo] [:redo]] + [[:grid-cell 0 0 0 1] [:remove-child 0] [:undo] [:redo]] + [[:grid-cell 0 0 0 1] [:detach 0] [:undo] [:redo]]]] + (let [result (cg/run {:world [1 0 {:layout :grid :layout-children true :grid-square true}] :ops ops})] + (doseq [kind (set (map first ops))] + (t/is (zero? (get-in result [:coverage :random kind :skipped] 0)) (str kind " in " ops)) + (when-not (#{:undo :redo} kind) + (t/is (pos? (get-in result [:coverage :random kind :changed] 0)) (str kind " in " ops))))))) + +(t/deftest grid-directions-track-both-groups-and-reject-dropped-main-edits + (let [initial (cg/initial-state [1 0 {:layout :grid :layout-children true :grid-square true}]) + resolved (cg/resolve-op (:situation initial) {} [:grid-direction 0 0 1 1]) + id (get-in resolved [:shape :id]) + edited (-> initial (cg/step [:grid-direction 0 0 1 1]) cg/settle-state) + file (tm/file (:situation edited)) + path [:data :pages-index (:id (thf/current-page file)) :objects id] + check (fn [file] + (try (oracle/check (tm/with-file (:situation edited) file) (:model edited)) nil + (catch #?(:clj Exception :cljs js/Error) error (:invariant (ex-data error))))) + main (tm/shape-by-id (:situation initial) (:shape-ref (:shape resolved))) + dropped (fn [action value] + (try (oracle/check-requested-grid main main action value) nil + (catch #?(:clj Exception :cljs js/Error) error (:invariant (ex-data error)))))] + (t/is (= #{:layout-grid-dir :layout-grid-cells} (set (keys (get (:model edited) id))))) + (t/is (= :I9 (check (assoc-in file (conj path :layout-grid-dir) :row)))) + (t/is (= :I3 (check (update-in file (conj path :touched) disj :layout-grid-dir)))) + (t/is (= :I3 (check (update-in file (conj path :touched) disj :layout-grid-cells)))) + (t/is (= :I9 (dropped :direction :column))))) + +(t/deftest detaching-an-outer-copy-remaps-inherited-grid-cell-intent + (let [result (cg/run {:world [1 1 {:layout :grid :layout-children true :grid-square true}] + :ops [[:grid-cell 0 0 0 1] [:detach 0] [:undo] [:redo]]})] + (t/is (= 1 (get-in result [:coverage :random :grid-cell :changed]))) + (t/is (= 1 (get-in result [:coverage :random :detach :changed]))))) + +(t/deftest grid-intent-rejects-corrupt-values-members-and-missing-groups + (let [initial (cg/initial-state [1 0 {:layout :grid :layout-children true :grid-square true}]) + resolved (cg/resolve-op (:situation initial) {} [:grid-cell 0 0 0 1]) + id (get-in resolved [:shape :id]) + cell-id (:id (first (sort-by (juxt :row :column) (vals (get-in resolved [:shape :layout-grid-cells]))))) + edited (-> initial (cg/step [:grid-cell 0 0 0 1]) cg/settle-state) + situation (:situation edited) + file (tm/file situation) + path [:data :pages-index (:id (thf/current-page file)) :objects id] + invariant (fn [file] + (try (oracle/check (tm/with-file situation file) (:model edited)) nil + (catch #?(:clj Exception :cljs js/Error) error (:invariant (ex-data error)))))] + (t/is (= :I9 (invariant (assoc-in file (into path [:layout-grid-cells cell-id :align-self]) :start)))) + (t/is (= :I9 (invariant (assoc-in file (into path [:layout-grid-cells cell-id :justify-self]) :end)))) + (t/is (= :I9 (invariant (assoc-in file (into path [:layout-grid-cells cell-id :shapes]) [])))) + (t/is (= :I4 (invariant (update-in file (conj path :shapes) #(vec (reverse %)))))) + (t/is (= :I3 (invariant (update-in file (conj path :touched) disj :layout-grid-cells)))))) + (t/deftest nesting-a-copy-alongside-a-hidden-layout-child-stays-settled (t/is (map? (cg/run {: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/generated_test.cljs b/frontend/test/frontend_tests/composable_tests/generated_test.cljs index f60cbbc709..33b43f0a3c 100644 --- a/frontend/test/frontend_tests/composable_tests/generated_test.cljs +++ b/frontend/test/frontend_tests/composable_tests/generated_test.cljs @@ -9,6 +9,7 @@ [app.common.test-helpers.composable.comp.undo-check :as uc] [app.common.test-helpers.composable.core :as tm] [app.common.test-helpers.composable.gen :as cg] + [app.common.test-helpers.composable.grid :as grid] [app.common.test-helpers.composable.oracle :as oracle] [app.common.test-helpers.files :as cthf] [app.common.test-helpers.normalize :as thn] @@ -23,11 +24,14 @@ (defn- live-model [state live] - (let [by-path (into {} (map (juxt :path (comp :id :shape))) (cg/forest live))] + (let [by-path (into {} (map (juxt :path (comp :id :shape))) (cg/forest live)) + ids-map (into {} (keep (fn [{:keys [path shape]}] + (when-let [id (get by-path path)] [(:id shape) id]))) + (cg/forest (:situation state)))] (into {} (keep (fn [{:keys [path shape]}] (when-let [groups (get (:model state) (:id shape))] - (when-let [id (get by-path path)] [id groups])))) + (when-let [id (get by-path path)] [id (grid/remap-groups groups ids-map)])))) (cg/forest (:situation state))))) (defn- ^:async replay-program @@ -61,6 +65,10 @@ (:objects (cthf/current-page (tm/file next-live))) (tm/shape-by-id next-live (get-in actual [:shape :id])) (cg/requested-geometry live actual (first tuple)))) + (when (and (:op actual) (#{:grid-cell :grid-direction} (first tuple))) + (oracle/check-requested-grid + (tm/shape-by-id next-live (get-in actual [:shape :id])) (:shape actual) + (get-in actual [:op :action]) (get-in actual [:op :value]))) (try (oracle/check next-live (live-model next-state next-live)) (catch :default error @@ -88,6 +96,22 @@ rx/timer mock/timer-mock} (await (replay-program program))))) +(t/deftest ^:async authored-grid-histories-use-independent-cell-expectations + (doseq [ops [[[:grid-direction 0 0 1 1] [:grid-direction 0 0 1 0] + [:grid-direction 0 0 0 0] [:update-main 0] + [:grid-direction 0 0 0 1] [:reset 0] [:undo] [:redo]] + [[:grid-cell 0 0 0 1] [:grid-cell 0 1 1 0] [:grid-cell 0 0 2 0] + [:update-main 0] [:grid-cell 0 1 2 1] [:reset 0] [:undo] [:redo]] + [[:grid-cell 0 0 0 1] [:duplicate 0] [:undo] [:redo] [:update-main 0]] + [[:grid-direction 0 0 1 1] [:add-child 0] [:undo] [:redo]] + [[:grid-cell 0 0 0 1] [:remove-child 0] [:undo] [:redo]] + [[:grid-cell 0 0 0 1] [:detach 0] [:undo] [:redo]]]] + (await (replay {:world [1 0 {:layout :grid :layout-children true :grid-square true}] :ops ops})))) + +(t/deftest ^:async detaching-an-outer-copy-remaps-inherited-grid-cell-intent + (await (replay {:world [1 1 {:layout :grid :layout-children true :grid-square true}] + :ops [[:grid-cell 0 0 0 1] [:detach 0] [:undo] [:redo]]}))) + (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]] [26 [:grid]]] diff --git a/frontend/test/frontend_tests/composable_tests/interpreter.cljs b/frontend/test/frontend_tests/composable_tests/interpreter.cljs index 01707c10de..0a6418080f 100644 --- a/frontend/test/frontend_tests/composable_tests/interpreter.cljs +++ b/frontend/test/frontend_tests/composable_tests/interpreter.cljs @@ -140,6 +140,16 @@ built from `situation` and from the same helpers the common node uses." [op situation] (cond + (instance? n/EditGrid op) + (let [{:keys [target action value]} op + id (tm/target-shape-id situation target) + shape (tm/shape-by-id situation id)] + (case action + :direction [(dwsl/change-grid-direction [id] value)] + :cell (let [cell (first (filter #(= [(:row value) (:column value)] [(:row %) (:column %)]) + (vals (:layout-grid-cells shape))))] + [(dwsl/update-grid-cells id [(:id cell)] (:props value))]))) + (instance? n/ChangeProperty op) (let [{:keys [target property value]} op id (tm/target-shape-id situation target)]