diff --git a/frontend/playwright/data/workspace/get-file-tokens-all-types.json b/frontend/playwright/data/workspace/get-file-tokens-all-types.json new file mode 100644 index 0000000000..75700e00fa --- /dev/null +++ b/frontend/playwright/data/workspace/get-file-tokens-all-types.json @@ -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__" + ] + } + } + } + } +} \ No newline at end of file diff --git a/frontend/playwright/ui/specs/tokens/crud.spec.js b/frontend/playwright/ui/specs/tokens/crud.spec.js index 8cf0ba50e7..60bbf862c6 100644 --- a/frontend/playwright/ui/specs/tokens/crud.spec.js +++ b/frontend/playwright/ui/specs/tokens/crud.spec.js @@ -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, diff --git a/frontend/playwright/ui/specs/tokens/helpers.js b/frontend/playwright/ui/specs/tokens/helpers.js index d641eb6c04..fc142e7de5 100644 --- a/frontend/playwright/ui/specs/tokens/helpers.js +++ b/frontend/playwright/ui/specs/tokens/helpers.js @@ -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", diff --git a/frontend/src/app/main/data/workspace/tokens/errors.cljs b/frontend/src/app/main/data/workspace/tokens/errors.cljs index 7338663b2d..0e7a78de9b 100644 --- a/frontend/src/app/main/data/workspace/tokens/errors.cljs +++ b/frontend/src/app/main/data/workspace/tokens/errors.cljs @@ -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 diff --git a/frontend/src/app/main/ui/workspace/tokens/management.cljs b/frontend/src/app/main/ui/workspace/tokens/management.cljs index 4442517184..7281679549 100644 --- a/frontend/src/app/main/ui/workspace/tokens/management.cljs +++ b/frontend/src/app/main/ui/workspace/tokens/management.cljs @@ -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] diff --git a/frontend/src/app/main/ui/workspace/tokens/management/forms/controls/combobox.cljs b/frontend/src/app/main/ui/workspace/tokens/management/forms/controls/combobox.cljs index 137551c260..6ab6f3ce0a 100644 --- a/frontend/src/app/main/ui/workspace/tokens/management/forms/controls/combobox.cljs +++ b/frontend/src/app/main/ui/workspace/tokens/management/forms/controls/combobox.cljs @@ -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}] diff --git a/frontend/src/app/main/ui/workspace/tokens/management/forms/controls/fonts_combobox.cljs b/frontend/src/app/main/ui/workspace/tokens/management/forms/controls/fonts_combobox.cljs index d663e9defe..8053d8fd13 100644 --- a/frontend/src/app/main/ui/workspace/tokens/management/forms/controls/fonts_combobox.cljs +++ b/frontend/src/app/main/ui/workspace/tokens/management/forms/controls/fonts_combobox.cljs @@ -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}] diff --git a/frontend/src/app/main/ui/workspace/tokens/management/forms/controls/input.cljs b/frontend/src/app/main/ui/workspace/tokens/management/forms/controls/input.cljs index 3e20c5a2e1..ff8d77387c 100644 --- a/frontend/src/app/main/ui/workspace/tokens/management/forms/controls/input.cljs +++ b/frontend/src/app/main/ui/workspace/tokens/management/forms/controls/input.cljs @@ -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}]