diff --git a/src/__tests__/e2e/tournament-edit-allowTies.test.ts b/src/__tests__/e2e/tournament-edit-allowTies.test.ts new file mode 100644 index 0000000..326fada --- /dev/null +++ b/src/__tests__/e2e/tournament-edit-allowTies.test.ts @@ -0,0 +1,117 @@ +/** + * E2E Test: Tournament Edit with allowTies + * + * User Story: As a tournament admin, I want to edit tournament settings including allowTies + * + * Acceptance Criteria: + * - Can edit tournament settings + * - allowTies checkbox can be toggled + * - allowTies value is saved correctly + */ + +import { test, expect } from '@playwright/test'; +import { prisma } from '@/lib/prisma'; + +test.describe('Tournament Edit - allowTies functionality', () => { + let tournamentId: number; + + test.beforeAll(async () => { + // Create a test tournament for editing + const tournament = await prisma.event.create({ + data: { + name: 'Test Tournament for AllowTies Edit', + eventType: 'tournament', + format: 'round_robin', + status: 'planned', + allowTies: false, + targetScore: 5, + createdAt: new Date(), + updatedAt: new Date(), + }, + }); + tournamentId = tournament.id; + }); + + test.afterAll(async () => { + // Clean up test tournament + if (tournamentId) { + await prisma.event.delete({ + where: { id: tournamentId }, + }); + } + }); + + test('should display allowTies checkbox on edit form', async ({ page }) => { + // Navigate to tournament edit page + await page.goto(`/admin/tournaments/${tournamentId}/edit`); + + // Wait for form to load + await expect(page.locator('text=Tournament Name')).toBeVisible(); + + // Check that allowTies checkbox exists + const allowTiesCheckbox = page.locator('input[name="allowTies"]'); + await expect(allowTiesCheckbox).toBeVisible(); + await expect(allowTiesCheckbox).not.toBeChecked(); + }); + + test('should save allowTies when toggled to true', async ({ page }) => { + // Navigate to tournament edit page + await page.goto(`/admin/tournaments/${tournamentId}/edit`); + + // Wait for form to load + await expect(page.locator('text=Tournament Name')).toBeVisible(); + + // Toggle allowTies checkbox + const allowTiesCheckbox = page.locator('input[name="allowTies"]'); + await allowTiesCheckbox.check(); + await expect(allowTiesCheckbox).toBeChecked(); + + // Submit form + await page.click('button[type="submit"]'); + + // Wait for success message + await expect(page.locator('text=Tournament updated successfully')).toBeVisible({ timeout: 5000 }); + + // Verify allowTies was saved by checking the database + const updatedTournament = await prisma.event.findUnique({ + where: { id: tournamentId }, + }); + + expect(updatedTournament?.allowTies).toBe(true); + }); + + test('should save allowTies when toggled to false', async ({ page }) => { + // First, set allowTies to true + await prisma.event.update({ + where: { id: tournamentId }, + data: { allowTies: true }, + }); + + // Navigate to tournament edit page + await page.goto(`/admin/tournaments/${tournamentId}/edit`); + + // Wait for form to load + await expect(page.locator('text=Tournament Name')).toBeVisible(); + + // Verify checkbox is checked + const allowTiesCheckbox = page.locator('input[name="allowTies"]'); + await expect(allowTiesCheckbox).toBeChecked(); + + // Uncheck allowTies checkbox + await allowTiesCheckbox.uncheck(); + await expect(allowTiesCheckbox).not.toBeChecked(); + + // Submit form + await page.click('button[type="submit"]'); + + // Wait for success message + await expect(page.locator('text=Tournament updated successfully')).toBeVisible({ timeout: 5000 }); + + // Verify allowTies was saved by checking the database + const updatedTournament = await prisma.event.findUnique({ + where: { id: tournamentId }, + }); + + expect(updatedTournament?.allowTies).toBe(false); + }); +}); diff --git a/src/__tests__/unit/tournament-update.test.ts b/src/__tests__/unit/tournament-update.test.ts new file mode 100644 index 0000000..7448c78 --- /dev/null +++ b/src/__tests__/unit/tournament-update.test.ts @@ -0,0 +1,200 @@ +/** + * Unit tests for tournament update functionality + * Tests the allowTies field is properly saved when updating tournaments + */ + +import { describe, it, expect, vi, beforeEach } from 'vitest'; +import { prisma } from '@/lib/prisma'; + +// Mock the prisma client +vi.mock('@/lib/prisma', () => ({ + prisma: { + event: { + findUnique: vi.fn(), + update: vi.fn(), + }, + }, +})); + +// Mock the permissions module +vi.mock('@/lib/permissions', () => ({ + canManageTournament: vi.fn().mockResolvedValue({ allowed: true }), + canDeleteTournament: vi.fn().mockResolvedValue({ allowed: true }), +})); + +// Import the route handler after mocking +import { PUT } from '@/app/api/tournaments/[id]/route'; + +describe('Tournament Update API', () => { + beforeEach(() => { + vi.clearAllMocks(); + }); + + it('should update allowTies field when provided', async () => { + // Mock existing tournament + vi.mocked(prisma.event.findUnique).mockResolvedValue({ + id: 1, + name: 'Test Tournament', + allowTies: false, + targetScore: 5, + eventType: 'tournament', + format: 'round_robin', + status: 'planned', + eventDate: null, + description: null, + maxParticipants: null, + ownerId: null, + createdAt: new Date(), + updatedAt: new Date(), + } as any); + + // Mock successful update + vi.mocked(prisma.event.update).mockResolvedValue({ + id: 1, + name: 'Test Tournament', + allowTies: true, + targetScore: 5, + eventType: 'tournament', + format: 'round_robin', + status: 'planned', + eventDate: null, + description: null, + maxParticipants: null, + ownerId: null, + createdAt: new Date(), + updatedAt: new Date(), + } as any); + + const request = new Request('http://localhost/api/tournaments/1', { + method: 'PUT', + body: JSON.stringify({ + name: 'Test Tournament', + allowTies: true, + targetScore: 5, + }), + }); + + const params = Promise.resolve({ id: '1' }); + const response = await PUT(request, { params }); + + expect(response.status).toBe(200); + expect(vi.mocked(prisma.event.update)).toHaveBeenCalledWith( + expect.objectContaining({ + data: expect.objectContaining({ + allowTies: true, + }), + }) + ); + }); + + it('should default allowTies to false when not provided', async () => { + // Mock existing tournament + vi.mocked(prisma.event.findUnique).mockResolvedValue({ + id: 1, + name: 'Test Tournament', + allowTies: true, + targetScore: 5, + eventType: 'tournament', + format: 'round_robin', + status: 'planned', + eventDate: null, + description: null, + maxParticipants: null, + ownerId: null, + createdAt: new Date(), + updatedAt: new Date(), + } as any); + + // Mock successful update + vi.mocked(prisma.event.update).mockResolvedValue({ + id: 1, + name: 'Test Tournament', + allowTies: false, + targetScore: 5, + eventType: 'tournament', + format: 'round_robin', + status: 'planned', + eventDate: null, + description: null, + maxParticipants: null, + ownerId: null, + createdAt: new Date(), + updatedAt: new Date(), + } as any); + + const request = new Request('http://localhost/api/tournaments/1', { + method: 'PUT', + body: JSON.stringify({ + name: 'Test Tournament', + targetScore: 5, + // allowTies not provided + }), + }); + + const params = Promise.resolve({ id: '1' }); + const response = await PUT(request, { params }); + + expect(response.status).toBe(200); + expect(vi.mocked(prisma.event.update)).toHaveBeenCalledWith( + expect.objectContaining({ + data: expect.objectContaining({ + allowTies: false, // Should default to false + }), + }) + ); + }); + + it('should preserve allowTies value when updating other fields', async () => { + // Mock existing tournament with allowTies = true + vi.mocked(prisma.event.findUnique).mockResolvedValue({ + id: 1, + name: 'Test Tournament', + allowTies: true, + targetScore: 5, + eventType: 'tournament', + format: 'round_robin', + status: 'planned', + eventDate: null, + description: null, + maxParticipants: null, + ownerId: null, + createdAt: new Date(), + updatedAt: new Date(), + } as any); + + // Mock successful update + vi.mocked(prisma.event.update).mockResolvedValue({ + id: 1, + name: 'Updated Tournament Name', + allowTies: true, + targetScore: 10, + eventType: 'tournament', + format: 'round_robin', + status: 'planned', + eventDate: null, + description: null, + maxParticipants: null, + ownerId: null, + createdAt: new Date(), + updatedAt: new Date(), + } as any); + + const request = new Request('http://localhost/api/tournaments/1', { + method: 'PUT', + body: JSON.stringify({ + name: 'Updated Tournament Name', + allowTies: true, + targetScore: 10, + }), + }); + + const params = Promise.resolve({ id: '1' }); + const response = await PUT(request, { params }); + + expect(response.status).toBe(200); + const updateCall = vi.mocked(prisma.event.update).mock.calls[0][0]; + expect(updateCall.data.allowTies).toBe(true); + expect(updateCall.data.name).toBe('Updated Tournament Name'); + expect(updateCall.data.targetScore).toBe(10); + }); +}); diff --git a/src/components/EditTournamentForm.tsx b/src/components/EditTournamentForm.tsx index 6852922..36d88ee 100644 --- a/src/components/EditTournamentForm.tsx +++ b/src/components/EditTournamentForm.tsx @@ -52,6 +52,7 @@ export default function EditTournamentForm({ tournament }: EditTournamentFormPro maxParticipants: formData.maxParticipants ? parseInt(formData.maxParticipants) : null, eventDate: formData.eventDate ? new Date(formData.eventDate).toISOString() : null, targetScore: formData.targetScore ? parseInt(formData.targetScore) : null, + allowTies: formData.allowTies, }), })