Files
ark-stronghold-protocol/test/ui/pwa-install-023.test.js
T
sganggs f3658a0dd4 0.2.3: prepare follow-up fixes and client features (#407, #408, #410, #413, #431, #435, #436, #440, #444)
Add special-target activation without bypassing attack cadence, cooperative
card preferences and emotes, skill sound banks, per-operator voice choices,
text sizing, install prompts, closed-tab recovery and unanimous setup rerolls.
Retain late deployment elapsed time and resolve echo/counter pulses without
recursive hook overflow. Independent final review remains pending.

Golden: roster-016, roster-035, roster-036, roster-045;
boss-boss_1-pair, boss-boss_3-pair, boss-boss_5-pair, boss-boss_7-pair,
hidden-boss_9-pair; solo-HARD-9-boosted: Lemuen attack cadence.
diy-132: Necras extra activation targets.
coop2-NORMAL-14-standins: cooperative purchase preference and simulation fixes.
hidden-boss_10-solo: echo pulse FIFO resolution.
See DESIGN 28.13-28.24 for assumptions, sources and contributor credit.
2026-10-09 22:55:24 +08:00

55 lines
2.7 KiB
JavaScript

import { test } from 'node:test';
import assert from 'node:assert/strict';
import { readFileSync } from 'node:fs';
import { createPwaInstall } from '../../public/js/pwa.js';
function rig(standalone = false) {
const win = new EventTarget(), display = new EventTarget();
display.matches = standalone;
win.matchMedia = () => display;
const pwa = createPwaInstall(win);
const offer = (outcome = 'dismissed', fail = false) => {
let calls = 0;
const e = new Event('beforeinstallprompt', { cancelable: true });
e.prompt = async () => { calls++; if (fail) throw Error('unavailable'); return { outcome }; };
win.dispatchEvent(e);
return { calls: () => calls, e };
};
return { pwa, win, display, offer };
}
test('install offer is consumed even on dismissal, duplicate clicks, or rejected prompt', async () => {
const { pwa, offer } = rig();
const states = []; const off = pwa.subscribe((x) => states.push(x));
assert.equal(pwa.available(), false);
const first = offer();
assert.equal(first.e.defaultPrevented, true);
assert.equal(pwa.available(), true);
const a = pwa.request(), b = pwa.request();
assert.equal(await a, false); assert.equal(await b, false); assert.equal(first.calls(), 1);
const broken = offer('accepted', true);
assert.equal(await pwa.request(), false); assert.equal(await pwa.request(), false); assert.equal(broken.calls(), 1);
const next = offer('accepted');
assert.equal(await pwa.request(), true); assert.equal(next.calls(), 1);
assert.deepEqual(states, [false, true, false, true, false, true, false]);
off(); pwa.dispose();
});
test('installed / standalone hides stale offers; disposing removes listeners', async () => {
const { pwa, win, display, offer } = rig();
offer(); display.matches = true; display.dispatchEvent(new Event('change'));
assert.equal(pwa.available(), false); assert.equal(await pwa.request(), false);
display.matches = false; win.dispatchEvent(new Event('appinstalled')); offer();
assert.equal(pwa.available(), false);
pwa.dispose();
assert.equal(offer().e.defaultPrevented, false);
const native = rig(true); native.offer(); assert.equal(native.pwa.available(), false); native.pwa.dispose();
});
test('manifest uses actual 192/512 icons shipped outside optional game assets', () => {
const m = JSON.parse(readFileSync(new URL('../../public/manifest.json', import.meta.url)));
assert.equal(m.start_url, '/'); assert.equal(m.scope, '/');
for (const icon of m.icons) {
assert.ok(icon.src.startsWith('/icons/'));
const bytes = readFileSync(new URL('../../public' + icon.src, import.meta.url));
const size = Number(icon.sizes.split('x')[0]);
assert.equal(bytes.readUInt32BE(16), size); assert.equal(bytes.readUInt32BE(20), size);
}
});