Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[Component] GPT component #26

Merged
merged 38 commits into from
Jul 10, 2017
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
38 commits
Select commit Hold shift + click to select a range
3182e19
feature: add gpt component with support for header biding
JGAntunes Jun 21, 2017
eb9ebe2
fix: deal with some issues in the gpt story
JGAntunes Jun 22, 2017
537546b
fix: render ad inside storybook
JGAntunes Jun 23, 2017
412e034
chore: document gpt story hack
JGAntunes Jun 23, 2017
ef55e14
chore: add tests for gpt, pbjs and ad managers
JGAntunes Jun 23, 2017
233571e
chore: add more tests for ad manager
JGAntunes Jun 23, 2017
95e344b
add more tests to ad-manager
sericaia Jun 26, 2017
4814b3c
fixes here and there (console.log, typos, etc)
sericaia Jun 26, 2017
73552e9
classes instead of functions
sericaia Jun 28, 2017
058b90d
corrected most comments [WIP]
sericaia Jun 28, 2017
769b110
snapshot test
sericaia Jun 29, 2017
b5fb4b4
change story to render two ads in article page
sericaia Jun 29, 2017
546ab30
quick refactor on ad manager test
sericaia Jun 29, 2017
d024d47
alternative using react broadcast
sericaia Jun 30, 2017
646286d
fix tests [WIP]
sericaia Jun 30, 2017
10fd132
snapshot tests; pbjs config; tests adapted
sericaia Jun 30, 2017
b89eb04
fix: add missing react-broadcast dep
JGAntunes Jul 3, 2017
58ede76
chore: redo initialisation checks on ad, gpt and pbjs managers
JGAntunes Jul 3, 2017
93af586
remove old test
sericaia Jul 3, 2017
b6f1256
fix: jest config on gpt component
JGAntunes Jul 4, 2017
a541098
chore: remove unneeded JSDOM dev dep
JGAntunes Jul 4, 2017
3605006
fix: add section as a prop of ad composer
JGAntunes Jul 4, 2017
952ca9f
fix: return on all callbacks
JGAntunes Jul 4, 2017
12b0b14
chore: lint gpt component
JGAntunes Jul 5, 2017
1760ba1
fix: network id should be a prop of AdManager
JGAntunes Jul 5, 2017
a6c617a
chore: add comments on gpt config
JGAntunes Jul 5, 2017
fafa73f
chore: use promises on the gpt, pbjs and ad managers
JGAntunes Jul 6, 2017
00f7878
chore: remove errors on improper class usage
JGAntunes Jul 6, 2017
0e75b03
CC linting err fixed; adUnit as prop
sericaia Jul 6, 2017
e1f930c
remove new.target as class constructors need to be called with new an…
sericaia Jul 6, 2017
39999e6
chore: increase coverage
sericaia Jul 6, 2017
bcc96e4
one more test
sericaia Jul 6, 2017
b5856ed
chore: update jest configuration
sericaia Jul 6, 2017
c1b1f82
prebid settings unit tests
sericaia Jul 6, 2017
2a3c4e1
fix: change test assumption titles
sericaia Jul 7, 2017
bf64aa5
fix: test message to remember to turn ad blocker off
sericaia Jul 7, 2017
acb59a9
fix: throw error if slot does not exist
sericaia Jul 7, 2017
ba307b2
fix: use storybook url and remove transform
craigbilner Jul 7, 2017
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 3 additions & 3 deletions .storybook/webpack.config.js
Original file line number Diff line number Diff line change
Expand Up @@ -4,12 +4,12 @@ module.exports = {
resolve: {
// Maps the 'react-native' import to 'react-native-web'.
alias: {
'react-native': 'react-native-web',
'@storybook/react-native': '@storybook/react',
"react-native": "react-native-web",
"@storybook/react-native": "@storybook/react"
},
// If you're working on a multi-platform React Native app, web-specific
// module implementations should be written in files using the extension
// `.web.js`.
extensions: ['.web.js', '.js', '.ios.js', '.android.js']
extensions: [".web.js", ".js", ".ios.js", ".android.js"]
}
};
1 change: 1 addition & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -95,6 +95,7 @@
"url": "0.11.0"
},
"dependencies": {
"babel-plugin-transform-class": "^0.3.0",
"dashify": "0.2.2",
"global": "4.3.2",
"handlebars": "4.0.10",
Expand Down
29 changes: 29 additions & 0 deletions packages/gpt/LICENSE
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.
6 changes: 6 additions & 0 deletions packages/gpt/__tests__/.eslintrc.json
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 packages/gpt/__tests__/__snapshots__/ad-composer.test.js.snap
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>
`;
13 changes: 13 additions & 0 deletions packages/gpt/__tests__/__snapshots__/gpt.test.js.snap
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"
/>
`;
38 changes: 38 additions & 0 deletions packages/gpt/__tests__/ad-composer.test.js
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();
});
});
252 changes: 252 additions & 0 deletions packages/gpt/__tests__/ad-manager.test.js
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();
});
});
Loading