-
Notifications
You must be signed in to change notification settings - Fork 42
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
* feature: add gpt component with support for header biding * fix: deal with some issues in the gpt story * fix: render ad inside storybook Storybook relies on iframes to render stories, gpt map sizes, used to build responsive ads don't seem to work with it. This hack solves it for now until further developments on storybookjs/storybook#862 * chore: document gpt story hack * chore: add tests for gpt, pbjs and ad managers * chore: add more tests for ad manager * add more tests to ad-manager * fixes here and there (console.log, typos, etc) * classes instead of functions * corrected most comments [WIP] * snapshot test * change story to render two ads in article page * quick refactor on ad manager test * alternative using react broadcast * fix tests [WIP] * snapshot tests; pbjs config; tests adapted * fix: add missing react-broadcast dep * chore: redo initialisation checks on ad, gpt and pbjs managers * remove old test * fix: jest config on gpt component * chore: remove unneeded JSDOM dev dep * fix: add section as a prop of ad composer * fix: return on all callbacks * chore: lint gpt component * fix: network id should be a prop of AdManager * chore: add comments on gpt config * chore: use promises on the gpt, pbjs and ad managers * chore: remove errors on improper class usage * CC linting err fixed; adUnit as prop * remove new.target as class constructors need to be called with new anyway * chore: increase coverage * one more test * chore: update jest configuration * prebid settings unit tests * fix: change test assumption titles * fix: test message to remember to turn ad blocker off * fix: throw error if slot does not exist * fix: use storybook url and remove transform
- Loading branch information
Showing
23 changed files
with
4,607 additions
and
40 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,29 @@ | ||
BSD 3-Clause License | ||
|
||
Copyright (c) 2017, News UK & Ireland Ltd | ||
All rights reserved. | ||
|
||
Redistribution and use in source and binary forms, with or without | ||
modification, are permitted provided that the following conditions are met: | ||
|
||
* Redistributions of source code must retain the above copyright notice, this | ||
list of conditions and the following disclaimer. | ||
|
||
* Redistributions in binary form must reproduce the above copyright notice, | ||
this list of conditions and the following disclaimer in the documentation | ||
and/or other materials provided with the distribution. | ||
|
||
* Neither the name of the copyright holder nor the names of its | ||
contributors may be used to endorse or promote products derived from | ||
this software without specific prior written permission. | ||
|
||
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" | ||
AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE | ||
IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE | ||
DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE | ||
FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL | ||
DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR | ||
SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER | ||
CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, | ||
OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE | ||
OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,6 @@ | ||
{ | ||
"env": { | ||
"jest": true, | ||
"browser": true | ||
} | ||
} |
22 changes: 22 additions & 0 deletions
22
packages/gpt/__tests__/__snapshots__/ad-composer.test.js.snap
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,22 @@ | ||
// Jest Snapshot v1, https://goo.gl/fbAQLP | ||
|
||
exports[`AdComposer renders no ad slots 1`] = `<div />`; | ||
|
||
exports[`AdComposer renders with more than one ad slot 1`] = ` | ||
<div> | ||
<div | ||
id="ad-header" | ||
/> | ||
<div | ||
id="intervention" | ||
/> | ||
</div> | ||
`; | ||
|
||
exports[`AdComposer renders with one ad slot 1`] = ` | ||
<div> | ||
<div | ||
id="ad-header" | ||
/> | ||
</div> | ||
`; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,13 @@ | ||
// Jest Snapshot v1, https://goo.gl/fbAQLP | ||
|
||
exports[`Gpt test renders an ad-header ad slot 1`] = ` | ||
<div | ||
id="ad-header" | ||
/> | ||
`; | ||
|
||
exports[`Gpt test renders an ad-pixel ad slot 1`] = ` | ||
<div | ||
id="ad-pixel" | ||
/> | ||
`; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,38 @@ | ||
import React from "react"; | ||
import renderer from "react-test-renderer"; | ||
|
||
import AdComposer from "../ad-composer"; | ||
import Ad from "../ad"; | ||
|
||
describe("AdComposer", () => { | ||
it("renders no ad slots", () => { | ||
const tree = renderer.create(<AdComposer section="article" />).toJSON(); | ||
|
||
expect(tree).toMatchSnapshot(); | ||
}); | ||
|
||
it("renders with one ad slot", () => { | ||
const tree = renderer | ||
.create( | ||
<AdComposer section="article"> | ||
<Ad code="ad-header" /> | ||
</AdComposer> | ||
) | ||
.toJSON(); | ||
|
||
expect(tree).toMatchSnapshot(); | ||
}); | ||
|
||
it("renders with more than one ad slot", () => { | ||
const tree = renderer | ||
.create( | ||
<AdComposer section="article"> | ||
<Ad code="ad-header" /> | ||
<Ad code="intervention" /> | ||
</AdComposer> | ||
) | ||
.toJSON(); | ||
|
||
expect(tree).toMatchSnapshot(); | ||
}); | ||
}); |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,252 @@ | ||
import AdManager from "../ad-manager"; | ||
import { getSlotConfig } from "../generate-config"; | ||
import gptManager from "../gpt-manager"; | ||
import pbjs from "../pbjs-manager"; | ||
import { pbjs as pbjsConfig } from "../config"; | ||
|
||
const pbjsManager = pbjs(pbjsConfig); | ||
|
||
describe("AdManager", () => { | ||
const managerOptions = { | ||
adUnit: "mock-ad-unit", | ||
networkId: "mock-network-id", | ||
section: "mock-section", | ||
gptManager, | ||
pbjsManager, | ||
getSlotConfig | ||
}; | ||
let adManager; | ||
|
||
beforeEach(() => { | ||
adManager = new AdManager(managerOptions); | ||
}); | ||
|
||
it("constructor returns an AdManager instance with correct props", () => { | ||
expect(adManager).toBeInstanceOf(AdManager); | ||
expect(adManager.initialised).toBeFalsy(); | ||
expect(adManager.adQueue).toHaveLength(0); | ||
}); | ||
|
||
it("constructor returns an AdManager instance with adUnit", () => { | ||
expect(adManager.adUnit).toBe(managerOptions.adUnit); | ||
}); | ||
|
||
it("constructor returns an AdManager instance with networkId", () => { | ||
expect(adManager.networkId).toBe(managerOptions.networkId); | ||
}); | ||
|
||
it("constructor returns an AdManager instance with section", () => { | ||
expect(adManager.section).toBe(managerOptions.section); | ||
}); | ||
|
||
it("init function sets the required scripts", () => { | ||
const newPbjsManager = adManager.pbjsManager; | ||
const newGptManager = adManager.gptManager; | ||
|
||
const pbjsLoadScript = jest.fn(); | ||
const gptLoadScript = jest.fn(); | ||
|
||
newPbjsManager.loadScript = pbjsLoadScript; | ||
newGptManager.loadScript = gptLoadScript; | ||
|
||
newPbjsManager.setConfig = () => Promise.resolve(); | ||
newGptManager.setConfig = () => Promise.resolve(); | ||
newPbjsManager.init = () => Promise.resolve(); | ||
newGptManager.init = () => Promise.resolve(); | ||
return adManager.init().then(() => { | ||
expect(pbjsLoadScript).toHaveBeenCalled(); | ||
expect(gptLoadScript).toHaveBeenCalled(); | ||
expect(adManager.initialised).toBeTruthy(); | ||
}); | ||
}); | ||
|
||
it("registerAd inserts configured ad in the queue and push it to gpt on it", () => { | ||
const newPbjsManager = adManager.pbjsManager; | ||
const newGptManager = adManager.gptManager; | ||
|
||
const windowWidth = 100; | ||
const mockAd = { | ||
code: "mock-code", | ||
mappings: [100, 200], | ||
options: { foo: "bar" } | ||
}; | ||
|
||
adManager.getSlotConfig = jest | ||
.fn() | ||
.mockImplementation((section, code, width) => { | ||
expect(section).toEqual(adManager.section); | ||
expect(code).toEqual(mockAd.code); | ||
expect(width).toEqual(windowWidth); | ||
return mockAd; | ||
}); | ||
adManager.pushAdToGPT = jest.fn(); | ||
|
||
adManager.registerAd(mockAd.code, { width: windowWidth }); | ||
expect(adManager.getSlotConfig).toHaveBeenCalled(); | ||
expect(adManager.adQueue).toHaveLength(1); | ||
expect(adManager.adQueue[0]).toEqual(mockAd); | ||
|
||
newPbjsManager.setConfig = () => Promise.resolve(); | ||
newGptManager.setConfig = () => Promise.resolve(); | ||
newPbjsManager.init = () => Promise.resolve(); | ||
newGptManager.init = () => Promise.resolve(); | ||
return adManager.init().then(() => { | ||
expect(adManager.pushAdToGPT).toHaveBeenCalled(); | ||
}); | ||
}); | ||
|
||
it("unregister one ad", () => { | ||
adManager.adQueue = [ | ||
{ | ||
id: "id-0" | ||
}, | ||
{ | ||
id: "id-1" | ||
} | ||
]; | ||
const itemId = "id-1"; | ||
expect(adManager.adQueue.length).toEqual(2); | ||
adManager.unregisterAd(itemId); | ||
expect(adManager.adQueue.length).toEqual(1); | ||
}); | ||
|
||
it("remove one item from the queue", () => { | ||
const queue = [ | ||
{ | ||
id: "id-0" | ||
}, | ||
{ | ||
id: "id-1" | ||
} | ||
]; | ||
const itemId = "id-1"; | ||
const newQueue = AdManager.removeItemFromQueue(queue, itemId); | ||
expect(queue.length).toEqual(2); | ||
expect(newQueue.length).toEqual(queue.length - 1); | ||
}); | ||
|
||
it("display should tell pbjs to handle targeting and gpt to refresh", () => { | ||
const newPbjsManager = adManager.pbjsManager; | ||
const newGptManager = adManager.gptManager; | ||
adManager.initialised = true; | ||
|
||
const refresh = jest.fn(); | ||
const pubads = jest.fn().mockImplementation(() => ({ | ||
refresh | ||
})); | ||
newGptManager.googletag = { | ||
cmd: [], | ||
pubads | ||
}; | ||
|
||
const setTargetingForGPTAsync = jest.fn(); | ||
newPbjsManager.pbjs = { | ||
que: [], | ||
setTargetingForGPTAsync | ||
}; | ||
|
||
expect(() => { | ||
adManager.display(); | ||
}).not.toThrowError(); | ||
|
||
newGptManager.googletag.cmd[0](); | ||
newPbjsManager.pbjs.que[0](); | ||
expect(pubads).toHaveBeenCalled(); | ||
expect(refresh).toHaveBeenCalled(); | ||
expect(setTargetingForGPTAsync).toHaveBeenCalled(); | ||
}); | ||
|
||
it("pushAdToGPT gives an error if ad manager is not initialised", () => { | ||
expect(adManager.initialised).toEqual(false); | ||
expect(adManager.pushAdToGPT).toThrowError(); | ||
}); | ||
|
||
it("pushAdToGPT creates and sets slot and asks gpt to display", () => { | ||
const newGptManager = adManager.gptManager; | ||
|
||
const addService = jest.fn(); | ||
const defineSizeMapping = jest.fn(); | ||
adManager.createSlot = jest.fn().mockImplementation(() => ({ | ||
addService, | ||
defineSizeMapping | ||
})); | ||
|
||
const display = jest.fn(); | ||
const pubads = jest.fn(); | ||
newGptManager.googletag = { | ||
cmd: [], | ||
display, | ||
pubads | ||
}; | ||
|
||
const slotId = "mock-slot-id"; | ||
const sizingMap = [ | ||
{ | ||
width: 300, | ||
height: 100, | ||
sizes: [[320, 50], [320, 48]] | ||
} | ||
]; | ||
|
||
adManager.initialised = true; | ||
adManager.generateSizings = jest.fn(); | ||
adManager.pushAdToGPT(slotId, sizingMap); | ||
newGptManager.googletag.cmd[0](); | ||
expect(addService).toHaveBeenCalled(); | ||
expect(defineSizeMapping).toHaveBeenCalled(); | ||
expect(display).toHaveBeenCalled(); | ||
expect(display).toHaveBeenCalledWith(slotId); | ||
}); | ||
|
||
it("pushAdToGPT gives an error if slot does not exist", () => { | ||
adManager.createSlot = jest.fn().mockImplementation(() => null); | ||
const display = jest.fn(); | ||
adManager.gptManager.googletag = { | ||
cmd: [], | ||
display | ||
}; | ||
adManager.pushAdToGPT(); | ||
adManager.gptManager.googletag.cmd[0](); | ||
expect(display).not.toHaveBeenCalled(); | ||
}); | ||
|
||
it("generateSizings calls gpt googletag to set sizings", () => { | ||
const newGptManager = adManager.gptManager; | ||
adManager.initialised = true; | ||
|
||
const addSize = jest.fn(); | ||
const build = jest.fn(); | ||
newGptManager.googletag = { | ||
sizeMapping: jest.fn().mockImplementation(() => ({ | ||
addSize, | ||
build | ||
})) | ||
}; | ||
|
||
const sizingMap = [ | ||
{ | ||
width: 300, | ||
height: 100, | ||
sizes: [[320, 50], [320, 48]] | ||
} | ||
]; | ||
|
||
adManager.generateSizings(sizingMap); | ||
expect(newGptManager.googletag.sizeMapping).toHaveBeenCalled(); | ||
expect(addSize).toHaveBeenCalled(); | ||
expect(build).toHaveBeenCalled(); | ||
}); | ||
|
||
it("createSlot calls gpt googletag to set slots", () => { | ||
const newGptManager = adManager.gptManager; | ||
adManager.initialised = true; | ||
|
||
newGptManager.googletag = { | ||
defineSlot: jest.fn() | ||
}; | ||
|
||
const slotId = "mock-slot-id"; | ||
adManager.createSlot(slotId, managerOptions.section); | ||
expect(newGptManager.googletag.defineSlot).toHaveBeenCalled(); | ||
}); | ||
}); |
Oops, something went wrong.