@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
package/src/utils/id.test.ts
DELETED
|
@@ -1,235 +0,0 @@
|
|
|
1
|
-
import { describe, test, expect } from "bun:test";
|
|
2
|
-
import {
|
|
3
|
-
generateId,
|
|
4
|
-
generateTaskId,
|
|
5
|
-
generateInboxId,
|
|
6
|
-
isValidTaskId,
|
|
7
|
-
isValidInboxId,
|
|
8
|
-
validateTaskId,
|
|
9
|
-
validateInboxId,
|
|
10
|
-
assertValidTaskId,
|
|
11
|
-
assertValidInboxId,
|
|
12
|
-
InvalidIdError,
|
|
13
|
-
} from "./id.js";
|
|
14
|
-
|
|
15
|
-
describe("ID Generation", () => {
|
|
16
|
-
describe("generateId", () => {
|
|
17
|
-
test("generates ID without prefix", () => {
|
|
18
|
-
const id = generateId();
|
|
19
|
-
expect(id.length).toBe(12);
|
|
20
|
-
expect(/^[a-z0-9]+$/.test(id)).toBe(true);
|
|
21
|
-
});
|
|
22
|
-
|
|
23
|
-
test("generates ID with prefix", () => {
|
|
24
|
-
const id = generateId("test");
|
|
25
|
-
expect(id).toMatch(/^test_[a-z0-9]+$/);
|
|
26
|
-
});
|
|
27
|
-
|
|
28
|
-
test("generates unique IDs", () => {
|
|
29
|
-
const ids = new Set(Array.from({ length: 100 }, () => generateId("test")));
|
|
30
|
-
expect(ids.size).toBe(100);
|
|
31
|
-
});
|
|
32
|
-
});
|
|
33
|
-
|
|
34
|
-
describe("generateTaskId", () => {
|
|
35
|
-
test("generates task ID with correct prefix", () => {
|
|
36
|
-
const id = generateTaskId();
|
|
37
|
-
expect(id).toMatch(/^task_[a-z0-9]+$/);
|
|
38
|
-
});
|
|
39
|
-
});
|
|
40
|
-
|
|
41
|
-
describe("generateInboxId", () => {
|
|
42
|
-
test("generates inbox ID with correct prefix", () => {
|
|
43
|
-
const id = generateInboxId();
|
|
44
|
-
expect(id).toMatch(/^inbox_[a-z0-9]+$/);
|
|
45
|
-
});
|
|
46
|
-
});
|
|
47
|
-
});
|
|
48
|
-
|
|
49
|
-
describe("Simple ID Validation (boolean)", () => {
|
|
50
|
-
describe("isValidTaskId", () => {
|
|
51
|
-
test("returns true for valid task IDs", () => {
|
|
52
|
-
expect(isValidTaskId("task_abc123")).toBe(true);
|
|
53
|
-
expect(isValidTaskId("task_a")).toBe(true);
|
|
54
|
-
expect(isValidTaskId("task_123")).toBe(true);
|
|
55
|
-
expect(isValidTaskId("task_abc123def456")).toBe(true);
|
|
56
|
-
});
|
|
57
|
-
|
|
58
|
-
test("returns false for invalid task IDs", () => {
|
|
59
|
-
expect(isValidTaskId("")).toBe(false);
|
|
60
|
-
expect(isValidTaskId("task_")).toBe(false);
|
|
61
|
-
expect(isValidTaskId("abc123")).toBe(false);
|
|
62
|
-
expect(isValidTaskId("proj_abc123")).toBe(false);
|
|
63
|
-
expect(isValidTaskId("task_ABC123")).toBe(false);
|
|
64
|
-
expect(isValidTaskId("task_abc-123")).toBe(false);
|
|
65
|
-
expect(isValidTaskId("task_abc.123")).toBe(false);
|
|
66
|
-
expect(isValidTaskId("task_abc/123")).toBe(false);
|
|
67
|
-
expect(isValidTaskId("task_abc 123")).toBe(false);
|
|
68
|
-
});
|
|
69
|
-
});
|
|
70
|
-
|
|
71
|
-
describe("isValidInboxId", () => {
|
|
72
|
-
test("returns true for valid inbox IDs", () => {
|
|
73
|
-
expect(isValidInboxId("inbox_abc123")).toBe(true);
|
|
74
|
-
expect(isValidInboxId("inbox_a")).toBe(true);
|
|
75
|
-
});
|
|
76
|
-
|
|
77
|
-
test("returns false for invalid inbox IDs", () => {
|
|
78
|
-
expect(isValidInboxId("")).toBe(false);
|
|
79
|
-
expect(isValidInboxId("in_abc123")).toBe(false);
|
|
80
|
-
expect(isValidInboxId("task_abc123")).toBe(false);
|
|
81
|
-
});
|
|
82
|
-
});
|
|
83
|
-
});
|
|
84
|
-
|
|
85
|
-
describe("Detailed ID Validation", () => {
|
|
86
|
-
describe("validateTaskId", () => {
|
|
87
|
-
test("returns valid: true for valid task IDs", () => {
|
|
88
|
-
expect(validateTaskId("task_abc123")).toEqual({ valid: true });
|
|
89
|
-
expect(validateTaskId("task_a1b2c3")).toEqual({ valid: true });
|
|
90
|
-
});
|
|
91
|
-
|
|
92
|
-
test("returns error for null/undefined", () => {
|
|
93
|
-
const result = validateTaskId(null);
|
|
94
|
-
expect(result.valid).toBe(false);
|
|
95
|
-
expect(result.reason).toContain("required");
|
|
96
|
-
|
|
97
|
-
const result2 = validateTaskId(undefined);
|
|
98
|
-
expect(result2.valid).toBe(false);
|
|
99
|
-
});
|
|
100
|
-
|
|
101
|
-
test("returns error for non-string", () => {
|
|
102
|
-
const result = validateTaskId(123);
|
|
103
|
-
expect(result.valid).toBe(false);
|
|
104
|
-
expect(result.reason).toContain("string");
|
|
105
|
-
expect(result.reason).toContain("number");
|
|
106
|
-
});
|
|
107
|
-
|
|
108
|
-
test("returns error for empty string", () => {
|
|
109
|
-
const result = validateTaskId("");
|
|
110
|
-
expect(result.valid).toBe(false);
|
|
111
|
-
expect(result.reason).toContain("empty");
|
|
112
|
-
});
|
|
113
|
-
|
|
114
|
-
test("returns error for missing prefix", () => {
|
|
115
|
-
const result = validateTaskId("abc123");
|
|
116
|
-
expect(result.valid).toBe(false);
|
|
117
|
-
expect(result.reason).toContain("task_");
|
|
118
|
-
});
|
|
119
|
-
|
|
120
|
-
test("returns error for wrong prefix", () => {
|
|
121
|
-
const result = validateTaskId("proj_abc123");
|
|
122
|
-
expect(result.valid).toBe(false);
|
|
123
|
-
expect(result.reason).toContain("task_");
|
|
124
|
-
});
|
|
125
|
-
|
|
126
|
-
test("returns error for invalid characters", () => {
|
|
127
|
-
const result = validateTaskId("task_abc-123");
|
|
128
|
-
expect(result.valid).toBe(false);
|
|
129
|
-
expect(result.reason).toContain("invalid characters");
|
|
130
|
-
});
|
|
131
|
-
});
|
|
132
|
-
|
|
133
|
-
describe("validateInboxId", () => {
|
|
134
|
-
test("returns valid: true for valid inbox IDs", () => {
|
|
135
|
-
expect(validateInboxId("inbox_abc123")).toEqual({ valid: true });
|
|
136
|
-
});
|
|
137
|
-
|
|
138
|
-
test("returns error for missing prefix", () => {
|
|
139
|
-
const result = validateInboxId("in_abc");
|
|
140
|
-
expect(result.valid).toBe(false);
|
|
141
|
-
expect(result.reason).toContain("inbox_");
|
|
142
|
-
});
|
|
143
|
-
});
|
|
144
|
-
});
|
|
145
|
-
|
|
146
|
-
describe("Assert Functions", () => {
|
|
147
|
-
describe("assertValidTaskId", () => {
|
|
148
|
-
test("does not throw for valid ID", () => {
|
|
149
|
-
expect(() => assertValidTaskId("task_abc123")).not.toThrow();
|
|
150
|
-
});
|
|
151
|
-
|
|
152
|
-
test("throws InvalidIdError for invalid ID", () => {
|
|
153
|
-
expect(() => assertValidTaskId("")).toThrow(InvalidIdError);
|
|
154
|
-
expect(() => assertValidTaskId("abc123")).toThrow(InvalidIdError);
|
|
155
|
-
expect(() => assertValidTaskId(null)).toThrow(InvalidIdError);
|
|
156
|
-
});
|
|
157
|
-
|
|
158
|
-
test("InvalidIdError contains correct properties", () => {
|
|
159
|
-
try {
|
|
160
|
-
assertValidTaskId("invalid");
|
|
161
|
-
} catch (error) {
|
|
162
|
-
expect(error).toBeInstanceOf(InvalidIdError);
|
|
163
|
-
if (error instanceof InvalidIdError) {
|
|
164
|
-
expect(error.idType).toBe("task");
|
|
165
|
-
expect(error.invalidValue).toBe("invalid");
|
|
166
|
-
expect(error.reason).toBeDefined();
|
|
167
|
-
expect(error.message).toContain("task");
|
|
168
|
-
}
|
|
169
|
-
}
|
|
170
|
-
});
|
|
171
|
-
});
|
|
172
|
-
|
|
173
|
-
describe("assertValidInboxId", () => {
|
|
174
|
-
test("does not throw for valid ID", () => {
|
|
175
|
-
expect(() => assertValidInboxId("inbox_abc123")).not.toThrow();
|
|
176
|
-
});
|
|
177
|
-
|
|
178
|
-
test("throws InvalidIdError for invalid ID", () => {
|
|
179
|
-
expect(() => assertValidInboxId("in_abc")).toThrow(InvalidIdError);
|
|
180
|
-
});
|
|
181
|
-
});
|
|
182
|
-
});
|
|
183
|
-
|
|
184
|
-
describe("InvalidIdError", () => {
|
|
185
|
-
test("extends Error", () => {
|
|
186
|
-
const error = new InvalidIdError("task", "bad_id", "test reason");
|
|
187
|
-
expect(error).toBeInstanceOf(Error);
|
|
188
|
-
expect(error).toBeInstanceOf(InvalidIdError);
|
|
189
|
-
});
|
|
190
|
-
|
|
191
|
-
test("has correct name", () => {
|
|
192
|
-
const error = new InvalidIdError("task", "bad_id", "test reason");
|
|
193
|
-
expect(error.name).toBe("InvalidIdError");
|
|
194
|
-
});
|
|
195
|
-
|
|
196
|
-
test("has correct message format", () => {
|
|
197
|
-
const error = new InvalidIdError("task", "bad_id", "test reason");
|
|
198
|
-
expect(error.message).toBe("Invalid task ID: test reason");
|
|
199
|
-
});
|
|
200
|
-
|
|
201
|
-
test("exposes idType, invalidValue, and reason", () => {
|
|
202
|
-
const error = new InvalidIdError("task", "wrong", "missing prefix");
|
|
203
|
-
expect(error.idType).toBe("task");
|
|
204
|
-
expect(error.invalidValue).toBe("wrong");
|
|
205
|
-
expect(error.reason).toBe("missing prefix");
|
|
206
|
-
});
|
|
207
|
-
});
|
|
208
|
-
|
|
209
|
-
describe("Security: ID Injection Prevention", () => {
|
|
210
|
-
test("rejects path traversal attempts", () => {
|
|
211
|
-
expect(validateTaskId("task_../etc/passwd").valid).toBe(false);
|
|
212
|
-
expect(validateTaskId("task_..%2F..%2Fetc").valid).toBe(false);
|
|
213
|
-
});
|
|
214
|
-
|
|
215
|
-
test("rejects null bytes", () => {
|
|
216
|
-
expect(validateTaskId("task_abc\0def").valid).toBe(false);
|
|
217
|
-
});
|
|
218
|
-
|
|
219
|
-
test("rejects SQL injection attempts", () => {
|
|
220
|
-
expect(validateTaskId("task_'; DROP TABLE tasks;--").valid).toBe(false);
|
|
221
|
-
expect(validateTaskId("task_1 OR 1=1").valid).toBe(false);
|
|
222
|
-
});
|
|
223
|
-
|
|
224
|
-
test("rejects script injection attempts", () => {
|
|
225
|
-
expect(validateTaskId("task_<script>alert(1)</script>").valid).toBe(false);
|
|
226
|
-
});
|
|
227
|
-
|
|
228
|
-
test("only allows safe alphanumeric characters", () => {
|
|
229
|
-
// The regex ^task_[a-z0-9]+$ only allows lowercase letters and numbers
|
|
230
|
-
expect(validateTaskId("task_abc123").valid).toBe(true);
|
|
231
|
-
expect(validateTaskId("task_UPPERCASE").valid).toBe(false);
|
|
232
|
-
expect(validateTaskId("task_with_underscore").valid).toBe(false);
|
|
233
|
-
expect(validateTaskId("task_with-dash").valid).toBe(false);
|
|
234
|
-
});
|
|
235
|
-
});
|
|
@@ -1,242 +0,0 @@
|
|
|
1
|
-
import { describe, test, expect } from "bun:test";
|
|
2
|
-
import {
|
|
3
|
-
parseTaskInput,
|
|
4
|
-
parseInboxInput,
|
|
5
|
-
InputValidationError,
|
|
6
|
-
} from "./natural-language.js";
|
|
7
|
-
|
|
8
|
-
describe("parseTaskInput", () => {
|
|
9
|
-
describe("priority parsing", () => {
|
|
10
|
-
test("parses !critical priority", () => {
|
|
11
|
-
const result = parseTaskInput("Fix bug !critical");
|
|
12
|
-
expect(result.priority).toBe("critical");
|
|
13
|
-
expect(result.title).toBe("Fix bug");
|
|
14
|
-
});
|
|
15
|
-
|
|
16
|
-
test("parses !high priority", () => {
|
|
17
|
-
const result = parseTaskInput("Review PR !high");
|
|
18
|
-
expect(result.priority).toBe("high");
|
|
19
|
-
});
|
|
20
|
-
|
|
21
|
-
test("parses !medium priority", () => {
|
|
22
|
-
const result = parseTaskInput("Write docs !medium");
|
|
23
|
-
expect(result.priority).toBe("medium");
|
|
24
|
-
});
|
|
25
|
-
|
|
26
|
-
test("parses !low priority", () => {
|
|
27
|
-
const result = parseTaskInput("Clean up !low");
|
|
28
|
-
expect(result.priority).toBe("low");
|
|
29
|
-
});
|
|
30
|
-
|
|
31
|
-
test("parses Korean priority !높음", () => {
|
|
32
|
-
const result = parseTaskInput("버그 수정 !높음");
|
|
33
|
-
expect(result.priority).toBe("high");
|
|
34
|
-
expect(result.title).toBe("버그 수정");
|
|
35
|
-
});
|
|
36
|
-
|
|
37
|
-
test("parses Korean priority !긴급", () => {
|
|
38
|
-
const result = parseTaskInput("장애 대응 !긴급");
|
|
39
|
-
expect(result.priority).toBe("critical");
|
|
40
|
-
expect(result.title).toBe("장애 대응");
|
|
41
|
-
});
|
|
42
|
-
});
|
|
43
|
-
|
|
44
|
-
describe("tag parsing", () => {
|
|
45
|
-
test("parses single tag", () => {
|
|
46
|
-
const result = parseTaskInput("Fix auth #backend");
|
|
47
|
-
expect(result.tags).toEqual(["backend"]);
|
|
48
|
-
expect(result.title).toBe("Fix auth");
|
|
49
|
-
});
|
|
50
|
-
|
|
51
|
-
test("parses multiple tags", () => {
|
|
52
|
-
const result = parseTaskInput("Update API #backend #api #v2");
|
|
53
|
-
expect(result.tags).toEqual(["backend", "api", "v2"]);
|
|
54
|
-
});
|
|
55
|
-
|
|
56
|
-
test("parses Korean tags", () => {
|
|
57
|
-
const result = parseTaskInput("보고서 작성 #업무 #문서");
|
|
58
|
-
expect(result.tags).toEqual(["업무", "문서"]);
|
|
59
|
-
expect(result.title).toBe("보고서 작성");
|
|
60
|
-
});
|
|
61
|
-
});
|
|
62
|
-
|
|
63
|
-
describe("context parsing", () => {
|
|
64
|
-
test("parses single context", () => {
|
|
65
|
-
const result = parseTaskInput("Deep work task @focus");
|
|
66
|
-
expect(result.contexts).toEqual(["focus"]);
|
|
67
|
-
expect(result.title).toBe("Deep work task");
|
|
68
|
-
});
|
|
69
|
-
|
|
70
|
-
test("parses multiple contexts", () => {
|
|
71
|
-
const result = parseTaskInput("Review code @focus @office");
|
|
72
|
-
expect(result.contexts).toEqual(["focus", "office"]);
|
|
73
|
-
});
|
|
74
|
-
});
|
|
75
|
-
|
|
76
|
-
describe("due date parsing", () => {
|
|
77
|
-
test("parses 'by tomorrow'", () => {
|
|
78
|
-
const result = parseTaskInput("Submit report by tomorrow");
|
|
79
|
-
expect(result.dueDate).toBeDefined();
|
|
80
|
-
expect(result.title).toBe("Submit report");
|
|
81
|
-
});
|
|
82
|
-
|
|
83
|
-
test("parses 'until Friday'", () => {
|
|
84
|
-
const result = parseTaskInput("Complete feature until Friday");
|
|
85
|
-
expect(result.dueDate).toBeDefined();
|
|
86
|
-
});
|
|
87
|
-
|
|
88
|
-
test("parses Korean '내일까지'", () => {
|
|
89
|
-
const result = parseTaskInput("보고서 제출 내일까지");
|
|
90
|
-
expect(result.dueDate).toBeDefined();
|
|
91
|
-
expect(result.title).toBe("보고서 제출");
|
|
92
|
-
});
|
|
93
|
-
|
|
94
|
-
test("parses standalone 'tomorrow' at end", () => {
|
|
95
|
-
const result = parseTaskInput("Submit PR tomorrow");
|
|
96
|
-
expect(result.dueDate).toBeDefined();
|
|
97
|
-
expect(result.title).toBe("Submit PR");
|
|
98
|
-
});
|
|
99
|
-
});
|
|
100
|
-
|
|
101
|
-
describe("time estimate parsing", () => {
|
|
102
|
-
test("parses hours only", () => {
|
|
103
|
-
const result = parseTaskInput("Write tests 2h");
|
|
104
|
-
expect(result.estimate?.expected).toBe(120);
|
|
105
|
-
});
|
|
106
|
-
|
|
107
|
-
test("parses minutes only", () => {
|
|
108
|
-
const result = parseTaskInput("Quick fix 30m");
|
|
109
|
-
expect(result.estimate?.expected).toBe(30);
|
|
110
|
-
});
|
|
111
|
-
|
|
112
|
-
test("parses combined hours and minutes", () => {
|
|
113
|
-
const result = parseTaskInput("Big refactor 1h30m");
|
|
114
|
-
expect(result.estimate?.expected).toBe(90);
|
|
115
|
-
});
|
|
116
|
-
|
|
117
|
-
test("removes time estimate from title", () => {
|
|
118
|
-
const result = parseTaskInput("Task 2h30m done");
|
|
119
|
-
expect(result.estimate?.expected).toBe(150);
|
|
120
|
-
expect(result.title).toBe("Task done");
|
|
121
|
-
});
|
|
122
|
-
});
|
|
123
|
-
|
|
124
|
-
describe("sortOrder parsing", () => {
|
|
125
|
-
test("parses ^1 as sortOrder 1", () => {
|
|
126
|
-
const result = parseTaskInput("First task ^1");
|
|
127
|
-
expect(result.sortOrder).toBe(1);
|
|
128
|
-
expect(result.title).toBe("First task");
|
|
129
|
-
});
|
|
130
|
-
|
|
131
|
-
test("parses ^10 as sortOrder 10", () => {
|
|
132
|
-
const result = parseTaskInput("Tenth task ^10");
|
|
133
|
-
expect(result.sortOrder).toBe(10);
|
|
134
|
-
expect(result.title).toBe("Tenth task");
|
|
135
|
-
});
|
|
136
|
-
|
|
137
|
-
test("parses sortOrder in middle of input", () => {
|
|
138
|
-
const result = parseTaskInput("Task ^5 with more text");
|
|
139
|
-
expect(result.sortOrder).toBe(5);
|
|
140
|
-
expect(result.title).toBe("Task with more text");
|
|
141
|
-
});
|
|
142
|
-
|
|
143
|
-
test("parses sortOrder with other metadata", () => {
|
|
144
|
-
const result = parseTaskInput("Task ^3 #dev !high");
|
|
145
|
-
expect(result.sortOrder).toBe(3);
|
|
146
|
-
expect(result.tags).toEqual(["dev"]);
|
|
147
|
-
expect(result.priority).toBe("high");
|
|
148
|
-
expect(result.title).toBe("Task");
|
|
149
|
-
});
|
|
150
|
-
});
|
|
151
|
-
|
|
152
|
-
describe("combined parsing", () => {
|
|
153
|
-
test("parses full English input", () => {
|
|
154
|
-
const result = parseTaskInput("Review PR #dev !high @focus");
|
|
155
|
-
expect(result.title).toBe("Review PR");
|
|
156
|
-
expect(result.tags).toEqual(["dev"]);
|
|
157
|
-
expect(result.priority).toBe("high");
|
|
158
|
-
expect(result.contexts).toEqual(["focus"]);
|
|
159
|
-
});
|
|
160
|
-
|
|
161
|
-
test("parses input with due date", () => {
|
|
162
|
-
const result = parseTaskInput("Submit report by tomorrow #work");
|
|
163
|
-
expect(result.title).toBe("Submit report");
|
|
164
|
-
expect(result.tags).toEqual(["work"]);
|
|
165
|
-
expect(result.dueDate).toBeDefined();
|
|
166
|
-
});
|
|
167
|
-
|
|
168
|
-
test("parses full Korean input", () => {
|
|
169
|
-
const result = parseTaskInput("보고서 작성 내일까지 #업무 !높음 @집중");
|
|
170
|
-
expect(result.title).toBe("보고서 작성");
|
|
171
|
-
expect(result.tags).toEqual(["업무"]);
|
|
172
|
-
expect(result.priority).toBe("high");
|
|
173
|
-
expect(result.contexts).toEqual(["집중"]);
|
|
174
|
-
expect(result.dueDate).toBeDefined();
|
|
175
|
-
});
|
|
176
|
-
|
|
177
|
-
test("returns clean title with no metadata", () => {
|
|
178
|
-
const result = parseTaskInput("Simple task");
|
|
179
|
-
expect(result.title).toBe("Simple task");
|
|
180
|
-
expect(result.tags).toBeUndefined();
|
|
181
|
-
expect(result.priority).toBeUndefined();
|
|
182
|
-
expect(result.contexts).toBeUndefined();
|
|
183
|
-
expect(result.dueDate).toBeUndefined();
|
|
184
|
-
});
|
|
185
|
-
});
|
|
186
|
-
|
|
187
|
-
describe("empty content validation", () => {
|
|
188
|
-
test("throws error when input contains only tags", () => {
|
|
189
|
-
expect(() => parseTaskInput("#tag #only")).toThrow(InputValidationError);
|
|
190
|
-
expect(() => parseTaskInput("#tag #only")).toThrow(
|
|
191
|
-
"Task title cannot be empty after parsing"
|
|
192
|
-
);
|
|
193
|
-
});
|
|
194
|
-
|
|
195
|
-
test("throws error when input contains only metadata", () => {
|
|
196
|
-
expect(() => parseTaskInput("#dev !high @focus")).toThrow(
|
|
197
|
-
InputValidationError
|
|
198
|
-
);
|
|
199
|
-
});
|
|
200
|
-
|
|
201
|
-
test("throws error for empty input", () => {
|
|
202
|
-
expect(() => parseTaskInput("")).toThrow(InputValidationError);
|
|
203
|
-
});
|
|
204
|
-
|
|
205
|
-
test("throws error for whitespace-only input", () => {
|
|
206
|
-
expect(() => parseTaskInput(" ")).toThrow(InputValidationError);
|
|
207
|
-
});
|
|
208
|
-
});
|
|
209
|
-
});
|
|
210
|
-
|
|
211
|
-
describe("parseInboxInput", () => {
|
|
212
|
-
describe("basic parsing", () => {
|
|
213
|
-
test("parses content with tags", () => {
|
|
214
|
-
const result = parseInboxInput("GraphQL 도입 검토 #backend #연구");
|
|
215
|
-
expect(result.content).toBe("GraphQL 도입 검토");
|
|
216
|
-
expect(result.tags).toEqual(["backend", "연구"]);
|
|
217
|
-
});
|
|
218
|
-
|
|
219
|
-
test("parses content without tags", () => {
|
|
220
|
-
const result = parseInboxInput("Simple idea to capture");
|
|
221
|
-
expect(result.content).toBe("Simple idea to capture");
|
|
222
|
-
expect(result.tags).toBeUndefined();
|
|
223
|
-
});
|
|
224
|
-
});
|
|
225
|
-
|
|
226
|
-
describe("empty content validation", () => {
|
|
227
|
-
test("throws error when input contains only tags", () => {
|
|
228
|
-
expect(() => parseInboxInput("#tag #only")).toThrow(InputValidationError);
|
|
229
|
-
expect(() => parseInboxInput("#tag #only")).toThrow(
|
|
230
|
-
"Content cannot be empty after parsing"
|
|
231
|
-
);
|
|
232
|
-
});
|
|
233
|
-
|
|
234
|
-
test("throws error for empty input", () => {
|
|
235
|
-
expect(() => parseInboxInput("")).toThrow(InputValidationError);
|
|
236
|
-
});
|
|
237
|
-
|
|
238
|
-
test("throws error for whitespace-only input", () => {
|
|
239
|
-
expect(() => parseInboxInput(" ")).toThrow(InputValidationError);
|
|
240
|
-
});
|
|
241
|
-
});
|
|
242
|
-
});
|
|
@@ -1,103 +0,0 @@
|
|
|
1
|
-
import { describe, expect, test } from "bun:test";
|
|
2
|
-
import { PriorityQueue } from "./priority-queue";
|
|
3
|
-
|
|
4
|
-
describe("PriorityQueue", () => {
|
|
5
|
-
test("creates empty queue", () => {
|
|
6
|
-
const pq = new PriorityQueue<number>((a, b) => a - b);
|
|
7
|
-
expect(pq.length).toBe(0);
|
|
8
|
-
expect(pq.isEmpty()).toBe(true);
|
|
9
|
-
});
|
|
10
|
-
|
|
11
|
-
test("pop from empty queue returns undefined", () => {
|
|
12
|
-
const pq = new PriorityQueue<number>((a, b) => a - b);
|
|
13
|
-
expect(pq.pop()).toBeUndefined();
|
|
14
|
-
});
|
|
15
|
-
|
|
16
|
-
test("peek from empty queue returns undefined", () => {
|
|
17
|
-
const pq = new PriorityQueue<number>((a, b) => a - b);
|
|
18
|
-
expect(pq.peek()).toBeUndefined();
|
|
19
|
-
});
|
|
20
|
-
|
|
21
|
-
test("single element operations", () => {
|
|
22
|
-
const pq = new PriorityQueue<number>((a, b) => a - b);
|
|
23
|
-
pq.push(42);
|
|
24
|
-
expect(pq.length).toBe(1);
|
|
25
|
-
expect(pq.peek()).toBe(42);
|
|
26
|
-
expect(pq.pop()).toBe(42);
|
|
27
|
-
expect(pq.isEmpty()).toBe(true);
|
|
28
|
-
});
|
|
29
|
-
|
|
30
|
-
test("maintains max-heap property (higher value first with a-b comparator)", () => {
|
|
31
|
-
const pq = new PriorityQueue<number>((a, b) => a - b);
|
|
32
|
-
pq.push(5);
|
|
33
|
-
pq.push(2);
|
|
34
|
-
pq.push(8);
|
|
35
|
-
pq.push(1);
|
|
36
|
-
pq.push(9);
|
|
37
|
-
|
|
38
|
-
// Max-heap: higher values come first
|
|
39
|
-
expect(pq.pop()).toBe(9);
|
|
40
|
-
expect(pq.pop()).toBe(8);
|
|
41
|
-
expect(pq.pop()).toBe(5);
|
|
42
|
-
expect(pq.pop()).toBe(2);
|
|
43
|
-
expect(pq.pop()).toBe(1);
|
|
44
|
-
});
|
|
45
|
-
|
|
46
|
-
test("works with reversed comparator (min-heap behavior)", () => {
|
|
47
|
-
const pq = new PriorityQueue<number>((a, b) => b - a);
|
|
48
|
-
pq.push(5);
|
|
49
|
-
pq.push(2);
|
|
50
|
-
pq.push(8);
|
|
51
|
-
|
|
52
|
-
// Reversed comparator: smaller values come first
|
|
53
|
-
expect(pq.pop()).toBe(2);
|
|
54
|
-
expect(pq.pop()).toBe(5);
|
|
55
|
-
expect(pq.pop()).toBe(8);
|
|
56
|
-
});
|
|
57
|
-
|
|
58
|
-
test("works with objects", () => {
|
|
59
|
-
interface Task {
|
|
60
|
-
priority: number;
|
|
61
|
-
name: string;
|
|
62
|
-
}
|
|
63
|
-
// Higher priority value = higher priority (max-heap)
|
|
64
|
-
const pq = new PriorityQueue<Task>((a, b) => a.priority - b.priority);
|
|
65
|
-
|
|
66
|
-
pq.push({ priority: 3, name: "high" });
|
|
67
|
-
pq.push({ priority: 1, name: "low" });
|
|
68
|
-
pq.push({ priority: 2, name: "medium" });
|
|
69
|
-
|
|
70
|
-
expect(pq.pop()?.name).toBe("high");
|
|
71
|
-
expect(pq.pop()?.name).toBe("medium");
|
|
72
|
-
expect(pq.pop()?.name).toBe("low");
|
|
73
|
-
});
|
|
74
|
-
|
|
75
|
-
test("length property tracks queue size correctly", () => {
|
|
76
|
-
const pq = new PriorityQueue<number>((a, b) => a - b);
|
|
77
|
-
expect(pq.length).toBe(0);
|
|
78
|
-
|
|
79
|
-
pq.push(1);
|
|
80
|
-
expect(pq.length).toBe(1);
|
|
81
|
-
|
|
82
|
-
pq.push(2);
|
|
83
|
-
pq.push(3);
|
|
84
|
-
expect(pq.length).toBe(3);
|
|
85
|
-
|
|
86
|
-
pq.pop();
|
|
87
|
-
expect(pq.length).toBe(2);
|
|
88
|
-
|
|
89
|
-
pq.pop();
|
|
90
|
-
pq.pop();
|
|
91
|
-
expect(pq.length).toBe(0);
|
|
92
|
-
});
|
|
93
|
-
|
|
94
|
-
test("peek does not remove element", () => {
|
|
95
|
-
const pq = new PriorityQueue<number>((a, b) => a - b);
|
|
96
|
-
pq.push(5);
|
|
97
|
-
pq.push(10);
|
|
98
|
-
|
|
99
|
-
expect(pq.peek()).toBe(10);
|
|
100
|
-
expect(pq.peek()).toBe(10);
|
|
101
|
-
expect(pq.length).toBe(2);
|
|
102
|
-
});
|
|
103
|
-
});
|