Skip to content

Commit

Permalink
finish all tests
Browse files Browse the repository at this point in the history
  • Loading branch information
kentcdodds committed Aug 23, 2024
1 parent 7f3a506 commit 8adeb30
Show file tree
Hide file tree
Showing 4 changed files with 273 additions and 0 deletions.
Original file line number Diff line number Diff line change
@@ -0,0 +1,93 @@
import { expect, testStep, dtl } from '@epic-web/workshop-utils/test'
const { screen, fireEvent, waitFor } = dtl

await import('./index.tsx')

function getSquares() {
return waitFor(() => {
const squares = document.querySelectorAll('button.square')
expect(squares).toHaveLength(9)
return squares
})
}

await testStep('Initial board state', getSquares)

const statusElement = await testStep('Find status element', async () => {
const status = await screen.findByText(/Next player: X/)
expect(status).toBeInTheDocument()
return status
})

await testStep('Play a game', async () => {
const squares = await getSquares()

// X plays
fireEvent.click(squares[0])
await waitFor(() => {
expect(squares[0]).toHaveTextContent('X')
})
expect(statusElement).toHaveTextContent('Next player: O')

// O plays
fireEvent.click(squares[4])
await waitFor(() => {
expect(squares[4]).toHaveTextContent('O')
})
expect(statusElement).toHaveTextContent('Next player: X')

// X plays
fireEvent.click(squares[1])
// O plays
fireEvent.click(squares[5])
// X plays and wins
fireEvent.click(squares[2])

await waitFor(() => {
expect(statusElement).toHaveTextContent('Winner: X')
})
})

await testStep('Restart game', async () => {
const restartButton = await screen.findByRole('button', { name: /restart/i })
fireEvent.click(restartButton)

await waitFor(async () => {
const squares = await getSquares()
expect(squares).toHaveLength(9)
expect(statusElement).toHaveTextContent('Next player: X')
})
})

await testStep('Cannot play on occupied square', async () => {
const squares = await getSquares()

fireEvent.click(squares[0])
await waitFor(() => {
expect(squares[0]).toHaveTextContent('X')
})

fireEvent.click(squares[0])
await waitFor(() => {
expect(squares[0]).toHaveTextContent('X')
expect(statusElement).toHaveTextContent('Next player: O')
})
})

await testStep('Game ends in a draw', async () => {
const restartButton = await screen.findByRole('button', { name: /restart/i })
fireEvent.click(restartButton)
await new Promise(resolve => setTimeout(resolve, 10))

const squares = await getSquares()
const moves = [0, 1, 2, 4, 3, 5, 7, 6, 8]

for (const move of moves) {
fireEvent.click(squares[move])
await new Promise(resolve => setTimeout(resolve, 10))
}

await waitFor(() => {
expect(statusElement).toHaveTextContent(`Cat's game`)
})
})
5 changes: 5 additions & 0 deletions exercises/06.tic-tac-toe/02.problem.local-storage/README.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -15,3 +15,8 @@ For keeping the squares up-to-date in `localStorage`, you'll want to use

