Bug 1919665 - Account Hub Sync Calendars and Address Books. r=freaktechnik

**How To Test**
- Get through to the sync accounts view
- Hit continue
- Close the modal and go to your address books and calendars
- Check if they are there and synced

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

--HG--
extra : moz-landing-system : lando
This commit is contained in:
Vineet Deo 2024-11-30 22:16:08 +00:00
Родитель a0a1895609
Коммит d6a66505f4
5 изменённых файлов: 180 добавлений и 32 удалений

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

@ -154,6 +154,31 @@ class EmailSyncAccountsForm extends AccountHubStep {
}
}
/**
* Returns the chosen address books and calendars
*
* @returns {SyncAccounts} The sync accounts selected.
*/
captureState() {
const checkedAddressBookUrls = Array.from(
this.querySelectorAll("#addressBooks input:checked:enabled"),
checkedAddressBook => checkedAddressBook.dataset.url
);
const checkedCalendarsUrls = Array.from(
this.querySelectorAll("#calendars input:checked:enabled"),
checkedCalendar => checkedCalendar.dataset.url
);
return {
calendars: this.#availableSyncAccounts.calendars.filter(calendar =>
checkedCalendarsUrls.includes(calendar.uri?.spec)
),
addressBooks: this.#availableSyncAccounts.addressBooks.filter(
addressBook => checkedAddressBookUrls.includes(addressBook.url.href)
),
};
}
/**
* Removes all of the input labels and resets #availableSyncAccounts.
*/
@ -187,10 +212,15 @@ class EmailSyncAccountsForm extends AccountHubStep {
input.type = "checkbox";
input.checked = true;
input.disabled = syncAccount.existing;
input.dataset.url =
inputClass === "address-book-input"
? syncAccount.url.href
: syncAccount.uri?.spec;
const title = document.createElement("span");
title.textContent = syncAccount.name;
label.append(input);
label.append(title);
return label;
}
@ -212,6 +242,7 @@ class EmailSyncAccountsForm extends AccountHubStep {
? 0
: this.#availableSyncAccounts[type].length;
}
/**
* Update strings associated with the sync account type.
*

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

@ -21,8 +21,7 @@
<label class="toggle-container-with-text">
<input id="rememberPassword"
class="check-button"
type="checkbox"
disabled="disabled" />
type="checkbox" />
<span class="checkbox-label"
data-l10n-id="account-setup-remember-password"
data-l10n-attrs="accesskey">

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

@ -318,7 +318,7 @@ class AccountHubEmail extends HTMLElement {
this.#clearNotifications();
this.#currentState = subview;
await this.#loadTemplateScript(this.#states[subview].templateId);
this.#states[subview].subview.hidden = false;
this.#currentSubview.hidden = false;
this.#setFooterButtons();
}
@ -357,8 +357,7 @@ class AccountHubEmail extends HTMLElement {
*/
#clearNotifications() {
if (this.#currentState) {
const stateDetails = this.#states[this.#currentState];
stateDetails.subview.clearNotifications();
this.#currentSubview.clearNotifications();
}
}
@ -432,7 +431,7 @@ class AccountHubEmail extends HTMLElement {
this.#hasCancelled ? this.#currentState : stateDetails.previousStep
);
} catch (error) {
stateDetails.subview.showNotification({
this.#currentSubview.showNotification({
title: error.cause.code,
description: error.cause.text,
error,
@ -449,7 +448,7 @@ class AccountHubEmail extends HTMLElement {
case "forward":
try {
this.#hasCancelled = false;
const stateData = stateDetails.subview.captureState();
const stateData = this.#currentSubview.captureState();
await this.#handleForwardAction(this.#currentState, stateData);
} catch (error) {
this.#handleAbortable();
@ -464,7 +463,7 @@ class AccountHubEmail extends HTMLElement {
try {
await this.#handleCustomAction(this.#currentState, event);
} catch (error) {
stateDetails.subview.showNotification({
this.#currentSubview.showNotification({
title: error.title,
description: error.text,
error,
@ -474,7 +473,7 @@ class AccountHubEmail extends HTMLElement {
break;
case "edit-configuration":
this.#currentConfig = this.#fillAccountConfig(
stateDetails.subview.captureState()
this.#currentSubview.captureState()
);
// The edit configuration button was pressed.
await this.#initUI("incomingConfigSubview");
@ -485,7 +484,7 @@ class AccountHubEmail extends HTMLElement {
try {
this.#emailFooter.toggleForwardDisabled(!event.detail.completed);
} catch (error) {
stateDetails.subview.showNotification({
this.#currentSubview.showNotification({
title: error.title,
description: error.text,
error,
@ -504,7 +503,7 @@ class AccountHubEmail extends HTMLElement {
stateData = this.#fillAccountConfig(stateData);
await this.#advancedSetup(stateData);
} catch (error) {
stateDetails.subview.showNotification({
this.#currentSubview.showNotification({
title: error.title,
description: error.text,
error,
@ -524,7 +523,6 @@ class AccountHubEmail extends HTMLElement {
* @param {string} currentState - The current state of the email flow.
*/
#handleBackAction(currentState) {
const stateDetails = this.#states[this.#currentState];
switch (currentState) {
case "autoConfigSubview":
this.#hasCancelled = true;
@ -536,7 +534,7 @@ class AccountHubEmail extends HTMLElement {
case "outgoingConfigSubview":
// Set the currentConfig outgoing to the updated fields in this form.
this.#currentConfig.outgoing =
stateDetails.subview.captureState().outgoing;
this.#currentSubview.captureState().outgoing;
break;
case "emailPasswordSubview":
break;
@ -580,6 +578,7 @@ class AccountHubEmail extends HTMLElement {
});
}
this.#hasCancelled = false;
this.#stopLoading();
this.#setCurrentConfigForSubview();
break;
}
@ -604,7 +603,6 @@ class AccountHubEmail extends HTMLElement {
break;
case "incomingConfigSubview":
await this.#initUI(this.#states[this.#currentState].nextStep);
this.#currentConfig.incoming = stateData.config.incoming;
this.#setCurrentConfigForSubview();
@ -674,10 +672,24 @@ class AccountHubEmail extends HTMLElement {
});
break;
case "emailSyncAccountsSubview":
this.#currentSubview.showNotification({
fluentTitleId: "account-hub-email-added-success",
type: "success",
});
try {
// Add the selected sync address books and calendars.
this.#addSyncAccounts(stateData);
// TODO: Show the account added page when it's finished.
// await this.#initUI(this.#states[this.#currentState].nextStep);
// this.#currentSubview.showNotification({
// fluentTitleId: "account-hub-email-added-success",
// type: "success",
// });
} catch (error) {
this.#currentSubview.showNotification({
fluentTitleId: "account-hub-unable-to-sync-accounts",
type: "error",
error,
});
}
break;
case "emailAddedSubview":
break;
@ -711,7 +723,7 @@ class AccountHubEmail extends HTMLElement {
if (config.isComplete()) {
this.#stopLoading();
this.#states[this.#currentState].subview.showNotification({
this.#currentSubview.showNotification({
fluentTitleId: "account-setup-success-half-manual",
type: "success",
});
@ -1144,6 +1156,27 @@ class AccountHubEmail extends HTMLElement {
return cals;
}
/**
* @typedef {object} SyncAccounts
* @property {Array} calendars - The selected calendars.
* @property {Array} addressBooks - The selected address books.
*/
/**
* Adds selected calendars and address books to Thunderbird.
*
* @type {SyncAccounts} syncAccounts - The sync accounts for the user.
*/
#addSyncAccounts(syncAccounts) {
for (const calendar of syncAccounts.calendars) {
cal.manager.registerCalendar(calendar);
}
for (const addressBook of syncAccounts.addressBooks) {
addressBook.create();
}
}
/**
* Request the opening of the account manager after the creation of a new
* account and reset any leftover data in the current setup flow.

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

@ -174,6 +174,8 @@ account-hub-sync-success = Thunderbird found some connected services
account-hub-sync-failure = Thunderbird was unable to find connected services
account-hub-unable-to-sync-accounts = Thunderbird was unable to connect the selected services
account-hub-email-added-success = Email account connected successfully
account-hub-config-test-success = Configuration settings valid

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

@ -13,6 +13,10 @@ const { MailServices } = ChromeUtils.importESModule(
);
const { DNS } = ChromeUtils.importESModule("resource:///modules/DNS.sys.mjs");
const { cal } = ChromeUtils.importESModule(
"resource:///modules/calendar/calUtils.sys.mjs"
);
let emailUser;
const PREF_NAME = "mailnews.auto_config_url";
const PREF_VALUE = Services.prefs.getCharPref(PREF_NAME);
@ -835,8 +839,14 @@ add_task(async function test_account_enter_password_imap_account() {
"The email password subview should be hidden."
);
const imapAccount = MailServices.accounts.accounts.find(
account => account.identities[0].email === emailUser.email
let imapAccount;
await TestUtils.waitForCondition(
() =>
(imapAccount = MailServices.accounts.accounts.find(
account => account.identities[0]?.email === emailUser.email
)),
"The imap account should be created."
);
Assert.ok(imapAccount, "IMAP account should be created");
@ -913,11 +923,15 @@ add_task(async function test_account_load_sync_accounts_imap_account() {
"The email password subview should be hidden."
);
const imapAccount = MailServices.accounts.accounts.find(
account => account.identities[0].email === emailUser.email
);
let imapAccount;
Assert.ok(imapAccount, "IMAP account should be created");
await TestUtils.waitForCondition(
() =>
(imapAccount = MailServices.accounts.accounts.find(
account => account.identities[0]?.email === emailUser.email
)),
"The imap account should be created."
);
await TestUtils.waitForCondition(
() =>
@ -988,7 +1002,7 @@ add_task(async function test_account_load_sync_accounts_imap_account() {
"There should 1 checked address book."
);
Assert.equal(
addressBooks.item(0).textContent,
addressBooks[0].textContent,
"You found me!",
"The address book found should have the name - You found me!"
);
@ -1001,26 +1015,26 @@ add_task(async function test_account_load_sync_accounts_imap_account() {
"There should 2 checked calendars."
);
Assert.equal(
calendars.item(0).textContent,
calendars[0].textContent,
"You found me!",
"The first calendar found should have the name - You found me!"
);
Assert.equal(
calendars.item(1).textContent,
calendars[1].textContent,
"Röda dagar",
"The second calendar found should have the name - Röda dagar"
);
// Unchecking an input should update the count label, and the select toggle
// fluent id.
const checkEvent = BrowserTestUtils.waitForEvent(
addressBooks.item(0).querySelector("input"),
let checkEvent = BrowserTestUtils.waitForEvent(
addressBooks[0].querySelector("input"),
"change",
true,
event => event.target.checked === false
event => !event.target.checked
);
EventUtils.synthesizeMouseAtCenter(
addressBooks.item(0).querySelector("input"),
addressBooks[0].querySelector("input"),
{}
);
await checkEvent;
@ -1054,6 +1068,75 @@ add_task(async function test_account_load_sync_accounts_imap_account() {
"Calendars count should be 0."
);
// Select the first calendar and address book and click continue to add
// the selected calendar and address book.
checkEvent = BrowserTestUtils.waitForEvent(
addressBooks[0].querySelector("input"),
"change",
true,
event => event.target.checked
);
EventUtils.synthesizeMouseAtCenter(
addressBooks[0].querySelector("input"),
{}
);
await checkEvent;
checkEvent = BrowserTestUtils.waitForEvent(
calendars[0].querySelector("input"),
"change",
true,
event => event.target.checked
);
EventUtils.synthesizeMouseAtCenter(calendars[0].querySelector("input"), {});
await checkEvent;
Assert.equal(
syncAccountsTemplate.l10n.getAttributes(selectedAddressBooks).args.count,
1,
"Address books count should be 1."
);
Assert.equal(
syncAccountsTemplate.l10n.getAttributes(selectedCalendars).args.count,
1,
"Calendars count should be 1."
);
const calendarPromise = new Promise(resolve => {
const observer = {
onCalendarRegistered(calendar) {
cal.manager.removeObserver(this);
resolve(calendar);
},
onCalendarUnregistering() {},
onCalendarDeleting() {},
};
cal.manager.addObserver(observer);
});
const addressBookDirectoryPromise = TestUtils.topicObserved(
"addrbook-directory-synced"
);
EventUtils.synthesizeMouseAtCenter(footerForward, {});
// Check existance of address book and calendar.
const [addressBookDirectory] = await addressBookDirectoryPromise;
Assert.equal(addressBookDirectory.dirName, "You found me!");
Assert.equal(
addressBookDirectory.dirType,
Ci.nsIAbManager.CARDDAV_DIRECTORY_TYPE
);
Assert.equal(
addressBookDirectory.getStringValue("carddav.url", ""),
"https://example.org/browser/comm/mail/components/addrbook/test/browser/data/addressbook.sjs"
);
const calendar = await calendarPromise;
Assert.equal(calendar.name, "You found me!");
Assert.equal(calendar.type, "caldav");
// Remove the address book and calendar.
MailServices.ab.deleteAddressBook(addressBookDirectory.URI);
cal.manager.removeCalendar(calendar);
MailServices.accounts.removeAccount(imapAccount);
Services.logins.removeAllLogins();