@@ -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 ( / F A I L I n v a l i d t r a n s f o r m r e j e c t e d f o r s h a p e r e c t a n g l e - 1 : M o v e X m u s t b e a f i n i t e n u m b e r \. / ) ;
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 ( / i s - s e l e c t e d / ) ;
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 ( / O K A p p l i e d g e o m e t r y e d i t s t o s h a p e r e c t a n g l e - 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 ( / O K M o v e d s h a p e r e c t a n g l e - 1 b y 1 0 , 1 0 \. / ) ;
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 ( / O K R e s i z e d s h a p e r e c t a n g l e - 1 b y 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 ( / i s - s e l e c t e d / ) ;
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 ( / O K D r a g g e d s h a p e r e c t a n g l e - 1 b y / ) ;
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 ( / O K R e s i z e d s h a p e r e c t a n g l e - 1 w i t h s e h a n d l e \. / ) ;
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 ( / O K M o v e d l i n e e n d f o r s h a p e l i n e - 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 ( / O K D e l e t e d s h a p e l i n e - 2 f r o m o b j e c t t i l e s h a p e d e l e t e \. / ) ;
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