diff --git a/common/test/common_tests/files/tokens_test.cljc b/common/test/common_tests/files/tokens_test.cljc index 8d6a28b393..540a7b9625 100644 --- a/common/test/common_tests/files/tokens_test.cljc +++ b/common/test/common_tests/files/tokens_test.cljc @@ -7,6 +7,7 @@ (ns common-tests.files.tokens-test (:require [app.common.files.tokens :as cfo] + [app.common.schema :as sm] [app.common.test-helpers.files :as thf] [app.common.test-helpers.ids-map :as thi] [app.common.test-helpers.tokens :as tht] @@ -1709,3 +1710,42 @@ (t/is (not (contains? (:data detached) :tokens-source))) (t/is (not (contains? (:data detached) :tokens-status))) (t/is (= detached (ctf/check-file detached))))) + +;; Token name schema + +(t/deftest test-make-token-name-schema + (t/testing "rejects a name that already exists as a token" + (let [schema (cfo/make-token-name-schema {"foo" {:name "foo"}})] + (t/is (false? (sm/validate schema "foo"))))) + + (t/testing "rejects a token inside an existing token" + (let [schema (cfo/make-token-name-schema {"foo" {:name "foo"}})] + (t/is (false? (sm/validate schema "foo.bar"))))) + + (t/testing "rejects a token name that collides with a group" + (let [schema (cfo/make-token-name-schema {"foo" {"child" {:name "foo.child"}}})] + (t/is (false? (sm/validate schema "foo"))))) + + (t/testing "rejects a name that exists as a child token" + (let [schema (cfo/make-token-name-schema {"foo" {"bar" {:name "foo.bar"}}})] + (t/is (false? (sm/validate schema "foo.bar"))))) + + (t/testing "accepts a valid sibling name" + (let [schema (cfo/make-token-name-schema {"foo" {:name "foo"}})] + (t/is (true? (sm/validate schema "bar"))) + (t/is (true? (sm/validate schema "bar.baz"))))) + + (t/testing "rejects every name when there is no tokens tree" + (let [schema (cfo/make-token-name-schema nil)] + (t/is (false? (sm/validate schema "foo"))))) + + (t/testing "rejects names that do not match the token name format" + (let [schema (cfo/make-token-name-schema {})] + (t/is (false? (sm/validate schema ""))) + (t/is (false? (sm/validate schema "$foo"))) + (t/is (false? (sm/validate schema "foo."))) + (t/is (false? (sm/validate schema "foo..bar"))))) + + (t/testing "rejects names longer than 255 characters" + (let [schema (cfo/make-token-name-schema {})] + (t/is (false? (sm/validate schema (apply str (repeat 256 "a")))))))) diff --git a/frontend/playwright/ui/specs/tokens/apply.spec.js b/frontend/playwright/ui/specs/tokens/apply.spec.js index e13a377ae7..6be288b751 100644 --- a/frontend/playwright/ui/specs/tokens/apply.spec.js +++ b/frontend/playwright/ui/specs/tokens/apply.spec.js @@ -58,7 +58,7 @@ test.describe("Tokens: Apply token", () => { const colorPicker = page.getByTestId("colorpicker"); await expect(colorPicker).toBeVisible(); - await colorPicker.getByTestId('opt-token-color').click(); + await colorPicker.getByTestId("opt-token-color").click(); await colorPicker.getByRole("button", { name: "black" }).click(); // The applied token badge replaces the plain color swatch/input in @@ -1649,9 +1649,7 @@ test.describe("Numeric Input and Token Integration Tests", () => { ).toBeVisible(); }); - test("Invalid formula reverts to previous value in padding inputs", async ({ - page, - }) => { + test("Invalid formula reverts to the previous value", async ({ page }) => { const { workspacePage, tokensSidebar, tokenContextMenuForToken } = await setupTokensFileRender(page, { flags: ["enable-token-combobox", "enable-feature-token-input"], @@ -1690,120 +1688,14 @@ test.describe("Numeric Input and Token Integration Tests", () => { // Enter a valid value first await verticalPaddingInput.fill("23"); await verticalPaddingInput.press("Enter"); - // Wait for potential error handling - await page.waitForTimeout(500); + await expect(verticalPaddingInput).toHaveValue("23"); - expect(await verticalPaddingInput.inputValue()).toMatch("23"); - - // Enter invalid expression + // An invalid expression reverts to the previous valid value await verticalPaddingInput.fill("abc+1"); await verticalPaddingInput.press("Enter"); - // Wait for potential error handling - await page.waitForTimeout(500); - - // Value should revert to previous valid value - expect(await verticalPaddingInput.inputValue()).toMatch("23"); - - // Should NOT contain invalid characters - expect(await verticalPaddingInput.inputValue()).not.toContain("abc"); - }); - - test("Division by zero reverts to previous value", async ({ page }) => { - const { workspacePage, tokensSidebar, tokenContextMenuForToken } = - await setupTokensFileRender(page, { - flags: ["enable-token-combobox", "enable-feature-token-input"], - }); - - await page.getByRole("tab", { name: "Layers" }).click(); - - await workspacePage.layers.getByTestId("layer-row").nth(1).click(); - - const layoutSection = - workspacePage.rightSidebar.getByTestId("inspect-layout"); - - const addLayoutButton = layoutSection - .getByRole("button", { name: "Add layout" }) - .first(); - - await addLayoutButton.click(); - - await page.getByText("Flex layout").click(); - - // Apply a spacing token to the Column gap property - const tokensTabButton = page.getByRole("tab", { name: "Tokens" }); - await tokensTabButton.click(); - await unfoldTokenType(tokensSidebar, "spacing"); - - await tokensSidebar - .getByRole("button", { name: "spacing.lg" }) - .click({ button: "right" }); - - await tokenContextMenuForToken.getByText("Column gap").click(); - - const verticalPaddingInput = layoutSection.getByRole("textbox", { - name: "Vertical padding", - }); - - // Enter a valid value first - await verticalPaddingInput.fill("23"); - await verticalPaddingInput.press("Enter"); - // Wait for potential error handling - await page.waitForTimeout(500); - - expect(await verticalPaddingInput.inputValue()).toMatch("23"); - - // Enter invalid expression - await verticalPaddingInput.fill("10/0"); - await verticalPaddingInput.press("Enter"); - - // Wait for potential error handling - await page.waitForTimeout(500); - - // Value should revert to previous valid value - expect(await verticalPaddingInput.inputValue()).toMatch("23"); - - // Should NOT contain invalid characters - expect(await verticalPaddingInput.inputValue()).not.toContain("10/0"); - - // Value should revert - expect(await verticalPaddingInput.inputValue()).toMatch(/^(\d+|--)$/); - expect(await verticalPaddingInput.inputValue()).not.toBe("Infinity"); - }); - - test("Negative expression result handled correctly", async ({ page }) => { - const { workspacePage, tokensSidebar, tokenContextMenuForToken } = - await setupTokensFileRender(page, { - flags: ["enable-token-combobox", "enable-feature-token-input"], - }); - - await page.getByRole("tab", { name: "Layers" }).click(); - - await workspacePage.layers.getByTestId("layer-row").nth(1).click(); - const widthInput = workspacePage.rightSidebar.getByRole("textbox", { - name: "Width", - }); - await expect(widthInput).toBeVisible(); - - // Enter a valid value first - await widthInput.fill("23"); - await widthInput.press("Enter"); - - // Wait for potential error handling - await page.waitForTimeout(500); - expect(await widthInput.inputValue()).toMatch("23"); - - // Enter a negative expression - await widthInput.fill("10-50"); - await widthInput.press("Enter"); - - // Wait for potential error handling - await page.waitForTimeout(500); - - expect(await widthInput.inputValue()).toMatch("0.01"); - - // Should NOT negative values - expect(await widthInput.inputValue()).not.toContain("-40"); + await expect(verticalPaddingInput).toHaveValue("23"); + await expect(verticalPaddingInput).not.toHaveValue(/abc/); }); test("Token pill show broken reference when set is not activated", async ({ @@ -1903,371 +1795,107 @@ test("BUG: 14234, Numeric input token filtering must be case sensitive", async ( test("BUG: 10471, Correct tooltip on right sidebar tokens", async ({ page, }) => { - const { - workspacePage, - tokensSidebar, - tokenContextMenuForToken, - tokenThemesSetsSidebar, - tokenSetGroupItems, - } = await setupTokensFileRender(page, { - file: "workspace/get-file-token-tooltip.json", - }); + const { workspacePage, tokenThemesSetsSidebar } = await setupTokensFileRender( + page, + { + file: "workspace/get-file-token-tooltip.json", + }, + ); await page.getByRole("tab", { name: "Layers" }).click(); - const borderRadiusSection = page.getByRole("region", { - name: "Border radius section", - }); const fillSection = workspacePage.rightSidebar.getByRole("region", { name: "Fill section", }); - const typographySection = workspacePage.rightSidebar.getByRole("region", { - name: "Text section", - }); - // ---------------------------------- - // Select rectangle with active token - // ---------------------------------- - await workspacePage.layers - .getByTestId("layer-row") - .filter({ hasText: "Rectangle with token" }) - .click(); - await expect(borderRadiusSection).toBeVisible(); - const brPill = borderRadiusSection.getByRole("button", { name: "base-50" }); - await expect(brPill).toBeVisible(); - await brPill.hover(); - await expect(page.getByRole("tooltip", { name: "base-50" })).toBeVisible(); - await expect(fillSection).toBeVisible(); - const fillTokenPill = fillSection.getByLabel("out-ref", { - exact: true, - }); - await expect(fillTokenPill).toBeVisible(); - await fillTokenPill.hover(); - const colorTokenTooltip = page.getByRole("tooltip", { - name: "out-ref", - }); - await expect(colorTokenTooltip).toBeVisible(); - await expect(colorTokenTooltip).toHaveText( - "Name: out-refResolved value: #da1fea", - ); - // ---------------------------------- - // Select text with active token - // ---------------------------------- - await workspacePage.layers - .getByTestId("layer-row") - .filter({ hasText: "Text with token" }) - .click(); - await expect(fillSection).toBeVisible(); - const textFillTokenPill = fillSection.getByLabel("out-ref", { - exact: true, - }); - await expect(textFillTokenPill).toBeVisible(); - await textFillTokenPill.hover(); - await expect(colorTokenTooltip).toBeVisible(); - await expect(colorTokenTooltip).toHaveText( - "Name: out-refResolved value: #da1fea", - ); - await expect(typographySection).toBeVisible(); - const typographyTokenPill = typographySection.getByLabel("out-typo", { - exact: true, - }); - await expect(typographyTokenPill).toBeVisible(); - await typographyTokenPill.hover(); - const typographyTokenTooltip = page.getByRole("tooltip", { - name: "out-typo", - }); - await expect(typographyTokenTooltip).toBeVisible(); - await expect(typographyTokenTooltip).toHaveText( - 'Name: out-typoResolved value:- font-family: "Arizonia"', - ); - // ----------------------------------------- - // Select rectangle layer with deleted token - // ----------------------------------------- - await workspacePage.layers - .getByTestId("layer-row") - .filter({ hasText: "Deleted token rect" }) - .click(); + await test.step("Deleted token", async () => { + await workspacePage.layers + .getByTestId("layer-row") + .filter({ hasText: "Deleted token rect" }) + .click(); - await expect(borderRadiusSection).toBeVisible(); - const deletedBrPill = borderRadiusSection.getByRole("button", { - name: "{deleted} token does not exist or", - }); - await expect(deletedBrPill).toBeVisible(); - await deletedBrPill.hover(); - await expect( - page.getByRole("tooltip", { name: "{deleted} token does not exist or" }), - ).toBeVisible(); - await expect(fillSection).toBeVisible(); - const fillBrokenTokenPill = fillSection.getByLabel("blue", { - exact: true, - }); - await expect(fillBrokenTokenPill).toBeVisible(); - await fillBrokenTokenPill.hover(); - const fillBrokenTokenTooltip = page.getByRole("tooltip", { - name: "blue", - }); - await expect(fillBrokenTokenTooltip).toBeVisible(); - await expect(fillBrokenTokenTooltip).toHaveText( - "{blue} token does not exist or has been deleted.", - ); - // ------------------------------------ - // Select text layer with deleted token - // ------------------------------------ - await workspacePage.layers - .getByTestId("layer-row") - .filter({ hasText: "Text with deleted token" }) - .click(); - await expect(typographySection).toBeVisible(); - const brokenTypographyTokenPill = typographySection.getByLabel( - "deleted-typo", - { exact: true }, - ); - await expect(brokenTypographyTokenPill).toBeVisible(); - await brokenTypographyTokenPill.hover(); - const brokenTypographyTokenTooltip = page.getByRole("tooltip", { - name: "deleted-typo", - }); - await expect(brokenTypographyTokenTooltip).toBeVisible(); - await expect(brokenTypographyTokenTooltip).toHaveText( - "{deleted-typo} token does not exist or has been deleted.", - ); + await expect(fillSection).toBeVisible(); - // --------------------------------------------------- - // Select rectangle layer with deleted reference token - // --------------------------------------------------- - await page.getByRole("tab", { name: "Tokens" }).click(); - await tokenThemesSetsSidebar.getByRole("button", { name: "Dark" }).click(); + const brokenPill = fillSection.getByLabel("blue", { exact: true }); + await expect(brokenPill).toBeVisible(); + await brokenPill.hover(); - await tokenThemesSetsSidebar - .getByRole("button", { name: "Dark" }) - .getByRole("checkbox") - .click(); - await page.getByRole("tab", { name: "Layers" }).click(); - await workspacePage.layers - .getByTestId("layer-row") - .filter({ hasText: "Rectangle with deleted reference" }) - .click(); - await expect(borderRadiusSection).toBeVisible(); - const deletedReferenceBrPill = borderRadiusSection.getByRole("button", { - name: "Reference in {ref-1} is not valid or is not in any active set.", + const tooltip = page.getByRole("tooltip", { name: "blue" }); + await expect(tooltip).toBeVisible(); + await expect(tooltip).toHaveText( + "{blue} token does not exist or has been deleted.", + ); }); - await expect(deletedReferenceBrPill).toBeVisible(); - await deletedReferenceBrPill.hover(); - await expect( - page.getByRole("tooltip", { - name: "Reference in {ref-1} is not valid or is not in any active set.", - }), - ).toBeVisible(); - await expect(fillSection).toBeVisible(); - const fillDeletedReferenceTokenPill = fillSection.getByLabel("ref-grfeen", { - exact: true, - }); - await expect(fillDeletedReferenceTokenPill).toBeVisible(); - await fillDeletedReferenceTokenPill.hover(); - const fillDeletedReferenceTokenTooltip = page.getByRole("tooltip", { - name: "Reference in {ref-grfeen} is not valid or is not in any active set.", - }); - await expect(fillDeletedReferenceTokenTooltip).toBeVisible(); - await expect(fillDeletedReferenceTokenTooltip).toHaveText( - "Reference in {ref-grfeen} is not valid or is not in any active set.", - ); - // ---------------------------------------------- - // Select text layer with deleted reference token - // ---------------------------------------------- + await test.step("Reference outside the active sets", async () => { + await page.getByRole("tab", { name: "Tokens" }).click(); - await workspacePage.layers - .getByTestId("layer-row") - .filter({ hasText: "Text with deleted reference" }) - .click(); + await tokenThemesSetsSidebar.getByRole("button", { name: "Dark" }).click(); + await tokenThemesSetsSidebar + .getByRole("button", { name: "Dark" }) + .getByRole("checkbox") + .click(); - await expect(typographySection).toBeVisible(); - const deletedRefTypographyTokenPill = typographySection.getByLabel( - "ref-typo", - { exact: true }, - ); - await expect(deletedRefTypographyTokenPill).toBeVisible(); - await deletedRefTypographyTokenPill.hover(); - const deletedRefTypographyTokenTooltip = page.getByRole("tooltip", { - name: "Reference in {ref-typo} is not valid or is", - }); - await expect(deletedRefTypographyTokenTooltip).toBeVisible(); - await expect(deletedRefTypographyTokenTooltip).toHaveText( - "Reference in {ref-typo} is not valid or is not in any active set.", - ); - await expect(fillSection).toBeVisible(); - const fillTextDeletedReferenceTokenPill = fillSection.getByLabel( - "ref-grfeen", - { + await page.getByRole("tab", { name: "Layers" }).click(); + + await workspacePage.layers + .getByTestId("layer-row") + .filter({ hasText: "Rectangle with deleted reference" }) + .click(); + + await expect(fillSection).toBeVisible(); + + const brokenReferencePill = fillSection.getByLabel("ref-grfeen", { exact: true, - }, - ); - await expect(fillTextDeletedReferenceTokenPill).toBeVisible(); - await fillTextDeletedReferenceTokenPill.hover(); - const fillTextDeletedReferenceTokenTooltip = page.getByRole("tooltip", { - name: "Reference in {ref-grfeen} is not valid or is not in any active set.", - }); - await expect(fillTextDeletedReferenceTokenTooltip).toBeVisible(); - await expect(fillTextDeletedReferenceTokenTooltip).toHaveText( - "Reference in {ref-grfeen} is not valid or is not in any active set.", - ); + }); + await expect(brokenReferencePill).toBeVisible(); + await brokenReferencePill.hover(); - // ----------------------------------------------------------- - // Select rectangle layer with reference token on inactive set - // ----------------------------------------------------------- - await page.getByRole("tab", { name: "Tokens" }).click(); - await tokenThemesSetsSidebar.getByRole("button", { name: "Global" }).click(); + const tooltip = page.getByRole("tooltip", { + name: "Reference in {ref-grfeen} is not valid or is not in any active set.", + }); + await expect(tooltip).toBeVisible(); + await expect(tooltip).toHaveText( + "Reference in {ref-grfeen} is not valid or is not in any active set.", + ); + }); - await tokenThemesSetsSidebar - .getByRole("button", { name: "Global" }) - .getByRole("checkbox") - .click(); - await page.getByRole("tab", { name: "Layers" }).click(); - await workspacePage.layers - .getByTestId("layer-row") - .filter({ hasText: "Rectangle with not active reference" }) - .click(); - await expect(borderRadiusSection).toBeVisible(); - const nonActiveReferenceBrPill = borderRadiusSection.getByRole("button", { - name: "Reference in {in-br} is not valid or is not in any active set.", - }); - await expect(nonActiveReferenceBrPill).toBeVisible(); - await nonActiveReferenceBrPill.hover(); - await expect( - page.getByRole("tooltip", { - name: "Reference in {in-br} is not valid or is not in any active set.", - }), - ).toBeVisible(); - await expect(fillSection).toBeVisible(); - const fillNonActiveReferenceTokenPill = fillSection.getByLabel("in-color", { - exact: true, - }); - await expect(fillNonActiveReferenceTokenPill).toBeVisible(); - await fillNonActiveReferenceTokenPill.hover(); - const fillNonActiveReferenceTokenTooltip = page.getByRole("tooltip", { - name: "Reference in {in-color} is not valid or is not in any active set.", - }); - await expect(fillNonActiveReferenceTokenTooltip).toBeVisible(); - await expect(fillNonActiveReferenceTokenTooltip).toHaveText( - "Reference in {in-color} is not valid or is not in any active set.", - ); - // ------------------------------------------------------ - // Select text layer with reference token on inactive set - // ------------------------------------------------------ + await test.step("Token in an inactive set", async () => { + await page.getByRole("tab", { name: "Tokens" }).click(); - await workspacePage.layers - .getByTestId("layer-row") - .filter({ hasText: "Text with not active reference" }) - .click(); + await tokenThemesSetsSidebar + .getByRole("button", { name: "Global" }) + .click(); + await tokenThemesSetsSidebar + .getByRole("button", { name: "Global" }) + .getByRole("checkbox") + .click(); - await expect(typographySection).toBeVisible(); - const notActiveRefTypographyTokenPill = typographySection.getByLabel( - "in-typo", - { exact: true }, - ); - await expect(notActiveRefTypographyTokenPill).toBeVisible(); - await notActiveRefTypographyTokenPill.hover(); - const notActiveRefTypographyTokenTooltip = page.getByRole("tooltip", { - name: "Reference in {in-typo} is not valid or is", - }); - await expect(notActiveRefTypographyTokenTooltip).toBeVisible(); - await expect(notActiveRefTypographyTokenTooltip).toHaveText( - "Reference in {in-typo} is not valid or is not in any active set.", - ); - await expect(fillSection).toBeVisible(); - const fillTextNotActiveReferenceTokenPill = fillSection.getByLabel( - "in-color", - { - exact: true, - }, - ); - await expect(fillTextNotActiveReferenceTokenPill).toBeVisible(); - await fillTextNotActiveReferenceTokenPill.hover(); - const fillTextNotActiveReferenceTokenTooltip = page.getByRole("tooltip", { - name: "Reference in {in-color} is not valid or is not in any active set.", - }); - await expect(fillTextNotActiveReferenceTokenTooltip).toBeVisible(); - await expect(fillTextNotActiveReferenceTokenTooltip).toHaveText( - "Reference in {in-color} is not valid or is not in any active set.", - ); + await tokenThemesSetsSidebar.getByRole("button", { name: "Dark" }).click(); + await tokenThemesSetsSidebar + .getByRole("button", { name: "Dark" }) + .getByRole("checkbox") + .click(); - // ------------------------------------------------- - // Select rectangle layer with token on inactive set - // ------------------------------------------------- - await page.getByRole("tab", { name: "Tokens" }).click(); - await tokenThemesSetsSidebar.getByRole("button", { name: "Dark" }).click(); + await page.getByRole("tab", { name: "Layers" }).click(); - await tokenThemesSetsSidebar - .getByRole("button", { name: "Dark" }) - .getByRole("checkbox") - .click(); - await page.getByRole("tab", { name: "Layers" }).click(); - await workspacePage.layers - .getByTestId("layer-row") - .filter({ hasText: "Rectangle with not active token" }) - .click(); - await expect(borderRadiusSection).toBeVisible(); - const nonActiveBrPill = borderRadiusSection.getByRole("button", { - name: "{border-radius} token is not in any active set", - }); - await expect(nonActiveBrPill).toBeVisible(); - await nonActiveBrPill.hover(); - await expect( - page.getByRole("tooltip", { - name: "{border-radius} token is not in any active set or has an invalid value", - }), - ).toBeVisible(); - await expect(fillSection).toBeVisible(); - const fillNonActiveTokenPill = fillSection.getByLabel("red", { - exact: true, - }); - await expect(fillNonActiveTokenPill).toBeVisible(); - await fillNonActiveTokenPill.hover(); - const fillNonActiveTokenTooltip = page.getByRole("tooltip", { - name: "{red} token is not in any active set or has an invalid value.", - }); - await expect(fillNonActiveTokenTooltip).toBeVisible(); - await expect(fillNonActiveTokenTooltip).toHaveText( - "{red} token is not in any active set or has an invalid value.", - ); + await workspacePage.layers + .getByTestId("layer-row") + .filter({ hasText: "Rectangle with not active token" }) + .click(); - // -------------------------------------------- - // Select text layer with token on inactive set - // -------------------------------------------- + await expect(fillSection).toBeVisible(); - await workspacePage.layers - .getByTestId("layer-row") - .filter({ hasText: "Text with not active token" }) - .click(); + const inactivePill = fillSection.getByLabel("red", { exact: true }); + await expect(inactivePill).toBeVisible(); + await inactivePill.hover(); - await expect(typographySection).toBeVisible(); - const notActiveTypographyTokenPill = typographySection.getByLabel( - "typo-2", - { exact: true }, - ); - await expect(notActiveTypographyTokenPill).toBeVisible(); - await notActiveTypographyTokenPill.hover(); - const notActiveTypographyTokenTooltip = page.getByRole("tooltip", { - name: "{typo-2} token is not in any active set or has an invalid value.", + const tooltip = page.getByRole("tooltip", { + name: "{red} token is not in any active set or has an invalid value.", + }); + await expect(tooltip).toBeVisible(); + await expect(tooltip).toHaveText( + "{red} token is not in any active set or has an invalid value.", + ); }); - await expect(notActiveTypographyTokenTooltip).toBeVisible(); - await expect(notActiveTypographyTokenTooltip).toHaveText( - "{typo-2} token is not in any active set or has an invalid value.", - ); - await expect(fillSection).toBeVisible(); - const fillTextNotActiveTokenPill = fillSection.getByLabel( - "red", - { - exact: true, - }, - ); - await expect(fillTextNotActiveTokenPill).toBeVisible(); - await fillTextNotActiveTokenPill.hover(); - const fillTextNotActiveTokenTooltip = page.getByRole("tooltip", { - name: "{red} token is not in any active set or has an invalid value.", - }); - await expect(fillTextNotActiveTokenTooltip).toBeVisible(); - await expect(fillTextNotActiveTokenTooltip).toHaveText( - "{red} token is not in any active set or has an invalid value.", - ); }); diff --git a/frontend/playwright/ui/specs/tokens/crud.spec.js b/frontend/playwright/ui/specs/tokens/crud.spec.js index 6fb6a0cf42..6c8d3c0d35 100644 --- a/frontend/playwright/ui/specs/tokens/crud.spec.js +++ b/frontend/playwright/ui/specs/tokens/crud.spec.js @@ -5,7 +5,10 @@ import { setupEmptyTokensFileRender, setupTokensFileRender, setupTypographyTokensFileRender, - testTokenCreationFlow, + openTokenCreationForm, + assertTokenCreationLadder, + saveToken, + createTokenWithValue, unfoldTokenType, createToken, createSet, @@ -17,19 +20,64 @@ test.beforeEach(async ({ page }) => { }); test.describe("Tokens - creation", () => { - test("User creates border radius token", async ({ page }) => { - await testTokenCreationFlow(page, { - tokenLabel: "Border Radius", - namePlaceholder: "Enter border radius token name", - valuePlaceholder: "Enter a value or alias with {alias}", - invalidValue: "red", - validValue: "2 + 3", - selfReferenceValue: "{my-token}", - missingReferenceValue: "{missing-token}", - secondValidValue: "{my-token} - 2", - resolvedValueText: "Resolved value: 5", - secondResolvedValueText: "Resolved value: 3", + test("User creates tokens of every numeric type", async ({ page }) => { + const { tokensUpdateCreateModal } = await setupEmptyTokensFileRender(page); + + const cases = [ + ["Border Radius", "border radius", "my-token-border-radius"], + ["Dimensions", "dimensions", "my-token-dimensions"], + ["Font Size", "font size", "my-token-font-size"], + ["Letter spacing", "letter spacing", "my-token-letter-spacing"], + ["Number", "number", "my-token-number"], + ["Rotation", "rotation", "my-token-rotation"], + ["Sizing", "sizing", "my-token-sizing"], + ["Spacing", "spacing", "my-token-spacing"], + ["Stroke width", "stroke width", "my-token-stroke-width"], + ]; + + const [first, ...rest] = cases; + + await test.step(first[0], async () => { + const [tokenLabel, placeholder, name] = first; + const namePlaceholder = `Enter ${placeholder} token name`; + + await openTokenCreationForm(page, tokensUpdateCreateModal, { + tokenLabel, + namePlaceholder, + }); + + await assertTokenCreationLadder(tokensUpdateCreateModal, { name }); + + await saveToken(page, tokensUpdateCreateModal, { + tokenLabel, + name, + value: "2 + 3", + resolvedText: "Resolved value: 5", + }); + + // A second token that references the first one + await openTokenCreationForm(page, tokensUpdateCreateModal, { + tokenLabel, + namePlaceholder, + }); + + await saveToken(page, tokensUpdateCreateModal, { + tokenLabel, + name: `${name}-ref`, + value: `{${name}} - 2`, + resolvedText: "Resolved value: 3", + }); }); + + for (const [tokenLabel, placeholder, name] of rest) { + await test.step(tokenLabel, async () => { + await createTokenWithValue(page, tokensUpdateCreateModal, { + tokenLabel, + namePlaceholder: `Enter ${placeholder} token name`, + name, + }); + }); + } }); test("User creates border radius token with combobox", async ({ page }) => { @@ -187,126 +235,6 @@ test.describe("Tokens - creation", () => { ).toBeEnabled(); }); - test("User creates dimensions token", async ({ page }) => { - await testTokenCreationFlow(page, { - tokenLabel: "Dimensions", - namePlaceholder: "Enter dimensions token name", - valuePlaceholder: "Enter a value or alias with {alias}", - invalidValue: "red", - validValue: "2 + 3", - selfReferenceValue: "{my-token}", - missingReferenceValue: "{missing-token}", - secondValidValue: "{my-token} - 2", - resolvedValueText: "Resolved value: 5", - secondResolvedValueText: "Resolved value: 3", - }); - }); - - test("User creates font size token", async ({ page }) => { - await testTokenCreationFlow(page, { - tokenLabel: "Font Size", - namePlaceholder: "Enter font size token name", - valuePlaceholder: "Enter a value or alias with {alias}", - invalidValue: "red", - validValue: "2 + 3", - selfReferenceValue: "{my-token}", - missingReferenceValue: "{missing-token}", - secondValidValue: "{my-token} - 2", - resolvedValueText: "Resolved value: 5", - secondResolvedValueText: "Resolved value: 3", - }); - }); - - test("User creates letter spacing token", async ({ page }) => { - await testTokenCreationFlow(page, { - tokenLabel: "Letter spacing", - namePlaceholder: "Enter letter spacing token name", - valuePlaceholder: "Enter a value or alias with {alias}", - invalidValue: "red", - validValue: "2 + 3", - selfReferenceValue: "{my-token}", - missingReferenceValue: "{missing-token}", - secondValidValue: "{my-token} - 2", - resolvedValueText: "Resolved value: 5", - secondResolvedValueText: "Resolved value: 3", - }); - }); - - test("User creates number token", async ({ page }) => { - await testTokenCreationFlow(page, { - tokenLabel: "Number", - namePlaceholder: "Enter number token name", - valuePlaceholder: "Enter a value or alias with {alias}", - invalidValue: "red", - validValue: "2 + 3", - selfReferenceValue: "{my-token}", - missingReferenceValue: "{missing-token}", - secondValidValue: "{my-token} - 2", - resolvedValueText: "Resolved value: 5", - secondResolvedValueText: "Resolved value: 3", - }); - }); - - test("User creates rotation token", async ({ page }) => { - await testTokenCreationFlow(page, { - tokenLabel: "Rotation", - namePlaceholder: "Enter rotation token name", - valuePlaceholder: "Enter a value or alias with {alias}", - invalidValue: "red", - validValue: "2 + 3", - selfReferenceValue: "{my-token}", - missingReferenceValue: "{missing-token}", - secondValidValue: "{my-token} - 2", - resolvedValueText: "Resolved value: 5", - secondResolvedValueText: "Resolved value: 3", - }); - }); - - test("User creates sizing token", async ({ page }) => { - await testTokenCreationFlow(page, { - tokenLabel: "Sizing", - namePlaceholder: "Enter sizing token name", - valuePlaceholder: "Enter a value or alias with {alias}", - invalidValue: "red", - validValue: "2 + 3", - selfReferenceValue: "{my-token}", - missingReferenceValue: "{missing-token}", - secondValidValue: "{my-token} - 2", - resolvedValueText: "Resolved value: 5", - secondResolvedValueText: "Resolved value: 3", - }); - }); - - test("User creates spacing token", async ({ page }) => { - await testTokenCreationFlow(page, { - tokenLabel: "Spacing", - namePlaceholder: "Enter spacing token name", - valuePlaceholder: "Enter a value or alias with {alias}", - invalidValue: "red", - validValue: "2 + 3", - selfReferenceValue: "{my-token}", - missingReferenceValue: "{missing-token}", - secondValidValue: "{my-token} - 2", - resolvedValueText: "Resolved value: 5", - secondResolvedValueText: "Resolved value: 3", - }); - }); - - test("User creates stroke width token", async ({ page }) => { - await testTokenCreationFlow(page, { - tokenLabel: "Stroke width", - namePlaceholder: "Enter stroke width token name", - valuePlaceholder: "Enter a value or alias with {alias}", - invalidValue: "red", - validValue: "2 + 3", - selfReferenceValue: "{my-token}", - missingReferenceValue: "{missing-token}", - secondValidValue: "{my-token} - 2", - resolvedValueText: "Resolved value: 5", - secondResolvedValueText: "Resolved value: 3", - }); - }); - test("User creates color token and auto created set show up in the sidebar", async ({ page, }) => { @@ -569,7 +497,9 @@ test.describe("Tokens - creation", () => { }); await selectDropdown.click(); - const fontOption = tokensUpdateCreateModal.getByRole('img', { name: 'ABeeZee' }) + const fontOption = tokensUpdateCreateModal.getByRole("img", { + name: "ABeeZee", + }); await expect(fontOption).toBeVisible(); await fontOption.click(); @@ -583,7 +513,9 @@ test.describe("Tokens - creation", () => { name: "Search font", }); await searchField.fill("alme"); - const fontOption2 = tokensUpdateCreateModal.getByRole('img', {name: "Almendra Display"}); + const fontOption2 = tokensUpdateCreateModal.getByRole("img", { + name: "Almendra Display", + }); await expect(fontOption2).toBeVisible(); await fontOption2.click(); @@ -1133,7 +1065,7 @@ test.describe("Tokens - creation", () => { // 8. Valid spread → resolved - await blurField.fill("3 - 3"); + await spreadField.fill("3 - 3"); await expect( tokensUpdateCreateModal.getByText("Resolved value: 0"), ).toBeVisible(); @@ -1175,7 +1107,7 @@ test.describe("Tokens - creation", () => { await referenceField.fill("{my-token}"); await expect( tokensUpdateCreateModal.getByText( - "Resolved value: - X: 6 - Y: 10 - Blur: 0 - Spread: 0 ", + "Resolved value: - X: 6 - Y: 10 - Blur: 4 - Spread: 0 ", ), ).toBeVisible(); @@ -1521,7 +1453,9 @@ test.describe("Tokens - creation", () => { }); await selectDropdown.click(); - const fontOption = tokensUpdateCreateModal.getByRole("img", {name: "ABeeZee"}); + const fontOption = tokensUpdateCreateModal.getByRole("img", { + name: "ABeeZee", + }); await expect(fontOption).toBeVisible(); await fontOption.click(); @@ -1536,7 +1470,9 @@ test.describe("Tokens - creation", () => { name: "Search font", }); await searchField.fill("alme"); - const fontOption2 = tokensUpdateCreateModal.getByRole("img", {name: "Almendra Display"}); + const fontOption2 = tokensUpdateCreateModal.getByRole("img", { + name: "Almendra Display", + }); await expect(fontOption2).toBeVisible(); await fontOption2.click(); await expect( @@ -1820,85 +1756,6 @@ test.describe("Tokens - creation", () => { }); }); -test("User creates grouped color token", async ({ page }) => { - const { workspacePage, tokensUpdateCreateModal, tokensSidebar } = - await setupEmptyTokensFileRender(page); - - await tokensSidebar.getByRole("button", { name: "Add Token: Color" }).click(); - - // Create grouped color token with mouse - - await expect(tokensUpdateCreateModal).toBeVisible(); - - const nameField = tokensUpdateCreateModal.getByLabel("Name"); - const valueField = tokensUpdateCreateModal.getByLabel("Value"); - - await nameField.click(); - await nameField.fill("dark.primary"); - - await valueField.click(); - await valueField.fill("red"); - - const submitButton = tokensUpdateCreateModal.getByRole("button", { - name: "Save", - }); - await expect(submitButton).toBeEnabled(); - await submitButton.click(); - - await unfoldTokenType(tokensSidebar, "color"); - - await expect(tokensSidebar.getByLabel("primary")).toBeEnabled(); -}); - -test("User can't create regular token with value missing", async ({ page }) => { - const { tokensUpdateCreateModal } = await setupEmptyTokensFileRender(page); - - const tokensTabPanel = page.getByRole("tabpanel", { name: "tokens" }); - await tokensTabPanel - .getByRole("button", { name: "Add Token: Color" }) - .click(); - - await expect(tokensUpdateCreateModal).toBeVisible(); - - const nameField = tokensUpdateCreateModal.getByLabel("Name"); - const submitButton = tokensUpdateCreateModal.getByRole("button", { - name: "Save", - }); - - // Initially submit button should be disabled - await expect(submitButton).toBeDisabled(); - - // Fill in name but leave value empty - await nameField.click(); - await nameField.fill("primary"); - - // Submit button should remain disabled when value is empty - await expect(submitButton).toBeDisabled(); -}); - -test("User duplicate color token", async ({ page }) => { - const { tokensSidebar, tokenContextMenuForToken } = - await setupTokensFileRender(page); - - await expect(tokensSidebar).toBeVisible(); - - await unfoldTokenType(tokensSidebar, "color"); - - const colorToken = tokensSidebar.getByRole("button", { - name: "colors.blue.100", - }); - - await colorToken.click({ button: "right" }); - await expect(tokenContextMenuForToken).toBeVisible(); - - await tokenContextMenuForToken.getByText("Duplicate token").click(); - await expect(tokenContextMenuForToken).not.toBeVisible(); - - await expect( - tokensSidebar.getByRole("button", { name: "colors.blue.100-copy" }), - ).toBeVisible(); -}); - test("User disables the current set but token still have resolved values shown in the sidebar", async ({ page, }) => { @@ -1932,401 +1789,135 @@ 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 createBadToken = async (page, type, name, textFieldName, value, inputType) => { + const clashError = (name) => + `Group name of ${name} conflicts with a token of the same name in another active set.`; + + const setupClashingFile = async (page) => { + const { tokensUpdateCreateModal, tokenThemesSetsSidebar, tokensSidebar } = + await setupTokensFileRender(page, { + file: "workspace/get-file-tokens-all-types.json", + flags: ["enable-token-shadow"], + }); + + await expect(tokensSidebar).toBeVisible(); + + await createSet(tokenThemesSetsSidebar, "Second set"); + + return { tokensUpdateCreateModal }; + }; + + const openTokenForm = async (page, tokensUpdateCreateModal, type) => { const tokensTabPanel = page.getByRole("tabpanel", { name: "tokens" }); - const { tokensUpdateCreateModal } = await setupTokensFileRender(page, { - // Same file as the outer setup: re-navigating would reload the - // app and wipe the fixture tokens this test clashes against. - file: "workspace/get-file-tokens-all-types.json", - 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(inputType, { - name: textFieldName, - }); - await valueField.fill(value); - - // Check that the value has an error - const errorNode = tokensUpdateCreateModal.getByText( - `Group name of ${name} conflicts with a token of the same name in another active set.`, - ); - - 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", - flags: ["enable-token-shadow"], - }); - - await expect(tokensSidebar).toBeVisible(); - - await createSet(tokenThemesSetsSidebar, "Second set"); - - await createBadToken(page, "Border Radius", "rad1.bad", "Value", "10", "combobox"); - }); - - 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", - flags: ["enable-token-shadow"], - }); - - await expect(tokensSidebar).toBeVisible(); - - await createSet(tokenThemesSetsSidebar, "Second set"); - - await createBadToken(page, "Color", "col1.bad", "Value", "red", "textbox"); - }); - - 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", - flags: ["enable-token-shadow"], - }); - - await expect(tokensSidebar).toBeVisible(); - - await createSet(tokenThemesSetsSidebar, "Second set"); - - await createBadToken(page, "Dimensions", "dim1.bad", "Value", "100", "combobox"); - }); - - 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", - flags: ["enable-token-shadow"], - }); - - await expect(tokensSidebar).toBeVisible(); - - await createSet(tokenThemesSetsSidebar, "Second set"); - - await createBadToken(page, "Font Size", "fsiz1.bad", "Value", "16", "combobox"); - }); - - 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", - flags: ["enable-token-shadow"], - }); - - await expect(tokensSidebar).toBeVisible(); - - await createSet(tokenThemesSetsSidebar, "Second set"); - - await createBadToken(page, "Font Weight", "wei1.bad", "Value", "400", "combobox"); - }); - - 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", - flags: ["enable-token-shadow"], - }); - - await expect(tokensSidebar).toBeVisible(); - - await createSet(tokenThemesSetsSidebar, "Second set"); - - await createBadToken(page, "Letter Spacing", "lspa1.bad", "Value", "1", "combobox"); - }); - - 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", - flags: ["enable-token-shadow"], - }); - - await expect(tokensSidebar).toBeVisible(); - - await createSet(tokenThemesSetsSidebar, "Second set"); - - await createBadToken(page, "Number", "num1.bad", "Value", "10", "combobox"); - }); - - 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", - flags: ["enable-token-shadow"], - }); - - await expect(tokensSidebar).toBeVisible(); - - await createSet(tokenThemesSetsSidebar, "Second set"); - - await createBadToken(page, "Rotation", "rot1.bad", "Value", "90", "combobox"); - }); - - 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", - flags: ["enable-token-shadow"], - }); - - await expect(tokensSidebar).toBeVisible(); - - await createSet(tokenThemesSetsSidebar, "Second set"); - - await createBadToken(page, "Sizing", "siz1.bad", "Value", "100", "combobox"); - }); - - 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", - flags: ["enable-token-shadow"], - }); - - await expect(tokensSidebar).toBeVisible(); - - await createSet(tokenThemesSetsSidebar, "Second set"); - - await createBadToken(page, "Spacing", "spa1.bad", "Value", "10", "combobox"); - }); - - 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", - flags: ["enable-token-shadow"], - }); - - await expect(tokensSidebar).toBeVisible(); - - await createSet(tokenThemesSetsSidebar, "Second set"); - - await createBadToken(page, "Stroke Width", "str1.bad", "Value", "2", "combobox"); - }); - - 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", - flags: ["enable-token-shadow"], - }); - - await expect(tokensSidebar).toBeVisible(); - - await createSet(tokenThemesSetsSidebar, "Second set"); - - await createBadToken(page, "Text Case", "cas1.bad", "Value", "uppercase", "combobox"); - }); - - 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", - flags: ["enable-token-shadow"], - }); - - await expect(tokensSidebar).toBeVisible(); - - await createSet(tokenThemesSetsSidebar, "Second set"); - - await createBadToken( - page, - "Text Decoration", - "dec1.bad", - "Value", - "strike-through", - "combobox" - ); - }); - - 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", - flags: ["enable-token-shadow"], - }); - - await expect(tokensSidebar).toBeVisible(); - - await createSet(tokenThemesSetsSidebar, "Second set"); - - const tokensTabPanel = page.getByRole("tabpanel", { name: "tokens" }); - - const { tokensUpdateCreateModal } = await setupTokensFileRender(page, { - // Same file as the outer setup: re-navigating would reload the - // app and wipe the fixture tokens this test clashes against. - file: "workspace/get-file-tokens-all-types.json", - flags: ["enable-token-shadow"], - }); - - await tokensTabPanel - .getByRole("button", { name: `Add Token: Typography` }) + const closeTokenForm = async (tokensUpdateCreateModal) => { + await tokensUpdateCreateModal + .getByRole("button", { name: "Cancel" }) .click(); - await expect(tokensUpdateCreateModal).toBeVisible(); - const nameField = tokensUpdateCreateModal.getByLabel("Name"); - await nameField.fill("typ1.bad"); + await expect(tokensUpdateCreateModal).not.toBeVisible(); + }; - const fontFamilyField = tokensUpdateCreateModal.getByRole("textbox", { - name: "Font family", - }); - await fontFamilyField.fill("Arial"); + const expectClashError = async (tokensUpdateCreateModal, name) => { + await expect( + tokensUpdateCreateModal.getByText(clashError(name)), + ).toHaveCount(1); - const fontSizeField = tokensUpdateCreateModal.getByRole("textbox", { - name: "Font size", - }); - await fontSizeField.fill("16"); + await expect( + tokensUpdateCreateModal.getByRole("button", { name: "Save" }), + ).toBeDisabled(); + }; - 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( - "Group name of typ1.bad conflicts with a token of the same name in another active set.", - ); - await expect(errorNode).toHaveCount(1); - await expect(submitButton).toBeDisabled(); - }); - - test("User can't create Shadow token with group name that clashes with existing token", async ({ + test("User can't create a simple 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", - flags: ["enable-token-shadow"], + const { tokensUpdateCreateModal } = await setupClashingFile(page); + + const cases = [ + ["Border Radius", "rad1.bad", "Value", "10", "combobox"], + ["Color", "col1.bad", "Value", "red", "textbox"], + ["Dimensions", "dim1.bad", "Value", "100", "combobox"], + ["Font Size", "fsiz1.bad", "Value", "16", "combobox"], + ["Font Weight", "wei1.bad", "Value", "400", "combobox"], + ["Letter Spacing", "lspa1.bad", "Value", "1", "combobox"], + ["Number", "num1.bad", "Value", "10", "combobox"], + ["Rotation", "rot1.bad", "Value", "90", "combobox"], + ["Sizing", "siz1.bad", "Value", "100", "combobox"], + ["Spacing", "spa1.bad", "Value", "10", "combobox"], + ["Stroke Width", "str1.bad", "Value", "2", "combobox"], + ["Text Case", "cas1.bad", "Value", "uppercase", "combobox"], + ["Text Decoration", "dec1.bad", "Value", "strike-through", "combobox"], + ]; + + for (const [type, name, field, value, role] of cases) { + await test.step(type, async () => { + await openTokenForm(page, tokensUpdateCreateModal, type); + await tokensUpdateCreateModal.getByLabel("Name").fill(name); + await tokensUpdateCreateModal + .getByRole(role, { name: field }) + .fill(value); + await expectClashError(tokensUpdateCreateModal, name); + await closeTokenForm(tokensUpdateCreateModal); }); + } + }); - await expect(tokensSidebar).toBeVisible(); + test("User can't create a composite token with group name that clashes with existing token", async ({ + page, + }) => { + const { tokensUpdateCreateModal } = await setupClashingFile(page); - await createSet(tokenThemesSetsSidebar, "Second set"); + await test.step("Typography", async () => { + await openTokenForm(page, tokensUpdateCreateModal, "Typography"); + await tokensUpdateCreateModal.getByLabel("Name").fill("typ1.bad"); - const tokensTabPanel = page.getByRole("tabpanel", { name: "tokens" }); + const fields = { + "Font family": "Arial", + "Font size": "16", + "Font weight": "400", + "Line height": "1.5", + "Letter spacing": "0", + "Text case": "none", + "Text decoration": "none", + }; - const { tokensUpdateCreateModal } = await setupTokensFileRender(page, { - // Same file as the outer setup: re-navigating would reload the - // app and wipe the fixture tokens this test clashes against. - file: "workspace/get-file-tokens-all-types.json", - flags: ["enable-token-shadow"], + for (const [label, value] of Object.entries(fields)) { + await tokensUpdateCreateModal + .getByRole("textbox", { name: label }) + .fill(value); + } + + await expectClashError(tokensUpdateCreateModal, "typ1.bad"); + await closeTokenForm(tokensUpdateCreateModal); }); - await tokensTabPanel - .getByRole("button", { name: `Add Token: Shadow` }) - .click(); - await expect(tokensUpdateCreateModal).toBeVisible(); + await test.step("Shadow", async () => { + await openTokenForm(page, tokensUpdateCreateModal, "Shadow"); + await tokensUpdateCreateModal.getByLabel("Name").fill("sha1.bad"); - const nameField = tokensUpdateCreateModal.getByLabel("Name"); - await nameField.fill("sha1.bad"); + const fields = { + Color: "red", + X: "7", + Y: "8", + Blur: "5", + Spread: "1", + }; - const colorField = tokensUpdateCreateModal.getByRole("textbox", { - name: "Color", + for (const [label, value] of Object.entries(fields)) { + await tokensUpdateCreateModal + .getByRole("textbox", { name: label }) + .fill(value); + } + + await expectClashError(tokensUpdateCreateModal, "sha1.bad"); + await closeTokenForm(tokensUpdateCreateModal); }); - 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( - "Group name of sha1.bad conflicts with a token of the same name in another active set.", - ); - await expect(errorNode).toHaveCount(1); - await expect(submitButton).toBeDisabled(); }); }); diff --git a/frontend/playwright/ui/specs/tokens/helpers.js b/frontend/playwright/ui/specs/tokens/helpers.js index b758696423..e8b0379c07 100644 --- a/frontend/playwright/ui/specs/tokens/helpers.js +++ b/frontend/playwright/ui/specs/tokens/helpers.js @@ -212,130 +212,118 @@ const setupTypographyTokensFileRender = async (page, options = {}) => { }); }; -const testTokenCreationFlow = async ( +const tokenValuePlaceholder = "Enter a value or alias with {alias}"; + +const openTokenCreationForm = async ( page, - { - tokenLabel, - namePlaceholder, - valuePlaceholder, - validValue, - invalidValue, - selfReferenceValue, - missingReferenceValue, - secondValidValue, - resolvedValueText, - secondResolvedValueText, - }, + tokensUpdateCreateModal, + { tokenLabel, namePlaceholder }, ) => { - const invalidValueError = "Invalid token value"; - const emptyNameError = "Name should be at least 1 character"; - const selfReferenceError = "Token has self reference"; - const missingReferenceError = "Missing token references"; - - const { tokensUpdateCreateModal, tokensSidebar } = - await setupEmptyTokensFileRender(page); - - // Open modal const tokensTabPanel = page.getByRole("tabpanel", { name: "tokens" }); - const addTokenButton = tokensTabPanel.getByRole("button", { - name: `Add Token: ${tokenLabel}`, - }); + await tokensTabPanel + .getByRole("button", { name: `Add Token: ${tokenLabel}` }) + .click(); - await addTokenButton.click(); await expect(tokensUpdateCreateModal).toBeVisible(); - // Placeholder checks await expect( tokensUpdateCreateModal.getByPlaceholder(namePlaceholder), ).toBeVisible(); await expect( - tokensUpdateCreateModal.getByPlaceholder(valuePlaceholder), + tokensUpdateCreateModal.getByPlaceholder(tokenValuePlaceholder), ).toBeVisible(); +}; +const assertTokenCreationLadder = async ( + tokensUpdateCreateModal, + { name, invalidValue = "red" }, +) => { const nameField = tokensUpdateCreateModal.getByLabel("Name"); const valueField = tokensUpdateCreateModal.getByLabel("Value"); const submitButton = tokensUpdateCreateModal.getByRole("button", { name: "Save", }); - // 1. Name filled + empty value → disabled - await nameField.fill("my-token"); + // 1. Name filled + empty value -> disabled + await nameField.fill(name); await expect(submitButton).toBeDisabled(); - // 2. Invalid value → disabled + error message + // 2. Invalid value -> disabled + error message await valueField.fill(invalidValue); - - const invalidValueErrorNode = - tokensUpdateCreateModal.getByText(invalidValueError); - - await expect(invalidValueErrorNode).toBeVisible(); + await expect( + tokensUpdateCreateModal.getByText("Invalid token value"), + ).toBeVisible(); await expect(submitButton).toBeDisabled(); - // 3. Empty name → disabled + error message + // 3. Empty name -> disabled + error message await nameField.fill(""); - - const emptyNameErrorNode = tokensUpdateCreateModal.getByText(emptyNameError); - - await expect(emptyNameErrorNode).toBeVisible(); - await expect(submitButton).toBeDisabled(); - - // 4. Self reference → disabled + error message - await nameField.fill("my-token"); - await valueField.fill(selfReferenceValue); - - const selfRefErrorNode = - tokensUpdateCreateModal.getByText(selfReferenceError); - - await expect(selfRefErrorNode).toBeVisible(); - await expect(submitButton).toBeDisabled(); - - // 5. Missing reference → disabled + error message - await valueField.fill(missingReferenceValue); - - const missingRefErrorNode = tokensUpdateCreateModal.getByText( - missingReferenceError, - ); - - await expect(missingRefErrorNode).toBeVisible(); - await expect(submitButton).toBeDisabled(); - - // - // ------- SUCCESSFUL CREATION ------- - // - - // 6. Basic valid value → enabled - await valueField.fill(validValue); await expect( - tokensUpdateCreateModal.getByText(resolvedValueText), + tokensUpdateCreateModal.getByText("Name should be at least 1 character"), ).toBeVisible(); - await expect(submitButton).toBeEnabled(); + await expect(submitButton).toBeDisabled(); + // 4. Self reference -> disabled + error message + await nameField.fill(name); + await valueField.fill(`{${name}}`); + await expect( + tokensUpdateCreateModal.getByText("Token has self reference"), + ).toBeVisible(); + await expect(submitButton).toBeDisabled(); + + // 5. Missing reference -> disabled + error message + await valueField.fill("{missing-token}"); + await expect( + tokensUpdateCreateModal.getByText("Missing token references"), + ).toBeVisible(); + await expect(submitButton).toBeDisabled(); +}; + +const saveToken = async ( + page, + tokensUpdateCreateModal, + { tokenLabel, name, value, resolvedText }, +) => { + const tokensTabPanel = page.getByRole("tabpanel", { name: "tokens" }); + + await tokensUpdateCreateModal.getByLabel("Name").fill(name); + await tokensUpdateCreateModal.getByLabel("Value").fill(value); + + await expect(tokensUpdateCreateModal.getByText(resolvedText)).toBeVisible(); + + const submitButton = tokensUpdateCreateModal.getByRole("button", { + name: "Save", + }); + await expect(submitButton).toBeEnabled(); await submitButton.click(); - await expect( - tokensTabPanel.getByRole("button", { name: "my-token" }), - ).toBeEnabled(); + await unfoldTokenType(tokensTabPanel, tokenLabel.toLocaleLowerCase()); - // - // ------- SECOND TOKEN WITH VALID REFERENCE ------- - // + await expect(tokensTabPanel.getByRole("button", { name })).toBeEnabled(); +}; - await addTokenButton.click(); +const createTokenWithValue = async ( + page, + tokensUpdateCreateModal, + { + tokenLabel, + namePlaceholder, + name, + value = "2 + 3", + resolvedText = "Resolved value: 5", + }, +) => { + await openTokenCreationForm(page, tokensUpdateCreateModal, { + tokenLabel, + namePlaceholder, + }); - await nameField.fill("my-token-2"); - await valueField.fill(secondValidValue); - - await expect( - tokensUpdateCreateModal.getByText(secondResolvedValueText), - ).toBeVisible(); - await expect(submitButton).toBeEnabled(); - - await submitButton.click(); - - await expect( - tokensTabPanel.getByRole("button", { name: "my-token-2" }), - ).toBeEnabled(); + await saveToken(page, tokensUpdateCreateModal, { + tokenLabel, + name, + value, + resolvedText, + }); }; const unfoldTokenType = async (tokensTabPanel, type) => { @@ -427,7 +415,10 @@ export { setupTokensFileRender, setupTypographyTokensFile, setupTypographyTokensFileRender, - testTokenCreationFlow, + openTokenCreationForm, + assertTokenCreationLadder, + saveToken, + createTokenWithValue, unfoldTokenType, createToken, createSet, diff --git a/frontend/playwright/ui/specs/tokens/remapping.spec.js b/frontend/playwright/ui/specs/tokens/remapping.spec.js index 703519e791..99cfb76d46 100644 --- a/frontend/playwright/ui/specs/tokens/remapping.spec.js +++ b/frontend/playwright/ui/specs/tokens/remapping.spec.js @@ -1,11 +1,7 @@ import { test, expect } from "@playwright/test"; import { WorkspacePage } from "../../pages/WorkspacePage"; import { WasmWorkspacePage } from "../../pages/WasmWorkspacePage"; -import { - createToken, - setupTokensFileRender, - setupTypographyTokensFileRender, -} from "./helpers"; +import { createToken, setupTokensFileRender } from "./helpers"; test.beforeEach(async ({ page }) => { await WorkspacePage.init(page); @@ -123,354 +119,60 @@ test.describe("Remapping a single token", () => { "{base-shadow}", ); - // Rename base-shadow token - await renameToken(page, "base-shadow", "foundation-shadow"); - - // Check for remapping modal const remappingModal = page.getByTestId("token-remapping-modal"); - await expect(remappingModal).toBeVisible({ timeout: 5000 }); - await expect(remappingModal).toContainText("base-shadow"); - await expect(remappingModal).toContainText("foundation-shadow"); - const confirmButton = remappingModal.getByRole("button", { - name: "remap tokens", + await test.step("Remap the references", async () => { + await renameToken(page, "base-shadow", "foundation-shadow"); + + await expect(remappingModal).toBeVisible({ timeout: 5000 }); + await expect(remappingModal).toContainText("base-shadow"); + await expect(remappingModal).toContainText("foundation-shadow"); + + await remappingModal + .getByRole("button", { name: "remap tokens" }) + .click(); + + await expect( + tokensSidebar.getByRole("button", { name: "foundation-shadow" }), + ).toBeVisible(); + await expect( + tokensSidebar.getByRole("button", { name: "derived-shadow" }), + ).toBeVisible(); }); - await confirmButton.click(); - // Verify token was renamed - await expect( - tokensSidebar.getByRole("button", { name: "foundation-shadow" }), - ).toBeVisible(); - await expect( - tokensSidebar.getByRole("button", { name: "derived-shadow" }), - ).toBeVisible(); - }); + await test.step("Close the modal and keep the original name", async () => { + await renameToken(page, "foundation-shadow", "shadow-base"); - test("User renames and updates shadow token - referenced token and applied shapes update", async ({ - page, - }) => { - const { - tokensUpdateCreateModal, - tokensSidebar, - tokenContextMenuForToken, - workspacePage, - } = await setupTokensFileRender(page, { flags: ["enable-token-shadow"] }); + await expect(remappingModal).toBeVisible({ timeout: 5000 }); + await remappingModal.getByRole("button", { name: "close" }).click(); - // Create base shadow token - await createToken( - page, - "Shadow", - "primary-shadow", - "Color", - "textbox", - "#000000", - ); - - // Create derived shadow token that references base - await createCompositeDerivedToken( - page, - "Shadow", - "card-shadow", - "{primary-shadow}", - ); - - // Apply the referenced token to a shape - await page.getByRole("tab", { name: "Layers" }).click(); - await workspacePage.layers - .getByTestId("layer-row") - .filter({ hasText: "Button" }) - .click(); - - await page.getByRole("tab", { name: "Tokens" }).click(); - const cardShadowToken = tokensSidebar.getByRole("button", { - name: "card-shadow", + await expect( + tokensSidebar.getByRole("button", { name: "foundation-shadow" }), + ).toBeVisible(); + await expect( + tokensSidebar.getByRole("button", { name: "derived-shadow" }), + ).toBeVisible(); }); - await cardShadowToken.click(); - // Rename and update value of base token - const primaryToken = tokensSidebar.getByRole("button", { - name: "primary-shadow", + await test.step("Do not remap and break the reference", async () => { + await renameToken(page, "foundation-shadow", "shadow-base"); + + await expect(remappingModal).toBeVisible({ timeout: 5000 }); + await remappingModal + .getByRole("button", { name: "don't remap" }) + .click(); + + await expect( + tokensSidebar.getByRole("button", { name: "shadow-base" }), + ).toBeVisible(); + await expect( + tokensSidebar.locator('[aria-label="Missing reference"]'), + ).toBeVisible(); }); - await primaryToken.click({ button: "right" }); - await tokenContextMenuForToken.getByText("Edit token").click(); - - await expect(tokensUpdateCreateModal).toBeVisible(); - const nameField = tokensUpdateCreateModal.getByLabel("Name"); - await nameField.fill("main-shadow"); - - // Update the color value - const colorField = tokensUpdateCreateModal.getByRole("textbox", { - name: "Color", - }); - await colorField.fill("#FF0000"); - - const submitButton = tokensUpdateCreateModal.getByRole("button", { - name: "Save", - }); - await submitButton.click(); - - // Confirm remapping - const remappingModal = page.getByTestId("token-remapping-modal"); - await expect(remappingModal).toBeVisible({ timeout: 5000 }); - - const confirmButton = remappingModal.getByRole("button", { - name: "remap tokens", - }); - await confirmButton.click(); - - // Verify base token was renamed - await expect( - tokensSidebar.getByRole("button", { name: "main-shadow" }), - ).toBeVisible(); - - // Verify referenced token still exists - await expect( - tokensSidebar.getByRole("button", { name: "card-shadow" }), - ).toBeVisible(); - - // Verify the shape still has the token applied with the NEW name - await page.getByRole("tab", { name: "Layers" }).click(); - await workspacePage.layers - .getByTestId("layer-row") - .filter({ hasText: "Button" }) - .click(); - - // Verify the shape still has the shadow applied with the UPDATED color value - // Expand the shadow section to access the color field - const shadowSection = - workspacePage.rightSidebar.getByTestId("shadow-section"); - await expect(shadowSection).toBeVisible(); - - // Click to expand the shadow options (the menu button) - const shadowMenuButton = shadowSection - .getByRole("button", { name: "options" }) - .first(); - await shadowMenuButton.click(); - - // Wait for the advanced options to appear - await page.waitForTimeout(500); - - // Verify the color value has updated from #000000 to #FF0000 - const colorInput = shadowSection.getByRole("textbox", { name: "Color" }); - expect(colorInput).not.toBeNull(); - const colorValue = await colorInput.inputValue(); - expect(colorValue.toUpperCase()).toBe("FF0000"); - }); - }); - - test.describe("Typography Token Remapping", () => { - test("User renames typography token with alias references", async ({ - page, - }) => { - const { - tokensUpdateCreateModal, - tokensSidebar, - tokenContextMenuForToken, - } = await setupTypographyTokensFileRender(page); - - const tokensTabPanel = page.getByRole("tabpanel", { name: "tokens" }); - - // Create base typography token - await createToken( - page, - "Typography", - "base-text", - "Font size", - "textbox", - "16", - ); - - // Create derived typography token - await createCompositeDerivedToken( - page, - "Typography", - "body-text", - "{base-text}", - ); - - // Rename base token - await renameToken(page, "base-text", "default-text"); - - // Check for remapping modal - const remappingModal = page.getByTestId("token-remapping-modal"); - await expect(remappingModal).toBeVisible({ timeout: 5000 }); - - const confirmButton = remappingModal.getByRole("button", { - name: "remap tokens", - }); - await confirmButton.click(); - - // Verify token was renamed - await expect( - tokensSidebar.getByRole("button", { name: "default-text" }), - ).toBeVisible(); - await expect( - tokensSidebar.getByRole("button", { name: "body-text" }), - ).toBeVisible(); - }); - - test("User renames and updates typography token - referenced token and applied shapes update", async ({ - page, - }) => { - const { - tokensUpdateCreateModal, - tokensSidebar, - tokenContextMenuForToken, - workspacePage, - } = await setupTypographyTokensFileRender(page); - - const tokensTabPanel = page.getByRole("tabpanel", { name: "tokens" }); - - // Create base typography token - await createToken( - page, - "Typography", - "body-style", - "Font size", - "textbox", - "16", - ); - - // Create derived typography token - await tokensTabPanel - .getByRole("button", { name: "Add Token: Typography" }) - .click(); - await expect(tokensUpdateCreateModal).toBeVisible(); - - let nameField = tokensUpdateCreateModal.getByRole("textbox", { - name: "Name", - }); - await nameField.fill("paragraph-style"); - - const referenceToggle = - tokensUpdateCreateModal.getByTestId("reference-opt"); - await referenceToggle.click(); - - const referenceField = tokensUpdateCreateModal.getByRole("textbox", { - name: "Reference", - }); - await referenceField.fill("{body-style}"); - - let submitButton = tokensUpdateCreateModal.getByRole("button", { - name: "Save", - }); - await submitButton.click(); - await expect(tokensUpdateCreateModal).not.toBeVisible(); - - // Apply the referenced token to a text shape - await page.getByRole("tab", { name: "Layers" }).click(); - await workspacePage.layers - .getByTestId("layer-row") - .filter({ hasText: "Some Text" }) - .click(); - - await page.getByRole("tab", { name: "Tokens" }).click(); - const paragraphToken = tokensSidebar.getByRole("button", { - name: "paragraph-style", - }); - await paragraphToken.click(); - - // Rename and update value of base token - const bodyToken = tokensSidebar.getByRole("button", { - name: "body-style", - }); - await bodyToken.click({ button: "right" }); - await tokenContextMenuForToken.getByText("Edit token").click(); - - await expect(tokensUpdateCreateModal).toBeVisible(); - nameField = tokensUpdateCreateModal.getByLabel("Name"); - await nameField.fill("text-base"); - - // Update the font size value - const fontSizeField = tokensUpdateCreateModal.getByRole("textbox", { - name: "Font size", - }); - await fontSizeField.fill("18"); - - submitButton = tokensUpdateCreateModal.getByRole("button", { - name: "Save", - }); - await submitButton.click(); - - // Confirm remapping - const remappingModal = page.getByTestId("token-remapping-modal"); - await expect(remappingModal).toBeVisible({ timeout: 5000 }); - - const confirmButton = remappingModal.getByRole("button", { - name: "remap tokens", - }); - await confirmButton.click(); - - // Verify base token was renamed - await expect( - tokensSidebar.getByRole("button", { name: "text-base" }), - ).toBeVisible(); - - // Verify referenced token still exists - await expect( - tokensSidebar.getByRole("button", { name: "paragraph-style" }), - ).toBeVisible(); - - // Verify the text shape still has the token applied with NEW name and value - await page.getByRole("tab", { name: "Layers" }).click(); - await workspacePage.layers - .getByTestId("layer-row") - .filter({ hasText: "Some Text" }) - .click(); - - // This proves the remapping worked and the value update propagated through the reference - const tokenPillSidebar = workspacePage.rightSidebar.getByLabel('paragraph-style') - await expect(tokenPillSidebar).toBeVisible(); }); }); test.describe("Border Radius Token Remapping", () => { - test("User renames border radius token with alias references", async ({ - page, - }) => { - const { tokensSidebar } = await setupTokensFileRender(page, { - flags: ["enable-token-combobox", "enable-feature-token-input"], - }); - - // Create base border radius token - await createTokenCombobox( - page, - "Border Radius", - "base-radius", - "Value", - "4", - ); - - // Create derived border radius token - await createTokenCombobox( - page, - "Border Radius", - "card-radius", - "Value", - "{base-radius}", - ); - - // Rename base token - await renameToken(page, "base-radius", "primary-radius"); - - // Check for remapping modal - const remappingModal = page.getByTestId("token-remapping-modal"); - await expect(remappingModal).toBeVisible({ timeout: 5000 }); - - const confirmButton = remappingModal.getByRole("button", { - name: "remap tokens", - }); - await confirmButton.click(); - - // Verify token was renamed - await expect( - tokensSidebar.getByRole("button", { name: "primary-radius" }), - ).toBeVisible(); - await expect( - tokensSidebar.getByRole("button", { name: "card-radius" }), - ).toBeVisible(); - }); - test("User renames and updates border radius token - referenced token updates", async ({ page, }) => { @@ -552,98 +254,6 @@ test.describe("Remapping a single token", () => { await expect(currentValue).toHaveValue("{radius-base}"); }); }); - - test.describe("Cancel remap", () => { - test("Only rename - breaks reference", async ({ page }) => { - const { tokensSidebar } = await setupTokensFileRender(page, { - flags: ["enable-token-shadow"], - }); - - // Create base shadow token - await createToken( - page, - "Shadow", - "base-shadow", - "Color", - "textbox", - "#000000", - ); - - // Create derived shadow token that references base-shadow - await createCompositeDerivedToken( - page, - "Shadow", - "derived-shadow", - "{base-shadow}", - ); - - // Rename base-shadow token - await renameToken(page, "base-shadow", "foundation-shadow"); - - // Check for remapping modal - const remappingModal = page.getByTestId("token-remapping-modal"); - await expect(remappingModal).toBeVisible({ timeout: 5000 }); - - const cancelButton = remappingModal.getByRole("button", { - name: "don't remap", - }); - await cancelButton.click(); - - // Verify token was renamed - await expect( - tokensSidebar.getByRole("button", { - name: "foundation-shadow", - }), - ).toBeVisible(); - await expect( - tokensSidebar.locator('[aria-label="Missing reference"]'), - ).toBeVisible(); - }); - - test("Cancel process - no changes applied", async ({ page }) => { - const { tokensSidebar } = await setupTokensFileRender(page, { - flags: ["enable-token-shadow"], - }); - - // Create base shadow token - await createToken( - page, - "Shadow", - "base-shadow", - "Color", - "textbox", - "#000000", - ); - - // Create derived shadow token that references base-shadow - await createCompositeDerivedToken( - page, - "Shadow", - "derived-shadow", - "{base-shadow}", - ); - - // Rename base-shadow token - await renameToken(page, "base-shadow", "foundation-shadow"); - - // Check for remapping modal - const remappingModal = page.getByTestId("token-remapping-modal"); - await expect(remappingModal).toBeVisible({ timeout: 5000 }); - - const closeButton = remappingModal.getByRole("button", { - name: "close", - }); - await closeButton.click(); - - // Verify original token name still exists - await expect( - tokensSidebar.getByRole("button", { name: "base-shadow" }), - ).toBeVisible(); - await expect( - tokensSidebar.getByRole("button", { name: "derived-shadow" }), - ).toBeVisible(); - }); - }); }); test.describe("Remapping group of tokens", () => { @@ -750,107 +360,4 @@ test.describe("Remapping group of tokens", () => { fillSection.getByLabel("brand.primary", { exact: true }), ).toBeVisible(); }); - - test("User renames a group - and remaps", async ({ page }) => { - const { tokensSidebar } = await setupTokensFileRender(page); - const workspacePage = new WasmWorkspacePage(page); - const rightSidebar = workspacePage.rightSidebar; - - // Create multiple tokens in a group - await createToken( - page, - "Color", - "light.primary", - "Value", - "textbox", - "#FFFFFF", - ); - await createToken( - page, - "Color", - "light.secondary", - "Value", - "textbox", - "#EEEEEE", - ); - - // Verify that the node and child token are visible before deletion - const lightNode = tokensSidebar.getByRole("button", { - name: "light", - exact: true, - }); - const lightNodeToken = tokensSidebar.getByRole("button", { - name: "primary", - }); - - // Select a node and right click on it to open context menu - await expect(lightNode).toBeVisible(); - await expect(lightNodeToken).toBeVisible(); - - // Apply token to a shape to ensure remapping modal appears with applied token reference - await page.getByRole("tab", { name: "Layers" }).click(); - await page - .getByTestId("layer-row") - .filter({ hasText: "Rectangle" }) - .first() - .click(); - - await page.getByRole("tab", { name: "Tokens" }).click(); - const lightPrimaryToken = tokensSidebar.getByRole("button", { - name: "primary", - }); - await lightPrimaryToken.click(); - - // Right click on the node to rename - - await lightNode.click({ button: "right" }); - const renameNodeButton = page.getByRole("button", { - name: "Rename", - exact: true, - }); - await expect(renameNodeButton).toBeVisible(); - await renameNodeButton.click(); - - // Expect the rename modal to be visible, fill in the new name and submit - const tokenRenameNodeModal = page.getByTestId("token-rename-node-modal"); - await expect(tokenRenameNodeModal).toBeVisible(); - - const nameField = tokenRenameNodeModal.getByRole("textbox", { - name: "Name", - }); - await nameField.fill("lighter"); - - const submitButton = tokenRenameNodeModal.getByRole("button", { - name: "Rename", - }); - await submitButton.click(); - - // Ensure that the remapping modal appears and confirm remap - const remappingModal = page.getByTestId("token-remapping-modal"); - await expect(remappingModal).toBeVisible({ timeout: 5000 }); - - const confirmButton = remappingModal.getByRole("button", { - name: "remap tokens", - }); - await confirmButton.click(); - - // Verify that the node has been renamed and tokens are still visible - const lighterNode = tokensSidebar.getByRole("button", { - name: "lighter", - exact: true, - }); - - await expect(lighterNode).toBeVisible(); - - // Verify that the applied token reference has been updated in the right sidebar for the selected shape - const fillSection = rightSidebar.getByRole("region", { - name: "Fill section", - }); - await expect(fillSection).toBeVisible(); - - const tokenReference = fillSection.getByLabel("lighter.primary", { - exact: true, - }); - await expect(tokenReference).toBeVisible(); - }); });