Skip to content

Commit 0be1cfd

Browse files
authored
chore: add basic tests (#15)
* configure vitest and happy-dom * add createUrl tests * explicitly values for enum * add getUpdatedClues tests * add puzzleEncoder tests * add puzzleCreator tests * remove jest leftover dependencies * simplify useTimeout implementation * add useTimeout tests * add useLocalStorage tests * make useHistory abstract * add useHistory tests * remove unused deps and code
1 parent 3d994e4 commit 0be1cfd

26 files changed

+1169
-577
lines changed

package-lock.json

Lines changed: 592 additions & 483 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

package.json

Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,9 @@
1111
"css-types": "tcm src/styles/components -w",
1212
"lint": "tcm src/styles/components -s && tsc && prettier -w --log-level=silent src && eslint '**/*.{ts,tsx}' --quiet --fix",
1313
"prepare": "husky install",
14-
"dev": "vite"
14+
"dev": "vite",
15+
"test": "vitest run",
16+
"test:watch": "vitest watch"
1517
},
1618
"dependencies": {
1719
"classnames": "2.2.6",
@@ -21,14 +23,10 @@
2123
"wouter": "2.12.0"
2224
},
2325
"devDependencies": {
24-
"@testing-library/jest-dom": "6.1.3",
2526
"@testing-library/react": "14.0.0",
26-
"@testing-library/user-event": "14.5.1",
2727
"@types/classnames": "2.2.11",
28-
"@types/jest": "29.5.5",
2928
"@types/node": "18.18.4",
3029
"@types/react": "18.2.25",
31-
"@types/react-copy-to-clipboard": "5.0.0",
3230
"@types/react-dom": "18.2.11",
3331
"@typescript-eslint/eslint-plugin": "6.7.4",
3432
"@typescript-eslint/parser": "6.7.4",
@@ -37,12 +35,14 @@
3735
"eslint-config-prettier": "9.0.0",
3836
"eslint-plugin-react": "7.33.2",
3937
"eslint-plugin-react-hooks": "4.6.0",
38+
"happy-dom": "12.9.0",
4039
"husky": "6.0.0",
4140
"postcss-preset-env": "9.1.4",
4241
"prettier": "3.0.3",
4342
"typed-css-modules": "0.7.2",
4443
"typescript": "5.2.2",
45-
"vite": "4.4.11"
44+
"vite": "4.4.11",
45+
"vitest": "0.34.6"
4646
},
4747
"browserslist": {
4848
"production": [

src/components/Board.tsx

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -19,7 +19,7 @@ type Props = {
1919
setCellState: (r: number, c: number) => (s: CellState) => void
2020
}
2121

22-
export const Board: FC<Props> = ({
22+
const Board: FC<Props> = ({
2323
blocked = false,
2424
buttons = null,
2525
crossable = false,

src/components/Cell.tsx

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -20,7 +20,7 @@ type Props = {
2020
setState: (s: CellState) => void
2121
}
2222

23-
export const Cell: FC<Props> = ({
23+
const Cell: FC<Props> = ({
2424
className = '',
2525
clickedState,
2626
crossable,

src/components/Play.tsx

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@ import Button from 'components/Button'
66
import { RefreshIcon, UndoIcon } from 'components/icons'
77
import ShareButton from 'components/ShareButton'
88
import { useModalContext } from 'contexts/ModalContext'
9-
import { ROUTE_HOME } from 'constants/router.constants'
9+
import { ROUTES } from 'constants/router.constants'
1010
import { COLORS } from 'constants/colors.constants'
1111
import { CellState, Puzzle } from 'models/Puzzle'
1212

@@ -48,7 +48,7 @@ const Play: FC<Props> = ({
4848
[reset, showConfirm]
4949
)
5050

51-
const handleClick = useCallback(() => navigate(ROUTE_HOME), [navigate])
51+
const handleClick = useCallback(() => navigate(ROUTES.HOME), [navigate])
5252

5353
const buttons = useMemo(
5454
() => (

src/components/Welcome.tsx

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@ import { FC } from 'react'
22
import { useLocation } from 'wouter'
33

44
import Button from 'components/Button'
5-
import { ROUTE_CREATE } from 'constants/router.constants'
5+
import { ROUTES } from 'constants/router.constants'
66
import { Puzzle } from 'models/Puzzle'
77
import { createPuzzleFromSize } from 'utils/puzzleCreator'
88

@@ -19,7 +19,7 @@ const Welcome: FC<Props> = ({ setPuzzle }) => {
1919
const setMediumPuzzle = () => setPuzzle(createPuzzleFromSize(10))
2020
const setHardPuzzle = () => setPuzzle(createPuzzleFromSize(15))
2121
const setExpertPuzzle = () => setPuzzle(createPuzzleFromSize(20))
22-
const navigateToCreate = () => navigate(ROUTE_CREATE)
22+
const navigateToCreate = () => navigate(ROUTES.CREATE)
2323

2424
return (
2525
<div className={styles.welcome}>

src/constants/puzzle.constants.ts

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,3 @@
11
export const MIN_SIZE = 5
22
export const MAX_SIZE = 20
3-
export const DEFAULT_SIZE = 10
4-
export const STEPS_LIMIT = 10
3+
export const HISTORY_LIMIT = 10

src/constants/router.constants.ts

Lines changed: 4 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,6 @@
1-
export const ROUTE_CREATE = '/create'
2-
export const ROUTE_HOME = '/'
3-
export const ROUTE_LOAD = '/load/:code'
4-
export const ROUTE_PLAY = '/play'
5-
61
export const ROUTES = {
7-
CREATE: ROUTE_CREATE,
8-
HOME: ROUTE_HOME,
9-
LOAD: ROUTE_LOAD,
10-
PLAY: ROUTE_PLAY,
2+
CREATE: '/create',
3+
HOME: '/',
4+
LOAD: '/load/:code',
5+
PLAY: '/play',
116
}

src/hooks/useHistory.test.ts

Lines changed: 53 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,53 @@
1+
import { act, renderHook } from '@testing-library/react'
2+
import { afterEach, describe, expect, test } from 'vitest'
3+
4+
import { useHistory } from './useHistory'
5+
6+
describe('useHistory', () => {
7+
afterEach(() => {
8+
window.localStorage.clear()
9+
})
10+
11+
test('initializes empty', () => {
12+
const { result } = renderHook(() => useHistory<number>('id', 3))
13+
14+
expect(result.current.hasHistory).toBe(false)
15+
expect(result.current.getEntry()).toBe(null)
16+
})
17+
18+
test('adds and gets entries', () => {
19+
const { result } = renderHook(() => useHistory<string>('id', 3))
20+
21+
act(() => result.current.addEntry('foo'))
22+
23+
expect(result.current.hasHistory).toBe(true)
24+
expect(result.current.getEntry()).toBe('foo')
25+
})
26+
27+
test('limits the number of entries', () => {
28+
const { result } = renderHook(() => useHistory<string>('id', 3))
29+
30+
act(() => result.current.addEntry('foo'))
31+
act(() => result.current.addEntry('bar'))
32+
act(() => result.current.addEntry('fizz'))
33+
act(() => result.current.addEntry('buzz'))
34+
35+
expect(result.current.hasHistory).toBe(true)
36+
act(() => expect(result.current.getEntry()).toBe('buzz'))
37+
act(() => expect(result.current.getEntry()).toBe('fizz'))
38+
act(() => expect(result.current.getEntry()).toBe('bar'))
39+
expect(result.current.hasHistory).toBe(false)
40+
expect(result.current.getEntry()).toBe(null)
41+
})
42+
43+
test('cleans the history', () => {
44+
const { result } = renderHook(() => useHistory<string>('id', 3))
45+
46+
act(() => result.current.addEntry('foo'))
47+
act(() => result.current.addEntry('bar'))
48+
act(() => result.current.cleanHistory())
49+
50+
expect(result.current.hasHistory).toBe(false)
51+
expect(result.current.getEntry()).toBe(null)
52+
})
53+
})

src/hooks/useHistory.ts

Lines changed: 20 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -1,43 +1,40 @@
11
import { useCallback } from 'react'
22

3-
import { STEPS_LIMIT } from 'constants/puzzle.constants'
4-
import { ST_HISTORY } from 'constants/storage.constants'
53
import { useLocalStorage } from 'hooks/useLocalStorage'
6-
import { CellState } from 'models/Puzzle'
74

8-
type Step = [number, number, CellState]
9-
10-
type UseHistoryType = {
5+
type UseHistoryType<T> = {
116
hasHistory: boolean
12-
addStep: (row: number, col: number, state: CellState) => void
13-
getStep: () => Step | null
14-
cleanSteps: () => void
7+
addEntry: (entry: T) => void
8+
getEntry: () => T | null
9+
cleanHistory: () => void
1510
}
1611

17-
export const useHistory = (): UseHistoryType => {
18-
const [history, setHistory, cleanHistory] = useLocalStorage(ST_HISTORY, [] as Step[])
12+
export const useHistory = <T>(
13+
storageId: string,
14+
entriesLimit: number
15+
): UseHistoryType<T> => {
16+
const [history, setHistory, clearHistory] = useLocalStorage(storageId, [] as T[])
1917

2018
const hasHistory = history.length > 0
2119

22-
const addStep = useCallback(
23-
(row: number, col: number, state: CellState) => {
24-
const newStep = [row, col, state] as Step
25-
setHistory((steps) => [newStep, ...steps].slice(0, STEPS_LIMIT))
20+
const addEntry = useCallback(
21+
(newEntry: T) => {
22+
setHistory((entries) => [newEntry, ...entries].slice(0, entriesLimit))
2623
},
27-
[setHistory]
24+
[entriesLimit, setHistory]
2825
)
2926

30-
const getStep = useCallback(() => {
27+
const getEntry = useCallback(() => {
3128
if (!hasHistory) return null
32-
const [step, ...nextHistory] = history
29+
const [entry, ...nextHistory] = history
3330
setHistory(nextHistory)
34-
return step as Step
31+
return entry as T
3532
}, [hasHistory, history, setHistory])
3633

37-
const cleanSteps = useCallback(() => {
38-
cleanHistory()
34+
const cleanHistory = useCallback(() => {
35+
clearHistory()
3936
setHistory([])
40-
}, [cleanHistory, setHistory])
37+
}, [clearHistory, setHistory])
4138

42-
return { hasHistory, addStep, getStep, cleanSteps }
39+
return { hasHistory, addEntry, getEntry, cleanHistory }
4340
}

0 commit comments

Comments
 (0)