mirror of
https://github.com/penpot/penpot.git
synced 2026-05-30 04:08:08 +00:00
🐛 Fix shadow token creation
This commit is contained in:
parent
3cecc29276
commit
5ffec3e5e9
@ -919,6 +919,7 @@ test.describe("Tokens - creation", () => {
|
||||
|
||||
test("User creates shadow token", async ({ page }) => {
|
||||
const emptyNameError = "Name should be at least 1 character";
|
||||
const emptyFieldError = "This field cannot be empty";
|
||||
|
||||
const { tokensUpdateCreateModal, tokenThemesSetsSidebar } =
|
||||
await setupEmptyTokensFileRender(page, {
|
||||
@ -997,6 +998,14 @@ test.describe("Tokens - creation", () => {
|
||||
await expect(emptyNameErrorNode).toBeVisible();
|
||||
await expect(submitButton).toBeDisabled();
|
||||
|
||||
// 5. Empty fill -> disabled + error message
|
||||
await offsetXField.fill("3"); // Fill should be touched in order to show the error message
|
||||
await offsetXField.fill("");
|
||||
const emptyFieldErrorNode =
|
||||
tokensUpdateCreateModal.getByText(emptyFieldError);
|
||||
|
||||
await expect(emptyFieldErrorNode).toBeVisible();
|
||||
|
||||
//
|
||||
// ------- SUCCESSFUL FIELDS -------
|
||||
//
|
||||
@ -1102,6 +1111,28 @@ test.describe("Tokens - creation", () => {
|
||||
await expect(
|
||||
tokensTabPanel.getByRole("button", { name: "my-token-2" }),
|
||||
).toBeEnabled();
|
||||
|
||||
//
|
||||
// ------- THIRD TOKEN WITH EMPTY BLUR AND SPREAD -------
|
||||
//
|
||||
await addTokenButton.click();
|
||||
|
||||
await nameField.fill("my-token-3");
|
||||
await colorField.fill("red");
|
||||
|
||||
// 1. Empty blur and spread
|
||||
|
||||
await blurField.fill("");
|
||||
|
||||
await spreadField.fill("");
|
||||
|
||||
await expect(submitButton).toBeEnabled();
|
||||
await submitButton.click();
|
||||
|
||||
await unfoldTokenType(tokensTabPanel, "shadow");
|
||||
await expect(
|
||||
tokensTabPanel.getByRole("button", { name: "my-token-3" }),
|
||||
).toBeEnabled();
|
||||
});
|
||||
|
||||
test("User cant submit empty typography token or reference", async ({
|
||||
@ -1691,13 +1722,21 @@ test.describe("Tokens - creation", () => {
|
||||
});
|
||||
});
|
||||
|
||||
test("User cannot create token with a conflicting name in other set", async ({ page }) => {
|
||||
const { tokensUpdateCreateModal, tokenThemesSetsSidebar, tokensSidebar, tokenContextMenuForToken } =
|
||||
await setupTokensFileRender(page);
|
||||
test("User cannot create token with a conflicting name in other set", async ({
|
||||
page,
|
||||
}) => {
|
||||
const {
|
||||
tokensUpdateCreateModal,
|
||||
tokenThemesSetsSidebar,
|
||||
tokensSidebar,
|
||||
tokenContextMenuForToken,
|
||||
} = await setupTokensFileRender(page);
|
||||
|
||||
await expect(tokensSidebar).toBeVisible();
|
||||
|
||||
await tokenThemesSetsSidebar.getByRole('button', { name: 'light', exact: true }).click();
|
||||
await tokenThemesSetsSidebar
|
||||
.getByRole("button", { name: "light", exact: true })
|
||||
.click();
|
||||
|
||||
const tokensTabPanel = page.getByRole("tabpanel", { name: "tokens" });
|
||||
await tokensTabPanel
|
||||
@ -1721,8 +1760,11 @@ test("User cannot create token with a conflicting name in other set", async ({ p
|
||||
await nameField.fill("accent.default");
|
||||
|
||||
// An error message should appear and submit button should be disabled
|
||||
await expect(tokensUpdateCreateModal.getByText('A token already exists at the path: accent.default'))
|
||||
.toBeVisible()
|
||||
await expect(
|
||||
tokensUpdateCreateModal.getByText(
|
||||
"A token already exists at the path: accent.default",
|
||||
),
|
||||
).toBeVisible();
|
||||
|
||||
await expect(submitButton).toBeDisabled();
|
||||
|
||||
@ -1730,8 +1772,11 @@ test("User cannot create token with a conflicting name in other set", async ({ p
|
||||
await nameField.fill("colors.red");
|
||||
|
||||
// An error message should appear and submit button should be disabled
|
||||
await expect(tokensUpdateCreateModal.getByText('A token already exists at the path: colors.red'))
|
||||
.toBeVisible()
|
||||
await expect(
|
||||
tokensUpdateCreateModal.getByText(
|
||||
"A token already exists at the path: colors.red",
|
||||
),
|
||||
).toBeVisible();
|
||||
|
||||
await expect(submitButton).toBeDisabled();
|
||||
|
||||
|
||||
@ -311,21 +311,39 @@
|
||||
(swap! form (fn [state]
|
||||
(-> state
|
||||
(assoc-in [:data :value value-subfield index field] (if trim? (str/trim value) value))
|
||||
(assoc-in [:touched :value value-subfield index field] true)
|
||||
(update :errors dissoc :value)
|
||||
(update :extra-errors dissoc :value)
|
||||
(update :errors clean-errors)
|
||||
(update :extra-errors clean-errors)))))))
|
||||
|
||||
|
||||
(mf/defc indexed-color-input*
|
||||
[{:keys [name tokens token index value-subfield] :rest props}]
|
||||
|
||||
(let [form (mf/use-ctx fc/context)
|
||||
input-name name
|
||||
token-name (get-in @form [:data :name] nil)
|
||||
error
|
||||
(get-in @form [:errors :value value-subfield index input-name])
|
||||
|
||||
touched?
|
||||
(get-in @form [:touched :value value-subfield index input-name])
|
||||
|
||||
value
|
||||
(get-in @form [:data :value value-subfield index input-name] "")
|
||||
|
||||
;; Resolution error for this specific field
|
||||
indexed-error
|
||||
(get-in @form [:errors :value value-subfield index input-name])
|
||||
|
||||
;; Empty-field error: scoped to this layer so each shadow layer is
|
||||
;; evaluated independently from the others.
|
||||
empty-error
|
||||
(when (str/blank? value)
|
||||
{:message (tr "errors.tokens.empty-field")})
|
||||
|
||||
error
|
||||
(when touched? (or indexed-error empty-error))
|
||||
|
||||
color-resolved
|
||||
(get-in @form [:data :value value-subfield index :color-result] "")
|
||||
|
||||
|
||||
@ -383,22 +383,40 @@
|
||||
(swap! form (fn [state]
|
||||
(-> state
|
||||
(assoc-in [:data :value value-subfield index field] (if trim? (str/trim value) value))
|
||||
(assoc-in [:touched :value value-subfield index field] true)
|
||||
(update :errors dissoc :value)
|
||||
(update :extra-errors dissoc :value)
|
||||
(update :errors clean-errors)
|
||||
(update :extra-errors clean-errors)))))))
|
||||
|
||||
(mf/defc input-indexed*
|
||||
[{:keys [name tokens token index value-subfield] :rest props}]
|
||||
[{:keys [name tokens token index value-subfield nillable] :rest props}]
|
||||
|
||||
(let [form (mf/use-ctx fc/context)
|
||||
input-name name
|
||||
token-name (get-in @form [:data :name] nil)
|
||||
nillable (d/nilv nillable false)
|
||||
|
||||
error
|
||||
(get-in @form [:errors :value value-subfield index input-name])
|
||||
touched?
|
||||
(get-in @form [:touched :value value-subfield index input-name])
|
||||
|
||||
value-from-form
|
||||
(get-in @form [:data :value value-subfield index input-name] "")
|
||||
|
||||
;; Resolution error for this specific field (e.g. missing reference)
|
||||
indexed-error
|
||||
(get-in @form [:errors :value value-subfield index input-name])
|
||||
|
||||
;; Empty-field error: derived purely from the local field value so that
|
||||
;; each shadow layer is evaluated independently.
|
||||
empty-error
|
||||
(when (and (not nillable)
|
||||
(str/blank? value-from-form))
|
||||
{:message (tr "errors.tokens.empty-field")})
|
||||
|
||||
error
|
||||
(when touched? (or indexed-error empty-error))
|
||||
|
||||
resolve-stream
|
||||
(mf/with-memo [token index input-name]
|
||||
(if-let [value (get-in token [:value value-subfield index input-name])]
|
||||
@ -428,7 +446,7 @@
|
||||
props
|
||||
(if error
|
||||
(mf/spread-props props {:hint-type "error"
|
||||
:hint-message (:message error)})
|
||||
:hint-message (or (:message error) (tr "errors.field-missing"))})
|
||||
props)
|
||||
|
||||
props
|
||||
|
||||
@ -57,10 +57,13 @@
|
||||
(update :token-value (fn [value]
|
||||
(->> (or value [])
|
||||
(mapv (fn [shadow]
|
||||
(d/update-when shadow :inset #(cond
|
||||
(boolean? %) %
|
||||
(= "true" %) true
|
||||
:else false)))))))
|
||||
(-> shadow
|
||||
(d/update-when :inset #(cond
|
||||
(boolean? %) %
|
||||
(= "true" %) true
|
||||
:else false))
|
||||
(update :blur #(if (str/blank? %) "0" %))
|
||||
(update :spread #(if (str/blank? %) "0" %))))))))
|
||||
(assoc :validators [check-empty-shadow-token
|
||||
check-shadow-token-self-reference]))]
|
||||
|
||||
@ -160,6 +163,7 @@
|
||||
{:aria-label (tr "workspace.tokens.shadow-blur")
|
||||
:placeholder (tr "workspace.tokens.shadow-blur")
|
||||
:name :blur
|
||||
:nillable true
|
||||
:slot-start (mf/html [:span {:class (stl/css :visible-label)}
|
||||
(str (tr "workspace.tokens.shadow-blur") ":")])
|
||||
:token blur-token
|
||||
@ -172,6 +176,7 @@
|
||||
{:aria-label (tr "workspace.tokens.shadow-spread")
|
||||
:placeholder (tr "workspace.tokens.shadow-spread")
|
||||
:name :spread
|
||||
:nillable true
|
||||
:slot-start (mf/html [:span {:class (stl/css :visible-label)}
|
||||
(str (tr "workspace.tokens.shadow-spread") ":")])
|
||||
:token spread-token
|
||||
@ -310,15 +315,14 @@
|
||||
ref-valid? (and reference (not (str/blank? reference)))
|
||||
|
||||
shadows (get value :shadow)
|
||||
;; To be a valid shadow it must contain one on each valid values
|
||||
;; To be a valid shadow it must contain one on each valid values.
|
||||
;; blur and spread are optional and default to "0" when blank.
|
||||
valid-composite-shadow?
|
||||
(and (seq shadows)
|
||||
(every?
|
||||
(fn [{:keys [offset-x offset-y blur spread color]}]
|
||||
(fn [{:keys [offset-x offset-y color]}]
|
||||
(and (not (str/blank? offset-x))
|
||||
(not (str/blank? offset-y))
|
||||
(not (str/blank? blur))
|
||||
(not (str/blank? spread))
|
||||
(not (str/blank? color))))
|
||||
shadows))]
|
||||
|
||||
@ -333,7 +337,11 @@
|
||||
|
||||
(vector? value)
|
||||
{:reference nil
|
||||
:shadow value}
|
||||
:shadow (mapv (fn [shadow]
|
||||
(-> shadow
|
||||
(update :blur #(if (str/blank? %) "0" %))
|
||||
(update :spread #(if (str/blank? %) "0" %))))
|
||||
value)}
|
||||
|
||||
:else
|
||||
{:reference nil
|
||||
|
||||
@ -8461,6 +8461,10 @@ msgstr "Edit %s token"
|
||||
msgid "errors.tokens.empty-input"
|
||||
msgstr "Token value cannot be empty"
|
||||
|
||||
#: src/app/main/data/workspace/tokens/errors.cljs
|
||||
msgid "errors.tokens.empty-field"
|
||||
msgstr "This field cannot be empty"
|
||||
|
||||
#: src/app/main/ui/workspace/tokens/management/forms/generic_form.cljs:241
|
||||
msgid "workspace.tokens.enter-token-name"
|
||||
msgstr "Enter %s token name"
|
||||
|
||||
@ -8221,6 +8221,10 @@ msgstr "Editar token de %s"
|
||||
msgid "errors.tokens.empty-input"
|
||||
msgstr "El valor del token no puede estar vacío"
|
||||
|
||||
#: src/app/main/data/workspace/tokens/errors.cljs
|
||||
msgid "errors.tokens.empty-field"
|
||||
msgstr "Este campo no puede estar vacío"
|
||||
|
||||
#: src/app/main/ui/workspace/tokens/management/forms/generic_form.cljs:241
|
||||
msgid "workspace.tokens.enter-token-name"
|
||||
msgstr "Introduce un nombre para el token %s"
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user