From 0bf13c04d725bb2a457bc1ec22564cd6878b10b0 Mon Sep 17 00:00:00 2001 From: Rachel Myers Date: Tue, 18 Aug 2020 16:10:19 -0400 Subject: [PATCH 01/22] ACI-71: Single Request Architecture --- src/index.js | 32 +++++++++++++++++++++++++++++++- src/services/prebid.js | 17 +++++++++++++---- 2 files changed, 44 insertions(+), 5 deletions(-) diff --git a/src/index.js b/src/index.js index 23c9901..30cd9e7 100644 --- a/src/index.js +++ b/src/index.js @@ -101,6 +101,36 @@ export class ArcAds { }); } + /** + * @desc Registers a collection of advertisements as single prebid and ad calls + * @param {array} collection - An array containing a list of objects containing advertisement data. + **/ + registerAdCollectionSingleCall(collection) { + + window.blockArcAdsLoad = true; + window.blockArcAdsPrebid = true; + + collection.forEach((advert) => { + this.registerAd(advert); + }); + + window.blockArcAdsLoad = false; + window.blockArcAdsPrebid = false; + + //prebid call + pbjs.requestBids({ + timeout: BIDDER_TIMEOUT || 700, + //adUnitCodes: codes, + bidsBackHandler: (result) => { + console.log('Bid Back Handler', result); + pbjs.setTargetingForGPTAsync(); + + //ads call + window.googletag.pubads().refresh(); + } + }); + } + /** * @desc Displays an advertisement and sets up any neccersary event binding. * @param {object} params - An object containing all of the function arguments. @@ -169,7 +199,7 @@ export class ArcAds { bidding, breakpoints: safebreakpoints }); - } else { + } else if(!window.blockArcAdsPrebid){ refreshSlot({ ad, prerender, diff --git a/src/services/prebid.js b/src/services/prebid.js index afd31f6..32f9b4a 100644 --- a/src/services/prebid.js +++ b/src/services/prebid.js @@ -17,11 +17,14 @@ export function queuePrebidCommand(fn) { * @param {function} prerender - An optional function that will run before the advertisement renders. * @param {function} cb - An optional callback function that should fire whenever the bidding has concluded. **/ -export function fetchPrebidBids(ad, code, timeout, info, prerender, cb = null) { - pbjs.addAdUnits(info); +export function fetchPrebidBidsArray(ad, codes, timeout, info, prerender, cb = null) { + pbjs.addAdUnits(info); //eslint-disable-line no-undef + if (window.blockArcAdsPrebid) { + return; + } pbjs.requestBids({ timeout, - adUnitCodes: [code], + adUnitCodes: codes, bidsBackHandler: (result) => { console.log('Bid Back Handler', result); pbjs.setTargetingForGPTAsync([code]); @@ -30,10 +33,16 @@ export function fetchPrebidBids(ad, code, timeout, info, prerender, cb = null) { } else { refreshSlot({ ad, info, prerender }); } - } + }, }); } +export function fetchPrebidBids(ad, code, timeout, info, prerender, cb = null) { + const newInfo = info; + newInfo.bids = Array.isArray(info.bids) ? info.bids : [info.bids]; + fetchPrebidBidsArray(ad, [code], timeout, newInfo, prerender, cb = null); +} + /** * @desc Registers an advertisement with Prebid.js so it's prepared to fetch bids for it. * @param {string} code - Contains the div id or slotname used for the advertisement From 9214d45813438ff0b140704d7810c8ac3b1c5b93 Mon Sep 17 00:00:00 2001 From: Rachel Myers Date: Tue, 18 Aug 2020 16:34:46 -0400 Subject: [PATCH 02/22] ACI-71: progress --- src/index.js | 3 +-- src/services/prebid.js | 2 +- 2 files changed, 2 insertions(+), 3 deletions(-) diff --git a/src/index.js b/src/index.js index 30cd9e7..ba65adc 100644 --- a/src/index.js +++ b/src/index.js @@ -106,7 +106,6 @@ export class ArcAds { * @param {array} collection - An array containing a list of objects containing advertisement data. **/ registerAdCollectionSingleCall(collection) { - window.blockArcAdsLoad = true; window.blockArcAdsPrebid = true; @@ -199,7 +198,7 @@ export class ArcAds { bidding, breakpoints: safebreakpoints }); - } else if(!window.blockArcAdsPrebid){ + } else if (!window.blockArcAdsPrebid) { refreshSlot({ ad, prerender, diff --git a/src/services/prebid.js b/src/services/prebid.js index 32f9b4a..c17c01f 100644 --- a/src/services/prebid.js +++ b/src/services/prebid.js @@ -40,7 +40,7 @@ export function fetchPrebidBidsArray(ad, codes, timeout, info, prerender, cb = n export function fetchPrebidBids(ad, code, timeout, info, prerender, cb = null) { const newInfo = info; newInfo.bids = Array.isArray(info.bids) ? info.bids : [info.bids]; - fetchPrebidBidsArray(ad, [code], timeout, newInfo, prerender, cb = null); + fetchPrebidBidsArray(ad, [code], timeout, newInfo, prerender, cb); } /** From 3d5ef2fda59aef2edc609b4816763f9ddb3661bf Mon Sep 17 00:00:00 2001 From: Rachel Myers Date: Thu, 20 Aug 2020 10:47:58 -0400 Subject: [PATCH 03/22] ACI-71: tests --- dist/arcads.js | 2 +- package.json | 2 +- src/__tests__/mobile.test.js | 479 +++++++++++++++++++++++++++++++++++ src/__tests__/util.test.js | 31 +++ 4 files changed, 512 insertions(+), 2 deletions(-) create mode 100644 src/__tests__/mobile.test.js diff --git a/dist/arcads.js b/dist/arcads.js index cd73372..ac706bc 100644 --- a/dist/arcads.js +++ b/dist/arcads.js @@ -1 +1 @@ -!function(e,n){if("object"==typeof exports&&"object"==typeof module)module.exports=n();else if("function"==typeof define&&define.amd)define([],n);else{var i=n();for(var t in i)("object"==typeof exports?exports:e)[t]=i[t]}}("undefined"!=typeof self?self:this,function(){return function(e){var n={};function i(t){if(n[t])return n[t].exports;var r=n[t]={i:t,l:!1,exports:{}};return e[t].call(r.exports,r,r.exports,i),r.l=!0,r.exports}return i.m=e,i.c=n,i.d=function(e,n,t){i.o(e,n)||Object.defineProperty(e,n,{configurable:!1,enumerable:!0,get:t})},i.n=function(e){var n=e&&e.__esModule?function(){return e.default}:function(){return e};return i.d(n,"a",n),n},i.o=function(e,n){return Object.prototype.hasOwnProperty.call(e,n)},i.p="",i(i.s=3)}([function(e,n,i){"use strict";Object.defineProperty(n,"__esModule",{value:!0}),n.initializeGPT=function(){window.googletag=window.googletag||{},window.googletag.cmd=window.googletag.cmd||[],(0,t.appendResource)("script","//www.googletagservices.com/tag/js/gpt.js",!0,!0)},n.refreshSlot=function(e){var n=e.ad,i=e.correlator,t=void 0!==i&&i,r=e.prerender,o=void 0===r?null:r,a=e.info,d=void 0===a?{}:a;new Promise(function(e){if(o)try{o(d).then(function(){e("Prerender function has completed.")})}catch(n){console.warn("ArcAds: Prerender function did not return a promise or there was an error.\n Documentation: https://github.com/wapopartners/arc-ads/wiki/Utilizing-a-Prerender-Hook"),e("Prerender function did not return a promise or there was an error, ignoring.")}else e("No Prerender function was provided.")}).then(function(){!function e(){if(window.blockArcAdsLoad)return;window.googletag&&googletag.pubadsReady?window.googletag.pubads().refresh([n],{changeCorrelator:t}):setTimeout(function(){e()},200)}()})},n.queueGoogletagCommand=function(e){window.googletag.cmd.push(e)},n.setTargeting=function(e,n){for(var i in n)n.hasOwnProperty(i)&&n[i]&&e.setTargeting(i,n[i])},n.dfpSettings=function(e){window.googletag.pubads().disableInitialLoad(),window.googletag.pubads().enableSingleRequest(),window.googletag.pubads().enableAsyncRendering(),this.collapseEmptyDivs&&window.googletag.pubads().collapseEmptyDivs();window.googletag.enableServices(),e&&window.googletag.pubads().addEventListener("slotRenderEnded",e)},n.determineSlotName=function(e,n){var i=(0,r.expandQueryString)("adslot");if(i&&(""!==i||null!==i))return"/"+e+"/"+i;return"/"+e+"/"+n};var t=i(5),r=i(6)},function(e,n,i){"use strict";Object.defineProperty(n,"__esModule",{value:!0}),n.initializeBiddingServices=a,n.fetchBids=function(e){var n=this,i=e.ad,d=e.id,s=e.slotName,u=e.dimensions,l=e.wrapper,c=e.bidding,p=e.correlator,f=void 0!==p&&p,g=e.prerender,h=e.breakpoints,b={adUnit:i,adSlot:s,adDimensions:u,adId:d,bids:c},v=new Promise(function(e){if(l.prebid&&l.prebid.enabled){var r=l.prebid.timeout||700;t.queuePrebidCommand.bind(n,(0,t.fetchPrebidBids)(i,l.prebid.useSlotForAdUnit?s:d,r,b,g,function(){e("Fetched Prebid ads!")}))}else e("Prebid is not enabled on the wrapper...")}),m=new Promise(function(e){l.amazon&&l.amazon.enabled?(0,r.fetchAmazonBids)(d,s,u,h,function(){e("Fetched Amazon ads!")}):e("Amazon is not enabled on the wrapper...")});window.arcBiddingReady?Promise.all([v,m]).then(function(){(0,o.refreshSlot)({ad:i,correlator:f,prerender:g,info:b})}):setTimeout(function(){return a()},200)};var t=i(2),r=i(7),o=i(0);function a(e){var n=e.prebid,i=void 0!==n&&n,t=e.amazon,o=void 0!==t&&t;if(!window.arcBiddingReady){window.arcBiddingReady=!1;var a=new Promise(function(e){if(i&&i.enabled){if("undefined"==typeof pbjs){var n=n||{};n.que=n.que||[]}e("Prebid has been initialized")}else e("Prebid is not enabled on the wrapper...")}),d=new Promise(function(e){o&&o.enabled&&window.apstag?o.id&&""!==o.id?(0,r.queueAmazonCommand)(function(){window.apstag.init({pubID:o.id,adServer:"googletag"}),e("Amazon scripts have been added onto the page!")}):(console.warn("ArcAds: Missing Amazon account id. \n Documentation: https://github.com/wapopartners/arc-ads#amazon-tama9"),e("Amazon is not enabled on the wrapper...")):e("Amazon is not enabled on the wrapper...")});Promise.all([a,d]).then(function(){window.arcBiddingReady=!0})}}},function(e,n,i){"use strict";Object.defineProperty(n,"__esModule",{value:!0});var t=Object.assign||function(e){for(var n=1;n5&&void 0!==arguments[5]?arguments[5]:null;pbjs.addAdUnits(t),pbjs.requestBids({timeout:i,adUnitCodes:[n],bidsBackHandler:function(i){console.log("Bid Back Handler",i),pbjs.setTargetingForGPTAsync([n]),a?a():(0,r.refreshSlot)({ad:e,info:t,prerender:o})}})},n.addUnit=function(e,n,i){var r=arguments.length>3&&void 0!==arguments[3]?arguments[3]:{},o=arguments.length>4&&void 0!==arguments[4]?arguments[4]:{},a=t({code:e,bids:i},o);a.mediaTypes={banner:{sizes:n}};var d=r.sizeConfig,s=r.config;if(pbjs.addAdUnits(a),s)return void pbjs.setConfig(s);d&&pbjs.setConfig({sizeConfig:d})};var r=i(0)},function(e,n,i){"use strict";Object.defineProperty(n,"__esModule",{value:!0}),n.ArcAds=void 0;var t=function(){function e(e,n){for(var i=0;i1&&void 0!==arguments[1]?arguments[1]:null;!function(e,n){if(!(e instanceof n))throw new TypeError("Cannot call a class as a function")}(this,e),this.dfpId=n.dfp.id||"",this.wrapper=n.bidding||{},this.positions=[],this.collapseEmptyDivs=n.dfp.collapseEmptyDivs,window.isMobile=r.MobileDetection,""===this.dfpId?console.warn("ArcAds: DFP id is missing from the arcads initialization script. \n Documentation: https://github.com/wapopartners/arc-ads#getting-started"):((0,a.initializeGPT)(),(0,a.queueGoogletagCommand)(a.dfpSettings.bind(this,i)),(0,o.initializeBiddingServices)(this.wrapper))}return t(e,[{key:"registerAd",value:function(e){var n=e.id,i=e.slotName,t=e.dimensions,r=e.adType,o=void 0!==r&&r,s=e.targeting,l=void 0===s?{}:s,c=e.display,p=void 0===c?"all":c,f=e.bidding,g=void 0!==f&&f,h=e.iframeBidders,b=void 0===h?["openx"]:h,v=e.others,m=void 0===v?{}:v,w=[],y=!1,A=function e(n){return Array.isArray(n)?1+Math.max.apply(Math,u(n.map(function(n){return e(n)}))):0}(t);t&&void 0!==t&&1===A?w.push.apply(w,u(t)):t&&void 0!==t&&t.length>0&&2===A?w.push.apply(w,u(t)):t&&t.forEach(function(e){w.push.apply(w,u(e))});try{if(!(l&&l.hasOwnProperty("position")||!1===o)){var P=this.positions[o]+1||1;this.positions[o]=P;var k=Object.assign(l,{position:P});Object.assign(e,{targeting:k})}if(isMobile.any()&&"mobile"===p||!isMobile.any()&&"desktop"===p||"all"===p){if(g.prebid&&g.prebid.bids&&this.wrapper.prebid&&this.wrapper.prebid.enabled&&w){pbjs&&b.length>0&&pbjs.setConfig({userSync:{iframeEnabled:!0,filterSettings:{iframe:{bidders:b,filter:"include"}}}});var z=this.wrapper.prebid.useSlotForAdUnit?(0,a.determineSlotName)(this.dfpId,i):n;d.queuePrebidCommand.bind(this,(0,d.addUnit)(z,w,g.prebid.bids,this.wrapper.prebid,m))}(y=this.displayAd.bind(this,e))&&(0,a.queueGoogletagCommand)(y)}}catch(e){console.error("ads error",e)}}},{key:"registerAdCollection",value:function(e){var n=this;e.forEach(function(e){n.registerAd(e)})}},{key:"displayAd",value:function(e){var n=e.id,i=e.slotName,t=e.dimensions,r=e.targeting,d=e.sizemap,u=void 0!==d&&d,l=e.bidding,c=void 0!==l&&l,p=e.prerender,f=void 0===p?null:p,g=(0,a.determineSlotName)(this.dfpId,i),h=t&&!t.length?null:t,b=t?window.googletag.defineSlot(g,h,n):window.googletag.defineOutOfPageSlot(g,n);if(u&&u.breakpoints&&t){var v=(0,s.prepareSizeMaps)(h,u.breakpoints),m=v.mapping,w=v.breakpoints,y=v.correlators;if(!b)return!1;b.defineSizeMapping(m),u.refresh&&(0,s.setResizeListener)({ad:b,slotName:g,breakpoints:w,id:n,mapping:m,correlators:y,bidding:c,wrapper:this.wrapper,prerender:f})}b&&(b.addService(window.googletag.pubads()),(0,a.setTargeting)(b,r));var A=u&&u.breakpoints?u.breakpoints:[];t&&c&&(c.amazon&&c.amazon.enabled||c.prebid&&c.prebid.enabled)?(0,o.fetchBids)({ad:b,id:n,slotName:g,dimensions:h,wrapper:this.wrapper,prerender:f,bidding:c,breakpoints:A}):(0,a.refreshSlot)({ad:b,prerender:f,info:{adUnit:b,adSlot:g,adDimensions:h,adId:n}})}}]),e}()},function(e,n,i){"use strict";Object.defineProperty(n,"__esModule",{value:!0});var t=function(){function e(e,n){for(var i=0;i1}},{key:"any",value:function(){return this.Android()||this.Kindle()||this.KindleFire()||this.Silk()||this.BlackBerry()||this.iOS()||this.Windows()||this.FirefoxOS()}}]),e}();n.default=r},function(e,n,i){"use strict";Object.defineProperty(n,"__esModule",{value:!0}),n.appendResource=function(e,n,i,t,r){var o=document.createElement(e);if("script"!==e)return;o.src=n,o.async=i||!1,o.defer=i||t||!1;(document.head||document.documentElement).appendChild(o),r&&r()}},function(e,n,i){"use strict";Object.defineProperty(n,"__esModule",{value:!0}),n.expandQueryString=function(e){var n=window.location.href,i=e.replace(/[[\]]/g,"\\$&"),t=new RegExp("[?&]"+i+"(=([^&#]*)|&|#|$)").exec(n);if(!t)return null;if(!t[2])return"";return decodeURIComponent(t[2].replace(/\+/g," "))}},function(e,n,i){"use strict";function t(e){window.apstag&&e()}Object.defineProperty(n,"__esModule",{value:!0}),n.fetchAmazonBids=function(e,n,i,r){var o=arguments.length>4&&void 0!==arguments[4]?arguments[4]:null,a=i;if(r&&void 0!==window.innerWidth&&void 0!==i[0][0][0]){for(var d=window.innerWidth,s=-1,u=r.length,l=0;l=r[l][0]){s=l;break}a=i[s]}t(function(){var i={slotName:n,slotID:e,sizes:a};window.apstag.fetchBids({slots:[i]},function(){window.apstag.setDisplayBids(),o&&o()})})},n.queueAmazonCommand=t},function(e,n,i){"use strict";Object.defineProperty(n,"__esModule",{value:!0}),n.resizeListeners=n.sizemapListeners=void 0,n.prepareSizeMaps=function(e,n){var i=[],t=[],r=[];return(n.length?n:null).forEach(function(n,o){i.push([n,e[o]]),-1===t.indexOf(n[0])&&(t.push(n[0]),r.push(!1))}),t.sort(function(e,n){return e-n}),{mapping:i,breakpoints:t,correlators:r}},n.parseSizeMappings=s,n.runResizeEvents=u,n.setResizeListener=function(e){var n=e.id,i=e.correlators;d[n]=(0,t.debounce)(u(e),250),window.addEventListener("resize",d[n]),a[n]={listener:d[n],correlators:i}};var t=i(9),r=i(1),o=i(0),a=n.sizemapListeners={},d=n.resizeListeners={};function s(e){try{var n=[window.innerWidth||document.documentElement.clientWidth||document.body.clientWidth,window.innerHeight||document.documentElement.clientHeight||document.body.clientHeight],i=e.filter(function(e){return e[0][0]<=n[0]&&e[0][1]<=n[1]}),t=i.length>0?i[0][1]:[];return t.length>0&&t[0].constructor!==Array&&(t=[t]),t}catch(n){return e[e.length-1][1]}}function u(e){var n=void 0,i=!1;return function(){for(var t=e.ad,d=e.breakpoints,u=e.id,l=e.bidding,c=e.mapping,p=e.slotName,f=e.wrapper,g=e.prerender,h=window.innerWidth,b=void 0,v=void 0,m=0;mb&&(h5&&void 0!==arguments[5]?arguments[5]:null,d=r;d.bids=Array.isArray(r.bids)?r.bids:[r.bids],o(e,[n],i,d,t,a)},n.addUnit=function(e,n,i){var t=arguments.length>3&&void 0!==arguments[3]?arguments[3]:{},o=arguments.length>4&&void 0!==arguments[4]?arguments[4]:{},a=r({code:e,bids:i},o);a.mediaTypes={banner:{sizes:n}};var d=t.sizeConfig,s=t.config;if(pbjs.addAdUnits(a),s)return void pbjs.setConfig(s);d&&pbjs.setConfig({sizeConfig:d})};var t=i(0);function o(e,n,i,r,o){var a=arguments.length>5&&void 0!==arguments[5]?arguments[5]:null;pbjs.addAdUnits(r),window.blockArcAdsPrebid||pbjs.requestBids({timeout:i,adUnitCodes:n,bidsBackHandler:function(n){console.log("Bid Back Handler",n),pbjs.setTargetingForGPTAsync([code]),a?a():(0,t.refreshSlot)({ad:e,info:r,prerender:o})}})}},function(e,n,i){"use strict";Object.defineProperty(n,"__esModule",{value:!0}),n.ArcAds=void 0;var r=function(){function e(e,n){for(var i=0;i1&&void 0!==arguments[1]?arguments[1]:null;!function(e,n){if(!(e instanceof n))throw new TypeError("Cannot call a class as a function")}(this,e),this.dfpId=n.dfp.id||"",this.wrapper=n.bidding||{},this.positions=[],this.collapseEmptyDivs=n.dfp.collapseEmptyDivs,window.isMobile=t.MobileDetection,""===this.dfpId?console.warn("ArcAds: DFP id is missing from the arcads initialization script. \n Documentation: https://github.com/wapopartners/arc-ads#getting-started"):((0,a.initializeGPT)(),(0,a.queueGoogletagCommand)(a.dfpSettings.bind(this,i)),(0,o.initializeBiddingServices)(this.wrapper))}return r(e,[{key:"registerAd",value:function(e){var n=e.id,i=e.slotName,r=e.dimensions,t=e.adType,o=void 0!==t&&t,s=e.targeting,c=void 0===s?{}:s,l=e.display,p=void 0===l?"all":l,f=e.bidding,g=void 0!==f&&f,b=e.iframeBidders,h=void 0===b?["openx"]:b,v=e.others,m=void 0===v?{}:v,w=[],y=!1,A=function e(n){return Array.isArray(n)?1+Math.max.apply(Math,u(n.map(function(n){return e(n)}))):0}(r);r&&void 0!==r&&1===A?w.push.apply(w,u(r)):r&&void 0!==r&&r.length>0&&2===A?w.push.apply(w,u(r)):r&&r.forEach(function(e){w.push.apply(w,u(e))});try{if(!(c&&c.hasOwnProperty("position")||!1===o)){var P=this.positions[o]+1||1;this.positions[o]=P;var k=Object.assign(c,{position:P});Object.assign(e,{targeting:k})}if(isMobile.any()&&"mobile"===p||!isMobile.any()&&"desktop"===p||"all"===p){if(g.prebid&&g.prebid.bids&&this.wrapper.prebid&&this.wrapper.prebid.enabled&&w){pbjs&&h.length>0&&pbjs.setConfig({userSync:{iframeEnabled:!0,filterSettings:{iframe:{bidders:h,filter:"include"}}}});var z=this.wrapper.prebid.useSlotForAdUnit?(0,a.determineSlotName)(this.dfpId,i):n;d.queuePrebidCommand.bind(this,(0,d.addUnit)(z,w,g.prebid.bids,this.wrapper.prebid,m))}(y=this.displayAd.bind(this,e))&&(0,a.queueGoogletagCommand)(y)}}catch(e){console.error("ads error",e)}}},{key:"registerAdCollection",value:function(e){var n=this;e.forEach(function(e){n.registerAd(e)})}},{key:"registerAdCollectionSingleCall",value:function(e){var n=this;window.blockArcAdsLoad=!0,window.blockArcAdsPrebid=!0,e.forEach(function(e){n.registerAd(e)}),window.blockArcAdsLoad=!1,window.blockArcAdsPrebid=!1,pbjs.requestBids({timeout:BIDDER_TIMEOUT||700,bidsBackHandler:function(e){console.log("Bid Back Handler",e),pbjs.setTargetingForGPTAsync(),window.googletag.pubads().refresh()}})}},{key:"displayAd",value:function(e){var n=e.id,i=e.slotName,r=e.dimensions,t=e.targeting,d=e.sizemap,u=void 0!==d&&d,c=e.bidding,l=void 0!==c&&c,p=e.prerender,f=void 0===p?null:p,g=(0,a.determineSlotName)(this.dfpId,i),b=r&&!r.length?null:r,h=r?window.googletag.defineSlot(g,b,n):window.googletag.defineOutOfPageSlot(g,n);if(u&&u.breakpoints&&r){var v=(0,s.prepareSizeMaps)(b,u.breakpoints),m=v.mapping,w=v.breakpoints,y=v.correlators;if(!h)return!1;h.defineSizeMapping(m),u.refresh&&(0,s.setResizeListener)({ad:h,slotName:g,breakpoints:w,id:n,mapping:m,correlators:y,bidding:l,wrapper:this.wrapper,prerender:f})}h&&(h.addService(window.googletag.pubads()),(0,a.setTargeting)(h,t));var A=u&&u.breakpoints?u.breakpoints:[];r&&l&&(l.amazon&&l.amazon.enabled||l.prebid&&l.prebid.enabled)?(0,o.fetchBids)({ad:h,id:n,slotName:g,dimensions:b,wrapper:this.wrapper,prerender:f,bidding:l,breakpoints:A}):window.blockArcAdsPrebid||(0,a.refreshSlot)({ad:h,prerender:f,info:{adUnit:h,adSlot:g,adDimensions:b,adId:n}})}}]),e}()},function(e,n,i){"use strict";Object.defineProperty(n,"__esModule",{value:!0});var r=function(){function e(e,n){for(var i=0;i1}},{key:"any",value:function(){return this.Android()||this.Kindle()||this.KindleFire()||this.Silk()||this.BlackBerry()||this.iOS()||this.Windows()||this.FirefoxOS()}}]),e}();n.default=t},function(e,n,i){"use strict";Object.defineProperty(n,"__esModule",{value:!0}),n.appendResource=function(e,n,i,r,t){var o=document.createElement(e);if("script"!==e)return;o.src=n,o.async=i||!1,o.defer=i||r||!1;(document.head||document.documentElement).appendChild(o),t&&t()}},function(e,n,i){"use strict";Object.defineProperty(n,"__esModule",{value:!0}),n.expandQueryString=function(e){var n=window.location.href,i=e.replace(/[[\]]/g,"\\$&"),r=new RegExp("[?&]"+i+"(=([^&#]*)|&|#|$)").exec(n);if(!r)return null;if(!r[2])return"";return decodeURIComponent(r[2].replace(/\+/g," "))}},function(e,n,i){"use strict";function r(e){window.apstag&&e()}Object.defineProperty(n,"__esModule",{value:!0}),n.fetchAmazonBids=function(e,n,i,t){var o=arguments.length>4&&void 0!==arguments[4]?arguments[4]:null,a=i;if(t&&void 0!==window.innerWidth&&void 0!==i[0][0][0]){for(var d=window.innerWidth,s=-1,u=t.length,c=0;c=t[c][0]){s=c;break}a=i[s]}r(function(){var i={slotName:n,slotID:e,sizes:a};window.apstag.fetchBids({slots:[i]},function(){window.apstag.setDisplayBids(),o&&o()})})},n.queueAmazonCommand=r},function(e,n,i){"use strict";Object.defineProperty(n,"__esModule",{value:!0}),n.resizeListeners=n.sizemapListeners=void 0,n.prepareSizeMaps=function(e,n){var i=[],r=[],t=[];return(n.length?n:null).forEach(function(n,o){i.push([n,e[o]]),-1===r.indexOf(n[0])&&(r.push(n[0]),t.push(!1))}),r.sort(function(e,n){return e-n}),{mapping:i,breakpoints:r,correlators:t}},n.parseSizeMappings=s,n.runResizeEvents=u,n.setResizeListener=function(e){var n=e.id,i=e.correlators;d[n]=(0,r.debounce)(u(e),250),window.addEventListener("resize",d[n]),a[n]={listener:d[n],correlators:i}};var r=i(9),t=i(1),o=i(0),a=n.sizemapListeners={},d=n.resizeListeners={};function s(e){try{var n=[window.innerWidth||document.documentElement.clientWidth||document.body.clientWidth,window.innerHeight||document.documentElement.clientHeight||document.body.clientHeight],i=e.filter(function(e){return e[0][0]<=n[0]&&e[0][1]<=n[1]}),r=i.length>0?i[0][1]:[];return r.length>0&&r[0].constructor!==Array&&(r=[r]),r}catch(n){return e[e.length-1][1]}}function u(e){var n=void 0,i=!1;return function(){for(var r=e.ad,d=e.breakpoints,u=e.id,c=e.bidding,l=e.mapping,p=e.slotName,f=e.wrapper,g=e.prerender,b=window.innerWidth,h=void 0,v=void 0,m=0;mh&&(b { + afterAll(() => { + window.__defineGetter__('navigator', function () { + return {}; + }); + + window.navigator.__defineGetter__('userAgent', function () { + return null; + }); + + window.__defineGetter__('retina', function () { + return false; + }); + + window.__defineGetter__('devicePixelRatio', function () { + return 0; + }); + + }); + + describe('Android()', () => { + it ('returns true if user agent contains Android', () => { + window.navigator.__defineGetter__('userAgent', function () { + return 'Android'; + }); + + const result = MobileDetection.Android(); + expect(result).toEqual(true); + }); + + it ('returns false if user agent does not contains Android', () => { + + window.navigator.__defineGetter__('userAgent', function () { + return 'that which shall not be named'; + }); + + const result = MobileDetection.Android(); + expect(result).toEqual(false); + }); + + }); + + describe('AndroidOld()', () => { + + it ('returns true if user agent contains Android 2.3.3', () => { + + window.navigator.__defineGetter__('userAgent', function () { + return 'Android 2.3.3'; + }); + + const result = MobileDetection.AndroidOld(); + expect(result).toEqual(true); + }); + + it ('returns false if user agent does not contain Android 2.3.3', () => { + + window.navigator.__defineGetter__('userAgent', function () { + return 'that which shall not be named'; + }); + + const result = MobileDetection.AndroidOld(); + expect(result).toEqual(false); + }); + + }); + + describe('AndroidTablet()', () => { + + it ('returns true if user agent contains Android Mobile', () => { + + window.navigator.__defineGetter__('userAgent', function () { + return 'Android'; + }); + + const result = MobileDetection.AndroidTablet(); + expect(result).toEqual(true); + }); + + it ('returns false if user agent does not contain Android Mobile', () => { + + window.navigator.__defineGetter__('userAgent', function () { + return 'that which shall not be named'; + }); + + const result = MobileDetection.AndroidTablet(); + expect(result).toEqual(false); + }); + + }); + + describe('Kindle()', () => { + + it ('returns true if user agent contains Kindle', () => { + + window.navigator.__defineGetter__('userAgent', function () { + return 'Kindle'; + }); + + const result = MobileDetection.Kindle(); + expect(result).toEqual(true); + }); + + it ('returns false if user agent does not contain Kindle', () => { + + window.navigator.__defineGetter__('userAgent', function () { + return 'that which shall not be named'; + }); + + const result = MobileDetection.Kindle(); + expect(result).toEqual(false); + }); + + }); + + describe('KindleFire()', () => { + + it ('returns true if user agent contains KFOT', () => { + + window.navigator.__defineGetter__('userAgent', function () { + return 'KFOT'; + }); + + const result = MobileDetection.KindleFire(); + expect(result).toEqual(true); + }); + + it ('returns false if user agent does not contain Kindle', () => { + + window.navigator.__defineGetter__('userAgent', function () { + return 'that which shall not be named'; + }); + + const result = MobileDetection.KindleFire(); + expect(result).toEqual(false); + }); + + }); + + + describe('Silk()', () => { + + it ('returns true if user agent contains Silk', () => { + + window.navigator.__defineGetter__('userAgent', function () { + return 'Silk'; + }); + + const result = MobileDetection.Silk(); + expect(result).toEqual(true); + }); + + it ('returns false if user agent does not contain Silk', () => { + + window.navigator.__defineGetter__('userAgent', function () { + return 'that which shall not be named'; + }); + + const result = MobileDetection.Silk(); + expect(result).toEqual(false); + }); + + }); + + describe('BlackBerry()', () => { + + it ('returns true if user agent contains BlackBerry', () => { + + window.navigator.__defineGetter__('userAgent', function () { + return 'BlackBerry'; + }); + + const result = MobileDetection.BlackBerry(); + expect(result).toEqual(true); + }); + + it ('returns false if user agent does not contain BlackBerry', () => { + + window.navigator.__defineGetter__('userAgent', function () { + return 'that which shall not be named'; + }); + + const result = MobileDetection.BlackBerry(); + expect(result).toEqual(false); + }); + + }); + + describe('iOS()', () => { + + it ('returns true if user agent contains iPhone', () => { + + window.navigator.__defineGetter__('userAgent', function () { + return 'iPhone'; + }); + + const result = MobileDetection.iOS(); + expect(result).toEqual(true); + }); + + it ('returns true if user agent contains iPad', () => { + + window.navigator.__defineGetter__('userAgent', function () { + return 'iPad'; + }); + + const result = MobileDetection.iOS(); + expect(result).toEqual(true); + }); + + it ('returns true if user agent contains iPod', () => { + + window.navigator.__defineGetter__('userAgent', function () { + return 'iPod'; + }); + + const result = MobileDetection.iOS(); + expect(result).toEqual(true); + }); + + it ('returns false if user agent does not contain iPhone/iPad/iPod', () => { + + window.navigator.__defineGetter__('userAgent', function () { + return 'that which shall not be named'; + }); + + const result = MobileDetection.iOS(); + expect(result).toEqual(false); + }); + + }); + + describe('iPhone()', () => { + + it ('returns true if user agent contains iPhone', () => { + + window.navigator.__defineGetter__('userAgent', function () { + return 'iPhone'; + }); + + const result = MobileDetection.iPhone(); + expect(result).toEqual(true); + }); + + + + it ('returns true if user agent contains iPod', () => { + + window.navigator.__defineGetter__('userAgent', function () { + return 'iPod'; + }); + + const result = MobileDetection.iPhone(); + expect(result).toEqual(true); + }); + + it ('returns false if user agent does not contain iPhone/iPad/iPod', () => { + + window.navigator.__defineGetter__('userAgent', function () { + return 'iPad'; + }); + + const result = MobileDetection.iPhone(); + expect(result).toEqual(false); + }); + + }); + + describe('iPad()', () => { + + it ('returns true if user agent contains iPad', () => { + + window.navigator.__defineGetter__('userAgent', function () { + return 'iPad'; + }); + + const result = MobileDetection.iPad(); + expect(result).toEqual(true); + }); + + it ('returns false if user agent does not contain iPad', () => { + + window.navigator.__defineGetter__('userAgent', function () { + return 'that which shall not be named'; + }); + + const result = MobileDetection.iPad(); + expect(result).toEqual(false); + }); + + }); + + describe('Windows()', () => { + + it ('returns true if user agent contains IEMobile', () => { + + window.navigator.__defineGetter__('userAgent', function () { + return 'IEMobile'; + }); + + const result = MobileDetection.Windows(); + expect(result).toEqual(true); + }); + + it ('returns false if user agent does not contain IEMobile', () => { + + window.navigator.__defineGetter__('userAgent', function () { + return 'that which shall not be named'; + }); + + const result = MobileDetection.Windows(); + expect(result).toEqual(false); + }); + + }); + + describe('FirefoxOS()', () => { + + it ('returns true if user agent contains Mozilla and Mobile', () => { + + window.navigator.__defineGetter__('userAgent', function () { + return 'Mozilla Mobile'; + }); + + const result = MobileDetection.FirefoxOS(); + expect(result).toEqual(true); + }); + + it ('returns false if user agent contains Mozilla and not Mobile', () => { + + window.navigator.__defineGetter__('userAgent', function () { + return 'Mozilla'; + }); + + const result = MobileDetection.FirefoxOS(); + expect(result).toEqual(false); + }); + + it ('returns false if user agent contains not Mozilla but Mobile', () => { + + window.navigator.__defineGetter__('userAgent', function () { + return 'Mobile'; + }); + + const result = MobileDetection.FirefoxOS(); + expect(result).toEqual(false); + }); + + it ('returns false if user agent contains neither Mozilla or Mobile', () => { + + window.navigator.__defineGetter__('userAgent', function () { + return 'that which shall not be named'; + }); + + const result = MobileDetection.FirefoxOS(); + expect(result).toEqual(false); + }); + + }); + + describe('any()', () => { + + it ('returns true if user agent contains Android', () => { + + window.navigator.__defineGetter__('userAgent', function () { + return 'Android'; + }); + + const result = MobileDetection.any(); + expect(result).toEqual(true); + }); + + it ('returns true if user agent contains Kindle', () => { + + window.navigator.__defineGetter__('userAgent', function () { + return 'Kindle'; + }); + + const result = MobileDetection.any(); + expect(result).toEqual(true); + }); + + it ('returns true if user agent contains KindleFire', () => { + + window.navigator.__defineGetter__('userAgent', function () { + return 'KFOT'; + }); + + const result = MobileDetection.any(); + expect(result).toEqual(true); + }); + + it ('returns true if user agent contains Silk', () => { + + window.navigator.__defineGetter__('userAgent', function () { + return 'Silk'; + }); + + const result = MobileDetection.any(); + expect(result).toEqual(true); + }); + + it ('returns true if user agent contains BlackBerry', () => { + + window.navigator.__defineGetter__('userAgent', function () { + return 'BlackBerry'; + }); + + const result = MobileDetection.any(); + expect(result).toEqual(true); + }); + + it ('returns true if user agent contains iPad', () => { + + window.navigator.__defineGetter__('userAgent', function () { + return 'iPad'; + }); + + const result = MobileDetection.any(); + expect(result).toEqual(true); + }); + + it ('returns true if user agent contains iPod', () => { + + window.navigator.__defineGetter__('userAgent', function () { + return 'iPod'; + }); + + const result = MobileDetection.any(); + expect(result).toEqual(true); + }); + + it ('returns true if user agent contains iPhone', () => { + + window.navigator.__defineGetter__('userAgent', function () { + return 'iPhone'; + }); + + const result = MobileDetection.any(); + expect(result).toEqual(true); + }); + + it ('returns true if user agent contains IEMobile', () => { + + window.navigator.__defineGetter__('userAgent', function () { + return 'IEMobile'; + }); + + const result = MobileDetection.any(); + expect(result).toEqual(true); + }); + + it ('returns true if user agent contains Mozilla Mobile', () => { + + window.navigator.__defineGetter__('userAgent', function () { + return 'Mozilla Mobile'; + }); + + const result = MobileDetection.any(); + expect(result).toEqual(true); + }); + + it ('returns false if user agent contains invalid mobile userAgent', () => { + + window.navigator.__defineGetter__('userAgent', function () { + return 'no no no'; + }); + + const result = MobileDetection.any(); + expect(result).toEqual(false); + }); + + + + }); + +}); diff --git a/src/__tests__/util.test.js b/src/__tests__/util.test.js index 1f829fe..eb25846 100644 --- a/src/__tests__/util.test.js +++ b/src/__tests__/util.test.js @@ -1,4 +1,5 @@ import { renamePositionKey } from '../util/customTargeting'; +import {debounce} from '../util/debounce.js'; describe('The CustomTargeting.js functions', () => { it('should take targeting and position value, and rename the key as posn', () => { @@ -17,3 +18,33 @@ describe('The CustomTargeting.js functions', () => { expect(updatedTargeting).toEqual(newTargeting); }); }); + +describe('debounce', () => { + beforeEach(() => { + jest.useFakeTimers(); + }); + + test('debounce', () => { + const func = jest.fn(); + const debouncedFunc = debounce(func, 1000); + + // Call debounced function immediately + debouncedFunc(); + expect(func).toHaveBeenCalledTimes(0); + + // Call debounced function several times with 500ms between each call + for (let i = 0; i < 10; i += 1) { + setTimeout(() => {}, 500); + debouncedFunc(); + } + + // Verify debounced function was not called yet + expect(func).toHaveBeenCalledTimes(0); + + // Fast forward time + jest.runAllTimers(); + + // Verify debounced function was only called once + expect(func).toHaveBeenCalledTimes(1); + }); +}); From b49f0e3c704c870d9b3a7082e334e52c78fcca0c Mon Sep 17 00:00:00 2001 From: Rachel Myers Date: Fri, 21 Aug 2020 15:50:44 -0400 Subject: [PATCH 04/22] ACI-71: working for APD integration --- package-lock.json | 44 ++++++++++++++++++++-------- src/index.js | 74 +++++++++++++++++++++++++++++++++++++++++++++-- 2 files changed, 104 insertions(+), 14 deletions(-) diff --git a/package-lock.json b/package-lock.json index e94c180..ce28efe 100644 --- a/package-lock.json +++ b/package-lock.json @@ -251,7 +251,8 @@ "acorn": { "version": "2.7.0", "resolved": "http://registry.npmjs.org/acorn/-/acorn-2.7.0.tgz", - "integrity": "sha1-q259nYhqrKiwhbwzEreaGYQz8Oc=" + "integrity": "sha1-q259nYhqrKiwhbwzEreaGYQz8Oc=", + "optional": true }, "acorn-dynamic-import": { "version": "2.0.2", @@ -4144,7 +4145,8 @@ "ansi-regex": { "version": "2.1.1", "bundled": true, - "dev": true + "dev": true, + "optional": true }, "aproba": { "version": "1.2.0", @@ -4165,12 +4167,14 @@ "balanced-match": { "version": "1.0.0", "bundled": true, - "dev": true + "dev": true, + "optional": true }, "brace-expansion": { "version": "1.1.11", "bundled": true, "dev": true, + "optional": true, "requires": { "balanced-match": "^1.0.0", "concat-map": "0.0.1" @@ -4185,17 +4189,20 @@ "code-point-at": { "version": "1.1.0", "bundled": true, - "dev": true + "dev": true, + "optional": true }, "concat-map": { "version": "0.0.1", "bundled": true, - "dev": true + "dev": true, + "optional": true }, "console-control-strings": { "version": "1.1.0", "bundled": true, - "dev": true + "dev": true, + "optional": true }, "core-util-is": { "version": "1.0.2", @@ -4312,7 +4319,8 @@ "inherits": { "version": "2.0.3", "bundled": true, - "dev": true + "dev": true, + "optional": true }, "ini": { "version": "1.3.5", @@ -4324,6 +4332,7 @@ "version": "1.0.0", "bundled": true, "dev": true, + "optional": true, "requires": { "number-is-nan": "^1.0.0" } @@ -4338,6 +4347,7 @@ "version": "3.0.4", "bundled": true, "dev": true, + "optional": true, "requires": { "brace-expansion": "^1.1.7" } @@ -4345,12 +4355,14 @@ "minimist": { "version": "0.0.8", "bundled": true, - "dev": true + "dev": true, + "optional": true }, "minipass": { "version": "2.2.4", "bundled": true, "dev": true, + "optional": true, "requires": { "safe-buffer": "^5.1.1", "yallist": "^3.0.0" @@ -4369,6 +4381,7 @@ "version": "0.5.1", "bundled": true, "dev": true, + "optional": true, "requires": { "minimist": "0.0.8" } @@ -4449,7 +4462,8 @@ "number-is-nan": { "version": "1.0.1", "bundled": true, - "dev": true + "dev": true, + "optional": true }, "object-assign": { "version": "4.1.1", @@ -4461,6 +4475,7 @@ "version": "1.4.0", "bundled": true, "dev": true, + "optional": true, "requires": { "wrappy": "1" } @@ -4546,7 +4561,8 @@ "safe-buffer": { "version": "5.1.1", "bundled": true, - "dev": true + "dev": true, + "optional": true }, "safer-buffer": { "version": "2.1.2", @@ -4582,6 +4598,7 @@ "version": "1.0.2", "bundled": true, "dev": true, + "optional": true, "requires": { "code-point-at": "^1.0.0", "is-fullwidth-code-point": "^1.0.0", @@ -4601,6 +4618,7 @@ "version": "3.0.1", "bundled": true, "dev": true, + "optional": true, "requires": { "ansi-regex": "^2.0.0" } @@ -4644,12 +4662,14 @@ "wrappy": { "version": "1.0.2", "bundled": true, - "dev": true + "dev": true, + "optional": true }, "yallist": { "version": "3.0.2", "bundled": true, - "dev": true + "dev": true, + "optional": true } } }, diff --git a/src/index.js b/src/index.js index ba65adc..9d5d55b 100644 --- a/src/index.js +++ b/src/index.js @@ -17,6 +17,7 @@ export class ArcAds { this.wrapper = options.bidding || {}; this.positions = []; this.collapseEmptyDivs = options.dfp.collapseEmptyDivs; + this.adsList = []; window.isMobile = MobileDetection; @@ -105,7 +106,7 @@ export class ArcAds { * @desc Registers a collection of advertisements as single prebid and ad calls * @param {array} collection - An array containing a list of objects containing advertisement data. **/ - registerAdCollectionSingleCall(collection) { + registerAdCollectionSingleCall(collection, bidderTimeout = 700) { window.blockArcAdsLoad = true; window.blockArcAdsPrebid = true; @@ -118,7 +119,7 @@ export class ArcAds { //prebid call pbjs.requestBids({ - timeout: BIDDER_TIMEOUT || 700, + timeout: bidderTimeout, //adUnitCodes: codes, bidsBackHandler: (result) => { console.log('Bid Back Handler', result); @@ -130,6 +131,27 @@ export class ArcAds { }); } + + /** + * @desc Sets blockArcAdsLoad to be true - stops Ad Calls from going out, + * allowing ads to be saved up for a single ad call to be sent out later. + **/ + static setAdsBlockGate() { + if (typeof window !== 'undefined') { + window.blockArcAdsLoad = true; + } + } + + /** + * @desc Sets blockArcAdsLoad to be true - stops Ad Calls from going out, + * allowing ads to be saved up for a single ad call to be sent out later. + **/ + static releaseAdsBlockGate() { + if (typeof window !== 'undefined') { + window.blockArcAdsLoad = false; + } + } + /** * @desc Displays an advertisement and sets up any neccersary event binding. * @param {object} params - An object containing all of the function arguments. @@ -211,4 +233,52 @@ export class ArcAds { }); } } + + /** + * @desc Send out ads that have been accumulated for the SRA + **/ + sendSingleCallAds(bidderTimeout = 700) { + // if no ads have been accumulated to send out together + // do nothing, return + if (this.adsList && this.adsList.length < 1) { + return; + } + + //ensure library is present and able to send out SRA ads + if (window && window.googletag && googletag.pubadsReady) { // eslint-disable-line + window.googletag.pubads().disableInitialLoad(); + window.googletag.pubads().enableSingleRequest(); + window.googletag.pubads().enableAsyncRendering(); + + this.registerAdCollectionSingleCall(this.adsList, bidderTimeout); + } else { + setTimeout(() => { + this.sendSingleCallAds(); + }, 2000); + } + } + + /** + * Append this ad information to the list of ads + * to be sent out as part of the singleAdCall + * + * @param {Object} params the ad parameters + */ + reserveAd(params) { + ArcAds.setAdsBlockGate(); + this.adsList.push(params); + } + + /** + * Page level targeting - any targeting set + * using this function will apply to all + * ads on the page. This is useful for SRA to + * reduce request length. + * + * @param {string} key Targeting parameter key. + * * @param {string} value Targeting parameter value or array of values. + */ + setPageLeveTargeting(key, value) { //TODO check for pubads + googletag.pubads().setTargeting(key, value); + } } From 32711aa57656bcd79ba95a3fb415e95d955e3fdb Mon Sep 17 00:00:00 2001 From: Rachel Myers Date: Fri, 21 Aug 2020 17:34:12 -0400 Subject: [PATCH 05/22] ACI-71: index test start --- src/__tests__/arcads.test.js | 70 +++++++++++++++++++++++++++++++++++- src/index.js | 7 ++-- 2 files changed, 74 insertions(+), 3 deletions(-) diff --git a/src/__tests__/arcads.test.js b/src/__tests__/arcads.test.js index ee0a08d..8eceb85 100644 --- a/src/__tests__/arcads.test.js +++ b/src/__tests__/arcads.test.js @@ -1,4 +1,6 @@ import { ArcAds } from '../index'; +import * as gptService from '../services/gpt.js'; +import * as prebidService from '../services/prebid.js'; describe('arcads', () => { const arcAds = new ArcAds({ @@ -16,7 +18,7 @@ describe('arcads', () => { } }); - describe('#constructor', () => { + describe('constructor', () => { it('should initialize arc ads', () => { expect(arcAds).not.toBeUndefined(); }); @@ -30,5 +32,71 @@ describe('arcads', () => { const { arcBiddingReady } = global; expect(arcBiddingReady).toBeDefined(); }); + + it('should console warn if no dfpID provided', () => { + const consoleMock = jest.fn(); + console.warn = consoleMock; + const arcAds = new ArcAds({ + dfp: { + } + }); + + expect(consoleMock).toHaveBeenCalledTimes(1); + expect(consoleMock).toHaveBeenCalledWith("ArcAds: DFP id is missing from the arcads initialization script.", '\n', + "Documentation: https://github.com/wapopartners/arc-ads#getting-started"); + }); }); + + describe('registerAds', () => { + it('should should call prebid setConfig if has bidding configs and prebid lib is present', () => { + //ArcAds obj + const arcAds = new ArcAds({ + dfp: { + id: '123' + }, + bidding: { + amazon: { + enabled: true, + id: '123' + }, + prebid: { + enabled: true + } + } + }); + + //pbjs setConfig mock setup + const setConfigMock = jest.fn(); + global.pbjs = { + setConfig: () => setConfigMock, + }; + + //displayAd mock + const displayAdMock = jest.fn(); + const displayAdBindMock = jest.fn(); + + arcAds.displayAds = displayAdMock; + arcAds.displayAds.bind = displayAdBindMock; + + + //queueGoogletagCommand mock + jest.spyOn(gptService, 'queueGoogletagCommand'); + + jest.spyOn(prebidService, 'queuePrebidCommand'); + + const adParams = { + id: "testID", + slotname: "testSlotname", + dimensions: [[300, 50], [300, 250]] + } + + arcAds.registerAd(adParams); + + expect(gptService.queueGoogletagCommand).toHaveBeenCalledTimes(1); + expect(prebidService.queuePrebidCommand).toHaveBeenCalledTimes(0); + + }); + }); + + }); diff --git a/src/index.js b/src/index.js index 9d5d55b..ca1be03 100644 --- a/src/index.js +++ b/src/index.js @@ -22,8 +22,11 @@ export class ArcAds { window.isMobile = MobileDetection; if (this.dfpId === '') { - console.warn(`ArcAds: DFP id is missing from the arcads initialization script. - Documentation: https://github.com/wapopartners/arc-ads#getting-started`); + console.warn( + 'ArcAds: DFP id is missing from the arcads initialization script.', + '\n', + 'Documentation: https://github.com/wapopartners/arc-ads#getting-started' + ); } else { initializeGPT(); queueGoogletagCommand(dfpSettings.bind(this, handleSlotRendered)); From 8b3c57096ac4b62b6051efd982c6c8d3615a6bef Mon Sep 17 00:00:00 2001 From: Rachel Myers Date: Mon, 24 Aug 2020 10:53:54 -0400 Subject: [PATCH 06/22] ACI-71: prebid tests --- src/__tests__/prebid.test.js | 67 ++++++++++++++++++++++++++++++++++++ 1 file changed, 67 insertions(+) create mode 100644 src/__tests__/prebid.test.js diff --git a/src/__tests__/prebid.test.js b/src/__tests__/prebid.test.js new file mode 100644 index 0000000..191cc1c --- /dev/null +++ b/src/__tests__/prebid.test.js @@ -0,0 +1,67 @@ +import { + queuePrebidCommand, + fetchPrebidBids, + addUnit, + } from '../services/prebid'; + + const setpbjs = () => { + global.pbjs = { + que: [], + addAdUnits: () => jest.fn().mockName('addAdUnits'), + requestBids: () => jest.fn().mockName('requestBids'), + setConfig: () => jest.fn().mockName('setConfig'), + setTargetingForGPTAsync: jest.fn().mockName('setTargetingForGPTAsync'), + }; + }; + + describe('pbjs', () => { + const info = { + bids: [], + }; + + beforeEach(() => { + setpbjs(); + }); + + afterEach(() => { + window.blockArcAdsPrebid = false; + global.pbjs = {}; + jest.restoreAllMocks(); + }); + + it('fetchPrebidBidsArray', () => { + const spy = jest.spyOn(pbjs, 'requestBids'); + fetchPrebidBids({}, {}, {}, info, {}); + expect(spy).toHaveBeenCalled(); + }); + + it('return undefined while window blockArcAdsPrebid is set to true', () => { + window.blockArcAdsPrebid = true; + const result = fetchPrebidBids({}, {}, {}, info, {}); + expect(result).toEqual(undefined); + }); + + it('push fn into queuePrebidCommand', () => { + const mockFn = jest.fn(); + queuePrebidCommand(mockFn); + expect(pbjs.que.length).toEqual(1); + }); + + it('addUnit while slot is available', () => { + const spy = jest.spyOn(pbjs, 'addAdUnits'); + addUnit('', '', {}, {}, {}); + expect(spy).toHaveBeenCalled(); + }); + + it('called config while sizeConfig is passed ', () => { + const spy = jest.spyOn(pbjs, 'setConfig'); + addUnit('', '', {}, { config: { sample: 'test' } }, {}); + expect(spy).toHaveBeenCalled(); + }); + + it('called setConfig while sizeConfig is passed ', () => { + const spy = jest.spyOn(pbjs, 'setConfig'); + addUnit('', '', {}, { sizeConfig: { sample: 'test' } }, {}); + expect(spy).toHaveBeenCalled(); + }); + }); From 726dce7d8f2f5cc7437fe335ea33ca81664c3722 Mon Sep 17 00:00:00 2001 From: Rachel Myers Date: Tue, 25 Aug 2020 14:51:39 -0400 Subject: [PATCH 07/22] ACI-71: amazon tests --- src/__tests__/amazon.test.js | 38 ++++++++++++++++++++++++++++++++++++ 1 file changed, 38 insertions(+) create mode 100644 src/__tests__/amazon.test.js diff --git a/src/__tests__/amazon.test.js b/src/__tests__/amazon.test.js new file mode 100644 index 0000000..77e7be0 --- /dev/null +++ b/src/__tests__/amazon.test.js @@ -0,0 +1,38 @@ +import { + fetchAmazonBids, + queueAmazonCommand, + } from '../services/amazon'; + + describe('amazon service', () => { + beforeEach(() => { + global.amazonTest = { + cmd: () => jest.fn().mockName('cmd'), + queueAmazonCommand: () => jest.fn().mockName('queueAmazonCommand'), + }; + }); + + afterEach(() => { + window.apstag = false; + jest.restoreAllMocks(); + }); + + it('fetchAmazonBids', () => { + window.innerWidth = '1280'; + window.apstag = { + fetchBids: () => jest.fn().mockName('fetchBids'), + setDisplayBids: () => jest.fn().mockName('setDisplayBids'), + }; + const mockSpy = jest.spyOn(window.apstag, 'fetchBids'); + fetchAmazonBids('id', 'slotname', [ + [[[728, 90], 90]], [728, 90], [468, 60] + ], 728); + expect(mockSpy).toHaveBeenCalled(); + }); + + it('call passed in function in queueAmazonCommand', () => { + window.apstag = true; + const spy = jest.spyOn(amazonTest, 'cmd'); + queueAmazonCommand(global.amazonTest.cmd); + expect(spy).toHaveBeenCalled(); + }); + }); From c5aa373fa60823e88d91127940e212b9aeca9985 Mon Sep 17 00:00:00 2001 From: Rachel Myers Date: Tue, 8 Sep 2020 12:49:34 -0400 Subject: [PATCH 08/22] registeradsDimensions tests --- src/__tests__/arcads.test.js | 51 +--------- src/__tests__/registerAds.test.js | 155 ++++++++++++++++++++++++++++++ src/index.js | 9 ++ 3 files changed, 165 insertions(+), 50 deletions(-) create mode 100644 src/__tests__/registerAds.test.js diff --git a/src/__tests__/arcads.test.js b/src/__tests__/arcads.test.js index 8eceb85..180b0a3 100644 --- a/src/__tests__/arcads.test.js +++ b/src/__tests__/arcads.test.js @@ -47,56 +47,7 @@ describe('arcads', () => { }); }); - describe('registerAds', () => { - it('should should call prebid setConfig if has bidding configs and prebid lib is present', () => { - //ArcAds obj - const arcAds = new ArcAds({ - dfp: { - id: '123' - }, - bidding: { - amazon: { - enabled: true, - id: '123' - }, - prebid: { - enabled: true - } - } - }); - - //pbjs setConfig mock setup - const setConfigMock = jest.fn(); - global.pbjs = { - setConfig: () => setConfigMock, - }; - - //displayAd mock - const displayAdMock = jest.fn(); - const displayAdBindMock = jest.fn(); - - arcAds.displayAds = displayAdMock; - arcAds.displayAds.bind = displayAdBindMock; - - - //queueGoogletagCommand mock - jest.spyOn(gptService, 'queueGoogletagCommand'); - - jest.spyOn(prebidService, 'queuePrebidCommand'); - - const adParams = { - id: "testID", - slotname: "testSlotname", - dimensions: [[300, 50], [300, 250]] - } - - arcAds.registerAd(adParams); - - expect(gptService.queueGoogletagCommand).toHaveBeenCalledTimes(1); - expect(prebidService.queuePrebidCommand).toHaveBeenCalledTimes(0); - - }); - }); + }); diff --git a/src/__tests__/registerAds.test.js b/src/__tests__/registerAds.test.js new file mode 100644 index 0000000..c4a7f00 --- /dev/null +++ b/src/__tests__/registerAds.test.js @@ -0,0 +1,155 @@ +import { ArcAds } from '../index'; +import * as gptService from '../services/gpt.js'; +import * as prebidService from '../services/prebid.js'; + + +describe('registerAds dimensions branches', () => { + //pbjs setConfig mock setup + let setConfigMock = jest.fn(); + global.pbjs = { + setConfig: () => setConfigMock, + }; + + //queueGoogletagCommand mock + jest.spyOn(gptService, 'queueGoogletagCommand'); + + jest.spyOn(prebidService, 'queuePrebidCommand'); + + const arcAds = new ArcAds({ + dfp: { + id: '123' + }, + bidding: { + amazon: { + enabled: true, + id: '123' + }, + prebid: { + enabled: true + } + } + }); + + //displayAd mock + const displayAdMock = jest.fn(); + const displayAdBindMock = jest.fn(); + arcAds.displayAd = displayAdMock; + arcAds.displayAd.bind = displayAdBindMock; + // jest.spyOn(arcAds, 'displayAd'); + // jest.spyOn(arcAds, 'displayAd.bind'); + + beforeEach(() => { + jest.clearAllMocks(); + }); + + it.skip('should should call prebid setConfig if has bidding configs and prebid lib is present', () => { + //ArcAds obj + const adParams = { + id: "testID", + slotname: "testSlotname", + dimensions: [[300, 50], [300, 250]] + } + + arcAds.registerAd(adParams); + + expect(gptService.queueGoogletagCommand).toHaveBeenCalledTimes(1); + expect(prebidService.queuePrebidCommand).toHaveBeenCalledTimes(0); + + }); + + it('should add single level dimensions appropriately', () => { + const adParams = { + id: "testID", + slotname: "testSlotname", + dimensions: [300, 50], + } + + console.log('displayAd', arcAds.displayAd) + console.log('displayAdbind', arcAds.displayAd.bind) + + arcAds.registerAd(adParams); + + expect(arcAds.displayAd.bind).toHaveBeenCalledTimes(1); + + const expectedArg1 = {"adsList": [], + "collapseEmptyDivs": undefined, + "dfpId": "123", + "displayAd": displayAdMock, + "positions": [], + "wrapper": {"amazon": {"enabled": true, "id": "123"}, + "prebid": {"enabled": true}} + }; + const expectedArg2 = {"dimensions": [300, 50], "id": "testID", "slotname": "testSlotname"}; + + expect(displayAdBindMock).toHaveBeenCalledWith(expectedArg1, expectedArg2); + + }); + + it('should add two level dimensions appropriately', () => { + + const adParams = { + id: "testID", + slotname: "testSlotname", + dimensions: [[300, 50], [250, 100]], + } + + arcAds.registerAd(adParams); + + expect(displayAdBindMock).toHaveBeenCalledTimes(1); + + const expectedArg1 = {"adsList": [], + "collapseEmptyDivs": undefined, + "dfpId": "123", + "displayAd": displayAdMock, + "positions": [], + "wrapper": {"amazon": {"enabled": true, "id": "123"}, + "prebid": {"enabled": true}} + }; + const expectedArg2 = {"dimensions": [[300, 50], [250, 100]], "id": "testID", "slotname": "testSlotname"}; + expect(displayAdBindMock).toHaveBeenCalledWith(expectedArg1, expectedArg2); + + }); + + it('should add no dimensions appropriately', () => { + const adParams = { + id: "testID", + slotname: "testSlotname", + dimensions: undefined, + } + arcAds.registerAd(adParams); + + expect(displayAdBindMock).toHaveBeenCalledTimes(1); + + const expectedArg1 = {"adsList": [], + "collapseEmptyDivs": undefined, + "dfpId": "123", + "displayAd": displayAdMock, + "positions": [], + "wrapper": {"amazon": {"enabled": true, "id": "123"}, + "prebid": {"enabled": true}}}; + const expectedArg2 = {"dimensions": undefined, "id": "testID", "slotname": "testSlotname"}; + expect(displayAdBindMock).toHaveBeenCalledWith(expectedArg1, expectedArg2); + }); + + it('should add non 1 or 2 level dimensions appropriately', () => { + const adParams = { + id: "testID", + slotname: "testSlotname", + dimensions: [[[100,50]]], + } + arcAds.registerAd(adParams); + + expect(displayAdBindMock).toHaveBeenCalledTimes(1); + + const expectedArg1 = {"adsList": [], + "collapseEmptyDivs": undefined, + "dfpId": "123", + "displayAd": displayAdMock, + "positions": [], + "wrapper": {"amazon": {"enabled": true, "id": "123"}, + "prebid": {"enabled": true}}}; + const expectedArg2 = {"dimensions": [[[100, 50]]], "id": "testID", "slotname": "testSlotname"}; + expect(displayAdBindMock).toHaveBeenCalledWith(expectedArg1, expectedArg2); + }); + +}); \ No newline at end of file diff --git a/src/index.js b/src/index.js index ca1be03..188614d 100644 --- a/src/index.js +++ b/src/index.js @@ -43,6 +43,7 @@ export class ArcAds { const flatDimensions = []; let processDisplayAd = false; const dimensionsDepth = getArrayDepth(dimensions); + console.log('dimensions =',dimensions, ', depth = ', dimensionsDepth); if (dimensions && typeof dimensions !== 'undefined' && dimensionsDepth === 1) { flatDimensions.push(...dimensions); @@ -86,6 +87,7 @@ export class ArcAds { } processDisplayAd = this.displayAd.bind(this, params); + console.log('processDisplayAd ', this.displayAd.bind); if (processDisplayAd) { queueGoogletagCommand(processDisplayAd); } @@ -120,6 +122,9 @@ export class ArcAds { window.blockArcAdsLoad = false; window.blockArcAdsPrebid = false; + window.googletag.pubads().refresh(window.adsList); + window.adsList = []; + //prebid call pbjs.requestBids({ timeout: bidderTimeout, @@ -212,6 +217,10 @@ export class ArcAds { const safebreakpoints = (sizemap && sizemap.breakpoints) ? sizemap.breakpoints : []; + if (window.adsList && ad) { + adsList.push(ad); + } + if (dimensions && bidding && ((bidding.amazon && bidding.amazon.enabled) || (bidding.prebid && bidding.prebid.enabled))) { fetchBids({ ad, From 51ed66a46dfc2db24a22fb9cefc72ca5606d93e3 Mon Sep 17 00:00:00 2001 From: Rachel Myers Date: Tue, 8 Sep 2020 13:37:09 -0400 Subject: [PATCH 09/22] targeting branches tests --- src/__tests__/registerAds.test.js | 19 ++++++++++--------- src/index.js | 2 -- 2 files changed, 10 insertions(+), 11 deletions(-) diff --git a/src/__tests__/registerAds.test.js b/src/__tests__/registerAds.test.js index c4a7f00..7ea846d 100644 --- a/src/__tests__/registerAds.test.js +++ b/src/__tests__/registerAds.test.js @@ -1,6 +1,7 @@ import { ArcAds } from '../index'; import * as gptService from '../services/gpt.js'; import * as prebidService from '../services/prebid.js'; +import * as mobileDetection from '../util/mobile.js'; describe('registerAds dimensions branches', () => { @@ -136,20 +137,20 @@ describe('registerAds dimensions branches', () => { id: "testID", slotname: "testSlotname", dimensions: [[[100,50]]], + targeting:{}, + adType: true, + display: 'mobile', } + + const mobileAny = jest.fn().mockReturnValue(true); + global.isMobile = {any: mobileAny}; + arcAds.registerAd(adParams); expect(displayAdBindMock).toHaveBeenCalledTimes(1); - const expectedArg1 = {"adsList": [], - "collapseEmptyDivs": undefined, - "dfpId": "123", - "displayAd": displayAdMock, - "positions": [], - "wrapper": {"amazon": {"enabled": true, "id": "123"}, - "prebid": {"enabled": true}}}; - const expectedArg2 = {"dimensions": [[[100, 50]]], "id": "testID", "slotname": "testSlotname"}; - expect(displayAdBindMock).toHaveBeenCalledWith(expectedArg1, expectedArg2); + const expectedArg2 = {"adType": true, "dimensions": [[[100, 50]]], "display": "mobile", "id": "testID", "slotname": "testSlotname", "targeting": {"position": 1}}; + expect(displayAdBindMock.mock.calls[0][1]).toEqual( expectedArg2); }); }); \ No newline at end of file diff --git a/src/index.js b/src/index.js index 188614d..1b61883 100644 --- a/src/index.js +++ b/src/index.js @@ -43,7 +43,6 @@ export class ArcAds { const flatDimensions = []; let processDisplayAd = false; const dimensionsDepth = getArrayDepth(dimensions); - console.log('dimensions =',dimensions, ', depth = ', dimensionsDepth); if (dimensions && typeof dimensions !== 'undefined' && dimensionsDepth === 1) { flatDimensions.push(...dimensions); @@ -87,7 +86,6 @@ export class ArcAds { } processDisplayAd = this.displayAd.bind(this, params); - console.log('processDisplayAd ', this.displayAd.bind); if (processDisplayAd) { queueGoogletagCommand(processDisplayAd); } From b9880b2086af24674d8aa26d3af7100e4c5e070d Mon Sep 17 00:00:00 2001 From: Rachel Myers Date: Tue, 8 Sep 2020 15:56:37 -0400 Subject: [PATCH 10/22] RegisterAd 100 test coverage --- src/__tests__/registerAds.test.js | 209 ++++++++++++++++++++++++++++-- src/index.js | 1 + 2 files changed, 196 insertions(+), 14 deletions(-) diff --git a/src/__tests__/registerAds.test.js b/src/__tests__/registerAds.test.js index 7ea846d..50ce038 100644 --- a/src/__tests__/registerAds.test.js +++ b/src/__tests__/registerAds.test.js @@ -5,16 +5,28 @@ import * as mobileDetection from '../util/mobile.js'; describe('registerAds dimensions branches', () => { - //pbjs setConfig mock setup - let setConfigMock = jest.fn(); + global.pbjs = { - setConfig: () => setConfigMock, - }; + que: [], + addAdUnits: () => jest.fn().mockName('addAdUnits'), + requestBids: () => jest.fn().mockName('requestBids'), + setConfig: () => jest.fn().mockName('setConfig'), + setTargetingForGPTAsync: jest.fn().mockName('setTargetingForGPTAsync'), + }; + + const setConfigSpy = jest.spyOn(pbjs, 'setConfig'); //queueGoogletagCommand mock jest.spyOn(gptService, 'queueGoogletagCommand'); + // jest.spyOn(prebidService, 'queuePrebidCommand'); + const queuePrebidCommandMock = jest.fn(); + const queuePrebidCommandBindMock = jest.fn(); + prebidService.queuePrebidCommand = queuePrebidCommandBindMock; + prebidService.queuePrebidCommand.bind = queuePrebidCommandBindMock; - jest.spyOn(prebidService, 'queuePrebidCommand'); + // prebidService.queuePrebidCommand.bind = queuePrebidCommandBindMock; + const addUnitMock = jest.fn(); + prebidService.addUnit = addUnitMock; const arcAds = new ArcAds({ dfp: { @@ -33,11 +45,9 @@ describe('registerAds dimensions branches', () => { //displayAd mock const displayAdMock = jest.fn(); - const displayAdBindMock = jest.fn(); + const displayAdBindMock = jest.fn().mockReturnValue(jest.fn()); arcAds.displayAd = displayAdMock; arcAds.displayAd.bind = displayAdBindMock; - // jest.spyOn(arcAds, 'displayAd'); - // jest.spyOn(arcAds, 'displayAd.bind'); beforeEach(() => { jest.clearAllMocks(); @@ -64,10 +74,6 @@ describe('registerAds dimensions branches', () => { slotname: "testSlotname", dimensions: [300, 50], } - - console.log('displayAd', arcAds.displayAd) - console.log('displayAdbind', arcAds.displayAd.bind) - arcAds.registerAd(adParams); expect(arcAds.displayAd.bind).toHaveBeenCalledTimes(1); @@ -140,17 +146,192 @@ describe('registerAds dimensions branches', () => { targeting:{}, adType: true, display: 'mobile', + bidding:{prebid:{bids:['bid1']}} } const mobileAny = jest.fn().mockReturnValue(true); global.isMobile = {any: mobileAny}; arcAds.registerAd(adParams); + + expect(setConfigSpy).toHaveBeenCalledTimes(1); + expect(setConfigSpy.mock.calls[0][0]).toEqual( + expect.objectContaining({ + "userSync": { + "filterSettings": { + "iframe": { + "bidders": ["openx"], "filter": "include"} + }, + "iframeEnabled": true + } + }) + ); expect(displayAdBindMock).toHaveBeenCalledTimes(1); - - const expectedArg2 = {"adType": true, "dimensions": [[[100, 50]]], "display": "mobile", "id": "testID", "slotname": "testSlotname", "targeting": {"position": 1}}; + const expectedArg2 = { + "adType": true, + "bidding": {"prebid": {"bids": ["bid1"]}}, + "dimensions": [[[100, 50]]], + "display": "mobile", + "id": "testID", + "slotname": "testSlotname", + "targeting": {"position": 1} + }; expect(displayAdBindMock.mock.calls[0][1]).toEqual( expectedArg2); }); + it('wrapper has useSlotForAdUnit for caclulating prebid code', () => { + arcAds.wrapper = { + amazon: { + enabled: true, + id: '123' + }, + prebid: { + enabled: true, + useSlotForAdUnit: true, + } + }; + const adParams = { + id: "testID", + slotname: "testSlotname", + dimensions: [[[100,50]]], + targeting:{}, + adType: true, + display: 'mobile', + bidding:{prebid:{bids:['bid1']}} + } + + const mobileAny = jest.fn().mockReturnValue(true); + global.isMobile = {any: mobileAny}; + + arcAds.registerAd(adParams); + + expect(addUnitMock).toHaveBeenCalledTimes(1); + expect(addUnitMock.mock.calls[0][0]).toEqual("/123/undefined"); + + expect(queuePrebidCommandBindMock).toHaveBeenCalledTimes(1); + }); + + it('handles no display case', () => { + arcAds.wrapper = { + amazon: { + enabled: true, + id: '123' + }, + prebid: { + enabled: true, + useSlotForAdUnit: true, + } + }; + const adParams = { + id: "testID", + slotname: "testSlotname", + dimensions: [[[100,50]]], + targeting:{}, + adType: true, + display: 'other', + bidding:{prebid:{bids:['bid1']}} + } + + const mobileAny = jest.fn().mockReturnValue(true); + global.isMobile = {any: mobileAny}; + + arcAds.registerAd(adParams); + + expect(queuePrebidCommandBindMock).toHaveBeenCalledTimes(0); + expect(displayAdBindMock).toHaveBeenCalledTimes(0); + }); + + it('handles iframeBidders case', () => { + arcAds.wrapper = { + amazon: { + enabled: true, + id: '123' + }, + prebid: { + enabled: true, + useSlotForAdUnit: true, + } + }; + const adParams = { + id: "testID", + slotname: "testSlotname", + dimensions: [[[100,50]]], + targeting:{}, + adType: true, + display: 'all', + bidding:{prebid:{bids:['bid1']}}, + iframeBidders:[], + } + + const mobileAny = jest.fn().mockReturnValue(true); + global.isMobile = {any: mobileAny}; + + arcAds.registerAd(adParams); + + expect(setConfigSpy).toHaveBeenCalledTimes(0); + }); + + it('if no processDisplayAd do not call queueGoogletagCommand' , () => { + arcAds.wrapper = { + amazon: { + enabled: true, + id: '123' + }, + prebid: { + enabled: true, + useSlotForAdUnit: true, + } + }; + const adParams = { + id: "testID", + slotname: "testSlotname", + dimensions: [[[100,50]]], + targeting:{}, + adType: true, + display: 'all', + bidding:{prebid:{bids:['bid1']}}, + iframeBidders:[], + } + arcAds.displayAd.bind = jest.fn().mockReturnValue(null); + + arcAds.registerAd(adParams); + + expect(gptService.queueGoogletagCommand).toHaveBeenCalledTimes(0); + }); + + it('if try error write console error' , () => { + arcAds.wrapper = { + amazon: { + enabled: true, + id: '123' + }, + prebid: { + enabled: true, + useSlotForAdUnit: true, + } + }; + const adParams = { + id: "testID", + slotname: "testSlotname", + dimensions: [[[100,50]]], + targeting:{}, + adType: true, + display: 'all', + bidding:{prebid:{bids:['bid1']}}, + iframeBidders:[], + } + arcAds.displayAd.bind.mockImplementation(() => { + throw new Error('test error msg'); + }); + + const errorMock = jest.fn(); + console.error = errorMock; + + arcAds.registerAd(adParams); + + expect(errorMock).toHaveBeenCalledTimes(1); + expect(errorMock.mock.calls[0][0]).toEqual('ads error'); + }); + }); \ No newline at end of file diff --git a/src/index.js b/src/index.js index 1b61883..eb45e94 100644 --- a/src/index.js +++ b/src/index.js @@ -91,6 +91,7 @@ export class ArcAds { } } } catch (err) { + console.log("err", err) console.error('ads error', err); } } From 6ad8a0e6d3ade59a6c2bbcc89701919f9a0038bf Mon Sep 17 00:00:00 2001 From: Rachel Myers Date: Tue, 8 Sep 2020 16:53:32 -0400 Subject: [PATCH 11/22] Gate Tests --- src/__tests__/arcads.test.js | 76 +++++++++++++++++++++++++++++++++++- src/index.js | 14 +++++-- 2 files changed, 85 insertions(+), 5 deletions(-) diff --git a/src/__tests__/arcads.test.js b/src/__tests__/arcads.test.js index 180b0a3..50e09d4 100644 --- a/src/__tests__/arcads.test.js +++ b/src/__tests__/arcads.test.js @@ -3,6 +3,12 @@ import * as gptService from '../services/gpt.js'; import * as prebidService from '../services/prebid.js'; describe('arcads', () => { + + + beforeEach(() => { + jest.clearAllMocks(); + }); + const arcAds = new ArcAds({ dfp: { id: '123' @@ -47,7 +53,75 @@ describe('arcads', () => { }); }); - + describe('registerAdCollection', () => { + it('calls registerAd for each advert in the collection param', () => { + + const registerAdMock = jest.fn(); + arcAds.registerAd = registerAdMock; + + const adCollection = ['ad1', 'ad2']; + arcAds.registerAdCollection(adCollection); + + expect(registerAdMock).toHaveBeenCalledTimes(2); + }); + }); + + describe('registerAdCollectionSingleCall', () => { + it('calls registerAd, requestBids, refresh', () => { + + const registerAdMock = jest.fn(); + arcAds.registerAd = registerAdMock; + + const refreshMock = jest.fn(); + window.googletag.pubads = jest.fn().mockReturnValue({refresh: refreshMock}); + const requestBidsMock = jest.fn(); + global.pbjs = {requestBids: requestBidsMock}; + + const adCollection = ['ad1', 'ad2']; + + arcAds.registerAdCollectionSingleCall(adCollection); + + + expect(registerAdMock).toHaveBeenCalledTimes(2); + expect(refreshMock).toHaveBeenCalledTimes(1); + expect(requestBidsMock).toHaveBeenCalledTimes(1); + }); + }); + + describe('setAdsBlockGate', () => { + + it('sets blockArcAdsLoad to true if has window', () => { + ArcAds.setAdsBlockGate(); + expect(window.blockArcAdsLoad).toEqual(true); + }); + + it('does nothing if no window', () => { + const saveGetWindow = ArcAds.getWindow; + const getWindowMock = jest.fn().mockReturnValue(undefined); + ArcAds.getWindow = getWindowMock; + + ArcAds.setAdsBlockGate(); + expect(getWindowMock()).toEqual(undefined); + ArcAds.getWindow = saveGetWindow; + }); + }); + + describe('releaseAdsBlockGate', () => { + + it('sets blockArcAdsLoad to false if has window', () => { + ArcAds.releaseAdsBlockGate(); + expect(window.blockArcAdsLoad).toEqual(false); + }); + + it('does nothing if no window', () => { + const getWindowMock = jest.fn().mockReturnValue(undefined); + ArcAds.getWindow = getWindowMock; + + ArcAds.releaseAdsBlockGate(); + expect(getWindowMock()).toEqual(undefined); + getWindowMock.mockRestore(); + }); + }); }); diff --git a/src/index.js b/src/index.js index eb45e94..183bf1d 100644 --- a/src/index.js +++ b/src/index.js @@ -144,8 +144,9 @@ export class ArcAds { * allowing ads to be saved up for a single ad call to be sent out later. **/ static setAdsBlockGate() { - if (typeof window !== 'undefined') { - window.blockArcAdsLoad = true; + const win = ArcAds.getWindow(); + if (typeof win !== 'undefined') { + win.blockArcAdsLoad = true; } } @@ -154,8 +155,9 @@ export class ArcAds { * allowing ads to be saved up for a single ad call to be sent out later. **/ static releaseAdsBlockGate() { - if (typeof window !== 'undefined') { - window.blockArcAdsLoad = false; + const win = ArcAds.getWindow(); + if (typeof win !== 'undefined') { + win.blockArcAdsLoad = false; } } @@ -292,4 +294,8 @@ export class ArcAds { setPageLeveTargeting(key, value) { //TODO check for pubads googletag.pubads().setTargeting(key, value); } + + static getWindow() { + return window; + } } From d5a002011211e26338e468b52a3b4e233c2f1d66 Mon Sep 17 00:00:00 2001 From: Rachel Myers Date: Tue, 8 Sep 2020 17:49:52 -0400 Subject: [PATCH 12/22] displayAd tests progress --- src/__tests__/displayAd.test.js | 134 ++++++++++++++++++++++++++++++ src/__tests__/registerAds.test.js | 2 +- 2 files changed, 135 insertions(+), 1 deletion(-) create mode 100644 src/__tests__/displayAd.test.js diff --git a/src/__tests__/displayAd.test.js b/src/__tests__/displayAd.test.js new file mode 100644 index 0000000..b79c858 --- /dev/null +++ b/src/__tests__/displayAd.test.js @@ -0,0 +1,134 @@ +import { ArcAds } from '../index'; +import * as gptService from '../services/gpt.js'; +import * as prebidService from '../services/prebid.js'; +import * as mobileDetection from '../util/mobile.js'; +import * as sizemappingService from '../services/sizemapping.js' + +describe('displayAd ', () => { + + beforeEach(() => { + jest.clearAllMocks(); + }); + + const defineOutOfPageSlotMock = jest.fn(); + const defineSlotMock = jest.fn(); + + window.googletag= { + defineOutOfPageSlot: defineOutOfPageSlotMock, + defineSlot: defineSlotMock, + pubads: jest.fn(), + }; + const refreshSlotSpy = jest.spyOn(gptService, 'refreshSlot'); + const setResizeListenerSpy = jest.spyOn(sizemappingService, 'setResizeListener'); + + + const arcAds = new ArcAds({ + dfp: { + id: '123' + }, + bidding: { + amazon: { + enabled: true, + id: '123' + }, + prebid: { + enabled: true + } + } + }); + + it('if does not have dimensions should call defineOutOfPageSlot', () => { + const adParams = { + id: "testID", + slotName: 'testSlotname', + dimensions: null, + targeting: null, + sizemap: {breakpoints:[0, 50]}, + bidding: false, + prerender: null, + }; + window.blockArcAdsPrebid = false; + const result = arcAds.displayAd(adParams); + + expect(result).toEqual(undefined); + expect(defineOutOfPageSlotMock).toHaveBeenCalledTimes(1); + expect(defineOutOfPageSlotMock).toHaveBeenCalledWith("/123/testSlotname", "testID"); + expect(defineSlotMock).toHaveBeenCalledTimes(0); + + expect(refreshSlotSpy).toHaveBeenCalledTimes(1); + const expectedRefreshSLotParams = {"ad": undefined, "info": {"adDimensions": null, "adId": "testID", "adSlot": "/123/testSlotname", "adUnit": undefined}, "prerender": null}; + expect(refreshSlotSpy).toHaveBeenCalledWith(expectedRefreshSLotParams); + + }); + + it('if no ad object return false', () => { + const adParams = { + id: "testID", + slotName: 'testSlotname', + dimensions: [100,40], + targeting: null, + sizemap: {breakpoints:[0, 50]}, + bidding: false, + prerender: null, + }; + + defineOutOfPageSlotMock.mockReturnValue(null); + window.blockArcAdsPrebid = false; + const result = arcAds.displayAd(adParams); + + expect(result).toEqual(false); + expect(defineOutOfPageSlotMock).toHaveBeenCalledTimes(0); + expect(defineSlotMock).toHaveBeenCalledTimes(1); + expect(defineSlotMock).toHaveBeenCalledWith( "/123/testSlotname", [100, 40], "testID"); + + expect(refreshSlotSpy).toHaveBeenCalledTimes(0); + }); + + it('if sizemap.refresh call resize listener', () => { + const adParams = { + id: "testID", + slotName: 'testSlotname', + dimensions: [100,40], + targeting: null, + sizemap: {breakpoints:[0, 50], refresh: true}, + bidding: false, + prerender: null, + }; + + window.blockArcAdsPrebid = true; + + const defineSizeMappingMock = jest.fn(); + defineSlotMock.mockReturnValue({ + defineSizeMapping: defineSizeMappingMock, + addService: jest.fn() + }); + const result = arcAds.displayAd(adParams); + + expect(result).toEqual(undefined); + expect(defineOutOfPageSlotMock).toHaveBeenCalledTimes(0); + + expect(defineSlotMock).toHaveBeenCalledTimes(1); + expect(defineSlotMock).toHaveBeenCalledWith( "/123/testSlotname", [100, 40], "testID"); + + expect(refreshSlotSpy).toHaveBeenCalledTimes(0); + + expect(defineSizeMappingMock).toHaveBeenCalledTimes(1); + expect(defineSizeMappingMock).toHaveBeenCalledWith([[0, 100], [50, 40]]); + + expect(setResizeListenerSpy).toHaveBeenCalledTimes(1); + + expect(setResizeListenerSpy.mock.calls[0][0]).toEqual( + expect.objectContaining({ + "bidding": false, + "breakpoints": [undefined], + "correlators": [false], + "id": "testID", + "mapping": [[0, 100], [50, 40]], + "prerender": null, + "slotName": "/123/testSlotname", + "wrapper": {"amazon": {"enabled": true, "id": "123"}, "prebid": {"enabled": true}}} + ) + ); + }); + +}); \ No newline at end of file diff --git a/src/__tests__/registerAds.test.js b/src/__tests__/registerAds.test.js index 50ce038..e0e949a 100644 --- a/src/__tests__/registerAds.test.js +++ b/src/__tests__/registerAds.test.js @@ -53,7 +53,7 @@ describe('registerAds dimensions branches', () => { jest.clearAllMocks(); }); - it.skip('should should call prebid setConfig if has bidding configs and prebid lib is present', () => { + it('should should call prebid setConfig if has bidding configs and prebid lib is present', () => { //ArcAds obj const adParams = { id: "testID", From 647e9accf65799dcc6e7a7a0aa4d3db7c262ac38 Mon Sep 17 00:00:00 2001 From: Rachel Myers Date: Wed, 9 Sep 2020 12:53:39 -0400 Subject: [PATCH 13/22] displayAd tests --- src/__tests__/displayAd.test.js | 99 +++++++++++++++++++++++++++++++++ src/services/sizemapping.js | 2 +- 2 files changed, 100 insertions(+), 1 deletion(-) diff --git a/src/__tests__/displayAd.test.js b/src/__tests__/displayAd.test.js index b79c858..fdb1b9a 100644 --- a/src/__tests__/displayAd.test.js +++ b/src/__tests__/displayAd.test.js @@ -3,6 +3,7 @@ import * as gptService from '../services/gpt.js'; import * as prebidService from '../services/prebid.js'; import * as mobileDetection from '../util/mobile.js'; import * as sizemappingService from '../services/sizemapping.js' +import * as headerBidding from '../services/headerbidding.js'; describe('displayAd ', () => { @@ -20,6 +21,7 @@ describe('displayAd ', () => { }; const refreshSlotSpy = jest.spyOn(gptService, 'refreshSlot'); const setResizeListenerSpy = jest.spyOn(sizemappingService, 'setResizeListener'); + const fetchBidsSpy = jest.spyOn(headerBidding, 'fetchBids'); const arcAds = new ArcAds({ @@ -131,4 +133,101 @@ describe('displayAd ', () => { ); }); + it('if no sizemap.refresh, do NOT call resize listener', () => { + const adParams = { + id: "testID", + slotName: 'testSlotname', + dimensions: [100,40], + targeting: null, + sizemap: {breakpoints:[0, 50], refresh: false}, + bidding: false, + prerender: null, + }; + + window.blockArcAdsPrebid = true; + + const defineSizeMappingMock = jest.fn(); + defineSlotMock.mockReturnValue({ + defineSizeMapping: defineSizeMappingMock, + addService: jest.fn() + }); + arcAds.displayAd(adParams); + expect(setResizeListenerSpy).toHaveBeenCalledTimes(0); + }); + + it('if has adsList and ad push ad to adsList', () => { + const adParams = { + id: "testID", + slotName: 'testSlotname', + dimensions: [100,40], + targeting: null, + sizemap: {breakpoints:[0, 50], refresh: false}, + bidding: false, + prerender: null, + }; + + window.blockArcAdsPrebid = true; + window.adsList = []; + + const defineSizeMappingMock = jest.fn(); + defineSlotMock.mockReturnValue({ + defineSizeMapping: defineSizeMappingMock, + addService: jest.fn() + }); + arcAds.displayAd(adParams); + expect(window.adsList.length).toEqual(1); + }); + + it('if has bidding.prebid.enabled call fetchBids', () => { + const adParams = { + id: "testID", + slotName: 'testSlotname', + dimensions: [100,40], + targeting: null, + sizemap: {breakpoints:[0, 50], refresh: false}, + bidding: {prebid: {enabled: true}}, + prerender: null, + }; + + window.blockArcAdsPrebid = false; + arcAds.displayAd(adParams); + + expect(refreshSlotSpy).toHaveBeenCalledTimes(0); + expect(fetchBidsSpy).toHaveBeenCalledTimes(1); + }); + + it('handles non-null dimnsions length 0 case', () => { + const adParams = { + id: "testID", + slotName: 'testSlotname', + dimensions: [], + targeting: null, + sizemap: {breakpoints:[0, 50], refresh: false}, + bidding: {prebid: {enabled: true}}, + prerender: null, + }; + + const defineSizeMappingMock = jest.fn(); + defineSlotMock.mockReturnValue({ + defineSizeMapping: defineSizeMappingMock, + addService: jest.fn() + }); + + const result = arcAds.displayAd(adParams); + + expect(result).toEqual(undefined); + expect(defineOutOfPageSlotMock).toHaveBeenCalledTimes(0); + + expect(defineSlotMock).toHaveBeenCalledTimes(1); + expect(defineSlotMock).toHaveBeenCalledWith( "/123/testSlotname", null, "testID"); + + expect(refreshSlotSpy).toHaveBeenCalledTimes(0); + + expect(defineSizeMappingMock).toHaveBeenCalledTimes(1); + expect(defineSizeMappingMock).toHaveBeenCalledWith([]); + + expect(setResizeListenerSpy).toHaveBeenCalledTimes(0); + + }); + }); \ No newline at end of file diff --git a/src/services/sizemapping.js b/src/services/sizemapping.js index d6e31f0..88ebcb9 100644 --- a/src/services/sizemapping.js +++ b/src/services/sizemapping.js @@ -19,7 +19,7 @@ export function prepareSizeMaps(dimensions, sizemap) { const correlators = []; const parsedSizemap = !sizemap.length ? null : sizemap; - parsedSizemap.forEach((value, index) => { + parsedSizemap && dimensions && parsedSizemap.forEach((value, index) => { mapping.push([value, dimensions[index]]); // Filters duplicates from the mapping From 8bfdb62dd32d7b464200a52b63d37222b32fbd16 Mon Sep 17 00:00:00 2001 From: Rachel Myers Date: Wed, 9 Sep 2020 14:40:08 -0400 Subject: [PATCH 14/22] index.js tests --- src/__tests__/arcads.test.js | 54 +++++++++++++++++++++++++++++++++++- src/index.js | 6 ++-- src/services/sizemapping.js | 20 +++++++------ 3 files changed, 66 insertions(+), 14 deletions(-) diff --git a/src/__tests__/arcads.test.js b/src/__tests__/arcads.test.js index 50e09d4..39fe790 100644 --- a/src/__tests__/arcads.test.js +++ b/src/__tests__/arcads.test.js @@ -4,7 +4,6 @@ import * as prebidService from '../services/prebid.js'; describe('arcads', () => { - beforeEach(() => { jest.clearAllMocks(); }); @@ -24,6 +23,8 @@ describe('arcads', () => { } }); + let registerAdCollectionSingleCallMock; + describe('constructor', () => { it('should initialize arc ads', () => { expect(arcAds).not.toBeUndefined(); @@ -124,4 +125,55 @@ describe('arcads', () => { }); }); + describe('sendSingleCallAds', () => { + + beforeAll(() => { + registerAdCollectionSingleCallMock = jest.fn(); + arcAds.registerAdCollectionSingleCall = registerAdCollectionSingleCallMock; + }); + + it('if has nothing in adsList return', () => { + arcAds.adsList = []; + const result = arcAds.sendSingleCallAds(); + expect(result).toEqual(false); + }); + + it('if has adsList elems and pubads do SRA call', () => { + arcAds.adsList = ['ad1', 'ad2']; + window.googletag.pubadsReady = true; + window.googletag.pubads =jest.fn().mockReturnValue({ + disableInitialLoad: jest.fn(), + enableSingleRequest: jest.fn(), + enableAsyncRendering: jest.fn(), + }); + + arcAds.sendSingleCallAds(); + expect(registerAdCollectionSingleCallMock).toHaveBeenCalledTimes(1); + }); + }); + + describe('reserveAd', () => { + it('sets block and adds ad to adsList', () => { + const gateSetSpy = jest.spyOn(ArcAds, 'setAdsBlockGate'); + arcAds.adsList = []; + arcAds.reserveAd({example: true}); + expect(gateSetSpy).toHaveBeenCalledTimes(1); + expect(arcAds.adsList.length).toEqual(1); + }); + }); + + describe('setPageLeveTargeting', () => { + it('sets block and adds ad to adsList', () => { + const setTargetingMock = jest.fn(); + + window.googletag.pubads =jest.fn().mockReturnValue({ + setTargeting: setTargetingMock, + }); + + arcAds.setPageLeveTargeting('testKey', 'testValue'); + expect(setTargetingMock).toHaveBeenCalledTimes(1); + expect(setTargetingMock).toHaveBeenCalledWith('testKey', 'testValue'); + }); + }); + }); diff --git a/src/index.js b/src/index.js index 183bf1d..27fef04 100644 --- a/src/index.js +++ b/src/index.js @@ -91,7 +91,6 @@ export class ArcAds { } } } catch (err) { - console.log("err", err) console.error('ads error', err); } } @@ -254,9 +253,8 @@ export class ArcAds { // if no ads have been accumulated to send out together // do nothing, return if (this.adsList && this.adsList.length < 1) { - return; + return false; } - //ensure library is present and able to send out SRA ads if (window && window.googletag && googletag.pubadsReady) { // eslint-disable-line window.googletag.pubads().disableInitialLoad(); @@ -295,7 +293,7 @@ export class ArcAds { googletag.pubads().setTargeting(key, value); } - static getWindow() { + static getWindow() { return window; } } diff --git a/src/services/sizemapping.js b/src/services/sizemapping.js index 88ebcb9..5cfda22 100644 --- a/src/services/sizemapping.js +++ b/src/services/sizemapping.js @@ -19,15 +19,17 @@ export function prepareSizeMaps(dimensions, sizemap) { const correlators = []; const parsedSizemap = !sizemap.length ? null : sizemap; - parsedSizemap && dimensions && parsedSizemap.forEach((value, index) => { - mapping.push([value, dimensions[index]]); - - // Filters duplicates from the mapping - if (breakpoints.indexOf(value[0]) === -1) { - breakpoints.push(value[0]); - correlators.push(false); - } - }); + if (parsedSizemap && dimensions) { + parsedSizemap.forEach((value, index) => { + mapping.push([value, dimensions[index]]); + + // Filters duplicates from the mapping + if (breakpoints.indexOf(value[0]) === -1) { + breakpoints.push(value[0]); + correlators.push(false); + } + }); + } breakpoints.sort((a, b) => { return a - b; }); From 2fb9d0605c9dd101388ffcbcc834414b97bea636 Mon Sep 17 00:00:00 2001 From: Rachel Myers Date: Wed, 9 Sep 2020 15:22:01 -0400 Subject: [PATCH 15/22] query.js tests --- src/__tests__/query.test.js | 34 ++++++++++++++++++++++++++++++++++ 1 file changed, 34 insertions(+) create mode 100644 src/__tests__/query.test.js diff --git a/src/__tests__/query.test.js b/src/__tests__/query.test.js new file mode 100644 index 0000000..b81c28a --- /dev/null +++ b/src/__tests__/query.test.js @@ -0,0 +1,34 @@ +import { expandQueryString } from '../util/query.js'; + + +describe('expandQueryString', () => { + + const saveLocation = global.window.location; + + afterAll(() => { + delete global.window.location; + global.window.location = saveLocation; + }); + + it('gets url value for param name passed', () => { + delete global.window.location; + global.window = Object.create(window); + global.window.location = { + href:'http://www.test.com?adslot=hello', + }; + + const result = expandQueryString('adslot'); + expect(result).toEqual('hello'); + }); + + it('if no result return empty string', () => { + delete global.window.location; + global.window = Object.create(window); + global.window.location = { + href:'http://www.test.com?adslot=', + }; + + const result = expandQueryString('adslot'); + expect(result).toEqual(''); + }); + }); \ No newline at end of file From feef3d4201b9741c99cdc6b6b63dccc7ae6e7d53 Mon Sep 17 00:00:00 2001 From: Rachel Myers Date: Wed, 9 Sep 2020 15:52:24 -0400 Subject: [PATCH 16/22] resources.js tests --- src/__tests__/resources.test.js | 49 +++++++++++++++++++++++++++++++++ 1 file changed, 49 insertions(+) create mode 100644 src/__tests__/resources.test.js diff --git a/src/__tests__/resources.test.js b/src/__tests__/resources.test.js new file mode 100644 index 0000000..d3809d8 --- /dev/null +++ b/src/__tests__/resources.test.js @@ -0,0 +1,49 @@ +import {appendResource} from '../util/resources.js'; + +describe('appendResource', () => { + const cbMock = jest.fn(); + const appendChildMock = jest.fn(); + const saveDocument = global.document; + + afterAll(() => { + delete global.document; + global.document = saveDocument; + }); + + beforeAll(() => { + delete global.document; + global.document = { + documentElement:{ + appendChild: appendChildMock, + }, + createElement: jest.fn().mockReturnValue({}), + } + }); + + afterEach(() => { + jest.clearAllMocks(); + }); + + it('if tag is script create tag and append child tag', () => { + appendResource('script', 'www.test.com', true, true, cbMock); + expect(appendChildMock).toHaveBeenCalledTimes(1); + const expectedParams = {"async": true, "defer": true, "src": "www.test.com"}; + expect(appendChildMock).toHaveBeenCalledWith(expectedParams); + expect(cbMock).toHaveBeenCalledTimes(1); + }); + + it('if tag is not script do nothing', () => { + appendResource('div', 'www.test.com', true, true, cbMock); + expect(appendChildMock).toHaveBeenCalledTimes(0); + expect(cbMock).toHaveBeenCalledTimes(0); + }); + + it('if no async or defer assume false for those values', () => { + appendResource('script', 'www.test.com', null,null , cbMock); + expect(appendChildMock).toHaveBeenCalledTimes(1); + const expectedParams = {"async": false, "defer": false, "src": "www.test.com"}; + expect(appendChildMock).toHaveBeenCalledWith(expectedParams); + expect(cbMock).toHaveBeenCalledTimes(1); + }); + + }); \ No newline at end of file From 3b0c39b8b34e73c80e18ad107816d470b24ed116 Mon Sep 17 00:00:00 2001 From: Rachel Myers Date: Thu, 10 Sep 2020 13:02:52 -0400 Subject: [PATCH 17/22] sizemapping tests --- src/__tests__/gpt.test.js | 27 ++++++++++ src/__tests__/sizemapping.test.js | 87 +++++++++++++++++++++++++++++++ 2 files changed, 114 insertions(+) create mode 100644 src/__tests__/sizemapping.test.js diff --git a/src/__tests__/gpt.test.js b/src/__tests__/gpt.test.js index 4483df5..b79ed34 100644 --- a/src/__tests__/gpt.test.js +++ b/src/__tests__/gpt.test.js @@ -104,4 +104,31 @@ describe('arcads', () => { expect(methods.setResizeListener.mock.calls.length).toBe(1); }); }); + + describe('refreshSlot', () => { + const defineOutOfPageSlotMock = jest.fn(); + const defineSlotMock = jest.fn(); + const refreshMock = jest.fn(); + const prerenderFnc = () =>{ + return Promise.resolve([1,2,3]); + } + + beforeEach(() => { + jest.clearAllMocks(); + }); + + beforeAll(() => { + window.googletag= { + pubads: jest.fn().mockReturnValue({ + refresh: refreshMock, + }), + }; + }); + + it('if has prerender that resolves call refresh', () => { + window.googletag.pubadsReady = true; + gpt.refreshSlot({ad:{name:"ad"}, correlator:false, prerender:prerenderFnc, info:{}}); + //expect(window.googletag.pubads().refresh).toHaveBeenCalledTimes(1); + }); + }); }); diff --git a/src/__tests__/sizemapping.test.js b/src/__tests__/sizemapping.test.js new file mode 100644 index 0000000..b7a10b7 --- /dev/null +++ b/src/__tests__/sizemapping.test.js @@ -0,0 +1,87 @@ +import { + prepareSizeMaps, + parseSizeMappings, + runResizeEvents, + setResizeListener, + sizemapListeners, + } from '../services/sizemapping'; + import { fetchBids } from '../services/headerbidding'; + const mockSizeMap = [[468, 60], [728, 90]]; + describe('prepareSizeMaps', () => { + it('return sizeMap object', () => { + const mockDimensions = [[1000, 300], [970, 90], [728, 90], [300, 250]]; + const result = prepareSizeMaps(mockDimensions, mockSizeMap); + expect(result.mapping.length).toEqual(2); + expect(result.breakpoints.length).toEqual(2); + expect(result.correlators.length).toEqual(2); + }); + }); + describe('parseSizeMappings', () => { + Object.defineProperty(window, 'innerWidth', { + writable: true, + value: 1080, + }); + Object.defineProperty(window, 'innerHeight', { + writable: true, + value: 680, + }); + it('return sizeMap object', () => { + const result = parseSizeMappings(mockSizeMap); + expect(result).toEqual([]); + }); + }); + describe('setResizeListener', () => { + const mockParams = { + id: '123', + correlators: [1, 2] + }; + beforeEach(() => { + global.window = { + addEventListener: () => jest.fn().mockName('addEventListener'), + }; + }); + it('return sizeMap object', () => { + const mockSpy = jest.spyOn(global.window, 'addEventListener'); + setResizeListener(mockParams); + expect(mockSpy).toHaveBeenCalled(); + }); + }); + describe('runResizeEvents', () => { + beforeEach(() => { + global.runResizeEvents = { + fetchBids: () => jest.fn().mockName('fetchBids'), + refreshSlot: () => jest.fn().mockName('fetchBids'), + parseSizeMappings: () => jest.fn().mockName('parseSizeMappings'), + }; + Object.assign(sizemapListeners, { abc: { correlators: [1, 2, 3] } }); + }); + afterEach(() => { + global = {}; + }); + const mockParams = { + ad: {}, + breakpoints: [768, 1080], + id: 'abc', + bidding: { + prebid: { + enabled: true, + }, + amazon: { + enabled: false, + } + }, + mapping: mockSizeMap, + slotName: 'mockSlotName', + wrapper: {}, + prerender: false, + }; + it('set ad correlators to true', () => { + Object.defineProperty(window, 'innerWidth', { + writable: true, + value: 1024, + }); + const result = runResizeEvents(mockParams); + const resultFn = result(); + expect(sizemapListeners.abc.correlators[0]).toEqual(true); + }); + }); \ No newline at end of file From 0eb6178b10d2ee71df2ee78c7b40cfab13d664a1 Mon Sep 17 00:00:00 2001 From: Rachel Myers Date: Mon, 21 Sep 2020 12:59:47 -0400 Subject: [PATCH 18/22] Tests at 85% coverage --- src/__tests__/amazon.test.js | 68 ++++++++------- src/__tests__/arcads.test.js | 2 +- src/__tests__/displayAd.test.js | 2 - src/__tests__/gpt.test.js | 112 ++++++++++++++++++++----- src/__tests__/headerbidding.test.js | 118 ++++++++++++++++++++++++++ src/__tests__/prebid.test.js | 125 ++++++++++++++++------------ src/__tests__/registerAds.test.js | 3 +- src/__tests__/resources.test.js | 5 +- src/index.js | 7 +- src/services/gpt.js | 3 +- 10 files changed, 325 insertions(+), 120 deletions(-) create mode 100644 src/__tests__/headerbidding.test.js diff --git a/src/__tests__/amazon.test.js b/src/__tests__/amazon.test.js index 77e7be0..32ff140 100644 --- a/src/__tests__/amazon.test.js +++ b/src/__tests__/amazon.test.js @@ -1,38 +1,42 @@ import { - fetchAmazonBids, - queueAmazonCommand, - } from '../services/amazon'; + fetchAmazonBids, + queueAmazonCommand, +} from '../services/amazon'; - describe('amazon service', () => { - beforeEach(() => { - global.amazonTest = { - cmd: () => jest.fn().mockName('cmd'), - queueAmazonCommand: () => jest.fn().mockName('queueAmazonCommand'), - }; - }); +describe('amazon service', () => { + beforeEach(() => { + global.amazonTest = { + cmd: () => jest.fn().mockName('cmd'), + queueAmazonCommand: () => jest.fn().mockName('queueAmazonCommand'), + }; + }); - afterEach(() => { - window.apstag = false; - jest.restoreAllMocks(); - }); + afterEach(() => { + window.apstag = false; + jest.restoreAllMocks(); + }); - it('fetchAmazonBids', () => { - window.innerWidth = '1280'; - window.apstag = { - fetchBids: () => jest.fn().mockName('fetchBids'), - setDisplayBids: () => jest.fn().mockName('setDisplayBids'), - }; - const mockSpy = jest.spyOn(window.apstag, 'fetchBids'); - fetchAmazonBids('id', 'slotname', [ - [[[728, 90], 90]], [728, 90], [468, 60] - ], 728); - expect(mockSpy).toHaveBeenCalled(); - }); + it('fetchAmazonBids', () => { + window.innerWidth = '1280'; + window.apstag = { + fetchBids: () => jest.fn(), + }; + const mockSpy = jest.spyOn(window.apstag, 'fetchBids'); + fetchAmazonBids( + 'id', 'slotname', + [ + [[728, 90], 90], + [728, 90], + [468, 60] + ], [[728, 90], 1080] + ); + expect(mockSpy).toHaveBeenCalled(); + }); - it('call passed in function in queueAmazonCommand', () => { - window.apstag = true; - const spy = jest.spyOn(amazonTest, 'cmd'); - queueAmazonCommand(global.amazonTest.cmd); - expect(spy).toHaveBeenCalled(); - }); + it('call passed in function in queueAmazonCommand', () => { + window.apstag = true; + const spy = jest.spyOn(amazonTest, 'cmd'); + queueAmazonCommand(global.amazonTest.cmd); + expect(spy).toHaveBeenCalled(); }); +}); \ No newline at end of file diff --git a/src/__tests__/arcads.test.js b/src/__tests__/arcads.test.js index 39fe790..56d6a4e 100644 --- a/src/__tests__/arcads.test.js +++ b/src/__tests__/arcads.test.js @@ -85,7 +85,7 @@ describe('arcads', () => { expect(registerAdMock).toHaveBeenCalledTimes(2); - expect(refreshMock).toHaveBeenCalledTimes(1); + expect(refreshMock).toHaveBeenCalledTimes(0); expect(requestBidsMock).toHaveBeenCalledTimes(1); }); }); diff --git a/src/__tests__/displayAd.test.js b/src/__tests__/displayAd.test.js index fdb1b9a..75eedb1 100644 --- a/src/__tests__/displayAd.test.js +++ b/src/__tests__/displayAd.test.js @@ -1,7 +1,5 @@ import { ArcAds } from '../index'; import * as gptService from '../services/gpt.js'; -import * as prebidService from '../services/prebid.js'; -import * as mobileDetection from '../util/mobile.js'; import * as sizemappingService from '../services/sizemapping.js' import * as headerBidding from '../services/headerbidding.js'; diff --git a/src/__tests__/gpt.test.js b/src/__tests__/gpt.test.js index b79ed34..489320e 100644 --- a/src/__tests__/gpt.test.js +++ b/src/__tests__/gpt.test.js @@ -2,6 +2,7 @@ import { ArcAds } from '../index'; import * as gpt from '../services/gpt'; import * as headerbidding from '../services/headerbidding'; import * as sizemap from '../services/sizemapping'; +import * as queryUtil from '../util/query'; describe('arcads', () => { const methods = { @@ -105,30 +106,101 @@ describe('arcads', () => { }); }); - describe('refreshSlot', () => { - const defineOutOfPageSlotMock = jest.fn(); - const defineSlotMock = jest.fn(); - const refreshMock = jest.fn(); - const prerenderFnc = () =>{ - return Promise.resolve([1,2,3]); - } + it('if has prerender that resolves call refresh', () => { + window.googletag.pubadsReady = true; + const prerenderFnc = jest.fn(); + gpt.refreshSlot({ad:{name:"ad"}, correlator:false, prerender:prerenderFnc, info:{}}); + expect(prerenderFnc).toHaveBeenCalledTimes(1); + }); + + it('if blockarcAds load is set do not call pubads refresh', () => { + window.googletag.pubadsReady = true; + window.blockArcAdsLoad = true; + const prerenderFnc = jest.fn(); - beforeEach(() => { - jest.clearAllMocks(); + const refreshMock = jest.fn(); + global.googletag.pubads = jest.fn().mockReturnValue({ + refresh: refreshMock, + }); + gpt.refreshSlot({ad:{name:"ad"}, correlator:false, prerender:prerenderFnc, info:{}}); + expect(refreshMock).toHaveBeenCalledTimes(0); }); - beforeAll(() => { - window.googletag= { - pubads: jest.fn().mockReturnValue({ - refresh: refreshMock, - }), - }; + describe('setTargeting', () => { + it('if options has key and value call ad SetTargeting', () => { + const setTargetingMock = jest.fn(); + const ad = {setTargeting: setTargetingMock}; + gpt.setTargeting(ad, {testKey:"testValue"}); + expect(setTargetingMock).toHaveBeenCalledTimes(1); + expect(setTargetingMock).toHaveBeenCalledWith("testKey","testValue" ); + }); + + it('if options has NOT key and value call ad SetTargeting', () => { + const setTargetingMock = jest.fn(); + const ad = {setTargeting: setTargetingMock}; + gpt.setTargeting(ad, {testKey:null}); + expect(setTargetingMock).toHaveBeenCalledTimes(0); + }); }); - it('if has prerender that resolves call refresh', () => { - window.googletag.pubadsReady = true; - gpt.refreshSlot({ad:{name:"ad"}, correlator:false, prerender:prerenderFnc, info:{}}); - //expect(window.googletag.pubads().refresh).toHaveBeenCalledTimes(1); + describe('dfpSettings', () => { + + const disableInitialLoadMock = jest.fn(); + const enableSingleRequestMock = jest.fn(); + const enableAsyncRenderingMock = jest.fn(); + const enableServicesMock = jest.fn(); + const collapseEmptyDivsMock = jest.fn(); + const addEventListenerMock = jest.fn(); + + beforeAll(() => { + global.googletag.pubads = jest.fn().mockReturnValue({ + disableInitialLoad: disableInitialLoadMock, + enableSingleRequest : enableSingleRequestMock, + enableAsyncRendering: enableAsyncRenderingMock, + collapseEmptyDivs: collapseEmptyDivsMock, + addEventListener: addEventListenerMock, + }); + + global.googletag.enableServices = enableServicesMock; + }); + + afterEach(() => { + jest.clearAllMocks(); + }); + + it('call non-logic dependent pubads setup functions', () => { + gpt.dfpSettings(); + expect(disableInitialLoadMock).toHaveBeenCalledTimes(1); + expect(enableSingleRequestMock).toHaveBeenCalledTimes(1); + expect(enableAsyncRenderingMock).toHaveBeenCalledTimes(1); + expect(enableServicesMock).toHaveBeenCalledTimes(1); + expect(enableAsyncRenderingMock).toHaveBeenCalledTimes(1); + expect(collapseEmptyDivsMock).toHaveBeenCalledTimes(0); + expect(addEventListenerMock).toHaveBeenCalledTimes(0); + }); + + it( 'if handleSlotRenderEnded function calls addEventListener', () => { + const handleMock = jest.fn(); + gpt.dfpSettings(handleMock); + expect(disableInitialLoadMock).toHaveBeenCalledTimes(1); + expect(enableSingleRequestMock).toHaveBeenCalledTimes(1); + expect(enableAsyncRenderingMock).toHaveBeenCalledTimes(1); + expect(enableServicesMock).toHaveBeenCalledTimes(1); + expect(enableAsyncRenderingMock).toHaveBeenCalledTimes(1); + expect(addEventListenerMock).toHaveBeenCalledTimes(1); + }); }); - }); + + describe('determineSlotName', () => { + it('return slotname based on dfpCode and slotname args', () => { + const result = gpt.determineSlotName('dfpCode',"testSlotname"); + expect(result).toEqual('/dfpCode/testSlotname'); + }); + + it('ifAdsSlot override then use that value for slotaName', () => { + jest.spyOn(queryUtil, 'expandQueryString').mockReturnValue('overrideSlotname'); + const result = gpt.determineSlotName('dfpCode',"testSlotname"); + expect(result).toEqual('/dfpCode/overrideSlotname'); + }); + }); }); diff --git a/src/__tests__/headerbidding.test.js b/src/__tests__/headerbidding.test.js new file mode 100644 index 0000000..e49873c --- /dev/null +++ b/src/__tests__/headerbidding.test.js @@ -0,0 +1,118 @@ +import { + initializeBiddingServices + } from '../services/headerbidding'; + + describe('initializeBiddingServices', function () { + afterEach(() => { + window.apstag = false; + window.arcBiddingReady = true; + jest.restoreAllMocks(); + }); + + it('return while window.arcBiddingReady is already set in initializeBiddingServices', () => { + Object.defineProperty(window, 'arcBiddingReady', { + writable: true, + value: true, + }); + const mockSetting = { + prebid: false, + amazon: false, + }; + const result = initializeBiddingServices(mockSetting); + expect(result).toEqual(undefined); + }); + + it('set arcBiddingReady to false while no prebid or amazon were set in initializeBiddingServices', () => { + Object.defineProperty(window, 'arcBiddingReady', { + writable: true, + value: false, + }); + const mockSetting = { + prebid: false, + amazon: false, + }; + initializeBiddingServices(mockSetting); + expect(window.arcBiddingReady).toEqual(false); + }); + + it('enable prebid ', () => { + Object.defineProperty(window, 'arcBiddingReady', { + writable: true, + value: false, + }); + Object.defineProperty(window, 'apstag', { + writable: true, + value: true, + }); + + const mockSetting = { + prebid: { + enabled: true, + }, + amazon: { + enabled: false, + } + }; + + initializeBiddingServices(mockSetting); + expect(window.arcBiddingReady).toEqual(false); + }); + + it('arcBiddingReady set to false while no pbjs available', () => { + global.pbjs = undefined; + const mockSetting = { + prebid: { + enabled: true, + }, + }; + initializeBiddingServices(mockSetting); + setTimeout(() => { + expect(window.arcBiddingReady).toEqual(false); + }, 2000); + }); + + it('enable Amazon ', () => { + Object.defineProperty(window, 'arcBiddingReady', { + writable: true, + value: false, + }); + Object.defineProperty(window, 'apstag', { + writable: true, + value: true, + }); + const mockSetting = { + prebid: false, + amazon: { + enabled: true, + id: 'mock-id' + }, + }; + initializeBiddingServices(mockSetting); + setTimeout(() => { + expect(window.arcBiddingReady).toEqual(false); + }, 2000); + }); + + it('enable Amazon without id ', () => { + Object.defineProperty(window, 'arcBiddingReady', { + writable: true, + value: false, + }); + Object.defineProperty(window, 'apstag', { + writable: true, + value: true, + }); + const mockSetting = { + prebid: false, + amazon: { + enabled: true, + id: '' + }, + }; + initializeBiddingServices(mockSetting); + + setTimeout(() => { + expect(window.arcBiddingReady).toEqual(false); + }, 2000); + }); + }); \ No newline at end of file diff --git a/src/__tests__/prebid.test.js b/src/__tests__/prebid.test.js index 191cc1c..54b730e 100644 --- a/src/__tests__/prebid.test.js +++ b/src/__tests__/prebid.test.js @@ -1,67 +1,84 @@ import { - queuePrebidCommand, - fetchPrebidBids, - addUnit, - } from '../services/prebid'; + queuePrebidCommand, + fetchPrebidBids, + addUnit, + fetchPrebidBidsArray, +} from '../services/prebid'; - const setpbjs = () => { - global.pbjs = { - que: [], - addAdUnits: () => jest.fn().mockName('addAdUnits'), - requestBids: () => jest.fn().mockName('requestBids'), - setConfig: () => jest.fn().mockName('setConfig'), - setTargetingForGPTAsync: jest.fn().mockName('setTargetingForGPTAsync'), - }; +const setpbjs = () => { + global.pbjs = { + que: [], + addAdUnits: () => jest.fn().mockName('addAdUnits'), + requestBids: () => jest.fn().mockName('requestBids').mockReturnValueOnce('result'), + setConfig: () => jest.fn().mockName('setConfig'), + bidsBackHandler: () => jest.fn().mockName('bidsBackHandler'), + setTargetingForGPTAsync: jest.fn().mockName('setTargetingForGPTAsync'), }; +}; - describe('pbjs', () => { - const info = { - bids: [], - }; +describe('pbjs', () => { + const info = { + bids: [], + }; + + beforeEach(() => { + setpbjs(); + }); - beforeEach(() => { - setpbjs(); - }); + afterEach(() => { + window.blockArcAdsPrebid = false; + global.pbjs = {}; + jest.restoreAllMocks(); + }); + + it('return if blockArcAdsPrebid is block', () => { + window.blockArcAdsPrebid = true; + const spy = jest.spyOn(pbjs, 'requestBids'); + const mockCb = jest.fn(); + fetchPrebidBidsArray({}, {}, {}, info, {}, mockCb()); + expect(spy).not.toHaveBeenCalled(); + }); - afterEach(() => { - window.blockArcAdsPrebid = false; - global.pbjs = {}; - jest.restoreAllMocks(); - }); + it('fetchPrebidBidsArray if blockArcAdsPrebid is not block', () => { + const spy = jest.spyOn(pbjs, 'requestBids'); + const mockCb = jest.fn(); + fetchPrebidBidsArray({}, {}, {}, info, {}, mockCb()); + expect(spy).toHaveBeenCalled(); + }); - it('fetchPrebidBidsArray', () => { - const spy = jest.spyOn(pbjs, 'requestBids'); - fetchPrebidBids({}, {}, {}, info, {}); - expect(spy).toHaveBeenCalled(); - }); + it('fetchPrebidBids', () => { + const spy = jest.spyOn(pbjs, 'requestBids'); + fetchPrebidBids({}, {}, {}, info, {}); + expect(spy).toHaveBeenCalled(); + }); - it('return undefined while window blockArcAdsPrebid is set to true', () => { - window.blockArcAdsPrebid = true; - const result = fetchPrebidBids({}, {}, {}, info, {}); - expect(result).toEqual(undefined); - }); + it('return undefined while window blockArcAdsPrebid is set to true', () => { + window.blockArcAdsPrebid = true; + const result = fetchPrebidBids({}, {}, {}, info, {}); + expect(result).toEqual(undefined); + }); - it('push fn into queuePrebidCommand', () => { - const mockFn = jest.fn(); - queuePrebidCommand(mockFn); - expect(pbjs.que.length).toEqual(1); - }); + it('push fn into queuePrebidCommand', () => { + const mockFn = jest.fn(); + queuePrebidCommand(mockFn); + expect(pbjs.que.length).toEqual(1); + }); - it('addUnit while slot is available', () => { - const spy = jest.spyOn(pbjs, 'addAdUnits'); - addUnit('', '', {}, {}, {}); - expect(spy).toHaveBeenCalled(); - }); + it('addUnit while slot is available', () => { + const spy = jest.spyOn(pbjs, 'addAdUnits'); + addUnit('', '', {}, {}, {}); + expect(spy).toHaveBeenCalled(); + }); - it('called config while sizeConfig is passed ', () => { - const spy = jest.spyOn(pbjs, 'setConfig'); - addUnit('', '', {}, { config: { sample: 'test' } }, {}); - expect(spy).toHaveBeenCalled(); - }); + it('called config while sizeConfig is passed ', () => { + const spy = jest.spyOn(pbjs, 'setConfig'); + addUnit('', '', {}, { config: { sample: 'test' } }, {}); + expect(spy).toHaveBeenCalled(); + }); - it('called setConfig while sizeConfig is passed ', () => { - const spy = jest.spyOn(pbjs, 'setConfig'); - addUnit('', '', {}, { sizeConfig: { sample: 'test' } }, {}); - expect(spy).toHaveBeenCalled(); - }); + it('called setConfig while sizeConfig is passed ', () => { + const spy = jest.spyOn(pbjs, 'setConfig'); + addUnit('', '', {}, { sizeConfig: { sample: 'test' } }, {}); + expect(spy).toHaveBeenCalled(); }); +}); \ No newline at end of file diff --git a/src/__tests__/registerAds.test.js b/src/__tests__/registerAds.test.js index e0e949a..18d84f4 100644 --- a/src/__tests__/registerAds.test.js +++ b/src/__tests__/registerAds.test.js @@ -18,8 +18,7 @@ describe('registerAds dimensions branches', () => { //queueGoogletagCommand mock jest.spyOn(gptService, 'queueGoogletagCommand'); - // jest.spyOn(prebidService, 'queuePrebidCommand'); - const queuePrebidCommandMock = jest.fn(); + //const queuePrebidCommandMock = jest.fn(); const queuePrebidCommandBindMock = jest.fn(); prebidService.queuePrebidCommand = queuePrebidCommandBindMock; prebidService.queuePrebidCommand.bind = queuePrebidCommandBindMock; diff --git a/src/__tests__/resources.test.js b/src/__tests__/resources.test.js index d3809d8..9af4f28 100644 --- a/src/__tests__/resources.test.js +++ b/src/__tests__/resources.test.js @@ -4,7 +4,7 @@ describe('appendResource', () => { const cbMock = jest.fn(); const appendChildMock = jest.fn(); const saveDocument = global.document; - + afterAll(() => { delete global.document; global.document = saveDocument; @@ -45,5 +45,4 @@ describe('appendResource', () => { expect(appendChildMock).toHaveBeenCalledWith(expectedParams); expect(cbMock).toHaveBeenCalledTimes(1); }); - - }); \ No newline at end of file +}); diff --git a/src/index.js b/src/index.js index 27fef04..2a5647c 100644 --- a/src/index.js +++ b/src/index.js @@ -120,9 +120,6 @@ export class ArcAds { window.blockArcAdsLoad = false; window.blockArcAdsPrebid = false; - window.googletag.pubads().refresh(window.adsList); - window.adsList = []; - //prebid call pbjs.requestBids({ timeout: bidderTimeout, @@ -131,8 +128,8 @@ export class ArcAds { console.log('Bid Back Handler', result); pbjs.setTargetingForGPTAsync(); - //ads call - window.googletag.pubads().refresh(); + window.googletag.pubads().refresh(window.adsList); + window.adsList = []; } }); } diff --git a/src/services/gpt.js b/src/services/gpt.js index be434ee..4a0ba61 100644 --- a/src/services/gpt.js +++ b/src/services/gpt.js @@ -44,7 +44,7 @@ export function refreshSlot({ }); function runRefreshEvent() { - if (window.blockArcAdsLoad) return; + if (window.blockArcAdsLoad) return 'blockArcAdsLoad'; if (window.googletag && googletag.pubadsReady) { window.googletag.pubads().refresh([ad], { changeCorrelator: correlator }); } else { @@ -84,6 +84,7 @@ export function dfpSettings(handleSlotRenderEnded) { window.googletag.pubads().disableInitialLoad(); window.googletag.pubads().enableSingleRequest(); window.googletag.pubads().enableAsyncRendering(); + if (this.collapseEmptyDivs) { window.googletag.pubads().collapseEmptyDivs(); } From d2486bafaa9ed019f7a03c60e444ceaa7becb0a8 Mon Sep 17 00:00:00 2001 From: Rachel Myers Date: Mon, 21 Sep 2020 14:07:42 -0400 Subject: [PATCH 19/22] README updates --- README.md | 14 +++++++++++++- 1 file changed, 13 insertions(+), 1 deletion(-) diff --git a/README.md b/README.md index f230f60..7d7771f 100644 --- a/README.md +++ b/README.md @@ -364,7 +364,7 @@ arcAds.registerAd({ ``` ## Registering Multiple Ads -You can display multiple ads at once using the `registerAdCollection` method. This is useful if you're initializing multiple advertisements at once in the page header. To do this you can pass an array of advertisement objects similar to the one you would with the `registerAd` call. +You can display multiple ads at once using the `registerAdCollection` method. This is useful if you're initializing multiple advertisements at once in the page header. To do this you can pass an array of advertisement objects similar to the one you would with the `registerAd` call. Note that when using this fuction, if setAdsBlockGate() has not been called, the calls for each ad will be made individuall. If you need to acheive Single Request Architecture, see the documentation below, "SRA Single Request Architecture". ```javascript const ads = [{ @@ -413,6 +413,17 @@ const ads = [{ arcAds.registerAdCollection(ads) ``` +## SRA Single Request Architecture +SRA architecture Functions will allow all ads to go out in one single ad call. The functions are presented in the order they should be called: + +1. setPageLevelTargeting(key, value): sets targeting parameters- applied to all ads on the page. Extracting common targetinig values is recommended in order to avoid repeating targeting for each ad in the single ad call. +1. setAdsBlockGate(): “closes” the gate - as ads are added, calls do not go out. This allows ads configurations to accumulated to be set out later, together all at once. +1. reserveAd(params): accumulates ads to be sent out later. This functions is called once per one ad. +1. releaseAdsBlockGate(): “opens” the gate - allows an ad call to go out. +1. sendSingleCallAds(): registers all the ads added via reserveAd(), and sends out a single ad call (SRA call) containing all the ads information that has been added so far via reserveAd(). + +To add more new ads repeat steps 1-5 as needed. + ## Developer Tools There's a series developer tools availble, to get started run `yarn install`. @@ -429,5 +440,6 @@ You can override the slot name of every advertisement on the page by appending ` You can also debug slot names and GPT in general by typing `window.googletag.openConsole()` into the browsers developer console. + ## Contributing If you'd like to contribute to ArcAds please read our [contributing guide](https://github.com/washingtonpost/ArcAds/blob/master/CONTRIBUTING.md). From 0520e907db6de6d1a645558347d174511baf2b9a Mon Sep 17 00:00:00 2001 From: Rachel Myers Date: Fri, 2 Oct 2020 12:45:05 -0400 Subject: [PATCH 20/22] ACI-71: readme update --- README.md | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/README.md b/README.md index 7d7771f..a6f54d2 100644 --- a/README.md +++ b/README.md @@ -363,6 +363,8 @@ arcAds.registerAd({ }) ``` +NOTE: Currently Amazon A9/TAM is not supported for use with Singe Request Architecture (SRA). + ## Registering Multiple Ads You can display multiple ads at once using the `registerAdCollection` method. This is useful if you're initializing multiple advertisements at once in the page header. To do this you can pass an array of advertisement objects similar to the one you would with the `registerAd` call. Note that when using this fuction, if setAdsBlockGate() has not been called, the calls for each ad will be made individuall. If you need to acheive Single Request Architecture, see the documentation below, "SRA Single Request Architecture". @@ -424,6 +426,10 @@ SRA architecture Functions will allow all ads to go out in one single ad call. T To add more new ads repeat steps 1-5 as needed. +NOTE: Prebid is supported for SRA. Amazon A9/TAM is not supported for SRA and will need to be implemented at a future date. + +NOTE: ArcAds SRA implementation calls enableSingleRequest() which means that when using pubads lazyLoad functions together with SRA, when the first ad slot comes within the viewport specified by the fetchMarginPercent parameter, the call for that ad and all other ad slots is made. If different behavior is desired after the inital SRA call is made, an outside lazy loading library may be used to manage the calls for regsterAd, reserveAd and other calls. + ## Developer Tools There's a series developer tools availble, to get started run `yarn install`. From c5b4ba02c6a263544cefa9f3787552385f18cb34 Mon Sep 17 00:00:00 2001 From: Rachel Myers Date: Fri, 2 Oct 2020 13:10:02 -0400 Subject: [PATCH 21/22] ACI-71: README proofread --- README.md | 36 ++++++++++++++++++------------------ 1 file changed, 18 insertions(+), 18 deletions(-) diff --git a/README.md b/README.md index a6f54d2..1feae04 100644 --- a/README.md +++ b/README.md @@ -18,7 +18,7 @@ To get started you must include the script tag for ArcAds in your page header, l ``` -Additionally you can install the package with npm. This is mostly useful for when you're integrating ArcAds into a single page application or a JavaScript heavy project. Most implementations should just include the script in the page header. +Additionally, you can install the package with npm. This is mostly useful for when you're integrating ArcAds into a single page application or a JavaScript heavy project. Most implementations should just include the script in the page header. ``` npm install arcads @@ -58,17 +58,17 @@ The following table shows all of the possible parameters the `registerAd` method | `slotName` | The `slotName` parameter is equal to the slot name configured within DFP, for example `sitename/hp/hp-1`. The publisher ID gets attached to the slot name within the ArcAds logic. | `String` | `Required` | | `dimensions` | The `dimensions` parameter should be an array with array of arrays containing the advertisement sizes the slot can load. If left empty the advertisement will be considered as an out of page unit. | `Array` | `Optional` | | `adType` | The `adType` parameter should describe the type of advertisement, for instance `leaderboard` or `cube`. | `String` | `Optional` | -| `display` | The `display` paramter determines which user agents can render the advertisement. The available choices are `desktop`, `mobile`, or `all`. If a value is not provided it will default to `all`. | `String` | `Optional` | -| `targeting` | The `targeting` paramter accepts an object containing key/value pairs which should attached to the advertisement request. | `Object` | `Optional` | -| `sizemap` | The `sizemap` paramter accepts an object containing information about the advertisements size mapping, for more information refer to the [Size Mapping portion of the readme](https://github.com/washingtonpost/arcads#size-mapping). | `Object` | `Optional` | -| `bidding` | The `bidding` paramter accepts an object containing information about the advertisements header bidding vendors, for more information refer to the [Header Bidding portion of the readme](https://github.com/washingtonpost/arcads#header-bidding). | `Object` | `Optional` | +| `display` | The `display` parameter determines which user agents can render the advertisement. The available choices are `desktop`, `mobile`, or `all`. If a value is not provided it will default to `all`. | `String` | `Optional` | +| `targeting` | The `targeting` parameter accepts an object containing key/value pairs which should attached to the advertisement request. | `Object` | `Optional` | +| `sizemap` | The `sizemap` parameter accepts an object containing information about the advertisements size mapping, for more information refer to the [Size Mapping portion of the readme](https://github.com/washingtonpost/arcads#size-mapping). | `Object` | `Optional` | +| `bidding` | The `bidding` parameter accepts an object containing information about the advertisements header bidding vendors, for more information refer to the [Header Bidding portion of the readme](https://github.com/washingtonpost/arcads#header-bidding). | `Object` | `Optional` | | `prerender` | The `prerender` parameter accepts an a function that should fire before the advertisement loads, for more information refer to the [Prerender Hook portion of the readme](https://github.com/washingtonpost/arcads/tree/master#prerender-hook). | `Function` | `Optional` | ### Out of Page Ads If an advertisement has an empty or missing `dimensions` parameter it will be considered as a [DFP Out of Page creative](https://support.google.com/dfp_premium/answer/6088046?hl=en) and rendered as such. ### Callback -Whenever an advertisement loads you can access data about the advertisement such as its size and id by passing in an optional callback to the initialization of ArcAds. This ties a handler to the `slotRenderEnded` event that DFP emits and is called everytime an advertisement is about to render, allowing you to make any page layout modifications to accomodate a specific advertisement. +Whenever an advertisement loads you can access data about the advertisement such as its size and id by passing in an optional callback to the initialization of ArcAds. This ties a handler to the `slotRenderEnded` event that DFP emits and is called every time an advertisement is about to render, allowing you to make any page layout modifications to accommodate a specific advertisement. ```javascript const arcAds = new ArcAds({ @@ -115,9 +115,9 @@ arcAds.registerAd({ }) ``` -The service will automatically give the advertisement a `position` target key/value pair if either the `targeting` object or `position` key of the targeting object are not present. The position value will incriment by 1 in sequence for each of the same `adType` on the page. This is a common practice between ad traffickers so this behavior is baked in, only if the trafficker makes use of this targeting will it have any effect on the advertisement rendering. +The service will automatically give the advertisement a `position` target key/value pair if either the `targeting` object or `position` key of the targeting object are not present. The position value will increment by 1 in sequence for each of the same `adType` on the page. This is a common practice between ad traffickers so this behavior is baked in, only if the trafficker makes use of this targeting will it have any effect on the advertisement rendering. -If `adType` is exluded from the `registerAd` call the automatic position targeting will not be included. +If `adType` is excluded from the `registerAd` call the automatic position targeting will not be included. ## Size Mapping You can configure DFP size mapped ads with the same registration call by adding a `sizemap` object. To utilize size mapping the `dimensions` key should be updated to include an array representing a nested array of arrays containing the applicable sizes for a specific breakpoint. @@ -157,7 +157,7 @@ arcAds.registerAd({ ## Prerender Hook ArcAds provides a way for you to get information about an advertisement before it loads, which is useful for attaching targeting data from third party vendors. -You can setup a function within the `registerAd` call by adding a `prerender` paramter, the value of which being the function you'd like to fire before the advertisement loads. This function will also fire before the advertisement refreshes if you're using sizemapping. +You can setup a function within the `registerAd` call by adding a `prerender` parameter, the value of which being the function you'd like to fire before the advertisement loads. This function will also fire before the advertisement refreshes if you're using sizemapping. ```javascript arcAds.registerAd({ @@ -217,7 +217,7 @@ If you'd like to include Prebid.js you must include the library before `arcads.j ``` -You can enable Prebid.js on the wrapper by adding a `prebid` object to the wrapper initialization and setting `enabled: true`. You can also optionally pass it a `timeout` value which corresponds in milliseconds how long Prebid.js will wait until it closs out the bidding for the advertisements on the page. By default the timeout will be set to `700`. +You can enable Prebid.js on the wrapper by adding a `prebid` object to the wrapper initialization and setting `enabled: true`. You can also optionally pass it a `timeout` value which corresponds in milliseconds how long Prebid.js will wait until it closes out the bidding for the advertisements on the page. By default, the timeout will be set to `700`. ```javascript const arcAds = new ArcAds({ @@ -292,7 +292,7 @@ const arcAds = new ArcAds({ }) ``` -On the advertisement registration you can then provide information about which bidding services that specific advertisement should use. You can find a list of paramters that Prebid.js accepts for each adapter on the [Prebid.js website](http://prebid.org/dev-docs/publisher-api-reference.html). Additionally you can turn on [Prebid.js debugging](http://prebid.org/dev-docs/toubleshooting-tips.html) by adding `?pbjs_debug=true` to the url. +On the advertisement registration you can then provide information about which bidding services that specific advertisement should use. You can find a list of parameters that Prebid.js accepts for each adapter on the [Prebid.js website](http://prebid.org/dev-docs/publisher-api-reference.html). Additionally you can turn on [Prebid.js debugging](http://prebid.org/dev-docs/toubleshooting-tips.html) by adding `?pbjs_debug=true` to the url. ```javascript arcAds.registerAd({ @@ -366,7 +366,7 @@ arcAds.registerAd({ NOTE: Currently Amazon A9/TAM is not supported for use with Singe Request Architecture (SRA). ## Registering Multiple Ads -You can display multiple ads at once using the `registerAdCollection` method. This is useful if you're initializing multiple advertisements at once in the page header. To do this you can pass an array of advertisement objects similar to the one you would with the `registerAd` call. Note that when using this fuction, if setAdsBlockGate() has not been called, the calls for each ad will be made individuall. If you need to acheive Single Request Architecture, see the documentation below, "SRA Single Request Architecture". +You can display multiple ads at once using the `registerAdCollection` method. This is useful if you're initializing multiple advertisements at once in the page header. To do this you can pass an array of advertisement objects similar to the one you would with the `registerAd` call. Note that when using this function, if setAdsBlockGate() has not been called, the calls for each ad will be made individually. If you need to achieve Single Request Architecture, see the documentation below, "SRA Single Request Architecture". ```javascript const ads = [{ @@ -418,20 +418,20 @@ arcAds.registerAdCollection(ads) ## SRA Single Request Architecture SRA architecture Functions will allow all ads to go out in one single ad call. The functions are presented in the order they should be called: -1. setPageLevelTargeting(key, value): sets targeting parameters- applied to all ads on the page. Extracting common targetinig values is recommended in order to avoid repeating targeting for each ad in the single ad call. +1. setPageLevelTargeting(key, value): sets targeting parameters- applied to all ads on the page. Extracting common targeting values is recommended in order to avoid repeating targeting for each ad in the single ad call. 1. setAdsBlockGate(): “closes” the gate - as ads are added, calls do not go out. This allows ads configurations to accumulated to be set out later, together all at once. -1. reserveAd(params): accumulates ads to be sent out later. This functions is called once per one ad. +1. reserveAd(params): accumulates ads to be sent out later. This function is called once per one ad. 1. releaseAdsBlockGate(): “opens” the gate - allows an ad call to go out. 1. sendSingleCallAds(): registers all the ads added via reserveAd(), and sends out a single ad call (SRA call) containing all the ads information that has been added so far via reserveAd(). -To add more new ads repeat steps 1-5 as needed. +To add more ads, repeat steps 1-5 as needed. NOTE: Prebid is supported for SRA. Amazon A9/TAM is not supported for SRA and will need to be implemented at a future date. -NOTE: ArcAds SRA implementation calls enableSingleRequest() which means that when using pubads lazyLoad functions together with SRA, when the first ad slot comes within the viewport specified by the fetchMarginPercent parameter, the call for that ad and all other ad slots is made. If different behavior is desired after the inital SRA call is made, an outside lazy loading library may be used to manage the calls for regsterAd, reserveAd and other calls. +NOTE: ArcAds SRA implementation calls enableSingleRequest() which means that when using pubads lazyLoad functions together with SRA, when the first ad slot comes within the viewport specified by the fetchMarginPercent parameter, the call for that ad and all other ad slots is made. If different behavior is desired after the initial SRA call is made, an outside lazy loading library may be used to manage the calls for regsterAd, reserveAd and other calls. ## Developer Tools -There's a series developer tools availble, to get started run `yarn install`. +There's a series developer tools available, to get started run `yarn install`. | Command | Description | | ------------- | ------------- | @@ -442,7 +442,7 @@ There's a series developer tools availble, to get started run `yarn install`. | `yarn debug` | Starts a local http server so you can link directly to the script during development. For example ` | ### Slot Override -You can override the slot name of every advertisement on the page by appending `?adslot=` to the URL. This will override whatever is placed inside of the `slotName` field when invoking the `registerAd` method. For example if you hit the URL `arcpublishing.com/?adslot=homepage/myad`, the full ad slot path will end up being your DFP id followed by the value: `123/homepage/myad`. +You can override the slot name of every advertisement on the page by appending `?adslot=` to the URL. This will override whatever is placed inside of the `slotName` field when invoking the `registerAd` method. For example, if you hit the URL `arcpublishing.com/?adslot=homepage/myad`, the full ad slot path will end up being your DFP id followed by the value: `123/homepage/myad`. You can also debug slot names and GPT in general by typing `window.googletag.openConsole()` into the browsers developer console. From a2b10a2a7860a7ecd0d14461a6a80b607c46f7df Mon Sep 17 00:00:00 2001 From: Rachel Myers Date: Mon, 5 Oct 2020 14:05:35 -0400 Subject: [PATCH 22/22] ACI-71: version bump to 2.0.0 --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index 91d3139..4fa6d65 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "arcads", - "version": "1.6.0", + "version": "2.0.0", "description": "ArcAds is a DFP wrapper created by Arc Publishing with publishers in mind.", "main": "dist/arcads.js", "scripts": {