Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions .jules/bolt.md
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
# Bolt's Journal
5 changes: 3 additions & 2 deletions src/components/tasks/TaskCard.tsx
Original file line number Diff line number Diff line change
@@ -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";
Expand Down Expand Up @@ -25,7 +26,7 @@ interface TaskCardProps {
isCompleted: boolean;
}

export function TaskCard({
export const TaskCard = memo(function TaskCard({
task,
onToggleComplete,
onEdit,
Expand Down Expand Up @@ -360,4 +361,4 @@ export function TaskCard({
</ElectricBorder>
</motion.div>
);
}
});
26 changes: 15 additions & 11 deletions src/components/tasks/TasksContainer.tsx
Original file line number Diff line number Diff line change
@@ -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";
Expand Down Expand Up @@ -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);
Expand Down Expand Up @@ -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;

Expand Down Expand Up @@ -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}
Expand All @@ -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}
Expand Down
22 changes: 3 additions & 19 deletions src/components/tasks/TasksList.tsx
Original file line number Diff line number Diff line change
@@ -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";
Expand Down Expand Up @@ -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 (
<div className="flex items-center justify-center h-64">
Expand Down Expand Up @@ -94,12 +78,12 @@ export function TasksList({
<TaskCard
key={task.id}
task={task}
onToggleComplete={handleToggleComplete}
onToggleComplete={onToggleComplete}
onEdit={onEdit}
onDelete={handleDelete}
onDelete={onDelete}
onClick={onTaskClick}
goals={goals}
getTagColor={(tag) => getTaskColor(tag)}
getTagColor={getTaskColor}
isCompleted={isCompleted}
/>
))}
Expand Down
92 changes: 92 additions & 0 deletions src/tests/components/TaskCard.test.tsx
Original file line number Diff line number Diff line change
@@ -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 }) => <span data-testid={`icon-${name}`}>{name}</span>
}));

// Mock ElectricBorder
vi.mock('@/components/ui/electric-border', () => ({
ElectricBorder: ({ children }: { children: React.ReactNode }) => <div>{children}</div>
}));

// Mock Framer Motion
vi.mock('framer-motion', () => ({
motion: {
div: ({ children, className, onClick }: any) => <div className={className} onClick={onClick}>{children}</div>
},
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(
<TaskCard
task={mockTask}
onToggleComplete={vi.fn()}
onEdit={vi.fn()}
onDelete={vi.fn()}
onClick={vi.fn()}
goals={mockGoals}
getTagColor={() => '#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(
<TaskCard
task={mockTask}
onToggleComplete={onToggle}
onEdit={onEdit}
onDelete={onDelete}
onClick={vi.fn()}
goals={mockGoals}
getTagColor={() => '#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);
});
});
Loading