diff --git a/app/(tabs)/beneficiaries/[id]/add-sensor.tsx b/app/(tabs)/beneficiaries/[id]/add-sensor.tsx index 81a208e..9e83b1a 100644 --- a/app/(tabs)/beneficiaries/[id]/add-sensor.tsx +++ b/app/(tabs)/beneficiaries/[id]/add-sensor.tsx @@ -14,6 +14,11 @@ import { SafeAreaView } from 'react-native-safe-area-context'; import { router, useLocalSearchParams, useFocusEffect } from 'expo-router'; import { useBLE } from '@/contexts/BLEContext'; import { analytics } from '@/services/analytics'; +import { + WiFiSignalIndicator, + getSignalStrengthLabel, + getSignalStrengthColor, +} from '@/components/WiFiSignalIndicator'; import { AppColors, BorderRadius, @@ -150,19 +155,6 @@ export default function AddSensorScreen() { }); }; - const getSignalIcon = (rssi: number) => { - if (rssi >= -50) return 'cellular'; - if (rssi >= -60) return 'cellular-outline'; - if (rssi >= -70) return 'cellular'; - return 'cellular-outline'; - }; - - const getSignalColor = (rssi: number) => { - if (rssi >= -50) return AppColors.success; - if (rssi >= -60) return AppColors.info; - if (rssi >= -70) return AppColors.warning; - return AppColors.error; - }; return ( @@ -309,13 +301,12 @@ export default function AddSensorScreen() { Well ID: {device.wellId} )} - - - {device.rssi} dBm + + + {getSignalStrengthLabel(device.rssi)} + + + ({device.rssi} dBm) @@ -657,11 +648,15 @@ const styles = StyleSheet.create({ signalRow: { flexDirection: 'row', alignItems: 'center', - gap: 4, + gap: 6, }, - signalText: { + signalLabel: { fontSize: FontSizes.xs, - fontWeight: FontWeights.medium, + fontWeight: FontWeights.semibold, + }, + signalDbm: { + fontSize: FontSizes.xs, + color: AppColors.textMuted, }, alreadyAddedBadge: { backgroundColor: AppColors.successLight, diff --git a/app/(tabs)/beneficiaries/__tests__/add-sensor.signal-ui.test.tsx b/app/(tabs)/beneficiaries/__tests__/add-sensor.signal-ui.test.tsx new file mode 100644 index 0000000..ba34da0 --- /dev/null +++ b/app/(tabs)/beneficiaries/__tests__/add-sensor.signal-ui.test.tsx @@ -0,0 +1,275 @@ +/** + * Add Sensor Screen - Signal Strength UI Tests + * Tests the WiFiSignalIndicator integration and signal strength display + */ + +import React from 'react'; +import { render } from '@testing-library/react-native'; +import { useLocalSearchParams } from 'expo-router'; +import AddSensorScreen from '../[id]/add-sensor'; +import { useBLE } from '@/contexts/BLEContext'; +import { useBeneficiary } from '@/contexts/BeneficiaryContext'; + +// Mock dependencies +jest.mock('expo-router', () => ({ + useLocalSearchParams: jest.fn(), + router: { + push: jest.fn(), + back: jest.fn(), + }, + useFocusEffect: jest.fn((callback) => callback()), +})); + +jest.mock('@/contexts/BLEContext', () => ({ + useBLE: jest.fn(), +})); + +jest.mock('@/contexts/BeneficiaryContext', () => ({ + useBeneficiary: jest.fn(), +})); + +jest.mock('expo-device', () => ({ + isDevice: true, +})); + +jest.mock('@/services/analytics', () => ({ + analytics: { + trackSensorScanStart: jest.fn(), + trackSensorScanComplete: jest.fn(), + trackSensorSetupStart: jest.fn(), + }, +})); + +describe('AddSensorScreen - Signal Strength UI', () => { + const mockStopScan = jest.fn(); + const mockScanDevices = jest.fn(); + const mockClearError = jest.fn(); + + const createMockDevice = (rssi: number, id: string = 'device-1') => ({ + id, + name: `WP_497_${id}`, + mac: id.toUpperCase(), + rssi, + wellId: 497, + }); + + beforeEach(() => { + jest.clearAllMocks(); + + (useLocalSearchParams as jest.Mock).mockReturnValue({ id: '1' }); + + (useBeneficiary as jest.Mock).mockReturnValue({ + currentBeneficiary: { + id: 1, + name: 'Maria', + }, + }); + }); + + const setupMockBLE = (devices: any[] = [], isScanning = false) => { + (useBLE as jest.Mock).mockReturnValue({ + foundDevices: devices, + isScanning, + connectedDevices: new Set(), + isBLEAvailable: true, + error: null, + permissionError: false, + scanDevices: mockScanDevices, + stopScan: mockStopScan, + connectDevice: jest.fn(), + disconnectDevice: jest.fn(), + getWiFiList: jest.fn(), + setWiFi: jest.fn(), + getCurrentWiFi: jest.fn(), + rebootDevice: jest.fn(), + cleanupBLE: jest.fn(), + clearError: mockClearError, + }); + }; + + describe('Signal strength labels', () => { + it('displays "Excellent" for strong signals (>= -50 dBm)', () => { + const devices = [createMockDevice(-45)]; + setupMockBLE(devices); + + const { getByText } = render(); + + expect(getByText('Excellent')).toBeTruthy(); + expect(getByText('(-45 dBm)')).toBeTruthy(); + }); + + it('displays "Good" for good signals (-51 to -60 dBm)', () => { + const devices = [createMockDevice(-55)]; + setupMockBLE(devices); + + const { getByText } = render(); + + expect(getByText('Good')).toBeTruthy(); + expect(getByText('(-55 dBm)')).toBeTruthy(); + }); + + it('displays "Fair" for fair signals (-61 to -70 dBm)', () => { + const devices = [createMockDevice(-65)]; + setupMockBLE(devices); + + const { getByText } = render(); + + expect(getByText('Fair')).toBeTruthy(); + expect(getByText('(-65 dBm)')).toBeTruthy(); + }); + + it('displays "Weak" for weak signals (< -70 dBm)', () => { + const devices = [createMockDevice(-75)]; + setupMockBLE(devices); + + const { getByText } = render(); + + expect(getByText('Weak')).toBeTruthy(); + expect(getByText('(-75 dBm)')).toBeTruthy(); + }); + }); + + describe('Multiple devices with different signal strengths', () => { + it('displays correct labels for each device', () => { + const devices = [ + createMockDevice(-45, 'dev1'), + createMockDevice(-55, 'dev2'), + createMockDevice(-65, 'dev3'), + createMockDevice(-80, 'dev4'), + ]; + setupMockBLE(devices); + + const { getByText } = render(); + + expect(getByText('Excellent')).toBeTruthy(); + expect(getByText('Good')).toBeTruthy(); + expect(getByText('Fair')).toBeTruthy(); + expect(getByText('Weak')).toBeTruthy(); + }); + + it('displays dBm values for each device', () => { + const devices = [ + createMockDevice(-50, 'dev1'), + createMockDevice(-60, 'dev2'), + ]; + setupMockBLE(devices); + + const { getByText } = render(); + + expect(getByText('(-50 dBm)')).toBeTruthy(); + expect(getByText('(-60 dBm)')).toBeTruthy(); + }); + }); + + describe('Device count display', () => { + it('shows correct device count in section header', () => { + const devices = [ + createMockDevice(-50, 'dev1'), + createMockDevice(-60, 'dev2'), + createMockDevice(-70, 'dev3'), + ]; + setupMockBLE(devices); + + const { getByText } = render(); + + expect(getByText('Found Sensors (3)')).toBeTruthy(); + }); + }); + + describe('Device selection with signal info', () => { + it('can select device with signal info displayed', () => { + const devices = [createMockDevice(-55, 'dev1')]; + setupMockBLE(devices); + + const { getByText } = render(); + + // Signal info should be visible + expect(getByText('Good')).toBeTruthy(); + expect(getByText('(-55 dBm)')).toBeTruthy(); + }); + }); + + describe('Boundary RSSI values', () => { + it('handles exact -50 dBm boundary (Excellent)', () => { + const devices = [createMockDevice(-50)]; + setupMockBLE(devices); + + const { getByText } = render(); + + expect(getByText('Excellent')).toBeTruthy(); + }); + + it('handles exact -60 dBm boundary (Good)', () => { + const devices = [createMockDevice(-60)]; + setupMockBLE(devices); + + const { getByText } = render(); + + expect(getByText('Good')).toBeTruthy(); + }); + + it('handles exact -70 dBm boundary (Fair)', () => { + const devices = [createMockDevice(-70)]; + setupMockBLE(devices); + + const { getByText } = render(); + + expect(getByText('Fair')).toBeTruthy(); + }); + + it('handles -71 dBm (Weak)', () => { + const devices = [createMockDevice(-71)]; + setupMockBLE(devices); + + const { getByText } = render(); + + expect(getByText('Weak')).toBeTruthy(); + }); + }); + + describe('Extreme RSSI values', () => { + it('handles very strong signal (-30 dBm)', () => { + const devices = [createMockDevice(-30)]; + setupMockBLE(devices); + + const { getByText } = render(); + + expect(getByText('Excellent')).toBeTruthy(); + expect(getByText('(-30 dBm)')).toBeTruthy(); + }); + + it('handles very weak signal (-100 dBm)', () => { + const devices = [createMockDevice(-100)]; + setupMockBLE(devices); + + const { getByText } = render(); + + expect(getByText('Weak')).toBeTruthy(); + expect(getByText('(-100 dBm)')).toBeTruthy(); + }); + }); + + describe('Empty state', () => { + it('does not show signal UI when no devices found', () => { + setupMockBLE([]); + + const { queryByText } = render(); + + expect(queryByText('Excellent')).toBeNull(); + expect(queryByText('Good')).toBeNull(); + expect(queryByText('Fair')).toBeNull(); + expect(queryByText('Weak')).toBeNull(); + }); + }); + + describe('Scanning state', () => { + it('does not show signal UI while scanning', () => { + setupMockBLE([], true); + + const { getByText, queryByText } = render(); + + expect(getByText('Scanning for WP sensors...')).toBeTruthy(); + expect(queryByText('Excellent')).toBeNull(); + }); + }); +});