diff --git a/web/package.json b/web/package.json index 8db99c8..1153141 100644 --- a/web/package.json +++ b/web/package.json @@ -24,6 +24,7 @@ "start": "react-scripts start", "build": "react-scripts build", "test": "react-scripts test", + "test:coverage": "react-scripts test --coverage --watchAll false", "eject": "react-scripts eject" }, "jest": { @@ -47,6 +48,7 @@ ] }, "devDependencies": { + "@testing-library/jest-dom": "^5.12.0", "@types/axios": "^0.14.0", "@types/node": "^12.0.0", "@types/react": "^16.9.0", diff --git a/web/src/__tests__/components/Input.spec.ts b/web/src/__tests__/components/Input.spec.ts new file mode 100644 index 0000000..038d6cf --- /dev/null +++ b/web/src/__tests__/components/Input.spec.ts @@ -0,0 +1,69 @@ +import React from 'react'; + +import { render, fireEvent, wait } from '@testing-library/react'; +import Input from '../../components/Input'; + +jest.mock('@unform/core', () => { + return { + useField() { + return { + fieldName: 'email', + defaultValue: '', + error: '', + registerField: jest.fn(), + }; + }, + }; +}); + +describe('Input component', () => { + it('should be able to render an input', () => { + const { getByPlaceholderText } = render( + , + ); + + expect(getByPlaceholderText('E-mail')).toBeTruthy(); + }); + + it('should render highlight on input focus', async () => { + const { getByPlaceholderText, getByTestId } = render( + , + ); + + const inputElement = getByPlaceholderText('E-mail'); + const containerElement = getByTestId('input-container'); + + fireEvent.focus(inputElement); + + await wait(() => { + expect(containerElement).toHaveStyle('border-color: #ff9000;'); + expect(containerElement).toHaveStyle('color: #ff9000;'); + }); + + fireEvent.blur(inputElement); + + await wait(() => { + expect(containerElement).not.toHaveStyle('border-color: #ff9000;'); + expect(containerElement).not.toHaveStyle('color: #ff9000;'); + }); + }); + + it('should keep input border highlight when input filled', async () => { + const { getByPlaceholderText, getByTestId } = render( + , + ); + + const inputElement = getByPlaceholderText('E-mail'); + const containerElement = getByTestId('input-container'); + + fireEvent.change(inputElement, { + target: { value: 'johndoe@example.com.br' }, + }); + + fireEvent.blur(inputElement); + + await wait(() => { + expect(containerElement).toHaveStyle('color: #ff9000;'); + }); + }); +}); diff --git a/web/src/__tests__/pages/SignIn.spec.tsx b/web/src/__tests__/pages/SignIn.spec.tsx index 6dc994e..ba427ec 100644 --- a/web/src/__tests__/pages/SignIn.spec.tsx +++ b/web/src/__tests__/pages/SignIn.spec.tsx @@ -1,19 +1,98 @@ import React from 'react' -import { render } from '@testing-library/react' +import { render, fireEvent, wait } from '@testing-library/react' import SignIn from '../../pages/SignIn' +const mockedHistoryPush = jest.fn() +const mockedSignIn = jest.fn() +const mockedAddToast = jest.fn() + jest.mock('react-router-dom', () => { return { - useHistory: jest.fn(), + useHistory: () => ({ + push: mockedHistoryPush + }), Link: ({ children }: { children: React.ReactNode }) => children } }) +jest.mock('../../hooks/auth', () => { + return { + useAuth: () => ({ + signIn: mockedSignIn + }) + } +}) + +jest.mock('../../hooks/toast', () => { + return { + useToast: () => ({ + addToast: mockedAddToast + }) + } +}) + describe('SignIn Page', () => { - it('should be able to sign in', () => { - const { debug } = render() + beforeEach(() => { + mockedHistoryPush.mockClear() + }) + + it('should be able to sign in', async () => { + const { getByPlaceholderText, getByText } = render() + + const emailField = getByPlaceholderText('E-mail') + const passwordField = getByPlaceholderText('Senha') + const buttonElement = getByText('Entrar') + + fireEvent.change(emailField, { target: { value: 'johndoe@example.com' } }) + fireEvent.change(passwordField, { target: { value: '123456' } }) + + fireEvent.click(buttonElement) + + await wait(() => { + expect(mockedHistoryPush).toHaveBeenCalledWith('/dashboard') + }) + }) + + it('should not be able to sign in with invalid credentials', async () => { + const { getByPlaceholderText, getByText } = render() + + const emailField = getByPlaceholderText('E-mail') + const passwordField = getByPlaceholderText('Senha') + const buttonElement = getByText('Entrar') + + fireEvent.change(emailField, { target: { value: 'not-valid-email' } }) + fireEvent.change(passwordField, { target: { value: '123456' } }) + + fireEvent.click(buttonElement) + + await wait(() => { + expect(mockedHistoryPush).not.toHaveBeenCalled() + }) + }) + + it('should display an error if login fails', async () => { + mockedSignIn.mockImplementation(() => { + throw new Error() + }) + + const { getByPlaceholderText, getByText } = render() + + const emailField = getByPlaceholderText('E-mail') + const passwordField = getByPlaceholderText('Senha') + const buttonElement = getByText('Entrar') + + fireEvent.change(emailField, { target: { value: 'johndoe@example.com' } }) + fireEvent.change(passwordField, { target: { value: '123456' } }) + + fireEvent.click(buttonElement) - debug() + await wait(() => { + expect(mockedAddToast).toHaveBeenCalledWith( + expect.objectContaining({ + type: 'error' + }) + ) + }) }) }) diff --git a/web/src/pages/SignIn/index.tsx b/web/src/pages/SignIn/index.tsx index 0d439ff..cd9b41f 100644 --- a/web/src/pages/SignIn/index.tsx +++ b/web/src/pages/SignIn/index.tsx @@ -115,4 +115,5 @@ const SingIn: React.FC = () => { ); } + export default SingIn; diff --git a/web/yarn.lock b/web/yarn.lock index a3f0a38..74a15d8 100644 --- a/web/yarn.lock +++ b/web/yarn.lock @@ -1095,7 +1095,7 @@ dependencies: regenerator-runtime "^0.13.4" -"@babel/runtime@^7.10.2": +"@babel/runtime@^7.10.2", "@babel/runtime@^7.9.2": version "7.14.0" resolved "https://registry.yarnpkg.com/@babel/runtime/-/runtime-7.14.0.tgz#46794bc20b612c5f75e62dd071e24dfd95f1cbe6" integrity sha512-JELkvo/DlpNdJ7dlyw/eY7E0suy5i5GQH+Vlxaq1nsNJ+H7f4Vtv3jMeCEgRhZZQFXTjldYfQgv2qmM6M1v5wA== @@ -1517,6 +1517,20 @@ lz-string "^1.4.4" pretty-format "^26.6.2" +"@testing-library/jest-dom@^5.12.0": + version "5.12.0" + resolved "https://registry.yarnpkg.com/@testing-library/jest-dom/-/jest-dom-5.12.0.tgz#6a5d340b092c44b7bce17a4791b47d9bc2c61443" + integrity sha512-N9Y82b2Z3j6wzIoAqajlKVF1Zt7sOH0pPee0sUHXHc5cv2Fdn23r+vpWm0MBBoGJtPOly5+Bdx1lnc3CD+A+ow== + dependencies: + "@babel/runtime" "^7.9.2" + "@types/testing-library__jest-dom" "^5.9.1" + aria-query "^4.2.2" + chalk "^3.0.0" + css "^3.0.0" + css.escape "^1.5.1" + lodash "^4.17.15" + redent "^3.0.0" + "@testing-library/react@^11.2.6": version "11.2.6" resolved "https://registry.yarnpkg.com/@testing-library/react/-/react-11.2.6.tgz#586a23adc63615985d85be0c903f374dab19200b" @@ -1623,7 +1637,7 @@ dependencies: "@types/istanbul-lib-report" "*" -"@types/jest@^26.0.23": +"@types/jest@*", "@types/jest@^26.0.23": version "26.0.23" resolved "https://registry.yarnpkg.com/@types/jest/-/jest-26.0.23.tgz#a1b7eab3c503b80451d019efb588ec63522ee4e7" integrity sha512-ZHLmWMJ9jJ9PTiT58juykZpL7KjwJywFN3Rr2pTSkyQfydf/rk22yS7W8p5DaVUMQ2BQC7oYiU3FjbTM/mYrOA== @@ -1730,6 +1744,13 @@ "@types/react" "*" csstype "^3.0.2" +"@types/testing-library__jest-dom@^5.9.1": + version "5.9.5" + resolved "https://registry.yarnpkg.com/@types/testing-library__jest-dom/-/testing-library__jest-dom-5.9.5.tgz#5bf25c91ad2d7b38f264b12275e5c92a66d849b0" + integrity sha512-ggn3ws+yRbOHog9GxnXiEZ/35Mow6YtPZpd7Z5mKDeZS/o7zx3yAle0ov/wjhVB5QT4N2Dt+GNoGCdqkBGCajQ== + dependencies: + "@types/jest" "*" + "@types/uuid@^8.3.0": version "8.3.0" resolved "https://registry.yarnpkg.com/@types/uuid/-/uuid-8.3.0.tgz#215c231dff736d5ba92410e6d602050cce7e273f" @@ -3092,6 +3113,14 @@ chalk@^1.1.3: strip-ansi "^3.0.0" supports-color "^2.0.0" +chalk@^3.0.0: + version "3.0.0" + resolved "https://registry.yarnpkg.com/chalk/-/chalk-3.0.0.tgz#3f73c2bf526591f574cc492c51e2456349f844e4" + integrity sha512-4D3B6Wf41KOYRFdszmDqMCGq5VV/uMAB273JILmO+3jAlh8X4qDtdtgCR3fxtbLEMzSx22QdhnDcJvu2u1fVwg== + dependencies: + ansi-styles "^4.1.0" + supports-color "^7.1.0" + chalk@^4.0.0: version "4.1.1" resolved "https://registry.yarnpkg.com/chalk/-/chalk-4.1.1.tgz#c80b3fab28bf6371e6863325eee67e618b77e6ad" @@ -3677,6 +3706,11 @@ css-what@^3.2.1: resolved "https://registry.yarnpkg.com/css-what/-/css-what-3.4.2.tgz#ea7026fcb01777edbde52124e21f327e7ae950e4" integrity sha512-ACUm3L0/jiZTqfzRM3Hi9Q8eZqd6IK37mMWPLz9PJxkLWllYeRf+EHUSHYEtFop2Eqytaq1FizFVh7XfBnXCDQ== +css.escape@^1.5.1: + version "1.5.1" + resolved "https://registry.yarnpkg.com/css.escape/-/css.escape-1.5.1.tgz#42e27d4fa04ae32f931a4b4d4191fa9cddee97cb" + integrity sha1-QuJ9T6BK4y+TGktNQZH6nN3ul8s= + css@^2.0.0: version "2.2.4" resolved "https://registry.yarnpkg.com/css/-/css-2.2.4.tgz#c646755c73971f2bba6a601e2cf2fd71b1298929" @@ -3687,6 +3721,15 @@ css@^2.0.0: source-map-resolve "^0.5.2" urix "^0.1.0" +css@^3.0.0: + version "3.0.0" + resolved "https://registry.yarnpkg.com/css/-/css-3.0.0.tgz#4447a4d58fdd03367c516ca9f64ae365cee4aa5d" + integrity sha512-DG9pFfwOrzc+hawpmqX/dHYHJG+Bsdb0klhyi1sDneOgGOXy9wQIC8hzyVp1e4NRYDBdxcylvywPkkXCHAzTyQ== + dependencies: + inherits "^2.0.4" + source-map "^0.6.1" + source-map-resolve "^0.6.0" + cssdb@^4.4.0: version "4.4.0" resolved "https://registry.yarnpkg.com/cssdb/-/cssdb-4.4.0.tgz#3bf2f2a68c10f5c6a08abd92378331ee803cddb0" @@ -7252,6 +7295,11 @@ mimic-fn@^2.1.0: resolved "https://registry.yarnpkg.com/mimic-fn/-/mimic-fn-2.1.0.tgz#7ed2c2ccccaf84d3ffcb7a69b57711fc2083401b" integrity sha512-OqbOk5oEQeAZ8WXWydlu9HJjz9WVdEIvamMCcXmuqUYjTknH/sqsWvhQ3vgwKFRR1HpjvNBKQ37nbJgYzGqGcg== +min-indent@^1.0.0: + version "1.0.1" + resolved "https://registry.yarnpkg.com/min-indent/-/min-indent-1.0.1.tgz#a63f681673b30571fbe8bc25686ae746eefa9869" + integrity sha512-I9jwMn07Sy/IwOj3zVkVik2JTvgpaykDZEigL6Rx6N9LbMywwUSMtxET+7lVoDLLd3O3IXwJwvuuns8UB/HeAg== + mini-create-react-context@^0.4.0: version "0.4.1" resolved "https://registry.yarnpkg.com/mini-create-react-context/-/mini-create-react-context-0.4.1.tgz#072171561bfdc922da08a60c2197a497cc2d1d5e" @@ -9357,6 +9405,14 @@ recursive-readdir@2.2.2: dependencies: minimatch "3.0.4" +redent@^3.0.0: + version "3.0.0" + resolved "https://registry.yarnpkg.com/redent/-/redent-3.0.0.tgz#e557b7998316bb53c9f1f56fa626352c6963059f" + integrity sha512-6tDA8g98We0zd0GvVeMT9arEOnTw9qM03L9cJXaCjrip1OO764RDBLBfrB4cwzNGDj5OA5ioymC9GkizgWJDUg== + dependencies: + indent-string "^4.0.0" + strip-indent "^3.0.0" + regenerate-unicode-properties@^8.2.0: version "8.2.0" resolved "https://registry.yarnpkg.com/regenerate-unicode-properties/-/regenerate-unicode-properties-8.2.0.tgz#e5de7111d655e7ba60c057dbe9ff37c87e65cdec" @@ -10083,6 +10139,14 @@ source-map-resolve@^0.5.0, source-map-resolve@^0.5.2: source-map-url "^0.4.0" urix "^0.1.0" +source-map-resolve@^0.6.0: + version "0.6.0" + resolved "https://registry.yarnpkg.com/source-map-resolve/-/source-map-resolve-0.6.0.tgz#3d9df87e236b53f16d01e58150fc7711138e5ed2" + integrity sha512-KXBr9d/fO/bWo97NXsPIAW1bFSBOuCnjbNTBMO7N59hsv5i9yzRDfcYwwt0l04+VqnKC+EwzvJZIP/qkuMgR/w== + dependencies: + atob "^2.1.2" + decode-uri-component "^0.2.0" + source-map-support@^0.5.6, source-map-support@~0.5.12: version "0.5.19" resolved "https://registry.yarnpkg.com/source-map-support/-/source-map-support-0.5.19.tgz#a98b62f86dcaf4f67399648c085291ab9e8fed61" @@ -10396,6 +10460,13 @@ strip-eof@^1.0.0: resolved "https://registry.yarnpkg.com/strip-eof/-/strip-eof-1.0.0.tgz#bb43ff5598a6eb05d89b59fcd129c983313606bf" integrity sha1-u0P/VZim6wXYm1n80SnJgzE2Br8= +strip-indent@^3.0.0: + version "3.0.0" + resolved "https://registry.yarnpkg.com/strip-indent/-/strip-indent-3.0.0.tgz#c32e1cee940b6b3432c771bc2c54bcce73cd3001" + integrity sha512-laJTa3Jb+VQpaC6DseHhF7dXVqHTfJPCRDaEbid/drOhgitgYku/letMUqOXFoWV0zIIUbjpdH2t+tYj4bQMRQ== + dependencies: + min-indent "^1.0.0" + strip-json-comments@^3.0.1: version "3.1.1" resolved "https://registry.yarnpkg.com/strip-json-comments/-/strip-json-comments-3.1.1.tgz#31f1281b3832630434831c310c01cccda8cbe006"