Skip to content

Commit 5e41b12

Browse files
committed
test: add basic test for KeyListener
1 parent fdad5f2 commit 5e41b12

File tree

4 files changed

+166
-2
lines changed

4 files changed

+166
-2
lines changed

.github/workflows/node.js.yml

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,9 @@ on:
55
branches: [master]
66
pull_request:
77

8+
env:
9+
FORCE_COLOR: "1"
10+
811
permissions:
912
contents: read
1013

src/class/Cursor.class.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -26,7 +26,7 @@ export class Cursor {
2626

2727
right() {
2828
if (this.position < this.offset) {
29-
process.stdout.moveCursor(1, 0);
29+
this.output.moveCursor(1, 0);
3030
this.position++;
3131

3232
return true;

test/KeyListener.class.test.ts

Lines changed: 141 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,141 @@
1+
// Import Node.js Dependencies
2+
import { describe, test, mock, beforeEach } from "node:test";
3+
import assert from "node:assert";
4+
import * as TTY from "node:tty";
5+
6+
// Import Internal Dependencies
7+
import { KeyListener } from "../src/class/KeyListener.class.js";
8+
9+
type MockTTYWriteStream = {
10+
write: ReturnType<typeof mock.fn>;
11+
moveCursor: ReturnType<typeof mock.fn>;
12+
clearLine: ReturnType<typeof mock.fn>;
13+
};
14+
15+
describe("KeyListener", () => {
16+
let mockOutput: MockTTYWriteStream;
17+
18+
beforeEach(() => {
19+
mockOutput = {
20+
write: mock.fn(),
21+
moveCursor: mock.fn(),
22+
clearLine: mock.fn()
23+
};
24+
});
25+
26+
test("should handle basic text input", () => {
27+
const keyListener = new KeyListener({}, mockOutput as unknown as TTY.WriteStream);
28+
const exitCallback = mock.fn();
29+
const listener = keyListener.createListener(exitCallback);
30+
31+
listener("a", { name: "a" });
32+
listener("b", { name: "b" });
33+
listener("c", { name: "c" });
34+
35+
assert.equal(exitCallback.mock.calls.length, 0);
36+
assert.equal(mockOutput.write.mock.calls.length, 3);
37+
});
38+
39+
test("should handle enter key to submit input", () => {
40+
const keyListener = new KeyListener({}, mockOutput as unknown as TTY.WriteStream);
41+
const exitCallback = mock.fn();
42+
const listener = keyListener.createListener(exitCallback);
43+
44+
listener("a", { name: "a" });
45+
listener("b", { name: "b" });
46+
listener("", { name: "return" });
47+
48+
assert.equal(exitCallback.mock.calls.length, 1);
49+
assert.equal(exitCallback.mock.calls[0].arguments[0], "ab");
50+
});
51+
52+
test("should handle escape key to submit input", () => {
53+
const keyListener = new KeyListener({}, mockOutput as unknown as TTY.WriteStream);
54+
const exitCallback = mock.fn();
55+
const listener = keyListener.createListener(exitCallback);
56+
57+
listener("test", { name: "t" });
58+
listener("", { name: "escape" });
59+
60+
assert.equal(exitCallback.mock.calls.length, 1);
61+
assert.equal(exitCallback.mock.calls[0].arguments[0], "test");
62+
});
63+
64+
test("should handle ctrl+c to submit input", () => {
65+
const keyListener = new KeyListener({}, mockOutput as unknown as TTY.WriteStream);
66+
const exitCallback = mock.fn();
67+
const listener = keyListener.createListener(exitCallback);
68+
69+
listener("test", { name: "t" });
70+
listener("", { name: "c", ctrl: true });
71+
72+
assert.equal(exitCallback.mock.calls.length, 1);
73+
assert.equal(exitCallback.mock.calls[0].arguments[0], "test");
74+
});
75+
76+
test("should handle history navigation", () => {
77+
const keyListener = new KeyListener(
78+
{ history: ["command1", "command2"] },
79+
mockOutput as unknown as TTY.WriteStream
80+
);
81+
const exitCallback = mock.fn();
82+
const listener = keyListener.createListener(exitCallback);
83+
84+
// Navigate up
85+
listener("", { name: "up" });
86+
assert.equal(mockOutput.write.mock.calls.length, 1);
87+
assert.equal(mockOutput.clearLine.mock.calls.length, 1);
88+
89+
// Navigate down
90+
listener("", { name: "down" });
91+
assert.equal(mockOutput.write.mock.calls.length, 2);
92+
assert.equal(mockOutput.clearLine.mock.calls.length, 2);
93+
});
94+
95+
test("should handle autocomplete", () => {
96+
const keyListener = new KeyListener(
97+
{ autocomplete: ["test1", "test2"] },
98+
mockOutput as unknown as TTY.WriteStream
99+
);
100+
const exitCallback = mock.fn();
101+
const listener = keyListener.createListener(exitCallback);
102+
103+
// Type 't' to trigger autocomplete
104+
listener("t", { name: "t" });
105+
assert.equal(mockOutput.write.mock.calls.length, 1);
106+
107+
// Press tab to complete
108+
listener("", { name: "tab" });
109+
assert.equal(mockOutput.write.mock.calls.length, 2);
110+
});
111+
112+
test("should handle backspace", () => {
113+
const keyListener = new KeyListener({}, mockOutput as unknown as TTY.WriteStream);
114+
const exitCallback = mock.fn();
115+
const listener = keyListener.createListener(exitCallback);
116+
117+
listener("a", { name: "a" });
118+
listener("b", { name: "b" });
119+
listener("", { name: "backspace" });
120+
121+
assert.equal(mockOutput.write.mock.calls.length, 3);
122+
assert.equal(mockOutput.clearLine.mock.calls.length, 1);
123+
});
124+
125+
test("should handle cursor movement", () => {
126+
const keyListener = new KeyListener({}, mockOutput as unknown as TTY.WriteStream);
127+
const exitCallback = mock.fn();
128+
const listener = keyListener.createListener(exitCallback);
129+
130+
listener("a", { name: "a" });
131+
listener("b", { name: "b" });
132+
133+
// Move left
134+
listener("", { name: "left" });
135+
assert.equal(mockOutput.moveCursor.mock.calls.length, 1);
136+
137+
// Move right
138+
listener("", { name: "right" });
139+
assert.equal(mockOutput.moveCursor.mock.calls.length, 2);
140+
});
141+
});

test/utils.test.ts

Lines changed: 21 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,13 @@
11
// Import Node.js Dependencies
22
import { describe, test } from "node:test";
3+
import { styleText } from "node:util";
34
import assert from "node:assert";
45

56
// Import Internal Dependencies
6-
import { localMatchOf } from "../src/utils/index.js";
7+
import {
8+
localMatchOf,
9+
stringLength
10+
} from "../src/utils/index.js";
711

812
describe("localMatchOf", () => {
913
const arr = ["hello world", "hello everyone", "foo bar", "baz qux", "hello", "foobar"];
@@ -28,3 +32,19 @@ describe("localMatchOf", () => {
2832
assert.equal(localMatchOf(arr, "hello", true), " everyone");
2933
});
3034
});
35+
36+
describe("stringLength", () => {
37+
test("should detect length of emojis", () => {
38+
assert.equal(
39+
stringLength("😍😁"),
40+
2
41+
);
42+
});
43+
44+
test("should detect length with ANSI codes", () => {
45+
assert.equal(
46+
stringLength(styleText("blue", "hello")),
47+
5
48+
);
49+
});
50+
});

0 commit comments

Comments
 (0)