diff --git a/.playwright-mcp/console-2026-05-13T10-13-49-591Z.log b/.playwright-mcp/console-2026-05-13T10-13-49-591Z.log new file mode 100644 index 0000000000..6af49f2ec5 --- /dev/null +++ b/.playwright-mcp/console-2026-05-13T10-13-49-591Z.log @@ -0,0 +1,5 @@ +[-1358936ms] [INFO] Autofocus processing was blocked because a document already has a focused element. @ devtools://devtools/bundled/devtools_app.html?remoteBase=https://chrome-devtools-frontend.appspot.com/serve_file/@8625e066febc721e015ea99842da12901eb7ed73/&targetType=tab&can_dock=true:0 +[-1358953ms] [TIMEEND] Main._createAppUI: 218.40185546875 ms @ devtools://devtools/bundled/entrypoints/main/main.js:10 +[-1358877ms] [TIMEEND] Main._showAppUI: 75.088134765625 ms @ devtools://devtools/bundled/entrypoints/main/main.js:10 +[-1358543ms] [TIMEEND] Main._initializeTarget: 333.44091796875 ms @ devtools://devtools/bundled/entrypoints/main/main.js:10 +[-1358443ms] [TIMEEND] Main._lateInitialization: 0.358154296875 ms @ devtools://devtools/bundled/entrypoints/main/main.js:10 diff --git a/CHANGES.md b/CHANGES.md index 3af4938cac..29fd907d44 100644 --- a/CHANGES.md +++ b/CHANGES.md @@ -24,6 +24,7 @@ - Fix lost-update race on `team.features` during concurrent file creation (by @web-dev0521) [Github #9197](https://github.com/penpot/penpot/issues/9197) - Fix copy and paste actions crashing the workspace on insecure origins (plain HTTP / non-`localhost`) where the Clipboard API is unavailable (by @MilosM348) [Github #6514](https://github.com/penpot/penpot/issues/6514) - Fix blend-mode dropdown leaving the canvas rendered with the last hover-preview blend mode when dismissed without selecting an option; the WASM render is now reverted to the saved blend mode on pointer-leave (by @edwin-rivera-dev) [Github #XXXX](https://github.com/penpot/penpot/issues/XXXX) +- Fix copy/paste of nested swapped component sub-instances persisting with a missing swap slot, causing the server to reject the change with a `:referential-integrity` validation error; when the clipboard advances a sub-instance head's `shape-ref` to the next reference level, the original `shape-ref` is now preserved as a swap slot ## 2.16.0 (Unreleased) diff --git a/frontend/src/app/main/data/workspace/clipboard.cljs b/frontend/src/app/main/data/workspace/clipboard.cljs index 70c34c5287..8fa9301fa1 100644 --- a/frontend/src/app/main/data/workspace/clipboard.cljs +++ b/frontend/src/app/main/data/workspace/clipboard.cljs @@ -169,13 +169,28 @@ objects)) (advance-shape [file libraries page level-delta objects shape] - (let [new-shape-ref (ctf/advance-shape-ref file page libraries shape level-delta {:include-deleted? true}) - container (ctn/make-container page :page) - new-touched (ctf/get-touched-from-ref-chain-until-target-ref container libraries shape new-shape-ref)] + (let [new-shape-ref (ctf/advance-shape-ref file page libraries shape level-delta {:include-deleted? true}) + container (ctn/make-container page :page) + new-touched (ctf/get-touched-from-ref-chain-until-target-ref container libraries shape new-shape-ref) + old-shape-ref (:shape-ref shape) + ref-changed? (and (some? new-shape-ref) (not= new-shape-ref old-shape-ref)) + ;; If we advance the shape-ref of a subinstance head, the new + ;; shape-ref will no longer match the parent component child + ;; at the same position, so the shape becomes "swapped" with + ;; respect to the enclosing instance. Preserve the original + ;; shape-ref as swap-slot so that referential integrity holds + ;; when the copy is pasted in a context where the parent's + ;; near main is reachable. + needs-swap-slot? (and ref-changed? + (ctc/subinstance-head? shape) + (nil? (ctc/get-swap-slot shape)))] (cond-> objects - (and (some? new-shape-ref) (not= new-shape-ref (:shape-ref shape))) + ref-changed? (-> (assoc-in [(:id shape) :shape-ref] new-shape-ref) - (assoc-in [(:id shape) :touched] new-touched))))) + (assoc-in [(:id shape) :touched] new-touched)) + + needs-swap-slot? + (update (:id shape) ctc/set-swap-slot old-shape-ref)))) (on-copy-error [error] (js/console.error "clipboard blocked:" error) diff --git a/frontend/test/frontend_tests/helpers/pages.cljs b/frontend/test/frontend_tests/helpers/pages.cljs index 8993326237..d8976b1d44 100644 --- a/frontend/test/frontend_tests/helpers/pages.cljs +++ b/frontend/test/frontend_tests/helpers/pages.cljs @@ -247,10 +247,21 @@ objects)) (advance-shape [file libraries page level-delta objects shape] - (let [new-shape-ref (ctf/advance-shape-ref file page libraries shape level-delta {:include-deleted? true})] + (let [new-shape-ref (ctf/advance-shape-ref file page libraries shape level-delta {:include-deleted? true}) + container (ctn/make-container page :page) + new-touched (ctf/get-touched-from-ref-chain-until-target-ref container libraries shape new-shape-ref) + old-shape-ref (:shape-ref shape) + ref-changed? (and (some? new-shape-ref) (not= new-shape-ref old-shape-ref)) + needs-swap-slot? (and ref-changed? + (ctk/subinstance-head? shape) + (nil? (ctk/get-swap-slot shape)))] (cond-> objects - (and (some? new-shape-ref) (not= new-shape-ref (:shape-ref shape))) - (assoc-in [(:id shape) :shape-ref] new-shape-ref))))] + ref-changed? + (-> (assoc-in [(:id shape) :shape-ref] new-shape-ref) + (assoc-in [(:id shape) :touched] new-touched)) + + needs-swap-slot? + (update (:id shape) ctk/set-swap-slot old-shape-ref))))] (let [file-id (:id file) frame-id (cfh/common-parent-frame objects selected) diff --git a/frontend/test/frontend_tests/logic/comp_remove_swap_slots_test.cljs b/frontend/test/frontend_tests/logic/comp_remove_swap_slots_test.cljs index ac8da0c92d..6d41ad3f32 100644 --- a/frontend/test/frontend_tests/logic/comp_remove_swap_slots_test.cljs +++ b/frontend/test/frontend_tests/logic/comp_remove_swap_slots_test.cljs @@ -95,778 +95,848 @@ (t/deftest test-remove-swap-slot-copy-paste-blue1-to-root (t/async - done - (let [;; ==== Setup - file (setup-file) - store (ths/setup-store file) + done + (let [;; ==== Setup + file (setup-file) + store (ths/setup-store file) - ;; ==== Action - page (cthf/current-page file) - blue1 (cths/get-shape file :blue1) - features #{"components/v2"} - version 46 + ;; ==== Action + page (cthf/current-page file) + blue1 (cths/get-shape file :blue1) + features #{"components/v2"} + version 46 - pdata (thp/simulate-copy-shape #{(:id blue1)} (:objects page) {(:id file) file} page file features version) + pdata (thp/simulate-copy-shape #{(:id blue1)} (:objects page) {(:id file) file} page file features version) - events - [(dws/select-shape uuid/zero) - (dw/paste-shapes pdata)]] + events + [(dws/select-shape uuid/zero) + (dw/paste-shapes pdata)]] - (ths/run-store - store done events - (fn [new-state] - (let [;; ==== Get - file' (ths/get-file-from-state new-state) - page' (cthf/current-page file') - blue1' (cths/get-shape file' :blue1) - copied-blue1' (find-copied-shape blue1' page' uuid/zero)] + (ths/run-store + store done events + (fn [new-state] + (let [;; ==== Get + file' (ths/get-file-from-state new-state) + page' (cthf/current-page file') + blue1' (cths/get-shape file' :blue1) + copied-blue1' (find-copied-shape blue1' page' uuid/zero)] - ;; ==== Check + ;; ==== Check - ;; blue1 has swap-id - (t/is (some? (ctk/get-swap-slot blue1'))) + ;; blue1 has swap-id + (t/is (some? (ctk/get-swap-slot blue1'))) - ;; copied-blue1 has not swap-id - (t/is (some? copied-blue1')) - (t/is (nil? (ctk/get-swap-slot copied-blue1'))))))))) + ;; copied-blue1 has not swap-id + (t/is (some? copied-blue1')) + (t/is (nil? (ctk/get-swap-slot copied-blue1'))))))))) (t/deftest test-remove-swap-slot-copy-paste-blue1-to-b1 (t/async - done - (let [;; ==== Setup - file (setup-file) - store (ths/setup-store file) + done + (let [;; ==== Setup + file (setup-file) + store (ths/setup-store file) - ;; ==== Action - page (cthf/current-page file) - b1 (cths/get-shape file :frame-b1) - blue1 (cths/get-shape file :blue1) - features #{"components/v2"} - version 46 + ;; ==== Action + page (cthf/current-page file) + b1 (cths/get-shape file :frame-b1) + blue1 (cths/get-shape file :blue1) + features #{"components/v2"} + version 46 - pdata (thp/simulate-copy-shape #{(:id blue1)} (:objects page) {(:id file) file} page file features version) + pdata (thp/simulate-copy-shape #{(:id blue1)} (:objects page) {(:id file) file} page file features version) - events - [(dws/select-shape (:id b1)) - (dw/paste-shapes pdata)]] + events + [(dws/select-shape (:id b1)) + (dw/paste-shapes pdata)]] - (ths/run-store - store done events - (fn [new-state] - (let [;; ==== Get - file' (ths/get-file-from-state new-state) - page' (cthf/current-page file') - b1' (cths/get-shape file' :frame-b1) - blue1' (cths/get-shape file' :blue1) - copied-blue1' (find-copied-shape blue1' page' (:id b1'))] + (ths/run-store + store done events + (fn [new-state] + (let [;; ==== Get + file' (ths/get-file-from-state new-state) + page' (cthf/current-page file') + b1' (cths/get-shape file' :frame-b1) + blue1' (cths/get-shape file' :blue1) + copied-blue1' (find-copied-shape blue1' page' (:id b1'))] - ;; ==== Check - ;; blue1 has swap-id - (t/is (some? (ctk/get-swap-slot blue1'))) + ;; ==== Check + ;; blue1 has swap-id + (t/is (some? (ctk/get-swap-slot blue1'))) - ;; copied-blue1 has not swap-id - (t/is (some? copied-blue1')) - (t/is (nil? (ctk/get-swap-slot copied-blue1'))))))))) + ;; copied-blue1 has not swap-id + (t/is (some? copied-blue1')) + (t/is (nil? (ctk/get-swap-slot copied-blue1'))))))))) (t/deftest test-remove-swap-slot-copy-paste-blue1-to-yellow (t/async - done - (let [;; ==== Setup - file (setup-file) - store (ths/setup-store file) + done + (let [;; ==== Setup + file (setup-file) + store (ths/setup-store file) - ;; ==== Action - page (cthf/current-page file) - yellow (cths/get-shape file :frame-yellow) - blue1 (cths/get-shape file :blue1) - features #{"components/v2"} - version 46 + ;; ==== Action + page (cthf/current-page file) + yellow (cths/get-shape file :frame-yellow) + blue1 (cths/get-shape file :blue1) + features #{"components/v2"} + version 46 - pdata (thp/simulate-copy-shape #{(:id blue1)} (:objects page) {(:id file) file} page file features version) + pdata (thp/simulate-copy-shape #{(:id blue1)} (:objects page) {(:id file) file} page file features version) - events - [(dws/select-shape (:id yellow)) - (dw/paste-shapes pdata)]] + events + [(dws/select-shape (:id yellow)) + (dw/paste-shapes pdata)]] - (ths/run-store - store done events - (fn [new-state] - (let [;; ==== Get - file' (ths/get-file-from-state new-state) - page' (cthf/current-page file') - yellow' (cths/get-shape file' :frame-yellow) - blue1' (cths/get-shape file' :blue1) - copied-blue1' (find-copied-shape blue1' page' (:id yellow'))] + (ths/run-store + store done events + (fn [new-state] + (let [;; ==== Get + file' (ths/get-file-from-state new-state) + page' (cthf/current-page file') + yellow' (cths/get-shape file' :frame-yellow) + blue1' (cths/get-shape file' :blue1) + copied-blue1' (find-copied-shape blue1' page' (:id yellow'))] - ;; ==== Check - ;; blue1 has swap-id - (t/is (some? (ctk/get-swap-slot blue1'))) + ;; ==== Check + ;; blue1 has swap-id + (t/is (some? (ctk/get-swap-slot blue1'))) - ;; copied-blue1 has not swap-id - (t/is (some? copied-blue1')) - (t/is (nil? (ctk/get-swap-slot copied-blue1'))))))))) + ;; copied-blue1 has not swap-id + (t/is (some? copied-blue1')) + (t/is (nil? (ctk/get-swap-slot copied-blue1'))))))))) (t/deftest test-remove-swap-slot-copy-paste-blue1-to-b2 (t/async - done - (let [;; ==== Setup - file (setup-file) - store (ths/setup-store file) + done + (let [;; ==== Setup + file (setup-file) + store (ths/setup-store file) - ;; ==== Action - page (cthf/current-page file) - b2 (cths/get-shape file :frame-b2) - blue1 (cths/get-shape file :blue1) - features #{"components/v2"} - version 46 + ;; ==== Action + page (cthf/current-page file) + b2 (cths/get-shape file :frame-b2) + blue1 (cths/get-shape file :blue1) + features #{"components/v2"} + version 46 - pdata (thp/simulate-copy-shape #{(:id blue1)} (:objects page) {(:id file) file} page file features version) + pdata (thp/simulate-copy-shape #{(:id blue1)} (:objects page) {(:id file) file} page file features version) - events - [(dws/select-shape (:id b2)) - (dw/paste-shapes pdata)]] + events + [(dws/select-shape (:id b2)) + (dw/paste-shapes pdata)]] - (ths/run-store - store done events - (fn [new-state] - (let [;; ==== Get - file' (ths/get-file-from-state new-state) - page' (cthf/current-page file') - b2' (cths/get-shape file' :frame-b2) - blue1' (cths/get-shape file' :blue1) - copied-blue1' (find-copied-shape blue1' page' (:id b2'))] + (ths/run-store + store done events + (fn [new-state] + (let [;; ==== Get + file' (ths/get-file-from-state new-state) + page' (cthf/current-page file') + b2' (cths/get-shape file' :frame-b2) + blue1' (cths/get-shape file' :blue1) + copied-blue1' (find-copied-shape blue1' page' (:id b2'))] - ;; ==== Check - ;; blue1 has swap-id - (t/is (some? (ctk/get-swap-slot blue1'))) + ;; ==== Check + ;; blue1 has swap-id + (t/is (some? (ctk/get-swap-slot blue1'))) - ;; copied-blue1 has not swap-id - (t/is (some? copied-blue1')) - (t/is (nil? (ctk/get-swap-slot copied-blue1'))))))))) + ;; copied-blue1 has not swap-id + (t/is (some? copied-blue1')) + (t/is (nil? (ctk/get-swap-slot copied-blue1'))))))))) (t/deftest test-remove-swap-slot-cut-paste-blue1-to-root (t/async - done - (let [;; ==== Setup - file (setup-file) - store (ths/setup-store file) + done + (let [;; ==== Setup + file (setup-file) + store (ths/setup-store file) - ;; ==== Action - page (cthf/current-page file) - blue1 (cths/get-shape file :blue1) - features #{"components/v2"} - version 46 + ;; ==== Action + page (cthf/current-page file) + blue1 (cths/get-shape file :blue1) + features #{"components/v2"} + version 46 - pdata (thp/simulate-copy-shape #{(:id blue1)} (:objects page) {(:id file) file} page file features version) + pdata (thp/simulate-copy-shape #{(:id blue1)} (:objects page) {(:id file) file} page file features version) - events - [(dws/select-shape (:id blue1)) - (dw/delete-selected) - (dws/select-shape uuid/zero) - (dw/paste-shapes pdata)]] + events + [(dws/select-shape (:id blue1)) + (dw/delete-selected) + (dws/select-shape uuid/zero) + (dw/paste-shapes pdata)]] - (ths/run-store - store done events - (fn [new-state] - (let [;; ==== Get - file' (ths/get-file-from-state new-state) - page' (cthf/current-page file') - copied-blue1' (find-copied-shape blue1 page' uuid/zero)] + (ths/run-store + store done events + (fn [new-state] + (let [;; ==== Get + file' (ths/get-file-from-state new-state) + page' (cthf/current-page file') + copied-blue1' (find-copied-shape blue1 page' uuid/zero)] - ;; ==== Check - ;; copied-blue1 has not swap-id - (t/is (some? copied-blue1')) - (t/is (nil? (ctk/get-swap-slot copied-blue1'))))))))) + ;; ==== Check + ;; copied-blue1 has not swap-id + (t/is (some? copied-blue1')) + (t/is (nil? (ctk/get-swap-slot copied-blue1'))))))))) (t/deftest test-remove-swap-slot-cut-paste-blue1-to-yellow (t/async - done - (let [;; ==== Setup - file (setup-file) - store (ths/setup-store file) + done + (let [;; ==== Setup + file (setup-file) + store (ths/setup-store file) - ;; ==== Action - page (cthf/current-page file) - yellow (cths/get-shape file :frame-yellow) - blue1 (cths/get-shape file :blue1) - features #{"components/v2"} - version 46 + ;; ==== Action + page (cthf/current-page file) + yellow (cths/get-shape file :frame-yellow) + blue1 (cths/get-shape file :blue1) + features #{"components/v2"} + version 46 - pdata (thp/simulate-copy-shape #{(:id blue1)} (:objects page) {(:id file) file} page file features version) + pdata (thp/simulate-copy-shape #{(:id blue1)} (:objects page) {(:id file) file} page file features version) - events - [(dws/select-shape (:id blue1)) - (dw/delete-selected) - (dws/select-shape (:id yellow)) - (dw/paste-shapes pdata)]] + events + [(dws/select-shape (:id blue1)) + (dw/delete-selected) + (dws/select-shape (:id yellow)) + (dw/paste-shapes pdata)]] - (ths/run-store - store done events - (fn [new-state] - (let [;; ==== Get - file' (ths/get-file-from-state new-state) - page' (cthf/current-page file') - yellow' (cths/get-shape file' :frame-yellow) - copied-blue1' (find-copied-shape blue1 page' (:id yellow'))] + (ths/run-store + store done events + (fn [new-state] + (let [;; ==== Get + file' (ths/get-file-from-state new-state) + page' (cthf/current-page file') + yellow' (cths/get-shape file' :frame-yellow) + copied-blue1' (find-copied-shape blue1 page' (:id yellow'))] - ;; ==== Check - ;; copied-blue1 has not swap-id - (t/is (some? copied-blue1')) - (t/is (nil? (ctk/get-swap-slot copied-blue1'))))))))) + ;; ==== Check + ;; copied-blue1 has not swap-id + (t/is (some? copied-blue1')) + (t/is (nil? (ctk/get-swap-slot copied-blue1'))))))))) (t/deftest test-remove-swap-slot-cut-paste-blue1-to-b2 (t/async - done - (let [;; ==== Setup - file (setup-file) - store (ths/setup-store file) + done + (let [;; ==== Setup + file (setup-file) + store (ths/setup-store file) - ;; ==== Action - page (cthf/current-page file) - b2 (cths/get-shape file :frame-b2) - blue1 (cths/get-shape file :blue1) - features #{"components/v2"} - version 46 + ;; ==== Action + page (cthf/current-page file) + b2 (cths/get-shape file :frame-b2) + blue1 (cths/get-shape file :blue1) + features #{"components/v2"} + version 46 - pdata (thp/simulate-copy-shape #{(:id blue1)} (:objects page) {(:id file) file} page file features version) + pdata (thp/simulate-copy-shape #{(:id blue1)} (:objects page) {(:id file) file} page file features version) - events - [(dws/select-shape (:id blue1)) - (dw/delete-selected) - (dws/select-shape (:id b2)) - (dw/paste-shapes pdata)]] + events + [(dws/select-shape (:id blue1)) + (dw/delete-selected) + (dws/select-shape (:id b2)) + (dw/paste-shapes pdata)]] - (ths/run-store - store done events - (fn [new-state] - (let [;; ==== Get - file' (ths/get-file-from-state new-state) - page' (cthf/current-page file') - b2' (cths/get-shape file' :frame-b2) - copied-blue1' (find-copied-shape blue1 page' (:id b2'))] + (ths/run-store + store done events + (fn [new-state] + (let [;; ==== Get + file' (ths/get-file-from-state new-state) + page' (cthf/current-page file') + b2' (cths/get-shape file' :frame-b2) + copied-blue1' (find-copied-shape blue1 page' (:id b2'))] - ;; ==== Check - ;; copied-blue1 has not swap-id - (t/is (some? copied-blue1')) - (t/is (nil? (ctk/get-swap-slot copied-blue1'))))))))) + ;; ==== Check + ;; copied-blue1 has not swap-id + (t/is (some? copied-blue1')) + (t/is (nil? (ctk/get-swap-slot copied-blue1'))))))))) (t/deftest test-remove-swap-slot-copy-paste-yellow-to-root (t/async - done - (let [;; ==== Setup - file (setup-file-blue1-in-yellow) - store (ths/setup-store file) + done + (let [;; ==== Setup + file (setup-file-blue1-in-yellow) + store (ths/setup-store file) - ;; ==== Action - page (cthf/current-page file) - yellow (cths/get-shape file :frame-yellow) - features #{"components/v2"} - version 46 + ;; ==== Action + page (cthf/current-page file) + yellow (cths/get-shape file :frame-yellow) + features #{"components/v2"} + version 46 - pdata (thp/simulate-copy-shape #{(:id yellow)} (:objects page) {(:id file) file} page file features version) + pdata (thp/simulate-copy-shape #{(:id yellow)} (:objects page) {(:id file) file} page file features version) - events - [(dws/select-shape uuid/zero) - (dw/paste-shapes pdata)]] + events + [(dws/select-shape uuid/zero) + (dw/paste-shapes pdata)]] - (ths/run-store - store done events - (fn [new-state] - (let [;; ==== Get - file' (ths/get-file-from-state new-state) - page' (cthf/current-page file') - copied-yellow' (find-copied-shape yellow page' uuid/zero) - blue1' (cths/get-shape file' :blue1) - copied-blue1' (find-copied-shape blue1' page' (:id copied-yellow'))] + (ths/run-store + store done events + (fn [new-state] + (let [;; ==== Get + file' (ths/get-file-from-state new-state) + page' (cthf/current-page file') + copied-yellow' (find-copied-shape yellow page' uuid/zero) + blue1' (cths/get-shape file' :blue1) + copied-blue1' (find-copied-shape blue1' page' (:id copied-yellow'))] - ;; ==== Check + ;; ==== Check - ;; blue1 has swap-id - (t/is (some? (ctk/get-swap-slot blue1'))) + ;; blue1 has swap-id + (t/is (some? (ctk/get-swap-slot blue1'))) - ;; copied-blue1 has not swap-id - (t/is (some? copied-blue1')) - (t/is (nil? (ctk/get-swap-slot copied-blue1'))))))))) + ;; copied-blue1 has not swap-id + (t/is (some? copied-blue1')) + (t/is (nil? (ctk/get-swap-slot copied-blue1'))))))))) (t/deftest test-remove-swap-slot-copy-paste-yellow-to-b1 (t/async - done - (let [;; ==== Setup - file (setup-file-blue1-in-yellow) - store (ths/setup-store file) + done + (let [;; ==== Setup + file (setup-file-blue1-in-yellow) + store (ths/setup-store file) - ;; ==== Action - page (cthf/current-page file) - yellow (cths/get-shape file :frame-yellow) - b1 (cths/get-shape file :frame-b1) - features #{"components/v2"} - version 46 + ;; ==== Action + page (cthf/current-page file) + yellow (cths/get-shape file :frame-yellow) + b1 (cths/get-shape file :frame-b1) + features #{"components/v2"} + version 46 - pdata (thp/simulate-copy-shape #{(:id yellow)} (:objects page) {(:id file) file} page file features version) + pdata (thp/simulate-copy-shape #{(:id yellow)} (:objects page) {(:id file) file} page file features version) - events - [(dws/select-shape (:id b1)) - (dw/paste-shapes pdata)]] + events + [(dws/select-shape (:id b1)) + (dw/paste-shapes pdata)]] - (ths/run-store - store done events - (fn [new-state] - (let [;; ==== Get - file' (ths/get-file-from-state new-state) - page' (cthf/current-page file') - b1' (cths/get-shape file' :frame-b1) - copied-yellow' (find-copied-shape yellow page' (:id b1')) - blue1' (cths/get-shape file' :blue1) - copied-blue1' (find-copied-shape blue1' page' (:id copied-yellow'))] + (ths/run-store + store done events + (fn [new-state] + (let [;; ==== Get + file' (ths/get-file-from-state new-state) + page' (cthf/current-page file') + b1' (cths/get-shape file' :frame-b1) + copied-yellow' (find-copied-shape yellow page' (:id b1')) + blue1' (cths/get-shape file' :blue1) + copied-blue1' (find-copied-shape blue1' page' (:id copied-yellow'))] - ;; ==== Check + ;; ==== Check - ;; blue1 has swap-id - (t/is (some? (ctk/get-swap-slot blue1'))) + ;; blue1 has swap-id + (t/is (some? (ctk/get-swap-slot blue1'))) - ;; copied-blue1 has not swap-id - (t/is (some? copied-blue1')) - (t/is (nil? (ctk/get-swap-slot copied-blue1'))))))))) + ;; copied-blue1 has not swap-id + (t/is (some? copied-blue1')) + (t/is (nil? (ctk/get-swap-slot copied-blue1'))))))))) (t/deftest test-remove-swap-slot-copy-paste-yellow-to-b2 (t/async - done - (let [;; ==== Setup - file (setup-file-blue1-in-yellow) - store (ths/setup-store file) + done + (let [;; ==== Setup + file (setup-file-blue1-in-yellow) + store (ths/setup-store file) - ;; ==== Action - page (cthf/current-page file) - yellow (cths/get-shape file :frame-yellow) - b2 (cths/get-shape file :frame-b2) - features #{"components/v2"} - version 46 + ;; ==== Action + page (cthf/current-page file) + yellow (cths/get-shape file :frame-yellow) + b2 (cths/get-shape file :frame-b2) + features #{"components/v2"} + version 46 - pdata (thp/simulate-copy-shape #{(:id yellow)} (:objects page) {(:id file) file} page file features version) + pdata (thp/simulate-copy-shape #{(:id yellow)} (:objects page) {(:id file) file} page file features version) - events - [(dws/select-shape (:id b2)) - (dw/paste-shapes pdata)]] + events + [(dws/select-shape (:id b2)) + (dw/paste-shapes pdata)]] - (ths/run-store - store done events - (fn [new-state] - (let [;; ==== Get - file' (ths/get-file-from-state new-state) - page' (cthf/current-page file') - b2' (cths/get-shape file' :frame-b2) - copied-yellow' (find-copied-shape yellow page' (:id b2')) - blue1' (cths/get-shape file' :blue1) - copied-blue1' (find-copied-shape blue1' page' (:id copied-yellow'))] + (ths/run-store + store done events + (fn [new-state] + (let [;; ==== Get + file' (ths/get-file-from-state new-state) + page' (cthf/current-page file') + b2' (cths/get-shape file' :frame-b2) + copied-yellow' (find-copied-shape yellow page' (:id b2')) + blue1' (cths/get-shape file' :blue1) + copied-blue1' (find-copied-shape blue1' page' (:id copied-yellow'))] - ;; ==== Check + ;; ==== Check - ;; blue1 has swap-id - (t/is (some? (ctk/get-swap-slot blue1'))) - ;; copied-blue1 has not swap-id - (t/is (some? copied-blue1')) - (t/is (nil? (ctk/get-swap-slot copied-blue1'))))))))) + ;; blue1 has swap-id + (t/is (some? (ctk/get-swap-slot blue1'))) + ;; copied-blue1 has not swap-id + (t/is (some? copied-blue1')) + (t/is (nil? (ctk/get-swap-slot copied-blue1'))))))))) (t/deftest test-remove-swap-slot-cut-paste-yellow-to-root (t/async - done - (let [;; ==== Setup - file (setup-file-blue1-in-yellow) - store (ths/setup-store file) - blue1 (cths/get-shape file :blue1) + done + (let [;; ==== Setup + file (setup-file-blue1-in-yellow) + store (ths/setup-store file) + blue1 (cths/get-shape file :blue1) - ;; ==== Action - page (cthf/current-page file) - yellow (cths/get-shape file :frame-yellow) - features #{"components/v2"} - version 46 + ;; ==== Action + page (cthf/current-page file) + yellow (cths/get-shape file :frame-yellow) + features #{"components/v2"} + version 46 - pdata (thp/simulate-copy-shape #{(:id yellow)} (:objects page) {(:id file) file} page file features version) + pdata (thp/simulate-copy-shape #{(:id yellow)} (:objects page) {(:id file) file} page file features version) - events - [(dws/select-shape (:id yellow)) - (dw/delete-selected) - (dws/select-shape uuid/zero) - (dw/paste-shapes pdata)]] + events + [(dws/select-shape (:id yellow)) + (dw/delete-selected) + (dws/select-shape uuid/zero) + (dw/paste-shapes pdata)]] - (ths/run-store - store done events - (fn [new-state] - (let [;; ==== Get - file' (ths/get-file-from-state new-state) - page' (cthf/current-page file') - copied-yellow' (find-copied-shape yellow page' uuid/zero) - copied-blue1' (find-copied-shape blue1 page' (:id copied-yellow'))] + (ths/run-store + store done events + (fn [new-state] + (let [;; ==== Get + file' (ths/get-file-from-state new-state) + page' (cthf/current-page file') + copied-yellow' (find-copied-shape yellow page' uuid/zero) + copied-blue1' (find-copied-shape blue1 page' (:id copied-yellow'))] - ;; ==== Check - ;; copied-blue1 has not swap-id - (t/is (some? copied-blue1')) - (t/is (nil? (ctk/get-swap-slot copied-blue1'))))))))) + ;; ==== Check + ;; copied-blue1 has not swap-id + (t/is (some? copied-blue1')) + (t/is (nil? (ctk/get-swap-slot copied-blue1'))))))))) (t/deftest test-remove-swap-slot-cut-paste-yellow-to-b1 (t/async - done - (let [;; ==== Setup - file (setup-file-blue1-in-yellow) - store (ths/setup-store file) - blue1 (cths/get-shape file :blue1) + done + (let [;; ==== Setup + file (setup-file-blue1-in-yellow) + store (ths/setup-store file) + blue1 (cths/get-shape file :blue1) - ;; ==== Action - page (cthf/current-page file) - yellow (cths/get-shape file :frame-yellow) - b1 (cths/get-shape file :frame-b1) - features #{"components/v2"} - version 46 + ;; ==== Action + page (cthf/current-page file) + yellow (cths/get-shape file :frame-yellow) + b1 (cths/get-shape file :frame-b1) + features #{"components/v2"} + version 46 - pdata (thp/simulate-copy-shape #{(:id yellow)} (:objects page) {(:id file) file} page file features version) + pdata (thp/simulate-copy-shape #{(:id yellow)} (:objects page) {(:id file) file} page file features version) - events - [(dws/select-shape (:id yellow)) - (dw/delete-selected) - (dws/select-shape (:id b1)) - (dw/paste-shapes pdata)]] + events + [(dws/select-shape (:id yellow)) + (dw/delete-selected) + (dws/select-shape (:id b1)) + (dw/paste-shapes pdata)]] - (ths/run-store - store done events - (fn [new-state] - (let [;; ==== Get - file' (ths/get-file-from-state new-state) - page' (cthf/current-page file') - b1' (cths/get-shape file' :frame-b1) - copied-yellow' (find-copied-shape yellow page' (:id b1')) - copied-blue1' (find-copied-shape blue1 page' (:id copied-yellow'))] + (ths/run-store + store done events + (fn [new-state] + (let [;; ==== Get + file' (ths/get-file-from-state new-state) + page' (cthf/current-page file') + b1' (cths/get-shape file' :frame-b1) + copied-yellow' (find-copied-shape yellow page' (:id b1')) + copied-blue1' (find-copied-shape blue1 page' (:id copied-yellow'))] - ;; ==== Check - ;; copied-blue1 has not swap-id - (t/is (some? copied-blue1')) - (t/is (nil? (ctk/get-swap-slot copied-blue1'))))))))) + ;; ==== Check + ;; copied-blue1 has not swap-id + (t/is (some? copied-blue1')) + (t/is (nil? (ctk/get-swap-slot copied-blue1'))))))))) (t/deftest test-remove-swap-slot-cut-paste-yellow-to-b2 (t/async - done - (let [;; ==== Setup - file (setup-file-blue1-in-yellow) - store (ths/setup-store file) - blue1 (cths/get-shape file :blue1) + done + (let [;; ==== Setup + file (setup-file-blue1-in-yellow) + store (ths/setup-store file) + blue1 (cths/get-shape file :blue1) - ;; ==== Action - page (cthf/current-page file) - yellow (cths/get-shape file :frame-yellow) - b2 (cths/get-shape file :frame-b2) - features #{"components/v2"} - version 46 + ;; ==== Action + page (cthf/current-page file) + yellow (cths/get-shape file :frame-yellow) + b2 (cths/get-shape file :frame-b2) + features #{"components/v2"} + version 46 - pdata (thp/simulate-copy-shape #{(:id yellow)} (:objects page) {(:id file) file} page file features version) + pdata (thp/simulate-copy-shape #{(:id yellow)} (:objects page) {(:id file) file} page file features version) - events - [(dws/select-shape (:id yellow)) - (dw/delete-selected) - (dws/select-shape (:id b2)) - (dw/paste-shapes pdata)]] + events + [(dws/select-shape (:id yellow)) + (dw/delete-selected) + (dws/select-shape (:id b2)) + (dw/paste-shapes pdata)]] - (ths/run-store - store done events - (fn [new-state] - (let [;; ==== Get - file' (ths/get-file-from-state new-state) - page' (cthf/current-page file') - b2' (cths/get-shape file' :frame-b2) - copied-yellow' (find-copied-shape yellow page' (:id b2')) - copied-blue1' (find-copied-shape blue1 page' (:id copied-yellow'))] + (ths/run-store + store done events + (fn [new-state] + (let [;; ==== Get + file' (ths/get-file-from-state new-state) + page' (cthf/current-page file') + b2' (cths/get-shape file' :frame-b2) + copied-yellow' (find-copied-shape yellow page' (:id b2')) + copied-blue1' (find-copied-shape blue1 page' (:id copied-yellow'))] - ;; ==== Check - ;; copied-blue1 has not swap-id - (t/is (some? copied-blue1')) - (t/is (nil? (ctk/get-swap-slot copied-blue1'))))))))) + ;; ==== Check + ;; copied-blue1 has not swap-id + (t/is (some? copied-blue1')) + (t/is (nil? (ctk/get-swap-slot copied-blue1'))))))))) (t/deftest test-keep-swap-slot-copy-paste-green-copy-to-root (t/async - done - (let [;; ==== Setup - file (setup-file) - store (ths/setup-store file) + done + (let [;; ==== Setup + file (setup-file) + store (ths/setup-store file) - ;; ==== Action - page (cthf/current-page file) - green (cths/get-shape file :green-copy) - features #{"components/v2"} - version 46 + ;; ==== Action + page (cthf/current-page file) + green (cths/get-shape file :green-copy) + features #{"components/v2"} + version 46 - pdata (thp/simulate-copy-shape #{(:id green)} (:objects page) {(:id file) file} page file features version) + pdata (thp/simulate-copy-shape #{(:id green)} (:objects page) {(:id file) file} page file features version) - events - [(dws/select-shape uuid/zero) - (dw/paste-shapes pdata)]] + events + [(dws/select-shape uuid/zero) + (dw/paste-shapes pdata)]] - (ths/run-store - store done events - (fn [new-state] - (let [;; ==== Get - file' (ths/get-file-from-state new-state) - page' (cthf/current-page file') - blue2' (cths/get-shape file' :blue-copy-in-green-copy) - copied-green' (find-copied-shape green page' uuid/zero) - copied-blue2' (find-copied-shape blue2' page' (:id copied-green'))] + (ths/run-store + store done events + (fn [new-state] + (let [;; ==== Get + file' (ths/get-file-from-state new-state) + page' (cthf/current-page file') + blue2' (cths/get-shape file' :blue-copy-in-green-copy) + copied-green' (find-copied-shape green page' uuid/zero) + copied-blue2' (find-copied-shape blue2' page' (:id copied-green'))] - ;; ==== Check + ;; ==== Check - ;; blue2 has swap-id - (t/is (some? (ctk/get-swap-slot blue2'))) + ;; blue2 has swap-id + (t/is (some? (ctk/get-swap-slot blue2'))) - ;; copied-blue2 also has swap-id - (t/is (some? copied-blue2')) - (t/is (some? (ctk/get-swap-slot copied-blue2'))))))))) + ;; copied-blue2 also has swap-id + (t/is (some? copied-blue2')) + (t/is (some? (ctk/get-swap-slot copied-blue2'))))))))) (t/deftest test-keep-swap-slot-copy-paste-green-copy-to-b1 (t/async - done - (let [;; ==== Setup - file (setup-file) - store (ths/setup-store file) + done + (let [;; ==== Setup + file (setup-file) + store (ths/setup-store file) - ;; ==== Action - page (cthf/current-page file) - b1 (cths/get-shape file :frame-b1) - green (cths/get-shape file :green-copy) - features #{"components/v2"} - version 46 + ;; ==== Action + page (cthf/current-page file) + b1 (cths/get-shape file :frame-b1) + green (cths/get-shape file :green-copy) + features #{"components/v2"} + version 46 - pdata (thp/simulate-copy-shape #{(:id green)} (:objects page) {(:id file) file} page file features version) + pdata (thp/simulate-copy-shape #{(:id green)} (:objects page) {(:id file) file} page file features version) - events - [(dws/select-shape (:id b1)) - (dw/paste-shapes pdata)]] + events + [(dws/select-shape (:id b1)) + (dw/paste-shapes pdata)]] - (ths/run-store - store done events - (fn [new-state] - (let [;; ==== Get - file' (ths/get-file-from-state new-state) - page' (cthf/current-page file') - b1' (cths/get-shape file' :frame-b1) - blue2' (cths/get-shape file' :blue-copy-in-green-copy) - copied-green' (find-copied-shape green page' (:id b1')) - copied-blue2' (find-copied-shape blue2' page' (:id copied-green'))] + (ths/run-store + store done events + (fn [new-state] + (let [;; ==== Get + file' (ths/get-file-from-state new-state) + page' (cthf/current-page file') + b1' (cths/get-shape file' :frame-b1) + blue2' (cths/get-shape file' :blue-copy-in-green-copy) + copied-green' (find-copied-shape green page' (:id b1')) + copied-blue2' (find-copied-shape blue2' page' (:id copied-green'))] - ;; ==== Check + ;; ==== Check - ;; blue1 has swap-id - (t/is (some? (ctk/get-swap-slot blue2'))) + ;; blue1 has swap-id + (t/is (some? (ctk/get-swap-slot blue2'))) - ;; copied-blue1 also has swap-id - (t/is (some? copied-blue2')) - (t/is (some? (ctk/get-swap-slot copied-blue2'))))))))) + ;; copied-blue1 also has swap-id + (t/is (some? copied-blue2')) + (t/is (some? (ctk/get-swap-slot copied-blue2'))))))))) (t/deftest test-keep-swap-slot-copy-paste-green-copy-to-b2 (t/async - done - (let [;; ==== Setup - file (setup-file) - store (ths/setup-store file) + done + (let [;; ==== Setup + file (setup-file) + store (ths/setup-store file) - ;; ==== Action - page (cthf/current-page file) - b2 (cths/get-shape file :frame-b2) - green (cths/get-shape file :green-copy) - features #{"components/v2"} - version 46 + ;; ==== Action + page (cthf/current-page file) + b2 (cths/get-shape file :frame-b2) + green (cths/get-shape file :green-copy) + features #{"components/v2"} + version 46 - pdata (thp/simulate-copy-shape #{(:id green)} (:objects page) {(:id file) file} page file features version) + pdata (thp/simulate-copy-shape #{(:id green)} (:objects page) {(:id file) file} page file features version) - events - [(dws/select-shape (:id b2)) - (dw/paste-shapes pdata)]] + events + [(dws/select-shape (:id b2)) + (dw/paste-shapes pdata)]] - (ths/run-store - store done events - (fn [new-state] - (let [;; ==== Get - file' (ths/get-file-from-state new-state) - page' (cthf/current-page file') - b2' (cths/get-shape file' :frame-b2) - blue2' (cths/get-shape file' :blue-copy-in-green-copy) - copied-green' (find-copied-shape green page' (:id b2')) - copied-blue2' (find-copied-shape blue2' page' (:id copied-green'))] + (ths/run-store + store done events + (fn [new-state] + (let [;; ==== Get + file' (ths/get-file-from-state new-state) + page' (cthf/current-page file') + b2' (cths/get-shape file' :frame-b2) + blue2' (cths/get-shape file' :blue-copy-in-green-copy) + copied-green' (find-copied-shape green page' (:id b2')) + copied-blue2' (find-copied-shape blue2' page' (:id copied-green'))] - ;; ==== Check + ;; ==== Check - ;; blue2 has swap-id - (t/is (some? (ctk/get-swap-slot blue2'))) + ;; blue2 has swap-id + (t/is (some? (ctk/get-swap-slot blue2'))) - ;; copied-blue1 also has swap-id - (t/is (some? copied-blue2')) - (t/is (some? (ctk/get-swap-slot copied-blue2'))))))))) + ;; copied-blue1 also has swap-id + (t/is (some? copied-blue2')) + (t/is (some? (ctk/get-swap-slot copied-blue2'))))))))) (t/deftest test-keep-swap-slot-cut-paste-green-copy-to-root (t/async - done - (let [;; ==== Setup - file (setup-file) - store (ths/setup-store file) + done + (let [;; ==== Setup + file (setup-file) + store (ths/setup-store file) - ;; ==== Action - page (cthf/current-page file) - green (cths/get-shape file :green-copy) - blue2 (cths/get-shape file :blue-copy-in-green-copy) - features #{"components/v2"} - version 46 + ;; ==== Action + page (cthf/current-page file) + green (cths/get-shape file :green-copy) + blue2 (cths/get-shape file :blue-copy-in-green-copy) + features #{"components/v2"} + version 46 - pdata (thp/simulate-copy-shape #{(:id green)} (:objects page) {(:id file) file} page file features version) + pdata (thp/simulate-copy-shape #{(:id green)} (:objects page) {(:id file) file} page file features version) - events - [(dws/select-shape (:id green)) - (dw/delete-selected) - (dws/select-shape uuid/zero) - (dw/paste-shapes pdata)]] + events + [(dws/select-shape (:id green)) + (dw/delete-selected) + (dws/select-shape uuid/zero) + (dw/paste-shapes pdata)]] - (ths/run-store - store done events - (fn [new-state] - (let [;; ==== Get - file' (ths/get-file-from-state new-state) - page' (cthf/current-page file') - copied-green' (find-copied-shape green page' uuid/zero) - copied-blue1' (find-copied-shape blue2 page' (:id copied-green'))] + (ths/run-store + store done events + (fn [new-state] + (let [;; ==== Get + file' (ths/get-file-from-state new-state) + page' (cthf/current-page file') + copied-green' (find-copied-shape green page' uuid/zero) + copied-blue1' (find-copied-shape blue2 page' (:id copied-green'))] - ;; ==== Check - ;; copied-blue1 has swap-id - (t/is (some? copied-blue1')) - (t/is (some? (ctk/get-swap-slot copied-blue1'))))))))) + ;; ==== Check + ;; copied-blue1 has swap-id + (t/is (some? copied-blue1')) + (t/is (some? (ctk/get-swap-slot copied-blue1'))))))))) (t/deftest test-keep-swap-slot-cut-paste-green-copy-to-b1 (t/async - done - (let [;; ==== Setup - file (setup-file) - store (ths/setup-store file) + done + (let [;; ==== Setup + file (setup-file) + store (ths/setup-store file) - ;; ==== Action - page (cthf/current-page file) - b1 (cths/get-shape file :frame-b1) - green (cths/get-shape file :green-copy) - blue2 (cths/get-shape file :blue-copy-in-green-copy) - features #{"components/v2"} - version 46 + ;; ==== Action + page (cthf/current-page file) + b1 (cths/get-shape file :frame-b1) + green (cths/get-shape file :green-copy) + blue2 (cths/get-shape file :blue-copy-in-green-copy) + features #{"components/v2"} + version 46 - pdata (thp/simulate-copy-shape #{(:id green)} (:objects page) {(:id file) file} page file features version) + pdata (thp/simulate-copy-shape #{(:id green)} (:objects page) {(:id file) file} page file features version) - events - [(dws/select-shape (:id green)) - (dw/delete-selected) - (dws/select-shape (:id b1)) - (dw/paste-shapes pdata)]] + events + [(dws/select-shape (:id green)) + (dw/delete-selected) + (dws/select-shape (:id b1)) + (dw/paste-shapes pdata)]] - (ths/run-store - store done events - (fn [new-state] - (let [;; ==== Get - file' (ths/get-file-from-state new-state) - page' (cthf/current-page file') - b1' (cths/get-shape file' :frame-b1) - copied-green' (find-copied-shape green page' (:id b1')) - copied-blue2' (find-copied-shape blue2 page' (:id copied-green'))] + (ths/run-store + store done events + (fn [new-state] + (let [;; ==== Get + file' (ths/get-file-from-state new-state) + page' (cthf/current-page file') + b1' (cths/get-shape file' :frame-b1) + copied-green' (find-copied-shape green page' (:id b1')) + copied-blue2' (find-copied-shape blue2 page' (:id copied-green'))] - ;; ==== Check - ;; copied-blue1 has swap-id - (t/is (some? copied-blue2')) - (t/is (some? (ctk/get-swap-slot copied-blue2'))))))))) + ;; ==== Check + ;; copied-blue1 has swap-id + (t/is (some? copied-blue2')) + (t/is (some? (ctk/get-swap-slot copied-blue2'))))))))) (t/deftest test-keep-swap-slot-cut-paste-green-copy-to-b2 (t/async - done - (let [;; ==== Setup - file (setup-file) - store (ths/setup-store file) + done + (let [;; ==== Setup + file (setup-file) + store (ths/setup-store file) - ;; ==== Action - page (cthf/current-page file) - b2 (cths/get-shape file :frame-b2) - blue2 (cths/get-shape file :blue-copy-in-green-copy) - green (cths/get-shape file :green-copy) - features #{"components/v2"} - version 46 + ;; ==== Action + page (cthf/current-page file) + b2 (cths/get-shape file :frame-b2) + blue2 (cths/get-shape file :blue-copy-in-green-copy) + green (cths/get-shape file :green-copy) + features #{"components/v2"} + version 46 - pdata (thp/simulate-copy-shape #{(:id green)} (:objects page) {(:id file) file} page file features version) + pdata (thp/simulate-copy-shape #{(:id green)} (:objects page) {(:id file) file} page file features version) - events - [(dws/select-shape (:id green)) - (dw/delete-selected) - (dws/select-shape (:id b2)) - (dw/paste-shapes pdata)]] + events + [(dws/select-shape (:id green)) + (dw/delete-selected) + (dws/select-shape (:id b2)) + (dw/paste-shapes pdata)]] - (ths/run-store - store done events - (fn [new-state] - (let [;; ==== Get - file' (ths/get-file-from-state new-state) - page' (cthf/current-page file') - b2' (cths/get-shape file' :frame-b2) - copied-green' (find-copied-shape green page' (:id b2')) - copied-blue2' (find-copied-shape blue2 page' (:id copied-green'))] + (ths/run-store + store done events + (fn [new-state] + (let [;; ==== Get + file' (ths/get-file-from-state new-state) + page' (cthf/current-page file') + b2' (cths/get-shape file' :frame-b2) + copied-green' (find-copied-shape green page' (:id b2')) + copied-blue2' (find-copied-shape blue2 page' (:id copied-green'))] - ;; ==== Check - ;; copied-blue1 has swap-id - (t/is (some? copied-blue2')) - (t/is (some? (ctk/get-swap-slot copied-blue2'))))))))) + ;; ==== Check + ;; copied-blue1 has swap-id + (t/is (some? copied-blue2')) + (t/is (some? (ctk/get-swap-slot copied-blue2'))))))))) (t/deftest test-remove-swap-slot-copy-paste-swapped-main (t/async - done - (let [;; ==== Setup - ;; {:frame-red} [:name frame-blue] # [Component :red] - ;; {:frame-blue} [:name frame-blue] #[Component :blue] - ;; {:frame-green} [:name frame-green] #[Component :green] - ;; :blue1 [:name frame-blue, :swap-slot-label :red-copy-green] @--> frame-blue + done + (let [;; ==== Setup + ;; {:frame-red} [:name frame-blue] # [Component :red] + ;; {:frame-blue} [:name frame-blue] #[Component :blue] + ;; {:frame-green} [:name frame-green] #[Component :green] + ;; :blue1 [:name frame-blue, :swap-slot-label :red-copy-green] @--> frame-blue - file (-> (cthf/sample-file :file1) - (ctho/add-frame :frame-red :name "frame-blue") - (cthc/make-component :red :frame-red) - (ctho/add-frame :frame-blue :name "frame-blue") - (cthc/make-component :blue :frame-blue) - (ctho/add-frame :frame-green :name "frame-green") - (cthc/make-component :green :frame-green) - (cthc/instantiate-component :red :red-copy-green :parent-label :frame-green) - (cthc/component-swap :red-copy-green :blue :blue1)) - store (ths/setup-store file) + file (-> (cthf/sample-file :file1) + (ctho/add-frame :frame-red :name "frame-blue") + (cthc/make-component :red :frame-red) + (ctho/add-frame :frame-blue :name "frame-blue") + (cthc/make-component :blue :frame-blue) + (ctho/add-frame :frame-green :name "frame-green") + (cthc/make-component :green :frame-green) + (cthc/instantiate-component :red :red-copy-green :parent-label :frame-green) + (cthc/component-swap :red-copy-green :blue :blue1)) + store (ths/setup-store file) - ;; ==== Action - page (cthf/current-page file) - green (cths/get-shape file :frame-green) - features #{"components/v2"} - version 47 + ;; ==== Action + page (cthf/current-page file) + green (cths/get-shape file :frame-green) + features #{"components/v2"} + version 47 - pdata (thp/simulate-copy-shape #{(:id green)} (:objects page) {(:id file) file} page file features version) + pdata (thp/simulate-copy-shape #{(:id green)} (:objects page) {(:id file) file} page file features version) - events - [(dws/select-shape uuid/zero) - (dw/paste-shapes pdata)]] + events + [(dws/select-shape uuid/zero) + (dw/paste-shapes pdata)]] - (ths/run-store - store done events - (fn [new-state] - (let [;; ==== Get - file' (ths/get-file-from-state new-state) - page' (cthf/current-page file') - green' (cths/get-shape file' :frame-green) - blue1' (cths/get-shape file' :blue1) - copied-green' (find-copied-shape green' page' uuid/zero) - copied-blue1' (find-copied-shape blue1' page' (:id copied-green'))] + (ths/run-store + store done events + (fn [new-state] + (let [;; ==== Get + file' (ths/get-file-from-state new-state) + page' (cthf/current-page file') + green' (cths/get-shape file' :frame-green) + blue1' (cths/get-shape file' :blue1) + copied-green' (find-copied-shape green' page' uuid/zero) + copied-blue1' (find-copied-shape blue1' page' (:id copied-green'))] - ;; ==== Check - ;; blue1 has swap-id - (t/is (some? (ctk/get-swap-slot blue1'))) + ;; ==== Check + ;; blue1 has swap-id + (t/is (some? (ctk/get-swap-slot blue1'))) - ;; copied-blue1 has not swap-id - (t/is (some? copied-blue1')) - (t/is (nil? (ctk/get-swap-slot copied-blue1'))))))))) + ;; copied-blue1 has not swap-id + (t/is (some? copied-blue1')) + (t/is (nil? (ctk/get-swap-slot copied-blue1'))))))))) + +(t/deftest test-set-swap-slot-copy-paste-two-levels-nested-head + ;; Test that when copy-pasting a sub-instance head that contains a deeper sub-instance + ;; head (with no swap-slot), the advance-shape step correctly sets a swap-slot on the + ;; inner head. Without the fix this causes a backend :referential-integrity / :missing-slot + ;; validation error. + (t/async + done + (let [;; ==== Setup + ;; {:main1-root} [:name Frame1] # [Component :comp1] + ;; :main1-child [:name Rect1] + ;; + ;; {:main2-root} [:name Frame2] # [Component :comp2] + ;; :nested-head1 [:name Frame1] @--> [Component :comp1] :main1-root + ;; [:name Rect1] ---> :main1-child + ;; + ;; {:main3-root} [:name Frame3] # [Component :comp3] + ;; :nested-head2 [:name Frame2] @--> [Component :comp2] :main2-root + ;; :nested-subhead2 [:name Frame1] @--> [Component :comp1] :main1-root + ;; [:name Rect1] ---> :main1-child + ;; + ;; :copy3-root [:name Frame3] #--> [Component :comp3] :main3-root + ;; [:name Frame2] @--> [Component :comp2] :nested-head2 (no swap-slot) + ;; [:name Frame1] @--> [Component :comp1] :nested-subhead2 (no swap-slot) + ;; [:name Rect1] ---> + file (-> (cthf/sample-file :file1) + (ctho/add-two-levels-nested-component-with-copy + :comp1 :main1-root :main1-child + :comp2 :main2-root :nested-head1 + :comp3 :main3-root :nested-head2 :nested-subhead2 + :copy3-root)) + store (ths/setup-store file) + + ;; ==== Action + page (cthf/current-page file) + copy3-root (cths/get-shape file :copy3-root) + ;; The copy of :nested-head2 inside copy3-root (sub-instance head of comp2, no swap-slot, + ;; nested 1 level deep — so level-delta=1 during copy-paste) + copy-head2-id (first (:shapes copy3-root)) + copy-head2 (get (:objects page) copy-head2-id) + features #{"components/v2"} + version 46 + + pdata (thp/simulate-copy-shape #{copy-head2-id} (:objects page) {(:id file) file} page file features version) + + events + [(dws/select-shape uuid/zero) + (dw/paste-shapes pdata)]] + + (ths/run-store + store done events + (fn [new-state] + (let [;; ==== Get + file' (ths/get-file-from-state new-state) + page' (cthf/current-page file') + ;; Find the pasted copy-head2 at the page root (parent = uuid/zero) + pasted-head2' (find-copied-shape copy-head2 page' uuid/zero) + ;; Its first child is the copy of :nested-subhead2 (sub-instance head of comp1) + pasted-subhead1-id' (some-> pasted-head2' :shapes first) + pasted-subhead1' (get (:objects page') pasted-subhead1-id')] + + ;; ==== Check + (t/is (some? pasted-head2')) + (t/is (some? pasted-subhead1')) + + ;; The inner sub-instance head must have a swap-slot set because advance-shape + ;; changed its shape-ref (level-delta=1). The swap-slot records the pre-advance + ;; shape-ref so the backend can validate referential integrity. + (t/is (some? (ctk/get-swap-slot pasted-subhead1')) + "Pasted inner sub-instance head must have a swap-slot after advance-shape")))))))