From 547ec3c3528a82b2bb39aa33c0bae369f5b1af82 Mon Sep 17 00:00:00 2001 From: AbhayMishra <grabhaymishra@gmail.com> Date: Sun, 26 Jan 2025 03:13:07 +0530 Subject: [PATCH 1/3] Implemented skeleton from chakra ui --- .../__tests__/src/pages/Chapters.test.tsx | 12 +-- .../src/pages/CommitteeDetails.test.tsx | 6 +- .../__tests__/src/pages/Committees.test.tsx | 13 +-- .../__tests__/src/pages/Contribute.test.tsx | 91 ++++-------------- .../__tests__/src/pages/Projects.test.tsx | 12 +-- frontend/__tests__/src/pages/Users.test.tsx | 12 +-- frontend/jest.setup.ts | 2 +- frontend/src/components/SearchPageLayout.tsx | 6 +- frontend/src/components/SkeletonsBase.tsx | 53 +++++++++++ frontend/src/components/skeletons/Card.tsx | 92 +++++++++++++++++++ .../src/components/skeletons/UserCard.tsx | 42 +++++++++ frontend/src/components/ui/skeleton.tsx | 37 ++++++++ frontend/src/pages/CommitteeDetails.tsx | 8 +- frontend/src/wrappers/testUtil.tsx | 7 +- 14 files changed, 283 insertions(+), 110 deletions(-) create mode 100644 frontend/src/components/SkeletonsBase.tsx create mode 100644 frontend/src/components/skeletons/Card.tsx create mode 100644 frontend/src/components/skeletons/UserCard.tsx create mode 100644 frontend/src/components/ui/skeleton.tsx diff --git a/frontend/__tests__/src/pages/Chapters.test.tsx b/frontend/__tests__/src/pages/Chapters.test.tsx index 820a4135f..bc0748a1e 100644 --- a/frontend/__tests__/src/pages/Chapters.test.tsx +++ b/frontend/__tests__/src/pages/Chapters.test.tsx @@ -35,11 +35,11 @@ describe('ChaptersPage Component', () => { jest.clearAllMocks() }) - test('renders loading spinner initially', async () => { + test('renders skeleton initially', async () => { render(<ChaptersPage />) - const loadingSpinner = screen.getAllByAltText('Loading indicator') await waitFor(() => { - expect(loadingSpinner.length).toBeGreaterThan(0) + const skeletonLoaders = screen.getAllByTestId('skeleton-loader') + expect(skeletonLoaders.length).toBeGreaterThan(0) }) }) @@ -86,9 +86,9 @@ describe('ChaptersPage Component', () => { }) render(<ChaptersPage />) - const loadingSpinner = screen.getAllByAltText('Loading indicator') + const skeletonLoaders = screen.getAllByTestId('skeleton-loader') await waitFor(() => { - expect(loadingSpinner.length).toBeGreaterThan(0) + expect(skeletonLoaders.length).toBeGreaterThan(0) expect(screen.queryByText('Next Page')).not.toBeInTheDocument() }) await waitFor(() => { @@ -97,7 +97,7 @@ describe('ChaptersPage Component', () => { expect(screen.getByText('Next Page')).toBeInTheDocument() }) - expect(screen.queryByAltText('Loading indicator')).not.toBeInTheDocument() + expect(screen.queryByTestId('skeleton-loader')).not.toBeInTheDocument() }) test('opens window on View Details button click', async () => { const navigateMock = jest.fn() diff --git a/frontend/__tests__/src/pages/CommitteeDetails.test.tsx b/frontend/__tests__/src/pages/CommitteeDetails.test.tsx index 9df0dd72b..4aa13e678 100644 --- a/frontend/__tests__/src/pages/CommitteeDetails.test.tsx +++ b/frontend/__tests__/src/pages/CommitteeDetails.test.tsx @@ -35,11 +35,11 @@ describe('Committees Component', () => { jest.clearAllMocks() }) - test('renders loading spinner initially', async () => { + test('renders skeleton initially', async () => { render(<CommitteeDetailsPage />) - const loadingSpinner = screen.getAllByAltText('Loading indicator') await waitFor(() => { - expect(loadingSpinner.length).toBeGreaterThan(0) + const skeletonLoaders = screen.getAllByTestId('skeleton-loader') + expect(skeletonLoaders.length).toBeGreaterThan(0) }) }) diff --git a/frontend/__tests__/src/pages/Committees.test.tsx b/frontend/__tests__/src/pages/Committees.test.tsx index b7d7ff9d6..a1547a697 100644 --- a/frontend/__tests__/src/pages/Committees.test.tsx +++ b/frontend/__tests__/src/pages/Committees.test.tsx @@ -36,11 +36,11 @@ describe('Committees Component', () => { jest.clearAllMocks() }) - test('renders loading spinner initially', async () => { + test('renders skeleton initially', async () => { render(<CommitteesPage />) - const loadingSpinner = screen.getAllByAltText('Loading indicator') await waitFor(() => { - expect(loadingSpinner.length).toBeGreaterThan(0) + const skeletonLoaders = screen.getAllByTestId('skeleton-loader') + expect(skeletonLoaders.length).toBeGreaterThan(0) }) }) @@ -53,11 +53,8 @@ describe('Committees Component', () => { render(<CommitteesPage />) - const loadingSpinner = screen.getAllByAltText('Loading indicator') await waitFor(() => { - expect(loadingSpinner.length).toBeGreaterThan(0) - - expect(screen.queryByText('Next Page')).not.toBeInTheDocument() + expect(screen.queryByTestId('skeleton-loader')).not.toBeInTheDocument() }) await waitFor(() => { @@ -65,7 +62,7 @@ describe('Committees Component', () => { expect(screen.getByText('Committee 1')).toBeInTheDocument() expect(screen.getByText('Next Page')).toBeInTheDocument() }) - expect(screen.queryByAltText('Loading indicator')).not.toBeInTheDocument() + expect(screen.queryByTestId('skeleton-loader')).not.toBeInTheDocument() }) test('renders committee data correctly', async () => { diff --git a/frontend/__tests__/src/pages/Contribute.test.tsx b/frontend/__tests__/src/pages/Contribute.test.tsx index 562d82c17..c61f2badc 100644 --- a/frontend/__tests__/src/pages/Contribute.test.tsx +++ b/frontend/__tests__/src/pages/Contribute.test.tsx @@ -1,7 +1,6 @@ -import { fireEvent, render, screen, waitFor } from '@testing-library/react' - +import { fireEvent, screen, waitFor } from '@testing-library/react' import { fetchAlgoliaData } from 'api/fetchAlgoliaData' -import { MemoryRouter } from 'react-router-dom' +import { render } from 'wrappers/testUtil' import ContributePage from 'pages/Contribute' @@ -30,15 +29,11 @@ describe('Contribute Component', () => { jest.clearAllMocks() }) - test('renders loading spinner initially', async () => { - render( - <MemoryRouter> - <ContributePage /> - </MemoryRouter> - ) - const loadingSpinner = screen.getAllByAltText('Loading indicator') + test('renders skeleton initially', async () => { + render(<ContributePage />) await waitFor(() => { - expect(loadingSpinner.length).toBeGreaterThan(0) + const skeletonLoaders = screen.getAllByTestId('skeleton-loader') + expect(skeletonLoaders.length).toBeGreaterThan(0) }) }) @@ -48,11 +43,7 @@ describe('Contribute Component', () => { hits: mockContributeData.issues, totalPages: 1, }) - render( - <MemoryRouter> - <ContributePage /> - </MemoryRouter> - ) + render(<ContributePage />) await waitFor(() => { expect(screen.getByText('Contribution 1')).toBeInTheDocument() @@ -68,11 +59,7 @@ describe('Contribute Component', () => { issues: [], totalPages: 0, }) - render( - <MemoryRouter> - <ContributePage /> - </MemoryRouter> - ) + render(<ContributePage />) await waitFor(() => { expect(screen.getByText('No issues found')).toBeInTheDocument() }) @@ -85,11 +72,7 @@ describe('Contribute Component', () => { hits: mockContributeData.issues, totalPages: 4, }) - render( - <MemoryRouter> - <ContributePage /> - </MemoryRouter> - ) + render(<ContributePage />) await waitFor(() => { const nextPageButton = screen.getByText('Next Page') fireEvent.click(nextPageButton) @@ -105,11 +88,7 @@ describe('Contribute Component', () => { totalPages: 2, currentPage: 1, }) - render( - <MemoryRouter> - <ContributePage /> - </MemoryRouter> - ) + render(<ContributePage />) await waitFor(() => { expect(screen.getByText('Next Page')).toBeInTheDocument() }) @@ -121,11 +100,7 @@ describe('Contribute Component', () => { totalPages: 2, currentPage: 2, }) - render( - <MemoryRouter> - <ContributePage /> - </MemoryRouter> - ) + render(<ContributePage />) await waitFor(() => { expect(screen.queryByText('Next Page')).not.toBeInTheDocument() }) @@ -136,29 +111,21 @@ describe('Contribute Component', () => { ...mockContributeData, total_pages: 1, }) - render( - <MemoryRouter> - <ContributePage /> - </MemoryRouter> - ) + render(<ContributePage />) await waitFor(() => { expect(screen.queryByText('Next Page')).not.toBeInTheDocument() }) }) test('handles search functionality', async () => { - render( - <MemoryRouter> - <ContributePage /> - </MemoryRouter> - ) + render(<ContributePage />) await waitFor(() => { const searchInput = screen.getByPlaceholderText('Search for OWASP issues...') fireEvent.change(searchInput, { target: { value: '' } }) }) - expect(fetchAlgoliaData).toHaveBeenCalledWith('issues', '', 1) + expect(fetchAlgoliaData).toHaveBeenCalledWith('issues', '', 2) }) test('handles error states in card rendering', async () => { @@ -174,11 +141,7 @@ describe('Contribute Component', () => { } ;(fetchAlgoliaData as jest.Mock).mockResolvedValue(mockErrorIssue) - render( - <MemoryRouter> - <ContributePage /> - </MemoryRouter> - ) + render(<ContributePage />) await waitFor(() => { expect(screen.queryByText('Read More')).not.toBeInTheDocument() @@ -191,11 +154,7 @@ describe('Contribute Component', () => { hits: mockContributeData.issues, totalPages: 1, }) - render( - <MemoryRouter> - <ContributePage /> - </MemoryRouter> - ) + render(<ContributePage />) await waitFor(() => { const readMoreButton = screen.getByText('Read More') @@ -209,11 +168,7 @@ describe('Contribute Component', () => { hits: mockContributeData.issues, totalPages: 1, }) - render( - <MemoryRouter> - <ContributePage /> - </MemoryRouter> - ) + render(<ContributePage />) await waitFor(() => { const readMoreButton = screen.getByText('Read More') @@ -232,11 +187,7 @@ describe('Contribute Component', () => { hits: mockContributeData.issues, totalPages: 1, }) - render( - <MemoryRouter> - <ContributePage /> - </MemoryRouter> - ) + render(<ContributePage />) await waitFor(() => { const readMoreButton = screen.getByText('Read More') @@ -264,11 +215,7 @@ describe('Contribute Component', () => { } ;(fetchAlgoliaData as jest.Mock).mockResolvedValue(mockMultipleIssues) - render( - <MemoryRouter> - <ContributePage /> - </MemoryRouter> - ) + render(<ContributePage />) // Wait for both cards to be rendered await waitFor(() => { diff --git a/frontend/__tests__/src/pages/Projects.test.tsx b/frontend/__tests__/src/pages/Projects.test.tsx index 5f3104341..04ade3567 100644 --- a/frontend/__tests__/src/pages/Projects.test.tsx +++ b/frontend/__tests__/src/pages/Projects.test.tsx @@ -35,11 +35,11 @@ describe('ProjectPage Component', () => { jest.clearAllMocks() }) - test('renders loading spinner initially', async () => { + test('renders skeleton initially', async () => { render(<ProjectsPage />) - const loadingSpinner = screen.getAllByAltText('Loading indicator') await waitFor(() => { - expect(loadingSpinner.length).toBeGreaterThan(0) + const skeletonLoaders = screen.getAllByTestId('skeleton-loader') + expect(skeletonLoaders.length).toBeGreaterThan(0) }) }) @@ -52,9 +52,9 @@ describe('ProjectPage Component', () => { render(<ProjectsPage />) - const loadingSpinner = screen.getAllByAltText('Loading indicator') + const skeletonLoaders = screen.getAllByTestId('skeleton-loader') await waitFor(() => { - expect(loadingSpinner.length).toBeGreaterThan(0) + expect(skeletonLoaders.length).toBeGreaterThan(0) expect(screen.queryByText('Next Page')).not.toBeInTheDocument() }) await waitFor(() => { @@ -63,7 +63,7 @@ describe('ProjectPage Component', () => { expect(screen.getByText('Next Page')).toBeInTheDocument() }) - expect(screen.queryByAltText('Loading indicator')).not.toBeInTheDocument() + expect(screen.queryByTestId('skeleton-loader')).not.toBeInTheDocument() }) test('renders project data correctly', async () => { diff --git a/frontend/__tests__/src/pages/Users.test.tsx b/frontend/__tests__/src/pages/Users.test.tsx index f12617140..4fe5680d5 100644 --- a/frontend/__tests__/src/pages/Users.test.tsx +++ b/frontend/__tests__/src/pages/Users.test.tsx @@ -39,11 +39,11 @@ describe('UsersPage Component', () => { jest.clearAllMocks() }) - test('renders loading spinner initially', async () => { + test('renders skeleton initially', async () => { render(<UsersPage />) - const loadingSpinner = screen.getAllByAltText('Loading indicator') await waitFor(() => { - expect(loadingSpinner.length).toBeGreaterThan(0) + const skeletonLoaders = screen.getAllByTestId('skeleton-loader') + expect(skeletonLoaders.length).toBeGreaterThan(0) }) }) @@ -52,9 +52,9 @@ describe('UsersPage Component', () => { render(<UsersPage />) // Check loading state - const loadingSpinner = screen.getAllByAltText('Loading indicator') + const skeletonLoaders = screen.getAllByTestId('skeleton-loader') await waitFor(() => { - expect(loadingSpinner.length).toBeGreaterThan(0) + expect(skeletonLoaders.length).toBeGreaterThan(0) expect(screen.queryByText('Next Page')).not.toBeInTheDocument() }) @@ -65,7 +65,7 @@ describe('UsersPage Component', () => { expect(screen.getByText('Next Page')).toBeInTheDocument() }) - expect(screen.queryByAltText('Loading indicator')).not.toBeInTheDocument() + expect(screen.queryByTestId('skeleton-loader')).not.toBeInTheDocument() }) test('renders user cards correctly', async () => { diff --git a/frontend/jest.setup.ts b/frontend/jest.setup.ts index 928fcbe07..154ad5abe 100644 --- a/frontend/jest.setup.ts +++ b/frontend/jest.setup.ts @@ -7,7 +7,7 @@ dotenv.config() global.React = React global.TextEncoder = TextEncoder - +global.structuredClone = (val) => JSON.parse(JSON.stringify(val)) beforeEach(() => { jest.spyOn(console, 'error').mockImplementation((...args) => { throw new Error(`Console error: ${args.join(' ')}`) diff --git a/frontend/src/components/SearchPageLayout.tsx b/frontend/src/components/SearchPageLayout.tsx index c91f54fdc..ab33ae17c 100644 --- a/frontend/src/components/SearchPageLayout.tsx +++ b/frontend/src/components/SearchPageLayout.tsx @@ -1,7 +1,7 @@ import React, { useState } from 'react' -import LoadingSpinner from 'components/LoadingSpinner' import Pagination from 'components/Pagination' import SearchBar from 'components/Search' +import SkeletonBase from 'components/SkeletonsBase' interface SearchPageLayoutProps { isLoaded: boolean @@ -53,9 +53,7 @@ const SearchPageLayout = ({ <div>{sortChildren}</div> </div> {!isSearchBarReady || !isLoaded ? ( - <div className="mt-20 flex h-64 w-full items-center justify-center"> - <LoadingSpinner imageUrl={loadingImageUrl} /> - </div> + <SkeletonBase indexName={indexName} loadingImageUrl={loadingImageUrl} /> ) : ( <> <div> diff --git a/frontend/src/components/SkeletonsBase.tsx b/frontend/src/components/SkeletonsBase.tsx new file mode 100644 index 000000000..12221aa20 --- /dev/null +++ b/frontend/src/components/SkeletonsBase.tsx @@ -0,0 +1,53 @@ +import LoadingSpinner from 'components/LoadingSpinner' +import CardSkeleton from 'components/skeletons/Card' +import UserCardSkeleton from 'components/skeletons/UserCard' +import { Skeleton } from 'components/ui/skeleton' + +function userCardRender() { + const cardCount = 12 + return ( + <div className="grid grid-cols-1 gap-6 sm:grid-cols-2 lg:grid-cols-3 xl:grid-cols-4"> + {Array.from({ length: cardCount }).map((_, index) => ( + <UserCardSkeleton key={index} /> + ))} + </div> + ) +} + +const SkeletonBase = ({ indexName, loadingImageUrl }) => { + let Component + switch (indexName) { + case 'chapters': + Component = () => <CardSkeleton showLevel={false} showIcons={1} showLink={false} /> + break + case 'issues': + Component = () => ( + <CardSkeleton showLevel={false} showIcons={2} showContributors={false} showSocial={false} /> + ) + break + case 'projects': + Component = () => <CardSkeleton showLink={false} showSocial={false} /> + break + case 'committees': + Component = () => <CardSkeleton showLink={false} showLevel={false} showIcons={1} /> + break + case 'users': + return userCardRender() + default: + return <LoadingSpinner imageUrl={loadingImageUrl} /> + } + return ( + <div className="flex w-full flex-col items-center justify-center"> + {indexName == 'chapters' ? ( + <Skeleton className="mb-2 w-full max-w-6xl" h={400} /> + ) : ( + <Component /> + )} + <Component /> + <Component /> + <Component /> + </div> + ) +} + +export default SkeletonBase diff --git a/frontend/src/components/skeletons/Card.tsx b/frontend/src/components/skeletons/Card.tsx new file mode 100644 index 000000000..253cb833b --- /dev/null +++ b/frontend/src/components/skeletons/Card.tsx @@ -0,0 +1,92 @@ +import { Box, Flex } from '@chakra-ui/react' +import type React from 'react' +import { Skeleton, SkeletonCircle, SkeletonText } from 'components/ui/skeleton' + +interface CardSkeletonProps { + showLevel?: boolean + showIcons?: number + showProjectName?: boolean + showSummary?: boolean + showLink?: boolean + showContributors?: boolean + showSocial?: boolean + showActionButton?: boolean +} + +const CardSkeleton: React.FC<CardSkeletonProps> = ({ + showLevel = true, + showIcons = 4, + showProjectName = true, + showSummary = true, + showLink = true, + showContributors = true, + showSocial = true, + showActionButton = true, +}) => { + const NUM_CONTRIBUTORS = 8 + + return ( + <div data-testid="skeleton-loader" className="flex w-full justify-center"> + <Box className="mb-6 w-full rounded-lg border border-border bg-card p-6 transition-colors duration-300 ease-linear hover:bg-accent/10 md:max-w-6xl"> + <Flex direction="column" className="flex flex-col sm:flex-row" gap={6}> + {/* Header Section */} + <Flex className="flex w-full flex-col items-start justify-between gap-4 sm:flex-row"> + <Flex className="items-center gap-4"> + {showLevel && <SkeletonCircle className="h-10 w-10" />} + <Flex direction="column" gap={2}> + {showProjectName && <Skeleton className="h-8 w-[180px] sm:w-[250px]" />} + </Flex> + </Flex> + + {showIcons && ( + <Flex className="flex min-w-[30%] flex-grow flex-row items-center justify-end gap-2 overflow-auto"> + {Array.from({ length: showIcons }).map((_, i) => ( + <Skeleton key={i} className="h-8 w-16" /> + ))} + <Skeleton /> + </Flex> + )} + </Flex> + + {/* Link Section */} + {showLink && <SkeletonText className="w-[180px] md:w-[350px]" noOfLines={1} />} + + {/* Description Section */} + {showSummary && <SkeletonText className="space-y-3" noOfLines={4} />} + + {/* Footer Section */} + <Flex className="items-center justify-between gap-4 pt-3"> + <div className="flex flex-col justify-start gap-2"> + {showContributors && ( + <Flex className="items-center space-x-2"> + {[...Array(NUM_CONTRIBUTORS)].map((_, i) => ( + <SkeletonCircle + key={i} + className="h-[30px] w-[30px] border-2 border-background" + /> + ))} + </Flex> + )} + {showSocial && ( + <Flex className="space-x-2"> + <SkeletonCircle className="h-5 w-5" /> + <SkeletonCircle className="h-5 w-5" /> + <SkeletonCircle className="h-5 w-5" /> + <SkeletonCircle className="h-5 w-5" /> + <SkeletonCircle className="h-5 w-5" /> + <SkeletonCircle className="h-5 w-5" /> + </Flex> + )} + </div> + + <Flex gap={4} className="ml-auto items-center"> + {showActionButton && <Skeleton className="h-9 w-[100px]" />} + </Flex> + </Flex> + </Flex> + </Box> + </div> + ) +} + +export default CardSkeleton diff --git a/frontend/src/components/skeletons/UserCard.tsx b/frontend/src/components/skeletons/UserCard.tsx new file mode 100644 index 000000000..0792a5fa5 --- /dev/null +++ b/frontend/src/components/skeletons/UserCard.tsx @@ -0,0 +1,42 @@ +import { Box, Flex } from '@chakra-ui/react' +import type React from 'react' +import { Skeleton, SkeletonCircle } from 'components/ui/skeleton' + +interface UserCardSkeletonProps { + showAvatar?: boolean + showName?: boolean + showViewProfile?: boolean +} + +const UserCardSkeleton: React.FC<UserCardSkeletonProps> = ({ + showAvatar = true, + showName = true, + showViewProfile = true, +}) => { + return ( + <Box + data-testid="skeleton-loader" + className="group flex h-64 w-80 flex-col items-center rounded-lg bg-white p-6 text-left shadow-lg dark:bg-gray-800 dark:shadow-gray-900/30" + > + <Flex direction="column" className="w-full items-center space-y-4"> + {showAvatar && ( + <Box className="relative h-20 w-20 overflow-hidden rounded-full ring-2 ring-gray-100 dark:ring-gray-700"> + <SkeletonCircle className="h-full w-full" /> + </Box> + )} + + <Flex direction="column" className="w-full items-center space-y-2"> + {showName && <Skeleton className="h-7 w-40" />} + </Flex> + </Flex> + + {showViewProfile && ( + <Flex className="mt-auto items-center justify-center"> + <Skeleton className="h-5 w-24" /> + </Flex> + )} + </Box> + ) +} + +export default UserCardSkeleton diff --git a/frontend/src/components/ui/skeleton.tsx b/frontend/src/components/ui/skeleton.tsx new file mode 100644 index 000000000..76b729ef5 --- /dev/null +++ b/frontend/src/components/ui/skeleton.tsx @@ -0,0 +1,37 @@ +import type { SkeletonProps as ChakraSkeletonProps, CircleProps } from '@chakra-ui/react' +import { Skeleton as ChakraSkeleton, Circle, Stack } from '@chakra-ui/react' +import * as React from 'react' + +export interface SkeletonCircleProps extends ChakraSkeletonProps { + size?: CircleProps['size'] +} + +export const SkeletonCircle = React.forwardRef<HTMLDivElement, SkeletonCircleProps>( + function SkeletonCircle(props, ref) { + const { size, ...rest } = props + return ( + <Circle size={size} asChild ref={ref}> + <ChakraSkeleton {...rest} /> + </Circle> + ) + } +) + +export interface SkeletonTextProps extends ChakraSkeletonProps { + noOfLines?: number +} + +export const SkeletonText = React.forwardRef<HTMLDivElement, SkeletonTextProps>( + function SkeletonText(props, ref) { + const { noOfLines = 3, gap, ...rest } = props + return ( + <Stack gap={gap} width="full" ref={ref}> + {Array.from({ length: noOfLines }).map((_, index) => ( + <ChakraSkeleton height="4" key={index} {...props} _last={{ maxW: '80%' }} {...rest} /> + ))} + </Stack> + ) + } +) + +export const Skeleton = ChakraSkeleton diff --git a/frontend/src/pages/CommitteeDetails.tsx b/frontend/src/pages/CommitteeDetails.tsx index c3eab91c2..b19596b14 100644 --- a/frontend/src/pages/CommitteeDetails.tsx +++ b/frontend/src/pages/CommitteeDetails.tsx @@ -5,7 +5,7 @@ import { getFilteredIcons, handleSocialUrls } from 'utils/utility' import { ErrorDisplay } from 'wrappers/ErrorWrapper' import FontAwesomeIconWrapper from 'wrappers/FontAwesomeIconWrapper' import Card from 'components/Card' -import LoadingSpinner from 'components/LoadingSpinner' +import CardSkeleton from 'components/skeletons/Card' const CommitteeDetailsPage = () => { const { committeeKey } = useParams() @@ -26,8 +26,10 @@ const CommitteeDetailsPage = () => { }, [committeeKey]) if (isLoading) return ( - <div className="flex min-h-[60vh] items-center justify-center"> - <LoadingSpinner imageUrl="/img/owasp_icon_white_sm.png" /> + <div className="mt-16 flex w-full flex-col items-center justify-center"> + <div className="w-full pt-12"> + <CardSkeleton showLink={false} showLevel={false} showIcons={1} /> + </div> </div> ) diff --git a/frontend/src/wrappers/testUtil.tsx b/frontend/src/wrappers/testUtil.tsx index 600789582..32339e784 100644 --- a/frontend/src/wrappers/testUtil.tsx +++ b/frontend/src/wrappers/testUtil.tsx @@ -1,9 +1,14 @@ +import { ChakraProvider, defaultSystem } from '@chakra-ui/react' import { render } from '@testing-library/react' import { ReactNode } from 'react' import { BrowserRouter } from 'react-router-dom' const customRender = (ui: ReactNode) => { - return render(<BrowserRouter>{ui}</BrowserRouter>) + return render( + <BrowserRouter> + <ChakraProvider value={defaultSystem}>{ui}</ChakraProvider> + </BrowserRouter> + ) } export * from '@testing-library/react' From 39174a86220d31715131196f129d5d139f051718 Mon Sep 17 00:00:00 2001 From: AbhayMishra <grabhaymishra@gmail.com> Date: Sun, 26 Jan 2025 03:38:31 +0530 Subject: [PATCH 2/3] Added a separate type file for skeleton --- frontend/src/components/skeletons/Card.tsx | 12 +----------- frontend/src/components/skeletons/UserCard.tsx | 7 +------ frontend/src/types/skeleton.ts | 16 ++++++++++++++++ 3 files changed, 18 insertions(+), 17 deletions(-) create mode 100644 frontend/src/types/skeleton.ts diff --git a/frontend/src/components/skeletons/Card.tsx b/frontend/src/components/skeletons/Card.tsx index 253cb833b..a8b71ead8 100644 --- a/frontend/src/components/skeletons/Card.tsx +++ b/frontend/src/components/skeletons/Card.tsx @@ -1,18 +1,8 @@ import { Box, Flex } from '@chakra-ui/react' import type React from 'react' +import { CardSkeletonProps } from 'types/skeleton' import { Skeleton, SkeletonCircle, SkeletonText } from 'components/ui/skeleton' -interface CardSkeletonProps { - showLevel?: boolean - showIcons?: number - showProjectName?: boolean - showSummary?: boolean - showLink?: boolean - showContributors?: boolean - showSocial?: boolean - showActionButton?: boolean -} - const CardSkeleton: React.FC<CardSkeletonProps> = ({ showLevel = true, showIcons = 4, diff --git a/frontend/src/components/skeletons/UserCard.tsx b/frontend/src/components/skeletons/UserCard.tsx index 0792a5fa5..c12a0e336 100644 --- a/frontend/src/components/skeletons/UserCard.tsx +++ b/frontend/src/components/skeletons/UserCard.tsx @@ -1,13 +1,8 @@ import { Box, Flex } from '@chakra-ui/react' import type React from 'react' +import { UserCardSkeletonProps } from 'types/skeleton' import { Skeleton, SkeletonCircle } from 'components/ui/skeleton' -interface UserCardSkeletonProps { - showAvatar?: boolean - showName?: boolean - showViewProfile?: boolean -} - const UserCardSkeleton: React.FC<UserCardSkeletonProps> = ({ showAvatar = true, showName = true, diff --git a/frontend/src/types/skeleton.ts b/frontend/src/types/skeleton.ts new file mode 100644 index 000000000..f4556d7de --- /dev/null +++ b/frontend/src/types/skeleton.ts @@ -0,0 +1,16 @@ +export interface CardSkeletonProps { + showLevel?: boolean + showIcons?: number + showProjectName?: boolean + showSummary?: boolean + showLink?: boolean + showContributors?: boolean + showSocial?: boolean + showActionButton?: boolean +} + +export interface UserCardSkeletonProps { + showAvatar?: boolean + showName?: boolean + showViewProfile?: boolean +} From 1e4fef1bd6f5849f3647b323c34f818337eacb5f Mon Sep 17 00:00:00 2001 From: AbhayMishra <grabhaymishra@gmail.com> Date: Mon, 27 Jan 2025 23:20:33 +0530 Subject: [PATCH 3/3] Naming Convention and add testing from role --- frontend/__tests__/src/pages/Chapters.test.tsx | 6 +++--- frontend/__tests__/src/pages/CommitteeDetails.test.tsx | 2 +- frontend/__tests__/src/pages/Committees.test.tsx | 6 +++--- frontend/__tests__/src/pages/Contribute.test.tsx | 2 +- frontend/__tests__/src/pages/Projects.test.tsx | 6 +++--- frontend/__tests__/src/pages/Users.test.tsx | 6 +++--- frontend/src/components/SkeletonsBase.tsx | 2 +- frontend/src/components/skeletons/Card.tsx | 4 ++-- frontend/src/components/skeletons/UserCard.tsx | 4 ++-- frontend/src/components/ui/{skeleton.tsx => Skeleton.tsx} | 0 10 files changed, 19 insertions(+), 19 deletions(-) rename frontend/src/components/ui/{skeleton.tsx => Skeleton.tsx} (100%) diff --git a/frontend/__tests__/src/pages/Chapters.test.tsx b/frontend/__tests__/src/pages/Chapters.test.tsx index bc0748a1e..64189123d 100644 --- a/frontend/__tests__/src/pages/Chapters.test.tsx +++ b/frontend/__tests__/src/pages/Chapters.test.tsx @@ -38,7 +38,7 @@ describe('ChaptersPage Component', () => { test('renders skeleton initially', async () => { render(<ChaptersPage />) await waitFor(() => { - const skeletonLoaders = screen.getAllByTestId('skeleton-loader') + const skeletonLoaders = screen.getAllByRole('status') expect(skeletonLoaders.length).toBeGreaterThan(0) }) }) @@ -86,7 +86,7 @@ describe('ChaptersPage Component', () => { }) render(<ChaptersPage />) - const skeletonLoaders = screen.getAllByTestId('skeleton-loader') + const skeletonLoaders = screen.getAllByRole('status') await waitFor(() => { expect(skeletonLoaders.length).toBeGreaterThan(0) expect(screen.queryByText('Next Page')).not.toBeInTheDocument() @@ -97,7 +97,7 @@ describe('ChaptersPage Component', () => { expect(screen.getByText('Next Page')).toBeInTheDocument() }) - expect(screen.queryByTestId('skeleton-loader')).not.toBeInTheDocument() + expect(screen.queryByTestId('status')).not.toBeInTheDocument() }) test('opens window on View Details button click', async () => { const navigateMock = jest.fn() diff --git a/frontend/__tests__/src/pages/CommitteeDetails.test.tsx b/frontend/__tests__/src/pages/CommitteeDetails.test.tsx index 4aa13e678..7edf1898d 100644 --- a/frontend/__tests__/src/pages/CommitteeDetails.test.tsx +++ b/frontend/__tests__/src/pages/CommitteeDetails.test.tsx @@ -38,7 +38,7 @@ describe('Committees Component', () => { test('renders skeleton initially', async () => { render(<CommitteeDetailsPage />) await waitFor(() => { - const skeletonLoaders = screen.getAllByTestId('skeleton-loader') + const skeletonLoaders = screen.getAllByRole('status') expect(skeletonLoaders.length).toBeGreaterThan(0) }) }) diff --git a/frontend/__tests__/src/pages/Committees.test.tsx b/frontend/__tests__/src/pages/Committees.test.tsx index a1547a697..0c50adec9 100644 --- a/frontend/__tests__/src/pages/Committees.test.tsx +++ b/frontend/__tests__/src/pages/Committees.test.tsx @@ -39,7 +39,7 @@ describe('Committees Component', () => { test('renders skeleton initially', async () => { render(<CommitteesPage />) await waitFor(() => { - const skeletonLoaders = screen.getAllByTestId('skeleton-loader') + const skeletonLoaders = screen.getAllByRole('status') expect(skeletonLoaders.length).toBeGreaterThan(0) }) }) @@ -54,7 +54,7 @@ describe('Committees Component', () => { render(<CommitteesPage />) await waitFor(() => { - expect(screen.queryByTestId('skeleton-loader')).not.toBeInTheDocument() + expect(screen.queryByRole('status')).not.toBeInTheDocument() }) await waitFor(() => { @@ -62,7 +62,7 @@ describe('Committees Component', () => { expect(screen.getByText('Committee 1')).toBeInTheDocument() expect(screen.getByText('Next Page')).toBeInTheDocument() }) - expect(screen.queryByTestId('skeleton-loader')).not.toBeInTheDocument() + expect(screen.queryByRole('status')).not.toBeInTheDocument() }) test('renders committee data correctly', async () => { diff --git a/frontend/__tests__/src/pages/Contribute.test.tsx b/frontend/__tests__/src/pages/Contribute.test.tsx index c61f2badc..1b5f9efeb 100644 --- a/frontend/__tests__/src/pages/Contribute.test.tsx +++ b/frontend/__tests__/src/pages/Contribute.test.tsx @@ -32,7 +32,7 @@ describe('Contribute Component', () => { test('renders skeleton initially', async () => { render(<ContributePage />) await waitFor(() => { - const skeletonLoaders = screen.getAllByTestId('skeleton-loader') + const skeletonLoaders = screen.getAllByRole('status') expect(skeletonLoaders.length).toBeGreaterThan(0) }) }) diff --git a/frontend/__tests__/src/pages/Projects.test.tsx b/frontend/__tests__/src/pages/Projects.test.tsx index 04ade3567..ca7e0b682 100644 --- a/frontend/__tests__/src/pages/Projects.test.tsx +++ b/frontend/__tests__/src/pages/Projects.test.tsx @@ -38,7 +38,7 @@ describe('ProjectPage Component', () => { test('renders skeleton initially', async () => { render(<ProjectsPage />) await waitFor(() => { - const skeletonLoaders = screen.getAllByTestId('skeleton-loader') + const skeletonLoaders = screen.getAllByRole('status') expect(skeletonLoaders.length).toBeGreaterThan(0) }) }) @@ -52,7 +52,7 @@ describe('ProjectPage Component', () => { render(<ProjectsPage />) - const skeletonLoaders = screen.getAllByTestId('skeleton-loader') + const skeletonLoaders = screen.getAllByRole('status') await waitFor(() => { expect(skeletonLoaders.length).toBeGreaterThan(0) expect(screen.queryByText('Next Page')).not.toBeInTheDocument() @@ -63,7 +63,7 @@ describe('ProjectPage Component', () => { expect(screen.getByText('Next Page')).toBeInTheDocument() }) - expect(screen.queryByTestId('skeleton-loader')).not.toBeInTheDocument() + expect(screen.queryByTestId('status')).not.toBeInTheDocument() }) test('renders project data correctly', async () => { diff --git a/frontend/__tests__/src/pages/Users.test.tsx b/frontend/__tests__/src/pages/Users.test.tsx index 4fe5680d5..edbdceea8 100644 --- a/frontend/__tests__/src/pages/Users.test.tsx +++ b/frontend/__tests__/src/pages/Users.test.tsx @@ -42,7 +42,7 @@ describe('UsersPage Component', () => { test('renders skeleton initially', async () => { render(<UsersPage />) await waitFor(() => { - const skeletonLoaders = screen.getAllByTestId('skeleton-loader') + const skeletonLoaders = screen.getAllByRole('status') expect(skeletonLoaders.length).toBeGreaterThan(0) }) }) @@ -52,7 +52,7 @@ describe('UsersPage Component', () => { render(<UsersPage />) // Check loading state - const skeletonLoaders = screen.getAllByTestId('skeleton-loader') + const skeletonLoaders = screen.getAllByRole('status') await waitFor(() => { expect(skeletonLoaders.length).toBeGreaterThan(0) expect(screen.queryByText('Next Page')).not.toBeInTheDocument() @@ -65,7 +65,7 @@ describe('UsersPage Component', () => { expect(screen.getByText('Next Page')).toBeInTheDocument() }) - expect(screen.queryByTestId('skeleton-loader')).not.toBeInTheDocument() + expect(screen.queryByTestId('status')).not.toBeInTheDocument() }) test('renders user cards correctly', async () => { diff --git a/frontend/src/components/SkeletonsBase.tsx b/frontend/src/components/SkeletonsBase.tsx index 12221aa20..2a0fcd9e7 100644 --- a/frontend/src/components/SkeletonsBase.tsx +++ b/frontend/src/components/SkeletonsBase.tsx @@ -1,7 +1,7 @@ import LoadingSpinner from 'components/LoadingSpinner' import CardSkeleton from 'components/skeletons/Card' import UserCardSkeleton from 'components/skeletons/UserCard' -import { Skeleton } from 'components/ui/skeleton' +import { Skeleton } from 'components/ui/Skeleton' function userCardRender() { const cardCount = 12 diff --git a/frontend/src/components/skeletons/Card.tsx b/frontend/src/components/skeletons/Card.tsx index a8b71ead8..59b175df4 100644 --- a/frontend/src/components/skeletons/Card.tsx +++ b/frontend/src/components/skeletons/Card.tsx @@ -1,7 +1,7 @@ import { Box, Flex } from '@chakra-ui/react' import type React from 'react' import { CardSkeletonProps } from 'types/skeleton' -import { Skeleton, SkeletonCircle, SkeletonText } from 'components/ui/skeleton' +import { Skeleton, SkeletonCircle, SkeletonText } from 'components/ui/Skeleton' const CardSkeleton: React.FC<CardSkeletonProps> = ({ showLevel = true, @@ -16,7 +16,7 @@ const CardSkeleton: React.FC<CardSkeletonProps> = ({ const NUM_CONTRIBUTORS = 8 return ( - <div data-testid="skeleton-loader" className="flex w-full justify-center"> + <div role="status" className="flex w-full justify-center"> <Box className="mb-6 w-full rounded-lg border border-border bg-card p-6 transition-colors duration-300 ease-linear hover:bg-accent/10 md:max-w-6xl"> <Flex direction="column" className="flex flex-col sm:flex-row" gap={6}> {/* Header Section */} diff --git a/frontend/src/components/skeletons/UserCard.tsx b/frontend/src/components/skeletons/UserCard.tsx index c12a0e336..fe9ef4f2c 100644 --- a/frontend/src/components/skeletons/UserCard.tsx +++ b/frontend/src/components/skeletons/UserCard.tsx @@ -1,7 +1,7 @@ import { Box, Flex } from '@chakra-ui/react' import type React from 'react' import { UserCardSkeletonProps } from 'types/skeleton' -import { Skeleton, SkeletonCircle } from 'components/ui/skeleton' +import { Skeleton, SkeletonCircle } from 'components/ui/Skeleton' const UserCardSkeleton: React.FC<UserCardSkeletonProps> = ({ showAvatar = true, @@ -10,7 +10,7 @@ const UserCardSkeleton: React.FC<UserCardSkeletonProps> = ({ }) => { return ( <Box - data-testid="skeleton-loader" + role="status" className="group flex h-64 w-80 flex-col items-center rounded-lg bg-white p-6 text-left shadow-lg dark:bg-gray-800 dark:shadow-gray-900/30" > <Flex direction="column" className="w-full items-center space-y-4"> diff --git a/frontend/src/components/ui/skeleton.tsx b/frontend/src/components/ui/Skeleton.tsx similarity index 100% rename from frontend/src/components/ui/skeleton.tsx rename to frontend/src/components/ui/Skeleton.tsx