import React from 'react'; import { render, screen, waitFor, act } from '@testing-library/react'; import { SmartLoader, useHasData } from '../SmartLoader'; // Mock setTimeout and clearTimeout for testing jest.useFakeTimers(); describe('SmartLoader', () => { const LoadingComponent = () =>
Loading...
; const ContentComponent = () => (
{/* eslint-disable-line i18next/no-literal-string */}Content loaded
); beforeEach(() => { jest.clearAllTimers(); }); afterEach(() => { jest.useRealTimers(); jest.useFakeTimers(); }); describe('Basic functionality', () => { it('shows content immediately when not loading', () => { render( }> , ); expect(screen.getByTestId('content')).toBeInTheDocument(); expect(screen.queryByTestId('loading')).not.toBeInTheDocument(); }); it('shows content immediately when loading but has existing data', () => { render( }> , ); expect(screen.getByTestId('content')).toBeInTheDocument(); expect(screen.queryByTestId('loading')).not.toBeInTheDocument(); }); it('shows content initially, then loading after delay when loading with no data', async () => { render( } > , ); // Initially shows content expect(screen.getByTestId('content')).toBeInTheDocument(); expect(screen.queryByTestId('loading')).not.toBeInTheDocument(); // After delay, shows loading act(() => { jest.advanceTimersByTime(150); }); await waitFor(() => { expect(screen.getByTestId('loading')).toBeInTheDocument(); expect(screen.queryByTestId('content')).not.toBeInTheDocument(); }); }); it('prevents loading flash for quick responses', async () => { const { rerender } = render( } > , ); // Initially shows content expect(screen.getByTestId('content')).toBeInTheDocument(); // Advance time but not past delay act(() => { jest.advanceTimersByTime(100); }); // Loading finishes before delay rerender( } > , ); // Should still show content, never showed loading expect(screen.getByTestId('content')).toBeInTheDocument(); expect(screen.queryByTestId('loading')).not.toBeInTheDocument(); // Advance past original delay to ensure loading doesn't appear act(() => { jest.advanceTimersByTime(100); }); expect(screen.getByTestId('content')).toBeInTheDocument(); expect(screen.queryByTestId('loading')).not.toBeInTheDocument(); }); }); describe('Delay behavior', () => { it('respects custom delay times', async () => { render( } > , ); // Should show content initially expect(screen.getByTestId('content')).toBeInTheDocument(); // Should not show loading before delay act(() => { jest.advanceTimersByTime(250); }); expect(screen.getByTestId('content')).toBeInTheDocument(); expect(screen.queryByTestId('loading')).not.toBeInTheDocument(); // Should show loading after delay act(() => { jest.advanceTimersByTime(60); }); await waitFor(() => { expect(screen.getByTestId('loading')).toBeInTheDocument(); }); }); it('uses default delay when not specified', async () => { render( }> , ); // Should show content initially expect(screen.getByTestId('content')).toBeInTheDocument(); // Should show loading after default delay (150ms) act(() => { jest.advanceTimersByTime(150); }); await waitFor(() => { expect(screen.getByTestId('loading')).toBeInTheDocument(); }); }); }); describe('State transitions', () => { it('immediately hides loading when loading completes', async () => { const { rerender } = render( } > , ); // Advance past delay to show loading act(() => { jest.advanceTimersByTime(100); }); await waitFor(() => { expect(screen.getByTestId('loading')).toBeInTheDocument(); }); // Loading completes rerender( } > , ); // Should immediately show content expect(screen.getByTestId('content')).toBeInTheDocument(); expect(screen.queryByTestId('loading')).not.toBeInTheDocument(); }); it('handles rapid loading state changes correctly', async () => { const { rerender } = render( } > , ); // Rapid state changes rerender( } > , ); rerender( } > , ); // Should show content throughout rapid changes expect(screen.getByTestId('content')).toBeInTheDocument(); expect(screen.queryByTestId('loading')).not.toBeInTheDocument(); }); }); describe('CSS classes', () => { it('applies custom className', () => { const { container } = render( } className="custom-class" > , ); const wrapper = container.firstChild as HTMLElement; expect(wrapper).toHaveClass('custom-class'); }); it('applies className to both loading and content states', async () => { const { container } = render( } className="custom-class" > , ); // Content state expect(container.firstChild).toHaveClass('custom-class'); // Loading state act(() => { jest.advanceTimersByTime(50); }); await waitFor(() => { expect(container.firstChild).toHaveClass('custom-class'); }); }); }); }); describe('useHasData', () => { const TestComponent: React.FC<{ data: any }> = ({ data }) => { const hasData = useHasData(data); return
{hasData ? 'has-data' : 'no-data'}
; }; it('returns false for null data', () => { render(); expect(screen.getByTestId('result')).toHaveTextContent('no-data'); }); it('returns false for undefined data', () => { render(); expect(screen.getByTestId('result')).toHaveTextContent('no-data'); }); it('detects empty agents array as no data', () => { render(); expect(screen.getByTestId('result')).toHaveTextContent('no-data'); }); it('detects non-empty agents array as has data', () => { render(); expect(screen.getByTestId('result')).toHaveTextContent('has-data'); }); it('detects invalid agents property as no data', () => { render(); expect(screen.getByTestId('result')).toHaveTextContent('no-data'); }); it('detects empty array as no data', () => { render(); expect(screen.getByTestId('result')).toHaveTextContent('no-data'); }); it('detects non-empty array as has data', () => { render(); expect(screen.getByTestId('result')).toHaveTextContent('has-data'); }); it('detects agent with id as has data', () => { render(); expect(screen.getByTestId('result')).toHaveTextContent('has-data'); }); it('detects agent with name only as has data', () => { render(); expect(screen.getByTestId('result')).toHaveTextContent('has-data'); }); it('detects object without id or name as no data', () => { render(); expect(screen.getByTestId('result')).toHaveTextContent('no-data'); }); it('handles string data as no data', () => { render(); expect(screen.getByTestId('result')).toHaveTextContent('no-data'); }); it('handles number data as no data', () => { render(); expect(screen.getByTestId('result')).toHaveTextContent('no-data'); }); it('handles boolean data as no data', () => { render(); expect(screen.getByTestId('result')).toHaveTextContent('no-data'); }); });