| | import { render, screen, waitFor, fireEvent } from '@testing-library/react'; |
| | import '@testing-library/jest-dom'; |
| | import Topics from '../../components/Topics'; |
| |
|
| | describe('Topics Component', () => { |
| | const mockOnTopicChange = jest.fn(); |
| | const mockFetch = jest.fn(); |
| | global.fetch = mockFetch; |
| |
|
| | beforeEach(() => { |
| | jest.clearAllMocks(); |
| | mockFetch.mockClear(); |
| | }); |
| |
|
| | it('shows loading state initially', () => { |
| | render(<Topics onTopicChange={mockOnTopicChange} />); |
| | |
| | expect(screen.getByLabelText('Topic')).toBeDisabled(); |
| | expect(screen.getByText('Loading topics...')).toBeInTheDocument(); |
| | }); |
| |
|
| | it('displays topics after successful fetch', async () => { |
| | const mockTopics = { sources: ['Topic 1', 'Topic 2', 'Topic 3'] }; |
| | mockFetch.mockImplementationOnce(() => |
| | Promise.resolve({ |
| | ok: true, |
| | json: () => Promise.resolve(mockTopics), |
| | }) |
| | ); |
| |
|
| | render(<Topics onTopicChange={mockOnTopicChange} />); |
| |
|
| | |
| | await waitFor(() => { |
| | expect(screen.queryByText('Loading topics...')).not.toBeInTheDocument(); |
| | }); |
| |
|
| | |
| | fireEvent.mouseDown(screen.getByLabelText('Topic')); |
| |
|
| | |
| | mockTopics.sources.forEach(topic => { |
| | expect(screen.getByText(topic)).toBeInTheDocument(); |
| | }); |
| | }); |
| |
|
| | it('handles API error correctly', async () => { |
| | mockFetch.mockImplementationOnce(() => |
| | Promise.resolve({ |
| | ok: false, |
| | status: 500, |
| | }) |
| | ); |
| |
|
| | render(<Topics onTopicChange={mockOnTopicChange} />); |
| |
|
| | await waitFor(() => { |
| | expect(screen.queryByText('Loading topics...')).not.toBeInTheDocument(); |
| | }); |
| |
|
| | const select = screen.getByLabelText('Topic'); |
| | expect(select).toHaveAttribute('aria-invalid', 'true'); |
| | }); |
| |
|
| | it('calls onTopicChange when topic is selected', async () => { |
| | const mockTopics = { sources: ['Topic 1', 'Topic 2'] }; |
| | mockFetch.mockImplementationOnce(() => |
| | Promise.resolve({ |
| | ok: true, |
| | json: () => Promise.resolve(mockTopics), |
| | }) |
| | ); |
| |
|
| | render(<Topics onTopicChange={mockOnTopicChange} />); |
| |
|
| | |
| | await waitFor(() => { |
| | expect(screen.queryByText('Loading topics...')).not.toBeInTheDocument(); |
| | }); |
| |
|
| | |
| | fireEvent.mouseDown(screen.getByLabelText('Topic')); |
| | |
| | |
| | fireEvent.click(screen.getByText('Topic 1')); |
| |
|
| | expect(mockOnTopicChange).toHaveBeenCalledWith('Topic 1'); |
| | }); |
| |
|
| | it('handles malformed API response correctly', async () => { |
| | const malformedResponse = { wrongKey: [] }; |
| | mockFetch.mockImplementationOnce(() => |
| | Promise.resolve({ |
| | ok: true, |
| | json: () => Promise.resolve(malformedResponse), |
| | }) |
| | ); |
| |
|
| | render(<Topics onTopicChange={mockOnTopicChange} />); |
| |
|
| | await waitFor(() => { |
| | expect(screen.queryByText('Loading topics...')).not.toBeInTheDocument(); |
| | }); |
| |
|
| | const select = screen.getByLabelText('Topic'); |
| | expect(select).toHaveAttribute('aria-invalid', 'true'); |
| | }); |
| |
|
| | it('handles network errors correctly', async () => { |
| | mockFetch.mockImplementationOnce(() => |
| | Promise.reject(new Error('Network error')) |
| | ); |
| |
|
| | render(<Topics onTopicChange={mockOnTopicChange} />); |
| |
|
| | await waitFor(() => { |
| | expect(screen.queryByText('Loading topics...')).not.toBeInTheDocument(); |
| | }); |
| |
|
| | const select = screen.getByLabelText('Topic'); |
| | expect(select).toHaveAttribute('aria-invalid', 'true'); |
| | }); |
| | }); |