Skip to content
Merged
Show file tree
Hide file tree
Changes from 7 commits
Commits
Show all changes
20 commits
Select commit Hold shift + click to select a range
1e0cd00
fix: add contrast improvement styles to global error component
sameersharmadev Dec 11, 2025
17140aa
Address make-check issues
kasya Dec 12, 2025
8992f06
Merge branch 'OWASP:main' into main
sameersharmadev Dec 14, 2025
e5125ac
migrate to react-icons
sameersharmadev Dec 10, 2025
6656fb3
apply code quality suggestions
sameersharmadev Dec 13, 2025
69196c8
update unit tests
sameersharmadev Dec 14, 2025
6af9f8e
implement coderabbit suggestions
sameersharmadev Dec 14, 2025
ccd0c23
chore: update pnpm-lock.yaml, fix NavButton test, migrate ChapterMap …
sameersharmadev Dec 14, 2025
071ca64
update frontend/src/components/ChapterMap
sameersharmadev Dec 14, 2025
58af16d
restore calendarbutton logic lost during bad merge
sameersharmadev Dec 15, 2025
a09f121
address quality gate issues
sameersharmadev Dec 15, 2025
0271429
chore: yet another code quality gate fix
sameersharmadev Dec 15, 2025
847444c
chore: address coderabbit review comments
sameersharmadev Dec 15, 2025
2e9de32
address make check issues
sameersharmadev Dec 16, 2025
e6c8d67
Revert accidental modification to cpell/Makefile
sameersharmadev Dec 16, 2025
92ebcd4
address final coderabbit and sonarqube reviews
sameersharmadev Dec 16, 2025
5030680
fix typescript definition for optional icon prop
sameersharmadev Dec 16, 2025
e8ca5aa
Update icons style and size
kasya Dec 17, 2025
c3336ee
Merge branch 'main' of github.com:OWASP/Nest into pr/sameersharmadev/…
kasya Dec 17, 2025
c93779c
Update code
arkid15r Dec 17, 2025
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
6 changes: 1 addition & 5 deletions frontend/__tests__/unit/components/AnchorTitle.test.tsx
Original file line number Diff line number Diff line change
@@ -1,11 +1,7 @@
import { library } from '@fortawesome/fontawesome-svg-core'
import { faLink } from '@fortawesome/free-solid-svg-icons'
import { screen, render, fireEvent, waitFor } from '@testing-library/react'
import slugifyMock from 'utils/slugify'
import AnchorTitle from 'components/AnchorTitle'

library.add(faLink)

