Commit 0e3a3aa5 authored by Mark Florian's avatar Mark Florian

Merge branch 'mrincon-rename-isLocalStorageAccessSafe' into 'master'

Rename isLocalStorageAccessSafe to canUseLocalStorage

See merge request gitlab-org/gitlab!70513
parents 7394acb1 9d83efc3
......@@ -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