|
1 | 1 | import { expect, test } from "@playwright/test"; |
2 | 2 | import { startRepoServer } from "../helpers/playwrightRepoServer.mjs"; |
3 | 3 |
|
| 4 | +async function installFakeRepoPicker(page, { validSampleIds = ["0102"], invalidSampleIds = [] } = {}) { |
| 5 | + await page.addInitScript(({ validSampleIds: validIds, invalidSampleIds: invalidIds }) => { |
| 6 | + class FakeFileHandle { |
| 7 | + constructor(name, path, contents = "") { |
| 8 | + this.kind = "file"; |
| 9 | + this.name = name; |
| 10 | + this.path = path; |
| 11 | + this.contents = contents; |
| 12 | + } |
| 13 | + |
| 14 | + async getFile() { |
| 15 | + const handle = this; |
| 16 | + return { |
| 17 | + async text() { |
| 18 | + return handle.contents; |
| 19 | + } |
| 20 | + }; |
| 21 | + } |
| 22 | + |
| 23 | + async createWritable() { |
| 24 | + const handle = this; |
| 25 | + return { |
| 26 | + async write(contents) { |
| 27 | + handle.contents = String(contents); |
| 28 | + window.__previewGeneratorV2Writes.push({ path: handle.path, contents: handle.contents }); |
| 29 | + }, |
| 30 | + async close() {} |
| 31 | + }; |
| 32 | + } |
| 33 | + } |
| 34 | + |
| 35 | + class FakeDirectoryHandle { |
| 36 | + constructor(name, path = name) { |
| 37 | + this.kind = "directory"; |
| 38 | + this.name = name; |
| 39 | + this.path = path; |
| 40 | + this.children = new Map(); |
| 41 | + } |
| 42 | + |
| 43 | + addDirectory(name) { |
| 44 | + const directory = new FakeDirectoryHandle(name, `${this.path}/${name}`); |
| 45 | + this.children.set(name, directory); |
| 46 | + return directory; |
| 47 | + } |
| 48 | + |
| 49 | + addFile(name, contents = "") { |
| 50 | + const file = new FakeFileHandle(name, `${this.path}/${name}`, contents); |
| 51 | + this.children.set(name, file); |
| 52 | + return file; |
| 53 | + } |
| 54 | + |
| 55 | + async getDirectoryHandle(name, options = {}) { |
| 56 | + const child = this.children.get(name); |
| 57 | + if (child?.kind === "directory") { |
| 58 | + return child; |
| 59 | + } |
| 60 | + if (options.create) { |
| 61 | + return this.addDirectory(name); |
| 62 | + } |
| 63 | + throw new Error(`Missing directory: ${this.path}/${name}`); |
| 64 | + } |
| 65 | + |
| 66 | + async getFileHandle(name, options = {}) { |
| 67 | + const child = this.children.get(name); |
| 68 | + if (child?.kind === "file") { |
| 69 | + return child; |
| 70 | + } |
| 71 | + if (options.create) { |
| 72 | + return this.addFile(name); |
| 73 | + } |
| 74 | + throw new Error(`Missing file: ${this.path}/${name}`); |
| 75 | + } |
| 76 | + |
| 77 | + async *entries() { |
| 78 | + for (const entry of this.children.entries()) { |
| 79 | + yield entry; |
| 80 | + } |
| 81 | + } |
| 82 | + } |
| 83 | + |
| 84 | + const repo = new FakeDirectoryHandle("HTML-JavaScript-Gaming"); |
| 85 | + const samples = repo.addDirectory("samples"); |
| 86 | + const phase01 = samples.addDirectory("phase-01"); |
| 87 | + repo.addDirectory("games"); |
| 88 | + repo.addDirectory("tools"); |
| 89 | + |
| 90 | + for (const sampleId of validIds) { |
| 91 | + phase01.addDirectory(sampleId).addFile("index.html", "<!doctype html><canvas></canvas>"); |
| 92 | + } |
| 93 | + for (const sampleId of invalidIds) { |
| 94 | + phase01.addDirectory(sampleId); |
| 95 | + } |
| 96 | + phase01.addFile("0199", "not a sample folder"); |
| 97 | + |
| 98 | + window.__previewGeneratorV2Writes = []; |
| 99 | + window.showDirectoryPicker = async () => repo; |
| 100 | + }, { validSampleIds, invalidSampleIds }); |
| 101 | +} |
| 102 | + |
| 103 | +async function openPreviewGenerator(page, { withFakeRepo = false, validSampleIds, invalidSampleIds } = {}) { |
| 104 | + const server = await startRepoServer(); |
| 105 | + if (withFakeRepo) { |
| 106 | + await installFakeRepoPicker(page, { validSampleIds, invalidSampleIds }); |
| 107 | + } |
| 108 | + await page.goto(`${server.baseUrl}/tools/preview-generator-v2/index.html`, { waitUntil: "domcontentloaded" }); |
| 109 | + return server; |
| 110 | +} |
| 111 | + |
| 112 | +async function expectAccordionToggles(page, contentId) { |
| 113 | + const header = page.locator(`.accordion-v2__header[aria-controls="${contentId}"]`); |
| 114 | + const content = page.locator(`#${contentId}`); |
| 115 | + await expect(header).toBeVisible(); |
| 116 | + await expect(content).toBeVisible(); |
| 117 | + |
| 118 | + await header.click(); |
| 119 | + await expect(content).toBeHidden(); |
| 120 | + await expect(header).toHaveAttribute("aria-expanded", "false"); |
| 121 | + |
| 122 | + await header.click(); |
| 123 | + await expect(content).toBeVisible(); |
| 124 | + await expect(header).toHaveAttribute("aria-expanded", "true"); |
| 125 | +} |
| 126 | + |
4 | 127 | test.describe("Preview Generator V2 baseline", () => { |
5 | 128 | test("launches the tool shell and toggles a working accordion", async ({ page }) => { |
6 | | - const server = await startRepoServer(); |
| 129 | + const server = await openPreviewGenerator(page); |
7 | 130 | const pageErrors = []; |
8 | 131 |
|
9 | 132 | page.on("pageerror", (error) => { |
10 | 133 | pageErrors.push(error.message); |
11 | 134 | }); |
12 | 135 |
|
13 | 136 | try { |
14 | | - await page.goto(`${server.baseUrl}/tools/preview-generator-v2/index.html`, { waitUntil: "domcontentloaded" }); |
15 | | - |
16 | 137 | await expect(page.locator(".preview-generator-v2.app-shell")).toBeVisible(); |
17 | 138 | await expect(page.locator('link[href="../common/toolShellCommon.css"]')).toHaveCount(1); |
18 | 139 | await expect(page.locator("h1", { hasText: "Preview Generator V2" })).toBeVisible(); |
@@ -79,4 +200,110 @@ test.describe("Preview Generator V2 baseline", () => { |
79 | 200 | await server.close(); |
80 | 201 | } |
81 | 202 | }); |
| 203 | + |
| 204 | + test("exercises controls, required-field gating, accordions, paths layout, and status clear", async ({ page }) => { |
| 205 | + const server = await openPreviewGenerator(page, { withFakeRepo: true }); |
| 206 | + const pageErrors = []; |
| 207 | + |
| 208 | + page.on("pageerror", (error) => { |
| 209 | + pageErrors.push(error.message); |
| 210 | + }); |
| 211 | + |
| 212 | + try { |
| 213 | + await expect(page.locator("#targetTypeGames")).toBeChecked(); |
| 214 | + await expect(page.locator("#executeBtn")).toBeDisabled(); |
| 215 | + |
| 216 | + await page.locator("#sampleList").fill("my-game"); |
| 217 | + await expect(page.locator("#sampleList")).toHaveValue("my-game"); |
| 218 | + await expect(page.locator("#executeBtn")).toBeDisabled(); |
| 219 | + |
| 220 | + const pathsLayout = await page.locator("#sampleList").evaluate((input) => { |
| 221 | + const content = document.getElementById("pathsOrIdsContent"); |
| 222 | + const inputRect = input.getBoundingClientRect(); |
| 223 | + const contentRect = content.getBoundingClientRect(); |
| 224 | + const inputStyle = getComputedStyle(input); |
| 225 | + const contentStyle = getComputedStyle(content); |
| 226 | + return { |
| 227 | + inputHeight: inputRect.height, |
| 228 | + contentHeight: contentRect.height, |
| 229 | + inputWidth: inputRect.width, |
| 230 | + contentWidth: contentRect.width, |
| 231 | + display: inputStyle.display, |
| 232 | + height: inputStyle.height, |
| 233 | + overflow: inputStyle.overflow, |
| 234 | + resize: inputStyle.resize, |
| 235 | + contentDisplay: contentStyle.display |
| 236 | + }; |
| 237 | + }); |
| 238 | + expect(pathsLayout.display).not.toBe("none"); |
| 239 | + expect(pathsLayout.contentDisplay).toBe("flex"); |
| 240 | + expect(pathsLayout.inputHeight).toBeGreaterThan(0); |
| 241 | + expect(pathsLayout.height).not.toBe("auto"); |
| 242 | + expect(pathsLayout.overflow).toBe("auto"); |
| 243 | + expect(pathsLayout.resize).toBe("none"); |
| 244 | + expect(pathsLayout.contentHeight - pathsLayout.inputHeight).toBeLessThan(40); |
| 245 | + expect(pathsLayout.contentWidth - pathsLayout.inputWidth).toBeLessThan(40); |
| 246 | + |
| 247 | + await page.locator("#pickRepoBtn").click(); |
| 248 | + await expect(page.locator("#repoSelectedValue")).toHaveText("HTML-JavaScript-Gaming"); |
| 249 | + await expect(page.locator("#executeBtn")).toBeEnabled(); |
| 250 | + |
| 251 | + await page.locator("#clearLogBtn").click(); |
| 252 | + await expect(page.locator("#log")).toHaveText(""); |
| 253 | + |
| 254 | + for (const contentId of [ |
| 255 | + "repoDestinationContent", |
| 256 | + "targetSourceContent", |
| 257 | + "assetFolderContent", |
| 258 | + "captureModeContent", |
| 259 | + "renderControlsContent", |
| 260 | + "outputSummaryContent" |
| 261 | + ]) { |
| 262 | + await expectAccordionToggles(page, contentId); |
| 263 | + } |
| 264 | + |
| 265 | + expect(pageErrors).toEqual([]); |
| 266 | + } finally { |
| 267 | + await server.close(); |
| 268 | + } |
| 269 | + }); |
| 270 | + |
| 271 | + test("phase folder input enumerates existing sample folders only", async ({ page }) => { |
| 272 | + const server = await openPreviewGenerator(page, { |
| 273 | + withFakeRepo: true, |
| 274 | + validSampleIds: ["0102"], |
| 275 | + invalidSampleIds: ["0101", "0103"] |
| 276 | + }); |
| 277 | + const pageErrors = []; |
| 278 | + |
| 279 | + page.on("pageerror", (error) => { |
| 280 | + pageErrors.push(error.message); |
| 281 | + }); |
| 282 | + |
| 283 | + try { |
| 284 | + await page.locator("#pickRepoBtn").click(); |
| 285 | + await page.locator("#targetTypeSamples").check(); |
| 286 | + await page.locator("#sampleList").fill("samples/phase-01"); |
| 287 | + |
| 288 | + await expect(page.locator("#writeFolderActualValue")).toHaveText("samples\\phase-01\\0102\\assets\\images"); |
| 289 | + await expect(page.locator("#executeBtn")).toBeEnabled(); |
| 290 | + |
| 291 | + await page.locator("#executeBtn").click(); |
| 292 | + await expect(page.locator("#log")).toContainText("RUN 0102", { timeout: 10000 }); |
| 293 | + await expect(page.locator("#log")).toContainText("Done.", { timeout: 10000 }); |
| 294 | + |
| 295 | + const logText = await page.locator("#log").textContent(); |
| 296 | + expect(logText).not.toContain("RUN 0101"); |
| 297 | + expect(logText).not.toContain("RUN 0103"); |
| 298 | + expect(logText).not.toContain("RUN 0199"); |
| 299 | + |
| 300 | + const writes = await page.evaluate(() => window.__previewGeneratorV2Writes); |
| 301 | + expect(writes).toHaveLength(1); |
| 302 | + expect(writes[0].path).toBe("HTML-JavaScript-Gaming/samples/phase-01/0102/assets/images/preview.svg"); |
| 303 | + |
| 304 | + expect(pageErrors).toEqual([]); |
| 305 | + } finally { |
| 306 | + await server.close(); |
| 307 | + } |
| 308 | + }); |
82 | 309 | }); |
0 commit comments