/* eslint-disable @typescript-eslint/no-explicit-any */ import { render, screen, fireEvent, act } from '@testing-library/react'; import '@testing-library/jest-dom'; import { vi, describe, it, expect, beforeEach, afterEach } from 'vitest'; import Timetable from '../../../../src/components/stats/SleepTimetable'; import { useStats } from '../../../../src/hooks/useStats'; vi.mock('../../../../src/hooks/useStats', () => ({ useStats: vi.fn(), })); describe('SleepTimetable', () => { const defaultProps = { from_date: '2025-04-18', // Friday to_date: '2025-04-21', // Monday day_start: '22:00', day_end: '08:00', }; beforeEach(() => { vi.clearAllMocks(); // Default to desktop width vi.stubGlobal('innerWidth', 1024); }); afterEach(() => { vi.useRealTimers(); }); describe('Loading & Empty States', () => { it('renders loading state', () => { (useStats as any).mockReturnValue({ timetable: null, isLoading: true }); render(); expect(screen.getByText(/Loading timetable.../i)).toBeInTheDocument(); }); it('renders empty state when timetable.slots is empty', () => { (useStats as any).mockReturnValue({ timetable: { slots: [] }, isLoading: false }); render(); expect(screen.getByText(/No data available/i)).toBeInTheDocument(); }); it('renders empty state when timetable is null', () => { (useStats as any).mockReturnValue({ timetable: null, isLoading: false }); render(); expect(screen.getByText(/No data available/i)).toBeInTheDocument(); }); }); describe('Date Range Edge Cases', () => { it('handles a single day range correctly', () => { const singleDayProps = { ...defaultProps, from_date: '2025-04-18', to_date: '2025-04-18' }; (useStats as any).mockReturnValue({ timetable: { slots: [{ date: '2025-04-18', sleeping: true, slot_start: '10:00', slot_end: '11:00' }] }, isLoading: false }); render(); // Check if it renders the single day header // Check if it renders the single day header with date format expect(screen.getByText(/Fri 18\/4/i)).toBeInTheDocument(); }); it('handles a large date range', () => { const largeRangeProps = { ...defaultProps, from_date: '2025-01-01', to_date: '2025-01-31' }; (useStats as any).mockReturnValue({ timetable: { slots: [{ date: '2025-01-05', sleeping: true, slot_start: '10:00', slot_end: '11:00' }] }, isLoading: false }); render(); // Ensure it doesn't crash and renders the title expect(screen.getByText(/Sleep Timetable/i)).toBeInTheDocument(); }); it('handles empty date range (start > end)', () => { const invalidRangeProps = { ...defaultProps, from_date: '2025-04-21', to_date: '2025-04-18' }; (useStats as any).mockReturnValue({ timetable: { slots: [] }, isLoading: false }); render(); // Should gracefully handle empty days array (no crash) and show no data expect(screen.getByText(/No data available/i)).toBeInTheDocument(); }); }); describe('Responsive Tiers', () => { const mockTimetable = { slots: [ { date: '2025-04-18', sleeping: true, slot_start: '23:00', slot_end: '07:00' }, ], }; it('Desktop Tier (>= 768px): shows all days', () => { (useStats as any).mockReturnValue({ timetable: mockTimetable, isLoading: false }); vi.stubGlobal('innerWidth', 1024); render(); expect(screen.getByText(/Fri 18\/4/i)).toBeInTheDocument(); expect(screen.getByText(/Sat 19\/4/i)).toBeInTheDocument(); expect(screen.getByText(/Sun 20\/4/i)).toBeInTheDocument(); expect(screen.getByText(/Mon 21\/4/i)).toBeInTheDocument(); }); it('Medium Tier (400-768px): shows tab groups and updates on click', () => { (useStats as any).mockReturnValue({ timetable: mockTimetable, isLoading: false }); vi.stubGlobal('innerWidth', 500); const { container } = render(); act(() => { window.dispatchEvent(new Event('resize')); }); const tabs = screen.getAllByRole('button'); expect(tabs.length).toBeGreaterThan(0); const tab1 = tabs[0]; expect(tab1.textContent).toMatch(/19/); expect(tab1.textContent).toMatch(/21/); expect(tab1).toHaveClass('bg-blue-600'); const tab2 = tabs[1]; act(() => { fireEvent.click(tab2); }); expect(tab2).toHaveClass('bg-blue-600'); expect(container.querySelector('[data-testid^="cell-2025-04-18-"]')).toBeInTheDocument(); expect(container.querySelector('[data-testid^="cell-2025-04-21-"]')).not.toBeInTheDocument(); }); it('Small Tier (< 400px): shows single day tabs', () => { (useStats as any).mockReturnValue({ timetable: mockTimetable, isLoading: false }); vi.stubGlobal('innerWidth', 300); const { container } = render(); act(() => { window.dispatchEvent(new Event('resize')); }); expect(screen.getByRole('button', { name: /21/i })).toBeInTheDocument(); expect(screen.getByRole('button', { name: /21\/4/i })).toBeInTheDocument(); act(() => { fireEvent.click(screen.getByRole('button', { name: /20/i })); }); expect(container.querySelector('[data-testid^="cell-2025-04-20-"]')).toBeInTheDocument(); expect(container.querySelector('[data-testid^="cell-2025-04-21-"]')).not.toBeInTheDocument(); }); }); describe('Grid Accuracy', () => { it('renders sleeping cells correctly', () => { const day = '2025-04-18'; (useStats as any).mockReturnValue({ timetable: { slots: [ { date: day, sleeping: true, slot_start: '10:00', slot_end: '10:30' } ], }, isLoading: false, }); vi.stubGlobal('innerWidth', 1024); const { container } = render(); expect(container.querySelector(`[data-testid="cell-${day}-10:00"]`)).toHaveClass('bg-green-500'); expect(container.querySelector(`[data-testid="cell-${day}-10:15"]`)).toHaveClass('bg-green-500'); expect(container.querySelector(`[data-testid="cell-${day}-10:30"]`)).toHaveClass('bg-gray-50'); }); it('handles midnight spanning correctly using two slots (as implemented)', () => { const day1 = '2025-04-18'; const day2 = '2025-04-19'; (useStats as any).mockReturnValue({ timetable: { slots: [ { date: day1, sleeping: true, slot_start: '23:00', slot_end: '00:00' }, { date: day2, sleeping: true, slot_start: '00:00', slot_end: '01:00' }, ], }, isLoading: false, }); vi.stubGlobal('innerWidth', 1024); const { container } = render(); expect(container.querySelector(`[data-testid="cell-${day1}-23:00"]`)).toHaveClass('bg-green-500'); expect(container.querySelector(`[data-testid="cell-${day2}-00:00"]`)).toHaveClass('bg-green-500'); }); }); describe('Time Marker', () => { it('renders red marker at correct position', () => { vi.useFakeTimers(); vi.setSystemTime(new Date('2025-04-18T12:00:00')); (useStats as any).mockReturnValue({ timetable: { slots: [{ date: '2025-04-18', sleeping: true, slot_start: '11:00', slot_end: '13:00' }] }, isLoading: false, }); vi.stubGlobal('innerWidth', 1024); const { container } = render(); const marker = container.querySelector('.bg-red-500'); expect(marker).toBeInTheDocument(); expect(marker).toHaveStyle({ top: '384px' }); vi.useRealTimers(); }); }); });