@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.
- package/package.json +1 -6
- package/dist/algorithms/critical-path.d.ts +0 -46
- package/dist/algorithms/critical-path.d.ts.map +0 -1
- package/dist/algorithms/critical-path.js +0 -320
- package/dist/algorithms/critical-path.js.map +0 -1
- package/dist/algorithms/critical-path.test.d.ts +0 -2
- package/dist/algorithms/critical-path.test.d.ts.map +0 -1
- package/dist/algorithms/critical-path.test.js +0 -194
- package/dist/algorithms/critical-path.test.js.map +0 -1
- package/dist/algorithms/dependency-integrity.d.ts +0 -81
- package/dist/algorithms/dependency-integrity.d.ts.map +0 -1
- package/dist/algorithms/dependency-integrity.js +0 -207
- package/dist/algorithms/dependency-integrity.js.map +0 -1
- package/dist/algorithms/dependency-integrity.test.d.ts +0 -2
- package/dist/algorithms/dependency-integrity.test.d.ts.map +0 -1
- package/dist/algorithms/dependency-integrity.test.js +0 -309
- package/dist/algorithms/dependency-integrity.test.js.map +0 -1
- package/dist/algorithms/index.d.ts +0 -5
- package/dist/algorithms/index.d.ts.map +0 -1
- package/dist/algorithms/index.js +0 -5
- package/dist/algorithms/index.js.map +0 -1
- package/dist/algorithms/tech-analysis.d.ts +0 -106
- package/dist/algorithms/tech-analysis.d.ts.map +0 -1
- package/dist/algorithms/tech-analysis.js +0 -344
- package/dist/algorithms/tech-analysis.js.map +0 -1
- package/dist/algorithms/tech-analysis.test.d.ts +0 -2
- package/dist/algorithms/tech-analysis.test.d.ts.map +0 -1
- package/dist/algorithms/tech-analysis.test.js +0 -338
- package/dist/algorithms/tech-analysis.test.js.map +0 -1
- package/dist/algorithms/topological-sort.d.ts +0 -41
- package/dist/algorithms/topological-sort.d.ts.map +0 -1
- package/dist/algorithms/topological-sort.js +0 -165
- package/dist/algorithms/topological-sort.js.map +0 -1
- package/dist/algorithms/topological-sort.test.d.ts +0 -2
- package/dist/algorithms/topological-sort.test.d.ts.map +0 -1
- package/dist/algorithms/topological-sort.test.js +0 -162
- package/dist/algorithms/topological-sort.test.js.map +0 -1
- package/dist/index.d.ts +0 -4
- package/dist/index.d.ts.map +0 -1
- package/dist/index.js +0 -7
- package/dist/index.js.map +0 -1
- package/dist/schemas/inbox.d.ts +0 -55
- package/dist/schemas/inbox.d.ts.map +0 -1
- package/dist/schemas/inbox.js +0 -25
- package/dist/schemas/inbox.js.map +0 -1
- package/dist/schemas/index.d.ts +0 -7
- package/dist/schemas/index.d.ts.map +0 -1
- package/dist/schemas/index.js +0 -17
- package/dist/schemas/index.js.map +0 -1
- package/dist/schemas/project.d.ts +0 -177
- package/dist/schemas/project.d.ts.map +0 -1
- package/dist/schemas/project.js +0 -56
- package/dist/schemas/project.js.map +0 -1
- package/dist/schemas/response-format.d.ts +0 -148
- package/dist/schemas/response-format.d.ts.map +0 -1
- package/dist/schemas/response-format.js +0 -18
- package/dist/schemas/response-format.js.map +0 -1
- package/dist/schemas/response-schema.d.ts +0 -307
- package/dist/schemas/response-schema.d.ts.map +0 -1
- package/dist/schemas/response-schema.js +0 -75
- package/dist/schemas/response-schema.js.map +0 -1
- package/dist/schemas/response-schema.test.d.ts +0 -2
- package/dist/schemas/response-schema.test.d.ts.map +0 -1
- package/dist/schemas/response-schema.test.js +0 -256
- package/dist/schemas/response-schema.test.js.map +0 -1
- package/dist/schemas/state.d.ts +0 -17
- package/dist/schemas/state.d.ts.map +0 -1
- package/dist/schemas/state.js +0 -17
- package/dist/schemas/state.js.map +0 -1
- package/dist/schemas/task.d.ts +0 -881
- package/dist/schemas/task.d.ts.map +0 -1
- package/dist/schemas/task.js +0 -189
- package/dist/schemas/task.js.map +0 -1
- package/dist/schemas/view.d.ts +0 -143
- package/dist/schemas/view.d.ts.map +0 -1
- package/dist/schemas/view.js +0 -48
- package/dist/schemas/view.js.map +0 -1
- package/dist/utils/dashboard-renderer.d.ts +0 -93
- package/dist/utils/dashboard-renderer.d.ts.map +0 -1
- package/dist/utils/dashboard-renderer.js +0 -424
- package/dist/utils/dashboard-renderer.js.map +0 -1
- package/dist/utils/dashboard-renderer.test.d.ts +0 -2
- package/dist/utils/dashboard-renderer.test.d.ts.map +0 -1
- package/dist/utils/dashboard-renderer.test.js +0 -774
- package/dist/utils/dashboard-renderer.test.js.map +0 -1
- package/dist/utils/date.d.ts +0 -94
- package/dist/utils/date.d.ts.map +0 -1
- package/dist/utils/date.js +0 -323
- package/dist/utils/date.js.map +0 -1
- package/dist/utils/date.test.d.ts +0 -2
- package/dist/utils/date.test.d.ts.map +0 -1
- package/dist/utils/date.test.js +0 -276
- package/dist/utils/date.test.js.map +0 -1
- package/dist/utils/hierarchy.d.ts +0 -102
- package/dist/utils/hierarchy.d.ts.map +0 -1
- package/dist/utils/hierarchy.js +0 -236
- package/dist/utils/hierarchy.js.map +0 -1
- package/dist/utils/hierarchy.test.d.ts +0 -2
- package/dist/utils/hierarchy.test.d.ts.map +0 -1
- package/dist/utils/hierarchy.test.js +0 -436
- package/dist/utils/hierarchy.test.js.map +0 -1
- package/dist/utils/id.d.ts +0 -60
- package/dist/utils/id.d.ts.map +0 -1
- package/dist/utils/id.js +0 -118
- package/dist/utils/id.js.map +0 -1
- package/dist/utils/id.test.d.ts +0 -2
- package/dist/utils/id.test.d.ts.map +0 -1
- package/dist/utils/id.test.js +0 -193
- package/dist/utils/id.test.js.map +0 -1
- package/dist/utils/index.d.ts +0 -12
- package/dist/utils/index.d.ts.map +0 -1
- package/dist/utils/index.js +0 -34
- package/dist/utils/index.js.map +0 -1
- package/dist/utils/natural-language.d.ts +0 -57
- package/dist/utils/natural-language.d.ts.map +0 -1
- package/dist/utils/natural-language.js +0 -211
- package/dist/utils/natural-language.js.map +0 -1
- package/dist/utils/natural-language.test.d.ts +0 -2
- package/dist/utils/natural-language.test.d.ts.map +0 -1
- package/dist/utils/natural-language.test.js +0 -197
- package/dist/utils/natural-language.test.js.map +0 -1
- package/dist/utils/priority-queue.d.ts +0 -17
- package/dist/utils/priority-queue.d.ts.map +0 -1
- package/dist/utils/priority-queue.js +0 -62
- package/dist/utils/priority-queue.js.map +0 -1
- package/dist/utils/priority-queue.test.d.ts +0 -2
- package/dist/utils/priority-queue.test.d.ts.map +0 -1
- package/dist/utils/priority-queue.test.js +0 -82
- package/dist/utils/priority-queue.test.js.map +0 -1
- package/dist/utils/projection.d.ts +0 -65
- package/dist/utils/projection.d.ts.map +0 -1
- package/dist/utils/projection.js +0 -180
- package/dist/utils/projection.js.map +0 -1
- package/dist/utils/projection.test.d.ts +0 -2
- package/dist/utils/projection.test.d.ts.map +0 -1
- package/dist/utils/projection.test.js +0 -336
- package/dist/utils/projection.test.js.map +0 -1
- package/dist/utils/terminal-ui.d.ts +0 -208
- package/dist/utils/terminal-ui.d.ts.map +0 -1
- package/dist/utils/terminal-ui.js +0 -611
- package/dist/utils/terminal-ui.js.map +0 -1
- package/dist/utils/terminal-ui.test.d.ts +0 -2
- package/dist/utils/terminal-ui.test.d.ts.map +0 -1
- package/dist/utils/terminal-ui.test.js +0 -683
- package/dist/utils/terminal-ui.test.js.map +0 -1
- package/dist/utils/workspace.d.ts +0 -100
- package/dist/utils/workspace.d.ts.map +0 -1
- package/dist/utils/workspace.js +0 -173
- package/dist/utils/workspace.js.map +0 -1
- package/dist/utils/workspace.test.d.ts +0 -2
- package/dist/utils/workspace.test.d.ts.map +0 -1
- package/dist/utils/workspace.test.js +0 -97
- package/dist/utils/workspace.test.js.map +0 -1
- package/src/algorithms/critical-path.test.ts +0 -241
- package/src/algorithms/dependency-integrity.test.ts +0 -348
- package/src/algorithms/tech-analysis.test.ts +0 -413
- package/src/algorithms/topological-sort.test.ts +0 -190
- package/src/schemas/response-schema.test.ts +0 -314
- package/src/utils/dashboard-renderer.test.ts +0 -983
- package/src/utils/date.test.ts +0 -329
- package/src/utils/hierarchy.test.ts +0 -505
- package/src/utils/id.test.ts +0 -235
- package/src/utils/natural-language.test.ts +0 -242
- package/src/utils/priority-queue.test.ts +0 -103
- package/src/utils/projection.test.ts +0 -425
- package/src/utils/terminal-ui.test.ts +0 -831
- 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
|