From 1d20df16d3d53e14ef35664270a1c28b3fecd78d Mon Sep 17 00:00:00 2001 From: MatteoGabriele Date: Sun, 30 Mar 2025 20:45:00 +0200 Subject: [PATCH 01/16] feat(pageview): send set utm manually --- src/api/pageview/pageview.test.ts | 24 ++++++++++++++++++++++++ src/api/pageview/pageview.ts | 11 +++++++++++ 2 files changed, 35 insertions(+) diff --git a/src/api/pageview/pageview.test.ts b/src/api/pageview/pageview.test.ts index 8dd0d2e5..8edf1991 100644 --- a/src/api/pageview/pageview.test.ts +++ b/src/api/pageview/pageview.test.ts @@ -120,6 +120,30 @@ describe("pageview", () => { ); }); + it("should send utm parameters manually outside the page_view event", () => { + pageview({ + page_path: "/", + page_location: + "http://localhost:3000/?utm_source=google&utm_medium=cpc&utm_campaign=summer_sale", + }); + + expect(query).toHaveBeenNthCalledWith(1, "set", "utm_tracking", { + utm_source: "google", + utm_medium: "cpc", + utm_campaign: "summer_sale", + }); + + expect(query).toHaveBeenNthCalledWith( + 2, + "event", + "page_view", + expect.objectContaining({ + page_path: "/", + page_location: "http://localhost:3000/", + }), + ); + }); + describe("pageTracker enabled", () => { beforeEach(() => { updateSettings({ diff --git a/src/api/pageview/pageview.ts b/src/api/pageview/pageview.ts index a56725a9..129b13f8 100644 --- a/src/api/pageview/pageview.ts +++ b/src/api/pageview/pageview.ts @@ -1,4 +1,5 @@ import { query } from "@/api/query"; +import { set } from "@/api/set"; import { type Route, getSettings } from "@/core/settings"; import type { GtagConfigParams } from "@/types/gtag"; @@ -48,5 +49,15 @@ export function pageview(params: PageviewParams) { template.page_path = template.page_path.slice(0, -1); } + if (template.page_location.match(/utm_/)) { + const url = new URL(template.page_location); + const utmParams = Object.fromEntries(url.searchParams.entries()); + + url.search = ""; + template.page_location = url.toString(); + + set("campaign", utmParams); + } + query("event", "page_view", template); } From 0d648e9f387c369b5ad094fca599ff04faf80cb8 Mon Sep 17 00:00:00 2001 From: MatteoGabriele Date: Sun, 30 Mar 2025 21:01:23 +0200 Subject: [PATCH 02/16] test: fix set param name --- src/api/pageview/pageview.test.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/api/pageview/pageview.test.ts b/src/api/pageview/pageview.test.ts index 8edf1991..f92cbd66 100644 --- a/src/api/pageview/pageview.test.ts +++ b/src/api/pageview/pageview.test.ts @@ -127,7 +127,7 @@ describe("pageview", () => { "http://localhost:3000/?utm_source=google&utm_medium=cpc&utm_campaign=summer_sale", }); - expect(query).toHaveBeenNthCalledWith(1, "set", "utm_tracking", { + expect(query).toHaveBeenNthCalledWith(1, "set", "campaign", { utm_source: "google", utm_medium: "cpc", utm_campaign: "summer_sale", From 5391c995ff2350d3abd7ae208140503f6902bbf5 Mon Sep 17 00:00:00 2001 From: MatteoGabriele Date: Mon, 31 Mar 2025 00:13:34 +0200 Subject: [PATCH 03/16] refactor: better regex --- src/api/pageview/pageview.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/api/pageview/pageview.ts b/src/api/pageview/pageview.ts index 129b13f8..747d981e 100644 --- a/src/api/pageview/pageview.ts +++ b/src/api/pageview/pageview.ts @@ -49,7 +49,7 @@ export function pageview(params: PageviewParams) { template.page_path = template.page_path.slice(0, -1); } - if (template.page_location.match(/utm_/)) { + if (template.page_location?.match(/[?&]utm_/)) { const url = new URL(template.page_location); const utmParams = Object.fromEntries(url.searchParams.entries()); From bda7165f2706a11b7453a2659d7662ef44d25fb0 Mon Sep 17 00:00:00 2001 From: MatteoGabriele Date: Thu, 3 Apr 2025 18:44:22 +0200 Subject: [PATCH 04/16] feat: remove utm_ from parameters --- src/api/pageview/pageview.test.ts | 6 +++--- src/api/pageview/pageview.ts | 20 +++++++++++++++++--- 2 files changed, 20 insertions(+), 6 deletions(-) diff --git a/src/api/pageview/pageview.test.ts b/src/api/pageview/pageview.test.ts index f92cbd66..11ba14fa 100644 --- a/src/api/pageview/pageview.test.ts +++ b/src/api/pageview/pageview.test.ts @@ -128,9 +128,9 @@ describe("pageview", () => { }); expect(query).toHaveBeenNthCalledWith(1, "set", "campaign", { - utm_source: "google", - utm_medium: "cpc", - utm_campaign: "summer_sale", + source: "google", + medium: "cpc", + campaign: "summer_sale", }); expect(query).toHaveBeenNthCalledWith( diff --git a/src/api/pageview/pageview.ts b/src/api/pageview/pageview.ts index 747d981e..891f622a 100644 --- a/src/api/pageview/pageview.ts +++ b/src/api/pageview/pageview.ts @@ -7,6 +7,18 @@ export type Pageview = GtagConfigParams; export type PageviewParams = string | Route | Pageview; +const UTM_PREFIX = "utm_"; + +function extractUtmParams(url: URL): Record { + const params: Record = {}; + + url.searchParams.forEach((value, key) => { + params[key.replace(UTM_PREFIX, "")] = value; + }); + + return params; +} + function getPathWithBase(path: string, base: string): string { const normalizedBase = base.endsWith("/") ? base : `${base}/`; const normalizedPath = path.startsWith("/") ? path.substring(1) : path; @@ -49,14 +61,16 @@ export function pageview(params: PageviewParams) { template.page_path = template.page_path.slice(0, -1); } - if (template.page_location?.match(/[?&]utm_/)) { + const utmRegex = new RegExp(`[?&]${UTM_PREFIX}`); + + if (template.page_location?.match(utmRegex)) { const url = new URL(template.page_location); - const utmParams = Object.fromEntries(url.searchParams.entries()); + const campaignParams = extractUtmParams(url); url.search = ""; template.page_location = url.toString(); - set("campaign", utmParams); + set("campaign", campaignParams); } query("event", "page_view", template); From f6a30f375ddd8cf9d9804541a9152fa9dc3d5da7 Mon Sep 17 00:00:00 2001 From: MatteoGabriele Date: Thu, 3 Apr 2025 19:12:19 +0200 Subject: [PATCH 05/16] chore: remove tscheck for build --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index 119d68f1..96070713 100644 --- a/package.json +++ b/package.json @@ -16,7 +16,7 @@ "dev": "vite build --watch", "fix": "biome check --write", "lint": "biome lint", - "build": "tsc --noEmit && vite build", + "build": "vite build", "typecheck": "tsc --noEmit", "test": "vitest", "test:cov": "vitest run --coverage", From 4f43f9df0239c404e407ef4e1890b055cce62d05 Mon Sep 17 00:00:00 2001 From: MatteoGabriele Date: Thu, 3 Apr 2025 21:51:25 +0200 Subject: [PATCH 06/16] feat: clean url from removed utm params --- src/api/pageview/pageview.test.ts | 4 +-- src/api/pageview/pageview.ts | 33 ++++--------------------- src/utils.ts | 41 +++++++++++++++++++++++++++++++ 3 files changed, 48 insertions(+), 30 deletions(-) diff --git a/src/api/pageview/pageview.test.ts b/src/api/pageview/pageview.test.ts index 11ba14fa..2cf2ec79 100644 --- a/src/api/pageview/pageview.test.ts +++ b/src/api/pageview/pageview.test.ts @@ -124,7 +124,7 @@ describe("pageview", () => { pageview({ page_path: "/", page_location: - "http://localhost:3000/?utm_source=google&utm_medium=cpc&utm_campaign=summer_sale", + "http://localhost:3000/?foo=1&utm_source=google&utm_medium=cpc&utm_campaign=summer_sale&bar=2", }); expect(query).toHaveBeenNthCalledWith(1, "set", "campaign", { @@ -139,7 +139,7 @@ describe("pageview", () => { "page_view", expect.objectContaining({ page_path: "/", - page_location: "http://localhost:3000/", + page_location: "http://localhost:3000/?foo=1&bar=2", }), ); }); diff --git a/src/api/pageview/pageview.ts b/src/api/pageview/pageview.ts index 891f622a..97152d46 100644 --- a/src/api/pageview/pageview.ts +++ b/src/api/pageview/pageview.ts @@ -2,30 +2,11 @@ import { query } from "@/api/query"; import { set } from "@/api/set"; import { type Route, getSettings } from "@/core/settings"; import type { GtagConfigParams } from "@/types/gtag"; +import { getPathWithBase, hasUtmParams, useUtmParams } from "@/utils"; export type Pageview = GtagConfigParams; - export type PageviewParams = string | Route | Pageview; -const UTM_PREFIX = "utm_"; - -function extractUtmParams(url: URL): Record { - const params: Record = {}; - - url.searchParams.forEach((value, key) => { - params[key.replace(UTM_PREFIX, "")] = value; - }); - - return params; -} - -function getPathWithBase(path: string, base: string): string { - const normalizedBase = base.endsWith("/") ? base : `${base}/`; - const normalizedPath = path.startsWith("/") ? path.substring(1) : path; - - return `${normalizedBase}${normalizedPath}`; -} - export function pageview(params: PageviewParams) { const { pageTracker } = getSettings(); @@ -61,16 +42,12 @@ export function pageview(params: PageviewParams) { template.page_path = template.page_path.slice(0, -1); } - const utmRegex = new RegExp(`[?&]${UTM_PREFIX}`); - - if (template.page_location?.match(utmRegex)) { - const url = new URL(template.page_location); - const campaignParams = extractUtmParams(url); + if (hasUtmParams(template.page_location)) { + const { utmParams, cleanUrl } = useUtmParams(template.page_location); - url.search = ""; - template.page_location = url.toString(); + template.page_location = cleanUrl; - set("campaign", campaignParams); + set("campaign", utmParams); } query("event", "page_view", template); diff --git a/src/utils.ts b/src/utils.ts index 903a85a9..16d4d4a8 100644 --- a/src/utils.ts +++ b/src/utils.ts @@ -79,3 +79,44 @@ export function deepMerge( return output as T & U; } + +type UseUtmParams = { + utmParams: Record; + cleanUrl: string; +}; + +const UTM_PREFIX = "utm_"; + +export function hasUtmParams(pageviewUrl: string): boolean { + const utmRegex = new RegExp(`[?&]${UTM_PREFIX}`); + return !!pageviewUrl.match(utmRegex); +} + +export function useUtmParams(pageviewUrl: string): UseUtmParams { + const url = new URL(pageviewUrl); + const utmParams: Record = {}; + const params: string[] = []; + + url.searchParams.forEach((value, key) => { + if (key.includes(UTM_PREFIX)) { + utmParams[key.replace(UTM_PREFIX, "")] = value; + params.push(key); + } + }); + + for (const utmParam of params) { + url.searchParams.delete(utmParam); + } + + return { + utmParams, + cleanUrl: url.toString(), + }; +} + +export function getPathWithBase(path: string, base: string): string { + const normalizedBase = base.endsWith("/") ? base : `${base}/`; + const normalizedPath = path.startsWith("/") ? path.substring(1) : path; + + return `${normalizedBase}${normalizedPath}`; +} From 360897c340f5e53fdd7360ba243493d22933336a Mon Sep 17 00:00:00 2001 From: MatteoGabriele Date: Thu, 3 Apr 2025 22:00:53 +0200 Subject: [PATCH 07/16] refactor: rename variables --- src/utils.ts | 24 ++++++++++++------------ 1 file changed, 12 insertions(+), 12 deletions(-) diff --git a/src/utils.ts b/src/utils.ts index 16d4d4a8..e8e9b548 100644 --- a/src/utils.ts +++ b/src/utils.ts @@ -80,24 +80,19 @@ export function deepMerge( return output as T & U; } +const UTM_PREFIX = "utm_"; + type UseUtmParams = { utmParams: Record; cleanUrl: string; }; -const UTM_PREFIX = "utm_"; - -export function hasUtmParams(pageviewUrl: string): boolean { - const utmRegex = new RegExp(`[?&]${UTM_PREFIX}`); - return !!pageviewUrl.match(utmRegex); -} - -export function useUtmParams(pageviewUrl: string): UseUtmParams { - const url = new URL(pageviewUrl); +export function useUtmParams(url: string): UseUtmParams { + const urlObject = new URL(url); const utmParams: Record = {}; const params: string[] = []; - url.searchParams.forEach((value, key) => { + urlObject.searchParams.forEach((value, key) => { if (key.includes(UTM_PREFIX)) { utmParams[key.replace(UTM_PREFIX, "")] = value; params.push(key); @@ -105,15 +100,20 @@ export function useUtmParams(pageviewUrl: string): UseUtmParams { }); for (const utmParam of params) { - url.searchParams.delete(utmParam); + urlObject.searchParams.delete(utmParam); } return { utmParams, - cleanUrl: url.toString(), + cleanUrl: urlObject.toString(), }; } +export function hasUtmParams(url: string): boolean { + const utmRegex = new RegExp(`[?&]${UTM_PREFIX}`); + return !!url.match(utmRegex); +} + export function getPathWithBase(path: string, base: string): string { const normalizedBase = base.endsWith("/") ? base : `${base}/`; const normalizedPath = path.startsWith("/") ? path.substring(1) : path; From 3fb5fa9e9488b43fe0db732d0800017c5b98c816 Mon Sep 17 00:00:00 2001 From: MatteoGabriele Date: Fri, 4 Apr 2025 00:25:41 +0200 Subject: [PATCH 08/16] refactor: use route location as string --- src/api/pageview/pageview.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/api/pageview/pageview.ts b/src/api/pageview/pageview.ts index 97152d46..1379e367 100644 --- a/src/api/pageview/pageview.ts +++ b/src/api/pageview/pageview.ts @@ -21,7 +21,7 @@ export function pageview(params: PageviewParams) { const path = pageTracker?.useRouteFullPath ? params.fullPath : params.path; template = { - ...(params.name ? { page_title: params.name as string } : {}), + ...(params.name ? { page_title: params.name.toString() } : {}), page_path: pageTracker?.useRouterBasePath ? getPathWithBase(path, base) : path, From efdae538e1d6a6636801c91fb0ebd95b30c097dc Mon Sep 17 00:00:00 2001 From: MatteoGabriele Date: Sat, 5 Apr 2025 22:33:43 +0200 Subject: [PATCH 09/16] feat: add useUtmTracking setting --- src/api/pageview/pageview.test.ts | 25 ++++++++++++++++++++++++- src/api/pageview/pageview.ts | 4 ++-- src/core/settings/types.ts | 6 ++++++ 3 files changed, 32 insertions(+), 3 deletions(-) diff --git a/src/api/pageview/pageview.test.ts b/src/api/pageview/pageview.test.ts index 2cf2ec79..385dae07 100644 --- a/src/api/pageview/pageview.test.ts +++ b/src/api/pageview/pageview.test.ts @@ -120,7 +120,30 @@ describe("pageview", () => { ); }); - it("should send utm parameters manually outside the page_view event", () => { + it("should send utm parameters within the page_view event", () => { + const pageLocation = + "http://localhost:3000/?foo=1&utm_source=google&utm_medium=cpc&utm_campaign=summer_sale&bar=2"; + + pageview({ + page_path: "/", + page_location: pageLocation, + }); + + expect(query).toHaveBeenCalledWith( + "event", + "page_view", + expect.objectContaining({ + page_path: "/", + page_location: pageLocation, + }), + ); + }); + + it("should send utm parameters manually with custom set command", () => { + updateSettings({ + useUtmTracking: true, + }); + pageview({ page_path: "/", page_location: diff --git a/src/api/pageview/pageview.ts b/src/api/pageview/pageview.ts index 1379e367..4f287314 100644 --- a/src/api/pageview/pageview.ts +++ b/src/api/pageview/pageview.ts @@ -8,7 +8,7 @@ export type Pageview = GtagConfigParams; export type PageviewParams = string | Route | Pageview; export function pageview(params: PageviewParams) { - const { pageTracker } = getSettings(); + const { useUtmTracking, pageTracker } = getSettings(); let template: PageviewParams | undefined; @@ -42,7 +42,7 @@ export function pageview(params: PageviewParams) { template.page_path = template.page_path.slice(0, -1); } - if (hasUtmParams(template.page_location)) { + if (useUtmTracking && hasUtmParams(template.page_location)) { const { utmParams, cleanUrl } = useUtmParams(template.page_location); template.page_location = cleanUrl; diff --git a/src/core/settings/types.ts b/src/core/settings/types.ts index 03b5563a..c1781657 100644 --- a/src/core/settings/types.ts +++ b/src/core/settings/types.ts @@ -205,6 +205,12 @@ export type Settings = { * Default value for `app_name` when using the `screen_view` tracking method. */ appName?: string; + + /** + * When enabled, automatically captures UTM parameters from URLs + * and sends them as separate tracking events. + */ + useUtmTracking?: boolean; }; export type PluginSettings = Partial & From 145241432774643d11822b2b0b3adf0b777447c8 Mon Sep 17 00:00:00 2001 From: MatteoGabriele Date: Sun, 6 Apr 2025 10:56:38 +0200 Subject: [PATCH 10/16] fix: utm_campaign as id --- src/api/pageview/pageview.ts | 1 + src/utils.ts | 2 +- 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/src/api/pageview/pageview.ts b/src/api/pageview/pageview.ts index 4f287314..8b891a22 100644 --- a/src/api/pageview/pageview.ts +++ b/src/api/pageview/pageview.ts @@ -46,6 +46,7 @@ export function pageview(params: PageviewParams) { const { utmParams, cleanUrl } = useUtmParams(template.page_location); template.page_location = cleanUrl; + window.location.href = cleanUrl; set("campaign", utmParams); } diff --git a/src/utils.ts b/src/utils.ts index e8e9b548..dae9a5d9 100644 --- a/src/utils.ts +++ b/src/utils.ts @@ -94,7 +94,7 @@ export function useUtmParams(url: string): UseUtmParams { urlObject.searchParams.forEach((value, key) => { if (key.includes(UTM_PREFIX)) { - utmParams[key.replace(UTM_PREFIX, "")] = value; + utmParams[key.replace(UTM_PREFIX, "").replace("campaign", "id")] = value; params.push(key); } }); From 1d8b2c282eb366e9013549e30cce7ab8324a1c10 Mon Sep 17 00:00:00 2001 From: MatteoGabriele Date: Fri, 2 May 2025 12:29:16 +0200 Subject: [PATCH 11/16] test: update expected result --- src/tests/api/pageview.test.ts | 11 ++++++++++- 1 file changed, 10 insertions(+), 1 deletion(-) diff --git a/src/tests/api/pageview.test.ts b/src/tests/api/pageview.test.ts index 88ea1cad..68bcc861 100644 --- a/src/tests/api/pageview.test.ts +++ b/src/tests/api/pageview.test.ts @@ -144,6 +144,15 @@ describe("pageview", () => { useUtmTracking: true, }); + const originalHref = window.location.href; + Object.defineProperty(window, 'location', { + value: { + ...window.location, + href: originalHref, + }, + writable: true + }); + pageview({ page_path: "/", page_location: @@ -153,7 +162,7 @@ describe("pageview", () => { expect(query).toHaveBeenNthCalledWith(1, "set", "campaign", { source: "google", medium: "cpc", - campaign: "summer_sale", + id: "summer_sale", }); expect(query).toHaveBeenNthCalledWith( From c508d2f5ee78d6be0cc0ee40de9feec9b972dde8 Mon Sep 17 00:00:00 2001 From: MatteoGabriele Date: Fri, 2 May 2025 14:34:43 +0200 Subject: [PATCH 12/16] feat: clean url --- package.json | 4 +--- src/api/pageview.ts | 11 ++++++++--- src/tests/api/pageview.test.ts | 36 +++++++++++++++++++++++++--------- src/utils.ts | 9 ++++++++- 4 files changed, 44 insertions(+), 16 deletions(-) diff --git a/package.json b/package.json index 021fc3fa..54cc860a 100644 --- a/package.json +++ b/package.json @@ -47,9 +47,7 @@ "main": "./dist/vue-gtag.js", "module": "./dist/vue-gtag.js", "types": "./dist/vue-gtag.d.ts", - "files": [ - "dist" - ], + "files": ["dist"], "bugs": { "url": "https://github.com/MatteoGabriele/vue-gtag/issues" }, diff --git a/src/api/pageview.ts b/src/api/pageview.ts index d4a5306a..fea1fc30 100644 --- a/src/api/pageview.ts +++ b/src/api/pageview.ts @@ -1,8 +1,8 @@ import { query } from "@/api/query"; import { set } from "@/api/set"; -import type { Route } from "@/types/settings"; import { getSettings } from "@/core/settings"; import type { GtagConfigParams } from "@/types/gtag"; +import type { Route } from "@/types/settings"; import { getPathWithBase, hasUtmParams, useUtmParams } from "@/utils"; export type Pageview = GtagConfigParams; @@ -44,10 +44,15 @@ export function pageview(params: PageviewParams) { } if (useUtmTracking && hasUtmParams(template.page_location)) { - const { utmParams, cleanUrl } = useUtmParams(template.page_location); + const { utmParams, cleanUrl, cleanQueryParams } = useUtmParams( + template.page_location, + ); template.page_location = cleanUrl; - window.location.href = cleanUrl; + + pageTracker?.router.replace({ + query: cleanQueryParams, + }); set("campaign", utmParams); } diff --git a/src/tests/api/pageview.test.ts b/src/tests/api/pageview.test.ts index 68bcc861..be475997 100644 --- a/src/tests/api/pageview.test.ts +++ b/src/tests/api/pageview.test.ts @@ -19,6 +19,7 @@ describe("pageview", () => { }); vi.spyOn(router, "isReady").mockResolvedValue(); + vi.spyOn(router, "replace"); await router.isReady(); await router.push({ name: "about", query: { id: 1 }, hash: "#title" }); @@ -144,15 +145,6 @@ describe("pageview", () => { useUtmTracking: true, }); - const originalHref = window.location.href; - Object.defineProperty(window, 'location', { - value: { - ...window.location, - href: originalHref, - }, - writable: true - }); - pageview({ page_path: "/", page_location: @@ -183,6 +175,32 @@ describe("pageview", () => { }); }); + it("should clear the query from utm params", async () => { + updateSettings({ + useUtmTracking: true, + pageTracker: { router }, + }); + + await router.push({ + query: { + foo: "2", + utm_source: "google", + utm_medium: "cpc", + utm_campaign: "summer_sale", + bar: "2", + }, + }); + + pageview(router.currentRoute.value); + + expect(router.replace).toHaveBeenCalledWith({ + query: { + foo: "2", + bar: "2", + }, + }); + }); + it("should track a page path using a route", () => { pageview(router.currentRoute.value); diff --git a/src/utils.ts b/src/utils.ts index dae9a5d9..8d985444 100644 --- a/src/utils.ts +++ b/src/utils.ts @@ -82,8 +82,10 @@ export function deepMerge( const UTM_PREFIX = "utm_"; +type QueryParams = Record; type UseUtmParams = { - utmParams: Record; + utmParams: QueryParams; + cleanQueryParams: QueryParams; cleanUrl: string; }; @@ -91,11 +93,15 @@ export function useUtmParams(url: string): UseUtmParams { const urlObject = new URL(url); const utmParams: Record = {}; const params: string[] = []; + const cleanQueryParams: Record = {}; urlObject.searchParams.forEach((value, key) => { if (key.includes(UTM_PREFIX)) { + // Replace "campaign" with "id" to match Google Analytics campaign parameter naming utmParams[key.replace(UTM_PREFIX, "").replace("campaign", "id")] = value; params.push(key); + } else { + cleanQueryParams[key] = value; } }); @@ -105,6 +111,7 @@ export function useUtmParams(url: string): UseUtmParams { return { utmParams, + cleanQueryParams, cleanUrl: urlObject.toString(), }; } From 4b617b3b8289e1f95d70b3044df09caa69ecc57f Mon Sep 17 00:00:00 2001 From: MatteoGabriele Date: Fri, 2 May 2025 14:39:26 +0200 Subject: [PATCH 13/16] test: remove unused prop --- src/tests/api/pageview.test.ts | 1 - 1 file changed, 1 deletion(-) diff --git a/src/tests/api/pageview.test.ts b/src/tests/api/pageview.test.ts index be475997..64f75249 100644 --- a/src/tests/api/pageview.test.ts +++ b/src/tests/api/pageview.test.ts @@ -178,7 +178,6 @@ describe("pageview", () => { it("should clear the query from utm params", async () => { updateSettings({ useUtmTracking: true, - pageTracker: { router }, }); await router.push({ From 2c562fb5155547dcf160f474578d95d20787c3da Mon Sep 17 00:00:00 2001 From: MatteoGabriele Date: Sat, 3 May 2025 11:39:23 +0200 Subject: [PATCH 14/16] refactor(pageview): use history.replaceState --- src/api/pageview.ts | 11 +++--- src/tests/api/pageview.test.ts | 12 +++---- src/utils.test.ts | 63 ++++++++++++++++++++++++++++++++++ src/utils.ts | 16 +++++++++ 4 files changed, 92 insertions(+), 10 deletions(-) diff --git a/src/api/pageview.ts b/src/api/pageview.ts index fea1fc30..dff66767 100644 --- a/src/api/pageview.ts +++ b/src/api/pageview.ts @@ -3,7 +3,12 @@ import { set } from "@/api/set"; import { getSettings } from "@/core/settings"; import type { GtagConfigParams } from "@/types/gtag"; import type { Route } from "@/types/settings"; -import { getPathWithBase, hasUtmParams, useUtmParams } from "@/utils"; +import { + getPathWithBase, + hasUtmParams, + urlQueryReplace, + useUtmParams, +} from "@/utils"; export type Pageview = GtagConfigParams; export type PageviewParams = string | Route | Pageview; @@ -50,9 +55,7 @@ export function pageview(params: PageviewParams) { template.page_location = cleanUrl; - pageTracker?.router.replace({ - query: cleanQueryParams, - }); + urlQueryReplace(cleanQueryParams); set("campaign", utmParams); } diff --git a/src/tests/api/pageview.test.ts b/src/tests/api/pageview.test.ts index 64f75249..b2b58f2a 100644 --- a/src/tests/api/pageview.test.ts +++ b/src/tests/api/pageview.test.ts @@ -1,6 +1,7 @@ import { pageview } from "@/api/pageview"; import { query } from "@/api/query"; import { resetSettings, updateSettings } from "@/core/settings"; +import * as utils from "@/utils"; import { type Router, createRouter, createWebHistory } from "vue-router"; vi.mock("@/api/query"); @@ -11,6 +12,8 @@ describe("pageview", () => { beforeEach(async () => { resetSettings(); + vi.spyOn(utils, "urlQueryReplace"); + router = createRouter({ history: createWebHistory("/base-path"), routes: [ @@ -19,7 +22,6 @@ describe("pageview", () => { }); vi.spyOn(router, "isReady").mockResolvedValue(); - vi.spyOn(router, "replace"); await router.isReady(); await router.push({ name: "about", query: { id: 1 }, hash: "#title" }); @@ -192,11 +194,9 @@ describe("pageview", () => { pageview(router.currentRoute.value); - expect(router.replace).toHaveBeenCalledWith({ - query: { - foo: "2", - bar: "2", - }, + expect(utils.urlQueryReplace).toHaveBeenCalledWith({ + foo: "2", + bar: "2", }); }); diff --git a/src/utils.test.ts b/src/utils.test.ts index df1dc67c..b673b221 100644 --- a/src/utils.test.ts +++ b/src/utils.test.ts @@ -73,4 +73,67 @@ describe("utils", () => { expect(scripts[0].getAttribute("type")).toEqual("text/partytown"); }); }); + + describe("urlQueryReplace", () => { + const originalLocation = window.location; + const originalHistory = window.history; + + beforeEach(() => { + Object.defineProperty(window, "location", { + value: { + ...originalLocation, + href: "https://example.com/page?oldParam=value&utm_source=test", + }, + writable: true, + }); + + // Mock window.history.replaceState + window.history.replaceState = vi.fn(); + }); + + afterEach(() => { + // Restore window.location + Object.defineProperty(window, "location", { + value: originalLocation, + writable: true, + }); + window.history.replaceState = originalHistory.replaceState; + }); + + it("should replace URL query parameters without page refresh", () => { + const newQueryParams = { + newParam: "newValue", + anotherParam: "anotherValue", + }; + + utils.urlQueryReplace(newQueryParams); + + // Check if history.replaceState was called correctly + expect(window.history.replaceState).toHaveBeenCalledWith( + {}, + "", + "https://example.com/page?newParam=newValue&anotherParam=anotherValue", + ); + }); + + it("should clear all existing query parameters", () => { + utils.urlQueryReplace({}); + + expect(window.history.replaceState).toHaveBeenCalledWith( + {}, + "", + "https://example.com/page", + ); + }); + + it("should handle empty parameters", () => { + utils.urlQueryReplace({ param: "" }); + + expect(window.history.replaceState).toHaveBeenCalledWith( + {}, + "", + "https://example.com/page?param=", + ); + }); + }); }); diff --git a/src/utils.ts b/src/utils.ts index 8d985444..1820d4b5 100644 --- a/src/utils.ts +++ b/src/utils.ts @@ -80,6 +80,22 @@ export function deepMerge( return output as T & U; } +export function urlQueryReplace(queryParams: Record): void { + if (isServer()) { + return; + } + + const url = new URL(window.location.href); + + url.search = ""; + + for (const [key, value] of Object.entries(queryParams)) { + url.searchParams.set(key, value); + } + + window.history.replaceState({}, "", url.toString()); +} + const UTM_PREFIX = "utm_"; type QueryParams = Record; From 115f8cd6447ed29fe1a376983f975161c9714e57 Mon Sep 17 00:00:00 2001 From: MatteoGabriele Date: Sat, 3 May 2025 11:40:28 +0200 Subject: [PATCH 15/16] refactor: move tests --- src/{ => tests}/index.test.ts | 2 +- src/{ => tests}/utils.test.ts | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) rename src/{ => tests}/index.test.ts (95%) rename src/{ => tests}/utils.test.ts (99%) diff --git a/src/index.test.ts b/src/tests/index.test.ts similarity index 95% rename from src/index.test.ts rename to src/tests/index.test.ts index 0ab9e727..71b97e09 100644 --- a/src/index.test.ts +++ b/src/tests/index.test.ts @@ -1,4 +1,4 @@ -import * as plugin from "./index"; +import * as plugin from "@/index"; describe("index", () => { it("should have the following exports", () => { diff --git a/src/utils.test.ts b/src/tests/utils.test.ts similarity index 99% rename from src/utils.test.ts rename to src/tests/utils.test.ts index b673b221..624acb1c 100644 --- a/src/utils.test.ts +++ b/src/tests/utils.test.ts @@ -1,5 +1,5 @@ +import * as utils from "@/utils"; import flushPromises from "flush-promises"; -import * as utils from "./utils"; const defaultUrl = "https://www.googletagmanager.com/gtag/js?id=12345678"; From 93fdc010f89a8ce17a8629fba072cab3f82bfa75 Mon Sep 17 00:00:00 2001 From: MatteoGabriele Date: Sun, 4 May 2025 18:24:07 +0200 Subject: [PATCH 16/16] refactor: utm handling is not optional --- src/api/pageview.ts | 4 ++-- src/tests/api/pageview.test.ts | 29 +---------------------------- src/types/settings.ts | 6 ------ 3 files changed, 3 insertions(+), 36 deletions(-) diff --git a/src/api/pageview.ts b/src/api/pageview.ts index dff66767..9c529f10 100644 --- a/src/api/pageview.ts +++ b/src/api/pageview.ts @@ -14,7 +14,7 @@ export type Pageview = GtagConfigParams; export type PageviewParams = string | Route | Pageview; export function pageview(params: PageviewParams) { - const { useUtmTracking, pageTracker } = getSettings(); + const { pageTracker } = getSettings(); let template: PageviewParams | undefined; @@ -48,7 +48,7 @@ export function pageview(params: PageviewParams) { template.page_path = template.page_path.slice(0, -1); } - if (useUtmTracking && hasUtmParams(template.page_location)) { + if (hasUtmParams(template.page_location)) { const { utmParams, cleanUrl, cleanQueryParams } = useUtmParams( template.page_location, ); diff --git a/src/tests/api/pageview.test.ts b/src/tests/api/pageview.test.ts index b2b58f2a..3b7f1349 100644 --- a/src/tests/api/pageview.test.ts +++ b/src/tests/api/pageview.test.ts @@ -123,30 +123,7 @@ describe("pageview", () => { ); }); - it("should send utm parameters within the page_view event", () => { - const pageLocation = - "http://localhost:3000/?foo=1&utm_source=google&utm_medium=cpc&utm_campaign=summer_sale&bar=2"; - - pageview({ - page_path: "/", - page_location: pageLocation, - }); - - expect(query).toHaveBeenCalledWith( - "event", - "page_view", - expect.objectContaining({ - page_path: "/", - page_location: pageLocation, - }), - ); - }); - - it("should send utm parameters manually with custom set command", () => { - updateSettings({ - useUtmTracking: true, - }); - + it("should send utm parameters", () => { pageview({ page_path: "/", page_location: @@ -178,10 +155,6 @@ describe("pageview", () => { }); it("should clear the query from utm params", async () => { - updateSettings({ - useUtmTracking: true, - }); - await router.push({ query: { foo: "2", diff --git a/src/types/settings.ts b/src/types/settings.ts index c1781657..03b5563a 100644 --- a/src/types/settings.ts +++ b/src/types/settings.ts @@ -205,12 +205,6 @@ export type Settings = { * Default value for `app_name` when using the `screen_view` tracking method. */ appName?: string; - - /** - * When enabled, automatically captures UTM parameters from URLs - * and sends them as separate tracking events. - */ - useUtmTracking?: boolean; }; export type PluginSettings = Partial &