diff --git a/packages/local-storage/spec/fixtures/in-memory-local-storage.ts b/packages/local-storage/spec/fixtures/in-memory-local-storage.ts new file mode 100644 index 00000000..9ca41f57 --- /dev/null +++ b/packages/local-storage/spec/fixtures/in-memory-local-storage.ts @@ -0,0 +1,45 @@ +export class InMemoryLocalStorage implements Storage { + private data: Record = {}; + + length: number; + clear(): void { + this.data = {}; + } + getItem(key: string): string | null { + return this.data[key]; + } + key(index: number): string | null { + return Object.keys(this.data)[index] || null; + } + removeItem(key: string): void { + if (key in this.data) { + delete this.data[key]; + } + } + setItem(key: string, value: string): void { + this.data[key] = value; + } +} + +export class AsyncInMemoryLocalStorage { + private data: Record = {}; + + length: number; + async clear(): Promise { + this.data = {}; + } + async getItem(key: string): Promise { + return this.data[key]; + } + async key(index: number): Promise { + return Object.keys(this.data)[index] || null; + } + async removeItem(key: string): Promise { + if (key in this.data) { + delete this.data[key]; + } + } + async setItem(key: string, value: string): Promise { + this.data[key] = value; + } +} diff --git a/packages/local-storage/spec/web/local_storage.spec.ts b/packages/local-storage/spec/web/local_storage.spec.ts index 61cab00c..30f1f080 100644 --- a/packages/local-storage/spec/web/local_storage.spec.ts +++ b/packages/local-storage/spec/web/local_storage.spec.ts @@ -1,9 +1,18 @@ /* global describe, it, expect */ import { Loki } from "../../../loki/src/loki"; import { LocalStorage } from "../../src/local_storage"; +import { + InMemoryLocalStorage, + AsyncInMemoryLocalStorage, +} from "../fixtures/in-memory-local-storage"; -describe("testing local storage", function () { +const STORAGES = { + "window.localStorage": window.localStorage, + InMemoryLocalStorage: new InMemoryLocalStorage(), + AsyncInMemoryLocalStorage: new AsyncInMemoryLocalStorage(), +}; +describe("testing local storage", function () { interface Name { name: string; } @@ -16,80 +25,63 @@ describe("testing local storage", function () { LocalStorage.deregister(); }); - it("LokiLocalStorage", function (done) { - const db = new Loki("myTestApp"); - const adapter = {adapter: new LocalStorage()}; - db.initializePersistence(adapter) - .then(() => { - db.addCollection("myColl").insert({name: "Hello World"}); - return db.saveDatabase(); - }) - .then(() => { - const db2 = new Loki("myTestApp"); - return db2.initializePersistence() - .then(() => { - return db2.loadDatabase(); - }).then(() => { - expect(db2.getCollection("myColl").find()[0].name).toEqual("Hello World"); - }); - }) - .then(() => { + for (const [storageName, storage] of Object.entries(STORAGES)) { + describe(`Testing with ${storageName}`, () => { + const adapter = new LocalStorage(storage); + + it("should isolate different databases, collections and objects", async function () { + const db = new Loki("myTestApp"); + await db.initializePersistence({ adapter }); + db.addCollection("myColl").insert({ name: "Hello World" }); + await db.saveDatabase(); + const db2 = new Loki("myTestApp"); - return db2.initializePersistence({persistenceMethod: "local-storage"}) - .then(() => { - return db2.loadDatabase(); - }).then(() => { - expect(db2.getCollection("myColl").find()[0].name).toEqual("Hello World"); - }); - }) - .then(() => { + await db2.initializePersistence({ adapter }); + await db2.loadDatabase(); + expect(db2.getCollection("myColl").find()[0].name).toEqual( + "Hello World" + ); + + await db2.initializePersistence({ + persistenceMethod: "local-storage", + adapter, + }); + await db2.loadDatabase(); + expect(db2.getCollection("myColl").find()[0].name).toEqual( + "Hello World" + ); + const db3 = new Loki("other"); - return db3.initializePersistence() - .then(() => { - return db3.loadDatabase(); - }).then(() => { - expect(false).toEqual(true); - }, () => { - expect(true).toEqual(true); - }); - }) - .then(() => { - return db.deleteDatabase(); - }) - .then(() => { - return db.loadDatabase() - .then(() => { - expect(db.getCollection("myColl").find()[0].name).toEqual("Hello World"); - expect(false).toEqual(true); - done(); - }, () => { - expect(true).toEqual(true); - done(); - }); + await db3.initializePersistence({ adapter }); + await expect(async () => { + await db3.loadDatabase(); + }).not.toThrow(); + + await db.deleteDatabase(); + await expect(async () => { + await db.loadDatabase(); + }).not.toThrow(); }); - }); + it("auto save and auto load", async function () { + const db = new Loki("myTestApp2"); + + await db.initializePersistence({ + autosave: true, + autoload: true, + adapter, + }); - it("auto save and auto load", function (done) { - const db = new Loki("myTestApp2"); + db.addCollection("myColl").insert({ name: "Hello World" }); + await db.close(); - db.initializePersistence({autosave: true, autoload: true}) - .then(() => { - db.addCollection("myColl").insert({name: "Hello World"}); - return db.close(); - }) - .then(() => { const db2 = new Loki("myTestApp2"); - return db2.initializePersistence({autosave: true, autoload: true}) - .then(() => { - expect(db2.getCollection("myColl").find()[0].name).toEqual("Hello World"); - }); - }) - .catch(e => { - fail(e); - }) - .then(() => { - done(); + + await db2.initializePersistence({ autosave: true, autoload: true }); + expect(db2.getCollection("myColl").find()[0].name).toEqual( + "Hello World" + ); }); - }); + }); + } }); diff --git a/packages/local-storage/src/index.ts b/packages/local-storage/src/index.ts index bba8a63d..48647fed 100644 --- a/packages/local-storage/src/index.ts +++ b/packages/local-storage/src/index.ts @@ -1,4 +1,5 @@ -import { LocalStorage } from "./local_storage"; +export * from "./types"; -export {LocalStorage}; +import { LocalStorage } from "./local_storage"; +export { LocalStorage }; export default LocalStorage; diff --git a/packages/local-storage/src/local_storage.ts b/packages/local-storage/src/local_storage.ts index 760cb098..3fb424f4 100644 --- a/packages/local-storage/src/local_storage.ts +++ b/packages/local-storage/src/local_storage.ts @@ -1,11 +1,14 @@ import { PLUGINS } from "../../common/plugin"; import { StorageAdapter } from "../../common/types"; +import { Storage } from "./types"; /** * A loki persistence adapter which persists to web browser's local storage object * @constructor LocalStorageAdapter */ export class LocalStorage implements StorageAdapter { + private storage: Storage; + /** * Registers the local storage as plugin. */ @@ -20,13 +23,20 @@ export class LocalStorage implements StorageAdapter { delete PLUGINS["LocalStorage"]; } + /** + * @param {Storage} [constructor=window.localStorage] - Application name context can be used to distinguish subdomains, "loki" by default + */ + constructor(storage: Storage = window.localStorage) { + this.storage = storage; + } + /** * loadDatabase() - Load data from localstorage * @param {string} dbname - the name of the database to load * @returns {Promise} a Promise that resolves after the database was loaded */ - loadDatabase(dbname: string) { - return Promise.resolve(localStorage.getItem(dbname)); + async loadDatabase(dbname: string) { + return this.storage.getItem(dbname); } /** @@ -35,8 +45,8 @@ export class LocalStorage implements StorageAdapter { * @param {string} dbname - the filename of the database to load * @returns {Promise} a Promise that resolves after the database was saved */ - saveDatabase(dbname: string, dbstring: string) { - return Promise.resolve(localStorage.setItem(dbname, dbstring)); + async saveDatabase(dbname: string, dbstring: string) { + return this.storage.setItem(dbname, dbstring); } /** @@ -45,8 +55,8 @@ export class LocalStorage implements StorageAdapter { * @param {string} dbname - the filename of the database to delete * @returns {Promise} a Promise that resolves after the database was deleted */ - deleteDatabase(dbname: string) { - return Promise.resolve(localStorage.removeItem(dbname)); + async deleteDatabase(dbname: string) { + return this.storage.removeItem(dbname); } } diff --git a/packages/local-storage/src/types.ts b/packages/local-storage/src/types.ts new file mode 100644 index 00000000..8b99a5e6 --- /dev/null +++ b/packages/local-storage/src/types.ts @@ -0,0 +1,10 @@ +export type SyncOrAsync = T | Promise; + +export interface Storage { + readonly length: number; + clear(): SyncOrAsync; + getItem(key: string): SyncOrAsync; + key(index: number): SyncOrAsync; + removeItem(key: string): SyncOrAsync; + setItem(key: string, value: string): SyncOrAsync; +}