Files
deepgrain-foundry/test/t_dev_console.py
T

101 lines
5.6 KiB
Python

"""Dev console confirmation, keyboard isolation, HUD, time controls and saved badge."""
import pathlib, sys
sys.path.insert(0, str(pathlib.Path(__file__).resolve().parent.parent / 'tools'))
from use_venv import ensure_playwright; ensure_playwright()
from playwright.sync_api import sync_playwright
ROOT = pathlib.Path(__file__).resolve().parent.parent
SHOTS = ROOT / 'shots'
SHOTS.mkdir(exist_ok=True)
fails = []
def check(name, ok):
print(('PASS ' if ok else 'FAIL ') + name)
if not ok: fails.append(name)
with sync_playwright() as p:
browser = p.chromium.launch()
page = browser.new_page(viewport={'width': 1280, 'height': 760})
errors = []
page.on('pageerror', lambda e: errors.append(str(e)))
page.add_init_script("localStorage.setItem('deepgrain.settings', JSON.stringify({look:'pixel', sv:2}));")
page.goto((ROOT / 'dist/index.html').as_uri())
page.wait_for_function("!$('title').hidden")
page.evaluate("startNewWorld(424242, 512, 384, 'Console test')")
page.wait_for_function('GAME.running && !GAME.loading')
page.evaluate("document.querySelectorAll('.card, .toast').forEach(e => e.remove())")
page.keyboard.press('`')
check('backtick opens the first-use warning', page.is_visible('#m-dialog') and not page.is_visible('#dev-console'))
page.screenshot(path=str(SHOTS / 't66-warning.png'))
check('warning explains permanent marking and future achievements', 'permanently' in page.inner_text('#dlg-body') and 'achievements' in page.inner_text('#dlg-body'))
page.get_by_role('button', name='Cancel', exact=True).click()
check('cancel leaves the world unmarked and console closed', page.evaluate("!devWorld && $('dev-console').hidden && !GAME.paused"))
page.keyboard.press('`')
page.get_by_role('button', name='Enable dev console', exact=True).click()
page.wait_for_function("!$('dev-console').hidden && document.activeElement === $('dev-input')")
check('accepting marks the world and resumes play', page.evaluate('devWorld && !GAME.paused'))
check('build material chip stays stable across HUD refreshes', page.evaluate("""() => {
selectTool(2); renderSelCard();
const chip = $('selcard').querySelector('[data-wall-picker]');
renderSelCard();
const stable = chip === $('selcard').querySelector('[data-wall-picker]');
selectTool(0); return stable;
}"""))
old_money = page.evaluate('money')
page.fill('#dev-input', 'money 1000'); page.keyboard.press('Enter')
page.wait_for_function(f"money === {old_money + 1000} && $('wallet').textContent.includes(fmtMoney(money))")
check('money command changes the wallet HUD', True)
tool = page.evaluate('GAME.tool')
page.fill('#dev-input', ''); page.keyboard.type('1234wasd')
check('typing game keys does not switch tools or hold movement', page.evaluate(f'GAME.tool === {tool} && keys.size === 0'))
page.keyboard.press('Escape')
check('Escape closes console without opening pause menu', page.evaluate("$('dev-console').hidden && $('m-pause').hidden && !GAME.paused"))
page.keyboard.press('`')
check('marked worlds reopen without the warning', page.evaluate("!$('dev-console').hidden && $('m-dialog').hidden"))
page.keyboard.press('`')
check('backtick closes even with input focused', not page.is_visible('#dev-console'))
page.keyboard.press('`')
def command(text):
page.fill('#dev-input', text); page.keyboard.press('Enter')
command('pause')
tick = page.evaluate('tick')
page.wait_for_timeout(150)
check('dev pause freezes simulation ticks', page.evaluate('tick') == tick)
command('step 5')
page.wait_for_function(f'tick === {tick + 5} && devSteps === 0')
page.wait_for_timeout(150)
check('step advances exactly five ticks then stays paused', page.evaluate('tick') == tick + 5)
# Drive the actual frame accumulator with controlled elapsed time and no scheduled frames.
counts = page.evaluate('''() => {
requestAnimationFrame = () => 0;
GAME.paused = false; devPaused = false; SET.fpsCap = 0; SET.maxSteps = 3;
const counts = [];
for (const speed of [0.5, 1, 2, 8]) {
devSpeed = speed; acc = 0; const before = tick;
const t = performance.now(); lastT = t - 34; lastRender = t - 34; frame(t);
counts.push(tick - before);
}
return counts;
}''')
check('speed scales fixed ticks and respects maxSteps', counts == [1, 2, 3, 3])
page.evaluate('devCommand("debt clear"); runDevCommand("money 1"); openNotebook("Claims")')
page.wait_for_function("$('nb-body').textContent.includes('paid off')")
check('cleared debt is reflected in Claims', True)
page.evaluate('closeAllModals(); runDevCommand("debt 123"); openNotebook("Market")')
check('new debt is reflected in the Market', '$123' in page.inner_text('#nb-body'))
page.evaluate('closeAllModals()')
page.evaluate("async () => { await saveWorld('main', true); await renderWorlds(); }")
page.evaluate("openModal('m-worlds')")
check('world list entry stores and draws the dev badge', page.locator('#worlds-list .dev-badge').is_visible() and page.inner_text('#worlds-list .dev-badge') == 'Dev console')
page.screenshot(path=str(SHOTS / 't66-worlds.png'))
page.evaluate('closeAllModals()')
check('saving and loading preserves mark and log, resets runtime controls', page.evaluate('''async () => {
const sv = await worldNewest(GAME.worldId);
const count = devLog.length;
await startFromSave(sv);
return devWorld && devLog.length === count && devSpeed === 1 && !devPaused && !devFree && $('dev-console').hidden;
}'''))
check('no browser errors', not errors)
browser.close()
sys.exit(1 if fails else 0)