Skip to content
Merged
Show file tree
Hide file tree
Changes from 3 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
6 changes: 6 additions & 0 deletions frontend/__tests__/e2e/pages/About.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -84,4 +84,10 @@ test.describe('About Page', () => {
await newPage.waitForLoadState()
expect(newPage.url()).toContain('/members/')
})

test('breadcrumb renders correct segments on /about', async ({ page }) => {
const breadcrumb = page.locator('[aria-label="breadcrumb"]')
await expect(breadcrumb).toBeVisible()
await expect(breadcrumb.getByText('About')).toBeVisible()
})
})
7 changes: 7 additions & 0 deletions frontend/__tests__/e2e/pages/Chapters.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -54,4 +54,11 @@ test.describe('Chapters Page', () => {
await contributeButton.click()
await expect(page).toHaveURL('chapters/chapter_1')
})

test('breadcrumb renders correct segments on /chapters', async ({ page }) => {
const breadcrumb = page.locator('[aria-label="breadcrumb"]')
await expect(breadcrumb).toBeVisible()
await expect(breadcrumb.getByText('Home')).toBeVisible()
await expect(breadcrumb.getByText('Chapters')).toBeVisible()
})
})
7 changes: 7 additions & 0 deletions frontend/__tests__/e2e/pages/Committees.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -54,4 +54,11 @@ test.describe('Committees Page', () => {
await contributeButton.click()
await expect(page).toHaveURL('/committees/committee_1')
})

test('breadcrumb renders correct segments on /committees', async ({ page }) => {
const breadcrumb = page.locator('[aria-label="breadcrumb"]')
await expect(breadcrumb).toBeVisible()
await expect(breadcrumb.getByText('Home')).toBeVisible()
await expect(breadcrumb.getByText('Committees')).toBeVisible()
})
})
5 changes: 5 additions & 0 deletions frontend/__tests__/e2e/pages/Contribute.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -71,4 +71,9 @@ test.describe('Contribute Page', () => {
await CloseButton.click()
await expect(contributeButton).toBeVisible()
})
test('breadcrumb renders correct segments on /contribute', async ({ page }) => {
const breadcrumb = page.locator('[aria-label="breadcrumb"]')
await expect(breadcrumb).toBeVisible()
await expect(breadcrumb.getByText('Contribute')).toBeVisible()
})
Copy link
Contributor

Choose a reason for hiding this comment

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

🛠️ Refactor suggestion

Test coverage can be improved for breadcrumb verification.

While the test verifies that the breadcrumb is visible and contains the 'Contribute' segment, it's missing some important checks:

  1. It doesn't verify the 'Home' segment that should also be present
  2. It doesn't verify that the 'Home' link points to the root path ('/')

Consider enhancing this test to align with the more comprehensive approach used in the Organizations.spec.ts:

  test('breadcrumb renders correct segments on /contribute', async ({ page }) => {
    const breadcrumb = page.locator('[aria-label="breadcrumb"]')
    await expect(breadcrumb).toBeVisible()
+   await expect(breadcrumb.getByText('Home')).toBeVisible()
    await expect(breadcrumb.getByText('Contribute')).toBeVisible()
+   const homeLink = breadcrumb.locator('a', { hasText: 'Home' })
+   await expect(homeLink).toHaveAttribute('href', '/')
  })
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
test('breadcrumb renders correct segments on /contribute', async ({ page }) => {
const breadcrumb = page.locator('[aria-label="breadcrumb"]')
await expect(breadcrumb).toBeVisible()
await expect(breadcrumb.getByText('Contribute')).toBeVisible()
})
test('breadcrumb renders correct segments on /contribute', async ({ page }) => {
const breadcrumb = page.locator('[aria-label="breadcrumb"]')
await expect(breadcrumb).toBeVisible()
await expect(breadcrumb.getByText('Home')).toBeVisible()
await expect(breadcrumb.getByText('Contribute')).toBeVisible()
const homeLink = breadcrumb.locator('a', { hasText: 'Home' })
await expect(homeLink).toHaveAttribute('href', '/')
})

})
9 changes: 9 additions & 0 deletions frontend/__tests__/e2e/pages/Organizations.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -40,4 +40,13 @@ test.describe('Organization Page', () => {
await expect(page.getByText('1k')).toBeVisible()
await expect(page.getByText('1.5k')).toBeVisible()
})

