Skip to content
Closed
Show file tree
Hide file tree
Changes from all 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
70 changes: 70 additions & 0 deletions frontend/__tests__/testUtils/sharedAssertions.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,70 @@
import { screen, waitFor, fireEvent } from '@testing-library/react'

export const assertRepoDetails = async ({
heading,
license,
stars,
forks,
commits,
contributors,
issues,
}: {
heading: string
license: string
stars: string
forks: string
commits?: string
contributors?: string
issues: string
}) => {
await waitFor(() => {
const title = screen.getByRole('heading', { name: heading })
expect(title).toBeInTheDocument()
expect(screen.getByText(license)).toBeInTheDocument()
})
expect(screen.getByText(stars)).toBeInTheDocument()
expect(screen.getByText(forks)).toBeInTheDocument()
if (commits) expect(screen.getByText(commits)).toBeInTheDocument()
if (contributors) expect(screen.getByText(contributors)).toBeInTheDocument()
expect(screen.getByText(issues)).toBeInTheDocument()
}

export const assertHeadingsAndTexts = async ({
headingText,
texts,
}: {
headingText: string
texts: string[]
}) => {
await waitFor(() => {
const heading = screen.getByRole('heading', { name: headingText })
expect(heading).toBeInTheDocument()
})

texts.forEach((text) => {
expect(screen.getByText(text)).toBeInTheDocument()
})
}

export const assertContributorToggle = async (initial: string, others: string[]) => {
await waitFor(() => {
expect(screen.getByText(initial)).toBeInTheDocument()
expect(screen.queryByText('Contributor 10')).not.toBeInTheDocument()
})

const showMoreButton = screen.getByRole('button', { name: /Show more/i })
fireEvent.click(showMoreButton)

await waitFor(() => {
others.forEach((name) => {
expect(screen.getByText(name)).toBeInTheDocument()
})
})

const showLessButton = screen.getByRole('button', { name: /Show less/i })
fireEvent.click(showLessButton)

await waitFor(() => {
expect(screen.queryByText('Contributor 10')).not.toBeInTheDocument()
})
}
76 changes: 34 additions & 42 deletions frontend/__tests__/unit/components/BreadCrumbs.test.tsx
Original file line number Diff line number Diff line change
@@ -1,68 +1,60 @@
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(),
}))
const renderBreadCrumbs = (items = []) => render(<BreadCrumbs breadcrumbItems={items} />)

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

