|
1 | | -PR_26174_ALFA_000-projectinstructions-archive-ignore review diff |
| 1 | +diff --git a/tests/playwright/tools/IdeaBoardTableNotes.spec.mjs b/tests/playwright/tools/IdeaBoardTableNotes.spec.mjs |
| 2 | +index 934faf46f..b2872b432 100644 |
| 3 | +--- a/tests/playwright/tools/IdeaBoardTableNotes.spec.mjs |
| 4 | ++++ b/tests/playwright/tools/IdeaBoardTableNotes.spec.mjs |
| 5 | +@@ -1,6 +1,7 @@ |
| 6 | + import { expect, test } from "@playwright/test"; |
| 7 | + import { MOCK_DB_KEYS } from "../../../src/dev-runtime/persistence/mock-db-store.js"; |
| 8 | + import { isBrowserExtensionNoise } from "../../helpers/browserExtensionNoise.mjs"; |
| 9 | ++import { createGameJourneyCompletionMetricsPostgresClientStub } from "../../helpers/gameJourneyCompletionMetricsPostgresClientStub.mjs"; |
| 10 | + import { startRepoServer } from "../../helpers/playwrightRepoServer.mjs"; |
2 | 11 |
|
3 | | -.gitignore |
4 | | -@@ |
5 | | - # Game Foundry Studio project files |
6 | | - assets/*.gfsp |
7 | | -+docs_build/dev/ProjectInstructions.zip |
| 12 | + function restoreEnvValue(key, value) { |
| 13 | +@@ -112,7 +113,10 @@ async function expectNoNavigationFallbackUi(page) { |
| 14 | + } |
| 15 | + |
| 16 | + test("Idea Board uses accordion table ideas and notes", async ({ page }) => { |
| 17 | +- const server = await startRepoServer(); |
| 18 | ++ const server = await startRepoServer({ |
| 19 | ++ gameJourneyCompletionMetricsLegacyDbPath: null, |
| 20 | ++ gameJourneyCompletionMetricsPostgresClient: createGameJourneyCompletionMetricsPostgresClientStub(), |
| 21 | ++ }); |
| 22 | + const previousApiUrl = process.env.GAMEFOUNDRY_API_URL; |
| 23 | + const previousSiteUrl = process.env.GAMEFOUNDRY_SITE_URL; |
| 24 | + const previousSupabaseEnv = { |
| 25 | +@@ -131,6 +135,7 @@ test("Idea Board uses accordion table ideas and notes", async ({ page }) => { |
| 26 | + const pageErrors = []; |
| 27 | + const consoleErrors = []; |
| 28 | + const mutatingApiRequests = []; |
| 29 | ++ const createGamePayloads = []; |
| 30 | + |
| 31 | + page.on("response", (response) => { |
| 32 | + if (response.status() >= 400) failedRequests.push(`${response.status()} ${response.url()}`); |
| 33 | +@@ -144,8 +149,12 @@ test("Idea Board uses accordion table ideas and notes", async ({ page }) => { |
| 34 | + if (message.type() === "error" && !isBrowserExtensionNoise(message.text())) consoleErrors.push(message.text()); |
| 35 | + }); |
| 36 | + page.on("request", (request) => { |
| 37 | +- if (request.url().includes("/api/") && request.method() !== "GET") { |
| 38 | +- mutatingApiRequests.push(`${request.method()} ${request.url()}`); |
| 39 | ++ const requestUrl = request.url(); |
| 40 | ++ if (requestUrl.includes("/api/") && request.method() !== "GET") { |
| 41 | ++ mutatingApiRequests.push(`${request.method()} ${requestUrl}`); |
| 42 | ++ } |
| 43 | ++ if (requestUrl.includes("/api/toolbox/game-hub/repositories/") && requestUrl.includes("/methods/createGame")) { |
| 44 | ++ createGamePayloads.push(request.postDataJSON()); |
| 45 | + } |
| 46 | + }); |
| 47 | + |
| 48 | +@@ -228,6 +237,7 @@ test("Idea Board uses accordion table ideas and notes", async ({ page }) => { |
| 49 | + await expect(page.locator("[data-idea-board-idea-row='top-thoughts'] td").nth(2)).toHaveText("2026-06-20"); |
| 50 | + await expect(page.locator("[data-idea-board-notes-count='top-thoughts']")).toHaveText("3 Notes"); |
| 51 | + await expect(page.locator("[data-idea-board-idea-row='top-thoughts'] [data-idea-board-idea-action]")).toHaveText(["Edit", "Delete"]); |
| 52 | ++ await expect(page.locator("[data-idea-board-idea-row='top-thoughts'] [data-idea-board-idea-action='create-project']")).toHaveCount(0); |
| 53 | + |
| 54 | + await expect(page.locator("[data-idea-board-idea-row='sky-orchard'] th")).toHaveText("Sky Orchard"); |
| 55 | + await expectIdeaChevron(page, "sky-orchard", "gfs-chevron-down.svg"); |
| 56 | +@@ -336,6 +346,19 @@ test("Idea Board uses accordion table ideas and notes", async ({ page }) => { |
| 57 | + await expect(page.locator("[data-idea-board-idea-row='lantern-reef'] [data-idea-board-idea-action='delete']")).toHaveCount(0); |
| 58 | + await expect(page.locator("[data-idea-board-add-note='lantern-reef']")).toHaveCount(0); |
| 59 | + await expect(page.locator("[data-idea-board-notes-table='lantern-reef'] [data-idea-board-note-action]")).toHaveCount(0); |
| 60 | ++ expect(createGamePayloads).toHaveLength(1); |
| 61 | ++ const [createGameInput] = createGamePayloads[0].args; |
| 62 | ++ expect(Object.keys(createGameInput).sort()).toEqual(["name", "purpose", "sourceIdea", "status"]); |
| 63 | ++ expect(createGameInput).toMatchObject({ |
| 64 | ++ name: "Lantern Reef", |
| 65 | ++ purpose: "Game", |
| 66 | ++ sourceIdea: { |
| 67 | ++ idea: "Lantern Reef", |
| 68 | ++ pitch: "Guide light through a reef that rearranges at dusk.", |
| 69 | ++ notes: ["Use dusk tide changes as the first Game Hub planning note."], |
| 70 | ++ }, |
| 71 | ++ status: "Planning", |
| 72 | ++ }); |
| 73 | + await page.locator("[data-idea-board-idea-row='lantern-reef'] [data-idea-board-idea-action='archive']").click(); |
| 74 | + await expect(page.locator("[data-idea-board-idea-row='lantern-reef']")).toHaveCount(0); |
| 75 | + await page.locator("[data-idea-board-status-filter-option][value='Archived']").check(); |
| 76 | +@@ -374,6 +397,78 @@ test("Idea Board uses accordion table ideas and notes", async ({ page }) => { |
| 77 | + } |
| 78 | + }); |
| 79 | + |
| 80 | ++test("Idea Board guest Create Project redirects to sign in without creating a project", async ({ page }) => { |
| 81 | ++ const server = await startRepoServer(); |
| 82 | ++ const previousApiUrl = process.env.GAMEFOUNDRY_API_URL; |
| 83 | ++ const previousSiteUrl = process.env.GAMEFOUNDRY_SITE_URL; |
| 84 | ++ const previousMetricsDbPath = process.env.GAMEFOUNDRY_GAME_JOURNEY_METRICS_DB_PATH; |
| 85 | ++ const previousSupabaseEnv = { |
| 86 | ++ GAMEFOUNDRY_DATABASE_URL: process.env.GAMEFOUNDRY_DATABASE_URL, |
| 87 | ++ GAMEFOUNDRY_SUPABASE_ANON_KEY: process.env.GAMEFOUNDRY_SUPABASE_ANON_KEY, |
| 88 | ++ GAMEFOUNDRY_SUPABASE_SERVICE_ROLE_KEY: process.env.GAMEFOUNDRY_SUPABASE_SERVICE_ROLE_KEY, |
| 89 | ++ GAMEFOUNDRY_SUPABASE_URL: process.env.GAMEFOUNDRY_SUPABASE_URL, |
| 90 | ++ }; |
| 91 | ++ process.env.GAMEFOUNDRY_API_URL = `${server.baseUrl}/api`; |
| 92 | ++ process.env.GAMEFOUNDRY_SITE_URL = server.baseUrl; |
| 93 | ++ process.env.GAMEFOUNDRY_DATABASE_URL = "postgres://idea-board:test@127.0.0.1:5432/idea_board"; |
| 94 | ++ process.env.GAMEFOUNDRY_GAME_JOURNEY_METRICS_DB_PATH = `tmp/test-results/idea-board-${process.pid}-${Date.now()}.sqlite`; |
| 95 | ++ process.env.GAMEFOUNDRY_SUPABASE_ANON_KEY = "idea-board-anon-key"; |
| 96 | ++ process.env.GAMEFOUNDRY_SUPABASE_SERVICE_ROLE_KEY = "idea-board-service-role-key"; |
| 97 | ++ process.env.GAMEFOUNDRY_SUPABASE_URL = `${server.baseUrl}/fake-supabase`; |
| 98 | ++ const createGameRequests = []; |
| 99 | ++ |
| 100 | ++ page.on("request", (request) => { |
| 101 | ++ const requestUrl = request.url(); |
| 102 | ++ if (requestUrl.includes("/api/toolbox/game-hub/repositories/") && requestUrl.includes("/methods/createGame")) { |
| 103 | ++ createGameRequests.push(requestUrl); |
| 104 | ++ } |
| 105 | ++ }); |
| 106 | ++ |
| 107 | ++ try { |
| 108 | ++ await page.route("**/api/platform-settings/banner", async (route) => { |
| 109 | ++ await route.fulfill({ |
| 110 | ++ contentType: "application/json", |
| 111 | ++ body: JSON.stringify({ |
| 112 | ++ data: { banner: { active: false, message: "", tone: "info" } }, |
| 113 | ++ ok: true, |
| 114 | ++ }), |
| 115 | ++ }); |
| 116 | ++ }); |
| 117 | ++ await page.route("**/api/toolbox/registry/snapshot", async (route) => { |
| 118 | ++ await route.fulfill({ |
| 119 | ++ contentType: "application/json", |
| 120 | ++ body: JSON.stringify({ |
| 121 | ++ data: { |
| 122 | ++ activeTools: [], |
| 123 | ++ readinessByStatus: {}, |
| 124 | ++ tools: [], |
| 125 | ++ toolboxContract: {}, |
| 126 | ++ }, |
| 127 | ++ ok: true, |
| 128 | ++ }), |
| 129 | ++ }); |
| 130 | ++ }); |
| 131 | ++ |
| 132 | ++ await page.goto(`${server.baseUrl}/toolbox/idea-board/index.html`, { waitUntil: "networkidle" }); |
| 133 | ++ await page.locator("[data-idea-board-add-idea]").click(); |
| 134 | ++ await page.locator("[data-idea-board-idea-input]").fill("Guest Reef"); |
| 135 | ++ await page.locator("[data-idea-board-pitch-input]").fill("Guest cannot create authoritative project keys."); |
| 136 | ++ await page.locator("[data-idea-board-idea-status-input]").selectOption("Ready"); |
| 137 | ++ await page.locator("[data-idea-board-idea-action='save']").click(); |
| 138 | ++ await expect(page.locator("[data-idea-board-idea-row='guest-reef'] [data-idea-board-idea-action]")).toHaveText(["Edit", "Create Project", "Delete"]); |
| 139 | ++ |
| 140 | ++ await page.locator("[data-idea-board-idea-row='guest-reef'] [data-idea-board-idea-action='create-project']").click(); |
| 141 | ++ await page.waitForURL(/\/account\/sign-in\.html$/); |
| 142 | ++ expect(createGameRequests).toEqual([]); |
| 143 | ++ } finally { |
| 144 | ++ restoreEnvValue("GAMEFOUNDRY_API_URL", previousApiUrl); |
| 145 | ++ restoreEnvValue("GAMEFOUNDRY_SITE_URL", previousSiteUrl); |
| 146 | ++ restoreEnvValue("GAMEFOUNDRY_GAME_JOURNEY_METRICS_DB_PATH", previousMetricsDbPath); |
| 147 | ++ Object.entries(previousSupabaseEnv).forEach(([key, value]) => restoreEnvValue(key, value)); |
| 148 | ++ await server.close(); |
| 149 | ++ } |
| 150 | ++}); |
| 151 | ++ |
| 152 | + test("Idea Board remains usable without visible navigation fallback when registry navigation is unavailable", async ({ page }) => { |
| 153 | + const server = await startRepoServer(); |
| 154 | + const previousApiUrl = process.env.GAMEFOUNDRY_API_URL; |
| 155 | +diff --git a/toolbox/idea-board/index.js b/toolbox/idea-board/index.js |
| 156 | +index d1e339336..0ae02bd7a 100644 |
| 157 | +--- a/toolbox/idea-board/index.js |
| 158 | ++++ b/toolbox/idea-board/index.js |
| 159 | +@@ -1,9 +1,11 @@ |
| 160 | + import { createServerRepositoryClient } from "../../src/api/server-api-client.js"; |
| 161 | ++import { getSessionCurrent } from "../../src/api/session-api-client.js"; |
| 162 | + |
| 163 | + const statusOptions = Object.freeze(["New", "Exploring", "Refining", "Ready", "Project", "Archived"]); |
| 164 | + const defaultVisibleStatuses = Object.freeze(["New", "Exploring", "Refining", "Ready", "Project"]); |
| 165 | + const userId = "user-1"; |
| 166 | + const gameHubRoute = "toolbox/game-hub/index.html"; |
| 167 | ++const signInRoute = "account/sign-in.html"; |
| 168 | + let gameHubRepository = null; |
| 169 | + |
| 170 | + const ideaTable = [ |
| 171 | +@@ -561,6 +563,28 @@ function gameHubUrl(record) { |
| 172 | + return `${gameHubRoute}${suffix}`; |
| 173 | + } |
| 174 | + |
| 175 | ++function signInUrl() { |
| 176 | ++ return new URL(signInRoute, document.baseURI || window.location.href).href; |
| 177 | ++} |
| 178 | ++ |
| 179 | ++function currentSessionState() { |
| 180 | ++ try { |
| 181 | ++ const session = getSessionCurrent(); |
| 182 | ++ return { |
| 183 | ++ apiAvailable: true, |
| 184 | ++ authenticated: Boolean(session?.authenticated && session.userKey), |
| 185 | ++ session, |
| 186 | ++ }; |
| 187 | ++ } catch (error) { |
| 188 | ++ console.warn("Idea Board could not verify the current session.", error instanceof Error ? error.message : String(error || "")); |
| 189 | ++ return { |
| 190 | ++ apiAvailable: false, |
| 191 | ++ authenticated: false, |
| 192 | ++ session: null, |
| 193 | ++ }; |
| 194 | ++ } |
| 195 | ++} |
| 196 | ++ |
| 197 | + function createProject(root, ideaId) { |
| 198 | + const record = ideaRecord(ideaId); |
| 199 | + if (!record) { |
| 200 | +@@ -571,6 +595,16 @@ function createProject(root, ideaId) { |
| 201 | + updateStatus(root, "Set this idea to Ready before creating a project."); |
| 202 | + return; |
| 203 | + } |
| 204 | ++ const sessionState = currentSessionState(); |
| 205 | ++ if (!sessionState.apiAvailable) { |
| 206 | ++ updateStatus(root, "Sign-in status could not be verified. Try again shortly."); |
| 207 | ++ return; |
| 208 | ++ } |
| 209 | ++ if (!sessionState.authenticated) { |
| 210 | ++ updateStatus(root, "Sign in to create a Game Hub project."); |
| 211 | ++ window.location.href = signInUrl(); |
| 212 | ++ return; |
| 213 | ++ } |
| 214 | + const repository = gameHubProjectRepository(); |
| 215 | + const project = repository.createGame({ |
| 216 | + name: record.idea, |
0 commit comments