PSA/packages/scheduling/tests/timeSheetClient.reopen.test.tsx
Hermes 284313f908
Some checks are pending
Bidi Control Character Guard / bidi-control-guard (push) Waiting to run
Circular Dependency Check / Check for new circular dependencies (push) Waiting to run
Citus Migration Smoke / Combined migrations on single-node Citus (push) Waiting to run
E2E Fresh Install Tests / fresh-install-e2e (push) Waiting to run
ext-v2 guardrails / Run ext-v2 guard and ESLint (push) Waiting to run
Integration Tests / Check for relevant changes (push) Waiting to run
Integration Tests / ${{ (github.event_name == 'schedule' || github.event.inputs.suite == 'full') && 'Full integration suite' || 'Tier-1 integration subset' }} (push) Blocked by required conditions
Mobile checks / Mobile lint + typecheck (push) Waiting to run
Mobile checks / Mobile unit tests (push) Waiting to run
Mobile checks / Mobile dependency audit (report) (push) Waiting to run
Mobile checks / Mobile reproducibility checks (push) Waiting to run
Secrets guard (env backups) / Ensure no tracked env backup files (push) Waiting to run
Temporal Readiness / fast-readiness (push) Waiting to run
Temporal Readiness / docker-parity (push) Waiting to run
TypeScript Type Check / Nx affected typecheck (push) Waiting to run
Unit Tests / Skipped-test budget (push) Waiting to run
Unit Tests / Nx affected unit tests (push) Waiting to run
Unit Tests / Server unit coverage (informational) (push) Waiting to run
Validate Tenant Management Schema / Check for relevant changes (push) Waiting to run
Validate Tenant Management Schema / Validate Tenant Management Schema (push) Blocked by required conditions
EE Workflows Build Guard / ee-workflows-build-guard (push) Waiting to run
Initial import of AlgaPSA codebase from PSA server
Excluded: .git, node_modules, secrets/, compose.env, assemblyscript tgz

Source: /opt/alga-psa on psa.joliet.tech
2026-06-22 16:12:17 -05:00

149 lines
4.4 KiB
TypeScript

// @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) => (
<div>
<div data-testid="timesheet-status">{timeSheet.approval_status}</div>
<button id="reopen-timesheet-button" onClick={() => void onReopenForEdits?.()}>
Reopen for edits
</button>
</div>
),
}));
vi.mock('@alga-psa/ui/components/ConfirmationDialog', () => ({
ConfirmationDialog: ({ isOpen, onConfirm, onClose }: any) =>
isOpen ? (
<div data-testid="reopen-confirmation-dialog">
<button id="confirm-reopen-button" onClick={() => void onConfirm()}>
Confirm reopen
</button>
<button id="cancel-reopen-button" onClick={() => onClose(false)}>
Cancel
</button>
</div>
) : 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(
<TimeSheetClient
timeSheet={approvedTimeSheet as any}
currentUser={currentUser as any}
isManager={false}
canReopenForEdits={true}
initialEntries={[] as any}
initialWorkItems={[] as any}
initialComments={[] as any}
/>
);
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();
});
});