@@ -1423,7 +1423,7 @@ test.describe("Workspace Manager V2 bootstrap", () => {
14231423 await page.locator("#objectVectorStudioV2ObjectNameInput").fill("Blocked Object");
14241424 await page.locator("#objectVectorStudioV2AddObjectButton").click();
14251425 await expect(page.locator("#statusLog")).toHaveValue(/FAIL Add object blocked: load a schema-valid Object Vector Studio V2 payload before adding objects\./);
1426- await expect(page.locator(".object-vector-studio-v2__tool-toggle")).toHaveText(["Select", "Arc", "Circle", "Ellipse", "Line", "Polygon", "Polyline", "Rectangle", "Square", "Triangle", "Text"]);
1426+ await expect(page.locator(".object-vector-studio-v2__tool-toggle")).toHaveText(["Select", "Arc", "Circle", "Ellipse", "Line", "Picker", " Polygon", "Polyline", "Rectangle", "Square", "Triangle", "Text"]);
14271427 const futureNotes = await readFile("tools/object-vector-studio-v2/possible.future.adds.txt", "utf8");
14281428 expect(futureNotes).toContain("Object Vector Studio V2 should stay focused on reusable atomic vector objects.");
14291429 expect(futureNotes).toContain("Future World Vector or Scene layers should instance Object Vector objects");
@@ -1511,6 +1511,7 @@ test.describe("Workspace Manager V2 bootstrap", () => {
15111511 circle: icon(".object-vector-studio-v2__shape-icon--circle"),
15121512 ellipse: icon(".object-vector-studio-v2__shape-icon--ellipse"),
15131513 line: icon(".object-vector-studio-v2__shape-icon--line"),
1514+ picker: icon(".object-vector-studio-v2__shape-icon--picker"),
15141515 polygon: icon(".object-vector-studio-v2__shape-icon--polygon"),
15151516 polyline: icon(".object-vector-studio-v2__shape-icon--polyline"),
15161517 rectangle: icon(".object-vector-studio-v2__shape-icon--rectangle"),
@@ -1593,6 +1594,7 @@ test.describe("Workspace Manager V2 bootstrap", () => {
15931594 circle: "circle",
15941595 ellipse: "ellipse",
15951596 line: "line",
1597+ picker: "picker",
15961598 polygon: "polygon",
15971599 polyline: "polyline",
15981600 rectangle: "rectangle",
@@ -1604,6 +1606,7 @@ test.describe("Workspace Manager V2 bootstrap", () => {
16041606 expect(iconStyleState.shapeIcons.arc.iconName).toBe("nf-md-vector_radius");
16051607 expect(iconStyleState.shapeIcons.circle.iconName).toBe("nf-md-vector_circle_variant");
16061608 expect(iconStyleState.shapeIcons.ellipse.iconName).toBe("nf-md-vector_ellipse");
1609+ expect(iconStyleState.shapeIcons.picker.iconName).toBe("nf-fa-eye_dropper");
16071610 expect(iconStyleState.shapeIcons.polygon.iconName).toBe("nf-md-vector_polygon");
16081611 expect(iconStyleState.shapeIcons.polyline.iconName).toBe("nf-md-vector_polyline");
16091612 expect(iconStyleState.shapeIcons.square.iconName).toBe("nf-fa-vector_square");
@@ -1619,6 +1622,7 @@ test.describe("Workspace Manager V2 bootstrap", () => {
16191622 circle: "none",
16201623 ellipse: "none",
16211624 line: "none",
1625+ picker: "none",
16221626 polygon: "none",
16231627 polyline: "none",
16241628 rectangle: "none",
@@ -1679,7 +1683,7 @@ test.describe("Workspace Manager V2 bootstrap", () => {
16791683 };
16801684 });
16811685 expect(shapeToolLayout).toEqual({
1682- buttonCount: 11 ,
1686+ buttonCount: 12 ,
16831687 labelBesideGrid: true,
16841688 leftPanelOverflowY: "auto",
16851689 textButtonWider: true,
@@ -2479,6 +2483,12 @@ test.describe("Workspace Manager V2 bootstrap", () => {
24792483 ...eventInit
24802484 });
24812485 };
2486+ const rightClickPreviewShape = async (shapeIndex) => {
2487+ const locator = page.locator(`#objectVectorStudioV2RenderSurface [data-shape-index='${shapeIndex}']`);
2488+ const box = await locator.boundingBox();
2489+ expect(box).not.toBeNull();
2490+ await page.mouse.click(box.x + box.width / 2, box.y + box.height / 2, { button: "right" });
2491+ };
24822492
24832493 const leftPanelObjectScrollBefore = await forceLeftPanelScroll();
24842494 expect(leftPanelObjectScrollBefore.leftPanelScrollTop).toBeGreaterThan(0);
@@ -2889,6 +2899,93 @@ test.describe("Workspace Manager V2 bootstrap", () => {
28892899 await expect(page.locator("#objectVectorStudioV2JsonDetails")).toContainText('"strokeOpacity": 0.651');
28902900 await expect(page.locator("#objectVectorStudioV2RenderSurface [data-shape-index='1']")).toHaveAttribute("stroke-opacity", "0.651");
28912901 await expect(page.locator("#statusLog")).toHaveValue(/OK Applied palette color #6fd3ff from cyan to shape row 1 by render surface click\. Target: stroke width 2, opacity 0\.651\./);
2902+ const shapeOneStyleAfterStrokeApply = await page.evaluate(() => ({ ...window.__objectVectorStudioV2App.selectedShape().style }));
2903+ expect(shapeOneStyleAfterStrokeApply.fillOpacity).toBe(0.502);
2904+ expect(shapeOneStyleAfterStrokeApply.strokeOpacity).toBe(0.651);
2905+ await expect(page.locator("#objectVectorStudioV2FillOpacity")).toHaveValue("128");
2906+ await expect(page.locator("#objectVectorStudioV2StrokeOpacity")).toHaveValue("166");
2907+
2908+ await page.evaluate(() => {
2909+ window.__objectVectorStudioV2ContextMenuPrevented = false;
2910+ document.querySelector("#objectVectorStudioV2RenderSurface").addEventListener("contextmenu", (event) => {
2911+ window.__objectVectorStudioV2ContextMenuPrevented = event.defaultPrevented;
2912+ }, { once: true });
2913+ });
2914+ await page.locator("#objectVectorStudioV2PaintModeButton").click();
2915+ await rightClickPreviewShape(1);
2916+ await expect(page.locator("#statusLog")).toHaveValue(/OK Applied transparent fill to shape row 1 by right-click\./);
2917+ await expect(page.locator("#objectVectorStudioV2JsonDetails")).toContainText('"fill": "#00000000"');
2918+ const shapeOneStyleAfterTransparentFill = await page.evaluate(() => ({
2919+ contextMenuPrevented: window.__objectVectorStudioV2ContextMenuPrevented,
2920+ style: { ...window.__objectVectorStudioV2App.selectedShape().style }
2921+ }));
2922+ expect(shapeOneStyleAfterTransparentFill).toEqual({
2923+ contextMenuPrevented: true,
2924+ style: {
2925+ ...shapeOneStyleAfterStrokeApply,
2926+ fill: "#00000000"
2927+ }
2928+ });
2929+ await expect(page.locator("#objectVectorStudioV2FillOpacity")).toHaveValue("128");
2930+ await expect(page.locator("#objectVectorStudioV2StrokeOpacity")).toHaveValue("166");
2931+
2932+ await page.evaluate(() => {
2933+ window.__objectVectorStudioV2ContextMenuPrevented = false;
2934+ document.querySelector("#objectVectorStudioV2RenderSurface").addEventListener("contextmenu", (event) => {
2935+ window.__objectVectorStudioV2ContextMenuPrevented = event.defaultPrevented;
2936+ }, { once: true });
2937+ });
2938+ await page.locator("#objectVectorStudioV2StrokeModeButton").click();
2939+ await rightClickPreviewShape(1);
2940+ await expect(page.locator("#statusLog")).toHaveValue(/OK Applied transparent stroke to shape row 1 by right-click\./);
2941+ await expect(page.locator("#objectVectorStudioV2JsonDetails")).toContainText('"stroke": "#00000000"');
2942+ const shapeOneStyleAfterTransparentStroke = await page.evaluate(() => ({
2943+ contextMenuPrevented: window.__objectVectorStudioV2ContextMenuPrevented,
2944+ style: { ...window.__objectVectorStudioV2App.selectedShape().style }
2945+ }));
2946+ expect(shapeOneStyleAfterTransparentStroke).toEqual({
2947+ contextMenuPrevented: true,
2948+ style: {
2949+ ...shapeOneStyleAfterTransparentFill.style,
2950+ stroke: "#00000000"
2951+ }
2952+ });
2953+ await expect(page.locator("#objectVectorStudioV2FillOpacity")).toHaveValue("128");
2954+ await expect(page.locator("#objectVectorStudioV2StrokeOpacity")).toHaveValue("166");
2955+
2956+ const shapeOneStyleBeforePicker = await page.evaluate(() => ({ ...window.__objectVectorStudioV2App.selectedShape().style }));
2957+ await page.keyboard.press("I");
2958+ await expect(page.locator('[data-shape-tool="picker"]')).toHaveAttribute("aria-pressed", "true");
2959+ await clickPreviewShape(1);
2960+ await expect(page.locator("#statusLog")).toHaveValue(/OK Picker sampled shape row 1: fill #00000000, stroke #00000000, fill opacity 0\.502, stroke opacity 0\.651, stroke width 2\./);
2961+ const pickerState = await page.evaluate(() => {
2962+ const app = window.__objectVectorStudioV2App;
2963+ return {
2964+ activeTool: app.activeTool,
2965+ fillInput: app.elements.fillOpacity.value,
2966+ paletteTarget: app.paletteTarget,
2967+ selectedFillColor: app.selectedFillColor,
2968+ selectedFillOpacity: app.selectedFillOpacity,
2969+ selectedStrokeColor: app.selectedStrokeColor,
2970+ selectedStrokeOpacity: app.selectedStrokeOpacity,
2971+ shapeStyle: { ...app.selectedShape().style },
2972+ strokeInput: app.elements.strokeOpacity.value,
2973+ strokeWidth: app.elements.strokeWidth.value
2974+ };
2975+ });
2976+ expect(pickerState).toEqual({
2977+ activeTool: "picker",
2978+ fillInput: "128",
2979+ paletteTarget: "stroke",
2980+ selectedFillColor: "#00000000",
2981+ selectedFillOpacity: 0.502,
2982+ selectedStrokeColor: "#00000000",
2983+ selectedStrokeOpacity: 0.651,
2984+ shapeStyle: shapeOneStyleBeforePicker,
2985+ strokeInput: "166",
2986+ strokeWidth: "2"
2987+ });
2988+
28922989 await page.evaluate(() => {
28932990 window.__objectVectorStudioV2App.selectedStrokeColor = "#123456";
28942991 window.__objectVectorStudioV2App.selectedStrokeLabel = "manual rogue";
@@ -2897,10 +2994,6 @@ test.describe("Workspace Manager V2 bootstrap", () => {
28972994 await clickPreviewShape(1);
28982995 await expect(page.locator("#statusLog")).toHaveValue(/FAIL Palette color application rejected: #123456 is not in the loaded palette\./);
28992996 await expect(page.locator("#objectVectorStudioV2JsonDetails")).not.toContainText("#123456");
2900- await page.locator("[data-palette-color='#ffffff']").click();
2901- await page.keyboard.press("I");
2902- await clickPreviewShape(1);
2903- await expect(page.locator("#statusLog")).toHaveValue(/OK Sampled stroke color #6fd3ff from shape row 1\./);
29042997 await page.keyboard.press("X");
29052998 await expect(page.locator("#statusLog")).toHaveValue(/OK Swapped fill and stroke colors/);
29062999 await page.keyboard.press("D");
@@ -4098,6 +4191,11 @@ test.describe("Workspace Manager V2 bootstrap", () => {
40984191 expect(polygonAfterBoundsDrag.geometry.points[2].y).toBeGreaterThan(polygonBeforeBoundsDrag.geometry.points[2].y);
40994192 await expect(page.locator("#statusLog")).toHaveValue(/OK Resized shape row 2 with se handle\./);
41004193 await page.locator("#objectVectorStudioV2RenderSurface [data-shape-index='3']").click();
4194+ const circleHandleSize = await page.locator("#objectVectorStudioV2RenderSurface [data-resize-handle='se']").evaluate((handle) => ({
4195+ height: Number(handle.getAttribute("height")),
4196+ width: Number(handle.getAttribute("width"))
4197+ }));
4198+ expect(circleHandleSize).toEqual({ height: 5, width: 5 });
41014199 const circleBeforeResize = await shapeSnapshot(3);
41024200 await dragLocator("#objectVectorStudioV2RenderSurface [data-resize-handle='se']", 28, 28);
41034201 const circleAfterResize = await shapeSnapshot(3);
0 commit comments