SOURCE / PINNED RELEASE
Made of little things.
Rail Game
- Release
- ccdd5b913413…
- Author-recorded commit
- ddf0122b6acc…
- License
- LICENSE
- Author’s source reference
- nostr://npub1n8ga89w8h6tvwamxusfyzexw8gjy84yxu9rxgnmk955cxtml4ujswzxydd/wss%3A%2F%2Fgit.napplet.soy%2F/n-91ec90c6fdf
Archive hash verified: dad7b678e25f96a3…. The source-to-build association is the author’s claim; it has not been independently rebuilt.
// Run against soyli dev: node tests/browser-smoke.mjs http://127.0.0.1:PORT/
import assert from 'node:assert/strict';
import { mkdir, writeFile } from 'node:fs/promises';
import { createRequire } from 'node:module';
const require = createRequire(import.meta.url);
const { chromium } = createRequire(require.resolve('@napplet/conformance-cli/package.json'))('playwright');
const url = process.argv[2];
if (!url) throw new Error('Pass the running soyli dev URL.');
const output = '.napplet-space/qa'; await mkdir(output, { recursive: true });
const browser = await chromium.launch({ headless: true });
const errors = [], results = [];
const themes = { dark: {background:'#101d25',text:'#e9f2f2',primary:'#78edbb'}, light: {background:'#f8f6ed',text:'#29302c',primary:'#386e62'} };
const check = (name, details = {}) => { results.push({name,...details}); console.log('PASS', name, JSON.stringify(details)); };
async function mount(page) {
page.on('pageerror', e => errors.push(e.message));
await page.goto(url);
const frame = await (await page.locator('iframe').elementHandle()).contentFrame();
await frame.locator('#primaryButton').waitFor();
await page.locator('iframe').evaluate(f => Object.assign(f.style, { position:'fixed', inset:'0', width:'100vw', height:'100vh', minHeight:'0', minWidth:'0', border:'0', zIndex:'1000' }));
await page.waitForTimeout(100);
return frame;
}
async function theme(page, colors) {
await page.evaluate(colors => document.querySelector('iframe').contentWindow.postMessage({type:'theme.changed',theme:{colors}}, '*'), colors);
await page.waitForTimeout(60);
}
try {
const page = await browser.newPage({ viewport: {width:900,height:600} });
const f = await mount(page);
const domains = await f.evaluate(() => ['theme','config','storage'].filter(domain => Boolean(window.napplet?.[domain])));
check('real soyLI domain injection', {domains}); assert.equal(domains.length, 3);
for (const [width,height] of [[200,160],[240,280],[320,560],[900,600],[2400,1200]]) {
await page.setViewportSize({width,height});
for (const [name,colors] of Object.entries(themes)) {
await theme(page, colors);
const layout = await f.evaluate(() => {
const root = document.documentElement, button = document.getElementById('primaryButton').getBoundingClientRect();
const small = getComputedStyle(document.querySelector('.too-small')).display !== 'none';
return {width:innerWidth,height:innerHeight,overflow:root.scrollWidth > innerWidth || root.scrollHeight > innerHeight,
small, actionVisible:small || button.top >= 0 && button.bottom <= innerHeight && button.left >= 0 && button.right <= innerWidth,
backgrounds:['html','body','#app'].map(s => getComputedStyle(document.querySelector(s)).backgroundColor)};
});
assert.equal(layout.width, width); assert.equal(layout.height, height); assert.equal(layout.overflow, false); assert.equal(layout.actionVisible, true);
assert.equal(layout.small, width < 240 || height < 280); assert.equal(new Set(layout.backgrounds).size, 1);
await page.locator('iframe').screenshot({path:`${output}/${width}x${height}-${name}.png`});
check(`layout ${width}x${height} ${name}`, layout);
}
}
await page.setViewportSize({width:900,height:600}); await f.locator('#primaryButton').click();
await page.keyboard.down('ArrowLeft'); await page.waitForTimeout(350); await page.keyboard.up('ArrowLeft');
assert.match(await f.locator('canvas').getAttribute('aria-label'), /Position left/); check('keyboard steering');
await page.setViewportSize({width:200,height:160}); await page.waitForTimeout(100);
assert.equal(await f.locator('#app').getAttribute('data-phase'), 'paused');
await page.setViewportSize({width:900,height:600});
assert.equal(await f.locator('#app').getAttribute('data-phase'), 'paused');
await f.locator('#primaryButton').click();
await page.evaluate(() => document.querySelector('iframe').focus());
await f.evaluate(() => window.dispatchEvent(new Event('blur')));
assert.equal(await f.locator('#app').getAttribute('data-phase'), 'paused'); check('undersize and focus pause require explicit resume');
await page.close();
for (const [width,height] of [[390,844],[360,640],[844,390]]) {
const context = await browser.newContext({viewport:{width,height},hasTouch:true,isMobile:true,deviceScaleFactor:1});
const page = await context.newPage(), f = await mount(page), cdp = await context.newCDPSession(page);
await theme(page, themes.dark); await f.locator('#primaryButton').tap();
await cdp.send('Input.dispatchTouchEvent',{type:'touchStart',touchPoints:[{x:width*.45,y:height*.68}]});
await cdp.send('Input.dispatchTouchEvent',{type:'touchMove',touchPoints:[{x:width*.75,y:height*.68}]});
await page.waitForTimeout(550);
assert.match(await f.locator('canvas').getAttribute('aria-label'), /Position right/);
await cdp.send('Input.dispatchTouchEvent',{type:'touchCancel',touchPoints:[]});
const left = await f.locator('#leftButton').boundingBox();
assert.ok(left.width>=44 && left.height>=44);
await cdp.send('Input.dispatchTouchEvent',{type:'touchStart',touchPoints:[{x:left.x+left.width/2,y:left.y+left.height/2}]});
await page.waitForTimeout(900); await cdp.send('Input.dispatchTouchEvent',{type:'touchEnd',touchPoints:[]});
assert.match(await f.locator('canvas').getAttribute('aria-label'), /Position left/);
await page.waitForTimeout(250); assert.equal(await f.locator('#leftButton').getAttribute('data-held'),'false');
await page.locator('iframe').screenshot({path:`${output}/touch-${width}x${height}.png`});
await f.locator('#pauseButton').tap(); assert.equal(await f.locator('#app').getAttribute('data-phase'),'paused');
check(`touch drag, hold, cancel, pause ${width}x${height}`); await context.close();
}
assert.deepEqual(errors, []); check('no browser runtime errors');
await writeFile(`${output}/browser-results.json`, JSON.stringify(results,null,2));
} finally { await browser.close(); }
