Skip to content

Commit 0980828

Browse files
committed
PR_26174_ALFA_012-game-hub-parent-child-final-validation
1 parent c4a3db1 commit 0980828

8 files changed

Lines changed: 306 additions & 147 deletions
Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
Branch Validation: PASS
2+
3+
PASS - Current branch is pr/26174-ALFA-012-game-hub-parent-child-final-validation.
4+
PASS - Expected branch is pr/26174-ALFA-012-game-hub-parent-child-final-validation.
5+
PASS - Continued stack from PR_26174_ALFA_011 branch lineage.
6+
PASS - Work stayed scoped to targeted Game Hub Playwright validation and required reports.
Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
Manual Validation Notes: PASS
2+
3+
- Reviewed the expanded Open Games row behavior through targeted Playwright assertions.
4+
- Confirmed Source Idea and Readiness Output use distinct child-table selectors and captions.
5+
- Confirmed Source Idea child context exposes no form controls, buttons, or editable regions.
6+
- Confirmed readiness checklist rows preserve the incoming service-contract order.
7+
- Confirmed creator-safe empty and unavailable states still pass targeted validation.
Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
Requirement Checklist: PASS
2+
3+
PASS - Open Games parent rows are validated.
4+
PASS - Expanded game shows Source Idea child table.
5+
PASS - Expanded game shows Readiness Output child table.
6+
PASS - Source Idea is read-only with no edit/delete controls.
7+
PASS - Readiness Output is separate from Source Idea.
8+
PASS - Empty and unavailable states still pass targeted validation.
9+
PASS - Added targeted Playwright coverage only.
10+
PASS - No unrelated cleanup was introduced.
Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
Validation Lane: PASS
2+
3+
Command:
4+
npx playwright test tests/playwright/tools/GameHubMockRepository.spec.mjs -g "Game Hub validates Open Games parent and child tables|Game Hub shows a creator-safe empty state|Game Hub shows a creator-safe unavailable state"
5+
6+
Result:
7+
3 passed.
8+
9+
Additional check:
10+
git diff --check
Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,16 @@
1+
# PR_26174_ALFA_012-game-hub-parent-child-final-validation
2+
3+
## Purpose
4+
5+
Final validation and report pass for the Game Hub parent/child table stack.
6+
7+
## Summary
8+
9+
- Added targeted Playwright coverage for Open Games parent rows and expanded child tables.
10+
- Validated Source Idea and Readiness Output render as separate child tables.
11+
- Validated Source Idea context is read-only and readiness output preserves incoming checklist order.
12+
- Re-ran targeted empty and unavailable state checks.
13+
14+
## Validation
15+
16+
PASS - `npx playwright test tests/playwright/tools/GameHubMockRepository.spec.mjs -g "Game Hub validates Open Games parent and child tables|Game Hub shows a creator-safe empty state|Game Hub shows a creator-safe unavailable state"`
Lines changed: 5 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,8 @@
1-
toolbox/game-hub/game-hub.js
21
tests/playwright/tools/GameHubMockRepository.spec.mjs
32
docs_build/dev/reports/codex_review.diff
43
docs_build/dev/reports/codex_changed_files.txt
5-
docs_build/dev/reports/PR_26174_ALFA_011-game-hub-readiness-output-child-table.md
6-
docs_build/dev/reports/PR_26174_ALFA_011-game-hub-readiness-output-child-table-branch-validation.txt
7-
docs_build/dev/reports/PR_26174_ALFA_011-game-hub-readiness-output-child-table-requirement-checklist.txt
8-
docs_build/dev/reports/PR_26174_ALFA_011-game-hub-readiness-output-child-table-validation-lane.txt
9-
docs_build/dev/reports/PR_26174_ALFA_011-game-hub-readiness-output-child-table-manual-validation-notes.txt
4+
docs_build/dev/reports/PR_26174_ALFA_012-game-hub-parent-child-final-validation.md
5+
docs_build/dev/reports/PR_26174_ALFA_012-game-hub-parent-child-final-validation-branch-validation.txt
6+
docs_build/dev/reports/PR_26174_ALFA_012-game-hub-parent-child-final-validation-requirement-checklist.txt
7+
docs_build/dev/reports/PR_26174_ALFA_012-game-hub-parent-child-final-validation-validation-lane.txt
8+
docs_build/dev/reports/PR_26174_ALFA_012-game-hub-parent-child-final-validation-manual-validation-notes.txt
Lines changed: 127 additions & 141 deletions
Original file line numberDiff line numberDiff line change
@@ -1,149 +1,135 @@
11
diff --git a/tests/playwright/tools/GameHubMockRepository.spec.mjs b/tests/playwright/tools/GameHubMockRepository.spec.mjs
2-
index 695b0a609..dac65c676 100644
2+
index dac65c676..4e617ed35 100644
33
--- a/tests/playwright/tools/GameHubMockRepository.spec.mjs
44
+++ b/tests/playwright/tools/GameHubMockRepository.spec.mjs
5-
@@ -288,6 +288,22 @@ test("Game Hub creates, opens, and deletes mock games", async ({ page }) => {
6-
"StatusUnder Construction",
7-
"OwnerUser 1",
8-
]);
9-
+ await expect(page.locator("[data-game-expanded-row='demo-game'] [data-game-child-table]")).toHaveCount(3);
10-
+ await expect(page.locator("[data-game-expanded-row='demo-game'] [data-game-child-table='source-idea'] caption")).toHaveText("Source Idea");
11-
+ const readinessOutputTable = page.locator("[data-game-expanded-row='demo-game'] [data-game-child-table='readiness-output']");
12-
+ await expect(readinessOutputTable.locator("caption")).toHaveText("Readiness Output");
13-
+ await expect(readinessOutputTable.locator("thead th")).toHaveText(["Output", "Status"]);
14-
+ await expect(readinessOutputTable.locator("tbody tr")).toHaveText([
15-
+ "Game StatusUnder Construction",
16-
+ "Game ProgressDemo Game identity ready",
17-
+ "Launch ProgressPublish blocked until configuration and required assets are ready",
18-
+ "Current FocusComplete Game Configuration",
19-
+ "Recommended Next ToolGame Configuration",
20-
+ "Game identityComplete",
21-
+ "Game configurationUnder Construction",
22-
+ "Playable buildPlanned",
23-
+ "Publishing reviewPlanned",
24-
+ ]);
25-
await demoGameRow.locator("[data-game-toggle='demo-game']").click();
26-
await expect(page.locator("[data-game-expanded-row='demo-game']")).toHaveCount(0);
27-
28-
diff --git a/toolbox/game-hub/game-hub.js b/toolbox/game-hub/game-hub.js
29-
index 67f56ca41..4b21814d7 100644
30-
--- a/toolbox/game-hub/game-hub.js
31-
+++ b/toolbox/game-hub/game-hub.js
32-
@@ -319,7 +319,53 @@ function renderSourceIdeaChildTable(parent, game) {
33-
parent.append(wrapper);
34-
}
5+
@@ -335,6 +335,131 @@ test("Game Hub creates, opens, and deletes mock games", async ({ page }) => {
6+
}
7+
});
358

