mirror of
https://github.com/AmanTahiliani/box-box.git
synced 2026-08-07 19:56:18 -04:00
Add React race hub frontend
This commit is contained in:
131
frontend/src/test/ClassificationTable.test.tsx
Normal file
131
frontend/src/test/ClassificationTable.test.tsx
Normal file
@@ -0,0 +1,131 @@
|
||||
import { describe, it, expect } from 'vitest'
|
||||
import { render, screen } from '@testing-library/react'
|
||||
import { ClassificationTable } from '../components/ClassificationTable'
|
||||
import type { EnrichedResult, EnrichedGrid } from '../types'
|
||||
|
||||
const mockResults: EnrichedResult[] = [
|
||||
{
|
||||
driver_number: 16,
|
||||
position: 1,
|
||||
name_acronym: 'LEC',
|
||||
full_name: 'Charles Leclerc',
|
||||
team_name: 'Ferrari',
|
||||
team_colour: 'e8002d',
|
||||
dnf: false,
|
||||
dns: false,
|
||||
dsq: false,
|
||||
duration: 5534.456,
|
||||
gap_to_leader: null,
|
||||
number_of_laps: 78,
|
||||
points: 25,
|
||||
session_key: 9472,
|
||||
meeting_key: 1234,
|
||||
},
|
||||
{
|
||||
driver_number: 1,
|
||||
position: 2,
|
||||
name_acronym: 'VER',
|
||||
full_name: 'Max Verstappen',
|
||||
team_name: 'Red Bull Racing',
|
||||
team_colour: '3671c6',
|
||||
dnf: false,
|
||||
dns: false,
|
||||
dsq: false,
|
||||
duration: null,
|
||||
gap_to_leader: 3.456,
|
||||
number_of_laps: 78,
|
||||
points: 18,
|
||||
session_key: 9472,
|
||||
meeting_key: 1234,
|
||||
},
|
||||
{
|
||||
driver_number: 44,
|
||||
position: 5,
|
||||
name_acronym: 'HAM',
|
||||
full_name: 'Lewis Hamilton',
|
||||
team_name: 'Ferrari',
|
||||
team_colour: 'e8002d',
|
||||
dnf: false,
|
||||
dns: false,
|
||||
dsq: false,
|
||||
duration: null,
|
||||
gap_to_leader: 21.234,
|
||||
number_of_laps: 78,
|
||||
points: 10,
|
||||
session_key: 9472,
|
||||
meeting_key: 1234,
|
||||
},
|
||||
]
|
||||
|
||||
const mockGrid: EnrichedGrid[] = [
|
||||
{
|
||||
driver_number: 1,
|
||||
position: 1,
|
||||
name_acronym: 'VER',
|
||||
full_name: 'Max Verstappen',
|
||||
team_name: 'Red Bull Racing',
|
||||
team_colour: '3671c6',
|
||||
session_key: 9472,
|
||||
meeting_key: 1234,
|
||||
lap_duration: 74.892,
|
||||
},
|
||||
{
|
||||
driver_number: 16,
|
||||
position: 3,
|
||||
name_acronym: 'LEC',
|
||||
full_name: 'Charles Leclerc',
|
||||
team_name: 'Ferrari',
|
||||
team_colour: 'e8002d',
|
||||
session_key: 9472,
|
||||
meeting_key: 1234,
|
||||
lap_duration: 75.123,
|
||||
},
|
||||
]
|
||||
|
||||
describe('ClassificationTable', () => {
|
||||
it('renders driver acronyms', () => {
|
||||
render(<ClassificationTable results={mockResults} grid={mockGrid} />)
|
||||
expect(screen.getByText('LEC')).toBeInTheDocument()
|
||||
expect(screen.getByText('VER')).toBeInTheDocument()
|
||||
expect(screen.getByText('HAM')).toBeInTheDocument()
|
||||
})
|
||||
|
||||
it('renders P1 badge for LEC', () => {
|
||||
const { container } = render(<ClassificationTable results={mockResults} grid={mockGrid} />)
|
||||
const p1 = container.querySelector('.pos-p1')
|
||||
expect(p1).toBeInTheDocument()
|
||||
expect(p1?.textContent).toBe('1')
|
||||
})
|
||||
|
||||
it('shows grid gain arrow for LEC (started P3, finished P1)', () => {
|
||||
render(<ClassificationTable results={mockResults} grid={mockGrid} />)
|
||||
expect(screen.getByText('↑2')).toBeInTheDocument()
|
||||
})
|
||||
|
||||
it('shows grid loss arrow for VER (started P1, finished P2)', () => {
|
||||
render(<ClassificationTable results={mockResults} grid={mockGrid} />)
|
||||
expect(screen.getByText('↓1')).toBeInTheDocument()
|
||||
})
|
||||
|
||||
it('renders missing notice when results are empty', () => {
|
||||
render(<ClassificationTable results={[]} grid={[]} />)
|
||||
expect(screen.getByText(/not ingested/i)).toBeInTheDocument()
|
||||
})
|
||||
})
|
||||
|
||||
describe('ClassificationTable — DNF/DNS/DSQ', () => {
|
||||
it('shows DNF label', () => {
|
||||
const dnfResult: EnrichedResult = {
|
||||
...mockResults[0],
|
||||
driver_number: 23,
|
||||
name_acronym: 'ALB',
|
||||
position: 20,
|
||||
dnf: true,
|
||||
duration: null,
|
||||
gap_to_leader: null,
|
||||
points: 0,
|
||||
}
|
||||
render(<ClassificationTable results={[dnfResult]} grid={[]} />)
|
||||
expect(screen.getByText('DNF')).toBeInTheDocument()
|
||||
})
|
||||
})
|
||||
1
frontend/src/test/setup.ts
Normal file
1
frontend/src/test/setup.ts
Normal file
@@ -0,0 +1 @@
|
||||
import '@testing-library/jest-dom'
|
||||
115
frontend/src/test/utils.test.ts
Normal file
115
frontend/src/test/utils.test.ts
Normal file
@@ -0,0 +1,115 @@
|
||||
import { describe, it, expect } from 'vitest'
|
||||
import {
|
||||
teamColor,
|
||||
formatDuration,
|
||||
formatGap,
|
||||
formatLapTime,
|
||||
gridDelta,
|
||||
gridDeltaClass,
|
||||
positionClass,
|
||||
} from '../utils'
|
||||
|
||||
describe('teamColor', () => {
|
||||
it('prepends # to bare hex', () => {
|
||||
expect(teamColor('e8002d')).toBe('#e8002d')
|
||||
})
|
||||
it('passes through already-prefixed hex', () => {
|
||||
expect(teamColor('#3671c6')).toBe('#3671c6')
|
||||
})
|
||||
it('returns fallback for empty string', () => {
|
||||
expect(teamColor('')).toBe('#444444')
|
||||
})
|
||||
it('returns fallback for undefined', () => {
|
||||
expect(teamColor(undefined)).toBe('#444444')
|
||||
})
|
||||
})
|
||||
|
||||
describe('formatDuration', () => {
|
||||
it('formats a race duration with hours', () => {
|
||||
expect(formatDuration(5534.456)).toBe('1:32:14.456')
|
||||
})
|
||||
it('formats a sub-hour duration', () => {
|
||||
expect(formatLapTime(74.892)).toBe('1:14.892')
|
||||
})
|
||||
it('returns — for null', () => {
|
||||
expect(formatDuration(null)).toBe('—')
|
||||
})
|
||||
it('returns — for undefined', () => {
|
||||
expect(formatDuration(undefined)).toBe('—')
|
||||
})
|
||||
it('handles array (qualifying)', () => {
|
||||
expect(formatDuration([74.892])).toBe('1:14.892')
|
||||
})
|
||||
it('returns — for zero', () => {
|
||||
expect(formatDuration(0)).toBe('—')
|
||||
})
|
||||
})
|
||||
|
||||
describe('formatGap', () => {
|
||||
it('formats a numeric gap', () => {
|
||||
expect(formatGap(3.456)).toBe('+3.456')
|
||||
})
|
||||
it('passes through a string gap (lapped)', () => {
|
||||
expect(formatGap('+1 LAP')).toBe('+1 LAP')
|
||||
})
|
||||
it('returns — for null', () => {
|
||||
expect(formatGap(null)).toBe('—')
|
||||
})
|
||||
it('handles array gap', () => {
|
||||
expect(formatGap([8.123])).toBe('+8.123')
|
||||
})
|
||||
})
|
||||
|
||||
describe('formatLapTime', () => {
|
||||
it('formats qualifying lap time', () => {
|
||||
expect(formatLapTime(74.892)).toBe('1:14.892')
|
||||
})
|
||||
it('returns — for null', () => {
|
||||
expect(formatLapTime(null)).toBe('—')
|
||||
})
|
||||
it('pads seconds correctly', () => {
|
||||
expect(formatLapTime(64.5)).toBe('1:04.500')
|
||||
})
|
||||
})
|
||||
|
||||
describe('gridDelta', () => {
|
||||
it('shows gain when finish position improved', () => {
|
||||
expect(gridDelta(1, 3)).toBe('↑2')
|
||||
})
|
||||
it('shows loss when finish position dropped', () => {
|
||||
expect(gridDelta(5, 2)).toBe('↓3')
|
||||
})
|
||||
it('shows — for same position', () => {
|
||||
expect(gridDelta(4, 4)).toBe('—')
|
||||
})
|
||||
it('shows — when grid position is 0', () => {
|
||||
expect(gridDelta(1, 0)).toBe('—')
|
||||
})
|
||||
})
|
||||
|
||||
describe('gridDeltaClass', () => {
|
||||
it('returns pos-gain for improvement', () => {
|
||||
expect(gridDeltaClass(1, 5)).toBe('pos-gain')
|
||||
})
|
||||
it('returns pos-loss for drop', () => {
|
||||
expect(gridDeltaClass(6, 2)).toBe('pos-loss')
|
||||
})
|
||||
it('returns pos-same for no change', () => {
|
||||
expect(gridDeltaClass(3, 3)).toBe('pos-same')
|
||||
})
|
||||
})
|
||||
|
||||
describe('positionClass', () => {
|
||||
it('returns pos-p1 for first', () => {
|
||||
expect(positionClass(1)).toBe('pos-p1')
|
||||
})
|
||||
it('returns pos-p2 for second', () => {
|
||||
expect(positionClass(2)).toBe('pos-p2')
|
||||
})
|
||||
it('returns pos-p3 for third', () => {
|
||||
expect(positionClass(3)).toBe('pos-p3')
|
||||
})
|
||||
it('returns pos-n for other positions', () => {
|
||||
expect(positionClass(10)).toBe('pos-n')
|
||||
})
|
||||
})
|
||||
Reference in New Issue
Block a user