diff --git a/CHANGELOG.md b/CHANGELOG.md index 13fc5dc..7c24426 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -10,6 +10,11 @@ and this project adheres to [Semantic Versioning](http://semver.org/). ### Fixed ### Removed +## [2.3.0] - 2021-01-07 + +### Added +- feat: Add compatibility with @data-provider/core v3 arguments. + ## [2.2.2] - 2020-12-27 ### Added diff --git a/jest.config.js b/jest.config.js index d0a4a38..0da5d32 100644 --- a/jest.config.js +++ b/jest.config.js @@ -28,7 +28,7 @@ module.exports = { testEnvironment: "node", // The glob patterns Jest uses to detect test files - testMatch: ["/test/**/?(*.)+(spec|test).js?(x)"], + testMatch: ["/test/**/*.spec.js"], transform: { ".js$": "babel-jest", diff --git a/package-lock.json b/package-lock.json index 2bdcd95..c6ed94a 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,6 +1,6 @@ { "name": "@data-provider/browser-storage", - "version": "2.2.2", + "version": "2.3.0", "lockfileVersion": 1, "requires": true, "dependencies": { @@ -2296,12 +2296,13 @@ } }, "@data-provider/core": { - "version": "2.9.0", - "resolved": "https://registry.npmjs.org/@data-provider/core/-/core-2.9.0.tgz", - "integrity": "sha512-X8y7LZioa7XNq4CzqPu7+nzfFMGQoeBNNJ6SGeHu0a4dE2SaXXBKuefB1T9mJcKgLbE3V3k1Nkm6tpVGzUDSNw==", + "version": "2.10.0", + "resolved": "https://registry.npmjs.org/@data-provider/core/-/core-2.10.0.tgz", + "integrity": "sha512-X7VhA3bBVHQtMdy63mm7HLCBNUF8/jp2G8yFk0sI3ciS+wCX66WRc86Z3YADEOBlW1dsH9QUHVhK4y0nAdjOqA==", "dev": true, "requires": { - "is-promise": "4.0.0" + "is-promise": "4.0.0", + "lodash.isplainobject": "4.0.6" } }, "@eslint/eslintrc": { @@ -7531,6 +7532,12 @@ "integrity": "sha1-Cwih3PaDl8OXhVwyOXg4Mt90A9E=", "dev": true }, + "lodash.isplainobject": { + "version": "4.0.6", + "resolved": "https://registry.npmjs.org/lodash.isplainobject/-/lodash.isplainobject-4.0.6.tgz", + "integrity": "sha1-fFJqUtibRcRcxpC4gWO+BJf1UMs=", + "dev": true + }, "lodash.sortby": { "version": "4.7.0", "resolved": "https://registry.npmjs.org/lodash.sortby/-/lodash.sortby-4.7.0.tgz", diff --git a/package.json b/package.json index 5e31dc1..883b3e9 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "@data-provider/browser-storage", - "version": "2.2.2", + "version": "2.3.0", "description": "Data Provider addon providing localStorage and sessionStorage origins", "keywords": [ "data-provider", @@ -38,12 +38,12 @@ "test:mutation": "stryker run" }, "peerDependencies": { - "@data-provider/core": "2.x" + "@data-provider/core": ">=2.10.0" }, "devDependencies": { "@babel/core": "7.12.3", "@babel/preset-env": "7.12.1", - "@data-provider/core": "2.9.0", + "@data-provider/core": "2.10.0", "@rollup/plugin-babel": "5.2.2", "@rollup/plugin-commonjs": "17.0.0", "@rollup/plugin-json": "4.1.0", diff --git a/sonar-project.properties b/sonar-project.properties index 318f3a2..6e335b6 100644 --- a/sonar-project.properties +++ b/sonar-project.properties @@ -1,6 +1,6 @@ sonar.organization=data-provider sonar.projectKey=data-provider-browser-storage -sonar.projectVersion=2.2.2 +sonar.projectVersion=2.3.0 sonar.sources=src,test sonar.exclusions=node_modules/** diff --git a/src/LocalStorage.js b/src/LocalStorage.js index c506238..cd8fd4e 100644 --- a/src/LocalStorage.js +++ b/src/LocalStorage.js @@ -12,9 +12,12 @@ Unless required by applicable law or agreed to in writing, software distributed import { Storage } from "./Storage"; import { TAG, LOCAL_TAG } from "./tags"; +import { providerArgsV3 } from "@data-provider/core"; + export class LocalStorage extends Storage { - constructor(id, options, query) { - super(id, { ...options, storageKey: "localStorage" }, query); + constructor(...args) { + const [id, options, queryValue] = providerArgsV3(args); + super({ id, ...options, storageKey: "localStorage" }, queryValue); } get baseTags() { diff --git a/src/SessionStorage.js b/src/SessionStorage.js index 905e92b..fedf07b 100644 --- a/src/SessionStorage.js +++ b/src/SessionStorage.js @@ -12,9 +12,12 @@ Unless required by applicable law or agreed to in writing, software distributed import { Storage } from "./Storage"; import { TAG, SESSION_TAG } from "./tags"; +import { providerArgsV3 } from "@data-provider/core"; + export class SessionStorage extends Storage { - constructor(id, options, query) { - super(id, { ...options, storageKey: "sessionStorage" }, query); + constructor(...args) { + const [id, options, queryValue] = providerArgsV3(args); + super({ id, ...options, storageKey: "sessionStorage" }, queryValue); } get baseTags() { diff --git a/src/Storage.js b/src/Storage.js index c8fe74f..0130101 100644 --- a/src/Storage.js +++ b/src/Storage.js @@ -42,12 +42,12 @@ class StorageErrorMock { } export class Storage extends Provider { - constructor(id, options, query) { + constructor(options, queryValue) { const extendedOptions = { ...options }; - if (!query) { - extendedOptions.parentId = id; + if (!queryValue) { + extendedOptions.parentId = options.id; } - super(id, extendedOptions, query); + super(extendedOptions, queryValue); } _getStorage(storageKey, root, storageFallback) { diff --git a/test/local-storage-methodsV3.spec.js b/test/local-storage-methodsV3.spec.js new file mode 100644 index 0000000..d372e94 --- /dev/null +++ b/test/local-storage-methodsV3.spec.js @@ -0,0 +1,487 @@ +/* +Copyright 2019 Javier Brea +Copyright 2019 XbyOrange + +Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. You may obtain a copy of the License at + +http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License. +*/ + +const { providers } = require("@data-provider/core"); +const Storage = require("./Storage.mock"); + +const { LocalStorage } = require("../src/LocalStorage"); + +const wait = (time) => { + return new Promise((resolve) => { + setTimeout(() => resolve(), time); + }); +}; + +describe("Local Storage v3", () => { + let storage; + + beforeEach(() => { + storage = new Storage("localStorage"); + }); + + afterEach(() => { + storage.restore(); + providers.clear(); + }); + + describe("Available methods", () => { + it("should have all CRUD methods", () => { + const userData = new LocalStorage({ id: "userData", root: storage.mock }); + expect(userData.read).toBeDefined(); + expect(userData.update).toBeDefined(); + expect(userData.delete).toBeDefined(); + }); + + it("should return a localStorage mock from window if root object is not defined", () => { + expect.assertions(1); + const userData = new LocalStorage({ id: "userData" }); + expect(userData._storage.removeItem).toEqual(undefined); + }); + }); + + describe("Tags", () => { + it("should contain memory tag", () => { + const userData = new LocalStorage({ id: "userData", root: storage.mock }); + expect(userData._tags).toContain("local-storage"); + }); + }); + + describe("When window or localStorage are not available and it uses storage mock", () => { + let userData; + const fooData = { + foo: "foo-value", + }; + + beforeEach(() => { + userData = new LocalStorage({ id: "userData" }); + }); + + it("should clean the cache when finish successfully", async () => { + expect.assertions(3); + let promise = userData.read(); + expect(userData.state.loading).toEqual(true); + await promise; + await userData.update(""); + promise = userData.read(); + expect(userData.state.loading).toEqual(true); + return promise.then(() => { + expect(userData.state.loading).toEqual(false); + }); + }); + + it("should set the new value", async () => { + expect.assertions(1); + await userData.update(fooData); + await userData.read(); + expect(userData.state.data).toEqual(fooData); + }); + }); + + describe("When window or localStorage are not available and it has disabled storageFallback", () => { + let userData; + + beforeEach(() => { + userData = new LocalStorage({ id: "userData", storageFallback: false }); + }); + + it("should return an empty object as root value", async () => { + expect(userData.state.data).toEqual({}); + }); + + it("should return undefined for queried values", async () => { + expect(userData.query({ prop: "foo" }).state.data).toEqual(undefined); + }); + + it("should reject read method when reading", async () => { + let receivedError; + await userData.read().catch((error) => { + receivedError = error; + }); + expect(receivedError.message).toEqual(expect.stringContaining("window is not defined")); + }); + + it("should reject read method when queried", async () => { + let receivedError; + await userData + .query({ prop: "foo" }) + .read() + .catch((error) => { + receivedError = error; + }); + expect(receivedError.message).toEqual(expect.stringContaining("window is not defined")); + }); + + it("should reject update method", async () => { + let receivedError; + await userData.update({ foo: "foo" }).catch((error) => { + receivedError = error; + }); + expect(receivedError.message).toEqual(expect.stringContaining("window is not defined")); + }); + + it("should reject update method queried", async () => { + let receivedError; + await userData + .query({ prop: "foo" }) + .update("foo2") + .catch((error) => { + receivedError = error; + }); + expect(receivedError.message).toEqual(expect.stringContaining("window is not defined")); + }); + + it("should reject delete method", async () => { + let receivedError; + await userData.delete().catch((error) => { + receivedError = error; + }); + expect(receivedError.message).toEqual(expect.stringContaining("window is not defined")); + }); + + it("should reject delete method queried", async () => { + let receivedError; + await userData + .query({ prop: "foo" }) + .delete() + .catch((error) => { + receivedError = error; + }); + expect(receivedError.message).toEqual(expect.stringContaining("window is not defined")); + }); + }); + + describe("when localStorage getItem method throws", () => { + let error; + let userData; + + beforeEach(() => { + error = new Error("foo"); + storage = new Storage("localStorage"); + storage.stubs.getItem.throws(error); + userData = new LocalStorage({ id: "userData", root: storage.mock }); + }); + + it("should return an empty object as root value", async () => { + expect(userData.state.data).toEqual({}); + }); + + it("should return undefined for queried values", async () => { + expect(userData.query({ prop: "foo" }).state.data).toEqual(undefined); + }); + + it("should reject read method with getItem exception when reading", async () => { + let receivedError; + await userData.read().catch((err) => { + receivedError = err; + }); + expect(receivedError).toEqual(error); + }); + + it("should reject read method with getItem exception when queried", async () => { + let receivedError; + await userData + .query({ prop: "foo" }) + .read() + .catch((err) => { + receivedError = err; + }); + expect(receivedError).toEqual(error); + }); + }); + + describe("Loading property of a method", () => { + let userData; + + beforeEach(() => { + userData = new LocalStorage({ id: "userData", root: storage.mock }); + }); + + it("should be true while resource is being loaded, false when finished", () => { + expect.assertions(2); + const promise = userData.read(); + expect(userData.state.loading).toEqual(true); + return promise.then(() => { + expect(userData.state.loading).toEqual(false); + }); + }); + + it("should not be loading when request promise is cached", async () => { + expect.assertions(3); + await userData.read(); + expect(userData.state.loading).toEqual(false); + const secondRead = userData.read(); + expect(userData.state.loading).toEqual(false); + return secondRead.then(() => { + expect(userData.state.loading).toEqual(false); + }); + }); + + it("should be loading again after cleaning cache", async () => { + expect.assertions(3); + await userData.read(); + expect(userData.state.loading).toEqual(false); + userData.cleanCache(); + const secondRead = userData.read(); + expect(userData.state.loading).toEqual(true); + return secondRead.then(() => { + expect(userData.state.loading).toEqual(false); + }); + }); + + it("should be loading again after update method even when cleanCacheThrottle option is set", async () => { + expect.assertions(4); + userData.config({ + cleanCacheThrottle: 1000, + }); + await userData.read(); + expect(userData.state.loading).toEqual(false); + userData.cleanCache(); + const secondRead = userData.read(); + expect(userData.state.loading).toEqual(true); + await secondRead; + userData.cleanCache(); + const read = userData.read(); + expect(userData.state.loading).toEqual(false); + await read; + await userData.update({ foo: "foo" }); + const thirdRead = userData.read(); + expect(userData.state.loading).toEqual(true); + await thirdRead; + await wait(1000); + }); + }); + + describe("Data property of a method", () => { + let userData; + const fooData = { + foo: "foo-value", + }; + + beforeEach(() => { + storage.stubs.getItem.returns(JSON.stringify(fooData)); + userData = new LocalStorage({ id: "userData", root: storage.mock }); + }); + + describe("localStorage key", () => { + it("should be the provider id", async () => { + await userData.read(); + expect(storage.stubs.getItem.getCall(0).args[0]).toEqual("userData"); + }); + }); + + describe("without query", () => { + it("should be localStorage value while resource is being loaded", () => { + expect.assertions(2); + const promise = userData.read(); + expect(userData.state.data).toEqual(fooData); + return promise.then(() => { + expect(userData.state.data).toEqual(fooData); + }); + }); + }); + + describe("when queried", () => { + it("should return the property corresponding to applied query", async () => { + let queriedData = userData.query({ prop: "foo" }); + const result = await queriedData.read(); + expect(result).toEqual("foo-value"); + }); + + it("should return default value correspondent to query while resource is being loaded", () => { + expect.assertions(2); + userData = new LocalStorage({ id: "userData", root: storage.mock }); + let queriedData = userData.query({ prop: "foo" }); + const promise = queriedData.read(); + expect(queriedData.state.data).toEqual(fooData.foo); + return promise.then(() => { + expect(queriedData.state.data).toEqual(fooData.foo); + }); + }); + }); + }); + + describe("Update method", () => { + let userData; + const fooData = { + foo: "foo-value", + }; + + beforeEach(() => { + storage.stubs.getItem.returns(JSON.stringify(fooData)); + userData = new LocalStorage({ id: "userData", root: storage.mock }); + }); + + describe("localStorage key", () => { + it("should be the provider id", async () => { + await userData.update(""); + expect(storage.stubs.setItem.getCall(0).args[0]).toEqual("userData"); + }); + }); + + describe("without query", () => { + it("should clean the cache when finish successfully", async () => { + expect.assertions(3); + let promise = userData.read(); + expect(userData.state.loading).toEqual(true); + await promise; + await userData.update(""); + promise = userData.read(); + expect(userData.state.loading).toEqual(true); + return promise.then(() => { + expect(userData.state.loading).toEqual(false); + }); + }); + + it("should set the new value", async () => { + const newValue = { foo2: "foo-new-value" }; + await userData.update(newValue); + expect(storage.stubs.setItem.getCall(0).args[1]).toEqual(JSON.stringify(newValue)); + }); + }); + + describe("when queried", () => { + it("should set the property corresponding to applied query", async () => { + let queriedData = userData.query({ prop: "foo" }); + await queriedData.update("foo-updated-value"); + expect(storage.stubs.setItem.getCall(0).args[1]).toEqual( + JSON.stringify({ + foo: "foo-updated-value", + }) + ); + }); + + it("should clean the cache of root when finish successfully", async () => { + expect.assertions(3); + let promise = userData.read(); + expect(userData.state.loading).toEqual(true); + await promise; + await userData.query({ prop: "foo" }).update(""); + promise = userData.read(); + expect(userData.state.loading).toEqual(true); + return promise.then(() => { + expect(userData.state.loading).toEqual(false); + }); + }); + }); + + describe("when localStorage setItem method throws", () => { + it("should reject the promise with the received error", async () => { + let queriedData = userData.query({ prop: "foo" }); + let receivedError; + const error = new Error("foo"); + storage.stubs.setItem.throws(error); + await queriedData.update("foo-updated-value").catch((err) => { + receivedError = err; + }); + expect(receivedError).toEqual(error); + }); + }); + }); + + describe("Delete method", () => { + let userData; + const fooData = { + foo: "foo-value", + }; + + beforeEach(() => { + storage.stubs.getItem.returns(JSON.stringify(fooData)); + userData = new LocalStorage({ id: "userData", root: storage.mock }); + }); + + it("should clean the cache when finish successfully", async () => { + expect.assertions(3); + let promise = userData.read(); + expect(userData.state.loading).toEqual(true); + await promise; + await userData.delete(); + promise = userData.read(); + expect(userData.state.loading).toEqual(true); + return promise.then(() => { + expect(userData.state.loading).toEqual(false); + }); + }); + + describe("when queried", () => { + it("should delete the property corresponding to applied query", async () => { + let queriedData = userData.query({ prop: "foo" }); + await queriedData.delete(); + expect(storage.stubs.setItem.getCall(0).args[1]).toEqual(JSON.stringify({})); + }); + }); + + describe("when localStorage setItem method throws", () => { + it("should reject the promise with the received error", async () => { + let queriedData = userData.query({ prop: "foo" }); + let receivedError; + const error = new Error("foo"); + storage.stubs.setItem.throws(error); + await queriedData.delete().catch((err) => { + receivedError = err; + }); + expect(receivedError).toEqual(error); + }); + }); + }); + + describe("Instance tags", () => { + let fooData; + + describe("when no options are defined", () => { + beforeEach(() => { + fooData = new LocalStorage({ id: "fooData" }); + }); + + it("should contain the browser-storage tag", () => { + expect(providers.getByTag("browser-storage").elements[0]).toEqual(fooData); + }); + + it("should contain the local-storage tag", () => { + expect(providers.getByTag("local-storage").elements[0]).toEqual(fooData); + }); + }); + + describe("when passing tags", () => { + it("should contain the local-storage tag even when a custom tag is received", () => { + fooData = new LocalStorage({ id: "fooData", tags: ["foo-tag"] }); + expect(providers.getByTag("local-storage").elements[0]).toEqual(fooData); + }); + + it("should contain the ocal-storage tag even when an array of custom tags is received", () => { + fooData = new LocalStorage({ id: "fooData", tags: ["foo-tag", "foo-tag-2"] }); + expect(providers.getByTag("local-storage").elements[0]).toEqual(fooData); + }); + + it("should contain defined custom tag if received", () => { + const FOO_TAG = "foo-tag"; + fooData = new LocalStorage({ id: "fooData", tags: [FOO_TAG] }); + expect(providers.getByTag(FOO_TAG).elements[0]).toEqual(fooData); + }); + + it("should contain defined custom tags if received", () => { + expect.assertions(2); + const FOO_TAG = "foo-tag"; + const FOO_TAG_2 = "foo-tag-2"; + fooData = new LocalStorage({ id: "fooData", tags: [FOO_TAG, FOO_TAG_2] }); + expect(providers.getByTag(FOO_TAG).elements[0]).toEqual(fooData); + expect(providers.getByTag(FOO_TAG_2).elements[0]).toEqual(fooData); + }); + }); + }); + + describe("Instance id", () => { + it("should be assigned based on first argument", () => { + const FOO_ID = "foo-id"; + const fooData = new LocalStorage({ id: FOO_ID }); + expect(providers.getById(FOO_ID).elements[0]).toEqual(fooData); + }); + }); +}); diff --git a/test/session-storage-methodsV3.spec.js b/test/session-storage-methodsV3.spec.js new file mode 100644 index 0000000..b618dd8 --- /dev/null +++ b/test/session-storage-methodsV3.spec.js @@ -0,0 +1,321 @@ +/* +Copyright 2019 Javier Brea +Copyright 2019 XbyOrange + +Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. You may obtain a copy of the License at + +http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License. +*/ + +const { providers } = require("@data-provider/core"); +const Storage = require("./Storage.mock"); + +const { SessionStorage } = require("../src/SessionStorage"); + +describe("SessionStorage Storage v3", () => { + let storage; + + beforeEach(() => { + storage = new Storage("sessionStorage"); + }); + + afterEach(() => { + storage.restore(); + providers.clear(); + }); + + describe("Available methods", () => { + it("should have all CRUD methods", () => { + const userData = new SessionStorage({ id: "userData", root: storage.mock }); + expect(userData.read).toBeDefined(); + expect(userData.update).toBeDefined(); + expect(userData.delete).toBeDefined(); + }); + + it("should return a sessionStorage mock from window if root object is not defined", () => { + expect.assertions(1); + const userData = new SessionStorage({ id: "userData" }); + expect(userData._storage.removeItem).toEqual(undefined); + }); + }); + + describe("Tags", () => { + it("should contain memory tag", () => { + const userData = new SessionStorage({ id: "userData", root: storage.mock }); + expect(userData._tags).toContain("session-storage"); + }); + }); + + describe("When window is not available and it uses storage mock", () => { + let userData; + const fooData = { + foo: "foo-value", + }; + + beforeEach(() => { + userData = new SessionStorage({ id: "userData" }); + }); + + it("should clean the cache when finish successfully", async () => { + expect.assertions(3); + let promise = userData.read(); + expect(userData.state.loading).toEqual(true); + await promise; + await userData.update(""); + promise = userData.read(); + expect(userData.state.loading).toEqual(true); + return promise.then(() => { + expect(userData.state.loading).toEqual(false); + }); + }); + + it("should set the new value", async () => { + expect.assertions(1); + await userData.update(fooData); + await userData.read(); + expect(userData.state.data).toEqual(fooData); + }); + }); + + describe("Loading property of a method", () => { + let userData; + + beforeEach(() => { + userData = new SessionStorage({ id: "userData", root: storage.mock }); + }); + + it("should be true while resource is being loaded, false when finished", () => { + expect.assertions(2); + const promise = userData.read(); + expect(userData.state.loading).toEqual(true); + return promise.then(() => { + expect(userData.state.loading).toEqual(false); + }); + }); + + it("should not be loading when request promise is cached", async () => { + expect.assertions(3); + await userData.read(); + expect(userData.state.loading).toEqual(false); + const secondRead = userData.read(); + expect(userData.state.loading).toEqual(false); + return secondRead.then(() => { + expect(userData.state.loading).toEqual(false); + }); + }); + + it("should be loading again after cleaning cache", async () => { + expect.assertions(3); + await userData.read(); + expect(userData.state.loading).toEqual(false); + userData.cleanCache(); + const secondRead = userData.read(); + expect(userData.state.loading).toEqual(true); + return secondRead.then(() => { + expect(userData.state.loading).toEqual(false); + }); + }); + }); + + describe("Data property of a method", () => { + let userData; + const fooData = { + foo: "foo-value", + }; + + beforeEach(() => { + storage.stubs.getItem.returns(JSON.stringify(fooData)); + userData = new SessionStorage({ id: "userData", root: storage.mock }); + }); + + describe("browserStorage key", () => { + it("should be the provider id", async () => { + await userData.read(); + expect(storage.stubs.getItem.getCall(0).args[0]).toEqual("userData"); + }); + }); + + describe("without query", () => { + it("should be sessionStorage value while resource is being loaded", () => { + expect.assertions(2); + const promise = userData.read(); + expect(userData.state.data).toEqual(fooData); + return promise.then(() => { + expect(userData.state.data).toEqual(fooData); + }); + }); + }); + + describe("when queried", () => { + it("should return the property corresponding to applied query", async () => { + let queriedData = userData.query({ prop: "foo" }); + const result = await queriedData.read(); + expect(result).toEqual("foo-value"); + }); + + it("should return default value correspondent to query while resource is being loaded", () => { + expect.assertions(2); + userData = new SessionStorage({ id: "userData", root: storage.mock }); + let queriedData = userData.query({ prop: "foo" }); + const promise = queriedData.read(); + expect(queriedData.state.data).toEqual(fooData.foo); + return promise.then(() => { + expect(queriedData.state.data).toEqual(fooData.foo); + }); + }); + }); + }); + + describe("Update method", () => { + let userData; + const fooData = { + foo: "foo-value", + }; + + beforeEach(() => { + storage.stubs.getItem.returns(JSON.stringify(fooData)); + userData = new SessionStorage({ id: "userData", root: storage.mock }); + }); + + describe("browserStorage key", () => { + it("should be the provider id", async () => { + await userData.update(""); + expect(storage.stubs.setItem.getCall(0).args[0]).toEqual("userData"); + }); + }); + + describe("without query", () => { + it("should clean the cache when finish successfully", async () => { + expect.assertions(3); + let promise = userData.read(); + expect(userData.state.loading).toEqual(true); + await promise; + await userData.update(""); + promise = userData.read(); + expect(userData.state.loading).toEqual(true); + return promise.then(() => { + expect(userData.state.loading).toEqual(false); + }); + }); + + it("should set the new value", async () => { + const newValue = { foo2: "foo-new-value" }; + await userData.update(newValue); + expect(storage.stubs.setItem.getCall(0).args[1]).toEqual(JSON.stringify(newValue)); + }); + }); + + describe("when queried", () => { + it("should set the property corresponding to applied query", async () => { + let queriedData = userData.query({ prop: "foo" }); + await queriedData.update("foo-updated-value"); + expect(storage.stubs.setItem.getCall(0).args[1]).toEqual( + JSON.stringify({ + foo: "foo-updated-value", + }) + ); + }); + + it("should clean the cache of root when finish successfully", async () => { + expect.assertions(3); + let promise = userData.read(); + expect(userData.state.loading).toEqual(true); + await promise; + await userData.query({ prop: "foo" }).update(""); + promise = userData.read(); + expect(userData.state.loading).toEqual(true); + return promise.then(() => { + expect(userData.state.loading).toEqual(false); + }); + }); + }); + }); + + describe("Delete method", () => { + let userData; + const fooData = { + foo: "foo-value", + }; + + beforeEach(() => { + storage.stubs.getItem.returns(JSON.stringify(fooData)); + userData = new SessionStorage({ id: "userData", root: storage.mock }); + }); + + it("should clean the cache when finish successfully", async () => { + expect.assertions(3); + let promise = userData.read(); + expect(userData.state.loading).toEqual(true); + await promise; + await userData.delete(); + promise = userData.read(); + expect(userData.state.loading).toEqual(true); + return promise.then(() => { + expect(userData.state.loading).toEqual(false); + }); + }); + + describe("when queried", () => { + it("should delete the property corresponding to applied query", async () => { + let queriedData = userData.query({ prop: "foo" }); + await queriedData.delete(); + expect(storage.stubs.setItem.getCall(0).args[1]).toEqual(JSON.stringify({})); + }); + }); + }); + + describe("Instance tags", () => { + let fooData; + + describe("when no options are defined", () => { + beforeEach(() => { + fooData = new SessionStorage({ id: "fooData" }); + }); + + it("should contain the browser-storage tag", () => { + expect(providers.getByTag("browser-storage").elements[0]).toEqual(fooData); + }); + + it("should contain the session-storage tag", () => { + expect(providers.getByTag("session-storage").elements[0]).toEqual(fooData); + }); + }); + + describe("when passing tags", () => { + it("should contain the session-storage tag even when a custom tag is received", () => { + fooData = new SessionStorage({ id: "fooData", tags: ["foo-tag"] }); + expect(providers.getByTag("session-storage").elements[0]).toEqual(fooData); + }); + + it("should contain the ocal-storage tag even when an array of custom tags is received", () => { + fooData = new SessionStorage({ id: "fooData", tags: ["foo-tag", "foo-tag-2"] }); + expect(providers.getByTag("session-storage").elements[0]).toEqual(fooData); + }); + + it("should contain defined custom tag if received", () => { + const FOO_TAG = "foo-tag"; + fooData = new SessionStorage({ id: "fooData", tags: [FOO_TAG] }); + expect(providers.getByTag(FOO_TAG).elements[0]).toEqual(fooData); + }); + + it("should contain defined custom tags if received", () => { + expect.assertions(2); + const FOO_TAG = "foo-tag"; + const FOO_TAG_2 = "foo-tag-2"; + fooData = new SessionStorage({ id: "fooData", tags: [FOO_TAG, FOO_TAG_2] }); + expect(providers.getByTag(FOO_TAG).elements[0]).toEqual(fooData); + expect(providers.getByTag(FOO_TAG_2).elements[0]).toEqual(fooData); + }); + }); + }); + + describe("Instance id", () => { + it("should be assigned based on first argument", () => { + const FOO_ID = "foo-id"; + const fooData = new SessionStorage({ id: FOO_ID }); + expect(providers.getById(FOO_ID).elements[0]).toEqual(fooData); + }); + }); +});