prebid-universal-creative 1.16.0 → 1.17.1

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.
Files changed (51) hide show
  1. package/.circleci/config.yml +44 -30
  2. package/.github/workflows/codeql.yml +98 -0
  3. package/README.md +2 -2
  4. package/dist/0df2612ebfffd5ad651a.max.js +1050 -0
  5. package/dist/amp.js +3 -3
  6. package/dist/banner.js +3 -3
  7. package/dist/creative.js +3 -3
  8. package/dist/load-cookie-with-consent.html +1 -1
  9. package/dist/load-cookie.html +1 -1
  10. package/dist/mobile.js +3 -3
  11. package/dist/native-render.js +3 -3
  12. package/dist/native-trk.js +3 -3
  13. package/dist/native.js +3 -3
  14. package/dist/uid.js +2 -2
  15. package/dist/video.js +3 -3
  16. package/gulpfile.js +12 -24
  17. package/integ-test/fixtures/test.js +79 -0
  18. package/integ-test/pages/amp.html +80 -0
  19. package/integ-test/pages/banner.html +96 -0
  20. package/integ-test/pages/native_legacy.html +107 -0
  21. package/integ-test/spec/amp_spec.js +111 -0
  22. package/integ-test/spec/banner_spec.js +85 -0
  23. package/integ-test/spec/native_legacy_spec.js +213 -0
  24. package/package.json +7 -13
  25. package/playwright.config.js +108 -0
  26. package/src/adHtmlRender.js +15 -0
  27. package/src/cookieSync.js +3 -0
  28. package/src/cookieSyncWithConsent.js +3 -0
  29. package/src/domHelper.js +25 -15
  30. package/src/dynamicRenderer.js +56 -0
  31. package/src/messaging.js +23 -2
  32. package/src/mobileAndAmpRender.js +17 -20
  33. package/src/nativeAssetManager.js +98 -79
  34. package/src/nativeORTBTrackerManager.js +1 -1
  35. package/src/nativeRenderManager.js +7 -12
  36. package/src/nativeTrackerManager.js +2 -2
  37. package/src/renderingManager.js +13 -19
  38. package/test/helpers/mocks.js +1 -0
  39. package/test/spec/dynamicRenderer_spec.js +167 -0
  40. package/test/spec/messaging_spec.js +98 -3
  41. package/test/spec/mobileAndAmpRender_spec.js +59 -66
  42. package/test/spec/nativeAssetManager_spec.js +73 -23
  43. package/test/spec/renderingManager_spec.js +20 -6
  44. package/webpack.conf.js +0 -1
  45. package/.nvmrc +0 -1
  46. package/dist/creative.max.js +0 -3102
  47. package/src/postscribeRender.js +0 -10
  48. package/test/e2e/specs/hello_world_banner_non_sf.spec.js +0 -14
  49. package/test/e2e/specs/outstream_non_sf.spec.js +0 -14
  50. package/test/e2e/specs/outstream_sf.spec.js +0 -14
  51. package/wdio.conf.js +0 -50
