From 0402597137e54bf5c153683cd8c2da1d62d889fb Mon Sep 17 00:00:00 2001 From: Francisco Jordano Date: Wed, 22 Oct 2014 06:59:16 +0100 Subject: [PATCH] Bug 1078381 - Provide confirmation that theme has been changed r=arthurcc --- apps/settings/js/panels/themes/themes.js | 98 ++++++-- .../test/unit/panels/themes/themes_test.js | 122 ++++++++-- build/config/phone/apps-engineering.list | 1 + dev_apps/theme-test-3/index.html | 10 + dev_apps/theme-test-3/manifest.webapp | 11 + .../shared/elements/gaia-theme/gaia-theme.css | 223 ++++++++++++++++++ dev_apps/theme-test-3/wallpaper.json | 3 + 7 files changed, 429 insertions(+), 39 deletions(-) create mode 100644 dev_apps/theme-test-3/index.html create mode 100644 dev_apps/theme-test-3/manifest.webapp create mode 100644 dev_apps/theme-test-3/shared/elements/gaia-theme/gaia-theme.css create mode 100644 dev_apps/theme-test-3/wallpaper.json diff --git a/apps/settings/js/panels/themes/themes.js b/apps/settings/js/panels/themes/themes.js index a10372f08e1f..afa7d380f454 100644 --- a/apps/settings/js/panels/themes/themes.js +++ b/apps/settings/js/panels/themes/themes.js @@ -15,6 +15,7 @@ define(function(require) { _container: null, _settings: null, _selectedTheme: null, + _previousTheme: null, _themes: null, _config: {}, @@ -62,26 +63,85 @@ define(function(require) { updateRadioButtons: function th_updateRadioButtons() { var currentSetting = SettingsCache.cache; - var theme = this._selectedTheme = currentSetting[THEME_SELECTED]; - var rule = 'input[value="' + theme + '"]'; - var node = this._container.querySelector(rule); - if (node) { - node.checked = true; + if (!currentSetting) { + return; } + var theme = this._selectedTheme = currentSetting[THEME_SELECTED]; + this._updateRow(theme, true); }, - setTheme: function th_setTheme(theme) { + _doSetTheme: function th_doSetTheme(theme) { if (this._selectedTheme === theme) { - return; + return Promise.resolve(); } - var setting = {}; - setting[THEME_SELECTED] = this._selectedTheme = theme; - this._settings.createLock().set(setting) - .onsuccess = (function() { + return new Promise((function(resolve, reject) { + var setting = {}; + this._previousTheme = this._selectedTheme; + setting[THEME_SELECTED] = this._selectedTheme = theme; + var req = this._settings.createLock().set(setting); + req.onsuccess = (function() { this.getWallpaperPath(). then((this.loadWallpaper).bind(this)). - then((this.setWallpaper).bind(this)); + then((this.setWallpaper).bind(this)). + then((this.saveConfig).bind(this)).then(resolve, reject); }).bind(this); + req.onerror = reject; + }).bind(this)); + }, + + setTheme: function th_setTheme(theme) { + this.disableSelection(); + return this._doSetTheme(theme).then(this.enableSelection.bind(this), + this.rollbackTheme.bind(this)); + }, + + /** + * Setting a theme will happen in a two steps process, we will first + * set the theme manifest, and after that we will save any other setting + * related to the new theme. + * If an error happens in any of those two processes, we will need to + * be sure that we enable back the previous theme selection + */ + rollbackTheme: function th_rollbackTheme() { + this._config = {}; + if (this._previousTheme === this._selectedTheme || + this._previousTheme === null) { + return Promise.reject('No previous theme to rollback'); + } + var previous = '' + this._previousTheme; + var current = '' + this._selectedTheme; + return this._doSetTheme(this._previousTheme).then((function() { + this._updateRow(previous, true); + this._updateRow(current, false); + this.enableSelection(); + }).bind(this)); + }, + + _updateRow: function th_updateRow(theme, checked) { + var rule = 'input[value="' + theme + '"]'; + var node = this._container.querySelector(rule); + if (node) { + node.checked = !!checked; + } + }, + + disableSelection: function th_disableSelection() { + var nodes = this._container.querySelectorAll('input:not([value="' + + this._selectedTheme + '"])'); + if (nodes) { + Array.prototype.slice.call(nodes).forEach(function(node) { + node.parentNode.parentNode.classList.add('disabled'); + }); + } + }, + + enableSelection: function th_enableSelection() { + var nodes = this._container.querySelectorAll('input'); + if (nodes) { + Array.prototype.slice.call(nodes).forEach(function(node) { + node.parentNode.parentNode.classList.remove('disabled'); + }); + } }, /** @@ -95,6 +155,7 @@ define(function(require) { var xhr = new XMLHttpRequest({ mozSystem: true }); xhr.open('GET', url, true); + xhr.responseType = 'json'; return new Promise(function(resolve, reject) { xhr.onload = function successGettingWallpaperList() { @@ -134,11 +195,20 @@ define(function(require) { }, /** - * Saves current wallpaper configuration. - * @returns {Promise} fulfilled when config is saved. + * Saves in memory the configuration for wallpaper. saveConfig + * will need to be invoked to make this changes permanent. + * @returns {Promise} fulfilled inmediately. */ setWallpaper: function th_setWallpaper(blob) { this._config[WALLPAPER_KEY] = blob; + return Promise.resolve(this._config); + }, + + /** + * Perform the operation to writing to settings + * @returns {Promise} fulfilled when config is saved + */ + saveConfig: function th_saveConfig() { var self = this; return new Promise(function(resolve, reject) { var request = self._settings.createLock().set(self._config); diff --git a/apps/settings/test/unit/panels/themes/themes_test.js b/apps/settings/test/unit/panels/themes/themes_test.js index 6808ea21a8e4..0e7be9058eef 100644 --- a/apps/settings/test/unit/panels/themes/themes_test.js +++ b/apps/settings/test/unit/panels/themes/themes_test.js @@ -65,6 +65,15 @@ suite('Themes > ', function() { manifestURL: 'app://mythemeapp/manifest.webapp' }; + var mock_app7 = { + manifest: { + name: 'My faulty Theme', + type: 'privileged', + role: 'theme' + }, + manifestURL: 'app://myfaultythemeapp/manifest.webapp' + }; + var modules = [ 'panels/themes/themes', 'unit/mock_apps_cache' @@ -78,6 +87,25 @@ suite('Themes > ', function() { } }; + var wallpaper = { + 'homescreen': '/path/to/wallpaper.png' + }; + + var fakeResponses = { + 'app://mythemeapp/wallpaper.json': { + 'status': 200, + 'response': wallpaper + }, + 'app://mythemeapp/path/to/wallpaper.png': { + 'status': 200, + 'response': 'FAKEBLOB' + }, + 'app://myfaultythemeapp/path/to/wallpaper.png': { + 'status': 402, + 'response': 'error' + } + }; + suiteSetup(function(done) { selectedTheme = mock_app2.manifestURL; realNavigatorSettings = navigator.mozSettings; @@ -102,7 +130,8 @@ suite('Themes > ', function() { }); testRequire(modules, maps, function(Themes, AppsCache) { - AppsCache._apps = [mock_app1, mock_app2, mock_app3, mock_app4, mock_app5]; + AppsCache._apps = [mock_app1, mock_app2, mock_app3, mock_app4, mock_app5, + mock_app6, mock_app7]; mockManifestHelper = MockManifestHelper; themes = Themes(); themes.onInit(document.body); @@ -146,24 +175,26 @@ suite('Themes > ', function() { }); }); - suite('Themes wallpaper >', function() { + suite('Themes manipulation >', function() { var realXHR = window.XMLHttpRequest; - var currentResponse; var lastXHRCall; - var wallpaper = { - 'homescreen': '/path/to/wallpaper.png' - }; suiteSetup(function() { window.XMLHttpRequest = function(){ this.status = 0; }; window.XMLHttpRequest.prototype.send = function() { - this.status = 200; - this.response = currentResponse; + var response = fakeResponses[this.url]; + if (response) { + this.status = 400; + } else { + this.status = response.status; + this.response = response.response; + } this.onload(); }; window.XMLHttpRequest.prototype.open = function(method, url) { + this.url = url; lastXHRCall = url; }; @@ -174,27 +205,68 @@ suite('Themes > ', function() { window.XMLHttpRequest = realXHR; }); - test('Getting a wallpaper path', function(done) { - currentResponse = wallpaper; - themes.getWallpaperPath().then(function(path) { - assert.equal(lastXHRCall, 'app://mythemeapp/wallpaper.json'); - assert.equal(path, currentResponse.homescreen); - }, function() {}).then(done, done); + suite('set Theme > ', function() { + setup(function() { + themes._selectedTheme = mock_app5.manifestURL; + }); + + test('setting a theme', function(done) { + themes.setTheme(mock_app6.manifestURL).then(function() { + assert.equal(navigator.mozSettings.mSettings['theme.selected'], + mock_app6.manifestURL); + assert.equal(themes._selectedTheme, mock_app6.manifestURL); + }, function() {}).then(done, done); + }); }); - test('Getting wallpaper blob', function(done) { - currentResponse = 'FAKEBLOB'; - themes.loadWallpaper(wallpaper.homescreen).then(function(blob) { - assert.equal('FAKEBLOB', blob); - assert.equal(lastXHRCall, 'app://mythemeapp/path/to/wallpaper.png'); - }, function() {}).then(done, done); + suite('rollback Theme >', function() { + setup(function(done) { + themes.setTheme(mock_app6.manifestURL).then(function() {}, + function() {}).then(done, done); + }); + test('a faulty theme rollback to the previous theme', function(done) { + this.sinon.spy(themes.rollbackTheme); + themes.setTheme(mock_app7.manifestURL).then(function() { + // We called rollbackTheme and previous theme is selected + assert.isTrue(themes.rollbackTheme.calledOnce); + assert.equal(navigator.mozSettings.mSettings['theme.selected'], + mock_app6.manifestURL); + assert.equal(themes._selectedTheme, mock_app6.manifestURL); + }, function() {}).then(done, done); + }); }); - test('Setting wallpaper', function(done) { - themes.setWallpaper('BLOB').then(function() { - assert.equal(MockNavigatorSettings.mSettings['wallpaper.image'], - 'BLOB'); - }, function() {}).then(done, done); + suite('Wallpaper >', function() { + test('Getting a wallpaper path', function(done) { + themes.getWallpaperPath().then(function(path) { + assert.equal(lastXHRCall, 'app://mythemeapp/wallpaper.json'); + assert.equal(path, wallpaper.homescreen); + }, function() {}).then(done, done); + }); + + test('Getting wallpaper blob', function(done) { + themes._selectedTheme = 'app://mythemeapp/manifest.webapp'; + themes.loadWallpaper(wallpaper.homescreen).then(function(blob) { + assert.equal('FAKEBLOB', blob); + assert.equal(lastXHRCall, 'app://mythemeapp/path/to/wallpaper.png'); + }, function() {}).then(done, done); + }); + + test('Setting wallpaper', function(done) { + themes.setWallpaper('BLOB').then(function(data) { + assert.equal(data['wallpaper.image'], + 'BLOB'); + }, function() {}).then(done, done); + }); + + test('Saving data', function(done) { + this.sinon.spy(themes, 'enableSelection'); + themes.setWallpaper('BLOB').then(themes.saveConfig).then(function() { + assert.equal(MockNavigatorSettings.mSettings['wallpaper.image'], + 'BLOB'); + assert.isTrue(themes.enableSelection.calledOnce); + }, function() {}).then(done, done); + }); }); }); }); diff --git a/build/config/phone/apps-engineering.list b/build/config/phone/apps-engineering.list index 07b1dc105494..b91f017018b5 100644 --- a/build/config/phone/apps-engineering.list +++ b/build/config/phone/apps-engineering.list @@ -36,3 +36,4 @@ dev_apps/demo-hci-event dev_apps/test-l10n-optimize dev_apps/theme-test-1 dev_apps/theme-test-2 +dev_apps/theme-test-3 diff --git a/dev_apps/theme-test-3/index.html b/dev_apps/theme-test-3/index.html new file mode 100644 index 000000000000..7c93e1350b7f --- /dev/null +++ b/dev_apps/theme-test-3/index.html @@ -0,0 +1,10 @@ + + + + + Broken Theme 3 + + + + + diff --git a/dev_apps/theme-test-3/manifest.webapp b/dev_apps/theme-test-3/manifest.webapp new file mode 100644 index 000000000000..1e4d817cecec --- /dev/null +++ b/dev_apps/theme-test-3/manifest.webapp @@ -0,0 +1,11 @@ +{ + "name": "Broken theme 3", + "description": "Test theme with pink wallpaper", + "launch_path": "/index.html", + "type": "certified", + "role": "theme", + "developer": { + "name": "The Gaia Team", + "url": "https://github.com/mozilla-b2g/gaia" + } +} diff --git a/dev_apps/theme-test-3/shared/elements/gaia-theme/gaia-theme.css b/dev_apps/theme-test-3/shared/elements/gaia-theme/gaia-theme.css new file mode 100644 index 000000000000..b37edb52947f --- /dev/null +++ b/dev_apps/theme-test-3/shared/elements/gaia-theme/gaia-theme.css @@ -0,0 +1,223 @@ +/* +Gaia theme variables +*/ + + +:root { + + /** Grey Colors + ---------------------------------------------------------*/ + + --color-alpha: #333333; + --color-beta: #ffffff; + --color-gamma: #4d4d4d; + --color-delta: #5f5f5f; + --color-epsilon: #858585; + --color-zeta: #a6a6a6; + --color-eta: #c7c7c7; + --color-theta: #e7e7e7; + --color-iota: #f4f4f4; + +/** Brand Colors + ---------------------------------------------------------*/ + + --color-darkblue: #00539f; + --color-blue: #00caf2; + --color-turquoise: #27c8c2; + --color-darkorange: #e66000; + --color-orange: #ff9500; + --color-yellow: #ffcb00; + --color-violet: #c40c84; + + --color-warning: #fbbd3c; + --color-destructive: #e2443a; + --color-preffered: #00ba91; + + /** Background + ---------------------------------------------------------*/ + + --background: var(--color-beta); + --background-plus: var(--color-theta); + --background-minus: var(--color-zeta); + + /** Text Color + ---------------------------------------------------------*/ + + --text-color: var(--color-gamma); + --text-color-minus: var(--color-eta); + + /** Button + ---------------------------------------------------------*/ + + --button-background: var(--color-iota); + + /** Links + ---------------------------------------------------------*/ + + --link-color: var(--highlight-color); + + /** Inputs + ---------------------------------------------------------*/ + + --input-background: var(--color-beta); + --input-color: var(--color-alpha); + --input-clear-background: #909ca7; + + /** Buttons + ---------------------------------------------------------*/ + + --button-box-shadow: + inset 0 -1px 0 #d8d8d8, + inset 0 1px 0 #eeeeee; + --button-box-shadow-active: var(--button-box-shadow); +} + + +.theme-productivity { + + /** Brand Color + ---------------------------------------------------------*/ + + --highlight-color: var(--color-orange); + + /** Header + ---------------------------------------------------------*/ + + --header-background: var(--highlight-color); + --header-color: var(--color-beta); +} + +.theme-communications { + + /** Brand Color + ---------------------------------------------------------*/ + + --highlight-color: var(--color-violet); + + /** Links + ---------------------------------------------------------*/ + + --link-color: #177874; + + /** Header + ---------------------------------------------------------*/ + + --header-background: var(--highlight-color); + --header-color: var(--color-beta); + --header-icon-color: var(--color-beta); + --header-button-color: var(--link-color); +} + +.theme-settings { + + /** Background + ---------------------------------------------------------*/ + + --background: var(--color-iota); + --background-plus: var(--color-beta); + --background-minus: var(--color-epsilon); + --background-minus-minus: var(--color-zeta); + + /** Highlight + ---------------------------------------------------------*/ + + --highlight-color: var(--color-blue); + + /** Borders + ---------------------------------------------------------*/ + + --border-color: var(--color-theta); + + /** Titles + ---------------------------------------------------------*/ + + --title-color: var(--background-minus); + + /** Buttons + ---------------------------------------------------------*/ + + --button-background: var(--background-plus); + + /** Switch + ---------------------------------------------------------*/ + + --switch-background: var(--background-minus-minus); + + /** Checkbox + ---------------------------------------------------------*/ + + --checkbox-border-color: var(--background-minus-minus); + + /** Header + ---------------------------------------------------------*/ + + --header-color: var(--title-color); + --header-background: var(--background); + --header-button-color: var(--highlight-color); + --header-action-button-color: var(--background-minus); +} + +.theme-media { + + /** Highlight Color + ---------------------------------------------------------*/ + + --highlight-color: var(--color-blue); + + /** Background + ---------------------------------------------------------*/ + + --background: var(--color-alpha); + --background-plus: var(--color-gamma); + --background-minus: #2B2B2B; + --background-minus-minus: #1a1a1a; + + /** Text Color + ---------------------------------------------------------*/ + + --text-color: var(--color-beta); + + /** Button + ---------------------------------------------------------*/ + + --button-background: var(--background-plus); + + /** Header + ---------------------------------------------------------*/ + + --header-background: var(--background); + --header-icon-color: var(--text-color); + --header-button-color: var(--highlight-color); + + /** Borders + ---------------------------------------------------------*/ + + --border-color: var(--color-orange); + + /** Inputs + ---------------------------------------------------------*/ + + --input-background: var(--background-plus); + + /** Text Input + ---------------------------------------------------------*/ + + --text-input-background: var(--background-minus); + + /** Switch + ---------------------------------------------------------*/ + + --switch-head-border-color: var(--color-orange); + --switch-background: var(--background-minus-minus); + + /** Checkbox + ---------------------------------------------------------*/ + + --checkbox-border-color: var(--color-orange); + + /** Buttons + ---------------------------------------------------------*/ + + --button-box-shadow: none; + --button-box-shadow-active: none; +} diff --git a/dev_apps/theme-test-3/wallpaper.json b/dev_apps/theme-test-3/wallpaper.json new file mode 100644 index 000000000000..5d37890a9bc7 --- /dev/null +++ b/dev_apps/theme-test-3/wallpaper.json @@ -0,0 +1,3 @@ +{ + "homescreen": "/wallpapers/Flat_Pink.jpg" +} \ No newline at end of file