import { describe, it, expect, vi, beforeEach } from 'vitest' import { render, screen, fireEvent, waitFor } from '@testing-library/react' import LoadMoreButton from '@/components/article/LoadMoreButton' process.env.NEXT_PUBLIC_DIRECTUS_URL = 'https://cms.achmad.dev' process.env.NEXT_PUBLIC_DIRECTUS_TOKEN = 'test-token' const mockFetch = vi.fn() global.fetch = mockFetch beforeEach(() => { vi.resetAllMocks() mockFetch.mockResolvedValue({ json: () => Promise.resolve({ data: [] }), }) }) describe('LoadMoreButton', () => { it('renders the Load more button by default', () => { render() expect(screen.getByText('Load more')).toBeInTheDocument() }) it('hides the button when hasMore is false', () => { render() expect(screen.queryByText('Load more')).not.toBeInTheDocument() }) it('shows loading state while fetching', async () => { mockFetch.mockImplementation( () => new Promise((resolve) => setTimeout( () => resolve({ json: () => Promise.resolve({ data: [] }), }), 100, ), ), ) render() fireEvent.click(screen.getByText('Load more')) expect(await screen.findByText('Loading…')).toBeInTheDocument() }) it('fetches articles with correct parameters', async () => { render() fireEvent.click(screen.getByText('Load more')) await waitFor(() => { expect(mockFetch).toHaveBeenCalledTimes(1) }) const url = mockFetch.mock.calls[0][0] as string expect(url).toContain('/items/articles') expect(url).toContain('games') expect(url).toContain('offset=12') expect(url).toContain('limit=12') expect(url).toContain('access_token=test-token') }) it('appends fetched articles to the display', async () => { mockFetch.mockResolvedValue({ json: () => Promise.resolve({ data: [ { id: '101', title: 'Loaded Article', slug: 'loaded-article', excerpt: null, featured_image: null, published_at: null, date_created: '2026-05-31T00:00:00Z', is_featured: false, category: { id: '1', name: 'Games', slug: 'games' }, }, ], }), }) render() fireEvent.click(screen.getByText('Load more')) await waitFor(() => { expect(screen.getByText('Loaded Article')).toBeInTheDocument() }) }) it('hides button after last page when fewer results than limit are returned', async () => { mockFetch.mockResolvedValue({ json: () => Promise.resolve({ data: [] }), }) render() expect(screen.getByText('Load more')).toBeInTheDocument() fireEvent.click(screen.getByText('Load more')) await waitFor(() => { expect(screen.queryByText('Load more')).not.toBeInTheDocument() }) }) it('keeps button visible when exactly limit articles are returned', async () => { const articles = Array.from({ length: 12 }, (_, i) => ({ id: String(200 + i), title: `Article ${i}`, slug: `article-${i}`, excerpt: null, featured_image: null, published_at: null, date_created: '2026-05-31T00:00:00Z', is_featured: false, category: { id: '1', name: 'Games', slug: 'games' }, })) mockFetch.mockResolvedValue({ json: () => Promise.resolve({ data: articles }), }) render() fireEvent.click(screen.getByText('Load more')) await waitFor(() => { expect(screen.getByText('Load more')).toBeInTheDocument() }) }) it('calls the correct Directus API URL', async () => { render() fireEvent.click(screen.getByText('Load more')) await waitFor(() => { expect(mockFetch).toHaveBeenCalledTimes(1) }) const url = mockFetch.mock.calls[0][0] as string expect(url).toContain('vtubers') expect(url).toContain('offset=14') }) })