mirror of
https://github.com/penpot/penpot.git
synced 2026-10-29 14:00:37 +00:00
♻️ Share the workspace edit watchers with the test harness
The composable test interpreter wired its own copy of the undo stack subscription and started only the component watcher, so it reacted to edits differently from the app, and a missing subscription failed silently. Gather the subscriptions that react to edits while a file is open (undo stack, layout updates, text reflow, WASM text measurement) in initialize-edit-watchers, stopped by finalize-edit-watchers, and the per-page component and token watchers in watch-library-changes. The workspace and the interpreter both start them. Starting the edit watchers again replaces the running ones. A file reload starts the workspace again without finalizing it, which left two undo stack watchers running, so an edit added an extra undo entry.
This commit is contained in:
parent
5cdd44d296
commit
02c51f435d
@ -122,7 +122,9 @@ cross-level propagation requires progressively NESTED levels (one variant + plai
|
||||
nestings do not propagate between each other.
|
||||
|
||||
**Interpreter:** installs the situation's files into the global `st/state` (aux files tagged
|
||||
`:library-of`), starts the real `watch-component-changes` (+ harness `watch-undo-stack`), maps
|
||||
`:library-of`), starts the production watchers the app starts — `dw/initialize-edit-watchers`
|
||||
(undo stack, layout, text reflow, WASM text measurement; stopped by `dw/finalize-edit-watchers`)
|
||||
and `dwl/watch-library-changes` (component + token watchers) — then maps
|
||||
event-ops to REAL workspace events (`dwsh/update-shapes`, `dwl/component-swap`,
|
||||
`dwv/variants-switch`, `dwt/increase-rotation` — which runs the `check-delta` placement
|
||||
classification — `dwt/update-dimensions`, `dwu/undo`, `dwl/sync-file`, …) and runs sync-ops'
|
||||
@ -137,10 +139,11 @@ Running (frontend): `cd frontend && pnpm run build:test`, then
|
||||
`node target/tests/test.js --focus frontend-tests.composable-tests.comp.sync-test`
|
||||
(var-level focus for one case).
|
||||
|
||||
**Fidelity warning:** the harness drives a MINIMALLY-ASSEMBLED app — only some
|
||||
`initialize-workspace` subscriptions are wired. Risk = SILENT UNDER-WIRING (e.g. undo needs the
|
||||
harness `watch-undo-stack`). When a case needs app behaviour beyond a raw edit, check for an
|
||||
unwired subscription and verify by PROBING store state, not by trusting a green assertion.
|
||||
**Fidelity warning:** the harness drives a MINIMALLY-ASSEMBLED app: no file loading, no
|
||||
persistence, no viewport. Edit-reacting subscriptions come from the shared events above; a NEW
|
||||
edit-reacting subscription belongs inside `initialize-edit-watchers` (or
|
||||
`watch-library-changes`), never wired inline in `initialize-workspace`, or the harness silently
|
||||
misses it. Headless there is no DOM text measurement, so `:text/reflow` work never drains.
|
||||
|
||||
**Caveats:** inline `Test` exceptions are UNCAUGHT on the frontend (crash the runner — assert in
|
||||
the trailing asserter). `(optional (in-sequence …))` is not flattened for the interpreter — use
|
||||
|
||||
@ -381,123 +381,38 @@
|
||||
:file-id (dm/str file-id))
|
||||
(rx/of (fetch-bundle file-id features))))))
|
||||
|
||||
(defn initialize-workspace
|
||||
[team-id file-id]
|
||||
(assert (uuid? team-id) "expected valud uuid for `team-id`")
|
||||
(assert (uuid? file-id) "expected valud uuid for `file-id`")
|
||||
|
||||
(ptk/reify ::initialize-workspace
|
||||
ptk/UpdateEvent
|
||||
(update [_ state]
|
||||
(-> state
|
||||
(assoc :recent-colors (:recent-colors storage/user))
|
||||
(assoc :recent-fonts (:recent-fonts storage/user))
|
||||
(assoc :current-file-id file-id)
|
||||
(assoc :workspace-presence {})
|
||||
(update :workspace-global dissoc :default-font)
|
||||
(update :comments-local dcmt/merge-persisted-filters)))
|
||||
(defn finalize-edit-watchers
|
||||
"Stops the watchers started by `initialize-edit-watchers`."
|
||||
[]
|
||||
(ptk/reify ::finalize-edit-watchers
|
||||
ptk/WatchEvent
|
||||
(watch [_ _ _]
|
||||
(rx/of (dwsl/finalize-shape-layout)
|
||||
(dwtxt/finalize-text-reflow)))))
|
||||
|
||||
(defn initialize-edit-watchers
|
||||
"Starts the watchers that react to edits while a file is open: the undo
|
||||
stack, layout updates, text reflow and the WASM text measurements. They run
|
||||
until `finalize-edit-watchers`, or until this event comes again (a file
|
||||
reload), which replaces them. Tests that drive edits without the whole
|
||||
workspace start them too, so they see the same reactions as the app."
|
||||
[]
|
||||
(ptk/reify ::initialize-edit-watchers
|
||||
ptk/WatchEvent
|
||||
(watch [_ state stream]
|
||||
(let [stoper-s (rx/filter (ptk/type? ::finalize-workspace) stream)
|
||||
rparams (rt/get-params state)
|
||||
features (features/get-enabled-features state team-id)
|
||||
(let [stopper-s (rx/merge
|
||||
(rx/filter (ptk/type? ::finalize-edit-watchers) stream)
|
||||
(rx/filter (ptk/type? ::initialize-edit-watchers) stream))
|
||||
render-wasm-enabled? (features/active-feature? state "render-wasm/v1")
|
||||
render-wasm-ready? #(and render-wasm-enabled?
|
||||
(wasm-state/ready?))]
|
||||
|
||||
(log/debug :hint "initialize-workspace"
|
||||
:team-id (dm/str team-id)
|
||||
:file-id (dm/str file-id))
|
||||
|
||||
(rx/concat
|
||||
(rx/merge
|
||||
;; Stop the layout and text watchers a previous start left running.
|
||||
(rx/of (dwsl/finalize-shape-layout)
|
||||
(dwtxt/finalize-text-reflow)
|
||||
(dwsl/initialize-shape-layout)
|
||||
(dwtxt/initialize-text-reflow))
|
||||
(->> (rx/merge
|
||||
(rx/concat
|
||||
;; Fetch all essential data that should be loaded before the file
|
||||
(rx/merge
|
||||
(if ^boolean render-wasm-enabled?
|
||||
(->> (rx/from @wasm/module)
|
||||
(rx/filter true?)
|
||||
(rx/tap (fn [_]
|
||||
(let [event (ug/event "penpot:wasm:loaded")]
|
||||
(ug/dispatch! event))))
|
||||
(rx/ignore))
|
||||
(rx/empty))
|
||||
|
||||
(->> stream
|
||||
(rx/filter (ptk/type? ::df/fonts-loaded))
|
||||
(rx/take 1)
|
||||
(rx/ignore))
|
||||
|
||||
(rx/of (ntf/hide)
|
||||
(dcmt/retrieve-comment-threads file-id)
|
||||
(dcmt/fetch-profiles)
|
||||
(df/fetch-fonts team-id)))
|
||||
|
||||
;; Once the essential data is fetched, lets proceed to
|
||||
;; fetch the file bundle
|
||||
(rx/of (initialize-file team-id file-id)))
|
||||
|
||||
(->> stream
|
||||
(rx/filter (ptk/type? ::bundle-fetched))
|
||||
(rx/take 1)
|
||||
(rx/map deref)
|
||||
(rx/mapcat
|
||||
(fn [{:keys [file]}]
|
||||
(log/debug :hint "bundle fetched"
|
||||
:team-id (dm/str team-id)
|
||||
:file-id (dm/str file-id))
|
||||
|
||||
(rx/of (dpj/initialize-project (:project-id file))
|
||||
(dwn/initialize team-id file-id)
|
||||
(dwsl/initialize-shape-layout)
|
||||
(dwtxt/initialize-text-reflow)
|
||||
(fetch-libraries file-id features)
|
||||
(-> (workspace-initialized file-id)
|
||||
(with-meta {:team-id team-id
|
||||
:file-id file-id}))))))
|
||||
|
||||
;; Install dev perf observers once the workspace is ready
|
||||
(when (contains? cf/flags :perf-logs)
|
||||
(->> stream
|
||||
(rx/filter (ptk/type? ::workspace-initialized))
|
||||
(rx/take 1)
|
||||
(rx/tap (fn [_] (perf/setup)))))
|
||||
|
||||
(->> stream
|
||||
(rx/filter (ptk/type? ::dps/persistence-notification))
|
||||
(rx/take 1)
|
||||
(rx/map dwc/set-workspace-visited))
|
||||
|
||||
;; Emit audit event with file statistics once all libraries are resolved
|
||||
(->> stream
|
||||
(rx/filter (ptk/type? ::all-libraries-resolved))
|
||||
(rx/take 1)
|
||||
(rx/map #(emit-workspace-file-stats file-id team-id)))
|
||||
|
||||
(when-let [component-id (some-> rparams :component-id uuid/parse)]
|
||||
(->> stream
|
||||
(rx/filter (ptk/type? ::workspace-initialized))
|
||||
(rx/observe-on :async)
|
||||
(rx/take 1)
|
||||
(rx/map #(dwl/go-to-local-component :id component-id :update-layout? (:update-layout rparams)))))
|
||||
|
||||
(when (:board-id rparams)
|
||||
(->> stream
|
||||
(rx/filter (ptk/type? ::dwv/initialize-viewport))
|
||||
(rx/take 1)
|
||||
(rx/map zoom-to-frame)))
|
||||
|
||||
(when-let [comment-id (some-> rparams :comment-id uuid/parse)]
|
||||
(->> stream
|
||||
(rx/filter (ptk/type? ::workspace-initialized))
|
||||
(rx/observe-on :async)
|
||||
(rx/take 1)
|
||||
(rx/map #(dwcm/navigate-to-comment-id comment-id))))
|
||||
|
||||
;; Keep comment thread positions in sync on undo/redo
|
||||
(rx/of (dwcm/watch-comment-thread-position-changes stoper-s))
|
||||
|
||||
;; Resize auto-grow text shapes whose selrect does not match
|
||||
;; the WASM text layout once their fonts finish loading.
|
||||
(->> stream
|
||||
@ -560,7 +475,7 @@
|
||||
:save-undo? false
|
||||
:tags #{:position-data}
|
||||
:skip-component-sync? true})))))
|
||||
(rx/take-until stoper-s)))
|
||||
(rx/take-until stopper-s)))
|
||||
|
||||
(->> stream
|
||||
(rx/filter dch/commit?)
|
||||
@ -575,6 +490,121 @@
|
||||
:selected-before selected-before}]
|
||||
(rx/of (dwu/append-undo entry stack-undo?)))
|
||||
(rx/empty))))))
|
||||
(rx/take-until stopper-s)))))))
|
||||
|
||||
(defn initialize-workspace
|
||||
[team-id file-id]
|
||||
(assert (uuid? team-id) "expected valud uuid for `team-id`")
|
||||
(assert (uuid? file-id) "expected valud uuid for `file-id`")
|
||||
|
||||
(ptk/reify ::initialize-workspace
|
||||
ptk/UpdateEvent
|
||||
(update [_ state]
|
||||
(-> state
|
||||
(assoc :recent-colors (:recent-colors storage/user))
|
||||
(assoc :recent-fonts (:recent-fonts storage/user))
|
||||
(assoc :current-file-id file-id)
|
||||
(assoc :workspace-presence {})
|
||||
(update :workspace-global dissoc :default-font)
|
||||
(update :comments-local dcmt/merge-persisted-filters)))
|
||||
|
||||
ptk/WatchEvent
|
||||
(watch [_ state stream]
|
||||
(let [stoper-s (rx/filter (ptk/type? ::finalize-workspace) stream)
|
||||
rparams (rt/get-params state)
|
||||
features (features/get-enabled-features state team-id)
|
||||
render-wasm-enabled? (features/active-feature? state "render-wasm/v1")]
|
||||
|
||||
(log/debug :hint "initialize-workspace"
|
||||
:team-id (dm/str team-id)
|
||||
:file-id (dm/str file-id))
|
||||
|
||||
(rx/concat
|
||||
(->> (rx/merge
|
||||
(rx/concat
|
||||
;; Fetch all essential data that should be loaded before the file
|
||||
(rx/merge
|
||||
(if ^boolean render-wasm-enabled?
|
||||
(->> (rx/from @wasm/module)
|
||||
(rx/filter true?)
|
||||
(rx/tap (fn [_]
|
||||
(let [event (ug/event "penpot:wasm:loaded")]
|
||||
(ug/dispatch! event))))
|
||||
(rx/ignore))
|
||||
(rx/empty))
|
||||
|
||||
(->> stream
|
||||
(rx/filter (ptk/type? ::df/fonts-loaded))
|
||||
(rx/take 1)
|
||||
(rx/ignore))
|
||||
|
||||
(rx/of (ntf/hide)
|
||||
(dcmt/retrieve-comment-threads file-id)
|
||||
(dcmt/fetch-profiles)
|
||||
(df/fetch-fonts team-id)))
|
||||
|
||||
;; Once the essential data is fetched, lets proceed to
|
||||
;; fetch the file bundle
|
||||
(rx/of (initialize-file team-id file-id)))
|
||||
|
||||
(->> stream
|
||||
(rx/filter (ptk/type? ::bundle-fetched))
|
||||
(rx/take 1)
|
||||
(rx/map deref)
|
||||
(rx/mapcat
|
||||
(fn [{:keys [file]}]
|
||||
(log/debug :hint "bundle fetched"
|
||||
:team-id (dm/str team-id)
|
||||
:file-id (dm/str file-id))
|
||||
|
||||
(rx/of (dpj/initialize-project (:project-id file))
|
||||
(dwn/initialize team-id file-id)
|
||||
(initialize-edit-watchers)
|
||||
(fetch-libraries file-id features)
|
||||
(-> (workspace-initialized file-id)
|
||||
(with-meta {:team-id team-id
|
||||
:file-id file-id}))))))
|
||||
|
||||
;; Install dev perf observers once the workspace is ready
|
||||
(when (contains? cf/flags :perf-logs)
|
||||
(->> stream
|
||||
(rx/filter (ptk/type? ::workspace-initialized))
|
||||
(rx/take 1)
|
||||
(rx/tap (fn [_] (perf/setup)))))
|
||||
|
||||
(->> stream
|
||||
(rx/filter (ptk/type? ::dps/persistence-notification))
|
||||
(rx/take 1)
|
||||
(rx/map dwc/set-workspace-visited))
|
||||
|
||||
;; Emit audit event with file statistics once all libraries are resolved
|
||||
(->> stream
|
||||
(rx/filter (ptk/type? ::all-libraries-resolved))
|
||||
(rx/take 1)
|
||||
(rx/map #(emit-workspace-file-stats file-id team-id)))
|
||||
|
||||
(when-let [component-id (some-> rparams :component-id uuid/parse)]
|
||||
(->> stream
|
||||
(rx/filter (ptk/type? ::workspace-initialized))
|
||||
(rx/observe-on :async)
|
||||
(rx/take 1)
|
||||
(rx/map #(dwl/go-to-local-component :id component-id :update-layout? (:update-layout rparams)))))
|
||||
|
||||
(when (:board-id rparams)
|
||||
(->> stream
|
||||
(rx/filter (ptk/type? ::dwv/initialize-viewport))
|
||||
(rx/take 1)
|
||||
(rx/map zoom-to-frame)))
|
||||
|
||||
(when-let [comment-id (some-> rparams :comment-id uuid/parse)]
|
||||
(->> stream
|
||||
(rx/filter (ptk/type? ::workspace-initialized))
|
||||
(rx/observe-on :async)
|
||||
(rx/take 1)
|
||||
(rx/map #(dwcm/navigate-to-comment-id comment-id))))
|
||||
|
||||
;; Keep comment thread positions in sync on undo/redo
|
||||
(rx/of (dwcm/watch-comment-thread-position-changes stoper-s)))
|
||||
|
||||
(rx/take-until stoper-s)))))
|
||||
|
||||
@ -608,8 +638,7 @@
|
||||
(let [project-id (:current-project-id state)]
|
||||
(rx/of (dwn/finalize file-id)
|
||||
(dpj/finalize-project project-id)
|
||||
(dwsl/finalize-shape-layout)
|
||||
(dwtxt/finalize-text-reflow)
|
||||
(finalize-edit-watchers)
|
||||
(dwcl/stop-picker)
|
||||
(dwc/set-workspace-visited)
|
||||
(modal/hide)
|
||||
|
||||
@ -1567,6 +1567,17 @@
|
||||
(rx/map (fn [_] (sync-tokens-status-with-lib)))
|
||||
(rx/take-until stopper-s))))))
|
||||
|
||||
(defn watch-library-changes
|
||||
"Starts the watchers that propagate edits of the file's library while a page
|
||||
is open: component changes to the copies, token changes to the tokens
|
||||
status. They stop when the page is finalized."
|
||||
[]
|
||||
(ptk/reify ::watch-library-changes
|
||||
ptk/WatchEvent
|
||||
(watch [_ _ _]
|
||||
(rx/of (watch-component-changes)
|
||||
(watch-token-changes)))))
|
||||
|
||||
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
|
||||
;; Backend interactions
|
||||
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
|
||||
|
||||
@ -113,8 +113,7 @@
|
||||
(rx/empty)
|
||||
(rx/of (dwth/watch-state-changes file-id page-id)))
|
||||
|
||||
(rx/of (dwl/watch-component-changes))
|
||||
(rx/of (dwl/watch-token-changes))
|
||||
(rx/of (dwl/watch-library-changes))
|
||||
|
||||
(let [profile (:profile state)
|
||||
props (get profile :props)]
|
||||
|
||||
@ -43,6 +43,7 @@
|
||||
[app.common.test-helpers.ids-map :as cthi]
|
||||
[app.common.test-helpers.shapes :as cths]
|
||||
[app.main.data.changes :as dch]
|
||||
[app.main.data.workspace :as dw]
|
||||
[app.main.data.workspace.libraries :as dwl]
|
||||
[app.main.data.workspace.shapes :as dwsh]
|
||||
[app.main.data.workspace.thumbnails :as dwth]
|
||||
@ -361,36 +362,6 @@
|
||||
(js/setTimeout continue grace)
|
||||
(continue)))))))))
|
||||
|
||||
(defn- watch-undo-stack
|
||||
"Maintain the workspace UNDO STACK in the (global) store, mirroring the
|
||||
production subscription in `app.main.data.workspace/initialize-workspace`:
|
||||
for every local commit with `save-undo?` and non-empty `:undo-changes`, dispatch
|
||||
`dwu/append-undo`. We start this in the harness because the minimal store setup
|
||||
does not run the full `initialize-workspace` (which is where the real app wires
|
||||
it). Re-emitting this event stops any previous instance (so re-install per
|
||||
variant does not stack watchers)."
|
||||
[]
|
||||
(ptk/reify ::watch-undo-stack
|
||||
ptk/WatchEvent
|
||||
(watch [_ _ stream]
|
||||
(let [stopper-s (->> stream (rx/filter (ptk/type? ::watch-undo-stack)))]
|
||||
(->> stream
|
||||
(rx/filter dch/commit?)
|
||||
(rx/map deref)
|
||||
(rx/filter #(= :local (:source %)))
|
||||
(rx/mapcat
|
||||
(fn [{:keys [save-undo? undo-changes redo-changes undo-group tags stack-undo? selected-before]}]
|
||||
(if (and save-undo? (seq undo-changes))
|
||||
(rx/of (dwu/append-undo
|
||||
{:undo-changes undo-changes
|
||||
:redo-changes redo-changes
|
||||
:undo-group undo-group
|
||||
:tags tags
|
||||
:selected-before selected-before}
|
||||
stack-undo?))
|
||||
(rx/empty))))
|
||||
(rx/take-until stopper-s))))))
|
||||
|
||||
(defonce ^:private original-store
|
||||
;; Captured at namespace-LOAD time — i.e. before any test in the run executes.
|
||||
;; Several test namespaces (the plugins suite) `set!` st/state / st/stream to
|
||||
@ -420,8 +391,11 @@
|
||||
(restore-global-store!)
|
||||
(let [situation (setup)]
|
||||
(st/emit! (install-situation-event situation))
|
||||
(st/emit! (dwl/watch-component-changes))
|
||||
(st/emit! (watch-undo-stack))
|
||||
;; The same watchers the workspace starts; re-emitting them replaces the
|
||||
;; previous variant's.
|
||||
(st/emit! (dw/finalize-edit-watchers)
|
||||
(dw/initialize-edit-watchers)
|
||||
(dwl/watch-library-changes))
|
||||
(run-ops situation ops k)))
|
||||
|
||||
;; --------------------------------------------------------------------------
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user