@marcoappio/marco-config 2.0.504 → 2.0.505

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 (108) hide show
  1. package/dist/types/AuthData.d.ts +4 -0
  2. package/dist/types/AuthData.d.ts.map +1 -0
  3. package/dist/types/AuthData.js +1 -0
  4. package/dist/types/IMAPSourceLocation.d.ts +7 -0
  5. package/dist/types/IMAPSourceLocation.d.ts.map +1 -0
  6. package/dist/types/IMAPSourceLocation.js +1 -0
  7. package/dist/types/Zero.d.ts +14 -8
  8. package/dist/types/Zero.d.ts.map +1 -1
  9. package/dist/types/index.d.ts +2 -0
  10. package/dist/types/index.d.ts.map +1 -1
  11. package/dist/types/index.js +2 -0
  12. package/dist/zero/index.d.ts +7745 -3109
  13. package/dist/zero/index.d.ts.map +1 -1
  14. package/dist/zero/index.js +14 -5
  15. package/dist/zero/mutatorSchemas/account.d.ts +80 -0
  16. package/dist/zero/mutatorSchemas/account.d.ts.map +1 -0
  17. package/dist/zero/mutatorSchemas/account.js +66 -0
  18. package/dist/zero/mutatorSchemas/draft.d.ts +150 -0
  19. package/dist/zero/mutatorSchemas/draft.d.ts.map +1 -0
  20. package/dist/zero/mutatorSchemas/draft.js +104 -0
  21. package/dist/zero/mutatorSchemas/index.d.ts +323 -0
  22. package/dist/zero/mutatorSchemas/index.d.ts.map +1 -0
  23. package/dist/zero/mutatorSchemas/index.js +10 -0
  24. package/dist/zero/mutatorSchemas/thread.d.ts +71 -0
  25. package/dist/zero/mutatorSchemas/thread.d.ts.map +1 -0
  26. package/dist/zero/mutatorSchemas/thread.js +47 -0
  27. package/dist/zero/mutatorSchemas/user.d.ts +26 -0
  28. package/dist/zero/mutatorSchemas/user.d.ts.map +1 -0
  29. package/dist/zero/mutatorSchemas/user.js +27 -0
  30. package/dist/zero/mutators/accountMutators/accountMutators.d.ts +3 -188
  31. package/dist/zero/mutators/accountMutators/accountMutators.d.ts.map +1 -1
  32. package/dist/zero/mutators/accountMutators/accountMutators.js +82 -118
  33. package/dist/zero/mutators/accountMutators/accountMutators.test.d.ts +2 -0
  34. package/dist/zero/mutators/accountMutators/accountMutators.test.d.ts.map +1 -0
  35. package/dist/zero/mutators/accountMutators/accountMutators.test.js +372 -0
  36. package/dist/zero/mutators/accountMutators/index.d.ts +1 -1
  37. package/dist/zero/mutators/accountMutators/index.d.ts.map +1 -1
  38. package/dist/zero/mutators/accountMutators/index.js +1 -1
  39. package/dist/zero/mutators/draftMutators/draftMutators.d.ts +3 -305
  40. package/dist/zero/mutators/draftMutators/draftMutators.d.ts.map +1 -1
  41. package/dist/zero/mutators/draftMutators/draftMutators.js +83 -157
  42. package/dist/zero/mutators/draftMutators/draftMutators.test.d.ts +2 -0
  43. package/dist/zero/mutators/draftMutators/draftMutators.test.d.ts.map +1 -0
  44. package/dist/zero/mutators/draftMutators/draftMutators.test.js +416 -0
  45. package/dist/zero/mutators/draftMutators/index.d.ts +1 -1
  46. package/dist/zero/mutators/draftMutators/index.d.ts.map +1 -1
  47. package/dist/zero/mutators/draftMutators/index.js +1 -1
  48. package/dist/zero/mutators/index.d.ts +1 -251
  49. package/dist/zero/mutators/index.d.ts.map +1 -1
  50. package/dist/zero/mutators/index.js +1 -11
  51. package/dist/zero/mutators/mutators.d.ts +17 -1519
  52. package/dist/zero/mutators/mutators.d.ts.map +1 -1
  53. package/dist/zero/mutators/mutators.js +38 -11
  54. package/dist/zero/mutators/threadMutators/index.d.ts +1 -1
  55. package/dist/zero/mutators/threadMutators/index.d.ts.map +1 -1
  56. package/dist/zero/mutators/threadMutators/index.js +1 -1
  57. package/dist/zero/mutators/threadMutators/threadMutators.d.ts +6 -199
  58. package/dist/zero/mutators/threadMutators/threadMutators.d.ts.map +1 -1
  59. package/dist/zero/mutators/threadMutators/threadMutators.js +87 -119
  60. package/dist/zero/mutators/threadMutators/threadMutators.test.d.ts +2 -0
  61. package/dist/zero/mutators/threadMutators/threadMutators.test.d.ts.map +1 -0
  62. package/dist/zero/mutators/threadMutators/threadMutators.test.js +755 -0
  63. package/dist/zero/mutators/userMutators/index.d.ts +1 -1
  64. package/dist/zero/mutators/userMutators/index.d.ts.map +1 -1
  65. package/dist/zero/mutators/userMutators/index.js +1 -1
  66. package/dist/zero/mutators/userMutators/userMutators.d.ts +3 -56
  67. package/dist/zero/mutators/userMutators/userMutators.d.ts.map +1 -1
  68. package/dist/zero/mutators/userMutators/userMutators.js +26 -40
  69. package/dist/zero/mutators/userMutators/userMutators.test.d.ts +2 -0
  70. package/dist/zero/mutators/userMutators/userMutators.test.d.ts.map +1 -0
  71. package/dist/zero/mutators/userMutators/userMutators.test.js +84 -0
  72. package/dist/zero/queries/getAccounts.d.ts +1025 -4
  73. package/dist/zero/queries/getAccounts.d.ts.map +1 -1
  74. package/dist/zero/queries/getAccounts.js +3 -6
  75. package/dist/zero/queries/getContacts.d.ts +1033 -18
  76. package/dist/zero/queries/getContacts.d.ts.map +1 -1
  77. package/dist/zero/queries/getContacts.js +15 -11
  78. package/dist/zero/queries/getDrafts.d.ts +1031 -11
  79. package/dist/zero/queries/getDrafts.d.ts.map +1 -1
  80. package/dist/zero/queries/getDrafts.js +11 -7
  81. package/dist/zero/queries/getThread.d.ts +1028 -7
  82. package/dist/zero/queries/getThread.d.ts.map +1 -1
  83. package/dist/zero/queries/getThread.js +9 -5
  84. package/dist/zero/queries/getThreadList.d.ts +1035 -23
  85. package/dist/zero/queries/getThreadList.d.ts.map +1 -1
  86. package/dist/zero/queries/getThreadList.js +18 -14
  87. package/dist/zero/queries/getThreads.d.ts +1035 -23
  88. package/dist/zero/queries/getThreads.d.ts.map +1 -1
  89. package/dist/zero/queries/getThreads.js +18 -14
  90. package/dist/zero/queries/getUser.d.ts +1025 -4
  91. package/dist/zero/queries/getUser.d.ts.map +1 -1
  92. package/dist/zero/queries/getUser.js +7 -10
  93. package/dist/zero/queries/index.d.ts +141 -460
  94. package/dist/zero/queries/index.d.ts.map +1 -1
  95. package/dist/zero/queries/index.js +10 -18
  96. package/dist/zero/schema.d.ts +133 -133
  97. package/dist/zero/schema.d.ts.map +1 -1
  98. package/dist/zero/schema.js +1 -0
  99. package/package.json +2 -2
  100. package/dist/zero/crud.d.ts +0 -1024
  101. package/dist/zero/crud.d.ts.map +0 -1
  102. package/dist/zero/crud.js +0 -3
  103. package/dist/zero/mutators/typedMutator.d.ts +0 -2056
  104. package/dist/zero/mutators/typedMutator.d.ts.map +0 -1
  105. package/dist/zero/mutators/typedMutator.js +0 -2
  106. package/dist/zero/queries/z.d.ts +0 -14439
  107. package/dist/zero/queries/z.d.ts.map +0 -1
  108. package/dist/zero/queries/z.js +0 -3
