This commit is contained in:
Pierre Ossman 2018-02-28 12:57:48 +01:00
commit 8aad8f269c
4 changed files with 205 additions and 15 deletions

View File

@ -721,21 +721,17 @@ var UI = {
if (val === null) { if (val === null) {
val = WebUtil.readSetting(name, defVal); val = WebUtil.readSetting(name, defVal);
} }
UI.updateSetting(name, val); WebUtil.setSetting(name, val);
UI.updateSetting(name);
return val; return val;
}, },
// Update cookie and form control setting. If value is not set, then // Update cookie and form control setting. If value is not set, then
// updates from control to current cookie setting. // updates from control to current cookie setting.
updateSetting: function(name, value) { updateSetting: function(name) {
// Save the cookie for this session
if (typeof value !== 'undefined') {
WebUtil.writeSetting(name, value);
}
// Update the settings control // Update the settings control
value = UI.getSetting(name); var value = UI.getSetting(name);
var ctrl = document.getElementById('noVNC_setting_' + name); var ctrl = document.getElementById('noVNC_setting_' + name);
if (ctrl.type === 'checkbox') { if (ctrl.type === 'checkbox') {

View File

@ -117,21 +117,25 @@ export function initSettings (callback /*, ...callbackArgs */) {
} }
}); });
} else { } else {
// No-op settings = {};
if (callback) { if (callback) {
callback.apply(this, callbackArgs); callback.apply(this, callbackArgs);
} }
} }
}; };
// Update the settings cache, but do not write to permanent storage
export function setSetting (name, value) {
settings[name] = value;
};
// No days means only for this browser session // No days means only for this browser session
export function writeSetting (name, value) { export function writeSetting (name, value) {
"use strict"; "use strict";
if (settings[name] === value) return;
settings[name] = value;
if (window.chrome && window.chrome.storage) { if (window.chrome && window.chrome.storage) {
if (settings[name] !== value) { window.chrome.storage.sync.set(settings);
settings[name] = value;
window.chrome.storage.sync.set(settings);
}
} else { } else {
localStorage.setItem(name, value); localStorage.setItem(name, value);
} }
@ -140,10 +144,11 @@ export function writeSetting (name, value) {
export function readSetting (name, defaultValue) { export function readSetting (name, defaultValue) {
"use strict"; "use strict";
var value; var value;
if (window.chrome && window.chrome.storage) { if ((name in settings) || (window.chrome && window.chrome.storage)) {
value = settings[name]; value = settings[name];
} else { } else {
value = localStorage.getItem(name); value = localStorage.getItem(name);
settings[name] = value;
} }
if (typeof value === "undefined") { if (typeof value === "undefined") {
value = null; value = null;
@ -157,9 +162,14 @@ export function readSetting (name, defaultValue) {
export function eraseSetting (name) { export function eraseSetting (name) {
"use strict"; "use strict";
// Deleting here means that next time the setting is read when using local
// storage, it will be pulled from local storage again.
// If the setting in local storage is changed (e.g. in another tab)
// between this delete and the next read, it could lead to an unexpected
// value change.
delete settings[name];
if (window.chrome && window.chrome.storage) { if (window.chrome && window.chrome.storage) {
window.chrome.storage.sync.remove(name); window.chrome.storage.sync.remove(name);
delete settings[name];
} else { } else {
localStorage.removeItem(name); localStorage.removeItem(name);
} }

View File

@ -62,6 +62,7 @@ module.exports = function(config) {
{ pattern: 'vendor/sinon.js', included: false }, { pattern: 'vendor/sinon.js', included: false },
{ pattern: 'node_modules/sinon-chai/lib/sinon-chai.js', included: false }, { pattern: 'node_modules/sinon-chai/lib/sinon-chai.js', included: false },
{ pattern: 'app/localization.js', included: false }, { pattern: 'app/localization.js', included: false },
{ pattern: 'app/webutil.js', included: false },
{ pattern: 'core/**/*.js', included: false }, { pattern: 'core/**/*.js', included: false },
{ pattern: 'vendor/pako/**/*.js', included: false }, { pattern: 'vendor/pako/**/*.js', included: false },
{ pattern: 'tests/test.*.js', included: false }, { pattern: 'tests/test.*.js', included: false },
@ -92,6 +93,7 @@ module.exports = function(config) {
// available preprocessors: https://npmjs.org/browse/keyword/karma-preprocessor // available preprocessors: https://npmjs.org/browse/keyword/karma-preprocessor
preprocessors: { preprocessors: {
'app/localization.js': ['babel'], 'app/localization.js': ['babel'],
'app/webutil.js': ['babel'],
'core/**/*.js': ['babel'], 'core/**/*.js': ['babel'],
'tests/test.*.js': ['babel'], 'tests/test.*.js': ['babel'],
'tests/fake.*.js': ['babel'], 'tests/fake.*.js': ['babel'],

182
tests/test.webutil.js Normal file
View File

@ -0,0 +1,182 @@
/* jshint expr: true */
var assert = chai.assert;
var expect = chai.expect;
import * as WebUtil from '../app/webutil.js';
import sinon from '../vendor/sinon.js';
describe('WebUtil', function() {
"use strict";
describe('settings', function () {
// on Firefox, localStorage methods cannot be replaced
// localStorage is (currently) mockable on Chrome
// test to see if localStorage is mockable
var mockTest = sinon.spy(window.localStorage, 'setItem');
var canMock = window.localStorage.setItem.getCall instanceof Function;
mockTest.restore();
if (!canMock) {
console.warn('localStorage cannot be mocked');
}
(canMock ? describe : describe.skip)('localStorage', function() {
var chrome = window.chrome;
before(function() {
chrome = window.chrome;
window.chrome = null;
});
after(function() {
window.chrome = chrome;
});
var lsSandbox = sinon.createSandbox();
beforeEach(function() {
lsSandbox.stub(window.localStorage, 'setItem');
lsSandbox.stub(window.localStorage, 'getItem');
lsSandbox.stub(window.localStorage, 'removeItem');
WebUtil.initSettings();
});
afterEach(function() {
lsSandbox.restore();
});
describe('writeSetting', function() {
it('should save the setting value to local storage', function() {
WebUtil.writeSetting('test', 'value');
expect(window.localStorage.setItem).to.have.been.calledWithExactly('test', 'value');
expect(WebUtil.readSetting('test')).to.equal('value');
});
});
describe('setSetting', function() {
it('should update the setting but not save to local storage', function() {
WebUtil.setSetting('test', 'value');
expect(window.localStorage.setItem).to.not.have.been.called;
expect(WebUtil.readSetting('test')).to.equal('value');
});
});
describe('readSetting', function() {
it('should read the setting value from local storage', function() {
localStorage.getItem.returns('value');
expect(WebUtil.readSetting('test')).to.equal('value');
});
it('should return the default value when not in local storage', function() {
expect(WebUtil.readSetting('test', 'default')).to.equal('default');
});
it('should return the cached value even if local storage changed', function() {
localStorage.getItem.returns('value');
expect(WebUtil.readSetting('test')).to.equal('value');
localStorage.getItem.returns('something else');
expect(WebUtil.readSetting('test')).to.equal('value');
});
it('should cache the value even if it is not initially in local storage', function() {
expect(WebUtil.readSetting('test')).to.be.null;
localStorage.getItem.returns('value');
expect(WebUtil.readSetting('test')).to.be.null;
});
it('should return the default value always if the first read was not in local storage', function() {
expect(WebUtil.readSetting('test', 'default')).to.equal('default');
localStorage.getItem.returns('value');
expect(WebUtil.readSetting('test', 'another default')).to.equal('another default');
});
it('should return the last local written value', function() {
localStorage.getItem.returns('value');
expect(WebUtil.readSetting('test')).to.equal('value');
WebUtil.writeSetting('test', 'something else');
expect(WebUtil.readSetting('test')).to.equal('something else');
});
});
// this doesn't appear to be used anywhere
describe('eraseSetting', function() {
it('should remove the setting from local storage', function() {
WebUtil.eraseSetting('test');
expect(window.localStorage.removeItem).to.have.been.calledWithExactly('test');
});
});
});
describe('chrome.storage', function() {
var chrome = window.chrome;
var settings = {};
before(function() {
chrome = window.chrome;
window.chrome = {
storage: {
sync: {
get: function(cb){ cb(settings); },
set: function(){},
remove: function() {}
}
}
};
});
after(function() {
window.chrome = chrome;
});
var csSandbox = sinon.createSandbox();
beforeEach(function() {
settings = {};
csSandbox.spy(window.chrome.storage.sync, 'set');
csSandbox.spy(window.chrome.storage.sync, 'remove');
WebUtil.initSettings();
});
afterEach(function() {
csSandbox.restore();
});
describe('writeSetting', function() {
it('should save the setting value to chrome storage', function() {
WebUtil.writeSetting('test', 'value');
expect(window.chrome.storage.sync.set).to.have.been.calledWithExactly(sinon.match({ test: 'value' }));
expect(WebUtil.readSetting('test')).to.equal('value');
});
});
describe('setSetting', function() {
it('should update the setting but not save to chrome storage', function() {
WebUtil.setSetting('test', 'value');
expect(window.chrome.storage.sync.set).to.not.have.been.called;
expect(WebUtil.readSetting('test')).to.equal('value');
});
});
describe('readSetting', function() {
it('should read the setting value from chrome storage', function() {
settings.test = 'value';
expect(WebUtil.readSetting('test')).to.equal('value');
});
it('should return the default value when not in chrome storage', function() {
expect(WebUtil.readSetting('test', 'default')).to.equal('default');
});
it('should return the last local written value', function() {
settings.test = 'value';
expect(WebUtil.readSetting('test')).to.equal('value');
WebUtil.writeSetting('test', 'something else');
expect(WebUtil.readSetting('test')).to.equal('something else');
});
});
// this doesn't appear to be used anywhere
describe('eraseSetting', function() {
it('should remove the setting from chrome storage', function() {
WebUtil.eraseSetting('test');
expect(window.chrome.storage.sync.remove).to.have.been.calledWithExactly('test');
});
});
});
});
});