-
Notifications
You must be signed in to change notification settings - Fork 60
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Merge pull request #1952 from dusk-network/feature-1946
explorer: Save market data in local storage
- Loading branch information
Showing
11 changed files
with
398 additions
and
7 deletions.
There are no files selected for viewing
80 changes: 80 additions & 0 deletions
80
explorer/src/lib/dusk/storage/__tests__/createStorage.spec.js
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,80 @@ | ||
import { afterEach, beforeEach, describe, expect, it, vi } from "vitest"; | ||
|
||
import { createStorage } from ".."; | ||
|
||
describe("createStorage", () => { | ||
const serializer = vi.fn(JSON.stringify); | ||
const deserializer = vi.fn(JSON.parse); | ||
|
||
afterEach(() => { | ||
serializer.mockClear(); | ||
deserializer.mockClear(); | ||
}); | ||
|
||
for (const type of /** @type {StorageType[]} */ (["local", "session"])) { | ||
const storage = createStorage(type, serializer, deserializer); | ||
const systemStorage = globalThis[`${type}Storage`]; | ||
const valueA = { a: 1, b: 2 }; | ||
const serializedA = JSON.stringify(valueA); | ||
const valueB = ["a", "b", "c", "d"]; | ||
const serializedB = JSON.stringify(valueB); | ||
|
||
beforeEach(() => { | ||
systemStorage.setItem("some-key", serializedA); | ||
}); | ||
|
||
it("should expose a method to clear the created storage", async () => { | ||
await expect(storage.clear()).resolves.toBe(undefined); | ||
|
||
expect(systemStorage.length).toBe(0); | ||
}); | ||
|
||
it("should expose a method to retrieve a value from the created storage", async () => { | ||
await expect(storage.getItem("some-key")).resolves.toStrictEqual(valueA); | ||
|
||
expect(deserializer).toHaveBeenCalledTimes(1); | ||
expect(deserializer).toHaveBeenCalledWith(serializedA); | ||
|
||
await expect(storage.getItem("some-other-key")).resolves.toBe(null); | ||
|
||
expect(deserializer).toHaveBeenCalledTimes(1); | ||
}); | ||
|
||
it("should expose a method to remove a value from the created storage", async () => { | ||
await expect(storage.removeItem("some-key")).resolves.toBe(undefined); | ||
|
||
expect(systemStorage.getItem("some-key")).toBe(null); | ||
}); | ||
|
||
it("should expose a method to set a value in the selected storage", async () => { | ||
await expect(storage.setItem("some-key", valueB)).resolves.toBe( | ||
undefined | ||
); | ||
|
||
expect(serializer).toHaveBeenCalledTimes(1); | ||
expect(serializer).toHaveBeenCalledWith(valueB); | ||
expect(systemStorage.getItem("some-key")).toBe(serializedB); | ||
}); | ||
|
||
it("should return a rejected promise if any of the underlying storage method fails", async () => { | ||
/** @type {Array<keyof DuskStorage & keyof Storage>} */ | ||
const methods = ["clear", "getItem", "removeItem", "setItem"]; | ||
const error = new Error("some error message"); | ||
|
||
for (const method of methods) { | ||
const methodSpy = vi | ||
.spyOn(Storage.prototype, method) | ||
.mockImplementation(() => { | ||
throw error; | ||
}); | ||
|
||
// we don't care for correct parameters here | ||
await expect( | ||
storage[method]("some-key", "some value") | ||
).rejects.toStrictEqual(error); | ||
|
||
methodSpy.mockRestore(); | ||
} | ||
}); | ||
} | ||
}); |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,31 @@ | ||
/** | ||
* @param {StorageType} type | ||
* @param {StorageSerializer} serializer | ||
* @param {StorageDeserializer} deserializer | ||
* @returns {DuskStorage} | ||
*/ | ||
function createStorage(type, serializer, deserializer) { | ||
const storage = type === "local" ? localStorage : sessionStorage; | ||
|
||
return { | ||
async clear() { | ||
return storage.clear(); | ||
}, | ||
|
||
async getItem(key) { | ||
const value = storage.getItem(key); | ||
|
||
return value === null ? null : deserializer(value); | ||
}, | ||
|
||
async removeItem(key) { | ||
return storage.removeItem(key); | ||
}, | ||
|
||
async setItem(key, value) { | ||
return storage.setItem(key, serializer(value)); | ||
}, | ||
}; | ||
} | ||
|
||
export default createStorage; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1 @@ | ||
export { default as createStorage } from "./createStorage"; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,12 @@ | ||
type StorageType = "local" | "session"; | ||
|
||
type StorageSerializer = (value: any) => string; | ||
|
||
type StorageDeserializer = (value: string) => any; | ||
|
||
type DuskStorage = { | ||
clear: () => Promise<void>; | ||
getItem: (key: string) => Promise<any>; | ||
removeItem: (key: string) => Promise<void>; | ||
setItem: (key: string, value: any) => Promise<void>; | ||
}; |
57 changes: 57 additions & 0 deletions
57
explorer/src/lib/services/__tests__/marketDataStorage.spec.js
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,57 @@ | ||
import { beforeEach, describe, expect, it, vi } from "vitest"; | ||
import { fireEvent } from "@testing-library/svelte"; | ||
|
||
import { marketDataStorage } from ".."; | ||
|
||
describe("marketDataStorage", () => { | ||
const marketData = { data: { a: 1 }, lastUpdate: new Date() }; | ||
|
||
beforeEach(() => { | ||
localStorage.setItem("market-data", JSON.stringify(marketData)); | ||
}); | ||
|
||
it("should expose a method to clear the market data storage", async () => { | ||
localStorage.setItem("some-key", "some value"); | ||
|
||
await expect(marketDataStorage.clear()).resolves.toBe(undefined); | ||
|
||
expect(localStorage.getItem("market-data")).toBeNull(); | ||
expect(localStorage.getItem("some-key")).toBe("some value"); | ||
}); | ||
|
||
it("should expose a method to retrieve market data from the storage", async () => { | ||
await expect(marketDataStorage.get()).resolves.toStrictEqual(marketData); | ||
}); | ||
|
||
it("should expose a method to set the data in the storage", async () => { | ||
const newData = { data: { b: 2 }, lastUpdate: new Date() }; | ||
|
||
// @ts-expect-error | ||
await expect(marketDataStorage.set(newData)).resolves.toBe(undefined); | ||
await expect(marketDataStorage.get()).resolves.toStrictEqual(newData); | ||
|
||
expect(localStorage.getItem("market-data")).toBe(JSON.stringify(newData)); | ||
}); | ||
|
||
it("should expose a method that allows to set a listener for storage events and returns a function to remove the listener", async () => { | ||
const eventA = new StorageEvent("storage", { key: "market-data" }); | ||
const eventB = new StorageEvent("storage", { key: "some-other-key" }); | ||
const listener = vi.fn(); | ||
const removeListener = marketDataStorage.onChange(listener); | ||
|
||
await fireEvent(window, eventA); | ||
|
||
expect(listener).toHaveBeenCalledTimes(1); | ||
expect(listener).toHaveBeenCalledWith(eventA); | ||
|
||
await fireEvent(window, eventB); | ||
|
||
expect(listener).toHaveBeenCalledTimes(1); | ||
|
||
removeListener(); | ||
|
||
await fireEvent(window, eventA); | ||
|
||
expect(listener).toHaveBeenCalledTimes(1); | ||
}); | ||
}); |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1 +1,2 @@ | ||
export { default as duskAPI } from "./duskAPI"; | ||
export { default as marketDataStorage } from "./marketDataStorage"; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,46 @@ | ||
import { createStorage } from "$lib/dusk/storage"; | ||
|
||
/** | ||
* @param {string} key | ||
* @param {any} value | ||
*/ | ||
const reviver = (key, value) => | ||
key === "lastUpdate" ? new Date(value) : value; | ||
const storage = createStorage("local", JSON.stringify, (value) => | ||
JSON.parse(value, reviver) | ||
); | ||
const key = "market-data"; | ||
|
||
export default { | ||
clear() { | ||
return storage.removeItem(key); | ||
}, | ||
|
||
/** @returns {Promise<MarketData>} */ | ||
get() { | ||
return storage.getItem(key); | ||
}, | ||
|
||
/** | ||
* | ||
* @param {(evt: StorageEvent) => void} listener | ||
* @returns {(() => void)} The function to remove the listener. | ||
*/ | ||
onChange(listener) { | ||
/** @param {StorageEvent} evt */ | ||
const handleStorageChange = (evt) => { | ||
if (evt.key === key) { | ||
listener(evt); | ||
} | ||
}; | ||
|
||
window.addEventListener("storage", handleStorageChange); | ||
|
||
return () => window.removeEventListener("storage", handleStorageChange); | ||
}, | ||
|
||
/** @param {MarketDataStorage} value */ | ||
set(value) { | ||
return storage.setItem(key, value); | ||
}, | ||
}; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,4 @@ | ||
type MarketDataStorage = { | ||
data: MarketData; | ||
lastUpdate: Date; | ||
}; |
Oops, something went wrong.