Commit 4c7a794a authored by Phil Hughes's avatar Phil Hughes

Merge branch 'fallback-localstorage-cases' into 'master'

Fallback localstorage cases

Closes #30179 and #25788

See merge request !10937
parents b8153535 bef42d9a
/* eslint-disable func-names, space-before-function-paren, wrap-iife, no-param-reassign, quotes, prefer-template, no-var, one-var, no-unused-vars, one-var-declaration-per-line, no-void, consistent-return, no-empty, max-len */
import AccessorUtilities from './lib/utils/accessor';
window.Autosave = (function() {
function Autosave(field, key) {
this.field = field;
this.isLocalStorageAvailable = AccessorUtilities.isLocalStorageAccessSafe();
if (key.join != null) {
key = key.join("/");
}
......@@ -17,16 +20,12 @@ window.Autosave = (function() {
}
Autosave.prototype.restore = function() {
var e, text;
if (window.localStorage == null) {
return;
}
try {
var text;
if (!this.isLocalStorageAvailable) return;
text = window.localStorage.getItem(this.key);
} catch (error) {
e = error;
return;
}
if ((text != null ? text.length : void 0) > 0) {
this.field.val(text);
}
......@@ -35,27 +34,22 @@ window.Autosave = (function() {
Autosave.prototype.save = function() {
var text;
if (window.localStorage == null) {
return;
}
text = this.field.val();
if ((text != null ? text.length : void 0) > 0) {
try {
if (this.isLocalStorageAvailable && (text != null ? text.length : void 0) > 0) {
return window.localStorage.setItem(this.key, text);
} catch (error) {}
} else {
return this.reset();
}
return this.reset();
};
Autosave.prototype.reset = function() {
if (window.localStorage == null) {
return;
}
try {
if (!this.isLocalStorageAvailable) return;
return window.localStorage.removeItem(this.key);
} catch (error) {}
};
return Autosave;
})();
export default window.Autosave;
import AccessorUtilities from '../../lib/utils/accessor';
const unicodeSupportTestMap = {
// man, student (emojione does not have any of these yet), http://emojipedia.org/emoji-zwj-sequences/
// occupationZwj: '\u{1F468}\u{200D}\u{1F393}',
......@@ -140,17 +142,26 @@ function generateUnicodeSupportMap(testMap) {
function getUnicodeSupportMap() {
let unicodeSupportMap;
const userAgentFromCache = window.localStorage.getItem('gl-emoji-user-agent');
let userAgentFromCache;
const isLocalStorageAvailable = AccessorUtilities.isLocalStorageAccessSafe();
if (isLocalStorageAvailable) userAgentFromCache = window.localStorage.getItem('gl-emoji-user-agent');
try {
unicodeSupportMap = JSON.parse(window.localStorage.getItem('gl-emoji-unicode-support-map'));
} catch (err) {
// swallow
}
if (!unicodeSupportMap || userAgentFromCache !== navigator.userAgent) {
unicodeSupportMap = generateUnicodeSupportMap(unicodeSupportTestMap);
if (isLocalStorageAvailable) {
window.localStorage.setItem('gl-emoji-user-agent', navigator.userAgent);
window.localStorage.setItem('gl-emoji-unicode-support-map', JSON.stringify(unicodeSupportMap));
}
}
return unicodeSupportMap;
}
......
......@@ -8,6 +8,11 @@ export default {
type: Array,
required: true,
},
isLocalStorageAvailable: {
type: Boolean,
required: false,
default: true,
},
},
computed: {
......@@ -47,7 +52,12 @@ export default {
template: `
<div>
<ul v-if="hasItems">
<div
v-if="!isLocalStorageAvailable"
class="dropdown-info-note">
This feature requires local storage to be enabled
</div>
<ul v-else-if="hasItems">
<li
v-for="(item, index) in processedItems"
:key="index">
......
/* global Flash */
import FilteredSearchContainer from './container';
import RecentSearchesRoot from './recent_searches_root';
import RecentSearchesStore from './stores/recent_searches_store';
......@@ -15,7 +13,9 @@ class FilteredSearchManager {
this.tokensContainer = this.container.querySelector('.tokens-container');
this.filteredSearchTokenKeys = gl.FilteredSearchTokenKeys;
this.recentSearchesStore = new RecentSearchesStore();
this.recentSearchesStore = new RecentSearchesStore({
isLocalStorageAvailable: RecentSearchesService.isAvailable(),
});
let recentSearchesKey = 'issue-recent-searches';
if (page === 'merge_requests') {
recentSearchesKey = 'merge-request-recent-searches';
......@@ -24,9 +24,10 @@ class FilteredSearchManager {
// Fetch recent searches from localStorage
this.fetchingRecentSearchesPromise = this.recentSearchesService.fetch()
.catch(() => {
.catch((error) => {
if (error.name === 'RecentSearchesServiceError') return undefined;
// eslint-disable-next-line no-new
new Flash('An error occured while parsing recent searches');
new window.Flash('An error occured while parsing recent searches');
// Gracefully fail to empty array
return [];
})
......
......@@ -183,6 +183,9 @@ class FilteredSearchVisualTokens {
static moveInputToTheRight() {
const input = FilteredSearchContainer.container.querySelector('.filtered-search');
if (!input) return;
const inputLi = input.parentElement;
const tokenContainer = FilteredSearchContainer.container.querySelector('.tokens-container');
......
......@@ -29,12 +29,15 @@ class RecentSearchesRoot {
}
render() {
const state = this.store.state;
this.vm = new Vue({
el: this.wrapperElement,
data: this.store.state,
data() { return state; },
template: `
<recent-searches-dropdown-content
:items="recentSearches" />
:items="recentSearches"
:is-local-storage-available="isLocalStorageAvailable"
/>
`,
components: {
'recent-searches-dropdown-content': RecentSearchesDropdownContent,
......
import RecentSearchesServiceError from './recent_searches_service_error';
import AccessorUtilities from '../../lib/utils/accessor';
class RecentSearchesService {
constructor(localStorageKey = 'issuable-recent-searches') {
this.localStorageKey = localStorageKey;
}
fetch() {
if (!RecentSearchesService.isAvailable()) {
const error = new RecentSearchesServiceError();
return Promise.reject(error);
}
const input = window.localStorage.getItem(this.localStorageKey);
let searches = [];
......@@ -19,8 +27,14 @@ class RecentSearchesService {
}
save(searches = []) {
if (!RecentSearchesService.isAvailable()) return;
window.localStorage.setItem(this.localStorageKey, JSON.stringify(searches));
}
static isAvailable() {
return AccessorUtilities.isLocalStorageAccessSafe();
}
}
export default RecentSearchesService;
class RecentSearchesServiceError {
constructor(message) {
this.name = 'RecentSearchesServiceError';
this.message = message || 'Recent Searches Service is unavailable';
}
}
// Can't use `extends` for builtin prototypes and get true inheritance yet
RecentSearchesServiceError.prototype = Error.prototype;
export default RecentSearchesServiceError;
function isPropertyAccessSafe(base, property) {
let safe;
try {
safe = !!base[property];
} catch (error) {
safe = false;
}
return safe;
}
function isFunctionCallSafe(base, functionName, ...args) {
let safe = true;
try {
base[functionName](...args);
} catch (error) {
safe = false;
}
return safe;
}
function isLocalStorageAccessSafe() {
let safe;
const TEST_KEY = 'isLocalStorageAccessSafe';
const TEST_VALUE = 'true';
safe = isPropertyAccessSafe(window, 'localStorage');
if (!safe) return safe;
safe = isFunctionCallSafe(window.localStorage, 'setItem', TEST_KEY, TEST_VALUE);
if (safe) window.localStorage.removeItem(TEST_KEY);
return safe;
}
const AccessorUtilities = {
isPropertyAccessSafe,
isFunctionCallSafe,
isLocalStorageAccessSafe,
};
export default AccessorUtilities;
/* eslint no-param-reassign: ["error", { "props": false }]*/
/* eslint no-new: "off" */
import AccessorUtilities from './lib/utils/accessor';
((global) => {
/**
* Memorize the last selected tab after reloading a page.
......@@ -9,6 +11,8 @@
constructor({ currentTabKey = 'current_signin_tab', tabSelector = 'ul.nav-tabs' } = {}) {
this.currentTabKey = currentTabKey;
this.tabSelector = tabSelector;
this.isLocalStorageAvailable = AccessorUtilities.isLocalStorageAccessSafe();
this.bootstrap();
}
......@@ -37,11 +41,15 @@
}
saveData(val) {
localStorage.setItem(this.currentTabKey, val);
if (!this.isLocalStorageAvailable) return undefined;
return window.localStorage.setItem(this.currentTabKey, val);
}
readData() {
return localStorage.getItem(this.currentTabKey);
if (!this.isLocalStorageAvailable) return null;
return window.localStorage.getItem(this.currentTabKey);
}
}
......
import Autosave from '~/autosave';
import AccessorUtilities from '~/lib/utils/accessor';
describe('Autosave', () => {
let autosave;
describe('class constructor', () => {
const key = 'key';
const field = jasmine.createSpyObj('field', ['data', 'on']);
beforeEach(() => {
spyOn(AccessorUtilities, 'isLocalStorageAccessSafe').and.returnValue(true);
spyOn(Autosave.prototype, 'restore');
autosave = new Autosave(field, key);
});
it('should set .isLocalStorageAvailable', () => {
expect(AccessorUtilities.isLocalStorageAccessSafe).toHaveBeenCalled();
expect(autosave.isLocalStorageAvailable).toBe(true);
});
});
describe('restore', () => {
const key = 'key';
const field = jasmine.createSpyObj('field', ['trigger']);
beforeEach(() => {
autosave = {
field,
key,
};
spyOn(window.localStorage, 'getItem');
});
describe('if .isLocalStorageAvailable is `false`', () => {
beforeEach(() => {
autosave.isLocalStorageAvailable = false;
Autosave.prototype.restore.call(autosave);
});
it('should not call .getItem', () => {
expect(window.localStorage.getItem).not.toHaveBeenCalled();
});
});
describe('if .isLocalStorageAvailable is `true`', () => {
beforeEach(() => {
autosave.isLocalStorageAvailable = true;
Autosave.prototype.restore.call(autosave);
});
it('should call .getItem', () => {
expect(window.localStorage.getItem).toHaveBeenCalledWith(key);
});
});
});
describe('save', () => {
const field = jasmine.createSpyObj('field', ['val']);
beforeEach(() => {
autosave = jasmine.createSpyObj('autosave', ['reset']);
autosave.field = field;
field.val.and.returnValue('value');
spyOn(window.localStorage, 'setItem');
});
describe('if .isLocalStorageAvailable is `false`', () => {
beforeEach(() => {
autosave.isLocalStorageAvailable = false;
Autosave.prototype.save.call(autosave);
});
it('should not call .setItem', () => {
expect(window.localStorage.setItem).not.toHaveBeenCalled();
});
});
describe('if .isLocalStorageAvailable is `true`', () => {
beforeEach(() => {
autosave.isLocalStorageAvailable = true;
Autosave.prototype.save.call(autosave);
});
it('should call .setItem', () => {
expect(window.localStorage.setItem).toHaveBeenCalled();
});
});
});
describe('reset', () => {
const key = 'key';
beforeEach(() => {
autosave = {
key,
};
spyOn(window.localStorage, 'removeItem');
});
describe('if .isLocalStorageAvailable is `false`', () => {
beforeEach(() => {
autosave.isLocalStorageAvailable = false;
Autosave.prototype.reset.call(autosave);
});
it('should not call .removeItem', () => {
expect(window.localStorage.removeItem).not.toHaveBeenCalled();
});
});
describe('if .isLocalStorageAvailable is `true`', () => {
beforeEach(() => {
autosave.isLocalStorageAvailable = true;
Autosave.prototype.reset.call(autosave);
});
it('should call .removeItem', () => {
expect(window.localStorage.removeItem).toHaveBeenCalledWith(key);
});
});
});
});
import { getUnicodeSupportMap } from '~/behaviors/gl_emoji/unicode_support_map';
import AccessorUtilities from '~/lib/utils/accessor';
describe('Unicode Support Map', () => {
describe('getUnicodeSupportMap', () => {
const stringSupportMap = 'stringSupportMap';
beforeEach(() => {
spyOn(AccessorUtilities, 'isLocalStorageAccessSafe');
spyOn(window.localStorage, 'getItem');
spyOn(window.localStorage, 'setItem');
spyOn(JSON, 'parse');
spyOn(JSON, 'stringify').and.returnValue(stringSupportMap);
});
describe('if isLocalStorageAvailable is `true`', function () {
beforeEach(() => {
AccessorUtilities.isLocalStorageAccessSafe.and.returnValue(true);
getUnicodeSupportMap();
});
it('should call .getItem and .setItem', () => {
const allArgs = window.localStorage.setItem.calls.allArgs();
expect(window.localStorage.getItem).toHaveBeenCalledWith('gl-emoji-user-agent');
expect(allArgs[0][0]).toBe('gl-emoji-user-agent');
expect(allArgs[0][1]).toBe(navigator.userAgent);
expect(allArgs[1][0]).toBe('gl-emoji-unicode-support-map');
expect(allArgs[1][1]).toBe(stringSupportMap);
});
});
describe('if isLocalStorageAvailable is `false`', function () {
beforeEach(() => {
AccessorUtilities.isLocalStorageAccessSafe.and.returnValue(false);
getUnicodeSupportMap();
});
it('should not call .getItem or .setItem', () => {
expect(window.localStorage.getItem.calls.count()).toBe(1);
expect(window.localStorage.setItem).not.toHaveBeenCalled();
});
});
});
});
......@@ -76,6 +76,26 @@ describe('RecentSearchesDropdownContent', () => {
});
});
describe('if isLocalStorageAvailable is `false`', () => {
let el;
beforeEach(() => {
const props = Object.assign({ isLocalStorageAvailable: false }, propsDataWithItems);
vm = createComponent(props);
el = vm.$el;
});
it('should render an info note', () => {
const note = el.querySelector('.dropdown-info-note');
const items = el.querySelectorAll('.filtered-search-history-dropdown-item');
expect(note).toBeDefined();
expect(note.innerText.trim()).toBe('This feature requires local storage to be enabled');
expect(items.length).toEqual(propsDataWithoutItems.items.length);
});
});
describe('computed', () => {
describe('processedItems', () => {
it('with items', () => {
......
import * as recentSearchesStoreSrc from '~/filtered_search/stores/recent_searches_store';
import RecentSearchesService from '~/filtered_search/services/recent_searches_service';
import RecentSearchesServiceError from '~/filtered_search/services/recent_searches_service_error';
require('~/lib/utils/url_utility');
require('~/lib/utils/common_utils');
require('~/filtered_search/filtered_search_token_keys');
......@@ -60,6 +64,36 @@ describe('Filtered Search Manager', () => {
manager.cleanup();
});
describe('class constructor', () => {
const isLocalStorageAvailable = 'isLocalStorageAvailable';
let filteredSearchManager;
beforeEach(() => {
spyOn(RecentSearchesService, 'isAvailable').and.returnValue(isLocalStorageAvailable);
spyOn(recentSearchesStoreSrc, 'default');
filteredSearchManager = new gl.FilteredSearchManager();
return filteredSearchManager;
});
it('should instantiate RecentSearchesStore with isLocalStorageAvailable', () => {
expect(RecentSearchesService.isAvailable).toHaveBeenCalled();
expect(recentSearchesStoreSrc.default).toHaveBeenCalledWith({
isLocalStorageAvailable,
});
});
it('should not instantiate Flash if an RecentSearchesServiceError is caught', () => {
spyOn(RecentSearchesService.prototype, 'fetch').and.callFake(() => Promise.reject(new RecentSearchesServiceError()));
spyOn(window, 'Flash');
filteredSearchManager = new gl.FilteredSearchManager();
expect(window.Flash).not.toHaveBeenCalled();
});
});
describe('search', () => {
const defaultParams = '?scope=all&utf8=%E2%9C%93&state=opened';
......
import RecentSearchesRoot from '~/filtered_search/recent_searches_root';
import * as vueSrc from 'vue';
describe('RecentSearchesRoot', () => {
describe('render', () => {
let recentSearchesRoot;
let data;
let template;
beforeEach(() => {
recentSearchesRoot = {
store: {
state: 'state',
},
};
spyOn(vueSrc, 'default').and.callFake((options) => {
data = options.data;
template = options.template;
});
RecentSearchesRoot.prototype.render.call(recentSearchesRoot);
});
it('should instantiate Vue', () => {
expect(vueSrc.default).toHaveBeenCalled();
expect(data()).toBe(recentSearchesRoot.store.state);
expect(template).toContain(':is-local-storage-available="isLocalStorageAvailable"');
});
});
});
import RecentSearchesServiceError from '~/filtered_search/services/recent_searches_service_error';
describe('RecentSearchesServiceError', () => {
let recentSearchesServiceError;
beforeEach(() => {
recentSearchesServiceError = new RecentSearchesServiceError();
});
it('instantiates an instance of RecentSearchesServiceError and not an Error', () => {
expect(recentSearchesServiceError).toEqual(jasmine.any(RecentSearchesServiceError));
expect(recentSearchesServiceError.name).toBe('RecentSearchesServiceError');
});
it('should set a default message', () => {
expect(recentSearchesServiceError.message).toBe('Recent Searches Service is unavailable');
});
});
/* eslint-disable promise/catch-or-return */
import RecentSearchesService from '~/filtered_search/services/recent_searches_service';
import AccessorUtilities from '~/lib/utils/accessor';
describe('RecentSearchesService', () => {
let service;
......@@ -11,6 +12,10 @@ describe('RecentSearchesService', () => {
});
describe('fetch', () => {
beforeEach(() => {
spyOn(RecentSearchesService, 'isAvailable').and.returnValue(true);
});
it('should default to empty array', (done) => {
const fetchItemsPromise = service.fetch();
......@@ -29,7 +34,17 @@ describe('RecentSearchesService', () => {
const fetchItemsPromise = service.fetch();
fetchItemsPromise
.catch(() => {
.catch((error) => {
expect(error).toEqual(jasmine.any(SyntaxError));
done();
});
});
it('should reject when service is unavailable', (done) => {
RecentSearchesService.isAvailable.and.returnValue(false);
service.fetch().catch((error) => {
expect(error).toEqual(jasmine.any(Error));
done();
});
});
......@@ -44,15 +59,89 @@ describe('RecentSearchesService', () => {
done();
});
});
describe('if .isAvailable returns `false`', () => {
beforeEach(() => {
RecentSearchesService.isAvailable.and.returnValue(false);
spyOn(window.localStorage, 'getItem');
RecentSearchesService.prototype.fetch();
});
it('should not call .getItem', () => {
expect(window.localStorage.getItem).not.toHaveBeenCalled();
});
});
});
describe('setRecentSearches', () => {
beforeEach(() => {
spyOn(RecentSearchesService, 'isAvailable').and.returnValue(true);
});
it('should save things in localStorage', () => {
const items = ['foo', 'bar'];
service.save(items);
const newLocalStorageValue =
window.localStorage.getItem(service.localStorageKey);
const newLocalStorageValue = window.localStorage.getItem(service.localStorageKey);
expect(JSON.parse(newLocalStorageValue)).toEqual(items);
});
});
describe('save', () => {
beforeEach(() => {
spyOn(window.localStorage, 'setItem');
spyOn(RecentSearchesService, 'isAvailable');
});
describe('if .isAvailable returns `true`', () => {
const searchesString = 'searchesString';
const localStorageKey = 'localStorageKey';
const recentSearchesService = {
localStorageKey,
};
beforeEach(() => {
RecentSearchesService.isAvailable.and.returnValue(true);
spyOn(JSON, 'stringify').and.returnValue(searchesString);
RecentSearchesService.prototype.save.call(recentSearchesService);
});
it('should call .setItem', () => {
expect(window.localStorage.setItem).toHaveBeenCalledWith(localStorageKey, searchesString);
});
});
describe('if .isAvailable returns `false`', () => {
beforeEach(() => {
RecentSearchesService.isAvailable.and.returnValue(false);
RecentSearchesService.prototype.save();
});
it('should not call .setItem', () => {
expect(window.localStorage.setItem).not.toHaveBeenCalled();
});
});
});
describe('isAvailable', () => {
let isAvailable;
beforeEach(() => {
spyOn(AccessorUtilities, 'isLocalStorageAccessSafe').and.callThrough();
isAvailable = RecentSearchesService.isAvailable();
});
it('should call .isLocalStorageAccessSafe', () => {
expect(AccessorUtilities.isLocalStorageAccessSafe).toHaveBeenCalled();
});
it('should return a boolean', () => {
expect(typeof isAvailable).toBe('boolean');
});
});
});
import AccessorUtilities from '~/lib/utils/accessor';
describe('AccessorUtilities', () => {
const testError = new Error('test error');
describe('isPropertyAccessSafe', () => {
let base;
it('should return `true` if access is safe', () => {
base = { testProp: 'testProp' };
expect(AccessorUtilities.isPropertyAccessSafe(base, 'testProp')).toBe(true);
});
it('should return `false` if access throws an error', () => {
base = { get testProp() { throw testError; } };
expect(AccessorUtilities.isPropertyAccessSafe(base, 'testProp')).toBe(false);
});
it('should return `false` if property is undefined', () => {
base = {};
expect(AccessorUtilities.isPropertyAccessSafe(base, 'testProp')).toBe(false);
});
});
describe('isFunctionCallSafe', () => {
const base = {};
it('should return `true` if calling is safe', () => {
base.func = () => {};
expect(AccessorUtilities.isFunctionCallSafe(base, 'func')).toBe(true);
});
it('should return `false` if calling throws an error', () => {
base.func = () => { throw new Error('test error'); };
expect(AccessorUtilities.isFunctionCallSafe(base, 'func')).toBe(false);
});
it('should return `false` if function is undefined', () => {
base.func = undefined;
expect(AccessorUtilities.isFunctionCallSafe(base, 'func')).toBe(false);
});
});
describe('isLocalStorageAccessSafe', () => {
beforeEach(() => {
spyOn(window.localStorage, 'setItem');
spyOn(window.localStorage, 'removeItem');
});
it('should return `true` if access is safe', () => {
expect(AccessorUtilities.isLocalStorageAccessSafe()).toBe(true);
});
it('should return `false` if access to .setItem isnt safe', () => {
window.localStorage.setItem.and.callFake(() => { throw testError; });
expect(AccessorUtilities.isLocalStorageAccessSafe()).toBe(false);
});
it('should set a test item if access is safe', () => {
AccessorUtilities.isLocalStorageAccessSafe();
expect(window.localStorage.setItem).toHaveBeenCalledWith('isLocalStorageAccessSafe', 'true');
});
it('should remove the test item if access is safe', () => {
AccessorUtilities.isLocalStorageAccessSafe();
expect(window.localStorage.removeItem).toHaveBeenCalledWith('isLocalStorageAccessSafe');
});
});
});
import AccessorUtilities from '~/lib/utils/accessor';
require('~/signin_tabs_memoizer');
((global) => {
......@@ -19,6 +21,8 @@ require('~/signin_tabs_memoizer');
beforeEach(() => {
loadFixtures(fixtureTemplate);
spyOn(AccessorUtilities, 'isLocalStorageAccessSafe').and.returnValue(true);
});
it('does nothing if no tab was previously selected', () => {
......@@ -49,5 +53,91 @@ require('~/signin_tabs_memoizer');
expect(memo.readData()).toEqual('#standard');
});
describe('class constructor', () => {
beforeEach(() => {
memo = createMemoizer();
});
it('should set .isLocalStorageAvailable', () => {
expect(AccessorUtilities.isLocalStorageAccessSafe).toHaveBeenCalled();
expect(memo.isLocalStorageAvailable).toBe(true);
});
});
describe('saveData', () => {
beforeEach(() => {
memo = {
currentTabKey,
};
spyOn(localStorage, 'setItem');
});
describe('if .isLocalStorageAvailable is `false`', () => {
beforeEach(function () {
memo.isLocalStorageAvailable = false;
global.ActiveTabMemoizer.prototype.saveData.call(memo);
});
it('should not call .setItem', () => {
expect(localStorage.setItem).not.toHaveBeenCalled();
});
});
describe('if .isLocalStorageAvailable is `true`', () => {
const value = 'value';
beforeEach(function () {
memo.isLocalStorageAvailable = true;
global.ActiveTabMemoizer.prototype.saveData.call(memo, value);
});
it('should call .setItem', () => {
expect(localStorage.setItem).toHaveBeenCalledWith(currentTabKey, value);
});
});
});
describe('readData', () => {
const itemValue = 'itemValue';
let readData;
beforeEach(() => {
memo = {
currentTabKey,
};
spyOn(localStorage, 'getItem').and.returnValue(itemValue);
});
describe('if .isLocalStorageAvailable is `false`', () => {
beforeEach(function () {
memo.isLocalStorageAvailable = false;
readData = global.ActiveTabMemoizer.prototype.readData.call(memo);
});
it('should not call .getItem and should return `null`', () => {
expect(localStorage.getItem).not.toHaveBeenCalled();
expect(readData).toBe(null);
});
});
describe('if .isLocalStorageAvailable is `true`', () => {
beforeEach(function () {
memo.isLocalStorageAvailable = true;
readData = global.ActiveTabMemoizer.prototype.readData.call(memo);
});
it('should call .getItem and return the localStorage value', () => {
expect(window.localStorage.getItem).toHaveBeenCalledWith(currentTabKey);
expect(readData).toBe(itemValue);
});
});
});
});
})(window);
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment