diff --git a/frontend/vue-app/src/components/child/__tests__/ParentView.spec.ts b/frontend/vue-app/src/components/child/__tests__/ParentView.spec.ts
index dba5d69..aa8f81a 100644
--- a/frontend/vue-app/src/components/child/__tests__/ParentView.spec.ts
+++ b/frontend/vue-app/src/components/child/__tests__/ParentView.spec.ts
@@ -1,6 +1,6 @@
import { describe, it, expect, vi, beforeEach, afterEach } from 'vitest'
import { mount, VueWrapper } from '@vue/test-utils'
-import { nextTick } from 'vue'
+import { nextTick, defineComponent } from 'vue'
import ParentView from '../ParentView.vue'
import { eventBus } from '@/common/eventBus'
@@ -8,6 +8,7 @@ import { eventBus } from '@/common/eventBus'
vi.mock('vue-router', () => ({
useRoute: vi.fn(() => ({
params: { id: 'child-123' },
+ query: {},
})),
useRouter: vi.fn(() => ({
push: vi.fn(),
@@ -17,6 +18,15 @@ vi.mock('vue-router', () => ({
vi.mock('@/common/api', () => ({
setChildOverride: vi.fn(),
parseErrorResponse: vi.fn(() => ({ msg: 'Test error', code: 'TEST_ERROR' })),
+ extendChoreTime: vi.fn(),
+ approveChore: vi.fn(),
+ rejectChore: vi.fn(),
+ resetChore: vi.fn(),
+}))
+
+vi.mock('@/common/imageCache', () => ({
+ getCachedImageUrl: vi.fn(() => Promise.resolve('/mock-image.png')),
+ revokeAllImageUrls: vi.fn(),
}))
global.fetch = vi.fn()
@@ -24,6 +34,51 @@ global.alert = vi.fn()
import { setChildOverride, parseErrorResponse } from '@/common/api'
+// Stub for ScrollingList that exposes the same interface
+const ScrollingListStub = defineComponent({
+ name: 'ScrollingList',
+ props: [
+ 'title',
+ 'fetchBaseUrl',
+ 'ids',
+ 'itemKey',
+ 'imageField',
+ 'imageFields',
+ 'enableEdit',
+ 'childId',
+ 'readyItemId',
+ 'isParentAuthenticated',
+ 'filterFn',
+ 'sortFn',
+ 'getItemClass',
+ ],
+ emits: ['trigger-item', 'edit-item', 'item-ready'],
+ setup(_, { expose }) {
+ const refresh = vi.fn()
+ const scrollToItem = vi.fn()
+ const centerItem = vi.fn()
+ expose({ refresh, items: [], centerItem, scrollToItem })
+ return { refresh, scrollToItem, centerItem }
+ },
+ template: '
',
+})
+
+const mountOptions = {
+ global: {
+ stubs: {
+ ScrollingList: ScrollingListStub,
+ ChildDetailCard: { template: '' },
+ StatusMessage: { template: '' },
+ ModalDialog: { template: '
' },
+ ScheduleModal: { template: '' },
+ PendingRewardDialog: { template: '' },
+ TaskConfirmDialog: { template: '' },
+ RewardConfirmDialog: { template: '' },
+ ChoreApproveDialog: { template: '' },
+ },
+ },
+}
+
describe('ParentView', () => {
let wrapper: VueWrapper
@@ -91,7 +146,7 @@ describe('ParentView', () => {
describe('Component Mounting', () => {
it('loads and displays child data on mount', async () => {
- wrapper = mount(ParentView)
+ wrapper = mount(ParentView, mountOptions)
await nextTick()
await new Promise((resolve) => setTimeout(resolve, 50))
@@ -100,7 +155,7 @@ describe('ParentView', () => {
it('registers SSE event listeners on mount', async () => {
const onSpy = vi.spyOn(eventBus, 'on')
- wrapper = mount(ParentView)
+ wrapper = mount(ParentView, mountOptions)
await nextTick()
expect(onSpy).toHaveBeenCalledWith('child_task_triggered', expect.any(Function))
@@ -111,7 +166,7 @@ describe('ParentView', () => {
it('unregisters SSE event listeners on unmount', async () => {
const offSpy = vi.spyOn(eventBus, 'off')
- wrapper = mount(ParentView)
+ wrapper = mount(ParentView, mountOptions)
await nextTick()
wrapper.unmount()
@@ -123,7 +178,7 @@ describe('ParentView', () => {
describe('Override Modal', () => {
beforeEach(async () => {
- wrapper = mount(ParentView)
+ wrapper = mount(ParentView, mountOptions)
await nextTick()
await new Promise((resolve) => setTimeout(resolve, 50))
})
@@ -184,7 +239,7 @@ describe('ParentView', () => {
describe('Save Override', () => {
beforeEach(async () => {
- wrapper = mount(ParentView)
+ wrapper = mount(ParentView, mountOptions)
await nextTick()
await new Promise((resolve) => setTimeout(resolve, 50))
})
@@ -229,7 +284,7 @@ describe('ParentView', () => {
describe('SSE Event Handlers', () => {
beforeEach(async () => {
- wrapper = mount(ParentView)
+ wrapper = mount(ParentView, mountOptions)
await nextTick()
await new Promise((resolve) => setTimeout(resolve, 50))
})
@@ -248,13 +303,15 @@ describe('ParentView', () => {
})
it('handles child_override_set event and refreshes appropriate lists', async () => {
- const mockChoreRefresh = vi.fn()
- const mockPenaltyRefresh = vi.fn()
- const mockRewardRefresh = vi.fn()
+ const mockChoreRefresh = vi.fn().mockResolvedValue(undefined)
+ const mockPenaltyRefresh = vi.fn().mockResolvedValue(undefined)
+ const mockRewardRefresh = vi.fn().mockResolvedValue(undefined)
+ const mockKindnessRefresh = vi.fn().mockResolvedValue(undefined)
- wrapper.vm.childChoreListRef = { refresh: mockChoreRefresh }
- wrapper.vm.childPenaltyListRef = { refresh: mockPenaltyRefresh }
- wrapper.vm.childRewardListRef = { refresh: mockRewardRefresh }
+ wrapper.vm.childChoreListRef = { refresh: mockChoreRefresh, scrollToItem: vi.fn() }
+ wrapper.vm.childKindnessListRef = { refresh: mockKindnessRefresh, scrollToItem: vi.fn() }
+ wrapper.vm.childPenaltyListRef = { refresh: mockPenaltyRefresh, scrollToItem: vi.fn() }
+ wrapper.vm.childRewardListRef = { refresh: mockRewardRefresh, scrollToItem: vi.fn() }
// Test task override
wrapper.vm.handleOverrideSet({
@@ -318,7 +375,7 @@ describe('ParentView', () => {
describe('Ready Item State Management', () => {
beforeEach(async () => {
- wrapper = mount(ParentView)
+ wrapper = mount(ParentView, mountOptions)
await nextTick()
await new Promise((resolve) => setTimeout(resolve, 50))
})
@@ -339,7 +396,7 @@ describe('ParentView', () => {
describe('Penalty Display', () => {
it('displays penalty values as negative in template', async () => {
- wrapper = mount(ParentView)
+ wrapper = mount(ParentView, mountOptions)
await nextTick()
// The template should show -custom_value or -points for penalties
@@ -361,7 +418,7 @@ describe('ParentView', () => {
}
beforeEach(async () => {
- wrapper = mount(ParentView)
+ wrapper = mount(ParentView, mountOptions)
await nextTick()
await new Promise((resolve) => setTimeout(resolve, 50))
})
@@ -453,7 +510,7 @@ describe('ParentView', () => {
describe('Chore Card Selection (selectedChoreId)', () => {
beforeEach(async () => {
- wrapper = mount(ParentView)
+ wrapper = mount(ParentView, mountOptions)
await nextTick()
await new Promise((resolve) => setTimeout(resolve, 50))
})
diff --git a/frontend/vue-app/src/components/shared/ScrollingList.vue b/frontend/vue-app/src/components/shared/ScrollingList.vue
index 47d7536..1a9e0de 100644
--- a/frontend/vue-app/src/components/shared/ScrollingList.vue
+++ b/frontend/vue-app/src/components/shared/ScrollingList.vue
@@ -89,13 +89,6 @@ async function refresh() {
loading.value = false
}
-defineExpose({
- refresh,
- items,
- centerItem,
- scrollToItem,
-})
-
const scrollToItem = async (itemId: string) => {
await nextTick()
const card = itemRefs.value[itemId]
@@ -123,6 +116,13 @@ const centerItem = async (itemId: string) => {
}
}
+defineExpose({
+ refresh,
+ items,
+ centerItem,
+ scrollToItem,
+})
+
const handleClicked = async (item: any) => {
await nextTick()
const wrapper = scrollWrapper.value