-
Notifications
You must be signed in to change notification settings - Fork 1.9k
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
- Loading branch information
1 parent
7f3a506
commit 8adeb30
Showing
4 changed files
with
273 additions
and
0 deletions.
There are no files selected for viewing
93 changes: 93 additions & 0 deletions
93
exercises/06.tic-tac-toe/01.solution.set-state-callback/board-game.test.ts
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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`) | ||
}) | ||
}) |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
69 changes: 69 additions & 0 deletions
69
exercises/06.tic-tac-toe/02.solution.local-storage/local-storage.test.ts
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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
106
exercises/06.tic-tac-toe/03.solution.history/local-storage.test.ts
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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 | ||
}) |