Files
vencord-wayland-keybinds/logic.test.ts
T
Evandro Fonseca Junior a3a9149184 WaylandKeybinds: reliable Discord global keybinds on GNOME Wayland
Vencord plugin for the Discord desktop app. Toggles fire on key press,
push-to-talk infers releases GNOME drops (re-registration probe), and
GNOME's trigger descriptions are matched correctly. Includes basis-path
tests, a patch checker against the live Discord bundle, the design spec
and the README assets.
2026-11-02 00:51:25 -03:00

244 lines
9.9 KiB
TypeScript

/*
* Vencord, a Discord client mod
* Copyright (c) 2026 Evandro
* SPDX-License-Identifier: GPL-3.0-or-later
*/
// Caminhos-base (McCabe): um caso por caminho independente de cada função de logic.ts.
import assert from "node:assert/strict";
import { describe, test } from "node:test";
import { adjustOptions, createDispatcher, ECHO_MS, FIRST_PROBE_MS, fixPortalState, normalizeTrigger, planRegistration, PROBE_WINDOW_MS } from "./logic";
function recorder() {
const calls: boolean[] = [];
return { calls, callback: (isDown: boolean) => { calls.push(isDown); } };
}
describe("planRegistration (V(G)=7)", () => {
test("P1 portal ativo, representável, segurar → hold", () => {
assert.equal(planRegistration({ keydown: true, keyup: true }, true, true), "hold");
});
test("P2 portal inativo → passthrough", () => {
assert.equal(planRegistration({ keydown: true, keyup: true }, false, true), "passthrough");
});
test("P3 combinação não representável → passthrough", () => {
assert.equal(planRegistration({ keydown: true, keyup: true }, true, false), "passthrough");
});
test("P4 ação com portal:false (SOUNDBOARD_HOLD no Linux) → passthrough", () => {
assert.equal(planRegistration({ keydown: true, keyup: true, portal: false }, true, true), "passthrough");
});
test("P5 só soltar (TOGGLE_MUTE) → pressAsRelease", () => {
assert.equal(planRegistration({ keydown: false, keyup: true }, true, true), "pressAsRelease");
});
test("P6 só pressionar (SAVE_CLIP) → passthrough", () => {
assert.equal(planRegistration({ keydown: true, keyup: false }, true, true), "passthrough");
});
test("P7 nenhum evento → passthrough", () => {
assert.equal(planRegistration({}, true, true), "passthrough");
});
});
describe("adjustOptions (V(G)=2)", () => {
test("P1 pressAsRelease → pede pressionar e soltar ao nativo e preserva o resto", () => {
const options = { focused: true, blurred: true, keydown: false, keyup: true, shortcutId: "toggle-mute" };
assert.deepEqual(adjustOptions(options, "pressAsRelease"),
{ focused: true, blurred: true, keydown: true, keyup: true, shortcutId: "toggle-mute" });
assert.equal(options.keydown, false, "não muta o objeto original");
});
test("P2 outros modos → mesmo objeto", () => {
const options = { keydown: true, keyup: true };
assert.equal(adjustOptions(options, "hold"), options);
assert.equal(adjustOptions(options, "passthrough"), options);
});
});
// Relógio falso: timers só disparam quando o teste avança o tempo.
function fakeScheduler() {
let now = 0, seq = 0;
const timers = new Map<number, { at: number; fn: () => void; }>();
return {
now: () => now,
setTimeout: (fn: () => void, ms: number) => { timers.set(++seq, { at: now + ms, fn }); return seq; },
clearTimeout: (id: unknown) => { timers.delete(id as number); },
pending: () => timers.size,
advance(ms: number) {
const target = now + ms;
for (;;) {
const next = [...timers.entries()].filter(([, t]) => t.at <= target).sort((a, b) => a[1].at - b[1].at)[0];
if (!next) break;
timers.delete(next[0]);
now = next[1].at;
next[1].fn();
}
now = target;
}
};
}
function rig(mode: "pressAsRelease" | "hold") {
const clock = fakeScheduler();
const calls: boolean[] = [];
let rearms = 0;
const { dispatch, cancel } = createDispatcher(d => { calls.push(d); }, mode, () => { rearms++; }, clock);
return { clock, calls, dispatch, cancel, rearms: () => rearms };
}
describe("createDispatcher (V(G)=2) e o despachante (V(G)=6)", () => {
test("P1 passthrough → devolve o próprio callback e cancel inofensivo", () => {
const { callback } = recorder();
const d = createDispatcher(callback, "passthrough", () => { throw new Error("não deve re-registrar"); }, fakeScheduler());
assert.equal(d.dispatch, callback);
d.cancel();
});
test("D1 ocioso + soltar avulso → ignorado, nada agendado", () => {
const r = rig("hold");
r.dispatch(false);
assert.deepEqual(r.calls, []);
assert.equal(r.clock.pending(), 0);
});
test("D2 ocioso + pressionar → alternar dispara como soltura (false) e agenda a 1ª sondagem", () => {
const r = rig("pressAsRelease");
r.dispatch(true);
assert.deepEqual(r.calls, [false]);
r.clock.advance(FIRST_PROBE_MS - 1);
assert.equal(r.rearms(), 0);
r.clock.advance(1);
assert.equal(r.rearms(), 1);
});
test("D2 ocioso + pressionar → segurar começa (true)", () => {
const r = rig("hold");
r.dispatch(true);
assert.deepEqual(r.calls, [true]);
});
test("D3 segurado + repetição logo após o re-registro → continua segurado e sonda de novo", () => {
const r = rig("hold");
r.dispatch(true);
r.clock.advance(FIRST_PROBE_MS);
r.clock.advance(20);
r.dispatch(true);
r.clock.advance(PROBE_WINDOW_MS - 20);
assert.deepEqual(r.calls, [true]);
assert.equal(r.rearms(), 2);
});
test("veredito sem repetição → segurar termina (false) e para de sondar", () => {
const r = rig("hold");
r.dispatch(true);
r.clock.advance(FIRST_PROBE_MS + PROBE_WINDOW_MS);
assert.deepEqual(r.calls, [true, false]);
assert.equal(r.clock.pending(), 0);
assert.equal(r.rearms(), 1);
});
test("veredito sem repetição → alternar não chama nada a mais", () => {
const r = rig("pressAsRelease");
r.dispatch(true);
r.clock.advance(FIRST_PROBE_MS + PROBE_WINDOW_MS);
assert.deepEqual(r.calls, [false]);
assert.equal(r.clock.pending(), 0);
});
test("D4 segurado + pressionar fora do eco (soltura perdida, novo gesto) → alternar dispara de novo", () => {
const r = rig("pressAsRelease");
r.dispatch(true);
r.clock.advance(FIRST_PROBE_MS + ECHO_MS);
r.dispatch(true);
assert.deepEqual(r.calls, [false, false]);
});
test("D5 segurado + soltar dentro do eco do re-registro → ignorado (soltura sintética)", () => {
const r = rig("hold");
r.dispatch(true);
r.clock.advance(FIRST_PROBE_MS + 1);
r.dispatch(false);
assert.deepEqual(r.calls, [true]);
});
test("D6 segurado + soltar real → segurar termina na hora e cancela a sondagem", () => {
const r = rig("hold");
r.dispatch(true);
r.clock.advance(400);
r.dispatch(false);
assert.deepEqual(r.calls, [true, false]);
assert.equal(r.clock.pending(), 0);
r.clock.advance(5000);
assert.equal(r.rearms(), 0);
});
test("D6 alternar + soltar real → sem chamada extra, próximo toque dispara (toque duplo)", () => {
const r = rig("pressAsRelease");
r.dispatch(true);
r.clock.advance(80);
r.dispatch(false);
r.clock.advance(200);
r.dispatch(true);
assert.deepEqual(r.calls, [false, false]);
});
test("cancel → para a sondagem e nada mais é re-registrado", () => {
const r = rig("hold");
r.dispatch(true);
r.cancel();
r.clock.advance(5000);
assert.equal(r.rearms(), 0);
assert.equal(r.clock.pending(), 0);
});
test("cenário medido: segurar 1,8 s soltando modificadores antes, depois novo toque", () => {
const r = rig("pressAsRelease");
r.dispatch(true); // Activated
r.clock.advance(FIRST_PROBE_MS); r.dispatch(true); // repetição logo após o 1º re-registro
r.clock.advance(PROBE_WINDOW_MS); r.dispatch(true); // ainda segurado no 2º re-registro
r.clock.advance(PROBE_WINDOW_MS); // soltou (sem Deactivated): veredito encerra
r.clock.advance(1500);
r.dispatch(true); // gesto seguinte não é engolido
assert.deepEqual(r.calls, [false, false]);
assert.equal(r.clock.pending(), 1);
});
});
describe("normalizeTrigger (V(G)=2 + apelidos)", () => {
test("P1 formato do portal (sem <>) → MODS|tecla", () => {
assert.equal(normalizeTrigger("CTRL+SHIFT+F12"), "CTRL+SHIFT|f12");
});
test("P2 descrição do GNOME (com <>) → mesma forma canônica", () => {
assert.equal(normalizeTrigger("Press <Shift><Control>F12"), "CTRL+SHIFT|f12");
});
test("tecla sem modificador, descrição localizada do GNOME", () => {
assert.equal(normalizeTrigger("Pressione F12"), "|f12");
assert.equal(normalizeTrigger("m"), "|m");
});
test("apelidos de modificador e modificador desconhecido", () => {
assert.equal(normalizeTrigger("Press <Primary><Super><Alt>m"), "ALT+CTRL+LOGO|m");
assert.equal(normalizeTrigger("NUM+a"), "NUM|a");
});
});
describe("fixPortalState (V(G)=5)", () => {
const gnome = { eventId: 7, bound: true, triggerDescription: "Press <Shift><Control>F12", matchesTrigger: false };
test("P1 vinculado, divergente só no formato → matchesTrigger true (cópia)", () => {
const fixed = fixPortalState(gnome, "CTRL+SHIFT+F12");
assert.deepEqual(fixed, { ...gnome, matchesTrigger: true });
assert.equal(gnome.matchesTrigger, false, "não muta o estado original");
});
test("P2 não vinculado → igual", () => {
const s = { ...gnome, bound: false };
assert.equal(fixPortalState(s, "CTRL+SHIFT+F12"), s);
});
test("P3 já casa → igual", () => {
const s = { ...gnome, matchesTrigger: true };
assert.equal(fixPortalState(s, "CTRL+SHIFT+F12"), s);
});
test("P4 sem gatilho esperado → igual", () => {
assert.equal(fixPortalState(gnome, undefined), gnome);
});
test("P5 atalho realmente diferente no sistema → igual", () => {
assert.equal(fixPortalState(gnome, "CTRL+ALT+F12"), gnome);
});
});