11import { expect , test } from "@playwright/test" ;
2- import { readFile } from "node:fs/promises" ;
32import { startRepoServer } from "../../helpers/playwrightRepoServer.mjs" ;
43import { workspaceV2CoverageReporter as coverageReporter } from "../../helpers/workspaceV2CoverageReporter.mjs" ;
54
@@ -33,6 +32,135 @@ async function installFakeAssetFilePicker(page, files = []) {
3332 } , files ) ;
3433}
3534
35+ async function installFakeWorkspaceRepoPicker ( page ) {
36+ await page . addInitScript ( ( ) => {
37+ const defaultManifestPaths = [
38+ "/games/Asteroids/game.manifest.json" ,
39+ "/games/GravityWell/game.manifest.json" ,
40+ "/games/Pong/game.manifest.json"
41+ ] ;
42+
43+ function appendManifestWrite ( path , contents ) {
44+ const writes = JSON . parse ( window . sessionStorage . getItem ( "workspace.repo.manifestWrites" ) || "[]" ) ;
45+ writes . push ( { path, contents } ) ;
46+ window . sessionStorage . setItem ( "workspace.repo.manifestWrites" , JSON . stringify ( writes ) ) ;
47+ }
48+
49+ function makeFileHandle ( name , text , path = name ) {
50+ let contents = text ;
51+ let lastModified = Date . now ( ) ;
52+ return {
53+ kind : "file" ,
54+ name,
55+ path,
56+ async createWritable ( ) {
57+ let draft = "" ;
58+ return {
59+ async write ( value ) {
60+ draft += value instanceof Blob
61+ ? await value . text ( )
62+ : String ( value ?? "" ) ;
63+ } ,
64+ async close ( ) {
65+ contents = draft ;
66+ lastModified += 1000 ;
67+ if ( path . endsWith ( "/game.manifest.json" ) ) {
68+ appendManifestWrite ( path , contents ) ;
69+ }
70+ }
71+ } ;
72+ } ,
73+ async getFile ( ) {
74+ const type = path . endsWith ( ".svg" ) ? "image/svg+xml" : "application/json" ;
75+ return new File ( [ contents ] , name , { lastModified, type } ) ;
76+ }
77+ } ;
78+ }
79+
80+ function makeDirectoryHandle ( name , children = { } , path = name , options = { } ) {
81+ return {
82+ kind : "directory" ,
83+ name,
84+ path,
85+ repoPath : options . repoPath || "" ,
86+ async getDirectoryHandle ( childName ) {
87+ const child = children [ childName ] ;
88+ if ( child ?. kind === "directory" ) {
89+ return child ;
90+ }
91+ throw new DOMException ( `${ childName } was not found.` , "NotFoundError" ) ;
92+ } ,
93+ async getFileHandle ( childName ) {
94+ const child = children [ childName ] ;
95+ if ( child ?. kind === "file" ) {
96+ return child ;
97+ }
98+ throw new DOMException ( `${ childName } was not found.` , "NotFoundError" ) ;
99+ } ,
100+ async * entries ( ) {
101+ for ( const entry of Object . entries ( children ) ) {
102+ yield entry ;
103+ }
104+ }
105+ } ;
106+ }
107+
108+ async function fetchManifestText ( path ) {
109+ const response = await fetch ( path , { cache : "no-store" } ) ;
110+ if ( ! response . ok ) {
111+ throw new Error ( `${ path } returned ${ response . status } ` ) ;
112+ }
113+ return await response . text ( ) ;
114+ }
115+
116+ async function makeMockRepoHandle ( config = { } ) {
117+ const repoName = config . repoName || "HTML-JavaScript-Gaming" ;
118+ const games = { } ;
119+ for ( const manifestPath of defaultManifestPaths ) {
120+ const parts = manifestPath . replace ( / ^ \/ + / , "" ) . split ( "/" ) ;
121+ const gameFolder = parts [ 1 ] ;
122+ const gamePath = `${ repoName } /games/${ gameFolder } ` ;
123+ games [ gameFolder ] = makeDirectoryHandle ( gameFolder , {
124+ "game.manifest.json" : makeFileHandle ( "game.manifest.json" , await fetchManifestText ( manifestPath ) , `${ gamePath } /game.manifest.json` )
125+ } , gamePath , config ) ;
126+ }
127+ return makeDirectoryHandle ( repoName , {
128+ games : makeDirectoryHandle ( "games" , games , `${ repoName } /games` , config ) ,
129+ tools : makeDirectoryHandle ( "tools" , { } , `${ repoName } /tools` , config )
130+ } , repoName , config ) ;
131+ }
132+
133+ window . __workspaceManagerV2MockRepoConfig = { } ;
134+ window . __workspaceManagerV2RepoHandleCache = {
135+ async save ( { reference, repoHandle } ) {
136+ const config = window . __workspaceManagerV2MockRepoConfig || { } ;
137+ window . sessionStorage . setItem ( "workspace-manager-v2-mock-repo-handle-cache" , JSON . stringify ( {
138+ repoName : reference ?. displayName || repoHandle ?. name || config . repoName || "HTML-JavaScript-Gaming" ,
139+ repoPath : repoHandle ?. repoPath || config . repoPath || ""
140+ } ) ) ;
141+ } ,
142+ async restore ( { reference } ) {
143+ const rawValue = window . sessionStorage . getItem ( "workspace-manager-v2-mock-repo-handle-cache" ) ;
144+ const cachedConfig = rawValue ? JSON . parse ( rawValue ) : { } ;
145+ return await makeMockRepoHandle ( {
146+ repoName : cachedConfig . repoName || reference ?. displayName || "HTML-JavaScript-Gaming" ,
147+ repoPath : cachedConfig . repoPath || ""
148+ } ) ;
149+ }
150+ } ;
151+ window . showDirectoryPicker = async ( ) => await makeMockRepoHandle ( window . __workspaceManagerV2MockRepoConfig || { } ) ;
152+ } ) ;
153+ }
154+
155+ async function selectFakeWorkspaceRepo ( page , { repoName = "HTML-JavaScript-Gaming" } = { } ) {
156+ await page . evaluate ( ( nextRepoName ) => {
157+ window . __workspaceManagerV2MockRepoConfig = { repoName : nextRepoName } ;
158+ } , repoName ) ;
159+ await page . locator ( "#pickRepoBtn" ) . click ( ) ;
160+ await expect ( page . locator ( "#repoSelectedValue" ) ) . toHaveText ( repoName ) ;
161+ await expect ( page . locator ( "#activeGameSelect" ) ) . toBeEnabled ( ) ;
162+ }
163+
36164async function queueAssetFile ( page , descriptor ) {
37165 await page . evaluate ( ( queuedFile ) => {
38166 window . __assetManagerV2FilePickerQueue . push ( queuedFile ) ;
@@ -49,8 +177,11 @@ async function openAssetManagerV2(page, query = "", { assetFiles = [] } = {}) {
49177 return server ;
50178}
51179
52- async function openWorkspaceManagerV2 ( page , { assetFiles = [ ] , query = "" } = { } ) {
180+ async function openWorkspaceManagerV2 ( page , { assetFiles = [ ] , query = "" , repoPicker = false } = { } ) {
53181 const server = await startRepoServer ( ) ;
182+ if ( repoPicker ) {
183+ await installFakeWorkspaceRepoPicker ( page ) ;
184+ }
54185 if ( assetFiles . length ) {
55186 await installFakeAssetFilePicker ( page , assetFiles ) ;
56187 }
@@ -1253,7 +1384,8 @@ test.describe("Asset Manager V2", () => {
12531384 contents : "png" ,
12541385 path : "HTML-JavaScript-Gaming/assets/images/title-preview.png"
12551386 }
1256- ]
1387+ ] ,
1388+ repoPicker : true
12571389 } ) ;
12581390 const pageErrors = [ ] ;
12591391
@@ -1264,12 +1396,14 @@ test.describe("Asset Manager V2", () => {
12641396 try {
12651397 await expect ( page . locator ( "#workspaceToolTiles [data-workspace-tool-id]" ) ) . toHaveCount ( 7 ) ;
12661398 await expect ( page . locator ( '[data-workspace-tool-id="workspace-manager-v2"]' ) ) . toHaveCount ( 0 ) ;
1399+ await selectFakeWorkspaceRepo ( page ) ;
12671400 await page . locator ( "#activeGameSelect" ) . selectOption ( "Asteroids" ) ;
12681401 await expect ( page . locator ( "#workspaceContextOutput" ) ) . toHaveValue ( / " g a m e R o o t " : " g a m e s \/ A s t e r o i d s \/ " / ) ;
12691402 await expect ( page . locator ( "#workspaceContextOutput" ) ) . toHaveValue ( / " a s s e t s P a t h " : " g a m e s \/ A s t e r o i d s \/ a s s e t s " / ) ;
12701403 await expect ( page . locator ( "#workspaceContextOutput" ) ) . toHaveValue ( / " a s s e t - m a n a g e r - v 2 " / ) ;
1271- await expect ( page . locator ( "#workspaceContextOutput" ) ) . toHaveValue ( / " v e c t o r - m a p - e d i t o r " / ) ;
1272- await expect ( page . locator ( "#workspaceContextOutput" ) ) . toHaveValue ( / " v e c t o r .a s t e r o i d s .s h i p " / ) ;
1404+ await expect ( page . locator ( "#workspaceContextOutput" ) ) . toHaveValue ( / " a s s e t s \. c o l o r \. b a c k g r o u n d \. g a m e " / ) ;
1405+ await expect ( page . locator ( "#workspaceContextOutput" ) ) . toHaveValue ( / " o b j e c t - v e c t o r - s t u d i o - v 2 " / ) ;
1406+ await expect ( page . locator ( "#workspaceContextOutput" ) ) . toHaveValue ( / " o b j e c t \. a s t e r o i d s \. s h i p " / ) ;
12731407 await expect ( page . locator ( "#workspaceContextOutput" ) ) . not . toHaveValue ( / " a c t i v e P a l e t t e " / ) ;
12741408 await expect ( page . locator ( "#workspaceContextOutput" ) ) . not . toHaveValue ( / " w o r k s p a c e M a n i f e s t " / ) ;
12751409 await expect ( page . locator ( '[data-workspace-tool-id="asset-manager-v2"]' ) ) . toBeEnabled ( ) ;
@@ -1390,7 +1524,7 @@ test.describe("Asset Manager V2", () => {
13901524 await expect ( page . locator ( "#inspectorOutput" ) ) . toContainText ( "\"type\": \"color\"" ) ;
13911525 await expect ( page . locator ( "#inspectorOutput" ) ) . toContainText ( "\"kind\": \"hex\"" ) ;
13921526 await expect ( page . locator ( "#inspectorOutput" ) ) . toContainText ( "\"name\": \"HUD Blue\"" ) ;
1393- await expect ( page . locator ( "#statusLog" ) ) . toHaveValue ( / O K W o r k s p a c e M a n a g e r V 2 s e s s i o n m a n i f e s t n o w h a s 1 8 v a l i d a t e d a s s e t s \. / ) ;
1527+ await expect ( page . locator ( "#statusLog" ) ) . toHaveValue ( / O K w o r k s p a c e \. t o o l s \. a s s e t - m a n a g e r - v 2 n o w h a s 1 9 v a l i d a t e d a s s e t s \. / ) ;
13941528
13951529 const storedContext = await page . evaluate ( ( id ) => JSON . parse ( sessionStorage . getItem ( id ) ) , hostContextId ) ;
13961530 expect ( storedContext . documentKind ) . toBe ( "workspace-manifest" ) ;
@@ -1399,37 +1533,53 @@ test.describe("Asset Manager V2", () => {
13991533 expect ( storedContext . workspaceManifest ) . toBeUndefined ( ) ;
14001534 expect ( storedContext . tools [ "asset-browser" ] ) . toBeUndefined ( ) ;
14011535 expect ( storedContext . tools [ "palette-browser" ] ) . toBeUndefined ( ) ;
1402- expect ( Object . keys ( storedContext . tools [ "asset-manager-v2" ] . assets ) ) . toHaveLength ( 18 ) ;
1536+ expect ( Object . keys ( storedContext . tools [ "asset-manager-v2" ] . assets ) ) . toHaveLength ( 15 ) ;
14031537 expect ( storedContext . tools [ "asset-manager-v2" ] . previewImagePath ) . toBeUndefined ( ) ;
1538+ expect ( storedContext . tools [ "asset-manager-v2" ] . assets [ "assets.color.background.game" ] ) . toEqual ( {
1539+ path : "palette://workspace/space-black" ,
1540+ type : "color" ,
1541+ kind : "hex" ,
1542+ role : "background" ,
1543+ source : "manifest" ,
1544+ color : {
1545+ hex : "#020617" ,
1546+ name : "Space Black" ,
1547+ symbol : "!" ,
1548+ source : "manifest"
1549+ }
1550+ } ) ;
14041551 expect ( storedContext . tools [ "asset-manager-v2" ] . assets [ "assets.audio.sound.fire" ] ) . toEqual ( {
14051552 path : "assets/audio/fire.wav" ,
14061553 type : "audio" ,
14071554 kind : "wav" ,
14081555 role : "sound" ,
14091556 source : "manifest"
14101557 } ) ;
1411- expect ( storedContext . tools [ "asset-manager-v2" ] . assets [ "assets.audio.sound.laser" ] ) . toEqual ( {
1558+ const storedAssetSession = await page . evaluate ( ( ) => JSON . parse ( sessionStorage . getItem ( "workspace.tools.asset-manager-v2" ) ) ) ;
1559+ expect ( storedAssetSession . dirty . isDirty ) . toBe ( true ) ;
1560+ expect ( Object . keys ( storedAssetSession . data . assets ) ) . toHaveLength ( 19 ) ;
1561+ expect ( storedAssetSession . data . assets [ "assets.audio.sound.laser" ] ) . toEqual ( {
14121562 path : "assets/audio/laser.wav" ,
14131563 type : "audio" ,
14141564 kind : "wav" ,
14151565 role : "sound" ,
14161566 source : "asset-manager-v2"
14171567 } ) ;
1418- expect ( storedContext . tools [ "asset-manager-v2" ] . assets [ "assets.font.ui.score" ] ) . toEqual ( {
1568+ expect ( storedAssetSession . data . assets [ "assets.font.ui.score" ] ) . toEqual ( {
14191569 path : "assets/fonts/score.ttf" ,
14201570 type : "font" ,
14211571 kind : "ttf" ,
14221572 role : "ui" ,
14231573 source : "asset-manager-v2"
14241574 } ) ;
1425- expect ( storedContext . tools [ "asset-manager-v2" ] . assets [ "assets.image.preview.title-preview" ] ) . toEqual ( {
1575+ expect ( storedAssetSession . data . assets [ "assets.image.preview.title-preview" ] ) . toEqual ( {
14261576 path : "assets/images/title-preview.png" ,
14271577 type : "image" ,
14281578 kind : "png" ,
14291579 role : "preview" ,
14301580 source : "asset-manager-v2"
14311581 } ) ;
1432- expect ( storedContext . tools [ "asset-manager-v2" ] . assets [ "assets.color.hud.primary-hud.hud-blue" ] ) . toEqual ( {
1582+ expect ( storedAssetSession . data . assets [ "assets.color.hud.primary-hud.hud-blue" ] ) . toEqual ( {
14331583 path : "palette://workspace/hud-blue" ,
14341584 type : "color" ,
14351585 kind : "hex" ,
@@ -1438,30 +1588,34 @@ test.describe("Asset Manager V2", () => {
14381588 color : {
14391589 hex : "#78B7FF" ,
14401590 name : "HUD Blue" ,
1441- symbol : "*"
1591+ symbol : "*" ,
1592+ source : "manifest"
14421593 }
14431594 } ) ;
14441595 expect ( storedContext . tools [ "palette-manager-v2" ] . source ) . toBe ( "manifest" ) ;
14451596 expect ( storedContext . tools [ "palette-manager-v2" ] . swatches . length ) . toBeGreaterThan ( 0 ) ;
1446- expect ( storedContext . tools [ "vector-map-editor " ] . vectorMapDocument . vectors . map ( ( vector ) => vector . id ) ) . toContain ( "vector .asteroids.ship" ) ;
1597+ expect ( storedContext . tools [ "object- vector-studio-v2 " ] . objects . map ( ( object ) => object . id ) ) . toContain ( "object .asteroids.ship" ) ;
14471598 expect ( storedContext . tools [ "workspace-v2" ] ) . toBeUndefined ( ) ;
1448- expect ( Object . keys ( storedContext . tools ) . sort ( ) ) . toEqual ( [ "asset-manager-v2" , "palette-manager-v2" , "vector-map-editor " ] ) ;
1599+ expect ( Object . keys ( storedContext . tools ) . sort ( ) ) . toEqual ( [ "asset-manager-v2" , "object-vector-studio-v2" , " palette-manager-v2", "text2speech-V2 " ] ) ;
14491600 await page . locator ( "#returnToWorkspaceButton" ) . click ( ) ;
14501601 await expect ( page ) . toHaveURL ( / w o r k s p a c e - m a n a g e r - v 2 \/ i n d e x \. h t m l \? h o s t C o n t e x t I d = w o r k s p a c e - m a n a g e r - v 2 - / ) ;
14511602 await expect ( page . locator ( "#activeGameSelect" ) ) . toHaveValue ( "Asteroids" ) ;
14521603 await expect ( page . locator ( "#activeAssetRegistrySummary" ) ) . toHaveCount ( 0 ) ;
14531604 await expect ( page . locator ( '[data-workspace-tool-id="asset-manager-v2"]' ) ) . toBeEnabled ( ) ;
1454- await expect ( page . locator ( "#exportManifestButton" ) ) . toBeEnabled ( ) ;
1455- const downloadPromise = page . waitForEvent ( "download" ) ;
1456- await page . locator ( "#exportManifestButton" ) . click ( ) ;
1457- const download = await downloadPromise ;
1458- expect ( download . suggestedFilename ( ) ) . toBe ( "workspace-manager-v2-Asteroids.workspace.manifest.json" ) ;
1459- const savedManifest = JSON . parse ( await readFile ( await download . path ( ) , "utf8" ) ) ;
1460- expect ( Object . keys ( savedManifest . tools [ "asset-manager-v2" ] . assets ) ) . toHaveLength ( 18 ) ;
1605+ await expect ( page . locator ( "#saveWorkspaceButton" ) ) . toBeEnabled ( ) ;
1606+ await page . locator ( "#saveWorkspaceButton" ) . click ( ) ;
1607+ await expect ( page . locator ( "#statusLog" ) ) . toHaveValue ( / O K S a v e d a n d m a r k e d c l e a n : w o r k s p a c e \. t o o l s \. a s s e t - m a n a g e r - v 2 \. / ) ;
1608+ await expect ( page . locator ( "#statusLog" ) ) . toHaveValue ( / O K S a v e v a l i d a t i o n r e s u l t : g a m e m a n i f e s t v a l i d ; r o o t \. t o o l s t o o l S t a t e v a l i d ; s a v e d c o n t e x t m a t c h e d r e - r e a d f i l e \. / ) ;
1609+ const manifestWrites = await page . evaluate ( ( ) => JSON . parse ( sessionStorage . getItem ( "workspace.repo.manifestWrites" ) || "[]" ) ) ;
1610+ expect ( manifestWrites ) . toHaveLength ( 1 ) ;
1611+ expect ( manifestWrites [ 0 ] . path ) . toBe ( "HTML-JavaScript-Gaming/games/Asteroids/game.manifest.json" ) ;
1612+ const savedManifest = JSON . parse ( manifestWrites [ 0 ] . contents ) ;
1613+ expect ( Object . keys ( savedManifest . tools [ "asset-manager-v2" ] . assets ) ) . toHaveLength ( 19 ) ;
14611614 expect ( savedManifest . tools [ "asset-manager-v2" ] . previewImagePath ) . toBeUndefined ( ) ;
1462- expect ( savedManifest . tools [ "asset-manager-v2" ] . assets [ "assets.audio.sound.laser" ] ) . toEqual ( storedContext . tools [ "asset-manager-v2" ] . assets [ "assets.audio.sound.laser" ] ) ;
1463- expect ( savedManifest . tools [ "asset-manager-v2" ] . assets [ "assets.color.hud.primary-hud.hud-blue" ] ) . toEqual ( storedContext . tools [ "asset-manager-v2" ] . assets [ "assets.color.hud.primary-hud.hud-blue" ] ) ;
1464- expect ( savedManifest . tools [ "vector-map-editor" ] . vectorMapDocument . vectors . map ( ( vector ) => vector . id ) ) . toContain ( "vector.asteroids.ship" ) ;
1615+ expect ( savedManifest . tools [ "asset-manager-v2" ] . assets [ "assets.color.background.game" ] ) . toEqual ( storedContext . tools [ "asset-manager-v2" ] . assets [ "assets.color.background.game" ] ) ;
1616+ expect ( savedManifest . tools [ "asset-manager-v2" ] . assets [ "assets.audio.sound.laser" ] ) . toEqual ( storedAssetSession . data . assets [ "assets.audio.sound.laser" ] ) ;
1617+ expect ( savedManifest . tools [ "asset-manager-v2" ] . assets [ "assets.color.hud.primary-hud.hud-blue" ] ) . toEqual ( storedAssetSession . data . assets [ "assets.color.hud.primary-hud.hud-blue" ] ) ;
1618+ expect ( savedManifest . tools [ "object-vector-studio-v2" ] . objects . map ( ( object ) => object . id ) ) . toContain ( "object.asteroids.ship" ) ;
14651619
14661620 expect ( pageErrors ) . toEqual ( [ ] ) ;
14671621 } finally {
@@ -1492,7 +1646,7 @@ test.describe("Asset Manager V2", () => {
14921646 await expect ( assetManagerCard ) . toContainText ( "Schema Validated" ) ;
14931647 await expect ( collisionInspectorLink ) . toBeVisible ( ) ;
14941648 await expect ( collisionInspectorLink ) . toHaveAttribute ( "href" , "/tools/collision-inspector-v2/index.html" ) ;
1495- await expect ( collisionInspectorCard ) . toContainText ( "Manifest-driven collision QA " ) ;
1649+ await expect ( collisionInspectorCard ) . toContainText ( "Manifest-driven collision visualization " ) ;
14961650 const plannedToolNames = await page . locator ( "[data-planned-tools-grid] h3" ) . allTextContents ( ) ;
14971651 for ( const plannedToolName of [
14981652 "Asset Manager V2" ,
0 commit comments