diff --git a/__tests__/components/WiFiSignalIndicator.test.tsx b/__tests__/components/WiFiSignalIndicator.test.tsx
new file mode 100644
index 0000000..f933a56
--- /dev/null
+++ b/__tests__/components/WiFiSignalIndicator.test.tsx
@@ -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();
+ expect(root).toBeDefined();
+ });
+
+ it('renders with small size', () => {
+ const { root } = render();
+ expect(root).toBeDefined();
+ });
+
+ it('renders with medium size', () => {
+ const { root } = render();
+ expect(root).toBeDefined();
+ });
+
+ it('renders with large size', () => {
+ const { root } = render();
+ 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();
+ expect(root).toBeDefined();
+ });
+ });
+
+ it('accepts all size variants', () => {
+ const sizes: ('small' | 'medium' | 'large')[] = ['small', 'medium', 'large'];
+ sizes.forEach((size) => {
+ const { root } = render();
+ expect(root).toBeDefined();
+ });
+ });
+
+ it('uses medium size by default', () => {
+ const withoutSize = render();
+ const withMediumSize = render();
+ expect(withoutSize.root).toBeDefined();
+ expect(withMediumSize.root).toBeDefined();
+ });
+ });
+});
diff --git a/components/WiFiSignalIndicator.tsx b/components/WiFiSignalIndicator.tsx
new file mode 100644
index 0000000..c77f9ed
--- /dev/null
+++ b/components/WiFiSignalIndicator.tsx
@@ -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 (
+
+ {[1, 2, 3, 4].map((barNumber) => {
+ const isActive = barNumber <= activeBars;
+ const barHeight = (maxHeight / 4) * barNumber;
+
+ return (
+
+ );
+ })}
+
+ );
+}
+
+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;
+}