🐛 Fix errors when token name conflicts with group name

This commit is contained in:
Andrés Moya 2026-05-19 17:09:34 +02:00 committed by Elena Torró
parent 8dbbd49c0e
commit 429103d076
8 changed files with 814 additions and 18 deletions

View File

@ -0,0 +1,439 @@
{
"~:features": {
"~#set": [
"fdata/path-data",
"plugins/runtime",
"design-tokens/v1",
"variants/v1",
"layout/grid",
"styles/v2",
"fdata/objects-map",
"tokens/numeric-input",
"render-wasm/v1",
"components/v2",
"fdata/shape-data-type"
]
},
"~:team-id": "~uc6b102e2-5aaa-809c-8007-dcd1eab2135d",
"~:permissions": {
"~:type": "~:membership",
"~:is-owner": true,
"~:is-admin": true,
"~:can-edit": true,
"~:can-read": true,
"~:is-logged": true
},
"~:has-media-trimmed": false,
"~:comment-thread-seqn": 0,
"~:name": "New File 11",
"~:revn": 3,
"~:modified-at": "~m1779204621124",
"~:vern": 0,
"~:id": "~u9fb430ed-e1e9-81bc-8008-0b7ae978d9c4",
"~:is-shared": false,
"~:migrations": {
"~#ordered-set": [
"legacy-2",
"legacy-3",
"legacy-5",
"legacy-6",
"legacy-7",
"legacy-8",
"legacy-9",
"legacy-10",
"legacy-11",
"legacy-12",
"legacy-13",
"legacy-14",
"legacy-16",
"legacy-17",
"legacy-18",
"legacy-19",
"legacy-25",
"legacy-26",
"legacy-27",
"legacy-28",
"legacy-29",
"legacy-31",
"legacy-32",
"legacy-33",
"legacy-34",
"legacy-36",
"legacy-37",
"legacy-38",
"legacy-39",
"legacy-40",
"legacy-41",
"legacy-42",
"legacy-43",
"legacy-44",
"legacy-45",
"legacy-46",
"legacy-47",
"legacy-48",
"legacy-49",
"legacy-50",
"legacy-51",
"legacy-52",
"legacy-53",
"legacy-54",
"legacy-55",
"legacy-56",
"legacy-57",
"legacy-59",
"legacy-62",
"legacy-65",
"legacy-66",
"legacy-67",
"0001-remove-tokens-from-groups",
"0002-normalize-bool-content-v2",
"0002-clean-shape-interactions",
"0003-fix-root-shape",
"0003-convert-path-content-v2",
"0005-deprecate-image-type",
"0006-fix-old-texts-fills",
"0008-fix-library-colors-v4",
"0009-clean-library-colors",
"0009-add-partial-text-touched-flags",
"0010-fix-swap-slots-pointing-non-existent-shapes",
"0011-fix-invalid-text-touched-flags",
"0012-fix-position-data",
"0013-fix-component-path",
"0013-clear-invalid-strokes-and-fills",
"0014-fix-tokens-lib-duplicate-ids",
"0014-clear-components-nil-objects",
"0015-fix-text-attrs-blank-strings",
"0015-clean-shadow-color",
"0016-copy-fills-from-position-data-to-text-node",
"0017-fix-layout-flex-dir",
"0018-remove-unneeded-objects-from-components",
"0019-fix-missing-swap-slots",
"0020-sync-component-id-with-near-main"
]
},
"~:version": 67,
"~:project-id": "~u4cdd76d8-0e6d-8168-8008-0118118e1a1a",
"~:created-at": "~m1779204571619",
"~:backend": "legacy-db",
"~:data": {
"~:pages": [
"~u9fb430ed-e1e9-81bc-8008-0b7ae978d9c5"
],
"~:pages-index": {
"~u9fb430ed-e1e9-81bc-8008-0b7ae978d9c5": {
"~:objects": {
"~#penpot/objects-map/v2": {
"~u00000000-0000-0000-0000-000000000000": "[\"~#shape\",[\"^ \",\"~:y\",0,\"~:hide-fill-on-export\",false,\"~:transform\",[\"~#matrix\",[\"^ \",\"~:a\",1.0,\"~:b\",0.0,\"~:c\",0.0,\"~:d\",1.0,\"~:e\",0.0,\"~:f\",0.0]],\"~:rotation\",0,\"~:name\",\"Root Frame\",\"~:width\",0.01,\"~:type\",\"~:frame\",\"~:points\",[[\"~#point\",[\"^ \",\"~:x\",0.0,\"~:y\",0.0]],[\"^:\",[\"^ \",\"~:x\",0.01,\"~:y\",0.0]],[\"^:\",[\"^ \",\"~:x\",0.01,\"~:y\",0.01]],[\"^:\",[\"^ \",\"~:x\",0.0,\"~:y\",0.01]]],\"~:r2\",0,\"~:proportion-lock\",false,\"~:transform-inverse\",[\"^3\",[\"^ \",\"~:a\",1.0,\"~:b\",0.0,\"~:c\",0.0,\"~:d\",1.0,\"~:e\",0.0,\"~:f\",0.0]],\"~:r3\",0,\"~:r1\",0,\"~:id\",\"~u00000000-0000-0000-0000-000000000000\",\"~:parent-id\",\"~u00000000-0000-0000-0000-000000000000\",\"~:frame-id\",\"~u00000000-0000-0000-0000-000000000000\",\"~:strokes\",[],\"~:x\",0,\"~:proportion\",1.0,\"~:r4\",0,\"~:selrect\",[\"~#rect\",[\"^ \",\"~:x\",0,\"~:y\",0,\"^6\",0.01,\"~:height\",0.01,\"~:x1\",0,\"~:y1\",0,\"~:x2\",0.01,\"~:y2\",0.01]],\"~:fills\",[[\"^ \",\"~:fill-color\",\"#FFFFFF\",\"~:fill-opacity\",1]],\"~:flip-x\",null,\"^H\",0.01,\"~:flip-y\",null,\"~:shapes\",[]]]"
}
},
"~:id": "~u9fb430ed-e1e9-81bc-8008-0b7ae978d9c5",
"~:name": "Page 1"
}
},
"~:id": "~u9fb430ed-e1e9-81bc-8008-0b7ae978d9c4",
"~:options": {
"~:components-v2": true,
"~:base-font-size": "16px"
},
"~:tokens-lib": {
"~#penpot/tokens-lib": {
"~:sets": {
"~#ordered-map": [
[
"S-Global",
{
"~#penpot/token-set": {
"~:id": "~u1dc5cafc-6d57-808d-8008-0b7af8180cbd",
"~:name": "Global",
"~:description": "",
"~:modified-at": "~m1779204621126",
"~:tokens": {
"~#ordered-map": [
[
"str1",
{
"~#penpot/token": {
"~:id": "~u1dc5cafc-6d57-808d-8008-0b7af8180cad",
"~:name": "str1",
"~:type": "~:stroke-width",
"~:value": "1",
"~:description": "",
"~:modified-at": "~m1779204586592"
}
}
],
[
"typ1",
{
"~#penpot/token": {
"~:id": "~u1dc5cafc-6d57-808d-8008-0b7af8180cae",
"~:name": "typ1",
"~:type": "~:typography",
"~:value": {
"~:font-family": [
"ABeeZee"
],
"~:font-size": "{fsiz1}",
"~:font-weight": "{wei1}",
"~:letter-spacing": "{lspa1}",
"~:line-height": "1",
"~:text-case": "{cas1}",
"~:text-decoration": "{dec1}"
},
"~:description": "",
"~:modified-at": "~m1779204586592"
}
}
],
[
"siz1",
{
"~#penpot/token": {
"~:id": "~u1dc5cafc-6d57-808d-8008-0b7af8180caf",
"~:name": "siz1",
"~:type": "~:sizing",
"~:value": "1",
"~:description": "",
"~:modified-at": "~m1779204586592"
}
}
],
[
"num1",
{
"~#penpot/token": {
"~:id": "~u1dc5cafc-6d57-808d-8008-0b7af8180cb0",
"~:name": "num1",
"~:type": "~:number",
"~:value": "1",
"~:description": "",
"~:modified-at": "~m1779204586592"
}
}
],
[
"lspa1",
{
"~#penpot/token": {
"~:id": "~u1dc5cafc-6d57-808d-8008-0b7af8180cb1",
"~:name": "lspa1",
"~:type": "~:letter-spacing",
"~:value": "1",
"~:description": "",
"~:modified-at": "~m1779204586592"
}
}
],
[
"opa1",
{
"~#penpot/token": {
"~:id": "~u1dc5cafc-6d57-808d-8008-0b7af8180cb2",
"~:name": "opa1",
"~:type": "~:opacity",
"~:value": "1",
"~:description": "",
"~:modified-at": "~m1779204586592"
}
}
],
[
"dec1",
{
"~#penpot/token": {
"~:id": "~u1dc5cafc-6d57-808d-8008-0b7af8180cb3",
"~:name": "dec1",
"~:type": "~:text-decoration",
"~:value": "none",
"~:description": "",
"~:modified-at": "~m1779204586592"
}
}
],
[
"fsiz1",
{
"~#penpot/token": {
"~:id": "~u1dc5cafc-6d57-808d-8008-0b7af8180cb4",
"~:name": "fsiz1",
"~:type": "~:font-size",
"~:value": "1",
"~:description": "",
"~:modified-at": "~m1779204586592"
}
}
],
[
"sha1",
{
"~#penpot/token": {
"~:id": "~u1dc5cafc-6d57-808d-8008-0b7af8180cb5",
"~:name": "sha1",
"~:type": "~:shadow",
"~:value": [
{
"~:offset-x": "4",
"~:offset-y": "4",
"~:blur": "4",
"~:spread": "0",
"~:color": "grey",
"~:inset": false
}
],
"~:description": "",
"~:modified-at": "~m1779204586592"
}
}
],
[
"rad1",
{
"~#penpot/token": {
"~:id": "~u1dc5cafc-6d57-808d-8008-0b7af8180cb6",
"~:name": "rad1",
"~:type": "~:border-radius",
"~:value": "1",
"~:description": "",
"~:modified-at": "~m1779204586592"
}
}
],
[
"cas1",
{
"~#penpot/token": {
"~:id": "~u1dc5cafc-6d57-808d-8008-0b7af8180cb7",
"~:name": "cas1",
"~:type": "~:text-case",
"~:value": "none",
"~:description": "",
"~:modified-at": "~m1779204586592"
}
}
],
[
"spa1",
{
"~#penpot/token": {
"~:id": "~u1dc5cafc-6d57-808d-8008-0b7af8180cb8",
"~:name": "spa1",
"~:type": "~:spacing",
"~:value": "1",
"~:description": "",
"~:modified-at": "~m1779204586592"
}
}
],
[
"rot1",
{
"~#penpot/token": {
"~:id": "~u1dc5cafc-6d57-808d-8008-0b7af8180cb9",
"~:name": "rot1",
"~:type": "~:rotation",
"~:value": "1",
"~:description": "",
"~:modified-at": "~m1779204586592"
}
}
],
[
"wei1",
{
"~#penpot/token": {
"~:id": "~u1dc5cafc-6d57-808d-8008-0b7af8180cba",
"~:name": "wei1",
"~:type": "~:font-weight",
"~:value": "regular",
"~:description": "",
"~:modified-at": "~m1779204586592"
}
}
],
[
"col1",
{
"~#penpot/token": {
"~:id": "~u1dc5cafc-6d57-808d-8008-0b7af8180cbb",
"~:name": "col1",
"~:type": "~:color",
"~:value": "red",
"~:description": "",
"~:modified-at": "~m1779204586592"
}
}
],
[
"dim1",
{
"~#penpot/token": {
"~:id": "~u1dc5cafc-6d57-808d-8008-0b7af8180cbc",
"~:name": "dim1",
"~:type": "~:dimensions",
"~:value": "1",
"~:description": "",
"~:modified-at": "~m1779204586592"
}
}
],
[
"fam1",
{
"~#penpot/token": {
"~:id": "~u1dc5cafc-6d57-808d-8008-0b7b16dca3ad",
"~:name": "fam1",
"~:type": "~:font-family",
"~:value": [
"Aboreto"
],
"~:description": "",
"~:modified-at": "~m1779204618098"
}
}
]
]
}
}
}
]
]
},
"~:themes": {
"~#ordered-map": [
[
"",
{
"~#ordered-map": [
[
"__PENPOT__HIDDEN__TOKEN__THEME__",
{
"~#penpot/token-theme": {
"~:id": "~u00000000-0000-0000-0000-000000000000",
"~:name": "__PENPOT__HIDDEN__TOKEN__THEME__",
"~:group": "",
"~:description": "",
"~:is-source": false,
"~:external-id": "",
"~:modified-at": "~m1779204586593",
"~:sets": {
"~#set": [
"Global"
]
}
}
}
]
]
}
]
]
},
"~:active-themes": {
"~#set": [
"/__PENPOT__HIDDEN__TOKEN__THEME__"
]
}
}
}
}
}

