diff --git a/CHANGELOG.md b/CHANGELOG.md index 18b64b6..09fc09d 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -6,6 +6,7 @@ and this project adheres to [Semantic Versioning](http://semver.org/). ## [unreleased] ### Added +- feat: Add compatibility with @data-provider/core v3 arguments. ### Changed ### Fixed ### Removed diff --git a/jest.config.js b/jest.config.js index a9408ef..a0a80e6 100644 --- a/jest.config.js +++ b/jest.config.js @@ -28,7 +28,8 @@ module.exports = { testEnvironment: "node", // The glob patterns Jest uses to detect test files - testMatch: ["**/test/**/?(*.)+(spec|test).js?(x)"], + testMatch: ["/test/**/*.spec.js"], + testMatch: ["/test/**/memoryV3.spec.js"], transform: { ".js$": "babel-jest", diff --git a/package-lock.json b/package-lock.json index 50c03f6..df6d0ca 100644 --- a/package-lock.json +++ b/package-lock.json @@ -2207,12 +2207,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": { @@ -9225,6 +9226,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 01ba9dc..347e677 100644 --- a/package.json +++ b/package.json @@ -37,12 +37,12 @@ "test:mutation": "stryker run" }, "peerDependencies": { - "@data-provider/core": "2.x" + "@data-provider/core": ">=2.10.0" }, "devDependencies": { "@babel/core": "7.12.10", "@babel/preset-env": "7.12.10", - "@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/src/Memory.js b/src/Memory.js index 16c0a12..3184f90 100644 --- a/src/Memory.js +++ b/src/Memory.js @@ -9,13 +9,14 @@ 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. */ -import { Provider } from "@data-provider/core"; +import { Provider, providerArgsV3 } from "@data-provider/core"; const TAG = "memory"; class Memory extends Provider { - constructor(id, options, query) { - super(id, options, query); + constructor(...args) { + const [id, options, queryValue] = providerArgsV3(args); + super({ id, ...options }, queryValue); this._data = this.initialState.data; this.options._data = this._options._data || this._data; } diff --git a/test/memoryV3.spec.js b/test/memoryV3.spec.js new file mode 100644 index 0000000..3b2fe93 --- /dev/null +++ b/test/memoryV3.spec.js @@ -0,0 +1,356 @@ +/* +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 { Memory } = require("../src/index"); + +const wait = (time) => { + return new Promise((resolve) => { + setTimeout(() => resolve(), time); + }); +}; + +describe("Memory origin V3", () => { + afterEach(() => { + providers.clear(); + }); + + describe("Available methods", () => { + it("should have all CRUD methods", () => { + const userData = new Memory(); + expect(userData.read).toBeDefined(); + expect(userData.update).toBeDefined(); + expect(userData.delete).toBeDefined(); + }); + }); + + describe("Tags", () => { + it("should contain memory tag", () => { + const userData = new Memory(); + expect(userData._tags).toContain("memory"); + }); + }); + + describe("Loading property of a method", () => { + let userData; + + beforeAll(() => { + userData = new Memory(); + }); + + 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 an update even when provider is throttled", 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("Value property of a method", () => { + let userData; + const fooData = { + foo: "foo-value", + }; + + beforeEach(() => { + userData = new Memory({ + id: "foo-id", + initialState: { + data: fooData, + }, + }); + }); + + describe("without initialState", () => { + it("should return an empty object while resource is being loaded", () => { + userData = new Memory({ id: "foo-id-2" }); + expect.assertions(2); + const promise = userData.read(); + expect(userData.state.data).toEqual({}); + return promise.then(() => { + expect(userData.state.data).toEqual({}); + }); + }); + }); + + describe("without query", () => { + it("should return default 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 default value correspondent to query while resource is being loaded", () => { + expect.assertions(2); + userData = new Memory({ + id: "foo-id-2", + initialState: { + data: fooData, + }, + }); + let queriedData = userData.query({ prop: "foo" }); + const promise = queriedData.read(); + expect(queriedData.state.data).toEqual(fooData.foo); + return promise.then((value) => { + expect(value).toEqual(fooData.foo); + }); + }); + + 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"); + }); + }); + }); + + describe("Update method", () => { + let userData; + let fooData; + + beforeEach(() => { + fooData = { + foo: "foo-value", + }; + + userData = new Memory({ + id: "foo-id", + initialState: { + data: fooData, + }, + }); + }); + + 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); + const value = await userData.read(); + expect(value).toEqual(newValue); + }); + }); + + describe("when queried", () => { + it("should set the property corresponding to applied query in parent", async () => { + let queriedData = userData.query({ prop: "foo" }); + await queriedData.update("foo-updated-value"); + const value = await userData.read(); + expect(value).toEqual({ + foo: "foo-updated-value", + }); + }); + + it("should set the property corresponding to applied query", async () => { + let queriedData = userData.query({ prop: "foo" }); + await queriedData.update("foo-updated-value"); + const value = await queriedData.read(); + expect(value).toEqual("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 property did not exist", () => { + it("should add the new prop in parent", async () => { + await userData.query({ prop: "foo2" }).update("foo-new"); + const value = await userData.read(); + expect(value).toEqual({ + foo: "foo-value", + foo2: "foo-new", + }); + }); + + it("should add the new prop", async () => { + await userData.query({ prop: "foo2" }).update("foo-new"); + const value = await userData.query({ prop: "foo2" }).read(); + expect(value).toEqual("foo-new"); + }); + }); + }); + + describe("Delete method", () => { + let userData; + const fooData = { + foo: "foo-value", + foo2: "foo-value-2", + }; + + beforeEach(() => { + userData = new Memory({ + id: "foo-id-2", + initialState: { + data: fooData, + }, + }); + }); + + it("should delete the property corresponding to applied query in parent", async () => { + let queriedData = userData.query({ prop: "foo" }); + await queriedData.delete(); + await userData.read(); + expect(userData.state.data).toEqual({ + foo2: "foo-value-2", + }); + }); + + it("should delete the property corresponding to applied query", async () => { + let queriedData = userData.query({ prop: "foo" }); + await queriedData.delete(); + await queriedData.read(); + expect(queriedData.state.data).toEqual(undefined); + }); + + 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("Instance id", () => { + it("should be assigned based on first argument", () => { + const FOO_ID = "foo-id"; + const fooData = new Memory({ id: FOO_ID }); + expect(providers.getById(FOO_ID).elements[0]).toEqual(fooData); + }); + }); + + describe("Instance tags", () => { + describe("when no options are defined", () => { + it("should contain the memory tag", () => { + const fooData = new Memory({ id: "foo" }); + expect(providers.getByTag("memory").elements[0]).toEqual(fooData); + }); + }); + + describe("when passing options as second argument", () => { + it("should contain the memory tag even when a custom tag is received", () => { + const fooData = new Memory({ + id: "foo", + tags: ["foo-tag"], + }); + expect(providers.getByTag("memory").elements[0]).toEqual(fooData); + }); + + it("should contain the memory tag even when an array of custom tags is received", () => { + const fooData = new Memory({ + id: "foo", + tags: ["foo-tag", "foo-tag-2"], + }); + expect(providers.getByTag("memory").elements[0]).toEqual(fooData); + }); + + it("should contain defined custom tag if received", () => { + const FOO_TAG = "foo-tag"; + const fooData = new Memory({ + id: "foo", + 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"; + const fooData = new Memory({ + id: "foo", + 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); + }); + }); + }); +});