import React from 'react'; import { render, fireEvent, waitFor } from '@testing-library/react-native'; import { Alert } from 'react-native'; import BeneficiaryDetailScreen from '@/app/(tabs)/beneficiaries/[id]/index'; import { api } from '@/services/api'; import { router, useLocalSearchParams } from 'expo-router'; // Mock dependencies jest.mock('expo-router', () => ({ router: { replace: jest.fn(), push: jest.fn(), setParams: jest.fn(), }, useLocalSearchParams: jest.fn(), })); jest.mock('@/services/api'); jest.mock('@/contexts/BeneficiaryContext', () => ({ useBeneficiary: () => ({ setCurrentBeneficiary: jest.fn(), }), })); jest.mock('@/components/ui/Toast', () => ({ useToast: () => ({ success: jest.fn(), error: jest.fn(), info: jest.fn(), }), })); jest.mock('react-native/Libraries/Alert/Alert', () => ({ alert: jest.fn(), })); describe('BeneficiaryDetailScreen - Role-based Edit Modal', () => { const mockBeneficiaryCustodian = { id: 1, name: 'John Doe', displayName: 'John Doe', address: '123 Main St', avatar: 'https://example.com/avatar.jpg', role: 'custodian', customName: null, status: 'online', subscription: { status: 'active' }, devices: [{ id: '1', type: 'motion', name: 'Sensor 1', status: 'online' }], equipmentStatus: 'active', }; const mockBeneficiaryCaretaker = { ...mockBeneficiaryCustodian, role: 'caretaker', customName: 'Dad', }; const mockBeneficiaryGuardian = { ...mockBeneficiaryCustodian, role: 'guardian', customName: 'Grandpa', }; beforeEach(() => { jest.clearAllMocks(); (useLocalSearchParams as jest.Mock).mockReturnValue({ id: '1' }); (api.getLegacyWebViewCredentials as jest.Mock).mockResolvedValue({ token: 'test-token', userName: 'test-user', userId: '1', }); (api.isLegacyTokenExpiringSoon as jest.Mock).mockResolvedValue(false); }); describe('Custodian Edit Modal', () => { beforeEach(() => { (api.getWellNuoBeneficiary as jest.Mock).mockResolvedValue({ ok: true, data: mockBeneficiaryCustodian, }); }); it('shows full edit form for custodian (name, address, avatar)', async () => { const { getByText, getByPlaceholderText } = render(); // Wait for data to load await waitFor(() => { expect(getByText('John Doe')).toBeTruthy(); }); // Open menu and click Edit const menuButton = getByText('☰'); // Adjust based on actual icon fireEvent.press(menuButton); const editButton = getByText('Edit'); fireEvent.press(editButton); // Should show custodian edit form await waitFor(() => { expect(getByText('Edit Profile')).toBeTruthy(); expect(getByPlaceholderText('Full name')).toBeTruthy(); expect(getByPlaceholderText('Street address')).toBeTruthy(); // Avatar picker should be visible }); // Should NOT show nickname field expect(() => getByPlaceholderText('e.g., "Mom", "Dad", "Grandma"')).toThrow(); }); it('allows custodian to update name and address', async () => { (api.updateWellNuoBeneficiary as jest.Mock).mockResolvedValue({ ok: true }); const { getByText, getByPlaceholderText, getByTestId } = render(); await waitFor(() => { expect(getByText('John Doe')).toBeTruthy(); }); // Open edit modal fireEvent.press(getByTestId('menu-button')); fireEvent.press(getByText('Edit')); // Update name const nameInput = getByPlaceholderText('Full name'); fireEvent.changeText(nameInput, 'Jane Doe'); // Update address const addressInput = getByPlaceholderText('Street address'); fireEvent.changeText(addressInput, '456 Oak Ave'); // Save const saveButton = getByText('Save'); fireEvent.press(saveButton); await waitFor(() => { expect(api.updateWellNuoBeneficiary).toHaveBeenCalledWith(1, { name: 'Jane Doe', address: '456 Oak Ave', }); }); }); it('validates that name is required for custodian', async () => { const { getByText, getByPlaceholderText, getByTestId } = render(); await waitFor(() => { expect(getByText('John Doe')).toBeTruthy(); }); // Open edit modal fireEvent.press(getByTestId('menu-button')); fireEvent.press(getByText('Edit')); // Clear name const nameInput = getByPlaceholderText('Full name'); fireEvent.changeText(nameInput, ''); // Try to save const saveButton = getByText('Save'); fireEvent.press(saveButton); // Should show error (via toast) await waitFor(() => { expect(api.updateWellNuoBeneficiary).not.toHaveBeenCalled(); }); }); }); describe('Caretaker Edit Modal', () => { beforeEach(() => { (api.getWellNuoBeneficiary as jest.Mock).mockResolvedValue({ ok: true, data: mockBeneficiaryCaretaker, }); }); it('shows only nickname field for caretaker', async () => { const { getByText, getByPlaceholderText, queryByPlaceholderText } = render( ); await waitFor(() => { expect(getByText('John Doe')).toBeTruthy(); }); // Open edit modal fireEvent.press(getByText('Edit')); // Should show caretaker edit form await waitFor(() => { expect(getByText('Edit Nickname')).toBeTruthy(); expect(getByPlaceholderText('e.g., "Mom", "Dad", "Grandma"')).toBeTruthy(); }); // Should NOT show full profile fields expect(queryByPlaceholderText('Full name')).toBeNull(); expect(queryByPlaceholderText('Street address')).toBeNull(); }); it('shows original name as reference for caretaker', async () => { const { getByText } = render(); await waitFor(() => { expect(getByText('John Doe')).toBeTruthy(); }); // Open edit modal fireEvent.press(getByText('Edit')); // Should show original name await waitFor(() => { expect(getByText('Original name:')).toBeTruthy(); expect(getByText('John Doe')).toBeTruthy(); }); }); it('allows caretaker to update nickname only', async () => { (api.updateBeneficiaryCustomName as jest.Mock).mockResolvedValue({ ok: true }); const { getByText, getByPlaceholderText } = render(); await waitFor(() => { expect(getByText('John Doe')).toBeTruthy(); }); // Open edit modal fireEvent.press(getByText('Edit')); // Update nickname const nicknameInput = getByPlaceholderText('e.g., "Mom", "Dad", "Grandma"'); fireEvent.changeText(nicknameInput, 'Papa'); // Save const saveButton = getByText('Save'); fireEvent.press(saveButton); await waitFor(() => { expect(api.updateBeneficiaryCustomName).toHaveBeenCalledWith(1, 'Papa'); }); }); it('allows caretaker to clear nickname', async () => { (api.updateBeneficiaryCustomName as jest.Mock).mockResolvedValue({ ok: true }); const { getByText, getByPlaceholderText } = render(); await waitFor(() => { expect(getByText('John Doe')).toBeTruthy(); }); // Open edit modal fireEvent.press(getByText('Edit')); // Clear nickname const nicknameInput = getByPlaceholderText('e.g., "Mom", "Dad", "Grandma"'); fireEvent.changeText(nicknameInput, ''); // Save const saveButton = getByText('Save'); fireEvent.press(saveButton); await waitFor(() => { expect(api.updateBeneficiaryCustomName).toHaveBeenCalledWith(1, null); }); }); it('does NOT call updateWellNuoBeneficiary for caretaker', async () => { (api.updateBeneficiaryCustomName as jest.Mock).mockResolvedValue({ ok: true }); const { getByText, getByPlaceholderText } = render(); await waitFor(() => { expect(getByText('John Doe')).toBeTruthy(); }); // Open edit modal fireEvent.press(getByText('Edit')); // Update nickname const nicknameInput = getByPlaceholderText('e.g., "Mom", "Dad", "Grandma"'); fireEvent.changeText(nicknameInput, 'Dad'); // Save fireEvent.press(getByText('Save')); await waitFor(() => { expect(api.updateWellNuoBeneficiary).not.toHaveBeenCalled(); expect(api.updateBeneficiaryCustomName).toHaveBeenCalled(); }); }); }); describe('Guardian Edit Modal', () => { beforeEach(() => { (api.getWellNuoBeneficiary as jest.Mock).mockResolvedValue({ ok: true, data: mockBeneficiaryGuardian, }); }); it('shows only nickname field for guardian (same as caretaker)', async () => { const { getByText, getByPlaceholderText, queryByPlaceholderText } = render( ); await waitFor(() => { expect(getByText('John Doe')).toBeTruthy(); }); // Open edit modal fireEvent.press(getByText('Edit')); // Should show nickname form (same as caretaker) await waitFor(() => { expect(getByText('Edit Nickname')).toBeTruthy(); expect(getByPlaceholderText('e.g., "Mom", "Dad", "Grandma"')).toBeTruthy(); }); // Should NOT show full profile fields expect(queryByPlaceholderText('Full name')).toBeNull(); expect(queryByPlaceholderText('Street address')).toBeNull(); }); it('allows guardian to update nickname', async () => { (api.updateBeneficiaryCustomName as jest.Mock).mockResolvedValue({ ok: true }); const { getByText, getByPlaceholderText } = render(); await waitFor(() => { expect(getByText('John Doe')).toBeTruthy(); }); // Open edit modal fireEvent.press(getByText('Edit')); // Update nickname const nicknameInput = getByPlaceholderText('e.g., "Mom", "Dad", "Grandma"'); fireEvent.changeText(nicknameInput, 'Gramps'); // Save fireEvent.press(getByText('Save')); await waitFor(() => { expect(api.updateBeneficiaryCustomName).toHaveBeenCalledWith(1, 'Gramps'); }); }); }); describe('Edit Modal Behavior', () => { it('opens edit modal when navigated with edit=true param', async () => { (useLocalSearchParams as jest.Mock).mockReturnValue({ id: '1', edit: 'true' }); (api.getWellNuoBeneficiary as jest.Mock).mockResolvedValue({ ok: true, data: mockBeneficiaryCustodian, }); const { getByText } = render(); // Edit modal should open automatically await waitFor(() => { expect(getByText('Edit Profile')).toBeTruthy(); }); // Should clear the edit param expect(router.setParams).toHaveBeenCalledWith({ edit: undefined }); }); it('closes edit modal when Save completes successfully', async () => { (api.updateWellNuoBeneficiary as jest.Mock).mockResolvedValue({ ok: true }); (api.getWellNuoBeneficiary as jest.Mock).mockResolvedValue({ ok: true, data: mockBeneficiaryCustodian, }); const { getByText, getByPlaceholderText, queryByText } = render( ); await waitFor(() => { expect(getByText('John Doe')).toBeTruthy(); }); // Open edit modal fireEvent.press(getByText('Edit')); // Update name const nameInput = getByPlaceholderText('Full name'); fireEvent.changeText(nameInput, 'Jane Doe'); // Save fireEvent.press(getByText('Save')); // Modal should close await waitFor(() => { expect(queryByText('Edit Profile')).toBeNull(); }); }); it('reloads beneficiary data after successful save', async () => { (api.updateWellNuoBeneficiary as jest.Mock).mockResolvedValue({ ok: true }); (api.getWellNuoBeneficiary as jest.Mock).mockResolvedValue({ ok: true, data: mockBeneficiaryCustodian, }); const { getByText, getByPlaceholderText } = render(); await waitFor(() => { expect(getByText('John Doe')).toBeTruthy(); }); // Open and save edit fireEvent.press(getByText('Edit')); const nameInput = getByPlaceholderText('Full name'); fireEvent.changeText(nameInput, 'Jane Doe'); fireEvent.press(getByText('Save')); // Should reload beneficiary data await waitFor(() => { expect(api.getWellNuoBeneficiary).toHaveBeenCalledTimes(2); // Initial load + reload }); }); }); });