Skip to content

Commit

Permalink
finish all tests
Browse files Browse the repository at this point in the history
  • Loading branch information
kentcdodds committed Sep 19, 2024
1 parent f86ec0f commit d2eaccf
Show file tree
Hide file tree
Showing 15 changed files with 404 additions and 7 deletions.
2 changes: 1 addition & 1 deletion exercises/05.portals/01.solution.create/portal.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ import './index.tsx'

await testStep('The portal is rendered to the body', async () => {
const [heartButton] = await screen.findAllByText('🤍')
heartButton.focus()
fireEvent.focusIn(heartButton)

const tooltip = await screen.findByText('Add favorite')
expect(tooltip).toBeTruthy()
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
import { expect, testStep, dtl } from '@epic-web/workshop-utils/test'
const { screen, fireEvent, waitFor } = dtl

import './index.tsx'

await testStep('The portal is rendered to the body', async () => {
const [heartButton] = await screen.findAllByText('🤍')

const tooltipPromise = waitFor(
() => {
const tooltip = screen.getByText('Add favorite')
return { tooltip, position: tooltip.getBoundingClientRect() }
},
{ interval: 0 },
)

fireEvent.focusIn(heartButton)

const { tooltip, position } = await tooltipPromise

await new Promise((resolve) => setTimeout(resolve, 250))
const newPosition = tooltip.getBoundingClientRect()

expect(
{ x: newPosition.x, y: newPosition.y },
'🚨 The tooltip is not correctly positioned initially. Use useLayoutEffect to prevent the incorrect position from being rendered.',
).toEqual({
x: position.x,
y: position.y,
})
})
36 changes: 36 additions & 0 deletions exercises/07.imperative-handle/01.solution.ref/scroll.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
import { expect, testStep, dtl } from '@epic-web/workshop-utils/test'
const { screen, fireEvent, waitFor } = dtl

import './index.tsx'

await testStep(
'Scrollable component handles scroll to top and bottom',
async () => {
// Find the scroll buttons
const scrollTopButton = await screen.findByText(/Scroll to Top/i)
const scrollBottomButton = await screen.findByText(/Scroll to Bottom/i)

// Find the scrollable container
const scrollableContainer = screen.getByRole('log')

// Scroll to bottom
fireEvent.click(scrollBottomButton)
await waitFor(() => {
expect(
scrollableContainer.scrollTop,
'🚨 Scrollable container should be scrolled to the bottom when the scroll to bottom button is clicked',
).toBe(
scrollableContainer.scrollHeight - scrollableContainer.clientHeight,
)
})

// Scroll to top
fireEvent.click(scrollTopButton)
await waitFor(() => {
expect(
scrollableContainer.scrollTop,
'🚨 Scrollable container should be scrolled to the top when the scroll to top button is clicked',
).toBe(0)
})
},
)
5 changes: 5 additions & 0 deletions exercises/08.focus/01.problem.flush-sync/README.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -29,3 +29,8 @@ test tab focus with. Good luck!
This example was uses code from
[trellix](https://github.com/remix-run/example-trellix/blob/3379b3d5e9c0173381031e4f062877e8a3696b2e/app/routes/board.%24id/components.tsx).
</callout-info>

<callout-warning>
🚨 Because this deals with focus, you'll need to expand the test and then run
it for it to pass.
</callout-warning>
90 changes: 90 additions & 0 deletions exercises/08.focus/01.solution.flush-sync/focus.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,90 @@
import { expect, testStep, dtl } from '@epic-web/workshop-utils/test'
const { screen, fireEvent, waitFor } = dtl

import './index.tsx'

await testStep('EditableText component renders', async () => {
const editButton = await screen.findByRole('button', {
name: /Edit project name/i,
})
expect(editButton).toBeTruthy()
return editButton
})

await testStep(
'Clicking edit button focuses input and selects text',
async () => {
const editButton = await screen.findByRole('button', {
name: /Edit project name/i,
})
fireEvent.click(editButton)

const input = screen.getByRole('textbox', { name: /Edit project name/i })
await waitFor(() => {
expect(
input,
'🚨 Input should be focused after clicking edit button',
).toHaveFocus()
if (!(input instanceof HTMLInputElement)) {
throw new Error('Input is not an HTMLInputElement')
}
expect(
input.selectionStart,
'🚨 Input text should be fully selected',
).toBe(0)
expect(input.selectionEnd, '🚨 Input text should be fully selected').toBe(
input.value.length,
)
})
return input
},
)

await testStep('Submitting form focuses button', async () => {
const input = await screen.findByRole('textbox', {
name: /Edit project name/i,
})
fireEvent.keyDown(input, { key: 'Enter' })
fireEvent.submit(input.closest('form')!)

await waitFor(() => {
const newButton = screen.getByRole('button', { name: /Edit project name/i })
expect(
newButton,
'🚨 Button should be focused after submitting',
).toHaveFocus()
})
})

await testStep('Canceling edit focuses button', async () => {
const editButton = screen.getByRole('button', { name: /Edit project name/i })
fireEvent.click(editButton)

const input = screen.getByRole('textbox', { name: /Edit project name/i })
fireEvent.keyDown(input, { key: 'Escape' })

await waitFor(() => {
const newButton = screen.getByRole('button', { name: /Edit project name/i })
expect(
newButton,
'🚨 Button should be focused after canceling',
).toHaveFocus()
})
})

await testStep('Blurring input focuses button', async () => {
const editButton = screen.getByRole('button', { name: /Edit project name/i })
fireEvent.click(editButton)

const input = screen.getByRole('textbox', { name: /Edit project name/i })
fireEvent.blur(input)
input.blur()

await waitFor(() => {
const newButton = screen.getByRole('button', { name: /Edit project name/i })
expect(
newButton,
'🚨 Button should be focused after blurring input',
).toHaveFocus()
})
})
6 changes: 5 additions & 1 deletion exercises/09.sync-external/01.problem.sub/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -22,4 +22,8 @@ function App() {

const rootEl = document.createElement('div')
document.body.append(rootEl)
ReactDOM.createRoot(rootEl).render(<App />)
const root = ReactDOM.createRoot(rootEl)
root.render(<App />)

// @ts-expect-error 🚨 this is for the test
window.__epicReactRoot = root
6 changes: 5 additions & 1 deletion exercises/09.sync-external/01.solution.sub/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -25,4 +25,8 @@ function App() {

const rootEl = document.createElement('div')
document.body.append(rootEl)
ReactDOM.createRoot(rootEl).render(<App />)
const root = ReactDOM.createRoot(rootEl)
root.render(<App />)

// @ts-expect-error 🚨 this is for the test
window.__epicReactRoot = root
Original file line number Diff line number Diff line change
@@ -0,0 +1,71 @@
import { expect, testStep, dtl } from '@epic-web/workshop-utils/test'
const { screen } = dtl

import './index.tsx'

let mediaQueryCallbacks: Array<(e: { matches: boolean }) => void> = []
let currentMatches = false

const originalMatchMedia = window.matchMedia
// @ts-expect-error - meh it's free javascript
window.matchMedia = (query: string) => ({
...originalMatchMedia(query),
matches: currentMatches,
media: query,
addEventListener: (
event: string,
callback: (e: { matches: boolean }) => void,
) => {
mediaQueryCallbacks.push(callback)
},
removeEventListener: (
event: string,
callback: (e: { matches: boolean }) => void,
) => {
mediaQueryCallbacks = mediaQueryCallbacks.filter((cb) => cb !== callback)
},
})

function triggerMediaQueryChange(matches: boolean) {
currentMatches = matches
mediaQueryCallbacks.forEach((callback) => callback({ matches }))
}

await testStep(
'NarrowScreenNotifier renders wide screen message initially',
async () => {
const message = await screen.findByText('You are on a wide screen')
expect(message).toBeTruthy()
},
)

await testStep(
'NarrowScreenNotifier updates when media query changes to narrow',
async () => {
triggerMediaQueryChange(true)
const message = await screen.findByText('You are on a narrow screen')
expect(message).toBeTruthy()
},
)

await testStep(
'NarrowScreenNotifier updates when media query changes back to wide',
async () => {
triggerMediaQueryChange(false)
const message = await screen.findByText('You are on a wide screen')
expect(message).toBeTruthy()
},
)

await testStep(
'NarrowScreenNotifier removes event listener on unmount',
async () => {
const initialCallbackCount = mediaQueryCallbacks.length
// @ts-expect-error 🚨 this is for the test
window.__epicReactRoot.unmount()
expect(mediaQueryCallbacks.length).toBe(initialCallbackCount - 1)
},
)

// Cleanup
window.matchMedia = originalMatchMedia
6 changes: 5 additions & 1 deletion exercises/09.sync-external/02.problem.util/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -34,4 +34,8 @@ function App() {

const rootEl = document.createElement('div')
document.body.append(rootEl)
ReactDOM.createRoot(rootEl).render(<App />)
const root = ReactDOM.createRoot(rootEl)
root.render(<App />)

// @ts-expect-error 🚨 this is for the test
window.__epicReactRoot = root
6 changes: 5 additions & 1 deletion exercises/09.sync-external/02.solution.util/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -32,4 +32,8 @@ function App() {

const rootEl = document.createElement('div')
document.body.append(rootEl)
ReactDOM.createRoot(rootEl).render(<App />)
const root = ReactDOM.createRoot(rootEl)
root.render(<App />)

// @ts-expect-error 🚨 this is for the test
window.__epicReactRoot = root
Original file line number Diff line number Diff line change
@@ -0,0 +1,71 @@
import { expect, testStep, dtl } from '@epic-web/workshop-utils/test'
const { screen } = dtl

import './index.tsx'

let mediaQueryCallbacks: Array<(e: { matches: boolean }) => void> = []
let currentMatches = false

const originalMatchMedia = window.matchMedia
// @ts-expect-error - meh it's free javascript
window.matchMedia = (query: string) => ({
...originalMatchMedia(query),
matches: currentMatches,
media: query,
addEventListener: (
event: string,
callback: (e: { matches: boolean }) => void,
) => {
mediaQueryCallbacks.push(callback)
},
removeEventListener: (
event: string,
callback: (e: { matches: boolean }) => void,
) => {
mediaQueryCallbacks = mediaQueryCallbacks.filter((cb) => cb !== callback)
},
})

function triggerMediaQueryChange(matches: boolean) {
currentMatches = matches
mediaQueryCallbacks.forEach((callback) => callback({ matches }))
}

await testStep(
'NarrowScreenNotifier renders wide screen message initially',
async () => {
const message = await screen.findByText('You are on a wide screen')
expect(message).toBeTruthy()
},
)

await testStep(
'NarrowScreenNotifier updates when media query changes to narrow',
async () => {
triggerMediaQueryChange(true)
const message = await screen.findByText('You are on a narrow screen')
expect(message).toBeTruthy()
},
)

await testStep(
'NarrowScreenNotifier updates when media query changes back to wide',
async () => {
triggerMediaQueryChange(false)
const message = await screen.findByText('You are on a wide screen')
expect(message).toBeTruthy()
},
)

await testStep(
'NarrowScreenNotifier removes event listener on unmount',
async () => {
const initialCallbackCount = mediaQueryCallbacks.length
// @ts-expect-error 🚨 this is for the test
window.__epicReactRoot.unmount()
expect(mediaQueryCallbacks.length).toBe(initialCallbackCount - 1)
},
)

// Cleanup
window.matchMedia = originalMatchMedia
5 changes: 4 additions & 1 deletion exercises/09.sync-external/03.problem.ssr/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -45,8 +45,11 @@ rootEl.innerHTML = (await import('react-dom/server')).renderToString(<App />)
// 🦉 here's how we simulate a delay in hydrating with client-side js
await new Promise((resolve) => setTimeout(resolve, 1000))

ReactDOM.hydrateRoot(rootEl, <App />, {
const root = ReactDOM.hydrateRoot(rootEl, <App />, {
// 💯 if you want to silence the error add a onRecoverableError function here
// and if the error includes 'Missing getServerSnapshot' then return early
// otherwise log the error so you don't miss any other errors.
})

// @ts-expect-error 🚨 this is for the test
window.__epicReactRoot = root
Empty file.
5 changes: 4 additions & 1 deletion exercises/09.sync-external/03.solution.ssr/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -45,10 +45,13 @@ rootEl.innerHTML = (await import('react-dom/server')).renderToString(<App />)
// 🦉 here's how we simulate a delay in hydrating with client-side js
await new Promise((resolve) => setTimeout(resolve, 1000))

ReactDOM.hydrateRoot(rootEl, <App />, {
const root = ReactDOM.hydrateRoot(rootEl, <App />, {
onRecoverableError(error) {
if (String(error).includes('Missing getServerSnapshot')) return

console.error(error)
},
})

// @ts-expect-error 🚨 this is for the test
window.__epicReactRoot = root
Loading

0 comments on commit d2eaccf

Please sign in to comment.