Ctrl K
ComunidadGuías…

tests/keymap.test.ts

Rama main · réplica de lectura del repositorio original.

Archivo original ↗Preguntar a KiBot
// THE LEDGER, ASSERTED. `GROUPS` in client/components/ShortcutsHelp.tsx is the
// one place a keyboard binding exists; this file is the arithmetic on it that
// does not need a browser, and it is the same arithmetic `npm run check-keymap`
// runs — one implementation in client/keymap.ts, driven from two doors, so a
// gate that passes and a suite that passes can never disagree.
//
// What it is guarding against is a bug with no symptom at the crash site: two
// rows claiming one keystroke. One handler answers, the other never sees the
// event, and neither knows the other exists — so it surfaces weeks later as
// "Ctrl+B does nothing", on one platform, from one reader. There is nothing to
// grep for, because nothing is wrong with either binding.
//
// The grammar half matters as much as the collision half. A row's `keys` array
// is PRINTED as <kbd> chips and READ here as a chord, and those two only stay
// the same thing while every row spells a chord the same way: modifiers in one
// canonical order, one key token, `↑ / ↓` for a pair. A row that spells itself
// differently still renders — it just stops being comparable, which is how a
// collision hides from the gate that exists to find it.
//
// tests/shortcuts.test.ts is the other half of the keyboard story: this file
// asks whether a binding is UNIQUE, that one asks whether it can be REACHED
// from a keyboard that types no Latin letters. A new binding needs both.

import assert from "node:assert/strict";
import { readFileSync } from "node:fs";
import { describe, it } from "node:test";
import {
  RESOLVED,
  parseGroups,
  parseKeymapDoc,
  parseKeys,
  scopeOverlap,
} from "../client/keymap.ts";

const read = (rel: string) => readFileSync(new URL(rel, import.meta.url).pathname, "utf8");
const ledger = parseGroups(read("../client/components/ShortcutsHelp.tsx"));
const doc = parseKeymapDoc(read("../docs/keymap.md"));

describe("the keymap ledger parses", () => {
  it("reads GROUPS out of the source with no complaints", () => {
    assert.deepEqual(ledger.errors, []);
    // A parser that quietly matches nothing reports a table with no
    // collisions, which is exactly what a broken parser and a healthy table
    // look like from the outside.
    assert.ok(ledger.rows.length > 30, `only ${ledger.rows.length} rows parsed`);
    assert.ok(new Set(ledger.rows.map((r) => r.group)).size > 3);
  });

  it("gives every row an answer — a key, or the surface that carries it", () => {
    for (const row of ledger.rows) {
      assert.ok(
        row.keys !== null || row.via !== null,
        `${row.label} (ShortcutsHelp.tsx:${row.line}) prints a label and an empty key column`,
      );
    }
  });

  it("spells every row's keys in the shape the gate expects", () => {
    for (const row of ledger.rows) {
      if (row.keys === null) continue;
      const { chords, error } = parseKeys(row.keys);
      assert.equal(error, null, `${row.label} (ShortcutsHelp.tsx:${row.line}): ${error}`);
      assert.ok(chords.length > 0, `${row.label} parsed to no chord at all`);
      for (const chord of chords) {
        assert.equal(chord.id, [...chord.mods, chord.key].join("+"));
      }
    }
  });
});

describe("no two bindings are the same keystroke", () => {
  /** chord id → the rows claiming it. */
  const claims = new Map<string, typeof ledger.rows>();
  for (const row of ledger.rows) {
    if (row.keys === null) continue;
    for (const chord of parseKeys(row.keys).chords) {
      const at = claims.get(chord.id) ?? [];
      at.push(row);
      claims.set(chord.id, at);
    }
  }

  const pairs: Array<{ id: string; a: string; b: string }> = [];
  for (const [id, rows] of claims) {
    for (let i = 0; i < rows.length; i++) {
      for (let j = i + 1; j < rows.length; j++) {
        if (scopeOverlap(rows[i], rows[j]) !== null) {
          pairs.push({ id, a: rows[i].label, b: rows[j].label });
        }
      }
    }
  }

  it("has no undeclared collision", () => {
    const declared = new Set(RESOLVED.map((r) => `${r.chord} ${[...r.rows].sort().join(" ")}`));
    const undeclared = pairs
      .filter((p) => !declared.has(`${p.id} ${[p.a, p.b].sort().join(" ")}`))
      .map((p) => `${p.id}: ${p.a} ⊗ ${p.b}`);
    assert.deepEqual(undeclared, []);
  });

  it("declares no collision that has stopped happening", () => {
    // Dead exceptions are the reason check-i18n fails on a dead dictionary
    // key: an argued-out paragraph about two rows that no longer meet is a
    // claim the next reader will believe.
    const live = new Set(pairs.map((p) => `${p.id} ${[p.a, p.b].sort().join(" ")}`));
    for (const entry of RESOLVED) {
      const token = `${entry.chord} ${[...entry.rows].sort().join(" ")}`;
      assert.ok(live.has(token), `RESOLVED still names ${entry.rows.join(" ⊗ ")} on ${entry.chord}`);
    }
  });

  it("every declared collision says where the tie is broken", () => {
    for (const entry of RESOLVED) {
      assert.ok(entry.why.length > 80, `${entry.chord}: "${entry.why}" is a shrug, not a rule`);
    }
  });

  it("counts a shell and a runtime as scope, and admin as neither", () => {
    const app = { shell: "app" as const, desktop: false };
    const blog = { shell: "blog" as const, desktop: false };
    const both = { shell: null, desktop: false };
    const only = { shell: null, desktop: true };
    assert.equal(scopeOverlap(app, blog), null);
    assert.deepEqual(scopeOverlap(app, both)?.shells, ["app"]);
    assert.deepEqual(scopeOverlap(both, only)?.runtimes, ["desktop"]);
    // An admin session sees the visitor's rows AND its own, so `admin` can
    // never keep two bindings apart — the reader with the most keys is
    // exactly the reader a collision reaches.
    assert.notEqual(scopeOverlap({ ...both }, { ...both }), null);
  });
});

