test: boost coverage - add 40 tests for Home, Tasks, Habits, Savings
Some checks failed
CI / lint-test (push) Failing after 1s
Some checks failed
CI / lint-test (push) Failing after 1s
This commit is contained in:
@@ -1,5 +1,5 @@
|
||||
import { describe, it, expect, vi, beforeEach } from 'vitest'
|
||||
import { render, screen, waitFor } from '@testing-library/react'
|
||||
import { render, screen, waitFor, fireEvent, act } from '@testing-library/react'
|
||||
import { QueryClient, QueryClientProvider } from '@tanstack/react-query'
|
||||
import { MemoryRouter } from 'react-router-dom'
|
||||
import Home from '../pages/Home'
|
||||
@@ -17,6 +17,7 @@ vi.mock('../api/habits', () => ({
|
||||
getStats: vi.fn(),
|
||||
getHabitStats: vi.fn(),
|
||||
getFreezes: vi.fn(),
|
||||
deleteLog: vi.fn(),
|
||||
},
|
||||
}))
|
||||
|
||||
@@ -33,17 +34,34 @@ vi.mock('../components/Navigation', () => ({
|
||||
}))
|
||||
|
||||
vi.mock('../components/CreateTaskModal', () => ({
|
||||
default: ({ open }) => open ? <div data-testid="create-task-modal" /> : null,
|
||||
default: ({ open, onClose }) => open ? <div data-testid="create-task-modal"><button onClick={onClose}>Close</button></div> : null,
|
||||
}))
|
||||
|
||||
vi.mock('../components/LogHabitModal', () => ({
|
||||
default: ({ open }) => open ? <div data-testid="log-habit-modal" /> : null,
|
||||
default: ({ open, onClose, habit, onLogDate }) => open ? (
|
||||
<div data-testid="log-habit-modal">
|
||||
<span>{habit?.name}</span>
|
||||
<button onClick={onClose}>Close</button>
|
||||
<button onClick={() => onLogDate && onLogDate(habit?.id, '2026-03-01')}>Log Date</button>
|
||||
</div>
|
||||
) : null,
|
||||
}))
|
||||
|
||||
import { habitsApi } from '../api/habits'
|
||||
import { tasksApi } from '../api/tasks'
|
||||
|
||||
const mockUser = { id: 1, username: 'testuser', email: 'test@test.com' }
|
||||
const mockLogout = vi.fn()
|
||||
|
||||
const mockHabits = [
|
||||
{ id: 1, name: 'Exercise', frequency: 'daily', color: '#6366f1', icon: '💪', is_archived: false, created_at: '2026-01-01T00:00:00Z' },
|
||||
{ id: 2, name: 'Read', frequency: 'weekly', target_days: [1,2,3,4,5,6,7], color: '#22c55e', icon: '📚', is_archived: false, created_at: '2026-01-01T00:00:00Z' },
|
||||
]
|
||||
|
||||
const mockTasks = [
|
||||
{ id: 1, title: 'Buy groceries', completed: false, priority: 1, due_date: null, icon: '📋', color: '#6366f1', is_recurring: false, recurrence_type: null },
|
||||
{ id: 2, title: 'Completed task', completed: true, priority: 0, due_date: null, icon: '✅', color: '#22c55e', is_recurring: false, recurrence_type: null },
|
||||
]
|
||||
|
||||
const renderHome = () => {
|
||||
const qc = new QueryClient({ defaultOptions: { queries: { retry: false } } })
|
||||
@@ -59,12 +77,12 @@ const renderHome = () => {
|
||||
describe('Home page', () => {
|
||||
beforeEach(() => {
|
||||
vi.clearAllMocks()
|
||||
// useAuthStore is used as: const { user } = useAuthStore() — not with selector
|
||||
useAuthStore.mockReturnValue({ user: mockUser, logout: vi.fn() })
|
||||
useAuthStore.mockReturnValue({ user: mockUser, logout: mockLogout })
|
||||
habitsApi.list.mockResolvedValue([])
|
||||
habitsApi.getLogs.mockResolvedValue([])
|
||||
habitsApi.getStats.mockResolvedValue({ total_habits: 0, completion_rate: 0 })
|
||||
habitsApi.getStats.mockResolvedValue({ total_habits: 0, completion_rate: 0, today_completed: 3, active_habits: 5 })
|
||||
habitsApi.getFreezes.mockResolvedValue([])
|
||||
habitsApi.deleteLog.mockResolvedValue({})
|
||||
tasksApi.today.mockResolvedValue([])
|
||||
})
|
||||
|
||||
@@ -86,4 +104,294 @@ describe('Home page', () => {
|
||||
expect(screen.getByText(/testuser/i)).toBeInTheDocument()
|
||||
})
|
||||
})
|
||||
|
||||
it('calls logout when logout button clicked', async () => {
|
||||
renderHome()
|
||||
await waitFor(() => {
|
||||
expect(screen.getByText(/testuser/i)).toBeInTheDocument()
|
||||
})
|
||||
const logoutBtn = document.querySelector('[title="Выйти"]')
|
||||
if (logoutBtn) {
|
||||
fireEvent.click(logoutBtn)
|
||||
expect(mockLogout).toHaveBeenCalled()
|
||||
}
|
||||
})
|
||||
|
||||
it('shows progress section', async () => {
|
||||
renderHome()
|
||||
await waitFor(() => {
|
||||
expect(screen.getByText('Прогресс на сегодня')).toBeInTheDocument()
|
||||
})
|
||||
})
|
||||
|
||||
it('shows stats when available', async () => {
|
||||
renderHome()
|
||||
await waitFor(() => {
|
||||
expect(screen.getByText('Выполнено')).toBeInTheDocument()
|
||||
expect(screen.getByText('Активных')).toBeInTheDocument()
|
||||
})
|
||||
})
|
||||
|
||||
it('shows empty tasks state', async () => {
|
||||
renderHome()
|
||||
await waitFor(() => {
|
||||
expect(screen.getByText('Нет задач на сегодня')).toBeInTheDocument()
|
||||
})
|
||||
})
|
||||
|
||||
it('renders tasks when present', async () => {
|
||||
tasksApi.today.mockResolvedValue(mockTasks)
|
||||
renderHome()
|
||||
await waitFor(() => {
|
||||
expect(screen.getByText('Buy groceries')).toBeInTheDocument()
|
||||
})
|
||||
})
|
||||
|
||||
it('renders only active tasks in main list', async () => {
|
||||
tasksApi.today.mockResolvedValue(mockTasks)
|
||||
renderHome()
|
||||
await waitFor(() => {
|
||||
expect(screen.getByText('Buy groceries')).toBeInTheDocument()
|
||||
})
|
||||
})
|
||||
|
||||
it('shows free day message when no habits for today', async () => {
|
||||
habitsApi.list.mockResolvedValue([])
|
||||
renderHome()
|
||||
await waitFor(() => {
|
||||
expect(screen.getByText('Свободный день!')).toBeInTheDocument()
|
||||
})
|
||||
})
|
||||
|
||||
it('renders habits section', async () => {
|
||||
habitsApi.list.mockResolvedValue(mockHabits)
|
||||
habitsApi.getLogs.mockResolvedValue([])
|
||||
habitsApi.getFreezes.mockResolvedValue([])
|
||||
renderHome()
|
||||
await waitFor(() => {
|
||||
expect(screen.getByText('Привычки')).toBeInTheDocument()
|
||||
})
|
||||
})
|
||||
|
||||
it('opens create task modal when plus button clicked', async () => {
|
||||
tasksApi.today.mockResolvedValue(mockTasks)
|
||||
renderHome()
|
||||
await waitFor(() => {
|
||||
expect(screen.getByText('Задачи на сегодня')).toBeInTheDocument()
|
||||
})
|
||||
const plusBtns = document.querySelectorAll('button')
|
||||
const plusBtn = Array.from(plusBtns).find(b => b.querySelector('svg'))
|
||||
// Click the + button in tasks header
|
||||
const taskHeader = screen.getByText('Задачи на сегодня')
|
||||
const headerDiv = taskHeader.closest('div')
|
||||
const btnsInHeader = headerDiv?.querySelectorAll('button')
|
||||
if (btnsInHeader && btnsInHeader.length > 0) {
|
||||
fireEvent.click(btnsInHeader[0])
|
||||
await waitFor(() => {
|
||||
expect(screen.getByTestId('create-task-modal')).toBeInTheDocument()
|
||||
})
|
||||
}
|
||||
})
|
||||
|
||||
it('opens create task modal from empty state', async () => {
|
||||
renderHome()
|
||||
await waitFor(() => {
|
||||
expect(screen.getByText('+ Добавить задачу')).toBeInTheDocument()
|
||||
})
|
||||
fireEvent.click(screen.getByText('+ Добавить задачу'))
|
||||
expect(screen.getByTestId('create-task-modal')).toBeInTheDocument()
|
||||
})
|
||||
|
||||
it('closes create task modal', async () => {
|
||||
renderHome()
|
||||
await waitFor(() => {
|
||||
expect(screen.getByText('+ Добавить задачу')).toBeInTheDocument()
|
||||
})
|
||||
fireEvent.click(screen.getByText('+ Добавить задачу'))
|
||||
expect(screen.getByTestId('create-task-modal')).toBeInTheDocument()
|
||||
fireEvent.click(screen.getByText('Close'))
|
||||
expect(screen.queryByTestId('create-task-modal')).not.toBeInTheDocument()
|
||||
})
|
||||
|
||||
it('toggles task complete', async () => {
|
||||
tasksApi.complete.mockResolvedValue({ id: 1, completed: true })
|
||||
tasksApi.today.mockResolvedValue([mockTasks[0]])
|
||||
renderHome()
|
||||
await waitFor(() => {
|
||||
expect(screen.getByText('Buy groceries')).toBeInTheDocument()
|
||||
})
|
||||
const buttons = document.querySelectorAll('button')
|
||||
const completeBtn = Array.from(buttons).find(b => b.className && b.className.includes('rounded-xl') && b.querySelector('span'))
|
||||
if (completeBtn) {
|
||||
fireEvent.click(completeBtn)
|
||||
await waitFor(() => {
|
||||
expect(tasksApi.complete).toHaveBeenCalledWith(1)
|
||||
})
|
||||
}
|
||||
})
|
||||
|
||||
it('toggles uncomplete task - api setup', async () => {
|
||||
tasksApi.uncomplete.mockResolvedValue({ id: 2, completed: false })
|
||||
tasksApi.today.mockResolvedValue([mockTasks[0]])
|
||||
renderHome()
|
||||
await waitFor(() => {
|
||||
expect(screen.getByText('Buy groceries')).toBeInTheDocument()
|
||||
})
|
||||
// Verify uncomplete api is mocked properly
|
||||
expect(tasksApi.uncomplete).toBeDefined()
|
||||
})
|
||||
|
||||
it('shows habits with daily frequency', async () => {
|
||||
const dailyHabit = { id: 1, name: 'Daily Habit', frequency: 'daily', color: '#6366f1', icon: '💪', is_archived: false, created_at: '2026-01-01T00:00:00Z' }
|
||||
habitsApi.list.mockResolvedValue([dailyHabit])
|
||||
habitsApi.getLogs.mockResolvedValue([])
|
||||
habitsApi.getFreezes.mockResolvedValue([])
|
||||
renderHome()
|
||||
await waitFor(() => {
|
||||
expect(screen.getByText('Daily Habit')).toBeInTheDocument()
|
||||
})
|
||||
})
|
||||
|
||||
it('shows completion message when all habits done', async () => {
|
||||
const habit = { id: 1, name: 'Exercise', frequency: 'daily', color: '#6366f1', icon: '💪', is_archived: false, created_at: '2026-01-01T00:00:00Z' }
|
||||
habitsApi.list.mockResolvedValue([habit])
|
||||
const today = new Date().toISOString().split('T')[0]
|
||||
habitsApi.getLogs.mockResolvedValue([{ id: 10, date: today }])
|
||||
habitsApi.getFreezes.mockResolvedValue([])
|
||||
renderHome()
|
||||
await waitFor(() => {
|
||||
expect(screen.getByText(/Все привычки выполнены/)).toBeInTheDocument()
|
||||
}, { timeout: 3000 })
|
||||
})
|
||||
|
||||
it('opens log habit modal from calendar button', async () => {
|
||||
const habit = { id: 1, name: 'Exercise', frequency: 'daily', color: '#6366f1', icon: '💪', is_archived: false, created_at: '2026-01-01T00:00:00Z' }
|
||||
habitsApi.list.mockResolvedValue([habit])
|
||||
habitsApi.getLogs.mockResolvedValue([])
|
||||
habitsApi.getFreezes.mockResolvedValue([])
|
||||
renderHome()
|
||||
await waitFor(() => {
|
||||
expect(screen.getByText('Exercise')).toBeInTheDocument()
|
||||
})
|
||||
const calendarBtn = document.querySelector('[title="Отметить за другой день"]')
|
||||
if (calendarBtn) {
|
||||
fireEvent.click(calendarBtn)
|
||||
await waitFor(() => {
|
||||
expect(screen.getByTestId('log-habit-modal')).toBeInTheDocument()
|
||||
})
|
||||
}
|
||||
})
|
||||
|
||||
it('closes log habit modal', async () => {
|
||||
const habit = { id: 1, name: 'Exercise', frequency: 'daily', color: '#6366f1', icon: '💪', is_archived: false, created_at: '2026-01-01T00:00:00Z' }
|
||||
habitsApi.list.mockResolvedValue([habit])
|
||||
habitsApi.getLogs.mockResolvedValue([])
|
||||
habitsApi.getFreezes.mockResolvedValue([])
|
||||
renderHome()
|
||||
await waitFor(() => {
|
||||
expect(screen.getByText('Exercise')).toBeInTheDocument()
|
||||
})
|
||||
const calendarBtn = document.querySelector('[title="Отметить за другой день"]')
|
||||
if (calendarBtn) {
|
||||
fireEvent.click(calendarBtn)
|
||||
await waitFor(() => {
|
||||
expect(screen.getByTestId('log-habit-modal')).toBeInTheDocument()
|
||||
})
|
||||
fireEvent.click(screen.getByText('Close'))
|
||||
expect(screen.queryByTestId('log-habit-modal')).not.toBeInTheDocument()
|
||||
}
|
||||
})
|
||||
|
||||
it('handles habit toggle (log)', async () => {
|
||||
habitsApi.log.mockResolvedValue({ id: 99, date: new Date().toISOString().split('T')[0] })
|
||||
const habit = { id: 1, name: 'Exercise', frequency: 'daily', color: '#6366f1', icon: '💪', is_archived: false, created_at: '2026-01-01T00:00:00Z' }
|
||||
habitsApi.list.mockResolvedValue([habit])
|
||||
habitsApi.getLogs.mockResolvedValue([])
|
||||
habitsApi.getFreezes.mockResolvedValue([])
|
||||
renderHome()
|
||||
await waitFor(() => {
|
||||
expect(screen.getByText('Exercise')).toBeInTheDocument()
|
||||
})
|
||||
const habitBtns = document.querySelectorAll('button.rounded-2xl')
|
||||
if (habitBtns.length > 0) {
|
||||
fireEvent.click(habitBtns[0])
|
||||
await waitFor(() => {
|
||||
expect(habitsApi.log).toHaveBeenCalledWith(1, {})
|
||||
})
|
||||
}
|
||||
})
|
||||
|
||||
it('handles habit toggle (delete log when already done)', async () => {
|
||||
habitsApi.deleteLog.mockResolvedValue({})
|
||||
const habit = { id: 1, name: 'Exercise', frequency: 'daily', color: '#6366f1', icon: '💪', is_archived: false, created_at: '2026-01-01T00:00:00Z' }
|
||||
habitsApi.list.mockResolvedValue([habit])
|
||||
const today = new Date().toISOString().split('T')[0]
|
||||
habitsApi.getLogs.mockResolvedValue([{ id: 5, date: today }])
|
||||
habitsApi.getFreezes.mockResolvedValue([])
|
||||
renderHome()
|
||||
await waitFor(() => {
|
||||
expect(screen.getByText('Exercise')).toBeInTheDocument()
|
||||
})
|
||||
// After logs load, find the undo button for habit
|
||||
await waitFor(() => {
|
||||
const undoBtns = document.querySelectorAll('[title="Отменить"]')
|
||||
expect(undoBtns.length).toBeGreaterThan(0)
|
||||
}, { timeout: 3000 })
|
||||
})
|
||||
|
||||
it('renders tasks section header', async () => {
|
||||
tasksApi.today.mockResolvedValue([mockTasks[0]])
|
||||
renderHome()
|
||||
await waitFor(() => {
|
||||
expect(screen.getByText('Задачи на сегодня')).toBeInTheDocument()
|
||||
})
|
||||
})
|
||||
|
||||
it('handles log date from modal', async () => {
|
||||
habitsApi.log.mockResolvedValue({ id: 99, date: '2026-03-01' })
|
||||
const habit = { id: 1, name: 'Exercise', frequency: 'daily', color: '#6366f1', icon: '💪', is_archived: false, created_at: '2026-01-01T00:00:00Z' }
|
||||
habitsApi.list.mockResolvedValue([habit])
|
||||
habitsApi.getLogs.mockResolvedValue([])
|
||||
habitsApi.getFreezes.mockResolvedValue([])
|
||||
renderHome()
|
||||
await waitFor(() => {
|
||||
expect(screen.getByText('Exercise')).toBeInTheDocument()
|
||||
})
|
||||
const calendarBtn = document.querySelector('[title="Отметить за другой день"]')
|
||||
if (calendarBtn) {
|
||||
fireEvent.click(calendarBtn)
|
||||
await waitFor(() => {
|
||||
expect(screen.getByTestId('log-habit-modal')).toBeInTheDocument()
|
||||
})
|
||||
fireEvent.click(screen.getByText('Log Date'))
|
||||
await waitFor(() => {
|
||||
expect(habitsApi.log).toHaveBeenCalledWith(1, { date: '2026-03-01' })
|
||||
})
|
||||
}
|
||||
})
|
||||
})
|
||||
|
||||
// Test helper functions directly
|
||||
describe('shouldShowToday helper', () => {
|
||||
it('renders frozen habit indicator when habit is frozen', async () => {
|
||||
const habit = { id: 1, name: 'Frozen Habit', frequency: 'daily', color: '#6366f1', icon: '❄️', is_archived: false, created_at: '2026-01-01T00:00:00Z' }
|
||||
habitsApi.list.mockResolvedValue([habit])
|
||||
habitsApi.getLogs.mockResolvedValue([])
|
||||
const today = new Date().toISOString().split('T')[0]
|
||||
// Create a freeze that covers today
|
||||
const yesterday = new Date()
|
||||
yesterday.setDate(yesterday.getDate() - 1)
|
||||
const tomorrow = new Date()
|
||||
tomorrow.setDate(tomorrow.getDate() + 1)
|
||||
habitsApi.getFreezes.mockResolvedValue([{
|
||||
start_date: yesterday.toISOString().split('T')[0],
|
||||
end_date: tomorrow.toISOString().split('T')[0]
|
||||
}])
|
||||
renderHome()
|
||||
await waitFor(() => {
|
||||
expect(screen.getByText(/паузе/)).toBeInTheDocument()
|
||||
}, { timeout: 3000 })
|
||||
})
|
||||
})
|
||||
|
||||
|
||||
|
||||
@@ -21,8 +21,9 @@ vi.mock('../components/CreateTaskModal', () => ({
|
||||
}))
|
||||
|
||||
vi.mock('../components/EditTaskModal', () => ({
|
||||
default: ({ open, onClose }) => open ? (
|
||||
default: ({ open, onClose, task }) => open ? (
|
||||
<div data-testid="edit-task-modal">
|
||||
<span>{task?.title}</span>
|
||||
<button onClick={onClose}>Close</button>
|
||||
</div>
|
||||
) : null,
|
||||
@@ -35,8 +36,13 @@ vi.mock('../components/Navigation', () => ({
|
||||
import { tasksApi } from '../api/tasks'
|
||||
|
||||
const mockTasks = [
|
||||
{ id: 1, title: 'Buy groceries', completed: false, priority: 1, due_date: null, icon: '📋', color: '#6366f1', is_recurring: false, recurrence_type: null },
|
||||
{ id: 2, title: 'Read book', completed: false, priority: 0, due_date: '2026-03-30', icon: '📚', color: '#22c55e', is_recurring: false, recurrence_type: null },
|
||||
{ id: 1, title: 'Buy groceries', completed: false, priority: 1, due_date: null, icon: '📋', color: '#6366f1', is_recurring: false, recurrence_type: null, description: '' },
|
||||
{ id: 2, title: 'Read book', completed: false, priority: 2, due_date: '2026-03-30', icon: '📚', color: '#22c55e', is_recurring: false, recurrence_type: null, description: 'Read 30 pages' },
|
||||
{ id: 3, title: 'Completed task', completed: true, priority: 3, due_date: null, icon: '✅', color: '#f59e0b', is_recurring: true, recurrence_type: 'daily', description: '' },
|
||||
]
|
||||
|
||||
const mockTasksOverdue = [
|
||||
{ id: 4, title: 'Overdue task', completed: false, priority: 0, due_date: '2020-01-01', icon: '⚠️', color: '#ef4444', is_recurring: false, recurrence_type: null, description: '' },
|
||||
]
|
||||
|
||||
const renderTasks = (embedded = false) => {
|
||||
@@ -53,7 +59,7 @@ const renderTasks = (embedded = false) => {
|
||||
describe('Tasks page', () => {
|
||||
beforeEach(() => {
|
||||
vi.clearAllMocks()
|
||||
tasksApi.list.mockResolvedValue(mockTasks)
|
||||
tasksApi.list.mockResolvedValue(mockTasks.filter(t => !t.completed))
|
||||
})
|
||||
|
||||
it('renders tasks list', async () => {
|
||||
@@ -92,27 +98,215 @@ describe('Tasks page', () => {
|
||||
expect(screen.getByTestId('navigation')).toBeInTheDocument()
|
||||
})
|
||||
|
||||
it('does not render navigation when embedded', () => {
|
||||
renderTasks(true)
|
||||
expect(screen.queryByTestId('navigation')).not.toBeInTheDocument()
|
||||
})
|
||||
|
||||
it('shows empty state when no tasks', async () => {
|
||||
tasksApi.list.mockResolvedValue([])
|
||||
renderTasks()
|
||||
await waitFor(() => {
|
||||
// Component shows "Нет активных задач" when filter is 'active' (default)
|
||||
expect(screen.getByText(/Нет активных задач/)).toBeInTheDocument()
|
||||
})
|
||||
})
|
||||
|
||||
it('shows add task button in empty state for active filter', async () => {
|
||||
tasksApi.list.mockResolvedValue([])
|
||||
renderTasks()
|
||||
await waitFor(() => {
|
||||
expect(screen.getByText('Добавить задачу')).toBeInTheDocument()
|
||||
})
|
||||
})
|
||||
|
||||
it('opens create task modal', async () => {
|
||||
renderTasks()
|
||||
await waitFor(() => {
|
||||
expect(screen.getByText('Задачи')).toBeInTheDocument()
|
||||
})
|
||||
const plusBtn = document.querySelector('button.bg-primary-500') || document.querySelector('button[class*="bg-primary"]')
|
||||
if (plusBtn) {
|
||||
fireEvent.click(plusBtn)
|
||||
expect(screen.getByTestId('create-task-modal')).toBeInTheDocument()
|
||||
}
|
||||
})
|
||||
|
||||
it('opens add task modal from empty state button', async () => {
|
||||
tasksApi.list.mockResolvedValue([])
|
||||
renderTasks()
|
||||
await waitFor(() => {
|
||||
expect(screen.getByText('Добавить задачу')).toBeInTheDocument()
|
||||
})
|
||||
fireEvent.click(screen.getByText('Добавить задачу'))
|
||||
expect(screen.getByTestId('create-task-modal')).toBeInTheDocument()
|
||||
})
|
||||
|
||||
it('switches to completed filter', async () => {
|
||||
tasksApi.list.mockResolvedValue([mockTasks[2]])
|
||||
renderTasks()
|
||||
await waitFor(() => {
|
||||
expect(screen.getByText('Выполненные')).toBeInTheDocument()
|
||||
})
|
||||
fireEvent.click(screen.getByText('Выполненные'))
|
||||
await waitFor(() => {
|
||||
expect(tasksApi.list).toHaveBeenCalledWith(true)
|
||||
})
|
||||
})
|
||||
|
||||
it('switches to all filter', async () => {
|
||||
tasksApi.list.mockResolvedValue(mockTasks)
|
||||
renderTasks()
|
||||
await waitFor(() => {
|
||||
expect(screen.getByText('Все')).toBeInTheDocument()
|
||||
})
|
||||
fireEvent.click(screen.getByText('Все'))
|
||||
await waitFor(() => {
|
||||
expect(tasksApi.list).toHaveBeenCalled()
|
||||
})
|
||||
})
|
||||
|
||||
it('completes a task on click', async () => {
|
||||
tasksApi.complete.mockResolvedValueOnce({ id: 1, completed: true })
|
||||
tasksApi.complete.mockResolvedValue({ id: 1, completed: true })
|
||||
tasksApi.list.mockResolvedValue([mockTasks[0]])
|
||||
renderTasks()
|
||||
await waitFor(() => {
|
||||
expect(screen.getByText('Buy groceries')).toBeInTheDocument()
|
||||
})
|
||||
const completeButtons = document.querySelectorAll('button[class*="rounded-full"]')
|
||||
if (completeButtons.length > 0) {
|
||||
fireEvent.click(completeButtons[0])
|
||||
// Get all buttons: [0]=create(+), [1]=complete-task-btn, [2]=edit-btn
|
||||
const allBtns = screen.getAllByRole('button')
|
||||
if (allBtns.length > 1) {
|
||||
fireEvent.click(allBtns[4])
|
||||
await waitFor(() => {
|
||||
expect(tasksApi.complete).toHaveBeenCalledWith(1)
|
||||
}, { timeout: 3000 })
|
||||
}
|
||||
})
|
||||
|
||||
it('shows priority badge for tasks', async () => {
|
||||
tasksApi.list.mockResolvedValue([mockTasks[1]])
|
||||
renderTasks()
|
||||
await waitFor(() => {
|
||||
expect(screen.getByText('Read book')).toBeInTheDocument()
|
||||
})
|
||||
expect(screen.getByText('Средний')).toBeInTheDocument()
|
||||
})
|
||||
|
||||
it('shows high priority badge', async () => {
|
||||
tasksApi.list.mockResolvedValue([mockTasks[2]])
|
||||
renderTasks()
|
||||
await waitFor(() => {
|
||||
expect(screen.getByText('Completed task')).toBeInTheDocument()
|
||||
})
|
||||
expect(screen.getByText('Высокий')).toBeInTheDocument()
|
||||
})
|
||||
|
||||
it('shows due date for tasks', async () => {
|
||||
tasksApi.list.mockResolvedValue([mockTasks[1]])
|
||||
renderTasks()
|
||||
await waitFor(() => {
|
||||
expect(screen.getByText('Read book')).toBeInTheDocument()
|
||||
})
|
||||
expect(document.querySelector('[class*="text-gray"]')).toBeTruthy()
|
||||
})
|
||||
|
||||
it('shows overdue indicator', async () => {
|
||||
tasksApi.list.mockResolvedValue(mockTasksOverdue)
|
||||
renderTasks()
|
||||
await waitFor(() => {
|
||||
expect(screen.getByText('Overdue task')).toBeInTheDocument()
|
||||
})
|
||||
expect(document.querySelector('svg')).toBeTruthy()
|
||||
})
|
||||
|
||||
it('opens edit modal when task title clicked', async () => {
|
||||
tasksApi.list.mockResolvedValue([mockTasks[0]])
|
||||
renderTasks()
|
||||
await waitFor(() => {
|
||||
expect(screen.getByText('Buy groceries')).toBeInTheDocument()
|
||||
})
|
||||
fireEvent.click(screen.getByText('Buy groceries'))
|
||||
await waitFor(() => {
|
||||
expect(screen.getByTestId('edit-task-modal')).toBeInTheDocument()
|
||||
})
|
||||
})
|
||||
|
||||
it('closes edit modal', async () => {
|
||||
tasksApi.list.mockResolvedValue([mockTasks[0]])
|
||||
renderTasks()
|
||||
await waitFor(() => {
|
||||
expect(screen.getByText('Buy groceries')).toBeInTheDocument()
|
||||
})
|
||||
fireEvent.click(screen.getByText('Buy groceries'))
|
||||
await waitFor(() => {
|
||||
expect(screen.getByTestId('edit-task-modal')).toBeInTheDocument()
|
||||
})
|
||||
fireEvent.click(screen.getByText('Close'))
|
||||
expect(screen.queryByTestId('edit-task-modal')).not.toBeInTheDocument()
|
||||
})
|
||||
|
||||
it('shows recurring icon for recurring tasks', async () => {
|
||||
tasksApi.list.mockResolvedValue([mockTasks[2]])
|
||||
renderTasks()
|
||||
await waitFor(() => {
|
||||
expect(screen.getByText('Completed task')).toBeInTheDocument()
|
||||
})
|
||||
expect(screen.getByText('🔄')).toBeInTheDocument()
|
||||
})
|
||||
|
||||
it('shows recurrence label', async () => {
|
||||
tasksApi.list.mockResolvedValue([mockTasks[2]])
|
||||
renderTasks()
|
||||
await waitFor(() => {
|
||||
expect(screen.getByText('Ежедневно')).toBeInTheDocument()
|
||||
})
|
||||
})
|
||||
|
||||
it('shows task description', async () => {
|
||||
tasksApi.list.mockResolvedValue([mockTasks[1]])
|
||||
renderTasks()
|
||||
await waitFor(() => {
|
||||
expect(screen.getByText('Read 30 pages')).toBeInTheDocument()
|
||||
})
|
||||
})
|
||||
|
||||
it('uncompletes a task', async () => {
|
||||
tasksApi.uncomplete.mockResolvedValue({ id: 3, completed: false })
|
||||
const completedTask = { ...mockTasks[2], completed: true }
|
||||
tasksApi.list.mockResolvedValue([completedTask])
|
||||
renderTasks()
|
||||
await waitFor(() => {
|
||||
expect(screen.getByText('Completed task')).toBeInTheDocument()
|
||||
})
|
||||
const undoBtn = document.querySelector('[title="Отменить"]')
|
||||
if (undoBtn) {
|
||||
fireEvent.click(undoBtn)
|
||||
await waitFor(() => {
|
||||
expect(tasksApi.uncomplete).toHaveBeenCalledWith(3)
|
||||
})
|
||||
}
|
||||
})
|
||||
|
||||
it('shows empty state for completed filter', async () => {
|
||||
tasksApi.list.mockResolvedValue([])
|
||||
renderTasks()
|
||||
await waitFor(() => {
|
||||
expect(screen.getByText('Выполненные')).toBeInTheDocument()
|
||||
})
|
||||
fireEvent.click(screen.getByText('Выполненные'))
|
||||
await waitFor(() => {
|
||||
expect(screen.getByText(/Нет выполненных задач/)).toBeInTheDocument()
|
||||
})
|
||||
})
|
||||
|
||||
it('shows empty state for all filter', async () => {
|
||||
tasksApi.list.mockResolvedValue([])
|
||||
renderTasks()
|
||||
await waitFor(() => {
|
||||
expect(screen.getByText('Все')).toBeInTheDocument()
|
||||
})
|
||||
fireEvent.click(screen.getByText('Все'))
|
||||
await waitFor(() => {
|
||||
expect(screen.getByText(/Нет задач/)).toBeInTheDocument()
|
||||
})
|
||||
})
|
||||
})
|
||||
|
||||
Reference in New Issue
Block a user