129 lines
No EOL
4.7 KiB
TypeScript
129 lines
No EOL
4.7 KiB
TypeScript
import { render, screen, waitFor, act } from '@testing-library/react'
|
|
import userEvent from '@testing-library/user-event'
|
|
import { BrowserRouter } from 'react-router-dom'
|
|
import { describe, it, expect, beforeEach } from 'vitest'
|
|
import App from '../App'
|
|
import { mockFetchResponse, resetFetchMock, getFetchCalls } from './api-mock'
|
|
|
|
const renderApp = () => {
|
|
return render(
|
|
<BrowserRouter>
|
|
<App />
|
|
</BrowserRouter>
|
|
)
|
|
}
|
|
|
|
const mockSkills = [
|
|
{ id: '1', name: 'Go', category: 'Backend', proficiency: 5, icon: 'go', order: 1, created_at: '', updated_at: '' },
|
|
{ id: '2', name: 'React', category: 'Frontend', proficiency: 4, icon: 'react', order: 2, created_at: '', updated_at: '' },
|
|
]
|
|
|
|
const mockProjects = [
|
|
{ id: '1', title: 'Test Project', description: 'Desc', short_desc: 'Short', tech_stack: ['Go', 'React'], image_url: '', demo_url: '', repo_url: '', category: 'Full Stack', featured: true, order: 1, created_at: '', updated_at: '' },
|
|
]
|
|
|
|
const mockExperience = [
|
|
{ id: '1', company: 'Test Co', role: 'Dev', location: 'Remote', start_date: '2020-01-01', end_date: null, current: true, description: 'Test', tech_stack: ['Go'], order: 1, created_at: '', updated_at: '' },
|
|
]
|
|
|
|
const mockServices = [
|
|
{ id: '1', title: 'Web Dev', description: 'Desc', icon: 'code', features: ['React', 'Go'], order: 1, active: true, created_at: '', updated_at: '' },
|
|
]
|
|
|
|
describe('App Integration Tests', () => {
|
|
beforeEach(() => {
|
|
resetFetchMock()
|
|
})
|
|
|
|
it('renders all sections without errors', async () => {
|
|
mockFetchResponse(mockSkills)
|
|
mockFetchResponse(mockProjects)
|
|
mockFetchResponse(mockProjects)
|
|
mockFetchResponse(mockExperience)
|
|
mockFetchResponse(mockServices)
|
|
|
|
renderApp()
|
|
|
|
await waitFor(() => {
|
|
expect(screen.getByText('Services & Expertise')).toBeInTheDocument()
|
|
expect(screen.getByText('Selected Projects')).toBeInTheDocument()
|
|
expect(screen.getByText('Technical Skills')).toBeInTheDocument()
|
|
expect(screen.getByText('Experience')).toBeInTheDocument()
|
|
expect(screen.getByText("Let's Build Something Great")).toBeInTheDocument()
|
|
}, { timeout: 15000 })
|
|
})
|
|
|
|
it('does not make excessive API calls on initial render', async () => {
|
|
mockFetchResponse([])
|
|
mockFetchResponse([])
|
|
mockFetchResponse([])
|
|
mockFetchResponse([])
|
|
mockFetchResponse([])
|
|
|
|
renderApp()
|
|
|
|
await waitFor(() => {
|
|
expect(screen.getByText('Services & Expertise')).toBeInTheDocument()
|
|
}, { timeout: 15000 })
|
|
|
|
const calls = getFetchCalls()
|
|
expect(calls.length).toBeLessThanOrEqual(6)
|
|
})
|
|
|
|
it('handles API errors gracefully', async () => {
|
|
;(globalThis.fetch as unknown as { mockResolvedValueOnce: (value: unknown) => void }).mockResolvedValueOnce({
|
|
ok: false,
|
|
json: () => Promise.resolve({ success: false, error: { code: 'INTERNAL_ERROR', message: 'Server error' } }),
|
|
})
|
|
;(globalThis.fetch as unknown as { mockResolvedValueOnce: (value: unknown) => void }).mockResolvedValueOnce({
|
|
ok: true,
|
|
json: () => Promise.resolve({ success: true, data: [] }),
|
|
})
|
|
mockFetchResponse([])
|
|
mockFetchResponse([])
|
|
mockFetchResponse([])
|
|
|
|
renderApp()
|
|
|
|
await waitFor(() => {
|
|
expect(screen.getByText('Services & Expertise')).toBeInTheDocument()
|
|
}, { timeout: 15000 })
|
|
|
|
expect(screen.getByText('Selected Projects')).toBeInTheDocument()
|
|
})
|
|
|
|
it('contact form submission works', async () => {
|
|
mockFetchResponse([])
|
|
mockFetchResponse([])
|
|
mockFetchResponse([])
|
|
mockFetchResponse([])
|
|
mockFetchResponse([])
|
|
mockFetchResponse({ id: '1', name: 'Test', email: 'test@test.com', subject: 'Test', message: 'Test message', status: 'new', created_at: '', updated_at: '' })
|
|
|
|
renderApp()
|
|
|
|
await waitFor(() => {
|
|
expect(screen.getByText("Let's Build Something Great")).toBeInTheDocument()
|
|
}, { timeout: 15000 })
|
|
|
|
// Use getByPlaceholderText for more specific targeting
|
|
const nameInput = screen.getByPlaceholderText('Your name')
|
|
const emailInput = screen.getByPlaceholderText('your@email.com')
|
|
const subjectInput = screen.getByPlaceholderText("What's this about?")
|
|
const messageInput = screen.getByPlaceholderText("Tell us about your project, timeline, budget, or just say hello...")
|
|
const submitButton = screen.getByRole('button', { name: /send message/i })
|
|
|
|
await userEvent.type(nameInput, 'John Doe')
|
|
await userEvent.type(emailInput, 'john@example.com')
|
|
await userEvent.type(subjectInput, 'Project Inquiry')
|
|
await userEvent.type(messageInput, 'Hello, I would like to discuss a project.')
|
|
|
|
await act(async () => {
|
|
await userEvent.click(submitButton)
|
|
})
|
|
|
|
await waitFor(() => {
|
|
expect(screen.getByText('Message Sent!')).toBeInTheDocument()
|
|
}, { timeout: 15000 })
|
|
})
|
|
}) |