describe("docs/keymap.md is a rendering of the ledger", () => {
  it("parses, between its markers", () => {
    assert.deepEqual(doc.errors, []);
    assert.ok(doc.chords.length > 0);
  });

  it("claims exactly the chords GROUPS binds", () => {
    const bound = new Set(
      ledger.rows.flatMap((r) => (r.keys === null ? [] : parseKeys(r.keys).chords.map((c) => c.id))),
    );
    const written = new Set(doc.chords.map((d) => d.chord.id));
    assert.deepEqual([...bound].filter((id) => !written.has(id)), [], "bound, undocumented");
    assert.deepEqual([...written].filter((id) => !bound.has(id)), [], "documented, unbound");
  });
});

describe("the chord grammar", () => {
  it("reads a plain chord", () => {
    assert.deepEqual(parseKeys(["Ctrl/Cmd", "Alt", "Shift", "B"]).chords.map((c) => c.id), [
      "Mod+Alt+Shift+b",
    ]);
  });

  it("expands the alternatives a single row prints", () => {
    // `Ctrl/Cmd` `↑ / ↓` is ONE row because move-line-up and move-line-down
    // are one thing to learn, and two chords because they are two keystrokes.
    assert.deepEqual(parseKeys(["Ctrl/Cmd", "↑ / ↓"]).chords.map((c) => c.id), [
      "Mod+Up",
      "Mod+Down",
    ]);
    assert.deepEqual(parseKeys(["← / →"]).chords.map((c) => c.id), ["Left", "Right"]);
  });

  it("keeps the slash that is a KEY out of the slash that is a separator", () => {
    // Ctrl/Cmd+/ opens the sheet this whole ledger lives in. Splitting its
    // key token on a bare "/" made it two empty alternatives — the binding
    // most likely to be pressed first, parsed into nothing.
    assert.deepEqual(parseKeys(["Ctrl/Cmd", "/"]).chords.map((c) => c.id), ["Mod+/"]);
  });

  it("refuses a spelling that would hide a collision", () => {
    assert.match(parseKeys(["Ctrl/Cmd", "Shift", "Alt", "T"]).error ?? "", /out of order/);
    assert.match(parseKeys(["Cmd", "T"]).error ?? "", /not a modifier/);
    assert.match(parseKeys(["Ctrl/Cmd", "Shift"]).error ?? "", /is a modifier, not a key/);
    assert.match(parseKeys(["Ctrl/Cmd", "Meta"]).error ?? "", /not a key/);
    assert.match(parseKeys([]).error ?? "", /empty/);
  });
});

// THE WAY BACK's chord: the twin's plus Shift, a keystroke of its own, bound
// in the global listener, for an admin, in both shells.
describe("the chrome-language chord", () => {
  const row = ledger.rows.find((r) => r.label === "scChromeLang");
  it("is in the ledger as Ctrl/Cmd Alt Shift L, for an admin, in both shells", () => {
    assert.ok(row, "no scChromeLang row");
    assert.deepEqual(parseKeys(row.keys ?? []).chords.map((c) => c.id), ["Mod+Alt+Shift+l"]);
    assert.equal(row.admin, true);
    assert.equal(row.shell ?? null, null);
  });
  it("is not the twin's chord", () => {
    const twin = ledger.rows.find((r) => r.label === "cmdTwinSwitch");
    assert.ok(twin);
    assert.notDeepEqual(parseKeys(twin.keys ?? []).chords.map((c) => c.id), parseKeys(row?.keys ?? []).chords.map((c) => c.id));
  });
  it("is answered in the global listener, which keeps Shift away from the twin", () => {
    const src = read("../client/globalKeys.ts");
    assert.match(src, /keymap: scChromeLang/);
    assert.match(src, /e\.altKey && e\.shiftKey && isKey\(e, "l"\)[^\n]*\n[^\n]*keymap: scChromeLang[\s\S]{0,120}toggleChromeLang\(\)/);
    assert.match(src, /key === "l" && e\.altKey && !e\.shiftKey/);
  });
});