summaryrefslogtreecommitdiffstats
path: root/comm/mailnews/base/test
diff options
context:
space:
mode:
authorDaniel Baumann <daniel.baumann@progress-linux.org>2024-04-07 17:32:43 +0000
committerDaniel Baumann <daniel.baumann@progress-linux.org>2024-04-07 17:32:43 +0000
commit6bf0a5cb5034a7e684dcc3500e841785237ce2dd (patch)
treea68f146d7fa01f0134297619fbe7e33db084e0aa /comm/mailnews/base/test
parentInitial commit. (diff)
downloadthunderbird-6bf0a5cb5034a7e684dcc3500e841785237ce2dd.tar.xz
thunderbird-6bf0a5cb5034a7e684dcc3500e841785237ce2dd.zip
Adding upstream version 1:115.7.0.upstream/1%115.7.0upstream
Signed-off-by: Daniel Baumann <daniel.baumann@progress-linux.org>
Diffstat (limited to 'comm/mailnews/base/test')
-rw-r--r--comm/mailnews/base/test/.eslintrc.js5
-rw-r--r--comm/mailnews/base/test/TestMsgStripRE.cpp91
-rw-r--r--comm/mailnews/base/test/gtest/TestHeaderReader.cpp205
-rw-r--r--comm/mailnews/base/test/gtest/TestLineReader.cpp200
-rw-r--r--comm/mailnews/base/test/gtest/moz.build15
-rw-r--r--comm/mailnews/base/test/moz.build19
-rw-r--r--comm/mailnews/base/test/unit/data/folderCache.json206
-rw-r--r--comm/mailnews/base/test/unit/data/panacea.dat70
-rw-r--r--comm/mailnews/base/test/unit/data/panacea_empty.dat1
-rw-r--r--comm/mailnews/base/test/unit/data/remoteContent.sql41
-rw-r--r--comm/mailnews/base/test/unit/head_mailbase.js23
-rw-r--r--comm/mailnews/base/test/unit/nodelist_test.xml7
-rw-r--r--comm/mailnews/base/test/unit/test_MsgIncomingServer.js224
-rw-r--r--comm/mailnews/base/test/unit/test_MsgKeySet.js85
-rw-r--r--comm/mailnews/base/test/unit/test_accountMgr.js102
-rw-r--r--comm/mailnews/base/test/unit/test_accountMgr2.js199
-rw-r--r--comm/mailnews/base/test/unit/test_accountMgrCustomTypes.js94
-rw-r--r--comm/mailnews/base/test/unit/test_accountMgrMovedLocalFolders.js43
-rw-r--r--comm/mailnews/base/test/unit/test_accountMgrRemoveDefault.js55
-rw-r--r--comm/mailnews/base/test/unit/test_accountMigration.js184
-rw-r--r--comm/mailnews/base/test/unit/test_acctRepair.js56
-rw-r--r--comm/mailnews/base/test/unit/test_bccInDatabase.js54
-rw-r--r--comm/mailnews/base/test/unit/test_bug428427.js249
-rw-r--r--comm/mailnews/base/test/unit/test_bug434810.js27
-rw-r--r--comm/mailnews/base/test/unit/test_bug471682.js118
-rw-r--r--comm/mailnews/base/test/unit/test_bug514945.js40
-rw-r--r--comm/mailnews/base/test/unit/test_closedDB.js104
-rw-r--r--comm/mailnews/base/test/unit/test_compactFailure.js134
-rw-r--r--comm/mailnews/base/test/unit/test_converterDeferredAccount.js206
-rw-r--r--comm/mailnews/base/test/unit/test_copyChaining.js109
-rw-r--r--comm/mailnews/base/test/unit/test_copyToInvalidDB.js106
-rw-r--r--comm/mailnews/base/test/unit/test_detachToFile.js159
-rw-r--r--comm/mailnews/base/test/unit/test_emptyTrash.js174
-rw-r--r--comm/mailnews/base/test/unit/test_fix_deferred_accounts.js59
-rw-r--r--comm/mailnews/base/test/unit/test_folderCompact.js335
-rw-r--r--comm/mailnews/base/test/unit/test_folderCompact2.js300
-rw-r--r--comm/mailnews/base/test/unit/test_folderLookupService.js83
-rw-r--r--comm/mailnews/base/test/unit/test_folderStringProperties.js41
-rw-r--r--comm/mailnews/base/test/unit/test_formatFileSize.js144
-rw-r--r--comm/mailnews/base/test/unit/test_getMsgTextFromStream.js88
-rw-r--r--comm/mailnews/base/test/unit/test_headerFoldingInDatabase.js58
-rw-r--r--comm/mailnews/base/test/unit/test_hostnameUtils.js276
-rw-r--r--comm/mailnews/base/test/unit/test_identity.js69
-rw-r--r--comm/mailnews/base/test/unit/test_imapPump.js81
-rw-r--r--comm/mailnews/base/test/unit/test_incomingServer.js99
-rw-r--r--comm/mailnews/base/test/unit/test_inheritedFolderProperties.js183
-rw-r--r--comm/mailnews/base/test/unit/test_junkingWhenDisabled.js176
-rw-r--r--comm/mailnews/base/test/unit/test_loadVirtualFolders.js64
-rw-r--r--comm/mailnews/base/test/unit/test_mailServices.js67
-rw-r--r--comm/mailnews/base/test/unit/test_mailstoreConverter.js376
-rw-r--r--comm/mailnews/base/test/unit/test_mimemaltdetach.js160
-rw-r--r--comm/mailnews/base/test/unit/test_newMailNotification.js203
-rw-r--r--comm/mailnews/base/test/unit/test_nsIFolderListener.js45
-rw-r--r--comm/mailnews/base/test/unit/test_nsIMsgContentPolicy.js68
-rw-r--r--comm/mailnews/base/test/unit/test_nsIMsgFolder.js102
-rw-r--r--comm/mailnews/base/test/unit/test_nsIMsgFolderCache.js228
-rw-r--r--comm/mailnews/base/test/unit/test_nsIMsgFolderListener.js214
-rw-r--r--comm/mailnews/base/test/unit/test_nsIMsgFolderListenerLocal.js444
-rw-r--r--comm/mailnews/base/test/unit/test_nsIMsgTagService.js113
-rw-r--r--comm/mailnews/base/test/unit/test_nsMailDirProvider.js23
-rw-r--r--comm/mailnews/base/test/unit/test_nsMsgDBView.js1212
-rw-r--r--comm/mailnews/base/test/unit/test_nsMsgDBView_headerValues.js110
-rw-r--r--comm/mailnews/base/test/unit/test_nsMsgMailSession_Alerts.js217
-rw-r--r--comm/mailnews/base/test/unit/test_nsMsgMailSession_Listeners.js161
-rw-r--r--comm/mailnews/base/test/unit/test_nsMsgTraitService.js130
-rw-r--r--comm/mailnews/base/test/unit/test_postPluginFilters.js223
-rw-r--r--comm/mailnews/base/test/unit/test_retention.js66
-rw-r--r--comm/mailnews/base/test/unit/test_saveAs.js172
-rw-r--r--comm/mailnews/base/test/unit/test_testsuite_base64.js22
-rw-r--r--comm/mailnews/base/test/unit/test_testsuite_fakeserverAuth.js58
-rw-r--r--comm/mailnews/base/test/unit/test_testsuite_fakeserver_imapd_gmail.js92
-rw-r--r--comm/mailnews/base/test/unit/test_testsuite_fakeserver_imapd_list-extended.js150
-rw-r--r--comm/mailnews/base/test/unit/test_viewSortByAddresses.js144
-rw-r--r--comm/mailnews/base/test/unit/test_virtualFolders1.js205
-rw-r--r--comm/mailnews/base/test/unit/test_virtualFolders2.js90
-rw-r--r--comm/mailnews/base/test/unit/test_virtualFolders3.js229
-rw-r--r--comm/mailnews/base/test/unit/xpcshell-imap.ini8
-rw-r--r--comm/mailnews/base/test/unit/xpcshell.ini77
78 files changed, 10870 insertions, 0 deletions
diff --git a/comm/mailnews/base/test/.eslintrc.js b/comm/mailnews/base/test/.eslintrc.js
new file mode 100644
index 0000000000..5816519fbb
--- /dev/null
+++ b/comm/mailnews/base/test/.eslintrc.js
@@ -0,0 +1,5 @@
+"use strict";
+
+module.exports = {
+ extends: ["plugin:mozilla/valid-jsdoc"],
+};
diff --git a/comm/mailnews/base/test/TestMsgStripRE.cpp b/comm/mailnews/base/test/TestMsgStripRE.cpp
new file mode 100644
index 0000000000..eae217dcb2
--- /dev/null
+++ b/comm/mailnews/base/test/TestMsgStripRE.cpp
@@ -0,0 +1,91 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* 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/. */
+
+#include "gtest/gtest.h"
+#include "mozilla/ArrayUtils.h"
+#include "nsCOMPtr.h"
+#include "nsMsgUtils.h"
+#include "nsIPrefService.h"
+#include "nsIPrefBranch.h"
+#include "nsISupportsPrimitives.h"
+#include "nsString.h"
+
+#define STRING_SIZE 255
+struct testInfo {
+ char encodedInput[STRING_SIZE];
+ char expectedOutput[STRING_SIZE];
+ bool expectedDidModify;
+};
+
+int testStripRe(const char* encodedInput, char* expectedOutput,
+ bool expectedDidModify) {
+ // call NS_StripRE with the appropriate args
+ nsCString modifiedSubject;
+ bool didModify;
+ didModify = NS_MsgStripRE(nsDependentCString(encodedInput), modifiedSubject);
+
+ // make sure we got the right results
+ if (didModify != expectedDidModify) return 2;
+
+ if (didModify) {
+ if (strcmp(expectedOutput, modifiedSubject.get())) {
+ return 3;
+ }
+ } else if (strcmp(expectedOutput, encodedInput)) {
+ return 4;
+ }
+
+ // test passed
+ return 0;
+}
+
+// int main(int argc, char** argv)
+TEST(TestMsgStripRE, TestMsgStripREMain)
+{
+ nsresult rv;
+ nsCOMPtr<nsIPrefBranch> prefBranch(
+ do_GetService(NS_PREFSERVICE_CONTRACTID, &rv));
+ EXPECT_TRUE(NS_SUCCEEDED(rv));
+
+ // set localizedRe pref, value "SV,ÆØÅ",
+ // \xC3\x86, \xC3\x98 and \xC3\x85 are the UTF-8 encodings of Æ, Ø and Å.
+ rv = prefBranch->SetStringPref("mailnews.localizedRe",
+ "SV,\xC3\x86\xC3\x98\xC3\x85"_ns);
+ EXPECT_TRUE(NS_SUCCEEDED(rv));
+
+ // run our tests
+ struct testInfo testInfoStructs[] = {
+ // Note that re-encoding always happens in UTF-8.
+ {"SV: =?ISO-8859-1?Q?=C6blegr=F8d?=", "=?UTF-8?B?w4ZibGVncsO4ZA==?=",
+ true},
+ {"=?ISO-8859-1?Q?SV=3A=C6blegr=F8d?=", "=?UTF-8?B?w4ZibGVncsO4ZA==?=",
+ true},
+
+ // Note that in the next two tests, the only ISO-8859-1 chars are in the
+ // localizedRe piece, so once they've been stripped, the re-encoding
+ // process simply writes out ASCII rather than an ISO-8859-1 encoded
+ // string with no actual ISO-8859-1 special characters, which seems
+ // reasonable.
+ {"=?ISO-8859-1?Q?=C6=D8=C5=3A_Foo_bar?=", "Foo bar", true},
+ {"=?ISO-8859-1?Q?=C6=D8=C5=3AFoo_bar?=", "Foo bar", true}};
+
+ bool allTestsPassed = true;
+ int result;
+ for (unsigned int i = 0; i < MOZ_ARRAY_LENGTH(testInfoStructs); i++) {
+ result = testStripRe(testInfoStructs[i].encodedInput,
+ testInfoStructs[i].expectedOutput,
+ testInfoStructs[i].expectedDidModify);
+ if (result) {
+ printf("Failed: %s, i=%d | result=%d\n", __FILE__, i, result);
+ allTestsPassed = false;
+ }
+ EXPECT_TRUE(result == 0);
+ }
+
+ if (allTestsPassed) {
+ printf("all tests passed\n");
+ }
+ EXPECT_TRUE(allTestsPassed);
+}
diff --git a/comm/mailnews/base/test/gtest/TestHeaderReader.cpp b/comm/mailnews/base/test/gtest/TestHeaderReader.cpp
new file mode 100644
index 0000000000..326279655a
--- /dev/null
+++ b/comm/mailnews/base/test/gtest/TestHeaderReader.cpp
@@ -0,0 +1,205 @@
+#include "gtest/gtest.h"
+#include "nsString.h"
+#include "HeaderReader.h"
+#include "nsTArray.h"
+#include "mozilla/ArrayUtils.h"
+
+// Invocation:
+// $ ./mach gtest "TestHeaderReader.*"
+
+TEST(TestHeaderReader, Basic)
+{
+ struct hdr {
+ nsCString name;
+ nsCString rawValue;
+ };
+
+ struct {
+ nsCString raw;
+ nsTArray<hdr> expected;
+ bool isComplete; // expect to find complete header block?
+ nsCString leftOver; // Data which should be left unprocessed.
+ } testMsgs[] = {
+ // Simple case works?
+ {
+ "Message-ID: one\r\n"
+ "To: alice@example.com\r\n"
+ "\r\n"
+ "Body here. HeaderReader should have stopped by now.\r\n"
+ "Note: this line looks like a header, but it isn't!\r\n"_ns,
+ {
+ {"Message-ID"_ns, "one"_ns},
+ {"To"_ns, "alice@example.com"_ns},
+ },
+ true,
+ "Body here. HeaderReader should have stopped by now.\r\n"
+ "Note: this line looks like a header, but it isn't!\r\n"_ns,
+ },
+ // Handle folded header values correctly?
+ {
+ "To: bob@example.com\r\n"
+ "Subject: Do\r\n"
+ " we\r\n"
+ " handle\r\n"
+ "\tfolded\r\n" // Can fold with tabs too.
+ " fields OK?\r\n"
+ "Message-ID: two\r\n"
+ "\r\n"
+ "...message body here...\r\n"_ns,
+ {
+ {"To"_ns, "bob@example.com"_ns},
+ {"Subject"_ns,
+ "Do\r\n we\r\n handle\r\n\tfolded\r\n fields OK?"_ns},
+ {"Message-ID"_ns, "two"_ns},
+ },
+ true,
+ "...message body here...\r\n"_ns,
+ },
+ // Handle no whitespace after colon?
+ {
+ "Foo:bar\r\n"
+ "\r\n"_ns,
+ {
+ {"Foo"_ns, "bar"_ns},
+ },
+ true,
+ ""_ns,
+ },
+ // Folding with no text on first line?
+ // (I _think_ this is legal...)
+ {
+ "Foo: \r\n"
+ " bar\r\n"
+ "\r\n"_ns,
+ {
+ {"Foo"_ns, "\r\n bar"_ns},
+ },
+ true,
+ ""_ns,
+ },
+ // Folded line with no end of header block.
+ // Input could be truncated. So we don't want this to output any headers
+ // (The missing next line could be folded or not - we just don't know).
+ {
+ "Foo:\r\n"
+ " bar\r\n"
+ " wibble\r\n"_ns,
+ {},
+ false,
+ "Foo:\r\n bar\r\n wibble\r\n"_ns,
+ },
+ // Ignore incomplete lines as expected?.
+ {
+ "Foo: bar\r\n"
+ "Wibble: this is a part"_ns, // ... "ial line".
+ {
+ {"Foo"_ns, "bar"_ns},
+ },
+ false,
+ "Wibble: this is a part"_ns,
+ },
+ // Ignore incomplete folded lines?
+ {
+ "Foo: bar\r\n"
+ "Wibble: this\r\n"
+ " value is not co"_ns, // ... "mplete".
+ {
+ {"Foo"_ns, "bar"_ns},
+ },
+ false,
+ "Wibble: this\r\n value is not co"_ns,
+ },
+ // Handle empty input without crashing?
+ {
+ ""_ns,
+ {},
+ false,
+ ""_ns,
+ }};
+
+ for (size_t i = 0; i < mozilla::ArrayLength(testMsgs); ++i) {
+ auto const& t = testMsgs[i];
+ // Collect all the headers.
+ nsTArray<HeaderReader::Hdr> gotHeaders;
+ HeaderReader rdr;
+ auto handler = [&](HeaderReader::Hdr const& hdr) {
+ gotHeaders.AppendElement(hdr);
+ return true; // Keep going.
+ };
+
+ // Simulate multiple passes over a growing buffer - we'll add a quarter
+ // of the data each pass.
+ for (uint32_t i = 1; i < 4; ++i) {
+ // Encourage each pass to use different memory.
+ nsCString fudge(t.raw.BeginReading(), i * t.raw.Length() / 4);
+ rdr.Parse(fudge, handler);
+ }
+
+ // Last pass - give the reader the entire input.
+ auto leftOver = rdr.Parse(t.raw, handler);
+
+ // Did we get all the headers we expected?
+ ASSERT_EQ(gotHeaders.Length(), t.expected.Length());
+ for (size_t i = 0; i < t.expected.Length(); ++i) {
+ auto const& expect = t.expected[i];
+ auto const& got = gotHeaders[i];
+ ASSERT_EQ(expect.name, nsCString(got.Name(t.raw)));
+ ASSERT_EQ(expect.rawValue, nsCString(got.RawValue(t.raw)));
+ }
+
+ // Correctly detected the end of the header block?
+ ASSERT_EQ(t.isComplete, rdr.IsComplete());
+
+ // Make sure processing stopped where expected.
+ ASSERT_EQ(t.leftOver, nsCString(leftOver));
+ }
+}
+
+// Check that callback can halt processing, and that it can be correctly
+// resumed.
+TEST(TestHeaderReader, Stop)
+{
+ // We'll stop each time we find a header name containing "STOP".
+ struct {
+ nsCString raw;
+ int expectedCount; // Number of headers we expect to find.
+ int expectedPasses; // Number of passes we expect to run.
+ } testMsgs[] = {
+ {"foo: bar\r\n\r\n"_ns, 1, 1},
+ {"Message-ID: one\r\n"
+ "STOP: pause here please\r\n"
+ "foo-One: nothing to see here...\r\n"
+ "foo-Two: still nothing...\r\n"
+ "\r\n"_ns,
+ 4, 2},
+ {"A: eh\r\n"
+ "B: bee\r\n"
+ "STOP_1: pause here.\r\n"
+ "C: sea\r\n"
+ "STOP_2: another pause.\r\n"
+ "E: eee\r\n"
+ "STOP_3: last one.\r\n"
+ "\r\n"_ns,
+ 7, 3},
+ };
+
+ for (size_t i = 0; i < mozilla::ArrayLength(testMsgs); ++i) {
+ auto const& t = testMsgs[i];
+ HeaderReader rdr;
+ int gotCount = 0;
+ auto handler = [&](HeaderReader::Hdr const& hdr) {
+ ++gotCount;
+ return nsCString(hdr.Name(t.raw)).Find("STOP") == kNotFound;
+ };
+
+ int passes = 0;
+ // All our examples have a complete header block.
+ while (!rdr.IsComplete()) {
+ rdr.Parse(t.raw, handler);
+ ++passes;
+ }
+ ASSERT_EQ(gotCount, t.expectedCount);
+ ASSERT_EQ(passes, t.expectedPasses);
+ ASSERT_TRUE(rdr.IsComplete());
+ }
+}
diff --git a/comm/mailnews/base/test/gtest/TestLineReader.cpp b/comm/mailnews/base/test/gtest/TestLineReader.cpp
new file mode 100644
index 0000000000..e84274fcb0
--- /dev/null
+++ b/comm/mailnews/base/test/gtest/TestLineReader.cpp
@@ -0,0 +1,200 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* 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/. */
+
+#include "gtest/gtest.h"
+#include "nsString.h"
+#include "LineReader.h"
+#include "mozilla/ArrayUtils.h"
+#include "mozilla/Span.h"
+
+// Invocation:
+// $ ./mach gtest "TestLineReader.*"
+
+TEST(TestLineReader, Basic)
+{
+ struct {
+ nsTArray<nsCString> chunks;
+ int expectedLines;
+ } testCases[] = {
+ // Split lines as expected?
+ {
+ {"Line one\r\nLine two\r\n"_ns},
+ 2,
+ },
+
+ // Plain LFs should be accepted.
+ {
+ {"Line one\nLine two\n"_ns},
+ 2,
+ },
+
+ // Empty lines appear as expected?
+ {
+ {"\r\n\r\n\r\n"_ns},
+ 3,
+ },
+
+ // Empty case handled?
+ {
+ {""_ns},
+ 0,
+ },
+
+ // Split in mid-CRLF.
+ {
+ {
+ "EOL split across\r"_ns,
+ "\nchunks.\r\n"_ns,
+ },
+ 2,
+ },
+
+ // Chunks join up correctly?
+ {
+ {"This single line is "_ns, "fed in as "_ns,
+ "multiple chunks\r\n"_ns},
+ 1,
+ },
+ // Handle an empty chunk OK?
+ {
+ {"foo"_ns, ""_ns,
+ "bar\n"_ns
+ "wibble\n"_ns},
+ 2,
+ },
+ // Handle lines without EOL?
+ {
+ {"This line has no EOL and relies on Flush()."_ns},
+ 1,
+ },
+ };
+
+ for (size_t i = 0; i < mozilla::ArrayLength(testCases); ++i) {
+ auto const& t = testCases[i];
+
+ // Join chunks into one string - we expect the output to be
+ // this, byte-for-byte.
+ nsCString expectedText;
+ for (auto chunk : t.chunks) {
+ expectedText.Append(chunk);
+ }
+
+ // Callback to collect the lines and count them.
+ int gotLineCount = 0;
+ nsCString gotText;
+ auto callback = [&](mozilla::Span<const char> line) {
+ ++gotLineCount;
+ gotText.Append(line);
+ return true;
+ };
+ // Parse the chunks.
+ LineReader chopper;
+ for (auto chunk : t.chunks) {
+ chopper.Feed(chunk, callback);
+ }
+ chopper.Flush(callback);
+
+ ASSERT_EQ(t.expectedLines, gotLineCount);
+ ASSERT_EQ(expectedText, gotText);
+ }
+}
+
+// Check that processing is aborted when callback returns false.
+TEST(TestLineReader, Stop)
+{
+ struct {
+ nsTArray<nsCString> chunks;
+ int expectedLines;
+ } testCases[] = {
+ // Stop at 2 lines.
+ {
+ {"Line one\nSTOP\nThis line is never seen.\n"_ns},
+ 2,
+ },
+
+ // Line split up over multiple chunks.
+ {
+ {"one\r\nST"_ns, "OP\r\nblah blah\r\n"_ns},
+ 2,
+ },
+
+ // Empty string -> no lines.
+ {
+ {""_ns},
+ 0,
+ },
+
+ // No EOL, relies on Flush().
+ {
+ {
+ "STOP"_ns,
+ },
+ 1,
+ },
+ };
+
+ for (size_t i = 0; i < mozilla::ArrayLength(testCases); ++i) {
+ auto const& t = testCases[i];
+
+ // Callback to collect the lines and count them, stopping when
+ // we find a line containing "STOP".
+ int gotLineCount = 0;
+ auto callback = [&](mozilla::Span<const char> line) -> bool {
+ ++gotLineCount;
+ return nsCString(line).Find("STOP"_ns) == kNotFound;
+ };
+ // Parse.
+ LineReader chopper;
+ for (auto chunk : t.chunks) {
+ chopper.Feed(chunk, callback);
+ }
+ chopper.Flush(callback);
+ ASSERT_EQ(t.expectedLines, gotLineCount);
+ }
+}
+
+// Test the SplitLines() fn.
+TEST(TestLineReader, SplitLines)
+{
+ struct {
+ nsCString input;
+ nsTArray<nsCString> expect; // The lines we expect to see.
+ nsCString expectLeftover; // The unconsumed data we expect at the end.
+ } testCases[] = {
+ // Empty string -> no lines.
+ {""_ns, {}, ""_ns},
+ // Incomplete line
+ {"foo"_ns, {}, "foo"_ns},
+ // Blank lines split as expected?
+ {"\r\n\r\n\r\n"_ns, {"\r\n"_ns, "\r\n"_ns, "\r\n"_ns}, ""_ns},
+ // A couple of normal-looking lines.
+ {"one\r\ntwo\r\n"_ns, {"one\r\n"_ns, "two\r\n"_ns}, ""_ns},
+ // Handles bare LFs?
+ {"one\ntwo\n"_ns, {"one\n"_ns, "two\n"_ns}, ""_ns},
+ // Ignores bare CRs?
+ {"one\rtwo\r"_ns, {}, "one\rtwo\r"_ns},
+
+ // Early-out works?
+ {"one\r\nSTOP\r\n3\r\n4\r\n"_ns,
+ {"one\r\n"_ns, "STOP\r\n"_ns},
+ "3\r\n4\r\n"_ns},
+ };
+
+ for (auto const& t : testCases) {
+ nsTArray<nsCString> got;
+ auto fn = [&](mozilla::Span<const char> line) -> bool {
+ got.AppendElement(line);
+ // Finish early if line contains "STOP".
+ return nsCString(line).Find("STOP"_ns) == kNotFound;
+ };
+ mozilla::Span<const char> leftover = SplitLines(t.input, fn);
+
+ ASSERT_EQ(t.expect.Length(), got.Length());
+ for (size_t i = 0; i < t.expect.Length(); ++i) {
+ ASSERT_EQ(t.expect[i], got[i]);
+ }
+ ASSERT_EQ(t.expectLeftover, nsCString(leftover));
+ }
+}
diff --git a/comm/mailnews/base/test/gtest/moz.build b/comm/mailnews/base/test/gtest/moz.build
new file mode 100644
index 0000000000..a0b6129efd
--- /dev/null
+++ b/comm/mailnews/base/test/gtest/moz.build
@@ -0,0 +1,15 @@
+# vim: set filetype=python:
+# 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/.
+
+FINAL_LIBRARY = "xul-gtest"
+
+UNIFIED_SOURCES += [
+ "TestHeaderReader.cpp",
+ "TestLineReader.cpp",
+]
+
+# LOCAL_INCLUDES += [
+# "../../src",
+# ]
diff --git a/comm/mailnews/base/test/moz.build b/comm/mailnews/base/test/moz.build
new file mode 100644
index 0000000000..4bb676ffb3
--- /dev/null
+++ b/comm/mailnews/base/test/moz.build
@@ -0,0 +1,19 @@
+# vim: set filetype=python:
+# 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/.
+
+XPCSHELL_TESTS_MANIFESTS += ["unit/xpcshell-imap.ini", "unit/xpcshell.ini"]
+
+FINAL_LIBRARY = "xul-gtest"
+
+UNIFIED_SOURCES += [
+ "TestMsgStripRE.cpp",
+]
+
+LOCAL_INCLUDES += [
+ "/netwerk/test",
+ "/xpcom/tests",
+]
+
+TEST_DIRS += ["gtest"]
diff --git a/comm/mailnews/base/test/unit/data/folderCache.json b/comm/mailnews/base/test/unit/data/folderCache.json
new file mode 100644
index 0000000000..e593e6cbb9
--- /dev/null
+++ b/comm/mailnews/base/test/unit/data/folderCache.json
@@ -0,0 +1,206 @@
+{
+ "/foo/bar/profile-default/ImapMail/imap.localhost": {
+ "aclFlags": 0,
+ "boxFlags": 0,
+ "expungedBytes": 0,
+ "flags": 0,
+ "folderSize": -1,
+ "hierDelim": 94,
+ "onlineName": "",
+ "pendingMsgs": 0,
+ "pendingUnreadMsgs": 0,
+ "serverRecent": 0,
+ "serverTotal": 0,
+ "serverUnseen": 0,
+ "totalMsgs": -1,
+ "totalUnreadMsgs": -1
+ },
+ "/foo/bar/profile-default/ImapMail/imap.localhost/Archives.msf": {
+ "aclFlags": 0,
+ "boxFlags": 0,
+ "expungedBytes": 0,
+ "flags": 0,
+ "folderSize": -1,
+ "hierDelim": 94,
+ "onlineName": "Archives",
+ "pendingMsgs": 0,
+ "pendingUnreadMsgs": 0,
+ "serverRecent": 0,
+ "serverTotal": 0,
+ "serverUnseen": 0,
+ "totalMsgs": 0,
+ "totalUnreadMsgs": 0
+ },
+ "/foo/bar/profile-default/ImapMail/imap.localhost/Drafts.msf": {
+ "aclFlags": 0,
+ "boxFlags": 0,
+ "expungedBytes": 0,
+ "flags": 0,
+ "folderSize": -1,
+ "hierDelim": 94,
+ "onlineName": "Drafts",
+ "pendingMsgs": 0,
+ "pendingUnreadMsgs": 0,
+ "serverRecent": 0,
+ "serverTotal": 0,
+ "serverUnseen": 0,
+ "totalMsgs": 0,
+ "totalUnreadMsgs": 0
+ },
+ "/foo/bar/profile-default/ImapMail/imap.localhost/INBOX.msf": {
+ "MRMTime": "1628202592",
+ "MRUTime": "1628202593",
+ "aclFlags": 0,
+ "boxFlags": 2097216,
+ "expungedBytes": 11536,
+ "flags": 2282237972,
+ "folderSize": 22906,
+ "hierDelim": 46,
+ "lastSyncTimeInSec": 0,
+ "nextUID": 2,
+ "onlineName": "INBOX",
+ "pendingMsgs": 0,
+ "pendingUnreadMsgs": 0,
+ "serverRecent": 0,
+ "serverTotal": 2,
+ "serverUnseen": 0,
+ "totalMsgs": 2,
+ "totalUnreadMsgs": 1
+ },
+ "/foo/bar/profile-default/ImapMail/imap.localhost/Junk.msf": {
+ "aclFlags": 0,
+ "boxFlags": 0,
+ "expungedBytes": 0,
+ "flags": 0,
+ "folderSize": -1,
+ "hierDelim": 94,
+ "onlineName": "Junk",
+ "pendingMsgs": 0,
+ "pendingUnreadMsgs": 0,
+ "serverRecent": 0,
+ "serverTotal": 0,
+ "serverUnseen": 0,
+ "totalMsgs": 0,
+ "totalUnreadMsgs": 0
+ },
+ "/foo/bar/profile-default/ImapMail/imap.localhost/Sent.msf": {
+ "aclFlags": 0,
+ "boxFlags": 0,
+ "expungedBytes": 0,
+ "flags": 0,
+ "folderSize": -1,
+ "hierDelim": 94,
+ "onlineName": "Sent",
+ "pendingMsgs": 0,
+ "pendingUnreadMsgs": 0,
+ "serverRecent": 0,
+ "serverTotal": 0,
+ "serverUnseen": 0,
+ "totalMsgs": 0,
+ "totalUnreadMsgs": 0
+ },
+ "/foo/bar/profile-default/ImapMail/imap.localhost/Templates.msf": {
+ "aclFlags": 0,
+ "boxFlags": 0,
+ "expungedBytes": 0,
+ "flags": 0,
+ "folderSize": -1,
+ "hierDelim": 94,
+ "onlineName": "Templates",
+ "pendingMsgs": 0,
+ "pendingUnreadMsgs": 0,
+ "serverRecent": 0,
+ "serverTotal": 0,
+ "serverUnseen": 0,
+ "totalMsgs": 0,
+ "totalUnreadMsgs": 0
+ },
+ "/foo/bar/profile-default/ImapMail/imap.localhost/Trash.msf": {
+ "aclFlags": 0,
+ "boxFlags": 262224,
+ "expungedBytes": 0,
+ "flags": 532756,
+ "folderSize": -1,
+ "hierDelim": 46,
+ "onlineName": "Trash",
+ "pendingMsgs": 0,
+ "pendingUnreadMsgs": 0,
+ "serverRecent": 0,
+ "serverTotal": 0,
+ "serverUnseen": 0,
+ "totalMsgs": 0,
+ "totalUnreadMsgs": 0
+ },
+ "/foo/bar/profile-default/ImapMail/imap.localhost/blah.msf": {
+ "MRUTime": "1628202572",
+ "aclFlags": 0,
+ "boxFlags": 262208,
+ "expungedBytes": 0,
+ "flags": 134750228,
+ "folderSize": 11872,
+ "hierDelim": 46,
+ "lastSyncTimeInSec": 0,
+ "nextUID": 3,
+ "onlineName": "blah",
+ "pendingMsgs": 0,
+ "pendingUnreadMsgs": 0,
+ "serverRecent": 0,
+ "serverTotal": 2,
+ "serverUnseen": 0,
+ "totalMsgs": 2,
+ "totalUnreadMsgs": 0
+ },
+ "/foo/bar/profile-default/ImapMail/imap.localhost/foo.msf": {
+ "MRUTime": "1628202573",
+ "aclFlags": 0,
+ "boxFlags": 262208,
+ "expungedBytes": 11536,
+ "flags": 134750228,
+ "folderSize": 10707667,
+ "hierDelim": 46,
+ "lastSyncTimeInSec": 0,
+ "nextUID": 5,
+ "onlineName": "foo",
+ "pendingMsgs": 0,
+ "pendingUnreadMsgs": 0,
+ "serverRecent": 0,
+ "serverTotal": 4,
+ "serverUnseen": 0,
+ "totalMsgs": 3,
+ "totalUnreadMsgs": 0
+ },
+ "/foo/bar/profile-default/Mail/Local Folders": {
+ "expungedBytes": 0,
+ "flags": 28,
+ "folderName": "Local Folders",
+ "folderSize": -1,
+ "pendingMsgs": 0,
+ "pendingUnreadMsgs": 0,
+ "totalMsgs": -1,
+ "totalUnreadMsgs": -1
+ },
+ "/foo/bar/profile-default/Mail/Local Folders/Trash.msf": {
+ "MRUTime": "1628202569",
+ "expungedBytes": 0,
+ "flags": 260,
+ "folderName": "Trash",
+ "folderSize": 0,
+ "pendingMsgs": 0,
+ "pendingUnreadMsgs": 0,
+ "totalMsgs": 0,
+ "totalUnreadMsgs": 0,
+ "useServerRetention": "1"
+ },
+ "/foo/bar/profile-default/Mail/Local Folders/Unsent Messages.msf": {
+ "MRUTime": "1628202570",
+ "expungedBytes": 0,
+ "flags": 2052,
+ "folderName": "Outbox",
+ "folderSize": 0,
+ "pendingMsgs": 0,
+ "pendingUnreadMsgs": 0,
+ "totalMsgs": 0,
+ "totalUnreadMsgs": 0,
+ "useServerRetention": "1"
+ }
+}
diff --git a/comm/mailnews/base/test/unit/data/panacea.dat b/comm/mailnews/base/test/unit/data/panacea.dat
new file mode 100644
index 0000000000..214176e31a
--- /dev/null
+++ b/comm/mailnews/base/test/unit/data/panacea.dat
@@ -0,0 +1,70 @@
+// <!-- <mdb:mork:z v="1.4"/> -->
+< <(a=c)> // (f=iso-8859-1)
+ (8A=boxFlags)(8B=hierDelim)(8C=onlineName)(8D=aclFlags)(8E=serverTotal)
+ (8F=serverUnseen)(90=serverRecent)(91=indexingPriority)(92=folderName)
+ (93=applyIncomingFilters.empty)(94=applyIncomingFilters)(95=MRUTime)
+ (96=dobayes.mailnews@mozilla.org#junk.empty)
+ (97=dobayes.mailnews@mozilla.org#junk)(98=nextUID)
+ (99=lastSyncTimeInSec)(9A=useServerRetention)(9B=MRMTime)
+ (80=ns:msg:db:row:scope:folders:all)(81=ns:msg:db:table:kind:folders)
+ (82=key)(83=flags)(84=totalMsgs)(85=totalUnreadMsgs)
+ (86=pendingUnreadMsgs)(87=pendingMsgs)(88=expungedBytes)(89=folderSize)>
+
+<(80
+ =/foo/bar/profile-default/ImapMail\
+/imap.localhost/INBOX.msf)(A6=88083014)(AA=2)(A8=1)(81=0)(B8=2d10)(BB=597a)
+ (A7=200040)(9D=2e)(84=INBOX)(BA=1628202593)(B5=1628202592)(86
+ =/foo/bar/profile-default/Mail/Loc\
+al Folders/Trash.msf)(87=104)(88=Trash)(AB=1628202569)(89
+ =/foo/bar/profile-default/Mail/Loc\
+al Folders/Unsent Messages.msf)(8A=804)(8C=Outbox)(AC=1628202570)(8D
+ =/foo/bar/profile-default/Mail/Loc\
+al Folders)(8E=1c)(8F=ffffffff)(82=ffffffffffffffff)(90=Local Folders)
+ (91
+ =/foo/bar/profile-default/ImapMail\
+/imap.localhost/Drafts.msf)(83=5e)(92=Drafts)(93
+ =/foo/bar/profile-default/ImapMail\
+/imap.localhost/Templates.msf)(94=Templates)(95
+ =/foo/bar/profile-default/ImapMail\
+/imap.localhost/Sent.msf)(96=Sent)(97
+ =/foo/bar/profile-default/ImapMail\
+/imap.localhost/Archives.msf)(98=Archives)(9A
+ =/foo/bar/profile-default/ImapMail\
+/imap.localhost/Trash.msf)(9B=82114)(9C=40050)(9E
+ =/foo/bar/profile-default/ImapMail\
+/imap.localhost/blah.msf)(A0=8082014)(AF=2e60)(A1=40040)(9F=blah)(AE
+ =1628202572)(B0=3)(A2
+ =/foo/bar/profile-default/ImapMail\
+/imap.localhost/foo.msf)(B9=a362d3)(A3=foo)(B2=4)(B1=1628202573)(B4=5)
+ (A4
+ =/foo/bar/profile-default/ImapMail\
+/imap.localhost/Junk.msf)(A5=Junk)(BC
+ =/foo/bar/profile-default/ImapMail\
+/imap.localhost)(BD=)>
+{1:^80 {(k^81:c)(s=9)}
+ [1(^82^80)(^83^A6)(^84=2)(^85=1)(^86=0)(^87=0)(^88^B8)(^89^BB)(^8A^A7)
+ (^8B=2e)(^8C^84)(^8D=0)(^8E=2)(^8F=0)(^90=0)(^95^BA)(^98=2)(^99=0)
+ (^9B^B5)]
+ [2(^82^86)(^83^87)(^84=0)(^85=0)(^86=0)(^87=0)(^88=0)(^89=0)(^92^88)
+ (^95^AB)(^9A=1)]
+ [3(^82^89)(^83^8A)(^84=0)(^85=0)(^86=0)(^87=0)(^88=0)(^89=0)(^92^8C)
+ (^95^AC)(^9A=1)]
+ [4(^82^8D)(^83=1c)(^84^8F)(^85^8F)(^86=0)(^87=0)(^88=0)(^89^82)(^92^90)]
+ [5(^82^91)(^83=0)(^84=0)(^85=0)(^86=0)(^87=0)(^88=0)(^89^82)(^8A=0)
+ (^8B=5e)(^8C^92)(^8D=0)(^8E=0)(^8F=0)(^90=0)]
+ [6(^82^93)(^83=0)(^84=0)(^85=0)(^86=0)(^87=0)(^88=0)(^89^82)(^8A=0)
+ (^8B=5e)(^8C^94)(^8D=0)(^8E=0)(^8F=0)(^90=0)]
+ [7(^82^95)(^83=0)(^84=0)(^85=0)(^86=0)(^87=0)(^88=0)(^89^82)(^8A=0)
+ (^8B=5e)(^8C^96)(^8D=0)(^8E=0)(^8F=0)(^90=0)]
+ [8(^82^97)(^83=0)(^84=0)(^85=0)(^86=0)(^87=0)(^88=0)(^89^82)(^8A=0)
+ (^8B=5e)(^8C^98)(^8D=0)(^8E=0)(^8F=0)(^90=0)]
+ [9(^82^9A)(^83^9B)(^84=0)(^85=0)(^86=0)(^87=0)(^88=0)(^89^82)(^8A^9C)
+ (^8B=2e)(^8C^88)(^8D=0)(^8E=0)(^8F=0)(^90=0)]
+ [A(^82^9E)(^83^A0)(^84=2)(^85=0)(^86=0)(^87=0)(^88=0)(^89^AF)(^8A^A1)
+ (^8B=2e)(^8C^9F)(^8D=0)(^8E=2)(^8F=0)(^90=0)(^95^AE)(^98=3)(^99=0)]
+ [B(^82^A2)(^83^A0)(^84=3)(^85=0)(^86=0)(^87=0)(^88^B8)(^89^B9)(^8A^A1)
+ (^8B=2e)(^8C^A3)(^8D=0)(^8E=4)(^8F=0)(^90=0)(^95^B1)(^98=5)(^99=0)]
+ [C(^82^A4)(^83=0)(^84=0)(^85=0)(^86=0)(^87=0)(^88=0)(^89^82)(^8A=0)
+ (^8B=5e)(^8C^A5)(^8D=0)(^8E=0)(^8F=0)(^90=0)]
+ [D(^82^BC)(^83=0)(^84^8F)(^85^8F)(^86=0)(^87=0)(^88=0)(^89^82)(^8A=0)
+ (^8B=5e)(^8C=)(^8D=0)(^8E=0)(^8F=0)(^90=0)]}
diff --git a/comm/mailnews/base/test/unit/data/panacea_empty.dat b/comm/mailnews/base/test/unit/data/panacea_empty.dat
new file mode 100644
index 0000000000..b169b34da9
--- /dev/null
+++ b/comm/mailnews/base/test/unit/data/panacea_empty.dat
@@ -0,0 +1 @@
+// <!-- <mdb:mork:z v="1.4"/> -->
diff --git a/comm/mailnews/base/test/unit/data/remoteContent.sql b/comm/mailnews/base/test/unit/data/remoteContent.sql
new file mode 100644
index 0000000000..042cfefbd9
--- /dev/null
+++ b/comm/mailnews/base/test/unit/data/remoteContent.sql
@@ -0,0 +1,41 @@
+-- Address book with remote content permissions for use in test_accountMigration.js.
+PRAGMA user_version = 1;
+
+CREATE TABLE cards (uid TEXT PRIMARY KEY, localId INTEGER);
+CREATE TABLE properties (card TEXT, name TEXT, value TEXT);
+CREATE TABLE lists (uid TEXT PRIMARY KEY, localId INTEGER, name TEXT, nickName TEXT, description TEXT);
+CREATE TABLE list_cards (list TEXT, card TEXT, PRIMARY KEY(list, card));
+
+INSERT INTO cards (uid, localId) VALUES
+ ('f36c4b94-fbab-4cd6-b175-bf8242e6e757', 1),
+ ('7ecf5197-87cd-4633-8fcb-bf4ed8f6239e', 2),
+ ('90a1de3d-0f5d-4352-b927-60ee8205ac8f', 3);
+
+INSERT INTO properties (card, name, value) VALUES
+ ('f36c4b94-fbab-4cd6-b175-bf8242e6e757', 'PrimaryEmail', 'no@test.invalid'),
+ ('f36c4b94-fbab-4cd6-b175-bf8242e6e757', 'PhotoType', 'generic'),
+ ('f36c4b94-fbab-4cd6-b175-bf8242e6e757', 'LowercasePrimaryEmail', 'no@test.invalid'),
+ ('f36c4b94-fbab-4cd6-b175-bf8242e6e757', 'PopularityIndex', '0'),
+ ('f36c4b94-fbab-4cd6-b175-bf8242e6e757', 'PreferMailFormat', '0'),
+ ('f36c4b94-fbab-4cd6-b175-bf8242e6e757', 'LastModifiedDate', '0'),
+ ('f36c4b94-fbab-4cd6-b175-bf8242e6e757', 'PreferDisplayName', '1'),
+ ('f36c4b94-fbab-4cd6-b175-bf8242e6e757', 'AllowRemoteContent', '0'),
+
+ ('7ecf5197-87cd-4633-8fcb-bf4ed8f6239e', 'PrimaryEmail', 'yes@test.invalid'),
+ ('7ecf5197-87cd-4633-8fcb-bf4ed8f6239e', 'PhotoType', 'generic'),
+ ('7ecf5197-87cd-4633-8fcb-bf4ed8f6239e', 'LowercasePrimaryEmail', 'yes@test.invalid'),
+ ('7ecf5197-87cd-4633-8fcb-bf4ed8f6239e', 'PopularityIndex', '0'),
+ ('7ecf5197-87cd-4633-8fcb-bf4ed8f6239e', 'PreferMailFormat', '0'),
+ ('7ecf5197-87cd-4633-8fcb-bf4ed8f6239e', 'LastModifiedDate', '0'),
+ ('7ecf5197-87cd-4633-8fcb-bf4ed8f6239e', 'PreferDisplayName', '1'),
+ ('7ecf5197-87cd-4633-8fcb-bf4ed8f6239e', 'AllowRemoteContent', '0'),
+
+ ('90a1de3d-0f5d-4352-b927-60ee8205ac8f', 'LastModifiedDate', '1397383824'),
+ ('90a1de3d-0f5d-4352-b927-60ee8205ac8f', 'PrimaryEmail', 'yes@test.invalid'),
+ ('90a1de3d-0f5d-4352-b927-60ee8205ac8f', 'PhotoType', 'generic'),
+ ('90a1de3d-0f5d-4352-b927-60ee8205ac8f', 'SecondEmail', 'yes2@test.invalid'),
+ ('90a1de3d-0f5d-4352-b927-60ee8205ac8f', 'LowercasePrimaryEmail', 'yes@test.invalid'),
+ ('90a1de3d-0f5d-4352-b927-60ee8205ac8f', 'PopularityIndex', '0'),
+ ('90a1de3d-0f5d-4352-b927-60ee8205ac8f', 'PreferMailFormat', '0'),
+ ('90a1de3d-0f5d-4352-b927-60ee8205ac8f', 'PreferDisplayName', '1'),
+ ('90a1de3d-0f5d-4352-b927-60ee8205ac8f', 'AllowRemoteContent', '1');
diff --git a/comm/mailnews/base/test/unit/head_mailbase.js b/comm/mailnews/base/test/unit/head_mailbase.js
new file mode 100644
index 0000000000..9f37623291
--- /dev/null
+++ b/comm/mailnews/base/test/unit/head_mailbase.js
@@ -0,0 +1,23 @@
+var { MailServices } = ChromeUtils.import(
+ "resource:///modules/MailServices.jsm"
+);
+var { XPCOMUtils } = ChromeUtils.importESModule(
+ "resource://gre/modules/XPCOMUtils.sys.mjs"
+);
+var { mailTestUtils } = ChromeUtils.import(
+ "resource://testing-common/mailnews/MailTestUtils.jsm"
+);
+var { localAccountUtils } = ChromeUtils.import(
+ "resource://testing-common/mailnews/LocalAccountUtils.jsm"
+);
+
+var CC = Components.Constructor;
+
+// Ensure the profile directory is set up
+do_get_profile();
+
+var gDEPTH = "../../../../";
+
+registerCleanupFunction(function () {
+ load(gDEPTH + "mailnews/resources/mailShutdown.js");
+});
diff --git a/comm/mailnews/base/test/unit/nodelist_test.xml b/comm/mailnews/base/test/unit/nodelist_test.xml
new file mode 100644
index 0000000000..e1640b5b5b
--- /dev/null
+++ b/comm/mailnews/base/test/unit/nodelist_test.xml
@@ -0,0 +1,7 @@
+<?xml version="1.0" ?>
+
+<rootnode>
+ <node id="childnode1" />
+ <node id="childnode2" />
+ <node id="childnode3" />
+</rootnode>
diff --git a/comm/mailnews/base/test/unit/test_MsgIncomingServer.js b/comm/mailnews/base/test/unit/test_MsgIncomingServer.js
new file mode 100644
index 0000000000..184d2d4072
--- /dev/null
+++ b/comm/mailnews/base/test/unit/test_MsgIncomingServer.js
@@ -0,0 +1,224 @@
+/* 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/. */
+
+Services.prefs.setBoolPref("mailnews.imap.jsmodule", true);
+
+registerCleanupFunction(() => {
+ Services.logins.removeAllLogins();
+ Services.prefs.clearUserPref("mailnews.imap.jsmodule");
+});
+
+/**
+ * Test password is migrated when changing hostname/username.
+ */
+add_task(function testMigratePasswordOnChangeUsernameHostname() {
+ // Add two logins.
+ let loginItems = [
+ ["news://news.localhost", "user-nntp", "password-nntp"],
+ ["mailbox://pop3.localhost", "user-pop", "password-pop"],
+ ];
+ for (let [uri, username, password] of loginItems) {
+ let login = Cc["@mozilla.org/login-manager/loginInfo;1"].createInstance(
+ Ci.nsILoginInfo
+ );
+ login.init(uri, null, uri, username, password, "", "");
+ Services.logins.addLogin(login);
+ }
+
+ // Create a nntp server, check the password can be found correctly.
+ let nntpIncomingServer = MailServices.accounts.createIncomingServer(
+ "user-nntp",
+ "news.localhost",
+ "nntp"
+ );
+ nntpIncomingServer.getPasswordWithUI("", "");
+ equal(nntpIncomingServer.password, "password-nntp");
+
+ // Change the username, check password can be found using the new username.
+ nntpIncomingServer.username = "nntp";
+ let password;
+ let serverUri = "news://news.localhost";
+ for (let login of Services.logins.findLogins(serverUri, "", serverUri)) {
+ if (login.username == "nntp") {
+ password = login.password;
+ }
+ }
+ equal(password, "password-nntp");
+
+ // Create a pop3 server, check the password can be found correctly.
+ let pop3IncomingServer = MailServices.accounts.createIncomingServer(
+ "user-pop",
+ "pop3.localhost",
+ "pop3"
+ );
+ pop3IncomingServer.getPasswordWithUI("", "");
+ equal(pop3IncomingServer.password, "password-pop");
+
+ // Change the hostname, check password can be found using the new hostname.
+ pop3IncomingServer.hostName = "localhost";
+ serverUri = "mailbox://localhost";
+ for (let login of Services.logins.findLogins(serverUri, "", serverUri)) {
+ if (login.username == "user-pop") {
+ password = login.password;
+ }
+ }
+ equal(password, "password-pop");
+});
+
+/**
+ * Test identity folders are migrated when changing hostname/username.
+ */
+add_task(function testMigrateIdentitiesOnChangeUsernameHostname() {
+ // Create an imap server.
+ let incomingServer1 = MailServices.accounts.createIncomingServer(
+ "user-imap",
+ "imap.localhost",
+ "imap"
+ );
+ // Create a pop server.
+ let incomingServer2 = MailServices.accounts.createIncomingServer(
+ "user-pop",
+ "pop3.localhost",
+ "pop3"
+ );
+
+ // Create an identity and point folders to incomingServer1.
+ let identity1 = MailServices.accounts.createIdentity();
+ identity1.fccFolder = incomingServer1.serverURI + "/Sent";
+ identity1.draftFolder = incomingServer1.serverURI + "/Drafts";
+ identity1.archiveFolder = incomingServer1.serverURI + "/Archives";
+ identity1.stationeryFolder = incomingServer1.serverURI + "/Templates";
+ let account1 = MailServices.accounts.createAccount();
+ account1.addIdentity(identity1);
+ // Create another identity and point folders to both servers.
+ let identity2 = MailServices.accounts.createIdentity();
+ identity2.fccFolder = incomingServer1.serverURI + "/Sent";
+ identity2.draftFolder = incomingServer2.serverURI + "/Drafts";
+ let account2 = MailServices.accounts.createAccount();
+ account2.addIdentity(identity2);
+
+ // Check folders were correctly set.
+ equal(identity1.fccFolder, "imap://user-imap@imap.localhost/Sent");
+ equal(identity1.draftFolder, "imap://user-imap@imap.localhost/Drafts");
+ equal(identity1.archiveFolder, "imap://user-imap@imap.localhost/Archives");
+ equal(
+ identity1.stationeryFolder,
+ "imap://user-imap@imap.localhost/Templates"
+ );
+ equal(identity2.fccFolder, "imap://user-imap@imap.localhost/Sent");
+ equal(identity2.draftFolder, "mailbox://user-pop@pop3.localhost/Drafts");
+
+ // Change the hostname.
+ incomingServer1.hostName = "localhost";
+
+ // Check folders were correctly updated.
+ identity1 = MailServices.accounts.getIdentity(identity1.key);
+ equal(identity1.fccFolder, "imap://user-imap@localhost/Sent");
+ equal(identity1.draftFolder, "imap://user-imap@localhost/Drafts");
+ equal(identity1.archiveFolder, "imap://user-imap@localhost/Archives");
+ equal(identity1.stationeryFolder, "imap://user-imap@localhost/Templates");
+ equal(identity2.fccFolder, "imap://user-imap@localhost/Sent");
+ equal(identity2.draftFolder, "mailbox://user-pop@pop3.localhost/Drafts");
+});
+
+/**
+ * Test spam action prefs are migrated when changing hostname/username.
+ */
+add_task(function testMigrateSpamActionsOnChangeUsernameHostname() {
+ // Create an imap server.
+ let incomingServer1 = MailServices.accounts.createIncomingServer(
+ "user-imap",
+ "imap.localhost",
+ "imap"
+ );
+ incomingServer1.setUnicharValue(
+ "spamActionTargetFolder",
+ incomingServer1.serverURI + "/Спам"
+ );
+
+ equal(
+ incomingServer1.spamSettings.actionTargetAccount,
+ "imap://user-imap@imap.localhost"
+ );
+ equal(
+ incomingServer1.spamSettings.actionTargetFolder,
+ "imap://user-imap@imap.localhost/Спам"
+ );
+
+ // Change the username.
+ incomingServer1.username = "user";
+
+ equal(
+ incomingServer1.spamSettings.actionTargetAccount,
+ "imap://user@imap.localhost"
+ );
+ equal(
+ incomingServer1.spamSettings.actionTargetFolder,
+ "imap://user@imap.localhost/Спам"
+ );
+});
+
+/**
+ * Test filters are migrated when changing hostname/username.
+ */
+add_task(function testMigrateFiltersOnChangeUsernameHostname() {
+ // Create a nntp server.
+ let nntpIncomingServer = MailServices.accounts.createIncomingServer(
+ "user-nntp",
+ "news.localhost",
+ "nntp"
+ );
+ let filterList = nntpIncomingServer.getFilterList(null);
+
+ // Insert a CopyToFolder filter.
+ let filter = filterList.createFilter("filter1");
+ let action = filter.createAction();
+ action.type = Ci.nsMsgFilterAction.CopyToFolder;
+ action.targetFolderUri = "news://user-nntp@news.localhost/dest1";
+ filter.appendAction(action);
+ filterList.insertFilterAt(filterList.filterCount, filter);
+
+ // Insert a MarkRead filter.
+ filter = filterList.createFilter("filter2");
+ action = filter.createAction();
+ action.type = Ci.nsMsgFilterAction.MarkRead;
+ filter.appendAction(action);
+ filterList.insertFilterAt(filterList.filterCount, filter);
+
+ // Insert a MoveToFolder filter.
+ filter = filterList.createFilter("filter3");
+ action = filter.createAction();
+ action.type = Ci.nsMsgFilterAction.MoveToFolder;
+ action.targetFolderUri = "news://user-nntp@news.localhost/dest2";
+ filter.appendAction(action);
+ filterList.insertFilterAt(filterList.filterCount, filter);
+
+ // Change the hostname, test targetFolderUri of filters are changed accordingly.
+ nntpIncomingServer.hostName = "localhost";
+ filterList = nntpIncomingServer.getFilterList(null);
+ filter = filterList.getFilterAt(0);
+ equal(
+ filter.sortedActionList[0].targetFolderUri,
+ "news://user-nntp@localhost/dest1"
+ );
+ filter = filterList.getFilterAt(2);
+ equal(
+ filter.sortedActionList[0].targetFolderUri,
+ "news://user-nntp@localhost/dest2"
+ );
+
+ // Change the username, test targetFolderUri of filters are changed accordingly.
+ nntpIncomingServer.username = "nntp";
+ filterList = nntpIncomingServer.getFilterList(null);
+ filter = filterList.getFilterAt(0);
+ equal(
+ filter.sortedActionList[0].targetFolderUri,
+ "news://nntp@localhost/dest1"
+ );
+ filter = filterList.getFilterAt(2);
+ equal(
+ filter.sortedActionList[0].targetFolderUri,
+ "news://nntp@localhost/dest2"
+ );
+});
diff --git a/comm/mailnews/base/test/unit/test_MsgKeySet.js b/comm/mailnews/base/test/unit/test_MsgKeySet.js
new file mode 100644
index 0000000000..7ae1eea938
--- /dev/null
+++ b/comm/mailnews/base/test/unit/test_MsgKeySet.js
@@ -0,0 +1,85 @@
+/* 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/. */
+
+var { MsgKeySet } = ChromeUtils.import("resource:///modules/MsgKeySet.jsm");
+
+/**
+ * Test MsgKeySet.addRange works correctly.
+ */
+add_task(function testAddRange() {
+ // Init an empty set.
+ let keySet = new MsgKeySet();
+ ok(!keySet.has(1));
+
+ // Add two ranges.
+ keySet.addRange(90, 99);
+ keySet.addRange(2, 19);
+
+ // Test members.
+ ok(!keySet.has(1));
+ ok(keySet.has(2));
+ ok(keySet.has(16));
+ ok(!keySet.has(20));
+ ok(keySet.has(99));
+ equal(keySet.toString(), "2-19,90-99");
+
+ // Init a set from a string.
+ keySet = new MsgKeySet("102,199");
+ ok(!keySet.has(22));
+ ok(keySet.has(199));
+
+ // Add two ranges.
+ keySet.addRange(2, 19);
+ keySet.addRange(12, 29);
+
+ // Test members.
+ ok(keySet.has(2));
+ ok(keySet.has(22));
+ ok(keySet.has(199));
+ equal(keySet.toString(), "2-29,102,199");
+});
+
+/**
+ * Test MsgKeySet.add works correctly.
+ */
+add_task(function testAdd() {
+ // Init an empty set.
+ let keySet = new MsgKeySet();
+ ok(!keySet.has(1));
+
+ // Add three values.
+ keySet.add(1);
+ keySet.add(2);
+ keySet.add(4);
+
+ // Test members.
+ ok(keySet.has(1));
+ ok(keySet.has(2));
+ ok(!keySet.has(3));
+ ok(keySet.has(4));
+ equal(keySet.toString(), "1-2,4");
+});
+
+/**
+ * Test MsgKeySet.getLastMissingRange works correctly.
+ */
+add_task(function testGetLastMissingRange() {
+ // Init a set.
+ let keySet = new MsgKeySet("2-9,12-29");
+
+ // Test `start` should be a value not already in keySet.
+ let [start, end] = keySet.getLastMissingRange(2, 33);
+ equal(start, 30);
+ equal(end, 33);
+
+ // Test `start` should be the lowest input value.
+ [start, end] = keySet.getLastMissingRange(33, 33);
+ equal(start, 33);
+ equal(end, 33);
+
+ // Test get missing old messages range works.
+ [start, end] = keySet.getLastMissingRange(3, 23);
+ equal(start, 10);
+ equal(end, 11);
+});
diff --git a/comm/mailnews/base/test/unit/test_accountMgr.js b/comm/mailnews/base/test/unit/test_accountMgr.js
new file mode 100644
index 0000000000..6036dccfa9
--- /dev/null
+++ b/comm/mailnews/base/test/unit/test_accountMgr.js
@@ -0,0 +1,102 @@
+/* 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/. */
+
+/**
+ * This tests that we cleanup the account prefs when the account manager is
+ * loaded. This entails removing duplicate accounts from
+ * mail.accountmanager.accounts list, and removing duplicate accounts with
+ * the same server.
+ */
+var { MailServices } = ChromeUtils.import(
+ "resource:///modules/MailServices.jsm"
+);
+
+function run_test() {
+ // Create account prefs with both kinds of duplication.
+
+ Services.prefs.setCharPref("mail.account.account1.identities", "id1");
+ Services.prefs.setCharPref("mail.account.account1.server", "server1");
+ Services.prefs.setCharPref("mail.account.account2.identities", "id2");
+ Services.prefs.setCharPref("mail.account.account2.server", "server2");
+ Services.prefs.setCharPref("mail.account.account4.identities", "id2");
+ Services.prefs.setCharPref("mail.account.account4.server", "server4");
+ Services.prefs.setCharPref("mail.account.account5.identities", "id3");
+ Services.prefs.setCharPref("mail.account.account5.server", "server5");
+ Services.prefs.setCharPref("mail.account.account6.identities", "id3");
+ Services.prefs.setCharPref("mail.account.account6.server", "server5");
+ Services.prefs.setCharPref("mail.server.server1.hostname", "Local Folders");
+ Services.prefs.setCharPref("mail.server.server1.type", "none");
+ Services.prefs.setCharPref("mail.server.server1.userName", "nobody");
+ Services.prefs.setCharPref(
+ "mail.server.server1.directory-rel",
+ "[ProfD]Mail/Local Folders"
+ );
+ Services.prefs.setCharPref("mail.server.server2.hostname", "Local Folders");
+ Services.prefs.setCharPref("mail.server.server2.type", "none");
+ Services.prefs.setCharPref("mail.server.server2.userName", "nobody");
+ Services.prefs.setCharPref(
+ "mail.server.server2.directory-rel",
+ "[ProfD]Mail/Local Folders-1"
+ );
+ Services.prefs.setCharPref("mail.server.server4.hostname", "mail.host4.org");
+ Services.prefs.setCharPref("mail.server.server4.type", "pop3");
+ Services.prefs.setCharPref("mail.server.server5.hostname", "pop3.host.org");
+ Services.prefs.setCharPref("mail.server.server5.type", "pop3");
+ Services.prefs.setCharPref(
+ "mail.server.server5.deferred_to_account",
+ "account2"
+ );
+
+ Services.prefs.setCharPref(
+ "mail.accountmanager.accounts",
+ "account4,account5,account5,account6,account1,account2"
+ );
+ // Set the default account to one we're going to get rid of. The account
+ // manager should recover relatively gracefully.
+ Services.prefs.setCharPref("mail.accountmanager.defaultaccount", "account6");
+
+ // This will force the load of the accounts setup above.
+ Assert.equal(MailServices.accounts.accounts.length, 3);
+ // Here all the accounts are local but the first account will behave as
+ // an actual local account and will be kept last always.
+ Assert.equal(
+ Services.prefs.getCharPref("mail.accountmanager.accounts"),
+ "account4,account5,account1"
+ );
+ let server5 = MailServices.accounts
+ .getIncomingServer("server5")
+ .QueryInterface(Ci.nsIPop3IncomingServer);
+ Assert.equal(server5.deferredToAccount, "account1");
+
+ // Just make sure this doesn't throw an exception, because we did remove the
+ // default account.
+ let defaultAccount = MailServices.accounts.defaultAccount;
+ Assert.equal(defaultAccount, null);
+
+ // Remove an account, and verify that the account list pref looks OK:
+ let server = MailServices.accounts.getIncomingServer("server4");
+
+ // We need to get the root folder to read from the folder cache
+ // before it gets removed or else we'll assert, because we're
+ // not completely initialized...
+ server.rootFolder.flags;
+
+ MailServices.accounts.removeAccount(
+ MailServices.accounts.FindAccountForServer(server)
+ );
+ Assert.equal(MailServices.accounts.accounts.length, 2);
+ Assert.equal(
+ Services.prefs.getCharPref("mail.accountmanager.accounts"),
+ "account5,account1"
+ );
+ // Make sure cleaning up duplicate accounts didn't hork accounts.
+ Assert.equal(
+ Services.prefs.getCharPref("mail.account.account1.server"),
+ "server1"
+ );
+ Assert.equal(
+ Services.prefs.getCharPref("mail.account.account5.server"),
+ "server5"
+ );
+}
diff --git a/comm/mailnews/base/test/unit/test_accountMgr2.js b/comm/mailnews/base/test/unit/test_accountMgr2.js
new file mode 100644
index 0000000000..a48685da00
--- /dev/null
+++ b/comm/mailnews/base/test/unit/test_accountMgr2.js
@@ -0,0 +1,199 @@
+/* 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/. */
+
+/**
+ * This tests various methods and attributes on nsIMsgAccountManager.
+ */
+var { MailServices } = ChromeUtils.import(
+ "resource:///modules/MailServices.jsm"
+);
+
+add_task(async function () {
+ // Create a couple of test accounts.
+ let acc1 = MailServices.accounts.createAccount();
+ acc1.incomingServer = MailServices.accounts.createIncomingServer(
+ "bob_imap",
+ "imap.example.com",
+ "imap"
+ );
+ let id1 = MailServices.accounts.createIdentity();
+ id1.email = "bob_imap@example.com";
+ acc1.addIdentity(id1);
+
+ let acc2 = MailServices.accounts.createAccount();
+ acc2.incomingServer = MailServices.accounts.createIncomingServer(
+ "bob_pop3",
+ "pop3.EXAMPLE.com.",
+ "pop3"
+ );
+ let id2 = MailServices.accounts.createIdentity();
+ id2.email = "bob_pop3@example.com";
+ acc2.addIdentity(id2);
+
+ // Add an identity shared by both accounts.
+ let id3 = MailServices.accounts.createIdentity();
+ id3.email = "bob_common@example.com";
+ acc1.addIdentity(id3);
+ acc2.addIdentity(id3);
+
+ // The special "Local Folders" account and server (server type is "none").
+ MailServices.accounts.createLocalMailAccount();
+
+ // Setup done. Now check that things are as we expect.
+
+ // At this point we should have 3 accounts and servers (imap, pop, local).
+ Assert.equal(MailServices.accounts.accounts.length, 3);
+ Assert.equal(MailServices.accounts.allServers.length, 3);
+
+ // The identities we explicitly created.
+ Assert.equal(MailServices.accounts.allIdentities.length, 3);
+
+ // Check we find the right number of identities associated with each server.
+ Assert.equal(
+ MailServices.accounts.getIdentitiesForServer(acc1.incomingServer).length,
+ 2
+ );
+ Assert.equal(
+ MailServices.accounts.getIdentitiesForServer(acc2.incomingServer).length,
+ 2
+ );
+ Assert.equal(
+ MailServices.accounts.getIdentitiesForServer(
+ MailServices.accounts.localFoldersServer
+ ).length,
+ 0
+ );
+
+ // id1 and id2 are on separate accounts (and servers).
+ Assert.equal(MailServices.accounts.getServersForIdentity(id1).length, 1);
+ Assert.equal(MailServices.accounts.getServersForIdentity(id2).length, 1);
+ // id3 is shared.
+ Assert.equal(MailServices.accounts.getServersForIdentity(id3).length, 2);
+
+ // Does allFolders return the default folders we'd expect?
+ // IMAP has Inbox only.
+ // POP3 and local accounts both have Inbox and Trash.
+ Assert.equal(MailServices.accounts.allFolders.length, 1 + 2 + 2);
+
+ // Let's ditch the IMAP account.
+ MailServices.accounts.removeAccount(acc1);
+
+ Assert.equal(MailServices.accounts.accounts.length, 2);
+ Assert.equal(MailServices.accounts.allServers.length, 2);
+
+ // It should have taken the imap-specific identity with it.
+ Assert.equal(MailServices.accounts.allIdentities.length, 2);
+
+ // Test a special hostname.
+ const acc4 = MailServices.accounts.createAccount();
+ acc4.incomingServer = MailServices.accounts.createIncomingServer(
+ "bob_unavail",
+ "0.0.0.0.", // Note ending dot which would not do anything for an IP.
+ "pop3"
+ );
+ let id4 = MailServices.accounts.createIdentity();
+ id4.email = "bob_unavail@example.com";
+ acc4.addIdentity(id4);
+
+ Assert.equal(
+ MailServices.accounts.accounts.length,
+ 3,
+ "acc4 should be in accounts"
+ );
+
+ // Test that an account with empty server hostname doesn't even get listed.
+ const serverKey = acc4.incomingServer.key;
+ Services.prefs.setStringPref(`mail.server.${serverKey}.hostname`, "");
+ MailServices.accounts.unloadAccounts();
+ MailServices.accounts.loadAccounts();
+ Assert.equal(
+ MailServices.accounts.accounts.length,
+ 2,
+ "invalid acc4 should have been removed"
+ );
+ Assert.equal(
+ Services.prefs.getCharPref("mail.accountmanager.accounts"),
+ "account2,account3",
+ "listed accounts should be correct after testing blank host"
+ );
+
+ // Test that an account that had punycode hostname entered is found.
+ const acc5 = MailServices.accounts.createAccount();
+ acc5.incomingServer = MailServices.accounts.createIncomingServer(
+ "bob_imap5",
+ "xn--thnderbird-beb.test",
+ "imap"
+ );
+ const id5 = MailServices.accounts.createIdentity();
+ id5.email = "bob_imap5@xn--thnderbird-beb.test";
+ acc5.addIdentity(id5);
+
+ MailServices.accounts.unloadAccounts();
+ MailServices.accounts.loadAccounts();
+
+ Assert.equal(
+ MailServices.accounts.accounts.length,
+ 3,
+ "added acc5 should still be listed"
+ );
+
+ Assert.equal(
+ Services.prefs.getCharPref("mail.accountmanager.accounts"),
+ "account2,account5,account3",
+ "listed accounts should be correct after testing punycode host"
+ );
+ const punyServer = MailServices.accounts.findServerByURI(
+ Services.io.newURI("imap://xn--thnderbird-beb.test:143/INBOX")
+ );
+ Assert.ok(
+ punyServer?.hostName,
+ "should find server by uri for punycode hostname"
+ );
+
+ const punyServer2 = MailServices.accounts.findServerByURI(
+ Services.io.newURI("imap://thünderbird.test:143/INBOX")
+ );
+ Assert.ok(
+ punyServer2?.hostName,
+ "should find ACE server by normalized IDN hostname"
+ );
+
+ // Test that an account with IDN hostname entered is found.
+ const acc6 = MailServices.accounts.createAccount();
+ acc6.incomingServer = MailServices.accounts.createIncomingServer(
+ "bob_imap6",
+ "thünderbird.example",
+ "imap"
+ );
+ const id6 = MailServices.accounts.createIdentity();
+ id6.email = "bob_imap6@thünderbird.example";
+ acc6.addIdentity(id6);
+
+ MailServices.accounts.unloadAccounts();
+ MailServices.accounts.loadAccounts();
+
+ Assert.equal(
+ MailServices.accounts.accounts.length,
+ 4,
+ "added acc6 should still be listed"
+ );
+
+ Assert.equal(
+ Services.prefs.getCharPref("mail.accountmanager.accounts"),
+ "account2,account5,account6,account3",
+ "listed accounts should be correct after testing IDN host"
+ );
+ const idnServer = MailServices.accounts.findServerByURI(
+ Services.io.newURI("imap://thünderbird.example:143/INBOX")
+ );
+ Assert.ok(idnServer?.hostName, "should find server by uri for IDN hostname");
+
+ const idnServer2 = MailServices.accounts.findServerByURI(
+ Services.io.newURI("imap://xn--thnderbird-beb.example:143/INBOX")
+ );
+ Assert.ok(
+ idnServer2?.hostName,
+ "should find idn server by by ACE encodeed uri"
+ );
+});
diff --git a/comm/mailnews/base/test/unit/test_accountMgrCustomTypes.js b/comm/mailnews/base/test/unit/test_accountMgrCustomTypes.js
new file mode 100644
index 0000000000..f8b8f707c7
--- /dev/null
+++ b/comm/mailnews/base/test/unit/test_accountMgrCustomTypes.js
@@ -0,0 +1,94 @@
+/* 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/. */
+
+/**
+ * This tests that accounts with invalid types, such as could be created
+ * from an extension, do not disappear immediately when the extension
+ * is unloaded.
+ *
+ * Adapted from test_AccountMgr.js by Kent James <kent@caspia.com>
+ */
+
+function run_test() {
+ Services.prefs.setCharPref("mail.account.account1.identities", "id1");
+ Services.prefs.setCharPref("mail.account.account1.server", "server1");
+ Services.prefs.setCharPref("mail.account.account2.identities", "id2");
+ Services.prefs.setCharPref("mail.account.account2.server", "server2");
+ Services.prefs.setCharPref("mail.server.server1.hostname", "Local Folders");
+ Services.prefs.setCharPref("mail.server.server1.type", "none");
+ Services.prefs.setCharPref("mail.server.server1.userName", "nobody");
+ Services.prefs.setCharPref(
+ "mail.server.server1.directory-rel",
+ "[ProfD]Mail/Local Folders"
+ );
+
+ // Here we are simulating a server and account that is added by an
+ // extension, but that extension is currently unloaded. The extension
+ // added "secondsToLeaveUnavailable" (though a typical value would be
+ // one month, not 2 seconds!) to tell the core code to leave this alone
+ // for awhile if the extension is unloaded.
+ Services.prefs.setCharPref("mail.server.server2.hostname", "pop3.host.org");
+ Services.prefs.setCharPref("mail.server.server2.type", "invalid");
+ Services.prefs.setIntPref("mail.server.server2.secondsToLeaveUnavailable", 2);
+
+ Services.prefs.setCharPref(
+ "mail.accountmanager.accounts",
+ "account2,account1"
+ );
+ Services.prefs.setCharPref("mail.accountmanager.defaultaccount", "account1");
+
+ // This will force the load of the accounts setup above.
+ // We don't see the invalid account.
+ Assert.equal(MailServices.accounts.accounts.length, 1);
+
+ // But it is really there.
+ Assert.equal(
+ Services.prefs.getCharPref("mail.accountmanager.accounts"),
+ "account2,account1"
+ );
+
+ // Add a new account (so that we can check if this clobbers the existing
+ // inactive account or its server).
+ let newAccount = MailServices.accounts.createAccount();
+ let newIdentity = MailServices.accounts.createIdentity();
+ newAccount.addIdentity(newIdentity);
+ newAccount.defaultIdentity = newIdentity;
+ newAccount.incomingServer = MailServices.accounts.createIncomingServer(
+ "somename",
+ "somehost.example.com",
+ "pop3"
+ );
+
+ // No collisions with the inactive account.
+ Assert.notEqual(newIdentity.key, "id2");
+ Assert.notEqual(newAccount.incomingServer.key, "server2");
+ Assert.notEqual(newAccount.key, "account2");
+ Assert.equal(MailServices.accounts.accounts.length, 2);
+
+ MailServices.accounts.unloadAccounts();
+
+ // Set the unavailable account to a valid type, and watch it appear.
+ Services.prefs.setCharPref("mail.server.server2.type", "pop3");
+ Assert.equal(MailServices.accounts.accounts.length, 3);
+
+ // Make it bad again, and reload it to restart the timeout before delete.
+ MailServices.accounts.unloadAccounts();
+ Services.prefs.setCharPref("mail.server.server2.type", "invalid");
+ Assert.equal(MailServices.accounts.accounts.length, 2);
+ MailServices.accounts.unloadAccounts();
+
+ // Now let the bad type timeout, and watch it magically disappear!
+ do_test_pending();
+ do_timeout(3000, function () {
+ Assert.equal(MailServices.accounts.accounts.length, 2);
+
+ // It is now gone.
+ Assert.equal(
+ Services.prefs.getCharPref("mail.accountmanager.accounts"),
+ newAccount.key + ",account1"
+ );
+
+ do_test_finished();
+ });
+}
diff --git a/comm/mailnews/base/test/unit/test_accountMgrMovedLocalFolders.js b/comm/mailnews/base/test/unit/test_accountMgrMovedLocalFolders.js
new file mode 100644
index 0000000000..5972012e85
--- /dev/null
+++ b/comm/mailnews/base/test/unit/test_accountMgrMovedLocalFolders.js
@@ -0,0 +1,43 @@
+/* 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/. */
+
+var { MailServices } = ChromeUtils.import(
+ "resource:///modules/MailServices.jsm"
+);
+
+function run_test() {
+ // Create account prefs with Local Folders in the middle
+ Services.prefs.setCharPref("mail.account.account1.identities", "id1");
+ Services.prefs.setCharPref("mail.account.account1.server", "server1");
+ Services.prefs.setCharPref("mail.account.account4.identities", "id2");
+ Services.prefs.setCharPref("mail.account.account4.server", "server4");
+ Services.prefs.setCharPref("mail.account.account5.identities", "id3");
+ Services.prefs.setCharPref("mail.account.account5.server", "server5");
+ Services.prefs.setCharPref("mail.account.account6.identities", "id3");
+ Services.prefs.setCharPref("mail.account.account6.server", "server5");
+ Services.prefs.setCharPref("mail.server.server1.hostname", "Local Folders");
+ Services.prefs.setCharPref("mail.server.server1.type", "none");
+ Services.prefs.setCharPref("mail.server.server1.userName", "nobody");
+ Services.prefs.setCharPref(
+ "mail.server.server1.directory-rel",
+ "[ProfD]Mail/Local Folders"
+ );
+ Services.prefs.setCharPref("mail.server.server4.hostname", "mail.host4.org");
+ Services.prefs.setCharPref("mail.server.server4.type", "pop3");
+ Services.prefs.setCharPref("mail.server.server5.hostname", "pop3.host.org");
+ Services.prefs.setCharPref("mail.server.server5.type", "pop3");
+
+ Services.prefs.setCharPref(
+ "mail.accountmanager.accounts",
+ "account4,account1,account5"
+ );
+ Services.prefs.setCharPref("mail.accountmanager.defaultaccount", "account4");
+
+ // This will force the load of the accounts setup above.
+ Assert.equal(MailServices.accounts.accounts.length, 3);
+ Assert.equal(
+ Services.prefs.getCharPref("mail.accountmanager.accounts"),
+ "account4,account1,account5"
+ );
+}
diff --git a/comm/mailnews/base/test/unit/test_accountMgrRemoveDefault.js b/comm/mailnews/base/test/unit/test_accountMgrRemoveDefault.js
new file mode 100644
index 0000000000..640c314499
--- /dev/null
+++ b/comm/mailnews/base/test/unit/test_accountMgrRemoveDefault.js
@@ -0,0 +1,55 @@
+/* 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/. */
+
+/**
+ * This tests that if the default account is removed, the default becomes
+ * another account or null. The removed account must not remain the default.
+ */
+var { MailServices } = ChromeUtils.import(
+ "resource:///modules/MailServices.jsm"
+);
+
+function run_test() {
+ // Create account prefs.
+
+ Services.prefs.setCharPref("mail.account.account1.identities", "id1");
+ Services.prefs.setCharPref("mail.account.account1.server", "server1");
+ Services.prefs.setCharPref("mail.account.account2.identities", "id2");
+ Services.prefs.setCharPref("mail.account.account2.server", "server2");
+ Services.prefs.setCharPref("mail.account.account3.identities", "id3");
+ Services.prefs.setCharPref("mail.account.account3.server", "server3");
+
+ Services.prefs.setCharPref("mail.server.server1.hostname", "Local Folders");
+ Services.prefs.setCharPref("mail.server.server1.type", "none");
+ Services.prefs.setCharPref("mail.server.server2.hostname", "host2.invalid");
+ Services.prefs.setCharPref("mail.server.server2.type", "pop3");
+ Services.prefs.setCharPref("mail.server.server3.hostname", "host3.invalid");
+ Services.prefs.setCharPref("mail.server.server3.type", "pop3");
+
+ Services.prefs.setCharPref(
+ "mail.accountmanager.accounts",
+ "account2,account3,account1"
+ );
+ Services.prefs.setCharPref("mail.accountmanager.defaultaccount", "account3");
+
+ // Load of the accounts setup above.
+ Assert.equal(MailServices.accounts.accounts.length, 3);
+ Assert.equal(
+ Services.prefs.getCharPref("mail.accountmanager.accounts"),
+ "account2,account3,account1"
+ );
+ Assert.equal(MailServices.accounts.defaultAccount?.key, "account3");
+
+ // Remove the default account, account3. The default should be set to a
+ // sensible replacement, account2.
+
+ MailServices.accounts.removeAccount(MailServices.accounts.defaultAccount);
+ Assert.equal(MailServices.accounts.defaultAccount?.key, "account2");
+
+ // Remove the default account, account2. No remaining accounts can be the
+ // default, so it should become null.
+
+ MailServices.accounts.removeAccount(MailServices.accounts.defaultAccount);
+ Assert.equal(MailServices.accounts.defaultAccount, null);
+}
diff --git a/comm/mailnews/base/test/unit/test_accountMigration.js b/comm/mailnews/base/test/unit/test_accountMigration.js
new file mode 100644
index 0000000000..d2bb801c49
--- /dev/null
+++ b/comm/mailnews/base/test/unit/test_accountMigration.js
@@ -0,0 +1,184 @@
+/* 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/. */
+
+/**
+ * This tests that we don't try to reset the mail.server.server<n>.authMethod
+ * preference every time we run the migration code, and other migration stuff
+ */
+
+var { migrateMailnews } = ChromeUtils.import(
+ "resource:///modules/MailnewsMigrator.jsm"
+);
+
+/* import-globals-from ../../../test/resources/abSetup.js */
+load("../../../resources/abSetup.js");
+
+function testPermission(aURI) {
+ let principal = Services.scriptSecurityManager.createContentPrincipal(
+ aURI,
+ {}
+ );
+ return Services.perms.testPermissionFromPrincipal(principal, "image");
+}
+
+function run_test() {
+ // Set up some basic accounts with limited prefs - enough to satisfy the
+ // migrator.
+ Services.prefs.setCharPref("mail.account.account1.server", "server1");
+ Services.prefs.setCharPref("mail.account.account2.server", "server2");
+ Services.prefs.setCharPref("mail.account.account3.server", "server3");
+
+ // Server1 has nothing set.
+
+ // Server2 has useSecAuth set to true, auth_login unset
+ Services.prefs.setBoolPref("mail.server.server2.useSecAuth", true);
+
+ Services.prefs.setCharPref(
+ "mail.accountmanager.accounts",
+ "account1,account2,account3"
+ );
+
+ // Set server1 and server2 username and hostname to test clientid population.
+ Services.prefs.setCharPref("mail.server.server1.userName", "testuser1");
+ Services.prefs.setCharPref("mail.server.server2.userName", "testuser2");
+ Services.prefs.setCharPref("mail.server.server1.type", "imap");
+ Services.prefs.setCharPref("mail.server.server2.type", "imap");
+ Services.prefs.setCharPref(
+ "mail.server.server1.hostname",
+ "mail.sampledomain1.com"
+ );
+ Services.prefs.setCharPref(
+ "mail.server.server2.hostname",
+ "mail.sampledomain2.com"
+ );
+
+ loadABFile("data/remoteContent", kPABData.fileName);
+
+ let uriAllowed = Services.io.newURI(
+ "chrome://messenger/content/email=yes@test.invalid"
+ );
+ let uriAllowed2 = Services.io.newURI(
+ "chrome://messenger/content/email=yes2@test.invalid"
+ );
+ let uriDisallowed = Services.io.newURI(
+ "chrome://messenger/content/email=no@test.invalid"
+ );
+
+ // Check that this email that according to the ab data has (had!)
+ // remote content premissions, has no premissions pre migration.
+ Assert.equal(testPermission(uriAllowed), Services.perms.UNKNOWN_ACTION);
+ Assert.equal(testPermission(uriAllowed2), Services.perms.UNKNOWN_ACTION);
+ Assert.equal(testPermission(uriDisallowed), Services.perms.UNKNOWN_ACTION);
+
+ // Now migrate the prefs.
+ migrateMailnews();
+
+ // Check that server1 and server2 have the same clientid.
+ Assert.ok(Services.prefs.prefHasUserValue("mail.server.server1.clientid"));
+ Assert.ok(Services.prefs.prefHasUserValue("mail.server.server2.clientid"));
+
+ // Check that server3 didn't get a clientid, since it has no userName.
+ // This is the case for nntp.
+ Assert.ok(!Services.prefs.prefHasUserValue("mail.server.server3.clientid"));
+
+ // Check what has been set.
+ Assert.ok(!Services.prefs.prefHasUserValue("mail.server.server1.authMethod"));
+ Assert.ok(Services.prefs.prefHasUserValue("mail.server.server2.authMethod"));
+ Assert.equal(
+ Services.prefs.getIntPref("mail.server.server2.authMethod"),
+ Ci.nsMsgAuthMethod.secure
+ );
+
+ // Now clear the authMethod for set for server2. This simulates the user
+ // setting the value back to "3", i.e. Ci.nsMsgAuthMethod.passwordCleartext.
+ Services.prefs.clearUserPref("mail.server.server2.authMethod");
+
+ // Now attempt migration again, e.g. a second load of TB
+ migrateMailnews();
+
+ // This time around, both of these should not be set.
+ Assert.ok(!Services.prefs.prefHasUserValue("mail.server.server1.authMethod"));
+ Assert.ok(!Services.prefs.prefHasUserValue("mail.server.server2.authMethod"));
+
+ //
+ // Now check SMTP
+ //
+
+ Services.prefs.setCharPref("mail.smtpservers", "smtp1,smtp2,smtp3");
+
+ // smtp1 has nothing set.
+
+ // smtp2 has useSecAuth set to true, auth_method unset
+ Services.prefs.setBoolPref("mail.smtpserver.smtp2.useSecAuth", true);
+
+ // Set server1 and server2 username and hostname to test clientid population.
+ Services.prefs.setCharPref("mail.smtpserver.smtp1.username", "testuser1");
+ Services.prefs.setCharPref("mail.smtpserver.smtp2.username", "testuser2");
+ Services.prefs.setCharPref(
+ "mail.smtpserver.smtp1.hostname",
+ "mail.sampledomain1.com"
+ );
+ Services.prefs.setCharPref(
+ "mail.smtpserver.smtp2.hostname",
+ "mail.sampledomain2.com"
+ );
+ Services.prefs.setCharPref(
+ "mail.smtpserver.smtp3.hostname",
+ "mail.nousername.example.com"
+ );
+
+ // Migration should now have added permissions for the address that had them
+ // and not for the one that didn't have them.
+ Assert.ok(Services.prefs.getIntPref("mail.ab_remote_content.migrated") > 0);
+ Assert.equal(testPermission(uriAllowed), Services.perms.ALLOW_ACTION);
+ Assert.equal(testPermission(uriAllowed2), Services.perms.ALLOW_ACTION);
+ Assert.equal(testPermission(uriDisallowed), Services.perms.UNKNOWN_ACTION);
+
+ // Now migrate the prefs
+ migrateMailnews();
+
+ // Check that smtpserver 1 and smtpserver 2 now have a clientid.
+ Assert.ok(Services.prefs.prefHasUserValue("mail.smtpserver.smtp1.clientid"));
+ Assert.ok(Services.prefs.prefHasUserValue("mail.smtpserver.smtp2.clientid"));
+
+ // Check that the smtp3server 2 got a clientid, even if it has no
+ // username. All SMTP servers don't require a username.
+ Assert.ok(Services.prefs.prefHasUserValue("mail.smtpserver.smtp3.clientid"));
+
+ Assert.ok(
+ !Services.prefs.prefHasUserValue("mail.smtpserver.smtp1.authMethod")
+ );
+ Assert.ok(
+ Services.prefs.prefHasUserValue("mail.smtpserver.smtp2.authMethod")
+ );
+ Assert.equal(
+ Services.prefs.getIntPref("mail.smtpserver.smtp2.authMethod"),
+ Ci.nsMsgAuthMethod.secure
+ );
+
+ // Now clear the authMethod for set for smtp2. This simulates the user
+ // setting the value back to "3", i.e. Ci.nsMsgAuthMethod.passwordCleartext.
+ Services.prefs.clearUserPref("mail.smtpserver.smtp2.authMethod");
+
+ // Now clear the mail.server.server1.clientid to test re-population.
+ Services.prefs.clearUserPref("mail.server.server2.clientid");
+
+ // Now attempt migration again, e.g. a second load of TB
+ migrateMailnews();
+
+ // This time around, both of these should not be set.
+ Assert.ok(
+ !Services.prefs.prefHasUserValue("mail.smtpserver.smtp1.authMethod")
+ );
+ Assert.ok(
+ !Services.prefs.prefHasUserValue("mail.smtpserver.smtp2.authMethod")
+ );
+
+ // The server2 clientid should be the same as the smtpserver2 now since
+ // they are for the same mail.sampledomain2.com domain.
+ Assert.equal(
+ Services.prefs.getCharPref("mail.smtpserver.smtp2.clientid"),
+ Services.prefs.getCharPref("mail.server.server2.clientid")
+ );
+}
diff --git a/comm/mailnews/base/test/unit/test_acctRepair.js b/comm/mailnews/base/test/unit/test_acctRepair.js
new file mode 100644
index 0000000000..b702dcfd61
--- /dev/null
+++ b/comm/mailnews/base/test/unit/test_acctRepair.js
@@ -0,0 +1,56 @@
+/* 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/. */
+
+/**
+ * This tests that we recover from having a local folders server
+ * without having an account that points at it.
+ */
+
+var { MailServices } = ChromeUtils.import(
+ "resource:///modules/MailServices.jsm"
+);
+
+function run_test() {
+ // Create account prefs with both kinds of duplication.
+
+ Services.prefs.setCharPref("mail.account.account2.identities", "id2");
+ Services.prefs.setCharPref("mail.account.account2.server", "server1");
+ Services.prefs.setCharPref("mail.account.account6.identities", "id3");
+ Services.prefs.setCharPref("mail.account.account6.server", "server5");
+ Services.prefs.setCharPref("mail.server.server1.hostname", "Local Folders");
+ Services.prefs.setCharPref("mail.server.server1.type", "none");
+ Services.prefs.setCharPref("mail.server.server1.userName", "nobody");
+ Services.prefs.setCharPref(
+ "mail.server.server1.directory-rel",
+ "[ProfD]Mail/Local Folders"
+ );
+ Services.prefs.setCharPref("mail.server.server5.hostname", "pop3.host.org");
+ Services.prefs.setCharPref("mail.server.server5.type", "pop3");
+ Services.prefs.setCharPref(
+ "mail.server.server5.deferred_to_account",
+ "account2"
+ );
+
+ Services.prefs.setCharPref("mail.accountmanager.accounts", "account6");
+ Services.prefs.setCharPref("mail.accountmanager.defaultaccount", "account6");
+ Services.prefs.setCharPref(
+ "mail.accountmanager.localfoldersserver",
+ "server1"
+ );
+ // This will force the load of the accounts setup above.
+ // We should have created an account for the local folders.
+ Assert.equal(MailServices.accounts.accounts.length, 2);
+ Assert.equal(
+ Services.prefs.getCharPref("mail.accountmanager.accounts"),
+ "account6,account7"
+ );
+ Assert.equal(
+ Services.prefs.getCharPref("mail.account.account7.server"),
+ "server1"
+ );
+ let server5 = MailServices.accounts
+ .getIncomingServer("server5")
+ .QueryInterface(Ci.nsIPop3IncomingServer);
+ Assert.equal(server5.deferredToAccount, "account7");
+}
diff --git a/comm/mailnews/base/test/unit/test_bccInDatabase.js b/comm/mailnews/base/test/unit/test_bccInDatabase.js
new file mode 100644
index 0000000000..ad9ff039b7
--- /dev/null
+++ b/comm/mailnews/base/test/unit/test_bccInDatabase.js
@@ -0,0 +1,54 @@
+/* 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/. */
+
+/*
+ * Testing of bcc in message summary file added in bug 481667
+ */
+
+var { MailServices } = ChromeUtils.import(
+ "resource:///modules/MailServices.jsm"
+);
+
+var hdr;
+
+function run_test() {
+ localAccountUtils.loadLocalMailAccount();
+
+ var copyListener = {
+ OnStartCopy() {},
+ OnProgress(aProgress, aProgressMax) {},
+ SetMessageKey(aKey) {
+ hdr = localAccountUtils.inboxFolder.GetMessageHeader(aKey);
+ },
+ SetMessageId(aMessageId) {},
+ OnStopCopy(aStatus) {
+ continueTest();
+ },
+ };
+
+ // Get a message into the local filestore.
+ var draft = do_get_file("../../../data/draft1");
+ do_test_pending();
+ MailServices.copy.copyFileMessage(
+ draft,
+ localAccountUtils.inboxFolder,
+ null,
+ false,
+ 0,
+ "",
+ copyListener,
+ null
+ );
+}
+
+function continueTest() {
+ // dump("\nbccList >" + hdr.bccList);
+ // dump("\nccList >" + hdr.ccList);
+ // dump("\n");
+ Assert.ok(hdr.bccList.includes("Another Person"));
+ Assert.ok(hdr.bccList.includes("<u1@example.com>"));
+ Assert.ok(!hdr.bccList.includes("IDoNotExist"));
+ hdr = null;
+ do_test_finished();
+}
diff --git a/comm/mailnews/base/test/unit/test_bug428427.js b/comm/mailnews/base/test/unit/test_bug428427.js
new file mode 100644
index 0000000000..f00f2bd571
--- /dev/null
+++ b/comm/mailnews/base/test/unit/test_bug428427.js
@@ -0,0 +1,249 @@
+/* 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/. */
+
+// Test of message count changes in virtual folder views
+
+var { MailServices } = ChromeUtils.import(
+ "resource:///modules/MailServices.jsm"
+);
+
+var bugmail1 = do_get_file("../../../data/bugmail1");
+// main test
+
+// the headers for the test messages. All messages are identical, but
+// have different properties set on them.
+var hdrs = [];
+
+// how many identical messages to load
+var messageCount = 5;
+
+// tag used with test messages
+var tag1 = "istag";
+
+function run_test() {
+ localAccountUtils.loadLocalMailAccount();
+
+ // Get messageCount messages into the local filestore.
+ do_test_pending();
+
+ // function setupVirtualFolder() continues the testing after copyFileMessage.
+ MailServices.copy.copyFileMessage(
+ bugmail1,
+ localAccountUtils.inboxFolder,
+ null,
+ false,
+ 0,
+ "",
+ copyListener,
+ null
+ );
+ return true;
+}
+
+// nsIMsgCopyServiceListener implementation
+var copyListener = {
+ OnStartCopy() {},
+ OnProgress(aProgress, aProgressMax) {},
+ SetMessageKey(aKey) {
+ hdrs.push(localAccountUtils.inboxFolder.GetMessageHeader(aKey));
+ },
+ SetMessageId(aMessageId) {},
+ OnStopCopy(aStatus) {
+ if (--messageCount) {
+ MailServices.copy.copyFileMessage(
+ bugmail1,
+ localAccountUtils.inboxFolder,
+ null,
+ false,
+ 0,
+ "",
+ copyListener,
+ null
+ );
+ } else {
+ try {
+ setupVirtualFolder();
+ } catch (ex) {
+ dump(ex);
+ }
+ }
+ },
+};
+
+var virtualFolder;
+var numTotalMessages;
+var numUnreadMessages;
+
+// virtual folder setup
+function setupVirtualFolder() {
+ // add as valid tag tag1, though probably not really necessary
+ MailServices.tags.addTagForKey(tag1, tag1, null, null);
+
+ // add tag1 to 4 messages
+ let messages0to3 = [hdrs[0], hdrs[1], hdrs[2], hdrs[3]];
+ localAccountUtils.inboxFolder.addKeywordsToMessages(messages0to3, tag1);
+
+ // set 3 messages unread, 2 messages read
+ let messages0to2 = [hdrs[0], hdrs[1], hdrs[2]];
+ localAccountUtils.inboxFolder.markMessagesRead(messages0to2, false);
+
+ let messages3to4 = [hdrs[3], hdrs[4]];
+ localAccountUtils.inboxFolder.markMessagesRead(messages3to4, true);
+
+ // search will look for tag tag1 in the inbox folder
+ var searchTerm = makeSearchTerm(
+ localAccountUtils.inboxFolder,
+ tag1,
+ Ci.nsMsgSearchAttrib.Keywords,
+ Ci.nsMsgSearchOp.Contains
+ );
+
+ dump("creating virtual folder\n");
+ var rootFolder = localAccountUtils.incomingServer.rootMsgFolder;
+ virtualFolder = CreateVirtualFolder(
+ "VfTest",
+ rootFolder,
+ localAccountUtils.inboxFolder.URI,
+ searchTerm,
+ false
+ );
+
+ // Setup search session. Execution continues with testVirtualFolder()
+ // after search is done.
+
+ var searchSession = Cc[
+ "@mozilla.org/messenger/searchSession;1"
+ ].createInstance(Ci.nsIMsgSearchSession);
+ searchSession.addScopeTerm(
+ Ci.nsMsgSearchScope.offlineMail,
+ localAccountUtils.inboxFolder
+ );
+ searchSession.appendTerm(searchTerm, false);
+ searchSession.registerListener(searchListener);
+ dump("starting search of vf\n");
+ searchSession.search(null);
+}
+
+// partially based on gSearchNotificationListener in searchBar.js
+// nsIMsgSearchNotify implementation
+var searchListener = {
+ onNewSearch() {
+ dump("in onnewsearch\n");
+ numTotalMessages = 0;
+ numUnreadMessages = 0;
+ },
+ onSearchHit(dbHdr, folder) {
+ print("Search hit, isRead is " + dbHdr.isRead);
+ numTotalMessages++;
+ if (!dbHdr.isRead) {
+ numUnreadMessages++;
+ }
+ },
+ onSearchDone(status) {
+ print("Finished search hitCount = " + numTotalMessages);
+ var db = virtualFolder.msgDatabase;
+ var dbFolderInfo = db.dBFolderInfo;
+ dbFolderInfo.numMessages = numTotalMessages;
+ dbFolderInfo.numUnreadMessages = numUnreadMessages;
+ virtualFolder.updateSummaryTotals(true);
+ print("virtual folder unread is " + virtualFolder.getNumUnread(false));
+ testVirtualFolder();
+ },
+};
+
+function testVirtualFolder() {
+ // basic functionality tests
+
+ // total messages matching search
+ Assert.equal(4, virtualFolder.getTotalMessages(false));
+
+ // total unread messages in search
+ Assert.equal(3, virtualFolder.getNumUnread(false));
+
+ // change unread of one item in search to decrease count
+ localAccountUtils.inboxFolder.markMessagesRead([hdrs[0]], true);
+ virtualFolder.updateSummaryTotals(true);
+
+ Assert.equal(2, virtualFolder.getNumUnread(false));
+
+ // failures fixed in this bug
+
+ // remove tag from one item to decrease count
+ var message1 = [hdrs[1]];
+ localAccountUtils.inboxFolder.removeKeywordsFromMessages(message1, tag1);
+ virtualFolder.updateSummaryTotals(true);
+ Assert.equal(3, virtualFolder.getTotalMessages(false));
+ Assert.equal(1, virtualFolder.getNumUnread(false));
+
+ // End of test, so release our header references
+ hdrs = null;
+
+ do_test_finished();
+ return true;
+}
+
+// helper functions
+
+// adapted from commandglue.js
+function CreateVirtualFolder(
+ newName,
+ parentFolder,
+ searchFolderURIs,
+ searchTerm,
+ searchOnline
+) {
+ var newFolder = parentFolder.addSubfolder(newName);
+ newFolder.setFlag(Ci.nsMsgFolderFlags.Virtual);
+ var vfdb = newFolder.msgDatabase;
+ var searchTermString = getSearchTermString(searchTerm);
+
+ var dbFolderInfo = vfdb.dBFolderInfo;
+ // set the view string as a property of the db folder info
+ // set the original folder name as well.
+ dbFolderInfo.setCharProperty("searchStr", searchTermString);
+ dbFolderInfo.setCharProperty("searchFolderUri", searchFolderURIs);
+ dbFolderInfo.setBooleanProperty("searchOnline", searchOnline);
+ // This fails because the folder doesn't exist - why were we doing it?
+ // vfdb.summaryValid = true;
+ vfdb.close(true);
+ // use acctMgr to setup the virtual folder listener
+ var acctMgr = MailServices.accounts.QueryInterface(Ci.nsIFolderListener);
+ // print(acctMgr);
+ acctMgr.onFolderAdded(parentFolder, newFolder);
+ return newFolder;
+}
+
+function getSearchTermString(term) {
+ var condition = "";
+
+ if (condition.length > 1) {
+ condition += " ";
+ }
+
+ if (term.matchAll) {
+ condition = "ALL";
+ }
+ condition += term.booleanAnd ? "AND (" : "OR (";
+ condition += term.termAsString + ")";
+ return condition;
+}
+
+// Create a search term for searching aFolder
+// using aAttrib, aOp, and string aStrValue
+function makeSearchTerm(aFolder, aStrValue, aAttrib, aOp) {
+ // use a temporary search session
+ var searchSession = Cc[
+ "@mozilla.org/messenger/searchSession;1"
+ ].createInstance(Ci.nsIMsgSearchSession);
+ searchSession.addScopeTerm(Ci.nsMsgSearchScope.offlineMail, aFolder);
+ var searchTerm = searchSession.createTerm();
+ var value = searchTerm.value;
+ value.str = aStrValue;
+ searchTerm.value = value;
+ searchTerm.attrib = aAttrib;
+ searchTerm.op = aOp;
+ searchTerm.booleanAnd = false;
+ searchSession = null;
+ return searchTerm;
+}
diff --git a/comm/mailnews/base/test/unit/test_bug434810.js b/comm/mailnews/base/test/unit/test_bug434810.js
new file mode 100644
index 0000000000..f41eea82d9
--- /dev/null
+++ b/comm/mailnews/base/test/unit/test_bug434810.js
@@ -0,0 +1,27 @@
+/* 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/. */
+
+// Test of setup of localMailFolders
+
+function run_test() {
+ localAccountUtils.loadLocalMailAccount();
+
+ var rootFolder = localAccountUtils.incomingServer.rootFolder;
+
+ var msgProps = Services.strings.createBundle(
+ "chrome://messenger/locale/messenger.properties"
+ );
+
+ var expectedFolders = ["Inbox"]; // Inbox hard-coded in LocalAccountUtils.jsm
+
+ // These two MailNews adds by default
+ expectedFolders.push(msgProps.GetStringFromName("outboxFolderName"));
+ expectedFolders.push(msgProps.GetStringFromName("trashFolderName"));
+
+ Assert.equal(rootFolder.numSubFolders, expectedFolders.length);
+ for (var i = 0; i < expectedFolders.length; ++i) {
+ Assert.ok(rootFolder.containsChildNamed(expectedFolders[i]));
+ }
+ Assert.ok(rootFolder.isAncestorOf(localAccountUtils.inboxFolder));
+}
diff --git a/comm/mailnews/base/test/unit/test_bug471682.js b/comm/mailnews/base/test/unit/test_bug471682.js
new file mode 100644
index 0000000000..73e431d84f
--- /dev/null
+++ b/comm/mailnews/base/test/unit/test_bug471682.js
@@ -0,0 +1,118 @@
+/* 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/. */
+
+/*
+ * Test of message database validity on local copy from bug 471682. What
+ * we want to do here is to copy a couple of message to a new folder, and
+ * then compare the date and filesize of the folder file with the
+ * stored result in dbfolderinfo. If they don't match, that's bad.
+ */
+var { MailServices } = ChromeUtils.import(
+ "resource:///modules/MailServices.jsm"
+);
+
+var bugmail1 = do_get_file("../../../data/bugmail1");
+var gHdr; // header of test message in local folder
+
+localAccountUtils.loadLocalMailAccount();
+// create a subfolder as a target for copies
+var gSubfolder =
+ localAccountUtils.inboxFolder.createLocalSubfolder("subfolder");
+
+function run_test() {
+ // make sure we're using berkeley mailbox format here since this test
+ // assumes berkeley mailbox format.
+ if (
+ Services.prefs.getCharPref("mail.serverDefaultStoreContractID") !=
+ "@mozilla.org/msgstore/berkeleystore;1"
+ ) {
+ return;
+ }
+
+ do_test_pending();
+ // step 1: copy a message into the local inbox
+ MailServices.copy.copyFileMessage(
+ bugmail1,
+ localAccountUtils.inboxFolder,
+ null,
+ false,
+ 0,
+ "",
+ step2,
+ null
+ );
+}
+
+// step 2: copy one message into a subfolder to establish an
+// mbox file time and size
+// nsIMsgCopyServiceListener implementation
+var step2 = {
+ OnStartCopy() {},
+ OnProgress(aProgress, aProgressMax) {},
+ SetMessageKey(aKey) {
+ dump("in set message key\n");
+ gHdr = localAccountUtils.inboxFolder.GetMessageHeader(aKey);
+ },
+ SetMessageId(aMessageId) {},
+ OnStopCopy(aStatus) {
+ Assert.notEqual(gHdr, null);
+ // copy the message into the subfolder
+ MailServices.copy.copyMessages(
+ localAccountUtils.inboxFolder,
+ [gHdr],
+ gSubfolder,
+ false,
+ step3,
+ null,
+ false
+ );
+ },
+};
+
+// step 3: after the copy, delay to allow copy to complete and allow possible
+// file error time
+// nsIMsgCopyServiceListener implementation
+var step3 = {
+ OnStartCopy() {},
+ OnProgress(aProgress, aProgressMax) {},
+ SetMessageKey(aKey) {},
+ SetMessageId(aMessageId) {},
+ OnStopCopy(aStatus) {
+ do_timeout(2000, step4);
+ },
+};
+
+// step 4: start a second copy
+function step4() {
+ MailServices.copy.copyMessages(
+ localAccountUtils.inboxFolder,
+ [gHdr],
+ gSubfolder,
+ false,
+ step5,
+ null,
+ false
+ );
+}
+
+// step 5: actual tests of file size and date
+// nsIMsgCopyServiceListener implementation
+var step5 = {
+ OnStartCopy() {},
+ OnProgress(aProgress, aProgressMax) {},
+ SetMessageKey(aKey) {},
+ SetMessageId(aMessageId) {},
+ OnStopCopy(aStatus) {
+ var dbSize = gSubfolder.msgDatabase.dBFolderInfo.folderSize;
+ var dbDate = gSubfolder.msgDatabase.dBFolderInfo.folderDate;
+ var filePath = gSubfolder.filePath;
+ var date = parseInt(filePath.lastModifiedTime / 1000);
+ var size = filePath.fileSize;
+ Assert.equal(size, dbSize);
+ Assert.equal(date, dbDate);
+ // End of test, so release our header reference
+ gHdr = null;
+ do_test_finished();
+ },
+};
diff --git a/comm/mailnews/base/test/unit/test_bug514945.js b/comm/mailnews/base/test/unit/test_bug514945.js
new file mode 100644
index 0000000000..90ec8084dd
--- /dev/null
+++ b/comm/mailnews/base/test/unit/test_bug514945.js
@@ -0,0 +1,40 @@
+/* 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/. */
+
+/*
+ * Testing of GetAvailable crashes in bug 514945
+ */
+
+function run_test() {
+ const kValidityManager = Cc[
+ "@mozilla.org/mail/search/validityManager;1"
+ ].getService(Ci.nsIMsgSearchValidityManager);
+
+ let validityTable = kValidityManager.getTable(
+ Ci.nsMsgSearchScope.offlineMail
+ );
+
+ // When we try to access a bad value of getAvailable, it should give an error,
+ // not crash.
+ let BAD_VALUE = 1000000; // some large value that is beyond the array bounds
+ let haveExpectedError = false;
+ try {
+ validityTable.getAvailable(Ci.nsMsgSearchAttrib.Subject, BAD_VALUE);
+ } catch (e) {
+ dump("Error but no crash, this is what we want:" + e + "\n");
+ haveExpectedError = true;
+ }
+
+ Assert.ok(haveExpectedError);
+
+ // One of the causes of this is that search term operators are not being
+ // initialized, resulting in random values of the operator. Make sure that is
+ // fixed.
+
+ const kSearchSession = Cc[
+ "@mozilla.org/messenger/searchSession;1"
+ ].createInstance(Ci.nsIMsgSearchSession);
+ let searchTerm = kSearchSession.createTerm();
+ Assert.equal(searchTerm.op, Ci.nsMsgSearchOp.Contains);
+}
diff --git a/comm/mailnews/base/test/unit/test_closedDB.js b/comm/mailnews/base/test/unit/test_closedDB.js
new file mode 100644
index 0000000000..fd752bf81b
--- /dev/null
+++ b/comm/mailnews/base/test/unit/test_closedDB.js
@@ -0,0 +1,104 @@
+/* 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/. */
+
+/**
+ * Tests that an nsMsgDBFolder's database connection and listeners are
+ * restored when called by certain functions.
+ */
+
+const { MessageGenerator } = ChromeUtils.import(
+ "resource://testing-common/mailnews/MessageGenerator.jsm"
+);
+
+add_task(async function () {
+ // Create a folder and some messages.
+
+ let generator = new MessageGenerator();
+
+ MailServices.accounts.createLocalMailAccount();
+ let account = MailServices.accounts.accounts[0];
+ account.addIdentity(MailServices.accounts.createIdentity());
+
+ let rootFolder = account.incomingServer.rootFolder;
+ rootFolder.QueryInterface(Ci.nsIMsgLocalMailFolder);
+
+ let testFolder = rootFolder.createLocalSubfolder("testFolder");
+ testFolder.QueryInterface(Ci.nsIMsgLocalMailFolder);
+ testFolder.addMessageBatch(
+ generator.makeMessages({ count: 5 }).map(message => message.toMboxString())
+ );
+ let testMessages = [...testFolder.messages];
+
+ // Listen for notifications.
+
+ let folderListener = {
+ QueryInterface: ChromeUtils.generateQI(["nsIFolderListener"]),
+ notifications: [],
+ onFolderIntPropertyChanged(folder, property, oldValue, newValue) {
+ if (property != "TotalUnreadMessages") {
+ return;
+ }
+
+ this.notifications.push({ folder, property, oldValue, newValue });
+ },
+ consumeNotification(expectedFolder, expectedOldValue, expectedNewValue) {
+ let { folder, oldValue, newValue } = this.notifications.shift();
+ Assert.equal(folder, expectedFolder, "notification folder");
+ Assert.equal(oldValue, expectedOldValue, "notification oldValue");
+ Assert.equal(newValue, expectedNewValue, "notification newValue");
+ },
+ };
+ MailServices.mailSession.AddFolderListener(
+ folderListener,
+ Ci.nsIFolderListener.intPropertyChanged
+ );
+
+ // Clear the database reference, then mark some messages as read. We should
+ // see the unread count change and get two notifications about it. We could
+ // check `testFolder.msgDatabase` is not null afterwards, but that would be
+ // pointless, because the getter restores the database.
+
+ testFolder.msgDatabase = null;
+ testFolder.markMessagesRead([testMessages[0], testMessages[4]], true);
+ Assert.equal(
+ testFolder.getNumUnread(false),
+ 3,
+ "unread message count should be updated"
+ );
+ Assert.equal(
+ folderListener.notifications.length,
+ 2,
+ "two folder notifications should have fired"
+ );
+ folderListener.consumeNotification(testFolder, 5, 4);
+ folderListener.consumeNotification(testFolder, 4, 3);
+
+ // Clear the database reference, then mark some messages as flagged. This
+ // doesn't prove much except that nothing exploded.
+
+ testFolder.msgDatabase = null;
+ testFolder.markMessagesFlagged([testMessages[1], testMessages[3]], true);
+
+ // Clear the database reference, then mark all messages as read. We should
+ // see the unread count change to zero and get a notification about it.
+
+ testFolder.msgDatabase = null;
+ testFolder.markAllMessagesRead(null);
+ Assert.equal(
+ testFolder.getNumUnread(false),
+ 0,
+ "unread message count should be updated"
+ );
+ Assert.equal(
+ folderListener.notifications.length,
+ 1,
+ "a folder notifications should have fired"
+ );
+ folderListener.consumeNotification(testFolder, 3, 0);
+
+ // Clean up.
+
+ MailServices.mailSession.RemoveFolderListener(folderListener);
+ MailServices.accounts.removeAccount(account, false);
+});
diff --git a/comm/mailnews/base/test/unit/test_compactFailure.js b/comm/mailnews/base/test/unit/test_compactFailure.js
new file mode 100644
index 0000000000..300c8abda7
--- /dev/null
+++ b/comm/mailnews/base/test/unit/test_compactFailure.js
@@ -0,0 +1,134 @@
+/* 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/. */
+
+var { MockRegistrar } = ChromeUtils.importESModule(
+ "resource://testing-common/MockRegistrar.sys.mjs"
+);
+
+/* import-globals-from ../../../test/resources/logHelper.js */
+load("../../../resources/logHelper.js");
+var { addMessagesToFolder, MessageGenerator, MessageScenarioFactory } =
+ ChromeUtils.import("resource://testing-common/mailnews/MessageGenerator.jsm");
+var { MessageInjection } = ChromeUtils.import(
+ "resource://testing-common/mailnews/MessageInjection.jsm"
+);
+var { PromiseTestUtils } = ChromeUtils.import(
+ "resource://testing-common/mailnews/PromiseTestUtils.jsm"
+);
+
+Services.prefs.setCharPref(
+ "mail.serverDefaultStoreContractID",
+ "@mozilla.org/msgstore/berkeleystore;1"
+);
+
+var gTargetFolder;
+var gCid;
+
+// Allow certain xpcom errors.
+logHelperAllowedErrors.push("NS_ERROR_FILE_NOT_FOUND");
+
+var MsgDBServiceFailure = {
+ QueryInterface: ChromeUtils.generateQI(["nsIMsgDBService"]),
+
+ openMailDBFromFile(file, folder, create, leaveInvalidDB) {
+ if (folder.name == "ShouldFail") {
+ throw Components.Exception("", Cr.NS_ERROR_FILE_NOT_FOUND);
+ }
+ return this._genuine.openMailDBFromFile(
+ file,
+ folder,
+ create,
+ leaveInvalidDB
+ );
+ },
+
+ openFolderDB(folder, leaveInvalidDB) {
+ return this._genuine.openFolderDB(folder, leaveInvalidDB);
+ },
+ createNewDB(folder) {
+ return this._genuine.createNewDB(folder);
+ },
+ registerPendingListener(folder, listener) {
+ this._genuine.registerPendingListener(folder, listener);
+ },
+ unregisterPendingListener(listener) {
+ this._genuine.unregisterPendingListener(listener);
+ },
+ cachedDBForFolder(folder) {
+ return this._genuine.cachedDBFolder(folder);
+ },
+ get openDBs() {
+ return this._genuine.openDBs;
+ },
+};
+
+function generate_messages() {
+ let messageGenerator = new MessageGenerator();
+ let scenarioFactory = new MessageScenarioFactory(messageGenerator);
+ let messages = [];
+ messages = messages.concat(scenarioFactory.directReply(10));
+ return messages;
+}
+
+async function compact_with_exception(expectedException) {
+ let compactor = Cc["@mozilla.org/messenger/foldercompactor;1"].createInstance(
+ Ci.nsIMsgFolderCompactor
+ );
+ let listener = new PromiseTestUtils.PromiseUrlListener();
+ compactor.compactFolders([gTargetFolder], listener, null);
+ try {
+ await listener.promise;
+ do_throw(
+ "nsIMsgFolderCompactor listener wasn't called with a failure code."
+ );
+ } catch (failureCode) {
+ Assert.equal(expectedException, failureCode);
+ }
+}
+
+function create_local_folders() {
+ let rootFolder = localAccountUtils.rootFolder;
+ let localTrashFolder = rootFolder.getChildNamed("Trash");
+ localTrashFolder.setFlag(Ci.nsMsgFolderFlags.Trash);
+}
+
+async function delete_all_messages() {
+ let promiseCopyListener = new PromiseTestUtils.PromiseCopyListener();
+ gTargetFolder.deleteMessages(
+ [...gTargetFolder.messages],
+ null,
+ false, // Do not delete storage.
+ true, // Is a move.
+ promiseCopyListener,
+ false // Do not allow undo, currently leaks.
+ );
+ await promiseCopyListener.promise;
+}
+
+add_setup(function () {
+ localAccountUtils.loadLocalMailAccount();
+ create_local_folders();
+});
+
+add_task(async function test_compact_without_failure() {
+ // Setup open failure folder.
+ gTargetFolder =
+ localAccountUtils.rootFolder.createLocalSubfolder("ShouldFail");
+ addMessagesToFolder(generate_messages(), gTargetFolder);
+
+ await new Promise(resolve => {
+ mailTestUtils.updateFolderAndNotify(gTargetFolder, resolve);
+ });
+ // Delete messages.
+ await delete_all_messages();
+ // Setup db service mock.
+ gCid = MockRegistrar.register(
+ "@mozilla.org/msgDatabase/msgDBService;1",
+ MsgDBServiceFailure
+ );
+ // Test compact without failure.
+ await compact_with_exception(Cr.NS_ERROR_FILE_NOT_FOUND);
+ // Teardown db service mock.
+ MockRegistrar.unregister(gCid);
+});
diff --git a/comm/mailnews/base/test/unit/test_converterDeferredAccount.js b/comm/mailnews/base/test/unit/test_converterDeferredAccount.js
new file mode 100644
index 0000000000..effc6d57e2
--- /dev/null
+++ b/comm/mailnews/base/test/unit/test_converterDeferredAccount.js
@@ -0,0 +1,206 @@
+/* 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/. */
+
+const { FileUtils } = ChromeUtils.importESModule(
+ "resource://gre/modules/FileUtils.sys.mjs"
+);
+const { PromiseTestUtils } = ChromeUtils.import(
+ "resource://testing-common/mailnews/PromiseTestUtils.jsm"
+);
+var { convertMailStoreTo } = ChromeUtils.import(
+ "resource:///modules/mailstoreConverter.jsm"
+);
+const { FolderUtils } = ChromeUtils.import(
+ "resource:///modules/FolderUtils.jsm"
+);
+
+// XXX: merge into test_converter.js
+
+var log = console.createInstance({
+ prefix: "mail.mailstoreconverter",
+ maxLogLevel: "Warn",
+ maxLogLevelPref: "mail.mailstoreconverter.loglevel",
+});
+
+Services.prefs.setCharPref(
+ "mail.serverDefaultStoreContractID",
+ "@mozilla.org/msgstore/berkeleystore;1"
+);
+
+// No. of messages/files and folders copied.
+var gMsgHdrs = [];
+// {nsIMsgLocalMailFolder} folder carrying messages for the pop server.
+var gInbox;
+// {nsIMsgIncomingServer} server for first deferred pop account.
+var gServer1;
+// {nsIMsgIncomingServer} server for second deferred pop account.
+var gServer2;
+// {nsIMsgIncomingServer} server to convert.
+var gServer;
+
+var copyListenerWrap = {
+ SetMessageKey(aKey) {
+ let hdr = gInbox.GetMessageHeader(aKey);
+ gMsgHdrs.push({ hdr, ID: hdr.messageId });
+ },
+ OnStopCopy(aStatus) {
+ // Check: message successfully copied.
+ Assert.equal(aStatus, 0);
+ },
+};
+
+var EventTarget = function () {
+ this.dispatchEvent = function (event) {
+ if (event.type == "progress") {
+ log.trace("Progress: " + event.detail);
+ }
+ };
+};
+
+function copyFileMessage(file, destFolder, isDraftOrTemplate) {
+ let listener = new PromiseTestUtils.PromiseCopyListener(copyListenerWrap);
+ MailServices.copy.copyFileMessage(
+ file,
+ destFolder,
+ null,
+ isDraftOrTemplate,
+ 0,
+ "",
+ listener,
+ null
+ );
+ return listener.promise;
+}
+
+/**
+ * Check that conversion worked for the given source.
+ *
+ * @param {nsIFile} source - mbox source directory.
+ * @param {nsIFile} target - maildir target directory.
+ */
+function checkConversion(source, target) {
+ for (let sourceContent of source.directoryEntries) {
+ let sourceContentName = sourceContent.leafName;
+ let ext = sourceContentName.substr(-4);
+ let targetFile = FileUtils.File(
+ PathUtils.join(target.path, sourceContentName)
+ );
+ log.debug("Checking path: " + targetFile.path);
+ if (ext == ".dat") {
+ Assert.ok(targetFile.exists());
+ } else if (sourceContent.isDirectory()) {
+ Assert.ok(targetFile.exists());
+ checkConversion(sourceContent, targetFile);
+ } else if (ext != ".msf") {
+ Assert.ok(targetFile.exists());
+ let cur = FileUtils.File(PathUtils.join(targetFile.path, "cur"));
+ Assert.ok(cur.exists());
+ let tmp = FileUtils.File(PathUtils.join(targetFile.path, "tmp"));
+ Assert.ok(tmp.exists());
+ if (targetFile.leafName == "Inbox") {
+ let curContents = cur.directoryEntries;
+ let curContentsCount = [...curContents].length;
+ Assert.equal(curContentsCount, 1000);
+ }
+ }
+ }
+}
+
+function run_test() {
+ localAccountUtils.loadLocalMailAccount();
+
+ // Set up two deferred pop accounts.
+ gServer1 = MailServices.accounts.createIncomingServer(
+ "test1",
+ "localhost1",
+ "pop3"
+ );
+ gServer2 = MailServices.accounts.createIncomingServer(
+ "test2",
+ "localhost2",
+ "pop3"
+ );
+ var accountPop1 = MailServices.accounts.createAccount();
+ var accountPop2 = MailServices.accounts.createAccount();
+
+ // Set incoming servers.
+ accountPop1.incomingServer = gServer1;
+ gServer1.QueryInterface(Ci.nsIPop3IncomingServer);
+ gServer1.valid = true;
+ accountPop2.incomingServer = gServer2;
+ gServer2.QueryInterface(Ci.nsIPop3IncomingServer);
+ gServer2.valid = true;
+
+ // Defer accounts to Local Folders.
+ gServer1.deferredToAccount = localAccountUtils.msgAccount.key;
+ gServer2.deferredToAccount = localAccountUtils.msgAccount.key;
+
+ // 'gServer1' should be deferred. Get the path of the root folder to which
+ // other accounts are deferred.
+ ok(gServer1.rootFolder.filePath.path != gServer1.rootMsgFolder.filePath.path);
+ let deferredToRootFolder = gServer1.rootMsgFolder.filePath.path;
+
+ // Account to which other accounts have been deferred.
+ let deferredToAccount;
+ // String to hold names of accounts to convert.
+ let accountsToConvert = "";
+
+ let accounts = FolderUtils.allAccountsSorted(true);
+ for (let account of accounts) {
+ if (
+ account.incomingServer.rootFolder.filePath.path == deferredToRootFolder
+ ) {
+ // Other accounts may be deferred to this account.
+ deferredToAccount = account;
+ } else if (
+ account.incomingServer.rootMsgFolder.filePath.path == deferredToRootFolder
+ ) {
+ // This is a deferred account.
+ accountsToConvert += account.incomingServer.username + ", ";
+ }
+ }
+
+ accountsToConvert =
+ accountsToConvert + deferredToAccount.incomingServer.username;
+ log.info(accountsToConvert + " will be converted");
+
+ gInbox = localAccountUtils.inboxFolder;
+ gServer = deferredToAccount.incomingServer;
+
+ run_next_test();
+}
+
+add_setup(async function () {
+ let msgFile = do_get_file("../../../data/bugmail10");
+ // Add 1000 messages to the "Inbox" folder.
+ for (let i = 0; i < 1000; i++) {
+ await copyFileMessage(msgFile, gInbox, false);
+ }
+});
+
+add_task(function testMaildirConversion() {
+ let mailstoreContractId = Services.prefs.getCharPref(
+ "mail.server." + gServer.key + ".storeContractID"
+ );
+
+ do_test_pending();
+ let pConverted = convertMailStoreTo(
+ mailstoreContractId,
+ gServer,
+ new EventTarget()
+ );
+ let originalRootFolder = gServer.rootFolder.filePath;
+
+ pConverted
+ .then(function (val) {
+ log.debug("Conversion done: " + originalRootFolder.path + " => " + val);
+ let newRootFolder = gServer.rootFolder.filePath;
+ checkConversion(originalRootFolder, newRootFolder);
+ do_test_finished();
+ })
+ .catch(function (reason) {
+ log.error("Conversion failed: " + reason.error);
+ ok(false); // Fail the test!
+ });
+});
diff --git a/comm/mailnews/base/test/unit/test_copyChaining.js b/comm/mailnews/base/test/unit/test_copyChaining.js
new file mode 100644
index 0000000000..b014049ed3
--- /dev/null
+++ b/comm/mailnews/base/test/unit/test_copyChaining.js
@@ -0,0 +1,109 @@
+/* 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/. */
+
+// Test of chaining copies between the same folders
+
+/* import-globals-from ../../../test/resources/MessageGenerator.jsm */
+load("../../../resources/MessageGenerator.jsm");
+
+var { MailServices } = ChromeUtils.import(
+ "resource:///modules/MailServices.jsm"
+);
+
+var gCopySource;
+var gCopyDest;
+var gMessages;
+var gCurTestNum = 1;
+
+// main test
+
+var gTestArray = [
+ function copyMsg1() {
+ gMessages = [...gCopySource.msgDatabase.enumerateMessages()];
+ CopyNextMessage();
+ },
+ function copyMsg2() {
+ CopyNextMessage();
+ },
+ function copyMsg3() {
+ CopyNextMessage();
+ },
+ function copyMsg4() {
+ CopyNextMessage();
+ },
+];
+
+function CopyNextMessage() {
+ if (gMessages.length > 0) {
+ let msgHdr = gMessages.shift();
+ MailServices.copy.copyMessages(
+ gCopySource,
+ [msgHdr],
+ gCopyDest,
+ true,
+ copyListener,
+ null,
+ false
+ );
+ } else {
+ do_throw("TEST FAILED - out of messages");
+ }
+}
+
+function run_test() {
+ localAccountUtils.loadLocalMailAccount();
+ let messageGenerator = new MessageGenerator();
+ let scenarioFactory = new MessageScenarioFactory(messageGenerator);
+
+ // "Master" do_test_pending(), paired with a do_test_finished() at the end of
+ // all the operations.
+ do_test_pending();
+
+ gCopyDest = localAccountUtils.inboxFolder.createLocalSubfolder("copyDest");
+ // build up a diverse list of messages
+ let messages = [];
+ messages = messages.concat(scenarioFactory.directReply(10));
+ gCopySource = localAccountUtils.rootFolder.createLocalSubfolder("copySource");
+ addMessagesToFolder(messages, gCopySource);
+
+ mailTestUtils.updateFolderAndNotify(gCopySource, doTest);
+ return true;
+}
+
+function doTest() {
+ var test = gCurTestNum;
+ if (test <= gTestArray.length) {
+ var testFn = gTestArray[test - 1];
+ dump("Doing test " + test + " " + testFn.name + "\n");
+
+ try {
+ testFn();
+ } catch (ex) {
+ do_throw("TEST FAILED " + ex);
+ }
+ } else {
+ endTest();
+ }
+}
+
+function endTest() {
+ // Cleanup, null out everything
+ dump(" Exiting mail tests\n");
+ gMessages = null;
+ do_test_finished(); // for the one in run_test()
+}
+
+// nsIMsgCopyServiceListener implementation
+var copyListener = {
+ OnStartCopy() {},
+ OnProgress(aProgress, aProgressMax) {},
+ SetMessageKey(aKey) {},
+ SetMessageId(aMessageId) {},
+ OnStopCopy(aStatus) {
+ // Check: message successfully copied.
+ Assert.equal(aStatus, 0);
+ ++gCurTestNum;
+ doTest();
+ },
+};
diff --git a/comm/mailnews/base/test/unit/test_copyToInvalidDB.js b/comm/mailnews/base/test/unit/test_copyToInvalidDB.js
new file mode 100644
index 0000000000..2c0dd85d9f
--- /dev/null
+++ b/comm/mailnews/base/test/unit/test_copyToInvalidDB.js
@@ -0,0 +1,106 @@
+/*
+ * Simple tests for copying local messages to a folder whose db is missing
+ * or invalid
+ */
+
+const { PromiseTestUtils } = ChromeUtils.import(
+ "resource://testing-common/mailnews/PromiseTestUtils.jsm"
+);
+
+const { MessageGenerator } = ChromeUtils.import(
+ "resource://testing-common/mailnews/MessageGenerator.jsm"
+);
+
+async function setup() {
+ let createSubfolder = async function (parentFolder, name) {
+ let promiseAdded = PromiseTestUtils.promiseFolderAdded(name);
+ parentFolder.createSubfolder(name, null);
+ await promiseAdded;
+ return parentFolder.getChildNamed(name);
+ };
+
+ // Create account.
+ MailServices.accounts.createLocalMailAccount();
+ let account = MailServices.accounts.FindAccountForServer(
+ MailServices.accounts.localFoldersServer
+ );
+ let root = account.incomingServer.rootFolder;
+
+ // Add a couple of folders containing some test messages.
+ let folder1 = await createSubfolder(root, "test1");
+ folder1.QueryInterface(Ci.nsIMsgLocalMailFolder);
+
+ let folder2 = await createSubfolder(root, "test2");
+ folder2.QueryInterface(Ci.nsIMsgLocalMailFolder);
+
+ let gen = new MessageGenerator();
+ let msg1 = gen.makeMessage();
+ let msg2 = gen.makeMessage({ inReplyTo: msg1 });
+ folder1.addMessageBatch([msg1, msg2].map(m => m.toMboxString()));
+
+ let msg3 = gen.makeMessage();
+ folder2.addMessage(msg3.toMboxString());
+
+ return [folder1, folder2];
+}
+
+add_task(async function test_copyToInvalidDB() {
+ let [folder1, folder2] = await setup();
+
+ // folder1 contains [msg1, msg2].
+ // folder2 contains [msg3].
+
+ // Take note of the message we're going to move (first msg in folder1).
+ let msgHdr = Array.from(folder1.msgDatabase.enumerateMessages())[0];
+ let expectedID = msgHdr.messageId;
+ let expectedMsg = mailTestUtils.loadMessageToString(folder1, msgHdr);
+
+ // Sabotage the destination folder2 database.
+ folder2.msgDatabase.summaryValid = false;
+ folder2.msgDatabase = null;
+ folder2.ForceDBClosed();
+ // In fact, delete the .msf file entirely.
+ folder2.summaryFile.remove(false);
+
+ // So folder2 has no trace of a DB.
+ Assert.equal(folder2.databaseOpen, false);
+ Assert.equal(folder2.summaryFile.exists(), false);
+
+ // Move the message from folder1 to folder2.
+ let copyListener = new PromiseTestUtils.PromiseCopyListener();
+ MailServices.copy.copyMessages(
+ folder1,
+ [msgHdr],
+ folder2,
+ true, // isMove
+ copyListener,
+ null, // window
+ false // allowUndo
+ );
+ await copyListener.promise;
+
+ // Current behaviour:
+ // After the move, there's still no sign of a DB file.
+ // Yet the copy didn't fail (see Bug 1737203).
+ Assert.equal(folder2.databaseOpen, false);
+ Assert.equal(folder2.summaryFile.exists(), false);
+
+ // Rebuild the the database.
+ let urlListener = new PromiseTestUtils.PromiseUrlListener();
+ try {
+ folder2.getDatabaseWithReparse(urlListener, null);
+ } catch (ex) {
+ // We expect this - it indicates the DB is not valid. But it will have
+ // kicked off an async reparse, so we need to wait for the listener.
+ Assert.equal(ex.result, Cr.NS_ERROR_NOT_INITIALIZED);
+ await urlListener.promise;
+ }
+
+ // Check that the message moved over intact.
+ let gotHdr = folder2.msgDatabase.getMsgHdrForMessageID(expectedID);
+ let gotMsg = mailTestUtils.loadMessageToString(folder2, gotHdr);
+ // NOTE: With maildir store, the message seems to gain an extra trailing
+ // "\n" during the copy. See Bug 1716651.
+ // For now, use .trim() as a workaround.
+ Assert.equal(gotMsg.trim(), expectedMsg.trim());
+});
diff --git a/comm/mailnews/base/test/unit/test_detachToFile.js b/comm/mailnews/base/test/unit/test_detachToFile.js
new file mode 100644
index 0000000000..3c1913a9a8
--- /dev/null
+++ b/comm/mailnews/base/test/unit/test_detachToFile.js
@@ -0,0 +1,159 @@
+/* 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/. */
+
+/*
+ * Tests nsIMessenger's detachAttachmentsWOPrompts
+ */
+
+var { PromiseTestUtils } = ChromeUtils.import(
+ "resource://testing-common/mailnews/PromiseTestUtils.jsm"
+);
+
+var { MailServices } = ChromeUtils.import(
+ "resource:///modules/MailServices.jsm"
+);
+var { MsgHdrToMimeMessage } = ChromeUtils.import(
+ "resource:///modules/gloda/MimeMessage.jsm"
+);
+
+function SaveAttachmentCallback() {
+ this.attachments = null;
+ this._promise = new Promise((resolve, reject) => {
+ this._resolve = resolve;
+ this._reject = reject;
+ });
+}
+
+SaveAttachmentCallback.prototype = {
+ callback: function saveAttachmentCallback_callback(aMsgHdr, aMimeMessage) {
+ this.attachments = aMimeMessage.allAttachments;
+ this._resolve();
+ },
+ get promise() {
+ return this._promise;
+ },
+};
+var gCallbackObject = new SaveAttachmentCallback();
+
+add_setup(async function () {
+ if (!localAccountUtils.inboxFolder) {
+ localAccountUtils.loadLocalMailAccount();
+ }
+});
+
+add_task(async function startCopy() {
+ // Get a message into the local filestore.
+ let mailFile = do_get_file("../../../data/external-attach-test");
+ let listener = new PromiseTestUtils.PromiseCopyListener();
+ MailServices.copy.copyFileMessage(
+ mailFile,
+ localAccountUtils.inboxFolder,
+ null,
+ false,
+ 0,
+ "",
+ listener,
+ null
+ );
+ await listener.promise;
+});
+
+// process the message through mime
+add_task(async function startMime() {
+ let msgHdr = mailTestUtils.firstMsgHdr(localAccountUtils.inboxFolder);
+
+ MsgHdrToMimeMessage(
+ msgHdr,
+ gCallbackObject,
+ gCallbackObject.callback,
+ true // allowDownload
+ );
+
+ await gCallbackObject.promise;
+});
+
+// detach any found attachments
+add_task(async function startDetach() {
+ let msgHdr = mailTestUtils.firstMsgHdr(localAccountUtils.inboxFolder);
+ let msgURI = msgHdr.folder.generateMessageURI(msgHdr.messageKey);
+
+ let messenger = Cc["@mozilla.org/messenger;1"].createInstance(
+ Ci.nsIMessenger
+ );
+ let attachment = gCallbackObject.attachments[0];
+ let listener = new PromiseTestUtils.PromiseUrlListener();
+
+ messenger.detachAttachmentsWOPrompts(
+ do_get_profile(),
+ [attachment.contentType],
+ [attachment.url],
+ [attachment.name],
+ [msgURI],
+ listener
+ );
+ await listener.promise;
+});
+
+/**
+ * Test that the detachment was successful.
+ */
+add_task(async function testDetach() {
+ // The message contained a file "check.pdf" which should
+ // now exist in the profile directory.
+ let checkFile = do_get_profile().clone();
+ checkFile.append("check.pdf");
+ Assert.ok(checkFile.exists());
+
+ // The message should now have a detached attachment. Read the message,
+ // and search for "AttachmentDetached" which is added on detachment.
+
+ // Get the message header
+ let msgHdr = mailTestUtils.firstMsgHdr(localAccountUtils.inboxFolder);
+
+ let messageContent = await getContentFromMessage(msgHdr);
+ Assert.ok(messageContent.includes("AttachmentDetached"));
+});
+
+/**
+ * Get the full message content.
+ *
+ * @param {nsIMsgDBHdr} aMsgHdr - Message whose text body will be read.
+ * @returns {Promise<string>} full message contents.
+ */
+function getContentFromMessage(aMsgHdr) {
+ let msgFolder = aMsgHdr.folder;
+ let msgUri = msgFolder.getUriForMsg(aMsgHdr);
+
+ return new Promise((resolve, reject) => {
+ let streamListener = {
+ QueryInterface: ChromeUtils.generateQI(["nsIStreamListener"]),
+ sis: Cc["@mozilla.org/scriptableinputstream;1"].createInstance(
+ Ci.nsIScriptableInputStream
+ ),
+ content: "",
+ onDataAvailable(request, inputStream, offset, count) {
+ this.sis.init(inputStream);
+ this.content += this.sis.read(count);
+ },
+ onStartRequest(request) {},
+ onStopRequest(request, statusCode) {
+ this.sis.close();
+ if (Components.isSuccessCode(statusCode)) {
+ resolve(this.content);
+ } else {
+ reject(new Error(statusCode));
+ }
+ },
+ };
+ MailServices.messageServiceFromURI(msgUri).streamMessage(
+ msgUri,
+ streamListener,
+ null,
+ null,
+ false,
+ "",
+ false
+ );
+ });
+}
diff --git a/comm/mailnews/base/test/unit/test_emptyTrash.js b/comm/mailnews/base/test/unit/test_emptyTrash.js
new file mode 100644
index 0000000000..528691117e
--- /dev/null
+++ b/comm/mailnews/base/test/unit/test_emptyTrash.js
@@ -0,0 +1,174 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* ***** BEGIN LICENSE BLOCK *****
+ *
+ * Any copyright is dedicated to the Public Domain.
+ * http://creativecommons.org/licenses/publicdomain/
+ *
+ * ***** END LICENSE BLOCK ***** */
+
+/*
+ * Test suite for empty trash
+ *
+ * Currently tested:
+ * - Empty local trash
+ * TODO
+ * - Empty imap trash
+ */
+
+// Globals
+var { MailServices } = ChromeUtils.import(
+ "resource:///modules/MailServices.jsm"
+);
+
+var gMsgFile1;
+var gLocalTrashFolder;
+var gCurTestNum;
+var gMsgHdrs = [];
+var gRootFolder;
+
+var nsIMFNService = Ci.nsIMsgFolderNotificationService;
+
+// nsIMsgCopyServiceListener implementation
+var copyListener = {
+ OnStartCopy() {},
+ OnProgress(aProgress, aProgressMax) {},
+ SetMessageKey(aKey) {
+ let hdr = localAccountUtils.inboxFolder.GetMessageHeader(aKey);
+ gMsgHdrs.push({ hdr, ID: hdr.messageId });
+ },
+ SetMessageId(aMessageId) {},
+ OnStopCopy(aStatus) {
+ // Check: message successfully copied.
+ Assert.equal(aStatus, 0);
+ // Ugly hack: make sure we don't get stuck in a JS->C++->JS->C++... call stack
+ // This can happen with a bunch of synchronous functions grouped together, and
+ // can even cause tests to fail because they're still waiting for the listener
+ // to return
+ do_timeout(0, function () {
+ doTest(++gCurTestNum);
+ });
+ },
+};
+
+var urlListener = {
+ OnStartRunningUrl(aUrl) {},
+ OnStopRunningUrl(aUrl, aExitCode) {
+ // Check: message successfully copied.
+ Assert.equal(aExitCode, 0);
+ // Ugly hack: make sure we don't get stuck in a JS->C++->JS->C++... call stack
+ // This can happen with a bunch of synchronous functions grouped together, and
+ // can even cause tests to fail because they're still waiting for the listener
+ // to return
+ do_timeout(0, function () {
+ doTest(++gCurTestNum);
+ });
+ },
+};
+
+function copyFileMessage(file, destFolder, isDraftOrTemplate) {
+ MailServices.copy.copyFileMessage(
+ file,
+ destFolder,
+ null,
+ isDraftOrTemplate,
+ 0,
+ "",
+ copyListener,
+ null
+ );
+}
+
+function deleteMessages(srcFolder, items) {
+ srcFolder.deleteMessages(items, null, false, true, copyListener, true);
+}
+
+/*
+ * TESTS
+ */
+
+// Beware before commenting out a test -- later tests might just depend on earlier ones
+var gTestArray = [
+ // Copying message from file
+ function testCopyFileMessage1() {
+ copyFileMessage(gMsgFile1, localAccountUtils.inboxFolder, false);
+ },
+
+ // Delete message
+ function testDeleteMessage() {
+ // delete to trash
+ // Let's take a moment to re-initialize stuff that got moved
+ let inboxDB = localAccountUtils.inboxFolder.msgDatabase;
+ gMsgHdrs[0].hdr = inboxDB.getMsgHdrForMessageID(gMsgHdrs[0].ID);
+
+ // Now delete the message
+ deleteMessages(localAccountUtils.inboxFolder, [gMsgHdrs[0].hdr]);
+ },
+ function emptyTrash() {
+ gRootFolder = localAccountUtils.incomingServer.rootMsgFolder;
+ gLocalTrashFolder = gRootFolder.getChildNamed("Trash");
+ // hold onto a db to make sure that empty trash deals with the case
+ // of someone holding onto the db, but the trash folder has a null db.
+ let gLocalTrashDB = gLocalTrashFolder.msgDatabase; // eslint-disable-line no-unused-vars
+ gLocalTrashFolder.msgDatabase = null;
+ // this is synchronous
+ gLocalTrashFolder.emptyTrash(null);
+ // check that the trash folder is 0 size, that the db has a 0 message count
+ // and has no messages.
+ Assert.equal(0, gLocalTrashFolder.filePath.fileSize);
+ Assert.equal(0, gLocalTrashFolder.msgDatabase.dBFolderInfo.numMessages);
+ let msgs = [...gLocalTrashFolder.msgDatabase.enumerateMessages()];
+ Assert.equal(0, msgs.length);
+ urlListener.OnStopRunningUrl(null, 0);
+ },
+];
+
+// Our listener, which captures events.
+function gMFListener() {}
+gMFListener.prototype = {
+ folderDeleted(aFolder) {
+ aFolder.msgDatabase = null;
+ },
+};
+
+function run_test() {
+ localAccountUtils.loadLocalMailAccount();
+ // Load up a message so that we can copy it in later.
+ gMsgFile1 = do_get_file("../../../data/bugmail10");
+ // our front end code clears the msg db when it gets told the folder for
+ // an open view has been deleted - so simulate that.
+ var folderDeletedListener = new gMFListener();
+ MailServices.mfn.addListener(
+ folderDeletedListener,
+ nsIMFNService.folderDeleted
+ );
+
+ // "Master" do_test_pending(), paired with a do_test_finished() at the end of all the operations.
+ do_test_pending();
+
+ // Do the test.
+ doTest(1);
+}
+
+function doTest(test) {
+ if (test <= gTestArray.length) {
+ gCurTestNum = test;
+
+ var testFn = gTestArray[test - 1];
+ // Set a limit of three seconds; if the notifications haven't arrived by then there's a problem.
+ do_timeout(10000, function () {
+ if (gCurTestNum == test) {
+ do_throw(
+ "Notifications not received in 10000 ms for operation " + testFn.name
+ );
+ }
+ });
+ try {
+ testFn();
+ } catch (ex) {
+ dump(ex);
+ }
+ } else {
+ gMsgHdrs = null;
+ do_test_finished(); // for the one in run_test()
+ }
+}
diff --git a/comm/mailnews/base/test/unit/test_fix_deferred_accounts.js b/comm/mailnews/base/test/unit/test_fix_deferred_accounts.js
new file mode 100644
index 0000000000..0fd09a543a
--- /dev/null
+++ b/comm/mailnews/base/test/unit/test_fix_deferred_accounts.js
@@ -0,0 +1,59 @@
+/* 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/. */
+
+/**
+ * This tests that we cleanup the account prefs when a pop3 account has
+ * been deferred to a hidden account.
+ */
+var { MailServices } = ChromeUtils.import(
+ "resource:///modules/MailServices.jsm"
+);
+
+function run_test() {
+ // Create account prefs with a pop3 account deferred to a hidden account.
+
+ Services.prefs.setCharPref("mail.account.account1.identities", "id1");
+ Services.prefs.setCharPref("mail.account.account1.server", "server1");
+ Services.prefs.setCharPref("mail.account.account2.server", "server2");
+ Services.prefs.setCharPref("mail.account.account4.identities", "id2");
+ Services.prefs.setCharPref("mail.account.account4.server", "server4");
+ Services.prefs.setCharPref("mail.account.account5.identities", "id3");
+ Services.prefs.setCharPref("mail.account.account5.server", "server5");
+ Services.prefs.setCharPref("mail.server.server1.hostname", "Local Folders");
+ Services.prefs.setCharPref("mail.server.server1.type", "none");
+ Services.prefs.setCharPref("mail.server.server2.hostname", "Smart Mailboxes");
+ Services.prefs.setCharPref("mail.server.server2.type", "none");
+ Services.prefs.setBoolPref("mail.server.server2.hidden", true);
+ Services.prefs.setCharPref("mail.server.server4.hostname", "mail.host4.org");
+ Services.prefs.setCharPref("mail.server.server4.type", "pop3");
+ Services.prefs.setCharPref(
+ "mail.server.server4.deferred_to_account",
+ "account2"
+ );
+ Services.prefs.setCharPref("mail.server.server5.hostname", "mail.host5.org");
+ Services.prefs.setCharPref("mail.server.server5.type", "pop3");
+ Services.prefs.setCharPref(
+ "mail.server.server5.deferred_to_account",
+ "account2"
+ );
+
+ Services.prefs.setCharPref(
+ "mail.accountmanager.accounts",
+ "account1,account2,account4,account5"
+ );
+ // Set the default account to one we're going to get rid of. The account manager
+ // should recover relatively gracefully.
+ Services.prefs.setCharPref("mail.accountmanager.defaultaccount", "account1");
+
+ // This will force the load of the accounts setup above.
+ Assert.equal(MailServices.accounts.accounts.length, 3); // hidden account not included
+ let server4 = MailServices.accounts
+ .getAccount("account4")
+ .incomingServer.QueryInterface(Ci.nsIPop3IncomingServer);
+ Assert.equal(server4.deferredToAccount, "account1");
+ let server5 = MailServices.accounts
+ .getAccount("account5")
+ .incomingServer.QueryInterface(Ci.nsIPop3IncomingServer);
+ Assert.equal(server5.deferredToAccount, "account1");
+}
diff --git a/comm/mailnews/base/test/unit/test_folderCompact.js b/comm/mailnews/base/test/unit/test_folderCompact.js
new file mode 100644
index 0000000000..876672c0e5
--- /dev/null
+++ b/comm/mailnews/base/test/unit/test_folderCompact.js
@@ -0,0 +1,335 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* ***** BEGIN LICENSE BLOCK *****
+ *
+ * Any copyright is dedicated to the Public Domain.
+ * http://creativecommons.org/licenses/publicdomain/
+ *
+ * ***** END LICENSE BLOCK ***** */
+
+/*
+ * Test suite for folder compaction
+ *
+ * Currently tested:
+ * - Compacting local folders
+ * TODO
+ * - Compacting imap offline stores.
+ */
+
+var { MailServices } = ChromeUtils.import(
+ "resource:///modules/MailServices.jsm"
+);
+const { PromiseTestUtils } = ChromeUtils.import(
+ "resource://testing-common/mailnews/PromiseTestUtils.jsm"
+);
+
+Services.prefs.setCharPref(
+ "mail.serverDefaultStoreContractID",
+ "@mozilla.org/msgstore/berkeleystore;1"
+);
+
+// Globals
+var gMsgFile1, gMsgFile2, gMsgFile3;
+var gMsg2ID = "200804111417.m3BEHTk4030129@mrapp51.mozilla.org";
+var gMsg3ID = "4849BF7B.2030800@example.com";
+var gLocalFolder2;
+var gLocalFolder3;
+// After a compact (or other operation), this is what we expect the
+// folder size to be.
+var gExpectedFolderSize;
+var gMsgHdrs = [];
+var gExpectedInboxSize;
+var gExpectedFolder2Size;
+var gExpectedFolder3Size;
+// Use this to account for the size of the separating line after the message
+// body. The compactor tries to preserve the convention already in the mbox,
+// and we know that our test messages have CRLFs, so that's what we'll use.
+var gSeparatorLine = "\r\n";
+
+// Transfer message keys between function calls.
+var gMsgKeys = [];
+
+// nsIMsgCopyServiceListener implementation
+var copyListenerWrap = {
+ SetMessageKey(aKey) {
+ let hdr = localAccountUtils.inboxFolder.GetMessageHeader(aKey);
+ gMsgHdrs.push({ hdr, ID: hdr.messageId });
+ },
+ OnStopCopy(aStatus) {
+ // Check: message successfully copied.
+ Assert.equal(aStatus, 0);
+ },
+};
+
+var urlListenerWrap = {
+ OnStopRunningUrl(aUrl, aExitCode) {
+ // Check: message successfully copied.
+ Assert.equal(aExitCode, 0);
+
+ if (gMsgKeys.length > 0) {
+ // Bug 854798: Check if the new message keys are the same as before compaction.
+ let folderMsgs = [...gMsgKeys.folder.messages];
+ // First message was deleted so skip it in the old array.
+ let expectedKeys = [...gMsgKeys].slice(1);
+ Assert.equal(folderMsgs.length, expectedKeys.length);
+ for (let i = 1; i < expectedKeys.length; i++) {
+ Assert.equal(folderMsgs[i], expectedKeys[i]);
+ }
+ gMsgKeys.length = 0;
+ }
+ },
+};
+
+function copyFileMessage(file, destFolder, isDraftOrTemplate) {
+ let listener = new PromiseTestUtils.PromiseCopyListener(copyListenerWrap);
+ MailServices.copy.copyFileMessage(
+ file,
+ destFolder,
+ null,
+ isDraftOrTemplate,
+ 0,
+ "",
+ listener,
+ null
+ );
+ return listener.promise;
+}
+
+function copyMessages(items, isMove, srcFolder, destFolder) {
+ let listener = new PromiseTestUtils.PromiseCopyListener(copyListenerWrap);
+ MailServices.copy.copyMessages(
+ srcFolder,
+ items,
+ destFolder,
+ isMove,
+ listener,
+ null,
+ true
+ );
+ return listener.promise;
+}
+
+function deleteMessages(srcFolder, items) {
+ let listener = new PromiseTestUtils.PromiseCopyListener(copyListenerWrap);
+ srcFolder.deleteMessages(items, null, false, true, listener, true);
+ return listener.promise;
+}
+
+function calculateFolderSize(folder) {
+ let msgDB = folder.msgDatabase;
+ let totalSize = 0;
+ for (let header of msgDB.enumerateMessages()) {
+ totalSize += header.messageSize + gSeparatorLine.length;
+ }
+ return totalSize;
+}
+
+function verifyMsgOffsets(folder) {
+ let msgDB = folder.msgDatabase;
+ let enumerator = msgDB.enumerateMessages();
+ if (enumerator) {
+ for (let header of enumerator) {
+ if (header instanceof Ci.nsIMsgDBHdr) {
+ let storeToken = header.getStringProperty("storeToken");
+ Assert.equal(storeToken, header.messageOffset);
+ }
+ }
+ }
+}
+
+/*
+ * TESTS
+ */
+
+// Beware before commenting out a test -- later tests might just depend on earlier ones
+var gTestArray = [
+ // Copying messages from files
+ async function testCopyFileMessage1() {
+ await copyFileMessage(gMsgFile1, localAccountUtils.inboxFolder, false);
+ },
+ async function testCopyFileMessage2() {
+ await copyFileMessage(gMsgFile2, localAccountUtils.inboxFolder, false);
+ },
+ async function testCopyFileMessage3() {
+ await copyFileMessage(gMsgFile3, localAccountUtils.inboxFolder, true);
+ showMessages(
+ localAccountUtils.inboxFolder,
+ "after initial 3 messages copy to inbox"
+ );
+ },
+
+ // Moving/copying messages
+ async function testCopyMessages1() {
+ await copyMessages(
+ [gMsgHdrs[0].hdr],
+ false,
+ localAccountUtils.inboxFolder,
+ gLocalFolder2
+ );
+ },
+ async function testCopyMessages2() {
+ await copyMessages(
+ [gMsgHdrs[1].hdr, gMsgHdrs[2].hdr],
+ false,
+ localAccountUtils.inboxFolder,
+ gLocalFolder2
+ );
+ showMessages(gLocalFolder2, "after copying 3 messages");
+ },
+ async function testMoveMessages1() {
+ await copyMessages(
+ [gMsgHdrs[0].hdr, gMsgHdrs[1].hdr],
+ true,
+ localAccountUtils.inboxFolder,
+ gLocalFolder3
+ );
+
+ showMessages(localAccountUtils.inboxFolder, "after moving 2 messages");
+ showMessages(gLocalFolder3, "after moving 2 messages");
+ },
+
+ // Deleting messages
+ async function testDeleteMessages1() {
+ // delete to trash
+ // Let's take a moment to re-initialize stuff that got moved
+ var folder3DB = gLocalFolder3.msgDatabase;
+ gMsgHdrs[0].hdr = folder3DB.getMsgHdrForMessageID(gMsgHdrs[0].ID);
+
+ // Store message keys before deletion and compaction.
+ gMsgKeys.folder = gLocalFolder3;
+ for (let header of gLocalFolder3.messages) {
+ gMsgKeys.push(header.messageKey);
+ }
+
+ // Now delete the message
+ await deleteMessages(gLocalFolder3, [gMsgHdrs[0].hdr]);
+
+ showMessages(gLocalFolder3, "after deleting 1 message to trash");
+ },
+ async function compactFolder() {
+ gExpectedFolderSize = calculateFolderSize(gLocalFolder3);
+ Assert.notEqual(gLocalFolder3.expungedBytes, 0);
+ let listener = new PromiseTestUtils.PromiseUrlListener(urlListenerWrap);
+ gLocalFolder3.compact(listener, null);
+ await listener.promise;
+
+ showMessages(gLocalFolder3, "after compact");
+ },
+ async function testDeleteMessages2() {
+ Assert.equal(gExpectedFolderSize, gLocalFolder3.filePath.fileSize);
+ verifyMsgOffsets(gLocalFolder3);
+ var folder2DB = gLocalFolder2.msgDatabase;
+ gMsgHdrs[0].hdr = folder2DB.getMsgHdrForMessageID(gMsgHdrs[0].ID);
+
+ // Store message keys before deletion and compaction.
+ gMsgKeys.folder = gLocalFolder2;
+ for (let header of gLocalFolder2.messages) {
+ gMsgKeys.push(header.messageKey);
+ }
+
+ // Now delete the message
+ await deleteMessages(gLocalFolder2, [gMsgHdrs[0].hdr]);
+
+ showMessages(gLocalFolder2, "after deleting 1 message");
+ },
+ async function compactAllFolders() {
+ gExpectedInboxSize = calculateFolderSize(localAccountUtils.inboxFolder);
+ gExpectedFolder2Size = calculateFolderSize(gLocalFolder2);
+ gExpectedFolder3Size = calculateFolderSize(gLocalFolder3);
+
+ // Save the first message key, which will change after compact with
+ // rebuild.
+ let f2m2Key =
+ gLocalFolder2.msgDatabase.getMsgHdrForMessageID(gMsg2ID).messageKey;
+
+ // force expunged bytes count to get cached.
+ gLocalFolder2.expungedBytes;
+ // mark localFolder2 as having an invalid db, and remove it
+ // for good measure.
+ gLocalFolder2.msgDatabase.summaryValid = false;
+ gLocalFolder2.msgDatabase = null;
+ gLocalFolder2.ForceDBClosed();
+ let dbPath = gLocalFolder2.filePath;
+ dbPath.leafName = dbPath.leafName + ".msf";
+ dbPath.remove(false);
+
+ showMessages(localAccountUtils.inboxFolder, "before compactAll");
+ // Save the key for the inbox message, we'll check after compact that it
+ // did not change.
+ let preInboxMsg3Key =
+ localAccountUtils.inboxFolder.msgDatabase.getMsgHdrForMessageID(
+ gMsg3ID
+ ).messageKey;
+
+ // We used to check here that the keys did not change during rebuild.
+ // But that is no true in general, it was only conicidental since the
+ // checked folder had never been compacted, so the key equaled the offset.
+ // We do not in guarantee that, indeed after rebuild we expect the keys
+ // to change.
+ let checkResult = {
+ OnStopRunningUrl(aUrl, aExitCode) {
+ // Check: message successfully compacted.
+ Assert.equal(aExitCode, 0);
+ },
+ };
+ let listener = new PromiseTestUtils.PromiseUrlListener(checkResult);
+ localAccountUtils.inboxFolder.compactAll(listener, null);
+ await listener.promise;
+
+ showMessages(localAccountUtils.inboxFolder, "after compactAll");
+ showMessages(gLocalFolder2, "after compactAll");
+
+ // For the inbox, which was compacted but not rebuild, key is unchanged.
+ let postInboxMsg3Key =
+ localAccountUtils.inboxFolder.msgDatabase.getMsgHdrForMessageID(
+ gMsg3ID
+ ).messageKey;
+ Assert.equal(preInboxMsg3Key, postInboxMsg3Key);
+
+ // For folder2, which was rebuilt, keys change but all messages should exist.
+ let message2 = gLocalFolder2.msgDatabase.getMsgHdrForMessageID(gMsg2ID);
+ Assert.ok(message2);
+ Assert.ok(gLocalFolder2.msgDatabase.getMsgHdrForMessageID(gMsg3ID));
+
+ // In folder2, gMsg2ID is the first message. After compact with database
+ // rebuild, that key has now changed.
+ Assert.notEqual(message2.messageKey, f2m2Key);
+ },
+ function lastTestCheck() {
+ Assert.equal(
+ gExpectedInboxSize,
+ localAccountUtils.inboxFolder.filePath.fileSize
+ );
+ Assert.equal(gExpectedFolder2Size, gLocalFolder2.filePath.fileSize);
+ Assert.equal(gExpectedFolder3Size, gLocalFolder3.filePath.fileSize);
+ verifyMsgOffsets(gLocalFolder2);
+ verifyMsgOffsets(gLocalFolder3);
+ verifyMsgOffsets(localAccountUtils.inboxFolder);
+ },
+];
+
+function run_test() {
+ localAccountUtils.loadLocalMailAccount();
+ // Load up some messages so that we can copy them in later.
+ gMsgFile1 = do_get_file("../../../data/bugmail10");
+ gMsgFile2 = do_get_file("../../../data/bugmail11");
+ gMsgFile3 = do_get_file("../../../data/draft1");
+
+ // Create another folder to move and copy messages around, and force initialization.
+ gLocalFolder2 = localAccountUtils.rootFolder.createLocalSubfolder("folder2");
+
+ // Create a third folder for more testing.
+ gLocalFolder3 = localAccountUtils.rootFolder.createLocalSubfolder("folder3");
+
+ gTestArray.forEach(x => add_task(x));
+ run_next_test();
+}
+
+// debug utility to show the key/offset/ID relationship of messages in a folder
+function showMessages(folder, text) {
+ dump(`***** Show messages for folder <${folder.name}> "${text} *****\n`);
+ for (let hdr of folder.messages) {
+ dump(
+ ` key: ${hdr.messageKey} offset: ${hdr.messageOffset} size: ${hdr.messageSize} ID: ${hdr.messageId}\n`
+ );
+ }
+}
diff --git a/comm/mailnews/base/test/unit/test_folderCompact2.js b/comm/mailnews/base/test/unit/test_folderCompact2.js
new file mode 100644
index 0000000000..dc9d0b865a
--- /dev/null
+++ b/comm/mailnews/base/test/unit/test_folderCompact2.js
@@ -0,0 +1,300 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* 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/. */
+var { MailServices } = ChromeUtils.import(
+ "resource:///modules/MailServices.jsm"
+);
+const { PromiseTestUtils } = ChromeUtils.import(
+ "resource://testing-common/mailnews/PromiseTestUtils.jsm"
+);
+
+/*
+ * A few more specific mbox compaction tests for local folders:
+ * - mbox file contains exactly what we expect after compaction?
+ * test_folderCompact.js tends to rely on checking values in the msgDB.
+ * - works with messages larger than compaction code internal buffer?
+ * - X-Mozilla-Status/Status2/Keys headers handled as expected?
+ *
+ * Note: all these tests perform a seemingly-arbitrary delete.
+ * This is to trigger the folder compactor to actually do work. Without that
+ * delete it tends to think that compaction isn't required and does nothing.
+ */
+
+Services.prefs.setCharPref(
+ "mail.serverDefaultStoreContractID",
+ "@mozilla.org/msgstore/berkeleystore;1"
+);
+
+// Escape a string for more useful debug output - show EOLs and spaces.
+function esc(s) {
+ s = s.replace(/\r/g, "\\r");
+ s = s.replace(/\n/g, "\\n\n");
+ s = s.replace(/ /g, "\u2420"); // U+2420 SYMBOL FOR SPACE
+ return s;
+}
+
+// Load raw messages into folder (and sanitycheck them).
+function loadMsgs(folder, inputMsgs) {
+ folder.addMessageBatch(inputMsgs);
+ // Make sure all the loaded messages are the expected size.
+ // If this fails, it probably means addMessageBatch() no longer assumes input
+ // data is mbox format, which is good! See Bug 1763263.
+ let hdrs = Array.from(folder.messages);
+ for (let i = 0; i < hdrs.length; ++i) {
+ Assert.equal(
+ inputMsgs[i].length,
+ hdrs[i].messageSize,
+ `Loaded message ${i} should match size in msgDB`
+ );
+ }
+}
+
+// Delete the specified messages.
+async function deleteMsgs(folder, indexesToDelete) {
+ let hdrs = Array.from(folder.messages);
+ let doomed = indexesToDelete.map(i => hdrs[i]);
+ let listener = new PromiseTestUtils.PromiseCopyListener();
+ folder.deleteMessages(doomed, null, false, true, listener, true);
+ await listener.promise;
+}
+
+// Check the raw mbox file against what we expect to see.
+async function checkMbox(folder, outputMsgs) {
+ let bytes = await IOUtils.read(folder.filePath.path);
+ let mbox = "";
+ for (let b of bytes) {
+ mbox += String.fromCharCode(b);
+ }
+
+ let expected = "";
+ for (let raw of outputMsgs) {
+ // mbox has blank line between messages
+ expected += raw + "\n";
+ }
+
+ // Force all EOLs to linefeeds for comparisons.
+ // EOLs are handled inconsistently - most code will just leave EOLs as they
+ // come in, but new EOLs (e.g. added between messages in mbox) will use
+ // platform native EOLs. So our cheap and cheerful hack here is to just
+ // ditch all CRs and use pure LFs.
+ mbox = mbox.replace(/\r/g, "");
+ expected = expected.replace(/\r/g, "");
+
+ if (mbox != expected) {
+ // Pretty-print before we assert. Makes life so much easier.
+ dump(`=======mbox=========\n${esc(mbox)}\n============\n`);
+ dump(`=======expected=====\n${esc(expected)}\n============\n`);
+ }
+ Assert.ok(mbox == expected, "mbox should contain expected data");
+}
+
+// Some chunks from which we'll construct test messages.
+
+// These are the default X-Mozilla- headers for local folders (they are
+// re-written in place when flags and keywords are modified).
+let xhdrs =
+ `X-Mozilla-Status: 0000\r\n` +
+ `X-Mozilla-Status2: 00010000\r\n` + // 'New' flag is set
+ `X-Mozilla-Keys: \r\n`;
+
+let hdrs1 =
+ "Date: Fri, 21 Nov 1997 09:26:06 -0600\r\n" +
+ "From: bob@invalid\r\n" +
+ "Subject: Test message 1\r\n" +
+ "Message-ID: <blah1@invalid>\r\n";
+
+let bod1 = "Body of message 1.\r\n";
+
+let hdrs2 =
+ "Date: Fri, 21 Nov 1997 10:55:32 -0600\r\n" +
+ "From: bob@invalid\r\n" +
+ "Subject: Test message 2\r\n" +
+ "Message-ID: <blah2@invalid>\r\n";
+
+let bod2 = "Body of message2.\r\n";
+
+let hdrs3 =
+ `Date: Fri, 21 Nov 1997 11:09:14 -0600\r\n` +
+ `From: bob@invalid\r\n` +
+ `Message-ID: <blah3@invalid>\r\n` +
+ `Subject: Test message 3\r\n`;
+
+let bod3 = `message\r\nthree\r\nis multiple\r\nlines.\r\n`;
+
+let from = "From \r\n";
+
+// Check compact works after a simple delete.
+add_task(async function testSimple() {
+ localAccountUtils.clearAll();
+ localAccountUtils.loadLocalMailAccount();
+ let inbox = localAccountUtils.inboxFolder;
+
+ let inMsgs = [
+ `${from}${xhdrs}${hdrs1}\r\n${bod1}\r\n`,
+ `${from}${xhdrs}${hdrs2}\r\n${bod2}\r\n`,
+ `${from}${xhdrs}${hdrs3}\r\n${bod3}\r\n`,
+ ];
+ let doomed = [1]; // Delete message msg2.
+ // Out expected output:
+ let outMsgs = [
+ `${from}${xhdrs}${hdrs1}\r\n${bod1}\r\n`,
+ `${from}${xhdrs}${hdrs3}\r\n${bod3}\r\n`,
+ ];
+
+ loadMsgs(inbox, inMsgs);
+ await deleteMsgs(inbox, doomed);
+
+ let l = new PromiseTestUtils.PromiseUrlListener();
+ inbox.compact(l, null);
+ await l.promise;
+
+ await checkMbox(inbox, outMsgs);
+});
+
+// Check that local folder compact adds missing X-Mozilla- headers.
+add_task(async function testMissingXMozillaHdrs() {
+ localAccountUtils.clearAll();
+ localAccountUtils.loadLocalMailAccount();
+ let inbox = localAccountUtils.inboxFolder;
+
+ // No X-Mozilla-* headers on input.
+ let inMsgs = [
+ `${from}${hdrs1}\r\n${bod1}\r\n`,
+ `${from}${hdrs2}\r\n${bod2}\r\n`,
+ `${from}${hdrs3}\r\n${bod3}\r\n`,
+ ];
+ let doomed = [1]; // Delete msg2.
+ // Out expected output.
+ // Compact should have added X-Mozilla-* headers.
+ let outMsgs = [
+ `${from}${xhdrs}${hdrs1}\r\n${bod1}\r\n`,
+ `${from}${xhdrs}${hdrs3}\r\n${bod3}\r\n`,
+ ];
+
+ loadMsgs(inbox, inMsgs);
+ await deleteMsgs(inbox, doomed);
+
+ let l = new PromiseTestUtils.PromiseUrlListener();
+ inbox.compact(l, null);
+ await l.promise;
+
+ await checkMbox(inbox, outMsgs);
+});
+
+// Check localfolder compact works on messages are bigger than internal read buffer.
+add_task(async function testBigMessages() {
+ localAccountUtils.clearAll();
+ localAccountUtils.loadLocalMailAccount();
+ let inbox = localAccountUtils.inboxFolder;
+
+ // Compaction uses buffer of around 16KB, so we'll go way bigger.
+ let targSize = 256 * 1024;
+
+ let inMsgs = [];
+ let outMsgs = [];
+ let doomed = [0, 1]; // We'll delete the first 2 messages.
+ for (let i = 0; i < 5; ++i) {
+ let raw =
+ `From \r\n` +
+ xhdrs +
+ `Date: Fri, 21 Nov 1997 09:55:06 -0600\r\n` +
+ `From: bob${i}@invalid\r\n` +
+ `Message-ID: <blah${i}@invalid>\r\n` +
+ `\r\n`;
+ while (raw.length < targSize) {
+ raw +=
+ "BlahBlahBlahBlahBlahBlahBlahBlahBlah" +
+ "BlahBlahBlahBlahBlahBlahBlahBlahBlah\r\n";
+ }
+ raw += `\r\n`;
+ inMsgs.push(raw);
+ if (!doomed.includes(i)) {
+ outMsgs.push(raw);
+ }
+ }
+
+ loadMsgs(inbox, inMsgs);
+
+ await deleteMsgs(inbox, doomed);
+
+ let l = new PromiseTestUtils.PromiseUrlListener();
+ inbox.compact(l, null);
+ await l.promise;
+
+ // outMsgs is what we expect to see in the mbox.
+ await checkMbox(inbox, outMsgs);
+});
+
+// Check that local folder compact moves X-Mozilla-* headers to start of
+// header block.
+add_task(async function testMoveXMozillaHdrs() {
+ localAccountUtils.clearAll();
+ localAccountUtils.loadLocalMailAccount();
+ let inbox = localAccountUtils.inboxFolder;
+
+ // These have X-Mozilla-* headers after all the other headers.
+ let inMsgs = [
+ `${from}${hdrs1}${xhdrs}\r\n${bod1}\r\n`,
+ `${from}${hdrs2}${xhdrs}\r\n${bod2}\r\n`,
+ `${from}${hdrs3}${xhdrs}\r\n${bod3}\r\n`,
+ ];
+ let doomed = [1]; // Delete msg2.
+ // The messages we expect to see in the final mbox.
+ // Compact should have moved the X-Mozilla-* headers to the front.
+ let outMsgs = [
+ `${from}${xhdrs}${hdrs1}\r\n${bod1}\r\n`,
+ `${from}${xhdrs}${hdrs3}\r\n${bod3}\r\n`,
+ ];
+
+ loadMsgs(inbox, inMsgs);
+ await deleteMsgs(inbox, doomed);
+
+ let l = new PromiseTestUtils.PromiseUrlListener();
+ inbox.compact(l, null);
+ await l.promise;
+
+ await checkMbox(inbox, outMsgs);
+});
+
+// Check that local folder compact handles large X-Mozilla-Keys value.
+add_task(async function testBigXMozillaKeys() {
+ localAccountUtils.clearAll();
+ localAccountUtils.loadLocalMailAccount();
+ let inbox = localAccountUtils.inboxFolder;
+
+ let bigKeyword =
+ "HugeGreatBigStupidlyLongKeywordNameWhichWillDefinitelyOverflowThe80" +
+ "CharactersUsuallyReservedInTheKeywordsHeaderForInPlaceEditing";
+
+ let inMsgs = [
+ `${from}${xhdrs}${hdrs1}\r\n${bod1}\r\n`,
+ `${from}${xhdrs}${hdrs2}\r\n${bod2}\r\n`,
+ `${from}${xhdrs}${hdrs3}\r\n${bod3}\r\n`,
+ ];
+ let doomed = [1]; // Delete msg2.
+
+ let bigxhdrs =
+ `X-Mozilla-Status: 0000\r\n` +
+ `X-Mozilla-Status2: 00010000\r\n` + // 'New' flag is set
+ `X-Mozilla-Keys: ${bigKeyword}\r\n`;
+
+ // The messages we expect to see in the final mbox:
+ let outMsgs = [
+ `${from}${bigxhdrs}${hdrs1}\r\n${bod1}\r\n`,
+ `${from}${xhdrs}${hdrs3}\r\n${bod3}\r\n`,
+ ];
+
+ loadMsgs(inbox, inMsgs);
+
+ let msgs = Array.from(inbox.messages);
+ inbox.addKeywordsToMessages([msgs[0]], bigKeyword);
+
+ await deleteMsgs(inbox, doomed);
+
+ let l = new PromiseTestUtils.PromiseUrlListener();
+ inbox.compact(l, null);
+ await l.promise;
+
+ await checkMbox(inbox, outMsgs);
+});
diff --git a/comm/mailnews/base/test/unit/test_folderLookupService.js b/comm/mailnews/base/test/unit/test_folderLookupService.js
new file mode 100644
index 0000000000..039fa2a383
--- /dev/null
+++ b/comm/mailnews/base/test/unit/test_folderLookupService.js
@@ -0,0 +1,83 @@
+/* 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/. */
+
+/**
+ * This tests that nsIFolderLookupService works according to specification.
+ */
+var { AppConstants } = ChromeUtils.importESModule(
+ "resource://gre/modules/AppConstants.sys.mjs"
+);
+var kRootURI = "mailbox://nobody@Local%20Folders";
+
+add_task(async function test_fls_basics() {
+ let fls = Cc["@mozilla.org/mail/folder-lookup;1"].getService(
+ Ci.nsIFolderLookupService
+ );
+
+ // Make sure that the local mail account exists.
+ localAccountUtils.loadLocalMailAccount();
+
+ // There should exist an inbox.
+ Assert.equal(
+ fls.getFolderForURL(kRootURI + "/Inbox"),
+ localAccountUtils.inboxFolder
+ );
+
+ // Can we get the root folder?
+ let root = localAccountUtils.rootFolder;
+ Assert.equal(fls.getFolderForURL(kRootURI), root);
+
+ // The child folder Child doesn't exist yet... make sure that fls doesn't
+ // return it yet
+ Assert.equal(fls.getFolderForURL(kRootURI + "/Child"), null);
+
+ // Create the Child folder, and test we can find it.
+ // (NOTE: createSubFolder() can be async for IMAP folders. But for now
+ // we know we're using local folders and it'll happen right away).
+ root.createSubfolder("Child", null);
+ Assert.equal(
+ fls.getFolderForURL(kRootURI + "/Child"),
+ root.getChildNamed("Child")
+ );
+
+ // Try it again... it should load from cache this time.
+ Assert.equal(
+ fls.getFolderForURL(kRootURI + "/Child"),
+ root.getChildNamed("Child")
+ );
+
+ // Now delete the folder; we should be unable to find it
+ root.propagateDelete(root.getChildNamed("Child"), true);
+ Assert.equal(fls.getFolderForURL(kRootURI + "/Child"), null);
+
+ Assert.equal(fls.getFolderForURL(kRootURI + "/"), null);
+ Assert.equal(
+ fls.getFolderForURL("mailbox://idonotexist@Local%20Folders"),
+ null
+ );
+});
+
+add_task(async function test_unicode_uris() {
+ let fls = Cc["@mozilla.org/mail/folder-lookup;1"].getService(
+ Ci.nsIFolderLookupService
+ );
+ localAccountUtils.loadLocalMailAccount();
+ let root = localAccountUtils.rootFolder;
+
+ // Create a folder with non-ASCII characters.
+ // Unicode abuse - dotless letter i and a metal umlaut over the n.
+ let tapName = "Sp\u0131n\u0308al Tap";
+ root.createSubfolder(tapName, null);
+
+ // Make sure we can find it.
+ // (URI is percent-escaped utf-8)
+ let tapNameEscaped = "Sp%C4%B1n%CC%88al%20Tap";
+ if (AppConstants.platform == "win") {
+ // For !ConvertibleToNative(), folder name is hashed on Windows.
+ tapNameEscaped = "a2d874f7";
+ }
+ let tapURI = kRootURI + "/" + tapNameEscaped;
+ let tap = fls.getFolderForURL(tapURI);
+ Assert.equal(tap.URI, tapURI);
+});
diff --git a/comm/mailnews/base/test/unit/test_folderStringProperties.js b/comm/mailnews/base/test/unit/test_folderStringProperties.js
new file mode 100644
index 0000000000..8f508625b7
--- /dev/null
+++ b/comm/mailnews/base/test/unit/test_folderStringProperties.js
@@ -0,0 +1,41 @@
+/* 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/. */
+
+/*
+ * Test the nsIMsgFolder .(get|set)StringProperty methods.
+ */
+
+add_task(function test_string_properties() {
+ localAccountUtils.loadLocalMailAccount();
+ let root = localAccountUtils.incomingServer.rootMsgFolder;
+
+ // Ensure unset properties return an error.
+ Assert.throws(function () {
+ root.getStringProperty("this-property-doesnt-exist");
+ }, /NS_ERROR_.*/);
+
+ // Check basic set/get operation.
+ root.setStringProperty("test-property", "wibble");
+ Assert.equal(root.getStringProperty("test-property"), "wibble");
+
+ // Keys are case-sensitive.
+ Assert.throws(function () {
+ root.getStringProperty("TEST-PROPERTY");
+ }, /NS_ERROR_.*/);
+
+ // Values with non-latin chars?
+ root.setStringProperty("test-property", "日本語");
+ Assert.equal(root.getStringProperty("test-property"), "日本語");
+
+ // Check that things stay as strings, even if they are values that could
+ // be misinterpreted in JSON.
+ root.setStringProperty("test-property", "");
+ Assert.equal(root.getStringProperty("test-property"), "");
+
+ root.setStringProperty("test-property", "null");
+ Assert.equal(root.getStringProperty("test-property"), "null");
+
+ root.setStringProperty("test-property", "0");
+ Assert.equal(root.getStringProperty("test-property"), "0");
+});
diff --git a/comm/mailnews/base/test/unit/test_formatFileSize.js b/comm/mailnews/base/test/unit/test_formatFileSize.js
new file mode 100644
index 0000000000..1269fd33c3
--- /dev/null
+++ b/comm/mailnews/base/test/unit/test_formatFileSize.js
@@ -0,0 +1,144 @@
+/* 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/. */
+
+/*
+ * Tests for the formatFileSize method.
+ */
+
+var gStringBundle = Services.strings.createBundle(
+ "chrome://messenger/locale/messenger.properties"
+);
+
+var gMessenger = Cc["@mozilla.org/messenger;1"].createInstance(Ci.nsIMessenger);
+
+function isDigit(c) {
+ return "0123456789".includes(c);
+}
+
+function test_formatFileSize(aArgs) {
+ const strings = {
+ b: "byteAbbreviation2",
+ kb: "kiloByteAbbreviation2",
+ mb: "megaByteAbbreviation2",
+ gb: "gigaByteAbbreviation2",
+ tb: "teraByteAbbreviation2",
+ pb: "petaByteAbbreviation2",
+ };
+
+ let actual = gMessenger.formatFileSize(aArgs.bytes, aArgs.useKB);
+ let expected = gStringBundle
+ .GetStringFromName(strings[aArgs.units])
+ .replace("%.*f", aArgs.mantissa);
+
+ // If the actual string contains a non-numeric character at the position
+ // where we'd expect a decimal separator, assume it is a localized separator
+ // and just convert it to a dot for easy comparing.
+ let separatorPos = aArgs.mantissa.indexOf(".");
+ if (!isDigit(actual.charAt(separatorPos))) {
+ actual =
+ actual.substring(0, separatorPos) + "." + actual.substr(separatorPos + 1);
+ }
+
+ Assert.equal(actual, expected);
+}
+
+var test_data = [
+ { bytes: 0, useKB: false, mantissa: "0", units: "b" },
+ { bytes: 1, useKB: false, mantissa: "1", units: "b" },
+ { bytes: 10, useKB: false, mantissa: "10", units: "b" },
+ { bytes: 999, useKB: false, mantissa: "999", units: "b" },
+ { bytes: 1000, useKB: false, mantissa: "1.0", units: "kb" },
+ { bytes: 1024, useKB: false, mantissa: "1.0", units: "kb" },
+ { bytes: 10 * 1024, useKB: false, mantissa: "10.0", units: "kb" },
+ { bytes: 999 * 1024, useKB: false, mantissa: "999", units: "kb" },
+ { bytes: 1000 * 1024, useKB: false, mantissa: "1.0", units: "mb" },
+ { bytes: 1024 * 1024, useKB: false, mantissa: "1.0", units: "mb" },
+ { bytes: 10 * 1024 * 1024, useKB: false, mantissa: "10.0", units: "mb" },
+ { bytes: 999 * 1024 * 1024, useKB: false, mantissa: "999", units: "mb" },
+ { bytes: 1000 * 1024 * 1024, useKB: false, mantissa: "1.0", units: "gb" },
+ { bytes: 1024 * 1024 * 1024, useKB: false, mantissa: "1.0", units: "gb" },
+ {
+ bytes: 10 * 1024 * 1024 * 1024,
+ useKB: false,
+ mantissa: "10.0",
+ units: "gb",
+ },
+ {
+ bytes: 999 * 1024 * 1024 * 1024,
+ useKB: false,
+ mantissa: "999",
+ units: "gb",
+ },
+ {
+ bytes: 1000 * 1024 * 1024 * 1024,
+ useKB: false,
+ mantissa: "1.0",
+ units: "tb",
+ },
+ {
+ bytes: 1024 * 1024 * 1024 * 1024,
+ useKB: false,
+ mantissa: "1.0",
+ units: "tb",
+ },
+ {
+ bytes: 10 * 1024 * 1024 * 1024 * 1024,
+ useKB: false,
+ mantissa: "10.0",
+ units: "tb",
+ },
+ {
+ bytes: 999 * 1024 * 1024 * 1024 * 1024,
+ useKB: false,
+ mantissa: "999",
+ units: "tb",
+ },
+ {
+ bytes: 1000 * 1024 * 1024 * 1024 * 1024,
+ useKB: false,
+ mantissa: "1.0",
+ units: "pb",
+ },
+ {
+ bytes: 1000 * 1024 * 1024 * 1024 * 1024,
+ useKB: false,
+ mantissa: "1.0",
+ units: "pb",
+ },
+ {
+ bytes: 1024 * 1024 * 1024 * 1024 * 1024,
+ useKB: false,
+ mantissa: "1.0",
+ units: "pb",
+ },
+ {
+ bytes: 10 * 1024 * 1024 * 1024 * 1024 * 1024,
+ useKB: false,
+ mantissa: "10.0",
+ units: "pb",
+ },
+ {
+ bytes: 999 * 1024 * 1024 * 1024 * 1024 * 1024,
+ useKB: false,
+ mantissa: "999",
+ units: "pb",
+ },
+ {
+ bytes: 1000 * 1024 * 1024 * 1024 * 1024 * 1024,
+ useKB: false,
+ mantissa: "1000",
+ units: "pb",
+ },
+
+ { bytes: 0, useKB: true, mantissa: "0", units: "kb" },
+ { bytes: 1, useKB: true, mantissa: "0.1", units: "kb" },
+ { bytes: 500, useKB: true, mantissa: "0.5", units: "kb" },
+ { bytes: 999, useKB: true, mantissa: "1.0", units: "kb" },
+];
+
+add_task(function test_format_file_size() {
+ test_data.map(entry => {
+ test_formatFileSize(entry);
+ });
+});
diff --git a/comm/mailnews/base/test/unit/test_getMsgTextFromStream.js b/comm/mailnews/base/test/unit/test_getMsgTextFromStream.js
new file mode 100644
index 0000000000..b803619e21
--- /dev/null
+++ b/comm/mailnews/base/test/unit/test_getMsgTextFromStream.js
@@ -0,0 +1,88 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* ***** BEGIN LICENSE BLOCK *****
+ *
+ * Any copyright is dedicated to the Public Domain.
+ * http://creativecommons.org/licenses/publicdomain/
+ *
+ * ***** END LICENSE BLOCK ***** */
+
+/**
+ * Test suite for GetMsgTextFromStream.
+ *
+ * Currently tests: text/plain, text/html -- with tags stripped and without,
+ * base64, multipart.
+ * Does not currently test: quoted-printable, stripping quotes, UTF-8, small values of
+ * bytesToRead.
+ */
+var kDataRoot = "../../../data/";
+
+function create(fileName, bytes, compressQuotes, stripHTML, outContentType) {
+ return {
+ name: fileName,
+ bytesToRead: bytes,
+ compressQuotes,
+ stripHTML,
+ contentType: outContentType,
+ };
+}
+
+var gTestFiles = [
+ create("basic1", 1024, false, false, "text/plain"), // Simple plain text
+ create("basic1", 1024, false, true, "text/plain"), // should be same as above
+ create("basic2", 1024, false, false, "text/html"), // Simple HTML
+ create("basic3", 1024, false, true, "text/html"), // HTML with tags stripped out
+ create("basic4", 1024, false, false, "text/plain"), // No content type, should be assumed to be text/plain
+ create("basic4", 1024, false, true, "text/plain"),
+ create("basic5", 1024, false, false, "text/plain"), // HTML content in text/plain
+ create("basic5", 1024, false, true, "text/plain"),
+ create("base64-1", 1024, false, false, "text/plain"), // base64 text/plain
+ create("base64-1", 1024, false, true, "text/plain"),
+ create("base64-2", 1024, false, false, "text/html"), // base64 text/html
+ create("base64-3", 1024, false, true, "text/html"), // strip out tags here
+ create("multipart1", 1024, false, false, "text/plain"), // basic multipart message
+ create("multipart1", 1024, false, true, "text/plain"),
+ create("multipart2", 1024, false, false, "text/html"), // multipart HTML
+ create("multipart3", 1024, false, true, "text/html"),
+ create("multipart4", 1024, false, false, "text/plain"), // text with no headers
+ create("multipart4", 1024, false, true, "text/plain"),
+ create("multipart-base64-1", 1024, false, false, "text/plain"), // base64 encoded text
+ create("multipart-base64-1", 1024, false, true, "text/plain"),
+ create("multipart-base64-2", 1024, false, false, "text/html"),
+ create("multipart-base64-3", 1024, false, true, "text/html"),
+ create("multipart-complex1", 1024, false, true, "text/html"), // Things get more complex here
+ create("multipart-complex2", 1024, false, false, "text/plain"),
+ create("multipart-complex2", 1024, false, true, "text/plain"),
+];
+
+function run_test() {
+ localAccountUtils.loadLocalMailAccount();
+ var folder = localAccountUtils.incomingServer.rootMsgFolder;
+
+ gTestFiles.forEach(function (test) {
+ dump("Testing " + test.name + "\n");
+ var inFile = do_get_file(kDataRoot + test.name);
+ var inStream = Cc[
+ "@mozilla.org/network/file-input-stream;1"
+ ].createInstance(Ci.nsIFileInputStream);
+ inStream.init(inFile, -1, -1, Ci.nsIFileInputStream.CLOSE_ON_EOF);
+
+ // Now get the message body using getMsgTextFromStream
+ var contentType = {};
+ var body = folder.getMsgTextFromStream(
+ inStream,
+ "",
+ test.bytesToRead,
+ 65536,
+ test.compressQuotes,
+ test.stripHTML,
+ contentType
+ );
+
+ // Now we need to compare the output
+ Assert.equal(test.contentType, contentType.value);
+
+ var resultFile = do_get_file(kDataRoot + test.name + ".out");
+ var actualBody = mailTestUtils.loadFileToString(resultFile, "UTF-8");
+ Assert.equal(body, actualBody);
+ });
+}
diff --git a/comm/mailnews/base/test/unit/test_headerFoldingInDatabase.js b/comm/mailnews/base/test/unit/test_headerFoldingInDatabase.js
new file mode 100644
index 0000000000..c3165ba720
--- /dev/null
+++ b/comm/mailnews/base/test/unit/test_headerFoldingInDatabase.js
@@ -0,0 +1,58 @@
+/* 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/. */
+
+/*
+ * Testing header folding in nsParseMailMessageState::ParseHeaders(),
+ * see bug 1454257 and bug 1456001.
+ */
+
+var { MailServices } = ChromeUtils.import(
+ "resource:///modules/MailServices.jsm"
+);
+
+var hdr;
+
+function run_test() {
+ localAccountUtils.loadLocalMailAccount();
+
+ var copyListener = {
+ OnStartCopy() {},
+ OnProgress(aProgress, aProgressMax) {},
+ SetMessageKey(aKey) {
+ hdr = localAccountUtils.inboxFolder.GetMessageHeader(aKey);
+ },
+ SetMessageId(aMessageId) {},
+ OnStopCopy(aStatus) {
+ continueTest();
+ },
+ };
+
+ // Get a message into the local filestore.
+ var message = do_get_file("../../../data/badly-folded-headers.eml");
+ do_test_pending();
+ MailServices.copy.copyFileMessage(
+ message,
+ localAccountUtils.inboxFolder,
+ null,
+ false,
+ 0,
+ "",
+ copyListener,
+ null
+ );
+}
+
+function continueTest() {
+ Assert.equal(hdr.author, "sender@example.com");
+ Assert.equal(
+ hdr.recipients,
+ '"Recipient with spaces" <recipient@example.com>'
+ );
+ Assert.equal(
+ hdr.subject,
+ "Badly folded headers, one line with space between To and From"
+ );
+ hdr = null;
+ do_test_finished();
+}
diff --git a/comm/mailnews/base/test/unit/test_hostnameUtils.js b/comm/mailnews/base/test/unit/test_hostnameUtils.js
new file mode 100644
index 0000000000..5f8839165d
--- /dev/null
+++ b/comm/mailnews/base/test/unit/test_hostnameUtils.js
@@ -0,0 +1,276 @@
+/* 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/. */
+
+/*
+ * Tests for hostnameUtils.jsm.
+ */
+
+var {
+ isLegalHostName,
+ isLegalHostNameOrIP,
+ isLegalIPAddress,
+ isLegalIPv4Address,
+ isLegalIPv6Address,
+ isLegalLocalIPAddress,
+} = ChromeUtils.import("resource:///modules/hostnameUtils.jsm");
+
+/**
+ * Checks if valid and invalid IPs are properly allowed or rejected.
+ */
+function test_IPaddresses() {
+ const kIPsToTest = [
+ // isValid, IP addr. isIPv6, isLocal, extend, result
+ // IPv4
+ [true, "1.2.3.4", false, false, false],
+ [true, "123.245.111.222", false, false, false],
+ [true, "255.255.255.255", false, false, false],
+ [true, "1.2.0.4", false, false, false],
+ [true, "1.2.3.4", false, false, false],
+ [true, "127.1.2.3", false, true, false],
+ [true, "10.1.2.3", false, true, false],
+ [true, "192.168.2.3", false, true, false],
+
+ [false, "1.2.3.4.5", false, false, false],
+ [false, "1.2.3", false, false, false],
+ [false, "1.2.3.", false, false, false],
+ [false, ".1.2.3", false, false, false],
+ [false, "1.2.3.256", false, false, false],
+ [false, "1.2.3.12345", false, false, false],
+ [false, "1.2..123", false, false, false],
+ [false, "1", false, false, false],
+ [false, "", false, false, false],
+ [false, "0.1.2.3", false, false, false],
+ [false, "0.0.2.3", false, false, false],
+ [false, "0.0.0.0", false, false, false],
+ [false, "1.2.3.d", false, false, false],
+ [false, "a.b.c.d", false, false, false],
+ [false, "a.b.c.d", false, false, true],
+ // IPv6
+ [
+ true,
+ "2001:0db8:85a3:0000:0000:8a2e:0370:7334",
+ true,
+ false,
+ false,
+ "2001:0db8:85a3:0000:0000:8a2e:0370:7334",
+ ],
+ [
+ true,
+ "2001:db8:85a3:0:0:8a2e:370:7334",
+ true,
+ false,
+ false,
+ "2001:0db8:85a3:0000:0000:8a2e:0370:7334",
+ ],
+ [
+ true,
+ "2001:db8:85a3::8a2e:370:7334",
+ true,
+ false,
+ false,
+ "2001:0db8:85a3:0000:0000:8a2e:0370:7334",
+ ],
+ [
+ true,
+ "2001:0db8:85a3:0000:0000:8a2e:0370:",
+ true,
+ false,
+ false,
+ "2001:0db8:85a3:0000:0000:8a2e:0370:0000",
+ ],
+ [
+ true,
+ "::ffff:c000:0280",
+ true,
+ false,
+ false,
+ "0000:0000:0000:0000:0000:ffff:c000:0280",
+ ],
+ [
+ true,
+ "::ffff:192.0.2.128",
+ true,
+ false,
+ false,
+ "0000:0000:0000:0000:0000:ffff:c000:0280",
+ ],
+ [
+ true,
+ "2001:db8::1",
+ true,
+ false,
+ false,
+ "2001:0db8:0000:0000:0000:0000:0000:0001",
+ ],
+ [
+ true,
+ "2001:DB8::1",
+ true,
+ false,
+ false,
+ "2001:0db8:0000:0000:0000:0000:0000:0001",
+ ],
+ [
+ true,
+ "1:2:3:4:5:6:7:8",
+ true,
+ false,
+ false,
+ "0001:0002:0003:0004:0005:0006:0007:0008",
+ ],
+
+ [true, "::1", true, true, false, "0000:0000:0000:0000:0000:0000:0000:0001"],
+ [
+ true,
+ "::0000:0000:1",
+ true,
+ true,
+ false,
+ "0000:0000:0000:0000:0000:0000:0000:0001",
+ ],
+
+ [false, "::", true, false, false],
+ [false, "2001:0db8:85a3:0000:0000:8a2e:0370:73346", true, false, false],
+ [false, "2001:0db8:85a3:0000:0000:8a2e:0370:7334:1", true, false, false],
+ [false, "2001:0db8:85a3:0000:0000:8a2e:0370:7334x", true, false, false],
+ [false, "2001:0db8:85a3:0000:0000:8a2e:03707334", true, false, false],
+ [false, "2001:0db8:85a3:0000:0000x8a2e:0370:7334", true, false, false],
+ [false, "2001:0db8:85a3:0000:0000:::1", true, false, false],
+ [false, "2001:0db8:85a3:0000:0000:0000:some:junk", true, false, false],
+ [false, "2001:0db8:85a3:0000:0000:0000::192.0.2.359", true, false, false],
+ [false, "some::junk", true, false, false],
+ [false, "some_junk", true, false, false],
+
+ // Extended formats of IPv4, hex, octal, decimal up to DWORD
+ [true, "0xff.0x12.0x45.0x78", false, false, true, "255.18.69.120"],
+ [true, "01.0123.056.077", false, false, true, "1.83.46.63"],
+ [true, "0xff.2.3.4", false, false, true, "255.2.3.4"],
+ [true, "0xff.2.3.077", false, false, true, "255.2.3.63"],
+ [true, "0x7f.2.3.077", false, true, true, "127.2.3.63"],
+
+ [false, "0xZZ.1.2.3", false, false, true],
+ [false, "0x00.0123.056.077", false, false, true],
+ [false, "0x11.0123.056.078", false, false, true],
+ [false, "0x11.0123.056.0789", false, false, true],
+
+ [true, "1234566945", false, false, true, "73.149.255.33"],
+ [false, "12345", false, false, true],
+ [false, "123456789123456", false, false, true],
+
+ [true, "127.1", false, true, true, "127.0.0.1"],
+ [true, "0x7f.100", false, true, true, "127.0.0.100"],
+ [true, "0x7f.100.1000", false, true, true, "127.100.3.232"],
+ [true, "0xff.100.1024", false, false, true, "255.100.4.0"],
+ [true, "0xC0.0xA8.0x2A48", false, true, true, "192.168.42.72"],
+ [true, "0xC0.0xA82A48", false, true, true, "192.168.42.72"],
+ [true, "0xC0A82A48", false, true, true, "192.168.42.72"],
+ [true, "0324.062477106", false, false, true, "212.202.126.70"],
+
+ [false, "0.0.1000", false, false, true],
+ [false, "0324.06247710677", false, false, true],
+ ];
+
+ for (let item of kIPsToTest) {
+ let result = null;
+ let [isValid, address, isIPv6, isLocal, isExtended, wantedResult] = item;
+ if (!wantedResult) {
+ wantedResult = isValid ? address : null;
+ }
+
+ if (isIPv6) {
+ result = isLegalIPv6Address(address);
+ Assert.equal(result, wantedResult);
+ if (isValid) {
+ // If this is valid IPv6, it can't be valid IPv4. The opposite is unknown.
+ result = isLegalIPv4Address(address);
+ Assert.equal(result, null);
+ }
+ } else {
+ result = isLegalIPv4Address(address, isExtended);
+ Assert.equal(result, wantedResult);
+ if (isValid) {
+ // If this is valid IPv4, it can't be valid IPv6. The opposite is unknown.
+ result = isLegalIPv6Address(address);
+ Assert.equal(result, null);
+ }
+ }
+
+ result = isLegalIPAddress(address, isExtended);
+ Assert.equal(result, wantedResult);
+
+ if (isValid) {
+ // isLegalLocalIPAddress operates on a normalized address,
+ // not the original one.
+ result = isLegalLocalIPAddress(result);
+ Assert.equal(result, isLocal);
+ }
+
+ // If something is a valid IP, it also passes isLegalHostNameOrIP.
+ // However, an invalid IP string may still be a valid hostname.
+ // So only check success if the IP is valid.
+ result = isLegalHostNameOrIP(address, isExtended);
+ if (isValid) {
+ Assert.equal(result, wantedResult);
+ }
+ }
+}
+/**
+ * Checks if valid and invalid host names are properly allowed or rejected.
+ */
+function test_hostnames() {
+ const kHostsToTest = [
+ // isValid, hostname
+ [true, "localhost"],
+ [true, "some-server"],
+ [true, "server.company.invalid"],
+ [true, "server.comp-any.invalid"],
+ [true, "server.123.invalid"],
+ [true, "1server.123.invalid"],
+ [true, "1.2.3.4.5"],
+ [true, "very.log.sub.domain.name.invalid"],
+ [true, "1234567890"],
+ [true, "1234567890."], // FQDN
+ [true, "server.company.invalid."], // FQDN
+
+ [false, ""],
+ [false, "server.badcompany!.invalid"],
+ [false, "server._badcompany.invalid"],
+ [false, "server.bad_company.invalid"],
+ [false, "server.badcompany-.invalid"],
+ [false, "server.bad company.invalid"],
+ [false, "server.b…dcompany.invalid"],
+ [false, ".server.badcompany.invalid"],
+ [
+ false,
+ "make-this-a-long-host-name-component-that-is-over-63-characters-long.invalid",
+ ],
+ [
+ false,
+ "append-strings-to-make-this-a-too-long-host-name.that-is-really-over-255-characters-long.invalid." +
+ "append-strings-to-make-this-a-too-long-host-name.that-is-really-over-255-characters-long.invalid." +
+ "append-strings-to-make-this-a-too-long-host-name.that-is-really-over-255-characters-long.invalid." +
+ "append-strings-to-make-this-a-too-long-host-name.that-is-really-over-255-characters-long.invalid",
+ ],
+ ];
+
+ for (let item of kHostsToTest) {
+ let result = null;
+ let [wantedResult, hostname] = item;
+ wantedResult = wantedResult ? hostname : null;
+
+ result = isLegalHostName(hostname);
+ Assert.equal(result, wantedResult);
+
+ result = isLegalHostNameOrIP(hostname, false);
+ Assert.equal(result, wantedResult);
+ }
+}
+
+var gTests = [test_IPaddresses, test_hostnames];
+
+function run_test() {
+ for (let test of gTests) {
+ test();
+ }
+}
diff --git a/comm/mailnews/base/test/unit/test_identity.js b/comm/mailnews/base/test/unit/test_identity.js
new file mode 100644
index 0000000000..c28615451a
--- /dev/null
+++ b/comm/mailnews/base/test/unit/test_identity.js
@@ -0,0 +1,69 @@
+/* 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/. */
+
+/**
+ * Tests the UID attribute of identities.
+ */
+add_task(async function testUID() {
+ const UUID_REGEXP =
+ /^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/;
+
+ // Create an identity and check it the UID is set when accessed.
+
+ let identityA = MailServices.accounts.createIdentity();
+ Assert.stringMatches(
+ identityA.UID,
+ UUID_REGEXP,
+ "identity A's UID should exist and be a UUID"
+ );
+ Assert.equal(
+ Services.prefs.getStringPref(`mail.identity.${identityA.key}.uid`),
+ identityA.UID,
+ "identity A's UID should be saved to the preferences"
+ );
+ Assert.throws(
+ () => (identityA.UID = "00001111-2222-3333-4444-555566667777"),
+ /NS_ERROR_ABORT/,
+ "identity A's UID should be unchangeable after it is set"
+ );
+
+ // Create a second identity and check the two UIDs don't match.
+
+ let identityB = MailServices.accounts.createIdentity();
+ Assert.stringMatches(
+ identityB.UID,
+ UUID_REGEXP,
+ "identity B's UID should exist and be a UUID"
+ );
+ Assert.equal(
+ Services.prefs.getStringPref(`mail.identity.${identityB.key}.uid`),
+ identityB.UID,
+ "identity B's UID should be saved to the preferences"
+ );
+ Assert.notEqual(
+ identityB.UID,
+ identityA.UID,
+ "identity B's UID should not be the same as identity A's"
+ );
+
+ // Create a third identity and set the UID before it is accessed.
+
+ let identityC = MailServices.accounts.createIdentity();
+ identityC.UID = "11112222-3333-4444-5555-666677778888";
+ Assert.equal(
+ identityC.UID,
+ "11112222-3333-4444-5555-666677778888",
+ "identity C's UID set correctly"
+ );
+ Assert.equal(
+ Services.prefs.getStringPref(`mail.identity.${identityC.key}.uid`),
+ "11112222-3333-4444-5555-666677778888",
+ "identity C's UID should be saved to the preferences"
+ );
+ Assert.throws(
+ () => (identityC.UID = "22223333-4444-5555-6666-777788889999"),
+ /NS_ERROR_ABORT/,
+ "identity C's UID should be unchangeable after it is set"
+ );
+});
diff --git a/comm/mailnews/base/test/unit/test_imapPump.js b/comm/mailnews/base/test/unit/test_imapPump.js
new file mode 100644
index 0000000000..ccc34bae60
--- /dev/null
+++ b/comm/mailnews/base/test/unit/test_imapPump.js
@@ -0,0 +1,81 @@
+/* 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/. */
+
+/**
+ * Simple demonstration of the imap pump test method.
+ */
+
+// async support
+/* import-globals-from ../../../test/resources/logHelper.js */
+load("../../../resources/logHelper.js");
+const { PromiseTestUtils } = ChromeUtils.import(
+ "resource://testing-common/mailnews/PromiseTestUtils.jsm"
+);
+
+// IMAP pump
+var { IMAPPump, setupIMAPPump, teardownIMAPPump } = ChromeUtils.import(
+ "resource://testing-common/mailnews/IMAPpump.jsm"
+);
+var { ImapMessage } = ChromeUtils.import(
+ "resource://testing-common/mailnews/Imapd.jsm"
+);
+
+var { fsDebugAll } = ChromeUtils.import(
+ "resource://testing-common/mailnews/Maild.jsm"
+);
+
+// Globals
+
+// Messages to load must have CRLF line endings, that is Windows style
+var gMessage = "bugmail10"; // message file used as the test message
+
+// Definition of tests
+
+// load and update a message in the imap fake server
+
+var gTestArray = [
+ // initial setup of IMAP environment
+ setupIMAPPump,
+
+ // optionally set server parameters, here enabling debug messages
+ function serverParms() {
+ IMAPPump.server.setDebugLevel(fsDebugAll);
+ },
+
+ // the main test
+ async function loadImapMessage() {
+ IMAPPump.mailbox.addMessage(
+ new ImapMessage(specForFileName(gMessage), IMAPPump.mailbox.uidnext++, [])
+ );
+ let promiseUrlListener = new PromiseTestUtils.PromiseUrlListener();
+ IMAPPump.inbox.updateFolderWithListener(null, promiseUrlListener);
+ await promiseUrlListener.promise;
+
+ Assert.equal(1, IMAPPump.inbox.getTotalMessages(false));
+ let msgHdr = mailTestUtils.firstMsgHdr(IMAPPump.inbox);
+ Assert.ok(msgHdr instanceof Ci.nsIMsgDBHdr);
+ },
+
+ // all done
+ teardownIMAPPump,
+];
+
+add_setup(() => {
+ Services.prefs.setBoolPref(
+ "mail.server.default.autosync_offline_stores",
+ false
+ );
+ gTestArray.forEach(x => add_task(x));
+});
+
+/*
+ * helper functions
+ */
+
+// given a test file, return the file uri spec
+function specForFileName(aFileName) {
+ let file = do_get_file(gDEPTH + "mailnews/data/" + aFileName);
+ let msgfileuri = Services.io.newFileURI(file).QueryInterface(Ci.nsIFileURL);
+ return msgfileuri.spec;
+}
diff --git a/comm/mailnews/base/test/unit/test_incomingServer.js b/comm/mailnews/base/test/unit/test_incomingServer.js
new file mode 100644
index 0000000000..9093ab4806
--- /dev/null
+++ b/comm/mailnews/base/test/unit/test_incomingServer.js
@@ -0,0 +1,99 @@
+/* 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/. */
+
+function subtestUID(type) {
+ const UUID_REGEXP =
+ /^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/;
+
+ // Create a server and check it the UID is set when accessed.
+
+ let serverA = MailServices.accounts.createIncomingServer(
+ "userA",
+ "hostA",
+ type
+ );
+ Assert.stringMatches(
+ serverA.UID,
+ UUID_REGEXP,
+ "server A's UID should exist and be a UUID"
+ );
+ Assert.equal(
+ Services.prefs.getStringPref(`mail.server.${serverA.key}.uid`),
+ serverA.UID,
+ "server A's UID should be saved to the preferences"
+ );
+ Assert.throws(
+ () => (serverA.UID = "00001111-2222-3333-4444-555566667777"),
+ /NS_ERROR_ABORT/,
+ "server A's UID should be unchangeable after it is set"
+ );
+
+ // Create a second server and check the two UIDs don't match.
+
+ let serverB = MailServices.accounts.createIncomingServer(
+ "userB",
+ "hostB",
+ type
+ );
+ Assert.stringMatches(
+ serverB.UID,
+ UUID_REGEXP,
+ "server B's UID should exist and be a UUID"
+ );
+ Assert.equal(
+ Services.prefs.getStringPref(`mail.server.${serverB.key}.uid`),
+ serverB.UID,
+ "server B's UID should be saved to the preferences"
+ );
+ Assert.notEqual(
+ serverB.UID,
+ serverA.UID,
+ "server B's UID should not be the same as server A's"
+ );
+
+ // Create a third server and set the UID before it is accessed.
+
+ let serverC = MailServices.accounts.createIncomingServer(
+ "userC",
+ "hostC",
+ type
+ );
+ serverC.UID = "11112222-3333-4444-5555-666677778888";
+ Assert.equal(
+ serverC.UID,
+ "11112222-3333-4444-5555-666677778888",
+ "server C's UID set correctly"
+ );
+ Assert.equal(
+ Services.prefs.getStringPref(`mail.server.${serverC.key}.uid`),
+ "11112222-3333-4444-5555-666677778888",
+ "server C's UID should be saved to the preferences"
+ );
+ Assert.throws(
+ () => (serverC.UID = "22223333-4444-5555-6666-777788889999"),
+ /NS_ERROR_ABORT/,
+ "server C's UID should be unchangeable after it is set"
+ );
+}
+
+/**
+ * Tests the UID attribute of IMAP servers.
+ */
+add_task(function testUID_IMAP() {
+ subtestUID("imap");
+});
+
+/**
+ * Tests the UID attribute of NNTP servers.
+ */
+add_task(function testUID_NNTP() {
+ subtestUID("nntp");
+});
+
+/**
+ * Tests the UID attribute of POP3 servers.
+ */
+add_task(function testUID_POP3() {
+ subtestUID("pop3");
+});
diff --git a/comm/mailnews/base/test/unit/test_inheritedFolderProperties.js b/comm/mailnews/base/test/unit/test_inheritedFolderProperties.js
new file mode 100644
index 0000000000..55fb1da6c8
--- /dev/null
+++ b/comm/mailnews/base/test/unit/test_inheritedFolderProperties.js
@@ -0,0 +1,183 @@
+/* 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/. */
+
+/*
+ * Testing of inherited folder properties
+ */
+
+function run_test() {
+ localAccountUtils.loadLocalMailAccount();
+ var rootFolder = localAccountUtils.incomingServer.rootMsgFolder;
+
+ // add subfolders to the inbox
+ const subFolder11 = localAccountUtils.inboxFolder
+ .createLocalSubfolder("subfolder11")
+ .QueryInterface(Ci.nsIMsgLocalMailFolder);
+ const subFolder12 = localAccountUtils.inboxFolder
+ .createLocalSubfolder("subfolder12")
+ .QueryInterface(Ci.nsIMsgLocalMailFolder);
+ const subFolder21 = subFolder11.createLocalSubfolder("subfolder21");
+ const subFolder22 = subFolder12.createLocalSubfolder("subfolder22");
+
+ // add a global preference
+ const propertyName = "iexist";
+ const invalidName = "idontexist";
+ const globalPref = "mail.server.default." + propertyName;
+ const globalValue = "iAmGlobal";
+ const folderValue = "iAmFolder";
+ const folderValue2 = "iAmFolder2";
+ const rootValue = "iAmRoot";
+ Services.prefs.setCharPref(globalPref, globalValue);
+
+ // test that the global preference is honored
+ Assert.equal(
+ rootFolder.getInheritedStringProperty(propertyName),
+ globalValue
+ );
+ Assert.equal(
+ subFolder11.getInheritedStringProperty(propertyName),
+ globalValue
+ );
+ Assert.equal(
+ subFolder22.getInheritedStringProperty(propertyName),
+ globalValue
+ );
+ Assert.equal(rootFolder.getInheritedStringProperty(invalidName), null);
+ Assert.equal(subFolder11.getInheritedStringProperty(invalidName), null);
+ Assert.equal(subFolder22.getInheritedStringProperty(invalidName), null);
+
+ // set a value on a subfolder and check
+ subFolder11.setStringProperty(propertyName, folderValue);
+ Assert.equal(
+ rootFolder.getInheritedStringProperty(propertyName),
+ globalValue
+ );
+ Assert.equal(
+ subFolder11.getInheritedStringProperty(propertyName),
+ folderValue
+ );
+ Assert.equal(
+ subFolder12.getInheritedStringProperty(propertyName),
+ globalValue
+ );
+ Assert.equal(
+ subFolder21.getInheritedStringProperty(propertyName),
+ folderValue
+ );
+ Assert.equal(
+ subFolder22.getInheritedStringProperty(propertyName),
+ globalValue
+ );
+
+ // set a root folder value and check
+ localAccountUtils.incomingServer.setCharValue(propertyName, rootValue);
+ Assert.equal(rootFolder.getInheritedStringProperty(propertyName), rootValue);
+ Assert.equal(
+ subFolder11.getInheritedStringProperty(propertyName),
+ folderValue
+ );
+ Assert.equal(subFolder12.getInheritedStringProperty(propertyName), rootValue);
+ Assert.equal(
+ subFolder21.getInheritedStringProperty(propertyName),
+ folderValue
+ );
+ Assert.equal(subFolder22.getInheritedStringProperty(propertyName), rootValue);
+
+ // force an empty string
+ subFolder12.setForcePropertyEmpty(propertyName, true);
+ Assert.equal(rootFolder.getInheritedStringProperty(propertyName), rootValue);
+ Assert.equal(
+ subFolder11.getInheritedStringProperty(propertyName),
+ folderValue
+ );
+ Assert.equal(subFolder12.getInheritedStringProperty(propertyName), "");
+ Assert.equal(
+ subFolder21.getInheritedStringProperty(propertyName),
+ folderValue
+ );
+ Assert.equal(subFolder22.getInheritedStringProperty(propertyName), "");
+
+ // reset a folder to allow inheritance
+ subFolder12.setForcePropertyEmpty(propertyName, false);
+ subFolder12.setStringProperty(propertyName, "");
+ Assert.equal(rootFolder.getInheritedStringProperty(propertyName), rootValue);
+ Assert.equal(
+ subFolder11.getInheritedStringProperty(propertyName),
+ folderValue
+ );
+ Assert.equal(subFolder12.getInheritedStringProperty(propertyName), rootValue);
+ Assert.equal(
+ subFolder21.getInheritedStringProperty(propertyName),
+ folderValue
+ );
+ Assert.equal(subFolder22.getInheritedStringProperty(propertyName), rootValue);
+
+ // force an empty string on the server
+ localAccountUtils.incomingServer.setForcePropertyEmpty(propertyName, true);
+ Assert.equal(rootFolder.getInheritedStringProperty(propertyName), "");
+ Assert.equal(
+ subFolder11.getInheritedStringProperty(propertyName),
+ folderValue
+ );
+ Assert.equal(subFolder12.getInheritedStringProperty(propertyName), "");
+ Assert.equal(
+ subFolder21.getInheritedStringProperty(propertyName),
+ folderValue
+ );
+ Assert.equal(subFolder22.getInheritedStringProperty(propertyName), "");
+
+ // reset a server to allow inheritance from the global
+ localAccountUtils.incomingServer.setCharValue(propertyName, "");
+ localAccountUtils.incomingServer.setForcePropertyEmpty(propertyName, false);
+ Assert.equal(
+ rootFolder.getInheritedStringProperty(propertyName),
+ globalValue
+ );
+ Assert.equal(
+ subFolder11.getInheritedStringProperty(propertyName),
+ folderValue
+ );
+ Assert.equal(
+ subFolder12.getInheritedStringProperty(propertyName),
+ globalValue
+ );
+ Assert.equal(
+ subFolder21.getInheritedStringProperty(propertyName),
+ folderValue
+ );
+ Assert.equal(
+ subFolder22.getInheritedStringProperty(propertyName),
+ globalValue
+ );
+
+ // check with all levels populated
+ subFolder21.setStringProperty(propertyName, folderValue2);
+ localAccountUtils.incomingServer.setCharValue(propertyName, rootValue);
+ Assert.equal(rootFolder.getInheritedStringProperty(propertyName), rootValue);
+ Assert.equal(
+ subFolder11.getInheritedStringProperty(propertyName),
+ folderValue
+ );
+ Assert.equal(subFolder12.getInheritedStringProperty(propertyName), rootValue);
+ Assert.equal(
+ subFolder21.getInheritedStringProperty(propertyName),
+ folderValue2
+ );
+ Assert.equal(subFolder22.getInheritedStringProperty(propertyName), rootValue);
+
+ // clear the global value and the root value
+ Services.prefs.clearUserPref(globalPref);
+ localAccountUtils.incomingServer.setCharValue(propertyName, "");
+ Assert.equal(rootFolder.getInheritedStringProperty(propertyName), null);
+ Assert.equal(
+ subFolder11.getInheritedStringProperty(propertyName),
+ folderValue
+ );
+ Assert.equal(subFolder12.getInheritedStringProperty(propertyName), null);
+ Assert.equal(
+ subFolder21.getInheritedStringProperty(propertyName),
+ folderValue2
+ );
+ Assert.equal(subFolder22.getInheritedStringProperty(propertyName), null);
+}
diff --git a/comm/mailnews/base/test/unit/test_junkingWhenDisabled.js b/comm/mailnews/base/test/unit/test_junkingWhenDisabled.js
new file mode 100644
index 0000000000..b6a103b069
--- /dev/null
+++ b/comm/mailnews/base/test/unit/test_junkingWhenDisabled.js
@@ -0,0 +1,176 @@
+/* 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/. */
+
+/*
+ * Test that junk actions work even when the bayes filtering of incoming
+ * messages is disabled, as fixed in bug 487610. Test developed by Kent
+ * James using test_nsMsgDBView.js as a base.
+ */
+
+const { TreeSelection } = ChromeUtils.importESModule(
+ "chrome://messenger/content/tree-selection.mjs"
+);
+var { MailServices } = ChromeUtils.import(
+ "resource:///modules/MailServices.jsm"
+);
+var { MessageGenerator, SyntheticMessageSet } = ChromeUtils.import(
+ "resource://testing-common/mailnews/MessageGenerator.jsm"
+);
+var { MessageInjection } = ChromeUtils.import(
+ "resource://testing-common/mailnews/MessageInjection.jsm"
+);
+var { PromiseUtils } = ChromeUtils.importESModule(
+ "resource://gre/modules/PromiseUtils.sys.mjs"
+);
+
+var nsIMFNService = Ci.nsIMsgFolderNotificationService;
+
+// fake objects needed to get nsMsgDBView to operate on selected messages.
+// Warning: these are partial implementations. If someone adds additional
+// calls to these objects in nsMsgDBView and friends, it will also
+// be necessary to add fake versions of those calls here.
+
+var gFakeSelection = new TreeSelection(null);
+
+// Items used to add messages to the folder
+
+var gMessageGenerator = new MessageGenerator();
+
+var messageInjection = new MessageInjection(
+ { mode: "local" },
+ gMessageGenerator
+);
+
+var gLocalInboxFolder = messageInjection.getInboxFolder();
+var gListener;
+var gCommandUpdater;
+
+var gDBView;
+var gTreeView;
+
+var CommandUpdaterWithPromise = function () {
+ this.deferred = PromiseUtils.defer();
+};
+CommandUpdaterWithPromise.prototype = {
+ async promiseSelectionSummarized() {
+ await this.deferred.promise;
+ this.deferred = PromiseUtils.defer();
+ return this.deferred.promise;
+ },
+
+ updateCommandStatus() {
+ // the back end is smart and is only telling us to update command status
+ // when the # of items in the selection has actually changed.
+ },
+
+ displayMessageChanged(aFolder, aSubject, aKeywords) {},
+
+ updateNextMessageAfterDelete() {},
+ summarizeSelection() {
+ this.deferred.resolve();
+ },
+};
+
+// Our listener, which captures events and does the real tests.
+function gMFListener() {
+ this._promiseMsgsMoveCopyCompleted = new Promise(resolve => {
+ this._resolveMsgsMoveCopyCompleted = resolve;
+ });
+ this._promiseFolderAdded = new Promise(resolve => {
+ this._resolveFolderAdded = resolve;
+ });
+}
+gMFListener.prototype = {
+ msgsMoveCopyCompleted(aMove, aSrcMsgs, aDestFolder, aDestMsgs) {
+ Assert.ok(aDestFolder.getFlag(Ci.nsMsgFolderFlags.Junk));
+ // I tried to test this by counting messages in the folder, didn't work.
+ // Maybe all updates are not completed yet. Anyway I do it by just
+ // making sure there is something in the destination array.
+ Assert.ok(aDestMsgs.length > 0);
+ this._resolveMsgsMoveCopyCompleted();
+ },
+
+ folderAdded(aFolder) {
+ // this should be a junk folder
+ Assert.ok(aFolder.getFlag(Ci.nsMsgFolderFlags.Junk));
+ this._resolveFolderAdded();
+ },
+ get promiseMsgsMoveCopyCompleted() {
+ return this._promiseMsgsMoveCopyCompleted;
+ },
+ get promiseFolderAdded() {
+ return this._promiseFolderAdded;
+ },
+};
+
+add_setup(async function () {
+ // Set option so that when messages are marked as junk, they move to the junk folder
+ Services.prefs.setBoolPref("mail.spam.manualMark", true);
+
+ // 0 == "move to junk folder", 1 == "delete"
+ Services.prefs.setIntPref("mail.spam.manualMarkMode", 0);
+
+ // Disable bayes filtering on the local account. That's the whole point of this test,
+ // to make sure that the junk move happens anyway.
+ gLocalInboxFolder.server.spamSettings.level = 0;
+
+ // Add folder listeners that will capture async events.
+ let flags = nsIMFNService.msgsMoveCopyCompleted | nsIMFNService.folderAdded;
+ gListener = new gMFListener();
+ MailServices.mfn.addListener(gListener, flags);
+
+ // Build up a message.
+ await messageInjection.makeNewSetsInFolders([gLocalInboxFolder], [{}]);
+ let view_type = "threaded";
+ let view_flag = Ci.nsMsgViewFlagsType.kThreadedDisplay;
+ let dbviewContractId = "@mozilla.org/messenger/msgdbview;1?type=" + view_type;
+
+ // Always start out fully expanded.
+ view_flag |= Ci.nsMsgViewFlagsType.kExpandAll;
+
+ gCommandUpdater = new CommandUpdaterWithPromise();
+
+ gDBView = Cc[dbviewContractId].createInstance(Ci.nsIMsgDBView);
+ gDBView.init(null, null, null);
+ var outCount = {};
+ gDBView.open(
+ gLocalInboxFolder,
+ Ci.nsMsgViewSortType.byDate,
+ Ci.nsMsgViewSortOrder.ascending,
+ view_flag,
+ outCount
+ );
+
+ gTreeView = gDBView.QueryInterface(Ci.nsITreeView);
+ gTreeView.selection = gFakeSelection;
+ gFakeSelection.view = gTreeView;
+});
+
+add_task(async function test_first_junking_create_folder() {
+ // In the proposed fix for bug 487610, the first call to junk messages
+ // only creates the junk folder, it does not actually successfully move
+ // messages. So we junk messages twice so we can really see a move. But
+ // if that gets fixed and the messages actually move on the first call,
+ // I want this test to succeed as well. So I don't actually count how
+ // many messages get moved, just that some do on the second move.
+
+ // Select and junk all messages.
+ gDBView.doCommand(Ci.nsMsgViewCommandType.selectAll);
+ gDBView.doCommand(Ci.nsMsgViewCommandType.junk);
+ await gCommandUpdater.promiseSelectionSummarized;
+ await gListener.promiseFolderAdded;
+});
+
+add_task(async function test_add_further_message() {
+ // Add another message in case the first one moved.
+ await messageInjection.makeNewSetsInFolders([gLocalInboxFolder], [{}]);
+});
+
+add_task(async function test_second_junking_move_msgs() {
+ // Select and junk all messages.
+ gDBView.doCommand(Ci.nsMsgViewCommandType.selectAll);
+ gDBView.doCommand(Ci.nsMsgViewCommandType.junk);
+ await gCommandUpdater.promiseSelectionSummarized;
+ await gListener.promiseMsgsMoveCopyCompleted;
+});
diff --git a/comm/mailnews/base/test/unit/test_loadVirtualFolders.js b/comm/mailnews/base/test/unit/test_loadVirtualFolders.js
new file mode 100644
index 0000000000..13afd3f03b
--- /dev/null
+++ b/comm/mailnews/base/test/unit/test_loadVirtualFolders.js
@@ -0,0 +1,64 @@
+/* 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/. */
+
+// Test loading of virtualFolders.dat, including verification of the search
+// scopes, i.e., folder uri's.
+
+var { MailServices } = ChromeUtils.import(
+ "resource:///modules/MailServices.jsm"
+);
+
+// As currently written, this test will only work with Berkeley store.
+Services.prefs.setCharPref(
+ "mail.serverDefaultStoreContractID",
+ "@mozilla.org/msgstore/berkeleystore;1"
+);
+
+// main test
+
+function run_test() {
+ let vfdat = do_get_file("../../../data/test_virtualFolders.dat");
+
+ vfdat.copyTo(do_get_profile(), "virtualFolders.dat");
+ localAccountUtils.loadLocalMailAccount();
+ let localMailDir = do_get_profile().clone();
+ localMailDir.append("Mail");
+ localMailDir.append("Local Folders");
+ localMailDir.append("unread-local");
+ localMailDir.create(Ci.nsIFile.NORMAL_FILE_TYPE, 0o644);
+ localMailDir.leafName = "invalidserver-local";
+ localMailDir.create(Ci.nsIFile.NORMAL_FILE_TYPE, 0o644);
+ localMailDir.leafName = "$label1";
+ localMailDir.create(Ci.nsIFile.NORMAL_FILE_TYPE, 0o644);
+
+ MailServices.accounts.loadVirtualFolders();
+ let unreadLocal =
+ localAccountUtils.incomingServer.rootMsgFolder.getChildNamed(
+ "unread-local"
+ );
+ let searchScope =
+ unreadLocal.msgDatabase.dBFolderInfo.getCharProperty("searchFolderUri");
+ Assert.equal(
+ searchScope,
+ "mailbox://nobody@Local%20Folders/Inbox|mailbox://nobody@Local%20Folders/Trash"
+ );
+ let invalidServer =
+ localAccountUtils.incomingServer.rootMsgFolder.getChildNamed(
+ "invalidserver-local"
+ );
+ searchScope =
+ invalidServer.msgDatabase.dBFolderInfo.getCharProperty("searchFolderUri");
+ Assert.equal(searchScope, "mailbox://nobody@Local%20Folders/Inbox");
+
+ let tagsFolder =
+ localAccountUtils.incomingServer.rootMsgFolder.getChildNamed("$label1");
+ Assert.equal(
+ tagsFolder.msgDatabase.dBFolderInfo.getCharProperty("searchFolderUri"),
+ "*"
+ );
+ Assert.equal(
+ tagsFolder.msgDatabase.dBFolderInfo.getCharProperty("searchStr"),
+ "AND (tag,contains,$label1)"
+ );
+}
diff --git a/comm/mailnews/base/test/unit/test_mailServices.js b/comm/mailnews/base/test/unit/test_mailServices.js
new file mode 100644
index 0000000000..1c8299b4f5
--- /dev/null
+++ b/comm/mailnews/base/test/unit/test_mailServices.js
@@ -0,0 +1,67 @@
+/* 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/. */
+
+/*
+ * Tests for the MailServices module.
+ */
+
+var { MailServices } = ChromeUtils.import(
+ "resource:///modules/MailServices.jsm"
+);
+
+add_task(function test_services() {
+ function check_service(service, serviceInterface) {
+ Assert.ok(
+ service in MailServices,
+ `${service} should be a member of MailServices`
+ );
+ Assert.ok(
+ MailServices[service] instanceof serviceInterface,
+ `MailServices.${service} should implement Ci.${serviceInterface.name}`
+ );
+ }
+
+ check_service("mailSession", Ci.nsIMsgMailSession);
+ check_service("accounts", Ci.nsIMsgAccountManager);
+ check_service("pop3", Ci.nsIPop3Service);
+ check_service("imap", Ci.nsIImapService);
+ check_service("nntp", Ci.nsINntpService);
+ check_service("smtp", Ci.nsISmtpService);
+ check_service("compose", Ci.nsIMsgComposeService);
+ check_service("ab", Ci.nsIAbManager);
+ check_service("copy", Ci.nsIMsgCopyService);
+ check_service("mfn", Ci.nsIMsgFolderNotificationService);
+ check_service("headerParser", Ci.nsIMsgHeaderParser);
+ check_service("mimeConverter", Ci.nsIMimeConverter);
+ check_service("tags", Ci.nsIMsgTagService);
+ check_service("filters", Ci.nsIMsgFilterService);
+ check_service("junk", Ci.nsIJunkMailPlugin);
+});
+
+add_task(function test_message_services() {
+ function check_message_service(uri) {
+ let service = MailServices.messageServiceFromURI(uri);
+ Assert.ok(
+ service instanceof Ci.nsIMsgMessageService,
+ `message service for ${uri.substring(
+ 0,
+ uri.indexOf(":")
+ )} URIs should exist`
+ );
+ }
+
+ check_message_service("file://it.does.not.matter/");
+ check_message_service("imap://it.does.not.matter/");
+ check_message_service("imap-message://it.does.not.matter/");
+ check_message_service("mailbox://it.does.not.matter/");
+ check_message_service("mailbox-message://it.does.not.matter/");
+ check_message_service("news://it.does.not.matter/");
+ check_message_service("news-message://it.does.not.matter/");
+
+ Assert.throws(
+ () => MailServices.messageServiceFromURI("fake://not.going.to.work/"),
+ () => true, // Accept any exception.
+ "message service for other URIs should not exist"
+ );
+});
diff --git a/comm/mailnews/base/test/unit/test_mailstoreConverter.js b/comm/mailnews/base/test/unit/test_mailstoreConverter.js
new file mode 100644
index 0000000000..f440b9c8cd
--- /dev/null
+++ b/comm/mailnews/base/test/unit/test_mailstoreConverter.js
@@ -0,0 +1,376 @@
+/* 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/. */
+
+var { convertMailStoreTo } = ChromeUtils.import(
+ "resource:///modules/mailstoreConverter.jsm"
+);
+
+Services.prefs.setCharPref(
+ "mail.serverDefaultStoreContractID",
+ "@mozilla.org/msgstore/berkeleystore;1"
+);
+
+// Test data for round-trip test.
+let testEmails = [
+ // Base64 encoded bodies.
+ "../../../data/01-plaintext.eml",
+ "../../../data/02-plaintext+attachment.eml",
+ "../../../data/03-HTML.eml",
+ "../../../data/04-HTML+attachment.eml",
+ "../../../data/05-HTML+embedded-image.eml",
+ "../../../data/06-plaintext+HMTL.eml",
+ "../../../data/07-plaintext+(HTML+embedded-image).eml",
+ "../../../data/08-plaintext+HTML+attachment.eml",
+ "../../../data/09-(HTML+embedded-image)+attachment.eml",
+ "../../../data/10-plaintext+(HTML+embedded-image)+attachment.eml",
+
+ // Bodies with non-ASCII characters in UTF-8 and other charsets.
+ "../../../data/11-plaintext.eml",
+ "../../../data/12-plaintext+attachment.eml", // using ISO-8859-7 (Greek)
+ "../../../data/13-HTML.eml",
+ "../../../data/14-HTML+attachment.eml",
+ "../../../data/15-HTML+embedded-image.eml",
+ "../../../data/16-plaintext+HMTL.eml", // text part is base64 encoded
+ "../../../data/17-plaintext+(HTML+embedded-image).eml", // HTML part is base64 encoded
+ "../../../data/18-plaintext+HTML+attachment.eml",
+ "../../../data/19-(HTML+embedded-image)+attachment.eml",
+ "../../../data/20-plaintext+(HTML+embedded-image)+attachment.eml", // using windows-1252
+
+ // Bodies with non-ASCII characters in UTF-8 and other charsets, all encoded with quoted printable.
+ "../../../data/21-plaintext.eml",
+ "../../../data/22-plaintext+attachment.eml", // using ISO-8859-7 (Greek)
+ "../../../data/23-HTML.eml",
+ "../../../data/24-HTML+attachment.eml",
+ "../../../data/25-HTML+embedded-image.eml",
+ "../../../data/26-plaintext+HMTL.eml", // text part is base64 encoded
+ "../../../data/27-plaintext+(HTML+embedded-image).eml", // HTML part is base64 encoded
+ "../../../data/28-plaintext+HTML+attachment.eml",
+ "../../../data/29-(HTML+embedded-image)+attachment.eml",
+ "../../../data/30-plaintext+(HTML+embedded-image)+attachment.eml", // using windows-1252
+];
+
+function run_test() {
+ localAccountUtils.loadLocalMailAccount();
+
+ add_task(async function () {
+ await doMboxTest("test1", "../../../data/mbox_modern", 2);
+ await doMboxTest("test2", "../../../data/mbox_mboxrd", 2);
+ await doMboxTest("test3", "../../../data/mbox_unquoted", 2);
+ await roundTripTest();
+ // Ideas for more tests:
+ // - check a really big mbox
+ // - check with really huge message (larger than one chunk)
+ // - check mbox with "From " line on chunk boundary
+ // - add tests for maildir->mbox conversion
+ // - check that conversions preserve message body (ie that the
+ // "From " line escaping scheme is reversible)
+ });
+
+ run_next_test();
+}
+
+/**
+ * Helper to create a server, account and inbox, and install an
+ * mbox file.
+ *
+ * @param {string} srvName - A unique server name to use for the test.
+ * @param {string} mboxFilename - mbox file to install and convert.
+ * @returns {nsIMsgIncomingServer} a server.
+ */
+function setupServer(srvName, mboxFilename) {
+ // {nsIMsgIncomingServer} pop server for the test.
+ let server = MailServices.accounts.createIncomingServer(
+ srvName,
+ "localhost",
+ "pop3"
+ );
+ let account = MailServices.accounts.createAccount();
+ account.incomingServer = server;
+ server.QueryInterface(Ci.nsIPop3IncomingServer);
+ server.valid = true;
+
+ let inbox = account.incomingServer.rootFolder.getFolderWithFlags(
+ Ci.nsMsgFolderFlags.Inbox
+ );
+
+ // install the mbox file
+ let mboxFile = do_get_file(mboxFilename);
+ mboxFile.copyTo(inbox.filePath.parent, inbox.filePath.leafName);
+
+ // TODO: is there some way to make folder rescan the mbox?
+ // We don't need it for this, but would be nice to do things properly.
+ return server;
+}
+
+/**
+ * Perform an mbox->maildir conversion test.
+ *
+ * @param {string} srvName - A unique server name to use for the test.
+ * @param {string} mboxFilename - mbox file to install and convert.
+ * @param {number} expectCnt - Number of messages expected.
+ * @returns {nsIMsgIncomingServer} a server.
+ */
+async function doMboxTest(srvName, mboxFilename, expectCnt) {
+ // set up an account+server+inbox and copy in the test mbox file
+ let server = setupServer(srvName, mboxFilename);
+
+ let mailstoreContractId = Services.prefs.getCharPref(
+ "mail.server." + server.key + ".storeContractID"
+ );
+
+ await convertMailStoreTo(mailstoreContractId, server, new EventTarget());
+
+ // Converted. Now find resulting Inbox/cur directory so
+ // we can count the messages there.
+
+ let inbox = server.rootFolder.getFolderWithFlags(Ci.nsMsgFolderFlags.Inbox);
+ // NOTE: the conversion updates the path of the root folder,
+ // but _not_ the path of the inbox...
+ // Ideally, we'd just use inbox.filePath here, but
+ // instead we'll have compose the path manually.
+
+ let curDir = server.rootFolder.filePath;
+ curDir.append(inbox.filePath.leafName);
+ curDir.append("cur");
+
+ // Sanity check.
+ Assert.ok(curDir.isDirectory(), "'cur' directory created");
+
+ // Check number of messages in Inbox/cur is what we expect.
+ let cnt = [...curDir.directoryEntries].length;
+
+ Assert.equal(
+ cnt,
+ expectCnt,
+ "expected number of messages (" + mboxFilename + ")"
+ );
+}
+
+/**
+ * Create a temporary directory. The caller is responsible for deleting it.
+ *
+ * @param {string} prefix - Generated dir name will be of the form:
+ * "<prefix><random_sequence>".
+ * @returns {string} full path of new directory.
+ */
+async function tempDir(prefix) {
+ if (!prefix) {
+ prefix = "";
+ }
+ let tmpDir = Services.dirsvc.get("TmpD", Ci.nsIFile).path;
+ while (true) {
+ let name = prefix + Math.floor(Math.random() * 0xffffffff).toString(16);
+ let fullPath = PathUtils.join(tmpDir, name);
+ try {
+ await IOUtils.makeDirectory(fullPath, { ignoreExisting: false });
+ return fullPath;
+ } catch (e) {
+ // If directory already exists, try another name. Else bail out.
+ if (
+ !(DOMException.isInstance(e) && e.name === "NoModificationAllowedError")
+ ) {
+ throw e;
+ }
+ }
+ }
+}
+
+/**
+ * Test that messages survive unscathed in a roundtrip conversion,
+ * maildir -> mbox -> maildir.
+ * The final mailbox should have an identical set of files to the initial one,
+ * albeit with different filenames.
+ * Purely filesystem based.
+ *
+ * Would be nice to do a mbox->maildir->mbox roundtrip too, but that'd involve
+ * parsing the mbox files to compare them (can't just compare mbox files because
+ * message order and "From " lines can change).
+ */
+async function roundTripTest() {
+ // Set up initial maildir structure
+ let initialRoot = await tempDir("initial");
+
+ let inbox = PathUtils.join(initialRoot, "INBOX");
+ await IOUtils.makeDirectory(inbox);
+ // Create a couple of subdirs under INBOX
+ let subdir = PathUtils.join(initialRoot, "INBOX.sbd");
+ await IOUtils.makeDirectory(subdir);
+ let foodir = PathUtils.join(subdir, "foo");
+ await IOUtils.makeDirectory(foodir);
+ let bardir = PathUtils.join(subdir, "bar");
+ await IOUtils.makeDirectory(bardir);
+
+ // Populate all the folders with some test emails.
+ const absolutePaths = testEmails.map(path => do_get_file(path).path);
+ await populateMaildir(inbox, absolutePaths);
+ await populateMaildir(foodir, absolutePaths);
+ await populateMaildir(bardir, absolutePaths);
+
+ // Add a pick of "special" files, which should survive the trip verbatim.
+ for (let special of ["filterlog.html", "feeds.json", "rules.dat"]) {
+ let f = PathUtils.join(initialRoot, special);
+ await IOUtils.writeUTF8(f, f); // Use the filename for content.
+ }
+
+ // Create root dirs for intermediate and final result.
+ let mboxRoot = await tempDir("mbox");
+ let finalRoot = await tempDir("final");
+
+ // Convert: maildir -> mbox -> maildir
+ await doConvert("maildir", initialRoot, "mbox", mboxRoot);
+ await doConvert("mbox", mboxRoot, "maildir", finalRoot);
+
+ // compare results - use checksums, because filenames will differ.
+ await recursiveMaildirCompare(initialRoot, finalRoot);
+}
+
+/**
+ * Helper to adapt the callbacks from converterWorker into a promise.
+ *
+ * @param {string} srcType - type of source ("maildir", "mbox")
+ * @param {string} srcRoot - root directory containing the src folders.
+ * @param {string} destType - type of destination ("maildir", "mbox")
+ * @param {string} destRoot - root directory to place converted store.
+ * @returns {Promise} resolved when when conversion is complete.
+ */
+function doConvert(srcType, srcRoot, destType, destRoot) {
+ return new Promise(function (resolve, reject) {
+ let worker = new ChromeWorker("resource:///modules/converterWorker.js");
+ worker.addEventListener("message", function (ev) {
+ if (ev.data.msg == "success") {
+ resolve();
+ }
+ });
+ worker.addEventListener("error", function (ev) {
+ reject(ev.message);
+ });
+ // Go.
+ worker.postMessage({
+ srcType,
+ destType,
+ srcRoot,
+ destRoot,
+ });
+ });
+}
+
+/**
+ * Copy a list of email files (.eml) files into a maildir, creating "cur"
+ * and "tmp" subdirs if required.
+ *
+ * @param {string} maildir - Path to the maildir directory.
+ * @param {Array<string>} emailFiles - paths of source .eml files to copy.
+ */
+async function populateMaildir(maildir, emailFiles) {
+ let cur = PathUtils.join(maildir, "cur");
+ await IOUtils.makeDirectory(cur);
+ await IOUtils.makeDirectory(PathUtils.join(maildir, "tmp"));
+
+ // Normally maildir files would have a name derived from their msg-id field,
+ // but here we'll just use a timestamp-based one to save parsing them.
+ let ident = Date.now();
+ for (let src of emailFiles) {
+ let dest = PathUtils.join(cur, ident.toString() + ".eml");
+ ident += 1;
+ await IOUtils.copy(src, dest);
+ }
+}
+
+/*
+ * List files in a directory (excludes subdirectories).
+ *
+ * @param {String} dirPath - Full path of directory.
+ * @returns {Array<String} full paths of the files.
+ */
+async function listFiles(dirPath) {
+ let files = [];
+ // Note: IOUtils has no dir iterator at time of writing.
+ for (const path of await IOUtils.getChildren(dirPath)) {
+ let fileInfo = await IOUtils.stat(path);
+ if (fileInfo.type !== "directory") {
+ files.push(path);
+ }
+ }
+ return files;
+}
+
+/*
+ * Calculate md5 checksum for a file.
+ *
+ * @param {String} fileName - Full path to file.
+ * @returns {String} checksum of the file contents.
+ */
+async function md5Sum(fileName) {
+ let md5 = Cc["@mozilla.org/security/hash;1"].createInstance(Ci.nsICryptoHash);
+ md5.init(Ci.nsICryptoHash.MD5);
+ let raw = await IOUtils.read(fileName);
+ md5.update(raw, raw.byteLength);
+ return md5.finish(true);
+}
+
+/**
+ * Compare all maildir directories in two directory trees.
+ * The comparison is per-maildir, by looking at the checksums of their emails.
+ * Asserts a test fail if any differences are found.
+ *
+ * @param {string} rootA - path to root of maildir store A.
+ * @param {string} rootB - path to root of maildir store B.
+ */
+async function recursiveMaildirCompare(rootA, rootB) {
+ let subdirs = [];
+ let maildirs = [];
+ let otherFiles = [];
+ for (let path of await IOUtils.getChildren(rootA)) {
+ let stat = await IOUtils.stat(path);
+ let name = PathUtils.filename(path);
+ if (stat.type === "directory") {
+ if (name.endsWith(".sbd")) {
+ subdirs.push(name);
+ } else {
+ // Assume all other dirs are maildirs.
+ maildirs.push(name);
+ }
+ } else {
+ otherFiles.push(name);
+ }
+ }
+
+ // Compare the maildirs we found here.
+ let md5DirContents = async function (dirPath) {
+ let checksums = [];
+ for (let f of await listFiles(dirPath)) {
+ checksums.push(await md5Sum(f));
+ }
+ return checksums;
+ };
+
+ for (let name of maildirs) {
+ let checksumsA = await md5DirContents(PathUtils.join(rootA, name, "cur"));
+ let checksumsB = await md5DirContents(PathUtils.join(rootB, name, "cur"));
+
+ checksumsA.sort();
+ checksumsB.sort();
+ let match = checksumsA.length == checksumsB.length;
+ for (let i = 0; match && i < checksumsA.length; i++) {
+ match = checksumsA[i] == checksumsB[i];
+ }
+ Assert.ok(match, "roundtrip preserves messages in maildir " + name);
+ }
+
+ // Make sure any "special" files survived the trip intact.
+ for (let name of otherFiles) {
+ let checksumA = await md5Sum(PathUtils.join(rootA, name));
+ let pathB = PathUtils.join(rootB, name);
+ let checksumB = (await IOUtils.exists(pathB)) ? await md5Sum(pathB) : null;
+ Assert.equal(checksumA, checksumB, "roundtrip preserves " + name);
+ }
+
+ // Recurse down into .sbd dirs.
+ for (let name of subdirs) {
+ await recursiveMaildirCompare(
+ PathUtils.join(rootA, name),
+ PathUtils.join(rootB, name)
+ );
+ }
+}
diff --git a/comm/mailnews/base/test/unit/test_mimemaltdetach.js b/comm/mailnews/base/test/unit/test_mimemaltdetach.js
new file mode 100644
index 0000000000..1f90566726
--- /dev/null
+++ b/comm/mailnews/base/test/unit/test_mimemaltdetach.js
@@ -0,0 +1,160 @@
+/* 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/. */
+
+/*
+ * Tests nsIMessenger's detachAttachmentsWOPrompts of Mime multi-part
+ * alternative messages.
+ */
+
+var { MsgHdrToMimeMessage } = ChromeUtils.import(
+ "resource:///modules/gloda/MimeMessage.jsm"
+);
+var { MailServices } = ChromeUtils.import(
+ "resource:///modules/MailServices.jsm"
+);
+var { PromiseTestUtils } = ChromeUtils.import(
+ "resource://testing-common/mailnews/PromiseTestUtils.jsm"
+);
+
+function SaveAttachmentCallback() {
+ this.attachments = null;
+ this._promise = new Promise((resolve, reject) => {
+ this._resolve = resolve;
+ this._reject = reject;
+ });
+}
+
+SaveAttachmentCallback.prototype = {
+ callback: function saveAttachmentCallback_callback(aMsgHdr, aMimeMessage) {
+ this.attachments = aMimeMessage.allAttachments;
+ this._resolve();
+ },
+ get promise() {
+ return this._promise;
+ },
+};
+var gCallbackObject = new SaveAttachmentCallback();
+
+add_setup(async function () {
+ if (!localAccountUtils.inboxFolder) {
+ localAccountUtils.loadLocalMailAccount();
+ }
+});
+
+add_task(async function startCopy() {
+ // Get a message into the local filestore.
+ let mailFile = do_get_file("../../../data/multipartmalt-detach");
+ let listener = new PromiseTestUtils.PromiseCopyListener();
+ MailServices.copy.copyFileMessage(
+ mailFile,
+ localAccountUtils.inboxFolder,
+ null,
+ false,
+ 0,
+ "",
+ listener,
+ null
+ );
+ await listener.promise;
+});
+
+// process the message through mime
+add_task(async function startMime() {
+ let msgHdr = mailTestUtils.firstMsgHdr(localAccountUtils.inboxFolder);
+
+ MsgHdrToMimeMessage(
+ msgHdr,
+ gCallbackObject,
+ gCallbackObject.callback,
+ true // allowDownload
+ );
+
+ await gCallbackObject.promise;
+});
+
+// detach any found attachments
+add_task(async function startDetach() {
+ let msgHdr = mailTestUtils.firstMsgHdr(localAccountUtils.inboxFolder);
+ let msgURI = msgHdr.folder.generateMessageURI(msgHdr.messageKey);
+
+ let messenger = Cc["@mozilla.org/messenger;1"].createInstance(
+ Ci.nsIMessenger
+ );
+ let attachment = gCallbackObject.attachments[0];
+ let listener = new PromiseTestUtils.PromiseUrlListener();
+
+ messenger.detachAttachmentsWOPrompts(
+ do_get_profile(),
+ [attachment.contentType],
+ [attachment.url],
+ [attachment.name],
+ [msgURI],
+ listener
+ );
+ await listener.promise;
+});
+
+// test that the detachment was successful
+add_task(async function testDetach() {
+ // The message contained a file "head_update.txt" which should
+ // now exist in the profile directory.
+ let checkFile = do_get_profile().clone();
+ checkFile.append("head_update.txt");
+ Assert.ok(checkFile.exists());
+ Assert.ok(checkFile.fileSize > 0);
+
+ // The message should now have a detached attachment. Read the message,
+ // and search for "AttachmentDetached" which is added on detachment.
+
+ // Get the message header
+ let msgHdr = mailTestUtils.firstMsgHdr(localAccountUtils.inboxFolder);
+
+ let messageContent = await getContentFromMessage(msgHdr);
+ Assert.ok(messageContent.includes("AttachmentDetached"));
+ // Make sure the body survived the detach.
+ Assert.ok(messageContent.includes("body hello"));
+});
+
+/**
+ * Get the full message content.
+ *
+ * @param {nsIMsgDBHdr} aMsgHdr - Message object whose text body will be read.
+ * @returns {Promise<string>} full message contents.
+ */
+function getContentFromMessage(aMsgHdr) {
+ let msgFolder = aMsgHdr.folder;
+ let msgUri = msgFolder.getUriForMsg(aMsgHdr);
+
+ return new Promise((resolve, reject) => {
+ let streamListener = {
+ QueryInterface: ChromeUtils.generateQI(["nsIStreamListener"]),
+ sis: Cc["@mozilla.org/scriptableinputstream;1"].createInstance(
+ Ci.nsIScriptableInputStream
+ ),
+ content: "",
+ onDataAvailable(request, inputStream, offset, count) {
+ this.sis.init(inputStream);
+ this.content += this.sis.read(count);
+ },
+ onStartRequest(request) {},
+ onStopRequest(request, statusCode) {
+ this.sis.close();
+ if (Components.isSuccessCode(statusCode)) {
+ resolve(this.content);
+ } else {
+ reject(new Error(statusCode));
+ }
+ },
+ };
+ MailServices.messageServiceFromURI(msgUri).streamMessage(
+ msgUri,
+ streamListener,
+ null,
+ null,
+ false,
+ "",
+ false
+ );
+ });
+}
diff --git a/comm/mailnews/base/test/unit/test_newMailNotification.js b/comm/mailnews/base/test/unit/test_newMailNotification.js
new file mode 100644
index 0000000000..d6e111d86a
--- /dev/null
+++ b/comm/mailnews/base/test/unit/test_newMailNotification.js
@@ -0,0 +1,203 @@
+/* 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/. */
+
+/* Tests for platform-independent code to count new and unread messages and pass the
+ * information to platform-specific notification modules */
+
+var { MailServices } = ChromeUtils.import(
+ "resource:///modules/MailServices.jsm"
+);
+
+/**
+ * Register listener for a particular event, make sure it shows up in the right lists
+ * of listeners (and not the wrong ones) and doesn't show up after being removed
+ */
+add_test(function testListeners() {
+ let notif = MailServices.newMailNotification.wrappedJSObject;
+ let listener = { onCountChanged: () => {} };
+
+ notif.addListener(listener, Ci.mozINewMailNotificationService.count);
+ let list = notif.listenersForFlag(Ci.mozINewMailNotificationService.count);
+ Assert.equal(list.length, 1);
+ Assert.equal(list[0], listener);
+
+ let newlist = notif.listenersForFlag(
+ Ci.mozINewMailNotificationService.messages
+ );
+ Assert.equal(newlist.length, 0);
+
+ notif.removeListener(listener);
+ list = notif.listenersForFlag(Ci.mozINewMailNotificationService.count);
+ Assert.equal(list.length, 0);
+
+ run_next_test();
+});
+
+/*
+ * Register a listener for two types and another for one type, make sure they show up,
+ * remove one and make sure the other stays put
+ */
+add_test(function testMultiListeners() {
+ let notif = MailServices.newMailNotification.wrappedJSObject;
+ let l1 = { onCountChanged: () => {} };
+ let l2 = { b: 2 };
+
+ notif.addListener(
+ l1,
+ Ci.mozINewMailNotificationService.count |
+ Ci.mozINewMailNotificationService.messages
+ );
+ // do_check_eq(notif._listeners.length, 1);
+ notif.addListener(l2, Ci.mozINewMailNotificationService.messages);
+ // do_check_eq(notif._listeners.length, 2);
+ let list = notif.listenersForFlag(Ci.mozINewMailNotificationService.count);
+ Assert.equal(list.length, 1);
+ Assert.equal(list[0], l1);
+
+ let newlist = notif.listenersForFlag(
+ Ci.mozINewMailNotificationService.messages
+ );
+ Assert.equal(newlist.length, 2);
+
+ notif.removeListener(l1);
+ list = notif.listenersForFlag(Ci.mozINewMailNotificationService.count);
+ Assert.equal(list.length, 0);
+ newlist = notif.listenersForFlag(Ci.mozINewMailNotificationService.messages);
+ Assert.equal(newlist.length, 1);
+ Assert.equal(newlist[0], l2);
+ notif.removeListener(l2);
+
+ run_next_test();
+});
+
+/* Make sure we get a notification call when the unread count changes on an Inbox */
+add_test(function testNotifyInbox() {
+ let notified = false;
+ let count = 0;
+ let mockListener = {
+ onCountChanged: function TNU_onCountChanged(updatedCount) {
+ notified = true;
+ count = updatedCount;
+ },
+ };
+ let folder = {
+ URI: "Test Inbox",
+ flags: Ci.nsMsgFolderFlags.Mail | Ci.nsMsgFolderFlags.Inbox,
+ };
+
+ const notificationService = MailServices.newMailNotification.wrappedJSObject;
+
+ // Set up the notification service to start with a non-zero unread count to
+ // verify this value is correctly passed to new listeners. Do this before any
+ // listeners are added.
+ const startCount = 3;
+ notificationService.unreadCount = startCount;
+
+ // Add a listener for count updates.
+ notificationService.addListener(
+ mockListener,
+ Ci.mozINewMailNotificationService.count
+ );
+
+ // Verify that a new listener is notified of the current count.
+ Assert.ok(notified, "New listeners should be notified of count when added.");
+ Assert.equal(
+ count,
+ startCount,
+ "New listener notification should contain the current unread count."
+ );
+
+ // Verify that listeners are notified of subsequent changes.
+ notified = false;
+ const updatedInboxCount = 5;
+ notificationService.onFolderIntPropertyChanged(
+ folder,
+ "TotalUnreadMessages",
+ startCount,
+ updatedInboxCount
+ );
+ Assert.ok(
+ notified,
+ "Listeners should be notified of changes in inbox unread count."
+ );
+ Assert.equal(
+ count,
+ updatedInboxCount,
+ "Notification should contain updated inbox unread count."
+ );
+
+ // Sanity check.
+ Assert.ok(
+ Services.prefs.getBoolPref("mail.notification.count.inbox_only", false),
+ "`inbox_only` pref should be true for test."
+ );
+
+ // Verify that listeners are not notified of changes outside of the inbox.
+ let nonInbox = {
+ URI: "Test Non-Inbox",
+ flags: Ci.nsMsgFolderFlags.Mail,
+ };
+ notified = false;
+ notificationService.onFolderIntPropertyChanged(
+ nonInbox,
+ "TotalUnreadMessages",
+ 0,
+ 2
+ );
+ Assert.ok(
+ !notified,
+ "Listeners should not be notified of changes in unread count outside of inbox by default."
+ );
+ Assert.equal(
+ count,
+ updatedInboxCount,
+ "Total unread message count should not have changed."
+ );
+
+ // Verify that, when `inbox_only` is false, unread messages outside of the
+ // inbox are counted.
+ Services.prefs.setBoolPref("mail.notification.count.inbox_only", false);
+ notified = false;
+ const updatedNonInboxCount = 2;
+ const updatedTotalCount = updatedInboxCount + updatedNonInboxCount;
+ notificationService.onFolderIntPropertyChanged(
+ nonInbox,
+ "TotalUnreadMessages",
+ 0,
+ updatedNonInboxCount
+ );
+ Assert.ok(
+ notified,
+ "Listeners should be notified of changes in unread count outside of inbox when pref is set."
+ );
+ Assert.equal(
+ count,
+ updatedTotalCount,
+ "Notification should contain total unread count for all counted folders."
+ );
+
+ // Verify that listeners are never informed of updates in special folders.
+ let special = {
+ URI: "Test Special",
+ flags: Ci.nsMsgFolderFlags.Mail | Ci.nsMsgFolderFlags.Junk,
+ };
+ notified = false;
+ notificationService.onFolderIntPropertyChanged(
+ special,
+ "TotalUnreadMessages",
+ 0,
+ 2
+ );
+ Assert.ok(
+ !notified,
+ "Listeners should not be notified of changes in special folder unread count."
+ );
+ Assert.equal(
+ count,
+ updatedTotalCount,
+ "Total unread message count should not have changed."
+ );
+
+ run_next_test();
+});
diff --git a/comm/mailnews/base/test/unit/test_nsIFolderListener.js b/comm/mailnews/base/test/unit/test_nsIFolderListener.js
new file mode 100644
index 0000000000..aa1b19ecfb
--- /dev/null
+++ b/comm/mailnews/base/test/unit/test_nsIFolderListener.js
@@ -0,0 +1,45 @@
+/* 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/. */
+
+/*
+ * Test that adding nsIFolderListener in js does not cause any crash.
+ */
+
+var { MessageGenerator } = ChromeUtils.import(
+ "resource://testing-common/mailnews/MessageGenerator.jsm"
+);
+var { MessageInjection } = ChromeUtils.import(
+ "resource://testing-common/mailnews/MessageInjection.jsm"
+);
+
+var folderListener = {
+ onFolderAdded() {},
+ onMessageAdded() {},
+ onFolderRemoved() {},
+ onMessageRemoved() {},
+ onFolderPropertyChanged() {},
+ onFolderIntPropertyChanged() {},
+ onFolderBoolPropertyChanged() {},
+ onFolderUnicharPropertyChanged() {},
+ onFolderPropertyFlagChanged() {},
+ onFolderEvent() {},
+};
+
+var targetFolder;
+var messageInjection;
+
+add_setup(async function () {
+ let msgGen = new MessageGenerator();
+ messageInjection = new MessageInjection({ mode: "local" }, msgGen);
+
+ targetFolder = await messageInjection.makeEmptyFolder();
+ targetFolder.AddFolderListener(folderListener);
+ registerCleanupFunction(function () {
+ targetFolder.RemoveFolderListener(folderListener);
+ });
+});
+
+add_task(async function create_new_message() {
+ await messageInjection.makeNewSetsInFolders([targetFolder], [{ count: 1 }]);
+});
diff --git a/comm/mailnews/base/test/unit/test_nsIMsgContentPolicy.js b/comm/mailnews/base/test/unit/test_nsIMsgContentPolicy.js
new file mode 100644
index 0000000000..de49d5b7eb
--- /dev/null
+++ b/comm/mailnews/base/test/unit/test_nsIMsgContentPolicy.js
@@ -0,0 +1,68 @@
+/* -*- mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/*
+ * Test suite for nsIMsgContentPolicy to check we could add/remove customized protocol to
+ * nsMsgContentPolicy.
+ */
+const { NetUtil } = ChromeUtils.import("resource://gre/modules/NetUtil.jsm");
+
+function makeURI(aURL) {
+ return Services.io.newURI(aURL);
+}
+
+function run_test() {
+ var content_policy = Cc["@mozilla.org/messenger/content-policy;1"].getService(
+ Ci.nsIContentPolicy
+ );
+
+ Assert.ok(content_policy);
+
+ var msg_content_policy = content_policy.QueryInterface(
+ Ci.nsIMsgContentPolicy
+ );
+
+ Assert.ok(msg_content_policy);
+
+ var req_uri = makeURI("custom-scheme://custom_url/1.emal");
+ Assert.ok(req_uri);
+
+ var content_uri = makeURI("custom-scheme://custom_content_url/1.jsp");
+ Assert.ok(content_uri);
+
+ let tmpChannel = NetUtil.newChannel({
+ uri: content_uri,
+ // Needs one of 'loadingNode', 'loadingPrincipal' or 'loadUsingSystemPrincipal' which we don't have.
+ // Even with `loadUsingSystemPrincipal: true` this fails with "unknown protocol". See bug 1446587.
+ securityFlags: Ci.nsILoadInfo.SEC_ONLY_FOR_EXPLICIT_CONTENTSEC_CHECK,
+ contentPolicyType: Ci.nsIContentPolicy.TYPE_IMAGE,
+ });
+ let tmpLoadInfo = tmpChannel.loadInfo;
+
+ var decision = content_policy.shouldLoad(
+ content_uri,
+ tmpLoadInfo,
+ "img/jpeg"
+ );
+ Assert.notEqual(
+ decision,
+ Ci.nsIContentPolicy.ACCEPT,
+ "customized protocol should not load"
+ );
+
+ msg_content_policy.addExposedProtocol("custom-scheme");
+
+ decision = content_policy.shouldLoad(content_uri, tmpLoadInfo, "img/jpeg");
+ Assert.equal(
+ decision,
+ Ci.nsIContentPolicy.ACCEPT,
+ "customized protocol should load"
+ );
+
+ msg_content_policy.removeExposedProtocol("custom-scheme");
+
+ decision = content_policy.shouldLoad(content_uri, tmpLoadInfo, "img/jpeg");
+ Assert.notEqual(
+ decision,
+ Ci.nsIContentPolicy.ACCEPT,
+ "customized protocol should not load"
+ );
+}
diff --git a/comm/mailnews/base/test/unit/test_nsIMsgFolder.js b/comm/mailnews/base/test/unit/test_nsIMsgFolder.js
new file mode 100644
index 0000000000..9cfb57a07c
--- /dev/null
+++ b/comm/mailnews/base/test/unit/test_nsIMsgFolder.js
@@ -0,0 +1,102 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/*
+ * Test suite for nsIMsgFolder functions.
+ */
+
+var { MailServices } = ChromeUtils.import(
+ "resource:///modules/MailServices.jsm"
+);
+
+function run_test() {
+ // Create a local mail account (we need this first)
+ MailServices.accounts.createLocalMailAccount();
+
+ // Get the account
+ let account = MailServices.accounts.accounts[0];
+
+ // Get the root folder
+ var root = account.incomingServer.rootFolder;
+
+ // Add a sub folder to ensure that we have some folders created
+ root.createSubfolder("folder1", null);
+
+ // Test - getChildNamed
+
+ var caught = false;
+ try {
+ root.getChildNamed("folder");
+ } catch (e) {
+ caught = true;
+ }
+ Assert.equal(caught, true);
+
+ caught = false;
+ try {
+ root.getChildNamed("Trash1");
+ } catch (e) {
+ caught = true;
+ }
+ Assert.equal(caught, true);
+
+ var folder1 = root.getChildNamed("folder1");
+
+ Assert.notEqual(folder1, folder2);
+ Assert.equal(folder1.prettyName, "folder1");
+
+ var folder2 = root.getChildNamed("FOLDER1");
+
+ Assert.equal(folder1, folder2);
+
+ // Check special folders aren't deletable, and that normal folders are.
+ if (!root.containsChildNamed("Inbox")) {
+ root.createSubfolder("Inbox", null);
+ }
+ var inbox = root.getChildNamed("Inbox");
+ inbox.setFlag(Ci.nsMsgFolderFlags.Inbox);
+ Assert.ok(!inbox.deletable);
+
+ if (!root.containsChildNamed("Drafts")) {
+ root.createSubfolder("Drafts", null);
+ }
+ var drafts = root.getChildNamed("Drafts");
+ drafts.setFlag(Ci.nsMsgFolderFlags.Drafts);
+ Assert.ok(!drafts.deletable);
+
+ if (!root.containsChildNamed("Templates")) {
+ root.createSubfolder("Templates", null);
+ }
+ var templates = root.getChildNamed("Templates");
+ templates.setFlag(Ci.nsMsgFolderFlags.Templates);
+ Assert.ok(!templates.deletable);
+
+ if (!root.containsChildNamed("Sent")) {
+ root.createSubfolder("Sent", null);
+ }
+ var sent = root.getChildNamed("Sent");
+ sent.setFlag(Ci.nsMsgFolderFlags.SentMail);
+ Assert.ok(!sent.deletable);
+
+ if (!root.containsChildNamed("Archives")) {
+ root.createSubfolder("Archives", null);
+ }
+ var archives = root.getChildNamed("Archives");
+ archives.setFlag(Ci.nsMsgFolderFlags.Archive);
+ Assert.ok(!archives.deletable);
+
+ if (!root.containsChildNamed("Trash")) {
+ root.createSubfolder("Trash", null);
+ }
+ var trash = root.getChildNamed("Trash");
+ trash.setFlag(Ci.nsMsgFolderFlags.Trash);
+ Assert.ok(!trash.deletable);
+
+ if (!root.containsChildNamed("Outbox")) {
+ root.createSubfolder("Outbox", null);
+ }
+ var outbox = root.getChildNamed("Outbox");
+ outbox.setFlag(Ci.nsMsgFolderFlags.Queue);
+ Assert.ok(!outbox.deletable);
+
+ // test a normal folder is deletable
+ Assert.ok(folder1.deletable);
+}
diff --git a/comm/mailnews/base/test/unit/test_nsIMsgFolderCache.js b/comm/mailnews/base/test/unit/test_nsIMsgFolderCache.js
new file mode 100644
index 0000000000..9fd9688b53
--- /dev/null
+++ b/comm/mailnews/base/test/unit/test_nsIMsgFolderCache.js
@@ -0,0 +1,228 @@
+/* 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/. */
+
+/**
+ * Sanity checks for nsIMsgFolderCache/nsIMsgFolderCacheElement.
+ */
+add_task(function test_basics() {
+ let profileDir = do_get_profile();
+ let jsonFile = profileDir.clone();
+ jsonFile.append("folderCache.json");
+ let legacyFile = profileDir.clone();
+ legacyFile.append("panacea.dat");
+
+ // Create an empty cache object and start poking it.
+ {
+ let cache = Cc["@mozilla.org/messenger/msgFolderCache;1"].createInstance(
+ Ci.nsIMsgFolderCache
+ );
+ // Neither of these files exist, and that's fine.
+ Assert.ok(!jsonFile.exists());
+ Assert.ok(!legacyFile.exists());
+ cache.init(jsonFile, legacyFile);
+
+ // getCacheElement has to be told to create non-existent keys.
+ Assert.throws(function () {
+ cache.getCacheElement("a/non/existent/key", false);
+ }, /NS_ERROR_NOT_AVAILABLE/);
+ let e1 = cache.getCacheElement("/made/up/path/Inbox", true);
+
+ // Can set, get and modify Int32 values?
+ e1.setCachedInt32("wibble", -1);
+ Assert.equal(e1.getCachedInt32("wibble"), -1);
+ e1.setCachedInt32("wibble", 42);
+ Assert.equal(e1.getCachedInt32("wibble"), 42);
+
+ // Check some allowed conversions from Int32.
+ Assert.equal(e1.getCachedUInt32("wibble"), 42);
+ Assert.equal(e1.getCachedInt64("wibble"), 42);
+ Assert.equal(e1.getCachedString("wibble"), "42");
+
+ // Can set, get and modify UInt32 values?
+ e1.setCachedUInt32("pibble", 0xffffffff);
+ Assert.equal(e1.getCachedUInt32("pibble"), 0xffffffff);
+ e1.setCachedUInt32("pibble", 42);
+ Assert.equal(e1.getCachedUInt32("pibble"), 42);
+
+ // Check some allowed conversions from UInt32.
+ Assert.equal(e1.getCachedInt32("pibble"), 42);
+ Assert.equal(e1.getCachedInt64("pibble"), 42);
+ Assert.equal(e1.getCachedString("pibble"), "42");
+
+ // Can set, get and modify Int64 values?
+ e1.setCachedInt64("foo", 2305843009213694000);
+ Assert.equal(e1.getCachedInt64("foo"), 2305843009213694000);
+ e1.setCachedInt64("foo", -2305843009213694000);
+ Assert.equal(e1.getCachedInt64("foo"), -2305843009213694000);
+ e1.setCachedInt64("foo", 42);
+ Assert.equal(e1.getCachedInt64("foo"), 42);
+
+ // Check some allowed conversions from Int64.
+ Assert.equal(e1.getCachedInt32("foo"), 42);
+ Assert.equal(e1.getCachedUInt32("foo"), 42);
+ Assert.equal(e1.getCachedString("foo"), "42");
+
+ // Can set, get and modify String values?
+ e1.setCachedString("bar", "Before");
+ Assert.equal(e1.getCachedString("bar"), "Before");
+ e1.setCachedString("bar", "After");
+ Assert.equal(e1.getCachedString("bar"), "After");
+ e1.setCachedString("bar", "日本語");
+ Assert.equal(e1.getCachedString("bar"), "日本語");
+
+ // Check some disallowed conversions from String.
+ Assert.throws(function () {
+ e1.getCachedInt32("bar");
+ }, /NS_ERROR_NOT_AVAILABLE/);
+ Assert.throws(function () {
+ e1.getCachedUInt32("bar");
+ }, /NS_ERROR_NOT_AVAILABLE/);
+ Assert.throws(function () {
+ e1.getCachedInt64("bar");
+ }, /NS_ERROR_NOT_AVAILABLE/);
+
+ // Trying to read missing properties is an error.
+ Assert.throws(function () {
+ e1.getCachedInt32("non-existent-property");
+ }, /NS_ERROR_NOT_AVAILABLE/);
+ Assert.throws(function () {
+ e1.getCachedUInt32("non-existent-property");
+ }, /NS_ERROR_NOT_AVAILABLE/);
+ Assert.throws(function () {
+ e1.getCachedInt64("non-existent-property");
+ }, /NS_ERROR_NOT_AVAILABLE/);
+ Assert.throws(function () {
+ e1.getCachedString("non-existent-property");
+ }, /NS_ERROR_NOT_AVAILABLE/);
+
+ // Force a save to jsonFile. The changes we made will have queued up a
+ // cache autosave but we don't want to wait that long. The cache dtor
+ // would also save, but we don't want to second-guess JS garbage
+ // collection here.
+ cache.flush();
+ }
+
+ // Create a new cache object, reload jsonFile and make sure all the expected
+ // values are there.
+ {
+ let cache = Cc["@mozilla.org/messenger/msgFolderCache;1"].createInstance(
+ Ci.nsIMsgFolderCache
+ );
+ // jsonFile is there now.
+ Assert.ok(jsonFile.exists());
+ Assert.ok(!legacyFile.exists());
+ cache.init(jsonFile, legacyFile);
+ // Make sure all the values we previously set are intact.
+ let e1 = cache.getCacheElement("/made/up/path/Inbox", true);
+ Assert.equal(e1.getCachedInt32("wibble"), 42);
+ Assert.equal(e1.getCachedUInt32("pibble"), 42);
+ Assert.equal(e1.getCachedInt64("foo"), 42);
+ Assert.equal(e1.getCachedString("bar"), "日本語");
+ }
+
+ // clean up for next test
+ jsonFile.remove(false);
+});
+
+add_task(async function test_null_entries() {
+ // Write out a trivial foldercache file with a null value.
+ let data = { "a-folder-key": { foo: null } };
+ let jsonFilename = PathUtils.join(PathUtils.tempDir, "foo.json");
+ await IOUtils.writeJSON(jsonFilename, data);
+
+ // Load it into an msIMsgFolderCache
+ let cache = Cc["@mozilla.org/messenger/msgFolderCache;1"].createInstance(
+ Ci.nsIMsgFolderCache
+ );
+ let jsonFile = Cc["@mozilla.org/file/local;1"].createInstance(Ci.nsIFile);
+ jsonFile.initWithPath(jsonFilename);
+ let morkFile = Cc["@mozilla.org/file/local;1"].createInstance(Ci.nsIFile);
+ morkFile.initWithPath(
+ PathUtils.join(PathUtils.tempDir, "non-existent-file.dat")
+ );
+ cache.init(jsonFile, morkFile);
+
+ //
+ let e1 = cache.getCacheElement("a-folder-key", false);
+
+ // Make sure all accessors convert the null appropriately.
+ Assert.equal(e1.getCachedInt32("foo"), 0);
+ Assert.equal(e1.getCachedUInt32("foo"), 0);
+ Assert.equal(e1.getCachedInt64("foo"), 0);
+ Assert.equal(e1.getCachedString("foo"), "");
+});
+
+/**
+ * Test foldercache migration from mork DB (panacea.dat) to JSON.
+ */
+add_task(async function test_migration() {
+ let profileDir = do_get_profile();
+ let jsonFile = profileDir.clone();
+ jsonFile.append("folderCache.json");
+ let legacyFile = profileDir.clone();
+ legacyFile.append("panacea.dat");
+
+ Assert.ok(!jsonFile.exists());
+ Assert.ok(!legacyFile.exists());
+
+ // Install our test legacy file.
+ do_get_file("data/panacea.dat").copyTo(profileDir, legacyFile.leafName);
+
+ // Set up the cache.
+ {
+ let cache = Cc["@mozilla.org/messenger/msgFolderCache;1"].createInstance(
+ Ci.nsIMsgFolderCache
+ );
+ cache.init(jsonFile, legacyFile);
+
+ // Migration should have occurred.
+ Assert.ok(jsonFile.exists());
+ Assert.ok(!legacyFile.exists());
+
+ // Done with the cache now.
+ }
+
+ // Compare the migrated json to the json we expect.
+ let raw = await IOUtils.readUTF8(jsonFile.path);
+ let got = JSON.parse(raw);
+
+ raw = await IOUtils.readUTF8(do_get_file("data/folderCache.json").path);
+ let expect = JSON.parse(raw);
+
+ Assert.deepEqual(got, expect);
+
+ // clean up for next test
+ jsonFile.remove(false);
+});
+
+/**
+ * Test foldercache migration doesn't crash with a dud panacea.dat.
+ */
+add_task(async function test_bad_pancea_dat() {
+ let profileDir = do_get_profile();
+ let jsonFile = profileDir.clone();
+ jsonFile.append("folderCache.json");
+ let legacyFile = profileDir.clone();
+ legacyFile.append("panacea.dat");
+
+ Assert.ok(!jsonFile.exists());
+ Assert.ok(!legacyFile.exists());
+
+ // Install our bad panacea.dat. It has only the first line - the mork magic
+ // cookie - so it's valid enough for mork to open, but doesn't have
+ // anything the migration is looking for.
+ do_get_file("data/panacea_empty.dat").copyTo(profileDir, legacyFile.leafName);
+
+ // Set up the cache.
+ let cache = Cc["@mozilla.org/messenger/msgFolderCache;1"].createInstance(
+ Ci.nsIMsgFolderCache
+ );
+ // init() returns OK even if migration fails - the show must go on!
+ cache.init(jsonFile, legacyFile);
+
+ // If we get this far, we didn't crash, which is good.
+ // The migration should have left everything as it was.
+ Assert.ok(legacyFile.exists());
+ Assert.ok(!jsonFile.exists());
+});
diff --git a/comm/mailnews/base/test/unit/test_nsIMsgFolderListener.js b/comm/mailnews/base/test/unit/test_nsIMsgFolderListener.js
new file mode 100644
index 0000000000..c0a9b72e64
--- /dev/null
+++ b/comm/mailnews/base/test/unit/test_nsIMsgFolderListener.js
@@ -0,0 +1,214 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* ***** BEGIN LICENSE BLOCK *****
+ *
+ * Any copyright is dedicated to the Public Domain.
+ * http://creativecommons.org/licenses/publicdomain/
+ *
+ * ***** END LICENSE BLOCK ***** */
+
+/*
+ * Test suite for basic functionality with nsIMsgFolderListeners.
+ */
+
+var { MailServices } = ChromeUtils.import(
+ "resource:///modules/MailServices.jsm"
+);
+
+var nsIMFNService = Ci.nsIMsgFolderNotificationService;
+
+var gIndividualFlags = [
+ nsIMFNService.msgAdded,
+ nsIMFNService.msgsClassified,
+ nsIMFNService.msgsJunkStatusChanged,
+ nsIMFNService.msgsDeleted,
+ nsIMFNService.msgsMoveCopyCompleted,
+ nsIMFNService.msgKeyChanged,
+ nsIMFNService.msgUnincorporatedMoved,
+ nsIMFNService.folderAdded,
+ nsIMFNService.folderDeleted,
+ nsIMFNService.folderMoveCopyCompleted,
+ nsIMFNService.folderRenamed,
+ nsIMFNService.folderCompactStart,
+ nsIMFNService.folderCompactFinish,
+ nsIMFNService.folderReindexTriggered,
+];
+
+// Our listener, which captures events.
+function gMFListener() {}
+gMFListener.prototype = {
+ mReceived: 0,
+ mRemoveSelf: false,
+ msgAdded(aMsg) {
+ Assert.equal(this.mReceived & nsIMFNService.msgAdded, 0);
+ this.mReceived |= nsIMFNService.msgAdded;
+ if (this.mRemoveSelf) {
+ MailServices.mfn.removeListener(this);
+ }
+ },
+
+ msgsClassified(aMsgs, aJunkProcessed, aTraitProcessed) {
+ Assert.equal(this.mReceived & nsIMFNService.msgsClassified, 0);
+ this.mReceived |= nsIMFNService.msgsClassified;
+ if (this.mRemoveSelf) {
+ MailServices.mfn.removeListener(this);
+ }
+ },
+
+ msgsJunkStatusChanged(messages) {
+ Assert.equal(this.mReceived & nsIMFNService.msgsJunkStatusChanged, 0);
+ this.mReceived |= nsIMFNService.msgsJunkStatusChanged;
+ if (this.mRemoveSelf) {
+ MailServices.mfn.removeListener(this);
+ }
+ },
+
+ msgsDeleted(aMsgs) {
+ Assert.equal(this.mReceived & nsIMFNService.msgsDeleted, 0);
+ this.mReceived |= nsIMFNService.msgsDeleted;
+ if (this.mRemoveSelf) {
+ MailServices.mfn.removeListener(this);
+ }
+ },
+
+ msgsMoveCopyCompleted(aMove, aSrcMsgs, aDestFolder, aDestMsgs) {
+ Assert.equal(this.mReceived & nsIMFNService.msgsMoveCopyCompleted, 0);
+ this.mReceived |= nsIMFNService.msgsMoveCopyCompleted;
+ if (this.mRemoveSelf) {
+ MailServices.mfn.removeListener(this);
+ }
+ },
+
+ msgKeyChanged(aOldMsgKey, aNewMsgHdr) {
+ Assert.equal(this.mReceived & nsIMFNService.msgKeyChanged, 0);
+ this.mReceived |= nsIMFNService.msgKeyChanged;
+ if (this.mRemoveSelf) {
+ MailServices.mfn.removeListener(this);
+ }
+ },
+
+ msgUnincorporatedMoved(srcFolder, msg) {
+ Assert.equal(this.mReceived & nsIMFNService.msgUnincorporatedMoved, 0);
+ this.mReceived |= nsIMFNService.msgUnincorporatedMoved;
+ if (this.mRemoveSelf) {
+ MailServices.mfn.removeListener(this);
+ }
+ },
+
+ folderAdded(aFolder) {
+ Assert.equal(this.mReceived & nsIMFNService.folderAdded, 0);
+ this.mReceived |= nsIMFNService.folderAdded;
+ if (this.mRemoveSelf) {
+ MailServices.mfn.removeListener(this);
+ }
+ },
+
+ folderDeleted(aFolder) {
+ Assert.equal(this.mReceived & nsIMFNService.folderDeleted, 0);
+ this.mReceived |= nsIMFNService.folderDeleted;
+ if (this.mRemoveSelf) {
+ MailServices.mfn.removeListener(this);
+ }
+ },
+
+ folderMoveCopyCompleted(aMove, aSrcFolder, aDestFolder) {
+ Assert.equal(this.mReceived & nsIMFNService.folderMoveCopyCompleted, 0);
+ this.mReceived |= nsIMFNService.folderMoveCopyCompleted;
+ if (this.mRemoveSelf) {
+ MailServices.mfn.removeListener(this);
+ }
+ },
+
+ folderRenamed(aOrigFolder, aNewFolder) {
+ Assert.equal(this.mReceived & nsIMFNService.folderRenamed, 0);
+ this.mReceived |= nsIMFNService.folderRenamed;
+ if (this.mRemoveSelf) {
+ MailServices.mfn.removeListener(this);
+ }
+ },
+
+ folderCompactStart(folder) {
+ Assert.equal(this.mReceived & nsIMFNService.folderCompactStart, 0);
+ this.mReceived |= nsIMFNService.folderCompactStart;
+ if (this.mRemoveSelf) {
+ MailServices.mfn.removeListener(this);
+ }
+ },
+
+ folderCompactFinish(folder) {
+ Assert.equal(this.mReceived & nsIMFNService.folderCompactFinish, 0);
+ this.mReceived |= nsIMFNService.folderCompactFinish;
+ if (this.mRemoveSelf) {
+ MailServices.mfn.removeListener(this);
+ }
+ },
+
+ folderReindexTriggered(folder) {
+ Assert.equal(this.mReceived & nsIMFNService.folderReindexTriggered, 0);
+ this.mReceived |= nsIMFNService.folderReindexTriggered;
+ if (this.mRemoveSelf) {
+ MailServices.mfn.removeListener(this);
+ }
+ },
+};
+
+function NotifyMsgFolderListeners() {
+ MailServices.mfn.notifyMsgAdded(null);
+ MailServices.mfn.notifyMsgsClassified([], null, null);
+ MailServices.mfn.notifyMsgsJunkStatusChanged([]);
+ MailServices.mfn.notifyMsgsDeleted([]);
+ MailServices.mfn.notifyMsgsMoveCopyCompleted(null, [], null, []);
+ MailServices.mfn.notifyMsgKeyChanged(null, null);
+ MailServices.mfn.notifyMsgUnincorporatedMoved(null, null);
+ MailServices.mfn.notifyFolderAdded(null);
+ MailServices.mfn.notifyFolderDeleted(null);
+ MailServices.mfn.notifyFolderMoveCopyCompleted(null, null, null);
+ MailServices.mfn.notifyFolderRenamed(null, null);
+ MailServices.mfn.notifyFolderCompactStart(null);
+ MailServices.mfn.notifyFolderCompactFinish(null);
+ MailServices.mfn.notifyFolderReindexTriggered(null);
+}
+
+function run_test() {
+ // Test: Add listeners
+ var singleListeners = [];
+
+ var addAListener = function (flag) {
+ var listener = new gMFListener();
+ MailServices.mfn.addListener(listener, flag);
+ singleListeners.push(listener);
+ };
+
+ gIndividualFlags.forEach(addAListener);
+
+ // Test: Notify the listeners of all events.
+ NotifyMsgFolderListeners();
+
+ // Test: check whether the correct number of notifications have been received.
+ // Then remove the listeners
+ var checkFlag = function (flag) {
+ var listener = singleListeners.shift();
+ Assert.equal(listener.mReceived, flag);
+ listener.mRemoveSelf = true;
+ listener.mReceived = 0;
+ singleListeners.push(listener);
+ };
+ gIndividualFlags.forEach(checkFlag);
+
+ // We'll do one more set of notifications, and remove ourselves in the middle of them
+ NotifyMsgFolderListeners();
+
+ // Test: all listeners should be removed at this point
+ Assert.ok(!MailServices.mfn.hasListeners);
+
+ // Test: Send notifications again. Check that we don't receive any notifications.
+ singleListeners.forEach(function (listener) {
+ listener.mReceived = 0;
+ });
+
+ NotifyMsgFolderListeners();
+
+ var checkNotReceived = function () {
+ Assert.equal(singleListeners.shift().mReceived, 0);
+ };
+ gIndividualFlags.forEach(checkNotReceived);
+}
diff --git a/comm/mailnews/base/test/unit/test_nsIMsgFolderListenerLocal.js b/comm/mailnews/base/test/unit/test_nsIMsgFolderListenerLocal.js
new file mode 100644
index 0000000000..aadbd75b8a
--- /dev/null
+++ b/comm/mailnews/base/test/unit/test_nsIMsgFolderListenerLocal.js
@@ -0,0 +1,444 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* ***** BEGIN LICENSE BLOCK *****
+ *
+ * Any copyright is dedicated to the Public Domain.
+ * http://creativecommons.org/licenses/publicdomain/
+ *
+ * ***** END LICENSE BLOCK ***** */
+
+/*
+ * Test suite for nsIMsgFolderListener events due to local mail folder
+ * operations.
+ *
+ * Currently tested:
+ * - Adding new folders
+ * - Copy messages from files into the db
+ * - Moving and copying one or more messages from one local folder to another
+ * - Moving folders, with and without subfolders
+ * - Renaming folders
+ * - Deleting messages and folders, to trash and from trash (permanently)
+ */
+
+/* import-globals-from ../../../test/resources/msgFolderListenerSetup.js */
+load("../../../resources/msgFolderListenerSetup.js");
+
+// Globals
+var gMsgFile1, gMsgFile2, gMsgFile3;
+var gRootFolder;
+var gLocalFolder2;
+var gLocalFolder3;
+var gLocalTrashFolder;
+
+// storeIn takes a string containing the variable to store the new folder in
+function addFolder(parent, folderName, storeIn) {
+ gExpectedEvents = [
+ [MailServices.mfn.folderAdded, parent, folderName, storeIn],
+ ];
+ // We won't receive a copy listener notification for this
+ gCurrStatus |= kStatus.onStopCopyDone;
+ parent.createSubfolder(folderName, null);
+ gCurrStatus |= kStatus.functionCallDone;
+ if (gCurrStatus == kStatus.everythingDone) {
+ resetStatusAndProceed();
+ }
+}
+
+/**
+ * This will introduce a new message to the system which will generate an added
+ * notification and subsequently a classification notification. For the
+ * classification because no messages have yet been marked as junk and there
+ * are no traits configured, aJunkProcessed and aTraitProcessed will be false.
+ */
+function copyFileMessage(file, destFolder, isDraftOrTemplate) {
+ copyListener.mFolderStoredIn = destFolder;
+ gExpectedEvents = [
+ [MailServices.mfn.msgAdded, gHdrsReceived],
+ [MailServices.mfn.msgsClassified, gHdrsReceived, false, false],
+ ];
+ MailServices.copy.copyFileMessage(
+ file,
+ destFolder,
+ null,
+ isDraftOrTemplate,
+ 0,
+ "",
+ copyListener,
+ null
+ );
+ gCurrStatus |= kStatus.functionCallDone;
+ if (gCurrStatus == kStatus.everythingDone) {
+ resetStatusAndProceed();
+ }
+}
+
+function copyMessages(items, isMove, srcFolder, destFolder) {
+ gExpectedEvents = [
+ [MailServices.mfn.msgsMoveCopyCompleted, isMove, items, destFolder, true],
+ ];
+ MailServices.copy.copyMessages(
+ srcFolder,
+ items,
+ destFolder,
+ isMove,
+ copyListener,
+ null,
+ true
+ );
+ gCurrStatus |= kStatus.functionCallDone;
+ if (gCurrStatus == kStatus.everythingDone) {
+ resetStatusAndProceed();
+ }
+}
+
+function copyFolder(srcFolder, isMove, destFolder) {
+ gExpectedEvents = [
+ [MailServices.mfn.folderMoveCopyCompleted, isMove, [srcFolder], destFolder],
+ ];
+ MailServices.copy.copyFolder(
+ srcFolder,
+ destFolder,
+ isMove,
+ copyListener,
+ null
+ );
+ gCurrStatus |= kStatus.functionCallDone;
+ if (gCurrStatus == kStatus.everythingDone) {
+ resetStatusAndProceed();
+ }
+}
+
+function deleteMessages(srcFolder, items, deleteStorage, isMove) {
+ // We should only get the delete notification only if we are not moving, and are deleting from
+ // the storage/trash. We should get only the move/copy notification if we aren't.
+ var isTrashFolder = srcFolder.getFlag(Ci.nsMsgFolderFlags.Trash);
+ if (!isMove && (deleteStorage || isTrashFolder)) {
+ // We won't be getting any OnStopCopy notification in this case
+ gCurrStatus = kStatus.onStopCopyDone;
+ gExpectedEvents = [[MailServices.mfn.msgsDeleted, items]];
+ } else {
+ // We have to be getting a move notification, even if isMove is false
+ gExpectedEvents = [
+ [
+ MailServices.mfn.msgsMoveCopyCompleted,
+ true,
+ items,
+ gLocalTrashFolder,
+ true,
+ ],
+ ];
+ }
+
+ srcFolder.deleteMessages(
+ items,
+ null,
+ deleteStorage,
+ isMove,
+ copyListener,
+ true
+ );
+ gCurrStatus |= kStatus.functionCallDone;
+ if (gCurrStatus == kStatus.everythingDone) {
+ resetStatusAndProceed();
+ }
+}
+
+function renameFolder(folder, newName) {
+ gExpectedEvents = [[MailServices.mfn.folderRenamed, [folder], newName]];
+ gCurrStatus = kStatus.onStopCopyDone;
+ folder.rename(newName, null);
+ gCurrStatus |= kStatus.functionCallDone;
+ if (gCurrStatus == kStatus.everythingDone) {
+ resetStatusAndProceed();
+ }
+}
+
+function deleteFolder(folder, child) {
+ // We won't be getting any OnStopCopy notification at all
+ // XXX delete to trash should get one, but we'll need to pass the listener
+ // somehow to deleteSelf
+ gCurrStatus = kStatus.onStopCopyDone;
+ // If ancestor is trash, expect a folderDeleted, otherwise expect
+ // a folderMoveCopyCompleted.
+ if (gLocalTrashFolder.isAncestorOf(folder)) {
+ if (child) {
+ gExpectedEvents = [
+ [MailServices.mfn.folderDeleted, [child]],
+ [MailServices.mfn.folderDeleted, [folder]],
+ ];
+ } else {
+ gExpectedEvents = [[MailServices.mfn.folderDeleted, [folder]]];
+ }
+ } else {
+ gExpectedEvents = [
+ [
+ MailServices.mfn.folderMoveCopyCompleted,
+ true,
+ [folder],
+ gLocalTrashFolder,
+ ],
+ ];
+ }
+
+ folder.deleteSelf(null);
+ gCurrStatus |= kStatus.functionCallDone;
+ if (gCurrStatus == kStatus.everythingDone) {
+ resetStatusAndProceed();
+ }
+}
+
+function compactFolder(folder) {
+ gExpectedEvents = [
+ [MailServices.mfn.folderCompactStart, folder],
+ [MailServices.mfn.folderCompactFinish, folder],
+ ];
+ // We won't receive a copy listener notification for this
+ gCurrStatus |= kStatus.onStopCopyDone;
+ folder.compact(null, null);
+ gCurrStatus |= kStatus.functionCallDone;
+ if (gCurrStatus == kStatus.everythingDone) {
+ resetStatusAndProceed();
+ }
+}
+
+/*
+ * TESTS
+ */
+
+// Beware before commenting out a test -- later tests might just depend on earlier ones
+var gTestArray = [
+ // Adding folders
+ // Create another folder to move and copy messages around, and force initialization.
+ function addFolder1() {
+ addFolder(gRootFolder, "folder2", function (folder) {
+ gLocalFolder2 = folder;
+ });
+ },
+ // Create a third folder for more testing.
+ function addFolder2() {
+ addFolder(gRootFolder, "folder3", function (folder) {
+ gLocalFolder3 = folder;
+ });
+ },
+ // Folder structure is now
+ // Inbox
+ // Trash
+ // folder2
+ // folder3
+ // Copying messages from files
+ function testCopyFileMessage1() {
+ copyFileMessage(gMsgFile1, localAccountUtils.inboxFolder, false);
+ },
+ function testCopyFileMessage2() {
+ copyFileMessage(gMsgFile2, localAccountUtils.inboxFolder, false);
+ },
+ function testCopyFileMessage3() {
+ copyFileMessage(gMsgFile3, localAccountUtils.inboxFolder, true);
+ },
+
+ // Moving/copying messages
+ function testCopyMessages1() {
+ copyMessages(
+ [gMsgHdrs[0].hdr],
+ false,
+ localAccountUtils.inboxFolder,
+ gLocalFolder2
+ );
+ },
+ function testCopyMessages2() {
+ copyMessages(
+ [gMsgHdrs[1].hdr, gMsgHdrs[2].hdr],
+ false,
+ localAccountUtils.inboxFolder,
+ gLocalFolder2
+ );
+ },
+ function testMoveMessages1() {
+ copyMessages(
+ [gMsgHdrs[0].hdr, gMsgHdrs[1].hdr],
+ true,
+ localAccountUtils.inboxFolder,
+ gLocalFolder3
+ );
+ },
+ function testMoveMessages2() {
+ copyMessages(
+ [gMsgHdrs[2].hdr],
+ true,
+ localAccountUtils.inboxFolder,
+ gLocalTrashFolder
+ );
+ },
+ function testMoveMessages3() {
+ // This is to test whether the notification is correct for moving from trash
+ gMsgHdrs[2].hdr = gLocalTrashFolder.msgDatabase.getMsgHdrForMessageID(
+ gMsgHdrs[2].ID
+ );
+ copyMessages([gMsgHdrs[2].hdr], true, gLocalTrashFolder, gLocalFolder3);
+ },
+ // Moving/copying folders
+ function testCopyFolder1() {
+ copyFolder(gLocalFolder3, false, gLocalFolder2);
+ },
+ function testMoveFolder1() {
+ copyFolder(gLocalFolder3, true, localAccountUtils.inboxFolder);
+ },
+ function testMoveFolder2() {
+ copyFolder(gLocalFolder2, true, localAccountUtils.inboxFolder);
+ },
+ // Folder structure should now be
+ // Inbox
+ // -folder2
+ // --folder3
+ // -folder3
+ // Trash
+
+ // Deleting messages
+ function testDeleteMessages1() {
+ // delete to trash
+ // Let's take a moment to re-initialize stuff that got moved
+ gLocalFolder2 = localAccountUtils.inboxFolder.getChildNamed("folder2");
+ gLocalFolder3 = gLocalFolder2.getChildNamed("folder3");
+ var folder3DB = gLocalFolder3.msgDatabase;
+ for (var i = 0; i < gMsgHdrs.length; i++) {
+ gMsgHdrs[i].hdr = folder3DB.getMsgHdrForMessageID(gMsgHdrs[i].ID);
+ }
+
+ // Now delete the message
+ deleteMessages(
+ gLocalFolder3,
+ [gMsgHdrs[0].hdr, gMsgHdrs[1].hdr],
+ false,
+ false
+ );
+ },
+ // shift delete
+ function testDeleteMessages2() {
+ deleteMessages(gLocalFolder3, [gMsgHdrs[2].hdr], true, false);
+ },
+ function testDeleteMessages3() {
+ // normal delete from trash
+ var trashDB = gLocalTrashFolder.msgDatabase;
+ for (var i = 0; i < gMsgHdrs.length; i++) {
+ gMsgHdrs[i].hdr = trashDB.getMsgHdrForMessageID(gMsgHdrs[i].ID);
+ }
+ deleteMessages(gLocalTrashFolder, [gMsgHdrs[0].hdr], false, false);
+ },
+ // shift delete from trash
+ function testDeleteMessages4() {
+ deleteMessages(gLocalTrashFolder, [gMsgHdrs[1].hdr], true, false);
+ },
+
+ // Renaming folders
+ function testRename1() {
+ renameFolder(gLocalFolder3, "folder4");
+ },
+ function testRename2() {
+ renameFolder(gLocalFolder2.getChildNamed("folder4"), "folder3");
+ },
+ function testRename3() {
+ renameFolder(gLocalFolder2, "folder4");
+ },
+ function testRename4() {
+ renameFolder(
+ localAccountUtils.inboxFolder.getChildNamed("folder4"),
+ "folder2"
+ );
+ },
+
+ // Folder structure should still be
+ // Inbox
+ // -folder2
+ // --folder3
+ // -folder3
+ // Trash
+
+ // Deleting folders (currently only one folder delete is supported through the UI)
+ function deleteFolder1() {
+ deleteFolder(localAccountUtils.inboxFolder.getChildNamed("folder3"), null);
+ },
+ // Folder structure should now be
+ // Inbox
+ // -folder2
+ // --folder3
+ // Trash
+ // -folder3
+ function deleteFolder2() {
+ deleteFolder(localAccountUtils.inboxFolder.getChildNamed("folder2"), null);
+ },
+ // Folder structure should now be
+ // Inbox
+ // Trash
+ // -folder2
+ // --folder3
+ // -folder3
+ function deleteFolder3() {
+ deleteFolder(gLocalTrashFolder.getChildNamed("folder3"), null);
+ },
+ // Folder structure should now be
+ // Inbox
+ // Trash
+ // -folder2
+ // --folder3
+ function deleteFolder4() {
+ // Let's take a moment to re-initialize stuff that got moved
+ gLocalFolder2 = gLocalTrashFolder.getChildNamed("folder2");
+ gLocalFolder3 = gLocalFolder2.getChildNamed("folder3");
+ deleteFolder(gLocalFolder2, gLocalFolder3);
+ },
+ function compactInbox() {
+ if (localAccountUtils.inboxFolder.msgStore.supportsCompaction) {
+ compactFolder(localAccountUtils.inboxFolder);
+ } else {
+ doTest(++gTest);
+ }
+ },
+];
+// Folder structure should just be
+// Inbox
+// Trash
+
+function run_test() {
+ localAccountUtils.loadLocalMailAccount();
+
+ // Add a listener.
+ MailServices.mfn.addListener(gMFListener, allTestedEvents);
+
+ // Load up some messages so that we can copy them in later.
+ gMsgFile1 = do_get_file("../../../data/bugmail10");
+ gMsgFile2 = do_get_file("../../../data/bugmail11");
+ gMsgFile3 = do_get_file("../../../data/draft1");
+
+ // "Trash" folder
+ gRootFolder = localAccountUtils.incomingServer.rootMsgFolder;
+ gLocalTrashFolder = gRootFolder.getChildNamed("Trash");
+
+ // "Master" do_test_pending(), paired with a do_test_finished() at the end of all the operations.
+ do_test_pending();
+
+ // Do the test.
+ doTest(1);
+}
+
+function doTest(test) {
+ if (test <= gTestArray.length) {
+ var testFn = gTestArray[test - 1];
+ // Set a limit of 10 seconds; if the notifications haven't arrived by then there's a problem.
+ do_timeout(10000, function () {
+ if (gTest == test) {
+ do_throw(
+ "Notifications not received in 10000 ms for operation " +
+ testFn.name +
+ ", current status is " +
+ gCurrStatus
+ );
+ }
+ });
+ dump("=== Test: " + testFn.name + "\n");
+ testFn();
+ } else {
+ gHdrsReceived = null;
+ gMsgHdrs = null;
+ MailServices.mfn.removeListener(gMFListener);
+ do_test_finished(); // for the one in run_test()
+ }
+}
diff --git a/comm/mailnews/base/test/unit/test_nsIMsgTagService.js b/comm/mailnews/base/test/unit/test_nsIMsgTagService.js
new file mode 100644
index 0000000000..52e36056ef
--- /dev/null
+++ b/comm/mailnews/base/test/unit/test_nsIMsgTagService.js
@@ -0,0 +1,113 @@
+/* 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/. */
+
+/*
+ * Tests of nsIMsgTagService.
+ *
+ * Specifically tests changes implemented in bug 217034
+ * Does not do comprehensive testing.
+ *
+ */
+
+var { MailServices } = ChromeUtils.import(
+ "resource:///modules/MailServices.jsm"
+);
+
+function run_test() {
+ // These are both tags and keys. Note keys are forced to be lower case
+ const tag1 = "istag";
+ const tag2 = "notistag";
+ const tag3 = "istagnot";
+ const tag4 = "istagtoo";
+
+ // add a tag
+ MailServices.tags.addTagForKey(tag1, tag1, null, null);
+
+ // delete any existing tags
+ let tagArray = MailServices.tags.getAllTags();
+ for (var i = 0; i < tagArray.length; i++) {
+ MailServices.tags.deleteKey(tagArray[i].key);
+ }
+
+ // make sure added tag is now gone
+ Assert.ok(!MailServices.tags.isValidKey(tag1));
+
+ // add single tag, and check again
+ MailServices.tags.addTagForKey(tag1, tag1, null, null);
+ Assert.ok(MailServices.tags.isValidKey(tag1));
+ Assert.ok(!MailServices.tags.isValidKey(tag4));
+
+ // add second tag and check
+ MailServices.tags.addTagForKey(tag4, tag4, null, null);
+ Assert.ok(MailServices.tags.isValidKey(tag1));
+ Assert.ok(!MailServices.tags.isValidKey(tag2));
+ Assert.ok(!MailServices.tags.isValidKey(tag3));
+ Assert.ok(MailServices.tags.isValidKey(tag4));
+
+ // delete a tag and check
+ MailServices.tags.deleteKey(tag1);
+ Assert.ok(!MailServices.tags.isValidKey(tag1));
+ Assert.ok(!MailServices.tags.isValidKey(tag2));
+ Assert.ok(!MailServices.tags.isValidKey(tag3));
+ Assert.ok(MailServices.tags.isValidKey(tag4));
+
+ // add many tags and check again
+ for (i = 0; i < 100; i++) {
+ MailServices.tags.addTagForKey(i, "lotsatags" + i, null, null);
+ }
+ Assert.ok(!MailServices.tags.isValidKey(tag1));
+ Assert.ok(!MailServices.tags.isValidKey(tag2));
+ Assert.ok(!MailServices.tags.isValidKey(tag3));
+ Assert.ok(MailServices.tags.isValidKey(tag4));
+
+ for (i = 0; i < 100; i++) {
+ Assert.ok(MailServices.tags.isValidKey(i));
+ // make sure it knows the difference betweens tags and keys
+ Assert.ok(!MailServices.tags.isValidKey("lotsatags" + i));
+ // are we confused by key at start of tag?
+ Assert.ok(!MailServices.tags.isValidKey(i + "lotsatags"));
+ }
+
+ // Test sort ordering for getAllTags() without ordinal.
+ for (let tag of MailServices.tags.getAllTags()) {
+ MailServices.tags.deleteKey(tag.key);
+ }
+ MailServices.tags.addTag("grapefruit", null, null);
+ MailServices.tags.addTag("orange", null, null);
+ MailServices.tags.addTag("lime", null, null);
+ MailServices.tags.addTag("lemon", null, null);
+
+ // Should be sorted by tag name.
+ let tagNames = MailServices.tags.getAllTags().map(t => t.tag);
+ Assert.deepEqual(
+ tagNames,
+ ["grapefruit", "lemon", "lime", "orange"],
+ "Sort without ordinals"
+ );
+
+ // Test sort ordering for getAllTags() with (some) ordinals.
+ for (let tag of MailServices.tags.getAllTags()) {
+ MailServices.tags.deleteKey(tag.key);
+ }
+ MailServices.tags.addTag("grapefruit", null, "3");
+ MailServices.tags.addTag("orange", null, "1");
+ MailServices.tags.addTag("lime", null, null);
+ MailServices.tags.addTag("lemon", null, "2");
+
+ // Should be sorted by ordinal, then tag name.
+ tagNames = MailServices.tags.getAllTags().map(t => t.tag);
+ Assert.deepEqual(
+ tagNames,
+ ["orange", "lemon", "grapefruit", "lime"],
+ "Sort with ordinals"
+ );
+}
+
+/*
+function printTags() {
+ for (let tag of MailServices.tags.getAllTags()) {
+ print(`# key [${tag.key}] tag [${tag.tag}] ordinal [${tag.ordinal}]`);
+ }
+}
+*/
diff --git a/comm/mailnews/base/test/unit/test_nsMailDirProvider.js b/comm/mailnews/base/test/unit/test_nsMailDirProvider.js
new file mode 100644
index 0000000000..19b848f3fd
--- /dev/null
+++ b/comm/mailnews/base/test/unit/test_nsMailDirProvider.js
@@ -0,0 +1,23 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/*
+ * Test suite for nsMailDirProvider to check we get the right directories and
+ * files.
+ */
+
+function run_test() {
+ const items = [
+ { key: "MailD", value: "Mail" },
+ { key: "IMapMD", value: "ImapMail" },
+ { key: "NewsD", value: "News" },
+ { key: "MLFCaF", value: "panacea.dat" }, // Legacy folder cache.
+ { key: "MFCaF", value: "folderCache.json" },
+ ];
+
+ items.forEach(function (item) {
+ var dir = Services.dirsvc.get(item.key, Ci.nsIFile);
+ dump(do_get_profile().path + " " + dir.path + "\n");
+ Assert.ok(do_get_profile().equals(dir.parent));
+
+ Assert.equal(dir.leafName, item.value);
+ });
+}
diff --git a/comm/mailnews/base/test/unit/test_nsMsgDBView.js b/comm/mailnews/base/test/unit/test_nsMsgDBView.js
new file mode 100644
index 0000000000..cb5527deab
--- /dev/null
+++ b/comm/mailnews/base/test/unit/test_nsMsgDBView.js
@@ -0,0 +1,1212 @@
+/* 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/. */
+
+/*
+ * Attempt to test nsMsgDBView and descendents. Right now this means we:
+ * - Ensure sorting and grouping sorta works, including using custom columns.
+ *
+ * Things we really should do:
+ * - Test that secondary sorting works, especially when the primary column is
+ * a custom column.
+ *
+ * You may also want to look into the test_viewWrapper_*.js tests as well.
+ */
+
+var { MessageGenerator, MessageScenarioFactory, SyntheticMessageSet } =
+ ChromeUtils.import("resource://testing-common/mailnews/MessageGenerator.jsm");
+const { TreeSelection } = ChromeUtils.importESModule(
+ "chrome://messenger/content/tree-selection.mjs"
+);
+var { MessageInjection } = ChromeUtils.import(
+ "resource://testing-common/mailnews/MessageInjection.jsm"
+);
+var { PromiseUtils } = ChromeUtils.importESModule(
+ "resource://gre/modules/PromiseUtils.sys.mjs"
+);
+var { dump_view_contents } = ChromeUtils.import(
+ "resource://testing-common/mozmill/ViewHelpers.jsm"
+);
+
+// Items used to add messages to the folder
+var gMessageGenerator = new MessageGenerator();
+var gScenarioFactory = new MessageScenarioFactory(gMessageGenerator);
+var messageInjection = new MessageInjection({ mode: "local" });
+
+var gTestFolder;
+var gSiblingsMissingParentsSubject;
+var gMessages;
+
+function setup_messages() {
+ // build up a diverse list of messages
+ let messages = [];
+ messages = messages.concat(gScenarioFactory.directReply(10));
+ // the message generator uses a constanty incrementing counter, so we need to
+ // mix up the order of messages ourselves to ensure that the timestamp
+ // ordering is not already in order. (a poor test of sorting otherwise.)
+ messages = gScenarioFactory.directReply(6).concat(messages);
+
+ messages = messages.concat(gScenarioFactory.fullPyramid(3, 3));
+ let siblingMessages = gScenarioFactory.siblingsMissingParent();
+ // cut off "Re: " part
+ gSiblingsMissingParentsSubject = siblingMessages[0].subject.slice(4);
+ dump("siblings subect = " + gSiblingsMissingParentsSubject + "\n");
+ messages = messages.concat(siblingMessages);
+ messages = messages.concat(gScenarioFactory.missingIntermediary());
+ // This next line was found to be faulty during linting, but fixing it breaks the test.
+ // messages.concat(gMessageGenerator.makeMessage({age: {days: 2, hours: 1}}));
+
+ // build a hierarchy like this (the UID order corresponds to the date order)
+ // 1
+ // 2
+ // 4
+ // 3
+ let msg1 = gMessageGenerator.makeMessage();
+ let msg2 = gMessageGenerator.makeMessage({ inReplyTo: msg1 });
+ let msg3 = gMessageGenerator.makeMessage({ inReplyTo: msg1 });
+ let msg4 = gMessageGenerator.makeMessage({ inReplyTo: msg2 });
+ messages = messages.concat([msg1, msg2, msg3, msg4]);
+
+ // test bug 600140, make a thread that Reply message has smaller MsgKey
+ let msgBiggerKey = gMessageGenerator.makeMessage();
+ let msgSmallerKey = gMessageGenerator.makeMessage({
+ inReplyTo: msgBiggerKey,
+ });
+ messages = messages.concat([msgSmallerKey, msgBiggerKey]);
+ let msgSet = new SyntheticMessageSet(messages);
+ return msgSet;
+}
+
+/**
+ * Sets gTestFolder with msgSet. Ensure that gTestFolder is clean for each test.
+ *
+ * @param {SyntheticMessageSet} msgSet
+ */
+async function set_gTestFolder(msgSet) {
+ gTestFolder = await messageInjection.makeEmptyFolder();
+ await messageInjection.addSetsToFolders([gTestFolder], [msgSet]);
+}
+
+/**
+ * Create a synthetic message by passing the provided aMessageArgs to
+ * the message generator, then add the resulting message to the given
+ * folder (or gTestFolder if no folder is provided).
+ */
+async function make_and_add_message(aMessageArgs) {
+ // create the message
+ let synMsg = gMessageGenerator.makeMessage(aMessageArgs);
+ let msgSet = new SyntheticMessageSet([synMsg]);
+ // this is synchronous for local stuff.
+ await messageInjection.addSetsToFolders([gTestFolder], [msgSet]);
+
+ return [synMsg, msgSet];
+}
+
+function view_throw(why) {
+ dump_view_contents();
+ do_throw(why);
+}
+
+/**
+ * Throw if gDBView has any rows.
+ */
+function assert_view_empty() {
+ if (gTreeView.rowCount != 0) {
+ view_throw(
+ "Expected view to be empty, but it was not! (" +
+ gTreeView.rowCount +
+ " rows)"
+ );
+ }
+}
+
+/**
+ * Throw if gDBView does not have aCount rows.
+ */
+function assert_view_row_count(aCount) {
+ if (gTreeView.rowCount != aCount) {
+ view_throw(
+ "Expected view to have " +
+ aCount +
+ " rows, but it had " +
+ gTreeView.rowCount +
+ " rows!"
+ );
+ }
+}
+
+/**
+ * Throw if any of the arguments (as view indices) do not correspond to dummy
+ * rows in gDBView.
+ */
+function assert_view_index_is_dummy(...aArgs) {
+ for (let viewIndex of aArgs) {
+ let flags = gDBView.getFlagsAt(viewIndex);
+ if (!(flags & MSG_VIEW_FLAG_DUMMY)) {
+ view_throw("Expected index " + viewIndex + " to be a dummy!");
+ }
+ }
+}
+
+/**
+ * Throw if any of the arguments (as view indices) correspond to dummy rows in
+ * gDBView.
+ */
+function assert_view_index_is_not_dummy(...aArgs) {
+ for (let viewIndex of aArgs) {
+ let flags = gDBView.getFlagsAt(viewIndex);
+ if (flags & MSG_VIEW_FLAG_DUMMY) {
+ view_throw("Expected index " + viewIndex + " to not be a dummy!");
+ }
+ }
+}
+
+function assert_view_level_is(index, level) {
+ if (gDBView.getLevel(index) != level) {
+ view_throw(
+ "Expected index " +
+ index +
+ " to be level " +
+ level +
+ " not " +
+ gDBView.getLevel(index)
+ );
+ }
+}
+
+/**
+ * Given a message, assert that it is present at the given indices.
+ *
+ * Usage:
+ * assert_view_message_at_indices(synMsg, 0);
+ * assert_view_message_at_indices(synMsg, 0, 1);
+ * assert_view_message_at_indices(aMsg, 0, bMsg, 1);
+ */
+function assert_view_message_at_indices(...aArgs) {
+ let curHdr;
+ for (let thing of aArgs) {
+ if (typeof thing == "number") {
+ let hdrAt = gDBView.getMsgHdrAt(thing);
+ if (curHdr != hdrAt) {
+ view_throw(
+ "Expected hdr at " +
+ thing +
+ " to be " +
+ curHdr.messageKey +
+ ":" +
+ curHdr.mime2DecodedSubject.substr(0, 30) +
+ " not " +
+ hdrAt.messageKey +
+ ":" +
+ hdrAt.mime2DecodedSubject.substr(0, 30)
+ );
+ }
+ } else {
+ // synthetic message, get the header...
+ curHdr = gTestFolder.msgDatabase.getMsgHdrForMessageID(thing.messageId);
+ }
+ }
+}
+
+var authorFirstLetterCustomColumn = {
+ getCellText(row, col) {
+ let msgHdr = this.dbView.getMsgHdrAt(row);
+ return msgHdr.mime2DecodedAuthor.charAt(0).toUpperCase() || "?";
+ },
+ getSortStringForRow(msgHdr) {
+ // charAt(0) is a quote, charAt(1) is the first letter!
+ return msgHdr.mime2DecodedAuthor.charAt(1).toUpperCase() || "?";
+ },
+ isString() {
+ return true;
+ },
+
+ getCellProperties(row, col) {
+ return "";
+ },
+ getRowProperties(row) {
+ return "";
+ },
+ getImageSrc(row, col) {
+ return null;
+ },
+ getSortLongForRow(hdr) {
+ return 0;
+ },
+};
+
+var gDBView;
+var gTreeView;
+
+var MSG_VIEW_FLAG_DUMMY = 0x20000000;
+
+var gFakeSelection = new TreeSelection(null);
+
+function setup_view(aViewType, aViewFlags, aTestFolder) {
+ let dbviewContractId = "@mozilla.org/messenger/msgdbview;1?type=" + aViewType;
+
+ if (aTestFolder == null) {
+ aTestFolder = gTestFolder;
+ }
+
+ // always start out fully expanded
+ aViewFlags |= Ci.nsMsgViewFlagsType.kExpandAll;
+
+ gDBView = Cc[dbviewContractId].createInstance(Ci.nsIMsgDBView);
+ gDBView.init(null, null, null);
+ var outCount = {};
+ gDBView.open(
+ aViewType != "search" ? aTestFolder : null,
+ Ci.nsMsgViewSortType.byDate,
+ aViewType != "search"
+ ? Ci.nsMsgViewSortOrder.ascending
+ : Ci.nsMsgViewSortOrder.descending,
+ aViewFlags,
+ outCount
+ );
+ // outCount is 0 if byCustom; view is built by addColumnHandler()
+ dump(" View Out Count: " + outCount.value + "\n");
+
+ // we need to cram messages into the search via nsIMsgSearchNotify interface
+ if (
+ aViewType == "search" ||
+ aViewType == "quicksearch" ||
+ aViewType == "xfvf"
+ ) {
+ let searchNotify = gDBView.QueryInterface(Ci.nsIMsgSearchNotify);
+ searchNotify.onNewSearch();
+ for (let msgHdr of aTestFolder.msgDatabase.enumerateMessages()) {
+ searchNotify.onSearchHit(msgHdr, msgHdr.folder);
+ }
+ searchNotify.onSearchDone(Cr.NS_OK);
+ }
+
+ gDBView.addColumnHandler(
+ "authorFirstLetterCol",
+ authorFirstLetterCustomColumn
+ );
+ // XXX this sets the custom column to use for sorting by the custom column.
+ // It has been argued (and is generally accepted) that this should not be
+ // so limited.
+ gDBView.curCustomColumn = "authorFirstLetterCol";
+
+ gTreeView = gDBView.QueryInterface(Ci.nsITreeView);
+ gTreeView.selection = gFakeSelection;
+ gFakeSelection.view = gTreeView;
+}
+
+function setup_group_view(aSortType, aSortOrder, aTestFolder) {
+ let dbviewContractId = "@mozilla.org/messenger/msgdbview;1?type=group";
+
+ if (aTestFolder == null) {
+ aTestFolder = gTestFolder;
+ }
+
+ // grouped view uses these flags
+ let viewFlags =
+ Ci.nsMsgViewFlagsType.kGroupBySort |
+ Ci.nsMsgViewFlagsType.kExpandAll |
+ Ci.nsMsgViewFlagsType.kThreadedDisplay;
+
+ gDBView = Cc[dbviewContractId].createInstance(Ci.nsIMsgDBView);
+ gDBView.init(null, null, null);
+ var outCount = {};
+ gDBView.open(aTestFolder, aSortType, aSortOrder, viewFlags, outCount);
+
+ gDBView.addColumnHandler(
+ "authorFirstLetterCol",
+ authorFirstLetterCustomColumn
+ );
+ gDBView.curCustomColumn = "authorFirstLetterCol";
+
+ gTreeView = gDBView.QueryInterface(Ci.nsITreeView);
+ gFakeSelection.view = gTreeView;
+ gTreeView.selection = gFakeSelection;
+}
+
+/**
+ * Comparison func for built-in types (including strings, so no subtraction.)
+ */
+function generalCmp(a, b) {
+ if (a < b) {
+ return -1;
+ } else if (a > b) {
+ return 1;
+ }
+ return 0;
+}
+
+/**
+ * Check that sort order and grouping logic (if applicable) are doing the right
+ * thing.
+ *
+ * In the case of groups (indicated by dummy headers), we want to ignore the
+ * dummies and 1) make sure all the values in the group have the same value,
+ * 2) verify that the headers meet our total ordering.
+ * In the case of threads, we want to ensure that each level of the hierarchy
+ * meets our ordering demands, recursing into children. Because the tree
+ * representation is rather quite horrible, the easiest thing for us is to
+ * track a per-level list of comparison values we have seen, nuking older
+ * values when changes in levels indicate closure of a level. (Namely,
+ * if we see a node at level N, then all levels >N are no longer valid.)
+ *
+ * @param {nsMsgViewType} aSortBy - The sort type.
+ * @param {nsMsgViewSortOrder} aDirection - The sort direction.
+ * @param {string|Function} aKeyOrValueGetter - A string naming the attribute on
+ * the message headerto retrieve, or if that is not sufficient a function that
+ * takes a message header and returns the sort value for it.
+ * @param {Function} [aGetGroupValue] - An optional function that takes a
+ * message header and returns the grouping value for the header.
+ * If omitted, it is assumed that the sort value is the grouping value.
+ */
+function ensure_view_ordering(
+ aSortBy,
+ aDirection,
+ aKeyOrValueGetter,
+ aGetGroupValue
+) {
+ if (!gTreeView.rowCount) {
+ do_throw("There are no rows in my folder! I can't test anything!");
+ }
+ dump(
+ " Ensuring sort order for " +
+ aSortBy +
+ " (Row count: " +
+ gTreeView.rowCount +
+ ")\n"
+ );
+ dump(" cur view flags: " + gDBView.viewFlags + "\n");
+
+ // standard grouping doesn't re-group when you sort. so we need to actually
+ // re-initialize the view.
+ // but search mode is special and does the right thing because asuth didn't
+ // realize that it shouldn't do the right thing, so it can just change the
+ // sort. (of course, under the hood, it is actually creating a new view...)
+ if (
+ gDBView.viewFlags & Ci.nsMsgViewFlagsType.kGroupBySort &&
+ gDBView.viewType != Ci.nsMsgViewType.eShowSearch
+ ) {
+ // we must close to re-open (or we could just use a new view)
+ let msgFolder = gDBView.msgFolder;
+ gDBView.close();
+ gDBView.open(msgFolder, aSortBy, aDirection, gDBView.viewFlags, {});
+ } else {
+ gDBView.sort(aSortBy, aDirection);
+ }
+
+ let comparisonValuesByLevel = [];
+ let expectedLevel0CmpResult =
+ aDirection == Ci.nsMsgViewSortOrder.ascending ? 1 : -1;
+ let comparator = generalCmp;
+
+ let dummyCount = 0,
+ emptyDummyCount = 0;
+
+ let valueGetter =
+ typeof aKeyOrValueGetter == "string"
+ ? function (msgHdr) {
+ return msgHdr[aKeyOrValueGetter];
+ }
+ : aKeyOrValueGetter;
+ let groupValueGetter = aGetGroupValue || valueGetter;
+
+ // don't do group testing until we see a dummy header (which we will see
+ // before we see any grouped headers, so it's fine to do this)
+ let inGroup = false;
+ // the current grouping value for the current group. this allows us to
+ // detect erroneous grouping of different group values together.
+ let curGroupValue = null;
+ // the set of group values observed before the current group. this allows
+ // us to detect improper grouping where there are multiple groups with the
+ // same grouping value.
+ let previouslySeenGroupValues = {};
+
+ for (let iViewIndex = 0; iViewIndex < gTreeView.rowCount; iViewIndex++) {
+ let msgHdr = gDBView.getMsgHdrAt(iViewIndex);
+ let msgViewFlags = gDBView.getFlagsAt(iViewIndex);
+
+ // ignore dummy headers; testing grouping logic happens elsewhere
+ if (msgViewFlags & MSG_VIEW_FLAG_DUMMY) {
+ if (dummyCount && curGroupValue == null) {
+ emptyDummyCount++;
+ }
+ dummyCount++;
+ if (curGroupValue != null) {
+ previouslySeenGroupValues[curGroupValue] = true;
+ }
+ curGroupValue = null;
+ inGroup = true;
+ continue;
+ }
+
+ // level is 0-based
+ let level = gTreeView.getLevel(iViewIndex);
+ // nuke existing comparison levels
+ if (level < comparisonValuesByLevel.length - 1) {
+ comparisonValuesByLevel.splice(level);
+ }
+
+ // get the value for comparison
+ let curValue = valueGetter(msgHdr);
+ if (inGroup) {
+ let groupValue = groupValueGetter(msgHdr);
+ if (groupValue in previouslySeenGroupValues) {
+ do_throw(`Group value ${groupValue} observed in more than one group!`);
+ }
+ if (curGroupValue == null) {
+ curGroupValue = groupValue;
+ } else if (curGroupValue != groupValue) {
+ do_throw(
+ "Inconsistent grouping! " + groupValue + " != " + curGroupValue
+ );
+ }
+ }
+
+ // is this level new to our comparisons? then track it...
+ if (level >= comparisonValuesByLevel.length) {
+ // null-fill any gaps (due to, say, dummy nodes)
+ while (comparisonValuesByLevel.length <= level) {
+ comparisonValuesByLevel.push(null);
+ }
+ comparisonValuesByLevel.push(curValue);
+ } else {
+ // otherwise compare it
+ let prevValue = comparisonValuesByLevel[level - 1];
+ let cmpResult = comparator(curValue, prevValue);
+ let expectedCmpResult = level > 0 ? 1 : expectedLevel0CmpResult;
+ if (cmpResult && cmpResult != expectedCmpResult) {
+ do_throw(
+ "Ordering failure on key " +
+ msgHdr.messageKey +
+ ". " +
+ curValue +
+ " should have been " +
+ (expectedCmpResult == 1 ? ">=" : "<=") +
+ " " +
+ prevValue +
+ " but was not."
+ );
+ }
+ }
+ }
+
+ if (inGroup && curGroupValue == null) {
+ emptyDummyCount++;
+ }
+ if (dummyCount) {
+ dump(
+ " saw " +
+ dummyCount +
+ " dummy headers (" +
+ emptyDummyCount +
+ " empty).\n"
+ );
+ }
+}
+
+/**
+ * Test sorting functionality.
+ */
+function test_sort_columns() {
+ ensure_view_ordering(
+ Ci.nsMsgViewSortType.byDate,
+ Ci.nsMsgViewSortOrder.descending,
+ "date",
+ function getDateAgeBucket(msgHdr) {
+ // so, this is a cop-out, but we know that the date age bucket for our
+ // generated messages is always more than 2-weeks ago!
+ return 5;
+ }
+ );
+ ensure_view_ordering(
+ Ci.nsMsgViewSortType.byDate,
+ Ci.nsMsgViewSortOrder.ascending,
+ "date",
+ function getDateAgeBucket(msgHdr) {
+ // so, this is a cop-out, but we know that the date age bucket for our
+ // generated messages is always more than 2-weeks ago!
+ return 5;
+ }
+ );
+ // (note, subject doesn't use dummy groups and so won't have grouping tested)
+ ensure_view_ordering(
+ Ci.nsMsgViewSortType.bySubject,
+ Ci.nsMsgViewSortOrder.ascending,
+ "mime2DecodedSubject"
+ );
+ ensure_view_ordering(
+ Ci.nsMsgViewSortType.byAuthor,
+ Ci.nsMsgViewSortOrder.ascending,
+ "mime2DecodedAuthor"
+ );
+ // Id
+ // Thread
+ // Priority
+ // Status
+ // Size
+ // Flagged
+ // Unread
+ ensure_view_ordering(
+ Ci.nsMsgViewSortType.byRecipient,
+ Ci.nsMsgViewSortOrder.ascending,
+ "mime2DecodedRecipients"
+ );
+ // Location
+ // Tags
+ // JunkStatus
+ // Attachments
+ // Account
+ // Custom
+ ensure_view_ordering(
+ Ci.nsMsgViewSortType.byCustom,
+ Ci.nsMsgViewSortOrder.ascending,
+ function (msgHdr) {
+ return authorFirstLetterCustomColumn.getSortStringForRow(msgHdr);
+ }
+ );
+ // Received
+}
+
+function test_number_of_messages() {
+ // Bug 574799
+ if (gDBView.numMsgsInView != gTestFolder.getTotalMessages(false)) {
+ do_throw(
+ "numMsgsInView is " +
+ gDBView.numMsgsInView +
+ " but should be " +
+ gTestFolder.getTotalMessages(false) +
+ "\n"
+ );
+ }
+ // Bug 600140
+ // Maybe elided so open it, now only consider the first one
+ if (gDBView.isContainer(0) && !gDBView.isContainerOpen(0)) {
+ gDBView.toggleOpenState(0);
+ }
+ let numMsgInTree = gTreeView.rowCount;
+ if (gDBView.viewFlags & Ci.nsMsgViewFlagsType.kGroupBySort) {
+ for (let iViewIndex = 0; iViewIndex < gTreeView.rowCount; iViewIndex++) {
+ let flags = gDBView.getFlagsAt(iViewIndex);
+ if (flags & MSG_VIEW_FLAG_DUMMY) {
+ numMsgInTree--;
+ }
+ }
+ }
+ if (gDBView.numMsgsInView != numMsgInTree) {
+ view_throw(
+ "message in tree is " +
+ numMsgInTree +
+ " but should be " +
+ gDBView.numMsgsInView +
+ "\n"
+ );
+ }
+}
+
+function test_selected_messages() {
+ gDBView.doCommand(Ci.nsMsgViewCommandType.expandAll);
+
+ // Select one message
+ gTreeView.selection.select(1);
+ let selectedMessages = gDBView.getSelectedMsgHdrs();
+
+ if (selectedMessages.length != 1) {
+ do_throw(
+ "getSelectedMsgHdrs.length is " +
+ selectedMessages.length +
+ " but should be 1\n"
+ );
+ }
+
+ let firstSelectedMsg = gDBView.hdrForFirstSelectedMessage;
+ if (selectedMessages[0] != firstSelectedMsg) {
+ do_throw(
+ "getSelectedMsgHdrs[0] is " +
+ selectedMessages[0].messageKey +
+ " but should be " +
+ firstSelectedMsg.messageKey +
+ "\n"
+ );
+ }
+
+ // Select all messages
+ gTreeView.selection.selectAll();
+ if (gDBView.numSelected != gTreeView.rowCount) {
+ do_throw(
+ "numSelected is " +
+ gDBView.numSelected +
+ " but should be " +
+ gTreeView.rowCount +
+ "\n"
+ );
+ }
+
+ selectedMessages = gDBView.getSelectedMsgHdrs();
+ if (selectedMessages.length != gTestFolder.getTotalMessages(false)) {
+ do_throw(
+ "getSelectedMsgHdrs.length is " +
+ selectedMessages.length +
+ " but should be " +
+ gTestFolder.getTotalMessages(false) +
+ "\n"
+ );
+ }
+
+ for (let i = 0; i < selectedMessages.length; i++) {
+ let expectedHdr = gDBView.getMsgHdrAt(i);
+ if (!selectedMessages.includes(expectedHdr)) {
+ view_throw(
+ "Expected " +
+ expectedHdr.messageKey +
+ ":" +
+ expectedHdr.mime2DecodedSubject.substr(0, 30) +
+ " to be selected, but it wasn't\n"
+ );
+ }
+ }
+
+ gTreeView.selection.clearSelection();
+}
+
+function test_insert_remove_view_rows() {
+ // Test insertion/removal into m_keys.
+ let startCount = gTreeView.rowCount;
+ let index = 0;
+ let rows = 3;
+ let msgKey = rows * 1000;
+ let flags = 0;
+ let level = 0;
+ let folder = null;
+ let xfview =
+ gDBView.viewType == Ci.nsMsgViewType.eShowSearch ||
+ gDBView.viewType == Ci.nsMsgViewType.eShowVirtualFolderResults;
+ if (xfview) {
+ folder = gDBView.getFolderForViewIndex(index);
+ }
+
+ gDBView.insertTreeRows(index, rows, msgKey, flags, level, folder);
+ assert_view_row_count(startCount + rows);
+ let key = gDBView.getKeyAt(rows - 1);
+ if (key != msgKey) {
+ view_throw("msgKey is " + key + " but should be " + msgKey + "\n");
+ }
+ gDBView.removeTreeRows(index, rows);
+ assert_view_row_count(startCount);
+
+ // These should fail.
+ try {
+ gDBView.insertTreeRows(startCount + 10, rows, msgKey, flags, level, folder);
+ view_throw("expected exception not caught; inserting at illegal index \n");
+ } catch (ex) {}
+ try {
+ gDBView.insertTreeRows(index, rows, msgKey, flags, level, folder);
+ gDBView.removeTreeRows(index, gTreeView.rowCount + 10);
+ view_throw("expected exception not caught; removing illegal rows \n");
+ } catch (ex) {
+ gDBView.removeTreeRows(index, rows);
+ }
+ if (xfview) {
+ try {
+ gDBView.insertTreeRows(index, rows, msgKey, flags, level, null);
+ view_throw(
+ "expected exception not caught; folder required for xfvf view \n"
+ );
+ } catch (ex) {}
+ }
+}
+
+async function test_msg_added_to_search_view() {
+ // if the view is a non-grouped search view, test adding a header to
+ // the search results, and verify it gets put at top.
+ if (!(gDBView.viewFlags & Ci.nsMsgViewFlagsType.kGroupBySort)) {
+ gDBView.sort(Ci.nsMsgViewSortType.byDate, Ci.nsMsgViewSortOrder.descending);
+ let [synMsg] = await make_and_add_message();
+ let msgHdr = gTestFolder.msgDatabase.getMsgHdrForMessageID(
+ synMsg.messageId
+ );
+ gDBView
+ .QueryInterface(Ci.nsIMsgSearchNotify)
+ .onSearchHit(msgHdr, msgHdr.folder);
+ assert_view_message_at_indices(synMsg, 0);
+ }
+}
+
+function IsHdrChildOf(possibleParent, possibleChild) {
+ let parentHdrId = possibleParent.messageId;
+ let numRefs = possibleChild.numReferences;
+ for (let refIndex = 0; refIndex < numRefs; refIndex++) {
+ if (parentHdrId == possibleChild.getStringReference(refIndex)) {
+ return true;
+ }
+ }
+ return false;
+}
+
+// This could be part of ensure_view_ordering() but I don't want to make that
+// function any harder to read.
+function test_threading_levels() {
+ if (!gTreeView.rowCount) {
+ do_throw("There are no rows in my folder! I can't test anything!");
+ }
+ // only look at threaded, non-grouped views.
+ if (
+ gDBView.viewFlags & Ci.nsMsgViewFlagsType.kGroupBySort ||
+ !(gDBView.viewFlags & Ci.nsMsgViewFlagsType.kThreadedDisplay)
+ ) {
+ return;
+ }
+
+ let prevLevel = 1;
+ let prevMsgHdr;
+ for (let iViewIndex = 0; iViewIndex < gTreeView.rowCount; iViewIndex++) {
+ let msgHdr = gDBView.getMsgHdrAt(iViewIndex);
+ let level = gTreeView.getLevel(iViewIndex);
+ if (level > prevLevel && msgHdr.subject != gSiblingsMissingParentsSubject) {
+ if (!IsHdrChildOf(prevMsgHdr, msgHdr)) {
+ view_throw("indented message not child of parent");
+ }
+ }
+ prevLevel = level;
+ prevMsgHdr = msgHdr;
+ }
+}
+
+function test_expand_collapse() {
+ let oldRowCount = gDBView.rowCount;
+ let thirdChild = gDBView.getMsgHdrAt(3);
+ gDBView.toggleOpenState(0);
+ if (gDBView.rowCount != oldRowCount - 9) {
+ view_throw("collapsing first item should have removed 9 items");
+ }
+
+ // test that expand/collapse works with killed sub-thread.
+ oldRowCount = gDBView.rowCount;
+ gTestFolder.msgDatabase.markHeaderKilled(thirdChild, true, null);
+ gDBView.toggleOpenState(0);
+ if (gDBView.rowCount != oldRowCount + 2) {
+ view_throw("expanding first item should have aded 2 items");
+ }
+ gTestFolder.msgDatabase.markHeaderKilled(thirdChild, false, null);
+ oldRowCount = gDBView.rowCount;
+ gDBView.toggleOpenState(0);
+ if (gDBView.rowCount != oldRowCount - 2) {
+ view_throw("collapsing first item should have removed 2 items");
+ }
+}
+
+function test_qs_results() {
+ // This just tests that bug 505967 hasn't regressed.
+ if (gTreeView.getLevel(0) != 0) {
+ view_throw("first message should be at level 0");
+ }
+ if (gTreeView.getLevel(1) != 1) {
+ view_throw("second message should be at level 1");
+ }
+ if (gTreeView.getLevel(2) != 2) {
+ view_throw("third message should be at level 2");
+ }
+ test_threading_levels();
+}
+
+async function test_group_sort_collapseAll_expandAll_threading() {
+ // - start with an empty folder
+ gTestFolder = await messageInjection.makeEmptyFolder();
+
+ // - create a normal unthreaded view
+ setup_view("threaded", 0);
+
+ // - ensure it's empty
+ assert_view_empty();
+
+ // - add 3 messages:
+ // msg1: from A, custom column val A, to be starred
+ // msg2: from A, custom column val A
+ // msg3: from B, custom column val B
+ let [smsg1] = await make_and_add_message({ from: ["A", "A@a.invalid"] });
+ await make_and_add_message({ from: ["A", "A@a.invalid"] });
+ let [smsg3] = await make_and_add_message({ from: ["B", "B@b.invalid"] });
+
+ assert_view_row_count(3);
+ gDBView.getMsgHdrAt(0).markFlagged(true);
+ if (!gDBView.getMsgHdrAt(0).isFlagged) {
+ view_throw("Expected smsg1 to be flagged");
+ }
+
+ // - create grouped view; open folder in byFlagged AZ sort
+ setup_group_view(
+ Ci.nsMsgViewSortType.byFlagged,
+ Ci.nsMsgViewSortOrder.ascending,
+ gTestFolder
+ );
+ // - make sure there are 5 rows; index 0 and 2 are dummy, 1 is flagged message,
+ // 3-4 are messages
+ assert_view_row_count(5);
+ assert_view_index_is_dummy(0);
+ assert_view_index_is_not_dummy(1);
+ assert_view_message_at_indices(smsg1, 1);
+ if (!gDBView.getMsgHdrAt(1).isFlagged) {
+ view_throw("Expected grouped smsg1 to be flagged");
+ }
+ assert_view_index_is_dummy(2);
+ assert_view_index_is_not_dummy(3);
+ assert_view_index_is_not_dummy(4);
+
+ // - collapse the grouped threads; there should be 2 dummy rows
+ gDBView.doCommand(Ci.nsMsgViewCommandType.collapseAll);
+ assert_view_row_count(2);
+ assert_view_index_is_dummy(0);
+ assert_view_index_is_dummy(1);
+
+ // - expand the grouped threads; there should be 5 rows
+ gDBView.doCommand(Ci.nsMsgViewCommandType.expandAll);
+ assert_view_row_count(5);
+ assert_view_index_is_dummy(0);
+ assert_view_index_is_dummy(2);
+
+ // - reverse sort; create grouped view; open folder in byFlagged ZA sort
+ setup_group_view(
+ Ci.nsMsgViewSortType.byFlagged,
+ Ci.nsMsgViewSortOrder.descending,
+ gTestFolder
+ );
+ // - make sure there are 5 rows; index 0 and 3 are dummy, 1-2 are messages,
+ // 4 is flagged message
+ assert_view_row_count(5);
+ assert_view_index_is_dummy(0);
+ assert_view_index_is_not_dummy(1);
+ assert_view_index_is_not_dummy(2);
+ assert_view_index_is_dummy(3);
+ assert_view_index_is_not_dummy(4);
+ assert_view_message_at_indices(smsg1, 4);
+ if (!gDBView.getMsgHdrAt(4).isFlagged) {
+ view_throw("Expected reverse sorted grouped smsg1 to be flagged");
+ }
+
+ // - test grouped by custom column; the custCol is first letter of author
+ // - create grouped view; open folder in byCustom ZA sort
+ setup_group_view(
+ Ci.nsMsgViewSortType.byCustom,
+ Ci.nsMsgViewSortOrder.descending,
+ gTestFolder
+ );
+
+ // - make sure there are 5 rows; index 0 and 2 are dummy, 1 is B value message,
+ // 3-4 are messages with A value
+ assert_view_row_count(5);
+ assert_view_index_is_dummy(0);
+ assert_view_index_is_not_dummy(1);
+ assert_view_message_at_indices(smsg3, 1);
+ if (
+ authorFirstLetterCustomColumn.getSortStringForRow(gDBView.getMsgHdrAt(1)) !=
+ "B"
+ ) {
+ view_throw(
+ "Expected grouped by custom column, ZA sortOrder smsg3 value to be B"
+ );
+ }
+ assert_view_index_is_dummy(2);
+ assert_view_index_is_not_dummy(3);
+ assert_view_index_is_not_dummy(4);
+ if (
+ authorFirstLetterCustomColumn.getSortStringForRow(gDBView.getMsgHdrAt(4)) !=
+ "A"
+ ) {
+ view_throw(
+ "Expected grouped by custom column, ZA sortOrder smsg2 value to be A"
+ );
+ }
+}
+
+async function test_group_dummies_under_mutation_by_date() {
+ // - start with an empty folder
+ gTestFolder = await messageInjection.makeEmptyFolder();
+
+ // - create the view
+ setup_view("group", Ci.nsMsgViewFlagsType.kGroupBySort);
+ gDBView.sort(Ci.nsMsgViewSortType.byDate, Ci.nsMsgViewSortOrder.ascending);
+
+ // - ensure it's empty
+ assert_view_empty();
+
+ // - add a message from this week
+ // (we want to make sure all the messages end up in the same bucket and that
+ // the current day changing as we run the test does not change buckets
+ // either. bucket 1 is same day, bucket 2 is yesterday, bucket 3 is last
+ // week, so 2 days ago or older is always last week, even if we roll over
+ // and it becomes 3 days ago.)
+ let [smsg, synSet] = await make_and_add_message({
+ age: { days: 2, hours: 1 },
+ });
+
+ // - make sure the message and a dummy appear
+ assert_view_row_count(2);
+ assert_view_index_is_dummy(0);
+ assert_view_index_is_not_dummy(1);
+ assert_view_message_at_indices(smsg, 0, 1);
+
+ // we used to display total in tag column - make sure we don't do that.
+ if (gDBView.cellTextForColumn(0, "tags") != "") {
+ view_throw("tag column shouldn't display total count in group view");
+ }
+
+ // - move the messages to the trash
+ await messageInjection.trashMessages(synSet);
+
+ // - make sure the message and dummy disappear
+ assert_view_empty();
+
+ // - add two messages from this week (same date bucket concerns)
+ let [newer, newerSet] = await make_and_add_message({
+ age: { days: 2, hours: 1 },
+ });
+ let [older] = await make_and_add_message({ age: { days: 2, hours: 2 } });
+
+ // - sanity check addition
+ assert_view_row_count(3); // 2 messages + 1 dummy
+ assert_view_index_is_dummy(0);
+ assert_view_index_is_not_dummy(1, 2);
+ // the dummy should be based off the older guy
+ assert_view_message_at_indices(older, 0, 1);
+ assert_view_message_at_indices(newer, 2);
+
+ // - delete the message right under the dummy
+ // (this will be the newer one)
+ await messageInjection.trashMessages(newerSet);
+
+ // - ensure we still have the dummy and the right child node
+ assert_view_row_count(2);
+ assert_view_index_is_dummy(0);
+ assert_view_index_is_not_dummy(1);
+ // now the dummy should be based off the remaining older one
+ assert_view_message_at_indices(older, 0, 1);
+}
+
+async function test_xfvf_threading() {
+ // - start with an empty folder
+ let save_gTestFolder = gTestFolder;
+ gTestFolder = await messageInjection.makeEmptyFolder();
+
+ let messages = [];
+ // Add messages such that ancestors arrive after their descendents in
+ // various interesting ways.
+ // build a hierarchy like this (the UID order corresponds to the date order)
+ // 3
+ // 1
+ // 4
+ // 2
+ // 5
+ let msg3 = gMessageGenerator.makeMessage({ age: { days: 2, hours: 5 } });
+ let msg1 = gMessageGenerator.makeMessage({
+ age: { days: 2, hours: 4 },
+ inReplyTo: msg3,
+ });
+ let msg4 = gMessageGenerator.makeMessage({
+ age: { days: 2, hours: 3 },
+ inReplyTo: msg1,
+ });
+ let msg2 = gMessageGenerator.makeMessage({
+ age: { days: 2, hours: 1 },
+ inReplyTo: msg4,
+ });
+ let msg5 = gMessageGenerator.makeMessage({
+ age: { days: 2, hours: 2 },
+ inReplyTo: msg1,
+ });
+ messages = messages.concat([msg1, msg2, msg3, msg4, msg5]);
+
+ let msgSet = new SyntheticMessageSet(messages);
+
+ gTestFolder = await messageInjection.makeEmptyFolder();
+
+ // - create the view
+ await messageInjection.addSetsToFolders([gTestFolder], [msgSet]);
+ setup_view("xfvf", Ci.nsMsgViewFlagsType.kThreadedDisplay);
+ assert_view_row_count(5);
+ gDBView.toggleOpenState(0);
+ gDBView.toggleOpenState(0);
+
+ assert_view_message_at_indices(msg3, 0);
+ assert_view_message_at_indices(msg1, 1);
+ assert_view_message_at_indices(msg4, 2);
+ assert_view_message_at_indices(msg2, 3);
+ assert_view_message_at_indices(msg5, 4);
+ assert_view_level_is(0, 0);
+ assert_view_level_is(1, 1);
+ assert_view_level_is(2, 2);
+ assert_view_level_is(3, 3);
+ assert_view_level_is(4, 2);
+ gTestFolder = save_gTestFolder;
+}
+
+/*
+ * Tests the sorting order of collapsed threads, not of messages within
+ * threads. Currently limited to testing the sort-threads-by-date case,
+ * sorting both by thread root and by newest message.
+ */
+async function test_thread_sorting() {
+ let save_gTestFolder = gTestFolder;
+ gTestFolder = await messageInjection.makeEmptyFolder();
+ let messages = [];
+ // build a hierarchy like this (the UID order corresponds to the date order)
+ // 1
+ // 4
+ // 2
+ // 5
+ // 3
+ let msg1 = gMessageGenerator.makeMessage({ age: { days: 1, hours: 10 } });
+ let msg2 = gMessageGenerator.makeMessage({ age: { days: 1, hours: 9 } });
+ let msg3 = gMessageGenerator.makeMessage({ age: { days: 1, hours: 8 } });
+ let msg4 = gMessageGenerator.makeMessage({
+ age: { days: 1, hours: 7 },
+ inReplyTo: msg1,
+ });
+ let msg5 = gMessageGenerator.makeMessage({
+ age: { days: 1, hours: 6 },
+ inReplyTo: msg2,
+ });
+ messages = messages.concat([msg1, msg2, msg3, msg4, msg5]);
+
+ let msgSet = new SyntheticMessageSet(messages);
+
+ await messageInjection.addSetsToFolders([gTestFolder], [msgSet]);
+
+ // test the non-default pref state first, so the pref gets left with its
+ // default value at the end
+ Services.prefs.setBoolPref("mailnews.sort_threads_by_root", true);
+ gDBView.open(
+ gTestFolder,
+ Ci.nsMsgViewSortType.byDate,
+ Ci.nsMsgViewSortOrder.ascending,
+ Ci.nsMsgViewFlagsType.kThreadedDisplay,
+ {}
+ );
+
+ assert_view_row_count(3);
+ assert_view_message_at_indices(msg1, 0);
+ assert_view_message_at_indices(msg2, 1);
+ assert_view_message_at_indices(msg3, 2);
+
+ gDBView.sort(Ci.nsMsgViewSortType.byDate, Ci.nsMsgViewSortOrder.descending);
+ assert_view_message_at_indices(msg3, 0);
+ assert_view_message_at_indices(msg2, 1);
+ assert_view_message_at_indices(msg1, 2);
+
+ Services.prefs.clearUserPref("mailnews.sort_threads_by_root");
+ gDBView.open(
+ gTestFolder,
+ Ci.nsMsgViewSortType.byDate,
+ Ci.nsMsgViewSortOrder.ascending,
+ Ci.nsMsgViewFlagsType.kThreadedDisplay,
+ {}
+ );
+
+ assert_view_row_count(3);
+ assert_view_message_at_indices(msg3, 0);
+ assert_view_message_at_indices(msg1, 1);
+ assert_view_message_at_indices(msg2, 2);
+
+ gDBView.sort(Ci.nsMsgViewSortType.byDate, Ci.nsMsgViewSortOrder.descending);
+ assert_view_message_at_indices(msg2, 0);
+ assert_view_message_at_indices(msg1, 1);
+ assert_view_message_at_indices(msg3, 2);
+
+ gDBView.close();
+ gTestFolder = save_gTestFolder;
+}
+
+const VIEW_TYPES = [
+ ["threaded", Ci.nsMsgViewFlagsType.kThreadedDisplay],
+ ["quicksearch", Ci.nsMsgViewFlagsType.kThreadedDisplay],
+ ["search", Ci.nsMsgViewFlagsType.kThreadedDisplay],
+ ["search", Ci.nsMsgViewFlagsType.kGroupBySort],
+ ["xfvf", Ci.nsMsgViewFlagsType.kNone],
+ // group does unspeakable things to gTestFolder, so put it last.
+ ["group", Ci.nsMsgViewFlagsType.kGroupBySort],
+];
+
+/**
+ * These are tests which are for every test configuration.
+ */
+function tests_for_all_views() {
+ test_sort_columns();
+ test_number_of_messages();
+ test_selected_messages();
+ test_insert_remove_view_rows();
+}
+
+add_setup(function () {
+ gMessages = setup_messages();
+});
+
+add_task(async function test_threaded() {
+ await set_gTestFolder(gMessages);
+ let [view_type, view_flag] = VIEW_TYPES[0];
+ setup_view(view_type, view_flag);
+
+ tests_for_all_views();
+
+ // Specific tests for threaded.
+ test_expand_collapse();
+ await test_thread_sorting();
+});
+
+add_task(async function test_quicksearch_threaded() {
+ await set_gTestFolder(gMessages);
+ let [view_type, view_flag] = VIEW_TYPES[1];
+ setup_view(view_type, view_flag);
+
+ tests_for_all_views();
+
+ // Specific tests for quicksearch threaded.
+ test_qs_results();
+});
+
+add_task(async function test_search_threaded() {
+ await set_gTestFolder(gMessages);
+ let [view_type, view_flag] = VIEW_TYPES[2];
+ setup_view(view_type, view_flag);
+
+ tests_for_all_views();
+
+ // Specific tests for search threaded.
+ await test_msg_added_to_search_view();
+});
+
+add_task(async function test_search_group_by_sort() {
+ await set_gTestFolder(gMessages);
+ let [view_type, view_flag] = VIEW_TYPES[3];
+ setup_view(view_type, view_flag);
+
+ tests_for_all_views();
+
+ // Specific tests for search group by sort.
+ await test_msg_added_to_search_view();
+});
+
+add_task(async function test_xfvf() {
+ await set_gTestFolder(gMessages);
+ let [view_type, view_flag] = VIEW_TYPES[4];
+ setup_view(view_type, view_flag);
+
+ tests_for_all_views();
+
+ // Specific tests for xfvf.
+ await test_xfvf_threading();
+});
+
+add_task(async function test_group() {
+ await set_gTestFolder(gMessages);
+ let [view_type, view_flag] = VIEW_TYPES[5];
+ setup_view(view_type, view_flag);
+
+ tests_for_all_views();
+
+ // Specific tests for group.
+ await test_group_sort_collapseAll_expandAll_threading;
+ await test_group_dummies_under_mutation_by_date;
+});
+
+add_task(function test_teardown() {
+ // Delete view reference to avoid a cycle leak.
+ gFakeSelection.view = null;
+});
diff --git a/comm/mailnews/base/test/unit/test_nsMsgDBView_headerValues.js b/comm/mailnews/base/test/unit/test_nsMsgDBView_headerValues.js
new file mode 100644
index 0000000000..796257a8a8
--- /dev/null
+++ b/comm/mailnews/base/test/unit/test_nsMsgDBView_headerValues.js
@@ -0,0 +1,110 @@
+/* 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/. */
+
+/**
+ * Test that nsMsgDBView properly reports the values of messages in the display.
+ */
+
+var { MessageGenerator, SyntheticMessageSet } = ChromeUtils.import(
+ "resource://testing-common/mailnews/MessageGenerator.jsm"
+);
+var { MessageInjection } = ChromeUtils.import(
+ "resource://testing-common/mailnews/MessageInjection.jsm"
+);
+
+var messageInjection = new MessageInjection({ mode: "local" });
+
+// This is an array of the actual test data. Each test datum is an array of two
+// elements: the first element is the argument into a simple message generator,
+// and the second element is a map of column names to expected values when
+// requesting the cell text for a given column name.
+var tests = [
+ [{ from: "John Doe <db@tinderbox.invalid>" }, { senderCol: "John Doe" }],
+ [{ from: '"Doe, John" <db@tinderbox.invalid>' }, { senderCol: "Doe, John" }],
+ // Multiple senders are indicated with 'et al.' suffix.
+ [
+ { from: "John Doe <db@tinderbox.invalid>, Sally Ann <db@null.invalid>" },
+ { senderCol: "John Doe et al." },
+ ],
+ [
+ { from: "=?UTF-8?Q?David_H=C3=A5s=C3=A4ther?= <db@null.invalid>" },
+ { senderCol: "David Håsäther" },
+ ],
+ [
+ { from: "=?UTF-8?Q?H=C3=A5s=C3=A4ther=2C_David?= <db@null.invalid>" },
+ { senderCol: "Håsäther, David" },
+ ],
+ [
+ { from: '"Håsäther, David" <db@null.invalid>' },
+ { senderCol: "Håsäther, David" },
+ ],
+ [
+ { from: "David Håsäther <db@null.invalid>" },
+ { senderCol: "David Håsäther" },
+ ],
+ [
+ {
+ from: "\xC2\xAB\xCE\xA0\xCE\x9F\xCE\x9B\xCE\x99\xCE\xA4\xCE\x97\xCE\xA3\xC2\xBB",
+ },
+ { senderCol: "«ΠΟΛΙΤΗΣ»" },
+ ],
+ [
+ {
+ from: "John Doe \xF5 <db@null.invalid>",
+ clobberHeaders: { "Content-type": "text/plain; charset=ISO-8859-1" },
+ },
+ { senderCol: "John Doe õ" },
+ ],
+ [
+ {
+ from: "John Doe \xF5 <db@null.invalid>",
+ clobberHeaders: { "Content-type": "text/plain; charset=ISO-8859-2" },
+ },
+ { senderCol: "John Doe ő" },
+ ],
+ [
+ {
+ from: "=?UTF-8?Q?H=C3=A5s=C3=A4ther=2C_David?= <db@null.invalid>",
+ clobberHeaders: { "Content-type": "text/plain; charset=ISO-8859-2" },
+ },
+ { senderCol: "Håsäther, David" },
+ ],
+];
+
+add_task(async function test_nsMsgDBView_headValues() {
+ // Add the messages to the folder
+ let msgGenerator = new MessageGenerator();
+ let genMessages = tests.map(data => msgGenerator.makeMessage(data[0]));
+ let folder = await messageInjection.makeEmptyFolder();
+ await messageInjection.addSetsToFolders(
+ [folder],
+ [new SyntheticMessageSet(genMessages)]
+ );
+
+ // Make the DB view
+ let dbviewContractId = "@mozilla.org/messenger/msgdbview;1?type=threaded";
+ let dbView = Cc[dbviewContractId].createInstance(Ci.nsIMsgDBView);
+ dbView.init(null, null, null);
+ let outCount = {};
+ dbView.open(
+ folder,
+ Ci.nsMsgViewSortType.byDate,
+ Ci.nsMsgViewSortOrder.ascending,
+ 0,
+ outCount
+ );
+
+ // Did we add all the messages properly?
+ let treeView = dbView.QueryInterface(Ci.nsITreeView);
+ Assert.equal(treeView.rowCount, tests.length);
+
+ // For each test, make sure that the display is correct.
+ tests.forEach(function (data, i) {
+ info("Checking data for " + uneval(data));
+ let expected = data[1];
+ for (let column in expected) {
+ Assert.equal(dbView.cellTextForColumn(i, column), expected[column]);
+ }
+ });
+});
diff --git a/comm/mailnews/base/test/unit/test_nsMsgMailSession_Alerts.js b/comm/mailnews/base/test/unit/test_nsMsgMailSession_Alerts.js
new file mode 100644
index 0000000000..61c1cf3fe9
--- /dev/null
+++ b/comm/mailnews/base/test/unit/test_nsMsgMailSession_Alerts.js
@@ -0,0 +1,217 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/*
+ * Test suite for nsMsgMailSession functions relating to alerts and their
+ * listeners.
+ */
+
+var { MailServices } = ChromeUtils.import(
+ "resource:///modules/MailServices.jsm"
+);
+
+/* import-globals-from ../../../test/resources/alertTestUtils.js */
+load("../../../resources/alertTestUtils.js");
+
+var gDialogTitle = null;
+var gText = null;
+
+function reset() {
+ gDialogTitle = null;
+ gText = null;
+}
+
+/* exported alert */
+// Used in alertTestUtils.
+function alert(aDialogTitle, aText) {
+ Assert.equal(gDialogTitle, null);
+ Assert.equal(gText, null);
+
+ gDialogTitle = aDialogTitle;
+ gText = aText;
+}
+
+var msgWindow = {
+ get promptDialog() {
+ return alertUtilsPrompts;
+ },
+
+ QueryInterface: ChromeUtils.generateQI(["nsIMsgWindow"]),
+};
+
+var msgUrl = {
+ _msgWindow: null,
+
+ get msgWindow() {
+ return this._msgWindow;
+ },
+
+ QueryInterface: ChromeUtils.generateQI(["nsIMsgMailNewsUrl"]),
+};
+
+function alertListener() {}
+
+alertListener.prototype = {
+ mReturn: false,
+ mMessage: null,
+ mMsgWindow: null,
+
+ reset() {
+ this.mMessage = null;
+ this.mMsgWindow = null;
+ },
+
+ onAlert(aMessage, aMsgWindow) {
+ Assert.equal(this.mMessage, null);
+ Assert.equal(this.mMsgWindow, null);
+
+ this.mMessage = aMessage;
+ this.mMsgWindow = aMsgWindow;
+
+ return this.mReturn;
+ },
+ QueryInferface: ChromeUtils.generateQI([Ci.nsIMsgMailNewsUrl]),
+};
+
+function run_test() {
+ // Test - No listeners, check alert tries to alert the user.
+
+ reset();
+
+ msgUrl._msgWindow = msgWindow;
+
+ MailServices.mailSession.alertUser("test message", msgUrl);
+
+ // The dialog title doesn't get set at the moment.
+ Assert.equal(gDialogTitle, null);
+ Assert.equal(gText, "test message");
+
+ // Test - No listeners and no msgWindow, check no alerts.
+
+ reset();
+
+ msgUrl._msgWindow = null;
+
+ MailServices.mailSession.alertUser("test no message", msgUrl);
+
+ // The dialog title doesn't get set at the moment.
+ Assert.equal(gDialogTitle, null);
+ Assert.equal(gText, null);
+
+ // Test - One listener, returning false (prompt should still happen).
+
+ reset();
+
+ var listener1 = new alertListener();
+ listener1.mReturn = false;
+
+ MailServices.mailSession.addUserFeedbackListener(listener1);
+
+ msgUrl._msgWindow = msgWindow;
+
+ MailServices.mailSession.alertUser("message test", msgUrl);
+
+ Assert.equal(gDialogTitle, null);
+ Assert.equal(gText, "message test");
+
+ Assert.equal(listener1.mMessage, "message test");
+ Assert.notEqual(listener1.mMsgWindow, null);
+
+ // Test - One listener, returning false, no msg window (prompt shouldn't
+ // happen).
+
+ reset();
+ listener1.reset();
+
+ MailServices.mailSession.alertUser("message test no prompt", null);
+
+ Assert.equal(gDialogTitle, null);
+ Assert.equal(gText, null);
+
+ Assert.equal(listener1.mMessage, "message test no prompt");
+ Assert.equal(listener1.mMsgWindow, null);
+
+ // Test - Two listeners, both returning false (prompt should happen).
+
+ reset();
+ listener1.reset();
+
+ var listener2 = new alertListener();
+ listener2.mReturn = false;
+
+ MailServices.mailSession.addUserFeedbackListener(listener2);
+
+ msgUrl._msgWindow = msgWindow;
+
+ MailServices.mailSession.alertUser("two listeners", msgUrl);
+
+ Assert.equal(gDialogTitle, null);
+ Assert.equal(gText, "two listeners");
+
+ Assert.equal(listener1.mMessage, "two listeners");
+ Assert.notEqual(listener1.mMsgWindow, null);
+
+ Assert.equal(listener2.mMessage, "two listeners");
+ Assert.notEqual(listener2.mMsgWindow, null);
+
+ // Test - Two listeners, one returning true (prompt shouldn't happen).
+
+ reset();
+ listener1.reset();
+ listener2.reset();
+
+ listener2.mReturn = true;
+
+ msgUrl._msgWindow = msgWindow;
+
+ MailServices.mailSession.alertUser("no prompt", msgUrl);
+
+ Assert.equal(gDialogTitle, null);
+ Assert.equal(gText, null);
+
+ Assert.equal(listener1.mMessage, "no prompt");
+ Assert.notEqual(listener1.mMsgWindow, null);
+
+ Assert.equal(listener2.mMessage, "no prompt");
+ Assert.notEqual(listener2.mMsgWindow, null);
+
+ // Test - Remove a listener.
+
+ reset();
+ listener1.reset();
+ listener2.reset();
+
+ MailServices.mailSession.removeUserFeedbackListener(listener1);
+
+ msgUrl._msgWindow = msgWindow;
+
+ MailServices.mailSession.alertUser("remove listener", msgUrl);
+
+ Assert.equal(gDialogTitle, null);
+ Assert.equal(gText, null);
+
+ Assert.equal(listener1.mMessage, null);
+ Assert.equal(listener1.mMsgWindow, null);
+
+ Assert.equal(listener2.mMessage, "remove listener");
+ Assert.notEqual(listener2.mMsgWindow, null);
+
+ // Test - Remove the other listener.
+
+ reset();
+ listener1.reset();
+ listener2.reset();
+
+ MailServices.mailSession.removeUserFeedbackListener(listener2);
+
+ msgUrl._msgWindow = msgWindow;
+
+ MailServices.mailSession.alertUser("no listeners", msgUrl);
+
+ Assert.equal(gDialogTitle, null);
+ Assert.equal(gText, "no listeners");
+
+ Assert.equal(listener1.mMessage, null);
+ Assert.equal(listener1.mMsgWindow, null);
+
+ Assert.equal(listener2.mMessage, null);
+ Assert.equal(listener2.mMsgWindow, null);
+}
diff --git a/comm/mailnews/base/test/unit/test_nsMsgMailSession_Listeners.js b/comm/mailnews/base/test/unit/test_nsMsgMailSession_Listeners.js
new file mode 100644
index 0000000000..e6fa785d79
--- /dev/null
+++ b/comm/mailnews/base/test/unit/test_nsMsgMailSession_Listeners.js
@@ -0,0 +1,161 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/*
+ * Test suite for nsMsgMailSession functions relating to listeners.
+ */
+
+var { MailServices } = ChromeUtils.import(
+ "resource:///modules/MailServices.jsm"
+);
+
+var numListenerFunctions = 8;
+
+// The MailSession also implements nsIFolderListener - used to relay
+// notifications onward to all the registered listeners.
+var gMailSessionNotifier = MailServices.mailSession.QueryInterface(
+ Ci.nsIFolderListener
+);
+
+var gFLAll;
+var gFLSingle = new Array(numListenerFunctions);
+
+function fL() {}
+
+fL.prototype = {
+ mReceived: 0,
+ mAutoRemoveItem: false,
+
+ onFolderAdded(parentFolder, child) {
+ this.mReceived |= Ci.nsIFolderListener.added;
+ if (this.mAutoRemoveItem) {
+ MailServices.mailSession.RemoveFolderListener(this);
+ }
+ },
+ onMessageAdded(parentFolder, msg) {
+ this.mReceived |= Ci.nsIFolderListener.added;
+ if (this.mAutoRemoveItem) {
+ MailServices.mailSession.RemoveFolderListener(this);
+ }
+ },
+ onFolderRemoved(parentFolder, child) {
+ this.mReceived |= Ci.nsIFolderListener.removed;
+ if (this.mAutoRemoveItem) {
+ MailServices.mailSession.RemoveFolderListener(this);
+ }
+ },
+ onMessageRemoved(parentFolder, msg) {
+ this.mReceived |= Ci.nsIFolderListener.removed;
+ if (this.mAutoRemoveItem) {
+ MailServices.mailSession.RemoveFolderListener(this);
+ }
+ },
+ onFolderPropertyChanged(item, property, oldValue, newValue) {
+ this.mReceived |= Ci.nsIFolderListener.propertyChanged;
+ if (this.mAutoRemoveItem) {
+ MailServices.mailSession.RemoveFolderListener(this);
+ }
+ },
+ onFolderIntPropertyChanged(item, property, oldValue, newValue) {
+ this.mReceived |= Ci.nsIFolderListener.intPropertyChanged;
+ if (this.mAutoRemoveItem) {
+ MailServices.mailSession.RemoveFolderListener(this);
+ }
+ },
+ onFolderBoolPropertyChanged(item, property, oldValue, newValue) {
+ this.mReceived |= Ci.nsIFolderListener.boolPropertyChanged;
+ if (this.mAutoRemoveItem) {
+ MailServices.mailSession.RemoveFolderListener(this);
+ }
+ },
+ onFolderUnicharPropertyChanged(item, property, oldValue, newValue) {
+ this.mReceived |= Ci.nsIFolderListener.unicharPropertyChanged;
+ if (this.mAutoRemoveItem) {
+ MailServices.mailSession.RemoveFolderListener(this);
+ }
+ },
+ onFolderPropertyFlagChanged(item, property, oldValue, newValue) {
+ this.mReceived |= Ci.nsIFolderListener.propertyFlagChanged;
+ if (this.mAutoRemoveItem) {
+ MailServices.mailSession.RemoveFolderListener(this);
+ }
+ },
+ onFolderEvent(parentItem, item) {
+ this.mReceived |= Ci.nsIFolderListener.event;
+ if (this.mAutoRemoveItem) {
+ MailServices.mailSession.RemoveFolderListener(this);
+ }
+ },
+};
+
+function NotifyMailSession() {
+ gMailSessionNotifier.onFolderAdded(null, null);
+ gMailSessionNotifier.onMessageAdded(null, null);
+ gMailSessionNotifier.onFolderRemoved(null, null);
+ gMailSessionNotifier.onMessageRemoved(null, null);
+ gMailSessionNotifier.onFolderPropertyChanged(null, null, null, null);
+ gMailSessionNotifier.onFolderIntPropertyChanged(null, null, null, null);
+ gMailSessionNotifier.onFolderBoolPropertyChanged(null, null, null, null);
+ gMailSessionNotifier.onFolderUnicharPropertyChanged(null, null, null, null);
+ gMailSessionNotifier.onFolderPropertyFlagChanged(null, null, null, null);
+ gMailSessionNotifier.onFolderEvent(null, null);
+}
+
+function run_test() {
+ var i;
+
+ Assert.ok(MailServices.mailSession != null);
+
+ // Test - Add a listener
+
+ gFLAll = new fL();
+
+ MailServices.mailSession.AddFolderListener(gFLAll, Ci.nsIFolderListener.all);
+
+ for (i = 0; i < numListenerFunctions; ++i) {
+ gFLSingle[i] = new fL();
+ MailServices.mailSession.AddFolderListener(gFLSingle[i], Math.pow(2, i));
+ }
+
+ // Test - Notify listener on all available items
+
+ NotifyMailSession();
+
+ Assert.equal(gFLAll.mReceived, Math.pow(2, numListenerFunctions) - 1);
+ gFLAll.mReceived = 0;
+
+ for (i = 0; i < numListenerFunctions; ++i) {
+ Assert.equal(gFLSingle[i].mReceived, Math.pow(2, i));
+ gFLSingle[i].mReceived = 0;
+
+ // And prepare for test 3.
+ gFLSingle[i].mAutoRemoveItem = true;
+ }
+
+ // Test - Remove Single Listeners as we go through the functions
+
+ // Check the for loop above for changes to the single listeners.
+
+ NotifyMailSession();
+
+ Assert.equal(gFLAll.mReceived, Math.pow(2, numListenerFunctions) - 1);
+ gFLAll.mReceived = 0;
+
+ for (i = 0; i < numListenerFunctions; ++i) {
+ Assert.equal(gFLSingle[i].mReceived, Math.pow(2, i));
+ gFLSingle[i].mReceived = 0;
+ }
+
+ // Test - Ensure the single listeners have been removed.
+
+ NotifyMailSession();
+
+ Assert.equal(gFLAll.mReceived, Math.pow(2, numListenerFunctions) - 1);
+ gFLAll.mReceived = 0;
+
+ for (i = 0; i < numListenerFunctions; ++i) {
+ Assert.equal(gFLSingle[i].mReceived, 0);
+ }
+
+ // Test - Remove main listener
+
+ MailServices.mailSession.RemoveFolderListener(gFLAll);
+}
diff --git a/comm/mailnews/base/test/unit/test_nsMsgTraitService.js b/comm/mailnews/base/test/unit/test_nsMsgTraitService.js
new file mode 100644
index 0000000000..f75b76ada6
--- /dev/null
+++ b/comm/mailnews/base/test/unit/test_nsMsgTraitService.js
@@ -0,0 +1,130 @@
+/* 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/. */
+
+var ts = Cc["@mozilla.org/msg-trait-service;1"].getService(
+ Ci.nsIMsgTraitService
+);
+
+// junk-related traits set by default
+var kJunkId = "mailnews@mozilla.org#junk";
+var kGoodId = "mailnews@mozilla.org#good";
+var kGoodIndex = Ci.nsIJunkMailPlugin.GOOD_TRAIT;
+var kJunkIndex = Ci.nsIJunkMailPlugin.JUNK_TRAIT;
+
+// a dummy set of traits
+var proId = "TheProTrait";
+var proName = "ProName";
+var antiId = "TheAntiTrait";
+
+function run_test() {
+ // Check lastIndex prior to adding, 3 - 1000 are reserved for mailnews
+ Assert.equal(ts.lastIndex, 1000);
+
+ // basic junk as traits should be setup automatically
+ Assert.equal(
+ kGoodId,
+ Services.prefs.getCharPref("mailnews.traits.id." + kGoodIndex)
+ );
+ Assert.equal(
+ kJunkId,
+ Services.prefs.getCharPref("mailnews.traits.id." + kJunkIndex)
+ );
+ Assert.equal(
+ kGoodId,
+ Services.prefs.getCharPref("mailnews.traits.antiId." + kJunkIndex)
+ );
+ Assert.ok(
+ Services.prefs.getBoolPref("mailnews.traits.enabled." + kJunkIndex)
+ );
+
+ // add the pro and anti test traits
+ Assert.ok(!ts.isRegistered(proId));
+ var proIndex = ts.registerTrait(proId);
+ Assert.ok(ts.isRegistered(proId));
+ Assert.equal(proIndex, 1001);
+ Assert.equal(proIndex, ts.getIndex(proId));
+ Assert.equal(proId, ts.getId(proIndex));
+ var antiIndex = ts.registerTrait(antiId);
+ Assert.equal(proIndex, 1001);
+ Assert.equal(antiIndex, 1002);
+
+ // check setting and getting things through the service
+ ts.setName(proId, proName);
+ Assert.equal(proName, ts.getName(proId));
+ Assert.ok(!ts.getEnabled(proId));
+ ts.setEnabled(proId, true);
+ Assert.ok(ts.getEnabled(proId));
+ ts.setAntiId(proId, antiId);
+ Assert.equal(antiId, ts.getAntiId(proId));
+ let proArray = ts.getEnabledProIndices();
+ let antiArray = ts.getEnabledAntiIndices();
+ Assert.equal(proArray.length, 2);
+ Assert.equal(antiArray.length, 2);
+ Assert.equal(proArray[1], proIndex);
+ Assert.equal(antiArray[1], antiIndex);
+
+ // check of aliases
+ // add three random aliases
+ ts.addAlias(1, 501);
+ ts.addAlias(1, 502);
+ ts.addAlias(1, 601);
+ let aliases = ts.getAliases(1);
+ Assert.equal(aliases[0], 501);
+ Assert.equal(aliases[1], 502);
+ Assert.equal(aliases[2], 601);
+
+ // remove the middle one
+ ts.removeAlias(1, 502);
+ aliases = ts.getAliases(1);
+ Assert.equal(aliases.length, 2);
+ Assert.equal(aliases[0], 501);
+ Assert.equal(aliases[1], 601);
+
+ // try to add an existing value
+ ts.addAlias(1, 501);
+ aliases = ts.getAliases(1);
+ Assert.equal(aliases.length, 2);
+ Assert.equal(aliases[0], 501);
+ Assert.equal(aliases[1], 601);
+
+ // now let's make sure this got saved in preferences
+ Assert.equal(
+ proId,
+ Services.prefs.getCharPref("mailnews.traits.id." + proIndex)
+ );
+ Assert.equal(
+ proName,
+ Services.prefs.getCharPref("mailnews.traits.name." + proIndex)
+ );
+ Assert.ok(Services.prefs.getBoolPref("mailnews.traits.enabled." + proIndex));
+ Assert.equal(
+ antiId,
+ Services.prefs.getCharPref("mailnews.traits.antiId." + proIndex)
+ );
+
+ // remove the pro trait
+ ts.unRegisterTrait(proId);
+ Assert.ok(!ts.isRegistered(proId));
+
+ // check that this is also removed from prefs. The get calls should fail
+ try {
+ Services.prefs.getCharPref("mailnews.traits.id." + proIndex);
+ Assert.ok(false);
+ } catch (e) {}
+
+ try {
+ Services.prefs.getCharPref("mailnews.traits.name." + proIndex);
+ Assert.ok(false);
+ } catch (e) {}
+
+ try {
+ Services.prefs.getBoolPref("mailnews.traits.enabled." + proIndex);
+ Assert.ok(false);
+ } catch (e) {}
+
+ try {
+ Services.prefs.getCharPref("mailnews.traits.antiId." + proIndex);
+ Assert.ok(false);
+ } catch (e) {}
+}
diff --git a/comm/mailnews/base/test/unit/test_postPluginFilters.js b/comm/mailnews/base/test/unit/test_postPluginFilters.js
new file mode 100644
index 0000000000..de75307886
--- /dev/null
+++ b/comm/mailnews/base/test/unit/test_postPluginFilters.js
@@ -0,0 +1,223 @@
+/* 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/. */
+
+/*
+ * tests post-plugin message filters as implemented in bug 198100
+ */
+
+/* import-globals-from ../../../test/resources/POP3pump.js */
+load("../../../resources/POP3pump.js");
+
+// Globals
+
+var gDbService = Cc["@mozilla.org/msgDatabase/msgDBService;1"].getService(
+ Ci.nsIMsgDBService
+);
+
+// command functions for test data
+var kTrain = 0; // train a file as a trait
+var kClass = 1; // classify files with traits
+
+var gTest; // currently active test
+var gMsgHdr; // current message header
+
+var kJunkFile = "../../../data/bugmail1";
+var kGoodFile = "../../../data/draft1";
+
+var kPriorityLow = 3;
+var kPriorityHigh = 5;
+var gInboxListener; // database listener object
+
+var gTests = [
+ {
+ // train two different messages
+ command: kTrain,
+ fileName: kGoodFile,
+ traitId: MailServices.junk.GOOD_TRAIT,
+ },
+ {
+ command: kTrain,
+ fileName: kJunkFile,
+ traitId: MailServices.junk.JUNK_TRAIT,
+ },
+ {
+ // test a filter that acts on GOOD messages
+ command: kClass,
+ fileName: kGoodFile,
+ test() {
+ Assert.equal(kPriorityHigh, gMsgHdr.priority);
+ },
+ },
+ {
+ // test a filter that acts on JUNK messages
+ command: kClass,
+ fileName: kJunkFile,
+ test() {
+ Assert.equal(kPriorityLow, gMsgHdr.priority);
+ },
+ },
+];
+
+// main test
+function run_test() {
+ // Setup some incoming filters, setting junk priority low, and good high.
+
+ // Can't use the fake server, must use the deferredTo local server!
+ let filterList = localAccountUtils.incomingServer.getFilterList(null);
+
+ // junkIsLow filter
+ let filter = filterList.createFilter("junkIsLow");
+ let searchTerm = filter.createTerm();
+ searchTerm.attrib = Ci.nsMsgSearchAttrib.JunkStatus;
+ let value = searchTerm.value;
+ value.attrib = Ci.nsMsgSearchAttrib.JunkStatus;
+ value.junkStatus = MailServices.junk.JUNK;
+ searchTerm.value = value;
+ searchTerm.op = Ci.nsMsgSearchOp.Is;
+ searchTerm.booleanAnd = false;
+ filter.appendTerm(searchTerm);
+ let action = filter.createAction();
+ action.type = Ci.nsMsgFilterAction.ChangePriority;
+ action.priority = kPriorityLow;
+ filter.appendAction(action);
+ filter.filterType = Ci.nsMsgFilterType.PostPlugin;
+ filter.enabled = true;
+ filterList.insertFilterAt(0, filter);
+
+ // goodIsHigh filter
+ filter = filterList.createFilter("goodIsHigh");
+ searchTerm = filter.createTerm();
+ searchTerm.attrib = Ci.nsMsgSearchAttrib.JunkStatus;
+ value = searchTerm.value;
+ value.attrib = Ci.nsMsgSearchAttrib.JunkStatus;
+ value.junkStatus = MailServices.junk.GOOD;
+ searchTerm.value = value;
+ searchTerm.op = Ci.nsMsgSearchOp.Is;
+ searchTerm.booleanAnd = false;
+ filter.appendTerm(searchTerm);
+ action = filter.createAction();
+ action.type = Ci.nsMsgFilterAction.ChangePriority;
+ action.priority = kPriorityHigh;
+ filter.appendAction(action);
+ filter.filterType = Ci.nsMsgFilterType.PostPlugin;
+ filter.enabled = true;
+ filterList.insertFilterAt(1, filter);
+
+ // setup a db listener to grab the message headers. There's probably an
+ // easier way, but this works.
+ gInboxListener = new DBListener();
+ gDbService.registerPendingListener(
+ localAccountUtils.inboxFolder,
+ gInboxListener
+ );
+
+ do_test_pending();
+
+ startCommand();
+}
+
+function endTest() {
+ // Cleanup
+ dump(" Exiting mail tests\n");
+ if (gInboxListener) {
+ gDbService.unregisterPendingListener(gInboxListener);
+ }
+
+ gPOP3Pump = null;
+
+ do_test_finished(); // for the one in run_test()
+}
+
+var classifyListener = {
+ // nsIMsgTraitClassificationListener implementation
+ onMessageTraitsClassified(aMsgURI, aTraits, aPercents) {
+ // print("Message URI is " + aMsgURI);
+ if (!aMsgURI) {
+ // Ignore end-of-batch signal.
+ return;
+ }
+
+ startCommand();
+ },
+};
+
+/** @implements {nsIDBChangeListener} */
+function DBListener() {}
+
+DBListener.prototype = {
+ onHdrFlagsChanged(aHdrChanged, aOldFlags, aNewFlags, aInstigator) {},
+
+ onHdrDeleted(aHdrChanged, aParentKey, Flags, aInstigator) {},
+
+ onHdrAdded(aHdrChanged, aParentKey, aFlags, aInstigator) {
+ gMsgHdr = aHdrChanged;
+ },
+
+ onParentChanged(aKeyChanged, oldParent, newParent, aInstigator) {},
+
+ onAnnouncerGoingAway(instigator) {
+ if (gInboxListener) {
+ try {
+ POP3Pump.inbox.msgDatabase.removeListener(gInboxListener);
+ } catch (e) {
+ dump("listener not found\n");
+ }
+ }
+ },
+
+ onReadChanged(aInstigator) {},
+
+ onJunkScoreChanged(aInstigator) {},
+
+ onHdrPropertyChanged(aHdrToChange, aPreChange, aStatus, aInstigator) {},
+ onEvent(aDB, aEvent) {},
+};
+
+// start the next test command
+function startCommand() {
+ if (gTest && gTest.test) {
+ dump("doing test " + gTest.test.name + "\n");
+ gTest.test();
+ }
+ if (!gTests.length) {
+ // Do we have more commands?
+ // no, all done
+ endTest();
+ return;
+ }
+
+ gTest = gTests.shift();
+ switch (gTest.command) {
+ case kTrain:
+ // train message
+ var proArray = [];
+ proArray.push(gTest.traitId);
+
+ MailServices.junk.setMsgTraitClassification(
+ getSpec(gTest.fileName), // aMsgURI
+ [], // aOldTraits
+ proArray, // aNewTraits
+ classifyListener // aTraitListener
+ );
+ // null, // [optional] in nsIMsgWindow aMsgWindow
+ // null, // [optional] in nsIJunkMailClassificationListener aJunkListener
+ break;
+
+ case kClass:
+ // classify message
+ gPOP3Pump.files = [gTest.fileName];
+ gPOP3Pump.onDone = function () {
+ do_timeout(100, startCommand);
+ };
+ gPOP3Pump.run();
+ break;
+ }
+}
+
+function getSpec(aFileName) {
+ var file = do_get_file(aFileName);
+ var uri = Services.io.newFileURI(file).QueryInterface(Ci.nsIURL);
+ uri = uri.mutate().setQuery("type=application/x-message-display").finalize();
+ return uri.spec;
+}
diff --git a/comm/mailnews/base/test/unit/test_retention.js b/comm/mailnews/base/test/unit/test_retention.js
new file mode 100644
index 0000000000..38038015b1
--- /dev/null
+++ b/comm/mailnews/base/test/unit/test_retention.js
@@ -0,0 +1,66 @@
+/* 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/. */
+
+/*
+ * Simple tests for retention settings. In particular, we'd like to make
+ * sure that applying retention settings works with the new code that avoids
+ * opening db's to apply retention settings if the folder doesn't override
+ * the server defaults.
+ */
+
+var { MessageGenerator, MessageScenarioFactory, SyntheticMessageSet } =
+ ChromeUtils.import("resource://testing-common/mailnews/MessageGenerator.jsm");
+var { MessageInjection } = ChromeUtils.import(
+ "resource://testing-common/mailnews/MessageInjection.jsm"
+);
+
+var gMessageGenerator = new MessageGenerator();
+var gScenarioFactory = new MessageScenarioFactory(gMessageGenerator);
+var messageInjection = new MessageInjection({ mode: "local" });
+
+var gTestFolder;
+
+add_setup(async function () {
+ // Add 10 messages
+ let messages = [];
+ messages = messages.concat(gScenarioFactory.directReply(10));
+
+ let msgSet = new SyntheticMessageSet(messages);
+
+ gTestFolder = await messageInjection.makeEmptyFolder();
+ await messageInjection.addSetsToFolders([gTestFolder], [msgSet]);
+});
+
+add_task(function test_retention() {
+ let numMessages = 10;
+ gTestFolder.msgDatabase = null;
+ gTestFolder.applyRetentionSettings();
+ const gDbService = Cc["@mozilla.org/msgDatabase/msgDBService;1"].getService(
+ Ci.nsIMsgDBService
+ );
+ // adding messages leaves some headers around as garbage - make sure
+ // those are cleaned up so the db will get closed.
+ Cu.forceGC();
+ Cu.forceCC();
+ Assert.equal(gDbService.cachedDBForFolder(gTestFolder), null);
+ // no retention settings, so we should have the same number of messages.
+ Assert.equal(numMessages, gTestFolder.msgDatabase.dBFolderInfo.numMessages);
+ let serverSettings = gTestFolder.server.retentionSettings;
+ serverSettings.retainByPreference =
+ Ci.nsIMsgRetentionSettings.nsMsgRetainByNumHeaders;
+ serverSettings.numHeadersToKeep = 9;
+ gTestFolder.server.retentionSettings = serverSettings;
+ gTestFolder.applyRetentionSettings();
+ // no retention settings, so we should have the same number of messages.
+ Assert.equal(9, gTestFolder.msgDatabase.dBFolderInfo.numMessages);
+ let folderSettings = gTestFolder.retentionSettings;
+ folderSettings.retainByPreference =
+ Ci.nsIMsgRetentionSettings.nsMsgRetainByNumHeaders;
+ folderSettings.numHeadersToKeep = 8;
+ folderSettings.useServerDefaults = false;
+ gTestFolder.retentionSettings = folderSettings;
+ gTestFolder.applyRetentionSettings();
+ // no retention settings, so we should have the same number of messages.
+ Assert.equal(8, gTestFolder.msgDatabase.dBFolderInfo.numMessages);
+});
diff --git a/comm/mailnews/base/test/unit/test_saveAs.js b/comm/mailnews/base/test/unit/test_saveAs.js
new file mode 100644
index 0000000000..39263d3854
--- /dev/null
+++ b/comm/mailnews/base/test/unit/test_saveAs.js
@@ -0,0 +1,172 @@
+/* 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/. */
+
+/**
+ * Invokes nsMessenger::saveAs with the bypass of the File Picker to check if
+ * the saved file as .eml .html or .txt contains certain strings (header, body, ...).
+ *
+ * See `checkedContent` for the compared strings.
+ */
+
+var { MessageGenerator } = ChromeUtils.import(
+ "resource://testing-common/mailnews/MessageGenerator.jsm"
+);
+var { PromiseTestUtils } = ChromeUtils.import(
+ "resource://testing-common/mailnews/PromiseTestUtils.jsm"
+);
+var { mailTestUtils } = ChromeUtils.import(
+ "resource://testing-common/mailnews/MailTestUtils.jsm"
+);
+var { ImapMessage } = ChromeUtils.import(
+ "resource://testing-common/mailnews/Imapd.jsm"
+);
+var { IMAPPump, setupIMAPPump, teardownIMAPPump } = ChromeUtils.import(
+ "resource://testing-common/mailnews/IMAPpump.jsm"
+);
+var { TestUtils } = ChromeUtils.importESModule(
+ "resource://testing-common/TestUtils.sys.mjs"
+);
+
+var { FileTestUtils } = ChromeUtils.importESModule(
+ "resource://testing-common/FileTestUtils.sys.mjs"
+);
+
+var messenger = Cc["@mozilla.org/messenger;1"].createInstance(Ci.nsIMessenger);
+
+setupIMAPPump();
+
+registerCleanupFunction(function () {
+ teardownIMAPPump();
+});
+
+/**
+ * Creates a SyntheticMessage and prepares it for loading it
+ * into a fake IMAP inbox.
+ *
+ * @returns {object[]}
+ * [0] is an {ImapMessage}
+ * [1] is an {SyntheticMessage}
+ */
+async function createMessage() {
+ let gMessageGenerator = new MessageGenerator();
+ let synthMessage = gMessageGenerator.makeMessage();
+
+ let msgURI = Services.io.newURI(
+ "data:text/plain;base64," + btoa(synthMessage.toMessageString())
+ );
+ let imapInbox = IMAPPump.daemon.getMailbox("INBOX");
+ let ImapMessageFromSynthMsg = new ImapMessage(
+ msgURI.spec,
+ imapInbox.uidnext++,
+ []
+ );
+ return [ImapMessageFromSynthMsg, synthMessage];
+}
+
+/**
+ * Adds a fake msg to a fake IMAP.
+ *
+ * @param {ImapMessage} fooMessage
+ */
+async function addImapMessage(fooMessage) {
+ IMAPPump.mailbox.addMessage(fooMessage);
+ let promiseUrlListener = new PromiseTestUtils.PromiseUrlListener();
+ IMAPPump.inbox.updateFolderWithListener(null, promiseUrlListener);
+ await promiseUrlListener.promise;
+}
+
+/**
+ * Saves the file and waits until its able to load.
+ * nsMessenger doesn't have an event for the loaded File.
+ *
+ * @param {string} fileEnding
+ * @returns {string}
+ */
+async function saveAndLoad(fileEnding) {
+ // getTempFile guarantees that the file doesn't exist.
+ let tmpFile = FileTestUtils.getTempFile(`someprefix${fileEnding}`);
+ Assert.ok(
+ tmpFile.path.endsWith(fileEnding),
+ "Sanity check if the file ending is intact"
+ );
+
+ // Get the ImapMessage.
+ let hdr = mailTestUtils.firstMsgHdr(IMAPPump.inbox);
+ let uri = IMAPPump.inbox.getUriForMsg(hdr);
+
+ // nsMessenger::saveAs
+ messenger.saveAs(uri, true, null, tmpFile.path, true);
+ info(`File saved at ${tmpFile.path}`);
+
+ await TestUtils.waitForCondition(
+ () => IOUtils.exists(tmpFile.path),
+ "wait for nsMessenger::saveAs file exists"
+ );
+ let fileContent = await IOUtils.readUTF8(tmpFile.path);
+ return fileContent;
+}
+
+/**
+ * All of these strings must appear in the saved file.
+ *
+ * @param {SyntheticMessage} synthMessage
+ * This message is the original message.
+ * @returns {object}
+ */
+function checkedContent(synthMessage) {
+ return {
+ // Skip dateCheck because of Formatting and Timezone.
+ // date: synthMessage.date.toString(),
+ fromName: synthMessage.from[0],
+ fromEmail: synthMessage.from[1],
+ subject: synthMessage.subject,
+ toName: synthMessage.toName,
+ toAddress: synthMessage.toAddress,
+ body: synthMessage.bodyPart.body,
+ };
+}
+
+async function emlTest(synthMessage) {
+ let loadedFileContent = await saveAndLoad(".eml");
+ let messageParts = checkedContent(synthMessage);
+ for (const msgPart in messageParts) {
+ Assert.stringContains(
+ loadedFileContent,
+ messageParts[msgPart],
+ `nsMessenger::saveAs with .eml should contain ${msgPart}`
+ );
+ }
+}
+
+async function htmlTest(synthMessage) {
+ let loadedFileContent = await saveAndLoad(".html");
+ let messageParts = checkedContent(synthMessage);
+ for (const msgPart in messageParts) {
+ Assert.stringContains(
+ loadedFileContent,
+ messageParts[msgPart],
+ `nsMessenger::saveAs with .html should contain ${msgPart}`
+ );
+ }
+}
+
+async function txtTest(synthMessage) {
+ let loadedFileContent = await saveAndLoad(".txt");
+ let messageParts = checkedContent(synthMessage);
+ for (const msgPart in messageParts) {
+ Assert.stringContains(
+ loadedFileContent,
+ messageParts[msgPart],
+ `nsMessenger::saveAs with .txt should contain ${msgPart}`
+ );
+ }
+}
+
+add_task(async function test_saveAs() {
+ let [fakedImapMessage, synthMessage] = await createMessage();
+ await addImapMessage(fakedImapMessage);
+ await emlTest(synthMessage);
+ await txtTest(synthMessage);
+ await htmlTest(synthMessage);
+});
diff --git a/comm/mailnews/base/test/unit/test_testsuite_base64.js b/comm/mailnews/base/test/unit/test_testsuite_base64.js
new file mode 100644
index 0000000000..a6faae3640
--- /dev/null
+++ b/comm/mailnews/base/test/unit/test_testsuite_base64.js
@@ -0,0 +1,22 @@
+/**
+ * Tests functions atob() and btoa() in mailnews/test/resources/MailTestUtils.jsm .
+ *
+ * Note:
+ * btoa() = base64 encode
+ * atob() = base64 decode
+ * (i.e. "binary" = plain, and "ascii" = encoded)
+ */
+
+function run_test() {
+ var plain = "testtesttest";
+ var encoded = "dGVzdHRlc3R0ZXN0";
+
+ // correct encoding according to spec
+ Assert.equal(btoa(plain), encoded); // encode
+ Assert.equal(atob(encoded), plain); // decode
+
+ // roundtrip works
+ Assert.equal(atob(btoa(plain)), plain);
+ Assert.equal(btoa(atob(encoded)), encoded);
+ return true;
+}
diff --git a/comm/mailnews/base/test/unit/test_testsuite_fakeserverAuth.js b/comm/mailnews/base/test/unit/test_testsuite_fakeserverAuth.js
new file mode 100644
index 0000000000..02181ae20b
--- /dev/null
+++ b/comm/mailnews/base/test/unit/test_testsuite_fakeserverAuth.js
@@ -0,0 +1,58 @@
+/**
+ * Tests functions in mailnews/test/fakeserver/Auth.jsm
+ * which are responsible for the authentication in the
+ * fakeserver.
+ *
+ * Do NOT essentially re-code the auth schemes here,
+ * just check roundtrips, against static values etc..
+ */
+
+var { AuthPLAIN, AuthCRAM } = ChromeUtils.import(
+ "resource://testing-common/mailnews/Auth.jsm"
+);
+
+var kUsername = "fred1";
+var kPassword = "wilma2";
+
+function run_test() {
+ authPLAIN();
+ authCRAMMD5();
+ return true;
+}
+
+/**
+ * Test AUTH PLAIN
+ */
+function authPLAIN() {
+ // roundtrip works
+ var line = AuthPLAIN.encodeLine(kUsername, kPassword);
+ var req = AuthPLAIN.decodeLine(line);
+ Assert.equal(req.username, kUsername);
+ Assert.equal(req.password, kPassword);
+
+ // correct encoding
+ Assert.equal(line, "AGZyZWQxAHdpbG1hMg==");
+}
+
+/**
+ * Test AUTH CRAM-MD5
+ */
+function authCRAMMD5() {
+ // AuthCRAM.createChallenge() creates a different challenge each time
+ var hardcodedChallenge = btoa("<123@fake.invalid>");
+ var hardcodedResponse =
+ "ZnJlZDEgOTA5YjgwMmM3NTI5NTJlYzI2NjgyMTNmYTdjNWU0ZjQ=";
+
+ // correct encoding
+ var req = AuthCRAM.decodeLine(hardcodedResponse);
+ Assert.equal(req.username, kUsername);
+ var expectedDigest = AuthCRAM.encodeCRAMMD5(hardcodedChallenge, kPassword);
+ Assert.equal(req.digest, expectedDigest);
+
+ var challenge = AuthCRAM.createChallenge("fake.invalid");
+ challenge = atob(challenge); // decode. function currently returns it already encoded
+ var challengeSplit = challenge.split("@");
+ Assert.equal(challengeSplit.length, 2);
+ Assert.equal(challengeSplit[1], "fake.invalid>");
+ Assert.equal(challengeSplit[0][0], "<");
+}
diff --git a/comm/mailnews/base/test/unit/test_testsuite_fakeserver_imapd_gmail.js b/comm/mailnews/base/test/unit/test_testsuite_fakeserver_imapd_gmail.js
new file mode 100644
index 0000000000..a9eb9ae2d7
--- /dev/null
+++ b/comm/mailnews/base/test/unit/test_testsuite_fakeserver_imapd_gmail.js
@@ -0,0 +1,92 @@
+/* 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/. */
+
+// Test that Imapd.jsm fakeserver correctly emulates GMail server
+// That means X-GM-EXT-1 capability and GMail flavor XLIST
+// per https://developers.google.com/google-apps/gmail/imap_extensions
+
+// IMAP pump
+var { IMAPPump, setupIMAPPump, teardownIMAPPump } = ChromeUtils.import(
+ "resource://testing-common/mailnews/IMAPpump.jsm"
+);
+var { PromiseTestUtils } = ChromeUtils.import(
+ "resource://testing-common/mailnews/PromiseTestUtils.jsm"
+);
+
+setupIMAPPump("GMail");
+// create our own handler so that we can call imapd functions directly
+var handler;
+
+add_setup(function () {
+ Services.prefs.setBoolPref(
+ "mail.server.server1.autosync_offline_stores",
+ false
+ );
+});
+
+// mbox mailboxes cannot contain both child mailboxes and messages, so this will
+// be one test case.
+add_setup(async function () {
+ IMAPPump.mailbox.specialUseFlag = "\\Inbox";
+ IMAPPump.daemon.createMailbox("[Gmail]", { flags: ["\\Noselect"] });
+ IMAPPump.daemon.createMailbox("[Gmail]/All Mail", {
+ specialUseFlag: "\\AllMail",
+ });
+ IMAPPump.daemon.createMailbox("[Gmail]/Drafts", {
+ specialUseFlag: "\\Drafts",
+ });
+ IMAPPump.daemon.createMailbox("[Gmail]/Sent", { specialUseFlag: "\\Sent" });
+ IMAPPump.daemon.createMailbox("[Gmail]/Spam", { specialUseFlag: "\\Spam" });
+ IMAPPump.daemon.createMailbox("[Gmail]/Starred", {
+ specialUseFlag: "\\Starred",
+ });
+ IMAPPump.daemon.createMailbox("[Gmail]/Trash", { specialUseFlag: "\\Trash" });
+ IMAPPump.daemon.createMailbox("test", {});
+
+ handler = IMAPPump.server._handlerCreator(IMAPPump.daemon);
+ let response = handler.onError("1", "LOGIN user password");
+ Assert.ok(response.includes("OK"));
+ // wait for imap pump to do its thing or else we get memory leaks
+ let listener = new PromiseTestUtils.PromiseUrlListener();
+ IMAPPump.inbox.updateFolderWithListener(null, listener);
+ await listener.promise;
+});
+
+// test that 'XLIST "" "*"' returns the proper responses
+add_task(function testXlist() {
+ let response = handler.onError("2", 'XLIST "" "*"');
+
+ Assert.ok(response.includes('* LIST (\\HasNoChildren \\Inbox) "/" "INBOX"'));
+ Assert.ok(
+ response.includes('* LIST (\\Noselect \\HasChildren) "/" "[Gmail]"')
+ );
+ Assert.ok(
+ response.includes(
+ '* LIST (\\HasNoChildren \\AllMail) "/" "[Gmail]/All Mail"'
+ )
+ );
+ Assert.ok(
+ response.includes('* LIST (\\HasNoChildren \\Drafts) "/" "[Gmail]/Drafts"')
+ );
+ Assert.ok(
+ response.includes('* LIST (\\HasNoChildren \\Sent) "/" "[Gmail]/Sent"')
+ );
+ Assert.ok(
+ response.includes('* LIST (\\HasNoChildren \\Spam) "/" "[Gmail]/Spam"')
+ );
+ Assert.ok(
+ response.includes(
+ '* LIST (\\HasNoChildren \\Starred) "/" "[Gmail]/Starred"'
+ )
+ );
+ Assert.ok(
+ response.includes('* LIST (\\HasNoChildren \\Trash) "/" "[Gmail]/Trash"')
+ );
+ Assert.ok(response.includes('* LIST (\\HasNoChildren) "/" "test"'));
+});
+
+// Cleanup at end
+add_task(function endTest() {
+ teardownIMAPPump();
+});
diff --git a/comm/mailnews/base/test/unit/test_testsuite_fakeserver_imapd_list-extended.js b/comm/mailnews/base/test/unit/test_testsuite_fakeserver_imapd_list-extended.js
new file mode 100644
index 0000000000..89214b2b51
--- /dev/null
+++ b/comm/mailnews/base/test/unit/test_testsuite_fakeserver_imapd_list-extended.js
@@ -0,0 +1,150 @@
+/* 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/. */
+
+// Test that Imapd.jsm fakeserver correctly implements LIST-EXTENDED imap
+// extension (RFC 5258 - http://tools.ietf.org/html/rfc5258)
+
+// IMAP pump
+var { IMAPPump, setupIMAPPump, teardownIMAPPump } = ChromeUtils.import(
+ "resource://testing-common/mailnews/IMAPpump.jsm"
+);
+var { PromiseTestUtils } = ChromeUtils.import(
+ "resource://testing-common/mailnews/PromiseTestUtils.jsm"
+);
+
+// Globals
+
+// Dovecot is one of the servers that supports LIST-EXTENDED
+setupIMAPPump("Dovecot");
+// create our own handler so that we can call imapd functions directly
+var handler;
+
+add_setup(function () {
+ Services.prefs.setBoolPref(
+ "mail.server.server1.autosync_offline_stores",
+ false
+ );
+});
+
+// mbox mailboxes cannot contain both child mailboxes and messages, so this will
+// be one test case.
+add_setup(async function () {
+ IMAPPump.mailbox.flags = ["\\Marked", "\\NoInferiors"];
+ IMAPPump.mailbox.subscribed = true;
+ IMAPPump.daemon.createMailbox("Fruit", {});
+ IMAPPump.daemon.createMailbox("Fruit/Apple", {});
+ IMAPPump.daemon.createMailbox("Fruit/Banana", { subscribed: true });
+ IMAPPump.daemon.createMailbox("Fruit/Peach", {
+ nonExistent: true,
+ subscribed: true,
+ });
+ IMAPPump.daemon.createMailbox("Tofu", {});
+ IMAPPump.daemon.createMailbox("Vegetable", { subscribed: true });
+ IMAPPump.daemon.createMailbox("Vegetable/Broccoli", { subscribed: true });
+ IMAPPump.daemon.createMailbox("Vegetable/Corn", {});
+
+ handler = IMAPPump.server._handlerCreator(IMAPPump.daemon);
+ let response = handler.onError("1", "LOGIN user password");
+ Assert.ok(response.includes("OK"));
+ // wait for imap pump to do it's thing or else we get memory leaks
+ let listener = new PromiseTestUtils.PromiseUrlListener();
+ IMAPPump.inbox.updateFolderWithListener(null, listener);
+ await listener.promise;
+});
+
+// test that 'LIST "" "*"' returns the proper responses (standard LIST usage)
+add_task(function testList() {
+ let response = handler.onError("2", 'LIST "" "*"');
+
+ Assert.ok(response.includes('* LIST (\\Marked \\NoInferiors) "/" "INBOX"'));
+ Assert.ok(response.includes('* LIST () "/" "Fruit"'));
+ Assert.ok(response.includes('* LIST () "/" "Fruit/Apple"'));
+ Assert.ok(response.includes('* LIST () "/" "Fruit/Banana"'));
+ Assert.ok(response.includes('* LIST () "/" "Tofu"'));
+ Assert.ok(response.includes('* LIST () "/" "Vegetable"'));
+ Assert.ok(response.includes('* LIST () "/" "Vegetable/Broccoli"'));
+ Assert.ok(response.includes('* LIST () "/" "Vegetable/Corn"'));
+ Assert.ok(!response.includes("Peach"));
+});
+
+// test that 'LIST (SUBSCRIBED) "" "*"' returns the proper responses
+add_task(function testListSelectSubscribed() {
+ let response = handler.onError("3", 'LIST (SUBSCRIBED) "" "*"');
+
+ Assert.ok(
+ response.includes(
+ '* LIST (\\Marked \\NoInferiors \\Subscribed) "/" "INBOX"'
+ )
+ );
+ Assert.ok(response.includes('* LIST (\\Subscribed) "/" "Fruit/Banana"'));
+ Assert.ok(
+ response.includes('* LIST (\\Subscribed \\NonExistent) "/" "Fruit/Peach"')
+ );
+ Assert.ok(response.includes('* LIST (\\Subscribed) "/" "Vegetable"'));
+ Assert.ok(
+ response.includes('* LIST (\\Subscribed) "/" "Vegetable/Broccoli"')
+ );
+ Assert.ok(!response.includes('"Fruit"'));
+ Assert.ok(!response.includes("Apple"));
+ Assert.ok(!response.includes("Tofu"));
+ Assert.ok(!response.includes("Corn"));
+});
+
+// test that 'LIST "" "%" RETURN (CHILDEREN)' returns the proper responses
+add_task(function testListReturnChilderen() {
+ let response = handler.onError("4", 'LIST "" "%" RETURN (CHILDREN)');
+
+ Assert.ok(response.includes('* LIST (\\Marked \\NoInferiors) "/" "INBOX"'));
+ Assert.ok(response.includes('* LIST (\\HasChildren) "/" "Fruit"'));
+ Assert.ok(response.includes('* LIST (\\HasNoChildren) "/" "Tofu"'));
+ Assert.ok(response.includes('* LIST (\\HasChildren) "/" "Vegetable"'));
+ Assert.ok(!response.includes("Apple"));
+ Assert.ok(!response.includes("Banana"));
+ Assert.ok(!response.includes("Peach"));
+ Assert.ok(!response.includes("Broccoli"));
+ Assert.ok(!response.includes("Corn"));
+});
+
+// test that 'LIST "" "*" RETURN (SUBSCRIBED)' returns the proper responses
+add_task(function testListReturnSubscribed() {
+ let response = handler.onError("5", 'LIST "" "*" RETURN (SUBSCRIBED)');
+
+ Assert.ok(
+ response.includes(
+ '* LIST (\\Marked \\NoInferiors \\Subscribed) "/" "INBOX"'
+ )
+ );
+ Assert.ok(response.includes('* LIST () "/" "Fruit"'));
+ Assert.ok(response.includes('* LIST () "/" "Fruit/Apple"'));
+ Assert.ok(response.includes('* LIST (\\Subscribed) "/" "Fruit/Banana"'));
+ Assert.ok(response.includes('* LIST () "/" "Tofu"'));
+ Assert.ok(response.includes('* LIST (\\Subscribed) "/" "Vegetable"'));
+ Assert.ok(
+ response.includes('* LIST (\\Subscribed) "/" "Vegetable/Broccoli"')
+ );
+ Assert.ok(response.includes('* LIST () "/" "Vegetable/Corn"'));
+ Assert.ok(!response.includes("Peach"));
+});
+
+// test that 'LIST "" ("INBOX" "Tofu" "Vegetable/%")' returns the proper responses
+add_task(function testListSelectMultiple() {
+ let response = handler._dispatchCommand("LIST", [
+ "",
+ '("INBOX" "Tofu" "Vegetable/%")',
+ ]);
+
+ Assert.ok(response.includes('* LIST (\\Marked \\NoInferiors) "/" "INBOX"'));
+ Assert.ok(response.includes('* LIST () "/" "Tofu"'));
+ Assert.ok(response.includes('* LIST () "/" "Vegetable/Broccoli"'));
+ Assert.ok(response.includes('* LIST () "/" "Vegetable/Corn"'));
+ Assert.ok(!response.includes('"Vegetable"'));
+ Assert.ok(!response.includes("Fruit"));
+ Assert.ok(!response.includes("Peach"));
+});
+
+// Cleanup at end
+add_task(function endTest() {
+ handler = null;
+ teardownIMAPPump();
+});
diff --git a/comm/mailnews/base/test/unit/test_viewSortByAddresses.js b/comm/mailnews/base/test/unit/test_viewSortByAddresses.js
new file mode 100644
index 0000000000..7235723bfd
--- /dev/null
+++ b/comm/mailnews/base/test/unit/test_viewSortByAddresses.js
@@ -0,0 +1,144 @@
+/* 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/. */
+
+/*
+ * Attempt to test nsMsgDBView's handling of sorting by sender/recipients
+ * when using a display name from the address book.
+ */
+
+/* import-globals-from ../../../test/resources/abSetup.js */
+load("../../../resources/abSetup.js");
+
+var { MailServices } = ChromeUtils.import(
+ "resource:///modules/MailServices.jsm"
+);
+var { MessageGenerator, SyntheticMessageSet } = ChromeUtils.import(
+ "resource://testing-common/mailnews/MessageGenerator.jsm"
+);
+var { MessageInjection } = ChromeUtils.import(
+ "resource://testing-common/mailnews/MessageInjection.jsm"
+);
+var { dump_view_contents } = ChromeUtils.import(
+ "resource://testing-common/mozmill/ViewHelpers.jsm"
+);
+
+var gMessageGenerator = new MessageGenerator();
+var messageInjection = new MessageInjection({ mode: "local" });
+
+Services.prefs.setBoolPref("mail.showCondensedAddresses", true);
+
+var gTestFolder;
+
+// Setup the display name to be opposite of alphabetic order of e-mail address.
+var cards = [
+ { email: "aaa@b.invalid", displayName: "4" },
+ { email: "ccc@d.invalid", displayName: "3" },
+ { email: "eee@f.invalid", displayName: "2" },
+ { email: "ggg@h.invalid", displayName: "1" },
+];
+
+add_setup(async function () {
+ // Ensure all the directories are initialised.
+ MailServices.ab.directories;
+
+ let ab = MailServices.ab.getDirectory(kPABData.URI);
+
+ function createAndAddCard(element) {
+ var card = Cc["@mozilla.org/addressbook/cardproperty;1"].createInstance(
+ Ci.nsIAbCard
+ );
+
+ card.primaryEmail = element.email;
+ card.displayName = element.displayName;
+
+ ab.addCard(card);
+ }
+
+ // Add address to addressbook so we can set display name and verify that
+ // the view uses the display name for display and sorting.
+ cards.forEach(createAndAddCard);
+
+ // build up a couple message with addresses in the ab.
+ let messages = [];
+ messages = messages.concat(
+ gMessageGenerator.makeMessage({
+ from: ["aaa", "aaa@b.invalid"],
+ to: [["ccc", "ccc@d.invalid"]],
+ })
+ );
+ messages = messages.concat(
+ gMessageGenerator.makeMessage({
+ from: ["eee", "eee@f.invalid"],
+ to: [["ggg", "ggg@h.invalid"]],
+ })
+ );
+
+ let msgSet = new SyntheticMessageSet(messages);
+ gTestFolder = await messageInjection.makeEmptyFolder();
+ await messageInjection.addSetsToFolders([gTestFolder], [msgSet]);
+});
+
+add_task(function test_view_sort_by_addresses() {
+ // - create the view
+ setup_view("threaded", Ci.nsMsgViewFlagsType.kNone);
+ // Check that sorting by sender uses the display name
+ gDBView.sort(Ci.nsMsgViewSortType.byAuthor, Ci.nsMsgViewSortOrder.ascending);
+ let sender1 = gDBView.cellTextForColumn(0, "senderCol");
+ let sender2 = gDBView.cellTextForColumn(1, "senderCol");
+
+ if (sender1 != 2) {
+ view_throw("expected sender 1 to be 2");
+ }
+ if (sender2 != 4) {
+ view_throw("expected sender 2 to be 4");
+ }
+
+ gDBView.sort(
+ Ci.nsMsgViewSortType.byRecipient,
+ Ci.nsMsgViewSortOrder.ascending
+ );
+ let recip1 = gDBView.cellTextForColumn(0, "recipientCol");
+ let recip2 = gDBView.cellTextForColumn(1, "recipientCol");
+
+ if (recip1 != 1) {
+ view_throw("expected recip 1 to be 1");
+ }
+ if (recip2 != 3) {
+ view_throw("expected recip 2 to be 3");
+ }
+});
+
+function view_throw(why) {
+ dump_view_contents();
+ do_throw(why);
+}
+var gDBView;
+var gTreeView;
+
+function setup_view(aViewType, aViewFlags, aTestFolder) {
+ let dbviewContractId = "@mozilla.org/messenger/msgdbview;1?type=" + aViewType;
+
+ if (aTestFolder == null) {
+ aTestFolder = gTestFolder;
+ }
+
+ // always start out fully expanded
+ aViewFlags |= Ci.nsMsgViewFlagsType.kExpandAll;
+
+ gDBView = Cc[dbviewContractId].createInstance(Ci.nsIMsgDBView);
+ gDBView.init(null, null, null);
+ var outCount = {};
+ gDBView.open(
+ aViewType != "search" ? aTestFolder : null,
+ Ci.nsMsgViewSortType.byDate,
+ aViewType != "search"
+ ? Ci.nsMsgViewSortOrder.ascending
+ : Ci.nsMsgViewSortOrder.descending,
+ aViewFlags,
+ outCount
+ );
+ dump(" View Out Count: " + outCount.value + "\n");
+
+ gTreeView = gDBView.QueryInterface(Ci.nsITreeView);
+}
diff --git a/comm/mailnews/base/test/unit/test_virtualFolders1.js b/comm/mailnews/base/test/unit/test_virtualFolders1.js
new file mode 100644
index 0000000000..5fe32de237
--- /dev/null
+++ b/comm/mailnews/base/test/unit/test_virtualFolders1.js
@@ -0,0 +1,205 @@
+/* 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/. */
+
+/**
+ * Tests that subfolders added to searched folders are also searched.
+ */
+
+const { VirtualFolderHelper } = ChromeUtils.import(
+ "resource:///modules/VirtualFolderWrapper.jsm"
+);
+
+add_task(function () {
+ MailServices.accounts.createLocalMailAccount();
+ let account = MailServices.accounts.accounts[0];
+ let rootFolder = account.incomingServer.rootFolder;
+
+ // Create some real folders to test.
+ rootFolder.createSubfolder("test A", null);
+ let testFolderA = rootFolder.getChildNamed("test A");
+ rootFolder.createSubfolder("test B", null);
+ let testFolderB = rootFolder.getChildNamed("test B");
+ rootFolder.createSubfolder("test C", null);
+ let testFolderC = rootFolder.getChildNamed("test C");
+
+ // Create a virtual folder with no search folders.
+ let wrappedFolderZ = VirtualFolderHelper.createNewVirtualFolder(
+ "virtual Z",
+ rootFolder,
+ [],
+ "ANY",
+ false
+ );
+ Assert.equal(
+ wrappedFolderZ.virtualFolder,
+ rootFolder.getChildNamed("virtual Z")
+ );
+ Assert.equal(wrappedFolderZ.searchFolderURIs, "");
+ Assert.deepEqual(wrappedFolderZ.searchFolders, []);
+
+ // Create a virtual folder with one search folder.
+ let wrappedFolderY = VirtualFolderHelper.createNewVirtualFolder(
+ "virtual Y",
+ rootFolder,
+ [testFolderA],
+ "ANY",
+ false
+ );
+ Assert.equal(
+ wrappedFolderY.virtualFolder,
+ rootFolder.getChildNamed("virtual Y")
+ );
+ Assert.equal(wrappedFolderY.searchFolderURIs, testFolderA.URI);
+ Assert.deepEqual(wrappedFolderY.searchFolders, [testFolderA]);
+
+ // Create a virtual folder with two search folders.
+ let wrappedFolderX = VirtualFolderHelper.createNewVirtualFolder(
+ "virtual X",
+ rootFolder,
+ [testFolderB, testFolderC],
+ "ANY",
+ false
+ );
+ Assert.equal(
+ wrappedFolderX.virtualFolder,
+ rootFolder.getChildNamed("virtual X")
+ );
+ Assert.equal(
+ wrappedFolderX.searchFolderURIs,
+ `${testFolderB.URI}|${testFolderC.URI}`
+ );
+ Assert.deepEqual(wrappedFolderX.searchFolders, [testFolderB, testFolderC]);
+
+ // Add a subfolder to real folder B. Check it is added to virtual folder X.
+ testFolderB.createSubfolder("test BB", null);
+ let testFolderBB = testFolderB.getChildNamed("test BB");
+ Assert.equal(
+ wrappedFolderZ.searchFolderURIs,
+ "",
+ "virtual folder Z should not change"
+ );
+ Assert.deepEqual(
+ wrappedFolderZ.searchFolders,
+ [],
+ "virtual folder Z should not change"
+ );
+ Assert.equal(
+ wrappedFolderY.searchFolderURIs,
+ testFolderA.URI,
+ "virtual folder Y should not change"
+ );
+ Assert.deepEqual(
+ wrappedFolderY.searchFolders,
+ [testFolderA],
+ "virtual folder Y should not change"
+ );
+ Assert.equal(
+ wrappedFolderX.searchFolderURIs,
+ `${testFolderB.URI}|${testFolderC.URI}|${testFolderBB.URI}`
+ );
+ Assert.deepEqual(wrappedFolderX.searchFolders, [
+ testFolderB,
+ testFolderBB,
+ testFolderC,
+ ]);
+
+ // Add a subfolder to real folder BB. Check it is added to virtual folder X.
+ testFolderBB.createSubfolder("test BBB", null);
+ let testFolderBBB = testFolderBB.getChildNamed("test BBB");
+ Assert.equal(
+ wrappedFolderZ.searchFolderURIs,
+ "",
+ "virtual folder Z should not change"
+ );
+ Assert.deepEqual(
+ wrappedFolderZ.searchFolders,
+ [],
+ "virtual folder Z should not change"
+ );
+ Assert.equal(
+ wrappedFolderY.searchFolderURIs,
+ testFolderA.URI,
+ "virtual folder Y should not change"
+ );
+ Assert.deepEqual(
+ wrappedFolderY.searchFolders,
+ [testFolderA],
+ "virtual folder Y should not change"
+ );
+ Assert.equal(
+ wrappedFolderX.searchFolderURIs,
+ `${testFolderB.URI}|${testFolderC.URI}|${testFolderBB.URI}|${testFolderBBB.URI}`
+ );
+ Assert.deepEqual(wrappedFolderX.searchFolders, [
+ testFolderB,
+ testFolderBB,
+ testFolderBBB,
+ testFolderC,
+ ]);
+
+ // Remove BB from virtual folder X.
+ wrappedFolderX.searchFolders = [testFolderB, testFolderBBB, testFolderC];
+ Assert.equal(
+ wrappedFolderZ.searchFolderURIs,
+ "",
+ "virtual folder Z should not change"
+ );
+ Assert.deepEqual(
+ wrappedFolderZ.searchFolders,
+ [],
+ "virtual folder Z should not change"
+ );
+ Assert.equal(
+ wrappedFolderY.searchFolderURIs,
+ testFolderA.URI,
+ "virtual folder Y should not change"
+ );
+ Assert.deepEqual(
+ wrappedFolderY.searchFolders,
+ [testFolderA],
+ "virtual folder Y should not change"
+ );
+ Assert.equal(
+ wrappedFolderX.searchFolderURIs,
+ `${testFolderB.URI}|${testFolderBBB.URI}|${testFolderC.URI}`
+ );
+ Assert.deepEqual(wrappedFolderX.searchFolders, [
+ testFolderB,
+ testFolderBBB,
+ testFolderC,
+ ]);
+
+ // Add a second subfolder to the removed folder. Check it is not added to
+ // virtual folder X, because the parent folder is not in X.
+ testFolderBB.createSubfolder("test BBB two", null);
+ Assert.equal(
+ wrappedFolderZ.searchFolderURIs,
+ "",
+ "virtual folder Z should not change"
+ );
+ Assert.deepEqual(
+ wrappedFolderZ.searchFolders,
+ [],
+ "virtual folder Z should not change"
+ );
+ Assert.equal(
+ wrappedFolderY.searchFolderURIs,
+ testFolderA.URI,
+ "virtual folder Y should not change"
+ );
+ Assert.deepEqual(
+ wrappedFolderY.searchFolders,
+ [testFolderA],
+ "virtual folder Y should not change"
+ );
+ Assert.equal(
+ wrappedFolderX.searchFolderURIs,
+ `${testFolderB.URI}|${testFolderBBB.URI}|${testFolderC.URI}`
+ );
+ Assert.deepEqual(wrappedFolderX.searchFolders, [
+ testFolderB,
+ testFolderBBB,
+ testFolderC,
+ ]);
+});
diff --git a/comm/mailnews/base/test/unit/test_virtualFolders2.js b/comm/mailnews/base/test/unit/test_virtualFolders2.js
new file mode 100644
index 0000000000..07c75db97f
--- /dev/null
+++ b/comm/mailnews/base/test/unit/test_virtualFolders2.js
@@ -0,0 +1,90 @@
+/* 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/. */
+
+/**
+ * Tests problems emptying a Trash folder that is searched by a virtual folder.
+ */
+
+const { VirtualFolderHelper } = ChromeUtils.import(
+ "resource:///modules/VirtualFolderWrapper.jsm"
+);
+
+add_task(function () {
+ MailServices.accounts.createLocalMailAccount();
+ let account = MailServices.accounts.accounts[0];
+ let rootFolder = account.incomingServer.rootFolder;
+
+ // Create a real folders inside the trash folder.
+ let trashFolder = rootFolder.getFolderWithFlags(Ci.nsMsgFolderFlags.Trash);
+ trashFolder.createSubfolder("deleted", null);
+ let deletedFolder = trashFolder.getChildNamed("deleted");
+
+ // Create a virtual folder that searches the trash folder.
+ let wrappedVirtualFolder = VirtualFolderHelper.createNewVirtualFolder(
+ "virtual",
+ rootFolder,
+ [trashFolder, deletedFolder],
+ "ANY",
+ false
+ );
+ let virtualFolder = wrappedVirtualFolder.virtualFolder;
+ Assert.equal(virtualFolder, rootFolder.getChildNamed("virtual"));
+ Assert.equal(
+ wrappedVirtualFolder.searchFolderURIs,
+ `${trashFolder.URI}|${deletedFolder.URI}`
+ );
+ Assert.deepEqual(wrappedVirtualFolder.searchFolders, [
+ trashFolder,
+ deletedFolder,
+ ]);
+
+ // Create a smart virtual folder that searches the trash folder. This is the
+ // same as before except we'll set the searchFolderFlag property, as we do
+ // for the Unified Folders section of the UI.
+ let wrappedSmartFolder = VirtualFolderHelper.createNewVirtualFolder(
+ "smart",
+ rootFolder,
+ [trashFolder, deletedFolder],
+ "ANY",
+ false
+ );
+ let smartFolder = wrappedSmartFolder.virtualFolder;
+ smartFolder.msgDatabase.dBFolderInfo.setUint32Property(
+ "searchFolderFlag",
+ Ci.nsMsgFolderFlags.Trash
+ );
+ Assert.equal(smartFolder, rootFolder.getChildNamed("smart"));
+ Assert.equal(
+ wrappedSmartFolder.searchFolderURIs,
+ `${trashFolder.URI}|${deletedFolder.URI}`
+ );
+ Assert.deepEqual(wrappedSmartFolder.searchFolders, [
+ trashFolder,
+ deletedFolder,
+ ]);
+
+ // Empty the trash. The normal virtual folder should disappear, but the
+ // smart folder shouldn't.
+ trashFolder.emptyTrash(null);
+ Assert.equal(
+ virtualFolder.parent,
+ null,
+ "virtual folder should be removed with last search folder"
+ );
+ Assert.equal(
+ smartFolder.parent,
+ rootFolder,
+ "smart virtual folder should NOT be removed with last search folder"
+ );
+ Assert.equal(
+ wrappedSmartFolder.searchFolderURIs,
+ trashFolder.URI,
+ "smart virtual folder should still search the trash folder (only)"
+ );
+ Assert.deepEqual(
+ wrappedSmartFolder.searchFolders,
+ [trashFolder],
+ "smart virtual folder should still search the trash folder (only)"
+ );
+});
diff --git a/comm/mailnews/base/test/unit/test_virtualFolders3.js b/comm/mailnews/base/test/unit/test_virtualFolders3.js
new file mode 100644
index 0000000000..e3d1b9c73c
--- /dev/null
+++ b/comm/mailnews/base/test/unit/test_virtualFolders3.js
@@ -0,0 +1,229 @@
+/* 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/. */
+
+/**
+ * Tests that subfolders added to searched folders are also searched.
+ */
+
+const { VirtualFolderHelper } = ChromeUtils.import(
+ "resource:///modules/VirtualFolderWrapper.jsm"
+);
+
+let rootFolder;
+
+add_setup(function () {
+ MailServices.accounts.createLocalMailAccount();
+ let account = MailServices.accounts.accounts[0];
+ rootFolder = account.incomingServer.rootFolder;
+ rootFolder.QueryInterface(Ci.nsIMsgLocalMailFolder);
+
+ // Listen to folder events for debugging purposes.
+
+ MailServices.mailSession.AddFolderListener(
+ {
+ QueryInterface: ChromeUtils.generateQI(["nsIFolderListener"]),
+ onFolderAdded(parentFolder, childFolder) {
+ console.log(
+ `onFolderAdded: ${
+ childFolder?.URI
+ } (flags: ${childFolder.flags.toString(16)}) added to ${
+ parentFolder?.URI
+ }`
+ );
+ },
+ onMessageAdded(parentFolder, msg) {},
+ onFolderRemoved(parentFolder, childFolder) {
+ console.log(
+ `onFolderRemoved: ${
+ childFolder?.URI
+ } (flags: ${childFolder.flags.toString(16)}) removed from ${
+ parentFolder?.URI
+ }`
+ );
+ },
+ onMessageRemoved(parentFolder, msg) {},
+ onFolderPropertyChanged(folder, property, oldValue, newValue) {},
+ onFolderIntPropertyChanged(folder, property, oldValue, newValue) {
+ if (property == "FolderFlag") {
+ console.log(
+ `onFolderIntPropertyChanged: ${
+ folder.URI
+ } changed flags from ${oldValue.toString(
+ 16
+ )} to ${newValue.toString(16)}`
+ );
+ }
+ },
+ onFolderBoolPropertyChanged(folder, property, oldValue, newValue) {},
+ onFolderUnicharPropertyChanged(folder, property, oldValue, newValue) {},
+ onFolderPropertyFlagChanged(folder, property, oldFlag, newFlag) {},
+ onFolderEvent(folder, event) {},
+ },
+ Ci.nsIFolderListener.all
+ );
+});
+
+// Test each of the folder types.
+add_task(function testInbox() {
+ subtest("Inbox");
+});
+add_task(function testDrafts() {
+ subtest("Drafts");
+});
+add_task(function testTemplates() {
+ subtest("Templates");
+});
+add_task(function testSentMail() {
+ subtest("SentMail");
+});
+add_task(function testArchive() {
+ subtest("Archive");
+});
+add_task(function testJunk() {
+ subtest("Junk");
+});
+add_task(function testTrash() {
+ subtest("Trash");
+});
+
+function subtest(flag) {
+ // Create a virtual folder. This is very similar to the code in about3Pane.js.
+
+ let virtualFolder = rootFolder.createLocalSubfolder(`virtual${flag}`);
+ virtualFolder.flags |=
+ Ci.nsMsgFolderFlags.Virtual | Ci.nsMsgFolderFlags[flag];
+
+ let msgDatabase = virtualFolder.msgDatabase;
+ let folderInfo = msgDatabase.dBFolderInfo;
+
+ folderInfo.setCharProperty("searchStr", "ALL");
+ folderInfo.setUint32Property("searchFolderFlag", Ci.nsMsgFolderFlags[flag]);
+ folderInfo.setBooleanProperty("searchOnline", true);
+ msgDatabase.summaryValid = true;
+ msgDatabase.close(true);
+
+ function checkVirtualFolder(searchFolders, message) {
+ let wrappedVirtualFolder =
+ VirtualFolderHelper.wrapVirtualFolder(virtualFolder);
+ Assert.deepEqual(
+ wrappedVirtualFolder.searchFolderURIs.split("|").filter(Boolean).sort(),
+ searchFolders.map(f => f.URI).sort(),
+ message
+ );
+ Assert.deepEqual(
+ wrappedVirtualFolder.searchFolders,
+ searchFolders,
+ message
+ );
+ }
+
+ rootFolder.notifyFolderAdded(virtualFolder);
+ checkVirtualFolder([], "new virtual folder should have no search folders");
+
+ // Create a disconnected folder with some descendants, add the flag and then
+ // add it to the parent. The folder and descendants should all be added to
+ // the virtual folder.
+
+ let parent = MailServices.folderLookup.getOrCreateFolderForURL(
+ `${rootFolder.URI}/parent${flag}`
+ );
+ parent.setFlag(Ci.nsMsgFolderFlags[flag]);
+ let child = MailServices.folderLookup.getOrCreateFolderForURL(
+ `${rootFolder.URI}/parent${flag}/child`
+ );
+ parent.addSubfolder(child.name);
+ let grandchild = MailServices.folderLookup.getOrCreateFolderForURL(
+ `${rootFolder.URI}/parent${flag}/child/grandchild`
+ );
+ child.addSubfolder(grandchild.name);
+
+ rootFolder.addSubfolder(parent.name);
+ rootFolder.notifyFolderAdded(parent);
+ parent.notifyFolderAdded(child);
+ child.notifyFolderAdded(grandchild);
+
+ checkVirtualFolder(
+ [parent, child, grandchild],
+ "added folder and descendants should be added to the virtual folder"
+ );
+
+ // Create a subfolder of a real folder with some descendants, then set the
+ // flag. The folder and descendants should all be added to the virtual folder.
+
+ let more = rootFolder.createLocalSubfolder(`more${flag}`);
+ more.QueryInterface(Ci.nsIMsgLocalMailFolder);
+ let evenMore = more.createLocalSubfolder("even more");
+ evenMore.QueryInterface(Ci.nsIMsgLocalMailFolder);
+ let yetMore = evenMore.createLocalSubfolder("yet more");
+ more.setFlag(Ci.nsMsgFolderFlags[flag]);
+
+ checkVirtualFolder(
+ [more, evenMore, yetMore, parent, child, grandchild],
+ "folder with changed flag and descendants should be added to the virtual folder"
+ );
+
+ // Test what happens if a folder of one type is not added to a parent in the
+ // virtual folder of another type. This should really only matter for inboxes
+ // containing other types of folders, which happens in some configurations.
+ // Other combinations shouldn't really exist, but let's test them anyway.
+
+ if (!["SentMail", "Archive"].includes(flag)) {
+ for (let otherFlag of [
+ "Inbox",
+ "Drafts",
+ "Templates",
+ "SentMail",
+ "Archive",
+ "Junk",
+ "Trash",
+ ]) {
+ if (otherFlag == flag) {
+ continue;
+ }
+ let otherFlagChild = MailServices.folderLookup.getOrCreateFolderForURL(
+ `${rootFolder.URI}/parent${flag}/other${otherFlag}Child`
+ );
+ otherFlagChild.setFlag(Ci.nsMsgFolderFlags[otherFlag]);
+ parent.addSubfolder(otherFlagChild.name);
+ parent.notifyFolderAdded(otherFlagChild);
+
+ if (flag == "Trash") {
+ checkVirtualFolder(
+ [more, evenMore, yetMore, parent, child, grandchild, otherFlagChild],
+ `folder with ${otherFlag} flag should be added to the virtual ${flag} folder`
+ );
+ } else {
+ checkVirtualFolder(
+ [more, evenMore, yetMore, parent, child, grandchild],
+ `folder with ${otherFlag} flag should not be added to the virtual ${flag} folder`
+ );
+ }
+
+ parent.propagateDelete(otherFlagChild, false);
+ }
+ }
+
+ // Now reverse the additions.
+
+ rootFolder.propagateDelete(parent, false);
+ checkVirtualFolder(
+ [more, evenMore, yetMore],
+ "deleted folder and descendants should be removed from the virtual folder"
+ );
+
+ if (flag != "Inbox") {
+ // Clearing the inbox flag from a folder that has it throws an assertion.
+ more.clearFlag(Ci.nsMsgFolderFlags[flag]);
+ checkVirtualFolder(
+ [],
+ "folder with changed flag and descendants should be removed from the virtual folder"
+ );
+
+ Assert.equal(
+ virtualFolder.parent,
+ rootFolder,
+ "smart virtual folder should NOT be removed with last search folder"
+ );
+ }
+}
diff --git a/comm/mailnews/base/test/unit/xpcshell-imap.ini b/comm/mailnews/base/test/unit/xpcshell-imap.ini
new file mode 100644
index 0000000000..2b343b30b7
--- /dev/null
+++ b/comm/mailnews/base/test/unit/xpcshell-imap.ini
@@ -0,0 +1,8 @@
+[DEFAULT]
+head = head_mailbase.js
+tail =
+prefs =
+ mailnews.imap.jsmodule=true
+dupe-manifest =
+
+[test_incomingServer.js]
diff --git a/comm/mailnews/base/test/unit/xpcshell.ini b/comm/mailnews/base/test/unit/xpcshell.ini
new file mode 100644
index 0000000000..0658d7d1b3
--- /dev/null
+++ b/comm/mailnews/base/test/unit/xpcshell.ini
@@ -0,0 +1,77 @@
+[DEFAULT]
+head = head_mailbase.js
+tail =
+dupe-manifest =
+prefs =
+ mail.biff.use_new_count_in_badge=false
+support-files = nodelist_test.xml data/*
+
+[test_accountMgr.js]
+[test_accountMgr2.js]
+[test_accountMgrCustomTypes.js]
+[test_accountMgrMovedLocalFolders.js]
+[test_accountMgrRemoveDefault.js]
+[test_accountMigration.js]
+[test_acctRepair.js]
+[test_bccInDatabase.js]
+[test_bug428427.js]
+[test_bug434810.js]
+[test_bug471682.js]
+[test_bug514945.js]
+[test_closedDB.js]
+[test_compactColumnSave.js]
+[test_compactFailure.js]
+[test_converterDeferredAccount.js]
+[test_copyChaining.js]
+[test_copyToInvalidDB.js]
+[test_detachToFile.js]
+[test_emptyTrash.js]
+[test_fix_deferred_accounts.js]
+[test_folderCompact.js]
+[test_folderCompact2.js]
+[test_folderLookupService.js]
+[test_folderStringProperties.js]
+[test_formatFileSize.js]
+[test_getMsgTextFromStream.js]
+[test_headerFoldingInDatabase.js]
+[test_hostnameUtils.js]
+[test_identity.js]
+[test_imapPump.js]
+[test_incomingServer.js]
+[test_inheritedFolderProperties.js]
+[test_junkingWhenDisabled.js]
+[test_loadVirtualFolders.js]
+[test_mailServices.js]
+[test_mailstoreConverter.js]
+[test_mimemaltdetach.js]
+[test_MsgIncomingServer.js]
+[test_MsgKeySet.js]
+[test_newMailNotification.js]
+# Not yet working for non-Mac OS
+skip-if = os != 'mac'
+[test_nsIFolderListener.js]
+[test_nsIMsgContentPolicy.js]
+skip-if = true # See bug 1446587.
+[test_nsIMsgFolder.js]
+[test_nsIMsgFolderCache.js]
+[test_nsIMsgFolderListener.js]
+[test_nsIMsgFolderListenerLocal.js]
+[test_nsIMsgTagService.js]
+[test_nsMailDirProvider.js]
+[test_nsMsgDBView.js]
+[test_nsMsgDBView_headerValues.js]
+[test_nsMsgMailSession_Alerts.js]
+skip-if = true # See bug 1418063.
+[test_nsMsgMailSession_Listeners.js]
+[test_nsMsgTraitService.js]
+[test_postPluginFilters.js]
+[test_retention.js]
+[test_saveAs.js]
+[test_testsuite_base64.js]
+[test_testsuite_fakeserver_imapd_gmail.js]
+[test_testsuite_fakeserver_imapd_list-extended.js]
+[test_testsuite_fakeserverAuth.js]
+[test_viewSortByAddresses.js]
+[test_virtualFolders1.js]
+[test_virtualFolders2.js]
+[test_virtualFolders3.js]