Skip to content

Commit 9b28f6e

Browse files
author
DavidQ
committed
Fix Object Vector Studio transform layout geometry apply preview editing and shape delete actions - PR_26133_011-object-editor-controls-and-preview-actions
1 parent bb618a9 commit 9b28f6e

5 files changed

Lines changed: 623 additions & 61 deletions

File tree

Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,19 +1,19 @@
1-
# PR_26133_010 Playwright V8 Coverage Report
1+
# PR_26133_011 Playwright V8 Coverage Report
22

33
Coverage source: `docs/dev/reports/playwright_v8_coverage_report.txt`, refreshed by the final `npm run test:workspace-v2` run.
44

55
## Summary
66

77
- Coverage is advisory only; no thresholds are enforced.
88
- Workspace Manager V2 entry point: 91%.
9-
- Changed Object Vector Studio V2 runtime coverage entries:
10-
- `tools/object-vector-studio-v2/js/ToolStarterApp.js`: 91%, executed lines 3335/3335, executed functions 349/385.
9+
- Object Vector Studio V2 runtime coverage entries from the generated report:
10+
- `tools/object-vector-studio-v2/js/ToolStarterApp.js`: 92%, executed lines 3619/3619, executed functions 383/417.
1111
- `tools/object-vector-studio-v2/js/bootstrap.js`: 80%, executed lines 100/100, executed functions 4/5.
1212
- The generated report lists `tests/playwright/tools/WorkspaceManagerV2.spec.mjs` as changed JS not collected by browser runtime coverage.
1313

1414
## Validation Context
1515

1616
- Main command: `npm run test:workspace-v2`.
17-
- Result: 46 passed.
18-
- Focused Object Vector Studio V2 layout scenario passed.
19-
- Manual Object Transform probe confirmed accordion separation, transform controls, collapse/reopen behavior, a move action, compact layout, and no console/runtime errors.
17+
- Result: 47 passed.
18+
- Focused Object Vector Studio V2 layout and mouse-editing scenarios passed.
19+
- Coverage includes the runtime paths for transform validation, geometry application, preview pointer editing, shape tile delete, and object ID preview updates.
Lines changed: 12 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -1,28 +1,27 @@
1-
# PR_26133_010 Workspace V2 Results
1+
# PR_26133_011 Workspace V2 Results
22

33
## Command Results
44

55
- `node --check tools/object-vector-studio-v2/js/ToolStarterApp.js`: passed.
6-
- `node --check tools/object-vector-studio-v2/js/bootstrap.js`: passed.
76
- `node --check tests/playwright/tools/WorkspaceManagerV2.spec.mjs`: passed.
8-
- `npx playwright test tests/playwright/tools/WorkspaceManagerV2.spec.mjs --grep "shows Object Vector Studio V2 layout shell and schema-only palette gate"`: 1 passed.
9-
- `npm run test:workspace-v2`: 46 passed.
7+
- `npx playwright test tests/playwright/tools/WorkspaceManagerV2.spec.mjs --grep "Object Vector Studio V2 (layout shell|preview shapes with mouse actions)"`: 2 passed.
8+
- `npm run test:workspace-v2`: 47 passed.
109
- `git diff --check`: passed with LF-to-CRLF working-copy warnings for touched files.
1110

1211
## Targeted Object Vector Studio V2 Verification
1312

