diff --git a/.Jules/palette.md b/.Jules/palette.md index bbbd06dfd..3c352a63d 100644 --- a/.Jules/palette.md +++ b/.Jules/palette.md @@ -29,3 +29,7 @@ ## 2026-01-22 - Replacing Text Characters with Semantic Icons **Learning:** In `AppSidebar.tsx`, a text character "▼" was used for the expand/collapse arrow. Screen readers might announce this as "Black Down-Pointing Triangle" or similar, which is distracting. Text characters also don't scale or style as consistently as SVGs. **Action:** Replace decorative text characters with `CyberIcon` (or proper SVG icons) and add `aria-hidden="true"` to ensure they are treated as visual decoration only. + +## 2026-01-24 - Keyboard Visibility for Hover Actions +**Learning:** In `GoalsList.tsx`, action buttons were hidden using `opacity-0 group-hover:opacity-100`, making them invisible to keyboard users who tabbed into the card. Focus does not trigger hover styles by default. +**Action:** Always add `group-focus-within:opacity-100` alongside `group-hover:opacity-100` for containers of interactive elements to ensure they become visible when a user focuses on them. diff --git a/src/components/goals/GoalsList.tsx b/src/components/goals/GoalsList.tsx index 7e6053de0..20ea71bea 100644 --- a/src/components/goals/GoalsList.tsx +++ b/src/components/goals/GoalsList.tsx @@ -75,13 +75,13 @@ export function GoalsList({ goals, onEdit, onDelete, onPlan, onStatusChange, onA -
- - diff --git a/src/tests/components/GoalsList.test.tsx b/src/tests/components/GoalsList.test.tsx new file mode 100644 index 000000000..e9075c38d --- /dev/null +++ b/src/tests/components/GoalsList.test.tsx @@ -0,0 +1,60 @@ +import { render, screen } from '@testing-library/react'; +import { GoalsList } from '../../components/goals/GoalsList'; +import { Goal } from '../../hooks/useGoals'; +import React from 'react'; +import { vi, describe, it, expect } from 'vitest'; + +// Mock CyberIcon to avoid rendering issues and focus on logic +vi.mock('@/components/ui/cyber-icons', () => ({ + CyberIcon: ({ name }: { name: string }) => {name}, +})); + +// Mock ElectricBorder to avoid complex rendering +vi.mock('@/components/ui/electric-border', () => ({ + ElectricBorder: ({ children, className }: { children: React.ReactNode; className?: string }) => ( +
{children}
+ ), +})); + +describe('GoalsList', () => { + const mockGoal: Goal = { + id: '1', + title: 'Test Goal', + description: 'Test Description', + category: 'personal', + status: 'active', + progress_percentage: 50, + created_at: new Date().toISOString(), + goal_milestones: [], + goal_checkins: [], + }; + + const defaultProps = { + goals: [mockGoal], + onEdit: vi.fn(), + onDelete: vi.fn(), + onPlan: vi.fn(), + onStatusChange: vi.fn(), + onAddTask: vi.fn(), + }; + + it('renders goals with accessible actions', () => { + render(); + + // Check if goal title is rendered + expect(screen.getByText('Test Goal')).toBeInTheDocument(); + + // Check for Edit button accessibility + const editButton = screen.getByRole('button', { name: /editar meta/i }); + expect(editButton).toBeInTheDocument(); + + // Check for Delete button accessibility (it's inside AlertDialogTrigger) + const deleteButton = screen.getByRole('button', { name: /excluir meta/i }); + expect(deleteButton).toBeInTheDocument(); + + // Check for container visibility class + // We find the container by finding the edit button and looking at its parent + const buttonContainer = editButton.parentElement; + expect(buttonContainer).toHaveClass('group-focus-within:opacity-100'); + }); +});