Open History to see the last commit
1import { describe, expect, it } from "vite-plus/test";
2import {
3 captureConfigBinding,
4 editCaptureConfig,
5 niriConfigConflict,
6 niriConfigIncludes,
7} from "./captureConfigEdit.ts";
8import { readKdlNodes } from "./captureConfigKdl.ts";
9
10const app = "com.t3tools.T3Code";
11const binding = captureConfigBinding("niri", app, "Ctrl+Shift+2");
12
13describe("Niri capture config edits", () => {
14 it("inserts into the existing top-level binds, preserving unrelated text", () => {
15 const before =
16 '// My desktop\ninput { keyboard { xkb { layout "us"; }; }; }\nbinds {\n Mod+Q { spawn "kitty"; } // keep me\n}\n';
17 const result = editCaptureConfig(before, "niri", app, "install");
18 expect(result.after).toBe(before.replace("\n}\n", `\n ${binding}\n}\n`));
19 expect(readKdlNodes(result.after).filter((node) => node.name === "binds")).toHaveLength(1);
20 });
21 it("ignores commented, nested, and string-valued binds and braces", () => {
22 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`;
23 const valid = before.replace('"a } \\" b"', '"a } b"');
24 const result = editCaptureConfig(valid, "niri", app, "install");
25 expect(result.after).toContain(` ${binding}\n}\n`);
26 expect(result.after.startsWith(valid.slice(0, valid.indexOf('"binds" {')))).toBe(true);
27 });
28 it.each(["binds {}", "binds { Mod+Q { quit; }; }", "input { }\n", ""])(
29 "handles compact or absent binds: %s",
30 (before) => {
31 const result = editCaptureConfig(before, "niri", app, "install");
32 const block = readKdlNodes(result.after).find((node) => node.name === "binds")!;
33 expect(block.children.some((node) => node.name === "Ctrl+Shift+2")).toBe(true);
34 },
35 );
36 it("preserves CRLF and the UTF-8 BOM", () => {
37 const before = "\uFEFFbinds {\r\n}\r\n";
38 expect(editCaptureConfig(before, "niri", app, "install").after).toBe(
39 `\uFEFFbinds {\r\n ${binding}\r\n}\r\n`,
40 );
41 });
42 it("does not duplicate an existing binding, including a custom chord", () => {
43 const before = `binds {\n ${binding.replace("Ctrl+Shift+2", "Alt+Ctrl+Y")}\n}\n`;
44 const result = editCaptureConfig(before, "niri", app, "install");
45 expect(result.after).toBe(before);
46 expect(result.shortcut).toBe("Ctrl+Alt+Y");
47 });
48 it("replaces and removes only this app's capture binding", () => {
49 const other = captureConfigBinding("niri", "com.t3tools.Other", "Ctrl+Alt+4");
50 const before = `binds {\n ${binding}\n ${other}\n}\n`;
51 const result = editCaptureConfig(before, "niri", app, "install", "Ctrl+Alt+Y");
52 expect(result.after).toContain(other);
53 expect(result.after).not.toContain("Ctrl+Shift+2");
54 expect(result.after).toContain("Ctrl+Alt+Y");
55 const removed = editCaptureConfig(result.after, "niri", app, "remove");
56 expect(removed.after).toBe(`binds {\n ${other}\n}\n`);
57 });
58 it("refuses occupied keys regardless of modifier order, without removing them", () => {
59 const before = "binds { Shift+Control+2 { quit; } }";
60 expect(niriConfigConflict(before, app, "Ctrl+Shift+2")).toBe(true);
61 expect(() => editCaptureConfig(before, "niri", app, "install")).toThrow("already used");
62 });
63 it.each(["binds {", "binds {}\nbinds {}", "/* unfinished", 'spawn "unfinished'])(
64 "fails safely for malformed structure: %s",
65 (source) => {
66 expect(() => editCaptureConfig(source, "niri", app, "install")).toThrow();
67 },
68 );
69 it("reads only real top-level include directives", () => {
70 expect(
71 niriConfigIncludes(
72 '// include "no"\n/- include "no"\ninclude "keys.kdl"\ninclude optional=true "extra.kdl"',
73 ),
74 ).toEqual([
75 { path: "keys.kdl", optional: false },
76 { path: "extra.kdl", optional: true },
77 ]);
78 });
79});
80
81describe("Hyprland capture config edits", () => {
82 it.each(["hyprland", "hyprland-lua"] as const)(
83 "is idempotent and reversible for %s",
84 (format) => {
85 const before =
86 format === "hyprland"
87 ? "# Keep my preferences\n$mainMod = SUPER\n"
88 : "-- Keep my preferences\nhl.config({ general = { border_size = 2 } })\n";
89 const added = editCaptureConfig(before, format, app, "install");
90 expect(added.after.startsWith(before)).toBe(true);
91 expect(editCaptureConfig(added.after, format, app, "install").after).toBe(added.after);
92 const changed = editCaptureConfig(added.after, format, app, "install", "Ctrl+Alt+Y");
93 expect(changed.after).not.toContain(captureConfigBinding(format, app, "Ctrl+Shift+2"));
94 expect(changed.after).toContain(captureConfigBinding(format, app, "Ctrl+Alt+Y"));
95 expect(editCaptureConfig(changed.after, format, app, "remove").after.trim()).toBe(
96 before.trim(),
97 );
98 },
99 );
100 it("does not steal an existing legacy shortcut", () => {
101 expect(() =>
102 editCaptureConfig("bind = SHIFT CTRL, 2, exec, kitty\n", "hyprland", app, "install"),
103 ).toThrow("already used");
104 });
105 it.each([
106 "return {}",
107 '--[[\nhl.bind("CTRL + SHIFT + 2", hl.dsp.global("com.t3tools.T3Code:capture-window"))\n]]',
108 ])("does not guess how to edit complex Lua", (source) => {
109 expect(() => editCaptureConfig(source, "hyprland-lua", app, "install")).toThrow("manual edit");
110 });
111 it.each(['Ctrl+"\nexec', "Ctrl+;", "2", "Mod+2"])(
112 "rejects unsafe or ambiguous keys: %s",
113 (keys) => {
114 expect(() => editCaptureConfig("", "hyprland", app, "install", keys)).toThrow();
115 },
116 );
117});