import { describe, expect, it } from "vite-plus/test";import { captureConfigBinding, editCaptureConfig, niriConfigConflict, niriConfigIncludes,} from "./captureConfigEdit.ts";import { readKdlNodes } from "./captureConfigKdl.ts"; const app = "com.t3tools.T3Code";const binding = captureConfigBinding("niri", app, "Ctrl+Shift+2"); describe("Niri capture config edits", () => { it("inserts into the existing top-level binds, preserving unrelated text", () => { const before = '// My desktop\ninput { keyboard { xkb { layout "us"; }; }; }\nbinds {\n Mod+Q { spawn "kitty"; } // keep me\n}\n'; const result = editCaptureConfig(before, "niri", app, "install"); expect(result.after).toBe(before.replace("\n}\n", `\n ${binding}\n}\n`)); expect(readKdlNodes(result.after).filter((node) => node.name === "binds")).toHaveLength(1); }); it("ignores commented, nested, and string-valued binds and braces", () => { const before = `// binds { fake\n/* binds { /* nested */ } */\n/- binds { Ctrl+Shift+2 { quit; } }\nrecent-windows { binds { Alt+Tab { next-window; } }; }\nspawn-at-startup "echo" r#"binds { }"#\n"binds" {\n Mod+Q { spawn "a } \\" b"; }\n}\n`; // A quoted brace in a valid shell argument must not close the binds section. const valid = before.replace('"a } \\" b"', '"a } b"'); const result = editCaptureConfig(valid, "niri", app, "install"); expect(result.after).toContain(` ${binding}\n}\n`); expect(result.after.startsWith(valid.slice(0, valid.indexOf('"binds" {')))).toBe(true); }); it.each(["binds {}", "binds { Mod+Q { quit; }; }", "input { }\n", ""])( "handles compact or absent binds: %s", (before) => { const result = editCaptureConfig(before, "niri", app, "install"); const block = readKdlNodes(result.after).find((node) => node.name === "binds")!; expect(block.children.some((node) => node.name === "Ctrl+Shift+2")).toBe(true); }, ); it("preserves CRLF and the UTF-8 BOM", () => { const before = "\uFEFFbinds {\r\n}\r\n"; expect(editCaptureConfig(before, "niri", app, "install").after).toBe( `\uFEFFbinds {\r\n ${binding}\r\n}\r\n`, ); }); it("preserves Unicode spacing around nodes and line continuations", () => { const spaces = "\u00A0\u1680\u2000\u2001\u2002\u2003\u2004\u2005\u2006\u2007\u2008\u2009\u200A\u202F\u205F\u3000"; const before = `binds${spaces}\\${spaces}\n{\n}\n`; expect(editCaptureConfig(before, "niri", app, "install").after).toBe( before.replace("}\n", ` ${binding}\n}\n`), ); }); it("does not duplicate an existing binding, including a custom chord", () => { const before = `binds {\n ${binding.replace("Ctrl+Shift+2", "Alt+Ctrl+Y")}\n}\n`; const result = editCaptureConfig(before, "niri", app, "install"); expect(result.after).toBe(before); expect(result.shortcut).toBe("Ctrl+Alt+Y"); }); it("replaces and removes only this app's capture binding", () => { const other = captureConfigBinding("niri", "com.t3tools.Other", "Ctrl+Alt+4"); const before = `binds {\n ${binding}\n ${other}\n}\n`; const result = editCaptureConfig(before, "niri", app, "install", "Ctrl+Alt+Y"); expect(result.after).toContain(other); expect(result.after).not.toContain("Ctrl+Shift+2"); expect(result.after).toContain("Ctrl+Alt+Y"); const removed = editCaptureConfig(result.after, "niri", app, "remove"); expect(removed.after).toBe(`binds {\n ${other}\n}\n`); }); it("refuses occupied keys regardless of modifier order, without removing them", () => { const before = "binds { Shift+Control+2 { quit; } }"; expect(niriConfigConflict(before, app, "Ctrl+Shift+2")).toBe(true); expect(() => editCaptureConfig(before, "niri", app, "install")).toThrow("already used"); }); it("does not count a slash-dash-disabled key as occupied", () => { expect(niriConfigConflict("binds { /- Ctrl+Shift+2 { quit; }; }", app, "Ctrl+Shift+2")).toBe( false, ); }); it.each(["binds {", "binds {}\nbinds {}", "/* unfinished", 'spawn "unfinished'])( "fails safely for malformed structure: %s", (source) => { expect(() => editCaptureConfig(source, "niri", app, "install")).toThrow(); }, ); it("reads only real top-level include directives", () => { expect( niriConfigIncludes( '// include "no"\n/- include "no"\ninclude "keys.kdl"\ninclude optional=true "extra.kdl"', ), ).toEqual([ { path: "keys.kdl", optional: false }, { path: "extra.kdl", optional: true }, ]); });}); describe("Hyprland capture config edits", () => { it.each(["hyprland", "hyprland-lua"] as const)( "is idempotent and reversible for %s", (format) => { const before = format === "hyprland" ? "# Keep my preferences\n$mainMod = SUPER\n" : "-- Keep my preferences\nhl.config({ general = { border_size = 2 } })\n"; const added = editCaptureConfig(before, format, app, "install"); expect(added.after.startsWith(before)).toBe(true); expect(editCaptureConfig(added.after, format, app, "install").after).toBe(added.after); const changed = editCaptureConfig(added.after, format, app, "install", "Ctrl+Alt+Y"); expect(changed.after).not.toContain(captureConfigBinding(format, app, "Ctrl+Shift+2")); expect(changed.after).toContain(captureConfigBinding(format, app, "Ctrl+Alt+Y")); expect(editCaptureConfig(changed.after, format, app, "remove").after.trim()).toBe( before.trim(), ); }, ); it("does not steal an existing legacy shortcut", () => { expect(() => editCaptureConfig("bind = SHIFT CTRL, 2, exec, kitty\n", "hyprland", app, "install"), ).toThrow("already used"); }); it("leaves commented bindings alone", () => { const before = "# bind = CTRL SHIFT, 2, global, com.t3tools.T3Code:capture-window\n"; expect(editCaptureConfig(before, "hyprland", app, "remove").after).toBe(before); }); it.each([ "return {}", '--[[\nhl.bind("CTRL + SHIFT + 2", hl.dsp.global("com.t3tools.T3Code:capture-window"))\n]]', ])("does not guess how to edit complex Lua", (source) => { expect(() => editCaptureConfig(source, "hyprland-lua", app, "install")).toThrow("manual edit"); }); it.each(['Ctrl+"\nexec', "Ctrl+;", "2", "Mod+2"])( "rejects unsafe or ambiguous keys: %s", (keys) => { expect(() => editCaptureConfig("", "hyprland", app, "install", keys)).toThrow(); }, );});
import { describe, expect, it } from "vite-plus/test";
import {
captureConfigBinding,
editCaptureConfig,
niriConfigConflict,
niriConfigIncludes,
} from "./captureConfigEdit.ts";
import { readKdlNodes } from "./captureConfigKdl.ts";
const app = "com.t3tools.T3Code";
const binding = captureConfigBinding("niri", app, "Ctrl+Shift+2");
describe("Niri capture config edits", () => {
it("inserts into the existing top-level binds, preserving unrelated text", () => {
const before =
'// My desktop\ninput { keyboard { xkb { layout "us"; }; }; }\nbinds {\n Mod+Q { spawn "kitty"; } // keep me\n}\n';
const result = editCaptureConfig(before, "niri", app, "install");
expect(result.after).toBe(before.replace("\n}\n", `\n ${binding}\n}\n`));
expect(readKdlNodes(result.after).filter((node) => node.name === "binds")).toHaveLength(1);
});
it("ignores commented, nested, and string-valued binds and braces", () => {
const before = `// binds { fake\n/* binds { /* nested */ } */\n/- binds { Ctrl+Shift+2 { quit; } }\nrecent-windows { binds { Alt+Tab { next-window; } }; }\nspawn-at-startup "echo" r#"binds { }"#\n"binds" {\n Mod+Q { spawn "a } \\" b"; }\n}\n`;
// A quoted brace in a valid shell argument must not close the binds section.
const valid = before.replace('"a } \\" b"', '"a } b"');
const result = editCaptureConfig(valid, "niri", app, "install");
expect(result.after).toContain(` ${binding}\n}\n`);
expect(result.after.startsWith(valid.slice(0, valid.indexOf('"binds" {')))).toBe(true);
});
it.each(["binds {}", "binds { Mod+Q { quit; }; }", "input { }\n", ""])(
"handles compact or absent binds: %s",
(before) => {
const result = editCaptureConfig(before, "niri", app, "install");
const block = readKdlNodes(result.after).find((node) => node.name === "binds")!;
expect(block.children.some((node) => node.name === "Ctrl+Shift+2")).toBe(true);
},
);
it("preserves CRLF and the UTF-8 BOM", () => {
const before = "\uFEFFbinds {\r\n}\r\n";
expect(editCaptureConfig(before, "niri", app, "install").after).toBe(
`\uFEFFbinds {\r\n ${binding}\r\n}\r\n`,
);
});
it("preserves Unicode spacing around nodes and line continuations", () => {
const spaces =
"\u00A0\u1680\u2000\u2001\u2002\u2003\u2004\u2005\u2006\u2007\u2008\u2009\u200A\u202F\u205F\u3000";
const before = `binds${spaces}\\${spaces}\n{\n}\n`;
expect(editCaptureConfig(before, "niri", app, "install").after).toBe(
before.replace("}\n", ` ${binding}\n}\n`),
);
});
it("does not duplicate an existing binding, including a custom chord", () => {
const before = `binds {\n ${binding.replace("Ctrl+Shift+2", "Alt+Ctrl+Y")}\n}\n`;
const result = editCaptureConfig(before, "niri", app, "install");
expect(result.after).toBe(before);
expect(result.shortcut).toBe("Ctrl+Alt+Y");
});
it("replaces and removes only this app's capture binding", () => {
const other = captureConfigBinding("niri", "com.t3tools.Other", "Ctrl+Alt+4");
const before = `binds {\n ${binding}\n ${other}\n}\n`;
const result = editCaptureConfig(before, "niri", app, "install", "Ctrl+Alt+Y");
expect(result.after).toContain(other);
expect(result.after).not.toContain("Ctrl+Shift+2");
expect(result.after).toContain("Ctrl+Alt+Y");
const removed = editCaptureConfig(result.after, "niri", app, "remove");
expect(removed.after).toBe(`binds {\n ${other}\n}\n`);
});
it("refuses occupied keys regardless of modifier order, without removing them", () => {
const before = "binds { Shift+Control+2 { quit; } }";
expect(niriConfigConflict(before, app, "Ctrl+Shift+2")).toBe(true);
expect(() => editCaptureConfig(before, "niri", app, "install")).toThrow("already used");
});
it("does not count a slash-dash-disabled key as occupied", () => {
expect(niriConfigConflict("binds { /- Ctrl+Shift+2 { quit; }; }", app, "Ctrl+Shift+2")).toBe(
false,
);
});
it.each(["binds {", "binds {}\nbinds {}", "/* unfinished", 'spawn "unfinished'])(
"fails safely for malformed structure: %s",
(source) => {
expect(() => editCaptureConfig(source, "niri", app, "install")).toThrow();
},
);
it("reads only real top-level include directives", () => {
expect(
niriConfigIncludes(
'// include "no"\n/- include "no"\ninclude "keys.kdl"\ninclude optional=true "extra.kdl"',
),
).toEqual([
{ path: "keys.kdl", optional: false },
{ path: "extra.kdl", optional: true },
]);
});
});
describe("Hyprland capture config edits", () => {
it.each(["hyprland", "hyprland-lua"] as const)(
"is idempotent and reversible for %s",
(format) => {
const before =
format === "hyprland"
? "# Keep my preferences\n$mainMod = SUPER\n"
: "-- Keep my preferences\nhl.config({ general = { border_size = 2 } })\n";
const added = editCaptureConfig(before, format, app, "install");
expect(added.after.startsWith(before)).toBe(true);
expect(editCaptureConfig(added.after, format, app, "install").after).toBe(added.after);
const changed = editCaptureConfig(added.after, format, app, "install", "Ctrl+Alt+Y");
expect(changed.after).not.toContain(captureConfigBinding(format, app, "Ctrl+Shift+2"));
expect(changed.after).toContain(captureConfigBinding(format, app, "Ctrl+Alt+Y"));
expect(editCaptureConfig(changed.after, format, app, "remove").after.trim()).toBe(
before.trim(),
);
},
);
it("does not steal an existing legacy shortcut", () => {
expect(() =>
editCaptureConfig("bind = SHIFT CTRL, 2, exec, kitty\n", "hyprland", app, "install"),
).toThrow("already used");
});
it("leaves commented bindings alone", () => {
const before = "# bind = CTRL SHIFT, 2, global, com.t3tools.T3Code:capture-window\n";
expect(editCaptureConfig(before, "hyprland", app, "remove").after).toBe(before);
});
it.each([
"return {}",
'--[[\nhl.bind("CTRL + SHIFT + 2", hl.dsp.global("com.t3tools.T3Code:capture-window"))\n]]',
])("does not guess how to edit complex Lua", (source) => {
expect(() => editCaptureConfig(source, "hyprland-lua", app, "install")).toThrow("manual edit");
});
it.each(['Ctrl+"\nexec', "Ctrl+;", "2", "Mod+2"])(
"rejects unsafe or ambiguous keys: %s",
(keys) => {
expect(() => editCaptureConfig("", "hyprland", app, "install", keys)).toThrow();
},
);
});