Removed all references to the defensive alignment field across frontend codebase after backend removal in Session 1. The alignment field was determined to be unused and was removed from DefensiveDecision model. Changes: - types/websocket.ts: Removed alignment from DefensiveDecisionRequest interface - composables/useGameActions.ts: Removed alignment from submit handler - pages/demo-decisions.vue: Updated demo state and summary text (alignment → depths) - pages/games/[id].vue: Updated decision history text for both defensive and offensive * Defensive: Now shows "infield depth, outfield depth" instead of "alignment, infield" * Offensive: Updated to use new action field with proper labels (swing_away, hit_and_run, etc.) - Test files (3): Updated all test cases to remove alignment references * tests/unit/composables/useGameActions.spec.ts * tests/unit/store/game-decisions.spec.ts * tests/unit/components/Decisions/DefensiveSetup.spec.ts Also updated offensive decision handling to match Session 2 changes (approach/hit_and_run/bunt_attempt → action field). Total: 7 files modified, all alignment references removed Verified: Zero remaining alignment references in .ts/.vue/.js files 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude <noreply@anthropic.com>
330 lines
8.8 KiB
TypeScript
330 lines
8.8 KiB
TypeScript
import { describe, it, expect, beforeEach } from 'vitest'
|
|
import { mount } from '@vue/test-utils'
|
|
import DefensiveSetup from '~/components/Decisions/DefensiveSetup.vue'
|
|
import type { DefensiveDecision } from '~/types/game'
|
|
|
|
describe('DefensiveSetup', () => {
|
|
const defaultProps = {
|
|
gameId: 'test-game-123',
|
|
isActive: true,
|
|
}
|
|
|
|
describe('Rendering', () => {
|
|
it('renders component with header', () => {
|
|
const wrapper = mount(DefensiveSetup, {
|
|
props: defaultProps,
|
|
})
|
|
|
|
expect(wrapper.text()).toContain('Defensive Setup')
|
|
})
|
|
|
|
it('shows opponent turn indicator when not active', () => {
|
|
const wrapper = mount(DefensiveSetup, {
|
|
props: {
|
|
...defaultProps,
|
|
isActive: false,
|
|
},
|
|
})
|
|
|
|
expect(wrapper.text()).toContain("Opponent's Turn")
|
|
})
|
|
|
|
it('renders all form sections', () => {
|
|
const wrapper = mount(DefensiveSetup, {
|
|
props: defaultProps,
|
|
})
|
|
|
|
expect(wrapper.text()).toContain('Infield Depth')
|
|
expect(wrapper.text()).toContain('Outfield Depth')
|
|
expect(wrapper.text()).toContain('Hold Runners')
|
|
})
|
|
})
|
|
|
|
describe('Initial Values', () => {
|
|
it('uses default values when no currentSetup provided', () => {
|
|
const wrapper = mount(DefensiveSetup, {
|
|
props: defaultProps,
|
|
})
|
|
|
|
// Check preview shows defaults
|
|
expect(wrapper.text()).toContain('Normal')
|
|
})
|
|
|
|
it('uses provided currentSetup values', () => {
|
|
const currentSetup: DefensiveDecision = {
|
|
infield_depth: 'back',
|
|
outfield_depth: 'normal',
|
|
hold_runners: [1, 3],
|
|
}
|
|
|
|
const wrapper = mount(DefensiveSetup, {
|
|
props: {
|
|
...defaultProps,
|
|
currentSetup,
|
|
},
|
|
})
|
|
|
|
expect(wrapper.vm.localSetup.infield_depth).toBe('back')
|
|
expect(wrapper.vm.localSetup.outfield_depth).toBe('normal')
|
|
expect(wrapper.vm.localSetup.hold_runners).toEqual([1, 3])
|
|
})
|
|
})
|
|
|
|
describe('Hold Runners', () => {
|
|
it('initializes hold runner toggles from currentSetup', () => {
|
|
const wrapper = mount(DefensiveSetup, {
|
|
props: {
|
|
...defaultProps,
|
|
currentSetup: {
|
|
infield_depth: 'normal',
|
|
outfield_depth: 'normal',
|
|
hold_runners: [1, 2],
|
|
},
|
|
},
|
|
})
|
|
|
|
expect(wrapper.vm.holdFirst).toBe(true)
|
|
expect(wrapper.vm.holdSecond).toBe(true)
|
|
expect(wrapper.vm.holdThird).toBe(false)
|
|
})
|
|
|
|
it('updates hold_runners array when toggles change', async () => {
|
|
const wrapper = mount(DefensiveSetup, {
|
|
props: defaultProps,
|
|
})
|
|
|
|
wrapper.vm.holdFirst = true
|
|
wrapper.vm.holdThird = true
|
|
await wrapper.vm.$nextTick()
|
|
|
|
expect(wrapper.vm.localSetup.hold_runners).toContain(1)
|
|
expect(wrapper.vm.localSetup.hold_runners).toContain(3)
|
|
expect(wrapper.vm.localSetup.hold_runners).not.toContain(2)
|
|
})
|
|
})
|
|
|
|
describe('Preview Display', () => {
|
|
it('displays current infield depth in preview', () => {
|
|
const wrapper = mount(DefensiveSetup, {
|
|
props: {
|
|
...defaultProps,
|
|
currentSetup: {
|
|
infield_depth: 'back',
|
|
outfield_depth: 'normal',
|
|
hold_runners: [],
|
|
},
|
|
},
|
|
})
|
|
|
|
expect(wrapper.text()).toContain('Back')
|
|
})
|
|
|
|
it('displays holding status for multiple runners', () => {
|
|
const wrapper = mount(DefensiveSetup, {
|
|
props: {
|
|
...defaultProps,
|
|
currentSetup: {
|
|
infield_depth: 'normal',
|
|
outfield_depth: 'normal',
|
|
hold_runners: [1, 2, 3],
|
|
},
|
|
},
|
|
})
|
|
|
|
const holdingText = wrapper.vm.holdingDisplay
|
|
expect(holdingText).toContain('1st')
|
|
expect(holdingText).toContain('2nd')
|
|
expect(holdingText).toContain('3rd')
|
|
})
|
|
|
|
it('shows "None" when no runners held', () => {
|
|
const wrapper = mount(DefensiveSetup, {
|
|
props: defaultProps,
|
|
})
|
|
|
|
expect(wrapper.vm.holdingDisplay).toBe('None')
|
|
})
|
|
})
|
|
|
|
describe('Form Submission', () => {
|
|
it('emits submit event with current setup', async () => {
|
|
const wrapper = mount(DefensiveSetup, {
|
|
props: defaultProps,
|
|
})
|
|
|
|
wrapper.vm.localSetup = {
|
|
infield_depth: 'in',
|
|
outfield_depth: 'normal',
|
|
hold_runners: [2],
|
|
}
|
|
|
|
await wrapper.find('form').trigger('submit.prevent')
|
|
|
|
expect(wrapper.emitted('submit')).toBeTruthy()
|
|
const emitted = wrapper.emitted('submit')![0][0] as DefensiveDecision
|
|
expect(emitted.infield_depth).toBe('in')
|
|
expect(emitted.outfield_depth).toBe('normal')
|
|
expect(emitted.hold_runners).toEqual([2])
|
|
})
|
|
|
|
it('does not submit when not active', async () => {
|
|
const wrapper = mount(DefensiveSetup, {
|
|
props: {
|
|
...defaultProps,
|
|
isActive: false,
|
|
},
|
|
})
|
|
|
|
await wrapper.find('form').trigger('submit.prevent')
|
|
expect(wrapper.emitted('submit')).toBeFalsy()
|
|
})
|
|
|
|
it('does not submit when no changes', async () => {
|
|
const currentSetup: DefensiveDecision = {
|
|
infield_depth: 'normal',
|
|
outfield_depth: 'normal',
|
|
hold_runners: [],
|
|
}
|
|
|
|
const wrapper = mount(DefensiveSetup, {
|
|
props: {
|
|
...defaultProps,
|
|
currentSetup,
|
|
},
|
|
})
|
|
|
|
await wrapper.find('form').trigger('submit.prevent')
|
|
expect(wrapper.emitted('submit')).toBeFalsy()
|
|
})
|
|
|
|
it('shows loading state during submission', async () => {
|
|
const wrapper = mount(DefensiveSetup, {
|
|
props: defaultProps,
|
|
})
|
|
|
|
// Trigger submission
|
|
wrapper.vm.submitting = true
|
|
await wrapper.vm.$nextTick()
|
|
|
|
// Verify button is in loading state
|
|
expect(wrapper.vm.submitting).toBe(true)
|
|
})
|
|
})
|
|
|
|
describe('Submit Button State', () => {
|
|
it('shows "Wait for Your Turn" when not active', () => {
|
|
const wrapper = mount(DefensiveSetup, {
|
|
props: {
|
|
...defaultProps,
|
|
isActive: false,
|
|
},
|
|
})
|
|
|
|
expect(wrapper.vm.submitButtonText).toBe('Wait for Your Turn')
|
|
})
|
|
|
|
it('shows "No Changes" when setup unchanged', () => {
|
|
const currentSetup: DefensiveDecision = {
|
|
infield_depth: 'normal',
|
|
outfield_depth: 'normal',
|
|
hold_runners: [],
|
|
}
|
|
|
|
const wrapper = mount(DefensiveSetup, {
|
|
props: {
|
|
...defaultProps,
|
|
currentSetup,
|
|
},
|
|
})
|
|
|
|
expect(wrapper.vm.submitButtonText).toBe('No Changes')
|
|
})
|
|
|
|
it('shows "Submit Defensive Setup" when active with changes', () => {
|
|
const wrapper = mount(DefensiveSetup, {
|
|
props: defaultProps,
|
|
})
|
|
|
|
wrapper.vm.localSetup.infield_depth = 'back'
|
|
expect(wrapper.vm.submitButtonText).toBe('Submit Defensive Setup')
|
|
})
|
|
})
|
|
|
|
describe('Change Detection', () => {
|
|
it('detects infield depth changes', () => {
|
|
const wrapper = mount(DefensiveSetup, {
|
|
props: {
|
|
...defaultProps,
|
|
currentSetup: {
|
|
infield_depth: 'normal',
|
|
outfield_depth: 'normal',
|
|
hold_runners: [],
|
|
},
|
|
},
|
|
})
|
|
|
|
expect(wrapper.vm.hasChanges).toBe(false)
|
|
wrapper.vm.localSetup.infield_depth = 'back'
|
|
expect(wrapper.vm.hasChanges).toBe(true)
|
|
})
|
|
|
|
it('detects hold runners changes', () => {
|
|
const wrapper = mount(DefensiveSetup, {
|
|
props: {
|
|
...defaultProps,
|
|
currentSetup: {
|
|
infield_depth: 'normal',
|
|
outfield_depth: 'normal',
|
|
hold_runners: [],
|
|
},
|
|
},
|
|
})
|
|
|
|
expect(wrapper.vm.hasChanges).toBe(false)
|
|
wrapper.vm.localSetup.hold_runners = [1]
|
|
expect(wrapper.vm.hasChanges).toBe(true)
|
|
})
|
|
})
|
|
|
|
describe('Prop Updates', () => {
|
|
it('updates local state when currentSetup prop changes', async () => {
|
|
const wrapper = mount(DefensiveSetup, {
|
|
props: defaultProps,
|
|
})
|
|
|
|
const newSetup: DefensiveDecision = {
|
|
infield_depth: 'double_play',
|
|
outfield_depth: 'normal',
|
|
hold_runners: [1, 2, 3],
|
|
}
|
|
|
|
await wrapper.setProps({ currentSetup: newSetup })
|
|
|
|
expect(wrapper.vm.localSetup.infield_depth).toBe('double_play')
|
|
expect(wrapper.vm.localSetup.outfield_depth).toBe('normal')
|
|
expect(wrapper.vm.localSetup.hold_runners).toEqual([1, 2, 3])
|
|
})
|
|
})
|
|
|
|
describe('Disabled State', () => {
|
|
it('disables all controls when not active', () => {
|
|
const wrapper = mount(DefensiveSetup, {
|
|
props: {
|
|
...defaultProps,
|
|
isActive: false,
|
|
},
|
|
})
|
|
|
|
const buttonGroups = wrapper.findAllComponents({ name: 'ButtonGroup' })
|
|
buttonGroups.forEach(bg => {
|
|
expect(bg.props('disabled')).toBe(true)
|
|
})
|
|
|
|
const toggles = wrapper.findAllComponents({ name: 'ToggleSwitch' })
|
|
toggles.forEach(toggle => {
|
|
expect(toggle.props('disabled')).toBe(true)
|
|
})
|
|
})
|
|
})
|
|
})
|