├── .env
├── .gitignore
├── LICENSE
├── README.md
├── package-lock.json
├── package.json
├── public
├── favicon.ico
├── index.html
├── manifest.json
└── robots.txt
├── screenshot.png
├── src
├── components
│ ├── button
│ │ ├── Button.js
│ │ ├── Button.test.js
│ │ └── index.js
│ ├── globalStyle
│ │ ├── GlobalStyle.js
│ │ └── index.js
│ ├── index.js
│ └── quotes
│ │ ├── Quotes.js
│ │ ├── Quotes.test.js
│ │ └── index.js
├── images
│ ├── bg.jpeg
│ └── naruto.png
├── index.js
├── pages
│ ├── app
│ │ ├── App.js
│ │ ├── App.test.js
│ │ └── index.js
│ └── index.js
├── services
│ ├── index.js
│ └── quotesService
│ │ ├── index.js
│ │ ├── quotesService.js
│ │ └── quotesService.test.js
├── setupTests.js
└── sounds
│ └── jutso.mp3
└── yarn.lock
/.env:
--------------------------------------------------------------------------------
1 | REACT_APP_API=http://localhost:5000/
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | # See https://help.github.com/articles/ignoring-files/ for more about ignoring files.
2 |
3 | # dependencies
4 | /node_modules
5 | /.pnp
6 | .pnp.js
7 |
8 | # testing
9 | /coverage
10 |
11 | # production
12 | /build
13 |
14 | # misc
15 | .DS_Store
16 | .env.local
17 | .env.development.local
18 | .env.test.local
19 | .env.production.local
20 |
21 | npm-debug.log*
22 | yarn-debug.log*
23 | yarn-error.log*
24 |
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | MIT License
2 |
3 | Copyright (c) 2021 Celso Henrique
4 |
5 | Permission is hereby granted, free of charge, to any person obtaining a copy
6 | of this software and associated documentation files (the "Software"), to deal
7 | in the Software without restriction, including without limitation the rights
8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9 | copies of the Software, and to permit persons to whom the Software is
10 | furnished to do so, subject to the following conditions:
11 |
12 | The above copyright notice and this permission notice shall be included in all
13 | copies or substantial portions of the Software.
14 |
15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21 | SOFTWARE.
22 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # Naruto Quotes Client
2 | Naruto quotes generator client for DIO lesson.
3 |
4 | 
5 |
6 | # Install
7 | Clone this repository and install it dependencies with this command:
8 | ```sh
9 | $ npm install
10 | ```
11 |
12 | # Running
13 | Run the application with `npm start` command, it will start the app on [localhost:3000](http://localhost:3000):
14 | ```sh
15 | $ npm start
16 | ```
17 |
18 | # License
19 | This project is licensed under the MIT License - see the [LICENSE.md](LICENSE.md) file for details
20 |
21 |
--------------------------------------------------------------------------------
/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "naruto-quotes",
3 | "version": "0.1.0",
4 | "private": true,
5 | "dependencies": {
6 | "@testing-library/jest-dom": "^5.11.4",
7 | "@testing-library/user-event": "^12.1.10",
8 | "msw": "^0.28.0",
9 | "react": "^17.0.2",
10 | "react-dom": "^17.0.2",
11 | "react-scripts": "4.0.3",
12 | "styled-components": "^5.2.1",
13 | "web-vitals": "^1.0.1"
14 | },
15 | "scripts": {
16 | "start": "react-scripts start",
17 | "build": "react-scripts build",
18 | "test": "react-scripts test",
19 | "eject": "react-scripts eject"
20 | },
21 | "eslintConfig": {
22 | "extends": [
23 | "react-app",
24 | "react-app/jest"
25 | ]
26 | },
27 | "browserslist": {
28 | "production": [
29 | ">0.2%",
30 | "not dead",
31 | "not op_mini all"
32 | ],
33 | "development": [
34 | "last 1 chrome version",
35 | "last 1 firefox version",
36 | "last 1 safari version"
37 | ]
38 | },
39 | "devDependencies": {
40 | "@testing-library/react": "^11.2.5"
41 | }
42 | }
43 |
--------------------------------------------------------------------------------
/public/favicon.ico:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/celso-henrique/naruto-quotes-client/e4765ec50ca401bb1596fa09e9306490c5448739/public/favicon.ico
--------------------------------------------------------------------------------
/public/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
12 |
16 |
17 |
26 |
27 |
28 | DIO Naruto Quotes
29 |
30 |
31 |
32 |
33 |
43 |
44 |
45 |
--------------------------------------------------------------------------------
/public/manifest.json:
--------------------------------------------------------------------------------
1 | {
2 | "short_name": "React App",
3 | "name": "Create React App Sample",
4 | "icons": [
5 | {
6 | "src": "favicon.ico",
7 | "sizes": "64x64 32x32 24x24 16x16",
8 | "type": "image/x-icon"
9 | }
10 | ],
11 | "start_url": ".",
12 | "display": "standalone",
13 | "theme_color": "#000000",
14 | "background_color": "#ffffff"
15 | }
16 |
--------------------------------------------------------------------------------
/public/robots.txt:
--------------------------------------------------------------------------------
1 | # https://www.robotstxt.org/robotstxt.html
2 | User-agent: *
3 | Disallow:
4 |
--------------------------------------------------------------------------------
/screenshot.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/celso-henrique/naruto-quotes-client/e4765ec50ca401bb1596fa09e9306490c5448739/screenshot.png
--------------------------------------------------------------------------------
/src/components/button/Button.js:
--------------------------------------------------------------------------------
1 | import styled from 'styled-components';
2 |
3 | export const Button = styled.button`
4 | background: #f27137;
5 | color: #fff;
6 | border: none;
7 | border-radius: 0;
8 | font-size: 1.5em;
9 | padding: 10px 20px;
10 | font-family: 'New Tegomin', serif;
11 | cursor: pointer;
12 | box-shadow: #332c36 3px 3px;
13 |
14 | &:hover {
15 | background-color: #a40000;
16 | }
17 |
18 | &:focus {
19 | outline: none;
20 | }
21 | `;
22 |
--------------------------------------------------------------------------------
/src/components/button/Button.test.js:
--------------------------------------------------------------------------------
1 | import { render, screen } from '@testing-library/react';
2 | import { Button } from './Button';
3 |
4 | test('renders button with text', () => {
5 | render();
6 |
7 | const buttonEl = screen.getByText(/Test/i);
8 |
9 | expect(buttonEl).toBeInTheDocument();
10 | });
11 |
--------------------------------------------------------------------------------
/src/components/button/index.js:
--------------------------------------------------------------------------------
1 | export * from './Button';
2 |
--------------------------------------------------------------------------------
/src/components/globalStyle/GlobalStyle.js:
--------------------------------------------------------------------------------
1 | import { createGlobalStyle } from 'styled-components';
2 | import bgImg from '../../images/bg.jpeg';
3 |
4 | export const GlobalStyle = createGlobalStyle`
5 | body {
6 | background: url(${bgImg}) center no-repeat;
7 | background-size: cover;
8 | color: #332c36;
9 | padding: 0;
10 | margin: 0;
11 | font-family: 'New Tegomin', serif;
12 | }
13 | `;
14 |
--------------------------------------------------------------------------------
/src/components/globalStyle/index.js:
--------------------------------------------------------------------------------
1 | export * from './GlobalStyle';
2 |
--------------------------------------------------------------------------------
/src/components/index.js:
--------------------------------------------------------------------------------
1 | export * from './globalStyle';
2 | export * from './quotes';
3 |
--------------------------------------------------------------------------------
/src/components/quotes/Quotes.js:
--------------------------------------------------------------------------------
1 | import styled from 'styled-components';
2 | import { string, func } from 'prop-types';
3 | import { Button } from '../button';
4 |
5 | export const Quotes = ({ quote, speaker, onUpdate = () => {} }) => {
6 | return (
7 |
8 | "{quote}"
9 | - {speaker}
10 |
11 |
12 | );
13 | };
14 |
15 | Quotes.propTypes = {
16 | quote: string,
17 | speaker: string,
18 | onUpdate: func
19 | };
20 |
21 | const Wrapper = styled.div`
22 | flex: 1;
23 | display: flex;
24 | flex-direction: column;
25 | align-items: center;
26 | `;
27 |
28 | const Quote = styled.p`
29 | font-size: 2em;
30 | text-shadow: rgba(0, 0, 0, 0.2) 1px 1px 1px;
31 | flex: 1;
32 | margin: 0;
33 | `;
34 |
35 | const Speaker = styled(Quote)`
36 | text-align: right;
37 | width: 100%;
38 | margin-bottom: 50px;
39 | `;
40 |
--------------------------------------------------------------------------------
/src/components/quotes/Quotes.test.js:
--------------------------------------------------------------------------------
1 | import { render, screen, fireEvent } from '@testing-library/react';
2 | import { Quotes } from './Quotes';
3 |
4 | const quote = 'test quote';
5 | const speaker = 'random speaker';
6 |
7 | test('renders received quote, speaker and a button', () => {
8 | render();
9 |
10 | const quoteEl = screen.getByText(/test quote/i);
11 | const speakerEl = screen.getByText(/random speaker/i);
12 | const buttonEl = screen.getByRole('button');
13 |
14 | expect(quoteEl).toBeInTheDocument();
15 | expect(speakerEl).toBeInTheDocument();
16 | expect(buttonEl).toBeInTheDocument();
17 | });
18 |
19 | test('calls a callback when button is pressed', () => {
20 | const callback = jest.fn();
21 |
22 | render();
23 |
24 | const buttonEl = screen.getByRole('button');
25 |
26 | fireEvent.click(buttonEl);
27 | expect(callback).toHaveBeenCalledTimes(1);
28 | });
29 |
--------------------------------------------------------------------------------
/src/components/quotes/index.js:
--------------------------------------------------------------------------------
1 | export * from './Quotes';
2 |
--------------------------------------------------------------------------------
/src/images/bg.jpeg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/celso-henrique/naruto-quotes-client/e4765ec50ca401bb1596fa09e9306490c5448739/src/images/bg.jpeg
--------------------------------------------------------------------------------
/src/images/naruto.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/celso-henrique/naruto-quotes-client/e4765ec50ca401bb1596fa09e9306490c5448739/src/images/naruto.png
--------------------------------------------------------------------------------
/src/index.js:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import ReactDOM from 'react-dom';
3 | import { GlobalStyle } from './components';
4 | import { App } from './pages';
5 |
6 | ReactDOM.render(
7 |
8 |
9 |
10 | ,
11 | document.getElementById('root')
12 | );
13 |
--------------------------------------------------------------------------------
/src/pages/app/App.js:
--------------------------------------------------------------------------------
1 | import { useState, useEffect, useRef } from 'react';
2 | import styled from 'styled-components';
3 | import narutoImg from '../../images/naruto.png';
4 | import jutsoSound from '../../sounds/jutso.mp3';
5 | import { Quotes } from '../../components';
6 | import { getQuote } from '../../services';
7 |
8 | const audio = new Audio(jutsoSound);
9 |
10 | export function App() {
11 | const isMounted = useRef(true);
12 | const [quote, setQuote] = useState({
13 | speaker: 'Loading speaker...',
14 | quote: 'Loading Quote'
15 | });
16 |
17 | const onUpdate = async () => {
18 | const resQuote = await getQuote();
19 |
20 | if (isMounted.current) {
21 | setQuote(resQuote);
22 | audio.play();
23 | }
24 | };
25 |
26 | useEffect(() => {
27 | onUpdate();
28 |
29 | return () => {
30 | isMounted.current = false;
31 | };
32 | }, []);
33 |
34 | return (
35 |
36 |
37 |
38 |
39 | );
40 | }
41 |
42 | const Content = styled.div`
43 | height: 100vh;
44 | box-sizing: border-box;
45 | padding: 0 50px;
46 | display: flex;
47 | flex-wrap: wrap;
48 | justify-content: center;
49 | align-items: center;
50 | `;
51 |
52 | const NarutoImg = styled.img`
53 | max-width: 50vw;
54 | align-self: flex-end;
55 | `;
56 |
--------------------------------------------------------------------------------
/src/pages/app/App.test.js:
--------------------------------------------------------------------------------
1 | import { rest } from 'msw';
2 | import { setupServer } from 'msw/node';
3 | import { render, screen, fireEvent } from '@testing-library/react';
4 | import { App } from './App';
5 |
6 | const response = { speaker: 'test speaker', quote: 'test quote' };
7 |
8 | const server = setupServer(
9 | rest.get(process.env.REACT_APP_API, (req, res, ctx) => {
10 | return res(ctx.json(response));
11 | })
12 | );
13 |
14 | beforeAll(() => server.listen());
15 | afterEach(() => server.resetHandlers());
16 | afterAll(() => server.close());
17 |
18 | test('renders a button and naruto image', () => {
19 | render();
20 |
21 | const buttonEl = screen.getByRole('button');
22 | const imageEl = screen.getByRole('img');
23 |
24 | expect(buttonEl).toBeInTheDocument();
25 | expect(imageEl).toBeInTheDocument();
26 | });
27 |
28 | test('calls api on startup and renders it response', async () => {
29 | render();
30 |
31 | const quoteEl = await screen.findByText(/test quote/i);
32 |
33 | expect(quoteEl).toBeInTheDocument();
34 | });
35 |
36 | test('calls api on button click and update its text', async () => {
37 | const customResponse = {
38 | speaker: 'custom test speaker',
39 | quote: 'teste quote'
40 | };
41 |
42 | render();
43 |
44 | server.use(
45 | rest.get(process.env.REACT_APP_API, (req, res, ctx) => {
46 | return res(ctx.json(customResponse));
47 | })
48 | );
49 |
50 | const buttonEl = screen.getByRole('button');
51 |
52 | fireEvent.click(buttonEl);
53 | const quoteEl = await screen.findByText(/custom test speaker/i);
54 |
55 | expect(quoteEl).toBeInTheDocument();
56 | });
57 |
--------------------------------------------------------------------------------
/src/pages/app/index.js:
--------------------------------------------------------------------------------
1 | export * from './App';
2 |
--------------------------------------------------------------------------------
/src/pages/index.js:
--------------------------------------------------------------------------------
1 | export * from './app';
2 |
--------------------------------------------------------------------------------
/src/services/index.js:
--------------------------------------------------------------------------------
1 | export * from './quotesService';
2 |
--------------------------------------------------------------------------------
/src/services/quotesService/index.js:
--------------------------------------------------------------------------------
1 | export * from './quotesService';
2 |
--------------------------------------------------------------------------------
/src/services/quotesService/quotesService.js:
--------------------------------------------------------------------------------
1 | export const getQuote = () =>
2 | fetch(process.env.REACT_APP_API).then((response) => response.json());
3 |
--------------------------------------------------------------------------------
/src/services/quotesService/quotesService.test.js:
--------------------------------------------------------------------------------
1 | import { rest } from 'msw';
2 | import { setupServer } from 'msw/node';
3 |
4 | import { getQuote } from './quotesService';
5 |
6 | const response = { test: 'testing' };
7 |
8 | const server = setupServer(
9 | rest.get(process.env.REACT_APP_API, (req, res, ctx) => {
10 | return res(ctx.json(response));
11 | })
12 | );
13 |
14 | beforeAll(() => server.listen());
15 | afterEach(() => server.resetHandlers());
16 | afterAll(() => server.close());
17 |
18 | test('transforms json response into object', async () => {
19 | const quote = await getQuote();
20 |
21 | expect(quote).toStrictEqual(response);
22 | });
23 |
--------------------------------------------------------------------------------
/src/setupTests.js:
--------------------------------------------------------------------------------
1 | // jest-dom adds custom jest matchers for asserting on DOM nodes.
2 | // allows you to do things like:
3 | // expect(element).toHaveTextContent(/react/i)
4 | // learn more: https://github.com/testing-library/jest-dom
5 | import '@testing-library/jest-dom';
6 |
7 | window.HTMLMediaElement.prototype.play = () => {
8 | /* do nothing */
9 | };
10 |
--------------------------------------------------------------------------------
/src/sounds/jutso.mp3:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/celso-henrique/naruto-quotes-client/e4765ec50ca401bb1596fa09e9306490c5448739/src/sounds/jutso.mp3
--------------------------------------------------------------------------------