mirror of
https://github.com/penpot/penpot.git
synced 2026-10-28 21:40:34 +00:00
Merge remote-tracking branch 'origin/staging' into develop
This commit is contained in:
commit
4dc684dd9c
@ -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))))
|
||||
|
||||
@ -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))))))
|
||||
|
||||
@ -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]
|
||||
|
||||
@ -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
|
||||
|
||||
@ -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]
|
||||
|
||||
@ -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 / "))))
|
||||
|
||||
@ -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")))))
|
||||
|
||||
@ -9,8 +9,7 @@ desc: Move a design system from Figma to Penpot. Read a short summary of the ent
|
||||
<p class="main-paragraph">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.</p>
|
||||
|
||||
<div class="advice">
|
||||
<p><strong>Open the full guide (PDF)</strong></p>
|
||||
<p><a href="https://nextcloud.kaleidos.net/index.php/s/mKordyz62QF3PQ4?dir=/&editing=false&openfile=true" target="_blank" rel="noopener"><strong>The Enterprise Guide to Migrating Design Systems from Figma to Penpot</strong></a></p>
|
||||
<p><a href="https://nextcloud.kaleidos.net/index.php/s/5zwmQRwDdpFoAqM" target="_blank" rel="noopener"><strong>The Enterprise Guide to Migrating Design Systems from Figma to Penpot (PDF)</strong></a></p>
|
||||
</div>
|
||||
|
||||
<h2 id="what-the-guide-covers">What the guide covers</h2>
|
||||
|
||||
@ -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})
|
||||
|
||||
@ -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
|
||||
|
||||
@ -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)]
|
||||
|
||||
|
||||
@ -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)))))))
|
||||
|
||||
|
||||
@ -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
|
||||
|
||||
@ -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
|
||||
|
||||
@ -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))))))))))
|
||||
@ -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
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user