mirror of
https://github.com/penpot/penpot.git
synced 2025-12-11 22:14:05 +01:00
2732 lines
86 KiB
JavaScript
2732 lines
86 KiB
JavaScript
import { test, expect } from "@playwright/test";
|
|
import { WorkspacePage } from "../pages/WorkspacePage";
|
|
import { BaseWebSocketPage } from "../pages/BaseWebSocketPage";
|
|
|
|
test.beforeEach(async ({ page }) => {
|
|
await WorkspacePage.init(page);
|
|
await BaseWebSocketPage.mockRPC(page, "get-teams", "get-teams-tokens.json");
|
|
});
|
|
|
|
const setupEmptyTokensFile = async (page, options = {}) => {
|
|
const { flags = [] } = options;
|
|
|
|
const workspacePage = new WorkspacePage(page);
|
|
if (flags.length > 0) {
|
|
await workspacePage.mockConfigFlags(flags);
|
|
}
|
|
|
|
await workspacePage.setupEmptyFile();
|
|
await workspacePage.mockRPC(
|
|
"get-team?id=*",
|
|
"workspace/get-team-tokens.json",
|
|
);
|
|
|
|
await workspacePage.mockRPC(
|
|
"update-file?id=*",
|
|
"workspace/update-file-create-rect.json",
|
|
);
|
|
|
|
await workspacePage.goToWorkspace({
|
|
fileId: "c7ce0794-0992-8105-8004-38f280443849",
|
|
pageId: "66697432-c33d-8055-8006-2c62cc084cad",
|
|
});
|
|
|
|
const tokensTabButton = page.getByRole("tab", { name: "Tokens" });
|
|
await tokensTabButton.click();
|
|
|
|
return {
|
|
workspacePage,
|
|
tokenThemeUpdateCreateModal: workspacePage.tokenThemeUpdateCreateModal,
|
|
tokensUpdateCreateModal: workspacePage.tokensUpdateCreateModal,
|
|
tokenThemesSetsSidebar: workspacePage.tokenThemesSetsSidebar,
|
|
tokenSetItems: workspacePage.tokenSetItems,
|
|
tokenSetGroupItems: workspacePage.tokenSetGroupItems,
|
|
tokenContextMenuForSet: workspacePage.tokenContextMenuForSet,
|
|
};
|
|
};
|
|
|
|
const setupTokensFile = async (page, options = {}) => {
|
|
const {
|
|
file = "workspace/get-file-tokens.json",
|
|
fileFragment = "workspace/get-file-fragment-tokens.json",
|
|
flags = [],
|
|
} = options;
|
|
|
|
const workspacePage = new WorkspacePage(page);
|
|
if (flags.length > 0) {
|
|
await workspacePage.mockConfigFlags(flags);
|
|
}
|
|
|
|
await workspacePage.setupEmptyFile();
|
|
await workspacePage.mockRPC(
|
|
"get-team?id=*",
|
|
"workspace/get-team-tokens.json",
|
|
);
|
|
await workspacePage.mockRPC(/get\-file\?/, file);
|
|
await workspacePage.mockRPC(/get\-file\-fragment\?/, fileFragment);
|
|
await workspacePage.mockRPC(
|
|
"update-file?id=*",
|
|
"workspace/update-file-create-rect.json",
|
|
);
|
|
|
|
await workspacePage.goToWorkspace({
|
|
fileId: "c7ce0794-0992-8105-8004-38f280443849",
|
|
pageId: "66697432-c33d-8055-8006-2c62cc084cad",
|
|
});
|
|
|
|
const tokensTabButton = page.getByRole("tab", { name: "Tokens" });
|
|
await tokensTabButton.click();
|
|
|
|
return {
|
|
workspacePage,
|
|
tokensUpdateCreateModal: workspacePage.tokensUpdateCreateModal,
|
|
tokenThemeUpdateCreateModal: workspacePage.tokenThemeUpdateCreateModal,
|
|
tokenThemesSetsSidebar: workspacePage.tokenThemesSetsSidebar,
|
|
tokenSetItems: workspacePage.tokenSetItems,
|
|
tokenSetGroupItems: workspacePage.tokenSetGroupItems,
|
|
tokensSidebar: workspacePage.tokensSidebar,
|
|
tokenContextMenuForToken: workspacePage.tokenContextMenuForToken,
|
|
tokenContextMenuForSet: workspacePage.tokenContextMenuForSet,
|
|
};
|
|
};
|
|
|
|
const setupTypographyTokensFile = async (page, options = {}) => {
|
|
return setupTokensFile(page, {
|
|
file: "workspace/get-file-typography-tokens.json",
|
|
fileFragment: "workspace/get-file-fragment-typography-tokens.json",
|
|
...options,
|
|
});
|
|
};
|
|
|
|
const checkInputFieldWithError = async (
|
|
tokenThemeUpdateCreateModal,
|
|
inputLocator,
|
|
) => {
|
|
await expect(inputLocator).toHaveAttribute("aria-invalid", "true");
|
|
|
|
const errorMessageId = await inputLocator.getAttribute("aria-describedby");
|
|
await expect(
|
|
tokenThemeUpdateCreateModal.locator(`#${errorMessageId}`),
|
|
).toBeVisible();
|
|
};
|
|
|
|
const checkInputFieldWithoutError = async (
|
|
tokenThemeUpdateCreateModal,
|
|
inputLocator,
|
|
) => {
|
|
expect(await inputLocator.getAttribute("aria-invalid")).toBeNull();
|
|
expect(await inputLocator.getAttribute("aria-describedby")).toBeNull();
|
|
};
|
|
|
|
async function testTokenCreationFlow(
|
|
page,
|
|
{
|
|
tokenLabel,
|
|
namePlaceholder,
|
|
valuePlaceholder,
|
|
validValue,
|
|
invalidValue,
|
|
selfReferenceValue,
|
|
missingReferenceValue,
|
|
secondValidValue,
|
|
resolvedValueText,
|
|
secondResolvedValueText,
|
|
},
|
|
) {
|
|
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, tokenThemesSetsSidebar } =
|
|
await setupEmptyTokensFile(page);
|
|
|
|
// Open modal
|
|
const tokensTabPanel = page.getByRole("tabpanel", { name: "tokens" });
|
|
|
|
const addTokenButton = tokensTabPanel.getByRole("button", {
|
|
name: `Add Token: ${tokenLabel}`,
|
|
});
|
|
|
|
await addTokenButton.click();
|
|
await expect(tokensUpdateCreateModal).toBeVisible();
|
|
|
|
// Placeholder checks
|
|
await expect(
|
|
tokensUpdateCreateModal.getByPlaceholder(namePlaceholder),
|
|
).toBeVisible();
|
|
await expect(
|
|
tokensUpdateCreateModal.getByPlaceholder(valuePlaceholder),
|
|
).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(invalidValue);
|
|
|
|
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(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),
|
|
).toBeVisible();
|
|
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");
|
|
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();
|
|
}
|
|
|
|
test.describe("Tokens: Tokens Tab", () => {
|
|
test("Clicking tokens tab button opens tokens sidebar tab", async ({
|
|
page,
|
|
}) => {
|
|
await setupEmptyTokensFile(page);
|
|
|
|
const tokensTabPanel = page.getByRole("tabpanel", { name: "tokens" });
|
|
|
|
await expect(tokensTabPanel).toHaveText(/TOKENS/);
|
|
await expect(tokensTabPanel).toHaveText(/Themes/);
|
|
});
|
|
|
|
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 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 } =
|
|
await setupEmptyTokensFile(page);
|
|
|
|
const tokensTabPanel = page.getByRole("tabpanel", { name: "tokens" });
|
|
const addTokenButton = tokensTabPanel.getByRole("button", {
|
|
name: `Add Token: Color`,
|
|
});
|
|
|
|
await addTokenButton.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 expect(
|
|
tokensTabPanel.getByRole("button", {
|
|
name: "color.primary",
|
|
}),
|
|
).toBeEnabled();
|
|
|
|
// Create token referencing the previous one with keyboard
|
|
|
|
await tokensTabPanel
|
|
.getByRole("button", { name: "Add Token: Color" })
|
|
.click();
|
|
await expect(tokensUpdateCreateModal).toBeVisible();
|
|
|
|
await nameField.click();
|
|
await nameField.fill("color.secondary");
|
|
await nameField.press("Tab");
|
|
|
|
await valueField.click();
|
|
await valueField.fill("{color.primary}");
|
|
|
|
await expect(submitButton).toBeEnabled();
|
|
await nameField.press("Enter");
|
|
|
|
await expect(
|
|
tokensTabPanel.getByRole("button", {
|
|
name: "color.secondary",
|
|
}),
|
|
).toBeEnabled();
|
|
|
|
// Tokens tab panel should have two tokens with the color red / #ff0000
|
|
await expect(
|
|
tokensTabPanel.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 tokensTabPanel
|
|
.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 setupEmptyTokensFile(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 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 setupEmptyTokensFile(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 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 setupEmptyTokensFile(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 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 setupEmptyTokensFile(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 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 { tokensUpdateCreateModal, tokenThemesSetsSidebar } =
|
|
await setupEmptyTokensFile(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 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 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 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 setupEmptyTokensFile(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 edits token and auto created set show up in the sidebar", async ({
|
|
page,
|
|
}) => {
|
|
const {
|
|
workspacePage,
|
|
tokensUpdateCreateModal,
|
|
tokenThemesSetsSidebar,
|
|
tokensSidebar,
|
|
tokenContextMenuForToken,
|
|
} = await setupTokensFile(page);
|
|
|
|
await expect(tokensSidebar).toBeVisible();
|
|
|
|
const tokensColorGroup = tokensSidebar.getByRole("button", {
|
|
name: "Color 92",
|
|
});
|
|
await expect(tokensColorGroup).toBeVisible();
|
|
await tokensColorGroup.click();
|
|
|
|
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();
|
|
|
|
const colorTokenChanged = tokensSidebar.getByRole("button", {
|
|
name: "colors.blue.100.changed",
|
|
});
|
|
await expect(colorTokenChanged).toBeVisible();
|
|
});
|
|
|
|
test("User edits theme and activates it in the sidebar", async ({ page }) => {
|
|
const { tokenThemesSetsSidebar, tokenThemeUpdateCreateModal } =
|
|
await setupTokensFile(page);
|
|
|
|
await expect(tokenThemesSetsSidebar).toBeVisible();
|
|
|
|
await tokenThemesSetsSidebar.getByRole("button", { name: "Edit" }).click();
|
|
|
|
await expect(tokenThemeUpdateCreateModal).toBeVisible();
|
|
await tokenThemeUpdateCreateModal
|
|
.getByRole("button", { name: "sets" })
|
|
.first()
|
|
.click();
|
|
|
|
await tokenThemeUpdateCreateModal.getByLabel("Theme").fill("Changed");
|
|
|
|
const lightDarkSetGroup = tokenThemeUpdateCreateModal.getByTestId(
|
|
"tokens-set-group-item",
|
|
{
|
|
name: "LightDark",
|
|
exact: true,
|
|
},
|
|
);
|
|
await expect(lightDarkSetGroup).toBeVisible();
|
|
|
|
const lightSet = tokenThemeUpdateCreateModal.getByRole("button", {
|
|
name: "light",
|
|
exact: true,
|
|
});
|
|
const darkSet = tokenThemeUpdateCreateModal.getByRole("button", {
|
|
name: "dark",
|
|
exact: true,
|
|
});
|
|
|
|
// Mixed set group
|
|
await expect(lightSet.getByRole("checkbox")).toBeChecked();
|
|
await expect(darkSet.getByRole("checkbox")).not.toBeChecked();
|
|
|
|
// Disable all
|
|
await lightDarkSetGroup.getByRole("checkbox").click();
|
|
await expect(lightSet.getByRole("checkbox")).not.toBeChecked();
|
|
await expect(darkSet.getByRole("checkbox")).not.toBeChecked();
|
|
|
|
// Enable all
|
|
await lightDarkSetGroup.getByRole("checkbox").click();
|
|
await expect(lightSet.getByRole("checkbox")).toBeChecked();
|
|
await expect(darkSet.getByRole("checkbox")).toBeChecked();
|
|
|
|
await tokenThemeUpdateCreateModal
|
|
.getByRole("button", {
|
|
name: "save theme",
|
|
})
|
|
.click();
|
|
|
|
await expect(
|
|
tokenThemeUpdateCreateModal.getByText("Changed" + "4 active sets"),
|
|
).toBeVisible();
|
|
|
|
await tokenThemeUpdateCreateModal
|
|
.getByRole("button")
|
|
.getByText("close")
|
|
.click();
|
|
await expect(tokenThemeUpdateCreateModal).not.toBeVisible();
|
|
|
|
const themeSelect = tokenThemesSetsSidebar.getByRole("combobox");
|
|
|
|
await themeSelect.click();
|
|
await page
|
|
.getByTestId("theme-select-dropdown")
|
|
.getByRole("option", { name: "Changed" })
|
|
.click();
|
|
|
|
const sidebarLightSet = tokenThemesSetsSidebar.getByRole("button", {
|
|
name: "light",
|
|
exact: true,
|
|
});
|
|
const sidebarDarkSet = tokenThemesSetsSidebar.getByRole("button", {
|
|
name: "dark",
|
|
exact: true,
|
|
});
|
|
|
|
await expect(sidebarLightSet.getByRole("checkbox")).toBeChecked();
|
|
await expect(sidebarDarkSet.getByRole("checkbox")).toBeChecked();
|
|
});
|
|
|
|
test("User creates grouped color token", async ({ page }) => {
|
|
const { workspacePage, tokensUpdateCreateModal, tokenThemesSetsSidebar } =
|
|
await setupEmptyTokensFile(page);
|
|
|
|
const tokensTabPanel = page.getByRole("tabpanel", { name: "tokens" });
|
|
await tokensTabPanel
|
|
.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("color.dark.primary");
|
|
|
|
await valueField.click();
|
|
await valueField.fill("red");
|
|
|
|
const submitButton = tokensUpdateCreateModal.getByRole("button", {
|
|
name: "Save",
|
|
});
|
|
await expect(submitButton).toBeEnabled();
|
|
await submitButton.click();
|
|
|
|
await expect(tokensTabPanel.getByLabel("color.dark.primary")).toBeEnabled();
|
|
});
|
|
|
|
test("User cant create regular token with value missing", async ({
|
|
page,
|
|
}) => {
|
|
const { tokensUpdateCreateModal } = await setupEmptyTokensFile(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 valueField = tokensUpdateCreateModal.getByLabel("Value");
|
|
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("color.primary");
|
|
|
|
// Submit button should remain disabled when value is empty
|
|
await expect(submitButton).toBeDisabled();
|
|
});
|
|
|
|
test("User changes color token color while keeping custom color space", async ({
|
|
page,
|
|
}) => {
|
|
const { workspacePage, tokensUpdateCreateModal, tokenThemesSetsSidebar } =
|
|
await setupEmptyTokensFile(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 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 duplicate color token", async ({ page }) => {
|
|
const { tokensSidebar, tokenContextMenuForToken } =
|
|
await setupTokensFile(page);
|
|
|
|
await expect(tokensSidebar).toBeVisible();
|
|
|
|
const tokensColorGroup = tokensSidebar.getByRole("button", {
|
|
name: "Color 92",
|
|
});
|
|
|
|
await expect(tokensColorGroup).toBeVisible();
|
|
await tokensColorGroup.click();
|
|
|
|
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 delete color token", async ({ page }) => {
|
|
const { tokensSidebar, tokenContextMenuForToken } =
|
|
await setupTokensFile(page);
|
|
|
|
await expect(tokensSidebar).toBeVisible();
|
|
|
|
const tokensColorGroup = tokensSidebar.getByRole("button", {
|
|
name: "Color 92",
|
|
});
|
|
await expect(tokensColorGroup).toBeVisible();
|
|
|
|
await tokensColorGroup.click();
|
|
|
|
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("User fold/unfold color tokens", async ({ page }) => {
|
|
const { tokensSidebar, tokenContextMenuForToken } =
|
|
await setupTokensFile(page);
|
|
|
|
await expect(tokensSidebar).toBeVisible();
|
|
|
|
const tokensColorGroup = tokensSidebar.getByRole("button", {
|
|
name: "Color 92",
|
|
});
|
|
await expect(tokensColorGroup).toBeVisible();
|
|
await tokensColorGroup.click();
|
|
|
|
const colorToken = tokensSidebar.getByRole("button", {
|
|
name: "colors.blue.100",
|
|
});
|
|
await expect(colorToken).toBeVisible();
|
|
await tokensColorGroup.click();
|
|
await expect(colorToken).not.toBeVisible();
|
|
});
|
|
});
|
|
|
|
test.describe("Tokens: Sets Tab", () => {
|
|
const changeSetInput = async (sidebar, setName, finalKey = "Enter") => {
|
|
const setInput = sidebar.locator("input:focus");
|
|
await expect(setInput).toBeVisible();
|
|
await setInput.fill(setName);
|
|
await setInput.press(finalKey);
|
|
};
|
|
|
|
const createSet = async (sidebar, setName, finalKey = "Enter") => {
|
|
const tokensTabButton = sidebar
|
|
.getByRole("button", { name: "Add set" })
|
|
.click();
|
|
|
|
await changeSetInput(sidebar, setName, (finalKey = "Enter"));
|
|
};
|
|
|
|
const assertEmptySetsList = async (el) => {
|
|
const buttons = await el.getByRole("button").allTextContents();
|
|
const filteredButtons = buttons.filter((text) => text === "Create one.");
|
|
await expect(filteredButtons.length).toEqual(2); // We assume there are no themes, so we have two "Create one" buttons.
|
|
};
|
|
|
|
const assertSetsList = async (el, sets) => {
|
|
const buttons = await el.getByRole("button").allTextContents();
|
|
const filteredButtons = buttons.filter(
|
|
(text) => text && text !== "Create one.",
|
|
);
|
|
await expect(filteredButtons).toEqual(sets);
|
|
};
|
|
|
|
test("User creates sets tree structure by entering a set path", async ({
|
|
page,
|
|
}) => {
|
|
const { tokenThemesSetsSidebar, tokenContextMenuForSet } =
|
|
await setupEmptyTokensFile(page);
|
|
|
|
const tokensTabButton = tokenThemesSetsSidebar
|
|
.getByRole("button", { name: "Add set" })
|
|
.click();
|
|
|
|
await createSet(tokenThemesSetsSidebar, "core/colors/light");
|
|
await createSet(tokenThemesSetsSidebar, "core/colors/dark");
|
|
|
|
await assertSetsList(tokenThemesSetsSidebar, [
|
|
"core",
|
|
"colors",
|
|
"light",
|
|
"dark",
|
|
]);
|
|
|
|
// User renames set
|
|
await tokenThemesSetsSidebar
|
|
.getByRole("button", { name: "light" })
|
|
.click({ button: "right" });
|
|
await expect(tokenContextMenuForSet).toBeVisible();
|
|
await tokenContextMenuForSet.getByText("Rename").click();
|
|
await changeSetInput(tokenThemesSetsSidebar, "light-renamed");
|
|
|
|
// User cancels during editing
|
|
await createSet(tokenThemesSetsSidebar, "core/colors/dark", "Escape");
|
|
|
|
await assertSetsList(tokenThemesSetsSidebar, [
|
|
"core",
|
|
"colors",
|
|
"light-renamed",
|
|
"dark",
|
|
]);
|
|
|
|
// Creates nesting by renaming set with double click
|
|
await tokenThemesSetsSidebar
|
|
.getByRole("button", { name: "light-renamed" })
|
|
.click({ button: "right" });
|
|
await expect(tokenContextMenuForSet).toBeVisible();
|
|
await tokenContextMenuForSet.getByText("Rename").click();
|
|
await changeSetInput(tokenThemesSetsSidebar, "nested/light");
|
|
|
|
await assertSetsList(tokenThemesSetsSidebar, [
|
|
"core",
|
|
"colors",
|
|
"nested",
|
|
"light",
|
|
"dark",
|
|
]);
|
|
|
|
// Create set in group
|
|
await tokenThemesSetsSidebar
|
|
.getByRole("button", { name: "core" })
|
|
.click({ button: "right" });
|
|
await expect(tokenContextMenuForSet).toBeVisible();
|
|
await tokenContextMenuForSet.getByText("Add set to this group").click();
|
|
await changeSetInput(tokenThemesSetsSidebar, "sizes/small");
|
|
|
|
await assertSetsList(tokenThemesSetsSidebar, [
|
|
"core",
|
|
"colors",
|
|
"nested",
|
|
"light",
|
|
"dark",
|
|
"sizes",
|
|
"small",
|
|
]);
|
|
|
|
// User deletes set
|
|
await tokenThemesSetsSidebar
|
|
.getByRole("button", { name: "nested" })
|
|
.click({ button: "right" });
|
|
await expect(tokenContextMenuForSet).toBeVisible();
|
|
await tokenContextMenuForSet.getByText("Delete").click();
|
|
|
|
await assertSetsList(tokenThemesSetsSidebar, [
|
|
"core",
|
|
"colors",
|
|
"dark",
|
|
"sizes",
|
|
"small",
|
|
]);
|
|
|
|
// User deletes all sets
|
|
await tokenThemesSetsSidebar
|
|
.getByRole("button", { name: "core" })
|
|
.click({ button: "right" });
|
|
await expect(tokenContextMenuForSet).toBeVisible();
|
|
await tokenContextMenuForSet.getByText("Delete").click();
|
|
|
|
await assertEmptySetsList(tokenThemesSetsSidebar);
|
|
});
|
|
|
|
test("User can create & edit sets and set groups with an identical name", async ({
|
|
page,
|
|
}) => {
|
|
const { tokenThemesSetsSidebar, tokenContextMenuForSet } =
|
|
await setupEmptyTokensFile(page);
|
|
|
|
const tokensTabButton = tokenThemesSetsSidebar
|
|
.getByRole("button", { name: "Add set" })
|
|
.click();
|
|
|
|
await createSet(tokenThemesSetsSidebar, "core/colors");
|
|
await createSet(tokenThemesSetsSidebar, "core");
|
|
await assertSetsList(tokenThemesSetsSidebar, ["core", "colors", "core"]);
|
|
await tokenThemesSetsSidebar
|
|
.getByRole("button", { name: "core" })
|
|
.nth(0)
|
|
.dblclick();
|
|
await changeSetInput(tokenThemesSetsSidebar, "core-group-renamed");
|
|
await assertSetsList(tokenThemesSetsSidebar, [
|
|
"core-group-renamed",
|
|
"colors",
|
|
"core",
|
|
]);
|
|
|
|
await page.keyboard.press(`ControlOrMeta+z`);
|
|
await assertSetsList(tokenThemesSetsSidebar, ["core", "colors", "core"]);
|
|
|
|
await tokenThemesSetsSidebar
|
|
.getByRole("button", { name: "core" })
|
|
.nth(1)
|
|
.dblclick();
|
|
await changeSetInput(tokenThemesSetsSidebar, "core-set-renamed");
|
|
await assertSetsList(tokenThemesSetsSidebar, [
|
|
"core",
|
|
"colors",
|
|
"core-set-renamed",
|
|
]);
|
|
});
|
|
|
|
test("Fold/Unfold set", async ({ page }) => {
|
|
const { tokenThemesSetsSidebar, tokenSetGroupItems } =
|
|
await setupTokensFile(page);
|
|
|
|
await expect(tokenThemesSetsSidebar).toBeVisible();
|
|
|
|
const darkSet = tokenThemesSetsSidebar.getByRole("button", {
|
|
name: "dark",
|
|
exact: true,
|
|
});
|
|
|
|
await expect(darkSet).toBeVisible();
|
|
|
|
const setGroup = await tokenSetGroupItems
|
|
.filter({ hasText: "LightDark" })
|
|
.first();
|
|
|
|
const setCollapsable = await setGroup
|
|
.getByTestId("tokens-set-group-collapse")
|
|
.first();
|
|
|
|
await setCollapsable.click();
|
|
|
|
await expect(darkSet).toHaveCount(0);
|
|
});
|
|
|
|
test("Change current theme", async ({ page }) => {
|
|
const { tokenThemesSetsSidebar, tokenSetItems } =
|
|
await setupTokensFile(page);
|
|
|
|
await expect(tokenSetItems.nth(1)).toHaveAttribute("aria-checked", "true");
|
|
await expect(tokenSetItems.nth(2)).toHaveAttribute("aria-checked", "false");
|
|
|
|
await tokenThemesSetsSidebar.getByTestId("theme-select").click();
|
|
await page
|
|
.getByTestId("theme-select-dropdown")
|
|
.getByRole("option", { name: "Dark", exact: true })
|
|
.click();
|
|
|
|
await expect(tokenSetItems.nth(1)).toHaveAttribute("aria-checked", "false");
|
|
await expect(tokenSetItems.nth(2)).toHaveAttribute("aria-checked", "true");
|
|
});
|
|
});
|
|
|
|
test.describe("Tokens: Themes modal", () => {
|
|
test("Delete theme", async ({ page }) => {
|
|
const { tokenThemeUpdateCreateModal, workspacePage } =
|
|
await setupTokensFile(page);
|
|
|
|
workspacePage.openTokenThemesModal();
|
|
|
|
await expect(
|
|
tokenThemeUpdateCreateModal.getByRole("button", { name: "Delete theme" }),
|
|
).toHaveCount(2);
|
|
|
|
await tokenThemeUpdateCreateModal
|
|
.getByRole("button", { name: "Delete theme" })
|
|
.first()
|
|
.click();
|
|
|
|
await expect(
|
|
tokenThemeUpdateCreateModal.getByRole("button", { name: "Delete theme" }),
|
|
).toHaveCount(1);
|
|
});
|
|
|
|
test("Add new theme in empty file", async ({ page }) => {
|
|
const { tokenThemesSetsSidebar, tokenThemeUpdateCreateModal } =
|
|
await setupEmptyTokensFile(page);
|
|
|
|
await tokenThemesSetsSidebar
|
|
.getByRole("button", { name: "Create one." })
|
|
.first()
|
|
.click();
|
|
|
|
await expect(tokenThemeUpdateCreateModal).toBeVisible();
|
|
|
|
const groupInput = tokenThemeUpdateCreateModal.getByLabel("Group");
|
|
const nameInput = tokenThemeUpdateCreateModal.getByLabel("Theme");
|
|
const saveButton = tokenThemeUpdateCreateModal.getByRole("button", {
|
|
name: "Save theme",
|
|
});
|
|
|
|
await groupInput.fill("New Group name");
|
|
await nameInput.fill("New Theme name");
|
|
|
|
await checkInputFieldWithoutError(tokenThemeUpdateCreateModal, nameInput);
|
|
await expect(saveButton).not.toBeDisabled();
|
|
|
|
await saveButton.click();
|
|
|
|
await expect(
|
|
tokenThemeUpdateCreateModal.getByText("New Theme name"),
|
|
).toBeVisible();
|
|
await expect(
|
|
tokenThemeUpdateCreateModal.getByText("New Group name"),
|
|
).toBeVisible();
|
|
});
|
|
|
|
test("Add new theme", async ({ page }) => {
|
|
const { tokenThemeUpdateCreateModal, workspacePage } =
|
|
await setupTokensFile(page);
|
|
|
|
workspacePage.openTokenThemesModal();
|
|
|
|
await tokenThemeUpdateCreateModal
|
|
.getByRole("button", {
|
|
name: "Add new theme",
|
|
})
|
|
.click();
|
|
|
|
const groupInput = tokenThemeUpdateCreateModal.getByLabel("Group");
|
|
const nameInput = tokenThemeUpdateCreateModal.getByLabel("Theme");
|
|
const saveButton = tokenThemeUpdateCreateModal.getByRole("button", {
|
|
name: "Save theme",
|
|
});
|
|
|
|
await groupInput.fill("Core"); // Invalid because "Core / Light" theme already exists
|
|
await nameInput.fill("Light");
|
|
|
|
await checkInputFieldWithError(tokenThemeUpdateCreateModal, nameInput);
|
|
await expect(saveButton).toBeDisabled();
|
|
|
|
await groupInput.fill("New Group name");
|
|
await nameInput.fill("New Theme name");
|
|
|
|
await checkInputFieldWithoutError(tokenThemeUpdateCreateModal, nameInput);
|
|
await expect(saveButton).not.toBeDisabled();
|
|
|
|
await saveButton.click();
|
|
|
|
await expect(
|
|
tokenThemeUpdateCreateModal.getByText("New Theme name"),
|
|
).toBeVisible();
|
|
await expect(
|
|
tokenThemeUpdateCreateModal.getByText("New Group name"),
|
|
).toBeVisible();
|
|
});
|
|
|
|
test("Edit theme", async ({ page }) => {
|
|
const { tokenThemeUpdateCreateModal, workspacePage } =
|
|
await setupTokensFile(page);
|
|
|
|
workspacePage.openTokenThemesModal();
|
|
|
|
await expect(
|
|
tokenThemeUpdateCreateModal.getByText("no sets"),
|
|
).not.toBeVisible();
|
|
await expect(
|
|
tokenThemeUpdateCreateModal.getByText("3 active sets"),
|
|
).toHaveCount(2);
|
|
|
|
await tokenThemeUpdateCreateModal
|
|
.getByText("3 active sets")
|
|
.first()
|
|
.click();
|
|
|
|
const groupInput = tokenThemeUpdateCreateModal.getByLabel("Group");
|
|
const nameInput = tokenThemeUpdateCreateModal.getByLabel("Theme");
|
|
const saveButton = tokenThemeUpdateCreateModal.getByRole("button", {
|
|
name: "Save theme",
|
|
});
|
|
|
|
await groupInput.fill("Core"); // Invalid because "Core / Dark" theme already exists
|
|
await nameInput.fill("Dark");
|
|
|
|
await checkInputFieldWithError(tokenThemeUpdateCreateModal, nameInput);
|
|
await expect(saveButton).toBeDisabled();
|
|
|
|
await groupInput.fill("Core"); // Valid because "Core / Light" theme already exists
|
|
await nameInput.fill("Light"); // but it's the same theme we are editing
|
|
|
|
await checkInputFieldWithoutError(tokenThemeUpdateCreateModal, nameInput);
|
|
await expect(saveButton).not.toBeDisabled();
|
|
|
|
await nameInput.fill("Changed Theme name"); // New names should be also valid
|
|
await groupInput.fill("Changed Group name");
|
|
|
|
await checkInputFieldWithoutError(tokenThemeUpdateCreateModal, nameInput);
|
|
await expect(saveButton).not.toBeDisabled();
|
|
|
|
expect(await nameInput.getAttribute("aria-invalid")).toBeNull();
|
|
expect(await nameInput.getAttribute("aria-describedby")).toBeNull();
|
|
|
|
const checkboxes = await tokenThemeUpdateCreateModal
|
|
.locator('[role="checkbox"]')
|
|
.all();
|
|
|
|
for (const checkbox of checkboxes) {
|
|
const isChecked = await checkbox.getAttribute("aria-checked");
|
|
|
|
if (isChecked === "true") {
|
|
await checkbox.click();
|
|
}
|
|
}
|
|
|
|
const firstButton = await tokenThemeUpdateCreateModal
|
|
.getByTestId("tokens-set-item")
|
|
.first();
|
|
|
|
await firstButton.click();
|
|
|
|
await expect(saveButton).not.toBeDisabled();
|
|
|
|
await saveButton.click();
|
|
|
|
await expect(
|
|
tokenThemeUpdateCreateModal.getByText("Changed Theme name"),
|
|
).toBeVisible();
|
|
await expect(
|
|
tokenThemeUpdateCreateModal.getByText("Changed Group name"),
|
|
).toBeVisible();
|
|
});
|
|
});
|
|
|
|
test.describe("Tokens: Apply token", () => {
|
|
test("User applies color token to a shape", async ({ page }) => {
|
|
const { workspacePage, tokensSidebar, tokenContextMenuForToken } =
|
|
await setupTokensFile(page);
|
|
|
|
await page.getByRole("tab", { name: "Layers" }).click();
|
|
|
|
await workspacePage.layers
|
|
.getByTestId("layer-row")
|
|
.filter({ hasText: "Button" })
|
|
.click();
|
|
|
|
const tokensTabButton = page.getByRole("tab", { name: "Tokens" });
|
|
await tokensTabButton.click();
|
|
|
|
await tokensSidebar
|
|
.getByRole("button")
|
|
.filter({ hasText: "Color" })
|
|
.click();
|
|
|
|
await tokensSidebar
|
|
.getByRole("button", { name: "colors.black" })
|
|
.click({ button: "right" });
|
|
await tokenContextMenuForToken.getByText("Fill").click();
|
|
|
|
await expect(
|
|
workspacePage.page.getByLabel("Name: colors.black"),
|
|
).toBeVisible();
|
|
});
|
|
|
|
test("User applies typography token to a text shape", async ({ page }) => {
|
|
const { workspacePage, tokensSidebar, tokenContextMenuForToken } =
|
|
await setupTypographyTokensFile(page);
|
|
|
|
await page.getByRole("tab", { name: "Layers" }).click();
|
|
|
|
await workspacePage.layers
|
|
.getByTestId("layer-row")
|
|
.filter({ hasText: "Some Text" })
|
|
.click();
|
|
|
|
const tokensTabButton = page.getByRole("tab", { name: "Tokens" });
|
|
await tokensTabButton.click();
|
|
|
|
await tokensSidebar
|
|
.getByRole("button")
|
|
.filter({ hasText: "Typography" })
|
|
.click();
|
|
|
|
await tokensSidebar.getByRole("button", { name: "Full" }).click();
|
|
|
|
const fontSizeInput = workspacePage.rightSidebar.getByRole("textbox", {
|
|
name: "Font Size",
|
|
});
|
|
await expect(fontSizeInput).toBeVisible();
|
|
await expect(fontSizeInput).toHaveValue("100");
|
|
});
|
|
|
|
test("User edits typography token and all fields are valid", async ({
|
|
page,
|
|
}) => {
|
|
const { tokensUpdateCreateModal, tokenThemesSetsSidebar, tokensSidebar } =
|
|
await setupTypographyTokensFile(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
|
|
.getByLabel("Font family")
|
|
.first();
|
|
await fontFamilyField.fill("OneWord");
|
|
|
|
// Invalidate incorrect values for font size
|
|
const fontSizeField = tokensUpdateCreateModal.getByLabel(/Font Size/i);
|
|
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 expect(saveButton).toBeDisabled();
|
|
|
|
// Fill in values for all fields and verify they persist when switching tabs
|
|
await fontSizeField.fill("16");
|
|
await expect(saveButton).toBeEnabled();
|
|
|
|
const fontWeightField = tokensUpdateCreateModal.getByLabel(/Font Weight/i);
|
|
const letterSpacingField =
|
|
tokensUpdateCreateModal.getByLabel(/Letter Spacing/i);
|
|
const lineHeightField = tokensUpdateCreateModal.getByLabel(/Line Height/i);
|
|
const textCaseField = tokensUpdateCreateModal.getByLabel(/Text Case/i);
|
|
const textDecorationField =
|
|
tokensUpdateCreateModal.getByLabel(/Text Decoration/i);
|
|
|
|
// 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 cant submit empty typography token or reference", async ({
|
|
page,
|
|
}) => {
|
|
const { tokensUpdateCreateModal, tokenThemesSetsSidebar, tokensSidebar } =
|
|
await setupTypographyTokensFile(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.getByLabel("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 adds typography token with reference", async ({ page }) => {
|
|
const { tokensUpdateCreateModal, tokenThemesSetsSidebar, tokensSidebar } =
|
|
await setupTypographyTokensFile(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");
|
|
referenceTabButton.click();
|
|
|
|
const referenceField = tokensUpdateCreateModal.getByLabel("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 adds shadow token with multiple shadows and applies it to shape", async ({
|
|
page,
|
|
}) => {
|
|
const {
|
|
tokensUpdateCreateModal,
|
|
tokensSidebar,
|
|
workspacePage,
|
|
tokenContextMenuForToken,
|
|
} = await setupTokensFile(page, { flags: ["enable-token-shadow"] });
|
|
|
|
const tokensTabPanel = page.getByRole("tabpanel", { name: "tokens" });
|
|
|
|
await test.step("Stage 1: Basic open", async () => {
|
|
// User adds shadow via the sidebar
|
|
await tokensTabPanel
|
|
.getByRole("button", { name: "Add Token: Shadow" })
|
|
.click();
|
|
|
|
await expect(tokensUpdateCreateModal).toBeVisible();
|
|
|
|
const nameField = tokensUpdateCreateModal.getByLabel("Name");
|
|
await nameField.fill("shadow.primary");
|
|
|
|
// User adds first shadow with a color from the color ramp
|
|
const firstShadowFields = tokensUpdateCreateModal.getByTestId(
|
|
"shadow-input-fields-0",
|
|
);
|
|
await expect(firstShadowFields).toBeVisible();
|
|
|
|
// Fill in the shadow values
|
|
const offsetXInput = firstShadowFields.getByLabel("X");
|
|
const offsetYInput = firstShadowFields.getByLabel("Y");
|
|
const blurInput = firstShadowFields.getByRole("textbox", {
|
|
name: "Blur",
|
|
});
|
|
const spreadInput = firstShadowFields.getByRole("textbox", {
|
|
name: "Spread",
|
|
});
|
|
|
|
await offsetXInput.fill("2");
|
|
await offsetYInput.fill("2");
|
|
await blurInput.fill("4");
|
|
await spreadInput.fill("0");
|
|
|
|
// Add color using the color picker
|
|
const colorBullet = firstShadowFields.getByTestId(
|
|
"token-form-color-bullet",
|
|
);
|
|
await colorBullet.click();
|
|
|
|
// Click on the color ramp to select a color
|
|
const valueSaturationSelector = tokensUpdateCreateModal.getByTestId(
|
|
"value-saturation-selector",
|
|
);
|
|
await expect(valueSaturationSelector).toBeVisible();
|
|
await valueSaturationSelector.click({ position: { x: 50, y: 50 } });
|
|
|
|
// Verify that a color value was set
|
|
const colorInput = firstShadowFields.getByRole("textbox", {
|
|
name: "Color",
|
|
});
|
|
await expect(colorInput).toHaveValue(/^rgb(.*)$/);
|
|
|
|
// Wait for validation to complete
|
|
await expect(
|
|
tokensUpdateCreateModal.getByText(/Resolved value:/).first(),
|
|
).toBeVisible();
|
|
|
|
// Save button should be enabled
|
|
const submitButton = tokensUpdateCreateModal.getByRole("button", {
|
|
name: "Save",
|
|
});
|
|
await expect(submitButton).toBeEnabled();
|
|
});
|
|
|
|
await test.step("Stage 2: Shadow adding/removing works", async () => {
|
|
const firstShadowFields = tokensUpdateCreateModal.getByTestId(
|
|
"shadow-input-fields-0",
|
|
);
|
|
const colorInput = firstShadowFields.getByRole("textbox", {
|
|
name: "Color",
|
|
});
|
|
const firstColorValue = await colorInput.inputValue();
|
|
|
|
// User adds a second shadow
|
|
const addButton = tokensUpdateCreateModal.getByRole("button", {
|
|
name: "Add Shadow",
|
|
});
|
|
await addButton.click();
|
|
|
|
const secondShadowFields = tokensUpdateCreateModal.getByTestId(
|
|
"shadow-input-fields-1",
|
|
);
|
|
await expect(secondShadowFields).toBeVisible();
|
|
|
|
// User adds a third shadow
|
|
await addButton.click();
|
|
|
|
const thirdShadowFields = tokensUpdateCreateModal.getByTestId(
|
|
"shadow-input-fields-2",
|
|
);
|
|
await expect(thirdShadowFields).toBeVisible();
|
|
|
|
// User adds values for the third shadow
|
|
const thirdOffsetXInput = thirdShadowFields.getByLabel("X");
|
|
const thirdOffsetYInput = thirdShadowFields.getByLabel("Y");
|
|
const thirdBlurInput = thirdShadowFields.getByRole("textbox", {
|
|
name: "Blur",
|
|
});
|
|
const thirdSpreadInput = thirdShadowFields.getByRole("textbox", {
|
|
name: "Spread",
|
|
});
|
|
const thirdColorInput = thirdShadowFields.getByRole("textbox", {
|
|
name: "Color",
|
|
});
|
|
|
|
await thirdOffsetXInput.fill("10");
|
|
await thirdOffsetYInput.fill("10");
|
|
await thirdBlurInput.fill("20");
|
|
await thirdSpreadInput.fill("5");
|
|
await thirdColorInput.fill("#FF0000");
|
|
|
|
// User removes the 2nd shadow
|
|
const removeButton2 = secondShadowFields.getByRole("button", {
|
|
name: "Remove Shadow",
|
|
});
|
|
await removeButton2.click();
|
|
|
|
// Verify that we have only two shadow fields
|
|
await expect(thirdShadowFields).not.toBeVisible();
|
|
|
|
// Verify that the first shadow kept its values
|
|
const firstOffsetXValue = await firstShadowFields
|
|
.getByLabel("X")
|
|
.inputValue();
|
|
const firstOffsetYValue = await firstShadowFields
|
|
.getByLabel("Y")
|
|
.inputValue();
|
|
const firstBlurValue = await firstShadowFields
|
|
.getByRole("textbox", { name: "Blur" })
|
|
.inputValue();
|
|
const firstSpreadValue = await firstShadowFields
|
|
.getByRole("textbox", { name: "Spread" })
|
|
.inputValue();
|
|
const firstColorValueAfter = await firstShadowFields
|
|
.getByRole("textbox", { name: "Color" })
|
|
.inputValue();
|
|
|
|
await expect(firstOffsetXValue).toBe("2");
|
|
await expect(firstOffsetYValue).toBe("2");
|
|
await expect(firstBlurValue).toBe("4");
|
|
await expect(firstSpreadValue).toBe("0");
|
|
await expect(firstColorValueAfter).toBe(firstColorValue);
|
|
|
|
// Verify that the second kept its values (after shadow 3)
|
|
// After removing index 1, the third shadow becomes the second shadow at index 1
|
|
const newSecondShadowFields = tokensUpdateCreateModal.getByTestId(
|
|
"shadow-input-fields-1",
|
|
);
|
|
await expect(newSecondShadowFields).toBeVisible();
|
|
|
|
const secondOffsetXValue = await newSecondShadowFields
|
|
.getByLabel("X")
|
|
.inputValue();
|
|
const secondOffsetYValue = await newSecondShadowFields
|
|
.getByLabel("Y")
|
|
.inputValue();
|
|
const secondBlurValue = await newSecondShadowFields
|
|
.getByRole("textbox", { name: "Blur" })
|
|
.inputValue();
|
|
const secondSpreadValue = await newSecondShadowFields
|
|
.getByRole("textbox", { name: "Spread" })
|
|
.inputValue();
|
|
const secondColorValue = await newSecondShadowFields
|
|
.getByRole("textbox", { name: "Color" })
|
|
.inputValue();
|
|
|
|
await expect(secondOffsetXValue).toBe("10");
|
|
await expect(secondOffsetYValue).toBe("10");
|
|
await expect(secondBlurValue).toBe("20");
|
|
await expect(secondSpreadValue).toBe("5");
|
|
await expect(secondColorValue).toBe("#FF0000");
|
|
});
|
|
|
|
await test.step("Stage 3: Restore when switching tabs works", async () => {
|
|
const firstShadowFields = tokensUpdateCreateModal.getByTestId(
|
|
"shadow-input-fields-0",
|
|
);
|
|
const newSecondShadowFields = tokensUpdateCreateModal.getByTestId(
|
|
"shadow-input-fields-1",
|
|
);
|
|
const colorInput = firstShadowFields.getByRole("textbox", {
|
|
name: "Color",
|
|
});
|
|
const firstColorValue = await colorInput.inputValue();
|
|
|
|
// Switch to reference tab
|
|
const referenceTabButton =
|
|
tokensUpdateCreateModal.getByTestId("reference-opt");
|
|
await referenceTabButton.click();
|
|
|
|
// Verify we're in reference mode - the composite fields should not be visible
|
|
await expect(firstShadowFields).not.toBeVisible();
|
|
|
|
// Switch back to composite tab
|
|
const compositeTabButton =
|
|
tokensUpdateCreateModal.getByTestId("composite-opt");
|
|
await compositeTabButton.click();
|
|
|
|
// Verify that shadows are restored
|
|
await expect(firstShadowFields).toBeVisible();
|
|
await expect(newSecondShadowFields).toBeVisible();
|
|
|
|
// Verify first shadow values are still there
|
|
const restoredFirstOffsetX = await firstShadowFields
|
|
.getByLabel("X")
|
|
.inputValue();
|
|
const restoredFirstOffsetY = await firstShadowFields
|
|
.getByLabel("Y")
|
|
.inputValue();
|
|
const restoredFirstBlur = await firstShadowFields
|
|
.getByRole("textbox", { name: "Blur" })
|
|
.inputValue();
|
|
const restoredFirstSpread = await firstShadowFields
|
|
.getByRole("textbox", { name: "Spread" })
|
|
.inputValue();
|
|
const restoredFirstColor = await firstShadowFields
|
|
.getByRole("textbox", { name: "Color" })
|
|
.inputValue();
|
|
|
|
await expect(restoredFirstOffsetX).toBe("2");
|
|
await expect(restoredFirstOffsetY).toBe("2");
|
|
await expect(restoredFirstBlur).toBe("4");
|
|
await expect(restoredFirstSpread).toBe("0");
|
|
await expect(restoredFirstColor).toBe(firstColorValue);
|
|
|
|
// Verify second shadow values are still there
|
|
const restoredSecondOffsetX = await newSecondShadowFields
|
|
.getByLabel("X")
|
|
.inputValue();
|
|
const restoredSecondOffsetY = await newSecondShadowFields
|
|
.getByLabel("Y")
|
|
.inputValue();
|
|
const restoredSecondBlur = await newSecondShadowFields
|
|
.getByRole("textbox", { name: "Blur" })
|
|
.inputValue();
|
|
const restoredSecondSpread = await newSecondShadowFields
|
|
.getByRole("textbox", { name: "Spread" })
|
|
.inputValue();
|
|
const restoredSecondColor = await newSecondShadowFields
|
|
.getByRole("textbox", { name: "Color" })
|
|
.inputValue();
|
|
|
|
await expect(restoredSecondOffsetX).toBe("10");
|
|
await expect(restoredSecondOffsetY).toBe("10");
|
|
await expect(restoredSecondBlur).toBe("20");
|
|
await expect(restoredSecondSpread).toBe("5");
|
|
await expect(restoredSecondColor).toBe("#FF0000");
|
|
});
|
|
|
|
await test.step("Stage 4: Layer application works", async () => {
|
|
// Save the token
|
|
const submitButton = tokensUpdateCreateModal.getByRole("button", {
|
|
name: "Save",
|
|
});
|
|
await submitButton.click();
|
|
await expect(tokensUpdateCreateModal).not.toBeVisible();
|
|
|
|
// Verify token appears in sidebar
|
|
const shadowToken = tokensSidebar.getByRole("button", {
|
|
name: "shadow.primary",
|
|
});
|
|
await expect(shadowToken).toBeEnabled();
|
|
|
|
// Apply the shadow
|
|
await workspacePage.clickLayers();
|
|
await workspacePage.clickLeafLayer("Button");
|
|
|
|
const shadowSection = workspacePage.rightSidebar.getByText("Drop shadow");
|
|
await expect(shadowSection).toHaveCount(0);
|
|
|
|
await page.getByRole("tab", { name: "Tokens" }).click();
|
|
await shadowToken.click();
|
|
|
|
await expect(shadowSection).toHaveCount(2);
|
|
});
|
|
});
|
|
});
|