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
260
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
160
Building LLM Powered Features
rstankov
0
200
Tips for Tailwind CSS
rstankov
0
96
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
72
Rails: The Missing Parts
rstankov
1
290
The dream that turned into nightmare
rstankov
0
360
The dream that turned into nightmare (lightning)
rstankov
0
180
Other Decks in Technology
See All in Technology
データマネジメント × 法務で作りあげる、 freeeの新しいデータガバナンスへの軌跡
freee
PRO
2
580
視野はAIに、視座は人に / ai-for-breadth-humans-for-perspective
nkzn
5
2.5k
Flutter Widget Previewerで始めるUIプレビューとVRTの自動化
sakusaku3939
0
180
AI駆動開発におけるハーネスエンジニアリングの勘所
tsukuboshi
1
880
[FECONF-2026] 신대륙을 향한 카카오페이증권 프론트엔드 항해기 (이주승)
l1ghthouse
0
320
Autonomous AI Lakehouse - Live AI Hubから始めるセキュアなAIデータ活用 -
oracle4engineer
PRO
2
180
Maestro MCPとClaude Code Skillsによる自律的なE2Eテスト支援
asmz
0
380
ひとりで始めた AI駆動開発、チームに広げて何が変わったのか
kenichiodo
1
160
新卒エンジニア、 介護士になる
ryu1013
1
490
re:Inventで、人生は変わったのか? / growth-builders-hub-1-reinvent-2026
mhrtech
2
160
AIエージェントに 権限と鍵をどこまで渡すか
nrinetcom
PRO
2
160
基本から分かる!AWSセキュリティ運用の考え方と任せ方
cmusudakeisuke
1
260
Featured
See All Featured
Test your architecture with Archunit
thirion
2
2.5k
Visual Storytelling: How to be a Superhuman Communicator
reverentgeek
2
730
Art, The Web, and Tiny UX
lynnandtonic
304
22k
A designer walks into a library…
pauljervisheath
211
25k
From π to Pie charts
rasagy
1
420
Product Roadmaps are Hard
iamctodd
55
13k
Amusing Abliteration
ianozsvald
1
370
[Rails World 2023 - Day 1 Closing Keynote] - The Magic of Rails
eileencodes
38
3.1k
Digital Ethics as a Driver of Design Innovation
axbom
PRO
1
470
Are puppies a ranking factor?
jonoalderson
2
4.1k
SEO for Brand Visibility & Recognition
aleyda
0
4.8k
The Organizational Zoo: Understanding Human Behavior Agility Through Metaphoric Constructive Conversations (based on the works of Arthur Shelley, Ph.D)
kimpetersen
PRO
0
510
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