diff --git a/app/(auth)/__tests__/wifi-setup.test.tsx b/app/(auth)/__tests__/wifi-setup.test.tsx
new file mode 100644
index 0000000..6135ef9
--- /dev/null
+++ b/app/(auth)/__tests__/wifi-setup.test.tsx
@@ -0,0 +1,686 @@
+/**
+ * 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
+ );
+ });
+ });
+ });
+});