Skip to content

Commit

Permalink
add tests to 02
Browse files Browse the repository at this point in the history
kentcdodds committed Aug 23, 2024
1 parent 9841c41 commit 99c1cb5
Showing 24 changed files with 413 additions and 76 deletions.
Original file line number Diff line number Diff line change
@@ -1,7 +1,8 @@
import { expect, testStep, dtl } from '@epic-web/workshop-utils/test'
const { screen, fireEvent } = dtl
const { screen } = dtl

window.history.pushState({}, '', '?query=dog')
const currentPath = window.location.pathname
window.history.pushState({}, '', `${currentPath}?query=dog`)

await import('./index.tsx')

4 changes: 2 additions & 2 deletions exercises/01.managing-ui-state/README.mdx
Original file line number Diff line number Diff line change
@@ -14,8 +14,8 @@ From there's a cycle of user interaction, state changes, and re-rendering. This
is the core of how React works for interactive applications.

The `render` phase is what what we've done so far with creating React elements.
Handling user interactions is what we've done with event listeners like
`onSubmit`. Now we're going to get into the `state changes` bit.
Handling user interactions is what we do with event listeners like `onChange`.
Now we're going to get into the `state changes` bit.

In React, you use special functions called "hooks" to do this. Common built-in
hooks include:
7 changes: 1 addition & 6 deletions exercises/02.side-effects/01.problem.effects/index.tsx
Original file line number Diff line number Diff line change
@@ -29,12 +29,7 @@ function App() {

return (
<div className="app">
<form
onSubmit={e => {
e.preventDefault()
setGlobalSearchParams({ query })
}}
>
<form action={() => setGlobalSearchParams({ query })}>
<div>
<label htmlFor="searchInput">Search:</label>
<input
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
import { expect, testStep, dtl } from '@epic-web/workshop-utils/test'
const { screen, fireEvent } = dtl

import './index.tsx'

const searchBox = await testStep(
'The user can see the search box',
async () => {
const result = await screen.findByRole('searchbox', { name: /search/i })
expect(result).toHaveValue('')
return result
},
)

const dogCheckbox = await testStep(
'The user can see the dog checkbox',
async () => {
const result = await screen.findByRole('checkbox', { name: /dog/i })
expect(result).not.toBeChecked()
return result
},
)

await testStep('The user can search for a checkbox value', async () => {
fireEvent.change(searchBox, { target: { value: 'dog' } })
})

await testStep('checkbox is checked automatically', async () => {
expect(dogCheckbox).toBeChecked()
})
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
import { expect, testStep, dtl } from '@epic-web/workshop-utils/test'
const { screen, fireEvent } = dtl

import './index.tsx'

const searchBox = await testStep(
'The user can see the search box',
async () => {
const result = await screen.findByRole('searchbox', { name: /search/i })
expect(result).toHaveValue('')
return result
},
)

const dogCheckbox = await testStep(
'The user can see the dog checkbox',
async () => {
const result = await screen.findByRole('checkbox', { name: /dog/i })
expect(result).not.toBeChecked()
return result
},
)

await testStep('The user can select the dog checkbox', async () => {
fireEvent.click(dogCheckbox)
expect(dogCheckbox).toBeChecked()
})

await testStep(
'Selecting the checkbox updates the search and results',
async () => {
// Check that the search box value has been updated
expect(searchBox).toHaveValue('dog')

// Check that the results have been filtered
await dtl.waitFor(async () => {
await screen.findByText(/the joy of owning a dog/i)

const catResult = screen.queryByText(/caring for your feline friend/i)
expect(catResult).not.toBeInTheDocument()
})
},
)
30 changes: 30 additions & 0 deletions exercises/02.side-effects/01.solution.effects/filtering.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
import { expect, testStep, dtl } from '@epic-web/workshop-utils/test'
const { screen, fireEvent } = dtl

import './index.tsx'

const searchBox = await testStep(
'The user can see the search box',
async () => {
const result = await screen.findByRole('searchbox', { name: /search/i })
expect(result).toHaveValue('')
return result
},
)

const catResult = await testStep('The user can see the results', async () => {
const result = screen.getByText(/caring for your feline friend/i)
expect(result).toBeInTheDocument()
return result
})

await testStep('The user can search for a term', async () => {
fireEvent.change(searchBox, { target: { value: 'dog' } })
})

await testStep('The results are filtered', async () => {
await dtl.waitFor(() => {
expect(catResult).not.toBeInTheDocument()
})
await screen.findByText(/the joy of owning a dog/i)
})
7 changes: 1 addition & 6 deletions exercises/02.side-effects/01.solution.effects/index.tsx
Original file line number Diff line number Diff line change
@@ -28,12 +28,7 @@ function App() {

return (
<div className="app">
<form
onSubmit={e => {
e.preventDefault()
setGlobalSearchParams({ query })
}}
>
<form action={() => setGlobalSearchParams({ query })}>
<div>
<label htmlFor="searchInput">Search:</label>
<input
38 changes: 38 additions & 0 deletions exercises/02.side-effects/01.solution.effects/popstate.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
import { expect, testStep, dtl } from '@epic-web/workshop-utils/test'
const { screen, fireEvent } = dtl

const currentPath = window.location.pathname
window.history.pushState({}, '', `${currentPath}?query=dog`)

await import('./index.tsx')

await testStep(
'The search box is initialized with URL query parameter',
async () => {
const searchBox = await screen.findByRole('searchbox', { name: /search/i })
expect(searchBox).toHaveValue('dog')
},
)

// wait for the event handler to be set up
// for some reason it takes a bit
await new Promise(resolve => setTimeout(resolve, 100))

await testStep(
'The search box updates when popstate event is triggered',
async () => {
// Simulate navigation to a new URL
const currentPath = window.location.pathname
window.history.pushState({}, '', `${currentPath}?query=cat`)

// Trigger popstate event
fireEvent.popState(window)

// Check if the search box value is updated
await dtl.waitFor(async () =>
expect(
await screen.findByRole('searchbox', { name: /search/i }),
).toHaveValue('cat'),
)
},
)
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
import { expect, testStep, dtl } from '@epic-web/workshop-utils/test'
const { screen, fireEvent } = dtl

window.history.pushState({}, '', '?query=dog')

await import('./index.tsx')

await testStep(
'The search box is initialized with URL query parameter',
async () => {
const searchBox = await screen.findByRole('searchbox', { name: /search/i })
expect(searchBox).toHaveValue('dog')
},
)
15 changes: 11 additions & 4 deletions exercises/02.side-effects/02.problem.cleanup/README.mdx
Original file line number Diff line number Diff line change
@@ -61,9 +61,16 @@ You can add `console.log` statements to make sure things are being called (unles
you want to open up the memory profiling tab in your dev tools and click the
checkbox a bunch of times to see the memory usage go up 😅).

💯 If you really do want to test this out, I've added a couple lines you can
uncomment to allocate huge amounts of memory to huge arrays. If you uncomment
those, then watch the quick climb of the memory in the
🚨 To test this, I've added a couple lines to allocate huge amounts of memory to
huge arrays. Watch the quick climb of the memory in the
[Memory tab of dev tools](https://developer.chrome.com/docs/devtools/memory) or
[Browser Task manager](https://developer.chrome.com/docs/devtools/memory-problems#monitor_memory_use_in_realtime_with_the_chrome_task_manager)
every time you check and uncheck the box.
every time you check and uncheck the box. The test toggles the checkbox many
times and then checks that the memory usage is a reasonable increase of the
initial memory usage.

<callout-warning>
Testing memory leaks is tricky. It's possible the memory usage starts out
higher than it should leading to a test that passes but should not. Try
running the test a few times to be certain you've got it right.
</callout-warning>
17 changes: 7 additions & 10 deletions exercises/02.side-effects/02.problem.cleanup/index.tsx
Original file line number Diff line number Diff line change
@@ -18,13 +18,15 @@ function App() {
const caterpillarChecked = words.includes('caterpillar')

useEffect(() => {
// 💯 you can use this to test whether your cleanup is working (make sure to include the console.log below as well)
// const hugeData = new Array(1_000_000).fill(new Array(1_000_000).fill('🐶🐱🐛'))
// 🚨 we use this to test whether your cleanup is working
const hugeData = new Array(1_000_000).fill(
new Array(1_000_000).fill('🐶🐱🐛'),
)

// 🐨 extract your event handler here into a function called updateQuery
window.addEventListener('popstate', () => {
// 💯 you can use this to test whether your cleanup is freeing up memory
// console.log(hugeData)
// 🚨 this console.log forces the hugeData to hang around as long as the event listener is active
console.log(hugeData)

console.log('popstate event listener called')
setQuery(getQueryParam())
@@ -40,12 +42,7 @@ function App() {

return (
<div className="app">
<form
onSubmit={e => {
e.preventDefault()
setGlobalSearchParams({ query })
}}
>
<form action={() => setGlobalSearchParams({ query })}>
<div>
<label htmlFor="searchInput">Search:</label>
<input
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
import { expect, testStep, dtl } from '@epic-web/workshop-utils/test'
const { screen, fireEvent } = dtl

import './index.tsx'

const searchBox = await testStep(
'The user can see the search box',
async () => {
const result = await screen.findByRole('searchbox', { name: /search/i })
expect(result).toHaveValue('')
return result
},
)

const dogCheckbox = await testStep(
'The user can see the dog checkbox',
async () => {
const result = await screen.findByRole('checkbox', { name: /dog/i })
expect(result).not.toBeChecked()
return result
},
)

await testStep('The user can search for a checkbox value', async () => {
fireEvent.change(searchBox, { target: { value: 'dog' } })
})

await testStep('checkbox is checked automatically', async () => {
expect(dogCheckbox).toBeChecked()
})
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
import { expect, testStep, dtl } from '@epic-web/workshop-utils/test'
const { screen, fireEvent } = dtl

import './index.tsx'

const searchBox = await testStep(
'The user can see the search box',
async () => {
const result = await screen.findByRole('searchbox', { name: /search/i })
expect(result).toHaveValue('')
return result
},
)

const dogCheckbox = await testStep(
'The user can see the dog checkbox',
async () => {
const result = await screen.findByRole('checkbox', { name: /dog/i })
expect(result).not.toBeChecked()
return result
},
)

await testStep('The user can select the dog checkbox', async () => {
fireEvent.click(dogCheckbox)
expect(dogCheckbox).toBeChecked()
})

await testStep(
'Selecting the checkbox updates the search and results',
async () => {
// Check that the search box value has been updated
expect(searchBox).toHaveValue('dog')

// Check that the results have been filtered
await dtl.waitFor(async () => {
await screen.findByText(/the joy of owning a dog/i)

const catResult = screen.queryByText(/caring for your feline friend/i)
expect(catResult).not.toBeInTheDocument()
})
},
)
30 changes: 30 additions & 0 deletions exercises/02.side-effects/02.solution.cleanup/filtering.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
import { expect, testStep, dtl } from '@epic-web/workshop-utils/test'
const { screen, fireEvent } = dtl

import './index.tsx'

const searchBox = await testStep(
'The user can see the search box',
async () => {
const result = await screen.findByRole('searchbox', { name: /search/i })
expect(result).toHaveValue('')
return result
},
)

const catResult = await testStep('The user can see the results', async () => {
const result = screen.getByText(/caring for your feline friend/i)
expect(result).toBeInTheDocument()
return result
})

await testStep('The user can search for a term', async () => {
fireEvent.change(searchBox, { target: { value: 'dog' } })
})

await testStep('The results are filtered', async () => {
await dtl.waitFor(() => {
expect(catResult).not.toBeInTheDocument()
})
await screen.findByText(/the joy of owning a dog/i)
})
Loading

0 comments on commit 99c1cb5

Please sign in to comment.