📜 If you need to learn a bit about the `localStorage` API, you can check out the
[MDN documentation](https://developer.mozilla.org/en-US/docs/Web/API/Window/localStorage).

<callout-warning>
🚨 Note this exercise depends on `localStorage` and so the tests could
interfer with your work by changing the `localStorage` you're working with.
</callout-warning>
Original file line number Diff line number Diff line change
@@ -0,0 +1,69 @@
import { expect, testStep, dtl } from '@epic-web/workshop-utils/test'
const { screen, fireEvent, waitFor } = dtl

const localStorageKey = 'squares'
const initialState = ['X', null, 'O', null, 'X', null, null, null, null]
window.localStorage.setItem(localStorageKey, JSON.stringify(initialState))

// Dynamically import the game component
await import('./index.tsx')

await testStep('Game initializes from localStorage', async () => {
await waitFor(() => {
const squares = document.querySelectorAll('button.square')
expect(squares[0]).toHaveTextContent('X')
expect(squares[2]).toHaveTextContent('O')
expect(squares[4]).toHaveTextContent('X')
})
})

await testStep('Game updates localStorage after a move', async () => {
// Make a move
const squares = document.querySelectorAll('button.square')
fireEvent.click(squares[1])

// Verify localStorage is updated
await waitFor(() => {
const storedState = JSON.parse(
window.localStorage.getItem(localStorageKey) || '[]',
)
expect(storedState).toEqual([
'X',
'O',
'O',
null,
'X',
null,
null,
null,
null,
])
})
})

await testStep('Restart button clears localStorage', async () => {
const restartButton = await screen.findByRole('button', { name: /restart/i })
fireEvent.click(restartButton)

// Check if localStorage is cleared
await waitFor(() => {
const storedState = JSON.parse(
window.localStorage.getItem(localStorageKey) || '[]',
)
expect(storedState).toEqual([
null,
null,
null,
null,
null,
null,
null,
null,
null,
])
})

// Check if the board is reset
const squares = document.querySelectorAll('button.square')
expect(squares).toHaveLength(9)
})
106 changes: 106 additions & 0 deletions exercises/06.tic-tac-toe/03.solution.history/local-storage.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,106 @@
import { expect, testStep, dtl } from '@epic-web/workshop-utils/test'
const { screen, fireEvent, waitFor } = dtl

const localStorageKey = 'tic-tac-toe'
const initialState = {
history: [['X', null, 'O', null, 'X', null, null, null, null]],
currentStep: 0,
}
window.localStorage.setItem(localStorageKey, JSON.stringify(initialState))

// Dynamically import the game component
await import('./index.tsx')

function getSquares() {
return waitFor(() => {
const squares = document.querySelectorAll('button.square')
expect(squares).toHaveLength(9)
return squares
})
}

await testStep('Game initializes from localStorage', async () => {
await waitFor(async () => {
const squares = await getSquares()
expect(squares[0]).toHaveTextContent('X')
expect(squares[2]).toHaveTextContent('O')
expect(squares[4]).toHaveTextContent('X')
})
})

await testStep('Game updates localStorage after a move', async () => {
// Make a move
const squares = await getSquares()
fireEvent.click(squares[1])

// Verify localStorage is updated
await waitFor(() => {
const storedState = JSON.parse(
window.localStorage.getItem(localStorageKey) || '{}',
)
expect(storedState.history).toHaveLength(2)
expect(storedState.currentStep).toBe(1)
expect(storedState.history[1]).toEqual([
'X',
'O',
'O',
null,
'X',
null,
null,
null,
null,
])
})
})

await testStep('Adding another move', async () => {
const squares = await getSquares()
fireEvent.click(squares[5])
await new Promise(resolve => setTimeout(resolve, 100))
})

await testStep('Game history allows going back to previous moves', async () => {
// Go back to the first move
const moveButtons = screen.getAllByRole('button', { name: /Go to move/i })
fireEvent.click(moveButtons[0])

// Verify the board state
await waitFor(async () => {
const squares = await getSquares()
expect(squares[0]).toHaveTextContent('X')
expect(squares[1]).toHaveTextContent('O')
expect(squares[2]).toHaveTextContent('O')
expect(squares[4]).toHaveTextContent('X')
})

// Verify localStorage is updated
const storedState = JSON.parse(
window.localStorage.getItem(localStorageKey) || '{}',
)
expect(storedState.currentStep).toBe(1)
})

await testStep('Restart button clears game history', async () => {
const restartButton = await screen.findByRole('button', { name: /restart/i })
fireEvent.click(restartButton)

// Check if localStorage is reset
await waitFor(() => {
const storedState = JSON.parse(
window.localStorage.getItem(localStorageKey) || '{}',
)
expect(storedState).toEqual({
history: [Array(9).fill(null)],
currentStep: 0,
})
})

// Check if the board is reset
const squares = await getSquares()
squares.forEach(square => expect(square).toHaveTextContent(''))

// Check if move history is cleared
const moveButtons = screen.queryAllByRole('button', { name: /Go to/i })
expect(moveButtons).toHaveLength(1) // Only "Go to game start" should remain
})

0 comments on commit 8adeb30

Please sign in to comment.