Skip to content

Commit 020875c

Browse files
authored
Merge pull request #23 from msd-code-academy/lesson-3
Lesson 3
2 parents 3455126 + f6c0a62 commit 020875c

28 files changed

+727
-0
lines changed

lesson-3/README.md

+113
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,113 @@
1+
# React Testing
2+
3+
## The Goal
4+
5+
Learn about different types of automated tests and how to apply them in React environment.
6+
7+
## Automated tests
8+
9+
![Test pyramid by Martin Fowler](https://martinfowler.com/bliki/images/testPyramid/test-pyramid.png)
10+
11+
[The test pyramid](https://martinfowler.com/bliki/TestPyramid.html)
12+
13+
- **End To End tests**: Slow, complicated, brittle, complete
14+
- **Unit tests**: Fast, simple, reliable, isolated
15+
16+
## How to write a good test
17+
18+
1. Write a failing test
19+
2. Observe that it actually fails!
20+
3. Make sure that it fails with red
21+
4. Fix code so that the test passes
22+
5. GOTO 1
23+
24+
## How to write a code that is easy to test
25+
26+
**Pure functions** for the win:
27+
- Given an input, always produces the same output
28+
- No side effects (including changing its arguments)
29+
30+
React Components are usually pure functions. Not a coincidence!
31+
32+
## Tools that we will use
33+
34+
- [Jest - Delightful JavaScript Testing](https://facebook.github.io/jest/) framework and toolset for testing
35+
- [Enzyme](https://github.com/airbnb/enzyme) library for testing React components
36+
37+
## Tools that we will not use
38+
39+
**E2E testing**: Selenium, Webdrivers, Headless browsers, Robot Framework (because they take too much time to set up and learn)
40+
41+
**Unit testing**: Mocha, Chai, Expect, Istanbul, Sinon (because these are alternatives to Jest)
42+
43+
**Server/API testing**: Supertest (because we focus on frontend only, for now)
44+
45+
## Hands on!
46+
47+
_Excercise 1: Checkout lesson-3 folder and run the app! `npm install`, `npm start`_
48+
49+
_Excercise 2: Find your favourite bug_
50+
51+
_Excercise 3: Write a unit test and `npm test`_
52+
53+
https://facebook.github.io/jest/docs/en/expect.html
54+
55+
Hint:
56+
```javascript
57+
import { removeFromArray } from "./functions"
58+
59+
// test suite: organize your tests!
60+
describe("functions.test.js: removeFromArray", () => {
61+
62+
// single test
63+
it("should remove item from array", () => {
64+
const input = ... // prepare data
65+
const actual = ... // call the function here
66+
const expected = ... // what you want to see?
67+
expect(actual).toEqual(expected) // test!
68+
})
69+
})
70+
```
71+
72+
_Excercise 4: Write a Component test using enzyme_
73+
74+
http://airbnb.io/enzyme/docs/api/shallow.html
75+
76+
https://github.com/facebookincubator/create-react-app/blob/master/packages/react-scripts/template/README.md#testing-components
77+
78+
```javascript
79+
import React from "react" // because we will use JSX
80+
import Party from "./Party"
81+
import { shallow } from "enzyme" // there are multiple kinds of rendering
82+
83+
describe("Party.js", () => {
84+
85+
it("should display party name", () => {
86+
const party = { name: "MyParty", members: 100 } // some data that we pass to props
87+
const wrapper = shallow(<Party party={party} />)
88+
const text = wrapper.text()
89+
expect(text).toMatch("MyParty")
90+
})
91+
92+
})
93+
```
94+
95+
_Excercise 5: Write propTypes_
96+
97+
Technically, this is not a test. But it will help you!
98+
99+
https://reactjs.org/docs/typechecking-with-proptypes.html
100+
101+
102+
103+
## Reading and more links
104+
105+
(You may think I am biased towards Eric Elliot - perhaps. But he does write well!)
106+
107+
- [5 Questions Every Unit Test Must Answer](https://medium.com/javascript-scene/what-every-unit-test-needs-f6cd34d9836d)
108+
- [5 Common Misconceptions About TDD & Unit Tests](https://medium.com/javascript-scene/5-common-misconceptions-about-tdd-unit-tests-863d5beb3ce9)
109+
- [TDD the RITE Way](https://medium.com/javascript-scene/tdd-the-rite-way-53c9b46f45e3)
110+
- [Learn Test Driven Development (TDD)](https://github.com/dwyl/learn-tdd)
111+
- https://github.com/msd-code-academy/03-testing-react-app
112+
- [What is a Pure Function?](https://medium.com/javascript-scene/master-the-javascript-interview-what-is-a-pure-function-d1c076bec976)
113+
- [Pure Happiness with Pure Functions](https://drboolean.gitbooks.io/mostly-adequate-guide/content/ch3.html)

lesson-3/coalition/.editorconfig

+15
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
1+
# http://editorconfig.org/
2+
3+
# top-most EditorConfig file
4+
root = true
5+
6+
# Unix-style newlines with a newline ending every file
7+
end_of_line = lf
8+
insert_final_newline = true
9+
10+
# UTF-8 as default charset
11+
charset = utf-8
12+
13+
# Consistent indentation
14+
indent_size = 2
15+
indent_style = space

lesson-3/coalition/.gitignore

+21
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
# See https://help.github.com/ignore-files/ for more about ignoring files.
2+
3+
# dependencies
4+
/node_modules
5+
6+
# testing
7+
/coverage
8+
9+
# production
10+
/build
11+
12+
# misc
13+
.DS_Store
14+
.env.local
15+
.env.development.local
16+
.env.test.local
17+
.env.production.local
18+
19+
npm-debug.log*
20+
yarn-debug.log*
21+
yarn-error.log*

lesson-3/coalition/package.json

+21
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
{
2+
"name": "coalition",
3+
"version": "0.1.0",
4+
"private": true,
5+
"dependencies": {
6+
"react": "^16.0.0",
7+
"react-dom": "^16.0.0",
8+
"react-scripts": "1.0.14"
9+
},
10+
"scripts": {
11+
"start": "react-scripts start",
12+
"build": "react-scripts build",
13+
"test": "react-scripts test --env=jsdom",
14+
"eject": "react-scripts eject"
15+
},
16+
"devDependencies": {
17+
"enzyme": "^3.1.0",
18+
"enzyme-adapter-react-16": "^1.0.2",
19+
"react-test-renderer": "^16.0.0"
20+
}
21+
}

lesson-3/coalition/public/favicon.ico

3.78 KB
Binary file not shown.

lesson-3/coalition/public/index.html

+40
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,40 @@
1+
<!doctype html>
2+
<html lang="en">
3+
<head>
4+
<meta charset="utf-8">
5+
<meta name="viewport" content="width=device-width, initial-scale=1, shrink-to-fit=no">
6+
<meta name="theme-color" content="#000000">
7+
<!--
8+
manifest.json provides metadata used when your web app is added to the
9+
homescreen on Android. See https://developers.google.com/web/fundamentals/engage-and-retain/web-app-manifest/
10+
-->
11+
<link rel="manifest" href="%PUBLIC_URL%/manifest.json">
12+
<link rel="shortcut icon" href="%PUBLIC_URL%/favicon.ico">
13+
<!--
14+
Notice the use of %PUBLIC_URL% in the tags above.
15+
It will be replaced with the URL of the `public` folder during the build.
16+
Only files inside the `public` folder can be referenced from the HTML.
17+
18+
Unlike "/favicon.ico" or "favicon.ico", "%PUBLIC_URL%/favicon.ico" will
19+
work correctly both with client-side routing and a non-root public URL.
20+
Learn how to configure a non-root public URL by running `npm run build`.
21+
-->
22+
<title>React App</title>
23+
</head>
24+
<body>
25+
<noscript>
26+
You need to enable JavaScript to run this app.
27+
</noscript>
28+
<div id="root"></div>
29+
<!--
30+
This HTML file is a template.
31+
If you open it directly in the browser, you will see an empty page.
32+
33+
You can add webfonts, meta tags, or analytics to this file.
34+
The build step will place the bundled scripts into the <body> tag.
35+
36+
To begin the development, run `npm start` or `yarn start`.
37+
To create a production bundle, use `npm run build` or `yarn build`.
38+
-->
39+
</body>
40+
</html>
+15
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
1+
{
2+
"short_name": "React App",
3+
"name": "Create React App Sample",
4+
"icons": [
5+
{
6+
"src": "favicon.ico",
7+
"sizes": "192x192",
8+
"type": "image/png"
9+
}
10+
],
11+
"start_url": "./index.html",
12+
"display": "standalone",
13+
"theme_color": "#000000",
14+
"background_color": "#ffffff"
15+
}

lesson-3/coalition/src/App.css

+28
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,28 @@
1+
.App {
2+
text-align: center;
3+
}
4+
5+
.App-logo {
6+
animation: App-logo-spin infinite 20s linear;
7+
height: 60px;
8+
}
9+
10+
.App-header {
11+
background-color: #222;
12+
height: 150px;
13+
padding: 20px;
14+
color: white;
15+
}
16+
17+
.App-title {
18+
font-size: 1.5em;
19+
}
20+
21+
.App-intro {
22+
font-size: large;
23+
}
24+
25+
@keyframes App-logo-spin {
26+
from { transform: rotate(0deg); }
27+
to { transform: rotate(360deg); }
28+
}

lesson-3/coalition/src/App.js

+47
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,47 @@
1+
import React, { Component } from 'react';
2+
import logo from './logo.svg';
3+
import './App.css';
4+
import parties from "./election-results"
5+
import PartiesList from "./PartiesList"
6+
import Coalition from "./Coalition"
7+
8+
import { removeFromArray } from "./functions"
9+
class App extends Component {
10+
11+
constructor(props) {
12+
super(props)
13+
this.state = {
14+
coalition: []
15+
}
16+
}
17+
18+
addParty = (party) => {
19+
this.setState({
20+
coalition: this.state.coalition.concat(party)
21+
})
22+
}
23+
24+
25+
removeParty = (partyToRemove) => {
26+
this.setState({
27+
coalition: removeFromArray(this.state.coalition, partyToRemove)
28+
})
29+
}
30+
31+
render() {
32+
return (
33+
<div className="App">
34+
<header className="App-header">
35+
<img src={logo} className="App-logo" alt="logo" />
36+
<h1 className="App-title">Welcome to Czech Republic, 2017</h1>
37+
<h2>Build your own coalition!</h2>
38+
</header>
39+
<PartiesList parties={parties} partySelected={this.addParty} />
40+
<hr />
41+
<Coalition parties={this.state.coalition} partySelected={this.removeParty} />
42+
</div>
43+
);
44+
}
45+
}
46+
47+
export default App;

lesson-3/coalition/src/App.test.js

+8
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
import React from 'react';
2+
import ReactDOM from 'react-dom';
3+
import App from './App';
4+
5+
it('renders without crashing', () => {
6+
const div = document.createElement('div');
7+
ReactDOM.render(<App />, div);
8+
});

lesson-3/coalition/src/Coalition.js

+26
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,26 @@
1+
import React, { Component } from "react"
2+
import "./PartiesList.css"
3+
4+
import Party from "./Party"
5+
6+
import { sumMembers } from "./functions"
7+
8+
export default class Coalition extends Component {
9+
render() {
10+
11+
const members = sumMembers(this.props.parties)
12+
13+
return (
14+
<div>
15+
<section className="PartiesList-perks">
16+
Your Coalition has {members} members.
17+
</section>
18+
<article className="PartiesList-wrapper">
19+
{this.props.parties.map(party =>
20+
<Party key={party.name} party={party} partySelected={this.props.partySelected} />
21+
)}
22+
</article>
23+
</div>
24+
)
25+
}
26+
}
+4
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
.PartiesList-wrapper {
2+
display: flex;
3+
flex-wrap: wrap;
4+
}

lesson-3/coalition/src/PartiesList.js

+16
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,16 @@
1+
import React, { Component } from "react"
2+
import "./PartiesList.css"
3+
4+
import Party from "./Party"
5+
6+
export default class PartiesList extends Component {
7+
render() {
8+
return (
9+
<section className="PartiesList-wrapper">
10+
{this.props.parties.map(party =>
11+
<Party key={party.name} party={party} partySelected={this.props.partySelected} />
12+
)}
13+
</section>
14+
)
15+
}
16+
}

lesson-3/coalition/src/Party.css

+14
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+
.Party {
2+
border: 3px solid #ddd;
3+
border-left-width: 15px;
4+
border-right-width: 15px;
5+
border-radius: 3px;
6+
width: 20em;
7+
margin: 5px;
8+
flex: 0 1 auto;
9+
}
10+
11+
.Party:hover {
12+
cursor: pointer;
13+
box-shadow: 0 0 10px 0 gray;
14+
}

0 commit comments

Comments
 (0)