Skip to content

Commit 85af310

Browse files
Breadcrumb component implementation (OWASP#1397)
* breadcrumb component * updated code * added e2e * update code * updated code * Update code * Update tests --------- Co-authored-by: Arkadii Yakovets <[email protected]>
1 parent ff90054 commit 85af310

File tree

11 files changed

+162
-0
lines changed

11 files changed

+162
-0
lines changed
Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,16 @@
1+
import { Page, expect } from '@playwright/test'
2+
3+
export async function expectBreadCrumbsToBeVisible(page: Page, breadcrumbs: string[] = ['Home']) {
4+
const breadcrumbsContainer = page.locator('[aria-label="breadcrumb"]')
5+
6+
await expect(breadcrumbsContainer).toBeVisible()
7+
await expect(breadcrumbsContainer).toHaveCount(1)
8+
9+
for (const breadcrumb of breadcrumbs) {
10+
await expect(breadcrumbsContainer.getByText(breadcrumb)).toBeVisible()
11+
}
12+
13+
const allBreadcrumbs = await breadcrumbsContainer.locator('li').allTextContents()
14+
const visibleBreadcrumbs = allBreadcrumbs.filter((text) => text.trim() !== '')
15+
await expect(visibleBreadcrumbs).toEqual(breadcrumbs)
16+
}

frontend/__tests__/e2e/pages/About.spec.ts

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,4 @@
1+
import { expectBreadCrumbsToBeVisible } from '@e2e/helpers/expects'
12
import { test, expect } from '@playwright/test'
23
import { mockAboutData } from '@unit/data/mockAboutData'
34

@@ -84,4 +85,8 @@ test.describe('About Page', () => {
8485
await newPage.waitForLoadState()
8586
expect(newPage.url()).toContain('/members/')
8687
})
88+
89+
test('breadcrumb renders correct segments on /about', async ({ page }) => {
90+
await expectBreadCrumbsToBeVisible(page, ['Home', 'About'])
91+
})
8792
})

frontend/__tests__/e2e/pages/Chapters.spec.ts

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,4 @@
1+
import { expectBreadCrumbsToBeVisible } from '@e2e/helpers/expects'
12
import { test, expect } from '@playwright/test'
23
import { mockChapterData } from '@unit/data/mockChapterData'
34

@@ -54,4 +55,8 @@ test.describe('Chapters Page', () => {
5455
await contributeButton.click()
5556
await expect(page).toHaveURL('chapters/chapter_1')
5657
})
58+
59+
test('breadcrumb renders correct segments on /chapters', async ({ page }) => {
60+
await expectBreadCrumbsToBeVisible(page, ['Home', 'Chapters'])
61+
})
5762
})

frontend/__tests__/e2e/pages/Committees.spec.ts

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,4 @@
1+
import { expectBreadCrumbsToBeVisible } from '@e2e/helpers/expects'
12
import { test, expect } from '@playwright/test'
23
import { mockCommitteeData } from '@unit/data/mockCommitteeData'
34

@@ -54,4 +55,8 @@ test.describe('Committees Page', () => {
5455
await contributeButton.click()
5556
await expect(page).toHaveURL('/committees/committee_1')
5657
})
58+
59+
test('breadcrumb renders correct segments on /committees', async ({ page }) => {
60+
await expectBreadCrumbsToBeVisible(page, ['Home', 'Committees'])
61+
})
5762
})

frontend/__tests__/e2e/pages/Contribute.spec.ts

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,4 @@
1+
import { expectBreadCrumbsToBeVisible } from '@e2e/helpers/expects'
12
import { test, expect } from '@playwright/test'
23
import { mockContributeData } from '@unit/data/mockContributeData'
34

@@ -71,4 +72,7 @@ test.describe('Contribute Page', () => {
7172
await CloseButton.click()
7273
await expect(contributeButton).toBeVisible()
7374
})
75+
test('breadcrumb renders correct segments on /contribute', async ({ page }) => {
76+
await expectBreadCrumbsToBeVisible(page, ['Home', 'Contribute'])
77+
})
7478
})

frontend/__tests__/e2e/pages/Organizations.spec.ts

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,4 @@
1+
import { expectBreadCrumbsToBeVisible } from '@e2e/helpers/expects'
12
import { test, expect } from '@playwright/test'
23
import { mockOrganizationData } from '@unit/data/mockOrganizationData'
34

