diff --git a/packages/web/src/generic.test.ts b/packages/web/src/generic.test.ts
index 910b22e..08ce8b1 100644
--- a/packages/web/src/generic.test.ts
+++ b/packages/web/src/generic.test.ts
@@ -1,10 +1,17 @@
import { beforeEach, describe, it, expect, jest } from '@jest/globals';
import { inject, track } from './generic';
-
-describe('inject', () => {
- describe('in development mode', () => {
- it('should add the script tag correctly', () => {
- inject({ mode: 'development' });
+import type { AllowedPropertyValues, Mode } from './types';
+
+describe.each([
+ {
+ mode: 'development',
+ file: 'https://va.vercel-scripts.com/v1/script.debug.js',
+ },
+ { mode: 'production', file: 'http://localhost/_vercel/insights/script.js' },
+] as { mode: Mode; file: string }[])('in $mode mode', ({ mode, file }) => {
+ describe('inject', () => {
+ it('adds the script tag correctly', () => {
+ inject({ mode });
const scripts = document.getElementsByTagName('script');
expect(scripts).toHaveLength(1);
@@ -15,172 +22,49 @@ describe('inject', () => {
throw new Error('Could not find script tag');
}
- expect(script.src).toEqual(
- 'https://va.vercel-scripts.com/v1/script.debug.js'
- );
+ expect(script.src).toEqual(file);
expect(script).toHaveAttribute('defer');
});
});
- describe('in production mode', () => {
- it('should add the script tag correctly', () => {
- inject({ mode: 'production' });
-
- const scripts = document.getElementsByTagName('script');
- expect(scripts).toHaveLength(1);
-
- const script = document.head.querySelector('script');
-
- if (!script) {
- throw new Error('Could not find script tag');
- }
-
- expect(script.src).toEqual('http://localhost/_vercel/insights/script.js');
- expect(script).toHaveAttribute('defer');
- });
- });
-});
-
-describe('track custom events', () => {
- beforeEach(() => {
- // reset the internal queue before every test
- window.vaq = [];
- });
-
- describe('in production mode', () => {
+ describe('track custom events', () => {
beforeEach(() => {
- inject({
- mode: 'production',
- });
+ // reset the internal queue before every test
+ window.vaq = [];
+ inject({ mode });
});
describe('queue custom events', () => {
- it('should track event with name only', () => {
- track('my event');
-
- expect(window.vaq).toBeDefined();
-
- if (!window.vaq) throw new Error('window.vaq is not defined');
-
- expect(window.vaq[0]).toEqual([
- 'event',
- {
- name: 'my event',
- },
- ]);
+ it('tracks event with name only', () => {
+ const name = 'my event';
+ track(name);
+ expect(window.vaq?.[0]).toEqual(['event', { name }]);
});
- it('should allow custom data to be tracked', () => {
- track('custom event', {
- string: 'string',
- number: 1,
- });
-
- expect(window.vaq).toBeDefined();
-
- if (!window.vaq) throw new Error('window.vaq is not defined');
-
- expect(window.vaq[0]).toEqual([
- 'event',
- {
- name: 'custom event',
- data: {
- string: 'string',
- number: 1,
- },
- },
- ]);
+ it('allows custom data to be tracked', () => {
+ const name = 'custom event';
+ const data = { string: 'string', number: 1 };
+ track(name, data);
+ expect(window.vaq?.[0]).toEqual(['event', { name, data }]);
});
it('should strip data for nested objects', () => {
- track('custom event', {
- string: 'string',
- number: 1,
- // eslint-disable-next-line @typescript-eslint/no-unsafe-assignment -- This is intentional
- nested: {
- object: '',
- // eslint-disable-next-line @typescript-eslint/no-explicit-any -- This is intentional
- } as any,
- });
-
- expect(window.vaq).toBeDefined();
-
- if (!window.vaq) throw new Error('window.vaq is not defined');
-
- expect(window.vaq[0]).toEqual([
- 'event',
- {
- name: 'custom event',
- data: {
- string: 'string',
- number: 1,
- },
- },
- ]);
- });
- });
- });
-
- describe('in development mode', () => {
- beforeEach(() => {
- inject({
- mode: 'development',
- });
- // eslint-disable-next-line @typescript-eslint/no-empty-function -- This is intentional
- jest.spyOn(global.console, 'error').mockImplementation(() => {});
- });
-
- describe('queue custom events', () => {
- it('should track event with name only', () => {
- track('my event');
-
- expect(window.vaq).toBeDefined();
-
- if (!window.vaq) throw new Error('window.vaq is not defined');
-
- expect(window.vaq[0]).toEqual([
- 'event',
- {
- name: 'my event',
- },
- ]);
- });
-
- it('should allow custom data to be tracked', () => {
- track('custom event', {
- string: 'string',
- number: 1,
- });
-
- expect(window.vaq).toBeDefined();
-
- if (!window.vaq) throw new Error('window.vaq is not defined');
-
- expect(window.vaq[0]).toEqual([
- 'event',
- {
- name: 'custom event',
- data: {
- string: 'string',
- number: 1,
- },
- },
- ]);
- });
+ jest.spyOn(global.console, 'error').mockImplementation(() => void 0);
- it('should log an error when nested properties are sent', () => {
- track('custom event', {
- string: 'string',
- number: 1,
- // eslint-disable-next-line @typescript-eslint/no-unsafe-assignment -- This is intentional
- nested: {
- object: '',
- // eslint-disable-next-line @typescript-eslint/no-explicit-any -- This is intentional
- } as any,
+ const name = 'custom event';
+ const data = { string: 'string', number: 1 };
+ track(name, {
+ ...data,
+ nested: { object: '' } as unknown as AllowedPropertyValues,
});
- // eslint-disable-next-line no-console -- Logging to console is intentional
- expect(console.error).toHaveBeenCalledTimes(1);
+ if (mode === 'development') {
+ // eslint-disable-next-line jest/no-conditional-expect, no-console -- only in development
+ expect(console.error).toHaveBeenCalledTimes(1);
+ } else {
+ // eslint-disable-next-line jest/no-conditional-expect -- only in production
+ expect(window.vaq?.[0]).toEqual(['event', { name, data }]);
+ }
});
});
});
diff --git a/packages/web/src/react.test.tsx b/packages/web/src/react.test.tsx
index a75b0b3..68480c8 100644
--- a/packages/web/src/react.test.tsx
+++ b/packages/web/src/react.test.tsx
@@ -2,67 +2,80 @@ import * as React from 'react';
import { afterEach, beforeEach, describe, it, expect } from '@jest/globals';
import { cleanup, render } from '@testing-library/react';
import { Analytics, track } from './react';
+import type { AllowedPropertyValues, AnalyticsProps, Mode } from './types';
describe('', () => {
afterEach(() => {
cleanup();
});
- describe('in development mode', () => {
- it('should add the script tag correctly', () => {
- render();
+ beforeEach(() => {
+ window.va = undefined;
+ // reset the internal queue before every test
+ window.vaq = [];
+ });
+
+ describe.each([
+ {
+ mode: 'development',
+ file: 'https://va.vercel-scripts.com/v1/script.debug.js',
+ },
+ { mode: 'production', file: 'http://localhost/_vercel/insights/script.js' },
+ ] as { mode: Mode; file: string }[])('in $mode mode', ({ mode, file }) => {
+ it('adds the script tag correctly', () => {
+ render();
const scripts = document.getElementsByTagName('script');
expect(scripts).toHaveLength(1);
const script = document.head.querySelector('script');
-
- if (!script) {
- throw new Error('Could not find script tag');
- }
-
- expect(script.src).toEqual(
- 'https://va.vercel-scripts.com/v1/script.debug.js'
- );
+ expect(script).toBeDefined();
+ expect(script?.src).toEqual(file);
expect(script).toHaveAttribute('defer');
});
- });
- describe('in production mode', () => {
- it('should add the script tag correctly', () => {
- render();
+ it('sets and changes beforeSend', () => {
+ const beforeSend: Required['beforeSend'] = (event) =>
+ event;
+ const beforeSend2: Required['beforeSend'] = (event) =>
+ event;
+ const { rerender } = render(
+
+ );
- const scripts = document.getElementsByTagName('script');
- expect(scripts).toHaveLength(1);
+ expect(window.vaq?.[0]).toEqual(['beforeSend', beforeSend]);
+ expect(window.vaq).toHaveLength(1);
+ window.vaq?.splice(0, 1);
- const script = document.head.querySelector('script');
+ rerender();
+ expect(window.vaq).toHaveLength(0);
+
+ rerender();
+ expect(window.vaq?.[0]).toEqual(['beforeSend', beforeSend2]);
+ expect(window.vaq).toHaveLength(1);
+ });
- if (!script) {
- throw new Error('Could not find script tag');
- }
+ it('does not change beforeSend when undefined', () => {
+ const beforeSend: Required['beforeSend'] = (event) =>
+ event;
+ const { rerender } = render();
- expect(script.src).toEqual('http://localhost/_vercel/insights/script.js');
- expect(script).toHaveAttribute('defer');
+ expect(window.vaq?.[0]).toEqual(['beforeSend', beforeSend]);
+ expect(window.vaq).toHaveLength(1);
+ window.vaq?.splice(0, 1);
+
+ rerender();
+ expect(window.vaq).toHaveLength(0);
});
});
describe('track custom events', () => {
- beforeEach(() => {
- // reset the internal queue before every test
- window.vaq = [];
- });
-
describe('queue custom events', () => {
- it('should track event with name only', () => {
+ it('tracks event with name only', () => {
render();
-
track('my event');
- expect(window.vaq).toBeDefined();
-
- if (!window.vaq) throw new Error('window.vaq is not defined');
-
- expect(window.vaq[0]).toEqual([
+ expect(window.vaq?.[0]).toEqual([
'event',
{
name: 'my event',
@@ -70,57 +83,25 @@ describe('', () => {
]);
});
- it('should allow custom data to be tracked', () => {
+ it('allows custom data to be tracked', () => {
render();
+ const name = 'custom event';
+ const data = { string: 'string', number: 1 };
+ track(name, data);
- track('custom event', {
- string: 'string',
- number: 1,
- });
-
- expect(window.vaq).toBeDefined();
-
- if (!window.vaq) throw new Error('window.vaq is not defined');
-
- expect(window.vaq[0]).toEqual([
- 'event',
- {
- name: 'custom event',
- data: {
- string: 'string',
- number: 1,
- },
- },
- ]);
+ expect(window.vaq?.[0]).toEqual(['event', { name, data }]);
});
- it('should strip data for nested objects', () => {
+ it('strips data for nested objects', () => {
render();
-
- track('custom event', {
- string: 'string',
- number: 1,
- // eslint-disable-next-line @typescript-eslint/no-unsafe-assignment -- Intentional to trigger error
- nested: {
- object: '',
- // eslint-disable-next-line @typescript-eslint/no-explicit-any -- Allow
- } as any,
+ const name = 'custom event';
+ const data = { string: 'string', number: 1 };
+ track(name, {
+ ...data,
+ nested: { object: '' } as unknown as AllowedPropertyValues,
});
- expect(window.vaq).toBeDefined();
-
- if (!window.vaq) throw new Error('window.vaq is not defined');
-
- expect(window.vaq[0]).toEqual([
- 'event',
- {
- name: 'custom event',
- data: {
- string: 'string',
- number: 1,
- },
- },
- ]);
+ expect(window.vaq?.[0]).toEqual(['event', { name, data }]);
});
});
});
diff --git a/packages/web/src/react.tsx b/packages/web/src/react.tsx
index d86b9e2..54d1377 100644
--- a/packages/web/src/react.tsx
+++ b/packages/web/src/react.tsx
@@ -33,6 +33,12 @@ function Analytics(
path?: string | null;
}
): null {
+ useEffect(() => {
+ if (props.beforeSend) {
+ window.va?.('beforeSend', props.beforeSend);
+ }
+ }, [props.beforeSend]);
+
// biome-ignore lint/correctness/useExhaustiveDependencies: only run once
useEffect(() => {
inject({