diff --git a/coral/.eslintrc.cjs b/coral/.eslintrc.cjs index c348ade65f..a001ceae13 100644 --- a/coral/.eslintrc.cjs +++ b/coral/.eslintrc.cjs @@ -70,7 +70,7 @@ module.exports = { "plugins": [ "react", "@typescript-eslint", - "no-relative-import-paths" + "no-relative-import-paths", ], "settings": { "react": { @@ -81,6 +81,9 @@ module.exports = { "no-relative-import-paths/no-relative-import-paths": [ "error" ], - "no-restricted-imports": strip_ids_from_no_restricted_imports(NO_RESTRICTED_IMPORTS_RULES) + "no-unused-vars": "off", + "no-restricted-imports": strip_ids_from_no_restricted_imports(NO_RESTRICTED_IMPORTS_RULES), + "@typescript-eslint/no-unused-vars": "error", + "@typescript-eslint/no-explicit-any": "error" } } diff --git a/coral/docs/directory-structure.md b/coral/docs/directory-structure.md index 3565103d2a..d27c5f7aa0 100644 --- a/coral/docs/directory-structure.md +++ b/coral/docs/directory-structure.md @@ -65,9 +65,10 @@ The structure is inspired in big parts by: │ │ ├── ... │ │ ├── index.ts │ │── pages/ - │ │ ├── page-one - │ │ ├── ... - │ │ └── index.ts + │ │ ├── index.tsx + │ │ ├── Login.tsx + │ │ ├── Users.tsx + │ │ └── ... │ └── router.tsx ├── domain/ │ ├── name-of-domain-one/ @@ -127,7 +128,7 @@ In this directory, we group similar code together based on one feature. The stru #### `pages` -Contains every page of the application, one file per page. The structure in this folder should mirror the structure of the web apps views and routing. If there is a link to a "dashboard" page in the web app, there should be a `Dashboard` page inside `pages`. +Contains every page of the application, one file per page. The structure in this folder should mirror the structure of the web apps views and routing. If there is a link to a "dashboard" page in the web app, there should be a `Dashboard` page inside `pages`. The files don't need to have a `Page` pre- or postfix since the directory already gives that information. #### `services` diff --git a/coral/index.html b/coral/index.html index 6ebc58fd0a..7610416fff 100644 --- a/coral/index.html +++ b/coral/index.html @@ -8,6 +8,6 @@
- + diff --git a/coral/package.json b/coral/package.json index ce2c7f9648..231643e81f 100644 --- a/coral/package.json +++ b/coral/package.json @@ -28,15 +28,21 @@ ] }, "dependencies": { - "@aivenio/design-system": "^18.4.3", + "@aivenio/design-system": "^19.0.1", + "@hookform/resolvers": "^2.9.10", "react": "^18.2.0", "react-dom": "^18.2.0", - "react-router-dom": "^6.4.2" + "react-hook-form": "^7.38.0", + "react-router-dom": "^6.4.2", + "zod": "^3.19.1" }, "devDependencies": { + "@testing-library/dom": "^8.19.0", "@testing-library/jest-dom": "^5.16.5", "@testing-library/react": "^13.4.0", + "@testing-library/user-event": "^14.4.3", "@types/jest": "^29.2.0", + "@types/lodash": "^4.14.182", "@types/node": "*", "@types/react": "^18.0.17", "@types/react-dom": "^18.0.6", @@ -53,7 +59,7 @@ "jest": "^29.2.1", "jest-environment-jsdom": "^29.2.1", "lint-staged": "^13.0.3", - "lodash": "4.x", + "lodash": "^4.17.21", "prettier": "^2.7.1", "ts-jest": "^29.0.3", "ts-node": "^10.9.1", diff --git a/coral/pnpm-lock.yaml b/coral/pnpm-lock.yaml index 9e83e8f9ac..8de75fd92e 100644 --- a/coral/pnpm-lock.yaml +++ b/coral/pnpm-lock.yaml @@ -1,10 +1,14 @@ lockfileVersion: 5.4 specifiers: - '@aivenio/design-system': ^18.4.3 + '@aivenio/design-system': ^19.0.1 + '@hookform/resolvers': ^2.9.10 + '@testing-library/dom': ^8.19.0 '@testing-library/jest-dom': ^5.16.5 '@testing-library/react': ^13.4.0 + '@testing-library/user-event': ^14.4.3 '@types/jest': ^29.2.0 + '@types/lodash': ^4.14.182 '@types/node': '*' '@types/react': ^18.0.17 '@types/react-dom': ^18.0.6 @@ -21,26 +25,34 @@ specifiers: jest: ^29.2.1 jest-environment-jsdom: ^29.2.1 lint-staged: ^13.0.3 - lodash: 4.x + lodash: ^4.17.21 prettier: ^2.7.1 react: ^18.2.0 react-dom: ^18.2.0 + react-hook-form: ^7.38.0 react-router-dom: ^6.4.2 ts-jest: ^29.0.3 ts-node: ^10.9.1 typescript: ^4.6.4 vite: ^3.1.0 + zod: ^3.19.1 dependencies: - '@aivenio/design-system': 18.4.3_pvnihi4muhoy7kenlmiyxwzuqy + '@aivenio/design-system': 19.0.1_pvnihi4muhoy7kenlmiyxwzuqy + '@hookform/resolvers': 2.9.10_react-hook-form@7.38.0 react: 18.2.0 react-dom: 18.2.0_react@18.2.0 + react-hook-form: 7.38.0_react@18.2.0 react-router-dom: 6.4.2_biqbaboplfbrettd7655fr4n2y + zod: 3.19.1 devDependencies: + '@testing-library/dom': 8.19.0 '@testing-library/jest-dom': 5.16.5 '@testing-library/react': 13.4.0_biqbaboplfbrettd7655fr4n2y + '@testing-library/user-event': 14.4.3_aaq3sbffpfe3jnxzm2zngsddei '@types/jest': 29.2.0 + '@types/lodash': 4.14.186 '@types/node': 18.11.2 '@types/react': 18.0.21 '@types/react-dom': 18.0.6 @@ -70,8 +82,8 @@ packages: resolution: {integrity: sha512-+u76oB43nOHrF4DDWRLWDCtci7f3QJoEBigemIdIeTi1ODqjx6Tad9NCVnPRwewWlKkVab5PlK8DCtPTyX7S8g==} dev: true - /@aivenio/design-system/18.4.3_pvnihi4muhoy7kenlmiyxwzuqy: - resolution: {integrity: sha512-zwVQR1JM7PuUPsyLBcsANqA61ikKEp+HmMn0RTh94lOMZogOPGF919GltF0uDUTKmZyRYTQnno4K/ldQ6c1uiw==} + /@aivenio/design-system/19.0.1_pvnihi4muhoy7kenlmiyxwzuqy: + resolution: {integrity: sha512-nN4KwqmqCLzYZFqelb28hMcg9D3K6FZgePpJAD8m91tWCa6W6AGjsVVSaU6HwOA4TU79898dAAAX5fqdEpcVPg==} peerDependencies: lodash: 4.x react: 16.x || 17.x @@ -527,6 +539,14 @@ packages: - supports-color dev: true + /@hookform/resolvers/2.9.10_react-hook-form@7.38.0: + resolution: {integrity: sha512-JIL1DgJIlH9yuxcNGtyhsWX/PgNltz+5Gr6+8SX9fhXc/hPbEIk6wPI82nhgvp3uUb6ZfAM5mqg/x7KR7NAb+A==} + peerDependencies: + react-hook-form: ^7.0.0 + dependencies: + react-hook-form: 7.38.0_react@18.2.0 + dev: false + /@humanwhocodes/config-array/0.10.7: resolution: {integrity: sha512-MDl6D6sBsaV452/QSdX+4CXIjZhIcI0PELsxUjk4U828yd58vk3bTIvk/6w5FY+4hIy9sLW0sfrV7K7Kc++j/w==} engines: {node: '>=10.10.0'} @@ -922,6 +942,15 @@ packages: react-dom: 18.2.0_react@18.2.0 dev: true + /@testing-library/user-event/14.4.3_aaq3sbffpfe3jnxzm2zngsddei: + resolution: {integrity: sha512-kCUc5MEwaEMakkO5x7aoD+DLi02ehmEM2QCGWvNqAS1dV/fAvORWEjnjsEIvml59M7Y5kCkWN6fCCyPOe8OL6Q==} + engines: {node: '>=12', npm: '>=6'} + peerDependencies: + '@testing-library/dom': '>=7.21.4' + dependencies: + '@testing-library/dom': 8.19.0 + dev: true + /@tootallnate/once/2.0.0: resolution: {integrity: sha512-XCuKFP5PS55gnMVu3dty8KPatLqUoy/ZYzDzAGCQ8JNFCkLXzmI7vNHCR+XpbZaMWQK/vQubr7PkYq8g470J/A==} engines: {node: '>= 10'} @@ -1017,6 +1046,10 @@ packages: resolution: {integrity: sha512-wOuvG1SN4Us4rez+tylwwwCV1psiNVOkJeM3AUWUNWg/jDQY2+HE/444y5gc+jBmRqASOm2Oeh5c1axHobwRKQ==} dev: true + /@types/lodash/4.14.186: + resolution: {integrity: sha512-eHcVlLXP0c2FlMPm56ITode2AgLMSa6aJ05JTTbYbI+7EMkCEE5qk2E41d5g2lCVTqRe0GnnRFurmlCsDODrPw==} + dev: true + /@types/node/18.11.2: resolution: {integrity: sha512-BWN3M23gLO2jVG8g/XHIRFWiiV4/GckeFIqbU/C4V3xpoBBWSMk4OZomouN0wCkfQFPqgZikyLr7DOYDysIkkw==} dev: true @@ -3973,6 +4006,15 @@ packages: resolution: {integrity: sha512-rtGImPZ0YyLrscKI9xTpV8psd6I8VAtjKCzQDlzyDvqJA8XOW78TXYQwNRNd8g8JZnDu8q9Fu/1v4HPAVwVdHA==} dev: false + /react-hook-form/7.38.0_react@18.2.0: + resolution: {integrity: sha512-gxWW1kMeru9xR1GoR+Iw4hA+JBOM3SHfr4DWCUKY0xc7Vv1MLsF109oHtBeWl9shcyPFx67KHru44DheN0XY5A==} + engines: {node: '>=12.22.0'} + peerDependencies: + react: ^16.8.0 || ^17 || ^18 + dependencies: + react: 18.2.0 + dev: false + /react-is/16.13.1: resolution: {integrity: sha512-24e6ynE2H+OKt4kqsOvNd8kBpV65zoxbA4BVsEOB3ARVWQki/DHzaUoC5KuON/BiccDaCCTZBuOcfZs70kR8bQ==} @@ -4816,3 +4858,7 @@ packages: resolution: {integrity: sha512-rVksvsnNCdJ/ohGc6xgPwyN8eheCxsiLM8mxuE/t/mOVqJewPuO1miLpTHQiRgTKCLexL4MeAFVagts7HmNZ2Q==} engines: {node: '>=10'} dev: true + + /zod/3.19.1: + resolution: {integrity: sha512-LYjZsEDhCdYET9ikFu6dVPGp2YH9DegXjdJToSzD9rO6fy4qiRYFoyEYwps88OseJlPyl2NOe2iJuhEhL7IpEA==} + dev: false diff --git a/coral/src/app/components/Form.test.tsx b/coral/src/app/components/Form.test.tsx new file mode 100644 index 0000000000..d799a446c5 --- /dev/null +++ b/coral/src/app/components/Form.test.tsx @@ -0,0 +1,153 @@ +import { Button } from "@aivenio/design-system"; +import type { RenderResult } from "@testing-library/react"; +import { cleanup, render, screen, waitFor } from "@testing-library/react"; +import userEvent from "@testing-library/user-event"; +import React from "react"; +import type { DeepPartial, FieldValues } from "react-hook-form"; +import { + Form, + SubmitErrorHandler, + SubmitHandler, + TextInput, + useForm, +} from "src/app/components/Form"; +import { z, ZodSchema } from "zod"; + +type WrapperProps = { + schema: ZodSchema; + defaultValues?: DeepPartial; + onSubmit: SubmitHandler; + onError: SubmitErrorHandler; +}; + +const Wrapper = ({ + schema, + defaultValues, + onSubmit, + onError, + children, +}: React.PropsWithChildren>): React.ReactElement => { + const form = useForm({ schema, defaultValues }); + return ( +
+ {children} +
+ ); +}; + +describe("Form", () => { + const onSubmit = jest.fn(); + const onError = jest.fn(); + let results: RenderResult; + let user: ReturnType; + + beforeEach(() => { + user = userEvent.setup(); + }); + + afterEach(() => { + cleanup(); + onSubmit.mockClear(); + onError.mockClear(); + }); + + const renderForm = ( + children: React.ReactNode, + { + schema, + defaultValues, + }: { schema: ZodSchema; defaultValues?: DeepPartial } + ) => { + return render( + + schema={schema} + defaultValues={defaultValues} + onSubmit={onSubmit} + onError={onError} + > + {children} +