summaryrefslogtreecommitdiffstats
path: root/comm/mail/components/extensions/test/xpcshell/test_ext_folders.js
blob: 39a8d63016afb10ea57e5175acf541652c98b7d4 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
/* This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this
 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */

"use strict";

var { ExtensionTestUtils } = ChromeUtils.importESModule(
  "resource://testing-common/ExtensionXPCShellUtils.sys.mjs"
);

add_task(
  {
    skip_if: () => IS_NNTP,
  },
  async function test_folders() {
    let files = {
      "background.js": async () => {
        let [accountId, IS_IMAP] = await window.waitForMessage();

        let account = await browser.accounts.get(accountId);
        browser.test.assertEq(3, account.folders.length);

        // Test create.

        let onCreatedPromise = window.waitForEvent("folders.onCreated");
        let folder1 = await browser.folders.create(account, "folder1");
        let [createdFolder] = await onCreatedPromise;
        for (let folder of [folder1, createdFolder]) {
          browser.test.assertEq(accountId, folder.accountId);
          browser.test.assertEq("folder1", folder.name);
          browser.test.assertEq("/folder1", folder.path);
        }

        account = await browser.accounts.get(accountId);
        // Check order of the returned folders being correct (new folder not last).
        browser.test.assertEq(4, account.folders.length);
        if (IS_IMAP) {
          browser.test.assertEq("Inbox", account.folders[0].name);
          browser.test.assertEq("Trash", account.folders[1].name);
        } else {
          browser.test.assertEq("Trash", account.folders[0].name);
          browser.test.assertEq("Outbox", account.folders[1].name);
        }
        browser.test.assertEq("folder1", account.folders[2].name);
        browser.test.assertEq("unused", account.folders[3].name);

        let folder2 = await browser.folders.create(folder1, "folder+2");
        browser.test.assertEq(accountId, folder2.accountId);
        browser.test.assertEq("folder+2", folder2.name);
        browser.test.assertEq("/folder1/folder+2", folder2.path);

        account = await browser.accounts.get(accountId);
        browser.test.assertEq(4, account.folders.length);
        browser.test.assertEq(1, account.folders[2].subFolders.length);
        browser.test.assertEq(
          "/folder1/folder+2",
          account.folders[2].subFolders[0].path
        );

        // Test reject on creating already existing folder.
        await browser.test.assertRejects(
          browser.folders.create(folder1, "folder+2"),
          `folders.create() failed, because folder+2 already exists in /folder1`,
          "browser.folders.create threw exception"
        );

        // Test rename.

        {
          let onRenamedPromise = window.waitForEvent("folders.onRenamed");
          let folder3 = await browser.folders.rename(
            { accountId, path: "/folder1/folder+2" },
            "folder3"
          );
          let [originalFolder, renamedFolder] = await onRenamedPromise;
          // Test the original folder.
          browser.test.assertEq(accountId, originalFolder.accountId);
          browser.test.assertEq("folder+2", originalFolder.name);
          browser.test.assertEq("/folder1/folder+2", originalFolder.path);
          // Test the renamed folder.
          for (let folder of [folder3, renamedFolder]) {
            browser.test.assertEq(accountId, folder.accountId);
            browser.test.assertEq("folder3", folder.name);
            browser.test.assertEq("/folder1/folder3", folder.path);
          }

          account = await browser.accounts.get(accountId);
          browser.test.assertEq(4, account.folders.length);
          browser.test.assertEq(1, account.folders[2].subFolders.length);
          browser.test.assertEq(
            "/folder1/folder3",
            account.folders[2].subFolders[0].path
          );

          // Test reject on renaming absolute root.
          await browser.test.assertRejects(
            browser.folders.rename({ accountId, path: "/" }, "UhhOh"),
            `folders.rename() failed, because it cannot rename the root of the account`,
            "browser.folders.rename threw exception"
          );

          // Test reject on renaming to existing folder.
          await browser.test.assertRejects(
            browser.folders.rename(
              { accountId, path: "/folder1/folder3" },
              "folder3"
            ),
            `folders.rename() failed, because folder3 already exists in /folder1`,
            "browser.folders.rename threw exception"
          );
        }

        // Test delete (and onMoved).

        {
          // The delete request will trigger an onDelete event for IMAP and an
          // onMoved event for local folders.
          let deletePromise = window.waitForEvent(
            `folders.${IS_IMAP ? "onDeleted" : "onMoved"}`
          );
          await browser.folders.delete({ accountId, path: "/folder1/folder3" });
          // The onMoved event returns the original/deleted and the new folder.
          // The onDeleted event returns just the original/deleted folder.
          let [originalFolder, folderMovedToTrash] = await deletePromise;

          // Test the originalFolder folder.
          browser.test.assertEq(accountId, originalFolder.accountId);
          browser.test.assertEq("folder3", originalFolder.name);
          browser.test.assertEq("/folder1/folder3", originalFolder.path);

          // Check if it really is in trash folder.
          account = await browser.accounts.get(accountId);
          browser.test.assertEq(4, account.folders.length);
          let trashFolder = account.folders.find(f => f.name == "Trash");
          browser.test.assertTrue(trashFolder);
          browser.test.assertEq("/Trash", trashFolder.path);
          browser.test.assertEq(1, trashFolder.subFolders.length);
          browser.test.assertEq(
            "/Trash/folder3",
            trashFolder.subFolders[0].path
          );
          browser.test.assertEq("/folder1", account.folders[2].path);

          if (!IS_IMAP) {
            // For non IMAP folders, the delete request has triggered an onMoved
            // event, check if that has reported moving the folder to trash.
            browser.test.assertEq(accountId, folderMovedToTrash.accountId);
            browser.test.assertEq("folder3", folderMovedToTrash.name);
            browser.test.assertEq("/Trash/folder3", folderMovedToTrash.path);

            // Delete the folder from trash.
            let onDeletedPromise = window.waitForEvent("folders.onDeleted");
            await browser.folders.delete({ accountId, path: "/Trash/folder3" });
            let [deletedFolder] = await onDeletedPromise;
            browser.test.assertEq(accountId, deletedFolder.accountId);
            browser.test.assertEq("folder3", deletedFolder.name);
            browser.test.assertEq("/Trash/folder3", deletedFolder.path);
            // Check if the folder is gone.
            let trashSubfolders = await browser.folders.getSubFolders(
              trashFolder,
              false
            );
            browser.test.assertEq(
              0,
              trashSubfolders.length,
              "Folder has been deleted from trash."
            );
          } else {
            // The IMAP test server signals success for the delete request, but
            // keeps the folder. Testing for this broken behavior to get notified
            // via test fails, if this behaviour changes.
            await browser.folders.delete({ accountId, path: "/Trash/folder3" });
            let trashSubfolders = await browser.folders.getSubFolders(
              trashFolder,
              false
            );
            browser.test.assertEq(
              "/Trash/folder3",
              trashSubfolders[0].path,
              "IMAP test server cannot delete from trash, the folder is still there."
            );
          }

          // Test reject on deleting non-existing folder.
          await browser.test.assertRejects(
            browser.folders.delete({ accountId, path: "/folder1/folder5" }),
            `Folder not found: /folder1/folder5`,
            "browser.folders.delete threw exception"
          );

          account = await browser.accounts.get(accountId);
          browser.test.assertEq(4, account.folders.length);
          browser.test.assertEq("/folder1", account.folders[2].path);
        }

        // Test move.

        {
          await browser.folders.create(folder1, "folder4");
          let onMovedPromise = window.waitForEvent("folders.onMoved");
          let folder4_moved = await browser.folders.move(
            { accountId, path: "/folder1/folder4" },
            { accountId, path: "/" }
          );
          let [originalFolder, movedFolder] = await onMovedPromise;
          // Test the original folder.
          browser.test.assertEq(accountId, originalFolder.accountId);
          browser.test.assertEq("folder4", originalFolder.name);
          browser.test.assertEq("/folder1/folder4", originalFolder.path);
          // Test the moved folder.
          for (let folder of [folder4_moved, movedFolder]) {
            browser.test.assertEq(accountId, folder.accountId);
            browser.test.assertEq("folder4", folder.name);
            browser.test.assertEq("/folder4", folder.path);
          }

          account = await browser.accounts.get(accountId);
          browser.test.assertEq(5, account.folders.length);
          browser.test.assertEq("/folder4", account.folders[3].path);

          // Test reject on moving to already existing folder.
          await browser.test.assertRejects(
            browser.folders.move(folder4_moved, account),
            `folders.move() failed, because folder4 already exists in /`,
            "browser.folders.move threw exception"
          );
        }

        // Test copy.

        {
          let onCopiedPromise = window.waitForEvent("folders.onCopied");
          let folder4_copied = await browser.folders.copy(
            { accountId, path: "/folder4" },
            { accountId, path: "/folder1" }
          );
          let [originalFolder, copiedFolder] = await onCopiedPromise;
          // Test the original folder.
          browser.test.assertEq(accountId, originalFolder.accountId);
          browser.test.assertEq("folder4", originalFolder.name);
          browser.test.assertEq("/folder4", originalFolder.path);
          // Test the copied folder.
          for (let folder of [folder4_copied, copiedFolder]) {
            browser.test.assertEq(accountId, folder.accountId);
            browser.test.assertEq("folder4", folder.name);
            browser.test.assertEq("/folder1/folder4", folder.path);
          }

          account = await browser.accounts.get(accountId);
          browser.test.assertEq(5, account.folders.length);
          browser.test.assertEq(1, account.folders[2].subFolders.length);
          browser.test.assertEq("/folder4", account.folders[3].path);
          browser.test.assertEq(
            "/folder1/folder4",
            account.folders[2].subFolders[0].path
          );

          // Test reject on copy to already existing folder.
          await browser.test.assertRejects(
            browser.folders.copy(folder4_copied, folder1),
            `folders.copy() failed, because folder4 already exists in /folder1`,
            "browser.folders.copy threw exception"
          );
        }

        browser.test.notifyPass("finished");
      },
      "utils.js": await getUtilsJS(),
    };
    let extension = ExtensionTestUtils.loadExtension({
      files,
      manifest: {
        background: { scripts: ["utils.js", "background.js"] },
        permissions: ["accountsRead", "accountsFolders", "messagesDelete"],
      },
    });

    let account = createAccount();
    // Not all folders appear immediately on IMAP. Creating a new one causes them to appear.
    await createSubfolder(account.incomingServer.rootFolder, "unused");

    // We should now have three folders. For IMAP accounts they are Inbox, Trash,
    // and unused. Otherwise they are Trash, Unsent Messages and unused.

    await extension.startup();
    extension.sendMessage(account.key, IS_IMAP);
    await extension.awaitFinish("finished");
    await extension.unload();
  }
);

