describe("Sync Preferences", function () { var win, doc; before(function* () { // Load prefs with sync pane win = yield loadWindow("chrome://zotero/content/preferences/preferences.xhtml", { pane: 'zotero-prefpane-sync' }); doc = win.document; yield win.Zotero_Preferences.waitForFirstPaneLoad(); }); after(function () { win.close(); }); describe("Settings", function () { describe("Data Syncing", function () { var createLoginSessionStub, checkLoginSessionStub, deleteAPIKey, launchURLStub, streamerSubscribeStub, streamerUnsubscribeStub, indicatorElem, apiKey; var performLogin = async function (username) { apiKey = Zotero.Utilities.randomString(24); createLoginSessionStub.resolves({ sessionToken: 'test-session-token', loginURL: 'https://www.zotero.org/authorize?token=test-session-token' }); checkLoginSessionStub.resolves({ status: 'completed', apiKey, userID: 1, username }); await win.Zotero_Preferences.Sync.linkAccount(); }; before(function* () { createLoginSessionStub = sinon.stub( Zotero.Sync.APIClient.prototype, 'createLoginSession'); checkLoginSessionStub = sinon.stub( Zotero.Sync.APIClient.prototype, 'checkLoginSession'); deleteAPIKey = sinon.stub(Zotero.Sync.APIClient.prototype, 'deleteAPIKey').resolves(); launchURLStub = sinon.stub(Zotero, 'launchURL'); streamerSubscribeStub = sinon.stub(Zotero.Streamer, 'subscribe').returns(false); streamerUnsubscribeStub = sinon.stub(Zotero.Streamer, 'unsubscribe'); indicatorElem = doc.querySelector('.account-login-status-indicator'); sinon.stub(Zotero, 'alert'); // Speed up polling for tests win.Zotero_Preferences.Sync._pollInterval = 10; }); beforeEach(function* () { yield win.Zotero_Preferences.Sync.unlinkAccount(false); deleteAPIKey.resetHistory(); createLoginSessionStub.resetHistory(); checkLoginSessionStub.resetHistory(); launchURLStub.resetHistory(); streamerSubscribeStub.resetHistory(); streamerUnsubscribeStub.resetHistory(); Zotero.alert.reset(); }); after(function () { Zotero.HTTP.mock = null; Zotero.alert.restore(); createLoginSessionStub.restore(); checkLoginSessionStub.restore(); deleteAPIKey.restore(); launchURLStub.restore(); streamerSubscribeStub.restore(); streamerUnsubscribeStub.restore(); win.Zotero_Preferences.Sync._pollInterval = 3000; }); it("should set API key and display full controls after successful login", async function () { await performLogin("Username"); assert.equal(await Zotero.Sync.Data.Local.getAPIKey(), apiKey); assert.equal(doc.getElementById('sync-unauthorized').getAttribute('hidden'), 'true'); assert.isTrue(launchURLStub.calledOnce); }); it("should show error when login session expires", async function () { createLoginSessionStub.resolves({ sessionToken: 'test-session-token', loginURL: 'https://www.zotero.org/authorize?token=test-session-token' }); let expiredError = new Error("Login session expired"); expiredError.expired = true; checkLoginSessionStub.rejects(expiredError); await win.Zotero_Preferences.Sync.linkAccount(); assert.isTrue(Zotero.alert.called); assert.equal(await Zotero.Sync.Data.Local.getAPIKey(), ""); assert.equal(doc.querySelector('.sync-settings').hidden, true); }); it("should reset UI when login session is cancelled on server", async function () { createLoginSessionStub.resolves({ sessionToken: 'test-session-token', loginURL: 'https://www.zotero.org/authorize?token=test-session-token' }); checkLoginSessionStub.resolves({ status: 'cancelled' }); await win.Zotero_Preferences.Sync.linkAccount(); assert.equal(await Zotero.Sync.Data.Local.getAPIKey(), ""); assert.equal(doc.querySelector('.account-login-default').hidden, false); assert.equal(doc.querySelector('.account-login-pending').hidden, true); }); it("should delete API key and display auth form when 'Unlink Account' clicked", async function () { await performLogin("Username"); assert.equal(await Zotero.Sync.Data.Local.getAPIKey(), apiKey); await win.Zotero_Preferences.Sync.unlinkAccount(false); assert.isTrue(deleteAPIKey.called); assert.equal(await Zotero.Sync.Data.Local.getAPIKey(), ""); assert.equal(doc.querySelector('.sync-settings').hidden, true); }); it("should reset the storage controller when unlinking", async function () { await performLogin("Username"); assert.equal(await Zotero.Sync.Data.Local.getAPIKey(), apiKey); let options = { apiClient: Zotero.Sync.Runner.getAPIClient({ apiKey }) }; let controller = Zotero.Sync.Runner.getStorageController('zfs', options); let apiKey1 = controller.apiClient.apiKey; await win.Zotero_Preferences.Sync.unlinkAccount(false); await performLogin("Username"); options = { apiClient: Zotero.Sync.Runner.getAPIClient({ apiKey }) }; controller = Zotero.Sync.Runner.getStorageController('zfs', options); assert.notEqual(controller.apiClient.apiKey, apiKey1); }); it("should not unlink on pressing cancel", async function () { await performLogin("Username"); waitForDialog(null, 'cancel'); await win.Zotero_Preferences.Sync.unlinkAccount(); assert.equal(await Zotero.Sync.Data.Local.getAPIKey(), apiKey); assert.equal(doc.getElementById('sync-unauthorized').getAttribute('hidden'), 'true'); }); it("should clear sync errors from the toolbar after logging in", async function () { let win = await loadZoteroPane(); let syncError = win.document.getElementById('zotero-tb-sync-error'); Zotero.Sync.Runner.updateIcons(new Error("a sync error")); assert.isFalse(syncError.hidden); await performLogin("Username"); assert.isTrue(syncError.hidden); win.close(); }); it("should cancel login and reset UI when cancelLogin is called", async function () { let cancelLoginSessionStub = sinon.stub( Zotero.Sync.APIClient.prototype, 'cancelLoginSession').resolves(); createLoginSessionStub.resolves({ sessionToken: 'test-session-token', loginURL: 'https://www.zotero.org/authorize?token=test-session-token' }); // Return "pending" so the poll loop keeps iterating checkLoginSessionStub.resolves({ status: 'pending' }); // Start login but don't await -- it will keep polling let loginPromise = win.Zotero_Preferences.Sync.linkAccount(); // Wait for the poll loop to start await Zotero.Promise.delay(50); win.Zotero_Preferences.Sync.cancelLogin(); // Wait for the login promise to resolve after cancellation await loginPromise; assert.equal(doc.querySelector('.account-login-default').hidden, false); assert.equal(doc.querySelector('.account-login-pending').hidden, true); assert.isTrue(cancelLoginSessionStub.calledWith('test-session-token')); cancelLoginSessionStub.restore(); }); }); }); });