import { test, expect } from "@playwright/test"; import { WasmWorkspacePage } from "../../pages/WasmWorkspacePage"; import { BaseWebSocketPage } from "../../pages/BaseWebSocketPage"; import { setupEmptyTokensFileRender, setupTokensFileRender, setupTypographyTokensFileRender, testTokenCreationFlow, unfoldTokenType, createToken, createSet, } from "./helpers"; test.beforeEach(async ({ page }) => { await WasmWorkspacePage.init(page); await BaseWebSocketPage.mockRPC(page, "get-teams", "get-teams-tokens.json"); }); 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 border radius token with combobox", async ({ page }) => { const { tokensUpdateCreateModal } = await setupEmptyTokensFileRender(page, { flags: ["enable-token-combobox", "enable-feature-token-input"], }); // Open modal const tokensTabPanel = page.getByRole("tabpanel", { name: "tokens" }); const addTokenButton = tokensTabPanel.getByRole("button", { name: `Add Token: Border Radius`, }); await addTokenButton.click(); await expect(tokensUpdateCreateModal).toBeVisible(); // Placeholder checks await expect( tokensUpdateCreateModal.getByPlaceholder( "Enter border radius token name", ), ).toBeVisible(); await expect( tokensUpdateCreateModal.getByPlaceholder( "Enter a value or alias with {alias}", ), ).toBeVisible(); // Elements const nameField = tokensUpdateCreateModal.getByLabel("Name"); const valueField = tokensUpdateCreateModal.getByRole("combobox", { name: "Value", }); const submitButton = tokensUpdateCreateModal.getByRole("button", { name: "Save", }); // Create first token await nameField.fill("my-token"); await valueField.fill("1 + 2"); await expect( tokensUpdateCreateModal.getByText("Resolved value: 3"), ).toBeVisible(); await expect(submitButton).toBeEnabled(); await submitButton.click(); await unfoldTokenType(tokensTabPanel, "border radius"); await expect( tokensTabPanel.getByRole("button", { name: "my-token" }), ).toBeEnabled(); // Create second token referencing the first one using the combobox options await addTokenButton.click(); await nameField.fill("my-token-2"); const toggleDropdownButton = tokensUpdateCreateModal.getByRole("button", { name: "Open token list", }); await toggleDropdownButton.click(); const option = page.getByRole("option", { name: "my-token" }); await expect(option).toBeVisible(); const resolvedValue = option.getByText("3"); await expect(resolvedValue).toBeVisible(); await option.click(); await expect( tokensUpdateCreateModal.getByText("Resolved value: 3"), ).toBeVisible(); await valueField.pressSequentially(" + 2"); await expect( tokensUpdateCreateModal.getByText("Resolved value: 5"), ).toBeVisible(); await valueField.pressSequentially(" + {"); await option.click(); await expect( tokensUpdateCreateModal.getByText("Resolved value: 8"), ).toBeVisible(); }); test("User creates a token referencing another via combobox, replacing existing text", async ({ page, }) => { const { tokensUpdateCreateModal } = await setupEmptyTokensFileRender(page, { flags: ["enable-token-combobox", "enable-feature-token-input"], }); const tokensTabPanel = page.getByRole("tabpanel", { name: "tokens" }); const addTokenButton = tokensTabPanel.getByRole("button", { name: "Add Token: Border Radius", }); await addTokenButton.click(); await expect(tokensUpdateCreateModal).toBeVisible(); const nameField = tokensUpdateCreateModal.getByLabel("Name"); const valueField = tokensUpdateCreateModal.getByRole("combobox", { name: "Value", }); const submitButton = tokensUpdateCreateModal.getByRole("button", { name: "Save", }); // Create first token await nameField.fill("my-token"); await valueField.fill("1 + 2"); await expect( tokensUpdateCreateModal.getByText("Resolved value: 3"), ).toBeVisible(); await expect(submitButton).toBeEnabled(); await submitButton.click(); await unfoldTokenType(tokensTabPanel, "border radius"); await expect( tokensTabPanel.getByRole("button", { name: "my-token" }), ).toBeEnabled(); // Create second token — type text first, then replace it via combobox await addTokenButton.click(); await nameField.fill("my-token-2"); await valueField.fill("4 + 4"); await expect( tokensUpdateCreateModal.getByText("Resolved value: 8"), ).toBeVisible(); const toggleDropdownButton = tokensUpdateCreateModal.getByRole("button", { name: "Open token list", }); await toggleDropdownButton.click(); const option = page.getByRole("option", { name: "my-token" }); await expect(option).toBeVisible(); await option.click(); // The typed text should have been replaced by {my-token} await expect(valueField).toHaveValue("{my-token}"); await expect( tokensUpdateCreateModal.getByText("Resolved value: 3"), ).toBeVisible(); // Original text must no longer be present await expect(valueField).not.toHaveValue("some text to replace"); await expect(submitButton).toBeEnabled(); await submitButton.click(); await unfoldTokenType(tokensTabPanel, "border radius"); await expect( tokensTabPanel.getByRole("button", { name: "my-token-2" }), ).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, }) => { const invalidValueError = "Invalid color value"; const emptyNameError = "Name should be at least 1 character"; const selfReferenceError = "Token has self reference"; const missingReferenceError = "Missing token references"; const { tokensUpdateCreateModal, tokenThemesSetsSidebar, tokensSidebar } = await setupEmptyTokensFileRender(page); await tokensSidebar .getByRole("button", { name: "Add Token: Color" }) .click(); await expect(tokensUpdateCreateModal).toBeVisible(); // Placeholder checks await expect( tokensUpdateCreateModal.getByPlaceholder("Enter color token name"), ).toBeVisible(); await expect( tokensUpdateCreateModal.getByPlaceholder( "Enter a value or alias with {alias}", ), ).toBeVisible(); 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.click(); await nameField.fill("color.primary"); await expect(submitButton).toBeDisabled(); // 2. Invalid value → disabled + error message await valueField.fill("1"); const invalidValueErrorNode = tokensUpdateCreateModal.getByText(invalidValueError); await expect(invalidValueErrorNode).toBeVisible(); await expect(submitButton).toBeDisabled(); // 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("color.primary"); await valueField.fill("{color.primary}"); const selfRefErrorNode = tokensUpdateCreateModal.getByText(selfReferenceError); await expect(selfRefErrorNode).toBeVisible(); await expect(submitButton).toBeDisabled(); // 5. Missing reference → disabled + error message await valueField.fill("{missing-reference}"); const missingRefErrorNode = tokensUpdateCreateModal.getByText( missingReferenceError, ); await expect(missingRefErrorNode).toBeVisible(); await expect(submitButton).toBeDisabled(); // valid value await valueField.fill("red"); await expect(submitButton).toBeEnabled(); await submitButton.click(); await unfoldTokenType(tokensSidebar, "color"); // Create token referencing the previous one with keyboard await tokensSidebar .getByRole("button", { name: "Add Token: Color" }) .click(); await expect(tokensUpdateCreateModal).toBeVisible(); await nameField.click(); await nameField.fill("secondary"); await nameField.press("Tab"); await valueField.click(); await valueField.fill("{color.primary}"); await expect(submitButton).toBeEnabled(); await submitButton.press("Enter"); await expect( tokensSidebar.getByRole("button", { name: "secondary", }), ).toBeEnabled(); // Tokens tab panel should have two tokens with the color red / #ff0000 await expect( tokensSidebar.getByRole("button", { name: "#ff0000" }), ).toHaveCount(2); // Global set has been auto created and is active await expect( tokenThemesSetsSidebar.getByRole("button", { name: "Global", }), ).toHaveCount(1); await expect( tokenThemesSetsSidebar.getByRole("button", { name: "Global", }), ).toHaveAttribute("aria-checked", "true"); // Check color picker await tokensSidebar .getByRole("button", { name: "Add Token: Color" }) .click(); await expect(tokensUpdateCreateModal).toBeVisible(); await nameField.click(); await nameField.fill("color.tertiary"); await nameField.press("Tab"); const colorSwatch = tokensUpdateCreateModal.getByTestId( "token-form-color-bullet", ); await colorSwatch.click(); const rampSelector = tokensUpdateCreateModal.getByTestId( "value-saturation-selector", ); await expect(rampSelector).toBeVisible(); await rampSelector.click({ position: { x: 50, y: 50 } }); await expect( tokensUpdateCreateModal.getByText("Resolved value:"), ).toBeVisible(); await expect(submitButton).toBeEnabled(); // Check color with opacity const sliderOpacity = tokensUpdateCreateModal.getByTestId("slider-opacity"); await sliderOpacity.click({ position: { x: 50, y: 0 } }); await expect( tokensUpdateCreateModal.getByText("Resolved value: rgba("), ).toBeVisible(); await expect(submitButton).toBeEnabled(); // Check hslv await colorSwatch.click(); await expect(rampSelector).not.toBeVisible(); await valueField.fill("hsv(1,1,1)"); await expect( tokensUpdateCreateModal.getByText("Resolved value: #ff0400"), ).toBeVisible(); await expect(submitButton).toBeEnabled(); }); test("User creates a font family token", async ({ page }) => { const emptyNameError = "Name should be at least 1 character"; const selfReferenceError = "Token has self reference"; const missingReferenceError = "Missing token references"; const { tokensUpdateCreateModal, tokenThemesSetsSidebar } = await setupEmptyTokensFileRender(page); // Open modal const tokensTabPanel = page.getByRole("tabpanel", { name: "tokens" }); const addTokenButton = tokensTabPanel.getByRole("button", { name: `Add Token: Font Family`, }); await addTokenButton.click(); await expect(tokensUpdateCreateModal).toBeVisible(); // Placeholder checks await expect( tokensUpdateCreateModal.getByPlaceholder("Enter font family token name"), ).toBeVisible(); await expect( tokensUpdateCreateModal.getByPlaceholder( "Enter a value or alias with {alias}", ), ).toBeVisible(); 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"); await expect(submitButton).toBeDisabled(); // 2. 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("{my-token}"); const selfRefErrorNode = tokensUpdateCreateModal.getByText(selfReferenceError); await expect(selfRefErrorNode).toBeVisible(); await expect(submitButton).toBeDisabled(); // 5. Missing reference → disabled + error message await valueField.fill("{missing-token}"); const missingRefErrorNode = tokensUpdateCreateModal.getByText( missingReferenceError, ); await expect(missingRefErrorNode).toBeVisible(); await expect(submitButton).toBeDisabled(); // // ------- SUCCESSFUL CREATION ------- // // 6. Basic valid value → enabled await valueField.fill("Times new roman"); await expect( tokensUpdateCreateModal.getByText("Resolved value: Times new roman"), ).toBeVisible(); await expect(submitButton).toBeEnabled(); await submitButton.click(); await unfoldTokenType(tokensTabPanel, "font family"); await expect( tokensTabPanel.getByRole("button", { name: "my-token" }), ).toBeEnabled(); // // ------- SECOND TOKEN DROPDOWN OPTION ------- // await addTokenButton.click(); await nameField.fill("my-token-2"); const selectDropdown = tokensUpdateCreateModal.getByRole("button", { name: "Select font family", }); await selectDropdown.click(); const fontOption = tokensUpdateCreateModal.getByText("ABeeZee"); await expect(fontOption).toBeVisible(); await fontOption.click(); await expect( tokensUpdateCreateModal.getByText("Resolved value: ABeeZee"), ).toBeVisible(); await selectDropdown.click(); const searchField = tokensUpdateCreateModal.getByRole("textbox", { name: "Search font", }); await searchField.fill("alme"); const fontOption2 = tokensUpdateCreateModal.getByText("Almendra Display"); await expect(fontOption2).toBeVisible(); await fontOption2.click(); await expect( tokensUpdateCreateModal.getByText("Resolved value: Almendra Display"), ).toBeVisible(); await expect(submitButton).toBeEnabled(); await submitButton.click(); await expect( tokensTabPanel.getByRole("button", { name: "my-token-2" }), ).toBeEnabled(); // // ------- THIRD TOKEN REFERENCE OPTION ------- // await addTokenButton.click(); await nameField.fill("my-token-3"); await valueField.fill("{my-token}"); await expect(submitButton).toBeEnabled(); await submitButton.click(); await expect( tokensTabPanel.getByRole("button", { name: "my-token-3" }), ).toBeEnabled(); }); test("User creates font weight token", async ({ page }) => { const invalidValueError = "Invalid font weight value: use numeric values (100-950) or standard names (thin, light, regular, bold, etc.) optionally followed by 'Italic'"; const emptyNameError = "Name should be at least 1 character"; const selfReferenceError = "Token has self reference"; const missingReferenceError = "Missing token references"; const { tokensUpdateCreateModal, tokenThemesSetsSidebar } = await setupEmptyTokensFileRender(page); // Open modal const tokensTabPanel = page.getByRole("tabpanel", { name: "tokens" }); const addTokenButton = tokensTabPanel.getByRole("button", { name: `Add Token: Font Weight`, }); await addTokenButton.click(); await expect(tokensUpdateCreateModal).toBeVisible(); // Placeholder checks await expect( tokensUpdateCreateModal.getByPlaceholder("Enter font weight token name"), ).toBeVisible(); await expect( tokensUpdateCreateModal.getByPlaceholder( "Font weight (300, Bold Italic...) or an {alias}", ), ).toBeVisible(); 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"); await expect(submitButton).toBeDisabled(); // 2. Invalid value → disabled + error message await valueField.fill("red"); const invalidValueErrorNode = tokensUpdateCreateModal.getByText(invalidValueError); await expect(invalidValueErrorNode).toBeVisible(); await expect(submitButton).toBeDisabled(); // 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("{my-token}"); const selfRefErrorNode = tokensUpdateCreateModal.getByText(selfReferenceError); await expect(selfRefErrorNode).toBeVisible(); await expect(submitButton).toBeDisabled(); // 5. Missing reference → disabled + error message await valueField.fill("{missing-reference}"); const missingRefErrorNode = tokensUpdateCreateModal.getByText( missingReferenceError, ); await expect(missingRefErrorNode).toBeVisible(); await expect(submitButton).toBeDisabled(); // // ------- SUCCESSFUL CREATION ------- // // 6. Basic valid value → enabled await valueField.fill("300"); await expect( tokensUpdateCreateModal.getByText("Resolved value: 300"), ).toBeVisible(); await expect(submitButton).toBeEnabled(); await submitButton.click(); await unfoldTokenType(tokensTabPanel, "font weight"); await expect( tokensTabPanel.getByRole("button", { name: "my-token" }), ).toBeEnabled(); // // ------- SECOND TOKEN WITH VALID REFERENCE ------- // await addTokenButton.click(); await nameField.fill("my-token-2"); await valueField.fill("{my-token} + 200"); await expect( tokensUpdateCreateModal.getByText("Resolved value: 500"), ).toBeVisible(); await expect(submitButton).toBeEnabled(); await submitButton.click(); await expect( tokensTabPanel.getByRole("button", { name: "my-token-2" }), ).toBeEnabled(); // // ------- THIRD TOKEN WITH NAMED FONT WEIGHT ------- // await addTokenButton.click(); await nameField.fill("my-token-3"); await valueField.fill("bold"); await expect( tokensUpdateCreateModal.getByText("Resolved value: bold"), ).toBeVisible(); await expect(submitButton).toBeEnabled(); await submitButton.click(); await expect( tokensTabPanel.getByRole("button", { name: "my-token-3" }), ).toBeEnabled(); }); test("User creates text case token", async ({ page }) => { const invalidValueError = "Invalid token value: only none, Uppercase, Lowercase or Capitalize are accepted"; const emptyNameError = "Name should be at least 1 character"; const selfReferenceError = "Token has self reference"; const missingReferenceError = "Missing token references"; const { tokensUpdateCreateModal, tokenThemesSetsSidebar } = await setupEmptyTokensFileRender(page); // Open modal const tokensTabPanel = page.getByRole("tabpanel", { name: "tokens" }); const addTokenButton = tokensTabPanel.getByRole("button", { name: `Add Token: Text Case`, }); await addTokenButton.click(); await expect(tokensUpdateCreateModal).toBeVisible(); // Placeholder checks await expect( tokensUpdateCreateModal.getByPlaceholder("Enter text case token name"), ).toBeVisible(); await expect( tokensUpdateCreateModal.getByPlaceholder( "none | uppercase | lowercase | capitalize or {alias}", ), ).toBeVisible(); 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"); await expect(submitButton).toBeDisabled(); // 2. Invalid value → disabled + error message await valueField.fill("red"); const invalidValueErrorNode = tokensUpdateCreateModal.getByText(invalidValueError); await expect(invalidValueErrorNode).toBeVisible(); await expect(submitButton).toBeDisabled(); // 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("{my-token}"); const selfRefErrorNode = tokensUpdateCreateModal.getByText(selfReferenceError); await expect(selfRefErrorNode).toBeVisible(); await expect(submitButton).toBeDisabled(); // 5. Missing reference → disabled + error message await valueField.fill("{missing-reference}"); const missingRefErrorNode = tokensUpdateCreateModal.getByText( missingReferenceError, ); await expect(missingRefErrorNode).toBeVisible(); await expect(submitButton).toBeDisabled(); // // ------- SUCCESSFUL CREATION ------- // // 6. Basic valid value → enabled await valueField.fill("uppercase"); await expect( tokensUpdateCreateModal.getByText("Resolved value: uppercase"), ).toBeVisible(); await expect(submitButton).toBeEnabled(); await submitButton.click(); await unfoldTokenType(tokensTabPanel, "text case"); await expect( tokensTabPanel.getByRole("button", { name: "my-token" }), ).toBeEnabled(); // // ------- SECOND TOKEN WITH VALID REFERENCE ------- // await addTokenButton.click(); await nameField.fill("my-token-2"); await valueField.fill("{my-token}"); await expect( tokensUpdateCreateModal.getByText("Resolved value: uppercase"), ).toBeVisible(); await expect(submitButton).toBeEnabled(); await submitButton.click(); await expect( tokensTabPanel.getByRole("button", { name: "my-token-2" }), ).toBeEnabled(); }); test("User creates text decoration token", async ({ page }) => { const invalidValueError = "Invalid token value: only none, underline and strike-through are accepted"; const emptyNameError = "Name should be at least 1 character"; const selfReferenceError = "Token has self reference"; const missingReferenceError = "Missing token references"; const { tokensUpdateCreateModal, tokenThemesSetsSidebar } = await setupEmptyTokensFileRender(page); // Open modal const tokensTabPanel = page.getByRole("tabpanel", { name: "tokens" }); const addTokenButton = tokensTabPanel.getByRole("button", { name: `Add Token: Text Decoration`, }); await addTokenButton.click(); await expect(tokensUpdateCreateModal).toBeVisible(); // Placeholder checks await expect( tokensUpdateCreateModal.getByPlaceholder( "Enter text decoration token name", ), ).toBeVisible(); await expect( tokensUpdateCreateModal.getByPlaceholder( "none | underline | strike-through or {alias}", ), ).toBeVisible(); 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"); await expect(submitButton).toBeDisabled(); // 2. Invalid value → disabled + error message await valueField.fill("red"); const invalidValueErrorNode = tokensUpdateCreateModal.getByText(invalidValueError); await expect(invalidValueErrorNode).toBeVisible(); await expect(submitButton).toBeDisabled(); // 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("{my-token}"); const selfRefErrorNode = tokensUpdateCreateModal.getByText(selfReferenceError); await expect(selfRefErrorNode).toBeVisible(); await expect(submitButton).toBeDisabled(); // 5. Missing reference → disabled + error message await valueField.fill("{missing-reference}"); const missingRefErrorNode = tokensUpdateCreateModal.getByText( missingReferenceError, ); await expect(missingRefErrorNode).toBeVisible(); await expect(submitButton).toBeDisabled(); // // ------- SUCCESSFUL CREATION ------- // // 6. Basic valid value → enabled await valueField.fill("none"); await expect( tokensUpdateCreateModal.getByText("Resolved value: none"), ).toBeVisible(); await expect(submitButton).toBeEnabled(); await submitButton.click(); await unfoldTokenType(tokensTabPanel, "text decoration"); await expect( tokensTabPanel.getByRole("button", { name: "my-token" }), ).toBeEnabled(); // // ------- SECOND TOKEN WITH VALID REFERENCE ------- // await addTokenButton.click(); await nameField.fill("my-token-2"); await valueField.fill("{my-token}"); await expect( tokensUpdateCreateModal.getByText("Resolved value: none"), ).toBeVisible(); await expect(submitButton).toBeEnabled(); await submitButton.click(); await expect( tokensTabPanel.getByRole("button", { name: "my-token-2" }), ).toBeEnabled(); }); test("User creates shadow token", async ({ page }) => { const emptyNameError = "Name should be at least 1 character"; const emptyFieldError = "This field cannot be empty"; const { tokensUpdateCreateModal, tokenThemesSetsSidebar } = await setupEmptyTokensFileRender(page, { flags: ["enable-token-shadow"], }); // Open modal const tokensTabPanel = page.getByRole("tabpanel", { name: "tokens" }); const addTokenButton = tokensTabPanel.getByRole("button", { name: `Add Token: Shadow`, }); await addTokenButton.click(); await expect(tokensUpdateCreateModal).toBeVisible(); await expect( tokensUpdateCreateModal.getByPlaceholder( "Enter a value or alias with {alias}", ), ).toBeVisible(); const nameField = tokensUpdateCreateModal.getByLabel("Name"); const colorField = tokensUpdateCreateModal.getByRole("textbox", { name: "Color", }); const offsetXField = tokensUpdateCreateModal.getByRole("textbox", { name: "X", }); const offsetYField = tokensUpdateCreateModal.getByRole("textbox", { name: "Y", }); const blurField = tokensUpdateCreateModal.getByRole("textbox", { name: "Blur", }); const spreadField = tokensUpdateCreateModal.getByRole("textbox", { name: "Spread", }); const submitButton = tokensUpdateCreateModal.getByRole("button", { name: "Save", }); // 1. Check default values await expect(offsetXField).toHaveValue("4"); await expect(offsetYField).toHaveValue("4"); await expect(blurField).toHaveValue("4"); await expect(spreadField).toHaveValue("0"); // 2. Name filled + empty value → disabled await nameField.fill("my-token"); await expect(submitButton).toBeDisabled(); // 3. Invalid color → disabled + error message await colorField.fill("1"); await expect( tokensUpdateCreateModal.getByText("Invalid color value: 1"), ).toBeVisible(); await expect(submitButton).toBeDisabled(); await colorField.fill("{missing-reference}"); await expect( tokensUpdateCreateModal.getByText( "Missing token references: missing-reference", ), ).toBeVisible(); // 4. Empty name → disabled + error message await nameField.fill(""); const emptyNameErrorNode = tokensUpdateCreateModal.getByText(emptyNameError); await expect(emptyNameErrorNode).toBeVisible(); await expect(submitButton).toBeDisabled(); // 5. Empty fill -> disabled + error message await offsetXField.fill("3"); // Fill should be touched in order to show the error message await offsetXField.fill(""); const emptyFieldErrorNode = tokensUpdateCreateModal.getByText(emptyFieldError); await expect(emptyFieldErrorNode).toBeVisible(); // // ------- SUCCESSFUL FIELDS ------- // // 5. Valid color → resolved await colorField.fill("red"); await expect( tokensUpdateCreateModal.getByText("Resolved value: #ff0000"), ).toBeVisible(); const colorSwatch = tokensUpdateCreateModal.getByTestId( "token-form-color-bullet", ); await colorSwatch.click(); const rampSelector = tokensUpdateCreateModal.getByTestId( "value-saturation-selector", ); await expect(rampSelector).toBeVisible(); await rampSelector.click({ position: { x: 50, y: 50 } }); await expect( tokensUpdateCreateModal.getByText("Resolved value:"), ).toBeVisible(); const sliderOpacity = tokensUpdateCreateModal.getByTestId("slider-opacity"); await sliderOpacity.click({ position: { x: 50, y: 0 } }); await expect( tokensUpdateCreateModal.getByRole("textbox", { name: "Color" }), ).toHaveValue(/rgba\s*\([^)]*\)/); // 6. Valid offset → resolved await offsetXField.fill("3 + 3"); await expect( tokensUpdateCreateModal.getByText("Resolved value: 6"), ).toBeVisible(); await offsetYField.fill("3 + 7"); await expect( tokensUpdateCreateModal.getByText("Resolved value: 10"), ).toBeVisible(); // 7. Valid blur → resolved await blurField.fill("3 + 1"); await expect( tokensUpdateCreateModal.getByText("Resolved value: 4"), ).toBeVisible(); // 8. Valid spread → resolved await blurField.fill("3 - 3"); await expect( tokensUpdateCreateModal.getByText("Resolved value: 0"), ).toBeVisible(); await nameField.fill("my-token"); await expect(submitButton).toBeEnabled(); await submitButton.click(); await unfoldTokenType(tokensTabPanel, "shadow"); await expect( tokensTabPanel.getByRole("button", { name: "my-token" }), ).toBeEnabled(); // // ------- SECOND TOKEN WITH VALID REFERENCE ------- // await addTokenButton.click(); await nameField.fill("my-token-2"); const referenceToggle = tokensUpdateCreateModal.getByTestId("reference-opt"); const compositeToggle = tokensUpdateCreateModal.getByTestId("composite-opt"); await referenceToggle.click(); const referenceInput = tokensUpdateCreateModal.getByPlaceholder( "Enter a token shadow alias", ); await expect(referenceInput).toBeVisible(); await compositeToggle.click(); await expect(colorField).toBeVisible(); await referenceToggle.click(); const referenceField = tokensUpdateCreateModal.getByRole("textbox", { name: "Reference", }); await referenceField.fill("{my-token}"); await expect( tokensUpdateCreateModal.getByText( "Resolved value: - X: 6 - Y: 10 - Blur: 0 - Spread: 0 ", ), ).toBeVisible(); await expect(submitButton).toBeEnabled(); await submitButton.click(); await unfoldTokenType(tokensTabPanel, "shadow"); await expect( tokensTabPanel.getByRole("button", { name: "my-token-2" }), ).toBeEnabled(); // // ------- THIRD TOKEN WITH EMPTY BLUR AND SPREAD ------- // await addTokenButton.click(); await nameField.fill("my-token-3"); await colorField.fill("red"); // 1. Empty blur and spread await blurField.fill(""); await spreadField.fill(""); await expect(submitButton).toBeEnabled(); await submitButton.click(); await unfoldTokenType(tokensTabPanel, "shadow"); await expect( tokensTabPanel.getByRole("button", { name: "my-token-3" }), ).toBeEnabled(); }); test("User can't submit empty typography token or reference", async ({ page, }) => { const { tokensUpdateCreateModal, tokenThemesSetsSidebar, tokensSidebar } = await setupTypographyTokensFileRender(page); const tokensTabPanel = page.getByRole("tabpanel", { name: "tokens" }); await tokensTabPanel .getByRole("button", { name: "Add Token: Typography" }) .click(); await expect(tokensUpdateCreateModal).toBeVisible(); const nameField = tokensUpdateCreateModal.getByLabel("Name"); await nameField.fill("typography.empty"); const valueField = tokensUpdateCreateModal.getByRole("textbox", { name: "Font Size", }); // Insert a value and then delete it await valueField.fill("1"); await valueField.fill(""); // Submit button should be disabled when field is empty const submitButton = tokensUpdateCreateModal.getByRole("button", { name: "Save", }); await expect(submitButton).toBeDisabled(); // Switch to reference tab, should not be submittable either const referenceTabButton = tokensUpdateCreateModal.getByTestId("reference-opt"); await referenceTabButton.click(); await expect(submitButton).toBeDisabled(); }); test("User creates shadow token with negative spread", async ({ page }) => { const emptyNameError = "Name should be at least 1 character"; const { tokensUpdateCreateModal, tokenThemesSetsSidebar } = await setupEmptyTokensFileRender(page, { flags: ["enable-token-shadow"], }); // Open modal const tokensTabPanel = page.getByRole("tabpanel", { name: "tokens" }); const addTokenButton = tokensTabPanel.getByRole("button", { name: `Add Token: Shadow`, }); await addTokenButton.click(); await expect(tokensUpdateCreateModal).toBeVisible(); await expect( tokensUpdateCreateModal.getByPlaceholder( "Enter a value or alias with {alias}", ), ).toBeVisible(); const nameField = tokensUpdateCreateModal.getByLabel("Name"); const colorField = tokensUpdateCreateModal.getByRole("textbox", { name: "Color", }); const offsetXField = tokensUpdateCreateModal.getByRole("textbox", { name: "X", }); const offsetYField = tokensUpdateCreateModal.getByRole("textbox", { name: "Y", }); const blurField = tokensUpdateCreateModal.getByRole("textbox", { name: "Blur", }); const spreadField = tokensUpdateCreateModal.getByRole("textbox", { name: "Spread", }); const submitButton = tokensUpdateCreateModal.getByRole("button", { name: "Save", }); // 1. Check default values await expect(offsetXField).toHaveValue("4"); await expect(offsetYField).toHaveValue("4"); await expect(blurField).toHaveValue("4"); await expect(spreadField).toHaveValue("0"); // 2. Name filled + empty value → disabled await nameField.fill("my-token"); await expect(submitButton).toBeDisabled(); // 3. Invalid color → disabled + error message await colorField.fill("1"); await expect( tokensUpdateCreateModal.getByText("Invalid color value: 1"), ).toBeVisible(); await expect(submitButton).toBeDisabled(); await colorField.fill("{missing-reference}"); await expect( tokensUpdateCreateModal.getByText( "Missing token references: missing-reference", ), ).toBeVisible(); // 4. Empty name → disabled + error message await nameField.fill(""); const emptyNameErrorNode = tokensUpdateCreateModal.getByText(emptyNameError); await expect(emptyNameErrorNode).toBeVisible(); await expect(submitButton).toBeDisabled(); // // ------- SUCCESSFUL FIELDS ------- // // 5. Valid color → resolved await colorField.fill("red"); await expect( tokensUpdateCreateModal.getByText("Resolved value: #ff0000"), ).toBeVisible(); const colorSwatch = tokensUpdateCreateModal.getByTestId( "token-form-color-bullet", ); await colorSwatch.click(); const rampSelector = tokensUpdateCreateModal.getByTestId( "value-saturation-selector", ); await expect(rampSelector).toBeVisible(); await rampSelector.click({ position: { x: 50, y: 50 } }); await expect( tokensUpdateCreateModal.getByText("Resolved value:"), ).toBeVisible(); const sliderOpacity = tokensUpdateCreateModal.getByTestId("slider-opacity"); await sliderOpacity.click({ position: { x: 50, y: 0 } }); await expect( tokensUpdateCreateModal.getByRole("textbox", { name: "Color" }), ).toHaveValue(/rgba\s*\([^)]*\)/); // 6. Valid offset → resolved await offsetXField.fill("3 + 3"); await expect( tokensUpdateCreateModal.getByText("Resolved value: 6"), ).toBeVisible(); await offsetYField.fill("3 + 7"); await expect( tokensUpdateCreateModal.getByText("Resolved value: 10"), ).toBeVisible(); // 7. Valid blur → resolved await blurField.fill("3 + 1"); await expect( tokensUpdateCreateModal.getByText("Resolved value: 4"), ).toBeVisible(); // 8. Valid spread → resolved await spreadField.fill("3 - 3"); await expect( tokensUpdateCreateModal.getByText("Resolved value: 0"), ).toBeVisible(); await spreadField.fill("1 - 3"); await expect( tokensUpdateCreateModal.getByText("Resolved value: -2"), ).toBeVisible(); await nameField.fill("my-token"); await expect(submitButton).toBeEnabled(); await submitButton.click(); await unfoldTokenType(tokensTabPanel, "shadow"); await expect( tokensTabPanel.getByRole("button", { name: "my-token" }), ).toBeEnabled(); // // ------- SECOND TOKEN WITH VALID REFERENCE ------- // await addTokenButton.click(); await nameField.fill("my-token-2"); const referenceToggle = tokensUpdateCreateModal.getByTestId("reference-opt"); const compositeToggle = tokensUpdateCreateModal.getByTestId("composite-opt"); await referenceToggle.click(); const referenceInput = tokensUpdateCreateModal.getByPlaceholder( "Enter a token shadow alias", ); await expect(referenceInput).toBeVisible(); await compositeToggle.click(); await expect(colorField).toBeVisible(); await referenceToggle.click(); const referenceField = tokensUpdateCreateModal.getByRole("textbox", { name: "Reference", }); await referenceField.fill("{my-token}"); await expect( tokensUpdateCreateModal.getByText( "Resolved value: - X: 6 - Y: 10 - Blur: 4 - Spread: -2", ), ).toBeVisible(); await expect(submitButton).toBeEnabled(); await submitButton.click(); await expect( tokensTabPanel.getByRole("button", { name: "my-token-2" }), ).toBeEnabled(); }); test("User creates typography token", async ({ page }) => { const emptyNameError = "Name should be at least 1 character"; const { tokensUpdateCreateModal, tokenThemesSetsSidebar } = await setupEmptyTokensFileRender(page); // Open modal const tokensTabPanel = page.getByRole("tabpanel", { name: "tokens" }); const addTokenButton = tokensTabPanel.getByRole("button", { name: `Add Token: Typography`, }); await addTokenButton.click(); await expect(tokensUpdateCreateModal).toBeVisible(); const nameField = tokensUpdateCreateModal.getByLabel("Name"); const fontFamilyField = tokensUpdateCreateModal.getByRole("textbox", { name: "Font family", }); await expect(fontFamilyField).toBeVisible(); const fontSizeField = tokensUpdateCreateModal.getByRole("textbox", { name: "Font size", }); await expect(fontSizeField).toBeVisible(); const fontWeightField = tokensUpdateCreateModal.getByRole("textbox", { name: "Font weight", }); await expect(fontWeightField).toBeVisible(); const lineHeightField = tokensUpdateCreateModal.getByRole("textbox", { name: "Line height", }); await expect(lineHeightField).toBeVisible(); const letterSpacingField = tokensUpdateCreateModal.getByRole("textbox", { name: "Letter spacing", }); await expect(letterSpacingField).toBeVisible(); const textCaseField = tokensUpdateCreateModal.getByRole("textbox", { name: "Text case", }); await expect(textCaseField).toBeVisible(); const textDecorationField = tokensUpdateCreateModal.getByRole("textbox", { name: "Text decoration", }); await expect(textDecorationField).toBeVisible(); const submitButton = tokensUpdateCreateModal.getByRole("button", { name: "Save", }); // 1. Name filled + empty value → disabled await nameField.fill("my-token"); await expect(submitButton).toBeDisabled(); // 2. Empty name → disabled + error message await nameField.fill(""); const emptyNameErrorNode = tokensUpdateCreateModal.getByText(emptyNameError); await expect(emptyNameErrorNode).toBeVisible(); await expect(submitButton).toBeDisabled(); await nameField.fill("my-token"); // // ------- CHECK FIELDS ------- // // 3. Font family const fontFamilyPlaceholder = tokensUpdateCreateModal.getByPlaceholder( "Font family or list of fonts separated by comma (,)", ); await expect(fontFamilyPlaceholder).toBeVisible(); await fontFamilyField.fill("red"); await expect( tokensUpdateCreateModal.getByText("Resolved value: red"), ).toBeVisible(); const selectDropdown = tokensUpdateCreateModal.getByRole("button", { name: "Select font family", }); await selectDropdown.click(); const fontOption = tokensUpdateCreateModal.getByText("ABeeZee"); await expect(fontOption).toBeVisible(); await fontOption.click(); await expect( tokensUpdateCreateModal.getByText("Resolved value: ABeeZee"), ).toBeVisible(); await selectDropdown.click(); const searchField = tokensUpdateCreateModal.getByRole("textbox", { name: "Search font", }); await searchField.fill("alme"); const fontOption2 = tokensUpdateCreateModal.getByText("Almendra Display"); await expect(fontOption2).toBeVisible(); await fontOption2.click(); await expect( tokensUpdateCreateModal.getByText("Resolved value: Almendra Display"), ).toBeVisible(); // 4. Font Size const fontSizePlaceholder = tokensUpdateCreateModal.getByPlaceholder( "Font size or {alias}", ); await expect(fontSizePlaceholder).toBeVisible(); await fontSizeField.fill("red"); await expect( tokensUpdateCreateModal.getByText("Invalid token value: red"), ).toBeVisible(); await fontSizeField.fill("24 + 2"); await expect( tokensUpdateCreateModal.getByText("Resolved value: 26"), ).toBeVisible(); // 4. Font Weight const fontWeightPlaceholder = tokensUpdateCreateModal.getByPlaceholder( "Font weight (300, Bold Italic...) or an {alias}", ); await expect(fontWeightPlaceholder).toBeVisible(); await fontWeightField.fill("2"); await expect( tokensUpdateCreateModal.getByText( "Invalid font weight value: use numeric values (100-950) or standard names (thin, light, regular, bold, etc.)", ), ).toBeVisible(); await fontWeightField.fill("100 + 200"); await expect( tokensUpdateCreateModal.getByText("Resolved value: 300"), ).toBeVisible(); await fontWeightField.fill("bold"); // 5. Line height const lineHegithPlaceholdewr = tokensUpdateCreateModal.getByPlaceholder( "Line height (multiplier, px, %) or {alias}", ); await expect(lineHegithPlaceholdewr).toBeVisible(); await fontSizeField.fill(""); await lineHeightField.fill("2"); await expect( tokensUpdateCreateModal.getByText( "Line Height depends on Font Size. Add a Font Size to get the resolved value.", ), ).toBeVisible(); await fontSizeField.fill("24 + 2"); await lineHeightField.fill("2"); // 6. Text case const textCasePlaceholder = tokensUpdateCreateModal.getByPlaceholder( "none | uppercase | lowercase | capitalize or {alias}", ); await expect(textCasePlaceholder).toBeVisible(); await textCaseField.fill("200"); await expect( tokensUpdateCreateModal.getByText( "Invalid token value: only none, Uppercase, Lowercase or Capitalize are accepted", ), ).toBeVisible(); await textCaseField.fill("none"); // 7. Letter spacing const letterSpacingPlaceholder = tokensUpdateCreateModal.getByPlaceholder( "Letter spacing or {alias}", ); await expect(letterSpacingPlaceholder).toBeVisible(); await letterSpacingField.fill("green"); await expect( tokensUpdateCreateModal.getByText("Invalid token value: green"), ).toBeVisible(); await letterSpacingField.fill("2.3"); // 7. Text decoration const textDecorationPlaceholder = tokensUpdateCreateModal.getByPlaceholder( "none | underline | strike-through or {alias}", ); await expect(textDecorationPlaceholder).toBeVisible(); await textDecorationField.fill("200"); await expect( tokensUpdateCreateModal.getByText( "Invalid token value: only none, underline and strike-through are accepted", ), ).toBeVisible(); await textDecorationField.fill("underline"); await expect(submitButton).toBeEnabled(); await submitButton.click(); await expect( tokensTabPanel.getByRole("button", { name: "my-token" }), ).toBeEnabled(); // // ------- SECOND TOKEN WITH VALID REFERENCE ------- // await addTokenButton.click(); await nameField.fill("my-token-2"); const referenceToggle = tokensUpdateCreateModal.getByTestId("reference-opt"); const compositeToggle = tokensUpdateCreateModal.getByTestId("composite-opt"); await referenceToggle.click(); const referenceInput = tokensUpdateCreateModal.getByPlaceholder( "Enter a token typography alias", ); await expect(referenceInput).toBeVisible(); await compositeToggle.click(); await expect( tokensUpdateCreateModal.getByPlaceholder("Enter typography token name"), ).toBeVisible(); await referenceToggle.click(); const referenceField = tokensUpdateCreateModal.getByRole("textbox", { name: "Reference", }); await referenceField.fill("{my-token}"); await expect( tokensUpdateCreateModal.getByText( "Resolved value: - Font Family: Almendra Display - Font Weight: bold - Font Size: 26 - Text Case: none - Letter Spacing: 2.3 - Text Decoration: underline - Line Height: 2", ), ).toBeVisible(); await expect(submitButton).toBeEnabled(); await submitButton.click(); await expect( tokensTabPanel.getByRole("button", { name: "my-token-2" }), ).toBeEnabled(); }); test("User adds typography token with reference", async ({ page }) => { const { tokensUpdateCreateModal, tokenThemesSetsSidebar, tokensSidebar } = await setupTypographyTokensFileRender(page); const newTokenTitle = "NewReference"; const tokensTabPanel = page.getByRole("tabpanel", { name: "tokens" }); await tokensTabPanel .getByRole("button", { name: "Add Token: Typography" }) .click(); await expect(tokensUpdateCreateModal).toBeVisible(); const nameField = tokensUpdateCreateModal.getByLabel("Name"); await nameField.fill(newTokenTitle); const referenceTabButton = tokensUpdateCreateModal.getByTestId("reference-opt"); await referenceTabButton.click(); const referenceField = tokensUpdateCreateModal.getByRole("textbox", { name: "Reference", }); await referenceField.fill("{Full}"); const submitButton = tokensUpdateCreateModal.getByRole("button", { name: "Save", }); const resolvedValue = await tokensUpdateCreateModal.getByText("Resolved value:"); await expect(resolvedValue).toBeVisible(); await expect(resolvedValue).toContainText("Font Family: 42dot Sans"); await expect(resolvedValue).toContainText("Font Size: 100"); await expect(resolvedValue).toContainText("Font Weight: 300"); await expect(resolvedValue).toContainText("Letter Spacing: 2"); await expect(resolvedValue).toContainText("Text Case: uppercase"); await expect(resolvedValue).toContainText("Text Decoration: underline"); await expect(submitButton).toBeEnabled(); await submitButton.click(); await expect(tokensUpdateCreateModal).not.toBeVisible(); const newToken = tokensSidebar.getByRole("button", { name: newTokenTitle, }); await expect(newToken).toBeVisible(); }); 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 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, }) => { const { tokenThemesSetsSidebar, tokensSidebar } = await setupEmptyTokensFileRender(page); // Create color token await createToken( page, "Color", "color.primary", "Value", "textbox", "#ff0000", ); await unfoldTokenType(tokensSidebar, "color"); // Deactivate current set await tokenThemesSetsSidebar.getByRole("checkbox").click(); // Tokens tab panel should have a token with the color #ff0000 and correct resolved value in the tooltip const colorTokenPill = tokensSidebar.getByRole("button", { name: "#ff0000 color.primary", }); await expect(colorTokenPill).toHaveCount(1); await colorTokenPill.hover(); // Force title attribute to be attached to the button await expect(colorTokenPill).toHaveAttribute( "title", /Resolved value: #ff0000/, ); }); test.describe("User can't create groups that clash with token names", () => { const createBadToken = async (page, type, name, textFieldName, value, inputType) => { 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(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", }); 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", }); 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", }); 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", }); 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", }); 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", }); 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", }); 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", }); 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", }); 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", }); 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", }); 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", }); 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", }); 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", }); 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( "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 ({ 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( "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(); }); }); test.describe("Tokens tab - edition", () => { test("User edits typography token and all fields are valid", async ({ page, }) => { const { tokensUpdateCreateModal, tokenThemesSetsSidebar, tokensSidebar } = await setupTypographyTokensFileRender(page); await tokensSidebar .getByRole("button") .filter({ hasText: "Typography" }) .click(); // Open edit modal for "Full" typography token const token = tokensSidebar.getByRole("button", { name: "Full" }); await token.click({ button: "right" }); await page.getByText("Edit token").click(); // Modal opens await expect(tokensUpdateCreateModal).toBeVisible(); const saveButton = tokensUpdateCreateModal.getByRole("button", { name: /save/i, }); // Fill font-family to verify to verify that input value doesn't get split into list of characters const fontFamilyField = tokensUpdateCreateModal .getByRole("textbox", { name: "Font family" }) .first(); await fontFamilyField.fill("OneWord"); // Invalidate incorrect values for font size const fontSizeField = tokensUpdateCreateModal.getByRole("textbox", { name: "Font Size", }); await fontSizeField.fill("invalid"); await expect( tokensUpdateCreateModal.getByText(/Invalid token value:/), ).toBeVisible(); await expect(saveButton).toBeDisabled(); // Show error with line-height depending on invalid font-size await fontSizeField.fill(""); await page.keyboard.press("Enter"); await expect(saveButton).toBeDisabled(); await expect( tokensUpdateCreateModal.getByText(/Invalid token value:/), ).not.toBeVisible(); await expect( tokensUpdateCreateModal.getByText(/Line Height depends on Font Size/), ).toBeVisible(); // Fill in values for all fields and verify they persist when switching tabs await fontSizeField.fill("16 + 3"); await expect(saveButton).toBeEnabled(); const fontWeightField = tokensUpdateCreateModal.getByRole("textbox", { name: "Font Weight", }); const letterSpacingField = tokensUpdateCreateModal.getByRole("textbox", { name: "Letter Spacing", }); const lineHeightField = tokensUpdateCreateModal.getByRole("textbox", { name: "Line Height", }); const textCaseField = tokensUpdateCreateModal.getByRole("textbox", { name: "Text Case", }); const textDecorationField = tokensUpdateCreateModal.getByRole("textbox", { name: "Text Decoration", }); // Capture all values before switching tabs const originalValues = { fontSize: await fontSizeField.inputValue(), fontFamily: await fontFamilyField.inputValue(), fontWeight: await fontWeightField.inputValue(), letterSpacing: await letterSpacingField.inputValue(), lineHeight: await lineHeightField.inputValue(), textCase: await textCaseField.inputValue(), textDecoration: await textDecorationField.inputValue(), }; // Switch to reference tab and back to composite tab const referenceTabButton = tokensUpdateCreateModal.getByTestId("reference-opt"); await referenceTabButton.click(); // Empty reference tab should be disabled await expect(saveButton).toBeDisabled(); const compositeTabButton = tokensUpdateCreateModal.getByTestId("composite-opt"); await compositeTabButton.click(); // Filled composite tab should be enabled await expect(saveButton).toBeEnabled(); // Verify all values are preserved after switching tabs await expect(fontSizeField).toHaveValue(originalValues.fontSize); await expect(fontFamilyField).toHaveValue(originalValues.fontFamily); await expect(fontWeightField).toHaveValue(originalValues.fontWeight); await expect(letterSpacingField).toHaveValue(originalValues.letterSpacing); await expect(lineHeightField).toHaveValue(originalValues.lineHeight); await expect(textCaseField).toHaveValue(originalValues.textCase); await expect(textDecorationField).toHaveValue( originalValues.textDecoration, ); await saveButton.click(); // Modal should close, token should be visible (with new name) in sidebar await expect(tokensUpdateCreateModal).not.toBeVisible(); }); test("User edits token and auto created set show up in the sidebar", async ({ page, }) => { const { tokensUpdateCreateModal, tokensSidebar, tokenContextMenuForToken } = await setupTokensFileRender(page); await expect(tokensSidebar).toBeVisible(); await unfoldTokenType(tokensSidebar, "color"); const colorToken = tokensSidebar.getByRole("button", { name: "colors.blue.100", }); await expect(colorToken).toBeVisible(); await colorToken.click({ button: "right" }); await expect(tokenContextMenuForToken).toBeVisible(); await tokenContextMenuForToken.getByText("Edit token").click(); await expect(tokensUpdateCreateModal).toBeVisible(); const nameField = tokensUpdateCreateModal.getByLabel("Name"); await nameField.pressSequentially(".changed"); await tokensUpdateCreateModal.getByRole("button", { name: "Save" }).click(); await expect(tokensUpdateCreateModal).not.toBeVisible(); await unfoldTokenType(tokensSidebar, "color"); const colorTokenChanged = tokensSidebar.getByRole("button", { name: "changed", }); await expect(colorTokenChanged).toBeVisible(); }); test("User edits color token color while keeping custom color space", async ({ page, }) => { const { workspacePage, tokensUpdateCreateModal, tokenThemesSetsSidebar } = await setupEmptyTokensFileRender(page); const tokensTabPanel = page.getByRole("tabpanel", { name: "tokens" }); await tokensTabPanel .getByRole("button", { name: "Add Token: Color" }) .click(); await expect(tokensUpdateCreateModal).toBeVisible(); const valueField = tokensUpdateCreateModal.getByLabel("Value"); await valueField.click(); await valueField.fill("hsv(1,1,1)"); await expect( tokensUpdateCreateModal.getByText("Resolved value: #ff0400"), ).toBeVisible(); const colorBullet = tokensUpdateCreateModal.getByTestId( "token-form-color-bullet", ); await colorBullet.click(); const valueSaturationSelector = tokensUpdateCreateModal.getByTestId( "value-saturation-selector", ); await expect(valueSaturationSelector).toBeVisible(); // Check if color space doesnt get overwritten when changing color via the picker // Not testing for exact value to avoid flakiness of px click await valueSaturationSelector.click({ position: { x: 100, y: 100 } }); await expect(valueField).not.toHaveValue("hsv(1,1,1)"); await expect(valueField).toHaveValue(/^hsv.*$/); // Clearing the input field should pick hex await valueField.fill(""); // TODO: We need to fix this translation await expect( tokensUpdateCreateModal.getByText("Token value cannot be empty"), ).toBeVisible(); await valueSaturationSelector.click({ position: { x: 50, y: 50 } }); await expect(valueField).toHaveValue(/^#[A-Fa-f\d]+$/); // Changing opacity for hex values converts to rgba const sliderOpacity = tokensUpdateCreateModal.getByTestId("slider-opacity"); await sliderOpacity.click({ position: { x: 50, y: 0 } }); await expect(valueField).toHaveValue(/^rgba(.*)$/); // Changing color now will stay in rgba await valueSaturationSelector.click({ position: { x: 0, y: 0 } }); await expect(valueField).toHaveValue(/^rgba(.*)$/); }); test("User sees self-reference error when editing a token to create a circular reference", async ({ page, }) => { const { tokensUpdateCreateModal, tokensSidebar, tokenContextMenuForToken } = await setupEmptyTokensFileRender(page, { flags: ["enable-token-combobox", "enable-feature-token-input"], }); const tokensTabPanel = page.getByRole("tabpanel", { name: "tokens" }); // Create first token "base" with value "10" const addTokenButton = tokensTabPanel.getByRole("button", { name: "Add Token: Border Radius", }); await addTokenButton.click(); await expect(tokensUpdateCreateModal).toBeVisible(); await createToken(page, "Border radius", "base", "Value", "combobox", "10"); await unfoldTokenType(tokensTabPanel, "border radius"); await expect( tokensTabPanel.getByRole("button", { name: "base" }), ).toBeEnabled(); // Create second token "linear" referencing "base" await createToken( page, "Border radius", "linear", "Value", "combobox", "{base} * 0.25", ); await expect( tokensTabPanel.getByRole("button", { name: "linear" }), ).toBeEnabled(); // Open the edit modal for "base" const baseToken = tokensTabPanel.getByRole("button", { name: "base" }); await expect(baseToken).toBeVisible(); await baseToken.click({ button: "right" }); await expect(tokenContextMenuForToken).toBeVisible(); const editOption = tokenContextMenuForToken .getByRole("listitem") .filter({ hasText: "Edit token" }); await expect(editOption).toBeVisible(); await editOption.click(); await expect(tokensUpdateCreateModal).toBeVisible(); // Change value to reference "linear.0.25", creating a circular reference const editValueField = tokensUpdateCreateModal.getByRole("combobox", { name: "Value", }); await editValueField.fill("{linear}"); // The circular reference error should appear gracefully const circularError = tokensUpdateCreateModal.getByText( "Token has circular reference", ); await expect(circularError).toBeVisible({ timeout: 5000 }); // Save button should be disabled const editSubmitButton = tokensUpdateCreateModal.getByRole("button", { name: "Save", }); await expect(editSubmitButton).toBeDisabled(); }); test("User sees self-reference error for a three-step circular reference (A → B → C → A)", async ({ page, }) => { const { tokensUpdateCreateModal, tokensSidebar, tokenContextMenuForToken } = await setupEmptyTokensFileRender(page, { flags: ["enable-token-combobox", "enable-feature-token-input"], }); const tokensTabPanel = page.getByRole("tabpanel", { name: "tokens" }); // Create first token "a" with value "10" const addTokenButton = tokensTabPanel.getByRole("button", { name: "Add Token: Border Radius", }); await createToken( page, "Border radius", "First", "Value", "combobox", "10", ); await createToken( page, "Border radius", "Second", "Value", "combobox", "{First}", ); await createToken( page, "Border radius", "Third", "Value", "combobox", "{Second}", ); // Edit "First" to reference "Third", completing the cycle A → B → C → A const tokenA = tokensTabPanel.getByRole("button", { name: "First" }); await tokenA.click({ button: "right" }); await expect(tokenContextMenuForToken).toBeVisible(); await tokenContextMenuForToken.getByText("Edit token").click(); await expect(tokensUpdateCreateModal).toBeVisible(); const editValueField = tokensUpdateCreateModal.getByRole("combobox", { name: "Value", }); await editValueField.fill("{Third}"); // The circular reference error should appear gracefully const circularError = tokensUpdateCreateModal.getByText( "Token has circular reference", ); await expect(circularError).toBeVisible({ timeout: 5000 }); // Save button should be disabled const editSubmitButton = tokensUpdateCreateModal.getByRole("button", { name: "Save", }); await expect(editSubmitButton).toBeDisabled(); }); }); test.describe("Tokens tab - delete", () => { test("User delete 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 expect(colorToken).toBeVisible(); await colorToken.click({ button: "right" }); await expect(tokenContextMenuForToken).toBeVisible(); await tokenContextMenuForToken.getByText("Delete token").click(); await expect(tokenContextMenuForToken).not.toBeVisible(); await expect(colorToken).not.toBeVisible(); }); }); test("BUG: 14262 Token pill must be highlighted when value references a token in a disabled set", async ({ page, }) => { const { tokensSidebar, tokenContextMenuForToken, tokenThemesSetsSidebar } = await setupTokensFileRender(page); await expect(tokensSidebar).toBeVisible(); await unfoldTokenType(tokensSidebar, "Border radius"); await createToken( page, "Border radius", "base-radius", "Value", "combobox", "20", ); await createToken( page, "Border radius", "ref-base", "Value", "combobox", "{base-radius}", ); const refTokenPill = tokensSidebar.getByRole("button", { name: "ref-base", }); await expect(refTokenPill).toBeVisible(); const CoreSetCheckbox = tokenThemesSetsSidebar .getByRole("button", { name: "core" }) .getByRole("checkbox"); await CoreSetCheckbox.click(); // Pill is not highlighted if both tokens are on the same disabled set const brokenTokenPill = tokensSidebar.getByRole("button", { name: "Missing reference ref-base", }); await expect(brokenTokenPill).not.toBeVisible(); await createSet(tokenThemesSetsSidebar, "New set"); await tokenThemesSetsSidebar.getByRole("button", { name: "New set" }).click(); await tokenThemesSetsSidebar .getByRole("button", { name: "New set" }) .getByRole("checkbox") .click(); await createToken( page, "Border radius", "new-ref", "Value", "combobox", "{base-radius}", ); // Pill is highlighted if the referenced token is in a different disabled set than the token with the reference const newBrokenTokenPill = tokensSidebar.getByRole("button", { name: "Missing reference new-ref", }); await expect(newBrokenTokenPill).toBeVisible(); await tokenThemesSetsSidebar .getByRole("button", { name: "core" }) .getByRole("checkbox") .click(); // When the disabled set is activated again, pill is not highlighted anymore await expect( tokenThemesSetsSidebar .getByRole("button", { name: "core" }) .getByRole("checkbox"), ).toBeChecked(); await expect(newBrokenTokenPill).not.toBeVisible(); });