@prosdevlab/experience-sdk-plugins 0.1.4 → 0.2.0
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/.turbo/turbo-build.log +6 -6
- package/CHANGELOG.md +30 -0
- package/dist/index.d.ts +608 -1
- package/dist/index.js +692 -2
- package/dist/index.js.map +1 -1
- package/package.json +1 -1
- package/src/exit-intent/exit-intent.test.ts +423 -0
- package/src/exit-intent/exit-intent.ts +372 -0
- package/src/exit-intent/index.ts +6 -0
- package/src/exit-intent/types.ts +59 -0
- package/src/index.ts +5 -0
- package/src/integration.test.ts +362 -0
- package/src/page-visits/index.ts +6 -0
- package/src/page-visits/page-visits.test.ts +562 -0
- package/src/page-visits/page-visits.ts +314 -0
- package/src/page-visits/types.ts +119 -0
- package/src/scroll-depth/index.ts +6 -0
- package/src/scroll-depth/scroll-depth.test.ts +545 -0
- package/src/scroll-depth/scroll-depth.ts +400 -0
- package/src/scroll-depth/types.ts +122 -0
- package/src/time-delay/index.ts +6 -0
- package/src/time-delay/time-delay.test.ts +477 -0
- package/src/time-delay/time-delay.ts +297 -0
- package/src/time-delay/types.ts +89 -0
- package/src/utils/sanitize.ts +1 -1
|
@@ -0,0 +1,562 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Page Visits Plugin Tests
|
|
3
|
+
*
|
|
4
|
+
* Comprehensive tests covering:
|
|
5
|
+
* - Session and lifetime counting
|
|
6
|
+
* - First-visit detection
|
|
7
|
+
* - Storage persistence
|
|
8
|
+
* - DNT (Do Not Track) support
|
|
9
|
+
* - API methods
|
|
10
|
+
* - Event emission
|
|
11
|
+
*/
|
|
12
|
+
|
|
13
|
+
import { SDK } from '@lytics/sdk-kit';
|
|
14
|
+
import { storagePlugin } from '@lytics/sdk-kit-plugins';
|
|
15
|
+
import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest';
|
|
16
|
+
import { pageVisitsPlugin } from './index';
|
|
17
|
+
import type { PageVisitsEvent, PageVisitsPlugin } from './types';
|
|
18
|
+
|
|
19
|
+
type SDKWithPageVisits = SDK & {
|
|
20
|
+
pageVisits: PageVisitsPlugin;
|
|
21
|
+
};
|
|
22
|
+
|
|
23
|
+
describe('Page Visits Plugin', () => {
|
|
24
|
+
let sdk: SDKWithPageVisits;
|
|
25
|
+
|
|
26
|
+
// Helper to initialize plugin with config
|
|
27
|
+
const initPlugin = async (config?: any) => {
|
|
28
|
+
sdk = new SDK({
|
|
29
|
+
pageVisits: config,
|
|
30
|
+
storage: { backend: 'memory' },
|
|
31
|
+
}) as SDKWithPageVisits;
|
|
32
|
+
sdk.use(storagePlugin);
|
|
33
|
+
sdk.use(pageVisitsPlugin);
|
|
34
|
+
await sdk.init();
|
|
35
|
+
};
|
|
36
|
+
|
|
37
|
+
beforeEach(() => {
|
|
38
|
+
vi.clearAllMocks();
|
|
39
|
+
// Clear storage
|
|
40
|
+
sessionStorage.clear();
|
|
41
|
+
localStorage.clear();
|
|
42
|
+
// Reset DNT mock
|
|
43
|
+
Object.defineProperty(navigator, 'doNotTrack', {
|
|
44
|
+
value: '0',
|
|
45
|
+
configurable: true,
|
|
46
|
+
});
|
|
47
|
+
});
|
|
48
|
+
|
|
49
|
+
afterEach(() => {
|
|
50
|
+
if (sdk) {
|
|
51
|
+
sdk.destroy?.();
|
|
52
|
+
}
|
|
53
|
+
});
|
|
54
|
+
|
|
55
|
+
describe('Default Configuration', () => {
|
|
56
|
+
it('should initialize with default config', async () => {
|
|
57
|
+
await initPlugin();
|
|
58
|
+
expect(sdk.pageVisits).toBeDefined();
|
|
59
|
+
});
|
|
60
|
+
|
|
61
|
+
it('should auto-increment on initialization', async () => {
|
|
62
|
+
await initPlugin();
|
|
63
|
+
expect(sdk.pageVisits.getTotalCount()).toBe(1);
|
|
64
|
+
expect(sdk.pageVisits.getSessionCount()).toBe(1);
|
|
65
|
+
});
|
|
66
|
+
|
|
67
|
+
it('should detect first visit', async () => {
|
|
68
|
+
await initPlugin();
|
|
69
|
+
expect(sdk.pageVisits.isFirstVisit()).toBe(false); // After increment, no longer first
|
|
70
|
+
});
|
|
71
|
+
});
|
|
72
|
+
|
|
73
|
+
describe('Session Counter (sessionStorage)', () => {
|
|
74
|
+
it('should increment session count on each page load', async () => {
|
|
75
|
+
await initPlugin();
|
|
76
|
+
expect(sdk.pageVisits.getSessionCount()).toBe(1);
|
|
77
|
+
|
|
78
|
+
// Simulate second page load (reinitialize)
|
|
79
|
+
sdk.destroy?.();
|
|
80
|
+
await initPlugin();
|
|
81
|
+
expect(sdk.pageVisits.getSessionCount()).toBe(2);
|
|
82
|
+
});
|
|
83
|
+
|
|
84
|
+
it('should reset session count when sessionStorage is cleared', async () => {
|
|
85
|
+
await initPlugin();
|
|
86
|
+
expect(sdk.pageVisits.getSessionCount()).toBe(1);
|
|
87
|
+
|
|
88
|
+
// Clear session storage
|
|
89
|
+
sessionStorage.clear();
|
|
90
|
+
|
|
91
|
+
// Reinitialize
|
|
92
|
+
sdk.destroy?.();
|
|
93
|
+
await initPlugin();
|
|
94
|
+
expect(sdk.pageVisits.getSessionCount()).toBe(1); // Back to 1
|
|
95
|
+
});
|
|
96
|
+
|
|
97
|
+
it('should not persist session count across tabs', async () => {
|
|
98
|
+
await initPlugin();
|
|
99
|
+
expect(sdk.pageVisits.getSessionCount()).toBe(1);
|
|
100
|
+
|
|
101
|
+
// Session storage is tab-specific, so count shouldn't persist
|
|
102
|
+
// (This is a characteristic test, not a functional test)
|
|
103
|
+
expect(sessionStorage.length).toBeGreaterThan(0);
|
|
104
|
+
});
|
|
105
|
+
});
|
|
106
|
+
|
|
107
|
+
describe('Lifetime Counter (localStorage)', () => {
|
|
108
|
+
it('should increment total count on each page load', async () => {
|
|
109
|
+
await initPlugin();
|
|
110
|
+
expect(sdk.pageVisits.getTotalCount()).toBe(1);
|
|
111
|
+
|
|
112
|
+
// Simulate second page load (reinitialize)
|
|
113
|
+
sdk.destroy?.();
|
|
114
|
+
await initPlugin();
|
|
115
|
+
expect(sdk.pageVisits.getTotalCount()).toBe(2);
|
|
116
|
+
|
|
117
|
+
// Third page load
|
|
118
|
+
sdk.destroy?.();
|
|
119
|
+
await initPlugin();
|
|
120
|
+
expect(sdk.pageVisits.getTotalCount()).toBe(3);
|
|
121
|
+
});
|
|
122
|
+
|
|
123
|
+
it('should persist total count in localStorage', async () => {
|
|
124
|
+
await initPlugin();
|
|
125
|
+
expect(sdk.pageVisits.getTotalCount()).toBe(1);
|
|
126
|
+
|
|
127
|
+
// Check localStorage directly
|
|
128
|
+
const stored = localStorage.getItem('pageVisits:total');
|
|
129
|
+
expect(stored).toBeDefined();
|
|
130
|
+
expect(stored).not.toBeNull();
|
|
131
|
+
});
|
|
132
|
+
|
|
133
|
+
it('should store timestamps for first and last visit', async () => {
|
|
134
|
+
await initPlugin();
|
|
135
|
+
|
|
136
|
+
const firstVisitTime = sdk.pageVisits.getFirstVisitTime();
|
|
137
|
+
const lastVisitTime = sdk.pageVisits.getLastVisitTime();
|
|
138
|
+
|
|
139
|
+
expect(firstVisitTime).toBeDefined();
|
|
140
|
+
expect(lastVisitTime).toBeDefined();
|
|
141
|
+
expect(typeof firstVisitTime).toBe('number');
|
|
142
|
+
expect(typeof lastVisitTime).toBe('number');
|
|
143
|
+
});
|
|
144
|
+
|
|
145
|
+
it('should keep first visit time constant across visits', async () => {
|
|
146
|
+
await initPlugin();
|
|
147
|
+
const firstVisitTime1 = sdk.pageVisits.getFirstVisitTime();
|
|
148
|
+
|
|
149
|
+
// Second visit
|
|
150
|
+
sdk.destroy?.();
|
|
151
|
+
await initPlugin();
|
|
152
|
+
const firstVisitTime2 = sdk.pageVisits.getFirstVisitTime();
|
|
153
|
+
|
|
154
|
+
expect(firstVisitTime1).toBe(firstVisitTime2);
|
|
155
|
+
});
|
|
156
|
+
|
|
157
|
+
it('should update last visit time on each visit', async () => {
|
|
158
|
+
await initPlugin();
|
|
159
|
+
const lastVisitTime1 = sdk.pageVisits.getLastVisitTime();
|
|
160
|
+
|
|
161
|
+
// Wait a bit
|
|
162
|
+
await new Promise((resolve) => setTimeout(resolve, 10));
|
|
163
|
+
|
|
164
|
+
// Second visit
|
|
165
|
+
sdk.destroy?.();
|
|
166
|
+
await initPlugin();
|
|
167
|
+
const lastVisitTime2 = sdk.pageVisits.getLastVisitTime();
|
|
168
|
+
|
|
169
|
+
expect(lastVisitTime2).toBeGreaterThan(lastVisitTime1 ?? 0);
|
|
170
|
+
});
|
|
171
|
+
});
|
|
172
|
+
|
|
173
|
+
describe('First Visit Detection', () => {
|
|
174
|
+
it('should detect first visit when no data exists', async () => {
|
|
175
|
+
const events: PageVisitsEvent[] = [];
|
|
176
|
+
sdk = new SDK({
|
|
177
|
+
pageVisits: { enabled: true },
|
|
178
|
+
storage: { backend: 'memory' },
|
|
179
|
+
}) as SDKWithPageVisits;
|
|
180
|
+
sdk.use(storagePlugin);
|
|
181
|
+
sdk.use(pageVisitsPlugin);
|
|
182
|
+
|
|
183
|
+
sdk.on('pageVisits:incremented', (event: PageVisitsEvent) => {
|
|
184
|
+
events.push(event);
|
|
185
|
+
});
|
|
186
|
+
|
|
187
|
+
await sdk.init();
|
|
188
|
+
|
|
189
|
+
expect(events.length).toBe(1);
|
|
190
|
+
expect(events[0].isFirstVisit).toBe(true);
|
|
191
|
+
expect(events[0].totalVisits).toBe(1);
|
|
192
|
+
expect(events[0].sessionVisits).toBe(1);
|
|
193
|
+
});
|
|
194
|
+
|
|
195
|
+
it('should not be first visit on subsequent loads', async () => {
|
|
196
|
+
// First visit
|
|
197
|
+
await initPlugin();
|
|
198
|
+
|
|
199
|
+
// Second visit - set up event listener BEFORE init
|
|
200
|
+
sdk.destroy?.();
|
|
201
|
+
|
|
202
|
+
const events: PageVisitsEvent[] = [];
|
|
203
|
+
sdk = new SDK({
|
|
204
|
+
pageVisits: { enabled: true },
|
|
205
|
+
storage: { backend: 'memory' },
|
|
206
|
+
}) as SDKWithPageVisits;
|
|
207
|
+
sdk.use(storagePlugin);
|
|
208
|
+
sdk.use(pageVisitsPlugin);
|
|
209
|
+
|
|
210
|
+
sdk.on('pageVisits:incremented', (event: PageVisitsEvent) => {
|
|
211
|
+
events.push(event);
|
|
212
|
+
});
|
|
213
|
+
|
|
214
|
+
await sdk.init();
|
|
215
|
+
|
|
216
|
+
expect(events.length).toBe(1);
|
|
217
|
+
expect(events[0].isFirstVisit).toBe(false);
|
|
218
|
+
expect(events[0].totalVisits).toBe(2);
|
|
219
|
+
});
|
|
220
|
+
});
|
|
221
|
+
|
|
222
|
+
describe('DNT (Do Not Track)', () => {
|
|
223
|
+
it('should respect DNT when enabled', async () => {
|
|
224
|
+
// Mock DNT
|
|
225
|
+
Object.defineProperty(navigator, 'doNotTrack', {
|
|
226
|
+
value: '1',
|
|
227
|
+
configurable: true,
|
|
228
|
+
});
|
|
229
|
+
|
|
230
|
+
const events: any[] = [];
|
|
231
|
+
sdk = new SDK({
|
|
232
|
+
pageVisits: { enabled: true, respectDNT: true },
|
|
233
|
+
storage: { backend: 'memory' },
|
|
234
|
+
}) as SDKWithPageVisits;
|
|
235
|
+
sdk.use(storagePlugin);
|
|
236
|
+
sdk.use(pageVisitsPlugin);
|
|
237
|
+
|
|
238
|
+
sdk.on('pageVisits:disabled', (event: any) => {
|
|
239
|
+
events.push(event);
|
|
240
|
+
});
|
|
241
|
+
|
|
242
|
+
await sdk.init();
|
|
243
|
+
|
|
244
|
+
expect(events.length).toBe(1);
|
|
245
|
+
expect(events[0].reason).toBe('dnt');
|
|
246
|
+
});
|
|
247
|
+
|
|
248
|
+
it('should not track when DNT is enabled', async () => {
|
|
249
|
+
// Mock DNT
|
|
250
|
+
Object.defineProperty(navigator, 'doNotTrack', {
|
|
251
|
+
value: '1',
|
|
252
|
+
configurable: true,
|
|
253
|
+
});
|
|
254
|
+
|
|
255
|
+
await initPlugin({ enabled: true, respectDNT: true });
|
|
256
|
+
|
|
257
|
+
// No tracking should occur
|
|
258
|
+
expect(sdk.pageVisits.getTotalCount()).toBe(0);
|
|
259
|
+
expect(sdk.pageVisits.getSessionCount()).toBe(0);
|
|
260
|
+
});
|
|
261
|
+
|
|
262
|
+
it('should ignore DNT when respectDNT is false', async () => {
|
|
263
|
+
// Mock DNT
|
|
264
|
+
Object.defineProperty(navigator, 'doNotTrack', {
|
|
265
|
+
value: '1',
|
|
266
|
+
configurable: true,
|
|
267
|
+
});
|
|
268
|
+
|
|
269
|
+
await initPlugin({ enabled: true, respectDNT: false });
|
|
270
|
+
|
|
271
|
+
// Should still track
|
|
272
|
+
expect(sdk.pageVisits.getTotalCount()).toBe(1);
|
|
273
|
+
expect(sdk.pageVisits.getSessionCount()).toBe(1);
|
|
274
|
+
});
|
|
275
|
+
});
|
|
276
|
+
|
|
277
|
+
describe('API Methods', () => {
|
|
278
|
+
describe('getTotalCount', () => {
|
|
279
|
+
it('should return total visit count', async () => {
|
|
280
|
+
await initPlugin();
|
|
281
|
+
expect(sdk.pageVisits.getTotalCount()).toBe(1);
|
|
282
|
+
});
|
|
283
|
+
});
|
|
284
|
+
|
|
285
|
+
describe('getSessionCount', () => {
|
|
286
|
+
it('should return session visit count', async () => {
|
|
287
|
+
await initPlugin();
|
|
288
|
+
expect(sdk.pageVisits.getSessionCount()).toBe(1);
|
|
289
|
+
});
|
|
290
|
+
});
|
|
291
|
+
|
|
292
|
+
describe('isFirstVisit', () => {
|
|
293
|
+
it('should return false after first increment', async () => {
|
|
294
|
+
await initPlugin();
|
|
295
|
+
expect(sdk.pageVisits.isFirstVisit()).toBe(false);
|
|
296
|
+
});
|
|
297
|
+
});
|
|
298
|
+
|
|
299
|
+
describe('getFirstVisitTime', () => {
|
|
300
|
+
it('should return first visit timestamp', async () => {
|
|
301
|
+
await initPlugin();
|
|
302
|
+
const time = sdk.pageVisits.getFirstVisitTime();
|
|
303
|
+
expect(time).toBeDefined();
|
|
304
|
+
expect(typeof time).toBe('number');
|
|
305
|
+
});
|
|
306
|
+
});
|
|
307
|
+
|
|
308
|
+
describe('getLastVisitTime', () => {
|
|
309
|
+
it('should return last visit timestamp', async () => {
|
|
310
|
+
await initPlugin();
|
|
311
|
+
const time = sdk.pageVisits.getLastVisitTime();
|
|
312
|
+
expect(time).toBeDefined();
|
|
313
|
+
expect(typeof time).toBe('number');
|
|
314
|
+
});
|
|
315
|
+
});
|
|
316
|
+
|
|
317
|
+
describe('increment', () => {
|
|
318
|
+
it('should manually increment counters', async () => {
|
|
319
|
+
await initPlugin({ autoIncrement: false });
|
|
320
|
+
expect(sdk.pageVisits.getTotalCount()).toBe(0);
|
|
321
|
+
|
|
322
|
+
sdk.pageVisits.increment();
|
|
323
|
+
expect(sdk.pageVisits.getTotalCount()).toBe(1);
|
|
324
|
+
expect(sdk.pageVisits.getSessionCount()).toBe(1);
|
|
325
|
+
});
|
|
326
|
+
|
|
327
|
+
it('should emit pageVisits:incremented event', async () => {
|
|
328
|
+
await initPlugin({ autoIncrement: false });
|
|
329
|
+
|
|
330
|
+
const events: PageVisitsEvent[] = [];
|
|
331
|
+
sdk.on('pageVisits:incremented', (event: PageVisitsEvent) => {
|
|
332
|
+
events.push(event);
|
|
333
|
+
});
|
|
334
|
+
|
|
335
|
+
sdk.pageVisits.increment();
|
|
336
|
+
|
|
337
|
+
expect(events.length).toBe(1);
|
|
338
|
+
expect(events[0].totalVisits).toBe(1);
|
|
339
|
+
expect(events[0].sessionVisits).toBe(1);
|
|
340
|
+
});
|
|
341
|
+
});
|
|
342
|
+
|
|
343
|
+
describe('reset', () => {
|
|
344
|
+
it('should reset all counters', async () => {
|
|
345
|
+
await initPlugin();
|
|
346
|
+
expect(sdk.pageVisits.getTotalCount()).toBe(1);
|
|
347
|
+
|
|
348
|
+
sdk.pageVisits.reset();
|
|
349
|
+
|
|
350
|
+
expect(sdk.pageVisits.getTotalCount()).toBe(0);
|
|
351
|
+
expect(sdk.pageVisits.getSessionCount()).toBe(0);
|
|
352
|
+
expect(sdk.pageVisits.isFirstVisit()).toBe(false);
|
|
353
|
+
expect(sdk.pageVisits.getFirstVisitTime()).toBeUndefined();
|
|
354
|
+
expect(sdk.pageVisits.getLastVisitTime()).toBeUndefined();
|
|
355
|
+
});
|
|
356
|
+
|
|
357
|
+
it('should clear storage', async () => {
|
|
358
|
+
await initPlugin();
|
|
359
|
+
sdk.pageVisits.reset();
|
|
360
|
+
|
|
361
|
+
const sessionData = sessionStorage.getItem('pageVisits:session');
|
|
362
|
+
const totalData = localStorage.getItem('pageVisits:total');
|
|
363
|
+
|
|
364
|
+
expect(sessionData).toBeNull();
|
|
365
|
+
expect(totalData).toBeNull();
|
|
366
|
+
});
|
|
367
|
+
|
|
368
|
+
it('should emit pageVisits:reset event', async () => {
|
|
369
|
+
await initPlugin();
|
|
370
|
+
|
|
371
|
+
const events: any[] = [];
|
|
372
|
+
sdk.on('pageVisits:reset', () => {
|
|
373
|
+
events.push(true);
|
|
374
|
+
});
|
|
375
|
+
|
|
376
|
+
sdk.pageVisits.reset();
|
|
377
|
+
|
|
378
|
+
expect(events.length).toBe(1);
|
|
379
|
+
});
|
|
380
|
+
});
|
|
381
|
+
|
|
382
|
+
describe('getState', () => {
|
|
383
|
+
it('should return full page visits state', async () => {
|
|
384
|
+
await initPlugin();
|
|
385
|
+
|
|
386
|
+
const state = sdk.pageVisits.getState();
|
|
387
|
+
|
|
388
|
+
expect(state).toHaveProperty('isFirstVisit');
|
|
389
|
+
expect(state).toHaveProperty('totalVisits');
|
|
390
|
+
expect(state).toHaveProperty('sessionVisits');
|
|
391
|
+
expect(state).toHaveProperty('firstVisitTime');
|
|
392
|
+
expect(state).toHaveProperty('lastVisitTime');
|
|
393
|
+
expect(state).toHaveProperty('timestamp');
|
|
394
|
+
});
|
|
395
|
+
});
|
|
396
|
+
});
|
|
397
|
+
|
|
398
|
+
describe('Configuration', () => {
|
|
399
|
+
it('should support custom storage keys', async () => {
|
|
400
|
+
await initPlugin({
|
|
401
|
+
sessionKey: 'custom:session',
|
|
402
|
+
totalKey: 'custom:total',
|
|
403
|
+
});
|
|
404
|
+
|
|
405
|
+
expect(sdk.pageVisits.getTotalCount()).toBe(1);
|
|
406
|
+
|
|
407
|
+
// Check custom keys in storage
|
|
408
|
+
const sessionData = sessionStorage.getItem('custom:session');
|
|
409
|
+
const totalData = localStorage.getItem('custom:total');
|
|
410
|
+
|
|
411
|
+
expect(sessionData).toBeDefined();
|
|
412
|
+
expect(totalData).toBeDefined();
|
|
413
|
+
});
|
|
414
|
+
|
|
415
|
+
it('should support disabling via config', async () => {
|
|
416
|
+
const events: any[] = [];
|
|
417
|
+
sdk = new SDK({
|
|
418
|
+
pageVisits: { enabled: false },
|
|
419
|
+
storage: { backend: 'memory' },
|
|
420
|
+
}) as SDKWithPageVisits;
|
|
421
|
+
sdk.use(storagePlugin);
|
|
422
|
+
sdk.use(pageVisitsPlugin);
|
|
423
|
+
|
|
424
|
+
sdk.on('pageVisits:disabled', (event: any) => {
|
|
425
|
+
events.push(event);
|
|
426
|
+
});
|
|
427
|
+
|
|
428
|
+
await sdk.init();
|
|
429
|
+
|
|
430
|
+
expect(events.length).toBe(1);
|
|
431
|
+
expect(events[0].reason).toBe('config');
|
|
432
|
+
});
|
|
433
|
+
|
|
434
|
+
it('should support disabling auto-increment', async () => {
|
|
435
|
+
await initPlugin({ autoIncrement: false });
|
|
436
|
+
|
|
437
|
+
expect(sdk.pageVisits.getTotalCount()).toBe(0);
|
|
438
|
+
expect(sdk.pageVisits.getSessionCount()).toBe(0);
|
|
439
|
+
});
|
|
440
|
+
});
|
|
441
|
+
|
|
442
|
+
describe('Event Emission', () => {
|
|
443
|
+
it('should emit pageVisits:incremented with full payload', async () => {
|
|
444
|
+
const events: PageVisitsEvent[] = [];
|
|
445
|
+
sdk = new SDK({
|
|
446
|
+
pageVisits: { enabled: true },
|
|
447
|
+
storage: { backend: 'memory' },
|
|
448
|
+
}) as SDKWithPageVisits;
|
|
449
|
+
sdk.use(storagePlugin);
|
|
450
|
+
sdk.use(pageVisitsPlugin);
|
|
451
|
+
|
|
452
|
+
sdk.on('pageVisits:incremented', (event: PageVisitsEvent) => {
|
|
453
|
+
events.push(event);
|
|
454
|
+
});
|
|
455
|
+
|
|
456
|
+
await sdk.init();
|
|
457
|
+
|
|
458
|
+
expect(events.length).toBe(1);
|
|
459
|
+
expect(events[0]).toMatchObject({
|
|
460
|
+
isFirstVisit: true,
|
|
461
|
+
totalVisits: 1,
|
|
462
|
+
sessionVisits: 1,
|
|
463
|
+
});
|
|
464
|
+
expect(events[0].firstVisitTime).toBeDefined();
|
|
465
|
+
expect(events[0].lastVisitTime).toBeDefined();
|
|
466
|
+
expect(events[0].timestamp).toBeDefined();
|
|
467
|
+
});
|
|
468
|
+
});
|
|
469
|
+
|
|
470
|
+
describe('Integration Scenarios', () => {
|
|
471
|
+
it('should track session-scoped visits correctly', async () => {
|
|
472
|
+
// First page load
|
|
473
|
+
await initPlugin();
|
|
474
|
+
const session1 = sdk.pageVisits.getSessionCount();
|
|
475
|
+
expect(session1).toBe(1);
|
|
476
|
+
|
|
477
|
+
// Second page load (same session)
|
|
478
|
+
sdk.destroy?.();
|
|
479
|
+
await initPlugin();
|
|
480
|
+
const session2 = sdk.pageVisits.getSessionCount();
|
|
481
|
+
expect(session2).toBe(2);
|
|
482
|
+
|
|
483
|
+
// Clear sessionStorage (simulate new session)
|
|
484
|
+
sessionStorage.clear();
|
|
485
|
+
sdk.destroy?.();
|
|
486
|
+
await initPlugin();
|
|
487
|
+
const session3 = sdk.pageVisits.getSessionCount();
|
|
488
|
+
expect(session3).toBe(1); // Reset
|
|
489
|
+
});
|
|
490
|
+
|
|
491
|
+
it('should track lifetime visits across sessions', async () => {
|
|
492
|
+
// First visit
|
|
493
|
+
await initPlugin();
|
|
494
|
+
const total1 = sdk.pageVisits.getTotalCount();
|
|
495
|
+
expect(total1).toBe(1);
|
|
496
|
+
|
|
497
|
+
// Second visit
|
|
498
|
+
sdk.destroy?.();
|
|
499
|
+
await initPlugin();
|
|
500
|
+
const total2 = sdk.pageVisits.getTotalCount();
|
|
501
|
+
expect(total2).toBe(2);
|
|
502
|
+
|
|
503
|
+
// Clear sessionStorage (new session) but keep localStorage
|
|
504
|
+
sessionStorage.clear();
|
|
505
|
+
sdk.destroy?.();
|
|
506
|
+
await initPlugin();
|
|
507
|
+
const total3 = sdk.pageVisits.getTotalCount();
|
|
508
|
+
expect(total3).toBe(3); // Continues incrementing
|
|
509
|
+
});
|
|
510
|
+
|
|
511
|
+
it('should support first-visit detection', async () => {
|
|
512
|
+
const events: PageVisitsEvent[] = [];
|
|
513
|
+
sdk = new SDK({
|
|
514
|
+
pageVisits: { enabled: true },
|
|
515
|
+
storage: { backend: 'memory' },
|
|
516
|
+
}) as SDKWithPageVisits;
|
|
517
|
+
sdk.use(storagePlugin);
|
|
518
|
+
sdk.use(pageVisitsPlugin);
|
|
519
|
+
|
|
520
|
+
sdk.on('pageVisits:incremented', (event: PageVisitsEvent) => {
|
|
521
|
+
events.push(event);
|
|
522
|
+
});
|
|
523
|
+
|
|
524
|
+
await sdk.init();
|
|
525
|
+
|
|
526
|
+
expect(events[0].isFirstVisit).toBe(true);
|
|
527
|
+
});
|
|
528
|
+
|
|
529
|
+
it('should support all comparison operators in targeting', async () => {
|
|
530
|
+
await initPlugin();
|
|
531
|
+
|
|
532
|
+
// Simulate 5 visits
|
|
533
|
+
for (let i = 0; i < 4; i++) {
|
|
534
|
+
sdk.destroy?.();
|
|
535
|
+
await initPlugin();
|
|
536
|
+
}
|
|
537
|
+
|
|
538
|
+
const count = sdk.pageVisits.getTotalCount();
|
|
539
|
+
expect(count).toBe(5);
|
|
540
|
+
|
|
541
|
+
// Support all operators for flexible targeting
|
|
542
|
+
expect(count >= 5).toBe(true);
|
|
543
|
+
expect(count === 5).toBe(true);
|
|
544
|
+
expect(count < 10).toBe(true);
|
|
545
|
+
});
|
|
546
|
+
});
|
|
547
|
+
|
|
548
|
+
describe('Storage Backend Integration', () => {
|
|
549
|
+
it('should auto-load storage plugin if missing', async () => {
|
|
550
|
+
// Don't manually load storagePlugin
|
|
551
|
+
sdk = new SDK({
|
|
552
|
+
pageVisits: { enabled: true },
|
|
553
|
+
}) as SDKWithPageVisits;
|
|
554
|
+
sdk.use(pageVisitsPlugin);
|
|
555
|
+
|
|
556
|
+
await sdk.init();
|
|
557
|
+
|
|
558
|
+
// Should still work (auto-loaded)
|
|
559
|
+
expect(sdk.pageVisits.getTotalCount()).toBe(1);
|
|
560
|
+
});
|
|
561
|
+
});
|
|
562
|
+
});
|