@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,774 +0,0 @@
1
- import { describe, test, expect } from "bun:test";
2
- import { calculateStats, calculateDependencyMetrics, renderStatusWidget, renderActionsWidget, renderInboxWidget, renderProjectsTable, renderTasksTable, renderDashboard, renderProjectDashboard, renderGlobalDashboard, } from "./dashboard-renderer.js";
3
- import { stripAnsi } from "./terminal-ui.js";
4
- // =============================================================================
5
- // Test Fixtures
6
- // =============================================================================
7
- const createTask = (overrides = {}) => ({
8
- id: "task-1",
9
- workspace: "test-workspace",
10
- title: "Test Task",
11
- status: "pending",
12
- priority: "medium",
13
- createdAt: "2025-01-01T00:00:00.000Z",
14
- updatedAt: "2025-01-01T00:00:00.000Z",
15
- ...overrides,
16
- });
17
- const createWorkspaceInfo = (overrides = {}) => ({
18
- name: "test-workspace",
19
- taskCount: 10,
20
- completedCount: 5,
21
- ...overrides,
22
- });
23
- const createInboxItem = (overrides = {}) => ({
24
- id: "inbox-1",
25
- content: "Quick idea for later",
26
- status: "pending",
27
- capturedAt: "2025-01-01T00:00:00.000Z",
28
- ...overrides,
29
- });
30
- // =============================================================================
31
- // calculateStats Tests
32
- // =============================================================================
33
- describe("calculateStats", () => {
34
- test("returns zero stats for empty task list", () => {
35
- const stats = calculateStats([]);
36
- expect(stats).toEqual({
37
- total: 0,
38
- completed: 0,
39
- inProgress: 0,
40
- pending: 0,
41
- blocked: 0,
42
- cancelled: 0,
43
- byPriority: { critical: 0, high: 0, medium: 0, low: 0 },
44
- });
45
- });
46
- test("counts tasks by status correctly", () => {
47
- const tasks = [
48
- createTask({ id: "t1", status: "completed" }),
49
- createTask({ id: "t2", status: "completed" }),
50
- createTask({ id: "t3", status: "in_progress" }),
51
- createTask({ id: "t4", status: "pending" }),
52
- createTask({ id: "t5", status: "pending" }),
53
- createTask({ id: "t6", status: "pending" }),
54
- createTask({ id: "t7", status: "blocked" }),
55
- createTask({ id: "t8", status: "cancelled" }),
56
- ];
57
- const stats = calculateStats(tasks);
58
- expect(stats.total).toBe(8);
59
- expect(stats.completed).toBe(2);
60
- expect(stats.inProgress).toBe(1);
61
- expect(stats.pending).toBe(3);
62
- expect(stats.blocked).toBe(1);
63
- expect(stats.cancelled).toBe(1);
64
- });
65
- test("counts tasks by priority correctly", () => {
66
- const tasks = [
67
- createTask({ id: "t1", priority: "critical" }),
68
- createTask({ id: "t2", priority: "critical" }),
69
- createTask({ id: "t3", priority: "high" }),
70
- createTask({ id: "t4", priority: "medium" }),
71
- createTask({ id: "t5", priority: "medium" }),
72
- createTask({ id: "t6", priority: "low" }),
73
- ];
74
- const stats = calculateStats(tasks);
75
- expect(stats.byPriority.critical).toBe(2);
76
- expect(stats.byPriority.high).toBe(1);
77
- expect(stats.byPriority.medium).toBe(2);
78
- expect(stats.byPriority.low).toBe(1);
79
- });
80
- test("defaults to medium priority in stats calculation", () => {
81
- // When priority is "medium", it should be counted in the medium bucket
82
- const tasks = [
83
- createTask({ id: "t1" }), // Uses default priority: "medium"
84
- createTask({ id: "t2" }), // Uses default priority: "medium"
85
- ];
86
- const stats = calculateStats(tasks);
87
- expect(stats.byPriority.medium).toBe(2);
88
- });
89
- });
90
- // =============================================================================
91
- // calculateDependencyMetrics Tests
92
- // =============================================================================
93
- describe("calculateDependencyMetrics", () => {
94
- test("returns zero metrics for empty task list", () => {
95
- const metrics = calculateDependencyMetrics([]);
96
- expect(metrics).toEqual({
97
- readyToWork: 0,
98
- blockedByDependencies: 0,
99
- mostDependedOn: undefined,
100
- });
101
- });
102
- test("counts tasks without dependencies as ready", () => {
103
- const tasks = [
104
- createTask({ id: "t1", status: "pending" }),
105
- createTask({ id: "t2", status: "pending" }),
106
- createTask({ id: "t3", status: "in_progress" }),
107
- ];
108
- const metrics = calculateDependencyMetrics(tasks);
109
- expect(metrics.readyToWork).toBe(3);
110
- expect(metrics.blockedByDependencies).toBe(0);
111
- });
112
- test("counts tasks with unsatisfied dependencies as blocked", () => {
113
- const tasks = [
114
- createTask({ id: "t1", status: "pending" }),
115
- createTask({
116
- id: "t2",
117
- status: "pending",
118
- dependencies: [{ taskId: "t1", type: "blocked_by" }],
119
- }),
120
- createTask({
121
- id: "t3",
122
- status: "pending",
123
- dependencies: [{ taskId: "t1", type: "blocked_by" }],
124
- }),
125
- ];
126
- const metrics = calculateDependencyMetrics(tasks);
127
- expect(metrics.readyToWork).toBe(1); // t1 has no deps
128
- expect(metrics.blockedByDependencies).toBe(2); // t2, t3 blocked by t1
129
- });
130
- test("counts tasks with satisfied dependencies as ready", () => {
131
- const tasks = [
132
- createTask({ id: "t1", status: "completed" }),
133
- createTask({
134
- id: "t2",
135
- status: "pending",
136
- dependencies: [{ taskId: "t1", type: "blocked_by" }],
137
- }),
138
- ];
139
- const metrics = calculateDependencyMetrics(tasks);
140
- expect(metrics.readyToWork).toBe(1); // t2 is ready because t1 is completed
141
- expect(metrics.blockedByDependencies).toBe(0);
142
- });
143
- test("ignores completed and cancelled tasks in readyToWork count", () => {
144
- const tasks = [
145
- createTask({ id: "t1", status: "completed" }),
146
- createTask({ id: "t2", status: "cancelled" }),
147
- createTask({ id: "t3", status: "pending" }),
148
- ];
149
- const metrics = calculateDependencyMetrics(tasks);
150
- expect(metrics.readyToWork).toBe(1); // Only t3 counts
151
- });
152
- test("identifies most depended on task", () => {
153
- const tasks = [
154
- createTask({ id: "t1", title: "Foundation Task", status: "pending" }),
155
- createTask({
156
- id: "t2",
157
- status: "pending",
158
- dependencies: [{ taskId: "t1", type: "blocked_by" }],
159
- }),
160
- createTask({
161
- id: "t3",
162
- status: "pending",
163
- dependencies: [{ taskId: "t1", type: "blocked_by" }],
164
- }),
165
- createTask({
166
- id: "t4",
167
- status: "pending",
168
- dependencies: [{ taskId: "t1", type: "blocked_by" }],
169
- }),
170
- ];
171
- const metrics = calculateDependencyMetrics(tasks);
172
- expect(metrics.mostDependedOn).toEqual({
173
- id: "t1",
174
- title: "Foundation Task",
175
- dependentCount: 3,
176
- });
177
- });
178
- test("returns undefined mostDependedOn when no dependencies exist", () => {
179
- const tasks = [
180
- createTask({ id: "t1", status: "pending" }),
181
- createTask({ id: "t2", status: "pending" }),
182
- ];
183
- const metrics = calculateDependencyMetrics(tasks);
184
- expect(metrics.mostDependedOn).toBeUndefined();
185
- });
186
- });
187
- // =============================================================================
188
- // renderStatusWidget Tests
189
- // =============================================================================
190
- describe("renderStatusWidget", () => {
191
- test("renders status widget with basic stats", () => {
192
- const tasks = [
193
- createTask({ id: "t1", status: "completed" }),
194
- createTask({ id: "t2", status: "pending" }),
195
- createTask({ id: "t3", status: "in_progress" }),
196
- ];
197
- const widget = renderStatusWidget(tasks);
198
- const plain = stripAnsi(widget);
199
- expect(plain).toContain("Status");
200
- expect(plain).toContain("Done");
201
- expect(plain).toContain("Progress");
202
- expect(plain).toContain("Pending");
203
- });
204
- test("renders progress percentage", () => {
205
- const tasks = [
206
- createTask({ id: "t1", status: "completed" }),
207
- createTask({ id: "t2", status: "completed" }),
208
- createTask({ id: "t3", status: "pending" }),
209
- createTask({ id: "t4", status: "pending" }),
210
- ];
211
- const widget = renderStatusWidget(tasks);
212
- const plain = stripAnsi(widget);
213
- expect(plain).toContain("50%");
214
- expect(plain).toContain("2/4 tasks");
215
- });
216
- test("renders priority breakdown", () => {
217
- const tasks = [
218
- createTask({ id: "t1", priority: "critical" }),
219
- createTask({ id: "t2", priority: "high" }),
220
- ];
221
- const widget = renderStatusWidget(tasks);
222
- const plain = stripAnsi(widget);
223
- expect(plain).toContain("Critical");
224
- expect(plain).toContain("High");
225
- expect(plain).toContain("Medium");
226
- expect(plain).toContain("Low");
227
- });
228
- test("renders dependency metrics", () => {
229
- const tasks = [
230
- createTask({ id: "t1", status: "pending" }),
231
- createTask({
232
- id: "t2",
233
- status: "pending",
234
- dependencies: [{ taskId: "t1", type: "blocked_by" }],
235
- }),
236
- ];
237
- const widget = renderStatusWidget(tasks);
238
- const plain = stripAnsi(widget);
239
- expect(plain).toContain("Ready");
240
- expect(plain).toContain("Blocked");
241
- });
242
- test("excludes cancelled tasks from active count", () => {
243
- const tasks = [
244
- createTask({ id: "t1", status: "completed" }),
245
- createTask({ id: "t2", status: "cancelled" }),
246
- createTask({ id: "t3", status: "pending" }),
247
- ];
248
- const widget = renderStatusWidget(tasks);
249
- const plain = stripAnsi(widget);
250
- // 1 completed out of 2 active = 50%
251
- expect(plain).toContain("50%");
252
- expect(plain).toContain("1/2 tasks");
253
- });
254
- });
255
- // =============================================================================
256
- // renderActionsWidget Tests
257
- // =============================================================================
258
- describe("renderActionsWidget", () => {
259
- test("renders next actions widget title", () => {
260
- const tasks = [createTask({ status: "pending" })];
261
- const widget = renderActionsWidget(tasks);
262
- const plain = stripAnsi(widget);
263
- expect(plain).toContain("Next Actions");
264
- });
265
- test("shows ready tasks without dependencies", () => {
266
- const tasks = [
267
- createTask({ id: "t1", title: "Ready Task 1", status: "pending" }),
268
- createTask({ id: "t2", title: "Ready Task 2", status: "pending" }),
269
- ];
270
- const widget = renderActionsWidget(tasks);
271
- const plain = stripAnsi(widget);
272
- expect(plain).toContain("Ready Task 1");
273
- expect(plain).toContain("Ready Task 2");
274
- expect(plain).toContain("ready");
275
- });
276
- test("excludes tasks with dependencies", () => {
277
- const tasks = [
278
- createTask({ id: "t1", title: "Ready Task", status: "pending" }),
279
- createTask({
280
- id: "t2",
281
- title: "Blocked Task",
282
- status: "pending",
283
- dependencies: [{ taskId: "t1", type: "blocked_by" }],
284
- }),
285
- ];
286
- const widget = renderActionsWidget(tasks);
287
- const plain = stripAnsi(widget);
288
- expect(plain).toContain("Ready Task");
289
- expect(plain).not.toContain("Blocked Task");
290
- });
291
- test("sorts by priority", () => {
292
- const tasks = [
293
- createTask({ id: "t1", title: "Low Priority", status: "pending", priority: "low" }),
294
- createTask({ id: "t2", title: "Critical Task", status: "pending", priority: "critical" }),
295
- createTask({ id: "t3", title: "High Priority", status: "pending", priority: "high" }),
296
- ];
297
- const widget = renderActionsWidget(tasks);
298
- const plain = stripAnsi(widget);
299
- // Critical should appear before others
300
- const criticalPos = plain.indexOf("Critical Task");
301
- const highPos = plain.indexOf("High Priority");
302
- const lowPos = plain.indexOf("Low Priority");
303
- expect(criticalPos).toBeLessThan(highPos);
304
- expect(highPos).toBeLessThan(lowPos);
305
- });
306
- test("limits to 4 tasks", () => {
307
- const tasks = Array.from({ length: 10 }, (_, i) => createTask({ id: `t${i}`, title: `Task ${i}`, status: "pending" }));
308
- const widget = renderActionsWidget(tasks);
309
- const plain = stripAnsi(widget);
310
- // Should show only first 4
311
- expect(plain).toContain("Task 0");
312
- expect(plain).toContain("Task 3");
313
- expect(plain).not.toContain("Task 9");
314
- });
315
- test("shows message when no tasks ready", () => {
316
- const tasks = [
317
- createTask({ id: "t1", status: "completed" }),
318
- createTask({
319
- id: "t2",
320
- status: "pending",
321
- dependencies: [{ taskId: "t1", type: "blocked_by" }],
322
- }),
323
- ];
324
- // t2 has deps, but t1 is completed so t2 is actually ready
325
- // Let's use a case where deps are NOT satisfied
326
- const blockedTasks = [
327
- createTask({ id: "t1", status: "pending" }),
328
- createTask({
329
- id: "t2",
330
- status: "pending",
331
- dependencies: [{ taskId: "t1", type: "blocked_by" }],
332
- }),
333
- ];
334
- // Filter out t1 to simulate only blocked tasks
335
- const onlyBlockedTask = blockedTasks.filter((t) => t.id === "t2");
336
- const widget = renderActionsWidget(onlyBlockedTask);
337
- const plain = stripAnsi(widget);
338
- expect(plain).toContain("No tasks ready");
339
- });
340
- });
341
- // =============================================================================
342
- // renderInboxWidget Tests
343
- // =============================================================================
344
- describe("renderInboxWidget", () => {
345
- test("returns null for empty inbox", () => {
346
- const widget = renderInboxWidget([]);
347
- expect(widget).toBeNull();
348
- });
349
- test("returns null when all items are processed", () => {
350
- const items = [
351
- createInboxItem({ id: "i1", status: "promoted" }),
352
- createInboxItem({ id: "i2", status: "discarded" }),
353
- ];
354
- const widget = renderInboxWidget(items);
355
- expect(widget).toBeNull();
356
- });
357
- test("renders pending items count", () => {
358
- const items = [
359
- createInboxItem({ id: "i1", status: "pending" }),
360
- createInboxItem({ id: "i2", status: "pending" }),
361
- createInboxItem({ id: "i3", status: "promoted" }),
362
- ];
363
- const widget = renderInboxWidget(items);
364
- const plain = stripAnsi(widget);
365
- expect(plain).toContain("Inbox");
366
- expect(plain).toContain("Pending");
367
- expect(plain).toContain("2 items");
368
- });
369
- test("shows up to 3 items", () => {
370
- const items = [
371
- createInboxItem({ id: "i1", content: "Item 1", status: "pending" }),
372
- createInboxItem({ id: "i2", content: "Item 2", status: "pending" }),
373
- createInboxItem({ id: "i3", content: "Item 3", status: "pending" }),
374
- createInboxItem({ id: "i4", content: "Item 4", status: "pending" }),
375
- createInboxItem({ id: "i5", content: "Item 5", status: "pending" }),
376
- ];
377
- const widget = renderInboxWidget(items);
378
- const plain = stripAnsi(widget);
379
- expect(plain).toContain("Item 1");
380
- expect(plain).toContain("Item 2");
381
- expect(plain).toContain("Item 3");
382
- expect(plain).not.toContain("Item 4");
383
- expect(plain).toContain("+2 more");
384
- });
385
- test("shows tags when present", () => {
386
- const items = [
387
- createInboxItem({
388
- id: "i1",
389
- content: "Tagged item",
390
- status: "pending",
391
- tags: ["important"],
392
- }),
393
- ];
394
- const widget = renderInboxWidget(items);
395
- const plain = stripAnsi(widget);
396
- expect(plain).toContain("#important");
397
- });
398
- });
399
- // =============================================================================
400
- // renderProjectsTable Tests
401
- // =============================================================================
402
- describe("renderProjectsTable", () => {
403
- test("returns message for empty workspaces", () => {
404
- const result = renderProjectsTable([], () => []);
405
- const plain = stripAnsi(result);
406
- expect(plain).toContain("No workspaces found");
407
- });
408
- test("renders workspace names", () => {
409
- const workspaces = [
410
- createWorkspaceInfo({ name: "workspace-alpha" }),
411
- createWorkspaceInfo({ name: "workspace-beta" }),
412
- ];
413
- const result = renderProjectsTable(workspaces, () => []);
414
- const plain = stripAnsi(result);
415
- expect(plain).toContain("workspace-alpha");
416
- expect(plain).toContain("workspace-beta");
417
- });
418
- test("renders progress percentage", () => {
419
- const workspaces = [createWorkspaceInfo({ name: "half-done", taskCount: 2, completedCount: 1 })];
420
- const tasks = [
421
- createTask({ id: "t1", workspace: "half-done", status: "completed" }),
422
- createTask({ id: "t2", workspace: "half-done", status: "pending" }),
423
- ];
424
- const result = renderProjectsTable(workspaces, () => tasks);
425
- const plain = stripAnsi(result);
426
- expect(plain).toContain("50%");
427
- });
428
- test("renders ready and blocked counts", () => {
429
- const workspaces = [createWorkspaceInfo({ name: "test-workspace" })];
430
- const tasks = [
431
- createTask({ id: "t1", workspace: "test-workspace", status: "pending" }),
432
- createTask({
433
- id: "t2",
434
- workspace: "test-workspace",
435
- status: "pending",
436
- dependencies: [{ taskId: "t1", type: "blocked_by" }],
437
- }),
438
- ];
439
- const result = renderProjectsTable(workspaces, () => tasks);
440
- const plain = stripAnsi(result);
441
- // Table headers should be present
442
- expect(plain).toContain("Workspace");
443
- expect(plain).toContain("Progress");
444
- expect(plain).toContain("Tasks");
445
- expect(plain).toContain("Ready");
446
- expect(plain).toContain("Blocked");
447
- });
448
- test("limits to 10 workspaces", () => {
449
- const workspaces = Array.from({ length: 15 }, (_, i) => createWorkspaceInfo({ name: `workspace-${i}` }));
450
- const result = renderProjectsTable(workspaces, () => []);
451
- const plain = stripAnsi(result);
452
- expect(plain).toContain("workspace-0");
453
- expect(plain).toContain("workspace-9");
454
- expect(plain).not.toContain("workspace-10");
455
- });
456
- });
457
- // =============================================================================
458
- // renderTasksTable Tests
459
- // =============================================================================
460
- describe("renderTasksTable", () => {
461
- test("returns message for no active tasks", () => {
462
- const tasks = [
463
- createTask({ id: "t1", status: "completed" }),
464
- createTask({ id: "t2", status: "cancelled" }),
465
- ];
466
- const result = renderTasksTable(tasks);
467
- const plain = stripAnsi(result);
468
- expect(plain).toContain("No active tasks");
469
- });
470
- test("renders task titles", () => {
471
- const tasks = [
472
- createTask({ id: "t1", title: "First Task", status: "pending" }),
473
- createTask({ id: "t2", title: "Second Task", status: "in_progress" }),
474
- ];
475
- const result = renderTasksTable(tasks);
476
- const plain = stripAnsi(result);
477
- expect(plain).toContain("First Task");
478
- expect(plain).toContain("Second Task");
479
- });
480
- test("excludes completed and cancelled tasks", () => {
481
- const tasks = [
482
- createTask({ id: "t1", title: "Active Task", status: "pending" }),
483
- createTask({ id: "t2", title: "Done Task", status: "completed" }),
484
- createTask({ id: "t3", title: "Cancelled Task", status: "cancelled" }),
485
- ];
486
- const result = renderTasksTable(tasks);
487
- const plain = stripAnsi(result);
488
- expect(plain).toContain("Active Task");
489
- expect(plain).not.toContain("Done Task");
490
- expect(plain).not.toContain("Cancelled Task");
491
- });
492
- test("respects limit parameter", () => {
493
- const tasks = Array.from({ length: 20 }, (_, i) => createTask({ id: `t${i}`, title: `Task ${i}`, status: "pending" }));
494
- const result = renderTasksTable(tasks, 5);
495
- const plain = stripAnsi(result);
496
- expect(plain).toContain("Task 0");
497
- expect(plain).toContain("Task 4");
498
- expect(plain).not.toContain("Task 5");
499
- expect(plain).toContain("+15 more tasks");
500
- });
501
- test("renders status column", () => {
502
- const tasks = [
503
- createTask({ id: "t1", title: "Pending Task", status: "pending" }),
504
- createTask({ id: "t2", title: "In Progress Task", status: "in_progress" }),
505
- ];
506
- const result = renderTasksTable(tasks);
507
- const plain = stripAnsi(result);
508
- expect(plain).toContain("Status");
509
- expect(plain).toContain("pending");
510
- // Status may be truncated in the column, check for partial match
511
- expect(plain).toMatch(/in_prog/);
512
- });
513
- test("renders priority column", () => {
514
- const tasks = [
515
- createTask({ id: "t1", title: "Critical Task", status: "pending", priority: "critical" }),
516
- createTask({ id: "t2", title: "Low Task", status: "pending", priority: "low" }),
517
- ];
518
- const result = renderTasksTable(tasks);
519
- const plain = stripAnsi(result);
520
- expect(plain).toContain("Priority");
521
- });
522
- });
523
- // =============================================================================
524
- // renderDashboard Tests
525
- // =============================================================================
526
- describe("renderDashboard", () => {
527
- const defaultData = {
528
- tasks: [createTask({ status: "pending" })],
529
- workspaces: [createWorkspaceInfo()],
530
- };
531
- test("renders banner by default", () => {
532
- const result = renderDashboard(defaultData, () => []);
533
- const plain = stripAnsi(result);
534
- // Banner is rendered as ASCII art blocks, check for pattern
535
- expect(plain).toMatch(/████/);
536
- });
537
- test("hides banner when showBanner is false", () => {
538
- const result = renderDashboard(defaultData, () => [], { showBanner: false });
539
- const plain = stripAnsi(result);
540
- // Banner is ASCII art blocks, should not be present
541
- expect(plain).not.toMatch(/████.*████.*████/);
542
- });
543
- test("shows version when provided", () => {
544
- const data = { ...defaultData, version: "1.2.3" };
545
- const result = renderDashboard(data, () => []);
546
- const plain = stripAnsi(result);
547
- expect(plain).toContain("v1.2.3");
548
- });
549
- test("shows current workspace name when provided", () => {
550
- const data = {
551
- ...defaultData,
552
- currentWorkspace: "my-workspace",
553
- };
554
- const result = renderDashboard(data, () => []);
555
- const plain = stripAnsi(result);
556
- expect(plain).toContain("Workspace:");
557
- expect(plain).toContain("my-workspace");
558
- });
559
- test("shows all workspaces count when no current workspace", () => {
560
- const data = {
561
- tasks: [],
562
- workspaces: [createWorkspaceInfo({ name: "ws1" }), createWorkspaceInfo({ name: "ws2" })],
563
- };
564
- const result = renderDashboard(data, () => []);
565
- const plain = stripAnsi(result);
566
- expect(plain).toContain("All Workspaces");
567
- expect(plain).toContain("2 workspaces");
568
- });
569
- test("renders inbox widget when items present", () => {
570
- const data = {
571
- ...defaultData,
572
- inboxItems: [createInboxItem({ content: "Test inbox item", status: "pending" })],
573
- };
574
- const result = renderDashboard(data, () => [], { showInbox: true });
575
- const plain = stripAnsi(result);
576
- expect(plain).toContain("Inbox");
577
- expect(plain).toContain("Test inbox item");
578
- });
579
- test("hides inbox widget when showInbox is false", () => {
580
- const data = {
581
- ...defaultData,
582
- inboxItems: [createInboxItem({ content: "Test inbox item", status: "pending" })],
583
- };
584
- const result = renderDashboard(data, () => [], { showInbox: false });
585
- const plain = stripAnsi(result);
586
- expect(plain).not.toContain("Test inbox item");
587
- });
588
- test("renders workspaces table for multi-workspace view", () => {
589
- const data = {
590
- tasks: [],
591
- workspaces: [
592
- createWorkspaceInfo({ name: "workspace-one" }),
593
- createWorkspaceInfo({ name: "workspace-two" }),
594
- ],
595
- };
596
- const result = renderDashboard(data, () => [], { showWorkspaces: true });
597
- const plain = stripAnsi(result);
598
- expect(plain).toContain("Workspaces");
599
- expect(plain).toContain("workspace-one");
600
- expect(plain).toContain("workspace-two");
601
- });
602
- test("strips ANSI codes when stripAnsiCodes is true", () => {
603
- const result = renderDashboard(defaultData, () => [], { stripAnsiCodes: true });
604
- // Should not contain ANSI escape sequences
605
- expect(result).not.toMatch(/\x1b\[/);
606
- });
607
- });
608
- // =============================================================================
609
- // renderProjectDashboard Tests
610
- // =============================================================================
611
- describe("renderProjectDashboard", () => {
612
- test("renders single workspace dashboard", () => {
613
- const workspace = "my-workspace";
614
- const tasks = [
615
- createTask({ id: "t1", title: "Task 1", status: "pending" }),
616
- createTask({ id: "t2", title: "Task 2", status: "completed" }),
617
- ];
618
- const result = renderProjectDashboard(workspace, tasks);
619
- const plain = stripAnsi(result);
620
- expect(plain).toContain("my-workspace");
621
- expect(plain).toContain("Task 1");
622
- });
623
- test("includes version when provided", () => {
624
- const workspace = "test-workspace";
625
- const result = renderProjectDashboard(workspace, [], { version: "2.0.0" });
626
- const plain = stripAnsi(result);
627
- expect(plain).toContain("v2.0.0");
628
- });
629
- test("strips ANSI when requested", () => {
630
- const workspace = "test-workspace";
631
- const result = renderProjectDashboard(workspace, [], { stripAnsiCodes: true });
632
- expect(result).not.toMatch(/\x1b\[/);
633
- });
634
- test("shows tasks table", () => {
635
- const workspace = "test-workspace";
636
- const tasks = [
637
- createTask({ id: "t1", title: "Active Task", status: "pending" }),
638
- ];
639
- const result = renderProjectDashboard(workspace, tasks);
640
- const plain = stripAnsi(result);
641
- expect(plain).toContain("Tasks");
642
- expect(plain).toContain("Active Task");
643
- });
644
- });
645
- // =============================================================================
646
- // renderGlobalDashboard Tests
647
- // =============================================================================
648
- describe("renderGlobalDashboard", () => {
649
- test("renders global dashboard with all components", () => {
650
- const workspaces = [
651
- createWorkspaceInfo({ name: "workspace-a" }),
652
- createWorkspaceInfo({ name: "workspace-b" }),
653
- ];
654
- const tasks = [
655
- createTask({ id: "t1", workspace: "workspace-a", status: "pending" }),
656
- createTask({ id: "t2", workspace: "workspace-b", status: "completed" }),
657
- ];
658
- const inboxItems = [
659
- createInboxItem({ content: "Inbox note", status: "pending" }),
660
- ];
661
- const result = renderGlobalDashboard(workspaces, tasks, inboxItems, (workspace) => tasks.filter((t) => t.workspace === workspace));
662
- const plain = stripAnsi(result);
663
- // Banner is rendered as ASCII art blocks
664
- expect(plain).toMatch(/████/);
665
- expect(plain).toContain("workspace-a");
666
- expect(plain).toContain("workspace-b");
667
- expect(plain).toContain("Inbox note");
668
- });
669
- test("includes version when provided", () => {
670
- const result = renderGlobalDashboard([], [], [], () => [], { version: "3.0.0" });
671
- const plain = stripAnsi(result);
672
- expect(plain).toContain("v3.0.0");
673
- });
674
- test("strips ANSI when requested", () => {
675
- const result = renderGlobalDashboard([], [], [], () => [], {
676
- stripAnsiCodes: true,
677
- });
678
- expect(result).not.toMatch(/\x1b\[/);
679
- });
680
- test("shows tasks table for single workspace", () => {
681
- const workspaces = [createWorkspaceInfo({ name: "solo-workspace" })];
682
- const tasks = [
683
- createTask({ id: "t1", workspace: "solo-workspace", title: "Solo Task", status: "pending" }),
684
- ];
685
- const result = renderGlobalDashboard(workspaces, tasks, [], () => tasks);
686
- const plain = stripAnsi(result);
687
- expect(plain).toContain("Tasks");
688
- expect(plain).toContain("Solo Task");
689
- });
690
- test("uses getWorkspaceTasks callback correctly", () => {
691
- const workspaces = [
692
- createWorkspaceInfo({ name: "workspace-one" }),
693
- createWorkspaceInfo({ name: "workspace-two" }),
694
- ];
695
- const allTasks = [
696
- createTask({ id: "t1", workspace: "workspace-one", status: "completed" }),
697
- createTask({ id: "t2", workspace: "workspace-one", status: "pending" }),
698
- createTask({ id: "t3", workspace: "workspace-two", status: "pending" }),
699
- ];
700
- let callCount = 0;
701
- const getWorkspaceTasks = (workspace) => {
702
- callCount++;
703
- return allTasks.filter((t) => t.workspace === workspace);
704
- };
705
- renderGlobalDashboard(workspaces, allTasks, [], getWorkspaceTasks);
706
- // Should be called for each workspace in the table
707
- expect(callCount).toBeGreaterThan(0);
708
- });
709
- });
710
- // =============================================================================
711
- // Edge Cases and Integration Tests
712
- // =============================================================================
713
- describe("edge cases", () => {
714
- test("handles tasks with all statuses", () => {
715
- const tasks = [
716
- createTask({ id: "t1", status: "pending" }),
717
- createTask({ id: "t2", status: "in_progress" }),
718
- createTask({ id: "t3", status: "completed" }),
719
- createTask({ id: "t4", status: "blocked" }),
720
- createTask({ id: "t5", status: "cancelled" }),
721
- ];
722
- const stats = calculateStats(tasks);
723
- expect(stats.total).toBe(5);
724
- expect(stats.pending).toBe(1);
725
- expect(stats.inProgress).toBe(1);
726
- expect(stats.completed).toBe(1);
727
- expect(stats.blocked).toBe(1);
728
- expect(stats.cancelled).toBe(1);
729
- });
730
- test("handles circular-like dependency scenario", () => {
731
- // Not truly circular, but complex dependency chain
732
- const tasks = [
733
- createTask({ id: "t1", status: "pending" }),
734
- createTask({
735
- id: "t2",
736
- status: "pending",
737
- dependencies: [{ taskId: "t1", type: "blocked_by" }],
738
- }),
739
- createTask({
740
- id: "t3",
741
- status: "pending",
742
- dependencies: [{ taskId: "t2", type: "blocked_by" }],
743
- }),
744
- ];
745
- const metrics = calculateDependencyMetrics(tasks);
746
- expect(metrics.readyToWork).toBe(1); // Only t1
747
- expect(metrics.blockedByDependencies).toBe(2); // t2, t3
748
- });
749
- test("handles very long task titles", () => {
750
- const tasks = [
751
- createTask({
752
- id: "t1",
753
- title: "A".repeat(200),
754
- status: "pending",
755
- }),
756
- ];
757
- const widget = renderActionsWidget(tasks);
758
- // Should not throw and should truncate
759
- expect(widget).toBeDefined();
760
- expect(widget.length).toBeLessThan(1000);
761
- });
762
- test("handles special characters in content", () => {
763
- const items = [
764
- createInboxItem({
765
- content: "Test with <special> & \"characters\"",
766
- status: "pending",
767
- }),
768
- ];
769
- const widget = renderInboxWidget(items);
770
- expect(widget).toBeDefined();
771
- expect(widget).not.toBeNull();
772
- });
773
- });
774
- //# sourceMappingURL=dashboard-renderer.test.js.map