diff --git a/src/pages/CommitDetailPage/subRoute/IndirectChangesTab/IndirectChangesTable/IndirectChangesTable.jsx b/src/pages/CommitDetailPage/subRoute/IndirectChangesTab/IndirectChangesTable/IndirectChangesTable.jsx index 423cf2eab4..2d7d34d5cf 100644 --- a/src/pages/CommitDetailPage/subRoute/IndirectChangesTab/IndirectChangesTable/IndirectChangesTable.jsx +++ b/src/pages/CommitDetailPage/subRoute/IndirectChangesTab/IndirectChangesTable/IndirectChangesTable.jsx @@ -1,12 +1,15 @@ import cs from 'classnames' +import isArray from 'lodash/isArray' import isEmpty from 'lodash/isEmpty' import isNumber from 'lodash/isNumber' import PropTypes from 'prop-types' +import qs from 'qs' import { lazy, Suspense } from 'react' -import { useParams } from 'react-router-dom' +import { useLocation, useParams } from 'react-router-dom' import Table from 'old_ui/Table' import { useCommit } from 'services/commit' +import { useFlags } from 'shared/featureFlags' import A from 'ui/A' import Icon from 'ui/Icon' import Spinner from 'ui/Spinner' @@ -136,6 +139,21 @@ RenderSubComponent.propTypes = { function IndirectChangesTable() { const { provider, owner, repo, commit: commitSha } = useParams() + const location = useLocation() + + const { commitTabFlagMultiSelect } = useFlags({ + commitTabFlagMultiSelect: false, + }) + + const queryParams = qs.parse(location.search, { + ignoreQueryPrefix: true, + depth: 1, + }) + + const flags = + queryParams?.flags?.length > 0 && commitTabFlagMultiSelect + ? queryParams?.flags + : undefined const { data: commitData, isLoading } = useCommit({ provider, @@ -144,6 +162,7 @@ function IndirectChangesTable() { commitid: commitSha, filters: { hasUnintendedChanges: true, + flags: flags, }, }) @@ -161,6 +180,20 @@ function IndirectChangesTable() { indirectChangedFiles = commit?.compareWithParent?.impactedFiles?.results } + if ( + isEmpty(indirectChangedFiles) && + (isArray(flags) || + (commit?.compareWithParent?.__typename === 'Comparison' && + commit?.compareWithParent?.impactedFiles?.__typename === + 'UnknownFlags')) + ) { + return ( +

+ No files covered by tests and selected flags were changed +

+ ) + } + if (isEmpty(indirectChangedFiles)) { return

No files covered by tests were changed

} diff --git a/src/pages/CommitDetailPage/subRoute/IndirectChangesTab/IndirectChangesTable/IndirectChangesTable.spec.jsx b/src/pages/CommitDetailPage/subRoute/IndirectChangesTab/IndirectChangesTable/IndirectChangesTable.spec.jsx index 1001b8df0a..1ca1b6b056 100644 --- a/src/pages/CommitDetailPage/subRoute/IndirectChangesTab/IndirectChangesTable/IndirectChangesTable.spec.jsx +++ b/src/pages/CommitDetailPage/subRoute/IndirectChangesTab/IndirectChangesTable/IndirectChangesTable.spec.jsx @@ -1,54 +1,75 @@ import { QueryClient, QueryClientProvider } from '@tanstack/react-query' -import { render, screen } from '@testing-library/react' +import { render, screen, waitFor } from '@testing-library/react' import userEvent from '@testing-library/user-event' import { graphql } from 'msw' import { setupServer } from 'msw/node' +import qs from 'qs' import { Suspense } from 'react' import { MemoryRouter, Route } from 'react-router-dom' +import { useFlags } from 'shared/featureFlags' + import IndirectChangesTable from '../IndirectChangesTable' +jest.mock('shared/featureFlags') jest.mock('./CommitFileDiff', () => () => 'CommitFileDiff') -const queryClient = new QueryClient({ - defaultOptions: { - queries: { - suspense: true, - retry: false, - }, - }, -}) const server = setupServer() -beforeAll(() => server.listen()) +beforeAll(() => { + server.listen() +}) + beforeEach(() => { server.resetHandlers() - queryClient.clear() }) -afterAll(() => server.close()) - -const wrapper = ({ children }) => ( - - - - {children} - - - -) + +afterAll(() => { + server.close() +}) + +const wrapper = + ( + queryClient, + initialEntries = '/gh/codecov/cool-repo/commit/123/indirect-changes' + ) => + ({ children }) => + ( + + + + {children} + + + + ) describe('IndirectChangesTable', () => { function setup(data = [], state = 'processed') { + const user = userEvent.setup() + const mockVars = jest.fn() + const queryClient = new QueryClient({ + defaultOptions: { + queries: { + suspense: true, + retry: false, + }, + }, + }) + + useFlags.mockReturnValue({ + commitTabFlagMultiSelect: true, + }) + server.use( - graphql.query('Commit', (req, res, ctx) => - res( + graphql.query('Commit', (req, res, ctx) => { + mockVars(req.variables) + return res( ctx.status(200), ctx.data({ owner: { @@ -81,33 +102,34 @@ describe('IndirectChangesTable', () => { }, }) ) - ) + }) ) + + return { mockVars, queryClient, user } } describe('when data is available', () => { - beforeEach(() => - setup({ - __typename: 'ImpactedFiles', - results: [ - { - headName: 'src/index2.py', - baseCoverage: { - coverage: 62.5, - }, - headCoverage: { - coverage: 50.0, - }, - patchCoverage: { - coverage: 37.5, - }, + const mockData = { + __typename: 'ImpactedFiles', + results: [ + { + headName: 'src/index2.py', + baseCoverage: { + coverage: 62.5, }, - ], - }) - ) + headCoverage: { + coverage: 50.0, + }, + patchCoverage: { + coverage: 37.5, + }, + }, + ], + } it('renders name', async () => { - render(, { wrapper }) + const { queryClient } = setup(mockData) + render(, { wrapper: wrapper(queryClient) }) const link = await screen.findByRole('link', { name: 'src/index2.py', @@ -115,49 +137,77 @@ describe('IndirectChangesTable', () => { expect(link).toBeInTheDocument() expect(link).toHaveAttribute( 'href', - '/gh/vex/trinket/commit/123/blob/src/index2.py' + '/gh/codecov/cool-repo/commit/123/blob/src/index2.py' ) }) it('renders coverage', async () => { - render(, { wrapper }) + const { queryClient } = setup(mockData) + render(, { wrapper: wrapper(queryClient) }) const coverage = await screen.findByText(/50.00%/) expect(coverage).toBeInTheDocument() }) it('render change', async () => { - render(, { wrapper }) + const { queryClient } = setup(mockData) + render(, { wrapper: wrapper(queryClient) }) const noData = await screen.findByText(/-12.50%/) expect(noData).toBeInTheDocument() }) - }) - describe('when all data is missing', () => { - beforeEach(() => { - setup({ - __typename: 'ImpactedFiles', - results: [ - { - headName: '', - baseCoverage: null, - headCoverage: null, - patchCoverage: null, - }, - ], + describe('flag is present in query params', () => { + it('fetches with flags filter', async () => { + const { queryClient, mockVars } = setup(mockData) + const path = `/gh/codecov/cool-repo/commit/123/indirect-changes${qs.stringify( + { flags: ['flag-1'] }, + { addQueryPrefix: true } + )}` + render(, { + wrapper: wrapper(queryClient, path), + }) + + await waitFor(() => + expect(mockVars).toBeCalledWith({ + commitid: '123', + filters: { + hasUnintendedChanges: true, + flags: ['flag-1'], + }, + owner: 'codecov', + provider: 'gh', + repo: 'cool-repo', + }) + ) }) }) + }) + + describe('when all data is missing', () => { + const mockData = { + __typename: 'ImpactedFiles', + results: [ + { + headName: '', + baseCoverage: null, + headCoverage: null, + patchCoverage: null, + }, + ], + } it('does not render coverage', () => { - render(, { wrapper }) + const { queryClient } = setup(mockData) + render(, { wrapper: wrapper(queryClient) }) const coverage = screen.queryByText(/0.00%/) expect(coverage).not.toBeInTheDocument() }) it('renders no available data copy', async () => { - render(, { wrapper }) + const { queryClient } = setup(mockData) + render(, { wrapper: wrapper(queryClient) }) const copy = await screen.findByText('No data') expect(copy).toBeInTheDocument() @@ -165,33 +215,33 @@ describe('IndirectChangesTable', () => { }) describe('when some data is missing', () => { - beforeEach(() => { - setup({ - __typename: 'ImpactedFiles', - results: [ - { - headName: '', - baseCoverage: null, - headCoverage: { - coverage: 67, - }, - patchCoverage: { - coverage: 98, - }, + const mockData = { + __typename: 'ImpactedFiles', + results: [ + { + headName: '', + baseCoverage: null, + headCoverage: { + coverage: 67, }, - ], - }) - }) + patchCoverage: { + coverage: 98, + }, + }, + ], + } it('renders head coverage', async () => { - render(, { wrapper }) + const { queryClient } = setup(mockData) + render(, { wrapper: wrapper(queryClient) }) const coverage = await screen.findByText(/67.00%/) expect(coverage).toBeInTheDocument() }) it('renders dash for change', async () => { - render(, { wrapper }) + const { queryClient } = setup(mockData) + render(, { wrapper: wrapper(queryClient) }) const dash = await screen.findByText('-') expect(dash).toBeInTheDocument() @@ -200,36 +250,51 @@ describe('IndirectChangesTable', () => { describe('when no changes', () => { describe('returns an empty results array', () => { - beforeEach(() => { - setup({ + it('renders no files covered error message', async () => { + const { queryClient } = setup({ __typename: 'ImpactedFiles', results: [], }) - }) - - it('renders coverage', async () => { - render(, { wrapper }) + render(, { wrapper: wrapper(queryClient) }) const coverage = await screen.findByText( 'No files covered by tests were changed' ) expect(coverage).toBeInTheDocument() }) + + describe('flags param in url is set', () => { + it('renders flags no files error message', async () => { + const { queryClient } = setup({ + __typename: 'ImpactedFiles', + results: [], + }) + const path = `/gh/codecov/cool-repo/commit/123/indirect-changes${qs.stringify( + { flags: ['flag-1'] }, + { addQueryPrefix: true } + )}` + render(, { + wrapper: wrapper(queryClient, path), + }) + + const coverage = await screen.findByText( + 'No files covered by tests and selected flags were changed' + ) + expect(coverage).toBeInTheDocument() + }) + }) }) describe('returns __typename of unknown flags', () => { - beforeEach(() => { - setup({ + it('renders flags no files error message', async () => { + const { queryClient } = setup({ __typename: 'UnknownFlags', message: 'no flags found', }) - }) - - it('renders coverage', async () => { - render(, { wrapper }) + render(, { wrapper: wrapper(queryClient) }) const coverage = await screen.findByText( - 'No files covered by tests were changed' + 'No files covered by tests and selected flags were changed' ) expect(coverage).toBeInTheDocument() }) @@ -237,18 +302,15 @@ describe('IndirectChangesTable', () => { }) describe('when impacted files are in pending state', () => { - beforeEach(() => { - setup( + it('renders spinner', async () => { + const { queryClient } = setup( { __typename: 'ImpactedFiles', results: [], }, 'pending' ) - }) - - it('renders spinner', async () => { - render(, { wrapper }) + render(, { wrapper: wrapper(queryClient) }) const spinner = await screen.findByTestId('spinner') expect(spinner).toBeInTheDocument() @@ -256,29 +318,27 @@ describe('IndirectChangesTable', () => { }) describe('when expanding the name column', () => { - beforeEach(() => { - setup({ - __typename: 'ImpactedFiles', - results: [ - { - headName: 'src/index2.py', - baseCoverage: { - coverage: 62.5, - }, - headCoverage: { - coverage: 50.0, - }, - patchCoverage: { - coverage: 37.5, - }, + const mockData = { + __typename: 'ImpactedFiles', + results: [ + { + headName: 'src/index2.py', + baseCoverage: { + coverage: 62.5, }, - ], - }) - }) + headCoverage: { + coverage: 50.0, + }, + patchCoverage: { + coverage: 37.5, + }, + }, + ], + } it('renders the CommitFileDiff component', async () => { - const user = userEvent.setup() - render(, { wrapper }) + const { queryClient, user } = setup(mockData) + render(, { wrapper: wrapper(queryClient) }) const nameExpander = await screen.findByText('src/index2.py') await user.click(nameExpander)