penpot/frontend/playwright/ui/specs/tokens/detach-reference.spec.js
Eva Marco b06a43404b
🎉 Add detach to typography and shadow tokens (#11969)
* 🎉 Add detach to typography tokens

* 🎉 Add detach to shadow tokens

* 🎉 Add tests
2026-10-05 11:32:29 +02:00

195 lines
7.1 KiB
JavaScript

import { test, expect } from "@playwright/test";
import { WasmWorkspacePage } from "../../pages/WasmWorkspacePage";
import { BaseWebSocketPage } from "../../pages/BaseWebSocketPage";
import {
setupEmptyTokensFileRender,
setupTypographyTokensFileRender,
unfoldTokenType,
} from "./helpers";
test.beforeEach(async ({ page }) => {
await WasmWorkspacePage.init(page);
await BaseWebSocketPage.mockRPC(page, "get-teams", "get-teams-tokens.json");
});
const openEditModal = async (page, tokensTabPanel, name) => {
await tokensTabPanel
.getByRole("button", { name, exact: true })
.click({ button: "right" });
await page.getByText("Edit token").click();
};
// Hovers the reference input and clicks the detach button, which is only
// visible while the reference row is hovered or focused.
const detachReference = async (modal) => {
const referenceField = modal.getByRole("textbox", { name: "Reference" });
const detachButton = modal.getByRole("button", { name: "Detach reference" });
await expect(referenceField).toBeVisible();
await expect(detachButton).toBeHidden();
await referenceField.hover();
await expect(detachButton).toBeVisible();
await detachButton.click();
};
test.describe("Tokens - detach reference", () => {
test("User detaches a typography reference and edits the copied values", async ({
page,
}) => {
const { tokensUpdateCreateModal, tokensSidebar } =
await setupTypographyTokensFileRender(page);
const tokensTabPanel = page.getByRole("tabpanel", { name: "tokens" });
const saveButton = tokensUpdateCreateModal.getByRole("button", {
name: "Save",
});
// Create a typography token that references "Full" from the fixture.
await tokensTabPanel
.getByRole("button", { name: "Add Token: Typography" })
.click();
await expect(tokensUpdateCreateModal).toBeVisible();
await tokensUpdateCreateModal.getByLabel("Name").fill("detached");
await tokensUpdateCreateModal.getByTestId("reference-opt").click();
await tokensUpdateCreateModal
.getByRole("textbox", { name: "Reference" })
.fill("{Full}");
await expect(saveButton).toBeEnabled();
await saveButton.click();
await expect(tokensUpdateCreateModal).not.toBeVisible();
await unfoldTokenType(tokensSidebar, "typography");
await openEditModal(page, tokensTabPanel, "detached");
await expect(tokensUpdateCreateModal).toBeVisible();
await detachReference(tokensUpdateCreateModal);
// The values of "Full" are now in the composite tab.
const fontFamilyField = tokensUpdateCreateModal
.getByRole("textbox", { name: "Font family" })
.first();
const fontSizeField = tokensUpdateCreateModal.getByRole("textbox", {
name: "Font Size",
});
await expect(fontFamilyField).toHaveValue("42dot Sans");
await expect(fontSizeField).toHaveValue("100");
await expect(
tokensUpdateCreateModal.getByRole("textbox", { name: "Font Weight" }),
).toHaveValue("300");
await expect(
tokensUpdateCreateModal.getByRole("textbox", { name: "Line Height" }),
).toHaveValue("2");
await expect(
tokensUpdateCreateModal.getByRole("textbox", { name: "Letter Spacing" }),
).toHaveValue("2");
await expect(
tokensUpdateCreateModal.getByRole("textbox", { name: "Text Case" }),
).toHaveValue("uppercase");
await expect(
tokensUpdateCreateModal.getByRole("textbox", { name: "Text Decoration" }),
).toHaveValue("underline");
await fontSizeField.fill("24");
await expect(saveButton).toBeEnabled();
await saveButton.click();
await expect(tokensUpdateCreateModal).not.toBeVisible();
// The detached token keeps its own values.
await openEditModal(page, tokensTabPanel, "detached");
await expect(fontSizeField).toHaveValue("24");
await tokensUpdateCreateModal
.getByRole("button", { name: "Cancel" })
.click();
// The referenced token is not modified.
await openEditModal(page, tokensTabPanel, "Full");
await expect(fontSizeField).toHaveValue("100");
});
test("User detaches a shadow reference and edits the copied values", async ({
page,
}) => {
const { tokensUpdateCreateModal } = await setupEmptyTokensFileRender(page, {
flags: ["enable-token-shadow"],
});
const tokensTabPanel = page.getByRole("tabpanel", { name: "tokens" });
const addShadowButton = tokensTabPanel.getByRole("button", {
name: "Add Token: Shadow",
});
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 saveButton = tokensUpdateCreateModal.getByRole("button", {
name: "Save",
});
// Base shadow token.
await addShadowButton.click();
await expect(tokensUpdateCreateModal).toBeVisible();
await nameField.fill("base-shadow");
await colorField.fill("red");
await offsetXField.fill("2");
await offsetYField.fill("3");
await blurField.fill("5");
await spreadField.fill("1");
await expect(saveButton).toBeEnabled();
await saveButton.click();
await expect(tokensUpdateCreateModal).not.toBeVisible();
// Shadow token that references the base one.
await addShadowButton.click();
await expect(tokensUpdateCreateModal).toBeVisible();
await nameField.fill("alias-shadow");
await tokensUpdateCreateModal.getByTestId("reference-opt").click();
await tokensUpdateCreateModal
.getByRole("textbox", { name: "Reference" })
.fill("{base-shadow}");
await expect(saveButton).toBeEnabled();
await saveButton.click();
await expect(tokensUpdateCreateModal).not.toBeVisible();
await unfoldTokenType(tokensTabPanel, "shadow");
await openEditModal(page, tokensTabPanel, "alias-shadow");
await expect(tokensUpdateCreateModal).toBeVisible();
await detachReference(tokensUpdateCreateModal);
// The values of "base-shadow" are now in the shadow tab.
await expect(colorField).toHaveValue("red");
await expect(offsetXField).toHaveValue("2");
await expect(offsetYField).toHaveValue("3");
await expect(blurField).toHaveValue("5");
await expect(spreadField).toHaveValue("1");
await offsetXField.fill("10");
await expect(saveButton).toBeEnabled();
await saveButton.click();
await expect(tokensUpdateCreateModal).not.toBeVisible();
// The detached token keeps its own values.
await openEditModal(page, tokensTabPanel, "alias-shadow");
await expect(offsetXField).toHaveValue("10");
await tokensUpdateCreateModal
.getByRole("button", { name: "Cancel" })
.click();
// The referenced token is not modified.
await openEditModal(page, tokensTabPanel, "base-shadow");
await expect(offsetXField).toHaveValue("2");
});
});