Merge remote-tracking branch 'origin/staging' into develop

This commit is contained in:
Alejandro Alonso 2026-10-28 11:50:56 +01:00
commit 4dc684dd9c
16 changed files with 457 additions and 62 deletions

View File

@ -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))))

View File

@ -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))))))

View File

@ -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]

View File

@ -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

View File

@ -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]

View File

@ -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 / "))))

View File

@ -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")))))

View File

@ -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=/&amp;editing=false&amp;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>

View File

@ -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})

View File

@ -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

View File

@ -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)]

View File

@ -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)))))))

View File

@ -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

View File

@ -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

View File

@ -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))))))))))

View File

@ -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