Bug 1851536 - Test the quota status bar panel and the folder properties dialog quota tab. r=mkmelin

This includes an incomplete implementation of RFC 2087 for the IMAP fakeserver, and resurrecting a long-dead
function in nsImapIncomingServer which should make future tests involving IMAP a bit easier to write.

Differential Revision: https://phabricator.services.mozilla.com/D194174

--HG--
extra : rebase_source : e70724b757af11fbb90b3881975eb7a3467cee5c
extra : amend_source : 3cc8117ce9a674c6edd08bbb35f249ddc3ac465e
This commit is contained in:
Geoff Lankow 2023-11-20 21:54:56 +13:00
Родитель 244dff4ca8
Коммит b3bcc45f50
7 изменённых файлов: 258 добавлений и 2 удалений

Просмотреть файл

@ -674,7 +674,7 @@ function openActivityMgr() {
function openFolderQuota() { function openFolderQuota() {
document document
.getElementById("tabmail") .getElementById("tabmail")
.currentAbout3Pane?.folderPane.editFolder("QuotaTab"); .currentAbout3Pane?.folderPane.editFolder(undefined, "QuotaTab");
} }
function openIMAccountMgr() { function openIMAccountMgr() {

Просмотреть файл

@ -4,6 +4,7 @@
const { const {
IMAP_GMAIL_extension, IMAP_GMAIL_extension,
IMAP_RFC2087_extension,
IMAP_RFC2197_extension, IMAP_RFC2197_extension,
IMAP_RFC2342_extension, IMAP_RFC2342_extension,
IMAP_RFC3348_extension, IMAP_RFC3348_extension,
@ -108,3 +109,32 @@ export class GmailServer extends IMAPServer {
this.testScope.registerCleanupFunction(() => this.close()); this.testScope.registerCleanupFunction(() => this.close());
} }
} }
/**
* A simple IMAP server, with RFC2087 extension, for testing purposes.
*/
export class QuotaServer extends IMAPServer {
open() {
this.daemon = new ImapDaemon();
this.server = new nsMailServer(daemon => {
const handler = new IMAP_RFC3501_handler(daemon, {
username: this.username,
});
mixinExtension(handler, IMAP_RFC2087_extension);
return handler;
}, this.daemon);
this.server.start();
this.testScope.registerCleanupFunction(() => this.close());
}
setQuota(folder, name, usage, limit) {
const mailbox = this.daemon.getMailbox(folder.name);
mailbox.quota = mailbox.quota ?? {};
if (limit) {
mailbox.quota[name] = { usage, limit };
} else {
delete mailbox.quota[name];
}
}
}

Просмотреть файл

@ -48,6 +48,7 @@ skip-if = headless
[browser_paneFocus.js] [browser_paneFocus.js]
[browser_paneSplitter.js] [browser_paneSplitter.js]
[browser_preferDisplayName.js] [browser_preferDisplayName.js]
[browser_quota.js]
[browser_searchBar.js] [browser_searchBar.js]
[browser_searchMessages.js] [browser_searchMessages.js]
[browser_spacesToolbar.js] [browser_spacesToolbar.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/. */
const { QuotaServer } = ChromeUtils.importESModule(
"resource://testing-common/IMAPServer.sys.mjs"
);
let imapServer;
const tabmail = document.getElementById("tabmail");
const about3Pane = tabmail.currentAbout3Pane;
let imapRootFolder, imapFolder;
add_setup(async function () {
// This test uses a single IMAP connection so that we can be sure it is idle
// before using it.
Services.prefs.setIntPref("mail.server.default.max_cached_connections", 1);
imapServer = new QuotaServer(this);
const imapAccount = MailServices.accounts.createAccount();
imapAccount.addIdentity(MailServices.accounts.createIdentity());
imapAccount.incomingServer = MailServices.accounts.createIncomingServer(
`${imapAccount.key}user`,
"localhost",
"imap"
);
imapAccount.incomingServer.port = imapServer.port;
imapAccount.incomingServer.username = "user";
imapAccount.incomingServer.password = "password";
imapAccount.incomingServer.deleteModel = Ci.nsMsgImapDeleteModels.IMAPDelete;
imapRootFolder = imapAccount.incomingServer.rootFolder;
imapFolder = imapRootFolder
.getFolderWithFlags(Ci.nsMsgFolderFlags.Inbox)
.QueryInterface(Ci.nsIMsgImapMailFolder);
registerCleanupFunction(() => {
MailServices.accounts.removeAccount(imapAccount, false);
Services.prefs.clearUserPref("mail.server.default.max_cached_connections");
});
});
add_task(async function () {
const quotaPanel = document.getElementById("quotaPanel");
// Load the folder with no quota.
about3Pane.displayFolder(imapFolder);
Assert.ok(BrowserTestUtils.is_hidden(quotaPanel), "panel should be hidden");
// Reload the folder with usage below mail.quota.mainwindow_threshold.show.
about3Pane.displayFolder(imapRootFolder);
Assert.ok(BrowserTestUtils.is_hidden(quotaPanel), "panel should be hidden");
await updateQuota(20, 123);
about3Pane.displayFolder(imapFolder);
Assert.ok(BrowserTestUtils.is_hidden(quotaPanel), "panel should be hidden");
// Reload the folder with usage above mail.quota.mainwindow_threshold.show
// but below mail.quota.mainwindow_threshold.warning.
about3Pane.displayFolder(imapRootFolder);
Assert.ok(BrowserTestUtils.is_hidden(quotaPanel), "panel should be hidden");
await updateQuota(98, 123);
about3Pane.displayFolder(imapFolder);
checkStatus(79, "98.0 KB", "123 KB");
// Reload the folder with usage above mail.quota.mainwindow_threshold.warning
// but below mail.quota.mainwindow_threshold.critical.
about3Pane.displayFolder(imapRootFolder);
Assert.ok(BrowserTestUtils.is_hidden(quotaPanel), "panel should be hidden");
await updateQuota(105, 123);
about3Pane.displayFolder(imapFolder);
checkStatus(85, "105 KB", "123 KB", "alert-warning");
about3Pane.displayFolder(imapRootFolder);
Assert.ok(BrowserTestUtils.is_hidden(quotaPanel), "panel should be hidden");
// Reload the folder with usage above mail.quota.mainwindow_threshold.critical.
await updateQuota(120, 123);
about3Pane.displayFolder(imapFolder);
checkStatus(97, "120 KB", "123 KB", "alert-critical");
// Click on the status bar panel to open the folder properties dialog.
const folderPropsPromise = BrowserTestUtils.promiseAlertDialog(
undefined,
"chrome://messenger/content/folderProps.xhtml",
{
async callback(win) {
await SimpleTest.promiseFocus(win);
const doc = win.document;
const tabBox = doc.getElementById("folderPropTabBox");
const quotaStatus = doc.getElementById("folderQuotaStatus");
const quotaDetails = doc.getElementById("quotaDetails");
const cancelButton = doc.querySelector("dialog").getButton("cancel");
Assert.equal(
tabBox.selectedPanel.id,
"quotaPanel",
"quota panel should be selected"
);
await TestUtils.waitForCondition(
() => BrowserTestUtils.is_hidden(quotaStatus),
"waiting for quota UI to update"
);
Assert.ok(
BrowserTestUtils.is_visible(quotaDetails),
"quota details should be visible"
);
Assert.equal(
quotaDetails.childElementCount,
1,
"one quota should be displayed"
);
const li = quotaDetails.firstElementChild;
Assert.equal(li.querySelector("span").textContent, "STORAGE");
Assert.equal(li.querySelector("progress").value, 120);
Assert.equal(li.querySelector("progress").max, 123);
Assert.deepEqual(
document.l10n.getAttributes(li.querySelector("span:nth-child(3)")),
{ id: "quota-percent-used", args: { percent: 97 } }
);
Assert.equal(
li.querySelector("span:nth-child(4)").textContent,
"120 KB / 123 KB"
);
cancelButton.click();
},
}
);
EventUtils.synthesizeMouseAtCenter(quotaPanel, {}, window);
await folderPropsPromise;
});
async function updateQuota(usage, limit) {
// Drain the event queue so that the folder just displayed starts to use the
// IMAP connection.
await TestUtils.waitForTick();
await TestUtils.waitForCondition(() => {
try {
return imapFolder.server.numIdleConnections;
} catch (ex) {
console.error(ex);
}
return 0;
}, "waiting for IMAP connection to become idle");
imapServer.setQuota(imapFolder, "STORAGE", usage, limit);
// Force the folder to be updated from the server.
await new Promise(resolve =>
imapFolder.updateFolderWithListener(window.msgWindow, {
OnStartRunningUrl() {},
OnStopRunningUrl() {
resolve();
},
})
);
await TestUtils.waitForCondition(
() => imapFolder.getQuota().length == 1,
"waiting for the folder to have a quota"
);
}
function checkStatus(percent, usage, limit, className) {
const quotaPanel = document.getElementById("quotaPanel");
const quotaMeter = document.getElementById("quotaMeter");
const quotaLabel = document.getElementById("quotaLabel");
Assert.ok(
BrowserTestUtils.is_visible(quotaPanel),
"status bar panel should be visible"
);
Assert.equal(
quotaMeter.value,
percent,
"meter should have the correct value"
);
Assert.equal(quotaMeter.max, 100, "meter should have the correct maximum");
Assert.deepEqual(
document.l10n.getAttributes(quotaLabel),
{
id: "quota-panel-percent-used",
args: { percent, usage, limit },
},
"label should have the correct text"
);
Assert.equal(
quotaPanel.classList.contains("alert-warning"),
className == "alert-warning",
"panel should have the correct classes"
);
Assert.equal(
quotaPanel.classList.contains("alert-critical"),
className == "alert-critical",
"panel should have the correct classes"
);
}

Просмотреть файл

@ -73,6 +73,9 @@ interface nsIImapIncomingServer : nsISupports {
/// Max age of messages we will autosync to, or keep in offline store. /// Max age of messages we will autosync to, or keep in offline store.
attribute long autoSyncMaxAgeDays; attribute long autoSyncMaxAgeDays;
/** Count of non-busy connections in cache. */
readonly attribute long numIdleConnections;
/** /**
* See IMAP RFC 6855 * See IMAP RFC 6855
*/ */

Просмотреть файл

@ -62,7 +62,6 @@ class nsImapIncomingServer : public nsMsgIncomingServer,
NS_IMETHOD GetSearchScope(nsMsgSearchScopeValue* searchScope) override; NS_IMETHOD GetSearchScope(nsMsgSearchScopeValue* searchScope) override;
NS_IMETHOD GetServerRequiresPasswordForBiff( NS_IMETHOD GetServerRequiresPasswordForBiff(
bool* aServerRequiresPasswordForBiff) override; bool* aServerRequiresPasswordForBiff) override;
NS_IMETHOD GetNumIdleConnections(int32_t* aNumIdleConnections);
NS_IMETHOD ForgetSessionPassword(bool modifyLogin) override; NS_IMETHOD ForgetSessionPassword(bool modifyLogin) override;
NS_IMETHOD GetMsgFolderFromURI(nsIMsgFolder* aFolderResource, NS_IMETHOD GetMsgFolderFromURI(nsIMsgFolder* aFolderResource,
const nsACString& aURI, const nsACString& aURI,

Просмотреть файл

@ -13,6 +13,7 @@ var EXPORTED_SYMBOLS = [
"IMAP_GMAIL_extension", "IMAP_GMAIL_extension",
"IMAP_MOVE_extension", "IMAP_MOVE_extension",
"IMAP_CUSTOM_extension", "IMAP_CUSTOM_extension",
"IMAP_RFC2087_extension",
"IMAP_RFC2197_extension", "IMAP_RFC2197_extension",
"IMAP_RFC2342_extension", "IMAP_RFC2342_extension",
"IMAP_RFC3348_extension", "IMAP_RFC3348_extension",
@ -2185,6 +2186,23 @@ var IMAP_CUSTOM_extension = {
kCapabilities: ["X-CUSTOM1"], kCapabilities: ["X-CUSTOM1"],
}; };
// RFC 2087: Quota (incomplete implementation)
var IMAP_RFC2087_extension = {
GETQUOTAROOT(args) {
const mailbox = this._daemon.getMailbox(args[0]);
const quota = mailbox.quota ?? {};
const response = [`* QUOTAROOT INBOX ""`];
for (const [name, { usage, limit }] of Object.entries(quota)) {
response.push(`* QUOTA "" (${name} ${usage} ${limit})`);
}
response.push("OK Getquota completed");
return response.join("\0");
},
kCapabilities: ["QUOTA"],
_argFormat: { GETQUOTAROOT: ["mailbox"] },
_enabledCommands: { 1: ["GETQUOTAROOT"], 2: ["GETQUOTAROOT"] },
};
// RFC 2197: ID // RFC 2197: ID
var IMAP_RFC2197_extension = { var IMAP_RFC2197_extension = {
ID(args) { ID(args) {