@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
@@ -0,0 +1,152 @@
1
+ import { jest } from '@jest/globals';
2
+ import { TEST_IDS, createMockProject, extractStructuredContent, extractTextContent, } from '../../utils/test-helpers.js';
3
+ import { ToolNames } from '../../utils/tool-names.js';
4
+ import { addProjects } from '../add-projects.js';
5
+ // Mock the Todoist API
6
+ const mockTodoistApi = {
7
+ addProject: jest.fn(),
8
+ };
9
+ const { ADD_TASKS, ADD_PROJECTS, ADD_SECTIONS } = ToolNames;
10
+ describe(`${ADD_PROJECTS} tool`, () => {
11
+ beforeEach(() => {
12
+ jest.clearAllMocks();
13
+ });
14
+ describe('creating a single project', () => {
15
+ it('should create a project and return mapped result', async () => {
16
+ const mockApiResponse = createMockProject({
17
+ id: TEST_IDS.PROJECT_TEST,
18
+ name: 'test-abc123def456-project',
19
+ childOrder: 1,
20
+ createdAt: '2024-01-01T00:00:00Z',
21
+ });
22
+ mockTodoistApi.addProject.mockResolvedValue(mockApiResponse);
23
+ const result = await addProjects.execute({ projects: [{ name: 'test-abc123def456-project' }] }, mockTodoistApi);
24
+ // Verify API was called correctly
25
+ expect(mockTodoistApi.addProject).toHaveBeenCalledWith({
26
+ name: 'test-abc123def456-project',
27
+ });
28
+ const textContent = extractTextContent(result);
29
+ expect(textContent).toMatchSnapshot();
30
+ expect(textContent).toContain('Added 1 project:');
31
+ expect(textContent).toContain('test-abc123def456-project');
32
+ expect(textContent).toContain(`id=${TEST_IDS.PROJECT_TEST}`);
33
+ expect(textContent).toContain(`Use ${ADD_TASKS} to add your first tasks`);
34
+ // Verify structured content
35
+ const structuredContent = extractStructuredContent(result);
36
+ expect(structuredContent).toEqual(expect.objectContaining({
37
+ projects: [
38
+ expect.objectContaining({
39
+ id: TEST_IDS.PROJECT_TEST,
40
+ name: 'test-abc123def456-project',
41
+ }),
42
+ ],
43
+ totalCount: 1,
44
+ }));
45
+ });
46
+ it('should handle different project properties from API', async () => {
47
+ const mockApiResponse = createMockProject({
48
+ id: 'project-456',
49
+ name: 'My Blue Project',
50
+ color: 'blue',
51
+ isFavorite: true,
52
+ isShared: true,
53
+ parentId: 'parent-123',
54
+ viewStyle: 'board',
55
+ childOrder: 2,
56
+ description: 'A test project',
57
+ createdAt: '2024-01-01T00:00:00Z',
58
+ });
59
+ mockTodoistApi.addProject.mockResolvedValue(mockApiResponse);
60
+ const result = await addProjects.execute({ projects: [{ name: 'My Blue Project' }] }, mockTodoistApi);
61
+ expect(mockTodoistApi.addProject).toHaveBeenCalledWith({
62
+ name: 'My Blue Project',
63
+ isFavorite: undefined,
64
+ viewStyle: undefined,
65
+ });
66
+ const textContent = extractTextContent(result);
67
+ expect(textContent).toMatchSnapshot();
68
+ expect(textContent).toContain('Added 1 project:');
69
+ expect(textContent).toContain('My Blue Project');
70
+ expect(textContent).toContain('id=project-456');
71
+ expect(textContent).toContain(`Use ${ADD_SECTIONS} to organize new project`);
72
+ });
73
+ it('should create project with isFavorite and viewStyle options', async () => {
74
+ const mockApiResponse = createMockProject({
75
+ id: 'project-789',
76
+ name: 'Board Project',
77
+ isFavorite: true,
78
+ viewStyle: 'board',
79
+ });
80
+ mockTodoistApi.addProject.mockResolvedValue(mockApiResponse);
81
+ const result = await addProjects.execute({ projects: [{ name: 'Board Project', isFavorite: true, viewStyle: 'board' }] }, mockTodoistApi);
82
+ expect(mockTodoistApi.addProject).toHaveBeenCalledWith({
83
+ name: 'Board Project',
84
+ isFavorite: true,
85
+ viewStyle: 'board',
86
+ });
87
+ const textContent = extractTextContent(result);
88
+ expect(textContent).toMatchSnapshot();
89
+ expect(textContent).toContain('Added 1 project:');
90
+ expect(textContent).toContain('Board Project');
91
+ expect(textContent).toContain('id=project-789');
92
+ });
93
+ });
94
+ describe('creating multiple projects', () => {
95
+ it('should create multiple projects and return mapped results', async () => {
96
+ const mockProjects = [
97
+ createMockProject({ id: 'project-1', name: 'First Project' }),
98
+ createMockProject({ id: 'project-2', name: 'Second Project' }),
99
+ createMockProject({ id: 'project-3', name: 'Third Project' }),
100
+ ];
101
+ const [project1, project2, project3] = mockProjects;
102
+ mockTodoistApi.addProject
103
+ .mockResolvedValueOnce(project1)
104
+ .mockResolvedValueOnce(project2)
105
+ .mockResolvedValueOnce(project3);
106
+ const result = await addProjects.execute({
107
+ projects: [
108
+ { name: 'First Project' },
109
+ { name: 'Second Project' },
110
+ { name: 'Third Project' },
111
+ ],
112
+ }, mockTodoistApi);
113
+ // Verify API was called correctly for each project
114
+ expect(mockTodoistApi.addProject).toHaveBeenCalledTimes(3);
115
+ expect(mockTodoistApi.addProject).toHaveBeenNthCalledWith(1, { name: 'First Project' });
116
+ expect(mockTodoistApi.addProject).toHaveBeenNthCalledWith(2, { name: 'Second Project' });
117
+ expect(mockTodoistApi.addProject).toHaveBeenNthCalledWith(3, { name: 'Third Project' });
118
+ const textContent = extractTextContent(result);
119
+ expect(textContent).toMatchSnapshot();
120
+ expect(textContent).toContain('Added 3 projects:');
121
+ expect(textContent).toContain('First Project (id=project-1)');
122
+ expect(textContent).toContain('Second Project (id=project-2)');
123
+ expect(textContent).toContain('Third Project (id=project-3)');
124
+ expect(textContent).toContain(`Use ${ADD_SECTIONS} to organize these projects`);
125
+ // Verify structured content
126
+ const structuredContent = extractStructuredContent(result);
127
+ expect(structuredContent).toEqual(expect.objectContaining({
128
+ projects: mockProjects,
129
+ totalCount: 3,
130
+ }));
131
+ });
132
+ });
133
+ describe('error handling', () => {
134
+ it('should propagate API errors', async () => {
135
+ const apiError = new Error('API Error: Project name is required');
136
+ mockTodoistApi.addProject.mockRejectedValue(apiError);
137
+ await expect(addProjects.execute({ projects: [{ name: '' }] }, mockTodoistApi)).rejects.toThrow('API Error: Project name is required');
138
+ });
139
+ it('should handle partial failures in multiple projects', async () => {
140
+ const mockProject = createMockProject({
141
+ id: 'project-1',
142
+ name: 'First Project',
143
+ });
144
+ mockTodoistApi.addProject
145
+ .mockResolvedValueOnce(mockProject)
146
+ .mockRejectedValueOnce(new Error('API Error: Invalid project name'));
147
+ await expect(addProjects.execute({
148
+ projects: [{ name: 'First Project' }, { name: 'Invalid' }],
149
+ }, mockTodoistApi)).rejects.toThrow('API Error: Invalid project name');
150
+ });
151
+ });
152
+ });
@@ -0,0 +1,2 @@
1
+ export {};
2
+ //# sourceMappingURL=add-sections.test.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"add-sections.test.d.ts","sourceRoot":"","sources":["../../../src/tools/__tests__/add-sections.test.ts"],"names":[],"mappings":""}
@@ -0,0 +1,181 @@
1
+ import { jest } from '@jest/globals';
2
+ import { TEST_IDS, createMockSection, extractStructuredContent, extractTextContent, } from '../../utils/test-helpers.js';
3
+ import { ToolNames } from '../../utils/tool-names.js';
4
+ import { addSections } from '../add-sections.js';
5
+ // Mock the Todoist API
6
+ const mockTodoistApi = {
7
+ addSection: jest.fn(),
8
+ };
9
+ const { ADD_TASKS, ADD_SECTIONS, FIND_TASKS } = ToolNames;
10
+ describe(`${ADD_SECTIONS} tool`, () => {
11
+ beforeEach(() => {
12
+ jest.clearAllMocks();
13
+ });
14
+ describe('creating a single section', () => {
15
+ it('should create a section and return mapped result', async () => {
16
+ const mockApiResponse = createMockSection({
17
+ id: TEST_IDS.SECTION_1,
18
+ projectId: TEST_IDS.PROJECT_TEST,
19
+ name: 'test-abc123def456-section',
20
+ });
21
+ mockTodoistApi.addSection.mockResolvedValue(mockApiResponse);
22
+ const result = await addSections.execute({
23
+ sections: [
24
+ { name: 'test-abc123def456-section', projectId: TEST_IDS.PROJECT_TEST },
25
+ ],
26
+ }, mockTodoistApi);
27
+ // Verify API was called correctly
28
+ expect(mockTodoistApi.addSection).toHaveBeenCalledWith({
29
+ name: 'test-abc123def456-section',
30
+ projectId: TEST_IDS.PROJECT_TEST,
31
+ });
32
+ const textContent = extractTextContent(result);
33
+ expect(textContent).toMatchSnapshot();
34
+ expect(textContent).toContain('Added 1 section:');
35
+ expect(textContent).toContain('test-abc123def456-section');
36
+ expect(textContent).toContain(`id=${TEST_IDS.SECTION_1}`);
37
+ expect(textContent).toContain(`Use ${ADD_TASKS} with sectionId`);
38
+ // Verify structured content
39
+ const structuredContent = extractStructuredContent(result);
40
+ expect(structuredContent).toEqual(expect.objectContaining({
41
+ sections: [
42
+ expect.objectContaining({
43
+ id: TEST_IDS.SECTION_1,
44
+ name: 'test-abc123def456-section',
45
+ }),
46
+ ],
47
+ totalCount: 1,
48
+ }));
49
+ });
50
+ it('should handle different section properties from API', async () => {
51
+ const mockApiResponse = createMockSection({
52
+ id: TEST_IDS.SECTION_2,
53
+ projectId: 'project-789',
54
+ sectionOrder: 2,
55
+ name: 'My Section Name',
56
+ });
57
+ mockTodoistApi.addSection.mockResolvedValue(mockApiResponse);
58
+ const result = await addSections.execute({ sections: [{ name: 'My Section Name', projectId: 'project-789' }] }, mockTodoistApi);
59
+ expect(mockTodoistApi.addSection).toHaveBeenCalledWith({
60
+ name: 'My Section Name',
61
+ projectId: 'project-789',
62
+ });
63
+ const textContent = extractTextContent(result);
64
+ expect(textContent).toMatchSnapshot();
65
+ expect(textContent).toContain('Added 1 section:');
66
+ expect(textContent).toContain('My Section Name');
67
+ expect(textContent).toContain(`id=${TEST_IDS.SECTION_2}`);
68
+ expect(textContent).toContain(`Use ${FIND_TASKS} with sectionId`);
69
+ });
70
+ });
71
+ describe('creating multiple sections', () => {
72
+ it('should create multiple sections and return mapped results', async () => {
73
+ const mockSections = [
74
+ createMockSection({
75
+ id: 'section-1',
76
+ projectId: TEST_IDS.PROJECT_TEST,
77
+ name: 'First Section',
78
+ }),
79
+ createMockSection({
80
+ id: 'section-2',
81
+ projectId: TEST_IDS.PROJECT_TEST,
82
+ name: 'Second Section',
83
+ }),
84
+ createMockSection({
85
+ id: 'section-3',
86
+ projectId: 'different-project',
87
+ name: 'Third Section',
88
+ }),
89
+ ];
90
+ const [section1, section2, section3] = mockSections;
91
+ mockTodoistApi.addSection
92
+ .mockResolvedValueOnce(section1)
93
+ .mockResolvedValueOnce(section2)
94
+ .mockResolvedValueOnce(section3);
95
+ const result = await addSections.execute({
96
+ sections: [
97
+ { name: 'First Section', projectId: TEST_IDS.PROJECT_TEST },
98
+ { name: 'Second Section', projectId: TEST_IDS.PROJECT_TEST },
99
+ { name: 'Third Section', projectId: 'different-project' },
100
+ ],
101
+ }, mockTodoistApi);
102
+ // Verify API was called correctly for each section
103
+ expect(mockTodoistApi.addSection).toHaveBeenCalledTimes(3);
104
+ expect(mockTodoistApi.addSection).toHaveBeenNthCalledWith(1, {
105
+ name: 'First Section',
106
+ projectId: TEST_IDS.PROJECT_TEST,
107
+ });
108
+ expect(mockTodoistApi.addSection).toHaveBeenNthCalledWith(2, {
109
+ name: 'Second Section',
110
+ projectId: TEST_IDS.PROJECT_TEST,
111
+ });
112
+ expect(mockTodoistApi.addSection).toHaveBeenNthCalledWith(3, {
113
+ name: 'Third Section',
114
+ projectId: 'different-project',
115
+ });
116
+ const textContent = extractTextContent(result);
117
+ expect(textContent).toMatchSnapshot();
118
+ expect(textContent).toContain('Added 3 sections:');
119
+ expect(textContent).toContain('First Section (id=section-1, projectId=');
120
+ expect(textContent).toContain('Second Section (id=section-2, projectId=');
121
+ expect(textContent).toContain('Third Section (id=section-3, projectId=different-project)');
122
+ // Verify structured content
123
+ const structuredContent = extractStructuredContent(result);
124
+ expect(structuredContent).toEqual(expect.objectContaining({
125
+ sections: mockSections,
126
+ totalCount: 3,
127
+ }));
128
+ });
129
+ it('should handle sections for the same project', async () => {
130
+ const mockSections = [
131
+ createMockSection({
132
+ id: 'section-1',
133
+ projectId: TEST_IDS.PROJECT_TEST,
134
+ name: 'To Do',
135
+ }),
136
+ createMockSection({
137
+ id: 'section-2',
138
+ projectId: TEST_IDS.PROJECT_TEST,
139
+ name: 'In Progress',
140
+ }),
141
+ ];
142
+ const [section1, section2] = mockSections;
143
+ mockTodoistApi.addSection
144
+ .mockResolvedValueOnce(section1)
145
+ .mockResolvedValueOnce(section2);
146
+ const result = await addSections.execute({
147
+ sections: [
148
+ { name: 'To Do', projectId: TEST_IDS.PROJECT_TEST },
149
+ { name: 'In Progress', projectId: TEST_IDS.PROJECT_TEST },
150
+ ],
151
+ }, mockTodoistApi);
152
+ const textContent = extractTextContent(result);
153
+ expect(textContent).toMatchSnapshot();
154
+ expect(textContent).toContain('Added 2 sections:');
155
+ expect(textContent).toContain(`Use ${ADD_TASKS} to add tasks to these new sections`);
156
+ });
157
+ });
158
+ describe('error handling', () => {
159
+ it('should propagate API errors', async () => {
160
+ const apiError = new Error('API Error: Section name is required');
161
+ mockTodoistApi.addSection.mockRejectedValue(apiError);
162
+ await expect(addSections.execute({ sections: [{ name: '', projectId: TEST_IDS.PROJECT_TEST }] }, mockTodoistApi)).rejects.toThrow('API Error: Section name is required');
163
+ });
164
+ it('should handle partial failures in multiple sections', async () => {
165
+ const mockSection = createMockSection({
166
+ id: 'section-1',
167
+ projectId: TEST_IDS.PROJECT_TEST,
168
+ name: 'First Section',
169
+ });
170
+ mockTodoistApi.addSection
171
+ .mockResolvedValueOnce(mockSection)
172
+ .mockRejectedValueOnce(new Error('API Error: Invalid project ID'));
173
+ await expect(addSections.execute({
174
+ sections: [
175
+ { name: 'First Section', projectId: TEST_IDS.PROJECT_TEST },
176
+ { name: 'Second Section', projectId: 'invalid-project' },
177
+ ],
178
+ }, mockTodoistApi)).rejects.toThrow('API Error: Invalid project ID');
179
+ });
180
+ });
181
+ });
@@ -0,0 +1,2 @@
1
+ export {};
2
+ //# sourceMappingURL=add-tasks.test.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"add-tasks.test.d.ts","sourceRoot":"","sources":["../../../src/tools/__tests__/add-tasks.test.ts"],"names":[],"mappings":""}
@@ -1,11 +1,13 @@
1
1
  import { jest } from '@jest/globals';
