Upgrade to Pro
— share decks privately, control downloads, hide ads and more …
Speaker Deck
Sign up for free
Menu
Search
Features
All features
Private URLs
Password Protection
Custom URLS
Scheduled publishing
Remove Branding
Restrict embedding
Deck Collections
Notes
Features
All features
Private URLs
Password Protection
Custom URLS
Scheduled publishing
Remove Branding
Restrict embedding
Deck Collections
Notes
Explore
Featured decks
Featured speakers
Programming
Technology
Storyboards
Explore
Featured decks
Featured speakers
Programming
Technology
Storyboards
Pricing
Search
Sign in
Sign up for free
Testing React Hooks with Confidence
Search
Radoslav Stankov
January 16, 2021
Technology
250
1
Share
Embed
Copy iframe code
Copy JS code
Copy link
Start on current slide
Testing React Hooks with Confidence
Code can be found at
https://github.com/rstankov/talks-code
Radoslav Stankov
January 16, 2021
More Decks by Radoslav Stankov
See All by Radoslav Stankov
Tips for Building Useful Agent Skills
rstankov
0
150
Building LLM Powered Features
rstankov
0
190
Tips for Tailwind CSS
rstankov
0
81
Building LLM Powered Features (lightning talk)
rstankov
0
110
All you need is CSS
rstankov
0
180
Ruby on Rails The Single Engineer Framework
rstankov
0
70
Rails: The Missing Parts
rstankov
1
280
The dream that turned into nightmare
rstankov
0
350
The dream that turned into nightmare (lightning)
rstankov
0
170
Other Decks in Technology
See All in Technology
味覇で作るパスタが美味い - 20261001_yabaubuki.dev
livesense
PRO
0
110
AI 時代の Azure エンジニアリング ~ 私たちは何を磨き、何を任せるのか ~
chack411
1
540
メルペイ 会計システム概要と歴史
mewuto
0
200
組み立てて楽しむ AWS Blocks 入門
kmiya84377
0
220
しろおびから始める!ログ活用の第一歩
shumei_ito
0
710
AIがコードを書く時代に、エンジニアは何で自分の価値を証明するのか / In an era where AI writes code how do engineers prove their value
carta_engineering
0
500
AI駆動開発の時代になったのでトヨタ生産方式から見直す
terurou
4
5.1k
DWH の限界突破! AI エージェント向け爆速リアルタイムデータ基盤 ClickHouse
jozono
1
300
二歩目のフロントエンド・ウェブアクセシビリティ
camiha
2
170
おそらく日本で唯一のDevRelインターン生として
husengs7
0
170
「Docker Desktop不要の時代」は 来たのか?
ueponx
0
280
ビジネスを止めない技術的負債の返済のための戦略とその手法 - 技術的負債と向き合う / Complexity and Simplicity
soudai
PRO
3
670
Featured
See All Featured
DevOps and Value Stream Thinking: Enabling flow, efficiency and business value
helenjbeal
2
400
Code Review Best Practice
trishagee
74
20k
A Soul's Torment
seathinner
8
3.7k
JAMstack: Web Apps at Ludicrous Speed - All Things Open 2022
reverentgeek
1
620
Beyond borders and beyond the search box: How to win the global "messy middle" with AI-driven SEO
davidcarrasco
3
270
The #1 spot is gone: here's how to win anyway
tamaranovitovic
4
1.2k
技術選定の審美眼(2025年版) / Understanding the Spiral of Technologies 2025 edition
twada
PRO
120
120k
Visualization
eitanlees
153
17k
[RailsConf 2023 Opening Keynote] The Magic of Rails
eileencodes
31
10k
[Rails World 2023 - Day 1 Closing Keynote] - The Magic of Rails
eileencodes
38
3k
Exploring the Power of Turbo Streams & Action Cable | RailsConf2023
kevinliebholz
37
6.6k
Reality Check: Gamification 10 Years Later
codingconduct
0
2.3k
Transcript
Testing React Hooks with Confidence Radoslav Stankov 29/01/2020
Radoslav Stankov @rstankov blog.rstankov.com twitter.com/rstankov github.com/rstankov speakerdeck.com/rstankov
None
None
https://rstankov.com/appearances
None
Automated Testing Test Driven Development
None
None
import * as React from 'react'; import Memory from './Memory';
import Grid from './Grid'; import Button from './Button'; const OPERATIONS = { '+': (a, b) => a + b, '-': (a, b) => a - b, '*': (a, b) => a * b, }; export default function Calculator() { const [memory, setMemory] = React.useState('0'); const actions = React.useMemo( () => ({ add(input) { if (!input.match(/\d/)) { if (!memory.match(/\d$/)) { return; } if (!OPERATIONS[input]) { return; } setMemory(`${memory} ${input} `); return;
const actions = React.useMemo( () => ({ add(input) { if
(!input.match(/\d/)) { if (!memory.match(/\d$/)) { return; } if (!OPERATIONS[input]) { return; } setMemory(`${memory} ${input} `); return; } if (memory.match(/(^| )0$/)) { setMemory(`${memory.slice(0, -1)}${input}`); return; } setMemory(`${memory}${input}`); }, remove() { const newMemory = memory.match(/\d$/) ? memory.slice(0, -1) : memory.slice(0, -3); setMemory(newMemory || '0');
if (memory.match(/(^| )0$/)) { setMemory(`${memory.slice(0, -1)}${input}`); return; } setMemory(`${memory}${input}`); },
remove() { const newMemory = memory.match(/\d$/) ? memory.slice(0, -1) : memory.slice(0, -3); setMemory(newMemory || '0'); }, reset() { setMemory('0'); }, evaluate() { const chunks = memory.split(' '); let value = parseInt(chunks[0], 10); for (let i = 1; i < chunks.length; i += 2) { const other = chunks[i + 1]; if (typeof other === 'undefined' || other.length === 0) { continue; } const operation = OPERATIONS[chunks[i]];
reset() { setMemory('0'); }, evaluate() { const chunks = memory.split('
'); let value = parseInt(chunks[0], 10); for (let i = 1; i < chunks.length; i += 2) { const other = chunks[i + 1]; if (typeof other === 'undefined' || other.length === 0) { continue; } const operation = OPERATIONS[chunks[i]]; if (!operation) { throw new Error(`Invalid "${chunks[i]}" operand`); } value = operation(value, parseInt(other, 10)); } setMemory(value.toString()); }, }), [memory, setMemory], ); return (
); return ( <Grid size="4"> <Grid.Column size="3"> <Memory>{memory.trim()}</Memory> <Button onClick={()
=> actions.add('1')}>1</Button> <Button onClick={() => actions.add('2')}>2</Button> <Button onClick={() => actions.add('3')}>3</Button> <Button onClick={() => actions.add('4')}>4</Button> <Button onClick={() => actions.add('5')}>5</Button> <Button onClick={() => actions.add('6')}>6</Button> <Button onClick={() => actions.add('7')}>7</Button> <Button onClick={() => actions.add('8')}>8</Button> <Button onClick={() => actions.add('9')}>9</Button> <Button onClick={actions.reset}>C</Button> <Button onClick={() => actions.add('0')}>0</Button> <Button onClick={actions.remove}>←</Button> </Grid.Column> <Grid.Column size="1"> <Button onClick={actions.evaluate}>=</Button> <Button onClick={() => actions.add('+')}>+</Button> <Button onClick={() => actions.add('-')}>-</Button> <Button onClick={() => actions.add('*')}>*</Button> </Grid.Column> </Grid> ); }
None
! Smoke Test
return ( <Grid size="4"> <Grid.Column size="3" data-testid="buttons"> <Memory data-testid="result">{memory.trim()}</Memory> <Button
data-testid="1" onClick={() => actions.add('1')}>1</Button> <Button data-testid="2" onClick={() => actions.add('2')}>2</Button> <Button data-testid="3" onClick={() => actions.add('3')}>3</Button> <Button data-testid="4" onClick={() => actions.add('4')}>4</Button> <Button data-testid="5" onClick={() => actions.add('5')}>5</Button> <Button data-testid="6" onClick={() => actions.add('6')}>6</Button> <Button data-testid="7" onClick={() => actions.add('7')}>7</Button> <Button data-testid="8" onClick={() => actions.add('8')}>8</Button> <Button data-testid="9" onClick={() => actions.add('9')}>9</Button> <Button data-testid="reset" onClick={actions.reset}>C</Button> <Button data-testid="0" onClick={() => actions.add('0')}>0</Button> <Button data-testid="remove" onClick={actions.remove}>← </Button> </Grid.Column> <Grid.Column size="1" data-testid="operations"> <Button data-testid="evaluate" onClick={actions.evaluate}>=</Button> <Button data-testid="+" onClick={() => actions.add('+')}>+</Button> <Button data-testid="-" onClick={() => actions.add('-')}>-</Button> <Button data-testid="*" onClick={() => actions.add('*')}>*</Button> </Grid.Column> </Grid> ); }
return ( <Grid size="4"> <Grid.Column size="3" data-testid="buttons"> <Memory data-testid="result">{memory.trim()}</Memory> <Button
data-testid="1" onClick={() => actions.add('1')}>1</Button> <Button data-testid="2" onClick={() => actions.add('2')}>2</Button> <Button data-testid="3" onClick={() => actions.add('3')}>3</Button> <Button data-testid="4" onClick={() => actions.add('4')}>4</Button> <Button data-testid="5" onClick={() => actions.add('5')}>5</Button> <Button data-testid="6" onClick={() => actions.add('6')}>6</Button> <Button data-testid="7" onClick={() => actions.add('7')}>7</Button> <Button data-testid="8" onClick={() => actions.add('8')}>8</Button> <Button data-testid="9" onClick={() => actions.add('9')}>9</Button> <Button data-testid="reset" onClick={actions.reset}>C</Button> <Button data-testid="0" onClick={() => actions.add('0')}>0</Button> <Button data-testid="remove" onClick={actions.remove}>← </Button> </Grid.Column> <Grid.Column size="1" data-testid="operations"> <Button data-testid="evaluate" onClick={actions.evaluate}>=</Button> <Button data-testid="+" onClick={() => actions.add('+')}>+</Button> <Button data-testid="-" onClick={() => actions.add('-')}>-</Button> <Button data-testid="*" onClick={() => actions.add('*')}>*</Button> </Grid.Column> </Grid> ); }
yarn add --dev @testing-library/react
import { render, fireEvent, screen } from '@testing-library/react'; const click
= (dt) => fireEvent.click(screen.getByTestId(dt)); const expectComponent = (dt) => expect(screen.getByTestId(dt)); describe(Component.name, () => { it('works', () => { render(<Component />); expectComponent('result').toHaveTextContent('0'); click('1'); click('+'); click('1'); expectComponent('result').toHaveTextContent('1 + 1'); click('remove'); expectComponent('result').toHaveTextContent('1 +'); click('2'); expectComponent('result').toHaveTextContent('1 + 2'); click('evaluate'); expectComponent('result').toHaveTextContent('3'); click('reset'); expectComponent('result').toHaveTextContent('0'); }); });
import { render, fireEvent, screen } from '@testing-library/react'; const click
= (dt) => fireEvent.click(screen.getByTestId(dt)); const expectComponent = (dt) => expect(screen.getByTestId(dt)); describe(Component.name, () => { it('works', () => { render(<Component />); expectComponent('result').toHaveTextContent('0'); click('1'); click('+'); click('1'); expectComponent('result').toHaveTextContent('1 + 1'); click('remove'); expectComponent('result').toHaveTextContent('1 +'); click('2'); expectComponent('result').toHaveTextContent('1 + 2'); click('evaluate'); expectComponent('result').toHaveTextContent('3'); click('reset'); expectComponent('result').toHaveTextContent('0'); }); });
import { render, fireEvent, screen } from '@testing-library/react'; const click
= (dt) => fireEvent.click(screen.getByTestId(dt)); const expectComponent = (dt) => expect(screen.getByTestId(dt)); describe(Component.name, () => { it('works', () => { render(<Component />); expectComponent('result').toHaveTextContent('0'); click('1'); click('+'); click('1'); expectComponent('result').toHaveTextContent('1 + 1'); click('remove'); expectComponent('result').toHaveTextContent('1 +'); click('2'); expectComponent('result').toHaveTextContent('1 + 2'); click('evaluate'); expectComponent('result').toHaveTextContent('3'); click('reset'); expectComponent('result').toHaveTextContent('0'); }); });
import { render, fireEvent, screen } from '@testing-library/react'; const click
= (dt) => fireEvent.click(screen.getByTestId(dt)); const expectComponent = (dt) => expect(screen.getByTestId(dt)); describe(Component.name, () => { it('works', () => { render(<Component />); expectComponent('result').toHaveTextContent('0'); click('1'); click('+'); click('1'); expectComponent('result').toHaveTextContent('1 + 1'); click('remove'); expectComponent('result').toHaveTextContent('1 +'); click('2'); expectComponent('result').toHaveTextContent('1 + 2'); click('evaluate'); expectComponent('result').toHaveTextContent('3'); click('reset'); expectComponent('result').toHaveTextContent('0'); }); });
import { render, fireEvent, screen } from '@testing-library/react'; const click
= (dt) => fireEvent.click(screen.getByTestId(dt)); const expectComponent = (dt) => expect(screen.getByTestId(dt)); describe(Component.name, () => { it('works', () => { render(<Component />); expectComponent('result').toHaveTextContent('0'); click('1'); click('+'); click('1'); expectComponent('result').toHaveTextContent('1 + 1'); click('remove'); expectComponent('result').toHaveTextContent('1 +'); click('2'); expectComponent('result').toHaveTextContent('1 + 2'); click('evaluate'); expectComponent('result').toHaveTextContent('3'); click('reset'); expectComponent('result').toHaveTextContent('0'); }); });
" This is placeholder " Not a "real" unit test.
It doesn't follow the 4 phases of good test: setup ➡ action ➡ assert ➡ teardown
import * as React from 'react'; import Memory from './Memory';
import Grid from './Grid'; import Button from './Button'; const OPERATIONS = { '+': (a, b) => a + b, '-': (a, b) => a - b, '*': (a, b) => a * b, }; export default function Calculator() { const [memory, setMemory] = React.useState('0'); const actions = React.useMemo( () => ({ add(input) { if (!input.match(/\d/)) { if (!memory.match(/\d$/)) { return; } if (!OPERATIONS[input]) { return; } setMemory(`${memory} ${input} `); return; $
% Extract Custom Hook
import * as React from 'react'; import Memory from './Memory';
import Grid from './Grid'; import Button from './Button'; import useCalculatorState from './useCalculatorState'; export default function Calculator() { const [memory, actions] = useCalculatorState(); return ( <Grid size="4"> <Grid.Column size="3" data-testid="buttons"> <Memory data-testid="result">{memory.trim()}</Memory> <Button data-testid="1" onClick={() => actions.add('1')}>1</Button> <Button data-testid="2" onClick={() => actions.add('2')}>2</Button> <Button data-testid="3" onClick={() => actions.add('3')}>3</Button> <Button data-testid="4" onClick={() => actions.add('4')}>4</Button> <Button data-testid="5" onClick={() => actions.add('5')}>5</Button> <Button data-testid="6" onClick={() => actions.add('6')}>6</Button> <Button data-testid="7" onClick={() => actions.add('7')}>7</Button> <Button data-testid="8" onClick={() => actions.add('8')}>8</Button> <Button data-testid="9" onClick={() => actions.add('9')}>9</Button> <Button data-testid="reset" onClick={actions.reset}>C</Button> <Button data-testid="0" onClick={() => actions.add('0')}>0</Button> <Button data-testid="remove" onClick={actions.remove}>← </Button> </Grid.Column> <Grid.Column size="1" data-testid="operations">
import * as React from 'react'; import Memory from './Memory';
import Grid from './Grid'; import Button from './Button'; import useCalculatorState from './useCalculatorState'; export default function Calculator() { const [memory, actions] = useCalculatorState(); return ( <Grid size="4"> <Grid.Column size="3" data-testid="buttons"> <Memory data-testid="result">{memory.trim()}</Memory> <Button data-testid="1" onClick={() => actions.add('1')}>1</Button> <Button data-testid="2" onClick={() => actions.add('2')}>2</Button> <Button data-testid="3" onClick={() => actions.add('3')}>3</Button> <Button data-testid="4" onClick={() => actions.add('4')}>4</Button> <Button data-testid="5" onClick={() => actions.add('5')}>5</Button> <Button data-testid="6" onClick={() => actions.add('6')}>6</Button> <Button data-testid="7" onClick={() => actions.add('7')}>7</Button> <Button data-testid="8" onClick={() => actions.add('8')}>8</Button> <Button data-testid="9" onClick={() => actions.add('9')}>9</Button> <Button data-testid="reset" onClick={actions.reset}>C</Button> <Button data-testid="0" onClick={() => actions.add('0')}>0</Button> <Button data-testid="remove" onClick={actions.remove}>← </Button> </Grid.Column> <Grid.Column size="1" data-testid="operations">
import * as React from 'react'; const OPERATIONS = {
'+': (a, b) => a + b, '-': (a, b) => a - b, '*': (a, b) => a * b, }; export default function useCalculatorState(initalState = '0') { const [memory, setMemory] = React.useState(initalState); const actions = React.useMemo( () => ({ add(input) { if (!input.match(/\d/)) { if (!memory.match(/\d$/)) { return; } if (!OPERATIONS[input]) { return; } setMemory(`${memory} ${input} `); return; } if (memory.match(/(^| )0$/)) {
& Test Custom hook
import useCalculatorState from './useCalculatorState'; import { renderHook, act } from
'@testing-library/react-hooks'; describe(useCalculatorState.name, () => { function initHook(initalState) { return renderHook(() => useCalculatorState(initalState)).result; } describe('add', () => { function call(initalState, value) { const hook = initHook(initalState); act(() => { hook.current[1].add(value); }); return hook.current[0]; } it('can add digits', () => { expect(call('1', '2')).toEqual('12'); }); it('ensures numbers cannot start with 0', () => { expect(call('0', '1')).toEqual('1'); expect(call('1 + 0', '0')).toEqual('1 + 0'); expect(call('1 + 0', '1')).toEqual('1 + 1');
yarn add --dev @testing-library/react-hooks
import useCalculatorState from './useCalculatorState'; import { renderHook, act } from
'@testing-library/react-hooks'; describe(useCalculatorState.name, () => { function initHook(initalState) { return renderHook(() => useCalculatorState(initalState)).result; } describe('add', () => { function call(initalState, value) { const hook = initHook(initalState); act(() => { hook.current[1].add(value); }); return hook.current[0]; } it('can add digits', () => { expect(call('1', '2')).toEqual('12'); }); it('ensures numbers cannot start with 0', () => { expect(call('0', '1')).toEqual('1'); expect(call('1 + 0', '0')).toEqual('1 + 0'); expect(call('1 + 0', '1')).toEqual('1 + 1');
import useCalculatorState from './useCalculatorState'; import { renderHook, act } from
'@testing-library/react-hooks'; describe(useCalculatorState.name, () => { function initHook(initalState) { return renderHook(() => useCalculatorState(initalState)).result; } describe('add', () => { function call(initalState, value) { const hook = initHook(initalState); act(() => { hook.current[1].add(value); }); return hook.current[0]; } it('can add digits', () => { expect(call('1', '2')).toEqual('12'); }); it('ensures numbers cannot start with 0', () => { expect(call('0', '1')).toEqual('1'); expect(call('1 + 0', '0')).toEqual('1 + 0'); expect(call('1 + 0', '1')).toEqual('1 + 1');
import useCalculatorState from './useCalculatorState'; import { renderHook, act } from
'@testing-library/react-hooks'; describe(useCalculatorState.name, () => { function initHook(initalState) { return renderHook(() => useCalculatorState(initalState)).result; } describe('add', () => { function call(initalState, value) { const hook = initHook(initalState); act(() => { hook.current[1].add(value); }); return hook.current[0]; } it('can add digits', () => { expect(call('1', '2')).toEqual('12'); }); it('ensures numbers cannot start with 0', () => { expect(call('0', '1')).toEqual('1'); expect(call('1 + 0', '0')).toEqual('1 + 0'); expect(call('1 + 0', '1')).toEqual('1 + 1');
import useCalculatorState from './useCalculatorState'; import { renderHook, act } from
'@testing-library/react-hooks'; describe(useCalculatorState.name, () => { function initHook(initalState) { return renderHook(() => useCalculatorState(initalState)).result; } describe('add', () => { function call(initalState, value) { const hook = initHook(initalState); act(() => { hook.current[1].add(value); }); return hook.current[0]; } it('can add digits', () => { expect(call('1', '2')).toEqual('12'); }); it('ensures numbers cannot start with 0', () => { expect(call('0', '1')).toEqual('1'); expect(call('1 + 0', '0')).toEqual('1 + 0'); expect(call('1 + 0', '1')).toEqual('1 + 1');
import useCalculatorState from './useCalculatorState'; import { renderHook, act } from
'@testing-library/react-hooks'; describe(useCalculatorState.name, () => { function initHook(initalState) { return renderHook(() => useCalculatorState(initalState)).result; } describe('add', () => { function call(initalState, value) { const hook = initHook(initalState); act(() => { hook.current[1].add(value); }); return hook.current[0]; } it('can add digits', () => { expect(call('1', '2')).toEqual('12'); }); it('ensures numbers cannot start with 0', () => { expect(call('0', '1')).toEqual('1'); expect(call('1 + 0', '0')).toEqual('1 + 0'); expect(call('1 + 0', '1')).toEqual('1 + 1');
it('can add digits', () => { expect(call('1', '2')).toEqual('12'); }); it('ensures
numbers cannot start with 0', () => { expect(call('0', '1')).toEqual('1'); expect(call('1 + 0', '0')).toEqual('1 + 0'); expect(call('1 + 0', '1')).toEqual('1 + 1'); expect(call('10', '0')).toEqual('100'); }); it('can add operand', () => { expect(call('1', '+')).toEqual('1 + '); }); it('guards against add operand before digit', () => { expect(call('', '+')).toEqual(''); }); it('guards against two operands one after the other', () => { expect(call('1 + ', '+')).toEqual('1 + '); }); it('guards against add invalid operator', () => { expect(call('1', '~')).toEqual('1'); }); });
describe('remove', () => { function call(initalState) { const hook =
initHook(initalState); act(() => { hook.current[1].remove(); }); return hook.current[0]; } it('removes one digit', () => { expect(call('1234')).toEqual('123'); }); it('removes one operand', () => { expect(call('1 + ')).toEqual('1'); }); it('returns at least 0 when fully cleared', () => { expect(call('1')).toEqual('0'); }); }); describe('evaluate', () => { function call(initalState) { const hook = initHook(initalState);
describe('evaluate', () => { function call(initalState) { const hook =
initHook(initalState); act(() => { hook.current[1].evaluate(); }); return hook.current[0]; } describe('operations', () => { it('handles noop', () => { expect(call('12')).toEqual('12'); }); it('handles "1 + 1" input', () => { expect(call('1 + 1')).toEqual('2'); }); it('handles "1 - 1" input', () => { expect(call('2 - 1')).toEqual('1'); }); it('handles "2 * 3" input', () => { expect(call('2 * 3')).toEqual('6'); }); });
expect(call('2 * 3')).toEqual('6'); }); }); describe('edge cases', () => {
it('handles zero in input', () => { expect(call('1 + 0')).toEqual('1'); }); it('handles larger number in input', () => { expect(call('123 + 456')).toEqual((123 + 456).toString()); }); it('handles multiple operations input', () => { expect(call('1 + 1 + 1')).toEqual('3'); }); it('handles incomplete operation input', () => { expect(call('1 + ')).toEqual('1'); }); it('handles negative values', () => { expect(call('-1 + 2')).toEqual('1'); }); }); }); });
' Refactor Custom Hook
import * as React from 'react'; export default function useCalculatorState(initalState
= '0') { const [memory, dispatch] = React.useReducer(calculatorReducer, initalState); const actions = React.useMemo( () => ({ add(value) { dispatch({ type: 'add', payload: value }); }, remove() { dispatch({ type: 'remove' }); }, reset() { dispatch({ type: 'reset' }); }, evaluate() { dispatch({ type: 'evaluate' }); }, }), [dispatch], ); return [memory, actions]; } const OPERATIONS = {
function calculatorReducer(memory, action) { switch (action.type) { case 'add': const
input = action.payload; if (!input.match(/\d/)) { if (!memory.match(/\d$/)) { return memory; } if (!OPERATIONS[input]) { return memory; } return `${memory} ${input} `; } if (memory.match(/(^| )0$/)) { return `${memory.slice(0, -1)}${input}`; } return `${memory}${input}`; case 'remove': const newMemory = memory.match(/\d$/) ? memory.slice(0, -1) : memory.slice(0, -3);
case 'reset': return '0'; case 'evaluate': const chunks = memory.split('
'); let value = parseInt(chunks[0], 10); for (let i = 1; i < chunks.length; i += 2) { const other = chunks[i + 1]; if (typeof other === 'undefined' || other.length === 0) { continue; } const operation = OPERATIONS[chunks[i]]; if (!operation) { throw new Error(`Invalid "${chunks[i]}" operand`); } value = operation(value, parseInt(other, 10)); } return value.toString(); default: throw new Error(`Invalid action - ${action.type}`); } }
Recap
( started with messy code ) added "smoke test" *
extracted custom hook + added test for the custom hook , refactored the hook - removed "smoke test"
None
Thanks .
Thanks . https://github.com/rstankov/talks-code
Thanks . https://rstankov.com/appearances