This commit is contained in:
Stefan Pejcic
2024-11-07 19:03:37 +01:00
parent c6df945ed5
commit 09f9f9502d
2472 changed files with 620417 additions and 0 deletions

View File

@@ -0,0 +1,10 @@
node_modules
.DS_Store
jest.config.js
**/*.spec.ts
**/*.spec.tsx
**/*.test.ts
**/*.test.tsx
tsup.config.ts
tsconfig.test.json
tsconfig.declarations.json

View File

@@ -0,0 +1,630 @@
# @refinedev/ui-tests
## 1.14.9
### Patch Changes
- [#6021](https://github.com/refinedev/refine/pull/6021) [`55cd0662b1e3ff8f8410eba812e80130afe75d14`](https://github.com/refinedev/refine/commit/55cd0662b1e3ff8f8410eba812e80130afe75d14) Thanks [@JayBhensdadia](https://github.com/JayBhensdadia)! - fix: update tests to handle lowercase and camelcased resource names correctly
This update ensures that resource names are correctly handled in both lowercase and camelcased formats, improving test coverage and accuracy.
Fixes #6004
- Updated dependencies [[`853bef97ed7baf59e74c98fc54c0ed11624fb491`](https://github.com/refinedev/refine/commit/853bef97ed7baf59e74c98fc54c0ed11624fb491), [`b86648f42cd849a506e4c32d740de26b72681f72`](https://github.com/refinedev/refine/commit/b86648f42cd849a506e4c32d740de26b72681f72), [`4265ae2509f79af9dbca8d52daf5c2f1b4a50a51`](https://github.com/refinedev/refine/commit/4265ae2509f79af9dbca8d52daf5c2f1b4a50a51), [`b516c18b828ba8823561d0fefc4afe02b45ce332`](https://github.com/refinedev/refine/commit/b516c18b828ba8823561d0fefc4afe02b45ce332)]:
- @refinedev/core@4.53.0
## 1.14.7
### Patch Changes
- [`6bd14228760d3e1e205ea9248e427f9afa2ec046`](https://github.com/refinedev/refine/commit/6bd14228760d3e1e205ea9248e427f9afa2ec046) Thanks [@BatuhanW](https://github.com/BatuhanW)! - chore(ui-tests): add test case for globally passed app title and app icon to title tests
- [`6bd14228760d3e1e205ea9248e427f9afa2ec046`](https://github.com/refinedev/refine/commit/6bd14228760d3e1e205ea9248e427f9afa2ec046) Thanks [@BatuhanW](https://github.com/BatuhanW)! - chore: added `type` qualifier to imports used as type only.
```diff
- import { A } from "./example.ts";
+ import type { A } from "./example.ts";
```
- Updated dependencies [[`6bd14228760d3e1e205ea9248e427f9afa2ec046`](https://github.com/refinedev/refine/commit/6bd14228760d3e1e205ea9248e427f9afa2ec046), [`6bd14228760d3e1e205ea9248e427f9afa2ec046`](https://github.com/refinedev/refine/commit/6bd14228760d3e1e205ea9248e427f9afa2ec046), [`6bd14228760d3e1e205ea9248e427f9afa2ec046`](https://github.com/refinedev/refine/commit/6bd14228760d3e1e205ea9248e427f9afa2ec046), [`6bd14228760d3e1e205ea9248e427f9afa2ec046`](https://github.com/refinedev/refine/commit/6bd14228760d3e1e205ea9248e427f9afa2ec046), [`6bd14228760d3e1e205ea9248e427f9afa2ec046`](https://github.com/refinedev/refine/commit/6bd14228760d3e1e205ea9248e427f9afa2ec046), [`6bd14228760d3e1e205ea9248e427f9afa2ec046`](https://github.com/refinedev/refine/commit/6bd14228760d3e1e205ea9248e427f9afa2ec046), [`6bd14228760d3e1e205ea9248e427f9afa2ec046`](https://github.com/refinedev/refine/commit/6bd14228760d3e1e205ea9248e427f9afa2ec046)]:
- @refinedev/core@4.51.0
- @refinedev/ui-types@1.22.9
## 1.14.6
### Patch Changes
- [#5945](https://github.com/refinedev/refine/pull/5945) [`903ea231538b00ce02ddc9394c72848ec1e90772`](https://github.com/refinedev/refine/commit/903ea231538b00ce02ddc9394c72848ec1e90772) Thanks [@aliemir](https://github.com/aliemir)! - chore(ui-tests): add test case for globally passed app title and app icon to title tests
- [#5945](https://github.com/refinedev/refine/pull/5945) [`90930b381d8d369c63bc59beedf69c391875166d`](https://github.com/refinedev/refine/commit/90930b381d8d369c63bc59beedf69c391875166d) Thanks [@aliemir](https://github.com/aliemir)! - chore: added `type` qualifier to imports used as type only.
```diff
- import { A } from "./example.ts";
+ import type { A } from "./example.ts";
```
- Updated dependencies [[`a39f1952554120893ea83db904037917fc293dc6`](https://github.com/refinedev/refine/commit/a39f1952554120893ea83db904037917fc293dc6), [`208f77177f9821ee1860ffe031e6b2a9645d1bb6`](https://github.com/refinedev/refine/commit/208f77177f9821ee1860ffe031e6b2a9645d1bb6), [`903ea231538b00ce02ddc9394c72848ec1e90772`](https://github.com/refinedev/refine/commit/903ea231538b00ce02ddc9394c72848ec1e90772), [`84cac61b84ab872394424ebf358eeb380f40121d`](https://github.com/refinedev/refine/commit/84cac61b84ab872394424ebf358eeb380f40121d), [`903ea231538b00ce02ddc9394c72848ec1e90772`](https://github.com/refinedev/refine/commit/903ea231538b00ce02ddc9394c72848ec1e90772), [`4cc74478cbec8caa3023a50ce62f1d5b2f7158a5`](https://github.com/refinedev/refine/commit/4cc74478cbec8caa3023a50ce62f1d5b2f7158a5), [`90930b381d8d369c63bc59beedf69c391875166d`](https://github.com/refinedev/refine/commit/90930b381d8d369c63bc59beedf69c391875166d)]:
- @refinedev/core@4.50.0
- @refinedev/ui-types@1.22.8
## 1.14.5
### Patch Changes
- [#5928](https://github.com/refinedev/refine/pull/5928) [`db9756e7908`](https://github.com/refinedev/refine/commit/db9756e79086ff80774ee75d570d610bf0d5d76d) Thanks [@aliemir](https://github.com/aliemir)! - fix: type errors on typescript <5
Due to the changes in #5881, typescript users below version 5 are facing type errors. This PR fixes the type errors by updating the file extensions required by the `d.mts` declaration files to provide a compatible declarations for both typescript 4 and 5 users.
- Updated dependencies [[`db9756e7908`](https://github.com/refinedev/refine/commit/db9756e79086ff80774ee75d570d610bf0d5d76d)]:
- @refinedev/core@4.49.2
- @refinedev/ui-types@1.22.7
## 1.14.4
### Patch Changes
- [#5881](https://github.com/refinedev/refine/pull/5881) [`ba719f6ea26`](https://github.com/refinedev/refine/commit/ba719f6ea264ee87226f42de900a754e81f1f22f) Thanks [@aliemir](https://github.com/aliemir)! - fix: declaration files in node10, node16 and nodenext module resolutions
- Updated dependencies [[`1c9a95f22ab`](https://github.com/refinedev/refine/commit/1c9a95f22ab8c3f1d1e48c7c889227ce1d9160cf), [`0a76576da0f`](https://github.com/refinedev/refine/commit/0a76576da0f18c6db372e737c610ad462b56ff21), [`8d2dd4376f6`](https://github.com/refinedev/refine/commit/8d2dd4376f672786d4722d3dee09e6344f1002e4), [`1c9a95f22ab`](https://github.com/refinedev/refine/commit/1c9a95f22ab8c3f1d1e48c7c889227ce1d9160cf), [`ba719f6ea26`](https://github.com/refinedev/refine/commit/ba719f6ea264ee87226f42de900a754e81f1f22f), [`9a0c1c8414a`](https://github.com/refinedev/refine/commit/9a0c1c8414a7b228378c234468396e6288cdb6f0)]:
- @refinedev/core@4.49.1
- @refinedev/ui-types@1.22.6
## 1.14.3
### Patch Changes
- [#5765](https://github.com/refinedev/refine/pull/5765) [`0c197d82393`](https://github.com/refinedev/refine/commit/0c197d823939ae1fd4e0ee4b5a422322853b1e45) Thanks [@aliemir](https://github.com/aliemir)! - refactor: package bundles and package.json configuration for exports
Previously, Refine packages had exported ESM and CJS bundles with same `.js` extension and same types for both with `.d.ts` extensions. This was causing issues with bundlers and compilers to pick up the wrong files for the wrong environment. Now we're outputting ESM bundles with `.mjs` extension and CJS bundles with `.cjs` extension. Also types are now exported with both `.d.mts` and `.d.cts` extensions.
In older versions ESM and CJS outputs of some packages were using wrong imports/requires to dependencies causing errors in some environments. This will be fixed since now we're also enforcing the module type with extensions.
Above mentioned changes also supported with changes in `package.json` files of the packages to support the new extensions and types. All Refine packages now include `exports` fields in their configuration to make sure the correct bundle is picked up by the bundlers and compilers.
- [#5754](https://github.com/refinedev/refine/pull/5754) [`56ed144a0f5`](https://github.com/refinedev/refine/commit/56ed144a0f5af218fd9e6edbfd999ae433329927) Thanks [@alicanerdurmaz](https://github.com/alicanerdurmaz)! - chore: TypeScript upgraded to [v5.x.x](https://www.typescriptlang.org/docs/handbook/release-notes/typescript-5-0.html). #5752
- [#5808](https://github.com/refinedev/refine/pull/5808) [`10ba9c34490`](https://github.com/refinedev/refine/commit/10ba9c344900d0fa4af7120c24b3b007081a4c39) Thanks [@aliemir](https://github.com/aliemir)! - chore: updated refresh button tests to be more UI focused and hand off the logic to the `@refinedev/core`'s `useRefreshButton` hook
- [#5755](https://github.com/refinedev/refine/pull/5755) [`404b2ef5e1b`](https://github.com/refinedev/refine/commit/404b2ef5e1b8fed469eeab753bac8736ed3fe58e) Thanks [@BatuhanW](https://github.com/BatuhanW)! - fix: incorrect type imports
- Updated dependencies [[`4e8188a6652`](https://github.com/refinedev/refine/commit/4e8188a665209b0d0b77aef27c795a29b9513226), [`10ba9c34490`](https://github.com/refinedev/refine/commit/10ba9c344900d0fa4af7120c24b3b007081a4c39), [`2b5ac6f5409`](https://github.com/refinedev/refine/commit/2b5ac6f5409b7b175c453793224a531e644f6513), [`0c197d82393`](https://github.com/refinedev/refine/commit/0c197d823939ae1fd4e0ee4b5a422322853b1e45), [`0c197d82393`](https://github.com/refinedev/refine/commit/0c197d823939ae1fd4e0ee4b5a422322853b1e45), [`404b2ef5e1b`](https://github.com/refinedev/refine/commit/404b2ef5e1b8fed469eeab753bac8736ed3fe58e), [`56ed144a0f5`](https://github.com/refinedev/refine/commit/56ed144a0f5af218fd9e6edbfd999ae433329927), [`0c197d82393`](https://github.com/refinedev/refine/commit/0c197d823939ae1fd4e0ee4b5a422322853b1e45), [`10ba9c34490`](https://github.com/refinedev/refine/commit/10ba9c344900d0fa4af7120c24b3b007081a4c39), [`38f129f40ee`](https://github.com/refinedev/refine/commit/38f129f40eea109c9b89b23a8fd3f217964330c7), [`f32512b9042`](https://github.com/refinedev/refine/commit/f32512b90427cbb97b28e9d5445dcd343067aa7e)]:
- @refinedev/core@4.49.0
- @refinedev/ui-types@1.22.5
## 1.14.2
### Patch Changes
- [#5695](https://github.com/refinedev/refine/pull/5695) [`79865affa1c`](https://github.com/refinedev/refine/commit/79865affa1c657e6b14ed34585caeec1f3d3da7f) Thanks [@BatuhanW](https://github.com/BatuhanW)! - chore: apply biome format and fix lint errors.
- Updated dependencies [[`fd38d9c71a6`](https://github.com/refinedev/refine/commit/fd38d9c71a6e03d87c5ac97f0dcd52c076bc9599), [`17c39ee2ee0`](https://github.com/refinedev/refine/commit/17c39ee2ee0146e532085761e1e9fcdc60ecb81e), [`79865affa1c`](https://github.com/refinedev/refine/commit/79865affa1c657e6b14ed34585caeec1f3d3da7f)]:
- @refinedev/core@4.48.0
## 1.14.1
### Patch Changes
- [#5425](https://github.com/refinedev/refine/pull/5425) [`190af9fce2`](https://github.com/refinedev/refine/commit/190af9fce292bc46b169e3e121be6bf1c2a939a5) Thanks [@aliemir](https://github.com/aliemir)! - Updated `@refinedev/core` peer dependencies to latest (`^4.46.1`)
- Updated dependencies [[`190af9fce2`](https://github.com/refinedev/refine/commit/190af9fce292bc46b169e3e121be6bf1c2a939a5)]:
- @refinedev/ui-types@1.22.4
## 1.14.0
### Minor Changes
- [#5307](https://github.com/refinedev/refine/pull/5307) [`f8e407f850`](https://github.com/refinedev/refine/commit/f8e407f85054bccf1e6ff45c84928bc01db7f5eb) Thanks [@jackprogramsjp](https://github.com/jackprogramsjp)! - feat: added `hideForm` props for `LoginPage` and `RegisterPage` for `AuthPage` feature.
Now with the `hideForm` props feature, you can be able to hide the forms (like email/password)
to only show the OAuth providers. This avoids having to make your own entire AuthPage.
### Patch Changes
- [#5325](https://github.com/refinedev/refine/pull/5325) [`7ff54b2060`](https://github.com/refinedev/refine/commit/7ff54b2060b0ce942c4170f744cbdf52d0940434) Thanks [@alicanerdurmaz](https://github.com/alicanerdurmaz)! - fix: `<AuthPage />` styling issues on mobile screens.
chore: new tests are added to `<AuthPage />`.
- Updated dependencies [[`17aa8c1cd6`](https://github.com/refinedev/refine/commit/17aa8c1cd6858c5a2b0c996c97230047e049bf3b), [`dd8f1270f6`](https://github.com/refinedev/refine/commit/dd8f1270f692d1eec279973e53fcc5a7e650b983), [`4c49ef0a06`](https://github.com/refinedev/refine/commit/4c49ef0a0660c2941c983025a187d45b521aa27c), [`3bdb9cb1cb`](https://github.com/refinedev/refine/commit/3bdb9cb1cb4cdcfaf363e7e9938737ed6f8e634e), [`f8e407f850`](https://github.com/refinedev/refine/commit/f8e407f85054bccf1e6ff45c84928bc01db7f5eb)]:
- @refinedev/core@4.46.0
## 1.13.3
### Patch Changes
- [#5208](https://github.com/refinedev/refine/pull/5208) [`72f9f608f42`](https://github.com/refinedev/refine/commit/72f9f608f4205cf4f3266068326d029546cd9f88) Thanks [@BatuhanW](https://github.com/BatuhanW)! - chore: update commit frequency branch from next to master on README.
- Updated dependencies [[`72f9f608f42`](https://github.com/refinedev/refine/commit/72f9f608f4205cf4f3266068326d029546cd9f88), [`72f9f608f42`](https://github.com/refinedev/refine/commit/72f9f608f4205cf4f3266068326d029546cd9f88)]:
- @refinedev/ui-types@1.22.3
- @refinedev/core@4.44.12
## 1.13.2
### Patch Changes
- [#4951](https://github.com/refinedev/refine/pull/4951) [`04837c62077`](https://github.com/refinedev/refine/commit/04837c6207758a7460cfb7a5aff2a104967e20ea) Thanks [@aliemir](https://github.com/aliemir)! - Remove redundant lodash plugin for esbuild and use the shared plugins instead
- Updated dependencies [[`04837c62077`](https://github.com/refinedev/refine/commit/04837c6207758a7460cfb7a5aff2a104967e20ea), [`04837c62077`](https://github.com/refinedev/refine/commit/04837c6207758a7460cfb7a5aff2a104967e20ea)]:
- @refinedev/ui-types@1.22.2
- @refinedev/core@4.38.4
## 1.13.1
### Patch Changes
- [#4951](https://github.com/refinedev/refine/pull/4951) [`04837c62077`](https://github.com/refinedev/refine/commit/04837c6207758a7460cfb7a5aff2a104967e20ea) Thanks [@aliemir](https://github.com/aliemir)! - Remove redundant lodash plugin for esbuild and use the shared plugins instead
- Updated dependencies [[`04837c62077`](https://github.com/refinedev/refine/commit/04837c6207758a7460cfb7a5aff2a104967e20ea), [`04837c62077`](https://github.com/refinedev/refine/commit/04837c6207758a7460cfb7a5aff2a104967e20ea)]:
- @refinedev/ui-types@1.22.1
- @refinedev/core@4.38.3
## 1.13.0
### Minor Changes
- [#4775](https://github.com/refinedev/refine/pull/4775) [`3052fb22449`](https://github.com/refinedev/refine/commit/3052fb22449c5e35c607e95c060c38ca48e00c82) Thanks [@alicanerdurmaz](https://github.com/alicanerdurmaz)! - feat: Added a new test, "should invalidates when button is clicked" to `buttonRefreshTests`.
### Patch Changes
- Updated dependencies [[`3052fb22449`](https://github.com/refinedev/refine/commit/3052fb22449c5e35c607e95c060c38ca48e00c82), [`3052fb22449`](https://github.com/refinedev/refine/commit/3052fb22449c5e35c607e95c060c38ca48e00c82)]:
- @refinedev/core@4.34.0
- @refinedev/ui-types@1.22.0
## 1.12.0
### Minor Changes
- [#4775](https://github.com/refinedev/refine/pull/4775) [`3052fb22449`](https://github.com/refinedev/refine/commit/3052fb22449c5e35c607e95c060c38ca48e00c82) Thanks [@alicanerdurmaz](https://github.com/alicanerdurmaz)! - feat: Added a new test, "should invalidates when button is clicked" to `buttonRefreshTests`.
### Patch Changes
- Updated dependencies [[`3052fb22449`](https://github.com/refinedev/refine/commit/3052fb22449c5e35c607e95c060c38ca48e00c82), [`3052fb22449`](https://github.com/refinedev/refine/commit/3052fb22449c5e35c607e95c060c38ca48e00c82)]:
- @refinedev/core@4.33.0
- @refinedev/ui-types@1.21.0
## 1.11.0
### Minor Changes
- [#4591](https://github.com/refinedev/refine/pull/4591) [`f8891ead2bd`](https://github.com/refinedev/refine/commit/f8891ead2bdb5f6743bbe9979230aa73ef3e69be) Thanks [@yildirayunlu](https://github.com/yildirayunlu)! - feat: add `autoSaveIndicatorTests` for `AutoSaveIndicator` component.
### Patch Changes
- Updated dependencies [[`3af99896101`](https://github.com/refinedev/refine/commit/3af99896101bd41a4d2878c4a9f671ca1da36a6f), [`96af6d25b7a`](https://github.com/refinedev/refine/commit/96af6d25b7a870a3c1c6fd33c30e0ca2224ed411), [`96af6d25b7a`](https://github.com/refinedev/refine/commit/96af6d25b7a870a3c1c6fd33c30e0ca2224ed411), [`f8891ead2bd`](https://github.com/refinedev/refine/commit/f8891ead2bdb5f6743bbe9979230aa73ef3e69be), [`3442f4bd00a`](https://github.com/refinedev/refine/commit/3442f4bd00ad4dbb17dcba08931fdeed3c2b1cb0), [`f8891ead2bd`](https://github.com/refinedev/refine/commit/f8891ead2bdb5f6743bbe9979230aa73ef3e69be)]:
- @refinedev/core@4.28.0
- @refinedev/ui-types@1.20.0
## 1.10.0
### Minor Changes
- [#4591](https://github.com/refinedev/refine/pull/4591) [`f8891ead2bd`](https://github.com/refinedev/refine/commit/f8891ead2bdb5f6743bbe9979230aa73ef3e69be) Thanks [@yildirayunlu](https://github.com/yildirayunlu)! - feat: add `autoSaveIndicatorTests` for `AutoSaveIndicator` component.
### Patch Changes
- Updated dependencies [[`3af99896101`](https://github.com/refinedev/refine/commit/3af99896101bd41a4d2878c4a9f671ca1da36a6f), [`96af6d25b7a`](https://github.com/refinedev/refine/commit/96af6d25b7a870a3c1c6fd33c30e0ca2224ed411), [`96af6d25b7a`](https://github.com/refinedev/refine/commit/96af6d25b7a870a3c1c6fd33c30e0ca2224ed411), [`f8891ead2bd`](https://github.com/refinedev/refine/commit/f8891ead2bdb5f6743bbe9979230aa73ef3e69be), [`3442f4bd00a`](https://github.com/refinedev/refine/commit/3442f4bd00ad4dbb17dcba08931fdeed3c2b1cb0), [`f8891ead2bd`](https://github.com/refinedev/refine/commit/f8891ead2bdb5f6743bbe9979230aa73ef3e69be)]:
- @refinedev/core@4.27.0
- @refinedev/ui-types@1.19.0
## 1.9.0
### Minor Changes
- [#4502](https://github.com/refinedev/refine/pull/4502) [`c7872ca621f`](https://github.com/refinedev/refine/commit/c7872ca621fdc6c0edd7ee113520bd898901ed38) Thanks [@Mr0nline](https://github.com/Mr0nline)! - feat: added tests for `<ThemedSiderV2/>`'s `activeItemDisabled` prop.
When `activeItemDisabled` prop is `trye`, the active item will be disabled and not clickable.
### Patch Changes
- Updated dependencies [[`c3c0deed564`](https://github.com/refinedev/refine/commit/c3c0deed564bdbded58c615357a55e666473923a), [`8c2b3be35b0`](https://github.com/refinedev/refine/commit/8c2b3be35b0132fc9a7b79287d281a9f922424d0), [`5bb6f47a4d4`](https://github.com/refinedev/refine/commit/5bb6f47a4d4e29a7de5426879754fcd78e3fa4d5), [`c7872ca621f`](https://github.com/refinedev/refine/commit/c7872ca621fdc6c0edd7ee113520bd898901ed38)]:
- @refinedev/core@4.26.0
- @refinedev/ui-types@1.18.0
## 1.8.0
### Minor Changes
- [#4502](https://github.com/refinedev/refine/pull/4502) [`c7872ca621f`](https://github.com/refinedev/refine/commit/c7872ca621fdc6c0edd7ee113520bd898901ed38) Thanks [@Mr0nline](https://github.com/Mr0nline)! - feat: added tests for `<ThemedSiderV2/>`'s `activeItemDisabled` prop.
When `activeItemDisabled` prop is `trye`, the active item will be disabled and not clickable.
### Patch Changes
- Updated dependencies [[`5bb6f47a4d4`](https://github.com/refinedev/refine/commit/5bb6f47a4d4e29a7de5426879754fcd78e3fa4d5), [`c7872ca621f`](https://github.com/refinedev/refine/commit/c7872ca621fdc6c0edd7ee113520bd898901ed38)]:
- @refinedev/core@4.25.1
- @refinedev/ui-types@1.17.0
## 1.7.2
### Patch Changes
- [#4550](https://github.com/refinedev/refine/pull/4550) [`74468063d31`](https://github.com/refinedev/refine/commit/74468063d31195f2f9b6808189d2b761b41cbb29) Thanks [@aliemir](https://github.com/aliemir)! - Updated layout header tests to fallback to `img` in case of `presentation` role is not present.c
- Updated dependencies [[`18d446b1069`](https://github.com/refinedev/refine/commit/18d446b1069c75b5033d0ce8defcb8c32fcce5cf), [`ceadcd29fc9`](https://github.com/refinedev/refine/commit/ceadcd29fc9e42c875a4b0a78622e9fc14b4ce42), [`18d446b1069`](https://github.com/refinedev/refine/commit/18d446b1069c75b5033d0ce8defcb8c32fcce5cf)]:
- @refinedev/core@4.24.0
## 1.7.1
### Patch Changes
- [#4550](https://github.com/refinedev/refine/pull/4550) [`74468063d31`](https://github.com/refinedev/refine/commit/74468063d31195f2f9b6808189d2b761b41cbb29) Thanks [@aliemir](https://github.com/aliemir)! - Updated layout header tests to fallback to `img` in case of `presentation` role is not present.c
- Updated dependencies [[`18d446b1069`](https://github.com/refinedev/refine/commit/18d446b1069c75b5033d0ce8defcb8c32fcce5cf), [`ceadcd29fc9`](https://github.com/refinedev/refine/commit/ceadcd29fc9e42c875a4b0a78622e9fc14b4ce42), [`18d446b1069`](https://github.com/refinedev/refine/commit/18d446b1069c75b5033d0ce8defcb8c32fcce5cf)]:
- @refinedev/core@4.23.0
## 1.7.0
### Minor Changes
- [#4449](https://github.com/refinedev/refine/pull/4449) [`cc84d61bc5c`](https://github.com/refinedev/refine/commit/cc84d61bc5c8cfc8ac7da391f965471ecad6c445) Thanks [@BatuhanW](https://github.com/BatuhanW)! - feat: updated button specs to test new access control provider behaviour.
### Patch Changes
- Updated dependencies [[`a3c8d4f84c7`](https://github.com/refinedev/refine/commit/a3c8d4f84c7b20b6d30f43310f5260b2f57b801a), [`cc84d61bc5c`](https://github.com/refinedev/refine/commit/cc84d61bc5c8cfc8ac7da391f965471ecad6c445)]:
- @refinedev/core@4.22.0
## 1.6.0
### Minor Changes
- [#4449](https://github.com/refinedev/refine/pull/4449) [`cc84d61bc5c`](https://github.com/refinedev/refine/commit/cc84d61bc5c8cfc8ac7da391f965471ecad6c445) Thanks [@BatuhanW](https://github.com/BatuhanW)! - feat: updated button specs to test new access control provider behaviour.
### Patch Changes
- Updated dependencies [[`a3c8d4f84c7`](https://github.com/refinedev/refine/commit/a3c8d4f84c7b20b6d30f43310f5260b2f57b801a), [`cc84d61bc5c`](https://github.com/refinedev/refine/commit/cc84d61bc5c8cfc8ac7da391f965471ecad6c445)]:
- @refinedev/core@4.21.0
## 1.5.0
### Minor Changes
- [#4303](https://github.com/refinedev/refine/pull/4303) [`0c569f42b4e`](https://github.com/refinedev/refine/commit/0c569f42b4e7caec75928fd8a1ebeb337c95ff81) Thanks [@alicanerdurmaz](https://github.com/alicanerdurmaz)! - added: button props tests for `headerButtons` and `footerButtons` renderer functions
### Patch Changes
- Updated dependencies [[`0c569f42b4e`](https://github.com/refinedev/refine/commit/0c569f42b4e7caec75928fd8a1ebeb337c95ff81), [`9a5f79186c1`](https://github.com/refinedev/refine/commit/9a5f79186c107d52e12b8ff87558a3c3dd7807b8)]:
- @refinedev/ui-types@1.16.0
## 1.4.0
### Minor Changes
- [#4303](https://github.com/refinedev/refine/pull/4303) [`0c569f42b4e`](https://github.com/refinedev/refine/commit/0c569f42b4e7caec75928fd8a1ebeb337c95ff81) Thanks [@alicanerdurmaz](https://github.com/alicanerdurmaz)! - added: button props tests for `headerButtons` and `footerButtons` renderer functions
### Patch Changes
- Updated dependencies [[`0c569f42b4e`](https://github.com/refinedev/refine/commit/0c569f42b4e7caec75928fd8a1ebeb337c95ff81), [`9a5f79186c1`](https://github.com/refinedev/refine/commit/9a5f79186c107d52e12b8ff87558a3c3dd7807b8)]:
- @refinedev/ui-types@1.15.0
## 1.3.2
### Patch Changes
- [#3996](https://github.com/refinedev/refine/pull/3996) [`327be2be623`](https://github.com/refinedev/refine/commit/327be2be623ab9a62a32974315c3d2453baf4a07) Thanks [@alicanerdurmaz](https://github.com/alicanerdurmaz)! - - Fixed typo in `src/tests/layout/header.tsx`'s test description
- Updated dependencies [[`4dcc20d6a60`](https://github.com/refinedev/refine/commit/4dcc20d6a6097bb81a094e4bcb630504b2a055d2)]:
- @refinedev/core@4.5.6
## 1.3.1
### Patch Changes
- [#3996](https://github.com/refinedev/refine/pull/3996) [`327be2be623`](https://github.com/refinedev/refine/commit/327be2be623ab9a62a32974315c3d2453baf4a07) Thanks [@alicanerdurmaz](https://github.com/alicanerdurmaz)! - - Fixed typo in `src/tests/layout/header.tsx`'s test description
- Updated dependencies [[`4dcc20d6a60`](https://github.com/refinedev/refine/commit/4dcc20d6a6097bb81a094e4bcb630504b2a055d2)]:
- @refinedev/core@4.5.5
## 1.3.0
### Minor Changes
- [#3912](https://github.com/refinedev/refine/pull/3912) [`0ffe70308b2`](https://github.com/refinedev/refine/commit/0ffe70308b24d2d70695399fb0a1b7b76bcf2ccb) Thanks [@alicanerdurmaz](https://github.com/alicanerdurmaz)! - Add tests for the `login`, `register`, `forgotPassword`, and, `updatePassword` pages.
### Patch Changes
- Updated dependencies [[`0ffe70308b2`](https://github.com/refinedev/refine/commit/0ffe70308b24d2d70695399fb0a1b7b76bcf2ccb), [`0ffe70308b2`](https://github.com/refinedev/refine/commit/0ffe70308b24d2d70695399fb0a1b7b76bcf2ccb)]:
- @refinedev/core@4.5.0
- @refinedev/ui-types@1.3.0
## 1.2.0
### Minor Changes
- [#3912](https://github.com/refinedev/refine/pull/3912) [`0ffe70308b2`](https://github.com/refinedev/refine/commit/0ffe70308b24d2d70695399fb0a1b7b76bcf2ccb) Thanks [@alicanerdurmaz](https://github.com/alicanerdurmaz)! - Add tests for the `login`, `register`, `forgotPassword`, and, `updatePassword` pages.
### Patch Changes
- Updated dependencies [[`0ffe70308b2`](https://github.com/refinedev/refine/commit/0ffe70308b24d2d70695399fb0a1b7b76bcf2ccb), [`0ffe70308b2`](https://github.com/refinedev/refine/commit/0ffe70308b24d2d70695399fb0a1b7b76bcf2ccb)]:
- @refinedev/core@4.4.0
- @refinedev/ui-types@1.2.0
## 1.1.2
### Patch Changes
- [#3919](https://github.com/refinedev/refine/pull/3919) [`dd90bf43d50`](https://github.com/refinedev/refine/commit/dd90bf43d50ffe4e78a5caa5c0831d2ba8610e0d) Thanks [@alicanerdurmaz](https://github.com/alicanerdurmaz)! - - New test added to crud components: - "should not render `title` when is false"
## 1.1.1
### Patch Changes
- [#3919](https://github.com/refinedev/refine/pull/3919) [`dd90bf43d50`](https://github.com/refinedev/refine/commit/dd90bf43d50ffe4e78a5caa5c0831d2ba8610e0d) Thanks [@alicanerdurmaz](https://github.com/alicanerdurmaz)! - - New test added to crud components: - "should not render `title` when is false"
## 1.1.0
### Minor Changes
- Thanks [@aliemir](https://github.com/aliemir), [@alicanerdurmaz](https://github.com/alicanerdurmaz), [@batuhanW](https://github.com/batuhanW), [@salihozdemir](https://github.com/salihozdemir), [@yildirayunlu](https://github.com/yildirayunlu), [@recepkutuk](https://github.com/recepkutuk)!
- Buttons and CRUD tests are updated to use `resource.meta` property instead of `resource.options` property.
- `<TestWrapper>` updated to use `authProvider@v3` and `authProvider@v4`.
- Header tests are updated to use `authProvider@v4`.
- Thanks [@aliemir](https://github.com/aliemir), [@alicanerdurmaz](https://github.com/alicanerdurmaz), [@batuhanW](https://github.com/batuhanW), [@salihozdemir](https://github.com/salihozdemir), [@yildirayunlu](https://github.com/yildirayunlu), [@recepkutuk](https://github.com/recepkutuk)!
`AuthProvider` is renamed to `LegacyAuthProvider` with refine@4. Components and functions are updated to support `LegacyAuthProvider`.
- Thanks [@aliemir](https://github.com/aliemir), [@alicanerdurmaz](https://github.com/alicanerdurmaz), [@batuhanW](https://github.com/batuhanW), [@salihozdemir](https://github.com/salihozdemir), [@yildirayunlu](https://github.com/yildirayunlu), [@recepkutuk](https://github.com/recepkutuk)!
**Moving to the `@refinedev` scope 🎉🎉**
Moved to the `@refinedev` scope and updated our packages to use the new scope. From now on, all packages will be published under the `@refinedev` scope with their new names.
Now, we're also removing the `refine` prefix from all packages. So, the `@pankod/refine-core` package is now `@refinedev/core`, `@pankod/refine-antd` is now `@refinedev/antd`, and so on.
### Patch Changes
## 0.13.0
### Minor Changes
- [#3822](https://github.com/refinedev/refine/pull/3822) [`0baa99ba787`](https://github.com/refinedev/refine/commit/0baa99ba7874394d9d28d0a7b29c082c604258fb) Thanks [@BatuhanW](https://github.com/BatuhanW)! - - refine v4 release announcement added to "postinstall". - refine v4 is released 🎉 The new version is 100% backward compatible. You can upgrade to v4 with a single command! See the migration guide here: https://refine.dev/docs/migration-guide/3x-to-4x
### Patch Changes
- Updated dependencies [[`0baa99ba787`](https://github.com/refinedev/refine/commit/0baa99ba7874394d9d28d0a7b29c082c604258fb)]:
- @pankod/refine-core@3.103.0
- @pankod/refine-ui-types@0.16.0
## 0.12.0
### Minor Changes
- [#3822](https://github.com/refinedev/refine/pull/3822) [`0baa99ba787`](https://github.com/refinedev/refine/commit/0baa99ba7874394d9d28d0a7b29c082c604258fb) Thanks [@BatuhanW](https://github.com/BatuhanW)! - - refine v4 release announcement added to "postinstall". - refine v4 is released 🎉 The new version is 100% backward compatible. You can upgrade to v4 with a single command! See the migration guide here: https://refine.dev/docs/migration-guide/3x-to-4x
### Patch Changes
- Updated dependencies [[`0baa99ba787`](https://github.com/refinedev/refine/commit/0baa99ba7874394d9d28d0a7b29c082c604258fb)]:
- @pankod/refine-core@3.102.0
- @pankod/refine-ui-types@0.15.0
## 0.11.6
### Patch Changes
- [#3220](https://github.com/refinedev/refine/pull/3220) [`b867497f469`](https://github.com/refinedev/refine/commit/b867497f4694a5fbd330106a39256dee3c56199b) Thanks [@aliemir](https://github.com/aliemir)! - Updated image links in `README.MD` with CDN
- Updated dependencies [[`a47f17931a8`](https://github.com/refinedev/refine/commit/a47f17931a8cad1466c25aa7ba4f9dce16dea2de), [`b867497f469`](https://github.com/refinedev/refine/commit/b867497f4694a5fbd330106a39256dee3c56199b), [`b867497f469`](https://github.com/refinedev/refine/commit/b867497f4694a5fbd330106a39256dee3c56199b)]:
- @pankod/refine-core@3.90.6
- @pankod/refine-ui-types@0.14.2
## 0.11.5
### Patch Changes
- [#3220](https://github.com/refinedev/refine/pull/3220) [`b867497f469`](https://github.com/refinedev/refine/commit/b867497f4694a5fbd330106a39256dee3c56199b) Thanks [@aliemir](https://github.com/aliemir)! - Updated image links in `README.MD` with CDN
- Updated dependencies [[`a47f17931a8`](https://github.com/refinedev/refine/commit/a47f17931a8cad1466c25aa7ba4f9dce16dea2de), [`b867497f469`](https://github.com/refinedev/refine/commit/b867497f4694a5fbd330106a39256dee3c56199b), [`b867497f469`](https://github.com/refinedev/refine/commit/b867497f4694a5fbd330106a39256dee3c56199b)]:
- @pankod/refine-core@3.90.5
- @pankod/refine-ui-types@0.14.1
## 0.11.4
### Patch Changes
- [#3045](https://github.com/refinedev/refine/pull/3045) [`753fda3186d`](https://github.com/refinedev/refine/commit/753fda3186d28ca608dd5b1c1ede304af46d44e9) Thanks [@aliemir](https://github.com/aliemir)! - Updated `CreateButton` tests to handle `disabled` check.
## 0.11.3
### Patch Changes
- [#3045](https://github.com/refinedev/refine/pull/3045) [`753fda3186d`](https://github.com/refinedev/refine/commit/753fda3186d28ca608dd5b1c1ede304af46d44e9) Thanks [@aliemir](https://github.com/aliemir)! - Updated `CreateButton` tests to handle `disabled` check.
## 0.11.2
### Patch Changes
- [#2835](https://github.com/refinedev/refine/pull/2835) [`e479bef562`](https://github.com/refinedev/refine/commit/e479bef5621e21595bbae81e77f25062956abf2b) Thanks [@yildirayunlu](https://github.com/yildirayunlu)! - Used find by test id instead of find by text on delete button tests.
## 0.11.1
### Patch Changes
- [#2835](https://github.com/refinedev/refine/pull/2835) [`e479bef562`](https://github.com/refinedev/refine/commit/e479bef5621e21595bbae81e77f25062956abf2b) Thanks [@yildirayunlu](https://github.com/yildirayunlu)! - Used find by test id instead of find by text on delete button tests.
## 0.11.0
### Minor Changes
- [#2836](https://github.com/refinedev/refine/pull/2836) [`e43e9a17ae`](https://github.com/refinedev/refine/commit/e43e9a17ae0ed41e649b8026b2b04d850136dcfd) Thanks [@alicanerdurmaz](https://github.com/alicanerdurmaz)! - added locales prop to date fields
### Patch Changes
- Updated dependencies [[`476285e342`](https://github.com/refinedev/refine/commit/476285e3427c7e065892a281da529c038aee83d2), [`5388a338ab`](https://github.com/refinedev/refine/commit/5388a338abb9a5e03599da0a2786bea394cbc516), [`5388a338ab`](https://github.com/refinedev/refine/commit/5388a338abb9a5e03599da0a2786bea394cbc516), [`e43e9a17ae`](https://github.com/refinedev/refine/commit/e43e9a17ae0ed41e649b8026b2b04d850136dcfd)]:
- @pankod/refine-ui-types@0.14.0
- @pankod/refine-core@3.86.2
## 0.10.0
### Minor Changes
- [#2836](https://github.com/refinedev/refine/pull/2836) [`e43e9a17ae`](https://github.com/refinedev/refine/commit/e43e9a17ae0ed41e649b8026b2b04d850136dcfd) Thanks [@alicanerdurmaz](https://github.com/alicanerdurmaz)! - added locales prop to date fields
### Patch Changes
- Updated dependencies [[`e43e9a17ae`](https://github.com/refinedev/refine/commit/e43e9a17ae0ed41e649b8026b2b04d850136dcfd)]:
- @pankod/refine-ui-types@0.13.0
## 0.9.1
### Patch Changes
- Updated dependencies [[`476285e342`](https://github.com/refinedev/refine/commit/476285e3427c7e065892a281da529c038aee83d2), [`5388a338ab`](https://github.com/refinedev/refine/commit/5388a338abb9a5e03599da0a2786bea394cbc516), [`5388a338ab`](https://github.com/refinedev/refine/commit/5388a338abb9a5e03599da0a2786bea394cbc516)]:
- @pankod/refine-ui-types@0.12.0
- @pankod/refine-core@3.86.1
## 0.9.0
### Minor Changes
- Removed timers from all of the tests.
### Patch Changes
- Updated dependencies []:
- @pankod/refine-core@3.74.6
## 0.8.0
### Minor Changes
- [#2505](https://github.com/refinedev/refine/pull/2505) [`a4dbb63c88`](https://github.com/refinedev/refine/commit/a4dbb63c881a83e5146829130b1377e791b44469) Thanks [@salihozdemir](https://github.com/salihozdemir)! - Removed timers from all of the tests.
### Patch Changes
- Updated dependencies [[`a4dbb63c88`](https://github.com/refinedev/refine/commit/a4dbb63c881a83e5146829130b1377e791b44469)]:
- @pankod/refine-core@3.74.5
## 0.7.4
### Patch Changes
- Updated dependencies []:
- @pankod/refine-ui-types@0.11.0
## 0.7.3
### Patch Changes
- Updated dependencies [[`a65525de6f`](https://github.com/refinedev/refine/commit/a65525de6f995babfca1058e933cdbea67d6032e)]:
- @pankod/refine-ui-types@0.10.0
## 0.7.2
### Patch Changes
- Updated dependencies []:
- @pankod/refine-ui-types@0.9.0
## 0.7.1
### Patch Changes
- Updated dependencies [[`ad99916d6d`](https://github.com/refinedev/refine/commit/ad99916d6dbd181b857fd7df7b9619d8cac5e3e0)]:
- @pankod/refine-ui-types@0.8.0
## 0.7.0
### Minor Changes
- Updated `Sider` test for `render` props.
### Patch Changes
- Fixed version of react-router to `6.3.0`
- Updated dependencies []:
- @pankod/refine-core@3.69.9
- @pankod/refine-ui-types@0.7.0
## 0.6.1
### Patch Changes
- [#2501](https://github.com/refinedev/refine/pull/2501) [`4095a578d4`](https://github.com/refinedev/refine/commit/4095a578d471254ee58412f130ac5a0f3a62880f) Thanks [@omeraplak](https://github.com/omeraplak)! - Fixed version of react-router to `6.3.0`
- Updated dependencies [[`4095a578d4`](https://github.com/refinedev/refine/commit/4095a578d471254ee58412f130ac5a0f3a62880f)]:
- @pankod/refine-core@3.69.8
## 0.6.0
### Minor Changes
- [#2454](https://github.com/refinedev/refine/pull/2454) [`72487a4126`](https://github.com/refinedev/refine/commit/72487a4126fb7d827dccd3bcbdee9a83aa1f56af) Thanks [@ozkalai](https://github.com/ozkalai)! - Updated `Sider` test for `render` props.
### Patch Changes
- Updated dependencies [[`72487a4126`](https://github.com/refinedev/refine/commit/72487a4126fb7d827dccd3bcbdee9a83aa1f56af)]:
- @pankod/refine-ui-types@0.6.0
## 0.5.0
### Minor Changes
- Update type declaration generation with `tsc` instead of `tsup` for better navigation throughout projects source code.
### Patch Changes
- Updated dependencies []:
- @pankod/refine-core@3.67.0
- @pankod/refine-ui-types@0.5.0
## 0.4.0
### Minor Changes
- [#2440](https://github.com/refinedev/refine/pull/2440) [`0150dcd070`](https://github.com/refinedev/refine/commit/0150dcd0700253f1c4908e7e5f2e178bb122e9af) Thanks [@aliemir](https://github.com/aliemir)! - Update type declaration generation with `tsc` instead of `tsup` for better navigation throughout projects source code.
### Patch Changes
- Updated dependencies [[`0150dcd070`](https://github.com/refinedev/refine/commit/0150dcd0700253f1c4908e7e5f2e178bb122e9af), [`0150dcd070`](https://github.com/refinedev/refine/commit/0150dcd0700253f1c4908e7e5f2e178bb122e9af), [`0150dcd070`](https://github.com/refinedev/refine/commit/0150dcd0700253f1c4908e7e5f2e178bb122e9af), [`f2faf99f25`](https://github.com/refinedev/refine/commit/f2faf99f25542f73215ee89c74b241311177b327), [`0150dcd070`](https://github.com/refinedev/refine/commit/0150dcd0700253f1c4908e7e5f2e178bb122e9af), [`2c428b3105`](https://github.com/refinedev/refine/commit/2c428b31057e3e7c8901fc3da2773bc810235491)]:
- @pankod/refine-core@3.66.0
- @pankod/refine-ui-types@0.4.0
## 0.3.2
### Patch Changes
- Fix failing tests after the upgrade of `react-query` to v4.
- Updated dependencies []:
- @pankod/refine-core@3.56.6
## 0.3.1
### Patch Changes
- [#2260](https://github.com/refinedev/refine/pull/2260) [`a97ec592df`](https://github.com/refinedev/refine/commit/a97ec592dfb6dcf5b5bd063d2d76f50ca195c20e) Thanks [@salihozdemir](https://github.com/salihozdemir)! - Fix failing tests after the upgrade of `react-query` to v4.
- Updated dependencies [[`a97ec592df`](https://github.com/refinedev/refine/commit/a97ec592dfb6dcf5b5bd063d2d76f50ca195c20e), [`a97ec592df`](https://github.com/refinedev/refine/commit/a97ec592dfb6dcf5b5bd063d2d76f50ca195c20e)]:
- @pankod/refine-core@3.56.5
## 0.3.0
### Minor Changes
- Added common tests for UI components to ensure we're expecting same outputs by same inputs in all UI framework integrations.
### Patch Changes
- Updated `@pankod/refine-antd` and `@pankod/refine-mui` `fields` properties by using `@pankod/refine-ui-types` common `fields` types.
Updated `@pankod/refine-antd` and `@pankod/refine-mui` `fields` tests by using `@pankod/refine-ui-tests` common `fields` tests.
Updated `@pankod/refine-ui-tests` `fields` properties.
- Updated dependencies []:
- @pankod/refine-core@3.56.2
- @pankod/refine-ui-types@0.3.0
## 0.2.0
### Minor Changes
- [#2216](https://github.com/refinedev/refine/pull/2216) [`201846c77d`](https://github.com/refinedev/refine/commit/201846c77dba07a61f0c0335716b60641430c22a) Thanks [@aliemir](https://github.com/aliemir)! - Added common tests for UI components to ensure we're expecting same outputs by same inputs in all UI framework integrations.
### Patch Changes
- [#2216](https://github.com/refinedev/refine/pull/2216) [`201846c77d`](https://github.com/refinedev/refine/commit/201846c77dba07a61f0c0335716b60641430c22a) Thanks [@aliemir](https://github.com/aliemir)! - Updated `@pankod/refine-antd` and `@pankod/refine-mui` `fields` properties by using `@pankod/refine-ui-types` common `fields` types.
Updated `@pankod/refine-antd` and `@pankod/refine-mui` `fields` tests by using `@pankod/refine-ui-tests` common `fields` tests.
Updated `@pankod/refine-ui-tests` `fields` properties.
- Updated dependencies [[`201846c77d`](https://github.com/refinedev/refine/commit/201846c77dba07a61f0c0335716b60641430c22a)]:
- @pankod/refine-ui-types@0.2.0

254
packages/ui-tests/README.md Normal file
View File

@@ -0,0 +1,254 @@
<br/>
<div align="center" style="margin: 30px;">
<a href="https://refine.dev/">
<img src="https://refine.ams3.cdn.digitaloceanspaces.com/refine_logo.png" style="width:250px;" align="center" />
</a>
<br />
<br />
<div align="center">
<a href="https://refine.dev">Home Page</a> |
<a href="https://discord.gg/refine">Discord</a> |
<a href="https://refine.dev/examples/">Examples</a> |
<a href="https://refine.dev/blog/">Blog</a> |
<a href="https://refine.dev/docs/">Documentation</a>
</div>
</div>
<br />
<div align="center"><strong>Build your <a href="https://reactjs.org/">React</a>-based CRUD applications, without constraints.</strong><br>An open source, headless web application framework developed with flexibility in mind.
<br />
<br />
[![Discord](https://img.shields.io/discord/837692625737613362.svg?label=&logo=discord&logoColor=ffffff&color=7389D8&labelColor=6A7EC2)](https://discord.gg/refine)
[![Twitter Follow](https://img.shields.io/twitter/follow/refine_dev?style=social)](https://twitter.com/refine_dev)
<a href="https://www.producthunt.com/posts/refine-3?utm_source=badge-top-post-badge&utm_medium=badge&utm_souce=badge-refine&#0045;3" target="_blank"><img src="https://api.producthunt.com/widgets/embed-image/v1/top-post-badge.svg?post_id=362220&theme=light&period=daily" alt="refine - 100&#0037;&#0032;open&#0032;source&#0032;React&#0032;framework&#0032;to&#0032;build&#0032;web&#0032;apps&#0032;3x&#0032;faster | Product Hunt" style="width: 250px; height: 54px;" width="250" height="54" /></a>
</div>
<div align="center">
[![Awesome](https://github.com/refinedev/awesome-refine/raw/main/images/badge.svg)](https://github.com/refinedev/awesome-refine)
[![npm version](https://img.shields.io/npm/v/@refinedev/core.svg)](https://www.npmjs.com/package/@refinedev/core)
[![npm](https://img.shields.io/npm/dm/@refinedev/core)](https://www.npmjs.com/package/@refinedev/core)
[![](https://img.shields.io/github/commit-activity/m/refinedev/refine)](https://github.com/refinedev/refine/commits/master)
[![Contributor Covenant](https://img.shields.io/badge/Contributor%20Covenant-2.0-4baaaa.svg)](CODE_OF_CONDUCT.md)
</div>
<br/>
<a href="https://refine.dev/">
<picture>
<source media="(prefers-color-scheme: dark)" srcset="https://user-images.githubusercontent.com/18739364/200257042-3f2aa7f7-a07f-4824-8d2a-b25f26b6fd32.png">
<img alt="how-works-refine" src="https://user-images.githubusercontent.com/18739364/200257209-8fc0c8b1-2568-453e-873f-00513434deed.png">
</picture>
</a>
## What is refine?
**refine** is a React-based framework for the rapid ✨ development of web applications.
It eliminates repetitive tasks demanded by **CRUD** operations and provides industry standard solutions for critical parts like **authentication**, **access control**, **routing**, **networking**, **state management**, and **i18n**.
**refine** is _headless by design_, thereby offering unlimited styling and customization options.
## What do you mean by "headless" ?
Instead of being a limited set of pre-styled components, **refine** is a collection of helper `hooks`, `components`, and `providers`. They are all decoupled from _UI components_ and _business logic_, so that they never keep you from customizing your _UI_ or coding your own flow.
**refine** seamlessly works with any **custom design** or **UI framework** that you favor. For convenience, it ships with ready-made integrations for [Ant Design System](https://ant.design/), [Material UI](https://mui.com/material-ui/getting-started/overview/), [Mantine](https://mantine.dev/), and [Chakra UI](https://chakra-ui.com/).
## Use cases
**refine** shines on _data-intensive⚡_ applications like **admin panels**, **dashboards** and **internal tools**. Thanks to the built-in **SSR support**, **refine** can also power _customer-facing_ applications like **storefronts**.
You can take a look at some live examples that can be built using **refine** from scratch:
<a href="https://s.refine.dev/readme-admin-panel" target="_blank">
<img src="https://user-images.githubusercontent.com/18739364/204285956-cc20fa11-b769-4bd5-b8f6-9c05a283ac85.gif" style="width:267px;" />
</a>
<a href="https://s.refine.dev/readme-medium-clone" target="_blank">
<img src="https://user-images.githubusercontent.com/18739364/204285047-8f24f1f4-65ea-4952-83ed-81e92cdd5b90.gif" style="width:200px;" />
</a>
<a href="https://s.refine.dev/readme-ssr-storefront" target="_blank">
<img src="https://user-images.githubusercontent.com/18739364/204285039-1ce0cb06-fbf8-4704-89c9-2e004620c9a8.gif" style="width:200px;" />
</a>
<br/>
<br/>
[👉 Refer to most popular real use case examples](https://refine.dev/examples/)
[👉 More **refine** powered different usage scenarios can be found here](https://refine.dev/docs/examples/)
## Key Features
⚙️ Zero-config, **one-minute setup** with a **single CLI command**
🔌 Connectors for **15+ backend services** including [REST API](https://github.com/refinedev/refine/tree/master/packages/simple-rest), [GraphQL](https://github.com/refinedev/refine/tree/master/packages/graphql), [NestJs CRUD](https://github.com/refinedev/refine/tree/master/packages/nestjsx-crud), [Airtable](https://github.com/refinedev/refine/tree/master/packages/airtable), [Strapi](https://github.com/refinedev/refine/tree/master/packages/strapi), [Strapi v4](https://github.com/refinedev/refine/tree/master/packages/strapi-v4), [Supabase](https://github.com/refinedev/refine/tree/master/packages/supabase), [Hasura](https://github.com/refinedev/refine/tree/master/packages/hasura), [Appwrite](https://github.com/refinedev/refine/tree/master/packages/appwrite), [Firebase](https://firebase.google.com/), [Nestjs-Query](https://github.com/refinedev/refine/tree/master/packages/nestjs-query) and [Directus](https://directus.io/).
🌐 **SSR support** with **Next.js** or **Remix**
🔍 Auto-generated **CRUD** UIs from **your API data structure**
⚛ Perfect **state management** & **mutations** with **React Query**
🔀 **Advanced routing** with any router library of your choice
🔐 Providers for seamless **authentication** and **access control** flows
⚡ Out-of-the-box support for **live / real-time applications**
📄 Easy **audit logs** & **document versioning**
💬 Support for any **i18n** framework
💪 Future-proof, **robust architecture**
⌛️ Built-in CLI with time-saving features
✅ Full **test coverage**
## Quick Start
The fastest way to get started with **refine** is by using the `create refine-app` project starter tool.
Run the following command to create a new **refine** project configured with [Ant Design System](https://ant.design/) as the default UI framework:
```
npm create refine-app@latest -- --preset refine-antd
```
Once the setup is complete, navigate to the project folder and start your project with:
```
npm run dev
```
<br/>
Your **refine** application will be accessible at [http://localhost:3000](http://localhost:3000):
<a href="http://localhost:3000">![Welcome on board](https://refine.ams3.cdn.digitaloceanspaces.com/website/static/img/welcome-on-board.png)</a>
<br/>
Let's consume a public `fake REST API` and add two resources (_posts_, _categories_) to our project. Replace the contents of `src/App.tsx` with the following code:
```tsx title="src/App.tsx"
import { Refine } from "@refinedev/core";
import {
Layout,
useNotificationProvider,
ErrorComponent,
} from "@refinedev/antd";
import routerProvider, { NavigateToResource } from "@refinedev/react-router-v6";
import dataProvider from "@refinedev/simple-rest";
import { BrowserRouter, Routes, Route, Outlet } from "react-router-dom";
import { AntdInferencer } from "@refinedev/inferencer/antd";
import "@refinedev/antd/dist/reset.css";
const App: React.FC = () => {
return (
<BrowserRouter>
<Refine
routerProvider={routerProvider}
dataProvider={dataProvider("https://api.fake-rest.refine.dev")}
notificationProvider={useNotificationProvider}
resources={[
{
name: "posts",
list: "/posts",
show: "/posts/show/:id",
create: "/posts/create",
edit: "/posts/edit/:id",
meta: { canDelete: true },
},
{
name: "categories",
list: "/categories",
show: "/categories/show/:id",
},
]}
>
<Routes>
<Route
element={
<Layout>
<Outlet />
</Layout>
}
>
<Route index element={<NavigateToResource />} />
<Route path="posts">
<Route index element={<AntdInferencer />} />
<Route path="show/:id" element={<AntdInferencer />} />
<Route path="create" element={<AntdInferencer />} />
<Route path="edit/:id" element={<AntdInferencer />} />
</Route>
<Route path="categories">
<Route index element={<AntdInferencer />} />
<Route path="show/:id" element={<AntdInferencer />} />
</Route>
<Route path="*" element={<ErrorComponent />} />
</Route>
</Routes>
</Refine>
</BrowserRouter>
);
};
export default App;
```
<br/>
🚀 Thanks to **refine Inferencer package**, it guesses the configuration to use for the `list`, `show`, `create`, and `edit` pages based on the data fetched from the API and generates the pages automatically.
Now, you should see the output as a table populated with `post` & `category` data:
![First example result](https://refine.ams3.cdn.digitaloceanspaces.com/website/static/img/readme-quick-start.png)
<br/>
You can get the auto-generated pages codes by clicking the `Show Code` button on each page. Afterward, simply pass the pages to the `resources` array by replacing with the Inferencer components.
## Next Steps
👉 Jump to [Tutorial](https://refine.dev/docs/tutorial/introduction/index/) to continue your work and turn the example into a full-blown CRUD application.
👉 Visit [Learn the Basics Page](https://refine.dev/docs/getting-started/overview/) to get informed about the fundamental concepts.
👉 Read more on [Advanced Tutorials
](https://refine.dev/docs/advanced-tutorials/) for different usage scenarios.
👉 See the real-life [Finefoods Demo](https://refine.dev/demo/) project.
👉 Play with interactive [Examples](https://refine.dev/docs/examples/)
## Stargazers
[![Stargazers repo roster for refinedev/refine](https://reporoster.com/stars/refinedev/refine)](https://github.com/refinedev/refine/stargazers)
## Contribution
[👉 Refer to contribution docs for more information](https://refine.dev/docs/contributing/#ways-to-contribute)
If you have any doubts related to the project or want to discuss something, then join our [Discord Server](https://discord.gg/refine).
## Our ♥️ Contributors
<a href="https://github.com/refinedev/refine/graphs/contributors">
<img src="https://contrib.rocks/image?repo=refinedev/refine" />
</a>
## License
Licensed under the MIT License, Copyright © 2021-present Refinedev

View File

@@ -0,0 +1,26 @@
const { pathsToModuleNameMapper } = require("ts-jest");
const { compilerOptions } = require("./tsconfig.json");
const paths = compilerOptions.paths ? compilerOptions.paths : {};
module.exports = {
preset: "ts-jest",
rootDir: "./",
testEnvironment: "jsdom",
setupFilesAfterEnv: ["<rootDir>/src/test/jest.setup.ts"],
testPathIgnorePatterns: ["<rootDir>/node_modules/"],
moduleNameMapper: {
...pathsToModuleNameMapper(paths, { prefix: "<rootDir>/" }),
},
displayName: "ui-tests",
transform: {
"^.+\\.svg$": "<rootDir>/src/test/svgTransform.ts",
"^.+\\.tsx?$": [
"ts-jest",
{
tsconfig: "<rootDir>/tsconfig.test.json",
},
],
},
coveragePathIgnorePatterns: ["<rootDir>/src/index.ts"],
};

View File

@@ -0,0 +1,64 @@
{
"name": "@refinedev/ui-tests",
"version": "1.14.9",
"private": false,
"description": "refine is a React-based framework for building internal tools, rapidly. It ships with Ant Design System, an enterprise-level UI toolkit.",
"license": "MIT",
"author": "refine",
"exports": {
".": {
"import": {
"types": "./dist/index.d.mts",
"default": "./dist/index.mjs"
},
"require": {
"types": "./dist/index.d.cts",
"default": "./dist/index.cjs"
}
}
},
"main": "dist/index.cjs",
"module": "dist/index.mjs",
"typings": "dist/index.d.ts",
"scripts": {
"attw": "attw --pack .",
"build": "tsup && node ../shared/generate-declarations.js",
"dev": "tsup --watch",
"prepare": "pnpm build",
"publint": "publint --strict=true --level=suggestion",
"test": "jest --passWithNoTests --runInBand",
"types": "node ../shared/generate-declarations.js"
},
"dependencies": {
"@refinedev/core": "^4.53.0",
"@refinedev/ui-types": "^1.22.9",
"tslib": "^2.6.2"
},
"devDependencies": {
"@esbuild-plugins/node-resolve": "^0.1.4",
"@testing-library/dom": "^8.5.0",
"@testing-library/jest-dom": "^5.16.4",
"@testing-library/react": "^13.1.1",
"@testing-library/react-hooks": "^8.0.0",
"@testing-library/user-event": "^14.1.1",
"@types/jest": "^29.2.4",
"@types/lodash": "^4.14.171",
"@types/react": "^18.0.0",
"@types/react-dom": "^18.0.0",
"@types/testing-library__jest-dom": "^5.14.3",
"jest": "^29.3.1",
"jest-environment-jsdom": "^29.3.1",
"react-router-dom": "^6.8.1",
"ts-jest": "^29.1.2",
"tsup": "^6.7.0",
"typescript": "^5.4.2"
},
"peerDependencies": {
"@refinedev/core": "^4.46.1",
"react": "^17.0.0 || ^18.0.0",
"react-dom": "^17.0.0 || ^18.0.0"
},
"publishConfig": {
"access": "public"
}
}

View File

@@ -0,0 +1 @@
export * from "./tests/index.js";

View File

@@ -0,0 +1,182 @@
import React from "react";
import type {
ParsedParams,
IResourceItem,
Action,
RouterBindings,
AuthProvider,
LegacyAuthProvider,
IRouterContext,
} from "@refinedev/core";
import { useParams, useLocation, Link, useNavigate } from "react-router-dom";
/* import {
IDataContext,
IRouterContext,
IAccessControlContext,
ILiveContext,
} from "@refinedev/core"; */
export const posts = [
{
id: "1",
title:
"Necessitatibus necessitatibus id et cupiditate provident est qui amet.",
slug: "ut-ad-et",
content:
"Cupiditate labore quaerat cum incidunt vel et consequatur modi illo. Et maxime aut commodi occaecati omnis. Est voluptatem quibusdam aliquam. Esse tenetur omnis eaque. Consequatur necessitatibus illum ipsum aspernatur architecto qui. Ut temporibus qui nobis. Reiciendis est magnam ipsa quasi dolor ipsa error. Et eaque cumque est. Eos et odit corporis delectus aut corrupti tempora velit. Perferendis ratione voluptas corrupti id temporibus nam.",
categoryId: 1,
status: "active",
userId: 5,
tags: [16, 31, 45],
},
{
id: "2",
title: "Recusandae consectetur aut atque est.",
slug: "consequatur-molestiae-rerum",
content:
"Quia ut autem. Hic dolorum magni est quisquam. Modi est id et est. Est sapiente velit iure non voluptatem natus enim. Distinctio ipsa repellendus est. Sunt ipsam dignissimos vero error est cumque eaque. Consequatur voluptas suscipit optio incidunt doloremque quia harum harum. Totam voluptatibus aperiam quia. Est omnis deleniti et aut at fugit temporibus debitis modi. Magni aut vel quod magnam.",
categoryId: 38,
status: "active",
userId: 36,
tags: [16, 30, 46],
},
];
const MockDataProvider = () => {
return {
create: () => Promise.resolve({ data: posts[0] }),
createMany: () => Promise.resolve({ data: posts }),
deleteOne: () => Promise.resolve({ data: posts[0] }),
deleteMany: () => Promise.resolve({ data: [] }),
getList: () => Promise.resolve({ data: posts, total: 2 }),
getMany: () => Promise.resolve({ data: [...posts] }),
getOne: () => Promise.resolve({ data: posts[0] }),
update: () => Promise.resolve({ data: posts[0] }),
updateMany: () => Promise.resolve({ data: [] }),
getApiUrl: () => "https://api.fake-rest.refine.dev",
custom: () => Promise.resolve({ data: [...posts] }),
};
};
export const MockJSONServer = MockDataProvider() as any;
export const MockRouterProvider = {
useHistory: () => {
const navigate = useNavigate();
return {
push: navigate,
replace: (path: string) => {
navigate(path, { replace: true });
},
goBack: () => {
navigate(-1);
},
};
},
useLocation,
useParams: () => {
const params = useParams();
return params as any;
},
Link,
Prompt: () => null,
};
export const MockAccessControlProvider: any = {
can: () => Promise.resolve({ can: true }),
};
export const MockLiveProvider: any = {
subscribe: () => ({}),
unsubscribe: () => ({}),
publish: () => ({}),
};
export const mockLegacyAuthProvider: LegacyAuthProvider = {
login: () => Promise.resolve(),
logout: () => Promise.resolve(),
checkError: () => Promise.resolve(),
checkAuth: () => Promise.resolve(),
getPermissions: () => Promise.resolve(["admin"]),
getUserIdentity: () =>
Promise.resolve({ name: "John Doe", avatar: "localhost:3000" }),
};
export const mockAuthProvider: AuthProvider = {
login: async () => ({ success: true }),
check: async () => ({ authenticated: true }),
onError: async () => ({}),
logout: async () => ({ success: true }),
updatePassword: jest.fn().mockResolvedValue({ success: true }),
forgotPassword: jest.fn().mockResolvedValue({ success: true }),
register: jest.fn().mockResolvedValue({ success: true }),
};
export const mockRouterBindings = ({
pathname,
params,
resource,
action,
id,
fns,
}: {
pathname?: string;
params?: ParsedParams;
resource?: IResourceItem;
action?: Action;
id?: string;
fns?: Partial<RouterBindings>;
} = {}): RouterBindings => {
const bindings: RouterBindings = {
go: () => {
return ({ type }) => {
if (type === "path") return "";
return undefined;
};
},
parse: () => {
return () => {
return {
params: {
...params,
},
pathname,
resource: resource,
action: action,
id: id || undefined,
};
};
},
back: () => {
return () => undefined;
},
Link: ({ to, children, ...props }) => (
<a href={to} {...props}>
{children}
</a>
),
...fns,
};
return bindings;
};
export const mockLegacyRouterProvider = () => {
const provider: IRouterContext = {
useHistory: () => {
return {
push: () => undefined,
replace: () => undefined,
goBack: () => undefined,
};
},
useLocation: () => ({}) as any,
useParams: () => ({}) as any,
Link: () => null,
Prompt: () => null,
};
return provider;
};

View File

@@ -0,0 +1,112 @@
import React from "react";
import { BrowserRouter } from "react-router-dom";
import { type AuthProvider, Refine } from "@refinedev/core";
import { MockRouterProvider, MockJSONServer } from "@test";
import type {
I18nProvider,
AccessControlProvider,
LegacyAuthProvider,
DataProvider,
NotificationProvider,
IResourceItem,
RouterBindings,
IRouterContext,
IRefineOptions,
} from "@refinedev/core";
/* interface ITestWrapperProps {
authProvider?: IAuthContext;
dataProvider?: IDataContext;
i18nProvider?: I18nProvider;
accessControlProvider?: IAccessControlContext;
liveProvider?: ILiveContext;
resources?: IResourceItem[];
children?: React.ReactNode;
routerInitialEntries?: string[];
refineProvider?: IRefineContextProvider;
} */
const List = () => {
return <div>hede</div>;
};
export interface ITestWrapperProps {
dataProvider?: DataProvider;
legacyAuthProvider?: LegacyAuthProvider;
authProvider?: AuthProvider;
resources?: IResourceItem[];
notificationProvider?: NotificationProvider;
accessControlProvider?: AccessControlProvider;
i18nProvider?: I18nProvider;
legacyRouterProvider?: IRouterContext;
routerProvider?: RouterBindings;
routerInitialEntries?: string[];
DashboardPage?: React.FC;
options?: IRefineOptions;
}
export const TestWrapper: (props: ITestWrapperProps) => React.FC = ({
dataProvider,
legacyAuthProvider,
authProvider,
resources,
notificationProvider,
accessControlProvider,
routerInitialEntries,
DashboardPage,
i18nProvider,
routerProvider,
legacyRouterProvider,
options,
}) => {
// Previously, MemoryRouter was used in this wrapper. However, the
// recommendation by react-router developers (see
// https://github.com/remix-run/react-router/discussions/8241#discussioncomment-159686)
// is essentially to use the same router as your actual application. Besides
// that, it's impossible to check for location changes with MemoryRouter if
// needed.
if (routerInitialEntries) {
routerInitialEntries.forEach((route) => {
window.history.replaceState({}, "", route);
});
}
return ({ children }: React.PropsWithChildren<{}>): React.ReactElement => {
return (
<BrowserRouter>
<Refine
options={{
disableTelemetry: true,
...options,
}}
dataProvider={dataProvider ?? MockJSONServer}
i18nProvider={i18nProvider}
routerProvider={routerProvider}
legacyRouterProvider={legacyRouterProvider ?? MockRouterProvider}
legacyAuthProvider={legacyAuthProvider}
authProvider={authProvider}
notificationProvider={notificationProvider}
resources={resources ?? [{ name: "posts", list: List }]}
accessControlProvider={accessControlProvider}
DashboardPage={DashboardPage ?? undefined}
>
{children}
</Refine>
</BrowserRouter>
);
};
};
export {
MockJSONServer,
MockRouterProvider,
MockAccessControlProvider,
MockLiveProvider,
mockRouterBindings,
mockAuthProvider,
mockLegacyAuthProvider,
mockLegacyRouterProvider,
} from "./dataMocks";
// re-export everything
export * from "@testing-library/react";

View File

@@ -0,0 +1,16 @@
import "@testing-library/jest-dom";
import "@testing-library/jest-dom/extend-expect";
/** Antd mocks */
window.matchMedia = jest.fn().mockImplementation((query) => {
return {
matches: false,
media: query,
onchange: null,
addListener: jest.fn(),
removeListener: jest.fn(),
};
});
window.scroll = jest.fn();
window.alert = jest.fn();

View File

@@ -0,0 +1,8 @@
module.exports = {
process() {
return "module.exports = {};";
},
getCacheKey() {
return "svgTransform";
},
};

View File

@@ -0,0 +1,44 @@
import React from "react";
import type { AutoSaveIndicatorProps } from "@refinedev/core";
import { render } from "@test";
export const autoSaveIndicatorTests = (
AutoSaveIndicator: React.ComponentType<AutoSaveIndicatorProps>,
): void => {
describe("[@refinedev/ui-tests] Common Tests / AutoSaveIndicator", () => {
it("should render success", async () => {
const { findByText, getByText } = render(
<AutoSaveIndicator status="success" />,
);
await findByText("saved");
getByText("saved");
});
it("should render error", async () => {
const { findByText, getByText } = render(
<AutoSaveIndicator status="error" />,
);
await findByText("auto save failure");
getByText("auto save failure");
});
it("should render idle", async () => {
const { findByText, getByText } = render(
<AutoSaveIndicator status="idle" />,
);
await findByText("waiting for changes");
getByText("waiting for changes");
});
it("should render loading", async () => {
const { findByText, getByText } = render(
<AutoSaveIndicator status="loading" />,
);
await findByText("saving...");
getByText("saving...");
});
});
};

View File

@@ -0,0 +1,80 @@
import React from "react";
import { Route, Routes } from "react-router-dom";
import type { RefineBreadcrumbProps } from "@refinedev/ui-types";
import { act, type ITestWrapperProps, render, TestWrapper } from "@test";
const renderBreadcrumb = (
children: React.ReactNode,
wrapperProps: ITestWrapperProps = {},
) => {
return render(
<Routes>
<Route path="/:resource/:action" element={children} />
</Routes>,
{
wrapper: TestWrapper(wrapperProps),
},
);
};
const DummyResourcePage = () => <div>Dummy</div>;
export const breadcrumbTests = (
Breadcrumb: React.ComponentType<RefineBreadcrumbProps<any>>,
): void => {
describe("[@refinedev/ui-tests] Common Tests / CRUD Create", () => {
it("should render successfuly", async () => {
const { container } = renderBreadcrumb(<Breadcrumb />);
expect(container).toBeTruthy();
});
it("should render breadcrumb items", async () => {
const { getByText } = renderBreadcrumb(<Breadcrumb />, {
resources: [{ name: "posts" }],
routerInitialEntries: ["/posts/create"],
});
getByText("Posts");
getByText("Create");
});
it("should render breadcrumb items with link", async () => {
const { container } = renderBreadcrumb(<Breadcrumb />, {
resources: [{ name: "posts", list: DummyResourcePage }],
routerInitialEntries: ["/posts/create"],
});
expect(container.querySelector("a")?.getAttribute("href")).toBe("/posts");
});
it("should render breadcrumb items with resource icon", async () => {
const { getByTestId } = renderBreadcrumb(<Breadcrumb />, {
resources: [
{
name: "posts",
icon: <div data-testid="resource-icon" />,
},
],
routerInitialEntries: ["/posts/create"],
});
getByTestId("resource-icon");
});
it("should render breadcrumb items without resource icon", async () => {
const { queryByTestId } = renderBreadcrumb(<Breadcrumb hideIcons />, {
resources: [
{
name: "posts",
icon: <div data-testid="resource-icon" />,
},
],
routerInitialEntries: ["/posts/create"],
});
expect(queryByTestId("resource-icon")).not.toBeInTheDocument();
});
});
};

View File

@@ -0,0 +1,441 @@
import React from "react";
import { Route, Routes } from "react-router-dom";
import {
type RefineCloneButtonProps,
RefineButtonTestIds,
} from "@refinedev/ui-types";
import { act, fireEvent, render, TestWrapper, waitFor } from "@test";
export const buttonCloneTests = (
CloneButton: React.ComponentType<RefineCloneButtonProps<any, any>>,
): void => {
describe("[@refinedev/ui-tests] Common Tests / Clone Button", () => {
beforeAll(() => {
jest.spyOn(console, "warn").mockImplementation(jest.fn());
});
it("should render button successfuly", async () => {
const { container, getByText } = render(<CloneButton />, {
wrapper: TestWrapper({}),
});
expect(container).toBeTruthy();
expect(getByText("Clone").closest("button")).not.toBeDisabled();
});
it("should have the correct test-id", async () => {
const { queryByTestId } = render(<CloneButton />, {
wrapper: TestWrapper({}),
});
expect(queryByTestId(RefineButtonTestIds.CloneButton)).toBeTruthy();
});
it("should render text by children", async () => {
const { container, getByText } = render(
<CloneButton>refine</CloneButton>,
{
wrapper: TestWrapper({}),
},
);
expect(container).toBeTruthy();
getByText("refine");
});
it("should render without text show only icon", async () => {
const { container, queryByText } = render(<CloneButton hideText />, {
wrapper: TestWrapper({}),
});
expect(container).toBeTruthy();
expect(queryByText("Clone")).not.toBeInTheDocument();
});
describe("access control", () => {
describe("with global access control only", () => {
describe("with default behaviour", () => {
describe("when user not have access", () => {
it("should render disabled button with reason text", async () => {
const { container, getByText } = render(
<CloneButton recordItemId="1">Clone</CloneButton>,
{
wrapper: TestWrapper({
accessControlProvider: {
can: async ({ params, action }) => {
if (action === "create" && params?.id === "1") {
return {
can: false,
reason: "Access Denied",
};
}
return {
can: true,
};
},
},
}),
},
);
expect(container).toBeTruthy();
await waitFor(() =>
expect(getByText("Clone").closest("button")).toBeDisabled(),
);
waitFor(() =>
expect(
getByText("Clone").closest("button")?.getAttribute("title"),
).toBe("Access Denied"),
);
});
});
describe("when user have access", () => {
it("should render enabled button", async () => {
const { container, getByText } = render(
<CloneButton recordItemId="2">Clone</CloneButton>,
{
wrapper: TestWrapper({
accessControlProvider: {
can: async ({ params, action }) => {
if (action === "create" && params?.id === "1") {
return {
can: false,
};
}
return {
can: true,
};
},
},
}),
},
);
expect(container).toBeTruthy();
await waitFor(() =>
expect(getByText("Clone").closest("button")).not.toBeDisabled(),
);
});
});
});
describe("when hideIfUnauthorized is true", () => {
it("should not render button", async () => {
const { container, queryByText } = render(
<CloneButton>Clone</CloneButton>,
{
wrapper: TestWrapper({
accessControlProvider: {
can: async () => ({ can: false }),
options: {
buttons: {
hideIfUnauthorized: true,
},
},
},
}),
},
);
expect(container).toBeTruthy();
expect(queryByText("Clone")).not.toBeInTheDocument();
});
});
describe("when access control is disabled explicitly", () => {
it("should render enabled button", async () => {
const { container, getByText } = render(
<CloneButton>Clone</CloneButton>,
{
wrapper: TestWrapper({
accessControlProvider: {
can: async () => ({ can: false }),
options: {
buttons: {
enableAccessControl: false,
hideIfUnauthorized: true,
},
},
},
}),
},
);
expect(container).toBeTruthy();
expect(getByText("Clone").closest("button")).not.toBeDisabled();
});
});
});
describe("with global config and accessControl prop", () => {
describe("when access control enabled globally", () => {
describe("when access control is disabled with prop", () => {
it("should render enabled button", async () => {
const { container, getByText } = render(
<CloneButton accessControl={{ enabled: false }}>
Clone
</CloneButton>,
{
wrapper: TestWrapper({
accessControlProvider: {
can: async () => {
return {
can: false,
};
},
options: {
buttons: {
enableAccessControl: true,
hideIfUnauthorized: true,
},
},
},
}),
},
);
expect(container).toBeTruthy();
await waitFor(() =>
expect(getByText("Clone").closest("button")).not.toBeDisabled(),
);
});
});
describe("when hideIfUnauthorized false globally", () => {
describe("when hideIfUnauthorized enabled with prop", () => {
it("should not render button", async () => {
const { container, queryByText } = render(
<CloneButton
accessControl={{
hideIfUnauthorized: true,
}}
>
Clone
</CloneButton>,
{
wrapper: TestWrapper({
accessControlProvider: {
can: async () => ({
can: false,
}),
options: {
buttons: {
hideIfUnauthorized: false,
},
},
},
}),
},
);
expect(container).toBeTruthy();
expect(queryByText("Clone")).not.toBeInTheDocument();
});
});
});
});
describe("when access control disabled globally", () => {
describe("when access control enabled with prop", () => {
it("should render disabled button with reason text", async () => {
const { container, getByText } = render(
<CloneButton accessControl={{ enabled: true }}>
Clone
</CloneButton>,
{
wrapper: TestWrapper({
accessControlProvider: {
can: async () => {
return {
can: false,
reason: "Access Denied",
};
},
},
}),
},
);
expect(container).toBeTruthy();
await waitFor(() =>
expect(getByText("Clone").closest("button")).toBeDisabled(),
);
waitFor(() =>
expect(
getByText("Clone").closest("button")?.getAttribute("title"),
).toBe("Access Denied"),
);
});
});
});
describe("when hideIfUnauthorized enabled globally", () => {
describe("when hideIfUnauthorized disabled with prop", () => {
it("should render button", async () => {
const { container, queryByText } = render(
<CloneButton
accessControl={{
hideIfUnauthorized: false,
}}
>
Clone
</CloneButton>,
{
wrapper: TestWrapper({
accessControlProvider: {
can: async () => ({
can: false,
}),
options: {
buttons: {
hideIfUnauthorized: true,
},
},
},
}),
},
);
expect(container).toBeTruthy();
expect(queryByText("Clone")).toBeInTheDocument();
});
});
});
});
});
it("should render called function successfully if click the button", async () => {
const clone = jest.fn();
const { getByText } = render(
<CloneButton onClick={() => clone()} recordItemId="1" />,
{
wrapper: TestWrapper({
resources: [{ name: "posts" }],
}),
},
);
await act(async () => {
fireEvent.click(getByText("Clone"));
});
expect(clone).toHaveBeenCalledTimes(1);
});
it("should create page redirect clone route called function successfully if click the button", async () => {
const { getByText } = render(
<Routes>
<Route path="/:resource" element={<CloneButton recordItemId="1" />} />
</Routes>,
{
wrapper: TestWrapper({
routerInitialEntries: ["/posts"],
}),
},
);
await act(async () => {
fireEvent.click(getByText("Clone"));
});
expect(window.location.pathname).toBe("/posts/clone/1");
});
it("should edit page redirect clone route called function successfully if click the button", async () => {
const { getByText } = render(
<Routes>
<Route path="/:resource/:action/:id" element={<CloneButton />} />
</Routes>,
{
wrapper: TestWrapper({
routerInitialEntries: ["/posts/edit/1"],
}),
},
);
await act(async () => {
fireEvent.click(getByText("Clone"));
});
expect(window.location.pathname).toBe("/posts/clone/1");
});
it("should custom resource and recordItemId redirect clone route called function successfully if click the button", async () => {
const { getByText } = render(
<Routes>
<Route
path="/:resource"
element={
<CloneButton
resourceNameOrRouteName="categories"
recordItemId="1"
/>
}
/>
</Routes>,
{
wrapper: TestWrapper({
resources: [{ name: "posts" }, { name: "categories" }],
routerInitialEntries: ["/posts"],
}),
},
);
await act(async () => {
fireEvent.click(getByText("Clone"));
});
expect(window.location.pathname).toBe("/categories/clone/1");
});
it("should redirect with custom route called function successfully if click the button", async () => {
const { getByText } = render(
<Routes>
<Route
path="/:resource"
element={
<CloneButton
resourceNameOrRouteName="custom-route-posts"
recordItemId="1"
/>
}
/>
</Routes>,
{
wrapper: TestWrapper({
resources: [
{
name: "posts",
meta: { route: "custom-route-posts" },
},
{ name: "posts" },
],
routerInitialEntries: ["/posts"],
}),
},
);
await act(async () => {
fireEvent.click(getByText("Clone"));
});
expect(window.location.pathname).toBe("/custom-route-posts/clone/1");
});
});
};

View File

@@ -0,0 +1,412 @@
import React from "react";
import { Route, Routes } from "react-router-dom";
import {
type RefineCreateButtonProps,
RefineButtonTestIds,
} from "@refinedev/ui-types";
import { act, render, TestWrapper, fireEvent, waitFor } from "@test";
export const buttonCreateTests = (
CreateButton: React.ComponentType<RefineCreateButtonProps<any, any>>,
): void => {
describe("[@refinedev/ui-tests] Common Tests / Create Button", () => {
beforeAll(() => {
jest.spyOn(console, "warn").mockImplementation(jest.fn());
});
const create = jest.fn();
it("should render button successfuly", async () => {
const { container, getByText } = render(<CreateButton />, {
wrapper: TestWrapper({}),
});
expect(container).toBeTruthy();
expect(getByText("Create").closest("button")).not.toBeDisabled();
});
it("should have the correct test-id", async () => {
const { queryByTestId } = render(<CreateButton />, {
wrapper: TestWrapper({}),
});
expect(queryByTestId(RefineButtonTestIds.CreateButton)).toBeTruthy();
});
it("should render text by children", async () => {
const { container, getByText } = render(
<CreateButton>refine</CreateButton>,
{
wrapper: TestWrapper({}),
},
);
expect(container).toBeTruthy();
getByText("refine");
});
it("should render without text show only icon", async () => {
const { container, queryByText } = render(<CreateButton hideText />, {
wrapper: TestWrapper({}),
});
expect(container).toBeTruthy();
expect(queryByText("Create")).not.toBeInTheDocument();
});
describe("access control", () => {
describe("with global access control only", () => {
describe("with default behaviour", () => {
describe("when user not have access", () => {
it("should render disabled button with reason text", async () => {
const { container, getByText } = render(
<CreateButton>Create</CreateButton>,
{
wrapper: TestWrapper({
accessControlProvider: {
can: async ({ action }) => {
if (action === "create") {
return {
can: false,
reason: "Access Denied",
};
}
return {
can: true,
};
},
},
}),
},
);
expect(container).toBeTruthy();
await waitFor(() =>
expect(getByText("Create").closest("button")).toBeDisabled(),
);
waitFor(() =>
expect(
getByText("Create").closest("button")?.getAttribute("title"),
).toBe("Access Denied"),
);
});
});
describe("when user have access", () => {
it("should render enabled button", async () => {
const { container, getByText } = render(
<CreateButton>Create</CreateButton>,
{
wrapper: TestWrapper({
accessControlProvider: {
can: async ({ action }) => {
if (action === "create") {
return {
can: false,
};
}
return {
can: true,
};
},
},
}),
},
);
expect(container).toBeTruthy();
await waitFor(() =>
expect(
getByText("Create").closest("button"),
).not.toBeDisabled(),
);
});
});
});
describe("when hideIfUnauthorized is true", () => {
it("should not render button", async () => {
const { container, queryByText } = render(
<CreateButton>Create</CreateButton>,
{
wrapper: TestWrapper({
accessControlProvider: {
can: async () => ({ can: false }),
options: {
buttons: {
hideIfUnauthorized: true,
},
},
},
}),
},
);
expect(container).toBeTruthy();
expect(queryByText("Create")).not.toBeInTheDocument();
});
});
describe("when access control is disabled explicitly", () => {
it("should render enabled button", async () => {
const { container, getByText } = render(
<CreateButton>Create</CreateButton>,
{
wrapper: TestWrapper({
accessControlProvider: {
can: async () => ({ can: false }),
options: {
buttons: {
enableAccessControl: false,
hideIfUnauthorized: true,
},
},
},
}),
},
);
expect(container).toBeTruthy();
expect(getByText("Create").closest("button")).not.toBeDisabled();
});
});
});
describe("with global config and accessControl prop", () => {
describe("when access control enabled globally", () => {
describe("when access control is disabled with prop", () => {
it("should render enabled button", async () => {
const { container, getByText } = render(
<CreateButton accessControl={{ enabled: false }}>
Create
</CreateButton>,
{
wrapper: TestWrapper({
accessControlProvider: {
can: async () => {
return {
can: false,
};
},
options: {
buttons: {
enableAccessControl: true,
hideIfUnauthorized: true,
},
},
},
}),
},
);
expect(container).toBeTruthy();
await waitFor(() =>
expect(
getByText("Create").closest("button"),
).not.toBeDisabled(),
);
});
});
describe("when hideIfUnauthorized false globally", () => {
describe("when hideIfUnauthorized enabled with prop", () => {
it("should not render button", async () => {
const { container, queryByText } = render(
<CreateButton
accessControl={{
hideIfUnauthorized: true,
}}
>
Create
</CreateButton>,
{
wrapper: TestWrapper({
accessControlProvider: {
can: async () => ({
can: false,
}),
options: {
buttons: {
hideIfUnauthorized: false,
},
},
},
}),
},
);
expect(container).toBeTruthy();
expect(queryByText("Create")).not.toBeInTheDocument();
});
});
});
});
describe("when access control disabled globally", () => {
describe("when access control enabled with prop", () => {
it("should render disabled button with reason text", async () => {
const { container, getByText } = render(
<CreateButton accessControl={{ enabled: true }}>
Create
</CreateButton>,
{
wrapper: TestWrapper({
accessControlProvider: {
can: async () => {
return {
can: false,
reason: "Access Denied",
};
},
},
}),
},
);
expect(container).toBeTruthy();
await waitFor(() =>
expect(getByText("Create").closest("button")).toBeDisabled(),
);
waitFor(() =>
expect(
getByText("Create").closest("button")?.getAttribute("title"),
).toBe("Access Denied"),
);
});
});
});
describe("when hideIfUnauthorized enabled globally", () => {
describe("when hideIfUnauthorized disabled with prop", () => {
it("should render button", async () => {
const { container, queryByText } = render(
<CreateButton
accessControl={{
hideIfUnauthorized: false,
}}
>
Create
</CreateButton>,
{
wrapper: TestWrapper({
accessControlProvider: {
can: async () => ({
can: false,
}),
options: {
buttons: {
hideIfUnauthorized: true,
},
},
},
}),
},
);
expect(container).toBeTruthy();
expect(queryByText("Create")).toBeInTheDocument();
});
});
});
});
});
it("should render called function successfully if click the button", async () => {
const { getByText } = render(<CreateButton onClick={() => create()} />, {
wrapper: TestWrapper({}),
});
await act(async () => {
fireEvent.click(getByText("Create"));
});
expect(create).toHaveBeenCalledTimes(1);
});
it("should redirect custom resource route called function successfully if click the button", async () => {
const { getByText } = render(
<Routes>
<Route
path="/:resource"
element={<CreateButton resourceNameOrRouteName="categories" />}
/>
</Routes>,
{
wrapper: TestWrapper({
resources: [{ name: "posts" }, { name: "categories" }],
routerInitialEntries: ["/posts"],
}),
},
);
await act(async () => {
fireEvent.click(getByText("Create"));
});
expect(window.location.pathname).toBe("/categories/create");
});
it("should redirect create route called function successfully if click the button", async () => {
const { getByText } = render(
<Routes>
<Route path="/:resource" element={<CreateButton />} />
</Routes>,
{
wrapper: TestWrapper({
resources: [{ name: "posts" }],
routerInitialEntries: ["/posts"],
}),
},
);
await act(async () => {
fireEvent.click(getByText("Create"));
});
expect(window.location.pathname).toBe("/posts/create");
});
it("should redirect with custom route called function successfully if click the button", async () => {
const { getByText } = render(
<Routes>
<Route
path="/:resource"
element={
<CreateButton resourceNameOrRouteName="custom-route-posts" />
}
/>
</Routes>,
{
wrapper: TestWrapper({
resources: [
{
name: "posts",
meta: { route: "custom-route-posts" },
},
{ name: "posts" },
],
routerInitialEntries: ["/posts"],
}),
},
);
await act(async () => {
fireEvent.click(getByText("Create"));
});
expect(window.location.pathname).toBe("/custom-route-posts/create");
});
});
};

View File

@@ -0,0 +1,593 @@
import React from "react";
import {
type RefineDeleteButtonProps,
RefineButtonTestIds,
} from "@refinedev/ui-types";
import {
act,
fireEvent,
MockJSONServer,
render,
TestWrapper,
waitFor,
} from "@test";
import { Route, Routes } from "react-router-dom";
export const buttonDeleteTests = (
DeleteButton: React.ComponentType<RefineDeleteButtonProps<any, any>>,
): void => {
describe("[@refinedev/ui-tests] Common Tests / Delete Button", () => {
beforeAll(() => {
jest.spyOn(console, "error").mockImplementation(jest.fn());
jest.clearAllTimers();
});
it("should render button successfuly", async () => {
const { container, getByText } = render(<DeleteButton />, {
wrapper: TestWrapper({}),
});
expect(container).toBeTruthy();
expect(getByText("Delete").closest("button")).not.toBeDisabled();
});
it("should have the correct test-id", async () => {
const { getByTestId } = render(<DeleteButton />, {
wrapper: TestWrapper({}),
});
getByTestId(RefineButtonTestIds.DeleteButton);
});
it("should render text by children", async () => {
const { container, getByText } = render(
<DeleteButton>refine</DeleteButton>,
{
wrapper: TestWrapper({}),
},
);
expect(container).toBeTruthy();
getByText("refine");
});
it("should render without text show only icon", async () => {
const { container, queryByText } = render(<DeleteButton hideText />, {
wrapper: TestWrapper({}),
});
expect(container).toBeTruthy();
expect(queryByText("Delete")).not.toBeInTheDocument();
});
describe("access control", () => {
describe("with global access control only", () => {
describe("with default behaviour", () => {
describe("when user not have access", () => {
it("should render disabled button with reason text", async () => {
const { container, getByTestId } = render(
<DeleteButton recordItemId="1">Delete</DeleteButton>,
{
wrapper: TestWrapper({
accessControlProvider: {
can: async ({ params, action }) => {
if (action === "delete" && params?.id === "1") {
return {
can: false,
reason: "Access Denied",
};
}
return {
can: true,
};
},
},
}),
},
);
expect(container).toBeTruthy();
await waitFor(() =>
expect(
getByTestId(RefineButtonTestIds.DeleteButton).closest(
"button",
),
).toBeDisabled(),
);
await waitFor(() =>
expect(
getByTestId(RefineButtonTestIds.DeleteButton)
.closest("button")
?.getAttribute("title"),
).toBe("Access Denied"),
);
});
});
describe("when user have access", () => {
it("should render enabled button", async () => {
const { container, getByTestId } = render(
<DeleteButton recordItemId="2">Delete</DeleteButton>,
{
wrapper: TestWrapper({
accessControlProvider: {
can: async ({ params, action }) => {
if (action === "delete" && params?.id === "1") {
return {
can: false,
};
}
return {
can: true,
};
},
},
}),
},
);
expect(container).toBeTruthy();
await waitFor(() =>
expect(
getByTestId(RefineButtonTestIds.DeleteButton).closest(
"button",
),
).not.toBeDisabled(),
);
});
});
});
describe("when hideIfUnauthorized is true", () => {
it("should not render button", async () => {
const { container, queryByText } = render(
<DeleteButton>Delete</DeleteButton>,
{
wrapper: TestWrapper({
accessControlProvider: {
can: async () => ({ can: false }),
options: {
buttons: {
hideIfUnauthorized: true,
},
},
},
}),
},
);
expect(container).toBeTruthy();
expect(queryByText("Delete")).not.toBeInTheDocument();
});
});
describe("when access control is disabled explicitly", () => {
it("should render enabled button", async () => {
const { container, getByTestId } = render(
<DeleteButton>Delete</DeleteButton>,
{
wrapper: TestWrapper({
accessControlProvider: {
can: async () => ({ can: false }),
options: {
buttons: {
enableAccessControl: false,
hideIfUnauthorized: true,
},
},
},
}),
},
);
expect(container).toBeTruthy();
expect(
getByTestId(RefineButtonTestIds.DeleteButton).closest("button"),
).not.toBeDisabled();
});
});
});
describe("with global config and accessControl prop", () => {
describe("when access control enabled globally", () => {
describe("when access control is disabled with prop", () => {
it("should render enabled button", async () => {
const { container, getByTestId } = render(
<DeleteButton accessControl={{ enabled: false }}>
Delete
</DeleteButton>,
{
wrapper: TestWrapper({
accessControlProvider: {
can: async () => {
return {
can: false,
};
},
options: {
buttons: {
enableAccessControl: true,
hideIfUnauthorized: true,
},
},
},
}),
},
);
expect(container).toBeTruthy();
await waitFor(() =>
expect(
getByTestId(RefineButtonTestIds.DeleteButton).closest(
"button",
),
).not.toBeDisabled(),
);
});
});
describe("when hideIfUnauthorized false globally", () => {
describe("when hideIfUnauthorized enabled with prop", () => {
it("should not render button", async () => {
const { container, queryByText } = render(
<DeleteButton
accessControl={{
hideIfUnauthorized: true,
}}
>
Delete
</DeleteButton>,
{
wrapper: TestWrapper({
accessControlProvider: {
can: async () => ({
can: false,
}),
options: {
buttons: {
hideIfUnauthorized: false,
},
},
},
}),
},
);
expect(container).toBeTruthy();
expect(queryByText("Delete")).not.toBeInTheDocument();
});
});
});
});
describe("when access control disabled globally", () => {
describe("when access control enabled with prop", () => {
it("should render disabled button with reason text", async () => {
const { container, getByTestId } = render(
<DeleteButton accessControl={{ enabled: true }}>
Delete
</DeleteButton>,
{
wrapper: TestWrapper({
accessControlProvider: {
can: async () => {
return {
can: false,
reason: "Access Denied",
};
},
},
}),
},
);
expect(container).toBeTruthy();
await waitFor(() =>
expect(
getByTestId(RefineButtonTestIds.DeleteButton).closest(
"button",
),
).toBeDisabled(),
);
await waitFor(() =>
expect(
getByTestId(RefineButtonTestIds.DeleteButton)
.closest("button")
?.getAttribute("title"),
).toBe("Access Denied"),
);
});
});
});
describe("when hideIfUnauthorized enabled globally", () => {
describe("when hideIfUnauthorized disabled with prop", () => {
it("should render button", async () => {
const { container, queryByText } = render(
<DeleteButton
accessControl={{
hideIfUnauthorized: false,
}}
>
Delete
</DeleteButton>,
{
wrapper: TestWrapper({
accessControlProvider: {
can: async () => ({
can: false,
}),
options: {
buttons: {
hideIfUnauthorized: true,
},
},
},
}),
},
);
expect(container).toBeTruthy();
expect(queryByText("Delete")).toBeInTheDocument();
});
});
});
});
});
it("should render called function successfully if click the button", async () => {
const deleteFunc = jest.fn();
const { getByTestId } = render(
<DeleteButton onClick={() => deleteFunc()} />,
{
wrapper: TestWrapper({}),
},
);
await act(async () => {
fireEvent.click(getByTestId(RefineButtonTestIds.DeleteButton));
});
expect(deleteFunc).toHaveBeenCalledTimes(1);
});
it("should render Popconfirm successfuly", async () => {
const { getByText, getAllByText, getByTestId } = render(
<DeleteButton resourceNameOrRouteName="posts" recordItemId="1" />,
{
wrapper: TestWrapper({}),
},
);
await act(async () => {
fireEvent.click(getByTestId(RefineButtonTestIds.DeleteButton));
});
getByText("Are you sure?");
getByText("Cancel");
getAllByText("Delete");
});
it("should confirm Popconfirm successfuly", async () => {
const deleteOneMock = jest.fn();
const { getByText, getAllByText, getByTestId } = render(
<DeleteButton resourceNameOrRouteName="posts" recordItemId="1" />,
{
wrapper: TestWrapper({
dataProvider: {
...MockJSONServer,
deleteOne: deleteOneMock,
},
}),
},
);
await act(async () => {
fireEvent.click(getByTestId(RefineButtonTestIds.DeleteButton));
});
getByText("Are you sure?");
getByText("Cancel");
const deleteButtons = getAllByText("Delete");
await act(async () => {
fireEvent.click(deleteButtons[1]);
});
expect(deleteOneMock).toBeCalledTimes(1);
});
it("should confirm Popconfirm successfuly with recordItemId", async () => {
const deleteOneMock = jest.fn();
const { getByText, getAllByText, getByTestId } = render(
<DeleteButton
recordItemId="record-id"
resourceNameOrRouteName="posts"
/>,
{
wrapper: TestWrapper({
dataProvider: {
...MockJSONServer,
deleteOne: deleteOneMock,
},
}),
},
);
await act(async () => {
fireEvent.click(getByTestId(RefineButtonTestIds.DeleteButton));
});
getByText("Are you sure?");
getByText("Cancel");
const deleteButtons = getAllByText("Delete");
await act(async () => {
fireEvent.click(deleteButtons[1]);
});
expect(deleteOneMock).toBeCalledWith(
expect.objectContaining({ id: "record-id" }),
);
});
it("should confirm Popconfirm successfuly with onSuccess", async () => {
const deleteOneMock = jest.fn();
const onSuccessMock = jest.fn();
const { getByText, getAllByText, getByTestId } = render(
<Routes>
<Route
path="/:resource/:action/:id"
element={<DeleteButton onSuccess={onSuccessMock} />}
/>
</Routes>,
{
wrapper: TestWrapper({
dataProvider: {
...MockJSONServer,
deleteOne: deleteOneMock,
},
routerInitialEntries: ["/posts/edit/1"],
}),
},
);
await act(async () => {
fireEvent.click(getByTestId(RefineButtonTestIds.DeleteButton));
});
getByText("Are you sure?");
getByText("Cancel");
const deleteButtons = getAllByText("Delete");
await act(async () => {
fireEvent.click(deleteButtons[1]);
});
expect(deleteOneMock).toBeCalledTimes(1);
expect(onSuccessMock).toBeCalledTimes(1);
});
it("should confirm Popconfirm successfuly with onSuccess", async () => {
const deleteOneMock = jest.fn();
const onSuccessMock = jest.fn();
const { getByText, getByTestId } = render(
<Routes>
<Route
path="/:resource/:action/:id"
element={
<DeleteButton
onSuccess={onSuccessMock}
confirmOkText="confirmOkText"
confirmCancelText="confirmCancelText"
confirmTitle="confirmTitle"
/>
}
/>
</Routes>,
{
wrapper: TestWrapper({
dataProvider: {
...MockJSONServer,
deleteOne: deleteOneMock,
},
routerInitialEntries: ["/posts/edit/1"],
}),
},
);
await act(async () => {
fireEvent.click(getByTestId(RefineButtonTestIds.DeleteButton));
});
getByText("confirmTitle");
getByText("confirmOkText");
getByText("confirmCancelText");
await act(async () => {
fireEvent.click(getByText("confirmOkText"));
});
expect(deleteOneMock).toBeCalledTimes(1);
expect(onSuccessMock).toBeCalledTimes(1);
});
it("should render with custom mutationMode", async () => {
const { getByTestId } = render(
<Routes>
<Route
path="/:resource"
element={<DeleteButton mutationMode="pessimistic" />}
/>
</Routes>,
{
wrapper: TestWrapper({
resources: [{ name: "posts" }],
routerInitialEntries: ["/posts"],
}),
},
);
getByTestId(RefineButtonTestIds.DeleteButton);
});
it("should render with custom resource", async () => {
const { getByTestId } = render(
<Routes>
<Route
path="/:resource"
element={<DeleteButton resourceNameOrRouteName="categories" />}
/>
</Routes>,
{
wrapper: TestWrapper({
resources: [{ name: "posts" }, { name: "categories" }],
routerInitialEntries: ["/posts"],
}),
},
);
getByTestId(RefineButtonTestIds.DeleteButton);
});
it("should render with resourceNameOrRouteName", async () => {
const { getByTestId } = render(
<Routes>
<Route
path="/:resource"
element={<DeleteButton resourceNameOrRouteName="users" />}
/>
</Routes>,
{
wrapper: TestWrapper({
resources: [{ name: "posts" }, { name: "users" }],
routerInitialEntries: ["/posts"],
}),
},
);
getByTestId(RefineButtonTestIds.DeleteButton);
});
});
};

View File

@@ -0,0 +1,393 @@
import React from "react";
import {
type RefineEditButtonProps,
RefineButtonTestIds,
} from "@refinedev/ui-types";
import { act, fireEvent, render, TestWrapper, waitFor } from "@test";
import { Route, Routes } from "react-router-dom";
export const buttonEditTests = (
EditButton: React.ComponentType<RefineEditButtonProps<any, any>>,
): void => {
describe("[@refinedev/ui-tests] Common Tests / Edit Button", () => {
const edit = jest.fn();
beforeAll(() => {
jest.spyOn(console, "warn").mockImplementation(jest.fn());
});
it("should render button successfuly", async () => {
const { container, getByText } = render(<EditButton />, {
wrapper: TestWrapper({}),
});
expect(container).toBeTruthy();
expect(getByText("Edit").closest("button")).not.toBeDisabled();
});
it("should have the correct test-id", async () => {
const { queryByTestId } = render(<EditButton />, {
wrapper: TestWrapper({}),
});
expect(queryByTestId(RefineButtonTestIds.EditButton)).toBeTruthy();
});
it("should render text by children", async () => {
const { container, getByText } = render(<EditButton>refine</EditButton>, {
wrapper: TestWrapper({}),
});
expect(container).toBeTruthy();
getByText("refine");
});
it("should render without text show only icon", async () => {
const { container, queryByText } = render(<EditButton hideText />, {
wrapper: TestWrapper({}),
});
expect(container).toBeTruthy();
expect(queryByText("Edit")).not.toBeInTheDocument();
});
describe("access control", () => {
describe("with global access control only", () => {
describe("with default behaviour", () => {
describe("when user not have access", () => {
it("should render disabled button with reason text", async () => {
const { container, getByText } = render(
<EditButton recordItemId="1">Edit</EditButton>,
{
wrapper: TestWrapper({
accessControlProvider: {
can: async ({ params, action }) => {
if (action === "edit" && params?.id === "1") {
return {
can: false,
reason: "Access Denied",
};
}
return {
can: true,
};
},
},
}),
},
);
expect(container).toBeTruthy();
await waitFor(() =>
expect(getByText("Edit").closest("button")).toBeDisabled(),
);
waitFor(() =>
expect(
getByText("Edit").closest("button")?.getAttribute("title"),
).toBe("Access Denied"),
);
});
});
describe("when user have access", () => {
it("should render enabled button", async () => {
const { container, getByText } = render(
<EditButton recordItemId="2">Edit</EditButton>,
{
wrapper: TestWrapper({
accessControlProvider: {
can: async ({ params, action }) => {
if (action === "edit" && params?.id === "1") {
return {
can: false,
};
}
return {
can: true,
};
},
},
}),
},
);
expect(container).toBeTruthy();
await waitFor(() =>
expect(getByText("Edit").closest("button")).not.toBeDisabled(),
);
});
});
});
describe("when hideIfUnauthorized is true", () => {
it("should not render button", async () => {
const { container, queryByText } = render(
<EditButton>Edit</EditButton>,
{
wrapper: TestWrapper({
accessControlProvider: {
can: async () => ({ can: false }),
options: {
buttons: {
hideIfUnauthorized: true,
},
},
},
}),
},
);
expect(container).toBeTruthy();
expect(queryByText("Edit")).not.toBeInTheDocument();
});
});
describe("when access control is disabled explicitly", () => {
it("should render enabled button", async () => {
const { container, getByText } = render(
<EditButton>Edit</EditButton>,
{
wrapper: TestWrapper({
accessControlProvider: {
can: async () => ({ can: false }),
options: {
buttons: {
enableAccessControl: false,
hideIfUnauthorized: true,
},
},
},
}),
},
);
expect(container).toBeTruthy();
expect(getByText("Edit").closest("button")).not.toBeDisabled();
});
});
});
describe("with global config and accessControl prop", () => {
describe("when access control enabled globally", () => {
describe("when access control is disabled with prop", () => {
it("should render enabled button", async () => {
const { container, getByText } = render(
<EditButton accessControl={{ enabled: false }}>
Edit
</EditButton>,
{
wrapper: TestWrapper({
accessControlProvider: {
can: async () => {
return {
can: false,
};
},
options: {
buttons: {
enableAccessControl: true,
hideIfUnauthorized: true,
},
},
},
}),
},
);
expect(container).toBeTruthy();
await waitFor(() =>
expect(getByText("Edit").closest("button")).not.toBeDisabled(),
);
});
});
describe("when hideIfUnauthorized false globally", () => {
describe("when hideIfUnauthorized enabled with prop", () => {
it("should not render button", async () => {
const { container, queryByText } = render(
<EditButton
accessControl={{
hideIfUnauthorized: true,
}}
>
Edit
</EditButton>,
{
wrapper: TestWrapper({
accessControlProvider: {
can: async () => ({
can: false,
}),
options: {
buttons: {
hideIfUnauthorized: false,
},
},
},
}),
},
);
expect(container).toBeTruthy();
expect(queryByText("Edit")).not.toBeInTheDocument();
});
});
});
});
describe("when access control disabled globally", () => {
describe("when access control enabled with prop", () => {
it("should render disabled button with reason text", async () => {
const { container, getByText } = render(
<EditButton accessControl={{ enabled: true }}>Edit</EditButton>,
{
wrapper: TestWrapper({
accessControlProvider: {
can: async () => {
return {
can: false,
reason: "Access Denied",
};
},
},
}),
},
);
expect(container).toBeTruthy();
await waitFor(() =>
expect(getByText("Edit").closest("button")).toBeDisabled(),
);
waitFor(() =>
expect(
getByText("Edit").closest("button")?.getAttribute("title"),
).toBe("Access Denied"),
);
});
});
});
describe("when hideIfUnauthorized enabled globally", () => {
describe("when hideIfUnauthorized disabled with prop", () => {
it("should render button", async () => {
const { container, queryByText } = render(
<EditButton
accessControl={{
hideIfUnauthorized: false,
}}
>
Edit
</EditButton>,
{
wrapper: TestWrapper({
accessControlProvider: {
can: async () => ({
can: false,
}),
options: {
buttons: {
hideIfUnauthorized: true,
},
},
},
}),
},
);
expect(container).toBeTruthy();
expect(queryByText("Edit")).toBeInTheDocument();
});
});
});
});
});
it("should render called function successfully if click the button", async () => {
const { getByText } = render(<EditButton onClick={() => edit()} />, {
wrapper: TestWrapper({}),
});
await act(async () => {
fireEvent.click(getByText("Edit"));
});
expect(edit).toHaveBeenCalledTimes(1);
});
it("should custom resource and recordItemId redirect show route called function successfully if click the button", async () => {
const { getByText } = render(
<Routes>
<Route
path="/:resource"
element={
<EditButton
resourceNameOrRouteName="categories"
recordItemId="1"
/>
}
/>
</Routes>,
{
wrapper: TestWrapper({
resources: [{ name: "posts" }, { name: "categories" }],
routerInitialEntries: ["/posts"],
}),
},
);
await act(async () => {
fireEvent.click(getByText("Edit"));
});
expect(window.location.pathname).toBe("/categories/edit/1");
});
it("should redirect with custom route called function successfully if click the button", async () => {
const { getByText } = render(
<Routes>
<Route
path="/:resource"
element={
<EditButton
resourceNameOrRouteName="custom-route-posts"
recordItemId={1}
/>
}
/>
</Routes>,
{
wrapper: TestWrapper({
resources: [
{
name: "posts",
meta: { route: "custom-route-posts" },
},
{ name: "posts" },
],
routerInitialEntries: ["/posts"],
}),
},
);
await act(async () => {
fireEvent.click(getByText("Edit"));
});
expect(window.location.pathname).toBe("/custom-route-posts/edit/1");
});
});
};

View File

@@ -0,0 +1,55 @@
import React from "react";
import {
type RefineExportButtonProps,
RefineButtonTestIds,
} from "@refinedev/ui-types";
import { act, render, TestWrapper } from "@test";
export const buttonExportTests = (
ExportButton: React.ComponentType<RefineExportButtonProps<any, any>>,
): void => {
describe("[@refinedev/ui-tests] Common Tests / Export Button", () => {
beforeAll(() => {
jest.spyOn(console, "warn").mockImplementation(jest.fn());
});
it("should render button successfuly", async () => {
const { container, getByText } = render(<ExportButton />, {
wrapper: TestWrapper({}),
});
expect(container).toBeTruthy();
getByText("Export");
});
it("should have the correct test-id", async () => {
const { queryByTestId } = render(<ExportButton />, {
wrapper: TestWrapper({}),
});
expect(queryByTestId(RefineButtonTestIds.ExportButton)).toBeTruthy();
});
it("should render text by children", async () => {
const { container, getByText } = render(
<ExportButton>refine</ExportButton>,
{
wrapper: TestWrapper({}),
},
);
expect(container).toBeTruthy();
getByText("refine");
});
it("should render without text show only icon", async () => {
const { container, queryByText } = render(<ExportButton hideText />);
expect(container).toBeTruthy();
expect(queryByText("Export")).not.toBeInTheDocument();
});
});
};

View File

@@ -0,0 +1,60 @@
import React from "react";
import {
type RefineImportButtonProps,
RefineButtonTestIds,
} from "@refinedev/ui-types";
import { act, render, TestWrapper } from "@test";
export const buttonImportTests = (
ImportButton: React.ComponentType<RefineImportButtonProps<any, any>>,
): void => {
describe("[@refinedev/ui-tests] Common Tests / Import Button", () => {
const parseMock = jest.fn();
beforeAll(() => {
jest.mock("papaparse", () => {
return {
parse: jest.fn(() => parseMock()),
};
});
});
it("should render button successfuly", async () => {
const { container, getByText } = render(<ImportButton />, {
wrapper: TestWrapper({}),
});
expect(container).toBeTruthy();
getByText("Import");
});
it("should have the correct test-id", async () => {
const { queryByTestId } = render(<ImportButton />, {
wrapper: TestWrapper({}),
});
expect(queryByTestId(RefineButtonTestIds.ImportButton)).toBeTruthy();
});
it("should render text by children", async () => {
const { container, getByText } = render(
<ImportButton>refine</ImportButton>,
{
wrapper: TestWrapper({}),
},
);
expect(container).toBeTruthy();
getByText("refine");
});
it("should render without text show only icon", async () => {
const { container, queryByText } = render(<ImportButton hideText />);
expect(container).toBeTruthy();
expect(queryByText("Import")).not.toBeInTheDocument();
});
});
};

View File

@@ -0,0 +1,10 @@
export { buttonCloneTests } from "./clone";
export { buttonCreateTests } from "./create";
export { buttonDeleteTests } from "./delete";
export { buttonEditTests } from "./edit";
export { buttonExportTests } from "./export";
export { buttonImportTests } from "./import";
export { buttonListTests } from "./list";
export { buttonRefreshTests } from "./refresh";
export { buttonSaveTests } from "./save";
export { buttonShowTests } from "./show";

View File

@@ -0,0 +1,394 @@
import React from "react";
import {
type RefineListButtonProps,
RefineButtonTestIds,
} from "@refinedev/ui-types";
import { act, fireEvent, render, TestWrapper, waitFor } from "@test";
import { Route, Routes } from "react-router-dom";
export const buttonListTests = (
ListButton: React.ComponentType<RefineListButtonProps<any, any>>,
): void => {
describe("[@refinedev/ui-tests] Common Tests / List Button", () => {
const list = jest.fn();
it("should render button successfuly", async () => {
const { container, getByText } = render(<ListButton>List</ListButton>, {
wrapper: TestWrapper({}),
});
expect(container).toBeTruthy();
expect(getByText("List").closest("button")).not.toBeDisabled();
});
it("should have the correct test-id", async () => {
const { queryByTestId } = render(<ListButton />, {
wrapper: TestWrapper({}),
});
expect(queryByTestId(RefineButtonTestIds.ListButton)).toBeTruthy();
});
it("should render text by children", async () => {
const { container, getByText } = render(<ListButton>refine</ListButton>, {
wrapper: TestWrapper({}),
});
expect(container).toBeTruthy();
getByText("refine");
});
it("should render label as children if specified", async () => {
const { container, getByText } = render(
<Routes>
<Route path="/:resource" element={<ListButton />} />
</Routes>,
{
wrapper: TestWrapper({
resources: [{ name: "posts", meta: { label: "test" } }],
routerInitialEntries: ["/posts"],
}),
},
);
expect(container).toBeTruthy();
getByText("Tests");
});
it("should render text by children", async () => {
const { container, getByText } = render(<ListButton>refine</ListButton>, {
wrapper: TestWrapper({
resources: [{ name: "posts" }],
}),
});
expect(container).toBeTruthy();
getByText("refine");
});
it("should render without text show only icon", async () => {
const { container, queryByText } = render(<ListButton hideText />, {
wrapper: TestWrapper({
resources: [{ name: "posts" }],
}),
});
expect(container).toBeTruthy();
expect(queryByText("Posts")).not.toBeInTheDocument();
});
describe("access control", () => {
describe("with global access control only", () => {
describe("with default behaviour", () => {
describe("when user not have access", () => {
it("should render disabled button with reason text", async () => {
const { container, getByText } = render(
<ListButton>List</ListButton>,
{
wrapper: TestWrapper({
accessControlProvider: {
can: async ({ action }) => {
if (action === "list") {
return {
can: false,
reason: "Access Denied",
};
}
return {
can: true,
};
},
},
}),
},
);
expect(container).toBeTruthy();
await waitFor(() =>
expect(getByText("List").closest("button")).toBeDisabled(),
);
waitFor(() =>
expect(
getByText("List").closest("button")?.getAttribute("title"),
).toBe("Access Denied"),
);
});
});
describe("when user have access", () => {
it("should render enabled button", async () => {
const { container, getByText } = render(
<ListButton>List</ListButton>,
{
wrapper: TestWrapper({
accessControlProvider: {
can: async ({ action }) => {
if (action === "list") {
return {
can: false,
};
}
return {
can: true,
};
},
},
}),
},
);
expect(container).toBeTruthy();
await waitFor(() =>
expect(getByText("List").closest("button")).not.toBeDisabled(),
);
});
});
});
describe("when hideIfUnauthorized is true", () => {
it("should not render button", async () => {
const { container, queryByText } = render(
<ListButton>List</ListButton>,
{
wrapper: TestWrapper({
accessControlProvider: {
can: async () => ({ can: false }),
options: {
buttons: {
hideIfUnauthorized: true,
},
},
},
}),
},
);
expect(container).toBeTruthy();
expect(queryByText("List")).not.toBeInTheDocument();
});
});
describe("when access control is disabled explicitly", () => {
it("should render enabled button", async () => {
const { container, getByText } = render(
<ListButton>List</ListButton>,
{
wrapper: TestWrapper({
accessControlProvider: {
can: async () => ({ can: false }),
options: {
buttons: {
enableAccessControl: false,
hideIfUnauthorized: true,
},
},
},
}),
},
);
expect(container).toBeTruthy();
expect(getByText("List").closest("button")).not.toBeDisabled();
});
});
});
describe("with global config and accessControl prop", () => {
describe("when access control enabled globally", () => {
describe("when access control is disabled with prop", () => {
it("should render enabled button", async () => {
const { container, getByText } = render(
<ListButton accessControl={{ enabled: false }}>
List
</ListButton>,
{
wrapper: TestWrapper({
accessControlProvider: {
can: async () => {
return {
can: false,
};
},
options: {
buttons: {
enableAccessControl: true,
hideIfUnauthorized: true,
},
},
},
}),
},
);
expect(container).toBeTruthy();
await waitFor(() =>
expect(getByText("List").closest("button")).not.toBeDisabled(),
);
});
});
describe("when hideIfUnauthorized false globally", () => {
describe("when hideIfUnauthorized enabled with prop", () => {
it("should not render button", async () => {
const { container, queryByText } = render(
<ListButton
accessControl={{
hideIfUnauthorized: true,
}}
>
List
</ListButton>,
{
wrapper: TestWrapper({
accessControlProvider: {
can: async () => ({
can: false,
}),
options: {
buttons: {
hideIfUnauthorized: false,
},
},
},
}),
},
);
expect(container).toBeTruthy();
expect(queryByText("List")).not.toBeInTheDocument();
});
});
});
});
describe("when access control disabled globally", () => {
describe("when access control enabled with prop", () => {
it("should render disabled button with reason text", async () => {
const { container, getByText } = render(
<ListButton accessControl={{ enabled: true }}>List</ListButton>,
{
wrapper: TestWrapper({
accessControlProvider: {
can: async () => {
return {
can: false,
reason: "Access Denied",
};
},
},
}),
},
);
expect(container).toBeTruthy();
await waitFor(() =>
expect(getByText("List").closest("button")).toBeDisabled(),
);
waitFor(() =>
expect(
getByText("List").closest("button")?.getAttribute("title"),
).toBe("Access Denied"),
);
});
});
});
describe("when hideIfUnauthorized enabled globally", () => {
describe("when hideIfUnauthorized disabled with prop", () => {
it("should render button", async () => {
const { container, queryByText } = render(
<ListButton
accessControl={{
hideIfUnauthorized: false,
}}
>
List
</ListButton>,
{
wrapper: TestWrapper({
accessControlProvider: {
can: async () => ({
can: false,
}),
options: {
buttons: {
hideIfUnauthorized: true,
},
},
},
}),
},
);
expect(container).toBeTruthy();
expect(queryByText("List")).toBeInTheDocument();
});
});
});
});
});
it("should render called function successfully if click the button", async () => {
const { getByText } = render(
<ListButton onClick={() => list()} resourceNameOrRouteName="posts" />,
{
wrapper: TestWrapper({
resources: [{ name: "posts" }],
}),
},
);
await act(async () => {
fireEvent.click(getByText("Posts"));
});
expect(list).toHaveBeenCalledTimes(1);
});
it("should redirect with custom route called function successfully if click the button", async () => {
const { getByText } = render(
<Routes>
<Route
path="/:resource"
element={
<ListButton resourceNameOrRouteName="custom-route-posts" />
}
/>
</Routes>,
{
wrapper: TestWrapper({
resources: [
{
name: "posts",
options: { route: "custom-route-posts" },
},
{ name: "posts" },
],
routerInitialEntries: ["/posts"],
}),
},
);
await act(async () => {
fireEvent.click(getByText("Posts"));
});
expect(window.location.pathname).toBe("/custom-route-posts");
});
});
};

View File

@@ -0,0 +1,175 @@
import React from "react";
import {
type RefineRefreshButtonProps,
RefineButtonTestIds,
} from "@refinedev/ui-types";
import { act, fireEvent, render, TestWrapper, waitFor } from "@test";
import { Route, Routes } from "react-router-dom";
import "@refinedev/core";
const invalidateMock = jest.fn();
jest.mock("@refinedev/core", () => ({
__esModule: true,
...jest.requireActual("@refinedev/core"),
useInvalidate: () => {
return invalidateMock;
},
}));
export const buttonRefreshTests = (
RefreshButton: React.ComponentType<RefineRefreshButtonProps<any, any>>,
): void => {
describe("[@refinedev/ui-tests] Common Tests / Refresh Button", () => {
const refresh = jest.fn();
it("should render button successfuly", async () => {
const { container } = render(<RefreshButton />, {
wrapper: TestWrapper({}),
});
expect(container).toBeTruthy();
});
it("should have the correct test-id", async () => {
const { queryByTestId } = render(<RefreshButton />, {
wrapper: TestWrapper({}),
});
expect(queryByTestId(RefineButtonTestIds.RefreshButton)).toBeTruthy();
});
it("should render text by children", async () => {
const { container, getByText } = render(
<RefreshButton>refine</RefreshButton>,
{
wrapper: TestWrapper({}),
},
);
expect(container).toBeTruthy();
getByText("refine");
});
it("should render without text show only icon", async () => {
const { container, queryByText } = render(<RefreshButton hideText />, {
wrapper: TestWrapper({}),
});
expect(container).toBeTruthy();
expect(queryByText("Refresh")).not.toBeInTheDocument();
});
it("should render called function successfully if click the button", async () => {
const { getByText } = render(
<RefreshButton onClick={() => refresh()} />,
{
wrapper: TestWrapper({}),
},
);
await act(async () => {
fireEvent.click(getByText("Refresh"));
});
expect(refresh).toHaveBeenCalledTimes(1);
});
/**
* Previously `useInvalidate` was imported directly inside the UI packages,
* which then can be mocked and tested through jest.
* Now we've switched to `useRefreshButton` from `@refinedev/core`
* which calls `useInvalidate` internally.
* We can't test the internal function calls of `useInvalidate` anymore.
* Extensive tests on the logic of the `useRefreshButton` which powers the `RefreshButton` are already covered in the core package.
*/
xit("should invalidates when button is clicked", async () => {
jest.resetAllMocks();
jest.restoreAllMocks();
const { getByText } = render(
<Routes>
<Route
path="/posts/show/:id"
element={
<RefreshButton dataProviderName="default" resource="posts" />
}
/>
</Routes>,
{
wrapper: TestWrapper({
routerInitialEntries: ["/posts/show/1"],
resources: [
{
name: "posts",
show: "/posts/show/:id",
},
],
}),
},
);
const button = getByText("Refresh");
await act(async () => {
fireEvent.click(button);
});
await waitFor(() => {
expect(invalidateMock).toHaveBeenCalledTimes(1);
expect(invalidateMock).toHaveBeenCalledWith({
id: "1",
invalidates: ["detail"],
dataProviderName: "default",
resource: "posts",
});
});
});
it("should when onClick is not passed, NOT invalidates when button is clicked", async () => {
jest.resetAllMocks();
jest.restoreAllMocks();
const onClickMock = jest.fn();
const { getByText } = render(
<Routes>
<Route
path="/posts/show/:id"
element={
<RefreshButton
onClick={onClickMock}
dataProviderName="default"
resource="posts"
/>
}
/>
</Routes>,
{
wrapper: TestWrapper({
routerInitialEntries: ["/posts/show/1"],
resources: [
{
name: "posts",
show: "/posts/show/:id",
},
],
}),
},
);
const button = getByText("Refresh");
await act(async () => {
fireEvent.click(button);
});
await waitFor(() => {
expect(invalidateMock).toHaveBeenCalledTimes(0);
expect(onClickMock).toHaveBeenCalledTimes(1);
});
});
});
};

View File

@@ -0,0 +1,63 @@
import React from "react";
import {
type RefineSaveButtonProps,
RefineButtonTestIds,
} from "@refinedev/ui-types";
import { act, fireEvent, render, TestWrapper } from "@test";
export const buttonSaveTests = (
SaveButton: React.ComponentType<RefineSaveButtonProps<any, any>>,
): void => {
describe("[@refinedev/ui-tests] Common Tests / Save Button", () => {
const save = jest.fn();
it("should render button successfuly", async () => {
const { container } = render(<SaveButton />, {
wrapper: TestWrapper({}),
});
expect(container).toBeTruthy();
});
it("should have the correct test-id", async () => {
const { queryByTestId } = render(<SaveButton />, {
wrapper: TestWrapper({}),
});
expect(queryByTestId(RefineButtonTestIds.SaveButton)).toBeTruthy();
});
it("should render text by children", async () => {
const { container, getByText } = render(<SaveButton>refine</SaveButton>, {
wrapper: TestWrapper({}),
});
expect(container).toBeTruthy();
getByText("refine");
});
it("should render without text show only icon", async () => {
const { container, queryByText } = render(<SaveButton hideText />, {
wrapper: TestWrapper({}),
});
expect(container).toBeTruthy();
expect(queryByText("Save")).not.toBeInTheDocument();
});
it("should render called function successfully if click the button", async () => {
const { getByText } = render(<SaveButton onClick={() => save()} />, {
wrapper: TestWrapper({}),
});
await act(async () => {
fireEvent.click(getByText("Save"));
});
expect(save).toHaveBeenCalledTimes(1);
});
});
};

View File

@@ -0,0 +1,433 @@
import React from "react";
import {
type RefineShowButtonProps,
RefineButtonTestIds,
} from "@refinedev/ui-types";
import { act, fireEvent, render, TestWrapper, waitFor } from "@test";
import { Route, Routes } from "react-router-dom";
export const buttonShowTests = (
ShowButton: React.ComponentType<RefineShowButtonProps<any, any>>,
): void => {
describe("[@refinedev/ui-tests] Common Tests / Show Button", () => {
const show = jest.fn();
beforeAll(() => {
jest.spyOn(console, "warn").mockImplementation(jest.fn());
});
it("should render button successfuly", async () => {
const { container, getByText } = render(<ShowButton />, {
wrapper: TestWrapper({}),
});
expect(container).toBeTruthy();
expect(getByText("Show").closest("button")).not.toBeDisabled();
});
it("should have the correct test-id", async () => {
const { queryByTestId } = render(<ShowButton />, {
wrapper: TestWrapper({}),
});
expect(queryByTestId(RefineButtonTestIds.ShowButton)).toBeTruthy();
});
it("should render text by children", async () => {
const { container, getByText } = render(<ShowButton>refine</ShowButton>, {
wrapper: TestWrapper({}),
});
expect(container).toBeTruthy();
getByText("refine");
});
it("should render without text show only icon", async () => {
const { container, queryByText } = render(<ShowButton hideText />, {
wrapper: TestWrapper({}),
});
expect(container).toBeTruthy();
expect(queryByText("Show")).not.toBeInTheDocument();
});
describe("access control", () => {
describe("with global access control only", () => {
describe("with default behaviour", () => {
describe("when user not have access", () => {
it("should render disabled button with reason text", async () => {
const { container, getByText } = render(
<ShowButton recordItemId="1">Show</ShowButton>,
{
wrapper: TestWrapper({
accessControlProvider: {
can: async ({ params, action }) => {
if (action === "show" && params?.id === "1") {
return {
can: false,
reason: "Access Denied",
};
}
return {
can: true,
};
},
},
}),
},
);
expect(container).toBeTruthy();
await waitFor(() =>
expect(getByText("Show").closest("button")).toBeDisabled(),
);
waitFor(() =>
expect(
getByText("Show").closest("button")?.getAttribute("title"),
).toBe("Access Denied"),
);
});
});
describe("when user have access", () => {
it("should render enabled button", async () => {
const { container, getByText } = render(
<ShowButton recordItemId="2">Show</ShowButton>,
{
wrapper: TestWrapper({
accessControlProvider: {
can: async ({ params, action }) => {
if (action === "show" && params?.id === "1") {
return {
can: false,
};
}
return {
can: true,
};
},
},
}),
},
);
expect(container).toBeTruthy();
await waitFor(() =>
expect(getByText("Show").closest("button")).not.toBeDisabled(),
);
});
});
});
describe("when hideIfUnauthorized is true", () => {
it("should not render button", async () => {
const { container, queryByText } = render(
<ShowButton>Show</ShowButton>,
{
wrapper: TestWrapper({
accessControlProvider: {
can: async () => ({ can: false }),
options: {
buttons: {
hideIfUnauthorized: true,
},
},
},
}),
},
);
expect(container).toBeTruthy();
expect(queryByText("Show")).not.toBeInTheDocument();
});
});
describe("when access control is disabled explicitly", () => {
it("should render enabled button", async () => {
const { container, getByText } = render(
<ShowButton>Show</ShowButton>,
{
wrapper: TestWrapper({
accessControlProvider: {
can: async () => ({ can: false }),
options: {
buttons: {
enableAccessControl: false,
hideIfUnauthorized: true,
},
},
},
}),
},
);
expect(container).toBeTruthy();
expect(getByText("Show").closest("button")).not.toBeDisabled();
});
});
});
describe("with global config and accessControl prop", () => {
describe("when access control enabled globally", () => {
describe("when access control is disabled with prop", () => {
it("should render enabled button", async () => {
const { container, getByText } = render(
<ShowButton accessControl={{ enabled: false }}>
Show
</ShowButton>,
{
wrapper: TestWrapper({
accessControlProvider: {
can: async () => {
return {
can: false,
};
},
options: {
buttons: {
enableAccessControl: true,
hideIfUnauthorized: true,
},
},
},
}),
},
);
expect(container).toBeTruthy();
await waitFor(() =>
expect(getByText("Show").closest("button")).not.toBeDisabled(),
);
});
});
describe("when hideIfUnauthorized false globally", () => {
describe("when hideIfUnauthorized enabled with prop", () => {
it("should not render button", async () => {
const { container, queryByText } = render(
<ShowButton
accessControl={{
hideIfUnauthorized: true,
}}
>
Show
</ShowButton>,
{
wrapper: TestWrapper({
accessControlProvider: {
can: async () => ({
can: false,
}),
options: {
buttons: {
hideIfUnauthorized: false,
},
},
},
}),
},
);
expect(container).toBeTruthy();
expect(queryByText("Show")).not.toBeInTheDocument();
});
});
});
});
describe("when access control disabled globally", () => {
describe("when access control enabled with prop", () => {
it("should render disabled button with reason text", async () => {
const { container, getByText } = render(
<ShowButton accessControl={{ enabled: true }}>Show</ShowButton>,
{
wrapper: TestWrapper({
accessControlProvider: {
can: async () => {
return {
can: false,
reason: "Access Denied",
};
},
},
}),
},
);
expect(container).toBeTruthy();
await waitFor(() =>
expect(getByText("Show").closest("button")).toBeDisabled(),
);
waitFor(() =>
expect(
getByText("Show").closest("button")?.getAttribute("title"),
).toBe("Access Denied"),
);
});
});
});
describe("when hideIfUnauthorized enabled globally", () => {
describe("when hideIfUnauthorized disabled with prop", () => {
it("should render button", async () => {
const { container, queryByText } = render(
<ShowButton
accessControl={{
hideIfUnauthorized: false,
}}
>
Show
</ShowButton>,
{
wrapper: TestWrapper({
accessControlProvider: {
can: async () => ({
can: false,
}),
options: {
buttons: {
hideIfUnauthorized: true,
},
},
},
}),
},
);
expect(container).toBeTruthy();
expect(queryByText("Show")).toBeInTheDocument();
});
});
});
});
});
it("should render called function successfully if click the button", async () => {
const { getByText } = render(<ShowButton onClick={() => show()} />, {
wrapper: TestWrapper({}),
});
await act(async () => {
fireEvent.click(getByText("Show"));
});
expect(show).toHaveBeenCalledTimes(1);
});
it("should create page redirect show route called function successfully if click the button", async () => {
const { getByText } = render(
<Routes>
<Route path="/:resource" element={<ShowButton recordItemId="1" />} />
</Routes>,
{
wrapper: TestWrapper({
resources: [{ name: "posts" }],
routerInitialEntries: ["/posts"],
}),
},
);
await act(async () => {
fireEvent.click(getByText("Show"));
});
expect(window.location.pathname).toBe("/posts/show/1");
});
it("should show page redirect show route called function successfully if click the button", async () => {
const { getByText } = render(
<Routes>
<Route path="/:resource/:action/:id" element={<ShowButton />} />
</Routes>,
{
wrapper: TestWrapper({
resources: [{ name: "posts" }],
routerInitialEntries: ["/posts/show/1"],
}),
},
);
await act(async () => {
fireEvent.click(getByText("Show"));
});
expect(window.location.pathname).toBe("/posts/show/1");
});
it("should custom resource and recordItemId redirect show route called function successfully if click the button", async () => {
const { getByText } = render(
<Routes>
<Route
path="/:resource"
element={
<ShowButton
resourceNameOrRouteName="categories"
recordItemId="1"
/>
}
/>
</Routes>,
{
wrapper: TestWrapper({
resources: [{ name: "posts" }, { name: "categories" }],
routerInitialEntries: ["/posts"],
}),
},
);
await act(async () => {
fireEvent.click(getByText("Show"));
});
expect(window.location.pathname).toBe("/categories/show/1");
});
it("should redirect with custom route called function successfully if click the button", async () => {
const { getByText } = render(
<Routes>
<Route
path="/:resource"
element={
<ShowButton
resourceNameOrRouteName="custom-route-posts"
recordItemId="1"
/>
}
/>
</Routes>,
{
wrapper: TestWrapper({
resources: [
{
name: "posts",
meta: { route: "custom-route-posts" },
},
{ name: "posts" },
],
routerInitialEntries: ["/posts"],
}),
},
);
await act(async () => {
fireEvent.click(getByText("Show"));
});
expect(window.location.pathname).toBe("/custom-route-posts/show/1");
});
});
};

View File

@@ -0,0 +1,117 @@
import React from "react";
import { Route, Routes } from "react-router-dom";
import {
type RefineCrudCreateProps,
RefineButtonTestIds,
} from "@refinedev/ui-types";
import { type ITestWrapperProps, render, TestWrapper } from "@test";
const renderCreate = (
create: React.ReactNode,
wrapperProps?: ITestWrapperProps,
) => {
return render(
<Routes>
<Route path="/:resource/:action" element={create} />
</Routes>,
{
wrapper: TestWrapper(
wrapperProps
? wrapperProps
: {
routerInitialEntries: ["/posts/create"],
},
),
},
);
};
export const crudCreateTests = (
Create: React.ComponentType<
RefineCrudCreateProps<any, any, any, any, any, any, {}>
>,
): void => {
describe("[@refinedev/ui-tests] Common Tests / CRUD Create", () => {
beforeAll(() => {
jest.spyOn(console, "warn").mockImplementation(jest.fn());
});
it("should render children", async () => {
const { getByText } = renderCreate(<Create>Something</Create>);
getByText("Something");
});
it("should render default save button successfuly", async () => {
const { queryByTestId } = renderCreate(<Create />);
expect(queryByTestId(RefineButtonTestIds.SaveButton)).not.toBeNull();
});
it("should render optional button with actionButtons prop", async () => {
const { container, getByText } = renderCreate(
<Create footerButtons={<button>Optional Button</button>} />,
);
expect(container.querySelector("button")).toBeTruthy();
getByText("Optional Button");
});
it("should render default title successfuly", async () => {
const { getByText } = renderCreate(<Create />);
getByText("Create Post");
});
it("should not render title when is false", async () => {
const { queryByText } = renderCreate(<Create title={false} />);
const text = queryByText("Create Post");
expect(text).not.toBeInTheDocument();
});
it("should render with label instead of resource name successfully", async () => {
const { getByText } = renderCreate(<Create />, {
resources: [
{
name: "posts",
meta: { route: "posts", label: "test label" },
},
],
routerInitialEntries: ["/posts/create"],
});
getByText("Create Test label");
});
it("should render optional title with title prop", async () => {
const { getByText } = renderCreate(<Create title="New Title" />);
getByText("New Title");
});
it("should render optional resource with resource prop", async () => {
const { queryByText } = renderCreate(<Create resource="posts" />, {
routerInitialEntries: ["/custom"],
});
queryByText("Create Post");
});
it("should render tags", async () => {
const { getByText } = render(
<Routes>
<Route path="/:resource/:action/:id" element={<Create />} />
</Routes>,
{
wrapper: TestWrapper({
routerInitialEntries: ["/posts/clone/1"],
}),
},
);
getByText("Create Post");
});
});
};

View File

@@ -0,0 +1,144 @@
import React from "react";
import { Route, Routes } from "react-router-dom";
import {
type RefineCrudEditProps,
RefineButtonTestIds,
} from "@refinedev/ui-types";
import type { AccessControlProvider } from "@refinedev/core";
import { type ITestWrapperProps, render, TestWrapper } from "@test";
const renderEdit = (
edit: React.ReactNode,
accessControlProvider?: AccessControlProvider,
wrapperProps?: ITestWrapperProps,
) => {
return render(
<Routes>
<Route path="/:resource/edit/:id" element={edit} />
</Routes>,
{
wrapper: TestWrapper(
wrapperProps
? wrapperProps
: {
routerInitialEntries: ["/posts/edit/1"],
accessControlProvider,
},
),
},
);
};
export const crudEditTests = (
Edit: React.ComponentType<
RefineCrudEditProps<any, any, any, any, any, any, any, {}, any, any>
>,
): void => {
describe("[@refinedev/ui-tests] Common Tests / CRUD Edit", () => {
beforeAll(() => {
jest.spyOn(console, "warn").mockImplementation(jest.fn());
});
it("should render children", async () => {
const { getByText } = renderEdit(<Edit>Something</Edit>);
getByText("Something");
});
it("should render default list button successfuly", async () => {
const { queryByTestId } = renderEdit(
<Edit
headerButtons={({ defaultButtons, listButtonProps }) => {
expect(listButtonProps).toBeDefined();
return <>{defaultButtons}</>;
}}
/>,
);
expect(queryByTestId(RefineButtonTestIds.ListButton)).not.toBeNull();
});
it("should render default save and delete buttons successfuly", async () => {
const { container, getByText } = renderEdit(
<Edit
canDelete
footerButtons={({
defaultButtons,
saveButtonProps,
deleteButtonProps,
}) => {
expect(saveButtonProps).toBeDefined();
expect(deleteButtonProps).toBeDefined();
return <>{defaultButtons}</>;
}}
/>,
);
expect(container.querySelector("button")).toBeTruthy();
getByText("Save");
getByText("Delete");
});
it("should render optional buttons with actionButtons prop", async () => {
const { getByText } = renderEdit(
<Edit
footerButtons={
<>
<button>New Save Button</button>
<button>New Delete Button</button>
</>
}
/>,
);
getByText("New Save Button");
getByText("New Delete Button");
});
it("should render default title successfuly", async () => {
const { getByText } = renderEdit(<Edit />);
getByText("Edit Post");
});
it("should not render title when is false", async () => {
const { queryByText } = renderEdit(<Edit title={false} />);
const text = queryByText("Edit Post");
expect(text).not.toBeInTheDocument();
});
it("should render custom title successfuly", async () => {
const { getByText } = renderEdit(<Edit title="Custom Title" />);
getByText("Custom Title");
});
it("should render optional recordItemId with resource prop", async () => {
const { getByText } = renderEdit(<Edit recordItemId="1" />);
getByText("Edit Post");
});
it("should render delete button ", async () => {
const { getByText, queryByTestId } = renderEdit(
<Edit
footerButtons={({ defaultButtons, deleteButtonProps }) => {
expect(deleteButtonProps).toBeDefined();
return <>{defaultButtons}</>;
}}
/>,
undefined,
{
resources: [{ name: "posts", canDelete: true }],
routerInitialEntries: ["/posts/edit/1"],
},
);
expect(queryByTestId(RefineButtonTestIds.DeleteButton)).not.toBeNull();
getByText("Edit Post");
});
});
};

View File

@@ -0,0 +1,4 @@
export * from "./list";
export * from "./create";
export * from "./edit";
export * from "./show";

View File

@@ -0,0 +1,229 @@
import React from "react";
import type { AccessControlProvider } from "@refinedev/core";
import { Route, Routes } from "react-router-dom";
import {
type RefineCrudListProps,
RefineButtonTestIds,
} from "@refinedev/ui-types";
import {
act,
type ITestWrapperProps,
render,
TestWrapper,
waitFor,
} from "@test";
const renderList = (
list: React.ReactNode,
accessControlProvider?: AccessControlProvider,
wrapperProps?: ITestWrapperProps,
) => {
return render(
<Routes>
<Route path="/:resource" element={list} />
</Routes>,
{
wrapper: TestWrapper(
wrapperProps
? wrapperProps
: {
routerInitialEntries: ["/posts"],
accessControlProvider,
},
),
},
);
};
export const crudListTests = (
List: React.ComponentType<RefineCrudListProps<any, any, any, any, any, {}>>,
): void => {
describe("[@refinedev/ui-tests] Common Tests / CRUD List", () => {
beforeAll(() => {
jest.spyOn(console, "warn").mockImplementation(jest.fn());
});
it("should render children", async () => {
const { getByText } = renderList(
<List key="posts">
<div>No Data</div>
</List>,
);
getByText("No Data");
});
it("should render optional title with title prop", async () => {
const { getByText } = renderList(<List title="New Title" />);
getByText("New Title");
});
it("should not render title when is false", async () => {
const { queryByText } = renderList(<List title={false} />, undefined, {
resources: [
{
name: "posts",
meta: { route: "posts" },
},
],
routerInitialEntries: ["/posts"],
});
const text = queryByText("Posts");
expect(text).not.toBeInTheDocument();
});
it("should render with label instead of resource name successfully", async () => {
const { getByText } = renderList(<List />, undefined, {
resources: [
{
name: "posts",
meta: { route: "posts", label: "test" },
},
],
routerInitialEntries: ["/posts"],
});
getByText("Tests");
});
it("should render create button", async () => {
const { queryByTestId } = renderList(
<List
headerButtons={({ defaultButtons, createButtonProps }) => {
expect(createButtonProps).toBeDefined();
return <>{defaultButtons}</>;
}}
/>,
undefined,
{
resources: [
{
name: "posts",
create: () => null,
},
],
routerInitialEntries: ["/posts"],
},
);
expect(queryByTestId(RefineButtonTestIds.CreateButton)).not.toBeNull();
});
it("should not render create button on resource#canCreate=false", async () => {
const { queryByTestId } = renderList(
<List
headerButtons={({ defaultButtons, createButtonProps }) => {
expect(createButtonProps).not.toBeDefined();
return <>{defaultButtons}</>;
}}
/>,
undefined,
{
resources: [
{
name: "posts",
canCreate: false,
},
],
routerInitialEntries: ["/posts"],
},
);
expect(queryByTestId(RefineButtonTestIds.CreateButton)).toBeNull();
});
it("should render create button on resource#canCreate=false & props#createButtonProps!=null", async () => {
const { getByText, queryByTestId } = renderList(
<List
createButtonProps={{ "aria-label": "Create Button" }}
headerButtons={({ defaultButtons, createButtonProps }) => {
expect(createButtonProps).toBeDefined();
return <>{defaultButtons}</>;
}}
/>,
undefined,
{ routerInitialEntries: ["/posts"] },
);
expect(queryByTestId(RefineButtonTestIds.CreateButton)).not.toBeNull();
getByText("Posts");
});
it("should not render create button on resource#canCreate=true & props#canCreate=false", async () => {
const { queryByTestId } = renderList(
<List
canCreate={false}
headerButtons={({ defaultButtons, createButtonProps }) => {
expect(createButtonProps).toBeUndefined();
return <>{defaultButtons}</>;
}}
/>,
undefined,
{
resources: [
{
name: "posts",
create: () => null,
},
],
routerInitialEntries: ["/posts"],
},
);
expect(queryByTestId(RefineButtonTestIds.CreateButton)).toBeNull();
});
it("should render create button on resource#canCreate=false & props#canCreate=true", async () => {
const { queryByTestId } = renderList(
<List
canCreate={true}
headerButtons={({ defaultButtons, createButtonProps }) => {
expect(createButtonProps).toBeDefined();
return <>{defaultButtons}</>;
}}
/>,
undefined,
{
resources: [
{
name: "posts",
},
],
routerInitialEntries: ["/posts"],
},
);
expect(queryByTestId(RefineButtonTestIds.CreateButton)).not.toBeNull();
});
it("should render disabled create button if user doesn't have permission", async () => {
const { queryByTestId } = renderList(
<List
canCreate={true}
headerButtons={({ defaultButtons, createButtonProps }) => {
expect(createButtonProps).toBeDefined();
return <>{defaultButtons}</>;
}}
/>,
{
can: ({ action }) => {
switch (action) {
case "create":
return Promise.resolve({ can: false });
default:
return Promise.resolve({ can: false });
}
},
},
);
await waitFor(() =>
expect(queryByTestId(RefineButtonTestIds.CreateButton)).toBeDisabled(),
);
});
});
};

View File

@@ -0,0 +1,120 @@
import React from "react";
import { Route, Routes } from "react-router-dom";
import {
type RefineCrudShowProps,
RefineButtonTestIds,
} from "@refinedev/ui-types";
import type { AccessControlProvider } from "@refinedev/core";
import { type ITestWrapperProps, render, TestWrapper } from "@test";
const renderShow = (
show: React.ReactNode,
accessControlProvider?: AccessControlProvider,
wrapperProps?: ITestWrapperProps,
) => {
return render(
<Routes>
<Route path="/:resource/:action/:id" element={show} />
</Routes>,
{
wrapper: TestWrapper(
wrapperProps
? wrapperProps
: {
routerInitialEntries: ["/posts/show/1"],
accessControlProvider,
},
),
},
);
};
export const crudShowTests = (
Show: React.ComponentType<
RefineCrudShowProps<any, any, any, any, any, {}, any, any, any, any>
>,
): void => {
describe("[@refinedev/ui-tests] Common Tests / CRUD Show", () => {
beforeAll(() => {
jest.spyOn(console, "warn").mockImplementation(jest.fn());
});
it("should render children", async () => {
const { getByText } = renderShow(<Show>Something</Show>);
getByText("Something");
});
it("should render default edit and delete buttons successfuly", async () => {
const { queryByTestId } = renderShow(
<Show
canEdit
canDelete
headerButtons={({
defaultButtons,
editButtonProps,
deleteButtonProps,
}) => {
expect(editButtonProps).toBeDefined();
expect(deleteButtonProps).toBeDefined();
return <>{defaultButtons}</>;
}}
/>,
);
expect(queryByTestId(RefineButtonTestIds.EditButton)).not.toBeNull();
expect(queryByTestId(RefineButtonTestIds.DeleteButton)).not.toBeNull();
});
it("should render optional buttons with actionButtons prop", async () => {
const { findByText } = renderShow(
<Show
headerButtons={
<>
<button>New Save Button</button>
<button>New Delete Button</button>
</>
}
/>,
);
await findByText("New Save Button");
await findByText("New Delete Button");
});
it("should render default title successfuly", async () => {
const { getByText } = renderShow(<Show />);
getByText("Show Post");
});
it("should not render title when is false", async () => {
const { queryByText } = renderShow(<Show title={false} />);
const text = queryByText("Show Post");
expect(text).not.toBeInTheDocument();
});
it("should render optional title with title prop", async () => {
const { getByText } = renderShow(<Show title="Test Title" />);
getByText("Test Title");
});
it("should render optional resource with resource prop", async () => {
const { getByText } = render(
<Routes>
<Route path="/:resource" element={<Show resource="posts" />} />
</Routes>,
{
wrapper: TestWrapper({
routerInitialEntries: ["/custom"],
}),
},
);
getByText("Show Post");
});
});
};

View File

@@ -0,0 +1,37 @@
import React from "react";
import type { RefineFieldBooleanProps } from "@refinedev/ui-types";
import { act, fireEvent, render } from "@test";
export const fieldBooleanTests = (
BooleanField: React.ComponentType<RefineFieldBooleanProps<unknown, any, any>>,
): void => {
describe("[@refinedev/ui-tests] Common Tests / Boolean Field", () => {
xit("should use prop for custom text", async () => {
const baseDom = render(
<BooleanField value={true} valueLabelTrue="test" />,
);
const booleanField = baseDom.getByTestId("custom-field");
act(() => {
fireEvent.mouseOver(booleanField);
});
expect(baseDom.getByLabelText("test")).toBeInTheDocument();
});
it("renders value with prop for custom icon", () => {
const { getByTestId } = render(
<div data-testid="custom-field">
<BooleanField
value={true}
trueIcon={<div data-testid="icon-custom-element" />}
/>
</div>,
);
expect(getByTestId("icon-custom-element")).toBeTruthy();
});
});
};

View File

@@ -0,0 +1,64 @@
import React from "react";
import type { ConfigType } from "dayjs";
import type { RefineFieldDateProps } from "@refinedev/ui-types";
import { render } from "@test";
import "dayjs/locale/tr";
export const fieldDateTests = (
DateField: React.ComponentType<RefineFieldDateProps<ConfigType, any, any>>,
): void => {
describe("[@refinedev/ui-tests] Common Tests / Date Field", () => {
it("renders date with default format", () => {
const { getByText } = render(
<DateField value={new Date("2021-05-20")} />,
);
getByText("05/20/2021");
});
it("renders date with given format", () => {
const { getByText } = render(
<DateField value={new Date("2021-05-20")} format="DD/MM/YYYY" />,
);
getByText("20/05/2021");
});
it("renders date with given LocalizedFormat", () => {
const { getByText, rerender } = render(
<DateField value={new Date("2021-05-20")} format="l" locales="tr" />,
);
getByText("20.5.2021");
rerender(<DateField value={new Date("2021-05-20")} format="l" />);
getByText("5/20/2021");
});
it("renders empty with given null", () => {
const { getByTestId } = render(
<DateField value={null} data-testid="date-field" />,
);
expect(getByTestId("date-field").textContent).toBe("");
});
it("renders empty with given undefined", () => {
const { getByTestId } = render(
<DateField value={undefined} data-testid="date-field" />,
);
expect(getByTestId("date-field").textContent).toBe("");
});
it("renders invalid date with given incorrect date", () => {
const { getByText } = render(<DateField value={new Date("test")} />);
getByText("Invalid Date");
});
});
};

View File

@@ -0,0 +1,19 @@
import React, { type ReactNode } from "react";
import type { RefineFieldEmailProps } from "@refinedev/ui-types";
import { render } from "@test";
export const fieldEmailTests = (
EmailField: React.ComponentType<RefineFieldEmailProps<ReactNode, any, any>>,
): void => {
describe("[@refinedev/ui-tests] Common Tests / Email Field", () => {
it("renders email with mailto href", () => {
const { getByText } = render(<EmailField value="test@test.com" />);
expect(getByText("test@test.com")).toHaveProperty(
"href",
"mailto:test@test.com",
);
});
});
};

View File

@@ -0,0 +1,33 @@
import React from "react";
import type { RefineFieldFileProps } from "@refinedev/ui-types";
import { render } from "@test";
export const fieldFileTests = (
FileField: React.ComponentType<RefineFieldFileProps<any, any>>,
): void => {
describe("[@refinedev/ui-tests] Common Tests / File Field", () => {
it("renders an anchor with file link", () => {
const value = {
title: "Test",
src: "www.google.com",
};
const { getByTitle } = render(
<FileField src={value.src} title={value.title} />,
);
expect(getByTitle(value.title)).toHaveAttribute("href", value.src);
});
it("renders an anchor with src", () => {
const value = {
src: "www.google.com",
};
const { getByText } = render(<FileField src={value.src} />);
expect(getByText(value.src)).toHaveAttribute("href", value.src);
});
});
};

View File

@@ -0,0 +1,27 @@
import React from "react";
import type { RefineFieldImageProps } from "@refinedev/ui-types";
import { render } from "@test";
export const fieldImageTests = (
ImageField: React.ComponentType<
RefineFieldImageProps<
string | undefined,
any,
{
imageTitle?: string;
}
>
>,
): void => {
describe("[@refinedev/ui-tests] Common Tests / Image Field", () => {
it("renders image with correct title", () => {
const imageUrl = "http://placeimg.com/640/480/animals";
const { getAllByRole } = render(
<ImageField value={imageUrl} data-testid="test-image" />,
);
expect(getAllByRole("img")[0]).toHaveProperty("src", imageUrl);
});
});
};

View File

@@ -0,0 +1,10 @@
export { fieldBooleanTests } from "./boolean";
export { fieldDateTests } from "./date";
export { fieldEmailTests } from "./email";
export { fieldFileTests } from "./file";
export { fieldImageTests } from "./image";
export { fieldMarkdownTests } from "./markdown";
export { fieldNumberTests } from "./number";
export { fieldTagTests } from "./tag";
export { fieldTextTests } from "./text";
export { fieldUrlTests } from "./url";

View File

@@ -0,0 +1,32 @@
import React from "react";
import type { RefineFieldMarkdownProps } from "@refinedev/ui-types";
import { render } from "@test";
export const fieldMarkdownTests = (
MarkdownField: React.ComponentType<
RefineFieldMarkdownProps<string | undefined>
>,
): void => {
describe("[@refinedev/ui-tests] Common Tests / Markdown Field", () => {
it("renders markDown text with correct value", () => {
const { getByText, container } = render(
<div data-testid="custom-field">
<MarkdownField value={"**MarkdownField Test**"} />
</div>,
);
expect(container.querySelector("strong")).toBeTruthy();
getByText("MarkdownField Test");
});
it("render markdown with undefined value should show empty string", () => {
const { container } = render(
<div data-testid="custom-field">
<MarkdownField value={undefined} />
</div>,
);
expect(container).toBeTruthy();
});
});
};

View File

@@ -0,0 +1,44 @@
import React, { type ReactChild } from "react";
import type { RefineFieldNumberProps } from "@refinedev/ui-types";
import { render } from "@test";
export const fieldNumberTests = (
NumberField: React.ComponentType<RefineFieldNumberProps<ReactChild, any>>,
): void => {
describe("[@refinedev/ui-tests] Common Tests / Number Field", () => {
it("renders numbers with given formatting", () => {
const testPrice = 12345.6789;
const options = {
style: "currency",
currency: "EUR",
maximumFractionDigits: 1,
minimumFractionDigits: 1,
};
const locale = "de-DE";
const { getByText } = render(
<NumberField value={testPrice} locale={locale} options={options} />,
);
const formattedTestPrice = testPrice
.toLocaleString(locale, options)
.replace(String.fromCharCode(160), " ");
// node 14 uses non-breaking space resulting in imcompatibility
getByText(formattedTestPrice);
});
it("should render NaN when value is undefined", () => {
const { getByText } = render(<NumberField value={undefined} />);
getByText("NaN");
});
it("should render NaN when value is string", () => {
const { getByText } = render(<NumberField value={"not a number"} />);
getByText("NaN");
});
});
};

View File

@@ -0,0 +1,22 @@
import React, { type ReactNode } from "react";
import type { RefineFieldTagProps } from "@refinedev/ui-types";
import { render } from "@test";
export const fieldTagTests = (
TagField: React.ComponentType<RefineFieldTagProps<ReactNode, any>>,
): void => {
describe("[@refinedev/ui-tests] Common Tests / Tag Field", () => {
it("renders boolean values correctly", () => {
const { getByText } = render(<TagField value={true} />);
getByText("true");
});
it("renders boolean values correctly", () => {
const { queryByText } = render(<TagField value={undefined} />);
expect(queryByText("true")).toBeNull();
});
});
};

View File

@@ -0,0 +1,16 @@
import React, { type ReactNode } from "react";
import type { RefineFieldTextProps } from "@refinedev/ui-types";
import { render } from "@test";
export const fieldTextTests = (
TextField: React.ComponentType<RefineFieldTextProps<ReactNode, any>>,
): void => {
describe("[@refinedev/ui-tests] Common Tests / Text Field", () => {
it("renders text correctly", () => {
const { getByText } = render(<TextField value="test" />);
getByText("test");
});
});
};

View File

@@ -0,0 +1,40 @@
import React from "react";
import type { RefineFieldUrlProps } from "@refinedev/ui-types";
import { render } from "@test";
export const fieldUrlTests = (
UrlField: React.ComponentType<
RefineFieldUrlProps<string | undefined, any, any>
>,
): void => {
describe("[@refinedev/ui-tests] Common Tests / Url Field", () => {
const url = "https://www.google.com/";
it("renders urlField with given value", () => {
const { getByText } = render(<UrlField value={url} />);
const link = getByText(url) as HTMLAnchorElement;
expect(link.href).toBe(url);
expect(link.tagName).toBe("A");
});
it("renders deep fields", () => {
const record = { id: 1, source: { path: url } };
const { getByText } = render(<UrlField value={record.source.path} />);
const link = getByText(url) as HTMLAnchorElement;
expect(link.href).toBe(url);
});
it("renders children element", () => {
const { getByText } = render(
<UrlField value={url}>Make this link</UrlField>,
);
const link = getByText("Make this link") as HTMLAnchorElement;
expect(link.href).toBe(url);
});
});
};

View File

@@ -0,0 +1,7 @@
export * from "./crud";
export * from "./breadcrumb";
export * from "./buttons";
export * from "./fields";
export * from "./layout";
export * from "./pages";
export * from "./autoSaveIndicator";

View File

@@ -0,0 +1,18 @@
import React from "react";
import type { RefineLayoutFooterProps } from "@refinedev/ui-types";
import { act, render, TestWrapper } from "@test";
export const layoutFooterTests = (
FooterElement: React.ComponentType<RefineLayoutFooterProps>,
): void => {
describe("[@refinedev/ui-tests] Common Tests / Footer Element", () => {
it("should render successfully", async () => {
const { container } = render(<FooterElement />, {
wrapper: TestWrapper({}),
});
expect(container).toBeTruthy();
});
});
};

View File

@@ -0,0 +1,71 @@
import React from "react";
import type { AuthProvider } from "@refinedev/core";
import type { RefineLayoutHeaderProps } from "@refinedev/ui-types";
import { render, TestWrapper } from "@test";
const mockLegacyAuthProvider = {
login: () => Promise.resolve(),
logout: () => Promise.resolve(),
checkError: () => Promise.resolve(),
checkAuth: () => Promise.resolve(),
getPermissions: () => Promise.resolve(["admin"]),
getUserIdentity: () =>
Promise.resolve({ name: "username", avatar: "localhost:3000" }),
};
const mockAuthProvider: AuthProvider = {
check: () => Promise.resolve({ authenticated: true }),
login: () => Promise.resolve({ success: true }),
logout: () => Promise.resolve({ success: true }),
onError: () => Promise.resolve({}),
getPermissions: () => Promise.resolve(["admin"]),
getIdentity: () =>
Promise.resolve({ name: "username", avatar: "localhost:3000" }),
};
export const layoutHeaderTests = (
HeaderElement: React.ComponentType<RefineLayoutHeaderProps>,
): void => {
describe("[@refinedev/ui-tests] Common Tests / Header Element", () => {
// NOTE : Will be removed in v5
it("should render successfull user name and avatar in header with legacy authProvider", async () => {
const { findByText, queryByRole, queryByAltText } = render(
<HeaderElement />,
{
wrapper: TestWrapper({
legacyAuthProvider: mockLegacyAuthProvider,
}),
},
);
await findByText("username");
const imgByRole = queryByRole("img", { queryFallbacks: true });
const imgByAltText = queryByAltText("username");
expect(imgByRole ?? imgByAltText).toHaveAttribute(
"src",
"localhost:3000",
);
});
it("should render successfull user name and avatar in header with authProvider", async () => {
const { findByText, queryByRole, queryByAltText } = render(
<HeaderElement />,
{
wrapper: TestWrapper({
authProvider: mockAuthProvider,
}),
},
);
await findByText("username");
const imgByRole = queryByRole("img", { queryFallbacks: true });
const imgByAltText = queryByAltText("username");
expect(imgByRole ?? imgByAltText).toHaveAttribute(
"src",
"localhost:3000",
);
});
});
};

View File

@@ -0,0 +1,5 @@
export { layoutFooterTests } from "./footer";
export { layoutHeaderTests } from "./header";
export { layoutLayoutTests } from "./layout";
export { layoutSiderTests } from "./sider";
export { layoutTitleTests } from "./title";

View File

@@ -0,0 +1,43 @@
import React from "react";
import type { RefineLayoutLayoutProps } from "@refinedev/ui-types";
import { act, render, TestWrapper } from "@test";
export const layoutLayoutTests = (
LayoutElement: React.ComponentType<RefineLayoutLayoutProps>,
): void => {
describe("[@refinedev/ui-tests] Common Tests / Layout Element", () => {
it("Layout renders sider, header, footer, title, offLayoutArea if given props", async () => {
const customTitleContent = "customTitleContent";
const CustomTitle = () => <p>{customTitleContent}</p>;
const customSiderContent = "customSiderContent";
const CustomSider = () => <p>{customSiderContent}</p>;
const customHeaderContent = "customHeaderContent";
const CustomHeader = () => <p>{customHeaderContent}</p>;
const customFooterContent = "customFooterContent";
const CustomFooter = () => <p>{customFooterContent}</p>;
const customOffLayoutAreaContent = "customOffLayoutAreaContent";
const CustomOffLayoutArea = () => <p>{customOffLayoutAreaContent}</p>;
const { getByText } = render(
<LayoutElement
Title={CustomTitle}
Sider={CustomSider}
Header={CustomHeader}
Footer={CustomFooter}
OffLayoutArea={CustomOffLayoutArea}
/>,
{ wrapper: TestWrapper({}) },
);
expect(getByText(customSiderContent));
expect(getByText(customHeaderContent));
expect(getByText(customFooterContent));
expect(getByText(customOffLayoutAreaContent));
});
});
};

View File

@@ -0,0 +1,307 @@
import React from "react";
import type { RefineThemedLayoutV2SiderProps } from "@refinedev/ui-types";
import { act, mockRouterBindings, render, TestWrapper, waitFor } from "@test";
import type { AuthProvider, LegacyAuthProvider } from "@refinedev/core";
import { Route, Router, Routes } from "react-router-dom";
const mockLegacyAuthProvider: LegacyAuthProvider & { isProvided: boolean } = {
login: () => Promise.resolve(),
logout: () => Promise.resolve(),
checkError: () => Promise.resolve(),
checkAuth: () => Promise.resolve(),
getPermissions: () => Promise.resolve(["admin"]),
getUserIdentity: () => Promise.resolve(),
isProvided: true,
};
const mockAuthProvider: AuthProvider = {
check: () => Promise.resolve({ authenticated: true }),
login: () => Promise.resolve({ success: true }),
logout: () => Promise.resolve({ success: true }),
getPermissions: () => Promise.resolve(["admin"]),
onError: () => Promise.resolve({}),
};
export const layoutSiderTests = (
SiderElement: React.ComponentType<RefineThemedLayoutV2SiderProps>,
): void => {
describe("[@refinedev/ui-tests] Common Tests / Sider Element", () => {
it("should render successful", async () => {
const { container } = render(<SiderElement />, {
wrapper: TestWrapper({}),
});
expect(container).toBeTruthy();
});
it("should render logout menu item successful", async () => {
const { getAllByText } = render(<SiderElement />, {
wrapper: TestWrapper({
legacyAuthProvider: mockLegacyAuthProvider,
}),
});
await waitFor(() =>
expect(getAllByText("Posts").length).toBeGreaterThanOrEqual(1),
);
expect(getAllByText("Logout").length).toBeGreaterThanOrEqual(1);
});
it("should work menu item click", async () => {
const { getAllByText } = render(<SiderElement />, {
wrapper: TestWrapper({
legacyAuthProvider: mockLegacyAuthProvider,
}),
});
await waitFor(() => getAllByText("Posts")[0].click());
await waitFor(() => expect(window.location.pathname).toBe("/posts"));
});
// NOTE : Will be removed in v5
it("should work legacy logout menu item click", async () => {
const logoutMockedAuthProvider = {
...mockLegacyAuthProvider,
logout: jest.fn().mockImplementation(() => Promise.resolve()),
};
const { getAllByText } = render(<SiderElement />, {
wrapper: TestWrapper({
legacyAuthProvider: logoutMockedAuthProvider,
}),
});
await act(async () => {
getAllByText("Logout")[0].click();
});
expect(logoutMockedAuthProvider.logout).toBeCalledTimes(1);
});
it("should work logout menu item click", async () => {
const logoutMockedAuthProvider = {
...mockAuthProvider,
logout: jest
.fn()
.mockImplementation(() => Promise.resolve({ success: true })),
};
const { getAllByText } = render(<SiderElement />, {
wrapper: TestWrapper({
authProvider: logoutMockedAuthProvider,
}),
});
await act(async () => {
getAllByText("Logout")[0].click();
});
expect(logoutMockedAuthProvider.logout).toBeCalledTimes(1);
});
it("should render only allowed menu items", async () => {
const { getAllByText, queryByText } = render(<SiderElement />, {
wrapper: TestWrapper({
resources: [
{
name: "posts",
list: function render() {
return <div>posts</div>;
},
},
{
name: "users",
list: function render() {
return <div>users</div>;
},
},
],
accessControlProvider: {
can: ({ action, resource }) => {
if (action === "list" && resource === "posts") {
return Promise.resolve({ can: true });
}
if (action === "list" && resource === "users") {
return Promise.resolve({ can: false });
}
return Promise.resolve({ can: false });
},
},
}),
});
await waitFor(() => getAllByText("Posts")[0]);
await waitFor(() => expect(queryByText("Users")).toBeNull());
});
it("should render custom element passed with render prop", async () => {
const { getAllByText, queryAllByText } = render(
<SiderElement
render={({ logout, dashboard, items }) => {
return (
<>
<div>custom-element</div>
{dashboard}
{items}
{logout}
</>
);
}}
/>,
{
wrapper: TestWrapper({
legacyAuthProvider: mockLegacyAuthProvider,
DashboardPage: function Dashboard() {
return <div>Dashboard</div>;
},
}),
},
);
await waitFor(() =>
expect(getAllByText("Posts").length).toBeGreaterThanOrEqual(1),
);
expect(queryAllByText("Logout").length).toBeGreaterThanOrEqual(1);
expect(queryAllByText("Dashboard").length).toBeGreaterThanOrEqual(1);
expect(queryAllByText("custom-element").length).toBeGreaterThanOrEqual(1);
});
it("should item disabled when activeItemDisabled:true (legacyRouterProvider)", async () => {
const { getAllByText, getAllByRole } = render(
<SiderElement activeItemDisabled={true} />,
{
wrapper: TestWrapper({
routerInitialEntries: ["/posts"],
resources: [
{
name: "posts",
list: "/posts",
},
],
}),
},
);
await waitFor(() => {
return expect(getAllByText("Posts").length).toBeGreaterThanOrEqual(1);
});
await waitFor(() => {
const allLinks = getAllByRole("link");
const postLink = allLinks.find((link) => {
return link.getAttribute("href") === "/posts";
});
return expect(postLink).toHaveStyle("pointer-events: none");
});
});
it("should item disabled when activeItemDisabled:true", async () => {
const { getAllByText, getAllByRole } = render(
<SiderElement activeItemDisabled={true} />,
{
wrapper: TestWrapper({
routerInitialEntries: ["/posts"],
routerProvider: mockRouterBindings({
pathname: "/posts",
action: "list",
resource: {
name: "posts",
list: "/posts",
},
}),
resources: [
{
name: "posts",
list: "/posts",
},
{
name: "users",
list: "/users",
},
],
}),
},
);
await waitFor(() => {
return expect(getAllByText("Posts").length).toBeGreaterThanOrEqual(1);
});
await waitFor(() => {
const allLinks = getAllByRole("link");
const postLink = allLinks.find((link) => {
return link.getAttribute("href") === "/posts";
});
return expect(postLink).toHaveStyle("pointer-events: none");
});
});
it("should handle lowercase resource names correctly", async () => {
const { getByText, getAllByText } = render(<SiderElement />, {
wrapper: TestWrapper({
resources: [
{
name: "posts",
list: "/posts",
},
{
name: "users",
list: "/users",
},
],
accessControlProvider: {
can: ({ action, resource }) => {
if (action === "list" && resource === "posts") {
return Promise.resolve({ can: true });
}
if (action === "list" && resource === "users") {
return Promise.resolve({ can: false });
}
return Promise.resolve({ can: false });
},
},
}),
});
const postsElements = await waitFor(() => getAllByText("Posts"));
postsElements.forEach((element) => {
expect(element).toBeInTheDocument();
});
expect(() => getByText("Users")).toThrow();
});
it("should handle camelcased resource names correctly", async () => {
const { getByText, getAllByText } = render(<SiderElement />, {
wrapper: TestWrapper({
resources: [
{
name: "blogPosts",
list: "/blog-posts",
},
{
name: "userProfiles",
list: "/user-profiles",
},
],
accessControlProvider: {
can: ({ action, resource }) => {
if (action === "list" && resource === "blogPosts") {
return Promise.resolve({ can: true });
}
if (action === "list" && resource === "userProfiles") {
return Promise.resolve({ can: false });
}
return Promise.resolve({ can: false });
},
},
}),
});
const blogPostsElements = await waitFor(() => getAllByText("Blog posts"));
blogPostsElements.forEach((element) => {
expect(element).toBeInTheDocument();
});
expect(() => getByText("User profiles")).toThrow();
});
});
};

View File

@@ -0,0 +1,34 @@
import React from "react";
import type { RefineLayoutTitleProps } from "@refinedev/ui-types";
import { render, TestWrapper } from "@test";
export const layoutTitleTests = (
TitleElement: React.ComponentType<RefineLayoutTitleProps>,
): void => {
describe("[@refinedev/ui-tests] Common Tests / Title Element", () => {
it("should render successfully", async () => {
const { container } = render(<TitleElement collapsed={false} />, {
wrapper: TestWrapper({}),
});
expect(container).toBeTruthy();
});
it("should use app name and icon from <Refine /> component", () => {
const { getByTestId } = render(<TitleElement collapsed={false} />, {
wrapper: TestWrapper({
options: {
title: {
text: <div data-testid="my-company-name">My Company</div>,
icon: <div data-testid="my-company-logo" />,
},
},
}),
});
expect(getByTestId("my-company-name")).toBeInTheDocument();
expect(getByTestId("my-company-logo")).toBeInTheDocument();
});
});
};

View File

@@ -0,0 +1,34 @@
import React, { type FC } from "react";
import type { AuthPageProps } from "@refinedev/core";
import { render, TestWrapper } from "@test";
export const authPageTests = (
AuthPage: FC<AuthPageProps<any, any, any>>,
): void => {
describe("[@refinedev/ui-tests] Common Tests / Auth Page", () => {
it.each(["register", "forgotPassword", "updatePassword", "login"] as const)(
"should render %s type",
async (type) => {
const { getByText } = render(<AuthPage type={type} />, {
wrapper: TestWrapper({}),
});
switch (type) {
case "register":
expect(getByText(/sign up for your account/i)).toBeInTheDocument();
break;
case "forgotPassword":
expect(getByText(/forgot your password?/i)).toBeInTheDocument();
break;
case "updatePassword":
expect(getByText(/update/i)).toBeInTheDocument();
break;
default:
expect(getByText(/Sign in to your account/i)).toBeInTheDocument();
break;
}
},
);
});
};

View File

@@ -0,0 +1,289 @@
import React, { type FC } from "react";
import type { ForgotPasswordPageProps } from "@refinedev/core";
import {
fireEvent,
mockAuthProvider,
mockRouterBindings,
MockRouterProvider,
render,
TestWrapper,
waitFor,
} from "@test";
export const pageForgotPasswordTests = (
ForgotPasswordPage: FC<ForgotPasswordPageProps<any, any, any>>,
): void => {
describe("[@refinedev/ui-tests] Common Tests / Forgot Password Page", () => {
it("should render card title", async () => {
const { getByText } = render(<ForgotPasswordPage />, {
wrapper: TestWrapper({}),
});
expect(getByText(/forgot your password?/i)).toBeInTheDocument();
});
it("should render email input", async () => {
const { getByLabelText } = render(<ForgotPasswordPage />, {
wrapper: TestWrapper({}),
});
expect(getByLabelText(/email/i)).toBeInTheDocument();
});
it("should login link", async () => {
const { getByRole } = render(<ForgotPasswordPage />, {
wrapper: TestWrapper({}),
});
expect(
getByRole("link", {
name: /sign in/i,
}),
).toBeInTheDocument();
});
it("should not render login link when is false", async () => {
const { queryByRole } = render(<ForgotPasswordPage loginLink={false} />, {
wrapper: TestWrapper({}),
});
expect(
queryByRole("link", {
name: /sign in/i,
}),
).not.toBeInTheDocument();
});
it("should render reset button", async () => {
const { getByRole } = render(<ForgotPasswordPage />, {
wrapper: TestWrapper({}),
});
expect(
getByRole("button", {
name: /send reset/i,
}),
).toBeInTheDocument();
});
it("should render default <ThemedTitle> with icon", async () => {
const { getByText, getByTestId } = render(<ForgotPasswordPage />, {
wrapper: TestWrapper({}),
});
expect(getByText(/refine project/i)).toBeInTheDocument();
expect(getByTestId("refine-logo")).toBeInTheDocument();
});
it("should render custom title", async () => {
const { queryByText, queryByTestId } = render(
<ForgotPasswordPage title="Custom Title" />,
{
wrapper: TestWrapper({}),
},
);
expect(queryByText(/custom title/i)).toBeInTheDocument();
expect(queryByText(/refine project/i)).not.toBeInTheDocument();
expect(queryByTestId("refine-logo")).not.toBeInTheDocument();
});
it("should not render title when is false", async () => {
const { queryByText } = render(<ForgotPasswordPage title={false} />, {
wrapper: TestWrapper({}),
});
expect(queryByText(/refine project/i)).not.toBeInTheDocument();
});
it("should pass contentProps", async () => {
const { getByTestId } = render(
<ForgotPasswordPage contentProps={{ "data-testid": "test" }} />,
{
wrapper: TestWrapper({}),
},
);
expect(getByTestId("test")).toBeInTheDocument();
});
it("should pass wrapperProps", async () => {
const { getByTestId } = render(
<ForgotPasswordPage wrapperProps={{ "data-testid": "test" }} />,
{
wrapper: TestWrapper({}),
},
);
expect(getByTestId("test")).toBeInTheDocument();
});
it("should renderContent only", async () => {
const { queryByText, queryByTestId, queryByRole, queryByLabelText } =
render(
<ForgotPasswordPage
renderContent={() => <div data-testid="custom-content" />}
/>,
{
wrapper: TestWrapper({}),
},
);
expect(queryByLabelText(/email/i)).not.toBeInTheDocument();
expect(queryByText(/refine project/i)).not.toBeInTheDocument();
expect(queryByTestId("refine-logo")).not.toBeInTheDocument();
expect(
queryByRole("button", {
name: /reset/i,
}),
).not.toBeInTheDocument();
expect(queryByTestId("custom-content")).toBeInTheDocument();
});
it("should customizable with renderContent", async () => {
const { queryByText, queryByTestId, queryByRole, queryByLabelText } =
render(
<ForgotPasswordPage
renderContent={(content: any, title: any) => (
<div>
{title}
<div data-testid="custom-content">
<div>Custom Content</div>
</div>
{content}
</div>
)}
/>,
{
wrapper: TestWrapper({}),
},
);
expect(queryByText(/custom content/i)).toBeInTheDocument();
expect(queryByTestId("custom-content")).toBeInTheDocument();
expect(queryByLabelText(/email/i)).toBeInTheDocument();
expect(queryByText(/refine project/i)).toBeInTheDocument();
expect(queryByTestId("refine-logo")).toBeInTheDocument();
expect(
queryByRole("button", {
name: /reset/i,
}),
).toBeInTheDocument();
expect(queryByTestId("custom-content")).toBeInTheDocument();
});
it("should run forgotPassword mutation when form is submitted", async () => {
const forgotPasswordMock = jest.fn().mockResolvedValue({ success: true });
const { getByLabelText, getByText } = render(<ForgotPasswordPage />, {
wrapper: TestWrapper({
authProvider: {
...mockAuthProvider,
forgotPassword: forgotPasswordMock,
},
}),
});
fireEvent.change(getByLabelText(/email/i), {
target: { value: "demo@refine.dev" },
});
fireEvent.click(getByText(/send reset instructions/i));
await waitFor(() => {
expect(forgotPasswordMock).toBeCalledTimes(1);
});
expect(forgotPasswordMock).toBeCalledWith({
email: "demo@refine.dev",
});
});
it("should work with legacy router provider Link", async () => {
jest.spyOn(console, "error").mockImplementation((message) => {
console.warn(message);
});
const LinkComponentMock = jest.fn();
render(<ForgotPasswordPage />, {
wrapper: TestWrapper({
legacyRouterProvider: {
...MockRouterProvider,
Link: LinkComponentMock,
},
}),
});
expect(LinkComponentMock).toBeCalledWith(
expect.objectContaining({
to: "/login",
}),
{},
);
});
it("should work with new router provider Link", async () => {
jest.spyOn(console, "error").mockImplementation((message) => {
console.warn(message);
});
const LinkComponentMock = jest.fn();
render(<ForgotPasswordPage />, {
wrapper: TestWrapper({
routerProvider: mockRouterBindings({
fns: {
Link: LinkComponentMock,
},
}),
}),
});
expect(LinkComponentMock).toBeCalledWith(
expect.objectContaining({
to: "/login",
}),
{},
);
});
it("should should accept 'mutationVariables'", async () => {
const forgotPasswordMock = jest.fn().mockResolvedValue({ success: true });
const { getByRole, getByLabelText } = render(
<ForgotPasswordPage
mutationVariables={{
foo: "bar",
xyz: "abc",
}}
/>,
{
wrapper: TestWrapper({
authProvider: {
...mockAuthProvider,
forgotPassword: forgotPasswordMock,
},
}),
},
);
fireEvent.change(getByLabelText(/email/i), {
target: { value: "demo@refine.dev" },
});
fireEvent.click(
getByRole("button", {
name: /reset/i,
}),
);
await waitFor(() => {
expect(forgotPasswordMock).toHaveBeenCalledWith({
foo: "bar",
xyz: "abc",
email: "demo@refine.dev",
});
});
});
});
};

View File

@@ -0,0 +1,5 @@
export { pageLoginTests } from "./login";
export { pageRegisterTests } from "./register";
export { pageForgotPasswordTests } from "./forgotPassword";
export { pageUpdatePasswordTests } from "./updatePassword";
export { authPageTests } from "./authPage";

View File

@@ -0,0 +1,452 @@
import React, { type FC } from "react";
import {
fireEvent,
mockAuthProvider,
mockRouterBindings,
render,
TestWrapper,
waitFor,
} from "@test";
import type { LoginPageProps } from "@refinedev/core";
export const pageLoginTests = (
LoginPage: FC<LoginPageProps<any, any, any>>,
): void => {
describe("[@refinedev/ui-tests] Common Tests / Login Page", () => {
it("should render card title", async () => {
const { getByText } = render(<LoginPage />, {
wrapper: TestWrapper({}),
});
expect(getByText(/sign in to your account/i)).toBeInTheDocument();
});
it("should render card email and password input", async () => {
const { getByLabelText } = render(<LoginPage />, {
wrapper: TestWrapper({}),
});
expect(getByLabelText(/email/i)).toBeInTheDocument();
expect(getByLabelText(/password/i)).toBeInTheDocument();
});
it("should render providers", async () => {
const { getByText } = render(
<LoginPage
providers={[
{
name: "Google",
label: "Google",
},
{
name: "Github",
label: "Github",
},
]}
/>,
{
wrapper: TestWrapper({}),
},
);
expect(getByText(/google/i)).toBeInTheDocument();
expect(getByText(/github/i)).toBeInTheDocument();
});
it("should register link", async () => {
const { getByRole } = render(<LoginPage />, {
wrapper: TestWrapper({}),
});
expect(
getByRole("link", {
name: /sign up/i,
}),
).toBeInTheDocument();
});
it("should not render register link when is false", async () => {
const { queryByRole } = render(<LoginPage registerLink={false} />, {
wrapper: TestWrapper({}),
});
expect(
queryByRole("link", {
name: /sign up/i,
}),
).not.toBeInTheDocument();
});
it("should forgotPassword link", async () => {
const { getByRole } = render(<LoginPage />, {
wrapper: TestWrapper({}),
});
expect(
getByRole("link", {
name: /forgot password?/i,
}),
).toBeInTheDocument();
});
it("should not render forgotPassword link when is false", async () => {
const { queryByRole } = render(<LoginPage forgotPasswordLink={false} />, {
wrapper: TestWrapper({}),
});
expect(
queryByRole("link", {
name: /forgot password/i,
}),
).not.toBeInTheDocument();
});
it("should render remember me", async () => {
const { queryByRole } = render(<LoginPage />, {
wrapper: TestWrapper({}),
});
expect(
queryByRole("checkbox", {
name: /remember me/i,
}),
).toBeInTheDocument();
});
it("should not render remember me when is false", async () => {
const { queryByRole } = render(<LoginPage rememberMe={false} />, {
wrapper: TestWrapper({}),
});
expect(
queryByRole("checkbox", {
name: /remember me/i,
}),
).not.toBeInTheDocument();
});
it("should render sign in button", async () => {
const { getByRole } = render(<LoginPage />, {
wrapper: TestWrapper({}),
});
expect(
getByRole("button", {
name: /sign in/i,
}),
).toBeInTheDocument();
});
it("should render default <ThemedTitle> with icon", async () => {
const { getByText, getByTestId } = render(<LoginPage />, {
wrapper: TestWrapper({}),
});
expect(getByText(/refine project/i)).toBeInTheDocument();
expect(getByTestId("refine-logo")).toBeInTheDocument();
});
it("should render custom title", async () => {
const { queryByText, queryByTestId } = render(
<LoginPage title="Custom Title" />,
{
wrapper: TestWrapper({}),
},
);
expect(queryByText(/custom title/i)).toBeInTheDocument();
expect(queryByText(/refine project/i)).not.toBeInTheDocument();
expect(queryByTestId("refine-logo")).not.toBeInTheDocument();
});
it("should not render title when is false", async () => {
const { queryByText } = render(<LoginPage title={false} />, {
wrapper: TestWrapper({}),
});
expect(queryByText(/refine project/i)).not.toBeInTheDocument();
});
it("should pass contentProps", async () => {
const { getByTestId } = render(
<LoginPage contentProps={{ "data-testid": "test" }} />,
{
wrapper: TestWrapper({}),
},
);
expect(getByTestId("test")).toBeInTheDocument();
});
it("should pass wrapperProps", async () => {
const { getByTestId } = render(
<LoginPage wrapperProps={{ "data-testid": "test" }} />,
{
wrapper: TestWrapper({}),
},
);
expect(getByTestId("test")).toBeInTheDocument();
});
it("should renderContent only", async () => {
const { queryByText, queryByTestId, queryByRole, queryByLabelText } =
render(
<LoginPage
renderContent={() => <div data-testid="custom-content" />}
/>,
{
wrapper: TestWrapper({}),
},
);
expect(queryByLabelText(/email/i)).not.toBeInTheDocument();
expect(queryByLabelText(/password/i)).not.toBeInTheDocument();
expect(queryByText(/refine project/i)).not.toBeInTheDocument();
expect(queryByTestId("refine-logo")).not.toBeInTheDocument();
expect(
queryByRole("button", {
name: /sign in/i,
}),
).not.toBeInTheDocument();
expect(queryByTestId("custom-content")).toBeInTheDocument();
});
it("should customizable with renderContent", async () => {
const { queryByText, queryByTestId, queryByRole, queryByLabelText } =
render(
<LoginPage
renderContent={(content: any, title: any) => (
<div>
{title}
<div data-testid="custom-content">
<div>Custom Content</div>
</div>
{content}
</div>
)}
/>,
{
wrapper: TestWrapper({}),
},
);
expect(queryByText(/custom content/i)).toBeInTheDocument();
expect(queryByTestId("custom-content")).toBeInTheDocument();
expect(queryByLabelText(/email/i)).toBeInTheDocument();
expect(queryByLabelText(/password/i)).toBeInTheDocument();
expect(queryByText(/refine project/i)).toBeInTheDocument();
expect(queryByTestId("refine-logo")).toBeInTheDocument();
expect(
queryByRole("button", {
name: /sign in/i,
}),
).toBeInTheDocument();
expect(queryByTestId("custom-content")).toBeInTheDocument();
});
it("should run login mutation when form is submitted", async () => {
const loginMock = jest.fn().mockResolvedValue({ success: true });
const { getByLabelText, getAllByText } = render(<LoginPage />, {
wrapper: TestWrapper({
authProvider: {
...mockAuthProvider,
login: loginMock,
},
}),
});
fireEvent.change(getByLabelText(/email/i), {
target: { value: "demo@refine.dev" },
});
fireEvent.change(getByLabelText(/password/i), {
target: { value: "demo" },
});
fireEvent.click(getByLabelText(/remember me/i));
fireEvent.click(getAllByText(/sign in/i)[1]);
await waitFor(() => {
expect(loginMock).toBeCalledTimes(1);
});
expect(loginMock).toBeCalledWith({
email: "demo@refine.dev",
password: "demo",
remember: true,
});
});
it("should work with new router provider Link", async () => {
jest.spyOn(console, "error").mockImplementation((message) => {
console.warn(message);
});
const LinkComponentMock = jest.fn();
render(<LoginPage />, {
wrapper: TestWrapper({
routerProvider: mockRouterBindings({
fns: {
Link: LinkComponentMock,
},
}),
}),
});
expect(LinkComponentMock).toBeCalledWith(
expect.objectContaining({
to: "/forgot-password",
}),
{},
);
expect(LinkComponentMock).toBeCalledWith(
expect.objectContaining({
to: "/register",
}),
{},
);
});
it("should run login mutation when provider button is clicked", async () => {
const loginMock = jest.fn().mockResolvedValue({ success: true });
const { getByText } = render(
<LoginPage
providers={[
{
name: "Google",
label: "Google",
},
]}
/>,
{
wrapper: TestWrapper({
authProvider: {
...mockAuthProvider,
login: loginMock,
},
}),
},
);
expect(getByText(/google/i)).toBeInTheDocument();
fireEvent.click(getByText(/google/i));
await waitFor(() => {
expect(loginMock).toBeCalledTimes(1);
});
expect(loginMock).toBeCalledWith({
providerName: "Google",
});
});
it("should not render form when `hideForm` is true", async () => {
const { queryByLabelText, getByText, queryByRole } = render(
<LoginPage
hideForm
providers={[
{
name: "google",
label: "Google",
},
{ name: "github", label: "GitHub" },
]}
/>,
{
wrapper: TestWrapper({}),
},
);
expect(queryByLabelText(/email/i)).not.toBeInTheDocument();
expect(queryByLabelText(/password/i)).not.toBeInTheDocument();
expect(queryByLabelText(/remember/i)).not.toBeInTheDocument();
expect(
queryByRole("link", {
name: /forgot password/i,
}),
).not.toBeInTheDocument();
expect(
queryByRole("button", {
name: /sign in/i,
}),
).not.toBeInTheDocument();
expect(getByText(/google/i)).toBeInTheDocument();
expect(getByText(/github/i)).toBeInTheDocument();
expect(
queryByRole("link", {
name: /sign up/i,
}),
).toBeInTheDocument();
});
it.each([true, false])("should has default links", async (hideForm) => {
const { getByRole } = render(<LoginPage hideForm={hideForm} />, {
wrapper: TestWrapper({}),
});
expect(
getByRole("link", {
name: /sign up/i,
}),
).toHaveAttribute("href", "/register");
if (hideForm === false) {
expect(
getByRole("link", {
name: /forgot password/i,
}),
).toHaveAttribute("href", "/forgot-password");
}
});
it("should should accept 'mutationVariables'", async () => {
const loginMock = jest.fn().mockResolvedValue({ success: true });
const { getByRole, getByLabelText } = render(
<LoginPage
mutationVariables={{
foo: "bar",
xyz: "abc",
}}
/>,
{
wrapper: TestWrapper({
authProvider: {
...mockAuthProvider,
login: loginMock,
},
}),
},
);
fireEvent.change(getByLabelText(/email/i), {
target: { value: "demo@refine.dev" },
});
fireEvent.change(getByLabelText(/password/i), {
target: { value: "demo" },
});
fireEvent.click(
getByRole("button", {
name: /sign in/i,
}),
);
await waitFor(() => {
expect(loginMock).toHaveBeenCalledWith({
foo: "bar",
xyz: "abc",
email: "demo@refine.dev",
password: "demo",
remember: false,
});
});
});
});
};

View File

@@ -0,0 +1,384 @@
import React, { type FC } from "react";
import {
fireEvent,
mockAuthProvider,
mockRouterBindings,
render,
TestWrapper,
waitFor,
} from "@test";
import type { RegisterPageProps } from "@refinedev/core";
export const pageRegisterTests = (
RegisterPage: FC<RegisterPageProps<any, any, any>>,
): void => {
describe("[@refinedev/ui-tests] Common Tests / Register Page", () => {
it("should render card title", async () => {
const { getByText } = render(<RegisterPage />, {
wrapper: TestWrapper({}),
});
expect(getByText(/sign up for your account/i)).toBeInTheDocument();
});
it("should render card email and password input", async () => {
const { getByLabelText } = render(<RegisterPage />, {
wrapper: TestWrapper({}),
});
expect(getByLabelText(/email/i)).toBeInTheDocument();
expect(getByLabelText(/password/i)).toBeInTheDocument();
});
it("should render providers", async () => {
const { getByText } = render(
<RegisterPage
providers={[
{
name: "Google",
label: "Google",
},
{
name: "Github",
label: "Github",
},
]}
/>,
{
wrapper: TestWrapper({}),
},
);
expect(getByText(/google/i)).toBeInTheDocument();
expect(getByText(/github/i)).toBeInTheDocument();
});
it("should login link", async () => {
const { getByRole } = render(<RegisterPage />, {
wrapper: TestWrapper({}),
});
expect(
getByRole("link", {
name: /sign in/i,
}),
).toBeInTheDocument();
});
it("should not render login link when is false", async () => {
const { queryByRole } = render(<RegisterPage loginLink={false} />, {
wrapper: TestWrapper({}),
});
expect(
queryByRole("link", {
name: /sign in/i,
}),
).not.toBeInTheDocument();
});
it("should render sign up button", async () => {
const { getByRole } = render(<RegisterPage />, {
wrapper: TestWrapper({}),
});
expect(
getByRole("button", {
name: /sign up/i,
}),
).toBeInTheDocument();
});
it("should render default <ThemedTitle> with icon", async () => {
const { getByText, getByTestId } = render(<RegisterPage />, {
wrapper: TestWrapper({}),
});
expect(getByText(/refine project/i)).toBeInTheDocument();
expect(getByTestId("refine-logo")).toBeInTheDocument();
});
it("should render custom title", async () => {
const { queryByText, queryByTestId } = render(
<RegisterPage title="Custom Title" />,
{
wrapper: TestWrapper({}),
},
);
expect(queryByText(/custom title/i)).toBeInTheDocument();
expect(queryByText(/refine project/i)).not.toBeInTheDocument();
expect(queryByTestId("refine-logo")).not.toBeInTheDocument();
});
it("should not render title when is false", async () => {
const { queryByText } = render(<RegisterPage title={false} />, {
wrapper: TestWrapper({}),
});
expect(queryByText(/refine project/i)).not.toBeInTheDocument();
});
it("should pass contentProps", async () => {
const { getByTestId } = render(
<RegisterPage contentProps={{ "data-testid": "test" }} />,
{
wrapper: TestWrapper({}),
},
);
expect(getByTestId("test")).toBeInTheDocument();
});
it("should pass wrapperProps", async () => {
const { getByTestId } = render(
<RegisterPage wrapperProps={{ "data-testid": "test" }} />,
{
wrapper: TestWrapper({}),
},
);
expect(getByTestId("test")).toBeInTheDocument();
});
it("should renderContent only", async () => {
const { queryByText, queryByTestId, queryByRole, queryByLabelText } =
render(
<RegisterPage
renderContent={() => <div data-testid="custom-content" />}
/>,
{
wrapper: TestWrapper({}),
},
);
expect(queryByLabelText(/email/i)).not.toBeInTheDocument();
expect(queryByLabelText(/password/i)).not.toBeInTheDocument();
expect(queryByText(/refine project/i)).not.toBeInTheDocument();
expect(queryByTestId("refine-logo")).not.toBeInTheDocument();
expect(
queryByRole("button", {
name: /sign up/i,
}),
).not.toBeInTheDocument();
expect(queryByTestId("custom-content")).toBeInTheDocument();
});
it("should customizable with renderContent", async () => {
const { queryByText, queryByTestId, queryByRole, queryByLabelText } =
render(
<RegisterPage
renderContent={(content: any, title: any) => (
<div>
{title}
<div data-testid="custom-content">
<div>Custom Content</div>
</div>
{content}
</div>
)}
/>,
{
wrapper: TestWrapper({}),
},
);
expect(queryByText(/custom content/i)).toBeInTheDocument();
expect(queryByTestId("custom-content")).toBeInTheDocument();
expect(queryByLabelText(/email/i)).toBeInTheDocument();
expect(queryByLabelText(/password/i)).toBeInTheDocument();
expect(queryByText(/refine project/i)).toBeInTheDocument();
expect(queryByTestId("refine-logo")).toBeInTheDocument();
expect(
queryByRole("button", {
name: /sign up/i,
}),
).toBeInTheDocument();
expect(queryByTestId("custom-content")).toBeInTheDocument();
});
it("should run register mutation when form is submitted", async () => {
const registerMock = jest.fn().mockResolvedValue({ success: true });
const { getByLabelText, getAllByText } = render(<RegisterPage />, {
wrapper: TestWrapper({
authProvider: {
...mockAuthProvider,
register: registerMock,
},
}),
});
fireEvent.change(getByLabelText(/email/i), {
target: { value: "demo@refine.dev" },
});
fireEvent.change(getByLabelText(/password/i), {
target: { value: "demo" },
});
fireEvent.click(getAllByText(/sign up/i)[1]);
await waitFor(() => {
expect(registerMock).toBeCalledTimes(1);
});
expect(registerMock).toBeCalledWith({
email: "demo@refine.dev",
password: "demo",
});
});
it("should run register mutation when provider button is clicked", async () => {
const registerMock = jest.fn().mockResolvedValue({ success: true });
const { getByText } = render(
<RegisterPage
providers={[
{
name: "Google",
label: "Google",
},
]}
/>,
{
wrapper: TestWrapper({
authProvider: {
...mockAuthProvider,
register: registerMock,
},
}),
},
);
expect(getByText(/google/i)).toBeInTheDocument();
fireEvent.click(getByText(/google/i));
await waitFor(() => {
expect(registerMock).toBeCalledTimes(1);
});
expect(registerMock).toBeCalledWith({
providerName: "Google",
});
});
it("should work with new router provider Link", async () => {
jest.spyOn(console, "error").mockImplementation((message) => {
console.warn(message);
});
const LinkComponentMock = jest.fn();
render(<RegisterPage />, {
wrapper: TestWrapper({
routerProvider: mockRouterBindings({
fns: {
Link: LinkComponentMock,
},
}),
}),
});
expect(LinkComponentMock).toBeCalledWith(
expect.objectContaining({
to: "/login",
}),
{},
);
});
it("should not render form when `hideForm` is true", async () => {
const { queryByLabelText, getByText, queryByRole } = render(
<RegisterPage
hideForm
providers={[
{
name: "google",
label: "Google",
},
{ name: "github", label: "GitHub" },
]}
/>,
{
wrapper: TestWrapper({}),
},
);
expect(queryByLabelText(/email/i)).not.toBeInTheDocument();
expect(queryByLabelText(/password/i)).not.toBeInTheDocument();
expect(
queryByRole("link", {
name: /forgot password/i,
}),
).not.toBeInTheDocument();
expect(
queryByRole("button", {
name: /sign up/i,
}),
).not.toBeInTheDocument();
expect(getByText(/google/i)).toBeInTheDocument();
expect(getByText(/github/i)).toBeInTheDocument();
expect(
queryByRole("link", {
name: /sign in/i,
}),
).toBeInTheDocument();
});
it.each([true, false])("should has default links", async (hideForm) => {
const { getByRole } = render(<RegisterPage hideForm={hideForm} />, {
wrapper: TestWrapper({}),
});
expect(
getByRole("link", {
name: /sign in/i,
}),
).toHaveAttribute("href", "/login");
});
it("should should accept 'mutationVariables'", async () => {
const registerMock = jest.fn().mockResolvedValue({ success: true });
const { getByRole, getByLabelText } = render(
<RegisterPage
mutationVariables={{
foo: "bar",
xyz: "abc",
}}
/>,
{
wrapper: TestWrapper({
authProvider: {
...mockAuthProvider,
register: registerMock,
},
}),
},
);
fireEvent.change(getByLabelText(/email/i), {
target: { value: "demo@refine.dev" },
});
fireEvent.change(getByLabelText(/password/i), {
target: { value: "demo" },
});
fireEvent.click(
getByRole("button", {
name: /sign up/i,
}),
);
await waitFor(() => {
expect(registerMock).toHaveBeenCalledWith({
foo: "bar",
xyz: "abc",
email: "demo@refine.dev",
password: "demo",
});
});
});
});
};

View File

@@ -0,0 +1,270 @@
import React, { type FC } from "react";
import {
fireEvent,
mockAuthProvider,
mockLegacyAuthProvider,
render,
TestWrapper,
waitFor,
} from "@test";
import type { UpdatePasswordPageProps } from "@refinedev/core";
export const pageUpdatePasswordTests = (
UpdatePasswordPage: FC<UpdatePasswordPageProps<any, any, any>>,
): void => {
describe("[@refinedev/ui-tests] Common Tests / Update Password Page", () => {
it("should render card title", async () => {
const { getByText } = render(<UpdatePasswordPage />, {
wrapper: TestWrapper({}),
});
expect(getByText(/set new password?/i)).toBeInTheDocument();
});
it("should render password input", async () => {
const { getByLabelText } = render(<UpdatePasswordPage />, {
wrapper: TestWrapper({}),
});
expect(getByLabelText("New Password")).toBeInTheDocument();
expect(getByLabelText("Confirm New Password")).toBeInTheDocument();
});
it("should render default <ThemedTitle> with icon", async () => {
const { getByText, getByTestId } = render(<UpdatePasswordPage />, {
wrapper: TestWrapper({}),
});
expect(getByText(/refine project/i)).toBeInTheDocument();
expect(getByTestId("refine-logo")).toBeInTheDocument();
});
it("should render custom title", async () => {
const { queryByText, queryByTestId } = render(
<UpdatePasswordPage title="Custom Title" />,
{
wrapper: TestWrapper({}),
},
);
expect(queryByText(/custom title/i)).toBeInTheDocument();
expect(queryByText(/refine project/i)).not.toBeInTheDocument();
expect(queryByTestId("refine-logo")).not.toBeInTheDocument();
});
it("should renderContent only", async () => {
const { queryByText, queryByTestId, queryByLabelText } = render(
<UpdatePasswordPage
renderContent={() => <div data-testid="custom-content" />}
/>,
{
wrapper: TestWrapper({}),
},
);
expect(queryByText(/refine project/i)).not.toBeInTheDocument();
expect(queryByTestId("refine-logo")).not.toBeInTheDocument();
expect(queryByLabelText("New Password")).not.toBeInTheDocument();
expect(queryByLabelText("Confirm New Password")).not.toBeInTheDocument();
expect(queryByTestId("custom-content")).toBeInTheDocument();
});
it("should not render title when is false", async () => {
const { queryByText } = render(<UpdatePasswordPage title={false} />, {
wrapper: TestWrapper({}),
});
expect(queryByText(/refine project/i)).not.toBeInTheDocument();
});
it("should pass contentProps", async () => {
const { getByTestId } = render(
<UpdatePasswordPage contentProps={{ "data-testid": "test" }} />,
{
wrapper: TestWrapper({}),
},
);
expect(getByTestId("test")).toBeInTheDocument();
});
it("should pass wrapperProps", async () => {
const { getByTestId } = render(
<UpdatePasswordPage wrapperProps={{ "data-testid": "test" }} />,
{
wrapper: TestWrapper({}),
},
);
expect(getByTestId("test")).toBeInTheDocument();
});
it("should customizable with renderContent", async () => {
const { queryByText, queryByTestId, getByLabelText } = render(
<UpdatePasswordPage
renderContent={(content: any, title: any) => (
<div>
{title}
<div data-testid="custom-content">
<div>Custom Content</div>
</div>
{content}
</div>
)}
/>,
{
wrapper: TestWrapper({}),
},
);
expect(queryByText(/custom content/i)).toBeInTheDocument();
expect(queryByTestId("custom-content")).toBeInTheDocument();
expect(queryByText(/refine project/i)).toBeInTheDocument();
expect(queryByTestId("refine-logo")).toBeInTheDocument();
expect(queryByTestId("custom-content")).toBeInTheDocument();
expect(getByLabelText("New Password")).toBeInTheDocument();
expect(getByLabelText("Confirm New Password")).toBeInTheDocument();
});
it("should run updatePassword mutation when form is submitted", async () => {
const updatePasswordMock = jest.fn().mockResolvedValue({ success: true });
const { getAllByLabelText, getByLabelText, getAllByText } = render(
<UpdatePasswordPage />,
{
wrapper: TestWrapper({
authProvider: {
...mockAuthProvider,
updatePassword: updatePasswordMock,
},
}),
},
);
fireEvent.change(getAllByLabelText(/password/i)[0], {
target: { value: "demo" },
});
fireEvent.change(getByLabelText(/confirm new password/i), {
target: { value: "demo" },
});
fireEvent.click(getAllByText(/update/i)[0]);
await waitFor(() => {
expect(updatePasswordMock).toBeCalledTimes(1);
});
expect(updatePasswordMock).toBeCalledWith({
password: "demo",
confirmPassword: "demo",
});
});
it("should run updatePassword mutation when form is submitted with legacy authProvider", async () => {
const updatePasswordMock = jest.fn().mockResolvedValue({ success: true });
const { getAllByLabelText, getByLabelText, getAllByText } = render(
<UpdatePasswordPage />,
{
wrapper: TestWrapper({
legacyAuthProvider: {
...mockLegacyAuthProvider,
updatePassword: updatePasswordMock,
},
}),
},
);
fireEvent.change(getAllByLabelText(/password/i)[0], {
target: { value: "demo" },
});
fireEvent.change(getByLabelText(/confirm new password/i), {
target: { value: "demo" },
});
fireEvent.click(getAllByText(/update/i)[0]);
await waitFor(() => {
expect(updatePasswordMock).toBeCalledTimes(1);
});
expect(updatePasswordMock).toBeCalledWith({
password: "demo",
confirmPassword: "demo",
});
});
it("if passwords are not matched, should display the validation error", async () => {
const { getAllByLabelText, getByLabelText, getByText } = render(
<UpdatePasswordPage />,
{
wrapper: TestWrapper({
authProvider: mockAuthProvider,
}),
},
);
fireEvent.change(getAllByLabelText(/password/i)[0], {
target: { value: "demo" },
});
fireEvent.change(getByLabelText(/confirm new password/i), {
target: { value: "demo2" },
});
fireEvent.click(getByText(/update/i));
await waitFor(() => {
expect(getByText(/asswords do not match/i)).toBeInTheDocument();
});
});
it("should should accept 'mutationVariables'", async () => {
const updatePasswordMock = jest.fn().mockResolvedValue({ success: true });
const { getByRole, getByLabelText, getAllByLabelText } = render(
<UpdatePasswordPage
mutationVariables={{
foo: "bar",
xyz: "abc",
}}
/>,
{
wrapper: TestWrapper({
authProvider: {
...mockAuthProvider,
updatePassword: updatePasswordMock,
},
}),
},
);
fireEvent.change(getAllByLabelText(/password/i)[0], {
target: { value: "demo" },
});
fireEvent.change(getByLabelText(/confirm new password/i), {
target: { value: "demo" },
});
fireEvent.click(
getByRole("button", {
name: /update/i,
}),
);
await waitFor(
() => {
expect(updatePasswordMock).toHaveBeenCalledWith({
foo: "bar",
xyz: "abc",
password: "demo",
confirmPassword: "demo",
});
},
{ timeout: 500 },
);
});
});
};

View File

@@ -0,0 +1,72 @@
import React from "react";
import type { RefineErrorPageProps } from "@refinedev/ui-types";
import {
fireEvent,
mockLegacyRouterProvider,
mockRouterBindings,
render,
TestWrapper,
waitFor,
} from "@test";
export const pageErrorTests = (
ErrorPage: React.ComponentType<RefineErrorPageProps>,
): void => {
describe("[@refinedev/ui-tests] Common Tests / Error Page", () => {
it("should render successfully", async () => {
const { container } = render(<ErrorPage />, {
wrapper: TestWrapper({}),
});
expect(container).toBeTruthy();
});
it("back home button should work with legacy router provider", async () => {
const pushMock = jest.fn();
const { getByText } = render(<ErrorPage />, {
wrapper: TestWrapper({
legacyRouterProvider: {
...mockLegacyRouterProvider(),
useHistory: () => ({
goBack: jest.fn(),
push: pushMock,
replace: jest.fn(),
}),
},
}),
});
fireEvent.click(getByText("Back Home"));
await waitFor(() => {
expect(pushMock).toBeCalledTimes(1);
});
expect(pushMock).toBeCalledWith("/");
});
it("back home button should work with router provider", async () => {
const goMock = jest.fn();
const { getByText } = render(<ErrorPage />, {
wrapper: TestWrapper({
routerProvider: mockRouterBindings({
fns: {
go: () => goMock,
},
}),
}),
});
fireEvent.click(getByText("Back Home"));
await waitFor(() => {
expect(goMock).toBeCalledTimes(1);
});
expect(goMock).toBeCalledWith({ to: "/" });
});
});
};

View File

@@ -0,0 +1,3 @@
export { pageErrorTests } from "./error";
export { pageReadyTests } from "./ready";
export * from "./auth";

View File

@@ -0,0 +1,18 @@
import React from "react";
import type { RefineReadyPageProps } from "@refinedev/ui-types";
import { render, TestWrapper } from "@test";
export const pageReadyTests = (
ReadyPage: React.ComponentType<RefineReadyPageProps>,
): void => {
describe("[@refinedev/ui-tests] Common Tests / Ready Page", () => {
it("should render successfully", async () => {
const { container } = render(<ReadyPage />, {
wrapper: TestWrapper({}),
});
expect(container).toBeTruthy();
});
});
};

View File

@@ -0,0 +1,19 @@
{
"extends": "./tsconfig.json",
"exclude": [
"node_modules",
"dist",
"**/*.spec.ts",
"**/*.test.ts",
"**/*.spec.tsx",
"**/*.test.tsx"
],
"compilerOptions": {
"outDir": "dist",
"declarationDir": "dist",
"declaration": true,
"emitDeclarationOnly": true,
"noEmit": false,
"declarationMap": true
}
}

View File

@@ -0,0 +1,21 @@
{
"include": ["src"],
"extends": "../../tsconfig.build.json",
"compilerOptions": {
"rootDir": "./src",
"baseUrl": ".",
"allowSyntheticDefaultImports": true,
"importHelpers": false,
"strict": true,
"paths": {
"@components/*": ["src/components/*"],
"@components": ["src/components"],
"@hooks/*": ["src/hooks/*"],
"@hooks": ["src/hooks"],
"@test/*": ["src/test/*"],
"@test": ["src/test"],
"@definitions/*": ["src/definitions/*"],
"@definitions": ["src/definitions"]
}
}
}

View File

@@ -0,0 +1,8 @@
{
"extends": "./tsconfig.json",
"include": ["test", "src/types"],
"compilerOptions": {
"rootDir": ".",
"types": ["jest"]
}
}

View File

@@ -0,0 +1,18 @@
import { defineConfig } from "tsup";
import { markAsExternalPlugin } from "../shared/mark-as-external-plugin";
export default defineConfig((options) => ({
entry: ["src/index.tsx"],
splitting: false,
sourcemap: true,
clean: false,
minify: true,
format: ["cjs", "esm"],
outExtension: ({ format }) => ({ js: format === "cjs" ? ".cjs" : ".mjs" }),
platform: "browser",
esbuildPlugins: [markAsExternalPlugin],
loader: {
".svg": "dataurl",
},
onSuccess: options.watch ? "pnpm types" : undefined,
}));

1
packages/ui-tests/util.d.ts vendored Normal file
View File

@@ -0,0 +1 @@
declare module "util";