14-
- Confirmed left accordion order: `Object`, `Object Details`, `Object Transform`, `Objects`.
15-
- Confirmed `Object Transform` renders directly under `Object Details`.
16-
- Confirmed `Object Details` no longer contains Transform text or transform action buttons.
17-
- Confirmed `Object Transform` contains the selected-shape transform section and the existing move/rotate/scale/resize/origin controls.
18-
- Confirmed `Object Transform` collapses and reopens through the existing accordion behavior.
19-
- Confirmed transform functionality by moving the selected rectangle and verifying JSON updated with transform `x: 7`.
20-
- Confirmed compact left sections use non-growing layout and their content reaches the section bottom.
21-
- Confirmed manual probe reported `consoleErrors: []` and `pageErrors: []`.
13+
- Confirmed Object Transform textboxes render inline beside labels for Move X, Move Y, Rotate, Scale, Origin X, Origin Y, and Resize.
14+
- Confirmed invalid transform input is marked with `aria-invalid`, writes an actionable failure, and leaves the selected shape transform unchanged.
15+
- Confirmed Apply Geometry commits schema-valid selected-shape geometry changes.
16+
- Confirmed selected-shape summary renders below Apply Geometry.
17+
- Confirmed Object Preview mouse editing works for selected-shape drag/drop, rectangle handle resize, and line endpoint movement.
18+
- Confirmed shape tile delete X removes only the targeted tile shape.
19+
- Confirmed Object Name edits immediately update Object Preview Object ID before Rename is clicked.
20+
- Confirmed Object Preview coordinate/grid/zoom expectations from prior PRs remain covered by the workspace-v2 suite.
21+
- Confirmed targeted Object Vector Studio tests reported no console/page errors.
2222

2323
## Scope Checks
2424

25-
- Transform controls were moved without changing their handlers.
2625
- Existing Object Vector Studio V2 JSON contracts were preserved.
2726
- No sample JSON files were changed.
2827
- No unrelated tool/runtime files were changed.

tests/playwright/tools/WorkspaceManagerV2.spec.mjs