test('breadcrumb renders correct segments on /organizations', async ({ page }) => {
const breadcrumb = page.locator('[aria-label="breadcrumb"]')
await expect(breadcrumb).toBeVisible()
await expect(breadcrumb.getByText('Home')).toBeVisible()
await expect(breadcrumb.getByText('Organizations')).toBeVisible()
const homeLink = breadcrumb.locator('a', { hasText: 'Home' })
await expect(homeLink).toHaveAttribute('href', '/')
})
})
6 changes: 6 additions & 0 deletions frontend/__tests__/e2e/pages/Projects.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -53,4 +53,10 @@ test.describe('Projects Page', () => {
await contributeButton.click()
await expect(page).toHaveURL('projects/project_1')
})
test('breadcrumb renders correct segments on /projects', async ({ page }) => {
const breadcrumb = page.locator('[aria-label="breadcrumb"]')
await expect(breadcrumb).toBeVisible()
await expect(breadcrumb.getByText('Home')).toBeVisible()
await expect(breadcrumb.getByText('Projects')).toBeVisible()
})
})
6 changes: 6 additions & 0 deletions frontend/__tests__/e2e/pages/Users.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -59,4 +59,10 @@ test.describe('Users Page', () => {
await expect(page.getByText('1k')).toBeVisible()
await expect(page.getByText('2k')).toBeVisible()
})
test('breadcrumb renders correct segments', async ({ page }) => {
const breadcrumb = page.locator('[aria-label="breadcrumb"]')
await expect(breadcrumb).toBeVisible()
await expect(breadcrumb.getByText('Home')).toBeVisible()
await expect(breadcrumb.getByText('Members')).toBeVisible()
})
})
42 changes: 42 additions & 0 deletions frontend/__tests__/unit/components/BreadCrumbs.test.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
import { render, screen } from '@testing-library/react'
import { usePathname } from 'next/navigation'
import BreadCrumbs from 'components/BreadCrumbs'
import '@testing-library/jest-dom'

jest.mock('next/navigation', () => ({
usePathname: jest.fn(),
}))

describe('BreadCrumb', () => {
afterEach(() => {
jest.clearAllMocks()
})

test('does not render on root path "/"', () => {
;(usePathname as jest.Mock).mockReturnValue('/')

render(<BreadCrumbs />)
expect(screen.queryByText('Home')).not.toBeInTheDocument()
})

test('renders breadcrumb with multiple segments', () => {
;(usePathname as jest.Mock).mockReturnValue('/dashboard/users/profile')

render(<BreadCrumbs />)

expect(screen.getByText('Home')).toBeInTheDocument()
expect(screen.getByText('Dashboard')).toBeInTheDocument()
expect(screen.getByText('Users')).toBeInTheDocument()
expect(screen.getByText('Profile')).toBeInTheDocument()
})

test('disables the last segment (non-clickable)', () => {
;(usePathname as jest.Mock).mockReturnValue('/settings/account')

render(<BreadCrumbs />)

const lastSegment = screen.getByText('Account')
expect(lastSegment).toBeInTheDocument()
expect(lastSegment).not.toHaveAttribute('href')
})
})
2 changes: 2 additions & 0 deletions frontend/src/app/layout.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ import type { Metadata } from 'next'
import { Geist, Geist_Mono } from 'next/font/google'
import React from 'react'
import { Providers } from 'wrappers/provider'
import BreadCrumbs from 'components/BreadCrumbs'
import Footer from 'components/Footer'

import Header from 'components/Header'
Expand Down Expand Up @@ -41,6 +42,7 @@ export default function RootLayout({
>
<Providers>
<Header />
<BreadCrumbs />
{children}
<Footer />
<ScrollToTop />
Expand Down
67 changes: 67 additions & 0 deletions frontend/src/components/BreadCrumbs.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,67 @@
'use client'

import { faChevronRight } from '@fortawesome/free-solid-svg-icons'
import { FontAwesomeIcon } from '@fortawesome/react-fontawesome'
import { Breadcrumbs, BreadcrumbItem } from '@heroui/react'
import Link from 'next/link'
import { usePathname } from 'next/navigation'

const capitalize = (str: string) => str.charAt(0).toUpperCase() + str.slice(1).replace(/-/g, ' ')

export default function BreadCrumbs() {
const pathname = usePathname()
const segments = pathname.split('/').filter(Boolean)

if (pathname === '/') return null

return (
<div className="absolute bottom-2 left-0 top-1 mt-16 w-full py-2">
<div className="w-full px-8 sm:px-8 md:px-8 lg:px-8">
<Breadcrumbs
aria-label="breadcrumb"
separator={
<FontAwesomeIcon
icon={faChevronRight}
className="mx-1 text-xs text-gray-400 dark:text-gray-500"
/>
}
className="text-gray-800 dark:text-gray-200"
itemClasses={{
base: 'transition-colors duration-200',
item: 'text-sm font-medium',
separator: 'flex items-center',
}}
>
<BreadcrumbItem>
<Link href="/" className="hover:text-blue-700 hover:underline dark:text-blue-400">
Home
</Link>
</BreadcrumbItem>

{segments.map((segment, index) => {
const href = '/' + segments.slice(0, index + 1).join('/')
const label = capitalize(segment)
const isLast = index === segments.length - 1

return (
<BreadcrumbItem key={href} isDisabled={isLast}>
{isLast ? (
<span className="cursor-default font-semibold text-gray-600 dark:text-gray-300">
{label}
</span>
) : (
<Link
href={href}
className="hover:text-blue-700 hover:underline dark:text-blue-400"
>
{label}
</Link>
)}
</BreadcrumbItem>
)
})}
</Breadcrumbs>
</div>
</div>
)
}