Files
chore/frontend/vue-app/src/components/__tests__/OverrideEditModal.spec.ts
Ryan Kegel 401c21ad82
All checks were successful
Gitea Actions Demo / build-and-push (push) Successful in 25s
feat: add PendingRewardDialog, RewardConfirmDialog, and TaskConfirmDialog components
- Implemented PendingRewardDialog for handling pending reward requests.
- Created RewardConfirmDialog for confirming reward redemption.
- Developed TaskConfirmDialog for task confirmation with child name display.

test: add unit tests for ChildView and ParentView components

- Added comprehensive tests for ChildView including task triggering and SSE event handling.
- Implemented tests for ParentView focusing on override modal and SSE event management.

test: add ScrollingList component tests

- Created tests for ScrollingList to verify item fetching, loading states, and custom item classes.
- Included tests for two-step click interactions and edit button display logic.
- Moved toward hashed passwords.
2026-02-10 20:21:05 -05:00

209 lines
7.1 KiB
TypeScript

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<any>
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')
})
})
})