diff --git a/README.md b/README.md index a28f3814f..29d3937ca 100644 --- a/README.md +++ b/README.md @@ -1,5 +1,9 @@ # Acebook +# Trello Board: + +https://trello.com/b/Ckf68HAB/acebook + In this project, you are tasked with working on an existing application. A significant part of the challenge will be to familiarise yourself with the codebase you've inherited, as you work to **improve and extend** it. ## Videos diff --git a/api/app.js b/api/app.js index 07aa00b3b..61d37e931 100644 --- a/api/app.js +++ b/api/app.js @@ -7,11 +7,12 @@ const JWT = require("jsonwebtoken"); const postsRouter = require("./routes/posts"); const tokensRouter = require("./routes/tokens"); const usersRouter = require("./routes/users"); +const homeRouter = require("./routes/home"); const app = express(); // setup for receiving JSON -app.use(express.json()) +app.use(express.json()); app.use(logger("dev")); app.use(express.json()); @@ -19,18 +20,17 @@ app.use(express.static(path.join(__dirname, "public"))); // middleware function to check for valid tokens const tokenChecker = (req, res, next) => { - let token; - const authHeader = req.get("Authorization") + const authHeader = req.get("Authorization"); - if(authHeader) { - token = authHeader.slice(7) + if (authHeader) { + token = authHeader.slice(7); } JWT.verify(token, process.env.JWT_SECRET, (err, payload) => { - if(err) { - console.log(err) - res.status(401).json({message: "auth error"}); + if (err) { + console.log(err); + res.status(401).json({ message: "auth error" }); } else { req.user_id = payload.user_id; next(); @@ -42,6 +42,7 @@ const tokenChecker = (req, res, next) => { app.use("/posts", tokenChecker, postsRouter); app.use("/tokens", tokensRouter); app.use("/users", usersRouter); +app.use("/", homeRouter); // catch 404 and forward to error handler app.use((req, res, next) => { @@ -55,7 +56,7 @@ app.use((err, req, res) => { res.locals.error = req.app.get("env") === "development" ? err : {}; // respond with details of the error - res.status(err.status || 500).json({message: 'server error'}) + res.status(err.status || 500).json({ message: "server error" }); }); module.exports = app; diff --git a/api/controllers/posts.js b/api/controllers/posts.js index dc487d2dd..331b6af1d 100644 --- a/api/controllers/posts.js +++ b/api/controllers/posts.js @@ -7,19 +7,21 @@ const PostsController = { if (err) { throw err; } - const token = await TokenGenerator.jsonwebtoken(req.user_id) + const token = await TokenGenerator.jsonwebtoken(req.user_id); res.status(200).json({ posts: posts, token: token }); - }); + }).sort({ time: -1 }); }, Create: (req, res) => { + req.body.time = Date.now(); + req.body.posterUserId = req.user_id; const post = new Post(req.body); post.save(async (err) => { if (err) { throw err; } - const token = await TokenGenerator.jsonwebtoken(req.user_id) - res.status(201).json({ message: 'OK', token: token }); + const token = await TokenGenerator.jsonwebtoken(req.user_id); + res.status(201).json({ message: "OK", token: token }); }); }, }; diff --git a/api/controllers/tokens.js b/api/controllers/tokens.js index cc983053a..60336a18e 100644 --- a/api/controllers/tokens.js +++ b/api/controllers/tokens.js @@ -1,25 +1,24 @@ const User = require("../models/user"); -const TokenGenerator = require("../models/token_generator") +const TokenGenerator = require("../models/token_generator"); const SessionsController = { - Create: (req, res) => { const email = req.body.email; const password = req.body.password; User.findOne({ email: email }).then(async (user) => { if (!user) { - console.log("auth error: user not found") + console.log("auth error: user not found"); res.status(401).json({ message: "auth error" }); } else if (user.password !== password) { - console.log("auth error: passwords do not match") + console.log("auth error: passwords do not match"); res.status(401).json({ message: "auth error" }); } else { - const token = await TokenGenerator.jsonwebtoken(user.id) + const token = await TokenGenerator.jsonwebtoken(user.id); res.status(201).json({ token: token, message: "OK" }); } }); - } + }, }; module.exports = SessionsController; diff --git a/api/controllers/users.js b/api/controllers/users.js index 8f195d29e..79a6e51d9 100644 --- a/api/controllers/users.js +++ b/api/controllers/users.js @@ -5,9 +5,9 @@ const UsersController = { const user = new User(req.body); user.save((err) => { if (err) { - res.status(400).json({message: 'Bad request'}) + res.status(400).json({ message: "Bad request" }); } else { - res.status(201).json({ message: 'OK' }); + res.status(201).json({ message: "OK" }); } }); }, diff --git a/api/models/post.js b/api/models/post.js index 6c4e213e9..341813064 100644 --- a/api/models/post.js +++ b/api/models/post.js @@ -1,7 +1,9 @@ const mongoose = require("mongoose"); const PostSchema = new mongoose.Schema({ - message: String + message: String, + time: Number, + posterUserId: String }); const Post = mongoose.model("Post", PostSchema); diff --git a/api/models/token_generator.js b/api/models/token_generator.js index 0ec5a0d0c..ad532349c 100644 --- a/api/models/token_generator.js +++ b/api/models/token_generator.js @@ -1,10 +1,10 @@ const JWT = require("jsonwebtoken"); -const options = {expiresIn: "10m"}; +const options = { expiresIn: "10m" }; const secret = process.env.JWT_SECRET; class TokenGenerator { static jsonwebtoken(user_id) { - return JWT.sign({user_id: user_id, iat: Date.now()}, secret, options); + return JWT.sign({ user_id: user_id, iat: Date.now() }, secret, options); } } diff --git a/api/package.json b/api/package.json index 48cd9be58..6c513d324 100644 --- a/api/package.json +++ b/api/package.json @@ -7,7 +7,7 @@ "scripts": { "start": "nodemon ./bin/www", "start:test": "MONGODB_URL='mongodb://0.0.0.0/acebook_test' npm start", - "test": "jest" + "test": "jest --runInBand" }, "engines": { "node": ">=18.1.0" diff --git a/api/spec/controllers/posts.spec.js b/api/spec/controllers/posts.spec.js index 9f1e67ec3..ace050779 100644 --- a/api/spec/controllers/posts.spec.js +++ b/api/spec/controllers/posts.spec.js @@ -1,27 +1,28 @@ const app = require("../../app"); const request = require("supertest"); require("../mongodb_helper"); -const Post = require('../../models/post'); -const User = require('../../models/user'); -const TokenGenerator = require('../../models/token_generator'); +const Post = require("../../models/post"); +const User = require("../../models/user"); +const TokenGenerator = require("../../models/token_generator"); const JWT = require("jsonwebtoken"); let token; describe("/posts", () => { - beforeAll( async () => { - const user = new User({email: "test@test.com", password: "12345678"}); + beforeAll(async () => { + const user = new User({ email: "test@test.com", password: "12345678" }); + userIdTest = user.id await user.save(); token = TokenGenerator.jsonwebtoken(user.id); }); - beforeEach( async () => { + beforeEach(async () => { await Post.deleteMany({}); - }) + }); - afterAll( async () => { + afterAll(async () => { await User.deleteMany({}); await Post.deleteMany({}); - }) + }); describe("POST, when token is present", () => { test("responds with a 201", async () => { @@ -31,7 +32,7 @@ describe("/posts", () => { .send({ message: "hello world", token: token }); expect(response.status).toEqual(201); }); - + test("creates a new post", async () => { await request(app) .post("/posts") @@ -41,18 +42,41 @@ describe("/posts", () => { expect(posts.length).toEqual(1); expect(posts[0].message).toEqual("hello world"); }); - + + test("creates a new post and adds in the time", async () => { + const time = Date.now(); + await request(app) + .post("/posts") + .set("Authorization", `Bearer ${token}`) + .send({ message: "hello world", token: token }); + let posts = await Post.find(); + expect(posts.length).toEqual(1); + expect(posts[0].message).toEqual("hello world"); + expect(posts[0].time / 10000).toBeCloseTo(time / 10000); + }); + + test("creates a new post and adds in the userID", async () => { + await request(app) + .post("/posts") + .set("Authorization", `Bearer ${token}`) + .send({ message: "hello world", token: token }); + let posts = await Post.find(); + expect(posts.length).toEqual(1); + expect(posts[0].message).toEqual("hello world"); + expect(posts[0].posterUserId).toEqual(userIdTest); + }); + test("returns a new token", async () => { let response = await request(app) .post("/posts") .set("Authorization", `Bearer ${token}`) - .send({ message: "hello world", token: token }) + .send({ message: "hello world", token: token }); let newPayload = JWT.decode(response.body.token, process.env.JWT_SECRET); let originalPayload = JWT.decode(token, process.env.JWT_SECRET); expect(newPayload.iat > originalPayload.iat).toEqual(true); - }); + }); }); - + describe("POST, when token is missing", () => { test("responds with a 401", async () => { let response = await request(app) @@ -60,93 +84,101 @@ describe("/posts", () => { .send({ message: "hello again world" }); expect(response.status).toEqual(401); }); - + test("a post is not created", async () => { - await request(app) - .post("/posts") - .send({ message: "hello again world" }); + await request(app).post("/posts").send({ message: "hello again world" }); let posts = await Post.find(); expect(posts.length).toEqual(0); }); - + test("a token is not returned", async () => { let response = await request(app) .post("/posts") .send({ message: "hello again world" }); expect(response.body.token).toEqual(undefined); }); - }) + }); describe("GET, when token is present", () => { test("returns every post in the collection", async () => { - let post1 = new Post({message: "howdy!"}); - let post2 = new Post({message: "hola!"}); + let post1 = new Post({ message: "howdy!" }); + let post2 = new Post({ message: "hola!" }); await post1.save(); await post2.save(); let response = await request(app) .get("/posts") .set("Authorization", `Bearer ${token}`) - .send({token: token}); - let messages = response.body.posts.map((post) => ( post.message )); + .send({ token: token }); + let messages = response.body.posts.map((post) => post.message); expect(messages).toEqual(["howdy!", "hola!"]); - }) + }); test("the response code is 200", async () => { - let post1 = new Post({message: "howdy!"}); - let post2 = new Post({message: "hola!"}); + let post1 = new Post({ message: "howdy!" }); + let post2 = new Post({ message: "hola!" }); await post1.save(); await post2.save(); let response = await request(app) .get("/posts") .set("Authorization", `Bearer ${token}`) - .send({token: token}); + .send({ token: token }); expect(response.status).toEqual(200); - }) + }); + + test("returns every post in the collection in reverse time order", async () => { + let post1 = new Post({ message: "howdy!", time: 1669897643479 }); + let post2 = new Post({ message: "hola!", time: 1669897643480 }); + await post1.save(); + await post2.save(); + let response = await request(app) + .get("/posts") + .set("Authorization", `Bearer ${token}`) + .send({ token: token }); + let messages = response.body.posts.map((post) => post.message); + expect(messages).toEqual(["hola!", "howdy!"]); + }); test("returns a new token", async () => { - let post1 = new Post({message: "howdy!"}); - let post2 = new Post({message: "hola!"}); + let post1 = new Post({ message: "howdy!" }); + let post2 = new Post({ message: "hola!" }); await post1.save(); await post2.save(); let response = await request(app) .get("/posts") .set("Authorization", `Bearer ${token}`) - .send({token: token}); + .send({ token: token }); let newPayload = JWT.decode(response.body.token, process.env.JWT_SECRET); let originalPayload = JWT.decode(token, process.env.JWT_SECRET); expect(newPayload.iat > originalPayload.iat).toEqual(true); - }) - }) + }); + }); describe("GET, when token is missing", () => { test("returns no posts", async () => { - let post1 = new Post({message: "howdy!"}); - let post2 = new Post({message: "hola!"}); + let post1 = new Post({ message: "howdy!" }); + let post2 = new Post({ message: "hola!" }); await post1.save(); await post2.save(); - let response = await request(app) - .get("/posts"); + let response = await request(app).get("/posts"); expect(response.body.posts).toEqual(undefined); - }) + }); test("the response code is 401", async () => { - let post1 = new Post({message: "howdy!"}); - let post2 = new Post({message: "hola!"}); + let post1 = new Post({ message: "howdy!" }); + let post2 = new Post({ message: "hola!" }); await post1.save(); await post2.save(); - let response = await request(app) - .get("/posts"); + let response = await request(app).get("/posts"); expect(response.status).toEqual(401); - }) + }); test("does not return a new token", async () => { - let post1 = new Post({message: "howdy!"}); - let post2 = new Post({message: "hola!"}); + let post1 = new Post({ message: "howdy!" }); + let post2 = new Post({ message: "hola!" }); await post1.save(); await post2.save(); - let response = await request(app) - .get("/posts"); + let response = await request(app).get("/posts"); expect(response.body.token).toEqual(undefined); - }) - }) + }); + }); }); diff --git a/api/spec/controllers/users.spec.js b/api/spec/controllers/users.spec.js index adccba0b6..5855635b9 100644 --- a/api/spec/controllers/users.spec.js +++ b/api/spec/controllers/users.spec.js @@ -22,6 +22,8 @@ describe("/users", () => { .send({email: "scarlett@email.com", password: "1234"}) let users = await User.find() let newUser = users[users.length - 1] + console.log(users) + console.log(newUser.email) expect(newUser.email).toEqual("scarlett@email.com") }) }) diff --git a/api/spec/models/post.spec.js b/api/spec/models/post.spec.js index 3acfd48ce..3407c6fab 100644 --- a/api/spec/models/post.spec.js +++ b/api/spec/models/post.spec.js @@ -37,4 +37,22 @@ describe("Post model", () => { }); }); }); + + it("can save a post with userid", (done) => { + var post = new Post({ message: "some message", posterUserId: "12345" }); + + post.save((err) => { + expect(err).toBeNull(); + + Post.find((err, posts) => { + expect(err).toBeNull(); + + expect(posts[0]).toMatchObject({ + message: "some message", + posterUserId: "12345", + }); + done(); + }); + }); + }); }); diff --git a/frontend/cypress.config.js b/frontend/cypress.config.js index 2e2eb5a8c..fa82fc97d 100644 --- a/frontend/cypress.config.js +++ b/frontend/cypress.config.js @@ -12,6 +12,6 @@ module.exports = defineConfig({ setupNodeEvents(on, config) { // implement node event listeners here }, - baseUrl: 'http://localhost:3000' + baseUrl: "http://localhost:3000", }, }); diff --git a/frontend/cypress/e2e/home.cy.js b/frontend/cypress/e2e/home.cy.js new file mode 100644 index 000000000..0c26f91ea --- /dev/null +++ b/frontend/cypress/e2e/home.cy.js @@ -0,0 +1,14 @@ +describe("Homepage", () => { + it("redirects to '/login' when login button is pressed", () => { + cy.visit('/'); + cy.get('[href="/login"] > button').click(); + + cy.url().should("include", "/login"); + }) + it("redirects to '/signup' when sign up button is pressed", () => { + cy.visit('/'); + cy.get('[href="/signup"] > button').click(); + + cy.url().should("include", "/signup"); + }) +}) \ No newline at end of file diff --git a/frontend/cypress/e2e/making_a_post.cy.js b/frontend/cypress/e2e/making_a_post.cy.js index e69de29bb..d2572e97a 100644 --- a/frontend/cypress/e2e/making_a_post.cy.js +++ b/frontend/cypress/e2e/making_a_post.cy.js @@ -0,0 +1,12 @@ +describe("making a post", () => { + before(() => { + cy.signup("user@email.com", "12345678"); + }); + + it("can log in and create a post", () => { + cy.visit("/login"); + cy.get("#email").type("user@email.com"); + cy.get("#password").type("12345678"); + cy.get("#submit").click(); + }); +}); diff --git a/frontend/cypress/e2e/making_a_post_on_page.cy.js b/frontend/cypress/e2e/making_a_post_on_page.cy.js new file mode 100644 index 000000000..790aba562 --- /dev/null +++ b/frontend/cypress/e2e/making_a_post_on_page.cy.js @@ -0,0 +1,26 @@ +describe("Making a post", () => { + before(() => { + cy.signup("user@email.com", "12345678"); + }); + + it("when signed-in when can make a post", () => { + cy.visit("/login"); + cy.get("#email").type("someone@example.com"); + cy.get("#password").type("password"); + cy.get("#submit").click(); + cy.visit("/new_post"); + cy.get("#content").type("this is a post"); + cy.get("#submit").click(); + + cy.url().should("include", "/posts"); + }); + + it("redirects to login when not signed-in", () => { + + cy.visit("/new_post"); + cy.get("#content").type("this is a post"); + cy.get("#submit").click(); + + cy.url().should("include", "/login"); + }); +}); diff --git a/frontend/cypress/e2e/signing_in.cy.js b/frontend/cypress/e2e/signing_in.cy.js index 93fe6bab3..aca20b90e 100644 --- a/frontend/cypress/e2e/signing_in.cy.js +++ b/frontend/cypress/e2e/signing_in.cy.js @@ -1,8 +1,7 @@ describe("Signing in", () => { - before(() => { - cy.signup("user@email.com", "12345678") - }) + cy.signup("user@email.com", "12345678"); + }); it("with valid credentials, redirects to '/posts'", () => { cy.visit("/login"); @@ -28,4 +27,4 @@ describe("Signing in", () => { cy.url().should("include", "/login"); }); -}); \ No newline at end of file +}); diff --git a/frontend/cypress/e2e/signing_up.cy.js b/frontend/cypress/e2e/signing_up.cy.js index 747f70c89..77bc5cc75 100644 --- a/frontend/cypress/e2e/signing_up.cy.js +++ b/frontend/cypress/e2e/signing_up.cy.js @@ -23,4 +23,4 @@ describe("Signing up", () => { cy.url().should("include", "/signup"); }); -}); \ No newline at end of file +}); diff --git a/frontend/cypress/screenshots/signing_in.cy.js/Signing in -- with valid credentials, redirects to 'posts' (failed).png b/frontend/cypress/screenshots/signing_in.cy.js/Signing in -- with valid credentials, redirects to 'posts' (failed).png new file mode 100644 index 000000000..f7a3f4716 Binary files /dev/null and b/frontend/cypress/screenshots/signing_in.cy.js/Signing in -- with valid credentials, redirects to 'posts' (failed).png differ diff --git a/frontend/cypress/screenshots/signing_up.cy.js/Signing up -- with valid credentials, redirects to 'login' (failed).png b/frontend/cypress/screenshots/signing_up.cy.js/Signing up -- with valid credentials, redirects to 'login' (failed).png new file mode 100644 index 000000000..83c205ebb Binary files /dev/null and b/frontend/cypress/screenshots/signing_up.cy.js/Signing up -- with valid credentials, redirects to 'login' (failed).png differ diff --git a/frontend/cypress/support/commands.js b/frontend/cypress/support/commands.js index e765fd7ee..516f91a7c 100644 --- a/frontend/cypress/support/commands.js +++ b/frontend/cypress/support/commands.js @@ -10,12 +10,12 @@ // // // -- This is a parent command -- -Cypress.Commands.add('signup', (email, password) => { +Cypress.Commands.add("signup", (email, password) => { cy.visit("/signup"); cy.get("#email").type(email); cy.get("#password").type(password); cy.get("#submit").click(); -}) +}); // // // -- This is a child command -- @@ -27,4 +27,4 @@ Cypress.Commands.add('signup', (email, password) => { // // // -- This will overwrite an existing command -- -// Cypress.Commands.overwrite('visit', (originalFn, url, options) => { ... }) \ No newline at end of file +// Cypress.Commands.overwrite('visit', (originalFn, url, options) => { ... }) diff --git a/frontend/cypress/support/component-index.html b/frontend/cypress/support/component-index.html index ac6e79fd8..e39ba4296 100644 --- a/frontend/cypress/support/component-index.html +++ b/frontend/cypress/support/component-index.html @@ -1,12 +1,12 @@ - - - + + + Components App
- \ No newline at end of file + diff --git a/frontend/cypress/support/component.js b/frontend/cypress/support/component.js index 8f9154b5e..e3c234921 100644 --- a/frontend/cypress/support/component.js +++ b/frontend/cypress/support/component.js @@ -14,14 +14,14 @@ // *********************************************************** // Import commands.js using ES2015 syntax: -import './commands' +import "./commands"; // Alternatively you can use CommonJS syntax: // require('./commands') -import { mount } from 'cypress/react18' +import { mount } from "cypress/react18"; -Cypress.Commands.add('mount', mount) +Cypress.Commands.add("mount", mount); // Example use: -// cy.mount() \ No newline at end of file +// cy.mount() diff --git a/frontend/cypress/support/e2e.js b/frontend/cypress/support/e2e.js index 0e7290a13..3a2522438 100644 --- a/frontend/cypress/support/e2e.js +++ b/frontend/cypress/support/e2e.js @@ -14,7 +14,7 @@ // *********************************************************** // Import commands.js using ES2015 syntax: -import './commands' +import "./commands"; // Alternatively you can use CommonJS syntax: -// require('./commands') \ No newline at end of file +// require('./commands') diff --git a/frontend/cypress/videos/making_a_post.cy.js.mp4 b/frontend/cypress/videos/making_a_post.cy.js.mp4 deleted file mode 100644 index 1cf991fc6..000000000 Binary files a/frontend/cypress/videos/making_a_post.cy.js.mp4 and /dev/null differ diff --git a/frontend/cypress/videos/signing_in.cy.js.mp4 b/frontend/cypress/videos/signing_in.cy.js.mp4 deleted file mode 100644 index e72b43561..000000000 Binary files a/frontend/cypress/videos/signing_in.cy.js.mp4 and /dev/null differ diff --git a/frontend/cypress/videos/signing_up.cy.js.mp4 b/frontend/cypress/videos/signing_up.cy.js.mp4 deleted file mode 100644 index bbc1b6bd8..000000000 Binary files a/frontend/cypress/videos/signing_up.cy.js.mp4 and /dev/null differ diff --git a/frontend/src/components/app/App.js b/frontend/src/components/app/App.js index 760c05a4a..d8ddd616c 100644 --- a/frontend/src/components/app/App.js +++ b/frontend/src/components/app/App.js @@ -1,22 +1,25 @@ -import './App.css'; -import LoginForm from '../auth/LoginForm' -import SignUpForm from '../user/SignUpForm' -import React, { useState } from 'react'; -import Feed from '../feed/Feed' -import { - useNavigate, - Routes, - Route, -} from "react-router-dom"; + +import "./App.css"; +import LoginForm from "../auth/LoginForm"; +import SignUpForm from "../user/SignUpForm"; +import NewPostForm from "../post/NewPostForm"; +import React, { useState } from "react"; +import Feed from "../feed/Feed"; +import { useNavigate, Routes, Route } from "react-router-dom"; const App = () => { - return ( - - }/> - }/> - }/> - - ); -} + return ( + + } /> + } /> + } /> + } + /> + + ); +}; + export default App; diff --git a/frontend/src/components/auth/LoginForm.cy.js b/frontend/src/components/auth/LoginForm.cy.js index 1fdd77899..21812be17 100644 --- a/frontend/src/components/auth/LoginForm.cy.js +++ b/frontend/src/components/auth/LoginForm.cy.js @@ -1,17 +1,17 @@ -import LoginForm from './LoginForm' -const navigate = () => {} +import LoginForm from "./LoginForm"; +const navigate = () => {}; describe("Logging in", () => { it("calls the /tokens endpoint", () => { - cy.mount() + cy.mount(); - cy.intercept('POST', '/tokens', { token: "fakeToken" }).as("loginRequest") + cy.intercept("POST", "/tokens", { token: "fakeToken" }).as("loginRequest"); cy.get("#email").type("someone@example.com"); cy.get("#password").type("password"); cy.get("#submit").click(); - cy.wait('@loginRequest').then( interception => { - expect(interception.response.body.token).to.eq("fakeToken") - }) - }) -}) + cy.wait("@loginRequest").then((interception) => { + expect(interception.response.body.token).to.eq("fakeToken"); + }); + }); +}); diff --git a/frontend/src/components/auth/LoginForm.js b/frontend/src/components/auth/LoginForm.js index 69ce0a192..4af987155 100644 --- a/frontend/src/components/auth/LoginForm.js +++ b/frontend/src/components/auth/LoginForm.js @@ -1,4 +1,4 @@ -import React, { useState } from 'react'; +import React, { useState } from "react"; const LogInForm = ({ navigate }) => { const [email, setEmail] = useState(""); @@ -7,41 +7,52 @@ const LogInForm = ({ navigate }) => { const handleSubmit = async (event) => { event.preventDefault(); - let response = await fetch( '/tokens', { - method: 'post', + let response = await fetch("/tokens", { + method: "post", headers: { - 'Content-Type': 'application/json', + "Content-Type": "application/json", }, - body: JSON.stringify({ email: email, password: password }) - }) + body: JSON.stringify({ email: email, password: password }), + }); - if(response.status !== 201) { - console.log("yay") - navigate('/login') + if (response.status !== 201) { + console.log("yay"); + navigate("/login"); } else { - console.log("oop") - let data = await response.json() - window.localStorage.setItem("token", data.token) - navigate('/posts'); + console.log("oop"); + let data = await response.json(); + window.localStorage.setItem("token", data.token); + navigate("/posts"); } - } + }; const handleEmailChange = (event) => { - setEmail(event.target.value) - } + setEmail(event.target.value); + }; const handlePasswordChange = (event) => { - setPassword(event.target.value) - } - - - return ( -
- - - -
- ); -} + setPassword(event.target.value); + }; + + return ( +
+ + + +
+ ); +}; export default LogInForm; diff --git a/frontend/src/components/feed/Feed.cy.js b/frontend/src/components/feed/Feed.cy.js index a99ea2e37..af1a9fbf9 100644 --- a/frontend/src/components/feed/Feed.cy.js +++ b/frontend/src/components/feed/Feed.cy.js @@ -1,10 +1,11 @@ -import Feed from './Feed' -const navigate = () => {} +import Feed from "./Feed"; +const navigate = () => {}; describe("Feed", () => { it("Calls the /posts endpoint and lists all the posts", () => { +<<<<<<< HEAD window.localStorage.setItem("token", "fakeToken") - cy.mount() + cy.intercept('GET', '/posts', (req) => { req.reply({ @@ -17,10 +18,32 @@ describe("Feed", () => { } ).as("getPosts") + //line below has been moved due to cy.intercept not intercepting in time + cy.mount() + cy.wait("@getPosts").then(() =>{ +======= + window.localStorage.setItem("token", "fakeToken"); + + cy.intercept("GET", "/posts", (req) => { + req.reply({ + statusCode: 200, + body: { + posts: [ + { _id: 1, message: "Hello, world" }, + { _id: 2, message: "Hello again, world" }, + ], + }, + }); + }).as("getPosts"); + + cy.mount(); + + cy.wait("@getPosts").then(() => { +>>>>>>> main cy.get('[data-cy="post"]') - .should('contain.text', "Hello, world") - .and('contain.text', "Hello again, world") - }) - }) -}) + .should("contain.text", "Hello, world") + .and("contain.text", "Hello again, world"); + }); + }); +}); diff --git a/frontend/src/components/feed/Feed.js b/frontend/src/components/feed/Feed.js index fc89c68fc..7bb3c2167 100644 --- a/frontend/src/components/feed/Feed.js +++ b/frontend/src/components/feed/Feed.js @@ -1,49 +1,46 @@ -import React, { useEffect, useState } from 'react'; -import Post from '../post/Post' +import React, { useEffect, useState } from "react"; +import Post from "../post/Post"; const Feed = ({ navigate }) => { const [posts, setPosts] = useState([]); const [token, setToken] = useState(window.localStorage.getItem("token")); useEffect(() => { - if(token) { + if (token) { fetch("/posts", { headers: { - 'Authorization': `Bearer ${token}` - } + Authorization: `Bearer ${token}`, + }, }) - .then(response => response.json()) - .then(async data => { - window.localStorage.setItem("token", data.token) - setToken(window.localStorage.getItem("token")) + .then((response) => response.json()) + .then(async (data) => { + window.localStorage.setItem("token", data.token); + setToken(window.localStorage.getItem("token")); setPosts(data.posts); - }) + }); } - }, []) - + }, []); const logout = () => { - window.localStorage.removeItem("token") - navigate('/login') + window.localStorage.removeItem("token"); + navigate("/login"); + }; + + if (token) { + return ( + <> +

Posts

+ +
+ {posts.map((post) => ( + + ))} +
+ + ); + } else { + navigate("/login"); } - - if(token) { - return( - <> -

Posts

- -
- {posts.map( - (post) => ( ) - )} -
- - ) - } else { - navigate('/signin') - } -} +}; -export default Feed; \ No newline at end of file +export default Feed; diff --git a/frontend/src/components/home/Home.css b/frontend/src/components/home/Home.css new file mode 100644 index 000000000..e69de29bb diff --git a/frontend/src/components/home/Home.js b/frontend/src/components/home/Home.js new file mode 100644 index 000000000..d465822dd --- /dev/null +++ b/frontend/src/components/home/Home.js @@ -0,0 +1,22 @@ +import React, { useEffect, useState } from "react"; + +const Home = ({navigate}) => { + return ( + <> +
+

Hello there!

+
+
+ +

Select below where you'd like to go

+ + + +
+ + ); +} + + + +export default Home; diff --git a/frontend/src/components/post/NewPostForm.cy.js b/frontend/src/components/post/NewPostForm.cy.js new file mode 100644 index 000000000..ced7daa72 --- /dev/null +++ b/frontend/src/components/post/NewPostForm.cy.js @@ -0,0 +1,18 @@ +import NewPostForm from "./NewPostForm"; + +const navigate = () => {}; + +describe("Making New Post", () => { + it("Calls the /new_post endpoint", () => { + + cy.intercept("POST", "/posts", { message: "OK" }).as("newPostRequest"); + + cy.mount(); + + cy.get("#content").type("Fake content"); + cy.get("#submit").click(); + cy.wait("@newPostRequest").then((interception) => { + expect(interception.response.body.message).to.eq("OK"); + }); + }); +}); diff --git a/frontend/src/components/post/NewPostForm.js b/frontend/src/components/post/NewPostForm.js new file mode 100644 index 000000000..852d4101b --- /dev/null +++ b/frontend/src/components/post/NewPostForm.js @@ -0,0 +1,49 @@ +import React, { useState } from "react"; + +const NewPostForm = ({ navigate }) => { + const [token, setToken] = useState(window.localStorage.getItem("token")); + const [content, setContent] = useState(""); + + const postSubmit = async (event) => { + event.preventDefault(); + + console.log(content); + + fetch("/posts", { + method: "post", + headers: { + "Content-Type": "application/json", + Authorization: `Bearer ${token}`, + }, + body: JSON.stringify({ message: content }), + }).then(async (response) => { + if (response.status === 201) { + const data = await response.json(); + window.localStorage.setItem("token", data.token); + setToken(window.localStorage.getItem("token")); + navigate("/posts"); + } else { + navigate("/login"); + } + }); + }; + + const handleContentChange = (event) => { + setContent(event.target.value); + }; + + return ( +
+ + +
+ ); +}; + +export default NewPostForm; diff --git a/frontend/src/components/post/Post.cy.js b/frontend/src/components/post/Post.cy.js index 4ca9eec8b..0ca39ae89 100644 --- a/frontend/src/components/post/Post.cy.js +++ b/frontend/src/components/post/Post.cy.js @@ -1,8 +1,17 @@ -import Post from './Post' +import Post from "./Post"; describe("Post", () => { - it('renders a post with a message', () => { - cy.mount(); - cy.get('[data-cy="post"]').should('contain.text', "Hello, world") - }) -}) + it("renders a post with a message", () => { + cy.mount(); + cy.get('[data-cy="post"]').should("contain.text", "Hello, world"); + }); + + it("renders a post with a message and a timestamp number", () => { + cy.mount( + + ); + cy.get('[data-cy="post"]') + .should("contain", "Hello, world") + .and("contain", "Thu Dec 01 2022"); + }); +}); diff --git a/frontend/src/components/post/Post.js b/frontend/src/components/post/Post.js index 87a77c109..5d3d82911 100644 --- a/frontend/src/components/post/Post.js +++ b/frontend/src/components/post/Post.js @@ -1,9 +1,16 @@ -import React from 'react'; +import React from "react"; -const Post = ({post}) => { - return( -
{ post.message }
- ) -} +const Post = ({ post }) => { + return ( +
+
+ {post.message} +
+
+ {new Date(post.time).toString().slice(0, 28)} +
+
+ ); +}; export default Post; diff --git a/frontend/src/components/user/SignUpForm.cy.js b/frontend/src/components/user/SignUpForm.cy.js index bef3e6e1a..b0443f4ce 100644 --- a/frontend/src/components/user/SignUpForm.cy.js +++ b/frontend/src/components/user/SignUpForm.cy.js @@ -1,17 +1,17 @@ -import SignUpForm from './SignUpForm' -const navigate = () => {} +import SignUpForm from "./SignUpForm"; +const navigate = () => {}; describe("Signing up", () => { it("calls the /users endpoint", () => { - cy.mount() + cy.mount(); - cy.intercept('POST', '/users', { message: "OK" }).as("signUpRequest") + cy.intercept("POST", "/users", { message: "OK" }).as("signUpRequest"); cy.get("#email").type("someone@example.com"); cy.get("#password").type("password"); cy.get("#submit").click(); - cy.wait('@signUpRequest').then( interception => { - expect(interception.response.body.message).to.eq("OK") - }) - }) -}) + cy.wait("@signUpRequest").then((interception) => { + expect(interception.response.body.message).to.eq("OK"); + }); + }); +}); diff --git a/frontend/src/components/user/SignUpForm.js b/frontend/src/components/user/SignUpForm.js index a20af015e..1369368a2 100644 --- a/frontend/src/components/user/SignUpForm.js +++ b/frontend/src/components/user/SignUpForm.js @@ -1,45 +1,54 @@ -import React, { useState } from 'react'; +import React, { useState } from "react"; const SignUpForm = ({ navigate }) => { - const [email, setEmail] = useState(""); const [password, setPassword] = useState(""); const handleSubmit = async (event) => { event.preventDefault(); - fetch( '/users', { - method: 'post', + fetch("/users", { + method: "post", headers: { - 'Content-Type': 'application/json', + "Content-Type": "application/json", }, - body: JSON.stringify({ email: email, password: password }) - }) - .then(response => { - if(response.status === 201) { - navigate('/login') - } else { - navigate('/signup') - } - }) - } + body: JSON.stringify({ email: email, password: password }), + }).then((response) => { + if (response.status === 201) { + navigate("/login"); + } else { + navigate("/signup"); + } + }); + }; const handleEmailChange = (event) => { - setEmail(event.target.value) - } + setEmail(event.target.value); + }; const handlePasswordChange = (event) => { - setPassword(event.target.value) - } - - - return ( -
- - - -
- ); -} + setPassword(event.target.value); + }; + + return ( +
+ + + +
+ ); +}; export default SignUpForm; diff --git a/frontend/src/index.js b/frontend/src/index.js index e37d8a008..4c85a70d5 100644 --- a/frontend/src/index.js +++ b/frontend/src/index.js @@ -1,11 +1,11 @@ -import React from 'react'; -import ReactDOM from 'react-dom/client'; -import './index.css'; -import App from './components/app/App'; -import reportWebVitals from './reportWebVitals'; -import { BrowserRouter } from 'react-router-dom'; +import React from "react"; +import ReactDOM from "react-dom/client"; +import "./index.css"; +import App from "./components/app/App"; +import reportWebVitals from "./reportWebVitals"; +import { BrowserRouter } from "react-router-dom"; -const root = ReactDOM.createRoot(document.getElementById('root')); +const root = ReactDOM.createRoot(document.getElementById("root")); root.render( diff --git a/frontend/src/reportWebVitals.js b/frontend/src/reportWebVitals.js index 5253d3ad9..9ecd33f9c 100644 --- a/frontend/src/reportWebVitals.js +++ b/frontend/src/reportWebVitals.js @@ -1,6 +1,6 @@ -const reportWebVitals = onPerfEntry => { +const reportWebVitals = (onPerfEntry) => { if (onPerfEntry && onPerfEntry instanceof Function) { - import('web-vitals').then(({ getCLS, getFID, getFCP, getLCP, getTTFB }) => { + import("web-vitals").then(({ getCLS, getFID, getFCP, getLCP, getTTFB }) => { getCLS(onPerfEntry); getFID(onPerfEntry); getFCP(onPerfEntry); diff --git a/frontend/src/setupTests.js b/frontend/src/setupTests.js index 8f2609b7b..1dd407a63 100644 --- a/frontend/src/setupTests.js +++ b/frontend/src/setupTests.js @@ -2,4 +2,4 @@ // allows you to do things like: // expect(element).toHaveTextContent(/react/i) // learn more: https://github.com/testing-library/jest-dom -import '@testing-library/jest-dom'; +import "@testing-library/jest-dom";