Files
alonso.torres 7eacbec245 🐛 Keep comments, focus, thumbnails and text in step with a rollback
A rollback reverts the local data with a rebase, which is not a commit,
so the parts of the workspace that follow commits missed it:

- Comment pins moved in the reverted action stayed where they were, on
  screen and on the server, and pins on a board the rollback removed
  kept pointing to it. They go back to the reverted position, and pins
  on a removed board move to the page root.
- Focus mode stayed on with no shape left when the rollback removed the
  focused ones, and the next shape drawn turned it on again. It ends
  when its last shape goes, and the selection drops removed shapes.
- Component thumbnails kept the rejected look. The thumbnails of the
  reverted mains are rendered and uploaded again, and an upload whose
  shapes changed during the render is skipped.
- Under WASM, an open text editor reset to the reverted content but the
  canvas kept the old layout. It is laid out again.
- A library sync whose save failed showed no update notice again once
  the release undid it. The notice is checked again after that
  rollback.
2026-11-05 23:59:32 +01:00

133 lines
5.4 KiB
Clojure

;; 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.logic.comp-watcher-test
"Automatic propagation of main edits to copies, driven by the real
`dwl/watch-component-changes` on an isolated store."
(: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.changes :as dch]
[app.main.data.helpers :as dsh]
[app.main.data.workspace.libraries :as dwl]
[app.main.data.workspace.pages :as dwpg]
[app.main.data.workspace.shapes :as dwsh]
[app.main.data.workspace.thumbnails-wasm :as dwt.wasm]
[app.main.refs :as refs]
[app.main.store :as st]
[beicon.v2.core :as rx]
[cljs.test :as t :include-macros true]
[frontend-tests.helpers.async :as async]
[frontend-tests.helpers.mock :as mock]
[frontend-tests.helpers.pages :as thp]
[frontend-tests.helpers.state :as ths]
[frontend-tests.helpers.wasm :as thw]
[okulary.core :as l]
[potok.v2.core :as ptk]))
(t/use-fixtures :each
{:before (fn []
(thp/reset-idmap!)
(thw/setup-wasm-mocks!))
:after thw/teardown-wasm-mocks!})
(defn- setup-file
[]
;; {:main-root} [:name Frame1] # [Component :component1]
;; :main-child [:name Rect1]
;;
;; :copy-root [:name Frame1] #--> [Component :component1] :main-root
;; :copy-child [:name Rect1] ---> :main-child
;;
;; :outside [:name Frame1]
(-> (cthf/sample-file :file1)
(ctho/add-simple-component-with-copy :component1
:main-root
:main-child
:copy-root
:copy-root-params {:children-labels [:copy-child]})
(ctho/add-frame :outside)))
(defn- noop-render-thumbnail
[& _]
(ptk/reify ::noop-render-thumbnail
ptk/WatchEvent
(watch [_ _ _] (rx/empty))))
(defn- copy-children
[store]
(let [file (ths/get-file-from-state @store)]
(:shapes (cths/get-shape file :copy-root))))
(t/deftest ^:async child-moved-out-of-main-leaves-copy-when-another-edit-follows
;; The move and the fill change commit in the same tick, so the watcher
;; inspects both in one batch. The move must be checked against the file
;; data right before it, where the rect still sits in the main.
(let [file (setup-file)
store (ths/setup-store file)]
(await
(mock/with-mocks*
{;; The watcher reads the file data through this lens.
refs/workspace-data (l/derived dsh/lookup-file-data store)
;; Thumbnail renders read the global store.
st/state store
dwt.wasm/render-thumbnail noop-render-thumbnail}
(ptk/emit! store (dwl/watch-component-changes))
(t/is (= 1 (count (copy-children store)))
"the copy starts with its rect")
(ptk/emit! store
(dwsh/relocate-shapes #{(cthi/id :main-child)} (cthi/id :outside) 0)
(dwsh/update-shapes [(cthi/id :outside)]
#(assoc % :fills [{:fill-color "#ff0000"
:fill-opacity 1}])))
(await (async/wait-for #(empty? (copy-children store))
"the copy to lose its rect"))
(let [file' (ths/get-file-from-state @store)]
(t/is (= (cthi/id :outside)
(:parent-id (cths/get-shape file' :main-child)))
"the rect moved out of the main")
(t/is (empty? (copy-children store))
"the copy lost its rect"))
(ptk/emit! store (dwpg/finalize-page (:id file) (cthf/current-page-id file)))
(await (async/settle))))))
;; Scenario: a rollback reverts the opacity of the main's child. A rollback
;; is not a commit, so the change watch sees no component change.
;; Proves: the component's thumbnail is rendered again and uploaded, so
;; neither the tab nor the server keeps a thumbnail of the rejected data.
(t/deftest ^:async a-rollback-of-a-main-renders-and-uploads-its-thumbnail
(let [file (setup-file)
store (ths/setup-store file)
page-id (cthf/current-page-id file)
renders (atom [])]
(await
(mock/with-mocks*
{refs/workspace-data (l/derived dsh/lookup-file-data store)
st/state store
dwt.wasm/render-thumbnail (fn [_ _ frame-id & {:as opts}]
(swap! renders conj [frame-id opts])
(noop-render-thumbnail))}
(ptk/emit! store (dwl/watch-component-changes))
(ptk/emit! store (dch/rebase-commits
(:id file)
{:undo []
:remote [{:type :mod-obj :id (cthi/id :main-child) :page-id page-id
:operations [{:type :set :attr :opacity :val 0.5}]}]
:redo []}))
(await (async/wait-for #(seq @renders) "the thumbnail to render"))
(t/is (= [[(cthi/id :main-root) {:persist? true}]] @renders))
(ptk/emit! store (dwpg/finalize-page (:id file) page-id))
(await (async/settle))))))