@@ -0,0 +1,167 @@
1
+ import {makeIframe} from '../../src/domHelper.js';
2
+ import {hasDynamicRenderer, MIN_RENDERER_VERSION, runDynamicRenderer} from '../../src/dynamicRenderer.js';
3
+ import {AD_RENDER_FAILED, AD_RENDER_SUCCEEDED, PREBID_EVENT} from '../../src/messaging.js';
4
+
5
+ describe('hasDynamicRenderer', () => {
6
+ Object.entries({
7
+ 'neither': {},
8
+ 'renderer, but no version': {
9
+ renderer: 'mock-renderer'
10
+ },
11
+ 'renderer, but version is too low': {
12
+ renderer: 'mock-renderer',
13
+ rendererVersion: 1
14
+ },
15
+ }).forEach(([t, data]) => {
16
+ it(`returns false with ${t}`, () => {
17
+ expect(hasDynamicRenderer(data)).to.be.false;
18
+ })
19
+ });
20
+
21
+ it('returns true when both renderer and version are present', () => {
22
+ expect(hasDynamicRenderer({
23
+ renderer: 'mock-renderer',
24
+ rendererVersion: MIN_RENDERER_VERSION
25
+ })).to.be.true;
26
+ })
27
+ })
28
+
29
+ describe('runDynamicRenderer', () => {
30
+ let sendMessage, frame, render;
31
+ const adId = '123';
32
+ beforeEach(() => {
33
+ render = sinon.stub();
34
+ sendMessage = sinon.stub();
35
+ frame = makeIframe(document);
36
+ return new Promise((resolve) => {
37
+ frame.onload = resolve;
38
+ document.body.appendChild(frame);
39
+ }).then(() => {
40
+ frame.contentWindow._render = render;
41
+ });
42
+ });
43
+
44
+ afterEach(() => {
45
+ document.body.removeChild(frame);
46
+ });
47
+
48
+ function runRenderer(data) {
49
+ return runDynamicRenderer(adId, Object.assign({
50
+ renderer: `window.render = window.parent._render`
51
+ }, data), sendMessage, frame.contentWindow).catch(() => null);
52
+ }
53
+
54
+ it('runs renderer', () => {
55
+ const data = {ad: 'markup'};
56
+ return runRenderer(data).then(() => {
57
+ sinon.assert.calledWith(render, sinon.match(data), sinon.match({mkFrame: makeIframe}), frame.contentWindow);
58
+ });
59
+ });
60
+
61
+ Object.entries({
62
+ 'returns': null,
63
+ 'returns a promise that resolves': Promise.resolve()
64
+ }).forEach(([t, ret]) => {
65
+ it(`emits AD_RENDER_SUCCEDED when renderer ${t}`, () => {
66
+ render.callsFake(() => ret);
67
+ return runRenderer().then(() => {
68
+ sinon.assert.calledWith(sendMessage, {
69
+ adId,
70
+ message: PREBID_EVENT,
71
+ event: AD_RENDER_SUCCEEDED
72
+ });
73
+ });
74
+ });
75
+ });
76
+
77
+ describe('emits AD_RENDER_FAILED', () => {
78
+ Object.entries({
79
+ throws: (ret) => {
80
+ throw ret;
81
+ },
82
+ 'returns a promise that rejects': (ret) => Promise.reject(ret)
83
+ }).forEach(([t, transform]) => {
84
+ describe(`when renderer ${t}`, () => {
85
+ Object.entries({
86
+ 'error': {
87
+ ret: new Error('error message'),
88
+ info: {
89
+ reason: 'exception',
90
+ message: 'error message'
91
+ }
92
+ },
93
+ 'error with reason': {
94
+ ret: {
95
+ reason: 'failure',
96
+ message: 'error message'
97
+ },
98
+ info: {
99
+ reason: 'failure',
100
+ message: 'error message'
101
+ }
102
+ }
103
+ }).forEach(([t, {ret, info}]) => {
104
+ it(`an ${t}`, () => {
105
+ render.callsFake(() => transform(ret));
106
+ return runRenderer().then(() => {
107
+ sinon.assert.calledWith(sendMessage, {
108
+ adId,
109
+ message: PREBID_EVENT,
110
+ event: AD_RENDER_FAILED,
111
+ info
112
+ });
113
+ });
114
+ });
115
+ });
116
+ });
117
+ });
118
+ });
119
+
120
+ describe('renderer sendMessage', () => {
121
+ let rndSendMessage;
122
+ beforeEach(() => {
123
+ render.callsFake(() => new Promise())
124
+ return new Promise((resolve) => {
125
+ render.callsFake((_, {sendMessage}) => {
126
+ rndSendMessage = sendMessage;
127
+ resolve();
128
+ });
129
+ runRenderer();
130
+ })
131
+ });
132
+ it('adds message type and adId', () => {
133
+ rndSendMessage('type', {msg: 'data'});
134
+ sinon.assert.calledWith(sendMessage, {
135
+ message: 'type',
136
+ adId,
137
+ msg: 'data'
138
+ });
139
+ });
140
+ it('accepts response listeners', () => {
141
+ const listener = sinon.stub();
142
+ sendMessage.callsFake((_, responseListener) => {
143
+ responseListener('response');
144
+ })
145
+ rndSendMessage('msg', {}, listener);
146
+ sinon.assert.calledWith(listener, 'response');
147
+ });
148
+
149
+ it('emits AD_RENDER_FAILED if listener throws', () => {
150
+ const listener = sinon.stub().callsFake(() => { throw new Error('err') });
151
+ sendMessage.callsFake((_, responseListener) => {
152
+ responseListener && responseListener('response');
153
+ });
154
+ rndSendMessage('msg', {}, listener);
155
+ sinon.assert.calledWith(sendMessage, {
156
+ message: PREBID_EVENT,
157
+ event: AD_RENDER_FAILED,
158
+ adId,
159
+ info: {
160
+ reason: 'exception',
161
+ message: 'err'
162
+ }
163
+ })
164
+ })
165
+ });
166
+
167
+ });
@@ -1,5 +1,11 @@
1
1
  import {mocks} from '../helpers/mocks.js';
