Skip to content

Commit 647d66a

Browse files
committed
Add localStorage utility functions
1 parent 96b17aa commit 647d66a

File tree

7 files changed

+130
-2
lines changed

7 files changed

+130
-2
lines changed

CHANGELOG.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
99

1010
- added `dateIsValid`, `dateIsLastDayOfMonth` and `dateDifferenceInDays` utility functions
1111
- added `newGuid` function and `emptyGuid` constant
12+
- added `getLocalStorageItem`, `setLocalStorageItem` and `removeLocalStorageItem` utility functions
1213

1314
## [0.1.1] - 2023-08-04
1415

jest.config.js

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,4 +2,6 @@
22
module.exports = {
33
preset: "ts-jest",
44
testEnvironment: "node",
5+
resetMocks: false,
6+
setupFiles: ["jest-localstorage-mock"],
57
};

package.json

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -68,6 +68,7 @@
6868
"eslint-plugin-jsdoc": "^46.4.4",
6969
"eslint-plugin-storybook": "^0.6.12",
7070
"jest": "^29.6.1",
71+
"jest-localstorage-mock": "^2.4.26",
7172
"nodemon": "^2.0.22",
7273
"prettier": "^2.8.8",
7374
"release-it": "^16.1.2",

src/index.ts

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,2 +1,3 @@
1-
export * from "./lib/date";
2-
export * from "./lib/enum";
1+
export * from "./lib/date";
2+
export * from "./lib/enum";
3+
export * from "./lib/localStorage";

src/lib/localStorage.spec.ts

Lines changed: 47 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,47 @@
1+
import { getLocalStorageItem, setLocalStorageItem, removeLocalStorageItem } from "./localStorage";
2+
3+
describe("localStorage tests", () => {
4+
beforeEach(() => {
5+
localStorage.clear();
6+
jest.clearAllMocks();
7+
});
8+
9+
test("getLocalStorageItem not existing", () => {
10+
expect(getLocalStorageItem("test")).toBeUndefined();
11+
});
12+
13+
test("getLocalStorageItem existing", () => {
14+
localStorage.setItem("test", '{"data":{"field1":"hello","field2":"world"}}');
15+
expect(getLocalStorageItem("test")).toMatchObject({ field1: "hello", field2: "world" });
16+
});
17+
18+
test("getLocalStorageItem expired", () => {
19+
const expiredDate = new Date(new Date().getTime() - 1);
20+
localStorage.setItem("test", `{"data":true,"expirationDate":"${expiredDate.toISOString()}"}`);
21+
expect(localStorage.length).toBe(1);
22+
expect(getLocalStorageItem("test")).toBeUndefined();
23+
expect(localStorage.length).toBe(0);
24+
});
25+
26+
test("setLocalStorageItem without expiration", () => {
27+
setLocalStorageItem("test", { field1: "hello", field2: "world" });
28+
expect(localStorage.length).toBe(1);
29+
expect(localStorage.getItem("test")).toBe('{"data":{"field1":"hello","field2":"world"}}');
30+
});
31+
32+
test("setLocalStorageItem with expiration", () => {
33+
const expirationDate = new Date(2050, 1, 1, 16, 30, 45, 123);
34+
// Remove the system timezone to properly verify the serialized time
35+
const expirationDateWithoutTimezone = new Date(expirationDate.getTime() - expirationDate.getTimezoneOffset() * 60000);
36+
setLocalStorageItem("test", true, expirationDateWithoutTimezone);
37+
expect(localStorage.length).toBe(1);
38+
expect(localStorage.getItem("test")).toBe('{"data":true,"expirationDate":"2050-02-01T16:30:45.123Z"}');
39+
});
40+
41+
test("removeLocalStorageItem", () => {
42+
localStorage.setItem("test", '{"data":true');
43+
expect(localStorage.length).toBe(1);
44+
removeLocalStorageItem("test");
45+
expect(localStorage.length).toBe(0);
46+
});
47+
});

src/lib/localStorage.ts

Lines changed: 71 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,71 @@
1+
import { dateIsValid } from "./date";
2+
3+
/**
4+
* The localStorage item
5+
*/
6+
interface LocalStorageItem<T> {
7+
/**
8+
* The data
9+
*/
10+
data: T;
11+
12+
/**
13+
* The expiration date
14+
*/
15+
expirationDate?: Date;
16+
}
17+
18+
/**
19+
* Check if the localStorage is supported
20+
*/
21+
const checkLocalStorageSupport = () => {
22+
if (typeof localStorage === "undefined") {
23+
throw new Error("localStorage not supported");
24+
}
25+
};
26+
27+
/**
28+
* Get an item from the localStorage
29+
* @param key The key
30+
* @returns The item
31+
*/
32+
export function getLocalStorageItem<T>(key: string): T | undefined {
33+
checkLocalStorageSupport();
34+
35+
const item = localStorage.getItem(key);
36+
if (!item) {
37+
return undefined;
38+
}
39+
40+
const parsedItem = JSON.parse(item) as LocalStorageItem<T>;
41+
42+
if (parsedItem.expirationDate) {
43+
const revivedExpirationDate = new Date(parsedItem.expirationDate);
44+
if (dateIsValid(revivedExpirationDate) && revivedExpirationDate < new Date()) {
45+
localStorage.removeItem(key);
46+
return undefined;
47+
}
48+
}
49+
50+
return parsedItem.data;
51+
}
52+
53+
/**
54+
* Set an item in the localStorage
55+
* @param key The key
56+
* @param data The item data
57+
* @param expirationDate The expiration date
58+
*/
59+
export function setLocalStorageItem<T>(key: string, data: T, expirationDate?: Date): void {
60+
checkLocalStorageSupport();
61+
localStorage.setItem(key, JSON.stringify({ data, expirationDate }));
62+
}
63+
64+
/**
65+
* Remove an item from the localStorage
66+
* @param key The key
67+
*/
68+
export function removeLocalStorageItem(key: string): void {
69+
checkLocalStorageSupport();
70+
localStorage.removeItem(key);
71+
}

yarn.lock

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3993,6 +3993,11 @@ jest-leak-detector@^29.6.1:
39933993
jest-get-type "^29.4.3"
39943994
pretty-format "^29.6.1"
39953995

3996+
jest-localstorage-mock@^2.4.26:
3997+
version "2.4.26"
3998+
resolved "https://registry.yarnpkg.com/jest-localstorage-mock/-/jest-localstorage-mock-2.4.26.tgz#7d57fb3555f2ed5b7ed16fd8423fd81f95e9e8db"
3999+
integrity sha512-owAJrYnjulVlMIXOYQIPRCCn3MmqI3GzgfZCXdD3/pmwrIvFMXcKVWZ+aMc44IzaASapg0Z4SEFxR+v5qxDA2w==
4000+
39964001
jest-matcher-utils@^29.6.1:
39974002
version "29.6.1"
39984003
resolved "https://registry.yarnpkg.com/jest-matcher-utils/-/jest-matcher-utils-29.6.1.tgz#6c60075d84655d6300c5d5128f46531848160b53"

0 commit comments

Comments
 (0)