From 9761cba337a096af8ea63a43439495ca46467ad3 Mon Sep 17 00:00:00 2001 From: Pablo Alba Date: Wed, 11 Jun 2025 10:53:42 +0200 Subject: [PATCH] :recycle: Restore separate the content of the text of the rest of properties on components updates This reverts commit b2aaa5f0df9630a2cff20bef1491a4d0f8e599a2. --- CHANGES.md | 14 + common/src/app/common/files/migrations.cljc | 29 +- common/src/app/common/logic/libraries.cljc | 68 +- common/src/app/common/types/container.cljc | 6 +- .../common_tests/logic/text_sync_test.cljc | 881 ++++++++++++++++++ .../common_tests/logic/text_touched_test.cljc | 132 +++ 6 files changed, 1124 insertions(+), 6 deletions(-) create mode 100644 common/test/common_tests/logic/text_sync_test.cljc create mode 100644 common/test/common_tests/logic/text_touched_test.cljc diff --git a/CHANGES.md b/CHANGES.md index 2e5798b752..7c759be733 100644 --- a/CHANGES.md +++ b/CHANGES.md @@ -1,5 +1,19 @@ # CHANGELOG +## 2.9.0 (Unreleased) + +### :rocket: Epics and highlights + +### :boom: Breaking changes & Deprecations + +### :heart: Community contributions (Thank you!) + +### :sparkles: New features & Enhancements + +- On components overrides, separate the content of the text from the rest of properties [Taiga #7434](https://tree.taiga.io/project/penpot/us/7434) + +### :bug: Bugs fixed + ## 2.8.0 (Next / Unreleased) ### :rocket: Epics and highlights diff --git a/common/src/app/common/files/migrations.cljc b/common/src/app/common/files/migrations.cljc index 11eb0dfed9..7fe4351bcb 100644 --- a/common/src/app/common/files/migrations.cljc +++ b/common/src/app/common/files/migrations.cljc @@ -32,6 +32,7 @@ [app.common.types.shape :as cts] [app.common.types.shape.interactions :as ctsi] [app.common.types.shape.shadow :as ctss] + [app.common.types.text :as cttx] [app.common.uuid :as uuid] [clojure.set :as set] [cuerdas.core :as str])) @@ -1477,6 +1478,31 @@ color))] (d/update-when data :colors d/update-vals update-color))) +(defmethod migrate-data "0009-add-partial-text-touched-flags" + [data _] + (letfn [(update-object [page object] + (if (and (cfh/text-shape? object) + (ctk/in-component-copy? object)) + (let [file {:id (:id data) :data data} + libs (when (:libs data) + (deref (:libs data))) + ref-shape (ctf/find-ref-shape file page libs object + {:include-deleted? true :with-context? true}) + partial-touched (when ref-shape + (cttx/get-diff-type (:content object) (:content ref-shape)))] + (if (seq partial-touched) + (update object :touched (fn [touched] + (reduce #(ctk/set-touched-group %1 %2) + touched + partial-touched))) + object)) + object)) + + (update-page [page] + (d/update-when page :objects d/update-vals (partial update-object page)))] + + (update data :pages-index d/update-vals update-page))) + (def available-migrations (into (d/ordered-set) ["legacy-2" @@ -1540,4 +1566,5 @@ "0005-deprecate-image-type" "0006-fix-old-texts-fills" "0007-clear-invalid-strokes-and-fills-v2" - "0008-fix-library-colors-opacity"])) + "0008-fix-library-colors-opacity" + "0009-add-partial-text-touched-flags"])) diff --git a/common/src/app/common/logic/libraries.cljc b/common/src/app/common/logic/libraries.cljc index 197f8c8a29..f9b7e18ff7 100644 --- a/common/src/app/common/logic/libraries.cljc +++ b/common/src/app/common/logic/libraries.cljc @@ -29,6 +29,7 @@ [app.common.types.shape-tree :as ctst] [app.common.types.shape.interactions :as ctsi] [app.common.types.shape.layout :as ctl] + [app.common.types.text :as cttx] [app.common.types.token :as cto] [app.common.types.typography :as cty] [app.common.types.variant :as ctv] @@ -1665,7 +1666,7 @@ (defn- add-update-attr-operations - [attr dest-shape origin-shape roperations uoperations touched] + [attr dest-shape origin-shape roperations uoperations touched is-text-partial-change?] (let [orig-value (get origin-shape attr) dest-value (get dest-shape attr) ;; position-data is a special case because can be affected by :geometry-group and :content-group @@ -1677,10 +1678,27 @@ (not= orig-value dest-value) (touched :geometry-group)) + ;; We want to split the changes on the text itself and on its properties + text-value + (when is-text-partial-change? + (cond + (touched :text-content-structure-same-attrs) + ;; Keep the dest structure and texts, update its attrs to make them like the origin + (cttx/copy-attrs-keys dest-value (cttx/get-first-paragraph-text-attrs orig-value)) + + (touched :text-content-text) + ;; Keep the texts touched in dest: copy the texts from dest over the attrs of origin + (cttx/copy-text-keys dest-value orig-value) + + (touched :text-content-attribute) + ;; Keep the attrs touched in dest: copy the texts from origin over the attrs of dest + (cttx/copy-text-keys orig-value dest-value))) + val (cond ;; If position data changes and the geometry group is touched ;; we need to put to nil so we can regenerate it reset-pos-data? nil + is-text-partial-change? text-value :else orig-value) roperation {:type :set @@ -1694,6 +1712,33 @@ [(conj roperations roperation) (conj uoperations uoperation)])) +(defn- is-text-partial-change? + "Check if the attr update is a text partial change" + [origin-shape dest-shape attr touched] + (let [partial-text-keys [:text-content-attribute :text-content-text] + active-keys (filter touched partial-text-keys) + orig-content (get origin-shape attr) + orig-attrs (cttx/get-first-paragraph-text-attrs orig-content) + + equal-orig-attrs? (cttx/equal-attrs? orig-content orig-attrs)] + (and + (or + ;; One and only one of the keys is pressent + (= 1 (count active-keys)) + (and + (not (touched :text-content-attribute)) + (touched :text-content-structure-same-attrs))) + + (or + ;; Both has the same structure + (cttx/equal-structure? (:content origin-shape) (:content dest-shape)) + + ;; The origin and destiny have different structures, but each have the same attrs + ;; for all the items on its content tree + (and + equal-orig-attrs? + (touched :text-content-structure-same-attrs)))))) + (defn- update-attrs "The main function that implements the attribute sync algorithm. Copy attributes that have changed in the origin shape to the dest shape. @@ -1737,14 +1782,29 @@ (generate-update-tokens container dest-shape origin-shape touched omit-touched?)) (let [attr-group (get ctk/sync-attrs attr) + ;; On texts, when we want to omit the touched attrs, both text (the actual letters) + ;; and attrs (bold, font, etc) are in the same attr :content. + ;; If only one of them is touched, we want to adress this case and + ;; only update the untouched one + text-partial-change? (when (and + omit-touched? + (= :text (:type origin-shape)) + (= :content attr) + (touched attr-group)) + (is-text-partial-change? origin-shape dest-shape attr touched)) + skip-operations? (or (= (get origin-shape attr) (get dest-shape attr)) (and (touched attr-group) - omit-touched?)) + omit-touched? + ;; When it is a text-partial-change, we should generate operations + ;; even when omit-touched? is true, but updating only the text or + ;; the attributes, omiting the other part + (not text-partial-change?))) [roperations' uoperations'] (if skip-operations? [roperations uoperations] - (add-update-attr-operations attr dest-shape origin-shape roperations uoperations touched))] + (add-update-attr-operations attr dest-shape origin-shape roperations uoperations touched text-partial-change?))] (recur (next attrs) roperations' uoperations'))))))) @@ -1779,7 +1839,7 @@ ;; If the attr is not touched in the origin shape, don't copy it (not (touched-origin attr-group))) [roperations uoperations] - (add-update-attr-operations attr dest-shape origin-shape roperations uoperations touched))] + (add-update-attr-operations attr dest-shape origin-shape roperations uoperations touched false))] (recur (next attrs) roperations' uoperations')) diff --git a/common/src/app/common/types/container.cljc b/common/src/app/common/types/container.cljc index 395e421315..6ca767f7cc 100644 --- a/common/src/app/common/types/container.cljc +++ b/common/src/app/common/types/container.cljc @@ -18,6 +18,7 @@ [app.common.types.plugins :as ctpg] [app.common.types.shape-tree :as ctst] [app.common.types.shape.layout :as ctl] + [app.common.types.text :as cttx] [app.common.types.token :as ctt] [app.common.uuid :as uuid] [clojure.set :as set])) @@ -569,13 +570,16 @@ (not equal?) (not (and ignore-geometry is-geometry?))) + content-diff-type (when (and (= (:type shape) :text) (= attr :content)) + (cttx/get-diff-type (:content shape) val)) + token-groups (if (= attr :applied-tokens) (get-token-groups shape val) #{}) groups (cond-> token-groups (and group (not equal?)) - (set/union #{group}))] + (set/union #{group} content-diff-type))] (cond-> shape ;; Depending on the origin of the attribute change, we need or not to ;; set the "touched" flag for the group the attribute belongs to. diff --git a/common/test/common_tests/logic/text_sync_test.cljc b/common/test/common_tests/logic/text_sync_test.cljc new file mode 100644 index 0000000000..3f1fb18c68 --- /dev/null +++ b/common/test/common_tests/logic/text_sync_test.cljc @@ -0,0 +1,881 @@ +;; 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 INC + +(ns common-tests.logic.text-sync-test + (:require + [app.common.files.changes-builder :as pcb] + [app.common.logic.libraries :as cll] + [app.common.logic.shapes :as cls] + [app.common.test-helpers.components :as thc] + [app.common.test-helpers.compositions :as tho] + [app.common.test-helpers.files :as thf] + [app.common.test-helpers.ids-map :as thi] + [app.common.test-helpers.shapes :as ths] + [clojure.test :as t])) + +(t/use-fixtures :each thi/test-fixture) + + +(t/deftest test-sync-unchanged-copy-when-changed-attribute + (let [;; ==== Setup + file (-> (thf/sample-file :file1) + (tho/add-frame-with-text :main-root :main-child "hello world") + (thc/make-component :component1 :main-root) + (thc/instantiate-component :component1 :copy-root {:children-labels [:copy-child]})) + page (thf/current-page file) + main-child (ths/get-shape file :main-child) + + ;; ==== Action + changes1 (cls/generate-update-shapes (pcb/empty-changes nil (:id page)) + #{(:id main-child)} + (fn [shape] + (assoc-in shape [:content :children 0 :children 0 :children 0 :font-size] "32")) + (:objects page) + {}) + + updated-file (thf/apply-changes file changes1) + + changes2 (cll/generate-sync-file-changes (pcb/empty-changes) + nil + :components + (:id updated-file) + (thi/id :component1) + (:id updated-file) + {(:id updated-file) updated-file} + (:id updated-file)) + + file' (thf/apply-changes updated-file changes2) + + ;; ==== Get + copy-child' (ths/get-shape file' :copy-child) + line (get-in copy-child' [:content :children 0 :children 0 :children 0])] + (t/is (= "32" (:font-size line))) + (t/is (= "hello world" (:text line))))) + +(t/deftest test-sync-unchanged-copy-when-changed-text + (let [;; ==== Setup + file (-> (thf/sample-file :file1) + (tho/add-frame-with-text :main-root :main-child "hello world") + (thc/make-component :component1 :main-root) + (thc/instantiate-component :component1 :copy-root {:children-labels [:copy-child]})) + page (thf/current-page file) + main-child (ths/get-shape file :main-child) + + ;; ==== Action + changes1 (cls/generate-update-shapes (pcb/empty-changes nil (:id page)) + #{(:id main-child)} + (fn [shape] + (assoc-in shape [:content :children 0 :children 0 :children 0 :text] "Bye")) + (:objects page) + {}) + + updated-file (thf/apply-changes file changes1) + + changes2 (cll/generate-sync-file-changes (pcb/empty-changes) + nil + :components + (:id updated-file) + (thi/id :component1) + (:id updated-file) + {(:id updated-file) updated-file} + (:id updated-file)) + + file' (thf/apply-changes updated-file changes2) + + ;; ==== Get + copy-child' (ths/get-shape file' :copy-child) + line (get-in copy-child' [:content :children 0 :children 0 :children 0])] + (t/is (= "14" (:font-size line))) + (t/is (= "Bye" (:text line))))) + +(t/deftest test-sync-unchanged-copy-when-changed-both + (let [;; ==== Setup + file (-> (thf/sample-file :file1) + (tho/add-frame-with-text :main-root :main-child "hello world") + (thc/make-component :component1 :main-root) + (thc/instantiate-component :component1 :copy-root {:children-labels [:copy-child]})) + page (thf/current-page file) + main-child (ths/get-shape file :main-child) + + ;; ==== Action + changes1 (cls/generate-update-shapes (pcb/empty-changes nil (:id page)) + #{(:id main-child)} + (fn [shape] + (-> shape + (assoc-in [:content :children 0 :children 0 :children 0 :font-size] "32") + (assoc-in [:content :children 0 :children 0 :children 0 :text] "Bye"))) + (:objects page) + {}) + + updated-file (thf/apply-changes file changes1) + + changes2 (cll/generate-sync-file-changes (pcb/empty-changes) + nil + :components + (:id updated-file) + (thi/id :component1) + (:id updated-file) + {(:id updated-file) updated-file} + (:id updated-file)) + + file' (thf/apply-changes updated-file changes2) + + ;; ==== Get + copy-child' (ths/get-shape file' :copy-child) + line (get-in copy-child' [:content :children 0 :children 0 :children 0])] + (t/is (= "32" (:font-size line))) + (t/is (= "Bye" (:text line))))) + +(t/deftest test-sync-updated-attr-copy-when-changed-attribute + (let [;; ==== Setup + file0 (-> (thf/sample-file :file1) + (tho/add-frame-with-text :main-root :main-child "hello world") + (thc/make-component :component1 :main-root) + (thc/instantiate-component :component1 :copy-root {:children-labels [:copy-child]})) + page0 (thf/current-page file0) + copy-child (ths/get-shape file0 :copy-child) + + changes (cls/generate-update-shapes (pcb/empty-changes nil (:id page0)) + #{(:id copy-child)} + (fn [shape] + (assoc-in shape [:content :children 0 :children 0 :children 0 :font-weight] "700")) + (:objects (thf/current-page file0)) + {}) + file (thf/apply-changes file0 changes) + main-child (ths/get-shape file :main-child) + page (thf/current-page file) + + ;; ==== Action + changes1 (cls/generate-update-shapes (pcb/empty-changes nil (:id page)) + #{(:id main-child)} + (fn [shape] + (assoc-in shape [:content :children 0 :children 0 :children 0 :font-size] "32")) + (:objects page) + {}) + + updated-file (thf/apply-changes file changes1) + + changes2 (cll/generate-sync-file-changes (pcb/empty-changes) + nil + :components + (:id updated-file) + (thi/id :component1) + (:id updated-file) + {(:id updated-file) updated-file} + (:id updated-file)) + + file' (thf/apply-changes updated-file changes2) + + ;; ==== Get + copy-child' (ths/get-shape file' :copy-child) + line (get-in copy-child' [:content :children 0 :children 0 :children 0])] + ;; The attr doesn't change, because it was touched + (t/is (= "14" (:font-size line))) + (t/is (= "hello world" (:text line))))) + +(t/deftest test-sync-updated-attr-copy-when-changed-text + (let [;; ==== Setup + file0 (-> (thf/sample-file :file1) + (tho/add-frame-with-text :main-root :main-child "hello world") + (thc/make-component :component1 :main-root) + (thc/instantiate-component :component1 :copy-root {:children-labels [:copy-child]})) + page0 (thf/current-page file0) + copy-child (ths/get-shape file0 :copy-child) + + changes (cls/generate-update-shapes (pcb/empty-changes nil (:id page0)) + #{(:id copy-child)} + (fn [shape] + (assoc-in shape [:content :children 0 :children 0 :children 0 :font-weight] "700")) + (:objects (thf/current-page file0)) + {}) + file (thf/apply-changes file0 changes) + main-child (ths/get-shape file :main-child) + page (thf/current-page file) + + ;; ==== Action + changes1 (cls/generate-update-shapes (pcb/empty-changes nil (:id page)) + #{(:id main-child)} + (fn [shape] + (assoc-in shape [:content :children 0 :children 0 :children 0 :text] "Bye")) + (:objects page) + {}) + + updated-file (thf/apply-changes file changes1) + + changes2 (cll/generate-sync-file-changes (pcb/empty-changes) + nil + :components + (:id updated-file) + (thi/id :component1) + (:id updated-file) + {(:id updated-file) updated-file} + (:id updated-file)) + + file' (thf/apply-changes updated-file changes2) + + ;; ==== Get + copy-child' (ths/get-shape file' :copy-child) + line (get-in copy-child' [:content :children 0 :children 0 :children 0])] + (t/is (= "14" (:font-size line))) + ;; The text is updated because only attrs were touched + (t/is (= "Bye" (:text line))))) + +(t/deftest test-sync-updated-attr-copy-when-changed-both + (let [;; ==== Setup + file0 (-> (thf/sample-file :file1) + (tho/add-frame-with-text :main-root :main-child "hello world") + (thc/make-component :component1 :main-root) + (thc/instantiate-component :component1 :copy-root {:children-labels [:copy-child]})) + page0 (thf/current-page file0) + copy-child (ths/get-shape file0 :copy-child) + + changes (cls/generate-update-shapes (pcb/empty-changes nil (:id page0)) + #{(:id copy-child)} + (fn [shape] + (assoc-in shape [:content :children 0 :children 0 :children 0 :font-weight] "700")) + (:objects (thf/current-page file0)) + {}) + file (thf/apply-changes file0 changes) + main-child (ths/get-shape file :main-child) + page (thf/current-page file) + + ;; ==== Action + changes1 (cls/generate-update-shapes (pcb/empty-changes nil (:id page)) + #{(:id main-child)} + (fn [shape] + (-> shape + (assoc-in [:content :children 0 :children 0 :children 0 :font-size] "32") + (assoc-in [:content :children 0 :children 0 :children 0 :text] "Bye"))) + (:objects page) + {}) + + updated-file (thf/apply-changes file changes1) + + changes2 (cll/generate-sync-file-changes (pcb/empty-changes) + nil + :components + (:id updated-file) + (thi/id :component1) + (:id updated-file) + {(:id updated-file) updated-file} + (:id updated-file)) + + file' (thf/apply-changes updated-file changes2) + + ;; ==== Get + copy-child' (ths/get-shape file' :copy-child) + line (get-in copy-child' [:content :children 0 :children 0 :children 0])] + ;; The attr doesn't change, because it was touched + (t/is (= "14" (:font-size line))) + ;; The text is updated because only attrs were touched + (t/is (= "Bye" (:text line))))) + +(t/deftest test-sync-updated-text-copy-when-changed-attribute + (let [;; ==== Setup + file0 (-> (thf/sample-file :file1) + (tho/add-frame-with-text :main-root :main-child "hello world") + (thc/make-component :component1 :main-root) + (thc/instantiate-component :component1 :copy-root {:children-labels [:copy-child]})) + page0 (thf/current-page file0) + copy-child (ths/get-shape file0 :copy-child) + + changes (cls/generate-update-shapes (pcb/empty-changes nil (:id page0)) + #{(:id copy-child)} + (fn [shape] + (assoc-in shape [:content :children 0 :children 0 :children 0 :text] "Hi")) + (:objects (thf/current-page file0)) + {}) + file (thf/apply-changes file0 changes) + main-child (ths/get-shape file :main-child) + page (thf/current-page file) + + ;; ==== Action + changes1 (cls/generate-update-shapes (pcb/empty-changes nil (:id page)) + #{(:id main-child)} + (fn [shape] + (assoc-in shape [:content :children 0 :children 0 :children 0 :font-size] "32")) + (:objects page) + {}) + + updated-file (thf/apply-changes file changes1) + + changes2 (cll/generate-sync-file-changes (pcb/empty-changes) + nil + :components + (:id updated-file) + (thi/id :component1) + (:id updated-file) + {(:id updated-file) updated-file} + (:id updated-file)) + + file' (thf/apply-changes updated-file changes2) + + ;; ==== Get + copy-child' (ths/get-shape file' :copy-child) + line (get-in copy-child' [:content :children 0 :children 0 :children 0])] + ;; The attr is updated because only text were touched + (t/is (= "32" (:font-size line))) + (t/is (= "Hi" (:text line))))) + +(t/deftest test-sync-updated-text-copy-when-changed-text + (let [;; ==== Setup + file0 (-> (thf/sample-file :file1) + (tho/add-frame-with-text :main-root :main-child "hello world") + (thc/make-component :component1 :main-root) + (thc/instantiate-component :component1 :copy-root {:children-labels [:copy-child]})) + page0 (thf/current-page file0) + copy-child (ths/get-shape file0 :copy-child) + + changes (cls/generate-update-shapes (pcb/empty-changes nil (:id page0)) + #{(:id copy-child)} + (fn [shape] + (assoc-in shape [:content :children 0 :children 0 :children 0 :text] "Hi")) + (:objects (thf/current-page file0)) + {}) + file (thf/apply-changes file0 changes) + main-child (ths/get-shape file :main-child) + page (thf/current-page file) + + ;; ==== Action + changes1 (cls/generate-update-shapes (pcb/empty-changes nil (:id page)) + #{(:id main-child)} + (fn [shape] + (assoc-in shape [:content :children 0 :children 0 :children 0 :text] "Bye")) + (:objects page) + {}) + + updated-file (thf/apply-changes file changes1) + + changes2 (cll/generate-sync-file-changes (pcb/empty-changes) + nil + :components + (:id updated-file) + (thi/id :component1) + (:id updated-file) + {(:id updated-file) updated-file} + (:id updated-file)) + + file' (thf/apply-changes updated-file changes2) + + ;; ==== Get + copy-child' (ths/get-shape file' :copy-child) + line (get-in copy-child' [:content :children 0 :children 0 :children 0])] + (t/is (= "14" (:font-size line))) + ;; The text doesn't change, because it was touched + (t/is (= "Hi" (:text line))))) + +(t/deftest test-sync-updated-text-copy-when-changed-both + (let [;; ==== Setup + file0 (-> (thf/sample-file :file1) + (tho/add-frame-with-text :main-root :main-child "hello world") + (thc/make-component :component1 :main-root) + (thc/instantiate-component :component1 :copy-root {:children-labels [:copy-child]})) + page0 (thf/current-page file0) + copy-child (ths/get-shape file0 :copy-child) + + changes (cls/generate-update-shapes (pcb/empty-changes nil (:id page0)) + #{(:id copy-child)} + (fn [shape] + (assoc-in shape [:content :children 0 :children 0 :children 0 :text] "Hi")) + (:objects (thf/current-page file0)) + {}) + file (thf/apply-changes file0 changes) + main-child (ths/get-shape file :main-child) + page (thf/current-page file) + + ;; ==== Action + changes1 (cls/generate-update-shapes (pcb/empty-changes nil (:id page)) + #{(:id main-child)} + (fn [shape] + (-> shape + (assoc-in [:content :children 0 :children 0 :children 0 :font-size] "32") + (assoc-in [:content :children 0 :children 0 :children 0 :text] "Bye"))) + (:objects page) + {}) + + updated-file (thf/apply-changes file changes1) + + changes2 (cll/generate-sync-file-changes (pcb/empty-changes) + nil + :components + (:id updated-file) + (thi/id :component1) + (:id updated-file) + {(:id updated-file) updated-file} + (:id updated-file)) + + file' (thf/apply-changes updated-file changes2) + + ;; ==== Get + copy-child' (ths/get-shape file' :copy-child) + line (get-in copy-child' [:content :children 0 :children 0 :children 0])] + ;; The attr is updated because only text were touched + (t/is (= "32" (:font-size line))) + ;; The text doesn't change, because it was touched + (t/is (= "Hi" (:text line))))) + +(t/deftest test-sync-updated-both-copy-when-changed-attribute + (let [;; ==== Setup + file0 (-> (thf/sample-file :file1) + (tho/add-frame-with-text :main-root :main-child "hello world") + (thc/make-component :component1 :main-root) + (thc/instantiate-component :component1 :copy-root {:children-labels [:copy-child]})) + page0 (thf/current-page file0) + copy-child (ths/get-shape file0 :copy-child) + + changes (cls/generate-update-shapes (pcb/empty-changes nil (:id page0)) + #{(:id copy-child)} + (fn [shape] + (-> shape + (assoc-in [:content :children 0 :children 0 :children 0 :font-weight] "700") + (assoc-in [:content :children 0 :children 0 :children 0 :text] "Hi"))) + (:objects (thf/current-page file0)) + {}) + file (thf/apply-changes file0 changes) + main-child (ths/get-shape file :main-child) + page (thf/current-page file) + + ;; ==== Action + changes1 (cls/generate-update-shapes (pcb/empty-changes nil (:id page)) + #{(:id main-child)} + (fn [shape] + (assoc-in shape [:content :children 0 :children 0 :children 0 :font-size] "32")) + (:objects page) + {}) + + updated-file (thf/apply-changes file changes1) + + changes2 (cll/generate-sync-file-changes (pcb/empty-changes) + nil + :components + (:id updated-file) + (thi/id :component1) + (:id updated-file) + {(:id updated-file) updated-file} + (:id updated-file)) + + file' (thf/apply-changes updated-file changes2) + + ;; ==== Get + copy-child' (ths/get-shape file' :copy-child) + line (get-in copy-child' [:content :children 0 :children 0 :children 0])] + ;; The attr doesn't change, because it was touched + (t/is (= "14" (:font-size line))) + (t/is (= "Hi" (:text line))))) + +(t/deftest test-sync-updated-both-copy-when-changed-text + (let [;; ==== Setup + file0 (-> (thf/sample-file :file1) + (tho/add-frame-with-text :main-root :main-child "hello world") + (thc/make-component :component1 :main-root) + (thc/instantiate-component :component1 :copy-root {:children-labels [:copy-child]})) + page0 (thf/current-page file0) + copy-child (ths/get-shape file0 :copy-child) + + changes (cls/generate-update-shapes (pcb/empty-changes nil (:id page0)) + #{(:id copy-child)} + (fn [shape] + (-> shape + (assoc-in [:content :children 0 :children 0 :children 0 :font-weight] "700") + (assoc-in [:content :children 0 :children 0 :children 0 :text] "Hi"))) + (:objects (thf/current-page file0)) + {}) + file (thf/apply-changes file0 changes) + main-child (ths/get-shape file :main-child) + page (thf/current-page file) + + ;; ==== Action + changes1 (cls/generate-update-shapes (pcb/empty-changes nil (:id page)) + #{(:id main-child)} + (fn [shape] + (assoc-in shape [:content :children 0 :children 0 :children 0 :text] "Bye")) + (:objects page) + {}) + + updated-file (thf/apply-changes file changes1) + + changes2 (cll/generate-sync-file-changes (pcb/empty-changes) + nil + :components + (:id updated-file) + (thi/id :component1) + (:id updated-file) + {(:id updated-file) updated-file} + (:id updated-file)) + + file' (thf/apply-changes updated-file changes2) + + ;; ==== Get + copy-child' (ths/get-shape file' :copy-child) + line (get-in copy-child' [:content :children 0 :children 0 :children 0])] + (t/is (= "14" (:font-size line))) + ;; The text doesn't change, because it was touched + (t/is (= "Hi" (:text line))))) + +(t/deftest test-sync-updated-both-copy-when-changed-both + (let [;; ==== Setup + file0 (-> (thf/sample-file :file1) + (tho/add-frame-with-text :main-root :main-child "hello world") + (thc/make-component :component1 :main-root) + (thc/instantiate-component :component1 :copy-root {:children-labels [:copy-child]})) + page0 (thf/current-page file0) + copy-child (ths/get-shape file0 :copy-child) + + changes (cls/generate-update-shapes (pcb/empty-changes nil (:id page0)) + #{(:id copy-child)} + (fn [shape] + (-> shape + (assoc-in [:content :children 0 :children 0 :children 0 :font-weight] "700") + (assoc-in [:content :children 0 :children 0 :children 0 :text] "Hi"))) + (:objects (thf/current-page file0)) + {}) + file (thf/apply-changes file0 changes) + main-child (ths/get-shape file :main-child) + page (thf/current-page file) + + ;; ==== Action + changes1 (cls/generate-update-shapes (pcb/empty-changes nil (:id page)) + #{(:id main-child)} + (fn [shape] + (-> shape + (assoc-in [:content :children 0 :children 0 :children 0 :font-size] "32") + (assoc-in [:content :children 0 :children 0 :children 0 :text] "Bye"))) + (:objects page) + {}) + + updated-file (thf/apply-changes file changes1) + + changes2 (cll/generate-sync-file-changes (pcb/empty-changes) + nil + :components + (:id updated-file) + (thi/id :component1) + (:id updated-file) + {(:id updated-file) updated-file} + (:id updated-file)) + + file' (thf/apply-changes updated-file changes2) + + ;; ==== Get + copy-child' (ths/get-shape file' :copy-child) + line (get-in copy-child' [:content :children 0 :children 0 :children 0])] + ;; The attr doesn't change, because it was touched + (t/is (= "14" (:font-size line))) + ;; The text doesn't change, because it was touched + (t/is (= "Hi" (:text line))))) + +(t/deftest test-sync-updated-structure-same-attrs-copy-when-changed-attribute + (let [;; ==== Setup + file0 (-> (thf/sample-file :file1) + (tho/add-frame-with-text :main-root :main-child "hello world") + (thc/make-component :component1 :main-root) + (thc/instantiate-component :component1 :copy-root {:children-labels [:copy-child]})) + page0 (thf/current-page file0) + copy-child (ths/get-shape file0 :copy-child) + + changes (cls/generate-update-shapes (pcb/empty-changes nil (:id page0)) + #{(:id copy-child)} + (fn [shape] + (let [line (get-in shape [:content :children 0 :children 0 :children 0])] + (update-in shape [:content :children 0 :children 0 :children] + #(conj % line)))) + (:objects (thf/current-page file0)) + {}) + file (thf/apply-changes file0 changes) + main-child (ths/get-shape file :main-child) + page (thf/current-page file) + + ;; ==== Action + changes1 (cls/generate-update-shapes (pcb/empty-changes nil (:id page)) + #{(:id main-child)} + (fn [shape] + ;; Update the attrs on all the content tree + (-> shape + (assoc-in [:content :children 0 :children 0 :children 0 :font-size] "32") + (assoc-in [:content :children 0 :children 0 :font-size] "32"))) + (:objects page) + {}) + + updated-file (thf/apply-changes file changes1) + + changes2 (cll/generate-sync-file-changes (pcb/empty-changes) + nil + :components + (:id updated-file) + (thi/id :component1) + (:id updated-file) + {(:id updated-file) updated-file} + (:id updated-file)) + + file' (thf/apply-changes updated-file changes2) + + ;; ==== Get + copy-child' (ths/get-shape file' :copy-child) + line (get-in copy-child' [:content :children 0 :children 0 :children 0])] + ;; The attr is updated because all the attrs on the structure are equal + (t/is (= "32" (:font-size line))) + (t/is (= "hello world" (:text line))))) + +(t/deftest test-sync-updated-structure-same-attrs-copy-when-changed-text + (let [;; ==== Setup + file0 (-> (thf/sample-file :file1) + (tho/add-frame-with-text :main-root :main-child "hello world") + (thc/make-component :component1 :main-root) + (thc/instantiate-component :component1 :copy-root {:children-labels [:copy-child]})) + page0 (thf/current-page file0) + copy-child (ths/get-shape file0 :copy-child) + + changes (cls/generate-update-shapes (pcb/empty-changes nil (:id page0)) + #{(:id copy-child)} + (fn [shape] + (let [line (get-in shape [:content :children 0 :children 0 :children 0])] + (update-in shape [:content :children 0 :children 0 :children] + #(conj % line)))) + (:objects (thf/current-page file0)) + {}) + file (thf/apply-changes file0 changes) + main-child (ths/get-shape file :main-child) + page (thf/current-page file) + + ;; ==== Action + changes1 (cls/generate-update-shapes (pcb/empty-changes nil (:id page)) + #{(:id main-child)} + (fn [shape] + (assoc-in shape [:content :children 0 :children 0 :children 0 :text] "Bye")) + (:objects page) + {}) + + updated-file (thf/apply-changes file changes1) + + changes2 (cll/generate-sync-file-changes (pcb/empty-changes) + nil + :components + (:id updated-file) + (thi/id :component1) + (:id updated-file) + {(:id updated-file) updated-file} + (:id updated-file)) + + file' (thf/apply-changes updated-file changes2) + + ;; ==== Get + copy-child' (ths/get-shape file' :copy-child) + line (get-in copy-child' [:content :children 0 :children 0 :children 0])] + (t/is (= "14" (:font-size line))) + ;; The text doesn't change, because the structure was touched + (t/is (= "hello world" (:text line))))) + +(t/deftest test-sync-updated-structure-same-attrs-copy-when-changed-both + (let [;; ==== Setup + file0 (-> (thf/sample-file :file1) + (tho/add-frame-with-text :main-root :main-child "hello world") + (thc/make-component :component1 :main-root) + (thc/instantiate-component :component1 :copy-root {:children-labels [:copy-child]})) + page0 (thf/current-page file0) + copy-child (ths/get-shape file0 :copy-child) + + changes (cls/generate-update-shapes (pcb/empty-changes nil (:id page0)) + #{(:id copy-child)} + (fn [shape] + (let [line (get-in shape [:content :children 0 :children 0 :children 0])] + (update-in shape [:content :children 0 :children 0 :children] + #(conj % line)))) + (:objects (thf/current-page file0)) + {}) + file (thf/apply-changes file0 changes) + main-child (ths/get-shape file :main-child) + page (thf/current-page file) + + ;; ==== Action + changes1 (cls/generate-update-shapes (pcb/empty-changes nil (:id page)) + #{(:id main-child)} + (fn [shape] + ;; Update the attrs on all the content tree + (-> shape + (assoc-in [:content :children 0 :children 0 :children 0 :font-size] "32") + (assoc-in [:content :children 0 :children 0 :font-size] "32") + (assoc-in [:content :children 0 :children 0 :children 0 :text] "Bye"))) + (:objects page) + {}) + + updated-file (thf/apply-changes file changes1) + + changes2 (cll/generate-sync-file-changes (pcb/empty-changes) + nil + :components + (:id updated-file) + (thi/id :component1) + (:id updated-file) + {(:id updated-file) updated-file} + (:id updated-file)) + + file' (thf/apply-changes updated-file changes2) + + ;; ==== Get + copy-child' (ths/get-shape file' :copy-child) + line (get-in copy-child' [:content :children 0 :children 0 :children 0])] + ;; The attr is updated because all the attrs on the structure are equal + (t/is (= "32" (:font-size line))) + ;; The text doesn't change, because the structure was touched + (t/is (= "hello world" (:text line))))) + +(t/deftest test-sync-updated-structure-diff-attrs-copy-when-changed-attribute + (let [;; ==== Setup + file0 (-> (thf/sample-file :file1) + (tho/add-frame-with-text :main-root :main-child "hello world") + (thc/make-component :component1 :main-root) + (thc/instantiate-component :component1 :copy-root {:children-labels [:copy-child]})) + page0 (thf/current-page file0) + copy-child (ths/get-shape file0 :copy-child) + + changes (cls/generate-update-shapes (pcb/empty-changes nil (:id page0)) + #{(:id copy-child)} + (fn [shape] + (let [line (-> (get-in shape [:content :children 0 :children 0 :children 0]) + (assoc :font-weight "700"))] + (update-in shape [:content :children 0 :children 0 :children] + #(conj % line)))) + (:objects (thf/current-page file0)) + {}) + file (thf/apply-changes file0 changes) + main-child (ths/get-shape file :main-child) + page (thf/current-page file) + + ;; ==== Action + changes1 (cls/generate-update-shapes (pcb/empty-changes nil (:id page)) + #{(:id main-child)} + (fn [shape] + ;; Update the attrs on all the content tree + (-> shape + (assoc-in [:content :children 0 :children 0 :children 0 :font-size] "32") + (assoc-in [:content :children 0 :children 0 :font-size] "32"))) + (:objects page) + {}) + + updated-file (thf/apply-changes file changes1) + + changes2 (cll/generate-sync-file-changes (pcb/empty-changes) + nil + :components + (:id updated-file) + (thi/id :component1) + (:id updated-file) + {(:id updated-file) updated-file} + (:id updated-file)) + + file' (thf/apply-changes updated-file changes2) + + ;; ==== Get + copy-child' (ths/get-shape file' :copy-child) + line (get-in copy-child' [:content :children 0 :children 0 :children 0])] + ;; The attr doesn't change, because not all the attrs on the structure are equal + (t/is (= "14" (:font-size line))) + (t/is (= "hello world" (:text line))))) + +(t/deftest test-sync-updated-structure-diff-attrs-copy-when-changed-text + (let [;; ==== Setup + file0 (-> (thf/sample-file :file1) + (tho/add-frame-with-text :main-root :main-child "hello world") + (thc/make-component :component1 :main-root) + (thc/instantiate-component :component1 :copy-root {:children-labels [:copy-child]})) + page0 (thf/current-page file0) + copy-child (ths/get-shape file0 :copy-child) + + changes (cls/generate-update-shapes (pcb/empty-changes nil (:id page0)) + #{(:id copy-child)} + (fn [shape] + (let [line (-> (get-in shape [:content :children 0 :children 0 :children 0]) + (assoc :font-weight "700"))] + (update-in shape [:content :children 0 :children 0 :children] + #(conj % line)))) + (:objects (thf/current-page file0)) + {}) + file (thf/apply-changes file0 changes) + main-child (ths/get-shape file :main-child) + page (thf/current-page file) + + ;; ==== Action + changes1 (cls/generate-update-shapes (pcb/empty-changes nil (:id page)) + #{(:id main-child)} + (fn [shape] + (assoc-in shape [:content :children 0 :children 0 :children 0 :text] "Bye")) + (:objects page) + {}) + + updated-file (thf/apply-changes file changes1) + + changes2 (cll/generate-sync-file-changes (pcb/empty-changes) + nil + :components + (:id updated-file) + (thi/id :component1) + (:id updated-file) + {(:id updated-file) updated-file} + (:id updated-file)) + + file' (thf/apply-changes updated-file changes2) + + ;; ==== Get + copy-child' (ths/get-shape file' :copy-child) + line (get-in copy-child' [:content :children 0 :children 0 :children 0])] + (t/is (= "14" (:font-size line))) + ;; The text doesn't change, because the structure was touched + (t/is (= "hello world" (:text line))))) + +(t/deftest test-sync-updated-structure-diff-attrs-copy-when-changed-both + (let [;; ==== Setup + file0 (-> (thf/sample-file :file1) + (tho/add-frame-with-text :main-root :main-child "hello world") + (thc/make-component :component1 :main-root) + (thc/instantiate-component :component1 :copy-root {:children-labels [:copy-child]})) + page0 (thf/current-page file0) + copy-child (ths/get-shape file0 :copy-child) + + changes (cls/generate-update-shapes (pcb/empty-changes nil (:id page0)) + #{(:id copy-child)} + (fn [shape] + (let [line (-> (get-in shape [:content :children 0 :children 0 :children 0]) + (assoc :font-weight "700"))] + (update-in shape [:content :children 0 :children 0 :children] + #(conj % line)))) + (:objects (thf/current-page file0)) + {}) + file (thf/apply-changes file0 changes) + main-child (ths/get-shape file :main-child) + page (thf/current-page file) + + ;; ==== Action + changes1 (cls/generate-update-shapes (pcb/empty-changes nil (:id page)) + #{(:id main-child)} + (fn [shape] + ;; Update the attrs on all the content tree + (-> shape + (assoc-in [:content :children 0 :children 0 :children 0 :font-size] "32") + (assoc-in [:content :children 0 :children 0 :font-size] "32") + (assoc-in [:content :children 0 :children 0 :children 0 :text] "Bye"))) + (:objects page) + {}) + + updated-file (thf/apply-changes file changes1) + + changes2 (cll/generate-sync-file-changes (pcb/empty-changes) + nil + :components + (:id updated-file) + (thi/id :component1) + (:id updated-file) + {(:id updated-file) updated-file} + (:id updated-file)) + + file' (thf/apply-changes updated-file changes2) + + ;; ==== Get + copy-child' (ths/get-shape file' :copy-child) + line (get-in copy-child' [:content :children 0 :children 0 :children 0])] + ;; The attr doesn't change, because not all the attrs on the structure are equal + (t/is (= "14" (:font-size line))) + ;; The text doesn't change, because the structure was touched + (t/is (= "hello world" (:text line))))) \ No newline at end of file diff --git a/common/test/common_tests/logic/text_touched_test.cljc b/common/test/common_tests/logic/text_touched_test.cljc new file mode 100644 index 0000000000..7aacf4fe52 --- /dev/null +++ b/common/test/common_tests/logic/text_touched_test.cljc @@ -0,0 +1,132 @@ +;; 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 INC + +(ns common-tests.logic.text-touched-test + (:require + [app.common.files.changes-builder :as pcb] + [app.common.logic.shapes :as cls] + [app.common.test-helpers.components :as thc] + [app.common.test-helpers.compositions :as tho] + [app.common.test-helpers.files :as thf] + [app.common.test-helpers.ids-map :as thi] + [app.common.test-helpers.shapes :as ths] + [clojure.test :as t])) + +(t/use-fixtures :each thi/test-fixture) + +(t/deftest test-text-copy-changed-attribute + (let [;; ==== Setup + file (-> (thf/sample-file :file1) + (tho/add-frame-with-text :main-root :main-child "hello world") + (thc/make-component :component1 :main-root) + (thc/instantiate-component :component1 :copy-root {:children-labels [:copy-child]})) + page (thf/current-page file) + copy-child (ths/get-shape file :copy-child) + + ;; ==== Action + changes (cls/generate-update-shapes (pcb/empty-changes nil (:id page)) + #{(:id copy-child)} + (fn [shape] + (assoc-in shape [:content :children 0 :children 0 :font-size] "32")) + (:objects page) + {}) + + file' (thf/apply-changes file changes) + copy-child' (ths/get-shape file' :copy-child)] + (t/is (= #{:content-group :text-content-attribute} (:touched copy-child'))))) + +(t/deftest test-text-copy-changed-text + (let [;; ==== Setup + file (-> (thf/sample-file :file1) + (tho/add-frame-with-text :main-root :main-child "hello world") + (thc/make-component :component1 :main-root) + (thc/instantiate-component :component1 :copy-root {:children-labels [:copy-child]})) + page (thf/current-page file) + copy-child (ths/get-shape file :copy-child) + + ;; ==== Action + changes (cls/generate-update-shapes (pcb/empty-changes nil (:id page)) + #{(:id copy-child)} + (fn [shape] + (assoc-in shape [:content :children 0 :children 0 :text] "Bye")) + (:objects page) + {}) + + file' (thf/apply-changes file changes) + copy-child' (ths/get-shape file' :copy-child)] + (t/is (= #{:content-group :text-content-text} (:touched copy-child'))))) + +(t/deftest test-text-copy-changed-both + (let [;; ==== Setup + file (-> (thf/sample-file :file1) + (tho/add-frame-with-text :main-root :main-child "hello world") + (thc/make-component :component1 :main-root) + (thc/instantiate-component :component1 :copy-root {:children-labels [:copy-child]})) + page (thf/current-page file) + copy-child (ths/get-shape file :copy-child) + + ;; ==== Action + changes (cls/generate-update-shapes (pcb/empty-changes nil (:id page)) + #{(:id copy-child)} + (fn [shape] + (-> shape + (assoc-in [:content :children 0 :children 0 :font-size] "32") + (assoc-in [:content :children 0 :children 0 :text] "Bye"))) + (:objects page) + {}) + + file' (thf/apply-changes file changes) + copy-child' (ths/get-shape file' :copy-child)] + (t/is (= #{:content-group :text-content-attribute :text-content-text} (:touched copy-child'))))) + +(t/deftest test-text-copy-changed-structure-same-attrs + (let [;; ==== Setup + file (-> (thf/sample-file :file1) + (tho/add-frame-with-text :main-root :main-child "hello world") + (thc/make-component :component1 :main-root) + (thc/instantiate-component :component1 :copy-root {:children-labels [:copy-child]})) + page (thf/current-page file) + copy-child (ths/get-shape file :copy-child) + + ;; ==== Action + changes (cls/generate-update-shapes (pcb/empty-changes nil (:id page)) + #{(:id copy-child)} + (fn [shape] + (let [line (get-in shape [:content :children 0 :children 0])] + (update-in shape [:content :children 0 :children] + #(conj % line)))) + (:objects page) + {}) + + file' (thf/apply-changes file changes) + copy-child' (ths/get-shape file' :copy-child)] + (t/is (= #{:content-group :text-content-structure :text-content-structure-same-attrs} (:touched copy-child'))))) + +(t/deftest test-text-copy-changed-structure-diff-attrs + (let [;; ==== Setup + file (-> (thf/sample-file :file1) + (tho/add-frame-with-text :main-root :main-child "hello world") + (thc/make-component :component1 :main-root) + (thc/instantiate-component :component1 :copy-root {:children-labels [:copy-child]})) + page (thf/current-page file) + copy-child (ths/get-shape file :copy-child) + + ;; ==== Action + changes (cls/generate-update-shapes (pcb/empty-changes nil (:id page)) + #{(:id copy-child)} + (fn [shape] + (let [line (-> shape + (get-in [:content :children 0 :children 0]) + (assoc :font-size "32"))] + (update-in shape [:content :children 0 :children] + #(conj % line)))) + (:objects page) + {}) + + file' (thf/apply-changes file changes) + copy-child' (ths/get-shape file' :copy-child)] + (t/is (= #{:content-group :text-content-structure} (:touched copy-child'))))) +