From e5cf673f5c07a25e547a64e820895d6da9c1715a Mon Sep 17 00:00:00 2001 From: Harsh Date: Wed, 14 Jan 2026 05:00:08 +0000 Subject: [PATCH 1/4] update: implement show more functionality for About page sections --- frontend/__tests__/mockData/mockAboutData.ts | 12 +++ frontend/__tests__/unit/pages/About.test.tsx | 83 ++++++++++++++------ frontend/src/app/about/page.tsx | 46 +++++++---- 3 files changed, 102 insertions(+), 39 deletions(-) diff --git a/frontend/__tests__/mockData/mockAboutData.ts b/frontend/__tests__/mockData/mockAboutData.ts index a2056cdce5..47c329613d 100644 --- a/frontend/__tests__/mockData/mockAboutData.ts +++ b/frontend/__tests__/mockData/mockAboutData.ts @@ -23,6 +23,18 @@ export const mockAboutData = { url: 'http/github.com/milestones/10', progress: 80, }, + { + title: 'Milestone 4', + body: 'Milestone 4 Idea', + url: 'http/github.com/milestones/11', + progress: 20, + }, + { + title: 'Milestone 5', + body: 'Milestone 5 Idea', + url: 'http/github.com/milestones/12', + progress: 40, + }, ], }, topContributors: Array.from({ length: 15 }, (_, i) => ({ diff --git a/frontend/__tests__/unit/pages/About.test.tsx b/frontend/__tests__/unit/pages/About.test.tsx index 0d9cee5263..7934189560 100644 --- a/frontend/__tests__/unit/pages/About.test.tsx +++ b/frontend/__tests__/unit/pages/About.test.tsx @@ -70,6 +70,11 @@ jest.mock('utils/aboutData', () => ({ projectTimeline: [ { title: 'Timeline Event 1', description: 'Timeline description 1', year: '2023' }, { title: 'Timeline Event 2', description: 'Timeline description 2', year: '2024' }, + { title: 'Timeline Event 3', description: 'Timeline description 3', year: '2025' }, + { title: 'Timeline Event 4', description: 'Timeline description 4', year: '2026' }, + { title: 'Timeline Event 5', description: 'Timeline description 5', year: '2027' }, + { title: 'Timeline Event 6', description: 'Timeline description 6', year: '2028' }, + { title: 'Timeline Event 7', description: 'Timeline description 7', year: '2029' }, ], technologies: [ { @@ -275,15 +280,33 @@ describe('About Component', () => { render() }) - const projectHistorySection = screen.getByText('Our Story').closest('div') - expect(projectHistorySection).toBeInTheDocument() - - expect(screen.getByText('Timeline Event 1')).toBeInTheDocument() + expect(screen.getByText('Project Timeline')).toBeInTheDocument() + expect(screen.getByText('Timeline Event 7')).toBeInTheDocument() expect(screen.getByText('Timeline Event 2')).toBeInTheDocument() - expect(screen.getByText('Timeline description 1')).toBeInTheDocument() - expect(screen.getByText('Timeline description 2')).toBeInTheDocument() - expect(screen.getByText('2023')).toBeInTheDocument() + expect(screen.queryByText('Timeline Event 1')).not.toBeInTheDocument() + expect(screen.getByText('2029')).toBeInTheDocument() + expect(screen.queryByText('2023')).not.toBeInTheDocument() expect(screen.getByText('2024')).toBeInTheDocument() + + expect(screen.getByText('Timeline Event 7')).toBeInTheDocument() + expect(screen.queryByText('Timeline Event 1')).not.toBeInTheDocument() + + const timelineSection = screen.getByText('Project Timeline').closest('h2')?.parentElement + if (!timelineSection) throw new Error('Could not find Timeline section') + + const showMoreButton = within(timelineSection).getByRole('button', { name: /Show more/i }) + fireEvent.click(showMoreButton) + + await waitFor(() => { + expect(screen.getByText('Timeline Event 1')).toBeInTheDocument() + }) + + const showLessButton = within(timelineSection).getByRole('button', { name: /Show less/i }) + fireEvent.click(showLessButton) + + await waitFor(() => { + expect(screen.queryByText('Timeline Event 1')).not.toBeInTheDocument() + }) }) test('renders leaders section with three leaders', async () => { @@ -327,7 +350,10 @@ describe('About Component', () => { expect(screen.queryByText('Contributor 13')).not.toBeInTheDocument() }) - const showMoreButton = screen.getByRole('button', { name: /Show more/i }) + const wallOfFameSection = screen.getByText('Wall of Fame').closest('h2')?.parentElement + if (!wallOfFameSection) throw new Error('Could not find Wall of Fame section') + + const showMoreButton = within(wallOfFameSection).getByRole('button', { name: /Show more/i }) fireEvent.click(showMoreButton) await waitFor(() => { @@ -336,7 +362,7 @@ describe('About Component', () => { expect(screen.getByText('Contributor 15')).toBeInTheDocument() }) - const showLessButton = screen.getByRole('button', { name: /Show less/i }) + const showLessButton = within(wallOfFameSection).getByRole('button', { name: /Show less/i }) fireEvent.click(showLessButton) await waitFor(() => { @@ -387,17 +413,28 @@ describe('About Component', () => { const roadmapSection = screen.getByRole('heading', { name: /Roadmap/ }).closest('div') expect(roadmapSection).toBeInTheDocument() - const roadmapData = mockAboutData.project.recentMilestones - const links = within(roadmapSection) - .getAllByRole('link') - .filter((link) => link.getAttribute('href') !== '#roadmap') - - 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) - } + const roadmapData = [...mockAboutData.project.recentMilestones].sort((a, b) => + a.title > b.title ? 1 : -1 + ) + + expect(screen.getByText(roadmapData[0].title)).toBeInTheDocument() + expect(screen.getByText(roadmapData[1].title)).toBeInTheDocument() + expect(screen.getByText(roadmapData[2].title)).toBeInTheDocument() + expect(screen.queryByText(roadmapData[3].title)).not.toBeInTheDocument() + + const showMoreButtonRef = within(roadmapSection).getByRole('button', { name: /Show more/i }) + fireEvent.click(showMoreButtonRef) + + await waitFor(() => { + expect(screen.getByText(roadmapData[3].title)).toBeInTheDocument() + }) + + const showLessButtonRef = within(roadmapSection).getByRole('button', { name: /Show less/i }) + fireEvent.click(showLessButtonRef) + + await waitFor(() => { + expect(screen.queryByText(roadmapData[3].title)).not.toBeInTheDocument() + }) }) test('renders project stats cards correctly', async () => { @@ -643,10 +680,10 @@ describe('About Component', () => { render() }) expect(screen.getByText('Project Timeline')).toBeInTheDocument() - expect(screen.getByText('Timeline Event 1')).toBeInTheDocument() + expect(screen.getByText('Project Timeline')).toBeInTheDocument() + expect(screen.getByText('Timeline Event 7')).toBeInTheDocument() expect(screen.getByText('Timeline Event 2')).toBeInTheDocument() - expect(screen.getByText('Timeline description 1')).toBeInTheDocument() - expect(screen.getByText('Timeline description 2')).toBeInTheDocument() + expect(screen.queryByText('Timeline Event 1')).not.toBeInTheDocument() }) test('triggers toaster error when GraphQL request fails for a leader', async () => { diff --git a/frontend/src/app/about/page.tsx b/frontend/src/app/about/page.tsx index aafe8282db..a2001e6c11 100644 --- a/frontend/src/app/about/page.tsx +++ b/frontend/src/app/about/page.tsx @@ -5,7 +5,7 @@ import upperFirst from 'lodash/upperFirst' import millify from 'millify' import Image from 'next/image' import Link from 'next/link' -import { useEffect } from 'react' +import { useState, useEffect } from 'react' import { FaMapSigns, FaTools } from 'react-icons/fa' import { FaCircleCheck, FaClock, FaScroll, FaBullseye, FaUser, FaUsersGear } from 'react-icons/fa6' import { HiUserGroup } from 'react-icons/hi' @@ -28,6 +28,7 @@ import AnchorTitle from 'components/AnchorTitle' import Leaders from 'components/Leaders' import Markdown from 'components/MarkdownWrapper' import SecondaryCard from 'components/SecondaryCard' +import ShowMoreButton from 'components/ShowMoreButton' import AboutSkeleton from 'components/skeletons/AboutSkeleton' import TopContributorsList from 'components/TopContributorsList' @@ -87,6 +88,9 @@ const About = () => { const projectMetadata = projectMetadataResponse?.project const topContributors = topContributorsResponse?.topContributors + const [showAllRoadmap, setShowAllRoadmap] = useState(false) + const [showAllTimeline, setShowAllTimeline] = useState(false) + useEffect(() => { if (projectMetadataRequestError) { handleAppError(projectMetadataRequestError) @@ -202,6 +206,7 @@ const About = () => { {[...projectMetadata.recentMilestones] .filter((milestone) => milestone.state !== 'closed') .sort((a, b) => (a.title > b.title ? 1 : -1)) + .slice(0, showAllRoadmap ? projectMetadata.recentMilestones.length : 3) .map((milestone, index) => (
{
))} + {projectMetadata.recentMilestones.length > 3 && ( + setShowAllRoadmap(!showAllRoadmap)} /> + )} )} }> @@ -250,23 +258,29 @@ const About = () => { }>
- {[...projectTimeline].reverse().map((milestone, index) => ( -
- {index !== projectTimeline.length - 1 && ( -
- )} - -
-

{milestone.title}

-

{milestone.year}

-

{milestone.description}

+ {[...projectTimeline] + .reverse() + .slice(0, showAllTimeline ? projectTimeline.length : 6) + .map((milestone, index) => ( +
+ {index !== projectTimeline.length - 1 && ( +
+ )} + +
+

{milestone.title}

+

{milestone.year}

+

{milestone.description}

+
-
- ))} + ))}
+ {projectTimeline.length > 6 && ( + setShowAllTimeline(!showAllTimeline)} /> + )}
From ffe525ef8f07b5d5eb7b69ffd60331a7e694a8c5 Mon Sep 17 00:00:00 2001 From: Harsh Date: Wed, 14 Jan 2026 23:22:44 +0000 Subject: [PATCH 2/4] fix: resolve CodeRabbit feedback on About page --- frontend/__tests__/e2e/pages/About.spec.ts | 26 ++++++++++++++++++-- frontend/__tests__/unit/pages/About.test.tsx | 1 - frontend/src/app/about/page.tsx | 14 ++++++----- 3 files changed, 32 insertions(+), 9 deletions(-) diff --git a/frontend/__tests__/e2e/pages/About.spec.ts b/frontend/__tests__/e2e/pages/About.spec.ts index 9b968b4dd5..56dd64ddfc 100644 --- a/frontend/__tests__/e2e/pages/About.spec.ts +++ b/frontend/__tests__/e2e/pages/About.spec.ts @@ -71,8 +71,19 @@ test.describe('About Page', () => { test('loads roadmap items correctly', async ({ page }) => { await expect(page.getByRole('heading', { name: 'Roadmap' })).toBeVisible() + + await expect(page.getByText('Contribution Hub title')).toBeVisible() + + const roadmapSection = page + .locator('div') + .filter({ has: page.getByRole('heading', { name: 'Roadmap' }) }) + .filter({ has: page.getByRole('button', { name: 'Show more' }) }) + .last() + + await roadmapSection.getByRole('button', { name: 'Show more' }).click() + for (const milestone of mockAboutData.project.recentMilestones) { - await expect(page.getByText(milestone.title)).toBeVisible() + await expect(page.getByText(milestone.title, { exact: true })).toBeVisible() await expect(page.getByText(milestone.body)).toBeVisible() } }) @@ -99,7 +110,18 @@ test.describe('About Page', () => { }) test('renders project history timeline section', async ({ page }) => { - await expect(page.getByText('Project Timeline')).toBeVisible() + await expect(page.getByRole('heading', { name: 'Project Timeline' })).toBeVisible() + + await expect(page.getByText('OWASP Nest Logo Introduction')).toBeVisible() + + const timelineSection = page + .locator('div') + .filter({ has: page.getByRole('heading', { name: 'Project Timeline' }) }) + .filter({ has: page.getByRole('button', { name: 'Show more' }) }) + .last() + + await timelineSection.getByRole('button', { name: 'Show more' }).click() + await expect(page.getByText('Project Inception')).toBeVisible() }) }) diff --git a/frontend/__tests__/unit/pages/About.test.tsx b/frontend/__tests__/unit/pages/About.test.tsx index 7934189560..9af466b734 100644 --- a/frontend/__tests__/unit/pages/About.test.tsx +++ b/frontend/__tests__/unit/pages/About.test.tsx @@ -680,7 +680,6 @@ describe('About Component', () => { render() }) expect(screen.getByText('Project Timeline')).toBeInTheDocument() - expect(screen.getByText('Project Timeline')).toBeInTheDocument() expect(screen.getByText('Timeline Event 7')).toBeInTheDocument() expect(screen.getByText('Timeline Event 2')).toBeInTheDocument() expect(screen.queryByText('Timeline Event 1')).not.toBeInTheDocument() diff --git a/frontend/src/app/about/page.tsx b/frontend/src/app/about/page.tsx index a2001e6c11..fb747d2835 100644 --- a/frontend/src/app/about/page.tsx +++ b/frontend/src/app/about/page.tsx @@ -258,12 +258,13 @@ const About = () => { }>
- {[...projectTimeline] - .reverse() - .slice(0, showAllTimeline ? projectTimeline.length : 6) - .map((milestone, index) => ( + {(() => { + const visibleTimeline = [...projectTimeline] + .reverse() + .slice(0, showAllTimeline ? projectTimeline.length : 6) + return visibleTimeline.map((milestone, index) => (
- {index !== projectTimeline.length - 1 && ( + {index !== visibleTimeline.length - 1 && (
)}
{

{milestone.description}

- ))} + )) + })()}
{projectTimeline.length > 6 && ( setShowAllTimeline(!showAllTimeline)} /> From 08b983676414cd748f2d1a02bb68885aece7d7ab Mon Sep 17 00:00:00 2001 From: Harsh Date: Thu, 15 Jan 2026 00:12:56 +0000 Subject: [PATCH 3/4] fix: correct roadmap filtering logic for show more button --- frontend/src/app/about/page.tsx | 85 ++++++++++++++++++--------------- 1 file changed, 46 insertions(+), 39 deletions(-) diff --git a/frontend/src/app/about/page.tsx b/frontend/src/app/about/page.tsx index fb747d2835..88492860bd 100644 --- a/frontend/src/app/about/page.tsx +++ b/frontend/src/app/about/page.tsx @@ -202,49 +202,56 @@ const About = () => { {projectMetadata.recentMilestones.length > 0 && ( }> -
- {[...projectMetadata.recentMilestones] + {(() => { + const filteredMilestones = [...projectMetadata.recentMilestones] .filter((milestone) => milestone.state !== 'closed') .sort((a, b) => (a.title > b.title ? 1 : -1)) - .slice(0, showAllRoadmap ? projectMetadata.recentMilestones.length : 3) - .map((milestone, index) => ( -
-
-
- +
+ {filteredMilestones + .slice(0, showAllRoadmap ? filteredMilestones.length : 3) + .map((milestone, index) => ( +
-

- {milestone.title} -

- - - - - - -
-

{milestone.body}

-
+
+
+ +

+ {milestone.title} +

+ + + + + + +
+

{milestone.body}

+
+
+ ))}
- ))} -
- {projectMetadata.recentMilestones.length > 3 && ( - setShowAllRoadmap(!showAllRoadmap)} /> - )} + {filteredMilestones.length > 3 && ( + setShowAllRoadmap(!showAllRoadmap)} /> + )} + + ) + })()} )} }> From ab557b07885d3bb4fa9e748c2179ffec9f94ed39 Mon Sep 17 00:00:00 2001 From: Kate Date: Wed, 14 Jan 2026 22:07:48 -0800 Subject: [PATCH 4/4] Add constants --- frontend/src/app/about/page.tsx | 11 +++++++---- 1 file changed, 7 insertions(+), 4 deletions(-) diff --git a/frontend/src/app/about/page.tsx b/frontend/src/app/about/page.tsx index 88492860bd..122c96b8e6 100644 --- a/frontend/src/app/about/page.tsx +++ b/frontend/src/app/about/page.tsx @@ -38,6 +38,9 @@ const leaders = { mamicidal: 'CISSP', } +const PROJECT_LIMIT = 6 +const MILESTONE_LIMIT = 3 + const projectKey = 'nest' const getMilestoneStatus = (progress: number): string => { @@ -210,7 +213,7 @@ const About = () => { <>
{filteredMilestones - .slice(0, showAllRoadmap ? filteredMilestones.length : 3) + .slice(0, showAllRoadmap ? filteredMilestones.length : MILESTONE_LIMIT) .map((milestone, index) => (
{
))}
- {filteredMilestones.length > 3 && ( + {filteredMilestones.length > MILESTONE_LIMIT && ( setShowAllRoadmap(!showAllRoadmap)} /> )} @@ -268,7 +271,7 @@ const About = () => { {(() => { const visibleTimeline = [...projectTimeline] .reverse() - .slice(0, showAllTimeline ? projectTimeline.length : 6) + .slice(0, showAllTimeline ? projectTimeline.length : PROJECT_LIMIT) return visibleTimeline.map((milestone, index) => (
{index !== visibleTimeline.length - 1 && ( @@ -287,7 +290,7 @@ const About = () => { )) })()}
- {projectTimeline.length > 6 && ( + {projectTimeline.length > PROJECT_LIMIT && ( setShowAllTimeline(!showAllTimeline)} /> )}