diff --git a/frontend/__tests__/unit/components/ModuleList.test.tsx b/frontend/__tests__/unit/components/ModuleList.test.tsx deleted file mode 100644 index ded2d57e51..0000000000 --- a/frontend/__tests__/unit/components/ModuleList.test.tsx +++ /dev/null @@ -1,316 +0,0 @@ -import { faChevronDown, faChevronUp } from '@fortawesome/free-solid-svg-icons' -import { fireEvent, render, screen } from '@testing-library/react' -import React from 'react' -import ModuleList from 'components/ModuleList' - -// Mock FontAwesome icons -jest.mock('@fortawesome/react-fontawesome', () => ({ - FontAwesomeIcon: ({ icon, className }: { icon: unknown; className?: string }) => { - let iconName = 'unknown' - - if (icon === faChevronDown) { - iconName = 'chevron-down' - } else if (icon === faChevronUp) { - iconName = 'chevron-up' - } - - return - }, -})) - -// Mock HeroUI Button component -jest.mock('@heroui/button', () => ({ - Button: ({ - children, - onPress, - className, - 'aria-label': ariaLabel, - disableAnimation: _disableAnimation, - ..._props - }: { - children: React.ReactNode - onPress: () => void - className?: string - 'aria-label'?: string - disableAnimation?: boolean - [key: string]: unknown - }) => ( - - ), -})) - -describe('ModuleList', () => { - describe('Empty and Null Cases', () => { - it('returns null when modules array is empty', () => { - const { container } = render() - expect(container.firstChild).toBeNull() - }) - - it('returns null when modules is undefined', () => { - const { container } = render() - expect(container.firstChild).toBeNull() - }) - - it('returns null when modules is null', () => { - const { container } = render() - expect(container.firstChild).toBeNull() - }) - }) - - describe('Rendering with Different Module Counts', () => { - it('renders all modules when count is less than 5', () => { - const modules = ['Module 1', 'Module 2', 'Module 3'] - render() - - expect(screen.getByText('Module 1')).toBeInTheDocument() - expect(screen.getByText('Module 2')).toBeInTheDocument() - expect(screen.getByText('Module 3')).toBeInTheDocument() - - // Should not show "Show more" button - expect(screen.queryByText('Show more')).not.toBeInTheDocument() - }) - - it('renders all modules when count is exactly 5', () => { - const modules = ['Module 1', 'Module 2', 'Module 3', 'Module 4', 'Module 5'] - render() - - for (const myModule of modules) { - expect(screen.getByText(myModule)).toBeInTheDocument() - } - - // Should not show "Show more" button - expect(screen.queryByText('Show more')).not.toBeInTheDocument() - }) - - it('renders only first 5 modules when count is greater than 5', () => { - const modules = [ - 'Module 1', - 'Module 2', - 'Module 3', - 'Module 4', - 'Module 5', - 'Module 6', - 'Module 7', - ] - render() - - // First 5 should be visible - expect(screen.getByText('Module 1')).toBeInTheDocument() - expect(screen.getByText('Module 2')).toBeInTheDocument() - expect(screen.getByText('Module 3')).toBeInTheDocument() - expect(screen.getByText('Module 4')).toBeInTheDocument() - expect(screen.getByText('Module 5')).toBeInTheDocument() - - // Last 2 should not be visible initially - expect(screen.queryByText('Module 6')).not.toBeInTheDocument() - expect(screen.queryByText('Module 7')).not.toBeInTheDocument() - - // Should show "Show more" button - expect(screen.getByText('Show more')).toBeInTheDocument() - }) - }) - - describe('Show More/Less Functionality', () => { - const manyModules = Array.from({ length: 8 }, (_, i) => `Module ${i + 1}`) - - it('shows "Show more" button with correct aria-label initially', () => { - render() - - const button = screen.getByRole('button', { name: 'Show more modules' }) - expect(button).toBeInTheDocument() - expect(screen.getByText('Show more')).toBeInTheDocument() - expect(screen.getByTestId('icon-chevron-down')).toBeInTheDocument() - }) - - it('expands to show all modules when "Show more" is clicked', () => { - render() - - // Initially only first 5 are visible - expect(screen.getByText('Module 1')).toBeInTheDocument() - expect(screen.getByText('Module 5')).toBeInTheDocument() - expect(screen.queryByText('Module 6')).not.toBeInTheDocument() - - // Click "Show more" - const showMoreButton = screen.getByText('Show more') - fireEvent.click(showMoreButton) - - // Now all modules should be visible - expect(screen.getByText('Module 6')).toBeInTheDocument() - expect(screen.getByText('Module 7')).toBeInTheDocument() - expect(screen.getByText('Module 8')).toBeInTheDocument() - }) - - it('changes to "Show less" button after expanding', () => { - render() - - const showMoreButton = screen.getByText('Show more') - fireEvent.click(showMoreButton) - - // Button should change to "Show less" - expect(screen.getByText('Show less')).toBeInTheDocument() - expect(screen.getByRole('button', { name: 'Show fewer modules' })).toBeInTheDocument() - expect(screen.getByTestId('icon-chevron-up')).toBeInTheDocument() - expect(screen.queryByText('Show more')).not.toBeInTheDocument() - }) - - it('collapses back to 5 modules when "Show less" is clicked', () => { - render() - - // Expand first - const showMoreButton = screen.getByText('Show more') - fireEvent.click(showMoreButton) - - // Verify all are visible - expect(screen.getByText('Module 8')).toBeInTheDocument() - - // Click "Show less" - const showLessButton = screen.getByText('Show less') - fireEvent.click(showLessButton) - - // Should be back to first 5 only - expect(screen.getByText('Module 5')).toBeInTheDocument() - expect(screen.queryByText('Module 6')).not.toBeInTheDocument() - expect(screen.queryByText('Module 8')).not.toBeInTheDocument() - - // Button should be back to "Show more" - expect(screen.getByText('Show more')).toBeInTheDocument() - }) - }) - - describe('Module Text Truncation', () => { - it('truncates module names longer than 50 characters', () => { - const longModuleName = - 'This is a very long module name that exceeds fifty characters and should be truncated' - const modules = [longModuleName] - - render() - - const truncatedButton = screen.getByRole('button', { - name: `Module: ${longModuleName}`, - }) - expect(truncatedButton).toHaveAttribute('title', longModuleName) - }) - - it('does not truncate module names 50 characters or shorter', () => { - const exactlyFiftyChars = 'A'.repeat(50) - const modules = [exactlyFiftyChars, 'Short'] - - render() - - expect(screen.getByText(exactlyFiftyChars)).toBeInTheDocument() - expect(screen.getByText('Short')).toBeInTheDocument() - }) - - it('adds title attribute for truncated modules', () => { - const longModuleName = - 'This is a very long module name that exceeds fifty characters and should be truncated' - const modules = [longModuleName] - - render() - - const truncatedButton = screen.getByRole('button', { - name: /This is a very long module name that exceeds/, - }) - expect(truncatedButton).toHaveAttribute('title', longModuleName) - }) - - it('does not add title attribute for non-truncated modules', () => { - const shortModuleName = 'Short Module' - const modules = [shortModuleName] - - render() - - const button = screen.getByRole('button', { name: `Module: ${shortModuleName}` }) - expect(button).not.toHaveAttribute('title') - }) - }) - - describe('Module Button Properties', () => { - it('renders module buttons with correct classes', () => { - const modules = ['Test Module'] - render() - - const button = screen.getByRole('button', { name: 'Module: Test Module' }) - expect(button).toHaveClass( - 'rounded-lg', - 'border', - 'border-gray-400', - 'px-3', - 'py-1', - 'text-sm', - 'transition-all', - 'duration-200', - 'ease-in-out', - 'hover:scale-105', - 'hover:bg-gray-200', - 'dark:border-gray-300', - 'dark:hover:bg-gray-700' - ) - }) - - it('sets correct button type', () => { - const modules = ['Test Module'] - render() - - const button = screen.getByRole('button', { name: 'Module: Test Module' }) - expect(button).toHaveAttribute('type', 'button') - }) - - it('generates unique keys for modules with same name', () => { - const modules = ['Same Name', 'Same Name', 'Different Name'] - render() - - const sameNameButtons = screen.getAllByText('Same Name') - expect(sameNameButtons).toHaveLength(2) - expect(screen.getByText('Different Name')).toBeInTheDocument() - }) - }) - - describe('Container Structure', () => { - it('renders with correct container classes', () => { - const modules = ['Module 1'] - const { container } = render() - - const outerDiv = container.firstChild as HTMLElement - expect(outerDiv).toHaveClass('mt-3') - - const innerDiv = outerDiv.firstChild as HTMLElement - expect(innerDiv).toHaveClass('flex', 'flex-wrap', 'items-center', 'gap-2') - }) - }) - - describe('Edge Cases', () => { - it('handles modules with empty strings', () => { - const modules = ['', 'Valid Module', ''] - render() - - const buttons = screen.getAllByRole('button') - // Should render 3 buttons (including empty string ones) - expect(buttons).toHaveLength(3) - expect(screen.getByText('Valid Module')).toBeInTheDocument() - }) - - it('handles exactly 6 modules (edge case for show more)', () => { - const modules = Array.from({ length: 6 }, (_, i) => `Module ${i + 1}`) - render() - - // First 5 should be visible - expect(screen.getByText('Module 5')).toBeInTheDocument() - expect(screen.queryByText('Module 6')).not.toBeInTheDocument() - - // Should show "Show more" button - expect(screen.getByText('Show more')).toBeInTheDocument() - - // Click to expand - fireEvent.click(screen.getByText('Show more')) - expect(screen.getByText('Module 6')).toBeInTheDocument() - }) - }) -}) diff --git a/frontend/src/components/Card.tsx b/frontend/src/components/Card.tsx index 61dcc91886..26d785a1d5 100644 --- a/frontend/src/components/Card.tsx +++ b/frontend/src/components/Card.tsx @@ -11,7 +11,6 @@ import ActionButton from 'components/ActionButton' import ContributorAvatar from 'components/ContributorAvatar' import DisplayIcon from 'components/DisplayIcon' import Markdown from 'components/MarkdownWrapper' -import ModuleList from 'components/ModuleList' const Card = ({ title, @@ -23,7 +22,6 @@ const Card = ({ button, projectName, projectLink, - modules, social, tooltipLabel, timeline, @@ -103,8 +101,6 @@ const Card = ({ {/* Project summary */} - {/* Modules section (if available) */} -
{/* Social icons section */} {social && social.length > 0 && ( diff --git a/frontend/src/components/ModuleList.tsx b/frontend/src/components/ModuleList.tsx deleted file mode 100644 index 0745199c23..0000000000 --- a/frontend/src/components/ModuleList.tsx +++ /dev/null @@ -1,58 +0,0 @@ -import { faChevronDown, faChevronUp } from '@fortawesome/free-solid-svg-icons' -import { FontAwesomeIcon } from '@fortawesome/react-fontawesome' -import { Button } from '@heroui/button' -import React, { useState } from 'react' - -interface ModuleListProps { - modules: string[] -} - -const ModuleList: React.FC = ({ modules }) => { - const [showAll, setShowAll] = useState(false) - - if (!modules || modules.length === 0) return null - - const displayedModules = showAll ? modules : modules.slice(0, 5) - - return ( -
-
- {displayedModules.map((module, index) => { - const displayText = module.length > 50 ? `${module.slice(0, 50)}...` : module - return ( - - ) - })} - - {modules.length > 5 && ( - - )} -
-
- ) -} - -export default ModuleList diff --git a/frontend/src/types/card.ts b/frontend/src/types/card.ts index 482761b65c..67c276df11 100644 --- a/frontend/src/types/card.ts +++ b/frontend/src/types/card.ts @@ -22,7 +22,6 @@ export type CardProps = { level?: Level projectLink?: string projectName?: string - modules?: string[] social?: { title: string; icon: string; url: string }[] summary: string title: string