diff --git a/backend/src/app/rpc/commands/binfile.clj b/backend/src/app/rpc/commands/binfile.clj index 29d3fcdd52..593a06a028 100644 --- a/backend/src/app/rpc/commands/binfile.clj +++ b/backend/src/app/rpc/commands/binfile.clj @@ -126,6 +126,35 @@ result)) +(defn- prepare-import-event + "Build the audit event of an import. The RPC layer cannot emit it + because the import runs later, inside the SSE response, and the + event must carry the ids of the files it creates." + [params manifest] + (-> (audit/event-from-rpc-params params) + (assoc :type "command") + (assoc :name "import-binfile") + (assoc :props (-> params + (dissoc :file) + (assoc :generated-by (:generated-by manifest)) + (assoc :referer (:referer manifest)) + (audit/clean-props))) + (assoc ::webhooks/event? true))) + +(defn- import-binfile-and-audit + [cfg params event] + (try + (let [result (import-binfile cfg params) + file-ids (:file-ids result)] + (audit/submit cfg (update event :props assoc + :file-ids (vec file-ids) + :file-count (count file-ids) + :status "completed")) + result) + (catch Throwable cause + (audit/submit cfg (assoc-in event [:props :status] "failed")) + (throw cause)))) + (def ^:private schema:import-binfile [:and [:map {:title "import-binfile" :closed true} @@ -151,6 +180,7 @@ ["2.15" "Add upload-id param for chunked upload support"]] ::webhooks/event? true + ::audit/skip true ::sse/stream? true ::sm/params schema:import-binfile ::rpc/multipart-only-params [:file] @@ -168,10 +198,6 @@ :binfile-v1 1 :binfile-v3 3)) - params (-> params - (assoc :profile-id profile-id) - (assoc :version version)) - manifest (case (int version) 1 nil @@ -179,10 +205,10 @@ (throw (ex-info (str "Unsupported binfile version: " version) {:type :validation :code :unsupported-version - :version version})))] + :version version}))) - (with-meta - (sse/response (partial import-binfile cfg params)) - {::audit/props {:file nil - :generated-by (:generated-by manifest) - :referer (:referer manifest)}}))) + params (assoc params :version version) + event (prepare-import-event params manifest) + params (assoc params :profile-id profile-id)] + + (sse/response (partial import-binfile-and-audit cfg params event)))) diff --git a/backend/test/backend_tests/rpc_binfile_test.clj b/backend/test/backend_tests/rpc_binfile_test.clj index 0447fe135d..e684d5b7e0 100644 --- a/backend/test/backend_tests/rpc_binfile_test.clj +++ b/backend/test/backend_tests/rpc_binfile_test.clj @@ -6,6 +6,7 @@ (ns backend-tests.rpc-binfile-test (:require + [app.binfile.common :as bfc] [app.binfile.v3 :as bf.v3] [app.common.schema :as sm] [app.common.uuid :as uuid] @@ -13,9 +14,11 @@ [app.db :as db] [app.rpc :as-alias rpc] [app.rpc.commands.binfile :as binfile] + [app.storage.tmp :as tmp] [backend-tests.helpers :as th] [clojure.test :as t] [datoteka.fs :as fs] + [datoteka.io :as io] [mockery.core :refer [with-mocks]])) (t/use-fixtures :once th/state-init) @@ -155,3 +158,69 @@ (t/is (= file-id (get-in (first events) [:props :id]))) (t/is (= (:id project) (get-in (first events) [:props :project-id]))) (t/is (= (:id team) (get-in (first events) [:props :team-id])))))))) + +(defn- export-file + [profile] + (let [file (th/create-file* 1 {:profile-id (:id profile) + :project-id (:default-project-id profile) + :is-shared false}) + output (tmp/tempfile :suffix ".zip")] + (bf.v3/export-files! + (-> th/*system* + (assoc ::bfc/ids #{(:id file)}) + (assoc ::bfc/embed-assets false) + (assoc ::bfc/include-libraries false)) + (io/output-stream output)) + output)) + +(defn- import-file + [profile path] + (let [out (th/command! {::th/type :import-binfile + ::rpc/profile-id (:id profile) + :project-id (:default-project-id profile) + :name "imported" + :file {:filename "imported.penpot" + :size (fs/size path) + :path path + :mtype "application/zip"}})] + (t/is (nil? (:error out))) + (th/consume-sse (:result out)))) + +(defn- import-events + [mock] + (->> (:call-args-list @mock) + (map second) + (filter #(= "import-binfile" (:name %))))) + +(t/deftest import-binfile-audit-event-records-file-ids + (with-mocks [mock {:target 'app.loggers.audit/submit :return nil}] + (let [profile (th/create-profile* 1 {:is-active true}) + events (import-file profile (export-file profile)) + file-ids (-> events last second :file-ids) + [event :as audit-events] (import-events mock)] + + (t/is (= :end (-> events last first))) + (t/is (= 1 (count audit-events))) + (t/is (= 1 (count file-ids))) + (t/is (= (vec file-ids) (-> event :props :file-ids))) + (t/is (= 1 (-> event :props :file-count))) + (t/is (= "completed" (-> event :props :status))) + (t/is (= (:default-project-id profile) (-> event :props :project-id))) + (t/is (= "penpot" (-> event :props :referer))) + (t/is (= (:id profile) (:profile-id event))) + (t/is (= "command" (:type event))) + (t/is (not (contains? (:props event) :file)))))) + +(t/deftest import-binfile-audit-event-records-failure + (with-mocks [mock {:target 'app.loggers.audit/submit :return nil}] + (let [profile (th/create-profile* 1 {:is-active true}) + path (export-file profile) + events (with-redefs [bf.v3/import-files! + (fn [_] (throw (ex-info "boom" {:type :internal})))] + (import-file profile path)) + [event :as audit-events] (import-events mock)] + + (t/is (= :error (-> events last first))) + (t/is (= 1 (count audit-events))) + (t/is (= "failed" (-> event :props :status))) + (t/is (not (contains? (:props event) :file-ids)))))) diff --git a/common/src/app/common/path_names.cljc b/common/src/app/common/path_names.cljc index cc1526eae0..3f5fd28603 100644 --- a/common/src/app/common/path_names.cljc +++ b/common/src/app/common/path_names.cljc @@ -55,6 +55,17 @@ Some naming conventions: (->> (split-path path) (join-path))) +(defn normalize-path-name + "Trim and clean a path-style name. Returns nil when the name is not usable." + [name] + (let [name (some-> name str/trim) + clean-name (when (string? name) (clean-path name))] + (when (and (string? name) + (not (str/ends-with? name "/")) + (string? clean-name) + (not (str/blank? clean-name))) + clean-name))) + (defn merge-path-item "Put the item at the end of the path." [path name] diff --git a/common/src/app/common/types/component.cljc b/common/src/app/common/types/component.cljc index deb5379e72..c45b59ada0 100644 --- a/common/src/app/common/types/component.cljc +++ b/common/src/app/common/types/component.cljc @@ -251,6 +251,24 @@ [shape] (boolean (:is-variant-container shape))) +(defn is-variant-main? + "True when this shape is a variant component main and not the variant + container." + [shape] + (and (is-variant? shape) + (not (is-variant-container? shape)))) + +(defn variant-formula-rename? + "True when renaming this shape with `name` should update the variant + formula instead of the layer or component name. + + `name` must already be trimmed by the caller." + [shape name] + (and (is-variant-main? shape) + (some? (:variant-name shape)) + (string? name) + (not (str/blank? name)))) + (defn set-touched-group [touched group] (when group diff --git a/common/src/app/common/types/shape_tree.cljc b/common/src/app/common/types/shape_tree.cljc index 90109d5b46..0080aae00d 100644 --- a/common/src/app/common/types/shape_tree.cljc +++ b/common/src/app/common/types/shape_tree.cljc @@ -376,7 +376,7 @@ (defn update-object-indices [file page-id] - (update-in file [:pages-index page-id :objects] update-page-index)) + (d/update-in-when file [:pages-index page-id :objects] update-page-index)) (defn rotated-frame? [frame] diff --git a/common/test/common_tests/path_names_test.cljc b/common/test/common_tests/path_names_test.cljc index 9e0d3fb873..04cf0650b8 100644 --- a/common/test/common_tests/path_names_test.cljc +++ b/common/test/common_tests/path_names_test.cljc @@ -31,3 +31,12 @@ (t/is (= (first path) "group")) (t/is (= (second path) "subgroup")) (t/is (= (nth path 2) "name")))) + +(t/deftest normalize-path-name + (t/is (= "Button" (cpn/normalize-path-name " Button "))) + (t/is (= "Group / Button" (cpn/normalize-path-name "Group / Button"))) + (t/is (nil? (cpn/normalize-path-name nil))) + (t/is (nil? (cpn/normalize-path-name ""))) + (t/is (nil? (cpn/normalize-path-name " "))) + (t/is (nil? (cpn/normalize-path-name "Button/"))) + (t/is (nil? (cpn/normalize-path-name "Group / ")))) diff --git a/common/test/common_tests/types/components_test.cljc b/common/test/common_tests/types/components_test.cljc index 4d42df92f8..6098db1e80 100644 --- a/common/test/common_tests/types/components_test.cljc +++ b/common/test/common_tests/types/components_test.cljc @@ -397,3 +397,24 @@ (t/is (= (:id near-copy2-nested-head) (thi/id :nested2-head))) (t/is (= (:id near-copy2-nested4-head) (thi/id :nested4-head))) (t/is (= (:id near-copy2-nested4-child) (thi/id :nested4-child)))))) + +(t/deftest variant-main-and-formula-rename + (let [container {:id (thi/new-id! :container) + :is-variant-container true} + main {:id (thi/new-id! :main) + :variant-id (thi/id :container) + :variant-name "Value1"} + main-bare {:id (thi/new-id! :main-bare) + :variant-id (thi/id :container)} + plain {:id (thi/new-id! :plain)}] + (t/is (false? (ctk/is-variant-main? container))) + (t/is (true? (ctk/is-variant-main? main))) + (t/is (true? (ctk/is-variant-main? main-bare))) + (t/is (false? (ctk/is-variant-main? plain))) + + (t/is (true? (ctk/variant-formula-rename? main "Property 1=another 21"))) + (t/is (false? (ctk/variant-formula-rename? main ""))) + (t/is (false? (ctk/variant-formula-rename? main nil))) + (t/is (false? (ctk/variant-formula-rename? main-bare "Property 1=another 21"))) + (t/is (false? (ctk/variant-formula-rename? container "Property 1=another 21"))) + (t/is (false? (ctk/variant-formula-rename? plain "Button"))))) diff --git a/docs/user-guide/first-steps/migration-guide.njk b/docs/user-guide/first-steps/migration-guide.njk index cd8600ff68..23450d3f1a 100644 --- a/docs/user-guide/first-steps/migration-guide.njk +++ b/docs/user-guide/first-steps/migration-guide.njk @@ -9,8 +9,7 @@ desc: Move a design system from Figma to Penpot. Read a short summary of the ent

If you are moving a design system to Penpot, especially from Figma, start with the enterprise migration guide. It covers file and library migration, tokens, validation, dual-tool workflows, and how different roles can run a pilot.

-

Open the full guide (PDF)

-

The Enterprise Guide to Migrating Design Systems from Figma to Penpot

+

The Enterprise Guide to Migrating Design Systems from Figma to Penpot (PDF)

What the guide covers

diff --git a/frontend/src/app/main/data/changes.cljs b/frontend/src/app/main/data/changes.cljs index bd8afa6ff7..ee8eaa33f3 100644 --- a/frontend/src/app/main/data/changes.cljs +++ b/frontend/src/app/main/data/changes.cljs @@ -75,7 +75,9 @@ (not-empty))) (def ^:private xf:map-page-id - (map :page-id)) + ;; Component-only changes have no :page-id; keep drops those nils so + ;; update-object-indices does not create a nil key in :pages-index. + (keep :page-id)) (def ^:private wasm-structural-change-types #{:add-obj :mov-objects}) diff --git a/frontend/src/app/main/data/workspace.cljs b/frontend/src/app/main/data/workspace.cljs index c8e538560f..4838e89049 100644 --- a/frontend/src/app/main/data/workspace.cljs +++ b/frontend/src/app/main/data/workspace.cljs @@ -737,6 +737,37 @@ (update [_ state] (assoc-in state [:workspace-local :shape-for-rename] id)))) +(defn- rename-variant-main + "Apply a layers-panel formula to one variant main. + + A valid formula updates the property names and values. Any other + text keeps the current malformed path: values are cleared and the + text is stored as the variant error." + [file-id shape name] + (ptk/reify ::rename-variant-main + ptk/WatchEvent + (watch [_ state _] + (let [file-data (dsh/lookup-file-data state file-id) + component-id (:component-id shape) + component (ctkl/get-component file-data component-id) + variant-id (:variant-id shape) + variant-properties (:variant-properties component)] + (if (ctv/valid-properties-formula? name) + (rx/of (dwva/update-properties-names-and-values + component-id + variant-id + variant-properties + (ctv/properties-formula->map name)) + (dwva/remove-empty-properties variant-id) + (dwva/update-error component-id)) + (rx/of (dwva/update-properties-names-and-values + component-id + variant-id + variant-properties + {}) + (dwva/remove-empty-properties variant-id) + (dwva/update-error component-id name))))))) + (defn end-rename-shape "End the ongoing shape rename process" ([] (end-rename-shape nil nil)) @@ -751,14 +782,17 @@ (when-let [shape-id (d/nilv shape-id (dm/get-in state [:workspace-local :shape-for-rename]))] (let [shape (dsh/lookup-shape state shape-id) name (str/trim name) - clean-name (cpn/clean-path name) - valid? (and (not (str/ends-with? name "/")) - (string? clean-name) - (not (str/blank? clean-name))) + clean-name (cpn/normalize-path-name name) component-id (:component-id shape) - undo-id (js/Symbol)] + undo-id (js/Symbol)] - (when valid? + (cond + ;; Variant formula edits go through rename-shape-or-variant. + ;; A variant main must not fall through to a plain rename. + (ctc/is-variant-main? shape) + (rx/empty) + + (some? clean-name) (if (ctc/is-variant-container? shape) ;; Rename the full variant when it is a variant container (rx/of (dwva/rename-variant shape-id clean-name)) @@ -782,32 +816,12 @@ (watch [_ state _] (let [file-id (d/nilv file-id (:current-file-id state)) page-id (d/nilv page-id (:current-page-id state)) - - file-data (dsh/lookup-file-data state file-id) - shape - (-> (dsh/lookup-page-objects state file-id page-id) - (get id)) - - is-variant? (ctc/is-variant? shape) - variant-id (when is-variant? (:variant-id shape)) - variant-name (when is-variant? (:variant-name shape)) - component-id (:component-id shape) - component (ctkl/get-component file-data (:component-id shape)) - variant-properties (:variant-properties component)] - (cond - (and variant-name (ctv/valid-properties-formula? name)) - (rx/of (dwva/update-properties-names-and-values - component-id variant-id variant-properties (ctv/properties-formula->map name)) - (dwva/remove-empty-properties variant-id) - (dwva/update-error component-id)) - - variant-name - (rx/of (dwva/update-properties-names-and-values - component-id variant-id variant-properties {}) - (dwva/remove-empty-properties variant-id) - (dwva/update-error component-id name)) - - :else + shape (-> (dsh/lookup-page-objects state file-id page-id) + (get id)) + name (str/trim name)] + (if (ctc/variant-formula-rename? shape name) + ;; update-properties-names-and-values clears :shape-for-rename. + (rx/of (rename-variant-main file-id shape name)) (rx/of (end-rename-shape id name)))))))) ;; --- Update Selected Shapes attrs diff --git a/frontend/src/app/main/data/workspace/libraries.cljs b/frontend/src/app/main/data/workspace/libraries.cljs index 3044a05f6c..bc52e63736 100644 --- a/frontend/src/app/main/data/workspace/libraries.cljs +++ b/frontend/src/app/main/data/workspace/libraries.cljs @@ -498,15 +498,11 @@ (ptk/reify ::rename-component-and-main-instance ptk/WatchEvent (watch [_ state _] - (let [name (str/trim name) - clean-name (cpn/clean-path name) - valid? (and (not (str/ends-with? name "/")) - (string? clean-name) - (not (str/blank? clean-name))) + (let [clean-name (cpn/normalize-path-name name) data (dsh/lookup-file-data state) component (dm/get-in data [:components component-id])] - (when (and valid? component) + (when (and (some? clean-name) component) (let [shape-id (:main-instance-id component) page-id (:main-instance-page component)] diff --git a/frontend/src/app/main/ui/workspace/sidebar/layer_name.cljs b/frontend/src/app/main/ui/workspace/sidebar/layer_name.cljs index db497cf242..95afbd2922 100644 --- a/frontend/src/app/main/ui/workspace/sidebar/layer_name.cljs +++ b/frontend/src/app/main/ui/workspace/sidebar/layer_name.cljs @@ -93,7 +93,7 @@ name (str/trim (dom/get-value name-input))] (on-stop-edit) (reset! edition* false) - (st/emit! (dw/end-rename-shape shape-id name)) + (st/emit! (dw/rename-shape-or-variant shape-id name)) (when (fn? on-tab-press) (on-tab-press event))))))) diff --git a/frontend/src/app/main/ui/workspace/viewport/widgets.cljs b/frontend/src/app/main/ui/workspace/viewport/widgets.cljs index 357f1faf34..2c5374ced8 100644 --- a/frontend/src/app/main/ui/workspace/viewport/widgets.cljs +++ b/frontend/src/app/main/ui/workspace/viewport/widgets.cljs @@ -181,7 +181,7 @@ (let [name-input (mf/ref-val ref) name (str/trim (dom/get-value name-input))] (reset! edition* false) - (st/emit! (dw/end-rename-shape frame-id name)) + (st/emit! (dw/rename-shape-or-variant frame-id name)) (on-frame-leave frame-id)))) cancel-edit diff --git a/frontend/src/app/plugins/shape.cljs b/frontend/src/app/plugins/shape.cljs index 89cfdd6daf..9a86ebd0d3 100644 --- a/frontend/src/app/plugins/shape.cljs +++ b/frontend/src/app/plugins/shape.cljs @@ -70,8 +70,7 @@ [app.plugins.utils :as u] [app.util.http :as http] [app.util.object :as obj] - [beicon.v2.core :as rx] - [cuerdas.core :as str])) + [beicon.v2.core :as rx])) (declare shape-proxy) (declare shape-proxy?) @@ -385,11 +384,8 @@ :get #(-> % u/proxy->shape :name) :set (fn [self value] - (let [id (obj/get self "$id") - value (when (string? value) (-> value str/trim cpn/clean-path)) - valid? (and (some? value) - (not (str/ends-with? value "/")) - (not (str/blank? value)))] + (let [id (obj/get self "$id") + value (cpn/normalize-path-name value)] (cond (not (r/check-permission plugin-id "content:write")) (u/not-valid plugin-id :name "Plugin doesn't have 'content:write' permission") @@ -397,7 +393,7 @@ (not (u/page-active? page-id)) (u/not-valid plugin-id :name "Cannot modify a page that is not currently active") - (not valid?) + (nil? value) (u/not-valid plugin-id :name value) :else diff --git a/frontend/test/frontend_tests/data/workspace_variant_rename_test.cljs b/frontend/test/frontend_tests/data/workspace_variant_rename_test.cljs new file mode 100644 index 0000000000..5e177b1b21 --- /dev/null +++ b/frontend/test/frontend_tests/data/workspace_variant_rename_test.cljs @@ -0,0 +1,232 @@ +;; 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-variant-rename-test + (:require + [app.common.test-helpers.components :as cthc] + [app.common.test-helpers.files :as cthf] + [app.common.test-helpers.ids-map :as cthi] + [app.common.test-helpers.shapes :as cths] + [app.common.test-helpers.variants :as thv] + [app.common.types.file :as ctf] + [app.common.types.pages-list :as ctpl] + [app.main.data.workspace :as dw] + [cljs.test :as t :include-macros true] + [frontend-tests.helpers.state :as ths])) + +(t/use-fixtures :each + {:before cthi/reset-idmap!}) + +(defn- variant-file + [] + (-> (cthf/sample-file :file1) + (thv/add-variant :variant :comp1 :root1 :comp2 :root2 + :variant1-params {:name "Button"} + :variant2-params {:name "Button"}) + (cths/update-shape :variant :name "Button"))) + +(defn- without-variant-name + [file label] + (let [page-id (cthf/current-page-id file) + shape-id (cthi/id label)] + (ctf/update-file-data + file + (fn [data] + (ctpl/update-page data + page-id + #(update-in % [:objects shape-id] dissoc :variant-name)))))) + +(defn- prop-value + [component] + (-> component :variant-properties first :value)) + +(defn- run + "Run workspace events, then validate the file before assertions. + + Pass `:validate? false` only when the fixture is intentionally incomplete + (e.g. a variant main without `:variant-name`)." + [file events done assert-fn & {:keys [validate?] :or {validate? true}}] + (ths/run-store + (ths/setup-store file) + done + events + (fn [state] + (when validate? + (cthf/validate-file! (ths/get-file-from-state state))) + (assert-fn state)))) + +(t/deftest end-rename-shape-does-not-apply-variant-formula + (t/async + done + (let [file (variant-file) + component (cthc/get-component file :comp1) + original-name (:name component) + original-path (:path component) + root2-id (cthi/id :root2)] + (run file + [(dw/start-rename-shape (cthi/id :root1)) + (dw/end-rename-shape (cthi/id :root1) "Property 1=another 21") + (dw/start-rename-shape root2-id)] + done + (fn [state] + (let [file (ths/get-file-from-state state) + shape (cths/get-shape file :root1) + component (cthc/get-component file :comp1)] + ;; Formula updates belong to rename-shape-or-variant. + (t/is (= "Value1" (prop-value component))) + (t/is (= "Value1" (:variant-name shape))) + (t/is (= "Button" (:name shape))) + (t/is (= "Button" (:name (cths/get-shape file :variant)))) + (t/is (= original-name (:name component))) + (t/is (= original-path (:path component))) + (t/is (= "Value2" (prop-value (cthc/get-component file :comp2)))) + (t/is (= root2-id (:shape-for-rename (:workspace-local state)))))))))) + +(t/deftest rename-shape-or-variant-updates-variant-value-and-keeps-layer-name + (t/async + done + (let [file (variant-file) + component (cthc/get-component file :comp1) + original-name (:name component) + original-path (:path component)] + (run file + [(dw/start-rename-shape (cthi/id :root1)) + (dw/rename-shape-or-variant (cthi/id :root1) "Property 1=another 21")] + done + (fn [state] + (let [file (ths/get-file-from-state state) + shape (cths/get-shape file :root1) + component (cthc/get-component file :comp1)] + (t/is (= "another 21" (prop-value component))) + (t/is (= "another 21" (:variant-name shape))) + (t/is (= "Button" (:name shape))) + (t/is (= original-name (:name component))) + (t/is (= original-path (:path component))) + (t/is (nil? (:shape-for-rename (:workspace-local state)))))))))) + +(t/deftest rename-shape-or-variant-clears-shape-for-rename-on-malformed-formula + (t/async + done + (run (variant-file) + [(dw/start-rename-shape (cthi/id :root1)) + (dw/rename-shape-or-variant (cthi/id :root1) "not-a-formula")] + done + (fn [state] + (let [file (ths/get-file-from-state state) + shape (cths/get-shape file :root1)] + (t/is (= "Button" (:name shape))) + (t/is (= "not-a-formula" (:variant-error shape))) + (t/is (= "" (prop-value (cthc/get-component file :comp1)))) + (t/is (nil? (:shape-for-rename (:workspace-local state))))))))) + +(t/deftest rename-shape-or-variant-blank-name-does-not-clear-variant-values + (t/async + done + (run (variant-file) + [(dw/start-rename-shape (cthi/id :root1)) + (dw/rename-shape-or-variant (cthi/id :root1) "")] + done + (fn [state] + (let [file (ths/get-file-from-state state) + shape (cths/get-shape file :root1)] + (t/is (= "Button" (:name shape))) + (t/is (= "Value1" (:variant-name shape))) + (t/is (= "Value1" (prop-value (cthc/get-component file :comp1)))) + (t/is (nil? (:variant-error shape))) + (t/is (nil? (:shape-for-rename (:workspace-local state))))))))) + +(t/deftest end-rename-shape-nil-leaves-variant-main-unchanged + (t/async + done + (run (variant-file) + [(dw/start-rename-shape (cthi/id :root1)) + (dw/end-rename-shape (cthi/id :root1) nil)] + done + (fn [state] + (let [file (ths/get-file-from-state state) + shape (cths/get-shape file :root1)] + (t/is (= "Button" (:name shape))) + (t/is (= "Value1" (:variant-name shape))) + (t/is (= "Value1" (prop-value (cthc/get-component file :comp1)))) + (t/is (nil? (:shape-for-rename (:workspace-local state))))))))) + +(t/deftest end-rename-shape-ignores-variant-main-without-variant-name + (t/async + done + (let [file (-> (variant-file) + (without-variant-name :root1)) + component (cthc/get-component file :comp1) + original-name (:name component) + original-path (:path component) + root2-id (cthi/id :root2)] + (run file + [(dw/start-rename-shape (cthi/id :root1)) + (dw/end-rename-shape (cthi/id :root1) "Property 1=another 21") + (dw/start-rename-shape root2-id)] + done + (fn [state] + (let [file (ths/get-file-from-state state) + shape (cths/get-shape file :root1) + component (cthc/get-component file :comp1)] + (t/is (= "Button" (:name shape))) + (t/is (nil? (:variant-name shape))) + (t/is (= "Value1" (prop-value component))) + (t/is (= original-name (:name component))) + (t/is (= original-path (:path component))) + (t/is (= root2-id (:shape-for-rename (:workspace-local state)))))) + ;; Fixture starts without :variant-name, so the file is incomplete. + :validate? false)))) + +(t/deftest end-rename-shape-keeps-formula-text-off-the-layer-name + (t/async + done + (run (variant-file) + [(dw/start-rename-shape (cthi/id :root1)) + (dw/end-rename-shape (cthi/id :root1) "not-a-formula")] + done + (fn [state] + (let [file (ths/get-file-from-state state) + shape (cths/get-shape file :root1)] + (t/is (= "Button" (:name shape))) + (t/is (= "Value1" (:variant-name shape))) + (t/is (= "Value1" (prop-value (cthc/get-component file :comp1)))) + (t/is (nil? (:variant-error shape))) + (t/is (nil? (:shape-for-rename (:workspace-local state))))))))) + +(t/deftest end-rename-shape-renames-variant-container + (t/async + done + (run (variant-file) + [(dw/end-rename-shape (cthi/id :variant) "Renamed set")] + done + (fn [state] + (let [file (ths/get-file-from-state state)] + (t/is (= "Renamed set" (:name (cths/get-shape file :variant)))) + (t/is (= "Renamed set" (:name (cths/get-shape file :root1)))) + (t/is (= "Renamed set" (:name (cths/get-shape file :root2)))) + (t/is (= "Renamed set" (:name (cthc/get-component file :comp1)))) + (t/is (= "Renamed set" (:name (cthc/get-component file :comp2))))))))) + +(t/deftest end-rename-shape-renames-plain-shape-and-component-main + (t/async + done + (let [file (-> (cthf/sample-file :file1) + (cths/add-sample-shape :frame :type :frame :name "Frame") + (cthc/make-component :comp :frame) + (cths/add-sample-shape :rect :name "Rect")) + rect-id (cthi/id :rect)] + (run file + [(dw/start-rename-shape (cthi/id :frame)) + (dw/end-rename-shape (cthi/id :frame) "Renamed") + (dw/start-rename-shape rect-id) + (dw/end-rename-shape rect-id "Box")] + done + (fn [state] + (let [file (ths/get-file-from-state state)] + (t/is (= "Renamed" (:name (cths/get-shape file :frame)))) + (t/is (= "Renamed" (:name (cthc/get-component file :comp)))) + (t/is (= "Box" (:name (cths/get-shape file :rect)))) + (t/is (nil? (:shape-for-rename (:workspace-local state)))))))))) diff --git a/frontend/test/frontend_tests/runner.cljs b/frontend/test/frontend_tests/runner.cljs index 343c2e2cc2..e23f915520 100644 --- a/frontend/test/frontend_tests/runner.cljs +++ b/frontend/test/frontend_tests/runner.cljs @@ -39,6 +39,7 @@ [frontend-tests.data.workspace-shortcuts-test] [frontend-tests.data.workspace-texts-test] [frontend-tests.data.workspace-thumbnails-test] + [frontend-tests.data.workspace-variant-rename-test] [frontend-tests.data.workspace-versions-test] [frontend-tests.debug-logging-test] [frontend-tests.errors-governor-test] @@ -206,6 +207,7 @@ 'frontend-tests.data.workspace-shortcuts-test 'frontend-tests.data.workspace-texts-test 'frontend-tests.data.workspace-thumbnails-test + 'frontend-tests.data.workspace-variant-rename-test 'frontend-tests.data.workspace-versions-test 'frontend-tests.debug-logging-test 'frontend-tests.errors-governor-test