From d683edf889691ce294a4c302946ba444e66263b3 Mon Sep 17 00:00:00 2001 From: Zac Date: Sat, 3 Sep 2022 11:35:55 +0100 Subject: [PATCH 1/5] project setup --- .gitignore | 2 ++ README.md | 37 +++++++++++++++++++++++++++++++++++++ index.html | 12 ++++++++++++ index.js | 0 package.json | 21 +++++++++++++++++++++ 5 files changed, 72 insertions(+) create mode 100644 .gitignore create mode 100644 README.md create mode 100644 index.html create mode 100644 index.js create mode 100644 package.json diff --git a/.gitignore b/.gitignore new file mode 100644 index 000000000..6fc8feddf --- /dev/null +++ b/.gitignore @@ -0,0 +1,2 @@ +node_modules +bundle.js \ No newline at end of file diff --git a/README.md b/README.md new file mode 100644 index 000000000..769d13f72 --- /dev/null +++ b/README.md @@ -0,0 +1,37 @@ +# Chitter API Frontend Challenge + +* Feel free to use Google, your notes, books, etc. but work on your own +* If you refer to the solution of another coach or student, please put a link to that in your README +* If you have a partial solution, **still check in a partial solution** +* You must submit a pull request to this repo with your code by 9am Monday morning + +Challenge: +------- + +As usual please start by forking this repo. + +We are going to write a small Twitter clone that will allow the users to post messages to a public stream. + +The scenario is similar to the [Chitter Challenge](https://github.com/makersacademy/chitter-challenge), except someone has already built a backend API for you and hosted it on Heroku. + +Your task is to build a front-end single-page-app to interface with this API. You can do this in any framework you like, or in pure Javascript. [The API documentation is here.](https://github.com/makersacademy/chitter_api_backend) + +Here are some interactions the API supports. Implement as many as you see fit. + +* Creating Users +* Logging in +* Posting Peeps +* Viewing all Peeps *(I suggest you start here)* +* Viewing individual Peeps +* Deleting Peeps +* Liking Peeps +* Unliking Peeps + +We are looking for well tested, easy to read, easy to change code. This is more important than the number of interactions you implement. + +Note that others may be doing the same task at the same time, so the data may change as you are using it. + +## Utilities you might find useful + +* [The Fetch API](https://developer.mozilla.org/en-US/docs/Web/API/Fetch_API/Using_Fetch) for making requests. +* [Postman](https://www.getpostman.com/) or [Insomnia](https://insomnia.rest/) for exploring the API. diff --git a/index.html b/index.html new file mode 100644 index 000000000..f73d88d48 --- /dev/null +++ b/index.html @@ -0,0 +1,12 @@ + + + + + + + Document + + + + + diff --git a/index.js b/index.js new file mode 100644 index 000000000..e69de29bb diff --git a/package.json b/package.json new file mode 100644 index 000000000..4aafbe92a --- /dev/null +++ b/package.json @@ -0,0 +1,21 @@ +{ + "name": "frontend-api-challenge", + "version": "1.0.0", + "description": "* Feel free to use Google, your notes, books, etc. but work on your own * If you refer to the solution of another coach or student, please put a link to that in your README * If you have a partial solution, **still check in a partial solution** * You must submit a pull request to this repo with your code by 9am Monday morning", + "main": "index.js", + "scripts": { + "test": "echo \"Error: no test specified\" && exit 1", + "build": "esbuild index.js --bundle --outfile=bundle.js --watch" + }, + "repository": { + "type": "git", + "url": "git+https://github.com/ZacMossHK/frontend-api-challenge.git" + }, + "keywords": [], + "author": "", + "license": "ISC", + "bugs": { + "url": "https://github.com/ZacMossHK/frontend-api-challenge/issues" + }, + "homepage": "https://github.com/ZacMossHK/frontend-api-challenge#readme" +} From ef22e137df3a8b5ff25af8326e0546d85dd22b87 Mon Sep 17 00:00:00 2001 From: Zac Date: Sat, 3 Sep 2022 13:01:44 +0100 Subject: [PATCH 2/5] chitterView loads on index.html but doesn't pass test --- chitterApi.js | 9 +++ chitterApi.test.js | 21 ++++++ chitterPeepsModel.js | 15 ++++ chitterPeepsModel.test.js | 14 ++++ chitterView.js | 61 +++++++++++++++++ chitterView.test.js | 47 +++++++++++++ index.html | 2 +- index.js | 6 ++ package-lock.json | 141 ++++++++++++++++++++++++++++++++++++++ package.json | 5 +- testPeepsArray.js | 33 +++++++++ 11 files changed, 352 insertions(+), 2 deletions(-) create mode 100644 chitterApi.js create mode 100644 chitterApi.test.js create mode 100644 chitterPeepsModel.js create mode 100644 chitterPeepsModel.test.js create mode 100644 chitterView.js create mode 100644 chitterView.test.js create mode 100644 package-lock.json create mode 100644 testPeepsArray.js diff --git a/chitterApi.js b/chitterApi.js new file mode 100644 index 000000000..fc65f2b93 --- /dev/null +++ b/chitterApi.js @@ -0,0 +1,9 @@ +class ChitterApi { + fetchPeeps(callback) { + fetch("https://chitter-backend-api-v2.herokuapp.com/peeps") + .then((response) => response.json()) + .then(callback); + } +} + +module.exports = ChitterApi; diff --git a/chitterApi.test.js b/chitterApi.test.js new file mode 100644 index 000000000..e727a928e --- /dev/null +++ b/chitterApi.test.js @@ -0,0 +1,21 @@ +const ChitterApi = require("./chitterApi"); + +require("jest-fetch-mock").enableMocks(); + +let api, testPeepsArray; + +describe("ChitterApi class", () => { + beforeEach(() => { + api = new ChitterApi(); + testPeepsArray = require("./testPeepsArray"); + }); + it("gets peeps from chitter", () => { + expect.assertions(3); + fetch.mockResponseOnce(JSON.stringify(testPeepsArray)); + api.fetchPeeps((data) => { + expect(data.length).toBe(2); + expect(data[0].id).toBe(1494); + expect(data[1].id).toBe(1461); + }); + }); +}); diff --git a/chitterPeepsModel.js b/chitterPeepsModel.js new file mode 100644 index 000000000..048210a0c --- /dev/null +++ b/chitterPeepsModel.js @@ -0,0 +1,15 @@ +class ChitterPeepsModel { + constructor() { + this.peepsArray = []; + } + + setPeeps(peepsArray) { + this.peepsArray = peepsArray; + } + + loadPeeps() { + return this.peepsArray; + } +} + +module.exports = ChitterPeepsModel; diff --git a/chitterPeepsModel.test.js b/chitterPeepsModel.test.js new file mode 100644 index 000000000..e50f3ada3 --- /dev/null +++ b/chitterPeepsModel.test.js @@ -0,0 +1,14 @@ +const ChitterPeepsModel = require("./chitterPeepsModel"); + +let model, peepsArray; + +describe("ChitterPeepsModel class", () => { + beforeEach(() => { + model = new ChitterPeepsModel(); + testPeepsArray = require("./testPeepsArray"); + }); + it("sets and loads Peeps", () => { + model.setPeeps(peepsArray); + expect(model.loadPeeps()).toBe(peepsArray); + }); +}); diff --git a/chitterView.js b/chitterView.js new file mode 100644 index 000000000..7d2da0212 --- /dev/null +++ b/chitterView.js @@ -0,0 +1,61 @@ +class ChitterView { + constructor(model, api) { + this.model = model; + this.api = api; + this.peepsContainerEl = document.querySelector("#peeps-container"); + } + displayPeeps() { + this.model.loadPeeps().forEach((peep) => { + const peepDivEl = document.createElement("div"); + peepDivEl.className = "peep"; + peepDivEl.append(this.createPeepBody(peep)); + peepDivEl.append(this.createPeepUserHandle(peep)); + peepDivEl.append(this.createPeepDatetimeCreated(peep)); + peepDivEl.append(this.createPeepLikesCount(peep)); + this.peepsContainerEl.append(peepDivEl); + }); + } + + createPeepBody(peep) { + const peepBodyEl = document.createElement("p"); + peepBodyEl.className = "peep-body"; + peepBodyEl.textContent = peep.body; + return peepBodyEl; + } + + createPeepUserHandle(peep) { + const peepUserHandleEl = document.createElement("p"); + peepUserHandleEl.className = "peep-user-handle"; + peepUserHandleEl.textContent = peep.user.handle; + return peepUserHandleEl; + } + + createPeepDatetimeCreated(peep) { + const peepDatetimeCreatedEl = document.createElement("p"); + peepDatetimeCreatedEl.className = "peep-datetime-created"; + const peepCreatedString = new Date(peep.created_at); + const peepTimeString = peepCreatedString.toTimeString().substring(0, 5); + const peepDateString = peepCreatedString.toDateString(); + peepDatetimeCreatedEl.textContent = `${peepTimeString} ${peepDateString}`; + return peepDatetimeCreatedEl; + } + + createPeepLikesCount(peep) { + const peepLikesCountEl = document.createElement("p"); + peepLikesCountEl.className = "peep-datetime-created"; + const likesCount = peep.likes.length; + peepLikesCountEl.textContent = `${likesCount} ${ + likesCount === 1 ? "like" : "likes" + }`; + return peepLikesCountEl; + } + + displayPeepsFromApi() { + this.api.fetchPeeps((peeps) => { + this.model.setPeeps(peeps); + this.displayPeeps(); + }); + } +} + +module.exports = ChitterView; diff --git a/chitterView.test.js b/chitterView.test.js new file mode 100644 index 000000000..f99f10cbe --- /dev/null +++ b/chitterView.test.js @@ -0,0 +1,47 @@ +/** + * @jest-environment jsdom + */ + +const fs = require("fs"); +const ChitterPeepsModel = require("./chitterPeepsModel"); +const ChitterView = require("./chitterView"); +let view, model, mockApi, testPeepsArray; + +describe("ChitterView", () => { + beforeEach(() => { + document.body.innerHTML = fs.readFileSync("./index.html"); + model = new ChitterPeepsModel(); + testPeepsArray = require("./testPeepsArray"); + mockApi = { fetchPeeps: () => testPeepsArray }; + view = new ChitterView(model, mockApi); + }); + it("displays all Peeps", () => { + view.displayPeepsFromApi(); + const peepDivEls = document.querySelector("div.peep"); + expect(peepDivEls.length).toBe(2); + expect(peepDivEls[0].querySelector(".peep-body").textContent).toBe( + "First peep" + ); + expect(peepDivEls[0].querySelector(".peep-user-handle").textContent).toBe( + "jony144" + ); + expect( + peepDivEls[0].querySelector(".peep-datetime-created").textContent + ).toBe("12:33 Sat Aug 20 2022"); + expect(peepDivEls[0].querySelector(".peep-likes-count").textContent).toBe( + "1 like" + ); + expect(peepDivEls[1].querySelector(".peep-body").textContent).toBe( + "i'm tiz" + ); + expect(peepDivEls[1].querySelector(".peep-user-handle").textContent).toBe( + "tiz" + ); + expect( + peepDivEls[1].querySelector(".peep-datetime-created").textContent + ).toBe("13:04 Sun Aug 07 2022'"); + expect(peepDivEls[1].querySelector(".peep-likes-count").textContent).toBe( + "0 likes" + ); + }); +}); diff --git a/index.html b/index.html index f73d88d48..7903184be 100644 --- a/index.html +++ b/index.html @@ -8,5 +8,5 @@ - +
diff --git a/index.js b/index.js index e69de29bb..19aff1175 100644 --- a/index.js +++ b/index.js @@ -0,0 +1,6 @@ +const ChitterApi = require("./chitterApi"); +const ChitterPeepsModel = require("./chitterPeepsModel"); +const ChitterView = require("./chitterView"); + +view = new ChitterView(new ChitterPeepsModel(), new ChitterApi()); +view.displayPeepsFromApi(); diff --git a/package-lock.json b/package-lock.json new file mode 100644 index 000000000..52bab134f --- /dev/null +++ b/package-lock.json @@ -0,0 +1,141 @@ +{ + "name": "frontend-api-challenge", + "version": "1.0.0", + "lockfileVersion": 2, + "requires": true, + "packages": { + "": { + "name": "frontend-api-challenge", + "version": "1.0.0", + "license": "ISC", + "devDependencies": { + "jest-fetch-mock": "^3.0.3" + } + }, + "node_modules/cross-fetch": { + "version": "3.1.5", + "resolved": "https://registry.npmjs.org/cross-fetch/-/cross-fetch-3.1.5.tgz", + "integrity": "sha512-lvb1SBsI0Z7GDwmuid+mU3kWVBwTVUbe7S0H52yaaAdQOXq2YktTCZdlAcNKFzE6QtRz0snpw9bNiPeOIkkQvw==", + "dev": true, + "dependencies": { + "node-fetch": "2.6.7" + } + }, + "node_modules/jest-fetch-mock": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/jest-fetch-mock/-/jest-fetch-mock-3.0.3.tgz", + "integrity": "sha512-Ux1nWprtLrdrH4XwE7O7InRY6psIi3GOsqNESJgMJ+M5cv4A8Lh7SN9d2V2kKRZ8ebAfcd1LNyZguAOb6JiDqw==", + "dev": true, + "dependencies": { + "cross-fetch": "^3.0.4", + "promise-polyfill": "^8.1.3" + } + }, + "node_modules/node-fetch": { + "version": "2.6.7", + "resolved": "https://registry.npmjs.org/node-fetch/-/node-fetch-2.6.7.tgz", + "integrity": "sha512-ZjMPFEfVx5j+y2yF35Kzx5sF7kDzxuDj6ziH4FFbOp87zKDZNx8yExJIb05OGF4Nlt9IHFIMBkRl41VdvcNdbQ==", + "dev": true, + "dependencies": { + "whatwg-url": "^5.0.0" + }, + "engines": { + "node": "4.x || >=6.0.0" + }, + "peerDependencies": { + "encoding": "^0.1.0" + }, + "peerDependenciesMeta": { + "encoding": { + "optional": true + } + } + }, + "node_modules/promise-polyfill": { + "version": "8.2.3", + "resolved": "https://registry.npmjs.org/promise-polyfill/-/promise-polyfill-8.2.3.tgz", + "integrity": "sha512-Og0+jCRQetV84U8wVjMNccfGCnMQ9mGs9Hv78QFe+pSDD3gWTpz0y+1QCuxy5d/vBFuZ3iwP2eycAkvqIMPmWg==", + "dev": true + }, + "node_modules/tr46": { + "version": "0.0.3", + "resolved": "https://registry.npmjs.org/tr46/-/tr46-0.0.3.tgz", + "integrity": "sha512-N3WMsuqV66lT30CrXNbEjx4GEwlow3v6rr4mCcv6prnfwhS01rkgyFdjPNBYd9br7LpXV1+Emh01fHnq2Gdgrw==", + "dev": true + }, + "node_modules/webidl-conversions": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/webidl-conversions/-/webidl-conversions-3.0.1.tgz", + "integrity": "sha512-2JAn3z8AR6rjK8Sm8orRC0h/bcl/DqL7tRPdGZ4I1CjdF+EaMLmYxBHyXuKL849eucPFhvBoxMsflfOb8kxaeQ==", + "dev": true + }, + "node_modules/whatwg-url": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/whatwg-url/-/whatwg-url-5.0.0.tgz", + "integrity": "sha512-saE57nupxk6v3HY35+jzBwYa0rKSy0XR8JSxZPwgLr7ys0IBzhGviA1/TUGJLmSVqs8pb9AnvICXEuOHLprYTw==", + "dev": true, + "dependencies": { + "tr46": "~0.0.3", + "webidl-conversions": "^3.0.0" + } + } + }, + "dependencies": { + "cross-fetch": { + "version": "3.1.5", + "resolved": "https://registry.npmjs.org/cross-fetch/-/cross-fetch-3.1.5.tgz", + "integrity": "sha512-lvb1SBsI0Z7GDwmuid+mU3kWVBwTVUbe7S0H52yaaAdQOXq2YktTCZdlAcNKFzE6QtRz0snpw9bNiPeOIkkQvw==", + "dev": true, + "requires": { + "node-fetch": "2.6.7" + } + }, + "jest-fetch-mock": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/jest-fetch-mock/-/jest-fetch-mock-3.0.3.tgz", + "integrity": "sha512-Ux1nWprtLrdrH4XwE7O7InRY6psIi3GOsqNESJgMJ+M5cv4A8Lh7SN9d2V2kKRZ8ebAfcd1LNyZguAOb6JiDqw==", + "dev": true, + "requires": { + "cross-fetch": "^3.0.4", + "promise-polyfill": "^8.1.3" + } + }, + "node-fetch": { + "version": "2.6.7", + "resolved": "https://registry.npmjs.org/node-fetch/-/node-fetch-2.6.7.tgz", + "integrity": "sha512-ZjMPFEfVx5j+y2yF35Kzx5sF7kDzxuDj6ziH4FFbOp87zKDZNx8yExJIb05OGF4Nlt9IHFIMBkRl41VdvcNdbQ==", + "dev": true, + "requires": { + "whatwg-url": "^5.0.0" + } + }, + "promise-polyfill": { + "version": "8.2.3", + "resolved": "https://registry.npmjs.org/promise-polyfill/-/promise-polyfill-8.2.3.tgz", + "integrity": "sha512-Og0+jCRQetV84U8wVjMNccfGCnMQ9mGs9Hv78QFe+pSDD3gWTpz0y+1QCuxy5d/vBFuZ3iwP2eycAkvqIMPmWg==", + "dev": true + }, + "tr46": { + "version": "0.0.3", + "resolved": "https://registry.npmjs.org/tr46/-/tr46-0.0.3.tgz", + "integrity": "sha512-N3WMsuqV66lT30CrXNbEjx4GEwlow3v6rr4mCcv6prnfwhS01rkgyFdjPNBYd9br7LpXV1+Emh01fHnq2Gdgrw==", + "dev": true + }, + "webidl-conversions": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/webidl-conversions/-/webidl-conversions-3.0.1.tgz", + "integrity": "sha512-2JAn3z8AR6rjK8Sm8orRC0h/bcl/DqL7tRPdGZ4I1CjdF+EaMLmYxBHyXuKL849eucPFhvBoxMsflfOb8kxaeQ==", + "dev": true + }, + "whatwg-url": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/whatwg-url/-/whatwg-url-5.0.0.tgz", + "integrity": "sha512-saE57nupxk6v3HY35+jzBwYa0rKSy0XR8JSxZPwgLr7ys0IBzhGviA1/TUGJLmSVqs8pb9AnvICXEuOHLprYTw==", + "dev": true, + "requires": { + "tr46": "~0.0.3", + "webidl-conversions": "^3.0.0" + } + } + } +} diff --git a/package.json b/package.json index 4aafbe92a..8848f400f 100644 --- a/package.json +++ b/package.json @@ -17,5 +17,8 @@ "bugs": { "url": "https://github.com/ZacMossHK/frontend-api-challenge/issues" }, - "homepage": "https://github.com/ZacMossHK/frontend-api-challenge#readme" + "homepage": "https://github.com/ZacMossHK/frontend-api-challenge#readme", + "devDependencies": { + "jest-fetch-mock": "^3.0.3" + } } diff --git a/testPeepsArray.js b/testPeepsArray.js new file mode 100644 index 000000000..be986cfd4 --- /dev/null +++ b/testPeepsArray.js @@ -0,0 +1,33 @@ +testPeepsArray = peepsArray = [ + { + id: 1494, + body: "First peep", + created_at: "2022-08-20T11:33:02.912Z", + updated_at: "2022-08-20T11:33:02.912Z", + user: { + id: 1124, + handle: "jony144", + }, + likes: [ + { + user: { + id: 1120, + handle: "margaritapeter", + }, + }, + ], + }, + { + id: 1461, + body: "i'm tiz", + created_at: "2022-08-07T12:04:31.253Z", + updated_at: "2022-08-07T12:04:31.253Z", + user: { + id: 1113, + handle: "tiz", + }, + likes: [], + }, +]; + +module.exports = testPeepsArray; From 2310fd10b0e7fd630e5d55f00e48be3263bb76b7 Mon Sep 17 00:00:00 2001 From: Zac Date: Sun, 4 Sep 2022 22:54:26 +0100 Subject: [PATCH 3/5] written test for post a peep in chitterview.test --- chitterApi.js | 52 ++++++++++++++ chitterApi.test.js | 51 +++++++++++++- chitterModel.js | 41 +++++++++++ chitterModel.test.js | 25 +++++++ chitterPeepsModel.js | 15 ---- chitterPeepsModel.test.js | 14 ---- chitterView.js | 106 ++++++++++++++++++---------- chitterView.test.js | 140 +++++++++++++++++++++++++++++++++++-- displaySinglePeep.js | 48 +++++++++++++ index.html | 27 ++++++- index.js | 9 ++- testPostPeepResponse.js | 20 ++++++ testUserSessionResponse.js | 6 ++ 13 files changed, 475 insertions(+), 79 deletions(-) create mode 100644 chitterModel.js create mode 100644 chitterModel.test.js delete mode 100644 chitterPeepsModel.js delete mode 100644 chitterPeepsModel.test.js create mode 100644 displaySinglePeep.js create mode 100644 testPostPeepResponse.js create mode 100644 testUserSessionResponse.js diff --git a/chitterApi.js b/chitterApi.js index fc65f2b93..a42ec4a7d 100644 --- a/chitterApi.js +++ b/chitterApi.js @@ -4,6 +4,58 @@ class ChitterApi { .then((response) => response.json()) .then(callback); } + + createUser(newUsername, newPassword, callback) { + fetch("https://chitter-backend-api-v2.herokuapp.com/users", { + method: "POST", + headers: { + "Content-Type": "application/json", + }, + body: JSON.stringify({ + user: { handle: newUsername, password: newPassword }, + }), + }) + .then((response) => response.json()) + .then(callback); + } + + loginUser(username, password, callback) { + fetch("https://chitter-backend-api-v2.herokuapp.com/sessions", { + method: "POST", + headers: { + "Content-Type": "application/json", + }, + body: JSON.stringify({ + session: { handle: username, password: password }, + }), + }) + .then((response) => response.json()) + .then(callback); + } + + postPeep(sessionKey, userId, peepBody, callback) { + fetch("https://chitter-backend-api-v2.herokuapp.com/peeps", { + method: "POST", + headers: { + Authorization: `Token token=${sessionKey}`, + "Content-Type": "application/json", + }, + body: JSON.stringify({ + peep: { + user_id: userId, + body: peepBody, + }, + }), + }) + .then((response) => response.json()) + .then(callback); + } } module.exports = ChitterApi; + +// curl "https://chitter-backend-api-v2.herokuapp.com/peeps" \ +// -X POST \ +// -H "Authorization: Token token=a_valid_session_key" \ +// -H "Content-Type: application/json" \ +// -d '{"peep": {"user_id":1, "body":"my first peep :)"}}' diff --git a/chitterApi.test.js b/chitterApi.test.js index e727a928e..5e0f7f02b 100644 --- a/chitterApi.test.js +++ b/chitterApi.test.js @@ -2,20 +2,67 @@ const ChitterApi = require("./chitterApi"); require("jest-fetch-mock").enableMocks(); -let api, testPeepsArray; +let api, testPeepsArray, testUserSessionResponse, testPostPeepResponse; describe("ChitterApi class", () => { beforeEach(() => { api = new ChitterApi(); testPeepsArray = require("./testPeepsArray"); + testUserSessionResponse = require("./testUserSessionResponse"); + testPostPeepResponse = require("./testPostPeepResponse"); + fetch.resetMocks(); }); + it("gets peeps from chitter", () => { - expect.assertions(3); + expect.assertions(4); fetch.mockResponseOnce(JSON.stringify(testPeepsArray)); api.fetchPeeps((data) => { expect(data.length).toBe(2); expect(data[0].id).toBe(1494); expect(data[1].id).toBe(1461); + expect(fetch.mock.calls[0][0]).toEqual( + "https://chitter-backend-api-v2.herokuapp.com/peeps" + ); + }); + }); + + it("creates a new user", () => { + expect.assertions(2); + + fetch.mockResponseOnce(JSON.stringify(testUserSessionResponse)); + api.createUser("kay", "mypassword", (data) => { + expect(data.handle).toBe("kay"); + expect(fetch.mock.calls[0][0]).toEqual( + "https://chitter-backend-api-v2.herokuapp.com/users" + ); + }); + }); + + it("logs in a user", () => { + expect.assertions(3); + fetch.mockResponseOnce( + JSON.stringify({ + user_id: 1, + session_key: "a_valid_session_key", + }) + ); + api.loginUser("kay", "mypassword", (data) => { + expect(data.user_id).toBe(1); + expect(data.session_key).toBe("a_valid_session_key"); + expect(fetch.mock.calls[0][0]).toEqual( + "https://chitter-backend-api-v2.herokuapp.com/sessions" + ); + }); + }); + + it("posts a Peep", () => { + expect.assertions(2); + fetch.mockResponseOnce(JSON.stringify(testPostPeepResponse)); + api.postPeep("sessionKey", 1, "my first peep :)", (data) => { + expect(data).toEqual(testPostPeepResponse); + expect(fetch.mock.calls[0][0]).toEqual( + "https://chitter-backend-api-v2.herokuapp.com/peeps" + ); }); }); }); diff --git a/chitterModel.js b/chitterModel.js new file mode 100644 index 000000000..92bb01db2 --- /dev/null +++ b/chitterModel.js @@ -0,0 +1,41 @@ +class ChitterModel { + constructor() { + this.peepsArray = []; + this.userId = null; + this.sessionKey = null; + } + + setPeeps(peepsArray) { + this.peepsArray = peepsArray; + } + + loadPeeps() { + return this.peepsArray; + } + + setUserId(userId) { + this.userId = userId; + } + + loadUserId() { + return this.userId; + } + + resetUserId() { + this.userId = null; + } + + setSessionKey(sessionKey) { + this.sessionKey = sessionKey; + } + + loadSessionKey() { + return this.sessionKey; + } + + resetSessionKey() { + this.sessionKey = null; + } +} + +module.exports = ChitterModel; diff --git a/chitterModel.test.js b/chitterModel.test.js new file mode 100644 index 000000000..6c56a49aa --- /dev/null +++ b/chitterModel.test.js @@ -0,0 +1,25 @@ +const ChitterModel = require("./chitterModel"); + +let model, peepsArray; + +describe("ChitterModel class", () => { + beforeEach(() => { + model = new ChitterModel(); + testPeepsArray = require("./testPeepsArray"); + }); + + it("sets and loads Peeps", () => { + model.setPeeps(peepsArray); + expect(model.loadPeeps()).toBe(peepsArray); + }); + + it("sets the user ID", () => { + model.setUserId(1); + expect(model.loadUserId()).toBe(1); + }); + + it("sets the session key", () => { + model.setSessionKey("session key"); + expect(model.loadSessionKey()).toBe("session key"); + }); +}); diff --git a/chitterPeepsModel.js b/chitterPeepsModel.js deleted file mode 100644 index 048210a0c..000000000 --- a/chitterPeepsModel.js +++ /dev/null @@ -1,15 +0,0 @@ -class ChitterPeepsModel { - constructor() { - this.peepsArray = []; - } - - setPeeps(peepsArray) { - this.peepsArray = peepsArray; - } - - loadPeeps() { - return this.peepsArray; - } -} - -module.exports = ChitterPeepsModel; diff --git a/chitterPeepsModel.test.js b/chitterPeepsModel.test.js deleted file mode 100644 index e50f3ada3..000000000 --- a/chitterPeepsModel.test.js +++ /dev/null @@ -1,14 +0,0 @@ -const ChitterPeepsModel = require("./chitterPeepsModel"); - -let model, peepsArray; - -describe("ChitterPeepsModel class", () => { - beforeEach(() => { - model = new ChitterPeepsModel(); - testPeepsArray = require("./testPeepsArray"); - }); - it("sets and loads Peeps", () => { - model.setPeeps(peepsArray); - expect(model.loadPeeps()).toBe(peepsArray); - }); -}); diff --git a/chitterView.js b/chitterView.js index 7d2da0212..229bcfcda 100644 --- a/chitterView.js +++ b/chitterView.js @@ -1,53 +1,83 @@ class ChitterView { - constructor(model, api) { + constructor(model, api, displaySinglePeep) { this.model = model; this.api = api; - this.peepsContainerEl = document.querySelector("#peeps-container"); - } - displayPeeps() { - this.model.loadPeeps().forEach((peep) => { - const peepDivEl = document.createElement("div"); - peepDivEl.className = "peep"; - peepDivEl.append(this.createPeepBody(peep)); - peepDivEl.append(this.createPeepUserHandle(peep)); - peepDivEl.append(this.createPeepDatetimeCreated(peep)); - peepDivEl.append(this.createPeepLikesCount(peep)); - this.peepsContainerEl.append(peepDivEl); + this.displaySinglePeep = displaySinglePeep; + this.createUserContainerEl = document.querySelector( + "div#create-user-container" + ); + this.newUsernameInputEl = document.querySelector("input#create-username"); + this.newPasswordInputEl = document.querySelector("input#create-password"); + + // create user button listener + document + .querySelector("#create-user-button") + .addEventListener("click", () => { + this.removeUserCreatedMessages(); + this.api.createUser( + this.newUsernameInputEl.value, + this.newPasswordInputEl.value, + (data) => { + if (data.handle[0] === "has already been taken") { + this.handleTaken(); + } else { + this.userCreated(); + } + this.newUsernameInputEl.value = ""; + this.newPasswordInputEl.value = ""; + } + ); + }); + + this.loginUsernameEl = document.querySelector("#login-username"); + this.loginPasswordEl = document.querySelector("#login-password"); + + // login button listener + document.querySelector("#login-button").addEventListener("click", () => { + this.api.loginUser( + this.loginUsernameEl.value, + this.loginPasswordEl.value, + (data) => { + this.model.setUserId(data.user_id); + this.model.setSessionKey(data.session_key); + this.loginUsernameEl.value = ""; + this.loginPasswordEl.value = ""; + } + ); + }); + + // logout button listener + document.querySelector("#logout-button").addEventListener("click", () => { + this.model.resetSessionKey(); + this.model.resetUserId(); }); } - createPeepBody(peep) { - const peepBodyEl = document.createElement("p"); - peepBodyEl.className = "peep-body"; - peepBodyEl.textContent = peep.body; - return peepBodyEl; + removeUserCreatedMessages() { + if (document.querySelector("#handle-taken-message") !== null) + document.querySelector("#handle-taken-message").remove(); + if (document.querySelector("#new-user-created-message") !== null) + document.querySelector("#new-user-created-message").remove(); } - createPeepUserHandle(peep) { - const peepUserHandleEl = document.createElement("p"); - peepUserHandleEl.className = "peep-user-handle"; - peepUserHandleEl.textContent = peep.user.handle; - return peepUserHandleEl; + handleTaken() { + const handleTakenMessageEl = document.createElement("p"); + handleTakenMessageEl.id = "handle-taken-message"; + handleTakenMessageEl.textContent = "This handle has been taken"; + this.createUserContainerEl.append(handleTakenMessageEl); } - createPeepDatetimeCreated(peep) { - const peepDatetimeCreatedEl = document.createElement("p"); - peepDatetimeCreatedEl.className = "peep-datetime-created"; - const peepCreatedString = new Date(peep.created_at); - const peepTimeString = peepCreatedString.toTimeString().substring(0, 5); - const peepDateString = peepCreatedString.toDateString(); - peepDatetimeCreatedEl.textContent = `${peepTimeString} ${peepDateString}`; - return peepDatetimeCreatedEl; + userCreated() { + const newUserCreatedMessageEl = document.createElement("p"); + newUserCreatedMessageEl.textContent = "You have created a new account!"; + newUserCreatedMessageEl.id = "new-user-created-message"; + this.createUserContainerEl.append(newUserCreatedMessageEl); } - createPeepLikesCount(peep) { - const peepLikesCountEl = document.createElement("p"); - peepLikesCountEl.className = "peep-datetime-created"; - const likesCount = peep.likes.length; - peepLikesCountEl.textContent = `${likesCount} ${ - likesCount === 1 ? "like" : "likes" - }`; - return peepLikesCountEl; + displayPeeps() { + this.model + .loadPeeps() + .forEach((peep) => this.displaySinglePeep.display(peep)); } displayPeepsFromApi() { diff --git a/chitterView.test.js b/chitterView.test.js index f99f10cbe..2d81911cf 100644 --- a/chitterView.test.js +++ b/chitterView.test.js @@ -3,21 +3,48 @@ */ const fs = require("fs"); -const ChitterPeepsModel = require("./chitterPeepsModel"); +const { default: JSDOMEnvironment } = require("jest-environment-jsdom"); +const ChitterModel = require("./chitterModel"); const ChitterView = require("./chitterView"); -let view, model, mockApi, testPeepsArray; +const DisplaySinglePeep = require("./displaySinglePeep"); +const testPostPeepResponse = require("./testPostPeepResponse"); +let view, + model, + mockApi, + displaySinglePeep, + testPeepsArray, + testUserSessionResponse, + fakeCallbackResponse; describe("ChitterView", () => { beforeEach(() => { document.body.innerHTML = fs.readFileSync("./index.html"); - model = new ChitterPeepsModel(); + model = new ChitterModel(); testPeepsArray = require("./testPeepsArray"); - mockApi = { fetchPeeps: () => testPeepsArray }; - view = new ChitterView(model, mockApi); + testUserSessionResponse = require("./testUserSessionResponse"); + fakeCallbackResponse = jest.fn(); + mockApi = { + fetchPeeps: jest.fn((callback) => callback(fakeCallbackResponse())), + createUser: jest.fn((username, password, callback) => + callback(fakeCallbackResponse()) + ), + loginUser: jest.fn((username, password, callback) => + callback(fakeCallbackResponse()) + ), + postPeep: jest.fn((sessionKey, userId, peepBody, callback) => + callback(fakeCallbackResponse()) + ), + }; + + displaySinglePeep = new DisplaySinglePeep(); + view = new ChitterView(model, mockApi, displaySinglePeep); }); + it("displays all Peeps", () => { + fakeCallbackResponse.mockReturnValueOnce(testPeepsArray); view.displayPeepsFromApi(); - const peepDivEls = document.querySelector("div.peep"); + expect(mockApi.fetchPeeps).toHaveBeenCalled(); + const peepDivEls = document.querySelectorAll("div.peep"); expect(peepDivEls.length).toBe(2); expect(peepDivEls[0].querySelector(".peep-body").textContent).toBe( "First peep" @@ -39,9 +66,108 @@ describe("ChitterView", () => { ); expect( peepDivEls[1].querySelector(".peep-datetime-created").textContent - ).toBe("13:04 Sun Aug 07 2022'"); + ).toBe("13:04 Sun Aug 07 2022"); expect(peepDivEls[1].querySelector(".peep-likes-count").textContent).toBe( "0 likes" ); }); + + it("creates a new user", () => { + fakeCallbackResponse.mockReturnValueOnce(testUserSessionResponse); + document.querySelector("input#create-username").value = "username"; + document.querySelector("input#create-password").value = "password"; + document.querySelector("#create-user-button").click(); + expect(mockApi.createUser).toHaveBeenCalled(); + expect(mockApi.createUser.mock.calls[0][0]).toBe("username"); + expect(mockApi.createUser.mock.calls[0][1]).toBe("password"); + expect( + document.querySelector("#new-user-created-message").textContent + ).toBe("You have created a new account!"); + expect(document.querySelector("input#create-username").value).toBe(""); + expect(document.querySelector("input#create-password").value).toBe(""); + }); + + it("displays a new user error", () => { + fakeCallbackResponse.mockReturnValueOnce({ + handle: ["has already been taken"], + }); + document.querySelector("input#create-username").value = "username"; + document.querySelector("input#create-password").value = "password"; + document.querySelector("#create-user-button").click(); + expect(mockApi.createUser).toHaveBeenCalled(); + expect(mockApi.createUser.mock.calls[0][0]).toBe("username"); + expect(mockApi.createUser.mock.calls[0][1]).toBe("password"); + expect(document.querySelector("#handle-taken-message").textContent).toBe( + "This handle has been taken" + ); + expect(document.querySelector("input#create-username").value).toBe(""); + expect(document.querySelector("input#create-password").value).toBe(""); + }); + + it("logs in a user", () => { + fakeCallbackResponse.mockReturnValueOnce({ + user_id: 1, + session_key: "a_valid_session_key", + }); + document.querySelector("input#login-username").value = "username"; + document.querySelector("input#login-password").value = "password"; + document.querySelector("#login-button").click(); + expect(mockApi.loginUser).toHaveBeenCalled(); + expect(mockApi.loginUser.mock.calls[0][0]).toBe("username"); + expect(mockApi.loginUser.mock.calls[0][1]).toBe("password"); + expect(document.querySelector("input#login-username").value).toBe(""); + expect(document.querySelector("input#login-password").value).toBe(""); + expect(model.loadUserId()).toBe(1); + expect(model.loadSessionKey()).toBe("a_valid_session_key"); + }); + + it("logs a user out", () => { + fakeCallbackResponse.mockReturnValueOnce({ + user_id: 1, + session_key: "a_valid_session_key", + }); + document.querySelector("input#login-username").value = "username"; + document.querySelector("input#login-password").value = "password"; + document.querySelector("#login-button").click(); + document.querySelector("#logout-button").click(); + expect(model.loadUserId()).toBe(null); + expect(model.loadSessionKey()).toBe(null); + }); + + it("posts a Peep", () => { + fakeCallbackResponse.mockReturnValueOnce({ + user_id: 1, + session_key: "a_valid_session_key", + }); + document.querySelector("input#login-username").value = "kay"; + document.querySelector("input#login-password").value = "mypassword"; + document.querySelector("#login-button").click(); + + fakeCallbackResponse.mockReturnValueOnce(testPostPeepResponse); + document.querySelector("#post-peep-body").value = "this is a new peep"; + document.querySelector("#post-peep-button").click(); + document.querySelector("#post-peep-body").value = ""; + expect(mockApi.postPeep).toHaveBeenCalled(); + expect(mockApi.postPeep.mock.calls[0][0]).toBe("a_valid_session_key"); + expect(mockApi.postPeep.mock.calls[0][1]).toBe(1); + expect(mockApi.postPeep.mock.calls[0][2]).toBe("this is a new peep"); + document.querySelector("#post-peep-success-message").textContent = + "Peep posted successfully!"; + + testPeepsArray.unshift(testPostPeepResponse); + fakeCallbackResponse.mockReturnValueOnce(testPeepsArray); + const newPeepEl = document.querySelector("div.peep"); + expect(newPeepEl.querySelector(".peep-body").textContent).toBe( + "this is a new peep" + ); + expect(newPeepEl.querySelector(".peep-user-handle")).textContent.toBe( + "kay" + ); + expect(newPeepEl.querySelector(".peep-datetime-created").textContent).toBe( + "14:21 Sat Jun 23 2018" + ); + expect(newPeepEl.querySelector(".peep-likes-count").textContent).toBe( + "0 likes" + ); + }); }); diff --git a/displaySinglePeep.js b/displaySinglePeep.js new file mode 100644 index 000000000..62cf21a4e --- /dev/null +++ b/displaySinglePeep.js @@ -0,0 +1,48 @@ +class DisplaySinglePeep { + display(peep) { + const peepsContainerEl = document.querySelector("#display-peeps-container"); + const peepDivEl = document.createElement("div"); + peepDivEl.className = "peep"; + peepDivEl.append(this.createPeepBody(peep)); + peepDivEl.append(this.createPeepUserHandle(peep)); + peepDivEl.append(this.createPeepDatetimeCreated(peep)); + peepDivEl.append(this.createPeepLikesCount(peep)); + peepsContainerEl.append(peepDivEl); + } + + createPeepBody(peep) { + const peepBodyEl = document.createElement("p"); + peepBodyEl.className = "peep-body"; + peepBodyEl.textContent = peep.body; + return peepBodyEl; + } + + createPeepUserHandle(peep) { + const peepUserHandleEl = document.createElement("p"); + peepUserHandleEl.className = "peep-user-handle"; + peepUserHandleEl.textContent = peep.user.handle; + return peepUserHandleEl; + } + + createPeepDatetimeCreated(peep) { + const peepDatetimeCreatedEl = document.createElement("p"); + peepDatetimeCreatedEl.className = "peep-datetime-created"; + const peepCreatedString = new Date(peep.created_at); + const peepTimeString = peepCreatedString.toTimeString().substring(0, 5); + const peepDateString = peepCreatedString.toDateString(); + peepDatetimeCreatedEl.textContent = `${peepTimeString} ${peepDateString}`; + return peepDatetimeCreatedEl; + } + + createPeepLikesCount(peep) { + const peepLikesCountEl = document.createElement("p"); + peepLikesCountEl.className = "peep-likes-count"; + const likesCount = peep.likes.length; + peepLikesCountEl.textContent = `${likesCount} ${ + likesCount === 1 ? "like" : "likes" + }`; + return peepLikesCountEl; + } +} + +module.exports = DisplaySinglePeep; diff --git a/index.html b/index.html index 7903184be..9e7e78af9 100644 --- a/index.html +++ b/index.html @@ -8,5 +8,30 @@ -
+ +
+
+ +
+ Create user: + Username: + Password: + +
+ +
+ Log in: + Username: + Password: + +
+
+ +
+
+ What would you like to say? + +
+
+ diff --git a/index.js b/index.js index 19aff1175..9a3f8ce80 100644 --- a/index.js +++ b/index.js @@ -1,6 +1,11 @@ const ChitterApi = require("./chitterApi"); -const ChitterPeepsModel = require("./chitterPeepsModel"); +const ChitterPeepsModel = require("./chitterModel"); const ChitterView = require("./chitterView"); +const DisplaySinglePeep = require("./displaySinglePeep"); -view = new ChitterView(new ChitterPeepsModel(), new ChitterApi()); +view = new ChitterView( + new ChitterPeepsModel(), + new ChitterApi(), + new DisplaySinglePeep() +); view.displayPeepsFromApi(); diff --git a/testPostPeepResponse.js b/testPostPeepResponse.js new file mode 100644 index 000000000..23ecfae8b --- /dev/null +++ b/testPostPeepResponse.js @@ -0,0 +1,20 @@ +const testPostPeepResponse = { + id: 3, + body: "my first peep :)", + created_at: "2018-06-23T13:21:23.317Z", + updated_at: "2018-06-23T13:21:23.317Z", + user: { + id: 1, + handle: "kay", + }, + likes: [ + { + user: { + id: 1, + handle: "kay", + }, + }, + ], +}; + +module.exports = testPostPeepResponse; diff --git a/testUserSessionResponse.js b/testUserSessionResponse.js new file mode 100644 index 000000000..92d966617 --- /dev/null +++ b/testUserSessionResponse.js @@ -0,0 +1,6 @@ +const testUserSessionResponse = { + id: 1, + handle: "kay", +}; + +module.exports = testUserSessionResponse; From 87e87451e7886257d37af71a4424539595b46bb6 Mon Sep 17 00:00:00 2001 From: Zac Date: Mon, 5 Sep 2022 01:41:46 +0100 Subject: [PATCH 4/5] passed delete peep test on chitterview.js --- chitterApi.js | 7 +++ chitterApi.test.js | 28 ++++++++++-- chitterView.js | 64 +++++++++++++++++++++++++--- chitterView.test.js | 89 ++++++++++++++++++++++++++++----------- displaySinglePeep.js | 13 +++++- testDeletePeepResponse.js | 8 ++++ testPostPeepResponse.js | 9 +--- 7 files changed, 175 insertions(+), 43 deletions(-) create mode 100644 testDeletePeepResponse.js diff --git a/chitterApi.js b/chitterApi.js index a42ec4a7d..a76145572 100644 --- a/chitterApi.js +++ b/chitterApi.js @@ -50,6 +50,13 @@ class ChitterApi { .then((response) => response.json()) .then(callback); } + + deletePeep(peepId, sessionKey, callback) { + fetch(`https://chitter-backend-api-v2.herokuapp.com/peeps/${peepId}`, { + method: "DELETE", + headers: { Authorization: `Token token=${sessionKey}` }, + }).then(callback); + } } module.exports = ChitterApi; diff --git a/chitterApi.test.js b/chitterApi.test.js index 5e0f7f02b..4209d6f1e 100644 --- a/chitterApi.test.js +++ b/chitterApi.test.js @@ -2,7 +2,11 @@ const ChitterApi = require("./chitterApi"); require("jest-fetch-mock").enableMocks(); -let api, testPeepsArray, testUserSessionResponse, testPostPeepResponse; +let api, + testPeepsArray, + testUserSessionResponse, + testPostPeepResponse, + testDeletePeepResponse; describe("ChitterApi class", () => { beforeEach(() => { @@ -10,6 +14,7 @@ describe("ChitterApi class", () => { testPeepsArray = require("./testPeepsArray"); testUserSessionResponse = require("./testUserSessionResponse"); testPostPeepResponse = require("./testPostPeepResponse"); + testDeletePeepResponse = require("./testDeletePeepResponse"); fetch.resetMocks(); }); @@ -27,7 +32,7 @@ describe("ChitterApi class", () => { }); it("creates a new user", () => { - expect.assertions(2); + expect.assertions(3); fetch.mockResponseOnce(JSON.stringify(testUserSessionResponse)); api.createUser("kay", "mypassword", (data) => { @@ -35,11 +40,12 @@ describe("ChitterApi class", () => { expect(fetch.mock.calls[0][0]).toEqual( "https://chitter-backend-api-v2.herokuapp.com/users" ); + expect(fetch.mock.calls[0][1].method).toBe("POST"); }); }); it("logs in a user", () => { - expect.assertions(3); + expect.assertions(4); fetch.mockResponseOnce( JSON.stringify({ user_id: 1, @@ -52,17 +58,31 @@ describe("ChitterApi class", () => { expect(fetch.mock.calls[0][0]).toEqual( "https://chitter-backend-api-v2.herokuapp.com/sessions" ); + expect(fetch.mock.calls[0][1].method).toBe("POST"); }); }); it("posts a Peep", () => { - expect.assertions(2); + expect.assertions(3); fetch.mockResponseOnce(JSON.stringify(testPostPeepResponse)); api.postPeep("sessionKey", 1, "my first peep :)", (data) => { expect(data).toEqual(testPostPeepResponse); expect(fetch.mock.calls[0][0]).toEqual( "https://chitter-backend-api-v2.herokuapp.com/peeps" ); + expect(fetch.mock.calls[0][1].method).toBe("POST"); + }); + }); + + it("deletes a Peep", () => { + expect.assertions(3); + fetch.mockResponseOnce({}, { status: 204 }); + api.deletePeep(1494, "session key", (response) => { + expect(fetch.mock.calls[0][0]).toEqual( + "https://chitter-backend-api-v2.herokuapp.com/peeps/1494" + ); + expect(fetch.mock.calls[0][1].method).toBe("DELETE"); + expect(response.status).toBe(204); }); }); }); diff --git a/chitterView.js b/chitterView.js index 229bcfcda..48b5ff119 100644 --- a/chitterView.js +++ b/chitterView.js @@ -6,6 +6,7 @@ class ChitterView { this.createUserContainerEl = document.querySelector( "div#create-user-container" ); + this.newUsernameInputEl = document.querySelector("input#create-username"); this.newPasswordInputEl = document.querySelector("input#create-password"); @@ -18,11 +19,7 @@ class ChitterView { this.newUsernameInputEl.value, this.newPasswordInputEl.value, (data) => { - if (data.handle[0] === "has already been taken") { - this.handleTaken(); - } else { - this.userCreated(); - } + this.handleTakenLogic(data); this.newUsernameInputEl.value = ""; this.newPasswordInputEl.value = ""; } @@ -42,6 +39,7 @@ class ChitterView { this.model.setSessionKey(data.session_key); this.loginUsernameEl.value = ""; this.loginPasswordEl.value = ""; + setTimeout(this.displayPeepsFromApi(), 500); } ); }); @@ -50,7 +48,45 @@ class ChitterView { document.querySelector("#logout-button").addEventListener("click", () => { this.model.resetSessionKey(); this.model.resetUserId(); + setTimeout(this.displayPeepsFromApi(), 500); }); + + this.postPeepBodyEl = document.querySelector("#post-peep-body"); + + // post peep button listener + document + .querySelector("#post-peep-button") + .addEventListener("click", () => { + this.api.postPeep( + this.model.loadSessionKey(), + this.model.loadUserId(), + this.postPeepBodyEl.value, + () => { + this.createPeepSuccessMesage(); + this.postPeepBodyEl.value = ""; + setTimeout(this.displayPeepsFromApi(), 500); + } + ); + }); + } + + addDeleteButtonListeners() { + document.querySelectorAll(".delete-peep-button").forEach((element) => { + const peepId = element.id.split("-")[3]; + element.addEventListener("click", () => { + this.api.deletePeep(peepId, this.model.loadSessionKey(), (data) => { + setTimeout(this.displayPeepsFromApi(), 500); + }); + }); + }); + } + createPeepSuccessMesage() { + const postPeepSuccessMessageEl = document.createElement("p"); + postPeepSuccessMessageEl.id = "post-peep-success-message"; + postPeepSuccessMessageEl.textContent = "Peep posted successfully!"; + document + .querySelector("div#post-peep-container") + .append(postPeepSuccessMessageEl); } removeUserCreatedMessages() { @@ -60,6 +96,14 @@ class ChitterView { document.querySelector("#new-user-created-message").remove(); } + handleTakenLogic(data) { + if (data.handle[0] === "has already been taken") { + this.handleTaken(); + } else { + this.userCreated(); + } + } + handleTaken() { const handleTakenMessageEl = document.createElement("p"); handleTakenMessageEl.id = "handle-taken-message"; @@ -75,9 +119,17 @@ class ChitterView { } displayPeeps() { + if (document.querySelectorAll("div.peep").length > 0) { + document + .querySelectorAll("div.peep") + .forEach((element) => element.remove()); + } this.model .loadPeeps() - .forEach((peep) => this.displaySinglePeep.display(peep)); + .forEach((peep) => + this.displaySinglePeep.display(peep, this.model.loadUserId()) + ); + this.addDeleteButtonListeners(); } displayPeepsFromApi() { diff --git a/chitterView.test.js b/chitterView.test.js index 2d81911cf..133ab127d 100644 --- a/chitterView.test.js +++ b/chitterView.test.js @@ -34,6 +34,9 @@ describe("ChitterView", () => { postPeep: jest.fn((sessionKey, userId, peepBody, callback) => callback(fakeCallbackResponse()) ), + deletePeep: jest.fn((peepId, sessionKey, callback) => + callback(fakeCallbackResponse()) + ), }; displaySinglePeep = new DisplaySinglePeep(); @@ -72,6 +75,15 @@ describe("ChitterView", () => { ); }); + it("doesn't add new peeps with each successive call of displayPeeps", () => { + fakeCallbackResponse.mockReturnValue(testPeepsArray); + view.displayPeepsFromApi(); + expect(mockApi.fetchPeeps).toHaveBeenCalled(); + view.displayPeepsFromApi(); + const peepDivEls = document.querySelectorAll("div.peep"); + expect(peepDivEls.length).toBe(2); + }); + it("creates a new user", () => { fakeCallbackResponse.mockReturnValueOnce(testUserSessionResponse); document.querySelector("input#create-username").value = "username"; @@ -105,10 +117,12 @@ describe("ChitterView", () => { }); it("logs in a user", () => { - fakeCallbackResponse.mockReturnValueOnce({ - user_id: 1, - session_key: "a_valid_session_key", - }); + fakeCallbackResponse + .mockReturnValueOnce({ + user_id: 1, + session_key: "a_valid_session_key", + }) + .mockReturnValueOnce(testPeepsArray); document.querySelector("input#login-username").value = "username"; document.querySelector("input#login-password").value = "password"; document.querySelector("#login-button").click(); @@ -119,55 +133,82 @@ describe("ChitterView", () => { expect(document.querySelector("input#login-password").value).toBe(""); expect(model.loadUserId()).toBe(1); expect(model.loadSessionKey()).toBe("a_valid_session_key"); + expect(mockApi.fetchPeeps).toHaveBeenCalled(); }); it("logs a user out", () => { - fakeCallbackResponse.mockReturnValueOnce({ + fakeCallbackResponse.mockReturnValue(testPeepsArray).mockReturnValueOnce({ user_id: 1, session_key: "a_valid_session_key", }); document.querySelector("input#login-username").value = "username"; document.querySelector("input#login-password").value = "password"; document.querySelector("#login-button").click(); + expect(mockApi.fetchPeeps).toHaveBeenCalled(); document.querySelector("#logout-button").click(); expect(model.loadUserId()).toBe(null); expect(model.loadSessionKey()).toBe(null); + expect(mockApi.fetchPeeps).toHaveBeenCalled(); }); it("posts a Peep", () => { - fakeCallbackResponse.mockReturnValueOnce({ - user_id: 1, - session_key: "a_valid_session_key", - }); + fakeCallbackResponse + .mockReturnValueOnce({ + user_id: 1, + session_key: "a_valid_session_key", + }) + .mockReturnValueOnce(testPeepsArray); document.querySelector("input#login-username").value = "kay"; document.querySelector("input#login-password").value = "mypassword"; document.querySelector("#login-button").click(); - - fakeCallbackResponse.mockReturnValueOnce(testPostPeepResponse); - document.querySelector("#post-peep-body").value = "this is a new peep"; + fakeCallbackResponse + .mockReturnValueOnce(testPostPeepResponse) + .mockReturnValueOnce([testPostPeepResponse].concat(testPeepsArray)); + document.querySelector("#post-peep-body").value = "my first peep :)"; document.querySelector("#post-peep-button").click(); - document.querySelector("#post-peep-body").value = ""; expect(mockApi.postPeep).toHaveBeenCalled(); expect(mockApi.postPeep.mock.calls[0][0]).toBe("a_valid_session_key"); expect(mockApi.postPeep.mock.calls[0][1]).toBe(1); - expect(mockApi.postPeep.mock.calls[0][2]).toBe("this is a new peep"); + expect(mockApi.postPeep.mock.calls[0][2]).toBe("my first peep :)"); + expect(document.querySelector("#post-peep-body").value).toBe(""); document.querySelector("#post-peep-success-message").textContent = "Peep posted successfully!"; - testPeepsArray.unshift(testPostPeepResponse); - fakeCallbackResponse.mockReturnValueOnce(testPeepsArray); - const newPeepEl = document.querySelector("div.peep"); - expect(newPeepEl.querySelector(".peep-body").textContent).toBe( - "this is a new peep" + expect(mockApi.fetchPeeps).toHaveBeenCalled(); + const newPeepEls = document.querySelectorAll("div.peep"); + expect(newPeepEls.length).toBe(3); + expect(newPeepEls[0].querySelector(".peep-body").textContent).toBe( + "my first peep :)" ); - expect(newPeepEl.querySelector(".peep-user-handle")).textContent.toBe( + expect(newPeepEls[0].querySelector(".peep-user-handle").textContent).toBe( "kay" ); - expect(newPeepEl.querySelector(".peep-datetime-created").textContent).toBe( - "14:21 Sat Jun 23 2018" - ); - expect(newPeepEl.querySelector(".peep-likes-count").textContent).toBe( + expect( + newPeepEls[0].querySelector(".peep-datetime-created").textContent + ).toBe("14:21 Sat Jun 23 2018"); + expect(newPeepEls[0].querySelector(".peep-likes-count").textContent).toBe( "0 likes" ); }); + + it("deletes a peep", () => { + fakeCallbackResponse + .mockReturnValueOnce({ + user_id: 1124, + session_key: "a_valid_session_key", + }) + .mockReturnValue(testPeepsArray); + document.querySelector("input#login-username").value = "jony144"; + document.querySelector("input#login-password").value = "mypassword"; + document.querySelector("#login-button").click(); + expect(document.querySelectorAll(".delete-peep-button").length).toBe(1); + document + .querySelectorAll("div.peep")[0] + .querySelector(".delete-peep-button") + .click(); + expect(mockApi.deletePeep).toHaveBeenCalled(); + expect(mockApi.deletePeep.mock.calls[0][0]).toBe("1494"); + expect(mockApi.deletePeep.mock.calls[0][1]).toBe("a_valid_session_key"); + expect(mockApi.fetchPeeps).toHaveBeenCalledTimes(2); + }); }); diff --git a/displaySinglePeep.js b/displaySinglePeep.js index 62cf21a4e..3bde2e2c5 100644 --- a/displaySinglePeep.js +++ b/displaySinglePeep.js @@ -1,5 +1,5 @@ class DisplaySinglePeep { - display(peep) { + display(peep, userId) { const peepsContainerEl = document.querySelector("#display-peeps-container"); const peepDivEl = document.createElement("div"); peepDivEl.className = "peep"; @@ -7,6 +7,9 @@ class DisplaySinglePeep { peepDivEl.append(this.createPeepUserHandle(peep)); peepDivEl.append(this.createPeepDatetimeCreated(peep)); peepDivEl.append(this.createPeepLikesCount(peep)); + if (userId === peep.user.id) { + peepDivEl.append(this.createDeletePeepButton(peep)); + } peepsContainerEl.append(peepDivEl); } @@ -43,6 +46,14 @@ class DisplaySinglePeep { }`; return peepLikesCountEl; } + + createDeletePeepButton(peep) { + const deletePeepButtonEl = document.createElement("button"); + deletePeepButtonEl.className = "delete-peep-button"; + deletePeepButtonEl.id = `delete-button-id-${peep.id}`; + deletePeepButtonEl.textContent = "delete peep"; + return deletePeepButtonEl; + } } module.exports = DisplaySinglePeep; diff --git a/testDeletePeepResponse.js b/testDeletePeepResponse.js new file mode 100644 index 000000000..370e57341 --- /dev/null +++ b/testDeletePeepResponse.js @@ -0,0 +1,8 @@ +const testDeletePeepResponse = { + user: { + id: 1120, + handle: "jony144", + }, +}; + +module.exports = testDeletePeepResponse; diff --git a/testPostPeepResponse.js b/testPostPeepResponse.js index 23ecfae8b..4677adb6b 100644 --- a/testPostPeepResponse.js +++ b/testPostPeepResponse.js @@ -7,14 +7,7 @@ const testPostPeepResponse = { id: 1, handle: "kay", }, - likes: [ - { - user: { - id: 1, - handle: "kay", - }, - }, - ], + likes: [], }; module.exports = testPostPeepResponse; From acd54cde8aefe551e20e5340818b64952b46f968 Mon Sep 17 00:00:00 2001 From: Zac Date: Mon, 5 Sep 2022 09:39:57 +0100 Subject: [PATCH 5/5] ready for pull request --- chitterApi.js | 6 ------ chitterView.js | 4 +--- 2 files changed, 1 insertion(+), 9 deletions(-) diff --git a/chitterApi.js b/chitterApi.js index a76145572..ec929f8b7 100644 --- a/chitterApi.js +++ b/chitterApi.js @@ -60,9 +60,3 @@ class ChitterApi { } module.exports = ChitterApi; - -// curl "https://chitter-backend-api-v2.herokuapp.com/peeps" \ -// -X POST \ -// -H "Authorization: Token token=a_valid_session_key" \ -// -H "Content-Type: application/json" \ -// -d '{"peep": {"user_id":1, "body":"my first peep :)"}}' diff --git a/chitterView.js b/chitterView.js index 48b5ff119..230c84001 100644 --- a/chitterView.js +++ b/chitterView.js @@ -3,9 +3,6 @@ class ChitterView { this.model = model; this.api = api; this.displaySinglePeep = displaySinglePeep; - this.createUserContainerEl = document.querySelector( - "div#create-user-container" - ); this.newUsernameInputEl = document.querySelector("input#create-username"); this.newPasswordInputEl = document.querySelector("input#create-password"); @@ -80,6 +77,7 @@ class ChitterView { }); }); } + createPeepSuccessMesage() { const postPeepSuccessMessageEl = document.createElement("p"); postPeepSuccessMessageEl.id = "post-peep-success-message";