test('does not render on root path "/"', () => {
;(usePathname as jest.Mock).mockReturnValue('/')
const sampleItems = [
{ title: 'Dashboard', path: '/dashboard' },
{ title: 'Users', path: '/dashboard/users' },
{ title: 'Profile', path: '/dashboard/users/profile' },
]

render(<BreadCrumbs />)
describe('BreadCrumbs', () => {
test('does not render when breadcrumb item is empty', () => {
renderBreadCrumbs()
expect(screen.queryByText('Home')).not.toBeInTheDocument()
})

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

render(<BreadCrumbs />)

renderBreadCrumbs(sampleItems)
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 items = [
{ title: 'Settings', path: '/settings' },
{ title: 'Account', path: '/settings/account' },
]
renderBreadCrumbs(items)
const lastSegment = screen.getByText('Account')
expect(lastSegment).toBeInTheDocument()
expect(lastSegment).not.toHaveAttribute('href')
expect(lastSegment.closest('a')).toBeNull()
})

test('links have correct href attributes', () => {
;(usePathname as jest.Mock).mockReturnValue('/dashboard/users/profile')

render(<BreadCrumbs />)

const homeLink = screen.getByText('Home').closest('a')
const dashboardLink = screen.getByText('Dashboard').closest('a')
const usersLink = screen.getByText('Users').closest('a')

expect(homeLink).toHaveAttribute('href', '/')
expect(dashboardLink).toHaveAttribute('href', '/dashboard')
expect(usersLink).toHaveAttribute('href', '/dashboard/users')
test('links have correct path attributes', () => {
renderBreadCrumbs(sampleItems)
expect(screen.getByText('Home').closest('a')).toHaveAttribute('href', '/')
expect(screen.getByText('Dashboard').closest('a')).toHaveAttribute('href', '/dashboard')
expect(screen.getByText('Users').closest('a')).toHaveAttribute('href', '/dashboard/users')
})

test('links have hover styles', () => {
;(usePathname as jest.Mock).mockReturnValue('/dashboard/users')

render(<BreadCrumbs />)

const homeLink = screen.getByText('Home').closest('a')
const dashboardLink = screen.getByText('Dashboard').closest('a')

expect(homeLink).toHaveClass('hover:text-blue-700', 'hover:underline')
expect(dashboardLink).toHaveClass('hover:text-blue-700', 'hover:underline')
const items = [
{ title: 'Dashboard', path: '/dashboard' },
{ title: 'Users', path: '/dashboard/users' },
]
renderBreadCrumbs(items)
expect(screen.getByText('Home').closest('a')).toHaveClass(
'hover:text-blue-700',
'hover:underline'
)
expect(screen.getByText('Dashboard').closest('a')).toHaveClass(
'hover:text-blue-700',
'hover:underline'
)
})
})
15 changes: 9 additions & 6 deletions frontend/__tests__/unit/pages/CommitteeDetails.test.tsx
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
import { useQuery } from '@apollo/client'
import { screen, waitFor } from '@testing-library/react'
import { assertHeadingsAndTexts } from '@testUtils/sharedAssertions'
import { mockCommitteeDetailsData } from '@unit/data/mockCommitteeDetailsData'
import { render } from 'wrappers/testUtil'
import CommitteeDetailsPage from 'app/committees/[committeeKey]/page'
Expand Down Expand Up @@ -50,13 +51,15 @@ describe('CommitteeDetailsPage Component', () => {

test('renders committee data correctly', async () => {
render(<CommitteeDetailsPage />)
await waitFor(() => {
expect(screen.getByText('Test Committee')).toBeInTheDocument()
await assertHeadingsAndTexts({
headingText: 'Test Committee',
texts: [
'This is a test committee summary.',
'Leader 1',
'Leader 2',
'https://owasp.org/test-committee',
],
})
expect(screen.getByText('This is a test committee summary.')).toBeInTheDocument()
expect(screen.getByText('Leader 1')).toBeInTheDocument()
expect(screen.getByText('Leader 2')).toBeInTheDocument()
expect(screen.getByText('https://owasp.org/test-committee')).toBeInTheDocument()
})

test('displays "Committee not found" when there is no committee', async () => {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -70,7 +70,8 @@ describe('OrganizationDetailsPage', () => {
render(<OrganizationDetailsPage />)

await waitFor(() => {
expect(screen.getByText('Test Organization')).toBeInTheDocument()
const title = screen.getByRole('heading', { name: 'Test Organization' })
expect(title).toBeInTheDocument()
})

expect(screen.getByText('@test-org')).toBeInTheDocument()
Expand Down
15 changes: 8 additions & 7 deletions frontend/__tests__/unit/pages/ProjectDetails.test.tsx
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import { useQuery } from '@apollo/client'
import { addToast } from '@heroui/toast'
import { act, fireEvent, screen, waitFor, within } from '@testing-library/react'
import { assertRepoDetails } from '@testUtils/sharedAssertions'
import { mockProjectDetailsData } from '@unit/data/mockProjectDetailsData'
import { render } from 'wrappers/testUtil'
import ProjectDetailsPage from 'app/projects/[projectKey]/page'
Expand Down Expand Up @@ -68,21 +69,21 @@ describe('ProjectDetailsPage', () => {
})
})

// eslint-disable-next-line jest/expect-expect
test('renders project details when data is available', async () => {
;(useQuery as jest.Mock).mockReturnValue({
data: mockProjectDetailsData,
error: null,
})

render(<ProjectDetailsPage />)

await waitFor(() => {
expect(screen.getByText('Test Project')).toBeInTheDocument()
expect(screen.getByText('Lab')).toBeInTheDocument()
await assertRepoDetails({
heading: 'Test Project',
license: 'Lab',
stars: '2.2K Stars',
forks: '10 Forks',
issues: '10 Issues',
})
expect(screen.getByText('2.2K Stars')).toBeInTheDocument()
expect(screen.getByText('10 Forks')).toBeInTheDocument()
expect(screen.getByText('10 Issues')).toBeInTheDocument()
})

test('renders error message when GraphQL request fails', async () => {
Expand Down
19 changes: 10 additions & 9 deletions frontend/__tests__/unit/pages/RepositoryDetails.test.tsx
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import { useQuery } from '@apollo/client'
import { addToast } from '@heroui/toast'
import { act, fireEvent, screen, waitFor } from '@testing-library/react'
import { assertRepoDetails } from '@testUtils/sharedAssertions'
import { mockRepositoryData } from '@unit/data/mockRepositoryData'
import { render } from 'wrappers/testUtil'
import RepositoryDetailsPage from 'app/organizations/[organizationKey]/repositories/[repositoryKey]/page'
Expand Down Expand Up @@ -67,15 +68,15 @@ describe('RepositoryDetailsPage', () => {

render(<RepositoryDetailsPage />)

await waitFor(() => {
expect(screen.getByText('Test Repo')).toBeInTheDocument()
expect(screen.getByText('MIT')).toBeInTheDocument()
})
expect(screen.getByText('50K Stars')).toBeInTheDocument()
expect(screen.getByText('3K Forks')).toBeInTheDocument()
expect(screen.getByText('10 Commits')).toBeInTheDocument()
expect(screen.getByText('5 Contributors')).toBeInTheDocument()
expect(screen.getByText('2 Issues')).toBeInTheDocument()
await assertRepoDetails({
heading: 'Test Repo',
license: 'MIT',
stars: '50K Stars',
forks: '3K Forks',
commits: '10 Commits',
contributors: '5 Contributors',
issues: '2 Issues',
})
})

test('renders error message when GraphQL request fails', async () => {
Expand Down
13 changes: 6 additions & 7 deletions frontend/__tests__/unit/pages/SnapshotDetails.test.tsx
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import { useQuery } from '@apollo/client'
import { addToast } from '@heroui/toast'
import { fireEvent, screen, waitFor } from '@testing-library/react'
import { assertHeadingsAndTexts } from '@testUtils/sharedAssertions'
import { mockSnapshotDetailsData } from '@unit/data/mockSnapshotData'
import { render } from 'wrappers/testUtil'
import SnapshotDetailsPage from 'app/snapshots/[id]/page'
Expand Down Expand Up @@ -70,13 +71,10 @@ describe('SnapshotDetailsPage', () => {

render(<SnapshotDetailsPage />)

await waitFor(() => {
expect(screen.getByText('New Snapshot')).toBeInTheDocument()
await assertHeadingsAndTexts({
headingText: 'New Snapshot',
texts: ['New Chapters', 'New Projects', 'New Releases'],
})

expect(screen.getByText('New Chapters')).toBeInTheDocument()
expect(screen.getByText('New Projects')).toBeInTheDocument()
expect(screen.getByText('New Releases')).toBeInTheDocument()
})

test('renders error message when GraphQL request fails', async () => {
Expand Down Expand Up @@ -145,7 +143,8 @@ describe('SnapshotDetailsPage', () => {
render(<SnapshotDetailsPage />)

await waitFor(() => {
expect(screen.getByText('New Snapshot')).toBeInTheDocument()
const title = screen.getByRole('heading', { name: 'New Snapshot' })
expect(title).toBeInTheDocument()
expect(screen.getByText('Latest pre-release')).toBeInTheDocument()
})

Expand Down
8 changes: 5 additions & 3 deletions frontend/__tests__/unit/pages/UserDetails.test.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -88,7 +88,8 @@ describe('UserDetailsPage', () => {
expect(screen.queryByAltText('Loading indicator')).not.toBeInTheDocument()
})

expect(screen.getByText('Test User')).toBeInTheDocument()
const title = screen.getByRole('heading', { name: 'Test User' })
expect(title).toBeInTheDocument()
expect(screen.getByText('Statistics')).toBeInTheDocument()
expect(screen.getByText('Contribution Heatmap')).toBeInTheDocument()
expect(screen.getByText('Test Company')).toBeInTheDocument()
Expand Down Expand Up @@ -267,7 +268,7 @@ describe('UserDetailsPage', () => {
render(<UserDetailsPage />)

await waitFor(() => {
const userName = screen.getByText('Test User')
const userName = screen.getByRole('heading', { name: 'Test User' })
expect(userName).toBeInTheDocument()
})
})
Expand Down Expand Up @@ -327,7 +328,8 @@ describe('UserDetailsPage', () => {

render(<UserDetailsPage />)
await waitFor(() => {
expect(screen.getByText('Test User')).toBeInTheDocument()
const userName = screen.getByRole('heading', { name: 'Test User' })
expect(userName).toBeInTheDocument()
expect(screen.queryByText('Test @User')).not.toBeInTheDocument()
})
})
Expand Down
7 changes: 6 additions & 1 deletion frontend/jest.config.ts
Original file line number Diff line number Diff line change
Expand Up @@ -32,12 +32,17 @@ const config: Config = {
globals: {},
setupFilesAfterEnv: ['<rootDir>/jest.setup.ts'],
testEnvironment: 'jest-environment-jsdom',
testPathIgnorePatterns: ['<rootDir>/__tests__/unit/data/', '<rootDir>/__tests__/e2e/'],
testPathIgnorePatterns: [
'<rootDir>/__tests__/unit/data/',
'<rootDir>/__tests__/e2e/',
'<rootDir>/__tests__/testUtils/',
],
transform: {
'^.+\\.tsx?$': '@swc/jest',
},
moduleNameMapper: {
'^@unit/(.*)$': '<rootDir>/__tests__/unit/$1',
'^@testUtils/(.*)$': '<rootDir>/__tests__/testUtils/$1',
'^@/(.*)$': '<rootDir>/src/$1',
'\\.(scss|sass|css)$': 'identity-obj-proxy',
},
Expand Down
Loading