penpot/frontend/test/frontend_tests/data/workspace_libraries_test.cljs
alonso.torres 222bbe6b4e ✨ Test the file and container helpers and the library events
The component helpers of types/file and types/container and the
library events of the workspace had the lowest coverage left.
- common-tests.types.file-test: the export detach, the reuse of the
  library page, the original ref of a swap, deleted components and
  used-in?. Pinned: F52 (a library copy nested in a local component
  loses its ref in every copy), F56 (the original ref of a swap is nil
  when the mains are on different pages) and F57 (library color refs
  inside :fills survive the export detach).
- common-tests.types.container-test: the placement rules for moves
  and pastes (copies, mains, variant containers, deleted components,
  component loops) and small helper branches. A plain shape moved
  into its own child passes the rules (F58); relocate-shapes drops it
  first.
- frontend-tests.data.workspace-libraries-test: the events on several
  shapes at once, each one undo entry; update of a library main; a
  blank rename; placing with an id ref.
- workspace-library-updates-test: how a file learns that a library
  changed, and the sync dialog with its update and dismiss, with the
  RPC mocked.
- workspace-library-links-test: linking, unlinking and sharing.
2026-10-30 09:47:18 +01:00

221 lines
8.5 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.data.workspace-libraries-test
"The library events that act on several shapes or components at once, each
as one undo entry, and the branches of the single-shape events that the
composable cases do not reach: creating or placing a component with an
id ref, a blank rename and updating a main that lives in a linked
library."
(:require
[app.common.data :as d]
[app.common.geom.point :as gpt]
[app.common.test-helpers.components :as cthc]
[app.common.test-helpers.composable.core :as tm]
[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.common.types.component :as ctk]
[app.common.types.components-list :as ctkl]
[app.main.data.workspace.libraries :as dwl]
[app.main.data.workspace.selection :as dws]
[app.main.data.workspace.shapes :as dwsh]
[app.main.data.workspace.undo :as dwu]
[app.main.store :as st]
[cljs.test :as t :include-macros true]
[frontend-tests.composable-tests.interpreter :as ftm]))
(t/use-fixtures :each ftm/fixture)
(def ^:private main-color "#111111")
(def ^:private override "#222222")
(defn- two-components
"Components :c1 and :c2, two copies of :c1 (:copy-a, :copy-b) and two plain
rects."
[]
(-> (cthf/sample-file :file1)
(ctho/add-simple-component :c1 :main1 :main1-child
:child-params {:fills (cths/sample-fills-color :fill-color main-color)})
(ctho/add-simple-component :c2 :main2 :main2-child)
(cthc/instantiate-component :c1 :copy-a :children-labels [:copy-a-child])
(cthc/instantiate-component :c1 :copy-b :children-labels [:copy-b-child])
(ctho/add-rect :rect-a)
(ctho/add-rect :rect-b)))
(defn- install-file!
[file]
(ftm/install! (tm/make-situation file)))
(defn- shape
[label]
(cths/get-shape (ftm/current-file) label))
(defn- shape-by-id
[id]
(cths/get-shape-by-id (ftm/current-file) id))
(defn- data
[]
(:data (ftm/current-file)))
(defn- undo-entries
[]
(count (get-in @st/state [:workspace-undo :items])))
(defn- step
"Run `events` and wait until their work drains."
[& events]
(ftm/await-step (vec events)))
(defn- select
[& labels]
(dws/select-shapes (into (d/ordered-set) (map cthi/id) labels)))
(defn- linked?
[label]
(some? (:component-id (shape label))))
(defn- fill
[label]
(-> (shape label) :fills first :fill-color))
(defn- set-fill
[& labels]
(dwsh/update-shapes (into #{} (map cthi/id) labels)
#(assoc % :fills (cths/sample-fills-color :fill-color override))))
(defn- component-count
[]
(count (ctkl/components-seq (data))))
(t/deftest ^:async detach-components-is-one-undo-entry
(install-file! (two-components))
(let [before (undo-entries)]
(t/is (= :settled (await (step (dwl/detach-components [(cthi/id :copy-a) (cthi/id :copy-b)])))))
(t/is (not (linked? :copy-a)))
(t/is (not (linked? :copy-b)))
(t/is (= (inc before) (undo-entries)))
(await (step dwu/undo))
(t/is (linked? :copy-a))
(t/is (linked? :copy-b))))
(t/deftest ^:async detach-selected-components
(install-file! (two-components))
(t/testing "nothing happens when a selected shape is inside a copy"
(await (step (select :copy-a :copy-b-child) dwl/detach-selected-components))
(t/is (linked? :copy-a))
(t/is (some? (:shape-ref (shape :copy-b-child)))))
(t/testing "the selected copies are detached"
(await (step (select :copy-a :copy-b) dwl/detach-selected-components))
(t/is (not (linked? :copy-a)))
(t/is (not (linked? :copy-b)))))
(t/deftest ^:async reset-components-is-one-undo-entry
(install-file! (two-components))
(await (step (set-fill :copy-a-child :copy-b-child)))
(t/is (= override (fill :copy-a-child)))
(let [before (undo-entries)]
(await (step (dwl/reset-components [(cthi/id :copy-a) (cthi/id :copy-b)])))
(t/is (= main-color (fill :copy-a-child)))
(t/is (= main-color (fill :copy-b-child)))
(t/is (= (inc before) (undo-entries)))
(await (step dwu/undo))
(t/is (= override (fill :copy-a-child)))
(t/is (= override (fill :copy-b-child)))))
(defn- copies-of
[component-label]
(->> (vals (:objects (cthf/current-page (ftm/current-file))))
(filter #(and (ctk/instance-head? %)
(not (ctk/main-instance? %))
(= (cthi/id component-label) (:component-id %))))))
(t/deftest ^:async multi-swap-is-one-undo-entry
(install-file! (two-components))
(let [before (undo-entries)]
(await (step (dwl/component-multi-swap [(shape :copy-a) (shape :copy-b)]
(:id (ftm/current-file))
(cthi/id :c2))))
(t/is (= 0 (count (copies-of :c1))))
(t/is (= 2 (count (copies-of :c2))))
(t/is (= (inc before) (undo-entries)))
(await (step dwu/undo))
(t/is (= 2 (count (copies-of :c1))))
(t/is (= 0 (count (copies-of :c2))))))
(t/deftest ^:async add-multiple-components-is-one-undo-entry
(install-file! (two-components))
(let [before (undo-entries)]
(await (step (select :rect-a :rect-b) (dwl/add-multiple-components)))
(t/is (= 4 (component-count)))
(t/is (= (inc before) (undo-entries)))
(await (step dwu/undo))
(t/is (= 2 (component-count)))))
(t/deftest ^:async add-multiple-components-skips-copies
(install-file! (two-components))
(await (step (select :rect-a :copy-a-child) (dwl/add-multiple-components)))
(t/is (= 2 (component-count))))
(t/deftest ^:async add-component-sets-the-id-ref
(install-file! (two-components))
(let [id-ref (atom nil)]
(await (step (dwl/add-component id-ref [(cthi/id :rect-a)])))
(t/is (some? (ctkl/get-component (data) @id-ref)))))
(t/deftest ^:async duplicate-component-makes-a-new-main
(install-file! (two-components))
(await (step (dwl/duplicate-component (:id (ftm/current-file)) (cthi/id :c1))))
(let [new-component (->> (ctkl/components-seq (data))
(remove #(#{(cthi/id :c1) (cthi/id :c2)} (:id %)))
first)]
(t/is (= 3 (component-count)))
(t/is (some? (shape-by-id (:main-instance-id new-component))))))
(t/deftest ^:async restore-components-is-one-undo-entry
(install-file! (two-components))
(let [file-id (:id (ftm/current-file))]
(await (step (dwl/delete-component {:id (cthi/id :c1)})
(dwl/delete-component {:id (cthi/id :c2)})))
(t/is (= 0 (component-count)))
(let [before (undo-entries)]
(await (step (dwl/restore-components {(cthi/id :c1) file-id
(cthi/id :c2) file-id})))
(t/is (= 2 (component-count)))
(t/is (= (inc before) (undo-entries))))))
(t/deftest ^:async update-component-of-a-library-main
(let [library (-> (cthf/sample-file :library :is-shared true)
(ctho/add-simple-component :lib-c :lib-main :lib-main-child))
file (-> (cthf/sample-file :file1)
(cthc/instantiate-component :lib-c :copy
:library library
:children-labels [:copy-child]))]
(ftm/install! (-> (tm/make-situation file)
(tm/with-aux-files {(:id library) library})))
(await (step (set-fill :copy-child)))
(await (step (dwl/update-component (cthi/id :copy))))
(let [library' (get-in @st/state [:files (:id library)])]
(t/is (= override (-> (cths/get-shape library' :lib-main-child) :fills first :fill-color))))
(t/testing "a shape that is not a head is left as it is"
(await (step (dwl/update-component (cthi/id :copy-child))))
(t/is (= override (fill :copy-child))))))
(t/deftest ^:async rename-component-to-a-blank-name
(install-file! (two-components))
(let [before (:name (ctkl/get-component (data) (cthi/id :c1)))]
(await (step (dwl/rename-component (cthi/id :c1) " ")))
(t/is (= before (:name (ctkl/get-component (data) (cthi/id :c1)))))))
(t/deftest ^:async instantiate-component-sets-the-id-ref
(install-file! (two-components))
(let [id-ref (atom nil)]
(await (step (dwl/instantiate-component (:id (ftm/current-file)) (cthi/id :c2)
(gpt/point 0 0) {:id-ref id-ref :origin "plugin"})))
(t/is (= (cthi/id :c2) (:component-id (shape-by-id @id-ref))))))