@@ -1157,7 +1157,7 @@ test.describe("Workspace Manager V2 bootstrap", () => {
11571157 await expect ( page . locator ( "#inspectorOutput" ) ) . toContainText ( `"toolId": "${ tool . id } "` ) ;
11581158 await expect ( page . locator ( "#statusLog" ) ) . toHaveValue ( new RegExp ( `Processed source value: ${ tool . name } launch coverage` ) ) ;
11591159 } else {
1160- await expect ( page . locator ( '[data-launch-mode-nav="tool"] button' ) ) . toHaveText ( [ "Import" , "Copy JSON" , "Export" ] ) ;
1160+ await expect ( page . locator ( '[data-launch-mode-nav="tool"] button' ) ) . toHaveText ( [ "Import" , "Copy JSON" , "Export" , "Export SVG" ] ) ;
11611161 await expect ( page . locator ( "#objectVectorStudioV2LoadStatus" ) ) . toContainText ( "Schema-only loading is idle" ) ;
11621162 await expect ( page . locator ( "#objectVectorStudioV2ObjectTiles" ) ) . toContainText ( "No objects loaded" ) ;
11631163 }
@@ -1184,10 +1184,11 @@ test.describe("Workspace Manager V2 bootstrap", () => {
11841184 await expect ( page . locator ( "[data-tool-starter-header]" ) ) . toContainText ( "Object Vector Studio V2" ) ;
11851185 await expect ( page . locator ( "#statusLog" ) ) . toHaveValue ( / O K O b j e c t V e c t o r S t u d i o V 2 s c h e m a c o n t r a c t l o a d e d f r o m \/ t o o l s \/ s c h e m a s \/ t o o l s \/ o b j e c t - v e c t o r - s t u d i o - v 2 \. s c h e m a \. j s o n \. / ) ;
11861186 await expect ( page . locator ( '[data-launch-mode-nav="tool"]' ) ) . toBeVisible ( ) ;
1187- await expect ( page . locator ( '[data-launch-mode-nav="tool"] button' ) ) . toHaveText ( [ "Import" , "Copy JSON" , "Export" ] ) ;
1187+ await expect ( page . locator ( '[data-launch-mode-nav="tool"] button' ) ) . toHaveText ( [ "Import" , "Copy JSON" , "Export" , "Export SVG" ] ) ;
11881188 await expect ( page . locator ( '[data-launch-mode-nav="workspace"]' ) ) . toBeHidden ( ) ;
11891189 await expect ( page . locator ( "#objectVectorStudioV2CopyJsonButton" ) ) . toBeDisabled ( ) ;
11901190 await expect ( page . locator ( "#objectVectorStudioV2ExportJsonButton" ) ) . toBeDisabled ( ) ;
1191+ await expect ( page . locator ( "#objectVectorStudioV2ExportSvgButton" ) ) . toBeDisabled ( ) ;
11911192
11921193 await expect ( page . locator ( ".tool-starter__panel--left > .accordion-v2 > .accordion-v2__header > span:first-child" ) ) . toHaveText ( [ "Object" , "Shape/Tools" , "Objects" ] ) ;
11931194 await expect ( page . locator ( ".tool-starter__panel--right > .accordion-v2 > .accordion-v2__header > span:first-child" ) ) . toHaveText ( [ "Palette" , "Object Details" , "JSON Details" , "Status Log" ] ) ;
@@ -1342,6 +1343,7 @@ test.describe("Workspace Manager V2 bootstrap", () => {
13421343 await expect ( page . locator ( "#objectVectorStudioV2JsonDetails" ) ) . not . toContainText ( '"palette"' ) ;
13431344 await expect ( page . locator ( "#objectVectorStudioV2CopyJsonButton" ) ) . toBeEnabled ( ) ;
13441345 await expect ( page . locator ( "#objectVectorStudioV2ExportJsonButton" ) ) . toBeEnabled ( ) ;
1346+ await expect ( page . locator ( "#objectVectorStudioV2ExportSvgButton" ) ) . toBeEnabled ( ) ;
13451347 await expect ( page . locator ( "#statusLog" ) ) . toHaveValue ( / O K L o a d e d O b j e c t V e c t o r S t u d i o V 2 s c h e m a p a y l o a d f r o m i m p o r t : o b j e c t - v e c t o r - v a l i d \. j s o n : 1 8 o b j e c t s \. / ) ;
13461348 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 w i t h 0 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 \. / ) ;
13471349
@@ -1482,7 +1484,7 @@ test.describe("Workspace Manager V2 bootstrap", () => {
14821484 const exportedSchemaValidation = await page . evaluate ( ( payload ) => window . __objectVectorStudioV2App . schemaService . validatePayload ( payload ) , exportedPayload ) ;
14831485 expect ( exportedSchemaValidation ) . toEqual ( { errors : [ ] , ok : true , payload : exportedPayload } ) ;
14841486
1485- await page . locator ( '[data-object-id="object-2"]' ) . click ( ) ;
1487+ await page . locator ( '[data-object-id="object-2"]' ) . evaluate ( ( button ) => button . click ( ) ) ;
14861488 await expect ( page . locator ( '[data-object-id="object-2"]' ) ) . toHaveAttribute ( "aria-pressed" , "true" ) ;
14871489 await expect ( page . locator ( "#objectVectorStudioV2ObjectDetails" ) ) . toContainText ( "Object 2" ) ;
14881490 await expect ( page . locator ( "#objectVectorStudioV2ObjectDetails" ) ) . toContainText ( "Enemy entity metadata framework" ) ;
@@ -1513,7 +1515,7 @@ test.describe("Workspace Manager V2 bootstrap", () => {
15131515
15141516 await page . locator ( "#objectVectorStudioV2FlattenObjectButton" ) . click ( ) ;
15151517 await expect ( page . locator ( "#objectVectorStudioV2JsonDetails" ) ) . not . toContainText ( '"flattened": true' ) ;
1516- await expect ( page . locator ( "#statusLog" ) ) . toHaveValue ( / W A R N F l a t t e n o b j e c t s k i p p e d : S h i e l d P i c k u p h a s n o d u r a b l e f l a t t e n f i e l d i n t h e t r i m m e d O b j e c t V e c t o r S t u d i o V 2 a s s e t s c h e m a \. / ) ;
1518+ await expect ( page . locator ( "#statusLog" ) ) . toHaveValue ( / W A R N F l a t t e n o b j e c t s k i p p e d : S h i e l d P i c k u p h a s n o s h a p e s t o f l a t t e n \. / ) ;
15171519
15181520 await page . locator ( "#objectVectorStudioV2DeleteObjectButton" ) . click ( ) ;
15191521 await expect ( page . locator ( "#objectVectorStudioV2ObjectCount" ) ) . toHaveValue ( "18 objects" ) ;
@@ -1602,6 +1604,119 @@ test.describe("Workspace Manager V2 bootstrap", () => {
16021604 }
16031605 } ) ;
16041606
1607+ test ( "expands Object Vector Studio V2 asset authoring controls" , async ( { page } , testInfo ) => {
1608+ const server = await startRepoServer ( ) ;
1609+ const pageErrors = [ ] ;
1610+
1611+ page . on ( "pageerror" , ( error ) => {
1612+ pageErrors . push ( error . message ) ;
1613+ } ) ;
1614+
1615+ await coverageReporter . start ( page ) ;
1616+ try {
1617+ await page . goto ( `${ server . baseUrl } /tools/object-vector-studio-v2/index.html` , { waitUntil : "networkidle" } ) ;
1618+ await page . evaluate ( ( ) => {
1619+ sessionStorage . setItem ( "object-vector-studio-v2.runtimePalette" , JSON . stringify ( {
1620+ id : "authoring-palette" ,
1621+ swatches : [
1622+ { id : "cyan" , value : "#6fd3ff" } ,
1623+ { id : "amber" , value : "#fbbf24" }
1624+ ]
1625+ } ) ) ;
1626+ Object . defineProperty ( navigator , "clipboard" , {
1627+ configurable : true ,
1628+ value : {
1629+ async writeText ( text ) {
1630+ sessionStorage . setItem ( "object-vector-studio-v2.authoring-copied-json" , text ) ;
1631+ }
1632+ }
1633+ } ) ;
1634+ } ) ;
1635+
1636+ const payloadPath = testInfo . outputPath ( "object-vector-authoring.json" ) ;
1637+ await writeFile ( payloadPath , JSON . stringify ( {
1638+ name : "Authoring Payload" ,
1639+ objects : [ ] ,
1640+ toolId : "object-vector-studio-v2" ,
1641+ version : 1
1642+ } , null , 2 ) , "utf8" ) ;
1643+ await page . locator ( "#objectVectorStudioV2ImportJsonInput" ) . setInputFiles ( payloadPath ) ;
1644+
1645+ await page . locator ( "#objectVectorStudioV2TemplateSelect" ) . selectOption ( "ufo" ) ;
1646+ await page . locator ( "#objectVectorStudioV2CreateTemplateButton" ) . click ( ) ;
1647+ await expect ( page . locator ( "#objectVectorStudioV2ObjectCount" ) ) . toHaveValue ( "1 object" ) ;
1648+ await expect ( page . locator ( '[data-object-id="ufo-template"]' ) ) . toHaveAttribute ( "aria-pressed" , "true" ) ;
1649+ await expect ( page . locator ( '[data-object-thumbnail="ufo-template"] .object-vector-studio-v2__object-thumbnail-shape' ) ) . toHaveCount ( 2 ) ;
1650+ await expect ( page . locator ( "#objectVectorStudioV2RenderSurface [data-object-bounds='ufo-template']" ) ) . toHaveCount ( 1 ) ;
1651+ await expect ( page . locator ( "#objectVectorStudioV2SelectionMetrics" ) ) . toContainText ( "bounds" ) ;
1652+ await expect ( page . locator ( "#statusLog" ) ) . toHaveValue ( / O K C r e a t e d U F O t e m p l a t e o b j e c t U F O T e m p l a t e w i t h 2 s h a p e s \. / ) ;
1653+
1654+ await page . locator ( "#objectVectorStudioV2CategoryFilter" ) . selectOption ( "enemy" ) ;
1655+ await expect ( page . locator ( "#objectVectorStudioV2ObjectTiles .object-vector-studio-v2__object-tile" ) ) . toHaveCount ( 1 ) ;
1656+ await page . locator ( "#objectVectorStudioV2SearchFilter" ) . fill ( "ufo" ) ;
1657+ await expect ( page . locator ( "#objectVectorStudioV2ObjectTiles" ) ) . toContainText ( "UFO Template" ) ;
1658+
1659+ await page . locator ( "#objectVectorStudioV2GridRenderButton" ) . click ( ) ;
1660+ await expect ( page . locator ( "#objectVectorStudioV2GridRenderButton" ) ) . toHaveAttribute ( "aria-pressed" , "true" ) ;
1661+ await expect ( page . locator ( "#objectVectorStudioV2RenderSurface [data-grid-rendered='true'] line" ) ) . toHaveCount ( 28 ) ;
1662+
1663+ await page . locator ( "#objectVectorStudioV2RenderSurface [data-shape-id='ufo-template-dome']" ) . click ( { modifiers : [ "Shift" ] } ) ;
1664+ await page . locator ( "#objectVectorStudioV2GroupShapesButton" ) . click ( ) ;
1665+ await expect ( page . locator ( "#objectVectorStudioV2JsonDetails" ) ) . toContainText ( '"groupId": "group-1"' ) ;
1666+ await expect ( page . locator ( "#statusLog" ) ) . toHaveValue ( / O K G r o u p e d 2 s h a p e s i n t o g r o u p - 1 \. / ) ;
1667+
1668+ await page . locator ( "#objectVectorStudioV2GridSnapButton" ) . click ( ) ;
1669+ await page . locator ( "#objectVectorStudioV2MoveXInput" ) . fill ( "13" ) ;
1670+ await page . locator ( "#objectVectorStudioV2MoveYInput" ) . fill ( "7" ) ;
1671+ await page . locator ( "#objectVectorStudioV2MoveShapeButton" ) . click ( ) ;
1672+ await expect ( page . locator ( "#statusLog" ) ) . toHaveValue ( / O K M o v e d s h a p e u f o - t e m p l a t e - d o m e b y 1 0 , 1 0 \. / ) ;
1673+ await page . locator ( "#objectVectorStudioV2FlattenObjectButton" ) . click ( ) ;
1674+ await expect ( page . locator ( "#statusLog" ) ) . toHaveValue ( / O K F l a t t e n e d o b j e c t U F O T e m p l a t e : b a k e d t r a n s f o r m s i n t o 2 s h a p e s \. / ) ;
1675+
1676+ await page . locator ( "#objectVectorStudioV2CopyJsonButton" ) . click ( ) ;
1677+ const copiedPayload = await page . evaluate ( ( ) => JSON . parse ( sessionStorage . getItem ( "object-vector-studio-v2.authoring-copied-json" ) ) ) ;
1678+ expect ( copiedPayload . palette ) . toBeUndefined ( ) ;
1679+ expect ( copiedPayload . objects [ 0 ] . shapes [ 0 ] ) . toHaveProperty ( "groupId" , "group-1" ) ;
1680+ const copiedSchemaValidation = await page . evaluate ( ( payload ) => window . __objectVectorStudioV2App . schemaService . validatePayload ( payload ) , copiedPayload ) ;
1681+ expect ( copiedSchemaValidation ) . toEqual ( { errors : [ ] , ok : true , payload : copiedPayload } ) ;
1682+
1683+ const jsonDownloadPromise = page . waitForEvent ( "download" ) ;
1684+ await page . locator ( "#objectVectorStudioV2ExportJsonButton" ) . click ( ) ;
1685+ const jsonDownload = await jsonDownloadPromise ;
1686+ const jsonExportPath = testInfo . outputPath ( "object-vector-authoring-export.json" ) ;
1687+ await jsonDownload . saveAs ( jsonExportPath ) ;
1688+ const exportedPayload = JSON . parse ( await readFile ( jsonExportPath , "utf8" ) ) ;
1689+ expect ( exportedPayload . objects [ 0 ] . name ) . toBe ( "UFO Template" ) ;
1690+ expect ( exportedPayload . palette ) . toBeUndefined ( ) ;
1691+
1692+ const svgDownloadPromise = page . waitForEvent ( "download" ) ;
1693+ await page . locator ( "#objectVectorStudioV2ExportSvgButton" ) . click ( ) ;
1694+ const svgDownload = await svgDownloadPromise ;
1695+ const svgExportPath = testInfo . outputPath ( "object-vector-ufo.svg" ) ;
1696+ await svgDownload . saveAs ( svgExportPath ) ;
1697+ const exportedSvg = await readFile ( svgExportPath , "utf8" ) ;
1698+ expect ( exportedSvg ) . toContain ( "<svg" ) ;
1699+ expect ( exportedSvg ) . toContain ( "ellipse" ) ;
1700+ await expect ( page . locator ( "#statusLog" ) ) . toHaveValue ( / O K E x p o r t S V G g e n e r a t e d f o r U F O T e m p l a t e : 2 v i s i b l e s h a p e s \. / ) ;
1701+
1702+ const invalidPayloadPath = testInfo . outputPath ( "object-vector-authoring-invalid.json" ) ;
1703+ await writeFile ( invalidPayloadPath , JSON . stringify ( {
1704+ name : "Invalid Authoring Payload" ,
1705+ objects : [ ] ,
1706+ toolId : "object-vector-studio-v2" ,
1707+ version : 1 ,
1708+ unexpected : "blocked"
1709+ } , null , 2 ) , "utf8" ) ;
1710+ await page . locator ( "#objectVectorStudioV2ImportJsonInput" ) . setInputFiles ( invalidPayloadPath ) ;
1711+ await expect ( page . locator ( "#statusLog" ) ) . toHaveValue ( / F A I L O b j e c t V e c t o r S t u d i o V 2 s c h e m a v a l i d a t i o n f a i l e d f r o m i m p o r t : o b j e c t - v e c t o r - a u t h o r i n g - i n v a l i d \. j s o n : r o o t \. u n e x p e c t e d i s n o t a l l o w e d \. / ) ;
1712+
1713+ expect ( pageErrors ) . toEqual ( [ ] ) ;
1714+ } finally {
1715+ await coverageReporter . stop ( page ) ;
1716+ await server . close ( ) ;
1717+ }
1718+ } ) ;
1719+
16051720 test ( "resolves asset-manager-v2 audio catalog paths and plays Asteroids sounds" , async ( { page } ) => {
16061721 const server = await startRepoServer ( ) ;
16071722 const pageErrors = [ ] ;
0 commit comments