2
- import { tasksAddMultiple } from '../tasks-add-multiple.js';
3
- import { createMockTask } from '../test-helpers.js';
2
+ import { TODAY, createMockTask, extractStructuredContent, extractTextContent, } from '../../utils/test-helpers.js';
3
+ import { ToolNames } from '../../utils/tool-names.js';
4
+ import { addTasks } from '../add-tasks.js';
4
5
  // Mock the Todoist API
5
6
  const mockTodoistApi = {
6
7
  addTask: jest.fn(),
7
8
  };
8
- describe('tasks-add-multiple tool', () => {
9
+ const { ADD_TASKS, GET_OVERVIEW } = ToolNames;
10
+ describe(`${ADD_TASKS} tool`, () => {
9
11
  beforeEach(() => {
10
12
  jest.clearAllMocks();
11
13
  });
@@ -38,15 +40,15 @@ describe('tasks-add-multiple tool', () => {
38
40
  mockTodoistApi.addTask
39
41
  .mockResolvedValueOnce(mockApiResponse1)
40
42
  .mockResolvedValueOnce(mockApiResponse2);
41
- const result = await tasksAddMultiple.execute({
42
- projectId: '6cfCcrrCFg2xP94Q',
43
+ const result = await addTasks.execute({
43
44
  tasks: [
44
- { content: 'First task content' },
45
+ { content: 'First task content', projectId: '6cfCcrrCFg2xP94Q' },
45
46
  {
46
47
  content: 'Second task content',
47
48
  description: 'Task description',
48
49
  priority: 2,
49
50
  dueString: 'Aug 15',
51
+ projectId: '6cfCcrrCFg2xP94Q',
50
52
  },
51
53
  ],
52
54
  }, mockTodoistApi);
@@ -67,23 +69,18 @@ describe('tasks-add-multiple tool', () => {
67
69
  sectionId: undefined,
68
70
  parentId: undefined,
69
71
  });
70
- // Verify result is properly mapped
71
- expect(result).toEqual([
72
- expect.objectContaining({
73
- id: '8485093748',
74
- content: 'First task content',
75
- description: '',
76
- labels: [],
77
- }),
78
- expect.objectContaining({
79
- id: '8485093749',
80
- content: 'Second task content',
81
- description: 'Task description',
82
- dueDate: '2025-08-15',
83
- priority: 2,
84
- labels: ['work', 'urgent'],
85
- }),
86
- ]);
72
+ // Verify result is a concise summary
73
+ expect(extractTextContent(result)).toMatchSnapshot();
74
+ // Verify structured content
75
+ const structuredContent = extractStructuredContent(result);
76
+ expect(structuredContent.tasks).toHaveLength(2);
77
+ expect(structuredContent).toEqual(expect.objectContaining({
78
+ totalCount: 2,
79
+ tasks: expect.arrayContaining([
80
+ expect.objectContaining({ id: '8485093748' }),
81
+ expect.objectContaining({ id: '8485093749' }),
82
+ ]),
83
+ }));
87
84
  });
88
85
  it('should handle tasks with section and parent IDs', async () => {
89
86
  const mockApiResponse = createMockTask({
@@ -97,15 +94,15 @@ describe('tasks-add-multiple tool', () => {
97
94
  addedAt: '2025-08-13T22:09:58.123456Z',
98
95
  });
99
96
  mockTodoistApi.addTask.mockResolvedValue(mockApiResponse);
100
- const result = await tasksAddMultiple.execute({
101
- projectId: '6cfCcrrCFg2xP94Q',
102
- sectionId: 'section-123',
103
- parentId: 'parent-task-456',
97
+ const result = await addTasks.execute({
104
98
  tasks: [
105
99
  {
106
100
  content: 'Subtask content',
107
101
  description: 'Subtask description',
108
102
  priority: 3,
103
+ projectId: '6cfCcrrCFg2xP94Q',
104
+ sectionId: 'section-123',
105
+ parentId: 'parent-task-456',
109
106
  },
110
107
  ],
111
108
  }, mockTodoistApi);
@@ -117,17 +114,14 @@ describe('tasks-add-multiple tool', () => {
117
114
  sectionId: 'section-123',
118
115
  parentId: 'parent-task-456',
119
116
  });
120
- expect(result).toEqual([
121
- expect.objectContaining({
122
- id: '8485093750',
123
- content: 'Subtask content',
124
- description: 'Subtask description',
125
- priority: 3,
126
- sectionId: 'section-123',
127
- parentId: 'parent-task-456',
128
- labels: [],
129
- }),
130
- ]);
117
+ // Verify result is a concise summary
118
+ expect(extractTextContent(result)).toMatchSnapshot();
119
+ // Verify structured content
120
+ const structuredContent = extractStructuredContent(result);
121
+ expect(structuredContent).toEqual(expect.objectContaining({
122
+ totalCount: 1,
123
+ tasks: expect.arrayContaining([expect.objectContaining({ id: '8485093750' })]),
124
+ }));
131
125
  });
132
126
  it('should add tasks with duration', async () => {
133
127
  const mockApiResponse1 = createMockTask({
@@ -147,16 +141,17 @@ describe('tasks-add-multiple tool', () => {
147
141
  mockTodoistApi.addTask
148
142
  .mockResolvedValueOnce(mockApiResponse1)
149
143
  .mockResolvedValueOnce(mockApiResponse2);
150
- const result = await tasksAddMultiple.execute({
151
- projectId: '6cfCcrrCFg2xP94Q',
144
+ const result = await addTasks.execute({
152
145
  tasks: [
153
146
  {
154
147
  content: 'Task with 2 hour duration',
155
148
  duration: '2h',
149
+ projectId: '6cfCcrrCFg2xP94Q',
156
150
  },
157
151
  {
158
152
  content: 'Task with 45 minute duration',
159
153
  duration: '45m',
154
+ projectId: '6cfCcrrCFg2xP94Q',
160
155
  },
161
156
  ],
162
157
  }, mockTodoistApi);
@@ -177,19 +172,18 @@ describe('tasks-add-multiple tool', () => {
177
172
  duration: 45,
178
173
  durationUnit: 'minute',
179
174
  });
180
- // Verify result includes formatted duration
181
- expect(result).toEqual([
182
- expect.objectContaining({
183
- id: '8485093752',
184
- content: 'Task with 2 hour duration',
185
- duration: '2h',
186
- }),
187
- expect.objectContaining({
188
- id: '8485093753',
189
- content: 'Task with 45 minute duration',
190
- duration: '45m',
191
- }),
192
- ]);
175
+ // Verify result is a concise summary
176
+ expect(extractTextContent(result)).toMatchSnapshot();
177
+ // Verify structured content
178
+ const structuredContent = extractStructuredContent(result);
179
+ expect(structuredContent.tasks).toHaveLength(2);
180
+ expect(structuredContent).toEqual(expect.objectContaining({
181
+ totalCount: 2,
182
+ tasks: expect.arrayContaining([
183
+ expect.objectContaining({ id: '8485093752' }),
184
+ expect.objectContaining({ id: '8485093753' }),
185
+ ]),
186
+ }));
193
187
  });
194
188
  it('should handle various duration formats', async () => {
195
189
  const mockApiResponse = createMockTask({
@@ -209,14 +203,7 @@ describe('tasks-add-multiple tool', () => {
209
203
  ];
210
204
  for (const testCase of testCases) {
211
205
  mockTodoistApi.addTask.mockClear();
212
- await tasksAddMultiple.execute({
213
- tasks: [
214
- {
215
- content: 'Test task',
216
- duration: testCase.input,
217
- },
218
- ],
219
- }, mockTodoistApi);
206
+ await addTasks.execute({ tasks: [{ content: 'Test task', duration: testCase.input }] }, mockTodoistApi);
220
207
  expect(mockTodoistApi.addTask).toHaveBeenCalledWith(expect.objectContaining({
221
208
  duration: testCase.expectedMinutes,
222
209
  durationUnit: 'minute',
@@ -226,29 +213,15 @@ describe('tasks-add-multiple tool', () => {
226
213
  });
227
214
  describe('error handling', () => {
228
215
  it('should throw error for invalid duration format', async () => {
229
- await expect(tasksAddMultiple.execute({
230
- tasks: [
231
- {
232
- content: 'Task with invalid duration',
233
- duration: 'invalid',
234
- },
235
- ],
236
- }, mockTodoistApi)).rejects.toThrow('Task "Task with invalid duration": Invalid duration format "invalid"');
216
+ await expect(addTasks.execute({ tasks: [{ content: 'Task with invalid duration', duration: 'invalid' }] }, mockTodoistApi)).rejects.toThrow('Task "Task with invalid duration": Invalid duration format "invalid"');
237
217
  });
238
218
  it('should throw error for duration exceeding 24 hours', async () => {
239
- await expect(tasksAddMultiple.execute({
240
- tasks: [
241
- {
242
- content: 'Task with too long duration',
243
- duration: '25h',
244
- },
245
- ],
246
- }, mockTodoistApi)).rejects.toThrow('Task "Task with too long duration": Invalid duration format "25h": Duration cannot exceed 24 hours (1440 minutes)');
219
+ await expect(addTasks.execute({ tasks: [{ content: 'Task with too long duration', duration: '25h' }] }, mockTodoistApi)).rejects.toThrow('Task "Task with too long duration": Invalid duration format "25h": Duration cannot exceed 24 hours (1440 minutes)');
247
220
  });
248
221
  it('should propagate API errors', async () => {
249
222
  const apiError = new Error('API Error: Task content is required');
250
223
  mockTodoistApi.addTask.mockRejectedValue(apiError);
251
- await expect(tasksAddMultiple.execute({ tasks: [{ content: '' }] }, mockTodoistApi)).rejects.toThrow(apiError.message);
224
+ await expect(addTasks.execute({ tasks: [{ content: '' }] }, mockTodoistApi)).rejects.toThrow(apiError.message);
252
225
  });
253
226
  it('should handle partial failures when adding multiple tasks', async () => {
254
227
  const mockApiResponse = createMockTask({
@@ -261,7 +234,7 @@ describe('tasks-add-multiple tool', () => {
261
234
  mockTodoistApi.addTask
262
235
  .mockResolvedValueOnce(mockApiResponse)
263
236
  .mockRejectedValueOnce(apiError);
264
- await expect(tasksAddMultiple.execute({
237
+ await expect(addTasks.execute({
265
238
  tasks: [
266
239
  { content: 'First task content' },
267
240
  { content: 'Second task content' },
@@ -271,4 +244,41 @@ describe('tasks-add-multiple tool', () => {
271
244
  expect(mockTodoistApi.addTask).toHaveBeenCalledTimes(2);
272
245
  });
273
246
  });
247
+ describe('next steps logic', () => {
248
+ it('should suggest find-tasks-by-date for today when hasToday is true', async () => {
249
+ const mockApiResponse = createMockTask({
250
+ id: '8485093755',
251
+ content: 'Task due today',
252
+ url: 'https://todoist.com/showTask?id=8485093755',
253
+ addedAt: '2025-08-13T22:09:56.123456Z',
254
+ due: {
255
+ date: TODAY,
256
+ isRecurring: false,
257
+ lang: 'en',
258
+ string: 'today',
259
+ timezone: null,
260
+ },
261
+ });
262
+ mockTodoistApi.addTask.mockResolvedValue(mockApiResponse);
263
+ const result = await addTasks.execute({ tasks: [{ content: 'Task due today', dueString: 'today' }] }, mockTodoistApi);
264
+ const textContent = extractTextContent(result);
265
+ expect(textContent).toMatchSnapshot();
266
+ expect(textContent).toContain(`Use ${GET_OVERVIEW} to see your updated project organization`);
267
+ });
268
+ it('should suggest overview tool when no hasToday context', async () => {
269
+ const mockApiResponse = createMockTask({
270
+ id: '8485093756',
271
+ content: 'Regular task',
272
+ url: 'https://todoist.com/showTask?id=8485093756',
273
+ addedAt: '2025-08-13T22:09:56.123456Z',
274
+ });
275
+ mockTodoistApi.addTask.mockResolvedValue(mockApiResponse);
276
+ const result = await addTasks.execute({
277
+ tasks: [{ content: 'Regular task', projectId: '6cfCcrrCFg2xP94Q' }],
278
+ }, mockTodoistApi);
279
+ const textContent = extractTextContent(result);
280
+ expect(textContent).toMatchSnapshot();
281
+ expect(textContent).toContain(`Use ${GET_OVERVIEW} to see your updated project organization`);
282
+ });
283
+ });
274
284
  });
@@ -0,0 +1,2 @@
1
+ export {};
2
+ //# sourceMappingURL=complete-tasks.test.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"complete-tasks.test.d.ts","sourceRoot":"","sources":["../../../src/tools/__tests__/complete-tasks.test.ts"],"names":[],"mappings":""}