@@ -1826,16 +1826,31 @@ test.describe("Workspace Manager V2 bootstrap", () => {
18261826 const transformIconState = await page.locator("#objectVectorStudioV2ObjectTransform").evaluate((panel) => ({
18271827 resize: {
18281828 iconKey: panel.querySelector("#objectVectorStudioV2ResizeShapeButton").dataset.ovsIconKey,
1829- iconName: panel.querySelector("#objectVectorStudioV2ResizeShapeButton").dataset.ovsIconName
1829+ iconName: panel.querySelector("#objectVectorStudioV2ResizeShapeButton").dataset.ovsIconName,
1830+ title: panel.querySelector("#objectVectorStudioV2ResizeShapeButton").title
18301831 },
1831- scale: {
1832- iconKey: panel.querySelector("#objectVectorStudioV2ScaleShapeButton").dataset.ovsIconKey,
1833- iconName: panel.querySelector("#objectVectorStudioV2ScaleShapeButton").dataset.ovsIconName
1834- }
1832+ scaleActionRemoved: panel.querySelector("#objectVectorStudioV2ScaleShapeButton") === null
18351833 }));
18361834 expect(transformIconState).toEqual({
1837- resize: { iconKey: "resize", iconName: "nf-md-resize" },
1838- scale: { iconKey: "scale", iconName: "nf-fa-scale_unbalanced" }
1835+ resize: { iconKey: "resize", iconName: "nf-md-resize", title: "Resize Geometry" },
1836+ scaleActionRemoved: true
1837+ });
1838+ const scaleControlLayout = await page.locator("#objectVectorStudioV2ObjectTransform .object-vector-studio-v2__scale-control-row").evaluate((row) => {
1839+ const children = Array.from(row.children);
1840+ const centers = children.map((child) => {
1841+ const rect = child.getBoundingClientRect();
1842+ return Math.round(rect.top + rect.height / 2);
1843+ });
1844+ return {
1845+ allOneLine: centers.every((center) => Math.abs(center - centers[0]) <= 2),
1846+ order: children.map((child) => child.tagName === "INPUT" ? child.value : child.textContent.trim()),
1847+ resizeTitle: row.querySelector("#objectVectorStudioV2ResizeShapeButton").title
1848+ };
1849+ });
1850+ expect(scaleControlLayout).toEqual({
1851+ allOneLine: true,
1852+ order: ["Scale", "--", "-", "1", "+", "++", "Resize"],
1853+ resizeTitle: "Resize Geometry"
18391854 });
18401855 const objectDetailsOrder = await page.locator("#objectVectorStudioV2ObjectDetails").evaluate((details) => {
18411856 const applyButton = details.querySelector("#objectVectorStudioV2ApplyGeometryButton");
@@ -1873,10 +1888,8 @@ test.describe("Workspace Manager V2 bootstrap", () => {
18731888 { inline: true, label: "Move X" },
18741889 { inline: true, label: "Move Y" },
18751890 { inline: true, label: "Rotate" },
1876- { inline: true, label: "Scale" },
18771891 { inline: true, label: "Origin X" },
1878- { inline: true, label: "Origin Y" },
1879- { inline: true, label: "Resize" }
1892+ { inline: true, label: "Origin Y" }
18801893 ]);
18811894 const transformBeforeInvalid = await page.evaluate(() => window.__objectVectorStudioV2App.selectedShape().transform);
18821895 await page.locator("#objectVectorStudioV2MoveXInput").fill("");
@@ -2223,19 +2236,67 @@ test.describe("Workspace Manager V2 bootstrap", () => {
22232236 await expect(page.locator("#statusLog")).toHaveValue(/OK Rotated shape row 0 by 15 degrees\./);
22242237
22252238 await page.locator("#objectVectorStudioV2ScaleInput").fill("0");
2226- await page.locator("#objectVectorStudioV2ScaleShapeButton").click( );
2239+ await expect( page.locator("#objectVectorStudioV2ScaleInput")).toHaveAttribute("aria-invalid", "true" );
22272240 await expect(page.locator("#statusLog")).toHaveValue(/FAIL Invalid transform rejected for shape row 0: scale must be greater than 0\./);
22282241 await expect(page.locator("#objectVectorStudioV2JsonDetails")).not.toContainText('"scaleX": 0');
22292242 await page.locator("#objectVectorStudioV2ScaleInput").fill("1.2");
2230- await page.locator("#objectVectorStudioV2ScaleShapeButton").click();
2243+ await expect(page.locator("#objectVectorStudioV2ScaleInput")).not.toHaveAttribute("aria-invalid", "true");
2244+ await expect(page.locator("#statusLog")).toHaveValue(/OK Scale preview set to 1\.2 for shape row 0\./);
22312245 await expect(page.locator("#objectVectorStudioV2JsonDetails")).toContainText('"scaleX": 1.2');
2232- await expect(page.locator("#statusLog")).toHaveValue(/OK Scaled shape row 0 by 1\.2\./);
2233-
2234- await page.locator("#objectVectorStudioV2ResizeInput").fill("5");
2246+ await expect(page.locator("#objectVectorStudioV2ObjectTransform .object-vector-studio-v2__transform-summary")).toHaveText("Transform x 10, y 10, rot 15, scale 1.2 x 1.2");
2247+ const selectionBeforeScaleStep = await page.locator("#objectVectorStudioV2RenderSurface [data-selection-bounds='0']").evaluate((box) => ({
2248+ height: Number(box.getAttribute("height")),
2249+ width: Number(box.getAttribute("width"))
2250+ }));
2251+ await page.locator("#objectVectorStudioV2ScaleDownSmallButton").click();
2252+ await expect(page.locator("#objectVectorStudioV2ScaleInput")).toHaveValue("1.19");
2253+ await page.locator("#objectVectorStudioV2ScaleDownLargeButton").click();
2254+ await expect(page.locator("#objectVectorStudioV2ScaleInput")).toHaveValue("1.09");
2255+ await page.locator("#objectVectorStudioV2ScaleUpSmallButton").click();
2256+ await expect(page.locator("#objectVectorStudioV2ScaleInput")).toHaveValue("1.1");
2257+ await page.locator("#objectVectorStudioV2ScaleUpLargeButton").click();
2258+ await expect(page.locator("#objectVectorStudioV2ScaleInput")).toHaveValue("1.2");
2259+ const selectionAfterScaleStep = await page.locator("#objectVectorStudioV2RenderSurface [data-selection-bounds='0']").evaluate((box) => ({
2260+ height: Number(box.getAttribute("height")),
2261+ width: Number(box.getAttribute("width"))
2262+ }));
2263+ expect(selectionAfterScaleStep.width).toBeCloseTo(selectionBeforeScaleStep.width, 1);
2264+ expect(selectionAfterScaleStep.height).toBeCloseTo(selectionBeforeScaleStep.height, 1);
2265+ const rectangleBeforeResizeGeometry = await page.evaluate(() => {
2266+ const shape = window.__objectVectorStudioV2App.selectedShape();
2267+ return {
2268+ geometry: { ...shape.geometry },
2269+ transform: { ...shape.transform, origin: { ...shape.transform.origin } }
2270+ };
2271+ });
22352272 await page.locator("#objectVectorStudioV2ResizeShapeButton").click();
2236- await expect(page.locator("#objectVectorStudioV2JsonDetails")).toContainText('"width": 85');
2237- await expect(page.locator("#objectVectorStudioV2ResizeInput")).toHaveValue("5");
2238- await expect(page.locator("#statusLog")).toHaveValue(/OK Resized shape row 0 by 5\./);
2273+ const rectangleAfterResizeGeometry = await page.evaluate(() => {
2274+ const shape = window.__objectVectorStudioV2App.selectedShape();
2275+ return {
2276+ geometry: { ...shape.geometry },
2277+ schemaOk: window.__objectVectorStudioV2App.schemaService.validatePayload(window.__objectVectorStudioV2App.currentPayload).ok,
2278+ transform: { ...shape.transform, origin: { ...shape.transform.origin } }
2279+ };
2280+ });
2281+ expect(rectangleAfterResizeGeometry).toMatchObject({
2282+ geometry: {
2283+ height: Number((rectangleBeforeResizeGeometry.geometry.height * 1.2).toFixed(3)),
2284+ width: Number((rectangleBeforeResizeGeometry.geometry.width * 1.2).toFixed(3)),
2285+ x: Number((rectangleBeforeResizeGeometry.transform.origin.x + (rectangleBeforeResizeGeometry.geometry.x - rectangleBeforeResizeGeometry.transform.origin.x) * 1.2).toFixed(3)),
2286+ y: Number((rectangleBeforeResizeGeometry.transform.origin.y + (rectangleBeforeResizeGeometry.geometry.y - rectangleBeforeResizeGeometry.transform.origin.y) * 1.2).toFixed(3))
2287+ },
2288+ schemaOk: true,
2289+ transform: {
2290+ rotation: rectangleBeforeResizeGeometry.transform.rotation,
2291+ scaleX: 1,
2292+ scaleY: 1,
2293+ x: rectangleBeforeResizeGeometry.transform.x,
2294+ y: rectangleBeforeResizeGeometry.transform.y
2295+ }
2296+ });
2297+ await expect(page.locator("#objectVectorStudioV2ScaleInput")).toHaveValue("1");
2298+ await expect(page.locator("#objectVectorStudioV2ObjectTransform .object-vector-studio-v2__transform-summary")).toHaveText("Transform x 10, y 10, rot 15, scale 1 x 1");
2299+ await expect(page.locator("#statusLog")).toHaveValue(/OK Resize Geometry applied scale 1\.2 to shape row 0; transform scale reset to 1\./);
22392300
22402301 await page.locator("#objectVectorStudioV2BringForwardButton").click();
22412302 await expect(page.locator("#objectVectorStudioV2JsonDetails")).toContainText('"order": 2');
@@ -3391,6 +3452,99 @@ test.describe("Workspace Manager V2 bootstrap", () => {
33913452 }
33923453 });
33933454
3455+ test("applies Object Vector Studio V2 Resize Geometry across supported shape tools", async ({ page }) => {
3456+ const server = await startRepoServer();
3457+ const pageErrors = [];
3458+ const consoleErrors = [];
3459+ const scaledTransform = { origin: { x: 0, y: 0 }, rotation: 0, scaleX: 1.5, scaleY: 1.5, x: 0, y: 0 };
3460+ const style = { fill: "#ffffff", fillOpacity: 1, stroke: "#60a5fa", strokeOpacity: 1, strokeWidth: 1 };
3461+
3462+ page.on("pageerror", (error) => {
3463+ pageErrors.push(error.message);
3464+ });
3465+ page.on("console", (message) => {
3466+ if (message.type() === "error") {
3467+ consoleErrors.push(message.text());
3468+ }
3469+ });
3470+
3471+ await coverageReporter.start(page);
3472+ try {
3473+ await page.setViewportSize({ width: 1366, height: 1000 });
3474+ await page.goto(`${server.baseUrl}/tools/object-vector-studio-v2/index.html`, { waitUntil: "networkidle" });
3475+ await page.evaluate(() => {
3476+ sessionStorage.setItem("object-vector-studio-v2.runtimePalette", JSON.stringify({
3477+ id: "resize-geometry-palette",
3478+ swatches: [
3479+ { id: "white", value: "#ffffff" },
3480+ { id: "blue", value: "#60a5fa" }
3481+ ]
3482+ }));
3483+ });
3484+ await page.locator("#objectVectorStudioV2ImportJsonInput").setInputFiles({
3485+ buffer: Buffer.from(JSON.stringify({
3486+ name: "Resize Geometry Object Set",
3487+ objects: [
3488+ {
3489+ id: "object.resize.geometry-probe",
3490+ name: "Resize Geometry Probe",
3491+ shapes: [
3492+ { geometry: { points: [{ x: 0, y: -10 }, { x: 10, y: 0 }, { x: 0, y: 10 }, { x: -10, y: 0 }] }, locked: false, order: 1, style, tool: "polygon", transform: scaledTransform, visible: true },
3493+ { geometry: { points: [{ x: 0, y: -8 }, { x: 8, y: 8 }, { x: -8, y: 8 }] }, locked: false, order: 2, style, tool: "triangle", transform: scaledTransform, visible: true },
3494+ { geometry: { point1: { x: -10, y: -5 }, point2: { x: 10, y: 5 } }, locked: false, order: 3, style: { ...style, fill: "none" }, tool: "line", transform: scaledTransform, visible: true },
3495+ { geometry: { height: 10, width: 20, x: -10, y: -5 }, locked: false, order: 4, style, tool: "rectangle", transform: scaledTransform, visible: true },
3496+ { geometry: { cx: 5, cy: -5, r: 10 }, locked: false, order: 5, style, tool: "circle", transform: scaledTransform, visible: true },
3497+ { geometry: { cx: 4, cy: -6, rx: 8, ry: 12 }, locked: false, order: 6, style, tool: "ellipse", transform: scaledTransform, visible: true },
3498+ { geometry: { cx: 2, cy: -4, r: 9, startAngle: -90, endAngle: 90 }, locked: false, order: 7, style: { ...style, fill: "none" }, tool: "arc", transform: scaledTransform, visible: true },
3499+ { geometry: { fontSize: 12, text: "Hi", x: 6, y: -6 }, locked: false, order: 8, style, tool: "text", transform: scaledTransform, visible: true }
3500+ ],
3501+ tags: []
3502+ }
3503+ ],
3504+ toolId: "object-vector-studio-v2",
3505+ version: 1
3506+ }, null, 2)),
3507+ mimeType: "application/json",
3508+ name: "object-vector-resize-geometry.json"
3509+ });
3510+
3511+ for (let shapeIndex = 0; shapeIndex < 8; shapeIndex += 1) {
3512+ await page.locator(`[data-object-tile-shape-index='${shapeIndex}']`).click();
3513+ await expect(page.locator("#objectVectorStudioV2ScaleInput")).toHaveValue("1.5");
3514+ await page.locator("#objectVectorStudioV2ResizeShapeButton").click();
3515+ await expect(page.locator("#statusLog")).toHaveValue(new RegExp(`OK Resize Geometry applied scale 1\\.5 to shape row ${shapeIndex}; transform scale reset to 1\\.`));
3516+ }
3517+
3518+ const resizeResult = await page.evaluate(() => {
3519+ const payload = window.__objectVectorStudioV2App.currentPayload;
3520+ const shapes = payload.objects[0].shapes;
3521+ return {
3522+ geometries: shapes.map((shape) => shape.geometry),
3523+ schemaOk: window.__objectVectorStudioV2App.schemaService.validatePayload(payload).ok,
3524+ scales: shapes.map((shape) => ({ scaleX: shape.transform.scaleX, scaleY: shape.transform.scaleY }))
3525+ };
3526+ });
3527+ expect(resizeResult.schemaOk).toBe(true);
3528+ expect(resizeResult.scales.every((scale) => scale.scaleX === 1 && scale.scaleY === 1)).toBe(true);
3529+ expect(resizeResult.geometries).toEqual([
3530+ { points: [{ x: 0, y: -15 }, { x: 15, y: 0 }, { x: 0, y: 15 }, { x: -15, y: 0 }] },
3531+ { points: [{ x: 0, y: -12 }, { x: 12, y: 12 }, { x: -12, y: 12 }] },
3532+ { point1: { x: -15, y: -7.5 }, point2: { x: 15, y: 7.5 } },
3533+ { height: 15, width: 30, x: -15, y: -7.5 },
3534+ { cx: 7.5, cy: -7.5, r: 15 },
3535+ { cx: 6, cy: -9, rx: 12, ry: 18 },
3536+ { cx: 3, cy: -6, r: 13.5, startAngle: -90, endAngle: 90 },
3537+ { fontSize: 18, text: "Hi", x: 9, y: -9 }
3538+ ]);
3539+
3540+ expect(pageErrors).toEqual([]);
3541+ expect(consoleErrors).toEqual([]);
3542+ } finally {
3543+ await coverageReporter.stop(page);
3544+ await server.close();
3545+ }
3546+ });
3547+
33943548 test("expands Object Vector Studio V2 asset authoring controls", async ({ page }, testInfo) => {
33953549 const server = await startRepoServer();
33963550 const pageErrors = [];
0 commit comments