mirror of
https://github.com/penpot/penpot.git
synced 2026-08-15 09:18:55 +00:00
543 lines
25 KiB
Clojure
543 lines
25 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 INC Sucursal en España SL
|
|
|
|
|
|
(ns frontend-tests.logic.path-lifecycle-test
|
|
(:require
|
|
[app.common.data.undo-stack :as u]
|
|
[app.common.geom.point :as gpt]
|
|
[app.common.test-helpers.files :as cthf]
|
|
[app.common.test-helpers.shapes :as cths]
|
|
[app.common.types.path :as path]
|
|
[app.main.data.workspace.drawing.common :as dwdc]
|
|
[app.main.data.workspace.edition :as dwe]
|
|
[app.main.data.workspace.path.changes :as path.changes]
|
|
[app.main.data.workspace.path.common :as path.common]
|
|
[app.main.data.workspace.path.drawing :as path.drawing]
|
|
[app.main.data.workspace.path.edition :as path.edition]
|
|
[app.main.data.workspace.path.helpers :as path.helpers]
|
|
[app.main.data.workspace.path.selection :as path.selection]
|
|
[app.main.data.workspace.path.shortcuts :as path.shortcuts]
|
|
[app.main.data.workspace.path.state :as path.state]
|
|
[app.main.data.workspace.path.streams :as path.streams]
|
|
[app.main.data.workspace.path.tools :as path.tools]
|
|
[app.main.data.workspace.path.undo :as path.undo]
|
|
[beicon.v2.core :as rx]
|
|
[cljs.test :as t :include-macros true]
|
|
[frontend-tests.helpers.state :as ths]
|
|
[frontend-tests.logic.path-test-helpers :as pth]
|
|
[potok.v2.core :as ptk]))
|
|
|
|
(t/deftest path-lifecycle-selectors-use-the-active-path
|
|
(let [id (random-uuid)
|
|
edit-state {:edit-mode :draw}
|
|
edit-path {id edit-state}
|
|
state {:workspace-local {:edition id
|
|
:edit-path edit-path}
|
|
:workspace-drawing {:object {:id id :type :path}}}]
|
|
(t/is (= edit-state (path.state/current-edit-state state)))
|
|
(t/is (= edit-state (path.state/current-edit-state edit-path id)))
|
|
(t/is (path.state/editing? state))
|
|
(t/is (path.state/editing? edit-path id))
|
|
(t/is (path.state/drawing? edit-state id :path {:id id :type :path}))))
|
|
|
|
(t/deftest path-drawing-selector-finds-new-paths
|
|
(let [id (random-uuid)
|
|
state {:workspace-local {:edition nil
|
|
:edit-path {id {}}}
|
|
:workspace-drawing {:tool :path
|
|
:object {:id id :type :path}}}]
|
|
(t/is (path.state/drawing? state))
|
|
(t/is (not (path.state/editing? state)))
|
|
(t/is (path.state/drawing? nil nil :path {:id id :type :path}))
|
|
(t/is (not (path.state/drawing? nil nil :curve {:id id :type :path})))))
|
|
|
|
(t/deftest clear-edition-mode-finishes-path-streams
|
|
(t/is (path.streams/finish-edition? (dwe/clear-edition-mode)))
|
|
(t/is (not (path.streams/finish-edition? :interrupt))))
|
|
|
|
(t/deftest clear-edition-mode-finishes-active-path-before-finalizing
|
|
(let [id (random-uuid)
|
|
event (dwe/clear-edition-mode)
|
|
state {:workspace-local {:edition id
|
|
:edit-path {id {:edit-mode :move}}}
|
|
:workspace-drawing {:object {:id id}}}
|
|
state' (ptk/update event state)
|
|
emissions (atom [])]
|
|
(->> (ptk/watch event state' nil)
|
|
(rx/subs! #(swap! emissions conj %)))
|
|
(t/is (nil? (get-in state' [:workspace-local :edition])))
|
|
(t/is (some? (get-in state' [:workspace-local :edit-path id])))
|
|
(t/is (= [::path.common/finish-path]
|
|
(mapv ptk/type @emissions)))))
|
|
|
|
(t/deftest clear-non-path-edition-does-not-emit-finish-path
|
|
(let [id (random-uuid)
|
|
event (dwe/clear-edition-mode)
|
|
state {:workspace-local {:edition id}}
|
|
state' (ptk/update event state)
|
|
emissions (atom [])]
|
|
(->> (ptk/watch event state' nil)
|
|
(rx/subs! #(swap! emissions conj %)))
|
|
(t/is (nil? (get-in state' [:workspace-local :edition])))
|
|
(t/is (empty? @emissions))))
|
|
|
|
(t/deftest restarting-draw-mode-finishes-pending-subpath
|
|
(let [file (pth/setup-rect-file)
|
|
shape (-> (cths/get-shape file :rect1)
|
|
(path/convert-to-path))
|
|
id (:id shape)
|
|
last-point (last (path/get-points (:content shape)))
|
|
state {:workspace-local
|
|
{:edition id
|
|
:edit-path
|
|
{id {:edit-mode :draw
|
|
:last-point last-point
|
|
:preview {:command :line-to
|
|
:params {:x 150 :y 150}}
|
|
:old-content (:content shape)}}}
|
|
:workspace-drawing {:object shape}}
|
|
stream (rx/subject)
|
|
emissions (atom [])]
|
|
(->> (ptk/watch (path.drawing/start-draw-mode*) state stream)
|
|
(rx/take 4)
|
|
(rx/subs! #(swap! emissions conj %)))
|
|
(rx/push! stream (ptk/data-event ::path.drawing/end-edition
|
|
{:restart? true}))
|
|
(t/is (= [::path.drawing/start-edition
|
|
::path.common/finish-path
|
|
::path.drawing/check-changed-content
|
|
::path.drawing/start-draw-mode*]
|
|
(mapv ptk/type @emissions)))
|
|
(let [state' (ptk/update (second @emissions) state)
|
|
state'' (ptk/update (path.drawing/preview-next-point
|
|
{:x 200 :y 200})
|
|
state')]
|
|
(t/is (nil? (get-in state' [:workspace-local :edit-path id :last-point])))
|
|
(t/is (nil? (get-in state' [:workspace-local :edit-path id :preview])))
|
|
(t/is (= :move-to
|
|
(get-in state'' [:workspace-local :edit-path id :preview :command]))))))
|
|
|
|
(t/deftest escape-does-not-restart-edited-path-draw-loop
|
|
(let [id (random-uuid)
|
|
state {:workspace-local
|
|
{:edition id
|
|
:edit-path {id {:edit-mode :draw}}}}
|
|
stream (rx/subject)
|
|
emissions (atom [])]
|
|
(t/is (path.drawing/restart-draw-loop? (path.common/finish-path)))
|
|
(->> (ptk/watch (path.drawing/start-draw-mode*) state stream)
|
|
(rx/subs! #(swap! emissions conj %)))
|
|
(rx/push! stream (ptk/data-event ::path.drawing/end-edition
|
|
{:restart? false}))
|
|
(t/is (= [::path.drawing/start-edition]
|
|
(mapv ptk/type @emissions)))))
|
|
|
|
(defn- run-handle-drawing-end
|
|
"Runs the draw-ending flow and passes its events to `callback`."
|
|
[restart? callback]
|
|
(let [state (pth/drawing-path-state)
|
|
stream (rx/subject)
|
|
emissions (atom [])]
|
|
(->> (ptk/watch (path.drawing/handle-drawing) state stream)
|
|
(rx/subs! #(swap! emissions conj %)))
|
|
(rx/push! stream (ptk/data-event ::path.drawing/end-edition
|
|
{:restart? restart?}))
|
|
;; Wait for the asynchronous drawing-end event.
|
|
(js/setTimeout
|
|
(fn []
|
|
(let [end-event (last @emissions)
|
|
end-emissions (atom [])]
|
|
(->> (ptk/watch end-event state stream)
|
|
(rx/subs! #(swap! end-emissions conj %)))
|
|
(callback @end-emissions))))))
|
|
|
|
(t/deftest escape-ending-new-path-draw-does-not-reenter-edition
|
|
(t/async
|
|
done
|
|
(run-handle-drawing-end
|
|
false
|
|
(fn [emissions]
|
|
(t/is (= [::path.drawing/close-drawn-loops
|
|
::path.drawing/setup-frame
|
|
::dwdc/handle-finish-drawing
|
|
::dwe/clear-edition-mode]
|
|
(mapv ptk/type emissions)))
|
|
(done)))))
|
|
|
|
(t/deftest finishing-new-path-draw-reenters-edition
|
|
(t/async
|
|
done
|
|
(run-handle-drawing-end
|
|
true
|
|
(fn [emissions]
|
|
(t/is (= [::path.common/finish-path
|
|
::path.drawing/close-drawn-loops
|
|
::path.drawing/setup-frame
|
|
::dwdc/handle-finish-drawing
|
|
::path.drawing/start-created-path-edition]
|
|
(mapv ptk/type emissions)))
|
|
(done)))))
|
|
|
|
(t/deftest escape-with-pending-segment-cancels-it-and-keeps-drawing
|
|
(let [id (random-uuid)
|
|
state {:workspace-local
|
|
{:edition id
|
|
:edit-path {id {:edit-mode :draw
|
|
:last-point (gpt/point 10 10)
|
|
:preview {:command :line-to
|
|
:params {:x 20 :y 20}}}}}}
|
|
emissions (atom [])]
|
|
(->> (ptk/watch (path.shortcuts/esc-pressed) state nil)
|
|
(rx/subs! #(swap! emissions conj %)))
|
|
(t/is (= [::path.common/cancel-pending-segment]
|
|
(mapv ptk/type @emissions)))
|
|
(let [state' (ptk/update (first @emissions) state)]
|
|
(t/is (nil? (get-in state' [:workspace-local :edit-path id :last-point])))
|
|
(t/is (nil? (get-in state' [:workspace-local :edit-path id :preview])))
|
|
(t/is (= :draw (get-in state' [:workspace-local :edit-path id :edit-mode]))))))
|
|
|
|
(t/deftest escape-while-creating-path-finishes-it-into-edition
|
|
(let [id (random-uuid)
|
|
state {:workspace-local
|
|
{:edit-path {id {:edit-mode :draw
|
|
:last-point (gpt/point 10 10)}}}
|
|
:workspace-drawing {:object {:id id :type :path}}}
|
|
emissions (atom [])]
|
|
(->> (ptk/watch (path.shortcuts/esc-pressed) state nil)
|
|
(rx/subs! #(swap! emissions conj %)))
|
|
;; Finishing creates the shape and clears its pending segment.
|
|
(t/is (= [::path.common/finish-path]
|
|
(mapv ptk/type @emissions)))))
|
|
|
|
(t/deftest escape-without-pending-segment-interrupts-edition
|
|
(let [id (random-uuid)
|
|
state {:workspace-local
|
|
{:edition id
|
|
:edit-path {id {:edit-mode :draw}}}}
|
|
emissions (atom [])]
|
|
(->> (ptk/watch (path.shortcuts/esc-pressed) state nil)
|
|
(rx/subs! #(swap! emissions conj %)))
|
|
(t/is (= [:interrupt] @emissions))))
|
|
|
|
(t/deftest editing-path-only-updates-drawing-copy
|
|
(t/async
|
|
done
|
|
(let [file (pth/setup-rect-file)
|
|
original-rect (cths/get-shape file :rect1)
|
|
id (:id original-rect)
|
|
delta (gpt/point 10 5)
|
|
store (ths/setup-store file)
|
|
events (conj (pth/start-path-edition-events id)
|
|
(pth/move-drawing-content delta))]
|
|
(ths/run-store
|
|
store done events
|
|
(fn [new-state]
|
|
(let [file' (ths/get-file-from-state new-state)
|
|
stored-shape (cths/get-shape file' :rect1)
|
|
drawing-copy (get-in new-state [:workspace-drawing :object])]
|
|
(t/is (= original-rect stored-shape))
|
|
(t/is (= :path (:type drawing-copy)))
|
|
(t/is (= (path/move-content
|
|
(:content (path/convert-to-path original-rect))
|
|
delta)
|
|
(:content drawing-copy)))))))))
|
|
|
|
(t/deftest unchanged-path-edition-preserves-simple-shape
|
|
(t/async
|
|
done
|
|
(let [file (pth/setup-rect-file)
|
|
original-rect (cths/get-shape file :rect1)
|
|
id (:id original-rect)
|
|
store (ths/setup-store file)
|
|
events (conj (pth/start-path-edition-events id) :interrupt)]
|
|
(ths/run-store
|
|
store done events
|
|
(fn [new-state]
|
|
(let [file' (ths/get-file-from-state new-state)
|
|
stored-shape (cths/get-shape file' :rect1)]
|
|
(t/is (= original-rect stored-shape))
|
|
(t/is (= :rect (:type stored-shape)))
|
|
(t/is (nil? (get-in new-state [:workspace-drawing :object])))))))))
|
|
|
|
(t/deftest changed-path-edition-is-persisted-when-finalized
|
|
(t/async
|
|
done
|
|
(let [file (pth/setup-rect-file)
|
|
original-rect (cths/get-shape file :rect1)
|
|
id (:id original-rect)
|
|
delta (gpt/point 10 5)
|
|
original-path (path/convert-to-path original-rect)
|
|
;; Persist the rectangle with an explicit close command.
|
|
changed-content (-> (:content original-path)
|
|
(path/move-content delta)
|
|
(path/close-loops))
|
|
expected-shape (-> original-path
|
|
(assoc :content changed-content)
|
|
(path/update-geometry))
|
|
store (ths/setup-store file)
|
|
events (into (pth/start-path-edition-events id)
|
|
[(pth/move-drawing-content delta)
|
|
:interrupt])]
|
|
(ths/run-store
|
|
store done events
|
|
(fn [new-state]
|
|
(let [file' (ths/get-file-from-state new-state)
|
|
stored-shape (cths/get-shape file' :rect1)]
|
|
(t/is (= expected-shape stored-shape))
|
|
(t/is (= :path (:type stored-shape)))
|
|
(t/is (nil? (get-in new-state [:workspace-drawing :object])))))))))
|
|
|
|
(t/deftest created-path-edition-cleans-drawing-state-on-exit
|
|
(t/async
|
|
done
|
|
(let [file (pth/setup-rect-file)
|
|
original-rect (cths/get-shape file :rect1)
|
|
id (:id original-rect)
|
|
store (ths/setup-store file)
|
|
events [(path.drawing/start-created-path-edition id)
|
|
:interrupt]]
|
|
(ths/run-store
|
|
store done events
|
|
(fn [new-state]
|
|
(let [file' (ths/get-file-from-state new-state)
|
|
stored-shape (cths/get-shape file' :rect1)]
|
|
(t/is (= original-rect stored-shape))
|
|
(t/is (nil? (get-in new-state [:workspace-local :edition])))
|
|
(t/is (nil? (get-in new-state [:workspace-local :edit-path id])))
|
|
(t/is (nil? (get-in new-state [:workspace-drawing :object])))))))))
|
|
|
|
(defn- with-dangling-subpath-start
|
|
[content]
|
|
(path/content (conj (vec content)
|
|
{:command :move-to
|
|
:params {:x 30 :y 40}})))
|
|
|
|
(t/deftest cancel-pending-segment-drops-dangling-subpath-start
|
|
(let [id (random-uuid)
|
|
content (pth/selectable-path-content)
|
|
state {:workspace-local
|
|
{:edition id
|
|
:edit-path {id {:edit-mode :draw
|
|
:last-point (gpt/point 30 40)
|
|
:preview {:command :line-to
|
|
:params {:x 50 :y 50}}}}}
|
|
:workspace-drawing
|
|
{:object {:id id
|
|
:type :path
|
|
:content (with-dangling-subpath-start content)}}}
|
|
state' (ptk/update (path.common/cancel-pending-segment) state)]
|
|
(t/is (= (vec content)
|
|
(vec (get-in state' [:workspace-drawing :object :content]))))
|
|
(t/is (nil? (get-in state' [:workspace-local :edit-path id :last-point])))
|
|
(t/is (nil? (get-in state' [:workspace-local :edit-path id :preview])))))
|
|
|
|
(t/deftest finish-path-drops-dangling-subpath-start
|
|
(let [id (random-uuid)
|
|
content (pth/selectable-path-content)
|
|
state {:workspace-local
|
|
{:edition id
|
|
:edit-path {id {:edit-mode :draw
|
|
:last-point (gpt/point 30 40)}}}
|
|
:workspace-drawing
|
|
{:object {:id id
|
|
:type :path
|
|
:content (with-dangling-subpath-start content)}}}
|
|
state' (ptk/update (path.common/finish-path) state)]
|
|
(t/is (= (vec (path/close-subpaths content))
|
|
(vec (get-in state' [:workspace-drawing :object :content]))))
|
|
(t/is (nil? (get-in state' [:workspace-local :edit-path id :last-point])))))
|
|
|
|
(t/deftest finalize-ignores-dangling-subpath-start
|
|
(let [file (pth/setup-rect-file)
|
|
rect (cths/get-shape file :rect1)
|
|
id (:id rect)
|
|
path-shape (path/convert-to-path rect)
|
|
old-content (:content path-shape)
|
|
state {:current-file-id (:id file)
|
|
:current-page-id (cthf/current-page-id file)
|
|
:files {(:id file) file}
|
|
:workspace-local
|
|
{:edition id
|
|
:edit-path {id {:old-content old-content}}}
|
|
:workspace-drawing
|
|
{:object (assoc path-shape
|
|
:content
|
|
(with-dangling-subpath-start old-content))}}
|
|
emissions (atom [])]
|
|
(->> (ptk/watch (path.changes/finalize-path-content id) state nil)
|
|
(rx/subs! #(swap! emissions conj %)))
|
|
(t/is (empty? @emissions))))
|
|
|
|
(t/deftest next-point-preview-is-suppressed-during-a-modifier-drag
|
|
(let [id (random-uuid)
|
|
content (path/content [{:command :move-to :params {:x 0 :y 0}}
|
|
{:command :line-to :params {:x 100 :y 0}}])
|
|
mk (fn [modifiers]
|
|
;; Draw mode keeps the path in the drawing object.
|
|
{:workspace-drawing {:object {:id id :type :path :content content}}
|
|
:workspace-local {:edition nil
|
|
:zoom 1
|
|
:edit-path {id {:edit-mode :draw
|
|
:last-point (gpt/point 100 0)
|
|
:content-modifiers modifiers}}}})
|
|
event (path.drawing/preview-next-point {:x 150 :y 40 :shift? false})
|
|
idle (ptk/update event (mk {}))
|
|
during (ptk/update event (mk {1 {:c1x 5 :c1y 5}}))]
|
|
;; no active drag: the next-point preview updates as usual
|
|
(t/is (some? (get-in idle [:workspace-local :edit-path id :preview])))
|
|
;; a placed handler is being dragged mid-draw: the preview must not move
|
|
(t/is (nil? (get-in during [:workspace-local :edit-path id :preview])))))
|
|
|
|
(t/deftest dragging-the-current-curve-forward-handle-while-drawing
|
|
(let [id (random-uuid)
|
|
content (path/content [{:command :move-to :params {:x 0 :y 0}}
|
|
{:command :curve-to
|
|
:params {:x 100 :y 0 :c1x 30 :c1y 0 :c2x 70 :c2y 0}}])
|
|
mk (fn []
|
|
;; Store the backward handle and transient forward handle.
|
|
{:workspace-drawing {:object {:id id :type :path :content content}}
|
|
:workspace-local {:edition nil
|
|
:zoom 1
|
|
:edit-path {id {:edit-mode :draw
|
|
:last-point (gpt/point 100 0)
|
|
:prev-handler (gpt/point 130 0)}}}})
|
|
drag (fn [alt?]
|
|
(ptk/update (path.drawing/drag-prev-handler
|
|
{:x 100 :y 50 :alt? alt? :shift? false})
|
|
(mk)))
|
|
finish (fn [state] (ptk/update (path.drawing/finish-drag) state))
|
|
c2-of (fn [state]
|
|
(-> (get-in state [:workspace-drawing :object :content])
|
|
(vec) (nth 1) :params (select-keys [:c2x :c2y])))]
|
|
|
|
(t/testing "no alt: the forward handle follows the pointer and the committed backward handle mirrors it"
|
|
(let [dragged (drag false)]
|
|
;; the forward handle tracks the pointer
|
|
(t/is (= (gpt/point 100 50)
|
|
(get-in dragged [:workspace-local :edit-path id :drag-handler])))
|
|
;; the stale transient forward handle is cleared so it is not double-rendered
|
|
(t/is (nil? (get-in dragged [:workspace-local :edit-path id :prev-handler])))
|
|
(let [committed (finish dragged)]
|
|
;; c2 = 2*node - forward = (100,-50)
|
|
(t/is (= {:c2x 100 :c2y -50} (c2-of committed)))
|
|
;; the new forward handle becomes the prev-handler
|
|
(t/is (= (gpt/point 100 50)
|
|
(get-in committed [:workspace-local :edit-path id :prev-handler]))))))
|
|
|
|
(t/testing "alt: the forward handle moves on its own, the backward handle stays put"
|
|
(let [committed (finish (drag true))]
|
|
(t/is (= {:c2x 70 :c2y 0} (c2-of committed)))))))
|
|
|
|
(t/deftest dragging-the-current-curve-backward-handle-while-drawing
|
|
;; Dragging the backward handle mirrors the transient forward handle.
|
|
(let [id (random-uuid)
|
|
content (path/content [{:command :move-to :params {:x 0 :y 0}}
|
|
{:command :curve-to
|
|
:params {:x 100 :y 0 :c1x 30 :c1y 0 :c2x 70 :c2y 0}}])
|
|
mk (fn []
|
|
{:workspace-drawing {:object {:id id :type :path :content content}}
|
|
:workspace-local {:edition nil
|
|
:zoom 1
|
|
:edit-path {id {:edit-mode :draw
|
|
:last-point (gpt/point 100 0)
|
|
:prev-handler (gpt/point 130 0)}}}})
|
|
;; Drag the backward handle to `(70, -40)`.
|
|
drag (fn [mode]
|
|
(ptk/update (path.edition/modify-selected-handlers
|
|
id [1 :c2] {} 0 -40 mode (= mode :smart))
|
|
(mk)))
|
|
prev-of (fn [state] (get-in state [:workspace-local :edit-path id :prev-handler]))]
|
|
|
|
(t/testing "smart (no modifier): the forward handle mirrors the angle, keeping its own length"
|
|
;; Keep the forward handle's length while mirroring its angle.
|
|
(t/is (= (gpt/point 118 24) (prev-of (drag :smart)))))
|
|
|
|
(t/testing "mirror (mod): the forward handle full-mirrors to equal length"
|
|
;; Mirror the forward handle around the node.
|
|
(t/is (= (gpt/point 130 40) (prev-of (drag :mirror)))))
|
|
|
|
(t/testing "independent (alt): the forward handle is left untouched"
|
|
(t/is (= (gpt/point 130 0) (prev-of (drag :independent)))))))
|
|
|
|
(t/deftest path-local-undo-redo-restores-content-and-clears-preview
|
|
(let [id (random-uuid)
|
|
content-a (path/content [{:command :move-to :params {:x 0 :y 0}}
|
|
{:command :line-to :params {:x 10 :y 0}}])
|
|
content-b (path/content [{:command :move-to :params {:x 0 :y 0}}
|
|
{:command :line-to :params {:x 10 :y 5}}])
|
|
base (-> (pth/selectable-path-state id content-a path.helpers/empty-selection)
|
|
(assoc-in [:workspace-local :edit-path id :undo-stack] (u/make-stack)))
|
|
;; Capture both content states around a stale preview.
|
|
s1 (ptk/update (path.undo/add-undo-entry) base)
|
|
s2 (-> (path.state/set-content s1 content-b)
|
|
(assoc-in [:workspace-local :edit-path id :preview]
|
|
{:command :line-to :params {:x 99 :y 99}}))
|
|
s3 (ptk/update (path.undo/add-undo-entry) s2)
|
|
s4 (ptk/update (path.undo/undo-path) s3)
|
|
s5 (ptk/update (path.undo/redo-path) s4)]
|
|
(t/is (= content-b (path.state/get-path s3 :content)))
|
|
(t/is (= content-a (path.state/get-path s4 :content)))
|
|
;; Restoring an entry drops its render-only preview.
|
|
(t/is (nil? (get-in s4 [:workspace-local :edit-path id :preview])))
|
|
(t/is (= content-b (path.state/get-path s5 :content)))))
|
|
|
|
(t/deftest path-undo-entry-never-captures-the-transient-preview
|
|
(let [id (random-uuid)
|
|
state (-> (pth/selectable-path-state id (pth/selectable-path-content)
|
|
path.helpers/empty-selection)
|
|
(assoc-in [:workspace-local :edit-path id :undo-stack] (u/make-stack))
|
|
(assoc-in [:workspace-local :edit-path id :preview]
|
|
{:command :line-to :params {:x 99 :y 99}}))
|
|
state' (ptk/update (path.undo/add-undo-entry) state)
|
|
entry (u/peek (get-in state' [:workspace-local :edit-path id :undo-stack]))]
|
|
(t/is (some? entry))
|
|
(t/is (not (contains? entry :preview)))))
|
|
|
|
;; Tool operations through the full edition lifecycle.
|
|
|
|
(t/deftest tool-make-curve-persists-through-edition-lifecycle
|
|
(t/async
|
|
done
|
|
(let [file (pth/setup-rect-file)
|
|
rect (cths/get-shape file :rect1)
|
|
id (:id rect)
|
|
store (ths/setup-store file)
|
|
events (into (pth/start-path-edition-events id)
|
|
[(path.selection/select-node 1 false)
|
|
(path.tools/make-curve)
|
|
:interrupt])]
|
|
(ths/run-store
|
|
store done events
|
|
(fn [new-state]
|
|
(let [stored (cths/get-shape (ths/get-file-from-state new-state) :rect1)]
|
|
(t/is (= :path (:type stored)))
|
|
;; make-curve on a corner introduces at least one curve segment
|
|
(t/is (some #(= :curve-to (:command %)) (seq (:content stored))))
|
|
(t/is (nil? (get-in new-state [:workspace-drawing :object])))))))))
|
|
|
|
(t/deftest tool-remove-node-persists-through-edition-lifecycle
|
|
(t/async
|
|
done
|
|
(let [file (pth/setup-rect-file)
|
|
rect (cths/get-shape file :rect1)
|
|
id (:id rect)
|
|
orig-nodes (count (path/get-points (:content (path/convert-to-path rect))))
|
|
store (ths/setup-store file)
|
|
events (into (pth/start-path-edition-events id)
|
|
[(path.selection/select-node 1 false)
|
|
(path.tools/remove-node)
|
|
:interrupt])]
|
|
(ths/run-store
|
|
store done events
|
|
(fn [new-state]
|
|
(let [stored (cths/get-shape (ths/get-file-from-state new-state) :rect1)]
|
|
(t/is (= :path (:type stored)))
|
|
;; removing a node leaves fewer nodes than the converted rect had
|
|
(t/is (< (count (path/get-points (:content stored))) orig-nodes))
|
|
(t/is (nil? (get-in new-state [:workspace-drawing :object])))))))))
|
|
|