/* * 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 https://mozilla.org/MPL/2.0/. */ // Writing the fake zip and deleting it can take some time. requestLongerTimeout(2); const { MockFilePicker } = ChromeUtils.importESModule( "resource://testing-common/MockFilePicker.sys.mjs" ); function checkSteps(importDocument, currentStep, totalSteps) { const stepNav = importDocument.getElementById("stepNav"); is(stepNav.childElementCount, totalSteps, "Expected amount of steps"); ok( stepNav .querySelector(`*:nth-child(${currentStep})`) ?.classList.contains("current"), `Expected step ${currentStep} is active` ); } function checkVisiblePane(importDocument, activePaneId, activeStepId) { const panes = importDocument.querySelectorAll(".tabPane"); for (const pane of panes) { if (pane.id === activePaneId) { ok(BrowserTestUtils.isVisible(pane), `Pane ${activePaneId} is visible`); const steps = pane.querySelectorAll("section[id]"); for (const step of steps) { if (step.id === activeStepId) { ok( BrowserTestUtils.isVisible(step), `Step ${activeStepId} is visible` ); } else { ok(BrowserTestUtils.isHidden(step), `Step ${step.id} is hidden`); } } } else { ok(BrowserTestUtils.isHidden(pane), `Pane ${pane.id} is not visible`); } } } add_setup(() => { MockFilePicker.init(window.browsingContext); registerCleanupFunction(() => { MockFilePicker.cleanup(); }); }); add_task(async function testProfileImport() { Services.fog.testResetFOG(); const profileDir = await IOUtils.createUniqueDirectory( PathUtils.tempDir, "profile-tmp" ); await IOUtils.writeUTF8( PathUtils.join(profileDir, "prefs.js"), [ ["mail.smtpserver.smtp1.username", "smtp-user-1"], ["mail.smtpservers", "smtp1"], ["mail.identity.id1.smtpServer", "smtp1"], ["mail.server.server2.type", "none"], ["mail.server.server6.type", "imap"], ["mail.account.account1.server", "server2"], ["mail.account.account2.server", "server6"], ["mail.account.account2.identities", "id1"], ["mail.accountmanager.accounts", "account2,account1"], ] .map( ([name, value]) => `user_pref(${JSON.stringify(name)}, ${JSON.stringify(value)});` ) .join("\n") ); const badProfileDir = await IOUtils.createUniqueDirectory( PathUtils.tempDir, "profile-bad" ); registerCleanupFunction(async () => { await IOUtils.remove(profileDir, { recursive: true, }); await IOUtils.remove(badProfileDir, { recursive: true, }); }); const tab = await new Promise(resolve => { const newTab = window.openTab("contentTab", { url: "about:import", onLoad() { resolve(newTab); }, }); }); const importDocument = tab.browser.contentDocument; ok( BrowserTestUtils.isHidden(importDocument.getElementById("exportDocs")), "Export docs link is hidden" ); ok( BrowserTestUtils.isVisible(importDocument.getElementById("importDocs")), "Import docs link is visible" ); checkSteps(importDocument, 1, 4); checkVisiblePane(importDocument, "tabPane-start", "start-sources"); ok( importDocument.querySelector('#start-sources input[value="Thunderbird"]') .checked, "Thunderbird profile is selected by default" ); ok( BrowserTestUtils.isHidden(importDocument.getElementById("startBackButton")), "Back button is hidden in first step" ); await BrowserTestUtils.synthesizeMouseAtCenter( "#tabPane-start .continue", {}, tab.browser ); const appPane = importDocument.getElementById("tabPane-app"); await BrowserTestUtils.waitForMutationCondition( appPane, { attributes: true, }, () => BrowserTestUtils.isVisible(appPane) ); checkSteps(importDocument, 2, 4); checkVisiblePane(importDocument, "tabPane-app", "app-profiles"); ok( BrowserTestUtils.isVisible( importDocument.getElementById("profileBackButton") ), "Back button is visible" ); // Try to import from a source that doesn't contain a profile. MockFilePicker.setFiles([await IOUtils.getFile(badProfileDir)]); await BrowserTestUtils.synthesizeMouseAtCenter( '#filePickerList [value="file-picker-dir"]', {}, tab.browser ); await BrowserTestUtils.synthesizeMouseAtCenter( "#profileNextButton", {}, tab.browser ); const itemsStep = importDocument.getElementById("app-items"); await BrowserTestUtils.waitForMutationCondition( itemsStep, { attributes: true, }, () => BrowserTestUtils.isVisible(itemsStep) ); checkSteps(importDocument, 3, 4); checkVisiblePane(importDocument, "tabPane-app", "app-items"); Assert.ok( BrowserTestUtils.isVisible( importDocument.getElementById("appSourceInvalid") ), "invalid source message should be visible" ); Assert.ok( BrowserTestUtils.isHidden(importDocument.getElementById("appSourceItems")), "item selection should be hidden" ); Assert.ok( importDocument.getElementById("profileNextButton").disabled, "forward button should be disabled" ); // Go back, and try to import from a source that does contain a profile. await BrowserTestUtils.synthesizeMouseAtCenter( "#profileBackButton", {}, tab.browser ); MockFilePicker.setFiles([await IOUtils.getFile(profileDir)]); await BrowserTestUtils.synthesizeMouseAtCenter( '#filePickerList [value="file-picker-dir"]', {}, tab.browser ); await BrowserTestUtils.synthesizeMouseAtCenter( "#profileNextButton", {}, tab.browser ); await BrowserTestUtils.waitForMutationCondition( itemsStep, { attributes: true, }, () => BrowserTestUtils.isVisible(itemsStep) ); checkSteps(importDocument, 3, 4); checkVisiblePane(importDocument, "tabPane-app", "app-items"); Assert.ok( BrowserTestUtils.isHidden( importDocument.getElementById("appSourceInvalid") ), "invalid source message should be hidden" ); Assert.ok( BrowserTestUtils.isVisible(importDocument.getElementById("appSourceItems")), "item selection should be visible" ); Assert.ok( !importDocument.getElementById("profileNextButton").disabled, "forward button should not be disabled" ); await BrowserTestUtils.synthesizeMouseAtCenter( "#profileNextButton", {}, tab.browser ); const summaryStep = importDocument.getElementById("app-summary"); await BrowserTestUtils.waitForMutationCondition( summaryStep, { attributes: true, }, () => BrowserTestUtils.isVisible(summaryStep) ); checkSteps(importDocument, 4, 4); checkVisiblePane(importDocument, "tabPane-app", "app-summary"); ok( BrowserTestUtils.isHidden( importDocument.getElementById("profileNextButton") ), "Can't advance from summary step" ); await BrowserTestUtils.synthesizeMouseAtCenter( "#appStartImport", {}, tab.browser ); const progressPane = importDocument.querySelector( "#app-summary .progressPane" ); await BrowserTestUtils.waitForMutationCondition( appPane, { attributes: true, }, () => BrowserTestUtils.isVisible(progressPane) ); ok( BrowserTestUtils.isHidden(importDocument.getElementById("appStartImport")), "Import button is hidden while import is in progress" ); const finish = importDocument.querySelector("#app-summary .progressFinish"); await BrowserTestUtils.waitForMutationCondition( appPane, { attributes: true, }, () => BrowserTestUtils.isVisible(finish) ); ok( BrowserTestUtils.isVisible(progressPane), "When import succeeds and finish is shown, progress is still displayed" ); // We close the tab ourselves instead of hitting the finish button, since // restarting Thunderbird within the test is a headache. document.getElementById("tabmail").closeTab(tab); const gleanEvents = Glean.mail.import.testGetValue(); Assert.equal( gleanEvents.length, 1, "the import should have been recorded in telemetry" ); Assert.deepEqual( gleanEvents[0].extra, { importer: "Thunderbird,directory", types: "accounts,addressBooks,calendars,mailMessages", result: "succeeded", }, "the telemetry data should be correct" ); }); add_task(async function testImportLargeZIP() { const profileDir = await IOUtils.createUniqueDirectory( PathUtils.tempDir, "profile-tmp" ); const profileZip = PathUtils.join(profileDir, "profile.zip"); // This block makes sure the ZIP file's fake contents go out of scope once // written out. { const halfAGigabyte = new Uint8Array(2 ** 29); await IOUtils.write(profileZip, halfAGigabyte); info("ZIP is 0.5 GB big now"); for (let i = 0; i < 3; ++i) { await IOUtils.write(profileZip, halfAGigabyte, { mode: "append" }); info(`ZIP is ${(i + 2) * 0.5} GB big now`); } } await IOUtils.write( profileZip, new Uint8Array(2), // These are extra bytes beyond 2 GB { mode: "append", } ); const filePath = Services.io .newURI(PathUtils.toFileURI(profileZip)) .QueryInterface(Ci.nsIFileURL); MockFilePicker.setFiles([filePath.file]); registerCleanupFunction(async () => { await IOUtils.remove(profileDir, { recursive: true, }); }); const tab = await new Promise(resolve => { const newTab = window.openTab("contentTab", { url: "about:import", onLoad() { resolve(newTab); }, }); }); const importDocument = tab.browser.contentDocument; checkSteps(importDocument, 1, 4); checkVisiblePane(importDocument, "tabPane-start", "start-sources"); ok( importDocument.querySelector('#start-sources input[value="Thunderbird"]') .checked, "Thunderbird profile is selected by default" ); ok( BrowserTestUtils.isHidden(importDocument.getElementById("startBackButton")), "Back button is hidden in first step" ); await BrowserTestUtils.synthesizeMouseAtCenter( "#tabPane-start .continue", {}, tab.browser ); const appPane = importDocument.getElementById("tabPane-app"); await BrowserTestUtils.waitForMutationCondition( appPane, { attributes: true, }, () => BrowserTestUtils.isVisible(appPane) ); checkSteps(importDocument, 2, 4); checkVisiblePane(importDocument, "tabPane-app", "app-profiles"); ok( BrowserTestUtils.isVisible( importDocument.getElementById("profileBackButton") ), "Back button is visible" ); await BrowserTestUtils.synthesizeMouseAtCenter( "#appFilePickerZip", {}, tab.browser ); await BrowserTestUtils.synthesizeMouseAtCenter( "#profileNextButton", {}, tab.browser ); const notificationBox = importDocument.getElementById("errorNotifications"); await BrowserTestUtils.waitForMutationCondition( notificationBox, { childList: true, }, () => notificationBox.childElementCount > 0 ); document.getElementById("tabmail").closeTab(tab); }); /** * Open the second pane for each of the options listed in the first pane. * This loads the each importer's module and calls `getSourceProfiles()` * which, if nothing else, proves the module loads and the function works * without error. */ add_task(async function testImportModules() { const tab = await new Promise(resolve => { const newTab = window.openTab("contentTab", { url: "about:import", onLoad() { resolve(newTab); }, }); }); const importDocument = tab.browser.contentDocument; checkVisiblePane(importDocument, "tabPane-start", "start-sources"); const sources = importDocument.querySelectorAll("#start-sources input"); for (const source of sources) { if (source.value == "Outlook") { // We can't load the Outlook importer in a CI test, because it will // trigger the MAPI code, display an error message from Windows, and // never return. continue; } info(`Opening ${source.value} pane.`); await BrowserTestUtils.synthesizeMouseAtCenter( `#start-sources input[value="${source.value}"]`, {}, tab.browser ); await BrowserTestUtils.synthesizeMouseAtCenter( "#tabPane-start .continue", {}, tab.browser ); if (source.value != "file") { checkVisiblePane(importDocument, "tabPane-app", "app-profiles"); Assert.ok( BrowserTestUtils.isVisible( importDocument.getElementById("profileBackButton") ), "Back button is visible" ); await BrowserTestUtils.synthesizeMouseAtCenter( "#profileBackButton", {}, tab.browser ); } else { checkVisiblePane(importDocument, "tabPane-start", "start-file"); Assert.ok( BrowserTestUtils.isVisible( importDocument.getElementById("startBackButton") ), "Back button is visible" ); await BrowserTestUtils.synthesizeMouseAtCenter( "#startBackButton", {}, tab.browser ); } checkVisiblePane(importDocument, "tabPane-start", "start-sources"); } document.getElementById("tabmail").closeTab(tab); });