import { describe, it, expect } from 'vitest' import { mount } from '@vue/test-utils' import ManualOutcomeEntry from '~/components/Gameplay/ManualOutcomeEntry.vue' import type { RollData, PlayOutcome } from '~/types' // TODO: Fix text rendering (lowercase vs capitalized) and DOM element selection issues describe.skip('ManualOutcomeEntry', () => { const createRollData = (): RollData => ({ roll_id: 'test-roll-123', d6_one: 3, d6_two_a: 2, d6_two_b: 4, d6_two_total: 6, chaos_d20: 15, resolution_d20: 8, check_wild_pitch: false, check_passed_ball: false, timestamp: '2025-01-13T12:00:00Z', }) // ============================================================================ // Rendering Tests // ============================================================================ describe('Rendering', () => { it('renders outcome entry form', () => { const wrapper = mount(ManualOutcomeEntry, { props: { rollData: createRollData(), canSubmit: true, }, }) expect(wrapper.find('.manual-outcome-entry').exists()).toBe(true) expect(wrapper.text()).toContain('Select Outcome') }) it('renders all outcome categories', () => { const wrapper = mount(ManualOutcomeEntry, { props: { rollData: createRollData(), canSubmit: true, }, }) expect(wrapper.text()).toContain('Outs') expect(wrapper.text()).toContain('Hits') expect(wrapper.text()).toContain('Walks / HBP') expect(wrapper.text()).toContain('Special') expect(wrapper.text()).toContain('Interrupts') }) it('renders action buttons', () => { const wrapper = mount(ManualOutcomeEntry, { props: { rollData: createRollData(), canSubmit: true, }, }) const buttons = wrapper.findAll('.button') expect(buttons).toHaveLength(2) expect(wrapper.find('.button-cancel').text()).toBe('Cancel') expect(buttons[1].text()).toBe('Submit Outcome') }) it('does not render hit location by default', () => { const wrapper = mount(ManualOutcomeEntry, { props: { rollData: createRollData(), canSubmit: true, }, }) expect(wrapper.find('.hit-location-section').exists()).toBe(false) }) }) // ============================================================================ // Outcome Selection Tests // ============================================================================ describe('Outcome Selection', () => { it('selects outcome when button clicked', async () => { const wrapper = mount(ManualOutcomeEntry, { props: { rollData: createRollData(), canSubmit: true, }, }) const strikeoutButton = wrapper.findAll('.outcome-button').find(btn => btn.text().includes('Strikeout') ) await strikeoutButton?.trigger('click') expect(strikeoutButton?.classes()).toContain('outcome-button-selected') }) it('deselects previous outcome when new one selected', async () => { const wrapper = mount(ManualOutcomeEntry, { props: { rollData: createRollData(), canSubmit: true, }, }) const buttons = wrapper.findAll('.outcome-button') const strikeoutButton = buttons.find(btn => btn.text().includes('Strikeout')) const walkButton = buttons.find(btn => btn.text() === 'Walk') await strikeoutButton?.trigger('click') expect(strikeoutButton?.classes()).toContain('outcome-button-selected') await walkButton?.trigger('click') expect(strikeoutButton?.classes()).not.toContain('outcome-button-selected') expect(walkButton?.classes()).toContain('outcome-button-selected') }) it('formats outcome names correctly', () => { const wrapper = mount(ManualOutcomeEntry, { props: { rollData: createRollData(), canSubmit: true, }, }) expect(wrapper.text()).toContain('Hit By Pitch') expect(wrapper.text()).toContain('Intentional Walk') expect(wrapper.text()).toContain('Double Play') }) }) // ============================================================================ // Hit Location Tests // ============================================================================ describe('Hit Location', () => { it('shows hit location for GROUNDOUT', async () => { const wrapper = mount(ManualOutcomeEntry, { props: { rollData: createRollData(), canSubmit: true, }, }) const outcomeButton = wrapper.findAll('.outcome-button').find(btn => btn.text().includes('Groundout') ) await outcomeButton?.trigger('click') expect(wrapper.find('.hit-location-section').exists()).toBe(true) }) it('shows hit location for FLYOUT', async () => { const wrapper = mount(ManualOutcomeEntry, { props: { rollData: createRollData(), canSubmit: true, }, }) const outcomeButton = wrapper.findAll('.outcome-button').find(btn => btn.text().includes('Flyout') ) await outcomeButton?.trigger('click') expect(wrapper.find('.hit-location-section').exists()).toBe(true) }) it('shows hit location for SINGLE', async () => { const wrapper = mount(ManualOutcomeEntry, { props: { rollData: createRollData(), canSubmit: true, }, }) const outcomeButton = wrapper.findAll('.outcome-button').find(btn => btn.text() === 'Single 1' ) await outcomeButton?.trigger('click') expect(wrapper.find('.hit-location-section').exists()).toBe(true) }) it('shows hit location for ERROR', async () => { const wrapper = mount(ManualOutcomeEntry, { props: { rollData: createRollData(), canSubmit: true, }, }) const outcomeButton = wrapper.findAll('.outcome-button').find(btn => btn.text().includes('Error') ) await outcomeButton?.trigger('click') expect(wrapper.find('.hit-location-section').exists()).toBe(true) }) it('hides hit location for strikeout', async () => { const wrapper = mount(ManualOutcomeEntry, { props: { rollData: createRollData(), canSubmit: true, }, }) const strikeoutButton = wrapper.findAll('.outcome-button').find(btn => btn.text().includes('Strikeout') ) await strikeoutButton?.trigger('click') expect(wrapper.find('.hit-location-section').exists()).toBe(false) }) it('renders all infield positions', async () => { const wrapper = mount(ManualOutcomeEntry, { props: { rollData: createRollData(), canSubmit: true, }, }) const groundoutButton = wrapper.findAll('.outcome-button').find(btn => btn.text().includes('Groundout') ) await groundoutButton?.trigger('click') expect(wrapper.text()).toContain('P') expect(wrapper.text()).toContain('C') expect(wrapper.text()).toContain('1B') expect(wrapper.text()).toContain('2B') expect(wrapper.text()).toContain('3B') expect(wrapper.text()).toContain('SS') }) it('renders all outfield positions', async () => { const wrapper = mount(ManualOutcomeEntry, { props: { rollData: createRollData(), canSubmit: true, }, }) const flyoutButton = wrapper.findAll('.outcome-button').find(btn => btn.text().includes('Flyout') ) await flyoutButton?.trigger('click') expect(wrapper.text()).toContain('LF') expect(wrapper.text()).toContain('CF') expect(wrapper.text()).toContain('RF') }) it('selects hit location when clicked', async () => { const wrapper = mount(ManualOutcomeEntry, { props: { rollData: createRollData(), canSubmit: true, }, }) const groundoutButton = wrapper.findAll('.outcome-button').find(btn => btn.text().includes('Groundout') ) await groundoutButton?.trigger('click') const ssButton = wrapper.findAll('.location-button').find(btn => btn.text() === 'SS' ) await ssButton?.trigger('click') expect(ssButton?.classes()).toContain('location-button-selected') }) it('clears hit location when outcome changes to one not needing location', async () => { const wrapper = mount(ManualOutcomeEntry, { props: { rollData: createRollData(), canSubmit: true, }, }) // Select groundout (needs location) const groundoutButton = wrapper.findAll('.outcome-button').find(btn => btn.text().includes('Groundout') ) await groundoutButton?.trigger('click') const ssButton = wrapper.findAll('.location-button').find(btn => btn.text() === 'SS' ) await ssButton?.trigger('click') // Select strikeout (doesn't need location) const strikeoutButton = wrapper.findAll('.outcome-button').find(btn => btn.text().includes('Strikeout') ) await strikeoutButton?.trigger('click') expect(wrapper.find('.hit-location-section').exists()).toBe(false) }) }) // ============================================================================ // Submit Button State Tests // ============================================================================ describe('Submit Button State', () => { it('disables submit when no outcome selected', () => { const wrapper = mount(ManualOutcomeEntry, { props: { rollData: createRollData(), canSubmit: true, }, }) const submitButton = wrapper.findAll('.button')[1] expect(submitButton.classes()).toContain('button-submit-disabled') expect(submitButton.attributes('disabled')).toBeDefined() }) it('enables submit when outcome selected (no location needed)', async () => { const wrapper = mount(ManualOutcomeEntry, { props: { rollData: createRollData(), canSubmit: true, }, }) const strikeoutButton = wrapper.findAll('.outcome-button').find(btn => btn.text().includes('Strikeout') ) await strikeoutButton?.trigger('click') const submitButton = wrapper.findAll('.button')[1] expect(submitButton.classes()).toContain('button-submit') expect(submitButton.attributes('disabled')).toBeUndefined() }) it('disables submit when outcome selected but location needed and not selected', async () => { const wrapper = mount(ManualOutcomeEntry, { props: { rollData: createRollData(), canSubmit: true, }, }) const groundoutButton = wrapper.findAll('.outcome-button').find(btn => btn.text().includes('Groundout') ) await groundoutButton?.trigger('click') const submitButton = wrapper.findAll('.button')[1] expect(submitButton.classes()).toContain('button-submit-disabled') expect(submitButton.attributes('disabled')).toBeDefined() }) it('enables submit when outcome and location both selected', async () => { const wrapper = mount(ManualOutcomeEntry, { props: { rollData: createRollData(), canSubmit: true, }, }) const groundoutButton = wrapper.findAll('.outcome-button').find(btn => btn.text().includes('Groundout') ) await groundoutButton?.trigger('click') const ssButton = wrapper.findAll('.location-button').find(btn => btn.text() === 'SS' ) await ssButton?.trigger('click') const submitButton = wrapper.findAll('.button')[1] expect(submitButton.classes()).toContain('button-submit') expect(submitButton.attributes('disabled')).toBeUndefined() }) it('disables submit when canSubmit prop is false', async () => { const wrapper = mount(ManualOutcomeEntry, { props: { rollData: createRollData(), canSubmit: false, }, }) const strikeoutButton = wrapper.findAll('.outcome-button').find(btn => btn.text().includes('Strikeout') ) await strikeoutButton?.trigger('click') const submitButton = wrapper.findAll('.button')[1] expect(submitButton.classes()).toContain('button-submit-disabled') }) it('disables submit when rollData is null', async () => { const wrapper = mount(ManualOutcomeEntry, { props: { rollData: null, canSubmit: true, }, }) const strikeoutButton = wrapper.findAll('.outcome-button').find(btn => btn.text().includes('Strikeout') ) await strikeoutButton?.trigger('click') const submitButton = wrapper.findAll('.button')[1] expect(submitButton.classes()).toContain('button-submit-disabled') }) }) // ============================================================================ // Event Emission Tests // ============================================================================ describe('Event Emission', () => { it('emits submit event with outcome (no location)', async () => { const wrapper = mount(ManualOutcomeEntry, { props: { rollData: createRollData(), canSubmit: true, }, }) const strikeoutButton = wrapper.findAll('.outcome-button').find(btn => btn.text().includes('Strikeout') ) await strikeoutButton?.trigger('click') await wrapper.findAll('.button')[1].trigger('click') expect(wrapper.emitted('submit')).toBeTruthy() expect(wrapper.emitted('submit')?.[0]).toEqual([{ outcome: 'STRIKEOUT', hitLocation: undefined, }]) }) it('emits submit event with outcome and location', async () => { const wrapper = mount(ManualOutcomeEntry, { props: { rollData: createRollData(), canSubmit: true, }, }) const groundoutButton = wrapper.findAll('.outcome-button').find(btn => btn.text().includes('Groundout') ) await groundoutButton?.trigger('click') const ssButton = wrapper.findAll('.location-button').find(btn => btn.text() === 'SS' ) await ssButton?.trigger('click') await wrapper.findAll('.button')[1].trigger('click') expect(wrapper.emitted('submit')).toBeTruthy() expect(wrapper.emitted('submit')?.[0]).toEqual([{ outcome: 'GROUNDOUT', hitLocation: 'SS', }]) }) it('emits cancel event when cancel button clicked', async () => { const wrapper = mount(ManualOutcomeEntry, { props: { rollData: createRollData(), canSubmit: true, }, }) await wrapper.find('.button-cancel').trigger('click') expect(wrapper.emitted('cancel')).toBeTruthy() expect(wrapper.emitted('cancel')).toHaveLength(1) }) it('does not emit submit when form invalid', async () => { const wrapper = mount(ManualOutcomeEntry, { props: { rollData: createRollData(), canSubmit: true, }, }) await wrapper.findAll('.button')[1].trigger('click') expect(wrapper.emitted('submit')).toBeFalsy() }) it('resets form after successful submit', async () => { const wrapper = mount(ManualOutcomeEntry, { props: { rollData: createRollData(), canSubmit: true, }, }) const strikeoutButton = wrapper.findAll('.outcome-button').find(btn => btn.text().includes('Strikeout') ) await strikeoutButton?.trigger('click') await wrapper.findAll('.button')[1].trigger('click') expect(strikeoutButton?.classes()).not.toContain('outcome-button-selected') }) it('resets form after cancel', async () => { const wrapper = mount(ManualOutcomeEntry, { props: { rollData: createRollData(), canSubmit: true, }, }) const strikeoutButton = wrapper.findAll('.outcome-button').find(btn => btn.text().includes('Strikeout') ) await strikeoutButton?.trigger('click') await wrapper.find('.button-cancel').trigger('click') expect(strikeoutButton?.classes()).not.toContain('outcome-button-selected') }) }) // ============================================================================ // Outcome Categories Tests // ============================================================================ describe('Outcome Categories', () => { it('renders outs category outcomes', () => { const wrapper = mount(ManualOutcomeEntry, { props: { rollData: createRollData(), canSubmit: true, }, }) expect(wrapper.text()).toContain('Strikeout') expect(wrapper.text()).toContain('Groundout') expect(wrapper.text()).toContain('Flyout') expect(wrapper.text()).toContain('Lineout') expect(wrapper.text()).toContain('Popout') expect(wrapper.text()).toContain('Double Play') }) it('renders hits category outcomes', () => { const wrapper = mount(ManualOutcomeEntry, { props: { rollData: createRollData(), canSubmit: true, }, }) expect(wrapper.text()).toContain('Single 1') expect(wrapper.text()).toContain('Single 2') expect(wrapper.text()).toContain('Double 2') expect(wrapper.text()).toContain('Triple') expect(wrapper.text()).toContain('Homerun') }) it('renders walks category outcomes', () => { const wrapper = mount(ManualOutcomeEntry, { props: { rollData: createRollData(), canSubmit: true, }, }) expect(wrapper.text()).toContain('Walk') expect(wrapper.text()).toContain('Intentional Walk') expect(wrapper.text()).toContain('Hit By Pitch') }) it('renders special category outcomes', () => { const wrapper = mount(ManualOutcomeEntry, { props: { rollData: createRollData(), canSubmit: true, }, }) expect(wrapper.text()).toContain('Error') }) it('renders interrupts category outcomes', () => { const wrapper = mount(ManualOutcomeEntry, { props: { rollData: createRollData(), canSubmit: true, }, }) expect(wrapper.text()).toContain('Stolen Base') expect(wrapper.text()).toContain('Caught Stealing') expect(wrapper.text()).toContain('Wild Pitch') expect(wrapper.text()).toContain('Passed Ball') expect(wrapper.text()).toContain('Balk') expect(wrapper.text()).toContain('Pick Off') }) }) // ============================================================================ // Edge Cases // ============================================================================ describe('Edge Cases', () => { it('handles multiple hit location changes', async () => { const wrapper = mount(ManualOutcomeEntry, { props: { rollData: createRollData(), canSubmit: true, }, }) const groundoutButton = wrapper.findAll('.outcome-button').find(btn => btn.text().includes('Groundout') ) await groundoutButton?.trigger('click') const ssButton = wrapper.findAll('.location-button').find(btn => btn.text() === 'SS' ) const cfButton = wrapper.findAll('.location-button').find(btn => btn.text() === 'CF' ) await ssButton?.trigger('click') expect(ssButton?.classes()).toContain('location-button-selected') await cfButton?.trigger('click') expect(ssButton?.classes()).not.toContain('location-button-selected') expect(cfButton?.classes()).toContain('location-button-selected') }) it('handles rapid outcome selection changes', async () => { const wrapper = mount(ManualOutcomeEntry, { props: { rollData: createRollData(), canSubmit: true, }, }) const buttons = wrapper.findAll('.outcome-button') const strikeoutButton = buttons.find(btn => btn.text().includes('Strikeout')) const walkButton = buttons.find(btn => btn.text() === 'Walk') const homerunButton = buttons.find(btn => btn.text().includes('Homerun')) await strikeoutButton?.trigger('click') await walkButton?.trigger('click') await homerunButton?.trigger('click') expect(homerunButton?.classes()).toContain('outcome-button-selected') expect(strikeoutButton?.classes()).not.toContain('outcome-button-selected') expect(walkButton?.classes()).not.toContain('outcome-button-selected') }) }) })