mirror of
https://github.com/penpot/penpot.git
synced 2026-10-03 01:06:14 +00:00
🐛 Fix token propagation on canvas color (#11950)
* 🐛 Update canvas background when its color token changes Token propagation only walked the shapes of each page, so a canvas background linked to a color token kept its old value after switching the active set or editing the token. Propagation now also updates the background of every page whose `:background-token` resolves to a new color, inside the same undo transaction. AI-assisted-by: claude-opus-5-5 * 🐛 Select the dragged token set by id instead of by path Starting a drag on an unselected token set stored its path as `:selected-token-set-id`. The sidebar then crashed on the `(uuid? force-set-id)` assert of `get-tokens-in-active-sets-force`. This could happen when toggling a set checkbox with a slight mouse move. AI-assisted-by: claude-opus-5-5 * 🎉 Add playwright test
This commit is contained in:
parent
534b1a6702
commit
f311c7ab05
@ -66,6 +66,74 @@ test.describe("Tokens: Apply token", () => {
|
||||
await expect(canvasSection.getByText("black")).toBeVisible();
|
||||
});
|
||||
|
||||
test("Canvas background follows its color token when the active set changes", async ({
|
||||
page,
|
||||
}) => {
|
||||
const { tokenThemesSetsSidebar, tokensUpdateCreateModal } =
|
||||
await setupTokensFileRender(page);
|
||||
|
||||
const tokensTabPanel = page.getByRole("tabpanel", { name: "tokens" });
|
||||
|
||||
// Creates a set holding a "canvas-bg" color token with `value`.
|
||||
const createSetWithCanvasToken = async (setName, value) => {
|
||||
await createSet(tokenThemesSetsSidebar, setName);
|
||||
const setButton = tokenThemesSetsSidebar.getByRole("button", {
|
||||
name: setName,
|
||||
});
|
||||
await setButton.click();
|
||||
|
||||
await tokensTabPanel
|
||||
.getByRole("button", { name: "Add Token: Color" })
|
||||
.click();
|
||||
await expect(tokensUpdateCreateModal).toBeVisible();
|
||||
await tokensUpdateCreateModal.getByLabel("Name").fill("canvas-bg");
|
||||
await tokensUpdateCreateModal.getByLabel("Value").fill(value);
|
||||
|
||||
const submitButton = tokensUpdateCreateModal.getByRole("button", {
|
||||
name: "Save",
|
||||
});
|
||||
await expect(submitButton).toBeEnabled();
|
||||
await submitButton.click();
|
||||
await expect(tokensUpdateCreateModal).not.toBeVisible();
|
||||
|
||||
return setButton;
|
||||
};
|
||||
|
||||
const firstSet = await createSetWithCanvasToken("first-bg", "#ff0000");
|
||||
const secondSet = await createSetWithCanvasToken("second-bg", "#00ff00");
|
||||
|
||||
await firstSet.getByRole("checkbox").click();
|
||||
|
||||
// No shape is selected, so the right sidebar shows the canvas options.
|
||||
const canvasSection = page.getByRole("region", {
|
||||
name: "Canvas background section",
|
||||
});
|
||||
await canvasSection.getByRole("button").first().click();
|
||||
|
||||
const colorPicker = page.getByTestId("colorpicker");
|
||||
await expect(colorPicker).toBeVisible();
|
||||
await colorPicker.getByTestId("opt-token-color").click();
|
||||
await colorPicker.getByRole("button", { name: "canvas-bg" }).click();
|
||||
await page.keyboard.press("Escape");
|
||||
await expect(colorPicker).not.toBeVisible();
|
||||
|
||||
const canvas = page.getByTestId("canvas-wasm-shapes");
|
||||
await expect(canvas).toHaveCSS("background-color", "rgb(255, 0, 0)");
|
||||
|
||||
await firstSet.getByRole("checkbox").click();
|
||||
await secondSet.getByRole("checkbox").click();
|
||||
|
||||
await expect(canvas).toHaveCSS("background-color", "rgb(0, 255, 0)");
|
||||
await expect(canvasSection.getByText("canvas-bg")).toBeVisible();
|
||||
|
||||
await firstSet.getByRole("checkbox").click();
|
||||
await expect(canvas).toHaveCSS("background-color", "rgb(0, 255, 0)");
|
||||
|
||||
await secondSet.getByRole("checkbox").click();
|
||||
await expect(canvas).toHaveCSS("background-color", "rgb(255, 0, 0)");
|
||||
await expect(canvasSection.getByText("canvas-bg")).toBeVisible();
|
||||
});
|
||||
|
||||
test("User applies border-radius token to a shape from sidebar", async ({
|
||||
page,
|
||||
}) => {
|
||||
|
||||
@ -7,12 +7,14 @@
|
||||
(ns app.main.data.workspace.tokens.propagation
|
||||
(:require
|
||||
[app.common.data :as d]
|
||||
[app.common.files.changes-builder :as pcb]
|
||||
[app.common.files.helpers :as cfh]
|
||||
[app.common.files.tokens :as cfo]
|
||||
[app.common.logging :as l]
|
||||
[app.common.time :as ct]
|
||||
[app.common.types.token :as ctt]
|
||||
[app.config :as cf]
|
||||
[app.main.data.changes :as dch]
|
||||
[app.main.data.helpers :as dsh]
|
||||
[app.main.data.style-dictionary :as sd]
|
||||
[app.main.data.tokenscript :as ts]
|
||||
@ -131,8 +133,27 @@
|
||||
update-infos)))
|
||||
shapes-update-info))
|
||||
|
||||
(defn- update-canvas-background
|
||||
"Set the background of `page-id` to the resolved value of its linked
|
||||
color token, when that value differs from the current background."
|
||||
[page-id resolved-tokens]
|
||||
(ptk/reify ::update-canvas-background
|
||||
ptk/WatchEvent
|
||||
(watch [it state _]
|
||||
(let [page (dsh/lookup-page state page-id)
|
||||
token-name (:background-token page)
|
||||
color (some-> (get-in resolved-tokens [token-name :resolved-value])
|
||||
(dwta/value->color)
|
||||
(:color))]
|
||||
(when (and (some? color) (not= color (:background page)))
|
||||
(rx/of (dch/commit-changes
|
||||
(-> (pcb/empty-changes it)
|
||||
(pcb/with-page page)
|
||||
(pcb/mod-page {:background color})))))))))
|
||||
|
||||
(defn propagate-tokens
|
||||
"Propagate tokens values to all shapes where they are applied"
|
||||
"Propagate tokens values to all shapes and page backgrounds where they
|
||||
are applied"
|
||||
[state resolved-tokens]
|
||||
(let [file-id (get state :current-file-id)
|
||||
current-page-id (get state :current-page-id)
|
||||
@ -168,6 +189,9 @@
|
||||
(when (seq observable) (apply rx/merge observable))
|
||||
(when (seq normal) (rx/concat-all (rx/of normal)))
|
||||
|
||||
(when (:background-token page)
|
||||
(rx/of (update-canvas-background page-id resolved-tokens)))
|
||||
|
||||
(->> (rx/from frame-ids)
|
||||
(rx/mapcat (fn [frame-id]
|
||||
(rx/of (dwt/clear-thumbnail file-id page-id frame-id "frame")
|
||||
|
||||
@ -260,10 +260,10 @@
|
||||
|
||||
on-drag
|
||||
(mf/use-fn
|
||||
(mf/deps path)
|
||||
(mf/deps id is-selected on-select)
|
||||
(fn [_]
|
||||
(when-not is-selected
|
||||
(on-select path))))
|
||||
(on-select id))))
|
||||
|
||||
on-drop
|
||||
(mf/use-fn
|
||||
|
||||
@ -308,6 +308,40 @@
|
||||
(t/is (= (get-in rect-2' [:strokes 0 :stroke-color]) "#ff0000"))
|
||||
(t/is (= (get-in rect-2' [:strokes 0 :stroke-opacity]) 0.5))))))))))
|
||||
|
||||
(t/deftest test-switch-set-updates-canvas-color
|
||||
(t/testing "switching the active set updates a canvas background linked to a color token"
|
||||
(t/async
|
||||
done
|
||||
(let [file (ctht/sample-file-with-tokens
|
||||
:lib-fn #(-> %
|
||||
(ctob/add-set (ctob/make-token-set :id (cthi/new-id! :set-a)
|
||||
:name "Set A"))
|
||||
(ctob/add-set (ctob/make-token-set :id (cthi/new-id! :set-b)
|
||||
:name "Set B"))
|
||||
(ctob/add-token (cthi/id :set-a)
|
||||
(ctob/make-token {:name "canvas.bg"
|
||||
:value "#ff0000"
|
||||
:type :color}))
|
||||
(ctob/add-token (cthi/id :set-b)
|
||||
(ctob/make-token {:name "canvas.bg"
|
||||
:value "#00ff00"
|
||||
:type :color})))
|
||||
;; Both sets active: "Set B" comes last, so its value wins.
|
||||
:status-fn #(ctos/set-tokens-status % #{} #{(cthi/id :set-a)
|
||||
(cthi/id :set-b)}))
|
||||
page-id (cthf/current-page-id file)
|
||||
file (update-in file [:data :pages-index page-id]
|
||||
assoc :background "#00ff00" :background-token "canvas.bg")
|
||||
store (ths/setup-store file)
|
||||
events [(dwtl/set-enabled-token-set (cthi/id :set-b) false)]]
|
||||
(tohs/run-store-async
|
||||
store done events
|
||||
(fn [new-state]
|
||||
(let [file' (ths/get-file-from-state new-state)
|
||||
page' (get-in file' [:data :pages-index page-id])]
|
||||
(t/is (= "#ff0000" (:background page')))
|
||||
(t/is (= "canvas.bg" (:background-token page'))))))))))
|
||||
|
||||
(t/deftest test-apply-dimensions
|
||||
(t/testing "applies dimensions token and updates the shapes width and height"
|
||||
(t/async
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user