Commit 9d83efc3 authored by Miguel Rincon's avatar Miguel Rincon

Rename isLocalStorageAccessSafe to canUseLocalStorage

This change renames the access checks for
window.localStorage to make the intent of the method more
clear.
parent 065adff9
......@@ -6,7 +6,7 @@ export default class Autosave {
constructor(field, key, fallbackKey, lockVersion) {
this.field = field;
this.isLocalStorageAvailable = AccessorUtilities.isLocalStorageAccessSafe();
this.isLocalStorageAvailable = AccessorUtilities.canUseLocalStorage();
if (key.join != null) {
key = key.join('/');
}
......
......@@ -19,7 +19,7 @@ export const LOCAL_STORAGE_KEY = 'gl-keyboard-shortcuts-customizations';
*/
export const getCustomizations = memoize(() => {
let parsedCustomizations = {};
const localStorageIsSafe = AccessorUtilities.isLocalStorageAccessSafe();
const localStorageIsSafe = AccessorUtilities.canUseLocalStorage();
if (localStorageIsSafe) {
try {
......
......@@ -13,7 +13,7 @@ export default {
},
data() {
return {
localStorageUsable: AccessorUtilities.isLocalStorageAccessSafe(),
localStorageUsable: AccessorUtilities.canUseLocalStorage(),
shortcutsEnabled: !shouldDisableShortcuts(),
};
},
......
......@@ -201,7 +201,7 @@ export default {
});
},
addToLocalStorage() {
if (AccessorUtilities.isLocalStorageAccessSafe()) {
if (AccessorUtilities.canUseLocalStorage()) {
localStorage.setItem(`${this.uniqueKey}.collapsed`, this.list.collapsed);
}
},
......
......@@ -218,14 +218,14 @@ export default class Clusters {
}
setBannerDismissedState(status, isDismissed) {
if (AccessorUtilities.isLocalStorageAccessSafe()) {
if (AccessorUtilities.canUseLocalStorage()) {
window.localStorage.setItem(this.clusterBannerDismissedKey, `${status}_${isDismissed}`);
}
}
isBannerDismissed(status) {
let bannerState;
if (AccessorUtilities.isLocalStorageAccessSafe()) {
if (AccessorUtilities.canUseLocalStorage()) {
bannerState = window.localStorage.getItem(this.clusterBannerDismissedKey);
}
......@@ -233,7 +233,7 @@ export default class Clusters {
}
setClusterNewlyCreated(state) {
if (AccessorUtilities.isLocalStorageAccessSafe()) {
if (AccessorUtilities.canUseLocalStorage()) {
window.localStorage.setItem(this.clusterNewlyCreatedKey, Boolean(state));
}
}
......@@ -242,7 +242,7 @@ export default class Clusters {
// once this is true, it will always be true for a given page load
if (!this.isNewlyCreated) {
let newlyCreated;
if (AccessorUtilities.isLocalStorageAccessSafe()) {
if (AccessorUtilities.canUseLocalStorage()) {
newlyCreated = window.localStorage.getItem(this.clusterNewlyCreatedKey);
}
......
......@@ -11,7 +11,7 @@ export const FALLBACK_EMOJI_KEY = 'grey_question';
export const EMOJI_VERSION = '1';
const isLocalStorageAvailable = AccessorUtilities.isLocalStorageAccessSafe();
const isLocalStorageAvailable = AccessorUtilities.canUseLocalStorage();
async function loadEmoji() {
if (
......
......@@ -141,7 +141,7 @@ function generateUnicodeSupportMap(testMap) {
}
export default function getUnicodeSupportMap() {
const isLocalStorageAvailable = AccessorUtilities.isLocalStorageAccessSafe();
const isLocalStorageAvailable = AccessorUtilities.canUseLocalStorage();
let glEmojiVersionFromCache;
let userAgentFromCache;
......
......@@ -118,7 +118,7 @@ export default {
required: true,
},
},
hasLocalStorage: AccessorUtils.isLocalStorageAccessSafe(),
hasLocalStorage: AccessorUtils.canUseLocalStorage(),
data() {
return {
errorSearchQuery: '',
......
......@@ -22,7 +22,7 @@ export default {
// only keep the last 5
state.recentSearches = recentSearches.slice(0, 5);
if (AccessorUtils.isLocalStorageAccessSafe()) {
if (AccessorUtils.canUseLocalStorage()) {
localStorage.setItem(
`recent-searches${state.indexPath}`,
JSON.stringify(state.recentSearches),
......@@ -31,7 +31,7 @@ export default {
},
[types.CLEAR_RECENT_SEARCHES](state) {
state.recentSearches = [];
if (AccessorUtils.isLocalStorageAccessSafe()) {
if (AccessorUtils.canUseLocalStorage()) {
localStorage.removeItem(`recent-searches${state.indexPath}`);
}
},
......
......@@ -33,7 +33,7 @@ class RecentSearchesService {
}
static isAvailable() {
return AccessorUtilities.isLocalStorageAccessSafe();
return AccessorUtilities.canUseLocalStorage();
}
}
......
......@@ -84,7 +84,7 @@ export default {
logItemAccess(storageKey, unsanitizedItem) {
const item = sanitizeItem(unsanitizedItem);
if (!AccessorUtilities.isLocalStorageAccessSafe()) {
if (!AccessorUtilities.canUseLocalStorage()) {
return false;
}
......
......@@ -25,7 +25,7 @@ export const receiveFrequentItemsError = ({ commit }) => {
export const fetchFrequentItems = ({ state, dispatch }) => {
dispatch('requestFrequentItems');
if (AccessorUtilities.isLocalStorageAccessSafe()) {
if (AccessorUtilities.canUseLocalStorage()) {
const storedFrequentItems = JSON.parse(localStorage.getItem(state.storageKey));
dispatch(
......
......@@ -7,7 +7,7 @@ const isFunction = (fn) => typeof fn === 'function';
* Persist alert data to localStorage.
*/
export const persistAlert = ({ title, message, linkUrl, variant } = {}) => {
if (!AccessorUtilities.isLocalStorageAccessSafe()) {
if (!AccessorUtilities.canUseLocalStorage()) {
return;
}
......@@ -19,7 +19,7 @@ export const persistAlert = ({ title, message, linkUrl, variant } = {}) => {
* Return alert data from localStorage.
*/
export const retrieveAlert = () => {
if (!AccessorUtilities.isLocalStorageAccessSafe()) {
if (!AccessorUtilities.canUseLocalStorage()) {
return null;
}
......
function isPropertyAccessSafe(base, property) {
function canAccessProperty(base, property) {
let safe;
try {
......@@ -10,7 +10,7 @@ function isPropertyAccessSafe(base, property) {
return safe;
}
function isFunctionCallSafe(base, functionName, ...args) {
function canCallFunction(base, functionName, ...args) {
let safe = true;
try {
......@@ -22,16 +22,28 @@ function isFunctionCallSafe(base, functionName, ...args) {
return safe;
}
function isLocalStorageAccessSafe() {
/**
* Determines if `window.localStorage` is available and
* can be written to and read from.
*
* Important: This is not a guarantee that
* `localStorage.setItem` will work in all cases.
*
* `setItem` can still throw exceptions and should be
* surrounded with a try/catch where used.
*
* See: https://developer.mozilla.org/en-US/docs/Web/API/Storage/setItem#exceptions
*/
function canUseLocalStorage() {
let safe;
const TEST_KEY = 'isLocalStorageAccessSafe';
const TEST_KEY = 'canUseLocalStorage';
const TEST_VALUE = 'true';
safe = isPropertyAccessSafe(window, 'localStorage');
safe = canAccessProperty(window, 'localStorage');
if (!safe) return safe;
safe = isFunctionCallSafe(window.localStorage, 'setItem', TEST_KEY, TEST_VALUE);
safe = canCallFunction(window.localStorage, 'setItem', TEST_KEY, TEST_VALUE);
if (safe) window.localStorage.removeItem(TEST_KEY);
......@@ -39,9 +51,7 @@ function isLocalStorageAccessSafe() {
}
const AccessorUtilities = {
isPropertyAccessSafe,
isFunctionCallSafe,
isLocalStorageAccessSafe,
canUseLocalStorage,
};
export default AccessorUtilities;
......@@ -8,7 +8,7 @@ export default class SigninTabsMemoizer {
constructor({ currentTabKey = 'current_signin_tab', tabSelector = 'ul.new-session-tabs' } = {}) {
this.currentTabKey = currentTabKey;
this.tabSelector = tabSelector;
this.isLocalStorageAvailable = AccessorUtilities.isLocalStorageAccessSafe();
this.isLocalStorageAvailable = AccessorUtilities.canUseLocalStorage();
// sets selected tab if given as hash tag
if (window.location.hash) {
this.saveData(window.location.hash);
......
......@@ -30,7 +30,7 @@ export default class ProjectSelectComboButton {
}
initLocalStorage() {
const localStorageIsSafe = AccessorUtilities.isLocalStorageAccessSafe();
const localStorageIsSafe = AccessorUtilities.canUseLocalStorage();
if (localStorageIsSafe) {
this.localStorageKey = [
......
......@@ -12,7 +12,7 @@ export default class ProtectedBranchCreate {
this.hasLicense = options.hasLicense;
this.$form = $('.js-new-protected-branch');
this.isLocalStorageAvailable = AccessorUtilities.isLocalStorageAccessSafe();
this.isLocalStorageAvailable = AccessorUtilities.canUseLocalStorage();
this.currentProjectUserDefaults = {};
this.buildDropdowns();
this.$forcePushToggle = this.$form.find('.js-force-push-toggle');
......
......@@ -6,7 +6,7 @@ function extractKeys(object, keyList) {
}
export const loadDataFromLS = (key) => {
if (!AccessorUtilities.isLocalStorageAccessSafe()) {
if (!AccessorUtilities.canUseLocalStorage()) {
return [];
}
......@@ -20,7 +20,7 @@ export const loadDataFromLS = (key) => {
};
export const setFrequentItemToLS = (key, data, itemData) => {
if (!AccessorUtilities.isLocalStorageAccessSafe()) {
if (!AccessorUtilities.canUseLocalStorage()) {
return [];
}
......
......@@ -219,7 +219,7 @@ export function urlQueryToFilter(query = '', { filteredSearchTermKey, filterName
*/
export function getRecentlyUsedSuggestions(recentSuggestionsStorageKey) {
let recentlyUsedSuggestions = [];
if (AccessorUtilities.isLocalStorageAccessSafe()) {
if (AccessorUtilities.canUseLocalStorage()) {
recentlyUsedSuggestions = JSON.parse(localStorage.getItem(recentSuggestionsStorageKey)) || [];
}
return recentlyUsedSuggestions;
......@@ -237,7 +237,7 @@ export function setTokenValueToRecentlyUsed(recentSuggestionsStorageKey, tokenVa
recentlyUsedSuggestions.splice(0, 0, { ...tokenValue });
if (AccessorUtilities.isLocalStorageAccessSafe()) {
if (AccessorUtilities.canUseLocalStorage()) {
localStorage.setItem(
recentSuggestionsStorageKey,
JSON.stringify(uniqWith(recentlyUsedSuggestions, isEqual).slice(0, MAX_RECENT_TOKENS_SIZE)),
......
......@@ -12,7 +12,7 @@ const PROTECTED_ENVIRONMENT_INPUT = 'input[name="protected_environment[name]"]';
export default class ProtectedEnvironmentCreate {
constructor() {
this.$form = $('.js-new-protected-environment');
this.isLocalStorageAvailable = AccessorUtilities.isLocalStorageAccessSafe();
this.isLocalStorageAvailable = AccessorUtilities.canUseLocalStorage();
this.currentProjectUserDefaults = {};
this.buildDropdowns();
this.bindEvents();
......
......@@ -128,7 +128,7 @@ export default {
const storageKey = `${currentUsername}/${STORAGE_KEY.projects}`;
if (!AccessorUtilities.isLocalStorageAccessSafe()) {
if (!AccessorUtilities.canUseLocalStorage()) {
return [];
}
......
......@@ -69,7 +69,7 @@ export default {
},
},
created() {
this.localStorageUsable = AccessorUtilities.isLocalStorageAccessSafe();
this.localStorageUsable = AccessorUtilities.canUseLocalStorage();
if (this.localStorageUsable) {
const shouldHideSummaryDetails = Boolean(localStorage.getItem(LOCAL_STORAGE_KEY));
this.isVisible = !shouldHideSummaryDetails;
......
......@@ -41,7 +41,7 @@ export default {
closePopover() {
this.showPopover = false;
if (AccessorUtils.isLocalStorageAccessSafe()) {
if (AccessorUtils.canUseLocalStorage()) {
localStorage.setItem(STORAGE_KEY, 'true');
}
},
......
......@@ -23,7 +23,7 @@ export default {
[types.SET_PROJECTS](state, projects = []) {
state.projects = projects;
state.isLoadingProjects = false;
if (AccessorUtilities.isLocalStorageAccessSafe()) {
if (AccessorUtilities.canUseLocalStorage()) {
localStorage.setItem(
state.projectEndpoints.list,
state.projects.map((p) => p.id),
......@@ -57,7 +57,7 @@ export default {
},
[types.RECEIVE_PROJECTS_SUCCESS](state, { projects, headers }) {
let projectIds = [];
if (AccessorUtilities.isLocalStorageAccessSafe()) {
if (AccessorUtilities.canUseLocalStorage()) {
projectIds = (localStorage.getItem(state.projectEndpoints.list) || '').split(',');
}
// order Projects by ID, with any unassigned ones added to the end
......@@ -65,7 +65,7 @@ export default {
(a, b) => projectIds.indexOf(a.id.toString()) - projectIds.indexOf(b.id.toString()),
);
state.isLoadingProjects = false;
if (AccessorUtilities.isLocalStorageAccessSafe()) {
if (AccessorUtilities.canUseLocalStorage()) {
localStorage.setItem(
state.projectEndpoints.list,
state.projects.map((p) => p.id),
......
......@@ -33,7 +33,7 @@ describe('Security reports summary component', () => {
const findDownloadLink = () => wrapper.find('[data-testid="download-link"]');
beforeEach(() => {
jest.spyOn(AccessorUtilities, 'isLocalStorageAccessSafe').mockReturnValue(true);
jest.spyOn(AccessorUtilities, 'canUseLocalStorage').mockReturnValue(true);
});
afterEach(() => {
......@@ -177,7 +177,7 @@ describe('Security reports summary component', () => {
describe('when localStorage is unavailable', () => {
beforeEach(() => {
jest.spyOn(AccessorUtilities, 'isLocalStorageAccessSafe').mockReturnValue(false);
jest.spyOn(AccessorUtilities, 'canUseLocalStorage').mockReturnValue(false);
createWrapper();
});
......
......@@ -138,7 +138,7 @@ describe('Csv Button Export', () => {
});
it('sets localStorage', async () => {
jest.spyOn(AccessorUtils, 'isLocalStorageAccessSafe').mockImplementation(() => true);
jest.spyOn(AccessorUtils, 'canUseLocalStorage').mockImplementation(() => true);
findPopoverButton().vm.$emit('click');
await wrapper.vm.$nextTick();
......@@ -146,7 +146,7 @@ describe('Csv Button Export', () => {
});
it(`does not set localStorage if it's not available`, async () => {
jest.spyOn(AccessorUtils, 'isLocalStorageAccessSafe').mockImplementation(() => false);
jest.spyOn(AccessorUtils, 'canUseLocalStorage').mockImplementation(() => false);
findPopoverButton().vm.$emit('click');
await wrapper.vm.$nextTick();
......
......@@ -15,28 +15,28 @@ describe('Autosave', () => {
describe('class constructor', () => {
beforeEach(() => {
jest.spyOn(AccessorUtilities, 'isLocalStorageAccessSafe').mockReturnValue(true);
jest.spyOn(AccessorUtilities, 'canUseLocalStorage').mockReturnValue(true);
jest.spyOn(Autosave.prototype, 'restore').mockImplementation(() => {});
});
it('should set .isLocalStorageAvailable', () => {
autosave = new Autosave(field, key);
expect(AccessorUtilities.isLocalStorageAccessSafe).toHaveBeenCalled();
expect(AccessorUtilities.canUseLocalStorage).toHaveBeenCalled();
expect(autosave.isLocalStorageAvailable).toBe(true);
});
it('should set .isLocalStorageAvailable if fallbackKey is passed', () => {
autosave = new Autosave(field, key, fallbackKey);
expect(AccessorUtilities.isLocalStorageAccessSafe).toHaveBeenCalled();
expect(AccessorUtilities.canUseLocalStorage).toHaveBeenCalled();
expect(autosave.isLocalStorageAvailable).toBe(true);
});
it('should set .isLocalStorageAvailable if lockVersion is passed', () => {
autosave = new Autosave(field, key, null, lockVersion);
expect(AccessorUtilities.isLocalStorageAccessSafe).toHaveBeenCalled();
expect(AccessorUtilities.canUseLocalStorage).toHaveBeenCalled();
expect(autosave.isLocalStorageAvailable).toBe(true);
});
});
......
......@@ -8,14 +8,14 @@ describe('Unicode Support Map', () => {
const stringSupportMap = 'stringSupportMap';
beforeEach(() => {
jest.spyOn(AccessorUtilities, 'isLocalStorageAccessSafe').mockImplementation(() => {});
jest.spyOn(AccessorUtilities, 'canUseLocalStorage').mockImplementation(() => {});
jest.spyOn(JSON, 'parse').mockImplementation(() => {});
jest.spyOn(JSON, 'stringify').mockReturnValue(stringSupportMap);
});
describe('if isLocalStorageAvailable is `true`', () => {
beforeEach(() => {
jest.spyOn(AccessorUtilities, 'isLocalStorageAccessSafe').mockReturnValue(true);
jest.spyOn(AccessorUtilities, 'canUseLocalStorage').mockReturnValue(true);
getUnicodeSupportMap();
});
......@@ -38,7 +38,7 @@ describe('Unicode Support Map', () => {
describe('if isLocalStorageAvailable is `false`', () => {
beforeEach(() => {
jest.spyOn(AccessorUtilities, 'isLocalStorageAccessSafe').mockReturnValue(false);
jest.spyOn(AccessorUtilities, 'canUseLocalStorage').mockReturnValue(false);
getUnicodeSupportMap();
});
......
......@@ -145,13 +145,13 @@ describe('RecentSearchesService', () => {
let isAvailable;
beforeEach(() => {
jest.spyOn(AccessorUtilities, 'isLocalStorageAccessSafe');
jest.spyOn(AccessorUtilities, 'canUseLocalStorage');
isAvailable = RecentSearchesService.isAvailable();
});
it('should call .isLocalStorageAccessSafe', () => {
expect(AccessorUtilities.isLocalStorageAccessSafe).toHaveBeenCalled();
it('should call .canUseLocalStorage', () => {
expect(AccessorUtilities.canUseLocalStorage).toHaveBeenCalled();
});
it('should return a boolean', () => {
......
......@@ -109,7 +109,7 @@ describe('Frequent Items Dropdown Store Actions', () => {
});
it('should dispatch `receiveFrequentItemsError`', (done) => {
jest.spyOn(AccessorUtilities, 'isLocalStorageAccessSafe').mockReturnValue(false);
jest.spyOn(AccessorUtilities, 'canUseLocalStorage').mockReturnValue(false);
mockedState.namespace = mockNamespace;
mockedState.storageKey = mockStorageKey;
......
......@@ -6,60 +6,9 @@ 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', () => {
describe('canUseLocalStorage', () => {
it('should return `true` if access is safe', () => {
expect(AccessorUtilities.isLocalStorageAccessSafe()).toBe(true);
expect(AccessorUtilities.canUseLocalStorage()).toBe(true);
});
it('should return `false` if access to .setItem isnt safe', () => {
......@@ -67,19 +16,19 @@ describe('AccessorUtilities', () => {
throw testError;
});
expect(AccessorUtilities.isLocalStorageAccessSafe()).toBe(false);
expect(AccessorUtilities.canUseLocalStorage()).toBe(false);
});
it('should set a test item if access is safe', () => {
AccessorUtilities.isLocalStorageAccessSafe();
AccessorUtilities.canUseLocalStorage();
expect(window.localStorage.setItem).toHaveBeenCalledWith('isLocalStorageAccessSafe', 'true');
expect(window.localStorage.setItem).toHaveBeenCalledWith('canUseLocalStorage', 'true');
});
it('should remove the test item if access is safe', () => {
AccessorUtilities.isLocalStorageAccessSafe();
AccessorUtilities.canUseLocalStorage();
expect(window.localStorage.removeItem).toHaveBeenCalledWith('isLocalStorageAccessSafe');
expect(window.localStorage.removeItem).toHaveBeenCalledWith('canUseLocalStorage');
});
});
});
......@@ -21,7 +21,7 @@ describe('SigninTabsMemoizer', () => {
beforeEach(() => {
loadFixtures(fixtureTemplate);
jest.spyOn(AccessorUtilities, 'isLocalStorageAccessSafe').mockReturnValue(true);
jest.spyOn(AccessorUtilities, 'canUseLocalStorage').mockReturnValue(true);
});
it('does nothing if no tab was previously selected', () => {
......@@ -90,7 +90,7 @@ describe('SigninTabsMemoizer', () => {
});
it('should set .isLocalStorageAvailable', () => {
expect(AccessorUtilities.isLocalStorageAccessSafe).toHaveBeenCalled();
expect(AccessorUtilities.canUseLocalStorage).toHaveBeenCalled();
expect(memo.isLocalStorageAvailable).toBe(true);
});
});
......
......@@ -14,7 +14,7 @@ const CURRENT_TIME = new Date().getTime();
useLocalStorageSpy();
jest.mock('~/lib/utils/accessor', () => ({
isLocalStorageAccessSafe: jest.fn().mockReturnValue(true),
canUseLocalStorage: jest.fn().mockReturnValue(true),
}));
describe('Global Search Store Utils', () => {
......
......@@ -25,7 +25,7 @@ import {
const mockStorageKey = 'recent-tokens';
function setLocalStorageAvailability(isAvailable) {
jest.spyOn(AccessorUtilities, 'isLocalStorageAccessSafe').mockReturnValue(isAvailable);
jest.spyOn(AccessorUtilities, 'canUseLocalStorage').mockReturnValue(isAvailable);
}
describe('Filtered Search Utils', () => {
......
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