add_task(
  {
    skip_if: () => IS_NNTP,
  },
  async function test_without_delete_permission() {
    let files = {
      "background.js": async () => {
        let [accountId] = await window.waitForMessage();

        // Test reject on delete without messagesDelete permission.
        await browser.test.assertRejects(
          browser.folders.delete({ accountId, path: "/unused" }),
          `Using folders.delete() requires the "accountsFolders" and the "messagesDelete" permission`,
          "It rejects for a missing permission."
        );

        browser.test.notifyPass("finished");
      },
      "utils.js": await getUtilsJS(),
    };
    let extension = ExtensionTestUtils.loadExtension({
      files,
      manifest: {
        background: { scripts: ["utils.js", "background.js"] },
        permissions: ["accountsRead", "accountsFolders"],
      },
    });

    let account = createAccount();
    // Not all folders appear immediately on IMAP. Creating a new one causes them to appear.
    await createSubfolder(account.incomingServer.rootFolder, "unused");

    // We should now have three folders. For IMAP accounts they are Inbox,
    // Trash, and unused. Otherwise they are Trash, Unsent Messages and unused.
    await extension.startup();
    extension.sendMessage(account.key);
    await extension.awaitFinish("finished");
    await extension.unload();
  }
);

add_task(async function test_getParentFolders_getSubFolders() {
  let files = {
    "background.js": async () => {
      let [accountId] = await window.waitForMessage();
      let account = await browser.accounts.get(accountId);

      async function createSubFolder(folderOrAccount, name) {
        let subFolder = await browser.folders.create(folderOrAccount, name);
        let basePath = folderOrAccount.path || "/";
        if (!basePath.endsWith("/")) {
          basePath = basePath + "/";
        }
        browser.test.assertEq(accountId, subFolder.accountId);
        browser.test.assertEq(name, subFolder.name);
        browser.test.assertEq(`${basePath}${name}`, subFolder.path);
        return subFolder;
      }

      // Create a new root folder in the account.
      let root = await createSubFolder(account, "MyRoot");

      // Build a flat list of newly created nested folders in MyRoot.
      let flatFolders = [root];
      for (let i = 0; i < 10; i++) {
        flatFolders.push(await createSubFolder(flatFolders[i], `level${i}`));
      }

      // Test getParentFolders().

      // Pop out the last child folder and get its parents.
      let lastChild = flatFolders.pop();
      let parentsWithSubDefault = await browser.folders.getParentFolders(
        lastChild
      );
      let parentsWithSubFalse = await browser.folders.getParentFolders(
        lastChild,
        false
      );
      let parentsWithSubTrue = await browser.folders.getParentFolders(
        lastChild,
        true
      );

      browser.test.assertEq(10, parentsWithSubDefault.length, "Correct depth.");
      browser.test.assertEq(10, parentsWithSubFalse.length, "Correct depth.");
      browser.test.assertEq(10, parentsWithSubTrue.length, "Correct depth.");

      // Reverse the flatFolders array, to match the expected return value of
      // getParentFolders().
      flatFolders.reverse();

      // Build expected nested subfolder structure.
      lastChild.subFolders = [];
      let flatFoldersWithSub = [];
      for (let i = 0; i < 10; i++) {
        let f = {};
        Object.assign(f, flatFolders[i]);
        if (i == 0) {
          f.subFolders = [lastChild];
        } else {
          f.subFolders = [flatFoldersWithSub[i - 1]];
        }
        flatFoldersWithSub.push(f);
      }

      // Test return values of getParentFolders(). The way the flatFolder array
      // has been created, its entries do not have subFolder properties.
      for (let i = 0; i < 10; i++) {
        window.assertDeepEqual(parentsWithSubFalse[i], flatFolders[i]);
        window.assertDeepEqual(flatFolders[i], parentsWithSubFalse[i]);

        window.assertDeepEqual(parentsWithSubTrue[i], flatFoldersWithSub[i]);
        window.assertDeepEqual(flatFoldersWithSub[i], parentsWithSubTrue[i]);

        // Default = false
        window.assertDeepEqual(parentsWithSubDefault[i], flatFolders[i]);
        window.assertDeepEqual(flatFolders[i], parentsWithSubDefault[i]);
      }

      // Test getSubFolders().

      let expectedSubsWithSub = [flatFoldersWithSub[8]];
      let expectedSubsWithoutSub = [flatFolders[8]];

      // Test excluding subfolders (so only the direct subfolder are reported).
      let subsWithSubFalse = await browser.folders.getSubFolders(root, false);
      window.assertDeepEqual(expectedSubsWithoutSub, subsWithSubFalse);
      window.assertDeepEqual(subsWithSubFalse, expectedSubsWithoutSub);

      // Test including all subfolders.
      let subsWithSubTrue = await browser.folders.getSubFolders(root, true);
      window.assertDeepEqual(expectedSubsWithSub, subsWithSubTrue);
      window.assertDeepEqual(subsWithSubTrue, expectedSubsWithSub);

      // Test default subfolder handling of getSubFolders (= true).
      let subsWithSubDefault = await browser.folders.getSubFolders(root);
      window.assertDeepEqual(subsWithSubDefault, subsWithSubTrue);
      window.assertDeepEqual(subsWithSubTrue, subsWithSubDefault);

      browser.test.notifyPass("finished");
    },
    "utils.js": await getUtilsJS(),
  };
  let extension = ExtensionTestUtils.loadExtension({
    files,
    manifest: {
      background: { scripts: ["utils.js", "background.js"] },
      permissions: ["accountsRead", "accountsFolders"],
    },
  });

  let account = createAccount();
  // Not all folders appear immediately on IMAP. Creating a new one causes them to appear.
  await createSubfolder(account.incomingServer.rootFolder, "unused");

  // We should now have three folders. For IMAP accounts they are Inbox,
  // Trash, and unused. Otherwise they are Trash, Unsent Messages and unused.
  await extension.startup();
  extension.sendMessage(account.key);
  await extension.awaitFinish("finished");
  await extension.unload();
});

add_task(async function test_getFolderInfo() {
  let files = {
    "background.js": async () => {
      let [accountId, IS_NNTP] = await window.waitForMessage();

      let account = await browser.accounts.get(accountId);
      browser.test.assertEq(IS_NNTP ? 1 : 3, account.folders.length);
      let folders = await browser.folders.getSubFolders(account, false);
      let InfoTestFolder = folders.find(f => f.name == "InfoTest");

      // Verify initial state.
      let info = await browser.folders.getFolderInfo(InfoTestFolder);
      window.assertDeepEqual(
        { totalMessageCount: 12, unreadMessageCount: 12, favorite: false },
        info
      );

      // Test flipping favorite to true and marking all messages as read.
      let onFolderInfoChangedPromise = window.waitForEvent(
        "folders.onFolderInfoChanged"
      );
      await window.sendMessage("markAllAsRead");
      await window.sendMessage("setFavorite", true);
      let [mailFolder, mailFolderInfo] = await onFolderInfoChangedPromise;
      window.assertDeepEqual(
        { unreadMessageCount: 0, favorite: true },
        mailFolderInfo
      );
      browser.test.assertEq(InfoTestFolder.path, mailFolder.path);

      info = await browser.folders.getFolderInfo(InfoTestFolder);
      window.assertDeepEqual(
        { totalMessageCount: 12, unreadMessageCount: 0, favorite: true },
        info
      );

      // Test flipping favorite back to false.
      onFolderInfoChangedPromise = window.waitForEvent(
        "folders.onFolderInfoChanged"
      );
      await window.sendMessage("setFavorite", false);
      [mailFolder, mailFolderInfo] = await onFolderInfoChangedPromise;
      window.assertDeepEqual({ favorite: false }, mailFolderInfo);
      browser.test.assertEq(InfoTestFolder.path, mailFolder.path);

      // Test setting some messages back to unread.
      onFolderInfoChangedPromise = window.waitForEvent(
        "folders.onFolderInfoChanged"
      );
      await window.sendMessage("markSomeAsUnread", 5);
      [mailFolder, mailFolderInfo] = await onFolderInfoChangedPromise;
      window.assertDeepEqual({ unreadMessageCount: 5 }, mailFolderInfo);

      browser.test.notifyPass("finished");
    },
    "utils.js": await getUtilsJS(),
  };
  let extension = ExtensionTestUtils.loadExtension({
    files,
    manifest: {
      background: { scripts: ["utils.js", "background.js"] },
      permissions: ["accountsRead", "accountsFolders", "messagesDelete"],
    },
  });

  let account = createAccount();
  // Not all folders appear immediately on IMAP. Creating a new one causes them to appear.
  let InfoTestFolder = await createSubfolder(
    account.incomingServer.rootFolder,
    "InfoTest"
  );
  await createMessages(InfoTestFolder, 12);

  extension.onMessage("markAllAsRead", () => {
    InfoTestFolder.markAllMessagesRead(null);
    extension.sendMessage();
  });

  extension.onMessage("markSomeAsUnread", count => {
    let messages = InfoTestFolder.messages;
    while (messages.hasMoreElements() && count > 0) {
      let msg = messages.getNext();
      msg.markRead(false);
      count--;
    }
    extension.sendMessage();
  });

  extension.onMessage("setFavorite", value => {
    if (value) {
      InfoTestFolder.setFlag(Ci.nsMsgFolderFlags.Favorite);
    } else {
      InfoTestFolder.clearFlag(Ci.nsMsgFolderFlags.Favorite);
    }
    extension.sendMessage();
  });

  // We should now have three folders. For IMAP accounts they are Inbox, Trash,
  // and InfoTest. Otherwise they are Trash, Unsent Messages and InfoTest.

  await extension.startup();
  extension.sendMessage(account.key, IS_NNTP);
  await extension.awaitFinish("finished");
  await extension.unload();
});