From 5f8de423f190bbb79a62f804151bc24824fa32d8 Mon Sep 17 00:00:00 2001 From: "Matt A. Tobin" Date: Fri, 2 Feb 2018 04:16:08 -0500 Subject: Add m-esr52 at 52.6.0 --- .../syncedtabs/test/xpcshell/.eslintrc.js | 7 + .../components/syncedtabs/test/xpcshell/head.js | 29 +++ .../syncedtabs/test/xpcshell/test_EventEmitter.js | 35 +++ .../test/xpcshell/test_SyncedTabsDeckComponent.js | 218 +++++++++++++++++ .../test/xpcshell/test_SyncedTabsDeckStore.js | 64 +++++ .../test/xpcshell/test_SyncedTabsListStore.js | 266 +++++++++++++++++++++ .../test/xpcshell/test_TabListComponent.js | 155 ++++++++++++ .../syncedtabs/test/xpcshell/xpcshell.ini | 10 + 8 files changed, 784 insertions(+) create mode 100644 browser/components/syncedtabs/test/xpcshell/.eslintrc.js create mode 100644 browser/components/syncedtabs/test/xpcshell/head.js create mode 100644 browser/components/syncedtabs/test/xpcshell/test_EventEmitter.js create mode 100644 browser/components/syncedtabs/test/xpcshell/test_SyncedTabsDeckComponent.js create mode 100644 browser/components/syncedtabs/test/xpcshell/test_SyncedTabsDeckStore.js create mode 100644 browser/components/syncedtabs/test/xpcshell/test_SyncedTabsListStore.js create mode 100644 browser/components/syncedtabs/test/xpcshell/test_TabListComponent.js create mode 100644 browser/components/syncedtabs/test/xpcshell/xpcshell.ini (limited to 'browser/components/syncedtabs/test/xpcshell') diff --git a/browser/components/syncedtabs/test/xpcshell/.eslintrc.js b/browser/components/syncedtabs/test/xpcshell/.eslintrc.js new file mode 100644 index 000000000..d35787cd2 --- /dev/null +++ b/browser/components/syncedtabs/test/xpcshell/.eslintrc.js @@ -0,0 +1,7 @@ +"use strict"; + +module.exports = { + "extends": [ + "../../../../../testing/xpcshell/xpcshell.eslintrc.js" + ] +}; diff --git a/browser/components/syncedtabs/test/xpcshell/head.js b/browser/components/syncedtabs/test/xpcshell/head.js new file mode 100644 index 000000000..00055231c --- /dev/null +++ b/browser/components/syncedtabs/test/xpcshell/head.js @@ -0,0 +1,29 @@ +const {classes: Cc, interfaces: Ci, utils: Cu, results: Cr} = Components; + +Cu.import("resource://gre/modules/XPCOMUtils.jsm"); +Cu.import("resource://gre/modules/Services.jsm"); + +XPCOMUtils.defineLazyGetter(this, "FxAccountsCommon", function () { + return Components.utils.import("resource://gre/modules/FxAccountsCommon.js", {}); +}); + +Cu.import("resource://gre/modules/Timer.jsm"); + +do_get_profile(); // fxa needs a profile directory for storage. + +// Create a window polyfill so sinon can load +let window = { + document: {}, + location: {}, + setTimeout: setTimeout, + setInterval: setInterval, + clearTimeout: clearTimeout, + clearinterval: clearInterval +}; +let self = window; + +// Load mocking/stubbing library, sinon +// docs: http://sinonjs.org/docs/ +/* global sinon */ +let loader = Cc["@mozilla.org/moz/jssubscript-loader;1"].getService(Ci.mozIJSSubScriptLoader); +loader.loadSubScript("resource://testing-common/sinon-1.16.1.js"); diff --git a/browser/components/syncedtabs/test/xpcshell/test_EventEmitter.js b/browser/components/syncedtabs/test/xpcshell/test_EventEmitter.js new file mode 100644 index 000000000..bc73ac621 --- /dev/null +++ b/browser/components/syncedtabs/test/xpcshell/test_EventEmitter.js @@ -0,0 +1,35 @@ +"use strict"; + +let { EventEmitter } = Cu.import("resource:///modules/syncedtabs/EventEmitter.jsm", {}); + +add_task(function* testSingleListener() { + let eventEmitter = new EventEmitter(); + let spy = sinon.spy(); + + eventEmitter.on("click", spy); + eventEmitter.emit("click", "foo", "bar"); + Assert.ok(spy.calledOnce); + Assert.ok(spy.calledWith("foo", "bar")); + + eventEmitter.off("click", spy); + eventEmitter.emit("click"); + Assert.ok(spy.calledOnce); +}); + +add_task(function* testMultipleListeners() { + let eventEmitter = new EventEmitter(); + let spy1 = sinon.spy(); + let spy2 = sinon.spy(); + + eventEmitter.on("some_event", spy1); + eventEmitter.on("some_event", spy2); + eventEmitter.emit("some_event"); + Assert.ok(spy1.calledOnce); + Assert.ok(spy2.calledOnce); + + eventEmitter.off("some_event", spy1); + eventEmitter.emit("some_event"); + Assert.ok(spy1.calledOnce); + Assert.ok(spy2.calledTwice); +}); + diff --git a/browser/components/syncedtabs/test/xpcshell/test_SyncedTabsDeckComponent.js b/browser/components/syncedtabs/test/xpcshell/test_SyncedTabsDeckComponent.js new file mode 100644 index 000000000..3d748b33c --- /dev/null +++ b/browser/components/syncedtabs/test/xpcshell/test_SyncedTabsDeckComponent.js @@ -0,0 +1,218 @@ +"use strict"; + +let { SyncedTabs } = Cu.import("resource://services-sync/SyncedTabs.jsm", {}); +let { SyncedTabsDeckComponent } = Cu.import("resource:///modules/syncedtabs/SyncedTabsDeckComponent.js", {}); +let { TabListComponent } = Cu.import("resource:///modules/syncedtabs/TabListComponent.js", {}); +let { SyncedTabsListStore } = Cu.import("resource:///modules/syncedtabs/SyncedTabsListStore.js", {}); +let { SyncedTabsDeckStore } = Cu.import("resource:///modules/syncedtabs/SyncedTabsDeckStore.js", {}); +let { TabListView } = Cu.import("resource:///modules/syncedtabs/TabListView.js", {}); +let { DeckView } = Cu.import("resource:///modules/syncedtabs/SyncedTabsDeckView.js", {}); + + +add_task(function* testInitUninit() { + let deckStore = new SyncedTabsDeckStore(); + let listComponent = {}; + + let ViewMock = sinon.stub(); + let view = {render: sinon.spy(), destroy: sinon.spy(), container: {}}; + ViewMock.returns(view); + + sinon.stub(SyncedTabs, "syncTabs", () => Promise.resolve()); + + sinon.spy(deckStore, "on"); + sinon.stub(deckStore, "setPanels"); + + let component = new SyncedTabsDeckComponent({ + window, + 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], window); + Assert.equal(ViewMock.args[0][1], listComponent); + Assert.ok(ViewMock.args[0][2].onAndroidClick, + "view is passed onAndroidClick prop"); + Assert.ok(ViewMock.args[0][2].oniOSClick, + "view is passed oniOSClick 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"); +}); + + +function waitForObserver() { + return new Promise((resolve, reject) => { + Services.obs.addObserver((subject, topic) => { + resolve(); + }, SyncedTabs.TOPIC_TABS_CHANGED, false); + }); +} + +add_task(function* testObserver() { + let deckStore = new SyncedTabsDeckStore(); + let listStore = new SyncedTabsListStore(SyncedTabs); + let listComponent = {}; + + let ViewMock = sinon.stub(); + let view = {render: sinon.spy(), destroy: sinon.spy(), container: {}}; + ViewMock.returns(view); + + sinon.stub(SyncedTabs, "syncTabs", () => Promise.resolve()); + + sinon.spy(deckStore, "on"); + sinon.stub(deckStore, "setPanels"); + + sinon.stub(listStore, "getData"); + + let component = new SyncedTabsDeckComponent({ + window, + deckStore, + listStore, + listComponent, + SyncedTabs, + DeckView: ViewMock, + }); + + sinon.spy(component, "observe"); + sinon.stub(component, "updatePanel"); + + component.init(); + SyncedTabs.syncTabs.restore(); + Assert.ok(component.updatePanel.called, "triggers panel 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, FxAccountsCommon.ONLOGIN_NOTIFICATION, ""); + + Assert.ok(component.observe.calledWith(null, FxAccountsCommon.ONLOGIN_NOTIFICATION, ""), + "component is notified of login"); + Assert.equal(component.updatePanel.callCount, 3, "triggers panel update again"); +}); + +add_task(function* testPanelStatus() { + let deckStore = new SyncedTabsDeckStore(); + let listStore = new SyncedTabsListStore(); + let listComponent = {}; + let fxAccounts = { + accountStatus() {} + }; + let SyncedTabsMock = { + getTabClients() {} + }; + + sinon.stub(listStore, "getData"); + + + let component = new SyncedTabsDeckComponent({ + fxAccounts, + deckStore, + listComponent, + SyncedTabs: SyncedTabsMock, + }); + + let isAuthed = false; + sinon.stub(fxAccounts, "accountStatus", () => Promise.resolve(isAuthed)); + let result = yield component.getPanelStatus(); + Assert.equal(result, component.PANELS.NOT_AUTHED_INFO); + + isAuthed = true; + + SyncedTabsMock.isConfiguredToSyncTabs = false; + result = yield component.getPanelStatus(); + Assert.equal(result, component.PANELS.TABS_DISABLED); + + SyncedTabsMock.isConfiguredToSyncTabs = true; + + SyncedTabsMock.hasSyncedThisSession = false; + result = yield component.getPanelStatus(); + Assert.equal(result, component.PANELS.TABS_FETCHING); + + SyncedTabsMock.hasSyncedThisSession = true; + + let clients = []; + sinon.stub(SyncedTabsMock, "getTabClients", () => Promise.resolve(clients)); + result = yield component.getPanelStatus(); + Assert.equal(result, component.PANELS.SINGLE_DEVICE_INFO); + + clients = ["mock-client"]; + result = yield component.getPanelStatus(); + Assert.equal(result, component.PANELS.TABS_CONTAINER); + + fxAccounts.accountStatus.restore(); + sinon.stub(fxAccounts, "accountStatus", () => Promise.reject("err")); + result = yield component.getPanelStatus(); + Assert.equal(result, component.PANELS.NOT_AUTHED_INFO); + + sinon.stub(component, "getPanelStatus", () => Promise.resolve("mock-panelId")); + sinon.spy(deckStore, "selectPanel"); + yield component.updatePanel(); + Assert.ok(deckStore.selectPanel.calledWith("mock-panelId")); +}); + +add_task(function* testActions() { + let windowMock = { + openUILink() {}, + }; + let chromeWindowMock = { + gSyncUI: { + openSetup() {} + } + }; + sinon.spy(windowMock, "openUILink"); + sinon.spy(chromeWindowMock.gSyncUI, "openSetup"); + + let getChromeWindowMock = sinon.stub(); + getChromeWindowMock.returns(chromeWindowMock); + + let component = new SyncedTabsDeckComponent({ + window: windowMock, + getChromeWindowMock + }); + + let href = Services.prefs.getCharPref("identity.mobilepromo.android") + "synced-tabs-sidebar"; + component.openAndroidLink("mock-event"); + Assert.ok(windowMock.openUILink.calledWith(href, "mock-event")); + + href = Services.prefs.getCharPref("identity.mobilepromo.ios") + "synced-tabs-sidebar"; + component.openiOSLink("mock-event"); + Assert.ok(windowMock.openUILink.calledWith(href, "mock-event")); + + component.openSyncPrefs(); + Assert.ok(getChromeWindowMock.calledWith(windowMock)); + Assert.ok(chromeWindowMock.gSyncUI.openSetup.called); +}); diff --git a/browser/components/syncedtabs/test/xpcshell/test_SyncedTabsDeckStore.js b/browser/components/syncedtabs/test/xpcshell/test_SyncedTabsDeckStore.js new file mode 100644 index 000000000..69abb4024 --- /dev/null +++ b/browser/components/syncedtabs/test/xpcshell/test_SyncedTabsDeckStore.js @@ -0,0 +1,64 @@ +"use strict"; + +let { SyncedTabsDeckStore } = Cu.import("resource:///modules/syncedtabs/SyncedTabsDeckStore.js", {}); + +add_task(function* testSelectUnkownPanel() { + let deckStore = new SyncedTabsDeckStore(); + let spy = sinon.spy(); + + deckStore.on("change", spy); + deckStore.selectPanel("foo"); + + Assert.ok(!spy.called); +}); + +add_task(function* testSetPanels() { + let deckStore = new SyncedTabsDeckStore(); + let spy = sinon.spy(); + + deckStore.on("change", spy); + deckStore.setPanels(["panel1", "panel2"]); + + Assert.ok(spy.calledWith({ + panels: [ + { id: "panel1", selected: false }, + { id: "panel2", selected: false }, + ], + isUpdatable: false + })); +}); + +add_task(function* testSelectPanel() { + let deckStore = new SyncedTabsDeckStore(); + let spy = sinon.spy(); + + deckStore.setPanels(["panel1", "panel2"]); + + deckStore.on("change", spy); + deckStore.selectPanel("panel2"); + + Assert.ok(spy.calledWith({ + panels: [ + { id: "panel1", selected: false }, + { id: "panel2", selected: true }, + ], + isUpdatable: true + })); + + deckStore.selectPanel("panel2"); + Assert.ok(spy.calledOnce, "doesn't trigger unless panel changes"); +}); + +add_task(function* testSetPanelsSameArray() { + let deckStore = new SyncedTabsDeckStore(); + let spy = sinon.spy(); + deckStore.on("change", spy); + + let panels = ["panel1", "panel2"]; + + deckStore.setPanels(panels); + deckStore.setPanels(panels); + + Assert.ok(spy.calledOnce, "doesn't trigger unless set of panels changes"); +}); + diff --git a/browser/components/syncedtabs/test/xpcshell/test_SyncedTabsListStore.js b/browser/components/syncedtabs/test/xpcshell/test_SyncedTabsListStore.js new file mode 100644 index 000000000..51580235f --- /dev/null +++ b/browser/components/syncedtabs/test/xpcshell/test_SyncedTabsListStore.js @@ -0,0 +1,266 @@ +"use strict"; + +let { SyncedTabs } = Cu.import("resource://services-sync/SyncedTabs.jsm", {}); +let { SyncedTabsListStore } = Cu.import("resource:///modules/syncedtabs/SyncedTabsListStore.js", {}); + +const FIXTURE = [ + { + "id": "2xU5h-4bkWqA", + "type": "client", + "name": "laptop", + "isMobile": false, + "tabs": [ + { + "type": "tab", + "title": "Firefox for iOS — Mobile Web browser for your iPhone, iPad and iPod touch — Mozilla", + "url": "https://www.mozilla.org/en-US/firefox/ios/?utm_source=firefox-browser&utm_medium=firefox-browser&utm_campaign=synced-tabs-sidebar", + "icon": "moz-anno:favicon:https://www.mozilla.org/media/img/firefox/favicon.dc6635050bf5.ico", + "client": "2xU5h-4bkWqA", + "lastUsed": 1451519425 + }, + { + "type": "tab", + "title": "Firefox Nightly First Run Page", + "url": "https://www.mozilla.org/en-US/firefox/nightly/firstrun/?oldversion=45.0a1", + "icon": "moz-anno:favicon:https://www.mozilla.org/media/img/firefox/favicon-nightly.560395bbb2e1.png", + "client": "2xU5h-4bkWqA", + "lastUsed": 1451519420 + } + ] + }, + { + "id": "OL3EJCsdb2JD", + "type": "client", + "name": "desktop", + "isMobile": false, + "tabs": [] + } +]; + +add_task(function* testGetDataEmpty() { + let store = new SyncedTabsListStore(SyncedTabs); + let spy = sinon.spy(); + + sinon.stub(SyncedTabs, "getTabClients", () => { + return Promise.resolve([]); + }); + store.on("change", spy); + + yield store.getData(); + + Assert.ok(SyncedTabs.getTabClients.calledWith("")); + Assert.ok(spy.calledWith({ + clients: [], + canUpdateAll: false, + canUpdateInput: false, + filter: "", + inputFocused: false + })); + + yield store.getData("filter"); + + Assert.ok(SyncedTabs.getTabClients.calledWith("filter")); + Assert.ok(spy.calledWith({ + clients: [], + canUpdateAll: false, + canUpdateInput: true, + filter: "filter", + inputFocused: false + })); + + SyncedTabs.getTabClients.restore(); +}); + +add_task(function* testRowSelectionWithoutFilter() { + let store = new SyncedTabsListStore(SyncedTabs); + let spy = sinon.spy(); + + sinon.stub(SyncedTabs, "getTabClients", () => { + return Promise.resolve(FIXTURE); + }); + + yield store.getData(); + SyncedTabs.getTabClients.restore(); + + store.on("change", spy); + + store.selectRow(0, -1); + Assert.ok(spy.args[0][0].canUpdateAll, "can update the whole view"); + Assert.ok(spy.args[0][0].clients[0].selected, "first client is selected"); + + store.moveSelectionUp(); + Assert.ok(spy.calledOnce, + "can't move up past first client, no change triggered"); + + store.selectRow(0, 0); + Assert.ok(spy.args[1][0].clients[0].tabs[0].selected, + "first tab of first client is selected"); + + store.selectRow(0, 0); + Assert.ok(spy.calledTwice, "selecting same row doesn't trigger change"); + + store.selectRow(0, 1); + Assert.ok(spy.args[2][0].clients[0].tabs[1].selected, + "second tab of first client is selected"); + + store.selectRow(1); + Assert.ok(spy.args[3][0].clients[1].selected, "second client is selected"); + + store.moveSelectionDown(); + Assert.equal(spy.callCount, 4, + "can't move selection down past last client, no change triggered"); + + store.moveSelectionUp(); + Assert.equal(spy.callCount, 5, + "changed"); + Assert.ok(spy.args[4][0].clients[0].tabs[FIXTURE[0].tabs.length - 1].selected, + "move selection up from client selects last tab of previous client"); + + store.moveSelectionUp(); + Assert.ok(spy.args[5][0].clients[0].tabs[FIXTURE[0].tabs.length - 2].selected, + "move selection up from tab selects previous tab of client"); +}); + + +add_task(function* testToggleBranches() { + let store = new SyncedTabsListStore(SyncedTabs); + let spy = sinon.spy(); + + sinon.stub(SyncedTabs, "getTabClients", () => { + return Promise.resolve(FIXTURE); + }); + + yield store.getData(); + SyncedTabs.getTabClients.restore(); + + store.selectRow(0); + store.on("change", spy); + + let clientId = FIXTURE[0].id; + store.closeBranch(clientId); + Assert.ok(spy.args[0][0].clients[0].closed, "first client is closed"); + + store.openBranch(clientId); + Assert.ok(!spy.args[1][0].clients[0].closed, "first client is open"); + + store.toggleBranch(clientId); + Assert.ok(spy.args[2][0].clients[0].closed, "first client is toggled closed"); + + store.moveSelectionDown(); + Assert.ok(spy.args[3][0].clients[1].selected, + "selection skips tabs if client is closed"); + + store.moveSelectionUp(); + Assert.ok(spy.args[4][0].clients[0].selected, + "selection skips tabs if client is closed"); +}); + + +add_task(function* testRowSelectionWithFilter() { + let store = new SyncedTabsListStore(SyncedTabs); + let spy = sinon.spy(); + + sinon.stub(SyncedTabs, "getTabClients", () => { + return Promise.resolve(FIXTURE); + }); + + yield store.getData("filter"); + SyncedTabs.getTabClients.restore(); + + store.on("change", spy); + + store.selectRow(0); + Assert.ok(spy.args[0][0].clients[0].tabs[0].selected, "first tab is selected"); + + store.moveSelectionUp(); + Assert.ok(spy.calledOnce, + "can't move up past first tab, no change triggered"); + + store.moveSelectionDown(); + Assert.ok(spy.args[1][0].clients[0].tabs[1].selected, + "selection skips tabs if client is closed"); + + store.moveSelectionDown(); + Assert.equal(spy.callCount, 2, + "can't move selection down past last tab, no change triggered"); + + store.selectRow(1); + Assert.equal(spy.callCount, 2, + "doesn't trigger change if same row selected"); + +}); + + +add_task(function* testFilterAndClearFilter() { + let store = new SyncedTabsListStore(SyncedTabs); + let spy = sinon.spy(); + + sinon.stub(SyncedTabs, "getTabClients", () => { + return Promise.resolve(FIXTURE); + }); + store.on("change", spy); + + yield store.getData("filter"); + + Assert.ok(SyncedTabs.getTabClients.calledWith("filter")); + Assert.ok(!spy.args[0][0].canUpdateAll, "can't update all"); + Assert.ok(spy.args[0][0].canUpdateInput, "can update just input"); + + store.selectRow(0); + + Assert.equal(spy.args[1][0].filter, "filter"); + Assert.ok(spy.args[1][0].clients[0].tabs[0].selected, + "tab is selected"); + + yield store.clearFilter(); + + Assert.ok(SyncedTabs.getTabClients.calledWith("")); + Assert.ok(!spy.args[2][0].canUpdateAll, "can't update all"); + Assert.ok(!spy.args[2][0].canUpdateInput, "can't just update input"); + + Assert.equal(spy.args[2][0].filter, ""); + Assert.ok(!spy.args[2][0].clients[0].tabs[0].selected, + "tab is no longer selected"); + + SyncedTabs.getTabClients.restore(); +}); + +add_task(function* testFocusBlurInput() { + let store = new SyncedTabsListStore(SyncedTabs); + let spy = sinon.spy(); + + sinon.stub(SyncedTabs, "getTabClients", () => { + return Promise.resolve(FIXTURE); + }); + store.on("change", spy); + + yield store.getData(); + SyncedTabs.getTabClients.restore(); + + Assert.ok(!spy.args[0][0].canUpdateAll, "must rerender all"); + + store.selectRow(0); + Assert.ok(!spy.args[1][0].inputFocused, + "input is not focused"); + Assert.ok(spy.args[1][0].clients[0].selected, + "client is selected"); + Assert.ok(spy.args[1][0].clients[0].focused, + "client is focused"); + + store.focusInput(); + Assert.ok(spy.args[2][0].inputFocused, + "input is focused"); + Assert.ok(spy.args[2][0].clients[0].selected, + "client is still selected"); + Assert.ok(!spy.args[2][0].clients[0].focused, + "client is no longer focused"); + + store.blurInput(); + Assert.ok(!spy.args[3][0].inputFocused, + "input is not focused"); + Assert.ok(spy.args[3][0].clients[0].selected, + "client is selected"); + Assert.ok(spy.args[3][0].clients[0].focused, + "client is focused"); +}); + diff --git a/browser/components/syncedtabs/test/xpcshell/test_TabListComponent.js b/browser/components/syncedtabs/test/xpcshell/test_TabListComponent.js new file mode 100644 index 000000000..0b0665a1b --- /dev/null +++ b/browser/components/syncedtabs/test/xpcshell/test_TabListComponent.js @@ -0,0 +1,155 @@ +"use strict"; + +let { SyncedTabs } = Cu.import("resource://services-sync/SyncedTabs.jsm", {}); +let { TabListComponent } = Cu.import("resource:///modules/syncedtabs/TabListComponent.js", {}); +let { SyncedTabsListStore } = Cu.import("resource:///modules/syncedtabs/SyncedTabsListStore.js", {}); +let { View } = Cu.import("resource:///modules/syncedtabs/TabListView.js", {}); + +const ACTION_METHODS = [ + "onSelectRow", + "onOpenTab", + "onOpenTabs", + "onMoveSelectionDown", + "onMoveSelectionUp", + "onToggleBranch", + "onBookmarkTab", + "onSyncRefresh", + "onFilter", + "onClearFilter", + "onFilterFocus", + "onFilterBlur", +]; + +add_task(function* testInitUninit() { + let store = new SyncedTabsListStore(); + let ViewMock = sinon.stub(); + let view = {render() {}, destroy() {}}; + + ViewMock.returns(view); + + sinon.spy(view, 'render'); + sinon.spy(view, 'destroy'); + + sinon.spy(store, "on"); + sinon.stub(store, "getData"); + sinon.stub(store, "focusInput"); + + let component = new TabListComponent({window, store, View: ViewMock, SyncedTabs}); + + for (let action of ACTION_METHODS) { + sinon.stub(component, action); + } + + component.init(); + + Assert.ok(ViewMock.calledWithNew(), "view is instantiated"); + Assert.ok(store.on.calledOnce, "listener is added to store"); + Assert.equal(store.on.args[0][0], "change"); + Assert.ok(view.render.calledWith({clients: []}), + "render is called on view instance"); + Assert.ok(store.getData.calledOnce, "store gets initial data"); + Assert.ok(store.focusInput.calledOnce, "input field is focused"); + + for (let method of ACTION_METHODS) { + let action = ViewMock.args[0][1][method]; + Assert.ok(action, method + " action is passed to View"); + action("foo", "bar"); + Assert.ok(component[method].calledWith("foo", "bar"), + method + " action passed to View triggers the component method with args"); + } + + store.emit("change", "mock state"); + Assert.ok(view.render.secondCall.calledWith("mock state"), + "view.render is called on state change"); + + component.uninit(); + Assert.ok(view.destroy.calledOnce, "view is destroyed on uninit"); +}); + +add_task(function* testActions() { + let store = new SyncedTabsListStore(); + let chromeWindowMock = { + gBrowser: { + loadTabs() {}, + }, + }; + let getChromeWindowMock = sinon.stub(); + getChromeWindowMock.returns(chromeWindowMock); + let clipboardHelperMock = { + copyString() {}, + }; + let windowMock = { + top: { + PlacesCommandHook: { + bookmarkLink() { return Promise.resolve(); } + }, + PlacesUtils: { bookmarksMenuFolderId: "id" } + }, + getBrowserURL() {}, + openDialog() {}, + openUILinkIn() {} + }; + let component = new TabListComponent({ + window: windowMock, store, View: null, SyncedTabs, + clipboardHelper: clipboardHelperMock, + getChromeWindow: getChromeWindowMock }); + + sinon.stub(store, "getData"); + component.onFilter("query"); + Assert.ok(store.getData.calledWith("query")); + + sinon.stub(store, "clearFilter"); + component.onClearFilter(); + Assert.ok(store.clearFilter.called); + + sinon.stub(store, "focusInput"); + component.onFilterFocus(); + Assert.ok(store.focusInput.called); + + sinon.stub(store, "blurInput"); + component.onFilterBlur(); + Assert.ok(store.blurInput.called); + + sinon.stub(store, "selectRow"); + component.onSelectRow([-1, -1]); + Assert.ok(store.selectRow.calledWith(-1, -1)); + + sinon.stub(store, "moveSelectionDown"); + component.onMoveSelectionDown(); + Assert.ok(store.moveSelectionDown.called); + + sinon.stub(store, "moveSelectionUp"); + component.onMoveSelectionUp(); + Assert.ok(store.moveSelectionUp.called); + + sinon.stub(store, "toggleBranch"); + component.onToggleBranch("foo-id"); + Assert.ok(store.toggleBranch.calledWith("foo-id")); + + sinon.spy(windowMock.top.PlacesCommandHook, "bookmarkLink"); + component.onBookmarkTab("uri", "title"); + Assert.equal(windowMock.top.PlacesCommandHook.bookmarkLink.args[0][1], "uri"); + Assert.equal(windowMock.top.PlacesCommandHook.bookmarkLink.args[0][2], "title"); + + sinon.spy(windowMock, "openUILinkIn"); + component.onOpenTab("uri", "where", "params"); + Assert.ok(windowMock.openUILinkIn.calledWith("uri", "where", "params")); + + sinon.spy(chromeWindowMock.gBrowser, "loadTabs"); + let tabsToOpen = ["uri1", "uri2"]; + component.onOpenTabs(tabsToOpen, "where"); + Assert.ok(getChromeWindowMock.calledWith(windowMock)); + Assert.ok(chromeWindowMock.gBrowser.loadTabs.calledWith(tabsToOpen, false, false)); + component.onOpenTabs(tabsToOpen, "tabshifted"); + Assert.ok(chromeWindowMock.gBrowser.loadTabs.calledWith(tabsToOpen, true, false)); + + sinon.spy(clipboardHelperMock, "copyString"); + component.onCopyTabLocation("uri"); + Assert.ok(clipboardHelperMock.copyString.calledWith("uri")); + + sinon.stub(SyncedTabs, "syncTabs"); + component.onSyncRefresh(); + Assert.ok(SyncedTabs.syncTabs.calledWith(true)); + SyncedTabs.syncTabs.restore(); +}); + diff --git a/browser/components/syncedtabs/test/xpcshell/xpcshell.ini b/browser/components/syncedtabs/test/xpcshell/xpcshell.ini new file mode 100644 index 000000000..1cb8dcb7a --- /dev/null +++ b/browser/components/syncedtabs/test/xpcshell/xpcshell.ini @@ -0,0 +1,10 @@ +[DEFAULT] +head = head.js +tail = +firefox-appdir = browser + +[test_EventEmitter.js] +[test_SyncedTabsDeckStore.js] +[test_SyncedTabsListStore.js] +[test_SyncedTabsDeckComponent.js] +[test_TabListComponent.js] -- cgit v1.2.3