// @vitest-environment jsdom import React from 'react'; import { describe, it, expect, vi, beforeEach } from 'vitest'; import { fireEvent, render, screen, waitFor } from '@testing-library/react'; import TimeSheetClient from '../src/components/time-management/time-entry/time-sheet/TimeSheetClient'; const { refresh, push, saveTimeEntry, fetchOrCreateTimeSheet, fetchEligibleTimeEntrySubjects, fetchTimeSheet, reverseTimeSheetApproval, toastSuccess, handleError } = vi.hoisted(() => ({ refresh: vi.fn(), push: vi.fn(), saveTimeEntry: vi.fn(), fetchOrCreateTimeSheet: vi.fn(), fetchEligibleTimeEntrySubjects: vi.fn(), fetchTimeSheet: vi.fn(), reverseTimeSheetApproval: vi.fn(), toastSuccess: vi.fn(), handleError: vi.fn(), })); vi.mock('next/navigation', () => ({ useRouter: () => ({ refresh, push }), useSearchParams: () => ({ get: () => null }), })); vi.mock('@alga-psa/ui/lib/i18n/client', () => ({ useTranslation: () => ({ t: (_key: string, options?: { defaultValue?: string }) => options?.defaultValue ?? _key, }), })); vi.mock('@alga-psa/ui/hooks', () => ({ useFeatureFlag: () => ({ enabled: false, loading: false }), })); vi.mock('@alga-psa/scheduling/actions/timeEntryActions', () => ({ saveTimeEntry, fetchOrCreateTimeSheet, })); vi.mock('@alga-psa/scheduling/actions/timeEntryDelegationActions', () => ({ fetchEligibleTimeEntrySubjects, })); vi.mock('@alga-psa/scheduling/actions/timeSheetActions', () => ({ fetchTimeSheet, reverseTimeSheetApproval, })); vi.mock('react-hot-toast', () => ({ toast: { success: toastSuccess, }, })); vi.mock('@alga-psa/ui/lib/errorHandling', () => ({ handleError, })); vi.mock('../src/components/time-management/time-entry/time-sheet/TimeSheet', () => ({ TimeSheet: ({ timeSheet, onReopenForEdits }: any) => (
{timeSheet.approval_status}
), })); vi.mock('@alga-psa/ui/components/ConfirmationDialog', () => ({ ConfirmationDialog: ({ isOpen, onConfirm, onClose }: any) => isOpen ? (
) : null, })); describe('TimeSheetClient reopen flow', () => { const currentUser = { user_id: 'user-1', first_name: 'Test', last_name: 'User', email: 'test@example.com', }; const approvedTimeSheet = { id: 'timesheet-1', user_id: 'user-1', period_id: 'period-1', approval_status: 'APPROVED', }; const reopenedTimeSheet = { ...approvedTimeSheet, approval_status: 'CHANGES_REQUESTED', }; beforeEach(() => { vi.clearAllMocks(); reverseTimeSheetApproval.mockResolvedValue(undefined); fetchTimeSheet.mockResolvedValue(reopenedTimeSheet); saveTimeEntry.mockResolvedValue(undefined); fetchOrCreateTimeSheet.mockResolvedValue(approvedTimeSheet); fetchEligibleTimeEntrySubjects.mockResolvedValue([]); }); it('updates the rendered timesheet immediately after reopen without requiring a refresh', async () => { render( ); expect(screen.getByTestId('timesheet-status').textContent).toBe('APPROVED'); fireEvent.click(screen.getByRole('button', { name: 'Reopen for edits' })); expect(screen.getByTestId('reopen-confirmation-dialog')).toBeTruthy(); fireEvent.click(screen.getByRole('button', { name: 'Confirm reopen' })); await waitFor(() => { expect(reverseTimeSheetApproval).toHaveBeenCalledWith( 'timesheet-1', 'user-1', 'Reopened for edits' ); }); await waitFor(() => { expect(fetchTimeSheet).toHaveBeenCalledWith('timesheet-1'); expect(screen.getByTestId('timesheet-status').textContent).toBe('CHANGES_REQUESTED'); }); expect(toastSuccess).toHaveBeenCalledWith('Time sheet reopened for edits'); expect(refresh).not.toHaveBeenCalled(); }); });