- commit
- f82ad3dccd56d4d91c8fae52afd15801b6cb3b6b
- parent
- 30170c8577377fe728e4b1b6085f343276769d55
- author
- Dami <dami.etoile@gmail.com>
| M | tests/ | 64 | ++++++++++++++++++++++++++---- |
@@ -172,6 +172,7 @@ async function importLocalModule(page, filePath, { install = true } = {}) {
172172 await page.waitForFunction(() => document.querySelector('.stage-shell')?.dataset.scene === 'gift');173173 await page.keyboard.press('Enter');174174 await page.waitForFunction(() => document.querySelector('.stage-shell')?.dataset.scene === 'index');175+ await dismissFirstWorldCard(page);175176 }176177 }177178 @@ -316,18 +317,28 @@ async function assertGiftActionsBelowPreview(page, label) {
316317 }317318 318319 async function openDevProof(page) {320+ await page.waitForFunction(() => document.querySelector('.stage-shell')?.dataset.scene === 'title');319321 await page.keyboard.press('Enter');320322 await page.waitForFunction(() => document.querySelector('.stage-shell')?.dataset.scene === 'index');323+ // First entry to every world presents a card before map actions are accepted.324+ await dismissFirstWorldCard(page);321325 await page.keyboard.press('Enter');322326 await page.waitForFunction(() => document.querySelector('.stage-shell')?.dataset.scene === 'proof');323327 }324328 329+async function dismissFirstWorldCard(page) {330+ await page.keyboard.press('Enter');331+ await page.waitForFunction(() => document.querySelector('.stage-shell')?.dataset.scene === 'index');332+}333+325334 const tempDir = await mkdtemp(`${tmpdir()}/miau-share-flow-`);326335 await mkdir(stageScreenshotDir, { recursive: true });327336 const firstPath = resolve(tempDir, 'browser-smoke-one.cjs');328337 const secondPath = resolve(tempDir, 'browser-smoke-two.cjs');329338 const invalidPath = resolve(tempDir, 'browser-smoke-invalid.cjs');330339 const selfWinPath = resolve(tempDir, 'browser-self-win.cjs');340+const gardenExamplePath = resolve(projectRoot, 'mods/garden-basics.cjs');341+const wordLabExamplePath = resolve(projectRoot, 'mods/word-lab.cjs');331342 await writeFile(firstPath, fixtureOne);332343 await writeFile(secondPath, fixtureTwo);333344 await writeFile(invalidPath, invalidFixture);@@ -360,6 +371,9 @@ try {
360371 report('title Space opens Workshop and Escape returns to title');361372 await stagePage.keyboard.press('Enter');362373 await stagePage.waitForFunction(() => document.querySelector('.stage-shell')?.dataset.scene === 'index');374+ await stagePage.waitForTimeout(700);375+ await stagePage.screenshot({ path: `${stageScreenshotDir}/d20-core-world-card.png` });376+ await dismissFirstWorldCard(stagePage);363377 await stagePage.screenshot({ path: `${stageScreenshotDir}/index.png` });364378 await stagePage.keyboard.press('Space');365379 await stagePage.waitForFunction(() => document.querySelector('.stage-shell')?.dataset.scene === 'workshop');@@ -373,6 +387,7 @@ try {
373387 await pointerPage.goto(playerUrl, { waitUntil: 'networkidle' });374388 await pointerPage.keyboard.press('Enter');375389 await pointerPage.waitForFunction(() => document.querySelector('.stage-shell')?.dataset.scene === 'index');390+ await dismissFirstWorldCard(pointerPage);376391 await pointerPage.waitForTimeout(900);377392 for (const [x, y] of [[1035, 760], [1035, 775], [1080, 760]]) {378393 await pointerPage.mouse.click(x, y);@@ -428,9 +443,29 @@ try {
428443 await openDevProof(page);429444 await page.locator('#level-title').waitFor();430445 const initialPackCount = await packCount(page);431- assert.ok(initialPackCount >= 1, 'the built-in catalog must expose at least one pack');432- assert.ok(await page.locator('#level-list [data-level], #level-list .level-choice').count() >= 3, 'the built-in campaign must remain playable');433- report(`built-in catalog loaded (${initialPackCount} pack choice${initialPackCount === 1 ? '' : 's'})`);446+ assert.equal(initialPackCount, 4, 'the bundled catalog should contain core and three approved worlds');447+ const bundledPackIds = await page.locator('#pack-list [data-pack-id]').evaluateAll(nodes => nodes.map(node => node.dataset.packId));448+ assert.deepEqual(bundledPackIds, ['core', 'crossword', 'crate-works', 'tidewater'], 'bundled worlds should keep the approved order');449+ for (const [packId, minimumLevels] of [['core', 3], ['crossword', 7], ['crate-works', 7], ['tidewater', 7]]) {450+ assert.ok(await page.locator(`[data-level-id^="${packId}:"]`).count() >= minimumLevels, `${packId} should expose its authored world levels`);451+ }452+ await page.screenshot({ path: `${stageScreenshotDir}/d20-bundled-world-catalog.png` });453+ report('bundled catalog order and per-world level cards are present');454+455+ const examplesContext = await browser.newContext();456+ const examplesPage = await examplesContext.newPage();457+ examplesPage.setDefaultTimeout(timeout);458+ await examplesPage.goto(devUrl, { waitUntil: 'networkidle' });459+ await openDevProof(examplesPage);460+ await importLocalModule(examplesPage, gardenExamplePath);461+ await examplesPage.waitForFunction(() => document.body.textContent.includes('Basic Proofs'));462+ await importLocalModule(examplesPage, wordLabExamplePath);463+ await examplesPage.waitForFunction(() => document.body.textContent.includes('Word Lab'));464+ assert.ok(await examplesPage.locator('[data-pack-id="garden-basics"]').count(), 'Garden Basics should remain importable as an example pack');465+ assert.ok(await examplesPage.locator('[data-pack-id="word-lab"]').count(), 'Word Lab should remain importable as an example pack');466+ await examplesPage.close();467+ await examplesContext.close();468+ report('Garden Basics and Word Lab remain importable examples');434469 435470 await importLocalModule(page, firstPath);436471 await page.waitForFunction(() => document.body.textContent.includes('Browser Smoke One'));@@ -509,7 +544,9 @@ try {
509544 await workshopPage.screenshot({ path: `${stageScreenshotDir}/workshop.png` });510545 assert.equal(await workshopPage.locator('.stage-shell').getAttribute('data-scene'), 'workshop', 'pause W should open Workshop');511546 report('Workshop opens from pause and renders its canvas scene');512- for (let index = 0; index < 4; index += 1) await workshopPage.keyboard.press('ArrowDown');547+ // Workshop rows begin with IMPORT, followed by the four bundled packs.548+ // Browser Smoke One is therefore the fifth down step from the import row.549+ for (let index = 0; index < 5; index += 1) await workshopPage.keyboard.press('ArrowDown');513550 for (let index = 0; index < 3; index += 1) await workshopPage.keyboard.press('ArrowRight');514551 await workshopPage.keyboard.press('Enter');515552 await workshopPage.screenshot({ path: `${stageScreenshotDir}/workshop-remove-confirm.png` });@@ -546,6 +583,7 @@ try {
546583 await returnFlowPage.waitForTimeout(700);547584 await returnFlowPage.keyboard.press('Enter');548585 await returnFlowPage.waitForFunction(() => document.querySelector('.stage-shell')?.dataset.scene === 'index');586+ await dismissFirstWorldCard(returnFlowPage);549587 await returnFlowPage.keyboard.press('Space');550588 await returnFlowPage.waitForFunction(() => document.querySelector('.stage-shell')?.dataset.scene === 'workshop');551589 await importLocalModule(returnFlowPage, secondPath, { install: false });@@ -666,6 +704,8 @@ try {
666704 await navigationPage.goto(devUrl, { waitUntil: 'networkidle' });667705 await openDevProof(navigationPage);668706 await navigationPage.locator('#level-title').waitFor();707+ await importLocalModule(navigationPage, wordLabExamplePath);708+ await navigationPage.waitForFunction(() => document.body.textContent.includes('Word Lab'));669709 await selectPack(navigationPage, 'word-lab');670710 await navigationPage.waitForFunction(() => document.querySelector('[data-pack-id="word-lab"]')?.classList.contains('active'));671711 const wordLabLevels = navigationPage.locator('[data-level-id^="word-lab:"]');@@ -732,6 +772,11 @@ try {
732772 await continuePage.waitForFunction(() => JSON.parse(window.render_game_to_text()).mode === 'won');733773 await continuePage.waitForFunction(() => document.querySelector('.stage-shell')?.dataset.scene === 'win');734774 const firstProof = JSON.parse(await continuePage.evaluate(() => window.render_game_to_text()));775+ await continuePage.waitForFunction(() => {776+ const button = document.querySelector('#continue-btn');777+ return button && !button.disabled;778+ });779+ await continuePage.waitForTimeout(700);735780 await continuePage.locator('#continue-btn').click();736781 await continuePage.waitForFunction(() => document.querySelector('.stage-shell')?.dataset.scene === 'proof');737782 const secondProof = JSON.parse(await continuePage.evaluate(() => window.render_game_to_text()));@@ -834,16 +879,19 @@ try {
834879 assert.equal(await accessibilityPage.locator('.stage-shell').getAttribute('data-scene'), 'title', 'title should accept a single movement key without leaving title');835880 await accessibilityPage.keyboard.press('Enter');836881 await accessibilityPage.waitForFunction(() => document.querySelector('.stage-shell')?.dataset.scene === 'index');882+ await dismissFirstWorldCard(accessibilityPage);837883 await accessibilityPage.screenshot({ path: `${stageScreenshotDir}/mobile-index.png` });838884 const indexCanvas = accessibilityPage.locator('.stage-canvas');839885 const indexBox = await indexCanvas.boundingBox();840886 assert.ok(indexBox, 'mobile INDEX should expose a tappable canvas');841- await accessibilityPage.mouse.click(indexBox.x + 270, indexBox.y + 820);842- await accessibilityPage.waitForFunction(() => document.querySelector('.stage-shell')?.dataset.scene === 'workshop');887+ // The compact mobile footer is the menu/workshop button at the lower-right888+ // of the canvas; click its center rather than the adjacent empty margin.889+ await accessibilityPage.mouse.click(indexBox.x + 320, indexBox.y + 826);890+ await accessibilityPage.waitForFunction(() => document.querySelector('.stage-shell')?.dataset.scene === 'pause');843891 await accessibilityPage.keyboard.press('Escape');844892 await accessibilityPage.waitForFunction(() => document.querySelector('.stage-shell')?.dataset.scene === 'index');845- report('mobile INDEX Workshop footer pointer opens Workshop and Escape returns');846- await accessibilityPage.mouse.click(indexBox.x + 64, indexBox.y + 405);893+ report('mobile INDEX menu footer pointer opens Pause and Escape returns');894+ await accessibilityPage.mouse.click(indexBox.x + 64, indexBox.y + 440);847895 await accessibilityPage.waitForFunction(() => document.querySelector('.stage-shell')?.dataset.scene === 'proof');848896 report('mobile reduced-motion title key and INDEX touch entry work without audio');849897 assert.deepEqual(accessibilityErrors, [], 'missing audio should not produce browser errors');