Skip to content

Commit

Permalink
Add test for Station No. 9; consistency for all tests
Browse files Browse the repository at this point in the history
  • Loading branch information
3c1u committed Apr 30, 2021
1 parent 6d5a390 commit 6c0841d
Show file tree
Hide file tree
Showing 9 changed files with 184 additions and 78 deletions.
36 changes: 30 additions & 6 deletions tests/mock/fetch.ts
Original file line number Diff line number Diff line change
Expand Up @@ -74,13 +74,13 @@ const mockResponse = (

const randomImageTest = /^(https?)?:\/\/dog.ceo\/api\/breeds\/image\/random\/?$/
const randomImageTestByBreed = /^(https?)?:\/\/dog.ceo\/api\/breed\/([A-Za-z]+)(\/([A-Za-z]+))?\/image\/random\/?$/
const breedsAllTest = /^(https?)?:\/\/dog.ceo\/api\/breeds\/all\/?$/
const breedsAllTest = /^(https?)?:\/\/dog.ceo\/api\/breeds\/list\/all\/?$/

// not used
// const randomMultipleImageTest = /^(https?)?:\/\/dog.ceo\/api\/breeds\/image\/random\/([1-9]*[0-9])\/?$/
// const breedImagesTest = /^(https?)?:\/\/dog.ceo\/api\/breed\/([A-Za-z]+)\/images\/?$/
// const randomMultipleImageTestByBreed = /^(https?)?:\/\/dog.ceo\/api\/breed\/([A-Za-z]+)(\/([A-Za-z]+))?\/image\/random\/([1-9]*[0-9])\/?$/
// const breedListTest = /^(https?)?:\/\/dog.ceo\/api\/breed\/([A-Za-z]+)\/list\/?$/
const randomMultipleImageTest = /^(https?)?:\/\/dog.ceo\/api\/breeds\/image\/random\/([1-9]*[0-9])\/?$/
const breedImagesTest = /^(https?)?:\/\/dog.ceo\/api\/breed\/([A-Za-z]+)\/images\/?$/
const randomMultipleImageTestByBreed = /^(https?)?:\/\/dog.ceo\/api\/breed\/([A-Za-z]+)(\/([A-Za-z]+))?\/image\/random\/([1-9]*[0-9])\/?$/
const breedListTest = /^(https?)?:\/\/dog.ceo\/api\/breed\/([A-Za-z]+)\/list\/?$/

const pickOne = <T>(a: T[]): T | undefined => {
if (a.length === 0) {
Expand All @@ -90,6 +90,10 @@ const pickOne = <T>(a: T[]): T | undefined => {
return a[Math.floor(a.length * Math.random())]
}

const unimplementedMockApiRouteHandler = () => {
throw new Error('unhandled API route. Did you read the manual?')
}

const mockApiRoutes: {
test: RegExp
handle(url: string): { message: any; status: string; code?: number }
Expand Down Expand Up @@ -157,10 +161,30 @@ const mockApiRoutes: {
}
},
},
{
test: randomMultipleImageTest,
handle: unimplementedMockApiRouteHandler,
},
{
test: breedImagesTest,
handle: unimplementedMockApiRouteHandler,
},
{
test: randomMultipleImageTestByBreed,
handle: unimplementedMockApiRouteHandler,
},
{
test: breedListTest,
handle: unimplementedMockApiRouteHandler,
}
]

export const fetchMock: typeof window.fetch = (resource, ..._) => {
const url = typeof resource === 'string' ? resource : resource.url
const res = mockApiRoutes.find(r => r.test.test(url))!.handle(url)
const handler = mockApiRoutes.find(r => r.test.test(url))
if (!handler) {
throw new Error(`unhandled API route "${url}". Did you read the manual?`)
}
const res = handler!.handle(url)
return Promise.resolve(mockResponse(url, res, res.code))
}
17 changes: 12 additions & 5 deletions tests/station01.test.tsx
Original file line number Diff line number Diff line change
@@ -1,9 +1,16 @@
import * as React from 'react'
import renderer from 'react-test-renderer'
import renderer, { act } from 'react-test-renderer'
import { App } from '../src/App'
import { fetchMock } from './mock/fetch'

it('Can create <App />', () => {
renderer.create(
<App />,
)
describe('Station No.1', () => {
const fetch = jest.fn()
window.fetch = fetch
fetch.mockImplementation(fetchMock)

it('Can create <App />', async () => {
await act(async () => {
renderer.create(<App />)
})
})
})
24 changes: 18 additions & 6 deletions tests/station02.test.tsx
Original file line number Diff line number Diff line change
@@ -1,11 +1,23 @@
import * as React from 'react'
import renderer from 'react-test-renderer'
import renderer, { act, ReactTestRenderer } from 'react-test-renderer'
import { App } from '../src/App'
import { fetchMock } from './mock/fetch'
import { createAsync } from './utils/createAsync'

it('<App /> has a <header>', () => {
const res = renderer.create(
<App />,
)

res.root.findByType('header')
describe('Station No.2', () => {
const fetch = jest.fn()
window.fetch = fetch
fetch.mockImplementation(fetchMock)

it('Can create <App />', async () => {
await act(async () => {
renderer.create(<App />)
})
})

it('<App /> has a <header>', async () => {
const res = await createAsync(<App />)
res.root.findByType('header')
})
})
81 changes: 43 additions & 38 deletions tests/station03.test.tsx
Original file line number Diff line number Diff line change
@@ -1,46 +1,51 @@
import * as React from 'react'
import renderer from 'react-test-renderer'
import { App } from '../src/App'

it('<App /> has a <header>', () => {
const res = renderer.create(
<App />,
)

res.root.findByType('header')
})

it('<App /> contains a text node', () => {
const res = renderer.create(<App />)

let hasChildTextNode = false
let stack = [...res.root.children]

// run a depth-first search for a text node
while (stack.length !== 0 && !hasChildTextNode) {
const child = stack.pop()
if (child === undefined) {
throw new Error('unreachable code')
import { fetchMock } from './mock/fetch'
import { createAsync } from './utils/createAsync'

describe('Station No.3', () => {
const fetch = jest.fn()
window.fetch = fetch
fetch.mockImplementation(fetchMock)

it('<App /> has a <header>', async () => {
const res = await createAsync(<App />)
res.root.findByType('header')
})

it('<App /> contains a text node', async () => {
const res = await createAsync(<App />)

let hasChildTextNode = false
let stack = [...res.root.children]

// run a depth-first search for a text node
while (stack.length !== 0 && !hasChildTextNode) {
const child = stack.pop()
if (child === undefined) {
throw new Error('unreachable code')
}

if ((hasChildTextNode = typeof child === 'string')) {
break
}

const children = child.children
if (children.length === 0) {
continue
}

stack = [...children, ...stack]
}

if ((hasChildTextNode = typeof child === 'string')) {
break
}

const children = child.children
if (children.length === 0) {
continue
}

stack = [...children, ...stack]
}

expect(hasChildTextNode).toBe(true)
})
expect(hasChildTextNode).toBe(true)
})

it('<App /> has a <img> with src', () => {
const res = renderer.create(<App />)
it('<App /> has a <img> with src', async () => {
const res = await createAsync(<App />)

const img = res.root.findByType('img')
expect(img.props.src).toBeTruthy()
const img = res.root.findByType('img')
expect(img.props.src).toBeTruthy()
})
})
18 changes: 11 additions & 7 deletions tests/station04.test.tsx
Original file line number Diff line number Diff line change
@@ -1,9 +1,14 @@
import React from 'react'
import renderer, { act } from 'react-test-renderer'
import { App } from '../src/App'
import { fetchMock } from './mock/fetch'
import { createAsync } from './utils/createAsync'

describe('Station No.4', () => {
const fetch = jest.fn()
window.fetch = fetch
fetch.mockImplementation(fetchMock)

describe('<App />', () => {
let setState: React.Dispatch<unknown> | undefined = undefined
const useState = React.useState
const useStateSpy = jest.spyOn(React, 'useState')
Expand All @@ -17,22 +22,21 @@ describe('<App />', () => {
jest.clearAllMocks()
})

it('<App /> calls useState', () => {
renderer.create(<App />)

it('<App /> calls useState', async () => {
await createAsync(<App />)
expect(useStateSpy).toBeCalled()
})

it('<img> uses a state value', () => {
const res = renderer.create(<App />)
it('<img> uses a state value', async () => {
const res = await createAsync(<App />)
const img = res.root.findByType('img')
const injectValue = '🐕'

expect(img.props.src).toBeTruthy()
expect(useStateSpy).toBeCalledWith(img.props.src)

useStateSpy.mockClear()

expect(setState).not.toBeUndefined()

act(() => {
Expand Down
14 changes: 10 additions & 4 deletions tests/station05.test.tsx
Original file line number Diff line number Diff line change
@@ -1,8 +1,14 @@
import React from 'react'
import renderer, { act } from 'react-test-renderer'
import { App } from '../src/App'
import { fetchMock } from './mock/fetch'
import { createAsync } from './utils/createAsync'

describe('Station No.5', () => {
const fetch = jest.fn()
window.fetch = fetch
fetch.mockImplementation(fetchMock)

describe('<App />', () => {
const useState = React.useState
const useStateSpy = jest.spyOn(React, 'useState')
useStateSpy.mockImplementation((v?: unknown) => useState(v))
Expand All @@ -17,15 +23,15 @@ describe('<App />', () => {
expect(useStateSpy).toBeCalled()
})

it('state changes when the button is clicked', () => {
const res = renderer.create(<App />)
it('state changes when the button is clicked', async () => {
const res = await createAsync(<App />)
const img = res.root.findByType('img')
const button = res.root.findByType('button')

const initialImg = img.props.src
expect(initialImg).not.toBeFalsy()

act(() => {
await act(async () => {
button.props.onClick()
})

Expand Down
42 changes: 30 additions & 12 deletions tests/station08.test.tsx
Original file line number Diff line number Diff line change
@@ -1,22 +1,22 @@
import React from 'react'
import renderer, { act } from 'react-test-renderer'
import renderer, { act, ReactTestRenderer } from 'react-test-renderer'
import { imageUrl, fetchMock } from './mock/fetch'

describe('<App />', () => {
const fetch = jest.fn()

window.fetch = fetch

fetch.mockImplementation(fetchMock)

afterEach(() => {
jest.clearAllMocks()
})

it('Can mount <App />', () => {
it('Can mount <App />', async () => {
const { App } = require('../src/App')
expect(App).toBeTruthy()
renderer.create(<App />)
await act(async () => {
renderer.create(<App />)
})
})

it('Can mount <Header />', () => {
Expand All @@ -31,10 +31,12 @@ describe('<App />', () => {
renderer.create(<Description />)
})

it('Can mount <RandomDogButton />', () => {
it('Can mount <RandomDogButton />', async () => {
const { RandomDogButton } = require('../src/RandomDogButton')
expect(RandomDogButton).toBeTruthy()
renderer.create(<RandomDogButton />)
await act(async () => {
renderer.create(<RandomDogButton />)
})
})

it('<RandomDogButton /> has a prop called `handleClickRandomButton` which is called with a image from API', async done => {
Expand All @@ -46,23 +48,39 @@ describe('<App />', () => {
expect(image).toStrictEqual(imageUrl)
done()
})
const res = renderer.create(
<RandomDogButton handleClickRandomButton={mockHandler} />,
)

let res: ReactTestRenderer | undefined
await act(async () => {
res = renderer.create(
<RandomDogButton handleClickRandomButton={mockHandler} />,
)
})

if (!res) {
throw new Error('failed to render')
}

const button = res.root.findByType('button')
act(() => {
button.props.onClick()
})
})

it('<App /> contains <Header />, <Description />, <RandomDogButton />', () => {
it('<App /> contains <Header />, <Description />, <RandomDogButton />', async () => {
const { App } = require('../src/App')
const { Header } = require('../src/Header')
const { Description } = require('../src/Description')
const { RandomDogButton } = require('../src/RandomDogButton')

const res = renderer.create(<App />)
let res: ReactTestRenderer | undefined
await act(async () => {
res = renderer.create(<App />)
})

if (!res) {
throw new Error('failed to render')
}

res.root.findByType(Header)
res.root.findByType(Description)
res.root.findByType(RandomDogButton)
Expand Down
13 changes: 13 additions & 0 deletions tests/station09.test.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
import React from 'react'
import renderer, { act } from 'react-test-renderer'
import { RandomDogButton } from '../src/RandomDogButton'
import { imageUrl, fetchMock } from './mock/fetch'

describe('<App />', () => {
const useEffectSpy = jest.spyOn(React, 'useEffect')

it('<RandomDogButton /> calls React.useEffect', () => {
renderer.create(<RandomDogButton />)
expect(useEffectSpy).toBeCalled()
})
})
Loading

0 comments on commit 6c0841d

Please sign in to comment.