Skip to content

Commit f42ab9d

Browse files
authored
fix: restore invite pageview tracking lost in the landing migration (#622)
1 parent d39d62f commit f42ab9d

2 files changed

Lines changed: 92 additions & 3 deletions

File tree

src/pages/invite/InvitePage.spec.tsx

Lines changed: 79 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,9 +4,13 @@ import { InvitePage } from './InvitePage'
44
const mockUseParams = jest.fn()
55
const mockInviteHero = jest.fn()
66
const mockFetch = jest.fn()
7+
const mockPage = jest.fn()
8+
let mockIsAnalyticsInitialized = true
9+
const INVITE_PATHNAME = '/invite/Brai'
710

811
jest.mock('react-router-dom', () => ({
9-
useParams: () => mockUseParams()
12+
useParams: () => mockUseParams(),
13+
useLocation: () => ({ pathname: INVITE_PATHNAME })
1014
}))
1115

1216
jest.mock('decentraland-ui2', () => ({
@@ -18,6 +22,7 @@ jest.mock('@dcl/hooks', () => {
1822
const ReactLib = require('react') as typeof import('react')
1923
return {
2024
useTranslation: () => ({ t: (id: string) => id }),
25+
useAnalytics: () => ({ isInitialized: mockIsAnalyticsInitialized, page: mockPage }),
2126
useAsyncMemo: <T,>(factory: () => Promise<T>, deps: unknown[]) => {
2227
const [state, setState] = ReactLib.useState<{ value: T | null; loading: boolean }>({ value: null, loading: true })
2328
ReactLib.useEffect(() => {
@@ -83,6 +88,12 @@ afterAll(() => {
8388
global.fetch = originalFetch
8489
})
8590

91+
// Reset the analytics-initialized flag before every test so a test that flips it
92+
// false doesn't leak into the next one.
93+
beforeEach(() => {
94+
mockIsAnalyticsInitialized = true
95+
})
96+
8697
describe('when the referrer param is a Decentraland name', () => {
8798
beforeEach(() => {
8899
mockUseParams.mockReturnValue({ referrer: 'Brai' })
@@ -214,3 +225,70 @@ describe('when the referrer is empty', () => {
214225
expect(mockFetch).not.toHaveBeenCalled()
215226
})
216227
})
228+
229+
describe('when tracking the invite pageview', () => {
230+
beforeEach(() => {
231+
mockUseParams.mockReturnValue({ referrer: 'Brai' })
232+
mockFetch.mockResolvedValue({ json: () => Promise.resolve(null) })
233+
})
234+
235+
afterEach(() => {
236+
jest.resetAllMocks()
237+
})
238+
239+
it('should fire a page() event for the invite path once analytics is initialized', async () => {
240+
render(<InvitePage />)
241+
242+
await waitFor(() => expect(mockPage).toHaveBeenCalledWith(INVITE_PATHNAME))
243+
expect(mockPage).toHaveBeenCalledTimes(1)
244+
})
245+
246+
it('should not fire page() before analytics is initialized', async () => {
247+
mockIsAnalyticsInitialized = false
248+
render(<InvitePage />)
249+
250+
await waitFor(() => expect(mockInviteHero).toHaveBeenCalled())
251+
expect(mockPage).not.toHaveBeenCalled()
252+
})
253+
})
254+
255+
describe('when the document head already has meta tags', () => {
256+
const addMeta = (attr: 'name' | 'property', key: string, content: string) => {
257+
const meta = document.createElement('meta')
258+
meta.setAttribute(attr, key)
259+
meta.setAttribute('content', content)
260+
document.head.appendChild(meta)
261+
return meta
262+
}
263+
let metaEls: HTMLMetaElement[] = []
264+
265+
beforeEach(() => {
266+
mockUseParams.mockReturnValue({ referrer: '' })
267+
metaEls = [
268+
addMeta('name', 'description', 'orig-desc'),
269+
addMeta('property', 'og:title', 'orig-og-title'),
270+
addMeta('property', 'og:description', 'orig-og-desc')
271+
]
272+
})
273+
274+
afterEach(() => {
275+
metaEls.forEach(el => el.remove())
276+
metaEls = []
277+
jest.resetAllMocks()
278+
})
279+
280+
it('should set the invite meta on mount and restore the originals on unmount', async () => {
281+
const { unmount } = render(<InvitePage />)
282+
await waitFor(() => expect(mockInviteHero).toHaveBeenCalled())
283+
284+
expect(document.querySelector('meta[name="description"]')?.getAttribute('content')).toBe('page_invite.social.description')
285+
expect(document.querySelector('meta[property="og:title"]')?.getAttribute('content')).toBe('page_invite.social.title')
286+
expect(document.querySelector('meta[property="og:description"]')?.getAttribute('content')).toBe('page_invite.social.description')
287+
288+
unmount()
289+
290+
expect(document.querySelector('meta[name="description"]')?.getAttribute('content')).toBe('orig-desc')
291+
expect(document.querySelector('meta[property="og:title"]')?.getAttribute('content')).toBe('orig-og-title')
292+
expect(document.querySelector('meta[property="og:description"]')?.getAttribute('content')).toBe('orig-og-desc')
293+
})
294+
})

src/pages/invite/InvitePage.tsx

Lines changed: 13 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
import { Suspense, lazy, memo, useEffect } from 'react'
2-
import { useParams } from 'react-router-dom'
2+
import { useLocation, useParams } from 'react-router-dom'
33
import type { Profile } from 'dcl-catalyst-client/dist/client/specs/lambdas-client'
4-
import { useAsyncMemo, useTranslation } from '@dcl/hooks'
4+
import { useAnalytics, useAsyncMemo, useTranslation } from '@dcl/hooks'
55
import { EthAddress } from '@dcl/schemas/dist/misc'
66
import { useDesktopMediaQuery } from 'decentraland-ui2'
77
import { InviteHero } from '../../components/Invite/InviteHero/InviteHero'
@@ -78,8 +78,10 @@ function useDocumentMeta(title: string, description: string) {
7878

7979
const InvitePage = memo(() => {
8080
const { referrer = '' } = useParams<{ referrer: string }>()
81+
const location = useLocation()
8182
const isDesktop = useDesktopMediaQuery()
8283
const { t } = useTranslation()
84+
const { isInitialized: isAnalyticsInitialized, page } = useAnalytics()
8385

8486
const [referrerProfile, referrerProfileStatus] = useAsyncMemo(async () => {
8587
if (!referrer) return null
@@ -88,6 +90,15 @@ const InvitePage = memo(() => {
8890

8991
useDocumentMeta(t('page_invite.social.title'), t('page_invite.social.description'))
9092

93+
// Invite is a Layout-less route, so it never gets the automatic page() that
94+
// Layout fires for wrapped routes. Fire it here to restore the invite
95+
// pageview lost in the Gatsby→SPA migration (the warehouse's invite funnel
96+
// reads FCT_PAGEVIEWS for /invite paths). Mirrors Layout.tsx exactly.
97+
useEffect(() => {
98+
if (!isAnalyticsInitialized) return
99+
page(location.pathname)
100+
}, [isAnalyticsInitialized, location.pathname, page])
101+
91102
return (
92103
<>
93104
<InviteHero

0 commit comments

Comments
 (0)