import { describe, it, expect, vi, beforeEach, afterEach } from 'vitest' import { mount, VueWrapper } from '@vue/test-utils' import { nextTick } from 'vue' import OverrideEditModal from '../OverrideEditModal.vue' // Mock API functions vi.mock('@/common/api', () => ({ setChildOverride: vi.fn(), parseErrorResponse: vi.fn(() => ({ msg: 'Test error', code: 'TEST_ERROR' })), })) import { setChildOverride } from '@/common/api' global.alert = vi.fn() describe('OverrideEditModal', () => { let wrapper: VueWrapper const defaultProps = { isOpen: true, childId: 'child-123', entityId: 'task-456', entityType: 'task' as 'task' | 'reward', entityName: 'Test Task', defaultValue: 100, currentOverride: undefined, } beforeEach(() => { vi.clearAllMocks() }) afterEach(() => { if (wrapper) { wrapper.unmount() } }) describe('Modal Display', () => { it('renders when isOpen is true', () => { wrapper = mount(OverrideEditModal, { props: defaultProps }) expect(wrapper.find('.modal-backdrop').exists()).toBe(true) expect(wrapper.text()).toContain('Test Task') }) it('does not render when isOpen is false', () => { wrapper = mount(OverrideEditModal, { props: { ...defaultProps, isOpen: false } }) expect(wrapper.find('.modal-backdrop').exists()).toBe(false) }) it('displays entity information correctly for tasks', () => { wrapper = mount(OverrideEditModal, { props: defaultProps }) expect(wrapper.text()).toContain('Test Task') expect(wrapper.text()).toContain('New Points') }) it('displays entity information correctly for rewards', () => { wrapper = mount(OverrideEditModal, { props: { ...defaultProps, entityType: 'reward', entityName: 'Test Reward' }, }) expect(wrapper.text()).toContain('Test Reward') expect(wrapper.text()).toContain('New Cost') }) }) describe('Input Validation', () => { it('initializes with default value when no override exists', async () => { wrapper = mount(OverrideEditModal, { props: defaultProps }) await nextTick() const input = wrapper.find('input[type="number"]') expect((input.element as HTMLInputElement).value).toBe('100') }) it('initializes with current override value when it exists', async () => { wrapper = mount(OverrideEditModal, { props: { ...defaultProps, currentOverride: 150 }, }) await nextTick() const input = wrapper.find('input[type="number"]') expect((input.element as HTMLInputElement).value).toBe('150') }) it('validates input within range (0-10000)', async () => { wrapper = mount(OverrideEditModal, { props: defaultProps }) const input = wrapper.find('input[type="number"]') const saveButton = wrapper.findAll('button').find((btn) => btn.text() === 'Save') // Valid value await input.setValue(5000) await nextTick() expect(saveButton?.attributes('disabled')).toBeUndefined() // Zero is valid await input.setValue(0) await nextTick() expect(saveButton?.attributes('disabled')).toBeUndefined() // Max is valid await input.setValue(10000) await nextTick() expect(saveButton?.attributes('disabled')).toBeUndefined() }) it('shows error for values outside range', async () => { wrapper = mount(OverrideEditModal, { props: defaultProps }) const input = wrapper.find('input[type="number"]') // Above max await input.setValue(10001) await nextTick() expect(wrapper.text()).toContain('Value must be between 0 and 10000') const saveButton = wrapper.findAll('button').find((btn) => btn.text() === 'Save') expect(saveButton?.attributes('disabled')).toBeDefined() }) }) describe('User Interactions', () => { it('emits close event when Cancel is clicked', async () => { wrapper = mount(OverrideEditModal, { props: defaultProps }) const cancelButton = wrapper.findAll('button').find((btn) => btn.text() === 'Cancel') await cancelButton?.trigger('click') expect(wrapper.emitted('close')).toBeTruthy() }) it('emits close event when clicking backdrop', async () => { wrapper = mount(OverrideEditModal, { props: defaultProps }) await wrapper.find('.modal-backdrop').trigger('click') expect(wrapper.emitted('close')).toBeTruthy() }) it('does not close when clicking modal dialog', async () => { wrapper = mount(OverrideEditModal, { props: defaultProps }) await wrapper.find('.modal-dialog').trigger('click') expect(wrapper.emitted('close')).toBeFalsy() }) it('calls API and emits events on successful save', async () => { ;(setChildOverride as any).mockResolvedValue({ ok: true }) wrapper = mount(OverrideEditModal, { props: defaultProps }) const input = wrapper.find('input[type="number"]') await input.setValue(250) await nextTick() const saveButton = wrapper.findAll('button').find((btn) => btn.text() === 'Save') await saveButton?.trigger('click') await nextTick() expect(setChildOverride).toHaveBeenCalledWith('child-123', 'task-456', 'task', 250) expect(wrapper.emitted('saved')).toBeTruthy() expect(wrapper.emitted('close')).toBeTruthy() }) it('shows alert on API error', async () => { ;(setChildOverride as any).mockResolvedValue({ ok: false, status: 400 }) wrapper = mount(OverrideEditModal, { props: defaultProps }) const saveButton = wrapper.findAll('button').find((btn) => btn.text() === 'Save') await saveButton?.trigger('click') await nextTick() expect(global.alert).toHaveBeenCalledWith('Error: Test error') expect(wrapper.emitted('saved')).toBeFalsy() }) it('does not save when validation fails', async () => { wrapper = mount(OverrideEditModal, { props: defaultProps }) const input = wrapper.find('input[type="number"]') await input.setValue(20000) await nextTick() const saveButton = wrapper.findAll('button').find((btn) => btn.text() === 'Save') await saveButton?.trigger('click') await nextTick() expect(setChildOverride).not.toHaveBeenCalled() }) }) describe('Modal State Updates', () => { it('reinitializes value when modal reopens', async () => { wrapper = mount(OverrideEditModal, { props: { ...defaultProps, isOpen: false } }) await nextTick() await wrapper.setProps({ isOpen: true }) await nextTick() const input = wrapper.find('input[type="number"]') expect((input.element as HTMLInputElement).value).toBe('100') }) it('uses updated currentOverride when modal reopens', async () => { wrapper = mount(OverrideEditModal, { props: { ...defaultProps, isOpen: true, currentOverride: 200 }, }) await nextTick() await wrapper.setProps({ isOpen: false }) await nextTick() await wrapper.setProps({ isOpen: true, currentOverride: 300 }) await nextTick() const input = wrapper.find('input[type="number"]') expect((input.element as HTMLInputElement).value).toBe('300') }) }) })