Skip to content

Commit

Permalink
Make code a bit more testeable. (#114)
Browse files Browse the repository at this point in the history
* Make code a bit more testeable.

 - refactor gpt api calls to make code testeable.
 - load(): ability to take arbitrary number of ads.
 - load(...slots): load arbitrary ads even on initial call.

* get rid of loadAlreadyCalled

* fixes
  • Loading branch information
jaanauati authored Feb 6, 2019
1 parent ac619bd commit 7aa13f4
Show file tree
Hide file tree
Showing 9 changed files with 421 additions and 231 deletions.
3 changes: 3 additions & 0 deletions .eslintrc.json
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,8 @@
"describe": true,
"expect": true,
"it": true,
"beforeAll": true,
"afterAll": true,
"beforeEach": true,
"afterEach": true,
"window": true,
Expand All @@ -19,6 +21,7 @@
"ecmaVersion": 6
},
"rules": {
"prefer-arrow-callback": 0,
"react/jsx-filename-extension": 0,
"react/no-unused-prop-types": 0,
"react/require-default-props": 0,
Expand Down
4 changes: 2 additions & 2 deletions Readme.md
Original file line number Diff line number Diff line change
Expand Up @@ -128,8 +128,8 @@ DFPManager.load();
#### Public methods
| Property | Type | Example | Description |
| ------------------ | ------------- | ----------- | ------- |
| load | ```fcn([slotId]) ```| ```DFPManager.load(); ``` | Fetches the gpt api (by calling init()) and renders the ad slots in the page. You can specify an individual slot. |
| refresh | ``` fcn([slotId]) ``` | ```DFPManager.refresh(); ``` | Refreshes all ad slots available in the page. Pass array of slotIds to only refresh specific slots. This method will call load() if it wasn't already called. Use the method ```<AdSlot shouldRefresh={function(){}} ...>``` to get control over the slots to be refreshed.|
| load | ```fcn(...slotId) ```| ```DFPManager.load(); ``` | Fetches the gpt api (by calling init()) and renders the ad slots in the page. You can pass any arbitrary number of slot ids; in this case only the specified ads are initialized. When no argument are passed, all the slots are initialized. |
| refresh | ``` fcn(...slotId) ``` | ```DFPManager.refresh('slot-1', 'slot-2'); ``` | Refreshes the ad slots available in the page. Pass any arbitrary number of slot ids to only refresh those specific slots. If no parameters are passed all the slots are refreshed (with exception of those that prevent it via `shouldRefresh` prop,) This method will call load() if it wasn't already called. Use the method ```<AdSlot shouldRefresh={function(){}} ...>``` to get control over the slots to be refreshed.|
| configureSingleRequest | ```fcn(boolean)``` | ```DFPManager.configureSingleRequest( false )``` | Controls the strategy to use for the network requests. This method accepts a boolean that tells wether to enable or disable the singleRequest mode. |
| configurePersonalizedAds | ```fcn(boolean)``` | ```DFPManager.configurePersonalizedAds( false )``` | Configure the strategy to serve ads. true: serve personalized ads, false: configure dfp to not serve personalized ads (see https://support.google.com/admanager/answer/7678538?hl=en). |
| singleRequestEnabled | ```fcn() => bool``` | ``` DFPManager.singleRequestEnabled(); ``` | Returns true when the singleRequest mode is enabled, false otherwise. |
Expand Down
194 changes: 102 additions & 92 deletions js/manager.js
Original file line number Diff line number Diff line change
@@ -1,8 +1,6 @@
import { EventEmitter } from 'events';
import * as Utils from './utils';

let loadAlreadyCalled = false;
let loadInProgress = false;
let loadPromise = null;
let googleGPTScriptLoadPromise = null;
let singleRequestEnabled = true;
Expand Down Expand Up @@ -142,96 +140,94 @@ const DFPManager = Object.assign(new EventEmitter().setMaxListeners(0), {
this.collapseEmptyDivs = collapse;
},

load(slotId) {
if (loadInProgress === true || loadPromise !== null) {
load(...slots) {
if (loadPromise === null) {
loadPromise = this.doLoad(...slots);
} else {
loadPromise = loadPromise.then(
() => this.doLoad(slotId),
() => this.doLoad(...slots),
);
} else {
loadPromise = this.doLoad(slotId);
}
},

doLoad(slotId) {
loadInProgress = true;
doLoad(...slots) {
this.init();
let availableSlots = {};

if (loadAlreadyCalled === true) {
const slot = registeredSlots[slotId];
if (slot !== undefined) {
availableSlots[slotId] = slot;
}
if (slots.length > 0) {
availableSlots = slots.filter(
slotId => Object.prototype.hasOwnProperty.call(registeredSlots, slotId),
);
} else {
availableSlots = registeredSlots;
availableSlots = Object.keys(registeredSlots);
}
availableSlots = availableSlots.filter(
id => !registeredSlots[id].loading && !registeredSlots[id].gptSlot,
);
availableSlots.forEach((slotId) => {
registeredSlots[slotId].loading = true;
});
return this.gptLoadAds(availableSlots);
},

availableSlots = Object.keys(availableSlots)
.filter(id => !availableSlots[id].loading && !availableSlots[id].gptSlot)
.reduce(
(result, id) => Object.assign(
result, {
[id]: Object.assign(availableSlots[id], { loading: true }),
},
), {},
);

return this.getGoogletag().then((googletag) => {
Object.keys(availableSlots).forEach((currentSlotId) => {
availableSlots[currentSlotId].loading = false;

googletag.cmd.push(() => {
const slot = availableSlots[currentSlotId];
let gptSlot;
const adUnit = `${slot.dfpNetworkId}/${slot.adUnit}`;
if (slot.renderOutOfThePage === true) {
gptSlot = googletag.defineOutOfPageSlot(adUnit, currentSlotId);
} else {
gptSlot = googletag.defineSlot(adUnit, slot.sizes, currentSlotId);
}
if (gptSlot !== null) {
slot.gptSlot = gptSlot;
const slotTargetingArguments = this.getSlotTargetingArguments(currentSlotId);
if (slotTargetingArguments !== null) {
Object.keys(slotTargetingArguments).forEach((varName) => {
if (slot && slot.gptSlot) {
slot.gptSlot.setTargeting(varName, slotTargetingArguments[varName]);
}
});
}
const slotAdSenseAttributes = this.getSlotAdSenseAttributes(currentSlotId);
if (slotAdSenseAttributes !== null) {
Object.keys(slotAdSenseAttributes).forEach((varName) => {
slot.gptSlot.set(varName, slotAdSenseAttributes[varName]);
});
gptLoadAds(slotsToInitialize) {
return new Promise((resolve) => {
this.getGoogletag().then((googletag) => {
slotsToInitialize.forEach((currentSlotId) => {
registeredSlots[currentSlotId].loading = false;

googletag.cmd.push(() => {
const slot = registeredSlots[currentSlotId];
let gptSlot;
const adUnit = `${slot.dfpNetworkId}/${slot.adUnit}`;
if (slot.renderOutOfThePage === true) {
gptSlot = googletag.defineOutOfPageSlot(adUnit, currentSlotId);
} else {
gptSlot = googletag.defineSlot(adUnit, slot.sizes, currentSlotId);
}
slot.gptSlot.addService(googletag.pubads());
if (slot.sizeMapping) {
let smbuilder = googletag.sizeMapping();
slot.sizeMapping.forEach((mapping) => {
smbuilder = smbuilder.addSize(mapping.viewport, mapping.sizes);
});
slot.gptSlot.defineSizeMapping(smbuilder.build());
if (gptSlot !== null) {
slot.gptSlot = gptSlot;
const slotTargetingArguments = this.getSlotTargetingArguments(currentSlotId);
if (slotTargetingArguments !== null) {
Object.keys(slotTargetingArguments).forEach((varName) => {
if (slot && slot.gptSlot) {
slot.gptSlot.setTargeting(varName, slotTargetingArguments[varName]);
}
});
}
const slotAdSenseAttributes = this.getSlotAdSenseAttributes(currentSlotId);
if (slotAdSenseAttributes !== null) {
Object.keys(slotAdSenseAttributes).forEach((varName) => {
slot.gptSlot.set(varName, slotAdSenseAttributes[varName]);
});
}
slot.gptSlot.addService(googletag.pubads());
if (slot.sizeMapping) {
let smbuilder = googletag.sizeMapping();
slot.sizeMapping.forEach((mapping) => {
smbuilder = smbuilder.addSize(mapping.viewport, mapping.sizes);
});
slot.gptSlot.defineSizeMapping(smbuilder.build());
}
}
}
});
});
});

googletag.cmd.push(() => {
if (this.singleRequestIsEnabled()) {
googletag.pubads().enableSingleRequest();
}
googletag.cmd.push(() => {
if (this.singleRequestIsEnabled()) {
googletag.pubads().enableSingleRequest();
}

if (this.collapseEmptyDivs === true || this.collapseEmptyDivs === false) {
googletag.pubads().collapseEmptyDivs(this.collapseEmptyDivs);
}
if (this.collapseEmptyDivs === true || this.collapseEmptyDivs === false) {
googletag.pubads().collapseEmptyDivs(this.collapseEmptyDivs);
}

googletag.enableServices();
Object.keys(availableSlots).forEach((theSlotId) => {
googletag.display(theSlotId);
googletag.enableServices();
slotsToInitialize.forEach((theSlotId) => {
googletag.display(theSlotId);
});
resolve();
});
loadAlreadyCalled = true;
loadInProgress = false;
});
});
},
Expand All @@ -258,25 +254,31 @@ const DFPManager = Object.assign(new EventEmitter().setMaxListeners(0), {
},

refresh(...slots) {
if (loadAlreadyCalled === false) {
if (loadPromise === null) {
this.load();
} else {
this.getGoogletag().then((googletag) => {
googletag.cmd.push(() => {
const pubadsService = googletag.pubads();
pubadsService.setRequestNonPersonalizedAds(
this.personalizedAdsEnabled() ? 0 : 1,
);
pubadsService.refresh(
Object.keys(
this.getRefreshableSlots(...slots),
).map(slotId => registeredSlots[slotId].gptSlot),
);
});
});
this.gptRefreshAds(
Object.keys(
this.getRefreshableSlots(...slots),
),
);
}
},

gptRefreshAds(slots) {
return this.getGoogletag().then((googletag) => {
googletag.cmd.push(() => {
const pubadsService = googletag.pubads();
pubadsService.setRequestNonPersonalizedAds(
this.personalizedAdsEnabled() ? 0 : 1,
);
pubadsService.refresh(
slots.map(slotId => registeredSlots[slotId].gptSlot),
);
});
});
},

destroyGPTSlots(...slotsToDestroy) {
const slots = slotsToDestroy.map(slotId => registeredSlots[slotId].gptSlot);
return this.getGoogletag()
Expand All @@ -294,16 +296,16 @@ const DFPManager = Object.assign(new EventEmitter().setMaxListeners(0), {
},

registerSlot({
slotId,
dfpNetworkId,
adUnit,
sizes,
renderOutOfThePage,
sizeMapping,
adSenseAttributes,
targetingArguments,
slotId,
slotShouldRefresh,
}) {
}, autoLoad = true) {
if (!Object.prototype.hasOwnProperty.call(registeredSlots, slotId)) {
registeredSlots[slotId] = {
slotId,
Expand All @@ -318,8 +320,16 @@ const DFPManager = Object.assign(new EventEmitter().setMaxListeners(0), {
loading: false,
};
this.emit('slotRegistered', { slotId });
if (loadAlreadyCalled === true) {
this.load(slotId);
if (autoLoad === true && loadPromise !== null) {
loadPromise = loadPromise.catch().then(() => {
const slot = registeredSlots[slotId];
if (typeof slot !== 'undefined') {
const { loading, gptSlot } = slot;
if (loading === false && !gptSlot) {
this.load(slotId);
}
}
});
}
}
},
Expand Down
3 changes: 3 additions & 0 deletions karma.conf.js
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,9 @@ module.exports = function karmaConfig(config) {
},
client: {
captureConsole: true,
jasmine: {
random: false,
},
},
webpack: {
module: {
Expand Down
Loading

0 comments on commit 7aa13f4

Please sign in to comment.