diff --git a/crates/trusted-server-js/lib/eslint.config.js b/crates/trusted-server-js/lib/eslint.config.js index e59d7c1ed..2720ba3a0 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'; @@ -36,4 +37,24 @@ export default [ '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 use 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.nodeBuiltin, + }, + }, ]; 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..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 \"src/**/*.{ts,tsx}\"", - "lint:fix": "eslint --fix \"src/**/*.{ts,tsx}\"", + "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}\"" }, @@ -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..50f078d0b 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', () => { @@ -247,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: [ @@ -274,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([]); @@ -286,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([]); @@ -298,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..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,10 +1,6 @@ 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; @@ -12,7 +8,7 @@ describe('core/index', () => { beforeEach(async () => { await vi.resetModules(); document.body.innerHTML = ''; - delete (window as any).tsjs; + delete window.tsjs; }); afterEach(() => { @@ -21,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); @@ -35,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'); @@ -65,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); @@ -76,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]] } } }, @@ -92,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/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..22dfc7185 100644 --- a/crates/trusted-server-js/lib/test/core/request.test.ts +++ b/crates/trusted-server-js/lib/test/core/request.test.ts @@ -21,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' }, @@ -41,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; @@ -67,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' }, @@ -78,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' }, @@ -129,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(); @@ -144,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' }, @@ -162,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(); @@ -174,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' }, @@ -194,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(); @@ -221,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' }, @@ -244,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(); @@ -256,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' }, @@ -276,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(); @@ -298,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' }, @@ -314,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/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/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/didomi/index.test.ts b/crates/trusted-server-js/lib/test/integrations/didomi/index.test.ts index 487ff471f..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 @@ -4,8 +4,14 @@ 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 }; }; @@ -20,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/' ); }); @@ -52,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 257d7fc2c..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 @@ -3,6 +3,8 @@ 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 @@ -43,14 +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. - // eslint-disable-next-line @typescript-eslint/no-explicit-any - tsjs?: any; + tsjs?: Partial; }; async function runGptBootstrap(googletag: object): Promise { @@ -142,8 +153,7 @@ describe('installTsAdInit', () => { burl: 'https://ssp/bill', }, }, - // eslint-disable-next-line @typescript-eslint/no-explicit-any - } as any; + }; const fetchSpy = vi.spyOn(global, 'fetch'); @@ -198,8 +208,7 @@ describe('installTsAdInit', () => { }, ], bids: {}, - // eslint-disable-next-line @typescript-eslint/no-explicit-any - } as any; + }; const { installTsAdInit } = await import('../../../src/integrations/gpt/index'); installTsAdInit(); @@ -253,8 +262,7 @@ describe('installTsAdInit', () => { }, ], bids: {}, - // eslint-disable-next-line @typescript-eslint/no-explicit-any - } as any; + }; const { installTsAdInit } = await import('../../../src/integrations/gpt/index'); installTsAdInit(); @@ -307,8 +315,7 @@ describe('installTsAdInit', () => { }, ], bids: {}, - // eslint-disable-next-line @typescript-eslint/no-explicit-any - } as any; + }; await runGptBootstrap(googletag); @@ -364,8 +371,7 @@ describe('installTsAdInit', () => { }, ], bids: {}, - // eslint-disable-next-line @typescript-eslint/no-explicit-any - } as any; + }; await runGptBootstrap(googletag); @@ -457,8 +463,7 @@ describe('installTsAdInit', () => { }, ], bids: {}, - // eslint-disable-next-line @typescript-eslint/no-explicit-any - } as any; + }; const { installTsAdInit } = await import('../../../src/integrations/gpt/index'); installTsAdInit(); @@ -567,8 +572,7 @@ describe('installTsAdInit', () => { }, ], bids: {}, - // eslint-disable-next-line @typescript-eslint/no-explicit-any - } as any; + }; const { installTsAdInit } = await import('../../../src/integrations/gpt/index'); installTsAdInit(); @@ -617,8 +621,7 @@ describe('installTsAdInit', () => { }, ], bids: {}, - // eslint-disable-next-line @typescript-eslint/no-explicit-any - } as any; + }; const { installTsAdInit } = await import('../../../src/integrations/gpt/index'); installTsAdInit(); @@ -657,8 +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'] }, - // eslint-disable-next-line @typescript-eslint/no-explicit-any - } as any; + }; const { installTsAdInit } = await import('../../../src/integrations/gpt/index'); installTsAdInit(); @@ -697,8 +699,7 @@ 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'); installTsAdInit(); @@ -1102,8 +1103,7 @@ 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'); installTsAdInit(); @@ -1149,8 +1149,7 @@ 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'); installTsAdInit(); @@ -1354,8 +1353,11 @@ 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); + origAdd( + type, + handler as EventListener, + opts as boolean | AddEventListenerOptions | undefined + ); } ); await import('../../../src/integrations/gpt/index'); @@ -1384,8 +1386,11 @@ 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); + origAdd( + type, + handler as EventListener, + opts as boolean | AddEventListenerOptions | undefined + ); } ); await import('../../../src/integrations/gpt/index'); @@ -1418,8 +1423,8 @@ 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; + + 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); @@ -1501,8 +1506,8 @@ 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; + + const parsed = JSON.parse(portMessages[0]) as PrebidResponseMessage; expect(parsed.ad).toBe(rawAd); expect(beaconSpy).toHaveBeenCalledTimes(2); beaconSpy.mockRestore(); @@ -1533,8 +1538,8 @@ 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; + + const parsed = JSON.parse(portMessages[0]) as PrebidResponseMessage; expect(parsed.width).toBe(300); expect(parsed.height).toBe(250); beaconSpy.mockRestore(); @@ -1569,8 +1574,8 @@ 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; + + const parsed = JSON.parse(portMessages[0]) as PrebidResponseMessage; expect(parsed.ad).toContain('p=2.5'); expect(parsed.ad).not.toContain('${AUCTION_PRICE}'); beaconSpy.mockRestore(); @@ -1741,8 +1746,11 @@ 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); + origAdd( + type, + handler as EventListener, + opts as boolean | AddEventListenerOptions | undefined + ); } ); await import('../../../src/integrations/gpt/index'); @@ -1769,8 +1777,8 @@ 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; + + 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); @@ -1830,8 +1838,8 @@ 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; + + const parsed = JSON.parse(portMessages[0]) as PrebidResponseMessage; expect(parsed.width).toBe(300); expect(parsed.height).toBe(250); } finally { @@ -1905,8 +1913,8 @@ 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; + + 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); @@ -1926,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', @@ -1972,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', @@ -2055,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', @@ -2064,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 812543a9a..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 -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 406c6d1f5..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,4 +1,7 @@ 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. @@ -9,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; }; }; @@ -217,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 () => { @@ -240,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[]) => { @@ -269,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'], }, @@ -293,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'); @@ -317,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; @@ -334,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 () => { @@ -354,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/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]>; 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..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 @@ -19,29 +19,33 @@ 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(); - let mockPbjs: { - setConfig: typeof mockSetConfig; - processQueue: typeof mockProcessQueue; - requestBids: typeof mockRequestBids; - registerBidAdapter: typeof mockRegisterBidAdapter; - getUserIdsAsEids: typeof mockGetUserIdsAsEids; - getConfig: typeof mockGetConfig; - removeAdUnit: ReturnType; - adUnits: any[]; - [key: string]: any; - }; + // 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)); + mockPbjs.adUnits = mockPbjs.adUnits.filter((unit) => !codes.has(unit.code!)); }); - mockPbjs = { + const mockPbjs: { + setConfig: typeof mockSetConfig; + processQueue: typeof mockProcessQueue; + requestBids: typeof mockRequestBids; + registerBidAdapter: typeof mockRegisterBidAdapter; + getUserIdsAsEids: typeof mockGetUserIdsAsEids; + getConfig: typeof mockGetConfig; + removeAdUnit: ReturnType; + adUnits: TestAdUnit[]; + [key: string]: unknown; + } = { setConfig: mockSetConfig, processQueue: mockProcessQueue, requestBids: mockRequestBids, @@ -94,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', () => { @@ -124,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', () => { @@ -132,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 }); }); }); @@ -238,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(() => { @@ -289,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: [], @@ -306,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'], @@ -322,7 +443,7 @@ describe('prebid/installPrebidNpm', () => { }); describe('adapter spec', () => { - function getAdapterSpec(): any { + function getAdapterSpec(): TestAdapterSpec { installPrebidNpm(); return mockRegisterBidAdapter.mock.calls[0][2]; } @@ -596,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 = [ { @@ -640,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 = [ @@ -664,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' }, @@ -681,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 = [ { @@ -714,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 = [ { @@ -733,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 = [ { @@ -763,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); }); @@ -803,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(); @@ -826,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(''); }); @@ -842,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(); @@ -860,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 }); @@ -883,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', () => { @@ -902,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', @@ -960,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', @@ -1005,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?.(); @@ -1021,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', @@ -1051,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 = [ { @@ -1075,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', @@ -1108,7 +1241,7 @@ describe('prebid/installRefreshHandler', () => { }) ); - delete (window as any).__tsjs_prebid; + delete (window as TestWindow).__tsjs_prebid; mockPbjs.adUnits = []; }); @@ -1130,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', @@ -1176,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', @@ -1195,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', @@ -1235,7 +1368,7 @@ describe('prebid/installRefreshHandler', () => { }) ); - delete (window as any).__tsjs_prebid; + delete (window as TestWindow).__tsjs_prebid; mockPbjs.adUnits = []; }); @@ -1263,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', @@ -1325,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', @@ -1395,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]); @@ -1420,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]); @@ -1436,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(() => { @@ -1447,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; @@ -1481,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, }; @@ -1489,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; @@ -1520,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 = { @@ -1530,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' }; @@ -1547,7 +1683,7 @@ describe('prebid publisher snapshots and delivery refreshes', () => { ], }, ], - } as any); + }); effectiveParams.placement = 'changed-after-auction'; pubads.refresh([slot]); @@ -1572,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, @@ -1581,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' }], @@ -1602,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'; @@ -1631,10 +1767,12 @@ describe('prebid publisher snapshots and delivery refreshes', () => { const firstRefreshBids = refreshAdUnitFromLastRequest().bids; expect(firstRefreshBids).toEqual(expectedBids); - firstRefreshBids[0].params.bidderParams.exampleServer.placement.rules[0].label = + const refreshServerParams = firstRefreshBids[0].params as NestedServerParams; + refreshServerParams.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'; + 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); @@ -1649,7 +1787,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: [ @@ -1659,7 +1797,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' } }, @@ -1680,7 +1818,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({ @@ -1709,7 +1847,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: [ @@ -1722,7 +1860,7 @@ describe('prebid publisher snapshots and delivery refreshes', () => { bids: [{ bidder: 'exampleServer', params: { placement: 'two' } }], }, ], - } as any); + }); mockPbjs.adUnits = [ { code: 'example-global-code', @@ -1745,7 +1883,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, @@ -1762,10 +1900,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([ @@ -1786,11 +1924,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]); @@ -1808,16 +1946,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: {} }); @@ -1828,7 +1966,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: {} }); }); @@ -1850,7 +1988,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({ @@ -1860,7 +1998,7 @@ describe('prebid publisher snapshots and delivery refreshes', () => { bids: [{ bidder: 'exampleServer', params: { placement: index } }], }, ], - } as any); + }); } pubads.refresh([activeSlot]); @@ -1871,7 +2009,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: {} }); @@ -1892,12 +2030,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: [ @@ -1908,7 +2046,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(); @@ -1927,11 +2065,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); @@ -1960,12 +2098,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'); @@ -1989,12 +2127,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(); @@ -2018,7 +2156,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: {} }] }], @@ -2026,13 +2164,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(); @@ -2057,7 +2195,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) => ({ @@ -2065,7 +2203,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()); @@ -2088,11 +2226,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]); @@ -2117,11 +2255,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]); @@ -2149,7 +2287,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: {} }] }], @@ -2159,7 +2297,7 @@ describe('prebid publisher snapshots and delivery refreshes', () => { pubads.refresh([slot]); }, 1500); }, - } as any); + }); vi.advanceTimersByTime(1500); @@ -2190,28 +2328,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); @@ -2219,7 +2357,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', () => { @@ -2230,17 +2368,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(); @@ -2259,13 +2399,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); @@ -2290,12 +2430,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'); @@ -2327,11 +2467,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]); @@ -2359,7 +2499,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: [ @@ -2371,9 +2511,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(); @@ -2394,7 +2534,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({ @@ -2404,7 +2544,7 @@ describe('prebid publisher snapshots and delivery refreshes', () => { bidsBackHandler: () => { deferredRefresh = Promise.resolve().then(() => pubads.refresh([slot])); }, - } as any); + }); await deferredRefresh; expect(mockRequestBids).toHaveBeenCalledTimes(1); @@ -2426,7 +2566,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({ @@ -2437,7 +2577,7 @@ describe('prebid publisher snapshots and delivery refreshes', () => { pubads.refresh([slot]); }); }, - } as any); + }); await deferredRefresh; expect(mockRequestBids).toHaveBeenCalledTimes(1); @@ -2455,16 +2595,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); @@ -2489,12 +2629,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(); @@ -2514,13 +2654,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'); @@ -2536,7 +2676,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'); }); @@ -2560,7 +2700,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(); @@ -2590,7 +2730,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; @@ -2620,7 +2760,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)); @@ -2633,10 +2773,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); }); @@ -2654,7 +2794,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: [ @@ -2666,10 +2806,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(); @@ -2689,7 +2829,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({ @@ -2702,7 +2842,7 @@ describe('prebid publisher snapshots and delivery refreshes', () => { bidsBackHandler: () => { throw new Error('example callback failure'); }, - } as any) + }) ).toThrow('example callback failure'); pubads.refresh([slot]); @@ -2740,17 +2880,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 = [ { @@ -2761,11 +2901,11 @@ 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 + // rubicon should NOT be in bidderParams because it runs client-side expect(tsBid.params.bidderParams).toEqual({ appnexus: { placementId: 123 }, kargo: { placementId: 'k1' }, @@ -2773,9 +2913,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 = [ { @@ -2785,19 +2925,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 = [ { @@ -2808,23 +2948,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(); + // No __tsjs_prebid at all, so all bidders go server-side + const pbjs = installPrebidNpm() as TestPbjs; const adUnits = [ { @@ -2834,9 +2974,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' }, @@ -2844,9 +2984,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 = [ { @@ -2856,9 +2996,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' }, @@ -2866,9 +3006,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 = [ { @@ -2878,10 +3018,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({}); }); @@ -2891,7 +3031,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(() => {}); @@ -2924,7 +3064,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(() => {}); @@ -2959,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'); @@ -2977,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); }); 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({