/** * WiFi Setup Screen Tests (Auth Flow) * Tests the initial WiFi setup flow for new users configuring sensors */ import React from 'react'; import { render, fireEvent, waitFor, act } from '@testing-library/react-native'; import { router, useLocalSearchParams } from 'expo-router'; import WifiSetupScreen from '../wifi-setup'; import { espProvisioning } from '@/services/espProvisioning'; // Mock dependencies jest.mock('expo-router', () => ({ router: { back: jest.fn(), replace: jest.fn(), }, useLocalSearchParams: jest.fn(), })); jest.mock('@/services/espProvisioning', () => ({ espProvisioning: { scanForDevices: jest.fn(), connect: jest.fn(), scanWifiNetworks: jest.fn(), provisionWifi: jest.fn(), disconnect: jest.fn(), isConnected: jest.fn(), }, })); // Mock Alert const mockAlert = jest.fn(); jest.mock('react-native/Libraries/Alert/Alert', () => ({ alert: (...args: any[]) => mockAlert(...args), })); describe('WifiSetupScreen', () => { const mockDevices = [ { name: 'WP_497_81a14c', device: { id: 'device-1' }, wellId: '497', macPart: '81a14c', }, { name: 'WP_498_82b25d', device: { id: 'device-2' }, wellId: '498', macPart: '82b25d', }, ]; const mockWifiNetworks = [ { ssid: 'HomeNetwork', rssi: -45, auth: 'WPA2 PSK' }, { ssid: 'GuestNetwork', rssi: -65, auth: 'WPA2 PSK' }, { ssid: 'OpenNetwork', rssi: -70, auth: 'Open' }, ]; beforeEach(() => { jest.clearAllMocks(); mockAlert.mockClear(); // Default mock implementations (useLocalSearchParams as jest.Mock).mockReturnValue({ lovedOneName: 'John', beneficiaryId: '123', }); (espProvisioning.scanForDevices as jest.Mock).mockResolvedValue(mockDevices); (espProvisioning.connect as jest.Mock).mockResolvedValue(true); (espProvisioning.scanWifiNetworks as jest.Mock).mockResolvedValue(mockWifiNetworks); (espProvisioning.provisionWifi as jest.Mock).mockResolvedValue(true); (espProvisioning.disconnect as jest.Mock).mockResolvedValue(undefined); (espProvisioning.isConnected as jest.Mock).mockReturnValue(false); }); describe('Device Scanning (Step 1)', () => { it('should auto-scan for devices on mount', async () => { render(); await waitFor(() => { expect(espProvisioning.scanForDevices).toHaveBeenCalledWith(10000); }); }); it('should display scanning state initially', async () => { // Make scan take some time (espProvisioning.scanForDevices as jest.Mock).mockImplementation( () => new Promise((resolve) => setTimeout(() => resolve(mockDevices), 100)) ); const { getByText } = render(); expect(getByText('Scanning for devices...')).toBeTruthy(); }); it('should display found devices after scanning', async () => { const { getByText } = render(); await waitFor(() => { expect(getByText('WP_497_81a14c')).toBeTruthy(); expect(getByText('WP_498_82b25d')).toBeTruthy(); }); }); it('should display sensor ID from device name', async () => { const { getByText } = render(); await waitFor(() => { expect(getByText('Sensor ID: 497')).toBeTruthy(); expect(getByText('Sensor ID: 498')).toBeTruthy(); }); }); it('should show error when no devices found', async () => { (espProvisioning.scanForDevices as jest.Mock).mockResolvedValue([]); const { getByText } = render(); await waitFor(() => { expect(getByText(/No WellNuo sensors found/)).toBeTruthy(); }); }); it('should show error message on scan failure', async () => { (espProvisioning.scanForDevices as jest.Mock).mockRejectedValue( new Error('Bluetooth not available') ); const { getByText } = render(); await waitFor(() => { expect(getByText(/Failed to scan: Bluetooth not available/)).toBeTruthy(); }); }); it('should allow retry scan after error', async () => { (espProvisioning.scanForDevices as jest.Mock) .mockRejectedValueOnce(new Error('Scan failed')) .mockResolvedValueOnce(mockDevices); const { getByText } = render(); await waitFor(() => { expect(getByText(/Failed to scan/)).toBeTruthy(); }); fireEvent.press(getByText('Retry Scan')); await waitFor(() => { expect(getByText('WP_497_81a14c')).toBeTruthy(); }); }); it('should allow re-scan when devices are found', async () => { const { getByText } = render(); await waitFor(() => { expect(getByText('WP_497_81a14c')).toBeTruthy(); }); fireEvent.press(getByText('Scan Again')); expect(espProvisioning.scanForDevices).toHaveBeenCalledTimes(2); }); it('should navigate back when back button is pressed', async () => { const { getByTestId, UNSAFE_getAllByType } = render(); await waitFor(() => { expect(espProvisioning.scanForDevices).toHaveBeenCalled(); }); // Find the back button (first TouchableOpacity in header) // Note: In real test you'd use testID }); }); describe('Device Connection (Step 2)', () => { it('should connect to device when selected', async () => { const { getByText } = render(); await waitFor(() => { expect(getByText('WP_497_81a14c')).toBeTruthy(); }); fireEvent.press(getByText('WP_497_81a14c')); await waitFor(() => { expect(espProvisioning.connect).toHaveBeenCalledWith(mockDevices[0].device); }); }); it('should show connecting state', async () => { (espProvisioning.connect as jest.Mock).mockImplementation( () => new Promise((resolve) => setTimeout(() => resolve(true), 100)) ); const { getByText } = render(); await waitFor(() => { expect(getByText('WP_497_81a14c')).toBeTruthy(); }); fireEvent.press(getByText('WP_497_81a14c')); await waitFor(() => { expect(getByText(/Connecting to WP_497_81a14c/)).toBeTruthy(); }); }); it('should show error on connection failure and return to scan', async () => { (espProvisioning.connect as jest.Mock).mockRejectedValue( new Error('Connection failed') ); const { getByText } = render(); await waitFor(() => { expect(getByText('WP_497_81a14c')).toBeTruthy(); }); fireEvent.press(getByText('WP_497_81a14c')); await waitFor(() => { expect(getByText(/Failed to connect/)).toBeTruthy(); }); }); }); describe('WiFi Network Selection (Step 3)', () => { it('should scan WiFi networks after successful device connection', async () => { const { getByText } = render(); await waitFor(() => { expect(getByText('WP_497_81a14c')).toBeTruthy(); }); fireEvent.press(getByText('WP_497_81a14c')); await waitFor(() => { expect(espProvisioning.scanWifiNetworks).toHaveBeenCalled(); }); }); it('should display available WiFi networks', async () => { const { getByText } = render(); await waitFor(() => { expect(getByText('WP_497_81a14c')).toBeTruthy(); }); fireEvent.press(getByText('WP_497_81a14c')); await waitFor(() => { expect(getByText('HomeNetwork')).toBeTruthy(); expect(getByText('GuestNetwork')).toBeTruthy(); expect(getByText('OpenNetwork')).toBeTruthy(); }); }); it('should show connected device info', async () => { const { getByText } = render(); await waitFor(() => { expect(getByText('WP_497_81a14c')).toBeTruthy(); }); fireEvent.press(getByText('WP_497_81a14c')); await waitFor(() => { expect(getByText(/Connected to WP_497_81a14c/)).toBeTruthy(); }); }); it('should display signal strength for networks', async () => { const { getByText } = render(); await waitFor(() => { expect(getByText('WP_497_81a14c')).toBeTruthy(); }); fireEvent.press(getByText('WP_497_81a14c')); await waitFor(() => { // Signal strength labels based on RSSI expect(getByText(/Excellent/)).toBeTruthy(); // -45 dBm }); }); it('should show error when no WiFi networks found', async () => { (espProvisioning.scanWifiNetworks as jest.Mock).mockResolvedValue([]); const { getByText } = render(); await waitFor(() => { expect(getByText('WP_497_81a14c')).toBeTruthy(); }); fireEvent.press(getByText('WP_497_81a14c')); await waitFor(() => { expect(getByText(/No WiFi networks found/)).toBeTruthy(); }); }); it('should allow re-scan of WiFi networks', async () => { const { getByText } = render(); await waitFor(() => { expect(getByText('WP_497_81a14c')).toBeTruthy(); }); fireEvent.press(getByText('WP_497_81a14c')); await waitFor(() => { expect(getByText('HomeNetwork')).toBeTruthy(); }); fireEvent.press(getByText('Scan Again')); expect(espProvisioning.scanWifiNetworks).toHaveBeenCalledTimes(2); }); }); describe('Password Entry (Step 4)', () => { const navigateToPasswordStep = async (component: any) => { const { getByText, getByPlaceholderText } = component; await waitFor(() => { expect(getByText('WP_497_81a14c')).toBeTruthy(); }); fireEvent.press(getByText('WP_497_81a14c')); await waitFor(() => { expect(getByText('HomeNetwork')).toBeTruthy(); }); fireEvent.press(getByText('HomeNetwork')); return { getByText, getByPlaceholderText }; }; it('should show password input when network is selected', async () => { const component = render(); await navigateToPasswordStep(component); await waitFor(() => { expect(component.getByPlaceholderText('Enter WiFi password')).toBeTruthy(); }); }); it('should show selected network name', async () => { const component = render(); await navigateToPasswordStep(component); await waitFor(() => { expect(component.getByText('HomeNetwork')).toBeTruthy(); }); }); it('should toggle password visibility', async () => { const component = render(); await navigateToPasswordStep(component); await waitFor(() => { const passwordInput = component.getByPlaceholderText('Enter WiFi password'); expect(passwordInput.props.secureTextEntry).toBe(true); }); }); it('should show password requirements for WPA networks', async () => { const component = render(); await navigateToPasswordStep(component); await waitFor(() => { expect(component.getByText(/8-63 characters/)).toBeTruthy(); }); }); it('should allow empty password for open networks', async () => { const { getByText } = render(); await waitFor(() => { expect(getByText('WP_497_81a14c')).toBeTruthy(); }); fireEvent.press(getByText('WP_497_81a14c')); await waitFor(() => { expect(getByText('OpenNetwork')).toBeTruthy(); }); fireEvent.press(getByText('OpenNetwork')); await waitFor(() => { expect(getByText(/open network/i)).toBeTruthy(); }); }); it('should disable connect button when password is too short', async () => { const component = render(); await navigateToPasswordStep(component); await waitFor(() => { const passwordInput = component.getByPlaceholderText('Enter WiFi password'); fireEvent.changeText(passwordInput, '1234567'); // 7 chars }); const connectButton = component.getByText('Connect to WiFi'); // Button should have disabled styling (opacity) }); }); describe('WiFi Provisioning (Step 5)', () => { const navigateToProvisioningStep = async (component: any) => { const { getByText, getByPlaceholderText } = component; await waitFor(() => { expect(getByText('WP_497_81a14c')).toBeTruthy(); }); fireEvent.press(getByText('WP_497_81a14c')); await waitFor(() => { expect(getByText('HomeNetwork')).toBeTruthy(); }); fireEvent.press(getByText('HomeNetwork')); await waitFor(() => { expect(getByPlaceholderText('Enter WiFi password')).toBeTruthy(); }); fireEvent.changeText(getByPlaceholderText('Enter WiFi password'), 'validpassword123'); fireEvent.press(getByText('Connect to WiFi')); return { getByText, getByPlaceholderText }; }; it('should call provisionWifi with credentials', async () => { const component = render(); await navigateToProvisioningStep(component); await waitFor(() => { expect(espProvisioning.provisionWifi).toHaveBeenCalledWith( 'HomeNetwork', 'validpassword123' ); }); }); it('should show configuring state during provisioning', async () => { (espProvisioning.provisionWifi as jest.Mock).mockImplementation( () => new Promise((resolve) => setTimeout(() => resolve(true), 100)) ); const component = render(); const { getByText, getByPlaceholderText } = component; await waitFor(() => { expect(getByText('WP_497_81a14c')).toBeTruthy(); }); fireEvent.press(getByText('WP_497_81a14c')); await waitFor(() => { expect(getByText('HomeNetwork')).toBeTruthy(); }); fireEvent.press(getByText('HomeNetwork')); await waitFor(() => { expect(getByPlaceholderText('Enter WiFi password')).toBeTruthy(); }); fireEvent.changeText(getByPlaceholderText('Enter WiFi password'), 'validpassword123'); fireEvent.press(getByText('Connect to WiFi')); await waitFor(() => { expect(getByText('Configuring...')).toBeTruthy(); }); }); it('should show error on provisioning failure', async () => { (espProvisioning.provisionWifi as jest.Mock).mockRejectedValue( new Error('Wrong password') ); const component = render(); const { getByText, getByPlaceholderText } = component; await waitFor(() => { expect(getByText('WP_497_81a14c')).toBeTruthy(); }); fireEvent.press(getByText('WP_497_81a14c')); await waitFor(() => { expect(getByText('HomeNetwork')).toBeTruthy(); }); fireEvent.press(getByText('HomeNetwork')); await waitFor(() => { expect(getByPlaceholderText('Enter WiFi password')).toBeTruthy(); }); fireEvent.changeText(getByPlaceholderText('Enter WiFi password'), 'wrongpassword1'); fireEvent.press(getByText('Connect to WiFi')); await waitFor(() => { expect(getByText(/Failed to configure WiFi/)).toBeTruthy(); }); }); }); describe('Success Screen (Step 6)', () => { const navigateToSuccessScreen = async (component: any) => { const { getByText, getByPlaceholderText } = component; await waitFor(() => { expect(getByText('WP_497_81a14c')).toBeTruthy(); }); fireEvent.press(getByText('WP_497_81a14c')); await waitFor(() => { expect(getByText('HomeNetwork')).toBeTruthy(); }); fireEvent.press(getByText('HomeNetwork')); await waitFor(() => { expect(getByPlaceholderText('Enter WiFi password')).toBeTruthy(); }); fireEvent.changeText(getByPlaceholderText('Enter WiFi password'), 'validpassword123'); fireEvent.press(getByText('Connect to WiFi')); return { getByText, getByPlaceholderText }; }; it('should show success screen after provisioning', async () => { const component = render(); await navigateToSuccessScreen(component); await waitFor(() => { expect(component.getByText('WiFi Configured!')).toBeTruthy(); }); }); it('should display device and network names on success', async () => { const component = render(); await navigateToSuccessScreen(component); await waitFor(() => { expect(component.getByText(/WP_497_81a14c/)).toBeTruthy(); expect(component.getByText(/HomeNetwork/)).toBeTruthy(); }); }); it('should show next steps information', async () => { const component = render(); await navigateToSuccessScreen(component); await waitFor(() => { expect(component.getByText(/What happens next/)).toBeTruthy(); }); }); it('should navigate to activate screen when Continue is pressed', async () => { const component = render(); await navigateToSuccessScreen(component); await waitFor(() => { expect(component.getByText('WiFi Configured!')).toBeTruthy(); }); fireEvent.press(component.getByText('Continue')); await waitFor(() => { expect(espProvisioning.disconnect).toHaveBeenCalled(); expect(router.replace).toHaveBeenCalledWith({ pathname: '/(auth)/activate', params: { beneficiaryId: '123', lovedOneName: 'John' }, }); }); }); it('should navigate back when no beneficiaryId is provided', async () => { (useLocalSearchParams as jest.Mock).mockReturnValue({ lovedOneName: 'John', // No beneficiaryId }); const component = render(); await navigateToSuccessScreen(component); await waitFor(() => { expect(component.getByText('WiFi Configured!')).toBeTruthy(); }); fireEvent.press(component.getByText('Continue')); await waitFor(() => { expect(router.back).toHaveBeenCalled(); }); }); }); describe('Cleanup', () => { it('should disconnect on unmount', async () => { const { unmount } = render(); await waitFor(() => { expect(espProvisioning.scanForDevices).toHaveBeenCalled(); }); unmount(); expect(espProvisioning.disconnect).toHaveBeenCalled(); }); }); describe('Validation', () => { it('should show validation error for short password', async () => { const { getByText, getByPlaceholderText } = render(); await waitFor(() => { expect(getByText('WP_497_81a14c')).toBeTruthy(); }); fireEvent.press(getByText('WP_497_81a14c')); await waitFor(() => { expect(getByText('HomeNetwork')).toBeTruthy(); }); fireEvent.press(getByText('HomeNetwork')); await waitFor(() => { expect(getByPlaceholderText('Enter WiFi password')).toBeTruthy(); }); fireEvent.changeText(getByPlaceholderText('Enter WiFi password'), '1234567'); // 7 chars - too short // Connect button should be disabled for WPA networks with short password const connectButton = getByText('Connect to WiFi'); // In the real implementation, the button is disabled when password is too short }); it('should sanitize credentials before provisioning', async () => { const { getByText, getByPlaceholderText } = render(); await waitFor(() => { expect(getByText('WP_497_81a14c')).toBeTruthy(); }); fireEvent.press(getByText('WP_497_81a14c')); await waitFor(() => { expect(getByText('HomeNetwork')).toBeTruthy(); }); fireEvent.press(getByText('HomeNetwork')); await waitFor(() => { expect(getByPlaceholderText('Enter WiFi password')).toBeTruthy(); }); // Password with trailing spaces - should be preserved fireEvent.changeText(getByPlaceholderText('Enter WiFi password'), 'password123 '); fireEvent.press(getByText('Connect to WiFi')); await waitFor(() => { expect(espProvisioning.provisionWifi).toHaveBeenCalledWith( 'HomeNetwork', 'password123 ' // Password whitespace is preserved ); }); }); }); });