diff --git a/src/analytics/ga4.ts b/src/analytics/ga4.ts index a106482..b65aa88 100644 --- a/src/analytics/ga4.ts +++ b/src/analytics/ga4.ts @@ -84,8 +84,11 @@ export function initGoogleAnalytics(): void { if (!id || (!isGA4ProductionHost(host) && !isGA4DebugHost(host))) return; try { window.dataLayer = window.dataLayer || []; - window.gtag = function gtagShim(...args: unknown[]) { - window.dataLayer!.push(args); + // MUST push `arguments`, not a rest-args array — gtag.js's command + // interpreter only processes Arguments objects in dataLayer; plain + // arrays are ignored (container registers but no hits ever flush). + window.gtag = function gtagShim() { + window.dataLayer!.push(arguments); }; window.gtag('js', new Date()); window.gtag('config', id, { diff --git a/test/ga4-analytics.test.mjs b/test/ga4-analytics.test.mjs index 2e7f510..6966ea8 100644 --- a/test/ga4-analytics.test.mjs +++ b/test/ga4-analytics.test.mjs @@ -103,6 +103,14 @@ test('config disables automatic page_view and ads/identity features', () => { assert.match(facade, /debug_mode: isGA4DebugHost/, 'localhost must run debug_mode'); }); +test('dataLayer shim pushes arguments — plain arrays are ignored by gtag.js', () => { + // gtag.js's command interpreter only processes Arguments objects; a + // (...args) => push(args) shim silently drops every command. + const facade = codeOnly('src/analytics/ga4.ts'); + assert.match(facade, /push\(arguments\)/); + assert.doesNotMatch(facade, /push\(args\)/); +}); + test('gtag script loads async from googletagmanager.com only', () => { const facade = src('src/analytics/ga4.ts'); assert.match(facade, /script\.async\s*=\s*true/);