Skip to content

Commit e2a931a

Browse files
author
Peter Hozak
committed
exercise 03
1 parent 04042b1 commit e2a931a

File tree

9 files changed

+327
-17
lines changed

9 files changed

+327
-17
lines changed

08-testing-hooks/README.md

+17-8
Original file line numberDiff line numberDiff line change
@@ -36,23 +36,32 @@ Starting with a stateless component with a test:
3636
## 02 TDD
3737
> [src/02](src/02)
3838
39-
Starting with the above solution and boilerplate code, display the last valid color name:
40-
- add validation test + update component tests
41-
- implement validation + update component
39+
Display the last valid color name:
40+
- add validation test
41+
- implement validation
42+
- update component tests
43+
- update component
4244

4345

4446
## 03 custom hooks
4547
> [src/03](src/03)
4648
47-
- code of component with lots of logic + test for just initial render
48-
- move to a new custom hook
49-
- add test for the hook, using dummy component
49+
Move the color logic to a reusable custom hook:
50+
- e.g. following API: `{rawColor, validColor, setColor} = useColor(defaultColor)`
51+
- refactor component (no need to update tests at this stage!)
52+
- add tests for the custom hook
53+
- use the hook multiple times
5054

5155

52-
## 04 testing libraries
56+
## 04 libraries for testing
57+
- https://testing-library.com/docs/react-testing-library/intro
58+
- https://airbnb.io/enzyme/
59+
5360
> [src/04](src/04)
5461
55-
- links to useful libraries (disclaimer whether or not I had time to try them)
62+
Simplify tests to react-testing-library:
63+
- replace `act()`
64+
- replace `modify()`
5665

5766

5867
## 05 react-redux

08-testing-hooks/src/01/01-solution.js

