SOURCE / PINNED RELEASE
Made of little things.
Black Site
- Release
- 666bc7c74ebd…
- Author-recorded commit
- 05e95af79676…
- License
- LICENSE
- Author’s source reference
- nostr://npub12lm3rxsva8kfempyjkxupt09e02n3fezurkyadyqfejdc5e0r2kscqty6r/wss%3A%2F%2Fgit.napplet.soy%2F/n-d2225ddc391
Archive hash verified: 1544e072e245c4a8…. The source-to-build association is the author’s claim; it has not been independently rebuilt.
import assert from 'node:assert/strict';
import { readFile } from 'node:fs/promises';
import { runInNewContext } from 'node:vm';
import test from 'node:test';
import ts from 'typescript';
const source = await readFile(new URL('../src/game-keyboard.ts', import.meta.url), 'utf8');
const compiled = ts.transpileModule(source, {
compilerOptions: { module: ts.ModuleKind.CommonJS, target: ts.ScriptTarget.ES2022 },
}).outputText;
function fixture({ available = true, fail = false, pending = false } = {}) {
const window = new EventTarget();
window.setTimeout = setTimeout;
const document = new EventTarget();
const callbacks = new Map();
const declarations = [];
const removed = [];
const invoked = [];
const held = [];
const waits = [];
const api = {
onAction(id, cb) { callbacks.set(id, cb); return { close() { callbacks.delete(id); } }; },
registerAction(action) {
declarations.push(action);
if (fail) throw new Error('denied');
if (pending) return new Promise(resolve => waits.push(() => resolve({ actionId: action.id })));
return Promise.resolve({ actionId: action.id }); // Runtime may leave actions unbound.
},
unregisterAction(id) { removed.push(id); },
};
const module = { exports: {} };
runInNewContext(compiled, {
module, exports: module.exports, window, document, Element: class {}, queueMicrotask,
console: { warn() {} },
require(name) { return name === '@napplet/sdk' ? { keys: api, KEYS_DOMAIN: 'keys' } : { runtimeHasDomain: () => available }; },
});
const close = module.exports.installGameKeyboard((...args) => held.push(args), code => invoked.push(code), () => held.push(['reset']));
function key(code, options = {}) {
const event = new Event('keydown', { cancelable: true });
Object.assign(event, { code, repeat: false, ctrlKey: false, altKey: false, metaKey: false, ...options });
if (options.consumed) event.preventDefault();
window.dispatchEvent(event);
}
return { key, close, callbacks, declarations, removed, invoked, held, waits, window, document };
}
const settle = () => new Promise(resolve => setImmediate(resolve));
test('registers safe stable actions; runtime owns actions even when unbound', async () => {
const f = fixture();
await settle();
assert.equal(f.declarations.length, 9);
assert.ok(f.declarations.every(a => !['Tab', 'Escape'].includes(a.defaultKey)));
f.key('KeyR');
assert.deepEqual(f.invoked, []);
f.callbacks.get('blacksite.reload')();
await settle();
assert.deepEqual(f.invoked, ['KeyR']);
f.key('KeyW', { consumed: true });
assert.ok(!f.held.some(([code]) => code === 'KeyW'));
f.key('Escape');
assert.equal(f.invoked.at(-1), 'Escape');
f.close();
assert.equal(f.callbacks.size, 0);
assert.equal(new Set(f.removed).size, 9);
f.key('KeyI');
assert.equal(f.invoked.length, 2);
});
test('missing or denied optional keys retains local shortcuts and movement', async () => {
for (const settings of [{ available: false }, { fail: true }]) {
const f = fixture(settings);
await settle();
f.key('KeyR');
f.key('KeyR', { repeat: true });
f.key('KeyQ', { ctrlKey: true });
f.key('KeyI', { isComposing: true });
f.key('KeyW');
assert.deepEqual(f.invoked, ['KeyR']);
assert.ok(f.held.some(([code, down]) => code === 'KeyW' && down));
f.close();
}
});
test('consumed action fires once and repeat callbacks are ignored', async () => {
const f = fixture();
await settle();
// Model the runtime document capture callback, after our window capture.
const dispatch = event => {
event.preventDefault();
f.callbacks.get('blacksite.reload')();
};
f.window.addEventListener('keydown', dispatch);
f.key('KeyR', { consumed: true });
await settle();
f.key('KeyR', { consumed: true, repeat: true });
await settle();
assert.deepEqual(f.invoked, ['KeyR']);
f.close();
});
test('late registration completion after teardown is unregistered', async () => {
const f = fixture({ pending: true });
f.close();
f.waits.forEach(resolve => resolve());
await settle();
assert.equal(f.callbacks.size, 0);
assert.equal(f.removed.length, 18);
});
