Skip to content

Commit 9785875

Browse files
author
DavidQ
committed
Fix Object Transform movement preview controls and geometry editing layouts - PR_26133_013-object-transform-preview-controls-and-geometry-layout
1 parent 397ff7a commit 9785875

7 files changed

Lines changed: 231 additions & 88 deletions

File tree

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
# PR_26133_012 Playwright V8 Coverage Report
1+
# PR_26133_013 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

@@ -7,13 +7,13 @@ Coverage source: `docs/dev/reports/playwright_v8_coverage_report.txt`, refreshed
77
- Coverage is advisory only; no thresholds are enforced.
88
- Workspace Manager V2 entry point: 91%.
99
- Object Vector Studio V2 runtime coverage entries from the generated report:
10-
- `tools/object-vector-studio-v2/js/ToolStarterApp.js`: 92%, executed lines 3672/3672, executed functions 389/423.
11-
- `tools/object-vector-studio-v2/js/bootstrap.js`: 80%, executed lines 100/100, executed functions 4/5.
10+
- `tools/object-vector-studio-v2/js/ToolStarterApp.js`: 92%, executed lines 3689/3689, executed functions 393/426.
11+
- `tools/object-vector-studio-v2/js/bootstrap.js`: 80%, executed lines 102/102, 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`.
1717
- Result: 47 passed.
18-
- Focused Object Vector Studio V2 layout, preview coordinate, and mouse-editing scenarios passed.
19-
- Coverage includes the runtime paths for Objects header counts, shape tile visibility/delete actions, palette swatch sync, polygon point-list geometry editing, and invalid point rejection.
18+
- Focused Object Vector Studio V2 layout, preview coordinate, mouse-editing, and asset-authoring scenarios passed.
19+
- Coverage includes the runtime paths for transform summary updates, negative snapped movement, compact preview controls, Up/Down panning, larger center dot rendering, preview mouse edits, polygon X/Y point-list geometry editing, invalid point rejection, and two-row inline ellipse fields.

docs/dev/reports/playwright_workspace_v2_results.md

Lines changed: 10 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -1,21 +1,23 @@
1-
# PR_26133_012 Workspace V2 Results
1+
# PR_26133_013 Workspace V2 Results
22

33
## Command Results
44

55
- `node --check tools/object-vector-studio-v2/js/ToolStarterApp.js`: passed.
66
- `node --check tests/playwright/tools/WorkspaceManagerV2.spec.mjs`: passed.
7-
- `npx playwright test tests/playwright/tools/WorkspaceManagerV2.spec.mjs --grep "Object Vector Studio V2 (layout shell|preview coordinates|preview shapes with mouse actions)"`: 3 passed.
7+
- `npx playwright test tests/playwright/tools/WorkspaceManagerV2.spec.mjs --grep "Object Vector Studio V2 (layout shell|preview coordinates|preview shapes with mouse actions|asset authoring controls)"`: 4 passed.
88
- `npm run test:workspace-v2`: 47 passed.
99
- `git diff --check`: passed with LF-to-CRLF working-copy warnings for touched files.
1010

1111
## Targeted Object Vector Studio V2 Verification
1212

13-
- Confirmed the Objects accordion header shows the object/shape count text.
14-
- Confirmed shape tile visibility and delete icon buttons are adjacent on the right, with `Toggle shape visibility` immediately beside `Delete this shape`.
15-
- Confirmed selecting a shape highlights the matching palette swatch.
16-
- Confirmed Polygon Geometry renders as an editable point list with one `x,y` entry per polygon point.
17-
- Confirmed Apply Geometry commits valid polygon point-list edits to the selected polygon.
18-
- Confirmed invalid polygon point rows are marked with `aria-invalid`, report an actionable failure, preserve the invalid text for correction, and do not partially apply geometry.
13+
- Confirmed negative snapped Move X/Move Y values move the selected shape back across both axes instead of snapping half-step negatives to zero.
14+
- Confirmed Object Transform summary renders as `Transform x 0, y 0, rot 0, scale 1 x 1` and updates after transform actions.
15+
- Confirmed Object Preview controls use compact final-word labels and include working Up/Down pan controls after In.
16+
- Confirmed the center origin dot renders at radius 9.
17+
- Confirmed preview mouse drag, negative drag, rectangle handle resize, and line endpoint movement still edit the selected shape.
18+
- Confirmed Polygon Geometry renders separate X/Y point inputs, applies valid edits, marks invalid point cells with `aria-invalid`, and preserves invalid text without partial apply.
19+
- Confirmed the polygon point list uses a fixed scrolling area.
20+
- Confirmed Ellipse Geometry renders as two inline label/input rows: Cx/Cy, then Rx/Ry.
1921
- Confirmed Object Preview coordinate/grid/zoom expectations from prior PRs remain covered by the workspace-v2 suite.
2022
- Confirmed targeted Object Vector Studio tests reported no console/page errors.
2123

tests/playwright/tools/WorkspaceManagerV2.spec.mjs

Lines changed: 72 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -1209,7 +1209,7 @@ test.describe("Workspace Manager V2 bootstrap", () => {
12091209
await expect(page.locator("#objectVectorStudioV2FlattenObjectButton")).toHaveCount(0);
12101210
await expect(page.locator("#statusLog")).toHaveValue(/INFO Disabled controls stay inactive until a schema-valid payload/);
12111211
await expect(page.locator("[data-control-group='object-actions']")).toHaveCount(0);
1212-
await expect(page.locator("#objectVectorStudioV2ObjectContent > .object-vector-studio-v2__object-actions button")).toHaveText(["Add", "Rename", "Duplicate", "Delete"]);
1212+
await expect(page.locator("#objectVectorStudioV2ObjectContent > .object-vector-studio-v2__object-actions button")).toHaveText(["Add", "Rename", "Dup", "Delete"]);
12131213
await expect(page.locator("#objectVectorStudioV2ObjectsContent > .object-vector-studio-v2__objects-actions")).toHaveCount(0);
12141214
await expect(page.locator("#objectVectorStudioV2ObjectTypeInput")).toHaveCount(0);
12151215
await expect(page.locator("#objectVectorStudioV2ObjectTypeSelect")).toHaveCount(0);
@@ -1221,13 +1221,18 @@ test.describe("Workspace Manager V2 bootstrap", () => {
12211221
const tagLabel = element.querySelector("label[for='objectVectorStudioV2ObjectTagInput'] span");
12221222
const tagInput = element.querySelector("#objectVectorStudioV2ObjectTagInput");
12231223
const actions = element.querySelector(".object-vector-studio-v2__object-actions");
1224+
const actionButtons = Array.from(actions.querySelectorAll("button"));
1225+
const actionButtonRects = actionButtons.map((button) => button.getBoundingClientRect());
12241226
return {
1227+
actionButtonLabels: actionButtons.map((button) => button.textContent.trim()),
1228+
actionButtonMaxHeight: Math.max(...actionButtonRects.map((rect) => Math.round(rect.height))),
1229+
actionsSingleLine: actionButtonRects.every((rect) => Math.abs(rect.top - actionButtonRects[0].top) < 2),
12251230
actionsAtBottom: actions.getBoundingClientRect().bottom >= element.getBoundingClientRect().bottom - 12,
12261231
nameInline: Math.abs((nameLabel.getBoundingClientRect().top + nameLabel.getBoundingClientRect().height / 2) - (nameInput.getBoundingClientRect().top + nameInput.getBoundingClientRect().height / 2)) < 4,
12271232
tagInline: Math.abs((tagLabel.getBoundingClientRect().top + tagLabel.getBoundingClientRect().height / 2) - (tagInput.getBoundingClientRect().top + tagInput.getBoundingClientRect().height / 2)) < 4
12281233
};
12291234
});
1230-
expect(objectPanelLayout).toEqual({ actionsAtBottom: true, nameInline: true, tagInline: true });
1235+
expect(objectPanelLayout).toEqual({ actionButtonLabels: ["Add", "Rename", "Dup", "Delete"], actionButtonMaxHeight: 28, actionsAtBottom: true, actionsSingleLine: true, nameInline: true, tagInline: true });
12311236
await page.locator("#objectVectorStudioV2ObjectNameInput").fill("Blocked Object");
12321237
await page.locator("#objectVectorStudioV2AddObjectButton").click();
12331238
await expect(page.locator("#statusLog")).toHaveValue(/FAIL Add object blocked: load a schema-valid Object Vector Studio V2 payload before adding objects\./);
@@ -1517,6 +1522,8 @@ test.describe("Workspace Manager V2 bootstrap", () => {
15171522
await expect(page.locator("#objectVectorStudioV2ObjectPreviewFooter")).toContainText("Object ID: object.asteroids.object-1");
15181523
await expect(page.locator("#objectVectorStudioV2RenderSurface")).toHaveAttribute("viewBox", "-1600 -1100 3200 2200");
15191524
await expect(page.locator("#objectVectorStudioV2RenderSurface [data-center-origin='0,0']")).toHaveCount(1);
1525+
await expect(page.locator("#objectVectorStudioV2RenderSurface [data-center-origin='0,0']")).toHaveAttribute("r", "9");
1526+
await expect(page.locator("#objectVectorStudioV2ViewportControls button")).toHaveText(["Out", "In", "Up", "Down", "Left", "Right", "View", "Dot"]);
15201527
await expect(page.locator("#objectVectorStudioV2CenterDotButton")).toHaveAttribute("aria-pressed", "true");
15211528
await page.locator("#objectVectorStudioV2CenterDotButton").click();
15221529
await expect(page.locator("#objectVectorStudioV2CenterDotButton")).toHaveAttribute("aria-pressed", "false");
@@ -1548,7 +1555,7 @@ test.describe("Workspace Manager V2 bootstrap", () => {
15481555
await expect(page.locator("#objectVectorStudioV2ObjectDetails")).not.toContainText("Transform");
15491556
await expect(page.locator("#objectVectorStudioV2ObjectDetails #objectVectorStudioV2MoveShapeButton")).toHaveCount(0);
15501557
await expect(page.locator("#objectVectorStudioV2ObjectTransform")).toContainText("Selected Shape: rectangle-1");
1551-
await expect(page.locator("#objectVectorStudioV2ObjectTransform")).toContainText("Transform");
1558+
await expect(page.locator("#objectVectorStudioV2ObjectTransform .object-vector-studio-v2__transform-summary")).toHaveText("Transform x 0, y 0, rot 0, scale 1 x 1");
15521559
await expect(page.locator("#objectVectorStudioV2ObjectTransform #objectVectorStudioV2MoveShapeButton")).toHaveCount(1);
15531560
const objectDetailsOrder = await page.locator("#objectVectorStudioV2ObjectDetails").evaluate((details) => {
15541561
const applyButton = details.querySelector("#objectVectorStudioV2ApplyGeometryButton");
@@ -2254,18 +2261,32 @@ test.describe("Workspace Manager V2 bootstrap", () => {
22542261
});
22552262
await expect(page.locator("#statusLog")).toHaveValue(/OK Render mode svg-work-surface: rendered Asteroids Ship Grid Check with 1 visible shapes; capture mode none\./);
22562263
await expect(page.locator("#objectVectorStudioV2ObjectDetails")).toContainText("Polygon Geometry");
2257-
await expect.poll(() => page.locator("#objectVectorStudioV2ObjectDetails [data-shape-geometry-field='points']").evaluateAll((inputs) => inputs.map((input) => input.value))).toEqual(["0,-18", "14,16", "0,8", "-14,16"]);
2258-
await page.locator("#objectVectorStudioV2ObjectDetails [data-polygon-point-index='1']").fill("14,17");
2264+
await expect.poll(() => page.locator("#objectVectorStudioV2ObjectDetails .object-vector-studio-v2__polygon-point-field").evaluateAll((rows) => rows.map((row) => ({
2265+
label: row.querySelector(".object-vector-studio-v2__polygon-point-label").textContent.trim(),
2266+
x: row.querySelector("[data-polygon-point-axis='x']").value,
2267+
y: row.querySelector("[data-polygon-point-axis='y']").value
2268+
})))).toEqual([
2269+
{ label: "Point 1", x: "0", y: "-18" },
2270+
{ label: "Point 2", x: "14", y: "16" },
2271+
{ label: "Point 3", x: "0", y: "8" },
2272+
{ label: "Point 4", x: "-14", y: "16" }
2273+
]);
2274+
const polygonPointListLayout = await page.locator("#objectVectorStudioV2ObjectDetails .object-vector-studio-v2__polygon-point-list").evaluate((list) => ({
2275+
maxHeight: Number.parseFloat(getComputedStyle(list).maxHeight),
2276+
overflowY: getComputedStyle(list).overflowY
2277+
}));
2278+
expect(polygonPointListLayout).toEqual({ maxHeight: 138, overflowY: "auto" });
2279+
await page.locator("#objectVectorStudioV2ObjectDetails [data-polygon-point-index='1'][data-polygon-point-axis='y']").fill("17");
22592280
await page.locator("#objectVectorStudioV2ApplyGeometryButton").click();
22602281
await expect(page.locator("#objectVectorStudioV2RenderSurface [data-shape-id='asteroids-ship-outline']")).toHaveAttribute("points", "0,-180 140,170 0,80 -140,160");
22612282
await expect(page.locator("#statusLog")).toHaveValue(/OK Applied geometry edits to shape asteroids-ship-outline\./);
2262-
await page.locator("#objectVectorStudioV2ObjectDetails [data-polygon-point-index='1']").fill("bad");
2283+
await page.locator("#objectVectorStudioV2ObjectDetails [data-polygon-point-index='1'][data-polygon-point-axis='y']").fill("bad");
22632284
await page.locator("#objectVectorStudioV2ApplyGeometryButton").click();
2264-
await expect(page.locator("#objectVectorStudioV2ObjectDetails [data-polygon-point-index='1']")).toHaveAttribute("aria-invalid", "true");
2265-
await expect(page.locator("#objectVectorStudioV2ObjectDetails [data-polygon-point-index='1']")).toHaveValue("bad");
2285+
await expect(page.locator("#objectVectorStudioV2ObjectDetails [data-polygon-point-index='1'][data-polygon-point-axis='y']")).toHaveAttribute("aria-invalid", "true");
2286+
await expect(page.locator("#objectVectorStudioV2ObjectDetails [data-polygon-point-index='1'][data-polygon-point-axis='y']")).toHaveValue("bad");
22662287
await expect(page.locator("#objectVectorStudioV2RenderSurface [data-shape-id='asteroids-ship-outline']")).toHaveAttribute("points", "0,-180 140,170 0,80 -140,160");
2267-
await expect(page.locator("#statusLog")).toHaveValue(/FAIL Invalid geometry rejected for shape asteroids-ship-outline: Point 2 must be an x,y pair with finite numbers\./);
2268-
await page.locator("#objectVectorStudioV2ObjectDetails [data-polygon-point-index='1']").fill("14,16");
2288+
await expect(page.locator("#statusLog")).toHaveValue(/FAIL Invalid geometry rejected for shape asteroids-ship-outline: Point 2 Y must be a finite number\./);
2289+
await page.locator("#objectVectorStudioV2ObjectDetails [data-polygon-point-index='1'][data-polygon-point-axis='y']").fill("16");
22692290
await page.locator("#objectVectorStudioV2ApplyGeometryButton").click();
22702291
await expect(page.locator("#objectVectorStudioV2RenderSurface [data-shape-id='asteroids-ship-outline']")).toHaveAttribute("points", "0,-180 140,160 0,80 -140,160");
22712292
await expect(page.locator("#objectVectorStudioV2RenderSurface")).toHaveAttribute("viewBox", "-1600 -1100 3200 2200");
@@ -2356,6 +2377,12 @@ test.describe("Workspace Manager V2 bootstrap", () => {
23562377
await page.locator("#objectVectorStudioV2PanLeftButton").click();
23572378
await expect(page.locator("#objectVectorStudioV2RenderSurface")).toHaveAttribute("viewBox", "-640 -440 1280 880");
23582379
await expect(page.locator("#objectVectorStudioV2CoordinateDisplay")).toHaveText("Origin: 0, 0 | Canvas 0,0 centered | Zoom 250%");
2380+
await page.locator("#objectVectorStudioV2PanUpButton").click();
2381+
await expect(page.locator("#objectVectorStudioV2RenderSurface")).toHaveAttribute("viewBox", "-640 -460 1280 880");
2382+
await expect(page.locator("#objectVectorStudioV2CoordinateDisplay")).toHaveText("Origin: 0, -2 | Canvas 0,0 centered | Zoom 250%");
2383+
await page.locator("#objectVectorStudioV2PanDownButton").click();
2384+
await expect(page.locator("#objectVectorStudioV2RenderSurface")).toHaveAttribute("viewBox", "-640 -440 1280 880");
2385+
await expect(page.locator("#objectVectorStudioV2CoordinateDisplay")).toHaveText("Origin: 0, 0 | Canvas 0,0 centered | Zoom 250%");
23592386
const examplePointerScreen = await page.locator("#objectVectorStudioV2RenderSurface").evaluate((surface) => {
23602387
const point = surface.createSVGPoint();
23612388
point.x = -140;
@@ -2477,6 +2504,13 @@ test.describe("Workspace Manager V2 bootstrap", () => {
24772504
expect(rectangleAfterDrag.transform.y).not.toBe(rectangleBeforeDrag.transform.y);
24782505
await expect(page.locator("#statusLog")).toHaveValue(/OK Dragged shape rectangle-1 by/);
24792506

2507+
const rectangleBeforeNegativeDrag = await shapeSnapshot("rectangle-1");
2508+
await dragLocator("#objectVectorStudioV2RenderSurface [data-shape-id='rectangle-1']", -36, -24);
2509+
const rectangleAfterNegativeDrag = await shapeSnapshot("rectangle-1");
2510+
expect(rectangleAfterNegativeDrag.transform.x).toBeLessThan(rectangleBeforeNegativeDrag.transform.x);
2511+
expect(rectangleAfterNegativeDrag.transform.y).toBeLessThan(rectangleBeforeNegativeDrag.transform.y);
2512+
await expect(page.locator("#statusLog")).toHaveValue(/OK Dragged shape rectangle-1 by/);
2513+
24802514
const rectangleBeforeResize = await shapeSnapshot("rectangle-1");
24812515
await dragLocator("#objectVectorStudioV2RenderSurface [data-resize-handle='se']", 28, 20);
24822516
const rectangleAfterResize = await shapeSnapshot("rectangle-1");
@@ -2563,6 +2597,28 @@ test.describe("Workspace Manager V2 bootstrap", () => {
25632597
await expect(page.locator("#objectVectorStudioV2RenderSurface [data-object-bounds='object.authoring.ufo-template']")).toHaveCount(1);
25642598
await expect(page.locator('[data-object-id="object.authoring.ufo-template"] [data-object-tile-shape-id]')).toHaveCount(2);
25652599
await expect(page.locator("#statusLog")).toHaveValue(/OK Created ellipse shape ellipse-2 on UFO Template\./);
2600+
const ellipseGeometryLayout = await page.locator("#objectVectorStudioV2ObjectDetails .object-vector-studio-v2__edit-grid--ellipse").evaluate((grid) => {
2601+
const fields = Array.from(grid.querySelectorAll(".object-vector-studio-v2__edit-field"));
2602+
const fieldRects = fields.map((field) => field.getBoundingClientRect());
2603+
const inlineFields = fields.map((field) => {
2604+
const label = field.querySelector("span").getBoundingClientRect();
2605+
const input = field.querySelector("input").getBoundingClientRect();
2606+
return Math.abs((label.top + label.height / 2) - (input.top + input.height / 2)) <= 2 && label.right <= input.left;
2607+
});
2608+
return {
2609+
labels: fields.map((field) => field.querySelector("span").textContent.trim()),
2610+
inlineFields,
2611+
rowLabels: [
2612+
fields.filter((field, index) => Math.abs(fieldRects[index].top - fieldRects[0].top) <= 2).map((field) => field.querySelector("span").textContent.trim()),
2613+
fields.filter((field, index) => Math.abs(fieldRects[index].top - fieldRects[2].top) <= 2).map((field) => field.querySelector("span").textContent.trim())
2614+
]
2615+
};
2616+
});
2617+
expect(ellipseGeometryLayout).toEqual({
2618+
inlineFields: [true, true, true, true],
2619+
labels: ["Cx", "Cy", "Rx", "Ry"],
2620+
rowLabels: [["Cx", "Cy"], ["Rx", "Ry"]]
2621+
});
25662622

25672623
await page.locator("#objectVectorStudioV2TagFilter").selectOption("enemy");
25682624
await expect(page.locator("#objectVectorStudioV2ObjectTiles .object-vector-studio-v2__object-tile")).toHaveCount(1);
@@ -2587,6 +2643,12 @@ test.describe("Workspace Manager V2 bootstrap", () => {
25872643
await page.locator("#objectVectorStudioV2MoveYInput").fill("7");
25882644
await page.locator("#objectVectorStudioV2MoveShapeButton").click();
25892645
await expect(page.locator("#statusLog")).toHaveValue(/OK Moved shape ellipse-1 by 10, 10\./);
2646+
await expect(page.locator("#objectVectorStudioV2ObjectTransform .object-vector-studio-v2__transform-summary")).toHaveText("Transform x 10, y 10, rot 0, scale 1 x 1");
2647+
await page.locator("#objectVectorStudioV2MoveXInput").fill("-5");
2648+
await page.locator("#objectVectorStudioV2MoveYInput").fill("-5");
2649+
await page.locator("#objectVectorStudioV2MoveShapeButton").click();
2650+
await expect(page.locator("#statusLog")).toHaveValue(/OK Moved shape ellipse-1 by -10, -10\./);
2651+
await expect(page.locator("#objectVectorStudioV2ObjectTransform .object-vector-studio-v2__transform-summary")).toHaveText("Transform x 0, y 0, rot 0, scale 1 x 1");
25902652

25912653
await page.locator("#objectVectorStudioV2CopyJsonButton").click();
25922654
const copiedPayload = await page.evaluate(() => JSON.parse(sessionStorage.getItem("object-vector-studio-v2.authoring-copied-json")));

0 commit comments

Comments
 (0)