SOURCE / PINNED RELEASE
Made of little things.
Pursuit PIT
- Release
- 565f4981bdc3…
- Author-recorded commit
- 197afdbed44b…
- License
- LICENSE
- Author’s source reference
- nostr://npub12lm3rxsva8kfempyjkxupt09e02n3fezurkyadyqfejdc5e0r2kscqty6r/wss%3A%2F%2Fgit.napplet.soy%2F/n-951414e7fd7
Archive hash verified: 3b0485589942fd5c…. The source-to-build association is the author’s claim; it has not been independently rebuilt.
// Run against the URL printed by soyli dev: pnpm test:hosted http://127.0.0.1:PORT/
// Uses Playwright already installed by the reference conformance CLI.
import assert from 'node:assert/strict';
import { createRequire } from 'node:module';
import { execFileSync } from 'node:child_process';
import { mkdir } from 'node:fs/promises';
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('Supply the URL printed by soyli dev.');
const paths = JSON.parse(execFileSync('soyli', ['browser', 'path', '--json'], { encoding: 'utf8' }));
const executablePath = paths.executables.find((p) => p.installed && p.name.includes('chromium'))?.path;
const browser = await chromium.launch({ executablePath, args: ['--no-sandbox'] });
const errors = [];
const page = await browser.newPage({ viewport: { width: 1400, height: 1100 } });
page.on('pageerror', (e) => errors.push(e.message));
const output = '.napplet-space/gamepad-tests';
await mkdir(output, { recursive: true });
try {
await page.goto(url);
const iframe = page.locator('#stage iframe');
await iframe.waitFor();
await iframe.evaluate((el) => { el.style.cssText = 'width:900px;height:600px;min-height:0;flex:none'; });
const frame = await (await iframe.elementHandle()).contentFrame();
await frame.locator('#modePursuit').waitFor();
assert.equal(await iframe.getAttribute('sandbox'), 'allow-scripts');
const domains = await frame.evaluate(() => Object.keys(window.napplet ?? {}));
assert.ok(domains.includes('storage'), 'target shell injected storage');
console.log('Injected domains:', domains.join(', '));
await page.evaluate(() => {
window.__gamepadWire = [];
window.addEventListener('message', (event) => {
if (typeof event.data?.type === 'string') window.__gamepadWire.push(event.data.type);
});
});
// Test-only synthetic input, confined to the napplet realm. This is not a hardware test.
await frame.evaluate(() => {
window.__gamepadResults = [];
window.addEventListener('message', (event) => {
if (event.data?.type === 'storage.set.result') window.__gamepadResults.push(event.data);
});
window.__pad = { index: 2, id: 'Synthetic standard', connected: true, mapping: 'standard', axes: [0, 0],
buttons: Array.from({ length: 16 }, () => ({ value: 0, pressed: false })) };
Object.defineProperty(navigator, 'getGamepads', { configurable: true, value: () => window.__pad.connected ? [null, null, window.__pad] : [] });
});
await frame.locator('#title .route').click();
const settle = () => frame.evaluate(() => new Promise((resolve) => requestAnimationFrame(() => requestAnimationFrame(resolve))));
const press = async (button) => {
await frame.evaluate((i) => { window.__pad.buttons[i] = { value: 1, pressed: true }; }, button);
await settle();
await frame.evaluate((i) => { window.__pad.buttons[i] = { value: 0, pressed: false }; }, button);
await settle();
};
const visible = (selector) => frame.locator(selector).isVisible();
await settle(); await press(0);
assert.match(await frame.locator('#title .controller-status').innerText(), /connected/);
assert.equal(await visible('#timePicker'), false, 'join must not also confirm');
// Controller-only mode/time navigation and analog driving.
for (const mode of ['modePursuit', 'modeGetaway', 'modeFree']) {
await frame.locator(`#${mode}`).focus();
await press(0); assert.equal(await visible('#timePicker'), true);
await press(14); await press(15); await press(0);
assert.equal(await visible('#title'), false);
assert.equal(await visible('#overlay'), false);
await frame.evaluate(() => { window.__pad.buttons[7].value = 0.7; });
await frame.waitForFunction(() => document.querySelector('#clock').textContent !== '0:00', null, { timeout: 30000 });
console.log('Driving mode:', mode);
await frame.evaluate(() => { window.__pad.buttons[7].value = 0; });
await press(0); assert.equal(await frame.locator('#wipeButton').evaluate((b) => b.classList.contains('on')), true);
await press(0);
await press(9); assert.equal(await frame.locator('#overlayKicker').innerText(), 'PAUSED');
await press(1); assert.equal(await visible('#overlay'), false);
// Disconnect pauses. Rejoin consumes confirm; resume is a separate action.
await frame.evaluate(() => { window.__pad.connected = false; }); await settle();
assert.equal(await visible('#overlay'), true);
await frame.evaluate(() => { window.__pad.connected = true; }); await settle();
await press(0); assert.equal(await visible('#overlay'), true);
await press(9); assert.equal(await visible('#overlay'), false);
await press(9); await press(13); await press(0);
assert.equal(await visible('#title'), true);
}
// Garage edits use the existing real SDK storage operation.
await press(13); await press(13); await press(13); await press(0);
assert.equal(await visible('#garage'), true);
await press(15); await press(5); await press(13); await press(15);
assert.equal(await frame.evaluate(() => document.activeElement.getAttribute('aria-pressed')), 'true', 'garage focuses selected paint');
assert.ok((await page.evaluate(() => window.__gamepadWire)).includes('storage.set'), 'garage edit emits SDK storage.set');
await frame.waitForFunction(() => window.__gamepadResults.length > 0);
assert.ok(await frame.evaluate(() => window.__gamepadResults.some((result) => !result.error)), 'host accepts SDK storage operation');
await press(1); assert.equal(await visible('#title'), true);
// Help and back navigation.
await press(13); await press(13); await press(13); await press(13); await press(0);
assert.equal(await frame.locator('details.controls').getAttribute('open'), '');
await press(1); assert.equal(await frame.locator('details.controls').getAttribute('open'), null);
await frame.locator('#modeFree').focus(); await press(0); await press(1);
assert.equal(await visible('#timePicker'), false);
// Frame dimensions are explicit test overrides; soyLI normally enforces 300px height.
for (const [width, height] of [[200, 160], [320, 560], [900, 600], [1400, 900]]) {
await iframe.evaluate((el, { width, height }) => {
el.style.cssText = `width:${width}px;height:${height}px;min-height:0;max-width:none;flex:none`;
}, { width, height });
await frame.waitForFunction(({ width, height }) => innerWidth === width && innerHeight === height, { width, height });
await frame.locator('#modePursuit').focus();
await frame.locator('#modePursuit').evaluate((el) => el.scrollIntoView({ block: 'nearest' }));
await iframe.screenshot({ path: `${output}/title-${width}x${height}.png` });
await press(0); await press(15);
await iframe.screenshot({ path: `${output}/time-${width}x${height}.png` });
await press(1);
}
// App-owned palette does not change when host appearance changes or theme is absent.
const palette = () => frame.evaluate(() => getComputedStyle(document.documentElement).getPropertyValue('--amber'));
const before = await palette();
for (const theme of ['dark', 'light']) {
await page.evaluate((value) => { document.documentElement.dataset.theme = value; }, theme);
assert.equal(await palette(), before);
}
await frame.evaluate(() => { delete window.napplet.theme; delete window.napplet.storage; });
await frame.locator('#titleGarage').click(); await press(15); await press(1);
assert.equal(await visible('#title'), true);
// Missing native API produces a useful fallback, without stopping the frame loop.
await frame.evaluate(() => Object.defineProperty(navigator, 'getGamepads', { configurable: true, value: undefined }));
await settle();
assert.match(await frame.locator('#title .controller-status').innerText(), /unavailable/);
assert.deepEqual(errors, []);
console.log('PASS: synthetic controller flows, real SDK storage operation, four iframe sizes, theme fallback.');
console.log('Screenshots:', output);
} finally {
await browser.close();
}
