From f066f2449799decbfb9c5a5712584e6501035b51 Mon Sep 17 00:00:00 2001 From: Rakshak05 Date: Thu, 9 Jul 2026 02:48:04 +0530 Subject: [PATCH 1/2] Resolves issue-6861 --- ...tributorsSearch.mock-integrations.test.tsx | 198 ++++++++++++++++++ 1 file changed, 198 insertions(+) create mode 100644 app/contributors/ContributorsSearch.mock-integrations.test.tsx diff --git a/app/contributors/ContributorsSearch.mock-integrations.test.tsx b/app/contributors/ContributorsSearch.mock-integrations.test.tsx new file mode 100644 index 000000000..3f92f6273 --- /dev/null +++ b/app/contributors/ContributorsSearch.mock-integrations.test.tsx @@ -0,0 +1,198 @@ +import { beforeEach, describe, expect, it, vi } from 'vitest'; +import { render, screen } from '@testing-library/react'; +import '@testing-library/jest-dom'; +import ContributorsSearch from './ContributorsSearch'; + +// Mock framer-motion to avoid animation issues in tests +vi.mock('framer-motion', async () => { + const React = await import('react'); + const motionProps = new Set([ + 'whileHover', + 'whileTap', + 'whileInView', + 'initial', + 'animate', + 'exit', + 'variants', + 'transition', + 'viewport', + 'drag', + 'layout', + 'layoutId', + ]); + + const stripMotionProps = (props: Record) => + Object.fromEntries(Object.entries(props).filter(([key]) => !motionProps.has(key))); + + const createMotionComponent = (tag: string) => { + const Component = ({ + children, + ...props + }: React.HTMLAttributes & { children?: React.ReactNode }) => + React.createElement(tag, stripMotionProps(props), children); + return Component; + }; + + return { + motion: { + div: createMotionComponent('div'), + span: createMotionComponent('span'), + p: createMotionComponent('p'), + a: createMotionComponent('a'), + button: createMotionComponent('button'), + }, + AnimatePresence: ({ children }: { children?: React.ReactNode }) => <>{children}, + }; +}); + +// Stubs for cache and service layer +interface Contributor { + id: number; + login: string; + avatar_url: string; + contributions: number; + html_url: string; +} + +const mockCache = { + get: vi.fn(), + set: vi.fn(), + clear: vi.fn(), +}; + +const mockFetchContributors = vi.fn(); + +describe('ContributorsSearch - Mock Integrations & Local Cache Stubs', () => { + beforeEach(() => { + vi.clearAllMocks(); + mockCache.get.mockReset(); + mockCache.set.mockReset(); + mockCache.clear.mockReset(); + mockFetchContributors.mockReset(); + }); + + // --- Test Case 1 --- + it('mocks standard asynchronous imports and databases using stubs', async () => { + const mockData: Contributor[] = [ + { + id: 1, + login: 'contributor-one', + avatar_url: 'https://example.com/one.png', + contributions: 5, + html_url: 'https://github.com/one', + }, + ]; + mockFetchContributors.mockResolvedValue(mockData); + + const result = await mockFetchContributors(); + expect(result).toHaveLength(1); + expect(result[0].login).toBe('contributor-one'); + + render(); + expect(screen.getByRole('textbox')).toBeInTheDocument(); + expect(screen.getByText('contributor-one')).toBeInTheDocument(); + }); + + // --- Test Case 2 --- + it('tests service loading paths to ensure pending state overlays render', async () => { + let resolvePromise: (value: Contributor[]) => void = () => {}; + const promise = new Promise((resolve) => { + resolvePromise = resolve; + }); + + mockFetchContributors.mockReturnValue(promise); + + // Simulated component container that handles loading state + const LoadingContainer = ({ isPending }: { isPending: boolean }) => { + if (isPending) { + return
Loading the collective...
; + } + return ; + }; + + const { rerender } = render(); + expect(screen.getByRole('status')).toHaveTextContent('Loading the collective...'); + + resolvePromise([]); + await promise; + + rerender(); + expect(screen.queryByRole('status')).not.toBeInTheDocument(); + expect(screen.getByRole('textbox')).toBeInTheDocument(); + }); + + // --- Test Case 3 --- + it('asserts local cache layers are queried before triggering database retrievals', async () => { + const cachedData: Contributor[] = [ + { + id: 2, + login: 'cached-contributor', + avatar_url: 'https://example.com/cached.png', + contributions: 10, + html_url: 'https://github.com/cached', + }, + ]; + mockCache.get.mockReturnValue(cachedData); + + const getContributorsData = async () => { + const cacheVal = mockCache.get('contributors_search_cache'); + if (cacheVal) return cacheVal; + return mockFetchContributors(); + }; + + const result = await getContributorsData(); + + expect(mockCache.get).toHaveBeenCalledWith('contributors_search_cache'); + expect(mockFetchContributors).not.toHaveBeenCalled(); + expect(result).toEqual(cachedData); + + render(); + expect(screen.getByText('cached-contributor')).toBeInTheDocument(); + }); + + // --- Test Case 4 --- + it('verifies correct fallback procedures during fake endpoint timeout blocks', async () => { + mockFetchContributors.mockRejectedValue(new Error('Gateway Timeout')); + + const getContributorsWithFallback = async () => { + try { + return await mockFetchContributors(); + } catch { + return []; + } + }; + + const data = await getContributorsWithFallback(); + expect(data).toEqual([]); + + render(); + // "No architects found" should render because data is empty and we filter it + expect(screen.getByText('No architects found')).toBeInTheDocument(); + }); + + // --- Test Case 5 --- + it('asserts complete cache sync is written on success callbacks', async () => { + const freshData: Contributor[] = [ + { + id: 3, + login: 'fresh-contributor', + avatar_url: 'https://example.com/fresh.png', + contributions: 15, + html_url: 'https://github.com/fresh', + }, + ]; + mockFetchContributors.mockResolvedValue(freshData); + + const syncAndFetch = async () => { + const data = await mockFetchContributors(); + mockCache.set('contributors_search_cache', data); + return data; + }; + + const result = await syncAndFetch(); + expect(mockCache.set).toHaveBeenCalledWith('contributors_search_cache', freshData); + + render(); + expect(screen.getByText('fresh-contributor')).toBeInTheDocument(); + }); +}); From 6cf2fd2d36fcee9229cdfe83d5b3019c6e816733 Mon Sep 17 00:00:00 2001 From: Rakshak05 Date: Thu, 9 Jul 2026 03:25:38 +0530 Subject: [PATCH 2/2] fix(navbar-test): align breakpoint class in test to match navbar component --- app/components/navbar.responsive-breakpoints.test.tsx | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/app/components/navbar.responsive-breakpoints.test.tsx b/app/components/navbar.responsive-breakpoints.test.tsx index b347e3531..d908eabdf 100644 --- a/app/components/navbar.responsive-breakpoints.test.tsx +++ b/app/components/navbar.responsive-breakpoints.test.tsx @@ -145,11 +145,11 @@ describe('Navbar Responsive Breakpoints & Menu Toggle', () => { it('4. Hides the desktop nav row on mobile and only reveals mobile hamburger controls, via complementary md: classes', () => { const { container } = render(); - const desktopNavRow = container.querySelector('.hidden.items-center.gap-2.md\\:flex'); + const desktopNavRow = container.querySelector('.hidden.items-center.gap-2.lg\\:flex'); expect(desktopNavRow).toBeInTheDocument(); const mobileControls = container.querySelector( - '.md\\:hidden.inline-flex.items-center.justify-center.gap-1' + '.lg\\:hidden.inline-flex.items-center.justify-center.gap-1' ); expect(mobileControls).toBeInTheDocument(); });