diff --git a/src/adaptermanager.js b/src/adaptermanager.js index 8aefc67cad4..c6568c0c9f7 100644 --- a/src/adaptermanager.js +++ b/src/adaptermanager.js @@ -1,7 +1,7 @@ /** @module adaptermanger */ import { flatten, getBidderCodes, getDefinedParams, shuffle } from './utils'; -import { mapSizes } from './sizeMapping'; +import { resolveStatus } from './sizeMapping'; import { processNativeAdUnitParams, nativeAdapters } from './native'; import { newBidder } from './adapters/bidderFactory'; import { ajaxBuilder } from 'src/ajax'; @@ -19,53 +19,74 @@ let _s2sConfig = config.getConfig('s2sConfig'); var _analyticsRegistry = {}; -function getBids({bidderCode, auctionId, bidderRequestId, adUnits}) { - return adUnits.map(adUnit => { - return adUnit.bids.filter(bid => bid.bidder === bidderCode) - .map(bid => { - let sizes = adUnit.sizes; - if (adUnit.sizeMapping) { - let sizeMapping = mapSizes(adUnit); - if (sizeMapping === '') { - return ''; - } - sizes = sizeMapping; - } +/** + * @typedef {object} LabelDescriptor + * @property {boolean} labelAll describes whether or not this object expects all labels to match, or any label to match + * @property {Array} labels the labels listed on the bidder or adUnit + * @property {Array} activeLabels the labels specified as being active by requestBids + */ + +/** + * Returns object describing the status of labels on the adUnit or bidder along with labels passed into requestBids + * @param bidOrAdUnit the bidder or adUnit to get label info on + * @param activeLabels the labels passed to requestBids + * @returns {LabelDescriptor} + */ +function getLabels(bidOrAdUnit, activeLabels) { + if (bidOrAdUnit.labelAll) { + return {labelAll: true, labels: bidOrAdUnit.labelAll, activeLabels}; + } + return {labelAll: false, labels: bidOrAdUnit.labelAny, activeLabels}; +} - if (adUnit.mediaTypes) { - if (utils.isValidMediaTypes(adUnit.mediaTypes)) { - bid = Object.assign({}, bid, { mediaTypes: adUnit.mediaTypes }); - } else { - utils.logError( - `mediaTypes is not correctly configured for adunit ${adUnit.code}` - ); +function getBids({bidderCode, auctionId, bidderRequestId, adUnits, labels}) { + return adUnits.reduce((result, adUnit) => { + let {active, sizes: filteredAdUnitSizes} = resolveStatus(getLabels(adUnit, labels), adUnit.sizes); + + if (active) { + result.push(adUnit.bids.filter(bid => bid.bidder === bidderCode) + .reduce((bids, bid) => { + if (adUnit.mediaTypes) { + if (utils.isValidMediaTypes(adUnit.mediaTypes)) { + bid = Object.assign({}, bid, {mediaTypes: adUnit.mediaTypes}); + } else { + utils.logError( + `mediaTypes is not correctly configured for adunit ${adUnit.code}` + ); + } } - } - const nativeParams = - adUnit.nativeParams || utils.deepAccess(adUnit, 'mediaTypes.native'); - if (nativeParams) { - bid = Object.assign({}, bid, { - nativeParams: processNativeAdUnitParams(nativeParams), - }); - } + const nativeParams = + adUnit.nativeParams || utils.deepAccess(adUnit, 'mediaTypes.native'); + if (nativeParams) { + bid = Object.assign({}, bid, { + nativeParams: processNativeAdUnitParams(nativeParams), + }); + } - bid = Object.assign({}, bid, getDefinedParams(adUnit, [ - 'mediaType', - 'renderer' - ])); - - return Object.assign({}, bid, { - adUnitCode: adUnit.code, - transactionId: adUnit.transactionId, - sizes: sizes, - bidId: bid.bid_id || utils.getUniqueIdentifierStr(), - bidderRequestId, - auctionId - }); - } + bid = Object.assign({}, bid, getDefinedParams(adUnit, [ + 'mediaType', + 'renderer' + ])); + + let {active, sizes} = resolveStatus(getLabels(bid, labels), filteredAdUnitSizes); + + if (active) { + bids.push(Object.assign({}, bid, { + adUnitCode: adUnit.code, + transactionId: adUnit.transactionId, + sizes: sizes, + bidId: bid.bid_id || utils.getUniqueIdentifierStr(), + bidderRequestId, + auctionId + })); + } + return bids; + }, []) ); - }).reduce(flatten, []).filter(val => val !== ''); + } + return result; + }, []).reduce(flatten, []).filter(val => val !== ''); } function getAdUnitCopyForPrebidServer(adUnits) { @@ -94,7 +115,7 @@ function getAdUnitCopyForPrebidServer(adUnits) { return adUnitsCopy; } -exports.makeBidRequests = function(adUnits, auctionStart, auctionId, cbTimeout) { +exports.makeBidRequests = function(adUnits, auctionStart, auctionId, cbTimeout, labels) { let bidRequests = []; let bidderCodes = getBidderCodes(adUnits); if (config.getConfig('bidderSequence') === RANDOM) { @@ -134,7 +155,7 @@ exports.makeBidRequests = function(adUnits, auctionStart, auctionId, cbTimeout) auctionId, bidderRequestId, tid, - bids: getBids({bidderCode, auctionId, bidderRequestId, 'adUnits': adUnitsS2SCopy}), + bids: getBids({bidderCode, auctionId, bidderRequestId, 'adUnits': adUnitsS2SCopy, labels}), auctionStart: auctionStart, timeout: _s2sConfig.timeout, src: CONSTANTS.S2S.SRC @@ -165,7 +186,7 @@ exports.makeBidRequests = function(adUnits, auctionStart, auctionId, cbTimeout) bidderCode, auctionId, bidderRequestId, - bids: getBids({bidderCode, auctionId, bidderRequestId, adUnits}), + bids: getBids({bidderCode, auctionId, bidderRequestId, adUnits, labels}), auctionStart: auctionStart, timeout: cbTimeout }; @@ -174,7 +195,7 @@ exports.makeBidRequests = function(adUnits, auctionStart, auctionId, cbTimeout) } }); return bidRequests; -} +}; exports.callBids = (adUnits, bidRequests, addBidResponse, doneCb) => { let serverBidRequests = bidRequests.filter(bidRequest => { @@ -194,22 +215,26 @@ exports.callBids = (adUnits, bidRequests, addBidResponse, doneCb) => { } } } - let ajax = ajaxBuilder(bidRequests[0].timeout); - bidRequests.forEach(bidRequest => { - bidRequest.start = new Date().getTime(); - // TODO : Do we check for bid in pool from here and skip calling adapter again ? - const adapter = _bidderRegistry[bidRequest.bidderCode]; - if (adapter) { - utils.logMessage(`CALLING BIDDER ======= ${bidRequest.bidderCode}`); - events.emit(CONSTANTS.EVENTS.BID_REQUESTED, bidRequest); - bidRequest.doneCbCallCount = 0; - let done = doneCb(bidRequest.bidderRequestId); - adapter.callBids(bidRequest, addBidResponse, done, ajax); - } else { - utils.logError(`Adapter trying to be called which does not exist: ${bidRequest.bidderCode} adaptermanager.callBids`); - } - }); -} + if (bidRequests.length) { + let ajax = ajaxBuilder(bidRequests[0].timeout); + bidRequests.forEach(bidRequest => { + bidRequest.start = new Date().getTime(); + // TODO : Do we check for bid in pool from here and skip calling adapter again ? + const adapter = _bidderRegistry[bidRequest.bidderCode]; + if (adapter) { + utils.logMessage(`CALLING BIDDER ======= ${bidRequest.bidderCode}`); + events.emit(CONSTANTS.EVENTS.BID_REQUESTED, bidRequest); + bidRequest.doneCbCallCount = 0; + let done = doneCb(bidRequest.bidderRequestId); + adapter.callBids(bidRequest, addBidResponse, done, ajax); + } else { + utils.logError(`Adapter trying to be called which does not exist: ${bidRequest.bidderCode} adaptermanager.callBids`); + } + }); + } else { + utils.logWarn('callBids executed with no bidRequests. Were they filtered by labels or sizing?'); + } +}; function transformHeightWidth(adUnit) { let sizesObj = []; diff --git a/src/auction.js b/src/auction.js index 36f1d4ed27b..17ce9dbfc46 100644 --- a/src/auction.js +++ b/src/auction.js @@ -82,8 +82,9 @@ events.on(CONSTANTS.EVENTS.BID_ADJUSTMENT, function (bid) { * * @returns {Auction} auction instance */ -export function newAuction({adUnits, adUnitCodes, callback, cbTimeout}) { +export function newAuction({adUnits, adUnitCodes, callback, cbTimeout, labels}) { let _adUnits = adUnits; + let _labels = labels; let _adUnitCodes = adUnitCodes; let _bidderRequests = []; let _bidsReceived = []; @@ -346,7 +347,7 @@ export function newAuction({adUnits, adUnitCodes, callback, cbTimeout}) { }; events.emit(CONSTANTS.EVENTS.AUCTION_INIT, auctionInit); - let bidRequests = adaptermanager.makeBidRequests(_adUnits, _auctionStart, _auctionId, _timeout); + let bidRequests = adaptermanager.makeBidRequests(_adUnits, _auctionStart, _auctionId, _timeout, _labels); utils.logInfo(`Bids Requested for Auction with id: ${_auctionId}`, bidRequests); bidRequests.forEach(bidRequest => { addBidRequests(bidRequest); diff --git a/src/auctionManager.js b/src/auctionManager.js index 5ca415d29c9..ba6439e3792 100644 --- a/src/auctionManager.js +++ b/src/auctionManager.js @@ -60,8 +60,8 @@ export function newAuctionManager() { .filter(uniques); }; - _public.createAuction = function({ adUnits, adUnitCodes, callback, cbTimeout }) { - const auction = newAuction({ adUnits, adUnitCodes, callback, cbTimeout }) + _public.createAuction = function({ adUnits, adUnitCodes, callback, cbTimeout, labels }) { + const auction = newAuction({ adUnits, adUnitCodes, callback, cbTimeout, labels }); _addAuction(auction); return auction; }; diff --git a/src/prebid.js b/src/prebid.js index b38b66d150e..bfd383002d1 100644 --- a/src/prebid.js +++ b/src/prebid.js @@ -296,9 +296,10 @@ $$PREBID_GLOBAL$$.removeAdUnit = function (adUnitCode) { * @param {number} requestOptions.timeout * @param {Array} requestOptions.adUnits * @param {Array} requestOptions.adUnitCodes + * @param {Array} requestOptions.labels * @alias module:pbjs.requestBids */ -$$PREBID_GLOBAL$$.requestBids = function ({ bidsBackHandler, timeout, adUnits, adUnitCodes } = {}) { +$$PREBID_GLOBAL$$.requestBids = function ({ bidsBackHandler, timeout, adUnits, adUnitCodes, labels } = {}) { events.emit('requestBids'); const cbTimeout = timeout || config.getConfig('bidderTimeout'); adUnits = adUnits || $$PREBID_GLOBAL$$.adUnits; @@ -346,7 +347,7 @@ $$PREBID_GLOBAL$$.requestBids = function ({ bidsBackHandler, timeout, adUnits, a return; } - const auction = auctionManager.createAuction({adUnits, adUnitCodes, callback: bidsBackHandler, cbTimeout}); + const auction = auctionManager.createAuction({adUnits, adUnitCodes, callback: bidsBackHandler, cbTimeout, labels}); auction.callBids(); }; diff --git a/src/sizeMapping.js b/src/sizeMapping.js index 9529c567308..6238df6cd26 100644 --- a/src/sizeMapping.js +++ b/src/sizeMapping.js @@ -1,61 +1,84 @@ +import { config } from 'src/config'; + +let sizeConfig = []; + /** - * @module sizeMapping + * @typedef {object} SizeConfig + * + * @property {string} [mediaQuery] A CSS media query string that will to be interpreted by window.matchMedia. If the + * media query matches then the this config will be active and sizesSupported will filter bid and adUnit sizes. If + * this property is not present then this SizeConfig will only be active if triggered manually by a call to + * pbjs.setConfig({labels:['label']) specifying one of the labels present on this SizeConfig. + * @property {Array} sizesSupported The sizes to be accepted if this SizeConfig is enabled. + * @property {Array} labels The active labels to match this SizeConfig to an adUnits and/or bidders. */ -import * as utils from './utils'; -let _win; - -function mapSizes(adUnit) { - if (!isSizeMappingValid(adUnit.sizeMapping)) { - return adUnit.sizes; - } - const width = getScreenWidth(); - if (!width) { - // size not detected - get largest value set for desktop - const mapping = adUnit.sizeMapping.reduce((prev, curr) => { - return prev.minWidth < curr.minWidth ? curr : prev; - }); - if (mapping.sizes && mapping.sizes.length) { - return mapping.sizes; - } - return adUnit.sizes; - } - let sizes = ''; - const mapping = adUnit.sizeMapping.find(sizeMapping => { - return width >= sizeMapping.minWidth; - }); - if (mapping && mapping.sizes && mapping.sizes.length) { - sizes = mapping.sizes; - utils.logMessage(`AdUnit : ${adUnit.code} resized based on device width to : ${sizes}`); - } else { - utils.logMessage(`AdUnit : ${adUnit.code} not mapped to any sizes for device width. This request will be suppressed.`); - } - return sizes; -} -function isSizeMappingValid(sizeMapping) { - if (utils.isArray(sizeMapping) && sizeMapping.length > 0) { - return true; - } - utils.logInfo('No size mapping defined'); - return false; +/** + * + * @param {Array} config + */ +export function setSizeConfig(config) { + sizeConfig = config; } +config.getConfig('sizeConfig', config => setSizeConfig(config.sizeConfig)); -function getScreenWidth(win) { - var w = win || _win || window; - var d = w.document; +/** + * Resolves the unique set of the union of all sizes and labels that are active from a SizeConfig.mediaQuery match + * @param {Array} labels Labels specified on adUnit or bidder + * @param {boolean} labelAll if true, all labels must match to be enabled + * @param {Array} activeLabels Labels passed in through requestBids + * @param {Array>} sizes Sizes specified on adUnit + * @param {Array} configs + * @returns {{labels: Array, sizes: Array>}} + */ +export function resolveStatus({labels = [], labelAll = false, activeLabels = []} = {}, sizes = [], configs = sizeConfig) { + let maps = evaluateSizeConfig(configs); - if (w.innerWidth) { - return w.innerWidth; - } else if (d.body.clientWidth) { - return d.body.clientWidth; - } else if (d.documentElement.clientWidth) { - return d.documentElement.clientWidth; + let filteredSizes; + if (maps.shouldFilter) { + filteredSizes = sizes.filter(size => maps.sizesSupported[size]); + } else { + filteredSizes = sizes; } - return 0; -} -function setWindow(win) { - _win = win; + return { + active: filteredSizes.length > 0 && ( + labels.length === 0 || ( + (!labelAll && ( + labels.some(label => maps.labels[label]) || + labels.some(label => activeLabels.includes(label)) + )) || + (labelAll && ( + labels.reduce((result, label) => !result ? result : ( + maps.labels[label] || activeLabels.includes(label) + ), true) + )) + ) + ), + sizes: filteredSizes + }; } -export { mapSizes, getScreenWidth, setWindow }; +function evaluateSizeConfig(configs) { + return configs.reduce((results, config) => { + if ( + typeof config === 'object' && + typeof config.mediaQuery === 'string' && + matchMedia(config.mediaQuery).matches + ) { + if (Array.isArray(config.sizesSupported)) { + results.shouldFilter = true; + } + ['labels', 'sizesSupported'].forEach( + type => (config[type] || []).forEach( + thing => results[type][thing] = true + ) + ); + } + return results; + }, { + labels: {}, + sizesSupported: {}, + shouldFilter: false + }); +} diff --git a/test/spec/sizeMapping_spec.js b/test/spec/sizeMapping_spec.js index 471d3ba2752..fd517d1145e 100644 --- a/test/spec/sizeMapping_spec.js +++ b/test/spec/sizeMapping_spec.js @@ -1,123 +1,194 @@ import { expect } from 'chai'; -import * as sizeMapping from 'src/sizeMapping'; - -var validAdUnit = { - 'sizes': [300, 250], - 'sizeMapping': [ - { - 'minWidth': 1024, - 'sizes': [[300, 250], [728, 90]] - }, - { - 'minWidth': 480, - 'sizes': [120, 60] - }, - { - 'minWidth': 0, - 'sizes': [20, 20] - } - ] -}; - -var invalidAdUnit = { - 'sizes': [300, 250], - 'sizeMapping': {} // wrong type -}; - -var invalidAdUnit2 = { - 'sizes': [300, 250], - 'sizeMapping': [{ - foo: 'bar' // bad - }] -}; - -let mockWindow = {}; - -function resetMockWindow() { - mockWindow = { - document: { - body: { - clientWidth: 1024 - }, - documentElement: { - clientWidth: 1024 +import { resolveStatus, setSizeConfig } from 'src/sizeMapping'; + +describe('sizeMapping', () => { + var testSizes = [[970, 90], [728, 90], [300, 250], [300, 100], [80, 80]]; + + var sizeConfig = [{ + 'mediaQuery': '(min-width: 1200px)', + 'sizesSupported': [ + [970, 90], + [728, 90], + [300, 250] + ] + }, { + 'mediaQuery': '(min-width: 768px) and (max-width: 1199px)', + 'sizesSupported': [ + [728, 90], + [300, 250], + [300, 100] + ] + }, { + 'mediaQuery': '(min-width: 0px) and (max-width: 767px)', + 'sizesSupported': [] + }]; + + var sizeConfigWithLabels = [{ + 'mediaQuery': '(min-width: 1200px)', + 'labels': ['desktop'] + }, { + 'mediaQuery': '(min-width: 768px) and (max-width: 1199px)', + 'sizesSupported': [ + [728, 90], + [300, 250] + ], + 'labels': ['tablet', 'phone'] + }, { + 'mediaQuery': '(min-width: 0px) and (max-width: 767px)', + 'sizesSupported': [ + [300, 250], + [300, 100] + ], + 'labels': ['phone'] + }]; + + let sandbox, + matchMediaOverride; + + beforeEach(() => { + setSizeConfig(sizeConfig); + + sandbox = sinon.sandbox.create(); + + matchMediaOverride = {matches: false}; + + sandbox.stub(window, 'matchMedia', (...args) => { + if (typeof matchMediaOverride === 'function') { + return matchMediaOverride.apply(window, args); } - }, - innerWidth: 1024 - }; -} - -describe('sizeMapping', function() { - beforeEach(resetMockWindow); - - it('minWidth should be inclusive', function() { - mockWindow.innerWidth = 1024; - sizeMapping.setWindow(mockWindow); - let sizes = sizeMapping.mapSizes(validAdUnit); - expect(sizes).to.deep.equal([[300, 250], [728, 90]]); + return matchMediaOverride; + }); }); - it('mapSizes 1029 width', function() { - mockWindow.innerWidth = 1029; - sizeMapping.setWindow(mockWindow); - let sizes = sizeMapping.mapSizes(validAdUnit); - expect(sizes).to.deep.equal([[300, 250], [728, 90]]); - expect(validAdUnit.sizes).to.deep.equal([300, 250]); - }); + afterEach(() => { + setSizeConfig([]); - it('mapSizes 400 width', function() { - mockWindow.innerWidth = 400; - sizeMapping.setWindow(mockWindow); - let sizes = sizeMapping.mapSizes(validAdUnit); - expect(sizes).to.deep.equal([20, 20]); - expect(validAdUnit.sizes).to.deep.equal([300, 250]); + sandbox.restore(); }); - it('mapSizes - invalid adUnit - should return sizes', function() { - mockWindow.innerWidth = 1029; - sizeMapping.setWindow(mockWindow); - let sizes = sizeMapping.mapSizes(invalidAdUnit); - expect(sizes).to.deep.equal([300, 250]); - expect(invalidAdUnit.sizes).to.deep.equal([300, 250]); - - mockWindow.innerWidth = 400; - sizeMapping.setWindow(mockWindow); - sizes = sizeMapping.mapSizes(invalidAdUnit); - expect(sizes).to.deep.equal([300, 250]); - expect(invalidAdUnit.sizes).to.deep.equal([300, 250]); - }); + describe('when handling sizes', () => { + it('when one mediaQuery block matches, it should filter the adUnit.sizes passed in', () => { + matchMediaOverride = (str) => str === '(min-width: 1200px)' ? {matches: true} : {matches: false}; - it('mapSizes - should return desktop (largest) sizes if screen width not detected', function() { - mockWindow.innerWidth = 0; - mockWindow.document.body.clientWidth = 0; - mockWindow.document.documentElement.clientWidth = 0; - sizeMapping.setWindow(mockWindow); - let sizes = sizeMapping.mapSizes(validAdUnit); - expect(sizes).to.deep.equal([[300, 250], [728, 90]]); - expect(validAdUnit.sizes).to.deep.equal([300, 250]); - }); + let status = resolveStatus(undefined, testSizes, sizeConfig); - it('mapSizes - should return sizes if sizemapping improperly defined ', function() { - mockWindow.innerWidth = 0; - mockWindow.document.body.clientWidth = 0; - mockWindow.document.documentElement.clientWidth = 0; - sizeMapping.setWindow(mockWindow); - let sizes = sizeMapping.mapSizes(invalidAdUnit2); - expect(sizes).to.deep.equal([300, 250]); - expect(validAdUnit.sizes).to.deep.equal([300, 250]); - }); + expect(status).to.deep.equal({ + active: true, + sizes: [[970, 90], [728, 90], [300, 250]] + }) + }); + + it('when multiple mediaQuery block matches, it should filter a union of the matched sizesSupported', () => { + matchMediaOverride = (str) => [ + '(min-width: 1200px)', + '(min-width: 768px) and (max-width: 1199px)' + ].includes(str) ? {matches: true} : {matches: false}; + + let status = resolveStatus(undefined, testSizes, sizeConfig); + expect(status).to.deep.equal({ + active: true, + sizes: [[970, 90], [728, 90], [300, 250], [300, 100]] + }) + }); + + it('if no mediaQueries match, it should allow all sizes specified', () => { + matchMediaOverride = () => ({matches: false}); + + let status = resolveStatus(undefined, testSizes, sizeConfig); + expect(status).to.deep.equal({ + active: true, + sizes: testSizes + }) + }); - it('getScreenWidth', function() { - mockWindow.innerWidth = 900; - mockWindow.document.body.clientWidth = 900; - mockWindow.document.documentElement.clientWidth = 900; - expect(sizeMapping.getScreenWidth(mockWindow)).to.equal(900); + it('if a mediaQuery matches and has sizesSupported: [], it should filter all sizes', () => { + matchMediaOverride = (str) => str === '(min-width: 0px) and (max-width: 767px)' ? {matches: true} : {matches: false}; + + let status = resolveStatus(undefined, testSizes, sizeConfig); + expect(status).to.deep.equal({ + active: false, + sizes: [] + }) + }); + + it('if a mediaQuery matches and no sizesSupported specified, it should not effect adUnit.sizes', () => { + matchMediaOverride = (str) => str === '(min-width: 1200px)' ? {matches: true} : {matches: false}; + + let status = resolveStatus(undefined, testSizes, sizeConfigWithLabels); + expect(status).to.deep.equal({ + active: true, + sizes: testSizes + }) + }); }); - it('getScreenWidth - should return 0 if it cannot deteremine size', function() { - mockWindow.innerWidth = null; - mockWindow.document.body.clientWidth = null; - mockWindow.document.documentElement.clientWidth = null; - expect(sizeMapping.getScreenWidth(mockWindow)).to.equal(0); + describe('when handling labels', () => { + it('should activate/deactivate adUnits/bidders based on sizeConfig.labels', () => { + matchMediaOverride = (str) => str === '(min-width: 1200px)' ? {matches: true} : {matches: false}; + + let status = resolveStatus({ + labels: ['desktop'] + }, testSizes, sizeConfigWithLabels); + + expect(status).to.deep.equal({ + active: true, + sizes: testSizes + }); + + status = resolveStatus({ + labels: ['tablet'] + }, testSizes, sizeConfigWithLabels); + + expect(status).to.deep.equal({ + active: false, + sizes: testSizes + }); + }); + + it('should active/deactivate adUnits/bidders based on requestBids labels', () => { + let activeLabels = ['us-visitor', 'desktop', 'smart']; + + let status = resolveStatus({ + labels: ['uk-visitor'], + activeLabels + }, testSizes, sizeConfigWithLabels); + + expect(status).to.deep.equal({ + active: false, + sizes: testSizes + }); + + status = resolveStatus({ + labels: ['us-visitor'], + activeLabels + }, testSizes, sizeConfigWithLabels); + + expect(status).to.deep.equal({ + active: true, + sizes: testSizes + }); + + status = resolveStatus({ + labels: ['us-visitor', 'tablet'], + labelAll: true, + activeLabels + }, testSizes, sizeConfigWithLabels); + + expect(status).to.deep.equal({ + active: false, + sizes: testSizes + }); + + status = resolveStatus({ + labels: ['us-visitor', 'desktop'], + labelAll: true, + activeLabels + }, testSizes, sizeConfigWithLabels); + + expect(status).to.deep.equal({ + active: true, + sizes: testSizes + }); + }); }); }); diff --git a/test/spec/unit/core/adapterManager_spec.js b/test/spec/unit/core/adapterManager_spec.js index ac4b2d3b587..8b5f0f714b4 100644 --- a/test/spec/unit/core/adapterManager_spec.js +++ b/test/spec/unit/core/adapterManager_spec.js @@ -5,6 +5,7 @@ import CONSTANTS from 'src/constants.json'; import * as utils from 'src/utils'; import { config } from 'src/config'; import { registerBidder } from 'src/adapters/bidderFactory'; +import { setSizeConfig } from 'src/sizeMapping'; var s2sTesting = require('../../../../modules/s2sTesting'); const CONFIG = { @@ -303,5 +304,119 @@ describe('adapterManager tests', () => { expect(AdapterManager.videoAdapters).to.include(alias); }); }); + + describe('makeBidRequests', () => { + let adUnits; + beforeEach(() => { + adUnits = utils.cloneJson(getAdUnits()).map(adUnit => { + adUnit.bids = adUnit.bids.filter(bid => ['appnexus', 'rubicon'].includes(bid.bidder)); + return adUnit; + }) + }); + + describe('sizeMapping', () => { + beforeEach(() => { + sinon.stub(window, 'matchMedia', () => ({matches: true})); + }); + + afterEach(() => { + matchMedia.restore(); + setSizeConfig([]); + }); + + it('should not filter bids w/ no labels', () => { + let bidRequests = AdapterManager.makeBidRequests( + adUnits, + Date.now(), + utils.getUniqueIdentifierStr(), + function callback() {}, + [] + ); + + expect(bidRequests.length).to.equal(2); + let rubiconBidRequests = bidRequests.find(bidRequest => bidRequest.bidderCode === 'rubicon'); + expect(rubiconBidRequests.bids.length).to.equal(1); + expect(rubiconBidRequests.bids[0].sizes).to.deep.equal(adUnits.find(adUnit => adUnit.code === rubiconBidRequests.bids[0].adUnitCode).sizes); + + let appnexusBidRequests = bidRequests.find(bidRequest => bidRequest.bidderCode === 'appnexus'); + expect(appnexusBidRequests.bids.length).to.equal(2); + expect(appnexusBidRequests.bids[0].sizes).to.deep.equal(adUnits.find(adUnit => adUnit.code === appnexusBidRequests.bids[0].adUnitCode).sizes); + expect(appnexusBidRequests.bids[1].sizes).to.deep.equal(adUnits.find(adUnit => adUnit.code === appnexusBidRequests.bids[1].adUnitCode).sizes); + }); + + it('should filter sizes using size config', () => { + let validSizes = [ + [728, 90], + [300, 250] + ]; + + let validSizeMap = validSizes.map(size => size.toString()).reduce((map, size) => { + map[size] = true; + return map; + }, {}); + + setSizeConfig([{ + 'mediaQuery': '(min-width: 768px) and (max-width: 1199px)', + 'sizesSupported': validSizes, + 'labels': ['tablet', 'phone'] + }]); + + let bidRequests = AdapterManager.makeBidRequests( + adUnits, + Date.now(), + utils.getUniqueIdentifierStr(), + function callback() {}, + [] + ); + + // only valid sizes as specified in size config should show up in bidRequests + bidRequests.forEach(bidRequest => { + bidRequest.bids.forEach(bid => { + bid.sizes.forEach(size => { + expect(validSizeMap[size]).to.equal(true); + }); + }); + }); + + setSizeConfig([{ + 'mediaQuery': '(min-width: 768px) and (max-width: 1199px)', + 'sizesSupported': [], + 'labels': ['tablet', 'phone'] + }]); + + bidRequests = AdapterManager.makeBidRequests( + adUnits, + Date.now(), + utils.getUniqueIdentifierStr(), + function callback() {}, + [] + ); + + // if no valid sizes, all bidders should be filtered out + expect(bidRequests.length).to.equal(0); + }); + + it('should filter adUnits/bidders based on applied labels', () => { + adUnits[0].labelAll = ['visitor-uk', 'mobile']; + adUnits[1].labelAny = ['visitor-uk', 'desktop']; + adUnits[1].bids[0].labelAny = ['mobile']; + adUnits[1].bids[1].labelAll = ['desktop']; + + let bidRequests = AdapterManager.makeBidRequests( + adUnits, + Date.now(), + utils.getUniqueIdentifierStr(), + function callback() {}, + ['visitor-uk', 'desktop'] + ); + + // only one adUnit and one bid from that adUnit should make it through the applied labels above + expect(bidRequests.length).to.equal(1); + expect(bidRequests[0].bidderCode).to.equal('rubicon'); + expect(bidRequests[0].bids.length).to.equal(1); + expect(bidRequests[0].bids[0].adUnitCode).to.equal(adUnits[1].code); + }); + }) + }); }); }); diff --git a/test/spec/unit/pbjs_api_spec.js b/test/spec/unit/pbjs_api_spec.js index f95b1d12aad..fc82cf4a704 100644 --- a/test/spec/unit/pbjs_api_spec.js +++ b/test/spec/unit/pbjs_api_spec.js @@ -898,6 +898,7 @@ describe('Unit: Prebid Module', function () { adUnits = [{ code: 'adUnit-code', mediaType: 'video', + sizes: [[300, 250], [300, 600]], bids: [ {bidder: 'appnexus', params: {placementId: 'id'}}, {bidder: 'appnexusAst', params: {placementId: 'id'}} @@ -933,6 +934,7 @@ describe('Unit: Prebid Module', function () { adUnits = [{ code: 'adUnit-code', mediaType: 'video', + sizes: [[300, 250], [300, 600]], bids: [ {bidder: 'appnexusAst', params: {placementId: 'id'}} ] @@ -967,6 +969,7 @@ describe('Unit: Prebid Module', function () { adUnits = [{ code: 'adUnit-code', mediaType: 'native', + sizes: [[300, 250], [300, 600]], bids: [ {bidder: 'appnexus', params: {placementId: 'id'}}, {bidder: 'appnexusAst', params: {placementId: 'id'}} @@ -1017,6 +1020,7 @@ describe('Unit: Prebid Module', function () { adUnits = [{ code: 'adUnit-code', nativeParams: {type: 'image'}, + sizes: [[300, 250], [300, 600]], bids: [ {bidder: 'appnexusAst', params: {placementId: 'id'}} ] @@ -1063,6 +1067,7 @@ describe('Unit: Prebid Module', function () { let adUnits = [{ code: 'adUnit-code', nativeParams: {type: 'image'}, + sizes: [[300, 250], [300, 600]], bids: [ {bidder: 'appnexusAst', params: {placementId: 'id'}} ]