@@ -1578,6 +1578,9 @@ test.describe("Workspace Manager V2 bootstrap", () => {
15781578 expect ( shapeHierarchyDensity . rowHeight ) . toBeLessThanOrEqual ( 28 ) ;
15791579 const gridObjectScale = await page . locator ( "#objectVectorStudioV2RenderSurface" ) . evaluate ( ( surface ) => {
15801580 const rectangle = surface . querySelector ( "[data-shape-id='rectangle-1']" ) ;
1581+ const workArea = document . querySelector ( ".object-vector-studio-v2__work-area" ) ;
1582+ const workAreaStyle = getComputedStyle ( workArea ) ;
1583+ const availableWidth = workArea . clientWidth - Number . parseFloat ( workAreaStyle . paddingLeft ) - Number . parseFloat ( workAreaStyle . paddingRight ) ;
15811584 const verticalLines = Array . from ( surface . querySelectorAll ( "[data-grid-rendered='true'] line" ) )
15821585 . filter ( ( line ) => line . getAttribute ( "x1" ) === line . getAttribute ( "x2" ) )
15831586 . map ( ( line ) => Number ( line . getAttribute ( "x1" ) ) ) ;
@@ -1588,21 +1591,23 @@ test.describe("Workspace Manager V2 bootstrap", () => {
15881591 const surfaceRect = surface . getBoundingClientRect ( ) ;
15891592 return {
15901593 aspectRatioMatchesViewBox : Math . abs ( ( surfaceRect . width / surfaceRect . height ) - ( viewBox [ 2 ] / viewBox [ 3 ] ) ) < 0.02 ,
1594+ canvasFillsAvailableWidth : Math . abs ( surfaceRect . width - availableWidth ) <= 2 ,
15911595 rectangle : {
15921596 height : Number ( rectangle . getAttribute ( "height" ) ) ,
15931597 width : Number ( rectangle . getAttribute ( "width" ) ) ,
15941598 x : Number ( rectangle . getAttribute ( "x" ) ) ,
15951599 y : Number ( rectangle . getAttribute ( "y" ) )
15961600 } ,
15971601 rectangleSpansGridLines : verticalLines . includes ( - 80 ) && verticalLines . includes ( 0 ) && horizontalLines . includes ( - 30 ) && horizontalLines . includes ( 30 ) ,
1598- stepMatchesSnap : verticalLines . includes ( - 80 ) && verticalLines . includes ( - 70 )
1602+ unitGridSpacing : verticalLines . includes ( - 80 ) && verticalLines . includes ( - 79 ) && horizontalLines . includes ( - 30 ) && horizontalLines . includes ( - 29 )
15991603 } ;
16001604 } ) ;
16011605 expect ( gridObjectScale ) . toEqual ( {
16021606 aspectRatioMatchesViewBox : true ,
1607+ canvasFillsAvailableWidth : true ,
16031608 rectangle : { height : 60 , width : 80 , x : - 80 , y : - 30 } ,
16041609 rectangleSpansGridLines : true ,
1605- stepMatchesSnap : true
1610+ unitGridSpacing : true
16061611 } ) ;
16071612 await expect ( page . locator ( ".object-vector-studio-v2__object-tile[data-object-id='object.asteroids.object-1'] [data-object-control='visibility']" ) ) . toHaveText ( "" ) ;
16081613 await expect ( page . locator ( ".object-vector-studio-v2__object-tile[data-object-id='object.asteroids.object-1'] [data-object-control='lock']" ) ) . toHaveText ( "" ) ;
@@ -2043,6 +2048,143 @@ test.describe("Workspace Manager V2 bootstrap", () => {
20432048 }
20442049 } ) ;
20452050
2051+ test ( "maps Object Vector Studio V2 preview coordinates directly to visible grid lines" , async ( { page } ) => {
2052+ const server = await startRepoServer ( ) ;
2053+ const pageErrors = [ ] ;
2054+ const consoleErrors = [ ] ;
2055+
2056+ page . on ( "pageerror" , ( error ) => {
2057+ pageErrors . push ( error . message ) ;
2058+ } ) ;
2059+ page . on ( "console" , ( message ) => {
2060+ if ( message . type ( ) === "error" ) {
2061+ consoleErrors . push ( message . text ( ) ) ;
2062+ }
2063+ } ) ;
2064+
2065+ await coverageReporter . start ( page ) ;
2066+ try {
2067+ await page . goto ( `${ server . baseUrl } /tools/object-vector-studio-v2/index.html` , { waitUntil : "networkidle" } ) ;
2068+ await page . evaluate ( ( ) => {
2069+ sessionStorage . setItem ( "object-vector-studio-v2.runtimePalette" , JSON . stringify ( {
2070+ id : "asteroids-ship-grid" ,
2071+ swatches : [
2072+ { id : "white" , value : "#ffffff" } ,
2073+ { id : "cyan" , value : "#6fd3ff" }
2074+ ]
2075+ } ) ) ;
2076+ } ) ;
2077+ await page . locator ( "#objectVectorStudioV2ImportJsonInput" ) . setInputFiles ( {
2078+ buffer : Buffer . from ( JSON . stringify ( {
2079+ name : "Asteroids Ship Grid Check" ,
2080+ objects : [
2081+ {
2082+ id : "object.asteroids.ship-grid" ,
2083+ name : "Asteroids Ship Grid Check" ,
2084+ shapes : [
2085+ {
2086+ geometry : {
2087+ points : [
2088+ { x : 0 , y : - 18 } ,
2089+ { x : 14 , y : 16 } ,
2090+ { x : 0 , y : 8 } ,
2091+ { x : - 14 , y : 16 }
2092+ ]
2093+ } ,
2094+ id : "asteroids-ship-outline" ,
2095+ locked : false ,
2096+ order : 1 ,
2097+ style : { fill : "#ffffff" , stroke : "#6fd3ff" , strokeWidth : 1 } ,
2098+ transform : { originX : 0 , originY : 0 , rotation : 0 , scaleX : 1 , scaleY : 1 , x : 0 , y : 0 } ,
2099+ type : "polygon" ,
2100+ visible : true
2101+ }
2102+ ]
2103+ }
2104+ ] ,
2105+ toolId : "object-vector-studio-v2" ,
2106+ version : 1
2107+ } , null , 2 ) ) ,
2108+ mimeType : "application/json" ,
2109+ name : "asteroids-ship-grid.object-vector.json"
2110+ } ) ;
2111+ await expect ( page . locator ( "#statusLog" ) ) . toHaveValue ( / O K R e n d e r m o d e s v g - w o r k - s u r f a c e : r e n d e r e d A s t e r o i d s S h i p G r i d C h e c k w i t h 1 v i s i b l e s h a p e s ; c a p t u r e m o d e n o n e \. / ) ;
2112+
2113+ const readPreviewScale = async ( ) => page . locator ( "#objectVectorStudioV2RenderSurface" ) . evaluate ( ( surface ) => {
2114+ const workArea = document . querySelector ( ".object-vector-studio-v2__work-area" ) ;
2115+ const workAreaStyle = getComputedStyle ( workArea ) ;
2116+ const availableWidth = workArea . clientWidth - Number . parseFloat ( workAreaStyle . paddingLeft ) - Number . parseFloat ( workAreaStyle . paddingRight ) ;
2117+ const surfaceRect = surface . getBoundingClientRect ( ) ;
2118+ const viewBox = surface . getAttribute ( "viewBox" ) . split ( / \s + / ) . map ( Number ) ;
2119+ const horizontalLines = Array . from ( surface . querySelectorAll ( "[data-grid-rendered='true'] line" ) )
2120+ . filter ( ( line ) => line . getAttribute ( "y1" ) === line . getAttribute ( "y2" ) )
2121+ . map ( ( line ) => Number ( line . getAttribute ( "y1" ) ) ) ;
2122+ const verticalLines = Array . from ( surface . querySelectorAll ( "[data-grid-rendered='true'] line" ) )
2123+ . filter ( ( line ) => line . getAttribute ( "x1" ) === line . getAttribute ( "x2" ) )
2124+ . map ( ( line ) => Number ( line . getAttribute ( "x1" ) ) ) ;
2125+ const screenPoint = ( x , y ) => {
2126+ const point = surface . createSVGPoint ( ) ;
2127+ point . x = x ;
2128+ point . y = y ;
2129+ const transformed = point . matrixTransform ( surface . getScreenCTM ( ) ) ;
2130+ return { x : transformed . x , y : transformed . y } ;
2131+ } ;
2132+ const shipPoints = [
2133+ { x : 0 , y : - 18 } ,
2134+ { x : 14 , y : 16 } ,
2135+ { x : 0 , y : 8 } ,
2136+ { x : - 14 , y : 16 }
2137+ ] ;
2138+ const pointScreens = shipPoints . map ( ( point ) => screenPoint ( point . x , point . y ) ) ;
2139+ const lineScreens = {
2140+ bottom : screenPoint ( 0 , 16 ) . y ,
2141+ origin : screenPoint ( 0 , 0 ) . y ,
2142+ top : screenPoint ( 0 , - 18 ) . y
2143+ } ;
2144+ return {
2145+ aspectRatioStable : Math . abs ( ( surfaceRect . width / surfaceRect . height ) - ( viewBox [ 2 ] / viewBox [ 3 ] ) ) < 0.02 ,
2146+ canvasFillsAvailableWidth : Math . abs ( surfaceRect . width - availableWidth ) <= 2 ,
2147+ gridLinesAboveOrigin : horizontalLines . filter ( ( y ) => y < 0 && y >= - 18 ) . length ,
2148+ gridLinesBelowOrigin : horizontalLines . filter ( ( y ) => y > 0 && y <= 16 ) . length ,
2149+ originCentered : Math . abs ( lineScreens . origin - ( surfaceRect . top + surfaceRect . height / 2 ) ) <= 1 ,
2150+ pointsOnVisibleGridLines : shipPoints . every ( ( point ) => horizontalLines . includes ( point . y ) && verticalLines . includes ( point . x ) ) ,
2151+ pointScreensMatchGrid : Math . abs ( pointScreens [ 0 ] . y - lineScreens . top ) <= 0.01
2152+ && Math . abs ( pointScreens [ 1 ] . y - lineScreens . bottom ) <= 0.01
2153+ && Math . abs ( pointScreens [ 3 ] . y - lineScreens . bottom ) <= 0.01 ,
2154+ unitGridSpacing : horizontalLines . includes ( - 18 ) && horizontalLines . includes ( - 17 ) && horizontalLines . includes ( 15 ) && horizontalLines . includes ( 16 ) ,
2155+ viewBox : surface . getAttribute ( "viewBox" )
2156+ } ;
2157+ } ) ;
2158+
2159+ const initialPreviewScale = await readPreviewScale ( ) ;
2160+ expect ( initialPreviewScale ) . toEqual ( {
2161+ aspectRatioStable : true ,
2162+ canvasFillsAvailableWidth : true ,
2163+ gridLinesAboveOrigin : 18 ,
2164+ gridLinesBelowOrigin : 16 ,
2165+ originCentered : true ,
2166+ pointsOnVisibleGridLines : true ,
2167+ pointScreensMatchGrid : true ,
2168+ unitGridSpacing : true ,
2169+ viewBox : "-160 -110 320 220"
2170+ } ) ;
2171+
2172+ await page . setViewportSize ( { width : 1040 , height : 720 } ) ;
2173+ const resizedPreviewScale = await readPreviewScale ( ) ;
2174+ expect ( resizedPreviewScale . aspectRatioStable ) . toBe ( true ) ;
2175+ expect ( resizedPreviewScale . canvasFillsAvailableWidth ) . toBe ( true ) ;
2176+ expect ( resizedPreviewScale . gridLinesAboveOrigin ) . toBe ( 18 ) ;
2177+ expect ( resizedPreviewScale . gridLinesBelowOrigin ) . toBe ( 16 ) ;
2178+ expect ( resizedPreviewScale . pointsOnVisibleGridLines ) . toBe ( true ) ;
2179+
2180+ expect ( pageErrors ) . toEqual ( [ ] ) ;
2181+ expect ( consoleErrors ) . toEqual ( [ ] ) ;
2182+ } finally {
2183+ await coverageReporter . stop ( page ) ;
2184+ await server . close ( ) ;
2185+ }
2186+ } ) ;
2187+
20462188 test ( "expands Object Vector Studio V2 asset authoring controls" , async ( { page } , testInfo ) => {
20472189 const server = await startRepoServer ( ) ;
20482190 const pageErrors = [ ] ;
0 commit comments