From b08002bc0b03b6d44b4850388733335f6b4637da Mon Sep 17 00:00:00 2001 From: Elena Torro Date: Thu, 3 Sep 2026 13:28:58 +0200 Subject: [PATCH] :bug: Keep component sync when a user text resize joins a derived batch --- .../app/main/data/workspace/wasm_text.cljs | 16 +++-- .../resize_debounce_opts_test.cljs | 64 +++++++++++++++++++ frontend/test/frontend_tests/runner.cljs | 2 + 3 files changed, 76 insertions(+), 6 deletions(-) create mode 100644 frontend/test/frontend_tests/render_wasm/resize_debounce_opts_test.cljs diff --git a/frontend/src/app/main/data/workspace/wasm_text.cljs b/frontend/src/app/main/data/workspace/wasm_text.cljs index 04495ad0f2..0b3a5fa96f 100644 --- a/frontend/src/app/main/data/workspace/wasm_text.cljs +++ b/frontend/src/app/main/data/workspace/wasm_text.cljs @@ -96,10 +96,15 @@ (defn- merge-resize-debounce-opts [prev {:keys [undo-group undo-id skip-component-sync?]}] - (cond-> (or prev {}) - (some? undo-group) (assoc :undo-group undo-group) - (some? undo-id) (assoc :undo-id undo-id) - skip-component-sync? (assoc :skip-component-sync? true))) + ;; One commit per batch, so one flag for every shape in it. Keep it on + ;; only while every resize in the batch is derived. + (let [prev (or prev {:skip-component-sync? true})] + (cond-> prev + (some? undo-group) (assoc :undo-group undo-group) + (some? undo-id) (assoc :undo-id undo-id) + :always (assoc :skip-component-sync? + (boolean (and skip-component-sync? + (:skip-component-sync? prev))))))) (defn resize-wasm-text-debounce-commit [] @@ -160,8 +165,7 @@ (-> state (update ::resize-wasm-text-debounce-ids (fnil conj []) id) (update ::resize-wasm-text-reflow-tasks (fnil conj []) reflow-task) - (cond-> (seq opts) - (update ::resize-wasm-text-debounce-opts merge-resize-debounce-opts opts)) + (update ::resize-wasm-text-debounce-opts merge-resize-debounce-opts opts) (cond-> (nil? (::resize-wasm-text-debounce-event state)) (assoc ::resize-wasm-text-debounce-event cur-event)))) diff --git a/frontend/test/frontend_tests/render_wasm/resize_debounce_opts_test.cljs b/frontend/test/frontend_tests/render_wasm/resize_debounce_opts_test.cljs new file mode 100644 index 0000000000..cc51ad38ca --- /dev/null +++ b/frontend/test/frontend_tests/render_wasm/resize_debounce_opts_test.cljs @@ -0,0 +1,64 @@ +;; 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.render-wasm.resize-debounce-opts-test + (:require + [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.shapes :as cths] + [app.main.data.workspace.wasm-text :as dwwt] + [cljs.test :as t :include-macros true] + [frontend-tests.helpers.pages :as thp] + [frontend-tests.helpers.state :as ths] + [frontend-tests.helpers.wasm :as thw])) + +(t/use-fixtures :each + {:before (fn [] + (thp/reset-idmap!) + (thw/setup-wasm-mocks!)) + :after thw/teardown-wasm-mocks!}) + +(defn- setup-file [] + (-> (cthf/sample-file :file-1 :page-label :page-1) + (ctho/add-text :text-1 "Derived") + (ctho/add-text :text-2 "User edit"))) + +(defn- batch-opts + [state] + (get state :app.main.data.workspace.wasm-text/resize-wasm-text-debounce-opts)) + +(t/deftest derived-flag-does-not-leak-into-a-user-resize-in-the-same-batch + (t/async + done + (let [file (setup-file) + store (ths/setup-store file) + text-1 (cths/get-shape file :text-1) + text-2 (cths/get-shape file :text-2) + ;; Same tick => both ids land in the same debounce batch, which is + ;; what a font-load resize racing a sidebar resize produces. + events [(dwwt/resize-wasm-text-debounce (:id text-1) {:skip-component-sync? true}) + (dwwt/resize-wasm-text-debounce (:id text-2) nil)]] + (ths/run-store + store done events + (fn [state] + ;; 3, not 2: the batch owner re-emits itself so text-1 is queued twice. + (t/is (= 3 (count (get state :app.main.data.workspace.wasm-text/resize-wasm-text-debounce-ids)))) + (t/is (not (:skip-component-sync? (batch-opts state))))))))) + +(t/deftest derived-only-batch-keeps-the-flag + (t/async + done + (let [file (setup-file) + store (ths/setup-store file) + text-1 (cths/get-shape file :text-1) + text-2 (cths/get-shape file :text-2) + events [(dwwt/resize-wasm-text-debounce (:id text-1) {:skip-component-sync? true}) + (dwwt/resize-wasm-text-debounce (:id text-2) {:skip-component-sync? true})]] + (ths/run-store + store done events + (fn [state] + (t/is (true? (:skip-component-sync? (batch-opts state))))))))) diff --git a/frontend/test/frontend_tests/runner.cljs b/frontend/test/frontend_tests/runner.cljs index 033d18b354..c7948eaf5f 100644 --- a/frontend/test/frontend_tests/runner.cljs +++ b/frontend/test/frontend_tests/runner.cljs @@ -63,6 +63,7 @@ [frontend-tests.plugins.value-objects-test] [frontend-tests.render-dimensions-test] [frontend-tests.render-wasm.process-objects-test] + [frontend-tests.render-wasm.resize-debounce-opts-test] [frontend-tests.render-wasm.text-editor-apply-styles-test] [frontend-tests.render-wasm.text-editor-caret-color-test] [frontend-tests.svg-fills-test] @@ -167,6 +168,7 @@ 'frontend-tests.plugins.utils-test 'frontend-tests.plugins.value-objects-test 'frontend-tests.render-wasm.process-objects-test + 'frontend-tests.render-wasm.resize-debounce-opts-test 'frontend-tests.render-wasm.text-editor-apply-styles-test 'frontend-tests.render-wasm.text-editor-caret-color-test 'frontend-tests.svg-fills-test