Lines changed: 179 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1550,6 +1550,57 @@ test.describe("Workspace Manager V2 bootstrap", () => {
15501550
await expect(page.locator("#objectVectorStudioV2ObjectTransform")).toContainText("Selected Shape: rectangle-1");
15511551
await expect(page.locator("#objectVectorStudioV2ObjectTransform")).toContainText("Transform");
15521552
await expect(page.locator("#objectVectorStudioV2ObjectTransform #objectVectorStudioV2MoveShapeButton")).toHaveCount(1);
1553+
const objectDetailsOrder = await page.locator("#objectVectorStudioV2ObjectDetails").evaluate((details) => {
1554+
const applyButton = details.querySelector("#objectVectorStudioV2ApplyGeometryButton");
1555+
const summaryHeading = Array.from(details.querySelectorAll("h3")).find((heading) => heading.textContent.trim() === "Selected Shape: rectangle-1");
1556+
return {
1557+
applyBeforeSummary: Boolean(applyButton && summaryHeading && (applyButton.compareDocumentPosition(summaryHeading) & Node.DOCUMENT_POSITION_FOLLOWING)),
1558+
summaryItems: Array.from(details.querySelectorAll("h4, #objectVectorStudioV2ApplyGeometryButton, h3, .object-vector-studio-v2__detail-label, .object-vector-studio-v2__detail-value, .tool-starter__hint"))
1559+
.map((element) => element.textContent.trim())
1560+
};
1561+
});
1562+
expect(objectDetailsOrder.applyBeforeSummary).toBe(true);
1563+
expect(objectDetailsOrder.summaryItems).toEqual([
1564+
"Rectangle Geometry",
1565+
"Apply Geometry",
1566+
"Selected Shape: rectangle-1",
1567+
"Selected Shape",
1568+
"rectangle-1 (rectangle)",
1569+
"Group",
1570+
"None",
1571+
"Color",
1572+
"#ffffff",
1573+
"Editable fields below are limited to schema-valid geometry fields for the selected shape."
1574+
]);
1575+
const transformFieldLayout = await page.locator("#objectVectorStudioV2ObjectTransform").evaluate((panel) => (
1576+
Array.from(panel.querySelectorAll(".object-vector-studio-v2__edit-field--inline")).map((field) => {
1577+
const label = field.querySelector("span");
1578+
const input = field.querySelector("input");
1579+
const labelRect = label.getBoundingClientRect();
1580+
const inputRect = input.getBoundingClientRect();
1581+
return {
1582+
inline: Math.abs((labelRect.top + labelRect.height / 2) - (inputRect.top + inputRect.height / 2)) < 4 && labelRect.right <= inputRect.left,
1583+
label: label.textContent.trim()
1584+
};
1585+
})
1586+
));
1587+
expect(transformFieldLayout).toEqual([
1588+
{ inline: true, label: "Move X" },
1589+
{ inline: true, label: "Move Y" },
1590+
{ inline: true, label: "Rotate" },
1591+
{ inline: true, label: "Scale" },
1592+
{ inline: true, label: "Origin X" },
1593+
{ inline: true, label: "Origin Y" },
1594+
{ inline: true, label: "Resize" }
1595+
]);
1596+
const transformBeforeInvalid = await page.evaluate(() => window.__objectVectorStudioV2App.selectedShape().transform);
1597+
await page.locator("#objectVectorStudioV2MoveXInput").fill("");
1598+
await page.locator("#objectVectorStudioV2MoveYInput").fill("7");
1599+
await page.locator("#objectVectorStudioV2MoveShapeButton").click();
1600+
await expect(page.locator("#objectVectorStudioV2MoveXInput")).toHaveAttribute("aria-invalid", "true");
1601+
await expect(page.locator("#statusLog")).toHaveValue(/FAIL Invalid transform rejected for shape rectangle-1: Move X must be a finite number\./);
1602+
const transformAfterInvalid = await page.evaluate(() => window.__objectVectorStudioV2App.selectedShape().transform);
1603+
expect(transformAfterInvalid).toEqual(transformBeforeInvalid);
15531604
await expect(page.locator("#objectVectorStudioV2ObjectDetailsActions")).toHaveCount(0);
15541605
await expect(page.locator("#objectVectorStudioV2ShapeVisibilityButton")).toHaveCount(0);
15551606
await expect(page.locator("#objectVectorStudioV2ShapeLockButton")).toHaveCount(0);
@@ -1736,6 +1787,10 @@ test.describe("Workspace Manager V2 bootstrap", () => {
17361787
await expect(page.locator("[data-palette-color='#6fd3ff']")).toHaveClass(/is-selected/);
17371788
await expect(page.locator("#objectVectorStudioV2ObjectDetails")).toContainText("Rectangle Geometry");
17381789
await expect(page.locator("#objectVectorStudioV2ObjectTransform")).toContainText("Transform");
1790+
await page.locator("#objectVectorStudioV2ObjectDetails [data-shape-geometry-field='x']").fill("-70");
1791+
await page.locator("#objectVectorStudioV2ApplyGeometryButton").click();
1792+
await expect(page.locator("#objectVectorStudioV2JsonDetails")).toContainText('"x": -70');
1793+
await expect(page.locator("#statusLog")).toHaveValue(/OK Applied geometry edits to shape rectangle-1\./);
17391794
await page.locator('button[aria-controls="objectVectorStudioV2ObjectTransformContent"]').click();
17401795
await expect(page.locator("#objectVectorStudioV2ObjectTransformContent")).toBeHidden();
17411796
await page.locator('button[aria-controls="objectVectorStudioV2ObjectTransformContent"]').click();
@@ -1773,6 +1828,8 @@ test.describe("Workspace Manager V2 bootstrap", () => {
17731828
await page.locator("#objectVectorStudioV2MoveShapeButton").click();
17741829
await expect(page.locator("#objectVectorStudioV2JsonDetails")).toContainText('"x": 10');
17751830
await expect(page.locator("#objectVectorStudioV2JsonDetails")).toContainText('"y": 10');
1831+
await expect(page.locator("#objectVectorStudioV2MoveXInput")).toHaveValue("13");
1832+
await expect(page.locator("#objectVectorStudioV2MoveYInput")).toHaveValue("7");
17761833
await expect(page.locator("#statusLog")).toHaveValue(/OK Moved shape rectangle-1 by 10, 10\./);
17771834

17781835
await page.locator("#objectVectorStudioV2AngleSnapButton").click();
@@ -1793,6 +1850,7 @@ test.describe("Workspace Manager V2 bootstrap", () => {
17931850
await page.locator("#objectVectorStudioV2ResizeInput").fill("5");
17941851
await page.locator("#objectVectorStudioV2ResizeShapeButton").click();
17951852
await expect(page.locator("#objectVectorStudioV2JsonDetails")).toContainText('"width": 85');
1853+
await expect(page.locator("#objectVectorStudioV2ResizeInput")).toHaveValue("5");
17961854
await expect(page.locator("#statusLog")).toHaveValue(/OK Resized shape rectangle-1 by 5\./);
17971855

17981856
await page.locator("#objectVectorStudioV2BringForwardButton").click();
@@ -1959,6 +2017,7 @@ test.describe("Workspace Manager V2 bootstrap", () => {
19592017
await expect(page.locator("#objectVectorStudioV2ObjectPreviewFooter")).toContainText("Object ID: object.asteroids.object-2");
19602018

19612019
await page.locator("#objectVectorStudioV2ObjectNameInput").fill("Object 2 Renamed");
2020+
await expect(page.locator("#objectVectorStudioV2ObjectPreviewFooter")).toContainText("Object ID: object.asteroids.object-2-renamed");
19622021
await page.locator("#objectVectorStudioV2RenameObjectButton").click();
19632022
await expect(page.locator('[data-object-id="object.asteroids.object-2"]')).toHaveCount(0);
19642023
await expect(page.locator('[data-object-id="object.asteroids.object-2-renamed"]')).toContainText("Object 2 Renamed");
@@ -2292,6 +2351,126 @@ test.describe("Workspace Manager V2 bootstrap", () => {
22922351
}
22932352
});
22942353

2354+
test("edits Object Vector Studio V2 preview shapes with mouse actions and tile delete controls", async ({ page }) => {
2355+
const server = await startRepoServer();
2356+
const pageErrors = [];
2357+
const consoleErrors = [];
2358+
2359+
page.on("pageerror", (error) => {
2360+
pageErrors.push(error.message);
2361+
});
2362+
page.on("console", (message) => {
2363+
if (message.type() === "error") {
2364+
consoleErrors.push(message.text());
2365+
}
2366+
});
2367+
2368+
const dragLocator = async (selector, deltaX, deltaY) => {
2369+
const target = page.locator(selector);
2370+
await target.scrollIntoViewIfNeeded();
2371+
const box = await target.boundingBox();
2372+
expect(box).not.toBeNull();
2373+
const x = box.x + box.width / 2;
2374+
const y = box.y + box.height / 2;
2375+
await page.mouse.move(x, y);
2376+
await page.mouse.down();
2377+
await page.mouse.move(x + deltaX, y + deltaY, { steps: 4 });
2378+
await page.mouse.up();
2379+
};
2380+
const shapeSnapshot = async (shapeId) => page.evaluate((id) => (
2381+
JSON.parse(JSON.stringify(window.__objectVectorStudioV2App.selectedObject().shapes.find((shape) => shape.id === id) || null))
2382+
), shapeId);
2383+
2384+
await coverageReporter.start(page);
2385+
try {
2386+
await page.setViewportSize({ width: 1366, height: 1000 });
2387+
await page.goto(`${server.baseUrl}/tools/object-vector-studio-v2/index.html`, { waitUntil: "networkidle" });
2388+
await page.evaluate(() => {
2389+
sessionStorage.setItem("object-vector-studio-v2.runtimePalette", JSON.stringify({
2390+
id: "mouse-edit-palette",
2391+
swatches: [
2392+
{ id: "white", value: "#ffffff" },
2393+
{ id: "cyan", value: "#6fd3ff" }
2394+
]
2395+
}));
2396+
});
2397+
await page.locator("#objectVectorStudioV2ImportJsonInput").setInputFiles({
2398+
buffer: Buffer.from(JSON.stringify({
2399+
name: "Mouse Edit Object Set",
2400+
objects: [
2401+
{
2402+
id: "object.mouse.editor",
2403+
name: "Mouse Editor",
2404+
shapes: [
2405+
{
2406+
geometry: { height: 30, width: 40, x: -40, y: -20 },
2407+
id: "rectangle-1",
2408+
locked: false,
2409+
order: 1,
2410+
style: { fill: "#ffffff", stroke: "#6fd3ff", strokeWidth: 1 },
2411+
transform: { originX: -20, originY: -5, rotation: 0, scaleX: 1, scaleY: 1, x: 0, y: 0 },
2412+
type: "rectangle",
2413+
visible: true
2414+
},
2415+
{
2416+
geometry: { x1: -60, x2: -10, y1: 50, y2: 30 },
2417+
id: "line-2",
2418+
locked: false,
2419+
order: 2,
2420+
style: { fill: "none", stroke: "#ffffff", strokeWidth: 1 },
2421+
transform: { originX: -35, originY: 40, rotation: 0, scaleX: 1, scaleY: 1, x: 0, y: 0 },
2422+
type: "line",
2423+
visible: true
2424+
}
2425+
]
2426+
}
2427+
],
2428+
toolId: "object-vector-studio-v2",
2429+
version: 1
2430+
}, null, 2)),
2431+
mimeType: "application/json",
2432+
name: "object-vector-mouse-edit.json"
2433+
});
2434+
await expect(page.locator("#objectVectorStudioV2RenderSurface [data-shape-id='rectangle-1']")).toHaveClass(/is-selected/);
2435+
2436+
const rectangleBeforeDrag = await shapeSnapshot("rectangle-1");
2437+
await dragLocator("#objectVectorStudioV2RenderSurface [data-shape-id='rectangle-1']", 44, 24);
2438+
const rectangleAfterDrag = await shapeSnapshot("rectangle-1");
2439+
expect(rectangleAfterDrag.transform.x).not.toBe(rectangleBeforeDrag.transform.x);
2440+
expect(rectangleAfterDrag.transform.y).not.toBe(rectangleBeforeDrag.transform.y);
2441+
await expect(page.locator("#statusLog")).toHaveValue(/OK Dragged shape rectangle-1 by/);
2442+
2443+
const rectangleBeforeResize = await shapeSnapshot("rectangle-1");
2444+
await dragLocator("#objectVectorStudioV2RenderSurface [data-resize-handle='se']", 28, 20);
2445+
const rectangleAfterResize = await shapeSnapshot("rectangle-1");
2446+
expect(rectangleAfterResize.geometry.width).toBeGreaterThan(rectangleBeforeResize.geometry.width);
2447+
expect(rectangleAfterResize.geometry.height).toBeGreaterThan(rectangleBeforeResize.geometry.height);
2448+
await expect(page.locator("#statusLog")).toHaveValue(/OK Resized shape rectangle-1 with se handle\./);
2449+
2450+
await page.locator("#objectVectorStudioV2RenderSurface [data-shape-id='line-2']").click();
2451+
await expect(page.locator("#objectVectorStudioV2RenderSurface [data-line-endpoint='end']")).toHaveCount(1);
2452+
const lineBeforeEndpoint = await shapeSnapshot("line-2");
2453+
await dragLocator("#objectVectorStudioV2RenderSurface [data-line-endpoint='end']", 36, -18);
2454+
const lineAfterEndpoint = await shapeSnapshot("line-2");
2455+
expect(lineAfterEndpoint.geometry.x2).not.toBe(lineBeforeEndpoint.geometry.x2);
2456+
expect(lineAfterEndpoint.geometry.y2).not.toBe(lineBeforeEndpoint.geometry.y2);
2457+
await expect(page.locator("#statusLog")).toHaveValue(/OK Moved line end for shape line-2\./);
2458+
2459+
await page.locator("[data-shape-delete-id='line-2']").click();
2460+
await expect(page.locator("[data-object-tile-shape-id='line-2']")).toHaveCount(0);
2461+
await expect(page.locator("[data-object-tile-shape-id='rectangle-1']")).toHaveCount(1);
2462+
await expect(page.locator("#objectVectorStudioV2RenderSurface [data-shape-id='line-2']")).toHaveCount(0);
2463+
await expect(page.locator("#objectVectorStudioV2RenderSurface [data-shape-id='rectangle-1']")).toHaveCount(1);
2464+
await expect(page.locator("#statusLog")).toHaveValue(/OK Deleted shape line-2 from object tile shape delete\./);
2465+
2466+
expect(pageErrors).toEqual([]);
2467+
expect(consoleErrors).toEqual([]);
2468+
} finally {
2469+
await coverageReporter.stop(page);
2470+
await server.close();
2471+
}
2472+
});
2473+
22952474
test("expands Object Vector Studio V2 asset authoring controls", async ({ page }, testInfo) => {
22962475
const server = await startRepoServer();
22972476
const pageErrors = [];

0 commit comments

Comments
 (0)