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,184 @@
|
|
|
1
|
+
import sinon from 'sinon';
|
|
2
|
+
import * as floorProvider from '../../../../../libraries/pubmaticUtils/plugins/floorProvider.js';
|
|
3
|
+
import * as priceFloors from '../../../../../modules/priceFloors.js';
|
|
4
|
+
import * as pubmaticUtils from '../../../../../libraries/pubmaticUtils/pubmaticUtils.js';
|
|
5
|
+
import {expect} from 'chai';
|
|
6
|
+
|
|
7
|
+
describe('FloorProvider', () => {
|
|
8
|
+
const floorsobj = {
|
|
9
|
+
enabled: true,
|
|
10
|
+
pmTargetingKeys: {
|
|
11
|
+
enabled: true,
|
|
12
|
+
multiplier: { win: 4, floored: 10, nobid: 100 }
|
|
13
|
+
},
|
|
14
|
+
config: {
|
|
15
|
+
endpoint: 'https://pubmatic.com/floor',
|
|
16
|
+
enforcement: { floorDeals: false, enforceJS: false },
|
|
17
|
+
floorMin: 0.22,
|
|
18
|
+
skipRate: 0,
|
|
19
|
+
defaultValues: { '*|*': 0.22 }
|
|
20
|
+
},
|
|
21
|
+
data: {
|
|
22
|
+
currency: 'USD',
|
|
23
|
+
skipRate: 0,
|
|
24
|
+
modelVersion: 'Mock API model version',
|
|
25
|
+
multiplier: { win: 1.1, floored: 0.9, nobid: 1.3 },
|
|
26
|
+
schema: {
|
|
27
|
+
fields: ['mediaType', 'size', 'domain', 'adUnitCode', 'deviceType', 'timeOfDay', 'browser', 'os', 'utm', 'country', 'bidder']
|
|
28
|
+
},
|
|
29
|
+
values: {
|
|
30
|
+
"banner|728x90|localhost|div1|0|afternoon|9|1|0|IN|pubmatic": 9.234,
|
|
31
|
+
},
|
|
32
|
+
default: 0.23,
|
|
33
|
+
userIds: ['id5id']
|
|
34
|
+
}
|
|
35
|
+
};
|
|
36
|
+
|
|
37
|
+
beforeEach(() => {
|
|
38
|
+
floorProvider.init('dynamicFloors', {
|
|
39
|
+
getConfigByName: () => floorsobj
|
|
40
|
+
});
|
|
41
|
+
});
|
|
42
|
+
it('should initialize floor provider and set config correctly', async () => {
|
|
43
|
+
const pluginName = 'dynamicFloors';
|
|
44
|
+
const configJsonManager = {
|
|
45
|
+
getConfigByName: (name) => name === pluginName ? floorsobj : undefined,
|
|
46
|
+
country: "IN"
|
|
47
|
+
};
|
|
48
|
+
|
|
49
|
+
let continueAuctionStub;
|
|
50
|
+
before(() => {
|
|
51
|
+
continueAuctionStub = sinon.stub(priceFloors, 'continueAuction').callsFake(() => true);
|
|
52
|
+
});
|
|
53
|
+
|
|
54
|
+
const result = await floorProvider.init(pluginName, configJsonManager);
|
|
55
|
+
|
|
56
|
+
expect(result).to.be.true;
|
|
57
|
+
expect(floorProvider.getFloorConfig()).to.deep.equal(floorsobj);
|
|
58
|
+
|
|
59
|
+
after(() => {
|
|
60
|
+
sinon.restore();
|
|
61
|
+
});
|
|
62
|
+
});
|
|
63
|
+
|
|
64
|
+
it('should return input unchanged if floor config is missing or disabled', async () => {
|
|
65
|
+
const input = {
|
|
66
|
+
adUnits: [
|
|
67
|
+
{
|
|
68
|
+
adUnitCode: 'div1',
|
|
69
|
+
sizes: [728, 90],
|
|
70
|
+
bids: [{ bidder: 'pubmatic' }]
|
|
71
|
+
}
|
|
72
|
+
],
|
|
73
|
+
adUnitCodes: ['div1']
|
|
74
|
+
};
|
|
75
|
+
const result = await floorProvider.processBidRequest(input);
|
|
76
|
+
// Check that adUnitCodes are unchanged
|
|
77
|
+
expect(result.adUnitCodes).to.deep.equal(input.adUnitCodes);
|
|
78
|
+
// Check that adUnits core fields are unchanged
|
|
79
|
+
expect(result.adUnits[0].adUnitCode).to.equal('div1');
|
|
80
|
+
expect(result.adUnits[0].sizes).to.deep.equal([728, 90]);
|
|
81
|
+
expect(result.adUnits[0].bids[0]).to.include({ bidder: 'pubmatic' });
|
|
82
|
+
});
|
|
83
|
+
|
|
84
|
+
it('should handle errors in continueAuction gracefully', async () => {
|
|
85
|
+
let continueAuctionStub;
|
|
86
|
+
before(() => {
|
|
87
|
+
continueAuctionStub = sinon.stub(priceFloors, 'continueAuction').callsFake(() => { throw new Error('fail!'); });
|
|
88
|
+
});
|
|
89
|
+
|
|
90
|
+
floorProvider.init('dynamicFloors', {
|
|
91
|
+
getConfigByName: () => floorsobj
|
|
92
|
+
});
|
|
93
|
+
|
|
94
|
+
const req = {err: 4};
|
|
95
|
+
const result = await floorProvider.processBidRequest(req);
|
|
96
|
+
|
|
97
|
+
expect(result).to.equal(req);
|
|
98
|
+
|
|
99
|
+
after(() => {
|
|
100
|
+
sinon.restore();
|
|
101
|
+
});
|
|
102
|
+
});
|
|
103
|
+
|
|
104
|
+
it('getTargeting should return undefined or do nothing', () => {
|
|
105
|
+
expect(floorProvider.getTargeting([], {}, {}, {})).to.be.undefined;
|
|
106
|
+
});
|
|
107
|
+
it('should return correct floor config using getFloorConfig', () => {
|
|
108
|
+
floorProvider.init('dynamicFloors', {
|
|
109
|
+
getConfigByName: () => floorsobj
|
|
110
|
+
});
|
|
111
|
+
expect(floorProvider.getFloorConfig()).to.deep.equal(floorsobj);
|
|
112
|
+
});
|
|
113
|
+
|
|
114
|
+
it('should return false if getConfigByName returns undefined', async () => {
|
|
115
|
+
const result = await floorProvider.init('', { getConfigByName: () => undefined });
|
|
116
|
+
expect(result).to.equal(false);
|
|
117
|
+
});
|
|
118
|
+
|
|
119
|
+
it('should return false when floor configuration is disabled', async () => {
|
|
120
|
+
const disabledConfig = { ...floorsobj, enabled: false };
|
|
121
|
+
const result = await floorProvider.init('dynamicFloors', {
|
|
122
|
+
getConfigByName: () => disabledConfig
|
|
123
|
+
});
|
|
124
|
+
expect(result).to.equal(false);
|
|
125
|
+
expect(floorProvider.getFloorConfig()).to.deep.equal(disabledConfig);
|
|
126
|
+
});
|
|
127
|
+
|
|
128
|
+
it('should cover getConfigJsonManager export and log its value', async () => {
|
|
129
|
+
const configJsonManager = { getConfigByName: () => floorsobj };
|
|
130
|
+
const result = await floorProvider.init('testPlugin', configJsonManager);
|
|
131
|
+
const mgr = floorProvider.getConfigJsonManager();
|
|
132
|
+
expect(mgr.getConfigByName('testPlugin')).to.deep.equal(floorsobj);
|
|
133
|
+
expect(result).to.be.true;
|
|
134
|
+
});
|
|
135
|
+
describe('Utility Exports', () => {
|
|
136
|
+
afterEach(() => {
|
|
137
|
+
sinon.restore();
|
|
138
|
+
});
|
|
139
|
+
|
|
140
|
+
it('getCountry should return country from configJsonManager', async () => {
|
|
141
|
+
const enabledConfig = { ...floorsobj, enabled: true };
|
|
142
|
+
floorProvider.init('any', { country: 'IN', getConfigByName: () => enabledConfig });
|
|
143
|
+
expect(floorProvider.getCountry()).to.equal('IN');
|
|
144
|
+
});
|
|
145
|
+
|
|
146
|
+
it('getOs should return string from getOS', async () => {
|
|
147
|
+
// Import userAgentUtils and stub getOS there
|
|
148
|
+
const userAgentUtils = require('libraries/userAgentUtils/index.js');
|
|
149
|
+
const fakeOS = { toString: () => 'MacOS' };
|
|
150
|
+
const stub = sinon.stub(userAgentUtils, 'getOS').returns(fakeOS);
|
|
151
|
+
expect(floorProvider.getOs()).to.equal('MacOS');
|
|
152
|
+
stub.restore();
|
|
153
|
+
});
|
|
154
|
+
afterEach(() => {
|
|
155
|
+
sinon.restore();
|
|
156
|
+
});
|
|
157
|
+
|
|
158
|
+
it('getTimeOfDay should return result from getCurrentTimeOfDay', async () => {
|
|
159
|
+
const stub = sinon.stub(pubmaticUtils, 'getCurrentTimeOfDay').returns('evening');
|
|
160
|
+
expect(floorProvider.getTimeOfDay()).to.equal('evening');
|
|
161
|
+
});
|
|
162
|
+
|
|
163
|
+
it('should return a string device type using getDeviceType', async () => {
|
|
164
|
+
expect(floorProvider.getDeviceType()).to.be.a('string');
|
|
165
|
+
});
|
|
166
|
+
|
|
167
|
+
it('getBrowser should return result from getBrowser', async () => {
|
|
168
|
+
const stub = sinon.stub(pubmaticUtils, 'getBrowserType').returns('Chrome');
|
|
169
|
+
expect(floorProvider.getBrowser()).to.equal('Chrome');
|
|
170
|
+
});
|
|
171
|
+
|
|
172
|
+
it('getUtm should return result from getUtmValue', async () => {
|
|
173
|
+
const stub = sinon.stub(pubmaticUtils, 'getUtmValue').returns('evening');
|
|
174
|
+
expect(floorProvider.getUtm()).to.equal('evening');
|
|
175
|
+
});
|
|
176
|
+
|
|
177
|
+
it('getBidder should return bidder from request', async () => {
|
|
178
|
+
floorProvider.init('dynamicFloors', { getConfigByName: () => floorsobj });
|
|
179
|
+
expect(floorProvider.getBidder({ bidder: 'pubmatic' })).to.equal('pubmatic');
|
|
180
|
+
expect(floorProvider.getBidder({})).to.equal(undefined);
|
|
181
|
+
expect(floorProvider.getBidder(undefined)).to.equal(undefined);
|
|
182
|
+
});
|
|
183
|
+
});
|
|
184
|
+
});
|
|
@@ -0,0 +1,489 @@
|
|
|
1
|
+
import { expect } from 'chai';
|
|
2
|
+
import sinon from 'sinon';
|
|
3
|
+
import * as utils from '../../../../../src/utils.js';
|
|
4
|
+
import { PluginManager, plugins, CONSTANTS } from '../../../../../libraries/pubmaticUtils/plugins/pluginManager.js';
|
|
5
|
+
|
|
6
|
+
describe('Plugin Manager', () => {
|
|
7
|
+
let sandbox;
|
|
8
|
+
let logInfoStub;
|
|
9
|
+
let logWarnStub;
|
|
10
|
+
let logErrorStub;
|
|
11
|
+
let pluginManager;
|
|
12
|
+
let mockPlugin;
|
|
13
|
+
let mockConfigJsonManager;
|
|
14
|
+
|
|
15
|
+
beforeEach(() => {
|
|
16
|
+
sandbox = sinon.createSandbox();
|
|
17
|
+
logInfoStub = sandbox.stub(utils, 'logInfo');
|
|
18
|
+
logWarnStub = sandbox.stub(utils, 'logWarn');
|
|
19
|
+
logErrorStub = sandbox.stub(utils, 'logError');
|
|
20
|
+
|
|
21
|
+
// Clear plugins map before each test
|
|
22
|
+
plugins.clear();
|
|
23
|
+
|
|
24
|
+
pluginManager = PluginManager();
|
|
25
|
+
|
|
26
|
+
// Create mock plugin with synchronous methods
|
|
27
|
+
mockPlugin = {
|
|
28
|
+
init: sandbox.stub().resolves(true),
|
|
29
|
+
testHook: sandbox.stub().returns({ result: 'success' }),
|
|
30
|
+
errorHook: sandbox.stub().throws(new Error('Test error')),
|
|
31
|
+
nullHook: sandbox.stub().returns(null),
|
|
32
|
+
objectHook: sandbox.stub().returns({ key1: 'value1', key2: 'value2' })
|
|
33
|
+
};
|
|
34
|
+
|
|
35
|
+
// Create mock config manager
|
|
36
|
+
mockConfigJsonManager = {
|
|
37
|
+
getConfigByName: sandbox.stub().returns({ enabled: true })
|
|
38
|
+
};
|
|
39
|
+
});
|
|
40
|
+
|
|
41
|
+
afterEach(() => {
|
|
42
|
+
sandbox.restore();
|
|
43
|
+
});
|
|
44
|
+
|
|
45
|
+
describe('register', () => {
|
|
46
|
+
it('should register a plugin successfully', () => {
|
|
47
|
+
pluginManager.register('testPlugin', mockPlugin);
|
|
48
|
+
|
|
49
|
+
expect(plugins.has('testPlugin')).to.be.true;
|
|
50
|
+
expect(plugins.get('testPlugin')).to.equal(mockPlugin);
|
|
51
|
+
});
|
|
52
|
+
|
|
53
|
+
it('should log warning when registering a plugin with existing name', () => {
|
|
54
|
+
pluginManager.register('testPlugin', mockPlugin);
|
|
55
|
+
pluginManager.register('testPlugin', { init: () => {} });
|
|
56
|
+
|
|
57
|
+
expect(logWarnStub.calledOnce).to.be.true;
|
|
58
|
+
expect(logWarnStub.firstCall.args[0]).to.equal(`${CONSTANTS.LOG_PRE_FIX} Plugin testPlugin already registered`);
|
|
59
|
+
expect(plugins.get('testPlugin')).to.equal(mockPlugin); // Should keep the original plugin
|
|
60
|
+
});
|
|
61
|
+
|
|
62
|
+
it('should handle registering plugins with null or undefined values', () => {
|
|
63
|
+
pluginManager.register('nullPlugin', null);
|
|
64
|
+
pluginManager.register('undefinedPlugin', undefined);
|
|
65
|
+
|
|
66
|
+
expect(plugins.has('nullPlugin')).to.be.true;
|
|
67
|
+
expect(plugins.get('nullPlugin')).to.be.null;
|
|
68
|
+
expect(plugins.has('undefinedPlugin')).to.be.true;
|
|
69
|
+
expect(plugins.get('undefinedPlugin')).to.be.undefined;
|
|
70
|
+
});
|
|
71
|
+
});
|
|
72
|
+
|
|
73
|
+
// Test the unregister functionality through the initialize method
|
|
74
|
+
describe('unregister functionality', () => {
|
|
75
|
+
it('should unregister plugins when initialization fails', async () => {
|
|
76
|
+
const failingPlugin = {
|
|
77
|
+
init: sandbox.stub().resolves(false)
|
|
78
|
+
};
|
|
79
|
+
|
|
80
|
+
pluginManager.register('failingPlugin', failingPlugin);
|
|
81
|
+
|
|
82
|
+
await pluginManager.initialize(mockConfigJsonManager);
|
|
83
|
+
|
|
84
|
+
// Verify plugin was removed
|
|
85
|
+
expect(plugins.has('failingPlugin')).to.be.false;
|
|
86
|
+
expect(logInfoStub.calledOnce).to.be.true;
|
|
87
|
+
expect(logInfoStub.firstCall.args[0]).to.equal(`${CONSTANTS.LOG_PRE_FIX} Unregistering plugin failingPlugin`);
|
|
88
|
+
});
|
|
89
|
+
|
|
90
|
+
it('should not unregister plugins when initialization succeeds', async () => {
|
|
91
|
+
pluginManager.register('testPlugin', mockPlugin);
|
|
92
|
+
|
|
93
|
+
await pluginManager.initialize(mockConfigJsonManager);
|
|
94
|
+
|
|
95
|
+
// Verify plugin was not removed
|
|
96
|
+
expect(plugins.has('testPlugin')).to.be.true;
|
|
97
|
+
expect(logInfoStub.called).to.be.false;
|
|
98
|
+
});
|
|
99
|
+
|
|
100
|
+
it('should handle multiple plugins with some failing initialization', async () => {
|
|
101
|
+
const failingPlugin = {
|
|
102
|
+
init: sandbox.stub().resolves(false)
|
|
103
|
+
};
|
|
104
|
+
|
|
105
|
+
pluginManager.register('failingPlugin', failingPlugin);
|
|
106
|
+
pluginManager.register('testPlugin', mockPlugin);
|
|
107
|
+
|
|
108
|
+
await pluginManager.initialize(mockConfigJsonManager);
|
|
109
|
+
|
|
110
|
+
// Verify only failing plugin was removed
|
|
111
|
+
expect(plugins.has('failingPlugin')).to.be.false;
|
|
112
|
+
expect(plugins.has('testPlugin')).to.be.true;
|
|
113
|
+
expect(logInfoStub.calledOnce).to.be.true;
|
|
114
|
+
expect(logInfoStub.firstCall.args[0]).to.equal(`${CONSTANTS.LOG_PRE_FIX} Unregistering plugin failingPlugin`);
|
|
115
|
+
});
|
|
116
|
+
});
|
|
117
|
+
|
|
118
|
+
describe('initialize', () => {
|
|
119
|
+
it('should initialize all registered plugins', async () => {
|
|
120
|
+
pluginManager.register('testPlugin1', mockPlugin);
|
|
121
|
+
|
|
122
|
+
const anotherPlugin = {
|
|
123
|
+
init: sandbox.stub().resolves(true)
|
|
124
|
+
};
|
|
125
|
+
pluginManager.register('testPlugin2', anotherPlugin);
|
|
126
|
+
|
|
127
|
+
await pluginManager.initialize(mockConfigJsonManager);
|
|
128
|
+
|
|
129
|
+
expect(mockPlugin.init.calledOnce).to.be.true;
|
|
130
|
+
expect(mockPlugin.init.firstCall.args[0]).to.equal('testPlugin1');
|
|
131
|
+
expect(mockPlugin.init.firstCall.args[1]).to.equal(mockConfigJsonManager);
|
|
132
|
+
|
|
133
|
+
expect(anotherPlugin.init.calledOnce).to.be.true;
|
|
134
|
+
expect(anotherPlugin.init.firstCall.args[0]).to.equal('testPlugin2');
|
|
135
|
+
expect(anotherPlugin.init.firstCall.args[1]).to.equal(mockConfigJsonManager);
|
|
136
|
+
});
|
|
137
|
+
|
|
138
|
+
it('should unregister plugin if initialization fails', async () => {
|
|
139
|
+
const failingPlugin = {
|
|
140
|
+
init: sandbox.stub().resolves(false)
|
|
141
|
+
};
|
|
142
|
+
|
|
143
|
+
pluginManager.register('failingPlugin', failingPlugin);
|
|
144
|
+
pluginManager.register('testPlugin', mockPlugin);
|
|
145
|
+
|
|
146
|
+
await pluginManager.initialize(mockConfigJsonManager);
|
|
147
|
+
|
|
148
|
+
expect(plugins.has('failingPlugin')).to.be.false;
|
|
149
|
+
expect(plugins.has('testPlugin')).to.be.true;
|
|
150
|
+
expect(logInfoStub.calledOnce).to.be.true;
|
|
151
|
+
expect(logInfoStub.firstCall.args[0]).to.equal(`${CONSTANTS.LOG_PRE_FIX} Unregistering plugin failingPlugin`);
|
|
152
|
+
});
|
|
153
|
+
|
|
154
|
+
it('should handle plugins without init method', async () => {
|
|
155
|
+
const pluginWithoutInit = {
|
|
156
|
+
testHook: sandbox.stub().returns({ result: 'success' })
|
|
157
|
+
};
|
|
158
|
+
|
|
159
|
+
pluginManager.register('pluginWithoutInit', pluginWithoutInit);
|
|
160
|
+
pluginManager.register('testPlugin', mockPlugin);
|
|
161
|
+
|
|
162
|
+
await pluginManager.initialize(mockConfigJsonManager);
|
|
163
|
+
|
|
164
|
+
expect(plugins.has('pluginWithoutInit')).to.be.true;
|
|
165
|
+
expect(plugins.has('testPlugin')).to.be.true;
|
|
166
|
+
expect(mockPlugin.init.calledOnce).to.be.true;
|
|
167
|
+
});
|
|
168
|
+
|
|
169
|
+
it('should handle rejected promises during initialization', async () => {
|
|
170
|
+
const rejectingPlugin = {
|
|
171
|
+
init: sandbox.stub().rejects(new Error('Initialization error'))
|
|
172
|
+
};
|
|
173
|
+
|
|
174
|
+
pluginManager.register('rejectingPlugin', rejectingPlugin);
|
|
175
|
+
pluginManager.register('testPlugin', mockPlugin);
|
|
176
|
+
|
|
177
|
+
try {
|
|
178
|
+
await pluginManager.initialize(mockConfigJsonManager);
|
|
179
|
+
// If we get here without an error being thrown, the test should fail
|
|
180
|
+
expect.fail('Expected initialize to throw an error');
|
|
181
|
+
} catch (e) {
|
|
182
|
+
// Expected to catch the error
|
|
183
|
+
expect(e.message).to.equal('Initialization error');
|
|
184
|
+
}
|
|
185
|
+
|
|
186
|
+
// The plugin should still be registered since the unregister happens only on false return
|
|
187
|
+
expect(plugins.has('rejectingPlugin')).to.be.true;
|
|
188
|
+
expect(plugins.has('testPlugin')).to.be.true;
|
|
189
|
+
});
|
|
190
|
+
|
|
191
|
+
it('should handle null or undefined configJsonManager', async () => {
|
|
192
|
+
pluginManager.register('testPlugin', mockPlugin);
|
|
193
|
+
|
|
194
|
+
await pluginManager.initialize(null);
|
|
195
|
+
|
|
196
|
+
expect(mockPlugin.init.calledOnce).to.be.true;
|
|
197
|
+
expect(mockPlugin.init.firstCall.args[0]).to.equal('testPlugin');
|
|
198
|
+
expect(mockPlugin.init.firstCall.args[1]).to.be.null;
|
|
199
|
+
|
|
200
|
+
// Reset for next test
|
|
201
|
+
mockPlugin.init.reset();
|
|
202
|
+
|
|
203
|
+
await pluginManager.initialize(undefined);
|
|
204
|
+
|
|
205
|
+
expect(mockPlugin.init.calledOnce).to.be.true;
|
|
206
|
+
expect(mockPlugin.init.firstCall.args[0]).to.equal('testPlugin');
|
|
207
|
+
expect(mockPlugin.init.firstCall.args[1]).to.be.undefined;
|
|
208
|
+
});
|
|
209
|
+
});
|
|
210
|
+
|
|
211
|
+
describe('executeHook', () => {
|
|
212
|
+
beforeEach(() => {
|
|
213
|
+
pluginManager.register('testPlugin', mockPlugin);
|
|
214
|
+
});
|
|
215
|
+
|
|
216
|
+
it('should execute hook on all registered plugins', () => {
|
|
217
|
+
const results = pluginManager.executeHook('testHook', 'arg1', 'arg2');
|
|
218
|
+
|
|
219
|
+
expect(mockPlugin.testHook.calledOnce).to.be.true;
|
|
220
|
+
expect(mockPlugin.testHook.firstCall.args[0]).to.equal('arg1');
|
|
221
|
+
expect(mockPlugin.testHook.firstCall.args[1]).to.equal('arg2');
|
|
222
|
+
expect(results).to.deep.equal({ result: 'success' });
|
|
223
|
+
});
|
|
224
|
+
|
|
225
|
+
it('should handle errors during hook execution', () => {
|
|
226
|
+
const results = pluginManager.executeHook('errorHook');
|
|
227
|
+
|
|
228
|
+
expect(mockPlugin.errorHook.calledOnce).to.be.true;
|
|
229
|
+
expect(logErrorStub.calledOnce).to.be.true;
|
|
230
|
+
expect(logErrorStub.firstCall.args[0]).to.equal(`${CONSTANTS.LOG_PRE_FIX} Error executing hook errorHook in plugin testPlugin: Test error`);
|
|
231
|
+
expect(results).to.deep.equal({});
|
|
232
|
+
});
|
|
233
|
+
|
|
234
|
+
it('should skip null or undefined results', () => {
|
|
235
|
+
const results = pluginManager.executeHook('nullHook');
|
|
236
|
+
|
|
237
|
+
expect(mockPlugin.nullHook.calledOnce).to.be.true;
|
|
238
|
+
expect(results).to.deep.equal({});
|
|
239
|
+
});
|
|
240
|
+
|
|
241
|
+
it('should merge results from multiple plugins', () => {
|
|
242
|
+
const anotherPlugin = {
|
|
243
|
+
testHook: sandbox.stub().returns({ key3: 'value3', key4: 'value4' })
|
|
244
|
+
};
|
|
245
|
+
|
|
246
|
+
pluginManager.register('anotherPlugin', anotherPlugin);
|
|
247
|
+
|
|
248
|
+
const results = pluginManager.executeHook('testHook');
|
|
249
|
+
|
|
250
|
+
expect(mockPlugin.testHook.calledOnce).to.be.true;
|
|
251
|
+
expect(anotherPlugin.testHook.calledOnce).to.be.true;
|
|
252
|
+
expect(results).to.deep.equal({
|
|
253
|
+
result: 'success',
|
|
254
|
+
key3: 'value3',
|
|
255
|
+
key4: 'value4'
|
|
256
|
+
});
|
|
257
|
+
});
|
|
258
|
+
|
|
259
|
+
it('should handle non-object results', () => {
|
|
260
|
+
mockPlugin.testHook = sandbox.stub().returns('string result');
|
|
261
|
+
|
|
262
|
+
const results = pluginManager.executeHook('testHook');
|
|
263
|
+
|
|
264
|
+
expect(mockPlugin.testHook.calledOnce).to.be.true;
|
|
265
|
+
expect(results).to.deep.equal({});
|
|
266
|
+
});
|
|
267
|
+
|
|
268
|
+
it('should handle plugins without the requested hook', () => {
|
|
269
|
+
const results = pluginManager.executeHook('nonExistentHook');
|
|
270
|
+
|
|
271
|
+
expect(results).to.deep.equal({});
|
|
272
|
+
});
|
|
273
|
+
|
|
274
|
+
it('should merge results from multiple object hooks', () => {
|
|
275
|
+
const results = pluginManager.executeHook('objectHook');
|
|
276
|
+
|
|
277
|
+
expect(mockPlugin.objectHook.calledOnce).to.be.true;
|
|
278
|
+
expect(results).to.deep.equal({
|
|
279
|
+
key1: 'value1',
|
|
280
|
+
key2: 'value2'
|
|
281
|
+
});
|
|
282
|
+
});
|
|
283
|
+
|
|
284
|
+
it('should handle errors during plugin filtering', () => {
|
|
285
|
+
// Create a scenario where Array.from throws an error
|
|
286
|
+
const originalArrayFrom = Array.from;
|
|
287
|
+
Array.from = sandbox.stub().throws(new Error('Array.from error'));
|
|
288
|
+
|
|
289
|
+
const results = pluginManager.executeHook('testHook');
|
|
290
|
+
|
|
291
|
+
expect(logErrorStub.calledOnce).to.be.true;
|
|
292
|
+
expect(logErrorStub.firstCall.args[0]).to.equal(`${CONSTANTS.LOG_PRE_FIX} Error in executeHookSync: Array.from error`);
|
|
293
|
+
expect(results).to.deep.equal({});
|
|
294
|
+
|
|
295
|
+
// Restore original Array.from
|
|
296
|
+
Array.from = originalArrayFrom;
|
|
297
|
+
});
|
|
298
|
+
|
|
299
|
+
it('should handle synchronous hook functions', () => {
|
|
300
|
+
const syncPlugin = {
|
|
301
|
+
syncHook: sandbox.stub().returns({ syncKey: 'syncValue' })
|
|
302
|
+
};
|
|
303
|
+
|
|
304
|
+
pluginManager.register('syncPlugin', syncPlugin);
|
|
305
|
+
|
|
306
|
+
const results = pluginManager.executeHook('syncHook');
|
|
307
|
+
|
|
308
|
+
expect(syncPlugin.syncHook.calledOnce).to.be.true;
|
|
309
|
+
expect(results).to.deep.equal({ syncKey: 'syncValue' });
|
|
310
|
+
});
|
|
311
|
+
|
|
312
|
+
it('should handle overwriting properties when merging results', () => {
|
|
313
|
+
const plugin1 = {
|
|
314
|
+
duplicateHook: sandbox.stub().returns({ key: 'value1' })
|
|
315
|
+
};
|
|
316
|
+
|
|
317
|
+
const plugin2 = {
|
|
318
|
+
duplicateHook: sandbox.stub().returns({ key: 'value2' })
|
|
319
|
+
};
|
|
320
|
+
|
|
321
|
+
pluginManager.register('plugin1', plugin1);
|
|
322
|
+
pluginManager.register('plugin2', plugin2);
|
|
323
|
+
|
|
324
|
+
const results = pluginManager.executeHook('duplicateHook');
|
|
325
|
+
|
|
326
|
+
expect(plugin1.duplicateHook.calledOnce).to.be.true;
|
|
327
|
+
expect(plugin2.duplicateHook.calledOnce).to.be.true;
|
|
328
|
+
|
|
329
|
+
// The last plugin's value should win in case of duplicate keys
|
|
330
|
+
expect(results).to.deep.equal({ key: 'value2' });
|
|
331
|
+
});
|
|
332
|
+
|
|
333
|
+
it('should handle empty plugins map', () => {
|
|
334
|
+
// Clear all plugins
|
|
335
|
+
plugins.clear();
|
|
336
|
+
|
|
337
|
+
const results = pluginManager.executeHook('testHook');
|
|
338
|
+
|
|
339
|
+
expect(results).to.deep.equal({});
|
|
340
|
+
});
|
|
341
|
+
|
|
342
|
+
it('should handle complex nested object results', () => {
|
|
343
|
+
const complexPlugin = {
|
|
344
|
+
complexHook: sandbox.stub().returns({
|
|
345
|
+
level1: {
|
|
346
|
+
level2: {
|
|
347
|
+
level3: 'deep value'
|
|
348
|
+
},
|
|
349
|
+
array: [1, 2, 3]
|
|
350
|
+
},
|
|
351
|
+
topLevel: 'top value'
|
|
352
|
+
})
|
|
353
|
+
};
|
|
354
|
+
|
|
355
|
+
pluginManager.register('complexPlugin', complexPlugin);
|
|
356
|
+
|
|
357
|
+
const results = pluginManager.executeHook('complexHook');
|
|
358
|
+
|
|
359
|
+
expect(complexPlugin.complexHook.calledOnce).to.be.true;
|
|
360
|
+
expect(results).to.deep.equal({
|
|
361
|
+
level1: {
|
|
362
|
+
level2: {
|
|
363
|
+
level3: 'deep value'
|
|
364
|
+
},
|
|
365
|
+
array: [1, 2, 3]
|
|
366
|
+
},
|
|
367
|
+
topLevel: 'top value'
|
|
368
|
+
});
|
|
369
|
+
});
|
|
370
|
+
|
|
371
|
+
it('should handle plugins that return promises', () => {
|
|
372
|
+
const promisePlugin = {
|
|
373
|
+
promiseHook: sandbox.stub().returns(Promise.resolve({ promiseKey: 'promiseValue' }))
|
|
374
|
+
};
|
|
375
|
+
|
|
376
|
+
pluginManager.register('promisePlugin', promisePlugin);
|
|
377
|
+
|
|
378
|
+
const results = pluginManager.executeHook('promiseHook');
|
|
379
|
+
|
|
380
|
+
// Since executeHook is synchronous, it should treat the promise as an object
|
|
381
|
+
expect(promisePlugin.promiseHook.calledOnce).to.be.true;
|
|
382
|
+
expect(results).to.deep.equal({});
|
|
383
|
+
});
|
|
384
|
+
});
|
|
385
|
+
|
|
386
|
+
describe('CONSTANTS', () => {
|
|
387
|
+
it('should have the correct LOG_PRE_FIX value', () => {
|
|
388
|
+
expect(CONSTANTS.LOG_PRE_FIX).to.equal('PubMatic-Plugin-Manager: ');
|
|
389
|
+
});
|
|
390
|
+
|
|
391
|
+
it('should be frozen', () => {
|
|
392
|
+
expect(Object.isFrozen(CONSTANTS)).to.be.true;
|
|
393
|
+
});
|
|
394
|
+
|
|
395
|
+
it('should not allow modification of constants', () => {
|
|
396
|
+
try {
|
|
397
|
+
CONSTANTS.LOG_PRE_FIX = 'Modified prefix';
|
|
398
|
+
// If we get here, the test should fail because the constant was modified
|
|
399
|
+
expect.fail('Expected an error when modifying frozen CONSTANTS');
|
|
400
|
+
} catch (e) {
|
|
401
|
+
// This is expected behavior
|
|
402
|
+
expect(e).to.be.an.instanceof(TypeError);
|
|
403
|
+
expect(CONSTANTS.LOG_PRE_FIX).to.equal('PubMatic-Plugin-Manager: ');
|
|
404
|
+
}
|
|
405
|
+
});
|
|
406
|
+
});
|
|
407
|
+
|
|
408
|
+
// Test browser compatibility
|
|
409
|
+
describe('browser compatibility', () => {
|
|
410
|
+
let originalMap;
|
|
411
|
+
let originalObjectEntries;
|
|
412
|
+
let originalObjectAssign;
|
|
413
|
+
|
|
414
|
+
beforeEach(() => {
|
|
415
|
+
// Store original implementations
|
|
416
|
+
originalMap = global.Map;
|
|
417
|
+
originalObjectEntries = Object.entries;
|
|
418
|
+
originalObjectAssign = Object.assign;
|
|
419
|
+
});
|
|
420
|
+
|
|
421
|
+
afterEach(() => {
|
|
422
|
+
// Restore original implementations
|
|
423
|
+
global.Map = originalMap;
|
|
424
|
+
Object.entries = originalObjectEntries;
|
|
425
|
+
Object.assign = originalObjectAssign;
|
|
426
|
+
});
|
|
427
|
+
|
|
428
|
+
it('should handle browser environments where Map is not available', function() {
|
|
429
|
+
// Skip this test if running in a real browser environment
|
|
430
|
+
if (typeof window !== 'undefined' && window.Map) {
|
|
431
|
+
this.skip();
|
|
432
|
+
return;
|
|
433
|
+
}
|
|
434
|
+
|
|
435
|
+
// Mock a browser environment where Map is not available
|
|
436
|
+
const MapBackup = global.Map;
|
|
437
|
+
global.Map = undefined;
|
|
438
|
+
|
|
439
|
+
try {
|
|
440
|
+
// This should not throw an error
|
|
441
|
+
expect(() => {
|
|
442
|
+
const pm = PluginManager();
|
|
443
|
+
pm.register('testPlugin', {});
|
|
444
|
+
}).to.not.throw();
|
|
445
|
+
} finally {
|
|
446
|
+
// Restore Map
|
|
447
|
+
global.Map = MapBackup;
|
|
448
|
+
}
|
|
449
|
+
});
|
|
450
|
+
|
|
451
|
+
it('should handle browser environments where Object.entries is not available', function() {
|
|
452
|
+
// Skip this test if running in a real browser environment
|
|
453
|
+
if (typeof window !== 'undefined') {
|
|
454
|
+
this.skip();
|
|
455
|
+
return;
|
|
456
|
+
}
|
|
457
|
+
|
|
458
|
+
// Mock a browser environment where Object.entries is not available
|
|
459
|
+
Object.entries = undefined;
|
|
460
|
+
|
|
461
|
+
// Register a plugin
|
|
462
|
+
pluginManager.register('testPlugin', mockPlugin);
|
|
463
|
+
|
|
464
|
+
// This should not throw an error
|
|
465
|
+
expect(() => {
|
|
466
|
+
pluginManager.executeHook('testHook');
|
|
467
|
+
}).to.not.throw();
|
|
468
|
+
});
|
|
469
|
+
|
|
470
|
+
it('should handle browser environments where Object.assign is not available', function() {
|
|
471
|
+
// Skip this test if running in a real browser environment
|
|
472
|
+
if (typeof window !== 'undefined') {
|
|
473
|
+
this.skip();
|
|
474
|
+
return;
|
|
475
|
+
}
|
|
476
|
+
|
|
477
|
+
// Mock a browser environment where Object.assign is not available
|
|
478
|
+
Object.assign = undefined;
|
|
479
|
+
|
|
480
|
+
// Register a plugin
|
|
481
|
+
pluginManager.register('testPlugin', mockPlugin);
|
|
482
|
+
|
|
483
|
+
// This should not throw an error
|
|
484
|
+
expect(() => {
|
|
485
|
+
pluginManager.executeHook('testHook');
|
|
486
|
+
}).to.not.throw();
|
|
487
|
+
});
|
|
488
|
+
});
|
|
489
|
+
});
|