prebid.js 9.53.3 → 9.53.4
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/33acrossAnalyticsAdapter.js +1 -1
- package/dist/33acrossBidAdapter.js +1 -1
- package/dist/33acrossIdSystem.js +1 -1
- package/dist/BTBidAdapter.js +1 -1
- package/dist/adagioAnalyticsAdapter.js +1 -1
- package/dist/adagioBidAdapter.js +1 -1
- package/dist/adagioUtils.js +1 -1
- package/dist/addefendBidAdapter.js +1 -1
- package/dist/adgenerationBidAdapter.js +1 -1
- package/dist/adlooxRtdProvider.js +1 -1
- package/dist/adqueryBidAdapter.js +1 -1
- package/dist/adrelevantisBidAdapter.js +1 -1
- package/dist/adstirBidAdapter.js +1 -1
- package/dist/adtrgtmeBidAdapter.js +1 -1
- package/dist/adxcgAnalyticsAdapter.js +1 -1
- package/dist/adxcgBidAdapter.js +1 -1
- package/dist/adyoulikeBidAdapter.js +1 -1
- package/dist/agmaAnalyticsAdapter.js +1 -1
- package/dist/ajaBidAdapter.js +1 -1
- package/dist/amxBidAdapter.js +1 -1
- package/dist/amxIdSystem.js +1 -1
- package/dist/aniviewBidAdapter.js +1 -1
- package/dist/appierAnalyticsAdapter.js +1 -1
- package/dist/appnexusBidAdapter.js +1 -1
- package/dist/asoBidAdapter.js +1 -1
- package/dist/axonixBidAdapter.js +1 -1
- package/dist/beopBidAdapter.js +1 -1
- package/dist/bidderTimeoutUtils.js +1 -0
- package/dist/bidglassBidAdapter.js +1 -1
- package/dist/big-richmediaBidAdapter.js +1 -1
- package/dist/bitmediaBidAdapter.js +1 -1
- package/dist/bridBidAdapter.js +1 -1
- package/dist/bridgeuppBidAdapter.js +1 -1
- package/dist/bridgewellBidAdapter.js +1 -1
- package/dist/brightMountainMediaBidAdapter.js +1 -1
- package/dist/carodaBidAdapter.js +1 -1
- package/dist/chtnwBidAdapter.js +1 -1
- package/dist/chunk-core.js +1 -1
- package/dist/concertBidAdapter.js +1 -1
- package/dist/connectadBidAdapter.js +1 -1
- package/dist/consumableBidAdapter.js +1 -1
- package/dist/contxtfulBidAdapter.js +1 -1
- package/dist/conversantAnalyticsAdapter.js +1 -1
- package/dist/conversantBidAdapter.js +1 -1
- package/dist/craftBidAdapter.js +1 -1
- package/dist/criteoBidAdapter.js +1 -1
- package/dist/cwireBidAdapter.js +1 -1
- package/dist/dailymotionBidAdapter.js +1 -1
- package/dist/dependencies.json +10 -1
- package/dist/dspxBidAdapter.js +1 -1
- package/dist/dxkultureBidAdapter.js +1 -1
- package/dist/eplanningBidAdapter.js +1 -1
- package/dist/equativBidAdapter.js +1 -1
- package/dist/eskimiBidAdapter.js +1 -1
- package/dist/euidIdSystem.js +1 -1
- package/dist/exadsBidAdapter.js +1 -1
- package/dist/excoBidAdapter.js +1 -1
- package/dist/feedadBidAdapter.js +1 -1
- package/dist/finativeBidAdapter.js +1 -1
- package/dist/freewheel-sspBidAdapter.js +1 -1
- package/dist/fwsspBidAdapter.js +1 -1
- package/dist/gmosspBidAdapter.js +1 -1
- package/dist/greenbidsAnalyticsAdapter.js +1 -1
- package/dist/greenbidsBidAdapter.js +1 -1
- package/dist/greenbidsRtdProvider.js +1 -1
- package/dist/gridBidAdapter.js +1 -1
- package/dist/gumgumBidAdapter.js +1 -1
- package/dist/h12mediaBidAdapter.js +1 -1
- package/dist/hypelabBidAdapter.js +1 -1
- package/dist/id5AnalyticsAdapter.js +1 -1
- package/dist/id5IdSystem.js +1 -1
- package/dist/imdsBidAdapter.js +1 -1
- package/dist/improvedigitalBidAdapter.js +1 -1
- package/dist/inmobiBidAdapter.js +1 -1
- package/dist/insticatorBidAdapter.js +1 -1
- package/dist/intentIqAnalyticsAdapter.js +1 -1
- package/dist/ixBidAdapter.js +1 -1
- package/dist/jixieBidAdapter.js +1 -1
- package/dist/justpremiumBidAdapter.js +1 -1
- package/dist/kargoBidAdapter.js +1 -1
- package/dist/kimberliteBidAdapter.js +1 -1
- package/dist/konduitAnalyticsAdapter.js +1 -1
- package/dist/kueezBidAdapter.js +1 -1
- package/dist/lassoBidAdapter.js +1 -1
- package/dist/lifestreetBidAdapter.js +1 -1
- package/dist/liveIntentId.js +1 -1
- package/dist/logicadBidAdapter.js +1 -1
- package/dist/loglyliftBidAdapter.js +1 -1
- package/dist/luceadBidAdapter.js +1 -1
- package/dist/mabidderBidAdapter.js +1 -1
- package/dist/madsenseBidAdapter.js +1 -1
- package/dist/magniteAnalyticsAdapter.js +1 -1
- package/dist/malltvAnalyticsAdapter.js +1 -1
- package/dist/marsmediaBidAdapter.js +1 -1
- package/dist/mediafuseBidAdapter.js +1 -1
- package/dist/medianetBidAdapter.js +1 -1
- package/dist/medianetUtils.js +1 -1
- package/dist/mediasquareBidAdapter.js +1 -1
- package/dist/mgidBidAdapter.js +1 -1
- package/dist/missenaBidAdapter.js +1 -1
- package/dist/mobilefuseBidAdapter.js +1 -1
- package/dist/nextMillenniumBidAdapter.js +1 -1
- package/dist/nexx360Utils.js +1 -1
- package/dist/nobidAnalyticsAdapter.js +1 -1
- package/dist/nobidBidAdapter.js +1 -1
- package/dist/nodalsAiRtdProvider.js +1 -1
- package/dist/not-for-prod/prebid.js +175 -172
- package/dist/objectGuard.js +1 -1
- package/dist/oguryBidAdapter.js +1 -1
- package/dist/onetagBidAdapter.js +1 -1
- package/dist/ooloAnalyticsAdapter.js +1 -1
- package/dist/openxBidAdapter.js +1 -1
- package/dist/optableRtdProvider.js +1 -1
- package/dist/optidigitalBidAdapter.js +1 -1
- package/dist/orbidderBidAdapter.js +1 -1
- package/dist/outbrainBidAdapter.js +1 -1
- package/dist/pixfutureBidAdapter.js +1 -1
- package/dist/publinkIdSystem.js +1 -1
- package/dist/pubmaticAnalyticsAdapter.js +1 -1
- package/dist/pubmaticBidAdapter.js +1 -1
- package/dist/pubmaticRtdProvider.js +1 -1
- package/dist/pubmaticUtils.js +1 -0
- package/dist/pubwiseAnalyticsAdapter.js +1 -1
- package/dist/pubxaiAnalyticsAdapter.js +1 -1
- package/dist/pxyzBidAdapter.js +1 -1
- package/dist/quantcastBidAdapter.js +1 -1
- package/dist/readpeakBidAdapter.js +1 -1
- package/dist/relaidoBidAdapter.js +1 -1
- package/dist/retailspotBidAdapter.js +1 -1
- package/dist/rhythmoneBidAdapter.js +1 -1
- package/dist/riseUtils.js +1 -1
- package/dist/rtdModule.js +1 -1
- package/dist/rubiconBidAdapter.js +1 -1
- package/dist/seedingAllianceBidAdapter.js +1 -1
- package/dist/seedtagBidAdapter.js +1 -1
- package/dist/sevioBidAdapter.js +1 -0
- package/dist/sharethroughAnalyticsAdapter.js +1 -1
- package/dist/sharethroughBidAdapter.js +1 -1
- package/dist/showheroes-bsBidAdapter.js +1 -1
- package/dist/smaatoBidAdapter.js +1 -1
- package/dist/smartadserverBidAdapter.js +1 -1
- package/dist/smartxBidAdapter.js +1 -1
- package/dist/smilewantedBidAdapter.js +1 -1
- package/dist/snigelBidAdapter.js +1 -1
- package/dist/sonobiBidAdapter.js +1 -1
- package/dist/sovrnBidAdapter.js +1 -1
- package/dist/sparteoBidAdapter.js +1 -1
- package/dist/sspBCBidAdapter.js +1 -1
- package/dist/stvBidAdapter.js +1 -1
- package/dist/sublimeBidAdapter.js +1 -1
- package/dist/taboolaBidAdapter.js +1 -1
- package/dist/tappxBidAdapter.js +1 -1
- package/dist/targetVideoBidAdapter.js +1 -1
- package/dist/teadsBidAdapter.js +1 -1
- package/dist/terceptAnalyticsAdapter.js +1 -1
- package/dist/themoneytizerBidAdapter.js +1 -1
- package/dist/timeoutRtdProvider.js +1 -1
- package/dist/trionBidAdapter.js +1 -1
- package/dist/tripleliftBidAdapter.js +1 -1
- package/dist/ttdBidAdapter.js +1 -1
- package/dist/ucfunnelAnalyticsAdapter.js +1 -1
- package/dist/uid2IdSystem.js +1 -1
- package/dist/underdogmediaBidAdapter.js +1 -1
- package/dist/undertoneBidAdapter.js +1 -1
- package/dist/unrulyBidAdapter.js +1 -1
- package/dist/vidazooUtils.js +1 -1
- package/dist/videobyteBidAdapter.js +1 -1
- package/dist/visxBidAdapter.js +1 -1
- package/dist/vuukleBidAdapter.js +1 -1
- package/dist/widespaceBidAdapter.js +1 -1
- package/dist/winrBidAdapter.js +1 -1
- package/dist/yahooAdsBidAdapter.js +1 -1
- package/dist/yandexBidAdapter.js +1 -1
- package/dist/yieldmoBidAdapter.js +1 -1
- package/dist/yieldoneAnalyticsAdapter.js +1 -1
- package/integrationExamples/gpt/pubmaticRtdProvider_Example.html +161 -0
- package/libraries/bidderTimeoutUtils/bidderTimeoutUtils.js +119 -0
- package/libraries/objectGuard/objectGuard.js +36 -15
- package/libraries/pubmaticUtils/plugins/dynamicTimeout.js +209 -0
- package/libraries/pubmaticUtils/plugins/floorProvider.js +168 -0
- package/libraries/pubmaticUtils/plugins/pluginManager.js +106 -0
- package/libraries/pubmaticUtils/plugins/unifiedPricingRule.js +375 -0
- package/libraries/pubmaticUtils/pubmaticUtils.js +76 -0
- package/modules/fwsspBidAdapter.js +134 -69
- package/modules/fwsspBidAdapter.md +121 -26
- package/modules/optableRtdProvider.js +33 -12
- package/modules/pubmaticAnalyticsAdapter.js +5 -1
- package/modules/pubmaticRtdProvider.js +105 -565
- package/modules/rtdModule/index.js +23 -2
- package/modules/sevioBidAdapter.js +413 -0
- package/modules/sevioBidAdapter.md +29 -0
- package/modules/sparteoBidAdapter.js +122 -10
- package/modules/timeoutRtdProvider.js +2 -105
- package/package.json +1 -1
- package/test/spec/activities/objectGuard_spec.js +49 -16
- package/test/spec/libraries/bidderTimeoutUtils/bidderTimeoutUtils_spec.js +213 -0
- package/test/spec/libraries/pubmaticUtils/plugins/dynamicTimeout_spec.js +746 -0
- package/test/spec/libraries/pubmaticUtils/plugins/floorProvider_spec.js +184 -0
- package/test/spec/libraries/pubmaticUtils/plugins/pluginManager_spec.js +489 -0
- package/test/spec/libraries/pubmaticUtils/plugins/unifiedPricingRule_spec.js +359 -0
- package/test/spec/libraries/pubmaticUtils/pubmaticUtils_spec.js +236 -0
- package/test/spec/modules/fwsspBidAdapter_spec.js +513 -78
- package/test/spec/modules/optableRtdProvider_spec.js +55 -5
- package/test/spec/modules/pubmaticRtdProvider_spec.js +252 -1183
- package/test/spec/modules/realTimeDataModule_spec.js +58 -8
- package/test/spec/modules/sevioBidAdapter_spec.js +513 -0
- package/test/spec/modules/sparteoBidAdapter_spec.js +528 -43
- package/test/spec/modules/timeoutRtdProvider_spec.js +1 -201
|
@@ -0,0 +1,359 @@
|
|
|
1
|
+
import sinon from 'sinon';
|
|
2
|
+
import { expect } from 'chai';
|
|
3
|
+
|
|
4
|
+
import * as unifiedPricingRule from '../../../../../libraries/pubmaticUtils/plugins/unifiedPricingRule.js';
|
|
5
|
+
import * as prebidGlobal from '../../../../../src/prebidGlobal.js';
|
|
6
|
+
import * as utils from '../../../../../src/utils.js';
|
|
7
|
+
|
|
8
|
+
// Helper profile configuration with multipliers defined at config.json level
|
|
9
|
+
const profileConfigs = {
|
|
10
|
+
plugins: {
|
|
11
|
+
dynamicFloors: {
|
|
12
|
+
pmTargetingKeys: {
|
|
13
|
+
enabled: true,
|
|
14
|
+
multiplier: { win: 2, floored: 3, nobid: 4 }
|
|
15
|
+
},
|
|
16
|
+
data: {
|
|
17
|
+
multiplier: { win: 1.5, floored: 0.8, nobid: 1.2 }
|
|
18
|
+
}
|
|
19
|
+
}
|
|
20
|
+
}
|
|
21
|
+
};
|
|
22
|
+
|
|
23
|
+
describe('UnifiedPricingRule - getTargeting scenarios', () => {
|
|
24
|
+
let sandbox;
|
|
25
|
+
let pbjsStub;
|
|
26
|
+
|
|
27
|
+
beforeEach(() => {
|
|
28
|
+
sandbox = sinon.createSandbox();
|
|
29
|
+
|
|
30
|
+
// Stub logger methods to keep test output clean
|
|
31
|
+
sandbox.stub(utils, 'logInfo');
|
|
32
|
+
sandbox.stub(utils, 'logError');
|
|
33
|
+
|
|
34
|
+
// Stub getGlobal to return our fake pbjs object
|
|
35
|
+
pbjsStub = {
|
|
36
|
+
getHighestCpmBids: sandbox.stub()
|
|
37
|
+
};
|
|
38
|
+
sandbox.stub(prebidGlobal, 'getGlobal').returns(pbjsStub);
|
|
39
|
+
|
|
40
|
+
// Initialise plugin with mock config manager
|
|
41
|
+
unifiedPricingRule.init('dynamicFloors', {
|
|
42
|
+
getYMConfig: () => profileConfigs
|
|
43
|
+
});
|
|
44
|
+
});
|
|
45
|
+
|
|
46
|
+
afterEach(() => {
|
|
47
|
+
sandbox.restore();
|
|
48
|
+
});
|
|
49
|
+
|
|
50
|
+
const AD_UNIT_CODE = 'div1';
|
|
51
|
+
|
|
52
|
+
/**
|
|
53
|
+
* Utility to build an auction object skeleton
|
|
54
|
+
*/
|
|
55
|
+
function buildAuction({ adUnits = [], bidsReceived = [], bidsRejected, bidderRequests } = {}) {
|
|
56
|
+
return {
|
|
57
|
+
adUnits,
|
|
58
|
+
bidsReceived,
|
|
59
|
+
bidsRejected,
|
|
60
|
+
bidderRequests
|
|
61
|
+
};
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
it('Winning bid', () => {
|
|
65
|
+
const winningBid = {
|
|
66
|
+
adUnitCode: AD_UNIT_CODE,
|
|
67
|
+
cpm: 2.5,
|
|
68
|
+
floorData: { floorProvider: 'PM', skipped: false, floorValue: 1.0 }
|
|
69
|
+
};
|
|
70
|
+
|
|
71
|
+
// pbjs should return highest CPM bids
|
|
72
|
+
pbjsStub.getHighestCpmBids.withArgs(AD_UNIT_CODE).returns([winningBid]);
|
|
73
|
+
|
|
74
|
+
const auction = buildAuction({
|
|
75
|
+
adUnits: [{ bids: [winningBid] }],
|
|
76
|
+
bidsReceived: [winningBid]
|
|
77
|
+
});
|
|
78
|
+
|
|
79
|
+
const targeting = unifiedPricingRule.getTargeting([AD_UNIT_CODE], {}, {}, auction);
|
|
80
|
+
expect(targeting[AD_UNIT_CODE].pm_ym_flrs).to.equal(1);
|
|
81
|
+
expect(targeting[AD_UNIT_CODE].pm_ym_bid_s).to.equal(1);
|
|
82
|
+
expect(targeting[AD_UNIT_CODE].pm_ym_flrv).to.equal('5.00'); // 2.5 * 2
|
|
83
|
+
});
|
|
84
|
+
|
|
85
|
+
it('No bid - uses findFloorValueFromBidderRequests to derive floor', () => {
|
|
86
|
+
pbjsStub.getHighestCpmBids.withArgs(AD_UNIT_CODE).returns([]);
|
|
87
|
+
|
|
88
|
+
const bidRequest = {
|
|
89
|
+
adUnitCode: AD_UNIT_CODE,
|
|
90
|
+
mediaTypes: { banner: { sizes: [[300, 250]] } },
|
|
91
|
+
getFloor: () => ({ currency: 'USD', floor: 2.0 })
|
|
92
|
+
};
|
|
93
|
+
|
|
94
|
+
// Dummy RTD-floor-applied bid for a different ad-unit so that hasRtdFloorAppliedBid === true
|
|
95
|
+
const dummyBid = {
|
|
96
|
+
adUnitCode: 'dummy',
|
|
97
|
+
floorData: { floorProvider: 'PM', skipped: false, floorValue: 1.0 }
|
|
98
|
+
};
|
|
99
|
+
|
|
100
|
+
const auction = buildAuction({
|
|
101
|
+
adUnits: [{ bids: [] }, { code: 'dummy', bids: [dummyBid] }],
|
|
102
|
+
bidsReceived: [dummyBid],
|
|
103
|
+
bidderRequests: [{ bids: [bidRequest] }]
|
|
104
|
+
});
|
|
105
|
+
|
|
106
|
+
const targeting = unifiedPricingRule.getTargeting([AD_UNIT_CODE], {}, {}, auction);
|
|
107
|
+
expect(targeting[AD_UNIT_CODE].pm_ym_flrs).to.equal(1);
|
|
108
|
+
expect(targeting[AD_UNIT_CODE].pm_ym_bid_s).to.equal(0);
|
|
109
|
+
// 2.0 (min floor) * 4 (nobid multiplier) => 8.00
|
|
110
|
+
expect(targeting[AD_UNIT_CODE].pm_ym_flrv).to.equal('8.00');
|
|
111
|
+
});
|
|
112
|
+
|
|
113
|
+
it('No bid & bidderRequests missing for adUnit - floor value 0', () => {
|
|
114
|
+
pbjsStub.getHighestCpmBids.withArgs(AD_UNIT_CODE).returns([]);
|
|
115
|
+
|
|
116
|
+
// bidderRequests only for different adUnit code
|
|
117
|
+
const otherBidRequest = {
|
|
118
|
+
adUnitCode: 'other',
|
|
119
|
+
mediaTypes: { banner: { sizes: [[300, 250]] } },
|
|
120
|
+
getFloor: () => ({ currency: 'USD', floor: 5.0 })
|
|
121
|
+
};
|
|
122
|
+
|
|
123
|
+
// RTD floor applied bid to ensure pm_ym_flrs logic executes
|
|
124
|
+
const dummyBid = {
|
|
125
|
+
adUnitCode: 'dummy',
|
|
126
|
+
floorData: { floorProvider: 'PM', skipped: false, floorValue: 1.0 }
|
|
127
|
+
};
|
|
128
|
+
|
|
129
|
+
const auction = buildAuction({
|
|
130
|
+
adUnits: [{ bids: [] }, { code: 'dummy', bids: [dummyBid] }],
|
|
131
|
+
bidsReceived: [dummyBid],
|
|
132
|
+
bidderRequests: [{ bids: [otherBidRequest] }]
|
|
133
|
+
});
|
|
134
|
+
|
|
135
|
+
const targeting = unifiedPricingRule.getTargeting([AD_UNIT_CODE], {}, {}, auction);
|
|
136
|
+
expect(targeting[AD_UNIT_CODE].pm_ym_flrs).to.equal(1);
|
|
137
|
+
expect(targeting[AD_UNIT_CODE].pm_ym_bid_s).to.equal(0);
|
|
138
|
+
expect(targeting[AD_UNIT_CODE].pm_ym_flrv).to.equal('0.00');
|
|
139
|
+
});
|
|
140
|
+
|
|
141
|
+
it('No bid - bidderRequests array missing', () => {
|
|
142
|
+
pbjsStub.getHighestCpmBids.withArgs(AD_UNIT_CODE).returns([]);
|
|
143
|
+
|
|
144
|
+
// Dummy bid to ensure RTD floor flag is true
|
|
145
|
+
const dummyBid = {
|
|
146
|
+
adUnitCode: 'dummy',
|
|
147
|
+
floorData: { floorProvider: 'PM', skipped: false, floorValue: 1.0 }
|
|
148
|
+
};
|
|
149
|
+
|
|
150
|
+
const auction = buildAuction({
|
|
151
|
+
adUnits: [{ bids: [] }, { code: 'dummy', bids: [dummyBid] }],
|
|
152
|
+
bidsReceived: [dummyBid]
|
|
153
|
+
// bidderRequests is undefined => triggers early return 0
|
|
154
|
+
});
|
|
155
|
+
|
|
156
|
+
const targeting = unifiedPricingRule.getTargeting([AD_UNIT_CODE], {}, {}, auction);
|
|
157
|
+
expect(targeting[AD_UNIT_CODE].pm_ym_flrs).to.equal(1);
|
|
158
|
+
expect(targeting[AD_UNIT_CODE].pm_ym_bid_s).to.equal(0);
|
|
159
|
+
expect(targeting[AD_UNIT_CODE].pm_ym_flrv).to.equal('0.00');
|
|
160
|
+
});
|
|
161
|
+
|
|
162
|
+
it('No bid - bidderRequest has no getFloor method', () => {
|
|
163
|
+
pbjsStub.getHighestCpmBids.withArgs(AD_UNIT_CODE).returns([]);
|
|
164
|
+
|
|
165
|
+
const bidRequest = {
|
|
166
|
+
adUnitCode: AD_UNIT_CODE,
|
|
167
|
+
mediaTypes: { banner: { sizes: [[300, 250]] } }, // no getFloor defined
|
|
168
|
+
};
|
|
169
|
+
|
|
170
|
+
const dummyBid = {
|
|
171
|
+
adUnitCode: 'dummy',
|
|
172
|
+
floorData: { floorProvider: 'PM', skipped: false, floorValue: 1.0 }
|
|
173
|
+
};
|
|
174
|
+
|
|
175
|
+
const auction = buildAuction({
|
|
176
|
+
adUnits: [{ bids: [] }, { code: 'dummy', bids: [dummyBid] }],
|
|
177
|
+
bidsReceived: [dummyBid],
|
|
178
|
+
bidderRequests: [{ bids: [bidRequest] }]
|
|
179
|
+
});
|
|
180
|
+
|
|
181
|
+
const targeting = unifiedPricingRule.getTargeting([AD_UNIT_CODE], {}, {}, auction);
|
|
182
|
+
expect(targeting[AD_UNIT_CODE].pm_ym_flrs).to.equal(1);
|
|
183
|
+
expect(targeting[AD_UNIT_CODE].pm_ym_bid_s).to.equal(0);
|
|
184
|
+
expect(targeting[AD_UNIT_CODE].pm_ym_flrv).to.equal('0.00');
|
|
185
|
+
});
|
|
186
|
+
|
|
187
|
+
it('No bid - video playerSize processed', () => {
|
|
188
|
+
pbjsStub.getHighestCpmBids.withArgs(AD_UNIT_CODE).returns([]);
|
|
189
|
+
|
|
190
|
+
const videoFloorStub = sinon.stub().returns({ currency: 'USD', floor: 3.0 });
|
|
191
|
+
|
|
192
|
+
const bidRequest = {
|
|
193
|
+
adUnitCode: AD_UNIT_CODE,
|
|
194
|
+
mediaTypes: { video: { playerSize: [[640, 480]] } },
|
|
195
|
+
getFloor: videoFloorStub
|
|
196
|
+
};
|
|
197
|
+
|
|
198
|
+
const dummyBid = {
|
|
199
|
+
adUnitCode: 'dummy',
|
|
200
|
+
floorData: { floorProvider: 'PM', skipped: false, floorValue: 1.0 }
|
|
201
|
+
};
|
|
202
|
+
|
|
203
|
+
const auction = buildAuction({
|
|
204
|
+
adUnits: [{ bids: [] }, { code: 'dummy', bids: [dummyBid] }],
|
|
205
|
+
bidsReceived: [dummyBid],
|
|
206
|
+
bidderRequests: [{ bids: [bidRequest] }]
|
|
207
|
+
});
|
|
208
|
+
|
|
209
|
+
const targeting = unifiedPricingRule.getTargeting([AD_UNIT_CODE], {}, {}, auction);
|
|
210
|
+
expect(videoFloorStub.called).to.be.true;
|
|
211
|
+
expect(videoFloorStub.firstCall.args[0].mediaType).to.equal('video');
|
|
212
|
+
expect(targeting[AD_UNIT_CODE].pm_ym_flrs).to.equal(1);
|
|
213
|
+
expect(targeting[AD_UNIT_CODE].pm_ym_bid_s).to.equal(0);
|
|
214
|
+
expect(targeting[AD_UNIT_CODE].pm_ym_flrv).to.equal('12.00'); // 3 * 4
|
|
215
|
+
});
|
|
216
|
+
|
|
217
|
+
it('No bid - fallback sizes array used', () => {
|
|
218
|
+
pbjsStub.getHighestCpmBids.withArgs(AD_UNIT_CODE).returns([]);
|
|
219
|
+
|
|
220
|
+
const sizesFloorStub = sinon.stub().returns({ currency: 'USD', floor: 4.0 });
|
|
221
|
+
|
|
222
|
+
const bidRequest = {
|
|
223
|
+
adUnitCode: AD_UNIT_CODE,
|
|
224
|
+
sizes: [[300, 600]], // general sizes (no mediaTypes)
|
|
225
|
+
getFloor: sizesFloorStub
|
|
226
|
+
};
|
|
227
|
+
|
|
228
|
+
const dummyBid = {
|
|
229
|
+
adUnitCode: 'dummy',
|
|
230
|
+
floorData: { floorProvider: 'PM', skipped: false, floorValue: 1.0 }
|
|
231
|
+
};
|
|
232
|
+
|
|
233
|
+
const auction = buildAuction({
|
|
234
|
+
adUnits: [{ bids: [] }, { code: 'dummy', bids: [dummyBid] }],
|
|
235
|
+
bidsReceived: [dummyBid],
|
|
236
|
+
bidderRequests: [{ bids: [bidRequest] }]
|
|
237
|
+
});
|
|
238
|
+
|
|
239
|
+
const targeting = unifiedPricingRule.getTargeting([AD_UNIT_CODE], {}, {}, auction);
|
|
240
|
+
expect(sizesFloorStub.called).to.be.true;
|
|
241
|
+
expect(sizesFloorStub.firstCall.args[0].mediaType).to.equal('banner');
|
|
242
|
+
expect(targeting[AD_UNIT_CODE].pm_ym_flrs).to.equal(1);
|
|
243
|
+
expect(targeting[AD_UNIT_CODE].pm_ym_bid_s).to.equal(0);
|
|
244
|
+
expect(targeting[AD_UNIT_CODE].pm_ym_flrv).to.equal('16.00'); // 4 * 4
|
|
245
|
+
});
|
|
246
|
+
|
|
247
|
+
it('No bid - wildcard size used when no sizes found', () => {
|
|
248
|
+
pbjsStub.getHighestCpmBids.withArgs(AD_UNIT_CODE).returns([]);
|
|
249
|
+
|
|
250
|
+
const wildcardFloorStub = sinon.stub().returns({ currency: 'USD', floor: 5.0 });
|
|
251
|
+
|
|
252
|
+
const bidRequest = {
|
|
253
|
+
adUnitCode: AD_UNIT_CODE,
|
|
254
|
+
getFloor: wildcardFloorStub
|
|
255
|
+
};
|
|
256
|
+
|
|
257
|
+
const dummyBid = {
|
|
258
|
+
adUnitCode: 'dummy',
|
|
259
|
+
floorData: { floorProvider: 'PM', skipped: false, floorValue: 1.0 }
|
|
260
|
+
};
|
|
261
|
+
|
|
262
|
+
const auction = buildAuction({
|
|
263
|
+
adUnits: [{ code: AD_UNIT_CODE, bids: [] }, { code: 'dummy', bids: [dummyBid] }],
|
|
264
|
+
bidsReceived: [dummyBid],
|
|
265
|
+
bidderRequests: [{ bids: [bidRequest] }]
|
|
266
|
+
});
|
|
267
|
+
|
|
268
|
+
const targeting = unifiedPricingRule.getTargeting([AD_UNIT_CODE], {}, {}, auction);
|
|
269
|
+
expect(wildcardFloorStub.called).to.be.true;
|
|
270
|
+
const floorArgs = wildcardFloorStub.firstCall.args[0];
|
|
271
|
+
expect(floorArgs.mediaType).to.equal('banner');
|
|
272
|
+
expect(floorArgs.size).to.deep.equal(['*', '*']);
|
|
273
|
+
expect(targeting[AD_UNIT_CODE].pm_ym_flrs).to.equal(1);
|
|
274
|
+
expect(targeting[AD_UNIT_CODE].pm_ym_bid_s).to.equal(0);
|
|
275
|
+
expect(targeting[AD_UNIT_CODE].pm_ym_flrv).to.equal('20.00'); // 5 * 4
|
|
276
|
+
});
|
|
277
|
+
|
|
278
|
+
it('Multiplier selection from floor.json when config multiplier missing', () => {
|
|
279
|
+
// Re-init with profileConfigs having multiplier only in data.multiplier (floor.json)
|
|
280
|
+
unifiedPricingRule.init('dynamicFloors', {
|
|
281
|
+
getYMConfig: () => ({
|
|
282
|
+
plugins: {
|
|
283
|
+
dynamicFloors: {
|
|
284
|
+
pmTargetingKeys: {
|
|
285
|
+
enabled: true // no multiplier object here
|
|
286
|
+
},
|
|
287
|
+
data: {
|
|
288
|
+
multiplier: { win: 1.7 }
|
|
289
|
+
}
|
|
290
|
+
}
|
|
291
|
+
}
|
|
292
|
+
})
|
|
293
|
+
});
|
|
294
|
+
|
|
295
|
+
const winningBid = { adUnitCode: AD_UNIT_CODE, cpm: 2.0, floorData: { floorProvider: 'PM', skipped: false, floorValue: 1 } };
|
|
296
|
+
pbjsStub.getHighestCpmBids.withArgs(AD_UNIT_CODE).returns([winningBid]);
|
|
297
|
+
|
|
298
|
+
const auction = buildAuction({ adUnits: [{ bids: [winningBid] }], bidsReceived: [winningBid] });
|
|
299
|
+
|
|
300
|
+
const targeting = unifiedPricingRule.getTargeting([AD_UNIT_CODE], {}, {}, auction);
|
|
301
|
+
expect(targeting[AD_UNIT_CODE].pm_ym_flrv).to.equal('3.40'); // 2 * 1.7
|
|
302
|
+
});
|
|
303
|
+
|
|
304
|
+
it('Multiplier default constant used when no multipliers in profileConfigs', () => {
|
|
305
|
+
unifiedPricingRule.init('dynamicFloors', {
|
|
306
|
+
getYMConfig: () => ({
|
|
307
|
+
plugins: {
|
|
308
|
+
dynamicFloors: {
|
|
309
|
+
pmTargetingKeys: { enabled: true },
|
|
310
|
+
data: {} // no multiplier in data
|
|
311
|
+
}
|
|
312
|
+
}
|
|
313
|
+
})
|
|
314
|
+
});
|
|
315
|
+
|
|
316
|
+
const winningBid = { adUnitCode: AD_UNIT_CODE, cpm: 2.0, floorData: { floorProvider: 'PM', skipped: false, floorValue: 1 } };
|
|
317
|
+
pbjsStub.getHighestCpmBids.withArgs(AD_UNIT_CODE).returns([winningBid]);
|
|
318
|
+
|
|
319
|
+
const auction = buildAuction({ adUnits: [{ bids: [winningBid] }], bidsReceived: [winningBid] });
|
|
320
|
+
|
|
321
|
+
const targeting = unifiedPricingRule.getTargeting([AD_UNIT_CODE], {}, {}, auction);
|
|
322
|
+
expect(targeting[AD_UNIT_CODE].pm_ym_flrv).to.equal('2.00'); // 2 * default 1.0
|
|
323
|
+
});
|
|
324
|
+
|
|
325
|
+
it('RTD floor not applied - pm_ym_flrs should be 0 only', () => {
|
|
326
|
+
// Floor data indicates skipped OR different provider
|
|
327
|
+
const nonRtdBid = {
|
|
328
|
+
adUnitCode: AD_UNIT_CODE,
|
|
329
|
+
cpm: 1.0,
|
|
330
|
+
floorData: { floorProvider: 'OTHER', skipped: true }
|
|
331
|
+
};
|
|
332
|
+
|
|
333
|
+
const auction = buildAuction({
|
|
334
|
+
adUnits: [{ bids: [nonRtdBid] }],
|
|
335
|
+
bidsReceived: [nonRtdBid]
|
|
336
|
+
});
|
|
337
|
+
|
|
338
|
+
const targeting = unifiedPricingRule.getTargeting([AD_UNIT_CODE], {}, {}, auction);
|
|
339
|
+
expect(targeting[AD_UNIT_CODE].pm_ym_flrs).to.equal(0);
|
|
340
|
+
expect(targeting[AD_UNIT_CODE]).to.not.have.property('pm_ym_flrv');
|
|
341
|
+
expect(targeting[AD_UNIT_CODE]).to.not.have.property('pm_ym_bid_s');
|
|
342
|
+
});
|
|
343
|
+
|
|
344
|
+
it('pmTargetingKeys disabled - should return empty object', () => {
|
|
345
|
+
// Re-init with pmTargetingKeys disabled
|
|
346
|
+
unifiedPricingRule.init('dynamicFloors', {
|
|
347
|
+
getYMConfig: () => ({
|
|
348
|
+
plugins: {
|
|
349
|
+
dynamicFloors: {
|
|
350
|
+
pmTargetingKeys: { enabled: false }
|
|
351
|
+
}
|
|
352
|
+
}
|
|
353
|
+
})
|
|
354
|
+
});
|
|
355
|
+
|
|
356
|
+
const targeting = unifiedPricingRule.getTargeting([AD_UNIT_CODE], {}, {}, {});
|
|
357
|
+
expect(targeting).to.deep.equal({});
|
|
358
|
+
});
|
|
359
|
+
});
|
|
@@ -0,0 +1,236 @@
|
|
|
1
|
+
/* globals describe, beforeEach, afterEach, it, sinon */
|
|
2
|
+
import { expect } from 'chai';
|
|
3
|
+
import * as sua from '../../../../src/fpd/sua.js';
|
|
4
|
+
import { getBrowserType, getCurrentTimeOfDay, getUtmValue } from '../../../../libraries/pubmaticUtils/pubmaticUtils.js';
|
|
5
|
+
|
|
6
|
+
describe('pubmaticUtils', () => {
|
|
7
|
+
let sandbox;
|
|
8
|
+
const ORIGINAL_USER_AGENT = window.navigator.userAgent;
|
|
9
|
+
const ORIGINAL_LOCATION = window.location;
|
|
10
|
+
|
|
11
|
+
beforeEach(() => {
|
|
12
|
+
sandbox = sinon.sandbox.create();
|
|
13
|
+
});
|
|
14
|
+
|
|
15
|
+
afterEach(() => {
|
|
16
|
+
sandbox.restore();
|
|
17
|
+
window.navigator.__defineGetter__('userAgent', () => ORIGINAL_USER_AGENT);
|
|
18
|
+
|
|
19
|
+
// Restore original window.location if it was modified
|
|
20
|
+
if (window.location !== ORIGINAL_LOCATION) {
|
|
21
|
+
delete window.location;
|
|
22
|
+
window.location = ORIGINAL_LOCATION;
|
|
23
|
+
}
|
|
24
|
+
});
|
|
25
|
+
|
|
26
|
+
describe('getBrowserType', () => {
|
|
27
|
+
it('should return browser type from SUA when available', () => {
|
|
28
|
+
const mockSuaData = {
|
|
29
|
+
browsers: [
|
|
30
|
+
{ brand: 'Chrome' }
|
|
31
|
+
]
|
|
32
|
+
};
|
|
33
|
+
|
|
34
|
+
sandbox.stub(sua, 'getLowEntropySUA').returns(mockSuaData);
|
|
35
|
+
|
|
36
|
+
expect(getBrowserType()).to.equal('9'); // Chrome ID from BROWSER_REGEX_MAP
|
|
37
|
+
});
|
|
38
|
+
|
|
39
|
+
it('should return browser type from userAgent when SUA is not available', () => {
|
|
40
|
+
sandbox.stub(sua, 'getLowEntropySUA').returns(null);
|
|
41
|
+
|
|
42
|
+
const chromeUserAgent = 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/91.0.4472.124 Safari/537.36';
|
|
43
|
+
window.navigator.__defineGetter__('userAgent', () => chromeUserAgent);
|
|
44
|
+
|
|
45
|
+
expect(getBrowserType()).to.equal('9'); // Chrome ID
|
|
46
|
+
});
|
|
47
|
+
|
|
48
|
+
it('should return browser type for Edge browser', () => {
|
|
49
|
+
sandbox.stub(sua, 'getLowEntropySUA').returns(null);
|
|
50
|
+
|
|
51
|
+
const edgeUserAgent = 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/91.0.4472.124 Safari/537.36 Edg/91.0.864.59';
|
|
52
|
+
window.navigator.__defineGetter__('userAgent', () => edgeUserAgent);
|
|
53
|
+
|
|
54
|
+
expect(getBrowserType()).to.equal('2'); // Edge ID
|
|
55
|
+
});
|
|
56
|
+
|
|
57
|
+
it('should return browser type for Opera browser', () => {
|
|
58
|
+
sandbox.stub(sua, 'getLowEntropySUA').returns(null);
|
|
59
|
+
|
|
60
|
+
const operaUserAgent = 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/91.0.4472.124 Safari/537.36 OPR/77.0.4054.277';
|
|
61
|
+
window.navigator.__defineGetter__('userAgent', () => operaUserAgent);
|
|
62
|
+
|
|
63
|
+
expect(getBrowserType()).to.equal('3'); // Opera ID
|
|
64
|
+
});
|
|
65
|
+
|
|
66
|
+
it('should return browser type for Firefox browser', () => {
|
|
67
|
+
sandbox.stub(sua, 'getLowEntropySUA').returns(null);
|
|
68
|
+
|
|
69
|
+
const firefoxUserAgent = 'Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:89.0) Gecko/20100101 Firefox/89.0';
|
|
70
|
+
window.navigator.__defineGetter__('userAgent', () => firefoxUserAgent);
|
|
71
|
+
|
|
72
|
+
expect(getBrowserType()).to.equal('12'); // Firefox ID
|
|
73
|
+
});
|
|
74
|
+
|
|
75
|
+
it('should return "0" when browser type cannot be determined', () => {
|
|
76
|
+
sandbox.stub(sua, 'getLowEntropySUA').returns(null);
|
|
77
|
+
|
|
78
|
+
const unknownUserAgent = 'Unknown Browser';
|
|
79
|
+
window.navigator.__defineGetter__('userAgent', () => unknownUserAgent);
|
|
80
|
+
|
|
81
|
+
expect(getBrowserType()).to.equal('0');
|
|
82
|
+
});
|
|
83
|
+
|
|
84
|
+
it('should return "-1" when userAgent is null', () => {
|
|
85
|
+
sandbox.stub(sua, 'getLowEntropySUA').returns(null);
|
|
86
|
+
window.navigator.__defineGetter__('userAgent', () => null);
|
|
87
|
+
|
|
88
|
+
expect(getBrowserType()).to.equal('-1');
|
|
89
|
+
});
|
|
90
|
+
});
|
|
91
|
+
|
|
92
|
+
describe('getCurrentTimeOfDay', () => {
|
|
93
|
+
let clock;
|
|
94
|
+
|
|
95
|
+
afterEach(() => {
|
|
96
|
+
if (clock) {
|
|
97
|
+
clock.restore();
|
|
98
|
+
}
|
|
99
|
+
});
|
|
100
|
+
|
|
101
|
+
it('should return "night" for hours between 0 and 4', () => {
|
|
102
|
+
// Set time to 3:30 AM
|
|
103
|
+
clock = sinon.useFakeTimers(new Date(2025, 7, 6, 3, 30, 0).getTime());
|
|
104
|
+
expect(getCurrentTimeOfDay()).to.equal('night');
|
|
105
|
+
});
|
|
106
|
+
|
|
107
|
+
it('should return "morning" for hours between 5 and 11', () => {
|
|
108
|
+
// Set time to 9:15 AM
|
|
109
|
+
clock = sinon.useFakeTimers(new Date(2025, 7, 6, 9, 15, 0).getTime());
|
|
110
|
+
expect(getCurrentTimeOfDay()).to.equal('morning');
|
|
111
|
+
});
|
|
112
|
+
|
|
113
|
+
it('should return "afternoon" for hours between 12 and 16', () => {
|
|
114
|
+
// Set time to 2:45 PM
|
|
115
|
+
clock = sinon.useFakeTimers(new Date(2025, 7, 6, 14, 45, 0).getTime());
|
|
116
|
+
expect(getCurrentTimeOfDay()).to.equal('afternoon');
|
|
117
|
+
});
|
|
118
|
+
|
|
119
|
+
it('should return "evening" for hours between 17 and 18', () => {
|
|
120
|
+
// Set time to 5:30 PM
|
|
121
|
+
clock = sinon.useFakeTimers(new Date(2025, 7, 6, 17, 30, 0).getTime());
|
|
122
|
+
expect(getCurrentTimeOfDay()).to.equal('evening');
|
|
123
|
+
});
|
|
124
|
+
|
|
125
|
+
it('should return "night" for hours between 19 and 23', () => {
|
|
126
|
+
// Set time to 10:00 PM
|
|
127
|
+
clock = sinon.useFakeTimers(new Date(2025, 7, 6, 22, 0, 0).getTime());
|
|
128
|
+
expect(getCurrentTimeOfDay()).to.equal('night');
|
|
129
|
+
});
|
|
130
|
+
});
|
|
131
|
+
|
|
132
|
+
describe('getUtmValue', () => {
|
|
133
|
+
// Setup for mocking URL and URLSearchParams
|
|
134
|
+
let mockUrl;
|
|
135
|
+
let mockUrlParams;
|
|
136
|
+
let origURL;
|
|
137
|
+
let origURLSearchParams;
|
|
138
|
+
|
|
139
|
+
beforeEach(() => {
|
|
140
|
+
// Save original constructors
|
|
141
|
+
origURL = global.URL;
|
|
142
|
+
origURLSearchParams = global.URLSearchParams;
|
|
143
|
+
|
|
144
|
+
// Create mock URL and URLSearchParams
|
|
145
|
+
mockUrl = {};
|
|
146
|
+
mockUrlParams = {
|
|
147
|
+
toString: sandbox.stub().returns(''),
|
|
148
|
+
includes: sandbox.stub().returns(false)
|
|
149
|
+
};
|
|
150
|
+
|
|
151
|
+
// Mock URL constructor
|
|
152
|
+
global.URL = sandbox.stub().returns(mockUrl);
|
|
153
|
+
|
|
154
|
+
// Mock URLSearchParams constructor
|
|
155
|
+
global.URLSearchParams = sandbox.stub().returns(mockUrlParams);
|
|
156
|
+
});
|
|
157
|
+
|
|
158
|
+
afterEach(() => {
|
|
159
|
+
// Restore original constructors
|
|
160
|
+
global.URL = origURL;
|
|
161
|
+
global.URLSearchParams = origURLSearchParams;
|
|
162
|
+
});
|
|
163
|
+
|
|
164
|
+
it('should return "1" when URL contains utm_source parameter', () => {
|
|
165
|
+
// Setup mock URL with utm_source parameter
|
|
166
|
+
mockUrl.search = '?utm_source=test';
|
|
167
|
+
mockUrlParams.toString.returns('utm_source=test');
|
|
168
|
+
mockUrlParams.includes.withArgs('utm_').returns(true);
|
|
169
|
+
|
|
170
|
+
expect(getUtmValue()).to.equal('1');
|
|
171
|
+
});
|
|
172
|
+
|
|
173
|
+
it('should return "1" when URL contains utm_medium parameter', () => {
|
|
174
|
+
// Setup mock URL with utm_medium parameter
|
|
175
|
+
mockUrl.search = '?utm_medium=social';
|
|
176
|
+
mockUrlParams.toString.returns('utm_medium=social');
|
|
177
|
+
mockUrlParams.includes.withArgs('utm_').returns(true);
|
|
178
|
+
|
|
179
|
+
expect(getUtmValue()).to.equal('1');
|
|
180
|
+
});
|
|
181
|
+
|
|
182
|
+
it('should return "1" when URL contains utm_campaign parameter', () => {
|
|
183
|
+
// Setup mock URL with utm_campaign parameter
|
|
184
|
+
mockUrl.search = '?utm_campaign=summer2025';
|
|
185
|
+
mockUrlParams.toString.returns('utm_campaign=summer2025');
|
|
186
|
+
mockUrlParams.includes.withArgs('utm_').returns(true);
|
|
187
|
+
|
|
188
|
+
expect(getUtmValue()).to.equal('1');
|
|
189
|
+
});
|
|
190
|
+
|
|
191
|
+
it('should return "1" when URL contains multiple UTM parameters', () => {
|
|
192
|
+
// Setup mock URL with multiple UTM parameters
|
|
193
|
+
mockUrl.search = '?utm_source=google&utm_medium=cpc&utm_campaign=brand';
|
|
194
|
+
mockUrlParams.toString.returns('utm_source=google&utm_medium=cpc&utm_campaign=brand');
|
|
195
|
+
mockUrlParams.includes.withArgs('utm_').returns(true);
|
|
196
|
+
|
|
197
|
+
expect(getUtmValue()).to.equal('1');
|
|
198
|
+
});
|
|
199
|
+
|
|
200
|
+
it('should return "1" when URL contains UTM parameters mixed with other parameters', () => {
|
|
201
|
+
// Setup mock URL with mixed parameters
|
|
202
|
+
mockUrl.search = '?id=123&utm_source=newsletter&page=2';
|
|
203
|
+
mockUrlParams.toString.returns('id=123&utm_source=newsletter&page=2');
|
|
204
|
+
mockUrlParams.includes.withArgs('utm_').returns(true);
|
|
205
|
+
|
|
206
|
+
expect(getUtmValue()).to.equal('1');
|
|
207
|
+
});
|
|
208
|
+
|
|
209
|
+
it('should return "0" when URL contains no UTM parameters', () => {
|
|
210
|
+
// Setup mock URL with no UTM parameters
|
|
211
|
+
mockUrl.search = '?id=123&page=2';
|
|
212
|
+
mockUrlParams.toString.returns('id=123&page=2');
|
|
213
|
+
mockUrlParams.includes.withArgs('utm_').returns(false);
|
|
214
|
+
|
|
215
|
+
expect(getUtmValue()).to.equal('0');
|
|
216
|
+
});
|
|
217
|
+
|
|
218
|
+
it('should return "0" when URL has no query parameters', () => {
|
|
219
|
+
// Setup mock URL with no query parameters
|
|
220
|
+
mockUrl.search = '';
|
|
221
|
+
mockUrlParams.toString.returns('');
|
|
222
|
+
mockUrlParams.includes.withArgs('utm_').returns(false);
|
|
223
|
+
|
|
224
|
+
expect(getUtmValue()).to.equal('0');
|
|
225
|
+
});
|
|
226
|
+
|
|
227
|
+
it('should handle URL with hash fragment correctly', () => {
|
|
228
|
+
// Setup mock URL with hash fragment
|
|
229
|
+
mockUrl.search = '?utm_source=test';
|
|
230
|
+
mockUrlParams.toString.returns('utm_source=test');
|
|
231
|
+
mockUrlParams.includes.withArgs('utm_').returns(true);
|
|
232
|
+
|
|
233
|
+
expect(getUtmValue()).to.equal('1');
|
|
234
|
+
});
|
|
235
|
+
});
|
|
236
|
+
});
|