diff --git a/src/disco/addonManager.js b/src/disco/addonManager.js index d03876eaa8c..c8b747e97b9 100644 --- a/src/disco/addonManager.js +++ b/src/disco/addonManager.js @@ -28,8 +28,12 @@ export function install(url, eventCallback, log.info(`[install] Adding listener for ${event}`); installObj.addEventListener(event, callback); } - log.info('Events to handle the installation initialized.'); - return installObj.install(); + return new Promise((resolve, reject) => { + installObj.addEventListener('onInstallEnded', () => resolve()); + installObj.addEventListener('onInstallFailed', () => reject()); + log.info('Events to handle the installation initialized.'); + installObj.install(); + }); }); } diff --git a/src/disco/components/Addon.js b/src/disco/components/Addon.js index d90ec719da1..2fcf5fda1f4 100644 --- a/src/disco/components/Addon.js +++ b/src/disco/components/Addon.js @@ -253,7 +253,8 @@ export function makeProgressHandler(dispatch, guid) { } export function mapDispatchToProps(dispatch, { _tracking = tracking, - _addonManager = addonManager } = {}) { + _addonManager = addonManager, + ...ownProps } = {}) { if (config.get('server')) { return {}; } @@ -272,10 +273,28 @@ export function mapDispatchToProps(dispatch, { _tracking = tracking, }); }, - install({ guid, installURL, name }) { + install() { + const { guid, i18n, iconUrl, installURL, name } = ownProps; dispatch({ type: START_DOWNLOAD, payload: { guid } }); _tracking.sendEvent({ action: 'addon', category: INSTALL_CATEGORY, label: name }); - return _addonManager.install(installURL, makeProgressHandler(dispatch, guid)); + return _addonManager.install(installURL, makeProgressHandler(dispatch, guid)) + .then(() => { + document.dispatchEvent(new CustomEvent('mozUITour', { + bubbles: true, + detail: { + action: 'showInfo', + data: { + target: 'appMenu', + icon: iconUrl, + title: i18n.gettext('Installed and added to toolbar'), + text: i18n.sprintf( + i18n.gettext('Click here to access %(name)s any time.'), + { name }), + buttons: [{ label: i18n.gettext('Ok'), callbackID: 'add-on-installed' }], + }, + }, + })); + }); }, uninstall({ guid, name, type }) { @@ -289,6 +308,6 @@ export function mapDispatchToProps(dispatch, { _tracking = tracking, }; } -export default connect( +export default translate({ withRef: true })(connect( mapStateToProps, mapDispatchToProps, undefined, { withRef: true } -)(translate({ withRef: true })(Addon)); +)(Addon)); diff --git a/src/disco/components/InstallButton.js b/src/disco/components/InstallButton.js index 51097a5a85e..6b8209a643c 100644 --- a/src/disco/components/InstallButton.js +++ b/src/disco/components/InstallButton.js @@ -44,7 +44,7 @@ export class InstallButton extends React.Component { if (type === THEME_TYPE && status === UNINSTALLED) { installTheme(this.refs.themeData, guid, name); } else if (status === UNINSTALLED) { - install({ guid, installURL, name }); + install(); } else if (status === INSTALLED) { uninstall({ guid, installURL, name, type }); } diff --git a/tests/client/disco/TestAddonManager.js b/tests/client/disco/TestAddonManager.js index 3e063fa8975..482e6b9a51f 100644 --- a/tests/client/disco/TestAddonManager.js +++ b/tests/client/disco/TestAddonManager.js @@ -20,15 +20,18 @@ describe('addonManager', () => { uninstall: sinon.stub(), }; fakeInstallObj = { - addEventListener: sinon.stub(), - install: sinon.stub(), + addEventListener: sinon.spy(function addEventListener(eventName, cb) { + this[`${eventName}Listener`] = cb; + }), + install: sinon.spy(function install() { + this.onInstallEndedListener(); + }), }; fakeMozAddonManager = { - createInstall: sinon.stub(), + createInstall: sinon.stub().returns(Promise.resolve(fakeInstallObj)), getAddonByID: sinon.stub(), addEventListener: sinon.stub(), }; - fakeMozAddonManager.createInstall.returns(Promise.resolve(fakeInstallObj)); }); describe('getAddon()', () => { @@ -49,31 +52,46 @@ describe('addonManager', () => { }); describe('install()', () => { - it('should call mozAddonManager.createInstall() with url', () => { - addonManager.install(fakeInstallUrl, fakeCallback, { _mozAddonManager: fakeMozAddonManager }); - assert.ok(fakeMozAddonManager.createInstall.calledWith({ url: fakeInstallUrl })); - }); - - it('should call installObj.addEventListener to setup events', () => addonManager.install( - fakeInstallUrl, fakeCallback, { _mozAddonManager: fakeMozAddonManager }) + it( + 'should call mozAddonManager.createInstall() with url', + () => addonManager.install( + fakeInstallUrl, fakeCallback, { _mozAddonManager: fakeMozAddonManager }) .then(() => { - assert.equal(fakeInstallObj.addEventListener.callCount, installEventList.length); + assert.ok(fakeMozAddonManager.createInstall.calledWith({ url: fakeInstallUrl })); })); + it( + 'should call installObj.addEventListener to setup events', + () => addonManager.install( + fakeInstallUrl, fakeCallback, { _mozAddonManager: fakeMozAddonManager }) + .then(() => { + // It registers an extra onInstallFailed and onInstallEnded listener. + assert.equal(fakeInstallObj.addEventListener.callCount, installEventList.length + 2); + })); + it('should call installObj.install()', () => addonManager.install( fakeInstallUrl, fakeCallback, { _mozAddonManager: fakeMozAddonManager }) .then(() => { assert.ok(fakeInstallObj.install.called); })); + it('rejects if the install fails', () => { + fakeInstallObj.install = sinon.spy(function install() { + this.onInstallFailedListener(); + }); + return addonManager.install( + fakeInstallUrl, fakeCallback, { _mozAddonManager: fakeMozAddonManager }) + .then( + unexpectedSuccess, + () => assert.ok(fakeInstallObj.install.called)); + }); + it('passes the installObj, the event and the id to the callback', () => { const fakeEvent = { type: 'fakeEvent' }; - let callback; - fakeInstallObj.addEventListener = (event, cb) => { callback = cb; }; return addonManager.install( fakeInstallUrl, fakeCallback, { _mozAddonManager: fakeMozAddonManager }) .then(() => { - callback(fakeEvent); + fakeInstallObj.onDownloadProgressListener(fakeEvent); assert.ok(fakeCallback.calledWith(fakeInstallObj, fakeEvent)); }); }); diff --git a/tests/client/disco/components/TestAddon.js b/tests/client/disco/components/TestAddon.js index efbad28b598..7aa9165f124 100644 --- a/tests/client/disco/components/TestAddon.js +++ b/tests/client/disco/components/TestAddon.js @@ -445,7 +445,10 @@ describe('', () => { it('calls addonManager.install()', () => { const fakeAddonManager = getFakeAddonManagerWrapper(); const dispatch = sinon.spy(); - const { install } = mapDispatchToProps(dispatch, { _addonManager: fakeAddonManager }); + const i18n = getFakeI18nInst(); + const { install } = mapDispatchToProps( + dispatch, + { _addonManager: fakeAddonManager, i18n, installURL }); return install({ guid, installURL }) .then(() => { assert(fakeAddonManager.install.calledWith(installURL, sinon.match.func)); @@ -456,12 +459,14 @@ describe('', () => { const fakeAddonManager = getFakeAddonManagerWrapper(); const name = 'hai-addon'; const type = 'extension'; + const i18n = getFakeI18nInst(); const dispatch = sinon.spy(); const fakeTracking = { sendEvent: sinon.spy(), }; - const { install } = mapDispatchToProps(dispatch, - { _tracking: fakeTracking, _addonManager: fakeAddonManager }); + const { install } = mapDispatchToProps( + dispatch, + { _tracking: fakeTracking, _addonManager: fakeAddonManager, i18n, name }); return install({ guid, installURL, name, type }) .then(() => { assert(fakeTracking.sendEvent.calledWith({ @@ -472,11 +477,13 @@ describe('', () => { }); }); - it('should dispatch START_DOWNLOAD', () => { const fakeAddonManager = getFakeAddonManagerWrapper(); + const i18n = getFakeI18nInst(); const dispatch = sinon.spy(); - const { install } = mapDispatchToProps(dispatch, { _addonManager: fakeAddonManager }); + const { install } = mapDispatchToProps( + dispatch, + { _addonManager: fakeAddonManager, guid, i18n }); return install({ guid, installURL }) .then(() => assert(dispatch.calledWith({ type: START_DOWNLOAD, diff --git a/tests/client/disco/components/TestInstallButton.js b/tests/client/disco/components/TestInstallButton.js index 39134f78368..f022f34bf65 100644 --- a/tests/client/disco/components/TestInstallButton.js +++ b/tests/client/disco/components/TestInstallButton.js @@ -136,7 +136,7 @@ describe('', () => { const button = renderButton({ guid, i18n, install, installURL, name, status: UNINSTALLED }); const root = findDOMNode(button); Simulate.click(root); - assert(install.calledWith({ guid, installURL, name })); + assert(install.calledWith()); }); it('should call uninstall function on click when installed', () => { diff --git a/tests/client/helpers.js b/tests/client/helpers.js index dd6a8077cb2..86bdb47fd06 100644 --- a/tests/client/helpers.js +++ b/tests/client/helpers.js @@ -46,6 +46,7 @@ export function getFakeI18nInst() { dpgettext: sinon.stub(), npgettext: sinon.stub(), dnpgettext: sinon.stub(), + sprintf: sinon.stub(), }; }