diff --git a/client/assets/css/__mocks__/styleMock.js b/client/assets/css/__mocks__/styleMock.js new file mode 100644 index 00000000..a0995453 --- /dev/null +++ b/client/assets/css/__mocks__/styleMock.js @@ -0,0 +1 @@ +module.exports = {}; \ No newline at end of file diff --git a/client/package.json b/client/package.json index 692fa418..bcd1a066 100644 --- a/client/package.json +++ b/client/package.json @@ -75,6 +75,9 @@ "jest": { "transformIgnorePatterns": [ "/node_modules/(?!(axios)/)" - ] + ], + "moduleNameMapper": { + "\\.(css|less)$": "/assets/css/__mocks__/styleMock.js" + } } } diff --git a/client/src/components/CreateProject.test.tsx b/client/src/components/CreateProject.test.tsx new file mode 100644 index 00000000..1e50e4db --- /dev/null +++ b/client/src/components/CreateProject.test.tsx @@ -0,0 +1,74 @@ +import React from 'react'; +import { render, fireEvent, screen, getByLabelText } from '../utils/test-utils'; +import CreateProject from './CreateProject'; +import { BrowserRouter } from 'react-router-dom'; + +describe('CreateProject rendering tests', () => { + const onClose = jest.fn(); + + it('renders the submit form', () => { + render( + + + + ); + const titleInput = screen.getByLabelText('Title:'); + const descriptionInput = screen.getByText('Description:'); + const imgInput = screen.getByLabelText('Image:'); + const tagsInput = screen.getByLabelText('Tags:'); + const submitBtn = screen.getByRole('submit-button'); + const closeBtn = screen.getByRole('close-button'); + expect(titleInput).toBeInTheDocument(); + expect(descriptionInput).toBeInTheDocument(); + expect(imgInput).toBeInTheDocument(); + expect(tagsInput).toBeInTheDocument(); + expect(submitBtn).toBeInTheDocument(); + expect(closeBtn).toBeInTheDocument(); + }) + + it('executes onClose() when closeBtn is clicked', () => { + render( + + + + ); + const closeBtn = screen.getByRole('close-button'); + fireEvent.click(closeBtn) + expect(onClose).toHaveBeenCalled(); + }) + + it('Does not render the component if open is false', () => { + render( + + + + ); + expect(document.querySelector('.overlay')).not.toBeInTheDocument(); + }) + + + it('Requires title and img inputs to be filled in order to submit the form', () => { + render( + + + + ); + const titleInput = screen.getByLabelText('Title:'); + const imgInput = screen.getByLabelText('Image:'); + const submitButton = screen.getByRole('submit-button'); + expect(submitButton).toBeDisabled(); + + fireEvent.change(titleInput, { target: { value: 'Not empty' } }); + expect(submitButton).toBeDisabled(); + + render( + + + + ); + fireEvent.change(imgInput, { target: { files: ['file'] } }); + fireEvent.change(titleInput, { target: { value: 'Not empty' } }); + expect(submitButton).not.toBeDisabled(); + }) +}) + diff --git a/client/src/components/CreateProject.tsx b/client/src/components/CreateProject.tsx index ca974f14..f130afaf 100644 --- a/client/src/components/CreateProject.tsx +++ b/client/src/components/CreateProject.tsx @@ -1,14 +1,13 @@ -import React, { useState, useContext, Requireable } from 'react' -import '../styles/createProject.css' -import UserContext from '../context/UserContext' -import { UserContextType } from '../types/user.type' -import Axios from 'axios' -import { AxiosResponse } from 'axios' -import { useNavigate } from 'react-router-dom' -import ReactQuill from 'react-quill' -import 'react-quill/dist/quill.snow.css' -import Project from '../types/project.type' -import http from '../services/api.service' +import React, { useState, useContext, Requireable } from 'react'; +import '../styles/createProject.css'; +import UserContext from '../context/UserContext'; +import { UserContextType } from '../types/user.type'; +import Axios from 'axios'; +import { useNavigate } from 'react-router-dom'; +import ReactQuill from 'react-quill'; +import 'react-quill/dist/quill.snow.css'; +import Project from '../types/project.type'; +import http from '../services/api.service'; const initialState: Project = { title: '', @@ -21,8 +20,7 @@ const initialState: Project = { createdBy: null, tags: [], followers: [], - quillValue: '' -} +}; interface CreateProjectProps { open: boolean | Requireable @@ -30,23 +28,27 @@ interface CreateProjectProps { } const CreateProject: React.FC = ({ open, onClose }) => { - if (!open) return null - const navigate = useNavigate() - const [selectedFile, setSelectedFile] = useState() // We might not need an initial state here - const { user } = useContext(UserContext) - const [projectInfo, setProjectInfo] = useState(initialState) - const [quillValue, setQuillValue] = useState('') + if (!open) return null; + const navigate = useNavigate(); + const [selectedFile, setSelectedFile] = useState(); // We might not need an initial state here + const { user } = useContext(UserContext); + const [projectInfo, setProjectInfo] = useState(initialState); + const [quillValue, setQuillValue] = useState(''); + const [submitDisabled, setSubmitDisabled] = useState(true); function handleChange(e: React.ChangeEvent) { const { name, value } = e.target setProjectInfo((prev) => ({ ...prev, - [name]: value - })) + [name]: value, + })); + projectInfo.image ? setSubmitDisabled(false) : setSubmitDisabled(true); + } function handleFileInputChange(e: React.ChangeEvent) { - if (e.target.files) setSelectedFile(e.target.files[0]) + if (e.target.files) setSelectedFile(e.target.files[0]); + projectInfo.title ? setSubmitDisabled(false) : setSubmitDisabled(true); } async function handleSubmit(e: React.FormEvent) { @@ -67,22 +69,25 @@ const CreateProject: React.FC = ({ open, onClose }) => { console.log(error) } - const project: Project = initialState - project.date = new Date().toLocaleDateString() - project.author = `${user?.firstName} ${user?.lastName}` - project.title = projectInfo.title - project.tags = projectInfo.tags - project.quillValue = quillValue - project.image = image - project.createdBy = user + const project: Project = initialState; + project.date = new Date().toLocaleDateString(); + project.author = `${user?.firstName} ${user?.lastName}`; + project.title = projectInfo.title; + project.tags = projectInfo.tags; + project.description = quillValue; + project.image = image; + project.createdBy = user; + const response = await http.createProject(project) //project should be stringified, review api.service if (response!.status > 400) { alert('Error creating Project') return } else { - navigate(`/posts/${project.id}`) - onClose() + navigate(`/posts/${project.id}`); + onClose(); + setSubmitDisabled(true); + } } @@ -93,6 +98,7 @@ const CreateProject: React.FC = ({ open, onClose }) => { @@ -100,6 +106,7 @@ const CreateProject: React.FC = ({ open, onClose }) => {
= ({ open, onClose }) => { diff --git a/client/src/components/Login.tsx b/client/src/components/Login.tsx new file mode 100644 index 00000000..72d0e16c --- /dev/null +++ b/client/src/components/Login.tsx @@ -0,0 +1,87 @@ +import React from 'react'; +import { useContext, useState } from 'react'; +import { useNavigate, Link } from 'react-router-dom'; +import UserContext from '../context/UserContext'; +import '../styles/login.css'; +import http from '../services/api.service'; +import { useForm, SubmitHandler } from 'react-hook-form'; + +interface ICredentials { + email: string; + password: string; +} + +const initialCredentials: ICredentials = { + email: '', + password: '', +}; + +interface IFormInput { + email: string; + password: string; +} + +function Login() { + const { user, setUser } = useContext(UserContext); + const [credentials, setCredentials] = useState(initialCredentials); + const navigate = useNavigate(); + const { + register, + handleSubmit, + formState: { errors }, + } = useForm(); + + const onSubmit: SubmitHandler = async (data) => { + const response = await http.login(credentials); + if (response!.status === 401) { + alert('Wrong email or password'); + return; + } else { + setUser(response!.data); + navigate('/home'); + } + }; + + return ( +
+
+

Login

+ + + + {errors.email && {errors.email.message}} + + + {errors.password && ( + {errors.password.message} + )} + + + Dont have an account? + Sign Up +
+
+ ); +} + +export default Login; diff --git a/client/src/types/project.type.tsx b/client/src/types/project.type.tsx index c3ef356d..415d67b5 100644 --- a/client/src/types/project.type.tsx +++ b/client/src/types/project.type.tsx @@ -10,8 +10,7 @@ export default interface Project { createdBy?: User | null; date: string; chat: string[]; - tags: string[]; + tags: string[] | string; followers: User[] | string[] | []; - quillValue?: string, }