Luigit
repositories / dotfiles

dotfiles

bugabingas dorkfiles

owned by admin

quickshell/nuguland/test-fixtures/ui/niri-integration.mjs

Raw
import assert from 'node:assert/strict';
import { appendFileSync, existsSync, readFileSync, writeFileSync } from 'node:fs';
import { join } from 'node:path';
import { setTimeout as delay } from 'node:timers/promises';

// Verify a kernel-owned direct child, never a process-name search or parent PID.
export function assertSubscription(pid, qmlPid, status, command) {
    assert(Number.isSafeInteger(pid) && pid > 1 && Number.isSafeInteger(qmlPid) && qmlPid > 1);
    assert.equal(Number(status.match(/^PPid:\s+(\d+)$/m)?.[1]), qmlPid, 'subscription must be a direct QML child');
    assert.deepEqual(command.split('\0').filter(Boolean), ['/usr/bin/niri', 'msg', '--json', 'event-stream']);
}

export function pngSize(bytes) {
    assert(bytes.length > 32, 'capture is not a complete PNG header');
    assert.deepEqual(bytes.subarray(0, 8), Buffer.from([137, 80, 78, 71, 13, 10, 26, 10]));
    assert.equal(bytes.toString('ascii', 12, 16), 'IHDR');
    const size = { width: bytes.readUInt32BE(16), height: bytes.readUInt32BE(20) };
    assert(size.width > 0 && size.height > 0);
    return size;
}

export async function prepareNiriIntegration({ run, start, waitFor, runtime, nativeNiri, nativeSocket, assertPureService }) {
    writeFileSync('/project/Niri.qml', nativeNiri);
    const vault = readFileSync('/project/test-fixtures/ui/niri-integration-vault.qml', 'utf8');
    assertPureService(vault);
    writeFileSync('/project/Vault.qml', vault);
    writeFileSync('/project/niri-integration-ui-test.qml', readFileSync('/project/test-fixtures/ui/niri-integration.qml', 'utf8').replace('import "../.." as N', 'import "." as N'));
    const protocol = '/usr/share/wayland-protocols/stable/xdg-shell/xdg-shell.xml';
    run('wayland-scanner', ['client-header', protocol, '/tmp/niri-integration-xdg.h']);
    run('wayland-scanner', ['private-code', protocol, '/tmp/niri-integration-xdg.c']);
    run('cc', ['-Wall', '-Wextra', '-Werror', '-I/tmp', '/project/test-fixtures/ui/niri-integration-client.c', '/tmp/niri-integration-xdg.c', '-lwayland-client', '-o', '/tmp/niri-integration-client']);
    const socket = join(runtime, 'niri.integration.sock');
    const modeFile = join(runtime, 'niri-integration-outputs.json');
    writeFileSync(modeFile, 'null');
    start('node', ['/project/test-fixtures/ui/niri-integration-proxy.mjs', socket, nativeSocket, modeFile], 'niri-integration-proxy');
    await waitFor('output fixture socket', () => existsSync(socket));
    return { socket, modeFile };
}

