@task-mcp/shared 1.0.20 → 1.0.21

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.
Files changed (167) hide show
  1. package/package.json +1 -6
  2. package/dist/algorithms/critical-path.d.ts +0 -46
  3. package/dist/algorithms/critical-path.d.ts.map +0 -1
  4. package/dist/algorithms/critical-path.js +0 -320
  5. package/dist/algorithms/critical-path.js.map +0 -1
  6. package/dist/algorithms/critical-path.test.d.ts +0 -2
  7. package/dist/algorithms/critical-path.test.d.ts.map +0 -1
  8. package/dist/algorithms/critical-path.test.js +0 -194
  9. package/dist/algorithms/critical-path.test.js.map +0 -1
  10. package/dist/algorithms/dependency-integrity.d.ts +0 -81
  11. package/dist/algorithms/dependency-integrity.d.ts.map +0 -1
  12. package/dist/algorithms/dependency-integrity.js +0 -207
  13. package/dist/algorithms/dependency-integrity.js.map +0 -1
  14. package/dist/algorithms/dependency-integrity.test.d.ts +0 -2
  15. package/dist/algorithms/dependency-integrity.test.d.ts.map +0 -1
  16. package/dist/algorithms/dependency-integrity.test.js +0 -309
  17. package/dist/algorithms/dependency-integrity.test.js.map +0 -1
  18. package/dist/algorithms/index.d.ts +0 -5
  19. package/dist/algorithms/index.d.ts.map +0 -1
  20. package/dist/algorithms/index.js +0 -5
  21. package/dist/algorithms/index.js.map +0 -1
  22. package/dist/algorithms/tech-analysis.d.ts +0 -106
  23. package/dist/algorithms/tech-analysis.d.ts.map +0 -1
  24. package/dist/algorithms/tech-analysis.js +0 -344
  25. package/dist/algorithms/tech-analysis.js.map +0 -1
  26. package/dist/algorithms/tech-analysis.test.d.ts +0 -2
  27. package/dist/algorithms/tech-analysis.test.d.ts.map +0 -1
  28. package/dist/algorithms/tech-analysis.test.js +0 -338
  29. package/dist/algorithms/tech-analysis.test.js.map +0 -1
  30. package/dist/algorithms/topological-sort.d.ts +0 -41
  31. package/dist/algorithms/topological-sort.d.ts.map +0 -1
  32. package/dist/algorithms/topological-sort.js +0 -165
  33. package/dist/algorithms/topological-sort.js.map +0 -1
  34. package/dist/algorithms/topological-sort.test.d.ts +0 -2
  35. package/dist/algorithms/topological-sort.test.d.ts.map +0 -1
  36. package/dist/algorithms/topological-sort.test.js +0 -162
  37. package/dist/algorithms/topological-sort.test.js.map +0 -1
  38. package/dist/index.d.ts +0 -4
  39. package/dist/index.d.ts.map +0 -1
  40. package/dist/index.js +0 -7
  41. package/dist/index.js.map +0 -1
  42. package/dist/schemas/inbox.d.ts +0 -55
  43. package/dist/schemas/inbox.d.ts.map +0 -1
  44. package/dist/schemas/inbox.js +0 -25
  45. package/dist/schemas/inbox.js.map +0 -1
  46. package/dist/schemas/index.d.ts +0 -7
  47. package/dist/schemas/index.d.ts.map +0 -1
  48. package/dist/schemas/index.js +0 -17
  49. package/dist/schemas/index.js.map +0 -1
  50. package/dist/schemas/project.d.ts +0 -177
  51. package/dist/schemas/project.d.ts.map +0 -1
  52. package/dist/schemas/project.js +0 -56
  53. package/dist/schemas/project.js.map +0 -1
  54. package/dist/schemas/response-format.d.ts +0 -148
  55. package/dist/schemas/response-format.d.ts.map +0 -1
  56. package/dist/schemas/response-format.js +0 -18
  57. package/dist/schemas/response-format.js.map +0 -1
  58. package/dist/schemas/response-schema.d.ts +0 -307
  59. package/dist/schemas/response-schema.d.ts.map +0 -1
  60. package/dist/schemas/response-schema.js +0 -75
  61. package/dist/schemas/response-schema.js.map +0 -1
  62. package/dist/schemas/response-schema.test.d.ts +0 -2
  63. package/dist/schemas/response-schema.test.d.ts.map +0 -1
  64. package/dist/schemas/response-schema.test.js +0 -256
  65. package/dist/schemas/response-schema.test.js.map +0 -1
  66. package/dist/schemas/state.d.ts +0 -17
  67. package/dist/schemas/state.d.ts.map +0 -1
  68. package/dist/schemas/state.js +0 -17
  69. package/dist/schemas/state.js.map +0 -1
  70. package/dist/schemas/task.d.ts +0 -881
  71. package/dist/schemas/task.d.ts.map +0 -1
  72. package/dist/schemas/task.js +0 -189
  73. package/dist/schemas/task.js.map +0 -1
  74. package/dist/schemas/view.d.ts +0 -143
  75. package/dist/schemas/view.d.ts.map +0 -1
  76. package/dist/schemas/view.js +0 -48
  77. package/dist/schemas/view.js.map +0 -1
  78. package/dist/utils/dashboard-renderer.d.ts +0 -93
  79. package/dist/utils/dashboard-renderer.d.ts.map +0 -1
  80. package/dist/utils/dashboard-renderer.js +0 -424
  81. package/dist/utils/dashboard-renderer.js.map +0 -1
  82. package/dist/utils/dashboard-renderer.test.d.ts +0 -2
  83. package/dist/utils/dashboard-renderer.test.d.ts.map +0 -1
  84. package/dist/utils/dashboard-renderer.test.js +0 -774
  85. package/dist/utils/dashboard-renderer.test.js.map +0 -1
  86. package/dist/utils/date.d.ts +0 -94
  87. package/dist/utils/date.d.ts.map +0 -1
  88. package/dist/utils/date.js +0 -323
  89. package/dist/utils/date.js.map +0 -1
  90. package/dist/utils/date.test.d.ts +0 -2
  91. package/dist/utils/date.test.d.ts.map +0 -1
  92. package/dist/utils/date.test.js +0 -276
  93. package/dist/utils/date.test.js.map +0 -1
  94. package/dist/utils/hierarchy.d.ts +0 -102
  95. package/dist/utils/hierarchy.d.ts.map +0 -1
  96. package/dist/utils/hierarchy.js +0 -236
  97. package/dist/utils/hierarchy.js.map +0 -1
  98. package/dist/utils/hierarchy.test.d.ts +0 -2
  99. package/dist/utils/hierarchy.test.d.ts.map +0 -1
  100. package/dist/utils/hierarchy.test.js +0 -436
  101. package/dist/utils/hierarchy.test.js.map +0 -1
  102. package/dist/utils/id.d.ts +0 -60
  103. package/dist/utils/id.d.ts.map +0 -1
  104. package/dist/utils/id.js +0 -118
  105. package/dist/utils/id.js.map +0 -1
  106. package/dist/utils/id.test.d.ts +0 -2
  107. package/dist/utils/id.test.d.ts.map +0 -1
  108. package/dist/utils/id.test.js +0 -193
  109. package/dist/utils/id.test.js.map +0 -1
  110. package/dist/utils/index.d.ts +0 -12
  111. package/dist/utils/index.d.ts.map +0 -1
  112. package/dist/utils/index.js +0 -34
  113. package/dist/utils/index.js.map +0 -1
  114. package/dist/utils/natural-language.d.ts +0 -57
  115. package/dist/utils/natural-language.d.ts.map +0 -1
  116. package/dist/utils/natural-language.js +0 -211
  117. package/dist/utils/natural-language.js.map +0 -1
  118. package/dist/utils/natural-language.test.d.ts +0 -2
  119. package/dist/utils/natural-language.test.d.ts.map +0 -1
  120. package/dist/utils/natural-language.test.js +0 -197
  121. package/dist/utils/natural-language.test.js.map +0 -1
  122. package/dist/utils/priority-queue.d.ts +0 -17
  123. package/dist/utils/priority-queue.d.ts.map +0 -1
  124. package/dist/utils/priority-queue.js +0 -62
  125. package/dist/utils/priority-queue.js.map +0 -1
  126. package/dist/utils/priority-queue.test.d.ts +0 -2
  127. package/dist/utils/priority-queue.test.d.ts.map +0 -1
  128. package/dist/utils/priority-queue.test.js +0 -82
  129. package/dist/utils/priority-queue.test.js.map +0 -1
  130. package/dist/utils/projection.d.ts +0 -65
  131. package/dist/utils/projection.d.ts.map +0 -1
  132. package/dist/utils/projection.js +0 -180
  133. package/dist/utils/projection.js.map +0 -1
  134. package/dist/utils/projection.test.d.ts +0 -2
  135. package/dist/utils/projection.test.d.ts.map +0 -1
  136. package/dist/utils/projection.test.js +0 -336
  137. package/dist/utils/projection.test.js.map +0 -1
  138. package/dist/utils/terminal-ui.d.ts +0 -208
  139. package/dist/utils/terminal-ui.d.ts.map +0 -1
  140. package/dist/utils/terminal-ui.js +0 -611
  141. package/dist/utils/terminal-ui.js.map +0 -1
  142. package/dist/utils/terminal-ui.test.d.ts +0 -2
  143. package/dist/utils/terminal-ui.test.d.ts.map +0 -1
  144. package/dist/utils/terminal-ui.test.js +0 -683
  145. package/dist/utils/terminal-ui.test.js.map +0 -1
  146. package/dist/utils/workspace.d.ts +0 -100
  147. package/dist/utils/workspace.d.ts.map +0 -1
  148. package/dist/utils/workspace.js +0 -173
  149. package/dist/utils/workspace.js.map +0 -1
  150. package/dist/utils/workspace.test.d.ts +0 -2
  151. package/dist/utils/workspace.test.d.ts.map +0 -1
  152. package/dist/utils/workspace.test.js +0 -97
  153. package/dist/utils/workspace.test.js.map +0 -1
  154. package/src/algorithms/critical-path.test.ts +0 -241
  155. package/src/algorithms/dependency-integrity.test.ts +0 -348
  156. package/src/algorithms/tech-analysis.test.ts +0 -413
  157. package/src/algorithms/topological-sort.test.ts +0 -190
  158. package/src/schemas/response-schema.test.ts +0 -314
  159. package/src/utils/dashboard-renderer.test.ts +0 -983
  160. package/src/utils/date.test.ts +0 -329
  161. package/src/utils/hierarchy.test.ts +0 -505
  162. package/src/utils/id.test.ts +0 -235
  163. package/src/utils/natural-language.test.ts +0 -242
  164. package/src/utils/priority-queue.test.ts +0 -103
  165. package/src/utils/projection.test.ts +0 -425
  166. package/src/utils/terminal-ui.test.ts +0 -831
  167. package/src/utils/workspace.test.ts +0 -125
