diff --git a/package-lock.json b/package-lock.json index af84d731fe..8609a240a9 100644 --- a/package-lock.json +++ b/package-lock.json @@ -96,7 +96,7 @@ "jest-junit": "^13.0.0", "lint-staged": "^13.2.2", "msw": "^1.2.1", - "postcss": "^8.4.12", + "postcss": "^8.4.31", "prettier": "^2.8.8", "react-scripts": "^5.0.1", "react-test-renderer": "^18.2.0", @@ -25706,9 +25706,9 @@ } }, "node_modules/postcss": { - "version": "8.4.23", - "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.4.23.tgz", - "integrity": "sha512-bQ3qMcpF6A/YjR55xtoTr0jGOlnPOKAIMdOWiv0EIT6HVPEaJiJB4NLljSbiHoC2RX7DN5Uvjtpbg1NPdwv1oA==", + "version": "8.4.31", + "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.4.31.tgz", + "integrity": "sha512-PS08Iboia9mts/2ygV3eLpY5ghnUcfLV/EXTOW1E2qYxJKGGBUtNjN76FYHnMs36RmARn41bC0AZmn+rR0OVpQ==", "dev": true, "funding": [ { @@ -52698,9 +52698,9 @@ } }, "postcss": { - "version": "8.4.23", - "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.4.23.tgz", - "integrity": "sha512-bQ3qMcpF6A/YjR55xtoTr0jGOlnPOKAIMdOWiv0EIT6HVPEaJiJB4NLljSbiHoC2RX7DN5Uvjtpbg1NPdwv1oA==", + "version": "8.4.31", + "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.4.31.tgz", + "integrity": "sha512-PS08Iboia9mts/2ygV3eLpY5ghnUcfLV/EXTOW1E2qYxJKGGBUtNjN76FYHnMs36RmARn41bC0AZmn+rR0OVpQ==", "dev": true, "requires": { "nanoid": "^3.3.6", diff --git a/package.json b/package.json index 042c7d506a..11030d033a 100644 --- a/package.json +++ b/package.json @@ -127,7 +127,7 @@ "jest-junit": "^13.0.0", "lint-staged": "^13.2.2", "msw": "^1.2.1", - "postcss": "^8.4.12", + "postcss": "^8.4.31", "prettier": "^2.8.8", "react-scripts": "^5.0.1", "react-test-renderer": "^18.2.0", diff --git a/src/layouts/BaseLayout/BaseLayout.spec.jsx b/src/layouts/BaseLayout/BaseLayout.spec.jsx index ee55dabbc0..3221d75457 100644 --- a/src/layouts/BaseLayout/BaseLayout.spec.jsx +++ b/src/layouts/BaseLayout/BaseLayout.spec.jsx @@ -78,6 +78,7 @@ const internalUserHasSyncedProviders = { service: 'github', }, ], + termsAgreement: false, } const queryClient = new QueryClient({ diff --git a/src/layouts/BaseLayout/hooks/useUserAccessGate.js b/src/layouts/BaseLayout/hooks/useUserAccessGate.js index 08bdfdf1d0..c2bd947465 100644 --- a/src/layouts/BaseLayout/hooks/useUserAccessGate.js +++ b/src/layouts/BaseLayout/hooks/useUserAccessGate.js @@ -73,13 +73,8 @@ const useUserAccessGate = () => { // the undefined provider check can be removed when the ToS has // been refactored to no longer use a provider - if ( - termsOfServicePage && - !isUndefined(provider) && - !isGuest && - !config.IS_SELF_HOSTED - ) { - showAgreeToTerms = userData?.termsAgreement === false + if (termsOfServicePage && !isGuest && !config.IS_SELF_HOSTED) { + showAgreeToTerms = internalUser?.termsAgreement === false } const onSyncPage = currentRoute.path === '/sync' diff --git a/src/layouts/BaseLayout/hooks/useUserAccessGate.spec.tsx b/src/layouts/BaseLayout/hooks/useUserAccessGate.spec.tsx index 3f350f6cb5..b8f59be8d1 100644 --- a/src/layouts/BaseLayout/hooks/useUserAccessGate.spec.tsx +++ b/src/layouts/BaseLayout/hooks/useUserAccessGate.spec.tsx @@ -160,6 +160,30 @@ const internalUserHasSyncedProviders = { ], } +const internalUserWithSignedTOS = { + email: userSignedInIdentity.email, + name: userSignedInIdentity.name, + externalId: '123', + owners: [ + { + service: 'github', + }, + ], + termsAgreement: true, +} + +const internalUserWithUnsignedTOS = { + email: userSignedInIdentity.email, + name: userSignedInIdentity.name, + externalId: '123', + owners: [ + { + service: 'github', + }, + ], + termsAgreement: false, +} + type InternalUser = | typeof internalUserNoSyncedProviders | typeof internalUserHasSyncedProviders @@ -249,7 +273,7 @@ describe('useUserAccessGate', () => { 'signed TOS', { user: loggedInUser, - internalUser: internalUserHasSyncedProviders, + internalUser: internalUserWithSignedTOS, termsOfServicePage: true, isSelfHosted: false, defaultOrgSelectorPage: false, @@ -361,7 +385,7 @@ describe('useUserAccessGate', () => { 'unsigned TOS', { user: loggedInUnsignedUser, - internalUser: internalUserHasSyncedProviders, + internalUser: internalUserWithUnsignedTOS, termsOfServicePage: true, isSelfHosted: false, defaultOrgSelectorPage: false, diff --git a/src/pages/CommitDetailPage/Header/Header.spec.tsx b/src/pages/CommitDetailPage/Header/Header.spec.tsx new file mode 100644 index 0000000000..f039752999 --- /dev/null +++ b/src/pages/CommitDetailPage/Header/Header.spec.tsx @@ -0,0 +1,38 @@ +import { QueryClient, QueryClientProvider } from '@tanstack/react-query' +import { render, screen } from '@testing-library/react' +import { setupServer } from 'msw/node' +import { MemoryRouter, Route } from 'react-router-dom' + +import Header from './Header' + +jest.mock('./HeaderDefault', () => () => 'Default Header') + +const queryClient = new QueryClient({ + defaultOptions: { queries: { retry: false } }, +}) +const server = setupServer() + +const wrapper: React.FC = ({ children }) => ( + + + {children} + + +) + +beforeAll(() => server.listen()) +afterEach(() => { + queryClient.clear() + server.resetHandlers() +}) +afterAll(() => server.close()) + +describe('Header', () => { + describe('when rendered with valid values', () => { + it('renders the default header', async () => { + render(
, { wrapper }) + const defaultHeader = await screen.findByText(/Default Header/) + expect(defaultHeader).toBeInTheDocument() + }) + }) +}) diff --git a/src/pages/CommitDetailPage/Header/Header.tsx b/src/pages/CommitDetailPage/Header/Header.tsx new file mode 100644 index 0000000000..d03d8e2ed3 --- /dev/null +++ b/src/pages/CommitDetailPage/Header/Header.tsx @@ -0,0 +1,7 @@ +import HeaderDefault from './HeaderDefault' + +function Header() { + return +} + +export default Header diff --git a/src/pages/CommitDetailPage/Header/Header.jsx b/src/pages/CommitDetailPage/Header/HeaderDefault/HeaderDefault.jsx similarity index 96% rename from src/pages/CommitDetailPage/Header/Header.jsx rename to src/pages/CommitDetailPage/Header/HeaderDefault/HeaderDefault.jsx index d15b7d0cda..b2f030fb71 100644 --- a/src/pages/CommitDetailPage/Header/Header.jsx +++ b/src/pages/CommitDetailPage/Header/HeaderDefault/HeaderDefault.jsx @@ -8,9 +8,10 @@ import Icon from 'ui/Icon' import TruncatedMessage from 'ui/TruncatedMessage/TruncatedMessage' import { useCommitHeaderData } from './hooks' -import PullLabel from './PullLabel' -function Header() { +import PullLabel from '../PullLabel' + +function HeaderDefault() { const { provider, owner, repo, commit: commitSha } = useParams() const shortSHA = commitSha?.slice(0, 7) @@ -85,4 +86,4 @@ function Header() { ) } -export default Header +export default HeaderDefault diff --git a/src/pages/CommitDetailPage/Header/Header.spec.jsx b/src/pages/CommitDetailPage/Header/HeaderDefault/HeaderDefault.spec.jsx similarity index 89% rename from src/pages/CommitDetailPage/Header/Header.spec.jsx rename to src/pages/CommitDetailPage/Header/HeaderDefault/HeaderDefault.spec.jsx index 61a5b9c05c..4e96afbf7b 100644 --- a/src/pages/CommitDetailPage/Header/Header.spec.jsx +++ b/src/pages/CommitDetailPage/Header/HeaderDefault/HeaderDefault.spec.jsx @@ -6,7 +6,7 @@ import { MemoryRouter, Route } from 'react-router-dom' import { useTruncation } from 'ui/TruncatedMessage/hooks' -import Header from './Header' +import HeaderDefault from './HeaderDefault' jest.mock('ui/TruncatedMessage/hooks') @@ -49,7 +49,7 @@ afterEach(() => { }) afterAll(() => server.close()) -describe('Header', () => { +describe('HeaderDefault', () => { function setup(pullId = 1234) { useTruncation.mockImplementation(() => ({ ref: () => {}, @@ -69,21 +69,21 @@ describe('Header', () => { }) it('renders commit message', async () => { - render(
, { wrapper }) + render(, { wrapper }) const message = await screen.findByText('Test Commit') expect(message).toBeInTheDocument() }) it('The summary header', async () => { - render(
, { wrapper }) + render(, { wrapper }) const authored = await screen.findByText(/authored commit/) expect(authored).toBeInTheDocument() }) it('renders commit id and link', async () => { - render(
, { wrapper }) + render(, { wrapper }) const commitLink = await screen.findByRole('link', { name: /id-1/i, @@ -95,14 +95,14 @@ describe('Header', () => { }) it('renders CI Passed', async () => { - render(
, { wrapper }) + render(, { wrapper }) const ciPassed = await screen.findByText('CI Passed') expect(ciPassed).toBeInTheDocument() }) it('renders branch name', async () => { - render(
, { wrapper }) + render(, { wrapper }) const branchName = await screen.findByText('cool-branch') expect(branchName).toBeInTheDocument() @@ -115,7 +115,7 @@ describe('Header', () => { }) it('does not render the pull label', async () => { - render(
, { wrapper }) + render(, { wrapper }) await waitFor(() => queryClient.isFetching) await waitFor(() => !queryClient.isFetching) diff --git a/src/pages/CommitDetailPage/Header/hooks/index.js b/src/pages/CommitDetailPage/Header/HeaderDefault/hooks/index.js similarity index 100% rename from src/pages/CommitDetailPage/Header/hooks/index.js rename to src/pages/CommitDetailPage/Header/HeaderDefault/hooks/index.js diff --git a/src/pages/CommitDetailPage/Header/hooks/useCommitHeaderData.spec.tsx b/src/pages/CommitDetailPage/Header/HeaderDefault/hooks/useCommitHeaderData.spec.tsx similarity index 100% rename from src/pages/CommitDetailPage/Header/hooks/useCommitHeaderData.spec.tsx rename to src/pages/CommitDetailPage/Header/HeaderDefault/hooks/useCommitHeaderData.spec.tsx diff --git a/src/pages/CommitDetailPage/Header/hooks/useCommitHeaderData.tsx b/src/pages/CommitDetailPage/Header/HeaderDefault/hooks/useCommitHeaderData.tsx similarity index 100% rename from src/pages/CommitDetailPage/Header/hooks/useCommitHeaderData.tsx rename to src/pages/CommitDetailPage/Header/HeaderDefault/hooks/useCommitHeaderData.tsx diff --git a/src/pages/CommitDetailPage/Header/HeaderDefault/index.js b/src/pages/CommitDetailPage/Header/HeaderDefault/index.js new file mode 100644 index 0000000000..a6b9f99db2 --- /dev/null +++ b/src/pages/CommitDetailPage/Header/HeaderDefault/index.js @@ -0,0 +1 @@ +export { default } from './HeaderDefault' diff --git a/src/pages/CommitDetailPage/Header/HeaderTeam/hooks/index.js b/src/pages/CommitDetailPage/Header/HeaderTeam/hooks/index.js new file mode 100644 index 0000000000..eaebac66b2 --- /dev/null +++ b/src/pages/CommitDetailPage/Header/HeaderTeam/hooks/index.js @@ -0,0 +1 @@ +export * from './useCommitHeaderDataTeam' diff --git a/src/pages/CommitDetailPage/Header/HeaderTeam/hooks/useCommitHeaderDataTeam.spec.tsx b/src/pages/CommitDetailPage/Header/HeaderTeam/hooks/useCommitHeaderDataTeam.spec.tsx new file mode 100644 index 0000000000..563b9dca9f --- /dev/null +++ b/src/pages/CommitDetailPage/Header/HeaderTeam/hooks/useCommitHeaderDataTeam.spec.tsx @@ -0,0 +1,292 @@ +import { QueryClient, QueryClientProvider } from '@tanstack/react-query' +import { renderHook, waitFor } from '@testing-library/react' +import { graphql } from 'msw' +import { setupServer } from 'msw/node' + +import { useCommitHeaderDataTeam } from './useCommitHeaderDataTeam' + +const mockRepository = { + owner: { + repository: { + __typename: 'Repository', + commit: { + author: { + username: 'cool-user', + }, + branchName: 'cool-branch', + ciPassed: true, + commitid: 'id-1', + createdAt: '2022-01-01T12:59:59', + message: 'cool commit message', + pullId: 1234, + compareWithParent: { + __typename: 'Comparison', + patchTotals: { + coverage: 100, + }, + }, + }, + }, + }, +} + +const mockNotFoundError = { + owner: { + repository: { + __typename: 'NotFoundError', + message: 'commit not found', + }, + }, +} + +const mockOwnerNotActivatedError = { + owner: { + repository: { + __typename: 'OwnerNotActivatedError', + message: 'owner not activated', + }, + }, +} + +const mockNullOwner = { + owner: null, +} + +const mockUnsuccessfulParseError = {} + +const queryClient = new QueryClient({ + defaultOptions: { queries: { retry: false, useErrorBoundary: false } }, +}) +const server = setupServer() + +const wrapper: React.FC = ({ children }) => ( + {children} +) + +beforeAll(() => { + server.listen() +}) + +afterEach(() => { + queryClient.clear() + server.resetHandlers() +}) + +afterAll(() => { + server.close() +}) + +interface SetupArgs { + isNotFoundError?: boolean + isOwnerNotActivatedError?: boolean + isUnsuccessfulParseError?: boolean + isNullOwner?: boolean +} + +describe('useCommitHeaderDataTeam', () => { + function setup({ + isNotFoundError = false, + isOwnerNotActivatedError = false, + isUnsuccessfulParseError = false, + isNullOwner = false, + }: SetupArgs) { + server.use( + graphql.query('CommitPageHeaderDataTeam', (req, res, ctx) => { + if (isNotFoundError) { + return res(ctx.status(200), ctx.data(mockNotFoundError)) + } else if (isOwnerNotActivatedError) { + return res(ctx.status(200), ctx.data(mockOwnerNotActivatedError)) + } else if (isUnsuccessfulParseError) { + return res(ctx.status(200), ctx.data(mockUnsuccessfulParseError)) + } else if (isNullOwner) { + return res(ctx.status(200), ctx.data(mockNullOwner)) + } else { + return res(ctx.status(200), ctx.data(mockRepository)) + } + }) + ) + } + + describe('fetching data', () => { + describe('returns Repository __typename', () => { + describe('there is data', () => { + it('sets the correct data', async () => { + setup({}) + + const { result } = renderHook( + () => + useCommitHeaderDataTeam({ + provider: 'gh', + owner: 'codecov', + repo: 'test-repo', + commitId: 'id-1', + }), + { wrapper } + ) + + await waitFor(() => result.current.isLoading) + await waitFor(() => !result.current.isLoading) + + const expectedResult = { + commit: { + author: { + username: 'cool-user', + }, + branchName: 'cool-branch', + ciPassed: true, + commitid: 'id-1', + createdAt: '2022-01-01T12:59:59', + message: 'cool commit message', + pullId: 1234, + compareWithParent: { + __typename: 'Comparison', + patchTotals: { + coverage: 100, + }, + }, + }, + } + + await waitFor(() => + expect(result.current.data).toStrictEqual(expectedResult) + ) + }) + }) + describe('there is a null owner', () => { + it('sets the correct data', async () => { + setup({ isNullOwner: true }) + + const { result } = renderHook( + () => + useCommitHeaderDataTeam({ + provider: 'gh', + owner: 'codecov', + repo: 'test-repo', + commitId: 'id-1', + }), + { wrapper } + ) + + await waitFor(() => result.current.isLoading) + await waitFor(() => !result.current.isLoading) + + const expectedResult = { + commit: null, + } + + await waitFor(() => + expect(result.current.data).toStrictEqual(expectedResult) + ) + }) + }) + }) + + describe('returns NotFoundError __typename', () => { + let oldConsoleError = console.error + + beforeEach(() => { + console.error = () => null + }) + + afterEach(() => { + console.error = oldConsoleError + }) + + it('throws an error', async () => { + setup({ isNotFoundError: true }) + + const { result } = renderHook( + () => + useCommitHeaderDataTeam({ + provider: 'gh', + owner: 'codecov', + repo: 'test-repo', + commitId: 'id-1', + }), + { wrapper } + ) + + await waitFor(() => expect(result.current.isError).toBeTruthy()) + await waitFor(() => + expect(result.current.error).toEqual( + expect.objectContaining({ + status: 404, + }) + ) + ) + }) + }) + + describe('returns OwnerNotActivatedError __typename', () => { + let oldConsoleError = console.error + + beforeEach(() => { + console.error = () => null + }) + + afterEach(() => { + console.error = oldConsoleError + }) + + it('throws an error', async () => { + setup({ isOwnerNotActivatedError: true }) + + const { result } = renderHook( + () => + useCommitHeaderDataTeam({ + provider: 'gh', + owner: 'codecov', + repo: 'test-repo', + commitId: 'id-1', + }), + { wrapper } + ) + + await waitFor(() => expect(result.current.isError).toBeTruthy()) + await waitFor(() => + expect(result.current.error).toEqual( + expect.objectContaining({ + status: 403, + }) + ) + ) + }) + }) + + describe('unsuccessful parse of zod schema', () => { + let oldConsoleError = console.error + + beforeEach(() => { + console.error = () => null + }) + + afterEach(() => { + console.error = oldConsoleError + }) + + it('throws an error', async () => { + setup({ isUnsuccessfulParseError: true }) + + const { result } = renderHook( + () => + useCommitHeaderDataTeam({ + provider: 'gh', + owner: 'codecov', + repo: 'test-repo', + commitId: 'id-1', + }), + { wrapper } + ) + + await waitFor(() => expect(result.current.isError).toBeTruthy()) + await waitFor(() => + expect(result.current.error).toEqual( + expect.objectContaining({ + status: 404, + }) + ) + ) + }) + }) + }) +}) diff --git a/src/pages/CommitDetailPage/Header/HeaderTeam/hooks/useCommitHeaderDataTeam.tsx b/src/pages/CommitDetailPage/Header/HeaderTeam/hooks/useCommitHeaderDataTeam.tsx new file mode 100644 index 0000000000..a3c36b3763 --- /dev/null +++ b/src/pages/CommitDetailPage/Header/HeaderTeam/hooks/useCommitHeaderDataTeam.tsx @@ -0,0 +1,198 @@ +import { useQuery } from '@tanstack/react-query' +import { z } from 'zod' + +import { + FirstPullRequestSchema, + MissingBaseCommitSchema, + MissingBaseReportSchema, + MissingComparisonSchema, + MissingHeadCommitSchema, + MissingHeadReportSchema, +} from 'services/comparison/schemas' +import { + RepoNotFoundErrorSchema, + RepoOwnerNotActivatedErrorSchema, +} from 'services/repo' +import Api from 'shared/api' +import A from 'ui/A' + +const CoverageObjSchema = z.object({ + coverage: z.number().nullable(), +}) + +const ComparisonSchema = z.object({ + __typename: z.literal('Comparison'), + patchTotals: CoverageObjSchema.nullable(), +}) + +const CompareWithParentSchema = z.discriminatedUnion('__typename', [ + ComparisonSchema, + FirstPullRequestSchema, + MissingBaseCommitSchema, + MissingBaseReportSchema, + MissingComparisonSchema, + MissingHeadCommitSchema, + MissingHeadReportSchema, +]) + +const RepositorySchema = z.object({ + __typename: z.literal('Repository'), + commit: z + .object({ + author: z + .object({ + username: z.string().nullable(), + }) + .nullable(), + branchName: z.string().nullable(), + ciPassed: z.boolean().nullable(), + commitid: z.string().nullable(), + createdAt: z.string().nullable(), + message: z.string().nullable(), + pullId: z.number().nullable(), + compareWithParent: CompareWithParentSchema.nullable(), + }) + .nullable(), +}) + +export const CommitHeaderDataTeamSchema = z.object({ + owner: z + .object({ + repository: z + .discriminatedUnion('__typename', [ + RepositorySchema, + RepoNotFoundErrorSchema, + RepoOwnerNotActivatedErrorSchema, + ]) + .nullable(), + }) + .nullable(), +}) + +export type CommitHeaderDataTeam = z.infer + +const query = ` + query CommitPageHeaderDataTeam( + $owner: String! + $repo: String! + $commitId: String! + ) { + owner(username: $owner) { + repository(name: $repo) { + __typename + ... on Repository { + commit(id: $commitId) { + author { + username + } + branchName + ciPassed + commitid + createdAt + message + pullId + compareWithParent { + __typename + ... on Comparison { + patchTotals { + coverage: percentCovered + } + } + ... on FirstPullRequest { + message + } + ... on MissingBaseCommit { + message + } + ... on MissingHeadCommit { + message + } + ... on MissingComparison { + message + } + ... on MissingBaseReport { + message + } + ... on MissingHeadReport { + message + } + } + } + } + ... on NotFoundError { + message + } + ... on OwnerNotActivatedError { + message + } + } + } + } +` + +interface UseCommitHeaderDataTeamArgs { + provider: string + owner: string + repo: string + commitId: string +} + +export const useCommitHeaderDataTeam = ({ + provider, + owner, + repo, + commitId, +}: UseCommitHeaderDataTeamArgs) => + useQuery({ + queryKey: ['CommitPageHeaderData', provider, owner, repo, commitId, query], + queryFn: ({ signal }) => + Api.graphql({ + provider, + query, + signal, + variables: { + provider, + owner, + repo, + commitId, + }, + }).then((res) => { + const parsedData = CommitHeaderDataTeamSchema.safeParse(res?.data) + + if (!parsedData.success) { + return Promise.reject({ + status: 404, + data: {}, + }) + } + + const data = parsedData.data + + if (data?.owner?.repository?.__typename === 'NotFoundError') { + return Promise.reject({ + status: 404, + data: {}, + }) + } + + if (data?.owner?.repository?.__typename === 'OwnerNotActivatedError') { + return Promise.reject({ + status: 403, + data: { + detail: ( +

+ Activation is required to view this repo, please{' '} + {/* @ts-expect-error */} + click here to activate + your account. +

+ ), + }, + }) + } + + return { + commit: data?.owner?.repository?.commit ?? null, + } + }), + }) diff --git a/src/pages/CommitDetailPage/Header/PullLabel.jsx b/src/pages/CommitDetailPage/Header/PullLabel/PullLabel.jsx similarity index 100% rename from src/pages/CommitDetailPage/Header/PullLabel.jsx rename to src/pages/CommitDetailPage/Header/PullLabel/PullLabel.jsx diff --git a/src/pages/CommitDetailPage/Header/PullLabel/PullLabel.spec.jsx b/src/pages/CommitDetailPage/Header/PullLabel/PullLabel.spec.jsx new file mode 100644 index 0000000000..6e7069ead7 --- /dev/null +++ b/src/pages/CommitDetailPage/Header/PullLabel/PullLabel.spec.jsx @@ -0,0 +1,57 @@ +import { render, screen } from '@testing-library/react' +import { MemoryRouter, Route } from 'react-router-dom' + +import PullLabel from './PullLabel' + +const wrapper = ({ children }) => ( + + {children} + +) + +const mockValidProps = { + pullId: 123, + provider: 'gh', + providerPullUrl: 'https://github.com/codecov/test-repo/pull/123', +} + +const mockInvalidProps = { + pullId: null, + provider: null, + providerPullUrl: null, +} + +describe('PullLabel', () => { + describe('when rendered with a pull id, provider and provider pull url', () => { + it('renders pull id', async () => { + render(, { wrapper }) + + const pullIdLink = await screen.findByRole('link', { name: /#123/ }) + expect(pullIdLink).toBeInTheDocument() + expect(pullIdLink).toHaveAttribute( + 'href', + '/gh/codecov/test-repo/pull/123' + ) + }) + + it('renders provider pull url', async () => { + render(, { wrapper }) + + const pullUrlLink = await screen.findByRole('link', { name: /Github/ }) + expect(pullUrlLink).toBeInTheDocument() + expect(pullUrlLink).toHaveAttribute( + 'href', + 'https://github.com/codecov/test-repo/pull/123' + ) + }) + }) + + describe('when rendered with no pullid, provider or provider pull url', () => { + it('does not render label', () => { + render(, { wrapper }) + + const message = screen.queryByText(/#123/) + expect(message).not.toBeInTheDocument() + }) + }) +}) diff --git a/src/pages/CommitDetailPage/Header/PullLabel/index.js b/src/pages/CommitDetailPage/Header/PullLabel/index.js new file mode 100644 index 0000000000..8d31afc1b8 --- /dev/null +++ b/src/pages/CommitDetailPage/Header/PullLabel/index.js @@ -0,0 +1 @@ +export { default } from './PullLabel' diff --git a/src/pages/TermsOfService/TermsOfService.jsx b/src/pages/TermsOfService/TermsOfService.jsx index 1aacdc2f6d..9ac09789e4 100644 --- a/src/pages/TermsOfService/TermsOfService.jsx +++ b/src/pages/TermsOfService/TermsOfService.jsx @@ -5,7 +5,7 @@ import { useForm } from 'react-hook-form' import { z } from 'zod' import umbrellaSvg from 'assets/svg/umbrella.svg' -import { useUser } from 'services/user' +import { useInternalUser } from 'services/user' import A from 'ui/A' import Button from 'ui/Button' import TextInput from 'ui/TextInput' @@ -18,8 +18,8 @@ const FormSchema = z.object({ tos: z.literal(true), }) -function isDisabled({ isValid, isDirty }) { - return (!isValid && isDirty) || !isDirty +function isDisabled({ isValid, isDirty, isMutationLoading }) { + return (!isValid && isDirty) || !isDirty || isMutationLoading } function EmailInput({ register, marketingEmailMessage, showEmailRequired }) { @@ -61,7 +61,7 @@ export default function TermsOfService() { resolver: zodResolver(FormSchema), mode: 'onChange', }) - const { mutate } = useSaveTermsAgreement({ + const { mutate, isLoading: isMutationLoading } = useSaveTermsAgreement({ onSuccess: ({ data }) => { if (data?.saveTermsAgreement?.error) { setError('apiError', data?.saveTermsAgreement?.error) @@ -70,12 +70,13 @@ export default function TermsOfService() { }, onError: (error) => setError('apiError', error), }) - const { data: currentUser, isLoading: userIsLoading } = useUser() + const { data: currentUser, isLoading: userIsLoading } = useInternalUser() const onSubmit = (data) => { mutate({ businessEmail: data?.marketingEmail || currentUser?.email, termsAgreement: true, + marketingConsent: data?.marketingConsent, }) } @@ -177,7 +178,7 @@ export default function TermsOfService() { )}