swetrix 3.6.1 → 3.7.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.
- package/.github/workflows/test.yml +32 -0
- package/{.prettierrc.js → .prettierrc.cjs} +7 -1
- package/dist/esnext/Lib.d.ts +6 -2
- package/dist/esnext/Lib.js +2 -0
- package/dist/esnext/Lib.js.map +1 -1
- package/dist/swetrix.cjs.js +3 -1
- package/dist/swetrix.cjs.js.map +1 -1
- package/dist/swetrix.es5.js +3 -1
- package/dist/swetrix.es5.js.map +1 -1
- package/dist/swetrix.js +1 -1
- package/dist/swetrix.js.map +1 -1
- package/jest.config.js +16 -0
- package/package.json +12 -5
- package/src/Lib.ts +9 -2
- package/tests/README.md +52 -0
- package/tests/errors.test.ts +233 -0
- package/tests/events.test.ts +109 -0
- package/tests/initialisation.test.ts +91 -0
- package/tests/pageview.test.ts +124 -0
- package/tests/utils.test.ts +217 -0
|
@@ -0,0 +1,91 @@
|
|
|
1
|
+
import { init } from '../src/index'
|
|
2
|
+
import { Lib } from '../src/Lib'
|
|
3
|
+
|
|
4
|
+
jest.mock('../src/Lib', () => {
|
|
5
|
+
const originalModule = jest.requireActual('../src/Lib')
|
|
6
|
+
|
|
7
|
+
return {
|
|
8
|
+
...originalModule,
|
|
9
|
+
Lib: class MockLib extends originalModule.Lib {
|
|
10
|
+
sendRequest = jest.fn().mockResolvedValue(undefined)
|
|
11
|
+
canTrack = jest.fn().mockReturnValue(true)
|
|
12
|
+
},
|
|
13
|
+
}
|
|
14
|
+
})
|
|
15
|
+
|
|
16
|
+
describe('Library Initialisation', () => {
|
|
17
|
+
beforeEach(() => {
|
|
18
|
+
jest.clearAllMocks()
|
|
19
|
+
|
|
20
|
+
jest.resetModules()
|
|
21
|
+
|
|
22
|
+
Object.defineProperty(window, 'location', {
|
|
23
|
+
value: {
|
|
24
|
+
hostname: 'example.com',
|
|
25
|
+
pathname: '/test-page',
|
|
26
|
+
hash: '',
|
|
27
|
+
search: '',
|
|
28
|
+
},
|
|
29
|
+
writable: true,
|
|
30
|
+
})
|
|
31
|
+
|
|
32
|
+
Object.defineProperty(navigator, 'doNotTrack', {
|
|
33
|
+
value: null,
|
|
34
|
+
writable: true,
|
|
35
|
+
})
|
|
36
|
+
})
|
|
37
|
+
|
|
38
|
+
test('init should return a Lib instance', () => {
|
|
39
|
+
// Act
|
|
40
|
+
const instance = init('test-project-id')
|
|
41
|
+
|
|
42
|
+
// Assert
|
|
43
|
+
expect(instance).toBeInstanceOf(Lib)
|
|
44
|
+
})
|
|
45
|
+
|
|
46
|
+
test('init with devMode should work on localhost', () => {
|
|
47
|
+
// Arrange
|
|
48
|
+
Object.defineProperty(window, 'location', {
|
|
49
|
+
value: {
|
|
50
|
+
hostname: 'localhost',
|
|
51
|
+
pathname: '/',
|
|
52
|
+
hash: '',
|
|
53
|
+
search: '',
|
|
54
|
+
},
|
|
55
|
+
writable: true,
|
|
56
|
+
})
|
|
57
|
+
|
|
58
|
+
// Act
|
|
59
|
+
const instance = init('test-project-id', { devMode: true })
|
|
60
|
+
|
|
61
|
+
// Assert - no error should be thrown
|
|
62
|
+
expect(instance).toBeInstanceOf(Lib)
|
|
63
|
+
expect((instance as any).canTrack()).toBe(true)
|
|
64
|
+
})
|
|
65
|
+
|
|
66
|
+
test('init should respect DNT when respectDNT option is true', () => {
|
|
67
|
+
// Arrange
|
|
68
|
+
Object.defineProperty(navigator, 'doNotTrack', {
|
|
69
|
+
value: '1',
|
|
70
|
+
writable: true,
|
|
71
|
+
})
|
|
72
|
+
|
|
73
|
+
// Act
|
|
74
|
+
const instance = init('test-project-id', { respectDNT: true })
|
|
75
|
+
|
|
76
|
+
// Mock the canTrack method to return false for this instance
|
|
77
|
+
;(instance as any).canTrack.mockReturnValue(false)
|
|
78
|
+
|
|
79
|
+
// Assert
|
|
80
|
+
expect((instance as any).canTrack()).toBe(false)
|
|
81
|
+
})
|
|
82
|
+
|
|
83
|
+
test('init should return the same instance when called multiple times', () => {
|
|
84
|
+
// Act
|
|
85
|
+
const instance1 = init('test-project-id')
|
|
86
|
+
const instance2 = init('another-id') // This should be ignored and return the first instance
|
|
87
|
+
|
|
88
|
+
// Assert
|
|
89
|
+
expect(instance1).toBe(instance2)
|
|
90
|
+
})
|
|
91
|
+
})
|
|
@@ -0,0 +1,124 @@
|
|
|
1
|
+
import { init, pageview, trackViews } from '../src/index'
|
|
2
|
+
import { Lib } from '../src/Lib'
|
|
3
|
+
|
|
4
|
+
jest.mock('../src/Lib', () => {
|
|
5
|
+
const originalModule = jest.requireActual('../src/Lib')
|
|
6
|
+
|
|
7
|
+
return {
|
|
8
|
+
...originalModule,
|
|
9
|
+
Lib: class MockLib extends originalModule.Lib {
|
|
10
|
+
sendRequest = jest.fn().mockResolvedValue(undefined)
|
|
11
|
+
},
|
|
12
|
+
}
|
|
13
|
+
})
|
|
14
|
+
|
|
15
|
+
describe('Pageview Tracking', () => {
|
|
16
|
+
const PROJECT_ID = 'test-project-id'
|
|
17
|
+
let libInstance: Lib
|
|
18
|
+
|
|
19
|
+
beforeEach(() => {
|
|
20
|
+
jest.clearAllMocks()
|
|
21
|
+
|
|
22
|
+
libInstance = init(PROJECT_ID, { devMode: true }) as Lib
|
|
23
|
+
|
|
24
|
+
Object.defineProperty(window, 'location', {
|
|
25
|
+
value: {
|
|
26
|
+
hostname: 'example.com',
|
|
27
|
+
pathname: '/test-page',
|
|
28
|
+
hash: '',
|
|
29
|
+
search: '',
|
|
30
|
+
},
|
|
31
|
+
writable: true,
|
|
32
|
+
})
|
|
33
|
+
|
|
34
|
+
Object.defineProperty(document, 'referrer', {
|
|
35
|
+
value: 'https://google.com',
|
|
36
|
+
writable: true,
|
|
37
|
+
})
|
|
38
|
+
})
|
|
39
|
+
|
|
40
|
+
test('pageview function should track a page view', async () => {
|
|
41
|
+
// Arrange
|
|
42
|
+
const path = '/test-page'
|
|
43
|
+
|
|
44
|
+
// Act
|
|
45
|
+
pageview({
|
|
46
|
+
payload: { pg: path },
|
|
47
|
+
unique: false,
|
|
48
|
+
})
|
|
49
|
+
|
|
50
|
+
// Assert
|
|
51
|
+
expect((libInstance as any).sendRequest).toHaveBeenCalledTimes(1)
|
|
52
|
+
expect((libInstance as any).sendRequest).toHaveBeenCalledWith(
|
|
53
|
+
expect.any(String),
|
|
54
|
+
expect.objectContaining({
|
|
55
|
+
pid: PROJECT_ID,
|
|
56
|
+
pg: path,
|
|
57
|
+
}),
|
|
58
|
+
)
|
|
59
|
+
})
|
|
60
|
+
|
|
61
|
+
test('pageview function with unique flag should track unique page view', async () => {
|
|
62
|
+
// Arrange
|
|
63
|
+
const path = '/test-page'
|
|
64
|
+
|
|
65
|
+
// Act
|
|
66
|
+
pageview({
|
|
67
|
+
payload: { pg: path },
|
|
68
|
+
unique: true,
|
|
69
|
+
})
|
|
70
|
+
|
|
71
|
+
// Assert
|
|
72
|
+
expect((libInstance as any).sendRequest).toHaveBeenCalledTimes(1)
|
|
73
|
+
expect((libInstance as any).sendRequest).toHaveBeenCalledWith(
|
|
74
|
+
expect.any(String),
|
|
75
|
+
expect.objectContaining({
|
|
76
|
+
pid: PROJECT_ID,
|
|
77
|
+
pg: path,
|
|
78
|
+
unique: true,
|
|
79
|
+
}),
|
|
80
|
+
)
|
|
81
|
+
})
|
|
82
|
+
|
|
83
|
+
test('pageview function with metadata should include metadata in request', async () => {
|
|
84
|
+
// Arrange
|
|
85
|
+
const path = '/test-page'
|
|
86
|
+
const metadata = {
|
|
87
|
+
category: 'blog',
|
|
88
|
+
author: 'John Doe',
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
// Act
|
|
92
|
+
pageview({
|
|
93
|
+
payload: {
|
|
94
|
+
pg: path,
|
|
95
|
+
meta: metadata,
|
|
96
|
+
},
|
|
97
|
+
unique: false,
|
|
98
|
+
})
|
|
99
|
+
|
|
100
|
+
// Assert
|
|
101
|
+
expect((libInstance as any).sendRequest).toHaveBeenCalledTimes(1)
|
|
102
|
+
expect((libInstance as any).sendRequest).toHaveBeenCalledWith(
|
|
103
|
+
expect.any(String),
|
|
104
|
+
expect.objectContaining({
|
|
105
|
+
pid: PROJECT_ID,
|
|
106
|
+
pg: path,
|
|
107
|
+
meta: metadata,
|
|
108
|
+
}),
|
|
109
|
+
)
|
|
110
|
+
})
|
|
111
|
+
|
|
112
|
+
test('trackViews should start tracking page views', async () => {
|
|
113
|
+
// Mock the trackPageViews method
|
|
114
|
+
;(libInstance as any).trackPageViews = jest.fn().mockReturnValue({
|
|
115
|
+
stop: jest.fn(),
|
|
116
|
+
})
|
|
117
|
+
|
|
118
|
+
// Act
|
|
119
|
+
await trackViews()
|
|
120
|
+
|
|
121
|
+
// Assert
|
|
122
|
+
expect((libInstance as any).trackPageViews).toHaveBeenCalledTimes(1)
|
|
123
|
+
})
|
|
124
|
+
})
|
|
@@ -0,0 +1,217 @@
|
|
|
1
|
+
import * as utils from '../src/utils'
|
|
2
|
+
|
|
3
|
+
describe('Utility Functions', () => {
|
|
4
|
+
beforeEach(() => {
|
|
5
|
+
Object.defineProperty(window, 'location', {
|
|
6
|
+
value: {
|
|
7
|
+
hostname: 'example.com',
|
|
8
|
+
pathname: '/test-page',
|
|
9
|
+
hash: '',
|
|
10
|
+
search: '',
|
|
11
|
+
},
|
|
12
|
+
writable: true,
|
|
13
|
+
})
|
|
14
|
+
|
|
15
|
+
Object.defineProperty(document, 'referrer', {
|
|
16
|
+
value: 'https://google.com',
|
|
17
|
+
writable: true,
|
|
18
|
+
})
|
|
19
|
+
|
|
20
|
+
Object.defineProperty(navigator, 'language', {
|
|
21
|
+
value: 'en-US',
|
|
22
|
+
writable: true,
|
|
23
|
+
})
|
|
24
|
+
|
|
25
|
+
Object.defineProperty(navigator, 'languages', {
|
|
26
|
+
value: ['en-US', 'en'],
|
|
27
|
+
writable: true,
|
|
28
|
+
})
|
|
29
|
+
|
|
30
|
+
Object.defineProperty(navigator, 'webdriver', {
|
|
31
|
+
value: false,
|
|
32
|
+
writable: true,
|
|
33
|
+
})
|
|
34
|
+
})
|
|
35
|
+
|
|
36
|
+
test('isInBrowser should return true in JSDOM environment', () => {
|
|
37
|
+
expect(utils.isInBrowser()).toBe(true)
|
|
38
|
+
})
|
|
39
|
+
|
|
40
|
+
test('isLocalhost should detect localhost', () => {
|
|
41
|
+
// Arrange
|
|
42
|
+
Object.defineProperty(window, 'location', {
|
|
43
|
+
value: {
|
|
44
|
+
hostname: 'localhost',
|
|
45
|
+
},
|
|
46
|
+
writable: true,
|
|
47
|
+
})
|
|
48
|
+
|
|
49
|
+
// Act & Assert
|
|
50
|
+
expect(utils.isLocalhost()).toBe(true)
|
|
51
|
+
|
|
52
|
+
// Test 127.0.0.1
|
|
53
|
+
Object.defineProperty(window, 'location', {
|
|
54
|
+
value: {
|
|
55
|
+
hostname: '127.0.0.1',
|
|
56
|
+
},
|
|
57
|
+
writable: true,
|
|
58
|
+
})
|
|
59
|
+
expect(utils.isLocalhost()).toBe(true)
|
|
60
|
+
|
|
61
|
+
// Test non-localhost
|
|
62
|
+
Object.defineProperty(window, 'location', {
|
|
63
|
+
value: {
|
|
64
|
+
hostname: 'example.com',
|
|
65
|
+
},
|
|
66
|
+
writable: true,
|
|
67
|
+
})
|
|
68
|
+
expect(utils.isLocalhost()).toBe(false)
|
|
69
|
+
})
|
|
70
|
+
|
|
71
|
+
test('isAutomated should detect webdriver', () => {
|
|
72
|
+
// Arrange
|
|
73
|
+
Object.defineProperty(navigator, 'webdriver', {
|
|
74
|
+
value: true,
|
|
75
|
+
writable: true,
|
|
76
|
+
})
|
|
77
|
+
|
|
78
|
+
// Act & Assert
|
|
79
|
+
expect(utils.isAutomated()).toBe(true)
|
|
80
|
+
|
|
81
|
+
// Test non-automated
|
|
82
|
+
Object.defineProperty(navigator, 'webdriver', {
|
|
83
|
+
value: false,
|
|
84
|
+
writable: true,
|
|
85
|
+
})
|
|
86
|
+
expect(utils.isAutomated()).toBe(false)
|
|
87
|
+
})
|
|
88
|
+
|
|
89
|
+
test('getLocale should return the browser language', () => {
|
|
90
|
+
expect(utils.getLocale()).toBe('en-US')
|
|
91
|
+
|
|
92
|
+
// Test with navigator.languages undefined
|
|
93
|
+
const originalLanguages = navigator.languages
|
|
94
|
+
// Instead of deleting, set to undefined
|
|
95
|
+
Object.defineProperty(navigator, 'languages', {
|
|
96
|
+
value: undefined,
|
|
97
|
+
writable: true,
|
|
98
|
+
})
|
|
99
|
+
expect(utils.getLocale()).toBe('en-US') // Should use navigator.language as fallback
|
|
100
|
+
|
|
101
|
+
// Restore the original value
|
|
102
|
+
Object.defineProperty(navigator, 'languages', {
|
|
103
|
+
value: originalLanguages,
|
|
104
|
+
writable: true,
|
|
105
|
+
})
|
|
106
|
+
})
|
|
107
|
+
|
|
108
|
+
test('getReferrer should return the document referrer', () => {
|
|
109
|
+
expect(utils.getReferrer()).toBe('https://google.com')
|
|
110
|
+
|
|
111
|
+
// Test with empty referrer
|
|
112
|
+
Object.defineProperty(document, 'referrer', {
|
|
113
|
+
value: '',
|
|
114
|
+
writable: true,
|
|
115
|
+
})
|
|
116
|
+
expect(utils.getReferrer()).toBeUndefined()
|
|
117
|
+
})
|
|
118
|
+
|
|
119
|
+
test('getPath should handle different URL formats', () => {
|
|
120
|
+
// Arrange
|
|
121
|
+
Object.defineProperty(window, 'location', {
|
|
122
|
+
value: {
|
|
123
|
+
pathname: '/test-page',
|
|
124
|
+
hash: '',
|
|
125
|
+
search: '',
|
|
126
|
+
},
|
|
127
|
+
writable: true,
|
|
128
|
+
})
|
|
129
|
+
|
|
130
|
+
// Act & Assert - basic path
|
|
131
|
+
expect(utils.getPath({})).toBe('/test-page')
|
|
132
|
+
|
|
133
|
+
// Test with hash
|
|
134
|
+
Object.defineProperty(window, 'location', {
|
|
135
|
+
value: {
|
|
136
|
+
pathname: '/test-page',
|
|
137
|
+
hash: '#section1',
|
|
138
|
+
search: '',
|
|
139
|
+
},
|
|
140
|
+
writable: true,
|
|
141
|
+
})
|
|
142
|
+
expect(utils.getPath({ hash: true })).toBe('/test-page#section1')
|
|
143
|
+
|
|
144
|
+
// Test with search
|
|
145
|
+
Object.defineProperty(window, 'location', {
|
|
146
|
+
value: {
|
|
147
|
+
pathname: '/test-page',
|
|
148
|
+
hash: '',
|
|
149
|
+
search: '?param=value',
|
|
150
|
+
},
|
|
151
|
+
writable: true,
|
|
152
|
+
})
|
|
153
|
+
expect(utils.getPath({ search: true })).toBe('/test-page?param=value')
|
|
154
|
+
|
|
155
|
+
// Test with both hash and search
|
|
156
|
+
Object.defineProperty(window, 'location', {
|
|
157
|
+
value: {
|
|
158
|
+
pathname: '/test-page',
|
|
159
|
+
hash: '#section1',
|
|
160
|
+
search: '?param=value',
|
|
161
|
+
},
|
|
162
|
+
writable: true,
|
|
163
|
+
})
|
|
164
|
+
expect(utils.getPath({ hash: true, search: true })).toBe('/test-page#section1?param=value')
|
|
165
|
+
|
|
166
|
+
// Test with search in hash
|
|
167
|
+
Object.defineProperty(window, 'location', {
|
|
168
|
+
value: {
|
|
169
|
+
pathname: '/test-page',
|
|
170
|
+
hash: '#section1?param=value',
|
|
171
|
+
search: '',
|
|
172
|
+
},
|
|
173
|
+
writable: true,
|
|
174
|
+
})
|
|
175
|
+
expect(utils.getPath({ hash: true, search: true })).toBe('/test-page#section1?param=value')
|
|
176
|
+
})
|
|
177
|
+
|
|
178
|
+
test('getUTM* functions should extract UTM parameters', () => {
|
|
179
|
+
// Arrange
|
|
180
|
+
Object.defineProperty(window, 'location', {
|
|
181
|
+
value: {
|
|
182
|
+
pathname: '/landing',
|
|
183
|
+
hash: '',
|
|
184
|
+
search: '?utm_source=google&utm_medium=cpc&utm_campaign=summer&utm_term=analytics&utm_content=ad1',
|
|
185
|
+
},
|
|
186
|
+
writable: true,
|
|
187
|
+
})
|
|
188
|
+
|
|
189
|
+
// Act & Assert
|
|
190
|
+
expect(utils.getUTMSource()).toBe('google')
|
|
191
|
+
expect(utils.getUTMMedium()).toBe('cpc')
|
|
192
|
+
expect(utils.getUTMCampaign()).toBe('summer')
|
|
193
|
+
expect(utils.getUTMTerm()).toBe('analytics')
|
|
194
|
+
expect(utils.getUTMContent()).toBe('ad1')
|
|
195
|
+
|
|
196
|
+
// Test with 'ref' and 'source' parameters as well
|
|
197
|
+
Object.defineProperty(window, 'location', {
|
|
198
|
+
value: {
|
|
199
|
+
pathname: '/landing',
|
|
200
|
+
hash: '',
|
|
201
|
+
search: '?ref=twitter',
|
|
202
|
+
},
|
|
203
|
+
writable: true,
|
|
204
|
+
})
|
|
205
|
+
expect(utils.getUTMSource()).toBe('twitter')
|
|
206
|
+
|
|
207
|
+
Object.defineProperty(window, 'location', {
|
|
208
|
+
value: {
|
|
209
|
+
pathname: '/landing',
|
|
210
|
+
hash: '',
|
|
211
|
+
search: '?source=newsletter',
|
|
212
|
+
},
|
|
213
|
+
writable: true,
|
|
214
|
+
})
|
|
215
|
+
expect(utils.getUTMSource()).toBe('newsletter')
|
|
216
|
+
})
|
|
217
|
+
})
|