@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.
Files changed (202) hide show
  1. package/README.md +6 -14
  2. package/dist/index.d.ts +619 -250
  3. package/dist/index.d.ts.map +1 -1
  4. package/dist/index.js +45 -29
  5. package/dist/main.js +2 -1
  6. package/dist/mcp-helpers.d.ts +25 -3
  7. package/dist/mcp-helpers.d.ts.map +1 -1
  8. package/dist/mcp-helpers.js +37 -19
  9. package/dist/mcp-server.d.ts.map +1 -1
  10. package/dist/mcp-server.js +44 -28
  11. package/dist/tools/__tests__/add-comments.test.d.ts +2 -0
  12. package/dist/tools/__tests__/add-comments.test.d.ts.map +1 -0
  13. package/dist/tools/__tests__/add-comments.test.js +241 -0
  14. package/dist/tools/__tests__/add-projects.test.d.ts +2 -0
  15. package/dist/tools/__tests__/add-projects.test.d.ts.map +1 -0
  16. package/dist/tools/__tests__/add-projects.test.js +152 -0
  17. package/dist/tools/__tests__/add-sections.test.d.ts +2 -0
  18. package/dist/tools/__tests__/add-sections.test.d.ts.map +1 -0
  19. package/dist/tools/__tests__/add-sections.test.js +181 -0
  20. package/dist/tools/__tests__/add-tasks.test.d.ts +2 -0
  21. package/dist/tools/__tests__/add-tasks.test.d.ts.map +1 -0
  22. package/dist/tools/__tests__/{tasks-add-multiple.test.js → add-tasks.test.js} +89 -79
  23. package/dist/tools/__tests__/complete-tasks.test.d.ts +2 -0
  24. package/dist/tools/__tests__/complete-tasks.test.d.ts.map +1 -0
  25. package/dist/tools/__tests__/complete-tasks.test.js +206 -0
  26. package/dist/tools/__tests__/delete-object.test.d.ts +2 -0
  27. package/dist/tools/__tests__/delete-object.test.d.ts.map +1 -0
  28. package/dist/tools/__tests__/{delete-one.test.js → delete-object.test.js} +42 -22
  29. package/dist/tools/__tests__/find-comments.test.d.ts +2 -0
  30. package/dist/tools/__tests__/find-comments.test.d.ts.map +1 -0
  31. package/dist/tools/__tests__/find-comments.test.js +242 -0
  32. package/dist/tools/__tests__/find-completed-tasks.test.d.ts +2 -0
  33. package/dist/tools/__tests__/find-completed-tasks.test.d.ts.map +1 -0
  34. package/dist/tools/__tests__/{tasks-list-completed.test.js → find-completed-tasks.test.js} +13 -36
  35. package/dist/tools/__tests__/find-projects.test.d.ts +2 -0
  36. package/dist/tools/__tests__/find-projects.test.d.ts.map +1 -0
  37. package/dist/tools/__tests__/{projects-list.test.js → find-projects.test.js} +55 -39
  38. package/dist/tools/__tests__/find-sections.test.d.ts +2 -0
  39. package/dist/tools/__tests__/find-sections.test.d.ts.map +1 -0
  40. package/dist/tools/__tests__/{sections-search.test.js → find-sections.test.js} +64 -50
  41. package/dist/tools/__tests__/find-tasks-by-date.test.d.ts +2 -0
  42. package/dist/tools/__tests__/find-tasks-by-date.test.d.ts.map +1 -0
  43. package/dist/tools/__tests__/{tasks-list-by-date.test.js → find-tasks-by-date.test.js} +96 -14
  44. package/dist/tools/__tests__/find-tasks.test.d.ts +2 -0
  45. package/dist/tools/__tests__/find-tasks.test.d.ts.map +1 -0
  46. package/dist/tools/__tests__/find-tasks.test.js +334 -0
  47. package/dist/tools/__tests__/get-overview.test.d.ts +2 -0
  48. package/dist/tools/__tests__/get-overview.test.d.ts.map +1 -0
  49. package/dist/tools/__tests__/{overview.test.js → get-overview.test.js} +77 -13
  50. package/dist/tools/__tests__/update-comments.test.d.ts +2 -0
  51. package/dist/tools/__tests__/update-comments.test.d.ts.map +1 -0
  52. package/dist/tools/__tests__/update-comments.test.js +296 -0
  53. package/dist/tools/__tests__/update-projects.test.d.ts +2 -0
  54. package/dist/tools/__tests__/update-projects.test.d.ts.map +1 -0
  55. package/dist/tools/__tests__/update-projects.test.js +205 -0
  56. package/dist/tools/__tests__/update-sections.test.d.ts +2 -0
  57. package/dist/tools/__tests__/update-sections.test.d.ts.map +1 -0
  58. package/dist/tools/__tests__/update-sections.test.js +156 -0
  59. package/dist/tools/__tests__/update-tasks.test.d.ts +2 -0
  60. package/dist/tools/__tests__/update-tasks.test.d.ts.map +1 -0
  61. package/dist/tools/__tests__/update-tasks.test.js +645 -0
  62. package/dist/tools/add-comments.d.ts +51 -0
  63. package/dist/tools/add-comments.d.ts.map +1 -0
  64. package/dist/tools/add-comments.js +79 -0
  65. package/dist/tools/add-projects.d.ts +50 -0
  66. package/dist/tools/add-projects.d.ts.map +1 -0
  67. package/dist/tools/add-projects.js +59 -0
  68. package/dist/tools/add-sections.d.ts +46 -0
  69. package/dist/tools/add-sections.d.ts.map +1 -0
  70. package/dist/tools/add-sections.js +61 -0
  71. package/dist/tools/add-tasks.d.ts +82 -0
  72. package/dist/tools/add-tasks.d.ts.map +1 -0
  73. package/dist/tools/add-tasks.js +96 -0
  74. package/dist/tools/complete-tasks.d.ts +40 -0
  75. package/dist/tools/complete-tasks.d.ts.map +1 -0
  76. package/dist/tools/complete-tasks.js +68 -0
  77. package/dist/tools/delete-object.d.ts +38 -0
  78. package/dist/tools/delete-object.d.ts.map +1 -0
  79. package/dist/tools/delete-object.js +79 -0
  80. package/dist/tools/find-comments.d.ts +46 -0
  81. package/dist/tools/find-comments.d.ts.map +1 -0
  82. package/dist/tools/find-comments.js +143 -0
  83. package/dist/tools/find-completed-tasks.d.ts +74 -0
  84. package/dist/tools/find-completed-tasks.d.ts.map +1 -0
  85. package/dist/tools/find-completed-tasks.js +112 -0
  86. package/dist/tools/find-projects.d.ts +53 -0
  87. package/dist/tools/find-projects.d.ts.map +1 -0
  88. package/dist/tools/find-projects.js +101 -0
  89. package/dist/tools/find-sections.d.ts +42 -0
  90. package/dist/tools/find-sections.d.ts.map +1 -0
  91. package/dist/tools/find-sections.js +96 -0
  92. package/dist/tools/find-tasks-by-date.d.ts +59 -0
  93. package/dist/tools/find-tasks-by-date.d.ts.map +1 -0
  94. package/dist/tools/find-tasks-by-date.js +121 -0
  95. package/dist/tools/find-tasks.d.ts +65 -0
  96. package/dist/tools/find-tasks.d.ts.map +1 -0
  97. package/dist/tools/find-tasks.js +182 -0
  98. package/dist/tools/get-overview.d.ts +67 -0
  99. package/dist/tools/get-overview.d.ts.map +1 -0
  100. package/dist/tools/{overview.js → get-overview.js} +66 -19
  101. package/dist/tools/update-comments.d.ts +50 -0
  102. package/dist/tools/update-comments.d.ts.map +1 -0
  103. package/dist/tools/update-comments.js +82 -0
  104. package/dist/tools/update-projects.d.ts +59 -0
  105. package/dist/tools/update-projects.d.ts.map +1 -0
  106. package/dist/tools/update-projects.js +84 -0
  107. package/dist/tools/update-sections.d.ts +47 -0
  108. package/dist/tools/update-sections.d.ts.map +1 -0
  109. package/dist/tools/update-sections.js +70 -0
  110. package/dist/tools/update-tasks.d.ts +94 -0
  111. package/dist/tools/update-tasks.d.ts.map +1 -0
  112. package/dist/tools/update-tasks.js +120 -0
  113. package/dist/utils/constants.d.ts +39 -0
  114. package/dist/utils/constants.d.ts.map +1 -0
  115. package/dist/utils/constants.js +41 -0
  116. package/dist/utils/response-builders.d.ts +88 -0
  117. package/dist/utils/response-builders.d.ts.map +1 -0
  118. package/dist/utils/response-builders.js +202 -0
  119. package/dist/{tools → utils}/test-helpers.d.ts +16 -0
  120. package/dist/utils/test-helpers.d.ts.map +1 -0
  121. package/dist/{tools → utils}/test-helpers.js +51 -0
  122. package/dist/utils/tool-names.d.ts +28 -0
  123. package/dist/utils/tool-names.d.ts.map +1 -0
  124. package/dist/utils/tool-names.js +31 -0
  125. package/package.json +1 -1
  126. package/dist/tools/__tests__/delete-one.test.d.ts +0 -2
  127. package/dist/tools/__tests__/delete-one.test.d.ts.map +0 -1
  128. package/dist/tools/__tests__/overview.test.d.ts +0 -2
  129. package/dist/tools/__tests__/overview.test.d.ts.map +0 -1
  130. package/dist/tools/__tests__/projects-list.test.d.ts +0 -2
  131. package/dist/tools/__tests__/projects-list.test.d.ts.map +0 -1
  132. package/dist/tools/__tests__/projects-manage.test.d.ts +0 -2
  133. package/dist/tools/__tests__/projects-manage.test.d.ts.map +0 -1
  134. package/dist/tools/__tests__/projects-manage.test.js +0 -106
  135. package/dist/tools/__tests__/sections-manage.test.d.ts +0 -2
  136. package/dist/tools/__tests__/sections-manage.test.d.ts.map +0 -1
  137. package/dist/tools/__tests__/sections-manage.test.js +0 -138
  138. package/dist/tools/__tests__/sections-search.test.d.ts +0 -2
  139. package/dist/tools/__tests__/sections-search.test.d.ts.map +0 -1
  140. package/dist/tools/__tests__/tasks-add-multiple.test.d.ts +0 -2
  141. package/dist/tools/__tests__/tasks-add-multiple.test.d.ts.map +0 -1
  142. package/dist/tools/__tests__/tasks-complete-multiple.test.d.ts +0 -2
  143. package/dist/tools/__tests__/tasks-complete-multiple.test.d.ts.map +0 -1
  144. package/dist/tools/__tests__/tasks-complete-multiple.test.js +0 -146
  145. package/dist/tools/__tests__/tasks-list-by-date.test.d.ts +0 -2
  146. package/dist/tools/__tests__/tasks-list-by-date.test.d.ts.map +0 -1
  147. package/dist/tools/__tests__/tasks-list-completed.test.d.ts +0 -2
  148. package/dist/tools/__tests__/tasks-list-completed.test.d.ts.map +0 -1
  149. package/dist/tools/__tests__/tasks-list-for-container.test.d.ts +0 -2
  150. package/dist/tools/__tests__/tasks-list-for-container.test.d.ts.map +0 -1
  151. package/dist/tools/__tests__/tasks-list-for-container.test.js +0 -232
  152. package/dist/tools/__tests__/tasks-organize-multiple.test.d.ts +0 -2
  153. package/dist/tools/__tests__/tasks-organize-multiple.test.d.ts.map +0 -1
  154. package/dist/tools/__tests__/tasks-organize-multiple.test.js +0 -245
  155. package/dist/tools/__tests__/tasks-search.test.d.ts +0 -2
  156. package/dist/tools/__tests__/tasks-search.test.d.ts.map +0 -1
  157. package/dist/tools/__tests__/tasks-search.test.js +0 -106
  158. package/dist/tools/__tests__/tasks-update-one.test.d.ts +0 -2
  159. package/dist/tools/__tests__/tasks-update-one.test.d.ts.map +0 -1
  160. package/dist/tools/__tests__/tasks-update-one.test.js +0 -251
  161. package/dist/tools/delete-one.d.ts +0 -17
  162. package/dist/tools/delete-one.d.ts.map +0 -1
  163. package/dist/tools/delete-one.js +0 -25
  164. package/dist/tools/overview.d.ts +0 -14
  165. package/dist/tools/overview.d.ts.map +0 -1
  166. package/dist/tools/projects-list.d.ts +0 -29
  167. package/dist/tools/projects-list.d.ts.map +0 -1
  168. package/dist/tools/projects-list.js +0 -39
  169. package/dist/tools/projects-manage.d.ts +0 -24
  170. package/dist/tools/projects-manage.d.ts.map +0 -1
  171. package/dist/tools/projects-manage.js +0 -26
  172. package/dist/tools/sections-manage.d.ts +0 -23
  173. package/dist/tools/sections-manage.d.ts.map +0 -1
  174. package/dist/tools/sections-manage.js +0 -37
  175. package/dist/tools/sections-search.d.ts +0 -18
  176. package/dist/tools/sections-search.d.ts.map +0 -1
  177. package/dist/tools/sections-search.js +0 -27
  178. package/dist/tools/tasks-add-multiple.d.ts +0 -55
  179. package/dist/tools/tasks-add-multiple.d.ts.map +0 -1
  180. package/dist/tools/tasks-add-multiple.js +0 -52
  181. package/dist/tools/tasks-complete-multiple.d.ts +0 -16
  182. package/dist/tools/tasks-complete-multiple.d.ts.map +0 -1
  183. package/dist/tools/tasks-complete-multiple.js +0 -23
  184. package/dist/tools/tasks-list-by-date.d.ts +0 -34
  185. package/dist/tools/tasks-list-by-date.d.ts.map +0 -1
  186. package/dist/tools/tasks-list-by-date.js +0 -53
  187. package/dist/tools/tasks-list-completed.d.ts +0 -44
  188. package/dist/tools/tasks-list-completed.d.ts.map +0 -1
  189. package/dist/tools/tasks-list-completed.js +0 -49
  190. package/dist/tools/tasks-list-for-container.d.ts +0 -34
  191. package/dist/tools/tasks-list-for-container.d.ts.map +0 -1
  192. package/dist/tools/tasks-list-for-container.js +0 -48
  193. package/dist/tools/tasks-organize-multiple.d.ts +0 -37
  194. package/dist/tools/tasks-organize-multiple.d.ts.map +0 -1
  195. package/dist/tools/tasks-organize-multiple.js +0 -34
  196. package/dist/tools/tasks-search.d.ts +0 -32
  197. package/dist/tools/tasks-search.d.ts.map +0 -1
  198. package/dist/tools/tasks-search.js +0 -30
  199. package/dist/tools/tasks-update-one.d.ts +0 -29
  200. package/dist/tools/tasks-update-one.d.ts.map +0 -1
  201. package/dist/tools/tasks-update-one.js +0 -63
  202. 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 { tasksListByDate } from '../tasks-list-by-date.js';
