Add WiFi signal strength indicator component
Add reusable WiFiSignalIndicator component with visual bars showing signal strength levels (excellent/good/fair/weak) based on RSSI values. Includes helper functions for signal labels and colors. 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
parent
cc626d6b67
commit
ed6970e67a
145
__tests__/components/WiFiSignalIndicator.test.tsx
Normal file
145
__tests__/components/WiFiSignalIndicator.test.tsx
Normal file
@ -0,0 +1,145 @@
|
||||
import React from 'react';
|
||||
import { render } from '@testing-library/react-native';
|
||||
import {
|
||||
WiFiSignalIndicator,
|
||||
getSignalStrengthLabel,
|
||||
getSignalStrengthColor,
|
||||
} from '@/components/WiFiSignalIndicator';
|
||||
import { AppColors } from '@/constants/theme';
|
||||
|
||||
describe('WiFiSignalIndicator', () => {
|
||||
describe('Component rendering', () => {
|
||||
it('renders without crashing', () => {
|
||||
const { root } = render(<WiFiSignalIndicator rssi={-50} />);
|
||||
expect(root).toBeDefined();
|
||||
});
|
||||
|
||||
it('renders with small size', () => {
|
||||
const { root } = render(<WiFiSignalIndicator rssi={-50} size="small" />);
|
||||
expect(root).toBeDefined();
|
||||
});
|
||||
|
||||
it('renders with medium size', () => {
|
||||
const { root } = render(<WiFiSignalIndicator rssi={-50} size="medium" />);
|
||||
expect(root).toBeDefined();
|
||||
});
|
||||
|
||||
it('renders with large size', () => {
|
||||
const { root } = render(<WiFiSignalIndicator rssi={-50} size="large" />);
|
||||
expect(root).toBeDefined();
|
||||
});
|
||||
});
|
||||
|
||||
describe('Signal strength logic', () => {
|
||||
it('shows excellent signal for RSSI >= -50', () => {
|
||||
expect(getSignalStrengthLabel(-50)).toBe('Excellent');
|
||||
expect(getSignalStrengthLabel(-40)).toBe('Excellent');
|
||||
expect(getSignalStrengthLabel(-30)).toBe('Excellent');
|
||||
});
|
||||
|
||||
it('shows good signal for RSSI >= -60 and < -50', () => {
|
||||
expect(getSignalStrengthLabel(-60)).toBe('Good');
|
||||
expect(getSignalStrengthLabel(-55)).toBe('Good');
|
||||
expect(getSignalStrengthLabel(-59)).toBe('Good');
|
||||
});
|
||||
|
||||
it('shows fair signal for RSSI >= -70 and < -60', () => {
|
||||
expect(getSignalStrengthLabel(-70)).toBe('Fair');
|
||||
expect(getSignalStrengthLabel(-65)).toBe('Fair');
|
||||
expect(getSignalStrengthLabel(-69)).toBe('Fair');
|
||||
});
|
||||
|
||||
it('shows weak signal for RSSI < -70', () => {
|
||||
expect(getSignalStrengthLabel(-71)).toBe('Weak');
|
||||
expect(getSignalStrengthLabel(-80)).toBe('Weak');
|
||||
expect(getSignalStrengthLabel(-90)).toBe('Weak');
|
||||
});
|
||||
});
|
||||
|
||||
describe('Signal strength colors', () => {
|
||||
it('returns success color for excellent signal', () => {
|
||||
expect(getSignalStrengthColor(-50)).toBe(AppColors.success);
|
||||
expect(getSignalStrengthColor(-40)).toBe(AppColors.success);
|
||||
});
|
||||
|
||||
it('returns info color for good signal', () => {
|
||||
expect(getSignalStrengthColor(-60)).toBe(AppColors.info);
|
||||
expect(getSignalStrengthColor(-55)).toBe(AppColors.info);
|
||||
});
|
||||
|
||||
it('returns warning color for fair signal', () => {
|
||||
expect(getSignalStrengthColor(-70)).toBe(AppColors.warning);
|
||||
expect(getSignalStrengthColor(-65)).toBe(AppColors.warning);
|
||||
});
|
||||
|
||||
it('returns error color for weak signal', () => {
|
||||
expect(getSignalStrengthColor(-71)).toBe(AppColors.error);
|
||||
expect(getSignalStrengthColor(-80)).toBe(AppColors.error);
|
||||
});
|
||||
});
|
||||
|
||||
describe('Edge cases', () => {
|
||||
it('handles boundary values correctly', () => {
|
||||
// Test exact boundary values
|
||||
expect(getSignalStrengthLabel(-50)).toBe('Excellent'); // Boundary: excellent/good
|
||||
expect(getSignalStrengthLabel(-51)).toBe('Good');
|
||||
|
||||
expect(getSignalStrengthLabel(-60)).toBe('Good'); // Boundary: good/fair
|
||||
expect(getSignalStrengthLabel(-61)).toBe('Fair');
|
||||
|
||||
expect(getSignalStrengthLabel(-70)).toBe('Fair'); // Boundary: fair/weak
|
||||
expect(getSignalStrengthLabel(-71)).toBe('Weak');
|
||||
});
|
||||
|
||||
it('handles extreme RSSI values', () => {
|
||||
expect(getSignalStrengthLabel(-10)).toBe('Excellent'); // Very strong signal
|
||||
expect(getSignalStrengthLabel(-100)).toBe('Weak'); // Very weak signal
|
||||
expect(getSignalStrengthLabel(0)).toBe('Excellent'); // Maximum possible
|
||||
});
|
||||
|
||||
it('handles negative zero', () => {
|
||||
expect(getSignalStrengthLabel(-0)).toBe('Excellent');
|
||||
});
|
||||
});
|
||||
|
||||
describe('Real-world RSSI values', () => {
|
||||
it('handles typical home WiFi signals', () => {
|
||||
expect(getSignalStrengthLabel(-45)).toBe('Excellent'); // Next to router
|
||||
expect(getSignalStrengthLabel(-55)).toBe('Good'); // Same room
|
||||
expect(getSignalStrengthLabel(-65)).toBe('Fair'); // Next room
|
||||
expect(getSignalStrengthLabel(-75)).toBe('Weak'); // Far away
|
||||
});
|
||||
|
||||
it('handles typical office WiFi signals', () => {
|
||||
expect(getSignalStrengthLabel(-50)).toBe('Excellent');
|
||||
expect(getSignalStrengthLabel(-60)).toBe('Good');
|
||||
expect(getSignalStrengthLabel(-70)).toBe('Fair');
|
||||
expect(getSignalStrengthLabel(-80)).toBe('Weak');
|
||||
});
|
||||
});
|
||||
|
||||
describe('Component props', () => {
|
||||
it('accepts valid RSSI values', () => {
|
||||
const rssiValues = [-40, -50, -60, -70, -80];
|
||||
rssiValues.forEach((rssi) => {
|
||||
const { root } = render(<WiFiSignalIndicator rssi={rssi} />);
|
||||
expect(root).toBeDefined();
|
||||
});
|
||||
});
|
||||
|
||||
it('accepts all size variants', () => {
|
||||
const sizes: ('small' | 'medium' | 'large')[] = ['small', 'medium', 'large'];
|
||||
sizes.forEach((size) => {
|
||||
const { root } = render(<WiFiSignalIndicator rssi={-50} size={size} />);
|
||||
expect(root).toBeDefined();
|
||||
});
|
||||
});
|
||||
|
||||
it('uses medium size by default', () => {
|
||||
const withoutSize = render(<WiFiSignalIndicator rssi={-50} />);
|
||||
const withMediumSize = render(<WiFiSignalIndicator rssi={-50} size="medium" />);
|
||||
expect(withoutSize.root).toBeDefined();
|
||||
expect(withMediumSize.root).toBeDefined();
|
||||
});
|
||||
});
|
||||
});
|
||||
101
components/WiFiSignalIndicator.tsx
Normal file
101
components/WiFiSignalIndicator.tsx
Normal file
@ -0,0 +1,101 @@
|
||||
import React from 'react';
|
||||
import { View, StyleSheet } from 'react-native';
|
||||
import { AppColors } from '@/constants/theme';
|
||||
|
||||
export type SignalStrength = 'excellent' | 'good' | 'fair' | 'weak';
|
||||
|
||||
interface WiFiSignalIndicatorProps {
|
||||
rssi: number;
|
||||
size?: 'small' | 'medium' | 'large';
|
||||
}
|
||||
|
||||
/**
|
||||
* Visual WiFi signal strength indicator with bars
|
||||
*
|
||||
* RSSI ranges:
|
||||
* - Excellent: >= -50 dBm (4 bars)
|
||||
* - Good: >= -60 dBm (3 bars)
|
||||
* - Fair: >= -70 dBm (2 bars)
|
||||
* - Weak: < -70 dBm (1 bar)
|
||||
*/
|
||||
export function WiFiSignalIndicator({ rssi, size = 'medium' }: WiFiSignalIndicatorProps) {
|
||||
const getSignalColor = (rssi: number): string => {
|
||||
if (rssi >= -50) return AppColors.success;
|
||||
if (rssi >= -60) return AppColors.info;
|
||||
if (rssi >= -70) return AppColors.warning;
|
||||
return AppColors.error;
|
||||
};
|
||||
|
||||
const getBars = (rssi: number): number => {
|
||||
if (rssi >= -50) return 4;
|
||||
if (rssi >= -60) return 3;
|
||||
if (rssi >= -70) return 2;
|
||||
return 1;
|
||||
};
|
||||
|
||||
const color = getSignalColor(rssi);
|
||||
const activeBars = getBars(rssi);
|
||||
|
||||
const sizeStyles = {
|
||||
small: { width: 2, maxHeight: 12, gap: 2 },
|
||||
medium: { width: 3, maxHeight: 16, gap: 3 },
|
||||
large: { width: 4, maxHeight: 20, gap: 4 },
|
||||
};
|
||||
|
||||
const { width: barWidth, maxHeight, gap } = sizeStyles[size];
|
||||
|
||||
return (
|
||||
<View style={styles.container}>
|
||||
{[1, 2, 3, 4].map((barNumber) => {
|
||||
const isActive = barNumber <= activeBars;
|
||||
const barHeight = (maxHeight / 4) * barNumber;
|
||||
|
||||
return (
|
||||
<View
|
||||
key={barNumber}
|
||||
style={[
|
||||
styles.bar,
|
||||
{
|
||||
width: barWidth,
|
||||
height: barHeight,
|
||||
backgroundColor: isActive ? color : AppColors.border,
|
||||
marginLeft: barNumber === 1 ? 0 : gap,
|
||||
},
|
||||
]}
|
||||
/>
|
||||
);
|
||||
})}
|
||||
</View>
|
||||
);
|
||||
}
|
||||
|
||||
const styles = StyleSheet.create({
|
||||
container: {
|
||||
flexDirection: 'row',
|
||||
alignItems: 'flex-end',
|
||||
justifyContent: 'center',
|
||||
},
|
||||
bar: {
|
||||
borderRadius: 1,
|
||||
},
|
||||
});
|
||||
|
||||
/**
|
||||
* Get human-readable signal strength label
|
||||
*/
|
||||
export function getSignalStrengthLabel(rssi: number): string {
|
||||
if (rssi >= -50) return 'Excellent';
|
||||
if (rssi >= -60) return 'Good';
|
||||
if (rssi >= -70) return 'Fair';
|
||||
return 'Weak';
|
||||
}
|
||||
|
||||
/**
|
||||
* Get signal strength color
|
||||
*/
|
||||
export function getSignalStrengthColor(rssi: number): string {
|
||||
if (rssi >= -50) return AppColors.success;
|
||||
if (rssi >= -60) return AppColors.info;
|
||||
if (rssi >= -70) return AppColors.warning;
|
||||
return AppColors.error;
|
||||
}
|
||||
Loading…
x
Reference in New Issue
Block a user