diff --git a/.jules/bolt.md b/.jules/bolt.md new file mode 100644 index 000000000..e1fbf01aa --- /dev/null +++ b/.jules/bolt.md @@ -0,0 +1 @@ +# Bolt's Journal diff --git a/src/components/tasks/TaskCard.tsx b/src/components/tasks/TaskCard.tsx index ea8e94218..bd1e06759 100644 --- a/src/components/tasks/TaskCard.tsx +++ b/src/components/tasks/TaskCard.tsx @@ -1,3 +1,4 @@ +import { memo } from "react"; import { Badge } from "@/components/ui/badge"; import { Button } from "@/components/ui/button"; import { Card, CardContent } from "@/components/ui/card"; @@ -25,7 +26,7 @@ interface TaskCardProps { isCompleted: boolean; } -export function TaskCard({ +export const TaskCard = memo(function TaskCard({ task, onToggleComplete, onEdit, @@ -360,4 +361,4 @@ export function TaskCard({ ); -} +}); diff --git a/src/components/tasks/TasksContainer.tsx b/src/components/tasks/TasksContainer.tsx index b9f8ad555..3572c778b 100644 --- a/src/components/tasks/TasksContainer.tsx +++ b/src/components/tasks/TasksContainer.tsx @@ -1,4 +1,4 @@ -import { useEffect, useMemo, useState } from "react"; +import { useCallback, useEffect, useMemo, useState } from "react"; import { TaskDetailsSheet } from "@/components/tasks/TaskDetailsSheet"; import { TaskFormDialog } from "@/components/tasks/TaskFormDialog"; @@ -380,11 +380,11 @@ export function TasksContainer() { setIsDialogOpen(true); }; - const openEditDialog = (task: Task) => { + const openEditDialog = useCallback((task: Task) => { setDialogMode("edit"); setSelectedTask(task); setIsDialogOpen(true); - }; + }, []); const handleDialogOpenChange = (open: boolean) => { setIsDialogOpen(open); @@ -413,9 +413,17 @@ export function TasksContainer() { } }; - const handleDelete = (task: Task) => { + const handleDelete = useCallback((task: Task) => { deleteMutation.mutate(task.id); - }; + }, [deleteMutation.mutate]); + + const handleToggleCompletePending = useCallback((task: Task) => { + toggleCompleteMutation.mutate({ id: task.id, completed: true }); + }, [toggleCompleteMutation.mutate]); + + const handleToggleCompleteCompleted = useCallback((task: Task) => { + toggleCompleteMutation.mutate({ id: task.id, completed: false }); + }, [toggleCompleteMutation.mutate]); const currentFormData = selectedTask ? toFormData(selectedTask) : EMPTY_FORM; @@ -491,9 +499,7 @@ export function TasksContainer() { isLoading={isLoading} isCompleted={false} onPageChange={setCurrentPage} - onToggleComplete={(task) => - toggleCompleteMutation.mutate({ id: task.id, completed: true }) - } + onToggleComplete={handleToggleCompletePending} onEdit={openEditDialog} onDelete={handleDelete} onTaskClick={setDetailsTask} @@ -508,9 +514,7 @@ export function TasksContainer() { isLoading={isLoading} isCompleted onPageChange={setCurrentPage} - onToggleComplete={(task) => - toggleCompleteMutation.mutate({ id: task.id, completed: false }) - } + onToggleComplete={handleToggleCompleteCompleted} onEdit={openEditDialog} onDelete={handleDelete} onTaskClick={setDetailsTask} diff --git a/src/components/tasks/TasksList.tsx b/src/components/tasks/TasksList.tsx index 8b91f4000..a79524eb0 100644 --- a/src/components/tasks/TasksList.tsx +++ b/src/components/tasks/TasksList.tsx @@ -1,5 +1,3 @@ -import { useCallback } from "react"; - import { TaskCard } from "@/components/tasks/TaskCard"; import { Badge } from "@/components/ui/badge"; import { Card } from "@/components/ui/card"; @@ -37,20 +35,6 @@ export function TasksList({ onTaskClick, goals, }: TasksListProps) { - const handleToggleComplete = useCallback( - (task: Task) => { - onToggleComplete(task); - }, - [onToggleComplete], - ); - - const handleDelete = useCallback( - (task: Task) => { - onDelete(task); - }, - [onDelete], - ); - if (isLoading) { return (
@@ -94,12 +78,12 @@ export function TasksList({ getTaskColor(tag)} + getTagColor={getTaskColor} isCompleted={isCompleted} /> ))} diff --git a/src/tests/components/TaskCard.test.tsx b/src/tests/components/TaskCard.test.tsx new file mode 100644 index 000000000..d910e092e --- /dev/null +++ b/src/tests/components/TaskCard.test.tsx @@ -0,0 +1,92 @@ +import { render, screen, fireEvent } from '@testing-library/react'; +import { TaskCard } from '../../components/tasks/TaskCard'; +import { vi } from 'vitest'; +import { Task } from '../../types/task'; +import React from 'react'; + +// Mock CyberIcon +vi.mock('@/components/ui/cyber-icons', () => ({ + default: ({ name }: { name: string }) => {name} +})); + +// Mock ElectricBorder +vi.mock('@/components/ui/electric-border', () => ({ + ElectricBorder: ({ children }: { children: React.ReactNode }) =>
{children}
+})); + +// Mock Framer Motion +vi.mock('framer-motion', () => ({ + motion: { + div: ({ children, className, onClick }: any) =>
{children}
+ }, + AnimatePresence: ({ children }: any) => <>{children} +})); + +const mockTask: Task = { + id: '1', + title: 'Test Task', + description: 'Test Description', + status: 'pending', + priority: 'medium', + due_date: '2023-12-31', + created_at: '2023-01-01', + completed_at: null, + user_id: 'user1', // Not in interface but might be used? Interface says: id, title... let's stick to interface + goal_id: 'goal1', + tags: ['tag1'] +} as unknown as Task; // Cast to avoid missing optional props mismatch if any + +const mockGoals = [{ id: 'goal1', title: 'Goal 1', status: 'active' }]; + +describe('TaskCard', () => { + it('renders task details correctly', () => { + render( + '#000'} + isCompleted={false} + /> + ); + + expect(screen.getByText('Test Task')).toBeInTheDocument(); + expect(screen.getByText('Test Description')).toBeInTheDocument(); + expect(screen.getByText('Goal 1')).toBeInTheDocument(); + expect(screen.getByText('#tag1')).toBeInTheDocument(); + }); + + it('calls handlers when buttons are clicked', () => { + const onToggle = vi.fn(); + const onEdit = vi.fn(); + const onDelete = vi.fn(); + + render( + '#000'} + isCompleted={false} + /> + ); + + const toggleButton = screen.getByLabelText('Concluir tarefa'); + fireEvent.click(toggleButton); + expect(onToggle).toHaveBeenCalledWith(mockTask); + + const editButton = screen.getByLabelText('Editar tarefa'); + fireEvent.click(editButton); + expect(onEdit).toHaveBeenCalledWith(mockTask); + + const deleteButton = screen.getByLabelText('Excluir tarefa'); + fireEvent.click(deleteButton); + expect(onDelete).toHaveBeenCalledWith(mockTask); + }); +});