11import { expect , test } from "@playwright/test" ;
22import { MOCK_DB_KEYS } from "../../../src/dev-runtime/persistence/mock-db-store.js" ;
33import { isBrowserExtensionNoise } from "../../helpers/browserExtensionNoise.mjs" ;
4+ import { createGameJourneyCompletionMetricsPostgresClientStub } from "../../helpers/gameJourneyCompletionMetricsPostgresClientStub.mjs" ;
45import { startRepoServer } from "../../helpers/playwrightRepoServer.mjs" ;
56
67function restoreEnvValue ( key , value ) {
@@ -112,7 +113,10 @@ async function expectNoNavigationFallbackUi(page) {
112113}
113114
114115test ( "Idea Board uses accordion table ideas and notes" , async ( { page } ) => {
115- const server = await startRepoServer ( ) ;
116+ const server = await startRepoServer ( {
117+ gameJourneyCompletionMetricsLegacyDbPath : null ,
118+ gameJourneyCompletionMetricsPostgresClient : createGameJourneyCompletionMetricsPostgresClientStub ( ) ,
119+ } ) ;
116120 const previousApiUrl = process . env . GAMEFOUNDRY_API_URL ;
117121 const previousSiteUrl = process . env . GAMEFOUNDRY_SITE_URL ;
118122 const previousSupabaseEnv = {
@@ -131,6 +135,7 @@ test("Idea Board uses accordion table ideas and notes", async ({ page }) => {
131135 const pageErrors = [ ] ;
132136 const consoleErrors = [ ] ;
133137 const mutatingApiRequests = [ ] ;
138+ const createGamePayloads = [ ] ;
134139
135140 page . on ( "response" , ( response ) => {
136141 if ( response . status ( ) >= 400 ) failedRequests . push ( `${ response . status ( ) } ${ response . url ( ) } ` ) ;
@@ -144,8 +149,12 @@ test("Idea Board uses accordion table ideas and notes", async ({ page }) => {
144149 if ( message . type ( ) === "error" && ! isBrowserExtensionNoise ( message . text ( ) ) ) consoleErrors . push ( message . text ( ) ) ;
145150 } ) ;
146151 page . on ( "request" , ( request ) => {
147- if ( request . url ( ) . includes ( "/api/" ) && request . method ( ) !== "GET" ) {
148- mutatingApiRequests . push ( `${ request . method ( ) } ${ request . url ( ) } ` ) ;
152+ const requestUrl = request . url ( ) ;
153+ if ( requestUrl . includes ( "/api/" ) && request . method ( ) !== "GET" ) {
154+ mutatingApiRequests . push ( `${ request . method ( ) } ${ requestUrl } ` ) ;
155+ }
156+ if ( requestUrl . includes ( "/api/toolbox/game-hub/repositories/" ) && requestUrl . includes ( "/methods/createGame" ) ) {
157+ createGamePayloads . push ( request . postDataJSON ( ) ) ;
149158 }
150159 } ) ;
151160
@@ -228,6 +237,7 @@ test("Idea Board uses accordion table ideas and notes", async ({ page }) => {
228237 await expect ( page . locator ( "[data-idea-board-idea-row='top-thoughts'] td" ) . nth ( 2 ) ) . toHaveText ( "2026-06-20" ) ;
229238 await expect ( page . locator ( "[data-idea-board-notes-count='top-thoughts']" ) ) . toHaveText ( "3 Notes" ) ;
230239 await expect ( page . locator ( "[data-idea-board-idea-row='top-thoughts'] [data-idea-board-idea-action]" ) ) . toHaveText ( [ "Edit" , "Delete" ] ) ;
240+ await expect ( page . locator ( "[data-idea-board-idea-row='top-thoughts'] [data-idea-board-idea-action='create-project']" ) ) . toHaveCount ( 0 ) ;
231241
232242 await expect ( page . locator ( "[data-idea-board-idea-row='sky-orchard'] th" ) ) . toHaveText ( "Sky Orchard" ) ;
233243 await expectIdeaChevron ( page , "sky-orchard" , "gfs-chevron-down.svg" ) ;
@@ -336,6 +346,19 @@ test("Idea Board uses accordion table ideas and notes", async ({ page }) => {
336346 await expect ( page . locator ( "[data-idea-board-idea-row='lantern-reef'] [data-idea-board-idea-action='delete']" ) ) . toHaveCount ( 0 ) ;
337347 await expect ( page . locator ( "[data-idea-board-add-note='lantern-reef']" ) ) . toHaveCount ( 0 ) ;
338348 await expect ( page . locator ( "[data-idea-board-notes-table='lantern-reef'] [data-idea-board-note-action]" ) ) . toHaveCount ( 0 ) ;
349+ expect ( createGamePayloads ) . toHaveLength ( 1 ) ;
350+ const [ createGameInput ] = createGamePayloads [ 0 ] . args ;
351+ expect ( Object . keys ( createGameInput ) . sort ( ) ) . toEqual ( [ "name" , "purpose" , "sourceIdea" , "status" ] ) ;
352+ expect ( createGameInput ) . toMatchObject ( {
353+ name : "Lantern Reef" ,
354+ purpose : "Game" ,
355+ sourceIdea : {
356+ idea : "Lantern Reef" ,
357+ pitch : "Guide light through a reef that rearranges at dusk." ,
358+ notes : [ "Use dusk tide changes as the first Game Hub planning note." ] ,
359+ } ,
360+ status : "Planning" ,
361+ } ) ;
339362 await page . locator ( "[data-idea-board-idea-row='lantern-reef'] [data-idea-board-idea-action='archive']" ) . click ( ) ;
340363 await expect ( page . locator ( "[data-idea-board-idea-row='lantern-reef']" ) ) . toHaveCount ( 0 ) ;
341364 await page . locator ( "[data-idea-board-status-filter-option][value='Archived']" ) . check ( ) ;
@@ -374,6 +397,78 @@ test("Idea Board uses accordion table ideas and notes", async ({ page }) => {
374397 }
375398} ) ;
376399
400+ test ( "Idea Board guest Create Project redirects to sign in without creating a project" , async ( { page } ) => {
401+ const server = await startRepoServer ( ) ;
402+ const previousApiUrl = process . env . GAMEFOUNDRY_API_URL ;
403+ const previousSiteUrl = process . env . GAMEFOUNDRY_SITE_URL ;
404+ const previousMetricsDbPath = process . env . GAMEFOUNDRY_GAME_JOURNEY_METRICS_DB_PATH ;
405+ const previousSupabaseEnv = {
406+ GAMEFOUNDRY_DATABASE_URL : process . env . GAMEFOUNDRY_DATABASE_URL ,
407+ GAMEFOUNDRY_SUPABASE_ANON_KEY : process . env . GAMEFOUNDRY_SUPABASE_ANON_KEY ,
408+ GAMEFOUNDRY_SUPABASE_SERVICE_ROLE_KEY : process . env . GAMEFOUNDRY_SUPABASE_SERVICE_ROLE_KEY ,
409+ GAMEFOUNDRY_SUPABASE_URL : process . env . GAMEFOUNDRY_SUPABASE_URL ,
410+ } ;
411+ process . env . GAMEFOUNDRY_API_URL = `${ server . baseUrl } /api` ;
412+ process . env . GAMEFOUNDRY_SITE_URL = server . baseUrl ;
413+ process . env . GAMEFOUNDRY_DATABASE_URL = "postgres://idea-board:test@127.0.0.1:5432/idea_board" ;
414+ process . env . GAMEFOUNDRY_GAME_JOURNEY_METRICS_DB_PATH = `tmp/test-results/idea-board-${ process . pid } -${ Date . now ( ) } .sqlite` ;
415+ process . env . GAMEFOUNDRY_SUPABASE_ANON_KEY = "idea-board-anon-key" ;
416+ process . env . GAMEFOUNDRY_SUPABASE_SERVICE_ROLE_KEY = "idea-board-service-role-key" ;
417+ process . env . GAMEFOUNDRY_SUPABASE_URL = `${ server . baseUrl } /fake-supabase` ;
418+ const createGameRequests = [ ] ;
419+
420+ page . on ( "request" , ( request ) => {
421+ const requestUrl = request . url ( ) ;
422+ if ( requestUrl . includes ( "/api/toolbox/game-hub/repositories/" ) && requestUrl . includes ( "/methods/createGame" ) ) {
423+ createGameRequests . push ( requestUrl ) ;
424+ }
425+ } ) ;
426+
427+ try {
428+ await page . route ( "**/api/platform-settings/banner" , async ( route ) => {
429+ await route . fulfill ( {
430+ contentType : "application/json" ,
431+ body : JSON . stringify ( {
432+ data : { banner : { active : false , message : "" , tone : "info" } } ,
433+ ok : true ,
434+ } ) ,
435+ } ) ;
436+ } ) ;
437+ await page . route ( "**/api/toolbox/registry/snapshot" , async ( route ) => {
438+ await route . fulfill ( {
439+ contentType : "application/json" ,
440+ body : JSON . stringify ( {
441+ data : {
442+ activeTools : [ ] ,
443+ readinessByStatus : { } ,
444+ tools : [ ] ,
445+ toolboxContract : { } ,
446+ } ,
447+ ok : true ,
448+ } ) ,
449+ } ) ;
450+ } ) ;
451+
452+ await page . goto ( `${ server . baseUrl } /toolbox/idea-board/index.html` , { waitUntil : "networkidle" } ) ;
453+ await page . locator ( "[data-idea-board-add-idea]" ) . click ( ) ;
454+ await page . locator ( "[data-idea-board-idea-input]" ) . fill ( "Guest Reef" ) ;
455+ await page . locator ( "[data-idea-board-pitch-input]" ) . fill ( "Guest cannot create authoritative project keys." ) ;
456+ await page . locator ( "[data-idea-board-idea-status-input]" ) . selectOption ( "Ready" ) ;
457+ await page . locator ( "[data-idea-board-idea-action='save']" ) . click ( ) ;
458+ await expect ( page . locator ( "[data-idea-board-idea-row='guest-reef'] [data-idea-board-idea-action]" ) ) . toHaveText ( [ "Edit" , "Create Project" , "Delete" ] ) ;
459+
460+ await page . locator ( "[data-idea-board-idea-row='guest-reef'] [data-idea-board-idea-action='create-project']" ) . click ( ) ;
461+ await page . waitForURL ( / \/ a c c o u n t \/ s i g n - i n \. h t m l $ / ) ;
462+ expect ( createGameRequests ) . toEqual ( [ ] ) ;
463+ } finally {
464+ restoreEnvValue ( "GAMEFOUNDRY_API_URL" , previousApiUrl ) ;
465+ restoreEnvValue ( "GAMEFOUNDRY_SITE_URL" , previousSiteUrl ) ;
466+ restoreEnvValue ( "GAMEFOUNDRY_GAME_JOURNEY_METRICS_DB_PATH" , previousMetricsDbPath ) ;
467+ Object . entries ( previousSupabaseEnv ) . forEach ( ( [ key , value ] ) => restoreEnvValue ( key , value ) ) ;
468+ await server . close ( ) ;
469+ }
470+ } ) ;
471+
377472test ( "Idea Board remains usable without visible navigation fallback when registry navigation is unavailable" , async ( { page } ) => {
378473 const server = await startRepoServer ( ) ;
379474 const previousApiUrl = process . env . GAMEFOUNDRY_API_URL ;
0 commit comments