4
- import { TEST_ERRORS, TEST_IDS, createMappedTask } from '../test-helpers.js';
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-list-by-date, but needed for type)
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
- describe('tasks-list-by-date tool', () => {
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 tasksListByDate.execute({ startDate: 'overdue', limit: 50, daysCount }, mockTodoistApi);
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
- expect(result).toEqual(mockResponse);
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 tasksListByDate.execute({ startDate: 'today', limit: 50, daysCount: 7 }, mockTodoistApi);
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
- expect(result).toEqual(mockResponse);
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 tasksListByDate.execute(params, mockTodoistApi);
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
- expect(result).toEqual(mockResponse);
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 tasksListByDate.execute(params, mockTodoistApi);
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 tasksListByDate.execute({ startDate, limit: 50, daysCount }, mockTodoistApi);
177
+ const result = await findTasksByDate.execute({ startDate, limit: 50, daysCount }, mockTodoistApi);
162
178
  expect(mockGetTasksByFilter).toHaveBeenCalledTimes(1);
163
179
  if (shouldReturnResult) {
164
- expect(result).toEqual(mockResponse);
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(tasksListByDate.execute(params, mockTodoistApi)).rejects.toThrow(error);
271
+ await expect(findTasksByDate.execute(params, mockTodoistApi)).rejects.toThrow(error);
190
272
  });
191
273
  });
192
274
  });
@@ -0,0 +1,2 @@
1
+ export {};
2
+ //# sourceMappingURL=find-tasks.test.d.ts.map
@@ -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,2 @@
1
+ export {};
2
+ //# sourceMappingURL=get-overview.test.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"get-overview.test.d.ts","sourceRoot":"","sources":["../../../src/tools/__tests__/get-overview.test.ts"],"names":[],"mappings":""}