Skip to content

Commit ceaf496

Browse files
author
DavidQ
committed
Anchor Object Preview mouse zoom to pointer and isolate shape tile action clicks - PR_26133_056-object-preview-pointer-zoom-and-shape-tile-actions
1 parent d10ec78 commit ceaf496

5 files changed

Lines changed: 220 additions & 83 deletions

File tree

docs/dev/reports/playwright_v8_coverage_report.md

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
1-
# PR_26133_055 Playwright V8 Coverage Report
1+
# PR_26133_056 Playwright V8 Coverage Report
22

3-
Task: PR_26133_055-shape-tile-group-icon-and-geometry-point-handles
3+
Task: PR_26133_056-object-preview-pointer-zoom-and-shape-tile-actions
44
Date: 2026-05-15
55

66
## Result
@@ -24,7 +24,7 @@ PASS - Coverage reporting was generated during `npm run test:workspace-v2`.
2424
## Relevant Runtime Coverage
2525

2626
```text
27-
(95%) tools/object-vector-studio-v2/js/ToolStarterApp.js - executed lines 5496/5496; executed functions 576/606
27+
(95%) tools/object-vector-studio-v2/js/ToolStarterApp.js - executed lines 5583/5583; executed functions 590/621
2828
```
2929

3030
## Guardrail
@@ -35,4 +35,4 @@ PASS - Coverage reporting was generated during `npm run test:workspace-v2`.
3535

3636
## PR-Specific Note
3737

38-
The Workspace V2 run exercised Object Vector Studio V2 shape tile group indicator layout, preview geometry point handles, line endpoint handles, polygon point drag editing, bounding-box geometry resize, dirty-state tracking, schema validation, and Asteroids runtime object-vector rendering.
38+
The Workspace V2 run exercised Object Vector Studio V2 pointer-anchored wheel zoom, off-center viewport origin updates, shape tile group/eye/trash action event handling, targeted visibility toggles, shape delete behavior, schema validation, and Asteroids runtime object-vector rendering.
Lines changed: 12 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
1-
# PR_26133_055 Workspace V2 Playwright Results
1+
# PR_26133_056 Workspace V2 Playwright Results
22

3-
Task: PR_26133_055-shape-tile-group-icon-and-geometry-point-handles
3+
Task: PR_26133_056-object-preview-pointer-zoom-and-shape-tile-actions
44
Date: 2026-05-15
55

66
## Result
@@ -14,19 +14,18 @@ PASS - `npm run test:workspace-v2` completed successfully.
1414

1515
## PR-Specific Coverage
1616

17-
- Verified grouped shape tile indicators render at the far right of shape rows while shape index/tool text stays on the left.
18-
- Verified rectangle corner handles expose geometry-point metadata and still resize geometry through bounding-box corner drag.
19-
- Verified line endpoint handles expose geometry-point metadata and still update point1/point2 geometry.
20-
- Verified polygon point handles render on each selected point and dragging a point updates the underlying geometry plus Object Geometry inputs.
21-
- Verified polygon bounding-box corner dragging adjusts geometry points.
22-
- Verified geometry handle drags mark Object Vector Studio V2 workspace state dirty after persisted edits.
17+
- Verified mouse-wheel zoom preserves the world coordinate under the pointer instead of zooming only from canvas center.
18+
- Verified off-center wheel zoom updates viewport origin and keeps existing zoom limits/display behavior.
19+
- Verified shape row layout renders as shape label, group button, eye button, and trash button.
20+
- Verified group/eye/trash controls are sibling buttons and are not nested inside the shape selection button.
21+
- Verified clicking the group button does not select the shape.
22+
- Verified clicking the eye action toggles the targeted shape visibility without selecting it.
23+
- Verified clicking the trash action deletes the targeted shape without selecting it first.
2324

2425
## Additional Validation
2526