View File

@ -1103,7 +1103,7 @@ test.describe("Tokens - creation", () => {
).toBeEnabled();
});
test("User cant submit empty typography token or reference", async ({
test("User can't submit empty typography token or reference", async ({
page,
}) => {
const { tokensUpdateCreateModal, tokenThemesSetsSidebar, tokensSidebar } =
@ -1661,7 +1661,7 @@ test.describe("Tokens - creation", () => {
await expect(tokensSidebar.getByLabel("primary")).toBeEnabled();
});
test("User cant create regular token with value missing", async ({
test("User can't create regular token with value missing", async ({
page,
}) => {
const { tokensUpdateCreateModal } = await setupEmptyTokensFileRender(page);
@ -1743,7 +1743,7 @@ test("User creates grouped color token", async ({ page }) => {
await expect(tokensSidebar.getByLabel("primary")).toBeEnabled();
});
test("User cant create regular token with value missing", async ({ page }) => {
test("User can't create regular token with value missing", async ({ page }) => {
const { tokensUpdateCreateModal } = await setupEmptyTokensFileRender(page);
const tokensTabPanel = page.getByRole("tabpanel", { name: "tokens" });
@ -1817,6 +1817,356 @@ test("User disables the current set but token still have resolved values shown i
);
});
test.describe("User can't create groups that clash with token names", () => {
const unknownError = "Unknown error";
const changeSetInput = async (sidebar, setName, finalKey = "Enter") => {
const setInput = sidebar.locator("input:focus");
await expect(setInput).toBeVisible();
await setInput.fill(setName);
await setInput.press(finalKey);
};
const createSet = async (sidebar, setName, finalKey = "Enter") => {
const tokensTabButton = sidebar
.getByRole("button", { name: "Add set" })
.click();
await changeSetInput(sidebar, setName, (finalKey = "Enter"));
};
const createBadToken = async (page, type, name, textFieldName, value) => {
const tokensTabPanel = page.getByRole("tabpanel", { name: "tokens" });
const { tokensUpdateCreateModal } = await setupTokensFileRender(page, {
flags: ["enable-token-shadow"],
});
// Add a token of the given type
await tokensTabPanel
.getByRole("button", { name: `Add Token: ${type}` })
.click();
await expect(tokensUpdateCreateModal).toBeVisible();
// Fill the bad name
const nameField = tokensUpdateCreateModal.getByLabel("Name");
await nameField.fill(name);
// Fill the value
const valueField = tokensUpdateCreateModal.getByRole("textbox", {
name: textFieldName,
});
await valueField.fill(value);
// Check that the value has an error
const errorNode =
tokensUpdateCreateModal.getByText(unknownError);
await expect(errorNode).toBeVisible();
// Check that the form cannot be saved
const submitButton = tokensUpdateCreateModal.getByRole("button", {
name: "Save",
});
await expect(submitButton).toBeDisabled();
};
test("User can't create Border Radius token with group name that clashes with existing token", async ({ page }) => {
const { tokenThemesSetsSidebar, tokensSidebar, tokenContextMenuForToken } =
await setupTokensFileRender(page, {
file: "workspace/get-file-tokens-all-types.json"
});
await expect(tokensSidebar).toBeVisible();
await createSet(tokenThemesSetsSidebar, "Second set");
await createBadToken(page, "Border Radius", "rad1.bad", "Value", "10");
});
test("User can't create Color token with group name that clashes with existing token", async ({ page }) => {
const { tokenThemesSetsSidebar, tokensSidebar, tokenContextMenuForToken } =
await setupTokensFileRender(page, {
file: "workspace/get-file-tokens-all-types.json"
});
await expect(tokensSidebar).toBeVisible();
await createSet(tokenThemesSetsSidebar, "Second set");
await createBadToken(page, "Color", "col1.bad", "Value", "red");
});
test("User can't create Dimensions token with group name that clashes with existing token", async ({ page }) => {
const { tokenThemesSetsSidebar, tokensSidebar, tokenContextMenuForToken } =
await setupTokensFileRender(page, {
file: "workspace/get-file-tokens-all-types.json"
});
await expect(tokensSidebar).toBeVisible();
await createSet(tokenThemesSetsSidebar, "Second set");
await createBadToken(page, "Dimensions", "dim1.bad", "Value", "100");
});
test("User can't create Font Size token with group name that clashes with existing token", async ({ page }) => {
const { tokenThemesSetsSidebar, tokensSidebar, tokenContextMenuForToken } =
await setupTokensFileRender(page, {
file: "workspace/get-file-tokens-all-types.json"
});
await expect(tokensSidebar).toBeVisible();
await createSet(tokenThemesSetsSidebar, "Second set");
await createBadToken(page, "Font Size", "fsiz1.bad", "Value", "16");
});
test("User can't create Font Weight token with group name that clashes with existing token", async ({ page }) => {
const { tokenThemesSetsSidebar, tokensSidebar, tokenContextMenuForToken } =
await setupTokensFileRender(page, {
file: "workspace/get-file-tokens-all-types.json"
});
await expect(tokensSidebar).toBeVisible();
await createSet(tokenThemesSetsSidebar, "Second set");
await createBadToken(page, "Font Weight", "wei1.bad", "Value", "400");
});
test("User can't create Letter Spacing token with group name that clashes with existing token", async ({ page }) => {
const { tokenThemesSetsSidebar, tokensSidebar, tokenContextMenuForToken } =
await setupTokensFileRender(page, {
file: "workspace/get-file-tokens-all-types.json"
});
await expect(tokensSidebar).toBeVisible();
await createSet(tokenThemesSetsSidebar, "Second set");
await createBadToken(page, "Letter Spacing", "lspa1.bad", "Value", "1");
});
test("User can't create Number token with group name that clashes with existing token", async ({ page }) => {
const { tokenThemesSetsSidebar, tokensSidebar, tokenContextMenuForToken } =
await setupTokensFileRender(page, {
file: "workspace/get-file-tokens-all-types.json"
});
await expect(tokensSidebar).toBeVisible();
await createSet(tokenThemesSetsSidebar, "Second set");
await createBadToken(page, "Number", "num1.bad", "Value", "10");
});
test("User can't create Rotation token with group name that clashes with existing token", async ({ page }) => {
const { tokenThemesSetsSidebar, tokensSidebar, tokenContextMenuForToken } =
await setupTokensFileRender(page, {
file: "workspace/get-file-tokens-all-types.json"
});
await expect(tokensSidebar).toBeVisible();
await createSet(tokenThemesSetsSidebar, "Second set");
await createBadToken(page, "Rotation", "rot1.bad", "Value", "90");
});
test("User can't create Sizing token with group name that clashes with existing token", async ({ page }) => {
const { tokenThemesSetsSidebar, tokensSidebar, tokenContextMenuForToken } =
await setupTokensFileRender(page, {
file: "workspace/get-file-tokens-all-types.json"
});
await expect(tokensSidebar).toBeVisible();
await createSet(tokenThemesSetsSidebar, "Second set");
await createBadToken(page, "Sizing", "siz1.bad", "Value", "100");
});
test("User can't create Spacing token with group name that clashes with existing token", async ({ page }) => {
const { tokenThemesSetsSidebar, tokensSidebar, tokenContextMenuForToken } =
await setupTokensFileRender(page, {
file: "workspace/get-file-tokens-all-types.json"
});
await expect(tokensSidebar).toBeVisible();
await createSet(tokenThemesSetsSidebar, "Second set");
await createBadToken(page, "Spacing", "spa1.bad", "Value", "10");
});
test("User can't create Stroke Width token with group name that clashes with existing token", async ({ page }) => {
const { tokenThemesSetsSidebar, tokensSidebar, tokenContextMenuForToken } =
await setupTokensFileRender(page, {
file: "workspace/get-file-tokens-all-types.json"
});
await expect(tokensSidebar).toBeVisible();
await createSet(tokenThemesSetsSidebar, "Second set");
await createBadToken(page, "Stroke Width", "str1.bad", "Value", "2");
});
test("User can't create Text Case token with group name that clashes with existing token", async ({ page }) => {
const { tokenThemesSetsSidebar, tokensSidebar, tokenContextMenuForToken } =
await setupTokensFileRender(page, {
file: "workspace/get-file-tokens-all-types.json"
});
await expect(tokensSidebar).toBeVisible();
await createSet(tokenThemesSetsSidebar, "Second set");
await createBadToken(page, "Text Case", "cas1.bad", "Value", "uppercase");
});
test("User can't create Text Decoration token with group name that clashes with existing token", async ({ page }) => {
const { tokenThemesSetsSidebar, tokensSidebar, tokenContextMenuForToken } =
await setupTokensFileRender(page, {
file: "workspace/get-file-tokens-all-types.json"
});
await expect(tokensSidebar).toBeVisible();
await createSet(tokenThemesSetsSidebar, "Second set");
await createBadToken(page, "Text Decoration", "td1.bad", "Value", "underline");
});
test("User can't create Typography token with group name that clashes with existing token", async ({ page }) => {
const { tokenThemesSetsSidebar, tokensSidebar } =
await setupTokensFileRender(page, {
file: "workspace/get-file-tokens-all-types.json"
});
await expect(tokensSidebar).toBeVisible();
await createSet(tokenThemesSetsSidebar, "Second set");
const tokensTabPanel = page.getByRole("tabpanel", { name: "tokens" });
const { tokensUpdateCreateModal } = await setupTokensFileRender(page, {
flags: ["enable-token-shadow"],
});
await tokensTabPanel
.getByRole("button", { name: `Add Token: Typography` })
.click();
await expect(tokensUpdateCreateModal).toBeVisible();
const nameField = tokensUpdateCreateModal.getByLabel("Name");
await nameField.fill("typ1.bad");
const fontFamilyField = tokensUpdateCreateModal.getByRole("textbox", {
name: "Font family",
});
await fontFamilyField.fill("Arial");
const fontSizeField = tokensUpdateCreateModal.getByRole("textbox", {
name: "Font size",
});
await fontSizeField.fill("16");
const fontWeightField = tokensUpdateCreateModal.getByRole("textbox", {
name: "Font weight",
});
await fontWeightField.fill("400");
const lineHeightField = tokensUpdateCreateModal.getByRole("textbox", {
name: "Line height",
});
await lineHeightField.fill("1.5");
const letterSpacingField = tokensUpdateCreateModal.getByRole("textbox", {
name: "Letter spacing",
});
await letterSpacingField.fill("0");
const textCaseField = tokensUpdateCreateModal.getByRole("textbox", {
name: "Text case",
});
await textCaseField.fill("none");
const textDecorationField = tokensUpdateCreateModal.getByRole("textbox", {
name: "Text decoration",
});
await textDecorationField.fill("none");
const submitButton = tokensUpdateCreateModal.getByRole("button", {
name: "Save",
});
const errorNode = tokensUpdateCreateModal.getByText(unknownError);
await expect(errorNode).toHaveCount(6);
await expect(submitButton).toBeDisabled();
});
test("User can't create Shadow token with group name that clashes with existing token", async ({ page }) => {
const { tokenThemesSetsSidebar, tokensSidebar } =
await setupTokensFileRender(page, {
file: "workspace/get-file-tokens-all-types.json"
});
await expect(tokensSidebar).toBeVisible();
await createSet(tokenThemesSetsSidebar, "Second set");
const tokensTabPanel = page.getByRole("tabpanel", { name: "tokens" });
const { tokensUpdateCreateModal } = await setupTokensFileRender(page, {
flags: ["enable-token-shadow"],
});
await tokensTabPanel
.getByRole("button", { name: `Add Token: Shadow` })
.click();
await expect(tokensUpdateCreateModal).toBeVisible();
const nameField = tokensUpdateCreateModal.getByLabel("Name");
await nameField.fill("sha1.bad");
const colorField = tokensUpdateCreateModal.getByRole("textbox", {
name: "Color",
});
await colorField.fill("red");
const offsetXField = tokensUpdateCreateModal.getByRole("textbox", {
name: "X",
});
await offsetXField.fill("7");
const offsetYField = tokensUpdateCreateModal.getByRole("textbox", {
name: "Y",
});
await offsetYField.fill("8");
const blurField = tokensUpdateCreateModal.getByRole("textbox", {
name: "Blur",
});
await blurField.fill("5");
const spreadField = tokensUpdateCreateModal.getByRole("textbox", {
name: "Spread",
});
await spreadField.fill("1");
const submitButton = tokensUpdateCreateModal.getByRole("button", {
name: "Save",
});
const errorNode = tokensUpdateCreateModal.getByText(unknownError);
await expect(errorNode).toHaveCount(4);
await expect(submitButton).toBeDisabled();
});
});
test.describe("Tokens tab - edition", () => {
test("User edits typography token and all fields are valid", async ({
page,

View File

@ -348,10 +348,10 @@ const createToken = async (page, type, name, textFieldName, value) => {
const nameField = tokensUpdateCreateModal.getByLabel("Name");
await nameField.fill(name);
const colorField = tokensUpdateCreateModal.getByRole("textbox", {
const valueField = tokensUpdateCreateModal.getByRole("textbox", {
name: textFieldName,
});
await colorField.fill(value);
await valueField.fill(value);
const submitButton = tokensUpdateCreateModal.getByRole("button", {
name: "Save",

View File

@ -141,9 +141,11 @@
:error/value to produce the message. Falls back to :message for
errors that originate from schema-validation (which have no :error/fn)."
[error]
(if-let [f (:error/fn error)]
(f (:error/value error))
(:message error)))
(if error
(if-let [f (:error/fn error)]
(f (:error/value error))
(:message error))
(tr "labels.unknown-error")))
(defn resolve-error-assoc-message
"Returns the error map with a :message key set to the resolved human-
@ -151,9 +153,11 @@
is called with :error/value; otherwise the map is returned unchanged
(it is expected to already carry a :message from schema-validation)."
[error]
(if-let [f (:error/fn error)]
(assoc error :message (f (:error/value error)))
error))
(if error
(if-let [f (:error/fn error)]
(assoc error :message (f (:error/value error)))
error)
(assoc error :message (tr "labels.unknown-error"))))
(defn humanize-errors [errors]
(->> errors

View File

@ -9,7 +9,6 @@
[app.config :as cf]
[app.main.data.helpers :as dh]
[app.main.data.modal :as modal]
[app.main.data.style-dictionary :as sd]
[app.main.data.workspace.tokens.application :as dwta]
[app.main.data.workspace.tokens.library-edit :as dwtl]
[app.main.data.workspace.tokens.propagation :as dwtp]
@ -112,9 +111,6 @@
(mf/with-memo [active-tokens selected-token-set-tokens]
(merge active-tokens selected-token-set-tokens))
tokens
(sd/use-resolved-tokens* tokens)
;; Group tokens by their type
tokens-by-type
(mf/with-memo [tokens selected-token-set-tokens]

View File

@ -13,6 +13,7 @@
[app.config :as cf]
[app.main.data.style-dictionary :as sd]
[app.main.data.tokenscript :as ts]
[app.main.data.workspace.tokens.errors :as wte]
[app.main.ui.context :as muc]
[app.main.ui.ds.buttons.icon-button :refer [icon-button*]]
[app.main.ui.ds.controls.input :as ds]
@ -60,7 +61,9 @@
resolved-value)]
(if resolved-value
(rx/of {:value resolved-value})
(rx/of {:error (first errors)}))))))))
(rx/of {:error (if errors
(first errors)
(wte/error-with-value :error/unknown value))}))))))))
(mf/defc value-combobox*
[{:keys [name tokens token token-type empty-to-end ref] :rest props}]

View File

@ -78,7 +78,9 @@
resolved-value)]
(if resolved-value
(rx/of {:value resolved-value})
(rx/of {:error (first errors)}))))))))
(rx/of {:error (if errors
(first errors)
(wte/error-with-value :error/unknown value))}))))))))
(mf/defc fonts-combobox*
[{:keys [token tokens name] :rest props}]

View File

@ -181,7 +181,9 @@
resolved-value)]
(if resolved-value
(rx/of {:value resolved-value})
(rx/of {:error (first errors)}))))))))
(rx/of {:error (if errors
(first errors)
(wte/error-with-value :error/unknown value))}))))))))
(mf/defc input*
[{:keys [name tokens token] :rest props}]