mirror of
https://github.com/penpot/penpot.git
synced 2026-09-22 20:06:16 +00:00
* ✨ 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>
295 lines
9.0 KiB
JavaScript
295 lines
9.0 KiB
JavaScript
import { test, expect } from "@playwright/test";
|
|
import { WasmWorkspacePage } from "../../pages/WasmWorkspacePage";
|
|
import { BaseWebSocketPage } from "../../pages/BaseWebSocketPage";
|
|
import { setupEmptyTokensFileRender, setupTokensFileRender } from "./helpers";
|
|
|
|
// THEMES HELPERS
|
|
|
|
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 (inputLocator) => {
|
|
expect(await inputLocator.getAttribute("aria-invalid")).toBeNull();
|
|
expect(await inputLocator.getAttribute("aria-describedby")).toBeNull();
|
|
};
|
|
|
|
test.beforeEach(async ({ page }) => {
|
|
await WasmWorkspacePage.init(page);
|
|
await WasmWorkspacePage.mockConfigFlags(page, [
|
|
"enable-feature-design-tokens-v1",
|
|
]);
|
|
await BaseWebSocketPage.mockRPC(page, "get-teams", "get-teams-tokens.json");
|
|
});
|
|
|
|
test.describe("Tokens Themes", () => {
|
|
test("User edits theme and activates it in the sidebar", async ({ page }) => {
|
|
const { tokenThemesSetsSidebar, tokenThemeUpdateCreateModal } =
|
|
await setupTokensFileRender(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("switch", {
|
|
name: "Changed",
|
|
})
|
|
.click();
|
|
|
|
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.describe("Tokens: Themes modal", () => {
|
|
test("Delete theme", async ({ page }) => {
|
|
const { tokenThemeUpdateCreateModal, workspacePage } =
|
|
await setupTokensFileRender(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 setupEmptyTokensFileRender(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);
|
|
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 setupTokensFileRender(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);
|
|
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 setupTokensFileRender(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);
|
|
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);
|
|
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();
|
|
});
|
|
});
|