diff --git a/.circleci/config.yml b/.circleci/config.yml index 70ba3b801..13074f6bf 100644 --- a/.circleci/config.yml +++ b/.circleci/config.yml @@ -108,7 +108,7 @@ jobs: command: | yarn install yarn run compile - yarn run compile:cljs + clojure -M:dev:shadow-cljs release main yarn playwright install --with-deps chromium yarn e2e:test diff --git a/CHANGES.md b/CHANGES.md index dcb9d48c0..a875e37b3 100644 --- a/CHANGES.md +++ b/CHANGES.md @@ -20,6 +20,8 @@ ### :sparkles: New features +- Fix clickable area of Penptot logo in the viewer [Taiga #7988](https://tree.taiga.io/project/penpot/issue/7988) +- Fix constraints dropdown when selecting multiple shapes [Taiga #7686](https://tree.taiga.io/project/penpot/issue/7686) - Improve auth process [Taiga #7094](https://tree.taiga.io/project/penpot/us/7094) - Add locking degrees increment (hold shift) on path edition [Taiga #7761](https://tree.taiga.io/project/penpot/issue/7761) - Persistence & Concurrent Edition Enhancements [Taiga #5657](https://tree.taiga.io/project/penpot/us/5657) diff --git a/common/src/app/common/types/container.cljc b/common/src/app/common/types/container.cljc index b50c5058a..c4166508a 100644 --- a/common/src/app/common/types/container.cljc +++ b/common/src/app/common/types/container.cljc @@ -375,8 +375,11 @@ {:skip-components? true :bottom-frames? true ;; We must avoid that destiny frame is inside the component frame - :validator #(nil? (get component-children (:id %)))})) - + :validator #(and + ;; We must avoid that destiny frame is inside the component frame + (nil? (get component-children (:id %))) + ;; We must avoid that destiny frame is inside a copy + (not (ctk/in-component-copy? %)))})) frame (get-shape container frame-id) component-frame (get-component-shape objects frame {:allow-main? true}) diff --git a/frontend/package.json b/frontend/package.json index 64b8d826b..51cf909c4 100644 --- a/frontend/package.json +++ b/frontend/package.json @@ -90,6 +90,7 @@ "workerpool": "^9.1.1" }, "dependencies": { + "compression": "^1.7.4", "date-fns": "^3.6.0", "eventsource-parser": "^1.1.2", "highlight.js": "^11.9.0", diff --git a/frontend/playwright/data/design/get-file-fragment-multiple-constraints.json b/frontend/playwright/data/design/get-file-fragment-multiple-constraints.json new file mode 100644 index 000000000..1a055d7d1 --- /dev/null +++ b/frontend/playwright/data/design/get-file-fragment-multiple-constraints.json @@ -0,0 +1,363 @@ +{ + "~:id": "~u03bff843-920f-81a1-8004-7563acdc8ca1", + "~:file-id": "~u03bff843-920f-81a1-8004-756365e1eb6a", + "~:created-at": "~m1717592543081", + "~:content": { + "~:options": {}, + "~:objects": { + "~u00000000-0000-0000-0000-000000000000": { + "~#shape": { + "~:y": 0, + "~:hide-fill-on-export": false, + "~:transform": { + "~#matrix": { + "~:a": 1.0, + "~:b": 0.0, + "~:c": 0.0, + "~:d": 1.0, + "~:e": 0.0, + "~:f": 0.0 + } + }, + "~:rotation": 0, + "~:name": "Root Frame", + "~:width": 0.01, + "~:type": "~:frame", + "~:points": [ + { + "~#point": { + "~:x": 0, + "~:y": 0 + } + }, + { + "~#point": { + "~:x": 0.01, + "~:y": 0 + } + }, + { + "~#point": { + "~:x": 0.01, + "~:y": 0.01 + } + }, + { + "~#point": { + "~:x": 0, + "~:y": 0.01 + } + } + ], + "~:proportion-lock": false, + "~:transform-inverse": { + "~#matrix": { + "~:a": 1.0, + "~:b": 0.0, + "~:c": 0.0, + "~:d": 1.0, + "~:e": 0.0, + "~:f": 0.0 + } + }, + "~:id": "~u00000000-0000-0000-0000-000000000000", + "~:parent-id": "~u00000000-0000-0000-0000-000000000000", + "~:frame-id": "~u00000000-0000-0000-0000-000000000000", + "~:strokes": [], + "~:x": 0, + "~:proportion": 1.0, + "~:selrect": { + "~#rect": { + "~:x": 0, + "~:y": 0, + "~:width": 0.01, + "~:height": 0.01, + "~:x1": 0, + "~:y1": 0, + "~:x2": 0.01, + "~:y2": 0.01 + } + }, + "~:fills": [ + { + "~:fill-color": "#FFFFFF", + "~:fill-opacity": 1 + } + ], + "~:flip-x": null, + "~:height": 0.01, + "~:flip-y": null, + "~:shapes": [ + "~ub574c052-1a31-80bb-8004-75636879759b" + ] + } + }, + "~ub574c052-1a31-80bb-8004-75636879759b": { + "~#shape": { + "~:y": 128, + "~:hide-fill-on-export": false, + "~:transform": { + "~#matrix": { + "~:a": 1.0, + "~:b": 0.0, + "~:c": 0.0, + "~:d": 1.0, + "~:e": 0.0, + "~:f": 0.0 + } + }, + "~:rotation": 0, + "~:grow-type": "~:fixed", + "~:hide-in-viewer": false, + "~:name": "Board", + "~:width": 256, + "~:type": "~:frame", + "~:points": [ + { + "~#point": { + "~:x": 128, + "~:y": 128 + } + }, + { + "~#point": { + "~:x": 384, + "~:y": 128 + } + }, + { + "~#point": { + "~:x": 384, + "~:y": 384 + } + }, + { + "~#point": { + "~:x": 128, + "~:y": 384 + } + } + ], + "~:proportion-lock": false, + "~:transform-inverse": { + "~#matrix": { + "~:a": 1.0, + "~:b": 0.0, + "~:c": 0.0, + "~:d": 1.0, + "~:e": 0.0, + "~:f": 0.0 + } + }, + "~:id": "~ub574c052-1a31-80bb-8004-75636879759b", + "~:parent-id": "~u00000000-0000-0000-0000-000000000000", + "~:frame-id": "~u00000000-0000-0000-0000-000000000000", + "~:strokes": [], + "~:x": 128, + "~:proportion": 1, + "~:selrect": { + "~#rect": { + "~:x": 128, + "~:y": 128, + "~:width": 256, + "~:height": 256, + "~:x1": 128, + "~:y1": 128, + "~:x2": 384, + "~:y2": 384 + } + }, + "~:fills": [ + { + "~:fill-color": "#FFFFFF", + "~:fill-opacity": 1 + } + ], + "~:flip-x": null, + "~:height": 256, + "~:flip-y": null, + "~:shapes": [ + "~ub574c052-1a31-80bb-8004-75636a9b8205", + "~ub574c052-1a31-80bb-8004-756392461069" + ] + } + }, + "~ub574c052-1a31-80bb-8004-75636a9b8205": { + "~#shape": { + "~:y": 136, + "~:rx": 0, + "~:transform": { + "~#matrix": { + "~:a": 1.0, + "~:b": 0.0, + "~:c": 0.0, + "~:d": 1.0, + "~:e": 0.0, + "~:f": 0.0 + } + }, + "~:rotation": 0, + "~:grow-type": "~:fixed", + "~:hide-in-viewer": false, + "~:name": "Rectangle", + "~:width": 64, + "~:type": "~:rect", + "~:points": [ + { + "~#point": { + "~:x": 136, + "~:y": 136 + } + }, + { + "~#point": { + "~:x": 200, + "~:y": 136 + } + }, + { + "~#point": { + "~:x": 200, + "~:y": 199.99999999999997 + } + }, + { + "~#point": { + "~:x": 136, + "~:y": 199.99999999999997 + } + } + ], + "~:proportion-lock": false, + "~:transform-inverse": { + "~#matrix": { + "~:a": 1.0, + "~:b": 0.0, + "~:c": 0.0, + "~:d": 1.0, + "~:e": 0.0, + "~:f": 0.0 + } + }, + "~:id": "~ub574c052-1a31-80bb-8004-75636a9b8205", + "~:parent-id": "~ub574c052-1a31-80bb-8004-75636879759b", + "~:frame-id": "~ub574c052-1a31-80bb-8004-75636879759b", + "~:strokes": [], + "~:x": 136, + "~:proportion": 1, + "~:selrect": { + "~#rect": { + "~:x": 136, + "~:y": 136, + "~:width": 64, + "~:height": 63.99999999999997, + "~:x1": 136, + "~:y1": 136, + "~:x2": 200, + "~:y2": 199.99999999999997 + } + }, + "~:fills": [ + { + "~:fill-color": "#B1B2B5", + "~:fill-opacity": 1 + } + ], + "~:flip-x": null, + "~:ry": 0, + "~:height": 63.99999999999997, + "~:flip-y": null + } + }, + "~ub574c052-1a31-80bb-8004-756392461069": { + "~#shape": { + "~:y": 136, + "~:transform": { + "~#matrix": { + "~:a": 1.0, + "~:b": 0.0, + "~:c": 0.0, + "~:d": 1.0, + "~:e": 0.0, + "~:f": 0.0 + } + }, + "~:rotation": 0, + "~:grow-type": "~:fixed", + "~:hide-in-viewer": false, + "~:name": "Ellipse", + "~:width": 64, + "~:type": "~:circle", + "~:points": [ + { + "~#point": { + "~:x": 256, + "~:y": 136 + } + }, + { + "~#point": { + "~:x": 320, + "~:y": 136 + } + }, + { + "~#point": { + "~:x": 320, + "~:y": 200 + } + }, + { + "~#point": { + "~:x": 256, + "~:y": 200 + } + } + ], + "~:proportion-lock": false, + "~:transform-inverse": { + "~#matrix": { + "~:a": 1.0, + "~:b": 0.0, + "~:c": 0.0, + "~:d": 1.0, + "~:e": 0.0, + "~:f": 0.0 + } + }, + "~:constraints-v": "~:bottom", + "~:constraints-h": "~:right", + "~:id": "~ub574c052-1a31-80bb-8004-756392461069", + "~:parent-id": "~ub574c052-1a31-80bb-8004-75636879759b", + "~:frame-id": "~ub574c052-1a31-80bb-8004-75636879759b", + "~:strokes": [], + "~:x": 256, + "~:proportion": 1, + "~:selrect": { + "~#rect": { + "~:x": 256, + "~:y": 136, + "~:width": 64, + "~:height": 64, + "~:x1": 256, + "~:y1": 136, + "~:x2": 320, + "~:y2": 200 + } + }, + "~:fills": [ + { + "~:fill-color": "#B1B2B5", + "~:fill-opacity": 1 + } + ], + "~:flip-x": null, + "~:height": 64, + "~:flip-y": null + } + } + }, + "~:id": "~u03bff843-920f-81a1-8004-756365e1eb6b", + "~:name": "Page 1" + } +} \ No newline at end of file diff --git a/frontend/playwright/data/design/get-file-multiple-constraints.json b/frontend/playwright/data/design/get-file-multiple-constraints.json new file mode 100644 index 000000000..76edd0baf --- /dev/null +++ b/frontend/playwright/data/design/get-file-multiple-constraints.json @@ -0,0 +1,49 @@ +{ + "~:features": { + "~#set": [ + "layout/grid", + "styles/v2", + "fdata/pointer-map", + "fdata/objects-map", + "components/v2", + "fdata/shape-data-type" + ] + }, + "~:permissions": { + "~:type": "~:membership", + "~:is-owner": true, + "~:is-admin": true, + "~:can-edit": true, + "~:can-read": true, + "~:is-logged": true + }, + "~:has-media-trimmed": false, + "~:comment-thread-seqn": 0, + "~:name": "New File 2", + "~:revn": 9, + "~:modified-at": "~m1717592543083", + "~:id": "~u03bff843-920f-81a1-8004-756365e1eb6a", + "~:is-shared": false, + "~:version": 48, + "~:project-id": "~u0515a066-e303-8169-8004-73eb401b5d55", + "~:created-at": "~m1717592470408", + "~:data": { + "~:pages": [ + "~u03bff843-920f-81a1-8004-756365e1eb6b" + ], + "~:pages-index": { + "~u03bff843-920f-81a1-8004-756365e1eb6b": { + "~#penpot/pointer": [ + "~u03bff843-920f-81a1-8004-7563acdc8ca1", + { + "~:created-at": "~m1717592543090" + } + ] + } + }, + "~:id": "~u03bff843-920f-81a1-8004-756365e1eb6a", + "~:options": { + "~:components-v2": true + } + } +} \ No newline at end of file diff --git a/frontend/playwright/data/design/get-file-object-thumbnails-multiple-constraints.json b/frontend/playwright/data/design/get-file-object-thumbnails-multiple-constraints.json new file mode 100644 index 000000000..f3a0d6d27 --- /dev/null +++ b/frontend/playwright/data/design/get-file-object-thumbnails-multiple-constraints.json @@ -0,0 +1,3 @@ +{ + "03bff843-920f-81a1-8004-756365e1eb6a/03bff843-920f-81a1-8004-756365e1eb6b/b574c052-1a31-80bb-8004-75636879759b/frame": "http://localhost:3449/assets/by-id/bdc9e592-f685-4b08-9a44-127ce20efee6" +} \ No newline at end of file diff --git a/frontend/playwright/data/viewer/get-file-fragment-empty-file.json b/frontend/playwright/data/viewer/get-file-fragment-empty-file.json new file mode 100644 index 000000000..544c559f7 --- /dev/null +++ b/frontend/playwright/data/viewer/get-file-fragment-empty-file.json @@ -0,0 +1,97 @@ +{ + "~:id": "~u0515a066-e303-8169-8004-73eb58e899c2", + "~:file-id": "~uc7ce0794-0992-8105-8004-38f280443849", + "~:created-at": "~m1717493890966", + "~:content": { + "~:options": {}, + "~:objects": { + "~u00000000-0000-0000-0000-000000000000": { + "~#shape": { + "~:y": 0, + "~:hide-fill-on-export": false, + "~:transform": { + "~#matrix": { + "~:a": 1.0, + "~:b": 0.0, + "~:c": 0.0, + "~:d": 1.0, + "~:e": 0.0, + "~:f": 0.0 + } + }, + "~:rotation": 0, + "~:name": "Root Frame", + "~:width": 0.01, + "~:type": "~:frame", + "~:points": [ + { + "~#point": { + "~:x": 0, + "~:y": 0 + } + }, + { + "~#point": { + "~:x": 0.01, + "~:y": 0 + } + }, + { + "~#point": { + "~:x": 0.01, + "~:y": 0.01 + } + }, + { + "~#point": { + "~:x": 0, + "~:y": 0.01 + } + } + ], + "~:proportion-lock": false, + "~:transform-inverse": { + "~#matrix": { + "~:a": 1.0, + "~:b": 0.0, + "~:c": 0.0, + "~:d": 1.0, + "~:e": 0.0, + "~:f": 0.0 + } + }, + "~:id": "~u00000000-0000-0000-0000-000000000000", + "~:parent-id": "~u00000000-0000-0000-0000-000000000000", + "~:frame-id": "~u00000000-0000-0000-0000-000000000000", + "~:strokes": [], + "~:x": 0, + "~:proportion": 1.0, + "~:selrect": { + "~#rect": { + "~:x": 0, + "~:y": 0, + "~:width": 0.01, + "~:height": 0.01, + "~:x1": 0, + "~:y1": 0, + "~:x2": 0.01, + "~:y2": 0.01 + } + }, + "~:fills": [ + { + "~:fill-color": "#FFFFFF", + "~:fill-opacity": 1 + } + ], + "~:flip-x": null, + "~:height": 0.01, + "~:flip-y": null, + "~:shapes": [] + } + } + }, + "~:id": "~uc7ce0794-0992-8105-8004-38f28044384a", + "~:name": "Page 1" + } +} \ No newline at end of file diff --git a/frontend/playwright/data/viewer/get-view-only-bundle-empty-file.json b/frontend/playwright/data/viewer/get-view-only-bundle-empty-file.json new file mode 100644 index 000000000..ef001224a --- /dev/null +++ b/frontend/playwright/data/viewer/get-view-only-bundle-empty-file.json @@ -0,0 +1,86 @@ +{ + "~:users": [ + { + "~:id": "~uc7ce0794-0992-8105-8004-38e630f29a9b", + "~:email": "leia@example.com", + "~:name": "Princesa Leia", + "~:fullname": "Princesa Leia", + "~:is-active": true + } + ], + "~:fonts": [], + "~:project": { + "~:id": "~uc7ce0794-0992-8105-8004-38e630f7920b", + "~:name": "Drafts", + "~:team-id": "~uc7ce0794-0992-8105-8004-38e630f40f6d" + }, + "~:share-links": [], + "~:libraries": [], + "~:file": { + "~:features": { + "~#set": [ + "layout/grid", + "styles/v2", + "fdata/pointer-map", + "fdata/objects-map", + "components/v2", + "fdata/shape-data-type" + ] + }, + "~:has-media-trimmed": false, + "~:comment-thread-seqn": 0, + "~:name": "New File 1", + "~:revn": 0, + "~:modified-at": "~m1717493891000", + "~:id": "~uc7ce0794-0992-8105-8004-38f280443849", + "~:is-shared": false, + "~:version": 48, + "~:project-id": "~uc7ce0794-0992-8105-8004-38e630f7920b", + "~:created-at": "~m1717493891000", + "~:data": { + "~:id": "~uc7ce0794-0992-8105-8004-38e630f7920b", + "~:options": { + "~:components-v2": true + }, + "~:pages": [ + "~uc7ce0794-0992-8105-8004-38f28044384a" + ], + "~:pages-index": { + "~uc7ce0794-0992-8105-8004-38f28044384a": { + "~#penpot/pointer": [ + "~u0515a066-e303-8169-8004-73eb58e899c2", + { + "~:created-at": "~m1717493890978" + } + ] + } + } + } + }, + "~:team": { + "~:id": "~uc7ce0794-0992-8105-8004-38e630f40f6d", + "~:created-at": "~m1717493865581", + "~:modified-at": "~m1717493865581", + "~:name": "Default", + "~:is-default": true, + "~:features": { + "~#set": [ + "layout/grid", + "styles/v2", + "fdata/pointer-map", + "fdata/objects-map", + "components/v2", + "fdata/shape-data-type" + ] + } + }, + "~:permissions": { + "~:type": "~:membership", + "~:is-owner": true, + "~:is-admin": true, + "~:can-edit": true, + "~:can-read": true, + "~:is-logged": true, + "~:in-team": true + } +} \ No newline at end of file diff --git a/frontend/playwright/ui/pages/ViewerPage.js b/frontend/playwright/ui/pages/ViewerPage.js new file mode 100644 index 000000000..cd328eb65 --- /dev/null +++ b/frontend/playwright/ui/pages/ViewerPage.js @@ -0,0 +1,48 @@ +import { BaseWebSocketPage } from "./BaseWebSocketPage"; + +export class ViewerPage extends BaseWebSocketPage { + static anyFileId = "c7ce0794-0992-8105-8004-38f280443849"; + static anyPageId = "c7ce0794-0992-8105-8004-38f28044384a"; + + /** + * This should be called on `test.beforeEach`. + * + * @param {Page} page + * @returns + */ + static async init(page) { + await BaseWebSocketPage.initWebSockets(page); + } + + async setupLoggedInUser() { + await this.mockRPC("get-profile", "logged-in-user/get-profile-logged-in.json"); + } + + async setupEmptyFile() { + await this.mockRPC(/get\-view\-only\-bundle\?/, "viewer/get-view-only-bundle-empty-file.json"); + await this.mockRPC("get-comment-threads?file-id=*", "workspace/get-comment-threads-empty.json"); + await this.mockRPC( + "get-file-fragment?file-id=*&fragment-id=*", + "viewer/get-file-fragment-empty-file.json", + ); + } + + #ws = null; + + constructor(page) { + super(page); + } + + async goToViewer() { + await this.page.goto( + `/#/view/${ViewerPage.anyFileId}?page-id=${ViewerPage.anyPageId}§ion=interactions&index=0`, + ); + + this.#ws = await this.waitForNotificationsWebSocket(); + await this.#ws.mockOpen(); + } + + async cleanUp() { + await this.#ws.mockClose(); + } +} diff --git a/frontend/playwright/ui/pages/WorkspacePage.js b/frontend/playwright/ui/pages/WorkspacePage.js index 3dafef465..9a3e83716 100644 --- a/frontend/playwright/ui/pages/WorkspacePage.js +++ b/frontend/playwright/ui/pages/WorkspacePage.js @@ -45,6 +45,7 @@ export class WorkspacePage extends BaseWebSocketPage { this.rootShape = page.locator(`[id="shape-00000000-0000-0000-0000-000000000000"]`); this.rectShapeButton = page.getByRole("button", { name: "Rectangle (R)" }); this.colorpicker = page.getByTestId("colorpicker"); + this.layers = page.getByTestId("layers"); this.palette = page.getByTestId("palette"); this.assets = page.getByTestId("assets"); this.libraries = page.getByTestId("libraries"); @@ -52,10 +53,8 @@ export class WorkspacePage extends BaseWebSocketPage { this.librariesModal = page.getByTestId("libraries-modal"); } - async goToWorkspace() { - await this.page.goto( - `/#/workspace/${WorkspacePage.anyProjectId}/${WorkspacePage.anyFileId}?page-id=${WorkspacePage.anyPageId}`, - ); + async goToWorkspace({ fileId = WorkspacePage.anyFileId, pageId = WorkspacePage.anyPageId } = {}) { + await this.page.goto(`/#/workspace/${WorkspacePage.anyProjectId}/${fileId}?page-id=${pageId}`); this.#ws = await this.waitForNotificationsWebSocket(); await this.#ws.mockOpen(); @@ -103,6 +102,16 @@ export class WorkspacePage extends BaseWebSocketPage { await this.page.mouse.up(); } + async clickLeafLayer(name, clickOptions = {}) { + const layer = this.layers.getByText(name); + await layer.click(clickOptions); + } + + async clickToggableLayer(name, clickOptions = {}) { + const layer = this.layers.getByTestId("layer-item").filter({ has: this.page.getByText(name) }); + await layer.getByRole("button").click(clickOptions); + } + async clickAssets(clickOptions = {}) { await this.assets.click(clickOptions); } diff --git a/frontend/playwright/ui/specs/design-tab.spec.js b/frontend/playwright/ui/specs/design-tab.spec.js new file mode 100644 index 000000000..0d637bace --- /dev/null +++ b/frontend/playwright/ui/specs/design-tab.spec.js @@ -0,0 +1,46 @@ +import { test, expect } from "@playwright/test"; +import { WorkspacePage } from "../pages/WorkspacePage"; + +test.beforeEach(async ({ page }) => { + await WorkspacePage.init(page); +}); + +const multipleConstraintsFileId = `03bff843-920f-81a1-8004-756365e1eb6a`; +const multipleConstraintsPageId = `03bff843-920f-81a1-8004-756365e1eb6b`; + +const setupFileWithMultipeConstraints = async (workspace) => { + await workspace.setupEmptyFile(); + await workspace.mockRPC(/get\-file\?/, "design/get-file-multiple-constraints.json"); + await workspace.mockRPC( + "get-file-object-thumbnails?file-id=*", + "design/get-file-object-thumbnails-multiple-constraints.json", + ); + await workspace.mockRPC( + "get-file-fragment?file-id=*", + "design/get-file-fragment-multiple-constraints.json", + ); +}; + +test.describe("Constraints", () => { + test("Constraint dropdown shows 'Mixed' when multiple layers are selected with different constraints", async ({ + page, + }) => { + const workspace = new WorkspacePage(page); + await setupFileWithMultipeConstraints(workspace); + await workspace.goToWorkspace({ + fileId: multipleConstraintsFileId, + pageId: multipleConstraintsPageId, + }); + + await workspace.clickToggableLayer("Board"); + await workspace.clickLeafLayer("Ellipse"); + await workspace.clickLeafLayer("Rectangle", { modifiers: ["Shift"] }); + + const constraintVDropdown = workspace.page.getByTestId("constraint-v-select"); + await expect(constraintVDropdown).toContainText("Mixed"); + const constraintHDropdown = workspace.page.getByTestId("constraint-h-select"); + await expect(constraintHDropdown).toContainText("Mixed"); + + expect(false); + }); +}); diff --git a/frontend/playwright/ui/specs/viewer-header.spec.js b/frontend/playwright/ui/specs/viewer-header.spec.js new file mode 100644 index 000000000..01cfb8634 --- /dev/null +++ b/frontend/playwright/ui/specs/viewer-header.spec.js @@ -0,0 +1,23 @@ +import { test, expect } from "@playwright/test"; +import { ViewerPage } from "../pages/ViewerPage"; + +test.beforeEach(async ({ page }) => { + await ViewerPage.init(page); +}); + +test("Clips link area of the logo", async ({ page }) => { + const viewerPage = new ViewerPage(page); + await viewerPage.setupLoggedInUser(); + await viewerPage.setupEmptyFile(); + + await viewerPage.goToViewer(); + + const viewerUrl = page.url(); + + const logoLink = viewerPage.page.getByTestId("penpot-logo-link"); + await expect(logoLink).toBeVisible(); + + const { x, y } = await logoLink.boundingBox(); + await viewerPage.page.mouse.click(x, y + 100); + await expect(page.url()).toBe(viewerUrl); +}); diff --git a/frontend/scripts/e2e-server.js b/frontend/scripts/e2e-server.js index 4c441d21c..c05743813 100644 --- a/frontend/scripts/e2e-server.js +++ b/frontend/scripts/e2e-server.js @@ -1,10 +1,14 @@ import express from "express"; +import compression from "compression"; + import { fileURLToPath } from "url"; import path from "path"; const app = express(); const port = 3000; +app.use(compression()); + const staticPath = path.join(fileURLToPath(import.meta.url), "../../resources/public"); app.use(express.static(staticPath)); diff --git a/frontend/src/app/main/data/workspace/libraries.cljs b/frontend/src/app/main/data/workspace/libraries.cljs index 1b8c59bee..f49569557 100644 --- a/frontend/src/app/main/data/workspace/libraries.cljs +++ b/frontend/src/app/main/data/workspace/libraries.cljs @@ -341,7 +341,7 @@ (defn- add-component2 "This is the second step of the component creation." ([selected components-v2] - (add-component2 selected components-v2)) + (add-component2 nil selected components-v2)) ([id-ref selected components-v2] (ptk/reify ::add-component2 ev/Event diff --git a/frontend/src/app/main/ui/viewer/header.cljs b/frontend/src/app/main/ui/viewer/header.cljs index abf81479f..03dd06548 100644 --- a/frontend/src/app/main/ui/viewer/header.cljs +++ b/frontend/src/app/main/ui/viewer/header.cljs @@ -256,6 +256,10 @@ [:span {:class (stl/css :frame-name)} frame-name] [:span {:class (stl/css :icon)} i/arrow]]]])) +(def ^:private penpot-logo-icon + (i/icon-xref :penpot-logo-icon (stl/css :logo-icon))) + + (mf/defc header [{:keys [project file page frame zoom section permissions index interactions-mode shown-thumbnails]}] (let [go-to-dashboard @@ -303,10 +307,10 @@ ;; If the user doesn't have permission we disable the link [:a {:class (stl/css :home-link) :on-click go-to-dashboard + :data-testid "penpot-logo-link" :style {:cursor (when-not (:in-team permissions) "auto") :pointer-events (when-not (:in-team permissions) "none")}} - [:span {:class (stl/css :logo-icon)} - i/logo-icon]] + penpot-logo-icon] [:& header-sitemap {:project project :file file diff --git a/frontend/src/app/main/ui/viewer/header.scss b/frontend/src/app/main/ui/viewer/header.scss index c131e07b4..1a338aac0 100644 --- a/frontend/src/app/main/ui/viewer/header.scss +++ b/frontend/src/app/main/ui/viewer/header.scss @@ -34,16 +34,14 @@ .home-link { padding: 0; + display: grid; + place-content: center; } .logo-icon { - @include flexCenter; - width: $s-32; - height: $s-32; - svg { - width: $s-28; - fill: var(--icon-foreground-hover); - } + width: $s-28; + height: $s-28; + fill: var(--icon-foreground-hover); } .sitemap-zone { diff --git a/frontend/src/app/main/ui/workspace/sidebar/layers.cljs b/frontend/src/app/main/ui/workspace/sidebar/layers.cljs index afe62a2b1..737f80fdd 100644 --- a/frontend/src/app/main/ui/workspace/sidebar/layers.cljs +++ b/frontend/src/app/main/ui/workspace/sidebar/layers.cljs @@ -69,7 +69,7 @@ highlighted (mf/deref refs/highlighted-shapes) highlighted (hooks/use-equal-memo highlighted) root (get objects uuid/zero)] - [:div {:class (stl/css :element-list)} + [:div {:class (stl/css :element-list) :data-testid "layer-item"} [:& hooks/sortable-container {} (for [[index id] (reverse (d/enumerate (:shapes root)))] (when-let [obj (get objects id)] @@ -510,7 +510,7 @@ (mf/use-fn #(st/emit! (dw/toggle-focus-mode)))] - [:div#layers {:class (stl/css :layers)} + [:div#layers {:class (stl/css :layers) :data-testid "layers"} (if (d/not-empty? focus) [:div {:class (stl/css :tool-window-bar)} [:button {:class (stl/css :focus-title) diff --git a/frontend/src/app/main/ui/workspace/sidebar/options/menus/constraints.cljs b/frontend/src/app/main/ui/workspace/sidebar/options/menus/constraints.cljs index c669ad445..f9ec1f889 100644 --- a/frontend/src/app/main/ui/workspace/sidebar/options/menus/constraints.cljs +++ b/frontend/src/app/main/ui/workspace/sidebar/options/menus/constraints.cljs @@ -207,14 +207,14 @@ :on-click on-constraint-button-clicked} [:span {:class (stl/css :resalted-area)}]]]] [:div {:class (stl/css :contraints-selects)} - [:div {:class (stl/css :horizontal-select)} + [:div {:class (stl/css :horizontal-select) :data-testid "constraint-h-select"} [:& select - {:default-value (d/nilv (d/name constraints-h) "scale") + {:default-value (if (not= constraints-h :multiple) (d/nilv (d/name constraints-h) "scale") "") :options options-h :on-change on-constraint-h-select-changed}]] - [:div {:class (stl/css :vertical-select)} + [:div {:class (stl/css :vertical-select) :data-testid "constraint-v-select"} [:& select - {:default-value (d/nilv (d/name constraints-v) "scale") + {:default-value (if (not= constraints-v :multiple) (d/nilv (d/name constraints-v) "scale") "") :options options-v :on-change on-constraint-v-select-changed}]] (when first-level? diff --git a/frontend/yarn.lock b/frontend/yarn.lock index c93f4b041..ce42f642a 100644 --- a/frontend/yarn.lock +++ b/frontend/yarn.lock @@ -7924,6 +7924,7 @@ __metadata: "@types/node": "npm:^20.11.20" animate.css: "npm:^4.1.1" autoprefixer: "npm:^10.4.19" + compression: "npm:^1.7.4" concurrently: "npm:^8.2.2" date-fns: "npm:^3.6.0" draft-js: "git+https://github.com/penpot/draft-js.git#commit=4a99b2a6020b2af97f6dc5fa1b4275ec16b559a0"