diff --git a/.github/workflows/build-tests.yml b/.github/workflows/build-tests.yml index 412d9c6..5839f79 100644 --- a/.github/workflows/build-tests.yml +++ b/.github/workflows/build-tests.yml @@ -11,15 +11,14 @@ jobs: - name: Set up Node.js uses: actions/setup-node@v2 with: - node-version: "18.17.1" + node-version: '18.17.1' - name: Build Docker Image - run: docker build -t codehammers/cd-testv1:latest -f Dockerfile-dev . + run: docker build -t codehammers/ch-dev-dep-v2:latest -f Dockerfile-dev . - name: Install Root Dependencies - run: docker run codehammers/cd-testv1:latest npm install + run: docker run codehammers/ch-dev-dep-v2:latest npm install - name: Install Client Dependencies - run: docker run codehammers/cd-testv1:latest /bin/sh -c "cd client && npm install" - #- name: List node_modules - #run: docker run codehammers/cd-testv1:latest /bin/sh -c "ls node_modules && cd client && ls node_modules" + run: docker run codehammers/ch-dev-dep-v2:latest /bin/sh -c "cd client && npm install" + - run: LINT_COMMAND=lint docker-compose -f docker-compose-lint.yml up --abort-on-container-exit - run: docker-compose -f docker-compose-test.yml up --abort-on-container-exit env: JWT_SECRET: ${{ secrets.JWT_SECRET }} diff --git a/.prettierignore b/.prettierignore new file mode 100644 index 0000000..c794dcd --- /dev/null +++ b/.prettierignore @@ -0,0 +1,9 @@ +# These files should always be ignored by Prettier. + +build/ +coverage/ +dist/ +node_modules/ +package-lock.json +*.test.jsx.snap +*.test.tsx.snap \ No newline at end of file diff --git a/.prettierrc b/.prettierrc new file mode 100644 index 0000000..48f074b --- /dev/null +++ b/.prettierrc @@ -0,0 +1,8 @@ +{ + "semi": true, + "singleQuote": true, + "jsxSingleQuote": false, + "printWidth": 100, + "tabWidth": 2, + "trailingComma": "all" +} diff --git a/.vscode/settings.json b/.vscode/settings.json new file mode 100644 index 0000000..ca9e953 --- /dev/null +++ b/.vscode/settings.json @@ -0,0 +1,8 @@ +{ + "editor.defaultFormatter": "esbenp.prettier-vscode", + "editor.formatOnSave": true, + + "editor.codeActionsOnSave": { + "source.fixAll.eslint": "always" + } +} diff --git a/Dockerfile b/Dockerfile index dd07b46..0366321 100644 --- a/Dockerfile +++ b/Dockerfile @@ -1,5 +1,5 @@ # Set Node.js version -FROM node:18.17.1 as builder +FROM node:20.14.0 as builder # Set the working directory WORKDIR /usr/src/app @@ -22,7 +22,7 @@ RUN cd client && npm run build RUN npm run build # Set up the final image -FROM node:18.17.1 +FROM node:20.14.0 # Set the working directory WORKDIR /usr/src/app diff --git a/Dockerfile-dev b/Dockerfile-dev index 5e1c903..1c2fad0 100644 --- a/Dockerfile-dev +++ b/Dockerfile-dev @@ -1,5 +1,5 @@ # Set Node version -FROM node:18.17.1 +FROM node:20.14.0 # Install webpack globally RUN npm install webpack -g diff --git a/PULL_REQUEST_TEMPLATE.md b/PULL_REQUEST_TEMPLATE.md deleted file mode 100644 index 12fc0ba..0000000 --- a/PULL_REQUEST_TEMPLATE.md +++ /dev/null @@ -1,31 +0,0 @@ -Thank you for your contribution to Code Hammers's repository! Before merging, please fill out this brief form. - -### Description - -(What does this PR do? What problem does it solve? What feature does it add? What is the impact of this change? What other PRs does this depend on? If this PR is a work in progress, please add the `WIP` label.) - -### Jira Task - -(**Replace this text** with a link to your Jira task link here, or N/A) - -### Testing Instructions - -_**Note:** this does not mean pasting in how to run the Jest tests. Let us know if there is a specific flow we need to follow to test the fuctionality on the site itself, or if there is a specific page you want to test, etc. This is not needed for most PRs._ - -### Checklist - -Please go through each item of this checklist carefully. - -- [x] (Example checked item. Don't add any spaces around the "x".) - -#### All Team Members - -- [ ] I added a descriptive title to this PR. -- [ ] I filled out the **Description**, **Jira Task**, and **Testing Instructions** sections above. -- [ ] I added or updated [Jest unit tests]for any changes to components, server-side controllers, etc. -- [ ] I ran `npm run docker-test` in my local environment to check that this PR passes all unit tests. -- [ ] I did a quick check to make sure my code changes follow the recomended style guide. - -### Additional Notes, Images, etc. - -(This is space for any additional information or attachments you'd like to add as part of this PR.) diff --git a/__tests__/db.test.ts b/__tests__/db.test.ts index cd35c38..fc11d44 100644 --- a/__tests__/db.test.ts +++ b/__tests__/db.test.ts @@ -1,56 +1,52 @@ -import mongoose from "mongoose"; -import connectDB from "../server/config/db"; +import mongoose from 'mongoose'; +import connectDB from '../server/config/db'; -jest.mock("mongoose", () => ({ +jest.mock('mongoose', () => ({ connect: jest.fn().mockImplementation(() => Promise.resolve({ - connection: { host: "test-host" }, - }) + connection: { host: 'test-host' }, + }), ), })); -describe("connectDB", () => { +describe('connectDB', () => { let mockExit: jest.SpyInstance; let mockConsoleError: jest.SpyInstance; - let mockConsoleLog: jest.SpyInstance; beforeEach(() => { - mockExit = jest - .spyOn(process, "exit") - .mockImplementation((_code) => undefined as never); - mockConsoleError = jest.spyOn(console, "error").mockImplementation(); - mockConsoleLog = jest.spyOn(console, "log").mockImplementation(); + mockExit = jest.spyOn(process, 'exit').mockImplementation((_code) => undefined as never); + mockConsoleError = jest.spyOn(console, 'error').mockImplementation(); }); afterEach(() => { jest.clearAllMocks(); }); - it("should call mongoose.connect with MONGO_URI", async () => { - process.env.MONGO_URI = "test-mongo-uri"; + it('should call mongoose.connect with MONGO_URI', async () => { + process.env.MONGO_URI = 'test-mongo-uri'; await connectDB(); - expect(mongoose.connect).toHaveBeenCalledWith("test-mongo-uri"); + expect(mongoose.connect).toHaveBeenCalledWith('test-mongo-uri'); }); - it("should log an error and exit the process if mongoose.connect fails", async () => { - process.env.MONGO_URI = "test-mongo-uri"; + it('should log an error and exit the process if mongoose.connect fails', async () => { + process.env.MONGO_URI = 'test-mongo-uri'; (mongoose.connect as jest.Mock).mockImplementationOnce(() => { - throw new Error("test error"); + throw new Error('test error'); }); await connectDB(); - expect(mockConsoleError).toHaveBeenCalledWith("test error"); + expect(mockConsoleError).toHaveBeenCalledWith('test error'); expect(mockExit).toHaveBeenCalledWith(1); }); - it("should throw an error if MONGO_URI is not defined", async () => { + it('should throw an error if MONGO_URI is not defined', async () => { delete process.env.MONGO_URI; await connectDB(); expect(mockConsoleError).toHaveBeenCalledWith( - "MONGO_URI must be defined in the environment variables." + 'MONGO_URI must be defined in the environment variables.', ); expect(mockExit).toHaveBeenCalledWith(1); }); diff --git a/__tests__/errorController.test.ts b/__tests__/errorController.test.ts index c39b24d..e723193 100644 --- a/__tests__/errorController.test.ts +++ b/__tests__/errorController.test.ts @@ -1,7 +1,7 @@ -import { Request, Response, NextFunction } from "express"; -import { notFound, errorHandler } from "../server/controllers/errorControllers"; +import { Request, Response, NextFunction } from 'express'; +import { notFound, errorHandler } from '../server/controllers/errorControllers'; -describe("Middleware Tests", () => { +describe('Middleware Tests', () => { let mockRequest: Partial; let mockResponse: Partial; let mockNext: NextFunction; @@ -15,29 +15,24 @@ describe("Middleware Tests", () => { mockNext = jest.fn(); }); - describe("notFound Middleware", () => { - it("should return 404 and the original URL", () => { + describe('notFound Middleware', () => { + it('should return 404 and the original URL', () => { notFound(mockRequest as Request, mockResponse as Response, mockNext); expect(mockResponse.status).toHaveBeenCalledWith(404); expect(mockNext).toHaveBeenCalled(); }); }); - describe("errorHandler Middleware", () => { - it("should handle the error correctly", () => { - const mockError = new Error("Some error"); - errorHandler( - mockError, - mockRequest as Request, - mockResponse as Response, - mockNext - ); + describe('errorHandler Middleware', () => { + it('should handle the error correctly', () => { + const mockError = new Error('Some error'); + errorHandler(mockError, mockRequest as Request, mockResponse as Response, mockNext); expect(mockResponse.status).toHaveBeenCalledWith(400); expect(mockResponse.json).toHaveBeenCalledWith( expect.objectContaining({ message: expect.anything(), stack: expect.any(String), - }) + }), ); }); }); diff --git a/__tests__/index.test.ts b/__tests__/index.test.ts index fdc88ba..f39a5eb 100644 --- a/__tests__/index.test.ts +++ b/__tests__/index.test.ts @@ -1,10 +1,12 @@ -import request from "supertest"; -import app, { startServer } from "../server/index"; -import { Server } from "http"; +import request from 'supertest'; +import app, { startServer } from '../server/index'; +import { Server } from 'http'; +import mongoose from 'mongoose'; -let server: Server; +// TODO +/*eslint jest/no-disabled-tests: "off"*/ -import mongoose from "mongoose"; +let server: Server; beforeEach(() => { server = startServer(); @@ -24,49 +26,47 @@ afterAll(async () => { await mongoose.connection.close(); }); -describe("API Endpoints", () => { - xit("should get the API Running message in development", async () => { - const res = await request(app).get("/api"); +describe('API Endpoints', () => { + xit('should get the API Running message in development', async () => { + const res = await request(app).get('/api'); expect(res.statusCode).toEqual(200); - expect(res.body).toHaveProperty("message", "API Running - Hazzah!"); + expect(res.body).toHaveProperty('message', 'API Running - Hazzah!'); }); - xit("should serve the frontend files in production", async () => { - process.env.NODE_ENV = "production"; + xit('should serve the frontend files in production', async () => { + process.env.NODE_ENV = 'production'; - const res = await request(app).get("/"); + const res = await request(app).get('/'); expect(res.statusCode).toEqual(200); - expect(res.headers["content-type"]).toContain("text/html"); + expect(res.headers['content-type']).toContain('text/html'); }); - xit("should catch all routes and serve the frontend in production", async () => { - process.env.NODE_ENV = "production"; - const res = await request(app).get("/nonexistentroute"); + xit('should catch all routes and serve the frontend in production', async () => { + process.env.NODE_ENV = 'production'; + const res = await request(app).get('/nonexistentroute'); expect(res.statusCode).toEqual(200); - expect(res.headers["content-type"]).toContain("text/html"); + expect(res.headers['content-type']).toContain('text/html'); }); }); -describe("Server Start-Up", () => { - it("should start up the server if required as main module", async () => { +describe('Server Start-Up', () => { + it('should start up the server if required as main module', async () => { const originalLog = console.log; const logCalls: string[] = []; - console.log = jest.fn((...args: any[]) => { - logCalls.push(args.join(" ")); + console.log = jest.fn((...args: string[]) => { + logCalls.push(args.join(' ')); }); jest.resetModules(); await new Promise((resolve) => { if (server) { - server.on("listening", resolve); + server.on('listening', resolve); } }); - const hasExpectedLog = logCalls.some((log) => - log.includes("Server running in") - ); + const hasExpectedLog = logCalls.some((log) => log.includes('Server running in')); expect(hasExpectedLog).toBe(true); console.log = originalLog; diff --git a/__tests__/profileController.test.ts b/__tests__/profileController.test.ts index 614e860..5081639 100644 --- a/__tests__/profileController.test.ts +++ b/__tests__/profileController.test.ts @@ -1,20 +1,23 @@ -import { Request, Response, NextFunction } from "express"; +import { Request, Response, NextFunction } from 'express'; import { createProfile, updateProfile, getAllProfiles, getProfileById, -} from "../server/controllers/profileController"; -import Profile from "../server/models/profileModel"; +} from '../server/controllers/profileController'; +import Profile from '../server/models/profileModel'; -jest.mock("../server/models/profileModel", () => ({ +// TODO +/*eslint jest/no-disabled-tests: "off"*/ + +jest.mock('../server/models/profileModel', () => ({ findOneAndUpdate: jest.fn(), findOne: jest.fn(), create: jest.fn(), find: jest.fn(), })); -describe("Profile Controller Tests", () => { +describe('Profile Controller Tests', () => { let mockRequest: Partial; let mockResponse: Partial; let mockNext: NextFunction = jest.fn(); @@ -28,81 +31,77 @@ describe("Profile Controller Tests", () => { }; }); - describe("createProfile function", () => { - xit("should handle profile creation", async () => { + describe('createProfile function', () => { + xit('should handle profile creation', async () => { (Profile.create as jest.Mock).mockResolvedValue({ - _id: "someId", - bio: "I am Code", + _id: 'someId', + bio: 'I am Code', job: { - title: "Senior Developer", - company: "ACME Corp.", - description: "Working on various projects...", - date: "2021-04-07T00:00:00.000Z", + title: 'Senior Developer', + company: 'ACME Corp.', + description: 'Working on various projects...', + date: '2021-04-07T00:00:00.000Z', }, socials: { - linkedIn: "https://www.linkedin.com/in/yourprofile", - github: "https://github.com/yourprofile", - twitter: "https://twitter.com/yourprofile", - facebook: "https://www.facebook.com/yourprofile", - instagram: "https://www.instagram.com/yourprofile", + linkedIn: 'https://www.linkedin.com/in/yourprofile', + github: 'https://github.com/yourprofile', + twitter: 'https://twitter.com/yourprofile', + facebook: 'https://www.facebook.com/yourprofile', + instagram: 'https://www.instagram.com/yourprofile', }, }); mockRequest.body = { - user: "65117c94f000c9930ef5c0ee", - bio: "I am Code", + user: '65117c94f000c9930ef5c0ee', + bio: 'I am Code', job: { - title: "Senior Developer", - company: "ACME Corp.", - description: "Working on various projects", - date: "2021-04-07T00:00:00.000Z", + title: 'Senior Developer', + company: 'ACME Corp.', + description: 'Working on various projects', + date: '2021-04-07T00:00:00.000Z', }, socials: { - linkedIn: "https://www.linkedin.com/in/yourprofile", - github: "https://github.com/yourprofile", - twitter: "https://twitter.com/yourprofile", - facebook: "https://www.facebook.com/yourprofile", - instagram: "https://www.instagram.com/yourprofile", + linkedIn: 'https://www.linkedin.com/in/yourprofile', + github: 'https://github.com/yourprofile', + twitter: 'https://twitter.com/yourprofile', + facebook: 'https://www.facebook.com/yourprofile', + instagram: 'https://www.instagram.com/yourprofile', }, }; - await createProfile( - mockRequest as Request, - mockResponse as Response, - mockNext - ); + await createProfile(mockRequest as Request, mockResponse as Response, mockNext); expect(mockResponse.json).toHaveBeenCalledWith( expect.objectContaining({ - _id: "someId", - bio: "I am Code", + _id: 'someId', + bio: 'I am Code', job: { - title: "Senior Developer", - company: "ACME Corp.", - description: "Working on various projects...", - date: "2021-04-07T00:00:00.000Z", + title: 'Senior Developer', + company: 'ACME Corp.', + description: 'Working on various projects...', + date: '2021-04-07T00:00:00.000Z', }, socials: { - linkedIn: "https://www.linkedin.com/in/yourprofile", - github: "https://github.com/yourprofile", - twitter: "https://twitter.com/yourprofile", - facebook: "https://www.facebook.com/yourprofile", - instagram: "https://www.instagram.com/yourprofile", + linkedIn: 'https://www.linkedin.com/in/yourprofile', + github: 'https://github.com/yourprofile', + twitter: 'https://twitter.com/yourprofile', + facebook: 'https://www.facebook.com/yourprofile', + instagram: 'https://www.instagram.com/yourprofile', }, - }) + }), ); }); }); - describe("updateProfile function", () => { + describe('updateProfile function', () => { beforeEach(() => { jest.clearAllMocks(); mockRequest = { - params: { userID: "65117c94f000c9930ef5c0ee" }, + params: { userID: '65117c94f000c9930ef5c0ee' }, body: { - firstName: "Bobby", - lastName: "Orr", - email: "test@test.com", + firstName: 'Bobby', + lastName: 'Orr', + email: 'test@test.com', }, }; mockResponse = { @@ -112,122 +111,96 @@ describe("Profile Controller Tests", () => { mockNext = jest.fn(); }); - it("should handle profile update", async () => { + it('should handle profile update', async () => { (Profile.findOneAndUpdate as jest.Mock).mockResolvedValue({ - _id: "65117c94f000c9930ef5c0ee", - firstName: "Bobby", - lastName: "Orr", - email: "test@test.com", + _id: '65117c94f000c9930ef5c0ee', + firstName: 'Bobby', + lastName: 'Orr', + email: 'test@test.com', }); - await updateProfile( - mockRequest as Request, - mockResponse as Response, - mockNext - ); + await updateProfile(mockRequest as Request, mockResponse as Response, mockNext); expect(Profile.findOneAndUpdate).toHaveBeenCalledWith( - { user: "65117c94f000c9930ef5c0ee" }, + { user: '65117c94f000c9930ef5c0ee' }, mockRequest.body, - { new: true } + { new: true }, ); expect(mockResponse.status).toHaveBeenCalledWith(200); expect(mockResponse.json).toHaveBeenCalledWith(expect.any(Object)); }); - it("should handle errors in profile updating", async () => { - (Profile.findOneAndUpdate as jest.Mock).mockRejectedValue( - new Error("Update failed") - ); + it('should handle errors in profile updating', async () => { + (Profile.findOneAndUpdate as jest.Mock).mockRejectedValue(new Error('Update failed')); - await updateProfile( - mockRequest as Request, - mockResponse as Response, - mockNext - ); + await updateProfile(mockRequest as Request, mockResponse as Response, mockNext); expect(mockNext).toHaveBeenCalledWith(expect.anything()); }); - it("should handle the case where no profile is found", async () => { + it('should handle the case where no profile is found', async () => { (Profile.findOneAndUpdate as jest.Mock).mockResolvedValue(null); - await updateProfile( - mockRequest as Request, - mockResponse as Response, - mockNext - ); + await updateProfile(mockRequest as Request, mockResponse as Response, mockNext); expect(mockNext).toHaveBeenCalledWith( expect.objectContaining({ - log: "Express error in updateProfile Middleware - NO PROFILE FOUND", + log: 'Express error in updateProfile Middleware - NO PROFILE FOUND', status: 404, - message: { err: "An error occurred during profile update" }, - }) + message: { err: 'An error occurred during profile update' }, + }), ); }); }); - describe("getAllProfiles function", () => { - xit("should handle successful retrieval of all profiles", async () => { + describe('getAllProfiles function', () => { + xit('should handle successful retrieval of all profiles', async () => { const mockProfiles = [ - { _id: "1", user: "user1", bio: "Bio 1" }, - { _id: "2", user: "user2", bio: "Bio 2" }, + { _id: '1', user: 'user1', bio: 'Bio 1' }, + { _id: '2', user: 'user2', bio: 'Bio 2' }, ]; (Profile.find as jest.Mock).mockResolvedValue(mockProfiles); - await getAllProfiles( - mockRequest as Request, - mockResponse as Response, - mockNext - ); + await getAllProfiles(mockRequest as Request, mockResponse as Response, mockNext); expect(mockResponse.status).toHaveBeenCalledWith(201); expect(mockResponse.json).toHaveBeenCalledWith(mockProfiles); }); - it("should handle no profiles found", async () => { + it('should handle no profiles found', async () => { (Profile.find as jest.Mock).mockResolvedValue([]); - await getAllProfiles( - mockRequest as Request, - mockResponse as Response, - mockNext - ); + await getAllProfiles(mockRequest as Request, mockResponse as Response, mockNext); expect(mockNext).toHaveBeenCalledWith( expect.objectContaining({ - log: "There are no profiles to retrieve", + log: 'There are no profiles to retrieve', status: 404, - message: { err: "There were no profiles to retrieve" }, - }) + message: { err: 'There were no profiles to retrieve' }, + }), ); }); - it("should handle errors during profile retrieval", async () => { - const errorMessage = { message: "Error finding profiles" }; + it('should handle errors during profile retrieval', async () => { + const errorMessage = { message: 'Error finding profiles' }; (Profile.find as jest.Mock).mockRejectedValue(errorMessage); - await getAllProfiles( - mockRequest as Request, - mockResponse as Response, - mockNext - ); + await getAllProfiles(mockRequest as Request, mockResponse as Response, mockNext); expect(mockNext).toHaveBeenCalledWith( expect.objectContaining({ - log: "Express error in getAllProfiles Middleware", + log: 'Express error in getAllProfiles Middleware', status: 500, - message: { err: "An error occurred during profile creation" }, - }) + message: { err: 'An error occurred during profile creation' }, + }), ); }); }); - describe("getProfileById function", () => { + describe('getProfileById function', () => { beforeEach(() => { jest.clearAllMocks(); - mockRequest = { params: { userID: "someUserId" } }; + mockRequest = { params: { userID: 'someUserId' } }; mockResponse = { status: jest.fn().mockReturnThis(), json: jest.fn(), @@ -235,58 +208,46 @@ describe("Profile Controller Tests", () => { mockNext = jest.fn(); }); - it("should handle successful profile retrieval", async () => { + it('should handle successful profile retrieval', async () => { const mockProfile = { - _id: "someUserId", - bio: "User Bio", + _id: 'someUserId', + bio: 'User Bio', //ABBRIEVIATED PROFILE OBJECT }; (Profile.findOne as jest.Mock).mockResolvedValue(mockProfile); - await getProfileById( - mockRequest as Request, - mockResponse as Response, - mockNext - ); + await getProfileById(mockRequest as Request, mockResponse as Response, mockNext); expect(mockResponse.status).toHaveBeenCalledWith(200); expect(mockResponse.json).toHaveBeenCalledWith(mockProfile); }); - it("should handle profile not found", async () => { + it('should handle profile not found', async () => { (Profile.findOne as jest.Mock).mockResolvedValue(null); - await getProfileById( - mockRequest as Request, - mockResponse as Response, - mockNext - ); + await getProfileById(mockRequest as Request, mockResponse as Response, mockNext); expect(mockNext).toHaveBeenCalledWith( expect.objectContaining({ - log: "Profile does not exist", + log: 'Profile does not exist', status: 404, - message: { err: "An error occurred during profile retrieval" }, - }) + message: { err: 'An error occurred during profile retrieval' }, + }), ); }); - it("should handle errors", async () => { - const errorMessage = { message: "Error finding profile" }; + it('should handle errors', async () => { + const errorMessage = { message: 'Error finding profile' }; (Profile.findOne as jest.Mock).mockRejectedValue(errorMessage); - await getProfileById( - mockRequest as Request, - mockResponse as Response, - mockNext - ); + await getProfileById(mockRequest as Request, mockResponse as Response, mockNext); expect(mockNext).toHaveBeenCalledWith( expect.objectContaining({ - log: "Express error in getProfileById Middleware", + log: 'Express error in getProfileById Middleware', status: 500, - message: { err: "An error occurred during profile retrieval" }, - }) + message: { err: 'An error occurred during profile retrieval' }, + }), ); }); }); diff --git a/__tests__/userController.tests.ts b/__tests__/userController.tests.ts index d5edf80..7c537c2 100644 --- a/__tests__/userController.tests.ts +++ b/__tests__/userController.tests.ts @@ -1,24 +1,24 @@ -import { Request, Response, NextFunction } from "express"; +import { Request, Response, NextFunction } from 'express'; import { registerUser, authUser, getUserById, deleteUserByEmail, -} from "../server/controllers/userController"; -import User from "../server/models/userModel"; +} from '../server/controllers/userController'; +import User from '../server/models/userModel'; -jest.mock("../server/models/userModel", () => ({ +jest.mock('../server/models/userModel', () => ({ findOne: jest.fn(), create: jest.fn(), findOneAndDelete: jest.fn(), })); -jest.mock("../server/utils/generateToken", () => () => "someFakeToken"); +jest.mock('../server/utils/generateToken', () => () => 'someFakeToken'); -describe("User Controller Tests", () => { +describe('User Controller Tests', () => { let mockRequest: Partial; let mockResponse: Partial; //TODO Add some error test for global error handler - let mockNext: NextFunction = jest.fn(); + const mockNext: NextFunction = jest.fn(); beforeEach(() => { mockRequest = {}; @@ -30,128 +30,112 @@ describe("User Controller Tests", () => { }; }); - describe("registerUser function", () => { - xit("should handle user registration", async () => { + describe('registerUser function', () => { + xit('should handle user registration', async () => { (User.findOne as jest.Mock).mockResolvedValue(null); (User.create as jest.Mock).mockResolvedValue({ - _id: "someId", - firstName: "John", - lastName: "Doh", - email: "john@example.com", - password: "hashedPassword", + _id: 'someId', + firstName: 'John', + lastName: 'Doh', + email: 'john@example.com', + password: 'hashedPassword', }); mockRequest.body = { - firstName: "John", - lastName: "Doh", - email: "john@example.com", - password: "password", + firstName: 'John', + lastName: 'Doh', + email: 'john@example.com', + password: 'password', }; - await registerUser( - mockRequest as Request, - mockResponse as Response, - mockNext - ); + await registerUser(mockRequest as Request, mockResponse as Response, mockNext); expect(mockResponse.json).toHaveBeenCalledWith({ - _id: "someId", - firstName: "John", - lastName: "Doh", - email: "john@example.com", + _id: 'someId', + firstName: 'John', + lastName: 'Doh', + email: 'john@example.com', }); expect(mockResponse.cookie).toHaveBeenCalledWith( - "token", - "someFakeToken", - expect.any(Object) + 'token', + 'someFakeToken', + expect.any(Object), ); }); }); - describe("authUser function", () => { - it("should handle user authentication", async () => { + describe('authUser function', () => { + it('should handle user authentication', async () => { (User.findOne as jest.Mock).mockResolvedValue({ - _id: "someId", - firstName: "John", - lastName: "Doh", - email: "john@example.com", - password: "hashedPassword", + _id: 'someId', + firstName: 'John', + lastName: 'Doh', + email: 'john@example.com', + password: 'hashedPassword', matchPassword: jest.fn().mockResolvedValue(true), }); - mockRequest.body = { email: "john@example.com", password: "password" }; + mockRequest.body = { email: 'john@example.com', password: 'password' }; - await authUser( - mockRequest as Request, - mockResponse as Response, - mockNext - ); + await authUser(mockRequest as Request, mockResponse as Response, mockNext); expect(mockResponse.json).toHaveBeenCalledWith({ - _id: "someId", - firstName: "John", - lastName: "Doh", - email: "john@example.com", + _id: 'someId', + firstName: 'John', + lastName: 'Doh', + email: 'john@example.com', }); expect(mockResponse.cookie).toHaveBeenCalledWith( - "token", - "someFakeToken", - expect.any(Object) + 'token', + 'someFakeToken', + expect.any(Object), ); }); }); - describe("getUserById function", () => { - it("should get a user by ID", async () => { + describe('getUserById function', () => { + it('should get a user by ID', async () => { (User.findOne as jest.Mock).mockResolvedValue({ - _id: "someId", - firstName: "John", - lastName: "Doh", - email: "john@example.com", + _id: 'someId', + firstName: 'John', + lastName: 'Doh', + email: 'john@example.com', }); - mockRequest.params = { userId: "someId" }; + mockRequest.params = { userId: 'someId' }; - await getUserById( - mockRequest as Request, - mockResponse as Response, - mockNext - ); + await getUserById(mockRequest as Request, mockResponse as Response, mockNext); expect(mockResponse.json).toHaveBeenCalledWith( expect.objectContaining({ - _id: "someId", - firstName: "John", - lastName: "Doh", - email: "john@example.com", - }) + _id: 'someId', + firstName: 'John', + lastName: 'Doh', + email: 'john@example.com', + }), ); }); }); - describe("deleteUserByEmail function", () => { - it("should delete a user by email", async () => { + describe('deleteUserByEmail function', () => { + it('should delete a user by email', async () => { (User.findOneAndDelete as jest.Mock).mockResolvedValue({ - _id: "someId", - firstName: "John", - lastName: "Doh", - email: "john@example.com", + _id: 'someId', + firstName: 'John', + lastName: 'Doh', + email: 'john@example.com', }); - mockRequest.params = { email: "john@example.com" }; + mockRequest.params = { email: 'john@example.com' }; - await deleteUserByEmail( - mockRequest as Request, - mockResponse as Response, - mockNext - ); + await deleteUserByEmail(mockRequest as Request, mockResponse as Response, mockNext); expect(mockResponse.status).toHaveBeenCalledWith(200); expect(mockResponse.json).toHaveBeenCalledWith( expect.objectContaining({ - msg: "User successfully deleted!", - }) + msg: 'User successfully deleted!', + }), ); }); }); diff --git a/client/.babelrc b/client/.babelrc index b57f614..fa82860 100644 --- a/client/.babelrc +++ b/client/.babelrc @@ -1,7 +1,7 @@ { - "presets": [ - "@babel/preset-env", - "@babel/preset-typescript" - ] - } - \ No newline at end of file + "presets": [ + "@babel/preset-env", + ["@babel/preset-react", { "runtime": "automatic" }], + "@babel/preset-typescript" + ] +} diff --git a/client/__mocks__/fileMock.js b/client/__mocks__/fileMock.js index 0a445d0..86059f3 100644 --- a/client/__mocks__/fileMock.js +++ b/client/__mocks__/fileMock.js @@ -1 +1 @@ -module.exports = "test-file-stub"; +module.exports = 'test-file-stub'; diff --git a/client/jest.config.js b/client/jest.config.js index 148bab9..2529d9c 100644 --- a/client/jest.config.js +++ b/client/jest.config.js @@ -1,17 +1,17 @@ -const path = require("path"); +const path = require('path'); module.exports = { - preset: "ts-jest", - testEnvironment: "jest-environment-jsdom", + preset: 'ts-jest', + testEnvironment: 'jest-environment-jsdom', moduleNameMapper: { - "\\.(css|less|scss|sass)$": "identity-obj-proxy", - "\\.(jpg|jpeg|png|gif|eot|otf|webp|svg|ttf|woff|woff2|mp4|webm|wav|mp3|m4a|aac|oga)$": - "/__mocks__/fileMock.js", + '\\.(css|less|scss|sass)$': 'identity-obj-proxy', + '\\.(jpg|jpeg|png|gif|eot|otf|webp|svg|ttf|woff|woff2|mp4|webm|wav|mp3|m4a|aac|oga)$': + '/__mocks__/fileMock.js', }, - setupFilesAfterEnv: [path.resolve(__dirname, "./setupTests.ts")], + setupFilesAfterEnv: [path.resolve(__dirname, './setupTests.ts')], transform: { - "^.+\\.tsx?$": "ts-jest", + '^.+\\.tsx?$': 'ts-jest', }, - testRegex: "(/__tests__/.*|(\\.|/)(test|spec))\\.tsx?$", - moduleFileExtensions: ["ts", "tsx", "js", "jsx", "json", "node"], + testRegex: '(/__tests__/.*|(\\.|/)(test|spec))\\.tsx?$', + moduleFileExtensions: ['ts', 'tsx', 'js', 'jsx', 'json', 'node'], }; diff --git a/client/package.json b/client/package.json index 0558c84..1ecb31d 100644 --- a/client/package.json +++ b/client/package.json @@ -4,10 +4,10 @@ "description": "", "main": "webpack.config.js", "scripts": { - "test": "jest --coverage", + "test": "jest --colors --coverage", "type-check": "tsc --noEmit", - "start": "webpack serve --open --hot --mode development", - "build": "webpack --mode production" + "start": "webpack serve --open --hot --mode=development", + "build": "webpack --mode=production" }, "author": "", "license": "ISC", diff --git a/client/postcss.config.js b/client/postcss.config.js index 36b474a..2cb79e6 100644 --- a/client/postcss.config.js +++ b/client/postcss.config.js @@ -1,4 +1,4 @@ -const tailwindcss = require("tailwindcss"); +const tailwindcss = require('tailwindcss'); module.exports = { - plugins: ["postcss-preset-env", tailwindcss], + plugins: ['postcss-preset-env', tailwindcss], }; diff --git a/client/public/index.html b/client/public/index.html index 4ef4415..a35efcf 100644 --- a/client/public/index.html +++ b/client/public/index.html @@ -1,14 +1,11 @@ - + - + Code Hammers diff --git a/client/setupTests.ts b/client/setupTests.ts index d0de870..7b0828b 100644 --- a/client/setupTests.ts +++ b/client/setupTests.ts @@ -1 +1 @@ -import "@testing-library/jest-dom"; +import '@testing-library/jest-dom'; diff --git a/client/src/App.test.tsx b/client/src/App.test.tsx index 3f1086b..56650e8 100644 --- a/client/src/App.test.tsx +++ b/client/src/App.test.tsx @@ -1,45 +1,41 @@ -import React from "react"; -import { render, RenderResult, screen } from "@testing-library/react"; -import { MemoryRouter } from "react-router-dom"; -import App from "./App"; -import { Provider } from "react-redux"; -import configureStore from "redux-mock-store"; +import { ReactNode } from 'react'; +import { render, screen } from '@testing-library/react'; +import { MemoryRouter } from 'react-router-dom'; +import App from './App'; +import { Provider } from 'react-redux'; +import configureStore from 'redux-mock-store'; const mockStore = configureStore([]); const initialState = { user: { userData: null, - status: "idle", + status: 'idle', error: null, }, }; // MOCK THE BROWSER ROUTER SO THAT WE CAN WRAP APP COMPONENT WITH MEMORYROUTER -jest.mock("react-router-dom", () => { - const originalModule = jest.requireActual("react-router-dom"); +jest.mock('react-router-dom', () => { + const originalModule = jest.requireActual('react-router-dom'); return { ...originalModule, - BrowserRouter: ({ children }: { children: React.ReactNode }) => ( -
{children}
- ), + BrowserRouter: ({ children }: { children: ReactNode }) =>
{children}
, }; }); -describe("App Component", () => { - let component: RenderResult; - +describe('App Component', () => { beforeEach(() => { const store = mockStore(initialState); - component = render( + render( - + - + , ); }); - test("renders LandingPage component at root path", () => { - expect(screen.getByText("Code Hammers")).toBeInTheDocument(); + test('renders LandingPage component at root path', () => { + expect(screen.getByText('Code Hammers')).toBeInTheDocument(); }); }); diff --git a/client/src/App.tsx b/client/src/App.tsx index 8e25d45..55ff068 100644 --- a/client/src/App.tsx +++ b/client/src/App.tsx @@ -1,11 +1,10 @@ -import React from "react"; -import { BrowserRouter as Router, Route, Routes } from "react-router-dom"; -import LandingPage from "./pages/LandingPage"; -import NotFoundPage from "./pages/NotFoundPage/NotFoundPage"; -import AuthenticatedApp from "./AuthenticatedApp"; -import RegistrationPage from "./pages/RegistrationPage/RegistrationPage"; +import { BrowserRouter as Router, Route, Routes } from 'react-router-dom'; +import LandingPage from './pages/LandingPage'; +import NotFoundPage from './pages/NotFoundPage/NotFoundPage'; +import AuthenticatedApp from './AuthenticatedApp'; +import RegistrationPage from './pages/RegistrationPage/RegistrationPage'; -const App = (): JSX.Element => { +const App = () => { return ( diff --git a/client/src/AuthenticatedApp.test.tsx b/client/src/AuthenticatedApp.test.tsx index e6651a9..c5ced58 100644 --- a/client/src/AuthenticatedApp.test.tsx +++ b/client/src/AuthenticatedApp.test.tsx @@ -1,45 +1,41 @@ -import React from "react"; -import { render, RenderResult, screen } from "@testing-library/react"; -import { MemoryRouter } from "react-router-dom"; -import { Provider } from "react-redux"; -import configureStore from "redux-mock-store"; -import AuthenticatedApp from "./AuthenticatedApp"; +import { ReactNode } from 'react'; +import { render, screen } from '@testing-library/react'; +import { MemoryRouter } from 'react-router-dom'; +import { Provider } from 'react-redux'; +import configureStore from 'redux-mock-store'; +import AuthenticatedApp from './AuthenticatedApp'; const mockStore = configureStore([]); const initialState = { user: { userData: null, - status: "idle", + status: 'idle', error: null, }, }; -jest.mock("react-router-dom", () => { - const originalModule = jest.requireActual("react-router-dom"); +jest.mock('react-router-dom', () => { + const originalModule = jest.requireActual('react-router-dom'); return { ...originalModule, - BrowserRouter: ({ children }: { children: React.ReactNode }) => ( -
{children}
- ), + BrowserRouter: ({ children }: { children: ReactNode }) =>
{children}
, useNavigate: () => jest.fn(), }; }); -describe("AuthenticatedApp Component", () => { - let component: RenderResult; - +describe('AuthenticatedApp Component', () => { beforeEach(() => { const store = mockStore(initialState); - component = render( + render( - + - + , ); }); - test("renders with the BANNER placeholder text", () => { - expect(screen.getByText("Code Hammers")).toBeInTheDocument(); + test('renders with the BANNER placeholder text', () => { + expect(screen.getByText('Code Hammers')).toBeInTheDocument(); }); }); diff --git a/client/src/AuthenticatedApp.tsx b/client/src/AuthenticatedApp.tsx index 2b4aa74..052b49f 100644 --- a/client/src/AuthenticatedApp.tsx +++ b/client/src/AuthenticatedApp.tsx @@ -1,38 +1,35 @@ -import React, { useEffect, useState } from "react"; -import { Route, Routes, useNavigate } from "react-router-dom"; -import Header from "./components/Header/Header"; -import MainPage from "./pages/MainPage/MainPage"; -import Forums from "./pages/Forums/Forums"; -import Profiles from "./pages/Profiles/Profiles"; -import Profile from "./pages/Profile/Profile"; -import EditProfilePage from "./pages/EditProfilePage/EditProfilePage"; -import Directory from "./pages/DirectoryPage/DirectoryPage"; -import NotFoundPage from "./pages/NotFoundPage/NotFoundPage"; -import ApplicationsPage from "./pages/ApplicationsPage/ApplicationsPage"; -import CreateApplicationPage from "./pages/CreateApplicationPage/CreateApplicationPage"; -import UpdateApplicationPage from "./pages/UpdateApplicationPage/UpdateApplicationPage"; +import { useEffect } from 'react'; +import { Route, Routes, useNavigate } from 'react-router-dom'; +import Header from './components/Header/Header'; +import MainPage from './pages/MainPage/MainPage'; +import Forums from './pages/Forums/Forums'; +import Profiles from './pages/Profiles/Profiles'; +import Profile from './pages/Profile/Profile'; +import EditProfilePage from './pages/EditProfilePage/EditProfilePage'; +import Directory from './pages/DirectoryPage/DirectoryPage'; +import NotFoundPage from './pages/NotFoundPage/NotFoundPage'; +import ApplicationsPage from './pages/ApplicationsPage/ApplicationsPage'; +import CreateApplicationPage from './pages/CreateApplicationPage/CreateApplicationPage'; +import UpdateApplicationPage from './pages/UpdateApplicationPage/UpdateApplicationPage'; const AuthenticatedApp = () => { const navigate = useNavigate(); - const [isAuthenticated, setIsAuthenticated] = useState(false); useEffect(() => { const validateSession = async () => { try { - const response = await fetch("/api/auth/validate-session", { - method: "GET", - credentials: "include", + const response = await fetch('/api/auth/validate-session', { + method: 'GET', + credentials: 'include', }); const data = await response.json(); - if (response.ok && data.isAuthenticated) { - setIsAuthenticated(true); - } else { - navigate("/"); + if (!response.ok || !data.isAuthenticated) { + navigate('/'); } } catch (error) { - console.error("Session validation failed:", error); - navigate("/"); + console.error('Session validation failed:', error); + navigate('/'); } }; @@ -51,10 +48,7 @@ const AuthenticatedApp = () => { } /> } /> } /> - } - /> + } /> } />
diff --git a/client/src/app/hooks.ts b/client/src/app/hooks.ts index 72d1476..4691088 100644 --- a/client/src/app/hooks.ts +++ b/client/src/app/hooks.ts @@ -1,5 +1,5 @@ -import { TypedUseSelectorHook, useDispatch, useSelector } from "react-redux"; -import { RootState, AppDispatch } from "./store"; +import { TypedUseSelectorHook, useDispatch, useSelector } from 'react-redux'; +import { RootState, AppDispatch } from './store'; export const useAppDispatch = () => useDispatch(); export const useAppSelector: TypedUseSelectorHook = useSelector; diff --git a/client/src/app/store.ts b/client/src/app/store.ts index 867644a..fda35e5 100644 --- a/client/src/app/store.ts +++ b/client/src/app/store.ts @@ -1,10 +1,10 @@ -import { configureStore } from "@reduxjs/toolkit"; -import userReducer from "../features/user/userSlice"; -import profilesReducer from "../features/profiles/profilesSlice"; -import userProfileReducer from "../features/userProfile/userProfileSlice"; -import alumniReducer from "../features/alumni/alumniSlice"; -import applicationReducer from "../features/applications/applicationSlice"; -import applicationsReducer from "../features/applications/applicationsSlice"; +import { configureStore } from '@reduxjs/toolkit'; +import userReducer from '../features/user/userSlice'; +import profilesReducer from '../features/profiles/profilesSlice'; +import userProfileReducer from '../features/userProfile/userProfileSlice'; +import alumniReducer from '../features/alumni/alumniSlice'; +import applicationReducer from '../features/applications/applicationSlice'; +import applicationsReducer from '../features/applications/applicationsSlice'; export const store = configureStore({ reducer: { diff --git a/client/src/components/ApplicationDashBoard/ApplicationDashBoard.tsx b/client/src/components/ApplicationDashBoard/ApplicationDashBoard.tsx index 7044588..52f8b1a 100644 --- a/client/src/components/ApplicationDashBoard/ApplicationDashBoard.tsx +++ b/client/src/components/ApplicationDashBoard/ApplicationDashBoard.tsx @@ -1,6 +1,6 @@ -import React, { useEffect, useState } from "react"; -import axios from "axios"; -import { useAppSelector } from "../../app/hooks"; +import React, { useEffect, useState } from 'react'; +import axios from 'axios'; +import { useAppSelector } from '../../app/hooks'; interface IStatusCount { status: string; @@ -9,9 +9,7 @@ interface IStatusCount { const ApplicationDashboard = (): JSX.Element => { const [totalApplications, setTotalApplications] = useState(0); - const [applicationsByStatus, setApplicationsByStatus] = useState< - IStatusCount[] - >([]); + const [applicationsByStatus, setApplicationsByStatus] = useState([]); const [loading, setLoading] = useState(false); const [error, setError] = useState(null); const user = useAppSelector((state) => state.user.userData); @@ -20,17 +18,14 @@ const ApplicationDashboard = (): JSX.Element => { async function fetchAggregatedData() { setLoading(true); try { - const response = await axios.get( - `/api/applications/aggregated-user-stats/${user?._id}` - ); - const { totalApplications = 0, applicationsByStatus = [] } = - response.data || {}; + const response = await axios.get(`/api/applications/aggregated-user-stats/${user?._id}`); + const { totalApplications = 0, applicationsByStatus = [] } = response.data || {}; setTotalApplications(totalApplications); setApplicationsByStatus(applicationsByStatus); setLoading(false); } catch (err) { const error = err as Error; - console.error("Error fetching aggregated data:", error); + console.error('Error fetching aggregated data:', error); setError(error.message); setLoading(false); } diff --git a/client/src/components/Banner/Banner.test.tsx b/client/src/components/Banner/Banner.test.tsx index 7d7e4dd..4dba96c 100644 --- a/client/src/components/Banner/Banner.test.tsx +++ b/client/src/components/Banner/Banner.test.tsx @@ -1,28 +1,27 @@ -import React from "react"; -import { render, screen, fireEvent } from "@testing-library/react"; -import "@testing-library/jest-dom"; -import Banner from "./Banner"; -import { useAppDispatch, useAppSelector } from "../../app/hooks"; -import { logout } from "../../features/user/userSlice"; -import { useNavigate } from "react-router-dom"; - -jest.mock("../../app/hooks", () => ({ +import { render, screen, fireEvent } from '@testing-library/react'; +import '@testing-library/jest-dom'; +import Banner from './Banner'; +import { useAppDispatch, useAppSelector } from '../../app/hooks'; +import { logout } from '../../features/user/userSlice'; +import { useNavigate } from 'react-router-dom'; + +jest.mock('../../app/hooks', () => ({ useAppDispatch: jest.fn(), useAppSelector: jest.fn(), })); -jest.mock("../../features/user/userSlice", () => ({ +jest.mock('../../features/user/userSlice', () => ({ logout: jest.fn(), })); -jest.mock("react-router-dom", () => ({ +jest.mock('react-router-dom', () => ({ useNavigate: jest.fn(), })); -describe("Banner Component", () => { +describe('Banner Component', () => { const mockDispatch = jest.fn(); const mockNavigate = jest.fn(); const mockUserData = { - id: "123", - name: "John Doe", + id: '123', + name: 'John Doe', }; beforeEach(() => { @@ -31,7 +30,7 @@ describe("Banner Component", () => { (useAppSelector as jest.Mock).mockImplementation((selector) => selector({ user: { userData: mockUserData }, - }) + }), ); }); @@ -39,56 +38,56 @@ describe("Banner Component", () => { jest.clearAllMocks(); }); - it("renders the logo image correctly", () => { + it('renders the logo image correctly', () => { render(); - const logo = screen.getByAltText("Code Hammers Logo"); + const logo = screen.getByAltText('Code Hammers Logo'); expect(logo).toBeInTheDocument(); }); - it("renders the title text correctly", () => { + it('renders the title text correctly', () => { render(); - const title = screen.getByText("Code Hammers"); + const title = screen.getByText('Code Hammers'); expect(title).toBeInTheDocument(); }); - it("renders the Options button", () => { + it('renders the Options button', () => { render(); - const optionsButton = screen.getByRole("button", { name: "Options" }); + const optionsButton = screen.getByRole('button', { name: 'Options' }); expect(optionsButton).toBeInTheDocument(); }); - it("opens the dropdown and shows options when Options button is clicked", () => { + it('opens the dropdown and shows options when Options button is clicked', () => { render(); - const optionsButton = screen.getByRole("button", { name: "Options" }); + const optionsButton = screen.getByRole('button', { name: 'Options' }); fireEvent.click(optionsButton); - const profileOption = screen.getByText("Edit Profile"); - const logoutOption = screen.getByText("Logout"); + const profileOption = screen.getByText('Edit Profile'); + const logoutOption = screen.getByText('Logout'); expect(profileOption).toBeInTheDocument(); expect(logoutOption).toBeInTheDocument(); }); - it("handles navigation to Profile on clicking Go to Profile", () => { + it('handles navigation to Profile on clicking Go to Profile', () => { render(); - const optionsButton = screen.getByRole("button", { name: "Options" }); + const optionsButton = screen.getByRole('button', { name: 'Options' }); fireEvent.click(optionsButton); - const profileOption = screen.getByText("Edit Profile"); + const profileOption = screen.getByText('Edit Profile'); fireEvent.click(profileOption); - expect(mockNavigate).toHaveBeenCalledWith("editProfile"); + expect(mockNavigate).toHaveBeenCalledWith('editProfile'); }); - it("handles logout on clicking Logout", () => { + it('handles logout on clicking Logout', () => { render(); - const optionsButton = screen.getByRole("button", { name: "Options" }); + const optionsButton = screen.getByRole('button', { name: 'Options' }); fireEvent.click(optionsButton); - const logoutOption = screen.getByText("Logout"); + const logoutOption = screen.getByText('Logout'); fireEvent.click(logoutOption); expect(mockDispatch).toHaveBeenCalledWith(logout()); - expect(mockNavigate).toHaveBeenCalledWith("/"); + expect(mockNavigate).toHaveBeenCalledWith('/'); }); }); diff --git a/client/src/components/Banner/Banner.tsx b/client/src/components/Banner/Banner.tsx index 3729191..9fe9d75 100644 --- a/client/src/components/Banner/Banner.tsx +++ b/client/src/components/Banner/Banner.tsx @@ -1,32 +1,29 @@ -import React, { useState } from "react"; -import logo from "../../assets/hammer.png"; -import { useAppDispatch, useAppSelector } from "../../app/hooks"; -import { logout } from "../../features/user/userSlice"; -import { useNavigate } from "react-router-dom"; +import { useState } from 'react'; +import logo from '../../assets/hammer.png'; +import { useAppDispatch } from '../../app/hooks'; +import { logout } from '../../features/user/userSlice'; +import { useNavigate } from 'react-router-dom'; -const Banner = (): JSX.Element => { - const user = useAppSelector((state) => state.user.userData); +const Banner = () => { const dispatch = useAppDispatch(); const navigate = useNavigate(); const [showDropdown, setShowDropdown] = useState(false); const handleLogout = () => { dispatch(logout()); - navigate("/"); + navigate('/'); //TODO CLEAR ALL STATE }; const goToEditProfile = () => { - navigate("editProfile"); + navigate('editProfile'); setShowDropdown(false); }; return (
Code Hammers Logo
-

- Code Hammers -

+

Code Hammers

); diff --git a/client/src/components/Forums/CreateThread/CreateThread.tsx b/client/src/components/Forums/CreateThread/CreateThread.tsx index 3d3efd2..03240c6 100644 --- a/client/src/components/Forums/CreateThread/CreateThread.tsx +++ b/client/src/components/Forums/CreateThread/CreateThread.tsx @@ -1,54 +1,41 @@ -import React, { useState, ChangeEvent, FormEvent } from "react"; -import axios from "axios"; +import { useState, ChangeEvent, FormEvent } from 'react'; +import axios from 'axios'; interface CreateThreadProps { forumId: string; onClose: () => void; } -const CreateThread: React.FC = ({ forumId, onClose }) => { +const CreateThread = ({ forumId, onClose }: CreateThreadProps) => { const [formData, setFormData] = useState<{ title: string; content: string }>({ - title: "", - content: "", + title: '', + content: '', }); const { title, content } = formData; - const handleChange = ( - e: ChangeEvent - ) => { + const handleChange = (e: ChangeEvent) => { setFormData({ ...formData, [e.target.name]: e.target.value }); }; const handleSubmit = async (e: FormEvent) => { e.preventDefault(); try { - const response = await axios.post( - `/api/forums/${forumId}/threads`, - formData, - { - withCredentials: true, - } - ); - console.log("Thread created:", response.data); + await axios.post(`/api/forums/${forumId}/threads`, formData, { + withCredentials: true, + }); onClose(); } catch (error) { - console.error("Failed to create thread:", error); + console.error('Failed to create thread:', error); //TODO userfeedback with errors } }; return (
-
+
-
-