Skip to content
Open
Show file tree
Hide file tree
Changes from 2 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
17 changes: 11 additions & 6 deletions frontend/__tests__/unit/components/ModuleList.test.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -5,12 +5,17 @@ import ModuleList from 'components/ModuleList'

// Mock FontAwesome icons
jest.mock('@fortawesome/react-fontawesome', () => ({
FontAwesomeIcon: ({ icon, className }: { icon: unknown; className?: string }) => (
<span
data-testid={`icon-${icon === faChevronDown ? 'chevron-down' : icon === faChevronUp ? 'chevron-up' : 'unknown'}`}
className={className}
/>
),
FontAwesomeIcon: ({ icon, className }: { icon: unknown; className?: string }) => {
let iconName = 'unknown'

if (icon === faChevronDown) {
iconName = 'chevron-down'
} else if (icon === faChevronUp) {
iconName = 'chevron-up'
}

return <span data-testid={`icon-${iconName}`} className={className} />
},
}))

// Mock HeroUI Button component
Expand Down
17 changes: 11 additions & 6 deletions frontend/__tests__/unit/components/ProgramCard.test.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -7,12 +7,17 @@ import type { Program } from 'types/mentorship'
import ProgramCard from 'components/ProgramCard'

jest.mock('@fortawesome/react-fontawesome', () => ({
FontAwesomeIcon: ({ icon, className }: { icon: unknown; className?: string }) => (
<span
data-testid={`icon-${icon === faEye ? 'eye' : icon === faEdit ? 'edit' : 'unknown'}`}
className={className}
/>
),
FontAwesomeIcon: ({ icon, className }: { icon: unknown; className?: string }) => {
let iconName = 'unknown'

if (icon === faEye) {
iconName = 'eye'
} else if (icon === faEdit) {
iconName = 'edit'
}

return <span data-testid={`icon-${iconName}`} className={className} />
},
}))

