mirror of
https://github.com/penpot/penpot.git
synced 2026-04-25 11:18:36 +00:00
🐛 Token tree must be expanded by default (#8799)
This commit is contained in:
parent
dff381c4fe
commit
1b68318c6b
@ -32,6 +32,7 @@
|
||||
- Fix scroll on library modal [Taiga #13639](https://tree.taiga.io/project/penpot/issue/13639)
|
||||
- Fix dates to avoid show them in english when browser is in auto [Taiga #13786](https://tree.taiga.io/project/penpot/issue/13786)
|
||||
- Fix focus radio button [Taiga #13841](https://tree.taiga.io/project/penpot/issue/13841)
|
||||
- Token tree should be expanded by default [Taiga #13631](https://tree.taiga.io/project/penpot/issue/13631)
|
||||
|
||||
## 2.15.0 (Unreleased)
|
||||
|
||||
|
||||
@ -148,16 +148,16 @@ Some naming conventions:
|
||||
:path 'one'
|
||||
:depth 0
|
||||
:leaf nil
|
||||
:children-fn (fn [] [{:name 'two'
|
||||
:path 'one.two'
|
||||
:depth 1
|
||||
:leaf nil
|
||||
:children-fn (fn [] [{... :name 'three'} {... :name 'four'}])}
|
||||
{:name 'five'
|
||||
:path 'one.five'
|
||||
:depth 1
|
||||
:leaf {... :name 'five'}
|
||||
...}])}]"
|
||||
:children [{:name 'two'
|
||||
:path 'one.two'
|
||||
:depth 1
|
||||
:leaf nil
|
||||
:children [{... :name 'three'} {... :name 'four'}]}
|
||||
{:name 'five'
|
||||
:path 'one.five'
|
||||
:depth 1
|
||||
:leaf {... :name 'five'}
|
||||
:children nil}]}]"
|
||||
|
||||
(defn- sort-by-children
|
||||
"Sorts segments so that those with children come first."
|
||||
@ -191,7 +191,7 @@ Some naming conventions:
|
||||
(into (sorted-map) grouped)))
|
||||
|
||||
(defn- build-tree-node
|
||||
"Builds a single tree node with lazy children."
|
||||
"Builds a single tree node with computed children."
|
||||
[segment-name remaining-segments separator parent-path depth]
|
||||
(let [current-path (if parent-path
|
||||
(str parent-path "." segment-name)
|
||||
@ -208,12 +208,11 @@ Some naming conventions:
|
||||
:path current-path
|
||||
:depth depth
|
||||
:leaf leaf-segment
|
||||
:children-fn (when-not is-leaf?
|
||||
(fn []
|
||||
(let [grouped-elements (sort-and-group-segments remaining-segments separator)]
|
||||
(mapv (fn [[child-segment-name remaining-child-segments]]
|
||||
(build-tree-node child-segment-name remaining-child-segments separator current-path (inc depth)))
|
||||
grouped-elements))))}]
|
||||
:children (when-not is-leaf?
|
||||
(let [grouped-elements (sort-and-group-segments remaining-segments separator)]
|
||||
(mapv (fn [[child-segment-name remaining-child-segments]]
|
||||
(build-tree-node child-segment-name remaining-child-segments separator current-path (inc depth)))
|
||||
grouped-elements)))}]
|
||||
node))
|
||||
|
||||
(defn build-tree-root
|
||||
|
||||
@ -4,7 +4,7 @@ import { WasmWorkspacePage } from "../../pages/WasmWorkspacePage";
|
||||
import {
|
||||
setupTokensFileRender,
|
||||
setupTypographyTokensFileRender,
|
||||
unfoldTokenTree,
|
||||
unfoldTokenType,
|
||||
} from "./helpers";
|
||||
|
||||
test.beforeEach(async ({ page }) => {
|
||||
@ -24,10 +24,9 @@ test.describe("Tokens: Apply token", () => {
|
||||
.filter({ hasText: "Button" })
|
||||
.click();
|
||||
|
||||
const tokensTabButton = page.getByRole("tab", { name: "Tokens" });
|
||||
await tokensTabButton.click();
|
||||
await page.getByRole("tab", { name: "Tokens" }).click();
|
||||
|
||||
unfoldTokenTree(tokensSidebar, "color", "colors.black");
|
||||
await unfoldTokenType(tokensSidebar, "color");
|
||||
|
||||
await tokensSidebar
|
||||
.getByRole("button", { name: "black" })
|
||||
@ -52,17 +51,15 @@ test.describe("Tokens: Apply token", () => {
|
||||
await workspacePage.layers.getByTestId("layer-row").nth(1).click();
|
||||
|
||||
// Open tokens sections on left sidebar
|
||||
const tokensTabButton = page.getByRole("tab", { name: "Tokens" });
|
||||
await tokensTabButton.click();
|
||||
|
||||
// Unfold border radius tokens
|
||||
await page.getByRole("button", { name: "Border Radius 3" }).click();
|
||||
await page.getByRole("tab", { name: "Tokens" }).click();
|
||||
|
||||
await unfoldTokenType(tokensSidebar, "border radius");
|
||||
await expect(
|
||||
tokensSidebar.getByRole("button", { name: "borderRadius" }),
|
||||
).toBeVisible();
|
||||
await tokensSidebar.getByRole("button", { name: "borderRadius" }).click();
|
||||
await expect(
|
||||
tokensSidebar.getByRole("button", { name: "borderRadius.sm" }),
|
||||
tokensSidebar.getByRole("button", {
|
||||
name: "borderRadius.sm",
|
||||
exact: true,
|
||||
}),
|
||||
).toBeVisible();
|
||||
|
||||
// Apply border radius token from token panels
|
||||
@ -119,13 +116,7 @@ test.describe("Tokens: Apply token", () => {
|
||||
await tokensTabButton.click();
|
||||
|
||||
// Unfold opacity tokens
|
||||
await page.getByRole("button", { name: "Opacity 3" }).click();
|
||||
await expect(
|
||||
tokensSidebar.getByRole("button", { name: "opacity", exact: true }),
|
||||
).toBeVisible();
|
||||
await tokensSidebar
|
||||
.getByRole("button", { name: "opacity", exact: true })
|
||||
.click();
|
||||
await unfoldTokenType(tokensSidebar, "opacity");
|
||||
await expect(
|
||||
tokensSidebar.getByRole("button", { name: "opacity.high" }),
|
||||
).toBeVisible();
|
||||
@ -203,12 +194,8 @@ test.describe("Tokens: Apply token", () => {
|
||||
test("User adds shadow token with multiple shadows and applies it to shape", async ({
|
||||
page,
|
||||
}) => {
|
||||
const {
|
||||
tokensUpdateCreateModal,
|
||||
tokensSidebar,
|
||||
workspacePage,
|
||||
tokenContextMenuForToken,
|
||||
} = await setupTokensFileRender(page, { flags: ["enable-token-shadow"] });
|
||||
const { tokensUpdateCreateModal, tokensSidebar, workspacePage } =
|
||||
await setupTokensFileRender(page, { flags: ["enable-token-shadow"] });
|
||||
|
||||
const tokensTabPanel = page.getByRole("tabpanel", { name: "tokens" });
|
||||
|
||||
@ -476,8 +463,6 @@ test.describe("Tokens: Apply token", () => {
|
||||
await submitButton.click();
|
||||
await expect(tokensUpdateCreateModal).not.toBeVisible();
|
||||
|
||||
unfoldTokenTree(tokensSidebar, "shadow", "primary");
|
||||
|
||||
// Verify token appears in sidebar
|
||||
const shadowToken = tokensSidebar.getByRole("button", {
|
||||
name: "primary",
|
||||
@ -512,7 +497,7 @@ test.describe("Tokens: Apply token", () => {
|
||||
const tokensTabButton = page.getByRole("tab", { name: "Tokens" });
|
||||
await tokensTabButton.click();
|
||||
|
||||
unfoldTokenTree(tokensSidebar, "dimensions", "dimension.dimension.sm");
|
||||
await unfoldTokenType(tokensSidebar, "dimensions");
|
||||
|
||||
// Apply token to width and height token from token panel
|
||||
await tokensSidebar.getByRole("button", { name: "dimension.sm" }).click();
|
||||
@ -565,7 +550,7 @@ test.describe("Tokens: Apply token", () => {
|
||||
const tokensTabButton = page.getByRole("tab", { name: "Tokens" });
|
||||
await tokensTabButton.click();
|
||||
|
||||
unfoldTokenTree(tokensSidebar, "dimensions", "dimension.dimension.sm");
|
||||
await unfoldTokenType(tokensSidebar, "dimensions");
|
||||
|
||||
// Apply token to width and height token from token panel
|
||||
await tokensSidebar
|
||||
@ -621,7 +606,7 @@ test.describe("Tokens: Apply token", () => {
|
||||
const tokensTabButton = page.getByRole("tab", { name: "Tokens" });
|
||||
await tokensTabButton.click();
|
||||
|
||||
unfoldTokenTree(tokensSidebar, "dimensions", "dimension.dimension.sm");
|
||||
await unfoldTokenType(tokensSidebar, "dimensions");
|
||||
|
||||
// Apply token to width and height token from token panel
|
||||
await tokensSidebar
|
||||
@ -677,7 +662,7 @@ test.describe("Tokens: Apply token", () => {
|
||||
const tokensTabButton = page.getByRole("tab", { name: "Tokens" });
|
||||
await tokensTabButton.click();
|
||||
|
||||
unfoldTokenTree(tokensSidebar, "dimensions", "dimension.dimension.xs");
|
||||
await unfoldTokenType(tokensSidebar, "dimensions");
|
||||
|
||||
// Apply token to width and height token from token panel
|
||||
await tokensSidebar
|
||||
@ -809,8 +794,7 @@ test.describe("Tokens: Apply token", () => {
|
||||
const tokensTab = page.getByRole("tab", { name: "Tokens" });
|
||||
await expect(tokensTab).toBeVisible();
|
||||
await tokensTab.click();
|
||||
await page.getByRole("button", { name: "Dimensions 4" }).click();
|
||||
await page.getByRole("button", { name: "dim", exact: true }).click();
|
||||
await unfoldTokenType(workspace.tokensSidebar, "dimensions");
|
||||
const tokensSidebar = workspace.tokensSidebar;
|
||||
await expect(
|
||||
tokensSidebar.getByRole("button", { name: "dim.md" }),
|
||||
@ -881,11 +865,7 @@ test.describe("Tokens: Detach token", () => {
|
||||
await tokensTabButton.click();
|
||||
|
||||
// Unfold border radius tokens
|
||||
await page.getByRole("button", { name: "Border Radius 3" }).click();
|
||||
await expect(
|
||||
tokensSidebar.getByRole("button", { name: "borderRadius" }),
|
||||
).toBeVisible();
|
||||
await tokensSidebar.getByRole("button", { name: "borderRadius" }).click();
|
||||
await unfoldTokenType(tokensSidebar, "Border Radius");
|
||||
await expect(
|
||||
tokensSidebar.getByRole("button", { name: "borderRadius.sm" }),
|
||||
).toBeVisible();
|
||||
|
||||
@ -6,7 +6,7 @@ import {
|
||||
setupTokensFileRender,
|
||||
setupTypographyTokensFileRender,
|
||||
testTokenCreationFlow,
|
||||
unfoldTokenTree,
|
||||
unfoldTokenType,
|
||||
} from "./helpers";
|
||||
|
||||
test.beforeEach(async ({ page }) => {
|
||||
@ -31,15 +31,9 @@ test.describe("Tokens - creation", () => {
|
||||
});
|
||||
|
||||
test("User creates border radius token with combobox", async ({ page }) => {
|
||||
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 setupEmptyTokensFileRender(page, {
|
||||
flags: ["enable-token-combobox", "enable-feature-token-input"],
|
||||
});
|
||||
const { tokensUpdateCreateModal } = await setupEmptyTokensFileRender(page, {
|
||||
flags: ["enable-token-combobox", "enable-feature-token-input"],
|
||||
});
|
||||
|
||||
// Open modal
|
||||
const tokensTabPanel = page.getByRole("tabpanel", { name: "tokens" });
|
||||
@ -83,8 +77,10 @@ test.describe("Tokens - creation", () => {
|
||||
|
||||
await submitButton.click();
|
||||
|
||||
await unfoldTokenType(tokensTabPanel, "border radius");
|
||||
|
||||
await expect(
|
||||
tokensTabPanel.getByRole('button', { name: 'my-token' }),
|
||||
tokensTabPanel.getByRole("button", { name: "my-token" }),
|
||||
).toBeEnabled();
|
||||
|
||||
// Create second token referencing the first one using the combobox options
|
||||
@ -310,7 +306,7 @@ test.describe("Tokens - creation", () => {
|
||||
await expect(submitButton).toBeEnabled();
|
||||
await submitButton.click();
|
||||
|
||||
await unfoldTokenTree(tokensSidebar, "color", "color.primary");
|
||||
await unfoldTokenType(tokensSidebar, "color");
|
||||
|
||||
// Create token referencing the previous one with keyboard
|
||||
|
||||
@ -477,6 +473,8 @@ test.describe("Tokens - creation", () => {
|
||||
|
||||
await submitButton.click();
|
||||
|
||||
await unfoldTokenType(tokensTabPanel, "font family");
|
||||
|
||||
await expect(
|
||||
tokensTabPanel.getByRole("button", { name: "my-token" }),
|
||||
).toBeEnabled();
|
||||
@ -631,6 +629,8 @@ test.describe("Tokens - creation", () => {
|
||||
|
||||
await submitButton.click();
|
||||
|
||||
await unfoldTokenType(tokensTabPanel, "font weight");
|
||||
|
||||
await expect(
|
||||
tokensTabPanel.getByRole("button", { name: "my-token" }),
|
||||
).toBeEnabled();
|
||||
@ -767,6 +767,8 @@ test.describe("Tokens - creation", () => {
|
||||
|
||||
await submitButton.click();
|
||||
|
||||
await unfoldTokenType(tokensTabPanel, "text case");
|
||||
|
||||
await expect(
|
||||
tokensTabPanel.getByRole("button", { name: "my-token" }),
|
||||
).toBeEnabled();
|
||||
@ -885,6 +887,8 @@ test.describe("Tokens - creation", () => {
|
||||
|
||||
await submitButton.click();
|
||||
|
||||
await unfoldTokenType(tokensTabPanel, "text decoration");
|
||||
|
||||
await expect(
|
||||
tokensTabPanel.getByRole("button", { name: "my-token" }),
|
||||
).toBeEnabled();
|
||||
@ -1051,6 +1055,8 @@ test.describe("Tokens - creation", () => {
|
||||
await expect(submitButton).toBeEnabled();
|
||||
await submitButton.click();
|
||||
|
||||
await unfoldTokenType(tokensTabPanel, "shadow");
|
||||
|
||||
await expect(
|
||||
tokensTabPanel.getByRole("button", { name: "my-token" }),
|
||||
).toBeEnabled();
|
||||
@ -1088,6 +1094,8 @@ test.describe("Tokens - creation", () => {
|
||||
|
||||
await expect(submitButton).toBeEnabled();
|
||||
await submitButton.click();
|
||||
|
||||
await unfoldTokenType(tokensTabPanel, "shadow");
|
||||
await expect(
|
||||
tokensTabPanel.getByRole("button", { name: "my-token-2" }),
|
||||
).toBeEnabled();
|
||||
@ -1109,7 +1117,9 @@ test.describe("Tokens - creation", () => {
|
||||
const nameField = tokensUpdateCreateModal.getByLabel("Name");
|
||||
await nameField.fill("typography.empty");
|
||||
|
||||
const valueField = tokensUpdateCreateModal.getByRole("textbox", { name: "Font Size" });
|
||||
const valueField = tokensUpdateCreateModal.getByRole("textbox", {
|
||||
name: "Font Size",
|
||||
});
|
||||
|
||||
// Insert a value and then delete it
|
||||
await valueField.fill("1");
|
||||
@ -1274,6 +1284,8 @@ test.describe("Tokens - creation", () => {
|
||||
await expect(submitButton).toBeEnabled();
|
||||
await submitButton.click();
|
||||
|
||||
await unfoldTokenType(tokensTabPanel, "shadow");
|
||||
|
||||
await expect(
|
||||
tokensTabPanel.getByRole("button", { name: "my-token" }),
|
||||
).toBeEnabled();
|
||||
@ -1642,7 +1654,7 @@ test.describe("Tokens - creation", () => {
|
||||
await expect(submitButton).toBeEnabled();
|
||||
await submitButton.click();
|
||||
|
||||
await unfoldTokenTree(tokensSidebar, "color", "dark.primary");
|
||||
await unfoldTokenType(tokensSidebar, "color");
|
||||
|
||||
await expect(tokensSidebar.getByLabel("primary")).toBeEnabled();
|
||||
});
|
||||
@ -1681,10 +1693,10 @@ test.describe("Tokens - creation", () => {
|
||||
|
||||
await expect(tokensSidebar).toBeVisible();
|
||||
|
||||
unfoldTokenTree(tokensSidebar, "color", "colors.blue.100");
|
||||
await unfoldTokenType(tokensSidebar, "color");
|
||||
|
||||
const colorToken = tokensSidebar.getByRole("button", {
|
||||
name: "100",
|
||||
name: "colors.blue.100",
|
||||
});
|
||||
|
||||
await colorToken.click({ button: "right" });
|
||||
@ -1724,7 +1736,7 @@ test("User creates grouped color token", async ({ page }) => {
|
||||
await expect(submitButton).toBeEnabled();
|
||||
await submitButton.click();
|
||||
|
||||
await unfoldTokenTree(tokensSidebar, "color", "dark.primary");
|
||||
await unfoldTokenType(tokensSidebar, "color");
|
||||
|
||||
await expect(tokensSidebar.getByLabel("primary")).toBeEnabled();
|
||||
});
|
||||
@ -1761,10 +1773,10 @@ test("User duplicate color token", async ({ page }) => {
|
||||
|
||||
await expect(tokensSidebar).toBeVisible();
|
||||
|
||||
unfoldTokenTree(tokensSidebar, "color", "colors.blue.100");
|
||||
await unfoldTokenType(tokensSidebar, "color");
|
||||
|
||||
const colorToken = tokensSidebar.getByRole("button", {
|
||||
name: "100",
|
||||
name: "colors.blue.100",
|
||||
});
|
||||
|
||||
await colorToken.click({ button: "right" });
|
||||
@ -1809,7 +1821,9 @@ test.describe("Tokens tab - edition", () => {
|
||||
await fontFamilyField.fill("OneWord");
|
||||
|
||||
// Invalidate incorrect values for font size
|
||||
const fontSizeField = tokensUpdateCreateModal.getByRole("textbox", { name: "Font Size" });
|
||||
const fontSizeField = tokensUpdateCreateModal.getByRole("textbox", {
|
||||
name: "Font Size",
|
||||
});
|
||||
await fontSizeField.fill("invalid");
|
||||
await expect(
|
||||
tokensUpdateCreateModal.getByText(/Invalid token value:/),
|
||||
@ -1824,13 +1838,21 @@ test.describe("Tokens tab - edition", () => {
|
||||
await fontSizeField.fill("16");
|
||||
await expect(saveButton).toBeEnabled();
|
||||
|
||||
const fontWeightField = tokensUpdateCreateModal.getByRole("textbox", { name: "Font Weight" });
|
||||
const letterSpacingField =
|
||||
tokensUpdateCreateModal.getByRole("textbox", { name: "Letter Spacing" });
|
||||
const lineHeightField = tokensUpdateCreateModal.getByRole("textbox", { name: "Line Height" });
|
||||
const textCaseField = tokensUpdateCreateModal.getByRole("textbox", { name: "Text Case" });
|
||||
const textDecorationField =
|
||||
tokensUpdateCreateModal.getByRole("textbox", { name: "Text Decoration" });
|
||||
const fontWeightField = tokensUpdateCreateModal.getByRole("textbox", {
|
||||
name: "Font Weight",
|
||||
});
|
||||
const letterSpacingField = tokensUpdateCreateModal.getByRole("textbox", {
|
||||
name: "Letter Spacing",
|
||||
});
|
||||
const lineHeightField = tokensUpdateCreateModal.getByRole("textbox", {
|
||||
name: "Line Height",
|
||||
});
|
||||
const textCaseField = tokensUpdateCreateModal.getByRole("textbox", {
|
||||
name: "Text Case",
|
||||
});
|
||||
const textDecorationField = tokensUpdateCreateModal.getByRole("textbox", {
|
||||
name: "Text Decoration",
|
||||
});
|
||||
|
||||
// Capture all values before switching tabs
|
||||
const originalValues = {
|
||||
@ -1883,10 +1905,10 @@ test.describe("Tokens tab - edition", () => {
|
||||
|
||||
await expect(tokensSidebar).toBeVisible();
|
||||
|
||||
await unfoldTokenTree(tokensSidebar, "color", "colors.blue.100");
|
||||
await unfoldTokenType(tokensSidebar, "color");
|
||||
|
||||
const colorToken = tokensSidebar.getByRole("button", {
|
||||
name: "100",
|
||||
name: "colors.blue.100",
|
||||
});
|
||||
|
||||
await expect(colorToken).toBeVisible();
|
||||
@ -1904,7 +1926,7 @@ test.describe("Tokens tab - edition", () => {
|
||||
|
||||
await expect(tokensUpdateCreateModal).not.toBeVisible();
|
||||
|
||||
await unfoldTokenTree(tokensSidebar, "color", "colors.blue.100.changed");
|
||||
await unfoldTokenType(tokensSidebar, "color");
|
||||
|
||||
const colorTokenChanged = tokensSidebar.getByRole("button", {
|
||||
name: "changed",
|
||||
@ -1975,10 +1997,10 @@ test.describe("Tokens tab - delete", () => {
|
||||
|
||||
await expect(tokensSidebar).toBeVisible();
|
||||
|
||||
unfoldTokenTree(tokensSidebar, "color", "colors.blue.100");
|
||||
await unfoldTokenType(tokensSidebar, "color");
|
||||
|
||||
const colorToken = tokensSidebar.getByRole("button", {
|
||||
name: "100",
|
||||
name: "colors.blue.100",
|
||||
});
|
||||
await expect(colorToken).toBeVisible();
|
||||
await colorToken.click({ button: "right" });
|
||||
@ -1996,7 +2018,7 @@ test.describe("Tokens tab - delete", () => {
|
||||
await expect(tokensSidebar).toBeVisible();
|
||||
|
||||
// Expand color tokens
|
||||
unfoldTokenTree(tokensSidebar, "color", "colors.blue.100");
|
||||
await unfoldTokenType(tokensSidebar, "color");
|
||||
|
||||
// Verify that the node and child token are visible before deletion
|
||||
const colorNode = tokensSidebar.getByRole("button", {
|
||||
@ -2004,7 +2026,7 @@ test.describe("Tokens tab - delete", () => {
|
||||
exact: true,
|
||||
});
|
||||
const colorNodeToken = tokensSidebar.getByRole("button", {
|
||||
name: "100",
|
||||
name: "colors.blue.100",
|
||||
});
|
||||
|
||||
// Select a node and right click on it to open context menu
|
||||
|
||||
@ -207,7 +207,7 @@ const testTokenCreationFlow = async (
|
||||
const selfReferenceError = "Token has self reference";
|
||||
const missingReferenceError = "Missing token references";
|
||||
|
||||
const { tokensUpdateCreateModal, tokenThemesSetsSidebar } =
|
||||
const { tokensUpdateCreateModal, tokensSidebar } =
|
||||
await setupEmptyTokensFileRender(page);
|
||||
|
||||
// Open modal
|
||||
@ -313,12 +313,11 @@ const testTokenCreationFlow = async (
|
||||
).toBeEnabled();
|
||||
};
|
||||
|
||||
const unfoldTokenTree = async (tokensTabPanel, type, tokenName) => {
|
||||
const tokenSegments = tokenName.split(".");
|
||||
const tokenFolderTree = tokenSegments.slice(0, -1);
|
||||
const tokenLeafName = tokenSegments.pop();
|
||||
|
||||
const typeParentWrapper = tokensTabPanel.getByTestId(`section-${type}`);
|
||||
const unfoldTokenType = async (tokensTabPanel, type) => {
|
||||
const kebabClaseType = type.toLocaleLowerCase().replace(/\s/g, "-");
|
||||
const typeParentWrapper = tokensTabPanel.getByTestId(
|
||||
`section-${kebabClaseType}`,
|
||||
);
|
||||
const typeSectionButton = typeParentWrapper
|
||||
.getByRole("button", {
|
||||
name: type,
|
||||
@ -331,24 +330,6 @@ const unfoldTokenTree = async (tokensTabPanel, type, tokenName) => {
|
||||
if (isSectionExpanded === "false") {
|
||||
await typeSectionButton.click();
|
||||
}
|
||||
|
||||
for (const segment of tokenFolderTree) {
|
||||
const segmentButton = typeParentWrapper
|
||||
.getByRole("listitem")
|
||||
.getByRole("button", { name: segment })
|
||||
.first();
|
||||
|
||||
const isExpanded = await segmentButton.getAttribute("aria-expanded");
|
||||
if (isExpanded === "false") {
|
||||
await segmentButton.click();
|
||||
}
|
||||
}
|
||||
|
||||
await expect(
|
||||
typeParentWrapper.getByRole("button", {
|
||||
name: tokenLeafName,
|
||||
}),
|
||||
).toBeEnabled();
|
||||
};
|
||||
|
||||
export {
|
||||
@ -359,5 +340,5 @@ export {
|
||||
setupTypographyTokensFile,
|
||||
setupTypographyTokensFileRender,
|
||||
testTokenCreationFlow,
|
||||
unfoldTokenTree,
|
||||
unfoldTokenType,
|
||||
};
|
||||
|
||||
@ -1,7 +1,7 @@
|
||||
import { test, expect } from "@playwright/test";
|
||||
import { WasmWorkspacePage } from "../../pages/WasmWorkspacePage";
|
||||
import { BaseWebSocketPage } from "../../pages/BaseWebSocketPage";
|
||||
import { setupTokensFileRender, unfoldTokenTree } from "./helpers";
|
||||
import { setupTokensFileRender } from "./helpers";
|
||||
|
||||
test.beforeEach(async ({ page }) => {
|
||||
await WasmWorkspacePage.init(page);
|
||||
@ -20,10 +20,8 @@ test.describe("Tokens - node tree", () => {
|
||||
await expect(tokensColorGroup).toBeVisible();
|
||||
await tokensColorGroup.click();
|
||||
|
||||
await unfoldTokenTree(tokensSidebar, "color", "colors.blue.100");
|
||||
|
||||
const colorToken = tokensSidebar.getByRole("button", {
|
||||
name: "100",
|
||||
name: "colors.blue.100",
|
||||
});
|
||||
await expect(colorToken).toBeVisible();
|
||||
await tokensColorGroup.click();
|
||||
|
||||
@ -11,7 +11,6 @@
|
||||
[app.common.files.helpers :as cfh]
|
||||
[app.common.geom.point :as gpt]
|
||||
[app.common.logic.tokens :as clt]
|
||||
[app.common.path-names :as cpn]
|
||||
[app.common.types.shape :as cts]
|
||||
[app.common.types.tokens-lib :as ctob]
|
||||
[app.common.uuid :as uuid]
|
||||
@ -62,52 +61,77 @@
|
||||
(watch [_ _ _]
|
||||
(rx/of (dwsh/update-shapes [id] #(merge % attrs)))))))
|
||||
|
||||
|
||||
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
|
||||
;; Toggle tree nodes
|
||||
;; TOKENS TREE - Type folders
|
||||
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
|
||||
|
||||
(defn- remove-paths-recursively
|
||||
(defn open-token-type
|
||||
([types type]
|
||||
(conj (or types #{}) type))
|
||||
([type]
|
||||
(ptk/reify ::open-token-type
|
||||
ptk/UpdateEvent
|
||||
(update [_ state]
|
||||
(update-in state [:workspace-tokens :unfolded-token-types]
|
||||
#(open-token-type % type))))))
|
||||
|
||||
(defn close-token-type
|
||||
([types type]
|
||||
(disj (or types #{}) type))
|
||||
([type]
|
||||
(ptk/reify ::close-token-type
|
||||
ptk/UpdateEvent
|
||||
(update [_ state]
|
||||
(update-in state [:workspace-tokens :unfolded-token-types]
|
||||
#(close-token-type % type))))))
|
||||
|
||||
(defn toggle-token-type
|
||||
[type]
|
||||
(ptk/reify ::toggle-token-type
|
||||
ptk/UpdateEvent
|
||||
(update [_ state]
|
||||
(update-in state [:workspace-tokens :unfolded-token-types]
|
||||
(fn [types]
|
||||
(if (contains? (or types #{}) type)
|
||||
(close-token-type types type)
|
||||
(open-token-type types type)))))))
|
||||
|
||||
|
||||
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
|
||||
;; TOKENS TREE - Toggle tree nodes
|
||||
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
|
||||
|
||||
(defn- remove-path
|
||||
[path paths]
|
||||
(->> paths
|
||||
(remove #(str/starts-with? % (str path)))
|
||||
(remove #(= % path))
|
||||
vec))
|
||||
|
||||
(defn add-path
|
||||
[path paths]
|
||||
(let [split-path (cpn/split-path path :separator ".")
|
||||
partial-paths (->> split-path
|
||||
(reduce
|
||||
(fn [acc segment]
|
||||
(let [new-acc (if (empty? acc)
|
||||
segment
|
||||
(str (last acc) "." segment))]
|
||||
(conj acc new-acc)))
|
||||
[]))]
|
||||
(->> paths
|
||||
(into partial-paths)
|
||||
distinct
|
||||
vec)))
|
||||
(vec (conj paths path)))
|
||||
|
||||
(defn clear-tokens-paths
|
||||
[]
|
||||
(ptk/reify ::clear-tokens-paths
|
||||
ptk/UpdateEvent
|
||||
(update [_ state]
|
||||
(assoc-in state [:workspace-tokens :unfolded-token-paths] []))))
|
||||
(assoc-in state [:workspace-tokens :folded-token-paths] []))))
|
||||
|
||||
(defn toggle-token-path
|
||||
[path]
|
||||
(ptk/reify ::toggle-token-path
|
||||
ptk/UpdateEvent
|
||||
(update [_ state]
|
||||
(update-in state [:workspace-tokens :unfolded-token-paths]
|
||||
(update-in state [:workspace-tokens :folded-token-paths]
|
||||
(fn [paths]
|
||||
(let [paths (or paths [])]
|
||||
(if (some #(= % path) paths)
|
||||
(remove-paths-recursively path paths)
|
||||
(remove-path path paths)
|
||||
(add-path path paths))))))))
|
||||
|
||||
|
||||
|
||||
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
|
||||
;; TOKENS Actions
|
||||
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
|
||||
|
||||
@ -171,14 +171,11 @@
|
||||
path (:name token)
|
||||
tokens-by-type (ctob/group-by-type selected-token-set-tokens)
|
||||
tokens-filtered-by-type (get tokens-by-type type)
|
||||
tokens-in-path-ids (filter-tokens-by-path-ids type path)
|
||||
remaining-tokens? (remaining-tokens-of-type-in-set? tokens-filtered-by-type tokens-in-path-ids)]
|
||||
;; Delete the token
|
||||
remaining-tokens? (remaining-tokens-of-type-in-set? tokens-filtered-by-type [id])]
|
||||
(st/emit! (dwtl/delete-token selected-token-set-id id))
|
||||
;; Remove from unfolded tree path
|
||||
(if remaining-tokens?
|
||||
(st/emit! (dwtl/toggle-token-path (str (name type) "." path)))
|
||||
(st/emit! (dwtl/toggle-token-path (name type)))))))
|
||||
(st/emit! (dwtl/close-token-type type))))))
|
||||
|
||||
delete-node
|
||||
(mf/with-memo [selected-token-set-tokens selected-token-set-id]
|
||||
@ -193,7 +190,7 @@
|
||||
;; Remove from unfolded tree path
|
||||
(if remaining-tokens?
|
||||
(st/emit! (dwtl/toggle-token-path (str (name type) "." path)))
|
||||
(st/emit! (dwtl/toggle-token-path (name type)))))))
|
||||
(st/emit! (dwtl/close-token-type type))))))
|
||||
|
||||
bulk-rename-tokens-in-path
|
||||
;; Rename tokens in bulk affected by a node rename.
|
||||
|
||||
@ -7,7 +7,6 @@
|
||||
(ns app.main.ui.workspace.tokens.management.forms.generic-form
|
||||
(:require-macros [app.main.style :as stl])
|
||||
(:require
|
||||
[app.common.data :as d]
|
||||
[app.common.files.tokens :as cfo]
|
||||
[app.common.schema :as sm]
|
||||
[app.common.types.tokens-lib :as ctob]
|
||||
@ -186,7 +185,6 @@
|
||||
(mf/deps validate-token token tokens token-type value-subfield value-type active-tab on-remap-token on-rename-token is-create)
|
||||
(fn [form _event]
|
||||
(let [name (get-in @form [:clean-data :name])
|
||||
path (str (d/name token-type) "." name)
|
||||
description (get-in @form [:clean-data :description])
|
||||
value (get-in @form [:clean-data :value])
|
||||
value-for-validation (get-value-for-validator active-tab value value-subfield value-type)]
|
||||
@ -221,7 +219,7 @@
|
||||
{:name name
|
||||
:value (:value valid-token)
|
||||
:description description}))
|
||||
(dwtl/toggle-token-path path)
|
||||
(dwtl/open-token-type (:type token))
|
||||
(dwtp/propagate-workspace-tokens)
|
||||
(modal/hide!)))))
|
||||
;; WORKAROUND: display validation errors in the form instead of crashing
|
||||
|
||||
@ -27,8 +27,11 @@
|
||||
[okulary.core :as l]
|
||||
[rumext.v2 :as mf]))
|
||||
|
||||
(def ref:unfolded-token-paths
|
||||
(l/derived (l/key :unfolded-token-paths) refs/workspace-tokens))
|
||||
(def ref:folded-token-paths
|
||||
(l/derived (l/key :folded-token-paths) refs/workspace-tokens))
|
||||
|
||||
(def ref:unfolded-token-types
|
||||
(l/derived (l/key :unfolded-token-types) refs/workspace-tokens))
|
||||
|
||||
(defn token-section-icon
|
||||
[type]
|
||||
@ -72,8 +75,10 @@
|
||||
(let [{:keys [modal title]}
|
||||
(get dwta/token-properties type)
|
||||
|
||||
unfolded-token-paths (mf/deref ref:unfolded-token-paths)
|
||||
is-type-unfolded (contains? (set unfolded-token-paths) (name type))
|
||||
folded-token-paths (mf/deref ref:folded-token-paths)
|
||||
unfolded-token-types (mf/deref ref:unfolded-token-types)
|
||||
|
||||
is-type-unfolded (contains? (set unfolded-token-types) type)
|
||||
|
||||
editing-ref (mf/deref refs/workspace-editor-state)
|
||||
edition (mf/deref refs/selected-edition)
|
||||
@ -117,7 +122,7 @@
|
||||
(mf/deps type expandable?)
|
||||
(fn []
|
||||
(when expandable?
|
||||
(st/emit! (dwtl/toggle-token-path (name type))))))
|
||||
(st/emit! (dwtl/toggle-token-type type)))))
|
||||
|
||||
on-popover-open-click
|
||||
(mf/use-fn
|
||||
@ -172,13 +177,12 @@
|
||||
(when is-type-unfolded
|
||||
[:> token-tree* {:tokens tokens
|
||||
:type type
|
||||
:id (dm/str "token-tree-" (name type))
|
||||
:tokens-lib tokens-lib
|
||||
:unfolded-token-paths unfolded-token-paths
|
||||
:folded-token-paths folded-token-paths
|
||||
:selected-shapes selected-shapes
|
||||
:is-selected-inside-layout is-selected-inside-layout
|
||||
:active-theme-tokens active-theme-tokens
|
||||
:selected-token-set-id selected-token-set-id
|
||||
:is-selected-inside-layout is-selected-inside-layout
|
||||
:tokens-lib tokens-lib
|
||||
:on-token-pill-click on-token-pill-click
|
||||
:on-pill-context-menu on-pill-context-menu
|
||||
:on-node-context-menu on-node-context-menu}])]))
|
||||
|
||||
@ -21,7 +21,7 @@
|
||||
[:map
|
||||
[:node :any]
|
||||
[:type :keyword]
|
||||
[:unfolded-token-paths {:optional true} [:vector :string]]
|
||||
[:folded-token-paths {:optional true} [:maybe [:vector :string]]]
|
||||
[:selected-shapes :any]
|
||||
[:is-selected-inside-layout {:optional true} :boolean]
|
||||
[:active-theme-tokens {:optional true} :any]
|
||||
@ -35,7 +35,7 @@
|
||||
{::mf/schema schema:folder-node}
|
||||
[{:keys [node
|
||||
type
|
||||
unfolded-token-paths
|
||||
folded-token-paths
|
||||
selected-shapes
|
||||
is-selected-inside-layout
|
||||
active-theme-tokens
|
||||
@ -45,12 +45,11 @@
|
||||
on-pill-context-menu
|
||||
on-node-context-menu]}]
|
||||
(let [full-path (str (name type) "." (:path node))
|
||||
is-folder-expanded (contains? (set (or unfolded-token-paths [])) full-path)
|
||||
is-folder-expanded (not (contains? (set (or folded-token-paths [])) full-path))
|
||||
swap-folder-expanded (mf/use-fn
|
||||
(mf/deps (:path node) type)
|
||||
(mf/deps full-path)
|
||||
(fn []
|
||||
(let [path (str (name type) "." (:path node))]
|
||||
(st/emit! (dwtl/toggle-token-path path)))))
|
||||
(st/emit! (dwtl/toggle-token-path full-path))))
|
||||
|
||||
node-context-menu-prep (mf/use-fn
|
||||
(mf/deps on-node-context-menu node)
|
||||
@ -66,18 +65,18 @@
|
||||
:on-toggle-expand swap-folder-expanded
|
||||
:on-context-menu node-context-menu-prep}]
|
||||
(when is-folder-expanded
|
||||
(let [children-fn (:children-fn node)]
|
||||
(let [children (:children node)]
|
||||
[:div {:class (stl/css :folder-children-wrapper)
|
||||
:id (str "folder-children-" (:path node))}
|
||||
(when children-fn
|
||||
(let [sorted-children (d/natural-sort-by :name (children-fn))]
|
||||
(when (seq children)
|
||||
(let [sorted-children (d/natural-sort-by :name children)]
|
||||
(for [child sorted-children]
|
||||
(if (not (:leaf child))
|
||||
[:ul {:class (stl/css :node-parent)
|
||||
:key (:path child)}
|
||||
[:> folder-node* {:type type
|
||||
:node child
|
||||
:unfolded-token-paths unfolded-token-paths
|
||||
:folded-token-paths folded-token-paths
|
||||
:selected-shapes selected-shapes
|
||||
:is-selected-inside-layout is-selected-inside-layout
|
||||
:active-theme-tokens active-theme-tokens
|
||||
@ -101,12 +100,12 @@
|
||||
[:map
|
||||
[:tokens :any]
|
||||
[:type :keyword]
|
||||
[:unfolded-token-paths {:optional true} [:vector :string]]
|
||||
[:folded-token-paths {:optional true} [:maybe [:vector :string]]]
|
||||
[:selected-shapes :any]
|
||||
[:is-selected-inside-layout {:optional true} :boolean]
|
||||
[:active-theme-tokens {:optional true} :any]
|
||||
[:selected-token-set-id {:optional true} :any]
|
||||
[:tokens-lib {:optional true} :any]
|
||||
[:selected-token-set-id {:optional true} :any]
|
||||
[:on-token-pill-click {:optional true} fn?]
|
||||
[:on-pill-context-menu {:optional true} fn?]
|
||||
[:on-node-context-menu {:optional true} fn?]])
|
||||
@ -115,7 +114,7 @@
|
||||
{::mf/schema schema:token-tree}
|
||||
[{:keys [tokens
|
||||
type
|
||||
unfolded-token-paths
|
||||
folded-token-paths
|
||||
selected-shapes
|
||||
is-selected-inside-layout
|
||||
active-theme-tokens
|
||||
@ -153,7 +152,7 @@
|
||||
:key (:path node)}
|
||||
[:> folder-node* {:node node
|
||||
:type type
|
||||
:unfolded-token-paths unfolded-token-paths
|
||||
:folded-token-paths folded-token-paths
|
||||
:selected-shapes selected-shapes
|
||||
:is-selected-inside-layout is-selected-inside-layout
|
||||
:active-theme-tokens active-theme-tokens
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user