Files
nuzlocke-tracker/frontend/src/components/GameGrid.test.tsx
Julian Tabel 9aaa95a1c7 Add component tests for EndRunModal, GameGrid, RulesConfiguration, Layout
33 tests covering rendering, user interactions (userEvent clicks), prop
callbacks, filter state, and conditional description text. Adds a
matchMedia stub to the vitest setup file so components importing
useTheme don't throw in jsdom. Also adds actionlint and zizmor
pre-commit hooks for GitHub Actions linting.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-21 13:57:12 +01:00

116 lines
3.5 KiB
TypeScript

import { render, screen } from '@testing-library/react'
import userEvent from '@testing-library/user-event'
import type { Game } from '../types'
import { GameGrid } from './GameGrid'
const RED: Game = {
id: 1,
name: 'Pokemon Red',
slug: 'red',
generation: 1,
region: 'kanto',
category: null,
boxArtUrl: null,
color: null,
releaseYear: null,
versionGroupId: 1,
}
const GOLD: Game = {
id: 2,
name: 'Pokemon Gold',
slug: 'gold',
generation: 2,
region: 'johto',
category: null,
boxArtUrl: null,
color: null,
releaseYear: null,
versionGroupId: 2,
}
const RUBY: Game = {
id: 3,
name: 'Pokemon Ruby',
slug: 'ruby',
generation: 3,
region: 'hoenn',
category: null,
boxArtUrl: null,
color: null,
releaseYear: null,
versionGroupId: 3,
}
function setup(overrides: Partial<React.ComponentProps<typeof GameGrid>> = {}) {
const props = {
games: [RED, GOLD, RUBY],
selectedId: null,
onSelect: vi.fn(),
...overrides,
}
render(<GameGrid {...props} />)
return props
}
describe('GameGrid', () => {
it('renders all game names', () => {
setup()
expect(screen.getByText('Pokemon Red')).toBeInTheDocument()
expect(screen.getByText('Pokemon Gold')).toBeInTheDocument()
expect(screen.getByText('Pokemon Ruby')).toBeInTheDocument()
})
it('renders generation filter pills for each unique generation', () => {
setup()
expect(screen.getByRole('button', { name: 'Gen 1' })).toBeInTheDocument()
expect(screen.getByRole('button', { name: 'Gen 2' })).toBeInTheDocument()
expect(screen.getByRole('button', { name: 'Gen 3' })).toBeInTheDocument()
})
it('filters games when a generation pill is clicked', async () => {
setup()
await userEvent.click(screen.getByRole('button', { name: 'Gen 1' }))
expect(screen.getByText('Pokemon Red')).toBeInTheDocument()
expect(screen.queryByText('Pokemon Gold')).not.toBeInTheDocument()
expect(screen.queryByText('Pokemon Ruby')).not.toBeInTheDocument()
})
it('restores all games when "All" generation pill is clicked', async () => {
setup()
await userEvent.click(screen.getByRole('button', { name: 'Gen 1' }))
await userEvent.click(screen.getAllByRole('button', { name: 'All' })[0]!)
expect(screen.getByText('Pokemon Red')).toBeInTheDocument()
expect(screen.getByText('Pokemon Gold')).toBeInTheDocument()
expect(screen.getByText('Pokemon Ruby')).toBeInTheDocument()
})
it('filters games when a region pill is clicked', async () => {
setup()
await userEvent.click(screen.getByRole('button', { name: 'Johto' }))
expect(screen.queryByText('Pokemon Red')).not.toBeInTheDocument()
expect(screen.getByText('Pokemon Gold')).toBeInTheDocument()
expect(screen.queryByText('Pokemon Ruby')).not.toBeInTheDocument()
})
it('calls onSelect with the game when a game card is clicked', async () => {
const { onSelect } = setup()
await userEvent.click(screen.getByText('Pokemon Red'))
expect(onSelect).toHaveBeenCalledWith(RED)
})
it('hides games with active runs when the checkbox is ticked', async () => {
setup({
runs: [{ id: 10, gameId: 1, status: 'active' } as never],
})
await userEvent.click(screen.getByLabelText(/hide games with active run/i))
expect(screen.queryByText('Pokemon Red')).not.toBeInTheDocument()
expect(screen.getByText('Pokemon Gold')).toBeInTheDocument()
})
it('does not render run-based checkboxes when runs prop is omitted', () => {
setup({ runs: undefined })
expect(screen.queryByLabelText(/hide games with active run/i)).not.toBeInTheDocument()
})
})