From 959f4b9fb685aad52f55f7e63b28723adc6ccf7c Mon Sep 17 00:00:00 2001 From: Aram Grigoryan <132480+aram356@users.noreply.github.com> Date: Thu, 30 Jul 2026 22:17:29 -0700 Subject: [PATCH 1/6] Lint the full JS package in CI, not just src MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The lint script only covered src/**, so eslint errors in test files and the Node build scripts were invisible to the CI lint gate in format.yml. Widen the script to eslint . and make the widened surface clean: - Disable @typescript-eslint/no-explicit-any for test files — tests routinely poke at private state and mock boundaries via any, and the rule produced 249 errors there; drop the per-line disables it makes redundant. - Declare Node globals for the .mjs build scripts and Node-run test harnesses via the globals package. - Honor the underscore convention for intentionally unused parameters in @typescript-eslint/no-unused-vars. - Fix the remaining import/order and prefer-const violations. --- crates/trusted-server-js/lib/eslint.config.js | 19 +++++++++++ .../trusted-server-js/lib/package-lock.json | 33 ++++++++++--------- crates/trusted-server-js/lib/package.json | 5 +-- .../lib/test/core/auction.test.ts | 1 + .../datadome/script_guard.test.ts | 1 + .../lib/test/integrations/gpt/ad_init.test.ts | 33 ++++++------------- .../integrations/gpt/gpt_bootstrap.test.ts | 1 - .../integrations/lockr/script_guard.test.ts | 1 + .../test/integrations/prebid/index.test.ts | 22 ++++++------- .../lib/test/shared/beacon_guard.test.ts | 1 + crates/trusted-server-js/lib/vitest.config.ts | 1 + 11 files changed, 65 insertions(+), 53 deletions(-) diff --git a/crates/trusted-server-js/lib/eslint.config.js b/crates/trusted-server-js/lib/eslint.config.js index e59d7c1ed..e2e5f6400 100644 --- a/crates/trusted-server-js/lib/eslint.config.js +++ b/crates/trusted-server-js/lib/eslint.config.js @@ -1,5 +1,6 @@ // ESLint v9 flat config import js from '@eslint/js'; +import globals from 'globals'; import tseslint from 'typescript-eslint'; import importPlugin from 'eslint-plugin-import'; import jsdoc from 'eslint-plugin-jsdoc'; @@ -34,6 +35,24 @@ export default [ 'unicorn/prevent-abbreviations': 'off', 'unicorn/filename-case': 'off', 'import/order': ['error', { 'newlines-between': 'always' }], + '@typescript-eslint/no-unused-vars': [ + 'error', + { argsIgnorePattern: '^_', varsIgnorePattern: '^_', caughtErrorsIgnorePattern: '^_' }, + ], + }, + }, + // Node build scripts and Node-run test harnesses + { + files: ['*.mjs', 'test/**/*.mjs'], + languageOptions: { + globals: globals.node, + }, + }, + // Tests routinely poke at private state and mock boundaries via `any` + { + files: ['test/**/*.ts', 'test/**/*.tsx'], + rules: { + '@typescript-eslint/no-explicit-any': 'off', }, }, ]; diff --git a/crates/trusted-server-js/lib/package-lock.json b/crates/trusted-server-js/lib/package-lock.json index ee30a4777..588ccd6be 100644 --- a/crates/trusted-server-js/lib/package-lock.json +++ b/crates/trusted-server-js/lib/package-lock.json @@ -21,6 +21,7 @@ "eslint-plugin-import": "^2.29.1", "eslint-plugin-jsdoc": "^62.5.4", "eslint-plugin-unicorn": "^62.0.0", + "globals": "^16.0.0", "jsdom": "^28.0.0", "prettier": "^3.2.5", "typescript": "^5.5.4", @@ -2373,6 +2374,19 @@ "concat-map": "0.0.1" } }, + "node_modules/@eslint/eslintrc/node_modules/globals": { + "version": "14.0.0", + "resolved": "https://registry.npmjs.org/globals/-/globals-14.0.0.tgz", + "integrity": "sha512-oahGvuMGQlPw/ivIYBjVSrWAfWLBeku5tpPE2fOPLi+WHffIWbuh2tCjhyQhTBPMf5E9jDEH4FOmTYgYwbKwtQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, "node_modules/@eslint/eslintrc/node_modules/ignore": { "version": "5.3.2", "resolved": "https://registry.npmjs.org/ignore/-/ignore-5.3.2.tgz", @@ -5005,19 +5019,6 @@ "eslint": ">=9.38.0" } }, - "node_modules/eslint-plugin-unicorn/node_modules/globals": { - "version": "16.5.0", - "resolved": "https://registry.npmjs.org/globals/-/globals-16.5.0.tgz", - "integrity": "sha512-c/c15i26VrJ4IRt5Z89DnIzCGDn9EcebibhAOjw5ibqEHsE1wLUgkPn9RDmNcUKyU87GeaL633nyJ+pplFR2ZQ==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=18" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, "node_modules/eslint-scope": { "version": "8.4.0", "resolved": "https://registry.npmjs.org/eslint-scope/-/eslint-scope-8.4.0.tgz", @@ -5632,9 +5633,9 @@ } }, "node_modules/globals": { - "version": "14.0.0", - "resolved": "https://registry.npmjs.org/globals/-/globals-14.0.0.tgz", - "integrity": "sha512-oahGvuMGQlPw/ivIYBjVSrWAfWLBeku5tpPE2fOPLi+WHffIWbuh2tCjhyQhTBPMf5E9jDEH4FOmTYgYwbKwtQ==", + "version": "16.5.0", + "resolved": "https://registry.npmjs.org/globals/-/globals-16.5.0.tgz", + "integrity": "sha512-c/c15i26VrJ4IRt5Z89DnIzCGDn9EcebibhAOjw5ibqEHsE1wLUgkPn9RDmNcUKyU87GeaL633nyJ+pplFR2ZQ==", "dev": true, "license": "MIT", "engines": { diff --git a/crates/trusted-server-js/lib/package.json b/crates/trusted-server-js/lib/package.json index 2ffed57e7..955e21dfc 100644 --- a/crates/trusted-server-js/lib/package.json +++ b/crates/trusted-server-js/lib/package.json @@ -10,8 +10,8 @@ "dev": "vite build --watch", "test": "vitest run", "test:watch": "vitest", - "lint": "eslint \"src/**/*.{ts,tsx}\"", - "lint:fix": "eslint --fix \"src/**/*.{ts,tsx}\"", + "lint": "eslint .", + "lint:fix": "eslint --fix .", "format": "prettier --check \"**/*.{ts,tsx,js,json,css,md}\"", "format:write": "prettier --write \"**/*.{ts,tsx,js,json,css,md}\"" }, @@ -29,6 +29,7 @@ "eslint-plugin-import": "^2.29.1", "eslint-plugin-jsdoc": "^62.5.4", "eslint-plugin-unicorn": "^62.0.0", + "globals": "^16.0.0", "jsdom": "^28.0.0", "prettier": "^3.2.5", "typescript": "^5.5.4", diff --git a/crates/trusted-server-js/lib/test/core/auction.test.ts b/crates/trusted-server-js/lib/test/core/auction.test.ts index 31e020eff..16f18d6bd 100644 --- a/crates/trusted-server-js/lib/test/core/auction.test.ts +++ b/crates/trusted-server-js/lib/test/core/auction.test.ts @@ -1,4 +1,5 @@ import { describe, it, expect, vi, beforeEach, afterEach } from 'vitest'; + import { buildAdRequest, parseAuctionResponse, sendAuction } from '../../src/core/auction'; describe('auction/buildAdRequest', () => { diff --git a/crates/trusted-server-js/lib/test/integrations/datadome/script_guard.test.ts b/crates/trusted-server-js/lib/test/integrations/datadome/script_guard.test.ts index 795b442a6..70fe191fd 100644 --- a/crates/trusted-server-js/lib/test/integrations/datadome/script_guard.test.ts +++ b/crates/trusted-server-js/lib/test/integrations/datadome/script_guard.test.ts @@ -1,4 +1,5 @@ import { describe, it, expect, beforeEach, afterEach } from 'vitest'; + import { installDataDomeGuard, isGuardInstalled, diff --git a/crates/trusted-server-js/lib/test/integrations/gpt/ad_init.test.ts b/crates/trusted-server-js/lib/test/integrations/gpt/ad_init.test.ts index 257d7fc2c..5a2871e7e 100644 --- a/crates/trusted-server-js/lib/test/integrations/gpt/ad_init.test.ts +++ b/crates/trusted-server-js/lib/test/integrations/gpt/ad_init.test.ts @@ -49,7 +49,6 @@ type TestWindow = Window & { // Typed as `any` to avoid the TypeScript intersection with the global // Window.tsjs declaration (TsjsApi from core/types.ts), which would require // every test fixture to satisfy the full TsjsApi shape. - // eslint-disable-next-line @typescript-eslint/no-explicit-any tsjs?: any; }; @@ -142,7 +141,6 @@ describe('installTsAdInit', () => { burl: 'https://ssp/bill', }, }, - // eslint-disable-next-line @typescript-eslint/no-explicit-any } as any; const fetchSpy = vi.spyOn(global, 'fetch'); @@ -198,7 +196,6 @@ describe('installTsAdInit', () => { }, ], bids: {}, - // eslint-disable-next-line @typescript-eslint/no-explicit-any } as any; const { installTsAdInit } = await import('../../../src/integrations/gpt/index'); @@ -253,7 +250,6 @@ describe('installTsAdInit', () => { }, ], bids: {}, - // eslint-disable-next-line @typescript-eslint/no-explicit-any } as any; const { installTsAdInit } = await import('../../../src/integrations/gpt/index'); @@ -307,7 +303,6 @@ describe('installTsAdInit', () => { }, ], bids: {}, - // eslint-disable-next-line @typescript-eslint/no-explicit-any } as any; await runGptBootstrap(googletag); @@ -364,7 +359,6 @@ describe('installTsAdInit', () => { }, ], bids: {}, - // eslint-disable-next-line @typescript-eslint/no-explicit-any } as any; await runGptBootstrap(googletag); @@ -457,7 +451,6 @@ describe('installTsAdInit', () => { }, ], bids: {}, - // eslint-disable-next-line @typescript-eslint/no-explicit-any } as any; const { installTsAdInit } = await import('../../../src/integrations/gpt/index'); @@ -567,7 +560,6 @@ describe('installTsAdInit', () => { }, ], bids: {}, - // eslint-disable-next-line @typescript-eslint/no-explicit-any } as any; const { installTsAdInit } = await import('../../../src/integrations/gpt/index'); @@ -617,7 +609,6 @@ describe('installTsAdInit', () => { }, ], bids: {}, - // eslint-disable-next-line @typescript-eslint/no-explicit-any } as any; const { installTsAdInit } = await import('../../../src/integrations/gpt/index'); @@ -657,7 +648,6 @@ describe('installTsAdInit', () => { // Previous route touched the publisher-owned slot on div-old-route. divToSlotId: { 'div-old-route': 'old_slot' }, prevSlotTargetingKeys: { 'div-old-route': ['pos'] }, - // eslint-disable-next-line @typescript-eslint/no-explicit-any } as any; const { installTsAdInit } = await import('../../../src/integrations/gpt/index'); @@ -697,7 +687,6 @@ describe('installTsAdInit', () => { (window as TestWindow).tsjs = { adSlots: [], bids: {}, - // eslint-disable-next-line @typescript-eslint/no-explicit-any } as any; const { installTsAdInit } = await import('../../../src/integrations/gpt/index'); @@ -1102,7 +1091,6 @@ describe('installTsAdInit', () => { bids: { atf_sidebar_ad: { hb_pb: '1.50', hb_bidder: 'aps', nurl: '', burl: '' }, }, - // eslint-disable-next-line @typescript-eslint/no-explicit-any } as any; const { installTsAdInit } = await import('../../../src/integrations/gpt/index'); @@ -1149,7 +1137,6 @@ describe('installTsAdInit', () => { bids: { atf_sidebar_ad: { hb_pb: '1.00', hb_bidder: 'kargo' }, }, - // eslint-disable-next-line @typescript-eslint/no-explicit-any } as any; const { installTsAdInit } = await import('../../../src/integrations/gpt/index'); @@ -1354,7 +1341,7 @@ describe('installTsRenderBridge', () => { .mockImplementation( (type: string, handler: EventListenerOrEventListenerObject, opts?: unknown) => { if (type === 'message') bridgeListener = handler as (e: MessageEvent) => unknown; - // eslint-disable-next-line @typescript-eslint/no-explicit-any + origAdd(type, handler as EventListener, opts as any); } ); @@ -1384,7 +1371,7 @@ describe('installTsRenderBridge', () => { .mockImplementation( (type: string, handler: EventListenerOrEventListenerObject, opts?: unknown) => { if (type === 'message') bridgeListener = handler as (e: MessageEvent) => unknown; - // eslint-disable-next-line @typescript-eslint/no-explicit-any + origAdd(type, handler as EventListener, opts as any); } ); @@ -1418,7 +1405,7 @@ describe('installTsRenderBridge', () => { ); expect(stopSpy).toHaveBeenCalled(); expect(portMessages).toHaveLength(1); - // eslint-disable-next-line @typescript-eslint/no-explicit-any + const parsed = JSON.parse(portMessages[0]) as Record; expect(parsed.message).toBe('Prebid Response'); expect(parsed.adId).toBe('test-cache-uuid'); @@ -1501,7 +1488,7 @@ describe('installTsRenderBridge', () => { await new Promise((resolve) => setTimeout(resolve, 50)); expect(portMessages).toHaveLength(1); - // eslint-disable-next-line @typescript-eslint/no-explicit-any + const parsed = JSON.parse(portMessages[0]) as Record; expect(parsed.ad).toBe(rawAd); expect(beaconSpy).toHaveBeenCalledTimes(2); @@ -1533,7 +1520,7 @@ describe('installTsRenderBridge', () => { await new Promise((resolve) => setTimeout(resolve, 50)); expect(portMessages).toHaveLength(1); - // eslint-disable-next-line @typescript-eslint/no-explicit-any + const parsed = JSON.parse(portMessages[0]) as Record; expect(parsed.width).toBe(300); expect(parsed.height).toBe(250); @@ -1569,7 +1556,7 @@ describe('installTsRenderBridge', () => { await new Promise((resolve) => setTimeout(resolve, 50)); expect(portMessages).toHaveLength(1); - // eslint-disable-next-line @typescript-eslint/no-explicit-any + const parsed = JSON.parse(portMessages[0]) as Record; expect(parsed.ad).toContain('p=2.5'); expect(parsed.ad).not.toContain('${AUCTION_PRICE}'); @@ -1741,7 +1728,7 @@ describe('installTsRenderBridge', () => { .mockImplementation( (type: string, handler: EventListenerOrEventListenerObject, opts?: unknown) => { if (type === 'message') bridgeListener = handler as (e: MessageEvent) => unknown; - // eslint-disable-next-line @typescript-eslint/no-explicit-any + origAdd(type, handler as EventListener, opts as any); } ); @@ -1769,7 +1756,7 @@ describe('installTsRenderBridge', () => { expect(fetchStub).not.toHaveBeenCalled(); expect(stopSpy).toHaveBeenCalled(); expect(portMessages).toHaveLength(1); - // eslint-disable-next-line @typescript-eslint/no-explicit-any + const parsed = JSON.parse(portMessages[0]) as Record; expect(parsed.message).toBe('Prebid Response'); expect(parsed.adId).toBe('debug-adid'); @@ -1830,7 +1817,7 @@ describe('installTsRenderBridge', () => { await new Promise((resolve) => setTimeout(resolve, 50)); expect(portMessages).toHaveLength(1); - // eslint-disable-next-line @typescript-eslint/no-explicit-any + const parsed = JSON.parse(portMessages[0]) as Record; expect(parsed.width).toBe(300); expect(parsed.height).toBe(250); @@ -1905,7 +1892,7 @@ describe('installTsRenderBridge', () => { await new Promise((resolve) => setTimeout(resolve, 50)); expect(portMessages).toHaveLength(1); - // eslint-disable-next-line @typescript-eslint/no-explicit-any + const parsed = JSON.parse(portMessages[0]) as Record; // The requesting slot's own creative and dimensions, not the first match's. expect(parsed.ad).toBe(inContentAdm); diff --git a/crates/trusted-server-js/lib/test/integrations/gpt/gpt_bootstrap.test.ts b/crates/trusted-server-js/lib/test/integrations/gpt/gpt_bootstrap.test.ts index 812543a9a..00d83500b 100644 --- a/crates/trusted-server-js/lib/test/integrations/gpt/gpt_bootstrap.test.ts +++ b/crates/trusted-server-js/lib/test/integrations/gpt/gpt_bootstrap.test.ts @@ -20,7 +20,6 @@ const BOOTSTRAP_SOURCE = readFileSync( 'utf8' ); -// eslint-disable-next-line @typescript-eslint/no-explicit-any type AnyRecord = Record; type TestWindow = Window & { diff --git a/crates/trusted-server-js/lib/test/integrations/lockr/script_guard.test.ts b/crates/trusted-server-js/lib/test/integrations/lockr/script_guard.test.ts index b9251b1e1..2f53c06b2 100644 --- a/crates/trusted-server-js/lib/test/integrations/lockr/script_guard.test.ts +++ b/crates/trusted-server-js/lib/test/integrations/lockr/script_guard.test.ts @@ -1,4 +1,5 @@ import { describe, it, expect, beforeEach, afterEach } from 'vitest'; + import { installLockrGuard, isGuardInstalled, diff --git a/crates/trusted-server-js/lib/test/integrations/prebid/index.test.ts b/crates/trusted-server-js/lib/test/integrations/prebid/index.test.ts index 1c7c32bb2..6535d9b3b 100644 --- a/crates/trusted-server-js/lib/test/integrations/prebid/index.test.ts +++ b/crates/trusted-server-js/lib/test/integrations/prebid/index.test.ts @@ -22,7 +22,16 @@ const { () => [] as Array<{ source: string; uids?: Array<{ id: string; atype?: number }> }> ); const mockGetConfig = vi.fn(); - let mockPbjs: { + // Only called after mockPbjs is initialized below, so the forward reference is safe. + const mockRemoveAdUnit = vi.fn((adUnitCode?: string | string[]) => { + if (!adUnitCode) { + mockPbjs.adUnits = []; + return; + } + const codes = new Set(Array.isArray(adUnitCode) ? adUnitCode : [adUnitCode]); + mockPbjs.adUnits = mockPbjs.adUnits.filter((unit) => !codes.has(unit.code)); + }); + const mockPbjs: { setConfig: typeof mockSetConfig; processQueue: typeof mockProcessQueue; requestBids: typeof mockRequestBids; @@ -32,16 +41,7 @@ const { removeAdUnit: ReturnType; adUnits: any[]; [key: string]: any; - }; - const mockRemoveAdUnit = vi.fn((adUnitCode?: string | string[]) => { - if (!adUnitCode) { - mockPbjs.adUnits = []; - return; - } - const codes = new Set(Array.isArray(adUnitCode) ? adUnitCode : [adUnitCode]); - mockPbjs.adUnits = mockPbjs.adUnits.filter((unit) => !codes.has(unit.code)); - }); - mockPbjs = { + } = { setConfig: mockSetConfig, processQueue: mockProcessQueue, requestBids: mockRequestBids, diff --git a/crates/trusted-server-js/lib/test/shared/beacon_guard.test.ts b/crates/trusted-server-js/lib/test/shared/beacon_guard.test.ts index 9ade23382..dc1228c3f 100644 --- a/crates/trusted-server-js/lib/test/shared/beacon_guard.test.ts +++ b/crates/trusted-server-js/lib/test/shared/beacon_guard.test.ts @@ -1,4 +1,5 @@ import { describe, it, expect, beforeEach, afterEach, vi } from 'vitest'; + import { createBeaconGuard, BeaconGuardConfig } from '../../src/shared/beacon_guard'; describe('Beacon Guard', () => { diff --git a/crates/trusted-server-js/lib/vitest.config.ts b/crates/trusted-server-js/lib/vitest.config.ts index 97d9d84c8..db53ad358 100644 --- a/crates/trusted-server-js/lib/vitest.config.ts +++ b/crates/trusted-server-js/lib/vitest.config.ts @@ -1,4 +1,5 @@ import path from 'node:path'; + import { defineConfig } from 'vitest/config'; export default defineConfig({ From 95317a46c0209e4e7635de043199a18e03e27d82 Mon Sep 17 00:00:00 2001 From: Aram Grigoryan <132480+aram356@users.noreply.github.com> Date: Fri, 31 Jul 2026 12:52:33 -0700 Subject: [PATCH 2/6] Address review: ESM-safe Node globals, global underscore convention, fail on warnings - Use globals.nodeBuiltin instead of globals.node for .mjs files so CommonJS-only names (__dirname, require, module) still fail no-undef in ES modules - Lift the no-unused-vars underscore ignore patterns to a top-level rules block so the convention also covers the .mjs build scripts, where tseslint recommended otherwise applies pattern-less defaults - Add --max-warnings=0 to the lint scripts so rules landing at 'warn' cannot pass CI silently - Collapse leftover blank lines in two-line arrow bodies in ad_init.test.ts from the eslint-disable directive removals --- crates/trusted-server-js/lib/eslint.config.js | 13 +++++++++++-- crates/trusted-server-js/lib/package.json | 4 ++-- .../lib/test/integrations/gpt/ad_init.test.ts | 3 --- 3 files changed, 13 insertions(+), 7 deletions(-) diff --git a/crates/trusted-server-js/lib/eslint.config.js b/crates/trusted-server-js/lib/eslint.config.js index e2e5f6400..bbd13302c 100644 --- a/crates/trusted-server-js/lib/eslint.config.js +++ b/crates/trusted-server-js/lib/eslint.config.js @@ -35,17 +35,26 @@ export default [ 'unicorn/prevent-abbreviations': 'off', 'unicorn/filename-case': 'off', 'import/order': ['error', { 'newlines-between': 'always' }], + }, + }, + // Honor the `_`-prefix convention for intentionally unused bindings in every + // linted file — tseslint recommended enables the rule globally, so scoping + // this to *.ts(x) would leave .mjs at the pattern-less defaults + { + rules: { '@typescript-eslint/no-unused-vars': [ 'error', { argsIgnorePattern: '^_', varsIgnorePattern: '^_', caughtErrorsIgnorePattern: '^_' }, ], }, }, - // Node build scripts and Node-run test harnesses + // Node build scripts and Node-run test harnesses — nodeBuiltin, not node, + // so CommonJS-only names (__dirname, require, module) still fail no-undef + // in these ES modules { files: ['*.mjs', 'test/**/*.mjs'], languageOptions: { - globals: globals.node, + globals: globals.nodeBuiltin, }, }, // Tests routinely poke at private state and mock boundaries via `any` diff --git a/crates/trusted-server-js/lib/package.json b/crates/trusted-server-js/lib/package.json index 955e21dfc..427fef1e1 100644 --- a/crates/trusted-server-js/lib/package.json +++ b/crates/trusted-server-js/lib/package.json @@ -10,8 +10,8 @@ "dev": "vite build --watch", "test": "vitest run", "test:watch": "vitest", - "lint": "eslint .", - "lint:fix": "eslint --fix .", + "lint": "eslint . --max-warnings=0", + "lint:fix": "eslint --fix . --max-warnings=0", "format": "prettier --check \"**/*.{ts,tsx,js,json,css,md}\"", "format:write": "prettier --write \"**/*.{ts,tsx,js,json,css,md}\"" }, diff --git a/crates/trusted-server-js/lib/test/integrations/gpt/ad_init.test.ts b/crates/trusted-server-js/lib/test/integrations/gpt/ad_init.test.ts index 5a2871e7e..5699e0f68 100644 --- a/crates/trusted-server-js/lib/test/integrations/gpt/ad_init.test.ts +++ b/crates/trusted-server-js/lib/test/integrations/gpt/ad_init.test.ts @@ -1341,7 +1341,6 @@ describe('installTsRenderBridge', () => { .mockImplementation( (type: string, handler: EventListenerOrEventListenerObject, opts?: unknown) => { if (type === 'message') bridgeListener = handler as (e: MessageEvent) => unknown; - origAdd(type, handler as EventListener, opts as any); } ); @@ -1371,7 +1370,6 @@ describe('installTsRenderBridge', () => { .mockImplementation( (type: string, handler: EventListenerOrEventListenerObject, opts?: unknown) => { if (type === 'message') bridgeListener = handler as (e: MessageEvent) => unknown; - origAdd(type, handler as EventListener, opts as any); } ); @@ -1728,7 +1726,6 @@ describe('installTsRenderBridge', () => { .mockImplementation( (type: string, handler: EventListenerOrEventListenerObject, opts?: unknown) => { if (type === 'message') bridgeListener = handler as (e: MessageEvent) => unknown; - origAdd(type, handler as EventListener, opts as any); } ); From 7aef9a509f6e01826a350d7a9d6af8775e232b0f Mon Sep 17 00:00:00 2001 From: Aram Grigoryan <132480+aram356@users.noreply.github.com> Date: Fri, 31 Jul 2026 14:00:16 -0700 Subject: [PATCH 3/6] Narrow the test no-explicit-any opt-out from blanket to per-file MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Remove the test/** rule-off block so the rule stays live for new test files, and handle the 255 existing occurrences by tier: - File-level eslint-disable in the six files where `any` is structural to the mocking approach (prebid/index, gpt/ad_init, gpt/index, core/request, core/index, didomi/index — 246 occurrences) - Real types where they were cheap: fetch stubs cast via `as unknown as typeof fetch` (auction, click, proxy_sign), `as AdUnit` with a type-only import in registry, and the config cast dropped entirely ('info' is already in the logLevel union) - A scoped eslint-disable-next-line for gpt_bootstrap's AnyRecord alias ESLint 9 reports unused disable directives as warnings by default, so with --max-warnings=0 any directive made redundant later fails CI. --- crates/trusted-server-js/lib/eslint.config.js | 7 ------- crates/trusted-server-js/lib/test/core/auction.test.ts | 10 ++++++---- crates/trusted-server-js/lib/test/core/config.test.ts | 2 +- crates/trusted-server-js/lib/test/core/index.test.ts | 1 + .../trusted-server-js/lib/test/core/registry.test.ts | 4 +++- crates/trusted-server-js/lib/test/core/request.test.ts | 1 + .../lib/test/integrations/creative/click.test.ts | 2 +- .../lib/test/integrations/creative/proxy_sign.test.ts | 2 +- .../lib/test/integrations/didomi/index.test.ts | 1 + .../lib/test/integrations/gpt/ad_init.test.ts | 1 + .../lib/test/integrations/gpt/gpt_bootstrap.test.ts | 1 + .../lib/test/integrations/gpt/index.test.ts | 1 + .../lib/test/integrations/prebid/index.test.ts | 1 + 13 files changed, 19 insertions(+), 15 deletions(-) diff --git a/crates/trusted-server-js/lib/eslint.config.js b/crates/trusted-server-js/lib/eslint.config.js index bbd13302c..4caeda322 100644 --- a/crates/trusted-server-js/lib/eslint.config.js +++ b/crates/trusted-server-js/lib/eslint.config.js @@ -57,11 +57,4 @@ export default [ globals: globals.nodeBuiltin, }, }, - // Tests routinely poke at private state and mock boundaries via `any` - { - files: ['test/**/*.ts', 'test/**/*.tsx'], - rules: { - '@typescript-eslint/no-explicit-any': 'off', - }, - }, ]; diff --git a/crates/trusted-server-js/lib/test/core/auction.test.ts b/crates/trusted-server-js/lib/test/core/auction.test.ts index 16f18d6bd..50f078d0b 100644 --- a/crates/trusted-server-js/lib/test/core/auction.test.ts +++ b/crates/trusted-server-js/lib/test/core/auction.test.ts @@ -248,7 +248,7 @@ describe('auction/sendAuction', () => { ], }), }; - globalThis.fetch = vi.fn().mockResolvedValue(mockResponse) as any; + globalThis.fetch = vi.fn().mockResolvedValue(mockResponse) as unknown as typeof fetch; const request = { adUnits: [ @@ -275,7 +275,9 @@ describe('auction/sendAuction', () => { }); it('returns empty array on network error', async () => { - globalThis.fetch = vi.fn().mockRejectedValue(new Error('network error')) as any; + globalThis.fetch = vi + .fn() + .mockRejectedValue(new Error('network error')) as unknown as typeof fetch; const bids = await sendAuction('/auction', { adUnits: [] }); expect(bids).toEqual([]); @@ -287,7 +289,7 @@ describe('auction/sendAuction', () => { status: 200, headers: { get: () => 'text/html' }, json: async () => ({}), - }) as any; + }) as unknown as typeof fetch; const bids = await sendAuction('/auction', { adUnits: [] }); expect(bids).toEqual([]); @@ -299,7 +301,7 @@ describe('auction/sendAuction', () => { status: 500, headers: { get: () => 'application/json' }, json: async () => ({}), - }) as any; + }) as unknown as typeof fetch; const bids = await sendAuction('/auction', { adUnits: [] }); expect(bids).toEqual([]); diff --git a/crates/trusted-server-js/lib/test/core/config.test.ts b/crates/trusted-server-js/lib/test/core/config.test.ts index 2b15d1929..6abacdf54 100644 --- a/crates/trusted-server-js/lib/test/core/config.test.ts +++ b/crates/trusted-server-js/lib/test/core/config.test.ts @@ -16,7 +16,7 @@ describe('config', () => { setConfig({ debug: true }); expect(log.getLevel()).toBe('debug'); - setConfig({ logLevel: 'info' as any }); + setConfig({ logLevel: 'info' }); expect(log.getLevel()).toBe('info'); }); }); diff --git a/crates/trusted-server-js/lib/test/core/index.test.ts b/crates/trusted-server-js/lib/test/core/index.test.ts index dc77439b1..9817a8c8b 100644 --- a/crates/trusted-server-js/lib/test/core/index.test.ts +++ b/crates/trusted-server-js/lib/test/core/index.test.ts @@ -1,3 +1,4 @@ +/* eslint-disable @typescript-eslint/no-explicit-any -- mocks and private state are poked via `any`; the file-level opt-out is deliberate */ import { describe, it, expect, beforeEach, afterEach, vi } from 'vitest'; declare global { diff --git a/crates/trusted-server-js/lib/test/core/registry.test.ts b/crates/trusted-server-js/lib/test/core/registry.test.ts index 726f67797..f06527e42 100644 --- a/crates/trusted-server-js/lib/test/core/registry.test.ts +++ b/crates/trusted-server-js/lib/test/core/registry.test.ts @@ -1,5 +1,7 @@ import { describe, it, expect, beforeEach, vi } from 'vitest'; +import type { AdUnit } from '../../src/core/types'; + describe('registry', () => { beforeEach(async () => { await vi.resetModules(); @@ -17,7 +19,7 @@ describe('registry', () => { ], }, }, - } as any; + } as AdUnit; addAdUnits(unit); const all = getAllUnits(); diff --git a/crates/trusted-server-js/lib/test/core/request.test.ts b/crates/trusted-server-js/lib/test/core/request.test.ts index 2c56361dc..274943299 100644 --- a/crates/trusted-server-js/lib/test/core/request.test.ts +++ b/crates/trusted-server-js/lib/test/core/request.test.ts @@ -1,3 +1,4 @@ +/* eslint-disable @typescript-eslint/no-explicit-any -- mocks and private state are poked via `any`; the file-level opt-out is deliberate */ import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest'; async function flushRequestAds(): Promise { diff --git a/crates/trusted-server-js/lib/test/integrations/creative/click.test.ts b/crates/trusted-server-js/lib/test/integrations/creative/click.test.ts index 05dcd0e02..7cf31afa3 100644 --- a/crates/trusted-server-js/lib/test/integrations/creative/click.test.ts +++ b/crates/trusted-server-js/lib/test/integrations/creative/click.test.ts @@ -17,7 +17,7 @@ describe('creative/click.ts', () => { it('repairs anchors via proxy rebuild fallback when fetch is unavailable', async () => { vi.useFakeTimers(); - global.fetch = undefined as any; + global.fetch = undefined as unknown as typeof fetch; const anchor = document.createElement('a'); anchor.setAttribute('data-tsclick', FIRST_PARTY_CLICK); diff --git a/crates/trusted-server-js/lib/test/integrations/creative/proxy_sign.test.ts b/crates/trusted-server-js/lib/test/integrations/creative/proxy_sign.test.ts index 41c86a873..7f31dbed9 100644 --- a/crates/trusted-server-js/lib/test/integrations/creative/proxy_sign.test.ts +++ b/crates/trusted-server-js/lib/test/integrations/creative/proxy_sign.test.ts @@ -47,7 +47,7 @@ describe('creative/proxy_sign.ts', () => { }); it('returns null when fetch is unavailable', async () => { - global.fetch = undefined as any; + global.fetch = undefined as unknown as typeof fetch; const result = await signProxyUrl('https://cdn.example/asset.js'); expect(result).toBeNull(); }); diff --git a/crates/trusted-server-js/lib/test/integrations/didomi/index.test.ts b/crates/trusted-server-js/lib/test/integrations/didomi/index.test.ts index 487ff471f..435f42054 100644 --- a/crates/trusted-server-js/lib/test/integrations/didomi/index.test.ts +++ b/crates/trusted-server-js/lib/test/integrations/didomi/index.test.ts @@ -1,3 +1,4 @@ +/* eslint-disable @typescript-eslint/no-explicit-any -- mocks and private state are poked via `any`; the file-level opt-out is deliberate */ import { afterEach, beforeEach, describe, expect, it } from 'vitest'; import { installDidomiSdkProxy } from '../../../src/integrations/didomi'; diff --git a/crates/trusted-server-js/lib/test/integrations/gpt/ad_init.test.ts b/crates/trusted-server-js/lib/test/integrations/gpt/ad_init.test.ts index 5699e0f68..ba01b889a 100644 --- a/crates/trusted-server-js/lib/test/integrations/gpt/ad_init.test.ts +++ b/crates/trusted-server-js/lib/test/integrations/gpt/ad_init.test.ts @@ -1,3 +1,4 @@ +/* eslint-disable @typescript-eslint/no-explicit-any -- mocks and private state are poked via `any`; the file-level opt-out is deliberate */ import { readFile } from 'node:fs/promises'; import path from 'node:path'; diff --git a/crates/trusted-server-js/lib/test/integrations/gpt/gpt_bootstrap.test.ts b/crates/trusted-server-js/lib/test/integrations/gpt/gpt_bootstrap.test.ts index 00d83500b..e9fced2cf 100644 --- a/crates/trusted-server-js/lib/test/integrations/gpt/gpt_bootstrap.test.ts +++ b/crates/trusted-server-js/lib/test/integrations/gpt/gpt_bootstrap.test.ts @@ -20,6 +20,7 @@ const BOOTSTRAP_SOURCE = readFileSync( 'utf8' ); +// eslint-disable-next-line @typescript-eslint/no-explicit-any -- bootstrap pokes arbitrary window surfaces type AnyRecord = Record; type TestWindow = Window & { diff --git a/crates/trusted-server-js/lib/test/integrations/gpt/index.test.ts b/crates/trusted-server-js/lib/test/integrations/gpt/index.test.ts index 406c6d1f5..50e401d9a 100644 --- a/crates/trusted-server-js/lib/test/integrations/gpt/index.test.ts +++ b/crates/trusted-server-js/lib/test/integrations/gpt/index.test.ts @@ -1,3 +1,4 @@ +/* eslint-disable @typescript-eslint/no-explicit-any -- mocks and private state are poked via `any`; the file-level opt-out is deliberate */ import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest'; // We import installGptShim dynamically so each test can control whether the diff --git a/crates/trusted-server-js/lib/test/integrations/prebid/index.test.ts b/crates/trusted-server-js/lib/test/integrations/prebid/index.test.ts index 6535d9b3b..40d1f25f1 100644 --- a/crates/trusted-server-js/lib/test/integrations/prebid/index.test.ts +++ b/crates/trusted-server-js/lib/test/integrations/prebid/index.test.ts @@ -1,3 +1,4 @@ +/* eslint-disable @typescript-eslint/no-explicit-any -- mocks and private state are poked via `any`; the file-level opt-out is deliberate */ import { describe, it, expect, vi, beforeEach, afterEach } from 'vitest'; // Define mocks using vi.hoisted so they're available inside vi.mock factories From 23ae86e5953bbbbe6d5e9714cbad9723dad178ca Mon Sep 17 00:00:00 2001 From: Aram Grigoryan <132480+aram356@users.noreply.github.com> Date: Fri, 31 Jul 2026 14:29:01 -0700 Subject: [PATCH 4/6] Type test mocks for real instead of exempting them from no-explicit-any Replace the six file-level eslint-disable headers and the one scoped directive with actual types, eliminating all 247 remaining any usages in test/. The rule now applies to the whole test tree with no exceptions. - Window mocks: Omit intersections with Partial from src/core/types, replacing the any-typed tsjs surfaces; this was the real fix for the intersection problem an old ad_init comment used to justify any - Prebid: structural TestPbjs/TestAdUnit/TestBid types over the hoisted mock; ~50 argument-site casts collapse into declaration-site as TestPbjs casts (a plain annotation cannot compile against Prebid's contravariant requestBids signature) - core/index: drop the local Window.tsjs declare-global that conflicted with the src declaration - didomi: local TestDidomiConfig mirroring the non-exported src type - Many casts turned out gratuitous (untyped vi.fn() already assigns to typeof fetch; ad-unit literals already satisfy AdUnit) and were deleted outright One deliberate double-cast survives: prebid feeds a non-array bids value through as unknown as TestAdUnit[] to prove the shim normalizes malformed input. Type-only change: no statement, assertion, or mock behavior touched. Side effect: repo tsc --noEmit errors drop from 50 to 12, all remaining ones pre-existing in files this PR does not touch. --- crates/trusted-server-js/lib/eslint.config.js | 4 +- .../lib/test/core/index.test.ts | 35 +- .../lib/test/core/request.test.ts | 43 +- .../test/integrations/didomi/index.test.ts | 17 +- .../lib/test/integrations/gpt/ad_init.test.ts | 87 ++- .../integrations/gpt/gpt_bootstrap.test.ts | 34 +- .../lib/test/integrations/gpt/index.test.ts | 65 +- .../test/integrations/prebid/index.test.ts | 615 +++++++++++------- 8 files changed, 556 insertions(+), 344 deletions(-) diff --git a/crates/trusted-server-js/lib/eslint.config.js b/crates/trusted-server-js/lib/eslint.config.js index 4caeda322..2720ba3a0 100644 --- a/crates/trusted-server-js/lib/eslint.config.js +++ b/crates/trusted-server-js/lib/eslint.config.js @@ -38,7 +38,7 @@ export default [ }, }, // Honor the `_`-prefix convention for intentionally unused bindings in every - // linted file — tseslint recommended enables the rule globally, so scoping + // linted file: tseslint recommended enables the rule globally, so scoping // this to *.ts(x) would leave .mjs at the pattern-less defaults { rules: { @@ -48,7 +48,7 @@ export default [ ], }, }, - // Node build scripts and Node-run test harnesses — nodeBuiltin, not node, + // Node build scripts and Node-run test harnesses use nodeBuiltin, not node, // so CommonJS-only names (__dirname, require, module) still fail no-undef // in these ES modules { diff --git a/crates/trusted-server-js/lib/test/core/index.test.ts b/crates/trusted-server-js/lib/test/core/index.test.ts index 9817a8c8b..a02082b59 100644 --- a/crates/trusted-server-js/lib/test/core/index.test.ts +++ b/crates/trusted-server-js/lib/test/core/index.test.ts @@ -1,11 +1,6 @@ -/* eslint-disable @typescript-eslint/no-explicit-any -- mocks and private state are poked via `any`; the file-level opt-out is deliberate */ import { describe, it, expect, beforeEach, afterEach, vi } from 'vitest'; -declare global { - interface Window { - tsjs?: any; - } -} +import type { AuctionBidData, AuctionSlot, TsjsApi } from '../../src/core/types'; const ORIGINAL_FETCH = global.fetch; @@ -13,7 +8,7 @@ describe('core/index', () => { beforeEach(async () => { await vi.resetModules(); document.body.innerHTML = ''; - delete (window as any).tsjs; + delete window.tsjs; }); afterEach(() => { @@ -22,7 +17,7 @@ describe('core/index', () => { it('initializes tsjs API with expected surface', async () => { await import('../../src/core/index'); - const api = window.tsjs; + const api = window.tsjs as TsjsApi; expect(api).toBeDefined(); expect(typeof api.version).toBe('string'); expect(Array.isArray(api.que)).toBe(true); @@ -36,28 +31,28 @@ describe('core/index', () => { it('defaults adSlots and bids so gated-off pages never see undefined', async () => { await import('../../src/core/index'); - const api = window.tsjs; + const api = window.tsjs as TsjsApi; expect(api.adSlots).toEqual([]); expect(api.bids).toEqual({}); }); it('preserves edge-injected adSlots and bids set before the bundle loads', async () => { - (window as any).tsjs = { - adSlots: [{ id: 'pre-injected' }], - bids: { 'pre-injected': { hb_pb: '1.00' } }, - }; + window.tsjs = { + adSlots: [{ id: 'pre-injected' } as AuctionSlot], + bids: { 'pre-injected': { hb_pb: '1.00' } } as Record, + } as TsjsApi; await import('../../src/core/index'); - expect(window.tsjs.adSlots).toEqual([{ id: 'pre-injected' }]); - expect(window.tsjs.bids).toEqual({ 'pre-injected': { hb_pb: '1.00' } }); + expect(window.tsjs!.adSlots).toEqual([{ id: 'pre-injected' }]); + expect(window.tsjs!.bids).toEqual({ 'pre-injected': { hb_pb: '1.00' } }); }); it('flushes queued callbacks that existed before initialization', async () => { - const callback = vi.fn(function () { + const callback = vi.fn(function (this: TsjsApi) { expect(this).toBe(window.tsjs); }); - (window as any).tsjs = { que: [callback] }; + window.tsjs = { que: [callback] as Array<() => void> } as TsjsApi; await import('../../src/core/index'); @@ -66,7 +61,7 @@ describe('core/index', () => { it('installs queue that executes callbacks immediately with api context', async () => { await import('../../src/core/index'); - const api = window.tsjs; + const api = window.tsjs as TsjsApi; const fn = vi.fn(); api.que.push(fn); @@ -77,7 +72,7 @@ describe('core/index', () => { it('renders registered ad units using core rendering helpers', async () => { await import('../../src/core/index'); - const api = window.tsjs; + const api = window.tsjs as TsjsApi; api.addAdUnits([ { code: 'slot-1', mediaTypes: { banner: { sizes: [[300, 250]] } } }, @@ -93,7 +88,7 @@ describe('core/index', () => { it('exposes requestAds from the core request module', async () => { const { requestAds } = await import('../../src/core/request'); await import('../../src/core/index'); - const api = window.tsjs; + const api = window.tsjs as TsjsApi; expect(api.requestAds).toBe(requestAds); }); diff --git a/crates/trusted-server-js/lib/test/core/request.test.ts b/crates/trusted-server-js/lib/test/core/request.test.ts index 274943299..22dfc7185 100644 --- a/crates/trusted-server-js/lib/test/core/request.test.ts +++ b/crates/trusted-server-js/lib/test/core/request.test.ts @@ -1,4 +1,3 @@ -/* eslint-disable @typescript-eslint/no-explicit-any -- mocks and private state are poked via `any`; the file-level opt-out is deliberate */ import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest'; async function flushRequestAds(): Promise { @@ -22,7 +21,7 @@ describe('request.requestAds', () => { it('sends fetch and renders creatives via iframe from response', async () => { // mock fetch - returns creative HTML inline in adm field const creativeHtml = '
Test Creative
'; - (globalThis as any).fetch = vi.fn().mockResolvedValue({ + globalThis.fetch = vi.fn().mockResolvedValue({ ok: true, status: 200, headers: { get: () => 'application/json' }, @@ -42,12 +41,12 @@ describe('request.requestAds', () => { const infoSpy = vi.spyOn(log, 'info').mockImplementation(() => undefined); document.body.innerHTML = '
'; - addAdUnits({ code: 'slot1', mediaTypes: { banner: { sizes: [[300, 250]] } } } as any); + addAdUnits({ code: 'slot1', mediaTypes: { banner: { sizes: [[300, 250]] } } }); requestAds(); await flushRequestAds(); - expect((globalThis as any).fetch).toHaveBeenCalled(); + expect(globalThis.fetch).toHaveBeenCalled(); // Verify iframe was created with creative HTML in srcdoc const iframe = document.querySelector('#slot1 iframe') as HTMLIFrameElement | null; @@ -68,7 +67,7 @@ describe('request.requestAds', () => { }); it('does not render on non-JSON response', async () => { - (globalThis as any).fetch = vi.fn().mockResolvedValue({ + globalThis.fetch = vi.fn().mockResolvedValue({ ok: true, status: 200, headers: { get: () => 'text/plain' }, @@ -79,35 +78,35 @@ describe('request.requestAds', () => { const { requestAds } = await import('../../src/core/request'); document.body.innerHTML = '
'; - addAdUnits({ code: 'slot1', mediaTypes: { banner: { sizes: [[300, 250]] } } } as any); + addAdUnits({ code: 'slot1', mediaTypes: { banner: { sizes: [[300, 250]] } } }); requestAds(); await flushRequestAds(); - expect((globalThis as any).fetch).toHaveBeenCalled(); + expect(globalThis.fetch).toHaveBeenCalled(); expect(document.querySelector('iframe')).toBeNull(); }); it('ignores fetch rejection gracefully', async () => { - (globalThis as any).fetch = vi.fn().mockRejectedValue(new Error('network-error')); + globalThis.fetch = vi.fn().mockRejectedValue(new Error('network-error')); const { addAdUnits } = await import('../../src/core/registry'); const { requestAds } = await import('../../src/core/request'); document.body.innerHTML = '
'; - addAdUnits({ code: 'slot1', mediaTypes: { banner: { sizes: [[300, 250]] } } } as any); + addAdUnits({ code: 'slot1', mediaTypes: { banner: { sizes: [[300, 250]] } } }); requestAds(); await flushRequestAds(); - expect((globalThis as any).fetch).toHaveBeenCalled(); + expect(globalThis.fetch).toHaveBeenCalled(); expect(document.querySelector('iframe')).toBeNull(); }); it('inserts an iframe with creative HTML from unified auction', async () => { // mock fetch for unified auction endpoint - returns inline HTML const creativeHtml = 'Ad'; - (globalThis as any).fetch = vi.fn().mockResolvedValue({ + globalThis.fetch = vi.fn().mockResolvedValue({ ok: true, status: 200, headers: { get: () => 'application/json' }, @@ -130,7 +129,7 @@ describe('request.requestAds', () => { document.body.appendChild(div); // Add an ad unit and request - addAdUnits({ code: 'slot1', mediaTypes: { banner: { sizes: [[300, 250]] } } } as any); + addAdUnits({ code: 'slot1', mediaTypes: { banner: { sizes: [[300, 250]] } } }); requestAds(); await flushRequestAds(); @@ -145,7 +144,7 @@ describe('request.requestAds', () => { it('renders creatives with safe URI markup', async () => { const creativeHtml = 'Contactad'; - (globalThis as any).fetch = vi.fn().mockResolvedValue({ + globalThis.fetch = vi.fn().mockResolvedValue({ ok: true, status: 200, headers: { get: () => 'application/json' }, @@ -163,7 +162,7 @@ describe('request.requestAds', () => { const { requestAds } = await import('../../src/core/request'); document.body.innerHTML = '
'; - addAdUnits({ code: 'slot1', mediaTypes: { banner: { sizes: [[300, 250]] } } } as any); + addAdUnits({ code: 'slot1', mediaTypes: { banner: { sizes: [[300, 250]] } } }); requestAds(); await flushRequestAds(); @@ -175,7 +174,7 @@ describe('request.requestAds', () => { }); it('rejects malformed non-string creative HTML without blanking the slot', async () => { - (globalThis as any).fetch = vi.fn().mockResolvedValue({ + globalThis.fetch = vi.fn().mockResolvedValue({ ok: true, status: 200, headers: { get: () => 'application/json' }, @@ -195,7 +194,7 @@ describe('request.requestAds', () => { const warnSpy = vi.spyOn(log, 'warn').mockImplementation(() => undefined); document.body.innerHTML = '
existing
'; - addAdUnits({ code: 'slot1', mediaTypes: { banner: { sizes: [[300, 250]] } } } as any); + addAdUnits({ code: 'slot1', mediaTypes: { banner: { sizes: [[300, 250]] } } }); requestAds(); await flushRequestAds(); @@ -222,7 +221,7 @@ describe('request.requestAds', () => { // Regression: multi-bid scenario where a rejected bid must not erase an earlier // successful render into the same slot. const goodCreative = '
Safe Ad
'; - (globalThis as any).fetch = vi.fn().mockResolvedValue({ + globalThis.fetch = vi.fn().mockResolvedValue({ ok: true, status: 200, headers: { get: () => 'application/json' }, @@ -245,7 +244,7 @@ describe('request.requestAds', () => { const { requestAds } = await import('../../src/core/request'); document.body.innerHTML = '
'; - addAdUnits({ code: 'slot1', mediaTypes: { banner: { sizes: [[300, 250]] } } } as any); + addAdUnits({ code: 'slot1', mediaTypes: { banner: { sizes: [[300, 250]] } } }); requestAds(); await flushRequestAds(); @@ -257,7 +256,7 @@ describe('request.requestAds', () => { }); it('rejects creatives that sanitize to empty markup', async () => { - (globalThis as any).fetch = vi.fn().mockResolvedValue({ + globalThis.fetch = vi.fn().mockResolvedValue({ ok: true, status: 200, headers: { get: () => 'application/json' }, @@ -277,7 +276,7 @@ describe('request.requestAds', () => { const warnSpy = vi.spyOn(log, 'warn').mockImplementation(() => undefined); document.body.innerHTML = '
'; - addAdUnits({ code: 'slot1', mediaTypes: { banner: { sizes: [[300, 250]] } } } as any); + addAdUnits({ code: 'slot1', mediaTypes: { banner: { sizes: [[300, 250]] } } }); requestAds(); await flushRequestAds(); @@ -299,7 +298,7 @@ describe('request.requestAds', () => { it('skips iframe insertion when slot is missing', async () => { // mock fetch for unified auction endpoint - returns inline HTML - (globalThis as any).fetch = vi.fn().mockResolvedValue({ + globalThis.fetch = vi.fn().mockResolvedValue({ ok: true, status: 200, headers: { get: () => 'application/json' }, @@ -315,7 +314,7 @@ describe('request.requestAds', () => { const { addAdUnits } = await import('../../src/core/registry'); const { requestAds } = await import('../../src/core/request'); - addAdUnits({ code: 'missing-slot', mediaTypes: { banner: { sizes: [[300, 250]] } } } as any); + addAdUnits({ code: 'missing-slot', mediaTypes: { banner: { sizes: [[300, 250]] } } }); requestAds(); await flushRequestAds(); diff --git a/crates/trusted-server-js/lib/test/integrations/didomi/index.test.ts b/crates/trusted-server-js/lib/test/integrations/didomi/index.test.ts index 435f42054..5ca9d7598 100644 --- a/crates/trusted-server-js/lib/test/integrations/didomi/index.test.ts +++ b/crates/trusted-server-js/lib/test/integrations/didomi/index.test.ts @@ -1,12 +1,17 @@ -/* eslint-disable @typescript-eslint/no-explicit-any -- mocks and private state are poked via `any`; the file-level opt-out is deliberate */ import { afterEach, beforeEach, describe, expect, it } from 'vitest'; import { installDidomiSdkProxy } from '../../../src/integrations/didomi'; const ORIGINAL_WINDOW = global.window; +// Mirrors the non-exported DidomiConfig shape in src/integrations/didomi. +type TestDidomiConfig = { + sdkPath?: string; + [key: string]: unknown; +}; + type TestDidomiWindow = Window & { - didomiConfig?: any; + didomiConfig?: TestDidomiConfig; __tsjs_didomi?: { proxyPath?: string }; }; @@ -21,18 +26,18 @@ describe('integrations/didomi', () => { beforeEach(() => { testWindow = createWindow('https://example.com/page'); - Object.assign(globalThis as any, { window: testWindow }); + Object.assign(globalThis, { window: testWindow }); }); afterEach(() => { - Object.assign(globalThis as any, { window: ORIGINAL_WINDOW }); + Object.assign(globalThis, { window: ORIGINAL_WINDOW }); }); it('initializes didomiConfig and forces sdkPath through trusted server proxy', () => { installDidomiSdkProxy(); expect(testWindow.didomiConfig).toBeDefined(); - expect(testWindow.didomiConfig.sdkPath).toBe( + expect(testWindow.didomiConfig!.sdkPath).toBe( 'https://example.com/integrations/didomi/consent/' ); }); @@ -53,6 +58,6 @@ describe('integrations/didomi', () => { installDidomiSdkProxy(); - expect(testWindow.didomiConfig.sdkPath).toBe('https://example.com/my-custom-consent/'); + expect(testWindow.didomiConfig!.sdkPath).toBe('https://example.com/my-custom-consent/'); }); }); diff --git a/crates/trusted-server-js/lib/test/integrations/gpt/ad_init.test.ts b/crates/trusted-server-js/lib/test/integrations/gpt/ad_init.test.ts index ba01b889a..e295aa7b1 100644 --- a/crates/trusted-server-js/lib/test/integrations/gpt/ad_init.test.ts +++ b/crates/trusted-server-js/lib/test/integrations/gpt/ad_init.test.ts @@ -1,9 +1,10 @@ -/* eslint-disable @typescript-eslint/no-explicit-any -- mocks and private state are poked via `any`; the file-level opt-out is deliberate */ import { readFile } from 'node:fs/promises'; import path from 'node:path'; import { describe, it, expect, vi, beforeEach, afterEach, afterAll } from 'vitest'; +import type { TsjsApi } from '../../../src/core/types'; + // Track every 'message' EventListener added to window across the entire test // file. This lets the installTsRenderBridge suite remove all accumulated // handlers (registered by each vi.resetModules() + module re-import in the @@ -44,13 +45,23 @@ interface SlotRenderEvent { }; } -type TestWindow = Window & { +// The `Prebid Response` payload the render bridge posts back to the Prebid +// Universal Creative over the message port. +interface PrebidResponseMessage { + message?: string; + adId?: string; + ad?: string; + width?: number; + height?: number; +} + +// `tsjs` is declared globally as the full `TsjsApi` (core/types.ts). Omitting +// it from `Window` before re-adding it as a `Partial` avoids the intersection +// that would force every fixture below to satisfy the whole `TsjsApi` shape. +type TestWindow = Omit & { googletag?: unknown; apstag?: { setDisplayBids?: () => void }; - // Typed as `any` to avoid the TypeScript intersection with the global - // Window.tsjs declaration (TsjsApi from core/types.ts), which would require - // every test fixture to satisfy the full TsjsApi shape. - tsjs?: any; + tsjs?: Partial; }; async function runGptBootstrap(googletag: object): Promise { @@ -142,7 +153,7 @@ describe('installTsAdInit', () => { burl: 'https://ssp/bill', }, }, - } as any; + }; const fetchSpy = vi.spyOn(global, 'fetch'); @@ -197,7 +208,7 @@ describe('installTsAdInit', () => { }, ], bids: {}, - } as any; + }; const { installTsAdInit } = await import('../../../src/integrations/gpt/index'); installTsAdInit(); @@ -251,7 +262,7 @@ describe('installTsAdInit', () => { }, ], bids: {}, - } as any; + }; const { installTsAdInit } = await import('../../../src/integrations/gpt/index'); installTsAdInit(); @@ -304,7 +315,7 @@ describe('installTsAdInit', () => { }, ], bids: {}, - } as any; + }; await runGptBootstrap(googletag); @@ -360,7 +371,7 @@ describe('installTsAdInit', () => { }, ], bids: {}, - } as any; + }; await runGptBootstrap(googletag); @@ -452,7 +463,7 @@ describe('installTsAdInit', () => { }, ], bids: {}, - } as any; + }; const { installTsAdInit } = await import('../../../src/integrations/gpt/index'); installTsAdInit(); @@ -561,7 +572,7 @@ describe('installTsAdInit', () => { }, ], bids: {}, - } as any; + }; const { installTsAdInit } = await import('../../../src/integrations/gpt/index'); installTsAdInit(); @@ -610,7 +621,7 @@ describe('installTsAdInit', () => { }, ], bids: {}, - } as any; + }; const { installTsAdInit } = await import('../../../src/integrations/gpt/index'); installTsAdInit(); @@ -649,7 +660,7 @@ describe('installTsAdInit', () => { // Previous route touched the publisher-owned slot on div-old-route. divToSlotId: { 'div-old-route': 'old_slot' }, prevSlotTargetingKeys: { 'div-old-route': ['pos'] }, - } as any; + }; const { installTsAdInit } = await import('../../../src/integrations/gpt/index'); installTsAdInit(); @@ -688,7 +699,7 @@ describe('installTsAdInit', () => { (window as TestWindow).tsjs = { adSlots: [], bids: {}, - } as any; + }; const { installTsAdInit } = await import('../../../src/integrations/gpt/index'); installTsAdInit(); @@ -1092,7 +1103,7 @@ describe('installTsAdInit', () => { bids: { atf_sidebar_ad: { hb_pb: '1.50', hb_bidder: 'aps', nurl: '', burl: '' }, }, - } as any; + }; const { installTsAdInit } = await import('../../../src/integrations/gpt/index'); installTsAdInit(); @@ -1138,7 +1149,7 @@ describe('installTsAdInit', () => { bids: { atf_sidebar_ad: { hb_pb: '1.00', hb_bidder: 'kargo' }, }, - } as any; + }; const { installTsAdInit } = await import('../../../src/integrations/gpt/index'); installTsAdInit(); @@ -1342,7 +1353,11 @@ describe('installTsRenderBridge', () => { .mockImplementation( (type: string, handler: EventListenerOrEventListenerObject, opts?: unknown) => { if (type === 'message') bridgeListener = handler as (e: MessageEvent) => unknown; - origAdd(type, handler as EventListener, opts as any); + origAdd( + type, + handler as EventListener, + opts as boolean | AddEventListenerOptions | undefined + ); } ); await import('../../../src/integrations/gpt/index'); @@ -1371,7 +1386,11 @@ describe('installTsRenderBridge', () => { .mockImplementation( (type: string, handler: EventListenerOrEventListenerObject, opts?: unknown) => { if (type === 'message') bridgeListener = handler as (e: MessageEvent) => unknown; - origAdd(type, handler as EventListener, opts as any); + origAdd( + type, + handler as EventListener, + opts as boolean | AddEventListenerOptions | undefined + ); } ); await import('../../../src/integrations/gpt/index'); @@ -1405,7 +1424,7 @@ describe('installTsRenderBridge', () => { expect(stopSpy).toHaveBeenCalled(); expect(portMessages).toHaveLength(1); - const parsed = JSON.parse(portMessages[0]) as Record; + const parsed = JSON.parse(portMessages[0]) as PrebidResponseMessage; expect(parsed.message).toBe('Prebid Response'); expect(parsed.adId).toBe('test-cache-uuid'); expect(parsed.ad).toBe(mockAd); @@ -1488,7 +1507,7 @@ describe('installTsRenderBridge', () => { expect(portMessages).toHaveLength(1); - const parsed = JSON.parse(portMessages[0]) as Record; + const parsed = JSON.parse(portMessages[0]) as PrebidResponseMessage; expect(parsed.ad).toBe(rawAd); expect(beaconSpy).toHaveBeenCalledTimes(2); beaconSpy.mockRestore(); @@ -1520,7 +1539,7 @@ describe('installTsRenderBridge', () => { expect(portMessages).toHaveLength(1); - const parsed = JSON.parse(portMessages[0]) as Record; + const parsed = JSON.parse(portMessages[0]) as PrebidResponseMessage; expect(parsed.width).toBe(300); expect(parsed.height).toBe(250); beaconSpy.mockRestore(); @@ -1556,7 +1575,7 @@ describe('installTsRenderBridge', () => { expect(portMessages).toHaveLength(1); - const parsed = JSON.parse(portMessages[0]) as Record; + const parsed = JSON.parse(portMessages[0]) as PrebidResponseMessage; expect(parsed.ad).toContain('p=2.5'); expect(parsed.ad).not.toContain('${AUCTION_PRICE}'); beaconSpy.mockRestore(); @@ -1727,7 +1746,11 @@ describe('installTsRenderBridge', () => { .mockImplementation( (type: string, handler: EventListenerOrEventListenerObject, opts?: unknown) => { if (type === 'message') bridgeListener = handler as (e: MessageEvent) => unknown; - origAdd(type, handler as EventListener, opts as any); + origAdd( + type, + handler as EventListener, + opts as boolean | AddEventListenerOptions | undefined + ); } ); await import('../../../src/integrations/gpt/index'); @@ -1755,7 +1778,7 @@ describe('installTsRenderBridge', () => { expect(stopSpy).toHaveBeenCalled(); expect(portMessages).toHaveLength(1); - const parsed = JSON.parse(portMessages[0]) as Record; + const parsed = JSON.parse(portMessages[0]) as PrebidResponseMessage; expect(parsed.message).toBe('Prebid Response'); expect(parsed.adId).toBe('debug-adid'); expect(parsed.ad).toBe(inlineAdm); @@ -1816,7 +1839,7 @@ describe('installTsRenderBridge', () => { expect(portMessages).toHaveLength(1); - const parsed = JSON.parse(portMessages[0]) as Record; + const parsed = JSON.parse(portMessages[0]) as PrebidResponseMessage; expect(parsed.width).toBe(300); expect(parsed.height).toBe(250); } finally { @@ -1891,7 +1914,7 @@ describe('installTsRenderBridge', () => { expect(portMessages).toHaveLength(1); - const parsed = JSON.parse(portMessages[0]) as Record; + const parsed = JSON.parse(portMessages[0]) as PrebidResponseMessage; // The requesting slot's own creative and dimensions, not the first match's. expect(parsed.ad).toBe(inContentAdm); expect(parsed.width).toBe(300); @@ -1911,7 +1934,7 @@ describe('installTsRenderBridge', () => { }); try { - (window as TestWindow).tsjs.bids.homepage_header = { + (window as TestWindow).tsjs!.bids!.homepage_header = { hb_adid: 'debug-no-beacon', hb_bidder: 'mocktioneer', hb_pb: '0.20', @@ -1957,7 +1980,7 @@ describe('installTsRenderBridge', () => { it('falls back to keepalive fetch when sendBeacon rejects the payload', async () => { const beaconSpy = vi.spyOn(navigator, 'sendBeacon').mockReturnValue(false); - (window as TestWindow).tsjs.bids.homepage_header = { + (window as TestWindow).tsjs!.bids!.homepage_header = { hb_adid: 'debug-rejected-beacon', hb_bidder: 'mocktioneer', hb_pb: '0.20', @@ -2040,7 +2063,7 @@ describe('installTsRenderBridge', () => { it('ignores a request whose source slot does not own the resolved adId', async () => { // Two configured slots; slot A's iframe requests slot B's hb_adid. The // bridge must not return slot B's creative or fire slot B's beacons. - (window as TestWindow).tsjs.bids.homepage_footer = { + (window as TestWindow).tsjs!.bids!.homepage_footer = { hb_adid: 'footer-uuid', hb_bidder: 'kargo', hb_pb: '2.00', @@ -2049,7 +2072,7 @@ describe('installTsRenderBridge', () => { nurl: 'https://ssp.example/footer-win', burl: 'https://ssp.example/footer-bill', }; - (window as TestWindow).tsjs.adSlots.push({ + (window as TestWindow).tsjs!.adSlots!.push({ id: 'homepage_footer', formats: [[300, 250]] as [number, number][], gam_unit_path: '/a/b/footer', diff --git a/crates/trusted-server-js/lib/test/integrations/gpt/gpt_bootstrap.test.ts b/crates/trusted-server-js/lib/test/integrations/gpt/gpt_bootstrap.test.ts index e9fced2cf..f5c43ed7f 100644 --- a/crates/trusted-server-js/lib/test/integrations/gpt/gpt_bootstrap.test.ts +++ b/crates/trusted-server-js/lib/test/integrations/gpt/gpt_bootstrap.test.ts @@ -3,6 +3,8 @@ import path from 'node:path'; import { describe, it, expect, vi, beforeEach, afterEach } from 'vitest'; +import type { TsjsApi } from '../../../src/core/types'; + /** * Executable coverage for the edge-injected `gpt_bootstrap.js` — the * head-inline fallback that keeps initial server-side ads working when the @@ -20,12 +22,24 @@ const BOOTSTRAP_SOURCE = readFileSync( 'utf8' ); -// eslint-disable-next-line @typescript-eslint/no-explicit-any -- bootstrap pokes arbitrary window surfaces -type AnyRecord = Record; +// The command queue the bootstrap pushes into: a real array once GPT has +// loaded, or the bare `push`-only stub GPT installs before then. +type MockCommandQueue = Array<() => void> | { push: (fn: () => void) => unknown }; + +// Minimal googletag surface the bootstrap touches. +interface MockGoogleTag { + cmd: MockCommandQueue; + defineSlot: (adUnitPath: string, sizes: Array<[number, number]>, divId: string) => unknown; + pubads: () => unknown; + enableServices: () => void; + display: (divId: string) => void; +} -type TestWindow = Window & { - googletag?: AnyRecord; - tsjs?: AnyRecord; +// `tsjs` is declared globally as the full `TsjsApi`; `Omit` drops it from +// `Window` so the fixtures below only have to satisfy the fields they set. +type TestWindow = Omit & { + googletag?: MockGoogleTag; + tsjs?: Partial; }; function runBootstrap(): void { @@ -94,7 +108,7 @@ describe('gpt_bootstrap.js fallback', () => { const adInit = vi.fn(); ts.adInit = adInit; - ts.scheduleInitialAdInit({ atf: { hb_pb: '1.00' } }); + ts.scheduleInitialAdInit!({ atf: { hb_pb: '1.00' } }); expect(ts.bids).toEqual({ atf: { hb_pb: '1.00' } }); expect(adInit).not.toHaveBeenCalled(); @@ -118,7 +132,7 @@ describe('gpt_bootstrap.js fallback', () => { const adInit = vi.fn(); ts.adInit = adInit; - ts.scheduleInitialAdInit({ atf: { hb_pb: '1.00' } }); + ts.scheduleInitialAdInit!({ atf: { hb_pb: '1.00' } }); window.dispatchEvent(new Event('load')); expect(rafQueue.length).toBeGreaterThan(0); expect(adInit).not.toHaveBeenCalled(); @@ -136,7 +150,7 @@ describe('gpt_bootstrap.js fallback', () => { ts.bids = { live_slot: { hb_pb: '2.50' } }; ts.navGeneration = 1; - ts.scheduleInitialAdInit({ ssr_slot: { hb_pb: '1.00' } }); + ts.scheduleInitialAdInit!({ ssr_slot: { hb_pb: '1.00' } }); expect(ts.bids).toEqual({ live_slot: { hb_pb: '2.50' } }); window.dispatchEvent(new Event('load')); @@ -177,7 +191,7 @@ describe('gpt_bootstrap.js fallback', () => { ]; ts.bids = { atf_sidebar_ad: { hb_pb: '1.00' } }; - ts.adInit(); + ts.adInit!(); const googletag = (window as TestWindow).googletag!; expect(googletag.defineSlot).toHaveBeenCalledWith( @@ -219,7 +233,7 @@ describe('gpt_bootstrap.js fallback', () => { ]; // GPT not loaded: adInit's work sits queued. - ts.adInit(); + ts.adInit!(); expect(commandQueue.length).toBeGreaterThan(0); // A navigation commits (the bundle's SPA hook advances the generation), diff --git a/crates/trusted-server-js/lib/test/integrations/gpt/index.test.ts b/crates/trusted-server-js/lib/test/integrations/gpt/index.test.ts index 50e401d9a..e8251be89 100644 --- a/crates/trusted-server-js/lib/test/integrations/gpt/index.test.ts +++ b/crates/trusted-server-js/lib/test/integrations/gpt/index.test.ts @@ -1,5 +1,7 @@ -/* eslint-disable @typescript-eslint/no-explicit-any -- mocks and private state are poked via `any`; the file-level opt-out is deliberate */ import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest'; +import type { Mock } from 'vitest'; + +import type { TsjsApi } from '../../../src/core/types'; // We import installGptShim dynamically so each test can control whether the // GPT enable flag is present before module evaluation. @@ -10,10 +12,9 @@ async function importGuardModule() { type GptWindow = Window & { googletag?: { - cmd: Array<() => void> & { - push: (...items: Array<() => void>) => number; - __tsPushed?: boolean; - }; + // The shim marks the queue it patched; `push` itself comes from `Array`, + // which GPT replaces with its own execute-immediately implementation. + cmd: Array<() => void> & { __tsPushed?: boolean }; _loaded_?: boolean; }; }; @@ -218,16 +219,48 @@ describe('GPT – installSlimPrebidLoader', () => { }); describe('GPT – installTsAdInit', () => { + // GPT slot mock: setTargeting/clearTargeting are chainable, so both return + // the slot itself. + interface MockGptSlot { + getSlotElementId: Mock<() => string>; + getTargeting: Mock<(key: string) => string[]>; + setTargeting: Mock<(key: string, value: string | string[]) => MockGptSlot>; + clearTargeting: Mock<(key?: string) => MockGptSlot>; + } + + // Minimal pubads surface adInit() drives. + interface MockPubAds { + getSlots: () => MockGptSlot[]; + enableSingleRequest: () => void; + addEventListener: (event: string, fn: (e: unknown) => void) => void; + refresh: (slots?: MockGptSlot[]) => void; + } + + interface MockGoogleTag { + cmd: Array<() => void>; + pubads: () => MockPubAds; + defineSlot: Mock; + destroySlots: Mock; + enableServices: Mock; + } + + // `tsjs` is declared globally as the full `TsjsApi`; `Omit` drops it from + // `Window` so the fixture below only has to satisfy the fields it sets. + type AdInitWindow = Omit & { + tsjs?: Partial; + googletag?: MockGoogleTag; + }; + beforeEach(() => { document.body.innerHTML = ''; - delete (window as any).tsjs; - delete (window as any).googletag; + delete (window as AdInitWindow).tsjs; + delete (window as AdInitWindow).googletag; }); afterEach(() => { document.body.innerHTML = ''; - delete (window as any).tsjs; - delete (window as any).googletag; + delete (window as AdInitWindow).tsjs; + delete (window as AdInitWindow).googletag; }); it('clears stale TS-managed targeting before applying a new route to a reused GPT slot', async () => { @@ -241,7 +274,7 @@ describe('GPT – installTsAdInit', () => { ['ts_initial', ['1']], ['pos', ['old-pos']], ]); - const gptSlot: any = { + const gptSlot: MockGptSlot = { getSlotElementId: vi.fn(() => 'div-ad-homepage-header'), getTargeting: vi.fn((key: string) => slotTargeting.get(key) ?? []), setTargeting: vi.fn((key: string, value: string | string[]) => { @@ -270,14 +303,14 @@ describe('GPT – installTsAdInit', () => { }; document.body.innerHTML = '
'; - (window as any).googletag = { + (window as AdInitWindow).googletag = { cmd, pubads: () => pubads, defineSlot: vi.fn(), destroySlots: vi.fn(), enableServices: vi.fn(), }; - (window as any).tsjs = { + (window as AdInitWindow).tsjs = { prevSlotTargetingKeys: { 'div-ad-homepage-header': ['pos'], }, @@ -294,7 +327,7 @@ describe('GPT – installTsAdInit', () => { }; installTsAdInit(); - (window as any).tsjs.adInit(); + (window as AdInitWindow).tsjs!.adInit!(); expect(gptSlot.clearTargeting).toHaveBeenCalledWith('hb_pb'); expect(gptSlot.clearTargeting).toHaveBeenCalledWith('hb_bidder'); @@ -318,6 +351,8 @@ describe('GPT shim – runtime gating', () => { type GatedWindow = Window & { __tsjs_gpt_enabled?: boolean; googletag?: { cmd: Array<() => void> }; + // Activation hook the module registers; the tests only assert its typeof. + __tsjs_installGptShim?: unknown; }; let win: GatedWindow; @@ -335,7 +370,7 @@ describe('GPT shim – runtime gating', () => { guard.resetGuardState(); delete (window as GatedWindow).googletag; delete (window as GatedWindow).__tsjs_gpt_enabled; - delete (window as Record).__tsjs_installGptShim; + delete (window as GatedWindow).__tsjs_installGptShim; }); it('installs the shim when activation function is called (simulates server inline script)', async () => { @@ -355,7 +390,7 @@ describe('GPT shim – runtime gating', () => { vi.resetModules(); await import('../../../src/integrations/gpt/index'); - expect(typeof (window as Record).__tsjs_installGptShim).toBe('function'); + expect(typeof (window as GatedWindow).__tsjs_installGptShim).toBe('function'); }); it('auto-installs the shim when the enable flag is set before import', async () => { diff --git a/crates/trusted-server-js/lib/test/integrations/prebid/index.test.ts b/crates/trusted-server-js/lib/test/integrations/prebid/index.test.ts index 40d1f25f1..4c0dac8b9 100644 --- a/crates/trusted-server-js/lib/test/integrations/prebid/index.test.ts +++ b/crates/trusted-server-js/lib/test/integrations/prebid/index.test.ts @@ -1,4 +1,3 @@ -/* eslint-disable @typescript-eslint/no-explicit-any -- mocks and private state are poked via `any`; the file-level opt-out is deliberate */ import { describe, it, expect, vi, beforeEach, afterEach } from 'vitest'; // Define mocks using vi.hoisted so they're available inside vi.mock factories @@ -20,7 +19,11 @@ const { const mockRegisterBidAdapter = vi.fn(); const mockGetBidAdapter = vi.fn(); const mockGetUserIdsAsEids = vi.fn( - () => [] as Array<{ source: string; uids?: Array<{ id: string; atype?: number }> }> + () => + [] as Array<{ + source: string; + uids?: Array<{ id: string; atype?: number; ext?: Record }>; + }> ); const mockGetConfig = vi.fn(); // Only called after mockPbjs is initialized below, so the forward reference is safe. @@ -30,7 +33,7 @@ const { return; } const codes = new Set(Array.isArray(adUnitCode) ? adUnitCode : [adUnitCode]); - mockPbjs.adUnits = mockPbjs.adUnits.filter((unit) => !codes.has(unit.code)); + mockPbjs.adUnits = mockPbjs.adUnits.filter((unit) => !codes.has(unit.code!)); }); const mockPbjs: { setConfig: typeof mockSetConfig; @@ -40,8 +43,8 @@ const { getUserIdsAsEids: typeof mockGetUserIdsAsEids; getConfig: typeof mockGetConfig; removeAdUnit: ReturnType; - adUnits: any[]; - [key: string]: any; + adUnits: TestAdUnit[]; + [key: string]: unknown; } = { setConfig: mockSetConfig, processQueue: mockProcessQueue, @@ -95,6 +98,123 @@ import { } from '../../../src/integrations/prebid/index'; import type { AuctionBid } from '../../../src/core/auction'; import { log } from '../../../src/core/log'; +import type { AuctionSlot } from '../../../src/core/types'; + +/** Bid entry the prebid suites build and assert on. */ +interface TestBid { + bidder?: string; + params?: Record; +} + +/** + * Ad unit shape these suites hand to the shim. + * + * Every field is optional because several cases deliberately exercise partial + * units, which is why the real Prebid `AdUnitDefinition` cannot be reused here. + */ +interface TestAdUnit { + code?: string; + mediaTypes?: { banner?: { name?: string; sizes?: number[][] } }; + bids?: TestBid[]; +} + +/** The `trustedServer` bid the shim injects, with the params it folds onto it. */ +interface TrustedServerTestBid { + bidder: string; + params: { + bidderParams?: Record>; + zone?: string; + [key: string]: unknown; + }; +} + +/** Synthetic refresh ad unit the refresh handler passes to `requestBids`. */ +interface RefreshAdUnit { + code?: string; + mediaTypes?: { banner?: { name?: string; sizes?: number[][] } }; + bids: TrustedServerTestBid[]; +} + +/** Options object the shimmed `requestBids` accepts in these tests. */ +interface TestRequestBidsOptions { + adUnits?: TestAdUnit[]; + bidsBackHandler?: (...args: unknown[]) => void; + timeout?: number; +} + +/** The slice of the installed pbjs surface these tests drive. */ +interface TestPbjs { + requestBids(request?: TestRequestBidsOptions): void; + removeAdUnit(adUnitCode?: string | string[]): void; + setTargetingForGPTAsync(adUnitCode?: unknown, customSlotMatching?: unknown): void; +} + +/** The `trustedServer` adapter spec the module registers with Prebid. */ +interface TestAdapterSpec { + code: string; + supportedMediaTypes: string[]; + isBidRequestValid: (bid: unknown) => boolean; + buildRequests: (bidRequests: unknown[]) => { + method: string; + url: string; + data: string; + options: { contentType: string }; + }; + interpretResponse: ( + serverResponse: unknown, + request: unknown + ) => Array<{ requestId: string; cpm: number }>; +} + +/** GPT slot stub the refresh and delivery suites install. */ +interface TestGptSlot { + getSlotElementId?: () => string; + getTargeting?: (key: string) => string[]; + getSizes?: () => unknown[]; + clearTargeting?: (key?: string) => void; +} + +/** GPT stub these suites install on `window.googletag`. */ +interface TestGoogletag { + cmd: { push: (fn: () => void) => void }; + pubads: () => unknown; +} + +/** + * Window surface these tests poke: the server-injected prebid config, the + * diagnostics blob the module publishes, a GPT stub, and a partial tsjs API. + * + * `tsjs` is replaced rather than intersected because the suites assign partial + * payloads that the full `TsjsApi` declared in `src/core/global.d.ts` rejects. + */ +type TestWindow = Omit & { + __tsjs_prebid?: { + accountId?: string; + timeout?: number; + debug?: boolean; + bidders?: string[]; + clientSideBidders?: string[]; + }; + __tsjs_prebid_diagnostics?: { + userIdModules: { + includedModules: string[]; + configuredUserIdNames: string[]; + missingConfiguredUserIdNames: string[]; + }; + }; + googletag?: TestGoogletag; + tsjs?: { adSlots?: Array>; adInitRefreshInProgress?: boolean }; +}; + +/** Nested server-side bidder params the mutation-isolation test rewrites. */ +type NestedServerParams = { + bidderParams: { + exampleServer: { placement: { rules: Array<{ label: string }>; sizes: number[] } }; + }; +}; + +/** Nested client-side bidder params the mutation-isolation test rewrites. */ +type NestedBrowserParams = { groups: Array<{ values: string[] }> }; describe('prebid/collectBidders', () => { it('returns empty array for empty ad units', () => { @@ -125,7 +245,7 @@ describe('prebid/collectBidders', () => { describe('prebid/getInjectedConfig', () => { afterEach(() => { - delete (window as any).__tsjs_prebid; + delete (window as TestWindow).__tsjs_prebid; }); it('returns undefined when window.__tsjs_prebid is not set', () => { @@ -133,7 +253,7 @@ describe('prebid/getInjectedConfig', () => { }); it('returns the injected config when present', () => { - (window as any).__tsjs_prebid = { accountId: 'server-42', timeout: 2000 }; + (window as TestWindow).__tsjs_prebid = { accountId: 'server-42', timeout: 2000 }; expect(getInjectedConfig()).toEqual({ accountId: 'server-42', timeout: 2000 }); }); }); @@ -239,8 +359,8 @@ describe('prebid/installPrebidNpm', () => { mockGetUserIdsAsEids.mockReturnValue([]); mockGetConfig.mockReset(); document.cookie = 'ts-eids=; Path=/; Max-Age=0'; - delete (window as any).__tsjs_prebid; - delete (window as any).__tsjs_prebid_diagnostics; + delete (window as TestWindow).__tsjs_prebid; + delete (window as TestWindow).__tsjs_prebid_diagnostics; }); afterEach(() => { @@ -290,7 +410,7 @@ describe('prebid/installPrebidNpm', () => { it('reports the User ID modules selected by the generated bundle', () => { installPrebidNpm(); - expect((window as any).__tsjs_prebid_diagnostics.userIdModules).toEqual({ + expect((window as TestWindow).__tsjs_prebid_diagnostics!.userIdModules).toEqual({ includedModules: ['sharedIdSystem'], configuredUserIdNames: [], missingConfiguredUserIdNames: [], @@ -307,7 +427,7 @@ describe('prebid/installPrebidNpm', () => { mockPbjs.requestBids({ adUnits: [] }); mockPbjs.requestBids({ adUnits: [] }); - expect((window as any).__tsjs_prebid_diagnostics.userIdModules).toEqual({ + expect((window as TestWindow).__tsjs_prebid_diagnostics!.userIdModules).toEqual({ includedModules: ['sharedIdSystem'], configuredUserIdNames: ['pairId', 'sharedId'], missingConfiguredUserIdNames: ['pairId'], @@ -323,7 +443,7 @@ describe('prebid/installPrebidNpm', () => { }); describe('adapter spec', () => { - function getAdapterSpec(): any { + function getAdapterSpec(): TestAdapterSpec { installPrebidNpm(); return mockRegisterBidAdapter.mock.calls[0][2]; } @@ -597,41 +717,43 @@ describe('prebid/installPrebidNpm', () => { describe('requestBids shim', () => { it('injects trustedServer bidder into every ad unit', () => { - const pbjs = installPrebidNpm(); + const pbjs = installPrebidNpm() as TestPbjs; const adUnits = [ { bids: [{ bidder: 'appnexus', params: {} }] }, { bids: [{ bidder: 'rubicon', params: {} }] }, ]; - pbjs.requestBids({ adUnits } as any); + pbjs.requestBids({ adUnits }); // Each ad unit should have trustedServer added for (const unit of adUnits) { - const hasTsBidder = unit.bids.some((b: any) => b.bidder === 'trustedServer'); + const hasTsBidder = unit.bids.some((b) => b.bidder === 'trustedServer'); expect(hasTsBidder).toBe(true); } - const trustedServerBid = adUnits[0].bids.find((b: any) => b.bidder === 'trustedServer'); + const trustedServerBid = adUnits[0].bids.find( + (b) => b.bidder === 'trustedServer' + ) as TrustedServerTestBid; expect(trustedServerBid.params.bidderParams).toEqual({ appnexus: {} }); - expect(adUnits[0].bids.map((b: any) => b.bidder)).toEqual(['trustedServer']); - expect(adUnits[1].bids.map((b: any) => b.bidder)).toEqual(['trustedServer']); + expect(adUnits[0].bids.map((b) => b.bidder)).toEqual(['trustedServer']); + expect(adUnits[1].bids.map((b) => b.bidder)).toEqual(['trustedServer']); // Should call through to original requestBids expect(mockRequestBids).toHaveBeenCalled(); }); it('does not duplicate trustedServer if already present', () => { - const pbjs = installPrebidNpm(); + const pbjs = installPrebidNpm() as TestPbjs; const adUnits = [{ bids: [{ bidder: 'trustedServer', params: {} }] }]; - pbjs.requestBids({ adUnits } as any); + pbjs.requestBids({ adUnits }); - const tsCount = adUnits[0].bids.filter((b: any) => b.bidder === 'trustedServer').length; + const tsCount = adUnits[0].bids.filter((b) => b.bidder === 'trustedServer').length; expect(tsCount).toBe(1); }); it('captures per-bidder params on trustedServer bid', () => { - const pbjs = installPrebidNpm(); + const pbjs = installPrebidNpm() as TestPbjs; const adUnits = [ { @@ -641,19 +763,21 @@ describe('prebid/installPrebidNpm', () => { ], }, ]; - pbjs.requestBids({ adUnits } as any); + pbjs.requestBids({ adUnits }); - const trustedServerBid = adUnits[0].bids.find((b: any) => b.bidder === 'trustedServer'); + const trustedServerBid = adUnits[0].bids.find( + (b) => b.bidder === 'trustedServer' + ) as TrustedServerTestBid; expect(trustedServerBid).toBeDefined(); expect(trustedServerBid.params.bidderParams).toEqual({ appnexus: { placementId: 123 }, rubicon: { accountId: 'abc' }, }); - expect(adUnits[0].bids.map((b: any) => b.bidder)).toEqual(['trustedServer']); + expect(adUnits[0].bids.map((b) => b.bidder)).toEqual(['trustedServer']); }); it('preserves captured bidder params when requestBids runs twice on the same ad unit', () => { - const pbjs = installPrebidNpm(); + const pbjs = installPrebidNpm() as TestPbjs; // First auction: inline server-side params supplied by the publisher. const adUnits = [ @@ -665,16 +789,16 @@ describe('prebid/installPrebidNpm', () => { ], }, ]; - pbjs.requestBids({ adUnits } as any); + pbjs.requestBids({ adUnits }); // Second auction (refresh/re-auction) with the SAME ad unit object: the // server-side bidder entries were already pruned, so the shim must not // overwrite the captured params with an empty object. - pbjs.requestBids({ adUnits } as any); + pbjs.requestBids({ adUnits }); const trustedServerBid = adUnits[0].bids.find( - (b: any) => b.bidder === 'trustedServer' - ) as any; + (b) => b.bidder === 'trustedServer' + ) as TrustedServerTestBid; expect(trustedServerBid.params.bidderParams).toEqual({ appnexus: { placementId: 123 }, rubicon: { accountId: 'abc' }, @@ -682,26 +806,28 @@ describe('prebid/installPrebidNpm', () => { }); it('adds bids array to ad units that have none', () => { - const pbjs = installPrebidNpm(); + const pbjs = installPrebidNpm() as TestPbjs; - const adUnits = [{ code: 'div-1' }] as any[]; - pbjs.requestBids({ adUnits } as any); + const adUnits = [{ code: 'div-1' }] as TestAdUnit[]; + pbjs.requestBids({ adUnits }); expect(adUnits[0].bids).toHaveLength(1); - expect(adUnits[0].bids[0].bidder).toBe('trustedServer'); + expect(adUnits[0].bids![0].bidder).toBe('trustedServer'); }); it('normalizes a truthy non-array bids value without throwing', () => { - const pbjs = installPrebidNpm(); - const adUnits = [{ code: 'example-malformed-slot', bids: { malformed: true } }] as any[]; + const pbjs = installPrebidNpm() as TestPbjs; + const adUnits = [ + { code: 'example-malformed-slot', bids: { malformed: true } }, + ] as unknown as TestAdUnit[]; - expect(() => pbjs.requestBids({ adUnits } as any)).not.toThrow(); + expect(() => pbjs.requestBids({ adUnits })).not.toThrow(); expect(adUnits[0].bids).toEqual([{ bidder: 'trustedServer', params: { bidderParams: {} } }]); }); it('includes zone from mediaTypes.banner.name in trustedServer params', () => { - const pbjs = installPrebidNpm(); + const pbjs = installPrebidNpm() as TestPbjs; const adUnits = [ { @@ -715,17 +841,21 @@ describe('prebid/installPrebidNpm', () => { bids: [{ bidder: 'kargo', params: { placementId: '_def' } }], }, ]; - pbjs.requestBids({ adUnits } as any); + pbjs.requestBids({ adUnits }); - const tsBid0 = adUnits[0].bids.find((b: any) => b.bidder === 'trustedServer') as any; + const tsBid0 = adUnits[0].bids.find( + (b) => b.bidder === 'trustedServer' + ) as TrustedServerTestBid; expect(tsBid0.params.zone).toBe('header'); - const tsBid1 = adUnits[1].bids.find((b: any) => b.bidder === 'trustedServer') as any; + const tsBid1 = adUnits[1].bids.find( + (b) => b.bidder === 'trustedServer' + ) as TrustedServerTestBid; expect(tsBid1.params.zone).toBe('fixed_bottom'); }); it('omits zone when mediaTypes.banner.name is not set', () => { - const pbjs = installPrebidNpm(); + const pbjs = installPrebidNpm() as TestPbjs; const adUnits = [ { @@ -734,24 +864,28 @@ describe('prebid/installPrebidNpm', () => { bids: [{ bidder: 'appnexus', params: {} }], }, ]; - pbjs.requestBids({ adUnits } as any); + pbjs.requestBids({ adUnits }); - const tsBid = adUnits[0].bids.find((b: any) => b.bidder === 'trustedServer') as any; + const tsBid = adUnits[0].bids.find( + (b) => b.bidder === 'trustedServer' + ) as TrustedServerTestBid; expect(tsBid.params.zone).toBeUndefined(); }); it('omits zone when ad unit has no mediaTypes', () => { - const pbjs = installPrebidNpm(); + const pbjs = installPrebidNpm() as TestPbjs; const adUnits = [{ bids: [{ bidder: 'rubicon', params: {} }] }]; - pbjs.requestBids({ adUnits } as any); + pbjs.requestBids({ adUnits }); - const tsBid = adUnits[0].bids.find((b: any) => b.bidder === 'trustedServer') as any; + const tsBid = adUnits[0].bids.find( + (b) => b.bidder === 'trustedServer' + ) as TrustedServerTestBid; expect(tsBid.params.zone).toBeUndefined(); }); it('clears stale zone when existing trustedServer bid is reused', () => { - const pbjs = installPrebidNpm(); + const pbjs = installPrebidNpm() as TestPbjs; const adUnits = [ { @@ -764,29 +898,27 @@ describe('prebid/installPrebidNpm', () => { }, ]; - pbjs.requestBids({ adUnits } as any); + pbjs.requestBids({ adUnits }); - let tsBid = adUnits[0].bids.find((b: any) => b.bidder === 'trustedServer') as any; + let tsBid = adUnits[0].bids.find((b) => b.bidder === 'trustedServer') as TrustedServerTestBid; expect(tsBid.params.zone).toBe('header'); expect(tsBid.params.custom).toBe('keep'); - delete adUnits[0].mediaTypes.banner.name; - pbjs.requestBids({ adUnits } as any); + delete (adUnits[0].mediaTypes.banner as { name?: string }).name; + pbjs.requestBids({ adUnits }); - tsBid = adUnits[0].bids.find((b: any) => b.bidder === 'trustedServer') as any; + tsBid = adUnits[0].bids.find((b) => b.bidder === 'trustedServer') as TrustedServerTestBid; expect(tsBid.params.zone).toBeUndefined(); expect(tsBid.params.custom).toBe('keep'); }); it('falls back to pbjs.adUnits when requestObj has no adUnits', () => { - const pbjs = installPrebidNpm(); + const pbjs = installPrebidNpm() as TestPbjs; - mockPbjs.adUnits = [{ bids: [{ bidder: 'openx', params: {} }] }] as any[]; - pbjs.requestBids({} as any); + mockPbjs.adUnits = [{ bids: [{ bidder: 'openx', params: {} }] }]; + pbjs.requestBids({}); - const hasTsBidder = (mockPbjs.adUnits[0] as any).bids.some( - (b: any) => b.bidder === 'trustedServer' - ); + const hasTsBidder = mockPbjs.adUnits[0].bids!.some((b) => b.bidder === 'trustedServer'); expect(hasTsBidder).toBe(true); }); @@ -804,8 +936,8 @@ describe('prebid/installPrebidNpm', () => { }, ]); - const pbjs = installPrebidNpm(); - pbjs.requestBids({ adUnits: [{ bids: [{ bidder: 'appnexus', params: {} }] }] } as any); + const pbjs = installPrebidNpm() as TestPbjs; + pbjs.requestBids({ adUnits: [{ bids: [{ bidder: 'appnexus', params: {} }] }] }); const cookieValue = document.cookie.match(/(?:^|; )ts-eids=([^;]+)/)?.[1]; expect(cookieValue).toBeDefined(); @@ -827,8 +959,8 @@ describe('prebid/installPrebidNpm', () => { }); mockGetUserIdsAsEids.mockReturnValue([]); - const pbjs = installPrebidNpm(); - pbjs.requestBids({ adUnits: [{ bids: [{ bidder: 'appnexus', params: {} }] }] } as any); + const pbjs = installPrebidNpm() as TestPbjs; + pbjs.requestBids({ adUnits: [{ bids: [{ bidder: 'appnexus', params: {} }] }] }); expect(document.cookie).toBe(''); }); @@ -843,15 +975,15 @@ describe('prebid/installPrebidNpm with server-injected config', () => { mockGetUserIdsAsEids.mockReset(); mockGetUserIdsAsEids.mockReturnValue([]); document.cookie = 'ts-eids=; Path=/; Max-Age=0'; - delete (window as any).__tsjs_prebid; + delete (window as TestWindow).__tsjs_prebid; }); afterEach(() => { - delete (window as any).__tsjs_prebid; + delete (window as TestWindow).__tsjs_prebid; }); it('reads timeout and debug from window.__tsjs_prebid', () => { - (window as any).__tsjs_prebid = { timeout: 1500, debug: true }; + (window as TestWindow).__tsjs_prebid = { timeout: 1500, debug: true }; installPrebidNpm(); @@ -861,7 +993,7 @@ describe('prebid/installPrebidNpm with server-injected config', () => { }); it('explicit config overrides server-injected values', () => { - (window as any).__tsjs_prebid = { timeout: 1500, debug: true }; + (window as TestWindow).__tsjs_prebid = { timeout: 1500, debug: true }; installPrebidNpm({ timeout: 3000, debug: false }); @@ -884,13 +1016,13 @@ describe('prebid/installRefreshHandler', () => { mockRequestBids.mockReset(); mockPbjs.requestBids = mockRequestBids; mockPbjs.adUnits = []; - (window as any).tsjs = undefined; - delete (window as any).googletag; + (window as TestWindow).tsjs = undefined; + delete (window as TestWindow).googletag; }); afterEach(() => { - (window as any).tsjs = undefined; - delete (window as any).googletag; + (window as TestWindow).tsjs = undefined; + delete (window as TestWindow).googletag; }); it('builds refresh ad units from injected slot metadata', () => { @@ -903,11 +1035,11 @@ describe('prebid/installRefreshHandler', () => { refresh: originalRefresh, getSlots: vi.fn(() => [gptSlot]), }; - (window as any).googletag = { + (window as TestWindow).googletag = { cmd: { push: (fn: () => void) => fn() }, pubads: () => pubads, }; - (window as any).tsjs = { + (window as TestWindow).tsjs = { adSlots: [ { id: 'homepage_header_ad', @@ -961,11 +1093,11 @@ describe('prebid/installRefreshHandler', () => { refresh: originalRefresh, getSlots: vi.fn(() => [gptSlot]), }; - (window as any).googletag = { + (window as TestWindow).googletag = { cmd: { push: (fn: () => void) => fn() }, pubads: () => pubads, }; - (window as any).tsjs = { + (window as TestWindow).tsjs = { adSlots: [ { id: 'prefix_ad', @@ -1006,7 +1138,7 @@ describe('prebid/installRefreshHandler', () => { it('scopes the GPT targeting call to the refreshed slot code', () => { const setTargetingForGPTAsync = vi.fn(); - (mockPbjs as any).setTargetingForGPTAsync = setTargetingForGPTAsync; + mockPbjs.setTargetingForGPTAsync = setTargetingForGPTAsync; // Run the bidsBackHandler synchronously so the targeting call fires. mockRequestBids.mockImplementation((opts?: { bidsBackHandler?: () => void }) => { opts?.bidsBackHandler?.(); @@ -1022,11 +1154,11 @@ describe('prebid/installRefreshHandler', () => { refresh: originalRefresh, getSlots: vi.fn(() => [headerSlot]), }; - (window as any).googletag = { + (window as TestWindow).googletag = { cmd: { push: (fn: () => void) => fn() }, pubads: () => pubads, }; - (window as any).tsjs = { + (window as TestWindow).tsjs = { adSlots: [ { id: 'header_ad', @@ -1052,11 +1184,11 @@ describe('prebid/installRefreshHandler', () => { expect(setTargetingForGPTAsync).toHaveBeenCalledWith(['div-ad-header']); expect(originalRefresh).toHaveBeenCalledWith([headerSlot], undefined); - delete (mockPbjs as any).setTargetingForGPTAsync; + delete mockPbjs.setTargetingForGPTAsync; }); it('includes configured client-side bidders in refresh ad units', () => { - (window as any).__tsjs_prebid = { clientSideBidders: ['rubicon'] }; + (window as TestWindow).__tsjs_prebid = { clientSideBidders: ['rubicon'] }; // Original publisher ad unit carries a client-side rubicon bid. mockPbjs.adUnits = [ { @@ -1076,11 +1208,11 @@ describe('prebid/installRefreshHandler', () => { refresh: originalRefresh, getSlots: vi.fn(() => [gptSlot]), }; - (window as any).googletag = { + (window as TestWindow).googletag = { cmd: { push: (fn: () => void) => fn() }, pubads: () => pubads, }; - (window as any).tsjs = { + (window as TestWindow).tsjs = { adSlots: [ { id: 'homepage_header_ad', @@ -1109,7 +1241,7 @@ describe('prebid/installRefreshHandler', () => { }) ); - delete (window as any).__tsjs_prebid; + delete (window as TestWindow).__tsjs_prebid; mockPbjs.adUnits = []; }); @@ -1131,11 +1263,11 @@ describe('prebid/installRefreshHandler', () => { refresh: originalRefresh, getSlots: vi.fn(() => [gptSlot]), }; - (window as any).googletag = { + (window as TestWindow).googletag = { cmd: { push: (fn: () => void) => fn() }, pubads: () => pubads, }; - (window as any).tsjs = { + (window as TestWindow).tsjs = { adSlots: [ { id: 'homepage_header_ad', @@ -1177,7 +1309,7 @@ describe('prebid/installRefreshHandler', () => { // publisher's Prebid ad unit is keyed by the inner div_id. The synthetic // refresh code stays the GPT element id (so GPT can match it), while params // and client-side bids are recovered from the injected div_id candidate. - (window as any).__tsjs_prebid = { clientSideBidders: ['rubicon'] }; + (window as TestWindow).__tsjs_prebid = { clientSideBidders: ['rubicon'] }; mockPbjs.adUnits = [ { code: 'div-ad-x', @@ -1196,11 +1328,11 @@ describe('prebid/installRefreshHandler', () => { refresh: originalRefresh, getSlots: vi.fn(() => [gptSlot]), }; - (window as any).googletag = { + (window as TestWindow).googletag = { cmd: { push: (fn: () => void) => fn() }, pubads: () => pubads, }; - (window as any).tsjs = { + (window as TestWindow).tsjs = { adSlots: [ { id: 'x_ad', @@ -1236,7 +1368,7 @@ describe('prebid/installRefreshHandler', () => { }) ); - delete (window as any).__tsjs_prebid; + delete (window as TestWindow).__tsjs_prebid; mockPbjs.adUnits = []; }); @@ -1264,11 +1396,11 @@ describe('prebid/installRefreshHandler', () => { refresh: originalRefresh, getSlots: vi.fn(() => [gptSlot]), }; - (window as any).googletag = { + (window as TestWindow).googletag = { cmd: { push: (fn: () => void) => fn() }, pubads: () => pubads, }; - (window as any).tsjs = { + (window as TestWindow).tsjs = { adSlots: [ { id: 'homepage_header_ad', @@ -1326,12 +1458,12 @@ describe('prebid/installRefreshHandler', () => { getSlots: vi.fn(() => [gptSlot]), }; const setTargetingForGPTAsync = vi.fn(); - (mockPbjs as any).setTargetingForGPTAsync = setTargetingForGPTAsync; - (window as any).googletag = { + mockPbjs.setTargetingForGPTAsync = setTargetingForGPTAsync; + (window as TestWindow).googletag = { cmd: { push: (fn: () => void) => fn() }, pubads: () => pubads, }; - (window as any).tsjs = { + (window as TestWindow).tsjs = { adSlots: [ { id: 'homepage_header_ad', @@ -1396,11 +1528,11 @@ describe('prebid/installRefreshHandler', () => { refresh: originalRefresh, getSlots: vi.fn(() => [gptSlot]), }; - (window as any).googletag = { + (window as TestWindow).googletag = { cmd: { push: (fn: () => void) => fn() }, pubads: () => pubads, }; - (window as any).tsjs = { adInitRefreshInProgress: true }; + (window as TestWindow).tsjs = { adInitRefreshInProgress: true }; installRefreshHandler(750); pubads.refresh([gptSlot]); @@ -1421,11 +1553,11 @@ describe('prebid/installRefreshHandler', () => { refresh: originalRefresh, getSlots: vi.fn(() => [gptSlot]), }; - (window as any).googletag = { + (window as TestWindow).googletag = { cmd: { push: (fn: () => void) => fn() }, pubads: () => pubads, }; - (window as any).tsjs = { adInitRefreshInProgress: false }; + (window as TestWindow).tsjs = { adInitRefreshInProgress: false }; installRefreshHandler(750); pubads.refresh([gptSlot]); @@ -1437,7 +1569,7 @@ describe('prebid/installRefreshHandler', () => { describe('prebid publisher snapshots and delivery refreshes', () => { let deliveryAdIds = new WeakMap(); - let installedGptSlots: any[] = []; + let installedGptSlots: TestGptSlot[] = []; let auctionSequence = 0; beforeEach(() => { @@ -1448,24 +1580,24 @@ describe('prebid publisher snapshots and delivery refreshes', () => { mockRequestBids.mockReset(); mockPbjs.requestBids = mockRequestBids; mockPbjs.removeAdUnit = mockRemoveAdUnit; - delete (mockPbjs as any).__tsRemoveAdUnitWrapped; + delete mockPbjs.__tsRemoveAdUnitWrapped; mockPbjs.adUnits = []; mockGetUserIdsAsEids.mockReset(); mockGetUserIdsAsEids.mockReturnValue([]); mockGetBidAdapter.mockReturnValue({}); - delete (mockPbjs as any).setTargetingForGPTAsync; - delete (window as any).__tsjs_prebid; - (window as any).tsjs = undefined; - delete (window as any).googletag; + delete mockPbjs.setTargetingForGPTAsync; + delete (window as TestWindow).__tsjs_prebid; + (window as TestWindow).tsjs = undefined; + delete (window as TestWindow).googletag; }); afterEach(() => { - delete (window as any).__tsjs_prebid; - (window as any).tsjs = undefined; - delete (window as any).googletag; + delete (window as TestWindow).__tsjs_prebid; + (window as TestWindow).tsjs = undefined; + delete (window as TestWindow).googletag; }); - function installGpt(slots: any[]) { + function installGpt(slots: TestGptSlot[]) { installedGptSlots = slots; for (const slot of slots) { if (!slot || typeof slot !== 'object') continue; @@ -1482,7 +1614,7 @@ describe('prebid publisher snapshots and delivery refreshes', () => { refresh: originalRefresh, getSlots: vi.fn(() => slots), }; - (window as any).googletag = { + (window as TestWindow).googletag = { cmd: { push: (fn: () => void) => fn() }, pubads: () => pubads, }; @@ -1490,17 +1622,20 @@ describe('prebid publisher snapshots and delivery refreshes', () => { return { originalRefresh, pubads }; } - function refreshAdUnitFromLastRequest(): any { + function refreshAdUnitFromLastRequest(): RefreshAdUnit { const lastCall = mockRequestBids.mock.calls[mockRequestBids.mock.calls.length - 1]; return lastCall?.[0]?.adUnits?.[0]; } function completePublisherAuction( - opts?: { adUnits?: Array<{ code?: string }>; bidsBackHandler?: (...args: any[]) => void }, + opts?: { adUnits?: Array<{ code?: string }>; bidsBackHandler?: (...args: unknown[]) => void }, options: { auctionId?: string; applyTargeting?: boolean } = {} ): void { const auctionId = options.auctionId ?? `example-auction-${auctionSequence++}`; - const bidResponses: Record = {}; + const bidResponses: Record< + string, + { bids: Array<{ adId: string; adUnitCode: string; auctionId: string }> } + > = {}; for (const unit of opts?.adUnits ?? []) { if (!unit.code) continue; @@ -1521,7 +1656,7 @@ describe('prebid publisher snapshots and delivery refreshes', () => { } it('recovers inline params, ordered client bids, and zone when pbjs.adUnits is empty', () => { - (window as any).__tsjs_prebid = { clientSideBidders: ['exampleBrowser'] }; + (window as TestWindow).__tsjs_prebid = { clientSideBidders: ['exampleBrowser'] }; const runtimeInstance = 'example-runtime-instance'; const code = `example-slot-${runtimeInstance}`; const slot = { @@ -1531,7 +1666,7 @@ describe('prebid publisher snapshots and delivery refreshes', () => { clearTargeting: vi.fn(), }; const { pubads } = installGpt([slot]); - const pbjs = installPrebidNpm(); + const pbjs = installPrebidNpm() as TestPbjs; const firstParams = { placement: 'first' }; const effectiveParams = { placement: 'effective' }; @@ -1548,7 +1683,7 @@ describe('prebid publisher snapshots and delivery refreshes', () => { ], }, ], - } as any); + }); effectiveParams.placement = 'changed-after-auction'; pubads.refresh([slot]); @@ -1573,7 +1708,7 @@ describe('prebid publisher snapshots and delivery refreshes', () => { }); it('isolates nested bidder-param objects and arrays from later publisher mutation', () => { - (window as any).__tsjs_prebid = { clientSideBidders: ['exampleBrowser'] }; + (window as TestWindow).__tsjs_prebid = { clientSideBidders: ['exampleBrowser'] }; const code = 'example-nested-params-slot'; const slot = { getSlotElementId: () => code, @@ -1582,7 +1717,7 @@ describe('prebid publisher snapshots and delivery refreshes', () => { clearTargeting: vi.fn(), }; const { pubads } = installGpt([slot]); - const pbjs = installPrebidNpm(); + const pbjs = installPrebidNpm() as TestPbjs; const serverParams = { placement: { rules: [{ label: 'original-rule' }], @@ -1603,7 +1738,7 @@ describe('prebid publisher snapshots and delivery refreshes', () => { ], }, ], - } as any); + }); serverParams.placement.rules[0].label = 'changed-rule'; serverParams.placement.sizes.push(999); browserParams.groups[0].values[0] = 'changed-value'; @@ -1632,10 +1767,14 @@ describe('prebid publisher snapshots and delivery refreshes', () => { const firstRefreshBids = refreshAdUnitFromLastRequest().bids; expect(firstRefreshBids).toEqual(expectedBids); - firstRefreshBids[0].params.bidderParams.exampleServer.placement.rules[0].label = - 'changed-refresh-rule'; - firstRefreshBids[0].params.bidderParams.exampleServer.placement.sizes.push(777); - firstRefreshBids[1].params.groups[0].values[0] = 'changed-refresh-value'; + ( + firstRefreshBids[0].params as NestedServerParams + ).bidderParams.exampleServer.placement.rules[0].label = 'changed-refresh-rule'; + ( + firstRefreshBids[0].params as NestedServerParams + ).bidderParams.exampleServer.placement.sizes.push(777); + (firstRefreshBids[1].params as NestedBrowserParams).groups[0].values[0] = + 'changed-refresh-value'; pubads.refresh([slot]); expect(refreshAdUnitFromLastRequest().bids).toEqual(expectedBids); @@ -1650,7 +1789,7 @@ describe('prebid publisher snapshots and delivery refreshes', () => { clearTargeting: vi.fn(), }; const { pubads } = installGpt([slot]); - const pbjs = installPrebidNpm(); + const pbjs = installPrebidNpm() as TestPbjs; pbjs.requestBids({ adUnits: [ @@ -1660,7 +1799,7 @@ describe('prebid publisher snapshots and delivery refreshes', () => { bids: [{ bidder: 'exampleServer', params: { placement: 'one' } }], }, ], - } as any); + }); pubads.refresh([slot]); expect(refreshAdUnitFromLastRequest().bids[0].params).toEqual({ bidderParams: { exampleServer: { placement: 'one' } }, @@ -1681,7 +1820,7 @@ describe('prebid publisher snapshots and delivery refreshes', () => { bids: [{ bidder: 'exampleServer', params: { placement: 'two' } }], }, ], - } as any); + }); pubads.refresh([slot]); expect(refreshAdUnitFromLastRequest().bids[0].params).toEqual({ @@ -1710,7 +1849,7 @@ describe('prebid publisher snapshots and delivery refreshes', () => { clearTargeting: vi.fn(), }; const { pubads } = installGpt([slotOne, slotTwo, globalSlot]); - const pbjs = installPrebidNpm(); + const pbjs = installPrebidNpm() as TestPbjs; pbjs.requestBids({ adUnits: [ @@ -1723,7 +1862,7 @@ describe('prebid publisher snapshots and delivery refreshes', () => { bids: [{ bidder: 'exampleServer', params: { placement: 'two' } }], }, ], - } as any); + }); mockPbjs.adUnits = [ { code: 'example-global-code', @@ -1746,7 +1885,7 @@ describe('prebid publisher snapshots and delivery refreshes', () => { }); it('prefers a rich live unit when a fresh same-code request overwrites the snapshot with empty bids', () => { - (window as any).__tsjs_prebid = { clientSideBidders: ['exampleBrowser'] }; + (window as TestWindow).__tsjs_prebid = { clientSideBidders: ['exampleBrowser'] }; const code = 'example-live-rich-slot'; const slot = { getSlotElementId: () => code, @@ -1763,10 +1902,10 @@ describe('prebid publisher snapshots and delivery refreshes', () => { ], }; mockPbjs.adUnits = [liveUnit]; - const pbjs = installPrebidNpm(); + const pbjs = installPrebidNpm() as TestPbjs; pbjs.requestBids(); - pbjs.requestBids({ adUnits: [{ code, bids: [] }] } as any); + pbjs.requestBids({ adUnits: [{ code, bids: [] }] }); pubads.refresh([slot]); expect(refreshAdUnitFromLastRequest().bids).toEqual([ @@ -1787,11 +1926,11 @@ describe('prebid publisher snapshots and delivery refreshes', () => { clearTargeting: vi.fn(), }; const { pubads } = installGpt([slot]); - const pbjs = installPrebidNpm(); + const pbjs = installPrebidNpm() as TestPbjs; pbjs.requestBids({ adUnits: [{ code, bids: [{ bidder: 'exampleServer', params: { placement: 'snapshot' } }] }], - } as any); + }); mockPbjs.adUnits = [{ code, bids: [] }]; pubads.refresh([slot]); @@ -1809,16 +1948,16 @@ describe('prebid publisher snapshots and delivery refreshes', () => { clearTargeting: vi.fn(), })); const { pubads } = installGpt(slots); - const pbjs = installPrebidNpm(); + const pbjs = installPrebidNpm() as TestPbjs; pbjs.requestBids({ adUnits: codes.map((code) => ({ code, bids: [{ bidder: 'exampleServer', params: { placement: code } }], })), - } as any); - (pbjs as any).removeAdUnit(codes[0]); - (pbjs as any).removeAdUnit([codes[1]]); + }); + pbjs.removeAdUnit(codes[0]); + pbjs.removeAdUnit([codes[1]]); pubads.refresh([slots[0]]); expect(refreshAdUnitFromLastRequest().bids[0].params).toEqual({ bidderParams: {} }); @@ -1829,7 +1968,7 @@ describe('prebid publisher snapshots and delivery refreshes', () => { exampleServer: { placement: codes[2] }, }); - (pbjs as any).removeAdUnit(); + pbjs.removeAdUnit(); pubads.refresh([slots[2]]); expect(refreshAdUnitFromLastRequest().bids[0].params).toEqual({ bidderParams: {} }); }); @@ -1851,7 +1990,7 @@ describe('prebid publisher snapshots and delivery refreshes', () => { clearTargeting: vi.fn(), }; const { pubads } = installGpt([oldestSlot, activeSlot]); - const pbjs = installPrebidNpm(); + const pbjs = installPrebidNpm() as TestPbjs; for (let index = 0; index < capacity; index += 1) { pbjs.requestBids({ @@ -1861,7 +2000,7 @@ describe('prebid publisher snapshots and delivery refreshes', () => { bids: [{ bidder: 'exampleServer', params: { placement: index } }], }, ], - } as any); + }); } pubads.refresh([activeSlot]); @@ -1872,7 +2011,7 @@ describe('prebid publisher snapshots and delivery refreshes', () => { bids: [{ bidder: 'exampleServer', params: { placement: capacity } }], }, ], - } as any); + }); pubads.refresh([oldestSlot]); expect(refreshAdUnitFromLastRequest().bids[0].params).toEqual({ bidderParams: {} }); @@ -1893,12 +2032,12 @@ describe('prebid publisher snapshots and delivery refreshes', () => { getTargeting: () => [], clearTargeting: vi.fn(), }; - (window as any).tsjs = { + (window as TestWindow).tsjs = { adSlots: [{ div_id: 'example-covered-two', formats: [[300, 250]], targeting: {} }], }; const { originalRefresh, pubads } = installGpt([slotOne, slotTwo]); mockRequestBids.mockImplementation((opts) => completePublisherAuction(opts)); - const pbjs = installPrebidNpm(); + const pbjs = installPrebidNpm() as TestPbjs; pbjs.requestBids({ adUnits: [ @@ -1909,7 +2048,7 @@ describe('prebid publisher snapshots and delivery refreshes', () => { pubads.refresh([slotOne]); pubads.refresh([slotTwo]); }, - } as any); + }); expect(mockRequestBids).toHaveBeenCalledTimes(1); expect(slotOne.clearTargeting).not.toHaveBeenCalled(); @@ -1928,11 +2067,11 @@ describe('prebid publisher snapshots and delivery refreshes', () => { }; const { originalRefresh, pubads } = installGpt([slot]); mockRequestBids.mockImplementation((opts) => completePublisherAuction(opts)); - const pbjs = installPrebidNpm(); + const pbjs = installPrebidNpm() as TestPbjs; pbjs.requestBids({ adUnits: [{ code, bids: [{ bidder: 'exampleServer', params: {} }] }], - } as any); + }); pubads.refresh([slot]); expect(mockRequestBids).toHaveBeenCalledTimes(1); @@ -1961,12 +2100,12 @@ describe('prebid publisher snapshots and delivery refreshes', () => { syntheticBidsBackHandler = opts.bidsBackHandler; } }); - const pbjs = installPrebidNpm(); + const pbjs = installPrebidNpm() as TestPbjs; pbjs.requestBids({ adUnits: [{ code: 'example-sra-delivery', bids: [{ bidder: 'exampleServer', params: {} }] }], bidsBackHandler: () => pubads.refresh([deliverySlot, independentSlot], refreshOptions), - } as any); + }); expect(originalRefresh).not.toHaveBeenCalled(); expect(independentSlot.clearTargeting).toHaveBeenCalledWith('hb_adid'); @@ -1990,12 +2129,12 @@ describe('prebid publisher snapshots and delivery refreshes', () => { }; const { originalRefresh, pubads } = installGpt([coveredSlot, gamOnlySlot]); mockRequestBids.mockImplementation((opts) => completePublisherAuction(opts)); - const pbjs = installPrebidNpm(); + const pbjs = installPrebidNpm() as TestPbjs; pbjs.requestBids({ adUnits: [{ code: 'example-covered', bids: [{ bidder: 'exampleServer', params: {} }] }], bidsBackHandler: () => pubads.refresh(), - } as any); + }); expect(mockRequestBids).toHaveBeenCalledTimes(2); expect(coveredSlot.clearTargeting).not.toHaveBeenCalled(); @@ -2019,7 +2158,7 @@ describe('prebid publisher snapshots and delivery refreshes', () => { }; const { originalRefresh, pubads } = installGpt([coveredSlot, unrelatedSlot]); mockRequestBids.mockImplementation((opts) => completePublisherAuction(opts)); - const pbjs = installPrebidNpm(); + const pbjs = installPrebidNpm() as TestPbjs; pbjs.requestBids({ adUnits: [{ code: 'example-covered', bids: [{ bidder: 'exampleServer', params: {} }] }], @@ -2027,13 +2166,13 @@ describe('prebid publisher snapshots and delivery refreshes', () => { pubads.refresh([unrelatedSlot]); pubads.refresh([coveredSlot, unrelatedSlot]); }, - } as any); + }); expect(mockRequestBids).toHaveBeenCalledTimes(3); - expect(mockRequestBids.mock.calls[1][0].adUnits.map((unit: any) => unit.code)).toEqual([ + expect(mockRequestBids.mock.calls[1][0].adUnits.map((unit: TestAdUnit) => unit.code)).toEqual([ 'example-unrelated', ]); - expect(mockRequestBids.mock.calls[2][0].adUnits.map((unit: any) => unit.code)).toEqual([ + expect(mockRequestBids.mock.calls[2][0].adUnits.map((unit: TestAdUnit) => unit.code)).toEqual([ 'example-unrelated', ]); expect(coveredSlot.clearTargeting).not.toHaveBeenCalled(); @@ -2058,7 +2197,7 @@ describe('prebid publisher snapshots and delivery refreshes', () => { const refreshSlots = [...coveredSlots, gamOnlySlot]; const { originalRefresh, pubads } = installGpt(refreshSlots); mockRequestBids.mockImplementation((opts) => completePublisherAuction(opts)); - const pbjs = installPrebidNpm(); + const pbjs = installPrebidNpm() as TestPbjs; pbjs.requestBids({ adUnits: coveredSlots.map((_, index) => ({ @@ -2066,7 +2205,7 @@ describe('prebid publisher snapshots and delivery refreshes', () => { bids: [{ bidder: 'exampleServer', params: { placement: index } }], })), bidsBackHandler: () => pubads.refresh(refreshSlots), - } as any); + }); expect(mockRequestBids).toHaveBeenCalledTimes(2); coveredSlots.forEach((slot) => expect(slot.clearTargeting).not.toHaveBeenCalled()); @@ -2089,11 +2228,11 @@ describe('prebid publisher snapshots and delivery refreshes', () => { mockRequestBids.mockImplementation((opts) => completePublisherAuction(opts, { applyTargeting: false }) ); - const pbjs = installPrebidNpm(); + const pbjs = installPrebidNpm() as TestPbjs; pbjs.requestBids({ adUnits: [{ code, bids: [{ bidder: 'exampleServer', params: {} }] }], - } as any); + }); vi.advanceTimersByTime(5001); pubads.refresh([slot]); @@ -2118,11 +2257,11 @@ describe('prebid publisher snapshots and delivery refreshes', () => { }; const { originalRefresh, pubads } = installGpt([slot]); mockRequestBids.mockImplementation((opts) => completePublisherAuction(opts)); - const pbjs = installPrebidNpm(); + const pbjs = installPrebidNpm() as TestPbjs; pbjs.requestBids({ adUnits: [{ code, bids: [{ bidder: 'exampleServer', params: {} }] }], - } as any); + }); vi.advanceTimersByTime(5001); pubads.refresh([slot]); @@ -2150,7 +2289,7 @@ describe('prebid publisher snapshots and delivery refreshes', () => { mockRequestBids.mockImplementation((opts) => completePublisherAuction(opts, { auctionId, applyTargeting: false }) ); - const pbjs = installPrebidNpm(); + const pbjs = installPrebidNpm() as TestPbjs; pbjs.requestBids({ adUnits: [{ code, bids: [{ bidder: 'exampleServer', params: {} }] }], @@ -2160,7 +2299,7 @@ describe('prebid publisher snapshots and delivery refreshes', () => { pubads.refresh([slot]); }, 1500); }, - } as any); + }); vi.advanceTimersByTime(1500); @@ -2191,28 +2330,28 @@ describe('prebid publisher snapshots and delivery refreshes', () => { const setTargetingForGPTAsync = vi.fn(() => { deliveryAdIds.set(slot, `${auctionId}-${code}`); }); - (mockPbjs as any).setTargetingForGPTAsync = setTargetingForGPTAsync; + mockPbjs.setTargetingForGPTAsync = setTargetingForGPTAsync; mockRequestBids.mockImplementation((opts) => completePublisherAuction(opts, { auctionId, applyTargeting: false }) ); - const pbjs = installPrebidNpm(); + const pbjs = installPrebidNpm() as TestPbjs; pbjs.requestBids({ adUnits: [{ code, bids: [{ bidder: 'exampleServer', params: {} }] }], bidsBackHandler: () => { - (pbjs as any).setTargetingForGPTAsync(null, () => () => true); + pbjs.setTargetingForGPTAsync(null, () => () => true); pubads.refresh([slot]); }, - } as any); + }); auctionId = 'example-no-argument-auction'; pbjs.requestBids({ adUnits: [{ code, bids: [{ bidder: 'exampleServer', params: {} }] }], bidsBackHandler: () => { - (pbjs as any).setTargetingForGPTAsync(); + pbjs.setTargetingForGPTAsync(); pubads.refresh([slot]); }, - } as any); + }); expect(setTargetingForGPTAsync).toHaveBeenNthCalledWith(1, null, expect.any(Function)); expect(setTargetingForGPTAsync).toHaveBeenNthCalledWith(2); @@ -2220,7 +2359,7 @@ describe('prebid publisher snapshots and delivery refreshes', () => { expect(slot.clearTargeting).not.toHaveBeenCalled(); expect(originalRefresh).toHaveBeenNthCalledWith(1, [slot], undefined); expect(originalRefresh).toHaveBeenNthCalledWith(2, [slot], undefined); - delete (mockPbjs as any).setTargetingForGPTAsync; + delete mockPbjs.setTargetingForGPTAsync; }); it('correlates requested no-bid slots without manufacturing unrelated bid state', () => { @@ -2231,17 +2370,19 @@ describe('prebid publisher snapshots and delivery refreshes', () => { clearTargeting: vi.fn(), }; const { originalRefresh, pubads } = installGpt([slot]); - mockRequestBids.mockImplementation((opts?: { bidsBackHandler?: (...args: any[]) => void }) => { - opts?.bidsBackHandler?.({ 'example-no-bid-delivery': { bids: [null, {}] } }, false, 'bad'); - }); - const pbjs = installPrebidNpm(); + mockRequestBids.mockImplementation( + (opts?: { bidsBackHandler?: (...args: unknown[]) => void }) => { + opts?.bidsBackHandler?.({ 'example-no-bid-delivery': { bids: [null, {}] } }, false, 'bad'); + } + ); + const pbjs = installPrebidNpm() as TestPbjs; pbjs.requestBids({ adUnits: [ { code: 'example-no-bid-delivery', bids: [{ bidder: 'exampleServer', params: {} }] }, ], bidsBackHandler: () => pubads.refresh([slot]), - } as any); + }); expect(mockRequestBids).toHaveBeenCalledTimes(1); expect(slot.clearTargeting).not.toHaveBeenCalled(); @@ -2260,13 +2401,13 @@ describe('prebid publisher snapshots and delivery refreshes', () => { mockRequestBids.mockImplementation((opts) => completePublisherAuction(opts, { applyTargeting: false }) ); - const pbjs = installPrebidNpm(); + const pbjs = installPrebidNpm() as TestPbjs; // Model an initial impression rendered with display() after an auction // that did not apply hb_adid targeting. Its code-only state is unconsumed. pbjs.requestBids({ adUnits: [{ code, bids: [{ bidder: 'exampleServer', params: {} }] }], - } as any); + }); pubads.refresh([slot]); expect(mockRequestBids).toHaveBeenCalledTimes(1); @@ -2291,12 +2432,12 @@ describe('prebid publisher snapshots and delivery refreshes', () => { mockRequestBids.mockImplementation((opts) => completePublisherAuction(opts, { applyTargeting: false }) ); - const pbjs = installPrebidNpm(); + const pbjs = installPrebidNpm() as TestPbjs; pbjs.requestBids({ adUnits: [{ code, bids: [{ bidder: 'exampleServer', params: {} }] }], bidsBackHandler: () => pubads.refresh([slot]), - } as any); + }); expect(mockRequestBids).toHaveBeenCalledTimes(2); expect(slot.clearTargeting).toHaveBeenCalledWith('hb_adid'); @@ -2328,11 +2469,11 @@ describe('prebid publisher snapshots and delivery refreshes', () => { } completePublisherAuction(opts); }); - const pbjs = installPrebidNpm(); + const pbjs = installPrebidNpm() as TestPbjs; pbjs.requestBids({ adUnits: [{ code, bids: [{ bidder: 'exampleServer', params: {} }] }], - } as any); + }); deliveryAdIds.set(slot, oldestAdId); pubads.refresh([slot]); @@ -2360,7 +2501,7 @@ describe('prebid publisher snapshots and delivery refreshes', () => { const refreshSlots = [innerSlot, outerSlot, gamOnlySlot]; const { originalRefresh, pubads } = installGpt(refreshSlots); mockRequestBids.mockImplementation((opts) => completePublisherAuction(opts)); - const pbjs = installPrebidNpm(); + const pbjs = installPrebidNpm() as TestPbjs; pbjs.requestBids({ adUnits: [ @@ -2372,9 +2513,9 @@ describe('prebid publisher snapshots and delivery refreshes', () => { { code: 'example-inner-delivery', bids: [{ bidder: 'exampleServer', params: {} }] }, ], bidsBackHandler: () => pubads.refresh(refreshSlots), - } as any); + }); }, - } as any); + }); expect(mockRequestBids).toHaveBeenCalledTimes(3); expect(innerSlot.clearTargeting).not.toHaveBeenCalled(); @@ -2395,7 +2536,7 @@ describe('prebid publisher snapshots and delivery refreshes', () => { mockRequestBids.mockImplementation((opts) => completePublisherAuction(opts, { applyTargeting: false }) ); - const pbjs = installPrebidNpm(); + const pbjs = installPrebidNpm() as TestPbjs; let deferredRefresh: Promise | undefined; pbjs.requestBids({ @@ -2405,7 +2546,7 @@ describe('prebid publisher snapshots and delivery refreshes', () => { bidsBackHandler: () => { deferredRefresh = Promise.resolve().then(() => pubads.refresh([slot])); }, - } as any); + }); await deferredRefresh; expect(mockRequestBids).toHaveBeenCalledTimes(1); @@ -2427,7 +2568,7 @@ describe('prebid publisher snapshots and delivery refreshes', () => { mockRequestBids.mockImplementation((opts) => completePublisherAuction(opts, { auctionId, applyTargeting: false }) ); - const pbjs = installPrebidNpm(); + const pbjs = installPrebidNpm() as TestPbjs; let deferredRefresh: Promise | undefined; pbjs.requestBids({ @@ -2438,7 +2579,7 @@ describe('prebid publisher snapshots and delivery refreshes', () => { pubads.refresh([slot]); }); }, - } as any); + }); await deferredRefresh; expect(mockRequestBids).toHaveBeenCalledTimes(1); @@ -2456,16 +2597,16 @@ describe('prebid publisher snapshots and delivery refreshes', () => { }; const { originalRefresh, pubads } = installGpt([slot]); mockRequestBids.mockImplementation((opts) => completePublisherAuction(opts)); - const pbjs = installPrebidNpm(); + const pbjs = installPrebidNpm() as TestPbjs; pbjs.requestBids({ adUnits: [{ code, bids: [{ bidder: 'exampleServer', params: {} }] }], bidsBackHandler: () => {}, - } as any); + }); pbjs.requestBids({ adUnits: [{ code, bids: [{ bidder: 'exampleServer', params: {} }] }], bidsBackHandler: () => {}, - } as any); + }); pubads.refresh([slot]); expect(mockRequestBids).toHaveBeenCalledTimes(2); @@ -2490,12 +2631,12 @@ describe('prebid publisher snapshots and delivery refreshes', () => { }; const { originalRefresh, pubads } = installGpt([slot]); mockRequestBids.mockImplementation((opts) => completePublisherAuction(opts)); - const pbjs = installPrebidNpm(); + const pbjs = installPrebidNpm() as TestPbjs; pbjs.requestBids({ adUnits: [{ code, bids: [{ bidder: 'exampleServer', params: {} }] }], - bidsBackHandler: () => pubads.refresh([slot, undefined, null] as any), - } as any); + bidsBackHandler: () => pubads.refresh([slot, undefined, null]), + }); expect(mockRequestBids).toHaveBeenCalledTimes(1); expect(slot.clearTargeting).not.toHaveBeenCalled(); @@ -2515,13 +2656,13 @@ describe('prebid publisher snapshots and delivery refreshes', () => { }; const { originalRefresh, pubads } = installGpt([slot]); mockRequestBids.mockImplementation((opts) => completePublisherAuction(opts)); - const pbjs = installPrebidNpm(); + const pbjs = installPrebidNpm() as TestPbjs; const request = { adUnits: [{ code, bids: [{ bidder: 'exampleServer', params: {} }] }], }; - pbjs.requestBids(request as any); - pbjs.requestBids(request as any); + pbjs.requestBids(request); + pbjs.requestBids(request); pubads.refresh([slot]); expect(request).not.toHaveProperty('bidsBackHandler'); @@ -2537,7 +2678,7 @@ describe('prebid publisher snapshots and delivery refreshes', () => { }; const { originalRefresh, pubads } = installGpt([slot]); const setTargetingForGPTAsync = vi.fn(); - (mockPbjs as any).setTargetingForGPTAsync = setTargetingForGPTAsync; + mockPbjs.setTargetingForGPTAsync = setTargetingForGPTAsync; mockRequestBids.mockImplementation(() => { throw new Error('example synthetic failure'); }); @@ -2561,7 +2702,7 @@ describe('prebid publisher snapshots and delivery refreshes', () => { }; const { originalRefresh, pubads } = installGpt([slot]); const setTargetingForGPTAsync = vi.fn(); - (mockPbjs as any).setTargetingForGPTAsync = setTargetingForGPTAsync; + mockPbjs.setTargetingForGPTAsync = setTargetingForGPTAsync; mockRequestBids.mockImplementation(() => undefined); installPrebidNpm(); @@ -2591,7 +2732,7 @@ describe('prebid publisher snapshots and delivery refreshes', () => { }; const { originalRefresh, pubads } = installGpt([slot]); const setTargetingForGPTAsync = vi.fn(); - (mockPbjs as any).setTargetingForGPTAsync = setTargetingForGPTAsync; + mockPbjs.setTargetingForGPTAsync = setTargetingForGPTAsync; let syntheticBidsBackHandler: (() => void) | undefined; mockRequestBids.mockImplementation((opts) => { syntheticBidsBackHandler = opts.bidsBackHandler; @@ -2621,7 +2762,7 @@ describe('prebid publisher snapshots and delivery refreshes', () => { clearTargeting: vi.fn(), }; const { originalRefresh, pubads } = installGpt([slot]); - (mockPbjs as any).setTargetingForGPTAsync = vi.fn(() => { + mockPbjs.setTargetingForGPTAsync = vi.fn(() => { throw new Error('example targeting failure'); }); mockRequestBids.mockImplementation((opts) => completePublisherAuction(opts)); @@ -2634,10 +2775,10 @@ describe('prebid publisher snapshots and delivery refreshes', () => { }); it('does not stack the removeAdUnit lifecycle wrapper across installation', () => { - const pbjs = installPrebidNpm(); + const pbjs = installPrebidNpm() as TestPbjs; installPrebidNpm(); - (pbjs as any).removeAdUnit('example-reinstalled-slot'); + pbjs.removeAdUnit('example-reinstalled-slot'); expect(mockRemoveAdUnit).toHaveBeenCalledTimes(1); }); @@ -2655,7 +2796,7 @@ describe('prebid publisher snapshots and delivery refreshes', () => { }; const { originalRefresh, pubads } = installGpt([outerSlot, innerSlot]); mockRequestBids.mockImplementation((opts) => completePublisherAuction(opts)); - const pbjs = installPrebidNpm(); + const pbjs = installPrebidNpm() as TestPbjs; pbjs.requestBids({ adUnits: [ @@ -2667,10 +2808,10 @@ describe('prebid publisher snapshots and delivery refreshes', () => { { code: 'example-inner-delivery', bids: [{ bidder: 'exampleServer', params: {} }] }, ], bidsBackHandler: () => pubads.refresh([innerSlot]), - } as any); + }); pubads.refresh([outerSlot]); }, - } as any); + }); expect(mockRequestBids).toHaveBeenCalledTimes(2); expect(innerSlot.clearTargeting).not.toHaveBeenCalled(); @@ -2690,7 +2831,7 @@ describe('prebid publisher snapshots and delivery refreshes', () => { mockRequestBids.mockImplementation((opts) => completePublisherAuction(opts, { applyTargeting: false }) ); - const pbjs = installPrebidNpm(); + const pbjs = installPrebidNpm() as TestPbjs; expect(() => pbjs.requestBids({ @@ -2703,7 +2844,7 @@ describe('prebid publisher snapshots and delivery refreshes', () => { bidsBackHandler: () => { throw new Error('example callback failure'); }, - } as any) + }) ).toThrow('example callback failure'); pubads.refresh([slot]); @@ -2741,17 +2882,17 @@ describe('prebid/client-side bidders', () => { mockGetUserIdsAsEids.mockReturnValue([]); // By default, pretend all adapters are registered mockGetBidAdapter.mockReturnValue({}); - delete (window as any).__tsjs_prebid; + delete (window as TestWindow).__tsjs_prebid; }); afterEach(() => { - delete (window as any).__tsjs_prebid; + delete (window as TestWindow).__tsjs_prebid; }); it('excludes client-side bidders from trustedServer bidderParams', () => { - (window as any).__tsjs_prebid = { clientSideBidders: ['rubicon'] }; + (window as TestWindow).__tsjs_prebid = { clientSideBidders: ['rubicon'] }; - const pbjs = installPrebidNpm(); + const pbjs = installPrebidNpm() as TestPbjs; const adUnits = [ { @@ -2762,9 +2903,9 @@ describe('prebid/client-side bidders', () => { ], }, ]; - pbjs.requestBids({ adUnits } as any); + pbjs.requestBids({ adUnits }); - const tsBid = adUnits[0].bids.find((b: any) => b.bidder === 'trustedServer') as any; + const tsBid = adUnits[0].bids.find((b) => b.bidder === 'trustedServer') as TrustedServerTestBid; expect(tsBid).toBeDefined(); // rubicon should NOT be in bidderParams — it runs client-side expect(tsBid.params.bidderParams).toEqual({ @@ -2774,9 +2915,9 @@ describe('prebid/client-side bidders', () => { }); it('preserves client-side bidder bids as standalone entries', () => { - (window as any).__tsjs_prebid = { clientSideBidders: ['rubicon'] }; + (window as TestWindow).__tsjs_prebid = { clientSideBidders: ['rubicon'] }; - const pbjs = installPrebidNpm(); + const pbjs = installPrebidNpm() as TestPbjs; const adUnits = [ { @@ -2786,19 +2927,19 @@ describe('prebid/client-side bidders', () => { ], }, ]; - pbjs.requestBids({ adUnits } as any); + pbjs.requestBids({ adUnits }); // rubicon bid should remain untouched as a standalone entry - const rubiconBid = adUnits[0].bids.find((b: any) => b.bidder === 'rubicon') as any; + const rubiconBid = adUnits[0].bids.find((b) => b.bidder === 'rubicon') as TestBid; expect(rubiconBid).toBeDefined(); expect(rubiconBid.params).toEqual({ accountId: 'abc' }); - expect(adUnits[0].bids.find((b: any) => b.bidder === 'appnexus')).toBeUndefined(); + expect(adUnits[0].bids.find((b) => b.bidder === 'appnexus')).toBeUndefined(); }); it('handles multiple client-side bidders', () => { - (window as any).__tsjs_prebid = { clientSideBidders: ['rubicon', 'openx'] }; + (window as TestWindow).__tsjs_prebid = { clientSideBidders: ['rubicon', 'openx'] }; - const pbjs = installPrebidNpm(); + const pbjs = installPrebidNpm() as TestPbjs; const adUnits = [ { @@ -2809,23 +2950,23 @@ describe('prebid/client-side bidders', () => { ], }, ]; - pbjs.requestBids({ adUnits } as any); + pbjs.requestBids({ adUnits }); - const tsBid = adUnits[0].bids.find((b: any) => b.bidder === 'trustedServer') as any; + const tsBid = adUnits[0].bids.find((b) => b.bidder === 'trustedServer') as TrustedServerTestBid; // Only appnexus should be in bidderParams expect(tsBid.params.bidderParams).toEqual({ appnexus: { placementId: 123 }, }); // Both client-side bidders should remain - expect(adUnits[0].bids.find((b: any) => b.bidder === 'rubicon')).toBeDefined(); - expect(adUnits[0].bids.find((b: any) => b.bidder === 'openx')).toBeDefined(); - expect(adUnits[0].bids.find((b: any) => b.bidder === 'appnexus')).toBeUndefined(); + expect(adUnits[0].bids.find((b) => b.bidder === 'rubicon')).toBeDefined(); + expect(adUnits[0].bids.find((b) => b.bidder === 'openx')).toBeDefined(); + expect(adUnits[0].bids.find((b) => b.bidder === 'appnexus')).toBeUndefined(); }); it('behaves normally when no client-side bidders are configured', () => { // No __tsjs_prebid at all — all bidders go server-side - const pbjs = installPrebidNpm(); + const pbjs = installPrebidNpm() as TestPbjs; const adUnits = [ { @@ -2835,9 +2976,9 @@ describe('prebid/client-side bidders', () => { ], }, ]; - pbjs.requestBids({ adUnits } as any); + pbjs.requestBids({ adUnits }); - const tsBid = adUnits[0].bids.find((b: any) => b.bidder === 'trustedServer') as any; + const tsBid = adUnits[0].bids.find((b) => b.bidder === 'trustedServer') as TrustedServerTestBid; expect(tsBid.params.bidderParams).toEqual({ appnexus: { placementId: 123 }, rubicon: { accountId: 'abc' }, @@ -2845,9 +2986,9 @@ describe('prebid/client-side bidders', () => { }); it('behaves normally when client-side bidders list is empty', () => { - (window as any).__tsjs_prebid = { clientSideBidders: [] }; + (window as TestWindow).__tsjs_prebid = { clientSideBidders: [] }; - const pbjs = installPrebidNpm(); + const pbjs = installPrebidNpm() as TestPbjs; const adUnits = [ { @@ -2857,9 +2998,9 @@ describe('prebid/client-side bidders', () => { ], }, ]; - pbjs.requestBids({ adUnits } as any); + pbjs.requestBids({ adUnits }); - const tsBid = adUnits[0].bids.find((b: any) => b.bidder === 'trustedServer') as any; + const tsBid = adUnits[0].bids.find((b) => b.bidder === 'trustedServer') as TrustedServerTestBid; expect(tsBid.params.bidderParams).toEqual({ appnexus: { placementId: 123 }, rubicon: { accountId: 'abc' }, @@ -2867,9 +3008,9 @@ describe('prebid/client-side bidders', () => { }); it('still injects trustedServer when all bidders are client-side', () => { - (window as any).__tsjs_prebid = { clientSideBidders: ['rubicon', 'appnexus'] }; + (window as TestWindow).__tsjs_prebid = { clientSideBidders: ['rubicon', 'appnexus'] }; - const pbjs = installPrebidNpm(); + const pbjs = installPrebidNpm() as TestPbjs; const adUnits = [ { @@ -2879,10 +3020,10 @@ describe('prebid/client-side bidders', () => { ], }, ]; - pbjs.requestBids({ adUnits } as any); + pbjs.requestBids({ adUnits }); // trustedServer should still be present (even with empty bidderParams) - const tsBid = adUnits[0].bids.find((b: any) => b.bidder === 'trustedServer') as any; + const tsBid = adUnits[0].bids.find((b) => b.bidder === 'trustedServer') as TrustedServerTestBid; expect(tsBid).toBeDefined(); expect(tsBid.params.bidderParams).toEqual({}); }); @@ -2892,7 +3033,7 @@ describe('prebid/client-side bidders', () => { mockGetBidAdapter.mockImplementation((bidder: string) => bidder === 'rubicon' ? {} : undefined ); - (window as any).__tsjs_prebid = { clientSideBidders: ['rubicon', 'openx'] }; + (window as TestWindow).__tsjs_prebid = { clientSideBidders: ['rubicon', 'openx'] }; const errorSpy = vi.spyOn(console, 'error').mockImplementation(() => {}); @@ -2925,7 +3066,7 @@ describe('prebid/client-side bidders', () => { it('does not log errors when all client-side bidders have adapters', () => { mockGetBidAdapter.mockReturnValue({}); - (window as any).__tsjs_prebid = { clientSideBidders: ['rubicon'] }; + (window as TestWindow).__tsjs_prebid = { clientSideBidders: ['rubicon'] }; const errorSpy = vi.spyOn(console, 'error').mockImplementation(() => {}); From c229bf84d32c128fda4204277b1c832f291d9dc6 Mon Sep 17 00:00:00 2001 From: Aram Grigoryan <132480+aram356@users.noreply.github.com> Date: Fri, 31 Jul 2026 20:03:50 -0700 Subject: [PATCH 5/6] Tidy prebid test comments and refresh-mutation casts Replace the four remaining em dashes in comments with plain wording, and hoist the repeated NestedServerParams/NestedBrowserParams casts in the nested-isolation test into refreshServerParams/refreshBrowserParams locals, named apart from the publisher-side serverParams/browserParams seeded earlier in the same test. --- .../test/integrations/prebid/index.test.ts | 22 +++++++++---------- 1 file changed, 10 insertions(+), 12 deletions(-) diff --git a/crates/trusted-server-js/lib/test/integrations/prebid/index.test.ts b/crates/trusted-server-js/lib/test/integrations/prebid/index.test.ts index 4c0dac8b9..74a9e4279 100644 --- a/crates/trusted-server-js/lib/test/integrations/prebid/index.test.ts +++ b/crates/trusted-server-js/lib/test/integrations/prebid/index.test.ts @@ -1767,14 +1767,12 @@ describe('prebid publisher snapshots and delivery refreshes', () => { const firstRefreshBids = refreshAdUnitFromLastRequest().bids; expect(firstRefreshBids).toEqual(expectedBids); - ( - firstRefreshBids[0].params as NestedServerParams - ).bidderParams.exampleServer.placement.rules[0].label = 'changed-refresh-rule'; - ( - firstRefreshBids[0].params as NestedServerParams - ).bidderParams.exampleServer.placement.sizes.push(777); - (firstRefreshBids[1].params as NestedBrowserParams).groups[0].values[0] = - 'changed-refresh-value'; + const refreshServerParams = firstRefreshBids[0].params as NestedServerParams; + refreshServerParams.bidderParams.exampleServer.placement.rules[0].label = + 'changed-refresh-rule'; + refreshServerParams.bidderParams.exampleServer.placement.sizes.push(777); + const refreshBrowserParams = firstRefreshBids[1].params as NestedBrowserParams; + refreshBrowserParams.groups[0].values[0] = 'changed-refresh-value'; pubads.refresh([slot]); expect(refreshAdUnitFromLastRequest().bids).toEqual(expectedBids); @@ -2907,7 +2905,7 @@ describe('prebid/client-side bidders', () => { const tsBid = adUnits[0].bids.find((b) => b.bidder === 'trustedServer') as TrustedServerTestBid; expect(tsBid).toBeDefined(); - // rubicon should NOT be in bidderParams — it runs client-side + // rubicon should NOT be in bidderParams because it runs client-side expect(tsBid.params.bidderParams).toEqual({ appnexus: { placementId: 123 }, kargo: { placementId: 'k1' }, @@ -2965,7 +2963,7 @@ describe('prebid/client-side bidders', () => { }); it('behaves normally when no client-side bidders are configured', () => { - // No __tsjs_prebid at all — all bidders go server-side + // No __tsjs_prebid at all, so all bidders go server-side const pbjs = installPrebidNpm() as TestPbjs; const adUnits = [ @@ -3101,7 +3099,7 @@ describe('prebid self-init user ID module timing', () => { it('installs user ID modules immediately when the bundle loads after window load', async () => { // The GPT slim loader appends this bundle from a window.load handler, so - // the document is already complete — a load listener would never fire. + // the document is already complete and a load listener would never fire. setReadyState('complete'); await import('../../../src/integrations/prebid/index'); @@ -3119,7 +3117,7 @@ describe('prebid self-init user ID module timing', () => { window.dispatchEvent(new Event('load')); expect(userSyncCallCount()).toBe(1); - // { once: true } — a second load event must not reinstall. + // { once: true }: a second load event must not reinstall. window.dispatchEvent(new Event('load')); expect(userSyncCallCount()).toBe(1); }); From ab82530a27f49bd6d7006cb93107f9ef5288f0fb Mon Sep 17 00:00:00 2001 From: Aram Grigoryan <132480+aram356@users.noreply.github.com> Date: Fri, 31 Jul 2026 20:18:37 -0700 Subject: [PATCH 6/6] Type the gpt_diagnostics stub listener as unknown instead of any The merge from main brought the new gpt_diagnostics test (#974), which predates this PR's test-wide no-explicit-any enforcement and failed the widened CI lint on the merge result. The stub only stores and relays event payloads without inspecting them, so unknown is the accurate parameter type; the src GptPubAdsService declares addEventListener as a method, whose bivariant parameter check accepts the narrowing. --- .../lib/test/integrations/gpt_diagnostics/index.test.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/crates/trusted-server-js/lib/test/integrations/gpt_diagnostics/index.test.ts b/crates/trusted-server-js/lib/test/integrations/gpt_diagnostics/index.test.ts index 55f00de57..f53683ee2 100644 --- a/crates/trusted-server-js/lib/test/integrations/gpt_diagnostics/index.test.ts +++ b/crates/trusted-server-js/lib/test/integrations/gpt_diagnostics/index.test.ts @@ -12,7 +12,7 @@ interface FakeSlot { getAdUnitPath(): string; } -type Listener = (event: any) => void; +type Listener = (event: unknown) => void; type DiagnosticsTestWindow = NonNullable[0]>;