Skip to content
Merged
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
96 changes: 50 additions & 46 deletions frontend/__tests__/unit/components/CalendarButton.test.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -332,58 +332,62 @@ describe('CalendarButton', () => {
})
})

describe('long title overflow handling', () => {
it('remains accessible with very long event titles', () => {
const longTitle =
'This Is A Very Long Event Title That Extends Beyond Normal Length With Additional Description'
render(
<CalendarButton
event={{
title: longTitle,
startDate: '2026-06-22',
endDate: '2026-06-26',
}}
className="flex-shrink-0"
/>
)
describe('hover state', () => {
it('toggles icon on hover - shows FaCalendarPlus when hovering', async () => {
render(<CalendarButton event={mockEvent} />)
const button = screen.getByRole('button')
expect(button).toBeInTheDocument()
expect(button).toHaveAttribute('aria-label', `Add ${longTitle} to Calendar`)

// Initially should show FaCalendar (not hovered)
const initialIconMarkup = button.querySelector('svg')?.outerHTML

// Simulate mouse enter
fireEvent.mouseEnter(button)
await waitFor(() => {
// After hover, FaCalendarPlus should be shown (different SVG)
const hoveredIconMarkup = button.querySelector('svg')?.outerHTML
expect(hoveredIconMarkup).not.toBe(initialIconMarkup)
})
})

it('maintains visibility with flex-shrink-0 class', () => {
render(
<CalendarButton
event={{
title: 'Very Long Event Title That Could Potentially Cause Overflow Issues',
startDate: '2025-12-01',
}}
className="flex-shrink-0 text-gray-600"
/>
)
it('reverts to FaCalendar icon when mouse leaves', async () => {
render(<CalendarButton event={mockEvent} />)
const button = screen.getByRole('button')
expect(button).toHaveClass('flex-shrink-0')
expect(button).toBeVisible()

// Capture initial icon (FaCalendar)
const initialIconHtml = button.innerHTML

// Mouse enter - hover state true
fireEvent.mouseEnter(button)

await waitFor(() => {
// Icon should change to FaCalendarPlus
expect(button.innerHTML).not.toEqual(initialIconHtml)
})

// Mouse leave - hover state false
fireEvent.mouseLeave(button)

await waitFor(() => {
// Icon should revert to FaCalendar
expect(button.innerHTML).toEqual(initialIconHtml)
})
})

it('works correctly in flex container with long text sibling', () => {
const { container } = render(
<div className="flex items-center justify-between gap-2">
<button className="min-w-0 flex-1 truncate">
This Is A Really Long Event Title That Should Be Truncated Properly
</button>
<CalendarButton
event={{
title: 'Event',
startDate: '2025-12-01',
}}
className="flex-shrink-0"
/>
</div>
)
const button = container.querySelector('button[aria-label="Add Event to Calendar"]')
expect(button).toBeInTheDocument()
expect(button).toHaveClass('flex-shrink-0')
it('maintains button functionality during hover state transitions', async () => {
render(<CalendarButton event={mockEvent} />)
const button = screen.getByRole('button')

fireEvent.mouseEnter(button)
fireEvent.mouseLeave(button)

expect(button).not.toBeDisabled()

fireEvent.click(button)
expect(button).toBeDisabled()

await waitFor(() => {
expect(button).not.toBeDisabled()
})
})
})
})
83 changes: 83 additions & 0 deletions frontend/__tests__/unit/components/Card.test.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -647,4 +647,87 @@ describe('Card', () => {
expect(screen.queryByTestId('label-more')).not.toBeInTheDocument()
expect(screen.getAllByTestId('label')).toHaveLength(5)
})

describe('timeline rendering', () => {
it('renders timeline when both start and end dates are provided', () => {
const propsWithTimeline = {
...baseProps,
timeline: {
start: '2024-01-01',
end: '2024-12-31',
},
}
render(<Card {...propsWithTimeline} />)
expect(screen.getByTestId('calendar-icon')).toBeInTheDocument()
})

it('does not render timeline when start date is empty string', () => {
const propsWithEmptyStart = {
...baseProps,
timeline: {
start: '',
end: '2024-12-31',
},
}
render(<Card {...propsWithEmptyStart} />)
expect(screen.queryByTestId('calendar-icon')).not.toBeInTheDocument()
})

it('does not render timeline when end date is empty string', () => {
const propsWithEmptyEnd = {
...baseProps,
timeline: {
start: '2024-01-01',
end: '',
},
}
render(<Card {...propsWithEmptyEnd} />)
expect(screen.queryByTestId('calendar-icon')).not.toBeInTheDocument()
})

it('does not render timeline when timeline is undefined', () => {
render(<Card {...baseProps} />)
expect(screen.queryByTestId('calendar-icon')).not.toBeInTheDocument()
})
})

describe('social media aria-label fallback', () => {
it('uses item title as aria-label when provided', () => {
const propsWithSocialTitle = {
...baseProps,
social: [
{ title: 'GitHub Profile', url: 'https://github.com/test', icon: MockIcon as IconType },
],
}
render(<Card {...propsWithSocialTitle} />)
const socialLink = screen.getByRole('link', { name: 'GitHub Profile' })
expect(socialLink).toHaveAttribute('aria-label', 'GitHub Profile')
})

it('uses fallback aria-label when item title is empty', () => {
const propsWithEmptySocialTitle = {
...baseProps,
social: [{ title: '', url: 'https://github.com/test', icon: MockIcon as IconType }],
}
render(<Card {...propsWithEmptySocialTitle} />)
const socialLink = screen.getByRole('link', { name: 'Social media link' })
expect(socialLink).toHaveAttribute('aria-label', 'Social media link')
})

it('uses fallback aria-label when item title is undefined', () => {
const propsWithUndefinedSocialTitle = {
...baseProps,
social: [
{
title: undefined as unknown as string,
url: 'https://github.com/test',
icon: MockIcon as IconType,
},
],
}
render(<Card {...propsWithUndefinedSocialTitle} />)
const socialLink = screen.getByRole('link', { name: 'Social media link' })
expect(socialLink).toHaveAttribute('aria-label', 'Social media link')
})
})
})
Loading