Repository: capricorn86/happy-dom
Pull request #240 (2021-04-07 22:15:32)

#239@minor: Adds support for window.Storage, window.localStorage and …
…window.sessionStorage.


Patch:
diff --git a/packages/happy-dom/src/index.ts b/packages/happy-dom/src/index.ts
index 9c5f35e6..a0ab8e80 100644
--- a/packages/happy-dom/src/index.ts
+++ b/packages/happy-dom/src/index.ts
@@ -72,6 +72,7 @@ import ElementTag from './config/ElementTag';
 import SelfClosingElements from './config/SelfClosingElements';
 import UnclosedElements from './config/UnclosedElements';
 import CSSStyleSheet from './css/CSSStyleSheet';
+import Storage from './storage/Storage';
 
 export {
 	AsyncWindow,
@@ -147,5 +148,6 @@ export {
 	ElementTag,
 	SelfClosingElements,
 	UnclosedElements,
-	CSSStyleSheet
+	CSSStyleSheet,
+	Storage
 };
diff --git a/packages/happy-dom/src/storage/Storage.ts b/packages/happy-dom/src/storage/Storage.ts
new file mode 100644
index 00000000..1c9cc780
--- /dev/null
+++ b/packages/happy-dom/src/storage/Storage.ts
@@ -0,0 +1,59 @@
+export default class Storage {
+	private _store: { [k: string]: string } = {};
+
+	/**
+	 * Returns length.
+	 *
+	 * @returns Length.
+	 */
+	public get length(): number {
+		return Object.keys(this._store).length;
+	}
+
+	/**
+	 * Returns name of the nth key.
+	 *
+	 * @param index Index.
+	 * @returns Name.
+	 */
+	public key(index: number): string {
+		const name = Object.keys(this._store)[index];
+		return name === undefined ? null : name;
+	}
+
+	/**
+	 * Sets item.
+	 *
+	 * @param name Name.
+	 * @param item Item.
+	 */
+	public setItem(name: string, item: string): void {
+		this._store[name] = item;
+	}
+
+	/**
+	 * Returns item.
+	 *
+	 * @param name Name.
+	 * @returns Item.
+	 */
+	public getItem(name: string): string {
+		return this._store[name] === undefined ? null : this._store[name];
+	}
+
+	/**
+	 * Removes item.
+	 *
+	 * @param name Name.
+	 */
+	public removeItem(name: string): void {
+		delete this._store[name];
+	}
+
+	/**
+	 * Clears storage.
+	 */
+	public clear(): void {
+		this._store = {};
+	}
+}
diff --git a/packages/happy-dom/src/window/Window.ts b/packages/happy-dom/src/window/Window.ts
index d9d57985..2a74ef7c 100644
--- a/packages/happy-dom/src/window/Window.ts
+++ b/packages/happy-dom/src/window/Window.ts
@@ -55,6 +55,7 @@ import IResponse from './IResponse';
 import { URLSearchParams } from 'url';
 import AsyncTaskTypeEnum from './AsyncTaskTypeEnum';
 import RelativeURL from '../location/RelativeURL';
+import Storage from '../storage/Storage';
 
 const FETCH_RESPONSE_TYPE_METHODS = ['blob', 'json', 'formData', 'text'];
 
@@ -125,6 +126,7 @@ export default class Window extends EventTarget implements NodeJS.Global {
 	public DOMException = DOMException;
 	public History = History;
 	public Screen = Screen;
+	public Storage = Storage;
 
 	// Public Properties
 	public document: Document;
@@ -139,6 +141,8 @@ export default class Window extends EventTarget implements NodeJS.Global {
 	public screen = new Screen();
 	public readonly innerWidth = 1024;
 	public readonly innerHeight = 768;
+	public readonly sessionStorage = new Storage();
+	public readonly localStorage = new Storage();
 
 	// Node.js Globals
 	public Array = global ? global.Array : null;


Test patch:
diff --git a/packages/happy-dom/test/storage/Storage.test.ts b/packages/happy-dom/test/storage/Storage.test.ts
new file mode 100644
index 00000000..f09692fc
--- /dev/null
+++ b/packages/happy-dom/test/storage/Storage.test.ts
@@ -0,0 +1,58 @@
+import Storage from '../../src/storage/Storage';
+
+describe('Storage', () => {
+	let storage: Storage;
+
+	beforeEach(() => {
+		storage = new Storage();
+	});
+
+	describe('get length()', () => {
+		test('Returns length.', () => {
+			storage.setItem('key1', 'value1');
+			storage.setItem('key2', 'value2');
+			expect(storage.length).toBe(2);
+			storage.setItem('key3', 'value3');
+			expect(storage.length).toBe(3);
+		});
+	});
+
+	describe('key()', () => {
+		test('Returns name of the nth name.', () => {
+			storage.setItem('key1', 'value1');
+			storage.setItem('key2', 'value2');
+			expect(storage.key(0)).toBe('key1');
+			expect(storage.key(1)).toBe('key2');
+			expect(storage.key(2)).toBe(null);
+		});
+	});
+
+	describe('getItem()', () => {
+		test('Returns item.', () => {
+			storage.setItem('key1', 'value1');
+			storage.setItem('key2', 'value2');
+			expect(storage.getItem('key1')).toBe('value1');
+			expect(storage.getItem('key2')).toBe('value2');
+		});
+	});
+
+	describe('setItem()', () => {
+		test('Returns item.', () => {
+			storage.setItem('key1', 'value1');
+			storage.setItem('key2', 'value2');
+			expect(storage.getItem('key1')).toBe('value1');
+			expect(storage.getItem('key2')).toBe('value2');
+		});
+	});
+
+	describe('removeItem()', () => {
+		test('Removes an item.', () => {
+			storage.setItem('key1', 'value1');
+			storage.setItem('key2', 'value2');
+			storage.removeItem('key2');
+			expect(storage.length).toBe(1);
+			expect(storage.getItem('key1')).toBe('value1');
+			expect(storage.getItem('key2')).toBe(null);
+		});
+	});
+});