2
- import {prebidMessenger} from '../../src/messaging.js';
2
+ import {
3
+ AD_RENDER_FAILED,
4
+ AD_RENDER_SUCCEEDED,
5
+ PREBID_EVENT,
6
+ prebidMessenger,
7
+ renderEventMessage
8
+ } from '../../src/messaging.js';
3
9
 
4
10
  describe('prebidMessenger',() => {
5
11
  let win;
@@ -25,7 +31,6 @@ describe('prebidMessenger',() => {
25
31
  describe('when publisher URL is available', () => {
26
32
  const URL = 'https://www.publisher.com/page.html';
27
33
  const ORIGIN = 'https://www.publisher.com'
28
- let sendMessage;
29
34
  let callback, handler;
30
35
 
31
36
  beforeEach(() => {
@@ -33,15 +38,73 @@ describe('prebidMessenger',() => {
33
38
  handler = h;
34
39
  }
35
40
  win.removeEventListener = sinon.spy();
36
- sendMessage = prebidMessenger(URL, win);
37
41
  callback = sinon.spy();
38
42
  })
39
43
 
44
+ function sendMessage(...args) {
45
+ return prebidMessenger(URL, win)(...args);
46
+ }
47
+
40
48
  it('should use origin for postMessage', () => {
41
49
  sendMessage('test');
42
50
  sinon.assert.calledWith(win.parent.postMessage, JSON.stringify('test'), ORIGIN);
43
51
  });
44
52
 
53
+ describe('when window has multiple ancestors', () => {
54
+ let target;
55
+ beforeEach(() => {
56
+ const top = mocks.createFakeWindow('top');
57
+ target = {
58
+ ...win.parent,
59
+ frames: {},
60
+ parent: {
61
+ top,
62
+ frames: {},
63
+ parent: top
64
+ }
65
+ };
66
+ win = {
67
+ top,
68
+ frames: {},
69
+ parent: {
70
+ top,
71
+ frames: {},
72
+ parent: target
73
+ }
74
+ };
75
+ })
76
+ Object.entries({
77
+ throws() { throw new DOMException() },
78
+ 'does not throw'() { return {} }
79
+ }).forEach(([t, getFrames]) => {
80
+ describe(`when ancestor ${t}`, () => {
81
+ beforeEach(() => {
82
+ Object.defineProperty(target.parent.parent, 'frames', {get: getFrames});
83
+ })
84
+ it('should post to first ancestor that has a __pb_locator__ child', () => {
85
+ [target, target.parent].forEach(win => {
86
+ win.frames = {
87
+ __pb_locator__: {}
88
+ };
89
+ })
90
+ sendMessage('test');
91
+ sinon.assert.called(target.postMessage);
92
+ });
93
+ })
94
+ })
95
+ it('should post to immediate parent when no ancestor has __pb_locator__', () => {
96
+ win.parent.postMessage = sinon.spy();
97
+ delete target.postMessage;
98
+ sendMessage('test');
99
+ sinon.assert.called(win.parent.postMessage);
100
+ });
101
+ it('should post to first restricted frame if no __pb_locator__ can be found', () => {
102
+ Object.defineProperty(target, 'frames', {get() { throw new DOMException() }});
103
+ sendMessage('test');
104
+ sinon.assert.called(target.postMessage)
105
+ })
106
+ });
107
+
45
108
  it('should not run callback on response if origin does not mach', ()=> {
46
109
  sendMessage('test', callback);
47
110
  handler({origin: 'different'});
@@ -62,3 +125,35 @@ describe('prebidMessenger',() => {
62
125
 
63
126
  });
64
127
  })
128
+
129
+ describe('renderEventMessage', () => {
130
+ Object.entries({
131
+ 'success': {
132
+ input: {adId: '123'},
133
+ output: {
134
+ event: AD_RENDER_SUCCEEDED
135
+ }
136
+ },
137
+ 'failure': {
138
+ input: {
139
+ adId: '321',
140
+ errorInfo: {
141
+ reason: 'failureReason',
142
+ message: 'error message'
143
+ }
144
+ },
145
+ output: {
146
+ event: AD_RENDER_FAILED,
147
+ info: {
148
+ reason: 'failureReason',
149
+ message: 'error message'
150
+ }
151
+ }
152
+ }
153
+ }).forEach(([t, {input: {adId, errorInfo}, output}]) => {
154
+ Object.assign(output, {message: PREBID_EVENT, adId});
155
+ it(t, () => {
156
+ expect(renderEventMessage(adId, errorInfo)).to.eql(output);
157
+ })
158
+ })
159
+ })
@@ -1,10 +1,10 @@
1
1
  import { renderAmpOrMobileAd } from 'src/mobileAndAmpRender';
2
- import * as postscribeRender from 'src/postscribeRender'
2
+ import * as postscribeRender from 'src/adHtmlRender'
3
3
  import * as utils from 'src/utils';
4
4
  import { expect } from 'chai';
5
5
  import { mocks } from 'test/helpers/mocks';
6
6
  import { merge } from 'lodash';
7
- import {writeAdHtml} from 'src/postscribeRender';
7
+ import {writeAdHtml} from 'src/adHtmlRender';
8
8
 
9
9
 
10
10
  function renderingMocks() {
@@ -188,51 +188,58 @@ describe("renderingManager", function () {
188
188
  });
189
189
 
190
190
  describe("amp creative", function () {
191
+ let sandbox;
191
192
  let writeHtmlSpy;
192
193
  let sendRequestSpy;
193
194
  let triggerPixelSpy;
194
195
  let mockWin;
195
-
196
- before(function () {
197
- writeHtmlSpy = sinon.spy(postscribeRender, "writeAdHtml");
198
- sendRequestSpy = sinon.spy(utils, "sendRequest");
199
- triggerPixelSpy = sinon.spy(utils, "triggerPixel");
200
- mockWin = merge(
201
- mocks.createFakeWindow("http://example.com"),
202
- renderingMocks().getWindowObject()
203
- );
204
- });
205
-
206
- afterEach(function () {
207
- writeHtmlSpy.resetHistory();
208
- sendRequestSpy.resetHistory();
209
- triggerPixelSpy.resetHistory();
210
- });
211
-
212
- after(function () {
213
- writeHtmlSpy.restore();
214
- sendRequestSpy.restore();
215
- triggerPixelSpy.restore();
216
- });
217
-
218
- it("should render amp creative", function () {
219
- let ucTagData = {
196
+ let ucTagData;
197
+ let response;
198
+
199
+ beforeEach(function () {
200
+ sandbox = sinon.sandbox.create();
201
+ writeHtmlSpy = sandbox.spy(postscribeRender, "writeAdHtml");
202
+ sendRequestSpy = sandbox.spy(utils, "sendRequest");
203
+ triggerPixelSpy = sandbox.spy(utils, "triggerPixel");
204
+ ucTagData = {
220
205
  cacheHost: "example.com",
221
206
  cachePath: "/path",
222
207
  uuid: "123",
223
208
  size: "300x250",
224
- hbPb: "10.00",
225
209
  };
226
-
227
- renderAmpOrMobileAd(ucTagData);
228
-
229
- let response = {
210
+ response = {
230
211
  width: 300,
231
212
  height: 250,
232
213
  crid: 123,
233
214
  adm: "ad-markup${AUCTION_PRICE}",
234
215
  wurl: "https://test.prebidcache.wurl",
235
216
  };
217
+ });
218
+
219
+
220
+
221
+ afterEach(function () {
222
+ sandbox.restore();
223
+ });
224
+
225
+ it('should send embed-resize message', () => {
226
+ sandbox.spy(window.parent, 'postMessage');
227
+ ucTagData.size = '400x500'
228
+ renderAmpOrMobileAd(ucTagData);
229
+ requests[0].respond(200, {}, JSON.stringify(response));
230
+ sinon.assert.calledWith(window.parent.postMessage, {
231
+ sentinel: "amp",
232
+ type: "embed-size",
233
+ width: 400,
234
+ height: 500,
235
+ });
236
+ })
237
+
238
+ it("should render amp creative", function () {
239
+ ucTagData.hbPb = "10.00";
240
+ renderAmpOrMobileAd(ucTagData);
241
+
242
+
236
243
  requests[0].respond(200, {}, JSON.stringify(response));
237
244
  expect(writeHtmlSpy.args[0][0]).to.equal(
238
245
  "<!--Creative 123 served by Prebid.js Header Bidding-->ad-markup10.00"
@@ -246,24 +253,8 @@ describe("renderingManager", function () {
246
253
  });
247
254
 
248
255
  it("should replace AUCTION_PRICE with response.price over hbPb", function () {
249
- let ucTagData = {
250
- cacheHost: "example.com",
251
- cachePath: "/path",
252
- uuid: "123",
253
- size: "300x250",
254
- hbPb: "10.00",
255
- };
256
-
257
256
  renderAmpOrMobileAd(ucTagData);
258
-
259
- let response = {
260
- width: 300,
261
- height: 250,
262
- crid: 123,
263
- price: 12.5,
264
- adm: "ad-markup${AUCTION_PRICE}",
265
- wurl: "https://test.prebidcache.wurl",
266
- };
257
+ response.price = 12.5;
267
258
  requests[0].respond(200, {}, JSON.stringify(response));
268
259
  expect(writeHtmlSpy.args[0][0]).to.equal(
269
260
  "<!--Creative 123 served by Prebid.js Header Bidding-->ad-markup12.5"
@@ -277,22 +268,7 @@ describe("renderingManager", function () {
277
268
  });
278
269
 
279
270
  it("should replace AUCTION_PRICE with with empty value when neither price nor hbPb exist", function () {
280
- let ucTagData = {
281
- cacheHost: "example.com",
282
- cachePath: "/path",
283
- uuid: "123",
284
- size: "300x250",
285
- };
286
-
287
271
  renderAmpOrMobileAd(ucTagData);
288
-
289
- let response = {
290
- width: 300,
291
- height: 250,
292
- crid: 123,
293
- adm: "ad-markup${AUCTION_PRICE}",
294
- wurl: "https://test.prebidcache.wurl",
295
- };
296
272
  requests[0].respond(200, {}, JSON.stringify(response));
297
273
  expect(writeHtmlSpy.args[0][0]).to.equal(
298
274
  "<!--Creative 123 served by Prebid.js Header Bidding-->ad-markup"
@@ -308,9 +284,26 @@ describe("renderingManager", function () {
308
284
  });
309
285
 
310
286
  describe('writeAdHtml', () => {
287
+
288
+ afterEach(() => {
289
+ window.testScriptExecuted = undefined;
290
+ });
291
+
311
292
  it('removes DOCTYPE from markup', () => {
312
293
  const ps = sinon.stub();
313
294
  writeAdHtml('<!DOCTYPE html><div>mock-ad</div>', ps);
314
- sinon.assert.calledWith(ps, sinon.match.any, '<div>mock-ad</div>')
315
- })
295
+ sinon.assert.calledWith(ps, '<div>mock-ad</div>')
296
+ });
297
+
298
+ it('removes lowercase doctype from markup', () => {
299
+ const ps = sinon.stub();
300
+ writeAdHtml('<!doctype html><div>mock-ad</div>', ps);
301
+ sinon.assert.calledWith(ps, '<div>mock-ad</div>')
302
+ });
303
+
304
+ it('should execute script tag inserted into the body', () => {
305
+ const markup = '<script>window.testScriptExecuted=true;</script>'
306
+ writeAdHtml(markup);
307
+ expect(window.testScriptExecuted).to.equal(true);
308
+ });
316
309
  })
@@ -3,7 +3,9 @@ import {merge} from 'lodash';
3
3
  import {newNativeAssetManager} from 'src/nativeAssetManager';
4
4
  import {mocks} from 'test/helpers/mocks';
5
5
  import * as utils from 'src/utils';
6
+ import * as dynamic from 'src/dynamicRenderer.js';
6
7
  import {prebidMessenger} from '../../src/messaging.js';
8
+ import {MIN_RENDERER_VERSION} from "src/dynamicRenderer.js";
7
9
 
8
10
  const ORIGIN = 'https://origin.com'
9
11
  const AD_ID = 'abc123';
@@ -90,7 +92,7 @@ function generateRenderer(assets) {
90
92
  }
91
93
 
92
94
  describe('nativeAssetManager', () => {
93
- let win;
95
+ let win, sandbox;
94
96
 
95
97
  function makeManager(args, mkMessenger = prebidMessenger) {
96
98
  return newNativeAssetManager(win, {
@@ -101,6 +103,29 @@ describe('nativeAssetManager', () => {
101
103
 
102
104
  beforeEach(() => {
103
105
  win = merge(mocks.createFakeWindow(), mockDocument.getWindowObject());
106
+ sandbox = sinon.createSandbox();
107
+ });
108
+
109
+ afterEach(() => {
110
+ sandbox.restore();
111
+ })
112
+
113
+ it(`should run dynamic renderer`, () => {
114
+ const data = {
115
+ renderer: 'mock-renderer',
116
+ rendererVersion: MIN_RENDERER_VERSION,
117
+ native: 'data'
118
+ }
119
+ sandbox.stub(dynamic, 'runDynamicRenderer');
120
+ const sendMessage = sinon.stub().callsFake((msg, reply) => {
121
+ reply({data: JSON.stringify(Object.assign({adId: '123', message: 'assetResponse'}, data))});
122
+ })
123
+ win.pbNativeData = {
124
+ requestAllAssets: true
125
+ };
126
+ const mgr = makeManager({}, () => sendMessage);
127
+ mgr.loadAssets('123');
128
+ sinon.assert.calledWith(dynamic.runDynamicRenderer, '123', sinon.match(data));
104
129
  });
105
130
 
106
131
  describe('safe frames enabled', () => {
@@ -256,7 +281,7 @@ describe('nativeAssetManager', () => {
256
281
  ],null,null);
257
282
 
258
283
  const nativeAssetManager = makeManager();
259
- nativeAssetManager.loadAssets(AD_ID);
284
+ nativeAssetManager.loadAssets(win.pbNativeData.adId);
260
285
 
261
286
  expect(win.document.body.innerHTML).to.equal(`<script>
262
287
  let nativeTag = {};
@@ -558,8 +583,9 @@ describe('nativeAssetManager', () => {
558
583
  }
559
584
  })
560
585
 
561
- it('should set the iframe to the width of the container', () => {
562
- const html = `<script>
586
+ describe('body width resizing', () => {
587
+ beforeEach(() => {
588
+ const html = `<script>
563
589
  let nativeTag = {};
564
590
  nativeTag.adTemplate = "<div class=\"sponsored-post\">\r\n <div class=\"thumbnail\"><\/div>\r\n <div class=\"content\">\r\n <h1>\r\n <a href=\"##hb_native_linkurl##\" target=\"_blank\" class=\"pb-click\">##hb_native_title##<\/a>\r\n <\/h1>\r\n <p>##hb_native_body##<\/p>\r\n \t<div class=\"attribution\">\r\n \t<img class=\"pb-icon\" src=\"##hb_native_image##\" alt=\"icon\" height=\"150\" width=\"50\">\r\n \t\r\n \t<\/div>\r\n\t<\/div>\r\n<\/div>";
565
591
  nativeTag.pubUrl = "https://www.url.com";
@@ -567,27 +593,51 @@ describe('nativeAssetManager', () => {
567
593
  nativeTag.requestAllAssets = true;
568
594
  window.pbNativeTag.renderNativeAd(nativeTag);
569
595
  </script>`;
570
- win.pbNativeData = {
571
- pubUrl : 'https://www.url.com',
572
- adId : AD_ID,
573
- adTemplate : '<div class=\"sponsored-post\">\r\n <div class=\"thumbnail\"><\/div>\r\n <div class=\"content\">\r\n <h1>\r\n <a href=\"##hb_native_linkurl##\" target=\"_blank\" class=\"pb-click\">##hb_native_title##<\/a>\r\n <\/h1>\r\n <p>##hb_native_body##<\/p>\r\n \t<div class=\"attribution\">\r\n \t<img class=\"pb-icon\" src=\"##hb_native_image##\" alt=\"icon\" height=\"150\" width=\"50\">\r\n \t\r\n \t<\/div>\r\n\t<\/div>\r\n<\/div>'
574
- };
575
-
576
- win.document.body.innerHTML = html;
577
- win.addEventListener = createResponder([
578
- { key: 'body', value: 'Body content' },
579
- { key: 'title', value: 'new value' },
580
- { key: 'clickUrl', value: 'http://www.example.com' },
581
- { key: 'image', value: 'http://www.image.com/picture.jpg' },
582
- ]);
596
+ win.pbNativeData = {
597
+ pubUrl : 'https://www.url.com',
598
+ adId : AD_ID,
599
+ adTemplate : '<div class=\"sponsored-post\">\r\n <div class=\"thumbnail\"><\/div>\r\n <div class=\"content\">\r\n <h1>\r\n <a href=\"##hb_native_linkurl##\" target=\"_blank\" class=\"pb-click\">##hb_native_title##<\/a>\r\n <\/h1>\r\n <p>##hb_native_body##<\/p>\r\n \t<div class=\"attribution\">\r\n \t<img class=\"pb-icon\" src=\"##hb_native_image##\" alt=\"icon\" height=\"150\" width=\"50\">\r\n \t\r\n \t<\/div>\r\n\t<\/div>\r\n<\/div>'
600
+ };
601
+
602
+ win.document.body.innerHTML = html;
603
+ win.addEventListener = createResponder([
604
+ { key: 'body', value: 'Body content' },
605
+ { key: 'title', value: 'new value' },
606
+ { key: 'clickUrl', value: 'http://www.example.com' },
607
+ { key: 'image', value: 'http://www.image.com/picture.jpg' },
608
+ ]);
609
+ });
583
610
 
584
- const nativeAssetManager = makeManager();
585
- nativeAssetManager.loadAssets(AD_ID);
611
+ it('should not choke when parent window is not available',() => {
612
+ win.parent.frames = [win];
613
+ Object.defineProperty(win.parent, 'document', {
614
+ get() {
615
+ throw new Error('unvailable');
616
+ }
617
+ })
618
+ const nativeAssetManager = makeManager();
619
+ nativeAssetManager.loadAssets(AD_ID);
620
+ expect(win.document.body.innerHTML).to.include(`<a href="http://www.example.com" target="_blank" class="pb-click">new value</a>`);
621
+ });
586
622
 
587
- expect(win.document.body.innerHTML).to.include(`<a href="http://www.example.com" target="_blank" class="pb-click">new value</a>`);
588
- expect(win.document.body.innerHTML).to.include(`<img class="pb-icon" src="http://www.image.com/picture.jpg" alt="icon" height="150" width="50">`);
589
- expect(win.document.body.innerHTML).to.include(`<p>Body content</p>`);
590
- expect(win.document.body.style.width).to.equal('600px');
623
+ it('should not request width resize if width is 1', () => {
624
+ sandbox.stub(document.body, 'clientWidth').get(() => 1);
625
+ const nativeAssetManager = makeManager();
626
+ nativeAssetManager.loadAssets(AD_ID);
627
+ const resizeRequest = win.parent.postMessage.args
628
+ .map(([msg]) => JSON.parse(msg))
629
+ .find((msg) => msg.action === 'resizeNativeHeight')
630
+ expect(resizeRequest.width).to.not.exist;
631
+ })
632
+
633
+ it('should set the iframe to the width of the container', () => {
634
+ const nativeAssetManager = makeManager();
635
+ nativeAssetManager.loadAssets(AD_ID);
636
+ expect(win.document.body.innerHTML).to.include(`<a href="http://www.example.com" target="_blank" class="pb-click">new value</a>`);
637
+ expect(win.document.body.innerHTML).to.include(`<img class="pb-icon" src="http://www.image.com/picture.jpg" alt="icon" height="150" width="50">`);
638
+ expect(win.document.body.innerHTML).to.include(`<p>Body content</p>`);
639
+ expect(win.document.body.style.width).to.equal('600px');
640
+ });
591
641
  });
592
642
  });
593
643
 
@@ -1,9 +1,11 @@
1
1
  import { renderCrossDomain, renderLegacy } from 'src/renderingManager';
2
2
  import * as utils from 'src/utils';
3
3
  import * as domHelper from 'src/domHelper';
4
+ import * as dynamic from 'src/dynamicRenderer.js';
4
5
  import { expect } from 'chai';
5
6
  import { mocks } from 'test/helpers/mocks';
6
7
  import { merge } from 'lodash';
8
+ import {MIN_RENDERER_VERSION} from "src/dynamicRenderer.js";
7
9
 
8
10
  function renderingMocks() {
9
11
  return {
@@ -72,6 +74,7 @@ describe('renderingManager', function() {
72
74
 
73
75
  describe('cross domain creative', function() {
74
76
  const ORIGIN = 'http://example.com';
77
+ let sandbox;
75
78
  let parseStub;
76
79
  let iframeStub;
77
80
  let triggerPixelSpy;
@@ -81,10 +84,11 @@ describe('renderingManager', function() {
81
84
  let eventSource;
82
85
 
83
86
  beforeEach(function(){
87
+ sandbox = sinon.createSandbox();
84
88
  mockIframe = createMockIframe();
85
- parseStub = sinon.stub(utils, 'parseUrl');
86
- iframeStub = sinon.stub(domHelper, 'getEmptyIframe').returns(mockIframe);
87
- triggerPixelSpy = sinon.stub(utils, 'triggerPixel');
89
+ parseStub = sandbox.stub(utils, 'parseUrl');
90
+ iframeStub = sandbox.stub(domHelper, 'getEmptyIframe').returns(mockIframe);
91
+ triggerPixelSpy = sandbox.stub(utils, 'triggerPixel');
88
92
  parseStub.returns({
89
93
  protocol: 'http',
90
94
  host: 'example.com'
@@ -102,9 +106,7 @@ describe('renderingManager', function() {
102
106
  });
103
107
 
104
108
  afterEach(function () {
105
- parseStub.restore();
106
- iframeStub.restore();
107
- triggerPixelSpy.restore();
109
+ sandbox.restore();
108
110
  });
109
111
 
110
112
  function mockPrebidResponse(msg) {
@@ -114,6 +116,18 @@ describe('renderingManager', function() {
114
116
  });
115
117
  }
116
118
 
119
+ it('should run renderer if present', () => {
120
+ sandbox.stub(dynamic, 'runDynamicRenderer');
121
+ const data = {
122
+ adId: '123',
123
+ renderer: 'mock-renderer',
124
+ rendererVersion: MIN_RENDERER_VERSION,
125
+ ad: 'markup'
126
+ };
127
+ mockPrebidResponse(data);
128
+ sinon.assert.calledWith(dynamic.runDynamicRenderer, data.adId, sinon.match(data))
129
+ })
130
+
117
131
  it("should render cross domain creative", function () {
118
132
  mockPrebidResponse({
119
133
  ad: "ad",