Skip to content
Merged
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
Original file line number Diff line number Diff line change
Expand Up @@ -21,19 +21,59 @@ test.describe('Projects Health Dashboard Metrics', () => {
await mockDashboardCookies(page, mockHealthMetricsData, true)
await page.goto('/projects/dashboard/metrics')
const firstMetric = mockHealthMetricsData.projectHealthMetrics[0]
await expect(page.getByText(firstMetric.projectName)).toBeVisible({ timeout: 10000 })
await expect(page.getByText(firstMetric.starsCount.toString())).toBeVisible()
await expect(page.getByText(firstMetric.forksCount.toString())).toBeVisible()
await expect(page.getByText(firstMetric.contributorsCount.toString())).toBeVisible()
const metricsLink = page
.getByRole('link')
.filter({
has: page.getByText(firstMetric.projectName),
})
.first()

await expect(metricsLink).toBeVisible({ timeout: 10000 })
await expect(
page
.getByRole('link')
.filter({
has: page.getByText(firstMetric.starsCount.toString()),
})
.first()
).toBeVisible()
await expect(
page
.getByRole('link')
.filter({
has: page.getByText(firstMetric.forksCount.toString()),
})
.first()
).toBeVisible()
await expect(
page
.getByRole('link')
.filter({
has: page.getByText(firstMetric.contributorsCount.toString()),
})
.first()
).toBeVisible()
await expect(
page
.getByRole('link')
.filter({
has: page.getByText(
new Date(firstMetric.createdAt).toLocaleString('en-US', {
month: 'short',
day: 'numeric',
year: 'numeric',
})
),
})
.first()
).toBeVisible()
await expect(
page.getByText(
new Date(firstMetric.createdAt).toLocaleString('default', {
month: 'short',
day: 'numeric',
year: 'numeric',
page
.getByRole('link')
.filter({
has: page.getByText(firstMetric.score.toString()),
})
)
.first()
).toBeVisible()
await expect(page.getByText(firstMetric.score.toString())).toBeVisible()
})
})
46 changes: 24 additions & 22 deletions frontend/__tests__/unit/components/MetricsCard.test.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -39,12 +39,12 @@ describe('MetricsCard component', () => {
const metric = makeMetric()
render(<MetricsCard metric={metric} />)

expect(screen.getByText('Test Project')).toBeInTheDocument()
expect(screen.getByText('42')).toBeInTheDocument()
expect(screen.getByText('13')).toBeInTheDocument()
expect(screen.getByText('5')).toBeInTheDocument()
expect(screen.getByText('Mar 25, 2023')).toBeInTheDocument()
expect(screen.getByText('80')).toBeInTheDocument()
expect(screen.getAllByText('Test Project')[0]).toBeInTheDocument()
expect(screen.getAllByText('42')[0]).toBeInTheDocument()
expect(screen.getAllByText('13')[0]).toBeInTheDocument()
expect(screen.getAllByText('5')[0]).toBeInTheDocument()
expect(screen.getAllByText('Mar 25, 2023')[0]).toBeInTheDocument()
expect(screen.getByText(/Score:/)).toBeInTheDocument()

const link = screen.getByRole('link')
expect(link).toHaveAttribute('href', '/projects/dashboard/metrics/test-project')
Expand All @@ -53,30 +53,32 @@ describe('MetricsCard component', () => {
it('renders "No name" placeholder when projectName is empty', () => {
const metric = makeMetric({ projectName: '' })
render(<MetricsCard metric={metric} />)
expect(screen.getByText('No name')).toBeInTheDocument()
expect(screen.getAllByText('No name')[0]).toBeInTheDocument()
})

it('applies correct styling class depending on score thresholds', () => {
const cases: Array<[number, string]> = [
[90, 'text-green-900'],
[75, 'text-green-900'],
[60, 'text-orange-900'],
[50, 'text-orange-900'],
[30, 'text-red-900'],
[90, 'bg-green-500'],
[75, 'bg-green-500'],
[60, 'bg-orange-500'],
[50, 'bg-orange-500'],
[30, 'bg-red-500'],
]

for (const [score, expectedClass] of cases) {
const metric = makeMetric({ score })
render(<MetricsCard metric={metric} />)
const scoreEl = screen.getByText(score.toString()).closest('div')
const { unmount } = render(<MetricsCard metric={metric} />)
const scoreText = screen.getByText(/Score:/)
const scoreEl = scoreText.closest('div')
expect(scoreEl).toHaveClass(expectedClass)
unmount()
}
})

it('updates displayed values and link when metric props change via rerender', () => {
const { rerender } = render(<MetricsCard metric={makeMetric()} />)
expect(screen.getByText('Test Project')).toBeInTheDocument()
expect(screen.getByText('80')).toBeInTheDocument()
expect(screen.getAllByText('Test Project')[0]).toBeInTheDocument()
expect(screen.getByText(/Score:/).textContent).toContain('80')

const updated = makeMetric({
projectKey: 'another',
Expand All @@ -89,12 +91,12 @@ describe('MetricsCard component', () => {
})
rerender(<MetricsCard metric={updated} />)

expect(screen.getByText('Another Project')).toBeInTheDocument()
expect(screen.getByText('99')).toBeInTheDocument()
expect(screen.getByText('20')).toBeInTheDocument()
expect(screen.getByText('7')).toBeInTheDocument()
expect(screen.getByText('Jan 1, 2024')).toBeInTheDocument()
expect(screen.getByText('55')).toBeInTheDocument()
expect(screen.getAllByText('Another Project')[0]).toBeInTheDocument()
expect(screen.getAllByText('99')[0]).toBeInTheDocument()
expect(screen.getAllByText('20')[0]).toBeInTheDocument()
expect(screen.getAllByText('7')[0]).toBeInTheDocument()
expect(screen.getAllByText('Jan 1, 2024')[0]).toBeInTheDocument()
expect(screen.getByText(/Score:/).textContent).toContain('55')
expect(screen.getByRole('link')).toHaveAttribute('href', '/projects/dashboard/metrics/another')
})
})
Original file line number Diff line number Diff line change
Expand Up @@ -91,15 +91,6 @@ describe('MetricsPage', () => {
})
}

const expectAllHeadersVisible = async () => {
const headers = ['Project Name', 'Stars', 'Forks', 'Contributors', 'Health Checked At', 'Score']
await waitFor(() => {
for (const header of headers) {
expect(screen.getAllByText(header).length).toBeGreaterThan(0)
}
})
}

const testFilterOptions = async () => {
const filterOptions = [
'Incubator',
Expand All @@ -119,14 +110,6 @@ describe('MetricsPage', () => {
}
}

const testSortableColumns = async () => {
const sortableColumns = ['Stars', 'Forks', 'Contributors', 'Health Checked At', 'Score']
for (const column of sortableColumns) {
const sortButton = screen.getByTitle(`Sort by ${column}`)
expect(sortButton).toBeInTheDocument()
}
}

const testFilterSections = async () => {
const filterSectionsLabels = ['Project Level', 'Project Health', 'Reset Filters']
for (const label of filterSectionsLabels) {
Expand Down Expand Up @@ -165,99 +148,33 @@ describe('MetricsPage', () => {
expect(true).toBe(true)
})

test('renders metrics table headers', async () => {
render(<MetricsPage />)
await expectAllHeadersVisible()

expect(true).toBe(true)
})

test('renders filter dropdown and sortable column headers', async () => {
test('renders filter dropdown', async () => {
render(<MetricsPage />)
await waitFor(async () => {
await testFilterSections()
await testFilterOptions()
await testSortableColumns()
})

expect(true).toBe(true)
})

test('SortableColumnHeader applies correct alignment classes', async () => {
render(<MetricsPage />)
const sortButton = await screen.findByTitle('Sort by Stars')
const wrapperDiv = sortButton.closest('div')
expect(wrapperDiv).not.toBeNull()
expect(wrapperDiv).toHaveClass('justify-center')
expect(sortButton).toHaveClass('text-center')
})

test('handles sorting state and URL updates', async () => {
const mockReplace = jest.fn()
const { useRouter, useSearchParams } = jest.requireMock('next/navigation')
;(useRouter as jest.Mock).mockReturnValue({
push: jest.fn(),
replace: mockReplace,
})

// Test unsorted -> descending
;(useSearchParams as jest.Mock).mockReturnValue(new URLSearchParams())
const { rerender } = render(<MetricsPage />)

const sortButton = screen.getByTitle('Sort by Stars')
fireEvent.click(sortButton)

await waitFor(() => {
const lastCall = mockReplace.mock.calls[mockReplace.mock.calls.length - 1][0]
const url = new URL(lastCall, 'http://localhost')
expect(url.searchParams.get('order')).toBe('-stars')
})

// Test descending -> ascending
mockReplace.mockClear()
;(useSearchParams as jest.Mock).mockReturnValue(new URLSearchParams('order=-stars'))
rerender(<MetricsPage />)

const sortButtonDesc = screen.getByTitle('Sort by Stars')
fireEvent.click(sortButtonDesc)

await waitFor(() => {
const lastCall = mockReplace.mock.calls[mockReplace.mock.calls.length - 1][0]
const url = new URL(lastCall, 'http://localhost')
expect(url.searchParams.get('order')).toBe('stars')
})

// Test ascending -> unsorted (removes order param, defaults to -score)
mockReplace.mockClear()
;(useSearchParams as jest.Mock).mockReturnValue(new URLSearchParams('order=stars'))
rerender(<MetricsPage />)

const sortButtonAsc = screen.getByTitle('Sort by Stars')
fireEvent.click(sortButtonAsc)

await waitFor(() => {
const lastCall = mockReplace.mock.calls[mockReplace.mock.calls.length - 1][0]
const url = new URL(lastCall, 'http://localhost')
expect(url.searchParams.get('order')).toBeNull()
})
})
const testMetricsDataDisplay = async () => {
const metrics = mockHealthMetricsData.projectHealthMetrics
for (const metric of metrics) {
expect(screen.getByText(metric.projectName)).toBeInTheDocument()
expect(screen.getByText(metric.starsCount.toString())).toBeInTheDocument()
expect(screen.getByText(metric.forksCount.toString())).toBeInTheDocument()
expect(screen.getByText(metric.contributorsCount.toString())).toBeInTheDocument()
expect(screen.getAllByText(metric.projectName)[0]).toBeInTheDocument()
expect(screen.getAllByText(metric.starsCount.toString())[0]).toBeInTheDocument()
expect(screen.getAllByText(metric.forksCount.toString())[0]).toBeInTheDocument()
expect(screen.getAllByText(metric.contributorsCount.toString())[0]).toBeInTheDocument()
expect(
screen.getByText(
screen.getAllByText(
new Date(metric.createdAt).toLocaleString('default', {
month: 'short',
day: 'numeric',
year: 'numeric',
})
)
)[0]
).toBeInTheDocument()
expect(screen.getByText(metric.score.toString())).toBeInTheDocument()
expect(screen.getByText(new RegExp(`Score:.*${metric.score}`))).toBeInTheDocument()
}
}

Expand Down
Loading