From 821413607a0718156f9d25d895e89b1c3d37aa8b Mon Sep 17 00:00:00 2001 From: Daniel Narvaez Date: Wed, 06 Feb 2013 13:49:14 +0000 Subject: Copy various bits from gaia --- (limited to 'apps/system/test') diff --git a/apps/system/test/integration/atoms/notification.js b/apps/system/test/integration/atoms/notification.js new file mode 100644 index 0000000..cf3e44f --- /dev/null +++ b/apps/system/test/integration/atoms/notification.js @@ -0,0 +1,15 @@ +(function notification(text, desc) { + window.addEventListener('mozChromeEvent', function(e) { + var detail = e.detail; + if (detail.type === 'desktop-notification') { + marionetteScriptFinished(JSON.stringify(detail)); + } + }); + + var notify = window.navigator.mozNotification; + var notification = notify.createNotification( + text, desc + ); + + notification.show(); +}.apply(this, arguments)); diff --git a/apps/system/test/integration/notification_test.js b/apps/system/test/integration/notification_test.js new file mode 100644 index 0000000..aa532e4 --- /dev/null +++ b/apps/system/test/integration/notification_test.js @@ -0,0 +1,41 @@ +require('/apps/system/test/integration/system_integration.js'); + +suite('notifications', function() { + + var device; + var app; + + MarionetteHelper.start(function(client) { + app = new SystemIntegration(client); + device = app.device; + }); + + setup(function() { + yield app.launch(); + }); + + test('text/description notification', function() { + + var title = 'uniq--integration--uniq'; + var description = 'q--desc--q'; + + yield device.setContext('chrome'); + + yield IntegrationHelper.sendAtom( + device, + '/apps/system/test/integration/atoms/notification', + true, + [title, description] + ); + + yield device.setContext('content'); + var container = yield app.element('notificationsContainer'); + + var text = yield container.getAttribute('innerHTML'); + assert.ok(text, 'container should have notifications'); + + assert.include(text, title, 'should include title'); + assert.include(text, description, 'should include description'); + }); +}); + diff --git a/apps/system/test/integration/system_integration.js b/apps/system/test/integration/system_integration.js new file mode 100644 index 0000000..1116e17 --- /dev/null +++ b/apps/system/test/integration/system_integration.js @@ -0,0 +1,49 @@ +require('/tests/js/app_integration.js'); +require('/tests/js/integration_helper.js'); + +function SystemIntegration() { + AppIntegration.apply(this, arguments); +} + +SystemIntegration.prototype = { + __proto__: AppIntegration.prototype, + + appName: 'System', + + selectors: { + /** notifications */ + notificationsContainer: '#notifications-container' + }, + + /** + * Override base launch method. + * The system app is launched by directly + * going to its url we we determine by getting + * all the apps and finding the 'System' apps origin. + */ + launch: function(callback) { + var self = this; + this.task(function(app, next, done) { + var device = app.device; + yield device.setScriptTimeout(5000); + + yield IntegrationHelper.importScript( + device, + '/tests/atoms/gaia_apps.js', + MochaTask.nodeNext + ); + + var result = yield device.executeAsyncScript( + 'GaiaApps.locateWithName("' + self.appName + '");' + ); + + // locate the origin of the system app. + // We must append the /index.html because of the app:// protocol. + yield device.goUrl(result.origin + '/index.html'); + + // complete the task + done(); + }, callback); + } + +}; diff --git a/apps/system/test/unit/_proxy.html b/apps/system/test/unit/_proxy.html new file mode 100644 index 0000000..2102451 --- /dev/null +++ b/apps/system/test/unit/_proxy.html @@ -0,0 +1,49 @@ + + + + + Serve the tests + + + + + + + + + +
+
+ + + + + + diff --git a/apps/system/test/unit/_sandbox.html b/apps/system/test/unit/_sandbox.html new file mode 100644 index 0000000..70d0efa --- /dev/null +++ b/apps/system/test/unit/_sandbox.html @@ -0,0 +1,28 @@ + + + + + Tests + + + + + + + +
+
+ +
+
+ + + + + diff --git a/apps/system/test/unit/app_install_manager_test.js b/apps/system/test/unit/app_install_manager_test.js new file mode 100644 index 0000000..7e235e5 --- /dev/null +++ b/apps/system/test/unit/app_install_manager_test.js @@ -0,0 +1,1036 @@ +'use strict'; + +requireApp('system/test/unit/mock_app.js'); +requireApp('system/test/unit/mock_chrome_event.js'); +requireApp('system/test/unit/mock_statusbar.js'); +requireApp('system/test/unit/mock_manifest_helper.js'); +requireApp('system/test/unit/mock_app.js'); +requireApp('system/test/unit/mock_system_banner.js'); +requireApp('system/test/unit/mock_notification_screen.js'); +requireApp('system/test/unit/mock_applications.js'); +requireApp('system/test/unit/mock_utility_tray.js'); +requireApp('system/test/unit/mock_modal_dialog.js'); +requireApp('system/test/unit/mock_navigator_wake_lock.js'); +requireApp('system/test/unit/mocks_helper.js'); + +requireApp('system/js/app_install_manager.js'); + +var mocksForAppInstallManager = [ + 'StatusBar', + 'SystemBanner', + 'NotificationScreen', + 'Applications', + 'UtilityTray', + 'ModalDialog', + 'ManifestHelper' +]; + +mocksForAppInstallManager.forEach(function(mockName) { + if (! window[mockName]) { + window[mockName] = null; + } +}); + +suite('system/AppInstallManager >', function() { + var realL10n; + var realDispatchResponse; + var realRequestWakeLock; + + var fakeDialog, fakeNotif; + var fakeInstallCancelDialog, fakeDownloadCancelDialog; + + var lastL10nParams = null; + var lastDispatchedResponse = null; + + var mocksHelper; + + suiteSetup(function() { + realL10n = navigator.mozL10n; + navigator.mozL10n = { + get: function get(key, params) { + lastL10nParams = params; + if (params) { + return key + JSON.stringify(params); + } + + return key; + } + }; + + realDispatchResponse = AppInstallManager.dispatchResponse; + AppInstallManager.dispatchResponse = function fakeDispatch(id, type) { + lastDispatchedResponse = { + id: id, + type: type + }; + }; + + realRequestWakeLock = navigator.requestWakeLock; + navigator.requestWakeLock = MockNavigatorWakeLock.requestWakeLock; + + mocksHelper = new MocksHelper(mocksForAppInstallManager); + mocksHelper.suiteSetup(); + }); + + suiteTeardown(function() { + AppInstallManager.dialog = null; + AppInstallManager.msg = null; + AppInstallManager.size = null; + AppInstallManager.authorName = null; + AppInstallManager.authorUrl = null; + AppInstallManager.installButton = null; + AppInstallManager.cancelButton = null; + AppInstallManager.installCallback = null; + AppInstallManager.cancelCallback = null; + + navigator.mozL10n = realL10n; + AppInstallManager.dispatchResponse = realDispatchResponse; + + navigator.requestWakeLock = realRequestWakeLock; + realRequestWakeLock = null; + + mocksHelper.suiteTeardown(); + }); + + setup(function() { + fakeDialog = document.createElement('form'); + fakeDialog.id = 'app-install-dialog'; + fakeDialog.innerHTML = [ + '
', + '

', + '', + '', + '', + '', + '', + '', + '', + '', + '', + '
Size
Author', + '', + '
', + '
', + '', + '', + '', + '', + '
' + ].join(''); + + fakeInstallCancelDialog = document.createElement('form'); + fakeInstallCancelDialog.id = 'app-install-cancel-dialog'; + fakeInstallCancelDialog.innerHTML = [ + '
', + '

Cancel Install

', + '

', + 'Cancelling ' + + 'will not refund a purchase. Refunds for paid content are ' + + 'provided by the original seller.', + 'Apps can be ' + + 'installed later from the original installation source.', + '

', + '

' + + 'Are you sure you want to cancel this install?

', + '', + '', + '', + '', + '
' + ].join(''); + + fakeDownloadCancelDialog = document.createElement('form'); + fakeDownloadCancelDialog.id = 'app-download-cancel-dialog'; + fakeDownloadCancelDialog.innerHTML = [ + '
', + '

', + '

' + + 'The download can be restarted later.

', + '', + '', + '', + '', + '
' + ].join(''); + + fakeNotif = document.createElement('div'); + fakeNotif.id = 'install-manager-notification-container'; + + document.body.appendChild(fakeDialog); + document.body.appendChild(fakeInstallCancelDialog); + document.body.appendChild(fakeDownloadCancelDialog); + document.body.appendChild(fakeNotif); + + mocksHelper.setup(); + + AppInstallManager.init(); + }); + + teardown(function() { + fakeDialog.parentNode.removeChild(fakeDialog); + fakeInstallCancelDialog.parentNode.removeChild(fakeInstallCancelDialog); + fakeDownloadCancelDialog.parentNode.removeChild(fakeDownloadCancelDialog); + fakeNotif.parentNode.removeChild(fakeNotif); + lastDispatchedResponse = null; + lastL10nParams = null; + + mocksHelper.teardown(); + MockNavigatorWakeLock.mTeardown(); + }); + + suite('init >', function() { + test('should bind dom elements', function() { + assert.equal('app-install-dialog', AppInstallManager.dialog.id); + assert.equal('app-install-message', AppInstallManager.msg.id); + assert.equal('app-install-size', AppInstallManager.size.id); + assert.equal('app-install-author-name', AppInstallManager.authorName.id); + assert.equal('app-install-author-url', AppInstallManager.authorUrl.id); + assert.equal('app-install-install-button', + AppInstallManager.installButton.id); + assert.equal('app-install-cancel-button', + AppInstallManager.cancelButton.id); + assert.equal('app-install-cancel-dialog', + AppInstallManager.installCancelDialog.id); + assert.equal('app-install-confirm-cancel-button', + AppInstallManager.confirmCancelButton.id); + assert.equal('app-install-resume-button', + AppInstallManager.resumeButton.id); + }); + + test('should bind to the click event', function() { + assert.equal(AppInstallManager.handleInstall.name, + AppInstallManager.installButton.onclick.name); + assert.equal(AppInstallManager.showInstallCancelDialog.name, + AppInstallManager.cancelButton.onclick.name); + assert.equal(AppInstallManager.handleInstallCancel.name, + AppInstallManager.confirmCancelButton.onclick.name); + assert.equal(AppInstallManager.hideInstallCancelDialog.name, + AppInstallManager.resumeButton.onclick.name); + }); + }); + + suite('events >', function() { + suite('webapps-ask-install >', function() { + setup(function() { + var evt = new MockChromeEvent({ + type: 'webapps-ask-install', + id: 42, + app: { + manifest: { + name: 'Fake app', + size: 5245678, + developer: { + name: 'Fake dev', + url: 'http://fakesoftware.com' + } + } + } + }); + + AppInstallManager.handleAppInstallPrompt(evt.detail); + }); + + test('should display the dialog', function() { + assert.equal('visible', AppInstallManager.dialog.className); + }); + + test('should fill the message with app name', function() { + assert.equal(AppInstallManager.msg.textContent, + 'install-app{"name":"Fake app"}'); + }); + + test('should use the mini manifest if no manifest', function() { + var evt = new MockChromeEvent({ + type: 'webapps-ask-install', + id: 42, + app: { + updateManifest: { + name: 'Fake app', + size: 5245678, + developer: { + name: 'Fake dev', + url: 'http://fakesoftware.com' + } + } + } + }); + + AppInstallManager.handleAppInstallPrompt(evt.detail); + + assert.equal(AppInstallManager.msg.textContent, + 'install-app{"name":"Fake app"}'); + }); + + suite('developer infos >', function() { + test('should fill the developer infos', function() { + assert.equal('Fake dev', AppInstallManager.authorName.textContent); + assert.equal('http://fakesoftware.com', + AppInstallManager.authorUrl.textContent); + }); + + test('should tell if the developer is unknown', function() { + var evt = new MockChromeEvent({ + type: 'webapps-ask-install', + id: 42, + app: { + updateManifest: { + name: 'Fake app', + size: 5245678 + } + } + }); + + AppInstallManager.handleAppInstallPrompt(evt.detail); + assert.equal('unknown', AppInstallManager.authorName.textContent); + assert.equal('', AppInstallManager.authorUrl.textContent); + }); + + test('should handle empty developer object properly', function() { + var evt = new MockChromeEvent({ + type: 'webapps-ask-install', + id: 42, + app: { + updateManifest: { + name: 'Fake app', + size: 5245678, + developer: {} + } + } + }); + + AppInstallManager.handleAppInstallPrompt(evt.detail); + assert.equal('unknown', AppInstallManager.authorName.textContent); + assert.equal('', AppInstallManager.authorUrl.textContent); + }); + + test('should tell if the developer name is unknown', function() { + var evt = new MockChromeEvent({ + type: 'webapps-ask-install', + id: 42, + app: { + updateManifest: { + name: 'Fake app', + size: 5245678, + developer: { + url: 'http://example.com' + } + } + } + }); + + AppInstallManager.handleAppInstallPrompt(evt.detail); + assert.equal('unknown', AppInstallManager.authorName.textContent); + assert.equal('http://example.com', + AppInstallManager.authorUrl.textContent); + }); + + test('the developer url should default to blank', function() { + var evt = new MockChromeEvent({ + type: 'webapps-ask-install', + id: 42, + app: { + updateManifest: { + name: 'Fake app', + size: 5245678, + developer: { + name: 'Fake dev' + } + } + } + }); + + AppInstallManager.handleAppInstallPrompt(evt.detail); + assert.equal('Fake dev', AppInstallManager.authorName.textContent); + assert.equal('', AppInstallManager.authorUrl.textContent); + }); + }); + + suite('install size >', function() { + test('should display the package size', function() { + assert.equal('5.00 MB', AppInstallManager.size.textContent); + }); + + test('should tell if the size is unknown', function() { + var evt = new MockChromeEvent({ + type: 'webapps-ask-install', + id: 42, + app: { + manifest: { + name: 'Fake app', + developer: { + name: 'Fake dev', + url: 'http://fakesoftware.com' + } + } + } + }); + + AppInstallManager.handleAppInstallPrompt(evt.detail); + assert.equal('unknown', AppInstallManager.size.textContent); + }); + }); + + suite('callbacks >', function() { + suite('install >', function() { + var defaultPrevented = false; + setup(function() { + AppInstallManager.handleInstall({preventDefault: function() { + defaultPrevented = true; + }}); + }); + + test('should dispatch a webapps-install-granted with the right id', + function() { + assert.equal(42, lastDispatchedResponse.id); + assert.equal('webapps-install-granted', + lastDispatchedResponse.type); + }); + + test('should prevent the default to avoid form submission', + function() { + assert.isTrue(defaultPrevented); + }); + + test('should hide the dialog', function() { + assert.equal('', AppInstallManager.dialog.className); + }); + + test('should remove the callback', function() { + assert.equal(null, AppInstallManager.installCallback); + }); + }); + + suite('show cancel dialog >', function() { + setup(function() { + AppInstallManager.showInstallCancelDialog(); + }); + + test('should show cancel dialog and hide dialog', function() { + assert.equal('visible', + AppInstallManager.installCancelDialog.className); + assert.equal('', AppInstallManager.dialog.className); + }); + }); + + suite('hide cancel dialog >', function() { + setup(function() { + AppInstallManager.hideInstallCancelDialog(); + }); + + test('should hide cancel dialog and show dialog', function() { + assert.equal('', AppInstallManager.installCancelDialog.className); + assert.equal('visible', AppInstallManager.dialog.className); + }); + }); + + suite('confirm cancel >', function() { + setup(function() { + AppInstallManager.handleInstallCancel(); + }); + + test('should dispatch a webapps-install-denied', function() { + assert.equal(42, lastDispatchedResponse.id); + assert.equal('webapps-install-denied', lastDispatchedResponse.type); + }); + + test('should hide the dialog', function() { + assert.equal('', AppInstallManager.installCancelDialog.className); + }); + + test('should remove the callback', function() { + assert.equal(null, AppInstallManager.installCancelCallback); + }); + }); + }); + }); + }); + + suite('duringInstall >', function() { + var mockApp, mockAppName; + + function dispatchEvent() { + var e = new CustomEvent('applicationinstall', { + detail: { application: mockApp } + }); + window.dispatchEvent(e); + } + + suite('hosted app without cache >', function() { + setup(function() { + mockAppName = 'Fake hosted app'; + mockApp = new MockApp({ + manifest: { + name: mockAppName, + developer: { + name: 'Fake dev', + url: 'http://fakesoftware.com' + } + }, + updateManifest: null, + installState: 'installed' + }); + MockSystemBanner.mTeardown(); + dispatchEvent(); + }); + + test('should not show the icon', function() { + assert.isUndefined(MockStatusBar.wasMethodCalled['incSystemDownloads']); + }); + + test('should not add a notification', function() { + assert.equal(fakeNotif.childElementCount, 0); + }); + + test('should display a confirmation', function() { + assert.equal(MockSystemBanner.mMessage, + 'app-install-success{"appName":"' + mockAppName + '"}'); + }); + + }); + + function beforeFirstProgressSuite() { + suite('before first progress >', function() { + test('should not show the icon', function() { + var method = 'incSystemDownloads'; + assert.isUndefined(MockStatusBar.wasMethodCalled[method]); + }); + + test('should not add a notification', function() { + assert.equal(fakeNotif.childElementCount, 0); + }); + + suite('on downloadsuccess >', function() { + setup(function() { + // reseting these mocks as we want to test only one call + MockNotificationScreen.mTeardown(); + MockStatusBar.mTeardown(); + + mockApp.mTriggerDownloadSuccess(); + }); + + test('should not remove a notification', function() { + var method = 'decExternalNotifications'; + assert.isUndefined(MockNotificationScreen.wasMethodCalled[method]); + }); + + test('should not remove the download icon', function() { + var method = 'decSystemDownloads'; + assert.isUndefined(MockStatusBar.wasMethodCalled[method]); + }); + + test('should display a confirmation', function() { + assert.equal(MockSystemBanner.mMessage, + 'app-install-success{"appName":"' + mockAppName + '"}'); + }); + + }); + + suite('on downloaderror >', function() { + setup(function() { + // reseting these mocks as we want to test only one call + MockNotificationScreen.mTeardown(); + MockStatusBar.mTeardown(); + + mockApp.mTriggerDownloadError(); + }); + + test('should not remove a notification', function() { + var method = 'decExternalNotifications'; + assert.isUndefined(MockNotificationScreen.wasMethodCalled[method]); + }); + + test('should not remove the download icon', function() { + var method = 'decSystemDownloads'; + assert.isUndefined(MockStatusBar.wasMethodCalled[method]); + }); + }); + }); + } + + function downloadErrorSuite(downloadEventsSuite) { + suite('on downloadError >', function() { + setup(function() { + // reseting these mocks as we only want to test the + // following call + MockStatusBar.mTeardown(); + MockSystemBanner.mTeardown(); + MockModalDialog.mTeardown(); + }); + + function downloadErrorTests(errorName) { + test('should display an error', function() { + var expectedErrorMsg = knownErrors[errorName] + + '{"appName":"' + mockAppName + '"}'; + + assert.equal(MockSystemBanner.mMessage, expectedErrorMsg); + }); + + test('should not display the error dialog', function() { + assert.isFalse(MockModalDialog.alert.mWasCalled); + }); + + } + + function specificDownloadErrorSuite(errorName) { + suite(errorName + ' >', function() { + setup(function() { + mockApp.mTriggerDownloadError(errorName); + }); + + downloadErrorTests(errorName); + }); + } + + var knownErrors = { + 'FALLBACK_ERROR': 'app-install-generic-error', + 'NETWORK_ERROR': 'app-install-download-failed', + 'DOWNLOAD_ERROR': 'app-install-download-failed', + 'MISSING_MANIFEST': 'app-install-install-failed', + 'INVALID_MANIFEST': 'app-install-install-failed', + 'INSTALL_FROM_DENIED': 'app-install-install-failed', + 'INVALID_SECURITY_LEVEL': 'app-install-install-failed', + 'INVALID_PACKAGE': 'app-install-install-failed', + 'APP_CACHE_DOWNLOAD_ERROR': 'app-install-download-failed' + }; + + Object.keys(knownErrors).forEach(specificDownloadErrorSuite); + + suite('GENERIC_ERROR >', function() { + setup(function() { + mockApp.mTriggerDownloadError('GENERIC_ERROR'); + }); + + test('should remove the notif', function() { + assert.equal(fakeNotif.childElementCount, 0); + }); + + test('should remove the icon', function() { + var method = 'decSystemDownloads'; + assert.ok(MockStatusBar.wasMethodCalled[method]); + }); + + beforeFirstProgressSuite(); + downloadEventsSuite(/*afterError*/ true); + }); + + }); + } + + suite('hosted app with cache >', function() { + setup(function() { + mockAppName = 'Fake hosted app with cache'; + mockApp = new MockApp({ + manifest: { + name: mockAppName, + developer: { + name: 'Fake dev', + url: 'http://fakesoftware.com' + } + }, + updateManifest: null, + installState: 'pending' + }); + MockSystemBanner.mTeardown(); + dispatchEvent(); + }); + + function downloadEventsSuite(afterError) { + var suiteName = 'on first progress'; + if (afterError) { + suiteName += ' after error'; + } + suiteName += ' >'; + + suite(suiteName, function() { + setup(function() { + // reseting these mocks as we want to test only the following + // calls + MockNotificationScreen.mTeardown(); + MockStatusBar.mTeardown(); + + mockApp.mTriggerDownloadProgress(NaN); + }); + + test('should add a notification', function() { + var method = 'incExternalNotifications'; + assert.equal(fakeNotif.childElementCount, 1); + assert.ok(MockNotificationScreen.wasMethodCalled[method]); + }); + + test('notification should have a message', function() { + assert.equal(fakeNotif.querySelector('.message').textContent, + 'downloadingAppMessage{"appName":"Fake hosted app with cache"}'); + assert.equal(fakeNotif.querySelector('progress').textContent, + 'downloadingAppProgressIndeterminate'); + }); + + test('notification progress should be indeterminate', function() { + assert.equal(fakeNotif.querySelector('progress').position, -1); + }); + + test('should request wifi wake lock', function() { + assert.equal('wifi', MockNavigatorWakeLock.mLastWakeLock.topic); + assert.isFalse(MockNavigatorWakeLock.mLastWakeLock.released); + }); + + suite('on downloadsuccess >', function() { + setup(function() { + mockApp.mTriggerDownloadSuccess(); + }); + + test('should remove the notif', function() { + var method = 'decExternalNotifications'; + assert.equal(fakeNotif.childElementCount, 0); + assert.ok(MockNotificationScreen.wasMethodCalled[method]); + }); + + test('should release the wifi wake lock', function() { + assert.equal('wifi', MockNavigatorWakeLock.mLastWakeLock.topic); + assert.isTrue(MockNavigatorWakeLock.mLastWakeLock.released); + }); + }); + + test('on downloadsuccess > should remove only its progress handler', + function() { + + var onprogressCalled = false; + mockApp.onprogress = function() { + onprogressCalled = true; + }; + mockApp.mTriggerDownloadSuccess(); + mockApp.mTriggerDownloadProgress(10); + assert.isTrue(onprogressCalled); + }); + + test('on downloadsuccess > should display a confirmation', + function() { + mockApp.mTriggerDownloadSuccess(); + assert.equal(MockSystemBanner.mMessage, + 'app-install-success{"appName":"' + mockAppName + '"}'); + }); + + suite('on indeterminate progress >', function() { + setup(function() { + mockApp.mTriggerDownloadProgress(NaN); + }); + + test('should keep the progress indeterminate', function() { + var progressNode = fakeNotif.querySelector('progress'); + assert.equal(progressNode.position, -1); + assert.equal(progressNode.textContent, + 'downloadingAppProgressIndeterminate'); + }); + }); + + suite('on quantified progress >', function() { + setup(function() { + mockApp.mTriggerDownloadProgress(10); + }); + + test('should have a quantified progress', function() { + var progressNode = fakeNotif.querySelector('progress'); + assert.equal(progressNode.position, -1); + assert.equal(progressNode.textContent, + 'downloadingAppProgressNoMax{"progress":"10.00 bytes"}'); + }); + }); + + if (!afterError) { + downloadErrorSuite(downloadEventsSuite); + } + }); + } + + beforeFirstProgressSuite(); + downloadEventsSuite(/*afterError*/ false); + }); + + suite('packaged app >', function() { + setup(function() { + mockAppName = 'Fake packaged app'; + mockApp = new MockApp({ + manifest: null, + updateManifest: { + name: mockAppName, + size: 5245678, + developer: { + name: 'Fake dev', + url: 'http://fakesoftware.com' + } + }, + installState: 'pending' + }); + + dispatchEvent(); + }); + + + function downloadEventsSuite(afterError) { + var suiteName = 'on first progress'; + if (afterError) { + suiteName += ' after error'; + } + suiteName += ' >'; + + suite(suiteName, function() { + var newprogress = 5; + + setup(function() { + // resetting this mock because we want to test only the + // following call + MockNotificationScreen.mTeardown(); + MockSystemBanner.mTeardown(); + mockApp.mTriggerDownloadProgress(newprogress); + }); + + test('should add a notification', function() { + var method = 'incExternalNotifications'; + assert.equal(fakeNotif.childElementCount, 1); + assert.ok(MockNotificationScreen.wasMethodCalled[method]); + }); + + test('notification should have a message', function() { + var expectedText = 'downloadingAppMessage{"appName":"' + + mockAppName + '"}'; + assert.equal(fakeNotif.querySelector('.message').textContent, + expectedText); + }); + + test('notification progress should have a max and a value', + function() { + assert.equal(fakeNotif.querySelector('progress').max, + mockApp.updateManifest.size); + assert.equal(fakeNotif.querySelector('progress').value, + newprogress); + }); + + test('notification progress should not be indeterminate', + function() { + assert.notEqual(fakeNotif.querySelector('progress').position, -1); + }); + + test('should request wifi wake lock', function() { + assert.equal('wifi', MockNavigatorWakeLock.mLastWakeLock.topic); + assert.isFalse(MockNavigatorWakeLock.mLastWakeLock.released); + }); + + suite('on downloadsuccess >', function() { + setup(function() { + mockApp.mTriggerDownloadSuccess(); + }); + + test('should remove the notif', function() { + var method = 'decExternalNotifications'; + assert.equal(fakeNotif.childElementCount, 0); + assert.ok(MockNotificationScreen.wasMethodCalled[method]); + }); + + test('should release the wifi wake lock', function() { + assert.equal('wifi', MockNavigatorWakeLock.mLastWakeLock.topic); + assert.isTrue(MockNavigatorWakeLock.mLastWakeLock.released); + }); + + }); + + test('on downloadsuccess > ' + + 'should not break if wifi unlock throws an exception', + function() { + MockNavigatorWakeLock.mThrowAtNextUnlock(); + mockApp.mTriggerDownloadSuccess(); + assert.ok(true); + }); + + test('on downloadsuccess > should display a confirmation', + function() { + mockApp.mTriggerDownloadSuccess(); + assert.equal(MockSystemBanner.mMessage, + 'app-install-success{"appName":"' + mockAppName + '"}'); + }); + + test('on indeterminate progress > ' + + 'should update the progress text content', + function() { + mockApp.mTriggerDownloadProgress(NaN); + + var progressNode = fakeNotif.querySelector('progress'); + assert.equal(progressNode.textContent, + 'downloadingAppProgressIndeterminate'); + }); + + suite('on progress >', function() { + var size, ratio; + var newprogress = 10; + + setup(function() { + size = mockApp.updateManifest.size; + ratio = newprogress / size; + mockApp.mTriggerDownloadProgress(newprogress); + }); + + test('should update the progress notification', function() { + var progressNode = fakeNotif.querySelector('progress'); + assert.equal(progressNode.position, ratio); + assert.equal(progressNode.textContent, + 'downloadingAppProgress{"progress":"10.00 bytes",' + + '"max":"5.00 MB"}'); + }); + }); + + if (!afterError) { + downloadErrorSuite(downloadEventsSuite); + } + }); + } + + beforeFirstProgressSuite(); + downloadEventsSuite(/*afterError*/ false); + + suite('on INSUFFICIENT_STORAGE downloaderror >', function() { + test('should display an alert', function() { + mockApp.mTriggerDownloadError('INSUFFICIENT_STORAGE'); + assert.isNull(MockSystemBanner.mMessage); + assert.isTrue(MockModalDialog.alert.mWasCalled); + var args = MockModalDialog.alert.mArgs; + assert.equal(args[0], 'not-enough-space'); + assert.equal(args[1], 'not-enough-space-message'); + assert.deepEqual(args[2], { title: 'ok' }); + }); + + beforeFirstProgressSuite(); + downloadEventsSuite(/*afterError*/ true); + }); + + + }); + + suite('cancelling a download >', function() { + setup(function() { + mockApp = new MockApp({ installState: 'pending' }); + MockApplications.mRegisterMockApp(mockApp); + dispatchEvent(); + mockApp.mTriggerDownloadProgress(10); + }); + + test('tapping the notification should display the dialog', function() { + fakeNotif.querySelector('.fake-notification').click(); + assert.isTrue(fakeDownloadCancelDialog.classList.contains('visible')); + }); + + test('tapping the container should not display the dialog', function() { + fakeNotif.click(); + assert.isFalse(fakeDownloadCancelDialog.classList.contains('visible')); + }); + + test('should set the title with the app name', function() { + fakeNotif.querySelector('.fake-notification').click(); + var title = fakeDownloadCancelDialog.querySelector('h1'); + assert.equal(title.textContent, 'stopDownloading{"app":"Mock app"}'); + }); + + test('should add the manifest url in data set', function() { + fakeNotif.querySelector('.fake-notification').click(); + assert.equal(fakeDownloadCancelDialog.dataset.manifest, + mockApp.manifestURL); + }); + + test('should hide the notification tray', function() { + fakeNotif.querySelector('.fake-notification').click(); + assert.isFalse(MockUtilityTray.mShown); + }); + + test('cancelling should hide the dialog only', function() { + fakeNotif.querySelector('.fake-notification').click(); + fakeDownloadCancelDialog.querySelector('.cancel').click(); + assert.isFalse(fakeDownloadCancelDialog.classList.contains('visible')); + assert.isFalse(mockApp.mCancelCalled); + }); + + test('accepting should hide the dialog and call cancelDownload on app', + function() { + fakeNotif.querySelector('.fake-notification').click(); + fakeDownloadCancelDialog.querySelector('.confirm').click(); + assert.isFalse(fakeDownloadCancelDialog.classList.contains('visible')); + assert.ok(mockApp.mCancelCalled); + }); + + test('accepting should hide the dialog but not call cancelDownload ' + + 'if app is uninstalled', + function() { + fakeNotif.querySelector('.fake-notification').click(); + MockApplications.mUnregisterMockApp(mockApp); + fakeDownloadCancelDialog.querySelector('.confirm').click(); + assert.isFalse(fakeDownloadCancelDialog.classList.contains('visible')); + assert.isFalse(mockApp.mCancelCalled); + }); + }); + + }); + + suite('restarting after reboot >', function() { + var mockApp, installedMockApp; + + setup(function() { + mockApp = new MockApp({ + updateManifest: null, + installState: 'pending' + }); + + installedMockApp = new MockApp({ + updateManifest: null, + installState: 'installed' + }); + + var e = new CustomEvent('applicationready', { + detail: { applications: {} } + }); + e.detail.applications[mockApp.manifestURL] = mockApp; + e.detail.applications[installedMockApp.manifestURL] = installedMockApp; + window.dispatchEvent(e); + + }); + + test('should add a notification for the pending app', function() { + mockApp.mTriggerDownloadProgress(50); + + var method = 'incExternalNotifications'; + assert.equal(fakeNotif.childElementCount, 1); + assert.ok(MockNotificationScreen.wasMethodCalled[method]); + }); + + test('should not add a notification for the installed app', function() { + installedMockApp.mTriggerDownloadProgress(50); + + var method = 'incExternalNotifications'; + assert.equal(fakeNotif.childElementCount, 0); + assert.isUndefined(MockNotificationScreen.wasMethodCalled[method]); + }); + }); + + suite('humanizeSize >', function() { + test('should handle bytes size', function() { + assert.equal('42.00 bytes', AppInstallManager.humanizeSize(42)); + }); + + test('should handle kilobytes size', function() { + assert.equal('1.00 kB', AppInstallManager.humanizeSize(1024)); + }); + + test('should handle megabytes size', function() { + assert.equal('4.67 MB', AppInstallManager.humanizeSize(4901024)); + }); + + test('should handle gigabytes size', function() { + assert.equal('3.73 GB', AppInstallManager.humanizeSize(4000901024)); + }); + + test('should handle 0', function() { + assert.equal('0.00 bytes', AppInstallManager.humanizeSize(0)); + }); + }); +}); diff --git a/apps/system/test/unit/battery_manager_test.js b/apps/system/test/unit/battery_manager_test.js new file mode 100644 index 0000000..ee85f86 --- /dev/null +++ b/apps/system/test/unit/battery_manager_test.js @@ -0,0 +1,204 @@ +'use strict'; + +requireApp('system/test/unit/mock_navigator_battery.js'); +requireApp('system/test/unit/mock_settings_listener.js'); +requireApp('system/test/unit/mock_sleep_menu.js'); +requireApp('system/test/unit/mock_gesture_detector.js'); +requireApp('system/test/unit/mocks_helper.js'); +requireApp('system/js/battery_manager.js'); + +var mocksForBatteryManager = [ + 'SettingsListener', + 'SleepMenu', + 'GestureDetector' +]; + +mocksForBatteryManager.forEach(function(mockName) { + if (! window[mockName]) { + window[mockName] = null; + } +}); + + +suite('battery manager >', function() { + var realBattery; + var screenNode, notifNode, overlayNode; + var mocksHelper; + var tinyTimeout = 10; + + suiteSetup(function() { + mocksHelper = new MocksHelper(mocksForBatteryManager); + mocksHelper.suiteSetup(); + + realBattery = BatteryManager._battery; + BatteryManager._battery = MockNavigatorBattery; + + // must be big enough, otherwise the BatteryManager timeout occurs + // before the different suites execute. + BatteryManager.TOASTER_TIMEOUT = tinyTimeout; + }); + + suiteTeardown(function() { + mocksHelper.suiteTeardown(); + + BatteryManager._battery = realBattery; + realBattery = null; + }); + + setup(function() { + mocksHelper.setup(); + + var batteryNotificationMarkup = + '
' + + '
' + + '' + + 'Battery almost empty' + + '' + + '
' + + '
'; + + screenNode = document.createElement('div'); + screenNode.id = 'screen'; + screenNode.innerHTML = batteryNotificationMarkup; + document.body.appendChild(screenNode); + + overlayNode = document.getElementById('system-overlay'); + notifNode = document.getElementById('battery'); + + MockNavigatorBattery.level = 1; + PowerSaveHandler.init(); + BatteryManager.init(); + }); + + teardown(function() { + mocksHelper.teardown(); + + screenNode.parentNode.removeChild(screenNode); + }); + + function sendScreenChange(val) { + var detail = { screenEnabled: val}; + var evt = new CustomEvent('screenchange', { detail: detail }); + window.dispatchEvent(evt); + } + + function sendLevelChange(level) { + MockNavigatorBattery.level = level; + + var evt = new CustomEvent('levelchange'); + MockNavigatorBattery.mTriggerEvent(evt); + } + + function sendChargingChange(val) { + MockNavigatorBattery.charging = val; + + var evt = new CustomEvent('chargingchange'); + MockNavigatorBattery.mTriggerEvent(evt); + } + + suite('"level is near empty" notification >', function() { + function assertDisplayed() { + assert.ok(overlayNode.classList.contains('battery')); + } + + function assertNotDisplayed() { + assert.isFalse(overlayNode.classList.contains('battery')); + } + + teardown(function(done) { + // wait for the notification timeout + setTimeout(done, tinyTimeout * 2); + }); + + suite('init >', function() { + setup(function() { + MockNavigatorBattery.level = 0.02; + BatteryManager.init(); + }); + + test('display notification', function() { + assertDisplayed(); + }); + }); + + suite('battery goes empty >', function() { + setup(function() { + sendLevelChange(0.05); + }); + + test('display notification', function() { + assertDisplayed(); + }); + + test('do not display twice', function(done) { + setTimeout(function() { + sendLevelChange(0.02); + + assertNotDisplayed(); + done(); + }, tinyTimeout * 2); + }); + + suite('charging >', function() { + setup(function() { + sendChargingChange(true); + }); + + test('hide notification', function() { + assertNotDisplayed(); + }); + + test('not charging > show notification', function() { + sendChargingChange(false); + assertDisplayed(); + }); + + suite('goes up >', function() { + setup(function() { + sendLevelChange(0.2); + }); + + test('hide notification', function() { + assertNotDisplayed(); + }); + + suite('not charging >', function() { + setup(function() { + sendChargingChange(false); + }); + + test('should not display', function() { + assertNotDisplayed(); + }); + + test('goes empty again > display notification', function() { + sendLevelChange(0.02); + + assertDisplayed(); + }); + }); + + }); + + }); + }); + + suite('screen goes off > battery goes empty >', function() { + setup(function() { + sendScreenChange(false); + sendLevelChange(0.05); + }); + + test('no notification', function() { + assertNotDisplayed(); + }); + + test('screen goes on > display notification', function() { + sendScreenChange(true); + + assertDisplayed(); + }); + }); + }); +}); diff --git a/apps/system/test/unit/date_picker_test.js b/apps/system/test/unit/date_picker_test.js new file mode 100644 index 0000000..51d8867 --- /dev/null +++ b/apps/system/test/unit/date_picker_test.js @@ -0,0 +1,483 @@ +requireApp('system/js/value_selector/date_picker.js'); + +suite('date picker', function() { + var subject; + var Calc; + var triggerEvent; + + teardown(function() { + var el = document.getElementById('test'); + el.parentNode.removeChild(el); + }); + + setup(function() { + var div = document.createElement('div'); + div.id = 'test'; + document.body.appendChild(div); + subject = new DatePicker(div); + Calc = DatePicker.Calc; + subject._position = new Date(2012, 1, 1); + }); + + suite('Calc', function() { + var mocked = {}; + + function mock(fn, value) { + if (!(fn in mocked)) { + mocked[fn] = subject[fn]; + } + subject[fn] = function() { + return value; + }; + } + + teardown(function() { + var key; + for (key in mocked) { + if (mocked.hasOwnProperty(key)) { + subject[key] = mocked[key]; + } + } + }); + + setup(function() { + subject = DatePicker.Calc; + }); + + suite('#isSameDate', function() { + + test('same day', function() { + assert.isTrue(subject.isSameDate( + new Date(2012, 1, 1, 8), + new Date(2012, 1, 1, 23) + )); + }); + + test('same day different month', function() { + assert.isFalse(subject.isSameDate( + new Date(2012, 2, 1, 8), + new Date(2012, 1, 1, 8) + )); + }); + }); + + suite('#isToday', function() { + test('when given is today', function() { + var result = subject.isToday(new Date()); + + assert.isTrue(result, 'should be true when given today'); + }); + + test('when given is not today', function() { + var now = new Date(); + now.setDate(now.getDate() - 1); + var result = subject.isToday(now); + + assert.isFalse(result, 'should be false when given is not today'); + }); + }); + + suite('#isPast', function() { + test('when date is passed', function() { + var date = new Date(); + date.setTime(Date.now() - 1000); + var result = subject.isPast(date); + + assert.isTrue(result, 'should be true when given is in the past'); + }); + + test('when given is in the future', function() { + var date = new Date(); + date.setTime(Date.now() + 100); + var result = subject.isPast(date); + + assert.isFalse(result, + 'should return false when date is in the future'); + }); + + }); + + suite('#isFuture', function() { + test('when date is passed', function() { + var date = new Date(); + date.setTime(Date.now() - 100); + var result = subject.isFuture(date); + + assert.isFalse(result); + }); + + test('when given is in the future', function() { + var date = new Date(Date.now() + 100); + var result = subject.isFuture(date); + + assert.isTrue(result); + }); + + }); + + suite('#dateFromId', function() { + var id; + var result; + var date = new Date(2012, 7, 3); + + setup(function() { + id = subject.getDayId(date); + }); + + test('id to date', function() { + assert.deepEqual( + subject.dateFromId(id), + date + ); + }); + + }); + + test('#getDayId', function() { + var result = subject.getDayId( + new Date(2012, 3, 7) + ); + + assert.equal(result, '2012-3-7'); + }); + + suite('#relativeState', function() { + + setup(function() { + mock('isToday', false); + }); + + test('when in the past', function() { + mock('isPast', true); + var state = subject.relativeState( + new Date(1991, 1, 1), + new Date(1991, 1, 1) + ); + + assert.equal(state, subject.PAST); + }); + + test('when in the future', function() { + mock('isPast', false); + var state = subject.relativeState( + new Date(1991, subject.today.getMonth(), 1), + new Date(1991, subject.today.getMonth(), 1) + ); + assert.equal(state, subject.FUTURE); + }); + + test('when is in a different month in the past', function() { + mock('isPast', true); + + var state = subject.relativeState( + new Date(1991, subject.today.getMonth() - 1, 1), + new Date(1991, subject.today.getMonth(), 1) + ); + + assert.include(state, subject.PAST); + assert.include(state, subject.OTHER_MONTH); + }); + + test('when is in a different month in the future', function() { + mock('isPast', false); + + var state = subject.relativeState( + new Date(1991, subject.today.getMonth() + 1, 1), + new Date(1991, subject.today.getMonth(), 1) + ); + + assert.include(state, subject.FUTURE); + assert.include(state, subject.OTHER_MONTH); + }); + + + test('when is today', function() { + mock('isToday', true); + var state = subject.relativeState(new Date(1991, 1, 1)); + + assert.equal(state, subject.PRESENT); + }); + + }); + + }); + + suite('#_daysIn', function() { + test('leap year', function() { + var result = subject._daysInMonth(2012, 1); + assert.equal(result, 29); + }); + + test('normal', function() { + var result = subject._daysInMonth(2012, 0); + assert.equal(result, 31); + }); + }); + + suite('#_renderDay', function() { + + test('simple', function() { + var date = new Date(2012, 1, 27); + var result = subject._renderDay(date).firstChild; + var html = result.outerHTML; + + assert.ok(html); + assert.equal(result.dataset.date, Calc.getDayId(date)); + assert.include(html, '27'); + }); + + test('today', function() { + var date = new Date(); + var result = subject._renderDay(date); + assert.ok(result.classList.contains('present')); + }); + + test('past', function() { + var date = new Date(2009, 1, 1); + var result = subject._renderDay(date); + assert.ok(result.classList.contains('past')); + }); + + test('future', function() { + var date = new Date(); + date.setDate(date.getDate() + 2); + + var result = subject._renderDay(date); + assert.ok(result.classList.contains('future')); + }); + }); + + suite('#_renderWeek', function() { + var days = [ + new Date(2012, 0, 29), + new Date(2012, 0, 30), + new Date(2012, 0, 31), + new Date(2012, 1, 1), + new Date(2012, 1, 2), + new Date(2012, 1, 3), + new Date(2012, 1, 4) + ]; + + var result; + + setup(function() { + result = subject._renderWeek(days); + }); + + test('container', function() { + assert.equal(result.tagName.toLowerCase(), 'ol'); + assert.ok(result.outerHTML); + }); + + days.forEach(function(day) { + test('week day ' + day, function() { + var expected = subject._renderDay(day); + assert.include(result.outerHTML, expected.outerHTML); + }); + }); + + }); + + suite('#_renderMonth', function() { + + function weekHtml(start, end) { + var range = Calc.daysBetween(start, end); + return subject._renderWeek(range).outerHTML; + } + + test('Feb 2012', function() { + var month = 1; + var year = 2012; + + var result = subject._renderMonth(year, month); + var html = result.outerHTML; + + assert.ok(html, 'has contents'); + + assert.include( + html, + weekHtml(new Date(2012, 0, 29), new Date(2012, 1, 4)), + 'has first week' + ); + + assert.include( + html, + weekHtml(new Date(2012, 1, 5), new Date(2012, 1, 11)), + 'has second week' + ); + + assert.include( + html, + weekHtml(new Date(2012, 1, 12), new Date(2012, 1, 18)), + 'has third week' + ); + + assert.include( + html, + weekHtml(new Date(2012, 1, 19), new Date(2012, 1, 25)), + 'has fourth week' + ); + + + assert.include( + html, + weekHtml(new Date(2012, 1, 26), new Date(2012, 2, 3)), + 'has fifth week' + ); + }); + }); + + suite('prev/next', function() { + var calledWith; + + setup(function() { + subject.display(2012, 0, 1); + }); + + test('#next', function() { + subject.next(); + assert.equal(subject.year, 2012); + assert.equal(subject.month, 1); + }); + + test('#previous', function() { + subject.previous(); + assert.equal(subject.year, 2011); + assert.equal(subject.month, 11); + }); + + }); + + suite('prev/next with last day of a month', function() { + var calledWith; + + setup(function() { + // init as 2012/3/31 + subject.display(2012, 2, 31); + }); + + test('#next', function() { + subject.next(); + + // should be 2012/4/30 + assert.equal(subject.year, 2012); + assert.equal(subject.month, 3); + assert.equal(subject.date, 30); + }); + + test('#previous', function() { + subject.previous(); + + // should be 2012/2/29 + assert.equal(subject.year, 2012); + assert.equal(subject.month, 1); + assert.equal(subject.date, 29); + }); + + }); + + suite('#display', function() { + var year = 2012; + var month = 11; + var date = 1; + var calledWith; + + setup(function() { + calledWith = null; + subject.onmonthchange = function() { + calledWith = arguments; + }; + + subject.display(year, month, date); + }); + + test('initial render', function() { + assert.deepEqual( + calledWith[0], + new Date(year, month), + 'should fire onmonthchange' + ); + + assert.equal(subject.year, 2012); + assert.equal(subject.month, 11); + assert.equal(subject.date, 1); + assert.ok(subject.monthDisplay); + }); + + test('second rendering', function() { + subject.display(2011, 2, 1); + + assert.deepEqual( + calledWith[0], + new Date(2011, 2), + 'should fire onmonthchange again' + ); + + assert.equal(subject.year, 2011); + assert.equal(subject.month, 2); + assert.equal(subject.date, 1); + assert.ok(subject.monthDisplay); + }); + }); + + suite('setters', function() { + test('#value', function() { + var calledWith; + var date; + + subject.onvaluechange = function() { + calledWith = arguments; + } + + subject.value = date = new Date(2012, 1, 1); + + assert.deepEqual(subject.value, date); + assert.deepEqual(calledWith, [date, null]); + }); + }); + + suite('dom events', function() { + function triggerEvent(element, eventName) { + var event = document.createEvent('HTMLEvents'); + event.initEvent(eventName, true, true); + element.dispatchEvent(event); + } + + setup(function() { + subject.display(2012, 1, 1); + }); + + test('select', function() { + var calledWith; + subject.onvaluechange = function() { + calledWith = arguments; + } + + var target = subject.element.querySelector('[data-date="2012-1-1"]'); + triggerEvent(target, 'click'); + + assert.ok(target.classList.contains('selected'), 'adds selected class'); + + assert.deepEqual( + subject.value, + new Date(2012, 1, 1), + 'changes value' + ); + + assert.deepEqual( + calledWith, + [new Date(2012, 1, 1), new Date(2012, 1, 1)], + 'calls onvaluechange' + ); + + triggerEvent( + subject.element.querySelector('[data-date="2012-1-2"]'), + 'click' + ); + + assert.ok(!target.classList.contains('selected'), 'clears past selected'); + }); + }); + +}); + diff --git a/apps/system/test/unit/identity_test.js b/apps/system/test/unit/identity_test.js new file mode 100644 index 0000000..150944c --- /dev/null +++ b/apps/system/test/unit/identity_test.js @@ -0,0 +1,92 @@ +'use strict'; + +requireApp('system/js/identity.js'); +requireApp('system/test/unit/mock_chrome_event.js'); +requireApp('system/test/unit/mock_trusted_ui_manager.js'); +requireApp('system/test/unit/mock_l10n.js'); + +// ensure its defined as a global so mocha will not complain about us +// leaking new global variables during the test +if (!window.TrustedUIManager) { + window.TrustedUIManager = true; +} + +suite('identity', function() { + var subject; + var realL10n; + var realTrustedUIManager; + var realDispatchEvent; + + var lastDispatchedEvent = null; + + suiteSetup(function() { + subject = Identity; + realTrustedUIManager = window.TrustedUIManager; + window.TrustedUIManager = MockTrustedUIManager; + + realL10n = navigator.mozL10n; + navigator.mozL10n = MockL10n; + + realDispatchEvent = subject._dispatchEvent; + subject._dispatchEvent = function (obj) { + lastDispatchedEvent = obj; + }; + }); + + suiteTeardown(function() { + window.TrustedUIManager = realTrustedUIManager; + subject._dispatchEvent = realDispatchEvent; + + navigator.mozL10n = realL10n; + }); + + setup(function() {}); + + teardown(function() { + MockTrustedUIManager.mTeardown(); + }); + + suite('open popup', function() { + setup(function() { + var event = new MockChromeEvent({ + type: 'open-id-dialog', + id: 'test-open-event-id', + showUI: true + }); + subject.handleEvent(event); + }); + + test('popup parameters', function() { + assert.equal(MockTrustedUIManager.mOpened, true); + assert.equal(MockTrustedUIManager.mName, 'persona-signin'); + assert.equal(MockTrustedUIManager.mChromeEventId, 'test-open-event-id'); + }); + + test('frame event listener', function() { + var frame = MockTrustedUIManager.mFrame; + var event = document.createEvent('CustomEvent'); + event.initCustomEvent('mozbrowserloadstart', true, true, {target: frame}); + frame.dispatchEvent(event); + + assert.equal(frame, lastDispatchedEvent.frame); + assert.equal('test-open-event-id', lastDispatchedEvent.id); + }); + }); + + suite('close popup', function() { + setup(function() { + var event = new MockChromeEvent({ + type: 'received-id-assertion', + id: 'test-close-event-id', + showUI: true + }); + subject.handleEvent(event); + }); + + test('close', function() { + assert.equal(false, MockTrustedUIManager.mOpened); + assert.equal('test-close-event-id', lastDispatchedEvent.id); + }); + }); +}); + diff --git a/apps/system/test/unit/lockscreen_test.js b/apps/system/test/unit/lockscreen_test.js new file mode 100644 index 0000000..7258670 --- /dev/null +++ b/apps/system/test/unit/lockscreen_test.js @@ -0,0 +1,122 @@ +'use strict'; + +requireApp('system/test/unit/mock_settings_listener.js'); +requireApp('system/test/unit/mocks_helper.js'); +requireApp('system/test/unit/mock_l10n.js'); + +requireApp('system/js/lockscreen.js'); + +var mocksForStatusBar = ['SettingsListener']; + +mocksForStatusBar.forEach(function(mockName) { + if (! window[mockName]) { + window[mockName] = null; + } +}); + +suite('lockscreen', function() { + var mocksHelper; + + var realSettingsListener, realMozL10n; + + var fakeLockscreenPanel; + + var red_png, green_png; + + suiteSetup(function() { + mocksHelper = new MocksHelper(mocksForStatusBar); + mocksHelper.suiteSetup(); + realMozL10n = navigator.mozL10n; + navigator.mozL10n = MockL10n; + + red_png = + 'data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAUAAAAHgAQMAAADuQiYHAAAAA1BMVEX/AAAZ4gk3AAAAKUlEQVR4Xu3AMQEAAADCIPuntsROWAQAAAAAAAAAAAAAAAAAAAAAAADgTOAAAZXle7kAAAAASUVORK5CYII='; + green_png = + 'data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAASwAAAHgAQMAAAAlmcL5AAAAA1BMVEUAgACc+aWRAAAAKUlEQVR4Xu3BMQEAAADCoPVPbQ0PoAAAAAAAAAAAAAAAAAAAAAAAAL4MSSAAAZTTyRkAAAAASUVORK5CYII='; + }); + + suiteTeardown(function() { + mocksHelper.suiteTeardown(); + navigator.mozL10n = realMozL10n; + }); + + setup(function() { + fakeLockscreenPanel = document.createElement('div'); + fakeLockscreenPanel.classList.add('lockscreen-panel'); + fakeLockscreenPanel.setAttribute('data-wallpaper', ''); + document.body.appendChild(fakeLockscreenPanel); + + mocksHelper.setup(); + }); + + teardown(function() { + fakeLockscreenPanel.parentNode.removeChild(fakeLockscreenPanel); + mocksHelper.teardown(); + }); + + test('wallpaper has vignette effect', function(done) { + LockScreen.updateBackground(red_png); + + (function checkCanvas() { + var canvas = fakeLockscreenPanel.getElementsByTagName('canvas')[0]; + if (!canvas) { + setTimeout(checkCanvas, 10); + return; + } + var ctx = canvas.getContext('2d'); + var top_pixel = ctx.getImageData(0, 0, 1, 1).data; + + assert.equal(top_pixel[0], 77); + assert.equal(top_pixel[1], 0); + assert.equal(top_pixel[2], 0); + + var center_width = Math.floor(canvas.width / 2); + var center_height = Math.floor(canvas.height / 2); + var center_pixel = ctx.getImageData(center_width, center_height, + 1 , 1).data; + assert.equal(center_pixel[0], 251); + assert.equal(center_pixel[1], 0); + assert.equal(center_pixel[2], 0); + + done(); + })(); + + }); + + test('multiple wallpaper updates only keep one canvas', function(done) { + function waitFirstUpdate(callback) { + var first_canvas = fakeLockscreenPanel.getElementsByTagName('canvas')[0]; + if (!first_canvas) { + setTimeout(waitFirstUpdate, 10, callback); + return; + } + + setTimeout(callback, 10); + } + + function waitSecondUpdate(callback) { + var second_canvas = fakeLockscreenPanel.getElementsByTagName('canvas')[0]; + + var ctx = second_canvas.getContext('2d'); + var top_pixel = ctx.getImageData(0, 0, 1, 1).data; + // Canvas is not green yet + if (top_pixel[1] == 0) { + setTimeout(waitSecondUpdate, 10, callback); + return; + } + + setTimeout(callback, 10); + } + + LockScreen.updateBackground(red_png); + setTimeout(waitFirstUpdate, 10, function then() { + LockScreen.updateBackground(green_png); + + setTimeout(waitSecondUpdate, 10, function then2() { + assert.equal(fakeLockscreenPanel.getElementsByTagName('canvas').length, 1); + done(); + }); + }); + + }); +}); diff --git a/apps/system/test/unit/mobile_operator_test.js b/apps/system/test/unit/mobile_operator_test.js new file mode 100644 index 0000000..6204217 --- /dev/null +++ b/apps/system/test/unit/mobile_operator_test.js @@ -0,0 +1,96 @@ +/* This should live in the shared directory */ + +'use strict'; + +requireApp('system/shared/js/mobile_operator.js'); + +suite('shared/MobileOperator', function() { + var MockMobileConnection; + var BRAZIL_MCC = 724; + + + setup(function() { + MockMobileConnection = { + voice: { + network: { + shortName: 'Fake short', + longName: 'Fake long', + mnc: '6' + }, + cell: { gsmLocationAreaCode: 71 } + }, + iccInfo: { spn: 'Fake SPN' } + }; + }); + + suite('Worldwide connection', function() { + test('Connection with short name', function() { + var infos = MobileOperator.userFacingInfo(MockMobileConnection); + assert.equal(infos.operator, 'Fake short'); + assert.isUndefined(infos.carrier); + assert.isUndefined(infos.region); + }); + test('Connection with long name', function() { + MockMobileConnection.voice.network.shortName = ''; + var infos = MobileOperator.userFacingInfo(MockMobileConnection); + assert.equal(infos.operator, 'Fake long'); + assert.isUndefined(infos.carrier); + assert.isUndefined(infos.region); + }); + test('Connection with SPN display', function() { + MockMobileConnection.iccInfo.isDisplaySpnRequired = true; + var infos = MobileOperator.userFacingInfo(MockMobileConnection); + assert.equal(infos.operator, 'Fake SPN'); + assert.isUndefined(infos.carrier); + assert.isUndefined(infos.region); + }); + test('Connection with SPN display and network display', function() { + MockMobileConnection.iccInfo.isDisplaySpnRequired = true; + MockMobileConnection.iccInfo.isDisplayNetworkNameRequired = true; + var infos = MobileOperator.userFacingInfo(MockMobileConnection); + assert.equal(infos.operator, 'Fake short Fake SPN'); + assert.isUndefined(infos.carrier); + assert.isUndefined(infos.region); + }); + test('Connection with roaming', function() { + MockMobileConnection.voice.roaming = true; + var infos = MobileOperator.userFacingInfo(MockMobileConnection); + assert.equal(infos.operator, 'Fake short'); + assert.isUndefined(infos.carrier); + assert.isUndefined(infos.region); + }); + test('Connection with roaming and SPN display', function() { + MockMobileConnection.voice.roaming = true; + MockMobileConnection.iccInfo.isDisplaySpnRequired = true; + var infos = MobileOperator.userFacingInfo(MockMobileConnection); + assert.equal(infos.operator, 'Fake short'); + assert.isUndefined(infos.carrier); + assert.isUndefined(infos.region); + }); + }); + suite('Brazilian connection', function() { + test('Connection ', function() { + MockMobileConnection.voice.network.mcc = BRAZIL_MCC; + var infos = MobileOperator.userFacingInfo(MockMobileConnection); + assert.equal(infos.operator, 'Fake short'); + assert.equal(infos.carrier, 'VIVO'); + assert.equal(infos.region, 'BA 71'); + }); + test('Connection with unknown mnc', function() { + MockMobileConnection.voice.network.mcc = BRAZIL_MCC; + MockMobileConnection.voice.network.mnc = 42; + var infos = MobileOperator.userFacingInfo(MockMobileConnection); + assert.equal(infos.operator, 'Fake short'); + assert.equal(infos.carrier, '72442'); + assert.equal(infos.region, 'BA 71'); + }); + test('Connection with unknown gsmLocationAreaCode', function() { + MockMobileConnection.voice.network.mcc = BRAZIL_MCC; + MockMobileConnection.voice.cell.gsmLocationAreaCode = 2; + var infos = MobileOperator.userFacingInfo(MockMobileConnection); + assert.equal(infos.operator, 'Fake short'); + assert.equal(infos.carrier, 'VIVO'); + assert.equal(infos.region, ''); + }); + }); +}); diff --git a/apps/system/test/unit/mock_app.js b/apps/system/test/unit/mock_app.js new file mode 100644 index 0000000..2d34051 --- /dev/null +++ b/apps/system/test/unit/mock_app.js @@ -0,0 +1,93 @@ +'use strict'; + +var idGen = 0; + +function MockApp(opts) { + /* default values */ + this.origin = 'https://testapp.gaiamobile.org'; + this.manifestURL = 'https://testapp.gaiamobile.org/manifest' + + idGen + '.webapp'; + this.manifest = { + name: 'Mock app' + }; + + this.removable = true; + this.installState = 'installed'; + this.downloadAvailable = false; + this.downloadError = null; + this.downloadSize = null; + + this.mId = idGen++; + this.mDownloadCalled = false; + this.mCancelCalled = false; + + /* overwrite default values with whatever comes in "opts" from the test */ + if (opts) { + for (var key in opts) { + this[key] = opts[key]; + } + } +} + +MockApp.prototype.download = function() { + this.mDownloadCalled = true; +}; + +MockApp.prototype.cancelDownload = function() { + this.mCancelCalled = true; +}; + +MockApp.prototype.mTriggerDownloadAvailable = function(size) { + this.downloadAvailable = true; + this.downloadSize = size; + if (this.ondownloadavailable) { + this.ondownloadavailable({ + application: this + }); + } +}; + +MockApp.prototype.mTriggerDownloadSuccess = function() { + this.downloadAvailable = false; + this.downloadSize = null; + if (this.ondownloadsuccess) { + this.ondownloadsuccess({ + application: this + }); + } +}; + +MockApp.prototype.mTriggerDownloadError = function(error) { + this.downloadAvailable = true; + this.downloadSize = null; + + this.downloadError = { + name: error || 'UNKNOWN_ERROR' + }; + + if (this.ondownloaderror) { + this.ondownloaderror({ + application: this + }); + } +}; + +MockApp.prototype.mTriggerDownloadProgress = function(progress) { + this.progress = progress; + + if (this.onprogress) { + this.onprogress({ + application: this + }); + } +}; + +MockApp.prototype.mTriggerDownloadApplied = function() { + this.downloadAvailable = false; + this.downloadSize = null; + if (this.ondownloadapplied) { + this.ondownloadapplied({ + application: this + }); + } +}; diff --git a/apps/system/test/unit/mock_applications.js b/apps/system/test/unit/mock_applications.js new file mode 100644 index 0000000..8b3e765 --- /dev/null +++ b/apps/system/test/unit/mock_applications.js @@ -0,0 +1,26 @@ +var MockApplications = (function() { + var mockApps = {}; + + function getByManifestURL(url) { + return mockApps[url]; + } + + function mRegisterMockApp(mockApp) { + mockApps[mockApp.manifestURL] = mockApp; + } + + function mUnregisterMockApp(mockApp) { + mockApps[mockApp.manifestURL] = null; + } + + function mTeardown() { + mockApps = {}; + } + + return { + getByManifestURL: getByManifestURL, + mRegisterMockApp: mRegisterMockApp, + mUnregisterMockApp: mUnregisterMockApp, + mTeardown: mTeardown + }; +})(); diff --git a/apps/system/test/unit/mock_apps_mgmt.js b/apps/system/test/unit/mock_apps_mgmt.js new file mode 100644 index 0000000..948ef02 --- /dev/null +++ b/apps/system/test/unit/mock_apps_mgmt.js @@ -0,0 +1,64 @@ +var MockAppsMgmt = { + getAll: function mam_getAll() { + var request = {}; + + setTimeout((function nextTick() { + if (request.onsuccess) { + var evt = { + target: { + result: this.mApps + } + }; + request.onsuccess(evt); + if (this.mNext) { + this.mNext(); + } + } + }).bind(this)); + + return request; + }, + + applyDownload: function mam_applyDownload(app) { + this.mLastAppApplied = app; + }, + + mApps: [], + mLastAppApplied: null, + mNext: null, + mTeardown: function mam_mTeardown() { + this.mLastAppApplied = null; + this.mApps = []; + this.mNext = null; + }, + + mTriggerOninstall: function mam_mTriggerOninstall(app) { + if (this.oninstall) { + var evt = { + application: app + }; + this.oninstall(evt); + } + + var evt = document.createEvent('CustomEvent'); + evt.initCustomEvent('applicationinstall', + true, false, + { application: app }); + window.dispatchEvent(evt); + }, + + mTriggerOnuninstall: function mam_mTriggerOnuninstall(app) { + if (this.onuninstall) { + var evt = { + application: app + }; + this.onuninstall(evt); + } + + var evt = document.createEvent('CustomEvent'); + evt.initCustomEvent('applicationuninstall', + true, false, + { application: app }); + window.dispatchEvent(evt); + } +}; diff --git a/apps/system/test/unit/mock_asyncStorage.js b/apps/system/test/unit/mock_asyncStorage.js new file mode 100644 index 0000000..a657e8b --- /dev/null +++ b/apps/system/test/unit/mock_asyncStorage.js @@ -0,0 +1,36 @@ +'use strict'; + +var MockasyncStorage = { + mItems: {}, + + setItem: function(key, value, callback) { + this.mItems[key] = value; + if (typeof callback === 'function') { + callback(); + } + }, + + getItem: function(key, callback) { + var value = this.mItems[key]; + // use '|| null' will turn a 'false' to null + if (value === undefined) + value = null; + if (typeof callback === 'function') { + callback(value); + } + }, + + removeItem: function(key, callback) { + if (key in this.mItems) { + delete this.mItems[key]; + } + + if (typeof callback === 'function') { + callback(); + } + }, + + mTeardown: function() { + this.mItems = {}; + } +}; diff --git a/apps/system/test/unit/mock_chrome_event.js b/apps/system/test/unit/mock_chrome_event.js new file mode 100644 index 0000000..40555f9 --- /dev/null +++ b/apps/system/test/unit/mock_chrome_event.js @@ -0,0 +1,4 @@ +function MockChromeEvent(detail) { + this.type = 'mozChromeEvent'; + this.detail = detail; +} diff --git a/apps/system/test/unit/mock_custom_dialog.js b/apps/system/test/unit/mock_custom_dialog.js new file mode 100644 index 0000000..ed4b927 --- /dev/null +++ b/apps/system/test/unit/mock_custom_dialog.js @@ -0,0 +1,26 @@ +var MockCustomDialog = { + show: function(title, msg, cancel, confirm) { + this.mShown = true; + this.mShowedTitle = title; + this.mShowedMsg = msg; + this.mShowedCancel = cancel; + this.mShowedConfirm = confirm; + }, + + hide: function() { + this.mShown = false; + }, + + mShown: false, + mShowedTitle: null, + mShowedMsg: null, + mShowedCancel: null, + mShowedConfirm: null, + mTeardown: function teardown() { + this.mShown = false; + this.mShowedTitle = null; + this.mShowedMsg = null; + this.mShowedCancel = null; + this.mShowedConfirm = null; + } +}; diff --git a/apps/system/test/unit/mock_gesture_detector.js b/apps/system/test/unit/mock_gesture_detector.js new file mode 100644 index 0000000..b66e51c --- /dev/null +++ b/apps/system/test/unit/mock_gesture_detector.js @@ -0,0 +1,9 @@ +'use strict'; + +var MockGestureDetector = function() {}; + +MockGestureDetector.prototype = { + startDetecting: function() {}, + stopDetecting: function() {} +}; + diff --git a/apps/system/test/unit/mock_l10n.js b/apps/system/test/unit/mock_l10n.js new file mode 100644 index 0000000..35b01d8 --- /dev/null +++ b/apps/system/test/unit/mock_l10n.js @@ -0,0 +1,17 @@ +'use strict'; + +var MockL10n = { + get: function get(key, params) { + if (params) { + return key + JSON.stringify(params); + } + return key; + }, + DateTimeFormat: function() {} +}; + +MockL10n.DateTimeFormat.prototype = { + localeFormat: function mockLocaleFormat(time, strFormat) { + return '' + time; + } +}; diff --git a/apps/system/test/unit/mock_manifest_helper.js b/apps/system/test/unit/mock_manifest_helper.js new file mode 100644 index 0000000..34f9a13 --- /dev/null +++ b/apps/system/test/unit/mock_manifest_helper.js @@ -0,0 +1,5 @@ +MockManifestHelper = function(manifest) { + for (var prop in manifest) { + this[prop] = manifest[prop]; + } +}; diff --git a/apps/system/test/unit/mock_mobile_operator.js b/apps/system/test/unit/mock_mobile_operator.js new file mode 100644 index 0000000..430a613 --- /dev/null +++ b/apps/system/test/unit/mock_mobile_operator.js @@ -0,0 +1,15 @@ +'use strict'; + +var MockMobileOperator = { + userFacingInfo: function mmo_userFacingInfo(mobileConnection) { + return { + 'operator': this.mOperator, + 'carrier': this.mCarrier, + 'region': this.mRegion + } + }, + + mOperator: '', + mCarrier: '', + mRegion: '' +}; diff --git a/apps/system/test/unit/mock_modal_dialog.js b/apps/system/test/unit/mock_modal_dialog.js new file mode 100644 index 0000000..beb452e --- /dev/null +++ b/apps/system/test/unit/mock_modal_dialog.js @@ -0,0 +1,34 @@ +var MockModalDialog = { + + mMethods: [ + 'alert' + ], + + mPopulate: function mmd_mPopulate() { + this.mMethods.forEach(function(methodName) { + this[methodName] = function mmd_method() { + this.mMethodCalled(methodName, Array.slice(arguments)); + }; + }, this); + }, + + init: function mmd_init() { + this.mMethods.forEach(function(methodName) { + this[methodName].mWasCalled = false; + this[methodName].mArgs = null; + }, this); + }, + + mMethodCalled: function mmd_mMethodCalled(name, args) { + this[name].mWasCalled = true; + this[name].mArgs = args; + }, + + mTeardown: function mmd_mTeardown() { + this.init(); + } +}; + +MockModalDialog.mPopulate(); + + diff --git a/apps/system/test/unit/mock_navigator_battery.js b/apps/system/test/unit/mock_navigator_battery.js new file mode 100644 index 0000000..5145de1 --- /dev/null +++ b/apps/system/test/unit/mock_navigator_battery.js @@ -0,0 +1,55 @@ +'use strict'; + +(function() { + + var props = ['level', 'charging']; + + var listeners; + + function mnb_init() { + props.forEach(function(prop) { + Mock[prop] = null; + }); + + listeners = {}; + } + + function mnb_addEventListener(evtName, func) { + listeners[evtName] = listeners[evtName] || []; + listeners[evtName].push(func); + } + + function mnb_removeEventListener(evtName, func) { + if (listeners[evtName]) { + var listenerArray = listeners[evtName]; + var index = listenerArray.indexOf(func); + if (index !== -1) { + listenerArray.splice(index, 1); + } + } + } + + function mnb_mTriggerEvent(evt) { + var evtName = evt.type; + if (listeners[evtName]) { + listeners[evtName].forEach(function(listener) { + if (listener.handleEvent) { + listener.handleEvent(evt); + } else { + listener.call(Mock, evt); + } + }); + } + } + + var Mock = { + addEventListener: mnb_addEventListener, + removeEventListener: mnb_removeEventListener, + mTeardown: mnb_init, + mTriggerEvent: mnb_mTriggerEvent + }; + + mnb_init(); + + window.MockNavigatorBattery = Mock; +})(); diff --git a/apps/system/test/unit/mock_navigator_moz_mobile_connection.js b/apps/system/test/unit/mock_navigator_moz_mobile_connection.js new file mode 100644 index 0000000..fb05aaa --- /dev/null +++ b/apps/system/test/unit/mock_navigator_moz_mobile_connection.js @@ -0,0 +1,21 @@ +'use strict'; + +(function() { + + var props = ['voice', 'cardState', 'iccInfo', 'data']; + + function mnmmc_init() { + props.forEach(function(prop) { + Mock[prop] = null; + }); + } + + var Mock = { + addEventListener: function() {}, + mTeardown: mnmmc_init + }; + + mnmmc_init(); + + window.MockNavigatorMozMobileConnection = Mock; +})(); diff --git a/apps/system/test/unit/mock_navigator_moz_telephony.js b/apps/system/test/unit/mock_navigator_moz_telephony.js new file mode 100644 index 0000000..21f461c --- /dev/null +++ b/apps/system/test/unit/mock_navigator_moz_telephony.js @@ -0,0 +1,55 @@ +'use strict'; + +(function() { + + var props = ['active', 'calls']; + + var listeners; + + function mnmt_init() { + props.forEach(function(prop) { + Mock[prop] = null; + }); + + listeners = {}; + } + + function mnmt_addEventListener(evtName, func) { + listeners[evtName] = listeners[evtName] || []; + listeners[evtName].push(func); + } + + function mnmt_removeEventListener(evtName, func) { + if (listeners[evtName]) { + var listenerArray = listeners[evtName]; + var index = listenerArray.indexOf(func); + if (index !== -1) { + listenerArray.splice(index, 1); + } + } + } + + function mnmt_mTriggerEvent(evt) { + var evtName = evt.type; + if (listeners[evtName]) { + listeners[evtName].forEach(function(listener) { + if (listener.handleEvent) { + listener.handleEvent(evt); + } else { + listener.call(Mock, evt); + } + }); + } + } + + var Mock= { + addEventListener: mnmt_addEventListener, + removeEventListener: mnmt_removeEventListener, + mTeardown: mnmt_init, + mTriggerEvent: mnmt_mTriggerEvent + }; + + mnmt_init(); + + window.MockNavigatorMozTelephony = Mock; +})(); diff --git a/apps/system/test/unit/mock_navigator_settings.js b/apps/system/test/unit/mock_navigator_settings.js new file mode 100644 index 0000000..8b0de8a --- /dev/null +++ b/apps/system/test/unit/mock_navigator_settings.js @@ -0,0 +1,64 @@ +(function(window) { + var observers = {}, + settings = {}, + removedObservers = {}; + + function mns_mLockSet(obj) { + for (var key in obj) { + settings[key] = obj[key]; + } + } + + function mns_addObserver(name, cb) { + observers[name] = observers[name] || []; + observers[name].push(cb); + } + + function mns_removeObserver(name, cb) { + removedObservers[name] = removedObservers[name] || []; + removedObservers[name].push(cb); + } + + function mns_createLock() { + return { + set: mns_mLockSet + }; + } + + function mns_mTriggerObservers(name, args) { + var theseObservers = observers[name]; + + if (! theseObservers) { + return; + } + + theseObservers.forEach(function(func) { + func(args); + }); + } + + function mns_teardown() { + observers = {}; + settings = {}; + removedObservers = {}; + } + + window.MockNavigatorSettings = { + addObserver: mns_addObserver, + removeObserver: mns_removeObserver, + createLock: mns_createLock, + + mTriggerObservers: mns_mTriggerObservers, + mTeardown: mns_teardown, + get mObservers() { + return observers; + }, + get mSettings() { + return settings; + }, + get mRemovedObservers() { + return removedObservers; + } + }; + +})(this); diff --git a/apps/system/test/unit/mock_navigator_wake_lock.js b/apps/system/test/unit/mock_navigator_wake_lock.js new file mode 100644 index 0000000..c9ff845 --- /dev/null +++ b/apps/system/test/unit/mock_navigator_wake_lock.js @@ -0,0 +1,41 @@ +'use strict'; + +(function() { + var lastWakeLock, + throwAtNextUnlock; + + function mnwl_requestWakeLock(lock) { + lastWakeLock = { + released: false, + topic: lock, + unlock: function() { + if (throwAtNextUnlock) { + throwAtNextUnlock = false; + throw "NS_ERROR_DOM_INVALID_STATE_ERR"; + } + + this.released = true; + } + }; + return lastWakeLock; + } + + function mnwl_teardown() { + lastWakeLock = undefined; + throwAtNextUnlock = undefined; + } + + function mnwl_throwAtNextUnlock() { + throwAtNextUnlock = true; + } + + window.MockNavigatorWakeLock = { + requestWakeLock: mnwl_requestWakeLock, + mTeardown: mnwl_teardown, + mThrowAtNextUnlock: mnwl_throwAtNextUnlock, + get mLastWakeLock() { + return lastWakeLock; + } + }; + +})(); diff --git a/apps/system/test/unit/mock_notification_helper.js b/apps/system/test/unit/mock_notification_helper.js new file mode 100644 index 0000000..adb80f9 --- /dev/null +++ b/apps/system/test/unit/mock_notification_helper.js @@ -0,0 +1,22 @@ +var MockNotificationHelper = { + send: function(title, body, icon, clickCB, closeCB) { + this.mTitle = title; + this.mBody = body; + this.mIcon = icon; + this.mClickCB = clickCB; + this.mCloseCB = closeCB; + }, + + mTitle: null, + mBody: null, + mIcon: null, + mClickCB: null, + mCloseCB: null, + mTeardown: function teardown() { + this.mTitle = null; + this.mBody = null; + this.mIcon = null; + this.mClickCB = null; + this.mCloseCB = null; + } +}; diff --git a/apps/system/test/unit/mock_notification_screen.js b/apps/system/test/unit/mock_notification_screen.js new file mode 100644 index 0000000..2c88a90 --- /dev/null +++ b/apps/system/test/unit/mock_notification_screen.js @@ -0,0 +1,38 @@ +var MockNotificationScreen = { + wasMethodCalled: {}, + + mockMethods: [ + 'incExternalNotifications', + 'decExternalNotifications', + 'updateStatusBarIcon' + ], + + mockPopulate: function mockPopulate() { + this.mockMethods.forEach(function(methodName) { + // we could probably put this method outside if we had a closure + this[methodName] = function mns_method() { + this.methodCalled(methodName); + }; + }, this); + }, + + init: function mns_init() { + this.wasMethodCalled = {}; + this.mockMethods.forEach(function(methodName) { + this[methodName].wasCalled = false; + }, this); + }, + + methodCalled: function mns_methodCalled(name) { + this.wasMethodCalled[name] = + this.wasMethodCalled[name] ? this.wasMethodCalled[name]++ : 1; + this[name].wasCalled = true; + }, + + mTeardown: function mns_mTeardown() { + this.init(); + } +}; + +MockNotificationScreen.mockPopulate(); + diff --git a/apps/system/test/unit/mock_settings_listener.js b/apps/system/test/unit/mock_settings_listener.js new file mode 100644 index 0000000..6a4da7f --- /dev/null +++ b/apps/system/test/unit/mock_settings_listener.js @@ -0,0 +1,16 @@ +var MockSettingsListener = { + observe: function msl_observe(name, defaultValue, cb) { + this.mName = name; + this.mDefaultValue = defaultValue; + this.mCallback = cb; + }, + + mName: null, + mDefaultValue: null, + mCallback: null, + mTeardown: function teardown() { + this.mName = null; + this.mDefaultValue = null; + this.mDefaultCallback = null; + } +}; diff --git a/apps/system/test/unit/mock_sleep_menu.js b/apps/system/test/unit/mock_sleep_menu.js new file mode 100644 index 0000000..1b883dc --- /dev/null +++ b/apps/system/test/unit/mock_sleep_menu.js @@ -0,0 +1,5 @@ +'use strict'; + +var MockSleepMenu = { + startPowerOff: function(){} +}; diff --git a/apps/system/test/unit/mock_statusbar.js b/apps/system/test/unit/mock_statusbar.js new file mode 100644 index 0000000..8813a3b --- /dev/null +++ b/apps/system/test/unit/mock_statusbar.js @@ -0,0 +1,36 @@ +var MockStatusBar = { + notificationsCount: null, + + wasMethodCalled: {}, + + methodCalled: function msb_methodCalled(name) { + this.wasMethodCalled[name] = + this.wasMethodCalled[name] ? this.wasMethodCalled[name]++ : 1; + }, + + updateNotification: function(count) { + var number = new Number(count); + this.notificationsCount = number.toString(); + this.methodCalled('updateNotification'); + }, + + updateNotificationUnread: function(unread) { + this.mNotificationUnread = unread; + }, + + mNotificationUnread: false, + mTeardown: function teardown() { + this.notificationsCount = null; + this.mNotificationsUpdated = false; + this.mNotificationUnread = false; + this.wasMethodCalled = {}; + }, + + incSystemDownloads: function msb_incSystemDownloads() { + this.methodCalled('incSystemDownloads'); + }, + + decSystemDownloads: function msb_decSystemDownloads() { + this.methodCalled('decSystemDownloads'); + } +}; diff --git a/apps/system/test/unit/mock_system_banner.js b/apps/system/test/unit/mock_system_banner.js new file mode 100644 index 0000000..e40d26a --- /dev/null +++ b/apps/system/test/unit/mock_system_banner.js @@ -0,0 +1,13 @@ +var MockSystemBanner = { + show: function(message) { + this.mShowCount++; + this.mMessage = message; + }, + + mShowCount: 0, + mMessage: null, + mTeardown: function teardown() { + this.mShowCount = 0; + this.mMessage = null; + } +}; diff --git a/apps/system/test/unit/mock_trusted_ui_manager.js b/apps/system/test/unit/mock_trusted_ui_manager.js new file mode 100644 index 0000000..672ed47 --- /dev/null +++ b/apps/system/test/unit/mock_trusted_ui_manager.js @@ -0,0 +1,25 @@ +'use strict'; + +var MockTrustedUIManager = { + open: function(name, frame, chromeEventId) { + this.mOpened = true; + this.mName = name; + this.mFrame = frame; + this.mChromeEventId = chromeEventId; + }, + + close: function() { + this.mOpened = false; + }, + + mOpened: false, + mName: null, + mFrame: null, + mChromeEventId: null, + mTeardown: function teardown() { + this.mOpened = false; + this.mName = null; + this.mFrame = null; + this.mChromeEventId = null; + } +}; diff --git a/apps/system/test/unit/mock_updatable.js b/apps/system/test/unit/mock_updatable.js new file mode 100644 index 0000000..33f5fb8 --- /dev/null +++ b/apps/system/test/unit/mock_updatable.js @@ -0,0 +1,72 @@ +'use strict'; + +function MockAppUpdatable(aApp) { + this.app = aApp; + + this.mDownloadCalled = false; + this.mCancelCalled = false; + this.mUninitCalled = false; + MockAppUpdatable.mCount++; +} + +MockAppUpdatable.mTeardown = function() { + MockAppUpdatable.mCount = 0; +}; + +MockAppUpdatable.mCount = 0; + +MockAppUpdatable.prototype.uninit = function() { + this.mUninitCalled = true; +}; + +MockAppUpdatable.prototype.download = function() { + this.mDownloadCalled = true; +}; + +MockAppUpdatable.prototype.cancelDownload = function() { + this.mCancelCalled = true; +}; + +function MockSystemUpdatable() { + this.size = null; + this.name = 'systemUpdate'; + + this.mDownloadCalled = false; + this.mCancelCalled = false; + this.mUninitCalled = false; + + MockSystemUpdatable.mInstancesCount++; +} + +MockSystemUpdatable.mInstancesCount = 0; +MockSystemUpdatable.mTeardown = function() { + MockSystemUpdatable.mInstancesCount = 0; + delete MockSystemUpdatable.mKnownUpdate; +}; + + +MockSystemUpdatable.prototype.uninit = function() { + this.mUninitCalled = true; +}; + +MockSystemUpdatable.prototype.download = function() { + this.mDownloadCalled = true; +}; + +MockSystemUpdatable.prototype.cancelDownload = function() { + this.mCancelCalled = true; +}; + +MockSystemUpdatable.prototype.rememberKnownUpdate = function() { + this.mKnownUpdate = true; +}; + +MockSystemUpdatable.prototype.forgetKnownUpdate = function() { + delete this.mKnownUpdate; +}; + +MockSystemUpdatable.prototype.checkKnownUpdate = function(callback) { + if (this.mKnownUpdate && typeof callback === 'function') { + callback(); + } +}; diff --git a/apps/system/test/unit/mock_update_manager.js b/apps/system/test/unit/mock_update_manager.js new file mode 100644 index 0000000..86cb4b9 --- /dev/null +++ b/apps/system/test/unit/mock_update_manager.js @@ -0,0 +1,60 @@ +'use strict'; + +var MockUpdateManager = { + addToUpdatesQueue: function mum_addtoUpdateQueue(updatable) { + this.mLastUpdatesAdd = updatable; + }, + addToUpdatableApps: function mum_addToUpdatableApps(updatable) { + this.mLastUpdatableAdd = updatable; + }, + + removeFromUpdatesQueue: function mum_removeFromUpdateQueue(updatable) { + this.mLastUpdatesRemoval = updatable; + }, + + addToDownloadsQueue: function mum_addtoActiveDownloads(updatable) { + this.mLastDownloadsAdd = updatable; + }, + removeFromDownloadsQueue: + function mum_removeFromActiveDownloads(updatable) { + + this.mLastDownloadsRemoval = updatable; + }, + + downloadProgressed: function mum_downloadProgressed(bytes) { + this.mProgressCalledWith = bytes; + }, + + startedUncompressing: function mum_startedUncompressing() { + this.mStartedUncompressingCalled = true; + }, + + requestErrorBanner: function mum_requestErrorBanner() { + this.mErrorBannerRequested = true; + }, + + checkForUpdates: function mum_checkForUpdate(forced) { + this.mCheckForUpdatesCalledWith = forced; + }, + + mErrorBannerRequested: false, + mLastUpdatesAdd: null, + mLastUpdatableAdd: null, + mLastUpdatesRemoval: null, + mLastDownloadsAdd: null, + mLastDownloadsRemoval: null, + mProgressCalledWith: null, + mCheckForUpdatesCalledWith: null, + mStartedUncompressingCalled: false, + mTeardown: function mum_mTeardown() { + this.mErrorBannerRequested = false; + this.mLastUpdatesAdd = null; + this.mLastUpdatableAdd = null; + this.mLastUpdatesRemoval = null; + this.mLastDownloadsAdd = null; + this.mLastDownloadsRemoval = null; + this.mProgressCalledWith = null; + this.mCheckForUpdatesCalledWith = null; + this.mStartedUncompressingCalled = false; + } +}; diff --git a/apps/system/test/unit/mock_utility_tray.js b/apps/system/test/unit/mock_utility_tray.js new file mode 100644 index 0000000..8bf8a8b --- /dev/null +++ b/apps/system/test/unit/mock_utility_tray.js @@ -0,0 +1,14 @@ +var MockUtilityTray = { + show: function() { + this.mShown = true; + }, + + hide: function() { + this.mShown = false; + }, + + mShown: false, + mTeardown: function teardown() { + this.mShown = false; + } +}; diff --git a/apps/system/test/unit/mock_window_manager.js b/apps/system/test/unit/mock_window_manager.js new file mode 100644 index 0000000..331e254 --- /dev/null +++ b/apps/system/test/unit/mock_window_manager.js @@ -0,0 +1,16 @@ +var MockWindowManager = { + getDisplayedApp: function mwm_getDisplayedApp() { + return this.mDisplayedApp; + }, + + kill: function mwm_kill(origin) { + this.mLastKilledOrigin = origin; + }, + + mDisplayedApp: '', + mLastKilledOrigin: '', + mTeardown: function() { + this.mDisplayedApp = ''; + this.mLastKilledOrigin = ''; + } +}; diff --git a/apps/system/test/unit/mocks_helper.js b/apps/system/test/unit/mocks_helper.js new file mode 100644 index 0000000..40c8689 --- /dev/null +++ b/apps/system/test/unit/mocks_helper.js @@ -0,0 +1,40 @@ +var MocksHelper = function(mocks) { + this.mocks = mocks; + this.realWindowObjects = {}; +}; + +MocksHelper.prototype = { + + setup: function mh_setup() { + }, + + suiteSetup: function mh_suiteSetup() { + this.mocks.forEach(function(objName) { + var mockName = 'Mock' + objName; + if (!window[mockName]) { + throw 'Mock ' + mockName + ' has not been loaded into the test'; + } + + this.realWindowObjects[objName] = window[objName]; + window[objName] = window[mockName]; + }, this); + }, + + suiteTeardown: function mh_suiteTeardown() { + this.mocks.forEach(function(objName) { + window[objName] = this.realWindowObjects[objName]; + }, this); + }, + + teardown: function mh_teardown() { + this.mocks.forEach(function(objName) { + var mockName = 'Mock' + objName; + var mock = window[mockName]; + + if (mock.mTeardown) { + mock.mTeardown(); + } + }); + } +}; + diff --git a/apps/system/test/unit/notifications_test.js b/apps/system/test/unit/notifications_test.js new file mode 100644 index 0000000..e323f07 --- /dev/null +++ b/apps/system/test/unit/notifications_test.js @@ -0,0 +1,132 @@ +'use strict'; + +requireApp('system/test/unit/mock_statusbar.js'); +requireApp('system/test/unit/mock_gesture_detector.js'); +requireApp('system/test/unit/mock_settings_listener.js'); +requireApp('system/test/unit/mocks_helper.js'); + +requireApp('system/js/notifications.js'); + +var mocksForNotificationScreen = ['StatusBar', 'GestureDetector', + 'SettingsListener']; + +mocksForNotificationScreen.forEach(function(mockName) { + if (! window[mockName]) { + window[mockName] = null; + } +}); + + +suite('system/NotificationScreen >', function() { + var fakeNotifContainer, fakeLockScreenContainer, fakeToaster, + fakeButton, fakeToasterIcon, fakeToasterTitle, fakeToasterDetail; + + var mocksHelper; + + suiteSetup(function() { + mocksHelper = new MocksHelper(mocksForNotificationScreen); + mocksHelper.suiteSetup(); + }); + + suiteTeardown(function() { + mocksHelper.suiteTeardown(); + }); + + setup(function() { + fakeNotifContainer = document.createElement('div'); + fakeNotifContainer.id = 'desktop-notifications-container'; + // add some children, we don't care what they are + fakeNotifContainer.appendChild(document.createElement('div')); + fakeNotifContainer.appendChild(document.createElement('div')); + + function createFakeElement(tag, id) { + var obj = document.createElement(tag); + obj.id = id; + return obj; + }; + + fakeLockScreenContainer = createFakeElement('div', + 'notifications-lockscreen-container'); + fakeToaster = createFakeElement('div', 'notification-toaster'); + fakeButton = createFakeElement('button', 'notification-clear'); + fakeToasterIcon = createFakeElement('img', 'toaster-icon'); + fakeToasterTitle = createFakeElement('div', 'toaster-title'); + fakeToasterDetail = createFakeElement('div', 'toaster-detail'); + + document.body.appendChild(fakeNotifContainer); + + document.body.appendChild(fakeLockScreenContainer); + document.body.appendChild(fakeToaster); + document.body.appendChild(fakeButton); + document.body.appendChild(fakeToasterIcon); + document.body.appendChild(fakeToasterTitle); + document.body.appendChild(fakeToasterDetail); + + mocksHelper.setup(); + + NotificationScreen.init(); + }); + + teardown(function() { + fakeNotifContainer.parentNode.removeChild(fakeNotifContainer); + fakeLockScreenContainer.parentNode.removeChild(fakeLockScreenContainer); + fakeToaster.parentNode.removeChild(fakeToaster); + fakeButton.parentNode.removeChild(fakeButton); + + mocksHelper.teardown(); + }); + + suite('updateStatusBarIcon >', function() { + setup(function() { + NotificationScreen.updateStatusBarIcon(); + }); + + test('should update the icon in the status bar', function() { + assert.ok(MockStatusBar.wasMethodCalled['updateNotification']); + assert.equal(2, MockStatusBar.notificationsCount); + }); + + test('external notif should not be able to decrease the global count', + function() { + + NotificationScreen.decExternalNotifications(); + assert.equal(2, MockStatusBar.notificationsCount); + }); + + test('external notif should increase the global count', + function() { + + NotificationScreen.incExternalNotifications(); + assert.isTrue(MockStatusBar.mNotificationUnread); + assert.equal(3, MockStatusBar.notificationsCount); + }); + + test('external notif should decrease the global count', + function() { + + NotificationScreen.incExternalNotifications(); + MockStatusBar.mNotificationUnread = false; + NotificationScreen.decExternalNotifications(); + assert.isFalse(MockStatusBar.mNotificationUnread); + assert.equal(2, MockStatusBar.notificationsCount); + }); + + test('should change the read status', function() { + NotificationScreen.updateStatusBarIcon(true); + assert.isTrue(MockStatusBar.mNotificationUnread); + }); + + test('calling addNotification without icon', function() { + var toasterIcon = NotificationScreen.toasterIcon; + var imgpath = 'http://example.com/test.png'; + var detail = {icon: imgpath, title: 'title', detail: 'detail'}; + NotificationScreen.addNotification(detail); + assert.equal(imgpath, toasterIcon.src); + assert.isFalse(toasterIcon.hidden); + delete detail.icon; + NotificationScreen.addNotification(detail); + assert.isTrue(toasterIcon.hidden); + }); + }); + +}); diff --git a/apps/system/test/unit/statusbar_test.js b/apps/system/test/unit/statusbar_test.js new file mode 100644 index 0000000..fba5c54 --- /dev/null +++ b/apps/system/test/unit/statusbar_test.js @@ -0,0 +1,421 @@ +'use strict'; + +requireApp('system/test/unit/mock_settings_listener.js'); +requireApp('system/test/unit/mock_l10n.js'); +requireApp('system/test/unit/mock_navigator_moz_mobile_connection.js'); +requireApp('system/test/unit/mock_navigator_moz_telephony.js'); +requireApp('system/test/unit/mock_mobile_operator.js'); +requireApp('system/test/unit/mocks_helper.js'); + +requireApp('system/js/statusbar.js'); + +var mocksForStatusBar = ['SettingsListener', 'MobileOperator']; + +mocksForStatusBar.forEach(function(mockName) { + if (! window[mockName]) { + window[mockName] = null; + } +}); + +suite('system/Statusbar', function() { + var fakeStatusBarNode; + var mocksHelper; + + var realSettingsListener, realMozL10n, realMozMobileConnection, + realMozTelephony, + fakeIcons = []; + + suiteSetup(function() { + mocksHelper = new MocksHelper(mocksForStatusBar); + mocksHelper.suiteSetup(); + realMozL10n = navigator.mozL10n; + navigator.mozL10n = MockL10n; + realMozMobileConnection = navigator.mozMobileConnection; + navigator.mozMobileConnection = MockNavigatorMozMobileConnection; + realMozTelephony = navigator.mozTelephony; + navigator.mozTelephony = MockNavigatorMozTelephony; + }); + + suiteTeardown(function() { + mocksHelper.suiteTeardown(); + navigator.mozL10n = realMozL10n; + navigator.mozMobileConnection = realMozMobileConnection; + navigator.mozTelephony = realMozTelephony; + window.SettingsListener = realSettingsListener; + }); + + setup(function() { + mocksHelper.setup(); + fakeStatusBarNode = document.createElement('div'); + fakeStatusBarNode.id = 'statusbar'; + document.body.appendChild(fakeStatusBarNode); + + StatusBar.ELEMENTS.forEach(function testAddElement(elementName) { + var elt = document.createElement('div'); + elt.id = 'statusbar-' + elementName; + elt.hidden = true; + fakeStatusBarNode.appendChild(elt); + fakeIcons[elementName] = elt; + }); + + // executing init again + StatusBar.init(); + }); + teardown(function() { + mocksHelper.teardown(); + fakeStatusBarNode.parentNode.removeChild(fakeStatusBarNode); + MockNavigatorMozTelephony.mTeardown(); + MockNavigatorMozMobileConnection.mTeardown(); + }); + + suite('system-downloads', function() { + test('incrementing should display the icon', function() { + StatusBar.incSystemDownloads(); + assert.isFalse(fakeIcons['system-downloads'].hidden); + }); + test('incrementing then decrementing should not display the icon', + function() { + StatusBar.incSystemDownloads(); + StatusBar.decSystemDownloads(); + assert.isTrue(fakeIcons['system-downloads'].hidden); + }); + test('incrementing twice then decrementing once should display the icon', + function() { + StatusBar.incSystemDownloads(); + StatusBar.incSystemDownloads(); + StatusBar.decSystemDownloads(); + assert.isFalse(fakeIcons['system-downloads'].hidden); + }); + test('incrementing then decrementing twice should not display the icon', + function() { + StatusBar.incSystemDownloads(); + StatusBar.decSystemDownloads(); + StatusBar.decSystemDownloads(); + assert.isTrue(fakeIcons['system-downloads'].hidden); + }); + + /* JW: testing that we can't have a negative counter */ + test('incrementing then decrementing twice then incrementing should ' + + 'display the icon', function() { + StatusBar.incSystemDownloads(); + StatusBar.decSystemDownloads(); + StatusBar.decSystemDownloads(); + StatusBar.incSystemDownloads(); + assert.isFalse(fakeIcons['system-downloads'].hidden); + }); + }); + + suite('signal icon', function() { + var dataset; + setup(function() { + dataset = fakeIcons.signal.dataset; + }); + + test('no network without sim, not searching', function() { + MockNavigatorMozMobileConnection.voice = { + connected: false, + relSignalStrength: null, + emergencyCallsOnly: false, + state: 'notSearching', + roaming: false, + network: {} + }; + + MockNavigatorMozMobileConnection.cardState = 'absent'; + MockNavigatorMozMobileConnection.iccInfo = {}; + + StatusBar.update.signal.call(StatusBar); + + assert.notEqual(dataset.roaming, 'true'); + assert.notEqual(dataset.emergency, 'true'); + assert.isUndefined(dataset.level); + assert.notEqual(dataset.searching, 'true'); + }); + + test('no network without sim, searching', function() { + MockNavigatorMozMobileConnection.voice = { + connected: false, + relSignalStrength: null, + emergencyCallsOnly: false, + state: 'searching', + roaming: false, + network: {} + }; + + MockNavigatorMozMobileConnection.cardState = 'absent'; + MockNavigatorMozMobileConnection.iccInfo = {}; + + StatusBar.update.signal.call(StatusBar); + + assert.notEqual(dataset.roaming, 'true'); + assert.notEqual(dataset.emergency, 'true'); + assert.isUndefined(dataset.level); + assert.notEqual(dataset.searching, 'true'); + }); + + test('no network with sim', function() { + MockNavigatorMozMobileConnection.voice = { + connected: false, + relSignalStrength: null, + emergencyCallsOnly: false, + state: 'notSearching', + roaming: false, + network: {} + }; + + MockNavigatorMozMobileConnection.cardState = 'pinRequired'; + MockNavigatorMozMobileConnection.iccInfo = {}; + + StatusBar.update.signal.call(StatusBar); + + assert.notEqual(dataset.roaming, 'true'); + assert.notEqual(dataset.emergency, 'true'); + assert.equal(dataset.level, -1); + assert.notEqual(dataset.searching, 'true'); + }); + + test('searching', function() { + MockNavigatorMozMobileConnection.voice = { + connected: false, + relSignalStrength: null, + emergencyCallsOnly: false, + state: 'searching', + roaming: false, + network: {} + }; + + MockNavigatorMozMobileConnection.cardState = 'ready'; + MockNavigatorMozMobileConnection.iccInfo = {}; + + StatusBar.update.signal.call(StatusBar); + + assert.notEqual(dataset.roaming, 'true'); + assert.notEqual(dataset.emergency, 'true'); + assert.equal(dataset.level, -1); + assert.equal(dataset.searching, 'true'); + }); + + test('emergency calls only, no sim', function() { + MockNavigatorMozMobileConnection.voice = { + connected: false, + relSignalStrength: 80, + emergencyCallsOnly: true, + state: 'notSearching', + roaming: false, + network: {} + }; + + MockNavigatorMozMobileConnection.cardState = 'absent'; + MockNavigatorMozMobileConnection.iccInfo = {}; + + StatusBar.update.signal.call(StatusBar); + + assert.notEqual(dataset.roaming, 'true'); + assert.notEqual(dataset.emergency, 'true'); + assert.isUndefined(dataset.level); + assert.notEqual(dataset.searching, 'true'); + }); + + test('emergency calls only, with sim', function() { + MockNavigatorMozMobileConnection.voice = { + connected: false, + relSignalStrength: 80, + emergencyCallsOnly: true, + state: 'notSearching', + roaming: false, + network: {} + }; + + MockNavigatorMozMobileConnection.cardState = 'pinRequired'; + MockNavigatorMozMobileConnection.iccInfo = {}; + + StatusBar.update.signal.call(StatusBar); + + assert.notEqual(dataset.roaming, 'true'); + assert.equal(dataset.emergency, 'true'); + assert.equal(dataset.level, '-1'); + assert.notEqual(dataset.searching, 'true'); + }); + + test('emergency calls only, in call', function() { + MockNavigatorMozMobileConnection.voice = { + connected: false, + relSignalStrength: 80, + emergencyCallsOnly: true, + state: 'notSearching', + roaming: false, + network: {} + }; + + MockNavigatorMozMobileConnection.cardState = 'pinRequired'; + MockNavigatorMozMobileConnection.iccInfo = {}; + + MockNavigatorMozTelephony.active = { + state: 'connected' + }; + + StatusBar.update.signal.call(StatusBar); + + assert.notEqual(dataset.roaming, 'true'); + assert.equal(dataset.level, 4); + assert.notEqual(dataset.emergency, 'true'); + assert.notEqual(dataset.searching, 'true'); + }); + + test('emergency calls only, dialing', function() { + MockNavigatorMozMobileConnection.voice = { + connected: false, + relSignalStrength: 80, + emergencyCallsOnly: true, + state: 'notSearching', + roaming: false, + network: {} + }; + + MockNavigatorMozMobileConnection.cardState = 'pinRequired'; + MockNavigatorMozMobileConnection.iccInfo = {}; + + MockNavigatorMozTelephony.active = { + state: 'dialing' + }; + + StatusBar.update.signal.call(StatusBar); + + assert.notEqual(dataset.roaming, 'true'); + assert.equal(dataset.level, 4); + assert.notEqual(dataset.emergency, 'true'); + assert.notEqual(dataset.searching, 'true'); + }); + + test('emergency calls, passing a call', function() { + MockNavigatorMozMobileConnection.voice = { + connected: false, + relSignalStrength: 80, + emergencyCallsOnly: true, + state: 'notSearching', + roaming: false, + network: {} + }; + + MockNavigatorMozMobileConnection.cardState = 'pinRequired'; + MockNavigatorMozMobileConnection.iccInfo = {}; + + StatusBar.update.signal.call(StatusBar); + + var activeCall = { + state: 'dialing' + }; + + MockNavigatorMozTelephony.active = activeCall; + MockNavigatorMozTelephony.calls = [activeCall]; + + var evt = new CustomEvent('callschanged'); + MockNavigatorMozTelephony.mTriggerEvent(evt); + + assert.notEqual(dataset.roaming, 'true'); + assert.equal(dataset.level, 4); + assert.notEqual(dataset.emergency, 'true'); + assert.notEqual(dataset.searching, 'true'); + }); + + test('normal carrier', function() { + MockNavigatorMozMobileConnection.voice = { + connected: true, + relSignalStrength: 80, + emergencyCallsOnly: false, + state: 'notSearching', + roaming: false, + network: {} + }; + + MockNavigatorMozMobileConnection.cardState = 'ready'; + MockNavigatorMozMobileConnection.iccInfo = {}; + + StatusBar.update.signal.call(StatusBar); + + assert.notEqual(dataset.roaming, 'true'); + assert.equal(dataset.level, 4); + assert.notEqual(dataset.emergency, 'true'); + assert.notEqual(dataset.searching, 'true'); + }); + + test('roaming', function() { + MockNavigatorMozMobileConnection.voice = { + connected: true, + relSignalStrength: 80, + emergencyCallsOnly: false, + state: 'notSearching', + roaming: true, + network: {} + }; + + MockNavigatorMozMobileConnection.cardState = 'ready'; + MockNavigatorMozMobileConnection.iccInfo = {}; + + StatusBar.update.signal.call(StatusBar); + + assert.equal(dataset.roaming, 'true'); + assert.equal(dataset.level, 4); + assert.notEqual(dataset.emergency, 'true'); + assert.notEqual(dataset.searching, 'true'); + }); + + test('emergency calls, roaming', function() { + MockNavigatorMozMobileConnection.voice = { + connected: false, + relSignalStrength: 80, + emergencyCallsOnly: true, + state: 'notSearching', + roaming: true, + network: {} + }; + + MockNavigatorMozMobileConnection.cardState = 'ready'; + MockNavigatorMozMobileConnection.iccInfo = {}; + + StatusBar.update.signal.call(StatusBar); + + assert.notEqual(dataset.roaming, 'true'); + assert.equal(dataset.level, -1); + assert.equal(dataset.emergency, 'true'); + assert.notEqual(dataset.searching, 'true'); + }); + }), + + suite('operator name', function() { + setup(function() { + MockNavigatorMozMobileConnection.voice = { + connected: true, + network: { + shortName: 'Fake short', + longName: 'Fake long', + mnc: 10 // VIVO + }, + cell: { + gsmLocationAreaCode: 71 // BA + } + } + + MockNavigatorMozMobileConnection.iccInfo = { + isDisplaySpnRequired: false, + spn: 'Fake SPN' + } + }); + + test('Connection without region', function() { + MobileOperator.mOperator = 'Orange'; + var evt = new CustomEvent('iccinfochange'); + StatusBar.handleEvent(evt); + assert.include(fakeIcons.label.textContent, 'Orange'); + }); + test('Connection with region', function() { + MobileOperator.mOperator = 'Orange'; + MobileOperator.mRegion = 'PR'; + var evt = new CustomEvent('iccinfochange'); + StatusBar.handleEvent(evt); + var label_content = fakeIcons.label.textContent; + assert.include(label_content, 'Orange'); + assert.include(label_content, 'PR'); + }); + }); +}); diff --git a/apps/system/test/unit/style/lockscreen/images/mask.png b/apps/system/test/unit/style/lockscreen/images/mask.png new file mode 100644 index 0000000..e1b8cf5 --- /dev/null +++ b/apps/system/test/unit/style/lockscreen/images/mask.png Binary files differ diff --git a/apps/system/test/unit/updatable_test.js b/apps/system/test/unit/updatable_test.js new file mode 100644 index 0000000..7af2abf --- /dev/null +++ b/apps/system/test/unit/updatable_test.js @@ -0,0 +1,630 @@ +'use strict'; + +requireApp('system/js/updatable.js'); + +requireApp('system/test/unit/mock_app.js'); +requireApp('system/test/unit/mock_asyncStorage.js'); +requireApp('system/test/unit/mock_update_manager.js'); +requireApp('system/test/unit/mock_window_manager.js'); +requireApp('system/test/unit/mock_apps_mgmt.js'); +requireApp('system/test/unit/mock_chrome_event.js'); +requireApp('system/test/unit/mock_custom_dialog.js'); +requireApp('system/test/unit/mock_utility_tray.js'); +requireApp('system/test/unit/mock_manifest_helper.js'); +requireApp('system/test/unit/mocks_helper.js'); + + +var mocksForUpdatable = [ + 'CustomDialog', + 'UpdateManager', + 'WindowManager', + 'UtilityTray', + 'ManifestHelper', + 'asyncStorage' +]; + +mocksForUpdatable.forEach(function(mockName) { + if (!window[mockName]) { + window[mockName] = null; + } +}); + +suite('system/Updatable', function() { + var subject; + var mockApp; + + var realDispatchEvent; + var realL10n; + + var mocksHelper; + + var lastDispatchedEvent = null; + var fakeDispatchEvent; + + suiteSetup(function() { + realL10n = navigator.mozL10n; + navigator.mozL10n = { + get: function get(key) { + return key; + } + }; + + mocksHelper = new MocksHelper(mocksForUpdatable); + mocksHelper.suiteSetup(); + }); + + suiteTeardown(function() { + navigator.mozL10n = realL10n; + mocksHelper.suiteTeardown(); + }); + + setup(function() { + mockApp = new MockApp(); + subject = new AppUpdatable(mockApp); + subject._mgmt = MockAppsMgmt; + + fakeDispatchEvent = function(type, value) { + lastDispatchedEvent = { + type: type, + value: value + }; + }; + subject._dispatchEvent = fakeDispatchEvent; + + mocksHelper.setup(); + }); + + teardown(function() { + MockAppsMgmt.mTeardown(); + mocksHelper.teardown(); + + subject._dispatchEvent = realDispatchEvent; + lastDispatchedEvent = null; + }); + + function downloadAvailableSuite(name, setupFunc) { + suite(name, function() { + setup(setupFunc); + + test('should add self to the available downloads', function() { + assert.isNotNull(MockUpdateManager.mLastUpdatesAdd); + assert.equal(MockUpdateManager.mLastUpdatesAdd.app.mId, + mockApp.mId); + }); + + suite('first progress', function() { + setup(function() { + mockApp.mTriggerDownloadProgress(42); + }); + + test('should add self to active downloads', function() { + assert.isNotNull(MockUpdateManager.mLastDownloadsAdd); + assert.equal(MockUpdateManager.mLastDownloadsAdd.app.mId, + mockApp.mId); + }); + + test('should start with first progress value', function() { + assert.equal(42, subject.progress); + }); + }); + }); + } + + suite('init', function() { + test('should keep a reference to the app', function() { + assert.equal(mockApp, subject.app); + }); + + test('should handle fresh app with just an updateManifest', function() { + var freshApp = new MockApp(); + freshApp.manifest = undefined; + subject = new AppUpdatable(freshApp); + assert.equal(freshApp, subject.app); + }); + + test('should add itself to updatable apps', function() { + assert.equal(MockUpdateManager.mLastUpdatableAdd, subject); + }); + + test('should remember about the update on startup', function() { + asyncStorage.mItems[SystemUpdatable.KNOWN_UPDATE_FLAG] = true; + var systemUpdatable = new SystemUpdatable(); + assert.equal(MockUpdateManager.mCheckForUpdatesCalledWith, true); + }); + + downloadAvailableSuite('app has a download available', function() { + mockApp.downloadAvailable = true; + subject = new AppUpdatable(mockApp); + }); + + test('should apply update if downloaded', function() { + mockApp.readyToApplyDownload = true; + subject = new AppUpdatable(mockApp); + // We cannot test for this._mgmt methods because it's created in + // a constructor, so we check if the window is killed because + // WindowManager.kill() is also called in applyUpdate() method + assert.equal(MockWindowManager.mLastKilledOrigin, subject.app.origin); + }); + }); + + suite('infos', function() { + suite('name', function() { + test('should give a name for system updates', function() { + subject = new SystemUpdatable(42); + assert.equal('systemUpdate', subject.name); + }); + + test('should give a name for app updates', function() { + assert.equal('Mock app', subject.name); + }); + }); + + suite('size', function() { + test('should give packaged app update size', function() { + assert.equal(null, subject.size); + }); + + test('should return null for hosted apps', function() { + mockApp.updateManifest = null; + subject = new AppUpdatable(mockApp); + assert.isNull(subject.size); + }); + + test('should update size on download available', function() { + mockApp.updateManifest = null; + subject = new AppUpdatable(mockApp); + assert.isNull(subject.size); + + mockApp.mTriggerDownloadAvailable(45678); + assert.equal(45678, subject.size); + }); + }); + }); + + suite('actions', function() { + suite('ask for download', function() { + setup(function() { + mockApp.mTriggerDownloadAvailable(); + subject.download(); + }); + + test('should call download on the app', function() { + assert.isTrue(mockApp.mDownloadCalled); + }); + }); + + suite('download system update', function() { + setup(function() { + subject = new SystemUpdatable(42); + subject._dispatchEvent = fakeDispatchEvent; + subject.progress = 42; + subject.download(); + }); + + test('should send download message for system updates', function() { + assert.equal('update-available-result', lastDispatchedEvent.type); + assert.equal('download', lastDispatchedEvent.value); + }); + + test('should add system updates to active downloads too', function() { + assert.isNotNull(MockUpdateManager.mLastDownloadsAdd); + assert.equal(subject, MockUpdateManager.mLastDownloadsAdd); + }); + + test('should start system updates with progress 0 too', function() { + assert.equal(subject.progress, 0); + }); + + test('should do nothing if already downloading', function() { + lastDispatchedEvent = null; + subject.progress = 42; + subject.download(); + + assert.equal(subject.progress, 42); + assert.isNull(lastDispatchedEvent); + }); + }); + + suite('cancel app update download', function() { + setup(function() { + subject.cancelDownload(); + }); + + test('should call cancelDownload on the app', function() { + assert.isTrue(mockApp.mCancelCalled); + }); + }); + + suite('cancel system update download', function() { + setup(function() { + asyncStorage.setItem(SystemUpdatable.KNOWN_UPDATE_FLAG, true); + subject = new SystemUpdatable(42); + subject.download(); + subject._dispatchEvent = fakeDispatchEvent; + subject.cancelDownload(); + }); + + test('should send cancel message', function() { + assert.equal('update-download-cancel', lastDispatchedEvent.type); + }); + + test('should remove the downloading flag', function() { + assert.isFalse(subject.downloading); + }); + }); + }); + + suite('events', function() { + suite('apps events', function() { + // This function checks that we release the callbacks properly + // at the end of a download. Assumes subject.download() was called. + function testCleanup() { + test('should stop responding to progress', function() { + mockApp.mTriggerDownloadProgress(42); + assert.notEqual(subject.progress, 42); + }); + + test('should stop responding to error', function() { + MockUpdateManager.mErrorBannerRequested = false; + mockApp.mTriggerDownloadError(); + assert.isFalse(MockUpdateManager.mErrorBannerRequested); + }); + + test('progress should be reset', function() { + assert.isNull(subject.progress); + }); + } + + downloadAvailableSuite('ondownloadavailable', function() { + mockApp.mTriggerDownloadAvailable(); + }); + + suite('ondownloadavailable when not installed', function() { + setup(function() { + mockApp.installState = 'pending'; + mockApp.mTriggerDownloadAvailable(); + }); + + test('should not add self to the available downloads', function() { + assert.isNull(MockUpdateManager.mLastUpdatesAdd); + }); + + test('should not answer to progress', function() { + mockApp.mTriggerDownloadSuccess(); + assert.isNull(MockUpdateManager.mLastDownloadsRemoval); + }); + }); + + suite('downloadavailable at init when not installed', function() { + setup(function() { + mockApp.installState = 'pending'; + subject = new AppUpdatable(mockApp); + mockApp.mTriggerDownloadAvailable(); + }); + + test('should not add self to the available downloads', function() { + assert.isNull(MockUpdateManager.mLastUpdatesAdd); + }); + + test('should not answer to progress', function() { + mockApp.mTriggerDownloadSuccess(); + assert.isNull(MockUpdateManager.mLastDownloadsRemoval); + }); + }); + + suite('ondownloadsuccess', function() { + test('should remove self from active downloads', function() { + mockApp.mTriggerDownloadAvailable(); + mockApp.mTriggerDownloadProgress(42); + mockApp.mTriggerDownloadSuccess(); + assert.isNotNull(MockUpdateManager.mLastDownloadsRemoval); + assert.equal(MockUpdateManager.mLastDownloadsRemoval.app.mId, + mockApp.mId); + }); + + test('should not remove self if not downloading', function() { + mockApp.mTriggerDownloadSuccess(); + assert.isNull(MockUpdateManager.mLastDownloadsRemoval); + }); + + test('should remove self from available downloads', function() { + mockApp.mTriggerDownloadAvailable(); + mockApp.mTriggerDownloadProgress(42); + mockApp.mTriggerDownloadSuccess(); + assert.isNotNull(MockUpdateManager.mLastUpdatesRemoval); + assert.equal(MockUpdateManager.mLastUpdatesRemoval.app.mId, + mockApp.mId); + }); + + suite('application of the download', function() { + test('should apply if the app is not in foreground', function() { + mockApp.mTriggerDownloadAvailable(); + MockWindowManager.mDisplayedApp = + 'http://homescreen.gaiamobile.org'; + mockApp.mTriggerDownloadSuccess(); + assert.isNotNull(MockAppsMgmt.mLastAppApplied); + assert.equal(MockAppsMgmt.mLastAppApplied.mId, mockApp.mId); + }); + + test('should wait for appwillclose if it is', function() { + var origin = 'http://testapp.gaiamobile.org'; + mockApp.origin = origin; + MockWindowManager.mDisplayedApp = origin; + + mockApp.mTriggerDownloadAvailable(); + mockApp.mTriggerDownloadSuccess(); + assert.isNull(MockAppsMgmt.mLastAppApplied); + + var evt = document.createEvent('CustomEvent'); + evt.initCustomEvent('appwillclose', true, false, + { origin: origin }); + window.dispatchEvent(evt); + + assert.isNotNull(MockAppsMgmt.mLastAppApplied); + assert.equal(MockAppsMgmt.mLastAppApplied.mId, mockApp.mId); + }); + + test('should kill the app before applying the update', function() { + mockApp.mTriggerDownloadAvailable(); + mockApp.mTriggerDownloadSuccess(); + assert.equal('https://testapp.gaiamobile.org', + MockWindowManager.mLastKilledOrigin); + }); + }); + }); + + suite('ondownloaderror', function() { + setup(function() { + mockApp.mTriggerDownloadAvailable(); + mockApp.mTriggerDownloadError(); + }); + + test('should request error banner', function() { + assert.isTrue(MockUpdateManager.mErrorBannerRequested); + }); + + test('should remove self from active downloads', function() { + assert.isNotNull(MockUpdateManager.mLastDownloadsRemoval); + assert.equal(MockUpdateManager.mLastDownloadsRemoval.app.mId, + mockApp.mId); + }); + + test('progress should be reset', function() { + assert.isNull(subject.progress); + }); + + test('should still answer to progress events', function() { + mockApp.mTriggerDownloadProgress(42); + assert.equal(42, subject.progress); + }); + }); + + suite('onprogress', function() { + setup(function() { + mockApp.mTriggerDownloadAvailable(); + }); + + test('should send progress to update manager', function() { + mockApp.mTriggerDownloadProgress(1234); + assert.equal(1234, MockUpdateManager.mProgressCalledWith); + }); + + test('should send progress delta to update manager', function() { + mockApp.mTriggerDownloadProgress(1234); + mockApp.mTriggerDownloadProgress(2234); + assert.equal(1000, MockUpdateManager.mProgressCalledWith); + }); + }); + + suite('ondownloadapplied', function() { + setup(function() { + mockApp.mTriggerDownloadAvailable(); + mockApp.mTriggerDownloadApplied(); + }); + + testCleanup(); + }); + }); + + suite('system update events', function() { + setup(function() { + subject = new SystemUpdatable(42); + subject._dispatchEvent = fakeDispatchEvent; + subject.download(); + }); + + suite('update-downloaded', function() { + setup(function() { + asyncStorage.setItem(SystemUpdatable.KNOWN_UPDATE_FLAG, true); + var event = new MockChromeEvent({ + type: 'update-downloaded' + }); + subject.handleEvent(event); + }); + + test('should reset the downloading flag', function() { + assert.isFalse(subject.downloading); + }); + + test('should reset SystemUpdatable.KNOWN_UPDATE_FLAG', function() { + assert.isUndefined(asyncStorage.mItems[SystemUpdatable.KNOWN_UPDATE_FLAG]); + }); + + testSystemApplyPrompt(); + }); + + suite('update-prompt-apply', function() { + setup(function() { + asyncStorage.setItem(SystemUpdatable.KNOWN_UPDATE_FLAG, true); + MockUtilityTray.show(); + var event = new MockChromeEvent({ + type: 'update-prompt-apply' + }); + subject.handleEvent(event); + }); + + test('should reset SystemUpdatable.KNOWN_UPDATE_FLAG', function() { + assert.isUndefined(asyncStorage.mItems[SystemUpdatable.KNOWN_UPDATE_FLAG]); + }); + + testSystemApplyPrompt(); + }); + + suite('update-error', function() { + setup(function() { + subject = new SystemUpdatable(42); + var event = new MockChromeEvent({ + type: 'update-error' + }); + subject.handleEvent(event); + }); + + test('should request error banner', function() { + assert.isTrue(MockUpdateManager.mErrorBannerRequested); + }); + + test('should remove self from active downloads', function() { + assert.isNotNull(MockUpdateManager.mLastDownloadsRemoval); + assert.equal(subject, MockUpdateManager.mLastDownloadsRemoval); + }); + + test('should remove the downloading flag', function() { + assert.isFalse(subject.downloading); + }); + }); + + suite('update download events', function() { + var event; + setup(function() { + subject = new SystemUpdatable(98734); + subject.download(); + }); + + suite('when the download starts', function() { + setup(function() { + event = new MockChromeEvent({ + type: 'update-download-started', + total: 98734 + }); + }); + + test('should clear paused flag', function() { + subject.paused = true; + subject.handleEvent(event); + assert.isFalse(subject.paused); + }); + }); + + suite('when the download receives progress', function() { + setup(function() { + event = new MockChromeEvent({ + type: 'update-download-progress', + progress: 1234, + total: 98734 + }); + }); + + test('should send progress to update manager', function() { + subject.handleEvent(event); + assert.equal(1234, MockUpdateManager.mProgressCalledWith); + }); + + test('should send progress delta to update manager', function() { + subject.handleEvent(event); + event.detail.progress = 2234; + subject.handleEvent(event); + assert.equal(1000, MockUpdateManager.mProgressCalledWith); + }); + }); + + suite('when the download is paused', function() { + setup(function() { + asyncStorage.setItem(SystemUpdatable.KNOWN_UPDATE_FLAG, true); + event = new MockChromeEvent({ + type: 'update-download-stopped', + paused: true + }); + subject.handleEvent(event); + }); + + test('should set the paused flag', function() { + assert.isTrue(subject.paused); + }); + test('shouldn\'t signal "started uncompressing"', function() { + assert.isFalse(MockUpdateManager.mStartedUncompressingCalled); + }); + test('should not reset SystemUpdatable.KNOWN_UPDATE_FLAG', function() { + assert.isTrue(asyncStorage.mItems[SystemUpdatable.KNOWN_UPDATE_FLAG]); + }); + }); + + suite('when the download is complete', function() { + setup(function() { + asyncStorage.setItem(SystemUpdatable.KNOWN_UPDATE_FLAG, true); + event = new MockChromeEvent({ + type: 'update-download-stopped', + paused: false + }); + subject.handleEvent(event); + }); + + test('should clear the paused flag', function() { + assert.isFalse(subject.paused); + }); + + test('should signal the UpdateManager', function() { + assert.isTrue(MockUpdateManager.mStartedUncompressingCalled); + }); + test('should not reset SystemUpdatable.KNOWN_UPDATE_FLAG', function() { + assert.isTrue(asyncStorage.mItems[SystemUpdatable.KNOWN_UPDATE_FLAG]); + }); + }); + }); + }); + }); + + + function testSystemApplyPrompt() { + test('apply prompt shown', function() { + assert.isTrue(MockCustomDialog.mShown); + assert.equal('systemUpdateReady', MockCustomDialog.mShowedTitle); + assert.equal('wantToInstall', MockCustomDialog.mShowedMsg); + + assert.equal('later', MockCustomDialog.mShowedCancel.title); + assert.equal('installNow', MockCustomDialog.mShowedConfirm.title); + }); + + test('utility tray hidden', function() { + assert.isFalse(MockUtilityTray.mShown); + }); + + test('apply prompt cancel callback', function() { + assert.equal(subject.declineInstall.name, + MockCustomDialog.mShowedCancel.callback.name); + + subject.declineInstall(); + assert.isFalse(MockCustomDialog.mShown); + + assert.equal('update-prompt-apply-result', lastDispatchedEvent.type); + assert.equal('wait', lastDispatchedEvent.value); + }); + + test('canceling should remove from downloads queue', function() { + subject.declineInstall(); + + assert.isNotNull(MockUpdateManager.mLastDownloadsRemoval); + assert.equal(subject, MockUpdateManager.mLastDownloadsRemoval); + }); + + test('apply prompt confirm callback', function() { + assert.equal(subject.acceptInstall.name, + MockCustomDialog.mShowedConfirm.callback.name); + + subject.acceptInstall(); + assert.isFalse(MockCustomDialog.mShown); + + assert.equal('update-prompt-apply-result', lastDispatchedEvent.type); + assert.equal('restart', lastDispatchedEvent.value); + }); + } +}); diff --git a/apps/system/test/unit/update_manager_test.js b/apps/system/test/unit/update_manager_test.js new file mode 100644 index 0000000..2b2ef4c --- /dev/null +++ b/apps/system/test/unit/update_manager_test.js @@ -0,0 +1,1449 @@ +'use strict'; + +requireApp('system/js/update_manager.js'); + +requireApp('system/test/unit/mock_app.js'); +requireApp('system/test/unit/mock_updatable.js'); +requireApp('system/test/unit/mock_apps_mgmt.js'); +requireApp('system/test/unit/mock_custom_dialog.js'); +requireApp('system/test/unit/mock_utility_tray.js'); +requireApp('system/test/unit/mock_system_banner.js'); +requireApp('system/test/unit/mock_chrome_event.js'); +requireApp('system/test/unit/mock_settings_listener.js'); +requireApp('system/test/unit/mock_statusbar.js'); +requireApp('system/test/unit/mock_notification_screen.js'); +requireApp('system/test/unit/mock_navigator_settings.js'); +requireApp('system/test/unit/mock_navigator_wake_lock.js'); +requireApp('system/test/unit/mock_navigator_moz_mobile_connection.js'); +requireApp('system/test/unit/mock_l10n.js'); +requireApp('system/test/unit/mock_asyncStorage.js'); + +requireApp('system/test/unit/mocks_helper.js'); + +var mocksForUpdateManager = [ + 'StatusBar', + 'SystemBanner', + 'NotificationScreen', + 'UtilityTray', + 'CustomDialog', + 'SystemUpdatable', + 'AppUpdatable', + 'SettingsListener', + 'asyncStorage' +]; + +mocksForUpdateManager.forEach(function(mockName) { + if (! window[mockName]) { + window[mockName] = null; + } +}); + +suite('system/UpdateManager', function() { + var realL10n; + var realWifiManager; + var realRequestWakeLock; + var realNavigatorSettings; + var realDispatchEvent; + + var apps; + var updatableApps; + var uAppWithDownloadAvailable; + var appWithDownloadAvailable; + var fakeNode; + var fakeToaster; + var fakeDialog; + var fakeWarning; + + var tinyTimeout = 10; + var lastDispatchedEvent = null; + + var mocksHelper; + + suiteSetup(function() { + realNavigatorSettings = navigator.mozSettings; + navigator.mozSettings = MockNavigatorSettings; + + realL10n = navigator.mozL10n; + navigator.mozL10n = MockL10n; + + realWifiManager = navigator.mozWifiManager; + navigator.mozWifiManager = { + connection: { + status: 'connected' + } + }; + + realRequestWakeLock = navigator.requestWakeLock; + navigator.requestWakeLock = MockNavigatorWakeLock.requestWakeLock; + + realDispatchEvent = UpdateManager._dispatchEvent; + UpdateManager._dispatchEvent = function fakeDispatch(type, value) { + lastDispatchedEvent = { + type: type, + value: value + }; + }; + + mocksHelper = new MocksHelper(mocksForUpdateManager); + mocksHelper.suiteSetup(); + + UpdateManager.NOTIFICATION_BUFFERING_TIMEOUT = 0; + UpdateManager.TOASTER_TIMEOUT = 0; + }); + + suiteTeardown(function() { + navigator.mozSettings = realNavigatorSettings; + realNavigatorSettings = null; + + navigator.mozL10n = realL10n; + navigator.mozWifiManager = realWifiManager; + navigator.requestWakeLock = realRequestWakeLock; + realRequestWakeLock = null; + + UpdateManager._dispatchEvent = realDispatchEvent; + + mocksHelper.suiteTeardown(); + }); + + setup(function() { + UpdateManager._mgmt = MockAppsMgmt; + + apps = [new MockApp(), new MockApp(), new MockApp()]; + updatableApps = apps.map(function(app) { + return new AppUpdatable(app); + }); + MockAppsMgmt.mApps = apps; + + uAppWithDownloadAvailable = updatableApps[2]; + appWithDownloadAvailable = apps[2]; + appWithDownloadAvailable.downloadAvailable = true; + + fakeNode = document.createElement('div'); + fakeNode.id = 'update-manager-container'; + fakeNode.innerHTML = [ + '
', + '
', + '
', + '
', + '
', + '
' + ].join(''); + + fakeToaster = document.createElement('div'); + fakeToaster.id = 'update-manager-toaster'; + fakeToaster.innerHTML = [ + '
', + '
', + '
', + '
' + ].join(''); + + fakeDialog = document.createElement('form'); + fakeDialog.id = 'updates-download-dialog'; + fakeDialog.innerHTML = [ + '
', + '

', + 'Updates', + '

', + '', + '', + '', + '', + '', + '
' + ].join(''); + + fakeWarning = document.createElement('form'); + fakeWarning.id = 'updates-viaDataConnection-dialog'; + fakeWarning.innerHTML = [ + '
', + '

', + 'Updates', + '

', + '

', + '

', + '', + '', + '', + '', + '
' + ].join(''); + + document.body.appendChild(fakeNode); + document.body.appendChild(fakeToaster); + document.body.appendChild(fakeDialog); + document.body.appendChild(fakeWarning); + + mocksHelper.setup(); + }); + + teardown(function(done) { + // We wait for the nextTick in order to let the UpdateManger's + // timeouts finish (they are all set to 0) + setTimeout(function() { + UpdateManager.updatableApps = []; + UpdateManager.systemUpdatable = null; + UpdateManager.updatesQueue = []; + UpdateManager.downloadsQueue = []; + UpdateManager._downloading = false; + UpdateManager._uncompressing = false; + UpdateManager.container = null; + UpdateManager.message = null; + UpdateManager.toaster = null; + UpdateManager.toasterMessage = null; + UpdateManager.laterButton = null; + UpdateManager.downloadButton = null; + UpdateManager.downloadDialog = null; + UpdateManager.downloadDialogTitle = null; + UpdateManager.downloadDialogList = null; + UpdateManager.lastUpdatesAvailable = 0; + + MockAppsMgmt.mTeardown(); + + mocksHelper.teardown(); + + fakeNode.parentNode.removeChild(fakeNode); + fakeToaster.parentNode.removeChild(fakeToaster); + fakeDialog.parentNode.removeChild(fakeDialog); + + lastDispatchedEvent = null; + MockNavigatorWakeLock.mTeardown(); + MockNavigatorSettings.mTeardown(); + + done(); + }); + }); + + suite('init', function() { + test('should get all applications', function(done) { + MockAppsMgmt.mNext = function() { + done(); + }; + UpdateManager.init(); + }); + + test('should create AppUpdatable on init', function(done) { + MockAppUpdatable.mTeardown(); + + MockAppsMgmt.mNext = function() { + assert.equal(MockAppUpdatable.mCount, apps.length); + done(); + }; + UpdateManager.init(); + }); + + test('should bind dom elements', function() { + UpdateManager.init(); + assert.equal('update-manager-container', UpdateManager.container.id); + assert.equal('message', UpdateManager.message.className); + + assert.equal('update-manager-toaster', UpdateManager.toaster.id); + assert.equal('message', UpdateManager.toasterMessage.className); + + assert.equal('updates-later-button', UpdateManager.laterButton.id); + assert.equal('updates-download-button', UpdateManager.downloadButton.id); + assert.equal('updates-download-dialog', UpdateManager.downloadDialog.id); + assert.equal('updates-viaDataConnection-dialog', + UpdateManager.downloadViaDataConnectionDialog.id); + assert.equal('updates-viaDataConnection-notnow-button', + UpdateManager.notnowButton.id); + assert.equal('updates-viaDataConnection-download-button', + UpdateManager.downloadViaDataConnectionButton.id); + assert.equal('H1', UpdateManager.downloadDialogTitle.tagName); + assert.equal('UL', UpdateManager.downloadDialogList.tagName); + }); + + test('should bind to the click event', function() { + UpdateManager.init(); + assert.equal(UpdateManager.containerClicked.name, + UpdateManager.container.onclick.name); + + assert.equal(UpdateManager.requestDownloads.name, + UpdateManager.downloadButton.onclick.name); + + assert.equal(UpdateManager.cancelPrompt.name, + UpdateManager.laterButton.onclick.name); + + assert.equal(UpdateManager.cancelDataConnectionUpdatesPrompt.name, + UpdateManager.notnowButton.onclick.name); + + assert.equal(UpdateManager.requestDownloads.name, + UpdateManager.downloadViaDataConnectionButton.onclick.name); + }); + }); + + suite('events', function() { + suite('app install', function() { + var installedApp; + + setup(function() { + MockAppUpdatable.mTeardown(); + + UpdateManager.init(); + + installedApp = new MockApp(); + installedApp.downloadAvailable = true; + MockAppsMgmt.mTriggerOninstall(installedApp); + }); + + test('should instantiate an updatable app', function() { + assert.equal(MockAppUpdatable.mCount, 1); + }); + }); + + suite('app uninstall', function() { + var partialApp; + + setup(function() { + UpdateManager.init(); + UpdateManager.updatableApps = updatableApps; + UpdateManager.addToUpdatesQueue(uAppWithDownloadAvailable); + + partialApp = { + origin: appWithDownloadAvailable.origin, + manifestURL: appWithDownloadAvailable.manifestURL + }; + }); + + test('should remove the updatable app', function() { + var initialLength = UpdateManager.updatableApps.length; + MockAppsMgmt.mTriggerOnuninstall(partialApp); + assert.equal(initialLength - 1, UpdateManager.updatableApps.length); + }); + + test('should remove from the update queue', function() { + var initialLength = UpdateManager.updatesQueue.length; + MockAppsMgmt.mTriggerOnuninstall(partialApp); + assert.equal(initialLength - 1, UpdateManager.updatesQueue.length); + }); + + test('should remove from the update queue even if no downloadavailable', + function() { + uAppWithDownloadAvailable.app.downloadAvailable = false; + var initialLength = UpdateManager.updatesQueue.length; + MockAppsMgmt.mTriggerOnuninstall(partialApp); + assert.equal(initialLength - 1, UpdateManager.updatesQueue.length); + }); + + test('should call uninit on the updatable', function() { + var lastIndex = UpdateManager.updatesQueue.length - 1; + var updatableApp = UpdateManager.updatesQueue[lastIndex]; + MockAppsMgmt.mTriggerOnuninstall(partialApp); + assert.isTrue(updatableApp.mUninitCalled); + }); + }); + + suite('system update available', function() { + var event; + + setup(function() { + UpdateManager.init(); + event = new MockChromeEvent({ + type: 'update-available', + size: 42 + }); + UpdateManager.handleEvent(event); + }); + + test('should add a system updatable to the updates', function() { + var lastIndex = UpdateManager.updatesQueue.length - 1; + assert.equal(undefined, UpdateManager.updatesQueue[lastIndex].app); + }); + + test('should init the updatable with the download size', function() { + var lastIndex = UpdateManager.updatesQueue.length - 1; + assert.equal(42, UpdateManager.updatesQueue[lastIndex].size); + }); + + test('should not add or instanciate a system updatable if there is one', + function() { + var initialLength = UpdateManager.updatesQueue.length; + + UpdateManager.handleEvent(event); + + assert.equal(UpdateManager.updatesQueue.length, initialLength); + assert.equal(MockSystemUpdatable.mInstancesCount, 1); + }); + + test('should remember that update is available', function() { + assert.isTrue(UpdateManager.systemUpdatable.mKnownUpdate); + }); + }); + + suite('no system update available', function() { + setup(function() { + UpdateManager.init(); + }); + + test('should not remember about the update', function() { + assert.isUndefined(UpdateManager.systemUpdatable.mKnownUpdate); + }); + }); + + }); + + suite('UI', function() { + setup(function() { + MockAppsMgmt.mApps = []; + UpdateManager.init(); + UpdateManager.updatableApps = updatableApps; + }); + + suite('downloading state', function() { + test('should add the css class if downloading', function() { + UpdateManager._downloading = true; + UpdateManager.render(); + var css = UpdateManager.container.classList; + assert.isTrue(css.contains('downloading')); + }); + + test('should remove the css class if not downloading', function() { + UpdateManager._downloading = true; + UpdateManager.render(); + + UpdateManager._downloading = false; + UpdateManager.render(); + var css = UpdateManager.container.classList; + assert.isFalse(css.contains('downloading')); + }); + + test('should show the downloading progress if downloading', function() { + UpdateManager._downloading = true; + UpdateManager.render(); + assert.equal('downloadingUpdateMessage{"progress":"0.00 bytes"}', + UpdateManager.message.textContent); + }); + + test('should not show the toaster if downloading', function(done) { + UpdateManager.NOTIFICATION_BUFFERING_TIMEOUT = tinyTimeout; + UpdateManager.TOASTER_TIMEOUT = tinyTimeout; + UpdateManager._downloading = true; + UpdateManager.addToUpdatesQueue(uAppWithDownloadAvailable); + + setTimeout(function() { + var css = UpdateManager.toaster.classList; + assert.isFalse(css.contains('displayed')); + UpdateManager.NOTIFICATION_BUFFERING_TIMEOUT = 0; + UpdateManager.TOASTER_TIMEOUT = 0; + done(); + }, tinyTimeout * 1.5); + }); + + test('should show the available message if not downloading', function() { + UpdateManager.updatesQueue = updatableApps; + UpdateManager.render(); + assert.equal('updateAvailableInfo{"n":3}', + UpdateManager.message.textContent); + }); + }); + + suite('progress display', function() { + setup(function() { + UpdateManager.updatesQueue = [uAppWithDownloadAvailable]; + + var evt = document.createEvent('MouseEvents'); + evt.initEvent('click', true, true); + UpdateManager.startDownloads(evt); + + UpdateManager.addToDownloadsQueue(uAppWithDownloadAvailable); + + UpdateManager.downloadProgressed(1234); + }); + + test('downloadedBytes should be reset by startDownloads', function() { + var evt = document.createEvent('MouseEvents'); + evt.initEvent('click', true, true); + UpdateManager.startDownloads(evt); + + assert.equal('downloadingUpdateMessage{"progress":"0.00 bytes"}', + UpdateManager.message.textContent); + }); + + test('downloadedBytes should be reset when stopping the download', + function() { + + UpdateManager.removeFromDownloadsQueue(uAppWithDownloadAvailable); + UpdateManager.addToDownloadsQueue(uAppWithDownloadAvailable); + + assert.equal('downloadingUpdateMessage{"progress":"0.00 bytes"}', + UpdateManager.message.textContent); + }); + + test('should increment the downloadedBytes', function() { + UpdateManager.downloadProgressed(100); + assert.equal('downloadingUpdateMessage{"progress":"1.30 kB"}', + UpdateManager.message.textContent); + }); + + test('should not update if bytes <= 0', function() { + UpdateManager.downloadProgressed(-100); + assert.equal('downloadingUpdateMessage{"progress":"1.21 kB"}', + UpdateManager.message.textContent); + }); + + test('should display the notification', function() { + assert.isTrue(fakeNode.classList.contains('displayed')); + }); + + }); + + suite('uncompress display', function() { + var systemUpdatable; + + setup(function() { + systemUpdatable = new MockSystemUpdatable(); + }); + + suite('when we only have the system update', function() { + setup(function() { + UpdateManager.addToUpdatesQueue(systemUpdatable); + UpdateManager.addToDownloadsQueue(systemUpdatable); + UpdateManager.startedUncompressing(); + }); + + test('should render in uncompressing mode', function() { + assert.equal(UpdateManager.message.textContent, + 'uncompressingMessage'); + }); + }); + + suite('when we have various ongoing updates', function() { + setup(function() { + UpdateManager.addToUpdatableApps(uAppWithDownloadAvailable); + UpdateManager.addToUpdatesQueue(uAppWithDownloadAvailable); + UpdateManager.addToDownloadsQueue(uAppWithDownloadAvailable); + + UpdateManager.addToUpdatesQueue(systemUpdatable); + UpdateManager.addToDownloadsQueue(systemUpdatable); + + UpdateManager.startedUncompressing(); + }); + + test('should stay in downloading mode', function() { + assert.include(UpdateManager.message.textContent, + 'downloadingUpdateMessage'); + }); + + suite('once the app updates are done', function() { + setup(function() { + UpdateManager.removeFromDownloadsQueue(uAppWithDownloadAvailable); + UpdateManager.removeFromUpdatesQueue(uAppWithDownloadAvailable); + }); + + test('should render in uncompressing mode', function() { + assert.equal(UpdateManager.message.textContent, + 'uncompressingMessage'); + }); + }); + }); + }); + + suite('container visibility', function() { + suiteSetup(function() { + UpdateManager.NOTIFICATION_BUFFERING_TIMEOUT = tinyTimeout; + UpdateManager.TOASTER_TIMEOUT = tinyTimeout; + }); + + suiteTeardown(function() { + UpdateManager.NOTIFICATION_BUFFERING_TIMEOUT = 0; + UpdateManager.TOASTER_TIMEOUT = 0; + }); + + setup(function() { + UpdateManager.addToUpdatesQueue(uAppWithDownloadAvailable); + }); + + teardown(function(done) { + // wait for all actions to happen in UpdateManager before reseting + setTimeout(function() { + done(); + }, tinyTimeout * 2); + }); + + suite('notification behavior after addToDownloadsQueue', function() { + setup(function() { + var css = UpdateManager.container.classList; + assert.isFalse(css.contains('displayed')); + UpdateManager.addToDownloadsQueue(uAppWithDownloadAvailable); + }); + + test('should be displayed only once', function() { + var css = UpdateManager.container.classList; + assert.isTrue(css.contains('displayed')); + assert.equal(MockNotificationScreen.wasMethodCalled['incExternalNotifications'], 1); + }); + + test('should not be displayed after timeout', function(done) { + setTimeout(function() { + var css = UpdateManager.container.classList; + assert.isTrue(css.contains('displayed')); + assert.equal(MockNotificationScreen.wasMethodCalled['incExternalNotifications'], 1); + done(); + }, tinyTimeout * 2); + + }); + }); + + suite('notification behavior after addToDownloadsQueue after timeout', function() { + setup(function(done) { + setTimeout(function() { + var css = UpdateManager.container.classList; + assert.isFalse(css.contains('displayed')); + UpdateManager.addToDownloadsQueue(uAppWithDownloadAvailable); + done(); + }); + }); + + test('should not increment the counter if already displayed', function() { + var css = UpdateManager.container.classList; + assert.isTrue(css.contains('displayed')); + assert.equal(MockNotificationScreen.wasMethodCalled['incExternalNotifications'], 1); + }); + }); + + suite('displaying the container after a timeout', function() { + setup(function() { + var css = UpdateManager.container.classList; + assert.isFalse(css.contains('displayed')); + }); + + test('should display after a timeout', function(done) { + setTimeout(function() { + var css = UpdateManager.container.classList; + assert.isTrue(css.contains('displayed')); + assert.equal(MockNotificationScreen.wasMethodCalled['incExternalNotifications'], 1); + done(); + }, tinyTimeout * 2); + }); + + test('should not display if there are no more updates', function(done) { + UpdateManager.updatesQueue.forEach(function(uApp) { + UpdateManager.removeFromUpdatesQueue(uApp); + }); + + setTimeout(function() { + var css = UpdateManager.container.classList; + assert.isFalse(css.contains('displayed')); + done(); + }, tinyTimeout * 2); + }); + + test('should display an updated count', function(done) { + UpdateManager.addToUpdatesQueue(updatableApps[1]); + setTimeout(function() { + assert.equal('updateAvailableInfo{"n":2}', + UpdateManager.message.textContent); + done(); + }, tinyTimeout * 2); + }); + + suite('update toaster', function() { + test('should display after a timeout', function(done) { + var css = UpdateManager.container.classList; + assert.isFalse(css.contains('displayed')); + setTimeout(function() { + var css = UpdateManager.toaster.classList; + assert.isTrue(css.contains('displayed')); + assert.equal('updateAvailableInfo{"n":1}', + UpdateManager.toasterMessage.textContent); + done(); + }, tinyTimeout * 1.5); + }); + + test('should reset toaster value when notification was activated', function(done) { + setTimeout(function() { + UpdateManager.addToUpdatesQueue(updatableApps[1]); + assert.equal('updateAvailableInfo{"n":1}', + UpdateManager.toasterMessage.textContent); + done(); + }, tinyTimeout * 2); + }); + + test('should show the right message', function(done) { + setTimeout(function() { + assert.equal('updateAvailableInfo{"n":1}', + UpdateManager.toasterMessage.textContent); + done(); + }, tinyTimeout * 2); + }); + + + test('should hide after TOASTER_TIMEOUT', function(done) { + UpdateManager.addToUpdatesQueue(updatableApps[1]); + setTimeout(function() { + setTimeout(function() { + var css = UpdateManager.toaster.classList; + assert.isFalse(css.contains('displayed')); + done(); + }, tinyTimeout * 2); + }, tinyTimeout * 2); + }); + + }); + + test('should add a new statusbar notification', function(done) { + var method1 = 'incExternalNotifications'; + setTimeout(function() { + assert.ok(MockNotificationScreen.wasMethodCalled[method1]); + done(); + }, tinyTimeout * 2); + }); + }); + + suite('no more updates', function() { + setup(function() { + UpdateManager.container.classList.add('displayed'); + UpdateManager.updatesQueue = [uAppWithDownloadAvailable]; + UpdateManager.removeFromUpdatesQueue(uAppWithDownloadAvailable); + }); + + test('should hide the container', function() { + var css = UpdateManager.container.classList; + assert.isFalse(css.contains('displayed')); + }); + + test('should decrease the external notifications count', function() { + var method1 = 'decExternalNotifications'; + assert.ok(MockNotificationScreen.wasMethodCalled[method1]); + }); + }); + }); + + suite('after downloads', function() { + test('should check if new updates where found', function() { + var uApp = updatableApps[0]; + + UpdateManager.updatableApps = updatableApps; + UpdateManager.downloadsQueue = [uApp]; + + UpdateManager.removeFromDownloadsQueue(uApp); + assert.equal(uAppWithDownloadAvailable.app.mId, + UpdateManager.updatesQueue[0].app.mId); + }); + }); + + suite('error banner requests', function() { + suiteSetup(function() { + UpdateManager.NOTIFICATION_BUFFERING_TIMEOUT = tinyTimeout; + UpdateManager.TOASTER_TIMEOUT = tinyTimeout; + }); + + suiteTeardown(function() { + UpdateManager.NOTIFICATION_BUFFERING_TIMEOUT = 0; + UpdateManager.TOASTER_TIMEOUT = 0; + }); + + setup(function() { + UpdateManager.init(); + UpdateManager.requestErrorBanner(); + }); + + teardown(function(done) { + // wait for all actions to happen in UpdateManager before reseting + setTimeout(function() { + done(); + }, tinyTimeout * 2); + }); + + test('should wait before showing the system banner', function(done) { + assert.equal(0, MockSystemBanner.mShowCount); + + setTimeout(function() { + done(); + }, tinyTimeout * 2); + }); + + test('should show after NOTIFICATION_BUFFERING_TIMEOUT', function(done) { + setTimeout(function() { + assert.equal(1, MockSystemBanner.mShowCount); + assert.equal('downloadError', MockSystemBanner.mMessage); + done(); + }, tinyTimeout * 2); + }); + + test('should show only once if called multiple time', function(done) { + UpdateManager.requestErrorBanner(); + setTimeout(function() { + assert.equal(1, MockSystemBanner.mShowCount); + done(); + }, tinyTimeout * 2); + }); + }); + + suite('humanizeSize', function() { + test('should handle 0', function() { + assert.equal('0.00 bytes', UpdateManager._humanizeSize(0)); + }); + + test('should handle bytes size', function() { + assert.equal('42.00 bytes', UpdateManager._humanizeSize(42)); + }); + + test('should handle kilobytes size', function() { + assert.equal('1.00 kB', UpdateManager._humanizeSize(1024)); + }); + + test('should handle megabytes size', function() { + assert.equal('4.67 MB', UpdateManager._humanizeSize(4901024)); + }); + + test('should handle gigabytes size', function() { + assert.equal('3.73 GB', UpdateManager._humanizeSize(4000901024)); + }); + }); + }); + + suite('actions', function() { + setup(function() { + UpdateManager.init(); + }); + + suite('start downloads', function() { + var systemUpdatable, appUpdatable, evt; + + setup(function() { + UpdateManager.init(); + + systemUpdatable = new MockSystemUpdatable(); + + appUpdatable = new MockAppUpdatable(new MockApp()); + appUpdatable.name = 'Angry birds'; + appUpdatable.size = '423459'; + + UpdateManager.addToUpdatableApps(appUpdatable); + UpdateManager.addToUpdatesQueue(appUpdatable); + UpdateManager.addToUpdatesQueue(systemUpdatable); + + UpdateManager.container.click(); + + evt = document.createEvent('MouseEvents'); + evt.initEvent('click', true, true); + }); + + suite('data connection warning', function() { + var downloadDialog; + setup(function() { + downloadDialog = UpdateManager.downloadDialog; + }); + + test('should switch the online data attribute when online', + function() { + downloadDialog.dataset.online = false; + window.dispatchEvent(new CustomEvent('online')); + assert.equal(downloadDialog.dataset.online, 'true'); + }); + + test('should leave the online data attribute true when online', + function() { + downloadDialog.dataset.online = true; + window.dispatchEvent(new CustomEvent('online')); + assert.equal(downloadDialog.dataset.online, 'true'); + }); + + test('should switch the nowifi data attribute when connected', + function() { + downloadDialog.dataset.nowifi = true; + window.dispatchEvent(new CustomEvent('wifi-statuschange')); + assert.equal(downloadDialog.dataset.nowifi, 'false'); + }); + + test('should switch the nowifi data attribute when disconnected', + function() { + downloadDialog.dataset.nowifi = false; + navigator.mozWifiManager.connection.status = 'disconnected'; + window.dispatchEvent(new CustomEvent('wifi-statuschange')); + assert.equal(downloadDialog.dataset.nowifi, 'true'); + }); + }); + + test('should enable the download button', function() { + var downloadButton = UpdateManager.downloadButton; + assert.isFalse(downloadButton.disabled); + }); + + suite('with all the checkboxes checked', function() { + setup(function() { + UpdateManager.startDownloads(evt); + }); + + test('should download system updates', function() { + assert.isTrue(systemUpdatable.mDownloadCalled); + }); + + test('should call download on checked app updatables', function() { + assert.isTrue(appUpdatable.mDownloadCalled); + }); + }); + + suite('with no checkbox checked', function() { + setup(function() { + var dialog = UpdateManager.downloadDialogList; + var checkboxes = dialog.querySelectorAll('input[type="checkbox"]'); + for (var i = 0; i < checkboxes.length; i++) { + var checkbox = checkboxes[i]; + if (checkbox.checked) { + checkbox.click(); + } + } + + UpdateManager.startDownloads(evt); + }); + + test('the download button should be enabled', function() { + assert.isFalse(UpdateManager.downloadButton.disabled); + }); + + test('should still download system updates', function() { + assert.isTrue(systemUpdatable.mDownloadCalled); + }); + + test('should not call download on unchecked app updatables', + function() { + assert.isFalse(appUpdatable.mDownloadCalled); + }); + }); + + suite('with only app updates', function() { + setup(function() { + UpdateManager.removeFromUpdatesQueue(systemUpdatable); + UpdateManager.container.click(); + }); + + suite('unchecking all the checkboxes', function() { + var dialog, downloadButton; + + setup(function() { + dialog = UpdateManager.downloadDialogList; + var checkboxes = dialog.querySelectorAll('input[type="checkbox"]'); + for (var i = 0; i < checkboxes.length; i++) { + var checkbox = checkboxes[i]; + if (checkbox.checked) { + checkboxes[i].click(); + } + } + + downloadButton = UpdateManager.downloadButton; + }); + + test('should disable the download button', function() { + assert.isTrue(downloadButton.disabled); + }); + + suite('then checking one back', function() { + setup(function() { + var checkbox = dialog.querySelector('input[type="checkbox"]'); + checkbox.click(); + }); + + test('should enable the download button back', function() { + assert.isFalse(downloadButton.disabled); + }); + }); + }); + }); + }); + + suite('cancel all downloads', function() { + var systemUpdatable; + + setup(function() { + systemUpdatable = new MockSystemUpdatable(); + UpdateManager.updatableApps = updatableApps; + [systemUpdatable, uAppWithDownloadAvailable].forEach(function(updatable) { + UpdateManager.addToUpdatesQueue(updatable); + UpdateManager.addToDownloadsQueue(updatable); + }); + + UpdateManager.cancelAllDownloads(); + }); + + test('should call cancelDownload on the app updatables', function() { + assert.isTrue(uAppWithDownloadAvailable.mCancelCalled); + }); + + test('should call cancelDownload on the system updatable', function() { + assert.isTrue(systemUpdatable.mCancelCalled); + }); + + test('should empty the downloads queue', function() { + assert.equal(UpdateManager.downloadsQueue.length, 0); + }); + + test('should leave the updates available', function() { + assert.equal(UpdateManager.updatesQueue.length, 2); + }); + }); + + suite('download prompt', function() { + setup(function() { + MockUtilityTray.show(); + var systemUpdatable = new MockSystemUpdatable(); + systemUpdatable.size = 5296345; + var appUpdatable = new MockAppUpdatable(new MockApp()); + appUpdatable.name = 'Angry birds'; + appUpdatable.size = '423459'; + var hostedAppUpdatable = new MockAppUpdatable(new MockApp()); + hostedAppUpdatable.name = 'Twitter'; + UpdateManager.updatesQueue = [hostedAppUpdatable, appUpdatable, + systemUpdatable]; + UpdateManager.containerClicked(); + UpdateManager._isDataConnectionWarningDialogEnabled = true; + UpdateManager.downloadDialog.dataset.nowifi = false; + }); + + suite('download prompt', function() { + test('should hide the utility tray', function() { + assert.isFalse(MockUtilityTray.mShown); + }); + + test('should show the download dialog', function() { + var css = UpdateManager.downloadDialog.classList; + assert.isTrue(css.contains('visible')); + }); + + test('should set the title', function() { + var title = fakeDialog.querySelector('h1'); + assert.equal('numberOfUpdates{"n":3}', title.textContent); + }); + + suite('update list rendering', function() { + test('should create an item for each update', function() { + assert.equal(3, UpdateManager.downloadDialogList.children.length); + }); + + test('should render system update item first with required', + function() { + var item = UpdateManager.downloadDialogList.children[0]; + + assert.include(item.textContent, 'systemUpdate'); + assert.include(item.textContent, '5.05 MB'); + assert.include(item.textContent, 'required'); + }); + + test('should render packaged app items alphabetically with checkbox', + function() { + var item = UpdateManager.downloadDialogList.children[1]; + + assert.include(item.textContent, 'Angry birds'); + assert.include(item.textContent, '413.53 kB'); + + var checkbox = item.querySelector('input'); + assert.equal(checkbox.type, 'checkbox'); + assert.isTrue(checkbox.checked); + assert.equal(checkbox.dataset.position, '1'); + }); + + test('should render hosted app items alphabetically with checkbox', + function() { + var item = UpdateManager.downloadDialogList.children[2]; + + assert.include(item.textContent, 'Twitter'); + + var checkbox = item.querySelector('input'); + assert.equal(checkbox.type, 'checkbox'); + assert.isTrue(checkbox.checked); + assert.equal(checkbox.dataset.position, '2'); + }); + }); + }); + + test('should handle clicking download in the data connection warning dialog', function() { + UpdateManager.downloadDialog.dataset.nowifi = true; + + var evt = { + preventDefault: function() {}, + type: 'click', + target: UpdateManager.downloadViaDataConnectionButton + }; + + UpdateManager.requestDownloads(evt); + MockasyncStorage.getItem('gaia.system.isDataConnectionWarningDialogEnabled', function(value) { + assert.isFalse(value); + }); + assert.isFalse(UpdateManager._isDataConnectionWarningDialogEnabled); + assert.equal(UpdateManager.downloadDialog.dataset.dataConnectionInlineWarning, 'true'); + + MockasyncStorage.mTeardown(); + }); + + test('should handle clicking download when using data connection in the first time', function() { + UpdateManager.downloadDialog.dataset.nowifi = true; + + var evt = document.createEvent('MouseEvents'); + evt.initEvent('click', true, true); + + UpdateManager.requestDownloads(evt); + var css = UpdateManager.downloadViaDataConnectionDialog.classList; + assert.isTrue(css.contains('visible')); + }); + + test('should handle clicking download when using wifi', function() { + UpdateManager._isDataConnectionWarningDialogEnabled = false; + + var calledToMockStartDownloads = false; + var realStartDownloadsFunc = UpdateManager.startDownloads; + UpdateManager.startDownloads = function() { + calledToMockStartDownloads = true; + }; + + var evt = document.createEvent('MouseEvents'); + evt.initEvent('click', true, true); + + UpdateManager.requestDownloads(evt); + assert.isTrue(calledToMockStartDownloads); + + UpdateManager.startDownloads = realStartDownloadsFunc; + }); + + test('should handle cancellation on the data connection warning dialog', function() { + UpdateManager.cancelDataConnectionUpdatesPrompt(); + + var css = UpdateManager.downloadViaDataConnectionDialog.classList; + assert.isFalse(css.contains('visible')); + css = UpdateManager.downloadDialog.classList; + assert.isFalse(css.contains('visible')); + }); + + test('should handle cancellation', function() { + UpdateManager.cancelPrompt(); + + var css = UpdateManager.downloadDialog.classList; + assert.isFalse(css.contains('visible')); + }); + + test('should handle confirmation', function() { + UpdateManager._isDataConnectionWarningDialogEnabled = false; + + var evt = document.createEvent('MouseEvents'); + evt.initEvent('click', true, true); + + UpdateManager.requestDownloads(evt); + var css = UpdateManager.downloadDialog.classList; + assert.isFalse(css.contains('visible')); + css = UpdateManager.downloadViaDataConnectionDialog.classList; + assert.isFalse(css.contains('visible')); + assert.isTrue(MockUtilityTray.mShown); + assert.isTrue(evt.defaultPrevented); + }); + }); + + suite('cancel prompt', function() { + setup(function() { + UpdateManager._downloading = true; + MockUtilityTray.show(); + UpdateManager.containerClicked(); + }); + + test('should show the cancel', function() { + assert.isTrue(MockCustomDialog.mShown); + assert.isFalse(MockUtilityTray.mShown); + + assert.equal('cancelAllDownloads', MockCustomDialog.mShowedTitle); + assert.equal('wantToCancelAll', MockCustomDialog.mShowedMsg); + + assert.equal('no', MockCustomDialog.mShowedCancel.title); + assert.equal('yes', MockCustomDialog.mShowedConfirm.title); + }); + + test('should handle cancellation', function() { + assert.equal('um_cancelPrompt', + MockCustomDialog.mShowedCancel.callback.name); + + UpdateManager.cancelPrompt(); + assert.isFalse(MockCustomDialog.mShown); + }); + + test('should handle confirmation', function() { + assert.equal('um_cancelAllDownloads', + MockCustomDialog.mShowedConfirm.callback.name); + + UpdateManager.cancelAllDownloads(); + assert.isFalse(MockCustomDialog.mShown); + }); + }); + + suite('check for updates', function() { + setup(function() { + UpdateManager.init(); + }); + + test('should observe the setting', function() { + assert.equal('gaia.system.checkForUpdates', MockSettingsListener.mName); + assert.equal(false, MockSettingsListener.mDefaultValue); + assert.equal(UpdateManager.checkForUpdates.name, + MockSettingsListener.mCallback.name); + }); + + suite('when asked to check', function() { + setup(function() { + UpdateManager.checkForUpdates(true); + }); + + test('should dispatch force update event if asked for', function() { + assert.equal('force-update-check', lastDispatchedEvent.type); + }); + + test('should set the setting back to false', function() { + var setting = 'gaia.system.checkForUpdates'; + assert.isFalse(MockNavigatorSettings.mSettings[setting]); + }); + }); + + test('should not dispatch force update event if not asked', function() { + UpdateManager.checkForUpdates(false); + assert.isNull(lastDispatchedEvent); + }); + }); + }); + + suite('queues support', function() { + suite('updates queue', function() { + suite('addToUpdatesQueue', function() { + setup(function() { + var installedApp = new MockApp(); + var updatableApp = new MockAppUpdatable(installedApp); + + var pendingApp = new MockApp({ installState: 'pending' }), + uPendingApp = new MockAppUpdatable(pendingApp); + + UpdateManager.updatableApps = [updatableApp, uPendingApp]; + UpdateManager.init(); + }); + + test('should add the updatable app to the array', function() { + var updatableApp = UpdateManager.updatableApps[0]; + + var initialLength = UpdateManager.updatesQueue.length; + UpdateManager.addToUpdatesQueue(updatableApp); + assert.equal(initialLength + 1, UpdateManager.updatesQueue.length); + }); + + test('should render', function() { + var updatableApp = UpdateManager.updatableApps[0]; + + UpdateManager.addToUpdatesQueue(updatableApp); + assert.equal('updateAvailableInfo{"n":1}', + UpdateManager.message.textContent); + }); + + test('should not add app if not in updatableApps array', function() { + var updatableApp = new MockAppUpdatable(new MockApp); + var initialLength = UpdateManager.updatesQueue.length; + UpdateManager.addToUpdatesQueue(updatableApp); + assert.equal(initialLength, UpdateManager.updatesQueue.length); + }); + + test('should add a system update to the array', function() { + var systemUpdate = new MockSystemUpdatable(); + + var initialLength = UpdateManager.updatesQueue.length; + UpdateManager.addToUpdatesQueue(systemUpdate); + assert.equal(initialLength + 1, UpdateManager.updatesQueue.length); + }); + + test('should not add more than one system update', function() { + var systemUpdate = new MockSystemUpdatable(); + + UpdateManager.updatesQueue.push(new MockSystemUpdatable()); + var initialLength = UpdateManager.updatesQueue.length; + UpdateManager.addToUpdatesQueue(systemUpdate); + assert.equal(initialLength, UpdateManager.updatesQueue.length); + }); + + test('should not add if app already in the array', function() { + var updatableApp = UpdateManager.updatableApps[0]; + UpdateManager.addToUpdatesQueue(updatableApp); + + var initialLength = UpdateManager.updatesQueue.length; + UpdateManager.addToUpdatesQueue(updatableApp); + assert.equal(initialLength, UpdateManager.updatesQueue.length); + }); + + test('should not add if downloading', function() { + UpdateManager._downloading = true; + var updatableApp = UpdateManager.updatableApps[0]; + + var initialLength = UpdateManager.updatesQueue.length; + UpdateManager.addToUpdatesQueue(updatableApp); + assert.equal(initialLength, UpdateManager.updatesQueue.length); + }); + + test('should not add a pending app to the array', function() { + var updatableApp = UpdateManager.updatableApps[1]; + + var initialLength = UpdateManager.updatesQueue.length; + + UpdateManager.addToUpdatesQueue(updatableApp); + assert.equal(UpdateManager.updatesQueue.length, initialLength); + }); + + }); + + suite('removeFromUpdatesQueue', function() { + var updatableApp; + + setup(function() { + var installedApp = new MockApp(); + updatableApp = new MockAppUpdatable(installedApp); + UpdateManager.updatableApps = [updatableApp]; + UpdateManager.updatesQueue = [updatableApp]; + UpdateManager.init(); + }); + + test('should remove if in updatesQueue array', function() { + var initialLength = UpdateManager.updatesQueue.length; + UpdateManager.removeFromUpdatesQueue(updatableApp); + assert.equal(initialLength - 1, UpdateManager.updatesQueue.length); + }); + + test('should render', function() { + UpdateManager.removeFromUpdatesQueue(updatableApp); + assert.equal('updateAvailableInfo{"n":0}', + UpdateManager.message.textContent); + }); + + test('should remove system updates too', function() { + var systemUpdate = new MockSystemUpdatable(); + UpdateManager.updatesQueue.push(systemUpdate); + + var initialLength = UpdateManager.updatesQueue.length; + UpdateManager.removeFromUpdatesQueue(systemUpdate); + assert.equal(initialLength - 1, UpdateManager.updatesQueue.length); + }); + }); + }); + + suite('downloads queue', function() { + suite('addToDownloadsQueue', function() { + var updatableApp; + + setup(function() { + var installedApp = new MockApp(); + updatableApp = new MockAppUpdatable(installedApp); + UpdateManager.updatableApps = [updatableApp]; + UpdateManager.init(); + }); + + test('should add the updatable to the array', function() { + var initialLength = UpdateManager.downloadsQueue.length; + UpdateManager.addToDownloadsQueue(updatableApp); + assert.equal(initialLength + 1, UpdateManager.downloadsQueue.length); + }); + + test('should add system updates too', function() { + var initialLength = UpdateManager.downloadsQueue.length; + UpdateManager.addToDownloadsQueue(new MockSystemUpdatable()); + assert.equal(initialLength + 1, UpdateManager.downloadsQueue.length); + }); + + test('should not add more than one system updates', function() { + var initialLength = UpdateManager.downloadsQueue.length; + UpdateManager.addToDownloadsQueue(new MockSystemUpdatable()); + UpdateManager.addToDownloadsQueue(new MockSystemUpdatable()); + assert.equal(initialLength + 1, UpdateManager.downloadsQueue.length); + }); + + suite('switching to downloading mode on first add', function() { + setup(function() { + UpdateManager.addToDownloadsQueue(updatableApp); + }); + + test('should add css class', function() { + var css = UpdateManager.container.classList; + assert.isTrue(css.contains('downloading')); + }); + + test('should ask for statusbar indicator', function() { + var incMethod = 'incSystemDownloads'; + assert.ok(MockStatusBar.wasMethodCalled[incMethod]); + }); + + test('should request wifi wake lock', function() { + assert.equal('wifi', MockNavigatorWakeLock.mLastWakeLock.topic); + assert.isFalse(MockNavigatorWakeLock.mLastWakeLock.released); + }); + }); + + test('should not add app if not in updatableApps array', function() { + var updatableApp = new MockAppUpdatable(new MockApp); + var initialLength = UpdateManager.downloadsQueue.length; + UpdateManager.addToDownloadsQueue(updatableApp); + assert.equal(initialLength, UpdateManager.downloadsQueue.length); + }); + + test('should not add if already in the array', function() { + UpdateManager.addToDownloadsQueue(updatableApp); + + var initialLength = UpdateManager.downloadsQueue.length; + UpdateManager.addToDownloadsQueue(updatableApp); + assert.equal(initialLength, UpdateManager.downloadsQueue.length); + }); + }); + + suite('removeFromDownloadsQueue', function() { + var updatableApp; + + setup(function() { + var installedApp = new MockApp(); + updatableApp = new MockAppUpdatable(installedApp); + UpdateManager.init(); + + UpdateManager.addToUpdatableApps(updatableApp); + UpdateManager.addToDownloadsQueue(updatableApp); + }); + + test('should remove if in downloadsQueue array', function() { + var initialLength = UpdateManager.downloadsQueue.length; + UpdateManager.removeFromDownloadsQueue(updatableApp); + assert.equal(initialLength - 1, UpdateManager.downloadsQueue.length); + }); + + suite('should switch off downloading mode on last remove', function() { + setup(function() { + UpdateManager.removeFromDownloadsQueue(updatableApp); + }); + + test('should remove css class', function() { + var css = UpdateManager.container.classList; + assert.isFalse(css.contains('downloading')); + }); + + test('should remove statusbar indicator', function() { + var decMethod = 'decSystemDownloads'; + assert.ok(MockStatusBar.wasMethodCalled[decMethod]); + }); + + test('should release the wifi wake lock', function() { + assert.equal('wifi', MockNavigatorWakeLock.mLastWakeLock.topic); + assert.isTrue(MockNavigatorWakeLock.mLastWakeLock.released); + }); + }); + + test('should not break if wifi unlock throws an exception', + function() { + MockNavigatorWakeLock.mThrowAtNextUnlock(); + UpdateManager.removeFromDownloadsQueue(updatableApp); + assert.ok(true); + }); + + test('should remove system updates too', function() { + var systemUpdate = new MockSystemUpdatable(); + UpdateManager.downloadsQueue.push(systemUpdate); + + var initialLength = UpdateManager.downloadsQueue.length; + UpdateManager.removeFromDownloadsQueue(systemUpdate); + assert.equal(initialLength - 1, UpdateManager.downloadsQueue.length); + }); + }); + }); + }); +}); -- cgit v0.9.1