36-
-function renderExpandedGameRow(tbody, game) {
37-
+function renderReadinessOutputChildTable(parent, game, progress, active) {
38-
+ const readiness = active && isRecord(progress)
39-
+ ? progress
40-
+ : {
41-
+ currentFocus: "Open this game to review readiness",
42-
+ gameProgress: `${game.name} identity ready`,
43-
+ gameStatus: game.status || "No status",
44-
+ publishingProgress: "Open this game to review launch progress",
45-
+ recommendedNextTool: "Game Hub",
46-
+ progressChecklist: [],
47-
+ };
48-
+ const wrapper = document.createElement("div");
49-
+ wrapper.className = "table-wrapper";
50-
+ const table = document.createElement("table");
51-
+ table.className = "data-table data-table--fixed";
52-
+ table.dataset.gameChildTable = "readiness-output";
53-
+ table.setAttribute("aria-label", `${game.name} readiness output`);
54-
+ table.innerHTML = "<caption>Readiness Output</caption><thead><tr><th scope=\"col\">Output</th><th scope=\"col\">Status</th></tr></thead>";
55-
+ const body = document.createElement("tbody");
56-
+ [
57-
+ ["Game Status", readiness.gameStatus],
58-
+ ["Game Progress", readiness.gameProgress],
59-
+ ["Launch Progress", readiness.publishingProgress],
60-
+ ["Current Focus", readiness.currentFocus],
61-
+ ["Recommended Next Tool", readiness.recommendedNextTool],
62-
+ ].forEach(([label, value]) => {
63-
+ const row = document.createElement("tr");
64-
+ row.append(createCell(label, "th"), createCell(value || "Not available"));
65-
+ row.firstElementChild.scope = "row";
66-
+ body.append(row);
67-
+ });
9+
+test("Game Hub validates Open Games parent and child tables", async ({ page }) => {
10+
+ const sourceLinkedGame = {
11+
+ id: "lantern-reef",
12+
+ ownerKey: MOCK_DB_KEYS.users.user1,
13+
+ name: "Lantern Reef",
14+
+ purpose: "Game",
15+
+ status: "Ready for Testing",
16+
+ ownerDisplayName: "User 1",
17+
+ members: [
18+
+ {
19+
+ displayName: "User 1",
20+
+ gameId: "lantern-reef",
21+
+ permission: "Owner",
22+
+ role: "Owner",
23+
+ userKey: MOCK_DB_KEYS.users.user1,
24+
+ },
25+
+ ],
26+
+ sourceIdea: {
27+
+ idea: "Lantern Reef",
28+
+ pitch: "Guide reef keepers through dusk storms.",
29+
+ notes: [
30+
+ "Keep traversal gentle.",
31+
+ "Use warm lantern art.",
32+
+ ],
33+
+ },
34+
+ };
35+
+ const journeyBuckets = [
36+
+ "Idea",
37+
+ "Design",
38+
+ "Graphics",
39+
+ "Audio",
40+
+ "Objects",
41+
+ "Worlds",
42+
+ "Interface",
43+
+ "Controls",
44+
+ "Rules",
45+
+ "Progression",
46+
+ "Play Test",
47+
+ "Publish",
48+
+ "Share",
49+
+ ];
6850
+
69-
+ const checklist = Array.isArray(readiness.progressChecklist) ? readiness.progressChecklist : [];
70-
+ checklist.forEach((item) => {
71-
+ const row = document.createElement("tr");
72-
+ row.dataset.readinessChecklistRow = item.label || "Checklist";
73-
+ row.append(createCell(item.label || "Checklist", "th"), createCell(item.status || "Not available"));
74-
+ row.firstElementChild.scope = "row";
75-
+ body.append(row);
51+
+ await page.route("**/api/toolbox/game-hub/repositories/*/methods/getActiveGame", async (route) => {
52+
+ await route.fulfill({
53+
+ body: JSON.stringify({
54+
+ data: { result: sourceLinkedGame },
55+
+ ok: true,
56+
+ rule: "Browser -> Server API -> Data Source",
57+
+ }),
58+
+ contentType: "application/json; charset=utf-8",
59+
+ status: 200,
60+
+ });
61+
+ });
62+
+ await page.route("**/api/toolbox/game-hub/repositories/*/methods/getGameProgress", async (route) => {
63+
+ await route.fulfill({
64+
+ body: JSON.stringify({
65+
+ data: {
66+
+ result: {
67+
+ currentFocus: "Review source idea context",
68+
+ gameProgress: "Lantern Reef identity ready",
69+
+ gameStatus: "Ready for Testing",
70+
+ publishingProgress: "Launch review pending",
71+
+ recommendedNextTool: "Game Journey",
72+
+ progressChecklist: journeyBuckets.map((label) => ({ label, status: "Planned" })),
73+
+ },
74+
+ },
75+
+ ok: true,
76+
+ rule: "Browser -> Server API -> Data Source",
77+
+ }),
78+
+ contentType: "application/json; charset=utf-8",
79+
+ status: 200,
80+
+ });
7681
+ });
82+
+ await page.route("**/api/toolbox/game-hub/repositories/*/methods/listGames", async (route) => {
83+
+ await route.fulfill({
84+
+ body: JSON.stringify({
85+
+ data: { result: [sourceLinkedGame] },
86+
+ ok: true,
87+
+ rule: "Browser -> Server API -> Data Source",
88+
+ }),
89+
+ contentType: "application/json; charset=utf-8",
90+
+ status: 200,
91+
+ });
92+
+ });
93+
+ const failures = await openRepoPage(page, "/toolbox/game-hub/index.html", { session: creatorSession() });
7794
+
78-
+ table.append(body);
79-
+ wrapper.append(table);
80-
+ parent.append(wrapper);
81-
+}
95+
+ try {
96+
+ await expect(page.locator("[data-game-parent-table='open-games'] caption")).toHaveText("Open Games");
97+
+ const parentRows = page.locator("[data-game-parent-table='open-games'] tbody > [data-game-row]");
98+
+ await expect(parentRows).toHaveCount(1);
99+
+ const gameRow = page.locator("[data-game-row='lantern-reef']");
100+
+ await expect(gameRow).toContainText("Lantern Reef");
101+
+ await expect(gameRow.locator("[data-game-toggle='lantern-reef']")).toHaveAttribute("aria-expanded", "false");
82102
+
83-
+function renderExpandedGameRow(tbody, game, progress, active) {
84-
const row = document.createElement("tr");
85-
row.dataset.gameExpandedRow = game.id;
86-
row.id = `game-child-${game.id}`;
87-
@@ -329,16 +375,18 @@ function renderExpandedGameRow(tbody, game) {
88-
stack.className = "content-stack content-stack--compact";
89-
renderGameSummaryChildTable(stack, game);
90-
renderSourceIdeaChildTable(stack, game);
91-
+ renderReadinessOutputChildTable(stack, game, progress, active);
92-
content.append(stack);
93-
row.append(content);
94-
tbody.append(row);
95-
}
96-
97-
-function renderGameParentRow(tbody, game, activeGame) {
98-
+function renderGameParentRow(tbody, game, activeGame, progress) {
99-
const expanded = state.expandedGameId === game.id;
100-
+ const active = activeGame?.id === game.id;
101-
const row = document.createElement("tr");
102-
row.dataset.gameRow = game.id;
103-
- if (activeGame?.id === game.id) {
104-
+ if (active) {
105-
row.dataset.gameActive = "true";
106-
}
107-
108-
@@ -353,12 +401,12 @@ function renderGameParentRow(tbody, game, activeGame) {
109-
);
110-
111-
const actions = document.createElement("td");
112-
- actions.append(createGameButton(game, activeGame?.id === game.id));
113-
+ actions.append(createGameButton(game, active));
114-
row.append(actions);
115-
tbody.append(row);
116-
117-
if (expanded) {
118-
- renderExpandedGameRow(tbody, game);
119-
+ renderExpandedGameRow(tbody, game, progress, active);
120-
}
121-
}
122-
123-
@@ -389,7 +437,7 @@ function renderProjectInformation(activeGame, currentMember, progress) {
124-
: "Project Information loaded. Sign in to save changes.");
125-
}
126-
127-
-function renderGameList() {
128-
+function renderGameList(progress) {
129-
if (!elements.gameList) {
130-
return;
131-
}
132-
@@ -421,7 +469,7 @@ function renderGameList() {
133-
table.setAttribute("aria-label", "Open Games");
134-
table.innerHTML = "<caption>Open Games</caption><thead><tr><th scope=\"col\">Game</th><th scope=\"col\">Purpose</th><th scope=\"col\">Status</th><th scope=\"col\">Owner</th><th scope=\"col\">Actions</th></tr></thead>";
135-
const body = document.createElement("tbody");
136-
- listResult.forEach((game) => renderGameParentRow(body, game, activeGame));
137-
+ listResult.forEach((game) => renderGameParentRow(body, game, activeGame, progress));
138-
table.append(body);
139-
wrapper.append(table);
140-
elements.gameList.append(wrapper);
141-
@@ -558,7 +606,7 @@ function renderWorkspace() {
142-
}
143-
}
144-
145-
- renderGameList();
146-
+ renderGameList(progress);
147-
renderMembersTable(activeGame);
148-
renderTableCounts();
149-
renderChecklist(progress);
103+
+ await gameRow.locator("[data-game-toggle='lantern-reef']").click();
104+
+ await expect(gameRow.locator("[data-game-toggle='lantern-reef']")).toHaveAttribute("aria-expanded", "true");
105+
+ const expandedRow = page.locator("[data-game-expanded-row='lantern-reef']");
106+
+ await expect(expandedRow).toHaveCount(1);
107+
+ await expect(expandedRow.locator("[data-game-child-table]")).toHaveCount(3);
108+
+
109+
+ const sourceIdeaTable = expandedRow.locator("[data-game-child-table='source-idea']");
110+
+ await expect(sourceIdeaTable.locator("caption")).toHaveText("Source Idea");
111+
+ await expect(sourceIdeaTable.locator("tbody tr")).toHaveText([
112+
+ "IdeaLantern Reef",
113+
+ "PitchGuide reef keepers through dusk storms.",
114+
+ "Note 1Keep traversal gentle.",
115+
+ "Note 2Use warm lantern art.",
116+
+ ]);
117+
+ await expect(sourceIdeaTable.locator("button, input, textarea, select, [contenteditable='true'], [role='button']")).toHaveCount(0);
118+
+ await expect(sourceIdeaTable).not.toContainText(/Edit|Delete|Current Focus|Recommended Next Tool/);
119+
+
120+
+ const readinessOutputTable = expandedRow.locator("[data-game-child-table='readiness-output']");
121+
+ await expect(readinessOutputTable.locator("caption")).toHaveText("Readiness Output");
122+
+ await expect(readinessOutputTable.locator("thead th")).toHaveText(["Output", "Status"]);
123+
+ await expect(readinessOutputTable).not.toContainText(/Guide reef keepers|Keep traversal gentle|Use warm lantern art/);
124+
+ await expect(readinessOutputTable.locator("[data-readiness-checklist-row] th")).toHaveText(journeyBuckets);
125+
+
126+
+ await expect(page.locator("[data-game-list] [data-game-list-status='empty']")).toHaveCount(0);
127+
+ await expect(page.locator("[data-game-list] [data-game-list-status='unavailable']")).toHaveCount(0);
128+
+ await expectNoPageFailures(failures);
129+
+ } finally {
130+
+ await failures.server.close();
131+
+ }
132+
+});
133+
+
134+
test("Game Hub preserves guest browsing and blocks guest saves", async ({ page }) => {
135+
const failures = await openRepoPage(page, "/toolbox/game-hub/index.html");

0 commit comments

Comments
 (0)