jest.mock('utils/slugify', () => ({
__esModule: true,
default: jest.fn((str: string) =>
Expand Down Expand Up @@ -101,7 +97,7 @@ describe('AnchorTitle Component', () => {
expect(titleElement).toHaveAttribute('data-anchor-title', 'true')
})

it('renders FontAwesome link icon', () => {
it('renders react-icons link icon', () => {
render(<AnchorTitle title="Test" />)
const link = screen.getByRole('link')
const icon = link.querySelector('svg')
Expand Down
29 changes: 7 additions & 22 deletions frontend/__tests__/unit/components/Badges.test.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -2,31 +2,16 @@ import { render, screen } from '@testing-library/react'
import React from 'react'
import Badges from 'components/Badges'

jest.mock('wrappers/FontAwesomeIconWrapper', () => {
const RealWrapper = jest.requireActual('wrappers/FontAwesomeIconWrapper').default

const getName = (icon) => {
if (!icon) return 'medal'
if (typeof icon === 'string') {
const m = icon.match(/fa-([a-z0-9-]+)$/i)
if (m) return m[1]
const last = icon.trim().split(/\s+/).pop() || ''
return last.replace(/^fa-/, '') || 'medal'
}
if (Array.isArray(icon) && icon.length >= 2) return String(icon[1])
if (icon && typeof icon === 'object') return icon.iconName || String(icon[1] ?? 'medal')
return 'medal'
}

return function MockFontAwesomeIconWrapper(props) {
const name = getName(props.icon)
jest.mock('wrappers/IconWrapper', () => ({
IconWrapper: ({ icon, className, ...props }: { icon: React.ComponentType<{ className?: string }>, className?: string }) => {
const iconName = icon.name?.toLowerCase().replace('fa', '') || 'medal'
return (
<div data-testid={`icon-${name}`}>
<RealWrapper {...props} />
<div data-testid="badge-icon" data-icon={iconName} className={className} {...props}>
<svg />
</div>
)
}
})
},
}))

jest.mock('@heroui/tooltip', () => ({
Tooltip: ({
Expand Down
9 changes: 2 additions & 7 deletions frontend/__tests__/unit/components/BarChart.test.tsx
Original file line number Diff line number Diff line change
@@ -1,12 +1,8 @@
import { library } from '@fortawesome/fontawesome-svg-core'
import { faFire } from '@fortawesome/free-solid-svg-icons'
import { FaFire } from 'react-icons/fa'
import { render, screen } from '@testing-library/react'
import '@testing-library/jest-dom'
import React from 'react'

// Register FontAwesome icon
library.add(faFire)

// Mock react-apexcharts completely
jest.mock('react-apexcharts', () => {
return function MockChart(props: {
Expand Down Expand Up @@ -170,8 +166,7 @@ describe('<BarChart />', () => {
})

it('renders with custom icon when provided', () => {
// cspell:ignore fas
renderWithTheme(<BarChart {...mockProps} icon={['fas', 'fire']} />)
renderWithTheme(<BarChart {...mockProps} icon={FaFire} />)
expect(screen.getByTestId('anchor-title')).toHaveTextContent('Calories Burned')
expect(screen.getByTestId('card-icon')).toBeInTheDocument()
})
Expand Down
14 changes: 6 additions & 8 deletions frontend/__tests__/unit/components/CalendarButton.test.tsx
Original file line number Diff line number Diff line change
@@ -1,5 +1,4 @@
import { faCalendarDay, faCalendarPlus } from '@fortawesome/free-solid-svg-icons'
import { FontAwesomeIcon } from '@fortawesome/react-fontawesome'
import { FaCalendarDay, FaCalendarPlus } from 'react-icons/fa6'
import { render, screen } from '@testing-library/react'
import CalendarButton from 'components/CalendarButton'

Expand All @@ -25,7 +24,7 @@ describe('CalendarButton', () => {
expect(link.tagName).toBe('A')
})

it('renders default FontAwesome calendar-plus icon', () => {
it('renders default calendar-plus icon', () => {
render(<CalendarButton event={mockEvent} />)
const svg = document.querySelector('svg')
expect(svg).toBeInTheDocument()
Expand All @@ -35,7 +34,7 @@ describe('CalendarButton', () => {
render(
<CalendarButton
event={mockEvent}
icon={<FontAwesomeIcon icon={faCalendarDay} data-testid="custom-icon" />}
icon={<FaCalendarDay data-testid="custom-icon" />}
/>
)
expect(screen.getByTestId('custom-icon')).toBeInTheDocument()
Expand Down Expand Up @@ -185,11 +184,11 @@ describe('CalendarButton', () => {
})

describe('icon prop extensibility', () => {
it('accepts FontAwesome icon as JSX', () => {
it('accepts icon as JSX', () => {
render(
<CalendarButton
event={mockEvent}
icon={<FontAwesomeIcon icon={faCalendarPlus} className="custom-icon-class" />}
icon={<FaCalendarPlus className="custom-icon-class" />}
/>
)
const svg = document.querySelector('svg')
Expand Down Expand Up @@ -322,8 +321,7 @@ describe('CalendarButton', () => {
<CalendarButton
event={mockEvent}
icon={
<FontAwesomeIcon
icon={faCalendarPlus}
<FaCalendarPlus
className="h-6 w-6 text-blue-500 hover:text-blue-700"
/>
}
Expand Down
53 changes: 23 additions & 30 deletions frontend/__tests__/unit/components/Card.test.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ import { screen, render, fireEvent } from '@testing-library/react'
import { ReactNode } from 'react'
import type { CardProps } from 'types/card'
import Card from 'components/Card'
import { FaLeaf, FaFire, FaCrown, FaStar, FaGithub, FaTwitter } from 'react-icons/fa6'

// Define proper types for mock props
interface MockLinkProps {
Expand All @@ -12,11 +13,6 @@ interface MockLinkProps {
className?: string
}

interface MockFontAwesomeIconProps {
icon: unknown
className?: string
}

interface MockTooltipProps {
children: ReactNode
content: string
Expand Down Expand Up @@ -63,14 +59,6 @@ jest.mock('next/link', () => {
}
})

jest.mock('@fortawesome/react-fontawesome', () => ({
FontAwesomeIcon: ({ icon, className }: MockFontAwesomeIconProps) => (
<span data-testid="font-awesome-icon" className={className}>
{String(icon)}
</span>
),
}))

jest.mock('@heroui/tooltip', () => ({
Tooltip: ({ children, content, id }: MockTooltipProps) => (
<div data-testid="tooltip" title={content} id={id}>
Expand All @@ -79,15 +67,16 @@ jest.mock('@heroui/tooltip', () => ({
),
}))

jest.mock('wrappers/FontAwesomeIconWrapper', () => {
return function FontAwesomeIconWrapper({ icon, className }: MockFontAwesomeIconProps) {
jest.mock('wrappers/IconWrapper', () => ({
IconWrapper: ({ icon, className }: { icon: React.ComponentType<{ className?: string }>, className?: string }) => {
const iconName = icon?.name?.toLowerCase().replace('fa', '') || 'icon'
return (
<span data-testid="font-awesome-wrapper" className={className}>
{String(icon)}
<span data-testid="icon-wrapper" data-icon={iconName} className={className}>
<svg />
</span>
)
}
})
},
}))

jest.mock('components/ActionButton', () => {
return function ActionButton({ children, onClick, tooltipLabel, url }: MockActionButtonProps) {
Expand Down Expand Up @@ -131,7 +120,11 @@ jest.mock('components/MarkdownWrapper', () => {
})

jest.mock('utils/urlIconMappings', () => ({
getSocialIcon: jest.fn().mockReturnValue('mocked-social-icon'),
getSocialIcon: jest.fn().mockReturnValue(({ className }: { className?: string }) => (
<span data-testid="social-icon" className={className}>
<svg />
</span>
)),
}))

jest.mock('utils/data', () => ({
Expand Down Expand Up @@ -179,12 +172,12 @@ describe('Card', () => {
it('conditionally renders level badge when provided', () => {
const propsWithLevel = {
...baseProps,
level: { level: 'Beginner', color: '#4CAF50', icon: 'leaf-icon' },
level: { level: 'Beginner', color: '#4CAF50', icon: FaLeaf },
}

render(<Card {...propsWithLevel} />)
expect(screen.getByTestId('tooltip')).toBeInTheDocument()
expect(screen.getByTestId('font-awesome-wrapper')).toBeInTheDocument()
expect(screen.getByTestId('icon-wrapper')).toBeInTheDocument()
})

it('does not render level badge when not provided', () => {
Expand Down Expand Up @@ -229,12 +222,12 @@ describe('Card', () => {
const propsWithSocial = {
...baseProps,
social: [
{ title: 'GitHub', url: 'https://github.com/test', icon: 'github' },
{ title: 'Twitter', url: 'https://twitter.com/test', icon: 'twitter' },
{ title: 'GitHub', url: 'https://github.com/test', icon: FaGithub },
{ title: 'Twitter', url: 'https://twitter.com/test', icon: FaTwitter },
],
}
render(<Card {...propsWithSocial} />)
expect(screen.getAllByTestId('font-awesome-icon')).toHaveLength(2)
expect(screen.getAllByTestId('social-icon')).toHaveLength(2)

const allLinks = screen.getAllByRole('link')
expect(allLinks.length).toBeGreaterThan(1)
Expand Down Expand Up @@ -296,7 +289,7 @@ describe('Card', () => {
it('applies different level badge colors based on props', () => {
const propsWithLevel = {
...baseProps,
level: { level: 'Advanced', color: '#FF5722', icon: 'fire-icon' },
level: { level: 'Advanced', color: '#FF5722', icon: FaFire },
}

render(<Card {...propsWithLevel} />)
Expand Down Expand Up @@ -400,7 +393,7 @@ describe('Card', () => {
it('has proper accessibility attributes', () => {
const propsWithTooltip = {
...baseProps,
level: { level: 'Intermediate', color: '#2196F3', icon: 'star-icon' },
level: { level: 'Intermediate', color: '#2196F3', icon: FaStar },
tooltipLabel: 'Click to contribute',
}

Expand Down Expand Up @@ -466,11 +459,11 @@ describe('Card', () => {
it('renders complete card with all optional props', () => {
const fullProps = {
...baseProps,
level: { level: 'Expert', color: '#9C27B0', icon: 'crown-icon' },
level: { level: 'Expert', color: '#9C27B0', icon: FaCrown },
icons: { react: 'active', typescript: 'active' },
projectName: 'Full Stack Project',
projectLink: 'https://fullstack.com',
social: [{ title: 'GitHub', url: 'https://github.com/full', icon: 'active' }],
social: [{ title: 'GitHub', url: 'https://github.com/full', icon: FaGithub }],
topContributors: [
{
login: 'expert',
Expand All @@ -486,7 +479,7 @@ describe('Card', () => {
expect(screen.getByTestId('tooltip')).toBeInTheDocument()
expect(screen.getAllByTestId('display-icon')).toHaveLength(2)
expect(screen.getByRole('link', { name: 'Full Stack Project' })).toBeInTheDocument()
expect(screen.getByTestId('font-awesome-icon')).toBeInTheDocument()
expect(screen.getByTestId('social-icon')).toBeInTheDocument()
expect(screen.getByTestId('contributor-avatar')).toBeInTheDocument()
})
})
43 changes: 15 additions & 28 deletions frontend/__tests__/unit/components/CardDetailsPage.test.tsx
Original file line number Diff line number Diff line change
@@ -1,10 +1,9 @@
import { faCode, faTags } from '@fortawesome/free-solid-svg-icons'
import { render, screen, cleanup } from '@testing-library/react'
import React from 'react'
import '@testing-library/jest-dom'
import type { DetailsCardProps } from 'types/card'
import CardDetailsPage from 'components/CardDetailsPage'

import { FaCode, FaTags } from 'react-icons/fa6'
jest.mock('next/link', () => {
const MockLink = ({
children,
Expand Down Expand Up @@ -56,31 +55,16 @@ jest.mock('next/image', () => ({
),
}))

jest.mock('@fortawesome/react-fontawesome', () => ({
FontAwesomeIcon: ({
icon,
className,
...props
}: {
icon: { iconName: string }
className?: string
[key: string]: unknown
}) => <span data-testid={`icon-${icon.iconName}`} className={className} {...props} />,
}))

jest.mock('utils/env.client', () => ({
IS_PROJECT_HEALTH_ENABLED: true,
}))

jest.mock('utils/urlIconMappings', () => ({
getSocialIcon: (url: string) => {
if (url?.includes('github')) {
return { iconName: 'github' }
const safe = encodeURIComponent(url)
return function MockSocialIcon(props: { className?: string }) {
return <span data-testid={`mock-social-icon-${safe}`} className={props.className} />
}
if (url?.includes('twitter')) {
return { iconName: 'twitter' }
}
return { iconName: 'link' }
},
}))
Comment thread
sameersharmadev marked this conversation as resolved.

Expand Down Expand Up @@ -134,13 +118,14 @@ jest.mock('components/HealthMetrics', () => ({
jest.mock('components/InfoBlock', () => ({
__esModule: true,
default: ({
icon: _icon, // ← Destructure icon and prefix with _ to mark as intentionally unused
pluralizedName,
unit,
value,
className,
...props
}: {
_icon: unknown
icon: unknown // ← Change _icon to icon in the type
pluralizedName?: string
unit?: string
value: number
Expand Down Expand Up @@ -276,9 +261,10 @@ jest.mock('components/SecondaryCard', () => ({
title,
children,
className,
icon,
...props
}: {
_icon: unknown
icon?: unknown
title: React.ReactNode
children: React.ReactNode
className?: string
Expand Down Expand Up @@ -314,11 +300,12 @@ jest.mock('components/ToggleableList', () => ({
__esModule: true,
default: ({
items,
icon,
label,
...props
}: {
items: string[]
_icon: unknown
icon: unknown
label: React.ReactNode
[key: string]: unknown
}) => (
Expand Down Expand Up @@ -412,13 +399,13 @@ describe('CardDetailsPage', () => {

const mockStats = [
{
icon: faCode,
icon: FaCode,
pluralizedName: 'repositories',
unit: '',
value: 10,
},
{
icon: faTags,
icon: FaTags,
pluralizedName: 'stars',
unit: '',
value: 100,
Expand Down Expand Up @@ -1329,9 +1316,9 @@ describe('CardDetailsPage', () => {

it('handles zero and negative values in stats', () => {
const statsWithZeroValues = [
{ icon: faCode, value: 0, unit: 'Star' },
{ icon: faTags, value: invalidValues.negativeNumber, unit: 'Issue' },
{ icon: faCode, value: invalidValues.nullValue, unit: 'Fork' },
{ icon: FaCode, value: 0, unit: 'Star' },
{ icon: FaTags, value: -10, unit: 'Issue' },
{ icon: FaCode, value: null, unit: 'Fork' },
]

expect(() =>
Expand Down
Loading
Loading