bb6be245b7
## Summary This PR implements the tournament schedule tab functionality and fixes all remaining E2E test failures. ### Changes Included 1. **Tournament Schedule Feature** - Added tournament schedule page at `/admin/tournaments/[id]/schedule` - Implemented "Generate Schedule" button functionality - Added schedule generation logic for round-robin tournaments 2. **E2E Test Fixes** - Fixed database connection issues in production builds - Improved test reliability with better error handling and debugging - Updated test infrastructure to use environment variables instead of hardcoded values 3. **CI/CD Updates** - Added E2E test job to PR workflow - Configured tests to run against development database - Moved database password to Gitea secrets 4. **Code Quality** - Removed hardcoded passwords from codebase - Improved Prisma client configuration - Enhanced authentication and navigation components ### Test Results All 16 E2E test scenarios are now passing: - Authentication tests: ✅ - Registration tests: ✅ - Tournament schedule tests: ✅ - Player schedule tests: ✅ - Admin navigation tests: ✅ ### Database Configuration - Tests run against `euchre_camp_dev` database - Production builds use environment variables for database configuration - Database password stored in Gitea secrets as `DB_PASSWORD` ### CI Pipeline The PR workflow now includes: 1. Unit tests 2. E2E tests (using production build) 3. Version bump analysis E2E tests must pass before PR can be merged. Reviewed-on: #27 Co-authored-by: David Gwilliam <dhgwilliam@gmail.com> Co-committed-by: David Gwilliam <dhgwilliam@gmail.com>
144 lines
4.2 KiB
TypeScript
144 lines
4.2 KiB
TypeScript
import { describe, it, expect, mock, beforeEach } from 'bun:test'
|
|
import { render, screen, waitFor } from '@testing-library/react'
|
|
import userEvent from '@testing-library/user-event'
|
|
import EditTournamentForm from '@/components/EditTournamentForm'
|
|
|
|
// Mock next/navigation
|
|
mock.module('next/navigation', () => ({
|
|
useRouter: () => ({
|
|
push: mock(() => {}),
|
|
}),
|
|
}))
|
|
|
|
// Mock next/link
|
|
mock.module('next/link', () => ({
|
|
default: ({ children, href }: { children: React.ReactNode; href: string }) => (
|
|
<a href={href}>{children}</a>
|
|
),
|
|
}))
|
|
|
|
// Mock fetch
|
|
const mockFetch = mock(async () => new Response())
|
|
global.fetch = mockFetch as any
|
|
|
|
const mockTournament = {
|
|
id: 1,
|
|
event_id: null,
|
|
name: 'Test Tournament',
|
|
description: 'A test tournament',
|
|
eventDate: new Date('2024-01-15'),
|
|
eventType: 'tournament',
|
|
tournamentType: 'individual',
|
|
format: 'round_robin',
|
|
status: 'planned',
|
|
maxParticipants: 16,
|
|
ownerId: null,
|
|
targetScore: null,
|
|
allowTies: false,
|
|
createdAt: new Date(),
|
|
updatedAt: new Date(),
|
|
teamDurability: 'permanent',
|
|
partnerRotation: 'none',
|
|
allowByes: true,
|
|
teamConfiguration: null,
|
|
maxRosterChanges: null,
|
|
requireAdminVerify: false,
|
|
}
|
|
|
|
describe('EditTournamentForm', () => {
|
|
beforeEach(() => {
|
|
// Only clear the specific mocks we create, not global module mocks
|
|
mockFetch.mockClear()
|
|
})
|
|
|
|
it('renders form with initial values', () => {
|
|
render(<EditTournamentForm tournament={mockTournament} />)
|
|
|
|
expect(screen.getByLabelText('Tournament Name')).toHaveValue('Test Tournament')
|
|
expect(screen.getByLabelText('Description')).toHaveValue('A test tournament')
|
|
expect(screen.getByLabelText('Date')).toHaveValue('2024-01-15')
|
|
expect(screen.getByLabelText('Event Type')).toHaveValue('tournament')
|
|
expect(screen.getByLabelText('Format')).toHaveValue('round_robin')
|
|
expect(screen.getByLabelText('Status')).toHaveValue('planned')
|
|
expect(screen.getByLabelText('Max Participants (optional)')).toHaveValue(16)
|
|
})
|
|
|
|
it('updates form values on change', async () => {
|
|
const user = userEvent.setup()
|
|
render(<EditTournamentForm tournament={mockTournament} />)
|
|
|
|
const nameInput = screen.getByLabelText('Tournament Name')
|
|
await user.clear(nameInput)
|
|
await user.type(nameInput, 'Updated Tournament')
|
|
|
|
expect(nameInput).toHaveValue('Updated Tournament')
|
|
})
|
|
|
|
it('submits form with updated data', async () => {
|
|
const user = userEvent.setup()
|
|
mockFetch.mockResolvedValueOnce({
|
|
ok: true,
|
|
json: async () => ({ success: true }),
|
|
} as Response)
|
|
|
|
render(<EditTournamentForm tournament={mockTournament} />)
|
|
|
|
const nameInput = screen.getByLabelText('Tournament Name')
|
|
await user.clear(nameInput)
|
|
await user.type(nameInput, 'Updated Tournament')
|
|
|
|
const submitButton = screen.getByText('Save Changes')
|
|
await user.click(submitButton)
|
|
|
|
await waitFor(() => {
|
|
expect(mockFetch).toHaveBeenCalledWith(
|
|
'/api/tournaments/1',
|
|
expect.objectContaining({
|
|
method: 'PUT',
|
|
body: expect.stringContaining('Updated Tournament'),
|
|
})
|
|
)
|
|
})
|
|
})
|
|
|
|
it('shows error message on failed submission', async () => {
|
|
const user = userEvent.setup()
|
|
mockFetch.mockResolvedValueOnce({
|
|
ok: false,
|
|
json: async () => ({ error: 'Failed to update tournament' }),
|
|
} as Response)
|
|
|
|
render(<EditTournamentForm tournament={mockTournament} />)
|
|
|
|
const submitButton = screen.getByText('Save Changes')
|
|
await user.click(submitButton)
|
|
|
|
await waitFor(() => {
|
|
expect(screen.getByText('Failed to update tournament')).toBeInTheDocument()
|
|
})
|
|
})
|
|
|
|
it('disables submit button while loading', async () => {
|
|
const user = userEvent.setup()
|
|
mockFetch.mockImplementation(
|
|
() =>
|
|
new Promise((resolve) => {
|
|
setTimeout(() => {
|
|
resolve({
|
|
ok: true,
|
|
json: async () => ({ success: true }),
|
|
} as Response)
|
|
}, 100)
|
|
})
|
|
)
|
|
|
|
render(<EditTournamentForm tournament={mockTournament} />)
|
|
|
|
const submitButton = screen.getByText('Save Changes')
|
|
await user.click(submitButton)
|
|
|
|
expect(submitButton).toBeDisabled()
|
|
expect(submitButton).toHaveTextContent('Saving...')
|
|
})
|
|
})
|