react-fathom 0.1.0 → 0.1.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (49) hide show
  1. package/README.md +215 -52
  2. package/dist/cjs/index.cjs +3 -4
  3. package/dist/cjs/index.cjs.map +1 -1
  4. package/dist/cjs/next/index.cjs +186 -725
  5. package/dist/cjs/next/index.cjs.map +1 -1
  6. package/dist/es/index.js +3 -4
  7. package/dist/es/index.js.map +1 -1
  8. package/dist/es/next/index.js +186 -724
  9. package/dist/es/next/index.js.map +1 -1
  10. package/dist/react-fathom.js +3 -4
  11. package/dist/react-fathom.js.map +1 -1
  12. package/dist/react-fathom.min.js +2 -2
  13. package/dist/react-fathom.min.js.map +1 -1
  14. package/package.json +15 -1
  15. package/src/FathomProvider.tsx +0 -1
  16. package/src/next/NextFathomTrackViewApp.test.tsx +265 -0
  17. package/src/next/NextFathomTrackViewApp.tsx +78 -0
  18. package/src/next/NextFathomTrackViewPages.test.tsx +222 -0
  19. package/src/next/NextFathomTrackViewPages.tsx +83 -0
  20. package/src/next/compositions/withAppRouter.test.tsx +31 -10
  21. package/src/next/compositions/withAppRouter.tsx +10 -3
  22. package/src/next/compositions/withPagesRouter.test.tsx +31 -10
  23. package/src/next/compositions/withPagesRouter.tsx +10 -3
  24. package/src/next/index.ts +3 -3
  25. package/src/next/types.ts +0 -7
  26. package/src/types.ts +0 -1
  27. package/types/FathomProvider.d.ts.map +1 -1
  28. package/types/next/NextFathomProviderApp.d.ts.map +1 -1
  29. package/types/next/NextFathomProviderPages.d.ts.map +1 -1
  30. package/types/next/NextFathomTrackViewApp.d.ts +34 -0
  31. package/types/next/NextFathomTrackViewApp.d.ts.map +1 -0
  32. package/types/next/NextFathomTrackViewPages.d.ts +30 -0
  33. package/types/next/NextFathomTrackViewPages.d.ts.map +1 -0
  34. package/types/next/compositions/withAppRouter.d.ts +1 -0
  35. package/types/next/compositions/withAppRouter.d.ts.map +1 -1
  36. package/types/next/compositions/withPagesRouter.d.ts +1 -0
  37. package/types/next/compositions/withPagesRouter.d.ts.map +1 -1
  38. package/types/next/index.d.ts +2 -3
  39. package/types/next/index.d.ts.map +1 -1
  40. package/types/next/types.d.ts +0 -6
  41. package/types/next/types.d.ts.map +1 -1
  42. package/types/types.d.ts +0 -1
  43. package/types/types.d.ts.map +1 -1
  44. package/src/next/NextFathomProvider.test.tsx +0 -131
  45. package/src/next/NextFathomProvider.tsx +0 -62
  46. package/src/next/NextFathomProviderApp.test.tsx +0 -308
  47. package/src/next/NextFathomProviderApp.tsx +0 -106
  48. package/src/next/NextFathomProviderPages.test.tsx +0 -330
  49. package/src/next/NextFathomProviderPages.tsx +0 -112