export async function runNiriIntegration({ run, start, check, snapshot, ipc, screenshot, click, input, key, compositorKey, stop, evidence, runtime, modeFile }) {
    const action = (...args) => run('niri', ['msg', 'action', ...args.map(String)]);
    const query = name => JSON.parse(run('niri', ['msg', '--json', name]));
    const file = name => join(evidence, `${name}.png`);
    async function closeClient(client) {
        action('close-window', '--id', client.id);
        let timeout;
        try {
            if (client.child.exitCode === null) await new Promise((resolve, reject) => {
                timeout = setTimeout(() => reject(new Error('native client did not acknowledge close')), 3000);
                client.child.once('exit', resolve);
            });
            assert.equal(client.child.exitCode, 0, 'xdg close must exit normally');
            assert.equal(client.child.signalCode, null);
            await stop(client.child);
        } finally {
            clearTimeout(timeout);
        }
    }
    const saved = async (kind, name) => {
        const path = file(name);
        const before = snapshot().captures.recentCount;
        const submitted = JSON.parse(ipc('capture', kind, path));
        assert.equal(submitted.lastAction.status, 'pending');
        assert.equal(submitted.recentCount, before, 'dispatch must not record success');
        const state = await check(`${kind} saves only on exact native completion: ${name}`, s => s.captures.recentCount === before + 1 && s.captures.recent[0].path === path && s.captures.recent[0].saved);
        const bytes = readFileSync(path);
        const dimensions = pngSize(bytes);
        // Decode the entire image independently, not just its existence/header.
        run('identify', ['-regard-warnings', path]);
        appendFileSync(join(evidence, 'niri-files.jsonl'), JSON.stringify({ path, ...dimensions, size: bytes.length, id: state.captures.recent[0].id }) + '\n');
        return dimensions;
    };
    let state = await check('real stream initial state and actual Bar pills', s => s.niri.ready && s.niri.outputCount === 1 && s.niri.windowCount === 0 && s.pills.length === 1 && s.pills[0].active);
    const firstWorkspace = state.niri.workspaces.find(row => row.is_focused).idx;
    async function client(name, title, app) {
        const fifo = join(runtime, `niri-integration-${name}.fifo`);
        run('mkfifo', [fifo]);
        const child = start('/tmp/niri-integration-client', [fifo, title, app], `niri-client-${name}`);
        await check(`native xdg window opens: ${name}`, s => s.niri.focusedTitle === title && s.niri.focusedAppId === app);
        return { child, fifo, id: snapshot().niri.focusedWindow };
    }
    const alpha = await client('alpha', 'alpha initial', 'synthetic.alpha');
    writeFileSync(alpha.fifo, 'title alpha renamed\n');
    await check('focused title delta replaces existing record', s => s.niri.windowCount === 1 && s.niri.focusedTitle === 'alpha renamed' && s.text.includes('focus: alpha renamed · synthetic.alpha'));
    writeFileSync(alpha.fifo, 'app synthetic.changed\n');
    state = await check('focused app-id delta reaches visible context', s => s.niri.focusedAppId === 'synthetic.changed');
    click(state.vault.rect);
    await check('actual Bar forwards focused app to safe vault sink', s => s.vault.opens === 1 && s.vault.app === 'synthetic.changed' && s.vault.opened === 'vault');
    const beta = await client('beta', 'beta window', 'synthetic.beta');
    action('move-window-to-workspace', '2');
    state = await check('workspace deltas add populated second pill', s => s.pills.length === 2 && s.niri.workspaces.some(row => row.idx === 2 && row.active_window_id === beta.id));
    click(state.pills.find(row => row.index === String(firstWorkspace)).rect);
    state = await check('actual Bar pill focuses first workspace and alpha', s => s.niri.focusedWindow === alpha.id && s.pills.find(row => row.index === String(firstWorkspace))?.active);
    click(state.vault.rect);
    await check('vault context updates on each opening after external focus', s => s.vault.opens === 2 && s.vault.app === 'synthetic.changed');
    action('focus-window', '--id', beta.id);
    state = await check('external focus reaches second workspace and beta', s => s.niri.focusedWindow === beta.id && s.pills.find(row => row.index === '2')?.active);
    click(state.vault.rect);
    await check('vault receives changed focused app, not retained prior context', s => s.vault.opens === 3 && s.vault.app === 'synthetic.beta');
    await screenshot('niri-bar-two-workspaces');
    action('toggle-overview');
    await check('native overview opens through stream', s => s.niri.overviewState === 'open');
    await compositorKey('Escape');
    await check('keyboard closes native overview', s => s.niri.overviewState === 'closed');

    // Only the subscription dies. Mutate a client before the bounded retry fires.
    state = snapshot();
    const pid = state.subscriptionPid;
    assertSubscription(pid, state.qmlPid, readFileSync(`/proc/${pid}/status`, 'utf8'), readFileSync(`/proc/${pid}/cmdline`, 'utf8'));
    const transitionCount = state.readiness.length;
    const originalQmlPid = state.qmlPid;
    writeFileSync(join(evidence, 'niri-subscription.json'), JSON.stringify({ pid, qmlPid: state.qmlPid, command: ['/usr/bin/niri', 'msg', '--json', 'event-stream'] }, null, 2));
    process.kill(pid, 'SIGTERM');
    writeFileSync(beta.fifo, 'title beta during disconnect\n');
    state = await check('subscription disconnect clears readiness and reconnects with fresh state', s => s.readiness.slice(transitionCount).some(row => !row.ready) && s.niri.ready && s.subscriptionPid !== pid && s.niri.focusedTitle === 'beta during disconnect');
    assert.equal(state.qmlPid, originalQmlPid);
    assert(state.readiness.slice(transitionCount).find(row => !row.ready).windows === 0);

    const original = query('outputs');
    const outputName = Object.keys(original)[0];
    run('niri', ['msg', 'output', outputName, 'position', 'set', '400', '120']);
    const repositioned = query('outputs');
    writeFileSync(join(evidence, 'niri-reposition.json'), JSON.stringify({ original, repositioned }, null, 2));
    assert.equal(repositioned[outputName].logical.x, 400);
    assert.equal(repositioned[outputName].logical.y, 120);
    await check('real output reposition reaches outputs-only poll', s => s.niri.outputs[outputName].logical.x === 400 && s.niri.outputs[outputName].logical.y === 120);
    run('niri', ['msg', 'output', outputName, 'position', 'set', '0', '0']);
    const stable = snapshot().niri;
    const extra = { ...original[outputName], name: 'fixture-hotplug', logical: { ...original[outputName].logical, x: -1280 } };
    assert.equal(ipc('fixtureOutputs', 'true'), 'true');
    writeFileSync(modeFile, JSON.stringify({ Ok: { Outputs: { ...original, 'fixture-hotplug': extra } } }));
    await check('fixture hotplug enters production outputs query without resetting stream', s => s.niri.outputCount === 2 && s.niri.outputs['fixture-hotplug'].logical.x === -1280 && s.niri.focusedTitle === stable.focusedTitle && s.niri.windowCount === stable.windowCount);
    writeFileSync(modeFile, JSON.stringify({ Err: 'controlled output query rejection' }));
    await check('rejected outputs query preserves last inventory and stream readiness', s => s.niri.outputError.length > 0 && s.niri.outputCount === 2 && s.niri.ready);
    writeFileSync(modeFile, 'null');
    await check('fixture unplug restores real inventory through periodic polling', s => s.niri.outputCount === 1 && s.niri.outputError === '' && !s.niri.outputs['fixture-hotplug']);

    assert.equal(ipc('fixtureOutputs', 'false'), 'true');

    assert.deepEqual(await saved('focused-screen', 'native-screen'), { width: 1280, height: 720 });
    assert.deepEqual(await saved('focused-window', 'native-window'), { width: 480, height: 280 });
    let submitted = JSON.parse(ipc('capture', 'interactive', file('native-interactive')));
    assert.equal(submitted.lastAction.saved, false);
    assert.equal(submitted.recentCount, 2);
    await delay(350);
    input('mousemove', '100', '100');
    input('mousedown', '1');
    input('mousemove', '500', '300');
    input('mouseup', '1');
    await screenshot('niri-selection-pending');
    await compositorKey('Return');
    await check('keyboard confirms native interactive save', s => s.captures.recentCount === 3 && s.captures.recent[0].path === file('native-interactive') && s.captures.pendingCount === 0);
    assert.deepEqual(pngSize(readFileSync(file('native-interactive'))), { width: 401, height: 201 });
    run('identify', ['-regard-warnings', file('native-interactive')]);
    await screenshot('niri-saved-history');

    submitted = JSON.parse(ipc('capture', 'interactive', file('native-cancelled')));
    const completions = snapshot().completions.length;
    await delay(350);
    await compositorKey('Escape');
    await delay(350);
    state = await check('native cancellation is unconfirmed, never fictitious saved history', s => s.captures.pendingCount === 1 && s.captures.recentCount === 3 && s.completions.length === completions);
    assert(!existsSync(file('native-cancelled')));
    assert.equal(state.captures.pending[0].id, submitted.lastAction.id);

    ipc('capture', 'interactive', file('native-clipboard-only'));
    await delay(350);
    input('mousemove', '100', '100');
    input('mousedown', '1');
    input('mousemove', '500', '300');
    input('mouseup', '1');
    await compositorKey('ctrl+c');
    await check('native clipboard-only null completion cannot consume a pending save', s => s.completions.at(-1) === null && s.captures.recentCount === 3 && s.captures.pendingCount === 2);
    assert(!existsSync(file('native-clipboard-only')));
    // wl-paste is a reader on the isolated compositor, never a second capture backend.
    assert(run('wl-paste', ['--list-types']).includes('image/png'));
    run('sh', ['-c', 'wl-paste --type image/png > /evidence/native-clipboard.png']);
    assert.deepEqual(pngSize(readFileSync(file('native-clipboard'))), { width: 401, height: 201 });
    run('identify', ['-regard-warnings', file('native-clipboard')]);

    const unavailable = JSON.parse(ipc('unavailable'));
    assert.equal(unavailable.lastAction.status, 'unavailable');
    assert.equal(unavailable.pendingCount, 2);
    assert(!existsSync(file('unavailable')));
    await saved('focused-screen', 'native-repeat');
    await saved('focused-screen', 'native-repeat');
    state = snapshot();
    assert.notEqual(state.captures.recent[0].id, state.captures.recent[1].id);
    assert.equal(state.captures.pendingCount, 2);
    await screenshot('niri-repeated-history');

    // This single warning is required, not hidden: record its action-local log
    // interval so the complete-log gate rejects missing, duplicate or stray warnings.
    const nulls = state.completions.filter(path => path === null).length;
    const nativeLog = join(evidence, 'niri.log');
    const writeFailure = { start: readFileSync(nativeLog, 'utf8').length };
    ipc('capture', 'focused-screen', '/evidence/missing-directory/native-failure.png');
    await check('native failed write emits null and leaves pending/history honest', s => s.completions.filter(path => path === null).length > nulls && s.captures.pendingCount === 3 && s.captures.recentCount === 5 && readFileSync(nativeLog, 'utf8').slice(writeFailure.start).includes('error saving screenshot image: Os { code: 2, kind: NotFound, message: "No such file or directory" }\n'));
    writeFailure.end = readFileSync(nativeLog, 'utf8').length;
    assert(!existsSync('/evidence/missing-directory/native-failure.png'));
    state = snapshot();
    for (const record of state.captures.recent) {
        assert(existsSync(record.path));
        assert(state.text.includes(`${record.kind} · ${record.path}`));
    }
    assert(state.text.includes('saved: 5 · unconfirmed: 3'));
    await screenshot('niri-unconfirmed-history');

    submitted = JSON.parse(ipc('burst'));
    assert.equal(submitted.recentCount, 5, 'a synchronous burst cannot fabricate completion');
    assert.equal(submitted.pendingCount, 5);
    await check('rapid native requests both dispatch without terminating one another', s => s.captures.recentCount === 7 && s.captures.pendingCount === 3);
    for (const name of ['native-burst-screen', 'native-burst-window']) {
        pngSize(readFileSync(file(name)));
        run('identify', ['-regard-warnings', file(name)]);
    }

    submitted = JSON.parse(ipc('completeSelection', file('native-complete-selection')));
    assert.equal(submitted.selection.phase, 'pending');
    assert.equal(submitted.selection.completionFlash, false);
    await delay(350);
    input('mousemove', '100', '100');
    input('mousedown', '1');
    input('mousemove', '500', '300');
    input('mouseup', '1');
    await compositorKey('Return');
    await check('completeSelection flashes only after its native save', s => s.captures.recentCount === 8 && s.captures.pendingCount === 3 && s.captures.selection.phase === 'completed' && s.captures.selection.completionFlash);
    assert.deepEqual(pngSize(readFileSync(file('native-complete-selection'))), { width: 401, height: 201 });
    run('identify', ['-regard-warnings', file('native-complete-selection')]);
    await closeClient(beta);
    await check('native close removes beta from the streamed window inventory', s => !s.niri.windows.some(row => row.id === beta.id) && s.niri.windowCount === 1);
    action('focus-window', '--id', alpha.id);
    await check('remaining window regains focus', s => s.niri.focusedWindow === alpha.id);
    await closeClient(alpha);
    await check('closing last native window clears title/app and retains one active pill', s => s.niri.windowCount === 0 && s.niri.focusedTitle === '' && s.niri.focusedAppId === '' && s.pills.length === 1);
    ipc('clear');
    await check('clear forgets saved and unconfirmed history visibly', s => s.captures.recentCount === 0 && s.captures.pendingCount === 0 && s.text.includes('saved: 0 · unconfirmed: 0'));
    await screenshot('niri-cleared-history');
    ipc('pasteFixture');
    state = await check('synthetic paste destination gains native focus', s => s.niri.focusedTitle === 'paste destination' && s.paste.destinationFocused && s.paste.destinationEmpty);
    const destinationId = state.niri.focusedWindow;
    click(state.paste.chip);
    state = await check('clipboard search owns popup focus before paste', s => s.paste.open && s.paste.searchFocused && s.paste.target === destinationId && s.paste.button);
    input('type', 'focus-safe');
    state = await check('clipboard destination search is retained', s => s.paste.queryPreserved && s.paste.destinationEmpty);
    click(state.paste.button);
    await check('pointer paste releases popup and types exactly once into destination', s => !s.paste.open && !s.paste.pending && s.paste.destinationMatches && s.paste.queryPreserved && s.niri.focusedWindow === destinationId && s.paste.error === '');
    await screenshot('clipboard-paste-pointer');
    key('ctrl+a');
    key('BackSpace');
    state = await check('destination editor remains keyboard operable after paste', s => s.paste.destinationEmpty);
    click(state.paste.chip);
    state = await check('clipboard reopen restores search before keyboard paste', s => s.paste.open && s.paste.searchFocused && s.paste.queryPreserved);
    key('Return');
    await check('keyboard paste returns focus and inserts exactly once', s => !s.paste.open && !s.paste.pending && s.paste.destinationMatches && s.paste.queryPreserved && s.niri.focusedWindow === destinationId && s.paste.error === '');
    await screenshot('clipboard-paste-keyboard');
    console.log('niri interaction assertions completed; complete runtime-log gate remains mandatory');
    return writeFailure;
}