/* Any copyright is dedicated to the Public Domain. * http://creativecommons.org/publicdomain/zero/1.0/ */ var {Log} = Cu.import("resource://gre/modules/Log.jsm", {}); var {Weave} = Cu.import("resource://services-sync/main.js", {}); var stringBundle = Cc["@mozilla.org/intl/stringbundle;1"] .getService(Ci.nsIStringBundleService) .createBundle("chrome://weave/locale/services/sync.properties"); // ensure test output sees log messages. Log.repository.getLogger("browserwindow.syncui").addAppender(new Log.DumpAppender()); // Send the specified sync-related notification and return a promise that // resolves once gSyncUI._promiseUpateUI is complete and the UI is ready to check. function notifyAndPromiseUIUpdated(topic) { return new Promise(resolve => { // Instrument gSyncUI so we know when the update is complete. let oldPromiseUpdateUI = gSyncUI._promiseUpdateUI.bind(gSyncUI); gSyncUI._promiseUpdateUI = function() { return oldPromiseUpdateUI().then(() => { // Restore our override. gSyncUI._promiseUpdateUI = oldPromiseUpdateUI; // Resolve the promise so the caller knows the update is done. resolve(); }); }; // Now send the notification. Services.obs.notifyObservers(null, topic, null); }); } // Sync manages 3 broadcasters so the menus correctly reflect the Sync state. // Only one of these 3 should ever be visible - pass the ID of the broadcaster // you expect to be visible and it will check it's the only one that is. function checkBroadcasterVisible(broadcasterId) { let all = ["sync-reauth-state", "sync-setup-state", "sync-syncnow-state"]; Assert.ok(all.indexOf(broadcasterId) >= 0, "valid id"); for (let check of all) { let eltHidden = document.getElementById(check).hidden; Assert.equal(eltHidden, check == broadcasterId ? false : true, check); } } function promiseObserver(topic) { return new Promise(resolve => { let obs = (aSubject, aTopic, aData) => { Services.obs.removeObserver(obs, aTopic); resolve(aSubject); } Services.obs.addObserver(obs, topic, false); }); } function checkButtonTooltips(stringPrefix) { for (let butId of ["PanelUI-remotetabs-syncnow", "PanelUI-fxa-icon"]) { let text = document.getElementById(butId).getAttribute("tooltiptext"); let desc = `Text is "${text}", expecting it to start with "${stringPrefix}"` Assert.ok(text.startsWith(stringPrefix), desc); } } add_task(function* prepare() { // add the Sync button to the toolbar so we can get it! CustomizableUI.addWidgetToArea("sync-button", CustomizableUI.AREA_NAVBAR); registerCleanupFunction(() => { CustomizableUI.removeWidgetFromArea("sync-button"); }); let xps = Components.classes["@mozilla.org/weave/service;1"] .getService(Components.interfaces.nsISupports) .wrappedJSObject; yield xps.whenLoaded(); // Put Sync and the UI into a known state. Weave.Status.login = Weave.LOGIN_FAILED_NO_USERNAME; yield notifyAndPromiseUIUpdated("weave:service:login:error"); checkBroadcasterVisible("sync-setup-state"); checkButtonTooltips("Sign In To Sync"); // mock out the "_needsSetup()" function so we don't short-circuit. let oldNeedsSetup = window.gSyncUI._needsSetup; window.gSyncUI._needsSetup = () => Promise.resolve(false); registerCleanupFunction(() => { window.gSyncUI._needsSetup = oldNeedsSetup; // and an observer to set the state back to what it should be now we've // restored the stub. Services.obs.notifyObservers(null, "weave:service:login:finish", null); }); // and a notification to have the state change away from "needs setup" yield notifyAndPromiseUIUpdated("weave:service:login:finish"); checkBroadcasterVisible("sync-syncnow-state"); // open the sync-button panel so we can check elements in that. document.getElementById("sync-button").click(); }); add_task(function* testSyncNeedsVerification() { // mock out the "_needsVerification()" function let oldNeedsVerification = window.gSyncUI._needsVerification; window.gSyncUI._needsVerification = () => true; try { // a notification for the state change yield notifyAndPromiseUIUpdated("weave:service:login:finish"); checkButtonTooltips("Verify"); } finally { window.gSyncUI._needsVerification = oldNeedsVerification; } }); add_task(function* testSyncLoginError() { checkBroadcasterVisible("sync-syncnow-state"); // Pretend we are in a "login failed" error state Weave.Status.sync = Weave.LOGIN_FAILED; Weave.Status.login = Weave.LOGIN_FAILED_LOGIN_REJECTED; yield notifyAndPromiseUIUpdated("weave:ui:sync:error"); // But the menu *should* reflect the login error. checkBroadcasterVisible("sync-reauth-state"); // The tooltips for the buttons should also reflect it. checkButtonTooltips("Reconnect"); // Now pretend we just had a successful login - the error notification should go away. Weave.Status.sync = Weave.STATUS_OK; Weave.Status.login = Weave.LOGIN_SUCCEEDED; yield notifyAndPromiseUIUpdated("weave:service:login:start"); yield notifyAndPromiseUIUpdated("weave:service:login:finish"); // The menus should be back to "all good" checkBroadcasterVisible("sync-syncnow-state"); }); function checkButtonsStatus(shouldBeActive) { for (let eid of [ "sync-status", // the broadcaster itself. "sync-button", // the main sync button which observes the broadcaster "PanelUI-fxa-icon", // the sync icon in the fxa footer that observes it. ]) { let elt = document.getElementById(eid); if (shouldBeActive) { Assert.equal(elt.getAttribute("syncstatus"), "active", `${eid} should be active`); } else { Assert.ok(!elt.hasAttribute("syncstatus"), `${eid} should have no status attr`); } } } function* testButtonActions(startNotification, endNotification, expectActive = true) { checkButtonsStatus(false); // pretend a sync is starting. yield notifyAndPromiseUIUpdated(startNotification); checkButtonsStatus(expectActive); // and has stopped yield notifyAndPromiseUIUpdated(endNotification); checkButtonsStatus(false); } function *doTestButtonActivities() { // logins do not "activate" the spinner/button as they may block and make // the UI look like Sync is never completing. yield testButtonActions("weave:service:login:start", "weave:service:login:finish", false); yield testButtonActions("weave:service:login:start", "weave:service:login:error", false); // But notifications for Sync itself should activate it. yield testButtonActions("weave:service:sync:start", "weave:service:sync:finish"); yield testButtonActions("weave:service:sync:start", "weave:service:sync:error"); // and ensure the counters correctly handle multiple in-flight syncs yield notifyAndPromiseUIUpdated("weave:service:sync:start"); checkButtonsStatus(true); // sync stops. yield notifyAndPromiseUIUpdated("weave:service:sync:finish"); // Button should not be active. checkButtonsStatus(false); } add_task(function* testButtonActivitiesInNavBar() { // check the button's functionality while the button is in the NavBar - which // it already is. yield doTestButtonActivities(); }); add_task(function* testFormatLastSyncDateNow() { let now = new Date(); let nowString = gSyncUI.formatLastSyncDate(now); Assert.equal(nowString, "Last sync: " + now.toLocaleDateString(undefined, {weekday: 'long', hour: 'numeric', minute: 'numeric'})); }); add_task(function* testFormatLastSyncDateMonthAgo() { let monthAgo = new Date(); monthAgo.setMonth(monthAgo.getMonth() - 1); let monthAgoString = gSyncUI.formatLastSyncDate(monthAgo); Assert.equal(monthAgoString, "Last sync: " + monthAgo.toLocaleDateString(undefined, {month: 'long', day: 'numeric'})); }); add_task(function* testButtonActivitiesInPanel() { // check the button's functionality while the button is in the panel - it's // currently in the NavBar - move it to the panel and open it. CustomizableUI.addWidgetToArea("sync-button", CustomizableUI.AREA_PANEL); yield PanelUI.show(); try { yield doTestButtonActivities(); } finally { PanelUI.hide(); } });