diff --git a/src/disco/addonManager.js b/src/disco/addonManager.js index e57d614b54e..6f9209f0efb 100644 --- a/src/disco/addonManager.js +++ b/src/disco/addonManager.js @@ -1,50 +1,38 @@ import { installEventList } from 'disco/constants'; -export class AddonManager { - constructor(id, url, eventCallback, {mozAddonManager = window.navigator.mozAddonManager} = {}) { - if (typeof mozAddonManager === 'undefined') { - throw new Error('mozAddonManager not available'); - } - this.id = id; - this.url = url; - this.mozAddonManager = mozAddonManager; - this.eventCallback = eventCallback; - } - - getAddon() { - // Resolves a promise with the addon on success. - return this.mozAddonManager.getAddonByID(this.id) - .then((addon) => { - if (!addon) { - return Promise.reject(new Error('Addon not found')); - } - return Promise.resolve(addon); - }); - } +export function getAddon(guid, {_mozAddonManager = window.navigator.mozAddonManager} = {}) { + // Resolves a promise with the addon on success. + return _mozAddonManager.getAddonByID(guid) + .then((addon) => { + if (!addon) { + throw new Error('Addon not found'); + } + return addon; + }); +} - install() { - return this.mozAddonManager.createInstall({url: this.url}) - .then((installObj) => { - const callback = (e) => this.eventCallback(installObj, e, this.id); - for (const event of installEventList) { - installObj.addEventListener(event, callback); - } - return installObj.install(); - }); - } +export function install(url, eventCallback, + {_mozAddonManager = window.navigator.mozAddonManager} = {}) { + return _mozAddonManager.createInstall({url}) + .then((installObj) => { + const callback = (e) => eventCallback(installObj, e); + for (const event of installEventList) { + installObj.addEventListener(event, callback); + } + return installObj.install(); + }); +} - uninstall() { - return this.getAddon() - .then((addon) => addon.uninstall()) - .then((result) => { - // Until bug 1268075 this will resolve with a boolean - // for success and failure. - if (result === false) { - return Promise.reject(new Error('Uninstall failed')); - } - // eslint-disable-next-line consistent-return - return; - }); - } +export function uninstall(guid, {_mozAddonManager = window.navigator.mozAddonManager} = {}) { + return getAddon(guid, {_mozAddonManager}) + .then((addon) => addon.uninstall()) + .then((result) => { + // Until bug 1268075 this will resolve with a boolean + // for success and failure. + if (result === false) { + throw new Error('Uninstall failed'); + } + return; + }); } diff --git a/src/disco/components/Addon.js b/src/disco/components/Addon.js index d57fc671452..7f89e4aca4e 100644 --- a/src/disco/components/Addon.js +++ b/src/disco/components/Addon.js @@ -8,7 +8,7 @@ import purify from 'core/purify'; import config from 'config'; import themeAction, { getThemeData } from 'disco/themePreview'; import tracking from 'core/tracking'; -import { AddonManager } from 'disco/addonManager'; +import * as addonManager from 'disco/addonManager'; import InstallButton from 'disco/components/InstallButton'; import { @@ -46,7 +46,7 @@ export class Addon extends React.Component { guid: PropTypes.string.isRequired, headerURL: PropTypes.string, heading: PropTypes.string.isRequired, - i18n: PropTypes.string.isRequired, + i18n: PropTypes.object.isRequired, iconUrl: PropTypes.string, id: PropTypes.string.isRequired, installURL: PropTypes.string, @@ -185,15 +185,15 @@ export function makeProgressHandler(dispatch, guid) { }; } -export function mapDispatchToProps(dispatch, { _tracking = tracking } = {}) { +export function mapDispatchToProps(dispatch, { _tracking = tracking, + _addonManager = addonManager } = {}) { if (config.get('server')) { return {}; } return { setInitialStatus({ guid, installURL }) { - const addonManager = new AddonManager(guid, installURL); const payload = {guid, url: installURL}; - return addonManager.getAddon() + return _addonManager.getAddon(guid) .then( (addon) => { const status = addon.type === THEME_TYPE && !addon.isEnabled ? UNINSTALLED : INSTALLED; @@ -203,10 +203,9 @@ export function mapDispatchToProps(dispatch, { _tracking = tracking } = {}) { }, install({ guid, installURL, name }) { - const addonManager = new AddonManager(guid, installURL, makeProgressHandler(dispatch, guid)); dispatch({type: 'START_DOWNLOAD', payload: {guid}}); _tracking.sendEvent({action: 'addon', category: INSTALL_CATEGORY, label: name}); - return addonManager.install(); + return _addonManager.install(installURL, makeProgressHandler(dispatch, guid)); }, installTheme(node, guid, name, _themeAction = themeAction) { @@ -220,15 +219,14 @@ export function mapDispatchToProps(dispatch, { _tracking = tracking } = {}) { }); }, - uninstall({ guid, installURL, name, type }) { - const addonManager = new AddonManager(guid, installURL); + uninstall({ guid, name, type }) { dispatch({type: 'START_UNINSTALL', payload: {guid}}); const action = { [EXTENSION_TYPE]: 'addon', [THEME_TYPE]: 'theme', }[type] || 'invalid'; _tracking.sendEvent({action, category: UNINSTALL_CATEGORY, label: name}); - return addonManager.uninstall() + return _addonManager.uninstall(guid) .then(() => dispatch({type: 'UNINSTALL_COMPLETE', payload: {guid}})); }, }; diff --git a/src/disco/containers/DiscoPane.js b/src/disco/containers/DiscoPane.js index 1cb8af263d3..5e5dfced0ff 100644 --- a/src/disco/containers/DiscoPane.js +++ b/src/disco/containers/DiscoPane.js @@ -21,6 +21,11 @@ export class DiscoPane extends React.Component { static propTypes = { i18n: PropTypes.object.isRequired, results: PropTypes.arrayOf(PropTypes.object), + AddonComponent: PropTypes.object.isRequred, + } + + static defaultProps = { + AddonComponent: Addon, } constructor() { @@ -41,7 +46,7 @@ export class DiscoPane extends React.Component { } render() { - const { results, i18n } = this.props; + const { results, i18n, AddonComponent } = this.props; const { showVideo } = this.state; return ( @@ -70,7 +75,7 @@ export class DiscoPane extends React.Component { - {results.map((item) => )} + {results.map((item) => )}
{i18n.gettext('See more add-ons!')} diff --git a/tests/client/disco/TestAddonManager.js b/tests/client/disco/TestAddonManager.js index c9620fef69f..0bf1bcf1952 100644 --- a/tests/client/disco/TestAddonManager.js +++ b/tests/client/disco/TestAddonManager.js @@ -1,9 +1,9 @@ -import { AddonManager } from 'disco/addonManager'; +import * as addonManager from 'disco/addonManager'; import { installEventList } from 'disco/constants'; import { unexpectedSuccess } from 'tests/client/helpers'; -describe('AddonManager', () => { +describe('addonManager', () => { let fakeAddon; let fakeCallback; let fakeInstallObj; @@ -27,29 +27,18 @@ describe('AddonManager', () => { fakeMozAddonManager.createInstall.returns(Promise.resolve(fakeInstallObj)); }); - it('should throw if mozAddonManager is not provided', () => { - assert.throws(() => { - // eslint-disable-next-line no-unused-vars - const addonManager = new AddonManager('whatevs', fakeInstallUrl, fakeCallback); - }, Error, /mozAddonManager not available/); - }); - describe('getAddon()', () => { it('should call mozAddonManager.getAddonByID() with id', () => { - const addonManager = new AddonManager('test-id', fakeInstallUrl, fakeCallback, - {mozAddonManager: fakeMozAddonManager}); fakeMozAddonManager.getAddonByID.returns(Promise.resolve(fakeAddon)); - return addonManager.getAddon() + return addonManager.getAddon('test-id', {_mozAddonManager: fakeMozAddonManager}) .then(() => { assert.ok(fakeMozAddonManager.getAddonByID.calledWith('test-id')); }); }); it('should reject if mozAddonManager.getAddonByID() resolves with falsey addon', () => { - const addonManager = new AddonManager('test-id', fakeInstallUrl, fakeCallback, - {mozAddonManager: fakeMozAddonManager}); fakeMozAddonManager.getAddonByID.returns(Promise.resolve(false)); - return addonManager.getAddon() + return addonManager.getAddon('test-id', {_mozAddonManager: fakeMozAddonManager}) .then(unexpectedSuccess, (err) => assert.equal(err.message, 'Addon not found')); }); @@ -57,81 +46,64 @@ describe('AddonManager', () => { describe('install()', () => { it('should call mozAddonManager.createInstall() with url', () => { - const addonManager = new AddonManager('test-id', fakeInstallUrl, fakeCallback, - {mozAddonManager: fakeMozAddonManager}); - addonManager.install(); + addonManager.install(fakeInstallUrl, fakeCallback, {_mozAddonManager: fakeMozAddonManager}); assert.ok(fakeMozAddonManager.createInstall.calledWith({url: fakeInstallUrl})); }); - it('should call installObj.addEventListener to setup events', () => { - const addonManager = new AddonManager('test-id', fakeInstallUrl, fakeCallback, - {mozAddonManager: fakeMozAddonManager}); - return addonManager.install() + it('should call installObj.addEventListener to setup events', () => addonManager.install( + fakeInstallUrl, fakeCallback, {_mozAddonManager: fakeMozAddonManager}) .then(() => { assert.equal(fakeInstallObj.addEventListener.callCount, installEventList.length); - }); - }); + })); - it('should call installObj.install()', () => { - const addonManager = new AddonManager('test-id', fakeInstallUrl, fakeCallback, - {mozAddonManager: fakeMozAddonManager}); - return addonManager.install() + it('should call installObj.install()', () => addonManager.install( + fakeInstallUrl, fakeCallback, {_mozAddonManager: fakeMozAddonManager}) .then(() => { assert.ok(fakeInstallObj.install.called); - }); - }); + })); it('passes the installObj, the event and the id to the callback', () => { - const addonManager = new AddonManager('test-id', fakeInstallUrl, fakeCallback, - {mozAddonManager: fakeMozAddonManager}); const fakeEvent = {type: 'fakeEvent'}; let callback; fakeInstallObj.addEventListener = (event, cb) => { callback = cb; }; - return addonManager.install() - .then(() => { - callback(fakeEvent); - assert.ok(fakeCallback.calledWith(fakeInstallObj, fakeEvent, 'test-id')); - }); + return addonManager.install( + fakeInstallUrl, fakeCallback, {_mozAddonManager: fakeMozAddonManager}) + .then(() => { + callback(fakeEvent); + assert.ok(fakeCallback.calledWith(fakeInstallObj, fakeEvent)); + }); }); }); describe('uninstall()', () => { it('should reject if getAddonByID resolves with falsey value', () => { - const addonManager = new AddonManager('test-id', fakeInstallUrl, fakeCallback, - {mozAddonManager: fakeMozAddonManager}); fakeMozAddonManager.getAddonByID.returns(Promise.resolve(false)); // If the code doesn't resolve this will blow up. - return addonManager.uninstall() + return addonManager.uninstall('test-id', {_mozAddonManager: fakeMozAddonManager}) .then(unexpectedSuccess, (err) => assert.equal(err.message, 'Addon not found')); }); it('should reject if addon.uninstall resolves with false', () => { - const addonManager = new AddonManager('test-id', fakeInstallUrl, fakeCallback, - {mozAddonManager: fakeMozAddonManager}); fakeAddon.uninstall.returns(Promise.resolve(false)); fakeMozAddonManager.getAddonByID.returns(Promise.resolve(fakeAddon)); - return addonManager.uninstall() + return addonManager.uninstall('test-id', {_mozAddonManager: fakeMozAddonManager}) .then(unexpectedSuccess, (err) => assert.equal(err.message, 'Uninstall failed')); }); it('should resolve if addon.uninstall resolves with true', () => { - const addonManager = new AddonManager('test-id', fakeInstallUrl, fakeCallback, - {mozAddonManager: fakeMozAddonManager}); fakeAddon.uninstall.returns(Promise.resolve(true)); fakeMozAddonManager.getAddonByID.returns(Promise.resolve(fakeAddon)); // If the code doesn't resolve this will blow up. - return addonManager.uninstall(); + return addonManager.uninstall('test-id', {_mozAddonManager: fakeMozAddonManager}); }); it('should resolve if addon.uninstall just resolves', () => { - const addonManager = new AddonManager('test-id', fakeInstallUrl, fakeCallback, - {mozAddonManager: fakeMozAddonManager}); fakeAddon.uninstall.returns(Promise.resolve()); fakeMozAddonManager.getAddonByID.returns(Promise.resolve(fakeAddon)); // If the code doesn't resolve this will blow up. - return addonManager.uninstall(); + return addonManager.uninstall('test-id', {_mozAddonManager: fakeMozAddonManager}); }); }); }); diff --git a/tests/client/disco/components/TestAddon.js b/tests/client/disco/components/TestAddon.js index 0c887cec2a8..65dcd386c2e 100644 --- a/tests/client/disco/components/TestAddon.js +++ b/tests/client/disco/components/TestAddon.js @@ -5,15 +5,13 @@ import { Simulate, } from 'react-addons-test-utils'; import { findDOMNode } from 'react-dom'; -import { Provider } from 'react-redux'; -import { createStore } from 'redux'; import config from 'config'; -import Addon, { +import { + Addon, makeProgressHandler, mapDispatchToProps, mapStateToProps, } from 'disco/components/Addon'; -import * as addonManager from 'disco/addonManager'; import { ERROR, INSTALL_CATEGORY, @@ -27,6 +25,7 @@ import { } from 'disco/constants'; import { stubAddonManager, getFakeI18nInst } from 'tests/client/helpers'; import I18nProvider from 'core/i18n/Provider'; +import translate from 'core/i18n/translate'; const result = { id: 'test-id', @@ -36,16 +35,16 @@ const result = { description: 'test-editorial-description', }; -const store = createStore((s) => s, {installations: {}, addons: {}}); - function renderAddon(data) { + const props = {...data, setInitialStatus: sinon.stub()}; + + const MyAddon = translate({withRef: true})(Addon); + return findRenderedComponentWithType(renderIntoDocument( - - - + - ), Addon).getWrappedInstance().getWrappedInstance(); + ), MyAddon).getWrappedInstance(); } describe('', () => { @@ -53,7 +52,6 @@ describe('', () => { let root; beforeEach(() => { - stubAddonManager(); root = renderAddon(result); }); @@ -121,7 +119,6 @@ describe('', () => { let root; beforeEach(() => { - stubAddonManager(); const data = {...result, type: THEME_TYPE}; root = renderAddon(data); }); @@ -142,7 +139,6 @@ describe('', () => { let themeAction; beforeEach(() => { - stubAddonManager(); themeAction = sinon.stub(); const data = {...result, type: THEME_TYPE, themeAction}; root = renderAddon(data); @@ -178,7 +174,6 @@ describe('', () => { describe('mapStateToProps', () => { it('pulls the installation data from the state', () => { - stubAddonManager(); const addon = { guid: 'foo@addon', downloadProgress: 75, @@ -229,11 +224,11 @@ describe('', () => { describe('setInitialStatus', () => { it('sets the status to INSTALLED when add-on found', () => { - stubAddonManager(); const dispatch = sinon.spy(); const guid = '@foo'; const installURL = 'http://the.url'; - const { setInitialStatus } = mapDispatchToProps(dispatch); + const { setInitialStatus } = mapDispatchToProps( + dispatch, {_addonManager: stubAddonManager()}); return setInitialStatus({guid, installURL}) .then(() => { assert(dispatch.calledWith({ @@ -244,11 +239,12 @@ describe('', () => { }); it('sets the status to INSTALLED when an installed theme is found', () => { - stubAddonManager({getAddon: Promise.resolve({type: THEME_TYPE, isEnabled: true})}); + const fakeAddonManager = stubAddonManager( + {getAddon: Promise.resolve({type: THEME_TYPE, isEnabled: true})}); const dispatch = sinon.spy(); const guid = '@foo'; const installURL = 'http://the.url'; - const { setInitialStatus } = mapDispatchToProps(dispatch); + const { setInitialStatus } = mapDispatchToProps(dispatch, {_addonManager: fakeAddonManager}); return setInitialStatus({guid, installURL}) .then(() => { assert(dispatch.calledWith({ @@ -259,11 +255,12 @@ describe('', () => { }); it('sets the status to UNINSTALLED when an uninstalled theme is found', () => { - stubAddonManager({getAddon: Promise.resolve({type: THEME_TYPE, isEnabled: false})}); + const fakeAddonManager = stubAddonManager( + {getAddon: Promise.resolve({type: THEME_TYPE, isEnabled: false})}); const dispatch = sinon.spy(); const guid = '@foo'; const installURL = 'http://the.url'; - const { setInitialStatus } = mapDispatchToProps(dispatch); + const { setInitialStatus } = mapDispatchToProps(dispatch, {_addonManager: fakeAddonManager}); return setInitialStatus({guid, installURL}) .then(() => { assert(dispatch.calledWith({ @@ -274,11 +271,11 @@ describe('', () => { }); it('sets the status to UNINSTALLED when not found', () => { - stubAddonManager({getAddon: Promise.reject()}); + const fakeAddonManager = stubAddonManager({getAddon: Promise.reject()}); const dispatch = sinon.spy(); const guid = '@foo'; const installURL = 'http://the.url'; - const { setInitialStatus } = mapDispatchToProps(dispatch); + const { setInitialStatus } = mapDispatchToProps(dispatch, {_addonManager: fakeAddonManager}); return setInitialStatus({guid, installURL}) .then(() => { assert(dispatch.calledWith({ @@ -293,26 +290,26 @@ describe('', () => { const guid = '@install'; const installURL = 'https://mysite.com/download.xpi'; - it('installs the addon on a new AddonManager', () => { - stubAddonManager(); + it('calls addonManager.install()', () => { + const fakeAddonManager = stubAddonManager(); const dispatch = sinon.spy(); - const { install } = mapDispatchToProps(dispatch); + const { install } = mapDispatchToProps(dispatch, {_addonManager: fakeAddonManager}); return install({guid, installURL}) .then(() => { - assert(addonManager.AddonManager.calledWithNew, 'new AddonManager() called'); - assert(addonManager.AddonManager.calledWith(guid, installURL, sinon.match.func)); + assert(fakeAddonManager.install.calledWith(installURL, sinon.match.func)); }); }); - it('tracks an addon inntall', () => { - stubAddonManager(); + it('tracks an addon install', () => { + const fakeAddonManager = stubAddonManager(); const name = 'hai-addon'; const type = 'extension'; const dispatch = sinon.spy(); const fakeTracking = { sendEvent: sinon.spy(), }; - const { install } = mapDispatchToProps(dispatch, {_tracking: fakeTracking}); + const { install } = mapDispatchToProps(dispatch, + {_tracking: fakeTracking, _addonManager: fakeAddonManager}); return install({guid, installURL, name, type}) .then(() => { assert(fakeTracking.sendEvent.calledWith({ @@ -325,9 +322,9 @@ describe('', () => { it('should dispatch START_DOWNLOAD', () => { - stubAddonManager(); + const fakeAddonManager = stubAddonManager(); const dispatch = sinon.spy(); - const { install } = mapDispatchToProps(dispatch); + const { install } = mapDispatchToProps(dispatch, {_addonManager: fakeAddonManager}); return install({guid, installURL}) .then(() => assert(dispatch.calledWith({ type: 'START_DOWNLOAD', @@ -340,26 +337,26 @@ describe('', () => { const guid = '@uninstall'; const installURL = 'https://mysite.com/download.xpi'; - it('prepares the addon on a new AddonManager', () => { - stubAddonManager(); + it('calls addonManager.uninstall()', () => { + const fakeAddonManager = stubAddonManager(); const dispatch = sinon.spy(); - const { uninstall } = mapDispatchToProps(dispatch); + const { uninstall } = mapDispatchToProps(dispatch, {_addonManager: fakeAddonManager}); return uninstall({guid, installURL}) .then(() => { - assert(addonManager.AddonManager.calledWithNew, 'new AddonManager() called'); - assert(addonManager.AddonManager.calledWith(guid, installURL)); + assert(fakeAddonManager.uninstall.calledWith(guid)); }); }); it('tracks an addon uninstall', () => { - stubAddonManager(); + const fakeAddonManager = stubAddonManager(); const dispatch = sinon.spy(); const name = 'whatevs'; const type = 'extension'; const fakeTracking = { sendEvent: sinon.spy(), }; - const { uninstall } = mapDispatchToProps(dispatch, {_tracking: fakeTracking}); + const { uninstall } = mapDispatchToProps(dispatch, + {_tracking: fakeTracking, _addonManager: fakeAddonManager}); return uninstall({guid, installURL, name, type}) .then(() => { assert.ok(fakeTracking.sendEvent.calledWith({ @@ -371,14 +368,15 @@ describe('', () => { }); it('tracks a theme uninstall', () => { - stubAddonManager(); + const fakeAddonManager = stubAddonManager(); const dispatch = sinon.spy(); const name = 'whatevs'; const type = 'persona'; const fakeTracking = { sendEvent: sinon.spy(), }; - const { uninstall } = mapDispatchToProps(dispatch, {_tracking: fakeTracking}); + const { uninstall } = mapDispatchToProps(dispatch, + {_tracking: fakeTracking, _addonManager: fakeAddonManager}); return uninstall({guid, installURL, name, type}) .then(() => { assert(fakeTracking.sendEvent.calledWith({ @@ -390,14 +388,15 @@ describe('', () => { }); it('tracks a unknown type uninstall', () => { - stubAddonManager(); + const fakeAddonManager = stubAddonManager(); const dispatch = sinon.spy(); const name = 'whatevs'; const type = 'foo'; const fakeTracking = { sendEvent: sinon.spy(), }; - const { uninstall } = mapDispatchToProps(dispatch, {_tracking: fakeTracking}); + const { uninstall } = mapDispatchToProps(dispatch, + {_tracking: fakeTracking, _addonManager: fakeAddonManager}); return uninstall({guid, installURL, name, type}) .then(() => { assert(fakeTracking.sendEvent.calledWith({ @@ -409,9 +408,9 @@ describe('', () => { }); it('should dispatch START_UNINSTALL', () => { - stubAddonManager(); + const fakeAddonManager = stubAddonManager(); const dispatch = sinon.spy(); - const { uninstall } = mapDispatchToProps(dispatch); + const { uninstall } = mapDispatchToProps(dispatch, {_addonManager: fakeAddonManager}); return uninstall({guid, installURL}) .then(() => assert(dispatch.calledWith({ type: 'START_UNINSTALL', diff --git a/tests/client/disco/containers/TestDiscoPane.js b/tests/client/disco/containers/TestDiscoPane.js index 2d7824ad373..18ad959887c 100644 --- a/tests/client/disco/containers/TestDiscoPane.js +++ b/tests/client/disco/containers/TestDiscoPane.js @@ -7,19 +7,16 @@ import * as discoApi from 'disco/api'; import createStore from 'disco/store'; import { EXTENSION_TYPE } from 'disco/constants'; import * as helpers from 'disco/containers/DiscoPane'; -import { stubAddonManager, getFakeI18nInst } from 'tests/client/helpers'; +import { getFakeI18nInst, MockedSubComponent } from 'tests/client/helpers'; import { loadEntities } from 'core/actions'; import I18nProvider from 'core/i18n/Provider'; + // Use DiscoPane that isn't wrapped in asyncConnect. const { DiscoPane } = helpers; describe('AddonPage', () => { - beforeEach(() => { - stubAddonManager(); - }); - function render() { const store = createStore({ addons: {foo: {type: EXTENSION_TYPE}}, @@ -27,23 +24,17 @@ describe('AddonPage', () => { }); const results = [{addon: 'foo', type: EXTENSION_TYPE}]; const i18n = getFakeI18nInst(); + // We need the providers for i18n and since InstallButton will pull data from the store. return findDOMNode(renderIntoDocument( - + )); } - describe('rendered fields', () => { - it('renders an addon', () => { - const root = render(); - assert.ok(root.querySelector('.addon')); - }); - }); - describe('video', () => { it('is small by default', () => { const root = render(); diff --git a/tests/client/helpers.js b/tests/client/helpers.js index 7c336bc2bea..c41c2db5e2f 100644 --- a/tests/client/helpers.js +++ b/tests/client/helpers.js @@ -1,8 +1,6 @@ +import React from 'react'; import { createRenderer } from 'react-addons-test-utils'; -import * as addonManager from 'disco/addonManager'; -const AddonManager = addonManager.AddonManager; - export function shallowRender(stuff) { const renderer = createRenderer(); renderer.render(stuff); @@ -20,13 +18,11 @@ export function findByTag(root, tag) { } export function stubAddonManager({ getAddon = Promise.resolve({type: 'addon'}) } = {}) { - const instance = sinon.createStubInstance(AddonManager); - instance.getAddon = sinon.stub().returns(getAddon); - instance.install = sinon.stub().returns(Promise.resolve()); - instance.uninstall = sinon.stub().returns(Promise.resolve()); - const mockAddonManager = sinon.spy(() => instance); - sinon.stub(addonManager, 'AddonManager', mockAddonManager); - return instance; + const fakeAddonManager = {}; + fakeAddonManager.getAddon = sinon.stub().returns(getAddon); + fakeAddonManager.install = sinon.stub().returns(Promise.resolve()); + fakeAddonManager.uninstall = sinon.stub().returns(Promise.resolve()); + return fakeAddonManager; } export function unexpectedSuccess() { @@ -48,3 +44,9 @@ export function getFakeI18nInst() { dnpgettext: sinon.stub(), }; } + +export class MockedSubComponent extends React.Component { + render() { + return
; + } +}