clone

a browser grid puzzle game where you push words around to rewrite the rules of each level

Honor external scene requests during menu exit

Dami committed 136662e
commit
136662e7c14af7d6fe427071d4366c1037ae4422
parent
a1e71dd425a2a7e13a1bfd13d720cfe14bf394f3
author
Dami <dami.etoile@gmail.com>
3 files changed, +74 -1
Mopenspec/changes/animate-stage-motion-language/specs/game-stage/spec.md4++
Msrc/stage.js8+++-
Atests/browser/import-transition-race.mjs63++++++++++++++++++++++++++++++
M openspec/changes/animate-stage-motion-language/specs/game-stage/spec.md +4 -0
M src/stage.js +7 -1
A tests/browser/import-transition-race.mjs +63 -0
@@ -0,0 +1,63 @@
1+#!/usr/bin/env node2+3+/**4+ * Regression check for a valid local import arriving during an invalid import's5+ * INDEX -> Workshop error transition.6+ *7+ * Usage:8+ * PLAYWRIGHT_MODULE=/path/to/playwright node tests/browser/import-transition-race.mjs9+ * MIAU_URL=http://127.0.0.1:5192/ ... node tests/browser/import-transition-race.mjs10+ */11+12+import assert from 'node:assert/strict';13+import { mkdtemp, rm, writeFile } from 'node:fs/promises';14+import { tmpdir } from 'node:os';15+import { resolve } from 'node:path';16+17+const configuredPlaywright = process.env.PLAYWRIGHT_MODULE || 'playwright';18+const playwrightModule = configuredPlaywright.startsWith('/')19+ ? `${configuredPlaywright.replace(/\/$/, '')}${configuredPlaywright.endsWith('.js') ? '' : '/index.js'}`20+ : configuredPlaywright;21+const playwright = await import(playwrightModule);22+const { chromium } = playwright.default || playwright;23+24+const projectRoot = resolve(new URL('..', import.meta.url).pathname, '..');25+const baseUrl = process.env.MIAU_URL || 'http://127.0.0.1:5173/';26+const timeout = Number(process.env.BROWSER_TIMEOUT || 8000);27+const tempDir = await mkdtemp(`${tmpdir()}/miau-import-transition-`);28+const invalidPath = resolve(tempDir, 'invalid.cjs');29+const validPath = resolve(projectRoot, 'mods/example.cjs');30+await writeFile(invalidPath, 'module.exports = null;\n');31+32+const browser = await chromium.launch({ headless: true });33+const context = await browser.newContext({ viewport: { width: 1280, height: 800 } });34+const page = await context.newPage();35+const errors = [];36+page.on('console', message => {37+ if (message.type() === 'error') errors.push(`console: ${message.text()}`);38+});39+page.on('pageerror', error => errors.push(`page: ${error.message}`));40+41+try {42+ await page.goto(`${baseUrl.replace(/\/$/, '')}/?dev=1`, { waitUntil: 'networkidle' });43+ const scene = () => page.locator('.stage-shell').getAttribute('data-scene');44+ await page.keyboard.press('Enter');45+ await page.waitForFunction(() => document.querySelector('.stage-shell')?.dataset.scene === 'index', null, { timeout });46+ await page.waitForTimeout(500);47+48+ const input = page.locator('#mod-input');49+ await input.setInputFiles(invalidPath);50+ // Deliberately dispatch the valid import before the invalid error transition51+ // has finished. The valid pack must become the pending destination.52+ await input.setInputFiles(validPath);53+ await page.waitForFunction(() => document.querySelector('.stage-shell')?.dataset.scene === 'gift', null, { timeout });54+55+ assert.equal(await scene(), 'gift', 'valid import should win over the pending Workshop error transition');56+ assert.match(await page.locator('#gift-title, body').first().textContent(), /Moon Cat|Gift/i);57+ assert.deepEqual(errors, [], 'import transition should not emit browser errors');58+ console.log('✓ invalid → valid local import resolves to Gift with no browser errors');59+} finally {60+ await context.close();61+ await browser.close();62+ await rm(tempDir, { recursive: true, force: true });63+}