From ac29ee9bd0d891ad24ef02ee9e310a52a43b241a Mon Sep 17 00:00:00 2001 From: Ryan Kegel Date: Tue, 24 Mar 2026 16:38:06 -0400 Subject: [PATCH] fix: update ParentView tests to use mount options and improve mock implementations --- .../child/__tests__/ParentView.spec.ts | 91 +++++++++++++++---- .../src/components/shared/ScrollingList.vue | 14 +-- 2 files changed, 81 insertions(+), 24 deletions(-) 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