@jeremyckahn/farmhand
Version:
A farming game
332 lines (262 loc) • 10.1 kB
JavaScript
import React from 'react'
import { render, screen } from '@testing-library/react'
import userEvent from '@testing-library/user-event'
import { vi } from 'vitest'
import { dialogView, stageFocusType } from '../../enums.js'
import { INFINITE_STORAGE_LIMIT } from '../../constants.js'
import { noop } from '../../utils/noop.js'
import FarmhandContext, {
createContextData,
} from '../Farmhand/Farmhand.context.js'
import { Navigation } from './Navigation.js'
const defaultProps = {
activePlayers: null,
blockInput: false,
currentDialogView: dialogView.NONE,
dayCount: 0,
farmName: 'Test Farm',
handleActivePlayerButtonClick: noop,
handleChatRoomOpenStateChange: noop,
handleClickDialogViewButton: noop,
handleCloseDialogView: noop,
handleDialogViewExited: noop,
handleFarmNameUpdate: noop,
handleOnlineToggleChange: noop,
handleRoomChange: noop,
handleViewChange: noop,
inventory: [],
inventoryLimit: INFINITE_STORAGE_LIMIT,
itemsSold: {},
isChatAvailable: false,
isDialogViewOpen: false,
isOnline: false,
room: '',
stageFocus: stageFocusType.FIELD,
viewList: ['HOME', 'FIELD', 'SHOP', 'WORKSHOP'],
}
const renderWithContext = (props = {}, gameState = {}) => {
const contextValue = createContextData()
contextValue.gameState = {
...contextValue.gameState,
dayCount: 0,
experience: 100,
itemsSold: {},
...gameState,
}
return render(
<FarmhandContext.Provider value={contextValue}>
<Navigation {...defaultProps} {...props} />
</FarmhandContext.Provider>
)
}
describe('Navigation', () => {
test('renders', () => {
renderWithContext()
expect(document.querySelector('.Navigation')).toBeInTheDocument()
})
test('displays navigation menu items', async () => {
const user = userEvent.setup()
renderWithContext()
// Open the dropdown to access menu items
const selectElement = screen.getByRole('combobox')
await user.click(selectElement)
expect(screen.getAllByText('1: Home')[0]).toBeInTheDocument()
expect(screen.getAllByText('2: Field')[0]).toBeInTheDocument()
expect(screen.getAllByText('3: Shop')[0]).toBeInTheDocument()
expect(screen.getAllByText('4: Workshop')[0]).toBeInTheDocument()
})
test('calls handleViewChange when menu item is clicked', async () => {
const user = userEvent.setup()
const handleViewChange = vitest.fn()
renderWithContext({ handleViewChange })
// First open the dropdown
const selectElement = screen.getByRole('combobox')
await user.click(selectElement)
const shopMenuItem = screen.getByRole('option', { name: '3: Shop' })
await user.click(shopMenuItem)
expect(handleViewChange).toHaveBeenCalledWith(
expect.objectContaining({
target: expect.objectContaining({ value: stageFocusType.SHOP }),
}),
expect.anything()
)
})
test('highlights current stage focus', () => {
renderWithContext({ stageFocus: stageFocusType.SHOP })
// Material-UI Select shows the current value in the displayed text
expect(screen.getByDisplayValue('SHOP')).toBeInTheDocument()
})
describe('cow pen option', () => {
test('does not show if player has not bought a cow pen', async () => {
const user = userEvent.setup()
renderWithContext()
// Open dropdown to check available options
const selectElement = screen.getByRole('combobox')
await user.click(selectElement)
expect(screen.queryByText(/Cows/)).not.toBeInTheDocument()
})
test('does show if player has bought a cow pen', async () => {
const user = userEvent.setup()
const propsWithCowPen = {
...defaultProps,
viewList: ['HOME', 'FIELD', 'SHOP', 'COW_PEN', 'WORKSHOP'],
}
renderWithContext(propsWithCowPen)
// Open dropdown to see all options
const selectElement = screen.getByRole('combobox')
await user.click(selectElement)
expect(screen.getByText('4: Cows')).toBeInTheDocument()
})
test('allows navigation to cow pen when available', async () => {
const user = userEvent.setup()
const handleViewChange = vitest.fn()
const propsWithCowPen = {
...defaultProps,
viewList: ['HOME', 'FIELD', 'SHOP', 'COW_PEN', 'WORKSHOP'],
handleViewChange,
}
renderWithContext(propsWithCowPen)
// Open dropdown first
const selectElement = screen.getByRole('combobox')
await user.click(selectElement)
const cowPenMenuItem = screen.getByRole('option', { name: '4: Cows' })
await user.click(cowPenMenuItem)
expect(handleViewChange).toHaveBeenCalledWith(
expect.objectContaining({
target: expect.objectContaining({ value: stageFocusType.COW_PEN }),
}),
expect.anything()
)
})
})
describe('forest option', () => {
test('does not show if player has not bought forest access', async () => {
const user = userEvent.setup()
renderWithContext()
// Open dropdown to check available options
const selectElement = screen.getByRole('combobox')
await user.click(selectElement)
expect(screen.queryByText(/Forest/)).not.toBeInTheDocument()
})
test('shows forest option when available in viewList', async () => {
const user = userEvent.setup()
const propsWithForest = {
...defaultProps,
viewList: ['HOME', 'FIELD', 'SHOP', 'FOREST', 'WORKSHOP'],
}
renderWithContext(propsWithForest)
// Open dropdown to see all options
const selectElement = screen.getByRole('combobox')
await user.click(selectElement)
expect(screen.getByText('4: Forest')).toBeInTheDocument()
})
})
describe('cellar option', () => {
test('does not show if player has not bought cellar access', async () => {
const user = userEvent.setup()
render(<Navigation {...defaultProps} />)
// Open dropdown to check available options
const selectElement = screen.getByRole('combobox')
await user.click(selectElement)
expect(screen.queryByText(/Cellar/)).not.toBeInTheDocument()
})
test('shows cellar option when available in viewList', async () => {
const user = userEvent.setup()
const propsWithCellar = {
...defaultProps,
viewList: ['HOME', 'FIELD', 'SHOP', 'CELLAR', 'WORKSHOP'],
}
renderWithContext(propsWithCellar)
// Open dropdown to see all options
const selectElement = screen.getByRole('combobox')
await user.click(selectElement)
expect(screen.getByText('4: Cellar')).toBeInTheDocument()
})
})
test('displays farm name', () => {
render(<Navigation {...defaultProps} farmName="My Test Farm" />)
const farmNameInput = document.querySelector('.farm-name input')
expect(farmNameInput).toHaveValue('My Test Farm')
})
test('displays day count', () => {
renderWithContext({}, { dayCount: 42 })
expect(screen.getByText(/Day.*42/)).toBeInTheDocument()
})
test('handles view change for all available views', async () => {
const user = userEvent.setup()
const handleViewChange = vi.fn()
const allViewsProps = {
...defaultProps,
viewList: ['HOME', 'FIELD', 'SHOP', 'WORKSHOP'],
handleViewChange,
}
renderWithContext(allViewsProps)
// Open dropdown to access menu items
const selectElement = screen.getByRole('combobox')
await user.click(selectElement)
// Test each view - just verify handleViewChange is called for each option
await user.click(screen.getByRole('option', { name: '1: Home' }))
// Reopen dropdown for next selection
await user.click(selectElement)
await user.click(screen.getByRole('option', { name: '2: Field' }))
// Reopen dropdown for next selection
await user.click(selectElement)
await user.click(screen.getByRole('option', { name: '3: Shop' }))
// Reopen dropdown for next selection
await user.click(selectElement)
await user.click(screen.getByRole('option', { name: '4: Workshop' }))
// Verify handleViewChange was called for the selections that worked
expect(handleViewChange).toHaveBeenCalledTimes(3)
})
test('blocks input when blockInput prop is true', () => {
renderWithContext({ blockInput: true, isDialogViewOpen: true })
const dialogElement = document.querySelector('.Farmhand')
expect(dialogElement).toHaveClass('block-input')
})
test('does not block input when blockInput prop is false', () => {
renderWithContext({ blockInput: false })
const navigation = document.querySelector('.Navigation')
expect(navigation).not.toHaveClass('block-input')
})
test('handles online state changes', async () => {
const user = userEvent.setup()
const handleOnlineToggleChange = vitest.fn()
renderWithContext({
handleOnlineToggleChange,
isChatAvailable: true,
})
// Look for online toggle switch or button
const onlineToggle = document.querySelector('[role="switch"]')
if (onlineToggle) {
await user.click(onlineToggle)
expect(handleOnlineToggleChange).toHaveBeenCalled()
}
})
test('displays online status when chat is available', () => {
renderWithContext({
isChatAvailable: true,
isOnline: true,
activePlayers: 5,
})
expect(screen.getByText(/Play online/)).toBeInTheDocument()
})
test('handles dialog view opening', async () => {
const user = userEvent.setup()
const handleClickDialogViewButton = vitest.fn()
renderWithContext({
handleClickDialogViewButton,
})
// Look for settings or dialog buttons
const dialogButton =
screen.queryByLabelText(/settings/i) || screen.queryByText(/settings/i)
if (dialogButton) {
await user.click(dialogButton)
expect(handleClickDialogViewButton).toHaveBeenCalled()
}
})
test('renders with minimal props', () => {
renderWithContext()
expect(document.querySelector('.Navigation')).toBeInTheDocument()
expect(screen.getByText('Farmhand')).toBeInTheDocument()
})
})