@@ -40,4 +41,8 @@ test.describe('Organization Page', () => {
4041
await expect(page.getByText('1k')).toBeVisible()
4142
await expect(page.getByText('1.5k')).toBeVisible()
4243
})
44+
45+
test('breadcrumb renders correct segments on /organizations', async ({ page }) => {
46+
await expectBreadCrumbsToBeVisible(page, ['Home', 'Organizations'])
47+
})
4348
})

frontend/__tests__/e2e/pages/Projects.spec.ts

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,4 @@
1+
import { expectBreadCrumbsToBeVisible } from '@e2e/helpers/expects'
12
import { test, expect } from '@playwright/test'
23
import mockProjectData from '@unit/data/mockProjectData'
34

@@ -53,4 +54,7 @@ test.describe('Projects Page', () => {
5354
await contributeButton.click()
5455
await expect(page).toHaveURL('projects/project_1')
5556
})
57+
test('breadcrumb renders correct segments on /projects', async ({ page }) => {
58+
await expectBreadCrumbsToBeVisible(page, ['Home', 'Projects'])
59+
})
5660
})

frontend/__tests__/e2e/pages/Users.spec.ts

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,4 @@
1+
import { expectBreadCrumbsToBeVisible } from '@e2e/helpers/expects'
12
import { test, expect } from '@playwright/test'
23
import { mockUserData } from '@unit/data/mockUserData'
34

@@ -59,4 +60,7 @@ test.describe('Users Page', () => {
5960
await expect(page.getByText('1k')).toBeVisible()
6061
await expect(page.getByText('2k')).toBeVisible()
6162
})
63+
test('breadcrumb renders correct segments on /members', async ({ page }) => {
64+
await expectBreadCrumbsToBeVisible(page, ['Home', 'Members'])
65+
})
6266
})
Lines changed: 42 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,42 @@
1+
import { render, screen } from '@testing-library/react'
2+
import { usePathname } from 'next/navigation'
3+
import BreadCrumbs from 'components/BreadCrumbs'
4+
import '@testing-library/jest-dom'
5+
6+
jest.mock('next/navigation', () => ({
7+
usePathname: jest.fn(),
8+
}))
9+
10+
describe('BreadCrumb', () => {
11+
afterEach(() => {
12+
jest.clearAllMocks()
13+
})
14+
15+
test('does not render on root path "/"', () => {
16+
;(usePathname as jest.Mock).mockReturnValue('/')
17+
18+
render(<BreadCrumbs />)
19+
expect(screen.queryByText('Home')).not.toBeInTheDocument()
20+
})
21+
22+
test('renders breadcrumb with multiple segments', () => {
23+
;(usePathname as jest.Mock).mockReturnValue('/dashboard/users/profile')
24+
25+
render(<BreadCrumbs />)
26+
27+
expect(screen.getByText('Home')).toBeInTheDocument()
28+
expect(screen.getByText('Dashboard')).toBeInTheDocument()
29+
expect(screen.getByText('Users')).toBeInTheDocument()
30+
expect(screen.getByText('Profile')).toBeInTheDocument()
31+
})
32+
33+
test('disables the last segment (non-clickable)', () => {
34+
;(usePathname as jest.Mock).mockReturnValue('/settings/account')
35+
36+
render(<BreadCrumbs />)
37+
38+
const lastSegment = screen.getByText('Account')
39+
expect(lastSegment).toBeInTheDocument()
40+
expect(lastSegment).not.toHaveAttribute('href')
41+
})
42+
})

frontend/src/app/layout.tsx

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@ import type { Metadata } from 'next'
22
import { Geist, Geist_Mono } from 'next/font/google'
33
import React from 'react'
44
import { Providers } from 'wrappers/provider'
5+
import BreadCrumbs from 'components/BreadCrumbs'
56
import Footer from 'components/Footer'
67

78
import Header from 'components/Header'
@@ -41,6 +42,7 @@ export default function RootLayout({
4142
>
4243
<Providers>
4344
<Header />
45+
<BreadCrumbs />
4446
{children}
4547
<Footer />
4648
<ScrollToTop />

0 commit comments

Comments
 (0)