Back to Black Site Map Editor
SOURCE / PINNED RELEASE

Made of little things.

Black Site Map Editor

Release
32a57733b7e9…
Author-recorded commit
3f54890f542f…
License
LICENSE
Author’s source reference
nostr://npub1ye5ptcxfyyxl5vjvdjar2ua3f0hynkjzpx552mu5snj3qmx5pzjscpknpr/wss%3A%2F%2Fgit.shakespeare.diy%2F/black-site-map-editor

Archive hash verified: 7a4b1abbac7db5b0…. The source-to-build association is the author’s claim; it has not been independently rebuilt.

tests/keys.test.mjs
import test from 'node:test';
import assert from 'node:assert/strict';
import { readFile } from 'node:fs/promises';
import ts from 'typescript';
const load = async (file, replacements = []) => {
    let js = ts.transpileModule(
        await readFile(new URL(`../src/${file}`, import.meta.url), 'utf8'),
        { compilerOptions: { target: ts.ScriptTarget.ES2022, module: ts.ModuleKind.ESNext } },
    ).outputText;
    for (const [from, to] of replacements) {
        assert.ok(js.includes(from), `${file}: missing ${from}`);
        js = js.replace(from, to);
    }
    return import(`data:text/javascript,${encodeURIComponent(`${js}\n// ${Math.random()}`)}`);
};
const combo = await load('key-combo.ts');
const key = (value, mods = {}) => ({
    key: value,
    altKey: false,
    ctrlKey: false,
    metaKey: false,
    shiftKey: false,
    ...mods,
});

test('matches built-in combos in the NAP-KEYS shim format', () => {
    const { matchesCombo } = combo;
    assert.ok(matchesCombo('Ctrl+Z', key('z', { ctrlKey: true })));
    assert.ok(matchesCombo('Ctrl+Z', key('z', { metaKey: true })), 'Cmd stands in for Ctrl');
    assert.ok(!matchesCombo('Ctrl+Z', key('z', { ctrlKey: true, shiftKey: true })));
    assert.ok(matchesCombo('Ctrl+Shift+Z', key('Z', { ctrlKey: true, shiftKey: true })));
    assert.ok(matchesCombo('W', key('w')) && !matchesCombo('W', key('w', { ctrlKey: true })));
    assert.ok(
        matchesCombo('Shift+?', key('?', { shiftKey: true })) && matchesCombo('Shift+?', key('?')),
        'shifted symbols ignore layout Shift',
    );
    assert.ok(
        matchesCombo('+', key('+', { shiftKey: true })) &&
            matchesCombo('Ctrl+,', key(',', { ctrlKey: true })),
    );
    assert.ok(
        matchesCombo('Shift+ArrowUp', key('ArrowUp', { shiftKey: true })) &&
            !matchesCombo('ArrowUp', key('ArrowUp', { shiftKey: true })),
    );
    assert.ok(matchesCombo('Delete', key('Delete')) && !matchesCombo('Bogus+X', key('x')));
});

test('formats bindings for people and assistive technology', () => {
    assert.equal(combo.formatCombo('Ctrl+Shift+z'), 'Ctrl+Shift+Z');
    assert.equal(combo.formatCombo('Shift+?'), '?');
    assert.equal(combo.formatCombo('Shift+ArrowLeft'), 'Shift+←');
    assert.equal(combo.ariaCombo('Ctrl+Enter'), 'Control+Enter');
    assert.equal(combo.ariaCombo('Delete'), 'Delete');
});

test('typing targets keep their keystrokes', () => {
    const { isTypingTarget } = combo;
    assert.ok(isTypingTarget({ tagName: 'INPUT', type: 'number' }));
    assert.ok(
        isTypingTarget({ tagName: 'TEXTAREA' }) &&
            isTypingTarget({ tagName: 'DIV', isContentEditable: true }),
    );
    assert.ok(
        !isTypingTarget({ tagName: 'INPUT', type: 'checkbox' }) &&
            !isTypingTarget({ tagName: 'BUTTON' }) &&
            !isTypingTarget(null),
    );
});

function fakeDocument() {
    const listeners = new Set();
    return {
        listeners,
        addEventListener: (_type, fn) => listeners.add(fn),
        removeEventListener: (_type, fn) => listeners.delete(fn),
        press(event) {
            const e = {
                defaultPrevented: false,
                isComposing: false,
                target: { tagName: 'CANVAS' },
                preventDefault() {
                    this.defaultPrevented = true;
                },
                ...key(event.key, event),
                ...event,
            };
            for (const fn of listeners) fn(e);
            return e;
        },
    };
}
async function loadShortcuts(fixture) {
    globalThis.__keysFixture = fixture;
    return load('shortcuts.ts', [
        ["import { keys } from '@napplet/sdk';", 'const {keys}=globalThis.__keysFixture;'],
        [
            "import { runtimeHasDomain } from './domain-availability.js';",
            'const runtimeHasDomain=globalThis.__keysFixture.hasDomain;',
        ],
        [
            "import { isTypingTarget, matchesCombo } from './key-combo';",
            `const {isTypingTarget,matchesCombo}=globalThis.__keysFixture.combo;`,
        ],
    ]);
}

test('without the keys domain built-in keys drive actions locally', async () => {
    const document = (globalThis.document = fakeDocument());
    const { installShortcuts } = await loadShortcuts({ combo, hasDomain: () => false, keys: {} });
    let undo = 0;
    const shortcuts = installShortcuts(
        [
            {
                id: 'edit.undo',
                label: 'Undo',
                group: 'Edit',
                defaultKey: 'Ctrl+Z',
                run: () => undo++,
            },
        ],
        () => {},
    );
    assert.equal(shortcuts.get('edit.undo').source, 'default');
    assert.ok(document.press({ key: 'z', ctrlKey: true }).defaultPrevented);
    document.press({ key: 'z', ctrlKey: true, target: { tagName: 'INPUT', type: 'text' } });
    document.press({ key: 'z', ctrlKey: true, defaultPrevented: true });
    assert.equal(undo, 1, 'text fields and shim-handled keys are skipped');
    shortcuts.close();
    assert.equal(document.listeners.size, 0);
});

test('host bindings replace built-in keys; unbound or failed registrations keep them', async () => {
    const document = (globalThis.document = fakeDocument());
    const handlers = new Map(),
        closed = [];
    const keys = {
        register: async (action, handler) => {
            if (action.id === 'map.fail') throw new Error('duplicate action ID');
            handlers.set(action.id, handler);
            return {
                actionId: action.id,
                binding: action.id === 'edit.undo' ? 'Alt+U' : undefined,
                close: () => closed.push(action.id),
            };
        },
    };
    const { installShortcuts } = await loadShortcuts({
        combo,
        hasDomain: (domain) => domain === 'keys',
        keys,
    });
    const runs = [];
    let changes = 0;
    const actions = ['edit.undo', 'view.grid', 'map.fail'].map((id, index) => ({
        id,
        label: id,
        group: 'G',
        defaultKey: ['Ctrl+Z', 'G', 'Ctrl+S'][index],
        run: () => runs.push(id),
    }));
    const shortcuts = installShortcuts(actions, () => changes++);
    assert.ok(shortcuts.states().every((state) => state.source === 'pending'));
    document.press({ key: 'g' });
    assert.deepEqual(runs, [], 'nothing fires while registration is pending');
    await new Promise((resolve) => setTimeout(resolve, 0));
    assert.equal(changes, 3);
    assert.deepEqual(
        shortcuts.states().map((state) => [state.action.id, state.source, state.binding]),
        [
            ['edit.undo', 'host', 'Alt+U'],
            ['view.grid', 'default', 'G'],
            ['map.fail', 'default', 'Ctrl+S'],
        ],
    );
    document.press({ key: 'z', ctrlKey: true });
    document.press({ key: 'g' });
    document.press({ key: 's', ctrlKey: true });
    handlers.get('edit.undo')();
    assert.deepEqual(
        runs,
        ['view.grid', 'map.fail', 'edit.undo'],
        'host-bound undo only runs when the host delivers it',
    );
    shortcuts.close();
    assert.deepEqual(closed.sort(), ['edit.undo', 'view.grid']);
    handlers.get('edit.undo')();
    assert.equal(runs.length, 3, 'closed shortcuts ignore late host actions');
});