SOURCE / PINNED RELEASE
Made of little things.
Napcraft
- Release
- ee47ac6a25df…
- Author-recorded commit
- 410dea87e109…
- License
- LICENSE
- Author’s source reference
- nostr://npub182jczunncwe0jn6frpqwq3e0qjws7yqqnc3auccqv9nte2dnd63scjm4rf/wss%3A%2F%2Fgit.napplet.soy%2F/n-52f9e22f5ce
Archive hash verified: 9e6e0cabb18d0d46…. The source-to-build association is the author’s claim; it has not been independently rebuilt.
import { chromium, executable, frameOf, fitFrame } from './browser-support.mjs';
import { mkdir, writeFile } from 'node:fs/promises';
import assert from 'node:assert/strict';
const browser = await chromium.launch({
executablePath: executable,
headless: true,
});
const results = [];
try {
await mkdir('.napplet-space/promotion', { recursive: true });
const context = await browser.newContext({
hasTouch: true,
isMobile: true,
viewport: { width: 390, height: 844 },
deviceScaleFactor: 1,
});
const page = await context.newPage();
const errors = [];
page.on('pageerror', (e) => errors.push(e.message));
await page.goto(process.env.NAPCRAFT_URL || 'http://127.0.0.1:51696/');
let frame = await frameOf(page);
await frame.locator('#practice').tap();
for (const [width, height] of [
[200, 160],
[320, 560],
[900, 600],
[1440, 900],
[390, 844],
[360, 640],
[844, 390],
]) {
frame = await fitFrame(page, width, height);
await page.waitForTimeout(200);
if (width < 300 || height < 340) {
assert.equal(await frame.locator('.tiny').isVisible(), true);
} else {
for (const selector of [
'#place',
'#remove',
'.materials',
'[data-key=KeyW]',
]) {
const r = await frame.locator(selector).evaluate((e) => {
const b = e.getBoundingClientRect();
return {
x: b.x,
y: b.y,
right: b.right,
bottom: b.bottom,
w: innerWidth,
h: innerHeight,
};
});
assert.ok(
r.x >= 0 && r.y >= 0 && r.right <= r.w + 1 && r.bottom <= r.h + 1,
`${selector} clipped at ${width}x${height}`,
);
}
await frame.locator('#place').tap();
await frame.locator('#remove').tap();
}
await page.locator('#stage iframe').screenshot({
path: `.napplet-space/promotion/napcraft-${width}x${height}.png`,
});
results.push(
`${width} × ${height}: exact inner viewport, controls reachable, screenshot`,
);
console.log('PASS', results.at(-1));
}
frame = await fitFrame(page, 390, 844);
const before = await frame.locator('canvas').getAttribute('data-local-x');
const pad = frame.locator('[data-key=KeyD]');
const box = await pad.boundingBox();
const cdp = await context.newCDPSession(page);
await cdp.send('Input.dispatchTouchEvent', {
type: 'touchStart',
touchPoints: [{ x: box.x + 22, y: box.y + 22, id: 1 }],
});
await page.waitForTimeout(300);
const build = await frame.locator('#place').boundingBox();
await cdp.send('Input.dispatchTouchEvent', {
type: 'touchStart',
touchPoints: [
{ x: box.x + 22, y: box.y + 22, id: 1 },
{ x: build.x + build.width / 2, y: build.y + build.height / 2, id: 2 },
],
});
await cdp.send('Input.dispatchTouchEvent', {
type: 'touchEnd',
touchPoints: [],
});
await page.waitForTimeout(100);
assert.notEqual(
await frame.locator('canvas').getAttribute('data-local-x'),
before,
);
results.push(
'Real emulated touch D-pad movement and simultaneous build touch',
);
const stopped = await frame.locator('canvas').getAttribute('data-local-x');
await page.waitForTimeout(150);
assert.equal(
await frame.locator('canvas').getAttribute('data-local-x'),
stopped,
);
results.push('Touch release clears movement');
for (const scheme of ['light', 'dark']) {
await page.emulateMedia({ colorScheme: scheme });
const color = await frame
.locator('body')
.evaluate((e) => getComputedStyle(e).color);
assert.equal(color, 'rgb(70, 54, 86)');
}
results.push('App-owned palette unchanged in light and dark color schemes');
await frame.evaluate(() => {
delete window.napplet.theme;
delete window.napplet.storage;
delete window.napplet.identity;
});
await frame.locator('#place').tap();
await frame.locator('#remove').tap();
results.push(
'Optional theme/storage/identity absent: practice interactions work',
);
assert.deepEqual(errors, []);
await writeFile(
'.napplet-space/responsive-results.json',
JSON.stringify(results, null, 2),
);
console.log('PASS', results.slice(-4));
} finally {
await browser.close();
}
