diff --git a/frontend/playwright/data/viewer/get-file-fragment-interaction-blocks-child.json b/frontend/playwright/data/viewer/get-file-fragment-interaction-blocks-child.json new file mode 100644 index 0000000000..e6d23f00de --- /dev/null +++ b/frontend/playwright/data/viewer/get-file-fragment-interaction-blocks-child.json @@ -0,0 +1,578 @@ +{ + "~:id": "~ucc000000-0000-0000-0000-000000000003", + "~:file-id": "~ucc000000-0000-0000-0000-000000000001", + "~:created-at": "~m1737715191353", + "~:data": { + "~:options": {}, + "~:objects": { + "~u00000000-0000-0000-0000-000000000000": { + "~#shape": { + "~:y": 0, + "~:hide-fill-on-export": false, + "~:transform": { + "~#matrix": { + "~:a": 1, + "~:b": 0, + "~:c": 0, + "~:d": 1, + "~:e": 0, + "~:f": 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 + } + } + ], + "~:r2": 0, + "~:proportion-lock": false, + "~:transform-inverse": { + "~#matrix": { + "~:a": 1, + "~:b": 0, + "~:c": 0, + "~:d": 1, + "~:e": 0, + "~:f": 0 + } + }, + "~:r3": 0, + "~:r1": 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, + "~:r4": 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": [ + "~ucc000000-0001-0000-0000-000000000001", + "~ucc000000-0002-0000-0000-000000000001", + "~ucc000000-0003-0000-0000-000000000001" + ] + } + }, + "~ucc000000-0001-0000-0000-000000000001": { + "~#shape": { + "~:y": 0, + "~:hide-fill-on-export": false, + "~:transform": { + "~#matrix": { + "~:a": 1, + "~:b": 0, + "~:c": 0, + "~:d": 1, + "~:e": 0, + "~:f": 0 + } + }, + "~:rotation": 0, + "~:grow-type": "~:fixed", + "~:hide-in-viewer": false, + "~:name": "Screen1", + "~:width": 400, + "~:type": "~:frame", + "~:points": [ + { + "~#point": { + "~:x": 0, + "~:y": 0 + } + }, + { + "~#point": { + "~:x": 400, + "~:y": 0 + } + }, + { + "~#point": { + "~:x": 400, + "~:y": 300 + } + }, + { + "~#point": { + "~:x": 0, + "~:y": 300 + } + } + ], + "~:r2": 0, + "~:proportion-lock": false, + "~:transform-inverse": { + "~#matrix": { + "~:a": 1, + "~:b": 0, + "~:c": 0, + "~:d": 1, + "~:e": 0, + "~:f": 0 + } + }, + "~:r3": 0, + "~:r1": 0, + "~:id": "~ucc000000-0001-0000-0000-000000000001", + "~:parent-id": "~u00000000-0000-0000-0000-000000000000", + "~:frame-id": "~u00000000-0000-0000-0000-000000000000", + "~:strokes": [], + "~:x": 0, + "~:proportion": 1, + "~:r4": 0, + "~:selrect": { + "~#rect": { + "~:x": 0, + "~:y": 0, + "~:width": 400, + "~:height": 300, + "~:x1": 0, + "~:y1": 0, + "~:x2": 400, + "~:y2": 300 + } + }, + "~:fills": [ + { + "~:fill-color": "#FFFFFF", + "~:fill-opacity": 1 + } + ], + "~:flip-x": null, + "~:height": 300, + "~:flip-y": null, + "~:shapes": [] + } + }, + "~ucc000000-0002-0000-0000-000000000001": { + "~#shape": { + "~:y": 0, + "~:hide-fill-on-export": false, + "~:transform": { + "~#matrix": { + "~:a": 1, + "~:b": 0, + "~:c": 0, + "~:d": 1, + "~:e": 0, + "~:f": 0 + } + }, + "~:rotation": 0, + "~:grow-type": "~:fixed", + "~:hide-in-viewer": false, + "~:name": "Screen2", + "~:width": 400, + "~:type": "~:frame", + "~:points": [ + { + "~#point": { + "~:x": 500, + "~:y": 0 + } + }, + { + "~#point": { + "~:x": 900, + "~:y": 0 + } + }, + { + "~#point": { + "~:x": 900, + "~:y": 300 + } + }, + { + "~#point": { + "~:x": 500, + "~:y": 300 + } + } + ], + "~:r2": 0, + "~:proportion-lock": false, + "~:transform-inverse": { + "~#matrix": { + "~:a": 1, + "~:b": 0, + "~:c": 0, + "~:d": 1, + "~:e": 0, + "~:f": 0 + } + }, + "~:r3": 0, + "~:r1": 0, + "~:id": "~ucc000000-0002-0000-0000-000000000001", + "~:parent-id": "~u00000000-0000-0000-0000-000000000000", + "~:frame-id": "~u00000000-0000-0000-0000-000000000000", + "~:strokes": [], + "~:x": 500, + "~:proportion": 1, + "~:r4": 0, + "~:selrect": { + "~#rect": { + "~:x": 500, + "~:y": 0, + "~:width": 400, + "~:height": 300, + "~:x1": 500, + "~:y1": 0, + "~:x2": 900, + "~:y2": 300 + } + }, + "~:fills": [ + { + "~:fill-color": "#F0F0F0", + "~:fill-opacity": 1 + } + ], + "~:flip-x": null, + "~:height": 300, + "~:flip-y": null, + "~:shapes": [ + "~ucc000000-0002-0001-0000-000000000001" + ] + } + }, + "~ucc000000-0003-0000-0000-000000000001": { + "~#shape": { + "~:y": 0, + "~:hide-fill-on-export": false, + "~:transform": { + "~#matrix": { + "~:a": 1, + "~:b": 0, + "~:c": 0, + "~:d": 1, + "~:e": 0, + "~:f": 0 + } + }, + "~:rotation": 0, + "~:grow-type": "~:fixed", + "~:hide-in-viewer": false, + "~:name": "Screen3", + "~:width": 400, + "~:type": "~:frame", + "~:points": [ + { + "~#point": { + "~:x": 1000, + "~:y": 0 + } + }, + { + "~#point": { + "~:x": 1400, + "~:y": 0 + } + }, + { + "~#point": { + "~:x": 1400, + "~:y": 300 + } + }, + { + "~#point": { + "~:x": 1000, + "~:y": 300 + } + } + ], + "~:r2": 0, + "~:proportion-lock": false, + "~:transform-inverse": { + "~#matrix": { + "~:a": 1, + "~:b": 0, + "~:c": 0, + "~:d": 1, + "~:e": 0, + "~:f": 0 + } + }, + "~:r3": 0, + "~:r1": 0, + "~:id": "~ucc000000-0003-0000-0000-000000000001", + "~:parent-id": "~u00000000-0000-0000-0000-000000000000", + "~:frame-id": "~u00000000-0000-0000-0000-000000000000", + "~:strokes": [], + "~:x": 1000, + "~:proportion": 1, + "~:r4": 0, + "~:selrect": { + "~#rect": { + "~:x": 1000, + "~:y": 0, + "~:width": 400, + "~:height": 300, + "~:x1": 1000, + "~:y1": 0, + "~:x2": 1400, + "~:y2": 300 + } + }, + "~:fills": [ + { + "~:fill-color": "#E0E0FF", + "~:fill-opacity": 1 + } + ], + "~:flip-x": null, + "~:height": 300, + "~:flip-y": null, + "~:shapes": [] + } + }, + "~ucc000000-0002-0001-0000-000000000001": { + "~#shape": { + "~:y": 50, + "~:hide-fill-on-export": false, + "~:transform": { + "~#matrix": { + "~:a": 1, + "~:b": 0, + "~:c": 0, + "~:d": 1, + "~:e": 0, + "~:f": 0 + } + }, + "~:rotation": 0, + "~:grow-type": "~:fixed", + "~:hide-in-viewer": false, + "~:name": "HighlightState", + "~:width": 300, + "~:type": "~:frame", + "~:points": [ + { + "~#point": { + "~:x": 550, + "~:y": 50 + } + }, + { + "~#point": { + "~:x": 850, + "~:y": 50 + } + }, + { + "~#point": { + "~:x": 850, + "~:y": 250 + } + }, + { + "~#point": { + "~:x": 550, + "~:y": 250 + } + } + ], + "~:r2": 0, + "~:proportion-lock": false, + "~:transform-inverse": { + "~#matrix": { + "~:a": 1, + "~:b": 0, + "~:c": 0, + "~:d": 1, + "~:e": 0, + "~:f": 0 + } + }, + "~:r3": 0, + "~:r1": 0, + "~:id": "~ucc000000-0002-0001-0000-000000000001", + "~:parent-id": "~ucc000000-0002-0000-0000-000000000001", + "~:frame-id": "~ucc000000-0002-0000-0000-000000000001", + "~:strokes": [], + "~:x": 550, + "~:proportion": 1, + "~:r4": 0, + "~:selrect": { + "~#rect": { + "~:x": 550, + "~:y": 50, + "~:width": 300, + "~:height": 200, + "~:x1": 550, + "~:y1": 50, + "~:x2": 850, + "~:y2": 250 + } + }, + "~:fills": [ + { + "~:fill-color": "#CCCCCC", + "~:fill-opacity": 1 + } + ], + "~:flip-x": null, + "~:height": 200, + "~:flip-y": null, + "~:shapes": [ + "~ucc000000-0002-0001-0000-000000000002" + ], + "~:interactions": [ + { + "~:event-type": "~:mouse-leave", + "~:action-type": "~:navigate", + "~:destination": "~ucc000000-0001-0000-0000-000000000001", + "~:preserve-scroll": false + } + ] + } + }, + "~ucc000000-0002-0001-0000-000000000002": { + "~#shape": { + "~:y": 190, + "~:hide-fill-on-export": false, + "~:transform": { + "~#matrix": { + "~:a": 1, + "~:b": 0, + "~:c": 0, + "~:d": 1, + "~:e": 0, + "~:f": 0 + } + }, + "~:rotation": 0, + "~:name": "HoverQuickToolArrowRight", + "~:width": 60, + "~:type": "~:rect", + "~:points": [ + { + "~#point": { + "~:x": 780, + "~:y": 190 + } + }, + { + "~#point": { + "~:x": 840, + "~:y": 190 + } + }, + { + "~#point": { + "~:x": 840, + "~:y": 230 + } + }, + { + "~#point": { + "~:x": 780, + "~:y": 230 + } + } + ], + "~:r2": 0, + "~:proportion-lock": false, + "~:transform-inverse": { + "~#matrix": { + "~:a": 1, + "~:b": 0, + "~:c": 0, + "~:d": 1, + "~:e": 0, + "~:f": 0 + } + }, + "~:r3": 0, + "~:r1": 0, + "~:id": "~ucc000000-0002-0001-0000-000000000002", + "~:parent-id": "~ucc000000-0002-0001-0000-000000000001", + "~:frame-id": "~ucc000000-0002-0001-0000-000000000001", + "~:strokes": [], + "~:x": 780, + "~:proportion": 1, + "~:r4": 0, + "~:selrect": { + "~#rect": { + "~:x": 780, + "~:y": 190, + "~:width": 60, + "~:height": 40, + "~:x1": 780, + "~:y1": 190, + "~:x2": 840, + "~:y2": 230 + } + }, + "~:fills": [ + { + "~:fill-color": "#FF6600", + "~:fill-opacity": 1 + } + ], + "~:flip-x": null, + "~:height": 40, + "~:flip-y": null, + "~:interactions": [ + { + "~:event-type": "~:click", + "~:action-type": "~:navigate", + "~:destination": "~ucc000000-0003-0000-0000-000000000001", + "~:preserve-scroll": false + } + ] + } + } + }, + "~:id": "~ucc000000-0000-0000-0000-000000000002", + "~:name": "Page 1", + "~:flows": {} + } +} \ No newline at end of file diff --git a/frontend/playwright/data/viewer/get-view-only-bundle-interaction-blocks-child.json b/frontend/playwright/data/viewer/get-view-only-bundle-interaction-blocks-child.json new file mode 100644 index 0000000000..008e6b3d23 --- /dev/null +++ b/frontend/playwright/data/viewer/get-view-only-bundle-interaction-blocks-child.json @@ -0,0 +1,85 @@ +{ + "~:permissions": { + "~:type": "~:membership", + "~:is-owner": true, + "~:is-admin": true, + "~:can-edit": true, + "~:can-read": true, + "~:is-logged": true, + "~:in-team": true + }, + "~:libraries": [], + "~:file": { + "~:features": { + "~#set": [ + "layout/grid", + "fdata/pointer-map", + "fdata/objects-map", + "components/v2", + "fdata/shape-data-type" + ] + }, + "~:has-media-trimmed": false, + "~:comment-thread-seqn": 0, + "~:name": "Interaction Blocks Child Test", + "~:revn": 1, + "~:modified-at": "~m1737715191361", + "~:vern": 0, + "~:id": "~ucc000000-0000-0000-0000-000000000001", + "~:is-shared": false, + "~:version": 61, + "~:project-id": "~ucc000000-0000-0000-0000-000000000004", + "~:created-at": "~m1737708162019", + "~:data": { + "~:id": "~ucc000000-0000-0000-0000-000000000001", + "~:options": { + "~:components-v2": true + }, + "~:pages": [ + "~ucc000000-0000-0000-0000-000000000002" + ], + "~:pages-index": { + "~ucc000000-0000-0000-0000-000000000002": { + "~#penpot/pointer": [ + "~ucc000000-0000-0000-0000-000000000003", + { + "~:created-at": "~m1737715191371" + } + ] + } + } + } + }, + "~:team": { + "~:id": "~ucc000000-0000-0000-0000-000000000005", + "~:created-at": "~m1737707537358", + "~:modified-at": "~m1737707537358", + "~:name": "Test Team", + "~:is-default": false, + "~:features": { + "~#set": [ + "layout/grid", + "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 + } + }, + "~:project": { + "~:id": "~ucc000000-0000-0000-0000-000000000004", + "~:name": "Test Project", + "~:team-id": "~ucc000000-0000-0000-0000-000000000005" + }, + "~:share-links": [], + "~:fonts": [] +} \ No newline at end of file diff --git a/frontend/playwright/ui/pages/ViewerPage.js b/frontend/playwright/ui/pages/ViewerPage.js index 05ba705bc5..5aaf2e0c37 100644 --- a/frontend/playwright/ui/pages/ViewerPage.js +++ b/frontend/playwright/ui/pages/ViewerPage.js @@ -56,6 +56,21 @@ export class ViewerPage extends BaseWebSocketPage { ); } + async setupFileWithInteractionBlocksChild() { + await this.mockRPC( + /get\-view\-only\-bundle\?/, + "viewer/get-view-only-bundle-interaction-blocks-child.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-interaction-blocks-child.json", + ); + } + async setupFileWithComments() { await this.mockRPC( /get\-view\-only\-bundle\?/, diff --git a/frontend/playwright/ui/specs/viewer-content.spec.js b/frontend/playwright/ui/specs/viewer-content.spec.js index ab85f18020..f17b1f22eb 100644 --- a/frontend/playwright/ui/specs/viewer-content.spec.js +++ b/frontend/playwright/ui/specs/viewer-content.spec.js @@ -8,6 +8,44 @@ test.beforeEach(async ({ page }) => { const multipleBoardsFileId = "dd5cc0bb-91ff-81b9-8004-77df9cd3edb0"; const multipleBoardsPageId = "dd5cc0bb-91ff-81b9-8004-77df9cd3edb3"; +const interactionBlocksChildFileId = "cc000000-0000-0000-0000-000000000001"; +const interactionBlocksChildPageId = "cc000000-0000-0000-0000-000000000002"; + +test("Child click interaction works when parent has mouse-leave interaction", async ({ + page, +}) => { + const viewer = new ViewerPage(page); + await viewer.setupLoggedInUser(); + await viewer.setupFileWithInteractionBlocksChild(); + + // Start at Screen2 (index=1): HighlightState (frame w/ mouse-leave) contains + // HoverQuickToolArrowRight (rect w/ click → Screen3) + await viewer.goToViewer({ + fileId: interactionBlocksChildFileId, + pageId: interactionBlocksChildPageId, + }); + + // Click Next to go to Screen2 (index=1 in the 3-board sequence) + await viewer.page.getByRole("button", { name: "Next" }).click(); + await expect(viewer.page).toHaveURL(/index=1/); + + // The child shape (HoverQuickToolArrowRight) has a click interaction. + // Its shape-container g element has cursor="pointer" set via SVG attribute. + // The parent (HighlightState) only has mouse-leave, so cursor is not "pointer". + const childShapeContainer = viewer.page + .locator('svg.main_ui_viewer_interactions__not-fixed g[cursor="pointer"]') + .first(); + + await expect(childShapeContainer).toBeVisible(); + + // Clicking on the child should navigate to Screen3. + // Bug (before fix): parent's interaction overlay rect is rendered on top and blocks this click. + // Note: Screen3 is at index=0 in the viewer (frames sorted by Z-index, highest first). + await childShapeContainer.click(); + + await expect(viewer.page).toHaveURL(/index=0/, { timeout: 5000 }); +}); + test("Navigate with arrows", async ({ page }) => { const viewer = new ViewerPage(page); await viewer.setupLoggedInUser(); diff --git a/frontend/src/app/main/ui/viewer/shapes.cljs b/frontend/src/app/main/ui/viewer/shapes.cljs index c7b65b5502..880c515c49 100644 --- a/frontend/src/app/main/ui/viewer/shapes.cljs +++ b/frontend/src/app/main/ui/viewer/shapes.cljs @@ -291,6 +291,7 @@ :stroke "var(--color-accent-tertiary)" :stroke-width (if show-interactions 1 0) :fill-opacity (if show-interactions 0.2 0) + :pointer-events "none" :transform (gsh/transform-str shape)}])))