@doist/todoist-ai 2.2.2 → 4.0.0
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/README.md +6 -14
- package/dist/index.d.ts +619 -250
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +45 -29
- package/dist/main.js +2 -1
- package/dist/mcp-helpers.d.ts +25 -3
- package/dist/mcp-helpers.d.ts.map +1 -1
- package/dist/mcp-helpers.js +37 -19
- package/dist/mcp-server.d.ts.map +1 -1
- package/dist/mcp-server.js +44 -28
- package/dist/tools/__tests__/add-comments.test.d.ts +2 -0
- package/dist/tools/__tests__/add-comments.test.d.ts.map +1 -0
- package/dist/tools/__tests__/add-comments.test.js +241 -0
- package/dist/tools/__tests__/add-projects.test.d.ts +2 -0
- package/dist/tools/__tests__/add-projects.test.d.ts.map +1 -0
- package/dist/tools/__tests__/add-projects.test.js +152 -0
- package/dist/tools/__tests__/add-sections.test.d.ts +2 -0
- package/dist/tools/__tests__/add-sections.test.d.ts.map +1 -0
- package/dist/tools/__tests__/add-sections.test.js +181 -0
- package/dist/tools/__tests__/add-tasks.test.d.ts +2 -0
- package/dist/tools/__tests__/add-tasks.test.d.ts.map +1 -0
- package/dist/tools/__tests__/{tasks-add-multiple.test.js → add-tasks.test.js} +89 -79
- package/dist/tools/__tests__/complete-tasks.test.d.ts +2 -0
- package/dist/tools/__tests__/complete-tasks.test.d.ts.map +1 -0
- package/dist/tools/__tests__/complete-tasks.test.js +206 -0
- package/dist/tools/__tests__/delete-object.test.d.ts +2 -0
- package/dist/tools/__tests__/delete-object.test.d.ts.map +1 -0
- package/dist/tools/__tests__/{delete-one.test.js → delete-object.test.js} +42 -22
- package/dist/tools/__tests__/find-comments.test.d.ts +2 -0
- package/dist/tools/__tests__/find-comments.test.d.ts.map +1 -0
- package/dist/tools/__tests__/find-comments.test.js +242 -0
- package/dist/tools/__tests__/find-completed-tasks.test.d.ts +2 -0
- package/dist/tools/__tests__/find-completed-tasks.test.d.ts.map +1 -0
- package/dist/tools/__tests__/{tasks-list-completed.test.js → find-completed-tasks.test.js} +13 -36
- package/dist/tools/__tests__/find-projects.test.d.ts +2 -0
- package/dist/tools/__tests__/find-projects.test.d.ts.map +1 -0
- package/dist/tools/__tests__/{projects-list.test.js → find-projects.test.js} +55 -39
- package/dist/tools/__tests__/find-sections.test.d.ts +2 -0
- package/dist/tools/__tests__/find-sections.test.d.ts.map +1 -0
- package/dist/tools/__tests__/{sections-search.test.js → find-sections.test.js} +64 -50
- package/dist/tools/__tests__/find-tasks-by-date.test.d.ts +2 -0
- package/dist/tools/__tests__/find-tasks-by-date.test.d.ts.map +1 -0
- package/dist/tools/__tests__/{tasks-list-by-date.test.js → find-tasks-by-date.test.js} +96 -14
- package/dist/tools/__tests__/find-tasks.test.d.ts +2 -0
- package/dist/tools/__tests__/find-tasks.test.d.ts.map +1 -0
- package/dist/tools/__tests__/find-tasks.test.js +334 -0
- package/dist/tools/__tests__/get-overview.test.d.ts +2 -0
- package/dist/tools/__tests__/get-overview.test.d.ts.map +1 -0
- package/dist/tools/__tests__/{overview.test.js → get-overview.test.js} +77 -13
- package/dist/tools/__tests__/update-comments.test.d.ts +2 -0
- package/dist/tools/__tests__/update-comments.test.d.ts.map +1 -0
- package/dist/tools/__tests__/update-comments.test.js +296 -0
- package/dist/tools/__tests__/update-projects.test.d.ts +2 -0
- package/dist/tools/__tests__/update-projects.test.d.ts.map +1 -0
- package/dist/tools/__tests__/update-projects.test.js +205 -0
- package/dist/tools/__tests__/update-sections.test.d.ts +2 -0
- package/dist/tools/__tests__/update-sections.test.d.ts.map +1 -0
- package/dist/tools/__tests__/update-sections.test.js +156 -0
- package/dist/tools/__tests__/update-tasks.test.d.ts +2 -0
- package/dist/tools/__tests__/update-tasks.test.d.ts.map +1 -0
- package/dist/tools/__tests__/update-tasks.test.js +645 -0
- package/dist/tools/add-comments.d.ts +51 -0
- package/dist/tools/add-comments.d.ts.map +1 -0
- package/dist/tools/add-comments.js +79 -0
- package/dist/tools/add-projects.d.ts +50 -0
- package/dist/tools/add-projects.d.ts.map +1 -0
- package/dist/tools/add-projects.js +59 -0
- package/dist/tools/add-sections.d.ts +46 -0
- package/dist/tools/add-sections.d.ts.map +1 -0
- package/dist/tools/add-sections.js +61 -0
- package/dist/tools/add-tasks.d.ts +82 -0
- package/dist/tools/add-tasks.d.ts.map +1 -0
- package/dist/tools/add-tasks.js +96 -0
- package/dist/tools/complete-tasks.d.ts +40 -0
- package/dist/tools/complete-tasks.d.ts.map +1 -0
- package/dist/tools/complete-tasks.js +68 -0
- package/dist/tools/delete-object.d.ts +38 -0
- package/dist/tools/delete-object.d.ts.map +1 -0
- package/dist/tools/delete-object.js +79 -0
- package/dist/tools/find-comments.d.ts +46 -0
- package/dist/tools/find-comments.d.ts.map +1 -0
- package/dist/tools/find-comments.js +143 -0
- package/dist/tools/find-completed-tasks.d.ts +74 -0
- package/dist/tools/find-completed-tasks.d.ts.map +1 -0
- package/dist/tools/find-completed-tasks.js +112 -0
- package/dist/tools/find-projects.d.ts +53 -0
- package/dist/tools/find-projects.d.ts.map +1 -0
- package/dist/tools/find-projects.js +101 -0
- package/dist/tools/find-sections.d.ts +42 -0
- package/dist/tools/find-sections.d.ts.map +1 -0
- package/dist/tools/find-sections.js +96 -0
- package/dist/tools/find-tasks-by-date.d.ts +59 -0
- package/dist/tools/find-tasks-by-date.d.ts.map +1 -0
- package/dist/tools/find-tasks-by-date.js +121 -0
- package/dist/tools/find-tasks.d.ts +65 -0
- package/dist/tools/find-tasks.d.ts.map +1 -0
- package/dist/tools/find-tasks.js +182 -0
- package/dist/tools/get-overview.d.ts +67 -0
- package/dist/tools/get-overview.d.ts.map +1 -0
- package/dist/tools/{overview.js → get-overview.js} +66 -19
- package/dist/tools/update-comments.d.ts +50 -0
- package/dist/tools/update-comments.d.ts.map +1 -0
- package/dist/tools/update-comments.js +82 -0
- package/dist/tools/update-projects.d.ts +59 -0
- package/dist/tools/update-projects.d.ts.map +1 -0
- package/dist/tools/update-projects.js +84 -0
- package/dist/tools/update-sections.d.ts +47 -0
- package/dist/tools/update-sections.d.ts.map +1 -0
- package/dist/tools/update-sections.js +70 -0
- package/dist/tools/update-tasks.d.ts +94 -0
- package/dist/tools/update-tasks.d.ts.map +1 -0
- package/dist/tools/update-tasks.js +120 -0
- package/dist/utils/constants.d.ts +39 -0
- package/dist/utils/constants.d.ts.map +1 -0
- package/dist/utils/constants.js +41 -0
- package/dist/utils/response-builders.d.ts +88 -0
- package/dist/utils/response-builders.d.ts.map +1 -0
- package/dist/utils/response-builders.js +202 -0
- package/dist/{tools → utils}/test-helpers.d.ts +16 -0
- package/dist/utils/test-helpers.d.ts.map +1 -0
- package/dist/{tools → utils}/test-helpers.js +51 -0
- package/dist/utils/tool-names.d.ts +28 -0
- package/dist/utils/tool-names.d.ts.map +1 -0
- package/dist/utils/tool-names.js +31 -0
- package/package.json +1 -1
- package/dist/tools/__tests__/delete-one.test.d.ts +0 -2
- package/dist/tools/__tests__/delete-one.test.d.ts.map +0 -1
- package/dist/tools/__tests__/overview.test.d.ts +0 -2
- package/dist/tools/__tests__/overview.test.d.ts.map +0 -1
- package/dist/tools/__tests__/projects-list.test.d.ts +0 -2
- package/dist/tools/__tests__/projects-list.test.d.ts.map +0 -1
- package/dist/tools/__tests__/projects-manage.test.d.ts +0 -2
- package/dist/tools/__tests__/projects-manage.test.d.ts.map +0 -1
- package/dist/tools/__tests__/projects-manage.test.js +0 -106
- package/dist/tools/__tests__/sections-manage.test.d.ts +0 -2
- package/dist/tools/__tests__/sections-manage.test.d.ts.map +0 -1
- package/dist/tools/__tests__/sections-manage.test.js +0 -138
- package/dist/tools/__tests__/sections-search.test.d.ts +0 -2
- package/dist/tools/__tests__/sections-search.test.d.ts.map +0 -1
- package/dist/tools/__tests__/tasks-add-multiple.test.d.ts +0 -2
- package/dist/tools/__tests__/tasks-add-multiple.test.d.ts.map +0 -1
- package/dist/tools/__tests__/tasks-complete-multiple.test.d.ts +0 -2
- package/dist/tools/__tests__/tasks-complete-multiple.test.d.ts.map +0 -1
- package/dist/tools/__tests__/tasks-complete-multiple.test.js +0 -146
- package/dist/tools/__tests__/tasks-list-by-date.test.d.ts +0 -2
- package/dist/tools/__tests__/tasks-list-by-date.test.d.ts.map +0 -1
- package/dist/tools/__tests__/tasks-list-completed.test.d.ts +0 -2
- package/dist/tools/__tests__/tasks-list-completed.test.d.ts.map +0 -1
- package/dist/tools/__tests__/tasks-list-for-container.test.d.ts +0 -2
- package/dist/tools/__tests__/tasks-list-for-container.test.d.ts.map +0 -1
- package/dist/tools/__tests__/tasks-list-for-container.test.js +0 -232
- package/dist/tools/__tests__/tasks-organize-multiple.test.d.ts +0 -2
- package/dist/tools/__tests__/tasks-organize-multiple.test.d.ts.map +0 -1
- package/dist/tools/__tests__/tasks-organize-multiple.test.js +0 -245
- package/dist/tools/__tests__/tasks-search.test.d.ts +0 -2
- package/dist/tools/__tests__/tasks-search.test.d.ts.map +0 -1
- package/dist/tools/__tests__/tasks-search.test.js +0 -106
- package/dist/tools/__tests__/tasks-update-one.test.d.ts +0 -2
- package/dist/tools/__tests__/tasks-update-one.test.d.ts.map +0 -1
- package/dist/tools/__tests__/tasks-update-one.test.js +0 -251
- package/dist/tools/delete-one.d.ts +0 -17
- package/dist/tools/delete-one.d.ts.map +0 -1
- package/dist/tools/delete-one.js +0 -25
- package/dist/tools/overview.d.ts +0 -14
- package/dist/tools/overview.d.ts.map +0 -1
- package/dist/tools/projects-list.d.ts +0 -29
- package/dist/tools/projects-list.d.ts.map +0 -1
- package/dist/tools/projects-list.js +0 -39
- package/dist/tools/projects-manage.d.ts +0 -24
- package/dist/tools/projects-manage.d.ts.map +0 -1
- package/dist/tools/projects-manage.js +0 -26
- package/dist/tools/sections-manage.d.ts +0 -23
- package/dist/tools/sections-manage.d.ts.map +0 -1
- package/dist/tools/sections-manage.js +0 -37
- package/dist/tools/sections-search.d.ts +0 -18
- package/dist/tools/sections-search.d.ts.map +0 -1
- package/dist/tools/sections-search.js +0 -27
- package/dist/tools/tasks-add-multiple.d.ts +0 -55
- package/dist/tools/tasks-add-multiple.d.ts.map +0 -1
- package/dist/tools/tasks-add-multiple.js +0 -52
- package/dist/tools/tasks-complete-multiple.d.ts +0 -16
- package/dist/tools/tasks-complete-multiple.d.ts.map +0 -1
- package/dist/tools/tasks-complete-multiple.js +0 -23
- package/dist/tools/tasks-list-by-date.d.ts +0 -34
- package/dist/tools/tasks-list-by-date.d.ts.map +0 -1
- package/dist/tools/tasks-list-by-date.js +0 -53
- package/dist/tools/tasks-list-completed.d.ts +0 -44
- package/dist/tools/tasks-list-completed.d.ts.map +0 -1
- package/dist/tools/tasks-list-completed.js +0 -49
- package/dist/tools/tasks-list-for-container.d.ts +0 -34
- package/dist/tools/tasks-list-for-container.d.ts.map +0 -1
- package/dist/tools/tasks-list-for-container.js +0 -48
- package/dist/tools/tasks-organize-multiple.d.ts +0 -37
- package/dist/tools/tasks-organize-multiple.d.ts.map +0 -1
- package/dist/tools/tasks-organize-multiple.js +0 -34
- package/dist/tools/tasks-search.d.ts +0 -32
- package/dist/tools/tasks-search.d.ts.map +0 -1
- package/dist/tools/tasks-search.js +0 -30
- package/dist/tools/tasks-update-one.d.ts +0 -29
- package/dist/tools/tasks-update-one.d.ts.map +0 -1
- package/dist/tools/tasks-update-one.js +0 -63
- package/dist/tools/test-helpers.d.ts.map +0 -1
|
@@ -1,13 +1,14 @@
|
|
|
1
1
|
import { jest } from '@jest/globals';
|
|
2
2
|
import { getTasksByFilter } from '../../tool-helpers.js';
|
|
3
|
-
import {
|
|
4
|
-
import {
|
|
3
|
+
import { TEST_ERRORS, TEST_IDS, createMappedTask, extractStructuredContent, extractTextContent, } from '../../utils/test-helpers.js';
|
|
4
|
+
import { ToolNames } from '../../utils/tool-names.js';
|
|
5
|
+
import { findTasksByDate } from '../find-tasks-by-date.js';
|
|
5
6
|
// Mock the tool helpers
|
|
6
7
|
jest.mock('../../tool-helpers', () => ({
|
|
7
8
|
getTasksByFilter: jest.fn(),
|
|
8
9
|
}));
|
|
9
10
|
const mockGetTasksByFilter = getTasksByFilter;
|
|
10
|
-
// Mock the Todoist API (not directly used by tasks-
|
|
11
|
+
// Mock the Todoist API (not directly used by find-tasks-by-date, but needed for type)
|
|
11
12
|
const mockTodoistApi = {};
|
|
12
13
|
// Mock date-fns functions to make tests deterministic
|
|
13
14
|
jest.mock('date-fns', () => ({
|
|
@@ -25,7 +26,8 @@ jest.mock('date-fns', () => ({
|
|
|
25
26
|
return '2025-08-16'; // Return predictable end date
|
|
26
27
|
}),
|
|
27
28
|
}));
|
|
28
|
-
|
|
29
|
+
const { FIND_TASKS_BY_DATE, UPDATE_TASKS } = ToolNames;
|
|
30
|
+
describe(`${FIND_TASKS_BY_DATE} tool`, () => {
|
|
29
31
|
beforeEach(() => {
|
|
30
32
|
jest.clearAllMocks();
|
|
31
33
|
// Mock current date to make tests deterministic
|
|
@@ -52,14 +54,26 @@ describe('tasks-list-by-date tool', () => {
|
|
|
52
54
|
: [];
|
|
53
55
|
const mockResponse = { tasks: mockTasks, nextCursor: null };
|
|
54
56
|
mockGetTasksByFilter.mockResolvedValue(mockResponse);
|
|
55
|
-
const result = await
|
|
57
|
+
const result = await findTasksByDate.execute({ startDate: 'overdue', limit: 50, daysCount }, mockTodoistApi);
|
|
56
58
|
expect(mockGetTasksByFilter).toHaveBeenCalledWith({
|
|
57
59
|
client: mockTodoistApi,
|
|
58
60
|
query: 'overdue',
|
|
59
61
|
cursor: undefined,
|
|
60
62
|
limit: 50,
|
|
61
63
|
});
|
|
62
|
-
|
|
64
|
+
// Verify result is a concise summary
|
|
65
|
+
expect(extractTextContent(result)).toMatchSnapshot();
|
|
66
|
+
// Verify structured content
|
|
67
|
+
const structuredContent = extractStructuredContent(result);
|
|
68
|
+
expect(structuredContent.tasks).toHaveLength(hasTasks ? 1 : 0);
|
|
69
|
+
expect(structuredContent).toEqual(expect.objectContaining({
|
|
70
|
+
totalCount: hasTasks ? 1 : 0,
|
|
71
|
+
hasMore: false,
|
|
72
|
+
nextCursor: null,
|
|
73
|
+
appliedFilters: expect.objectContaining({
|
|
74
|
+
startDate: 'overdue',
|
|
75
|
+
}),
|
|
76
|
+
}));
|
|
63
77
|
});
|
|
64
78
|
});
|
|
65
79
|
describe('listing tasks by date range', () => {
|
|
@@ -67,14 +81,15 @@ describe('tasks-list-by-date tool', () => {
|
|
|
67
81
|
const mockTasks = [createMappedTask({ content: 'Today task', dueDate: '2025-08-15' })];
|
|
68
82
|
const mockResponse = { tasks: mockTasks, nextCursor: null };
|
|
69
83
|
mockGetTasksByFilter.mockResolvedValue(mockResponse);
|
|
70
|
-
const result = await
|
|
84
|
+
const result = await findTasksByDate.execute({ startDate: 'today', limit: 50, daysCount: 7 }, mockTodoistApi);
|
|
71
85
|
expect(mockGetTasksByFilter).toHaveBeenCalledWith({
|
|
72
86
|
client: mockTodoistApi,
|
|
73
87
|
query: expect.stringContaining('due after:') && expect.stringContaining('due before:'),
|
|
74
88
|
cursor: undefined,
|
|
75
89
|
limit: 50,
|
|
76
90
|
});
|
|
77
|
-
|
|
91
|
+
// Verify result is a concise summary
|
|
92
|
+
expect(extractTextContent(result)).toMatchSnapshot();
|
|
78
93
|
});
|
|
79
94
|
it.each([
|
|
80
95
|
{
|
|
@@ -108,14 +123,15 @@ describe('tasks-list-by-date tool', () => {
|
|
|
108
123
|
])('should handle $name', async ({ params, tasks, cursor }) => {
|
|
109
124
|
const mockResponse = { tasks, nextCursor: cursor };
|
|
110
125
|
mockGetTasksByFilter.mockResolvedValue(mockResponse);
|
|
111
|
-
const result = await
|
|
126
|
+
const result = await findTasksByDate.execute(params, mockTodoistApi);
|
|
112
127
|
expect(mockGetTasksByFilter).toHaveBeenCalledWith({
|
|
113
128
|
client: mockTodoistApi,
|
|
114
129
|
query: expect.stringContaining('2025-08-20'),
|
|
115
130
|
cursor: params.cursor || undefined,
|
|
116
131
|
limit: params.limit,
|
|
117
132
|
});
|
|
118
|
-
|
|
133
|
+
// Verify result is a concise summary
|
|
134
|
+
expect(extractTextContent(result)).toMatchSnapshot();
|
|
119
135
|
});
|
|
120
136
|
});
|
|
121
137
|
describe('pagination and limits', () => {
|
|
@@ -140,7 +156,7 @@ describe('tasks-list-by-date tool', () => {
|
|
|
140
156
|
])('should handle $name', async ({ params, expectedCursor, expectedLimit }) => {
|
|
141
157
|
const mockResponse = { tasks: [], nextCursor: null };
|
|
142
158
|
mockGetTasksByFilter.mockResolvedValue(mockResponse);
|
|
143
|
-
await
|
|
159
|
+
await findTasksByDate.execute(params, mockTodoistApi);
|
|
144
160
|
expect(mockGetTasksByFilter).toHaveBeenCalledWith({
|
|
145
161
|
client: mockTodoistApi,
|
|
146
162
|
query: expect.any(String),
|
|
@@ -158,13 +174,79 @@ describe('tasks-list-by-date tool', () => {
|
|
|
158
174
|
const mockResponse = { tasks: [], nextCursor: null };
|
|
159
175
|
mockGetTasksByFilter.mockResolvedValue(mockResponse);
|
|
160
176
|
const startDate = daysCount === 7 ? 'today' : '2025-08-15';
|
|
161
|
-
const result = await
|
|
177
|
+
const result = await findTasksByDate.execute({ startDate, limit: 50, daysCount }, mockTodoistApi);
|
|
162
178
|
expect(mockGetTasksByFilter).toHaveBeenCalledTimes(1);
|
|
163
179
|
if (shouldReturnResult) {
|
|
164
|
-
|
|
180
|
+
// Verify result is a concise summary
|
|
181
|
+
expect(extractTextContent(result)).toMatchSnapshot();
|
|
165
182
|
}
|
|
166
183
|
});
|
|
167
184
|
});
|
|
185
|
+
describe('next steps logic', () => {
|
|
186
|
+
it('should suggest appropriate actions when hasOverdue is true', async () => {
|
|
187
|
+
const mockTasks = [
|
|
188
|
+
createMappedTask({
|
|
189
|
+
id: TEST_IDS.TASK_1,
|
|
190
|
+
content: 'Overdue task from list',
|
|
191
|
+
dueDate: '2025-08-10', // Past date - creates hasOverdue context
|
|
192
|
+
}),
|
|
193
|
+
];
|
|
194
|
+
const mockResponse = { tasks: mockTasks, nextCursor: null };
|
|
195
|
+
mockGetTasksByFilter.mockResolvedValue(mockResponse);
|
|
196
|
+
const result = await findTasksByDate.execute({ startDate: '2025-08-15', limit: 10, daysCount: 1 }, mockTodoistApi);
|
|
197
|
+
const textContent = extractTextContent(result);
|
|
198
|
+
expect(textContent).toMatchSnapshot();
|
|
199
|
+
expect(textContent).toContain(`Use ${UPDATE_TASKS} to modify priorities or due dates`);
|
|
200
|
+
});
|
|
201
|
+
it('should suggest today-focused actions when startDate is today', async () => {
|
|
202
|
+
const mockTasks = [
|
|
203
|
+
createMappedTask({
|
|
204
|
+
id: TEST_IDS.TASK_1,
|
|
205
|
+
content: "Today's task",
|
|
206
|
+
dueDate: '2025-08-15', // Today's date based on our mock
|
|
207
|
+
}),
|
|
208
|
+
];
|
|
209
|
+
const mockResponse = { tasks: mockTasks, nextCursor: null };
|
|
210
|
+
mockGetTasksByFilter.mockResolvedValue(mockResponse);
|
|
211
|
+
const result = await findTasksByDate.execute({ startDate: 'today', limit: 10, daysCount: 1 }, mockTodoistApi);
|
|
212
|
+
const textContent = extractTextContent(result);
|
|
213
|
+
expect(textContent).toMatchSnapshot();
|
|
214
|
+
expect(textContent).toContain(`Use ${UPDATE_TASKS} to modify priorities or due dates`);
|
|
215
|
+
});
|
|
216
|
+
it('should suggest appropriate actions when startDate is overdue', async () => {
|
|
217
|
+
const mockTasks = [
|
|
218
|
+
createMappedTask({
|
|
219
|
+
id: TEST_IDS.TASK_1,
|
|
220
|
+
content: 'Overdue task',
|
|
221
|
+
dueDate: '2025-08-10',
|
|
222
|
+
}),
|
|
223
|
+
];
|
|
224
|
+
const mockResponse = { tasks: mockTasks, nextCursor: null };
|
|
225
|
+
mockGetTasksByFilter.mockResolvedValue(mockResponse);
|
|
226
|
+
const result = await findTasksByDate.execute({ startDate: 'overdue', limit: 10, daysCount: 1 }, mockTodoistApi);
|
|
227
|
+
const textContent = extractTextContent(result);
|
|
228
|
+
expect(textContent).toMatchSnapshot();
|
|
229
|
+
expect(textContent).toContain(`Use ${UPDATE_TASKS} to modify priorities or due dates`);
|
|
230
|
+
});
|
|
231
|
+
it('should provide helpful suggestions for empty overdue results', async () => {
|
|
232
|
+
const mockResponse = { tasks: [], nextCursor: null };
|
|
233
|
+
mockGetTasksByFilter.mockResolvedValue(mockResponse);
|
|
234
|
+
const result = await findTasksByDate.execute({ startDate: 'overdue', limit: 10, daysCount: 1 }, mockTodoistApi);
|
|
235
|
+
const textContent = extractTextContent(result);
|
|
236
|
+
expect(textContent).toMatchSnapshot();
|
|
237
|
+
expect(textContent).toContain('Great job! No overdue tasks');
|
|
238
|
+
expect(textContent).toContain("Check today's tasks with startDate='today'");
|
|
239
|
+
});
|
|
240
|
+
it('should provide helpful suggestions for empty date range results', async () => {
|
|
241
|
+
const mockResponse = { tasks: [], nextCursor: null };
|
|
242
|
+
mockGetTasksByFilter.mockResolvedValue(mockResponse);
|
|
243
|
+
const result = await findTasksByDate.execute({ startDate: '2025-08-20', limit: 10, daysCount: 1 }, mockTodoistApi);
|
|
244
|
+
const textContent = extractTextContent(result);
|
|
245
|
+
expect(textContent).toMatchSnapshot();
|
|
246
|
+
expect(textContent).toContain("Expand date range with larger 'daysCount'");
|
|
247
|
+
expect(textContent).toContain("Check 'overdue' for past-due items");
|
|
248
|
+
});
|
|
249
|
+
});
|
|
168
250
|
describe('error handling', () => {
|
|
169
251
|
it.each([
|
|
170
252
|
{
|
|
@@ -186,7 +268,7 @@ describe('tasks-list-by-date tool', () => {
|
|
|
186
268
|
},
|
|
187
269
|
])('should propagate $error', async ({ error, params }) => {
|
|
188
270
|
mockGetTasksByFilter.mockRejectedValue(new Error(error));
|
|
189
|
-
await expect(
|
|
271
|
+
await expect(findTasksByDate.execute(params, mockTodoistApi)).rejects.toThrow(error);
|
|
190
272
|
});
|
|
191
273
|
});
|
|
192
274
|
});
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"find-tasks.test.d.ts","sourceRoot":"","sources":["../../../src/tools/__tests__/find-tasks.test.ts"],"names":[],"mappings":""}
|
|
@@ -0,0 +1,334 @@
|
|
|
1
|
+
import { jest } from '@jest/globals';
|
|
2
|
+
import { getTasksByFilter } from '../../tool-helpers.js';
|
|
3
|
+
import { TEST_ERRORS, TEST_IDS, TODAY, createMappedTask, createMockApiResponse, createMockTask, extractStructuredContent, extractTextContent, } from '../../utils/test-helpers.js';
|
|
4
|
+
import { ToolNames } from '../../utils/tool-names.js';
|
|
5
|
+
import { findTasks } from '../find-tasks.js';
|
|
6
|
+
jest.mock('../../tool-helpers', () => {
|
|
7
|
+
const actual = jest.requireActual('../../tool-helpers');
|
|
8
|
+
return {
|
|
9
|
+
getTasksByFilter: jest.fn(),
|
|
10
|
+
mapTask: actual.mapTask,
|
|
11
|
+
};
|
|
12
|
+
});
|
|
13
|
+
const { FIND_TASKS, UPDATE_TASKS, FIND_COMPLETED_TASKS } = ToolNames;
|
|
14
|
+
const mockGetTasksByFilter = getTasksByFilter;
|
|
15
|
+
// Mock the Todoist API
|
|
16
|
+
const mockTodoistApi = {
|
|
17
|
+
getTasks: jest.fn(),
|
|
18
|
+
};
|
|
19
|
+
describe(`${FIND_TASKS} tool`, () => {
|
|
20
|
+
beforeEach(() => {
|
|
21
|
+
jest.clearAllMocks();
|
|
22
|
+
});
|
|
23
|
+
describe('searching tasks', () => {
|
|
24
|
+
it('should search tasks and return results', async () => {
|
|
25
|
+
const mockTasks = [
|
|
26
|
+
createMappedTask({
|
|
27
|
+
id: TEST_IDS.TASK_1,
|
|
28
|
+
content: 'Task containing search term',
|
|
29
|
+
description: 'Description with more details',
|
|
30
|
+
labels: ['work'],
|
|
31
|
+
}),
|
|
32
|
+
createMappedTask({
|
|
33
|
+
id: TEST_IDS.TASK_2,
|
|
34
|
+
content: 'Another matching task',
|
|
35
|
+
priority: 2,
|
|
36
|
+
sectionId: TEST_IDS.SECTION_1,
|
|
37
|
+
}),
|
|
38
|
+
];
|
|
39
|
+
const mockResponse = { tasks: mockTasks, nextCursor: 'cursor-for-next-page' };
|
|
40
|
+
mockGetTasksByFilter.mockResolvedValue(mockResponse);
|
|
41
|
+
const result = await findTasks.execute({
|
|
42
|
+
searchText: 'important meeting',
|
|
43
|
+
limit: 10,
|
|
44
|
+
}, mockTodoistApi);
|
|
45
|
+
expect(mockGetTasksByFilter).toHaveBeenCalledWith({
|
|
46
|
+
client: mockTodoistApi,
|
|
47
|
+
query: 'search: important meeting',
|
|
48
|
+
cursor: undefined,
|
|
49
|
+
limit: 10,
|
|
50
|
+
});
|
|
51
|
+
// Verify result is a concise summary
|
|
52
|
+
expect(extractTextContent(result)).toMatchSnapshot();
|
|
53
|
+
// Verify structured content
|
|
54
|
+
const structuredContent = extractStructuredContent(result);
|
|
55
|
+
expect(structuredContent).toEqual(expect.objectContaining({
|
|
56
|
+
tasks: expect.any(Array),
|
|
57
|
+
totalCount: 2,
|
|
58
|
+
hasMore: true,
|
|
59
|
+
nextCursor: 'cursor-for-next-page',
|
|
60
|
+
appliedFilters: {
|
|
61
|
+
searchText: 'important meeting',
|
|
62
|
+
limit: 10,
|
|
63
|
+
cursor: undefined,
|
|
64
|
+
},
|
|
65
|
+
}));
|
|
66
|
+
expect(structuredContent).toEqual(expect.objectContaining({
|
|
67
|
+
tasks: expect.any(Array),
|
|
68
|
+
}));
|
|
69
|
+
});
|
|
70
|
+
it.each([
|
|
71
|
+
{
|
|
72
|
+
name: 'custom limit',
|
|
73
|
+
params: { searchText: 'project update', limit: 5 },
|
|
74
|
+
expectedQuery: 'search: project update',
|
|
75
|
+
expectedLimit: 5,
|
|
76
|
+
expectedCursor: undefined,
|
|
77
|
+
},
|
|
78
|
+
{
|
|
79
|
+
name: 'pagination cursor',
|
|
80
|
+
params: { searchText: 'follow up', limit: 20, cursor: 'cursor-from-first-page' },
|
|
81
|
+
expectedQuery: 'search: follow up',
|
|
82
|
+
expectedLimit: 20,
|
|
83
|
+
expectedCursor: 'cursor-from-first-page',
|
|
84
|
+
},
|
|
85
|
+
])('should handle $name', async ({ params, expectedQuery, expectedLimit, expectedCursor }) => {
|
|
86
|
+
const mockTask = createMappedTask({ content: 'Test result' });
|
|
87
|
+
const mockResponse = { tasks: [mockTask], nextCursor: null };
|
|
88
|
+
mockGetTasksByFilter.mockResolvedValue(mockResponse);
|
|
89
|
+
const result = await findTasks.execute(params, mockTodoistApi);
|
|
90
|
+
expect(mockGetTasksByFilter).toHaveBeenCalledWith({
|
|
91
|
+
client: mockTodoistApi,
|
|
92
|
+
query: expectedQuery,
|
|
93
|
+
cursor: expectedCursor,
|
|
94
|
+
limit: expectedLimit,
|
|
95
|
+
});
|
|
96
|
+
// Verify result is a concise summary
|
|
97
|
+
expect(extractTextContent(result)).toMatchSnapshot();
|
|
98
|
+
// Verify structured content
|
|
99
|
+
const structuredContent = extractStructuredContent(result);
|
|
100
|
+
expect(structuredContent).toEqual(expect.objectContaining({
|
|
101
|
+
tasks: expect.any(Array),
|
|
102
|
+
totalCount: 1,
|
|
103
|
+
hasMore: false,
|
|
104
|
+
nextCursor: null,
|
|
105
|
+
appliedFilters: expect.objectContaining({
|
|
106
|
+
searchText: params.searchText,
|
|
107
|
+
limit: expectedLimit,
|
|
108
|
+
}),
|
|
109
|
+
}));
|
|
110
|
+
expect(structuredContent).toEqual(expect.objectContaining({
|
|
111
|
+
tasks: expect.any(Array),
|
|
112
|
+
}));
|
|
113
|
+
});
|
|
114
|
+
it.each([
|
|
115
|
+
{ searchText: '@work #urgent "exact phrase"', description: 'special characters' },
|
|
116
|
+
{ searchText: 'nonexistent keyword', description: 'empty results' },
|
|
117
|
+
])('should handle search with $description', async ({ searchText }) => {
|
|
118
|
+
const mockResponse = { tasks: [], nextCursor: null };
|
|
119
|
+
mockGetTasksByFilter.mockResolvedValue(mockResponse);
|
|
120
|
+
const result = await findTasks.execute({ searchText, limit: 10 }, mockTodoistApi);
|
|
121
|
+
expect(mockGetTasksByFilter).toHaveBeenCalledWith({
|
|
122
|
+
client: mockTodoistApi,
|
|
123
|
+
query: `search: ${searchText}`,
|
|
124
|
+
cursor: undefined,
|
|
125
|
+
limit: 10,
|
|
126
|
+
});
|
|
127
|
+
// Verify result is a concise summary
|
|
128
|
+
expect(extractTextContent(result)).toMatchSnapshot();
|
|
129
|
+
// Verify structured content for empty results
|
|
130
|
+
const structuredContent = extractStructuredContent(result);
|
|
131
|
+
expect(structuredContent).toEqual(expect.objectContaining({
|
|
132
|
+
tasks: [],
|
|
133
|
+
totalCount: 0,
|
|
134
|
+
hasMore: false,
|
|
135
|
+
nextCursor: null,
|
|
136
|
+
appliedFilters: expect.objectContaining({
|
|
137
|
+
searchText: searchText,
|
|
138
|
+
}),
|
|
139
|
+
}));
|
|
140
|
+
});
|
|
141
|
+
});
|
|
142
|
+
describe('validation', () => {
|
|
143
|
+
it('should require at least one filter parameter', async () => {
|
|
144
|
+
await expect(findTasks.execute({ limit: 10 }, mockTodoistApi)).rejects.toThrow('At least one filter must be provided: searchText, projectId, sectionId, or parentId');
|
|
145
|
+
});
|
|
146
|
+
});
|
|
147
|
+
describe('container filtering', () => {
|
|
148
|
+
it.each([
|
|
149
|
+
{
|
|
150
|
+
name: 'project',
|
|
151
|
+
params: { projectId: TEST_IDS.PROJECT_TEST, limit: 10 },
|
|
152
|
+
expectedApiParam: { projectId: TEST_IDS.PROJECT_TEST },
|
|
153
|
+
tasks: [createMockTask({ content: 'Project task' })],
|
|
154
|
+
},
|
|
155
|
+
{
|
|
156
|
+
name: 'section',
|
|
157
|
+
params: { sectionId: TEST_IDS.SECTION_1, limit: 10 },
|
|
158
|
+
expectedApiParam: { sectionId: TEST_IDS.SECTION_1 },
|
|
159
|
+
tasks: [createMockTask({ content: 'Section task' })],
|
|
160
|
+
},
|
|
161
|
+
{
|
|
162
|
+
name: 'parent task',
|
|
163
|
+
params: { parentId: TEST_IDS.TASK_1, limit: 10 },
|
|
164
|
+
expectedApiParam: { parentId: TEST_IDS.TASK_1 },
|
|
165
|
+
tasks: [createMockTask({ content: 'Subtask' })],
|
|
166
|
+
},
|
|
167
|
+
])('should find tasks in $name', async ({ params, expectedApiParam, tasks }) => {
|
|
168
|
+
mockTodoistApi.getTasks.mockResolvedValue(createMockApiResponse(tasks));
|
|
169
|
+
const result = await findTasks.execute(params, mockTodoistApi);
|
|
170
|
+
expect(mockTodoistApi.getTasks).toHaveBeenCalledWith({
|
|
171
|
+
limit: 10,
|
|
172
|
+
cursor: null,
|
|
173
|
+
...expectedApiParam,
|
|
174
|
+
});
|
|
175
|
+
expect(extractTextContent(result)).toMatchSnapshot();
|
|
176
|
+
const structuredContent = extractStructuredContent(result);
|
|
177
|
+
expect(structuredContent).toEqual(expect.objectContaining({
|
|
178
|
+
tasks: expect.any(Array),
|
|
179
|
+
totalCount: tasks.length,
|
|
180
|
+
hasMore: false,
|
|
181
|
+
appliedFilters: params,
|
|
182
|
+
}));
|
|
183
|
+
});
|
|
184
|
+
it('should handle combined search text and container filtering', async () => {
|
|
185
|
+
const tasks = [
|
|
186
|
+
createMockTask({
|
|
187
|
+
id: '8485093749',
|
|
188
|
+
content: 'relevant task',
|
|
189
|
+
description: 'contains search term',
|
|
190
|
+
}),
|
|
191
|
+
createMockTask({
|
|
192
|
+
id: '8485093750',
|
|
193
|
+
content: 'other task',
|
|
194
|
+
description: 'different content',
|
|
195
|
+
}),
|
|
196
|
+
];
|
|
197
|
+
mockTodoistApi.getTasks.mockResolvedValue(createMockApiResponse(tasks));
|
|
198
|
+
const result = await findTasks.execute({
|
|
199
|
+
projectId: TEST_IDS.PROJECT_TEST,
|
|
200
|
+
searchText: 'relevant',
|
|
201
|
+
limit: 10,
|
|
202
|
+
}, mockTodoistApi);
|
|
203
|
+
expect(mockTodoistApi.getTasks).toHaveBeenCalledWith({
|
|
204
|
+
limit: 10,
|
|
205
|
+
cursor: null,
|
|
206
|
+
projectId: TEST_IDS.PROJECT_TEST,
|
|
207
|
+
});
|
|
208
|
+
const structuredContent = extractStructuredContent(result);
|
|
209
|
+
expect(structuredContent.tasks).toHaveLength(1);
|
|
210
|
+
expect(structuredContent.tasks).toEqual([
|
|
211
|
+
expect.objectContaining({ content: 'relevant task' }),
|
|
212
|
+
]);
|
|
213
|
+
});
|
|
214
|
+
it('should handle empty containers', async () => {
|
|
215
|
+
mockTodoistApi.getTasks.mockResolvedValue(createMockApiResponse([]));
|
|
216
|
+
const result = await findTasks.execute({
|
|
217
|
+
sectionId: 'empty-section',
|
|
218
|
+
limit: 10,
|
|
219
|
+
}, mockTodoistApi);
|
|
220
|
+
const textContent = extractTextContent(result);
|
|
221
|
+
expect(textContent).toContain('Section is empty');
|
|
222
|
+
expect(textContent).toContain('Tasks may be in other sections of the project');
|
|
223
|
+
});
|
|
224
|
+
it('should handle pagination with containers', async () => {
|
|
225
|
+
mockTodoistApi.getTasks.mockResolvedValue({
|
|
226
|
+
results: [],
|
|
227
|
+
nextCursor: 'next-cursor',
|
|
228
|
+
});
|
|
229
|
+
const result = await findTasks.execute({
|
|
230
|
+
projectId: TEST_IDS.PROJECT_TEST,
|
|
231
|
+
limit: 25,
|
|
232
|
+
cursor: 'current-cursor',
|
|
233
|
+
}, mockTodoistApi);
|
|
234
|
+
expect(mockTodoistApi.getTasks).toHaveBeenCalledWith({
|
|
235
|
+
limit: 25,
|
|
236
|
+
cursor: 'current-cursor',
|
|
237
|
+
projectId: TEST_IDS.PROJECT_TEST,
|
|
238
|
+
});
|
|
239
|
+
const structuredContent = extractStructuredContent(result);
|
|
240
|
+
expect(structuredContent.hasMore).toBe(true);
|
|
241
|
+
expect(structuredContent.nextCursor).toBe('next-cursor');
|
|
242
|
+
});
|
|
243
|
+
});
|
|
244
|
+
describe('container error handling', () => {
|
|
245
|
+
it('should propagate API errors for container queries', async () => {
|
|
246
|
+
const apiError = new Error('API Error: Project not found');
|
|
247
|
+
mockTodoistApi.getTasks.mockRejectedValue(apiError);
|
|
248
|
+
await expect(findTasks.execute({ projectId: 'non-existent', limit: 10 }, mockTodoistApi)).rejects.toThrow('API Error: Project not found');
|
|
249
|
+
});
|
|
250
|
+
});
|
|
251
|
+
describe('next steps logic', () => {
|
|
252
|
+
it('should suggest different actions when hasOverdue is true', async () => {
|
|
253
|
+
const mockTasks = [
|
|
254
|
+
createMappedTask({
|
|
255
|
+
id: TEST_IDS.TASK_1,
|
|
256
|
+
content: 'Overdue search result',
|
|
257
|
+
dueDate: '2025-08-10', // Past date
|
|
258
|
+
}),
|
|
259
|
+
];
|
|
260
|
+
const mockResponse = { tasks: mockTasks, nextCursor: null };
|
|
261
|
+
mockGetTasksByFilter.mockResolvedValue(mockResponse);
|
|
262
|
+
const result = await findTasks.execute({ searchText: 'overdue tasks', limit: 10 }, mockTodoistApi);
|
|
263
|
+
const textContent = extractTextContent(result);
|
|
264
|
+
expect(textContent).toMatchSnapshot();
|
|
265
|
+
// Should prioritize overdue tasks in next steps
|
|
266
|
+
expect(textContent).toContain(`Use ${UPDATE_TASKS} to modify priorities or due dates`);
|
|
267
|
+
});
|
|
268
|
+
it('should suggest today tasks when hasToday is true', async () => {
|
|
269
|
+
const mockTasks = [
|
|
270
|
+
createMappedTask({
|
|
271
|
+
id: TEST_IDS.TASK_1,
|
|
272
|
+
content: 'Task due today',
|
|
273
|
+
dueDate: TODAY,
|
|
274
|
+
}),
|
|
275
|
+
];
|
|
276
|
+
const mockResponse = { tasks: mockTasks, nextCursor: null };
|
|
277
|
+
mockGetTasksByFilter.mockResolvedValue(mockResponse);
|
|
278
|
+
const result = await findTasks.execute({ searchText: 'today tasks', limit: 10 }, mockTodoistApi);
|
|
279
|
+
const textContent = extractTextContent(result);
|
|
280
|
+
expect(textContent).toMatchSnapshot();
|
|
281
|
+
// Should suggest today-focused actions
|
|
282
|
+
expect(textContent).toContain(`Use ${UPDATE_TASKS} to modify priorities or due dates`);
|
|
283
|
+
});
|
|
284
|
+
it('should provide different next steps for regular tasks', async () => {
|
|
285
|
+
const mockTasks = [
|
|
286
|
+
createMappedTask({
|
|
287
|
+
id: TEST_IDS.TASK_1,
|
|
288
|
+
content: 'Regular future task',
|
|
289
|
+
dueDate: '2025-08-25', // Future date
|
|
290
|
+
}),
|
|
291
|
+
];
|
|
292
|
+
const mockResponse = { tasks: mockTasks, nextCursor: null };
|
|
293
|
+
mockGetTasksByFilter.mockResolvedValue(mockResponse);
|
|
294
|
+
const result = await findTasks.execute({ searchText: 'future tasks', limit: 10 }, mockTodoistApi);
|
|
295
|
+
const textContent = extractTextContent(result);
|
|
296
|
+
expect(textContent).toMatchSnapshot();
|
|
297
|
+
expect(textContent).toContain(`Use ${UPDATE_TASKS} to modify priorities or due dates`);
|
|
298
|
+
});
|
|
299
|
+
it('should provide helpful suggestions for empty search results', async () => {
|
|
300
|
+
const mockResponse = { tasks: [], nextCursor: null };
|
|
301
|
+
mockGetTasksByFilter.mockResolvedValue(mockResponse);
|
|
302
|
+
const result = await findTasks.execute({ searchText: 'nonexistent', limit: 10 }, mockTodoistApi);
|
|
303
|
+
const textContent = extractTextContent(result);
|
|
304
|
+
expect(textContent).toMatchSnapshot();
|
|
305
|
+
expect(textContent).toContain('Try broader search terms');
|
|
306
|
+
expect(textContent).toContain(`Check completed tasks with ${FIND_COMPLETED_TASKS}`);
|
|
307
|
+
expect(textContent).toContain('Verify spelling and try partial words');
|
|
308
|
+
});
|
|
309
|
+
});
|
|
310
|
+
describe('error handling', () => {
|
|
311
|
+
it.each([
|
|
312
|
+
{
|
|
313
|
+
error: 'At least one filter must be provided: searchText, projectId, sectionId, or parentId',
|
|
314
|
+
params: { limit: 10 },
|
|
315
|
+
expectValidation: true,
|
|
316
|
+
},
|
|
317
|
+
{
|
|
318
|
+
error: TEST_ERRORS.API_RATE_LIMIT,
|
|
319
|
+
params: { searchText: 'any search term', limit: 10 },
|
|
320
|
+
expectValidation: false,
|
|
321
|
+
},
|
|
322
|
+
{
|
|
323
|
+
error: TEST_ERRORS.INVALID_CURSOR,
|
|
324
|
+
params: { searchText: 'test', cursor: 'invalid-cursor-format', limit: 10 },
|
|
325
|
+
expectValidation: false,
|
|
326
|
+
},
|
|
327
|
+
])('should propagate $error', async ({ error, params, expectValidation }) => {
|
|
328
|
+
if (!expectValidation) {
|
|
329
|
+
mockGetTasksByFilter.mockRejectedValue(new Error(error));
|
|
330
|
+
}
|
|
331
|
+
await expect(findTasks.execute(params, mockTodoistApi)).rejects.toThrow(error);
|
|
332
|
+
});
|
|
333
|
+
});
|
|
334
|
+
});
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"get-overview.test.d.ts","sourceRoot":"","sources":["../../../src/tools/__tests__/get-overview.test.ts"],"names":[],"mappings":""}
|