/** * Issue #7: Schedule Tab * Acceptance Test: Schedule Generation and Display * * User Story: As a tournament admin, I want a Schedule tab to view round matchups * * Acceptance Criteria: * - Schedule tab added to tournament detail page * - Displays round-robin schedule with round numbers * - Round-robin schedule can be generated from teams * - Matches are linkable to result entry */ import { test, expect } from '@playwright/test'; import { prisma } from '@/lib/prisma'; const BASE_URL = process.env.CI ? 'https://euchre-ci.notsosm.art' : 'http://localhost:3000'; function getTestCredentials() { const timestamp = Date.now(); return { email: `schedule-admin-${timestamp}@example.com`, password: 'AdminPassword123!', name: `Schedule Admin ${timestamp}`, }; } test.describe.skip('Issue #7: Schedule Tab', () => { let testEmail: string; let testPassword: string; let tournamentId: number; test.beforeAll(async () => { const credentials = getTestCredentials(); testEmail = credentials.email; testPassword = credentials.password; // Create admin user via API const response = await fetch(`${BASE_URL}/api/auth/sign-up/email`, { method: 'POST', headers: { 'Content-Type': 'application/json', Origin: BASE_URL, }, body: JSON.stringify({ email: testEmail, password: testPassword, name: credentials.name, }), }); console.log('Schedule test user creation response:', response.status); // Update user to club_admin role const user = await prisma.user.findUnique({ where: { email: testEmail }, }); if (user) { await prisma.user.update({ where: { id: user.id }, data: { role: 'club_admin' }, }); } // Create players for teams const players = await Promise.all([ prisma.player.create({ data: { name: 'Alice', normalizedName: 'alice' }, }), prisma.player.create({ data: { name: 'Bob', normalizedName: 'bob' }, }), prisma.player.create({ data: { name: 'Charlie', normalizedName: 'charlie' }, }), prisma.player.create({ data: { name: 'Diana', normalizedName: 'diana' }, }), ]); // Create tournament const tournament = await prisma.event.create({ data: { name: `Schedule Test Tournament ${Date.now()}`, format: 'round_robin', ownerId: user?.id, }, }); tournamentId = tournament.id; // Register participants (teams are now ephemeral and generated during schedule creation) await Promise.all( players.map((player) => prisma.eventParticipant.create({ data: { eventId: tournamentId, playerId: player.id, status: 'registered', registrationDate: new Date(), }, }) ) ); }); test.afterAll(async () => { try { // Clean up schedule data if (tournamentId) { await prisma.bracketMatchup.deleteMany({ where: { eventId: tournamentId } }); await prisma.tournamentRound.deleteMany({ where: { eventId: tournamentId } }); await prisma.eventParticipant.deleteMany({ where: { eventId: tournamentId } }); await prisma.event.delete({ where: { id: tournamentId } }).catch(() => {}); } // Clean up user const user = await prisma.user.findUnique({ where: { email: testEmail } }); if (user) { await prisma.user.delete({ where: { id: user.id } }); } // Clean up players await prisma.player.deleteMany({ where: { normalizedName: { in: ['alice', 'bob', 'charlie', 'diana'] } }, }); } catch (error) { console.error('Cleanup error:', error); } }); test('Schedule tab link exists on tournament detail page', async ({ page }) => { // Login await page.goto('/auth/login'); await page.fill('input[name="email"]', testEmail); await page.fill('input[name="password"]', testPassword); await page.click('button[type="submit"]'); await page.waitForURL(/\/(admin|players)/, { timeout: 5000 }); // Navigate to tournament detail await page.goto(`/admin/tournaments/${tournamentId}`); // Check Schedule tab link exists - use button since page uses buttons for tabs const scheduleLink = page.locator('button', { hasText: 'Schedule' }); await expect(scheduleLink).toBeVisible(); }); test('Schedule page loads with no schedule message', async ({ page }) => { // Login await page.goto('/auth/login'); await page.fill('input[name="email"]', testEmail); await page.fill('input[name="password"]', testPassword); await page.click('button[type="submit"]'); await page.waitForURL(/\/(admin|players)/, { timeout: 5000 }); // Navigate to schedule page await page.goto(`/admin/tournaments/${tournamentId}/schedule`); // Check page content await expect(page.locator('h1')).toContainText('Tournament Schedule'); await expect(page.locator('text=No Schedule Generated')).toBeVisible(); await expect(page.locator('button', { hasText: 'Generate Schedule' })).toBeVisible(); }); test('Generate schedule creates rounds and matchups', async ({ page }) => { // Login await page.goto('/auth/login'); await page.fill('input[name="email"]', testEmail); await page.fill('input[name="password"]', testPassword); await page.click('button[type="submit"]'); await page.waitForURL(/\/(admin|players)/, { timeout: 5000 }); // Navigate to schedule page await page.goto(`/admin/tournaments/${tournamentId}/schedule`); // Click generate schedule await page.click('button:has-text("Generate Schedule")'); // Wait for success message or page reload await page.waitForTimeout(500); // Verify rounds were created in database const rounds = await prisma.tournamentRound.findMany({ where: { eventId: tournamentId }, }); expect(rounds.length).toBeGreaterThan(0); // Verify matchups were created const matchups = await prisma.bracketMatchup.findMany({ where: { eventId: tournamentId }, }); expect(matchups.length).toBeGreaterThan(0); }); test('Schedule page displays generated rounds and matchups', async ({ page }) => { // Login await page.goto('/auth/login'); await page.fill('input[name="email"]', testEmail); await page.fill('input[name="password"]', testPassword); await page.click('button[type="submit"]'); await page.waitForURL(/\/(admin|players)/, { timeout: 5000 }); // Navigate to schedule page await page.goto(`/admin/tournaments/${tournamentId}/schedule`); // Check that rounds are displayed await expect(page.locator('text=Round 1')).toBeVisible(); // Check that team names are displayed await expect(page.locator('text=Alice + Bob')).toBeVisible(); await expect(page.locator('text=Charlie + Diana')).toBeVisible(); // Check that "Enter Result" link exists for pending matchups await expect(page.locator('a:has-text("Enter Result")')).toBeVisible(); }); test('Schedule API returns rounds with matchups', async ({ page }) => { // Login await page.goto('/auth/login'); await page.fill('input[name="email"]', testEmail); await page.fill('input[name="password"]', testPassword); await page.click('button[type="submit"]'); await page.waitForURL(/\/(admin|players)/, { timeout: 5000 }); // Call the schedule API const response = await page.request.get( `/api/tournaments/${tournamentId}/schedule` ); expect(response.ok()).toBe(true); const data = await response.json(); expect(data.rounds).toBeDefined(); expect(data.rounds.length).toBeGreaterThan(0); expect(data.rounds[0].bracketMatchups).toBeDefined(); }); });