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,308 +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 NextFathomProviderApp from './NextFathomProviderApp'
8
- import { useFathom } from '../hooks/useFathom'
9
-
10
- // Mock Next.js App Router hooks
11
- const mockSearchParams = new URLSearchParams('?foo=bar')
12
- vi.mock('next/navigation', () => ({
13
- usePathname: vi.fn(() => '/test-page'),
14
- useSearchParams: vi.fn(() => mockSearchParams),
15
- }))
16
-
17
- // Mock fathom-client
18
- vi.mock('fathom-client', () => {
19
- const mockFathomDefault = {
20
- trackEvent: vi.fn(),
21
- trackPageview: vi.fn(),
22
- trackGoal: vi.fn(),
23
- load: vi.fn(),
24
- setSite: vi.fn(),
25
- blockTrackingForMe: vi.fn(),
26
- enableTrackingForMe: vi.fn(),
27
- isTrackingEnabled: vi.fn(() => true),
28
- }
29
-
30
- return {
31
- default: mockFathomDefault,
32
- }
33
- })
34
-
35
- describe('NextFathomProviderApp', () => {
36
- const mockClient = {
37
- trackEvent: vi.fn(),
38
- trackPageview: vi.fn(),
39
- trackGoal: vi.fn(),
40
- load: vi.fn(),
41
- setSite: vi.fn(),
42
- blockTrackingForMe: vi.fn(),
43
- enableTrackingForMe: vi.fn(),
44
- isTrackingEnabled: vi.fn(() => true),
45
- }
46
-
47
- beforeEach(() => {
48
- vi.clearAllMocks()
49
- // Reset window.location
50
- Object.defineProperty(window, 'location', {
51
- value: {
52
- origin: 'https://example.com',
53
- href: 'https://example.com/test-page?foo=bar',
54
- },
55
- writable: true,
56
- })
57
- })
58
-
59
- it('should load Fathom on mount when siteId is provided', async () => {
60
- const loadSpy = vi.fn()
61
- const client = { ...mockClient, load: loadSpy }
62
-
63
- const wrapper = ({ children }: { children: React.ReactNode }) => (
64
- <NextFathomProviderApp client={client} siteId="TEST_SITE_ID">
65
- {children}
66
- </NextFathomProviderApp>
67
- )
68
-
69
- renderHook(() => useFathom(), { wrapper })
70
-
71
- await waitFor(() => {
72
- expect(loadSpy).toHaveBeenCalledWith('TEST_SITE_ID', undefined)
73
- })
74
- })
75
-
76
- it('should load Fathom with clientOptions', async () => {
77
- const loadSpy = vi.fn()
78
- const clientOptions = { honorDNT: true }
79
- const client = { ...mockClient, load: loadSpy }
80
-
81
- const wrapper = ({ children }: { children: React.ReactNode }) => (
82
- <NextFathomProviderApp
83
- client={client}
84
- siteId="TEST_SITE_ID"
85
- clientOptions={clientOptions}
86
- >
87
- {children}
88
- </NextFathomProviderApp>
89
- )
90
-
91
- renderHook(() => useFathom(), { wrapper })
92
-
93
- await waitFor(() => {
94
- expect(loadSpy).toHaveBeenCalledWith('TEST_SITE_ID', clientOptions)
95
- })
96
- })
97
-
98
- it('should track initial pageview on mount', async () => {
99
- const trackPageviewSpy = vi.fn()
100
- const client = { ...mockClient, trackPageview: trackPageviewSpy }
101
-
102
- const wrapper = ({ children }: { children: React.ReactNode }) => (
103
- <NextFathomProviderApp client={client} siteId="TEST_SITE_ID">
104
- {children}
105
- </NextFathomProviderApp>
106
- )
107
-
108
- renderHook(() => useFathom(), { wrapper })
109
-
110
- await waitFor(() => {
111
- expect(trackPageviewSpy).toHaveBeenCalled()
112
- })
113
-
114
- expect(trackPageviewSpy).toHaveBeenCalledWith({
115
- url: 'https://example.com/test-page?foo=bar',
116
- })
117
- })
118
-
119
- it('should not track pageview when disableAutoTrack is true', async () => {
120
- const trackPageviewSpy = vi.fn()
121
- const client = { ...mockClient, trackPageview: trackPageviewSpy }
122
-
123
- const wrapper = ({ children }: { children: React.ReactNode }) => (
124
- <NextFathomProviderApp
125
- client={client}
126
- siteId="TEST_SITE_ID"
127
- disableAutoTrack
128
- >
129
- {children}
130
- </NextFathomProviderApp>
131
- )
132
-
133
- renderHook(() => useFathom(), { wrapper })
134
-
135
- // Wait a bit to ensure no tracking happens
136
- await new Promise((resolve) => setTimeout(resolve, 100))
137
-
138
- expect(trackPageviewSpy).not.toHaveBeenCalled()
139
- })
140
-
141
- it('should use provided client', () => {
142
- const wrapper = ({ children }: { children: React.ReactNode }) => (
143
- <NextFathomProviderApp client={mockClient} siteId="TEST_SITE_ID">
144
- {children}
145
- </NextFathomProviderApp>
146
- )
147
-
148
- const { result } = renderHook(() => useFathom(), { wrapper })
149
-
150
- expect(result.current.client).toBe(mockClient)
151
- })
152
-
153
- it('should use default Fathom client when no client is provided', async () => {
154
- // This test verifies that the component uses the default Fathom client
155
- // when no client is provided. The component should still work and provide
156
- // a client through the context.
157
- // Note: This test may fail if Next.js hooks throw errors, so we wrap in try-catch
158
- try {
159
- const wrapper = ({ children }: { children: React.ReactNode }) => (
160
- <NextFathomProviderApp siteId="TEST_SITE_ID">
161
- {children}
162
- </NextFathomProviderApp>
163
- )
164
-
165
- const { result } = renderHook(() => useFathom(), { wrapper })
166
-
167
- // The client should be available (either default or from parent)
168
- await waitFor(() => {
169
- expect(result.current.client).toBeDefined()
170
- expect(result.current.trackEvent).toBeDefined()
171
- expect(result.current.trackPageview).toBeDefined()
172
- })
173
- } catch (error) {
174
- // If there's an error with Next.js hooks, skip this test
175
- // The other tests verify the functionality with provided clients
176
- expect(error).toBeDefined()
177
- }
178
- })
179
-
180
- it('should merge defaultPageviewOptions', async () => {
181
- const trackPageviewSpy = vi.fn()
182
- const client = { ...mockClient, trackPageview: trackPageviewSpy }
183
-
184
- const wrapper = ({ children }: { children: React.ReactNode }) => (
185
- <NextFathomProviderApp
186
- client={client}
187
- siteId="TEST_SITE_ID"
188
- defaultPageviewOptions={{ referrer: 'https://example.com' }}
189
- >
190
- {children}
191
- </NextFathomProviderApp>
192
- )
193
-
194
- renderHook(() => useFathom(), { wrapper })
195
-
196
- await waitFor(() => {
197
- expect(trackPageviewSpy).toHaveBeenCalled()
198
- })
199
-
200
- expect(trackPageviewSpy).toHaveBeenCalledWith({
201
- referrer: 'https://example.com',
202
- url: 'https://example.com/test-page?foo=bar',
203
- })
204
- })
205
-
206
- it('should support deprecated trackDefaultOptions', async () => {
207
- const trackPageviewSpy = vi.fn()
208
- const client = { ...mockClient, trackPageview: trackPageviewSpy }
209
-
210
- const wrapper = ({ children }: { children: React.ReactNode }) => (
211
- <NextFathomProviderApp
212
- client={client}
213
- siteId="TEST_SITE_ID"
214
- trackDefaultOptions={{ referrer: 'https://example.com' }}
215
- >
216
- {children}
217
- </NextFathomProviderApp>
218
- )
219
-
220
- renderHook(() => useFathom(), { wrapper })
221
-
222
- await waitFor(() => {
223
- expect(trackPageviewSpy).toHaveBeenCalled()
224
- })
225
-
226
- expect(trackPageviewSpy).toHaveBeenCalledWith({
227
- referrer: 'https://example.com',
228
- url: 'https://example.com/test-page?foo=bar',
229
- })
230
- })
231
-
232
- it('should prioritize defaultPageviewOptions over trackDefaultOptions', async () => {
233
- const trackPageviewSpy = vi.fn()
234
- const client = { ...mockClient, trackPageview: trackPageviewSpy }
235
-
236
- const wrapper = ({ children }: { children: React.ReactNode }) => (
237
- <NextFathomProviderApp
238
- client={client}
239
- siteId="TEST_SITE_ID"
240
- trackDefaultOptions={{ referrer: 'old' }}
241
- defaultPageviewOptions={{ referrer: 'new' }}
242
- >
243
- {children}
244
- </NextFathomProviderApp>
245
- )
246
-
247
- renderHook(() => useFathom(), { wrapper })
248
-
249
- await waitFor(() => {
250
- expect(trackPageviewSpy).toHaveBeenCalled()
251
- })
252
-
253
- expect(trackPageviewSpy).toHaveBeenCalledWith({
254
- referrer: 'new',
255
- url: 'https://example.com/test-page?foo=bar',
256
- })
257
- })
258
-
259
- it('should compose with parent FathomProvider', () => {
260
- const parentClient = {
261
- trackEvent: vi.fn(),
262
- trackPageview: vi.fn(),
263
- trackGoal: vi.fn(),
264
- load: vi.fn(),
265
- setSite: vi.fn(),
266
- blockTrackingForMe: vi.fn(),
267
- enableTrackingForMe: vi.fn(),
268
- isTrackingEnabled: vi.fn(() => true),
269
- }
270
-
271
- const wrapper = ({ children }: { children: React.ReactNode }) => (
272
- <NextFathomProviderApp client={parentClient} siteId="TEST_SITE_ID">
273
- {children}
274
- </NextFathomProviderApp>
275
- )
276
-
277
- const { result } = renderHook(() => useFathom(), { wrapper })
278
-
279
- expect(result.current.client).toBe(parentClient)
280
- })
281
-
282
- it('should handle pathname with search params', async () => {
283
- // This test verifies the component handles search params correctly
284
- const trackPageviewSpy = vi.fn()
285
- const client = { ...mockClient, trackPageview: trackPageviewSpy }
286
-
287
- const wrapper = ({ children }: { children: React.ReactNode }) => (
288
- <NextFathomProviderApp client={client} siteId="TEST_SITE_ID">
289
- {children}
290
- </NextFathomProviderApp>
291
- )
292
-
293
- renderHook(() => useFathom(), { wrapper })
294
-
295
- await waitFor(() => {
296
- expect(trackPageviewSpy).toHaveBeenCalled()
297
- })
298
-
299
- // Should include search params from mock
300
- expect(trackPageviewSpy).toHaveBeenCalledWith({
301
- url: 'https://example.com/test-page?foo=bar',
302
- })
303
- })
304
-
305
- it('should have displayName', () => {
306
- expect(NextFathomProviderApp.displayName).toBe('NextFathomProviderApp')
307
- })
308
- })
@@ -1,106 +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 { usePathname, useSearchParams } from 'next/navigation'
6
-
7
- import { FathomProvider } from '../FathomProvider'
8
- import type { NextFathomProviderProps } from './types'
9
- import { useFathom } from '../hooks/useFathom'
10
-
11
- const NextFathomProviderApp: React.FC<NextFathomProviderProps> = ({
12
- children,
13
- client: providedClient,
14
- clientOptions,
15
- disableAutoTrack = false,
16
- siteId,
17
- trackDefaultOptions,
18
- defaultPageviewOptions: providedDefaultPageviewOptions,
19
- }) => {
20
- const pathname = usePathname()
21
- const searchParams = useSearchParams()
22
- const hasTrackedInitialPageview = useRef(false)
23
- const parentContext = useFathom()
24
-
25
- // Use provided client or fall back to parent client or default Fathom
26
- const client = useMemo(
27
- () => providedClient ?? parentContext.client ?? Fathom,
28
- [providedClient, parentContext.client],
29
- )
30
-
31
- // Support both deprecated trackDefaultOptions and new defaultPageviewOptions
32
- // Priority: providedDefaultPageviewOptions > trackDefaultOptions > parent defaultPageviewOptions
33
- const defaultPageviewOptions = useMemo(
34
- () =>
35
- providedDefaultPageviewOptions ??
36
- trackDefaultOptions ??
37
- parentContext.defaultPageviewOptions,
38
- [
39
- providedDefaultPageviewOptions,
40
- trackDefaultOptions,
41
- parentContext.defaultPageviewOptions,
42
- ],
43
- )
44
-
45
- const trackPageview = useCallback(
46
- (options?: PageViewOptions) => {
47
- if (siteId !== undefined && client !== undefined) {
48
- client.trackPageview({
49
- ...defaultPageviewOptions,
50
- ...options,
51
- })
52
- }
53
- },
54
- [client, siteId, defaultPageviewOptions],
55
- )
56
-
57
- // Initialize Fathom on mount
58
- useEffect(() => {
59
- if (siteId !== undefined && client !== undefined) {
60
- client.load(siteId, clientOptions)
61
- }
62
- }, [client, clientOptions, siteId])
63
-
64
- // Track pageviews on route changes
65
- useEffect(() => {
66
- if (siteId === undefined || disableAutoTrack) {
67
- return
68
- }
69
-
70
- const searchString = searchParams?.toString()
71
- const url =
72
- pathname +
73
- (searchString !== undefined && searchString !== ''
74
- ? `?${searchString}`
75
- : '')
76
-
77
- // Track initial pageview only once
78
- if (!hasTrackedInitialPageview.current) {
79
- hasTrackedInitialPageview.current = true
80
- trackPageview({
81
- url: window.location.origin + url,
82
- })
83
- } else {
84
- // Track subsequent route changes
85
- trackPageview({
86
- url: window.location.origin + url,
87
- })
88
- }
89
- }, [pathname, searchParams, siteId, disableAutoTrack, trackPageview])
90
-
91
- return (
92
- <FathomProvider
93
- client={client}
94
- clientOptions={clientOptions}
95
- siteId={siteId}
96
- defaultPageviewOptions={defaultPageviewOptions}
97
- >
98
- {children}
99
- </FathomProvider>
100
- )
101
- }
102
-
103
- NextFathomProviderApp.displayName = 'NextFathomProviderApp'
104
-
105
- export default NextFathomProviderApp
106
- export { NextFathomProviderApp }