Improve BLE scan UI with WiFiSignalIndicator component
- Replace simple icon-based signal display with WiFiSignalIndicator bars - Add human-readable signal strength labels (Excellent, Good, Fair, Weak) - Display dBm values in parentheses for technical reference - Add comprehensive tests for signal strength UI integration 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
parent
dad084c775
commit
e420631eba
@ -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 (
|
||||
<SafeAreaView style={styles.container} edges={['top', 'bottom']}>
|
||||
@ -309,13 +301,12 @@ export default function AddSensorScreen() {
|
||||
<Text style={styles.deviceMeta}>Well ID: {device.wellId}</Text>
|
||||
)}
|
||||
<View style={styles.signalRow}>
|
||||
<Ionicons
|
||||
name={getSignalIcon(device.rssi)}
|
||||
size={14}
|
||||
color={getSignalColor(device.rssi)}
|
||||
/>
|
||||
<Text style={[styles.signalText, { color: getSignalColor(device.rssi) }]}>
|
||||
{device.rssi} dBm
|
||||
<WiFiSignalIndicator rssi={device.rssi} size="small" />
|
||||
<Text style={[styles.signalLabel, { color: getSignalStrengthColor(device.rssi) }]}>
|
||||
{getSignalStrengthLabel(device.rssi)}
|
||||
</Text>
|
||||
<Text style={styles.signalDbm}>
|
||||
({device.rssi} dBm)
|
||||
</Text>
|
||||
</View>
|
||||
</View>
|
||||
@ -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,
|
||||
|
||||
275
app/(tabs)/beneficiaries/__tests__/add-sensor.signal-ui.test.tsx
Normal file
275
app/(tabs)/beneficiaries/__tests__/add-sensor.signal-ui.test.tsx
Normal file
@ -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(<AddSensorScreen />);
|
||||
|
||||
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(<AddSensorScreen />);
|
||||
|
||||
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(<AddSensorScreen />);
|
||||
|
||||
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(<AddSensorScreen />);
|
||||
|
||||
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(<AddSensorScreen />);
|
||||
|
||||
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(<AddSensorScreen />);
|
||||
|
||||
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(<AddSensorScreen />);
|
||||
|
||||
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(<AddSensorScreen />);
|
||||
|
||||
// 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(<AddSensorScreen />);
|
||||
|
||||
expect(getByText('Excellent')).toBeTruthy();
|
||||
});
|
||||
|
||||
it('handles exact -60 dBm boundary (Good)', () => {
|
||||
const devices = [createMockDevice(-60)];
|
||||
setupMockBLE(devices);
|
||||
|
||||
const { getByText } = render(<AddSensorScreen />);
|
||||
|
||||
expect(getByText('Good')).toBeTruthy();
|
||||
});
|
||||
|
||||
it('handles exact -70 dBm boundary (Fair)', () => {
|
||||
const devices = [createMockDevice(-70)];
|
||||
setupMockBLE(devices);
|
||||
|
||||
const { getByText } = render(<AddSensorScreen />);
|
||||
|
||||
expect(getByText('Fair')).toBeTruthy();
|
||||
});
|
||||
|
||||
it('handles -71 dBm (Weak)', () => {
|
||||
const devices = [createMockDevice(-71)];
|
||||
setupMockBLE(devices);
|
||||
|
||||
const { getByText } = render(<AddSensorScreen />);
|
||||
|
||||
expect(getByText('Weak')).toBeTruthy();
|
||||
});
|
||||
});
|
||||
|
||||
describe('Extreme RSSI values', () => {
|
||||
it('handles very strong signal (-30 dBm)', () => {
|
||||
const devices = [createMockDevice(-30)];
|
||||
setupMockBLE(devices);
|
||||
|
||||
const { getByText } = render(<AddSensorScreen />);
|
||||
|
||||
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(<AddSensorScreen />);
|
||||
|
||||
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(<AddSensorScreen />);
|
||||
|
||||
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(<AddSensorScreen />);
|
||||
|
||||
expect(getByText('Scanning for WP sensors...')).toBeTruthy();
|
||||
expect(queryByText('Excellent')).toBeNull();
|
||||
});
|
||||
});
|
||||
});
|
||||
Loading…
x
Reference in New Issue
Block a user