@agnishc/edb-ask-user 0.10.6 → 0.10.9
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +4 -0
- package/package.json +1 -1
- package/src/utils.test.ts +131 -0
package/CHANGELOG.md
CHANGED
package/package.json
CHANGED
|
@@ -0,0 +1,131 @@
|
|
|
1
|
+
import { afterEach, beforeEach, describe, expect, it, vi } from "vitest";
|
|
2
|
+
import { acquireModalLock, isModalActive, sleep, wrapText } from "./utils.js";
|
|
3
|
+
|
|
4
|
+
// Mock visibleWidth to treat each ASCII char as width 1
|
|
5
|
+
vi.mock("@earendil-works/pi-tui", () => ({
|
|
6
|
+
visibleWidth: (s: string) => s.replace(/\x1b\[[0-9;]*m/g, "").length,
|
|
7
|
+
}));
|
|
8
|
+
|
|
9
|
+
// Reset modal lock between tests
|
|
10
|
+
const MODAL_LOCK_SYMBOL = Symbol.for("edb.ask-user.modal-lock");
|
|
11
|
+
function resetModalLock() {
|
|
12
|
+
const host = globalThis as Record<symbol, unknown>;
|
|
13
|
+
delete host[MODAL_LOCK_SYMBOL];
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
beforeEach(() => resetModalLock());
|
|
17
|
+
afterEach(() => resetModalLock());
|
|
18
|
+
|
|
19
|
+
describe("wrapText", () => {
|
|
20
|
+
it("returns single empty line for empty input", () => {
|
|
21
|
+
const lines = wrapText("", 40);
|
|
22
|
+
expect(lines).toEqual([""]);
|
|
23
|
+
});
|
|
24
|
+
|
|
25
|
+
it("returns single line when text fits", () => {
|
|
26
|
+
const lines = wrapText("hello world", 40);
|
|
27
|
+
expect(lines).toHaveLength(1);
|
|
28
|
+
expect(lines[0]).toBe("hello world");
|
|
29
|
+
});
|
|
30
|
+
|
|
31
|
+
it("wraps multiple words across lines", () => {
|
|
32
|
+
const lines = wrapText("one two three four five six seven eight nine ten", 15, 4);
|
|
33
|
+
expect(lines.length).toBeGreaterThan(1);
|
|
34
|
+
// Each line should be <= 15 chars visible width
|
|
35
|
+
for (const line of lines) {
|
|
36
|
+
expect(line.length).toBeLessThanOrEqual(15);
|
|
37
|
+
}
|
|
38
|
+
});
|
|
39
|
+
|
|
40
|
+
it("respects maxLines limit", () => {
|
|
41
|
+
const longText = "one two three four five six seven eight nine ten eleven twelve";
|
|
42
|
+
const lines = wrapText(longText, 10, 3);
|
|
43
|
+
expect(lines.length).toBeLessThanOrEqual(3);
|
|
44
|
+
});
|
|
45
|
+
|
|
46
|
+
it("adds ellipsis when text exceeds maxLines", () => {
|
|
47
|
+
const lines = wrapText("one two three four five six seven eight nine ten", 10, 2);
|
|
48
|
+
// At least one line should end with ellipsis
|
|
49
|
+
const hasEllipsis = lines.some((l) => l.endsWith("…"));
|
|
50
|
+
expect(hasEllipsis).toBe(true);
|
|
51
|
+
});
|
|
52
|
+
|
|
53
|
+
it("handles whitespace-only input", () => {
|
|
54
|
+
const lines = wrapText(" ", 40);
|
|
55
|
+
expect(lines).toEqual([""]);
|
|
56
|
+
});
|
|
57
|
+
|
|
58
|
+
it("handles single word longer than width", () => {
|
|
59
|
+
const lines = wrapText("superlongword", 5, 4);
|
|
60
|
+
expect(lines.length).toBeLessThanOrEqual(4);
|
|
61
|
+
});
|
|
62
|
+
});
|
|
63
|
+
|
|
64
|
+
describe("isModalActive", () => {
|
|
65
|
+
it("returns false when no modal is active", () => {
|
|
66
|
+
expect(isModalActive()).toBe(false);
|
|
67
|
+
});
|
|
68
|
+
|
|
69
|
+
it("returns true when modal is acquired", () => {
|
|
70
|
+
const release = acquireModalLock();
|
|
71
|
+
expect(isModalActive()).toBe(true);
|
|
72
|
+
release();
|
|
73
|
+
});
|
|
74
|
+
|
|
75
|
+
it("returns false after release", () => {
|
|
76
|
+
const release = acquireModalLock();
|
|
77
|
+
release();
|
|
78
|
+
expect(isModalActive()).toBe(false);
|
|
79
|
+
});
|
|
80
|
+
});
|
|
81
|
+
|
|
82
|
+
describe("acquireModalLock", () => {
|
|
83
|
+
it("returns a release function", () => {
|
|
84
|
+
const release = acquireModalLock();
|
|
85
|
+
expect(typeof release).toBe("function");
|
|
86
|
+
});
|
|
87
|
+
|
|
88
|
+
it("supports nested locks", () => {
|
|
89
|
+
const release1 = acquireModalLock();
|
|
90
|
+
const release2 = acquireModalLock();
|
|
91
|
+
expect(isModalActive()).toBe(true);
|
|
92
|
+
release1();
|
|
93
|
+
expect(isModalActive()).toBe(true);
|
|
94
|
+
release2();
|
|
95
|
+
expect(isModalActive()).toBe(false);
|
|
96
|
+
});
|
|
97
|
+
|
|
98
|
+
it("calling release twice is safe", () => {
|
|
99
|
+
const release = acquireModalLock();
|
|
100
|
+
release();
|
|
101
|
+
expect(() => release()).not.toThrow();
|
|
102
|
+
});
|
|
103
|
+
});
|
|
104
|
+
|
|
105
|
+
describe("sleep", () => {
|
|
106
|
+
it("resolves after specified ms", async () => {
|
|
107
|
+
vi.useFakeTimers();
|
|
108
|
+
const promise = sleep(100);
|
|
109
|
+
expect(isModalActive()).toBe(false); // Not related, just showing fake timers work
|
|
110
|
+
|
|
111
|
+
vi.advanceTimersByTime(100);
|
|
112
|
+
await promise;
|
|
113
|
+
vi.useRealTimers();
|
|
114
|
+
});
|
|
115
|
+
|
|
116
|
+
it("does not resolve before ms elapses", async () => {
|
|
117
|
+
vi.useFakeTimers();
|
|
118
|
+
let resolved = false;
|
|
119
|
+
sleep(100).then(() => {
|
|
120
|
+
resolved = true;
|
|
121
|
+
});
|
|
122
|
+
|
|
123
|
+
vi.advanceTimersByTime(50);
|
|
124
|
+
expect(resolved).toBe(false);
|
|
125
|
+
|
|
126
|
+
vi.advanceTimersByTime(50);
|
|
127
|
+
await vi.runAllTimersAsync();
|
|
128
|
+
expect(resolved).toBe(true);
|
|
129
|
+
vi.useRealTimers();
|
|
130
|
+
});
|
|
131
|
+
});
|