From 063218895047ff490f0153243b89cbb18b7c99ae Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C3=81lvaro=20Tejero=20Cantero?= Date: Wed, 28 Oct 2026 14:34:07 +0100 Subject: [PATCH] :sparkles: Prove a reset of copy overrides survives a branch merge A copy whose override the base holds can be reset on one side while the other side leaves it alone. The tests check that the reset reaches the other file in both directions: the copy loses its override, takes the component's value, and follows a later component edit. The merge direction failed before copies followed their component. AI-assisted-by: claude-opus-5-5 --- .../branch_copies_follow_component_test.clj | 75 ++++++++++++++++++- .../common_tests/files_branch_merge_test.cljc | 16 ++++ 2 files changed, 89 insertions(+), 2 deletions(-) diff --git a/backend/test/backend_tests/branch_copies_follow_component_test.clj b/backend/test/backend_tests/branch_copies_follow_component_test.clj index b192105544..bc242ec026 100644 --- a/backend/test/backend_tests/branch_copies_follow_component_test.clj +++ b/backend/test/backend_tests/branch_copies_follow_component_test.clj @@ -20,7 +20,10 @@ (:require [app.binfile.common :as bfc] [app.common.features :as cfeat] + [app.common.files.changes-builder :as pcb] [app.common.files.validate :as cfv] + [app.common.logic.libraries :as cll] + [app.common.types.container :as ctn] [app.common.types.shape :as cts] [app.common.uuid :as uuid] [app.config :as cf] @@ -97,8 +100,10 @@ (defn- setup "Main holding a local component (main `:mi-id`) with one copy (`:copy-id`), branched once. `n` numbers the profile and the file, so - one test can build more than one." - [n] + one test can build more than one. With `:override`, the copy's fill is + overridden to that colour before branching, so the base holds the + override." + [n & {:keys [override]}] (let [profile (th/create-profile* n {:is-active true}) file (th/create-file* n {:profile-id (:id profile) :project-id (:default-project-id profile) @@ -116,6 +121,8 @@ {:type :add-component :id comp-id :name "Button" :path "" :main-instance-id mi-id :main-instance-page page-id} (copy-of page-id copy-id comp-id mi-id main-id)]) + (when override + (save profile main-id [(set-fill page-id copy-id override)])) (let [branch (:result (th/command! {::th/type :create-file-branch ::rpc/profile-id (:id profile) :file-id main-id @@ -302,3 +309,67 @@ (let [out (update-branch ctx)] (t/is (= :conflicts (-> out :result :status))) (t/is (= #{copy-id} (conflict-ids out))))))) + +;; --- e: one side resets an override the base holds, the other leaves +;; the copy alone + +(defn- reset-copy + "Reset the overrides of the copy `copy-id` in `file-id`, as the + workspace's \"reset overrides\" does (`cll/generate-reset-component`)." + [profile file-id page-id copy-id] + (let [file (get-file profile file-id) + page (ctn/make-container (get-in file [:data :pages-index page-id]) :page) + changes (cll/generate-reset-component (pcb/empty-changes) file {file-id file} page copy-id)] + (save profile file-id (:redo-changes changes)))) + +(defn- follows-component? + "True when an edit of the component's fill on `file-id` reaches the + copy through the component sync the workspace runs after the edit + (`cll/generate-sync-file-changes`)." + [profile file-id page-id comp-id mi-id copy-id] + (save profile file-id [(set-fill page-id mi-id "#123456")]) + (let [file (get-file profile file-id) + changes (cll/generate-sync-file-changes (pcb/empty-changes) nil :components file-id + comp-id file-id {file-id file} file-id)] + (save profile file-id (:redo-changes changes)) + (= "#123456" (fill (get-file profile file-id) page-id copy-id)))) + +(t/deftest merge-keeps-a-reset-of-an-override-the-base-holds + (with-redefs [cf/flags (conj cf/flags :branching)] + (let [{:keys [profile main-id branch-file-id page-id comp-id mi-id copy-id] :as ctx} + (setup 1 :override "#ff0000")] + (reset-copy profile branch-file-id page-id copy-id) + (t/testing "the reset clears the override on the branch" + (let [branch (get-file profile branch-file-id)] + (t/is (not (contains? (:touched (shape branch page-id copy-id)) :fill-group))) + (t/is (= (fill branch page-id mi-id) (fill branch page-id copy-id))))) + + (let [out (merge-branch ctx)] + (t/is (nil? (:error out))) + (t/is (= :merged (-> out :result :status)))) + + (let [main (get-file profile main-id)] + (t/is (not (contains? (:touched (shape main page-id copy-id)) :fill-group))) + (t/is (= (fill main page-id mi-id) (fill main page-id copy-id))) + (t/is (empty? (validation-codes main)))) + (t/is (follows-component? profile main-id page-id comp-id mi-id copy-id))))) + +(t/deftest update-keeps-a-reset-of-an-override-the-base-holds + (with-redefs [cf/flags (conj cf/flags :branching)] + (let [{:keys [profile main-id branch-file-id page-id comp-id mi-id copy-id] :as ctx} + (setup 1 :override "#ff0000")] + (reset-copy profile main-id page-id copy-id) + (t/testing "the reset clears the override on main" + (let [main (get-file profile main-id)] + (t/is (not (contains? (:touched (shape main page-id copy-id)) :fill-group))) + (t/is (= (fill main page-id mi-id) (fill main page-id copy-id))))) + + (let [out (update-branch ctx)] + (t/is (nil? (:error out))) + (t/is (= :updated (-> out :result :status)))) + + (let [branch (get-file profile branch-file-id)] + (t/is (not (contains? (:touched (shape branch page-id copy-id)) :fill-group))) + (t/is (= (fill branch page-id mi-id) (fill branch page-id copy-id))) + (t/is (empty? (validation-codes branch)))) + (t/is (follows-component? profile branch-file-id page-id comp-id mi-id copy-id))))) diff --git a/common/test/common_tests/files_branch_merge_test.cljc b/common/test/common_tests/files_branch_merge_test.cljc index f66bc5504f..2221aa5cd0 100644 --- a/common/test/common_tests/files_branch_merge_test.cljc +++ b/common/test/common_tests/files_branch_merge_test.cljc @@ -1995,6 +1995,22 @@ {:type :set-touched :touched #{:fill-group}}] ops)))) +(t/deftest reset-of-a-base-override-clears-it-on-the-target + ;; the base holds K's fill override; the branch reset it (the + ;; component's value, no `:fill-group`), main left K alone: the merge + ;; writes the value and clears the override, in that order, whatever + ;; order the attr diff yields + (let [base (mkdata {:k (copy-shape "red" :touched #{:fill-group})}) + branch (mkdata {:k (copy-shape "white")})] + (t/is (= [{:type :set :attr :fills :val [{:fill-color "white"}] :ignore-touched true} + {:type :set-touched :touched nil}] + (copy-ops (:changes (bm/compute-changes base base branch))))) + (t/testing "updating from main, the same reset on main reaches the branch" + (t/is (= [{:type :set :attr :fills :val [{:fill-color "white"}] :ignore-touched true} + {:type :set-touched :touched nil}] + (copy-ops (:changes (bm/compute-changes base base branch {} + (bm/compute-merge base branch base :main->branch))))))))) + (t/deftest clean-copy-change-marks-no-override ;; the branch synced K from its component and main left K alone: the ;; merge writes the value without marking it overridden