@@ -1308,7 +1308,7 @@ test.describe("Workspace Manager V2 bootstrap", () => {
13081308 await page.locator("#objectVectorStudioV2ObjectNameInput").fill("Blocked Object");
13091309 await page.locator("#objectVectorStudioV2AddObjectButton").click();
13101310 await expect(page.locator("#statusLog")).toHaveValue(/FAIL Add object blocked: load a schema-valid Object Vector Studio V2 payload before adding objects\./);
1311- await expect(page.locator(".object-vector-studio-v2__tool-toggle")).toHaveText(["Select", "Triangle", "Rectangle", "Circle", "Ellipse", "Line", "Polygon", "Arc", "Text"]);
1311+ await expect(page.locator(".object-vector-studio-v2__tool-toggle")).toHaveText(["Select", "Triangle", "Rectangle", "Square", " Circle", "Ellipse", "Line", "Polygon", "Arc", "Text"]);
13121312 await expect(page.locator(".object-vector-studio-v2__shape-icon--triangle")).toBeVisible();
13131313 await expect(page.locator(".object-vector-studio-v2__shape-icon--arc")).toBeVisible();
13141314 const iconStyleState = await page.evaluate(async () => {
@@ -1378,6 +1378,7 @@ test.describe("Workspace Manager V2 bootstrap", () => {
13781378 polygon: icon(".object-vector-studio-v2__shape-icon--polygon"),
13791379 rectangle: icon(".object-vector-studio-v2__shape-icon--rectangle"),
13801380 select: icon(".object-vector-studio-v2__shape-icon--select"),
1381+ square: icon(".object-vector-studio-v2__shape-icon--square"),
13811382 text: icon(".object-vector-studio-v2__shape-icon--text"),
13821383 triangle: icon(".object-vector-studio-v2__shape-icon--triangle")
13831384 },
@@ -1444,23 +1445,34 @@ test.describe("Workspace Manager V2 bootstrap", () => {
14441445 polygon: "polygon",
14451446 rectangle: "rectangle",
14461447 select: "select",
1448+ square: "square",
14471449 text: "text",
14481450 triangle: "triangle"
14491451 });
1452+ expect(iconStyleState.shapeIcons.arc.iconName).toBe("nf-md-vector_radius");
1453+ expect(iconStyleState.shapeIcons.circle.iconName).toBe("nf-md-vector_circle_variant");
1454+ expect(iconStyleState.shapeIcons.ellipse.iconName).toBe("nf-md-vector_ellipse");
14501455 expect(iconStyleState.shapeIcons.polygon.iconName).toBe("nf-md-vector_polygon");
1456+ expect(iconStyleState.shapeIcons.square.iconName).toBe("nf-fa-vector_square");
14511457 expect(iconStyleState.shapeIcons.triangle.iconName).toBe("nf-md-vector_triangle");
14521458 expect(iconStyleState.shapeIcons.select.iconName).toBe("nf-md-select");
14531459 expect(iconStyleState.shapeIcons.line.iconName).toBe("nf-md-vector_line");
14541460 expect(iconStyleState.shapeIcons.rectangle.iconName).toBe("nf-md-vector_rectangle");
1455- expect(iconStyleState.shapeIcons.circle.iconName).toBe("nf-fa-circle_o");
1456- expect(iconStyleState.shapeIcons.ellipse.iconName).toBe("nf-fa-circle_o");
14571461 expect(Math.round(iconStyleState.shapeIcons.select.fontSize)).toBe(Math.round(iconStyleState.shapeIcons.circle.fontSize * 1.125));
14581462 expect(Math.round(iconStyleState.shapeIcons.triangle.fontSize)).toBe(Math.round(iconStyleState.shapeIcons.circle.fontSize * 1.25));
14591463 expect(Math.round(iconStyleState.shapeIcons.rectangle.fontSize)).toBe(Math.round(iconStyleState.shapeIcons.circle.fontSize * 1.25));
1460- expect(iconStyleState.shapeIcons.select.transform).not.toBe("none");
1461- expect(iconStyleState.shapeIcons.triangle.transform).not.toBe("none");
1462- expect(iconStyleState.shapeIcons.line.transform).not.toBe("none");
1463- expect(iconStyleState.shapeIcons.rectangle.transform).not.toBe("none");
1464+ expect(Object.fromEntries(Object.entries(iconStyleState.shapeIcons).map(([key, value]) => [key, value.transform]))).toEqual({
1465+ arc: "none",
1466+ circle: "none",
1467+ ellipse: "none",
1468+ line: "none",
1469+ polygon: "none",
1470+ rectangle: "none",
1471+ select: "none",
1472+ square: "none",
1473+ text: "none",
1474+ triangle: "none"
1475+ });
14641476 expect(Object.fromEntries(Object.entries(iconStyleState.viewportIcons).map(([key, value]) => [key, value.iconKey]))).toEqual({
14651477 down: "panDown",
14661478 reset: "reset",
@@ -1488,19 +1500,29 @@ test.describe("Workspace Manager V2 bootstrap", () => {
14881500 const shapeToolsContent = document.querySelector("#objectVectorStudioV2ShapeToolsContent").getBoundingClientRect();
14891501 const shapeToolsAccordion = document.querySelector("#objectVectorStudioV2ShapeToolsContent").closest(".accordion-v2").getBoundingClientRect();
14901502 const leftPanel = document.querySelector(".tool-starter__panel--left");
1503+ const toolButtons = Array.from(document.querySelectorAll(".object-vector-studio-v2__tool-toggle"));
1504+ const iconTopOffsets = toolButtons.map((toolButton) => {
1505+ const buttonRect = toolButton.getBoundingClientRect();
1506+ const iconRect = toolButton.querySelector(".object-vector-studio-v2__shape-icon").getBoundingClientRect();
1507+ return Math.round(iconRect.top - buttonRect.top);
1508+ });
14911509 return {
1510+ buttonCount: toolButtons.length,
14921511 labelBesideGrid: gridButton.getBoundingClientRect().right <= labelButton.getBoundingClientRect().left,
14931512 leftPanelOverflowY: getComputedStyle(leftPanel).overflowY,
14941513 textButtonWider: Math.round(rect.width) > Math.round(rect.height),
1514+ visibleIconTopOffsetRange: Math.max(...iconTopOffsets) - Math.min(...iconTopOffsets),
14951515 shapeToolsReachesBottom: Math.abs(shapeToolsContent.bottom - shapeToolsAccordion.bottom) <= 1,
14961516 zOrderAbsentBeforeObjectSelection: !document.querySelector(".object-vector-studio-v2__z-order-actions"),
14971517 zOrderAbsentFromShapeTools: !document.querySelector("#objectVectorStudioV2ShapeToolsContent .object-vector-studio-v2__z-order-actions")
14981518 };
14991519 });
15001520 expect(shapeToolLayout).toEqual({
1521+ buttonCount: 10,
15011522 labelBesideGrid: true,
15021523 leftPanelOverflowY: "auto",
15031524 textButtonWider: true,
1525+ visibleIconTopOffsetRange: 0,
15041526 shapeToolsReachesBottom: true,
15051527 zOrderAbsentBeforeObjectSelection: true,
15061528 zOrderAbsentFromShapeTools: true
@@ -1511,6 +1533,24 @@ test.describe("Workspace Manager V2 bootstrap", () => {
15111533 await expect(page.locator(".object-vector-studio-v2__z-order-actions")).toHaveCount(0);
15121534 await expect(page.locator("#objectVectorStudioV2ResetViewButton")).not.toHaveClass(/is-icon-only/);
15131535 await expect(page.locator("#statusLog")).toHaveValue(/OK Shape\/Tools display mode set to compact icons\./);
1536+ const iconOnlyToolLayout = await page.locator("#objectVectorStudioV2ToolToggleGrid").evaluate((grid) => {
1537+ const toolButtons = Array.from(grid.querySelectorAll(".object-vector-studio-v2__tool-toggle"));
1538+ const measurements = toolButtons.map((toolButton) => {
1539+ const buttonRect = toolButton.getBoundingClientRect();
1540+ const iconRect = toolButton.querySelector(".object-vector-studio-v2__shape-icon").getBoundingClientRect();
1541+ return {
1542+ centerDeltaX: Math.abs((iconRect.left + iconRect.width / 2) - (buttonRect.left + buttonRect.width / 2)),
1543+ centerDeltaY: Math.abs((iconRect.top + iconRect.height / 2) - (buttonRect.top + buttonRect.height / 2)),
1544+ height: Math.round(buttonRect.height),
1545+ width: Math.round(buttonRect.width)
1546+ };
1547+ });
1548+ return {
1549+ iconsCentered: measurements.every((entry) => entry.centerDeltaX < 1 && entry.centerDeltaY < 1),
1550+ squareButtons: measurements.every((entry) => entry.width === entry.height)
1551+ };
1552+ });
1553+ expect(iconOnlyToolLayout).toEqual({ iconsCentered: true, squareButtons: true });
15141554 await page.reload({ waitUntil: "networkidle" });
15151555 await expect(page.locator("#objectVectorStudioV2ToolToggleGrid")).toHaveClass(/is-icon-only/);
15161556 await expect(page.locator(".object-vector-studio-v2__z-order-actions")).toHaveCount(0);
@@ -3020,6 +3060,104 @@ test.describe("Workspace Manager V2 bootstrap", () => {
30203060 }
30213061 });
30223062
3063+ test("creates Object Vector Studio V2 square shapes with one size control", async ({ page }) => {
3064+ const server = await startRepoServer();
3065+ const pageErrors = [];
3066+ const consoleErrors = [];
3067+
3068+ page.on("pageerror", (error) => {
3069+ pageErrors.push(error.message);
3070+ });
3071+ page.on("console", (message) => {
3072+ if (message.type() === "error") {
3073+ consoleErrors.push(message.text());
3074+ }
3075+ });
3076+
3077+ await coverageReporter.start(page);
3078+ try {
3079+ await page.goto(`${server.baseUrl}/tools/object-vector-studio-v2/index.html`, { waitUntil: "networkidle" });
3080+ await page.evaluate(() => {
3081+ sessionStorage.setItem("object-vector-studio-v2.runtimePalette", JSON.stringify({
3082+ id: "square-tool-palette",
3083+ swatches: [
3084+ { id: "white", value: "#ffffff" }
3085+ ]
3086+ }));
3087+ });
3088+ await page.locator("#objectVectorStudioV2ImportJsonInput").setInputFiles({
3089+ buffer: Buffer.from(JSON.stringify({
3090+ name: "Square Tool Check",
3091+ objects: [
3092+ {
3093+ id: "object.square.tool-check",
3094+ name: "Square Tool Check",
3095+ shapes: [],
3096+ tags: []
3097+ }
3098+ ],
3099+ toolId: "object-vector-studio-v2",
3100+ version: 1
3101+ }, null, 2)),
3102+ mimeType: "application/json",
3103+ name: "square-tool-check.object-vector.json"
3104+ });
3105+
3106+ await page.locator('[data-shape-tool="square"]').click();
3107+ await expect(page.locator("#statusLog")).toHaveValue(/OK Created square shape on Square Tool Check\./);
3108+ await expect(page.locator("#objectVectorStudioV2ObjectDetails")).toContainText("Square Geometry");
3109+ await expect(page.locator(".object-vector-studio-v2__shape-select-label")).toHaveText("0. Square");
3110+ const createdSquare = await page.evaluate(() => {
3111+ const app = window.__objectVectorStudioV2App;
3112+ const fields = Array.from(document.querySelectorAll("#objectVectorStudioV2ObjectDetails [data-shape-geometry-field]")).map((input) => ({
3113+ key: input.dataset.shapeGeometryField,
3114+ label: input.closest("label").querySelector("span").textContent.trim(),
3115+ value: input.value
3116+ }));
3117+ const shape = app.selectedShape();
3118+ return {
3119+ fields,
3120+ geometry: shape.geometry,
3121+ schemaOk: app.schemaService.validatePayload(app.currentPayload).ok,
3122+ tool: shape.tool
3123+ };
3124+ });
3125+ expect(createdSquare).toEqual({
3126+ fields: [
3127+ { key: "x", label: "x", value: "-80" },
3128+ { key: "y", label: "y", value: "-30" },
3129+ { key: "size", label: "Size", value: "60" }
3130+ ],
3131+ geometry: { height: 60, width: 60, x: -80, y: -30 },
3132+ schemaOk: true,
3133+ tool: "square"
3134+ });
3135+
3136+ await page.locator("#objectVectorStudioV2ObjectDetails [data-shape-geometry-field='size']").fill("42");
3137+ await page.locator("#objectVectorStudioV2ApplyGeometryButton").click();
3138+ await expect(page.locator("#statusLog")).toHaveValue(/OK Applied geometry edits to shape row 0\./);
3139+ await expect(page.locator("#objectVectorStudioV2RenderSurface [data-shape-index='0']")).toHaveAttribute("width", "420");
3140+ await expect(page.locator("#objectVectorStudioV2RenderSurface [data-shape-index='0']")).toHaveAttribute("height", "420");
3141+ const resizedSquare = await page.evaluate(() => {
3142+ const app = window.__objectVectorStudioV2App;
3143+ return {
3144+ geometry: app.selectedShape().geometry,
3145+ schemaOk: app.schemaService.validatePayload(app.currentPayload).ok
3146+ };
3147+ });
3148+ expect(resizedSquare).toEqual({
3149+ geometry: { height: 42, width: 42, x: -80, y: -30 },
3150+ schemaOk: true
3151+ });
3152+
3153+ expect(pageErrors).toEqual([]);
3154+ expect(consoleErrors).toEqual([]);
3155+ } finally {
3156+ await coverageReporter.stop(page);
3157+ await server.close();
3158+ }
3159+ });
3160+
30233161 test("maps Object Vector Studio V2 preview coordinates directly to visible grid lines", async ({ page }) => {
30243162 const server = await startRepoServer();
30253163 const pageErrors = [];
0 commit comments