@marcoappio/marco-config 2.0.539 → 2.0.541

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 (94) hide show
  1. package/dist/constants/index.d.ts +0 -4
  2. package/dist/constants/index.d.ts.map +1 -1
  3. package/dist/constants/index.js +0 -2
  4. package/dist/index.d.ts +0 -1
  5. package/dist/index.d.ts.map +1 -1
  6. package/dist/index.js +0 -1
  7. package/dist/marcoPublicConfig.d.ts +0 -3
  8. package/dist/marcoPublicConfig.d.ts.map +1 -1
  9. package/dist/marcoPublicConfig.js +0 -9
  10. package/dist/schemas/index.d.ts +2 -0
  11. package/dist/schemas/index.d.ts.map +1 -1
  12. package/dist/schemas/index.js +2 -0
  13. package/dist/schemas/mutators/index.d.ts +2 -0
  14. package/dist/schemas/mutators/index.d.ts.map +1 -0
  15. package/dist/schemas/mutators/index.js +1 -0
  16. package/dist/{zero → schemas/mutators}/mutatorSchemas.d.ts +4 -4
  17. package/dist/{zero → schemas/mutators}/mutatorSchemas.d.ts.map +1 -1
  18. package/dist/{zero → schemas/mutators}/mutatorSchemas.js +4 -4
  19. package/dist/sdk/endpoints/private/mutations/account/createAccount.js +1 -1
  20. package/dist/sdk/endpoints/private/mutations/account/createAlias.d.ts +1 -1
  21. package/dist/sdk/endpoints/private/mutations/account/createAlias.js +2 -2
  22. package/dist/sdk/endpoints/private/mutations/account/deleteAccount.d.ts +1 -1
  23. package/dist/sdk/endpoints/private/mutations/account/deleteAccount.js +2 -2
  24. package/dist/sdk/endpoints/private/mutations/account/deleteAlias.d.ts +1 -1
  25. package/dist/sdk/endpoints/private/mutations/account/deleteAlias.js +2 -2
  26. package/dist/sdk/endpoints/private/mutations/account/setAliasName.d.ts +1 -1
  27. package/dist/sdk/endpoints/private/mutations/account/setAliasName.js +2 -2
  28. package/dist/sdk/endpoints/private/mutations/account/setAliasPrimary.d.ts +1 -1
  29. package/dist/sdk/endpoints/private/mutations/account/setAliasPrimary.js +2 -2
  30. package/dist/sdk/endpoints/private/mutations/account/setConnectionConfigImap.d.ts +1 -1
  31. package/dist/sdk/endpoints/private/mutations/account/setConnectionConfigImap.js +2 -2
  32. package/dist/sdk/endpoints/private/mutations/account/setConnectionConfigOauth.d.ts +1 -1
  33. package/dist/sdk/endpoints/private/mutations/account/setConnectionConfigOauth.js +2 -2
  34. package/dist/sdk/endpoints/private/mutations/account/setSettings.d.ts +1 -1
  35. package/dist/sdk/endpoints/private/mutations/account/setSettings.js +2 -2
  36. package/dist/sdk/endpoints/private/mutations/draft/cancelSend.d.ts +1 -1
  37. package/dist/sdk/endpoints/private/mutations/draft/cancelSend.js +2 -2
  38. package/dist/sdk/endpoints/private/mutations/draft/createAttachment.d.ts +1 -1
  39. package/dist/sdk/endpoints/private/mutations/draft/createAttachment.js +2 -2
  40. package/dist/sdk/endpoints/private/mutations/draft/createDraft.js +1 -1
  41. package/dist/sdk/endpoints/private/mutations/draft/deleteAttachment.d.ts +1 -1
  42. package/dist/sdk/endpoints/private/mutations/draft/deleteAttachment.js +2 -2
  43. package/dist/sdk/endpoints/private/mutations/draft/deleteDraft.d.ts +1 -1
  44. package/dist/sdk/endpoints/private/mutations/draft/deleteDraft.js +2 -2
  45. package/dist/sdk/endpoints/private/mutations/draft/scheduleSend.d.ts +1 -1
  46. package/dist/sdk/endpoints/private/mutations/draft/scheduleSend.js +2 -2
  47. package/dist/sdk/endpoints/private/mutations/draft/setContent.d.ts +1 -1
  48. package/dist/sdk/endpoints/private/mutations/draft/setContent.js +2 -2
  49. package/dist/sdk/endpoints/private/mutations/draft/setEnvelope.d.ts +3 -3
  50. package/dist/sdk/endpoints/private/mutations/draft/setEnvelope.js +2 -2
  51. package/dist/sdk/endpoints/private/mutations/draft/setFrom.d.ts +1 -1
  52. package/dist/sdk/endpoints/private/mutations/draft/setFrom.js +2 -2
  53. package/dist/sdk/endpoints/private/mutations/thread/addLabel.js +1 -1
  54. package/dist/sdk/endpoints/private/mutations/thread/deleteThreads.js +1 -1
  55. package/dist/sdk/endpoints/private/mutations/thread/removeLabel.js +1 -1
  56. package/dist/sdk/endpoints/private/mutations/thread/setArchive.js +1 -1
  57. package/dist/sdk/endpoints/private/mutations/thread/setFlagged.js +1 -1
  58. package/dist/sdk/endpoints/private/mutations/thread/setInbox.js +1 -1
  59. package/dist/sdk/endpoints/private/mutations/thread/setSeen.js +1 -1
  60. package/dist/sdk/endpoints/private/mutations/thread/setSpam.js +1 -1
  61. package/dist/sdk/endpoints/private/mutations/thread/setTrash.js +1 -1
  62. package/dist/sdk/endpoints/private/mutations/user/createView.js +1 -1
  63. package/dist/sdk/endpoints/private/mutations/user/deletePushToken.js +1 -1
  64. package/dist/sdk/endpoints/private/mutations/user/deleteView.d.ts +1 -1
  65. package/dist/sdk/endpoints/private/mutations/user/deleteView.js +2 -2
  66. package/dist/sdk/endpoints/private/mutations/user/setPushToken.js +1 -1
  67. package/dist/sdk/endpoints/private/mutations/user/setSettingsName.js +1 -1
  68. package/dist/sdk/endpoints/private/mutations/user/updateView.d.ts +1 -1
  69. package/dist/sdk/endpoints/private/mutations/user/updateView.js +2 -2
  70. package/dist/types/index.d.ts +0 -1
  71. package/dist/types/index.d.ts.map +1 -1
  72. package/dist/types/index.js +0 -1
  73. package/package.json +1 -2
  74. package/dist/constants/zero.d.ts +0 -5
  75. package/dist/constants/zero.d.ts.map +0 -1
  76. package/dist/constants/zero.js +0 -4
  77. package/dist/types/Zero.d.ts +0 -17
  78. package/dist/types/Zero.d.ts.map +0 -1
  79. package/dist/types/Zero.js +0 -5
  80. package/dist/zero/index.d.ts +0 -5072
  81. package/dist/zero/index.d.ts.map +0 -1
  82. package/dist/zero/index.js +0 -12
  83. package/dist/zero/mutators.d.ts +0 -1523
  84. package/dist/zero/mutators.d.ts.map +0 -1
  85. package/dist/zero/mutators.js +0 -560
  86. package/dist/zero/mutators.test.d.ts +0 -2
  87. package/dist/zero/mutators.test.d.ts.map +0 -1
  88. package/dist/zero/mutators.test.js +0 -1158
  89. package/dist/zero/queries.d.ts +0 -1372
  90. package/dist/zero/queries.d.ts.map +0 -1
  91. package/dist/zero/queries.js +0 -173
  92. package/dist/zero/schema.d.ts +0 -1821
  93. package/dist/zero/schema.d.ts.map +0 -1
  94. package/dist/zero/schema.js +0 -434