26-
- Focused preview-handle slice passed:
27-
`npx playwright test tests/playwright/tools/WorkspaceManagerV2.spec.mjs --project=playwright --workers=1 --reporter=list --grep "preview shapes with mouse actions"` completed with 1 passed, 0 failed.
28-
- Focused group tile layout slice passed:
27+
- Focused pointer-zoom/shape-tile layout slice passed:
2928
`npx playwright test tests/playwright/tools/WorkspaceManagerV2.spec.mjs --project=playwright --workers=1 --reporter=list --grep "layout shell"` completed with 1 passed, 0 failed.
30-
- Focused dirty-state slice passed:
31-
`npx playwright test tests/playwright/tools/WorkspaceManagerV2.spec.mjs --project=playwright --workers=1 --reporter=list --grep "dirty state through persisted edits"` completed with 1 passed, 0 failed.
29+
- Focused preview delete/action slice passed:
30+
`npx playwright test tests/playwright/tools/WorkspaceManagerV2.spec.mjs --project=playwright --workers=1 --reporter=list --grep "preview shapes with mouse actions"` completed with 1 passed, 0 failed.
3231
- `git diff --check` passed.

tests/playwright/tools/WorkspaceManagerV2.spec.mjs

Lines changed: 68 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -2525,34 +2525,59 @@ test.describe("Workspace Manager V2 bootstrap", () => {
25252525
await expect(page.locator("#statusLog")).toHaveValue(/OK Grouped 2 shapes into group-1\./);
25262526
await expect(selectedShapeActions.locator("[data-shape-list-action='ungroup']")).toBeEnabled();
25272527
await expect(page.locator(".object-vector-studio-v2__object-tile[data-object-id='object.asteroids.object-1'] [data-shape-group-id='group-1']")).toHaveCount(2);
2528-
const groupIconColors = await page.locator(".object-vector-studio-v2__object-tile[data-object-id='object.asteroids.object-1'] [data-shape-group-id='group-1']").evaluateAll((icons) => icons.map((icon) => getComputedStyle(icon).color));
2528+
const groupIconColors = await page.locator(".object-vector-studio-v2__object-tile[data-object-id='object.asteroids.object-1'] [data-shape-group-id='group-1'] .object-vector-studio-v2__shape-group-indicator").evaluateAll((icons) => icons.map((icon) => getComputedStyle(icon).color));
25292529
expect(new Set(groupIconColors).size).toBe(1);
25302530
expect(groupIconColors[0]).not.toBe("");
25312531
const groupIconLayout = await page.locator(".object-vector-studio-v2__object-tile[data-object-id='object.asteroids.object-1'] .object-vector-studio-v2__object-tile-shape-row").first().evaluate((row) => {
25322532
const label = row.querySelector(".object-vector-studio-v2__shape-select-label");
2533+
const visibility = row.querySelector("[data-shape-visibility-index]");
25332534
const deleteButton = row.querySelector("[data-shape-delete-index]");
2534-
const groupIcon = row.querySelector("[data-shape-group-id='group-1']");
2535+
const groupButton = row.querySelector("[data-shape-group-id='group-1']");
25352536
const rowRect = row.getBoundingClientRect();
25362537
const labelRect = label.getBoundingClientRect();
2538+
const visibilityRect = visibility.getBoundingClientRect();
25372539
const deleteRect = deleteButton.getBoundingClientRect();
2538-
const groupRect = groupIcon.getBoundingClientRect();
2540+
const groupRect = groupButton.getBoundingClientRect();
25392541
return {
2540-
groupAfterActions: groupRect.left > deleteRect.right,
2541-
groupAtFarRight: Math.abs(rowRect.right - groupRect.right) <= 4,
2542+
actionButtonOrder: Array.from(row.querySelectorAll(".object-vector-studio-v2__shape-inline-actions > button")).map((button) => {
2543+
if (button.matches("[data-shape-group-id]")) return "group";
2544+
if (button.matches("[data-shape-visibility-index]")) return "eye";
2545+
if (button.matches("[data-shape-delete-index]")) return "trash";
2546+
return "unknown";
2547+
}),
2548+
groupAfterLabel: groupRect.left > labelRect.right,
2549+
groupButtonTag: groupButton.tagName.toLowerCase(),
2550+
groupBeforeEye: groupRect.right < visibilityRect.left,
25422551
labelStartsLeft: labelRect.left < deleteRect.left,
2543-
labelText: label.textContent.trim()
2552+
labelText: label.textContent.trim(),
2553+
nestedButtons: row.querySelectorAll(".object-vector-studio-v2__shape-select button").length,
2554+
trashAtFarRight: Math.abs(rowRect.right - deleteRect.right) <= 4
25442555
};
25452556
});
25462557
expect(groupIconLayout).toEqual({
2547-
groupAfterActions: true,
2548-
groupAtFarRight: true,
2558+
actionButtonOrder: ["group", "eye", "trash"],
2559+
groupAfterLabel: true,
2560+
groupButtonTag: "button",
2561+
groupBeforeEye: true,
25492562
labelStartsLeft: true,
2550-
labelText: "1. Circle"
2563+
labelText: "1. Circle",
2564+
nestedButtons: 0,
2565+
trashAtFarRight: true
25512566
});
25522567
await page.locator(".object-vector-studio-v2__object-tile[data-object-id='object.asteroids.object-1'] [data-object-tile-shape-index='0']").click();
25532568
await expect(page.locator(".object-vector-studio-v2__object-tile[data-object-id='object.asteroids.object-1'] [data-object-tile-shape-index='0']")).toHaveAttribute("aria-pressed", "true");
25542569
await expect(page.locator(".object-vector-studio-v2__object-tile[data-object-id='object.asteroids.object-1'] [data-object-tile-shape-index='1']")).toHaveAttribute("aria-pressed", "true");
25552570
await expect(page.locator("#statusLog")).toHaveValue(/Multi-select count: 2/);
2571+
await page.locator(".object-vector-studio-v2__object-tile[data-object-id='object.asteroids.object-1'] .object-vector-studio-v2__object-tile-shape-row:has([data-object-tile-shape-index='1']) [data-shape-group-id='group-1']").click();
2572+
await expect.poll(async () => page.evaluate(() => window.__objectVectorStudioV2App.selectedShapeIndex)).toBe(0);
2573+
await page.locator(".object-vector-studio-v2__object-tile[data-object-id='object.asteroids.object-1'] [data-shape-visibility-index='1']").click();
2574+
await expect.poll(async () => page.evaluate(() => window.__objectVectorStudioV2App.selectedShapeIndex)).toBe(0);
2575+
await expect(page.locator("#statusLog")).toHaveValue(/[Ss]hape row 1 visibility set to hidden/);
2576+
await page.locator(".object-vector-studio-v2__object-tile[data-object-id='object.asteroids.object-1'] [data-shape-visibility-index='1']").click();
2577+
await expect.poll(async () => page.evaluate(() => window.__objectVectorStudioV2App.selectedShapeIndex)).toBe(0);
2578+
await expect(page.locator("#statusLog")).toHaveValue(/[Ss]hape row 1 visibility set to visible/);
2579+
await page.locator(".object-vector-studio-v2__object-tile[data-object-id='object.asteroids.object-1'] [data-object-tile-shape-index='1']").click();
2580+
await expect.poll(async () => page.evaluate(() => window.__objectVectorStudioV2App.selectedShapeIndex)).toBe(1);
25562581
await selectedShapeActions.locator("[data-shape-list-action='ungroup']").click();
25572582
await expect(page.locator("#objectVectorStudioV2JsonDetails")).not.toContainText('"groupId": "group-1"');
25582583
await expect(page.locator(".object-vector-studio-v2__object-tile[data-object-id='object.asteroids.object-1'] [data-shape-group-id='group-1']")).toHaveCount(0);
@@ -2656,21 +2681,45 @@ test.describe("Workspace Manager V2 bootstrap", () => {
26562681
await expect(page.locator("#objectVectorStudioV2RenderSurface")).toHaveAttribute("viewBox", "-16000 -11000 32000 22000");
26572682
await page.locator("#objectVectorStudioV2ResetViewButton").click();
26582683
await expect(page.locator("#objectVectorStudioV2RenderSurface")).toHaveAttribute("viewBox", "-1600 -1100 3200 2200");
2659-
await page.locator("#objectVectorStudioV2RenderSurface").hover();
2684+
const wheelAnchor = await page.locator("#objectVectorStudioV2RenderSurface").evaluate((surface) => {
2685+
const rect = surface.getBoundingClientRect();
2686+
const clientX = Math.round(rect.left + rect.width * 0.75);
2687+
const clientY = Math.round(rect.top + rect.height * 0.35);
2688+
return {
2689+
before: window.__objectVectorStudioV2App.viewportPointFromClient(clientX, clientY),
2690+
clientX,
2691+
clientY
2692+
};
2693+
});
2694+
await page.mouse.move(wheelAnchor.clientX, wheelAnchor.clientY);
26602695
await page.mouse.wheel(0, -240);
26612696
await expect(page.locator("#objectVectorStudioV2CoordinateDisplay")).toContainText("Zoom 110%");
26622697
await expect(page.locator("#objectVectorStudioV2RenderSurface [data-grid-rendered='true']")).toHaveCount(1);
26632698
await expect(page.locator("#objectVectorStudioV2RenderSurface [data-center-origin='0,0']")).toHaveCount(1);
2664-
const zoomedGridState = await page.locator("#objectVectorStudioV2RenderSurface").evaluate((surface) => ({
2665-
backgroundImage: getComputedStyle(surface).backgroundImage,
2666-
gridLineCount: surface.querySelectorAll("[data-grid-rendered='true'] line").length,
2667-
viewBox: surface.getAttribute("viewBox")
2668-
}));
2699+
const zoomedGridState = await page.locator("#objectVectorStudioV2RenderSurface").evaluate((surface, anchor) => {
2700+
const app = window.__objectVectorStudioV2App;
2701+
const after = app.viewportPointFromClient(anchor.clientX, anchor.clientY);
2702+
return {
2703+
backgroundImage: getComputedStyle(surface).backgroundImage,
2704+
gridLineCount: surface.querySelectorAll("[data-grid-rendered='true'] line").length,
2705+
pointerDelta: {
2706+
x: Math.abs(after.x - anchor.before.x),
2707+
y: Math.abs(after.y - anchor.before.y)
2708+
},
2709+
viewport: { ...app.viewport },
2710+
viewBox: surface.getAttribute("viewBox")
2711+
};
2712+
}, wheelAnchor);
26692713
expect(zoomedGridState.backgroundImage).toBe("none");
26702714
expect(zoomedGridState.gridLineCount).toBeGreaterThan(0);
2671-
expect(zoomedGridState.viewBox).toBe("-1454.545 -1000 2909.091 2000");
2715+
expect(zoomedGridState.pointerDelta.x).toBeLessThan(0.001);
2716+
expect(zoomedGridState.pointerDelta.y).toBeLessThan(0.001);
2717+
expect(Math.abs(zoomedGridState.viewport.x)).toBeGreaterThan(1);
2718+
expect(Math.abs(zoomedGridState.viewport.y)).toBeGreaterThan(1);
2719+
expect(zoomedGridState.viewBox).not.toBe("-1454.545 -1000 2909.091 2000");
26722720
await page.mouse.wheel(0, 240);
26732721
await expect(page.locator("#objectVectorStudioV2CoordinateDisplay")).toContainText("Zoom 100%");
2722+
await expect(page.locator("#objectVectorStudioV2RenderSurface")).toHaveAttribute("viewBox", "-1600 -1100 3200 2200");
26742723
await page.evaluate(() => {
26752724
window.__objectVectorStudioV2App.zoomViewport(0.25);
26762725
});
@@ -3581,7 +3630,10 @@ test.describe("Workspace Manager V2 bootstrap", () => {
35813630
iconKey: "delete",
35823631
iconName: "nf-md-trash_can_outline"
35833632
});
3633+
await page.evaluate(() => window.__objectVectorStudioV2App.selectShape(0, "trash click selection guard"));
3634+
await expect.poll(async () => page.evaluate(() => window.__objectVectorStudioV2App.selectedShapeIndex)).toBe(0);
35843635
await page.locator("[data-shape-delete-index='2']").click();
3636+
await expect.poll(async () => page.evaluate(() => window.__objectVectorStudioV2App.selectedShapeIndex)).toBe(0);
35853637
await expect(page.locator("[data-object-tile-shape-index='2']")).toHaveCount(0);
35863638
await expect(page.locator("[data-object-tile-shape-index='1']")).toHaveCount(1);
35873639
await expect(page.locator("[data-object-tile-shape-index='0']")).toHaveCount(1);

0 commit comments

Comments
 (0)