Implement testing framework with Vitest, add unit tests for API routes and utility functions, and configure CI workflow for automated testing. Update package dependencies for testing libraries and add test scripts to package.json.
Some checks are pending
CI / test (push) Waiting to run
Some checks are pending
CI / test (push) Waiting to run
This commit is contained in:
281
app/page.test.tsx
Normal file
281
app/page.test.tsx
Normal file
@@ -0,0 +1,281 @@
|
||||
import { describe, it, expect, vi, beforeEach, afterEach } from 'vitest'
|
||||
import { render, screen, fireEvent, waitFor, cleanup } from '@testing-library/react'
|
||||
import Home from './page'
|
||||
|
||||
global.fetch = vi.fn()
|
||||
|
||||
const mockFetch = vi.mocked(global.fetch)
|
||||
|
||||
describe('Home Page', () => {
|
||||
beforeEach(() => {
|
||||
vi.clearAllMocks()
|
||||
})
|
||||
|
||||
afterEach(() => {
|
||||
cleanup()
|
||||
})
|
||||
|
||||
it('renders the main heading', () => {
|
||||
render(<Home />)
|
||||
expect(screen.getByText('Downlink')).toBeInTheDocument()
|
||||
})
|
||||
|
||||
it('renders the URL input', () => {
|
||||
render(<Home />)
|
||||
const inputs = screen.getAllByPlaceholderText('Paste YouTube URL here...')
|
||||
expect(inputs.length).toBeGreaterThanOrEqual(1)
|
||||
})
|
||||
|
||||
it('renders the Fetch button', () => {
|
||||
render(<Home />)
|
||||
const buttons = screen.getAllByRole('button', { name: /fetch/i })
|
||||
expect(buttons.length).toBeGreaterThanOrEqual(1)
|
||||
})
|
||||
|
||||
it('shows error for empty URL on fetch', async () => {
|
||||
render(<Home />)
|
||||
|
||||
const fetchButtons = screen.getAllByRole('button', { name: /fetch/i })
|
||||
fireEvent.click(fetchButtons[0])
|
||||
|
||||
await waitFor(() => {
|
||||
expect(screen.getByText('Please enter a YouTube URL')).toBeInTheDocument()
|
||||
})
|
||||
})
|
||||
|
||||
it('shows error for invalid YouTube URL', async () => {
|
||||
render(<Home />)
|
||||
|
||||
const inputs = screen.getAllByPlaceholderText('Paste YouTube URL here...')
|
||||
fireEvent.change(inputs[0], { target: { value: 'https://vimeo.com/123' } })
|
||||
|
||||
const fetchButtons = screen.getAllByRole('button', { name: /fetch/i })
|
||||
fireEvent.click(fetchButtons[0])
|
||||
|
||||
await waitFor(() => {
|
||||
expect(screen.getByText('Please enter a valid YouTube URL')).toBeInTheDocument()
|
||||
})
|
||||
})
|
||||
|
||||
it('fetches video info for valid URL', async () => {
|
||||
const mockVideoInfo = {
|
||||
title: 'Test Video',
|
||||
duration: 180,
|
||||
thumbnail: 'https://example.com/thumb.jpg',
|
||||
}
|
||||
|
||||
mockFetch.mockResolvedValueOnce({
|
||||
json: () => Promise.resolve({ success: true, videoInfo: mockVideoInfo }),
|
||||
} as Response)
|
||||
|
||||
render(<Home />)
|
||||
|
||||
const inputs = screen.getAllByPlaceholderText('Paste YouTube URL here...')
|
||||
fireEvent.change(inputs[0], { target: { value: 'https://youtube.com/watch?v=dQw4w9WgXcQ' } })
|
||||
|
||||
const fetchButtons = screen.getAllByRole('button', { name: /fetch/i })
|
||||
fireEvent.click(fetchButtons[0])
|
||||
|
||||
await waitFor(() => {
|
||||
expect(screen.getByText('Test Video')).toBeInTheDocument()
|
||||
})
|
||||
|
||||
expect(mockFetch).toHaveBeenCalledWith('/api/info', {
|
||||
method: 'POST',
|
||||
headers: { 'Content-Type': 'application/json' },
|
||||
body: JSON.stringify({ url: 'https://youtube.com/watch?v=dQw4w9WgXcQ' }),
|
||||
})
|
||||
})
|
||||
|
||||
it('displays video duration after fetch', async () => {
|
||||
const mockVideoInfo = {
|
||||
title: 'Test Video',
|
||||
duration: 125,
|
||||
thumbnail: 'https://example.com/thumb.jpg',
|
||||
}
|
||||
|
||||
mockFetch.mockResolvedValueOnce({
|
||||
json: () => Promise.resolve({ success: true, videoInfo: mockVideoInfo }),
|
||||
} as Response)
|
||||
|
||||
render(<Home />)
|
||||
|
||||
const inputs = screen.getAllByPlaceholderText('Paste YouTube URL here...')
|
||||
fireEvent.change(inputs[0], { target: { value: 'https://youtube.com/watch?v=dQw4w9WgXcQ' } })
|
||||
|
||||
const fetchButtons = screen.getAllByRole('button', { name: /fetch/i })
|
||||
fireEvent.click(fetchButtons[0])
|
||||
|
||||
await waitFor(() => {
|
||||
expect(screen.getByText('2:05')).toBeInTheDocument()
|
||||
})
|
||||
})
|
||||
|
||||
it('shows download options after fetching video info', async () => {
|
||||
const mockVideoInfo = {
|
||||
title: 'Test Video',
|
||||
duration: 180,
|
||||
thumbnail: 'https://example.com/thumb.jpg',
|
||||
}
|
||||
|
||||
mockFetch.mockResolvedValueOnce({
|
||||
json: () => Promise.resolve({ success: true, videoInfo: mockVideoInfo }),
|
||||
} as Response)
|
||||
|
||||
render(<Home />)
|
||||
|
||||
const inputs = screen.getAllByPlaceholderText('Paste YouTube URL here...')
|
||||
fireEvent.change(inputs[0], { target: { value: 'https://youtube.com/watch?v=dQw4w9WgXcQ' } })
|
||||
|
||||
const fetchButtons = screen.getAllByRole('button', { name: /fetch/i })
|
||||
fireEvent.click(fetchButtons[0])
|
||||
|
||||
await waitFor(() => {
|
||||
expect(screen.getByText('Download Options')).toBeInTheDocument()
|
||||
})
|
||||
})
|
||||
|
||||
it('handles API error response', async () => {
|
||||
mockFetch.mockResolvedValueOnce({
|
||||
json: () => Promise.resolve({ success: false, error: 'Video not found' }),
|
||||
} as Response)
|
||||
|
||||
render(<Home />)
|
||||
|
||||
const inputs = screen.getAllByPlaceholderText('Paste YouTube URL here...')
|
||||
fireEvent.change(inputs[0], { target: { value: 'https://youtube.com/watch?v=dQw4w9WgXcQ' } })
|
||||
|
||||
const fetchButtons = screen.getAllByRole('button', { name: /fetch/i })
|
||||
fireEvent.click(fetchButtons[0])
|
||||
|
||||
await waitFor(() => {
|
||||
expect(screen.getByText('Video not found')).toBeInTheDocument()
|
||||
})
|
||||
})
|
||||
|
||||
it('handles fetch network error', async () => {
|
||||
mockFetch.mockRejectedValueOnce(new Error('Network error'))
|
||||
|
||||
render(<Home />)
|
||||
|
||||
const inputs = screen.getAllByPlaceholderText('Paste YouTube URL here...')
|
||||
fireEvent.change(inputs[0], { target: { value: 'https://youtube.com/watch?v=dQw4w9WgXcQ' } })
|
||||
|
||||
const fetchButtons = screen.getAllByRole('button', { name: /fetch/i })
|
||||
fireEvent.click(fetchButtons[0])
|
||||
|
||||
await waitFor(() => {
|
||||
expect(screen.getByText('Network error')).toBeInTheDocument()
|
||||
})
|
||||
})
|
||||
|
||||
it('allows switching between video and audio format types', async () => {
|
||||
const mockVideoInfo = {
|
||||
title: 'Test Video',
|
||||
duration: 180,
|
||||
thumbnail: 'https://example.com/thumb.jpg',
|
||||
}
|
||||
|
||||
mockFetch.mockResolvedValueOnce({
|
||||
json: () => Promise.resolve({ success: true, videoInfo: mockVideoInfo }),
|
||||
} as Response)
|
||||
|
||||
render(<Home />)
|
||||
|
||||
const inputs = screen.getAllByPlaceholderText('Paste YouTube URL here...')
|
||||
fireEvent.change(inputs[0], { target: { value: 'https://youtube.com/watch?v=dQw4w9WgXcQ' } })
|
||||
|
||||
const fetchButtons = screen.getAllByRole('button', { name: /fetch/i })
|
||||
fireEvent.click(fetchButtons[0])
|
||||
|
||||
await waitFor(() => {
|
||||
expect(screen.getByText('Download Options')).toBeInTheDocument()
|
||||
})
|
||||
|
||||
const audioButton = screen.getByRole('button', { name: /audio/i })
|
||||
fireEvent.click(audioButton)
|
||||
|
||||
expect(screen.getByText('Audio Format')).toBeInTheDocument()
|
||||
})
|
||||
|
||||
it('triggers download on button click', async () => {
|
||||
const mockVideoInfo = {
|
||||
title: 'Test Video',
|
||||
duration: 180,
|
||||
thumbnail: 'https://example.com/thumb.jpg',
|
||||
}
|
||||
|
||||
mockFetch
|
||||
.mockResolvedValueOnce({
|
||||
json: () => Promise.resolve({ success: true, videoInfo: mockVideoInfo }),
|
||||
} as Response)
|
||||
.mockResolvedValueOnce({
|
||||
json: () => Promise.resolve({
|
||||
success: true,
|
||||
downloadUrl: '/downloads/test.mp4',
|
||||
filename: 'test.mp4',
|
||||
}),
|
||||
} as Response)
|
||||
|
||||
render(<Home />)
|
||||
|
||||
const inputs = screen.getAllByPlaceholderText('Paste YouTube URL here...')
|
||||
fireEvent.change(inputs[0], { target: { value: 'https://youtube.com/watch?v=dQw4w9WgXcQ' } })
|
||||
|
||||
const fetchButtons = screen.getAllByRole('button', { name: /fetch/i })
|
||||
fireEvent.click(fetchButtons[0])
|
||||
|
||||
await waitFor(() => {
|
||||
expect(screen.getByText('Download Options')).toBeInTheDocument()
|
||||
})
|
||||
|
||||
const downloadButton = screen.getByRole('button', { name: /download mp4/i })
|
||||
fireEvent.click(downloadButton)
|
||||
|
||||
await waitFor(() => {
|
||||
expect(mockFetch).toHaveBeenCalledTimes(2)
|
||||
})
|
||||
|
||||
expect(mockFetch).toHaveBeenLastCalledWith('/api/download', expect.objectContaining({
|
||||
method: 'POST',
|
||||
}))
|
||||
})
|
||||
|
||||
it('shows success message after download completes', async () => {
|
||||
const mockVideoInfo = {
|
||||
title: 'Test Video',
|
||||
duration: 180,
|
||||
thumbnail: 'https://example.com/thumb.jpg',
|
||||
}
|
||||
|
||||
mockFetch
|
||||
.mockResolvedValueOnce({
|
||||
json: () => Promise.resolve({ success: true, videoInfo: mockVideoInfo }),
|
||||
} as Response)
|
||||
.mockResolvedValueOnce({
|
||||
json: () => Promise.resolve({
|
||||
success: true,
|
||||
downloadUrl: '/downloads/test.mp4',
|
||||
filename: 'test.mp4',
|
||||
}),
|
||||
} as Response)
|
||||
|
||||
render(<Home />)
|
||||
|
||||
const inputs = screen.getAllByPlaceholderText('Paste YouTube URL here...')
|
||||
fireEvent.change(inputs[0], { target: { value: 'https://youtube.com/watch?v=dQw4w9WgXcQ' } })
|
||||
|
||||
const fetchButtons = screen.getAllByRole('button', { name: /fetch/i })
|
||||
fireEvent.click(fetchButtons[0])
|
||||
|
||||
await waitFor(() => {
|
||||
expect(screen.getByText('Download Options')).toBeInTheDocument()
|
||||
})
|
||||
|
||||
fireEvent.click(screen.getByRole('button', { name: /download mp4/i }))
|
||||
|
||||
await waitFor(() => {
|
||||
expect(screen.getByText('Ready to Download')).toBeInTheDocument()
|
||||
})
|
||||
})
|
||||
})
|
||||
Reference in New Issue
Block a user