@@ -1,1158 +0,0 @@
1
- import { beforeEach, describe, expect, it, mock } from 'bun:test';
2
- import * as v from 'valibot';
3
- import { CustomViewIcon, USER_SETTINGS_CUSTOM_VIEW_ICONS } from '../types';
4
- import { mutatorSchemas } from './mutatorSchemas';
5
- import { mutators } from './mutators';
6
- const createMockTx = () => ({
7
- mutate: {
8
- account: {
9
- delete: mock(() => Promise.resolve()),
10
- insert: mock(() => Promise.resolve()),
11
- update: mock(() => Promise.resolve()),
12
- },
13
- accountAlias: {
14
- delete: mock(() => Promise.resolve()),
15
- insert: mock(() => Promise.resolve()),
16
- update: mock(() => Promise.resolve()),
17
- },
18
- accountLabel: { update: mock(() => Promise.resolve()) },
19
- draft: {
20
- delete: mock(() => Promise.resolve()),
21
- insert: mock(() => Promise.resolve()),
22
- update: mock(() => Promise.resolve()),
23
- },
24
- draftAttachment: { delete: mock(() => Promise.resolve()), insert: mock(() => Promise.resolve()) },
25
- thread: { delete: mock(() => Promise.resolve()), update: mock(() => Promise.resolve()) },
26
- threadByLabel: { delete: mock(() => Promise.resolve()), insert: mock(() => Promise.resolve()) },
27
- threadLabel: { delete: mock(() => Promise.resolve()), insert: mock(() => Promise.resolve()) },
28
- user: { update: mock(() => Promise.resolve()) },
29
- userPushNotificationToken: { delete: mock(() => Promise.resolve()), insert: mock(() => Promise.resolve()) },
30
- },
31
- run: mock(() => Promise.resolve(null)),
32
- });
33
- describe('mutators', () => {
34
- let tx;
35
- const ctx = { userId: 'test-user-id' };
36
- beforeEach(() => {
37
- tx = createMockTx();
38
- });
39
- describe('account', () => {
40
- describe('createAccount', () => {
41
- it('creates an account and primary alias', async () => {
42
- const args = {
43
- aliasId: 'test-alias-id-1',
44
- color: '#ff0000',
45
- emailAddress: 'test@example.com',
46
- id: 'test-account-id-1',
47
- };
48
- await mutators.account.createAccount.fn({ args, ctx, tx: tx });
49
- expect(tx.mutate.account.insert).toHaveBeenCalledWith({
50
- color: '#ff0000',
51
- displayName: null,
52
- id: 'test-account-id-1',
53
- imapConnectionStatus: 'AWAITING_CONNECTION',
54
- mailProcessedCount: 0,
55
- mailTotalCount: 0,
56
- primaryAliasId: 'test-alias-id-1',
57
- userId: 'test-user-id',
58
- });
59
- expect(tx.mutate.accountAlias.insert).toHaveBeenCalledWith({
60
- accountId: 'test-account-id-1',
61
- emailAddress: 'test@example.com',
62
- id: 'test-alias-id-1',
63
- isPrimary: true,
64
- name: null,
65
- });
66
- });
67
- });
68
- describe('createAlias', () => {
69
- it('creates a non-primary alias', async () => {
70
- const args = {
71
- accountId: 'test-account-id-1',
72
- alias: {
73
- emailAddress: 'alias@example.com',
74
- id: 'test-alias-id-2',
75
- name: 'test-alias-name',
76
- },
77
- };
78
- await mutators.account.createAlias.fn({ args, ctx, tx: tx });
79
- expect(tx.mutate.accountAlias.insert).toHaveBeenCalledWith({
80
- accountId: 'test-account-id-1',
81
- emailAddress: 'alias@example.com',
82
- id: 'test-alias-id-2',
83
- isPrimary: false,
84
- name: 'test-alias-name',
85
- });
86
- });
87
- it('creates an alias with null name', async () => {
88
- const args = {
89
- accountId: 'test-account-id-1',
90
- alias: {
91
- emailAddress: 'alias@example.com',
92
- id: 'test-alias-id-2',
93
- name: null,
94
- },
95
- };
96
- await mutators.account.createAlias.fn({ args, ctx, tx: tx });
97
- expect(tx.mutate.accountAlias.insert).toHaveBeenCalledWith({
98
- accountId: 'test-account-id-1',
99
- emailAddress: 'alias@example.com',
100
- id: 'test-alias-id-2',
101
- isPrimary: false,
102
- name: null,
103
- });
104
- });
105
- });
106
- describe('deleteAccount', () => {
107
- it('deletes an account', async () => {
108
- const args = { id: 'test-account-id-1' };
109
- await mutators.account.deleteAccount.fn({ args, ctx, tx: tx });
110
- expect(tx.mutate.account.delete).toHaveBeenCalledWith({ id: 'test-account-id-1' });
111
- });
112
- });
113
- describe('deleteAlias', () => {
114
- it('deletes a non-primary alias', async () => {
115
- tx.run = mock(() => Promise.resolve({ isPrimary: false }));
116
- const args = { accountId: 'test-account-id-1', aliasId: 'test-alias-id-2' };
117
- await mutators.account.deleteAlias.fn({ args, ctx, tx: tx });
118
- expect(tx.mutate.accountAlias.delete).toHaveBeenCalledWith({ id: 'test-alias-id-2' });
119
- expect(tx.mutate.accountAlias.update).not.toHaveBeenCalled();
120
- });
121
- it('promotes another alias when deleting primary', async () => {
122
- tx.run = mock()
123
- .mockResolvedValueOnce({ isPrimary: true })
124
- .mockResolvedValueOnce([{ id: 'test-alias-id-3' }]);
125
- const args = { accountId: 'test-account-id-1', aliasId: 'test-alias-id-1' };
126
- await mutators.account.deleteAlias.fn({ args, ctx, tx: tx });
127
- expect(tx.mutate.accountAlias.delete).toHaveBeenCalledWith({ id: 'test-alias-id-1' });
128
- expect(tx.mutate.accountAlias.update).toHaveBeenCalledWith({ id: 'test-alias-id-3', isPrimary: true });
129
- expect(tx.mutate.account.update).toHaveBeenCalledWith({
130
- id: 'test-account-id-1',
131
- primaryAliasId: 'test-alias-id-3',
132
- });
133
- });
134
- it('sets primaryAliasId to null when no remaining aliases', async () => {
135
- tx.run = mock().mockResolvedValueOnce({ isPrimary: true }).mockResolvedValueOnce([]);
136
- const args = { accountId: 'test-account-id-1', aliasId: 'test-alias-id-1' };
137
- await mutators.account.deleteAlias.fn({ args, ctx, tx: tx });
138
- expect(tx.mutate.account.update).toHaveBeenCalledWith({ id: 'test-account-id-1', primaryAliasId: null });
139
- });
140
- });
141
- describe('setAliasName', () => {
142
- it('updates alias name', async () => {
143
- const args = { accountId: 'test-account-id-1', aliasId: 'test-alias-id-1', displayName: 'test-alias-new-name' };
144
- await mutators.account.setAliasName.fn({ args, ctx, tx: tx });
145
- expect(tx.mutate.accountAlias.update).toHaveBeenCalledWith({
146
- id: 'test-alias-id-1',
147
- name: 'test-alias-new-name',
148
- });
149
- });
150
- });
151
- describe('setAliasPrimary', () => {
152
- it('sets a new primary alias', async () => {
153
- tx.run = mock(() => Promise.resolve([{ id: 'test-alias-id-1' }, { id: 'test-alias-id-2' }]));
154
- const args = { accountId: 'test-account-id-1', aliasId: 'test-alias-id-2' };
155
- await mutators.account.setAliasPrimary.fn({ args, ctx, tx: tx });
156
- expect(tx.mutate.accountAlias.update).toHaveBeenCalledWith({ id: 'test-alias-id-1', isPrimary: false });
157
- expect(tx.mutate.accountAlias.update).toHaveBeenCalledWith({ id: 'test-alias-id-2', isPrimary: true });
158
- expect(tx.mutate.account.update).toHaveBeenCalledWith({
159
- id: 'test-account-id-1',
160
- primaryAliasId: 'test-alias-id-2',
161
- });
162
- });
163
- });
164
- describe('setConnectionConfigImapRaw', () => {
165
- it('updates connection status to awaiting', async () => {
166
- const args = {
167
- connectionConfig: {
168
- imapHost: 'imap.example.com',
169
- imapPassword: 'test-imap-password',
170
- imapPort: 993,
171
- imapSocketType: 'SSL',
172
- imapUser: 'test-imap-user',
173
- smtpHost: 'smtp.example.com',
174
- smtpPassword: 'test-smtp-password',
175
- smtpPort: 465,
176
- smtpSocketType: 'SSL',
177
- smtpUser: 'test-smtp-user',
178
- },
179
- id: 'test-account-id-1',
180
- };
181
- await mutators.account.setConnectionConfigImapRaw.fn({ args, ctx, tx: tx });
182
- expect(tx.mutate.account.update).toHaveBeenCalledWith({
183
- id: 'test-account-id-1',
184
- imapConnectionStatus: 'AWAITING_CONNECTION',
185
- });
186
- });
187
- });
188
- describe('setConnectionConfigOauth', () => {
189
- it('updates connection status for oauth', async () => {
190
- const args = {
191
- connectionConfig: { code: 'test-auth-code', provider: 'GOOGLE', user: 'test-user@example.com' },
192
- id: 'test-account-id-1',
193
- };
194
- await mutators.account.setConnectionConfigOauth.fn({ args, ctx, tx: tx });
195
- expect(tx.mutate.account.update).toHaveBeenCalledWith({
196
- id: 'test-account-id-1',
197
- imapConnectionStatus: 'AWAITING_CONNECTION',
198
- });
199
- });
200
- });
201
- describe('setSettings', () => {
202
- it('updates account settings', async () => {
203
- const args = { color: '#00ff00', displayName: 'test-account-display-name', id: 'test-account-id-1' };
204
- await mutators.account.setSettings.fn({ args, ctx, tx: tx });
205
- expect(tx.mutate.account.update).toHaveBeenCalledWith({
206
- color: '#00ff00',
207
- displayName: 'test-account-display-name',
208
- id: 'test-account-id-1',
209
- });
210
- });
211
- });
212
- });
213
- describe('draft', () => {
214
- describe('cancelSend', () => {
215
- it('cancels a scheduled send', async () => {
216
- tx.run = mock(() => Promise.resolve({ status: 'SEND_REQUESTED' }));
217
- const args = { id: 'test-draft-id-1', updatedAt: 1234567890 };
218
- await mutators.draft.cancelSend.fn({ args, ctx, tx: tx });
219
- expect(tx.mutate.draft.update).toHaveBeenCalledWith({
220
- id: 'test-draft-id-1',
221
- scheduledFor: null,
222
- status: 'DRAFT',
223
- updatedAt: 1234567890,
224
- });
225
- });
226
- it('does nothing if draft not found', async () => {
227
- tx.run = mock(() => Promise.resolve(null));
228
- const args = { id: 'test-draft-id-1', updatedAt: 1234567890 };
229
- await mutators.draft.cancelSend.fn({ args, ctx, tx: tx });
230
- expect(tx.mutate.draft.update).not.toHaveBeenCalled();
231
- });
232
- it('does nothing if already confirmed', async () => {
233
- tx.run = mock(() => Promise.resolve({ status: 'SEND_CONFIRMED' }));
234
- const args = { id: 'test-draft-id-1', updatedAt: 1234567890 };
235
- await mutators.draft.cancelSend.fn({ args, ctx, tx: tx });
236
- expect(tx.mutate.draft.update).not.toHaveBeenCalled();
237
- });
238
- });
239
- describe('createAttachment', () => {
240
- it('creates an attachment and updates draft', async () => {
241
- const args = {
242
- attachment: {
243
- fileName: 'file.pdf',
244
- id: 'test-attachment-id-1',
245
- mimeType: 'application/pdf',
246
- status: 'PENDING',
247
- totalSize: 1024,
248
- },
249
- id: 'test-draft-id-1',
250
- updatedAt: 1234567890,
251
- };
252
- await mutators.draft.createAttachment.fn({ args, ctx, tx: tx });
253
- expect(tx.mutate.draftAttachment.insert).toHaveBeenCalledWith({
254
- draftId: 'test-draft-id-1',
255
- fileName: 'file.pdf',
256
- id: 'test-attachment-id-1',
257
- mimeType: 'application/pdf',
258
- status: 'PENDING',
259
- totalSize: 1024,
260
- });
261
- expect(tx.mutate.draft.update).toHaveBeenCalledWith({
262
- id: 'test-draft-id-1',
263
- updatedAt: 1234567890,
264
- });
265
- });
266
- });
267
- describe('createDraft', () => {
268
- it('creates a draft with attachments', async () => {
269
- const args = {
270
- accountId: 'test-account-id-1',
271
- attachments: [
272
- {
273
- fileName: 'test-file-name.pdf',
274
- id: 'test-attachment-id-1',
275
- mimeType: 'application/pdf',
276
- status: 'COMPLETE',
277
- totalSize: 1024,
278
- },
279
- ],
280
- body: {
281
- bcc: [],
282
- cc: ['test-email-1@example.com'],
283
- content: 'test-content',
284
- subject: 'test-subject',
285
- to: ['test-email-2@example.com'],
286
- },
287
- error: null,
288
- from: 'test-email-3@example.com',
289
- fromName: 'test-sender-name',
290
- id: 'test-draft-id-1',
291
- referencedMessageId: null,
292
- scheduledFor: null,
293
- status: 'DRAFT',
294
- type: 'NEW',
295
- updatedAt: 1234567890,
296
- };
297
- await mutators.draft.createDraft.fn({ args, ctx, tx: tx });
298
- expect(tx.mutate.draft.insert).toHaveBeenCalledWith({
299
- accountId: 'test-account-id-1',
300
- body: {
301
- bcc: [],
302
- cc: ['test-email-1@example.com'],
303
- content: 'test-content',
304
- to: ['test-email-2@example.com'],
305
- },
306
- error: null,
307
- fromAliasId: null,
308
- fromEmail: 'test-email-3@example.com',
309
- fromName: 'test-sender-name',
310
- id: 'test-draft-id-1',
311
- referencedMessageId: null,
312
- scheduledFor: null,
313
- status: 'DRAFT',
314
- subject: 'test-subject',
315
- type: 'NEW',
316
- updatedAt: 1234567890,
317
- userId: 'test-user-id',
318
- });
319
- expect(tx.mutate.draftAttachment.insert).toHaveBeenCalledWith({
320
- draftId: 'test-draft-id-1',
321
- fileName: 'test-file-name.pdf',
322
- id: 'test-attachment-id-1',
323
- mimeType: 'application/pdf',
324
- status: 'COMPLETE',
325
- totalSize: 1024,
326
- });
327
- });
328
- });
329
- describe('deleteAttachment', () => {
330
- it('deletes an attachment and updates draft', async () => {
331
- const args = { attachmentId: 'test-attachment-id-1', id: 'test-draft-id-1', updatedAt: 1234567890 };
332
- await mutators.draft.deleteAttachment.fn({ args, ctx, tx: tx });
333
- expect(tx.mutate.draftAttachment.delete).toHaveBeenCalledWith({ id: 'test-attachment-id-1' });
334
- expect(tx.mutate.draft.update).toHaveBeenCalledWith({ id: 'test-draft-id-1', updatedAt: 1234567890 });
335
- });
336
- });
337
- describe('deleteDraft', () => {
338
- it('deletes a draft', async () => {
339
- const args = { id: 'test-draft-id-1' };
340
- await mutators.draft.deleteDraft.fn({ args, ctx, tx: tx });
341
- expect(tx.mutate.draft.delete).toHaveBeenCalledWith({ id: 'test-draft-id-1' });
342
- });
343
- });
344
- describe('scheduleSend', () => {
345
- it('schedules an immediate send with undo delay', async () => {
346
- const args = { id: 'test-draft-id-1', kind: 'IMMEDIATE', undoMs: 5000, updatedAt: 1000000 };
347
- await mutators.draft.scheduleSend.fn({ args, ctx, tx: tx });
348
- expect(tx.mutate.draft.update).toHaveBeenCalledWith({
349
- id: 'test-draft-id-1',
350
- scheduledFor: 1005000,
351
- status: 'SEND_REQUESTED',
352
- updatedAt: 1000000,
353
- });
354
- });
355
- it('schedules a future send', async () => {
356
- const args = { id: 'test-draft-id-1', kind: 'SCHEDULED', scheduledFor: 2000000, updatedAt: 1000000 };
357
- await mutators.draft.scheduleSend.fn({ args, ctx, tx: tx });
358
- expect(tx.mutate.draft.update).toHaveBeenCalledWith({
359
- id: 'test-draft-id-1',
360
- scheduledFor: 2000000,
361
- status: 'SEND_REQUESTED',
362
- updatedAt: 1000000,
363
- });
364
- });
365
- });
366
- describe('setContent', () => {
367
- it('applies a content patch', async () => {
368
- tx.run = mock(() => Promise.resolve({ body: { bcc: [], cc: [], content: 'test-content', to: [] } }));
369
- const args = {
370
- id: 'test-draft-id-1',
371
- patch: [{ index: 12, type: 'INSERTION', value: ' test-value' }],
372
- updatedAt: 1234567890,
373
- };
374
- await mutators.draft.setContent.fn({ args, ctx, tx: tx });
375
- expect(tx.mutate.draft.update).toHaveBeenCalledWith({
376
- body: { bcc: [], cc: [], content: 'test-content test-value', to: [] },
377
- id: 'test-draft-id-1',
378
- updatedAt: 1234567890,
379
- });
380
- });
381
- it('does nothing if draft not found', async () => {
382
- tx.run = mock(() => Promise.resolve(null));
383
- const args = { id: 'test-draft-id-1', patch: [], updatedAt: 1234567890 };
384
- await mutators.draft.setContent.fn({ args, ctx, tx: tx });
385
- expect(tx.mutate.draft.update).not.toHaveBeenCalled();
386
- });
387
- });
388
- describe('setEnvelope', () => {
389
- it('updates envelope fields', async () => {
390
- tx.run = mock(() => Promise.resolve({ body: { content: 'test-content' } }));
391
- const args = {
392
- envelope: {
393
- bcc: ['test-email-1@example.com'],
394
- cc: [],
395
- subject: 'test-subject',
396
- to: ['test-email-2@example.com'],
397
- },
398
- id: 'test-draft-id-1',
399
- updatedAt: 1234567890,
400
- };
401
- await mutators.draft.setEnvelope.fn({ args, ctx, tx: tx });
402
- expect(tx.mutate.draft.update).toHaveBeenCalledWith({
403
- body: {
404
- bcc: ['test-email-1@example.com'],
405
- cc: [],
406
- content: 'test-content',
407
- to: ['test-email-2@example.com'],
408
- },
409
- id: 'test-draft-id-1',
410
- subject: 'test-subject',
411
- updatedAt: 1234567890,
412
- });
413
- });
414
- });
415
- describe('setFrom', () => {
416
- it('updates from fields', async () => {
417
- const args = {
418
- accountId: 'test-account-id-2',
419
- aliasId: 'test-alias-id-2',
420
- from: 'test-email-3@example.com',
421
- fromName: 'test-alias-new-name',
422
- id: 'test-draft-id-1',
423
- updatedAt: 1234567890,
424
- };
425
- await mutators.draft.setFrom.fn({ args, ctx, tx: tx });
426
- expect(tx.mutate.draft.update).toHaveBeenCalledWith({
427
- accountId: 'test-account-id-2',
428
- fromAliasId: 'test-alias-id-2',
429
- fromEmail: 'test-email-3@example.com',
430
- fromName: 'test-alias-new-name',
431
- id: 'test-draft-id-1',
432
- updatedAt: 1234567890,
433
- });
434
- });
435
- });
436
- });
437
- describe('thread', () => {
438
- describe('addLabel', () => {
439
- it('adds a label to threads', async () => {
440
- tx.run = mock()
441
- .mockResolvedValueOnce({ id: 'test-label-id-1', uidValidity: 1, unreadCount: 0 })
442
- .mockResolvedValueOnce({
443
- id: 'test-thread-id-1',
444
- labelIdList: ' test-label-id-2 ',
445
- latestMessageDate: 1234567890,
446
- seen: true,
447
- })
448
- .mockResolvedValueOnce([{ id: 'test-message-id-1' }])
449
- .mockResolvedValueOnce(null);
450
- const args = {
451
- accounts: { 'test-account-id-1': { threadIds: ['test-thread-id-1'] } },
452
- labelPath: 'INBOX',
453
- };
454
- await mutators.thread.addLabel.fn({ args, ctx, tx: tx });
455
- expect(tx.mutate.threadByLabel.insert).toHaveBeenCalledWith({
456
- labelId: 'test-label-id-1',
457
- latestMessageDate: 1234567890,
458
- threadId: 'test-thread-id-1',
459
- });
460
- expect(tx.mutate.threadLabel.insert).toHaveBeenCalled();
461
- expect(tx.mutate.thread.update).toHaveBeenCalledWith({
462
- id: 'test-thread-id-1',
463
- labelIdList: expect.stringContaining('test-label-id-1'),
464
- });
465
- });
466
- it('does nothing if label not found', async () => {
467
- tx.run = mock(() => Promise.resolve(null));
468
- const args = {
469
- accounts: { 'test-account-id-1': { threadIds: ['test-thread-id-1'] } },
470
- labelPath: 'NONEXISTENT',
471
- };
472
- await mutators.thread.addLabel.fn({ args, ctx, tx: tx });
473
- expect(tx.mutate.thread.update).not.toHaveBeenCalled();
474
- expect(tx.mutate.threadByLabel.insert).not.toHaveBeenCalled();
475
- });
476
- });
477
- describe('delete', () => {
478
- it('deletes threads and updates unread counts', async () => {
479
- tx.run = mock()
480
- .mockResolvedValueOnce({ id: 'test-thread-id-1', labelIdList: ' test-label-id-1 ', seen: false })
481
- .mockResolvedValueOnce([{ id: 'test-label-id-1', unreadCount: 5 }]);
482
- const args = { accounts: { 'test-account-id-1': { threadIds: ['test-thread-id-1'] } } };
483
- await mutators.thread.delete.fn({ args, ctx, tx: tx });
484
- expect(tx.mutate.accountLabel.update).toHaveBeenCalledWith({ id: 'test-label-id-1', unreadCount: 4 });
485
- expect(tx.mutate.thread.delete).toHaveBeenCalledWith({ id: 'test-thread-id-1' });
486
- });
487
- it('skips unread count update for seen threads', async () => {
488
- tx.run = mock(() => Promise.resolve({ id: 'test-thread-id-1', labelIdList: '', seen: true }));
489
- const args = { accounts: { 'test-account-id-1': { threadIds: ['test-thread-id-1'] } } };
490
- await mutators.thread.delete.fn({ args, ctx, tx: tx });
491
- expect(tx.mutate.accountLabel.update).not.toHaveBeenCalled();
492
- expect(tx.mutate.thread.delete).toHaveBeenCalledWith({ id: 'test-thread-id-1' });
493
- });
494
- });
495
- describe('removeLabel', () => {
496
- it('removes a label from threads', async () => {
497
- tx.run = mock()
498
- .mockResolvedValueOnce({ id: 'test-label-id-1', unreadCount: 5 })
499
- .mockResolvedValueOnce({
500
- id: 'test-thread-id-1',
501
- labelIdList: ' test-label-id-1 test-label-id-2 ',
502
- seen: false,
503
- })
504
- .mockResolvedValueOnce([{ id: 'test-message-id-1' }]);
505
- const args = {
506
- accounts: { 'test-account-id-1': { threadIds: ['test-thread-id-1'] } },
507
- labelPath: 'INBOX',
508
- };
509
- await mutators.thread.removeLabel.fn({ args, ctx, tx: tx });
510
- expect(tx.mutate.threadLabel.delete).toHaveBeenCalledWith({
511
- accountId: 'test-account-id-1',
512
- labelId: 'test-label-id-1',
513
- threadMessageId: 'test-message-id-1',
514
- });
515
- expect(tx.mutate.threadByLabel.delete).toHaveBeenCalledWith({
516
- labelId: 'test-label-id-1',
517
- threadId: 'test-thread-id-1',
518
- });
519
- expect(tx.mutate.accountLabel.update).toHaveBeenCalledWith({ id: 'test-label-id-1', unreadCount: 4 });
520
- });
521
- it('does nothing if thread does not have label', async () => {
522
- tx.run = mock()
523
- .mockResolvedValueOnce({ id: 'test-label-id-1' })
524
- .mockResolvedValueOnce({ id: 'test-thread-id-1', labelIdList: ' test-label-id-2 ' })
525
- .mockResolvedValueOnce([]);
526
- const args = {
527
- accounts: { 'test-account-id-1': { threadIds: ['test-thread-id-1'] } },
528
- labelPath: 'INBOX',
529
- };
530
- await mutators.thread.removeLabel.fn({ args, ctx, tx: tx });
531
- expect(tx.mutate.threadLabel.delete).not.toHaveBeenCalled();
532
- expect(tx.mutate.threadByLabel.delete).not.toHaveBeenCalled();
533
- });
534
- });
535
- describe('setFlagged', () => {
536
- it('sets flagged status', async () => {
537
- const args = {
538
- accounts: { 'test-account-id-1': { threadIds: ['test-thread-id-1', 'test-thread-id-2'] } },
539
- flagged: true,
540
- };
541
- await mutators.thread.setFlagged.fn({ args, ctx, tx: tx });
542
- expect(tx.mutate.thread.update).toHaveBeenCalledWith({ flagged: true, id: 'test-thread-id-1' });
543
- expect(tx.mutate.thread.update).toHaveBeenCalledWith({ flagged: true, id: 'test-thread-id-2' });
544
- });
545
- });
546
- describe('setSeen', () => {
547
- it('marks threads as seen and updates label counts', async () => {
548
- tx.run = mock()
549
- .mockResolvedValueOnce([{ id: 'test-thread-id-1', labelIdList: ' test-label-id-1 ', seen: false }])
550
- .mockResolvedValueOnce([{ id: 'test-label-id-1', unreadCount: 5 }]);
551
- const args = { accounts: { 'test-account-id-1': { threadIds: ['test-thread-id-1'] } }, seen: true };
552
- await mutators.thread.setSeen.fn({ args, ctx, tx: tx });
553
- expect(tx.mutate.accountLabel.update).toHaveBeenCalledWith({ id: 'test-label-id-1', unreadCount: 4 });
554
- expect(tx.mutate.thread.update).toHaveBeenCalledWith({ id: 'test-thread-id-1', seen: true });
555
- });
556
- it('marks threads as unseen and increments label counts', async () => {
557
- tx.run = mock()
558
- .mockResolvedValueOnce([{ id: 'test-thread-id-1', labelIdList: ' test-label-id-1 ', seen: true }])
559
- .mockResolvedValueOnce([{ id: 'test-label-id-1', unreadCount: 5 }]);
560
- const args = { accounts: { 'test-account-id-1': { threadIds: ['test-thread-id-1'] } }, seen: false };
561
- await mutators.thread.setSeen.fn({ args, ctx, tx: tx });
562
- expect(tx.mutate.accountLabel.update).toHaveBeenCalledWith({ id: 'test-label-id-1', unreadCount: 6 });
563
- expect(tx.mutate.thread.update).toHaveBeenCalledWith({ id: 'test-thread-id-1', seen: false });
564
- });
565
- it('skips label update when thread seen status unchanged', async () => {
566
- tx.run = mock(() => Promise.resolve([{ id: 'test-thread-id-1', labelIdList: ' test-label-id-1 ', seen: true }]));
567
- const args = { accounts: { 'test-account-id-1': { threadIds: ['test-thread-id-1'] } }, seen: true };
568
- await mutators.thread.setSeen.fn({ args, ctx, tx: tx });
569
- expect(tx.mutate.accountLabel.update).not.toHaveBeenCalled();
570
- });
571
- });
572
- const systemLabelTestCases = [
573
- { mutatorName: 'setArchive', specialUse: 'ARCHIVE' },
574
- { mutatorName: 'setInbox', specialUse: 'INBOX' },
575
- { mutatorName: 'setSpam', specialUse: 'SPAM' },
576
- { mutatorName: 'setTrash', specialUse: 'TRASH' },
577
- ];
578
- for (const { mutatorName, specialUse } of systemLabelTestCases) {
579
- describe(mutatorName, () => {
580
- it(`moves thread to ${specialUse} label`, async () => {
581
- tx.run = mock()
582
- .mockResolvedValueOnce({
583
- accountId: 'test-account-id-1',
584
- id: 'test-thread-id-1',
585
- labelIdList: ' test-label-id-1 ',
586
- latestMessageDate: 1234567890,
587
- seen: true,
588
- })
589
- .mockResolvedValueOnce({ id: 'test-label-id-2', uidValidity: 1 })
590
- .mockResolvedValueOnce([{ id: 'test-message-id-1' }])
591
- .mockResolvedValueOnce([{ id: 'test-label-id-1', unreadCount: 0 }]);
592
- const args = { accounts: { 'test-account-id-1': { threadIds: ['test-thread-id-1'] } } };
593
- await mutators.thread[mutatorName].fn({ args, ctx, tx: tx });
594
- expect(tx.mutate.threadByLabel.delete).toHaveBeenCalledWith({
595
- labelId: 'test-label-id-1',
596
- threadId: 'test-thread-id-1',
597
- });
598
- expect(tx.mutate.threadByLabel.insert).toHaveBeenCalledWith({
599
- labelId: 'test-label-id-2',
600
- latestMessageDate: 1234567890,
601
- threadId: 'test-thread-id-1',
602
- });
603
- expect(tx.mutate.thread.update).toHaveBeenCalledWith({
604
- id: 'test-thread-id-1',
605
- labelIdList: expect.stringContaining('test-label-id-2'),
606
- });
607
- });
608
- });
609
- }
610
- });
611
- describe('user', () => {
612
- describe('deleteSettingsPushNotificationToken', () => {
613
- it('deletes a push notification token', async () => {
614
- const args = { id: 'test-token-id-1', token: 'test-token-1' };
615
- await mutators.user.deleteSettingsPushNotificationToken.fn({ args, ctx, tx: tx });
616
- expect(tx.mutate.userPushNotificationToken.delete).toHaveBeenCalledWith({ id: 'test-token-id-1' });
617
- });
618
- });
619
- describe('setSettingsName', () => {
620
- it('updates user name', async () => {
621
- const args = { id: 'test-user-id-1', name: 'test-user-new-name' };
622
- await mutators.user.setSettingsName.fn({ args, ctx, tx: tx });
623
- expect(tx.mutate.user.update).toHaveBeenCalledWith({ id: 'test-user-id-1', name: 'test-user-new-name' });
624
- });
625
- });
626
- describe('setSettingsPushNotificationToken', () => {
627
- it('inserts a new push notification token', async () => {
628
- tx.run = mock(() => Promise.resolve(null));
629
- const args = {
630
- id: 'test-user-id-1',
631
- pushNotificationToken: { createdAt: 1234567890, id: 'test-token-id-1', token: 'test-token-1' },
632
- };
633
- await mutators.user.setSettingsPushNotificationToken.fn({ args, ctx, tx: tx });
634
- expect(tx.mutate.userPushNotificationToken.insert).toHaveBeenCalledWith({
635
- createdAt: 1234567890,
636
- id: 'test-token-id-1',
637
- token: 'test-token-1',
638
- userId: 'test-user-id-1',
639
- });
640
- });
641
- it('skips insert if token already exists', async () => {
642
- tx.run = mock(() => Promise.resolve({ id: 'existing-token' }));
643
- const args = {
644
- id: 'test-user-id-1',
645
- pushNotificationToken: { createdAt: 1234567890, id: 'test-token-id-1', token: 'test-token-1' },
646
- };
647
- await mutators.user.setSettingsPushNotificationToken.fn({ args, ctx, tx: tx });
648
- expect(tx.mutate.userPushNotificationToken.insert).not.toHaveBeenCalled();
649
- });
650
- });
651
- describe('createView', () => {
652
- it('creates a new view', async () => {
653
- tx.run = mock(() => Promise.resolve({ id: 'test-user-id-1', views: [] }));
654
- const args = {
655
- id: 'test-user-id-1',
656
- view: {
657
- aliasEmails: ['test-email-1@example.com', 'test-email-2@example.com'],
658
- icon: CustomViewIcon.STAR,
659
- id: 'test-view-id-1',
660
- name: 'test-view-name-1',
661
- },
662
- };
663
- await mutators.user.createView.fn({ args, ctx, tx: tx });
664
- expect(tx.mutate.user.update).toHaveBeenCalledWith({
665
- id: 'test-user-id-1',
666
- views: [
667
- {
668
- aliasEmails: ['test-email-1@example.com', 'test-email-2@example.com'],
669
- icon: CustomViewIcon.STAR,
670
- id: 'test-view-id-1',
671
- name: 'test-view-name-1',
672
- },
673
- ],
674
- });
675
- });
676
- it('deduplicates aliasEmails', async () => {
677
- tx.run = mock(() => Promise.resolve({ id: 'test-user-id-1', views: [] }));
678
- const args = {
679
- id: 'test-user-id-1',
680
- view: {
681
- aliasEmails: ['test-email-1@example.com', 'test-email-2@example.com', 'test-email-1@example.com'],
682
- icon: CustomViewIcon.STAR,
683
- id: 'test-view-id-1',
684
- name: 'test-view-name-1',
685
- },
686
- };
687
- await mutators.user.createView.fn({ args, ctx, tx: tx });
688
- expect(tx.mutate.user.update).toHaveBeenCalledWith({
689
- id: 'test-user-id-1',
690
- views: [
691
- {
692
- aliasEmails: ['test-email-1@example.com', 'test-email-2@example.com'],
693
- icon: CustomViewIcon.STAR,
694
- id: 'test-view-id-1',
695
- name: 'test-view-name-1',
696
- },
697
- ],
698
- });
699
- });
700
- it('appends to existing views', async () => {
701
- tx.run = mock(() => Promise.resolve({
702
- id: 'test-user-id-1',
703
- views: [
704
- {
705
- aliasEmails: ['test-email-1@example.com', 'test-email-1b@example.com'],
706
- icon: CustomViewIcon.STAR,
707
- id: 'test-view-id-1',
708
- name: 'test-view-name-1',
709
- },
710
- ],
711
- }));
712
- const args = {
713
- id: 'test-user-id-1',
714
- view: {
715
- aliasEmails: ['test-email-2@example.com', 'test-email-2b@example.com'],
716
- icon: CustomViewIcon.STAR,
717
- id: 'test-view-id-2',
718
- name: 'test-view-name-2',
719
- },
720
- };
721
- await mutators.user.createView.fn({ args, ctx, tx: tx });
722
- expect(tx.mutate.user.update).toHaveBeenCalledWith({
723
- id: 'test-user-id-1',
724
- views: [
725
- {
726
- aliasEmails: ['test-email-1@example.com', 'test-email-1b@example.com'],
727
- icon: CustomViewIcon.STAR,
728
- id: 'test-view-id-1',
729
- name: 'test-view-name-1',
730
- },
731
- {
732
- aliasEmails: ['test-email-2@example.com', 'test-email-2b@example.com'],
733
- icon: CustomViewIcon.STAR,
734
- id: 'test-view-id-2',
735
- name: 'test-view-name-2',
736
- },
737
- ],
738
- });
739
- });
740
- it('does nothing if user not found', async () => {
741
- tx.run = mock(() => Promise.resolve(null));
742
- const args = {
743
- id: 'test-user-id-1',
744
- view: {
745
- aliasEmails: ['test-email-1@example.com', 'test-email-1b@example.com'],
746
- icon: CustomViewIcon.STAR,
747
- id: 'test-view-id-1',
748
- name: 'test-view-name-1',
749
- },
750
- };
751
- await mutators.user.createView.fn({ args, ctx, tx: tx });
752
- expect(tx.mutate.user.update).not.toHaveBeenCalled();
753
- });
754
- it('does nothing if view with same id already exists', async () => {
755
- tx.run = mock(() => Promise.resolve({
756
- id: 'test-user-id-1',
757
- views: [
758
- {
759
- aliasEmails: ['test-email-1@example.com', 'test-email-1b@example.com'],
760
- icon: CustomViewIcon.STAR,
761
- id: 'test-view-id-1',
762
- name: 'test-view-name-1',
763
- },
764
- ],
765
- }));
766
- const args = {
767
- id: 'test-user-id-1',
768
- view: {
769
- aliasEmails: ['test-email-2@example.com', 'test-email-2b@example.com'],
770
- icon: CustomViewIcon.STAR,
771
- id: 'test-view-id-1',
772
- name: 'test-view-name-2',
773
- },
774
- };
775
- await mutators.user.createView.fn({ args, ctx, tx: tx });
776
- expect(tx.mutate.user.update).not.toHaveBeenCalled();
777
- });
778
- it('does nothing if max views limit reached', async () => {
779
- const existingViews = Array.from({ length: 25 }, (_, i) => ({
780
- aliasEmails: [`test-email-${i}@example.com`, `test-email-${i}b@example.com`],
781
- icon: CustomViewIcon.STAR,
782
- id: `test-view-id-${i}`,
783
- name: `test-view-name-${i}`,
784
- }));
785
- tx.run = mock(() => Promise.resolve({ id: 'test-user-id-1', views: existingViews }));
786
- const args = {
787
- id: 'test-user-id-1',
788
- view: {
789
- aliasEmails: ['test-email-1@example.com', 'test-email-1b@example.com'],
790
- icon: CustomViewIcon.STAR,
791
- id: 'test-view-id-1',
792
- name: 'test-view-name-1',
793
- },
794
- };
795
- await mutators.user.createView.fn({ args, ctx, tx: tx });
796
- expect(tx.mutate.user.update).not.toHaveBeenCalled();
797
- });
798
- });
799
- describe('deleteView', () => {
800
- it('deletes a view', async () => {
801
- tx.run = mock(() => Promise.resolve({
802
- id: 'test-user-id-1',
803
- views: [
804
- {
805
- aliasEmails: ['test-email-1@example.com', 'test-email-1b@example.com'],
806
- icon: CustomViewIcon.STAR,
807
- id: 'test-view-id-1',
808
- name: 'test-view-name-1',
809
- },
810
- {
811
- aliasEmails: ['test-email-2@example.com', 'test-email-2b@example.com'],
812
- icon: CustomViewIcon.STAR,
813
- id: 'test-view-id-2',
814
- name: 'test-view-name-2',
815
- },
816
- ],
817
- }));
818
- const args = { id: 'test-user-id-1', viewId: 'test-view-id-1' };
819
- await mutators.user.deleteView.fn({ args, ctx, tx: tx });
820
- expect(tx.mutate.user.update).toHaveBeenCalledWith({
821
- id: 'test-user-id-1',
822
- views: [
823
- {
824
- aliasEmails: ['test-email-2@example.com', 'test-email-2b@example.com'],
825
- icon: CustomViewIcon.STAR,
826
- id: 'test-view-id-2',
827
- name: 'test-view-name-2',
828
- },
829
- ],
830
- });
831
- });
832
- it('does nothing if user not found', async () => {
833
- tx.run = mock(() => Promise.resolve(null));
834
- const args = { id: 'test-user-id-1', viewId: 'test-view-id-1' };
835
- await mutators.user.deleteView.fn({ args, ctx, tx: tx });
836
- expect(tx.mutate.user.update).not.toHaveBeenCalled();
837
- });
838
- });
839
- describe('updateView', () => {
840
- it('updates view name', async () => {
841
- tx.run = mock(() => Promise.resolve({
842
- id: 'test-user-id-1',
843
- views: [
844
- {
845
- aliasEmails: ['test-email-1@example.com', 'test-email-1b@example.com'],
846
- icon: CustomViewIcon.STAR,
847
- id: 'test-view-id-1',
848
- name: 'test-view-name-1',
849
- },
850
- ],
851
- }));
852
- const args = {
853
- id: 'test-user-id-1',
854
- view: {
855
- aliasEmails: ['test-email-1@example.com', 'test-email-1b@example.com'],
856
- icon: CustomViewIcon.STAR,
857
- id: 'test-view-id-1',
858
- name: 'test-updated-name',
859
- },
860
- };
861
- await mutators.user.updateView.fn({ args, ctx, tx: tx });
862
- expect(tx.mutate.user.update).toHaveBeenCalledWith({
863
- id: 'test-user-id-1',
864
- views: [
865
- {
866
- aliasEmails: ['test-email-1@example.com', 'test-email-1b@example.com'],
867
- icon: CustomViewIcon.STAR,
868
- id: 'test-view-id-1',
869
- name: 'test-updated-name',
870
- },
871
- ],
872
- });
873
- });
874
- it('updates view aliasEmails', async () => {
875
- tx.run = mock(() => Promise.resolve({
876
- id: 'test-user-id-1',
877
- views: [
878
- {
879
- aliasEmails: ['test-email-1@example.com', 'test-email-1b@example.com'],
880
- icon: CustomViewIcon.STAR,
881
- id: 'test-view-id-1',
882
- name: 'test-view-name-1',
883
- },
884
- ],
885
- }));
886
- const args = {
887
- id: 'test-user-id-1',
888
- view: {
889
- aliasEmails: ['test-email-1@example.com', 'test-email-2@example.com'],
890
- icon: CustomViewIcon.STAR,
891
- id: 'test-view-id-1',
892
- name: 'test-view-name-1',
893
- },
894
- };
895
- await mutators.user.updateView.fn({ args, ctx, tx: tx });
896
- expect(tx.mutate.user.update).toHaveBeenCalledWith({
897
- id: 'test-user-id-1',
898
- views: [
899
- {
900
- aliasEmails: ['test-email-1@example.com', 'test-email-2@example.com'],
901
- icon: CustomViewIcon.STAR,
902
- id: 'test-view-id-1',
903
- name: 'test-view-name-1',
904
- },
905
- ],
906
- });
907
- });
908
- it('deduplicates aliasEmails on update', async () => {
909
- tx.run = mock(() => Promise.resolve({
910
- id: 'test-user-id-1',
911
- views: [
912
- {
913
- aliasEmails: ['test-email-1@example.com', 'test-email-1b@example.com'],
914
- icon: CustomViewIcon.STAR,
915
- id: 'test-view-id-1',
916
- name: 'test-view-name-1',
917
- },
918
- ],
919
- }));
920
- const args = {
921
- id: 'test-user-id-1',
922
- view: {
923
- aliasEmails: ['test-email-1@example.com', 'test-email-2@example.com', 'test-email-1@example.com'],
924
- icon: CustomViewIcon.STAR,
925
- id: 'test-view-id-1',
926
- name: 'test-view-name-1',
927
- },
928
- };
929
- await mutators.user.updateView.fn({ args, ctx, tx: tx });
930
- expect(tx.mutate.user.update).toHaveBeenCalledWith({
931
- id: 'test-user-id-1',
932
- views: [
933
- {
934
- aliasEmails: ['test-email-1@example.com', 'test-email-2@example.com'],
935
- icon: CustomViewIcon.STAR,
936
- id: 'test-view-id-1',
937
- name: 'test-view-name-1',
938
- },
939
- ],
940
- });
941
- });
942
- it('updates view icon', async () => {
943
- tx.run = mock(() => Promise.resolve({
944
- id: 'test-user-id-1',
945
- views: [
946
- {
947
- aliasEmails: ['test-email-1@example.com', 'test-email-1b@example.com'],
948
- icon: CustomViewIcon.STAR,
949
- id: 'test-view-id-1',
950
- name: 'test-view-name-1',
951
- },
952
- ],
953
- }));
954
- const args = {
955
- id: 'test-user-id-1',
956
- view: {
957
- aliasEmails: ['test-email-1@example.com', 'test-email-1b@example.com'],
958
- icon: CustomViewIcon.HEART,
959
- id: 'test-view-id-1',
960
- name: 'test-view-name-1',
961
- },
962
- };
963
- await mutators.user.updateView.fn({ args, ctx, tx: tx });
964
- expect(tx.mutate.user.update).toHaveBeenCalledWith({
965
- id: 'test-user-id-1',
966
- views: [
967
- {
968
- aliasEmails: ['test-email-1@example.com', 'test-email-1b@example.com'],
969
- icon: CustomViewIcon.HEART,
970
- id: 'test-view-id-1',
971
- name: 'test-view-name-1',
972
- },
973
- ],
974
- });
975
- });
976
- it('updates both name and aliasEmails', async () => {
977
- tx.run = mock(() => Promise.resolve({
978
- id: 'test-user-id-1',
979
- views: [
980
- {
981
- aliasEmails: ['test-email-1@example.com', 'test-email-1b@example.com'],
982
- icon: CustomViewIcon.STAR,
983
- id: 'test-view-id-1',
984
- name: 'test-view-name-1',
985
- },
986
- ],
987
- }));
988
- const args = {
989
- id: 'test-user-id-1',
990
- view: {
991
- aliasEmails: ['test-email-1@example.com', 'test-email-1b@example.com'],
992
- icon: CustomViewIcon.STAR,
993
- id: 'test-view-id-1',
994
- name: 'test-updated-name',
995
- },
996
- };
997
- await mutators.user.updateView.fn({ args, ctx, tx: tx });
998
- expect(tx.mutate.user.update).toHaveBeenCalledWith({
999
- id: 'test-user-id-1',
1000
- views: [
1001
- {
1002
- aliasEmails: ['test-email-1@example.com', 'test-email-1b@example.com'],
1003
- icon: CustomViewIcon.STAR,
1004
- id: 'test-view-id-1',
1005
- name: 'test-updated-name',
1006
- },
1007
- ],
1008
- });
1009
- });
1010
- it('does nothing if user not found', async () => {
1011
- tx.run = mock(() => Promise.resolve(null));
1012
- const args = {
1013
- id: 'test-user-id-1',
1014
- view: {
1015
- aliasEmails: ['test-email-1@example.com', 'test-email-1b@example.com'],
1016
- icon: CustomViewIcon.STAR,
1017
- id: 'test-view-id-1',
1018
- name: 'test-updated-name',
1019
- },
1020
- };
1021
- await mutators.user.updateView.fn({ args, ctx, tx: tx });
1022
- expect(tx.mutate.user.update).not.toHaveBeenCalled();
1023
- });
1024
- it('does nothing if view does not exist', async () => {
1025
- tx.run = mock(() => Promise.resolve({
1026
- id: 'test-user-id-1',
1027
- views: [
1028
- {
1029
- aliasEmails: ['test-email-1@example.com', 'test-email-1b@example.com'],
1030
- icon: CustomViewIcon.STAR,
1031
- id: 'test-view-id-1',
1032
- name: 'test-view-name-1',
1033
- },
1034
- ],
1035
- }));
1036
- const args = {
1037
- id: 'test-user-id-1',
1038
- view: {
1039
- aliasEmails: ['test-email-1@example.com', 'test-email-1b@example.com'],
1040
- icon: CustomViewIcon.STAR,
1041
- id: 'test-view-id-nonexistent',
1042
- name: 'test-updated-name',
1043
- },
1044
- };
1045
- await mutators.user.updateView.fn({ args, ctx, tx: tx });
1046
- expect(tx.mutate.user.update).not.toHaveBeenCalled();
1047
- });
1048
- it('leaves non-matching views unchanged', async () => {
1049
- tx.run = mock(() => Promise.resolve({
1050
- id: 'test-user-id-1',
1051
- views: [
1052
- {
1053
- aliasEmails: ['test-email-1@example.com', 'test-email-1b@example.com'],
1054
- icon: CustomViewIcon.STAR,
1055
- id: 'test-view-id-1',
1056
- name: 'test-view-name-1',
1057
- },
1058
- {
1059
- aliasEmails: ['test-email-2@example.com', 'test-email-2b@example.com'],
1060
- icon: CustomViewIcon.STAR,
1061
- id: 'test-view-id-2',
1062
- name: 'test-view-name-2',
1063
- },
1064
- ],
1065
- }));
1066
- const args = {
1067
- id: 'test-user-id-1',
1068
- view: {
1069
- aliasEmails: ['test-email-1@example.com', 'test-email-1b@example.com'],
1070
- icon: CustomViewIcon.STAR,
1071
- id: 'test-view-id-1',
1072
- name: 'test-view-updated-name',
1073
- },
1074
- };
1075
- await mutators.user.updateView.fn({ args, ctx, tx: tx });
1076
- expect(tx.mutate.user.update).toHaveBeenCalledWith({
1077
- id: 'test-user-id-1',
1078
- views: [
1079
- {
1080
- aliasEmails: ['test-email-1@example.com', 'test-email-1b@example.com'],
1081
- icon: CustomViewIcon.STAR,
1082
- id: 'test-view-id-1',
1083
- name: 'test-view-updated-name',
1084
- },
1085
- {
1086
- aliasEmails: ['test-email-2@example.com', 'test-email-2b@example.com'],
1087
- icon: CustomViewIcon.STAR,
1088
- id: 'test-view-id-2',
1089
- name: 'test-view-name-2',
1090
- },
1091
- ],
1092
- });
1093
- });
1094
- it('schema rejects empty aliasEmails array', () => {
1095
- const args = {
1096
- id: 'test-user-id-1',
1097
- view: { aliasEmails: [], icon: CustomViewIcon.STAR, id: 'test-view-id-1', name: 'test-view-name-1' },
1098
- };
1099
- const result = v.safeParse(mutatorSchemas.user.updateView, args);
1100
- expect(result.success).toBe(false);
1101
- });
1102
- it('schema rejects aliasEmails array with only 1 email', () => {
1103
- const args = {
1104
- id: 'test-user-id-1',
1105
- view: {
1106
- aliasEmails: ['test-email-1@example.com'],
1107
- icon: CustomViewIcon.STAR,
1108
- id: 'test-view-id-1',
1109
- name: 'test-view-name-1',
1110
- },
1111
- };
1112
- const result = v.safeParse(mutatorSchemas.user.updateView, args);
1113
- expect(result.success).toBe(false);
1114
- });
1115
- for (const icon of USER_SETTINGS_CUSTOM_VIEW_ICONS) {
1116
- it(`schema accepts valid icon value: ${icon}`, () => {
1117
- const args = {
1118
- id: 'test-user-id-1',
1119
- view: {
1120
- aliasEmails: ['test-email-1@example.com', 'test-email-1b@example.com'],
1121
- icon,
1122
- id: 'test-view-id-1',
1123
- name: 'test-view-name-1',
1124
- },
1125
- };
1126
- const result = v.safeParse(mutatorSchemas.user.updateView, args);
1127
- expect(result.success).toBe(true);
1128
- });
1129
- }
1130
- it('schema rejects null icon value', () => {
1131
- const args = {
1132
- id: 'test-user-id-1',
1133
- view: {
1134
- aliasEmails: ['test-email-1@example.com', 'test-email-1b@example.com'],
1135
- icon: null,
1136
- id: 'test-view-id-1',
1137
- name: 'test-view-name-1',
1138
- },
1139
- };
1140
- const result = v.safeParse(mutatorSchemas.user.updateView, args);
1141
- expect(result.success).toBe(false);
1142
- });
1143
- it('schema rejects invalid icon value', () => {
1144
- const args = {
1145
- id: 'test-user-id-1',
1146
- view: {
1147
- aliasEmails: ['test-email-1@example.com', 'test-email-1b@example.com'],
1148
- icon: 'INVALID_ICON',
1149
- id: 'test-view-id-1',
1150
- name: 'test-view-name-1',
1151
- },
1152
- };
1153
- const result = v.safeParse(mutatorSchemas.user.updateView, args);
1154
- expect(result.success).toBe(false);
1155
- });
1156
- });
1157
- });
1158
- });