WellNuo/services/__tests__/NavigationController.nullsafety.test.ts
Sergei deddd3d5bc Add comprehensive null safety to navigation system
Implemented null/undefined handling throughout NavigationController
and useNavigationFlow hook to prevent crashes from invalid data:

- Added null checks for all profile and beneficiary parameters
- Validated beneficiary IDs before navigation (type and value checks)
- Added fallback routes when data is invalid or missing
- Implemented safe navigation with error handling and logging
- Added defensive guards for optional purchaseResult parameter

Key improvements:
- getRouteAfterLogin: handles null profile, null beneficiaries, invalid IDs
- getRouteForBeneficiarySetup: validates beneficiary exists before routing
- getRouteAfterAddBeneficiary: validates beneficiary ID type and value
- getRouteAfterPurchase: handles null purchaseResult safely
- getBeneficiaryRoute: returns fallback route for invalid beneficiaries
- navigate hook: wraps router calls in try-catch with validation

All methods now gracefully handle edge cases without crashing,
logging warnings for debugging while maintaining UX flow.

Tests included for all null/undefined scenarios.
2026-01-29 12:05:29 -08:00

425 lines
15 KiB
TypeScript

/**
* NavigationController Null Safety Tests
*
* Tests for null/undefined handling and edge cases in navigation logic
*/
import { NavigationController, ROUTES } from '../NavigationController';
import type { Beneficiary, EquipmentStatus } from '@/types';
describe('NavigationController - Null Safety', () => {
describe('getRouteAfterLogin', () => {
it('should handle null profile', () => {
const result = NavigationController.getRouteAfterLogin(null, []);
expect(result.path).toBe(ROUTES.AUTH.ENTER_NAME);
});
it('should handle undefined profile', () => {
const result = NavigationController.getRouteAfterLogin(undefined, []);
expect(result.path).toBe(ROUTES.AUTH.ENTER_NAME);
});
it('should handle profile without firstName', () => {
const profile = {
id: 1,
email: 'test@example.com',
firstName: null,
lastName: null,
phone: null,
};
const result = NavigationController.getRouteAfterLogin(profile, []);
expect(result.path).toBe(ROUTES.AUTH.ENTER_NAME);
});
it('should handle null beneficiaries array', () => {
const profile = {
id: 1,
email: 'test@example.com',
firstName: 'John',
lastName: 'Doe',
phone: null,
};
const result = NavigationController.getRouteAfterLogin(profile, null);
expect(result.path).toBe(ROUTES.AUTH.ADD_LOVED_ONE);
});
it('should handle undefined beneficiaries array', () => {
const profile = {
id: 1,
email: 'test@example.com',
firstName: 'John',
lastName: 'Doe',
phone: null,
};
const result = NavigationController.getRouteAfterLogin(profile, undefined);
expect(result.path).toBe(ROUTES.AUTH.ADD_LOVED_ONE);
});
it('should handle empty beneficiaries array', () => {
const profile = {
id: 1,
email: 'test@example.com',
firstName: 'John',
lastName: 'Doe',
phone: null,
};
const result = NavigationController.getRouteAfterLogin(profile, []);
expect(result.path).toBe(ROUTES.AUTH.ADD_LOVED_ONE);
});
it('should handle beneficiary with invalid id', () => {
const profile = {
id: 1,
email: 'test@example.com',
firstName: 'John',
lastName: 'Doe',
phone: null,
};
const beneficiaries = [
{
id: null as any,
name: 'Mom',
displayName: 'Mom',
status: 'online' as const,
},
];
const result = NavigationController.getRouteAfterLogin(profile, beneficiaries);
expect(result.path).toBe(ROUTES.AUTH.ADD_LOVED_ONE);
});
it('should navigate to dashboard for active beneficiary', () => {
const profile = {
id: 1,
email: 'test@example.com',
firstName: 'John',
lastName: 'Doe',
phone: null,
};
const beneficiaries: Beneficiary[] = [
{
id: 1,
name: 'Mom',
displayName: 'Mom',
status: 'online',
hasDevices: true,
equipmentStatus: 'active',
},
];
const result = NavigationController.getRouteAfterLogin(profile, beneficiaries);
expect(result.path).toBe(ROUTES.TABS.DASHBOARD);
});
});
describe('getRouteForBeneficiarySetup', () => {
it('should handle null beneficiary', () => {
const result = NavigationController.getRouteForBeneficiarySetup(null);
expect(result.path).toBe(ROUTES.AUTH.ADD_LOVED_ONE);
});
it('should handle undefined beneficiary', () => {
const result = NavigationController.getRouteForBeneficiarySetup(undefined);
expect(result.path).toBe(ROUTES.AUTH.ADD_LOVED_ONE);
});
it('should handle beneficiary with invalid id', () => {
const beneficiary = {
id: null as any,
name: 'Mom',
displayName: 'Mom',
status: 'online' as const,
};
const result = NavigationController.getRouteForBeneficiarySetup(beneficiary);
expect(result.path).toBe(ROUTES.AUTH.ADD_LOVED_ONE);
});
it('should navigate to purchase for beneficiary with no equipment', () => {
const beneficiary: Beneficiary = {
id: 1,
name: 'Mom',
displayName: 'Mom',
status: 'online',
equipmentStatus: 'none',
};
const result = NavigationController.getRouteForBeneficiarySetup(beneficiary);
expect(result.path).toBe(ROUTES.AUTH.PURCHASE);
expect(result.params).toEqual({ beneficiaryId: 1 });
});
it('should navigate to equipment page for ordered status', () => {
const beneficiary: Beneficiary = {
id: 1,
name: 'Mom',
displayName: 'Mom',
status: 'online',
equipmentStatus: 'ordered',
};
const result = NavigationController.getRouteForBeneficiarySetup(beneficiary);
expect(result.path).toBe(ROUTES.BENEFICIARY.EQUIPMENT(1));
});
});
describe('getRouteAfterAddBeneficiary', () => {
it('should handle null beneficiaryId', () => {
const result = NavigationController.getRouteAfterAddBeneficiary(null, false);
expect(result.path).toBe(ROUTES.AUTH.ADD_LOVED_ONE);
});
it('should handle undefined beneficiaryId', () => {
const result = NavigationController.getRouteAfterAddBeneficiary(undefined, false);
expect(result.path).toBe(ROUTES.AUTH.ADD_LOVED_ONE);
});
it('should handle invalid beneficiaryId type', () => {
const result = NavigationController.getRouteAfterAddBeneficiary('invalid' as any, false);
expect(result.path).toBe(ROUTES.AUTH.ADD_LOVED_ONE);
});
it('should navigate to purchase for new equipment', () => {
const result = NavigationController.getRouteAfterAddBeneficiary(1, false);
expect(result.path).toBe(ROUTES.AUTH.PURCHASE);
expect(result.params).toEqual({ beneficiaryId: 1 });
});
it('should navigate to activate for existing devices', () => {
const result = NavigationController.getRouteAfterAddBeneficiary(1, true);
expect(result.path).toBe(ROUTES.AUTH.ACTIVATE);
expect(result.params).toEqual({ beneficiaryId: 1 });
});
});
describe('getRouteAfterPurchase', () => {
it('should handle null beneficiaryId', () => {
const result = NavigationController.getRouteAfterPurchase(null, {});
expect(result.path).toBe(ROUTES.AUTH.ADD_LOVED_ONE);
});
it('should handle undefined beneficiaryId', () => {
const result = NavigationController.getRouteAfterPurchase(undefined, {});
expect(result.path).toBe(ROUTES.AUTH.ADD_LOVED_ONE);
});
it('should handle null purchaseResult', () => {
const result = NavigationController.getRouteAfterPurchase(1, null);
expect(result.path).toBe(ROUTES.BENEFICIARY.EQUIPMENT(1));
});
it('should handle undefined purchaseResult', () => {
const result = NavigationController.getRouteAfterPurchase(1, undefined);
expect(result.path).toBe(ROUTES.BENEFICIARY.EQUIPMENT(1));
});
it('should navigate to activate for demo mode', () => {
const result = NavigationController.getRouteAfterPurchase(1, { demo: true });
expect(result.path).toBe(ROUTES.AUTH.ACTIVATE);
expect(result.params).toEqual({ beneficiaryId: 1, demo: true });
});
it('should navigate to equipment page for normal purchase', () => {
const result = NavigationController.getRouteAfterPurchase(1, {});
expect(result.path).toBe(ROUTES.BENEFICIARY.EQUIPMENT(1));
});
});
describe('getRouteAfterActivation', () => {
it('should handle null beneficiaryId', () => {
const result = NavigationController.getRouteAfterActivation(null);
expect(result.path).toBe(ROUTES.TABS.DASHBOARD);
});
it('should handle undefined beneficiaryId', () => {
const result = NavigationController.getRouteAfterActivation(undefined);
expect(result.path).toBe(ROUTES.TABS.DASHBOARD);
});
it('should handle invalid beneficiaryId type', () => {
const result = NavigationController.getRouteAfterActivation('invalid' as any);
expect(result.path).toBe(ROUTES.TABS.DASHBOARD);
});
it('should navigate to beneficiary detail for valid id', () => {
const result = NavigationController.getRouteAfterActivation(1);
expect(result.path).toBe(ROUTES.BENEFICIARY.DETAIL(1));
expect(result.params).toEqual({ justActivated: true });
});
});
describe('getBeneficiaryRoute', () => {
it('should handle null beneficiary', () => {
const result = NavigationController.getBeneficiaryRoute(null, 'view');
expect(result.path).toBe(ROUTES.TABS.BENEFICIARIES);
});
it('should handle undefined beneficiary', () => {
const result = NavigationController.getBeneficiaryRoute(undefined, 'view');
expect(result.path).toBe(ROUTES.TABS.BENEFICIARIES);
});
it('should handle beneficiary with invalid id', () => {
const beneficiary = {
id: null as any,
name: 'Mom',
displayName: 'Mom',
status: 'online' as const,
};
const result = NavigationController.getBeneficiaryRoute(beneficiary, 'view');
expect(result.path).toBe(ROUTES.TABS.BENEFICIARIES);
});
it('should navigate to view for valid beneficiary', () => {
const beneficiary: Beneficiary = {
id: 1,
name: 'Mom',
displayName: 'Mom',
status: 'online',
};
const result = NavigationController.getBeneficiaryRoute(beneficiary, 'view');
expect(result.path).toBe(ROUTES.BENEFICIARY.DETAIL(1));
});
it('should navigate to subscription for valid beneficiary', () => {
const beneficiary: Beneficiary = {
id: 1,
name: 'Mom',
displayName: 'Mom',
status: 'online',
};
const result = NavigationController.getBeneficiaryRoute(beneficiary, 'subscription');
expect(result.path).toBe(ROUTES.BENEFICIARY.SUBSCRIPTION(1));
});
});
describe('shouldShowEquipmentSetup', () => {
it('should return false for null beneficiary', () => {
const result = NavigationController.shouldShowEquipmentSetup(null);
expect(result).toBe(false);
});
it('should return false for undefined beneficiary', () => {
const result = NavigationController.shouldShowEquipmentSetup(undefined);
expect(result).toBe(false);
});
it('should return true for beneficiary without devices', () => {
const beneficiary: Beneficiary = {
id: 1,
name: 'Mom',
displayName: 'Mom',
status: 'online',
hasDevices: false,
equipmentStatus: 'none',
};
const result = NavigationController.shouldShowEquipmentSetup(beneficiary);
expect(result).toBe(true);
});
it('should return false for beneficiary with devices', () => {
const beneficiary: Beneficiary = {
id: 1,
name: 'Mom',
displayName: 'Mom',
status: 'online',
hasDevices: true,
equipmentStatus: 'active',
};
const result = NavigationController.shouldShowEquipmentSetup(beneficiary);
expect(result).toBe(false);
});
});
describe('getBeneficiaryCallToAction', () => {
it('should return null for null beneficiary', () => {
const result = NavigationController.getBeneficiaryCallToAction(null);
expect(result).toBe(null);
});
it('should return null for undefined beneficiary', () => {
const result = NavigationController.getBeneficiaryCallToAction(undefined);
expect(result).toBe(null);
});
it('should return null for beneficiary with invalid id', () => {
const beneficiary = {
id: null as any,
name: 'Mom',
displayName: 'Mom',
status: 'online' as const,
};
const result = NavigationController.getBeneficiaryCallToAction(beneficiary);
expect(result).toBe(null);
});
it('should return null for active beneficiary', () => {
const beneficiary: Beneficiary = {
id: 1,
name: 'Mom',
displayName: 'Mom',
status: 'online',
hasDevices: true,
equipmentStatus: 'active',
};
const result = NavigationController.getBeneficiaryCallToAction(beneficiary);
expect(result).toBe(null);
});
it('should return setup action for beneficiary with no equipment', () => {
const beneficiary: Beneficiary = {
id: 1,
name: 'Mom',
displayName: 'Mom',
status: 'online',
hasDevices: false,
equipmentStatus: 'none',
};
const result = NavigationController.getBeneficiaryCallToAction(beneficiary);
expect(result).not.toBe(null);
expect(result?.label).toBe('Setup Equipment');
});
it('should return track delivery action for ordered equipment', () => {
const beneficiary: Beneficiary = {
id: 1,
name: 'Mom',
displayName: 'Mom',
status: 'online',
hasDevices: false,
equipmentStatus: 'ordered',
};
const result = NavigationController.getBeneficiaryCallToAction(beneficiary);
expect(result).not.toBe(null);
expect(result?.label).toBe('Track Delivery');
});
});
describe('getEquipmentStatusText', () => {
it('should handle undefined status', () => {
const result = NavigationController.getEquipmentStatusText(undefined);
expect(result).toBe('Unknown');
});
it('should return correct text for each status', () => {
expect(NavigationController.getEquipmentStatusText('none')).toBe('No equipment');
expect(NavigationController.getEquipmentStatusText('ordered')).toBe('Equipment ordered');
expect(NavigationController.getEquipmentStatusText('shipped')).toBe('In transit');
expect(NavigationController.getEquipmentStatusText('delivered')).toBe('Ready to activate');
expect(NavigationController.getEquipmentStatusText('active')).toBe('Active');
expect(NavigationController.getEquipmentStatusText('demo')).toBe('Demo mode');
});
});
describe('getEquipmentStatusColor', () => {
it('should handle undefined status', () => {
const result = NavigationController.getEquipmentStatusColor(undefined);
expect(result).toBe('#888888'); // gray
});
it('should return correct color for each status', () => {
expect(NavigationController.getEquipmentStatusColor('none')).toBe('#888888');
expect(NavigationController.getEquipmentStatusColor('ordered')).toBe('#FFA500');
expect(NavigationController.getEquipmentStatusColor('shipped')).toBe('#007AFF');
expect(NavigationController.getEquipmentStatusColor('delivered')).toBe('#34C759');
expect(NavigationController.getEquipmentStatusColor('active')).toBe('#34C759');
expect(NavigationController.getEquipmentStatusColor('demo')).toBe('#9C27B0');
});
});
});