Skip to content

Commit 6144625

Browse files
Fix coverage
1 parent c8902a9 commit 6144625

File tree

8 files changed

+268
-3
lines changed

8 files changed

+268
-3
lines changed

src/layouts/BaseLayout/BaseLayout.test.tsx

+24
Original file line numberDiff line numberDiff line change
@@ -170,6 +170,22 @@ const mockNavigatorData = {
170170
},
171171
}
172172

173+
const mockOwnerContext = {
174+
owner: {
175+
ownerid: 123,
176+
},
177+
}
178+
179+
const mockRepoContext = {
180+
owner: {
181+
repository: {
182+
__typename: 'Repository',
183+
repoid: 321,
184+
private: false,
185+
},
186+
},
187+
}
188+
173189
const server = setupServer()
174190
const queryClient = new QueryClient({
175191
defaultOptions: {
@@ -288,6 +304,14 @@ describe('BaseLayout', () => {
288304
graphql.query('NavigatorData', () => {
289305
return HttpResponse.json({ data: mockNavigatorData })
290306
}),
307+
graphql.query('OwnerContext', () => {
308+
return HttpResponse.json({ data: mockOwnerContext })
309+
}),
310+
graphql.query('RepoContext', () => {
311+
return HttpResponse.json({
312+
data: mockRepoContext,
313+
})
314+
}),
291315
http.get('/internal/users/current', () => {
292316
return HttpResponse.json({})
293317
})

src/layouts/Header/components/UserDropdown/UserDropdown.test.tsx

+35
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@ import { type Mock } from 'vitest'
88

99
import config from 'config'
1010

11+
import { eventTracker } from 'services/events/events'
1112
import { useImage } from 'services/image'
1213
import { Plans } from 'shared/utils/billing'
1314

@@ -60,6 +61,7 @@ const mockUser = {
6061
vi.mock('services/image')
6162
vi.mock('config')
6263
vi.mock('js-cookie')
64+
vi.mock('services/events/events')
6365

6466
const queryClient = new QueryClient({
6567
defaultOptions: { queries: { retry: false } },
@@ -214,6 +216,39 @@ describe('UserDropdown', () => {
214216
'https://github.com/apps/codecov/installations/new'
215217
)
216218
})
219+
220+
describe('when app access link is clicked', () => {
221+
it('tracks a Button Clicked event', async () => {
222+
const { user } = setup()
223+
render(<UserDropdown />, {
224+
wrapper: wrapper(),
225+
})
226+
227+
expect(
228+
screen.queryByText('Install Codecov app')
229+
).not.toBeInTheDocument()
230+
231+
const openSelect = await screen.findByTestId('user-dropdown-trigger')
232+
await user.click(openSelect)
233+
234+
const link = screen.getByText('Install Codecov app')
235+
expect(link).toBeVisible()
236+
expect(link).toHaveAttribute(
237+
'href',
238+
'https://github.com/apps/codecov/installations/new'
239+
)
240+
241+
await user.click(link)
242+
243+
expect(eventTracker().track).toHaveBeenCalledWith({
244+
type: 'Button Clicked',
245+
properties: {
246+
buttonType: 'Install GitHub App',
247+
buttonLocation: 'User dropdown',
248+
},
249+
})
250+
})
251+
})
217252
})
218253
})
219254
describe('when not on GitHub', () => {

src/pages/OwnerPage/HeaderBanners/GithubConfigBanner/GithubConfigBanner.test.jsx

+26-1
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,12 @@
1-
import { render, screen } from '@testing-library/react'
1+
import { act, render, screen } from '@testing-library/react'
22
import { MemoryRouter, Route, Switch } from 'react-router-dom'
33

4+
import { eventTracker } from 'services/events/events'
5+
46
import GithubConfigBanner from './GithubConfigBanner'
57

8+
vi.mock('services/events/events')
9+
610
const wrapper =
711
({ provider = 'gh' }) =>
812
({ children }) => {
@@ -34,6 +38,27 @@ describe('GithubConfigBanner', () => {
3438
)
3539
expect(body).toBeInTheDocument()
3640
})
41+
42+
describe('and button is clicked', () => {
43+
it('tracks a Button Clicked event', async () => {
44+
render(<GithubConfigBanner />, {
45+
wrapper: wrapper({ provider: 'gh' }),
46+
})
47+
48+
const title = screen.getByText(/Codecov's GitHub app/)
49+
expect(title).toBeInTheDocument()
50+
51+
act(() => title.click())
52+
53+
expect(eventTracker().track).toHaveBeenCalledWith({
54+
type: 'Button Clicked',
55+
properties: {
56+
buttonType: 'Install GitHub App',
57+
buttonLocation: 'Configure GitHub app banner',
58+
},
59+
})
60+
})
61+
})
3762
})
3863

3964
describe('when rendered with other providers', () => {
+18
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,18 @@
1+
import { EventTracker } from '../types'
2+
3+
//
4+
// Use this mock by
5+
// vi.mock('services/events/events')
6+
// and
7+
// expect(eventTracker().track).toHaveBeenCalledWith()
8+
//
9+
10+
const MOCK_EVENT_TRACKER: EventTracker = {
11+
identify: vi.fn(),
12+
track: vi.fn(),
13+
setContext: vi.fn(),
14+
}
15+
16+
export function eventTracker() {
17+
return MOCK_EVENT_TRACKER
18+
}

src/services/events/events.test.tsx

+46
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
import config from 'config'
22

33
import { eventTracker, initEventTracker, StubbedEventTracker } from './events'
4+
import { EventTracker } from './types'
45

56
vi.mock('config')
67
const mockCaptureException = vi.hoisted(() => vi.fn())
@@ -38,6 +39,51 @@ describe('EventTracker', () => {
3839
})
3940
})
4041

42+
describe('abstract EventTracker', () => {
43+
it('should throw errors if instantiated', () => {
44+
// @ts-expect-error instantiating an abstract class
45+
const tracker = new EventTracker()
46+
try {
47+
tracker.setContext({})
48+
} catch (e) {
49+
expect(e).toEqual(
50+
new Error(
51+
'EventTracker is abstract. Method setContext must be implemented.'
52+
)
53+
)
54+
}
55+
56+
try {
57+
tracker.identify({
58+
userOwnerId: 1,
59+
provider: 'gh',
60+
})
61+
} catch (e) {
62+
expect(e).toEqual(
63+
new Error(
64+
'EventTracker is abstract. Method identify must be implemented.'
65+
)
66+
)
67+
}
68+
69+
try {
70+
tracker.track({
71+
type: 'Button Clicked',
72+
properties: {
73+
buttonType: 'Install GitHub App',
74+
buttonLocation: 'test',
75+
},
76+
})
77+
} catch (e) {
78+
expect(e).toEqual(
79+
new Error(
80+
'EventTracker is abstract. Method track must be implemented.'
81+
)
82+
)
83+
}
84+
})
85+
})
86+
4187
describe('when initEventTracker is called', () => {
4288
describe('and initAmplitude() throws an error', () => {
4389
beforeEach(() => {

src/services/events/hooks.test.tsx

+26-1
Original file line numberDiff line numberDiff line change
@@ -75,17 +75,26 @@ afterAll(() => {
7575

7676
interface SetupArgs {
7777
badOwner?: boolean
78+
nullOwnerid?: boolean
7879
badRepo?: boolean
7980
repoError?: 'NotFoundError' | 'OwnerNotActivatedError'
8081
}
8182

8283
describe('useEventContext', () => {
83-
function setup({ badOwner = false, badRepo = false, repoError }: SetupArgs) {
84+
function setup({
85+
badOwner = false,
86+
nullOwnerid = false,
87+
badRepo = false,
88+
repoError,
89+
}: SetupArgs) {
8490
server.use(
8591
graphql.query('OwnerContext', () => {
8692
if (badOwner) {
8793
return HttpResponse.json({ data: {} })
8894
}
95+
if (nullOwnerid) {
96+
return HttpResponse.json({ data: { owner: { ownerid: null } } })
97+
}
8998
return HttpResponse.json({ data: mockOwnerContext })
9099
}),
91100
graphql.query('RepoContext', () => {
@@ -149,6 +158,22 @@ describe('useEventContext', () => {
149158
})
150159
})
151160

161+
describe('when called with null ownerid', () => {
162+
it('sets event context with undefined ownerid', async () => {
163+
setup({ nullOwnerid: true })
164+
renderHook(() => useEventContext(), {
165+
wrapper: ownerWrapper,
166+
})
167+
168+
await waitFor(() => {
169+
expect(mockedSetContext).toHaveBeenCalledWith({
170+
owner: undefined,
171+
repo: undefined,
172+
})
173+
})
174+
})
175+
})
176+
152177
describe('OwnerContext hook', () => {
153178
describe('when bad data is returned', () => {
154179
it('rejects with 404 failed to parse', async () => {

src/shared/ListRepo/InactiveRepo/InactiveRepo.test.tsx

+31-1
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,10 @@
11
import { QueryClient, QueryClientProvider } from '@tanstack/react-query'
2-
import { render, screen } from '@testing-library/react'
2+
import { act, render, screen } from '@testing-library/react'
33
import React from 'react'
44
import { MemoryRouter, Route } from 'react-router-dom'
55

6+
import { eventTracker } from 'services/events/events'
7+
68
import InactiveRepo from './InactiveRepo'
79

810
const queryClient = new QueryClient()
@@ -14,6 +16,8 @@ const wrapper: React.FC<React.PropsWithChildren> = ({ children }) => (
1416
</QueryClientProvider>
1517
)
1618

19+
vi.mock('services/events/events')
20+
1721
describe('InactiveRepo', () => {
1822
it('renders "Deactivated" Text when not active', async () => {
1923
render(<InactiveRepo isActive owner="bob" />, { wrapper })
@@ -44,4 +48,30 @@ describe('InactiveRepo', () => {
4448
expect(ctaText).toBeInTheDocument()
4549
expect(ctaText).toHaveAttribute('href', '/gh/bob/coolguy/new')
4650
})
51+
52+
it('tracks a Button Clicked event when Configure is clicked', async () => {
53+
render(
54+
<InactiveRepo
55+
isActive={false}
56+
isCurrentUserPartOfOrg
57+
repoName="coolguy"
58+
owner="bob"
59+
/>,
60+
{ wrapper }
61+
)
62+
63+
const ctaText = await screen.findByText(/Configure/)
64+
expect(ctaText).toBeInTheDocument()
65+
expect(ctaText).toHaveAttribute('href', '/gh/bob/coolguy/new')
66+
67+
act(() => ctaText.click())
68+
69+
expect(eventTracker().track).toHaveBeenCalledWith({
70+
type: 'Button Clicked',
71+
properties: {
72+
buttonType: 'Configure Repo',
73+
buttonLocation: 'Repo list',
74+
},
75+
})
76+
})
4777
})

src/ui/ContextSwitcher/ContextSwitcher.test.jsx

+62
Original file line numberDiff line numberDiff line change
@@ -7,10 +7,12 @@ import { MemoryRouter, Route, Switch } from 'react-router-dom'
77

88
import config, { DEFAULT_GH_APP } from 'config'
99

10+
import { eventTracker } from 'services/events/events'
1011
import { useImage } from 'services/image'
1112

1213
import ContextSwitcher from './ContextSwitcher'
1314

15+
vi.mock('services/events/events')
1416
vi.mock('services/image')
1517
const mocks = vi.hoisted(() => ({
1618
useIntersection: vi.fn(),
@@ -713,6 +715,66 @@ describe('ContextSwitcher', () => {
713715
})
714716
})
715717

718+
describe('when install gh app button is clicked', () => {
719+
it('tracks a Button Clicked event', async () => {
720+
const { user } = setup()
721+
render(
722+
<ContextSwitcher
723+
activeContext={{
724+
username: 'laudna',
725+
avatarUrl: 'http://127.0.0.1/avatar-url',
726+
}}
727+
contexts={[
728+
{
729+
owner: {
730+
username: 'laudna',
731+
avatarUrl: 'http://127.0.0.1/avatar-url',
732+
},
733+
pageName: 'provider',
734+
},
735+
{
736+
owner: {
737+
username: 'spotify',
738+
avatarUrl: 'http://127.0.0.1/avatar-url',
739+
},
740+
pageName: 'owner',
741+
},
742+
{
743+
owner: {
744+
username: 'codecov',
745+
avatarUrl: 'http://127.0.0.1/avatar-url',
746+
},
747+
pageName: 'owner',
748+
},
749+
]}
750+
currentUser={{
751+
defaultOrgUsername: 'codecov',
752+
}}
753+
src="imageUrl"
754+
isLoading={false}
755+
error={null}
756+
/>,
757+
{
758+
wrapper: wrapper(),
759+
}
760+
)
761+
762+
const button = await screen.findByRole('button', { expanded: false })
763+
await user.click(button)
764+
765+
const appButton = await screen.findByText('Install Codecov GitHub app')
766+
await user.click(appButton)
767+
768+
expect(eventTracker().track).toHaveBeenCalledWith({
769+
type: 'Button Clicked',
770+
properties: {
771+
buttonType: 'Install GitHub App',
772+
buttonLocation: 'Org selector',
773+
},
774+
})
775+
})
776+
})
777+
716778
describe('when on self-hosted', () => {
717779
beforeEach(() => {
718780
config.IS_SELF_HOSTED = true

0 commit comments

Comments
 (0)