jest.mock('components/ActionButton', () => ({
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -148,6 +148,18 @@ describe('MetricsPage', () => {
})
})

test('SortableColumnHeader applies correct alignment classes', async () => {
render(<MetricsPage />)

// Find the "Stars" sort button
const sortButton = await screen.findByTitle('Sort by Stars')
const wrapperDiv = sortButton.closest('div')

// "Stars" uses align="center" in the MetricsPage
expect(wrapperDiv).toHaveClass('justify-center')
expect(sortButton).toHaveClass('text-center')
})

test('handles sorting state and URL updates', async () => {
const mockReplace = jest.fn()
const { useRouter, useSearchParams } = jest.requireMock('next/navigation')
Expand Down Expand Up @@ -236,4 +248,6 @@ describe('MetricsPage', () => {
expect(nextPageButton).toBeInTheDocument()
fireEvent.click(nextPageButton)
})

//test for helper functions
})
33 changes: 33 additions & 0 deletions frontend/__tests__/unit/utils/milestoneProgress.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
import { faCircleCheck, faClock, faUserGear } from '@fortawesome/free-solid-svg-icons'

import { getMilestoneProgressIcon, getMilestoneProgressText } from 'utils/milestoneProgress'

describe('milestone progress helpers', () => {
describe('getMilestoneProgressText', () => {
test('returns "Completed" when progress is 100', () => {
expect(getMilestoneProgressText(100)).toBe('Completed')
})

test('returns "In Progress" when progress is between 1 and 99', () => {
expect(getMilestoneProgressText(50)).toBe('In Progress')
})

test('returns "Not Started" when progress is 0', () => {
expect(getMilestoneProgressText(0)).toBe('Not Started')
})
})

describe('getMilestoneProgressIcon', () => {
test('returns faCircleCheck when progress is 100', () => {
expect(getMilestoneProgressIcon(100)).toBe(faCircleCheck)
})

test('returns faUserGear when progress is between 1 and 99', () => {
expect(getMilestoneProgressIcon(50)).toBe(faUserGear)
})

test('returns faClock when progress is 0', () => {
expect(getMilestoneProgressIcon(0)).toBe(faClock)
})
})
})
20 changes: 3 additions & 17 deletions frontend/src/app/about/page.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,6 @@ import { useQuery } from '@apollo/client/react'
import {
faCircleCheck,
faClock,
faUserGear,
faMapSigns,
faScroll,
faUsers,
Expand Down Expand Up @@ -34,6 +33,7 @@ import {
projectTimeline,
projectStory,
} from 'utils/aboutData'
import { getMilestoneProgressIcon, getMilestoneProgressText } from 'utils/milestoneProgress'
import AnchorTitle from 'components/AnchorTitle'
import AnimatedCounter from 'components/AnimatedCounter'
import Leaders from 'components/Leaders'
Expand Down Expand Up @@ -218,28 +218,14 @@ const About = () => {
</Link>
<Tooltip
closeDelay={100}
content={
milestone.progress === 100
? 'Completed'
: milestone.progress > 0
? 'In Progress'
: 'Not Started'
}
content={getMilestoneProgressText(milestone.progress)}
id={`tooltip-state-${index}`}
delay={100}
placement="top"
showArrow
>
<span className="absolute top-0 right-0 text-xl text-gray-400">
<FontAwesomeIcon
icon={
milestone.progress === 100
? faCircleCheck
: milestone.progress > 0
? faUserGear
: faClock
}
/>
<FontAwesomeIcon icon={getMilestoneProgressIcon(milestone.progress)} />
</span>
</Tooltip>
</div>
Expand Down
12 changes: 8 additions & 4 deletions frontend/src/app/global-error.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -64,10 +64,14 @@ export class AppError extends Error {
}

export const handleAppError = (error: unknown) => {
const appError =
error instanceof AppError
? error
: new AppError(500, error instanceof Error ? error.message : ERROR_CONFIGS['500'].message)
let appError: AppError

if (error instanceof AppError) {
appError = error
} else {
const message = error instanceof Error ? error.message : ERROR_CONFIGS['500'].message
appError = new AppError(500, message)
}

if (appError.statusCode >= 500) {
Sentry.captureException(error instanceof Error ? error : appError)
Expand Down
35 changes: 30 additions & 5 deletions frontend/src/app/projects/dashboard/metrics/page.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -77,10 +77,35 @@ const SortableColumnHeader: FC<{
}
}

const alignmentClass =
align === 'center' ? 'justify-center' : align === 'right' ? 'justify-end' : 'justify-start'
const textAlignClass =
align === 'center' ? 'text-center' : align === 'right' ? 'text-right' : 'text-left'
const alignmentClass = (() => {
if (align === 'center') {
return 'justify-center'
} else if (align === 'right') {
return 'justify-end'
} else {
return 'justify-start'
}
})()

const textAlignClass = (() => {
if (align === 'center') {
return 'text-center'
} else if (align === 'right') {
return 'text-right'
} else {
return 'text-left'
}
})()

const fontAwesomeIconType = (() => {
if (isActiveSortDesc) {
return faSortDown
} else if (isActiveSortAsc) {
return faSortUp
} else {
return faSort
}
})()

return (
<div className={`flex items-center gap-1 ${alignmentClass}`}>
Expand All @@ -93,7 +118,7 @@ const SortableColumnHeader: FC<{
>
<span className="truncate">{label}</span>
<FontAwesomeIcon
icon={isActiveSortDesc ? faSortDown : isActiveSortAsc ? faSortUp : faSort}
icon={fontAwesomeIconType}
className={`h-3 w-3 ${isActive ? 'text-blue-600' : 'text-gray-400'}`}
/>
</button>
Expand Down
19 changes: 12 additions & 7 deletions frontend/src/components/CardDetailsPage.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -73,6 +73,17 @@ const DetailsCard = ({
}: DetailsCardProps) => {
const { data } = useSession()
const router = useRouter()

const secondaryCardStyles = (() => {
if (type === 'program' || type === 'module') {
return 'gap-2 md:col-span-7'
} else if (type !== 'chapter') {
return 'gap-2 md:col-span-5'
} else {
return 'gap-2 md:col-span-3'
}
})()
Comment on lines 78 to 85
Copy link
Collaborator

@kasya kasya Nov 6, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Let's not introduce negated conditions. This is also one of the issues we happen to have quite a few of in SonarQube and something that makes the code less readable.

Suggested change
const secondaryCardStyles = (() => {
if (type === 'program' || type === 'module') {
return 'gap-2 md:col-span-7'
} else if (type !== 'chapter') {
return 'gap-2 md:col-span-5'
} else {
return 'gap-2 md:col-span-3'
}
})()
const secondaryCardStyles = (() => {
if (type === 'program' || type === 'module') {
return 'gap-2 md:col-span-7'
}
if (type === 'chapter') {
return 'gap-2 md:col-span-3'
}
return 'gap-2 md:col-span-5'
})()

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@kasya I'have updated the code as you have suggested.

The issue you are talking about is the one which is failing CI/CD ?
or I have to fix something else


return (
<div className="min-h-screen bg-white p-8 text-gray-600 dark:bg-[#212529] dark:text-gray-300">
<div className="mx-auto max-w-6xl">
Expand Down Expand Up @@ -122,13 +133,7 @@ const DetailsCard = ({
<SecondaryCard
icon={faRectangleList}
title={<AnchorTitle title={`${upperFirst(type)} Details`} />}
className={
type === 'program' || type === 'module'
? 'gap-2 md:col-span-7'
: type !== 'chapter'
? 'gap-2 md:col-span-5'
: 'gap-2 md:col-span-3'
}
className={secondaryCardStyles}
>
{details?.map((detail) =>
detail?.label === 'Leaders' ? (
Expand Down
18 changes: 11 additions & 7 deletions frontend/src/components/ProgramCard.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,16 @@ const ProgramCard: React.FC<ProgramCardProps> = ({ program, onEdit, onView, acce
? `${program.description.slice(0, 100)}...`
: program.description || 'No description available.'

const dateInfo = (() => {
if (program.startedAt && program.endedAt) {
return `${formatDate(program.startedAt)} – ${formatDate(program.endedAt)}`
} else if (program.startedAt) {
return `Started: ${formatDate(program.startedAt)}`
} else {
return 'No dates set'
}
})()

return (
<div className="h-64 w-80 rounded-[5px] border border-gray-400 bg-white p-6 text-left transition-transform duration-300 hover:scale-[1.02] hover:brightness-105 dark:border-gray-600 dark:bg-gray-800">
<div className="flex h-full flex-col justify-between">
Expand All @@ -47,13 +57,7 @@ const ProgramCard: React.FC<ProgramCardProps> = ({ program, onEdit, onView, acce
</span>
)}
</div>
<div className="mb-2 text-xs text-gray-600 dark:text-gray-400">
{program.startedAt && program.endedAt
? `${formatDate(program.startedAt)} – ${formatDate(program.endedAt)}`
: program.startedAt
? `Started: ${formatDate(program.startedAt)}`
: 'No dates set'}
</div>
<div className="mb-2 text-xs text-gray-600 dark:text-gray-400">{dateInfo}</div>
<p className="mb-4 text-sm text-gray-700 dark:text-gray-300">{description}</p>
</div>

Expand Down
13 changes: 10 additions & 3 deletions frontend/src/hooks/useSearchPage.ts
Original file line number Diff line number Diff line change
Expand Up @@ -82,10 +82,17 @@ export function useSearchPage<T>({

const fetchData = async () => {
try {
let computedIndexName = indexName

const hasValidSort = sortBy && sortBy !== 'default' && sortBy[0] !== 'default'

if (hasValidSort) {
const orderSuffix = order && order !== '' ? `_${order}` : ''
computedIndexName = `${indexName}_${sortBy}${orderSuffix}`
}

const response = await fetchAlgoliaData<T>(
sortBy && sortBy !== 'default' && sortBy[0] !== 'default'
? `${indexName}_${sortBy}${order && order !== '' ? `_${order}` : ''}`
: indexName,
computedIndexName,
searchQuery,
currentPage,
hitsPerPage
Expand Down
21 changes: 21 additions & 0 deletions frontend/src/utils/milestoneProgress.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
import { faCircleCheck, faClock, faUserGear } from '@fortawesome/free-solid-svg-icons'

export const getMilestoneProgressText = (progress: number): string => {
if (progress === 100) {
return 'Completed'
} else if (progress > 0) {
return 'In Progress'
} else {
return 'Not Started'
}
}

export const getMilestoneProgressIcon = (progress: number) => {
if (progress === 100) {
return faCircleCheck
} else if (progress > 0) {
return faUserGear
} else {
return faClock
}
}