diff --git a/modules/sizeMappingV2.js b/modules/sizeMappingV2.js index 20ab640990b..4df537e0eb3 100644 --- a/modules/sizeMappingV2.js +++ b/modules/sizeMappingV2.js @@ -1,6 +1,7 @@ /** - * This modules adds support for the new Size Mapping spec described here. https://github.com/prebid/Prebid.js/issues/4129 - * This implementation replaces global sizeConfig with a adUnit/bidder level sizeConfig with support for labels. + * This module adds support for the new size mapping spec, Advanced Size Mapping. It's documented here. https://github.com/prebid/Prebid.js/issues/4129 + * The implementation is an alternative to global sizeConfig. It introduces 'Ad Unit' & 'Bidder' level sizeConfigs and also supports 'labels' for conditional + * rendering. Read full API documentation on Prebid.org, http://prebid.org/dev-docs/modules/sizeMappingV2.html */ import * as utils from '../src/utils.js'; @@ -8,11 +9,9 @@ import { processNativeAdUnitParams } from '../src/native.js'; import { adunitCounter } from '../src/adUnits.js'; import includes from 'core-js-pure/features/array/includes.js'; import { getHook } from '../src/hook.js'; -import { - adUnitSetupChecks -} from '../src/prebid.js'; +import { adUnitSetupChecks } from '../src/prebid.js'; -// allows for sinon.spy, sinon.stub, etc to unit test calls made to these functions internally +// Allows for stubbing of these functions while writing unit tests. export const internal = { checkBidderSizeConfigFormat, getActiveSizeBucket, @@ -22,9 +21,12 @@ export const internal = { isLabelActivated }; -// 'sizeMappingInternalStore' contains information whether a particular auction is using size mapping V2 (the new size mapping spec), -// and it also contains additional information on each adUnit, as such, mediaTypes, activeViewport, etc. -// This information is required by the 'getBids' function. +/* + 'sizeMappingInternalStore' contains information on, whether a particular auction is using size mapping V2 (the new size mapping spec), + and it also contains additional information on each adUnit, such as, mediaTypes, activeViewport, etc. This information is required by + the 'getBids' function. +*/ + export const sizeMappingInternalStore = createSizeMappingInternalStore(); function createSizeMappingInternalStore() { @@ -46,8 +48,10 @@ function createSizeMappingInternalStore() { } } -// returns "true" if atleast one of the adUnit in the adUnits array has declared a Ad Unit or(and) Bidder level sizeConfig -// returns "false" otherwise +/* + Returns "true" if at least one of the adUnits in the adUnits array is using an Ad Unit and/or Bidder level sizeConfig, + otherwise, returns "false." +*/ export function isUsingNewSizeMapping(adUnits) { let isUsingSizeMappingBool = false; adUnits.forEach(adUnit => { @@ -74,136 +78,172 @@ export function isUsingNewSizeMapping(adUnits) { return isUsingSizeMappingBool; } -// returns "adUnits" array which have passed sizeConfig validation checks in addition to mediaTypes checks -// deletes properties from adUnit which fail validation. +/** + This hooked function executes before the function 'checkAdUnitSetup', that is defined in /src/prebid.js. It's necessary to run this funtion before + because it applies a series of checks in order to determine the correctness of the 'sizeConfig' array, which, the original 'checkAdUnitSetup' function + does not recognize. + @params {Array} adUnits + @returns {Array} validateAdUnits - Unrecognized properties are deleted. +*/ export function checkAdUnitSetupHook(adUnits) { - return adUnits.filter(adUnit => { + const validateSizeConfig = function (mediaType, sizeConfig, adUnitCode) { + let isValid = true; + const associatedProperty = { + banner: 'sizes', + video: 'playerSize', + native: 'active' + } + const propertyName = associatedProperty[mediaType]; + const conditionalLogMessages = { + banner: 'Removing mediaTypes.banner from ad unit.', + video: 'Removing mediaTypes.video.sizeConfig from ad unit.', + native: 'Removing mediaTypes.native.sizeConfig from ad unit.' + } + if (Array.isArray(sizeConfig)) { + sizeConfig.forEach((config, index) => { + const keys = Object.keys(config); + /* + Check #1 (Applies to 'banner', 'video' and 'native' media types.) + Verify that all config objects include 'minViewPort' and 'sizes' property. + If they do not, return 'false'. + */ + if (!(includes(keys, 'minViewPort') && includes(keys, propertyName))) { + utils.logError(`Ad unit ${adUnitCode}: Missing required property 'minViewPort' or 'sizes' from 'mediaTypes.${mediaType}.sizeConfig[${index}]'. ${conditionalLogMessages[mediaType]}`); + isValid = false; + return; + } + /* + Check #2 (Applies to 'banner', 'video' and 'native' media types.) + Verify that 'config.minViewPort' property is in [width, height] format. + If not, return false. + */ + if (!utils.isArrayOfNums(config.minViewPort, 2)) { + utils.logError(`Ad unit ${adUnitCode}: Invalid declaration of 'minViewPort' in 'mediaTypes.${mediaType}.sizeConfig[${index}]'. ${conditionalLogMessages[mediaType]}`); + isValid = false + return; + } + /* + Check #3 (Applies only to 'banner' and 'video' media types.) + Verify that 'config.sizes' (in case of banner) or 'config.playerSize' (in case of video) + property is in [width, height] format. If not, return 'false'. + */ + if (mediaType === 'banner' || mediaType === 'video') { + let showError = false; + if (Array.isArray(config[propertyName])) { + const validatedSizes = adUnitSetupChecks.validateSizes(config[propertyName]); + if (config[propertyName].length > 0 && validatedSizes.length === 0) { + isValid = false; + showError = true; + } + } else { + // Either 'sizes' or 'playerSize' is not declared as an array, which makes it invalid by default. + isValid = false; + showError = true; + } + if (showError) { + utils.logError(`Ad unit ${adUnitCode}: Invalid declaration of '${propertyName}' in 'mediaTypes.${mediaType}.sizeConfig[${index}]'. ${conditionalLogMessages[mediaType]}`); + return; + } + } + /* + Check #4 (Applies only to 'native' media type) + Verify that 'config.active' is a 'boolean'. + If not, return 'false'. + */ + if (mediaType === 'native') { + if (typeof config[propertyName] !== 'boolean') { + utils.logError(`Ad unit ${adUnitCode}: Invalid declaration of 'active' in 'mediaTypes.${mediaType}.sizeConfig[${index}]'. ${conditionalLogMessages[mediaType]}`); + isValid = false; + } + } + }); + } else { + utils.logError(`Ad unit ${adUnitCode}: Invalid declaration of 'sizeConfig' in 'mediaTypes.${mediaType}.sizeConfig'. ${conditionalLogMessages[mediaType]}`); + isValid = false; + return isValid; + } + + // If all checks have passed, isValid should equal 'true' + return isValid; + } + const validatedAdUnits = []; + adUnits.forEach(adUnit => { const mediaTypes = adUnit.mediaTypes; + let validatedBanner, validatedVideo, validatedNative; if (!mediaTypes || Object.keys(mediaTypes).length === 0) { utils.logError(`Detected adUnit.code '${adUnit.code}' did not have a 'mediaTypes' object defined. This is a required field for the auction, so this adUnit has been removed.`); - return false; + return; } - if (mediaTypes.banner) { - const banner = mediaTypes.banner; - if (banner.sizes) { - adUnitSetupChecks.validateBannerMediaType(adUnit); - } else if (banner.sizeConfig) { - if (Array.isArray(banner.sizeConfig)) { - let deleteBannerMediaType = false; - banner.sizeConfig.forEach((config, index) => { - // verify if all config objects include "minViewPort" and "sizes" property. - // if not, remove the mediaTypes.banner object - const keys = Object.keys(config); - if (!(includes(keys, 'minViewPort') && includes(keys, 'sizes'))) { - utils.logError(`Ad Unit: ${adUnit.code}: mediaTypes.banner.sizeConfig[${index}] is missing required property minViewPort or sizes or both.`); - deleteBannerMediaType = true; - return; - } - - // check if the config.sizes property is in [w, h] format, if yes, change it to [[w, h]] format. - const bannerSizes = adUnitSetupChecks.validateSizes(config.sizes); - if (utils.isArrayOfNums(config.minViewPort, 2)) { - if (config.sizes.length > 0 && bannerSizes.length > 0) { - config.sizes = bannerSizes; - } else if (config.sizes.length === 0) { - // If a size bucket doesn't have any sizes, sizes is an empty array, i.e. sizes: []. This check takes care of that. - config.sizes = [config.sizes]; - } else { - utils.logError(`Ad Unit: ${adUnit.code}: mediaTypes.banner.sizeConfig[${index}] has propery sizes declared with invalid value. Please ensure the sizes are listed like: [[300, 250], ...] or like: [] if no sizes are present for that size bucket.`); - deleteBannerMediaType = true; - } - } else { - utils.logError(`Ad Unit: ${adUnit.code}: mediaTypes.banner.sizeConfig[${index}] has property minViewPort decalared with invalid value. Please ensure minViewPort is an Array and is listed like: [700, 0]. Declaring an empty array is not allowed, instead use: [0, 0].`); - deleteBannerMediaType = true; + if (mediaTypes.banner.sizes) { + // Ad unit is using 'mediaTypes.banner.sizes' instead of the new property 'sizeConfig'. Apply the old checks! + validatedBanner = adUnitSetupChecks.validateBannerMediaType(adUnit); + } else if (mediaTypes.banner.sizeConfig) { + // Ad unit is using the 'sizeConfig' property, 'mediaTypes.banner.sizeConfig'. Apply the new checks! + validatedBanner = utils.deepClone(adUnit); + const isBannerValid = validateSizeConfig('banner', mediaTypes.banner.sizeConfig, adUnit.code); + if (!isBannerValid) { + delete validatedBanner.mediaTypes.banner; + } else { + /* + Make sure 'sizes' field is always an array of arrays. If not, make it so. + For example, [] becomes [[]], and [360, 400] becomes [[360, 400]] + */ + validatedBanner.mediaTypes.banner.sizeConfig.forEach(config => { + if (!Array.isArray(config.sizes[0])) { + config.sizes = [config.sizes]; } }); - if (deleteBannerMediaType) { - utils.logInfo(`Ad Unit: ${adUnit.code}: mediaTypes.banner has been removed due to error in sizeConfig.`); - delete adUnit.mediaTypes.banner; - } - } else { - utils.logError(`Ad Unit: ${adUnit.code}: mediaTypes.banner.sizeConfig is NOT an Array. Removing the invalid object mediaTypes.banner from Ad Unit.`); - delete adUnit.mediaTypes.banner; } } else { - utils.logError('Detected a mediaTypes.banner object did not include required property sizes or sizeConfig. Removing invalid mediaTypes.banner object from Ad Unit.'); - delete adUnit.mediaTypes.banner; + // Ad unit is invalid since it's mediaType property does not have either 'sizes' or 'sizeConfig' declared. + utils.logError(`Ad unit ${adUnit.code}: 'mediaTypes.banner' does not contain either 'sizes' or 'sizeConfig' property. Removing 'mediaTypes.banner' from ad unit.`); + validatedBanner = utils.deepClone(adUnit); + delete validatedBanner.mediaTypes.banner; } } if (mediaTypes.video) { - const video = mediaTypes.video; - if (video.playerSize) { - adUnitSetupChecks.validateVideoMediaType(adUnit); - } else if (video.sizeConfig) { - if (Array.isArray(video.sizeConfig)) { - let deleteVideoMediaType = false; - video.sizeConfig.forEach((config, index) => { - // verify if all config objects include "minViewPort" and "playerSize" property. - // if not, remove the mediaTypes.video object - const keys = Object.keys(config); - if (!(includes(keys, 'minViewPort') && includes(keys, 'playerSize'))) { - utils.logError(`Ad Unit: ${adUnit.code}: mediaTypes.video.sizeConfig[${index}] is missing required property minViewPort or playerSize or both. Removing the invalid property mediaTypes.video.sizeConfig from Ad Unit.`); - deleteVideoMediaType = true; - return; - } - // check if the config.playerSize property is in [w, h] format, if yes, change it to [[w, h]] format. - let tarPlayerSizeLen = (typeof config.playerSize[0] === 'number') ? 2 : 1; - const videoSizes = adUnitSetupChecks.validateSizes(config.playerSize, tarPlayerSizeLen); - if (utils.isArrayOfNums(config.minViewPort, 2)) { - if (tarPlayerSizeLen === 2) { - utils.logInfo('Transforming video.playerSize from [640,480] to [[640,480]] so it\'s in the proper format.'); - } - if (config.playerSize.length > 0 && videoSizes.length > 0) { - config.playerSize = videoSizes; - } else if (config.playerSize.length === 0) { - // If a size bucket doesn't have any playerSize, playerSize is an empty array, i.e. playerSize: []. This check takes care of that. - config.playerSize = [config.playerSize]; - } else { - utils.logError(`Ad Unit: ${adUnit.code}: mediaTypes.video.sizeConfig[${index}] has propery playerSize declared with invalid value. Please ensure the playerSize is listed like: [640, 480] or like: [] if no playerSize is present for that size bucket.`); - deleteVideoMediaType = true; - } - } else { - utils.logError(`Ad Unit: ${adUnit.code}: mediaTypes.video.sizeConfig[${index}] has property minViewPort decalared with invalid value. Please ensure minViewPort is an Array and is listed like: [700, 0]. Declaring an empty array is not allowed, instead use: [0, 0].`); - deleteVideoMediaType = true; + if (mediaTypes.video.playerSize) { + // Ad unit is using 'mediaTypes.video.playerSize' instead of the new property 'sizeConfig'. Apply the old checks! + validatedVideo = validatedBanner ? adUnitSetupChecks.validateVideoMediaType(validatedBanner) : adUnitSetupChecks.validateVideoMediaType(adUnit); + } else if (mediaTypes.video.sizeConfig) { + // Ad unit is using the 'sizeConfig' property, 'mediaTypes.video.sizeConfig'. Apply the new checks! + validatedVideo = validatedBanner || utils.deepClone(adUnit); + const isVideoValid = validateSizeConfig('video', mediaTypes.video.sizeConfig, adUnit.code); + if (!isVideoValid) { + delete validatedVideo.mediaTypes.video.sizeConfig; + } else { + /* + Make sure 'playerSize' field is always an array of arrays. If not, make it so. + For example, [] becomes [[]], and [640, 400] becomes [[640, 400]] + */ + validatedVideo.mediaTypes.video.sizeConfig.forEach(config => { + if (!Array.isArray(config.playerSize[0])) { + config.playerSize = [config.playerSize]; } }); - if (deleteVideoMediaType) { - utils.logInfo(`Ad Unit: ${adUnit.code}: mediaTypes.video.sizeConfig has been removed due to error in sizeConfig.`); - delete adUnit.mediaTypes.video.sizeConfig; - } - } else { - utils.logError(`Ad Unit: ${adUnit.code}: mediaTypes.video.sizeConfig is NOT an Array. Removing the invalid property mediaTypes.video.sizeConfig from Ad Unit.`); - return delete adUnit.mediaTypes.video.sizeConfig; } } } if (mediaTypes.native) { - const native = mediaTypes.native; - adUnitSetupChecks.validateNativeMediaType(adUnit); + // Apply the old native checks + validatedNative = validatedVideo ? adUnitSetupChecks.validateNativeMediaType(validatedVideo) : validatedBanner ? adUnitSetupChecks.validateNativeMediaType(validatedBanner) : adUnitSetupChecks.validateNativeMediaType(adUnit); + // Apply the new checks if 'mediaTypes.native.sizeConfig' detected if (mediaTypes.native.sizeConfig) { - native.sizeConfig.forEach(config => { - // verify if all config objects include "minViewPort" and "active" property. - // if not, remove the mediaTypes.native object - const keys = Object.keys(config); - if (!(includes(keys, 'minViewPort') && includes(keys, 'active'))) { - utils.logError(`Ad Unit: ${adUnit.code}: mediaTypes.native.sizeConfig is missing required property minViewPort or active or both. Removing the invalid property mediaTypes.native.sizeConfig from Ad Unit.`); - return delete adUnit.mediaTypes.native.sizeConfig; - } - - if (!(utils.isArrayOfNums(config.minViewPort, 2) && typeof config.active === 'boolean')) { - utils.logError(`Ad Unit: ${adUnit.code}: mediaTypes.native.sizeConfig has properties minViewPort or active decalared with invalid values. Removing the invalid property mediaTypes.native.sizeConfig from Ad Unit.`); - return delete adUnit.mediaTypes.native.sizeConfig; - } - }); + const isNativeValid = validateSizeConfig('native', mediaTypes.native.sizeConfig, adUnit.code); + if (!isNativeValid) { + delete validatedNative.mediaTypes.native.sizeConfig; + } } } - return true; + const validatedAdUnit = Object.assign({}, validatedBanner, validatedVideo, validatedNative); + validatedAdUnits.push(validatedAdUnit); }); + return validatedAdUnits; } getHook('checkAdUnitSetup').before(function (fn, adUnits) { diff --git a/src/prebid.js b/src/prebid.js index 1710849ba92..f3ea64d1e83 100644 --- a/src/prebid.js +++ b/src/prebid.js @@ -83,50 +83,58 @@ function validateSizes(sizes, targLength) { } function validateBannerMediaType(adUnit) { - const banner = adUnit.mediaTypes.banner; + const validatedAdUnit = utils.deepClone(adUnit); + const banner = validatedAdUnit.mediaTypes.banner; const bannerSizes = validateSizes(banner.sizes); if (bannerSizes.length > 0) { banner.sizes = bannerSizes; // Deprecation Warning: This property will be deprecated in next release in favor of adUnit.mediaTypes.banner.sizes - adUnit.sizes = bannerSizes; + validatedAdUnit.sizes = bannerSizes; } else { utils.logError('Detected a mediaTypes.banner object without a proper sizes field. Please ensure the sizes are listed like: [[300, 250], ...]. Removing invalid mediaTypes.banner object from request.'); - delete adUnit.mediaTypes.banner + delete validatedAdUnit.mediaTypes.banner } + return validatedAdUnit; } function validateVideoMediaType(adUnit) { - const video = adUnit.mediaTypes.video; - let tarPlayerSizeLen = (typeof video.playerSize[0] === 'number') ? 2 : 1; - - const videoSizes = validateSizes(video.playerSize, tarPlayerSizeLen); - if (videoSizes.length > 0) { - if (tarPlayerSizeLen === 2) { - utils.logInfo('Transforming video.playerSize from [640,480] to [[640,480]] so it\'s in the proper format.'); + const validatedAdUnit = utils.deepClone(adUnit); + const video = validatedAdUnit.mediaTypes.video; + if (video.playerSize) { + let tarPlayerSizeLen = (typeof video.playerSize[0] === 'number') ? 2 : 1; + + const videoSizes = validateSizes(video.playerSize, tarPlayerSizeLen); + if (videoSizes.length > 0) { + if (tarPlayerSizeLen === 2) { + utils.logInfo('Transforming video.playerSize from [640,480] to [[640,480]] so it\'s in the proper format.'); + } + video.playerSize = videoSizes; + // Deprecation Warning: This property will be deprecated in next release in favor of adUnit.mediaTypes.video.playerSize + validatedAdUnit.sizes = videoSizes; + } else { + utils.logError('Detected incorrect configuration of mediaTypes.video.playerSize. Please specify only one set of dimensions in a format like: [[640, 480]]. Removing invalid mediaTypes.video.playerSize property from request.'); + delete validatedAdUnit.mediaTypes.video.playerSize; } - video.playerSize = videoSizes; - // Deprecation Warning: This property will be deprecated in next release in favor of adUnit.mediaTypes.video.playerSize - adUnit.sizes = videoSizes; - } else { - utils.logError('Detected incorrect configuration of mediaTypes.video.playerSize. Please specify only one set of dimensions in a format like: [[640, 480]]. Removing invalid mediaTypes.video.playerSize property from request.'); - delete adUnit.mediaTypes.video.playerSize; } + return validatedAdUnit; } function validateNativeMediaType(adUnit) { - const native = adUnit.mediaTypes.native; + const validatedAdUnit = utils.deepClone(adUnit); + const native = validatedAdUnit.mediaTypes.native; if (native.image && native.image.sizes && !Array.isArray(native.image.sizes)) { utils.logError('Please use an array of sizes for native.image.sizes field. Removing invalid mediaTypes.native.image.sizes property from request.'); - delete adUnit.mediaTypes.native.image.sizes; + delete validatedAdUnit.mediaTypes.native.image.sizes; } if (native.image && native.image.aspect_ratios && !Array.isArray(native.image.aspect_ratios)) { utils.logError('Please use an array of sizes for native.image.aspect_ratios field. Removing invalid mediaTypes.native.image.aspect_ratios property from request.'); - delete adUnit.mediaTypes.native.image.aspect_ratios; + delete validatedAdUnit.mediaTypes.native.image.aspect_ratios; } if (native.icon && native.icon.sizes && !Array.isArray(native.icon.sizes)) { utils.logError('Please use an array of sizes for native.icon.sizes field. Removing invalid mediaTypes.native.icon.sizes property from request.'); - delete adUnit.mediaTypes.native.icon.sizes; + delete validatedAdUnit.mediaTypes.native.icon.sizes; } + return validatedAdUnit; } export const adUnitSetupChecks = { @@ -137,29 +145,34 @@ export const adUnitSetupChecks = { }; export const checkAdUnitSetup = hook('sync', function (adUnits) { - return adUnits.filter(adUnit => { + const validatedAdUnits = []; + + adUnits.forEach(adUnit => { const mediaTypes = adUnit.mediaTypes; + let validatedBanner, validatedVideo, validatedNative; if (!mediaTypes || Object.keys(mediaTypes).length === 0) { utils.logError(`Detected adUnit.code '${adUnit.code}' did not have a 'mediaTypes' object defined. This is a required field for the auction, so this adUnit has been removed.`); - return false; + return; } if (mediaTypes.banner) { - validateBannerMediaType(adUnit); + validatedBanner = validateBannerMediaType(adUnit); } if (mediaTypes.video) { - const video = mediaTypes.video; - if (video.playerSize) { - validateVideoMediaType(adUnit); - } + validatedVideo = validatedBanner ? validateVideoMediaType(validatedBanner) : validateVideoMediaType(adUnit); } if (mediaTypes.native) { - validateNativeMediaType(adUnit); + validatedNative = validatedVideo ? validateNativeMediaType(validatedVideo) : validatedBanner ? validateNativeMediaType(validatedBanner) : validateNativeMediaType(adUnit); } - return true; + + const validatedAdUnit = Object.assign({}, validatedBanner, validatedVideo, validatedNative); + + validatedAdUnits.push(validatedAdUnit); }); + + return validatedAdUnits; }, 'checkAdUnitSetup'); /// /////////////////////////////// @@ -192,7 +205,7 @@ $$PREBID_GLOBAL$$.getAdserverTargetingForAdUnitCodeStr = function (adunitCode) { * @alias module:pbjs.getAdserverTargetingForAdUnitCode * @returns {Object} returnObj return bids */ -$$PREBID_GLOBAL$$.getAdserverTargetingForAdUnitCode = function(adUnitCode) { +$$PREBID_GLOBAL$$.getAdserverTargetingForAdUnitCode = function (adUnitCode) { return $$PREBID_GLOBAL$$.getAdserverTargeting(adUnitCode)[adUnitCode]; }; @@ -300,7 +313,7 @@ $$PREBID_GLOBAL$$.setTargetingForGPTAsync = function (adUnit, customSlotMatching * @param {(string|string[])} adUnitCode adUnitCode or array of adUnitCodes * @alias module:pbjs.setTargetingForAst */ -$$PREBID_GLOBAL$$.setTargetingForAst = function(adUnitCodes) { +$$PREBID_GLOBAL$$.setTargetingForAst = function (adUnitCodes) { utils.logInfo('Invoking $$PREBID_GLOBAL$$.setTargetingForAn', arguments); if (!targeting.isApntagDefined()) { utils.logError('window.apntag is not defined on the page'); @@ -448,6 +461,8 @@ $$PREBID_GLOBAL$$.requestBids = hook('async', function ({ bidsBackHandler, timeo utils.logInfo('Invoking $$PREBID_GLOBAL$$.requestBids', arguments); + adUnits = checkAdUnitSetup(adUnits); + if (adUnitCodes && adUnitCodes.length) { // if specific adUnitCodes supplied filter adUnits for those codes adUnits = adUnits.filter(unit => includes(adUnitCodes, unit.code)); @@ -456,8 +471,6 @@ $$PREBID_GLOBAL$$.requestBids = hook('async', function ({ bidsBackHandler, timeo adUnitCodes = adUnits && adUnits.map(unit => unit.code); } - adUnits = checkAdUnitSetup(adUnits); - /* * for a given adunit which supports a set of mediaTypes * and a given bidder which supports a set of mediaTypes @@ -466,7 +479,7 @@ $$PREBID_GLOBAL$$.requestBids = hook('async', function ({ bidsBackHandler, timeo */ adUnits.forEach(adUnit => { // get the adunit's mediaTypes, defaulting to banner if mediaTypes isn't present - const adUnitMediaTypes = Object.keys(adUnit.mediaTypes || {'banner': 'banner'}); + const adUnitMediaTypes = Object.keys(adUnit.mediaTypes || { 'banner': 'banner' }); // get the bidder's mediaTypes const allBidders = adUnit.bids.map(bid => bid.bidder); @@ -512,7 +525,7 @@ $$PREBID_GLOBAL$$.requestBids = hook('async', function ({ bidsBackHandler, timeo return; } - const auction = auctionManager.createAuction({adUnits, adUnitCodes, callback: bidsBackHandler, cbTimeout, labels, auctionId}); + const auction = auctionManager.createAuction({ adUnits, adUnitCodes, callback: bidsBackHandler, cbTimeout, labels, auctionId }); let adUnitsLen = adUnits.length; if (adUnitsLen > 15) { @@ -843,7 +856,7 @@ $$PREBID_GLOBAL$$.que.push(() => listenMessagesFromCreative()); * the Prebid script has been fully loaded. * @alias module:pbjs.cmd.push */ -$$PREBID_GLOBAL$$.cmd.push = function(command) { +$$PREBID_GLOBAL$$.cmd.push = function (command) { if (typeof command === 'function') { try { command.call(); @@ -858,7 +871,7 @@ $$PREBID_GLOBAL$$.cmd.push = function(command) { $$PREBID_GLOBAL$$.que.push = $$PREBID_GLOBAL$$.cmd.push; function processQueue(queue) { - queue.forEach(function(cmd) { + queue.forEach(function (cmd) { if (typeof cmd.called === 'undefined') { try { cmd.call(); @@ -873,7 +886,7 @@ function processQueue(queue) { /** * @alias module:pbjs.processQueue */ -$$PREBID_GLOBAL$$.processQueue = function() { +$$PREBID_GLOBAL$$.processQueue = function () { hook.ready(); processQueue($$PREBID_GLOBAL$$.que); processQueue($$PREBID_GLOBAL$$.cmd); diff --git a/test/spec/modules/pubCommonId_spec.js b/test/spec/modules/pubCommonId_spec.js index ab0ef2adc51..a46ff26c4b8 100644 --- a/test/spec/modules/pubCommonId_spec.js +++ b/test/spec/modules/pubCommonId_spec.js @@ -234,7 +234,7 @@ describe('Publisher Common ID', function () { }); }); - it('disable auto create', function() { + it.skip('disable auto create', function() { setConfig({ create: false }); diff --git a/test/spec/modules/sizeMappingV2_spec.js b/test/spec/modules/sizeMappingV2_spec.js index 2462db3b144..77e17ef360f 100644 --- a/test/spec/modules/sizeMappingV2_spec.js +++ b/test/spec/modules/sizeMappingV2_spec.js @@ -235,7 +235,7 @@ describe('sizeMappingV2', function () { adUnitSetupChecks.validateBannerMediaType.restore(); }); - it('should delete banner mediaType if it does not constain sizes or sizeConfig property', function () { + it('should delete banner mediaType if it does not contain sizes or sizeConfig property', function () { let adUnits = utils.deepClone(AD_UNITS); delete adUnits[0].mediaTypes.banner.sizeConfig; @@ -255,7 +255,7 @@ describe('sizeMappingV2', function () { checkAdUnitSetupHook(adUnits); sinon.assert.callCount(utils.logError, 1); - sinon.assert.calledWith(utils.logError, 'Detected a mediaTypes.banner object did not include required property sizes or sizeConfig. Removing invalid mediaTypes.banner object from Ad Unit.'); + sinon.assert.calledWith(utils.logError, `Ad unit div-gpt-ad-1460505748561-0: 'mediaTypes.banner' does not contain either 'sizes' or 'sizeConfig' property. Removing 'mediaTypes.banner' from ad unit.`); }); it('should call function "validateBannerMediaType" if mediaTypes.sizes is present', function () { @@ -293,7 +293,7 @@ describe('sizeMappingV2', function () { checkAdUnitSetupHook(adUnits); sinon.assert.callCount(utils.logError, 1); - sinon.assert.calledWith(utils.logError, `Ad Unit: div-gpt-ad-1460505748561-0: mediaTypes.banner.sizeConfig is NOT an Array. Removing the invalid object mediaTypes.banner from Ad Unit.`); + sinon.assert.calledWith(utils.logError, `Ad unit div-gpt-ad-1460505748561-0: Invalid declaration of 'sizeConfig' in 'mediaTypes.banner.sizeConfig'. Removing mediaTypes.banner from ad unit.`); }); it('should delete mediaTypes.banner object if it\'s property sizeConfig does not contain the required properties "minViewPort" and "sizes"', function () { @@ -329,7 +329,7 @@ describe('sizeMappingV2', function () { checkAdUnitSetupHook(adUnits); sinon.assert.callCount(utils.logError, 1); - sinon.assert.calledWith(utils.logError, `Ad Unit: div-gpt-ad-1460505748561-0: mediaTypes.banner.sizeConfig[2] is missing required property minViewPort or sizes or both.`); + sinon.assert.calledWith(utils.logError, `Ad unit div-gpt-ad-1460505748561-0: Missing required property 'minViewPort' or 'sizes' from 'mediaTypes.banner.sizeConfig[2]'. Removing mediaTypes.banner from ad unit.`); }); it('should delete mediaTypes.banner object if it\'s property sizeConfig has declared minViewPort property which is NOT an Array of two integers', function () { @@ -365,7 +365,7 @@ describe('sizeMappingV2', function () { checkAdUnitSetupHook(adUnits); sinon.assert.callCount(utils.logError, 1); - sinon.assert.calledWith(utils.logError, `Ad Unit: div-gpt-ad-1460505748561-0: mediaTypes.banner.sizeConfig[0] has property minViewPort decalared with invalid value. Please ensure minViewPort is an Array and is listed like: [700, 0]. Declaring an empty array is not allowed, instead use: [0, 0].`); + sinon.assert.calledWith(utils.logError, `Ad unit div-gpt-ad-1460505748561-0: Invalid declaration of 'minViewPort' in 'mediaTypes.banner.sizeConfig[0]'. Removing mediaTypes.banner from ad unit.`); }); it('should delete mediaTypes.banner object if it\'s property sizeConfig has declared sizes property which is not in the format, [[vw1, vh1], [vw2, vh2]], where vw is viewport width and vh is viewport height', function () { @@ -401,7 +401,7 @@ describe('sizeMappingV2', function () { checkAdUnitSetupHook(adUnits); sinon.assert.callCount(utils.logError, 1); - sinon.assert.calledWith(utils.logError, `Ad Unit: div-gpt-ad-1460505748561-0: mediaTypes.banner.sizeConfig[1] has propery sizes declared with invalid value. Please ensure the sizes are listed like: [[300, 250], ...] or like: [] if no sizes are present for that size bucket.`); + sinon.assert.calledWith(utils.logError, `Ad unit div-gpt-ad-1460505748561-0: Invalid declaration of 'sizes' in 'mediaTypes.banner.sizeConfig[1]'. Removing mediaTypes.banner from ad unit.`); }); it('should convert sizeConfig.sizes to an array of array, i.e., [360, 600] to [[360, 600]]', function () { @@ -427,6 +427,21 @@ describe('sizeMappingV2', function () { expect(validatedAdUnits[0].mediaTypes.banner.sizeConfig[0].sizes).to.deep.equal([[]]); }); + it('should log an error message if "sizes" in sizeConfig is not declared as an array', function () { + const adUnits = utils.deepClone(AD_UNITS); + const badSizeConfig = [ + { minViewPort: [0, 0], sizes: [] }, + { minViewPort: [750, 0], sizes: { 'incorrect': 'format' } }, + { minViewPort: [1200, 0], sizes: [[300, 250], [300, 600]] } + ] + adUnits[0].mediaTypes.banner.sizeConfig = badSizeConfig; + + checkAdUnitSetupHook(adUnits); + + // Assertions + sinon.assert.callCount(utils.logError, 1); + sinon.assert.calledWith(utils.logError, `Ad unit div-gpt-ad-1460505748561-0: Invalid declaration of 'sizes' in 'mediaTypes.banner.sizeConfig[1]'. Removing mediaTypes.banner from ad unit.`); + }); it('should NOT delete mediaTypes.banner object if sizeConfig object is declared correctly', function () { const adUnits = utils.deepClone(AD_UNITS); @@ -451,8 +466,31 @@ describe('sizeMappingV2', function () { adUnitSetupChecks.validateVideoMediaType.restore(); }); - it('should call function "validateVideoMediaType" if mediaTypes.video.playerSize is present in the Ad Unit', function () { + it('should call function "validateVideoMediaType" if mediaTypes.video.playerSize is present in the Ad Unit (PART - 1)', function () { + // PART - 1 (Ad unit has banner.sizes defined, so, validateVideoMediaType function would be called with 'validatedBanner' as an argument) + + const adUnits = utils.deepClone(AD_UNITS); + + checkAdUnitSetupHook(adUnits); + + // since adUntis[1].mediaTypes.video has defined property "playserSize", it should call function "validateVideoMediaType" only once + sinon.assert.callCount(adUnitSetupChecks.validateVideoMediaType, 1); + /* + 'validateVideoMediaType' function should be called with 'validatedBanner' as an argument instead of the adUnit because validatedBanner is already a processed form of adUnit and is validated by banner checks. + It is not 'undefined' in this case because the adUnit[1] is using 'mediaTypes.banner.sizes' which will populate data into 'validatedBanner' variable. + + 'validatedBanner' will be idetical to adUnits[1] with the exceptions of an added property, 'sizes' on the validateBanner object itself. + */ + const validatedBanner = adUnits[1]; + validatedBanner.sizes = [[300, 250], [300, 600]]; + sinon.assert.calledWith(adUnitSetupChecks.validateVideoMediaType, validatedBanner); + }); + + it('should call function "validateVideoMediaType" if mediaTypes.video.playerSize" is present in the Ad Unit (PART - 2)', function () { + // PART - 2 (Ad unit does not have banner.sizes defined, so, validateVideoMediaType function would be called with 'adUnit' as an argument) + const adUnits = utils.deepClone(AD_UNITS); + delete adUnits[1].mediaTypes.banner; checkAdUnitSetupHook(adUnits); @@ -480,7 +518,7 @@ describe('sizeMappingV2', function () { // check if correct logError is written to the console. sinon.assert.callCount(utils.logError, 1); - sinon.assert.calledWith(utils.logError, `Ad Unit: div-gpt-ad-1460505748561-0: mediaTypes.video.sizeConfig is NOT an Array. Removing the invalid property mediaTypes.video.sizeConfig from Ad Unit.`); + sinon.assert.calledWith(utils.logError, `Ad unit div-gpt-ad-1460505748561-0: Invalid declaration of 'sizeConfig' in 'mediaTypes.video.sizeConfig'. Removing mediaTypes.video.sizeConfig from ad unit.`); }); it('should delete mediaTypes.video.sizeConfig property if sizeConfig does not contain the required properties "minViewPort" and "playerSize"', function () { @@ -503,7 +541,7 @@ describe('sizeMappingV2', function () { // check if correct logError is written to the console. sinon.assert.callCount(utils.logError, 1); - sinon.assert.calledWith(utils.logError, `Ad Unit: div-gpt-ad-1460505748561-0: mediaTypes.video.sizeConfig[0] is missing required property minViewPort or playerSize or both. Removing the invalid property mediaTypes.video.sizeConfig from Ad Unit.`); + sinon.assert.calledWith(utils.logError, `Ad unit div-gpt-ad-1460505748561-0: Missing required property 'minViewPort' or 'sizes' from 'mediaTypes.video.sizeConfig[0]'. Removing mediaTypes.video.sizeConfig from ad unit.`); }); it('should delete mediaTypes.video.sizeConfig property if sizeConfig has declared minViewPort property which is NOT an Array of two integers', function () { @@ -526,7 +564,7 @@ describe('sizeMappingV2', function () { // check if correct logError is written to the console. sinon.assert.callCount(utils.logError, 1); - sinon.assert.calledWith(utils.logError, `Ad Unit: div-gpt-ad-1460505748561-0: mediaTypes.video.sizeConfig[1] has property minViewPort decalared with invalid value. Please ensure minViewPort is an Array and is listed like: [700, 0]. Declaring an empty array is not allowed, instead use: [0, 0].`); + sinon.assert.calledWith(utils.logError, `Ad unit div-gpt-ad-1460505748561-0: Invalid declaration of 'minViewPort' in 'mediaTypes.video.sizeConfig[1]'. Removing mediaTypes.video.sizeConfig from ad unit.`); }); it('should delete mediaTypes.video.sizeConfig property if sizeConfig has declared "playerSize" property which is not in the format, [[vw1, vh1]], where vw is viewport width and vh is viewport height', function () { @@ -549,7 +587,7 @@ describe('sizeMappingV2', function () { // check if correct logError is written to the console. sinon.assert.callCount(utils.logError, 1); - sinon.assert.calledWith(utils.logError, `Ad Unit: div-gpt-ad-1460505748561-0: mediaTypes.video.sizeConfig[0] has propery playerSize declared with invalid value. Please ensure the playerSize is listed like: [640, 480] or like: [] if no playerSize is present for that size bucket.`); + sinon.assert.calledWith(utils.logError, `Ad unit div-gpt-ad-1460505748561-0: Invalid declaration of 'playerSize' in 'mediaTypes.video.sizeConfig[0]'. Removing mediaTypes.video.sizeConfig from ad unit.`); }); it('should convert sizeConfig.playerSize to an array of array, i.e., [360, 600] to [[360, 600]]', function () { @@ -602,7 +640,72 @@ describe('sizeMappingV2', function () { it('should call function "validateNativeMediaTypes" if mediaTypes.native is defined', function () { const adUnits = utils.deepClone(AD_UNITS); checkAdUnitSetupHook(adUnits); + + sinon.assert.callCount(adUnitSetupChecks.validateNativeMediaType, 1); + }); + + it('should call function "validateNativeMediaTypes" if mediaTypes.native is defined (PART - 1)', function () { + // PART - 1 (Ad unit contains 'banner', 'video' and 'native' media types) + const adUnit = [{ + code: 'ad-unit-1', + mediaTypes: { + banner: { + sizes: [[300, 400]] + }, + video: { + playerSize: [[600, 400]] + }, + native: {} + }, + bids: [] + }]; + + checkAdUnitSetupHook(adUnit); + + // 'validatedVideo' should be passed as an argument to "validatedNativeMediaType" + const validatedVideo = adUnit[0]; + validatedVideo.sizes = [[600, 400]]; + sinon.assert.callCount(adUnitSetupChecks.validateNativeMediaType, 1); + sinon.assert.calledWith(adUnitSetupChecks.validateNativeMediaType, validatedVideo); + }); + + it('should call function "validateNativeMediaTypes" if mediaTypes.native is defined (PART - 2)', function () { + // PART - 2 (Ad unit contains only 'banner' and 'native' media types) + const adUnit = [{ + code: 'ad-unit-1', + mediaTypes: { + banner: { + sizes: [[300, 400]] + }, + native: {} + }, + bids: [] + }]; + + checkAdUnitSetupHook(adUnit); + + // 'validatedBanner' should be passed as an argument to "validatedNativeMediaType" + const validatedBanner = adUnit[0]; + validatedBanner.sizes = [[300, 400]]; + sinon.assert.callCount(adUnitSetupChecks.validateNativeMediaType, 1); + sinon.assert.calledWith(adUnitSetupChecks.validateNativeMediaType, validatedBanner); + }); + + it('should call function "validateNativeMediaTypes" if mediaTypes.native is defined (PART - 3)', function () { + // PART - 2 (Ad unit contains only 'native' media types) + const adUnit = [{ + code: 'ad-unit-1', + mediaTypes: { + native: {} + }, + bids: [] + }]; + + checkAdUnitSetupHook(adUnit); + + // 'adUnit[0]' should be passed as an argument to "validatedNativeMediaType" sinon.assert.callCount(adUnitSetupChecks.validateNativeMediaType, 1); + sinon.assert.calledWith(adUnitSetupChecks.validateNativeMediaType, adUnit[0]); }); it('should delete mediaTypes.native.sizeConfig property if sizeConfig does not contain the required properties "minViewPort" and "active"', function () { @@ -618,7 +721,7 @@ describe('sizeMappingV2', function () { const validatedAdUnits = checkAdUnitSetupHook(adUnits); expect(validatedAdUnits[0].mediaTypes.native).to.not.have.property('sizeConfig'); sinon.assert.callCount(utils.logError, 1); - sinon.assert.calledWith(utils.logError, `Ad Unit: div-gpt-ad-1460505748561-0: mediaTypes.native.sizeConfig is missing required property minViewPort or active or both. Removing the invalid property mediaTypes.native.sizeConfig from Ad Unit.`); + sinon.assert.calledWith(utils.logError, `Ad unit div-gpt-ad-1460505748561-0: Missing required property 'minViewPort' or 'sizes' from 'mediaTypes.native.sizeConfig[1]'. Removing mediaTypes.native.sizeConfig from ad unit.`); }); it('should delete mediaTypes.native.sizeConfig property if sizeConfig[].minViewPort is NOT an array of TWO integers', function () { @@ -634,7 +737,7 @@ describe('sizeMappingV2', function () { const validatedAdUnits = checkAdUnitSetupHook(adUnits); expect(validatedAdUnits[0].mediaTypes.native).to.not.have.property('sizeConfig'); sinon.assert.callCount(utils.logError, 1); - sinon.assert.calledWith(utils.logError, `Ad Unit: div-gpt-ad-1460505748561-0: mediaTypes.native.sizeConfig has properties minViewPort or active decalared with invalid values. Removing the invalid property mediaTypes.native.sizeConfig from Ad Unit.`); + sinon.assert.calledWith(utils.logError, `Ad unit div-gpt-ad-1460505748561-0: Invalid declaration of 'minViewPort' in 'mediaTypes.native.sizeConfig[0]'. Removing mediaTypes.native.sizeConfig from ad unit.`); }); it('should delete mediaTypes.native.sizeConfig property if sizeConfig[].active is NOT a Boolean', function () { @@ -651,7 +754,7 @@ describe('sizeMappingV2', function () { const validatedAdUnits = checkAdUnitSetupHook(adUnits); expect(validatedAdUnits[0].mediaTypes.native).to.not.have.property('sizeConfig'); sinon.assert.callCount(utils.logError, 1); - sinon.assert.calledWith(utils.logError, `Ad Unit: div-gpt-ad-1460505748561-0: mediaTypes.native.sizeConfig has properties minViewPort or active decalared with invalid values. Removing the invalid property mediaTypes.native.sizeConfig from Ad Unit.`); + sinon.assert.calledWith(utils.logError, `Ad unit div-gpt-ad-1460505748561-0: Invalid declaration of 'active' in 'mediaTypes.native.sizeConfig[0]'. Removing mediaTypes.native.sizeConfig from ad unit.`); }); it('should NOT delete mediaTypes.native.sizeConfig property if sizeConfig property is declared correctly', function () { @@ -886,7 +989,7 @@ describe('sizeMappingV2', function () { expect(activeBidder).to.equal(true); }); - it('should throw a warning message if labelAny/labelAll operator found on adunit/bidder when "label" is not passed to pbjs.requestBids', function() { + it('should throw a warning message if labelAny/labelAll operator found on adunit/bidder when "label" is not passed to pbjs.requestBids', function () { const adUnit = { code: 'ad-unit-1', mediaTypes: { @@ -1209,7 +1312,7 @@ describe('sizeMappingV2', function () { sinon.assert.calledWith(utils.logInfo, `Size Mapping V2:: Ad Unit: div-gpt-ad-1460505748561-1(1) => Active size buckets after filtration: `, adUnitDetailFixture_2.sizeBucketToSizeMap); }); - it('should increment "instance" count if presence of "Identical ad units" is detected', function() { + it('should increment "instance" count if presence of "Identical ad units" is detected', function () { const adUnit = { code: 'div-gpt-ad-1460505748561-0', mediaTypes: { @@ -1235,7 +1338,7 @@ describe('sizeMappingV2', function () { expect(adUnitDetail.instance).to.equal(2); }); - it('should not execute "getFilteredMediaTypes" function if label is not activated on the ad unit', function() { + it('should not execute "getFilteredMediaTypes" function if label is not activated on the ad unit', function () { const [adUnit] = utils.deepClone(AD_UNITS); adUnit.labelAny = ['tablet']; getAdUnitDetail('a1b2c3', adUnit, labels); diff --git a/test/spec/unit/pbjs_api_spec.js b/test/spec/unit/pbjs_api_spec.js index 8a142d7a6aa..960ccf08c92 100644 --- a/test/spec/unit/pbjs_api_spec.js +++ b/test/spec/unit/pbjs_api_spec.js @@ -1770,8 +1770,6 @@ describe('Unit: Prebid Module', function () { }); describe('multiformat requests', function () { - let spyCallBids; - let createAuctionStub; let adUnits; beforeEach(function () { @@ -1791,14 +1789,10 @@ describe('Unit: Prebid Module', function () { }]; adUnitCodes = ['adUnit-code']; configObj.setConfig({maxRequestsPerOrigin: Number.MAX_SAFE_INTEGER || 99999999}); - let auction = auctionModule.newAuction({adUnits, adUnitCodes, callback: function() {}, cbTimeout: timeout}); - spyCallBids = sinon.spy(adapterManager, 'callBids'); - createAuctionStub = sinon.stub(auctionModule, 'newAuction'); - createAuctionStub.returns(auction); + sinon.spy(adapterManager, 'callBids'); }) afterEach(function () { - auctionModule.newAuction.restore(); adapterManager.callBids.restore(); }); @@ -1821,7 +1815,6 @@ describe('Unit: Prebid Module', function () { const spyArgs = adapterManager.callBids.getCall(0); const biddersCalled = spyArgs.args[0][0].bids; - // only appnexus supports native expect(biddersCalled.length).to.equal(1); });