Skip to content

Commit 673fba3

Browse files
Add local storage utils (#972)
Add utils for getting, setting, and clearing local storage values
1 parent 50ab63f commit 673fba3

File tree

7 files changed

+152
-0
lines changed

7 files changed

+152
-0
lines changed
Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
1+
import clearLocalStorageValue from '../clear-local-storage-value';
2+
3+
describe(clearLocalStorageValue, () => {
4+
beforeEach(() => {
5+
jest.restoreAllMocks();
6+
});
7+
8+
it('should call localStorage.removeItem with the provided key', () => {
9+
const removeItemSpy = jest.spyOn(Storage.prototype, 'removeItem');
10+
11+
clearLocalStorageValue('test-key');
12+
13+
expect(removeItemSpy).toHaveBeenCalledWith('test-key');
14+
});
15+
});
Lines changed: 55 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,55 @@
1+
import { z } from 'zod';
2+
3+
import getLocalStorageValue from '../get-local-storage-value';
4+
5+
const testSchema = z.string().startsWith('test-');
6+
7+
describe('getLocalStorageValue', () => {
8+
beforeEach(() => {
9+
jest.restoreAllMocks();
10+
});
11+
12+
it('should return the value from localStorage.getItem', () => {
13+
const mockValue = 'test-value';
14+
jest.spyOn(Storage.prototype, 'getItem').mockReturnValue(mockValue);
15+
16+
const result = getLocalStorageValue('test-key', testSchema);
17+
18+
expect(result).toBe(mockValue);
19+
});
20+
21+
it('should return null when localStorage.getItem returns null', () => {
22+
jest.spyOn(Storage.prototype, 'getItem').mockReturnValue(null);
23+
24+
const result = getLocalStorageValue('test-key', testSchema);
25+
26+
expect(result).toBeNull();
27+
});
28+
29+
it('should return null if the value in localStorage does not match the schema', () => {
30+
const mockValue = 'invalid-value';
31+
jest.spyOn(Storage.prototype, 'getItem').mockReturnValue(mockValue);
32+
33+
const result = getLocalStorageValue('test-key', testSchema);
34+
35+
expect(result).toBeNull();
36+
});
37+
38+
it('should return plain string when a schema is not provided', () => {
39+
const mockValue = 'mock-value';
40+
jest.spyOn(Storage.prototype, 'getItem').mockReturnValue(mockValue);
41+
42+
const result = getLocalStorageValue('test-key');
43+
44+
expect(result).toBe('mock-value');
45+
});
46+
47+
it('should return empty string when localStorage.getItem returns empty string', () => {
48+
const mockValue = '';
49+
jest.spyOn(Storage.prototype, 'getItem').mockReturnValue(mockValue);
50+
51+
const result = getLocalStorageValue('test-key');
52+
53+
expect(result).toBe('');
54+
});
55+
});
Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,34 @@
1+
import logger from '../../logger';
2+
import setLocalStorageValue from '../set-local-storage-value';
3+
4+
jest.mock('@/utils/logger', () => ({
5+
warn: jest.fn(),
6+
}));
7+
8+
describe(setLocalStorageValue.name, () => {
9+
beforeEach(() => {
10+
jest.restoreAllMocks();
11+
});
12+
13+
it('should call localStorage.setItem with the provided key and value', () => {
14+
const setItemSpy = jest.spyOn(Storage.prototype, 'setItem');
15+
16+
setLocalStorageValue('test-key', 'test-value');
17+
18+
expect(setItemSpy).toHaveBeenCalledWith('test-key', 'test-value');
19+
});
20+
21+
it('should handle localStorage.setItem errors gracefully', () => {
22+
const mockError = new Error('Storage quota exceeded');
23+
jest.spyOn(Storage.prototype, 'setItem').mockImplementation(() => {
24+
throw mockError;
25+
});
26+
27+
setLocalStorageValue('test-key', 'test-value');
28+
29+
expect(logger.warn).toHaveBeenCalledWith(
30+
{ key: 'test-key', error: mockError, value: 'test-value' },
31+
'Failed to save value to local storage'
32+
);
33+
});
34+
});
Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
import logger from '../logger';
2+
3+
export default function clearLocalStorageValue(key: string) {
4+
if (typeof window === 'undefined') return;
5+
6+
try {
7+
return localStorage.removeItem(key);
8+
} catch (error) {
9+
logger.warn({ key, error }, 'Failed to clear value from local storage');
10+
}
11+
}
Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,23 @@
1+
import { type z } from 'zod';
2+
3+
import logger from '../logger';
4+
5+
export default function getLocalStorageValue<
6+
S extends z.ZodTypeAny = z.ZodString,
7+
>(key: string, schema?: S): z.infer<S> | null {
8+
if (typeof window === 'undefined') return null;
9+
10+
try {
11+
const localStorageVal = localStorage.getItem(key);
12+
if (localStorageVal === null) return null;
13+
14+
if (schema) {
15+
return schema.parse(localStorageVal);
16+
}
17+
18+
return localStorageVal;
19+
} catch (error) {
20+
logger.warn({ key, error }, 'Failed to get value from local storage');
21+
return null;
22+
}
23+
}

src/utils/local-storage/index.ts

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
export { default as getLocalStorageValue } from './get-local-storage-value';
2+
export { default as setLocalStorageValue } from './set-local-storage-value';
3+
export { default as clearLocalStorageValue } from './clear-local-storage-value';
Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
import logger from '../logger';
2+
3+
export default function setLocalStorageValue(key: string, value: string) {
4+
if (typeof window === 'undefined') return;
5+
6+
try {
7+
localStorage.setItem(key, value);
8+
} catch (error) {
9+
logger.warn({ key, error, value }, 'Failed to save value to local storage');
10+
}
11+
}

0 commit comments

Comments
 (0)