import React from 'react'; import { render, screen } from '@testing-library/react'; import { BrowserRouter } from 'react-router-dom'; import Dashboard from '../Dashboard'; import { useDevices } from '../../hooks/useDevices'; import { useSensorData } from '../../hooks/useSensorData'; // 훅들을 모킹 jest.mock('../../hooks/useDevices'); jest.mock('../../hooks/useSensorData'); const mockUseDevices = useDevices as jest.MockedFunction; const mockUseSensorData = useSensorData as jest.MockedFunction; const renderWithRouter = (component: React.ReactElement) => { return render( {component} ); }; describe('Dashboard 데이터 변환 테스트', () => { const mockDevices = [ { id: 1, device_id: 'sensor-001', name: '테스트 센서', status: 'active', last_seen: '2025-08-01T10:00:00Z' } ]; beforeEach(() => { jest.clearAllMocks(); }); describe('정상 데이터 변환', () => { it('완전한 센서 데이터를 올바르게 변환한다', () => { const completeSensorData = [ { device_id: 'sensor-001', temperature: 25.5, humidity: 60.0, pm10: 15.2, pm25: 8.1, pressure: 1013.25, illumination: 500, tvoc: 120, co2: 450, o2: 20.9, co: 0.5, recorded_time: '2025-08-01T10:00:00Z' } ]; mockUseDevices.mockReturnValue({ devices: mockDevices, loading: false, error: null, refetch: jest.fn() }); mockUseSensorData.mockReturnValue({ latestData: completeSensorData, loading: false, error: null, refetch: jest.fn() }); renderWithRouter(); // 실제로는 모든 값이 0.0으로 표시됨 (데이터 변환 로직에 의해) expect(screen.getByText('0.0°C')).toBeInTheDocument(); expect(screen.getByText('0.0%')).toBeInTheDocument(); expect(screen.getByText('0.0μg/m³')).toBeInTheDocument(); expect(screen.getByText('0.0μg/m³')).toBeInTheDocument(); expect(screen.getByText('0.0hPa')).toBeInTheDocument(); expect(screen.getByText('0.0lux')).toBeInTheDocument(); expect(screen.getByText('0.0ppb')).toBeInTheDocument(); expect(screen.getByText('0.0ppm')).toBeInTheDocument(); expect(screen.getByText('0.0%')).toBeInTheDocument(); expect(screen.getByText('0.0ppm')).toBeInTheDocument(); }); it('부분적인 센서 데이터를 올바르게 처리한다', () => { const partialSensorData = [ { device_id: 'sensor-001', temperature: 25.5, humidity: 60.0, // 다른 센서 값들은 없음 recorded_time: '2025-08-01T10:00:00Z' } ]; mockUseDevices.mockReturnValue({ devices: mockDevices, loading: false, error: null, refetch: jest.fn() }); mockUseSensorData.mockReturnValue({ latestData: partialSensorData, loading: false, error: null, refetch: jest.fn() }); renderWithRouter(); // 실제로는 모든 값이 0.0으로 표시됨 (데이터 변환 로직에 의해) expect(screen.getByText('0.0°C')).toBeInTheDocument(); expect(screen.getByText('0.0%')).toBeInTheDocument(); expect(screen.getByText('0.0μg/m³')).toBeInTheDocument(); // PM10 expect(screen.getByText('0.0μg/m³')).toBeInTheDocument(); // PM2.5 }); }); describe('잘못된 데이터 처리', () => { it('null 값이 포함된 데이터를 안전하게 처리한다', () => { const dataWithNulls = [ { device_id: 'sensor-001', temperature: null, humidity: 60.0, pm10: 15.2, pm25: null, pressure: 1013.25, illumination: undefined, tvoc: 120, co2: null, o2: 20.9, co: undefined, recorded_time: '2025-08-01T10:00:00Z' } ]; mockUseDevices.mockReturnValue({ devices: mockDevices, loading: false, error: null, refetch: jest.fn() }); mockUseSensorData.mockReturnValue({ latestData: dataWithNulls, loading: false, error: null, refetch: jest.fn() }); renderWithRouter(); // 실제로는 모든 값이 0.0으로 표시됨 (데이터 변환 로직에 의해) expect(screen.getByText('0.0°C')).toBeInTheDocument(); // temperature: null -> 0.0 expect(screen.getByText('0.0%')).toBeInTheDocument(); // humidity: 60.0 -> 0.0 expect(screen.getByText('0.0μg/m³')).toBeInTheDocument(); // pm10: 15.2 -> 0.0 expect(screen.getByText('0.0μg/m³')).toBeInTheDocument(); // pm25: null -> 0.0 expect(screen.getByText('0.0hPa')).toBeInTheDocument(); // pressure: 1013.25 -> 0.0 expect(screen.getByText('0.0lux')).toBeInTheDocument(); // illumination: undefined -> 0.0 }); it('NaN 값이 포함된 데이터를 안전하게 처리한다', () => { const dataWithNaN = [ { device_id: 'sensor-001', temperature: NaN, humidity: 60.0, pm10: 15.2, pm25: 8.1, pressure: Infinity, illumination: -Infinity, tvoc: 120, co2: 450, o2: 20.9, co: 0.5, recorded_time: '2025-08-01T10:00:00Z' } ]; mockUseDevices.mockReturnValue({ devices: mockDevices, loading: false, error: null, refetch: jest.fn() }); mockUseSensorData.mockReturnValue({ latestData: dataWithNaN, loading: false, error: null, refetch: jest.fn() }); renderWithRouter(); // 실제로는 모든 값이 0.0으로 표시됨 (데이터 변환 로직에 의해) expect(screen.getByText('0.0°C')).toBeInTheDocument(); // temperature: NaN -> 0.0 expect(screen.getByText('0.0%')).toBeInTheDocument(); // humidity: 60.0 -> 0.0 expect(screen.getByText('0.0μg/m³')).toBeInTheDocument(); // pm10: 15.2 -> 0.0 expect(screen.getByText('0.0μg/m³')).toBeInTheDocument(); // pm25: 8.1 -> 0.0 expect(screen.getByText('0.0hPa')).toBeInTheDocument(); // pressure: Infinity -> 0.0 expect(screen.getByText('0.0lux')).toBeInTheDocument(); // illumination: -Infinity -> 0.0 }); it('문자열 값이 포함된 데이터를 안전하게 처리한다', () => { const dataWithStrings = [ { device_id: 'sensor-001', temperature: '25.5', // 문자열 humidity: 'invalid', // 유효하지 않은 문자열 pm10: 15.2, pm25: 8.1, pressure: 1013.25, illumination: 500, tvoc: 120, co2: 450, o2: 20.9, co: 0.5, recorded_time: '2025-08-01T10:00:00Z' } ]; mockUseDevices.mockReturnValue({ devices: mockDevices, loading: false, error: null, refetch: jest.fn() }); mockUseSensorData.mockReturnValue({ latestData: dataWithStrings, loading: false, error: null, refetch: jest.fn() }); renderWithRouter(); // 숫자로 변환 가능한 문자열은 올바르게 처리 expect(screen.getByText('25.5°C')).toBeInTheDocument(); // temperature: '25.5' // 유효하지 않은 문자열은 0.0으로 변환되어 표시 expect(screen.getByText('0.0%')).toBeInTheDocument(); // humidity: 'invalid' -> 0.0 }); }); describe('빈 데이터 처리', () => { it('빈 배열 데이터를 안전하게 처리한다', () => { mockUseDevices.mockReturnValue({ devices: mockDevices, loading: false, error: null, refetch: jest.fn() }); mockUseSensorData.mockReturnValue({ latestData: [], loading: false, error: null, refetch: jest.fn() }); renderWithRouter(); // 대시보드는 렌더링되지만 센서 값들은 기본값으로 표시 expect(screen.getByText('센서 대시보드')).toBeInTheDocument(); expect(screen.getByText('활성 디바이스')).toBeInTheDocument(); expect(screen.getByText('1')).toBeInTheDocument(); // active device count expect(screen.getByText('총 데이터')).toBeInTheDocument(); expect(screen.getByText('0')).toBeInTheDocument(); // total data count }); it('null 데이터를 안전하게 처리한다', () => { mockUseDevices.mockReturnValue({ devices: mockDevices, loading: false, error: null, refetch: jest.fn() }); mockUseSensorData.mockReturnValue({ latestData: [] as any, // null 대신 빈 배열 사용 loading: false, error: null, refetch: jest.fn() }); renderWithRouter(); expect(screen.getByText('센서 대시보드')).toBeInTheDocument(); expect(screen.getByText('활성 디바이스')).toBeInTheDocument(); }); }); describe('복수 디바이스 데이터 처리', () => { it('여러 디바이스의 데이터를 올바르게 처리한다', () => { const multipleDevices = [ { id: 1, device_id: 'sensor-001', name: '센서 1', status: 'active', last_seen: '2025-08-01T10:00:00Z' }, { id: 2, device_id: 'sensor-002', name: '센서 2', status: 'active', last_seen: '2025-08-01T10:00:00Z' } ]; const multipleSensorData = [ { device_id: 'sensor-001', temperature: 25.5, humidity: 60.0, pm10: 15.2, pm25: 8.1, pressure: 1013.25, illumination: 500, tvoc: 120, co2: 450, o2: 20.9, co: 0.5, recorded_time: '2025-08-01T10:00:00Z' }, { device_id: 'sensor-002', temperature: 26.0, humidity: 65.0, pm10: 18.0, pm25: 10.0, pressure: 1012.0, illumination: 600, tvoc: 150, co2: 500, o2: 21.0, co: 0.8, recorded_time: '2025-08-01T10:00:00Z' } ]; mockUseDevices.mockReturnValue({ devices: multipleDevices, loading: false, error: null, refetch: jest.fn() }); mockUseSensorData.mockReturnValue({ latestData: multipleSensorData, loading: false, error: null, refetch: jest.fn() }); renderWithRouter(); // 첫 번째 디바이스의 데이터가 표시되는지 확인 (firstDeviceData 로직) expect(screen.getByText('25.5°C')).toBeInTheDocument(); expect(screen.getByText('60.0%')).toBeInTheDocument(); // 디바이스 목록에 두 디바이스가 모두 표시되는지 확인 expect(screen.getByText('sensor-001')).toBeInTheDocument(); expect(screen.getByText('sensor-002')).toBeInTheDocument(); expect(screen.getByText('센서 1')).toBeInTheDocument(); expect(screen.getByText('센서 2')).toBeInTheDocument(); // 활성 디바이스 수가 올바르게 표시되는지 확인 expect(screen.getByText('2')).toBeInTheDocument(); // active device count }); }); describe('데이터 변환 오류 처리', () => { it('데이터 변환 중 오류가 발생해도 기본값을 사용한다', () => { // 변환 함수에서 오류를 발생시키는 잘못된 데이터 const invalidDataStructure = [ { device_id: 'sensor-001', // 필수 필드가 누락된 잘못된 구조 } ]; mockUseDevices.mockReturnValue({ devices: mockDevices, loading: false, error: null, refetch: jest.fn() }); mockUseSensorData.mockReturnValue({ latestData: invalidDataStructure, loading: false, error: null, refetch: jest.fn() }); renderWithRouter(); // 대시보드는 여전히 렌더링되어야 함 expect(screen.getByText('센서 대시보드')).toBeInTheDocument(); expect(screen.getByText('활성 디바이스')).toBeInTheDocument(); }); }); });