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
});
});
});
});