@@ -1,330 +0,0 @@
1
- import React from 'react'
2
-
3
- import { beforeEach, describe, expect, it, vi } from 'vitest'
4
-
5
- import { renderHook, waitFor } from '@testing-library/react'
6
-
7
- import NextFathomProviderPages from './NextFathomProviderPages'
8
- import { useFathom } from '../hooks/useFathom'
9
-
10
- // Mock Next.js Pages Router hook
11
- const mockRouter = {
12
- pathname: '/test-page',
13
- query: { foo: 'bar' },
14
- asPath: '/test-page?foo=bar',
15
- isReady: true,
16
- events: {
17
- on: vi.fn(),
18
- off: vi.fn(),
19
- },
20
- }
21
-
22
- vi.mock('next/router', () => ({
23
- useRouter: vi.fn(() => mockRouter),
24
- }))
25
-
26
- // Mock fathom-client
27
- vi.mock('fathom-client', async () => {
28
- const mockFathomClient = {
29
- trackEvent: vi.fn(),
30
- trackPageview: vi.fn(),
31
- trackGoal: vi.fn(),
32
- load: vi.fn(),
33
- setSite: vi.fn(),
34
- blockTrackingForMe: vi.fn(),
35
- enableTrackingForMe: vi.fn(),
36
- isTrackingEnabled: vi.fn(() => true),
37
- }
38
-
39
- return {
40
- default: mockFathomClient,
41
- }
42
- })
43
-
44
- describe('NextFathomProviderPages', () => {
45
- const mockClient = {
46
- trackEvent: vi.fn(),
47
- trackPageview: vi.fn(),
48
- trackGoal: vi.fn(),
49
- load: vi.fn(),
50
- setSite: vi.fn(),
51
- blockTrackingForMe: vi.fn(),
52
- enableTrackingForMe: vi.fn(),
53
- isTrackingEnabled: vi.fn(() => true),
54
- }
55
-
56
- beforeEach(() => {
57
- vi.clearAllMocks()
58
- mockRouter.isReady = true
59
- mockRouter.events.on = vi.fn()
60
- mockRouter.events.off = vi.fn()
61
- // Reset window.location
62
- Object.defineProperty(window, 'location', {
63
- value: {
64
- origin: 'https://example.com',
65
- href: 'https://example.com/test-page?foo=bar',
66
- },
67
- writable: true,
68
- })
69
- })
70
-
71
- it('should load Fathom on mount when siteId is provided', async () => {
72
- const loadSpy = vi.fn()
73
- const client = { ...mockClient, load: loadSpy }
74
-
75
- const wrapper = ({ children }: { children: React.ReactNode }) => (
76
- <NextFathomProviderPages client={client} siteId="TEST_SITE_ID">
77
- {children}
78
- </NextFathomProviderPages>
79
- )
80
-
81
- renderHook(() => useFathom(), { wrapper })
82
-
83
- await waitFor(() => {
84
- expect(loadSpy).toHaveBeenCalledWith('TEST_SITE_ID', undefined)
85
- })
86
- })
87
-
88
- it('should load Fathom with clientOptions', async () => {
89
- const loadSpy = vi.fn()
90
- const clientOptions = { honorDNT: true }
91
- const client = { ...mockClient, load: loadSpy }
92
-
93
- const wrapper = ({ children }: { children: React.ReactNode }) => (
94
- <NextFathomProviderPages
95
- client={client}
96
- siteId="TEST_SITE_ID"
97
- clientOptions={clientOptions}
98
- >
99
- {children}
100
- </NextFathomProviderPages>
101
- )
102
-
103
- renderHook(() => useFathom(), { wrapper })
104
-
105
- await waitFor(() => {
106
- expect(loadSpy).toHaveBeenCalledWith('TEST_SITE_ID', clientOptions)
107
- })
108
- })
109
-
110
- it('should track initial pageview when router is ready', async () => {
111
- const trackPageviewSpy = vi.fn()
112
- const client = { ...mockClient, trackPageview: trackPageviewSpy }
113
-
114
- const wrapper = ({ children }: { children: React.ReactNode }) => (
115
- <NextFathomProviderPages client={client} siteId="TEST_SITE_ID">
116
- {children}
117
- </NextFathomProviderPages>
118
- )
119
-
120
- renderHook(() => useFathom(), { wrapper })
121
-
122
- await waitFor(() => {
123
- expect(trackPageviewSpy).toHaveBeenCalled()
124
- })
125
-
126
- expect(trackPageviewSpy).toHaveBeenCalledWith({
127
- url: 'https://example.com/test-page?foo=bar',
128
- })
129
- })
130
-
131
- it('should not track initial pageview when router is not ready', async () => {
132
- mockRouter.isReady = false
133
- const trackPageviewSpy = vi.fn()
134
- const client = { ...mockClient, trackPageview: trackPageviewSpy }
135
-
136
- const wrapper = ({ children }: { children: React.ReactNode }) => (
137
- <NextFathomProviderPages client={client} siteId="TEST_SITE_ID">
138
- {children}
139
- </NextFathomProviderPages>
140
- )
141
-
142
- renderHook(() => useFathom(), { wrapper })
143
-
144
- // Wait a bit to ensure no tracking happens
145
- await new Promise((resolve) => setTimeout(resolve, 100))
146
-
147
- expect(trackPageviewSpy).not.toHaveBeenCalled()
148
- })
149
-
150
- it('should register route change listener', async () => {
151
- const wrapper = ({ children }: { children: React.ReactNode }) => (
152
- <NextFathomProviderPages client={mockClient} siteId="TEST_SITE_ID">
153
- {children}
154
- </NextFathomProviderPages>
155
- )
156
-
157
- renderHook(() => useFathom(), { wrapper })
158
-
159
- await waitFor(() => {
160
- expect(mockRouter.events.on).toHaveBeenCalledWith(
161
- 'routeChangeComplete',
162
- expect.any(Function),
163
- )
164
- })
165
- })
166
-
167
- it('should unregister route change listener on unmount', async () => {
168
- const wrapper = ({ children }: { children: React.ReactNode }) => (
169
- <NextFathomProviderPages client={mockClient} siteId="TEST_SITE_ID">
170
- {children}
171
- </NextFathomProviderPages>
172
- )
173
-
174
- const { unmount } = renderHook(() => useFathom(), { wrapper })
175
-
176
- await waitFor(() => {
177
- expect(mockRouter.events.on).toHaveBeenCalled()
178
- })
179
-
180
- unmount()
181
-
182
- expect(mockRouter.events.off).toHaveBeenCalledWith(
183
- 'routeChangeComplete',
184
- expect.any(Function),
185
- )
186
- })
187
-
188
- it('should track pageview on route change', async () => {
189
- const trackPageviewSpy = vi.fn()
190
- const client = { ...mockClient, trackPageview: trackPageviewSpy }
191
-
192
- const wrapper = ({ children }: { children: React.ReactNode }) => (
193
- <NextFathomProviderPages client={client} siteId="TEST_SITE_ID">
194
- {children}
195
- </NextFathomProviderPages>
196
- )
197
-
198
- renderHook(() => useFathom(), { wrapper })
199
-
200
- await waitFor(() => {
201
- expect(mockRouter.events.on).toHaveBeenCalled()
202
- })
203
-
204
- // Get the route change handler
205
- const routeChangeHandler = mockRouter.events.on.mock.calls[0][1]
206
- routeChangeHandler('/new-page')
207
-
208
- expect(trackPageviewSpy).toHaveBeenCalledWith({
209
- url: 'https://example.com/new-page',
210
- })
211
- })
212
-
213
- it('should not track pageview when disableAutoTrack is true', async () => {
214
- const trackPageviewSpy = vi.fn()
215
- const client = { ...mockClient, trackPageview: trackPageviewSpy }
216
-
217
- const wrapper = ({ children }: { children: React.ReactNode }) => (
218
- <NextFathomProviderPages
219
- client={client}
220
- siteId="TEST_SITE_ID"
221
- disableAutoTrack
222
- >
223
- {children}
224
- </NextFathomProviderPages>
225
- )
226
-
227
- renderHook(() => useFathom(), { wrapper })
228
-
229
- // Wait a bit to ensure no tracking happens
230
- await new Promise((resolve) => setTimeout(resolve, 100))
231
-
232
- expect(trackPageviewSpy).not.toHaveBeenCalled()
233
- expect(mockRouter.events.on).not.toHaveBeenCalled()
234
- })
235
-
236
- it('should use provided client', () => {
237
- const wrapper = ({ children }: { children: React.ReactNode }) => (
238
- <NextFathomProviderPages client={mockClient} siteId="TEST_SITE_ID">
239
- {children}
240
- </NextFathomProviderPages>
241
- )
242
-
243
- const { result } = renderHook(() => useFathom(), { wrapper })
244
-
245
- expect(result.current.client).toBe(mockClient)
246
- })
247
-
248
- it('should merge defaultPageviewOptions', async () => {
249
- const trackPageviewSpy = vi.fn()
250
- const client = { ...mockClient, trackPageview: trackPageviewSpy }
251
-
252
- const wrapper = ({ children }: { children: React.ReactNode }) => (
253
- <NextFathomProviderPages
254
- client={client}
255
- siteId="TEST_SITE_ID"
256
- defaultPageviewOptions={{ referrer: 'https://example.com' }}
257
- >
258
- {children}
259
- </NextFathomProviderPages>
260
- )
261
-
262
- renderHook(() => useFathom(), { wrapper })
263
-
264
- await waitFor(() => {
265
- expect(trackPageviewSpy).toHaveBeenCalled()
266
- })
267
-
268
- expect(trackPageviewSpy).toHaveBeenCalledWith({
269
- referrer: 'https://example.com',
270
- url: 'https://example.com/test-page?foo=bar',
271
- })
272
- })
273
-
274
- it('should support deprecated trackDefaultOptions', async () => {
275
- const trackPageviewSpy = vi.fn()
276
- const client = { ...mockClient, trackPageview: trackPageviewSpy }
277
-
278
- const wrapper = ({ children }: { children: React.ReactNode }) => (
279
- <NextFathomProviderPages
280
- client={client}
281
- siteId="TEST_SITE_ID"
282
- trackDefaultOptions={{ referrer: 'https://example.com' }}
283
- >
284
- {children}
285
- </NextFathomProviderPages>
286
- )
287
-
288
- renderHook(() => useFathom(), { wrapper })
289
-
290
- await waitFor(() => {
291
- expect(trackPageviewSpy).toHaveBeenCalled()
292
- })
293
-
294
- expect(trackPageviewSpy).toHaveBeenCalledWith({
295
- referrer: 'https://example.com',
296
- url: 'https://example.com/test-page?foo=bar',
297
- })
298
- })
299
-
300
- it('should prioritize defaultPageviewOptions over trackDefaultOptions', async () => {
301
- const trackPageviewSpy = vi.fn()
302
- const client = { ...mockClient, trackPageview: trackPageviewSpy }
303
-
304
- const wrapper = ({ children }: { children: React.ReactNode }) => (
305
- <NextFathomProviderPages
306
- client={client}
307
- siteId="TEST_SITE_ID"
308
- trackDefaultOptions={{ referrer: 'old' }}
309
- defaultPageviewOptions={{ referrer: 'new' }}
310
- >
311
- {children}
312
- </NextFathomProviderPages>
313
- )
314
-
315
- renderHook(() => useFathom(), { wrapper })
316
-
317
- await waitFor(() => {
318
- expect(trackPageviewSpy).toHaveBeenCalled()
319
- })
320
-
321
- expect(trackPageviewSpy).toHaveBeenCalledWith({
322
- referrer: 'new',
323
- url: 'https://example.com/test-page?foo=bar',
324
- })
325
- })
326
-
327
- it('should have displayName', () => {
328
- expect(NextFathomProviderPages.displayName).toBe('NextFathomProviderPages')
329
- })
330
- })
@@ -1,112 +0,0 @@
1
- import React, { useCallback, useEffect, useRef, useMemo } from 'react'
2
-
3
- import * as Fathom from 'fathom-client'
4
- import type { PageViewOptions } from 'fathom-client'
5
- import { useRouter } from 'next/router'
6
-
7
- import { FathomProvider } from '../FathomProvider'
8
- import type { NextFathomProviderProps } from './types'
9
- import { useFathom } from '../hooks/useFathom'
10
-
11
- const NextFathomProviderPages: React.FC<NextFathomProviderProps> = ({
12
- children,
13
- client: providedClient,
14
- clientOptions,
15
- disableAutoTrack = false,
16
- siteId,
17
- trackDefaultOptions,
18
- defaultPageviewOptions: providedDefaultPageviewOptions,
19
- }) => {
20
- const router = useRouter()
21
- const hasTrackedInitialPageview = useRef(false)
22
- const parentContext = useFathom()
23
-
24
- // Use provided client or fall back to parent client or default Fathom
25
- const client = useMemo(
26
- () => providedClient ?? parentContext.client ?? Fathom,
27
- [providedClient, parentContext.client],
28
- )
29
-
30
- // Support both deprecated trackDefaultOptions and new defaultPageviewOptions
31
- // Priority: providedDefaultPageviewOptions > trackDefaultOptions > parent defaultPageviewOptions
32
- const defaultPageviewOptions = useMemo(
33
- () =>
34
- providedDefaultPageviewOptions ??
35
- trackDefaultOptions ??
36
- parentContext.defaultPageviewOptions,
37
- [
38
- providedDefaultPageviewOptions,
39
- trackDefaultOptions,
40
- parentContext.defaultPageviewOptions,
41
- ],
42
- )
43
-
44
- const trackPageview = useCallback(
45
- (options?: PageViewOptions) => {
46
- if (siteId !== undefined && client !== undefined) {
47
- client.trackPageview({
48
- ...defaultPageviewOptions,
49
- ...options,
50
- })
51
- }
52
- },
53
- [client, siteId, defaultPageviewOptions],
54
- )
55
-
56
- // Initialize Fathom on mount
57
- useEffect(() => {
58
- if (siteId !== undefined && client !== undefined) {
59
- client.load(siteId, clientOptions)
60
- }
61
- }, [client, clientOptions, siteId])
62
-
63
- // Track pageviews on route changes
64
- useEffect(() => {
65
- if (siteId === undefined || disableAutoTrack) {
66
- return
67
- }
68
-
69
- const handleRouteChangeComplete = (url: string): void => {
70
- trackPageview({
71
- url: window.location.origin + url,
72
- })
73
- }
74
-
75
- router.events.on('routeChangeComplete', handleRouteChangeComplete)
76
-
77
- return () => {
78
- router.events.off('routeChangeComplete', handleRouteChangeComplete)
79
- }
80
- }, [router.events, siteId, disableAutoTrack, trackPageview])
81
-
82
- // Track initial pageview (routeChangeComplete doesn't fire on initial load)
83
- useEffect(() => {
84
- if (
85
- siteId !== undefined &&
86
- !disableAutoTrack &&
87
- router.isReady &&
88
- !hasTrackedInitialPageview.current
89
- ) {
90
- hasTrackedInitialPageview.current = true
91
- trackPageview({
92
- url: window.location.href,
93
- })
94
- }
95
- }, [siteId, disableAutoTrack, router.isReady, trackPageview])
96
-
97
- return (
98
- <FathomProvider
99
- client={client}
100
- clientOptions={clientOptions}
101
- siteId={siteId}
102
- defaultPageviewOptions={defaultPageviewOptions}
103
- >
104
- {children}
105
- </FathomProvider>
106
- )
107
- }
108
-
109
- NextFathomProviderPages.displayName = 'NextFathomProviderPages'
110
-
111
- export default NextFathomProviderPages
112
- export { NextFathomProviderPages }