From 78e7d5a92e8ae8bb28038f736e2b4806f17b2285 Mon Sep 17 00:00:00 2001 From: Elena Torro Date: Mon, 18 May 2026 15:57:59 +0200 Subject: [PATCH] :bug: Fix token rename allowing cross-set name collision --- .../ui/specs/tokens/remapping.spec.js | 72 +++++++++++++++++++ .../management/forms/form_container.cljs | 7 +- 2 files changed, 77 insertions(+), 2 deletions(-) diff --git a/frontend/playwright/ui/specs/tokens/remapping.spec.js b/frontend/playwright/ui/specs/tokens/remapping.spec.js index 55472cb4a1..a9b25e544e 100644 --- a/frontend/playwright/ui/specs/tokens/remapping.spec.js +++ b/frontend/playwright/ui/specs/tokens/remapping.spec.js @@ -696,3 +696,75 @@ test.describe("Remapping group of tokens", () => { await expect(tokenReference).toBeVisible(); }); }); + +test.describe("Token rename validation across sets", () => { + test("Renaming a token to a name present in another active set fails validation", async ({ + page, + }) => { + const { + tokensUpdateCreateModal, + tokensSidebar, + tokenContextMenuForToken, + tokenThemesSetsSidebar, + } = await setupTokensFileRender(page); + + const tokensTabPanel = page.getByRole("tabpanel", { name: "tokens" }); + + // Create "base-color" in the default active set (theme) + await tokensTabPanel + .getByRole("button", { name: "Add Token: Color" }) + .click(); + await expect(tokensUpdateCreateModal).toBeVisible(); + await tokensUpdateCreateModal.getByLabel("Name").fill("base-color"); + await tokensUpdateCreateModal.getByLabel("Value").fill("#FFFFFF"); + await tokensUpdateCreateModal + .getByRole("button", { name: "Save" }) + .click(); + await expect(tokensUpdateCreateModal).not.toBeVisible(); + + // Create a new set-b, select it, and enable it + await tokenThemesSetsSidebar + .getByRole("button", { name: "Add set" }) + .click(); + const setBInput = tokenThemesSetsSidebar.locator("input:focus"); + await setBInput.fill("set-b"); + await setBInput.press("Enter"); + await tokenThemesSetsSidebar + .getByRole("button", { name: "set-b" }) + .click(); + await tokenThemesSetsSidebar + .getByRole("button", { name: "set-b" }) + .getByRole("checkbox") + .click(); + + // Create "unique-name" in set-b + await tokensTabPanel + .getByRole("button", { name: "Add Token: Color" }) + .click(); + await expect(tokensUpdateCreateModal).toBeVisible(); + await tokensUpdateCreateModal.getByLabel("Name").fill("unique-name"); + await tokensUpdateCreateModal.getByLabel("Value").fill("#000000"); + await tokensUpdateCreateModal + .getByRole("button", { name: "Save" }) + .click(); + await expect(tokensUpdateCreateModal).not.toBeVisible(); + + // Try to rename "unique-name" (in set-b) to "base-color" (which exists in the active theme set) + await tokensSidebar + .getByRole("button", { name: "unique-name" }) + .click({ button: "right" }); + await tokenContextMenuForToken.getByText("Edit token").click(); + + await expect(tokensUpdateCreateModal).toBeVisible(); + const nameField = tokensUpdateCreateModal.getByLabel("Name"); + await nameField.fill("base-color"); + + // The validation error should appear reactively and the Save button should be disabled + await expect( + tokensUpdateCreateModal.getByText(/already exists at the path/i), + ).toBeVisible(); + await expect( + tokensUpdateCreateModal.getByRole("button", { name: "Save" }), + ).toBeDisabled(); + }); +}); diff --git a/frontend/src/app/main/ui/workspace/tokens/management/forms/form_container.cljs b/frontend/src/app/main/ui/workspace/tokens/management/forms/form_container.cljs index 0e6d55df03..4ea5eb40d1 100644 --- a/frontend/src/app/main/ui/workspace/tokens/management/forms/form_container.cljs +++ b/frontend/src/app/main/ui/workspace/tokens/management/forms/form_container.cljs @@ -27,13 +27,16 @@ tokens-in-selected-set (mf/deref refs/workspace-all-tokens-in-selected-set) + active-tokens + (mf/deref refs/workspace-active-theme-sets-tokens) + token-path (mf/with-memo [token] (ctob/get-token-path token)) tokens-tree-in-selected-set - (mf/with-memo [token-path tokens-in-selected-set] - (-> (ctob/tokens-tree tokens-in-selected-set) + (mf/with-memo [token-path tokens-in-selected-set active-tokens] + (-> (ctob/tokens-tree (merge active-tokens tokens-in-selected-set)) (d/dissoc-in token-path))) props (mf/spread-props props {:token-type token-type