From 24f6ace4e28e8140a30be515a863f80acb5174ae Mon Sep 17 00:00:00 2001 From: Gabriel Lew Date: Thu, 8 Mar 2018 22:27:30 -0800 Subject: [PATCH 1/4] Adds support for googletag.events.SlotOnloadEvent (#20) (#73) --- docs/api/ReactGPT.md | 1 + examples/apps/log.js | 7 +++++++ src/Bling.js | 7 +++++++ src/Events.js | 3 ++- src/createManager.js | 3 ++- 5 files changed, 19 insertions(+), 2 deletions(-) diff --git a/docs/api/ReactGPT.md b/docs/api/ReactGPT.md index a366274..c055ed7 100644 --- a/docs/api/ReactGPT.md +++ b/docs/api/ReactGPT.md @@ -25,6 +25,7 @@ A React component which renders [GPT](https://support.google.com/dfp_sb/answer/1 - `onSlotRenderEnded`(optional) - An optional event handler function for `googletag.events.SlotRenderEndedEvent`. - `onImpressionViewable`(optional) - An optional event handler function for `googletag.events.ImpressionViewableEvent`. - `onSlotVisibilityChanged`(optional) - An optional event handler function for `googletag.events.slotVisibilityChangedEvent`. +- `onSlotLoad`(optional) - An optional event handler function for `googletag.events.SlotOnloadEvent`. - `renderWhenViewable`(optional) - An optional flag to indicate whether an ad should only render when it's fully in the viewport area. - `viewableThreshold`(optional) - An optional number to indicate how much percentage of an ad area needs to be in a viewable area before rendering. Acceptable range is between `0` and `1`. - `onScriptLoaded`(optional) - An optional call back function to notify when the script is loaded. diff --git a/examples/apps/log.js b/examples/apps/log.js index 4d945bf..2741e5f 100644 --- a/examples/apps/log.js +++ b/examples/apps/log.js @@ -42,4 +42,11 @@ GPT.on(Events.SLOT_VISIBILITY_CHANGED, event => { const divId = slot.getSlotElementId(); const sizes = slot.getSizes(); console.log(`SLOT_VISIBILITY_CHANGED for ${divId}(${JSON.stringify(sizes)}) to ${event.inViewPercentage}`, event); +}); + +GPT.on(Events.SLOT_LOADED, event => { + const slot = event.slot; + const divId = slot.getSlotElementId(); + const sizes = slot.getSizes(); + console.log(`SLOT_LOADED for ${divId}(${JSON.stringify(sizes)})`, event); });*/ diff --git a/src/Bling.js b/src/Bling.js index d2e26e9..ab0affa 100644 --- a/src/Bling.js +++ b/src/Bling.js @@ -19,6 +19,7 @@ import {createManager, pubadsAPI} from "./createManager"; * @fires Bling#Events.SLOT_RENDER_ENDED * @fires Bling#Events.IMPRESSION_VIEWABLE * @fires Bling#Events.SLOT_VISIBILITY_CHANGED + * @fires Bling#Events.SLOT_LOADED */ class Bling extends Component { static propTypes = { @@ -154,6 +155,12 @@ class Bling extends Component { * @property onSlotVisibilityChanged */ onSlotVisibilityChanged: PropTypes.func, + /** + * An optional event handler function for `googletag.events.SlotOnloadEvent`. + * + * @property onSlotLoad + */ + onSlotLoad: PropTypes.func, /** * An optional flag to indicate whether an ad should only render when it's fully in the viewport area. * diff --git a/src/Events.js b/src/Events.js index 38123f2..cac498c 100644 --- a/src/Events.js +++ b/src/Events.js @@ -3,7 +3,8 @@ const Events = { RENDER: "render", SLOT_RENDER_ENDED: "slotRenderEnded", IMPRESSION_VIEWABLE: "impressionViewable", - SLOT_VISIBILITY_CHANGED: "slotVisibilityChanged" + SLOT_VISIBILITY_CHANGED: "slotVisibilityChanged", + SLOT_LOADED: "slotOnload" }; export default Events; diff --git a/src/createManager.js b/src/createManager.js index 4b27071..1830f96 100644 --- a/src/createManager.js +++ b/src/createManager.js @@ -186,7 +186,8 @@ export class AdManager extends EventEmitter { [ Events.SLOT_RENDER_ENDED, Events.IMPRESSION_VIEWABLE, - Events.SLOT_VISIBILITY_CHANGED + Events.SLOT_VISIBILITY_CHANGED, + Events.SLOT_LOADED ].forEach(eventType => { ["pubads", "content", "companionAds"].forEach(service => { // there is no API to remove listeners. From 08a9b1c10ca6894e84754904b179aeadb7ca3994 Mon Sep 17 00:00:00 2001 From: Christian Shum-Harden Date: Mon, 12 Mar 2018 17:40:28 -0700 Subject: [PATCH 2/4] (refactor) allow ['fluid'] slotSize fixes #72 (#74) * (refactor) allow ['fluid'] slotSize * (refactor) remove logs and broken test --- .gitignore | 1 + src/Bling.js | 42 ++++++++++++--------- test/Bling.spec.js | 91 ++++++++++++++++++++++++++++++++++++---------- yarn.lock | 18 ++++----- 4 files changed, 106 insertions(+), 46 deletions(-) diff --git a/.gitignore b/.gitignore index e9cced1..7d573ff 100644 --- a/.gitignore +++ b/.gitignore @@ -4,4 +4,5 @@ lib/ dist/ coverage/ npm-debug.log +yarn-error.log .DS_Store diff --git a/src/Bling.js b/src/Bling.js index ab0affa..707527b 100644 --- a/src/Bling.js +++ b/src/Bling.js @@ -1,5 +1,5 @@ /* eslint-disable react/sort-comp */ -import React, {Component} from "react"; +import React, { Component } from "react"; import PropTypes from "prop-types"; import ReactDOM from "react-dom"; import invariant from "invariant"; @@ -7,7 +7,7 @@ import deepEqual from "deep-equal"; import hoistStatics from "hoist-non-react-statics"; import Events from "./Events"; import filterPropsSimple from "./utils/filterProps"; -import {createManager, pubadsAPI} from "./createManager"; +import { createManager, pubadsAPI } from "./createManager"; /** * An Ad Component using Google Publisher Tags. * This component should work standalone w/o context. @@ -387,8 +387,8 @@ class Bling extends Component { } componentWillReceiveProps(nextProps) { - const {propsEqual} = Bling._config; - const {sizeMapping} = this.props; + const { propsEqual } = Bling._config; + const { sizeMapping } = this.props; if ( (nextProps.sizeMapping || sizeMapping) && !propsEqual(nextProps.sizeMapping, sizeMapping) @@ -400,7 +400,7 @@ class Bling extends Component { shouldComponentUpdate(nextProps, nextState) { // if adUnitPath changes, need to create a new slot, re-render // otherwise, just refresh - const {scriptLoaded, inViewport} = nextState; + const { scriptLoaded, inViewport } = nextState; const notInViewport = this.notInViewport(nextProps, nextState); const inViewportChanged = this.state.inViewport !== inViewport; const isScriptLoaded = this.state.scriptLoaded !== scriptLoaded; @@ -412,7 +412,7 @@ class Bling extends Component { return true; } - const {filterProps, propsEqual} = Bling._config; + const { filterProps, propsEqual } = Bling._config; const refreshableProps = filterProps( Bling.refreshableProps, this.props, @@ -430,7 +430,6 @@ class Bling extends Component { const shouldRefresh = !shouldRender && !propsEqual(refreshableProps.props, refreshableProps.nextProps); - // console.log(`shouldRefresh: ${shouldRefresh}, shouldRender: ${shouldRender}, isScriptLoaded: ${isScriptLoaded}, syncCorrelator: ${Bling._adManager._syncCorrelator}`); if (shouldRefresh) { this.configureSlot(this._adSlot, nextProps); @@ -479,12 +478,12 @@ class Bling extends Component { } onScriptLoaded() { - const {onScriptLoaded} = this.props; + const { onScriptLoaded } = this.props; if (this.getRenderWhenViewable()) { this.foldCheck(); } - this.setState({scriptLoaded: true}, onScriptLoaded); // eslint-disable-line react/no-did-mount-set-state + this.setState({ scriptLoaded: true }, onScriptLoaded); // eslint-disable-line react/no-did-mount-set-state } onScriptError(err) { @@ -509,7 +508,10 @@ class Bling extends Component { if (Array.isArray(slotSize) && Array.isArray(slotSize[0])) { slotSize = slotSize[0]; } - if (slotSize === "fluid") { + if ( + slotSize === "fluid" || + (Array.isArray(slotSize) && slotSize[0] === "fluid") + ) { slotSize = [0, 0]; } @@ -519,7 +521,7 @@ class Bling extends Component { this.viewableThreshold ); if (inViewport) { - this.setState({inViewport: true}); + this.setState({ inViewport: true }); } } @@ -594,12 +596,12 @@ class Bling extends Component { } notInViewport(props = this.props, state = this.state) { - const {inViewport} = state; + const { inViewport } = state; return this.getRenderWhenViewable(props) && !inViewport; } defineSlot() { - const {adUnitPath, outOfPage} = this.props; + const { adUnitPath, outOfPage } = this.props; const divId = this._divId; const slotSize = this.getSlotSize(); @@ -690,7 +692,7 @@ class Bling extends Component { } display() { - const {content} = this.props; + const { content } = this.props; const divId = this._divId; const adSlot = this._adSlot; @@ -735,8 +737,8 @@ class Bling extends Component { } render() { - const {scriptLoaded} = this.state; - const {id, outOfPage, style} = this.props; + const { scriptLoaded } = this.state; + const { id, outOfPage, style } = this.props; const shouldNotRender = this.notInViewport(this.props, this.state); if (!scriptLoaded || shouldNotRender) { @@ -753,7 +755,10 @@ class Bling extends Component { slotSize = slotSize[0]; } // https://developers.google.com/doubleclick-gpt/reference?hl=en#googletag.NamedSize - if (slotSize === "fluid") { + if ( + slotSize === "fluid" || + (Array.isArray(slotSize) && slotSize[0] === "fluid") + ) { slotSize = ["auto", "auto"]; } const emptyStyle = slotSize && { @@ -780,7 +785,8 @@ class Bling extends Component { export default hoistStatics( Bling, pubadsAPI.reduce((api, method) => { - api[method] = (...args) => Bling._adManager.pubadsProxy({method, args}); + api[method] = (...args) => + Bling._adManager.pubadsProxy({ method, args }); return api; }, {}) ); diff --git a/test/Bling.spec.js b/test/Bling.spec.js index 220396e..b5719d8 100644 --- a/test/Bling.spec.js +++ b/test/Bling.spec.js @@ -1,20 +1,20 @@ /* eslint-disable react/no-multi-comp */ -import React, {Component} from "react"; +import React, { Component } from "react"; import PropTypes from "prop-types"; import ReactTestUtils from "react-dom/test-utils"; import ShallowRenderer from "react-test-renderer/shallow"; import Bling from "../src/Bling"; import Events from "../src/Events"; -import {pubadsAPI, APIToCallBeforeServiceEnabled} from "../src/createManager"; -import {gptVersion, pubadsVersion} from "../src/utils/apiList"; -import {createManagerTest} from "../src/utils/createManagerTest"; +import { pubadsAPI, APIToCallBeforeServiceEnabled } from "../src/createManager"; +import { gptVersion, pubadsVersion } from "../src/utils/apiList"; +import { createManagerTest } from "../src/utils/createManagerTest"; describe("Bling", () => { let googletag; const stubs = []; beforeEach(() => { - Bling.configure({renderWhenViewable: false}); + Bling.configure({ renderWhenViewable: false }); Bling.testManager = createManagerTest(); googletag = Bling._adManager.googletag; }); @@ -43,7 +43,27 @@ describe("Bling", () => { ); const result = renderer.getRenderOutput(); expect(result.type).to.equal("div"); - expect(result.props.style).to.eql({width: 728, height: 90}); + expect(result.props.style).to.eql({ width: 728, height: 90 }); + }); + + it("renders fluid to auto width and height", () => { + const renderer = new ShallowRenderer(); + renderer.render( + + ); + const result = renderer.getRenderOutput(); + expect(result.type).to.equal("div"); + expect(result.props.style).to.eql({ width: "auto", height: "auto" }); + }); + + it("renders ['fluid'] to auto width and height", () => { + const renderer = new ShallowRenderer(); + renderer.render( + + ); + const result = renderer.getRenderOutput(); + expect(result.type).to.equal("div"); + expect(result.props.style).to.eql({ width: "auto", height: "auto" }); }); it("returns gpt version", done => { @@ -239,7 +259,7 @@ describe("Bling", () => { it("calls getServices on adSlot on clear", () => { const instance = new Bling(); - const adSlot = sinon.mock({getServices: () => {}}); + const adSlot = sinon.mock({ getServices: () => {} }); adSlot.expects("getServices").once(); instance._adSlot = adSlot; instance.clear(); @@ -296,16 +316,16 @@ describe("Bling", () => { ); }); it("reflects targeting props to adSlot", done => { - const targeting = {t1: "v1", t2: [1, 2, 3]}; + const targeting = { t1: "v1", t2: [1, 2, 3] }; Bling.once(Events.RENDER, () => { const adSlot = instance.adSlot; @@ -372,7 +392,7 @@ describe("Bling", () => { const instance = ReactTestUtils.renderIntoDocument( ); @@ -575,6 +595,39 @@ describe("Bling", () => { ); }); + it("renders slotSize with an array", () => { + const renderer = new ShallowRenderer(); + renderer.render( + + ); + const result = renderer.getRenderOutput(); + expect(result.type).to.equal("div"); + expect(result.props.style).to.eql({ width: 300, height: 250 }); + }); + + it("sets slotSize to 0,0 on foldCheck of 'fluid' or ['fluid']", done => { + const isInViewport = sinon.spy(Bling._adManager, "isInViewport"); + + class Wrapper extends Component { + onSlotRenderEnded = () => { + expect(isInViewport.args[0][1].join()).to.equal([0, 0].join()); + done(); + }; + render() { + return ( + + ); + } + } + + ReactTestUtils.renderIntoDocument(); + }); + it("refreshes ad when refreshable prop changes", done => { let count = 0; @@ -582,13 +635,13 @@ describe("Bling", () => { class Wrapper extends Component { state = { - targeting: {prop: "val"} + targeting: { prop: "val" } }; onSlotRenderEnded = event => { if (count === 0) { expect(event.slot.getTargeting("prop")).to.equal("val"); this.setState({ - targeting: {prop: "val2"} + targeting: { prop: "val2" } }); count++; } else { @@ -597,7 +650,7 @@ describe("Bling", () => { } }; render() { - const {targeting} = this.state; + const { targeting } = this.state; return ( { class Wrapper extends Component { state = { - targeting: {prop: "val"} + targeting: { prop: "val" } }; onSlotRenderEnded = event => { if (count === 0) { expect(event.slot.getTargeting("prop")).to.equal("val"); this.setState({ - targeting: {prop: "val2"} + targeting: { prop: "val2" } }); count++; } else { @@ -634,7 +687,7 @@ describe("Bling", () => { } }; render() { - const {targeting} = this.state; + const { targeting } = this.state; return ( { } }; render() { - const {adUnitPath} = this.state; + const { adUnitPath } = this.state; return ( Date: Tue, 13 Mar 2018 10:47:43 -0700 Subject: [PATCH 3/4] Fix slotOnload to use the correct on event name (#75) --- docs/api/ReactGPT.md | 2 +- src/Bling.js | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/docs/api/ReactGPT.md b/docs/api/ReactGPT.md index c055ed7..c917388 100644 --- a/docs/api/ReactGPT.md +++ b/docs/api/ReactGPT.md @@ -25,7 +25,7 @@ A React component which renders [GPT](https://support.google.com/dfp_sb/answer/1 - `onSlotRenderEnded`(optional) - An optional event handler function for `googletag.events.SlotRenderEndedEvent`. - `onImpressionViewable`(optional) - An optional event handler function for `googletag.events.ImpressionViewableEvent`. - `onSlotVisibilityChanged`(optional) - An optional event handler function for `googletag.events.slotVisibilityChangedEvent`. -- `onSlotLoad`(optional) - An optional event handler function for `googletag.events.SlotOnloadEvent`. +- `onSlotOnload`(optional) - An optional event handler function for `googletag.events.SlotOnloadEvent`. - `renderWhenViewable`(optional) - An optional flag to indicate whether an ad should only render when it's fully in the viewport area. - `viewableThreshold`(optional) - An optional number to indicate how much percentage of an ad area needs to be in a viewable area before rendering. Acceptable range is between `0` and `1`. - `onScriptLoaded`(optional) - An optional call back function to notify when the script is loaded. diff --git a/src/Bling.js b/src/Bling.js index 707527b..18e3776 100644 --- a/src/Bling.js +++ b/src/Bling.js @@ -158,9 +158,9 @@ class Bling extends Component { /** * An optional event handler function for `googletag.events.SlotOnloadEvent`. * - * @property onSlotLoad + * @property onSlotOnload */ - onSlotLoad: PropTypes.func, + onSlotOnload: PropTypes.func, /** * An optional flag to indicate whether an ad should only render when it's fully in the viewport area. * From 8278236156b11ba35619bede3e32219d4c24f41b Mon Sep 17 00:00:00 2001 From: Christian Shum-Harden Date: Wed, 14 Mar 2018 14:45:05 -0700 Subject: [PATCH 4/4] (release) 2.0.1 (#76) --- CHANGELOG.md | 31 +++++++++++++++++++++---------- package.json | 26 ++++++++++++-------------- 2 files changed, 33 insertions(+), 24 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index c268e1b..1380687 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,12 @@ + + +## [2.0.1](https://github.com/nfl/react-gpt/compare/v2.0.0...v2.0.1) (2018-03-13) + +### Code Refactoring + +* Adds `onSlotOnload` event +* Allows `["fluid"]` slotSize as an array + ## [2.0.0](https://github.com/nfl/react-gpt/compare/v1.1.1...v2.0.0) (2018-01-04) @@ -9,10 +18,13 @@ ### Migration notes **< 2.0.0** you may have imported `createManagerTest` like this: + ``` import {createManagerTest} from "react-gpt"; ``` + **>= 2.0.0** you now need to import `createManagerTest` like this: + ``` import {createManagerTest} from "react-gpt/es/utils/createManagerTest"; ``` @@ -33,12 +45,12 @@ import {createManagerTest} from "react-gpt/es/utils/createManagerTest"; ## [1.0.1](https://github.com/nfl/react-gpt/compare/v1.0.0...v1.0.1) (2017-09-19) - ### Bug Fixes * **package.json:** Add es folder to published package ([2aa1a03](https://github.com/nfl/react-gpt/commit/2aa1a03)) + ## [1.0.0](https://github.com/nfl/react-gpt/compare/v0.3.0...v1.0.0) (2017-09-18) ### Features @@ -47,8 +59,8 @@ import {createManagerTest} from "react-gpt/es/utils/createManagerTest"; * **package.json:** Add Yarn ([#38](https://github.com/nfl/react-gpt/issues/38)) ([8b7a570](https://github.com/nfl/react-gpt/commit/8b7a570)) * **package.json:** Export es modules ([#54](https://github.com/nfl/react-gpt/issues/54)) ([2d7a3ec](https://github.com/nfl/react-gpt/commit/2d7a3ec)), closes [#29](https://github.com/nfl/react-gpt/issues/29) - + ## [0.3.0](https://github.com/nfl/react-gpt/compare/v0.2.5...v0.3.0) (2017-09-18) ### Code Refactoring @@ -62,35 +74,34 @@ import {createManagerTest} from "react-gpt/es/utils/createManagerTest"; * Upgrade eslint and introduce prettier ([17c8b89](https://github.com/nfl/react-gpt/commit/17c8b89)) + ## [0.2.5](https://github.com/nfl/react-gpt/compare/v0.2.4...v0.2.5) (2017-07-31) ### Bug Fixes * Add yarn.lock ([b7c7c50](https://github.com/nfl/react-gpt/commit/b7c7c50)) -* Import PropTypes from prop-types package ([34b61be](https://github.com/nfl/react-gpt/commit/34b61be)) +* Import PropTypes from prop-types package ([34b61be](https://github.com/nfl/react-gpt/commit/34b61be)) * Move MockGPT out of distribution files ([775fe26](https://github.com/nfl/react-gpt/commit/775fe26)) * Import ReactTestUtils from test-utils ([75e74f6](https://github.com/nfl/react-gpt/commit/75e74f6)) -## [0.2.4](https://github.com/nfl/react-gpt/compare/v0.2.3...v0.2.4) (2017-03-23) +## [0.2.4](https://github.com/nfl/react-gpt/compare/v0.2.3...v0.2.4) (2017-03-23) ### Bug Fixes -* more gracefully handle adSlot becoming empty object due to AdBlocker ([7f9a989](https://github.com/nfl/react-gpt/commit/7f9a989)) - +* more gracefully handle adSlot becoming empty object due to AdBlocker ([7f9a989](https://github.com/nfl/react-gpt/commit/7f9a989)) -## [0.2.3](https://github.com/nfl/react-gpt/compare/v0.2.2...v0.2.3) (2017-02-21) +## [0.2.3](https://github.com/nfl/react-gpt/compare/v0.2.2...v0.2.3) (2017-02-21) ### Bug Fixes * fix calling the same pubads API on Bling not overriding the previous one ([fc374b6](https://github.com/nfl/react-gpt/commit/fc374b6)) - - + ## [0.2.2](https://github.com/nfl/react-gpt/compare/v0.2.1...v0.2.2) (2016-10-13) ### Code Refactoring @@ -101,4 +112,4 @@ import {createManagerTest} from "react-gpt/es/utils/createManagerTest"; Features: - - Initial release +* Initial release diff --git a/package.json b/package.json index b254f36..d812022 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "react-gpt", - "version": "2.0.0", + "version": "2.0.1", "description": "A react display ad component using Google Publisher Tag", "main": "lib/index.js", "jsnext:main": "es/index.js", @@ -25,14 +25,7 @@ "bugs": { "url": "https://github.com/nfl/react-gpt/issues" }, - "files": [ - "*.md", - "docs", - "es", - "src", - "dist", - "lib" - ], + "files": ["*.md", "docs", "es", "src", "dist", "lib"], "dependencies": { "deep-equal": "^1.0.1", "eventemitter3": "^2.0.2", @@ -107,16 +100,21 @@ "changelog": "conventional-changelog -p angular -i CHANGELOG.md -s", "build": "npm run clean && npm run compile", "build:es": "BABEL_ENV=es babel --copy-files ./src -d es", - "build:umd": "NODE_ENV=development webpack src/index.js dist/react-gpt.js", - "build:umd:min": "NODE_ENV=production webpack -p src/index.js dist/react-gpt.min.js", + "build:umd": + "NODE_ENV=development webpack src/index.js dist/react-gpt.js", + "build:umd:min": + "NODE_ENV=production webpack -p src/index.js dist/react-gpt.min.js", "bundlesize": "npm run build:umd:min && bundlesize", "clean": "rimraf lib coverage dist lib es", "compile": "babel src --out-dir lib", - "examples": "webpack-dev-server --config examples/webpack.config.js --content-base examples/apps --inline", + "examples": + "webpack-dev-server --config examples/webpack.config.js --content-base examples/apps --inline", "lint": "eslint --fix src test examples", - "start": "npm run build && env BABEL_ENV=examples node examples/server/index.js", + "start": + "npm run build && env BABEL_ENV=examples node examples/server/index.js", "pretest": "npm run build", - "prepublish": "npm run build && npm run build:es && npm run build:umd && npm run build:umd:min", + "prepublish": + "npm run build && npm run build:es && npm run build:umd && npm run build:umd:min", "test": "npm run lint && karma start", "update-apilist": "node ./scripts/updateAPIList.js" },