import React from 'react'; import { render, screen, waitFor } from '@testing-library/react'; import { BrowserRouter } from 'react-router-dom'; import Dashboard from '../pages/Dashboard'; import { transformSensorDataToChartData, transformSensorDataToCardData, validateChartData, createDefaultChartData, createDefaultCardData } from '../utils/dataTransformers'; import { useDevices } from '../hooks/useDevices'; import { useSensorData } from '../hooks/useSensorData'; // Mock the hooks jest.mock('../hooks/useDevices'); jest.mock('../hooks/useSensorData'); const mockUseDevices = useDevices as jest.MockedFunction; const mockUseSensorData = useSensorData as jest.MockedFunction; // Mock Recharts components jest.mock('recharts', () => ({ LineChart: ({ children, data }: any) => (
{children}
), Line: ({ dataKey }: any) => (
Line: {dataKey}
), XAxis: () =>
XAxis
, YAxis: () =>
YAxis
, CartesianGrid: () =>
CartesianGrid
, Tooltip: () =>
Tooltip
, Legend: () =>
Legend
, ResponsiveContainer: ({ children }: any) => (
{children}
), })); 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(); // 기본 mock 설정 mockUseDevices.mockReturnValue({ devices: mockDevices, loading: false, error: null, refetch: jest.fn() }); mockUseSensorData.mockReturnValue({ latestData: [], loading: false, error: null, refetch: jest.fn() }); }); describe('데이터 변환 유틸리티 함수 테스트', () => { it('transformSensorDataToChartData가 빈 배열을 처리한다', () => { const result = transformSensorDataToChartData([]); expect(result).toEqual(createDefaultChartData()); }); it('transformSensorDataToChartData가 null 데이터를 처리한다', () => { const result = transformSensorDataToChartData(null as any); expect(result).toEqual(createDefaultChartData()); }); it('transformSensorDataToChartData가 유효한 데이터를 변환한다', () => { const validData = [ { 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' } ]; const result = transformSensorDataToChartData(validData); expect(result).toHaveLength(1); expect(result[0].device_id).toBe('sensor-001'); expect(result[0].temperature).toBe(25.5); expect(result[0].humidity).toBe(60.0); }); it('transformSensorDataToChartData가 잘못된 데이터를 필터링한다', () => { const invalidData = [ { invalid_key: 'test' }, // device_id가 없음 { device_id: 'sensor-001', temperature: 'invalid' }, // 숫자가 아닌 값 { device_id: 'sensor-002', temperature: 25.5, humidity: 60.0 } // 유효한 데이터 ]; const result = transformSensorDataToChartData(invalidData); // 유효한 데이터만 처리됨 expect(result).toHaveLength(1); expect(result[0].device_id).toBe('sensor-002'); }); it('transformSensorDataToCardData가 null 데이터를 처리한다', () => { const result = transformSensorDataToCardData(null); expect(result).toEqual(createDefaultCardData()); }); it('transformSensorDataToCardData가 유효한 데이터를 변환한다', () => { const validData = { 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' }; const result = transformSensorDataToCardData(validData); expect(result).toHaveLength(10); // 모든 센서 타입 expect(result[0].title).toBe('현재 온도'); expect(result[0].value).toBe(25.5); expect(result[0].unit).toBe('°C'); }); it('validateChartData가 유효한 데이터를 검증한다', () => { const validData = createDefaultChartData(); expect(validateChartData(validData)).toBe(true); }); it('validateChartData가 잘못된 데이터를 감지한다', () => { const invalidData = [ { device_id: 'test' } // 필수 필드 누락 ] as any; expect(validateChartData(invalidData)).toBe(false); }); }); describe('Dashboard 컴포넌트 데이터 변환 테스트', () => { it('빈 데이터 시 기본 카드들이 렌더링된다', () => { mockUseSensorData.mockReturnValue({ latestData: [], loading: false, error: null, refetch: jest.fn() }); renderWithRouter(); // 기본 센서 카드들이 렌더링되는지 확인 expect(screen.getByText('현재 온도')).toBeInTheDocument(); expect(screen.getByText('현재 습도')).toBeInTheDocument(); expect(screen.getByText('PM10')).toBeInTheDocument(); expect(screen.getByText('PM2.5')).toBeInTheDocument(); expect(screen.getByText('기압')).toBeInTheDocument(); expect(screen.getByText('조도')).toBeInTheDocument(); expect(screen.getByText('TVOC')).toBeInTheDocument(); expect(screen.getByText('CO2')).toBeInTheDocument(); expect(screen.getByText('O2')).toBeInTheDocument(); expect(screen.getByText('CO')).toBeInTheDocument(); }); it('유효한 센서 데이터가 올바르게 변환되어 표시된다', async () => { const validData = [ { 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' } ]; mockUseSensorData.mockReturnValue({ latestData: validData, loading: false, error: null, refetch: jest.fn() }); renderWithRouter(); // 변환된 값들이 올바르게 표시되는지 확인 await waitFor(() => { expect(screen.getByText('25.5°C')).toBeInTheDocument(); expect(screen.getByText('60.0%')).toBeInTheDocument(); expect(screen.getByText('15.2μg/m³')).toBeInTheDocument(); expect(screen.getByText('8.1μg/m³')).toBeInTheDocument(); expect(screen.getByText('1013.3hPa')).toBeInTheDocument(); expect(screen.getByText('500lux')).toBeInTheDocument(); expect(screen.getByText('120ppb')).toBeInTheDocument(); expect(screen.getByText('450ppm')).toBeInTheDocument(); expect(screen.getByText('20.9%')).toBeInTheDocument(); expect(screen.getByText('0.5ppm')).toBeInTheDocument(); }); }); it('잘못된 데이터가 포함된 경우 유효한 데이터만 처리한다', async () => { const mixedData = [ { 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: null, humidity: undefined, pm10: NaN, pm25: Infinity, pressure: 'invalid', illumination: 500, tvoc: 120, co2: 450, o2: 20.9, co: 0.5, recorded_time: '2025-08-01T10:00:00Z' } ]; mockUseSensorData.mockReturnValue({ latestData: mixedData, loading: false, error: null, refetch: jest.fn() }); renderWithRouter(); // 첫 번째 센서의 유효한 데이터가 표시되는지 확인 await waitFor(() => { expect(screen.getByText('25.5°C')).toBeInTheDocument(); expect(screen.getByText('60.0%')).toBeInTheDocument(); expect(screen.getByText('15.2μg/m³')).toBeInTheDocument(); expect(screen.getByText('8.1μg/m³')).toBeInTheDocument(); }); }); it('차트 데이터가 올바르게 변환되어 렌더링된다', async () => { const validData = [ { 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' } ]; mockUseSensorData.mockReturnValue({ latestData: validData, loading: false, error: null, refetch: jest.fn() }); renderWithRouter(); // 차트들이 렌더링되는지 확인 (여러 개의 차트가 있음) await waitFor(() => { const charts = screen.getAllByTestId('line-chart'); expect(charts.length).toBeGreaterThan(0); }); // 첫 번째 차트의 데이터가 올바르게 변환되었는지 확인 const charts = screen.getAllByTestId('line-chart'); const firstChartData = charts[0].getAttribute('data-chart-data'); const parsedData = JSON.parse(firstChartData || '[]'); expect(parsedData).toHaveLength(1); expect(parsedData[0].device_id).toBe('sensor-001'); expect(parsedData[0].temperature).toBe(25.5); expect(parsedData[0].humidity).toBe(60.0); }); }); describe('데이터 변환 오류 처리 테스트', () => { it('데이터 변환 중 오류가 발생하면 기본 데이터를 사용한다', async () => { // 잘못된 데이터 구조로 오류 유발 const invalidData = [ { device_id: 'sensor-001', temperature: 'invalid_string', // 숫자가 아닌 값 humidity: null, pm10: undefined, pm25: NaN, pressure: Infinity, illumination: -Infinity, tvoc: 'not_a_number', co2: {}, o2: [], co: () => {}, recorded_time: 123 // 문자열이어야 함 } ]; mockUseSensorData.mockReturnValue({ latestData: invalidData, loading: false, error: null, refetch: jest.fn() }); renderWithRouter(); // 기본값들이 표시되는지 확인 await waitFor(() => { expect(screen.getByText('0.0°C')).toBeInTheDocument(); expect(screen.getByText('0.0%')).toBeInTheDocument(); expect(screen.getByText('0.0μg/m³')).toBeInTheDocument(); }); }); it('빈 배열이 전달되면 기본 차트 데이터가 사용된다', async () => { mockUseSensorData.mockReturnValue({ latestData: [], loading: false, error: null, refetch: jest.fn() }); renderWithRouter(); await waitFor(() => { const charts = screen.getAllByTestId('line-chart'); expect(charts.length).toBeGreaterThan(0); }); const charts = screen.getAllByTestId('line-chart'); const firstChartData = charts[0].getAttribute('data-chart-data'); const parsedData = JSON.parse(firstChartData || '[]'); expect(parsedData).toEqual(createDefaultChartData()); }); }); });