diff --git a/src/testUtils/__tests__/stub_browser_storage.js b/src/testUtils/__tests__/stub_browser_storage.js new file mode 100644 index 0000000000000..83e4a7440ddd3 --- /dev/null +++ b/src/testUtils/__tests__/stub_browser_storage.js @@ -0,0 +1,85 @@ +import expect from 'expect.js'; + +import StubBrowserStorage from '../stub_browser_storage'; + +describe('StubBrowserStorage', () => { + describe('#getItem() / #setItem()', () => { + it('stores items as strings', () => { + const store = new StubBrowserStorage(); + store.setItem(1, 1); + expect(store.getItem(1)).to.be('1'); + }); + + it('stores keys as strings', () => { + const store = new StubBrowserStorage(); + store.setItem(1, 1); + expect(store.key(0)).to.be('1'); + }); + + it('returns null for missing keys', () => { + const store = new StubBrowserStorage(); + expect(store.getItem('unknown key')).to.be(null); + }); + }); + + describe('#length', () => { + it('reports the number of items stored', () => { + const store = new StubBrowserStorage(); + store.setItem(1, 1); + store.setItem(2, 2); + store.setItem(3, 3); + store.setItem(4, 4); + expect(store).to.have.length(4); + }); + + it('does not trip on items getting reset', () => { + const store = new StubBrowserStorage(); + store.setItem(1, 1); + store.setItem(1, 2); + expect(store).to.have.length(1); + }); + }); + + describe('#key()', () => { + it('returns the key as a specific index', () => { + const store = new StubBrowserStorage(); + store.setItem(1, 2); + expect(store.key(0)).to.be('1'); + expect(store.key(1)).to.be(undefined); + }); + }); + + describe('size limiting', () => { + it('allows limiting the storage size', () => { + const store = new StubBrowserStorage(); + store._setSizeLimit(10); + store.setItem('abc', 'def'); // store size is 6, key.length + val.length + expect(() => { + store.setItem('ghi', 'jkl'); + }).throwError(/quota/); + }); + + it('allows defining the limit as infinity', () => { + const store = new StubBrowserStorage(); + store._setSizeLimit(Infinity); + store.setItem('abc', 'def'); + store.setItem('ghi', 'jkl'); // unlike the previous test, this doesn't throw + }); + + it('requires setting the limit before keys', () => { + const store = new StubBrowserStorage(); + store.setItem('key', 'val'); + expect(() => { + store._setSizeLimit(10); + }).throwError(/before setting/); + }); + + it('respects removed items', () => { + const store = new StubBrowserStorage(); + store._setSizeLimit(10); + store.setItem('abc', 'def'); + store.removeItem('abc'); + store.setItem('ghi', 'jkl'); // unlike the previous test, this doesn't throw + }); + }); +}); diff --git a/src/testUtils/stub_browser_storage.js b/src/testUtils/stub_browser_storage.js new file mode 100644 index 0000000000000..cf628e0eca515 --- /dev/null +++ b/src/testUtils/stub_browser_storage.js @@ -0,0 +1,92 @@ +const keys = Symbol('keys'); +const values = Symbol('values'); +const remainingSize = Symbol('remainingSize'); + +export default class StubBrowserStorage { + constructor() { + this[keys] = []; + this[values] = []; + this[remainingSize] = 5000000; // 5mb, minimum browser storage size + } + + get length() { + return this[keys].length; + } + + key(i) { + return this[keys][i]; + } + + getItem(key) { + key = String(key); + + const i = this[keys].indexOf(key); + if (i === -1) return null; + return this[values][i]; + } + + setItem(key, value) { + key = String(key); + value = String(value); + this._takeUpSpace(this._calcSizeOfAdd(key, value)); + + const i = this[keys].indexOf(key); + if (i === -1) { + this[keys].push(key); + this[values].push(value); + } else { + this[values][i] = value; + } + } + + removeItem(key) { + key = String(key); + this._takeUpSpace(this._calcSizeOfRemove(key)); + + const i = this[keys].indexOf(key); + if (i === -1) return; + this[keys].splice(i, 1); + this[values].splice(i, 1); + } + + // non-standard api methods + _getKeys() { + return this[keys].slice(); + } + + _getValues() { + return this[values].slice(); + } + + _setSizeLimit(limit) { + if (this[keys].length) { + throw new Error('You must call _setSizeLimit() before setting any values'); + } + + this[remainingSize] = limit; + } + + _calcSizeOfAdd(key, value) { + const i = this[keys].indexOf(key); + if (i === -1) { + return key.length + value.length; + } + return value.length - this[values][i].length; + } + + _calcSizeOfRemove(key) { + const i = this[keys].indexOf(key); + if (i === -1) { + return 0; + } + return 0 - (key.length + this[values][i].length); + } + + _takeUpSpace(delta) { + if (this[remainingSize] - delta < 0) { + throw new Error('something about quota exceeded, browsers are not consistent here'); + } + + this[remainingSize] -= delta; + } +} diff --git a/src/ui/public/chrome/__tests__/Tab.js b/src/ui/public/chrome/__tests__/Tab.js index b25995730ec20..c83b1a5a6cbf0 100644 --- a/src/ui/public/chrome/__tests__/Tab.js +++ b/src/ui/public/chrome/__tests__/Tab.js @@ -1,6 +1,6 @@ const Tab = require('../Tab'); const expect = require('expect.js'); -const TabFakeStore = require('./_TabFakeStore'); +const StubBrowserStorage = require('testUtils/stub_browser_storage'); describe('Chrome Tab', function () { describe('construction', function () { @@ -87,7 +87,7 @@ describe('Chrome Tab', function () { }); it('discovers the lastUrl', function () { - const lastUrlStore = new TabFakeStore(); + const lastUrlStore = new StubBrowserStorage(); const tab = new Tab({ id: 'foo', lastUrlStore }); expect(tab.lastUrl).to.not.equal('bar'); @@ -102,7 +102,7 @@ describe('Chrome Tab', function () { describe('#setLastUrl()', function () { it('updates the lastUrl and storage value if passed a lastUrlStore', function () { - const lastUrlStore = new TabFakeStore(); + const lastUrlStore = new StubBrowserStorage(); const tab = new Tab({ id: 'foo', lastUrlStore }); expect(tab.lastUrl).to.not.equal('foo'); diff --git a/src/ui/public/chrome/__tests__/TabCollection.js b/src/ui/public/chrome/__tests__/TabCollection.js index fa12a4454fab8..631c1e5ba553f 100644 --- a/src/ui/public/chrome/__tests__/TabCollection.js +++ b/src/ui/public/chrome/__tests__/TabCollection.js @@ -1,7 +1,7 @@ const expect = require('expect.js'); const { indexBy, random } = require('lodash'); -const TabFakeStore = require('./_TabFakeStore'); +const StubBrowserStorage = require('testUtils/stub_browser_storage'); const TabCollection = require('../TabCollection'); const Tab = require('../Tab'); @@ -54,7 +54,7 @@ describe('Chrome TabCollection', function () { describe('#consumeRouteUpdate()', function () { it('updates the active tab', function () { - const store = new TabFakeStore(); + const store = new StubBrowserStorage(); const baseUrl = `http://localhost:${random(1000, 9999)}`; const tabs = new TabCollection({ store, defaults: { baseUrl } }); tabs.set([ diff --git a/src/ui/public/chrome/__tests__/_TabFakeStore.js b/src/ui/public/chrome/__tests__/_TabFakeStore.js deleted file mode 100644 index 1b8ab81602259..0000000000000 --- a/src/ui/public/chrome/__tests__/_TabFakeStore.js +++ /dev/null @@ -1,9 +0,0 @@ -const store = Symbol('store'); - -export default class TabFakeStore { - constructor() { this[store] = new Map(); } - getItem(k) { return this[store].get(k); } - setItem(k, v) { return this[store].set(k, v); } - getKeys() { return [ ...this[store].keys() ]; } - getValues() { return [ ...this[store].values() ]; } -} diff --git a/src/ui/public/chrome/api/__tests__/apps.js b/src/ui/public/chrome/api/__tests__/apps.js index ddb53ada7186e..4e4d8c9857bd6 100644 --- a/src/ui/public/chrome/api/__tests__/apps.js +++ b/src/ui/public/chrome/api/__tests__/apps.js @@ -1,7 +1,7 @@ const expect = require('expect.js'); const setup = require('../apps'); -const TabFakeStore = require('../../__tests__/_TabFakeStore'); +const StubBrowserStorage = require('testUtils/stub_browser_storage'); describe('Chrome API :: apps', function () { describe('#get/setShowAppsLink()', function () { @@ -147,13 +147,13 @@ describe('Chrome API :: apps', function () { describe('#get/setLastUrlFor()', function () { it('reads/writes last url from storage', function () { const chrome = {}; - const store = new TabFakeStore(); + const store = new StubBrowserStorage(); setup(chrome, { appUrlStore: store }); - expect(chrome.getLastUrlFor('app')).to.equal(undefined); + expect(chrome.getLastUrlFor('app')).to.equal(null); chrome.setLastUrlFor('app', 'url'); expect(chrome.getLastUrlFor('app')).to.equal('url'); - expect(store.getKeys().length).to.equal(1); - expect(store.getValues().shift()).to.equal('url'); + expect(store._getKeys().length).to.equal(1); + expect(store._getValues().shift()).to.equal('url'); }); }); });