@@ -0,0 +1,755 @@
1
+ import { describe, expect, it, mock } from 'bun:test';
2
+ import { createMutators } from '../mutators';
3
+ describe('threadMutators', () => {
4
+ describe('addLabel', () => {
5
+ it('adds label to thread', async () => {
6
+ const threadRecord = {
7
+ accountId: 'test-account-id-1',
8
+ id: 'test-thread-id-1',
9
+ };
10
+ const labelRecord = {
11
+ accountId: 'test-account-id-1',
12
+ id: 'test-label-id-1',
13
+ path: 'Work',
14
+ };
15
+ const messageRecords = [{ id: 'test-message-id-1' }, { id: 'test-message-id-2' }];
16
+ const runThread = mock(async () => threadRecord);
17
+ const oneThread = mock(() => ({ run: runThread }));
18
+ const whereThread = mock(() => ({ one: oneThread }));
19
+ const runLabel = mock(async () => labelRecord);
20
+ const oneLabel = mock(() => ({ run: runLabel }));
21
+ const whereLabelPath = mock(() => ({ one: oneLabel }));
22
+ const whereLabelAccount = mock(() => ({ where: whereLabelPath }));
23
+ const runMessages = mock(async () => messageRecords);
24
+ const whereMessages = mock(() => ({ run: runMessages }));
25
+ const runExisting = mock(async () => null);
26
+ const oneExisting = mock(() => ({ run: runExisting }));
27
+ const whereLabelId = mock(() => ({ one: oneExisting }));
28
+ const runAllThreadLabels = mock(async () => [{ labelId: 'test-label-id-1', threadId: 'test-thread-id-1' }]);
29
+ const whereThreadLabel = mock((field) => {
30
+ if (field === 'threadId') {
31
+ return { run: runAllThreadLabels, where: whereLabelId };
32
+ }
33
+ if (field === 'threadMessageId') {
34
+ return { where: whereLabelId };
35
+ }
36
+ return { where: whereLabelId };
37
+ });
38
+ const threadLabelInsert = mock(async () => { });
39
+ const threadUpdate = mock(async () => { });
40
+ const threadByLabelInsert = mock(async () => { });
41
+ const runThreadByLabel = mock(async () => null);
42
+ const oneThreadByLabel = mock(() => ({ run: runThreadByLabel }));
43
+ const whereThreadByLabelThreadId = mock(() => ({ one: oneThreadByLabel }));
44
+ const whereThreadByLabel = mock(() => ({ where: whereThreadByLabelThreadId }));
45
+ const transaction = {
46
+ mutate: {
47
+ accountLabel: {
48
+ update: mock(async () => { }),
49
+ },
50
+ thread: {
51
+ update: threadUpdate,
52
+ },
53
+ threadByLabel: {
54
+ delete: mock(async () => { }),
55
+ insert: threadByLabelInsert,
56
+ },
57
+ threadLabel: {
58
+ delete: mock(async () => { }),
59
+ insert: threadLabelInsert,
60
+ },
61
+ },
62
+ query: {
63
+ accountLabel: {
64
+ where: whereLabelAccount,
65
+ },
66
+ thread: {
67
+ where: whereThread,
68
+ },
69
+ threadByLabel: {
70
+ where: whereThreadByLabel,
71
+ },
72
+ threadLabel: {
73
+ where: whereThreadLabel,
74
+ },
75
+ threadMessage: {
76
+ where: whereMessages,
77
+ },
78
+ },
79
+ };
80
+ const mutators = createMutators();
81
+ await mutators.thread.addLabel(transaction, {
82
+ accounts: { 'test-account-id-1': { threadIds: ['test-thread-id-1'] } },
83
+ labelPath: 'Work',
84
+ });
85
+ expect(threadLabelInsert).toHaveBeenCalledTimes(2);
86
+ expect(threadLabelInsert).toHaveBeenCalledWith(expect.objectContaining({
87
+ accountId: 'test-account-id-1',
88
+ labelId: 'test-label-id-1',
89
+ threadId: 'test-thread-id-1',
90
+ threadMessageId: 'test-message-id-1',
91
+ }));
92
+ expect(threadLabelInsert).toHaveBeenCalledWith(expect.objectContaining({
93
+ accountId: 'test-account-id-1',
94
+ labelId: 'test-label-id-1',
95
+ threadId: 'test-thread-id-1',
96
+ threadMessageId: 'test-message-id-2',
97
+ }));
98
+ expect(threadUpdate).toHaveBeenCalledWith({
99
+ id: 'test-thread-id-1',
100
+ labelIdList: ' test-label-id-1 ',
101
+ });
102
+ });
103
+ });
104
+ describe('delete', () => {
105
+ it('deletes threads and decrements unread counts for unread threads', async () => {
106
+ const threadDelete = mock(async () => { });
107
+ const labelUpdate = mock(async () => { });
108
+ const threads = [
109
+ { id: 'test-thread-id-1', labelIdList: ' test-label-id-1 ', seen: false },
110
+ { id: 'test-thread-id-2', labelIdList: ' test-label-id-1 ', seen: true },
111
+ ];
112
+ const labels = [{ id: 'test-label-id-1', unreadCount: 5 }];
113
+ const runThread = mock(async (id) => threads.find(x => x.id === id));
114
+ const oneThread = mock(() => ({ run: () => runThread(whereThreadId) }));
115
+ let whereThreadId = '';
116
+ const whereThread = mock((_field, id) => {
117
+ whereThreadId = id;
118
+ return { one: oneThread };
119
+ });
120
+ const runLabels = mock(async () => labels);
121
+ const whereLabels = mock(() => ({ run: runLabels }));
122
+ const transaction = {
123
+ mutate: {
124
+ accountLabel: {
125
+ update: labelUpdate,
126
+ },
127
+ thread: {
128
+ delete: threadDelete,
129
+ },
130
+ },
131
+ query: {
132
+ accountLabel: {
133
+ where: whereLabels,
134
+ },
135
+ thread: {
136
+ where: whereThread,
137
+ },
138
+ },
139
+ };
140
+ const mutators = createMutators();
141
+ await mutators.thread.delete(transaction, {
142
+ accounts: { 'test-account-id-1': { threadIds: ['test-thread-id-1', 'test-thread-id-2'] } },
143
+ });
144
+ expect(threadDelete).toHaveBeenCalledTimes(2);
145
+ expect(threadDelete).toHaveBeenCalledWith({ id: 'test-thread-id-1' });
146
+ expect(threadDelete).toHaveBeenCalledWith({ id: 'test-thread-id-2' });
147
+ expect(labelUpdate).toHaveBeenCalledTimes(1);
148
+ expect(labelUpdate).toHaveBeenCalledWith({
149
+ id: 'test-label-id-1',
150
+ unreadCount: 4,
151
+ });
152
+ });
153
+ it('does not update unread counts when deleting read threads', async () => {
154
+ const threadDelete = mock(async () => { });
155
+ const labelUpdate = mock(async () => { });
156
+ const thread = { id: 'test-thread-id-1', labelIdList: ' test-label-id-1 ', seen: true };
157
+ const runThread = mock(async () => thread);
158
+ const oneThread = mock(() => ({ run: runThread }));
159
+ const whereThread = mock(() => ({ one: oneThread }));
160
+ const transaction = {
161
+ mutate: {
162
+ accountLabel: {
163
+ update: labelUpdate,
164
+ },
165
+ thread: {
166
+ delete: threadDelete,
167
+ },
168
+ },
169
+ query: {
170
+ thread: {
171
+ where: whereThread,
172
+ },
173
+ },
174
+ };
175
+ const mutators = createMutators();
176
+ await mutators.thread.delete(transaction, {
177
+ accounts: { 'test-account-id-1': { threadIds: ['test-thread-id-1'] } },
178
+ });
179
+ expect(threadDelete).toHaveBeenCalledTimes(1);
180
+ expect(labelUpdate).not.toHaveBeenCalled();
181
+ });
182
+ it('handles non-existent threads gracefully', async () => {
183
+ const threadDelete = mock(async () => { });
184
+ const runThread = mock(async () => null);
185
+ const oneThread = mock(() => ({ run: runThread }));
186
+ const whereThread = mock(() => ({ one: oneThread }));
187
+ const transaction = {
188
+ mutate: {
189
+ thread: {
190
+ delete: threadDelete,
191
+ },
192
+ },
193
+ query: {
194
+ thread: {
195
+ where: whereThread,
196
+ },
197
+ },
198
+ };
199
+ const mutators = createMutators();
200
+ await mutators.thread.delete(transaction, {
201
+ accounts: { 'test-account-id-1': { threadIds: ['non-existent-thread'] } },
202
+ });
203
+ expect(threadDelete).not.toHaveBeenCalled();
204
+ });
205
+ });
206
+ describe('removeLabel', () => {
207
+ it('removes label from thread', async () => {
208
+ const threadRecord = {
209
+ accountId: 'test-account-id-1',
210
+ id: 'test-thread-id-1',
211
+ labelIdList: ' test-label-id-1 ',
212
+ };
213
+ const labelRecord = {
214
+ accountId: 'test-account-id-1',
215
+ id: 'test-label-id-1',
216
+ path: 'Work',
217
+ };
218
+ const messageRecords = [{ id: 'test-message-id-1' }, { id: 'test-message-id-2' }];
219
+ const runThread = mock(async () => threadRecord);
220
+ const oneThread = mock(() => ({ run: runThread }));
221
+ const whereThread = mock(() => ({ one: oneThread }));
222
+ const runLabel = mock(async () => labelRecord);
223
+ const oneLabel = mock(() => ({ run: runLabel }));
224
+ const whereLabelPath = mock(() => ({ one: oneLabel }));
225
+ const whereLabelAccount = mock(() => ({ where: whereLabelPath }));
226
+ const runMessages = mock(async () => messageRecords);
227
+ const whereMessages = mock(() => ({ run: runMessages }));
228
+ const threadLabelDelete = mock(async () => { });
229
+ const threadUpdate = mock(async () => { });
230
+ const threadByLabelDelete = mock(async () => { });
231
+ const transaction = {
232
+ mutate: {
233
+ accountLabel: {
234
+ update: mock(async () => { }),
235
+ },
236
+ thread: {
237
+ update: threadUpdate,
238
+ },
239
+ threadByLabel: {
240
+ delete: threadByLabelDelete,
241
+ },
242
+ threadLabel: {
243
+ delete: threadLabelDelete,
244
+ },
245
+ },
246
+ query: {
247
+ accountLabel: {
248
+ where: whereLabelAccount,
249
+ },
250
+ thread: {
251
+ where: whereThread,
252
+ },
253
+ threadMessage: {
254
+ where: whereMessages,
255
+ },
256
+ },
257
+ };
258
+ const mutators = createMutators();
259
+ await mutators.thread.removeLabel(transaction, {
260
+ accounts: { 'test-account-id-1': { threadIds: ['test-thread-id-1'] } },
261
+ labelPath: 'Work',
262
+ });
263
+ expect(threadLabelDelete).toHaveBeenCalledTimes(2);
264
+ expect(threadLabelDelete).toHaveBeenCalledWith({
265
+ accountId: 'test-account-id-1',
266
+ labelId: 'test-label-id-1',
267
+ threadMessageId: 'test-message-id-1',
268
+ });
269
+ expect(threadLabelDelete).toHaveBeenCalledWith({
270
+ accountId: 'test-account-id-1',
271
+ labelId: 'test-label-id-1',
272
+ threadMessageId: 'test-message-id-2',
273
+ });
274
+ expect(threadUpdate).toHaveBeenCalledWith({
275
+ id: 'test-thread-id-1',
276
+ labelIdList: '',
277
+ });
278
+ });
279
+ });
280
+ describe('setArchive', () => {
281
+ it('archives thread', async () => {
282
+ const threadRecord = {
283
+ accountId: 'test-account-id-1',
284
+ id: 'test-thread-id-1',
285
+ labelIdList: ' test-inbox-label-id ',
286
+ };
287
+ const archiveLabel = {
288
+ accountId: 'test-account-id-1',
289
+ id: 'test-archive-label-id',
290
+ specialUse: 'ARCHIVE',
291
+ };
292
+ const messageRecords = [{ id: 'test-message-id-1' }];
293
+ const runThread = mock(async () => threadRecord);
294
+ const oneThread = mock(() => ({ run: runThread }));
295
+ const whereThread = mock(() => ({ one: oneThread }));
296
+ const runLabel = mock(async () => archiveLabel);
297
+ const oneLabel = mock(() => ({ run: runLabel }));
298
+ const whereSpecialUse = mock(() => ({ one: oneLabel }));
299
+ const runLabelsToUpdate = mock(async () => [{ id: 'test-inbox-label-id', unreadCount: 1 }]);
300
+ const whereLabelAccount = mock((field) => {
301
+ if (field === 'id') {
302
+ return { run: runLabelsToUpdate };
303
+ }
304
+ return { where: whereSpecialUse };
305
+ });
306
+ const runMessages = mock(async () => messageRecords);
307
+ const whereMessages = mock(() => ({ run: runMessages }));
308
+ const threadLabelDelete = mock(async () => { });
309
+ const threadLabelInsert = mock(async () => { });
310
+ const threadUpdate = mock(async () => { });
311
+ const threadByLabelDelete = mock(async () => { });
312
+ const threadByLabelInsert = mock(async () => { });
313
+ const transaction = {
314
+ mutate: {
315
+ accountLabel: {
316
+ update: mock(async () => { }),
317
+ },
318
+ thread: {
319
+ update: threadUpdate,
320
+ },
321
+ threadByLabel: {
322
+ delete: threadByLabelDelete,
323
+ insert: threadByLabelInsert,
324
+ },
325
+ threadLabel: {
326
+ delete: threadLabelDelete,
327
+ insert: threadLabelInsert,
328
+ },
329
+ },
330
+ query: {
331
+ accountLabel: {
332
+ where: whereLabelAccount,
333
+ },
334
+ thread: {
335
+ where: whereThread,
336
+ },
337
+ threadMessage: {
338
+ where: whereMessages,
339
+ },
340
+ },
341
+ };
342
+ const mutators = createMutators();
343
+ await mutators.thread.setArchive(transaction, {
344
+ accounts: { 'test-account-id-1': { threadIds: ['test-thread-id-1'] } },
345
+ });
346
+ expect(threadLabelDelete).toHaveBeenCalledWith({
347
+ accountId: 'test-account-id-1',
348
+ labelId: 'test-inbox-label-id',
349
+ threadMessageId: 'test-message-id-1',
350
+ });
351
+ expect(threadLabelInsert).toHaveBeenCalledWith(expect.objectContaining({
352
+ accountId: 'test-account-id-1',
353
+ labelId: 'test-archive-label-id',
354
+ threadId: 'test-thread-id-1',
355
+ threadMessageId: 'test-message-id-1',
356
+ }));
357
+ expect(threadUpdate).toHaveBeenCalledWith({
358
+ id: 'test-thread-id-1',
359
+ labelIdList: ' test-archive-label-id ',
360
+ });
361
+ });
362
+ });
363
+ describe('setFlagged', () => {
364
+ it('sets thread flagged status', async () => {
365
+ const threadUpdate = mock(async () => { });
366
+ const transaction = {
367
+ mutate: {
368
+ thread: {
369
+ update: threadUpdate,
370
+ },
371
+ },
372
+ query: {},
373
+ };
374
+ const mutators = createMutators();
375
+ await mutators.thread.setFlagged(transaction, {
376
+ accounts: { 'test-account-id-1': { threadIds: ['test-thread-id-1'] } },
377
+ flagged: true,
378
+ });
379
+ expect(threadUpdate).toHaveBeenCalledWith({
380
+ flagged: true,
381
+ id: 'test-thread-id-1',
382
+ });
383
+ });
384
+ });
385
+ describe('setInbox', () => {
386
+ it('moves thread to inbox', async () => {
387
+ const threadRecord = {
388
+ accountId: 'test-account-id-1',
389
+ id: 'test-thread-id-1',
390
+ labelIdList: ' test-archive-label-id ',
391
+ };
392
+ const inboxLabel = {
393
+ accountId: 'test-account-id-1',
394
+ id: 'test-inbox-label-id',
395
+ specialUse: 'INBOX',
396
+ };
397
+ const messageRecords = [{ id: 'test-message-id-1' }];
398
+ const runThread = mock(async () => threadRecord);
399
+ const oneThread = mock(() => ({ run: runThread }));
400
+ const whereThread = mock(() => ({ one: oneThread }));
401
+ const runLabel = mock(async () => inboxLabel);
402
+ const oneLabel = mock(() => ({ run: runLabel }));
403
+ const whereSpecialUse = mock(() => ({ one: oneLabel }));
404
+ const runLabelsToUpdate = mock(async () => [{ id: 'test-archive-label-id', unreadCount: 1 }]);
405
+ const whereLabelAccount = mock((field) => {
406
+ if (field === 'id') {
407
+ return { run: runLabelsToUpdate };
408
+ }
409
+ return { where: whereSpecialUse };
410
+ });
411
+ const runMessages = mock(async () => messageRecords);
412
+ const whereMessages = mock(() => ({ run: runMessages }));
413
+ const threadLabelDelete = mock(async () => { });
414
+ const threadLabelInsert = mock(async () => { });
415
+ const threadUpdate = mock(async () => { });
416
+ const threadByLabelDelete = mock(async () => { });
417
+ const threadByLabelInsert = mock(async () => { });
418
+ const transaction = {
419
+ mutate: {
420
+ accountLabel: {
421
+ update: mock(async () => { }),
422
+ },
423
+ thread: {
424
+ update: threadUpdate,
425
+ },
426
+ threadByLabel: {
427
+ delete: threadByLabelDelete,
428
+ insert: threadByLabelInsert,
429
+ },
430
+ threadLabel: {
431
+ delete: threadLabelDelete,
432
+ insert: threadLabelInsert,
433
+ },
434
+ },
435
+ query: {
436
+ accountLabel: {
437
+ where: whereLabelAccount,
438
+ },
439
+ thread: {
440
+ where: whereThread,
441
+ },
442
+ threadMessage: {
443
+ where: whereMessages,
444
+ },
445
+ },
446
+ };
447
+ const mutators = createMutators();
448
+ await mutators.thread.setInbox(transaction, {
449
+ accounts: { 'test-account-id-1': { threadIds: ['test-thread-id-1'] } },
450
+ });
451
+ expect(threadLabelDelete).toHaveBeenCalledWith({
452
+ accountId: 'test-account-id-1',
453
+ labelId: 'test-archive-label-id',
454
+ threadMessageId: 'test-message-id-1',
455
+ });
456
+ expect(threadLabelInsert).toHaveBeenCalledWith(expect.objectContaining({
457
+ accountId: 'test-account-id-1',
458
+ labelId: 'test-inbox-label-id',
459
+ threadId: 'test-thread-id-1',
460
+ threadMessageId: 'test-message-id-1',
461
+ }));
462
+ expect(threadUpdate).toHaveBeenCalledWith({
463
+ id: 'test-thread-id-1',
464
+ labelIdList: ' test-inbox-label-id ',
465
+ });
466
+ });
467
+ });
468
+ describe('setSeen', () => {
469
+ it('decrements unread count when marking unread threads as read', async () => {
470
+ const threadUpdate = mock(async () => { });
471
+ const labelUpdate = mock(async () => { });
472
+ const threads = [
473
+ { id: 'test-thread-id-1', labelIdList: ' test-inbox-label-id ', seen: false },
474
+ { id: 'test-thread-id-2', labelIdList: ' test-inbox-label-id ', seen: false },
475
+ ];
476
+ const labels = [{ id: 'test-inbox-label-id', specialUse: 'INBOX', unreadCount: 5 }];
477
+ const runThreads = mock(async () => threads);
478
+ const whereThread = mock(() => ({ run: runThreads }));
479
+ const runLabels = mock(async () => labels);
480
+ const whereLabel = mock(() => ({ run: runLabels }));
481
+ const transaction = {
482
+ mutate: {
483
+ accountLabel: {
484
+ update: labelUpdate,
485
+ },
486
+ thread: {
487
+ update: threadUpdate,
488
+ },
489
+ },
490
+ query: {
491
+ accountLabel: {
492
+ where: whereLabel,
493
+ },
494
+ thread: {
495
+ where: whereThread,
496
+ },
497
+ },
498
+ };
499
+ const mutators = createMutators();
500
+ await mutators.thread.setSeen(transaction, {
501
+ accounts: { 'test-account-id-1': { threadIds: ['test-thread-id-1', 'test-thread-id-2'] } },
502
+ seen: true,
503
+ });
504
+ expect(threadUpdate).toHaveBeenCalledTimes(2);
505
+ expect(labelUpdate).toHaveBeenCalledWith({
506
+ id: 'test-inbox-label-id',
507
+ unreadCount: 3,
508
+ });
509
+ });
510
+ it('increments unread count when marking read threads as unread', async () => {
511
+ const threadUpdate = mock(async () => { });
512
+ const labelUpdate = mock(async () => { });
513
+ const threads = [{ id: 'test-thread-id-1', labelIdList: ' test-inbox-label-id ', seen: true }];
514
+ const labels = [{ id: 'test-inbox-label-id', specialUse: 'INBOX', unreadCount: 5 }];
515
+ const runThreads = mock(async () => threads);
516
+ const whereThread = mock(() => ({ run: runThreads }));
517
+ const runLabels = mock(async () => labels);
518
+ const whereLabel = mock(() => ({ run: runLabels }));
519
+ const transaction = {
520
+ mutate: {
521
+ accountLabel: {
522
+ update: labelUpdate,
523
+ },
524
+ thread: {
525
+ update: threadUpdate,
526
+ },
527
+ },
528
+ query: {
529
+ accountLabel: {
530
+ where: whereLabel,
531
+ },
532
+ thread: {
533
+ where: whereThread,
534
+ },
535
+ },
536
+ };
537
+ const mutators = createMutators();
538
+ await mutators.thread.setSeen(transaction, {
539
+ accounts: { 'test-account-id-1': { threadIds: ['test-thread-id-1'] } },
540
+ seen: false,
541
+ });
542
+ expect(threadUpdate).toHaveBeenCalledTimes(1);
543
+ expect(labelUpdate).toHaveBeenCalledWith({
544
+ id: 'test-inbox-label-id',
545
+ unreadCount: 6,
546
+ });
547
+ });
548
+ it('skips label update when threads have no labels', async () => {
549
+ const threadUpdate = mock(async () => { });
550
+ const labelUpdate = mock(async () => { });
551
+ const threads = [{ id: 'test-thread-id-1', labelIdList: '', seen: false }];
552
+ const runThreads = mock(async () => threads);
553
+ const whereThread = mock(() => ({ run: runThreads }));
554
+ const transaction = {
555
+ mutate: {
556
+ accountLabel: {
557
+ update: labelUpdate,
558
+ },
559
+ thread: {
560
+ update: threadUpdate,
561
+ },
562
+ },
563
+ query: {
564
+ thread: {
565
+ where: whereThread,
566
+ },
567
+ },
568
+ };
569
+ const mutators = createMutators();
570
+ await mutators.thread.setSeen(transaction, {
571
+ accounts: { 'test-account-id-1': { threadIds: ['test-thread-id-1'] } },
572
+ seen: true,
573
+ });
574
+ expect(threadUpdate).toHaveBeenCalledTimes(1);
575
+ expect(labelUpdate).not.toHaveBeenCalled();
576
+ });
577
+ it('skips already-read threads when marking as read', async () => {
578
+ const threadUpdate = mock(async () => { });
579
+ const labelUpdate = mock(async () => { });
580
+ const threads = [{ id: 'test-thread-id-1', labelIdList: ' test-inbox-label-id ', seen: true }];
581
+ const runThreads = mock(async () => threads);
582
+ const whereThread = mock(() => ({ run: runThreads }));
583
+ const transaction = {
584
+ mutate: {
585
+ accountLabel: {
586
+ update: labelUpdate,
587
+ },
588
+ thread: {
589
+ update: threadUpdate,
590
+ },
591
+ },
592
+ query: {
593
+ thread: {
594
+ where: whereThread,
595
+ },
596
+ },
597
+ };
598
+ const mutators = createMutators();
599
+ await mutators.thread.setSeen(transaction, {
600
+ accounts: { 'test-account-id-1': { threadIds: ['test-thread-id-1'] } },
601
+ seen: true,
602
+ });
603
+ expect(threadUpdate).toHaveBeenCalledTimes(1);
604
+ expect(labelUpdate).not.toHaveBeenCalled();
605
+ });
606
+ });
607
+ describe('setSpam', () => {
608
+ it('marks thread as spam', async () => {
609
+ const threadRecord = {
610
+ accountId: 'test-account-id-1',
611
+ id: 'test-thread-id-1',
612
+ };
613
+ const spamLabel = {
614
+ accountId: 'test-account-id-1',
615
+ id: 'test-spam-label-id',
616
+ specialUse: 'SPAM',
617
+ };
618
+ const messageRecords = [{ id: 'test-message-id-1' }];
619
+ const runThread = mock(async () => threadRecord);
620
+ const oneThread = mock(() => ({ run: runThread }));
621
+ const whereThread = mock(() => ({ one: oneThread }));
622
+ const runSpam = mock(async () => spamLabel);
623
+ const oneLabel = mock(() => ({ run: runSpam }));
624
+ const whereSpecialUse = mock(() => ({ one: oneLabel }));
625
+ const whereLabelAccount = mock(() => ({ where: whereSpecialUse }));
626
+ const runAllThreadLabels = mock(async () => []);
627
+ const whereThreadLabel = mock(() => ({ run: runAllThreadLabels }));
628
+ const runMessages = mock(async () => messageRecords);
629
+ const whereMessages = mock(() => ({ run: runMessages }));
630
+ const threadLabelInsert = mock(async () => { });
631
+ const threadUpdate = mock(async () => { });
632
+ const threadByLabelInsert = mock(async () => { });
633
+ const transaction = {
634
+ mutate: {
635
+ accountLabel: {
636
+ update: mock(async () => { }),
637
+ },
638
+ thread: {
639
+ update: threadUpdate,
640
+ },
641
+ threadByLabel: {
642
+ delete: mock(async () => { }),
643
+ insert: threadByLabelInsert,
644
+ },
645
+ threadLabel: {
646
+ delete: mock(async () => { }),
647
+ insert: threadLabelInsert,
648
+ },
649
+ },
650
+ query: {
651
+ accountLabel: {
652
+ where: whereLabelAccount,
653
+ },
654
+ thread: {
655
+ where: whereThread,
656
+ },
657
+ threadLabel: {
658
+ where: whereThreadLabel,
659
+ },
660
+ threadMessage: {
661
+ where: whereMessages,
662
+ },
663
+ },
664
+ };
665
+ const mutators = createMutators();
666
+ await mutators.thread.setSpam(transaction, {
667
+ accounts: { 'test-account-id-1': { threadIds: ['test-thread-id-1'] } },
668
+ });
669
+ expect(threadLabelInsert).toHaveBeenCalledWith(expect.objectContaining({
670
+ accountId: 'test-account-id-1',
671
+ labelId: 'test-spam-label-id',
672
+ threadId: 'test-thread-id-1',
673
+ threadMessageId: 'test-message-id-1',
674
+ }));
675
+ expect(threadUpdate).toHaveBeenCalledWith({
676
+ id: 'test-thread-id-1',
677
+ labelIdList: ' test-spam-label-id ',
678
+ });
679
+ });
680
+ });
681
+ describe('setTrash', () => {
682
+ it('moves thread to trash', async () => {
683
+ const threadRecord = {
684
+ accountId: 'test-account-id-1',
685
+ id: 'test-thread-id-1',
686
+ };
687
+ const trashLabel = {
688
+ accountId: 'test-account-id-1',
689
+ id: 'test-trash-label-id',
690
+ specialUse: 'TRASH',
691
+ };
692
+ const messageRecords = [{ id: 'test-message-id-1' }];
693
+ const runThread = mock(async () => threadRecord);
694
+ const oneThread = mock(() => ({ run: runThread }));
695
+ const whereThread = mock(() => ({ one: oneThread }));
696
+ const runTrash = mock(async () => trashLabel);
697
+ const oneLabel = mock(() => ({ run: runTrash }));
698
+ const whereSpecialUse = mock(() => ({ one: oneLabel }));
699
+ const whereLabelAccount = mock(() => ({ where: whereSpecialUse }));
700
+ const runAllThreadLabels = mock(async () => []);
701
+ const whereThreadLabel = mock(() => ({ run: runAllThreadLabels }));
702
+ const runMessages = mock(async () => messageRecords);
703
+ const whereMessages = mock(() => ({ run: runMessages }));
704
+ const threadLabelInsert = mock(async () => { });
705
+ const threadUpdate = mock(async () => { });
706
+ const threadByLabelInsert = mock(async () => { });
707
+ const transaction = {
708
+ mutate: {
709
+ accountLabel: {
710
+ update: mock(async () => { }),
711
+ },
712
+ thread: {
713
+ update: threadUpdate,
714
+ },
715
+ threadByLabel: {
716
+ delete: mock(async () => { }),
717
+ insert: threadByLabelInsert,
718
+ },
719
+ threadLabel: {
720
+ delete: mock(async () => { }),
721
+ insert: threadLabelInsert,
722
+ },
723
+ },
724
+ query: {
725
+ accountLabel: {
726
+ where: whereLabelAccount,
727
+ },
728
+ thread: {
729
+ where: whereThread,
730
+ },
731
+ threadLabel: {
732
+ where: whereThreadLabel,
733
+ },
734
+ threadMessage: {
735
+ where: whereMessages,
736
+ },
737
+ },
738
+ };
739
+ const mutators = createMutators();
740
+ await mutators.thread.setTrash(transaction, {
741
+ accounts: { 'test-account-id-1': { threadIds: ['test-thread-id-1'] } },
742
+ });
743
+ expect(threadLabelInsert).toHaveBeenCalledWith(expect.objectContaining({
744
+ accountId: 'test-account-id-1',
745
+ labelId: 'test-trash-label-id',
746
+ threadId: 'test-thread-id-1',
747
+ threadMessageId: 'test-message-id-1',
748
+ }));
749
+ expect(threadUpdate).toHaveBeenCalledWith({
750
+ id: 'test-thread-id-1',
751
+ labelIdList: ' test-trash-label-id ',
752
+ });
753
+ });
754
+ });
755
+ });