Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
15 changes: 15 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
node_modules
*.log

# Output directory
dist

# @vitjs/vit temporary directory
.vit

# Generated by rollup-plugin-visualizer
stats.html

.eslintcache

.DS_Store
55 changes: 22 additions & 33 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,37 +1,26 @@
# 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.
This is a prototype of the Chitter Frontend.

The features include:
* 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.
* Viewing all Peeps

Techology:
- React.js
- Tailwind
- Cypress
- Axios

## Get started

To start the development server:
```
npm install
npm run dev
```

To run the tests (E2E & Component test):
```
npx cypress open
```
17 changes: 17 additions & 0 deletions cypress.config.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
import { defineConfig } from "cypress";

export default defineConfig({
e2e: {
baseUrl: "http://localhost:5173",
setupNodeEvents(on, config) {
// implement node event listeners here
},
},

component: {
devServer: {
framework: "react",
bundler: "vite",
},
},
});
31 changes: 31 additions & 0 deletions cypress/component/CreateUser.cy.jsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
import CreateUser from "../../src/components/CreateUser";

describe("CreateUser component", () => {
it("should render the component correctly", () => {
cy.mount(<CreateUser />);
cy.get("#username").should("exist");
cy.get("#password").should("exist");
cy.get("[data-test='submit-btn']").should("exist");
});

it("should update the username and password values when typed in", () => {
cy.mount(<CreateUser />);
cy.get("#username").type("testuser");
cy.get("#password").type("testpassword");
cy.get("#username").should("have.value", "testuser");
cy.get("#password").should("have.value", "testpassword");
});

it("should not create a new user when the form is submitted", () => {
cy.intercept("POST", "https://chitter-backend-api-v2.herokuapp.com/users", {
statusCode: 201,
body: {},
}).as("createUser");
cy.mount(<CreateUser onChangePage={() => {}} />);
cy.get("#username").type("testuser");
cy.get("#password").type("testpassword");
cy.get("[data-test='submit-btn']").click();
cy.wait("@createUser");
cy.get("#create-user-btn").should("not.exist");
});
});
49 changes: 49 additions & 0 deletions cypress/component/Peeps.cy.jsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,49 @@
// import { useQuery } from "react-query";
import Peeps from "../../src/components/Peeps";
import { useQuery, QueryClient, QueryClientProvider } from "react-query";

describe("Peeps component", () => {
beforeEach(() => {
const mockData = [
{
id: 1,
user: { handle: "user1" },
body: "Hello world!",
created_at: "2023-03-01T12:00:00.000Z",
},
{
id: 2,
user: { handle: "user2" },
body: "How are you?",
created_at: "2023-03-01T13:00:00.000Z",
},
];
cy.intercept("https://chitter-backend-api-v2.herokuapp.com/peeps", {
body: mockData,
}).as("repoData");
});

it("displays a list of peeps", () => {
const queryClient = new QueryClient();
cy.mount(
<QueryClientProvider client={queryClient}>
<Peeps />
</QueryClientProvider>
);
cy.wait("@repoData");
cy.get(".peeps-container")
.children()
.should("have.length", 2)
.each(($peep, index) => {
cy.wrap($peep)
.find("p")
.should(($p) => {
const text = $p.text();
expect(text).to.match(/^User: user\d/);
expect(text).to.include("Body:");
expect(text).to.include("Created at:");
expect(text).to.include(`User: user${index + 1}`);
});
});
});
});
33 changes: 33 additions & 0 deletions cypress/e2e/react-demo-spec.cy.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
describe("template spec", () => {
it("starts from count 0 to 1 after a click", () => {
cy.visit("/");
// const counterBtn = cy.get("div.card > button");
// counterBtn.should("have.text", "count is 0");
// counterBtn.click();
// counterBtn.should("have.text", "count is 1");
});

it("shows 50 peeps on the main page", () => {
cy.visit("http://localhost:5173");
cy.get("div.peep").should("have.length", 50);
});

it("shows the create user page", () => {
cy.intercept("POST", "https://chitter-backend-api-v2.herokuapp.com/users", {
statusCode: 201,
body: {},
}).as("createUser");
cy.visit("http://localhost:5173");
cy.get("button").contains("Create User").click();
cy.get("input#username").should("exist");
cy.get("input#password").should("exist");
cy.getByData("submit-btn").contains("Create User");
cy.get("input#username").type("testuser");
cy.get("input#password").type("testpassword");
cy.getByData("submit-btn").click();
cy.wait("@createUser").then(({ response }) => {
expect(response.statusCode).to.eq(201);
cy.get("#create-user-btn").should("not.exist");
});
});
});
5 changes: 5 additions & 0 deletions cypress/fixtures/example.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
{
"name": "Using fixtures to represent data",
"email": "hello@cypress.io",
"body": "Fixtures are a great way to mock data for responses to routes"
}
5 changes: 5 additions & 0 deletions cypress/support/commands.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
/// <reference types="cypress" />

Cypress.Commands.add("getByData", (selector) => {
return cy.get(`[data-test=${selector}]`);
});
12 changes: 12 additions & 0 deletions cypress/support/component-index.html
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width,initial-scale=1.0">
<title>Components App</title>
</head>
<body>
<div data-cy-root></div>
</body>
</html>
27 changes: 27 additions & 0 deletions cypress/support/component.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
// ***********************************************************
// This example support/component.js is processed and
// loaded automatically before your test files.
//
// This is a great place to put global configuration and
// behavior that modifies Cypress.
//
// You can change the location of this file or turn off
// automatically serving support files with the
// 'supportFile' configuration option.
//
// You can read more here:
// https://on.cypress.io/configuration
// ***********************************************************

// Import commands.js using ES2015 syntax:
import './commands'

// Alternatively you can use CommonJS syntax:
// require('./commands')

import { mount } from 'cypress/react18'

Cypress.Commands.add('mount', mount)

// Example use:
// cy.mount(<MyComponent />)
20 changes: 20 additions & 0 deletions cypress/support/e2e.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
// ***********************************************************
// This example support/e2e.js is processed and
// loaded automatically before your test files.
//
// This is a great place to put global configuration and
// behavior that modifies Cypress.
//
// You can change the location of this file or turn off
// automatically serving support files with the
// 'supportFile' configuration option.
//
// You can read more here:
// https://on.cypress.io/configuration
// ***********************************************************

// Import commands.js using ES2015 syntax:
import './commands'

// Alternatively you can use CommonJS syntax:
// require('./commands')
Binary file added cypress/videos/react-demo-spec.cy.js.mp4
Binary file not shown.
13 changes: 13 additions & 0 deletions index.html
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<link rel="icon" type="image/svg+xml" href="/vite.svg" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>Vite + React</title>
</head>
<body>
<div id="root"></div>
<script type="module" src="/src/main.jsx"></script>
</body>
</html>
Loading