@@ -1,831 +0,0 @@
1
- import { describe, test, expect } from "bun:test";
2
- import {
3
- color,
4
- style,
5
- styled,
6
- c,
7
- stripAnsi,
8
- isFullWidth,
9
- displayWidth,
10
- visibleLength,
11
- pad,
12
- padEnd,
13
- padStart,
14
- center,
15
- truncateStr,
16
- hline,
17
- progressBar,
18
- box,
19
- drawBox,
20
- sideBySide,
21
- sideBySideArrays,
22
- table,
23
- renderTable,
24
- formatStatus,
25
- formatPriority,
26
- formatDependencies,
27
- banner,
28
- BOX,
29
- } from "./terminal-ui.js";
30
-
31
- // =============================================================================
32
- // ANSI Color and Style Tests
33
- // =============================================================================
34
-
35
- describe("color", () => {
36
- test("applies red color to text", () => {
37
- const result = color("test", "red");
38
- expect(result).toContain("\x1b[31m");
39
- expect(result).toContain("test");
40
- expect(result).toContain("\x1b[0m");
41
- });
42
-
43
- test("applies green color to text", () => {
44
- const result = color("hello", "green");
45
- expect(result).toContain("\x1b[32m");
46
- expect(result).toContain("hello");
47
- });
48
-
49
- test("applies bright colors", () => {
50
- const result = color("bright", "brightCyan");
51
- expect(result).toContain("\x1b[96m");
52
- });
53
- });
54
-
55
- describe("style", () => {
56
- test("applies bold style", () => {
57
- const result = style("bold text", "bold");
58
- expect(result).toContain("\x1b[1m");
59
- expect(result).toContain("bold text");
60
- });
61
-
62
- test("applies dim style", () => {
63
- const result = style("dim", "dim");
64
- expect(result).toContain("\x1b[2m");
65
- });
66
-
67
- test("applies underline style", () => {
68
- const result = style("underlined", "underline");
69
- expect(result).toContain("\x1b[4m");
70
- });
71
- });
72
-
73
- describe("styled", () => {
74
- test("combines color and style", () => {
75
- const result = styled("combo", "cyan", "bold");
76
- expect(result).toContain("\x1b[1;36m");
77
- expect(result).toContain("combo");
78
- });
79
-
80
- test("applies only color when no style provided", () => {
81
- const result = styled("just color", "yellow");
82
- expect(result).toContain("\x1b[33m");
83
- expect(result).not.toContain(";");
84
- });
85
- });
86
-
87
- describe("c helper object", () => {
88
- test("c.bold applies bold style", () => {
89
- const result = c.bold("text");
90
- expect(stripAnsi(result)).toBe("text");
91
- expect(result).toContain("\x1b[1m");
92
- });
93
-
94
- test("c.red applies red color", () => {
95
- const result = c.red("error");
96
- expect(result).toContain("\x1b[31m");
97
- });
98
-
99
- test("semantic colors work", () => {
100
- expect(c.success("ok")).toContain("\x1b[32m"); // green
101
- expect(c.error("fail")).toContain("\x1b[31m"); // red
102
- expect(c.warning("warn")).toContain("\x1b[33m"); // yellow
103
- expect(c.info("info")).toContain("\x1b[36m"); // cyan
104
- });
105
- });
106
-
107
- // =============================================================================
108
- // String Utility Tests
109
- // =============================================================================
110
-
111
- describe("stripAnsi", () => {
112
- test("removes ANSI escape codes from string", () => {
113
- const colored = "\x1b[31mred text\x1b[0m";
114
- expect(stripAnsi(colored)).toBe("red text");
115
- });
116
-
117
- test("handles multiple escape codes", () => {
118
- const multi = "\x1b[1;32mgreen bold\x1b[0m and \x1b[34mblue\x1b[0m";
119
- expect(stripAnsi(multi)).toBe("green bold and blue");
120
- });
121
-
122
- test("returns plain string unchanged", () => {
123
- expect(stripAnsi("plain text")).toBe("plain text");
124
- });
125
-
126
- test("handles empty string", () => {
127
- expect(stripAnsi("")).toBe("");
128
- });
129
- });
130
-
131
- // =============================================================================
132
- // isFullWidth Tests
133
- // =============================================================================
134
-
135
- describe("isFullWidth", () => {
136
- // ASCII characters (not full-width)
137
- test("returns false for ASCII characters", () => {
138
- expect(isFullWidth("a")).toBe(false);
139
- expect(isFullWidth("Z")).toBe(false);
140
- expect(isFullWidth("0")).toBe(false);
141
- expect(isFullWidth(" ")).toBe(false);
142
- expect(isFullWidth("!")).toBe(false);
143
- });
144
-
145
- test("returns false for empty string", () => {
146
- expect(isFullWidth("")).toBe(false);
147
- });
148
-
149
- // CJK characters
150
- test("returns true for Hangul Jamo", () => {
151
- expect(isFullWidth("\u1100")).toBe(true); // Hangul Choseong Kiyeok
152
- expect(isFullWidth("\u115F")).toBe(true); // Hangul Choseong Filler
153
- });
154
-
155
- test("returns true for Hangul Syllables", () => {
156
- expect(isFullWidth("가")).toBe(true); // U+AC00
157
- expect(isFullWidth("한")).toBe(true);
158
- expect(isFullWidth("글")).toBe(true);
159
- expect(isFullWidth("\uD7AF")).toBe(true); // End of Hangul Syllables
160
- });
161
-
162
- test("returns true for CJK Ideographs", () => {
163
- expect(isFullWidth("中")).toBe(true); // Chinese
164
- expect(isFullWidth("国")).toBe(true);
165
- expect(isFullWidth("日")).toBe(true); // Japanese Kanji
166
- expect(isFullWidth("本")).toBe(true);
167
- });
168
-
169
- test("returns true for CJK Radicals and Kangxi", () => {
170
- expect(isFullWidth("\u2E80")).toBe(true); // CJK Radical Repeat
171
- expect(isFullWidth("\u2F00")).toBe(true); // Kangxi Radical One
172
- });
173
-
174
- test("returns true for Fullwidth ASCII", () => {
175
- expect(isFullWidth("A")).toBe(true); // Fullwidth A (U+FF21)
176
- expect(isFullWidth("1")).toBe(true); // Fullwidth 1 (U+FF11)
177
- });
178
-
179
- test("returns true for CJK Extension B+ characters", () => {
180
- expect(isFullWidth("\u{20000}")).toBe(true); // CJK Extension B start
181
- expect(isFullWidth("\u{2A6DF}")).toBe(true); // CJK Extension B
182
- });
183
-
184
- test("returns true for CJK Extension G+ characters", () => {
185
- expect(isFullWidth("\u{30000}")).toBe(true); // CJK Extension G start
186
- });
187
-
188
- // Common emoji (faces)
189
- test("returns true for face emoji", () => {
190
- expect(isFullWidth("😀")).toBe(true); // Grinning Face U+1F600
191
- expect(isFullWidth("😁")).toBe(true); // Beaming Face
192
- expect(isFullWidth("😂")).toBe(true); // Face with Tears of Joy
193
- expect(isFullWidth("🙂")).toBe(true); // Slightly Smiling Face
194
- expect(isFullWidth("😎")).toBe(true); // Smiling Face with Sunglasses
195
- expect(isFullWidth("😍")).toBe(true); // Smiling Face with Heart-Eyes
196
- expect(isFullWidth("🥺")).toBe(true); // Pleading Face
197
- expect(isFullWidth("😭")).toBe(true); // Loudly Crying Face
198
- });
199
-
200
- // Hand gestures
201
- test("returns true for hand/gesture emoji", () => {
202
- expect(isFullWidth("👍")).toBe(true); // Thumbs Up
203
- expect(isFullWidth("👎")).toBe(true); // Thumbs Down
204
- expect(isFullWidth("👋")).toBe(true); // Waving Hand
205
- expect(isFullWidth("🤝")).toBe(true); // Handshake
206
- expect(isFullWidth("✌️".charAt(0))).toBe(true); // Victory Hand (base char)
207
- });
208
-
209
- // Animals
210
- test("returns true for animal emoji", () => {
211
- expect(isFullWidth("🐶")).toBe(true); // Dog Face
212
- expect(isFullWidth("🐱")).toBe(true); // Cat Face
213
- expect(isFullWidth("🦁")).toBe(true); // Lion
214
- expect(isFullWidth("🐻")).toBe(true); // Bear
215
- expect(isFullWidth("🦄")).toBe(true); // Unicorn
216
- });
217
-
218
- // Food
219
- test("returns true for food emoji", () => {
220
- expect(isFullWidth("🍎")).toBe(true); // Red Apple
221
- expect(isFullWidth("🍕")).toBe(true); // Pizza
222
- expect(isFullWidth("🍔")).toBe(true); // Hamburger
223
- expect(isFullWidth("☕")).toBe(true); // Hot Beverage (U+2615)
224
- expect(isFullWidth("🍺")).toBe(true); // Beer Mug
225
- });
226
-
227
- // Transport
228
- test("returns true for transport emoji", () => {
229
- expect(isFullWidth("🚗")).toBe(true); // Automobile
230
- expect(isFullWidth("✈️".charAt(0))).toBe(true); // Airplane (base char U+2708)
231
- expect(isFullWidth("🚀")).toBe(true); // Rocket
232
- expect(isFullWidth("🚂")).toBe(true); // Locomotive
233
- expect(isFullWidth("⛵")).toBe(true); // Sailboat (U+26F5)
234
- });
235
-
236
- // Symbols
237
- test("returns true for symbol emoji", () => {
238
- expect(isFullWidth("❤️".charAt(0))).toBe(true); // Red Heart (base char U+2764)
239
- expect(isFullWidth("⭐")).toBe(true); // Star (U+2B50)
240
- expect(isFullWidth("✨")).toBe(true); // Sparkles (U+2728)
241
- expect(isFullWidth("🔥")).toBe(true); // Fire
242
- expect(isFullWidth("💯")).toBe(true); // Hundred Points
243
- expect(isFullWidth("✅")).toBe(true); // Check Mark Button (U+2705)
244
- expect(isFullWidth("❌")).toBe(true); // Cross Mark (U+274C)
245
- });
246
-
247
- // Weather/nature
248
- test("returns true for weather/nature emoji", () => {
249
- expect(isFullWidth("☀️".charAt(0))).toBe(true); // Sun (base char)
250
- expect(isFullWidth("🌙")).toBe(true); // Crescent Moon
251
- expect(isFullWidth("🌈")).toBe(true); // Rainbow
252
- expect(isFullWidth("❄️".charAt(0))).toBe(true); // Snowflake (U+2744)
253
- expect(isFullWidth("⛄")).toBe(true); // Snowman (U+26C4)
254
- });
255
-
256
- // Misc Symbols (U+2600-U+26FF)
257
- test("returns true for misc symbols", () => {
258
- expect(isFullWidth("☀")).toBe(true); // Black Sun (U+2600)
259
- expect(isFullWidth("☁")).toBe(true); // Cloud (U+2601)
260
- expect(isFullWidth("☂")).toBe(true); // Umbrella (U+2602)
261
- expect(isFullWidth("⚡")).toBe(true); // High Voltage (U+26A1)
262
- expect(isFullWidth("⚽")).toBe(true); // Soccer Ball (U+26BD)
263
- expect(isFullWidth("⛳")).toBe(true); // Flag in Hole (U+26F3)
264
- expect(isFullWidth("⛺")).toBe(true); // Tent (U+26FA)
265
- expect(isFullWidth("⛽")).toBe(true); // Fuel Pump (U+26FD)
266
- });
267
-
268
- // Dingbats (U+2700-U+27BF)
269
- test("returns true for dingbats", () => {
270
- expect(isFullWidth("✂")).toBe(true); // Scissors (U+2702)
271
- expect(isFullWidth("✈")).toBe(true); // Airplane (U+2708)
272
- expect(isFullWidth("✉")).toBe(true); // Envelope (U+2709)
273
- expect(isFullWidth("✏")).toBe(true); // Pencil (U+270F)
274
- expect(isFullWidth("✔")).toBe(true); // Check Mark (U+2714)
275
- expect(isFullWidth("✖")).toBe(true); // Heavy Multiplication X (U+2716)
276
- expect(isFullWidth("➡")).toBe(true); // Right Arrow (U+27A1)
277
- });
278
-
279
- // Regional Indicator Symbols (for flags)
280
- test("returns true for regional indicators", () => {
281
- expect(isFullWidth("🇦")).toBe(true); // Regional Indicator A (U+1F1E6)
282
- expect(isFullWidth("🇿")).toBe(true); // Regional Indicator Z (U+1F1FF)
283
- });
284
-
285
- // Extended emoji ranges (newer emoji)
286
- test("returns true for Symbols Extended-A emoji (U+1FA70-U+1FAFF)", () => {
287
- expect(isFullWidth("🥽")).toBe(true); // Goggles (U+1F97D - in 1F900-1F9FF)
288
- expect(isFullWidth("🪐")).toBe(true); // Ringed Planet (U+1FA90)
289
- expect(isFullWidth("🪴")).toBe(true); // Potted Plant (U+1FAB4)
290
- expect(isFullWidth("🫀")).toBe(true); // Anatomical Heart (U+1FAC0)
291
- expect(isFullWidth("🫠")).toBe(true); // Melting Face (U+1FAE0)
292
- });
293
-
294
- // Chess symbols (U+1FA00-U+1FA6F)
295
- test("returns true for chess symbols", () => {
296
- expect(isFullWidth("\u{1FA00}")).toBe(true); // Chess symbols start
297
- });
298
-
299
- // Media control symbols
300
- test("returns true for media control symbols", () => {
301
- expect(isFullWidth("⏩")).toBe(true); // Fast Forward (U+23E9)
302
- expect(isFullWidth("⏪")).toBe(true); // Rewind (U+23EA)
303
- expect(isFullWidth("⏯")).toBe(true); // Play/Pause (U+23EF)
304
- expect(isFullWidth("⏰")).toBe(true); // Alarm Clock (U+23F0)
305
- expect(isFullWidth("⌚")).toBe(true); // Watch (U+231A)
306
- expect(isFullWidth("⌛")).toBe(true); // Hourglass (U+231B)
307
- });
308
-
309
- // Math operators as emoji
310
- test("returns true for math emoji", () => {
311
- expect(isFullWidth("➕")).toBe(true); // Plus (U+2795)
312
- expect(isFullWidth("➖")).toBe(true); // Minus (U+2796)
313
- expect(isFullWidth("➗")).toBe(true); // Division (U+2797)
314
- });
315
-
316
- // Question/exclamation marks
317
- test("returns true for punctuation emoji", () => {
318
- expect(isFullWidth("❓")).toBe(true); // Question Mark (U+2753)
319
- expect(isFullWidth("❔")).toBe(true); // White Question Mark (U+2754)
320
- expect(isFullWidth("❕")).toBe(true); // White Exclamation (U+2755)
321
- expect(isFullWidth("❗")).toBe(true); // Exclamation Mark (U+2757)
322
- });
323
-
324
- // Arrows as emoji
325
- test("returns true for arrow emoji", () => {
326
- expect(isFullWidth("⬅")).toBe(true); // Left Arrow (U+2B05)
327
- expect(isFullWidth("⬆")).toBe(true); // Up Arrow (U+2B06)
328
- expect(isFullWidth("⬇")).toBe(true); // Down Arrow (U+2B07)
329
- expect(isFullWidth("⤴")).toBe(true); // Right Arrow Curving Up (U+2934)
330
- expect(isFullWidth("⤵")).toBe(true); // Right Arrow Curving Down (U+2935)
331
- });
332
-
333
- // Note: Basic arrows (U+2190-U+21FF) are typically narrow in terminals
334
- test("returns false for basic arrows (narrow)", () => {
335
- expect(isFullWidth("↩")).toBe(false); // Leftwards Arrow with Hook (U+21A9)
336
- expect(isFullWidth("←")).toBe(false); // Leftwards Arrow (U+2190)
337
- expect(isFullWidth("→")).toBe(false); // Rightwards Arrow (U+2192)
338
- });
339
-
340
- // Zodiac
341
- test("returns true for zodiac symbols", () => {
342
- expect(isFullWidth("♈")).toBe(true); // Aries (U+2648)
343
- expect(isFullWidth("♓")).toBe(true); // Pisces (U+2653)
344
- });
345
-
346
- // Geometric shapes as emoji
347
- test("returns true for geometric shape emoji", () => {
348
- expect(isFullWidth("▶")).toBe(true); // Play Button (U+25B6)
349
- expect(isFullWidth("◀")).toBe(true); // Reverse Button (U+25C0)
350
- expect(isFullWidth("⬛")).toBe(true); // Black Large Square (U+2B1B)
351
- expect(isFullWidth("⬜")).toBe(true); // White Large Square (U+2B1C)
352
- expect(isFullWidth("⭕")).toBe(true); // Hollow Red Circle (U+2B55)
353
- });
354
-
355
- // Japanese-specific symbols
356
- test("returns true for Japanese ideographic symbols", () => {
357
- expect(isFullWidth("㊗")).toBe(true); // Circled Ideograph Congratulation (U+3297)
358
- expect(isFullWidth("㊙")).toBe(true); // Circled Ideograph Secret (U+3299)
359
- expect(isFullWidth("〰")).toBe(true); // Wavy Dash (U+3030)
360
- expect(isFullWidth("〽")).toBe(true); // Part Alternation Mark (U+303D)
361
- });
362
- });
363
-
364
- describe("displayWidth", () => {
365
- test("returns length for ASCII string", () => {
366
- expect(displayWidth("hello")).toBe(5);
367
- });
368
-
369
- test("excludes ANSI codes from width", () => {
370
- const colored = c.red("test");
371
- expect(displayWidth(colored)).toBe(4);
372
- });
373
-
374
- test("counts CJK characters as width 2", () => {
375
- expect(displayWidth("한글")).toBe(4); // 2 chars * 2 width
376
- expect(displayWidth("中文")).toBe(4);
377
- expect(displayWidth("日本")).toBe(4);
378
- });
379
-
380
- test("handles mixed ASCII and CJK", () => {
381
- expect(displayWidth("hello한글")).toBe(9); // 5 + 4
382
- });
383
-
384
- test("handles empty string", () => {
385
- expect(displayWidth("")).toBe(0);
386
- });
387
-
388
- // Emoji width tests
389
- test("counts common emoji as width 2", () => {
390
- expect(displayWidth("😀")).toBe(2); // Single emoji
391
- expect(displayWidth("😀😁")).toBe(4); // Two emoji
392
- expect(displayWidth("👍")).toBe(2); // Thumbs up
393
- expect(displayWidth("❤")).toBe(2); // Heart
394
- });
395
-
396
- test("handles mixed ASCII and emoji", () => {
397
- expect(displayWidth("hi😀")).toBe(4); // 2 + 2
398
- expect(displayWidth("test🚀done")).toBe(10); // 4 + 2 + 4
399
- });
400
-
401
- test("handles mixed CJK and emoji", () => {
402
- expect(displayWidth("한글😀")).toBe(6); // 4 + 2
403
- expect(displayWidth("日本🇯🇵")).toBe(8); // 4 + 2 + 2 (flag is 2 regional indicators)
404
- });
405
-
406
- test("handles emoji with variation selectors correctly", () => {
407
- // Emoji with variation selector (U+FE0F) - the base char should be counted
408
- // Note: variation selector is a combining character and adds 0 width
409
- const heartWithSelector = "❤️"; // U+2764 + U+FE0F
410
- // This should be 2 (for the heart) + 1 (for variation selector which isn't full-width)
411
- // But variation selectors are typically invisible, so actual terminal behavior varies
412
- expect(displayWidth(heartWithSelector)).toBeGreaterThanOrEqual(2);
413
- });
414
-
415
- test("handles strings with only emoji", () => {
416
- expect(displayWidth("🎉🎊🎈")).toBe(6); // 3 emoji * 2 width each
417
- });
418
-
419
- test("handles newer extended emoji", () => {
420
- expect(displayWidth("🪐")).toBe(2); // Ringed Planet
421
- expect(displayWidth("🫠")).toBe(2); // Melting Face
422
- });
423
- });
424
-
425
- describe("visibleLength alias", () => {
426
- test("is the same as displayWidth", () => {
427
- expect(visibleLength("test")).toBe(displayWidth("test"));
428
- expect(visibleLength("한글")).toBe(displayWidth("한글"));
429
- });
430
- });
431
-
432
- describe("pad", () => {
433
- test("pads string to the right (left align) by default", () => {
434
- expect(pad("hi", 5)).toBe("hi ");
435
- });
436
-
437
- test("pads string to the left (right align)", () => {
438
- expect(pad("hi", 5, "right")).toBe(" hi");
439
- });
440
-
441
- test("centers string", () => {
442
- expect(pad("hi", 6, "center")).toBe(" hi ");
443
- });
444
-
445
- test("handles odd padding for center", () => {
446
- const result = pad("hi", 5, "center");
447
- expect(result.length).toBe(5);
448
- expect(result).toBe(" hi ");
449
- });
450
-
451
- test("returns original string if already at width", () => {
452
- expect(pad("hello", 5)).toBe("hello");
453
- });
454
-
455
- test("returns original string if longer than width", () => {
456
- expect(pad("hello world", 5)).toBe("hello world");
457
- });
458
-
459
- test("handles CJK characters correctly", () => {
460
- const result = pad("한", 4); // "한" has display width 2
461
- expect(displayWidth(result)).toBe(4);
462
- });
463
- });
464
-
465
- describe("padEnd alias", () => {
466
- test("pads to the right", () => {
467
- expect(padEnd("hi", 5)).toBe("hi ");
468
- });
469
- });
470
-
471
- describe("padStart alias", () => {
472
- test("pads to the left", () => {
473
- expect(padStart("hi", 5)).toBe(" hi");
474
- });
475
- });
476
-
477
- describe("center alias", () => {
478
- test("centers the string", () => {
479
- expect(center("hi", 6)).toBe(" hi ");
480
- });
481
- });
482
-
483
- describe("truncateStr", () => {
484
- test("truncates long string with ellipsis", () => {
485
- const result = truncateStr("hello world", 8);
486
- expect(result).toBe("hello...");
487
- });
488
-
489
- test("returns original string if within limit", () => {
490
- expect(truncateStr("hello", 10)).toBe("hello");
491
- });
492
-
493
- test("uses custom suffix", () => {
494
- const result = truncateStr("hello world", 9, "..");
495
- expect(result).toBe("hello w..");
496
- });
497
-
498
- test("handles CJK characters", () => {
499
- const result = truncateStr("한글테스트", 6);
500
- expect(displayWidth(result)).toBeLessThanOrEqual(6);
501
- });
502
-
503
- test("handles exact length", () => {
504
- expect(truncateStr("hello", 5)).toBe("hello");
505
- });
506
- });
507
-
508
- describe("hline", () => {
509
- test("creates horizontal line with default character", () => {
510
- const result = hline(5);
511
- expect(result).toBe("─────");
512
- expect(result.length).toBe(5);
513
- });
514
-
515
- test("creates horizontal line with custom character", () => {
516
- expect(hline(3, "=")).toBe("===");
517
- });
518
- });
519
-
520
- // =============================================================================
521
- // Progress Bar Tests
522
- // =============================================================================
523
-
524
- describe("progressBar", () => {
525
- test("creates progress bar with default options", () => {
526
- const result = progressBar(50, 100);
527
- expect(stripAnsi(result)).toContain("50%");
528
- });
529
-
530
- test("shows 0% for empty progress", () => {
531
- const result = progressBar(0, 100);
532
- expect(stripAnsi(result)).toContain("0%");
533
- });
534
-
535
- test("shows 100% for complete progress", () => {
536
- const result = progressBar(100, 100);
537
- expect(stripAnsi(result)).toContain("100%");
538
- });
539
-
540
- test("handles zero total gracefully", () => {
541
- const result = progressBar(0, 0);
542
- expect(stripAnsi(result)).toContain("0%");
543
- });
544
-
545
- test("hides percent when showPercent is false", () => {
546
- const result = progressBar(50, 100, { showPercent: false });
547
- expect(stripAnsi(result)).not.toContain("%");
548
- });
549
-
550
- test("respects custom width", () => {
551
- const result = progressBar(50, 100, { width: 10, showPercent: false });
552
- // 10 characters total for the bar
553
- expect(stripAnsi(result).length).toBe(10);
554
- });
555
- });
556
-
557
- // =============================================================================
558
- // Box Drawing Tests
559
- // =============================================================================
560
-
561
- describe("box", () => {
562
- test("creates box around content", () => {
563
- const result = box("hello");
564
- expect(result).toContain("hello");
565
- expect(result).toContain(BOX.rTopLeft);
566
- expect(result).toContain(BOX.rBottomRight);
567
- });
568
-
569
- test("includes title when provided", () => {
570
- const result = box("content", { title: "Title" });
571
- expect(result).toContain("Title");
572
- });
573
-
574
- test("uses square corners when rounded is false", () => {
575
- const result = box("test", { rounded: false });
576
- expect(result).toContain(BOX.topLeft);
577
- expect(result).not.toContain(BOX.rTopLeft);
578
- });
579
- });
580
-
581
- describe("drawBox", () => {
582
- test("returns array of lines", () => {
583
- const result = drawBox(["line 1", "line 2"]);
584
- expect(Array.isArray(result)).toBe(true);
585
- expect(result.length).toBeGreaterThan(2);
586
- });
587
-
588
- test("includes title when provided", () => {
589
- // Use content wider than title to avoid edge case
590
- const result = drawBox(["longer content here"], { title: "Title" });
591
- expect(result.join("\n")).toContain("Title");
592
- });
593
-
594
- test("wraps content with borders", () => {
595
- const result = drawBox(["test"]);
596
- const joined = result.join("\n");
597
- expect(joined).toContain(BOX.topLeft);
598
- expect(joined).toContain(BOX.bottomRight);
599
- expect(joined).toContain("test");
600
- });
601
- });
602
-
603
- // =============================================================================
604
- // Side-by-Side Layout Tests
605
- // =============================================================================
606
-
607
- describe("sideBySide", () => {
608
- test("places boxes horizontally", () => {
609
- const box1 = "A\nB";
610
- const box2 = "X\nY";
611
- const result = sideBySide([box1, box2]);
612
- const lines = result.split("\n");
613
- expect(lines[0]).toContain("A");
614
- expect(lines[0]).toContain("X");
615
- });
616
-
617
- test("handles different heights", () => {
618
- const short = "A";
619
- const tall = "X\nY\nZ";
620
- const result = sideBySide([short, tall]);
621
- const lines = result.split("\n");
622
- expect(lines.length).toBe(3);
623
- });
624
-
625
- test("respects gap parameter", () => {
626
- const result = sideBySide(["A", "B"], 4);
627
- expect(result).toContain(" "); // 4 space gap
628
- });
629
- });
630
-
631
- describe("sideBySideArrays", () => {
632
- test("merges two arrays side by side", () => {
633
- const left = ["L1", "L2"];
634
- const right = ["R1", "R2"];
635
- const result = sideBySideArrays(left, right);
636
- expect(result.length).toBe(2);
637
- expect(result[0]).toContain("L1");
638
- expect(result[0]).toContain("R1");
639
- });
640
-
641
- test("handles different length arrays", () => {
642
- const left = ["L1"];
643
- const right = ["R1", "R2", "R3"];
644
- const result = sideBySideArrays(left, right);
645
- expect(result.length).toBe(3);
646
- });
647
-
648
- test("respects gap parameter", () => {
649
- const result = sideBySideArrays(["L"], ["R"], 5);
650
- expect(result[0]).toContain(" "); // 5 space gap
651
- });
652
- });
653
-
654
- // =============================================================================
655
- // Table Tests
656
- // =============================================================================
657
-
658
- describe("table", () => {
659
- test("renders table with headers and data", () => {
660
- const data = [
661
- { name: "Alice", age: 30 },
662
- { name: "Bob", age: 25 },
663
- ];
664
- const columns = [
665
- { key: "name", header: "Name" },
666
- { key: "age", header: "Age" },
667
- ];
668
- const result = table(data, columns);
669
- expect(result).toContain("Name");
670
- expect(result).toContain("Age");
671
- expect(result).toContain("Alice");
672
- expect(result).toContain("Bob");
673
- });
674
-
675
- test("uses custom format function", () => {
676
- const data = [{ value: 100 }];
677
- const columns = [
678
- {
679
- key: "value",
680
- header: "Value",
681
- format: (v: unknown) => `$${v}`,
682
- },
683
- ];
684
- const result = table(data, columns);
685
- expect(result).toContain("$100");
686
- });
687
-
688
- test("handles empty data", () => {
689
- const columns = [{ key: "name", header: "Name" }];
690
- const result = table([], columns);
691
- expect(result).toContain("Name");
692
- });
693
- });
694
-
695
- describe("renderTable", () => {
696
- test("returns array of lines with borders", () => {
697
- const columns = [{ key: "id", header: "ID" }];
698
- const rows = [{ id: 1 }];
699
- const result = renderTable(columns, rows);
700
- expect(Array.isArray(result)).toBe(true);
701
- expect(result.join("\n")).toContain(BOX.topLeft);
702
- expect(result.join("\n")).toContain(BOX.bottomRight);
703
- });
704
-
705
- test("aligns columns correctly", () => {
706
- const columns = [
707
- { key: "name", header: "Name", align: "left" as const },
708
- { key: "count", header: "Count", align: "right" as const },
709
- ];
710
- const rows = [{ name: "Test", count: 42 }];
711
- const result = renderTable(columns, rows);
712
- expect(result.join("\n")).toContain("Test");
713
- expect(result.join("\n")).toContain("42");
714
- });
715
- });
716
-
717
- // =============================================================================
718
- // Status & Priority Formatter Tests
719
- // =============================================================================
720
-
721
- describe("formatStatus", () => {
722
- test("formats pending status with icon", () => {
723
- const result = formatStatus("pending");
724
- expect(stripAnsi(result)).toContain("pending");
725
- expect(stripAnsi(result)).toContain("○");
726
- });
727
-
728
- test("formats completed status", () => {
729
- const result = formatStatus("completed");
730
- expect(stripAnsi(result)).toContain("completed");
731
- expect(stripAnsi(result)).toContain("●");
732
- });
733
-
734
- test("formats blocked status", () => {
735
- const result = formatStatus("blocked");
736
- expect(stripAnsi(result)).toContain("blocked");
737
- });
738
-
739
- test("handles unknown status", () => {
740
- const result = formatStatus("unknown");
741
- expect(stripAnsi(result)).toContain("unknown");
742
- expect(stripAnsi(result)).toContain("?");
743
- });
744
- });
745
-
746
- describe("formatPriority", () => {
747
- test("formats critical priority", () => {
748
- const result = formatPriority("critical");
749
- expect(stripAnsi(result)).toBe("critical");
750
- });
751
-
752
- test("formats high priority", () => {
753
- const result = formatPriority("high");
754
- expect(stripAnsi(result)).toBe("high");
755
- });
756
-
757
- test("handles unknown priority", () => {
758
- const result = formatPriority("unknown");
759
- expect(stripAnsi(result)).toBe("unknown");
760
- });
761
- });
762
-
763
- describe("formatDependencies", () => {
764
- test("formats empty dependencies as None", () => {
765
- const result = formatDependencies([]);
766
- expect(stripAnsi(result)).toBe("None");
767
- });
768
-
769
- test("formats single dependency", () => {
770
- const result = formatDependencies(["task-1"]);
771
- expect(stripAnsi(result)).toBe("task-1");
772
- });
773
-
774
- test("formats multiple dependencies with commas", () => {
775
- const result = formatDependencies(["task-1", "task-2", "task-3"]);
776
- expect(stripAnsi(result)).toBe("task-1, task-2, task-3");
777
- });
778
- });
779
-
780
- // =============================================================================
781
- // Banner Tests
782
- // =============================================================================
783
-
784
- describe("banner", () => {
785
- test("creates ASCII art banner", () => {
786
- const result = banner("TASK");
787
- expect(result).toContain("█");
788
- const lines = result.split("\n");
789
- expect(lines.length).toBe(5);
790
- });
791
-
792
- test("handles spaces", () => {
793
- const result = banner("A B");
794
- expect(result).toBeTruthy();
795
- });
796
-
797
- test("handles unknown characters gracefully", () => {
798
- const result = banner("XYZ");
799
- // Should not throw, returns spaces for unknown chars
800
- expect(result).toBeTruthy();
801
- });
802
- });
803
-
804
- // =============================================================================
805
- // BOX Constants Tests
806
- // =============================================================================
807
-
808
- describe("BOX constants", () => {
809
- test("has single line box characters", () => {
810
- expect(BOX.topLeft).toBe("┌");
811
- expect(BOX.topRight).toBe("┐");
812
- expect(BOX.bottomLeft).toBe("└");
813
- expect(BOX.bottomRight).toBe("┘");
814
- expect(BOX.horizontal).toBe("─");
815
- expect(BOX.vertical).toBe("│");
816
- });
817
-
818
- test("has rounded corner characters", () => {
819
- expect(BOX.rTopLeft).toBe("╭");
820
- expect(BOX.rTopRight).toBe("╮");
821
- expect(BOX.rBottomLeft).toBe("╰");
822
- expect(BOX.rBottomRight).toBe("╯");
823
- });
824
-
825
- test("has double line characters", () => {
826
- expect(BOX.dblTopLeft).toBe("╔");
827
- expect(BOX.dblBottomRight).toBe("╝");
828
- expect(BOX.dblHorizontal).toBe("═");
829
- expect(BOX.dblVertical).toBe("║");
830
- });
831
- });