From 81ddc5c382c3c9f5c802aecea5ab9ca9cb55b2ec Mon Sep 17 00:00:00 2001 From: Chrystal Maria Griffiths Date: Fri, 18 Sep 2020 12:04:31 +0200 Subject: [PATCH] Merge pull request #3205 from overleaf/ta-as-local-storage New Custom LocalStorage Implementation GitOrigin-RevId: f80fd5f9f24af02690a51cf3c57f61f95b90e98e --- .../js/infrastructure/local-storage.js | 45 ++++++++++++ .../infrastructure/local-storage.test.js | 72 +++++++++++++++++++ 2 files changed, 117 insertions(+) create mode 100644 services/web/frontend/js/infrastructure/local-storage.js create mode 100644 services/web/test/frontend/infrastructure/local-storage.test.js diff --git a/services/web/frontend/js/infrastructure/local-storage.js b/services/web/frontend/js/infrastructure/local-storage.js new file mode 100644 index 0000000000..8e4f0fb6ab --- /dev/null +++ b/services/web/frontend/js/infrastructure/local-storage.js @@ -0,0 +1,45 @@ +/** + * localStorage can throw browser exceptions, for example if it is full We don't + * use localStorage for anything critical, so in that case just fail gracefully. + */ + +/** + * Catch, log and otherwise ignore errors. + * + * @param {function} fn localStorage function to call + * @param {string?} key Key passed to the localStorage function (if any) + * @param {any?} value Value passed to the localStorage function (if any) + */ +const callSafe = function(fn, key, value) { + try { + return fn(key, value) + } catch (e) { + console.error('localStorage exception', e) + return null + } +} + +const getItem = function(key) { + return JSON.parse(localStorage.getItem(key)) +} + +const setItem = function(key, value) { + localStorage.setItem(key, JSON.stringify(value)) +} + +const clear = function() { + localStorage.clear() +} + +const removeItem = function(key) { + return localStorage.removeItem(key) +} + +const customLocalStorage = { + getItem: key => callSafe(getItem, key), + setItem: (key, value) => callSafe(setItem, key, value), + clear: () => callSafe(clear), + removeItem: key => callSafe(removeItem, key) +} + +export default customLocalStorage diff --git a/services/web/test/frontend/infrastructure/local-storage.test.js b/services/web/test/frontend/infrastructure/local-storage.test.js new file mode 100644 index 0000000000..614f0d29ab --- /dev/null +++ b/services/web/test/frontend/infrastructure/local-storage.test.js @@ -0,0 +1,72 @@ +import { expect } from 'chai' +import sinon from 'sinon' + +import customLocalStorage from '../../../frontend/js/infrastructure/local-storage' + +describe('localStorage', function() { + beforeEach(function() { + global.localStorage = { + getItem: sinon.stub().returns(null), + setItem: sinon.stub(), + clear: sinon.stub(), + removeItem: sinon.stub() + } + global.console.error = sinon.stub() + }) + + afterEach(function() { + global.console.error.reset() + delete global.localStorage + }) + + it('getItem', function() { + expect(customLocalStorage.getItem('foo')).to.be.null + + global.localStorage.getItem.returns('false') + expect(customLocalStorage.getItem('foo')).to.equal(false) + + global.localStorage.getItem.returns('{"foo":"bar"}') + expect(customLocalStorage.getItem('foo')).to.deep.equal({ foo: 'bar' }) + + global.localStorage.getItem.throws(new Error('Nope')) + expect(customLocalStorage.getItem('foo')).to.be.null + expect(global.console.error).to.be.calledOnce + }) + + it('setItem', function() { + customLocalStorage.setItem('foo', 'bar') + expect(global.localStorage.setItem).to.be.calledOnceWith('foo', '"bar"') + global.localStorage.setItem.reset() + + customLocalStorage.setItem('foo', true) + expect(global.localStorage.setItem).to.be.calledOnceWith('foo', 'true') + global.localStorage.setItem.reset() + + customLocalStorage.setItem('foo', { bar: 1 }) + expect(global.localStorage.setItem).to.be.calledOnceWith('foo', '{"bar":1}') + global.localStorage.setItem.reset() + + global.localStorage.setItem.throws(new Error('Nope')) + expect(customLocalStorage.setItem('foo', 'bar')).to.be.null + expect(global.console.error).to.be.calledOnce + }) + + it('clear', function() { + customLocalStorage.clear() + expect(global.localStorage.clear).to.be.calledOnce + + global.localStorage.clear.throws(new Error('Nope')) + expect(customLocalStorage.clear()).to.be.null + expect(global.console.error).to.be.calledOnce + }) + + it('removeItem', function() { + customLocalStorage.removeItem('foo') + expect(global.localStorage.removeItem).to.be.calledOnceWith('foo') + global.localStorage.removeItem.reset() + + global.localStorage.removeItem.throws(new Error('Nope')) + expect(customLocalStorage.removeItem('foo')).to.be.null + expect(global.console.error).to.be.calledOnce + }) +})