Back to Napcraft
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.

tests/responsive.mjs
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();
}