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
17 changes: 13 additions & 4 deletions backend/apps/github/graphql/nodes/milestone.py
Original file line number Diff line number Diff line change
Expand Up @@ -10,24 +10,33 @@ class MilestoneNode(BaseNode):
"""Github Milestone Node."""

organization_name = graphene.String()
progress = graphene.Float()
repository_name = graphene.String()

class Meta:
model = Milestone

fields = (
"author",
"body",
"created_at",
"title",
"open_issues_count",
"closed_issues_count",
"url",
)

def resolve_repository_name(self, info):
"""Resolve repository name."""
return self.repository.name

def resolve_organization_name(self, info):
"""Return organization name."""
return self.repository.organization.login if self.repository.organization else None

def resolve_progress(self, info):
"""Return milestone progress."""
total_issues_count = self.closed_issues_count + self.open_issues_count
if not total_issues_count:
return 0.0
return round((self.closed_issues_count / total_issues_count) * 100, 2)

def resolve_repository_name(self, info):
"""Resolve repository name."""
return self.repository.name
2 changes: 2 additions & 0 deletions backend/tests/apps/github/graphql/nodes/milestone_test.py
Original file line number Diff line number Diff line change
Expand Up @@ -17,10 +17,12 @@ def test_meta_configuration(self):
assert MilestoneNode._meta.model == Milestone
expected_fields = {
"author",
"body",
"closed_issues_count",
"created_at",
"open_issues_count",
"organization_name",
"progress",
"repository_name",
"title",
"url",
Expand Down
5 changes: 4 additions & 1 deletion frontend/__tests__/e2e/pages/About.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -70,7 +70,10 @@ test.describe('About Page', () => {

test('loads roadmap items correctly', async ({ page }) => {
await expect(page.getByRole('heading', { name: 'Roadmap' })).toBeVisible()
expect(await page.locator('li').count()).toBeGreaterThan(0)
for (const milestone of mockAboutData.project.recentMilestones) {
await expect(page.getByText(milestone.title)).toBeVisible()
await expect(page.getByText(milestone.body)).toBeVisible()
}
})

test('displays animated counters with correct values', async ({ page }) => {
Expand Down
20 changes: 20 additions & 0 deletions frontend/__tests__/unit/data/mockAboutData.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,26 @@ export const mockAboutData = {
issuesCount: 40,
forksCount: 60,
starsCount: 890,
recentMilestones: [
{
title: 'NestBot title',
body: 'NestBot Idea',
url: 'http/github.com/milestones/5',
progress: 58,
},
{
title: 'Contribution Hub title',
body: 'Contribution Hub Idea',
url: 'http/github.com/milestones/8',
progress: 75,
},
{
title: 'Project Dashboard title',
body: 'Project Dashboard Idea',
url: 'http/github.com/milestones/10',
progress: 80,
},
],
},
topContributors: Array.from({ length: 15 }, (_, i) => ({
avatarUrl: `https://avatars.githubusercontent.com/avatar${i + 1}.jpg`,
Expand Down
113 changes: 69 additions & 44 deletions frontend/__tests__/unit/pages/About.test.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ import { addToast } from '@heroui/toast'
import { fireEvent, screen, waitFor, within } from '@testing-library/react'
import { mockAboutData } from '@unit/data/mockAboutData'
import { useRouter } from 'next/navigation'
import { act } from 'react'
import { render } from 'wrappers/testUtil'
import About from 'app/about/page'
import { GET_PROJECT_METADATA, GET_TOP_CONTRIBUTORS } from 'server/queries/projectQueries'
Expand Down Expand Up @@ -34,11 +35,6 @@ jest.mock('utils/aboutData', () => ({
'This is a test paragraph about the project.',
'This is another paragraph about the project history.',
],
roadmap: [
{ title: 'Feature 1', issueLink: 'https://github.com/owasp/test/issues/1' },
{ title: 'Feature 2', issueLink: 'https://github.com/owasp/test/issues/2' },
{ title: 'Feature 3', issueLink: 'https://github.com/owasp/test/issues/3' },
],
technologies: [
{
section: 'Backend',
Expand Down Expand Up @@ -129,13 +125,7 @@ describe('About Component', () => {
return mockTopContributorsData
}
} else if (query === GET_LEADER_DATA) {
if (key === 'arkid15r') {
return mockUserData('arkid15r')
} else if (key === 'kasya') {
return mockUserData('kasya')
} else if (key === 'mamicidal') {
return mockUserData('mamicidal')
}
return mockUserData(key)
}

return { loading: true }
Expand All @@ -149,7 +139,9 @@ describe('About Component', () => {
})

test('renders project history correctly', async () => {
render(<About />)
await act(async () => {
render(<About />)
})

const historySection = screen.getByText('History').closest('div')
expect(historySection).toBeInTheDocument()
Expand All @@ -163,7 +155,9 @@ describe('About Component', () => {
})

test('renders leaders section with three leaders', async () => {
render(<About />)
await act(async () => {
render(<About />)
})

const leadersSection = screen.getByText('Leaders').closest('div')
expect(leadersSection).toBeInTheDocument()
Expand All @@ -183,15 +177,15 @@ describe('About Component', () => {
return mockProjectData
} else if (options?.variables?.key === 'arkid15r') {
return { data: null, loading: false, error: mockError }
} else if (options?.variables?.key === 'kasya') {
return mockUserData('kasya')
} else if (options?.variables?.key === 'mamicidal') {
return mockUserData('mamicidal')
} else if (options?.variables?.key === 'kasya' || options?.variables?.key === 'mamicidal') {
return mockUserData(options?.variables?.key)
}
return { loading: true }
})

render(<About />)
await act(async () => {
render(<About />)
})

await waitFor(() => {
expect(screen.getByText("Error loading arkid15r's data")).toBeInTheDocument()
Expand All @@ -201,7 +195,9 @@ describe('About Component', () => {
})

test('renders top contributors section correctly', async () => {
render(<About />)
await act(async () => {
render(<About />)
})

await waitFor(() => {
expect(screen.getByText('Top Contributors')).toBeInTheDocument()
Expand All @@ -212,7 +208,9 @@ describe('About Component', () => {
})

test('toggles contributors list when show more/less is clicked', async () => {
render(<About />)
await act(async () => {
render(<About />)
})
await waitFor(() => {
expect(screen.getByText('Contributor 6')).toBeInTheDocument()
expect(screen.queryByText('Contributor 10')).not.toBeInTheDocument()
Expand All @@ -235,7 +233,9 @@ describe('About Component', () => {
})

test('renders technologies section correctly', async () => {
render(<About />)
await act(async () => {
render(<About />)
})

const technologiesSection = screen.getByText('Technologies & Tools').closest('div')
expect(technologiesSection).toBeInTheDocument()
Expand Down Expand Up @@ -269,26 +269,29 @@ describe('About Component', () => {
})

test('renders roadmap correctly', async () => {
render(<About />)
await act(async () => {
render(<About />)
})

const roadmapSection = screen.getByRole('heading', { name: 'Roadmap' }).closest('div')
expect(roadmapSection).toBeInTheDocument()

const roadmapItems = within(roadmapSection).getAllByRole('listitem')
expect(roadmapItems).toHaveLength(3)

expect(screen.getByText('Feature 1')).toBeInTheDocument()
expect(screen.getByText('Feature 2')).toBeInTheDocument()
expect(screen.getByText('Feature 3')).toBeInTheDocument()

const roadmapData = mockAboutData.project.recentMilestones
const links = within(roadmapSection)
.getAllByRole('link')
.filter((link) => link.getAttribute('href') !== '#roadmap')
expect(links[0].getAttribute('href')).toBe('https://github.com/owasp/test/issues/1')

for (let i = 0; i < roadmapData.length; i++) {
const milestone = [...roadmapData].sort((a, b) => (a.title > b.title ? 1 : -1))[i]
expect(screen.getByText(milestone.title)).toBeInTheDocument()
expect(screen.getByText(milestone.body)).toBeInTheDocument()
expect(links[i].getAttribute('href')).toBe(milestone.url)
}
})

test('renders project stats cards correctly', async () => {
render(<About />)
await act(async () => {
render(<About />)
})

await waitFor(() => {
expect(screen.getByText('Contributors')).toBeInTheDocument()
Expand All @@ -312,7 +315,9 @@ describe('About Component', () => {
return { loading: true }
})

render(<About />)
await act(async () => {
render(<About />)
})

await waitFor(() => {
expect(screen.getByText('Loading arkid15r...')).toBeInTheDocument()
Expand Down Expand Up @@ -341,7 +346,9 @@ describe('About Component', () => {
return { loading: true }
})

render(<About />)
await act(async () => {
render(<About />)
})

await waitFor(() => {
expect(screen.getByText('No data available for arkid15r')).toBeInTheDocument()
Expand All @@ -366,7 +373,9 @@ describe('About Component', () => {
return { loading: true }
})

render(<About />)
await act(async () => {
render(<About />)
})

await waitFor(() => {
expect(screen.getByText('Data not found')).toBeInTheDocument()
Expand All @@ -388,7 +397,9 @@ describe('About Component', () => {
return { loading: true }
})

render(<About />)
await act(async () => {
render(<About />)
})

await waitFor(() => {
expect(screen.getByText('No data available for arkid15r')).toBeInTheDocument()
Expand All @@ -398,7 +409,9 @@ describe('About Component', () => {
})

test('navigates to user details on View Profile button click', async () => {
render(<About />)
await act(async () => {
render(<About />)
})

await waitFor(() => {
const viewDetailsButtons = screen.getAllByText('View Profile')
Expand Down Expand Up @@ -434,7 +447,9 @@ describe('About Component', () => {
return { loading: true }
})

render(<About />)
await act(async () => {
render(<About />)
})

await waitFor(() => {
expect(screen.getByText('arkid15r')).toBeInTheDocument()
Expand All @@ -457,7 +472,9 @@ describe('About Component', () => {
return { loading: true }
})

render(<About />)
await act(async () => {
render(<About />)
})

await waitFor(() => {
expect(screen.getByText(/No data available for arkid15r/i)).toBeInTheDocument()
Expand All @@ -476,7 +493,9 @@ describe('About Component', () => {
}
})

render(<About />)
await act(async () => {
render(<About />)
})
await waitFor(() => {
// Look for the element with alt text "Loading indicator"
const spinner = screen.getAllByAltText('Loading indicator')
Expand All @@ -495,7 +514,9 @@ describe('About Component', () => {
error: null,
}
})
render(<About />)
await act(async () => {
render(<About />)
})
await waitFor(() => {
expect(screen.getByText(/Data not found/)).toBeInTheDocument()
expect(
Expand All @@ -515,7 +536,9 @@ describe('About Component', () => {
error: null,
}
})
render(<About />)
await act(async () => {
render(<About />)
})
await waitFor(() => {
expect(addToast).toHaveBeenCalledWith({
color: 'danger',
Expand All @@ -539,7 +562,9 @@ describe('About Component', () => {
error: null,
}
})
render(<About />)
await act(async () => {
render(<About />)
})
await waitFor(() => {
expect(addToast).toHaveBeenCalledWith({
color: 'danger',
Expand Down
Loading