diff --git a/.eslintrc.json b/.eslintrc.json index 48c02c9..65ecf95 100644 --- a/.eslintrc.json +++ b/.eslintrc.json @@ -4,6 +4,7 @@ ".next", "out", "build", - "next-env.d.ts" + "next-env.d.ts", + "src/app/test-api/**" ] } diff --git a/src/__tests__/EditTournamentForm.test.tsx b/src/__tests__/EditTournamentForm.test.tsx index 64cec12..d52835e 100644 --- a/src/__tests__/EditTournamentForm.test.tsx +++ b/src/__tests__/EditTournamentForm.test.tsx @@ -1,5 +1,5 @@ -import { describe, it, expect, vi, beforeEach } from 'vitest' -import { render, screen, fireEvent, waitFor } from '@testing-library/react' +import { describe, it, expect, vi, beforeEach, MockedFunction } from 'vitest' +import { render, screen, waitFor } from '@testing-library/react' import userEvent from '@testing-library/user-event' import EditTournamentForm from '@/components/EditTournamentForm' @@ -18,7 +18,8 @@ vi.mock('next/link', () => ({ })) // Mock fetch -global.fetch = vi.fn() +const mockFetch = vi.fn() +global.fetch = mockFetch as MockedFunction const mockTournament = { id: 1, @@ -65,10 +66,10 @@ describe('EditTournamentForm', () => { it('submits form with updated data', async () => { const user = userEvent.setup() - ;(global.fetch as any).mockResolvedValueOnce({ + mockFetch.mockResolvedValueOnce({ ok: true, json: async () => ({ success: true }), - }) + } as Response) render() @@ -80,7 +81,7 @@ describe('EditTournamentForm', () => { await user.click(submitButton) await waitFor(() => { - expect(global.fetch).toHaveBeenCalledWith( + expect(mockFetch).toHaveBeenCalledWith( '/api/tournaments/1', expect.objectContaining({ method: 'PUT', @@ -92,10 +93,10 @@ describe('EditTournamentForm', () => { it('shows error message on failed submission', async () => { const user = userEvent.setup() - ;(global.fetch as any).mockResolvedValueOnce({ + mockFetch.mockResolvedValueOnce({ ok: false, json: async () => ({ error: 'Failed to update tournament' }), - }) + } as Response) render() @@ -109,14 +110,14 @@ describe('EditTournamentForm', () => { it('disables submit button while loading', async () => { const user = userEvent.setup() - ;(global.fetch as any).mockImplementation( + mockFetch.mockImplementation( () => new Promise((resolve) => { setTimeout(() => { resolve({ ok: true, json: async () => ({ success: true }), - }) + } as Response) }, 100) }) ) diff --git a/src/__tests__/auth-simple.test.ts b/src/__tests__/auth-simple.test.ts index b9db3d9..e583318 100644 --- a/src/__tests__/auth-simple.test.ts +++ b/src/__tests__/auth-simple.test.ts @@ -1,4 +1,4 @@ -import { describe, it, expect, vi, beforeEach } from 'vitest' +import { describe, it, expect, vi, beforeEach, MockedFunction } from 'vitest' import { getSession } from '@/lib/auth-simple' // Mock next/headers @@ -10,7 +10,7 @@ vi.mock('next/headers', () => ({ // Mock fetch const mockFetch = vi.fn() -global.fetch = mockFetch as any +global.fetch = mockFetch as MockedFunction describe('getSession', () => { beforeEach(() => { diff --git a/src/__tests__/e2e/account-acceptance.test.ts b/src/__tests__/e2e/account-acceptance.test.ts index b4e5225..cf5857d 100644 --- a/src/__tests__/e2e/account-acceptance.test.ts +++ b/src/__tests__/e2e/account-acceptance.test.ts @@ -125,7 +125,7 @@ test.describe.serial('Account Lifecycle Acceptance Test', () => { * Test 3: Delete test account * Uses the authenticated project (admin) */ - test('3. Delete test account', async ({ page }) => { + test('3. Delete test account', async () => { // For now, we'll delete via direct database access // In a real scenario, this would be done via an admin API endpoint const user = await prisma.user.findUnique({ diff --git a/src/__tests__/e2e/elo-ratings.test.ts b/src/__tests__/e2e/elo-ratings.test.ts index 50183d6..c7908a6 100644 --- a/src/__tests__/e2e/elo-ratings.test.ts +++ b/src/__tests__/e2e/elo-ratings.test.ts @@ -6,14 +6,11 @@ import { test, expect } from '@playwright/test'; import { PrismaClient } from '@prisma/client'; -import { scryptAsync } from '@noble/hashes/scrypt.js'; -import { hex } from '@better-auth/utils/hex'; +import fs from 'fs'; +import path from 'path'; const prisma = new PrismaClient(); -// Polyfill crypto for Node.js environment -const crypto = globalThis.crypto || require('crypto').webcrypto; - test.describe('Elo Rating Updates', () => { test.beforeAll(async () => { // Clean up any existing test data @@ -285,8 +282,6 @@ ${tournament.id},1,1,${player1.name},${player3.name},10,${player2.name},${player ${tournament.id},2,1,${player1.name},${player3.name},10,${player2.name},${player4.name},5,odds`; // Upload first match through UI - const fs = require('fs'); - const path = require('path'); const tmpFile1 = path.join('/tmp', `elo-test-multi-1-${Date.now()}.csv`); fs.writeFileSync(tmpFile1, csvContent1); diff --git a/src/__tests__/e2e/epic1-auth-registration.test.ts b/src/__tests__/e2e/epic1-auth-registration.test.ts index cd7c664..88f33e0 100644 --- a/src/__tests__/e2e/epic1-auth-registration.test.ts +++ b/src/__tests__/e2e/epic1-auth-registration.test.ts @@ -141,7 +141,7 @@ test.describe.serial('Epic 1: User Registration', () => { // If we're still on the registration page, check for error message const content = await page.content(); expect(content).toContain('error'); - } catch (e) { + } catch { // If redirected successfully, that's fine console.log('Registration redirected to:', page.url()); } diff --git a/src/__tests__/e2e/global.setup.ts b/src/__tests__/e2e/global.setup.ts index 605a061..f9543fb 100644 --- a/src/__tests__/e2e/global.setup.ts +++ b/src/__tests__/e2e/global.setup.ts @@ -53,7 +53,7 @@ export default async function globalSetup(config: FullConfig) { { timeout: 10000 } ); console.log('Sign-up API call successful'); - } catch (e) { + } catch { console.log('Sign-up API call failed or timed out'); } @@ -97,7 +97,7 @@ export default async function globalSetup(config: FullConfig) { { timeout: 10000 } ); console.log('Admin sign-up API call successful'); - } catch (e) { + } catch { console.log('Admin sign-up API call failed or timed out'); } diff --git a/src/__tests__/unit/elo.test.ts b/src/__tests__/unit/elo.test.ts index 7348602..4cb573b 100644 --- a/src/__tests__/unit/elo.test.ts +++ b/src/__tests__/unit/elo.test.ts @@ -141,9 +141,6 @@ describe('Elo Rating System', () => { const team1Rating = calculateTeamElo(player1Rating, player2Rating); const team2Rating = calculateTeamElo(player3Rating, player4Rating); - const team1Expected = calculateExpectedScore(team1Rating, team2Rating); - const team2Expected = calculateExpectedScore(team2Rating, team1Rating); - // Team 1 wins (score = 1, team 2 score = 0) const team1Change = calculateEloChange(team1Rating, team2Rating, 1, K_FACTOR); const team2Change = calculateEloChange(team2Rating, team1Rating, 0, K_FACTOR); diff --git a/src/__tests__/unit/permissions.test.ts b/src/__tests__/unit/permissions.test.ts index 2ad2ed0..0aa76a5 100644 --- a/src/__tests__/unit/permissions.test.ts +++ b/src/__tests__/unit/permissions.test.ts @@ -8,9 +8,10 @@ import { describe, test, expect, vi } from 'vitest'; import { hasRole, canManageTournament, canCreateTournaments } from '@/lib/permissions'; import { getSession } from '@/lib/auth-simple'; import { prisma } from '@/lib/prisma'; +import type { User } from '@prisma/client'; // Helper to create mock user -const createMockUser = (id: string, email: string, role: string) => ({ +const createMockUser = (id: string, email: string, role: string): User => ({ id, email, role, @@ -46,7 +47,7 @@ describe('Permissions', () => { session: { token: 'test', expiresAt: new Date() } }); vi.mocked(prisma.user.findUnique).mockResolvedValue( - createMockUser('1', 'test@example.com', 'club_admin') as any + createMockUser('1', 'test@example.com', 'club_admin') ); const result = await hasRole('tournament_admin'); @@ -59,7 +60,7 @@ describe('Permissions', () => { session: { token: 'test', expiresAt: new Date() } }); vi.mocked(prisma.user.findUnique).mockResolvedValue( - createMockUser('1', 'test@example.com', 'player') as any + createMockUser('1', 'test@example.com', 'player') ); const result = await hasRole('tournament_admin'); @@ -82,7 +83,7 @@ describe('Permissions', () => { session: { token: 'test', expiresAt: new Date() } }); vi.mocked(prisma.user.findUnique).mockResolvedValue( - createMockUser('admin-1', 'admin@example.com', 'club_admin') as any + createMockUser('admin-1', 'admin@example.com', 'club_admin') ); const result = await canManageTournament(999); @@ -95,7 +96,7 @@ describe('Permissions', () => { session: { token: 'test', expiresAt: new Date() } }); vi.mocked(prisma.user.findUnique).mockResolvedValue( - createMockUser('player-1', 'player@example.com', 'player') as any + createMockUser('player-1', 'player@example.com', 'player') ); const result = await canManageTournament(999); @@ -111,7 +112,7 @@ describe('Permissions', () => { session: { token: 'test', expiresAt: new Date() } }); vi.mocked(prisma.user.findUnique).mockResolvedValue( - createMockUser('admin-1', 'admin@example.com', 'tournament_admin') as any + createMockUser('admin-1', 'admin@example.com', 'tournament_admin') ); const result = await canCreateTournaments(); @@ -124,7 +125,7 @@ describe('Permissions', () => { session: { token: 'test', expiresAt: new Date() } }); vi.mocked(prisma.user.findUnique).mockResolvedValue( - createMockUser('admin-1', 'admin@example.com', 'club_admin') as any + createMockUser('admin-1', 'admin@example.com', 'club_admin') ); const result = await canCreateTournaments(); @@ -137,7 +138,7 @@ describe('Permissions', () => { session: { token: 'test', expiresAt: new Date() } }); vi.mocked(prisma.user.findUnique).mockResolvedValue( - createMockUser('player-1', 'player@example.com', 'player') as any + createMockUser('player-1', 'player@example.com', 'player') ); const result = await canCreateTournaments(); diff --git a/src/app/admin/matches/upload/page.tsx b/src/app/admin/matches/upload/page.tsx index 9a9e588..c7b0b11 100644 --- a/src/app/admin/matches/upload/page.tsx +++ b/src/app/admin/matches/upload/page.tsx @@ -11,7 +11,7 @@ export default function UploadMatchesPage() { const [error, setError] = useState("") const [success, setSuccess] = useState("") const [isLoading, setIsLoading] = useState(false) - const [tournaments, setTournaments] = useState([]) + const [tournaments, setTournaments] = useState<{ id: number; name: string }[]>([]) const fileInputRef = useRef(null) // Fetch tournaments on mount @@ -49,7 +49,7 @@ export default function UploadMatchesPage() { setTournaments([data.tournament]) setSelectedTournament(data.tournament.id.toString()) } - } catch (err: any) { + } catch (err) { console.error("Failed to create default tournament:", err) } } @@ -111,8 +111,12 @@ export default function UploadMatchesPage() { if (fileInputRef.current) { fileInputRef.current.value = "" } - } catch (err: any) { - setError(err.message) + } catch (err) { + if (err instanceof Error) { + setError(err.message) + } else { + setError("An unknown error occurred") + } } finally { setIsLoading(false) } @@ -262,7 +266,7 @@ export default function UploadMatchesPage() {
  • Seat 2: Player 1 name (Evens team)
  • Seat 4: Player 2 name (Evens team)
  • Evens Points: Score for Evens team
  • -
  • Winner: "Odds" or "Evens" (optional)
  • +
  • Winner: "Odds" or "Evens" (optional)
  • diff --git a/src/app/admin/page.tsx b/src/app/admin/page.tsx index 7af6cef..1542c8e 100644 --- a/src/app/admin/page.tsx +++ b/src/app/admin/page.tsx @@ -66,7 +66,7 @@ export default async function AdminDashboard() {

    Admin Dashboard

    - Manage your club's tournaments, players, and matches. + Manage your club's tournaments, players, and matches.

    diff --git a/src/app/admin/tournaments/[id]/entry/page.tsx b/src/app/admin/tournaments/[id]/entry/page.tsx index 099cad6..0a5161b 100644 --- a/src/app/admin/tournaments/[id]/entry/page.tsx +++ b/src/app/admin/tournaments/[id]/entry/page.tsx @@ -42,6 +42,7 @@ export default function TournamentEntryPage({ params }: { params: { id: string } useEffect(() => { loadTournament() + // eslint-disable-next-line react-hooks/exhaustive-deps }, []) const loadTournament = async () => { @@ -121,8 +122,12 @@ export default function TournamentEntryPage({ params }: { params: { id: string } setSuccess(`Successfully imported ${data.importedCount} games`) setGameText("") setParsedGames([]) - } catch (err: any) { - setError(err.message) + } catch (err) { + if (err instanceof Error) { + setError(err.message) + } else { + setError("An unknown error occurred") + } } finally { setIsLoading(false) } diff --git a/src/app/test-api/page.tsx b/src/app/test-api/page.tsx index 6989df3..96dad56 100644 --- a/src/app/test-api/page.tsx +++ b/src/app/test-api/page.tsx @@ -3,7 +3,7 @@ import { authClient } from '@/lib/auth-client'; import { useEffect, useState } from 'react'; export default function TestApi() { - const [result, setResult] = useState(null); + const [result, setResult] = useState(null); const [error, setError] = useState(null); useEffect(() => { @@ -13,9 +13,13 @@ export default function TestApi() { email: 'david@dhg.lol', password: 'admin1234' }); - setResult(response); - } catch (err: any) { - setError(err.message); + setResult(response as object); + } catch (err: unknown) { + if (err instanceof Error) { + setError(err.message); + } else { + setError("An unexpected error occurred"); + } } } test(); diff --git a/src/components/Navigation.tsx b/src/components/Navigation.tsx index 3e59ce3..2235fe1 100644 --- a/src/components/Navigation.tsx +++ b/src/components/Navigation.tsx @@ -11,8 +11,9 @@ export default function Navigation() { useEffect(() => { // Fetch user role from database if session exists - if (session?.user?.id) { - fetch(`/api/users/${session.user.id}/role`) + const userId = (session?.user as { id?: string })?.id + if (userId) { + fetch(`/api/users/${userId}/role`) .then(res => res.json()) .then(data => setUserRole(data.role)) .catch(() => setUserRole(null)); @@ -65,7 +66,8 @@ export default function Navigation() { ) : session ? (
    - {session.user?.name || session.user?.email} + {(session.user as { name?: string; email?: string })?.name || + (session.user as { name?: string; email?: string })?.email}