@marcoappio/marco-config 2.0.530 → 2.0.532

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