diff --git a/frontend/__tests__/e2e/helpers/expects.ts b/frontend/__tests__/e2e/helpers/expects.ts new file mode 100644 index 0000000000..1251f251e6 --- /dev/null +++ b/frontend/__tests__/e2e/helpers/expects.ts @@ -0,0 +1,16 @@ +import { Page, expect } from '@playwright/test' + +export async function expectBreadCrumbsToBeVisible(page: Page, breadcrumbs: string[] = ['Home']) { + const breadcrumbsContainer = page.locator('[aria-label="breadcrumb"]') + + await expect(breadcrumbsContainer).toBeVisible() + await expect(breadcrumbsContainer).toHaveCount(1) + + for (const breadcrumb of breadcrumbs) { + await expect(breadcrumbsContainer.getByText(breadcrumb)).toBeVisible() + } + + const allBreadcrumbs = await breadcrumbsContainer.locator('li').allTextContents() + const visibleBreadcrumbs = allBreadcrumbs.filter((text) => text.trim() !== '') + await expect(visibleBreadcrumbs).toEqual(breadcrumbs) +} diff --git a/frontend/__tests__/e2e/pages/About.spec.ts b/frontend/__tests__/e2e/pages/About.spec.ts index 05d9e45d7a..39e9497aab 100644 --- a/frontend/__tests__/e2e/pages/About.spec.ts +++ b/frontend/__tests__/e2e/pages/About.spec.ts @@ -1,3 +1,4 @@ +import { expectBreadCrumbsToBeVisible } from '@e2e/helpers/expects' import { test, expect } from '@playwright/test' import { mockAboutData } from '@unit/data/mockAboutData' @@ -84,4 +85,8 @@ test.describe('About Page', () => { await newPage.waitForLoadState() expect(newPage.url()).toContain('/members/') }) + + test('breadcrumb renders correct segments on /about', async ({ page }) => { + await expectBreadCrumbsToBeVisible(page, ['Home', 'About']) + }) }) diff --git a/frontend/__tests__/e2e/pages/Chapters.spec.ts b/frontend/__tests__/e2e/pages/Chapters.spec.ts index 5962f9e8d7..17fb099ba1 100644 --- a/frontend/__tests__/e2e/pages/Chapters.spec.ts +++ b/frontend/__tests__/e2e/pages/Chapters.spec.ts @@ -1,3 +1,4 @@ +import { expectBreadCrumbsToBeVisible } from '@e2e/helpers/expects' import { test, expect } from '@playwright/test' import { mockChapterData } from '@unit/data/mockChapterData' @@ -54,4 +55,8 @@ test.describe('Chapters Page', () => { await contributeButton.click() await expect(page).toHaveURL('chapters/chapter_1') }) + + test('breadcrumb renders correct segments on /chapters', async ({ page }) => { + await expectBreadCrumbsToBeVisible(page, ['Home', 'Chapters']) + }) }) diff --git a/frontend/__tests__/e2e/pages/Committees.spec.ts b/frontend/__tests__/e2e/pages/Committees.spec.ts index 802988ce28..10bce0825c 100644 --- a/frontend/__tests__/e2e/pages/Committees.spec.ts +++ b/frontend/__tests__/e2e/pages/Committees.spec.ts @@ -1,3 +1,4 @@ +import { expectBreadCrumbsToBeVisible } from '@e2e/helpers/expects' import { test, expect } from '@playwright/test' import { mockCommitteeData } from '@unit/data/mockCommitteeData' @@ -54,4 +55,8 @@ test.describe('Committees Page', () => { await contributeButton.click() await expect(page).toHaveURL('/committees/committee_1') }) + + test('breadcrumb renders correct segments on /committees', async ({ page }) => { + await expectBreadCrumbsToBeVisible(page, ['Home', 'Committees']) + }) }) diff --git a/frontend/__tests__/e2e/pages/Contribute.spec.ts b/frontend/__tests__/e2e/pages/Contribute.spec.ts index 04b9ddbecf..da6bc252fe 100644 --- a/frontend/__tests__/e2e/pages/Contribute.spec.ts +++ b/frontend/__tests__/e2e/pages/Contribute.spec.ts @@ -1,3 +1,4 @@ +import { expectBreadCrumbsToBeVisible } from '@e2e/helpers/expects' import { test, expect } from '@playwright/test' import { mockContributeData } from '@unit/data/mockContributeData' @@ -71,4 +72,7 @@ test.describe('Contribute Page', () => { await CloseButton.click() await expect(contributeButton).toBeVisible() }) + test('breadcrumb renders correct segments on /contribute', async ({ page }) => { + await expectBreadCrumbsToBeVisible(page, ['Home', 'Contribute']) + }) }) diff --git a/frontend/__tests__/e2e/pages/Organizations.spec.ts b/frontend/__tests__/e2e/pages/Organizations.spec.ts index fbe69887fb..11cfd5f162 100644 --- a/frontend/__tests__/e2e/pages/Organizations.spec.ts +++ b/frontend/__tests__/e2e/pages/Organizations.spec.ts @@ -1,3 +1,4 @@ +import { expectBreadCrumbsToBeVisible } from '@e2e/helpers/expects' import { test, expect } from '@playwright/test' import { mockOrganizationData } from '@unit/data/mockOrganizationData' @@ -40,4 +41,8 @@ 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 }) => { + await expectBreadCrumbsToBeVisible(page, ['Home', 'Organizations']) + }) }) diff --git a/frontend/__tests__/e2e/pages/Projects.spec.ts b/frontend/__tests__/e2e/pages/Projects.spec.ts index 437aec0ac2..230ca6d403 100644 --- a/frontend/__tests__/e2e/pages/Projects.spec.ts +++ b/frontend/__tests__/e2e/pages/Projects.spec.ts @@ -1,3 +1,4 @@ +import { expectBreadCrumbsToBeVisible } from '@e2e/helpers/expects' import { test, expect } from '@playwright/test' import mockProjectData from '@unit/data/mockProjectData' @@ -53,4 +54,7 @@ test.describe('Projects Page', () => { await contributeButton.click() await expect(page).toHaveURL('projects/project_1') }) + test('breadcrumb renders correct segments on /projects', async ({ page }) => { + await expectBreadCrumbsToBeVisible(page, ['Home', 'Projects']) + }) }) diff --git a/frontend/__tests__/e2e/pages/Users.spec.ts b/frontend/__tests__/e2e/pages/Users.spec.ts index 57bb6af62e..cbc547727c 100644 --- a/frontend/__tests__/e2e/pages/Users.spec.ts +++ b/frontend/__tests__/e2e/pages/Users.spec.ts @@ -1,3 +1,4 @@ +import { expectBreadCrumbsToBeVisible } from '@e2e/helpers/expects' import { test, expect } from '@playwright/test' import { mockUserData } from '@unit/data/mockUserData' @@ -59,4 +60,7 @@ test.describe('Users Page', () => { await expect(page.getByText('1k')).toBeVisible() await expect(page.getByText('2k')).toBeVisible() }) + test('breadcrumb renders correct segments on /members', async ({ page }) => { + await expectBreadCrumbsToBeVisible(page, ['Home', 'Members']) + }) }) diff --git a/frontend/__tests__/unit/components/BreadCrumbs.test.tsx b/frontend/__tests__/unit/components/BreadCrumbs.test.tsx new file mode 100644 index 0000000000..ed8d6c1a8a --- /dev/null +++ b/frontend/__tests__/unit/components/BreadCrumbs.test.tsx @@ -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() + expect(screen.queryByText('Home')).not.toBeInTheDocument() + }) + + test('renders breadcrumb with multiple segments', () => { + ;(usePathname as jest.Mock).mockReturnValue('/dashboard/users/profile') + + render() + + 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() + + const lastSegment = screen.getByText('Account') + expect(lastSegment).toBeInTheDocument() + expect(lastSegment).not.toHaveAttribute('href') + }) +}) diff --git a/frontend/src/app/layout.tsx b/frontend/src/app/layout.tsx index e0415148c9..f253adb786 100644 --- a/frontend/src/app/layout.tsx +++ b/frontend/src/app/layout.tsx @@ -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' @@ -41,6 +42,7 @@ export default function RootLayout({ >
+ {children}