diff --git a/CHANGES.md b/CHANGES.md index f47c0905a3..8feaf09197 100644 --- a/CHANGES.md +++ b/CHANGES.md @@ -80,6 +80,7 @@ A non exhaustive list of changes: - Deselect layers (and path nodes) with Ctrl+Shift+Drag [Github #2509](https://github.com/penpot/penpot/issues/2509) - Copy to SVG from contextual menu [Github #838](https://github.com/penpot/penpot/issues/838) - Add styles for Inkeep Chat at workspace [Taiga #10708](https://tree.taiga.io/project/penpot/us/10708) +- On components overrides, separate the content of the text from the rest of properties [Taiga #7434](https://tree.taiga.io/project/penpot/us/7434) - Add configuration for air gapped installations with Docker - Support system color scheme [Github #5030](https://github.com/penpot/penpot/issues/5030) diff --git a/common/src/app/common/files/migrations.cljc b/common/src/app/common/files/migrations.cljc index 8beef88d5a..282bfa6c6a 100644 --- a/common/src/app/common/files/migrations.cljc +++ b/common/src/app/common/files/migrations.cljc @@ -31,6 +31,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])) @@ -1327,6 +1328,33 @@ (update :pages-index d/update-vals update-container) (d/update-when :components d/update-vals update-container)))) + +(defmethod migrate-data "0004-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" @@ -1385,4 +1413,5 @@ "0002-normalize-bool-content" "0002-clean-shape-interactions" "0003-fix-root-shape" - "0003-convert-path-content"])) + "0003-convert-path-content" + "0004-add-partial-text-touched-flags"])) diff --git a/common/src/app/common/logic/libraries.cljc b/common/src/app/common/logic/libraries.cljc index fd2922f384..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] @@ -1663,24 +1664,46 @@ {:type :reg-objects :shapes all-parents})])))) + (defn- add-update-attr-operations - [attr dest-shape origin-shape roperations uoperations touched] - (let [;; position-data is a special case because can be affected by :geometry-group and :content-group + [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 ;; so, if the position-data changes but the geometry is touched we need to reset the position-data ;; so it's calculated again reset-pos-data? (and (cfh/text-shape? origin-shape) (= attr :position-data) - (not= (get origin-shape attr) (get dest-shape attr)) + (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 :attr attr - :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 - :else (get origin-shape attr)) + :val val :ignore-touched true} uoperation {:type :set :attr attr @@ -1689,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. @@ -1731,12 +1781,30 @@ :always (generate-update-tokens container dest-shape origin-shape touched omit-touched?)) - (let [attr-group (get ctk/sync-attrs attr) + (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? + ;; 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 (or (= (get origin-shape attr) (get dest-shape attr)) - (and (touched attr-group) omit-touched?)) + (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'))))))) @@ -1771,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/test_helpers/compositions.cljc b/common/src/app/common/test_helpers/compositions.cljc index 2d890844a7..ff87d6e15d 100644 --- a/common/src/app/common/test_helpers/compositions.cljc +++ b/common/src/app/common/test_helpers/compositions.cljc @@ -14,7 +14,9 @@ [app.common.test-helpers.components :as thc] [app.common.test-helpers.files :as thf] [app.common.test-helpers.shapes :as ths] - [app.common.types.container :as ctn])) + [app.common.text :as txt] + [app.common.types.container :as ctn] + [app.common.types.shape :as cts])) ;; ----- File building @@ -58,6 +60,18 @@ :parent-label frame-label} child-params)))) +(defn add-frame-with-text + [file frame-label child-label text & {:keys [frame-params child-params]}] + (let [shape (-> (cts/setup-shape {:type :text :x 0 :y 0 :grow-type :auto-width}) + (txt/change-text text) + (assoc :position-data nil + :parent-label frame-label))] + (-> file + (add-frame frame-label frame-params) + (ths/add-sample-shape child-label + (merge shape + child-params))))) + (defn add-minimal-component [file component-label root-label & {:keys [component-params root-params]}] diff --git a/common/src/app/common/types/container.cljc b/common/src/app/common/types/container.cljc index f087088a66..6ca767f7cc 100644 --- a/common/src/app/common/types/container.cljc +++ b/common/src/app/common/types/container.cljc @@ -18,8 +18,10 @@ [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])) + [app.common.uuid :as uuid] + [clojure.set :as set])) ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; ;; SCHEMA @@ -534,8 +536,6 @@ indicating if shape is touched or not." [shape attr val & {:keys [ignore-touched ignore-geometry]}] (let [group (get ctk/sync-attrs attr) - token-groups (when (= attr :applied-tokens) - (get-token-groups shape val)) shape-val (get shape attr) ignore? @@ -566,22 +566,33 @@ (gsh/close-attrs? attr val shape-val)) touched? - (and group (not equal?) (not (and ignore-geometry is-geometry?)))] + (and group + (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} 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. ;; In some cases we need to ignore touched only if the attribute is ;; geometric (position, width or transformation). (and in-copy? - (or (and group (not equal?)) (seq token-groups)) - (not ignore?) (not (and ignore-geometry is-geometry?))) + (not-empty groups) + (not ignore?) + (not (and ignore-geometry is-geometry?))) (-> (update :touched (fn [touched] (reduce #(ctk/set-touched-group %1 %2) touched - (if group - (cons group token-groups) - token-groups)))) + groups))) (dissoc :remote-synced)) (nil? val) diff --git a/common/src/app/common/types/file.cljc b/common/src/app/common/types/file.cljc index 0c0b8b7219..c6765bbd83 100644 --- a/common/src/app/common/types/file.cljc +++ b/common/src/app/common/types/file.cljc @@ -299,7 +299,6 @@ (ctkl/get-component (:data component-file) (:component-id head-shape) include-deleted?))] (when (some? component) (get-ref-shape (:data component-file) component shape :with-context? with-context?))))] - (some find-ref-shape-in-head (ctn/get-parent-heads (:objects container) shape)))) (defn advance-shape-ref diff --git a/common/src/app/common/types/text.cljc b/common/src/app/common/types/text.cljc new file mode 100644 index 0000000000..4d097c48a8 --- /dev/null +++ b/common/src/app/common/types/text.cljc @@ -0,0 +1,144 @@ +;; 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 app.common.types.text + (:require + [app.common.data.macros :as dm] + [clojure.set :as set])) + +(defn- compare-text-content + "Given two content text structures, conformed by maps and vectors, + compare them, and returns a set with the type of differences. + The possibilities are :text-content-text :text-content-attribute and :text-content-structure." + [a b] + (cond + ;; If a and b are equal, there is no diff + (= a b) + #{} + + ;; If types are different, the structure is different + (not= (type a) (type b)) + #{:text-content-structure} + + ;; If they are maps, check the keys + (map? a) + (let [keys (-> (set/union (set (keys a)) (set (keys b))) + (disj :key))] ;; We have to ignore :key because it is a draft artifact + (reduce + (fn [acc k] + (let [v1 (get a k) + v2 (get b k)] + (cond + ;; If the key is :children, keep digging + (= k :children) + (if (not= (count v1) (count v2)) + #{:text-content-structure} + (into acc + (apply set/union + (map #(compare-text-content %1 %2) v1 v2)))) + + ;; If the key is :text, and they are different, it is a text differece + (= k :text) + (if (not= v1 v2) + (conj acc :text-content-text) + acc) + + :else + ;; If the key is not :text, and they are different, it is an attribute differece + (if (not= v1 v2) + (conj acc :text-content-attribute) + acc)))) + #{} + keys)) + + :else + #{:text-content-structure})) + + +(defn equal-attrs? + "Given a text structure, and a map of attrs, check that all the internal attrs in + paragraphs and sentences have the same attrs" + [item attrs] + (let [item-attrs (dissoc item :text :type :key :children)] + (and + (or (empty? item-attrs) + (= attrs (dissoc item :text :type :key :children))) + (every? #(equal-attrs? % attrs) (:children item))))) + +(defn get-first-paragraph-text-attrs + "Given a content text structure, extract it's first paragraph + text attrs" + [content] + (-> content + (dm/get-in [:children 0 :children 0]) + (dissoc :text :type :key :children))) + +(defn get-diff-type + "Given two content text structures, conformed by maps and vectors, + compare them, and returns a set with the type of differences. + The possibilities are :text-content-text :text-content-attribute, + :text-content-structure and :text-content-structure-same-attrs." + [a b] + (let [diff-type (compare-text-content a b)] + (if-not (contains? diff-type :text-content-structure) + diff-type + (let [;; get attrs of the first paragraph of the first paragraph-set + attrs (get-first-paragraph-text-attrs a)] + (if (and (equal-attrs? a attrs) + (equal-attrs? b attrs)) + #{:text-content-structure :text-content-structure-same-attrs} + diff-type))))) + +;; TODO We know that there are cases that the blocks of texts are separated +;; differently: ["one" " " "two"], ["one " "two"], ["one" " two"] +;; so this won't work for 100% of the situations. But it's good enough for now, +;; we can iterate on the solution again in the future if needed. +(defn equal-structure? + "Given two content text structures, check that the structures are equal. + This means that all the :children keys at any level has the same number of + entries" + [a b] + (cond + (not= (type a) (type b)) + false + + (map? a) + (let [children-a (:children a) + children-b (:children b)] + (if (not= (count children-a) (count children-b)) + false + (every? true? + (map equal-structure? children-a children-b)))) + + :else + true)) + + +(defn copy-text-keys + "Given two equal content text structures, deep copy all the keys :text + from origin to destiny" + [origin destiny] + (cond + (map? origin) + (into {} + (for [k (keys origin) :when (not= k :key)] ;; We ignore :key because it is a draft artifact + (cond + (= :children k) + [k (vec (map #(copy-text-keys %1 %2) (get origin k) (get destiny k)))] + (= :text k) + [k (:text origin)] + :else + [k (get destiny k)]))))) + +(defn copy-attrs-keys + "Given a content text structure and a list of attrs, copy that + attrs values on all the content tree" + [content attrs] + (into {} + (for [[k v] content] + (if (= :children k) + [k (vec (map #(copy-attrs-keys %1 attrs) v))] + [k (get attrs k v)])))) 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'))))) + diff --git a/common/test/common_tests/types/text_test.cljc b/common/test/common_tests/types/text_test.cljc new file mode 100644 index 0000000000..b72f1387c3 --- /dev/null +++ b/common/test/common_tests/types/text_test.cljc @@ -0,0 +1,88 @@ +;; 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.types.text-test + (:require + + [app.common.text :as txt] + [app.common.types.shape :as cts] + [app.common.types.text :as cttx] + [clojure.test :as t :include-macros true])) + +(def content-base (-> (cts/setup-shape {:type :text :x 0 :y 0 :grow-type :auto-width}) + (txt/change-text "hello world") + (assoc :position-data nil) + :content)) + +(def content-changed-text (assoc-in content-base [:children 0 :children 0 :children 0 :text] "changed")) +(def content-changed-attr (assoc-in content-base [:children 0 :children 0 :children 0 :font-size] "32")) +(def content-changed-both (-> content-base + (assoc-in [:children 0 :children 0 :children 0 :text] "changed") + (assoc-in [:children 0 :children 0 :children 0 :font-size] "32"))) +(def line (get-in content-base [:children 0 :children 0 :children 0])) +(def content-changed-structure (update-in content-base [:children 0 :children 0 :children] + #(conj % (assoc line :font-weight "700")))) +(def content-changed-structure-same-attrs (update-in content-base [:children 0 :children 0 :children] + #(conj % line))) + +(t/deftest test-get-diff-type + (let [diff-text (cttx/get-diff-type content-base content-changed-text) + diff-attr (cttx/get-diff-type content-base content-changed-attr) + diff-both (cttx/get-diff-type content-base content-changed-both) + diff-structure (cttx/get-diff-type content-base content-changed-structure) + diff-structure-same-attrs (cttx/get-diff-type content-base content-changed-structure-same-attrs)] + (t/is (= #{:text-content-text} diff-text)) + (t/is (= #{:text-content-attribute} diff-attr)) + (t/is (= #{:text-content-text :text-content-attribute} diff-both)) + (t/is (= #{:text-content-structure} diff-structure)) + (t/is (= #{:text-content-structure :text-content-structure-same-attrs} diff-structure-same-attrs)))) + + +(t/deftest test-equal-structure + (t/is (true? (cttx/equal-structure? content-base content-changed-text))) + (t/is (true? (cttx/equal-structure? content-base content-changed-attr))) + (t/is (true? (cttx/equal-structure? content-base content-changed-both))) + (t/is (false? (cttx/equal-structure? content-base content-changed-structure)))) + + +(t/deftest test-copy-text-keys + (let [copy-base-to-changed-text (cttx/copy-text-keys content-base content-changed-text) + copy-changed-text-to-base (cttx/copy-text-keys content-changed-text content-base) + + copy-base-to-changed-attr (cttx/copy-text-keys content-base content-changed-attr) + + copy-changes-text-to-changed-attr (cttx/copy-text-keys content-changed-text content-changed-attr) + updates-text-in-changed-attr (assoc-in content-changed-attr [:children 0 :children 0 :children 0 :text] "changed")] + + ;; If we copy the text of the base to the content-changed-text, the result is equal than the base + (t/is (= copy-base-to-changed-text content-base)) + + ;; If we copy the text of the content-changed-text to the base, the result is equal than the content-changed-text + (t/is (= copy-changed-text-to-base content-changed-text)) + + ;; If we copy the text of the base to the content-changed-attr, it doesn't nothing because the text were equal + (t/is (= copy-base-to-changed-attr content-changed-attr)) + + ;; If we copy the text of the content-changed-text to the content-changed-attr, it keeps the changes on the attrs + ;; and the changes on the texts + (t/is (= copy-changes-text-to-changed-attr updates-text-in-changed-attr)))) + + +(t/deftest test-copy-attrs-keys + (let [attrs (-> (cttx/get-first-paragraph-text-attrs content-changed-structure-same-attrs) + (assoc :font-size "32")) + updated (cttx/copy-attrs-keys content-changed-structure-same-attrs attrs) + get-font-sizes (fn get-font-sizes [fonts item] + (let [font-size (:font-size item) + fonts (if font-size (conj fonts font-size) fonts)] + (if (seq (:children item)) + (reduce get-font-sizes fonts (:children item)) + fonts))) + original-font-sizes (get-font-sizes [] content-changed-structure-same-attrs) + updated-font-sizes (get-font-sizes [] updated)] + + (t/is (every? #(= % "14") original-font-sizes)) + (t/is (every? #(= % "32") updated-font-sizes))))