penpot/frontend/playwright/ui/specs/tokens/libraries.spec.js
Andrés Moya 3cd9bfa9de
Add sync with design tokens in external libraries (#10293)
*  Auto link tokens when adding external libraries (provisional)

* 🔧 Refactor tokens-lib initialization

* 🔧 Add separated TokenStatus to store status apart of TokensLib

* 🔧 Make all status operations use the new data structure

* 🔧 Normalize status helper functions and access token sets by id

* 🔧 Rename :tokens-file to :tokens-source

* 🎉 Allow the user to choose the tokens-source of a file

* 🎉 Make tokens library readonly when it's in an external file

* 🎉 Show tokens in library summaries

* 🎉 Show source info in sidebar

* 🔧 Fix integration tests

* 🐛 Propagate changes of token values in external library

* 🎉 Layout updates

* 🔧 Refactor tokens source calculations

* 🔧 Add harder checks for nil or empty values in everything

* 🐛 Fix some integration tests

* 🔧 Add integration tests for tokens in external libs

* 🔧 Validate and repair missing tokens status

* 🎉 Make ui changes optional with config flag

* 🐛 Propagate tokens after synchronizing components in ext library

* 🐛 Propagate tokens after creating new instances

* 🐛 Propagate tokens after synchronizing tokens in ext library

* 🐛 Add a tokens source icon to libraries section (#11439)

* 🐛 Add a tokens source icon to libraries section

* 🐛 Fix ellipsis on library names

* ♻️ Remove code under flag on legacy component

* 🐛 Fix token theme name on inspect tab

* 🎉 Add changes notification (#11476)

* 🎉 Add changes notification

* ♻️ Change fn names

* 🐛 Fix tokens source label truncation and missing translations (#11533)

* 🐛 Fix tokens source label truncation and missing translations

The tokens source file name always showed, even for the current file,
and long names wrapped onto a second line instead of truncating
because the header used flex-wrap and overflow-wrap: break-word
instead of single-line ellipsis.

Show the source row unconditionally (it now displays "This file" when
the source is the current file, matching the connected-library case),
truncate the file name to one line with an ellipsis, and only attach a
tooltip with the full name when the text is actually truncated.

Replace the hardcoded UI strings with translated ones and add their
English and Spanish entries.

AI-assisted-by: claude-sonnet-5

* 🐛 Remove redundant effect dependency in tokens source

file-name-truncated? was listed as a dependency of the with-effect
that checks and observes label truncation, even though it isn't
read inside the effect body. Since the effect itself flips that
state via check-file-name-truncated, including it as a dependency
caused the ResizeObserver to be needlessly disconnected and
reconnected on every truncation change.

AI-assisted-by: claude-sonnet-5

* 🐛 Fix small visual error

* 🐛 Fix problem with plugins

* 🐛 Fix playwright tests

---------

Co-authored-by: Eva Marco <evamarcod@gmail.com>
Co-authored-by: Eva Marco <eva.marco@kaleidos.net>
Co-authored-by: alonso.torres <alonso.torres@kaleidos.net>
2026-09-21 15:37:45 +02:00

164 lines
4.8 KiB
JavaScript

import { test, expect } from "@playwright/test";
import { WasmWorkspacePage } from "../../pages/WasmWorkspacePage";
import {
setupEmptyTokensFileRender,
createToken,
unfoldTokenType,
} from "./helpers";
test.beforeEach(async ({ page }) => {
await WasmWorkspacePage.init(page);
});
test("File with tokens is its own tokens source", async ({ page }) => {
await WasmWorkspacePage.mockConfigFlags(page, ["enable-token-lib-sync"]);
const workspacePage = new WasmWorkspacePage(page);
await workspacePage.setupEmptyFile(page);
await workspacePage.mockRPC(
"get-team-shared-files?team-id=*",
"workspace/get-team-shared-libraries-non-empty.json",
);
await workspacePage.goToWorkspace();
// A file without tokens is not tokens source
await workspacePage.clickAssets();
await workspacePage.openLibrariesModal();
await workspacePage.librariesModal
.getByRole("tab", { name: "This file" })
.click();
await expect(
workspacePage.librariesModal.getByText("Tokens source"),
).not.toBeVisible();
await workspacePage.closeLibrariesModal();
// Create a token in the file
await workspacePage.clickTokens();
await createToken(
page,
"Color",
"color.primary",
"Value",
"textbox",
"#ff0000",
);
// Now it is a tokens source
await workspacePage.clickAssets();
await workspacePage.openLibrariesModal();
await workspacePage.librariesModal
.getByRole("tab", { name: "This file" })
.click();
await expect(
workspacePage.librariesModal.getByText("Tokens source"),
).toBeVisible();
});
test("User sets a library as tokens source and then they can use the tokens in it", async ({
page,
}) => {
await WasmWorkspacePage.mockConfigFlags(page, ["enable-token-lib-sync"]);
// Set up a file linked to a library with tokens
const { workspacePage, tokenThemesSetsSidebar, tokenContextMenuForSet } =
await setupEmptyTokensFileRender(page);
await workspacePage.mockGetFile("workspace/get-file-with-lib.json");
await workspacePage.mockRPC(
"get-file-libraries?file-id=*",
"workspace/get-file-libraries-tokens.json",
);
await workspacePage.mockRPC(
/get\-file\?id=c7ce0794-0992-8105-8004-38f280443849/,
"workspace/get-file-tokens.json",
);
await workspacePage.mockRPC(
/get\-file\-fragment\?/,
"workspace/get-file-fragment-tokens.json",
);
await workspacePage.mockRPC(
"get-team-shared-files?team-id=*",
"workspace/get-team-shared-libraries-non-empty.json",
);
await workspacePage.goToWorkspace();
// Set the external library as the tokens source
await workspacePage.clickAssets();
await workspacePage.openLibrariesModal();
await workspacePage.librariesModal
.getByRole("tab", { name: "This file" })
.click();
await workspacePage.librariesModal
.getByRole("button", { name: "Set as tokens source" })
.click();
await workspacePage.closeLibrariesModal();
// Check that we can apply a token from the library to a shape
await workspacePage.sidebar.getByRole("tab", { name: "Layers" }).click();
await workspacePage.layers
.getByTestId("layer-row")
.filter({ hasText: "Rectangle" })
.click();
// Apply a color token from the library as the shape fill
await workspacePage.sidebar.getByRole("tab", { name: "Tokens" }).click();
await unfoldTokenType(workspacePage.tokensSidebar, "Color");
const colorTokenName = "colors.red.600";
await workspacePage.tokensSidebar
.getByRole("button", { name: colorTokenName })
.click({ button: "right" });
await workspacePage.tokenContextMenuForToken.getByText("Fill").click();
// Check that the token has been correctly applied in the right sidebar
const fillSection = workspacePage.rightSidebar.getByRole("region", {
name: "Fill section",
});
await expect(fillSection).toBeVisible();
const fillTokenPill = fillSection.getByLabel(colorTokenName, {
exact: true,
});
await expect(fillTokenPill).toBeVisible();
// Go back to the libraries modal and set the current file as tokens source
await workspacePage.clickAssets();
await workspacePage.openLibrariesModal();
await workspacePage.librariesModal
.getByRole("tab", { name: "This file" })
.click();
await workspacePage.librariesModal
.getByRole("button", { name: "Set as tokens source" })
.click();
await workspacePage.closeLibrariesModal();
// Check that the applied token now looks broken
await expect(fillSection).toBeVisible();
const brokenTokenPill = fillSection.getByLabel(colorTokenName, {
exact: true,
});
await expect(brokenTokenPill).toBeVisible();
await brokenTokenPill.hover();
const brokenTokenTooltip = page.getByRole("tooltip", {
name: colorTokenName,
});
await expect(brokenTokenTooltip).toBeVisible();
await expect(brokenTokenTooltip).toHaveText(
`{${colorTokenName}} token does not exist or has been deleted.`,
);
});