import { describe, it, expect } from 'vitest' import { mount } from '@vue/test-utils' import PitchingChangeSelector from '~/components/Substitutions/PitchingChangeSelector.vue' import type { Lineup, SbaPlayer } from '~/types' // Helper to create mock player function createMockPlayer(id: number, name: string, positions: string[]): SbaPlayer { const player: SbaPlayer = { id, name, image: `player-${id}.jpg`, pos_1: null, pos_2: null, pos_3: null, pos_4: null, pos_5: null, pos_6: null, pos_7: null, pos_8: null, wara: 1.5, team_id: 1, team_name: 'Test Team', season: '2024', strat_code: null, bbref_id: null, injury_rating: null, } positions.forEach((pos, index) => { const key = `pos_${index + 1}` as keyof SbaPlayer ;(player as any)[key] = pos }) return player } // Helper to create mock lineup entry function createMockLineup( id: number, player: SbaPlayer, isActive: boolean, position = 'P', isFatigued = false, enteredInning = 1 ): Lineup { return { lineup_id: id, // Changed from 'id' to 'lineup_id' card_id: player.id, // Changed from 'player_id' to 'card_id' game_id: 'test-game-123', team_id: 1, position, batting_order: null, is_starter: true, is_active: isActive, entered_inning: enteredInning, replacing_id: null, after_play: null, is_fatigued: isFatigued, player, } } describe('PitchingChangeSelector', () => { const mockCurrentPitcher = createMockLineup( 1, createMockPlayer(101, 'John Starter', ['P']), true, 'P', false, 1 ) const mockBenchPlayers = [ createMockLineup(2, createMockPlayer(201, 'Relief Pitcher 1', ['P']), false), createMockLineup(3, createMockPlayer(202, 'Relief Pitcher 2', ['P']), false), createMockLineup(4, createMockPlayer(203, 'Position Player', ['1B', 'OF']), false), ] const defaultProps = { currentPitcher: mockCurrentPitcher, benchPlayers: mockBenchPlayers, teamId: 1, } describe('Rendering', () => { it('renders component with header', () => { const wrapper = mount(PitchingChangeSelector, { props: defaultProps, }) expect(wrapper.text()).toContain('Pitching Change') }) it('displays current pitcher information', () => { const wrapper = mount(PitchingChangeSelector, { props: defaultProps, }) expect(wrapper.text()).toContain('Current Pitcher:') expect(wrapper.text()).toContain('John Starter') expect(wrapper.text()).toContain('Entered Inning 1') }) it('shows descriptive text with pitcher name', () => { const wrapper = mount(PitchingChangeSelector, { props: defaultProps, }) expect(wrapper.text()).toContain('Bring in a relief pitcher to replace John Starter') }) it('renders action buttons', () => { const wrapper = mount(PitchingChangeSelector, { props: defaultProps, }) expect(wrapper.text()).toContain('Cancel') expect(wrapper.text()).toContain('Bring in Reliever') }) }) describe('Pitcher Filtering', () => { it('filters to only show pitchers from bench', () => { const wrapper = mount(PitchingChangeSelector, { props: defaultProps, }) expect(wrapper.text()).toContain('Relief Pitcher 1') expect(wrapper.text()).toContain('Relief Pitcher 2') expect(wrapper.text()).not.toContain('Position Player') }) it('filters out active pitchers', () => { const mixedBench = [ createMockLineup(2, createMockPlayer(201, 'Active Pitcher', ['P']), true), createMockLineup(3, createMockPlayer(202, 'Bench Pitcher', ['P']), false), ] const wrapper = mount(PitchingChangeSelector, { props: { ...defaultProps, benchPlayers: mixedBench, }, }) expect(wrapper.text()).not.toContain('Active Pitcher') expect(wrapper.text()).toContain('Bench Pitcher') }) it('shows fatigued pitchers but marks them as unavailable', () => { const mixedBench = [ createMockLineup(2, createMockPlayer(201, 'Fatigued Pitcher', ['P']), false, 'P', true), createMockLineup(3, createMockPlayer(202, 'Fresh Pitcher', ['P']), false), ] const wrapper = mount(PitchingChangeSelector, { props: { ...defaultProps, benchPlayers: mixedBench, }, }) expect(wrapper.text()).toContain('Fatigued Pitcher') expect(wrapper.text()).toContain('Fatigued') expect(wrapper.text()).toContain('Fresh Pitcher') }) it('shows no pitchers message when no eligible relievers', () => { const wrapper = mount(PitchingChangeSelector, { props: { ...defaultProps, benchPlayers: [ createMockLineup(2, createMockPlayer(201, 'Only Fielder', ['1B']), false), ], }, }) expect(wrapper.text()).toContain('No relief pitchers available') }) }) describe('Pitcher Selection', () => { it('allows selecting a relief pitcher', async () => { const wrapper = mount(PitchingChangeSelector, { props: defaultProps, }) const pitcherCards = wrapper.findAll('.reliever-card') await pitcherCards[0].trigger('click') expect(wrapper.vm.selectedPitcherId).toBe(201) }) it('highlights selected pitcher', async () => { const wrapper = mount(PitchingChangeSelector, { props: defaultProps, }) const pitcherCards = wrapper.findAll('.reliever-card') await pitcherCards[0].trigger('click') await wrapper.vm.$nextTick() expect(pitcherCards[0].classes()).toContain('reliever-selected') }) it('shows checkmark on selected pitcher', async () => { const wrapper = mount(PitchingChangeSelector, { props: defaultProps, }) const pitcherCards = wrapper.findAll('.reliever-card') await pitcherCards[0].trigger('click') await wrapper.vm.$nextTick() expect(pitcherCards[0].find('.selected-indicator').exists()).toBe(true) }) it('prevents selecting fatigued pitcher', async () => { const mixedBench = [ createMockLineup(2, createMockPlayer(201, 'Fatigued Pitcher', ['P']), false, 'P', true), ] const wrapper = mount(PitchingChangeSelector, { props: { ...defaultProps, benchPlayers: mixedBench, }, }) const pitcherCards = wrapper.findAll('.reliever-card') await pitcherCards[0].trigger('click') expect(wrapper.vm.selectedPitcherId).toBeNull() }) it('allows changing selection to different pitcher', async () => { const wrapper = mount(PitchingChangeSelector, { props: defaultProps, }) const pitcherCards = wrapper.findAll('.reliever-card') await pitcherCards[0].trigger('click') expect(wrapper.vm.selectedPitcherId).toBe(201) await pitcherCards[1].trigger('click') expect(wrapper.vm.selectedPitcherId).toBe(202) }) }) describe('Fatigue Display', () => { it('shows fatigue indicator for current pitcher when fatigued', () => { const fatiguedPitcher = createMockLineup( 1, createMockPlayer(101, 'Tired Starter', ['P']), true, 'P', true ) const wrapper = mount(PitchingChangeSelector, { props: { ...defaultProps, currentPitcher: fatiguedPitcher, }, }) expect(wrapper.text()).toContain('Fatigued') }) it('shows fatigue badge on relief pitchers who are fatigued', () => { const mixedBench = [ createMockLineup(2, createMockPlayer(201, 'Fatigued Reliever', ['P']), false, 'P', true), ] const wrapper = mount(PitchingChangeSelector, { props: { ...defaultProps, benchPlayers: mixedBench, }, }) const fatigueBadges = wrapper.findAll('.fatigue-badge') expect(fatigueBadges.length).toBeGreaterThan(0) }) }) describe('Submit Validation', () => { it('disables submit when no pitcher selected', () => { const wrapper = mount(PitchingChangeSelector, { props: defaultProps, }) const submitButton = wrapper.findAll('button').find(btn => btn.text().includes('Bring in Reliever')) expect(submitButton?.attributes('disabled')).toBeDefined() }) it('enables submit when fresh pitcher selected', async () => { const wrapper = mount(PitchingChangeSelector, { props: defaultProps, }) const pitcherCards = wrapper.findAll('.reliever-card') await pitcherCards[0].trigger('click') await wrapper.vm.$nextTick() const submitButton = wrapper.findAll('button').find(btn => btn.text().includes('Bring in Reliever')) expect(submitButton?.attributes('disabled')).toBeUndefined() }) it('disables submit when fatigued pitcher selected', async () => { const mixedBench = [ createMockLineup(2, createMockPlayer(201, 'Fatigued Pitcher', ['P']), false, 'P', true), ] const wrapper = mount(PitchingChangeSelector, { props: { ...defaultProps, benchPlayers: mixedBench, }, }) // Try to select fatigued pitcher (should be prevented) const pitcherCards = wrapper.findAll('.reliever-card') await pitcherCards[0].trigger('click') const submitButton = wrapper.findAll('button').find(btn => btn.text().includes('Bring in Reliever')) expect(submitButton?.attributes('disabled')).toBeDefined() }) it('disables submit when currentPitcher is null', () => { const wrapper = mount(PitchingChangeSelector, { props: { ...defaultProps, currentPitcher: null, }, }) const submitButton = wrapper.findAll('button').find(btn => btn.text().includes('Bring in Reliever')) expect(submitButton?.attributes('disabled')).toBeDefined() }) }) describe('Emit Events', () => { it('emits submit event with correct payload', async () => { const wrapper = mount(PitchingChangeSelector, { props: defaultProps, }) const pitcherCards = wrapper.findAll('.reliever-card') await pitcherCards[0].trigger('click') const submitButton = wrapper.find('.button-submit') await submitButton.trigger('click') expect(wrapper.emitted()).toHaveProperty('submit') const submitEvents = wrapper.emitted('submit') expect(submitEvents).toHaveLength(1) expect(submitEvents![0]).toEqual([{ playerOutLineupId: 1, playerInCardId: 201, teamId: 1, }]) }) it('emits cancel event when cancel clicked', async () => { const wrapper = mount(PitchingChangeSelector, { props: defaultProps, }) const cancelButton = wrapper.find('.button-cancel') await cancelButton.trigger('click') expect(wrapper.emitted()).toHaveProperty('cancel') expect(wrapper.emitted('cancel')).toHaveLength(1) }) it('resets selection after submit', async () => { const wrapper = mount(PitchingChangeSelector, { props: defaultProps, }) const pitcherCards = wrapper.findAll('.reliever-card') await pitcherCards[0].trigger('click') expect(wrapper.vm.selectedPitcherId).toBe(201) const submitButton = wrapper.find('.button-submit') await submitButton.trigger('click') expect(wrapper.vm.selectedPitcherId).toBeNull() }) it('resets selection after cancel', async () => { const wrapper = mount(PitchingChangeSelector, { props: defaultProps, }) const pitcherCards = wrapper.findAll('.reliever-card') await pitcherCards[0].trigger('click') expect(wrapper.vm.selectedPitcherId).toBe(201) const cancelButton = wrapper.find('.button-cancel') await cancelButton.trigger('click') expect(wrapper.vm.selectedPitcherId).toBeNull() }) }) describe('Pitcher Metadata Display', () => { it('displays pitcher role', () => { const wrapper = mount(PitchingChangeSelector, { props: defaultProps, }) expect(wrapper.text()).toContain('Pitcher') }) it('displays WARA stat when available', () => { const wrapper = mount(PitchingChangeSelector, { props: defaultProps, }) expect(wrapper.text()).toContain('WARA: 1.5') }) }) describe('Edge Cases', () => { it('handles missing currentPitcher gracefully', () => { const wrapper = mount(PitchingChangeSelector, { props: { ...defaultProps, currentPitcher: null, }, }) expect(wrapper.text()).toContain('Bring in a relief pitcher to replace current pitcher') }) it('prevents submit when currentPitcher is missing', async () => { const wrapper = mount(PitchingChangeSelector, { props: { ...defaultProps, currentPitcher: null, }, }) const pitcherCards = wrapper.findAll('.reliever-card') if (pitcherCards.length > 0) { await pitcherCards[0].trigger('click') } const submitButton = wrapper.findAll('button').find(btn => btn.text().includes('Bring in Reliever')) if (submitButton) { await submitButton.trigger('click') } expect(wrapper.emitted('submit')).toBeUndefined() }) it('disables fatigued pitcher card with styling', () => { const mixedBench = [ createMockLineup(2, createMockPlayer(201, 'Fatigued Pitcher', ['P']), false, 'P', true), ] const wrapper = mount(PitchingChangeSelector, { props: { ...defaultProps, benchPlayers: mixedBench, }, }) const pitcherCards = wrapper.findAll('.reliever-card') expect(pitcherCards[0].classes()).toContain('reliever-fatigued') expect(pitcherCards[0].attributes('disabled')).toBeDefined() }) }) })