diff --git a/common/src/app/common/files/changes_builder.cljc b/common/src/app/common/files/changes_builder.cljc index ea0fb5f24f..5f4b60897e 100644 --- a/common/src/app/common/files/changes_builder.cljc +++ b/common/src/app/common/files/changes_builder.cljc @@ -585,7 +585,8 @@ (defn update-shapes "Calculate the changes and undos to be done when a function is applied to a - single object" + single object. `ignore-touched` is a boolean, or the set of attributes whose + change on a copy is derived and touches nothing." ([changes ids update-fn] (update-shapes changes ids update-fn nil)) @@ -612,7 +613,9 @@ changed? (conj {:type :set :attr attr :val new-val :ignore-geometry ignore-geometry? - :ignore-touched ignore-touched})) + :ignore-touched (if (set? ignore-touched) + (contains? ignore-touched attr) + ignore-touched)})) uops (cond-> uops diff --git a/common/src/app/common/logic/libraries.cljc b/common/src/app/common/logic/libraries.cljc index 215fe4ffcf..b360942448 100644 --- a/common/src/app/common/logic/libraries.cljc +++ b/common/src/app/common/logic/libraries.cljc @@ -971,6 +971,29 @@ [[] '()] updatable-attrs)) +(defn- automatic-name + "The name the editors give a text after its `content`, or nil when it has + no letters." + [content] + (let [name (txt/generate-shape-name (txt/content->text content))] + (when (not= name "") name))) + +(defn- synced-text-name + "The name an untouched copy text takes from `origin-shape`, its main: the + main's own name, or, when that one follows the main's letters, the name + that follows the letters the copy shows after the sync." + [dest-shape origin-shape touched omit-touched?] + (let [origin-name (:name origin-shape)] + (if (and (some? origin-name) + (= origin-name (automatic-name (:content origin-shape)))) + (let [content (if (and omit-touched? (touched :content-group)) + (txt/merge-overrides (:content dest-shape) + (:content origin-shape) + (txt/text-overrides touched)) + (:content origin-shape))] + (or (automatic-name content) origin-name)) + origin-name))) + (defn- sync-attr-operations "The `[attr value]` pairs that sync `attr` from `origin-shape`, a main shape placed like `dest-shape`, into `dest-shape`, its copy. With @@ -1003,8 +1026,15 @@ (= :content attr) (touched sync-group)) + ;; the name of a text may follow its letters + text-name? + (and (cfh/text-shape? origin-shape) + (cfh/text-shape? dest-shape) + (= :name attr)) + skip? - (or (= (get origin-shape attr) (get dest-shape attr)) + (or (and (not text-name?) + (= (get origin-shape attr) (get dest-shape attr))) (and (touched sync-group) omit-touched? ;; When it is a text-partial-change, we should generate operations @@ -1025,6 +1055,9 @@ (:content origin-shape) (txt/text-overrides touched)) + text-name? + (synced-text-name dest-shape origin-shape touched omit-touched?) + :else (get origin-shape attr)))] diff --git a/common/src/app/common/logic/shapes.cljc b/common/src/app/common/logic/shapes.cljc index 31fc414f54..2b9c697ac1 100644 --- a/common/src/app/common/logic/shapes.cljc +++ b/common/src/app/common/logic/shapes.cljc @@ -106,7 +106,7 @@ (-> (pcb/update-shapes grid-ids ctl/assign-cell-positions {:with-objects? true}) (pcb/reorder-grid-children ids)) - (not ignore-touched) + (not (true? ignore-touched)) (generate-unapply-tokens objects changed-sub-attr changed-item-index))] changes)) diff --git a/common/src/app/common/test_helpers/composable/comp/cases.cljc b/common/src/app/common/test_helpers/composable/comp/cases.cljc index 8a289afc91..89a47abd2c 100644 --- a/common/src/app/common/test_helpers/composable/comp/cases.cljc +++ b/common/src/app/common/test_helpers/composable/comp/cases.cljc @@ -37,7 +37,7 @@ W — one undo node undoes several steps over nested cascades. X — an undo committed before the edit's sync leaves copies unchanged (frontend only). - AJ — one undo after a nested sync reverts only the reflow (F43, frontend + AJ — one undo after a nested sync reverts the change and its reflow (frontend only). Y — a text override merges with a main text change per subgroup. Z — a move of a main child reaches the copies (frontend only). @@ -85,6 +85,7 @@ [app.common.types.components-list :as ctkl] [app.common.types.shape-tree :as ctst] [app.common.types.shape.layout :as ctl] + [app.common.types.text :as txt] [app.common.uuid :as uuid] [clojure.test :as t] [clojure.walk :as walk])) @@ -653,7 +654,8 @@ the same for the font size. An edited copy has `:content-group` and the group of its edit touched; an unedited one has nothing touched. The text starts with the name generated from its content, so an edit of its characters renames - it, an automatic rename that must not touch `:name-group` (decision (d))." + it, an automatic rename that must not touch `:name-group` (decision (d)), + and the copy's name keeps following the letters it shows." [] (let [copy-edit (tm/one-of [(n/edit-text :copy-instance :text "copy text") (n/edit-text :copy-instance :font-size "32")]) @@ -667,6 +669,8 @@ main (setup/main-instance s) edited (tm/get-choice s copy-edit)] (t/is (n/has-property-of (tm/get-choice s main-edit) main)) + (t/is (= (txt/generate-shape-name (txt/content->text (:content copy))) + (:name copy))) (doseq [edit [:text :font-size]] (t/is (= (if (= edit (:edit edited)) (:value edited) @@ -677,13 +681,6 @@ (nil? edited) (t/is (empty? (:touched copy))) - ;; the automatic rename touches the name - (= :text (:edit edited)) - (tm/known-failure "F27" - #{:content-group :text-content-text} - #{:content-group :text-content-text :name-group} - (:touched copy)) - :else (t/is (= #{:content-group (subgroup (:edit edited))} (:touched copy))))))})) diff --git a/common/src/app/common/test_helpers/composable/comp/nodes.cljc b/common/src/app/common/test_helpers/composable/comp/nodes.cljc index e013e12a28..6810186388 100644 --- a/common/src/app/common/test_helpers/composable/comp/nodes.cljc +++ b/common/src/app/common/test_helpers/composable/comp/nodes.cljc @@ -254,11 +254,13 @@ (let [the-file (tm/file situation) shape-id (tm/target-shape-id situation target) page (thf/current-page the-file) + ;; as the editor's commit, the name that follows the content is a + ;; derived write changes (cls/generate-update-shapes (pcb/empty-changes nil (:id page)) #{shape-id} #(edit-text-content % edit value) (:objects page) - {}) + {:ignore-touched #{:name}}) file' (thf/apply-changes the-file changes)] (-> situation (tm/with-file file') diff --git a/common/src/app/common/types/text.cljc b/common/src/app/common/types/text.cljc index 4aa1d5e29f..b85f6b10a5 100644 --- a/common/src/app/common/types/text.cljc +++ b/common/src/app/common/types/text.cljc @@ -242,8 +242,11 @@ (update :content transform)))) (defn generate-shape-name + "The name of a text whose letters are `text`: its first 280 characters, + each line break a space." [text] - (subs text 0 (min 280 (count text)))) + (let [text (str/replace text #"\s*\n\s*" " ")] + (subs text 0 (min 280 (count text))))) (defn- compare-text-attr "Compare two attribute values and return true if they are different. diff --git a/common/test/common_tests/logic/golden_master.edn b/common/test/common_tests/logic/golden_master.edn index 0c788be712..ae0d47f1e8 100644 --- a/common/test/common_tests/logic/golden_master.edn +++ b/common/test/common_tests/logic/golden_master.edn @@ -185,8 +185,8 @@ {:transcript "create-component {:component \"main\", :id sync-main-component}\n make-nested-component {:component \"main\", :level 0, :outer sync-main-outercomp-0}\n instantiate-copy {:component \"main\", :copy sync-main-copyhead-0}\n change-property {:target #fn, :property :fills, :value \"#0000ff\"}\n change-property {:target #fn, :property :fills, :value \"#00ff00\"}\n change-property {:target #fn, :property :fills, :value \"#aaaaaa\"}\n undo {:undone 6}", :states ["1de51816cdc0ad11" "ed2117b3a6ae1dd6" "c90876e1b44d00fb" "a5b1e4b00086c891" "f09bed0fa67d711c"]} {:transcript "create-component {:component \"main\", :id sync-main-component}\n make-nested-component {:component \"main\", :level 1, :outer sync-main-outercomp-1}\n make-nested-component {:component \"main\", :level 1, :outer sync-main-outercomp-1}\n instantiate-copy {:component \"main\", :copy sync-main-copyhead-0}\n change-property {:target #fn, :property :fills, :value \"#0000ff\"}\n change-property {:target #fn, :property :fills, :value \"#00ff00\"}\n change-property {:target #fn, :property :fills, :value \"#aaaaaa\"}\n undo {:undone 7}", :states ["262f632dd4a0ac41" "af99191229bac7b4" "8ba7cefbe6e101d8" "ab0a68e1af334a44" "987979e4d37001b6"]}] :y - [{:transcript "one-of -> recorded-choice\n edit-text {:target :copy-instance, :edit :text, :value \"copy text\"}\n edit-text {:target :main-instance, :edit :text, :value \"main text\"}", :states ["8cd35014bebff5a0" "260d9897b862470e" "01f96b240a9f7786"]} - {:transcript "one-of -> recorded-choice\n edit-text {:target :copy-instance, :edit :text, :value \"copy text\"}\n edit-text {:target :main-instance, :edit :font-size, :value \"24\"}", :states ["8cd35014bebff5a0" "9a2bfd72cc5a6898" "8df2236d989a127c"]} + [{:transcript "one-of -> recorded-choice\n edit-text {:target :copy-instance, :edit :text, :value \"copy text\"}\n edit-text {:target :main-instance, :edit :text, :value \"main text\"}", :states ["73a1f9665fb9d817" "c6b6d7c691dfcaa8" "728400277dd671c4"]} + {:transcript "one-of -> recorded-choice\n edit-text {:target :copy-instance, :edit :text, :value \"copy text\"}\n edit-text {:target :main-instance, :edit :font-size, :value \"24\"}", :states ["73a1f9665fb9d817" "85eac5dd891c03b4" "3eaf765ba42c63d1"]} {:transcript "one-of -> recorded-choice\n edit-text {:target :copy-instance, :edit :font-size, :value \"32\"}\n edit-text {:target :main-instance, :edit :text, :value \"main text\"}", :states ["c0b1a5e708231296" "75e32d376c1957f9" "b1213922be7ac4cb"]} {:transcript "one-of -> recorded-choice\n edit-text {:target :copy-instance, :edit :font-size, :value \"32\"}\n edit-text {:target :main-instance, :edit :font-size, :value \"24\"}", :states ["c0b1a5e708231296" "8eaab011168c2a4b" "14063d206179c049"]} {:transcript "one-of -> skip\n edit-text {:target :main-instance, :edit :text, :value \"main text\"}", :states ["6c385dbccb923ae0" "f357affeab5963f4"]} diff --git a/common/test/common_tests/logic/text_sync_test.cljc b/common/test/common_tests/logic/text_sync_test.cljc index 32296284fa..6b325d46c6 100644 --- a/common/test/common_tests/logic/text_sync_test.cljc +++ b/common/test/common_tests/logic/text_sync_test.cljc @@ -15,6 +15,7 @@ [app.common.test-helpers.files :as thf] [app.common.test-helpers.ids-map :as thi] [app.common.test-helpers.shapes :as ths] + [app.common.test-helpers.texts :as thtx] [app.common.types.text :as txt] [clojure.test :as t])) @@ -933,3 +934,47 @@ (t/is (= "copy text" (:text line))) (t/is (= "32" (:font-size line))) (t/is (= #{:content-group :text-content-text} (:touched copy-child'))))) + +(defn- type-letters + "The editor's commit of `text` (in the notation of `thtx`) into the text + `label`: the content, and the name that follows it, a derived write." + [file label text] + (let [page (thf/current-page file) + content (thtx/content text) + changes (cls/generate-update-shapes (pcb/empty-changes nil (:id page)) + #{(thi/id label)} + #(assoc % :content content + :name (txt/generate-shape-name (txt/content->text content))) + (:objects page) + {:ignore-touched #{:name}})] + (thf/apply-changes file changes))) + +(defn- sync-component1 + [file] + (thf/apply-changes file (cll/generate-sync-file-changes (pcb/empty-changes) nil :components + (:id file) (thi/id :component1) (:id file) + {(:id file) file} (:id file)))) + +(t/deftest the-automatic-name-of-a-copy-text-follows-its-letters + (let [file (-> (thf/sample-file :file1) + (thtx/add-text-component-with-copy [[["hello world"]]]) + (type-letters :main-child [[["hello world"]]]) + (sync-component1))] + (t/testing "an untouched copy takes the main's letters and name" + (let [file' (-> file (type-letters :main-child [[["main text"]]]) (sync-component1))] + (t/is (= "main text" (:name (ths/get-shape file' :copy-child)))) + (t/is (nil? (:touched (ths/get-shape file' :copy-child)))))) + (t/testing "a copy that typed its letters keeps the name they give" + (let [file' (-> file + (type-letters :copy-child [[["copy text"]]]) + (type-letters :main-child [[["main text"]]]) + (sync-component1)) + copy (ths/get-shape file' :copy-child)] + (t/is (= "copy text" (:name copy))) + (t/is (= #{:content-group :text-content-text} (:touched copy))))) + (t/testing "a main named by hand passes its name on" + (let [file' (-> file + (ths/update-shape :main-child :name "Title") + (sync-component1))] + (t/is (= "Title" (:name (ths/get-shape file' :copy-child)))))))) + diff --git a/common/test/common_tests/types/text_test.cljc b/common/test/common_tests/types/text_test.cljc index ace36f0c4d..ad61648a4f 100644 --- a/common/test/common_tests/types/text_test.cljc +++ b/common/test/common_tests/types/text_test.cljc @@ -368,3 +368,8 @@ (t/is (every? #(= % "14") original-font-sizes)) (t/is (every? #(= % "32") updated-font-sizes)))) + +(t/deftest the-name-of-a-text-with-several-paragraphs + (t/is (= "Hello world Ends Sunday" (cttx/generate-shape-name "Hello world\nEnds Sunday"))) + (t/is (= "One Two" (cttx/generate-shape-name "One \n\n Two"))) + (t/is (= 280 (count (cttx/generate-shape-name (apply str (repeat 300 "a"))))))) diff --git a/frontend/src/app/main/data/workspace/libraries.cljs b/frontend/src/app/main/data/workspace/libraries.cljs index 6f9d1f83e3..f98d151f13 100644 --- a/frontend/src/app/main/data/workspace/libraries.cljs +++ b/frontend/src/app/main/data/workspace/libraries.cljs @@ -1070,13 +1070,33 @@ (assoc state :updating-library true) (dissoc state :updating-library))))) +(defn- synced-text-ids + "The texts of the current page that grow with their content and that + `changes` write: their size comes from their main, so the WASM renderer + measures them again (the SVG renderer does when it draws them)." + [state changes] + (when (features/active-feature? state "render-wasm/v1") + (let [page-id (:current-page-id state) + objects (dsh/lookup-page-objects state page-id)] + (into [] + (comp (filter #(and (= :mod-obj (:type %)) (= page-id (:page-id %)))) + (map :id) + (distinct) + (filter #(let [shape (get objects %)] + (and (cfh/text-shape? shape) + (not= :fixed (:grow-type shape)))))) + (:redo-changes changes))))) + (defn- sync-file-frontend-events - [file-id changes updated-frames undo-group] + [file-id changes updated-frames text-ids undo-group] (rx/concat (rx/of (set-updating-library false) (ntf/hide {:tag :sync-dialog})) (when (seq (:redo-changes changes)) (rx/of (dch/commit-changes changes))) + (when (seq text-ids) + (rx/of (dwwt/resize-wasm-text-all text-ids {:skip-component-sync? true + :undo-group undo-group}))) (when-not (empty? updated-frames) (let [frames-by-page (group-by :page-id updated-frames)] (rx/merge @@ -1159,9 +1179,17 @@ pending-ids (sync-file-pending-ids file-id changes) + text-ids (when (= file-id current-file-id) + (synced-text-ids state changes)) + + ;; the texts measured again join the undo of the sync + undo-group (or undo-group + (when (seq text-ids) (uuid/next))) + changes (pcb/set-undo-group changes undo-group) + frontend-sync (sync-file-frontend-events - file-id changes updated-frames undo-group)] + file-id changes updated-frames text-ids undo-group)] (log/debug :msg "SYNC-FILE finished" :js/rchanges (log-changes (:redo-changes changes) diff --git a/frontend/src/app/main/data/workspace/texts.cljs b/frontend/src/app/main/data/workspace/texts.cljs index faa68099fc..e4198a9c48 100644 --- a/frontend/src/app/main/data/workspace/texts.cljs +++ b/frontend/src/app/main/data/workspace/texts.cljs @@ -295,6 +295,11 @@ (update :workspace-editor-state dissoc id) (update :workspace-new-text-shapes disj id)))))) +(def ^:private derived-attrs + "The attributes an edit of a text writes that follow its content: its name + and the size it grows to." + #{:name :selrect :points :x :y :width :height :transform :transform-inverse :position-data}) + (defn finalize-editor-state [id update-name?] (ptk/reify ::finalize-editor-state @@ -330,7 +335,8 @@ (cond-> (some? new-size) (gsh/transform-shape (ctm/change-size shape (:width new-size) (:height new-size)))))) - {:undo-group (when new-shape? id)}))))) + {:undo-group (when new-shape? id) + :ignore-touched derived-attrs}))))) (let [content (d/merge (ted/export-content content) (dissoc (:content shape) :children)) @@ -353,7 +359,8 @@ (assoc :name name)) (cond-> (or (some? width) (some? height)) (gsh/transform-shape (ctm/change-size shape width height)))))) - {:undo-group (when new-shape? id)})))))) + {:undo-group (when new-shape? id) + :ignore-touched derived-attrs})))))) (when (some? id) (rx/of (dws/deselect-shape id) @@ -1268,7 +1275,8 @@ final-shape-fn (fn [shape] (merge shape final-geom))] (-> base (pcb/with-objects objects-with-old) - (pcb/update-shapes [id] final-shape-fn {:attrs attrs})))) + ;; the size a text grows to is derived: it touches nothing + (pcb/update-shapes [id] final-shape-fn {:attrs attrs :ignore-touched true})))) (defn- build-finalize-commit-changes "Builds the commit changes for text finalization (content + geometry undo). @@ -1284,7 +1292,8 @@ (pcb/with-objects objects) (pcb/set-text-content id content original-content) (cond-> (and update-name? (some? name) (not= (:name shape*) name)) - (pcb/update-shapes [id] (fn [s] (assoc s :name name)) {:attrs [:name]})) + ;; the name follows the content: a derived write + (pcb/update-shapes [id] (fn [s] (assoc s :name name)) {:attrs [:name] :ignore-touched true})) (cond-> new-shape? (-> (pcb/set-undo-group id) (pcb/set-stack-undo? true)))) @@ -1394,12 +1403,19 @@ (assoc :name name)))) {:save-undo? finalize-save-undo-first? :stack-undo? effective-stack-undo? - :undo-group (when new-shape? id)}) + :undo-group (when new-shape? id) + ;; the name follows the content: a derived write + :ignore-touched #{:name}}) ;; Push the auto-grow geometry to WASM/app state; the commit persists it via `resize-geom`. ;; Skipped for a no-op finalize: applying it would record an undo transaction. + ;; An existing text's finalize commit holds the undo of that geometry, so this + ;; one records none. The size a text grows to is derived: it touches nothing. (when (and (some? resize-modifiers) (not finalize-no-op?)) - (dwm/apply-wasm-modifiers resize-modifiers {:undo-group (when new-shape? id)}))) + (dwm/apply-wasm-modifiers resize-modifiers + (if new-shape? + {:undo-group id :ignore-touched true} + {:save-undo? false :ignore-touched true})))) (when finalize? (rx/concat @@ -1455,7 +1471,8 @@ (assoc :name name)) (cond-> (or (some? width) (some? height)) (gsh/transform-shape (ctm/change-size shape width height)))))) - {:undo-group (when new-shape? id)})))))))) + {:undo-group (when new-shape? id) + :ignore-touched derived-attrs})))))))) (defn- editor-content-update "Event saving `content` from the WASM editor, renaming the shape after its text." @@ -1708,7 +1725,8 @@ new-name (txt/generate-shape-name (txt/content->text new-content))] (-> shape (assoc :content new-content) (assoc :name new-name))) shape)) - {:attrs #{:content :name} :undo-group undo-group})] + ;; the name follows the content: a derived write + {:attrs #{:content :name} :undo-group undo-group :ignore-touched #{:name}})] (rx/concat (rx/of update-event) (if (features/active-feature? state "render-wasm/v1") diff --git a/frontend/src/app/main/data/workspace/wasm_text.cljs b/frontend/src/app/main/data/workspace/wasm_text.cljs index 9ff79af325..555dd3681b 100644 --- a/frontend/src/app/main/data/workspace/wasm_text.cljs +++ b/frontend/src/app/main/data/workspace/wasm_text.cljs @@ -92,7 +92,8 @@ (watch [_ state _] (let [objects (dsh/lookup-page-objects state) shape (get objects id) - apply-opts (or opts {}) + ;; the size a text grows to is derived: it touches nothing + apply-opts (assoc opts :ignore-touched true) resize-stream (if (and (some? shape) (cfh/text-shape? shape) @@ -132,7 +133,8 @@ ;; When undo-id is present, extend the current undo transaction instead of ;; creating a new one, and commit it after the resize (single undo action). extend-tx? (some? undo-id) - apply-opts (cond-> {} + ;; the size a text grows to is derived: it touches nothing + apply-opts (cond-> {:ignore-touched true} (some? undo-group) (assoc :undo-group undo-group) extend-tx? (assoc :undo-transation? false) skip-component-sync? (assoc :skip-component-sync? true))] diff --git a/frontend/test/frontend_tests/composable_tests/golden_master.edn b/frontend/test/frontend_tests/composable_tests/golden_master.edn index 26259fbe2d..09adef69e1 100644 --- a/frontend/test/frontend_tests/composable_tests/golden_master.edn +++ b/frontend/test/frontend_tests/composable_tests/golden_master.edn @@ -207,8 +207,8 @@ :case-x-undo-before-sync-leaves-copies-unchanged [{:transcript " {:target :main-instance, :property :fills, :value \"#ff0000\"}", :states ["734ec524d88eb6bb" "ff1fa863b88027e5"]}] :case-y-text-override-merges-with-main-text-change - [{:transcript "one-of -> \n {:target :copy-instance, :edit :text, :value \"copy text\"}\n {:target :main-instance, :edit :text, :value \"main text\"}", :states ["1fdda10ff2de93bb" "9caff776f882850b" "cc39679f8f901c8e"]} - {:transcript "one-of -> \n {:target :copy-instance, :edit :text, :value \"copy text\"}\n {:target :main-instance, :edit :font-size, :value \"24\"}", :states ["1fdda10ff2de93bb" "b44573ca4817a1c2" "805af3e9523db814"]} + [{:transcript "one-of -> \n {:target :copy-instance, :edit :text, :value \"copy text\"}\n {:target :main-instance, :edit :text, :value \"main text\"}", :states ["7ea87003598e5626" "f823f8147e941701" "3a6ecb19a9315cb8"]} + {:transcript "one-of -> \n {:target :copy-instance, :edit :text, :value \"copy text\"}\n {:target :main-instance, :edit :font-size, :value \"24\"}", :states ["7ea87003598e5626" "bc06b54fa303bde9" "230d1c77384fc557"]} {:transcript "one-of -> \n {:target :copy-instance, :edit :font-size, :value \"32\"}\n {:target :main-instance, :edit :text, :value \"main text\"}", :states ["c5ad2b1e98811951" "ed2ffe7c84c65ffa" "50166a4e5025f6cb"]} {:transcript "one-of -> \n {:target :copy-instance, :edit :font-size, :value \"32\"}\n {:target :main-instance, :edit :font-size, :value \"24\"}", :states ["c5ad2b1e98811951" "c75ab24b318795d7" "8896671645bb0a65"]} {:transcript "one-of -> \n {:target :main-instance, :edit :text, :value \"main text\"}", :states ["dd9f23b4112dddae" "3bd3720125090159"]} diff --git a/frontend/test/frontend_tests/data/workspace_sync_wasm_text_test.cljs b/frontend/test/frontend_tests/data/workspace_sync_wasm_text_test.cljs new file mode 100644 index 0000000000..9f7f6b6467 --- /dev/null +++ b/frontend/test/frontend_tests/data/workspace_sync_wasm_text_test.cljs @@ -0,0 +1,84 @@ +;; This Source Code Form is subject to the terms of the Mozilla Public +;; License, v. 2.0. If a copy of the MPL was not distributed with this +;; file, You can obtain one at http://mozilla.org/MPL/2.0/. +;; +;; Copyright (c) KALEIDOS SUBSIDIARY SL + +(ns frontend-tests.data.workspace-sync-wasm-text-test + "With the WASM renderer, the size of a text that grows with its content + is measured again after a sync writes it, as the SVG renderer does when + it draws the text: a copy text with content of its own keeps its own + size." + (:require + [app.common.files.changes-builder :as pcb] + [app.common.geom.shapes :as gsh] + [app.common.logic.shapes :as cls] + [app.common.test-helpers.components :as cthc] + [app.common.test-helpers.compositions :as ctho] + [app.common.test-helpers.files :as cthf] + [app.common.test-helpers.ids-map :as cthi] + [app.common.test-helpers.texts :as ctht] + [app.common.types.modifiers :as ctm] + [app.main.data.workspace.libraries :as dwl] + [app.main.data.workspace.wasm-text :as dwwt] + [cljs.test :as t :include-macros true] + [frontend-tests.helpers.state :as ths])) + +(def ^:private orig-resize-wasm-text-all dwwt/resize-wasm-text-all) + +(def ^:private resize-calls (atom [])) + +(defn- mock-resize-wasm-text-all + ([ids] (mock-resize-wasm-text-all ids nil)) + ([ids opts] + (swap! resize-calls conj {:ids (set ids) :opts opts}) + nil)) + +(t/use-fixtures :each + {:before (fn [] + (cthi/reset-idmap!) + (reset! resize-calls []) + (set! dwwt/resize-wasm-text-all mock-resize-wasm-text-all)) + :after (fn [] + (set! dwwt/resize-wasm-text-all orig-resize-wasm-text-all))}) + +(defn- resize + "Resize the shape `label` of `file` to `width`, as a measure of its text + would." + [file label width] + (let [page (cthf/current-page file) + changes (cls/generate-update-shapes + (pcb/empty-changes nil (:id page)) + #{(cthi/id label)} + #(gsh/transform-shape % (ctm/change-size % width (:height %))) + (:objects page) + {:ignore-touched true})] + (cthf/apply-changes file changes))) + +(defn- setup-file + "A component with a text and a copy whose text has content of its own; + the main text then grows." + [] + (-> (cthf/sample-file :file1) + (ctho/add-frame-with-text :main-root :main-text "hello") + (cthc/make-component :component1 :main-root) + (cthc/instantiate-component :component1 :copy-root + :children-labels [:copy-text]) + (ctht/set-text :copy-text [[["a longer text of its own"]]]) + (resize :main-text 200))) + +(t/deftest a-sync-measures-again-the-copy-texts-it-writes + (t/async + done + (let [file (setup-file) + store (ths/setup-store file) + undo-group (random-uuid)] + (ths/run-store + store done + [(dwl/sync-file (:id file) (:id file) :components (cthi/id :component1) undo-group)] + (fn [_] + (t/is (= 1 (count @resize-calls))) + (let [{:keys [ids opts]} (first @resize-calls)] + (t/is (contains? ids (cthi/id :copy-text))) + (t/is (:skip-component-sync? opts)) + (t/is (= undo-group (:undo-group opts))))))))) diff --git a/frontend/test/frontend_tests/data/workspace_text_modifier_test.cljs b/frontend/test/frontend_tests/data/workspace_text_modifier_test.cljs new file mode 100644 index 0000000000..5e3d381c20 --- /dev/null +++ b/frontend/test/frontend_tests/data/workspace_text_modifier_test.cljs @@ -0,0 +1,37 @@ +;; This Source Code Form is subject to the terms of the Mozilla Public +;; License, v. 2.0. If a copy of the MPL was not distributed with this +;; file, You can obtain one at http://mozilla.org/MPL/2.0/. +;; +;; Copyright (c) KALEIDOS SUBSIDIARY SL + +(ns frontend-tests.data.workspace-text-modifier-test + "The size the SVG renderer measures for a text while it is edited, which + the edit stores when it ends: it follows the content, so it touches + nothing." + (:require + [app.common.test-helpers.composable.comp.setups :as setup] + [app.common.test-helpers.ids-map :as thi] + [app.common.test-helpers.shapes :as ths] + [app.common.test-helpers.texts :as tht] + [app.main.data.workspace.texts :as dwt] + [cljs.test :as t :include-macros true] + [frontend-tests.composable-tests.interpreter :as ftm])) + +(t/use-fixtures :each ftm/fixture) + +(defn- measure + "Stands in for the DOM measurement of the text `id`." + [id size] + (fn [state] + (assoc-in state [:workspace-text-modifier id] size))) + +(t/deftest ^:async an-edit-of-a-copy-text-touches-its-content-only + (ftm/install! (setup/simple-text-component-with-copy)) + (let [id (thi/id :copy-text)] + (await (ftm/await-step [(measure id {:width 140}) + (dwt/v2-update-text-shape-content id (tht/content [[["hello there"]]]))])) + (let [text (ths/get-shape (ftm/current-file) :copy-text)] + (t/is (= [[["hello there"]]] (tht/text-of (ftm/current-file) :copy-text))) + (t/is (= 140 (:width text))) + (t/is (contains? (:touched text) :content-group)) + (t/is (not (contains? (:touched text) :geometry-group)))))) diff --git a/frontend/test/frontend_tests/runner.cljs b/frontend/test/frontend_tests/runner.cljs index 418ad5358f..ee9afbd663 100644 --- a/frontend/test/frontend_tests/runner.cljs +++ b/frontend/test/frontend_tests/runner.cljs @@ -45,6 +45,8 @@ [frontend-tests.data.workspace-reflow-test] [frontend-tests.data.workspace-shortcuts-test] [frontend-tests.data.workspace-sync-barrier-test] + [frontend-tests.data.workspace-sync-wasm-text-test] + [frontend-tests.data.workspace-text-modifier-test] [frontend-tests.data.workspace-texts-test] [frontend-tests.data.workspace-thumbnails-test] [frontend-tests.data.workspace-undo-test] @@ -205,6 +207,7 @@ 'frontend-tests.data.workspace-interactions-test 'frontend-tests.data.workspace-font-variants-test 'frontend-tests.data.workspace-frame-guides-undo-test + 'frontend-tests.data.workspace-text-modifier-test 'frontend-tests.data.workspace-libraries-test 'frontend-tests.data.workspace-library-links-test 'frontend-tests.data.workspace-library-updates-test @@ -215,6 +218,7 @@ 'frontend-tests.data.workspace-reflow-test 'frontend-tests.data.workspace-shortcuts-test 'frontend-tests.data.workspace-sync-barrier-test + 'frontend-tests.data.workspace-sync-wasm-text-test 'frontend-tests.data.workspace-texts-test 'frontend-tests.data.workspace-thumbnails-test 'frontend-tests.data.workspace-undo-test