"use strict"; let { SyncedTabs } = ChromeUtils.importESModule( "resource://services-sync/SyncedTabs.sys.mjs" ); let { SyncedTabsDeckComponent } = ChromeUtils.importESModule( "resource:///modules/syncedtabs/SyncedTabsDeckComponent.sys.mjs" ); let { SyncedTabsListStore } = ChromeUtils.importESModule( "resource:///modules/syncedtabs/SyncedTabsListStore.sys.mjs" ); let { SyncedTabsDeckStore } = ChromeUtils.importESModule( "resource:///modules/syncedtabs/SyncedTabsDeckStore.sys.mjs" ); const { UIState } = ChromeUtils.importESModule( "resource://services-sync/UIState.sys.mjs" ); add_task(async function testInitUninit() { let deckStore = new SyncedTabsDeckStore(); let listComponent = {}; let mockWindow = {}; let ViewMock = sinon.stub(); let view = { render: sinon.spy(), destroy: sinon.spy(), container: {} }; ViewMock.returns(view); sinon.stub(SyncedTabs, "syncTabs").callsFake(() => Promise.resolve()); sinon.spy(deckStore, "on"); sinon.stub(deckStore, "setPanels"); let component = new SyncedTabsDeckComponent({ window: mockWindow, deckStore, listComponent, SyncedTabs, DeckView: ViewMock, }); sinon.stub(component, "updatePanel"); component.init(); Assert.ok(SyncedTabs.syncTabs.called); SyncedTabs.syncTabs.restore(); Assert.ok(ViewMock.calledWithNew(), "view is instantiated"); Assert.equal(ViewMock.args[0][0], mockWindow); Assert.equal(ViewMock.args[0][1], listComponent); Assert.ok( ViewMock.args[0][2].onConnectDeviceClick, "view is passed onConnectDeviceClick prop" ); Assert.ok( ViewMock.args[0][2].onSyncPrefClick, "view is passed onSyncPrefClick prop" ); Assert.equal( component.container, view.container, "component returns view's container" ); Assert.ok(deckStore.on.calledOnce, "listener is added to store"); Assert.equal(deckStore.on.args[0][0], "change"); // Object.values only in nightly let values = Object.keys(component.PANELS).map(k => component.PANELS[k]); Assert.ok( deckStore.setPanels.calledWith(values), "panels are set on deck store" ); Assert.ok(component.updatePanel.called); deckStore.emit("change", "mock state"); Assert.ok( view.render.calledWith("mock state"), "view.render is called on state change" ); component.uninit(); Assert.ok(view.destroy.calledOnce, "view is destroyed on uninit"); }); add_task(async function testObserver() { let deckStore = new SyncedTabsDeckStore(); let listStore = new SyncedTabsListStore(SyncedTabs); let listComponent = {}; let mockWindow = {}; let ViewMock = sinon.stub(); let view = { render: sinon.spy(), destroy: sinon.spy(), container: {} }; ViewMock.returns(view); sinon.stub(SyncedTabs, "syncTabs").callsFake(() => Promise.resolve()); sinon.spy(deckStore, "on"); sinon.stub(deckStore, "setPanels"); sinon.stub(listStore, "getData"); let component = new SyncedTabsDeckComponent({ window: mockWindow, deckStore, listStore, listComponent, SyncedTabs, DeckView: ViewMock, }); sinon.spy(component, "observe"); sinon.stub(component, "updatePanel"); sinon.stub(component, "updateDir"); component.init(); SyncedTabs.syncTabs.restore(); Assert.ok(component.updatePanel.called, "triggers panel update during init"); Assert.ok( component.updateDir.called, "triggers UI direction update during init" ); Services.obs.notifyObservers(null, SyncedTabs.TOPIC_TABS_CHANGED); Assert.ok( component.observe.calledWith(null, SyncedTabs.TOPIC_TABS_CHANGED), "component is notified" ); Assert.ok(listStore.getData.called, "gets list data"); Assert.ok(component.updatePanel.calledTwice, "triggers panel update"); Services.obs.notifyObservers(null, UIState.ON_UPDATE); Assert.ok( component.observe.calledWith(null, UIState.ON_UPDATE), "component is notified of FxA/Sync UI Update" ); Assert.equal( component.updatePanel.callCount, 3, "triggers panel update again" ); Services.locale.availableLocales = ["ab-CD"]; Services.locale.requestedLocales = ["ab-CD"]; Assert.ok( component.updateDir.calledTwice, "locale change triggers UI direction update" ); Services.prefs.setStringPref("intl.l10n.pseudo", "bidi"); Assert.equal( component.updateDir.callCount, 3, "pref change triggers UI direction update" ); }); add_task(async function testPanelStatus() { let deckStore = new SyncedTabsDeckStore(); let listStore = new SyncedTabsListStore(); let listComponent = {}; let SyncedTabsMock = { getTabClients() {}, }; sinon.stub(listStore, "getData"); let component = new SyncedTabsDeckComponent({ deckStore, listComponent, SyncedTabs: SyncedTabsMock, }); sinon.stub(UIState, "get").returns({ status: UIState.STATUS_NOT_CONFIGURED }); let result = await component.getPanelStatus(); Assert.equal(result, component.PANELS.NOT_AUTHED_INFO); UIState.get.restore(); sinon.stub(UIState, "get").returns({ status: UIState.STATUS_NOT_VERIFIED }); result = await component.getPanelStatus(); Assert.equal(result, component.PANELS.UNVERIFIED); UIState.get.restore(); sinon.stub(UIState, "get").returns({ status: UIState.STATUS_LOGIN_FAILED }); result = await component.getPanelStatus(); Assert.equal(result, component.PANELS.LOGIN_FAILED); UIState.get.restore(); sinon .stub(UIState, "get") .returns({ status: UIState.STATUS_SIGNED_IN, syncEnabled: false }); SyncedTabsMock.isConfiguredToSyncTabs = true; result = await component.getPanelStatus(); Assert.equal(result, component.PANELS.SYNC_DISABLED); UIState.get.restore(); sinon .stub(UIState, "get") .returns({ status: UIState.STATUS_SIGNED_IN, syncEnabled: true }); SyncedTabsMock.isConfiguredToSyncTabs = false; result = await component.getPanelStatus(); Assert.equal(result, component.PANELS.TABS_DISABLED); SyncedTabsMock.isConfiguredToSyncTabs = true; SyncedTabsMock.hasSyncedThisSession = false; result = await component.getPanelStatus(); Assert.equal(result, component.PANELS.TABS_FETCHING); SyncedTabsMock.hasSyncedThisSession = true; let clients = []; sinon .stub(SyncedTabsMock, "getTabClients") .callsFake(() => Promise.resolve(clients)); result = await component.getPanelStatus(); Assert.equal(result, component.PANELS.SINGLE_DEVICE_INFO); clients = ["mock-client"]; result = await component.getPanelStatus(); Assert.equal(result, component.PANELS.TABS_CONTAINER); sinon .stub(component, "getPanelStatus") .callsFake(() => Promise.resolve("mock-panelId")); sinon.spy(deckStore, "selectPanel"); await component.updatePanel(); Assert.ok(deckStore.selectPanel.calledWith("mock-panelId")); }); add_task(async function testActions() { let windowMock = {}; let chromeWindowMock = { gSync: { openPrefs() {}, openConnectAnotherDevice() {}, }, }; sinon.spy(chromeWindowMock.gSync, "openPrefs"); sinon.spy(chromeWindowMock.gSync, "openConnectAnotherDevice"); let getChromeWindowMock = sinon.stub(); getChromeWindowMock.returns(chromeWindowMock); let component = new SyncedTabsDeckComponent({ window: windowMock, getChromeWindowMock, }); component.openConnectDevice(); Assert.ok(chromeWindowMock.gSync.openConnectAnotherDevice.called); component.openSyncPrefs(); Assert.ok(getChromeWindowMock.calledWith(windowMock)); Assert.ok(chromeWindowMock.gSync.openPrefs.called); });