From 880a60dbc5107500cffae89921ae24bdbcb0c40c Mon Sep 17 00:00:00 2001 From: Spencer Murray Date: Mon, 17 Mar 2025 09:54:24 -0400 Subject: [PATCH] Convert BranchDirEntry to tsx --- .../TableEntries/BaseEntries/DirEntry.tsx | 20 +- .../BranchEntries/BranchDirEntry.jsx | 45 ---- .../BranchEntries/BranchDirEntry.test.jsx | 201 ------------------ .../BranchEntries/BranchDirEntry.test.tsx | 120 +++++++++++ .../BranchEntries/BranchDirEntry.tsx | 33 +++ .../BranchEntries/BranchFileEntry.test.tsx | 2 +- .../BranchEntries/BranchFileEntry.tsx | 46 +--- .../BranchEntries/useTypeSafeFilters.ts | 43 ++++ 8 files changed, 209 insertions(+), 301 deletions(-) delete mode 100644 src/shared/ContentsTable/TableEntries/BranchEntries/BranchDirEntry.jsx delete mode 100644 src/shared/ContentsTable/TableEntries/BranchEntries/BranchDirEntry.test.jsx create mode 100644 src/shared/ContentsTable/TableEntries/BranchEntries/BranchDirEntry.test.tsx create mode 100644 src/shared/ContentsTable/TableEntries/BranchEntries/BranchDirEntry.tsx create mode 100644 src/shared/ContentsTable/TableEntries/BranchEntries/useTypeSafeFilters.ts diff --git a/src/shared/ContentsTable/TableEntries/BaseEntries/DirEntry.tsx b/src/shared/ContentsTable/TableEntries/BaseEntries/DirEntry.tsx index c59ae228b9..c81958b893 100644 --- a/src/shared/ContentsTable/TableEntries/BaseEntries/DirEntry.tsx +++ b/src/shared/ContentsTable/TableEntries/BaseEntries/DirEntry.tsx @@ -1,6 +1,16 @@ import A from 'ui/A' import Icon from 'ui/Icon' +interface DirEntryProps { + linkRef?: string + name: string + urlPath?: string + runPrefetch?: () => Promise + pageName?: string + commitSha?: string + queryParams?: any +} + function DirEntry({ linkRef, name, @@ -32,14 +42,4 @@ function DirEntry({ ) } -interface DirEntryProps { - linkRef?: string - name: string - urlPath?: string - runPrefetch?: () => Promise - pageName?: string - commitSha?: string - queryParams?: any -} - export default DirEntry diff --git a/src/shared/ContentsTable/TableEntries/BranchEntries/BranchDirEntry.jsx b/src/shared/ContentsTable/TableEntries/BranchEntries/BranchDirEntry.jsx deleted file mode 100644 index 2b35e1c786..0000000000 --- a/src/shared/ContentsTable/TableEntries/BranchEntries/BranchDirEntry.jsx +++ /dev/null @@ -1,45 +0,0 @@ -import PropTypes from 'prop-types' -import qs from 'qs' -import { useLocation } from 'react-router-dom' - -import { usePrefetchBranchDirEntry } from 'services/pathContents/branch/dir' - -import DirEntry from '../BaseEntries/DirEntry' - -function BranchDirEntry({ branch, urlPath, name }) { - const location = useLocation() - - const queryParams = qs.parse(location.search, { - ignoreQueryPrefix: true, - depth: 1, - }) - - const filters = { - ...(queryParams?.flags ? { flags: queryParams.flags } : {}), - ...(queryParams?.components ? { components: queryParams.components } : {}), - } - - const { runPrefetch } = usePrefetchBranchDirEntry({ - branch, - path: name, - filters, - }) - - return ( - - ) -} - -BranchDirEntry.propTypes = { - branch: PropTypes.string.isRequired, - name: PropTypes.string.isRequired, - urlPath: PropTypes.string, -} - -export default BranchDirEntry diff --git a/src/shared/ContentsTable/TableEntries/BranchEntries/BranchDirEntry.test.jsx b/src/shared/ContentsTable/TableEntries/BranchEntries/BranchDirEntry.test.jsx deleted file mode 100644 index 0c9b2333a0..0000000000 --- a/src/shared/ContentsTable/TableEntries/BranchEntries/BranchDirEntry.test.jsx +++ /dev/null @@ -1,201 +0,0 @@ -import { QueryClient, QueryClientProvider } from '@tanstack/react-query' -import { render, screen, waitFor } from '@testing-library/react' -import userEvent from '@testing-library/user-event' -import { graphql, HttpResponse } from 'msw' -import { setupServer } from 'msw/node' -import { MemoryRouter, Route } from 'react-router-dom' - -import BranchDirEntry from './BranchDirEntry' - -const mockData = { - owner: { - username: 'codecov', - repository: { - __typename: 'Repository', - repositoryConfig: { - indicationRange: { - upperRange: 80, - lowerRange: 60, - }, - }, - branch: { - head: { - deprecatedPathContents: { - __typename: 'PathContentConnection', - edges: [ - { - node: { - __typename: 'PathContentDir', - name: 'src', - path: 'src', - percentCovered: 0.0, - hits: 4, - misses: 2, - lines: 7, - partials: 1, - }, - }, - ], - pageInfo: { - hasNextPage: false, - endCursor: null, - }, - }, - }, - }, - }, - }, -} - -const queryClient = new QueryClient({ - defaultOptions: { queries: { retry: false } }, -}) -const server = setupServer() - -const wrapper = - (initialEntries = ['/gh/codecov/test-repo/tree/main/src/']) => - ({ children }) => ( - - - - {children} - - - - ) - -beforeAll(() => server.listen()) -afterEach(() => { - queryClient.clear() - server.resetHandlers() -}) -afterAll(() => server.close()) - -describe('BranchDirEntry', () => { - function setup() { - const user = userEvent.setup() - - server.use( - graphql.query('BranchContents', () => { - return HttpResponse.json({ data: mockData }) - }) - ) - - return { user } - } - - it('displays the directory name', async () => { - setup() - render( - , - { wrapper: wrapper() } - ) - - const dir = await screen.findByText('dir') - expect(dir).toBeInTheDocument() - }) - - it('sets the correct href', async () => { - setup() - render( - , - { wrapper: wrapper() } - ) - - const a = await screen.findByRole('link') - expect(a).toHaveAttribute( - 'href', - '/gh/codecov/test-repo/tree/branch/path%2Fto%2Fdirectory%2Fdir' - ) - }) - - describe('flags filter is set', () => { - it('sets the correct href', async () => { - setup() - render( - , - { - wrapper: wrapper([ - '/gh/codecov/test-repo/tree/main/src?flags=flag-1', - ]), - } - ) - const a = await screen.findByRole('link') - expect(a).toHaveAttribute( - 'href', - '/gh/codecov/test-repo/tree/branch/path%2Fto%2Fdirectory%2Fdir?flags=flag-1' - ) - }) - }) - - describe('components and flags filters is set', () => { - it('sets the correct href', async () => { - setup() - render( - , - { - wrapper: wrapper([ - '/gh/codecov/test-repo/tree/main/src?flags=flag-1&components=component-1', - ]), - } - ) - - const a = await screen.findByRole('link') - expect(a).toHaveAttribute( - 'href', - '/gh/codecov/test-repo/tree/branch/path%2Fto%2Fdirectory%2Fdir?flags=flag-1&components=component-1' - ) - }) - }) - - it('fires the prefetch function on hover', async () => { - const { user } = setup() - - render( - , - { wrapper: wrapper() } - ) - - await user.hover(screen.getByText('dir')) - - await waitFor(() => queryClient.getQueryState().isFetching) - await waitFor(() => !queryClient.getQueryState().isFetching) - await waitFor(() => - expect(queryClient.getQueryState().data).toStrictEqual({ - indicationRange: { - lowerRange: 60, - upperRange: 80, - }, - pathContentsType: 'PathContentConnection', - results: [ - { - __typename: 'PathContentDir', - hits: 4, - lines: 7, - misses: 2, - name: 'src', - partials: 1, - path: 'src', - percentCovered: 0, - }, - ], - }) - ) - }) -}) diff --git a/src/shared/ContentsTable/TableEntries/BranchEntries/BranchDirEntry.test.tsx b/src/shared/ContentsTable/TableEntries/BranchEntries/BranchDirEntry.test.tsx new file mode 100644 index 0000000000..b76ede1506 --- /dev/null +++ b/src/shared/ContentsTable/TableEntries/BranchEntries/BranchDirEntry.test.tsx @@ -0,0 +1,120 @@ +import { render, screen } from '@testing-library/react' +import userEvent from '@testing-library/user-event' +import { MemoryRouter, Route } from 'react-router-dom' + +import BranchDirEntry from './BranchDirEntry' + +const mockRunPrefetch = vi.hoisted(() => vi.fn()) + +vi.mock('services/pathContents/branch/dir', () => ({ + usePrefetchBranchDirEntry: vi + .fn() + .mockReturnValue({ runPrefetch: mockRunPrefetch }), +})) + +const wrapper: ( + initialEntries?: string[] +) => React.FC = + (initialEntries = ['/gh/codecov/test-repo/tree/main/src/']) => + ({ children }) => ( + + + {children} + + + ) + +describe('BranchDirEntry', () => { + function setup() { + return { user: userEvent.setup() } + } + + it('displays the directory name', async () => { + setup() + render( + , + { wrapper: wrapper() } + ) + + const dir = await screen.findByText('dir') + expect(dir).toBeInTheDocument() + }) + + it('sets the correct href', async () => { + setup() + render( + , + { wrapper: wrapper() } + ) + + const a = await screen.findByRole('link') + expect(a).toHaveAttribute( + 'href', + '/gh/codecov/test-repo/tree/branch/path%2Fto%2Fdirectory%2Fdir' + ) + }) + + describe('flags filter is set', () => { + it('sets the correct href', async () => { + setup() + render( + , + { + wrapper: wrapper([ + '/gh/codecov/test-repo/tree/main/src?flags%5B0%5D=flag-1', + ]), + } + ) + const a = await screen.findByRole('link') + expect(a).toHaveAttribute( + 'href', + '/gh/codecov/test-repo/tree/branch/path%2Fto%2Fdirectory%2Fdir?flags%5B0%5D=flag-1' + ) + }) + }) + + describe('components and flags filters is set', () => { + it('sets the correct href', async () => { + setup() + render( + , + { + wrapper: wrapper([ + '/gh/codecov/test-repo/tree/main/src?flags%5B0%5D=flag-1&components%5B0%5D=component-1', + ]), + } + ) + + const a = await screen.findByRole('link') + expect(a).toHaveAttribute( + 'href', + '/gh/codecov/test-repo/tree/branch/path%2Fto%2Fdirectory%2Fdir?flags%5B0%5D=flag-1&components%5B0%5D=component-1' + ) + }) + }) + + it('fires the prefetch function on hover', async () => { + const { user } = setup() + + render( + , + { wrapper: wrapper() } + ) + + const dir = await screen.findByText('dir') + + expect(mockRunPrefetch).not.toHaveBeenCalled() + + await user.hover(dir) + + expect(mockRunPrefetch).toHaveBeenCalled() + }) +}) diff --git a/src/shared/ContentsTable/TableEntries/BranchEntries/BranchDirEntry.tsx b/src/shared/ContentsTable/TableEntries/BranchEntries/BranchDirEntry.tsx new file mode 100644 index 0000000000..57e5793bac --- /dev/null +++ b/src/shared/ContentsTable/TableEntries/BranchEntries/BranchDirEntry.tsx @@ -0,0 +1,33 @@ +import { usePrefetchBranchDirEntry } from 'services/pathContents/branch/dir' + +import { useTypeSafeFilters } from './useTypeSafeFilters' + +import DirEntry from '../BaseEntries/DirEntry' + +interface BranchDirEntryProps { + branch: string + name: string + urlPath?: string +} + +function BranchDirEntry({ branch, urlPath, name }: BranchDirEntryProps) { + const filters = useTypeSafeFilters() + + const { runPrefetch } = usePrefetchBranchDirEntry({ + branch, + path: name, + filters, + }) + + return ( + + ) +} + +export default BranchDirEntry diff --git a/src/shared/ContentsTable/TableEntries/BranchEntries/BranchFileEntry.test.tsx b/src/shared/ContentsTable/TableEntries/BranchEntries/BranchFileEntry.test.tsx index 050613a35f..faf0143406 100644 --- a/src/shared/ContentsTable/TableEntries/BranchEntries/BranchFileEntry.test.tsx +++ b/src/shared/ContentsTable/TableEntries/BranchEntries/BranchFileEntry.test.tsx @@ -17,7 +17,7 @@ vi.mock('services/pathContents/branch/file', () => ({ })) const wrapper: ( - initialEntried?: string[] + initialEntries?: string[] ) => React.FC = (initialEntries = ['/gh/codecov/test-repo/']) => ({ children }) => ( diff --git a/src/shared/ContentsTable/TableEntries/BranchEntries/BranchFileEntry.tsx b/src/shared/ContentsTable/TableEntries/BranchEntries/BranchFileEntry.tsx index 42d984a36d..1db11c6c63 100644 --- a/src/shared/ContentsTable/TableEntries/BranchEntries/BranchFileEntry.tsx +++ b/src/shared/ContentsTable/TableEntries/BranchEntries/BranchFileEntry.tsx @@ -1,52 +1,10 @@ -import qs from 'qs' -import { useMemo } from 'react' -import { useLocation } from 'react-router-dom' - import { usePrefetchBranchFileEntry } from 'services/pathContents/branch/file' +import { useTypeSafeFilters } from './useTypeSafeFilters' + import { displayTypeParameter } from '../../constants' import FileEntry from '../BaseEntries/FileEntry' -type Filters = { - flags: string[] - components: string[] -} - -function useTypeSafeFilters(): Filters { - const location = useLocation() - const queryParams = qs.parse(location.search, { - ignoreQueryPrefix: true, - depth: 1, - }) - - return useMemo(() => { - const filters: Filters = { - flags: [], - components: [], - } - - if ( - queryParams?.flags && - Array.isArray(queryParams.flags) && - queryParams.flags.length > 0 && - queryParams.flags.every((val) => typeof val == 'string') - ) { - filters.flags = queryParams.flags - } - - if ( - queryParams?.components && - Array.isArray(queryParams.components) && - queryParams.components.length > 0 && - queryParams.components.every((val) => typeof val == 'string') - ) { - filters.components = queryParams.components - } - - return filters - }, [queryParams]) -} - interface BranchFileEntryProps { branch: string path: string diff --git a/src/shared/ContentsTable/TableEntries/BranchEntries/useTypeSafeFilters.ts b/src/shared/ContentsTable/TableEntries/BranchEntries/useTypeSafeFilters.ts new file mode 100644 index 0000000000..5b8a23bdb6 --- /dev/null +++ b/src/shared/ContentsTable/TableEntries/BranchEntries/useTypeSafeFilters.ts @@ -0,0 +1,43 @@ +import qs from 'qs' +import { useMemo } from 'react' +import { useLocation } from 'react-router' + +type Filters = { + flags: string[] + components: string[] +} + +export function useTypeSafeFilters(): Filters { + const location = useLocation() + const queryParams = qs.parse(location.search, { + ignoreQueryPrefix: true, + depth: 1, + }) + + return useMemo(() => { + const filters: Filters = { + flags: [], + components: [], + } + + if ( + queryParams?.flags && + Array.isArray(queryParams.flags) && + queryParams.flags.length > 0 && + queryParams.flags.every((val) => typeof val == 'string') + ) { + filters.flags = queryParams.flags + } + + if ( + queryParams?.components && + Array.isArray(queryParams.components) && + queryParams.components.length > 0 && + queryParams.components.every((val) => typeof val == 'string') + ) { + filters.components = queryParams.components + } + + return filters + }, [queryParams]) +}