diff --git a/.github/pull_request_template.md b/.github/pull_request_template.md
new file mode 100644
index 000000000..c8972cd16
--- /dev/null
+++ b/.github/pull_request_template.md
@@ -0,0 +1,38 @@
+# Description
+
+Please describe the issue of the pull request and the changes
+
+
+
+## Type of Change
+
+Please check the options that apply
+
+- [ ] Bug fix (non-breaking change which fixes an issue)
+- [ ] New feature (non-breaking change which adds functionality)
+- [ ] Breaking change (fix or feature that would cause existing functionality to not work as expected)
+- [ ] This change requires a documentation update
+
+# How Has the Changes Been Tested?
+
+
+
+# Checklist:
+
+- [ ] My code follows the style guidelines of this project
+- [ ] Changes included in this pull request covers minimal topic
+- [ ] I have performed a self-review of my code
+- [ ] I have commented my code properly, particularly in hard-to-understand areas
+- [ ] I have made corresponding changes to the documentation
+- [ ] My changes generate no new warnings
+- [ ] I have added tests that prove my fix is effective or that my feature works
+- [ ] New and existing unit tests pass locally with my changes
+- [ ] Any dependent changes have been merged and published in downstream modules
diff --git a/CHANGE_LOG.md b/CHANGE_LOG.md
index 14d239c84..ba6f1d328 100644
--- a/CHANGE_LOG.md
+++ b/CHANGE_LOG.md
@@ -1,17 +1,61 @@
-
+
ReacType Change Log
-**Version 19.0.0 Changes**
-
-Changes:
-
-- Developer Improvement:
+## Version 20.0.0 Changes
+
+### Changes:
+
+- **Developer Improvement:**
+ - Migrated from Webpack to Vite, improving HMR times drastically
+ - Deployed app using Heroku instead of AWS decreasing time to deployment
+- **User Features:**
+ - **Collaboration Room:**
+ - Implemented live video, audio, and text functionality using socket.IO
+ - Added authentication and error handling to joining existing rooms
+ - **UI updates to enhance user experience:**
+ - In addition to drag to add, users are now able to click to add
+ - Updated left panel to include user information and settings
+ - Added scroll and zoom buttons to canvas. Scroll now automatically scrolls to bottom once enough elements are added
+ - Updated UI design to reflect a more modern look
+- **Bugs Fixed:**
+ - Canvas - All appropriate elements can now be nested - Nested Elements in the code preview now accurately reflect nested elements. They can also be dragged.
+ - Bottom Panel - Now opens by click instead of hover
+ - Users can now delete elements without first clicking it and then the X. This applies to the nested components as well.
+- **Landing Page:**
+ - Revamped entire landing page for a more modern look
+
+### Recommendations for Future Enhancements:
+
+- Fix bottom panel to only close upon clicking the icon, and not anywhere else
+- Populate settings tab in the left panel with additional functionality
+- Allow users to modify code dynamically in the code preview and reflect visual componenets in real time
+- Add zoom in and zoom out / scroll functionality to code preview and component tree
+- Convert from 95% to 100% typescript
+- Add more functionality to the nav bar
+- List all active rooms to join
+- Clean up unnecessary code / comments and deprecated libraries
+- a tags which are nested do not display accurate code in code preview
+- Eliminate all Webpack associated files/folders/dependencies/etc... now that we run on Vite
+- Remove the many deprecated dependencies
+- Add additional features to the live chat (Links, reactions, raise hand feature etc)
+- Allow live chat to be a popup and draggable outside of the app
+- Implement MUI/ShadcnUI in addition to standard html elements on left panel so that users are able to start off with pre styled elements
+- Make the app mobile responsive. Right now it does not work/look good on mobile
+- We had to deploy via Heroku due to time limitations and Vite. We would recommend going back to AWS with dockerized containers.
+- Light/Dark mode in the left settings tab
+- Update links in the footer of the landing page
+
+## Version 19.0.0 Changes
+
+### Changes:
+
+- **Developer Improvement:**
- Typescript conversion continued and now sits at ~95%
-- User Features:
- - Collaboration Room:
- - Bug Fixes:
+- **User Features:**
+ - **Collaboration Room:**
+ - **Bug Fixes:**
- Debug “Leave Room” functionality removing username from the users list
- Debug “Join Room” functionality so the current canvas does not reset upon new user joining collaboration
- Debug Code Preview button that sent error if toggled more than once and does not force toggled view to other users in the room
@@ -23,15 +67,15 @@ Changes:
- Significantly reduces the amount of data being passed among users by passing only the payload for each individual action, triggering singular updates for other users in the collaboration environment
- Added Event Emitters for each action that updates canvas
- Created a websocket service layer to maintain a single socket instance throughout the app
- - User List:
+ - **User List:**
- Displays the username and mouse cursor of all connected users in a particular room with a specific color scheme
- - UI updated to enhance user experience
+ - UI updated to enhance user experience:
- Rendered MUI Icons in HTML Element Panel
- Redesigned drag-n-drop to be more intuitive and professionalize application design.
- Updated styling to overall style and theme to maintain consistency across the application
- Removed Tailwind and CSS save buttons in Customization panel for cleaner UI and drying up repetitive functionality
-Recommendations for Future Enhancements:
+### Recommendations for Future Enhancements:
- Fix Undo & Redo functionality. Undo & Redo buttons on the customization page not functioning as expected.
- Update Electron for desktop application use. Resolve electron app functionality to coincide with web app functionality.
@@ -49,7 +93,7 @@ Recommendations for Future Enhancements:
- True real-time rendering so users can see components as they're being dragged onto the canvas, rather than only when they're placed.
- List of active rooms so users can simply pick one to join. Will likely be paired with a password feature for security, so only users with the proper credentials can join a particular room.
- Chat Feature in Collaboration Room
- - Currently, the live tracking cursor is rendered based on the users username/nickname. If multiple users create the same username/nickname, the most recent username/nickname creator will override the former. Possible solution to this issue could be to store cursor with the socket id rather than username/nickname. "
+ - Currently, the live tracking cursor is rendered based on the users username/nickname. If multiple users create the same username/nickname, the most recent username/nickname creator will override the former. Possible solution to this issue could be to store cursor with the socket id rather than username/nickname. "
**Version 18.0.0 Changes**
diff --git a/Dockerfile b/Dockerfile
index 7454cafeb..5eebc20b0 100644
--- a/Dockerfile
+++ b/Dockerfile
@@ -1,5 +1,5 @@
# Stage 1: Build
-FROM node:19-alpine as build
+FROM node:21.2.0-alpine as build
# python: required dependency for node alpine, shrinks image size from 2.17GB to 1.67GB
RUN apk add --no-cache --virtual .gyp \
@@ -16,7 +16,7 @@ RUN npm install --no-install-recommends --fetch-retry-maxtimeout 500000
COPY . .
# Stage 2: Runtime
-FROM node:19-alpine as runtime
+FROM node:21.2.0-alpine as runtime
WORKDIR /app
@@ -27,10 +27,10 @@ RUN npm install --no-install-recommends --only=production --fetch-retry-maxtimeo
# COPY --from=build /app/.env .env
COPY --from=build /app/config.js ./config.js
COPY --from=build /app/server ./server
-COPY --from=build /app/app/dist /app
+COPY --from=build /app/build /app
EXPOSE 5656
ENV IS_DOCKER true
-CMD [ "npm", "start" ]
+CMD [ "npm", "start" ]
\ No newline at end of file
diff --git a/Procfile b/Procfile
index 038261f7d..e9e2aa262 100644
--- a/Procfile
+++ b/Procfile
@@ -1 +1 @@
-web: node server/server.js
\ No newline at end of file
+web: npx tsx server/server.ts
\ No newline at end of file
diff --git a/README.md b/README.md
index 0a1c28f86..4c507c99e 100644
--- a/README.md
+++ b/README.md
@@ -1,4 +1,3 @@
-
-## File Structure of ReacType Version 19.0.0
+## File Structure of ReacType Version 20.0.0
Here is the main file structure:
-Given to us courtesy of our friends over at React Relay
-
-Please refer to the [Excalidraw](https://excalidraw.com/#json=JKwzVD5qx6lsfiHW1_pQ9,XJ6uDoehVu-1bsx0SMlC6w) provided by ReacType Version 14.0 for more details.
+Given to us courtesy of our friends over at React Relay
## Run ReacType using CLI
@@ -121,7 +119,7 @@ npm run test
npm run dev
```
-- Note that a .env with DEV_PORT, and a NODE_ENV flag (=production or development) are needed.
+- Note that DEV_PORT, NODE_ENV flag (=production or development) and VIDEOSDK token are needed in the .env file.
- Please note that the development build is not connected to the production server. `npm run dev` should spin up the development server from the server folder of this repo. For additional information, the readme is [here](https://github.com/open-source-labs/ReacType/blob/master/server/README.md). Alternatively, you can select "Continue as guest" on the login page of the app, which will not use any features that rely on the server (authentication and saving project data.)
- To run the development build of electron app
@@ -146,10 +144,8 @@ npm install
npm run build
```
-
- Start an instance
-
```bash
npm run start
```
@@ -158,7 +154,7 @@ npm run start
## Stack
-Typescript, React.js, Redux Toolkit, Javascript, ESM, Node.js (Express), HTML, CSS, MUI, GraphQL, Next.js, Gatsby.js, Electron, NoSQL, Webpack, TDD (Jest, React Testing Library, Playwright), OAuth 2.0, Websocket, SocketIO, Continuous Integration (Github Actions), Docker, AWS (ECR, Elastic Beanstalk), Ace Editor, Google Charts, React DnD
+Typescript, React.js, Redux Toolkit, Javascript, ESM, Node.js (Express), HTML, CSS, MUI, GraphQL, Next.js, Gatsby.js, Electron, NoSQL, Webpack, TDD (Jest, React Testing Library, Playwright), OAuth 2.0, Websocket, SocketIO, Continuous Integration (Github Actions), Docker, AWS (ECR, Elastic Beanstalk), Ace Editor, Google Charts, React DnD, Vite
## Contributions
diff --git a/__tests__/server.test.tsx b/__tests__/server.test.tsx
index c64ca9f96..d1c339383 100644
--- a/__tests__/server.test.tsx
+++ b/__tests__/server.test.tsx
@@ -2,7 +2,7 @@
* @jest-environment node
*/
-import marketplaceController from '../server/controllers/marketplaceController';
+import marketplaceController from '../server/controllers/marketplaceController';
import sessionController from '../server/controllers/sessionController';
import app from '../server/server';
import mockData from '../mockData';
@@ -11,33 +11,34 @@ import { Projects, Users, Sessions } from '../server/models/reactypeModels';
const request = require('supertest');
const mongoose = require('mongoose');
const mockNext = jest.fn(); // Mock nextFunction
-const MONGO_DB = process.env.MONGO_DB_TEST;
-const { state, projectToSave, user } = mockData
+const MONGO_DB = import.meta.env.MONGO_DB_TEST;
+const { state, projectToSave, user } = mockData;
const PORT = 8080;
beforeAll(async () => {
await mongoose.connect(MONGO_DB, {
useNewUrlParser: true,
- useUnifiedTopology: true,
+ useUnifiedTopology: true
});
});
afterAll(async () => {
-
- const result = await Projects.deleteMany({});//clear the projects collection after tests are done
- const result2 = await Users.deleteMany({_id: {$ne: '64f551e5b28d5292975e08c8'}});//clear the users collection after tests are done except for the mockdata user account
- const result3 = await Sessions.deleteMany({cookieId: {$ne: '64f551e5b28d5292975e08c8'}});
+ const result = await Projects.deleteMany({}); //clear the projects collection after tests are done
+ const result2 = await Users.deleteMany({
+ _id: { $ne: '64f551e5b28d5292975e08c8' }
+ }); //clear the users collection after tests are done except for the mockdata user account
+ const result3 = await Sessions.deleteMany({
+ cookieId: { $ne: '64f551e5b28d5292975e08c8' }
+ });
await mongoose.connection.close();
});
-
describe('Server endpoint tests', () => {
it('should pass this test request', async () => {
const response = await request(app).get('/test');
expect(response.status).toBe(200);
expect(response.text).toBe('test request is working');
});
-
// // test saveProject endpoint
// describe('/login', () => {
@@ -45,7 +46,7 @@ describe('Server endpoint tests', () => {
// it('responds with a status of 200 and json object equal to project sent', async () => {
// return request(app)
// .post('/login')
- // .set('Cookie', [`ssid=${user.userId}`])
+ // .set('Cookie', [`ssid=${user.userId}`])
// .set('Accept', 'application/json')
// .send(projectToSave)
// .expect(200)
@@ -62,14 +63,14 @@ describe('Server endpoint tests', () => {
it('responds with a status of 200 and json object equal to project sent', async () => {
return request(app)
.post('/saveProject')
- .set('Cookie', [`ssid=${user.userId}`])
+ .set('Cookie', [`ssid=${user.userId}`])
.set('Accept', 'application/json')
.send(projectToSave)
.expect(200)
.expect('Content-Type', /application\/json/)
.then((res) => expect(res.body.name).toBe(projectToSave.name));
});
- // });
+ // });
});
});
// test getProjects endpoint
@@ -79,7 +80,7 @@ describe('Server endpoint tests', () => {
return request(app)
.post('/getProjects')
.set('Accept', 'application/json')
- .set('Cookie', [`ssid=${user.userId}`])
+ .set('Cookie', [`ssid=${user.userId}`])
.send({ userId: projectToSave.userId })
.expect(200)
.expect('Content-Type', /json/)
@@ -94,16 +95,20 @@ describe('Server endpoint tests', () => {
describe('/deleteProject', () => {
describe('DELETE', () => {
it('responds with status of 200 and json object equal to deleted project', async () => {
- const response: Response = await request(app).post('/getProjects').set('Accept', 'application/json').set('Cookie', [`ssid=${user.userId}`]).send({ userId: projectToSave.userId });
+ const response: Response = await request(app)
+ .post('/getProjects')
+ .set('Accept', 'application/json')
+ .set('Cookie', [`ssid=${user.userId}`])
+ .send({ userId: projectToSave.userId });
const _id: String = response.body[0]._id;
const userId: String = user.userId;
return request(app)
.delete('/deleteProject')
- .set('Cookie', [`ssid=${user.userId}`])
+ .set('Cookie', [`ssid=${user.userId}`])
.set('Content-Type', 'application/json')
.send({ _id, userId })
.expect(200)
- .then((res) => expect(res.body._id).toBe(_id));
+ .then((res) => expect(res.body._id).toBe(_id));
});
});
});
@@ -112,12 +117,11 @@ describe('Server endpoint tests', () => {
describe('/publishProject', () => {
describe('POST', () => {
it('responds with status of 200 and json object equal to published project', async () => {
-
const projObj = await request(app)
.post('/saveProject')
- .set('Cookie', [`ssid=${user.userId}`])
+ .set('Cookie', [`ssid=${user.userId}`])
.set('Accept', 'application/json')
- .send(projectToSave)
+ .send(projectToSave);
const _id: String = projObj.body._id;
const project: String = projObj.body.project;
const comments: String = projObj.body.comments;
@@ -127,56 +131,65 @@ describe('Server endpoint tests', () => {
return request(app)
.post('/publishProject')
.set('Content-Type', 'application/json')
- .set('Cookie', [`ssid=${user.userId}`])
- .send({ _id, project, comments, userId, username, name })//_id, project, comments, userId, username, name
+ .set('Cookie', [`ssid=${user.userId}`])
+ .send({ _id, project, comments, userId, username, name }) //_id, project, comments, userId, username, name
.expect(200)
.then((res) => {
- expect(res.body._id).toBe(_id)
+ expect(res.body._id).toBe(_id);
expect(res.body.published).toBe(true);
- });
+ });
});
it('responds with status of 500 and error if userId and cookie ssid do not match', async () => {
- const projObj: Response = await request(app).post('/getProjects').set('Accept', 'application/json').set('Cookie', [`ssid=${user.userId}`]).send({ userId: projectToSave.userId });
+ const projObj: Response = await request(app)
+ .post('/getProjects')
+ .set('Accept', 'application/json')
+ .set('Cookie', [`ssid=${user.userId}`])
+ .send({ userId: projectToSave.userId });
const _id: String = projObj.body[0]._id;
const project: String = projObj.body[0].project;
const comments: String = projObj.body[0].comments;
const username: String = projObj.body[0].username;
const name: String = projObj.body[0].name;
- const userId: String = "ERROR";
+ const userId: String = 'ERROR';
return request(app)
.post('/publishProject')
.set('Content-Type', 'application/json')
- .set('Cookie', [`ssid=${user.userId}`])
- .send({ _id, project, comments, userId, username, name })//_id, project, comments, userId, username, name
+ .set('Cookie', [`ssid=${user.userId}`])
+ .send({ _id, project, comments, userId, username, name }) //_id, project, comments, userId, username, name
.expect(500)
.then((res) => {
- expect(res.body.err).not.toBeNull()
- });
+ expect(res.body.err).not.toBeNull();
+ });
});
it('responds with status of 500 and error if _id was not a valid mongo ObjectId', async () => {
- const projObj: Response = await request(app).post('/getProjects').set('Accept', 'application/json').set('Cookie', [`ssid=${user.userId}`]).send({ userId: projectToSave.userId });
+ const projObj: Response = await request(app)
+ .post('/getProjects')
+ .set('Accept', 'application/json')
+ .set('Cookie', [`ssid=${user.userId}`])
+ .send({ userId: projectToSave.userId });
const _id: String = 'ERROR';
const project: String = projObj.body[0].project;
const comments: String = projObj.body[0].comments;
const username: String = user.username;
const name: String = projObj.body[0].name;
const userId: String = user.userId;
-
+
return request(app)
.post('/publishProject')
.set('Content-Type', 'application/json')
- .set('Cookie', [`ssid=${user.userId}`])
- .send({ _id, project, comments, userId, username, name })//_id, project, comments, userId, username, name
+ .set('Cookie', [`ssid=${user.userId}`])
+ .send({ _id, project, comments, userId, username, name }) //_id, project, comments, userId, username, name
.expect(200)
.then((res) => {
- expect(res.body._id).not.toEqual(_id)
- });
+ expect(res.body._id).not.toEqual(_id);
+ });
});
});
});
//test getMarketplaceProjects endpoint
- describe('/getMarketplaceProjects', () => {//most recent project should be the one from publishProject
+ describe('/getMarketplaceProjects', () => {
+ //most recent project should be the one from publishProject
describe('GET', () => {
it('responds with status of 200 and json object equal to unpublished project', async () => {
@@ -187,7 +200,7 @@ describe('Server endpoint tests', () => {
.then((res) => {
expect(Array.isArray(res.body)).toBe(true);
expect(res.body[0]._id).toBeTruthy;
- });
+ });
});
});
});
@@ -196,10 +209,9 @@ describe('Server endpoint tests', () => {
describe('/cloneProject/:docId', () => {
describe('GET', () => {
it('responds with status of 200 and json object equal to cloned project', async () => {
-
const projObj = await request(app)
.get('/getMarketplaceProjects')
- .set('Content-Type', 'application/json')
+ .set('Content-Type', 'application/json');
return request(app)
.get(`/cloneProject/${projObj.body[0]._id}`)
@@ -209,13 +221,12 @@ describe('Server endpoint tests', () => {
.then((res) => {
expect(res.body.forked).toBeTruthy;
expect(res.body.username).toBe(user.username);
- });
+ });
});
it('responds with status of 500 and error', async () => {
-
const projObj = await request(app)
.get('/getMarketplaceProjects')
- .set('Content-Type', 'application/json')
+ .set('Content-Type', 'application/json');
return request(app)
.get(`/cloneProject/${projObj.body[0]._id}`)
@@ -223,8 +234,8 @@ describe('Server endpoint tests', () => {
.query({ username: [] })
.expect(500)
.then((res) => {
- expect(res.body.err).not.toBeNull()
- });
+ expect(res.body.err).not.toBeNull();
+ });
});
});
});
@@ -233,7 +244,11 @@ describe('Server endpoint tests', () => {
describe('/unpublishProject', () => {
describe('PATCH', () => {
it('responds with status of 200 and json object equal to unpublished project', async () => {
- const response: Response = await request(app).post('/getProjects').set('Accept', 'application/json').set('Cookie', [`ssid=${user.userId}`]).send({ userId: projectToSave.userId }); //most recent project should be the one from publishProject
+ const response: Response = await request(app)
+ .post('/getProjects')
+ .set('Accept', 'application/json')
+ .set('Cookie', [`ssid=${user.userId}`])
+ .send({ userId: projectToSave.userId }); //most recent project should be the one from publishProject
const _id: String = response.body[0]._id;
const userId: String = user.userId;
return request(app)
@@ -243,26 +258,29 @@ describe('Server endpoint tests', () => {
.send({ _id, userId })
.expect(200)
.then((res) => {
- expect(res.body._id).toBe(_id)
+ expect(res.body._id).toBe(_id);
expect(res.body.published).toBe(false);
- });
+ });
});
it('responds with status of 500 and error if userId and cookie ssid do not match', async () => {
- const projObj: Response = await request(app).post('/getProjects').set('Accept', 'application/json').set('Cookie', [`ssid=${user.userId}`]).send({ userId: projectToSave.userId });
+ const projObj: Response = await request(app)
+ .post('/getProjects')
+ .set('Accept', 'application/json')
+ .set('Cookie', [`ssid=${user.userId}`])
+ .send({ userId: projectToSave.userId });
const _id: String = projObj.body[0]._id;
const project: String = projObj.body[0].project;
const comments: String = projObj.body[0].comments;
const username: String = projObj.body[0].username;
const name: String = projObj.body[0].name;
let userId: String = user.userId;
- await request(app)//publishing a project first
+ await request(app) //publishing a project first
.post('/publishProject')
.set('Content-Type', 'application/json')
- .set('Cookie', [`ssid=${user.userId}`])
- .send({ _id, project, comments, userId, username, name })
-
-
- userId = "ERROR";
+ .set('Cookie', [`ssid=${user.userId}`])
+ .send({ _id, project, comments, userId, username, name });
+
+ userId = 'ERROR';
return request(app)
.patch('/unpublishProject')
.set('Content-Type', 'application/json')
@@ -270,8 +288,8 @@ describe('Server endpoint tests', () => {
.send({ _id, userId })
.expect(500)
.then((res) => {
- expect(res.body.err).not.toBeNull()
- });
+ expect(res.body.err).not.toBeNull();
+ });
});
it('responds with status of 500 and error if _id was not a string', async () => {
const userId: String = user.userId;
@@ -280,32 +298,28 @@ describe('Server endpoint tests', () => {
.patch('/unpublishProject')
.set('Content-Type', 'application/json')
.set('Cookie', [`ssid=${user.userId}`])
- .send({userId })
+ .send({ userId })
.expect(500)
.then((res) => {
- expect(res.body.err).not.toBeNull()
- });
+ expect(res.body.err).not.toBeNull();
+ });
});
});
});
});
describe('SessionController tests', () => {
-
-
-
- describe('isLoggedIn',() => {
-
+ describe('isLoggedIn', () => {
afterEach(() => {
jest.resetAllMocks();
- })
- // Mock Express request and response objects and next function
+ });
+ // Mock Express request and response objects and next function
const mockReq: any = {
- cookies: null,//trying to trigger if cookies was not assigned
+ cookies: null, //trying to trigger if cookies was not assigned
body: {
- userId: 'sampleUserId', // Set up a sample userId in the request body
- },
- }
+ userId: 'sampleUserId' // Set up a sample userId in the request body
+ }
+ };
const mockRes: any = {
json: jest.fn(),
status: jest.fn(),
@@ -313,79 +327,88 @@ describe('SessionController tests', () => {
};
const next = jest.fn();
it('Assign userId from request body to cookieId', async () => {
- // Call isLoggedIn
+ // Call isLoggedIn
await sessionController.isLoggedIn(mockReq, mockRes, next);
expect(mockRes.redirect).toHaveBeenCalledWith('/');
- // Ensure that next() was called
+ // Ensure that next() was called
});
it('Trigger a database query error for findOne', async () => {
- const mockFindOne = jest.spyOn(mongoose.model('Sessions'), 'findOne').mockImplementation(() => {
- throw new Error('Database query error');
- });
- // Call isLoggedIn
+ const mockFindOne = jest
+ .spyOn(mongoose.model('Sessions'), 'findOne')
+ .mockImplementation(() => {
+ throw new Error('Database query error');
+ });
+ // Call isLoggedIn
await sessionController.isLoggedIn(mockReq, mockRes, next);
- // Ensure that next() was called with the error
- expect(next).toHaveBeenCalledWith(expect.objectContaining({
- log: expect.stringMatching('Database query error'), // The 'i' flag makes it case-insensitive
- }));
+ // Ensure that next() was called with the error
+ expect(next).toHaveBeenCalledWith(
+ expect.objectContaining({
+ log: expect.stringMatching('Database query error') // The 'i' flag makes it case-insensitive
+ })
+ );
mockFindOne.mockRestore();
});
});
-
-
- describe('startSession',() => {
-
+
+ describe('startSession', () => {
afterEach(() => {
jest.resetAllMocks();
- })
+ });
it('Trigger a database query error for findOne', async () => {
-
const mockReq: any = {
- cookies: projectToSave.userId,//trying to trigger if cookies was not assigned
+ cookies: projectToSave.userId, //trying to trigger if cookies was not assigned
body: {
- userId: 'sampleUserId', // Set up a sample userId in the request body
- },
- }
+ userId: 'sampleUserId' // Set up a sample userId in the request body
+ }
+ };
const mockRes: any = {
json: jest.fn(),
status: jest.fn(),
redirect: jest.fn(),
- locals: {id: projectToSave.userId}
+ locals: { id: projectToSave.userId }
};
-
+
const next = jest.fn();
- const findOneMock = jest.spyOn(mongoose.model('Sessions'), 'findOne') as jest.Mock;
- findOneMock.mockImplementation((query: any, callback: (err: any, ses: any) => void) => {
- callback(new Error('Database query error'), null);
- });
+ const findOneMock = jest.spyOn(
+ mongoose.model('Sessions'),
+ 'findOne'
+ ) as jest.Mock;
+ findOneMock.mockImplementation(
+ (query: any, callback: (err: any, ses: any) => void) => {
+ callback(new Error('Database query error'), null);
+ }
+ );
// Call startSession
await sessionController.startSession(mockReq, mockRes, next);
// Check that next() was called with the error
- expect(next).toHaveBeenCalledWith(expect.objectContaining({
- log: expect.stringMatching('Database query error'), // The 'i' flag makes it case-insensitive
- }));
+ expect(next).toHaveBeenCalledWith(
+ expect.objectContaining({
+ log: expect.stringMatching('Database query error') // The 'i' flag makes it case-insensitive
+ })
+ );
findOneMock.mockRestore();
});
- xit('Check if a new Session is created', async () => {//not working for some reason cannot get mocknext() to be called in test?
+ xit('Check if a new Session is created', async () => {
+ //not working for some reason cannot get mocknext() to be called in test?
const mockReq: any = {
- cookies: projectToSave.userId,//trying to trigger if cookies was not assigned
+ cookies: projectToSave.userId, //trying to trigger if cookies was not assigned
body: {
- userId: 'sampleUserId', // Set up a sample userId in the request body
- },
- }
+ userId: 'sampleUserId' // Set up a sample userId in the request body
+ }
+ };
const mockRes: any = {
json: jest.fn(),
status: jest.fn(),
redirect: jest.fn(),
- locals: {id: 'testID'}//a sesion id that doesnt exist
+ locals: { id: 'testID' } //a sesion id that doesnt exist
};
-
+
const mockNext = jest.fn();
-
+
//Call startSession
// Wrap your test logic in an async function
await sessionController.startSession(mockReq, mockRes, mockNext);
@@ -393,21 +416,10 @@ describe('SessionController tests', () => {
//check if it reaches next()
//await expect(mockRes.locals.ssid).toBe('testID');
expect(mockNext).toHaveBeenCalled();
-
-
-
});
});
});
-
-
-
-
-
-
-
-
// describe('marketplaceController Middleware', () => {
// describe('getProjects tests', () => {
// it('should add the projects as an array to res.locals', () => {
@@ -421,13 +433,12 @@ describe('SessionController tests', () => {
// });
// });
-
// it('should send an error response if there is an error in the middleware', () => {
// const req = { user: { isAuthenticated: false } };
// const res = mockResponse();
-
+
// marketplaceController.authenticateMiddleware(req, res, mockNext);
-
+
// expect(res.status).toHaveBeenCalledWith(500);
// expect(res.json).toHaveBeenCalledWith({ err: 'Error in marketplaceController.getPublishedProjects, check server logs for details' });
// });
diff --git a/__tests__/userAuth.test.ts b/__tests__/userAuth.test.ts
index 2f5137d97..4a3d8c907 100644
--- a/__tests__/userAuth.test.ts
+++ b/__tests__/userAuth.test.ts
@@ -2,17 +2,14 @@
* @jest-environment node
*/
-
-import marketplaceController from '../server/controllers/marketplaceController';
import app from '../server/server';
import mockData from '../mockData';
-import { profileEnd } from 'console';
import { Sessions, Users } from '../server/models/reactypeModels';
const request = require('supertest');
const mongoose = require('mongoose');
const mockNext = jest.fn(); // Mock nextFunction
-const MONGO_DB = process.env.MONGO_DB_TEST;
-const { state, projectToSave, user } = mockData
+const MONGO_DB = import.meta.env.MONGO_DB_TEST;
+const { user } = mockData;
const PORT = 8080;
const num = Math.floor(Math.random() * 1000);
@@ -20,15 +17,20 @@ const num = Math.floor(Math.random() * 1000);
beforeAll(async () => {
await mongoose.connect(MONGO_DB, {
useNewUrlParser: true,
- useUnifiedTopology: true,
+ useUnifiedTopology: true
});
});
afterAll(async () => {
-
- const result = await Users.deleteMany({_id: {$ne: '64f551e5b28d5292975e08c8'}});//clear the users collection after tests are done except for the mockdata user account
- const result2 = await Sessions.deleteMany({cookieId: {$ne: '64f551e5b28d5292975e08c8'}});
- console.log(`${result.deletedCount} and ${result2.deletedCount} documents deleted.`);
+ const result = await Users.deleteMany({
+ _id: { $ne: '64f551e5b28d5292975e08c8' }
+ }); //clear the users collection after tests are done except for the mockdata user account
+ const result2 = await Sessions.deleteMany({
+ cookieId: { $ne: '64f551e5b28d5292975e08c8' }
+ });
+ console.log(
+ `${result.deletedCount} and ${result2.deletedCount} documents deleted.`
+ );
await mongoose.connection.close();
});
@@ -40,7 +42,7 @@ describe('User Authentication tests', () => {
expect(response.text).toBe('test request is working');
});
});
- describe('/signup', ()=> {
+ describe('/signup', () => {
describe('POST', () => {
//testing new signup
it('responds with status 200 and sessionId on valid new user signup', () => {
@@ -55,7 +57,7 @@ describe('User Authentication tests', () => {
.expect(200)
.then((res) => expect(res.body.sessionId).not.toBeNull());
});
-
+
it('responds with status 400 and json string on invalid new user signup (Already taken)', () => {
return request(app)
.post('/signup')
@@ -67,8 +69,9 @@ describe('User Authentication tests', () => {
});
});
});
+
describe('/login', () => {
- // tests whether existing login information permits user to log in
+ // tests whether existing login information permits user to log in
describe('POST', () => {
it('responds with status 200 and json object on verified user login', () => {
return request(app)
@@ -80,7 +83,7 @@ describe('User Authentication tests', () => {
.then((res) => expect(res.body.sessionId).toEqual(user.userId));
});
// if invalid username/password, should respond with status 400
- it('responds with status 400 and json string on invalid user login', () => {
+ it('responds with status 400 and json string on invalid user login', () => {
return request(app)
.post('/login')
.send({ username: 'wrongusername', password: 'wrongpassword' })
@@ -88,175 +91,60 @@ describe('User Authentication tests', () => {
.expect('Content-Type', /json/)
.then((res) => expect(typeof res.body).toBe('string'));
});
- });
- });
-
-});
-
-
-
-// import request from 'supertest';
-// import app from '../server/server';
-// import mockObj from '../mockData';
-// const user = mockObj.user;
-// import mongoose from 'mongoose';
-// const URI = process.env.MONGO_DB;
-
-// beforeAll(() => {
-// mongoose
-// .connect(URI, { useNewUrlParser: true }, { useUnifiedTopology: true })
-// .then(() => console.log('connected to test database'));
-// });
-
-// afterAll(async () => {
-// await mongoose.connection.close();
-// });
-// //for creating unqiue login credentials
-// const num = Math.floor(Math.random() * 1000);
-
-// describe('User authentication tests', () => {
-// //test connection to server
-// describe('initial connection test', () => {
-// it('should connect to the server', async () => {
-// const response = await request(app).get('/test');
-// expect(response.text).toEqual('test request is working');
-// });
-// });
-
-// xdescribe('POST', () => {
-// it('responds with status 200 and json object on valid new user signup', () => {
-// return request(app)
-// .post('/signup')
-// .set('Content-Type', 'application/json')
-// .send({
-// username: `supertest${num}`,
-// email: `test${num}@test.com`,
-// password: `${num}`
-// })
-// .expect(200)
-// .then((res) => expect(typeof res.body).toBe('object'));
-// });
-
-// it('responds with status 400 and json string on invalid new user signup', () => {
-// return request(app)
-// .post('/signup')
-// .send(user)
-// .set('Accept', 'application/json')
-// .expect('Content-Type', /json/)
-// .expect(400)
-// .then((res) => expect(typeof res.body).toBe('string'));
-// });
-// });
-// });
-// describe('/login', () => {
-// // tests whether existing login information permits user to log in
-// xdescribe('POST', () => {
-// it('responds with status 200 and json object on verified user login', () => {
-// return request(app)
-// .post('/login')
-// .set('Accept', 'application/json')
-// .send(user)
-// .expect(200)
-// .expect('Content-Type', /json/)
-// .then((res) => expect(res.body.sessionId).toEqual(user.userId));
-// });
-// // if invalid username/password, should respond with status 400
-// it('responds with status 400 and json string on invalid user login', () => {
-// return request(app)
-// .post('/login')
-// .send({ username: 'wrongusername', password: 'wrongpassword' })
-// .expect(400)
-// .expect('Content-Type', /json/)
-// .then((res) => expect(typeof res.body).toBe('string'));
-// });
-// it('responds with status 400 and json string on invalid new user signup', () => {
-// return request(app)
-// .post('/signup')
-// .send(user)
-// .set('Accept', 'application/json')
-// .expect('Content-Type', /json/)
-// .expect(400)
-// .then((res) => expect(typeof res.body).toBe('string'));
-// });
-// });
-// });
-
-// describe('sessionIsCreated', () => {
-// it("returns the message 'No Username Input' when no username is entered", () => {
-// return request(app)
-// .post('/login')
-// .send({
-// username: '',
-// password: 'Reactype123!@#',
-// isFbOauth: false
-// })
-// .then((res) => expect(res.text).toBe('"No Username Input"'));
-// });
-
-// it("returns the message 'No Password Input' when no password is entered", () => {
-// return request(app)
-// .post('/login')
-// .send({
-// username: 'reactype123',
-// password: '',
-// isFbOauth: false
-// })
-// .then((res) => expect(res.text).toBe('"No Password Input"'));
-// });
+ it("returns the message 'No Username Input' when no username is entered", () => {
+ return request(app)
+ .post('/login')
+ .send({
+ username: '',
+ password: 'Reactype123!@#',
+ isFbOauth: false
+ })
+ .then((res) => expect(res.text).toBe('"No Username Input"'));
+ });
-// it("returns the message 'Invalid Username' when username does not exist", () => {
-// return request(app)
-// .post('/login')
-// .send({
-// username: 'l!b',
-// password: 'test',
-// isFbOauth: false
-// })
-// .then((res) => expect(res.text).toBe('"Invalid Username"'));
-// });
-// });
+ it("returns the message 'No Username Input' when no username is entered", () => {
+ return request(app)
+ .post('/login')
+ .send({
+ username: '',
+ password: 'Reactype123!@#',
+ isFbOauth: false
+ })
+ .then((res) => expect(res.text).toBe('"No Username Input"'));
+ });
-// it("returns the message 'Incorrect Password' when password does not match", () => {
-// return request(app)
-// .post('/login')
-// .send({
-// username: 'test',
-// password: 'test',
-// isFbOauth: false
-// })
-// .then((res) => expect(res.text).toBe('"Incorrect Password"'));
-// });
-// // note that the username and password in this test are kept in the heroku database
-// // DO NOT CHANGE unless you have access to the heroku database
-// it("returns the message 'Success' when the user passes all auth checks", () => {
-// return request(app)
-// .post('/login')
-// .send({
-// username: 'test',
-// password: 'password1!',
-// isFbOauth: false
-// })
-// .then((res) => expect(res.body).toHaveProperty('sessionId'));
-// });
+ it("returns the message 'No Password Input' when no password is entered", () => {
+ return request(app)
+ .post('/login')
+ .send({
+ username: 'reactype123',
+ password: '',
+ isFbOauth: false
+ })
+ .then((res) => expect(res.text).toBe('"No Password Input"'));
+ });
-// // // OAuth tests (currently inoperative)
+ it("returns the message 'Invalid Username' when username does not exist", () => {
+ return request(app)
+ .post('/login')
+ .send({
+ username: 'l!b',
+ password: 'test',
+ isFbOauth: false
+ })
+ .then((res) => expect(res.text).toBe('"Invalid Username"'));
+ });
+ });
-// // xdescribe('Github oauth tests', () => {
-// // describe('/github/callback?code=', () => {
-// // describe('GET', () => {
-// // it('responds with status 400 and error message if no code received', () => {
-// // return request(server)
-// // .get('/github/callback?code=')
-// // .expect(400)
-// // .then((res) => {
-// // return expect(res.text).toEqual(
-// // '"Undefined or no code received from github.com"'
-// // );
-// // });
-// // });
-// // it('responds with status 400 if invalid code received', () => {
-// // return request(server).get('/github/callback?code=123456').expect(400);
-// // });
-// // });
-// // });
-// // });
+ it("returns the message 'Incorrect Password' when password does not match", () => {
+ return request(app)
+ .post('/login')
+ .send({
+ username: 'test',
+ password: 'test',
+ isFbOauth: false
+ })
+ .then((res) => expect(res.text).toBe('"Incorrect Password"'));
+ });
+ });
+});
diff --git a/app/.electron/main.ts b/app/.electron/main.ts
index a9209dfbe..6bf974c01 100644
--- a/app/.electron/main.ts
+++ b/app/.electron/main.ts
@@ -30,7 +30,8 @@ import MenuBuilder from './menu';
// mode that the app is running in
const isDev =
- process.env.NODE_ENV === 'development' || process.env.NODE_ENV === 'test';
+ import.meta.env.NODE_ENV === 'development' ||
+ import.meta.env.NODE_ENV === 'test';
const port = 8080;
const selfHost = `http://localhost:${port}`;
@@ -388,8 +389,8 @@ ipcMain.on('github', (event) => {
? `http://localhost:${DEV_PORT}/auth/github`
: `https://reactype-caret.herokuapp.com/auth/github`;
const options = {
- client_id: process.env.GITHUB_ID,
- client_secret: process.env.GITHUB_SECRET,
+ client_id: import.meta.env.GITHUB_ID,
+ client_secret: import.meta.env.GITHUB_SECRET,
scopes: ['user:email', 'notifications']
};
// create new browser window object with size, title, security options
diff --git a/app/.electron/menu.ts b/app/.electron/menu.ts
index dcdb2708a..c0814ebe2 100644
--- a/app/.electron/menu.ts
+++ b/app/.electron/menu.ts
@@ -47,7 +47,7 @@ var MenuBuilder = function (mainWindow, appName) {
devTools: false
}
});
- if (process.env.NODE_ENV === 'development') {
+ if (import.meta.env.NODE_ENV === 'development') {
tutorial.loadURL(`http://localhost:8080/#/tutorial`);
} else {
tutorial.loadURL(`${Protocol.scheme}://rse/index-prod.html#/tutorial`);
diff --git a/app/src/Dashboard/NavbarDash.tsx b/app/src/Dashboard/NavbarDash.tsx
index 019feab0d..328c0ec81 100644
--- a/app/src/Dashboard/NavbarDash.tsx
+++ b/app/src/Dashboard/NavbarDash.tsx
@@ -19,36 +19,38 @@ import SortIcon from '@mui/icons-material/Sort';
import StarBorderIcon from '@mui/icons-material/StarBorder';
import PersonIcon from '@mui/icons-material/Person';
import greenLogo from '../public/icons/png/512x512.png';
-import {setStyle} from '../redux/reducers/slice/styleSlice'
-import { useSelector,useDispatch } from 'react-redux';
+import { setStyle } from '../redux/reducers/slice/styleSlice';
+import { useSelector, useDispatch } from 'react-redux';
// NavBar text and button styling
-const useStyles = makeStyles((theme: Theme) => createStyles({
- root: {
- flexGrow: 1,
- width: '100%',
- },
- menuButton: {
- marginRight: theme.spacing(2),
- color: 'white',
- },
- title: {
- flexGrow: 1,
- color: 'white',
- },
- manageProject: {
- display: 'flex',
- justifyContent: 'center',
- },
-}));
+const useStyles = makeStyles((theme: Theme) =>
+ createStyles({
+ root: {
+ flexGrow: 1,
+ width: '100%'
+ },
+ menuButton: {
+ marginRight: theme.spacing(2),
+ color: 'white'
+ },
+ title: {
+ flexGrow: 1,
+ color: 'white'
+ },
+ manageProject: {
+ display: 'flex',
+ justifyContent: 'center'
+ }
+ })
+);
// sorting options
const sortMethods = ['RATING', 'DATE', 'USER'];
// Drop down menu button for SORT PROJECTS
const StyledMenu = withStyles({
paper: {
- border: '1px solid #d3d4d5',
+ border: '1px solid #d3d4d5'
}
-})(props => (
+})((props) => (
));
-const StyledMenuItem = withStyles(theme => ({
+const StyledMenuItem = withStyles((theme) => ({
root: {
'&:focus': {
'& .MuiListItemIcon-root, & .MuiListItemText-primary': {
@@ -76,14 +78,14 @@ const StyledMenuItem = withStyles(theme => ({
export default function NavBar(props) {
// TO DO: import setStyle
const classes = useStyles();
- const style = useSelector(store => store.styleSlice);
+ const style = useSelector((store) => store.styleSlice);
const dispatch = useDispatch();
const toggling = () => setIsOpen(!isOpen);
// toggle to open and close dropdown sorting menu
const [isOpen, setIsOpen] = useState(false);
// State for sort projects button
const [anchorEl, setAnchorEl] = React.useState(null);
- const handleClick = event => {
+ const handleClick = (event) => {
setAnchorEl(event.currentTarget);
};
const handleClose = () => {
@@ -91,80 +93,108 @@ export default function NavBar(props) {
};
return (