From ef0c81cad6163b1daacd9dabd2f867c346bf239a Mon Sep 17 00:00:00 2001 From: "javier.brea" Date: Sun, 8 Nov 2020 10:57:13 +0100 Subject: [PATCH 01/41] chore(actions): Check if package.json version has been changed since last publish --- .github/workflows/check-package-version.yml | 14 ++++++++++++++ 1 file changed, 14 insertions(+) create mode 100644 .github/workflows/check-package-version.yml diff --git a/.github/workflows/check-package-version.yml b/.github/workflows/check-package-version.yml new file mode 100644 index 0000000..4ffb66c --- /dev/null +++ b/.github/workflows/check-package-version.yml @@ -0,0 +1,14 @@ +name: learn-github-actions +on: [push] +jobs: + check-package-version: + runs-on: ubuntu-latest + steps: + - id: check + uses: EndBug/version-check@v1.6.0 + with: + file-url: https://unpkg.com/@data-provider/react@latest/package.json + static-checking: localIsNew + - name: Fail when unchanged + if: steps.check.outputs.changed != 'true' + run: exit 1 From 549a96c2b753cac9bc1538a92c9a811cc7a564f2 Mon Sep 17 00:00:00 2001 From: "javier.brea" Date: Sun, 8 Nov 2020 11:02:48 +0100 Subject: [PATCH 02/41] chore: Fix check-package-version action --- .github/workflows/check-package-version.yml | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/.github/workflows/check-package-version.yml b/.github/workflows/check-package-version.yml index 4ffb66c..725bcd2 100644 --- a/.github/workflows/check-package-version.yml +++ b/.github/workflows/check-package-version.yml @@ -1,4 +1,4 @@ -name: learn-github-actions +name: check-package-version on: [push] jobs: check-package-version: @@ -7,6 +7,7 @@ jobs: - id: check uses: EndBug/version-check@v1.6.0 with: + diff-search: true file-url: https://unpkg.com/@data-provider/react@latest/package.json static-checking: localIsNew - name: Fail when unchanged From 893f88193a0bc4f4928c82999258d5979a17c845 Mon Sep 17 00:00:00 2001 From: "javier.brea" Date: Sun, 8 Nov 2020 11:04:01 +0100 Subject: [PATCH 03/41] chore: Fix check-package-version action --- .github/workflows/check-package-version.yml | 1 + 1 file changed, 1 insertion(+) diff --git a/.github/workflows/check-package-version.yml b/.github/workflows/check-package-version.yml index 725bcd2..02e7147 100644 --- a/.github/workflows/check-package-version.yml +++ b/.github/workflows/check-package-version.yml @@ -8,6 +8,7 @@ jobs: uses: EndBug/version-check@v1.6.0 with: diff-search: true + file-name: ./package.json file-url: https://unpkg.com/@data-provider/react@latest/package.json static-checking: localIsNew - name: Fail when unchanged From 037f735af55cf3492bfd3900dfc106fe6ab03bab Mon Sep 17 00:00:00 2001 From: "javier.brea" Date: Sun, 8 Nov 2020 11:08:35 +0100 Subject: [PATCH 04/41] chore: Fix check-package-version action --- .github/workflows/check-package-version.yml | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/.github/workflows/check-package-version.yml b/.github/workflows/check-package-version.yml index 02e7147..8e9d2a2 100644 --- a/.github/workflows/check-package-version.yml +++ b/.github/workflows/check-package-version.yml @@ -4,7 +4,10 @@ jobs: check-package-version: runs-on: ubuntu-latest steps: - - id: check + - name: Checkout + uses: actions/checkout@v2 + - name: Check local version + id: check uses: EndBug/version-check@v1.6.0 with: diff-search: true From 97c9213c0eb048f21b00b20afd813342e8c17720 Mon Sep 17 00:00:00 2001 From: "javier.brea" Date: Sun, 8 Nov 2020 11:09:39 +0100 Subject: [PATCH 05/41] chore(version): Upgrade version for testing action --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index f9cc0bb..dbeeaf6 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "@data-provider/react", - "version": "1.3.0", + "version": "1.3.1", "description": "React bindings for @data-provider", "keywords": [ "data-provider", From b15c71cb6493d0ac6d81bba84ec60666eda1729a Mon Sep 17 00:00:00 2001 From: "javier.brea" Date: Sun, 8 Nov 2020 11:12:07 +0100 Subject: [PATCH 06/41] chore(action): Improve logs when version is not upgraded --- .github/workflows/check-package-version.yml | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/.github/workflows/check-package-version.yml b/.github/workflows/check-package-version.yml index 8e9d2a2..4399a89 100644 --- a/.github/workflows/check-package-version.yml +++ b/.github/workflows/check-package-version.yml @@ -6,7 +6,7 @@ jobs: steps: - name: Checkout uses: actions/checkout@v2 - - name: Check local version + - name: Check local version is newer id: check uses: EndBug/version-check@v1.6.0 with: @@ -14,6 +14,8 @@ jobs: file-name: ./package.json file-url: https://unpkg.com/@data-provider/react@latest/package.json static-checking: localIsNew - - name: Fail when unchanged + - name: Fail when version not upgraded if: steps.check.outputs.changed != 'true' - run: exit 1 + run: | + echo "Version not upgraded" + exit 1 From 8ff91ddfcbc02a5e61f123f3e4d28586cc049fac Mon Sep 17 00:00:00 2001 From: "javier.brea" Date: Sun, 8 Nov 2020 11:18:57 +0100 Subject: [PATCH 07/41] chore(ci/cd): Use existing version again to test github action --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index dbeeaf6..f9cc0bb 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "@data-provider/react", - "version": "1.3.1", + "version": "1.3.0", "description": "React bindings for @data-provider", "keywords": [ "data-provider", From 2426b23d2f6f6e45dc4b467ed78b6d256f2df4c2 Mon Sep 17 00:00:00 2001 From: "javier.brea" Date: Sun, 8 Nov 2020 11:38:51 +0100 Subject: [PATCH 08/41] chore(ci-cd): Check if version has an entry defined in CHANGELOG --- .github/workflows/check-package-version.yml | 17 ++++++++++++++++- 1 file changed, 16 insertions(+), 1 deletion(-) diff --git a/.github/workflows/check-package-version.yml b/.github/workflows/check-package-version.yml index 4399a89..ad8c64b 100644 --- a/.github/workflows/check-package-version.yml +++ b/.github/workflows/check-package-version.yml @@ -6,7 +6,7 @@ jobs: steps: - name: Checkout uses: actions/checkout@v2 - - name: Check local version is newer + - name: Check NPM version is new id: check uses: EndBug/version-check@v1.6.0 with: @@ -19,3 +19,18 @@ jobs: run: | echo "Version not upgraded" exit 1 + - name: Get NPM version + id: package-version + uses: martinbeentjes/npm-get-version-action@v1.1.0 + - name: Get Changelog Entry + id: changelog_reader + uses: mindsers/changelog-reader-action@v2.0.0 + with: + validation_depth: 10 + version: ${{ steps.package-version.outputs.current-version }} + path: ./CHANGELOG.md + - name: Fail when version has not entry in CHANGELOG + if: steps.changelog_reader.outputs.version != steps.package-version.outputs.current-version + run: | + echo "Version not included in CHANGELOG" + exit 1 From dc507e5fdd00c029ffabbc8d847bec24ab60d1c6 Mon Sep 17 00:00:00 2001 From: "javier.brea" Date: Sun, 8 Nov 2020 11:41:08 +0100 Subject: [PATCH 09/41] chore(ci-cd): Test package upgraded --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index f9cc0bb..dbeeaf6 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "@data-provider/react", - "version": "1.3.0", + "version": "1.3.1", "description": "React bindings for @data-provider", "keywords": [ "data-provider", From d2ea83c23880e632d98daadb2d332078ad4802c2 Mon Sep 17 00:00:00 2001 From: "javier.brea" Date: Sun, 8 Nov 2020 11:44:15 +0100 Subject: [PATCH 10/41] chore(ci-cd): Change github changelog action config --- .github/workflows/check-package-version.yml | 1 - 1 file changed, 1 deletion(-) diff --git a/.github/workflows/check-package-version.yml b/.github/workflows/check-package-version.yml index ad8c64b..a4e48c8 100644 --- a/.github/workflows/check-package-version.yml +++ b/.github/workflows/check-package-version.yml @@ -26,7 +26,6 @@ jobs: id: changelog_reader uses: mindsers/changelog-reader-action@v2.0.0 with: - validation_depth: 10 version: ${{ steps.package-version.outputs.current-version }} path: ./CHANGELOG.md - name: Fail when version has not entry in CHANGELOG From 9519fcd2b121f06e125065ce3ba63630701a9775 Mon Sep 17 00:00:00 2001 From: "javier.brea" Date: Sun, 8 Nov 2020 11:45:10 +0100 Subject: [PATCH 11/41] chore(ci-cd): Add entry to changelog to test github action --- CHANGELOG.md | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index bdcdaf5..b4288ba 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -10,6 +10,11 @@ and this project adheres to [Semantic Versioning](http://semver.org/). ### Fixed ### Removed +## [1.3.1] - 2020-10-31 + +### Added +- chore(ci-cd): Test github action + ## [1.3.0] - 2020-10-31 ### Added From 4bab204df9f74d20bbc4c853158a624fc12e646b Mon Sep 17 00:00:00 2001 From: "javier.brea" Date: Sun, 8 Nov 2020 11:46:29 +0100 Subject: [PATCH 12/41] chore(ci-cd): Test with an older npm version --- CHANGELOG.md | 2 +- package.json | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index b4288ba..397210d 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -10,7 +10,7 @@ and this project adheres to [Semantic Versioning](http://semver.org/). ### Fixed ### Removed -## [1.3.1] - 2020-10-31 +## [1.1.8] - 2020-10-31 ### Added - chore(ci-cd): Test github action diff --git a/package.json b/package.json index dbeeaf6..6e2873e 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "@data-provider/react", - "version": "1.3.1", + "version": "1.1.8", "description": "React bindings for @data-provider", "keywords": [ "data-provider", From daf11a21fed58e15caef58a1d5d857095e5ff83b Mon Sep 17 00:00:00 2001 From: "javier.brea" Date: Sun, 8 Nov 2020 11:52:43 +0100 Subject: [PATCH 13/41] chore(ci-cd): Check version in Sonar config --- .github/workflows/check-package-version.yml | 18 ++++++++++++------ CHANGELOG.md | 2 +- package.json | 2 +- 3 files changed, 14 insertions(+), 8 deletions(-) diff --git a/.github/workflows/check-package-version.yml b/.github/workflows/check-package-version.yml index a4e48c8..28344e5 100644 --- a/.github/workflows/check-package-version.yml +++ b/.github/workflows/check-package-version.yml @@ -14,22 +14,28 @@ jobs: file-name: ./package.json file-url: https://unpkg.com/@data-provider/react@latest/package.json static-checking: localIsNew - - name: Fail when version not upgraded + - name: Fail when version exists if: steps.check.outputs.changed != 'true' run: | - echo "Version not upgraded" + echo "Version not changed" exit 1 - name: Get NPM version id: package-version uses: martinbeentjes/npm-get-version-action@v1.1.0 - - name: Get Changelog Entry + - name: Check Changelog version id: changelog_reader uses: mindsers/changelog-reader-action@v2.0.0 with: version: ${{ steps.package-version.outputs.current-version }} path: ./CHANGELOG.md - - name: Fail when version has not entry in CHANGELOG - if: steps.changelog_reader.outputs.version != steps.package-version.outputs.current-version + - name: Read version from Sonar config + id: sonar-version + uses: christian-draeger/read-properties@1.0.1 + with: + path: './sonar-project.properties' + property: 'sonar.projectVersion' + - name: Check Sonar version + if: steps.sonar-version.outputs.value != steps.package-version.outputs.current-version run: | - echo "Version not included in CHANGELOG" + echo "Version not changed" exit 1 diff --git a/CHANGELOG.md b/CHANGELOG.md index 397210d..b4288ba 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -10,7 +10,7 @@ and this project adheres to [Semantic Versioning](http://semver.org/). ### Fixed ### Removed -## [1.1.8] - 2020-10-31 +## [1.3.1] - 2020-10-31 ### Added - chore(ci-cd): Test github action diff --git a/package.json b/package.json index 6e2873e..dbeeaf6 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "@data-provider/react", - "version": "1.1.8", + "version": "1.3.1", "description": "React bindings for @data-provider", "keywords": [ "data-provider", From 17f61dcb3b2586e665857807796cf99b6ed46950 Mon Sep 17 00:00:00 2001 From: "javier.brea" Date: Sun, 8 Nov 2020 11:53:50 +0100 Subject: [PATCH 14/41] chore(ci-cd): Upgrade Sonar version to check github action --- sonar-project.properties | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/sonar-project.properties b/sonar-project.properties index 753656c..fc8d4fb 100644 --- a/sonar-project.properties +++ b/sonar-project.properties @@ -1,6 +1,6 @@ sonar.organization=data-provider sonar.projectKey=data-provider-react -sonar.projectVersion=1.3.0 +sonar.projectVersion=1.3.1 sonar.sources=src,test sonar.exclusions=node_modules/** From 6e71ec999a11c5d9dfa31e793eb012df773f0184 Mon Sep 17 00:00:00 2001 From: "javier.brea" Date: Sun, 8 Nov 2020 11:56:10 +0100 Subject: [PATCH 15/41] chore(ci-cd): Change actions names --- .github/workflows/check-package-version.yml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/workflows/check-package-version.yml b/.github/workflows/check-package-version.yml index 28344e5..0534cb9 100644 --- a/.github/workflows/check-package-version.yml +++ b/.github/workflows/check-package-version.yml @@ -6,7 +6,7 @@ jobs: steps: - name: Checkout uses: actions/checkout@v2 - - name: Check NPM version is new + - name: Get NPM version is new id: check uses: EndBug/version-check@v1.6.0 with: @@ -14,7 +14,7 @@ jobs: file-name: ./package.json file-url: https://unpkg.com/@data-provider/react@latest/package.json static-checking: localIsNew - - name: Fail when version exists + - name: Check version is new if: steps.check.outputs.changed != 'true' run: | echo "Version not changed" From 4a5c808c7b7ffcc29e4132eb83d87d447295dd32 Mon Sep 17 00:00:00 2001 From: "javier.brea" Date: Sun, 8 Nov 2020 11:57:36 +0100 Subject: [PATCH 16/41] chore(ci-cd): Run check-package-version action only on PRs to master --- .github/workflows/check-package-version.yml | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/.github/workflows/check-package-version.yml b/.github/workflows/check-package-version.yml index 0534cb9..4b20aba 100644 --- a/.github/workflows/check-package-version.yml +++ b/.github/workflows/check-package-version.yml @@ -1,5 +1,8 @@ name: check-package-version -on: [push] +on: + pull_request: + branches: + - master jobs: check-package-version: runs-on: ubuntu-latest From 365e0cf36403a4aac8bc279213517ee5177c0b51 Mon Sep 17 00:00:00 2001 From: "javier.brea" Date: Sun, 8 Nov 2020 11:58:10 +0100 Subject: [PATCH 17/41] chore(ci-cd): Restore package version --- CHANGELOG.md | 5 ----- package.json | 2 +- sonar-project.properties | 2 +- 3 files changed, 2 insertions(+), 7 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index b4288ba..bdcdaf5 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -10,11 +10,6 @@ and this project adheres to [Semantic Versioning](http://semver.org/). ### Fixed ### Removed -## [1.3.1] - 2020-10-31 - -### Added -- chore(ci-cd): Test github action - ## [1.3.0] - 2020-10-31 ### Added diff --git a/package.json b/package.json index dbeeaf6..f9cc0bb 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "@data-provider/react", - "version": "1.3.1", + "version": "1.3.0", "description": "React bindings for @data-provider", "keywords": [ "data-provider", diff --git a/sonar-project.properties b/sonar-project.properties index fc8d4fb..753656c 100644 --- a/sonar-project.properties +++ b/sonar-project.properties @@ -1,6 +1,6 @@ sonar.organization=data-provider sonar.projectKey=data-provider-react -sonar.projectVersion=1.3.1 +sonar.projectVersion=1.3.0 sonar.sources=src,test sonar.exclusions=node_modules/** From 133d1097f6da0f6481f2bce5f1a69301e63508db Mon Sep 17 00:00:00 2001 From: "javier.brea" Date: Sun, 8 Nov 2020 12:09:56 +0100 Subject: [PATCH 18/41] chore(release): Publish to github registry --- .../workflows/publish-to-github-registry.yml | 21 +++++++++++++++++++ 1 file changed, 21 insertions(+) create mode 100644 .github/workflows/publish-to-github-registry.yml diff --git a/.github/workflows/publish-to-github-registry.yml b/.github/workflows/publish-to-github-registry.yml new file mode 100644 index 0000000..55eb0f5 --- /dev/null +++ b/.github/workflows/publish-to-github-registry.yml @@ -0,0 +1,21 @@ +name: publish-to-github-registry +on: + release: + types: [created] +jobs: + build: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v2 + # Setup .npmrc file to publish to GitHub Packages + - uses: actions/setup-node@v1 + with: + node-version: '14.x' + registry-url: 'https://npm.pkg.github.com' + # Defaults to the user or organization that owns the workflow file + scope: '@data-provider' + - run: npm install + - run: npm run build + - run: npm publish + env: + NODE_AUTH_TOKEN: ${{ secrets.GITHUB_TOKEN }} From 172efca83416c4e2202cb49af00970760697eb7b Mon Sep 17 00:00:00 2001 From: "javier.brea" Date: Sun, 8 Nov 2020 12:12:02 +0100 Subject: [PATCH 19/41] chore(release): Publish to github registry current version --- .github/workflows/publish-to-github-registry.yml | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/.github/workflows/publish-to-github-registry.yml b/.github/workflows/publish-to-github-registry.yml index 55eb0f5..a6e1091 100644 --- a/.github/workflows/publish-to-github-registry.yml +++ b/.github/workflows/publish-to-github-registry.yml @@ -1,7 +1,5 @@ name: publish-to-github-registry -on: - release: - types: [created] +on: [push] jobs: build: runs-on: ubuntu-latest From 814514b574f85a868ce3df01fd21f4591cc96f11 Mon Sep 17 00:00:00 2001 From: "javier.brea" Date: Sun, 8 Nov 2020 12:23:22 +0100 Subject: [PATCH 20/41] chore(ci-cd): Add auth token to npm i stage --- .github/workflows/publish-to-github-registry.yml | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/.github/workflows/publish-to-github-registry.yml b/.github/workflows/publish-to-github-registry.yml index a6e1091..55eb0f5 100644 --- a/.github/workflows/publish-to-github-registry.yml +++ b/.github/workflows/publish-to-github-registry.yml @@ -1,5 +1,7 @@ name: publish-to-github-registry -on: [push] +on: + release: + types: [created] jobs: build: runs-on: ubuntu-latest From 47bf99bebb5b7c1d7a958459a124290e05c349fe Mon Sep 17 00:00:00 2001 From: "javier.brea" Date: Sun, 8 Nov 2020 12:24:20 +0100 Subject: [PATCH 21/41] chore(ci-cd): Puclish current version --- .github/workflows/publish-to-github-registry.yml | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/.github/workflows/publish-to-github-registry.yml b/.github/workflows/publish-to-github-registry.yml index 55eb0f5..b65b125 100644 --- a/.github/workflows/publish-to-github-registry.yml +++ b/.github/workflows/publish-to-github-registry.yml @@ -1,7 +1,5 @@ name: publish-to-github-registry -on: - release: - types: [created] +on: [push] jobs: build: runs-on: ubuntu-latest @@ -15,6 +13,8 @@ jobs: # Defaults to the user or organization that owns the workflow file scope: '@data-provider' - run: npm install + env: + NODE_AUTH_TOKEN: ${{ secrets.GITHUB_TOKEN }} - run: npm run build - run: npm publish env: From c05729fa7ef656eaeea7078252f0d74f9fc52076 Mon Sep 17 00:00:00 2001 From: "javier.brea" Date: Sun, 8 Nov 2020 12:30:54 +0100 Subject: [PATCH 22/41] chore(ci-cd): Set environment variables for entire job --- .github/workflows/publish-to-github-registry.yml | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/.github/workflows/publish-to-github-registry.yml b/.github/workflows/publish-to-github-registry.yml index b65b125..d7dc519 100644 --- a/.github/workflows/publish-to-github-registry.yml +++ b/.github/workflows/publish-to-github-registry.yml @@ -3,6 +3,8 @@ on: [push] jobs: build: runs-on: ubuntu-latest + env: + NODE_AUTH_TOKEN: ${{ secrets.GITHUB_TOKEN }} steps: - uses: actions/checkout@v2 # Setup .npmrc file to publish to GitHub Packages @@ -13,9 +15,5 @@ jobs: # Defaults to the user or organization that owns the workflow file scope: '@data-provider' - run: npm install - env: - NODE_AUTH_TOKEN: ${{ secrets.GITHUB_TOKEN }} - run: npm run build - run: npm publish - env: - NODE_AUTH_TOKEN: ${{ secrets.GITHUB_TOKEN }} From 727d39e86dd55444de790bf64bb616c0ac4bbe6c Mon Sep 17 00:00:00 2001 From: "javier.brea" Date: Sun, 8 Nov 2020 12:31:34 +0100 Subject: [PATCH 23/41] chore(ci-cd): Fix indentation --- .github/workflows/publish-to-github-registry.yml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/workflows/publish-to-github-registry.yml b/.github/workflows/publish-to-github-registry.yml index d7dc519..a925746 100644 --- a/.github/workflows/publish-to-github-registry.yml +++ b/.github/workflows/publish-to-github-registry.yml @@ -1,10 +1,10 @@ name: publish-to-github-registry on: [push] jobs: - build: + publish: runs-on: ubuntu-latest env: - NODE_AUTH_TOKEN: ${{ secrets.GITHUB_TOKEN }} + NODE_AUTH_TOKEN: ${{ secrets.GITHUB_TOKEN }} steps: - uses: actions/checkout@v2 # Setup .npmrc file to publish to GitHub Packages From 05f8054a1f3854d344bfc167a4ce4ab1d64e1fa1 Mon Sep 17 00:00:00 2001 From: "javier.brea" Date: Sun, 8 Nov 2020 12:36:21 +0100 Subject: [PATCH 24/41] chore(ci-cd): Publish to gpr only on releases --- .github/workflows/publish-to-github-registry.yml | 10 +++++++--- 1 file changed, 7 insertions(+), 3 deletions(-) diff --git a/.github/workflows/publish-to-github-registry.yml b/.github/workflows/publish-to-github-registry.yml index a925746..faa045c 100644 --- a/.github/workflows/publish-to-github-registry.yml +++ b/.github/workflows/publish-to-github-registry.yml @@ -1,10 +1,10 @@ name: publish-to-github-registry -on: [push] +on: + release: + types: [created] jobs: publish: runs-on: ubuntu-latest - env: - NODE_AUTH_TOKEN: ${{ secrets.GITHUB_TOKEN }} steps: - uses: actions/checkout@v2 # Setup .npmrc file to publish to GitHub Packages @@ -15,5 +15,9 @@ jobs: # Defaults to the user or organization that owns the workflow file scope: '@data-provider' - run: npm install + env: + NODE_AUTH_TOKEN: ${{ secrets.GITHUB_TOKEN }} - run: npm run build - run: npm publish + env: + NODE_AUTH_TOKEN: ${{ secrets.GITHUB_TOKEN }} From 420899ad6ec5034a3955e1ae12e0e3be521cbc49 Mon Sep 17 00:00:00 2001 From: "javier.brea" Date: Sun, 8 Nov 2020 12:44:44 +0100 Subject: [PATCH 25/41] chore(ci-cd): Publish current version to gpr --- .github/workflows/publish-to-github-registry.yml | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/.github/workflows/publish-to-github-registry.yml b/.github/workflows/publish-to-github-registry.yml index faa045c..7c72fee 100644 --- a/.github/workflows/publish-to-github-registry.yml +++ b/.github/workflows/publish-to-github-registry.yml @@ -1,7 +1,5 @@ name: publish-to-github-registry -on: - release: - types: [created] +on: [push] jobs: publish: runs-on: ubuntu-latest From ec0e110514497634c378f8435c416999392ca230 Mon Sep 17 00:00:00 2001 From: "javier.brea" Date: Sun, 8 Nov 2020 12:52:06 +0100 Subject: [PATCH 26/41] chore(ci-cd): Use npm ci instead of npm i --- .github/workflows/publish-to-github-registry.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/publish-to-github-registry.yml b/.github/workflows/publish-to-github-registry.yml index 7c72fee..185ed69 100644 --- a/.github/workflows/publish-to-github-registry.yml +++ b/.github/workflows/publish-to-github-registry.yml @@ -12,7 +12,7 @@ jobs: registry-url: 'https://npm.pkg.github.com' # Defaults to the user or organization that owns the workflow file scope: '@data-provider' - - run: npm install + - run: npm ci env: NODE_AUTH_TOKEN: ${{ secrets.GITHUB_TOKEN }} - run: npm run build From 5fc7a4ba459c0f2bb2f90f422a48fb19d49ab453 Mon Sep 17 00:00:00 2001 From: "javier.brea" Date: Sun, 8 Nov 2020 12:56:51 +0100 Subject: [PATCH 27/41] chore(ci-cd): Change node version --- .github/workflows/publish-to-github-registry.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/publish-to-github-registry.yml b/.github/workflows/publish-to-github-registry.yml index 185ed69..7ea72fe 100644 --- a/.github/workflows/publish-to-github-registry.yml +++ b/.github/workflows/publish-to-github-registry.yml @@ -8,7 +8,7 @@ jobs: # Setup .npmrc file to publish to GitHub Packages - uses: actions/setup-node@v1 with: - node-version: '14.x' + node-version: '12.x' registry-url: 'https://npm.pkg.github.com' # Defaults to the user or organization that owns the workflow file scope: '@data-provider' From cf1528dfdad1c67e85e0ac9d309dbfe3340161e7 Mon Sep 17 00:00:00 2001 From: "javier.brea" Date: Sun, 8 Nov 2020 12:59:35 +0100 Subject: [PATCH 28/41] chore(ci-cd): Install with npm registry --- .github/workflows/publish-to-github-registry.yml | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/.github/workflows/publish-to-github-registry.yml b/.github/workflows/publish-to-github-registry.yml index 7ea72fe..13db616 100644 --- a/.github/workflows/publish-to-github-registry.yml +++ b/.github/workflows/publish-to-github-registry.yml @@ -6,16 +6,14 @@ jobs: steps: - uses: actions/checkout@v2 # Setup .npmrc file to publish to GitHub Packages + - run: npm ci + - run: npm run build - uses: actions/setup-node@v1 with: node-version: '12.x' registry-url: 'https://npm.pkg.github.com' # Defaults to the user or organization that owns the workflow file scope: '@data-provider' - - run: npm ci - env: - NODE_AUTH_TOKEN: ${{ secrets.GITHUB_TOKEN }} - - run: npm run build - run: npm publish env: NODE_AUTH_TOKEN: ${{ secrets.GITHUB_TOKEN }} From e04ff6582c06469d1f3e4385e036e13bf253f0a2 Mon Sep 17 00:00:00 2001 From: "javier.brea" Date: Sun, 8 Nov 2020 13:01:21 +0100 Subject: [PATCH 29/41] chore(release): Publish to gpr only on new release --- .github/workflows/publish-to-github-registry.yml | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/.github/workflows/publish-to-github-registry.yml b/.github/workflows/publish-to-github-registry.yml index 13db616..37187f6 100644 --- a/.github/workflows/publish-to-github-registry.yml +++ b/.github/workflows/publish-to-github-registry.yml @@ -1,5 +1,7 @@ name: publish-to-github-registry -on: [push] +on: + release: + types: [created] jobs: publish: runs-on: ubuntu-latest From e85584098cd3da396573866afae2c272f66e7456 Mon Sep 17 00:00:00 2001 From: "javier.brea" Date: Sun, 8 Nov 2020 13:02:52 +0100 Subject: [PATCH 30/41] chore(ci-cd): Use node 14 --- .github/workflows/publish-to-github-registry.yml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/workflows/publish-to-github-registry.yml b/.github/workflows/publish-to-github-registry.yml index 37187f6..17e80b6 100644 --- a/.github/workflows/publish-to-github-registry.yml +++ b/.github/workflows/publish-to-github-registry.yml @@ -7,12 +7,12 @@ jobs: runs-on: ubuntu-latest steps: - uses: actions/checkout@v2 - # Setup .npmrc file to publish to GitHub Packages - run: npm ci - run: npm run build + # Setup .npmrc file to publish to GitHub Packages - uses: actions/setup-node@v1 with: - node-version: '12.x' + node-version: '14.x' registry-url: 'https://npm.pkg.github.com' # Defaults to the user or organization that owns the workflow file scope: '@data-provider' From 326c9a6778c1d89fdd6c66f1d728d7d616da8564 Mon Sep 17 00:00:00 2001 From: "javier.brea" Date: Sun, 8 Nov 2020 13:03:14 +0100 Subject: [PATCH 31/41] chore(ci-cd): Use node 12 --- .github/workflows/publish-to-github-registry.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/publish-to-github-registry.yml b/.github/workflows/publish-to-github-registry.yml index 17e80b6..dec7fab 100644 --- a/.github/workflows/publish-to-github-registry.yml +++ b/.github/workflows/publish-to-github-registry.yml @@ -12,7 +12,7 @@ jobs: # Setup .npmrc file to publish to GitHub Packages - uses: actions/setup-node@v1 with: - node-version: '14.x' + node-version: '12.x' registry-url: 'https://npm.pkg.github.com' # Defaults to the user or organization that owns the workflow file scope: '@data-provider' From 5f7e64dcfc4ce2a00782d58625b2f49dad912713 Mon Sep 17 00:00:00 2001 From: "javier.brea" Date: Mon, 9 Nov 2020 08:49:45 +0100 Subject: [PATCH 32/41] feat: Add useDataLoadedError hooks and hocs. Deprecate useDataProvider --- CHANGELOG.md | 7 + src/helpers.js | 5 + src/useDataProvider.js | 18 +- src/withDataProvider.js | 75 +++- test/hocs.spec.js | 407 +-------------------- test/hocsDeprecated.spec.js | 454 ++++++++++++++++++++++++ test/hocsMultipleProps.spec.js | 607 ++++++++++++++++++++++++++++++++ test/hooks.spec.js | 66 +--- test/hooksDeprecated.spec.js | 110 ++++++ test/hooksMultipleProps.spec.js | 165 +++++++++ 10 files changed, 1428 insertions(+), 486 deletions(-) create mode 100644 src/helpers.js create mode 100644 test/hocsDeprecated.spec.js create mode 100644 test/hocsMultipleProps.spec.js create mode 100644 test/hooksDeprecated.spec.js create mode 100644 test/hooksMultipleProps.spec.js diff --git a/CHANGELOG.md b/CHANGELOG.md index bdcdaf5..a86b8cf 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -6,7 +6,14 @@ and this project adheres to [Semantic Versioning](http://semver.org/). ## [unreleased] ### Added +- feat(hocs): Add withDataLoadedError, withDataLoadingError, withDataLoadingErrorComponents +- feat(hooks): Add useDataLoadedError, useDataLoadingError + ### Changed +- feat(hocs): Deprecate withDataProvider in favour of withDataLoadingError +- feat(hocs): Deprecate withDataProviderBranch in favour of withDataLoadingErrorComponents +- feat(hooks): Deprecate useDataProvider in favour of useDataLoadingError + ### Fixed ### Removed diff --git a/src/helpers.js b/src/helpers.js new file mode 100644 index 0000000..531b569 --- /dev/null +++ b/src/helpers.js @@ -0,0 +1,5 @@ +export const deprecatedMethod = (method, newMethod) => { + console.warn( + `@data-provider/react: "${method}" is deprecated. Please use "${newMethod}" instead.` + ); +}; diff --git a/src/useDataProvider.js b/src/useDataProvider.js index d27ca9e..f150d03 100644 --- a/src/useDataProvider.js +++ b/src/useDataProvider.js @@ -1,6 +1,8 @@ import { useEffect } from "react"; import { useSelector } from "react-redux"; +import { deprecatedMethod } from "./helpers"; + export const useRefresh = (dataProvider) => { useEffect(() => { if (dataProvider) { @@ -42,7 +44,16 @@ export const useError = (dataProvider) => { return useSelector(getError(dataProvider)); }; -export const useDataProvider = (dataProvider, comparator) => { +export const useDataLoadedError = (dataProvider, comparator) => { + useRefresh(dataProvider); + return [ + useSelector(getData(dataProvider), comparator), + useSelector(getLoaded(dataProvider)), + useSelector(getError(dataProvider)), + ]; +}; + +export const useDataLoadingError = (dataProvider, comparator) => { useRefresh(dataProvider); return [ useSelector(getData(dataProvider), comparator), @@ -50,3 +61,8 @@ export const useDataProvider = (dataProvider, comparator) => { useSelector(getError(dataProvider)), ]; }; + +export const useDataProvider = (dataProvider, comparator) => { + deprecatedMethod("useDataProvider", "useDataLoadingError"); + return useDataLoadingError(dataProvider, comparator); +}; diff --git a/src/withDataProvider.js b/src/withDataProvider.js index 94bd528..4497ea8 100644 --- a/src/withDataProvider.js +++ b/src/withDataProvider.js @@ -1,8 +1,11 @@ import React, { useMemo } from "react"; +import { deprecatedMethod } from "./helpers"; + import { useRefresh, - useDataProvider, + useDataLoadingError, + useDataLoadedError, useData, useLoading, useLoaded, @@ -39,14 +42,22 @@ const useProp = (data, key) => { }, [data, key]); }; -const useDataProviderCustomProps = (provider, keys = defaultKeys) => { - const [data, loading, error] = useDataProvider(provider); - const dataProp = useProp(data, keys[0]); +const useDataLoadingErrorCustomProps = (provider, keys = []) => { + const [data, loading, error] = useDataLoadingError(provider); + const dataProp = useProp(data, keys[0] || defaultKeys[0]); const loadingProp = useProp(loading, keys[1] || defaultKeys[1]); const errorProp = useProp(error, keys[2] || defaultKeys[2]); return { data, loading, error, dataProp, loadingProp, errorProp }; }; +const useDataLoadedErrorCustomProps = (provider, keys = []) => { + const [data, loaded, error] = useDataLoadedError(provider); + const dataProp = useProp(data, keys[0] || defaultKeys[0]); + const loadedProp = useProp(loaded, keys[1] || defaultKeys[3]); + const errorProp = useProp(error, keys[2] || defaultKeys[2]); + return { data, loaded, error, dataProp, loadedProp, errorProp }; +}; + const useDataCustomProp = (provider, key = defaultKeys[0]) => { const data = useData(provider); const dataProp = useProp(data, key); @@ -71,14 +82,14 @@ const useErrorCustomProp = (provider, key = defaultKeys[2]) => { return { error, errorProp }; }; -export const withDataProviderBranch = (provider, keys) => ( +export const withDataLoadingErrorComponents = (provider, keys) => ( Component, LoadingComponent, ErrorComponent ) => { - const WithDataProviderBranch = (props) => { + const WithDataLoadingErrorComponents = (props) => { const providerToRead = useProvider(provider, props); - const { dataProp, loadingProp, errorProp, loading, error } = useDataProviderCustomProps( + const { dataProp, loadingProp, errorProp, loading, error } = useDataLoadingErrorCustomProps( providerToRead, keys ); @@ -96,18 +107,54 @@ export const withDataProviderBranch = (provider, keys) => ( } return ; }; - WithDataProviderBranch.displayName = `WithDataProviderBranch${getDisplayName(Component)}`; - return WithDataProviderBranch; + WithDataLoadingErrorComponents.displayName = `WithDataLoadingErrorComponents${getDisplayName( + Component + )}`; + return WithDataLoadingErrorComponents; }; -export const withDataProvider = (provider, keys) => (Component) => { - const WithDataProvider = (props) => { +export const withDataProviderBranch = (provider, keys) => ( + Component, + LoadingComponent, + ErrorComponent +) => { + deprecatedMethod("withDataProviderBranch", "withDataLoadingErrorComponents"); + return withDataLoadingErrorComponents(provider, keys)( + Component, + LoadingComponent, + ErrorComponent + ); +}; + +export const withDataLoadedError = (provider, keys) => (Component) => { + const WithDataLoadedError = (props) => { const providerToRead = useProvider(provider, props); - const { dataProp, loadingProp, errorProp } = useDataProviderCustomProps(providerToRead, keys); + const { dataProp, loadedProp, errorProp } = useDataLoadedErrorCustomProps( + providerToRead, + keys + ); + return ; + }; + WithDataLoadedError.displayName = `WithDataLoadedError${getDisplayName(Component)}`; + return WithDataLoadedError; +}; + +export const withDataLoadingError = (provider, keys) => (Component) => { + const WithDataLoadingError = (props) => { + const providerToRead = useProvider(provider, props); + const { dataProp, loadingProp, errorProp } = useDataLoadingErrorCustomProps( + providerToRead, + keys + ); return ; }; - WithDataProvider.displayName = `WithDataProvider${getDisplayName(Component)}`; - return WithDataProvider; + WithDataLoadingError.displayName = `WithDataLoadingError${getDisplayName(Component)}`; + return WithDataLoadingError; +}; + +export const withDataProvider = (provider, keys) => (Component) => { + deprecatedMethod("withDataProvider", "withDataLoadingError"); + return withDataLoadingError(provider, keys)(Component); }; export const withData = (provider, key) => (Component) => { diff --git a/test/hocs.spec.js b/test/hocs.spec.js index ab0aba0..a34d35b 100644 --- a/test/hocs.spec.js +++ b/test/hocs.spec.js @@ -6,15 +6,7 @@ import { render, screen } from "@testing-library/react"; import { providers } from "@data-provider/core"; import sinon from "sinon"; -import { - withData, - withLoading, - withLoaded, - withError, - withDataProvider, - withRefresh, - withDataProviderBranch, -} from "../src"; +import { withData, withLoading, withLoaded, withError, withRefresh } from "../src"; import MockProvider from "./MockProvider"; import { BOOKS, BOOKS_ID, LOADING_ID, ERROR_ID } from "./constants"; @@ -373,401 +365,4 @@ describe("HOCs", () => { expect(provider.read.callCount).toEqual(2); }); }); - - describe("withDataProvider", () => { - beforeEach(() => { - BooksComponent = ({ data, loading, error }) => { - return ; - }; - - BooksConnectedComponent = withDataProvider(provider)(BooksComponent); - - Component = () => ( - - - - ); - }); - - it("should wrap displayName of the component", async () => { - expect(BooksConnectedComponent.displayName).toEqual("WithDataProviderBooksComponent"); - }); - - it("loading should be true when provider is loading and false when finish", async () => { - render(); - expect(screen.queryByTestId(LOADING_ID)).toBeInTheDocument(); - await wait(); - expect(screen.queryByTestId(LOADING_ID)).not.toBeInTheDocument(); - }); - - it("loading should change when provider cache is cleaned", async () => { - render(); - await wait(); - expect(screen.queryByTestId(LOADING_ID)).not.toBeInTheDocument(); - provider.cleanCache(); - expect(screen.queryByTestId(LOADING_ID)).toBeInTheDocument(); - await wait(); - expect(screen.queryByTestId(LOADING_ID)).not.toBeInTheDocument(); - }); - - it("should pass data to the component", async () => { - const bookTitle = "Animal Farm"; - render(); - await wait(); - expect(screen.getByText(bookTitle)).toBeInTheDocument(); - }); - - it("should refresh data", async () => { - const TEST_ID = "book-2"; - render(); - await wait(); - expect(screen.queryByTestId(TEST_ID)).toBeInTheDocument(); - provider.delete(2); - await wait(); - expect(screen.queryByTestId(TEST_ID)).not.toBeInTheDocument(); - }); - - it("error should be null when provider does not throw error", async () => { - render(); - expect(screen.queryByTestId(ERROR_ID)).not.toBeInTheDocument(); - await wait(); - expect(screen.queryByTestId(ERROR_ID)).not.toBeInTheDocument(); - }); - - it("should return error when provider throws error", async () => { - const ERROR_MESSAGE = "Foo error"; - provider.error = new Error(ERROR_MESSAGE); - render(); - await wait(); - expect(screen.getByText(ERROR_MESSAGE)).toBeInTheDocument(); - }); - }); - - describe("withDataProvider using custom props", () => { - beforeEach(() => { - BooksComponent = ({ books, booksAreLoading, booksError }) => { - return ; - }; - - BooksConnectedComponent = withDataProvider(provider, [ - "books", - "booksAreLoading", - "booksError", - ])(BooksComponent); - - Component = () => ( - - - - ); - }); - - it("loading should be true when provider is loading and false when finish", async () => { - render(); - expect(screen.queryByTestId(LOADING_ID)).toBeInTheDocument(); - await wait(); - expect(screen.queryByTestId(LOADING_ID)).not.toBeInTheDocument(); - }); - - it("should pass data to the component", async () => { - const bookTitle = "Animal Farm"; - render(); - await wait(); - expect(screen.getByText(bookTitle)).toBeInTheDocument(); - }); - - it("error should be null when provider does not throw error", async () => { - render(); - expect(screen.queryByTestId(ERROR_ID)).not.toBeInTheDocument(); - await wait(); - expect(screen.queryByTestId(ERROR_ID)).not.toBeInTheDocument(); - }); - - it("should return error when provider throws error", async () => { - const ERROR_MESSAGE = "Foo error"; - provider.error = new Error(ERROR_MESSAGE); - render(); - await wait(); - expect(screen.getByText(ERROR_MESSAGE)).toBeInTheDocument(); - }); - }); - - describe("withDataProvider using data custom prop", () => { - beforeEach(() => { - BooksComponent = ({ books, loading, error }) => { - return ; - }; - - BooksConnectedComponent = withDataProvider(provider, ["books"])(BooksComponent); - - Component = () => ( - - - - ); - }); - - it("loading should be true when provider is loading and false when finish", async () => { - render(); - expect(screen.queryByTestId(LOADING_ID)).toBeInTheDocument(); - await wait(); - expect(screen.queryByTestId(LOADING_ID)).not.toBeInTheDocument(); - }); - - it("should pass data to the component", async () => { - const bookTitle = "Animal Farm"; - render(); - await wait(); - expect(screen.getByText(bookTitle)).toBeInTheDocument(); - }); - - it("error should be null when provider does not throw error", async () => { - render(); - expect(screen.queryByTestId(ERROR_ID)).not.toBeInTheDocument(); - await wait(); - expect(screen.queryByTestId(ERROR_ID)).not.toBeInTheDocument(); - }); - - it("should return error when provider throws error", async () => { - const ERROR_MESSAGE = "Foo error"; - provider.error = new Error(ERROR_MESSAGE); - render(); - await wait(); - expect(screen.getByText(ERROR_MESSAGE)).toBeInTheDocument(); - }); - }); - - describe("withDataProviderBranch", () => { - const CUSTOM_LOADING_ID = `${LOADING_ID}-custom`; - const CUSTOM_ERROR_ID = `${ERROR_ID}-custom`; - let CustomLoadingComponent, CustomErrorComponent; - - beforeEach(() => { - CustomLoadingComponent = ({ loading }) => { - if (!loading) { - return null; - } - return
Loading
; - }; - - CustomErrorComponent = ({ error = {} }) => { - return
{error.message}
; - }; - - BooksComponent = ({ data }) => { - return ; - }; - - BooksConnectedComponent = withDataProviderBranch(provider)( - BooksComponent, - CustomLoadingComponent, - CustomErrorComponent - ); - - Component = () => ( - - - - ); - }); - - it("should wrap displayName of the component", async () => { - expect(BooksConnectedComponent.displayName).toEqual("WithDataProviderBranchBooksComponent"); - }); - - it("loading should be true when provider is loading and false when finish", async () => { - render(); - expect(screen.queryByTestId(CUSTOM_LOADING_ID)).toBeInTheDocument(); - await wait(); - expect(screen.queryByTestId(CUSTOM_LOADING_ID)).not.toBeInTheDocument(); - }); - - it("loading should change when provider cache is cleaned", async () => { - render(); - await wait(); - expect(screen.queryByTestId(CUSTOM_LOADING_ID)).not.toBeInTheDocument(); - provider.cleanCache(); - expect(screen.queryByTestId(CUSTOM_LOADING_ID)).toBeInTheDocument(); - await wait(); - expect(screen.queryByTestId(CUSTOM_LOADING_ID)).not.toBeInTheDocument(); - }); - - it("should return null when provider is loading and no loading component is provided", async () => { - BooksConnectedComponent = withDataProviderBranch(provider)(BooksComponent); - - Component = () => ( - - - - ); - render(); - expect(screen.queryByTestId(CUSTOM_LOADING_ID)).not.toBeInTheDocument(); - expect(screen.queryByTestId(CUSTOM_ERROR_ID)).not.toBeInTheDocument(); - expect(screen.queryByTestId(BOOKS_ID)).not.toBeInTheDocument(); - }); - - it("should pass data to the component", async () => { - const bookTitle = "Animal Farm"; - render(); - await wait(); - expect(screen.getByText(bookTitle)).toBeInTheDocument(); - }); - - it("should refresh data", async () => { - const TEST_ID = "book-2"; - render(); - await wait(); - expect(screen.queryByTestId(TEST_ID)).toBeInTheDocument(); - provider.delete(2); - await wait(); - expect(screen.queryByTestId(TEST_ID)).not.toBeInTheDocument(); - }); - - it("error should be null when provider does not throw error", async () => { - render(); - expect(screen.queryByTestId(CUSTOM_ERROR_ID)).not.toBeInTheDocument(); - await wait(); - expect(screen.queryByTestId(CUSTOM_ERROR_ID)).not.toBeInTheDocument(); - }); - - it("should return error when provider throws error", async () => { - const ERROR_MESSAGE = "Foo error"; - provider.error = new Error(ERROR_MESSAGE); - render(); - await wait(); - expect(screen.getByText(ERROR_MESSAGE)).toBeInTheDocument(); - }); - - it("should return null when provider throws an error and no error component is provided", async () => { - BooksConnectedComponent = withDataProviderBranch(provider)( - BooksComponent, - CustomLoadingComponent - ); - - Component = () => ( - - - - ); - provider.error = new Error(); - render(); - await wait(); - expect(screen.queryByTestId(CUSTOM_LOADING_ID)).not.toBeInTheDocument(); - expect(screen.queryByTestId(CUSTOM_ERROR_ID)).not.toBeInTheDocument(); - expect(screen.queryByTestId(BOOKS_ID)).not.toBeInTheDocument(); - }); - }); - - describe("withDataProviderBranch using custom prop", () => { - const CUSTOM_LOADING_ID = `${LOADING_ID}-custom`; - const CUSTOM_ERROR_ID = `${ERROR_ID}-custom`; - let CustomLoadingComponent, CustomErrorComponent; - - beforeEach(() => { - CustomLoadingComponent = ({ booksAreLoading }) => { - if (!booksAreLoading) { - return null; - } - return
Loading
; - }; - - CustomErrorComponent = ({ booksError = {} }) => { - return
{booksError.message}
; - }; - - BooksConnectedComponent = withDataProviderBranch(provider, [ - "books", - "booksAreLoading", - "booksError", - ])(Books, CustomLoadingComponent, CustomErrorComponent); - - Component = () => ( - - - - ); - }); - - it("loading should be true when provider is loading and false when finish", async () => { - render(); - expect(screen.queryByTestId(CUSTOM_LOADING_ID)).toBeInTheDocument(); - await wait(); - expect(screen.queryByTestId(CUSTOM_LOADING_ID)).not.toBeInTheDocument(); - }); - - it("loading should change when provider cache is cleaned", async () => { - render(); - await wait(); - expect(screen.queryByTestId(CUSTOM_LOADING_ID)).not.toBeInTheDocument(); - provider.cleanCache(); - expect(screen.queryByTestId(CUSTOM_LOADING_ID)).toBeInTheDocument(); - await wait(); - expect(screen.queryByTestId(CUSTOM_LOADING_ID)).not.toBeInTheDocument(); - }); - - it("should return null when provider is loading and no loading component is provided", async () => { - BooksConnectedComponent = withDataProviderBranch(provider)(BooksComponent); - - Component = () => ( - - - - ); - render(); - expect(screen.queryByTestId(CUSTOM_LOADING_ID)).not.toBeInTheDocument(); - expect(screen.queryByTestId(CUSTOM_ERROR_ID)).not.toBeInTheDocument(); - expect(screen.queryByTestId(BOOKS_ID)).not.toBeInTheDocument(); - }); - - it("should pass data to the component", async () => { - const bookTitle = "Animal Farm"; - render(); - await wait(); - expect(screen.getByText(bookTitle)).toBeInTheDocument(); - }); - - it("should refresh data", async () => { - const TEST_ID = "book-2"; - render(); - await wait(); - expect(screen.queryByTestId(TEST_ID)).toBeInTheDocument(); - provider.delete(2); - await wait(); - expect(screen.queryByTestId(TEST_ID)).not.toBeInTheDocument(); - }); - - it("error should be null when provider does not throw error", async () => { - render(); - expect(screen.queryByTestId(CUSTOM_ERROR_ID)).not.toBeInTheDocument(); - await wait(); - expect(screen.queryByTestId(CUSTOM_ERROR_ID)).not.toBeInTheDocument(); - }); - - it("should return error when provider throws error", async () => { - const ERROR_MESSAGE = "Foo error"; - provider.error = new Error(ERROR_MESSAGE); - render(); - await wait(); - expect(screen.getByText(ERROR_MESSAGE)).toBeInTheDocument(); - }); - - it("should return null when provider throws an error and no error component is provided", async () => { - BooksConnectedComponent = withDataProviderBranch(provider)( - BooksComponent, - CustomLoadingComponent - ); - - Component = () => ( - - - - ); - provider.error = new Error(); - render(); - await wait(); - expect(screen.queryByTestId(CUSTOM_LOADING_ID)).not.toBeInTheDocument(); - expect(screen.queryByTestId(CUSTOM_ERROR_ID)).not.toBeInTheDocument(); - expect(screen.queryByTestId(BOOKS_ID)).not.toBeInTheDocument(); - }); - }); }); diff --git a/test/hocsDeprecated.spec.js b/test/hocsDeprecated.spec.js new file mode 100644 index 0000000..a74fe54 --- /dev/null +++ b/test/hocsDeprecated.spec.js @@ -0,0 +1,454 @@ +/* eslint-disable react/prop-types, react/display-name */ + +import React from "react"; +import "@testing-library/jest-dom"; +import { render, screen } from "@testing-library/react"; +import { providers } from "@data-provider/core"; +import sinon from "sinon"; + +import { withDataProvider, withDataProviderBranch } from "../src"; + +import MockProvider from "./MockProvider"; +import { BOOKS, BOOKS_ID, LOADING_ID, ERROR_ID } from "./constants"; +import Books from "./Books"; +import ReduxProvider from "./ReduxProvider"; + +const wait = (time = 600) => { + return new Promise((resolve) => { + setTimeout(() => resolve(), time); + }); +}; + +describe("HOCs deprecated", () => { + let provider, BooksComponent, BooksConnectedComponent, Component, sandbox; + + beforeEach(() => { + sandbox = sinon.createSandbox(); + provider = new MockProvider(BOOKS_ID, { + data: BOOKS, + }); + sandbox.spy(console, "warn"); + }); + + afterEach(() => { + providers.clear(); + sandbox.restore(); + }); + + describe("withDataProvider", () => { + beforeEach(() => { + BooksComponent = ({ data, loading, error }) => { + return ; + }; + + BooksConnectedComponent = withDataProvider(provider)(BooksComponent); + + Component = () => ( + + + + ); + }); + + it("should have logged a deprecation warning", async () => { + render(); + expect( + console.warn.calledWith( + '@data-provider/react: "withDataProvider" is deprecated. Please use "withDataLoadingError" instead.' + ) + ).toEqual(true); + }); + + it("should wrap displayName of the component", async () => { + expect(BooksConnectedComponent.displayName).toEqual("WithDataLoadingErrorBooksComponent"); + }); + + it("loading should be true when provider is loading and false when finish", async () => { + render(); + expect(screen.queryByTestId(LOADING_ID)).toBeInTheDocument(); + await wait(); + expect(screen.queryByTestId(LOADING_ID)).not.toBeInTheDocument(); + }); + + it("loading should change when provider cache is cleaned", async () => { + render(); + await wait(); + expect(screen.queryByTestId(LOADING_ID)).not.toBeInTheDocument(); + provider.cleanCache(); + expect(screen.queryByTestId(LOADING_ID)).toBeInTheDocument(); + await wait(); + expect(screen.queryByTestId(LOADING_ID)).not.toBeInTheDocument(); + }); + + it("should pass data to the component", async () => { + const bookTitle = "Animal Farm"; + render(); + await wait(); + expect(screen.getByText(bookTitle)).toBeInTheDocument(); + }); + + it("should refresh data", async () => { + const TEST_ID = "book-2"; + render(); + await wait(); + expect(screen.queryByTestId(TEST_ID)).toBeInTheDocument(); + provider.delete(2); + await wait(); + expect(screen.queryByTestId(TEST_ID)).not.toBeInTheDocument(); + }); + + it("error should be null when provider does not throw error", async () => { + render(); + expect(screen.queryByTestId(ERROR_ID)).not.toBeInTheDocument(); + await wait(); + expect(screen.queryByTestId(ERROR_ID)).not.toBeInTheDocument(); + }); + + it("should return error when provider throws error", async () => { + const ERROR_MESSAGE = "Foo error"; + provider.error = new Error(ERROR_MESSAGE); + render(); + await wait(); + expect(screen.getByText(ERROR_MESSAGE)).toBeInTheDocument(); + }); + }); + + describe("withDataProvider using custom props", () => { + beforeEach(() => { + BooksComponent = ({ books, booksAreLoading, booksError }) => { + return ; + }; + + BooksConnectedComponent = withDataProvider(provider, [ + "books", + "booksAreLoading", + "booksError", + ])(BooksComponent); + + Component = () => ( + + + + ); + }); + + it("loading should be true when provider is loading and false when finish", async () => { + render(); + expect(screen.queryByTestId(LOADING_ID)).toBeInTheDocument(); + await wait(); + expect(screen.queryByTestId(LOADING_ID)).not.toBeInTheDocument(); + }); + + it("should pass data to the component", async () => { + const bookTitle = "Animal Farm"; + render(); + await wait(); + expect(screen.getByText(bookTitle)).toBeInTheDocument(); + }); + + it("error should be null when provider does not throw error", async () => { + render(); + expect(screen.queryByTestId(ERROR_ID)).not.toBeInTheDocument(); + await wait(); + expect(screen.queryByTestId(ERROR_ID)).not.toBeInTheDocument(); + }); + + it("should return error when provider throws error", async () => { + const ERROR_MESSAGE = "Foo error"; + provider.error = new Error(ERROR_MESSAGE); + render(); + await wait(); + expect(screen.getByText(ERROR_MESSAGE)).toBeInTheDocument(); + }); + }); + + describe("withDataProvider using data custom prop", () => { + beforeEach(() => { + BooksComponent = ({ books, loading, error }) => { + return ; + }; + + BooksConnectedComponent = withDataProvider(provider, ["books"])(BooksComponent); + + Component = () => ( + + + + ); + }); + + it("loading should be true when provider is loading and false when finish", async () => { + render(); + expect(screen.queryByTestId(LOADING_ID)).toBeInTheDocument(); + await wait(); + expect(screen.queryByTestId(LOADING_ID)).not.toBeInTheDocument(); + }); + + it("should pass data to the component", async () => { + const bookTitle = "Animal Farm"; + render(); + await wait(); + expect(screen.getByText(bookTitle)).toBeInTheDocument(); + }); + + it("error should be null when provider does not throw error", async () => { + render(); + expect(screen.queryByTestId(ERROR_ID)).not.toBeInTheDocument(); + await wait(); + expect(screen.queryByTestId(ERROR_ID)).not.toBeInTheDocument(); + }); + + it("should return error when provider throws error", async () => { + const ERROR_MESSAGE = "Foo error"; + provider.error = new Error(ERROR_MESSAGE); + render(); + await wait(); + expect(screen.getByText(ERROR_MESSAGE)).toBeInTheDocument(); + }); + }); + + describe("withDataProviderBranch", () => { + const CUSTOM_LOADING_ID = `${LOADING_ID}-custom`; + const CUSTOM_ERROR_ID = `${ERROR_ID}-custom`; + let CustomLoadingComponent, CustomErrorComponent; + + beforeEach(() => { + CustomLoadingComponent = ({ loading }) => { + if (!loading) { + return null; + } + return
Loading
; + }; + + CustomErrorComponent = ({ error = {} }) => { + return
{error.message}
; + }; + + BooksComponent = ({ data }) => { + return ; + }; + + BooksConnectedComponent = withDataProviderBranch(provider)( + BooksComponent, + CustomLoadingComponent, + CustomErrorComponent + ); + + Component = () => ( + + + + ); + }); + + it("should have logged a deprecation warning", async () => { + render(); + expect( + console.warn.calledWith( + '@data-provider/react: "withDataProviderBranch" is deprecated. Please use "withDataLoadingErrorComponents" instead.' + ) + ).toEqual(true); + }); + + it("should wrap displayName of the component", async () => { + expect(BooksConnectedComponent.displayName).toEqual( + "WithDataLoadingErrorComponentsBooksComponent" + ); + }); + + it("loading should be true when provider is loading and false when finish", async () => { + render(); + expect(screen.queryByTestId(CUSTOM_LOADING_ID)).toBeInTheDocument(); + await wait(); + expect(screen.queryByTestId(CUSTOM_LOADING_ID)).not.toBeInTheDocument(); + }); + + it("loading should change when provider cache is cleaned", async () => { + render(); + await wait(); + expect(screen.queryByTestId(CUSTOM_LOADING_ID)).not.toBeInTheDocument(); + provider.cleanCache(); + expect(screen.queryByTestId(CUSTOM_LOADING_ID)).toBeInTheDocument(); + await wait(); + expect(screen.queryByTestId(CUSTOM_LOADING_ID)).not.toBeInTheDocument(); + }); + + it("should return null when provider is loading and no loading component is provided", async () => { + BooksConnectedComponent = withDataProviderBranch(provider)(BooksComponent); + + Component = () => ( + + + + ); + render(); + expect(screen.queryByTestId(CUSTOM_LOADING_ID)).not.toBeInTheDocument(); + expect(screen.queryByTestId(CUSTOM_ERROR_ID)).not.toBeInTheDocument(); + expect(screen.queryByTestId(BOOKS_ID)).not.toBeInTheDocument(); + }); + + it("should pass data to the component", async () => { + const bookTitle = "Animal Farm"; + render(); + await wait(); + expect(screen.getByText(bookTitle)).toBeInTheDocument(); + }); + + it("should refresh data", async () => { + const TEST_ID = "book-2"; + render(); + await wait(); + expect(screen.queryByTestId(TEST_ID)).toBeInTheDocument(); + provider.delete(2); + await wait(); + expect(screen.queryByTestId(TEST_ID)).not.toBeInTheDocument(); + }); + + it("error should be null when provider does not throw error", async () => { + render(); + expect(screen.queryByTestId(CUSTOM_ERROR_ID)).not.toBeInTheDocument(); + await wait(); + expect(screen.queryByTestId(CUSTOM_ERROR_ID)).not.toBeInTheDocument(); + }); + + it("should return error when provider throws error", async () => { + const ERROR_MESSAGE = "Foo error"; + provider.error = new Error(ERROR_MESSAGE); + render(); + await wait(); + expect(screen.getByText(ERROR_MESSAGE)).toBeInTheDocument(); + }); + + it("should return null when provider throws an error and no error component is provided", async () => { + BooksConnectedComponent = withDataProviderBranch(provider)( + BooksComponent, + CustomLoadingComponent + ); + + Component = () => ( + + + + ); + provider.error = new Error(); + render(); + await wait(); + expect(screen.queryByTestId(CUSTOM_LOADING_ID)).not.toBeInTheDocument(); + expect(screen.queryByTestId(CUSTOM_ERROR_ID)).not.toBeInTheDocument(); + expect(screen.queryByTestId(BOOKS_ID)).not.toBeInTheDocument(); + }); + }); + + describe("withDataProviderBranch using custom prop", () => { + const CUSTOM_LOADING_ID = `${LOADING_ID}-custom`; + const CUSTOM_ERROR_ID = `${ERROR_ID}-custom`; + let CustomLoadingComponent, CustomErrorComponent; + + beforeEach(() => { + CustomLoadingComponent = ({ booksAreLoading }) => { + if (!booksAreLoading) { + return null; + } + return
Loading
; + }; + + CustomErrorComponent = ({ booksError = {} }) => { + return
{booksError.message}
; + }; + + BooksConnectedComponent = withDataProviderBranch(provider, [ + "books", + "booksAreLoading", + "booksError", + ])(Books, CustomLoadingComponent, CustomErrorComponent); + + Component = () => ( + + + + ); + }); + + it("loading should be true when provider is loading and false when finish", async () => { + render(); + expect(screen.queryByTestId(CUSTOM_LOADING_ID)).toBeInTheDocument(); + await wait(); + expect(screen.queryByTestId(CUSTOM_LOADING_ID)).not.toBeInTheDocument(); + }); + + it("loading should change when provider cache is cleaned", async () => { + render(); + await wait(); + expect(screen.queryByTestId(CUSTOM_LOADING_ID)).not.toBeInTheDocument(); + provider.cleanCache(); + expect(screen.queryByTestId(CUSTOM_LOADING_ID)).toBeInTheDocument(); + await wait(); + expect(screen.queryByTestId(CUSTOM_LOADING_ID)).not.toBeInTheDocument(); + }); + + it("should return null when provider is loading and no loading component is provided", async () => { + BooksConnectedComponent = withDataProviderBranch(provider)(BooksComponent); + + Component = () => ( + + + + ); + render(); + expect(screen.queryByTestId(CUSTOM_LOADING_ID)).not.toBeInTheDocument(); + expect(screen.queryByTestId(CUSTOM_ERROR_ID)).not.toBeInTheDocument(); + expect(screen.queryByTestId(BOOKS_ID)).not.toBeInTheDocument(); + }); + + it("should pass data to the component", async () => { + const bookTitle = "Animal Farm"; + render(); + await wait(); + expect(screen.getByText(bookTitle)).toBeInTheDocument(); + }); + + it("should refresh data", async () => { + const TEST_ID = "book-2"; + render(); + await wait(); + expect(screen.queryByTestId(TEST_ID)).toBeInTheDocument(); + provider.delete(2); + await wait(); + expect(screen.queryByTestId(TEST_ID)).not.toBeInTheDocument(); + }); + + it("error should be null when provider does not throw error", async () => { + render(); + expect(screen.queryByTestId(CUSTOM_ERROR_ID)).not.toBeInTheDocument(); + await wait(); + expect(screen.queryByTestId(CUSTOM_ERROR_ID)).not.toBeInTheDocument(); + }); + + it("should return error when provider throws error", async () => { + const ERROR_MESSAGE = "Foo error"; + provider.error = new Error(ERROR_MESSAGE); + render(); + await wait(); + expect(screen.getByText(ERROR_MESSAGE)).toBeInTheDocument(); + }); + + it("should return null when provider throws an error and no error component is provided", async () => { + BooksConnectedComponent = withDataProviderBranch(provider)( + BooksComponent, + CustomLoadingComponent + ); + + Component = () => ( + + + + ); + provider.error = new Error(); + render(); + await wait(); + expect(screen.queryByTestId(CUSTOM_LOADING_ID)).not.toBeInTheDocument(); + expect(screen.queryByTestId(CUSTOM_ERROR_ID)).not.toBeInTheDocument(); + expect(screen.queryByTestId(BOOKS_ID)).not.toBeInTheDocument(); + }); + }); +}); diff --git a/test/hocsMultipleProps.spec.js b/test/hocsMultipleProps.spec.js new file mode 100644 index 0000000..c5ddfaf --- /dev/null +++ b/test/hocsMultipleProps.spec.js @@ -0,0 +1,607 @@ +/* eslint-disable react/prop-types, react/display-name */ + +import React from "react"; +import "@testing-library/jest-dom"; +import { render, screen } from "@testing-library/react"; +import { providers } from "@data-provider/core"; +import sinon from "sinon"; + +import { withDataLoadedError, withDataLoadingError, withDataLoadingErrorComponents } from "../src"; + +import MockProvider from "./MockProvider"; +import { BOOKS, BOOKS_ID, LOADING_ID, ERROR_ID } from "./constants"; +import Books from "./Books"; +import ReduxProvider from "./ReduxProvider"; + +const wait = (time = 600) => { + return new Promise((resolve) => { + setTimeout(() => resolve(), time); + }); +}; + +describe("HOCs providing multiple props", () => { + let provider, BooksComponent, BooksConnectedComponent, Component, sandbox; + + beforeEach(() => { + sandbox = sinon.createSandbox(); + provider = new MockProvider(BOOKS_ID, { + data: BOOKS, + }); + }); + + afterEach(() => { + providers.clear(); + sandbox.restore(); + }); + + describe("withDataLoadedError", () => { + beforeEach(() => { + provider = new MockProvider(`${BOOKS_ID}-1`, { + data: BOOKS, + }); + BooksComponent = ({ data, loaded, error }) => { + return ; + }; + + BooksConnectedComponent = withDataLoadedError(provider)(BooksComponent); + + Component = () => ( + + + + ); + }); + + it("should wrap displayName of the component", async () => { + expect(BooksConnectedComponent.displayName).toEqual("WithDataLoadedErrorBooksComponent"); + }); + + it("loading should be true when provider is loading first time and false when finish", async () => { + render(); + expect(screen.queryByTestId(LOADING_ID)).toBeInTheDocument(); + await wait(); + expect(screen.queryByTestId(LOADING_ID)).not.toBeInTheDocument(); + }); + + it("loading should not change when provider cache is cleaned", async () => { + render(); + await wait(); + expect(screen.queryByTestId(LOADING_ID)).not.toBeInTheDocument(); + provider.cleanCache(); + expect(screen.queryByTestId(LOADING_ID)).not.toBeInTheDocument(); + await wait(); + expect(screen.queryByTestId(LOADING_ID)).not.toBeInTheDocument(); + }); + + it("should pass data to the component", async () => { + const bookTitle = "Animal Farm"; + render(); + await wait(); + expect(screen.getByText(bookTitle)).toBeInTheDocument(); + }); + + it("should refresh data", async () => { + const TEST_ID = "book-2"; + render(); + await wait(); + expect(screen.queryByTestId(TEST_ID)).toBeInTheDocument(); + provider.delete(2); + await wait(); + expect(screen.queryByTestId(TEST_ID)).not.toBeInTheDocument(); + }); + + it("error should be null when provider does not throw error", async () => { + render(); + expect(screen.queryByTestId(ERROR_ID)).not.toBeInTheDocument(); + await wait(); + expect(screen.queryByTestId(ERROR_ID)).not.toBeInTheDocument(); + }); + + it("should return error when provider throws error", async () => { + const ERROR_MESSAGE = "Foo error"; + provider.error = new Error(ERROR_MESSAGE); + render(); + await wait(); + expect(screen.getByText(ERROR_MESSAGE)).toBeInTheDocument(); + }); + }); + + describe("withDataLoadedError using custom props", () => { + beforeEach(() => { + provider = new MockProvider(`${BOOKS_ID}-2`, { + data: BOOKS, + }); + BooksComponent = ({ books, booksAreLoaded, booksError }) => { + return ; + }; + + BooksConnectedComponent = withDataLoadedError(provider, [ + "books", + "booksAreLoaded", + "booksError", + ])(BooksComponent); + + Component = () => ( + + + + ); + }); + + it("loading should be true when provider is loading first time and false when finish", async () => { + render(); + expect(screen.queryByTestId(LOADING_ID)).toBeInTheDocument(); + await wait(); + expect(screen.queryByTestId(LOADING_ID)).not.toBeInTheDocument(); + }); + + it("should pass data to the component", async () => { + const bookTitle = "Animal Farm"; + render(); + await wait(); + expect(screen.getByText(bookTitle)).toBeInTheDocument(); + }); + + it("error should be null when provider does not throw error", async () => { + render(); + expect(screen.queryByTestId(ERROR_ID)).not.toBeInTheDocument(); + await wait(); + expect(screen.queryByTestId(ERROR_ID)).not.toBeInTheDocument(); + }); + + it("should return error when provider throws error", async () => { + const ERROR_MESSAGE = "Foo error"; + provider.error = new Error(ERROR_MESSAGE); + render(); + await wait(); + expect(screen.getByText(ERROR_MESSAGE)).toBeInTheDocument(); + }); + }); + + describe("withDataLoadedError using data custom prop", () => { + beforeEach(() => { + provider = new MockProvider(`${BOOKS_ID}-3`, { + data: BOOKS, + }); + BooksComponent = ({ books, loaded, error }) => { + return ; + }; + + BooksConnectedComponent = withDataLoadedError(provider, ["books"])(BooksComponent); + + Component = () => ( + + + + ); + }); + + it("loading should be true when provider is loading first time and false when finish", async () => { + render(); + expect(screen.queryByTestId(LOADING_ID)).toBeInTheDocument(); + await wait(); + expect(screen.queryByTestId(LOADING_ID)).not.toBeInTheDocument(); + }); + + it("should pass data to the component", async () => { + const bookTitle = "Animal Farm"; + render(); + await wait(); + expect(screen.getByText(bookTitle)).toBeInTheDocument(); + }); + + it("error should be null when provider does not throw error", async () => { + render(); + expect(screen.queryByTestId(ERROR_ID)).not.toBeInTheDocument(); + await wait(); + expect(screen.queryByTestId(ERROR_ID)).not.toBeInTheDocument(); + }); + + it("should return error when provider throws error", async () => { + const ERROR_MESSAGE = "Foo error"; + provider.error = new Error(ERROR_MESSAGE); + render(); + await wait(); + expect(screen.getByText(ERROR_MESSAGE)).toBeInTheDocument(); + }); + }); + + describe("withDataLoadingError", () => { + beforeEach(() => { + BooksComponent = ({ data, loading, error }) => { + return ; + }; + + BooksConnectedComponent = withDataLoadingError(provider)(BooksComponent); + + Component = () => ( + + + + ); + }); + + it("should wrap displayName of the component", async () => { + expect(BooksConnectedComponent.displayName).toEqual("WithDataLoadingErrorBooksComponent"); + }); + + it("loading should be true when provider is loading and false when finish", async () => { + render(); + expect(screen.queryByTestId(LOADING_ID)).toBeInTheDocument(); + await wait(); + expect(screen.queryByTestId(LOADING_ID)).not.toBeInTheDocument(); + }); + + it("loading should change when provider cache is cleaned", async () => { + render(); + await wait(); + expect(screen.queryByTestId(LOADING_ID)).not.toBeInTheDocument(); + provider.cleanCache(); + expect(screen.queryByTestId(LOADING_ID)).toBeInTheDocument(); + await wait(); + expect(screen.queryByTestId(LOADING_ID)).not.toBeInTheDocument(); + }); + + it("should pass data to the component", async () => { + const bookTitle = "Animal Farm"; + render(); + await wait(); + expect(screen.getByText(bookTitle)).toBeInTheDocument(); + }); + + it("should refresh data", async () => { + const TEST_ID = "book-2"; + render(); + await wait(); + expect(screen.queryByTestId(TEST_ID)).toBeInTheDocument(); + provider.delete(2); + await wait(); + expect(screen.queryByTestId(TEST_ID)).not.toBeInTheDocument(); + }); + + it("error should be null when provider does not throw error", async () => { + render(); + expect(screen.queryByTestId(ERROR_ID)).not.toBeInTheDocument(); + await wait(); + expect(screen.queryByTestId(ERROR_ID)).not.toBeInTheDocument(); + }); + + it("should return error when provider throws error", async () => { + const ERROR_MESSAGE = "Foo error"; + provider.error = new Error(ERROR_MESSAGE); + render(); + await wait(); + expect(screen.getByText(ERROR_MESSAGE)).toBeInTheDocument(); + }); + }); + + describe("withDataLoadingError using custom props", () => { + beforeEach(() => { + BooksComponent = ({ books, booksAreLoading, booksError }) => { + return ; + }; + + BooksConnectedComponent = withDataLoadingError(provider, [ + "books", + "booksAreLoading", + "booksError", + ])(BooksComponent); + + Component = () => ( + + + + ); + }); + + it("loading should be true when provider is loading and false when finish", async () => { + render(); + expect(screen.queryByTestId(LOADING_ID)).toBeInTheDocument(); + await wait(); + expect(screen.queryByTestId(LOADING_ID)).not.toBeInTheDocument(); + }); + + it("should pass data to the component", async () => { + const bookTitle = "Animal Farm"; + render(); + await wait(); + expect(screen.getByText(bookTitle)).toBeInTheDocument(); + }); + + it("error should be null when provider does not throw error", async () => { + render(); + expect(screen.queryByTestId(ERROR_ID)).not.toBeInTheDocument(); + await wait(); + expect(screen.queryByTestId(ERROR_ID)).not.toBeInTheDocument(); + }); + + it("should return error when provider throws error", async () => { + const ERROR_MESSAGE = "Foo error"; + provider.error = new Error(ERROR_MESSAGE); + render(); + await wait(); + expect(screen.getByText(ERROR_MESSAGE)).toBeInTheDocument(); + }); + }); + + describe("withDataLoadingError using data custom prop", () => { + beforeEach(() => { + BooksComponent = ({ books, loading, error }) => { + return ; + }; + + BooksConnectedComponent = withDataLoadingError(provider, ["books"])(BooksComponent); + + Component = () => ( + + + + ); + }); + + it("loading should be true when provider is loading and false when finish", async () => { + render(); + expect(screen.queryByTestId(LOADING_ID)).toBeInTheDocument(); + await wait(); + expect(screen.queryByTestId(LOADING_ID)).not.toBeInTheDocument(); + }); + + it("should pass data to the component", async () => { + const bookTitle = "Animal Farm"; + render(); + await wait(); + expect(screen.getByText(bookTitle)).toBeInTheDocument(); + }); + + it("error should be null when provider does not throw error", async () => { + render(); + expect(screen.queryByTestId(ERROR_ID)).not.toBeInTheDocument(); + await wait(); + expect(screen.queryByTestId(ERROR_ID)).not.toBeInTheDocument(); + }); + + it("should return error when provider throws error", async () => { + const ERROR_MESSAGE = "Foo error"; + provider.error = new Error(ERROR_MESSAGE); + render(); + await wait(); + expect(screen.getByText(ERROR_MESSAGE)).toBeInTheDocument(); + }); + }); + + describe("withDataLoadingErrorComponents", () => { + const CUSTOM_LOADING_ID = `${LOADING_ID}-custom`; + const CUSTOM_ERROR_ID = `${ERROR_ID}-custom`; + let CustomLoadingComponent, CustomErrorComponent; + + beforeEach(() => { + CustomLoadingComponent = ({ loading }) => { + if (!loading) { + return null; + } + return
Loading
; + }; + + CustomErrorComponent = ({ error = {} }) => { + return
{error.message}
; + }; + + BooksComponent = ({ data }) => { + return ; + }; + + BooksConnectedComponent = withDataLoadingErrorComponents(provider)( + BooksComponent, + CustomLoadingComponent, + CustomErrorComponent + ); + + Component = () => ( + + + + ); + }); + + it("should wrap displayName of the component", async () => { + expect(BooksConnectedComponent.displayName).toEqual( + "WithDataLoadingErrorComponentsBooksComponent" + ); + }); + + it("loading should be true when provider is loading and false when finish", async () => { + render(); + expect(screen.queryByTestId(CUSTOM_LOADING_ID)).toBeInTheDocument(); + await wait(); + expect(screen.queryByTestId(CUSTOM_LOADING_ID)).not.toBeInTheDocument(); + }); + + it("loading should change when provider cache is cleaned", async () => { + render(); + await wait(); + expect(screen.queryByTestId(CUSTOM_LOADING_ID)).not.toBeInTheDocument(); + provider.cleanCache(); + expect(screen.queryByTestId(CUSTOM_LOADING_ID)).toBeInTheDocument(); + await wait(); + expect(screen.queryByTestId(CUSTOM_LOADING_ID)).not.toBeInTheDocument(); + }); + + it("should return null when provider is loading and no loading component is provided", async () => { + BooksConnectedComponent = withDataLoadingErrorComponents(provider)(BooksComponent); + + Component = () => ( + + + + ); + render(); + expect(screen.queryByTestId(CUSTOM_LOADING_ID)).not.toBeInTheDocument(); + expect(screen.queryByTestId(CUSTOM_ERROR_ID)).not.toBeInTheDocument(); + expect(screen.queryByTestId(BOOKS_ID)).not.toBeInTheDocument(); + }); + + it("should pass data to the component", async () => { + const bookTitle = "Animal Farm"; + render(); + await wait(); + expect(screen.getByText(bookTitle)).toBeInTheDocument(); + }); + + it("should refresh data", async () => { + const TEST_ID = "book-2"; + render(); + await wait(); + expect(screen.queryByTestId(TEST_ID)).toBeInTheDocument(); + provider.delete(2); + await wait(); + expect(screen.queryByTestId(TEST_ID)).not.toBeInTheDocument(); + }); + + it("error should be null when provider does not throw error", async () => { + render(); + expect(screen.queryByTestId(CUSTOM_ERROR_ID)).not.toBeInTheDocument(); + await wait(); + expect(screen.queryByTestId(CUSTOM_ERROR_ID)).not.toBeInTheDocument(); + }); + + it("should return error when provider throws error", async () => { + const ERROR_MESSAGE = "Foo error"; + provider.error = new Error(ERROR_MESSAGE); + render(); + await wait(); + expect(screen.getByText(ERROR_MESSAGE)).toBeInTheDocument(); + }); + + it("should return null when provider throws an error and no error component is provided", async () => { + BooksConnectedComponent = withDataLoadingErrorComponents(provider)( + BooksComponent, + CustomLoadingComponent + ); + + Component = () => ( + + + + ); + provider.error = new Error(); + render(); + await wait(); + expect(screen.queryByTestId(CUSTOM_LOADING_ID)).not.toBeInTheDocument(); + expect(screen.queryByTestId(CUSTOM_ERROR_ID)).not.toBeInTheDocument(); + expect(screen.queryByTestId(BOOKS_ID)).not.toBeInTheDocument(); + }); + }); + + describe("withDataLoadingErrorComponents using custom prop", () => { + const CUSTOM_LOADING_ID = `${LOADING_ID}-custom`; + const CUSTOM_ERROR_ID = `${ERROR_ID}-custom`; + let CustomLoadingComponent, CustomErrorComponent; + + beforeEach(() => { + CustomLoadingComponent = ({ booksAreLoading }) => { + if (!booksAreLoading) { + return null; + } + return
Loading
; + }; + + CustomErrorComponent = ({ booksError = {} }) => { + return
{booksError.message}
; + }; + + BooksConnectedComponent = withDataLoadingErrorComponents(provider, [ + "books", + "booksAreLoading", + "booksError", + ])(Books, CustomLoadingComponent, CustomErrorComponent); + + Component = () => ( + + + + ); + }); + + it("loading should be true when provider is loading and false when finish", async () => { + render(); + expect(screen.queryByTestId(CUSTOM_LOADING_ID)).toBeInTheDocument(); + await wait(); + expect(screen.queryByTestId(CUSTOM_LOADING_ID)).not.toBeInTheDocument(); + }); + + it("loading should change when provider cache is cleaned", async () => { + render(); + await wait(); + expect(screen.queryByTestId(CUSTOM_LOADING_ID)).not.toBeInTheDocument(); + provider.cleanCache(); + expect(screen.queryByTestId(CUSTOM_LOADING_ID)).toBeInTheDocument(); + await wait(); + expect(screen.queryByTestId(CUSTOM_LOADING_ID)).not.toBeInTheDocument(); + }); + + it("should return null when provider is loading and no loading component is provided", async () => { + BooksConnectedComponent = withDataLoadingErrorComponents(provider)(BooksComponent); + + Component = () => ( + + + + ); + render(); + expect(screen.queryByTestId(CUSTOM_LOADING_ID)).not.toBeInTheDocument(); + expect(screen.queryByTestId(CUSTOM_ERROR_ID)).not.toBeInTheDocument(); + expect(screen.queryByTestId(BOOKS_ID)).not.toBeInTheDocument(); + }); + + it("should pass data to the component", async () => { + const bookTitle = "Animal Farm"; + render(); + await wait(); + expect(screen.getByText(bookTitle)).toBeInTheDocument(); + }); + + it("should refresh data", async () => { + const TEST_ID = "book-2"; + render(); + await wait(); + expect(screen.queryByTestId(TEST_ID)).toBeInTheDocument(); + provider.delete(2); + await wait(); + expect(screen.queryByTestId(TEST_ID)).not.toBeInTheDocument(); + }); + + it("error should be null when provider does not throw error", async () => { + render(); + expect(screen.queryByTestId(CUSTOM_ERROR_ID)).not.toBeInTheDocument(); + await wait(); + expect(screen.queryByTestId(CUSTOM_ERROR_ID)).not.toBeInTheDocument(); + }); + + it("should return error when provider throws error", async () => { + const ERROR_MESSAGE = "Foo error"; + provider.error = new Error(ERROR_MESSAGE); + render(); + await wait(); + expect(screen.getByText(ERROR_MESSAGE)).toBeInTheDocument(); + }); + + it("should return null when provider throws an error and no error component is provided", async () => { + BooksConnectedComponent = withDataLoadingErrorComponents(provider)( + BooksComponent, + CustomLoadingComponent + ); + + Component = () => ( + + + + ); + provider.error = new Error(); + render(); + await wait(); + expect(screen.queryByTestId(CUSTOM_LOADING_ID)).not.toBeInTheDocument(); + expect(screen.queryByTestId(CUSTOM_ERROR_ID)).not.toBeInTheDocument(); + expect(screen.queryByTestId(BOOKS_ID)).not.toBeInTheDocument(); + }); + }); +}); diff --git a/test/hooks.spec.js b/test/hooks.spec.js index d747e26..cedce35 100644 --- a/test/hooks.spec.js +++ b/test/hooks.spec.js @@ -5,7 +5,7 @@ import "@testing-library/jest-dom"; import { render, screen } from "@testing-library/react"; import { providers } from "@data-provider/core"; -import { useData, useLoading, useLoaded, useError, useDataProvider } from "../src"; +import { useData, useLoading, useLoaded, useError } from "../src"; import MockProvider from "./MockProvider"; import { BOOKS, BOOKS_ID, LOADING_ID, ERROR_ID } from "./constants"; @@ -182,68 +182,4 @@ describe("hooks", () => { expect(screen.getByText(ERROR_MESSAGE)).toBeInTheDocument(); }); }); - - describe("useDataProvider", () => { - beforeEach(() => { - BooksComponent = () => { - const [books, loading, error] = useDataProvider(provider); - return ; - }; - - Component = () => ( - - - - ); - }); - - it("loading should be true when provider is loading and false when finish", async () => { - render(); - expect(screen.queryByTestId(LOADING_ID)).toBeInTheDocument(); - await wait(); - expect(screen.queryByTestId(LOADING_ID)).not.toBeInTheDocument(); - }); - - it("loading should change when provider cache is cleaned", async () => { - render(); - await wait(); - expect(screen.queryByTestId(LOADING_ID)).not.toBeInTheDocument(); - provider.cleanCache(); - expect(screen.queryByTestId(LOADING_ID)).toBeInTheDocument(); - await wait(); - expect(screen.queryByTestId(LOADING_ID)).not.toBeInTheDocument(); - }); - - it("should pass data to the component", async () => { - const bookTitle = "Animal Farm"; - render(); - await wait(); - expect(screen.getByText(bookTitle)).toBeInTheDocument(); - }); - - it("should refresh data", async () => { - const TEST_ID = "book-2"; - render(); - await wait(); - expect(screen.queryByTestId(TEST_ID)).toBeInTheDocument(); - provider.delete(2); - await wait(); - expect(screen.queryByTestId(TEST_ID)).not.toBeInTheDocument(); - }); - - it("error should be null when provider does not throw error", async () => { - render(); - expect(screen.queryByTestId(ERROR_ID)).not.toBeInTheDocument(); - await wait(); - expect(screen.queryByTestId(ERROR_ID)).not.toBeInTheDocument(); - }); - - it("should return error when provider throws error", async () => { - const ERROR_MESSAGE = "Foo error"; - provider.error = new Error(ERROR_MESSAGE); - render(); - await wait(); - expect(screen.getByText(ERROR_MESSAGE)).toBeInTheDocument(); - }); - }); }); diff --git a/test/hooksDeprecated.spec.js b/test/hooksDeprecated.spec.js new file mode 100644 index 0000000..fc80101 --- /dev/null +++ b/test/hooksDeprecated.spec.js @@ -0,0 +1,110 @@ +/* eslint-disable react/prop-types, react/display-name */ + +import React from "react"; +import "@testing-library/jest-dom"; +import { render, screen } from "@testing-library/react"; +import { providers } from "@data-provider/core"; +import sinon from "sinon"; + +import { useDataProvider } from "../src"; + +import MockProvider from "./MockProvider"; +import { BOOKS, BOOKS_ID, LOADING_ID, ERROR_ID } from "./constants"; +import Books from "./Books"; +import ReduxProvider from "./ReduxProvider"; + +const wait = (time = 600) => { + return new Promise((resolve) => { + setTimeout(() => resolve(), time); + }); +}; + +describe("hooks deprecated", () => { + let provider, BooksComponent, Component, sandbox; + + beforeEach(() => { + sandbox = sinon.createSandbox(); + provider = new MockProvider(BOOKS_ID, { + data: BOOKS, + }); + sandbox.spy(console, "warn"); + }); + + afterEach(() => { + providers.clear(); + sandbox.restore(); + }); + + describe("useDataProvider", () => { + beforeEach(() => { + BooksComponent = () => { + const [books, loading, error] = useDataProvider(provider); + return ; + }; + + Component = () => ( + + + + ); + }); + + it("should have logged a deprecation warning", async () => { + render(); + expect( + console.warn.calledWith( + '@data-provider/react: "useDataProvider" is deprecated. Please use "useDataLoadingError" instead.' + ) + ).toEqual(true); + }); + + it("loading should be true when provider is loading and false when finish", async () => { + render(); + expect(screen.queryByTestId(LOADING_ID)).toBeInTheDocument(); + await wait(); + expect(screen.queryByTestId(LOADING_ID)).not.toBeInTheDocument(); + }); + + it("loading should change when provider cache is cleaned", async () => { + render(); + await wait(); + expect(screen.queryByTestId(LOADING_ID)).not.toBeInTheDocument(); + provider.cleanCache(); + expect(screen.queryByTestId(LOADING_ID)).toBeInTheDocument(); + await wait(); + expect(screen.queryByTestId(LOADING_ID)).not.toBeInTheDocument(); + }); + + it("should pass data to the component", async () => { + const bookTitle = "Animal Farm"; + render(); + await wait(); + expect(screen.getByText(bookTitle)).toBeInTheDocument(); + }); + + it("should refresh data", async () => { + const TEST_ID = "book-2"; + render(); + await wait(); + expect(screen.queryByTestId(TEST_ID)).toBeInTheDocument(); + provider.delete(2); + await wait(); + expect(screen.queryByTestId(TEST_ID)).not.toBeInTheDocument(); + }); + + it("error should be null when provider does not throw error", async () => { + render(); + expect(screen.queryByTestId(ERROR_ID)).not.toBeInTheDocument(); + await wait(); + expect(screen.queryByTestId(ERROR_ID)).not.toBeInTheDocument(); + }); + + it("should return error when provider throws error", async () => { + const ERROR_MESSAGE = "Foo error"; + provider.error = new Error(ERROR_MESSAGE); + render(); + await wait(); + expect(screen.getByText(ERROR_MESSAGE)).toBeInTheDocument(); + }); + }); +}); diff --git a/test/hooksMultipleProps.spec.js b/test/hooksMultipleProps.spec.js new file mode 100644 index 0000000..1ce58d1 --- /dev/null +++ b/test/hooksMultipleProps.spec.js @@ -0,0 +1,165 @@ +/* eslint-disable react/prop-types, react/display-name */ + +import React from "react"; +import "@testing-library/jest-dom"; +import { render, screen } from "@testing-library/react"; +import { providers } from "@data-provider/core"; + +import { useDataLoadingError, useDataLoadedError } from "../src"; + +import MockProvider from "./MockProvider"; +import { BOOKS, BOOKS_ID, LOADING_ID, ERROR_ID } from "./constants"; +import Books from "./Books"; +import ReduxProvider from "./ReduxProvider"; + +const wait = (time = 600) => { + return new Promise((resolve) => { + setTimeout(() => resolve(), time); + }); +}; + +describe("hooks returning multiple props", () => { + let provider, BooksComponent, Component; + + beforeEach(() => { + provider = new MockProvider(BOOKS_ID, { + data: BOOKS, + }); + }); + + afterEach(() => { + providers.clear(); + }); + + describe("useDataLoadingError", () => { + beforeEach(() => { + BooksComponent = () => { + const [books, loading, error] = useDataLoadingError(provider); + return ; + }; + + Component = () => ( + + + + ); + }); + + it("loading should be true when provider is loading and false when finish", async () => { + render(); + expect(screen.queryByTestId(LOADING_ID)).toBeInTheDocument(); + await wait(); + expect(screen.queryByTestId(LOADING_ID)).not.toBeInTheDocument(); + }); + + it("loading should change when provider cache is cleaned", async () => { + render(); + await wait(); + expect(screen.queryByTestId(LOADING_ID)).not.toBeInTheDocument(); + provider.cleanCache(); + expect(screen.queryByTestId(LOADING_ID)).toBeInTheDocument(); + await wait(); + expect(screen.queryByTestId(LOADING_ID)).not.toBeInTheDocument(); + }); + + it("should pass data to the component", async () => { + const bookTitle = "Animal Farm"; + render(); + await wait(); + expect(screen.getByText(bookTitle)).toBeInTheDocument(); + }); + + it("should refresh data", async () => { + const TEST_ID = "book-2"; + render(); + await wait(); + expect(screen.queryByTestId(TEST_ID)).toBeInTheDocument(); + provider.delete(2); + await wait(); + expect(screen.queryByTestId(TEST_ID)).not.toBeInTheDocument(); + }); + + it("error should be null when provider does not throw error", async () => { + render(); + expect(screen.queryByTestId(ERROR_ID)).not.toBeInTheDocument(); + await wait(); + expect(screen.queryByTestId(ERROR_ID)).not.toBeInTheDocument(); + }); + + it("should return error when provider throws error", async () => { + const ERROR_MESSAGE = "Foo error"; + provider.error = new Error(ERROR_MESSAGE); + render(); + await wait(); + expect(screen.getByText(ERROR_MESSAGE)).toBeInTheDocument(); + }); + }); + + describe("useDataLoadedError", () => { + beforeEach(() => { + provider = new MockProvider(`${BOOKS_ID}-2`, { + data: BOOKS, + }); + + BooksComponent = () => { + const [books, loaded, error] = useDataLoadedError(provider); + return ; + }; + + Component = () => ( + + + + ); + }); + + it("loading should be true when provider is loading first time and false when finish", async () => { + render(); + expect(screen.queryByTestId(LOADING_ID)).toBeInTheDocument(); + await wait(); + expect(screen.queryByTestId(LOADING_ID)).not.toBeInTheDocument(); + }); + + it("loading not should change when provider cache is cleaned", async () => { + render(); + await wait(); + expect(screen.queryByTestId(LOADING_ID)).not.toBeInTheDocument(); + provider.cleanCache(); + expect(screen.queryByTestId(LOADING_ID)).not.toBeInTheDocument(); + await wait(); + expect(screen.queryByTestId(LOADING_ID)).not.toBeInTheDocument(); + }); + + it("should pass data to the component", async () => { + const bookTitle = "Animal Farm"; + render(); + await wait(); + expect(screen.getByText(bookTitle)).toBeInTheDocument(); + }); + + it("should refresh data", async () => { + const TEST_ID = "book-2"; + render(); + await wait(); + expect(screen.queryByTestId(TEST_ID)).toBeInTheDocument(); + provider.delete(2); + await wait(); + expect(screen.queryByTestId(TEST_ID)).not.toBeInTheDocument(); + }); + + it("error should be null when provider does not throw error", async () => { + render(); + expect(screen.queryByTestId(ERROR_ID)).not.toBeInTheDocument(); + await wait(); + expect(screen.queryByTestId(ERROR_ID)).not.toBeInTheDocument(); + }); + + it("should return error when provider throws error", async () => { + const ERROR_MESSAGE = "Foo error"; + provider.error = new Error(ERROR_MESSAGE); + render(); + await wait(); + expect(screen.getByText(ERROR_MESSAGE)).toBeInTheDocument(); + }); + }); +}); From 3768b266ece166ff99de2aebf3808bb9107a3219 Mon Sep 17 00:00:00 2001 From: "javier.brea" Date: Mon, 9 Nov 2020 09:22:15 +0100 Subject: [PATCH 33/41] feat(hoc): Add withDataLoadedErrorComponents hoc --- CHANGELOG.md | 2 +- src/withDataProvider.js | 31 ++++ test/hocsMultipleProps.spec.js | 263 +++++++++++++++++++++++++++++++-- 3 files changed, 285 insertions(+), 11 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index a86b8cf..c7e0f12 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -6,7 +6,7 @@ and this project adheres to [Semantic Versioning](http://semver.org/). ## [unreleased] ### Added -- feat(hocs): Add withDataLoadedError, withDataLoadingError, withDataLoadingErrorComponents +- feat(hocs): Add withDataLoadedError, withDataLoadingError, withDataLoadingErrorComponents, withDataLoadedErrorComponents - feat(hooks): Add useDataLoadedError, useDataLoadingError ### Changed diff --git a/src/withDataProvider.js b/src/withDataProvider.js index 4497ea8..b219241 100644 --- a/src/withDataProvider.js +++ b/src/withDataProvider.js @@ -82,6 +82,37 @@ const useErrorCustomProp = (provider, key = defaultKeys[2]) => { return { error, errorProp }; }; +export const withDataLoadedErrorComponents = (provider, keys) => ( + Component, + LoadingComponent, + ErrorComponent +) => { + const WithDataLoadedErrorComponents = (props) => { + const providerToRead = useProvider(provider, props); + const { dataProp, loadedProp, errorProp, loaded, error } = useDataLoadedErrorCustomProps( + providerToRead, + keys + ); + if (error) { + if (ErrorComponent) { + return ; + } + return null; + } + if (!loaded) { + if (LoadingComponent) { + return ; + } + return null; + } + return ; + }; + WithDataLoadedErrorComponents.displayName = `WithDataLoadedErrorComponents${getDisplayName( + Component + )}`; + return WithDataLoadedErrorComponents; +}; + export const withDataLoadingErrorComponents = (provider, keys) => ( Component, LoadingComponent, diff --git a/test/hocsMultipleProps.spec.js b/test/hocsMultipleProps.spec.js index c5ddfaf..01c0e7b 100644 --- a/test/hocsMultipleProps.spec.js +++ b/test/hocsMultipleProps.spec.js @@ -6,7 +6,12 @@ import { render, screen } from "@testing-library/react"; import { providers } from "@data-provider/core"; import sinon from "sinon"; -import { withDataLoadedError, withDataLoadingError, withDataLoadingErrorComponents } from "../src"; +import { + withDataLoadedError, + withDataLoadingError, + withDataLoadingErrorComponents, + withDataLoadedErrorComponents, +} from "../src"; import MockProvider from "./MockProvider"; import { BOOKS, BOOKS_ID, LOADING_ID, ERROR_ID } from "./constants"; @@ -36,9 +41,8 @@ describe("HOCs providing multiple props", () => { describe("withDataLoadedError", () => { beforeEach(() => { - provider = new MockProvider(`${BOOKS_ID}-1`, { - data: BOOKS, - }); + provider.resetState(); + provider.cleanCache(); BooksComponent = ({ data, loaded, error }) => { return ; }; @@ -108,9 +112,8 @@ describe("HOCs providing multiple props", () => { describe("withDataLoadedError using custom props", () => { beforeEach(() => { - provider = new MockProvider(`${BOOKS_ID}-2`, { - data: BOOKS, - }); + provider.resetState(); + provider.cleanCache(); BooksComponent = ({ books, booksAreLoaded, booksError }) => { return ; }; @@ -160,9 +163,8 @@ describe("HOCs providing multiple props", () => { describe("withDataLoadedError using data custom prop", () => { beforeEach(() => { - provider = new MockProvider(`${BOOKS_ID}-3`, { - data: BOOKS, - }); + provider.resetState(); + provider.cleanCache(); BooksComponent = ({ books, loaded, error }) => { return ; }; @@ -604,4 +606,245 @@ describe("HOCs providing multiple props", () => { expect(screen.queryByTestId(BOOKS_ID)).not.toBeInTheDocument(); }); }); + + describe("withDataLoadedErrorComponents", () => { + const CUSTOM_LOADING_ID = `${LOADING_ID}-custom`; + const CUSTOM_ERROR_ID = `${ERROR_ID}-custom`; + let CustomLoadingComponent, CustomErrorComponent; + + beforeEach(() => { + provider.resetState(); + provider.cleanCache(); + CustomLoadingComponent = ({ loaded }) => { + if (loaded) { + return null; + } + return
Loading
; + }; + + CustomErrorComponent = ({ error = {} }) => { + return
{error.message}
; + }; + + BooksComponent = ({ data }) => { + return ; + }; + + BooksConnectedComponent = withDataLoadedErrorComponents(provider)( + BooksComponent, + CustomLoadingComponent, + CustomErrorComponent + ); + + Component = () => ( + + + + ); + }); + + it("should wrap displayName of the component", async () => { + expect(BooksConnectedComponent.displayName).toEqual( + "WithDataLoadedErrorComponentsBooksComponent" + ); + }); + + it("loading should be true when provider is loading first time and false when finish", async () => { + render(); + expect(screen.queryByTestId(CUSTOM_LOADING_ID)).toBeInTheDocument(); + await wait(); + expect(screen.queryByTestId(CUSTOM_LOADING_ID)).not.toBeInTheDocument(); + }); + + it("loading should not change when provider cache is cleaned", async () => { + render(); + await wait(); + expect(screen.queryByTestId(CUSTOM_LOADING_ID)).not.toBeInTheDocument(); + provider.cleanCache(); + expect(screen.queryByTestId(CUSTOM_LOADING_ID)).not.toBeInTheDocument(); + await wait(); + expect(screen.queryByTestId(CUSTOM_LOADING_ID)).not.toBeInTheDocument(); + }); + + it("should return null when provider is loading and no loading component is provided", async () => { + BooksConnectedComponent = withDataLoadedErrorComponents(provider)(BooksComponent); + + Component = () => ( + + + + ); + render(); + expect(screen.queryByTestId(CUSTOM_LOADING_ID)).not.toBeInTheDocument(); + expect(screen.queryByTestId(CUSTOM_ERROR_ID)).not.toBeInTheDocument(); + expect(screen.queryByTestId(BOOKS_ID)).not.toBeInTheDocument(); + }); + + it("should pass data to the component", async () => { + const bookTitle = "Animal Farm"; + render(); + await wait(); + expect(screen.getByText(bookTitle)).toBeInTheDocument(); + }); + + it("should refresh data", async () => { + const TEST_ID = "book-2"; + render(); + await wait(); + expect(screen.queryByTestId(TEST_ID)).toBeInTheDocument(); + provider.delete(2); + await wait(); + expect(screen.queryByTestId(TEST_ID)).not.toBeInTheDocument(); + }); + + it("error should be null when provider does not throw error", async () => { + render(); + expect(screen.queryByTestId(CUSTOM_ERROR_ID)).not.toBeInTheDocument(); + await wait(); + expect(screen.queryByTestId(CUSTOM_ERROR_ID)).not.toBeInTheDocument(); + }); + + it("should return error when provider throws error", async () => { + const ERROR_MESSAGE = "Foo error"; + provider.error = new Error(ERROR_MESSAGE); + render(); + await wait(); + expect(screen.getByText(ERROR_MESSAGE)).toBeInTheDocument(); + }); + + it("should return null when provider throws an error and no error component is provided", async () => { + BooksConnectedComponent = withDataLoadedErrorComponents(provider)( + BooksComponent, + CustomLoadingComponent + ); + + Component = () => ( + + + + ); + provider.error = new Error(); + render(); + await wait(); + expect(screen.queryByTestId(CUSTOM_LOADING_ID)).not.toBeInTheDocument(); + expect(screen.queryByTestId(CUSTOM_ERROR_ID)).not.toBeInTheDocument(); + expect(screen.queryByTestId(BOOKS_ID)).not.toBeInTheDocument(); + }); + }); + + describe("withDataLoadedErrorComponents using custom prop", () => { + const CUSTOM_LOADING_ID = `${LOADING_ID}-custom`; + const CUSTOM_ERROR_ID = `${ERROR_ID}-custom`; + let CustomLoadingComponent, CustomErrorComponent; + + beforeEach(() => { + provider = new MockProvider(BOOKS_ID, { + data: BOOKS, + }); + CustomLoadingComponent = ({ booksAreLoaded }) => { + if (booksAreLoaded) { + return null; + } + return
Loading
; + }; + + CustomErrorComponent = ({ booksError = {} }) => { + return
{booksError.message}
; + }; + + BooksConnectedComponent = withDataLoadedErrorComponents(provider, [ + "books", + "booksAreLoaded", + "booksError", + ])(Books, CustomLoadingComponent, CustomErrorComponent); + + Component = () => ( + + + + ); + }); + + it("loading should be true when provider is loading and false when finish", async () => { + render(); + expect(screen.queryByTestId(CUSTOM_LOADING_ID)).toBeInTheDocument(); + await wait(); + expect(screen.queryByTestId(CUSTOM_LOADING_ID)).not.toBeInTheDocument(); + }); + + it("loading should not change when provider cache is cleaned", async () => { + render(); + await wait(); + expect(screen.queryByTestId(CUSTOM_LOADING_ID)).not.toBeInTheDocument(); + provider.cleanCache(); + expect(screen.queryByTestId(CUSTOM_LOADING_ID)).not.toBeInTheDocument(); + await wait(); + expect(screen.queryByTestId(CUSTOM_LOADING_ID)).not.toBeInTheDocument(); + }); + + it("should return null when provider is loading and no loading component is provided", async () => { + BooksConnectedComponent = withDataLoadedErrorComponents(provider)(BooksComponent); + + Component = () => ( + + + + ); + render(); + expect(screen.queryByTestId(CUSTOM_LOADING_ID)).not.toBeInTheDocument(); + expect(screen.queryByTestId(CUSTOM_ERROR_ID)).not.toBeInTheDocument(); + expect(screen.queryByTestId(BOOKS_ID)).not.toBeInTheDocument(); + }); + + it("should pass data to the component", async () => { + const bookTitle = "Animal Farm"; + render(); + await wait(); + expect(screen.getByText(bookTitle)).toBeInTheDocument(); + }); + + it("should refresh data", async () => { + const TEST_ID = "book-2"; + render(); + await wait(); + expect(screen.queryByTestId(TEST_ID)).toBeInTheDocument(); + provider.delete(2); + await wait(); + expect(screen.queryByTestId(TEST_ID)).not.toBeInTheDocument(); + }); + + it("error should be null when provider does not throw error", async () => { + render(); + expect(screen.queryByTestId(CUSTOM_ERROR_ID)).not.toBeInTheDocument(); + await wait(); + expect(screen.queryByTestId(CUSTOM_ERROR_ID)).not.toBeInTheDocument(); + }); + + it("should return error when provider throws error", async () => { + const ERROR_MESSAGE = "Foo error"; + provider.error = new Error(ERROR_MESSAGE); + render(); + await wait(); + expect(screen.getByText(ERROR_MESSAGE)).toBeInTheDocument(); + }); + + it("should return null when provider throws an error and no error component is provided", async () => { + BooksConnectedComponent = withDataLoadedErrorComponents(provider)( + BooksComponent, + CustomLoadingComponent + ); + + Component = () => ( + + + + ); + provider.error = new Error(); + render(); + await wait(); + expect(screen.queryByTestId(CUSTOM_LOADING_ID)).not.toBeInTheDocument(); + expect(screen.queryByTestId(CUSTOM_ERROR_ID)).not.toBeInTheDocument(); + expect(screen.queryByTestId(BOOKS_ID)).not.toBeInTheDocument(); + }); + }); }); From 834b1bcd2769598baca47984d7b58446a52da5d8 Mon Sep 17 00:00:00 2001 From: "javier.brea" Date: Mon, 9 Nov 2020 09:28:27 +0100 Subject: [PATCH 34/41] test: Reset provider before each test instead of creating a new one --- test/hocsMultipleProps.spec.js | 5 ++--- test/hooksMultipleProps.spec.js | 5 ++--- 2 files changed, 4 insertions(+), 6 deletions(-) diff --git a/test/hocsMultipleProps.spec.js b/test/hocsMultipleProps.spec.js index 01c0e7b..e7da325 100644 --- a/test/hocsMultipleProps.spec.js +++ b/test/hocsMultipleProps.spec.js @@ -738,9 +738,8 @@ describe("HOCs providing multiple props", () => { let CustomLoadingComponent, CustomErrorComponent; beforeEach(() => { - provider = new MockProvider(BOOKS_ID, { - data: BOOKS, - }); + provider.resetState(); + provider.cleanCache(); CustomLoadingComponent = ({ booksAreLoaded }) => { if (booksAreLoaded) { return null; diff --git a/test/hooksMultipleProps.spec.js b/test/hooksMultipleProps.spec.js index 1ce58d1..91fa79e 100644 --- a/test/hooksMultipleProps.spec.js +++ b/test/hooksMultipleProps.spec.js @@ -97,9 +97,8 @@ describe("hooks returning multiple props", () => { describe("useDataLoadedError", () => { beforeEach(() => { - provider = new MockProvider(`${BOOKS_ID}-2`, { - data: BOOKS, - }); + provider.resetState(); + provider.cleanCache(); BooksComponent = () => { const [books, loaded, error] = useDataLoadedError(provider); From 105e08020818baee1daf8b36afc12c652cf5c10c Mon Sep 17 00:00:00 2001 From: "javier.brea" Date: Mon, 9 Nov 2020 09:56:38 +0100 Subject: [PATCH 35/41] test(e2e): Use new hocs and hooks in e2e tests --- test-e2e/cypress/integration/demo.js | 29 +++++++++++++++++-- .../support/page-objects/AuthorsLoaded.js | 12 ++++++++ .../support/page-objects/BooksLoaded.js | 12 ++++++++ .../support/page-objects/common/List.js | 6 ++-- test-e2e/react-app/.eslintrc.json | 1 - test-e2e/react-app/src/App.js | 6 ++++ .../modules/authors-loaded/AuthorsLoaded.js | 24 +++++++++++++++ .../src/modules/authors-loaded/index.js | 1 + .../react-app/src/modules/authors/Authors.js | 3 +- .../src/modules/books-loaded/BooksLoaded.js | 25 ++++++++++++++++ .../src/modules/books-loaded/index.js | 1 + .../src/modules/rerenderer/Rerenderer.js | 16 +++++----- 12 files changed, 121 insertions(+), 15 deletions(-) create mode 100644 test-e2e/cypress/support/page-objects/AuthorsLoaded.js create mode 100644 test-e2e/cypress/support/page-objects/BooksLoaded.js create mode 100644 test-e2e/react-app/src/modules/authors-loaded/AuthorsLoaded.js create mode 100644 test-e2e/react-app/src/modules/authors-loaded/index.js create mode 100644 test-e2e/react-app/src/modules/books-loaded/BooksLoaded.js create mode 100644 test-e2e/react-app/src/modules/books-loaded/index.js diff --git a/test-e2e/cypress/integration/demo.js b/test-e2e/cypress/integration/demo.js index 31fad23..4ebc72a 100644 --- a/test-e2e/cypress/integration/demo.js +++ b/test-e2e/cypress/integration/demo.js @@ -1,5 +1,7 @@ import Books from "../support/page-objects/Books"; import Authors from "../support/page-objects/Authors"; +import BooksLoaded from "../support/page-objects/BooksLoaded"; +import AuthorsLoaded from "../support/page-objects/AuthorsLoaded"; describe("Demo page", () => { const NEW_AUTHOR = "Cervantes"; @@ -7,22 +9,34 @@ describe("Demo page", () => { const NEW_BOOK = "Don Quijote"; const NEW_BOOK_2 = "Don Quijote parte 1"; let books; + let booksLoaded; let authors; + let authorsLoaded; before(() => { books = new Books(); authors = new Authors(); + booksLoaded = new BooksLoaded(); + authorsLoaded = new AuthorsLoaded(); cy.visit("/"); }); describe("loaders", () => { - it("should display books loading", () => { + it("should display authors loading", () => { authors.shouldBeLoading(); }); - it("should display authors loading", () => { + it("should display authorsLoaded loading the first time", () => { + authorsLoaded.shouldBeLoading(); + }); + + it("should display books loading", () => { books.shouldBeLoading(); }); + + it("should display booksLoaded loading the first time", () => { + booksLoaded.shouldBeLoading(); + }); }); describe("authors column", () => { @@ -30,10 +44,16 @@ describe("Demo page", () => { authors.shouldDisplayItems(4); }); + it("should not be loading", () => { + authors.shouldNotBeLoading(); + }); + it("should display 5 results after adding a new book", () => { authors.add(NEW_AUTHOR); authors.shouldBeLoading(); + authorsLoaded.shouldNotBeLoading(); books.shouldBeLoading(); + booksLoaded.shouldNotBeLoading(); authors.shouldDisplayItems(5); }); @@ -48,7 +68,9 @@ describe("Demo page", () => { authors.shouldDisplayItems(6); authors.delete(6); authors.shouldBeLoading(); + authorsLoaded.shouldNotBeLoading(); books.shouldBeLoading(); + booksLoaded.shouldNotBeLoading(); authors.shouldDisplayItems(5); }); }); @@ -87,6 +109,7 @@ describe("Demo page", () => { it("should display 6 results after adding a new book", () => { books.add("Don Quijote", NEW_AUTHOR); books.shouldBeLoading(); + booksLoaded.shouldNotBeLoading(); books.shouldDisplayItems(6); }); @@ -101,6 +124,7 @@ describe("Demo page", () => { books.shouldDisplayItems(7); books.delete(7); books.shouldBeLoading(); + booksLoaded.shouldNotBeLoading(); authors.shouldNotBeLoading(); books.shouldDisplayItems(6); }); @@ -140,6 +164,7 @@ describe("Demo page", () => { books.shouldBeLoading(); books.search.shouldBeLoading(); authors.shouldBeLoading(); + authorsLoaded.shouldNotBeLoading(); authors.search.shouldBeLoading(); authors.shouldDisplayItems(4); books.shouldDisplayItems(4); diff --git a/test-e2e/cypress/support/page-objects/AuthorsLoaded.js b/test-e2e/cypress/support/page-objects/AuthorsLoaded.js new file mode 100644 index 0000000..f227ad3 --- /dev/null +++ b/test-e2e/cypress/support/page-objects/AuthorsLoaded.js @@ -0,0 +1,12 @@ +import List from "./common/List"; + +class AuthorsLoaded extends List { + constructor() { + super({ + COLUMN: "#authors-loaded-column", + CONTAINER: "#authors-loaded-container", + }); + } +} + +export default AuthorsLoaded; diff --git a/test-e2e/cypress/support/page-objects/BooksLoaded.js b/test-e2e/cypress/support/page-objects/BooksLoaded.js new file mode 100644 index 0000000..955b0e4 --- /dev/null +++ b/test-e2e/cypress/support/page-objects/BooksLoaded.js @@ -0,0 +1,12 @@ +import List from "./common/List"; + +class BooksLoaded extends List { + constructor() { + super({ + COLUMN: "#books-loaded-column", + CONTAINER: "#books-loaded-container", + }); + } +} + +export default BooksLoaded; diff --git a/test-e2e/cypress/support/page-objects/common/List.js b/test-e2e/cypress/support/page-objects/common/List.js index 2c26ff2..0d8f8ef 100644 --- a/test-e2e/cypress/support/page-objects/common/List.js +++ b/test-e2e/cypress/support/page-objects/common/List.js @@ -3,8 +3,8 @@ class List { this.SELECTORS = selectors; } - getColumn() { - return cy.get(this.SELECTORS.COLUMN); + getColumn(options = {}) { + return cy.get(this.SELECTORS.COLUMN, options); } getContainer() { @@ -44,7 +44,7 @@ class List { } shouldNotBeLoading() { - this.getColumn().should("not.have.class", "loading"); + this.getColumn({ timeout: 0 }).should("not.have.class", "loading"); } delete(index) { diff --git a/test-e2e/react-app/.eslintrc.json b/test-e2e/react-app/.eslintrc.json index be85317..a36e019 100644 --- a/test-e2e/react-app/.eslintrc.json +++ b/test-e2e/react-app/.eslintrc.json @@ -4,7 +4,6 @@ "es6": true }, "parser": "babel-eslint", - "extends": ["plugin:react/recommended"], "parserOptions": { "sourceType": "module" }, diff --git a/test-e2e/react-app/src/App.js b/test-e2e/react-app/src/App.js index 33a07f5..88ab7f1 100644 --- a/test-e2e/react-app/src/App.js +++ b/test-e2e/react-app/src/App.js @@ -4,7 +4,9 @@ import { storeManager } from "@data-provider/core"; import "./configProviders"; import Authors from "modules/authors"; +import AuthorsLoaded from "modules/authors-loaded"; import Books from "modules/books"; +import BooksLoaded from "modules/books-loaded"; import AuthorsSearch from "modules/authors-search"; import BooksSearch from "modules/books-search"; // import Rerender from "modules/rerenderer"; @@ -32,6 +34,10 @@ function App() { +
+ + +
diff --git a/test-e2e/react-app/src/modules/authors-loaded/AuthorsLoaded.js b/test-e2e/react-app/src/modules/authors-loaded/AuthorsLoaded.js new file mode 100644 index 0000000..962d868 --- /dev/null +++ b/test-e2e/react-app/src/modules/authors-loaded/AuthorsLoaded.js @@ -0,0 +1,24 @@ +import { useDataLoadedError } from "@data-provider/react"; + +import { authorsProvider } from "data/authors"; +import AuthorsList from "modules/authors-list"; +import SectionContainer from "components/section-container"; +import ItemsTitle from "components/items-title"; +import ItemsListContainer from "components/items-list-container"; + +const AuthorsLoaded = () => { + const [authors, loaded] = useDataLoadedError(authorsProvider); + + console.log("Rendering authors loaded", loaded, authors); + + return ( + + + + + + + ); +}; + +export default AuthorsLoaded; diff --git a/test-e2e/react-app/src/modules/authors-loaded/index.js b/test-e2e/react-app/src/modules/authors-loaded/index.js new file mode 100644 index 0000000..0ecd396 --- /dev/null +++ b/test-e2e/react-app/src/modules/authors-loaded/index.js @@ -0,0 +1 @@ +export { default } from "./AuthorsLoaded"; diff --git a/test-e2e/react-app/src/modules/authors/Authors.js b/test-e2e/react-app/src/modules/authors/Authors.js index 540e0ee..9fe1b6a 100644 --- a/test-e2e/react-app/src/modules/authors/Authors.js +++ b/test-e2e/react-app/src/modules/authors/Authors.js @@ -1,4 +1,4 @@ -import { useRefresh, useData, useLoading } from "@data-provider/react"; +import { useData, useLoading } from "@data-provider/react"; import { authorsProvider } from "data/authors"; import AuthorsList from "modules/authors-list"; @@ -9,7 +9,6 @@ import ItemsListContainer from "components/items-list-container"; import AuthorNew from "./modules/author-new"; const Authors = () => { - useRefresh(authorsProvider); const authors = useData(authorsProvider); const loading = useLoading(authorsProvider); diff --git a/test-e2e/react-app/src/modules/books-loaded/BooksLoaded.js b/test-e2e/react-app/src/modules/books-loaded/BooksLoaded.js new file mode 100644 index 0000000..a8135df --- /dev/null +++ b/test-e2e/react-app/src/modules/books-loaded/BooksLoaded.js @@ -0,0 +1,25 @@ +import { useData, useLoaded } from "@data-provider/react"; + +import { booksWithAuthorName } from "data/books"; +import BooksList from "modules/books-list"; +import SectionContainer from "components/section-container"; +import ItemsTitle from "components/items-title"; +import ItemsListContainer from "components/items-list-container"; + +const Books = () => { + const books = useData(booksWithAuthorName); + const loaded = useLoaded(booksWithAuthorName); + + console.log("Rendering books loaded", loaded, books); + + return ( + + + + + + + ); +}; + +export default Books; diff --git a/test-e2e/react-app/src/modules/books-loaded/index.js b/test-e2e/react-app/src/modules/books-loaded/index.js new file mode 100644 index 0000000..4f00ee5 --- /dev/null +++ b/test-e2e/react-app/src/modules/books-loaded/index.js @@ -0,0 +1 @@ +export { default } from "./BooksLoaded"; diff --git a/test-e2e/react-app/src/modules/rerenderer/Rerenderer.js b/test-e2e/react-app/src/modules/rerenderer/Rerenderer.js index 8b2b4e8..3180cb7 100644 --- a/test-e2e/react-app/src/modules/rerenderer/Rerenderer.js +++ b/test-e2e/react-app/src/modules/rerenderer/Rerenderer.js @@ -5,8 +5,8 @@ import BooksSearch from "modules/books-search"; import BooksList from "modules/books-list"; import { - withDataProviderBranch, - withDataProvider, + withDataLoadingErrorComponents, + withDataLoadingError, withData, withLoading, } from "helpers/data-provider"; @@ -14,12 +14,14 @@ import { import Loading from "./Loading"; import Wrapper from "./Wrapper"; -const BooksListConnected = withDataProviderBranch(booksWithAuthorName, ["books", "isLoading"])( - BooksList, - Loading -); +const BooksListConnected = withDataLoadingErrorComponents(booksWithAuthorName, [ + "books", + "isLoading", +])(BooksList, Loading); -const WrapperConnected = withDataProvider(booksWithAuthorName, ["books", "isLoading"])(Wrapper); +const WrapperConnected = withDataLoadingError(booksWithAuthorName, ["books", "isLoading"])( + Wrapper +); const WrapperConnectedToDataAndLoading = withLoading( booksWithAuthorName, "isLoading" From a23b448e9c6f9332e8065bd16243efac77261389 Mon Sep 17 00:00:00 2001 From: "javier.brea" Date: Mon, 9 Nov 2020 10:23:56 +0100 Subject: [PATCH 36/41] docs: Add new hocs and hooks docs --- README.md | 104 ++++++++++++++++++++++++++++++++++++++++++------------ 1 file changed, 82 insertions(+), 22 deletions(-) diff --git a/README.md b/README.md index ff1b382..79bed5b 100644 --- a/README.md +++ b/README.md @@ -16,7 +16,8 @@ npm i --save @data-provider/react ## Available hooks -* [useDataProvider](#usedataproviderprovider-equalityfn) +* [useDataLoadingError](#usedataloadingerrorprovider-equalityfn) +* [useDataLoadedError](#usedataloadederrorprovider-equalityfn) * [useData](#usedataprovider-equalityfn) * [useLoading](#useloadingprovider) * [useLoaded](#useloadedprovider) @@ -25,17 +26,19 @@ npm i --save @data-provider/react ## Available HOCs -* [withDataProvider](#withdataproviderprovider-custompropertiesnamescomponent) +* [withDataLoadingError](#withdataloadingerrorprovider-custompropertiesnamescomponent) +* [withDataLoadedError](#withdataloadederrorprovider-custompropertiesnamescomponent) * [withData](#withdataprovider-custompropnamecomponent) * [withLoading](#withloadingprovider-custompropnamecomponent) * [withLoaded](#withloadedprovider-custompropnamecomponent) * [withError](#witherrorprovider-custompropnamecomponent) * [withPolling](#withpollingprovider-intervalcomponent) -* [withDataProviderBranch](#withdataproviderbranchprovider-custompropertiesnamescomponent-loadingcomponent-errorcomponent) +* [withDataLoadingErrorComponents](#withdataloadingerrorcomponentsprovider-custompropertiesnamescomponent-loadingcomponent-errorcomponent) +* [withDataLoadedErrorComponents](#withdataloadederrorcomponentsprovider-custompropertiesnamescomponent-notloadedcomponent-errorcomponent) ## Hooks -### `useDataProvider(provider, [equalityFn])` +### `useDataLoadingError(provider, [equalityFn])` Triggers the provider `read` method and gives you the `data`, `loading` and `error` properties from the state of the provider or selector. When the provider cache is cleaned, it automatically triggers `read` again. @@ -53,12 +56,37 @@ Use this hook only when you need all mentioned properties, because your componen #### Example ```jsx -import { useDataProvider } from "@data-provider/react"; +import { useDataLoadingError } from "@data-provider/react"; import { books } from "../data/books"; const BooksList = () => { - const [data, loading, error] = useDataProvider(books); + const [data, loading, error] = useDataLoadingError(books); + // Do your stuff here +}; +``` + +### `useDataLoadedError(provider, [equalityFn])` + +This hook has the same behavior and interface than the described for the [`useDataLoadingError`](#usedataloadingerrorprovider-equalityfn) one, but it returns the `data`, `loaded` and `error` properties from the state of the provider or selector. + +Use this hook only when you don't want to rerender a Component each time the provider is loading. It will return `loaded` as `true` once the provider has loaded for the first time, and it will not change again. This is useful to avoid rerenders in scenarios having "pollings", for example, as it will avoid to render a "loading" each time the data is refreshed. + +Take into account that the `loaded` property will not be set as `true` until a success read has finished, so the error may have a value, even when `loaded` is `false`. + +#### Returns + +* _(Array)_ - Array containing `data`, `loaded` and `error` properties, in that order. + +#### Example + +```jsx +import { useDataLoadedError } from "@data-provider/react"; + +import { books } from "../data/books"; + +const BooksList = () => { + const [data, loaded, error] = useDataLoadedError(books); // Do your stuff here }; ``` @@ -67,7 +95,7 @@ const BooksList = () => { Triggers `read` and gives you only the `data` property from the state of the provider or selector. When the provider cache is cleaned, it automatically triggers `read` again. -Arguments are the same than described for the [`useDataProvider` hook](#usedataproviderprovider-equalityfn). +Arguments are the same than described for the [`useDataLoadingError` hook](#usedataloadingerrorprovider-equalityfn). #### Returns @@ -189,7 +217,7 @@ const BooksList = () => { ## HOCs -### `withDataProvider(provider, [customPropertiesNames])(Component)` +### `withDataLoadingError(provider, [customPropertiesNames])(Component)` This High Order Component triggers the read method of the provider and gives to the component the `data`, `loading` and `error` properties from its state. It will trigger the `read` method each time the provider cache is cleaned. @@ -205,7 +233,7 @@ Use this HOC only when you need all mentioned properties, because your component Using a provider: ```jsx -import { withDataProvider } from "@data-provider/react"; +import { withDataLoadingError } from "@data-provider/react"; import { books } from "../data/books"; @@ -213,7 +241,7 @@ const BooksList = ({ data, loading, error }) => { // Do your stuff here }; -export default withDataProvider(books)(BooksList); +export default withDataLoadingError(books)(BooksList); ``` With custom properties: @@ -223,7 +251,7 @@ const BooksList = ({ booksData, booksLoading, booksError }) => { // Do your stuff here }; -export default withDataProvider(books, ["booksData", "booksLoading", "booksError"])(BooksList); +export default withDataLoadingError(books, ["booksData", "booksLoading", "booksError"])(BooksList); ``` Using a function: @@ -233,16 +261,24 @@ const BookDetail = ({ data, loading, error }) => { // Do your stuff here }; -export default withDataProvider(({ id }) => books.query({ urlParam: { id }}))(BookDetail); +export default withDataLoadingError(({ id }) => books.query({ urlParam: { id }}))(BookDetail); ``` +### `withDataLoadedError(provider, [customPropertiesNames])(Component)` + +This hoc has the same behavior and interface than the described for the [`withDataLoadingError`](#withdataloadingerrorprovider-custompropertiesnamescomponent) one, but it provides the `data`, `loaded` and `error` properties from the state. + +Use this hook only when you don't want to rerender a Component each time the provider is loading. It will return `loaded` as `true` once the provider has loaded for the first time, and it will not change again. This is useful to avoid rerenders in scenarios having "pollings", for example, as it will avoid to render a "loading" each time the data is refreshed. + +Take into account that the `loaded` property will not be set as `true` until a success read has finished, so the error may have a value, even when `loaded` is `false`. + ### `withData(provider, customPropName)(Component)` This High Order Component triggers the read method of the provider and gives to the component only the `data` property from its state. It will trigger the `read` method each time the provider cache is cleaned. #### Arguments -* `provider` _(Object)_: [Data Provider][data-provider] provider or selector instance, or a function as described in the [withDataProvider HOC docs](#withdataproviderprovider-custompropertiesnamescomponent) +* `provider` _(Object)_: [Data Provider][data-provider] provider or selector instance, or a function as described in the [withDataLoadingError HOC docs](#withdataloadingerrorprovider-custompropertiesnamescomponent) * `customPropName` _(String)_: By default, the HOC will pass to the component a `data` property. You can change that prop passing a new property name as second argument. #### Examples @@ -275,7 +311,7 @@ This High Order Component triggers the read method of the provider and gives to #### Arguments -* `provider` _(Object)_: [Data Provider][data-provider] provider or selector instance, or a function as described in the [withDataProvider HOC docs](#withdataproviderprovider-custompropertiesnamescomponent) +* `provider` _(Object)_: [Data Provider][data-provider] provider or selector instance, or a function as described in the [withDataLoadingError HOC docs](#withdataloadingerrorprovider-custompropertiesnamescomponent) * `customPropName` _(String)_: By default, the HOC will pass to the component a `loading` property. You can change that prop passing a new property name as second argument. #### Examples @@ -308,7 +344,7 @@ This High Order Component triggers the read method of the provider and gives to #### Arguments -* `provider` _(Object)_: [Data Provider][data-provider] provider or selector instance, or a function as described in the [withDataProvider HOC docs](#withdataproviderprovider-custompropertiesnamescomponent) +* `provider` _(Object)_: [Data Provider][data-provider] provider or selector instance, or a function as described in the [withDataLoadingError HOC docs](#withdataloadingerrorprovider-custompropertiesnamescomponent) * `customPropName` _(String)_: By default, the HOC will pass to the component a `loaded` property. You can change that prop passing a new property name as second argument. #### Examples @@ -341,7 +377,7 @@ This High Order Component triggers the read method of the provider and gives to #### Arguments -* `provider` _(Object)_: [Data Provider][data-provider] provider or selector instance, or a function as described in the [withDataProvider HOC docs](#withdataproviderprovider-custompropertiesnamescomponent) +* `provider` _(Object)_: [Data Provider][data-provider] provider or selector instance, or a function as described in the [withDataLoadingError HOC docs](#withdataloadingerrorprovider-custompropertiesnamescomponent) * `customPropName` _(String)_: By default, the HOC will pass to the component an `error` property. You can change that prop passing a new property name as second argument. #### Examples @@ -374,7 +410,7 @@ This High Order Component works as the hook `usePolling` described above. #### Arguments -* `provider` _(Object)_: [Data Provider][data-provider] provider or selector instance, or a function as described in the [withDataProvider HOC docs](#withdataproviderprovider-custompropertiesnamescomponent) +* `provider` _(Object)_: [Data Provider][data-provider] provider or selector instance, or a function as described in the [withDataLoadingError HOC docs](#withdataloadingerrorprovider-custompropertiesnamescomponent) * `interval` _(Object)_: Interval in miliseconds to clean the provider dependencies cache. Default is 5000. #### Example @@ -393,14 +429,14 @@ export default withPolling(books, 3000)(withData(books)(BooksList)); #### Arguments -* `provider` _(Object)_: [Data Provider][data-provider] provider or selector instance, or a function as described in the [withDataProvider HOC docs](#withdataproviderprovider-custompropertiesnamescomponent) +* `provider` _(Object)_: [Data Provider][data-provider] provider or selector instance, or a function as described in the [withDataLoadingError HOC docs](#withdataloadingerrorprovider-custompropertiesnamescomponent) -### `withDataProviderBranch(provider, [customPropertiesNames])(Component, LoadingComponent, ErrorComponent)` +### `withDataLoadingErrorComponents(provider, [customPropertiesNames])(Component, LoadingComponent, ErrorComponent)` -This HOC works as the already described [`withDataProvider`](#withdataproviderprovider-custompropertiesnamescomponent), but it will render one component or another depending of the result. If the provider is loading, it will render `LoadingComponent`, if it has an error, it will render `ErrorComponent` (passing the `error` property to it), or `Component` when there is no error and it is not loading (passing the `data` property to it). +This HOC works as the already described [`withDataLoadingError`](#withdataloadingerrorprovider-custompropertiesnamescomponent), but it will render one component or another depending of the result. If the provider is loading, it will render `LoadingComponent`, if it has an error, it will render `ErrorComponent` (passing the `error` property to it), or it will render `Component` when there is no error and it is not loading (passing the `data` property to it). ```jsx -import { withDataProviderBranch } from "@data-provider/react"; +import { withDataLoadingErrorComponents } from "@data-provider/react"; import { books } from "../data/books"; @@ -416,7 +452,31 @@ const BooksError = ({ error }) => { // Do your stuff here }; -export default withDataProviderBranch(books)(BooksList, BooksLoading, BooksError); +export default withDataLoadingErrorComponents(books)(BooksList, BooksLoading, BooksError); +``` + +### `withDataLoadedErrorComponents(provider, [customPropertiesNames])(Component, NotLoadedComponent, ErrorComponent)` + +This HOC works as the already described [`withDataLoadedError`](#withdataloadederrorprovider-custompropertiesnamescomponent), but it will render one component or another depending of the result. If the provider has an error, it will render `ErrorComponent` (passing the `error` property to it), if it has not loaded, it will render `NotLoadedComponent`, or it will render `Component` when there is no error and it has loaded (passing the `data` property to it). + +```jsx +import { withDataLoadedErrorComponents } from "@data-provider/react"; + +import { books } from "../data/books"; + +const BooksList = ({ data }) => { + // Do your stuff here +}; + +const BooksNotLoaded = () => { + // Do your stuff here +}; + +const BooksError = ({ error }) => { + // Do your stuff here +}; + +export default withDataLoadedErrorComponents(books)(BooksList, BooksNotLoaded, BooksError); ``` ## Contributing From 57a4511f5c15e1c2a557943aef1dcd1ec642f30d Mon Sep 17 00:00:00 2001 From: "javier.brea" Date: Mon, 9 Nov 2020 10:27:57 +0100 Subject: [PATCH 37/41] docs(#99): Add example of withDataLoadedError usage --- README.md | 28 +++++++++++++++++++++++++++- 1 file changed, 27 insertions(+), 1 deletion(-) diff --git a/README.md b/README.md index 79bed5b..ce5e6cd 100644 --- a/README.md +++ b/README.md @@ -270,7 +270,33 @@ This hoc has the same behavior and interface than the described for the [`withDa Use this hook only when you don't want to rerender a Component each time the provider is loading. It will return `loaded` as `true` once the provider has loaded for the first time, and it will not change again. This is useful to avoid rerenders in scenarios having "pollings", for example, as it will avoid to render a "loading" each time the data is refreshed. -Take into account that the `loaded` property will not be set as `true` until a success read has finished, so the error may have a value, even when `loaded` is `false`. +Take into account that the `loaded` property will not be set as `true` until a success read has finished, so the error may have a value, even when `loaded` is `false`. + +#### Examples + +Using a provider: + +```jsx +import { withDataLoadedError } from "@data-provider/react"; + +import { books } from "../data/books"; + +const BooksList = ({ data, loaded, error }) => { + // Do your stuff here +}; + +export default withDataLoadedError(books)(BooksList); +``` + +With custom properties: + +```jsx +const BooksList = ({ booksData, booksAreLoaded, booksError }) => { + // Do your stuff here +}; + +export default withDataLoadedError(books, ["booksData", "booksAreLoaded", "booksError"])(BooksList); +``` ### `withData(provider, customPropName)(Component)` From 7dd4296eef0ce732c57463cb6250978b7361279f Mon Sep 17 00:00:00 2001 From: "javier.brea" Date: Mon, 9 Nov 2020 11:26:15 +0100 Subject: [PATCH 38/41] fix(#101): Add hoist-non-react-statics to HOCs --- CHANGELOG.md | 3 +++ jest.config.js | 2 +- package-lock.json | 4 +--- package.json | 3 +++ rollup.config.js | 3 ++- src/withDataProvider.js | 11 +++++++++++ test/Books.js | 2 ++ test/hocs.spec.js | 26 ++++++++++++++++++++++++++ test/hocsDeprecated.spec.js | 12 ++++++++++++ test/hocsMultipleProps.spec.js | 24 ++++++++++++++++++++++++ 10 files changed, 85 insertions(+), 5 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index c7e0f12..edce98b 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -15,6 +15,9 @@ and this project adheres to [Semantic Versioning](http://semver.org/). - feat(hooks): Deprecate useDataProvider in favour of useDataLoadingError ### Fixed + +- fix(#101): Add hoist-non-react-statics to HOCs + ### Removed ## [1.3.0] - 2020-10-31 diff --git a/jest.config.js b/jest.config.js index 1fe0a5e..3d020c8 100644 --- a/jest.config.js +++ b/jest.config.js @@ -29,7 +29,7 @@ module.exports = { // The glob patterns Jest uses to detect test files testMatch: ["**/test/**/?(*.)+(spec|test).js?(x)"], - // testMatch: ["**/test/withPolling.spec.js"], + // testMatch: ["**/test/hocs.spec.js"], transform: { ".js$": "babel-jest", diff --git a/package-lock.json b/package-lock.json index f989070..356d022 100644 --- a/package-lock.json +++ b/package-lock.json @@ -5547,7 +5547,6 @@ "version": "3.3.2", "resolved": "https://registry.npmjs.org/hoist-non-react-statics/-/hoist-non-react-statics-3.3.2.tgz", "integrity": "sha512-/gGivxi8JPKWNm/W0jSmzcMPpfpPLc3dY/6GxhX2hQ9iGj3aDfklV4ET7NjKpSinLpJ5vafa9iiGIEZg10SfBw==", - "dev": true, "requires": { "react-is": "^16.7.0" }, @@ -5555,8 +5554,7 @@ "react-is": { "version": "16.13.1", "resolved": "https://registry.npmjs.org/react-is/-/react-is-16.13.1.tgz", - "integrity": "sha512-24e6ynE2H+OKt4kqsOvNd8kBpV65zoxbA4BVsEOB3ARVWQki/DHzaUoC5KuON/BiccDaCCTZBuOcfZs70kR8bQ==", - "dev": true + "integrity": "sha512-24e6ynE2H+OKt4kqsOvNd8kBpV65zoxbA4BVsEOB3ARVWQki/DHzaUoC5KuON/BiccDaCCTZBuOcfZs70kR8bQ==" } } }, diff --git a/package.json b/package.json index f9cc0bb..54941ce 100644 --- a/package.json +++ b/package.json @@ -40,6 +40,9 @@ "test:coverage": "npm run test:unit:ci", "coveralls": "cat ./coverage/lcov.info | coveralls" }, + "dependencies": { + "hoist-non-react-statics": "3.3.2" + }, "peerDependencies": { "@data-provider/core": ">=2.5.2", "react-redux": ">=7.1.0", diff --git a/rollup.config.js b/rollup.config.js index 4a959c7..77f96b3 100644 --- a/rollup.config.js +++ b/rollup.config.js @@ -33,7 +33,7 @@ const BASE_PLUGINS = [ const BASE_CONFIG = { input: "src/index.js", - external: ["@data-provider/core", "react", "react-redux"], + external: ["@data-provider/core", "react", "react-redux", "hoist-non-react-statics"], plugins: [...BASE_PLUGINS, terser()], }; @@ -41,6 +41,7 @@ const GLOBALS = { "@data-provider/core": "dataProvider", "react-redux": "ReactRedux", react: "React", + "hoist-non-react-statics": "hoistNonReactStatics", }; module.exports = [ diff --git a/src/withDataProvider.js b/src/withDataProvider.js index b219241..24156b3 100644 --- a/src/withDataProvider.js +++ b/src/withDataProvider.js @@ -1,4 +1,5 @@ import React, { useMemo } from "react"; +import hoistNonReactStatics from "hoist-non-react-statics"; import { deprecatedMethod } from "./helpers"; @@ -107,6 +108,7 @@ export const withDataLoadedErrorComponents = (provider, keys) => ( } return ; }; + hoistNonReactStatics(WithDataLoadedErrorComponents, Component); WithDataLoadedErrorComponents.displayName = `WithDataLoadedErrorComponents${getDisplayName( Component )}`; @@ -138,6 +140,7 @@ export const withDataLoadingErrorComponents = (provider, keys) => ( } return ; }; + hoistNonReactStatics(WithDataLoadingErrorComponents, Component); WithDataLoadingErrorComponents.displayName = `WithDataLoadingErrorComponents${getDisplayName( Component )}`; @@ -166,6 +169,7 @@ export const withDataLoadedError = (provider, keys) => (Component) => { ); return ; }; + hoistNonReactStatics(WithDataLoadedError, Component); WithDataLoadedError.displayName = `WithDataLoadedError${getDisplayName(Component)}`; return WithDataLoadedError; }; @@ -179,6 +183,7 @@ export const withDataLoadingError = (provider, keys) => (Component) => { ); return ; }; + hoistNonReactStatics(WithDataLoadingError, Component); WithDataLoadingError.displayName = `WithDataLoadingError${getDisplayName(Component)}`; return WithDataLoadingError; }; @@ -194,6 +199,7 @@ export const withData = (provider, key) => (Component) => { const { dataProp } = useDataCustomProp(providerToRead, key); return ; }; + hoistNonReactStatics(WithData, Component); WithData.displayName = `WithData${getDisplayName(Component)}`; return WithData; }; @@ -204,6 +210,7 @@ export const withLoading = (provider, key) => (Component) => { const { loadingProp } = useLoadingCustomProp(providerToRead, key); return ; }; + hoistNonReactStatics(WithLoading, Component); WithLoading.displayName = `WithLoading${getDisplayName(Component)}`; return WithLoading; }; @@ -214,6 +221,7 @@ export const withLoaded = (provider, key) => (Component) => { const { loadedProp } = useLoadedCustomProp(providerToRead, key); return ; }; + hoistNonReactStatics(WithLoaded, Component); WithLoaded.displayName = `WithLoaded${getDisplayName(Component)}`; return WithLoaded; }; @@ -224,6 +232,7 @@ export const withError = (provider, key) => (Component) => { const { errorProp } = useErrorCustomProp(providerToRead, key); return ; }; + hoistNonReactStatics(WithError, Component); WithError.displayName = `WithError${getDisplayName(Component)}`; return WithError; }; @@ -234,6 +243,7 @@ export const withPolling = (provider, interval) => (Component) => { usePolling(providerToRead, interval); return ; }; + hoistNonReactStatics(WithPolling, Component); WithPolling.displayName = `WithPolling${getDisplayName(Component)}`; return WithPolling; }; @@ -244,6 +254,7 @@ export const withRefresh = (provider) => (Component) => { useRefresh(providerToRead); return ; }; + hoistNonReactStatics(WithRefresh, Component); WithRefresh.displayName = `WithRefresh${getDisplayName(Component)}`; return WithRefresh; }; diff --git a/test/Books.js b/test/Books.js index 1c47e16..31bd024 100644 --- a/test/Books.js +++ b/test/Books.js @@ -29,4 +29,6 @@ Books.propTypes = { error: PropTypes.instanceOf(Error), }; +Books.fooProperty = "foo"; + export default Books; diff --git a/test/hocs.spec.js b/test/hocs.spec.js index a34d35b..d5b8df7 100644 --- a/test/hocs.spec.js +++ b/test/hocs.spec.js @@ -40,6 +40,8 @@ describe("HOCs", () => { return ; }; + BooksComponent.fooProperty = "foo"; + BooksConnectedComponent = withData(provider)(BooksComponent); Component = () => ( @@ -53,6 +55,10 @@ describe("HOCs", () => { expect(BooksConnectedComponent.displayName).toEqual("WithDataBooksComponent"); }); + it("should have available statics of the component", async () => { + expect(BooksConnectedComponent.fooProperty).toEqual("foo"); + }); + it("should wrap displayName of the component using name property if component has not displayName", async () => { BooksComponent = ({ data }) => { return ; @@ -162,6 +168,10 @@ describe("HOCs", () => { ); }); + it("should have available statics of the component", async () => { + expect(BooksConnectedComponent.fooProperty).toEqual("foo"); + }); + it("should wrap displayName of the component", async () => { expect(BooksConnectedComponent.displayName).toEqual("WithLoadingBooks"); }); @@ -213,6 +223,8 @@ describe("HOCs", () => { return ; }; + BooksComponent.fooProperty = "foo"; + BooksConnectedComponent = withLoaded(provider)(BooksComponent); Component = () => ( @@ -222,6 +234,10 @@ describe("HOCs", () => { ); }); + it("should have available statics of the component", async () => { + expect(BooksConnectedComponent.fooProperty).toEqual("foo"); + }); + it("should wrap displayName of the component", async () => { expect(BooksConnectedComponent.displayName).toEqual("WithLoadedBooksComponent"); }); @@ -289,6 +305,10 @@ describe("HOCs", () => { ); }); + it("should have available statics of the component", async () => { + expect(BooksConnectedComponent.fooProperty).toEqual("foo"); + }); + it("should be null when provider does not throw error", async () => { render(); expect(screen.queryByTestId(ERROR_ID)).not.toBeInTheDocument(); @@ -343,6 +363,8 @@ describe("HOCs", () => { return ; }; + BooksComponent.fooProperty = "foo"; + BooksConnectedComponent = withRefresh(provider)(BooksComponent); Component = () => ( @@ -352,6 +374,10 @@ describe("HOCs", () => { ); }); + it("should have available statics of the component", async () => { + expect(BooksConnectedComponent.fooProperty).toEqual("foo"); + }); + it("should wrap displayName of the component", async () => { expect(BooksConnectedComponent.displayName).toEqual("WithRefreshBooksComponent"); }); diff --git a/test/hocsDeprecated.spec.js b/test/hocsDeprecated.spec.js index a74fe54..dbfcf18 100644 --- a/test/hocsDeprecated.spec.js +++ b/test/hocsDeprecated.spec.js @@ -41,6 +41,8 @@ describe("HOCs deprecated", () => { return ; }; + BooksComponent.fooProperty = "foo"; + BooksConnectedComponent = withDataProvider(provider)(BooksComponent); Component = () => ( @@ -50,6 +52,10 @@ describe("HOCs deprecated", () => { ); }); + it("should have available statics of the component", async () => { + expect(BooksConnectedComponent.fooProperty).toEqual("foo"); + }); + it("should have logged a deprecation warning", async () => { render(); expect( @@ -228,6 +234,8 @@ describe("HOCs deprecated", () => { return ; }; + BooksComponent.fooProperty = "foo"; + BooksConnectedComponent = withDataProviderBranch(provider)( BooksComponent, CustomLoadingComponent, @@ -241,6 +249,10 @@ describe("HOCs deprecated", () => { ); }); + it("should have available statics of the component", async () => { + expect(BooksConnectedComponent.fooProperty).toEqual("foo"); + }); + it("should have logged a deprecation warning", async () => { render(); expect( diff --git a/test/hocsMultipleProps.spec.js b/test/hocsMultipleProps.spec.js index e7da325..4ef3dd0 100644 --- a/test/hocsMultipleProps.spec.js +++ b/test/hocsMultipleProps.spec.js @@ -47,6 +47,8 @@ describe("HOCs providing multiple props", () => { return ; }; + BooksComponent.fooProperty = "foo"; + BooksConnectedComponent = withDataLoadedError(provider)(BooksComponent); Component = () => ( @@ -56,6 +58,10 @@ describe("HOCs providing multiple props", () => { ); }); + it("should have available statics of the component", async () => { + expect(BooksConnectedComponent.fooProperty).toEqual("foo"); + }); + it("should wrap displayName of the component", async () => { expect(BooksConnectedComponent.displayName).toEqual("WithDataLoadedErrorBooksComponent"); }); @@ -214,6 +220,8 @@ describe("HOCs providing multiple props", () => { return ; }; + BooksComponent.fooProperty = "foo"; + BooksConnectedComponent = withDataLoadingError(provider)(BooksComponent); Component = () => ( @@ -223,6 +231,10 @@ describe("HOCs providing multiple props", () => { ); }); + it("should have available statics of the component", async () => { + expect(BooksConnectedComponent.fooProperty).toEqual("foo"); + }); + it("should wrap displayName of the component", async () => { expect(BooksConnectedComponent.displayName).toEqual("WithDataLoadingErrorBooksComponent"); }); @@ -392,6 +404,8 @@ describe("HOCs providing multiple props", () => { return ; }; + BooksComponent.fooProperty = "foo"; + BooksConnectedComponent = withDataLoadingErrorComponents(provider)( BooksComponent, CustomLoadingComponent, @@ -405,6 +419,10 @@ describe("HOCs providing multiple props", () => { ); }); + it("should have available statics of the component", async () => { + expect(BooksConnectedComponent.fooProperty).toEqual("foo"); + }); + it("should wrap displayName of the component", async () => { expect(BooksConnectedComponent.displayName).toEqual( "WithDataLoadingErrorComponentsBooksComponent" @@ -630,6 +648,8 @@ describe("HOCs providing multiple props", () => { return ; }; + BooksComponent.fooProperty = "foo"; + BooksConnectedComponent = withDataLoadedErrorComponents(provider)( BooksComponent, CustomLoadingComponent, @@ -643,6 +663,10 @@ describe("HOCs providing multiple props", () => { ); }); + it("should have available statics of the component", async () => { + expect(BooksConnectedComponent.fooProperty).toEqual("foo"); + }); + it("should wrap displayName of the component", async () => { expect(BooksConnectedComponent.displayName).toEqual( "WithDataLoadedErrorComponentsBooksComponent" From a56e3cf0b62c746a31872912c19b93da43401b0e Mon Sep 17 00:00:00 2001 From: "javier.brea" Date: Mon, 9 Nov 2020 11:51:31 +0100 Subject: [PATCH 39/41] chore(release): Upgrade package version --- package-lock.json | 2 +- package.json | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/package-lock.json b/package-lock.json index 356d022..e20736c 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,6 +1,6 @@ { "name": "@data-provider/react", - "version": "1.3.0", + "version": "1.4.0", "lockfileVersion": 1, "requires": true, "dependencies": { diff --git a/package.json b/package.json index 54941ce..410a295 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "@data-provider/react", - "version": "1.3.0", + "version": "1.4.0", "description": "React bindings for @data-provider", "keywords": [ "data-provider", From 6cba30c899cd7e45a3d4db8ee47821b0bbf3fce4 Mon Sep 17 00:00:00 2001 From: "javier.brea" Date: Mon, 9 Nov 2020 11:54:14 +0100 Subject: [PATCH 40/41] docs(changelog): Add new release to changelog --- CHANGELOG.md | 11 ++++++++--- 1 file changed, 8 insertions(+), 3 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index edce98b..167614e 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -6,8 +6,16 @@ and this project adheres to [Semantic Versioning](http://semver.org/). ## [unreleased] ### Added +### Changed +### Fixed +### Removed + +## [1.4.0] - 2020-11-09 +### Added - feat(hocs): Add withDataLoadedError, withDataLoadingError, withDataLoadingErrorComponents, withDataLoadedErrorComponents - feat(hooks): Add useDataLoadedError, useDataLoadingError +- chore(ci-cd): Check package version on PRs to master +- chore(release): Publish releases to github packages repository ### Changed - feat(hocs): Deprecate withDataProvider in favour of withDataLoadingError @@ -15,11 +23,8 @@ and this project adheres to [Semantic Versioning](http://semver.org/). - feat(hooks): Deprecate useDataProvider in favour of useDataLoadingError ### Fixed - - fix(#101): Add hoist-non-react-statics to HOCs -### Removed - ## [1.3.0] - 2020-10-31 ### Added From c7953cb9c4e7fc20eca474464da21c443d50626e Mon Sep 17 00:00:00 2001 From: "javier.brea" Date: Mon, 9 Nov 2020 11:55:23 +0100 Subject: [PATCH 41/41] chore(sonar): Upgrade sonar version --- sonar-project.properties | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/sonar-project.properties b/sonar-project.properties index 753656c..2302329 100644 --- a/sonar-project.properties +++ b/sonar-project.properties @@ -1,6 +1,6 @@ sonar.organization=data-provider sonar.projectKey=data-provider-react -sonar.projectVersion=1.3.0 +sonar.projectVersion=1.4.0 sonar.sources=src,test sonar.exclusions=node_modules/**