+2-2
Original file line numberDiff line numberDiff line change
@@ -4,14 +4,14 @@ const Main = () => {
44
const defaultColor = 'orange'
55

66
const [color, setColor] = useState('')
7-
const handleHange = (event) => {
7+
const handleChange = (event) => {
88
setColor(event.target.value)
99
}
1010

1111
return (
1212
<div className="Main">
1313
<h3>Adding State</h3>
14-
<input placeholder={defaultColor} value={color} onChange={handleHange} />
14+
<input placeholder={defaultColor} value={color} onChange={handleChange} />
1515
<div className="Main-box" style={{ backgroundColor: color || defaultColor }} />
1616
</div>
1717
)

08-testing-hooks/src/02/02-exercise.js

+3-3
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@ const Main = () => {
66
const defaultColor = 'orange'
77

88
const [inputColor, setInputColor] = useState('')
9-
const handleHange = (event) => {
9+
const handleChange = (event) => {
1010
const { value } = event.target
1111
setInputColor(value)
1212
if (validateColor(value)) {
@@ -16,8 +16,8 @@ const Main = () => {
1616

1717
return (
1818
<div className="Main">
19-
<h3>Adding State</h3>
20-
<input placeholder={defaultColor} value={inputColor} onChange={handleHange} />
19+
<h3>TDD</h3>
20+
<input placeholder={defaultColor} value={inputColor} onChange={handleChange} />
2121
<div className="Main-box" style={{ backgroundColor: inputColor || defaultColor }}>
2222
{'orange '}
2323
</div>

08-testing-hooks/src/02/02-solution.js

+3-3
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,7 @@ const Main = () => {
1111

1212
const [inputColor, setInputColor] = useState('')
1313
const [validColor, setValidColor] = useState(defaultColor)
14-
const handleHange = (event) => {
14+
const handleChange = (event) => {
1515
const { value } = event.target
1616
setInputColor(value)
1717
if (!value) {
@@ -23,8 +23,8 @@ const Main = () => {
2323

2424
return (
2525
<div className="Main">
26-
<h3>Adding State</h3>
27-
<input placeholder={defaultColor} value={inputColor} onChange={handleHange} />
26+
<h3>TDD</h3>
27+
<input placeholder={defaultColor} value={inputColor} onChange={handleChange} />
2828
<div className="Main-box" style={{ backgroundColor: validColor }}>
2929
{validColor}
3030
</div>
+53
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,53 @@
1+
import React, { useState } from 'react'
2+
3+
export const validateColor = (value) => {
4+
const div = document.createElement('div')
5+
div.style.color = value
6+
return !!div.style.color
7+
}
8+
export const useColor = (defaultColor) => {
9+
const [rawColor, setRawColor] = useState('')
10+
const [validColor, setValidColor] = useState(defaultColor)
11+
12+
const setColor = (value) => {
13+
setRawColor(value)
14+
if (!value) {
15+
setValidColor(defaultColor)
16+
} else if (validateColor(value)) {
17+
setValidColor(value)
18+
}
19+
}
20+
21+
return { rawColor, validColor, setColor }
22+
}
23+
24+
const Main = () => {
25+
const defaultColor = 'orange'
26+
27+
// TODO: replace with the custom hook
28+
const [inputColor, setInputColor] = useState('')
29+
const [validColor, setValidColor] = useState(defaultColor)
30+
const handleChange = (event) => {
31+
const { value } = event.target
32+
setInputColor(value)
33+
if (!value) {
34+
setValidColor(defaultColor)
35+
} else if (validateColor(value)) {
36+
setValidColor(value)
37+
}
38+
}
39+
// Tip:
40+
// const { rawColor, validColor, setColor } = useColor()
41+
42+
return (
43+
<div className="Main">
44+
<h3>Custom Hooks</h3>
45+
<input placeholder={defaultColor} value={inputColor} onChange={handleChange} />
46+
<div className="Main-box" style={{ backgroundColor: validColor }}>
47+
{validColor}
48+
</div>
49+
</div>
50+
)
51+
}
52+
53+
export default Main
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,90 @@
1+
import React from 'react'
2+
import { render, unmountComponentAtNode } from 'react-dom'
3+
import { act } from "react-dom/test-utils"
4+
import Main, { validateColor, useColor } from './03-exercise'
5+
6+
describe('useColor', () => {
7+
let div
8+
beforeEach(() => {
9+
div = document.createElement('div')
10+
document.body.appendChild(div)
11+
})
12+
afterEach(() => {
13+
unmountComponentAtNode(div)
14+
div.remove()
15+
})
16+
17+
test('default value', () => { })
18+
test('modify value', () => { })
19+
})
20+
21+
describe('validateColor', () => {
22+
test('some colors', () => {
23+
expect(validateColor('')).toBeFalsy()
24+
expect(validateColor('orang')).toBeFalsy()
25+
expect(validateColor('abc')).toBeFalsy()
26+
27+
expect(validateColor('orange')).toBeTruthy()
28+
expect(validateColor('green')).toBeTruthy()
29+
expect(validateColor('#abc')).toBeTruthy()
30+
})
31+
})
32+
33+
// input.value = "green" does not work in React 16, see https://stackoverflow.com/a/46012210/1176601
34+
const inputValueSetter = Object.getOwnPropertyDescriptor(HTMLInputElement.prototype, "value").set;
35+
const modify = (input, value) => {
36+
inputValueSetter.call(input, value);
37+
input.dispatchEvent(new Event('input', { bubbles: true }))
38+
}
39+
40+
describe('Main', () => {
41+
let div
42+
beforeEach(() => {
43+
div = document.createElement('div')
44+
document.body.appendChild(div)
45+
})
46+
afterEach(() => {
47+
unmountComponentAtNode(div)
48+
div.remove()
49+
})
50+
51+
test('default color', () => {
52+
act(() => { render(<Main />, div) })
53+
54+
const box = div.querySelector('.Main-box')
55+
expect(box.style.backgroundColor).toBe('orange')
56+
expect(box.textContent).toBe('orange')
57+
});
58+
59+
test('modified color', () => {
60+
act(() => { render(<Main />, div) })
61+
62+
const input = div.querySelector('input')
63+
const box = div.querySelector('.Main-box')
64+
expect(input).toHaveProperty('value', '')
65+
66+
act(() => { modify(input, 'gree') })
67+
68+
expect(input).toHaveProperty('value', 'gree')
69+
expect(box.style.backgroundColor).toBe('orange')
70+
expect(box.textContent).toBe('orange')
71+
72+
act(() => { modify(input, 'green') })
73+
74+
expect(input).toHaveProperty('value', 'green')
75+
expect(box.style.backgroundColor).toBe('green')
76+
expect(box.textContent).toBe('green')
77+
78+
act(() => { modify(input, 'g') })
79+
80+
expect(input).toHaveProperty('value', 'g')
81+
expect(box.style.backgroundColor).toBe('green')
82+
expect(box.textContent).toBe('green')
83+
84+
act(() => { modify(input, '') })
85+
86+
expect(input).toHaveProperty('value', '')
87+
expect(box.style.backgroundColor).toBe('orange')
88+
expect(box.textContent).toBe('orange')
89+
})
90+
})
+42
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,42 @@
1+
import React, { useState } from 'react'
2+
3+
export const validateColor = (value) => {
4+
const div = document.createElement('div')
5+
div.style.color = value
6+
return !!div.style.color
7+
}
8+
export const useColor = (defaultColor) => {
9+
const [rawColor, setRawColor] = useState('')
10+
const [validColor, setValidColor] = useState(defaultColor)
11+
12+
const setColor = (value) => {
13+
setRawColor(value)
14+
if (!value) {
15+
setValidColor(defaultColor)
16+
} else if (validateColor(value)) {
17+
setValidColor(value)
18+
}
19+
}
20+
21+
return { rawColor, validColor, setColor }
22+
}
23+
24+
const Main = () => {
25+
const { rawColor: bgInput, validColor: bgColor, setColor: bgSet } = useColor('orange')
26+
const { rawColor: fgInput, validColor: fgColor, setColor: fgSet } = useColor('blue')
27+
const bgChange = (event) => { bgSet(event.target.value) }
28+
const fgChange = (event) => { fgSet(event.target.value) }
29+
30+
return (
31+
<div className="Main">
32+
<h3>Custom Hooks</h3>
33+
<input placeholder="background" value={bgInput} onChange={bgChange} />
34+
<input placeholder="foreground" value={fgInput} onChange={fgChange} />
35+
<div className="Main-box" style={{ backgroundColor: bgColor, color: fgColor }}>
36+
{bgColor}/{fgColor}
37+
</div>
38+
</div>
39+
)
40+
}
41+
42+
export default Main

0 commit comments

Comments
 (0)