diff --git a/.babelrc b/.babelrc new file mode 100644 index 00000000000..41342ee9d04 --- /dev/null +++ b/.babelrc @@ -0,0 +1,4 @@ +{ + "presets": ["es2015"], + "plugins": ["transform-object-assign"] +} diff --git a/.gitignore b/.gitignore index dc38ee1160a..29039036e21 100644 --- a/.gitignore +++ b/.gitignore @@ -1,54 +1,71 @@ -node_modules/ - - -# Covers JetBrains IDEs: IntelliJ, RubyMine, PhpStorm, AppCode, PyCharm, CLion - -*.iml - -## Directory-based project format: -.idea/ -# if you remove the above rule, at least ignore the following: - -# User-specific stuff: -# .idea/workspace.xml -# .idea/tasks.xml -# .idea/dictionaries - -# Sensitive or high-churn files: -# .idea/dataSources.ids -# .idea/dataSources.xml -# .idea/sqlDataSources.xml -# .idea/dynamic.xml -# .idea/uiDesigner.xml - -# Gradle: -# .idea/gradle.xml -# .idea/libraries - -# Mongo Explorer plugin: -# .idea/mongoSettings.xml - -## File-based project format: -*.ipr -*.iws - -## Plugin-specific files: - -# IntelliJ -/out/ - -# mpeltonen/sbt-idea plugin -.idea_modules/ - -# JIRA plugin -atlassian-ide-plugin.xml - -# Crashlytics plugin (for Android Studio and IntelliJ) -com_crashlytics_export_strings.xml -crashlytics.properties -crashlytics-build.properties -*.swp -*.swo - -#zip files (for releases) -*.zip +# Built Files +node_modules/ +build + +# Test Files +test/app +gpt.html +gpt-each-bidder3.html + +# Dev File + +integrationExamples/gpt/gpt.html +integrationExamples/implementations/ + +# Coverage reports +build/coverage/ + +# Covers JetBrains IDEs: IntelliJ, RubyMine, PhpStorm, AppCode, PyCharm, CLion + +*.iml + +## Directory-based project format: +.idea/ +# if you remove the above rule, at least ignore the following: + +# User-specific stuff: +# .idea/workspace.xml +# .idea/tasks.xml +# .idea/dictionaries + +# Sensitive or high-churn files: +# .idea/dataSources.ids +# .idea/dataSources.xml +# .idea/sqlDataSources.xml +# .idea/dynamic.xml +# .idea/uiDesigner.xml + +# Gradle: +# .idea/gradle.xml +# .idea/libraries + +# Mongo Explorer plugin: +# .idea/mongoSettings.xml + +## File-based project format: +*.ipr +*.iws + +## Plugin-specific files: + +# IntelliJ +/out/ + +# mpeltonen/sbt-idea plugin +.idea_modules/ + +# JIRA plugin +atlassian-ide-plugin.xml + +# Crashlytics plugin (for Android Studio and IntelliJ) +com_crashlytics_export_strings.xml +crashlytics.properties +crashlytics-build.properties +*.swp +*.swo + +#zip files (for releases) +*.zip + +# TypeScript typings +typings/ diff --git a/.jscsrc b/.jscsrc new file mode 100644 index 00000000000..e08e9be9c3d --- /dev/null +++ b/.jscsrc @@ -0,0 +1,11 @@ +{ + "maxErrors": 1000, + "requireTrailingComma": null, + "requireCamelCaseOrUpperCaseIdentifiers": null, + "requireSpacesInAnonymousFunctionExpression": null, + "validateIndentation": 2, + "disallowSpacesInFunctionDeclaration": { + "beforeOpeningRoundBrace": true + }, + "disallowNewlineBeforeBlockStatements": true +} diff --git a/.jshintrc b/.jshintrc new file mode 100644 index 00000000000..98f28c3eb68 --- /dev/null +++ b/.jshintrc @@ -0,0 +1,38 @@ +{ + "bitwise": false, + "browser": true, + "curly": false, + "devel": true, + "eqeqeq": true, + "freeze": true, + "immed": true, + "maxdepth": 5, + "newcap": true, + "noarg": true, + "node": true, + "notypeof": true, + "esnext": true, + "trailing": true, + "undef": true, + "unused": true, + "strict": false, + "scripturl": true, + "globals": { + "before": true, + "after": true, + "exports": true, + "pbjs": true, + "pbjsTestOnly": true, + "assert": false, + "expect": false, + "dump": false, + "describe": false, + "it": false, + "xit": false, + "pkg": false, + "sinon": false, + "beforeEach": false, + "afterEach": false, + "JSON": true + } +} diff --git a/.travis.yml b/.travis.yml index 1ba876fd2c3..f64544d53d8 100644 --- a/.travis.yml +++ b/.travis.yml @@ -1,11 +1,10 @@ -language: node_js - -node_js: - - 0.12 - - 0.11 - -before_install: - - npm install -g gulp - -script: - - gulp build \ No newline at end of file +language: node_js + +node_js: + - "5.1" + +before_install: + - npm install -g gulp + +script: + - gulp build diff --git a/README.md b/README.md index 12eba8789bb..3f754667928 100644 --- a/README.md +++ b/README.md @@ -1,114 +1,200 @@ -Prebid.js -======== - -> Setup and manage header bidding advertising partners without writing code or confusing line items. Prebid.js is open source and free. - -Many SSPs, bidders, and publishers have all contributed to this project. - -Check out the overview and documentation at http://prebid.org. - -No more week-long development. Header bidding is made easy by prebid.js :) - -**Table of Contents** - -- [Prebid.js](#) - - [Usage](#usage) - - [Download the latest released code](#download-the-latest-released-code) - - [Example code](#example-code) - - [API](#api) - - [Contribute](#contribute) - - [Add an Bidder Adapter](#add-an-bidder-adapter) - - [install](#install) - - [Build](#build) - - [Configure](#configure) - - [Run](#run) - - -Usage ----------- -Download the integration example [here](https://github.com/prebid/Prebid.js/blob/master/integrationExamples/gpt/pbjs_example_gpt.html). - -### Download the latest released code ### -[See the releases page here](https://github.com/prebid/Prebid.js/releases) and download a copy. - -### Example code ### - -**Include the prebid.js libraray** -Note that you need to host `prebid.js` locally or on a CDN and update the reference in the code snippet below for `cdn.host.com/prebid.min.js -```javascript -(function() { - var d = document, pbs = d.createElement('script'), pro = d.location.protocol; - pbs.type = 'text/javascript'; - pbs.src = ((pro === 'https:') ? 'https' : 'http') + '://cdn.host.com/prebid.min.js'; - var target = document.getElementsByTagName('head')[0]; - target.insertBefore(pbs, target.firstChild); -})(); -``` - -**Setup ad units** -```javascript -pbjs.que.push(function(){ - var adUnits = [{ - code: '{id}', - sizes: [[300, 250], [300, 600]], - bids: [ - { - bidder: 'appnexus', - params: { - placementId: '{id}' - } - } - ] - }]; - //add the adUnits - pbjs.addAdUnits(adUnits); -}); -``` - -**Request Bids** -```javascript -pbjs.que.push(function(){ - pbjs.requestBids({ - bidsBackHandler: function(bidResponses) { - //do stuff when the bids are back - } - }) -}); -``` - -**See Console Debug Errors during testing** -By default console errors are supressed. To enabled add `?pbjs_debug=true` to the end of the URL for testing. - -API ----------- -Full Developer API reference: - -[Click here to access the API](http://prebid.org/dev-docs/publisher-api-reference.html) - -Contribute ----------- - -### Add an Bidder Adapter ### -Follow the [guide outlined here](http://prebid.org/dev-docs/bidder-adaptor.html) to add an adapter. - -### install ### - $ sudo npm install - -### Build ### - $ gulp build - -### Configure ### -Edit `./integrationExamples/gpt/pbjs_example_gpt.html` - -Change `{id}` values appropriately - -### Run ### - - $ gulp serve - -Navigate to http://localhost:9999/integrationExamples/gpt/pbjs_example_gpt.html to run the example file - -### Unit Test In the Browser ### - -Navigate to http://localhost:9999/test/spec/runner.html to run the test file. - +[![Build Status](https://travis-ci.org/prebid/Prebid.js.svg?branch=master)](https://travis-ci.org/prebid/Prebid.js) +[![Percentage of issues still open](http://isitmaintained.com/badge/open/prebid/Prebid.js.svg)](http://isitmaintained.com/project/prebid/Prebid.js "Percentage of issues still open") +[![Average time to resolve an issue](http://isitmaintained.com/badge/resolution/prebid/Prebid.js.svg)](http://isitmaintained.com/project/prebid/Prebid.js "Average time to resolve an issue") +[![Code Climate](https://codeclimate.com/github/prebid/Prebid.js/badges/gpa.svg)](https://codeclimate.com/github/prebid/Prebid.js) + +Prebid.js +======== + +> Setup and manage header bidding advertising partners without writing code or confusing line items. Prebid.js is open source and free. + +Many SSPs, bidders, and publishers have all contributed to this project. + +Check out the overview and documentation at http://prebid.org. + +No more week-long development. Header bidding is made easy by prebid.js :) + +**Table of Contents** + +- [Prebid.js](#) + - [Usage](#usage) + - [Download the latest released code](#download-the-latest-released-code) + - [Example code](#example-code) + - [API](#api) + - [Contribute](#contribute) + - [Add a Bidder Adapter](#add-a-bidder-adapter) + - [install](#install) + - [Build](#build) + - [Configure](#configure) + - [Run](#run) + + +Usage +---------- +Download the integration example [here](https://github.com/prebid/Prebid.js/blob/master/integrationExamples/gpt/pbjs_example_gpt.html). + +### Download the latest released code ### +[See the releases page here](https://github.com/prebid/Prebid.js/releases) and download a copy. + +### Example code ### + +**Include the prebid.js library** +Note that you need to host `prebid.js` locally or on a CDN and update the reference in the code snippet below for `cdn.host.com/prebid.min.js +```javascript +(function () { + var d = document; + var pbs = d.createElement('script'); + pbs.type = 'text/javascript'; + //replace with your CDN hosted version. HTTPS is strongly recommended. + pbs.src = '//cdn.host.com/prebid.min.js'; + var target = d.getElementsByTagName('script')[0]; + target.parentNode.insertBefore(pbs, target); +})(); +``` + +**Setup ad units** +```javascript +pbjs.que.push(function(){ + var adUnits = [{ + code: '{id}', + sizes: [[300, 250], [300, 600]], + bids: [ + { + bidder: 'appnexus', + params: { + placementId: '{id}' + } + } + ] + }]; + //add the adUnits + pbjs.addAdUnits(adUnits); +}); +``` + +**Request Bids** +```javascript +pbjs.que.push(function(){ + pbjs.requestBids({ + bidsBackHandler: function(bidResponses) { + //do stuff when the bids are back + } + }) +}); +``` + +**See Console Debug Errors during testing** +By default console errors are suppressed. To enabled add `?pbjs_debug=true` to the end of the URL + for testing. + +API +---------- +Full Developer API reference: + +[Click here to access the API](http://prebid.org/dev-docs/publisher-api-reference.html) + +Contribute +---------- +**Note:** You need to have at least `node.js 4.x` or greater installed to be able to run the gulp build commands. + +### Add a Bidder Adapter ### +Follow the [guide outlined here](http://prebid.org/dev-docs/bidder-adaptor.html) to add an adapter. + +### Install ### + $ npm install + +If you experience errors, after a version update, try a fresh install: + + $ rm -rf ./node_modules && npm cache clean && npm install + +### Build ### + $ gulp build + +Runs code quality checks, generates prebid.js files and creates zip archive distributable: + + `./build/dev/prebid.js` Full source code for dev and debug + `./build/dev/prebid.js.map` Source map for dev and debug + `./build/dist/prebid.js` Minified production code + `./prebid.js_.zip` Distributable + +Code quality is defined by `./.jscs` and `./.jshint` files and errors are reported in the +terminal. The build will continue with quality errors, however. If you are contributing code +you can configure your editor with the provided .jscs and .jshint settings. + +### Configure ### +Edit example file `./integrationExamples/gpt/pbjs_example_gpt.html`: + +1. Change `{id}` values appropriately to set up ad units and bidders. + +1. Set path for prebid.js in your example file: + #####Development `pbs.src = './build/dev/prebid.js';` ##### + ```javascript + (function() { + var d = document, pbs = d.createElement('script'), pro = d.location.protocol; + pbs.type = 'text/javascript'; + pbs.src = ((pro === 'https:') ? 'https' : 'http') + ':./build/dev/prebid.js'; + var target = document.getElementsByTagName('head')[0]; + target.insertBefore(pbs, target.firstChild); + })(); + ``` + #####Test/Deploy (default) `pbs.src = './build/dist/prebid.js';`##### + ```javascript + (function() { + var d = document, pbs = d.createElement('script'), pro = d.location.protocol; + pbs.type = 'text/javascript'; + pbs.src = ((pro === 'https:') ? 'https' : 'http') + './build/dist/prebid.js'; + var target = document.getElementsByTagName('head')[0]; + target.insertBefore(pbs, target.firstChild); + })(); + ``` +1. (optional optimization) Edit `./package.json` to set the adapters you want to build with: + + ```json + "adapters": [ + "adform", + "admedia", + "aol", + "appnexus", + "indexExchange", + "openx", + "pubmatic", + "pulsepoint", + "rubicon", + "rubiconLegacy", + "sovrn", + "springserve", + "yieldbot" + ] + ``` + +### Run ### + + $ gulp serve + +This runs code quality checks, generates prebid files and starts a webserver at +`http://localhost:9999` serving from project root. Navigate to your example implementation to test, +and if your prebid.js file is sourced from the `./build/dev` directory you will have sourcemaps +available in browser developer tools. + +Navigate to `http://localhost:9999/integrationExamples/gpt/pbjs_example_gpt.html` to run the +example file. + +Navigate to `http://localhost:9999/build/coverage/karma_html/report` to view a test coverage report. + +A watch is also in place that will run continuous tests in the terminal as you edit code and +tests. + +### Unit Test In the Browser ### + $ gulp test --watch + +This will run tests and keep the Karma test browser open. If your prebid.js file is sourced from +the build/dev directory you will also have sourcemaps available when viewing browser developer +tools. Access the Karma debug page at: +`http://localhost:9876/debug.html` +View console for test results and developer tools to set breakpoints in source code. + +Detailed code coverage reporting can be generated explicitly with `$ gulp test --coverage` and +results found in `./build/coverage`. + +### Supported Browsers ### +Prebid.js is supported on IE9+ and modern browsers. diff --git a/bower.json b/bower.json index ec994aee553..6a8938ae686 100644 --- a/bower.json +++ b/bower.json @@ -1,26 +1,26 @@ -{ - "name": "prebid-js", - "version": "0.4.1", - "authors": [ - "Matt Kendall", - "Paul Yang" - ], - "description": "Setup and manage header bidding advertising partners without writing code or confusing line items. Prebid.js is open source and free", - "main": "dist/prebid.min.js", - "keywords": [ - "Prebid" - ], - "repository": { - "type": "git", - "url": "git://github.com/prebid/Prebid.js.git" - }, - "license": "http://www.apache.org/licenses/LICENSE-2.0", - "homepage": "http://prebid.org/", - "ignore": [ - "**/.*", - "node_modules", - "bower_components", - "test", - "tests" - ] -} +{ + "name": "prebid-js", + "version": "0.6.0", + "authors": [ + "Matt Kendall", + "Paul Yang" + ], + "description": "Setup and manage header bidding advertising partners without writing code or confusing line items. Prebid.js is open source and free", + "main": "build/dist/prebid.js", + "keywords": [ + "Prebid" + ], + "repository": { + "type": "git", + "url": "git://github.com/prebid/Prebid.js.git" + }, + "license": "http://www.apache.org/licenses/LICENSE-2.0", + "homepage": "http://prebid.org/", + "ignore": [ + "**/.*", + "node_modules", + "bower_components", + "test", + "tests" + ] +} diff --git a/gulpHelpers.js b/gulpHelpers.js new file mode 100644 index 00000000000..62e86a31f4f --- /dev/null +++ b/gulpHelpers.js @@ -0,0 +1,16 @@ +module.exports = { + parseBrowserArgs: function (argv) { + return (argv.browsers) ? argv.browsers.split(',') : []; + }, + + toCapitalCase: function (str) { + return str.charAt(0).toUpperCase() + str.slice(1); + }, + + jsonifyHTML: function (str) { + console.log(arguments); + return str.replace(/\n/g, '') + .replace(/<\//g, '<\\/') + .replace(/\/>/g, '\\/>'); + } +}; diff --git a/gulpfile.js b/gulpfile.js index 141cac6d585..06d83708ca9 100644 --- a/gulpfile.js +++ b/gulpfile.js @@ -1,213 +1,178 @@ -var gulp = require('gulp'); -var uglify = require('gulp-uglify'); -var rename = require('gulp-rename'); -var jshint = require('gulp-jshint'); -var jscs = require('gulp-jscs'); -var header = require('gulp-header'); -var del = require('del'); -var ecstatic = require('ecstatic'); -var gulpBrowserify = require('gulp-browserify'); -var gutil = require("gulp-util"); -var gulpJsdoc2md = require("gulp-jsdoc-to-markdown"); -var concat = require("gulp-concat"); -var zip = require('gulp-zip'); -var mocha = require('gulp-mocha'); -var preprocessify = require('preprocessify'); -/** for automated unit testing */ -var browserSync = require("browser-sync"), - browserify = require("browserify"), - source = require("vinyl-source-stream"), - mochaPhantomJS = require("gulp-mocha-phantomjs"); - - -var releaseDir = './dist/'; -var csaSrcLocation = './src/prebid.js'; - -var pkg = require('./package.json'); - -var dateString = 'Updated : ' + (new Date()).toISOString().substring(0, 10); -var packageNameVersion = pkg.name + '_' + pkg.version; - -var banner = '/* <%= pkg.name %> v<%= pkg.version %> \n' + dateString + ' */\n'; - -//basic/quick tests go here, to be run on every build -gulp.task('runBasicTests', function() { - return gulp.src('test/*', {read: false}) - .pipe(mocha()); -}); - - -gulp.task('jscs', function() { - return gulp.src(csaSrcLocation) - .pipe(jscs({ - 'configPath': 'styleRules.jscs.json' - })); - - -}); - -//run code quality checks here -gulp.task('jshint', function() { - - gulp.src(['./src/*.js', './src/adapters/*.js']) - .pipe(jshint({ - 'bitwise': 'true', - 'curly': 'true', - 'scripturl': 'false', - //'enforceall':'false', - //since we are never orriding Prootype (we control all this code) - //I am ok letting this be false in the name of faster code execution - 'forin': false, - 'eqeqeq': true, - //'es3':true, - //'es5':true, - 'freeze': true, - 'futurehostile': true, - 'latedef': true, - 'maxerr': '1000', - 'noarg': true, - 'nocomma': true, - 'nonbsp': true, - 'nonew': true, - 'notypeof': true, - //excessive parens are ok as long as they increase code readability - //and help to prevent errors, especially when helping to provide - //structure to long conditonal statements - 'singleGroups': false, - 'undef': true, - 'unused': true, - 'globals': { - 'require': false, - 'module' : true, - 'exports' : true, - 'pbjs' : true, - 'googletag': true, - 'ActiveXObject': true - }, - 'browser': true, - 'devel': true - - })) - .pipe(jshint.reporter('jshint-stylish')) - .pipe(jshint.reporter('fail')); - -}); - -gulp.task('clean-dist', function(cb) { - del([releaseDir + '']); - cb(); - -}); - -gulp.task('codeQuality', ['jshint', 'jscs'], function() {}); - - -gulp.task('default', ['build'], function() {}); - -gulp.task('serve', ['build-dev', 'watch', 'browser-sync'], function () { - var port = 9999; - require('http').createServer(ecstatic({ - root: __dirname - })).listen(port); - console.log('Server started at http://localhost:' + port + '/'); -}); - -gulp.task('build-dev', ['jscs', 'clean-dist', 'browserify', 'unit-tests'], function () { - gulp.src(['src/prebid.js']) - .pipe(gulpBrowserify({ - debug: false - })) - .pipe(header(banner, { - pkg: pkg - })) - .pipe(gulp.dest(releaseDir)); - -}); - -gulp.task('build-minify', ['clean-dist', 'quality'], function(cb){ - - gulp.src(['src/prebid.js']) - .pipe(gulpBrowserify({ - transform : preprocessify({ NODE_ENV: 'production'}) - } - - )) - //unminified version: - .pipe(header(banner, {pkg: pkg})) - .pipe(gulp.dest(releaseDir)) - //minified version - .pipe(uglify()) - .pipe(header(banner, {pkg: pkg})) - .pipe(rename({ - basename: 'prebid.min', - extname: '.js' - })) - .pipe(gulp.dest(releaseDir)); - //notify that release is ready - cb(); -}); - -gulp.task('build', ['clean-dist', 'quality', 'build-minify', 'zip']); - -gulp.task('quality', ['jscs'], function(cb){ - cb(); -}); - -gulp.task('watch', function () { - gulp.watch(['src/**/*.js'], ['build-dev']); - //gulp.watch(["test/tests.js", "src/*"], ["browserify", "unit-tests"]); -}); - - -gulp.task('clean-docs', function(){ - del(['docs']); -}); - -gulp.task("docs", ['clean-docs'], function() { - return gulp.src("src/prebid.js") - .pipe(concat("readme.md")) - .pipe(gulpJsdoc2md()) - .on("error", function(err){ - gutil.log("jsdoc2md failed:", err.message); - }) - .pipe(gulp.dest("docs")); -}); - -//zip up for release -gulp.task('zip', ['quality', 'clean-dist', 'build-minify'], function () { - return gulp.src(['dist/*', 'integrationExamples/gpt/*']) - .pipe(zip(packageNameVersion + '.zip')) - .pipe(gulp.dest('./')); -}); - -gulp.task("browser-sync", function () { - "use strict"; - browserSync({ - server: { - //serve tests and the root as base dirs - baseDir: ["./test/", "./"], - //make tests.html the index file - index: "automatedRunnner.html" - } - }); -}); - -//see http://fettblog.eu/gulp-browserify-multiple-bundles/ -gulp.task("browserify", function() { - "use strict"; - return browserify("./test/test.js") - .bundle() - .on("error", function (err) { - console.log(err.toString()); - this.emit("end"); - }) - .pipe(source("tests-browserify.js")) - .pipe(gulp.dest("test/")) - .pipe(browserSync.reload({stream:true})); -}); - -gulp.task("unit-tests", function () { - "use strict"; - return gulp.src("./test/automatedRunnner.html") - .pipe(mochaPhantomJS()); -}); +'use strict'; + +var argv = require('yargs').argv; +var gulp = require('gulp'); +var gutil = require('gulp-util'); +var connect = require('gulp-connect'); +var webpack = require('webpack-stream'); +var uglify = require('gulp-uglify'); +var jshint = require('gulp-jshint'); +var clean = require('gulp-clean'); +var karma = require('gulp-karma'); +var opens = require('open'); +var webpackConfig = require('./webpack.conf.js'); +var helpers = require('./gulpHelpers'); +var del = require('del'); +var gulpJsdoc2md = require('gulp-jsdoc-to-markdown'); +var concat = require('gulp-concat'); +var jscs = require('gulp-jscs'); +var header = require('gulp-header'); +var zip = require('gulp-zip'); +var replace = require('gulp-replace'); + +var CI_MODE = process.env.NODE_ENV === 'ci'; +var prebid = require('./package.json'); +var dateString = 'Updated : ' + (new Date()).toISOString().substring(0, 10); +var packageNameVersion = prebid.name + '_' + prebid.version; +var banner = '/* <%= prebid.name %> v<%= prebid.version %>\n' + dateString + ' */\n'; + +// Tasks +gulp.task('default', ['clean', 'quality', 'webpack']); + +gulp.task('serve', ['clean', 'quality', 'devpack', 'webpack', 'watch', 'test']); + +gulp.task('build', ['clean', 'quality', 'webpack', 'devpack', 'zip']); + +gulp.task('clean', function () { + return gulp.src(['build'], { + read: false + }) + .pipe(clean()); +}); + +gulp.task('devpack', function () { + webpackConfig.devtool = 'source-map'; + return gulp.src(['src/prebid.js']) + .pipe(webpack(webpackConfig)) + .pipe(replace('$prebid.version$', prebid.version)) + .pipe(gulp.dest('build/dev')) + .pipe(connect.reload()); +}); + +gulp.task('webpack', function () { + + // change output filename if argument --tag given + if (argv.tag && argv.tag.length) { + webpackConfig.output.filename = 'prebid.' + argv.tag + '.js'; + } + + webpackConfig.devtool = null; + + return gulp.src(['src/prebid.js']) + .pipe(webpack(webpackConfig)) + .pipe(replace('$prebid.version$', prebid.version)) + .pipe(uglify()) + .pipe(header(banner, { prebid: prebid })) + .pipe(gulp.dest('build/dist')) + .pipe(connect.reload()); +}); + +//zip up for release +gulp.task('zip', ['jscs', 'clean', 'webpack'], function () { + return gulp.src(['build/dist/*', 'integrationExamples/gpt/*']) + .pipe(zip(packageNameVersion + '.zip')) + .pipe(gulp.dest('./')); +}); + +// Karma Continuous Testing +// Pass your browsers by using --browsers=chrome,firefox,ie9 +// Run CI by passing --watch +gulp.task('test', function () { + var defaultBrowsers = CI_MODE ? ['PhantomJS'] : ['Chrome']; + var browserArgs = helpers.parseBrowserArgs(argv).map(helpers.toCapitalCase); + + if (argv.browserstack) { + browserArgs = [ + 'bs_ie_13_windows_10', + 'bs_ie_12_windows_10', + 'bs_ie_11_windows_10', + 'bs_firefox_46_windows_10', + 'bs_chrome_51_windows_10', + 'bs_ie_11_windows_8.1', + 'bs_firefox_46_windows_8.1', + 'bs_chrome_51_windows_8.1', + 'bs_ie_10_windows_8', + 'bs_firefox_46_windows_8', + 'bs_chrome_51_windows_8', + 'bs_ie_11_windows_7', + 'bs_ie_10_windows_7', + 'bs_ie_9_windows_7', + 'bs_firefox_46_windows_7', + 'bs_chrome_51_windows_7', + 'bs_safari_9.1_mac_elcapitan', + 'bs_firefox_46_mac_elcapitan', + 'bs_chrome_51_mac_elcapitan', + 'bs_safari_8_mac_yosemite', + 'bs_firefox_46_mac_yosemite', + 'bs_chrome_51_mac_yosemite', + 'bs_safari_7.1_mac_mavericks', + 'bs_safari_6.2_mac_mavericks', + 'bs_firefox_46_mac_mavericks', + 'bs_chrome_49_mac_mavericks' + ]; + } + + return gulp.src('lookAtKarmaConfJS') + .pipe(karma({ + browsers: (browserArgs.length > 0) ? browserArgs : defaultBrowsers, + configFile: 'karma.conf.js', + action: (argv.watch) ? 'watch' : 'run' + })); +}); + +// Small task to load coverage reports in the browser +gulp.task('coverage', function (done) { + var coveragePort = 1999; + + connect.server({ + port: 1999, + root: 'build/coverage', + livereload: false + }); + opens('http://localhost:' + coveragePort + '/coverage/'); + done(); +}); + +// Watch Task with Live Reload +gulp.task('watch', function () { + + gulp.watch(['test/spec/**/*.js'], ['quality', 'webpack', 'devpack', 'test']); + gulp.watch(['integrationExamples/gpt/*.html'], ['test']); + gulp.watch(['src/**/*.js'], ['quality', 'webpack', 'devpack', 'test']); + connect.server({ + port: 9999, + root: './', + livereload: true + }); +}); + +gulp.task('quality', ['hint', 'jscs']); + +gulp.task('hint', function () { + return gulp.src('src/**/*.js') + .pipe(jshint('.jshintrc')) + .pipe(jshint.reporter('default')); +}); + +gulp.task('jscs', function () { + return gulp.src('src/**/*.js') + .pipe(jscs({ + configPath: '.jscsrc' + })) + .pipe(jscs.reporter()); +}); + +gulp.task('clean-docs', function () { + del(['docs']); +}); + +gulp.task('docs', ['clean-docs'], function () { + return gulp.src('src/prebid.js') + .pipe(concat('readme.md')) + .pipe(gulpJsdoc2md()) + .on('error', function (err) { + gutil.log('jsdoc2md failed:', err.message); + }) + .pipe(gulp.dest('docs')); +}); + diff --git a/integrationExamples/gpt/creative_rendering.html b/integrationExamples/gpt/creative_rendering.html index 6379b78bfc1..65931b4e9b7 100644 --- a/integrationExamples/gpt/creative_rendering.html +++ b/integrationExamples/gpt/creative_rendering.html @@ -1,3 +1,20 @@ - + + + + + diff --git a/integrationExamples/gpt/gpt_aliasingBidder.html b/integrationExamples/gpt/gpt_aliasingBidder.html index 4d6c2833a46..a22822aa049 100644 --- a/integrationExamples/gpt/gpt_aliasingBidder.html +++ b/integrationExamples/gpt/gpt_aliasingBidder.html @@ -1,184 +1,192 @@ - - - - - - - - - - - - - -

Prebid.js Test

- -
- -
- - -
- -
- - - - - - - + + + + + + + + + + + + + +

Prebid.js Test

+ +
+ +
+ + +
+ +
+ + + + + + + diff --git a/integrationExamples/gpt/pbjs_example_gpt.html b/integrationExamples/gpt/pbjs_example_gpt.html index 24196e5aa51..2a8bbb1c150 100644 --- a/integrationExamples/gpt/pbjs_example_gpt.html +++ b/integrationExamples/gpt/pbjs_example_gpt.html @@ -1,32 +1,31 @@ + - - +Prebid.js integration example - - - + - - -

Prebid.js Test

- -
- -
+

Prebid.js Test

+
+ +
-
- -
+
+ +
- + + diff --git a/integrationExamples/gpt/pbjs_partial_refresh_gpt.html b/integrationExamples/gpt/pbjs_partial_refresh_gpt.html index 8445d0cf923..a2fa0e3579f 100644 --- a/integrationExamples/gpt/pbjs_partial_refresh_gpt.html +++ b/integrationExamples/gpt/pbjs_partial_refresh_gpt.html @@ -1,302 +1,296 @@ - - - - - - - - - - - - - -

Prebid.js Test

- -
Div-1, 300x250 or 300x600
- - - -
- -
- - -
Div-2, 728x90 or 970x90
- - - - -
- -
- - - - - + + + + + + + + + + + + + +

Prebid.js Test

+ +
Div-1, 300x250 or 300x600
+ + + +
+ +
+ + +
Div-2, 728x90 or 970x90
+ + + + +
+ +
+ + + + + diff --git a/karma.conf.js b/karma.conf.js new file mode 100644 index 00000000000..34b069f4001 --- /dev/null +++ b/karma.conf.js @@ -0,0 +1,332 @@ +// Karma configuration +// Generated on Thu Aug 07 2014 09:45:28 GMT-0700 (PDT) +var webpackConfig = require('./webpack.conf'); +webpackConfig.module.postLoaders = [ + { + test: /\.js$/, + exclude: /(node_modules)|(test)|(integrationExamples)|(build)/, + loader: 'istanbul-instrumenter' + } +]; + +var CI_MODE = process.env.NODE_ENV === 'ci'; + +module.exports = function (config) { + config.set({ + + // base path that will be used to resolve all patterns (eg. files, exclude) + basePath: './', + + // BrowserStack Config + browserStack: { + username: process.env.BROWSERSTACK_USERNAME, + accessKey: process.env.BROWSERSTACK_KEY + }, + + // define browsers + customLaunchers: { + bs_ie_13_windows_10: { + base: 'BrowserStack', + os_version: '10', + browser: 'edge', + browser_version: '13.0', + device: null, + os: 'Windows' + }, + bs_ie_12_windows_10: { + base: 'BrowserStack', + os_version: '10', + browser: 'ie', + browser_version: '12.0', + device: null, + os: 'Windows' + }, + bs_ie_11_windows_10: { + base: 'BrowserStack', + os_version: '10', + browser: 'ie', + browser_version: '11.0', + device: null, + os: 'Windows' + }, + bs_firefox_46_windows_10: { + base: 'BrowserStack', + os_version: '10', + browser: 'firefox', + browser_version: '46.0', + device: null, + os: 'Windows' + }, + bs_chrome_51_windows_10: { + base: 'BrowserStack', + os_version: '10', + browser: 'chrome', + browser_version: '51.0', + device: null, + os: 'Windows' + }, + 'bs_ie_11_windows_8.1': { + base: 'BrowserStack', + os_version: '8.1', + browser: 'ie', + browser_version: '11.0', + device: null, + os: 'Windows' + }, + 'bs_firefox_46_windows_8.1': { + base: 'BrowserStack', + os_version: '8.1', + browser: 'firefox', + browser_version: '46.0', + device: null, + os: 'Windows' + }, + 'bs_chrome_51_windows_8.1': { + base: 'BrowserStack', + os_version: '8.1', + browser: 'chrome', + browser_version: '51.0', + device: null, + os: 'Windows' + }, + bs_ie_10_windows_8: { + base: 'BrowserStack', + os_version: '8', + browser: 'ie', + browser_version: '10.0', + device: null, + os: 'Windows' + }, + bs_firefox_46_windows_8: { + base: 'BrowserStack', + os_version: '8', + browser: 'firefox', + browser_version: '46.0', + device: null, + os: 'Windows' + }, + bs_chrome_51_windows_8: { + base: 'BrowserStack', + os_version: '8', + browser: 'chrome', + browser_version: '51.0', + device: null, + os: 'Windows' + }, + bs_ie_11_windows_7: { + base: 'BrowserStack', + os_version: '7', + browser: 'ie', + browser_version: '11.0', + device: null, + os: 'Windows' + }, + bs_ie_10_windows_7: { + base: 'BrowserStack', + os_version: '7', + browser: 'ie', + browser_version: '10.0', + device: null, + os: 'Windows' + }, + bs_ie_9_windows_7: { + base: 'BrowserStack', + os_version: '7', + browser: 'ie', + browser_version: '9.0', + device: null, + os: 'Windows' + }, + bs_firefox_46_windows_7: { + base: 'BrowserStack', + os_version: '7', + browser: 'firefox', + browser_version: '46.0', + device: null, + os: 'Windows' + }, + bs_chrome_51_windows_7: { + base: 'BrowserStack', + os_version: '7', + browser: 'chrome', + browser_version: '51.0', + device: null, + os: 'Windows' + }, + 'bs_safari_9.1_mac_elcapitan': { + base: 'BrowserStack', + os_version: 'El Capitan', + browser: 'safari', + browser_version: '9.1', + device: null, + os: 'OS X' + }, + bs_firefox_46_mac_elcapitan: { + base: 'BrowserStack', + os_version: 'El Capitan', + browser: 'firefox', + browser_version: '46.0', + device: null, + os: 'OS X' + }, + bs_chrome_51_mac_elcapitan: { + base: 'BrowserStack', + os_version: 'El Capitan', + browser: 'chrome', + browser_version: '51.0', + device: null, + os: 'OS X' + }, + bs_safari_8_mac_yosemite: { + base: 'BrowserStack', + os_version: 'Yosemite', + browser: 'safari', + browser_version: '8.0', + device: null, + os: 'OS X' + }, + bs_firefox_46_mac_yosemite: { + base: 'BrowserStack', + os_version: 'Yosemite', + browser: 'firefox', + browser_version: '46.0', + device: null, + os: 'OS X' + }, + bs_chrome_51_mac_yosemite: { + base: 'BrowserStack', + os_version: 'Yosemite', + browser: 'chrome', + browser_version: '51.0', + device: null, + os: 'OS X' + }, + 'bs_safari_7.1_mac_mavericks': { + base: 'BrowserStack', + os_version: 'Mavericks', + browser: 'safari', + browser_version: '7.1', + device: null, + os: 'OS X' + }, + 'bs_safari_6.2_mac_mavericks': { + base: 'BrowserStack', + os_version: 'Mavericks', + browser: 'safari', + browser_version: '6.2', + device: null, + os: 'OS X' + }, + bs_firefox_46_mac_mavericks: { + base: 'BrowserStack', + os_version: 'Mavericks', + browser: 'firefox', + browser_version: '46.0', + device: null, + os: 'OS X' + }, + bs_chrome_49_mac_mavericks: { + base: 'BrowserStack', + os_version: 'Mavericks', + browser: 'chrome', + browser_version: '49.0', + device: null, + os: 'OS X' + } + }, + + // frameworks to use + // available frameworks: https://npmjs.org/browse/keyword/karma-adapter + frameworks: ['es5-shim', 'mocha', 'expect', 'sinon'], + + // list of files / patterns to load in the browser + files: [ + 'test/**/*_spec.js' + ], + + // list of files to exclude + exclude: [], + + // preprocess matching files before serving them to the browser + // available preprocessors: https://npmjs.org/browse/keyword/karma-preprocessor + preprocessors: { + 'test/**/*_spec.js': ['webpack'], + 'src/**/*.js': ['webpack', 'coverage'] + }, + + // WebPack Related + webpack: webpackConfig, + webpackMiddleware: { + noInfo: true + }, + + // test results reporter to use + // possible values: 'dots', 'progress' + // available reporters: https://npmjs.org/browse/keyword/karma-reporter + reporters: CI_MODE ? ['junit', 'coverage'] : ['progress', 'html', 'nyan', 'coverage'], + + // junit reporter config + junitReporter: { + outputDir: 'test' + }, + + // optionally, configure the reporter + coverageReporter: { + reporters: [ + { type: 'html', dir: './build/coverage/' }, + { type: 'text', dir: './build/coverage/' }, + { type: 'lcov', dir: './build/coverage/lcov', subdir: '.' } + ] + }, + + htmlReporter: { + outputDir: 'build/coverage/karma_html', // where to put the reports + urlFriendlyName: true, // simply replaces spaces with _ for files/dirs + reportName: 'report' // report summary filename; browser info by default + }, + + // web server port + port: 9876, + + // enable / disable colors in the output (reporters and logs) + colors: true, + + // level of logging + // possible values: config.LOG_DISABLE || config.LOG_ERROR || config.LOG_WARN || config.LOG_INFO || config.LOG_DEBUG + logLevel: config.LOG_INFO, + + // enable / disable watching file and executing tests whenever any file changes + autoWatch: true, + + // start these browsers + // NOTE: these get defined again in gulpfile.js for the gulp tasks + // available browser launchers: https://npmjs.org/browse/keyword/karma-launcher + browsers: ['Chrome', 'Firefox'], + + // Continuous Integration mode + // if true, Karma captures browsers, runs the tests and exits + singleRun: false, + + plugins: [ + 'karma-browserstack-launcher', + 'karma-phantomjs-launcher', + 'karma-nyan-reporter', + 'karma-coverage', + 'karma-es5-shim', + 'karma-mocha', + 'karma-expect', + 'karma-sinon', + 'karma-webpack', + 'karma-junit-reporter', + 'karma-html-reporter', + 'karma-chrome-launcher', + 'karma-sauce-launcher', + 'karma-firefox-launcher', + 'karma-opera-launcher', + 'karma-safari-launcher', + 'karma-script-launcher', + 'karma-requirejs', + 'karma-ie-launcher' + ] + }); +}; diff --git a/loaders/adapterLoader.js b/loaders/adapterLoader.js new file mode 100644 index 00000000000..097301d0ffe --- /dev/null +++ b/loaders/adapterLoader.js @@ -0,0 +1,117 @@ +/** adapterLoader + * Webpack loader to insert dynamic javascript into `./src/adaptermanager.js` + * This is used in `./webpack.conf.js` + * */ + +'use strict'; + +const fs = require('fs'); +const blockLoader = require('block-loader'); +const adapters = require('../package.json').adapters; + +const files = fs.readdirSync('src/adapters').map((file) => file.replace(/\.[^/.]+$/, '')); +const adapterNames = adapters.map(getNames).filter(getUniques); +const aliases = adapters.filter(getAliases); + +var options = { + start: '/** INSERT ADAPTERS - DO NOT EDIT OR REMOVE */', + end: '/** END INSERT ADAPTERS */', + process: insertAdapters +}; + +/** + * Returns a block of javascript statements to load adapter modules, register the adapters and + * set adapter aliases + * @returns {*} + */ +function insertAdapters() { + + if (!adapters) { + console.log('Prebid Warning: adapters config not found in package.json, no adapters will' + + ' be loaded'); + return ''; + } + + const inserts = adapterNames.map(name => { + if (files.includes(name)) { + return name; + } else { + console.log(`Prebid Warning: no adapter found for ${name}, continuing.`); + } + }); + + if (!inserts.length) { + console.log('Prebid Warning: no matching adapters found for config, no adapters will be' + + ' loaded.'); + return ''; + } + + return inserts.map(name => { + if (name === 'appnexusAst') { + return `import { AppnexusAst } from './adapters/appnexusAst'; + exports.registerBidAdapter(new AppnexusAst('appnexus'), 'appnexus');\n`; + } else { + return `var ${adapterName(name)} = require('./adapters/${name}.js'); + exports.registerBidAdapter(new ${adapterName(name)}${useCreateNew(name)}(), '${name}');\n`; + } + }) + .concat(aliases.map(adapter => { + const name = Object.keys(adapter)[0]; + return `exports.aliasBidAdapter('${name}','${adapter[name].alias}');\n`; + })) + .join(''); +} + +/** + * Derive the variable name to use for the adapter + * @param adapter + * @returns {string} + */ +function adapterName(adapter) { + const result = adapter.split(''); + return result[0].toUpperCase() + result.join('').substr(1) + 'Adapter'; +} + +/** + * Some adapters export an object with a `createNew` constructor so accommodate this pattern + * @param adapter + * @returns {string} + */ +function useCreateNew(adapter) { + return adapter === 'appnexus' ? '.createNew' : ''; +} + +/** + * Filter an array to return unique values + * @param value current array element value + * @param index current array element index + * @param self current array + * @returns {boolean} if true the current array element is returned + * + * http://stackoverflow.com/questions/1960473/unique-values-in-an-array + */ +function getUniques(value, index, self) { + return self.indexOf(value) === index; +} + +/** + * Filter to derive the adapter name from array elements as strings or objects + * @param adapter + * @returns {*} + */ +function getNames(adapter) { + // if `length` then `adapter` is a string, otherwise an object + return adapter.length ? adapter : Object.keys(adapter)[0]; +} + +/** + * Return adapter objects that have an alias field + * @param adapter + * @returns {*} + */ +function getAliases(adapter) { + const name = Object.keys(adapter)[0]; + return adapter && name && adapter[name].alias; +} + +module.exports = blockLoader(options); diff --git a/package.json b/package.json index 029abcc693b..3bdfcdd4933 100644 --- a/package.json +++ b/package.json @@ -1,40 +1,123 @@ -{ - "name": "prebid.js", - "version": "0.5.0", - "description": "Header Bidding Management Library", - "main": "prebid.js", - "scripts": {}, - "author": [], - "license": "Apache-2.0", - "devDependencies": { - "browser-sync": "^2.11.0", - "browserify": "^12.0.1", - "del": "^1.1.1", - "ecstatic": "^0.8.0", - "gulp": "^3.9.0", - "gulp-beautify": "^1.1.2", - "gulp-browserify": "^0.5.1", - "gulp-concat": "^2.6.0", - "gulp-header": "^1.2.2", - "gulp-jscs": "^1.4.0", - "gulp-jsdoc": "^0.1.4", - "gulp-jsdoc-to-markdown": "^1.1.1", - "gulp-jshint": "^1.9.2", - "gulp-mocha": "^2.1.3", - "gulp-mocha-phantomjs": "^0.10.1", - "gulp-notify": "^2.2.0", - "gulp-rename": "^1.2.2", - "gulp-sourcemaps": "^1.5.0", - "gulp-strip-comments": "^1.0.1", - "gulp-uglify": "^1.1.0", - "gulp-util": "^3.0.6", - "gulp-zip": "^3.0.2", - "jsdoc-to-markdown": "^1.1.1", - "jshint-stylish": "^2.0.1", - "mocha": "^2.3.4", - "mocha-phantomjs": "^4.0.2", - "preprocessify": "0.0.6", - "vinyl-source-stream": "^1.1.0" - }, - "dependencies": {} -} +{ + "name": "prebid.js", + "version": "0.9.2", + "description": "Header Bidding Management Library", + "main": "src/prebid.js", + "scripts": { + "test": "gulp test" + }, + "repository": { + "type": "git", + "url": "https://github.com/prebid/Prebid.js.git" + }, + "adapters": [ + "aardvark", + "adequant", + "adform", + "admedia", + "aol", + "appnexus", + "indexExchange", + "kruxlink", + "openx", + "pubmatic", + "pulsepoint", + "rubicon", + "sekindo", + "sonobi", + "sovrn", + "springserve", + "triplelift", + "yieldbot", + "nginad", + "brightcom", + "wideorbit", + { + "appnexus": { + "alias": "brealtime" + } + }, + { + "appnexus": { + "alias": "pagescience" + } + } + ], + "author": "the prebid.js contributors", + "license": "Apache-2.0", + "devDependencies": { + "babel-core": "^6.5.2", + "babel-loader": "^6.2.3", + "babel-plugin-transform-object-assign": "^6.8.0", + "babel-preset-es2015": "^6.5.0", + "block-loader": "^2.1.0", + "chai": "^3.3.0", + "chai-as-promised": "^5.1.0", + "clone": "^0.1.17", + "del": "^2.2.0", + "es5-shim": "^4.5.2", + "eslint": "^1.9.0", + "express": "^4.10.2", + "fuzzyset.js": "0.0.1", + "gulp": "^3.8.7", + "gulp-babel": "^6.1.2", + "gulp-clean": "^0.3.1", + "gulp-concat": "^2.6.0", + "gulp-connect": "^2.0.6", + "gulp-header": "^1.7.1", + "gulp-jscs": "^3.0.2", + "gulp-jsdoc-to-markdown": "^1.2.1", + "gulp-jshint": "^1.8.4", + "gulp-karma": "0.0.4", + "gulp-rename": "^1.2.0", + "gulp-replace": "^0.4.0", + "gulp-uglify": "^0.3.1", + "gulp-util": "^3.0.0", + "gulp-webdriver": "^1.0.1", + "gulp-zip": "^3.1.0", + "istanbul": "^0.3.2", + "istanbul-instrumenter-loader": "^0.1.2", + "jquery": "1.11.1", + "json-loader": "^0.5.1", + "karma": "^0.13.2", + "karma-babel-preprocessor": "^6.0.1", + "karma-browserstack-launcher": "^1.0.1", + "karma-chai": "^0.1.0", + "karma-chrome-launcher": "^0.2.0", + "karma-coverage": "^0.2.6", + "karma-es5-shim": "https://github.com/pokehanai/karma-es5-shim/archive/v2.1.0.tar.gz", + "karma-expect": "^1.1.0", + "karma-firefox-launcher": "^0.1.3", + "karma-html-reporter": "^0.2.7", + "karma-ie-launcher": "^0.1.5", + "karma-junit-reporter": "^0.3.8", + "karma-mocha": "^0.2.0", + "karma-nyan-reporter": "0.2.2", + "karma-opera-launcher": "^0.1.0", + "karma-phantomjs-launcher": "^0.2.1", + "karma-requirejs": "^0.2.2", + "karma-safari-launcher": "^0.1.1", + "karma-sauce-launcher": "^0.2.10", + "karma-script-launcher": "^0.1.0", + "karma-sinon": "^1.0.4", + "karma-webpack": "^1.5.1", + "localtunnel": "^1.3.0", + "lodash": "^2.4.1", + "mocha": "^1.21.4", + "open": "0.0.5", + "phantomjs": "^1.9.18", + "querystringify": "0.0.3", + "raw-loader": "^0.5.1", + "redis": "^0.12.1", + "requirejs": "^2.1.20", + "run-sequence": "^1.1.4", + "sinon": "^1.12.1", + "string-replace-webpack-plugin": "0.0.3", + "uglify-js": "^2.4.15", + "url-parse": "^1.0.5", + "webpack": "^1.12.3", + "webpack-stream": "^3.1.0", + "yargs": "^1.3.1" + }, + "dependencies": {} +} diff --git a/src/adaptermanager.js b/src/adaptermanager.js index a9f7c2d3e60..26f1767782b 100644 --- a/src/adaptermanager.js +++ b/src/adaptermanager.js @@ -1,103 +1,98 @@ -/** @module adaptermanger */ - -var RubiconAdapter = require('./adapters/rubicon.js'); -var AppNexusAdapter = require('./adapters/appnexus.js'); -var AolAdapter = require('./adapters/aol'); -var OpenxAdapter = require('./adapters/openx'); -var PubmaticAdapter = require('./adapters/pubmatic.js'); -var CriteoAdapter = require('./adapters/criteo'); -var YieldbotAdapter = require('./adapters/yieldbot'); -var IndexExchange = require('./adapters/indexExchange'); -var AdformAdapter = require('./adapters/adform'); -var Sovrn = require('./adapters/sovrn'); -var PulsePointAdapter = require('./adapters/pulsepoint.js'); -var bidmanager = require('./bidmanager.js'); -var utils = require('./utils.js'); -var CONSTANTS = require('./constants.json'); -var events = require('./events'); - -var _bidderRegistry = {}; -exports.bidderRegistry = _bidderRegistry; - - -exports.callBids = function(bidderArr) { - for (var i = 0; i < bidderArr.length; i++) { - //use the bidder code to identify which function to call - var bidder = bidderArr[i]; - if (bidder.bidderCode && _bidderRegistry[bidder.bidderCode]) { - utils.logMessage('CALLING BIDDER ======= ' + bidder.bidderCode); - var currentBidder = _bidderRegistry[bidder.bidderCode]; - //emit 'bidRequested' event - events.emit(CONSTANTS.EVENTS.BID_REQUESTED, bidder); - currentBidder.callBids(bidder); - - // if the bidder didn't explicitly set the number of bids - // expected, default to the number of bids passed into the bidder - if (bidmanager.getExpectedBidsCount(bidder.bidderCode) === undefined) { - bidmanager.setExpectedBidsCount(bidder.bidderCode, bidder.bids.length); - } - - var currentTime = new Date().getTime(); - bidmanager.registerBidRequestTime(bidder.bidderCode, currentTime); - - if (currentBidder.defaultBidderSettings) { - bidmanager.registerDefaultBidderSetting(bidder.bidderCode, currentBidder.defaultBidderSettings); - } - } - else{ - utils.logError('Adapter trying to be called which does not exist: ' + bidder.bidderCode, 'adaptermanager.callBids'); - } - } -}; - - -exports.registerBidAdapter = function(bidAdaptor, bidderCode) { - if (bidAdaptor && bidderCode) { - - if (typeof bidAdaptor.callBids === CONSTANTS.objectType_function) { - _bidderRegistry[bidderCode] = bidAdaptor; - - } else { - utils.logError('Bidder adaptor error for bidder code: ' + bidderCode + 'bidder must implement a callBids() function'); - } - - } else { - utils.logError('bidAdaptor or bidderCode not specified'); - } -}; - -exports.aliasBidAdapter = function(bidderCode, alias){ - var existingAlias = _bidderRegistry[alias]; - - if(typeof existingAlias === CONSTANTS.objectType_undefined){ - var bidAdaptor = _bidderRegistry[bidderCode]; - - if(typeof bidAdaptor === CONSTANTS.objectType_undefined){ - utils.logError('bidderCode "' + bidderCode + '" is not an existing bidder.', 'adaptermanager.aliasBidAdapter'); - }else{ - try{ - var newAdapter = bidAdaptor.createNew(); - newAdapter.setBidderCode(alias); - this.registerBidAdapter(newAdapter,alias); - }catch(e){ - utils.logError(bidderCode + ' bidder does not currently support aliasing.', 'adaptermanager.aliasBidAdapter'); - } - } - }else{ - utils.logMessage('alias name "' + alias + '" has been already specified.'); - } -}; - - -// Register the bid adaptors here -this.registerBidAdapter(RubiconAdapter(), 'rubicon'); -this.registerBidAdapter(AppNexusAdapter.createNew(), 'appnexus'); -this.registerBidAdapter(OpenxAdapter(), 'openx'); -this.registerBidAdapter(PubmaticAdapter(), 'pubmatic'); -this.registerBidAdapter(CriteoAdapter(), 'criteo'); -this.registerBidAdapter(YieldbotAdapter(), 'yieldbot'); -this.registerBidAdapter(IndexExchange(), 'indexExchange'); -this.registerBidAdapter(AdformAdapter(), 'adform'); -this.registerBidAdapter(Sovrn(),'sovrn'); -this.registerBidAdapter(AolAdapter(), 'aol'); -this.registerBidAdapter(PulsePointAdapter(),'pulsepoint'); +/** @module adaptermanger */ + +import { flatten, getBidderCodes } from './utils'; + +var utils = require('./utils.js'); +var CONSTANTS = require('./constants.json'); +var events = require('./events'); +import { BaseAdapter } from './adapters/baseAdapter'; + +var _bidderRegistry = {}; +exports.bidderRegistry = _bidderRegistry; + +function getBids({ bidderCode, requestId, bidderRequestId, adUnits }) { + return adUnits.map(adUnit => { + return adUnit.bids.filter(bid => bid.bidder === bidderCode) + .map(bid => Object.assign(bid, { + placementCode: adUnit.code, + sizes: adUnit.sizes, + bidId: utils.getUniqueIdentifierStr(), + bidderRequestId, + requestId + })); + }).reduce(flatten, []); +} + +exports.callBids = ({ adUnits }) => { + const requestId = utils.getUniqueIdentifierStr(); + + getBidderCodes(adUnits).forEach(bidderCode => { + const adapter = _bidderRegistry[bidderCode]; + if (adapter) { + const bidderRequestId = utils.getUniqueIdentifierStr(); + const bidderRequest = { + bidderCode, + requestId, + bidderRequestId, + bids: getBids({ bidderCode, requestId, bidderRequestId, adUnits }), + start: new Date().getTime() + }; + utils.logMessage(`CALLING BIDDER ======= ${bidderCode}`); + pbjs._bidsRequested.push(bidderRequest); + events.emit(CONSTANTS.EVENTS.BID_REQUESTED, bidderRequest); + if (bidderRequest.bids && bidderRequest.bids.length) { + adapter.callBids(bidderRequest); + } + } else { + utils.logError(`Adapter trying to be called which does not exist: ${bidderCode} adaptermanager.callBids`); + } + }); +}; + +exports.registerBidAdapter = function (bidAdaptor, bidderCode) { + if (bidAdaptor && bidderCode) { + + if (typeof bidAdaptor.callBids === CONSTANTS.objectType_function) { + _bidderRegistry[bidderCode] = bidAdaptor; + + } else { + utils.logError('Bidder adaptor error for bidder code: ' + bidderCode + 'bidder must implement a callBids() function'); + } + + } else { + utils.logError('bidAdaptor or bidderCode not specified'); + } +}; + +exports.aliasBidAdapter = function (bidderCode, alias) { + var existingAlias = _bidderRegistry[alias]; + + if (typeof existingAlias === CONSTANTS.objectType_undefined) { + var bidAdaptor = _bidderRegistry[bidderCode]; + + if (typeof bidAdaptor === CONSTANTS.objectType_undefined) { + utils.logError('bidderCode "' + bidderCode + '" is not an existing bidder.', 'adaptermanager.aliasBidAdapter'); + } else { + try { + let newAdapter = null; + if (bidAdaptor instanceof BaseAdapter) { + //newAdapter = new bidAdaptor.constructor(alias); + utils.logError(bidderCode + ' bidder does not currently support aliasing.', 'adaptermanager.aliasBidAdapter'); + } else { + newAdapter = bidAdaptor.createNew(); + newAdapter.setBidderCode(alias); + this.registerBidAdapter(newAdapter, alias); + } + } catch (e) { + utils.logError(bidderCode + ' bidder does not currently support aliasing.', 'adaptermanager.aliasBidAdapter'); + } + } + } else { + utils.logMessage('alias name "' + alias + '" has been already specified.'); + } +}; + +/** INSERT ADAPTERS - DO NOT EDIT OR REMOVE */ + +// here be adapters +/** END INSERT ADAPTERS */ \ No newline at end of file diff --git a/src/adapters/aardvark.js b/src/adapters/aardvark.js new file mode 100644 index 00000000000..a5cefd82e44 --- /dev/null +++ b/src/adapters/aardvark.js @@ -0,0 +1,112 @@ +var utils = require('../utils.js'); +var bidfactory = require('../bidfactory.js'); +var bidmanager = require('../bidmanager.js'); +var adloader = require('../adloader'); + + +/** + * Adapter for requesting bids from RTK Aardvark + * To request an RTK Aardvark Header bidding account + * or for additional integration support please contact sales@rtk.io + */ + +var AardvarkAdapter = function AardvarkAdapter() { + + function _callBids(params) { + var rtkBids = params.bids || []; + + _requestBids(rtkBids); + } + + function _requestBids(bidReqs) { + + try { + var ref = window.top.location.host; + } + catch (err) { + var ref = "thor.rtk.io"; + + } + var ai = ""; + var shortcodes = []; + + //build bid URL for RTK + utils._each(bidReqs, function (bid) { + ai = utils.getBidIdParamater('ai', bid.params); + var sc = utils.getBidIdParamater('sc', bid.params); + shortcodes.push(sc); + }); + + var scURL = ""; + + if (shortcodes.length > 1) { + scURL = shortcodes.join("_"); + } else { + scURL = shortcodes[0]; + } + + var scriptUrl = '//thor.rtk.io/' + ai + "/" + scURL + "/aardvark/?jsonp=window.pbjs.aardvarkResponse&rtkreferer=" + ref; + adloader.loadScript(scriptUrl); + } + + //expose the callback to the global object: + window.pbjs.aardvarkResponse = function (rtkResponseObj) { + + //Get all initial Aardvark Bid Objects + var bidsObj = pbjs._bidsRequested.filter(function (bidder) { + return bidder.bidderCode === 'aardvark'; + })[0]; + + var returnedBidIDs = {}; + var placementIDmap = {}; + + if (rtkResponseObj.length > 0) { + rtkResponseObj.forEach(function (bid) { + + if (!bid.error) { + var currentBid = bidsObj.bids.filter(function (r) { + return r.params.sc === bid.id; + })[0]; + if (currentBid) { + var bidResponse = bidfactory.createBid(1); + bidResponse.bidderCode = "aardvark"; + bidResponse.cpm = bid.cpm; + bidResponse.ad = bid.adm; + bidResponse.ad += utils.createTrackPixelHtml(decodeURIComponent(bid.nurl)); + bidResponse.width = currentBid.sizes[0][0]; + bidResponse.height = currentBid.sizes[0][1]; + returnedBidIDs[bid.id] = currentBid.placementCode; + bidmanager.addBidResponse(currentBid.placementCode, bidResponse); + } + + } + + }); + + } + + //All bids are back - lets add a bid response for anything that did not receive a bid. + var initialSC = []; + bidsObj.bids.forEach(function (bid) { + initialSC.push(bid.params.sc); + placementIDmap[bid.params.sc] = bid.placementCode; + }); + + let difference = initialSC.filter(x => Object.keys(returnedBidIDs).indexOf(x) === -1); + + difference.forEach(function (shortcode) { + var bidResponse = bidfactory.createBid(2); + var placementcode = placementIDmap[shortcode]; + bidResponse.bidderCode = "aardvark"; + bidmanager.addBidResponse(placementcode, bidResponse); + }); + + + }; // aardvarkResponse + + return { + callBids: _callBids + }; +}; + +module.exports = AardvarkAdapter; diff --git a/src/adapters/adapter.js b/src/adapters/adapter.js index 200f3cf1509..85acb5a3aba 100644 --- a/src/adapters/adapter.js +++ b/src/adapters/adapter.js @@ -1,24 +1,24 @@ -function Adapter(code){ - var bidderCode = code; - - function setBidderCode(code){ - bidderCode = code; - } - - function getBidderCode(){ - return bidderCode; - } - - function callBids(){ - } - - return { - callBids: callBids, - setBidderCode: setBidderCode, - getBidderCode: getBidderCode - }; -} - -exports.createNew = function(bidderCode){ - return new Adapter(bidderCode); -}; \ No newline at end of file +function Adapter(code) { + var bidderCode = code; + + function setBidderCode(code) { + bidderCode = code; + } + + function getBidderCode() { + return bidderCode; + } + + function callBids() { + } + + return { + callBids: callBids, + setBidderCode: setBidderCode, + getBidderCode: getBidderCode + }; +} + +exports.createNew = function (bidderCode) { + return new Adapter(bidderCode); +}; diff --git a/src/adapters/adequant.js b/src/adapters/adequant.js new file mode 100644 index 00000000000..5003ac012ea --- /dev/null +++ b/src/adapters/adequant.js @@ -0,0 +1,69 @@ +var bidfactory = require('../bidfactory.js'); +var bidmanager = require('../bidmanager.js'); +var adloader = require('../adloader.js'); + +module.exports = function() { + var req_url_base = 'https://rex.adequant.com/rex/c2s_prebid?'; + + function _callBids(params) { + var req_url = []; + var publisher_id = null; + var sizes = []; + var cats = null; + var replies = []; + var placements = {}; + + var bids = params.bids || []; + for (var i = 0; i < bids.length; i++) { + var bid_request = bids[i]; + var br_params = bid_request.params || {}; + placements[bid_request.placementCode] = true; + + publisher_id = br_params.publisher_id.toString() || publisher_id; + var bidfloor = br_params.bidfloor || 0.01; + cats = br_params.cats || cats; + if (typeof(cats) === 'string') { cats = cats.split(' '); } + for (var j = 0; j < bid_request.sizes.length; j++) { + sizes.push(bid_request.sizes[j].join('x')+'_'+bidfloor); + replies.push(bid_request.placementCode); + } + } + // send out 1 bid request for all bids + if (publisher_id) { req_url.push('a='+publisher_id); } + if (cats) { req_url.push('c='+cats.join('+')); } + if (sizes) { req_url.push('s='+sizes.join('+')); } + + adloader.loadScript(req_url_base+req_url.join('&'), function() { process_bids(replies, placements); }); + } + + function process_bids(replies, placements) { + var placement_code, bid, adequant_creatives = window.adequant_creatives; + if (adequant_creatives && adequant_creatives.seatbid) { + for (var i=0; i> 2; - enc2 = ((chr1 & 3) << 4) | (chr2 >> 4); - enc3 = ((chr2 & 15) << 2) | (chr3 >> 6); - enc4 = chr3 & 63; - - if (isNaN(chr2)) { - enc3 = enc4 = 64; - } else if (isNaN(chr3)) { - enc4 = 64; - } - out.push(_keyStr.charAt(enc1), _keyStr.charAt(enc2)); - if (enc3 != 64) - out.push(_keyStr.charAt(enc3)); - if (enc4 != 64) - out.push(_keyStr.charAt(enc4)); - } - - return out.join(''); - } - - function utf8_encode(string) { - string = string.replace(/\r\n/g, "\n"); - var utftext = ""; - - for (var n = 0; n < string.length; n++) { - - var c = string.charCodeAt(n); - - if (c < 128) { - utftext += String.fromCharCode(c); - } else if ((c > 127) && (c < 2048)) { - utftext += String.fromCharCode((c >> 6) | 192); - utftext += String.fromCharCode((c & 63) | 128); - } else { - utftext += String.fromCharCode((c >> 12) | 224); - utftext += String.fromCharCode(((c >> 6) & 63) | 128); - utftext += String.fromCharCode((c & 63) | 128); - } - } - - return utftext; - } - -} - -module.exports = AdformAdapter; +var utils = require('../utils.js'); +var adloader = require('../adloader.js'); +var bidmanager = require('../bidmanager.js'); +var bidfactory = require('../bidfactory.js'); + +function AdformAdapter() { + + return { + callBids: _callBids + }; + + function _callBids(params) { + var callbackName = '_adf_' + utils.getUniqueIdentifierStr(); + var bid; + var noDomain = true; + var bids = params.bids; + var request = []; + var adxDomain; + + var singleRequest = {}, + singleParam, + singleParams = [ + 'url' + ]; + + for (var i = 0, l = bids.length; i < l; i++) { + bid = bids[i]; + adxDomain = bid.params.adxDomain || bid.adxDomain; + + if (adxDomain && noDomain) { + noDomain = false; + request.unshift('//' + adxDomain + '/adx/?rp=4'); + } + + request.push(formRequestUrl(bid.params)); + + for (var j = 0, k = singleParams.length; j < k; j++) { + singleParam = singleParams[ j ]; + if ( bid.params[ singleParam ] ) { + singleRequest[ singleParam ] = bid.params[ singleParam ]; + } + } + } + + if (noDomain) { + request.unshift('//adx.adform.net/adx/?rp=4'); + } + + for (var j = 0, k = singleParams.length; j < k; j++) { + singleParam = singleParams[ j ]; + if ( singleRequest[ singleParam ] ) { + request.push( singleParam + '=' + encodeURIComponent( singleRequest[ singleParam ] ) ); + } + } + + pbjs[callbackName] = handleCallback(bids); + request.push('callback=pbjs.' + callbackName); + + adloader.loadScript(request.join('&')); + } + + function formRequestUrl(reqData) { + var key; + var url = []; + + var validProps = [ + 'mid', 'inv', 'pdom', 'mname', 'mkw', 'mkv', 'cat', 'bcat', 'bcatrt', 'adv', 'advt', 'cntr', 'cntrt', 'maxp', + 'minp', 'sminp', 'w', 'h', 'pb', 'pos', 'cturl', 'iturl', 'cttype', 'hidedomain', 'cdims', 'test' + ]; + + for (var i = 0, l = validProps.length; i < l; i++) { + key = validProps[i]; + if (reqData.hasOwnProperty(key)) + url.push(key, '=', reqData[key], '&'); + } + + return encode64(url.join('')); + } + + function handleCallback(bids) { + return function handleResponse(adItems) { + var bidObject, bidder = 'adform', adItem, bid; + for(var i = 0, l = adItems.length; i < l; i++){ + adItem = adItems[i]; + bid = bids[i]; + if (adItem && adItem.response === 'banner' && + verifySize(adItem, bid.sizes)) { + + bidObject = bidfactory.createBid(1); + bidObject.bidderCode = bidder; + bidObject.cpm = adItem.win_bid; + bidObject.cur = adItem.win_cur; + bidObject.ad = adItem.banner; + bidObject.width = adItem.width; + bidObject.height = adItem.height; + bidmanager.addBidResponse(bid.placementCode, bidObject); + } else { + bidObject = bidfactory.createBid(2); + bidObject.bidderCode = bidder; + bidmanager.addBidResponse(bid.placementCode, bidObject); + } + } + }; + + function verifySize(adItem, validSizes) { + for (var j = 0, k = validSizes.length; j < k; j++) { + if (adItem.width === validSizes[j][0] && + adItem.height === validSizes[j][1]) { + return true; + } + } + return false; + } + } + + function encode64(input) { + var out = []; + var chr1, chr2, chr3, enc1, enc2, enc3, enc4; + var i = 0; + var _keyStr = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789-_='; + + input = utf8_encode(input); + + while (i < input.length) { + + chr1 = input.charCodeAt(i++); + chr2 = input.charCodeAt(i++); + chr3 = input.charCodeAt(i++); + + enc1 = chr1 >> 2; + enc2 = ((chr1 & 3) << 4) | (chr2 >> 4); + enc3 = ((chr2 & 15) << 2) | (chr3 >> 6); + enc4 = chr3 & 63; + + if (isNaN(chr2)) { + enc3 = enc4 = 64; + } else if (isNaN(chr3)) { + enc4 = 64; + } + out.push(_keyStr.charAt(enc1), _keyStr.charAt(enc2)); + if (enc3 !== 64) + out.push(_keyStr.charAt(enc3)); + if (enc4 !== 64) + out.push(_keyStr.charAt(enc4)); + } + + return out.join(''); + } + + function utf8_encode(string) { + string = string.replace(/\r\n/g, '\n'); + var utftext = ''; + + for (var n = 0; n < string.length; n++) { + + var c = string.charCodeAt(n); + + if (c < 128) { + utftext += String.fromCharCode(c); + } else if ((c > 127) && (c < 2048)) { + utftext += String.fromCharCode((c >> 6) | 192); + utftext += String.fromCharCode((c & 63) | 128); + } else { + utftext += String.fromCharCode((c >> 12) | 224); + utftext += String.fromCharCode(((c >> 6) & 63) | 128); + utftext += String.fromCharCode((c & 63) | 128); + } + } + + return utftext; + } + +} + +module.exports = AdformAdapter; \ No newline at end of file diff --git a/src/adapters/admedia.js b/src/adapters/admedia.js new file mode 100644 index 00000000000..fed48e99d98 --- /dev/null +++ b/src/adapters/admedia.js @@ -0,0 +1,108 @@ +import { getBidRequest } from '../utils.js'; +var bidfactory = require('../bidfactory.js'); +var bidmanager = require('../bidmanager.js'); +var adloader = require('../adloader.js'); +var utils = require('../utils.js'); +var CONSTANTS = require('../constants.json'); + +/** + * Adapter for requesting bids from AdMedia. + * + */ +var AdmediaAdapter = function AdmediaAdapter() { + + function _callBids(params){ + var bids, bidderUrl = (window.location.protocol) + "//b.admedia.com/banner/prebid/bidder/?"; + bids = params.bids || []; + for (var i = 0; i < bids.length; i++) { + var request_obj = {}; + var bid = bids[i]; + + if (bid.params.aid) { + request_obj.aid = bid.params.aid; + } + else{ + utils.logError('required param aid is missing', "admedia"); + continue; + } + + //optional page_url macro + if (bid.params.page_url) { + request_obj.page_url = bid.params.page_url; + } + + //if set, return a test ad for all aids + if (bid.params.test_ad === 1) { + request_obj.test_ad = 1; + } + + var parsedSizes = utils.parseSizesInput(bid.sizes); + var parsedSizesLength = parsedSizes.length; + if (parsedSizesLength > 0) { + //first value should be "size" + request_obj.size = parsedSizes[0]; + if (parsedSizesLength > 1) { + //any subsequent values should be "promo_sizes" + var promo_sizes = []; + for (var j = 1; j < parsedSizesLength; j++) { + promo_sizes.push(parsedSizes[j]); + } + + request_obj.promo_sizes = promo_sizes.join(","); + + } + } + + //detect urls + request_obj.siteDomain = window.location.host; + request_obj.sitePage = window.location.href; + request_obj.siteRef = document.referrer; + request_obj.topUrl = utils.getTopWindowUrl(); + + request_obj.callbackId = bid.bidId; + + var endpoint = bidderUrl+utils.parseQueryStringParameters(request_obj); + + //utils.logMessage('Admedia request built: ' + endpoint); + + adloader.loadScript(endpoint); + } + } + + //expose the callback to global object + pbjs.admediaHandler = function(response){ + var bidObject = {}; + var callback_id = response.callback_id; + var placementCode = ''; + var bidObj = getBidRequest(callback_id); + if (bidObj) { + placementCode = bidObj.placementCode; + } + + if(bidObj && response.cpm>0 && !!response.ad){ + bidObject = bidfactory.createBid(CONSTANTS.STATUS.GOOD); + bidObject.bidderCode = bidObj.bidder; + bidObject.cpm = parseFloat(response.cpm); + bidObject.ad = response.ad; + bidObject.width = response.width; + bidObject.height = response.height; + } + else{ + bidObject = bidfactory.createBid(CONSTANTS.STATUS.NO_BID); + bidObject.bidderCode = bidObj.bidder; + utils.logMessage('No prebid response from Admedia for placement code ' + placementCode); + } + + bidmanager.addBidResponse(placementCode, bidObject); + + }; + + + // Export the callBids function, so that prebid.js can execute this function + // when the page asks to send out bid requests. + return { + callBids: _callBids + }; +}; + +module.exports = AdmediaAdapter; \ No newline at end of file diff --git a/src/adapters/aol.js b/src/adapters/aol.js index 710e5a136c8..f61805d6bba 100644 --- a/src/adapters/aol.js +++ b/src/adapters/aol.js @@ -1,184 +1,188 @@ -var utils = require('../utils.js'), - bidfactory = require('../bidfactory.js'), - bidmanager = require('../bidmanager.js'), - adloader = require('../adloader'); - -var AolAdapter = function AolAdapter() { - - // constants - var ADTECH_PLACEMENT_RXP = /\W/g, - ADTECH_URI = (window.location.protocol) + '//aka-cdn.adtechus.com/dt/common/DAC.js', - ADTECH_BIDDER_NAME = 'aol', - ADTECH_PUBAPI_CONFIG = { - pixelsDivId: 'pixelsDiv', - defaultKey: 'aolBid', - roundingConfig: [{ - from: 0, - to: 999, - roundFunction: 'tenCentsRound' - }, { - from: 1000, - to: -1, - roundValue: 1000 - }], - pubApiOK: _addBid, - pubApiER: _addErrorBid - }; - - var bids, - bidsMap = {}, - d = window.document, - h = d.getElementsByTagName('HEAD')[0], - aliasCount = 0, - dummyUnitIdCount = 0; - - /** - * @private Given a placementCode slot path/div id - * for a unit, return a unique alias - * @param {String} placementCode - * @return {String} alias - */ - function _generateAlias(placementCode) { - return (placementCode || 'alias').replace(ADTECH_PLACEMENT_RXP, '') + (++aliasCount); - } - - /** - * @private create a div that we'll use as the - * location for the AOL unit; AOL will document.write - * if the div is not present in the document. - * @param {String} id to identify the div - * @return {String} the id used with the div - */ - function _dummyUnit(id) { - var div = d.createElement('DIV'); - - if (!id || !id.length) { - id = 'ad-placeholder-' + (++dummyUnitIdCount); - } - - div.id = id + '-head-unit'; - h.appendChild(div); - return div.id; - } - - /** - * @private Add a succesful bid response for aol - * @param {ADTECHResponse} response the response for the bid - * @param {ADTECHContext} context the context passed from aol - */ - function _addBid(response, context) { - var bid = bidsMap[context.placement], - cpm; - - if (!bid) { - utils.logError('mismatched bid: ' + context.placement, ADTECH_BIDDER_NAME, context); - return; - } - - cpm = response.getCPM(); - if (cpm === null || isNaN(cpm)) { - return _addErrorBid(response, context); - } - - var bidResponse = bidfactory.createBid(1); - bidResponse.bidderCode = ADTECH_BIDDER_NAME; - bidResponse.ad = response.getCreative() + response.getPixels(); - bidResponse.cpm = cpm; - bidResponse.width = response.getAdWidth(); - bidResponse.height = response.getAdHeight(); - bidResponse.creativeId = response.getCreativeId(); - - // add it to the bid manager - bidmanager.addBidResponse(bid.placementCode, bidResponse); - } - - /** - * @private Add an error bid response for aol - * @param {ADTECHResponse} response the response for the bid - * @param {ADTECHContext} context the context passed from aol - */ - function _addErrorBid(response, context) { - var bid = bidsMap[context.alias || context.placement]; - - if (!bid) { - utils.logError('mismatched bid: ' + context.placement, ADTECH_BIDDER_NAME, context); - return; - } - - var bidResponse = bidfactory.createBid(2); - bidResponse.bidderCode = ADTECH_BIDDER_NAME; - bidResponse.reason = response.getNbr(); - bidResponse.raw = response.getResponse(); - bidmanager.addBidResponse(bid.placementCode, bidResponse); - } - - - /** - * @private map a prebid bidrequest to an ADTECH/aol bid request - * @param {Bid} bid the bid request - * @return {Object} the bid request, formatted for the ADTECH/DAC api - */ - function _mapUnit(bid) { - // save the bid - bidsMap[bid.params.placement] = bid; - - return { - adContainerId: _dummyUnit(bid.params.adContainerId), - server: bid.params.server, // By default, DAC.js will use the US region endpoint (adserver.adtechus.com) - sizeid: bid.params.sizeId || 0, - pageid: bid.params.pageId, - secure: false, - serviceType: 'pubapi', - performScreenDetection: false, - alias: bid.params.alias || _generateAlias(bid.placementCode), - network: bid.params.network, - placement: parseInt(bid.params.placement), - gpt: { - adUnitPath: bid.params.adUnitPath || bid.placementCode, - size: bid.params.size || (bid.sizes || [])[0] - }, - params: { - cors: 'yes', - cmd: 'bid' - }, - pubApiConfig: ADTECH_PUBAPI_CONFIG, - placementCode: bid.placementCode - }; - } - - /** - * @private once ADTECH is loaded, request bids by - * calling ADTECH.loadAd - */ - function _reqBids() { - if (!window.ADTECH) { - utils.logError('window.ADTECH is not present!', ADTECH_BIDDER_NAME); - return; - } - - // get the bids - utils._each(bids, function(bid) { - var bidreq = _mapUnit(bid); - window.ADTECH.loadAd(bidreq); - }); - } - - /** - * @public call the bids - * this requests the specified bids - * from aol marketplace - * @param {Object} params - * @param {Array} params.bids the bids to be requested - */ - function _callBids(params) { - bids = params.bids; - if (!bids || !bids.length) return; - adloader.loadScript(ADTECH_URI, _reqBids); - } - - return { - callBids: _callBids - }; -}; - -module.exports = AolAdapter; \ No newline at end of file +var utils = require('../utils.js'); +var bidfactory = require('../bidfactory.js'); +var bidmanager = require('../bidmanager.js'); +var adloader = require('../adloader'); + +var AolAdapter = function AolAdapter() { + + // constants + var ADTECH_URI = 'https://secure-ads.pictela.net/rm/marketplace/pubtaglib/0_4_0/pubtaglib_0_4_0.js'; + var ADTECH_BIDDER_NAME = 'aol'; + var ADTECH_PUBAPI_CONFIG = { + pixelsDivId: 'pixelsDiv', + defaultKey: 'aolBid', + roundingConfig: [ + { + from: 0, + to: 999, + roundFunction: 'tenCentsRound' + }, { + from: 1000, + to: -1, + roundValue: 1000 + } + ], + pubApiOK: _addBid, + pubApiER: _addErrorBid + }; + + var bids; + var bidsMap = {}; + var d = window.document; + var h = d.getElementsByTagName('HEAD')[0]; + var dummyUnitIdCount = 0; + + /** + * @private create a div that we'll use as the + * location for the AOL unit; AOL will document.write + * if the div is not present in the document. + * @param {String} id to identify the div + * @return {String} the id used with the div + */ + function _dummyUnit(id) { + var div = d.createElement('DIV'); + + if (!id || !id.length) { + id = 'ad-placeholder-' + (++dummyUnitIdCount); + } + + div.id = id + '-head-unit'; + h.appendChild(div); + return div.id; + } + + /** + * @private Add a succesful bid response for aol + * @param {ADTECHResponse} response the response for the bid + * @param {ADTECHContext} context the context passed from aol + */ + function _addBid(response, context) { + var bid = bidsMap[context.alias]; + var cpm; + + if (!bid) { + utils.logError('mismatched bid: ' + context.placement, ADTECH_BIDDER_NAME, context); + return; + } + + cpm = response.getCPM(); + if (cpm === null || isNaN(cpm)) { + return _addErrorBid(response, context); + } + + // clean up--we no longer need to store the bid + delete bidsMap[context.alias]; + + var bidResponse = bidfactory.createBid(1); + var ad = response.getCreative(); + if (typeof response.getPixels() !== 'undefined') { + ad += response.getPixels(); + } + bidResponse.bidderCode = ADTECH_BIDDER_NAME; + bidResponse.ad = ad; + bidResponse.cpm = cpm; + bidResponse.width = response.getAdWidth(); + bidResponse.height = response.getAdHeight(); + bidResponse.creativeId = response.getCreativeId(); + + // add it to the bid manager + bidmanager.addBidResponse(bid.placementCode, bidResponse); + } + + /** + * @private Add an error bid response for aol + * @param {ADTECHResponse} response the response for the bid + * @param {ADTECHContext} context the context passed from aol + */ + function _addErrorBid(response, context) { + var bid = bidsMap[context.alias]; + + if (!bid) { + utils.logError('mismatched bid: ' + context.placement, ADTECH_BIDDER_NAME, context); + return; + } + + // clean up--we no longer need to store the bid + delete bidsMap[context.alias]; + + var bidResponse = bidfactory.createBid(2); + bidResponse.bidderCode = ADTECH_BIDDER_NAME; + bidResponse.reason = response.getNbr(); + bidResponse.raw = response.getResponse(); + bidmanager.addBidResponse(bid.placementCode, bidResponse); + } + + /** + * @private map a prebid bidrequest to an ADTECH/aol bid request + * @param {Bid} bid the bid request + * @return {Object} the bid request, formatted for the ADTECH/DAC api + */ + function _mapUnit(bid) { + var alias = bid.params.alias || utils.getUniqueIdentifierStr(); + + // save the bid + bidsMap[alias] = bid; + + return { + adContainerId: _dummyUnit(bid.params.adContainerId), + server: bid.params.server, // By default, DAC.js will use the US region endpoint (adserver.adtechus.com) + sizeid: bid.params.sizeId || 0, + pageid: bid.params.pageId, + secure: document.location.protocol === 'https:', + serviceType: 'pubapi', + performScreenDetection: false, + alias: alias, + network: bid.params.network, + placement: parseInt(bid.params.placement), + gpt: { + adUnitPath: bid.params.adUnitPath || bid.placementCode, + size: bid.params.size || (bid.sizes || [])[0] + }, + params: { + cors: 'yes', + cmd: 'bid', + bidfloor: (typeof bid.params.bidFloor !== "undefined") ? bid.params.bidFloor.toString() : '' + }, + pubApiConfig: ADTECH_PUBAPI_CONFIG, + placementCode: bid.placementCode + }; + } + + /** + * @private once ADTECH is loaded, request bids by + * calling ADTECH.loadAd + */ + function _reqBids() { + if (!window.ADTECH) { + utils.logError('window.ADTECH is not present!', ADTECH_BIDDER_NAME); + return; + } + + // get the bids + utils._each(bids, function (bid) { + var bidreq = _mapUnit(bid); + window.ADTECH.loadAd(bidreq); + }); + } + + /** + * @public call the bids + * this requests the specified bids + * from aol marketplace + * @param {Object} params + * @param {Array} params.bids the bids to be requested + */ + function _callBids(params) { + window.bidRequestConfig = window.bidRequestConfig || {}; + window.dacBidRequestConfigs = window.dacBidRequestConfigs || {}; + bids = params.bids; + if (!bids || !bids.length) return; + adloader.loadScript(ADTECH_URI, _reqBids); + } + + return { + callBids: _callBids + }; +}; + +module.exports = AolAdapter; diff --git a/src/adapters/appnexus.js b/src/adapters/appnexus.js index 2dbefc16077..955d866e71e 100644 --- a/src/adapters/appnexus.js +++ b/src/adapters/appnexus.js @@ -1,257 +1,227 @@ -var CONSTANTS = require('../constants.json'); -var utils = require('../utils.js'); -var adloader = require('../adloader.js'); -var bidmanager = require('../bidmanager.js'); -var bidfactory = require('../bidfactory.js'); -var Adapter = require('./adapter.js'); - -var AppNexusAdapter = function AppNexusAdapter() { - var baseAdapter = Adapter.createNew('appnexus'); - var isCalled = false; - - //time tracking buckets, to be used to track latency within script - //array index is timeslice in ms, value passed to buildTrackingTag() is impbus tracker id - var timeTrackingBuckets = []; - timeTrackingBuckets[100] = buildTrackingTag(21139); - timeTrackingBuckets[200] = buildTrackingTag(21140); - timeTrackingBuckets[300] = buildTrackingTag(21141); - timeTrackingBuckets[400] = buildTrackingTag(21142); - timeTrackingBuckets[500] = buildTrackingTag(21143); - timeTrackingBuckets[600] = buildTrackingTag(21144); - timeTrackingBuckets[700] = buildTrackingTag(21145); - timeTrackingBuckets[800] = buildTrackingTag(21146); - timeTrackingBuckets[1000] = buildTrackingTag(21147); - timeTrackingBuckets[1300] = buildTrackingTag(21148); - timeTrackingBuckets[1600] = buildTrackingTag(21149); - timeTrackingBuckets[2000] = buildTrackingTag(21150); - timeTrackingBuckets[5000] = buildTrackingTag(21151); - timeTrackingBuckets[10000] = buildTrackingTag(21152); - - //over 10.000 tracker - var timeTrackerOverMaxBucket = buildTrackingTag(21154); - //var timeTrackerBidTimeout = buildTrackingTag(19432); - - //generic bid requeted tracker - var timeTrackerBidRequested = buildTrackingTag(21153); - - // var timeTrackerBidRequested = buildTrackingTag(19435); - - //helper function to construct impbus trackers - function buildTrackingTag(id) { - return 'https://secure.adnxs.com/imptr?id=' + id + '&t=2'; - } - - baseAdapter.callBids = function(params){ - var bidCode = baseAdapter.getBidderCode(); - - var anArr = params.bids; - var bidsCount = anArr.length; - - //set expected bids count for callback execution - bidmanager.setExpectedBidsCount(bidCode,bidsCount); - - for (var i = 0; i < bidsCount; i++) { - var bidReqeust = anArr[i]; - var callbackId = utils.getUniqueIdentifierStr(); - adloader.loadScript(buildJPTCall(bidReqeust, callbackId)); - //store a reference to the bidRequest from the callback id - bidmanager.pbCallbackMap[callbackId] = bidReqeust; - } - }; - - //given a starttime and an end time, hit the correct impression tracker - function processAndTrackLatency(startTime, endTime, placementCode) { - - if (startTime && endTime) { - //get the difference between times - var timeDiff = endTime - startTime; - var trackingPixelFound = false; - var trackingUrl = ''; - for (var curTrackerItem in timeTrackingBuckets) { - //find the closest upper bound of defined tracking times - if (timeDiff <= curTrackerItem) { - trackingPixelFound = true; - trackingUrl = timeTrackingBuckets[curTrackerItem]; - adloader.trackPixel(trackingUrl); - break; - } - } - //if we didn't find a bucket, assume use the catch-all time over bucket - if (!trackingPixelFound) { - trackingUrl = timeTrackerOverMaxBucket; - adloader.trackPixel(trackingUrl); - } - - utils.logMessage('latency for placement code : ' + placementCode + ' : ' + timeDiff + ' ms.' + ' Tracking URL Fired : ' + trackingUrl); - } - } - - - function buildJPTCall(bid, callbackId) { - - //determine tag params - var placementId = utils.getBidIdParamater('placementId', bid.params); - var memberId = utils.getBidIdParamater('memberId', bid.params); - var inventoryCode = utils.getBidIdParamater('invCode', bid.params); - var query = utils.getBidIdParamater('query', bid.params); - var referrer = utils.getBidIdParamater('referrer', bid.params); - var altReferrer = utils.getBidIdParamater('alt_referrer', bid.params); - - //build our base tag, based on if we are http or https - - var jptCall = 'http' + ('https:' === document.location.protocol ? 's://secure.adnxs.com/jpt?' : '://ib.adnxs.com/jpt?'); - - jptCall = utils.tryAppendQueryString(jptCall, 'callback', 'pbjs.handleAnCB'); - jptCall = utils.tryAppendQueryString(jptCall, 'callback_uid', callbackId); - jptCall = utils.tryAppendQueryString(jptCall, 'psa', '0'); - jptCall = utils.tryAppendQueryString(jptCall, 'id', placementId); - jptCall = utils.tryAppendQueryString(jptCall, 'member_id', memberId); - jptCall = utils.tryAppendQueryString(jptCall, 'code', inventoryCode); - - - - //sizes takes a bit more logic - var sizeQueryString = utils.parseSizesInput(bid.sizes); - if (sizeQueryString) { - jptCall += sizeQueryString + '&'; - } - - //this will be deprecated soon - var targetingParams = utils.parseQueryStringParameters(query); - - if (targetingParams) { - //don't append a & here, we have already done it in parseQueryStringParameters - jptCall += targetingParams; - } - - //append custom attributes: - var paramsCopy = utils.extend({}, bid.params); - //delete attributes already used - delete paramsCopy.placementId; - delete paramsCopy.memberId; - delete paramsCopy.invCode; - delete paramsCopy.query; - delete paramsCopy.referrer; - delete paramsCopy.alt_referrer; - - //get the reminder - var queryParams = utils.parseQueryStringParameters(paramsCopy); - //append - if (queryParams) { - jptCall += queryParams; - } - - //append referrer - if(referrer===''){ - referrer = utils.getTopWindowUrl(); - } - - jptCall = utils.tryAppendQueryString(jptCall, 'referrer', referrer); - jptCall = utils.tryAppendQueryString(jptCall, 'alt_referrer', altReferrer); - - //remove the trailing "&" - if (jptCall.lastIndexOf('&') === jptCall.length - 1) { - jptCall = jptCall.substring(0, jptCall.length - 1); - } - - // @if NODE_ENV='debug' - utils.logMessage('jpt request built: ' + jptCall); - // @endif - - //append a timer here to track latency - bid.startTime = new Date().getTime(); - - return jptCall; - - } - - //expose the callback to the global object: - pbjs.handleAnCB = function(jptResponseObj) { - - var bidCode; - - if (jptResponseObj && jptResponseObj.callback_uid) { - - var error; - var responseCPM; - var id = jptResponseObj.callback_uid, - placementCode = '', - //retrieve bid object by callback ID - bidObj = bidmanager.getPlacementIdByCBIdentifer(id); - if (bidObj) { - - bidCode = bidObj.bidder; - - placementCode = bidObj.placementCode; - //set the status - bidObj.status = CONSTANTS.STATUS.GOOD; - //track latency - try { - processAndTrackLatency(bidObj.startTime, new Date().getTime(), placementCode); - } catch (e) {} - - //place ad response on bidmanager._adResponsesByBidderId - } - - // @if NODE_ENV='debug' - utils.logMessage('JSONP callback function called for ad ID: ' + id); - // @endif - var bid = []; - if (jptResponseObj.result && jptResponseObj.result.cpm && jptResponseObj.result.cpm !== 0) { - responseCPM = parseInt(jptResponseObj.result.cpm, 10); - - //CPM response from /jpt is dollar/cent multiplied by 10000 - //in order to avoid using floats - //switch CPM to "dollar/cent" - responseCPM = responseCPM / 10000; - var responseAd = jptResponseObj.result.ad; - //store bid response - //bid status is good (indicating 1) - var adId = jptResponseObj.result.creative_id; - bid = bidfactory.createBid(1); - bid.creative_id = adId; - bid.bidderCode = bidCode; - bid.cpm = responseCPM; - bid.adUrl = jptResponseObj.result.ad; - bid.width = jptResponseObj.result.width; - bid.height = jptResponseObj.result.height; - bid.dealId = jptResponseObj.result.deal_id; - - bidmanager.addBidResponse(placementCode, bid); - - - } else { - //no response data - // @if NODE_ENV='debug' - utils.logMessage('No prebid response from AppNexus for placement code ' + placementCode); - // @endif - //indicate that there is no bid for this placement - bid = bidfactory.createBid(2); - bid.bidderCode = bidCode; - bidmanager.addBidResponse(placementCode, bid); - } - - - - } else { - //no response data - // @if NODE_ENV='debug' - utils.logMessage('No prebid response for placement %%PLACEMENT%%'); - // @endif - - } - - }; - - return { - callBids: baseAdapter.callBids, - setBidderCode: baseAdapter.setBidderCode, - createNew: exports.createNew, - buildJPTCall : buildJPTCall - }; -}; - -exports.createNew = function(){ - return new AppNexusAdapter(); -}; -// module.exports = AppNexusAdapter; \ No newline at end of file +import { getBidRequest } from '../utils.js'; + +var CONSTANTS = require('../constants.json'); +var utils = require('../utils.js'); +var adloader = require('../adloader.js'); +var bidmanager = require('../bidmanager.js'); +var bidfactory = require('../bidfactory.js'); +var Adapter = require('./adapter.js'); + +var AppNexusAdapter; +AppNexusAdapter = function AppNexusAdapter() { + var baseAdapter = Adapter.createNew('appnexus'); + + baseAdapter.callBids = function (params) { + //var bidCode = baseAdapter.getBidderCode(); + + var anArr = params.bids; + + //var bidsCount = anArr.length; + + //set expected bids count for callback execution + //bidmanager.setExpectedBidsCount(bidCode, bidsCount); + + for (var i = 0; i < anArr.length; i++) { + var bidRequest = anArr[i]; + var callbackId = bidRequest.bidId; + adloader.loadScript(buildJPTCall(bidRequest, callbackId)); + + //store a reference to the bidRequest from the callback id + //bidmanager.pbCallbackMap[callbackId] = bidRequest; + } + }; + + function buildJPTCall(bid, callbackId) { + + //determine tag params + var placementId = utils.getBidIdParamater('placementId', bid.params); + + //memberId will be deprecated, use member instead + var memberId = utils.getBidIdParamater('memberId', bid.params); + var member = utils.getBidIdParamater('member', bid.params); + var inventoryCode = utils.getBidIdParamater('invCode', bid.params); + var query = utils.getBidIdParamater('query', bid.params); + var referrer = utils.getBidIdParamater('referrer', bid.params); + var altReferrer = utils.getBidIdParamater('alt_referrer', bid.params); + + //build our base tag, based on if we are http or https + + var jptCall = 'http' + (document.location.protocol === 'https:' ? 's://secure.adnxs.com/jpt?' : '://ib.adnxs.com/jpt?'); + + jptCall = utils.tryAppendQueryString(jptCall, 'callback', 'pbjs.handleAnCB'); + jptCall = utils.tryAppendQueryString(jptCall, 'callback_uid', callbackId); + jptCall = utils.tryAppendQueryString(jptCall, 'psa', '0'); + jptCall = utils.tryAppendQueryString(jptCall, 'id', placementId); + if (member) { + jptCall = utils.tryAppendQueryString(jptCall, 'member', member); + } else if (memberId) { + jptCall = utils.tryAppendQueryString(jptCall, 'member', memberId); + utils.logMessage('appnexus.callBids: "memberId" will be deprecated soon. Please use "member" instead'); + } + + jptCall = utils.tryAppendQueryString(jptCall, 'code', inventoryCode); + + //sizes takes a bit more logic + var sizeQueryString = ''; + var parsedSizes = utils.parseSizesInput(bid.sizes); + + //combine string into proper querystring for impbus + var parsedSizesLength = parsedSizes.length; + if (parsedSizesLength > 0) { + //first value should be "size" + sizeQueryString = 'size=' + parsedSizes[0]; + if (parsedSizesLength > 1) { + //any subsequent values should be "promo_sizes" + sizeQueryString += '&promo_sizes='; + for (var j = 1; j < parsedSizesLength; j++) { + sizeQueryString += parsedSizes[j] += ','; + } + + //remove trailing comma + if (sizeQueryString && sizeQueryString.charAt(sizeQueryString.length - 1) === ',') { + sizeQueryString = sizeQueryString.slice(0, sizeQueryString.length - 1); + } + } + } + + if (sizeQueryString) { + jptCall += sizeQueryString + '&'; + } + + //this will be deprecated soon + var targetingParams = utils.parseQueryStringParameters(query); + + if (targetingParams) { + //don't append a & here, we have already done it in parseQueryStringParameters + jptCall += targetingParams; + } + + //append custom attributes: + var paramsCopy = utils.extend({}, bid.params); + + //delete attributes already used + delete paramsCopy.placementId; + delete paramsCopy.memberId; + delete paramsCopy.invCode; + delete paramsCopy.query; + delete paramsCopy.referrer; + delete paramsCopy.alt_referrer; + delete paramsCopy.member; + + //get the reminder + var queryParams = utils.parseQueryStringParameters(paramsCopy); + + //append + if (queryParams) { + jptCall += queryParams; + } + + //append referrer + if (referrer === '') { + referrer = utils.getTopWindowUrl(); + } + + jptCall = utils.tryAppendQueryString(jptCall, 'referrer', referrer); + jptCall = utils.tryAppendQueryString(jptCall, 'alt_referrer', altReferrer); + + //remove the trailing "&" + if (jptCall.lastIndexOf('&') === jptCall.length - 1) { + jptCall = jptCall.substring(0, jptCall.length - 1); + } + + // @if NODE_ENV='debug' + utils.logMessage('jpt request built: ' + jptCall); + + // @endif + + //append a timer here to track latency + bid.startTime = new Date().getTime(); + + return jptCall; + + } + + //expose the callback to the global object: + pbjs.handleAnCB = function (jptResponseObj) { + + var bidCode; + + if (jptResponseObj && jptResponseObj.callback_uid) { + + var responseCPM; + var id = jptResponseObj.callback_uid; + var placementCode = ''; + var bidObj = getBidRequest(id); + if (bidObj) { + + bidCode = bidObj.bidder; + + placementCode = bidObj.placementCode; + + //set the status + bidObj.status = CONSTANTS.STATUS.GOOD; + } + + // @if NODE_ENV='debug' + utils.logMessage('JSONP callback function called for ad ID: ' + id); + + // @endif + var bid = []; + if (jptResponseObj.result && jptResponseObj.result.cpm && jptResponseObj.result.cpm !== 0) { + responseCPM = parseInt(jptResponseObj.result.cpm, 10); + + //CPM response from /jpt is dollar/cent multiplied by 10000 + //in order to avoid using floats + //switch CPM to "dollar/cent" + responseCPM = responseCPM / 10000; + + //store bid response + //bid status is good (indicating 1) + var adId = jptResponseObj.result.creative_id; + bid = bidfactory.createBid(1); + bid.creative_id = adId; + bid.bidderCode = bidCode; + bid.cpm = responseCPM; + bid.adUrl = jptResponseObj.result.ad; + bid.width = jptResponseObj.result.width; + bid.height = jptResponseObj.result.height; + bid.dealId = jptResponseObj.result.deal_id; + + bidmanager.addBidResponse(placementCode, bid); + + } else { + //no response data + // @if NODE_ENV='debug' + utils.logMessage('No prebid response from AppNexus for placement code ' + placementCode); + + // @endif + //indicate that there is no bid for this placement + bid = bidfactory.createBid(2); + bid.bidderCode = bidCode; + bidmanager.addBidResponse(placementCode, bid); + } + + } else { + //no response data + // @if NODE_ENV='debug' + utils.logMessage('No prebid response for placement %%PLACEMENT%%'); + + // @endif + + } + + }; + + return { + callBids: baseAdapter.callBids, + setBidderCode: baseAdapter.setBidderCode, + createNew: exports.createNew, + buildJPTCall: buildJPTCall + }; +}; + +exports.createNew = function () { + return new AppNexusAdapter(); +}; + +// module.exports = AppNexusAdapter; diff --git a/src/adapters/appnexusAst.js b/src/adapters/appnexusAst.js new file mode 100644 index 00000000000..70207303262 --- /dev/null +++ b/src/adapters/appnexusAst.js @@ -0,0 +1,104 @@ +import { BaseAdapter } from './baseAdapter'; +const utils = require('../utils'); +const adloader = require('../adloader.js'); +const bidmanager = require('../bidmanager'); +const bidfactory = require('../bidfactory'); +const CONSTANTS = require('../constants.json'); + +const AST_URL = 'https://acdn.adnxs.com/ast/alpha/ast.js'; + +export class AppnexusAst extends BaseAdapter { + constructor(code) { + super(code); + this._bidRequests = null; + } + + callBids(params) { + window.apntag = window.apntag || {}; + window.apntag.anq = window.apntag.anq || []; + this._bidRequests = params.bids; + adloader.loadScript(AST_URL, () => { + this._requestAds(this.code); + }, true); + } + + _requestAds(code) { + if (utils.debugTurnedOn()) { + window.apntag.debug = true; + } + + window.apntag.clearRequest(); + + for (let bidRequest of this._bidRequests) { + const astTag = this._buildTag(bidRequest); + const requestTag = window.apntag.defineTag(astTag); + const placementCode = bidRequest.placementCode; + + //successful bid + /*jshint -W083 */ + requestTag.on('adAvailable', function(ad) { + const bid = bidfactory.createBid(CONSTANTS.STATUS.GOOD); + bid.code = code; + bid.bidderCode = code; + bid.creative_id = ad.creativeId; + bid.cpm = ad.cpm; + bid.ad = ad.banner.content; + try { + const url = ad.banner.trackers[0].impression_urls[0]; + const tracker = utils.createTrackPixelHtml(url); + bid.ad += tracker; + } catch (e) { + utils.logError('Error appending tracking pixel', 'appnexusAst.js:_requestAds', e); + } + + bid.width = ad.banner.width; + bid.height = ad.banner.height; + bidmanager.addBidResponse(placementCode, bid); + }); + + //no bid + requestTag.on('adNoBid', function() { + const bid = bidfactory.createBid(CONSTANTS.STATUS.NO_BID); + bid.code = code; + bid.bidderCode = code; + bidmanager.addBidResponse(placementCode, bid); + }); + } + + window.apntag.loadTags(); + } + + _buildTag(bid) { + let tag = {}; + const uuid = utils.getUniqueIdentifierStr(); + + //clone bid.params to tag + const jsonBid = JSON.stringify(bid.params); + tag = JSON.parse(jsonBid); + + //append member if available. Should not use multiple member IDs. + utils._each(tag, function(value, key) { + if (key === 'member') { + window.apntag.setPageOpts({ + member: value + }); + } + + if (key === 'keywords') { + window.apntag.setPageOpts({ + keywords: value + }); + } + }); + + tag.targetId = uuid; + tag.prebid = true; + if (!tag.sizes) { + tag.sizes = bid.sizes; + } + + tag.tagId = Number.parseInt(bid.params.placementId); + tag.disablePsa = true; + return tag; + } +} diff --git a/src/adapters/baseAdapter.js b/src/adapters/baseAdapter.js new file mode 100644 index 00000000000..6864bac4abe --- /dev/null +++ b/src/adapters/baseAdapter.js @@ -0,0 +1,17 @@ +export class BaseAdapter { + constructor(code) { + this.code = code; + } + + getCode() { + return this.code; + } + + setCode(code) { + this.code = code; + } + + callBids() { + throw 'adapter implementation must override callBids method'; + } +} diff --git a/src/adapters/brightcom.js b/src/adapters/brightcom.js new file mode 100644 index 00000000000..cffc2fdd74d --- /dev/null +++ b/src/adapters/brightcom.js @@ -0,0 +1,214 @@ +var CONSTANTS = require('../constants.json'); +var utils = require('../utils.js'); +var bidfactory = require('../bidfactory.js'); +var bidmanager = require('../bidmanager.js'); +var adloader = require('../adloader'); + +/** + * Adapter for requesting bids from Brightcom + */ +var BrightcomAdapter = function BrightcomAdapter() { + + // Set Brightcom Bidder URL + var brightcomUrl = 'hb.iselephant.com/auc/ortb'; + + // Define the bidder code + var brightcomBidderCode = 'brightcom'; + + // Define the callback function + var brightcomCallbackFunction = 'window.pbjs=window.pbjs||window.parent.pbjs||window.top.pbjs;window.pbjs.brightcomResponse'; + + // Manage the requested and received ad units' codes, to know which are invalid (didn't return) + var reqAdUnitsCode = [], + resAdUnitsCode = []; + + function _callBids(params) { + + var bidRequests = params.bids || []; + + // Get page data + var siteDomain = window.location.host; + var sitePage = window.location.href; + + // Prepare impressions object + var brightcomImps = []; + + // Prepare a variable for publisher id + var pubId = ''; + + // Go through the requests and build array of impressions + utils._each(bidRequests, function(bid) { + + // Get impression details + var tagId = utils.getBidIdParamater('tagId', bid.params); + var ref = utils.getBidIdParamater('ref', bid.params); + var adWidth=0; + var adHeight=0; + + // If no publisher id is set, use the current + if (pubId === '') { + // Get the current publisher id (if it doesn't exist, it'll return '') + pubId = utils.getBidIdParamater('pubId', bid.params); + } + + // Brightcom supports only 1 size per impression + // Check if the array contains 1 size or array of sizes + if (bid.sizes.length === 2 && typeof bid.sizes[0] === 'number' && typeof bid.sizes[1] === 'number') { + // The array contains 1 size (the items are the values) + adWidth = bid.sizes[0]; + adHeight = bid.sizes[1]; + } else { + // The array contains array of sizes, use the first size + adWidth = bid.sizes[0][0]; + adHeight = bid.sizes[0][1]; + } + + // Build the impression + var imp = { + id: utils.getUniqueIdentifierStr(), + banner: { + w: adWidth, + h: adHeight + }, + tagid: tagId + }; + + // If ref exists, create it (in the "ext" object) + if (ref !== '') { + imp.ext = { + refoverride: ref + }; + } + + // Add current impression to collection + brightcomImps.push(imp); + // Add mapping to current bid via impression id + //bidmanager.pbCallbackMap[imp.id] = bid; + + // Add current ad unit's code to tracking + reqAdUnitsCode.push(bid.placementCode); + + }); + + // Build the bid request + var brightcomBidReq = { + id: utils.getUniqueIdentifierStr(), + imp: brightcomImps, + site:{ + publisher: { + id: pubId + }, + domain: siteDomain, + page: sitePage + } + }; + + // Add timeout data, if available + var PREBID_TIMEOUT = PREBID_TIMEOUT || 0; + var curTimeout = PREBID_TIMEOUT; + if (curTimeout > 0) { + brightcomBidReq.tmax = curTimeout; + } + + // Define the bid request call URL + var bidRequestCallUrl = 'https://' + brightcomUrl + + '?callback=' + brightcomCallbackFunction + + '&request=' + encodeURIComponent(JSON.stringify(brightcomBidReq)); + + // Add the call to get the bid + adloader.loadScript(bidRequestCallUrl, null); + + } + + //expose the callback to the global object: + pbjs.brightcomResponse = function(brightcomResponseObj) { + + var bid = {}; + + // Make sure response is valid + if ( + (brightcomResponseObj) && (brightcomResponseObj.id) && + (brightcomResponseObj.seatbid) && (brightcomResponseObj.seatbid.length !== 0) && + (brightcomResponseObj.seatbid[0].bid) && (brightcomResponseObj.seatbid[0].bid.length !== 0) + ) { + + // Go through the received bids + brightcomResponseObj.seatbid[0].bid.forEach( function(curBid) { + + // Get the bid request data + var bidRequest = pbjs._bidsRequested.find(bidSet => bidSet.bidderCode === 'brightcom').bids[0]; // this assumes a single request only + + // Make sure the bid exists + if (bidRequest) { + + var placementCode = bidRequest.placementCode; + bidRequest.status = CONSTANTS.STATUS.GOOD; + + curBid.placementCode = placementCode; + curBid.size = bidRequest.sizes; + + // Get the creative + var responseCreative = curBid.adm; + // Build the NURL element + var responseNurl = ''; + // Build the ad to display: + var responseAd = decodeURIComponent(responseCreative + responseNurl); + + // Create a valid bid + bid = bidfactory.createBid(1); + + // Set the bid data + bid.creative_id = curBid.Id; + bid.bidderCode = brightcomBidderCode; + bid.cpm = parseFloat(curBid.price); + + // Brightcom tag is in '; - bid.ad_id = rubiconAd.ad_id; - bid.bidderCode = 'rubicon'; - bid.sizeId = rubiconAd.size_id; - bid.width = width; - bid.height = height; - - }else{ - bid = bidfactory.createBid(2); - bid.bidderCode = 'rubicon'; - var bidObj = bidmanager.getPlacementIdByCBIdentifer(getBidId(response)); - if (bidObj) { - placementCode = bidObj.placementCode; - } - } - - } catch (e) { - utils.logError('Error parsing rubicon response bid: ' + e.message); - } - - } else { - //set bid response code to 2 = no response or error - bid = bidfactory.createBid(2); - bid.bidderCode = 'rubicon'; - var bidObj = bidmanager.getPlacementIdByCBIdentifer(getBidId(response)); - if (bidObj) { - placementCode = bidObj.placementCode; - } - - } - - //add the bid response here - bidmanager.addBidResponse(placementCode, bid); - - }; - - return { - callBids: callBids - - }; - //end of Rubicon bid adaptor -}; - -module.exports = RubiconAdapter; +/** + * @file Rubicon (Rubicon) adapter + */ + +// jshint ignore:start +var utils = require('../utils'); +var bidmanager = require('../bidmanager'); +var bidfactory = require('../bidfactory'); +var adloader = require('../adloader'); + +/** + * @class RubiconAdapter + * Prebid adapter for Rubicon's header bidding client + */ +var RubiconAdapter = function RubiconAdapter() { + var RUBICONTAG_URL = (window.location.protocol) + '//ads.rubiconproject.com/header/'; + var RUBICON_OK_STATUS = 'ok'; + var RUBICON_BIDDER_CODE = 'rubicon'; + var RUBICON_SIZE_MAP = { + '468x60': 1, + '728x90': 2, + '120x600': 8, + '160x600': 9, + '300x600': 10, + '300x250': 15, + '336x280': 16, + '320x50': 43, + '300x50': 44, + '300x1050': 54, + '970x90': 55, + '970x250': 57, + '1000x90': 58, + '320x480': 67, + '1800x1000': 68, + '480x320':101, + '768x1024': 102, + '1000x300':113, + '320x100':117 + }; + var RUBICON_INITIALIZED = 0; + + // the fastlane creative code + var RUBICON_CREATIVE_START = ''; + + // pre-initialize the rubicon object + // needs to be attached to the window + window.rubicontag = window.rubicontag || {}; + window.rubicontag.cmd = window.rubicontag.cmd || []; + + // timestamp for logging + var _bidStart = null; + var bidCount = 0; + + /** + * Create an error bid + * @param {String} placement - the adunit path + * @param {Object} response - the (error) response from fastlane + * @return {Bid} a bid, for prebid + */ + function _errorBid(response, ads) { + var bidResponse = bidfactory.createBid(2); + bidResponse.bidderCode = RUBICON_BIDDER_CODE; + + // use the raw ads as the 'error' + bidResponse.error = ads; + return bidResponse; + } + + /** + * Sort function for CPM + * @param {Object} adA + * @param {Object} adB + * @return {Float} sort order value + */ + function _adCpmSort(adA, adB) { + return (adB.cpm || 0.0) - (adA.cpm || 0.0); + } + + /** + * Produce the code to render a creative + * @param {String} elemId the element passed to rubicon; this is essentially the ad-id + * @param {Array} size array of width, height + * @return {String} creative + */ + function _creative(elemId, size) { + + // convert the size to a rubicon sizeId + var sizeId = RUBICON_SIZE_MAP[size.join('x')]; + + if (!sizeId) { + utils.logError( + 'fastlane: missing sizeId for size: ' + size.join('x') + ' could not render creative', + RUBICON_BIDDER_CODE, RUBICON_SIZE_MAP); + return ''; + } + + return RUBICON_CREATIVE_START + elemId + '", "' + sizeId + RUBICON_CREATIVE_END; + } + + /** + * Create a (successful) bid for a unit, + * based on the given response + * @param {String} placement placement code/unit path + * @param {Object} response the response from rubicon + * @return {Bid} a bid objectj + */ + function _makeBid(response, ads) { + + // if there are multiple ads, sort by CPM + ads = ads.sort(_adCpmSort); + + var bidResponse = bidfactory.createBid(1); + var ad = ads[0]; + var size = ad.dimensions; + + if (!size) { + // this really shouldn't happen + utils.logError('no dimensions given', RUBICON_BIDDER_CODE, ad); + return _errorBid(response, ads); + } + + bidResponse.bidderCode = RUBICON_BIDDER_CODE; + bidResponse.cpm = ad.cpm; + + // the element id is what the iframe will use to render + // itself using the rubicontag.renderCreative API + bidResponse.ad = _creative(response.getElementId(), size); + bidResponse.width = size[0]; + bidResponse.height = size[1]; + return bidResponse; + } + + /** + * Add a success/error bid based + * on the response from rubicon + * @param {Object} response -- AJAX response from fastlane + */ + function _addBid(response, ads) { + // get the bid for the placement code + var bid; + if (!ads || ads.length === 0) { + bid = _errorBid(response, ads); + } else { + bid = _makeBid(response, ads); + } + + bidmanager.addBidResponse(response.getElementId(), bid); + } + + /** + * Helper to queue functions on rubicontag + * ready/available + * @param {Function} callback + */ + function _rready(callback) { + window.rubicontag.cmd.push(callback); + } + + /** + * download the rubicontag sdk + * @param {Object} options + * @param {String} options.accountId + * @param {Function} callback + */ + function _initSDK(options, done) { + if (RUBICON_INITIALIZED) { + return; + } + + RUBICON_INITIALIZED = 1; + + var accountId = options.accountId; + var scripttUrl = RUBICONTAG_URL + accountId + '.js'; + + adloader.loadScript(scripttUrl, done); + } + + /** + * map the sizes in `bid.sizes` to Rubicon specific keys + * @param {object} array of bids + * @return {[type]} [description] + */ + function _mapSizes(bids) { + utils._each(bids, function (bid) { + if (bid.params.sizes) { + return; + } + + //return array like ['300x250', '728x90'] + var parsedSizes = utils.parseSizesInput(bid.sizes); + + //iterate the bid.sizes array to lookup codes + var tempSize = []; + for (var i = 0; i < parsedSizes.length; i++) { + var rubiconKey = RUBICON_SIZE_MAP[parsedSizes[i]]; + if (rubiconKey) { + tempSize.push(rubiconKey); + } + } + + bid.params.sizes = tempSize; + }); + } + + /** + * Define the slot using the rubicontag.defineSlot API + * @param {Object} Bidrequest + * @returns {RubiconSlot} Instance of RubiconSlot + */ + function _defineSlot(bid) { + var userId = bid.params.userId; + var position = bid.params.position; + var visitor = bid.params.visitor || []; + var keywords = bid.params.keywords || []; + var inventory = bid.params.inventory || []; + var slot = window.rubicontag.defineSlot({ + siteId: bid.params.siteId, + zoneId: bid.params.zoneId, + sizes: bid.params.sizes, + id: bid.placementCode + }); + + slot.clearTargeting(); + + if (userId) { + window.rubicontag.setUserKey(userId); + } + + if (position) { + slot.setPosition(position); + } + + for (var key in visitor) { + if (visitor.hasOwnProperty(key)) { + slot.addFPV(key, visitor[key]); + } + } + + for (var key in inventory) { + if (inventory.hasOwnProperty(key)) { + slot.addFPI(key, inventory[key]); + } + } + + slot.addKW(keywords); + + return slot; + } + + /** + * Handle the bids received (from rubicon) + * @param {array} slots + */ + function _bidsReady(slots) { + // NOTE: we don't really need to do anything, + // because right now we're shimming XMLHttpRequest.open, + // but in the future we'll get data from rubicontag here + utils.logMessage('Rubicon Project bidding complete: ' + ((new Date).getTime() - _bidStart)); + + utils._each(slots, function (slot) { + _addBid(slot, slot.getRawResponses()); + }); + } + + /** + * Request the specified bids from + * Rubicon + * @param {Object} params the bidder-level params (from prebid) + * @param {Array} params.bids the bids requested + */ + function _callBids(params) { + + // start the timer; want to measure from + // even just loading the SDK + _bidStart = (new Date).getTime(); + + _mapSizes(params.bids); + + if (utils.isEmpty(params.bids)) { + return; + } + + // on the first bid, set up the SDK + if (!RUBICON_INITIALIZED) { + _initSDK(params.bids[0].params); + } + + _rready(function () { + var slots = []; + var bids = params.bids; + + for (var i=0, ln=bids.length; i < ln; i++) { + slots.push(_defineSlot(bids[i])); + } + + var parameters = { slots: slots }; + var callback = function () { + _bidsReady(slots); + }; + + window.rubicontag.setIntegration('pbjs'); + window.rubicontag.run(callback, parameters); + }); + } + + return { + /** + * @public callBids + * the interface to Prebid + */ + callBids: _callBids + }; +}; + +module.exports = RubiconAdapter; diff --git a/src/adapters/sekindo.js b/src/adapters/sekindo.js new file mode 100755 index 00000000000..220d6627b9a --- /dev/null +++ b/src/adapters/sekindo.js @@ -0,0 +1,107 @@ +import { getBidRequest } from '../utils.js'; +var CONSTANTS = require('../constants.json'); +var utils = require('../utils.js'); +var bidfactory = require('../bidfactory.js'); +var bidmanager = require('../bidmanager.js'); + +var SekindoAdapter; +SekindoAdapter = function SekindoAdapter() { + + function _callBids(params) { + var bids = params.bids; + var bidsCount = bids.length; + + var pubUrl = null; + if (parent !== window) + pubUrl = document.referrer; + else + pubUrl = window.location.href; + + for (var i = 0; i < bidsCount; i++) { + var bidReqeust = bids[i]; + var callbackId = bidReqeust.bidId; + _requestBids(bidReqeust, callbackId, pubUrl); + //store a reference to the bidRequest from the callback id + //bidmanager.pbCallbackMap[callbackId] = bidReqeust; + } + } + + pbjs.sekindoCB = function(callbackId, response) { + var bidObj = getBidRequest(callbackId); + if (typeof (response) !== 'undefined' && typeof (response.cpm) !== 'undefined') { + var bid = []; + if (bidObj) { + var bidCode = bidObj.bidder; + var placementCode = bidObj.placementCode; + + if (response.cpm !== undefined && response.cpm > 0) { + + bid = bidfactory.createBid(CONSTANTS.STATUS.GOOD); + bid.adId = response.adId; + bid.callback_uid = callbackId; + bid.bidderCode = bidCode; + bid.creative_id = response.adId; + bid.cpm = parseFloat(response.cpm); + bid.ad = response.ad; + bid.width = response.width; + bid.height = response.height; + + bidmanager.addBidResponse(placementCode, bid); + } + + else { + bid = bidfactory.createBid(CONSTANTS.STATUS.NO_BID); + bid.callback_uid = callbackId; + bid.bidderCode = bidCode; + bidmanager.addBidResponse(placementCode, bid); + } + } + } + + else { + if (bidObj) { + utils.logMessage('No prebid response for placement '+bidObj.placementCode); + } + + else { + utils.logMessage('sekindo callback general error'); + } + } + }; + + function _requestBids(bid, callbackId, pubUrl) { + //determine tag params + var spaceId = utils.getBidIdParamater('spaceId', bid.params); + var bidfloor = utils.getBidIdParamater('bidfloor', bid.params); + var protocol = ('https:' === document.location.protocol ? 's' : ''); + var scriptSrc = 'https://live.sekindo.com/live/liveView.php?'; + + scriptSrc = utils.tryAppendQueryString(scriptSrc, 's', spaceId); + scriptSrc = utils.tryAppendQueryString(scriptSrc, 'pubUrl', pubUrl); + scriptSrc = utils.tryAppendQueryString(scriptSrc, 'hbcb', callbackId); + scriptSrc = utils.tryAppendQueryString(scriptSrc, 'dcpmflr', bidfloor); + scriptSrc = utils.tryAppendQueryString(scriptSrc, 'hbto', pbjs.bidderTimeout); + scriptSrc = utils.tryAppendQueryString(scriptSrc, 'protocol', protocol); + + var html = ''; + + var iframe = utils.createInvisibleIframe(); + iframe.id = 'skIfr_'+callbackId; + + var elToAppend = document.getElementsByTagName('head')[0]; + //insert the iframe into document + elToAppend.insertBefore(iframe, elToAppend.firstChild); + + var iframeDoc = utils.getIframeDocument(iframe); + iframeDoc.open(); + iframeDoc.write(html); + iframeDoc.close(); + } + + return { + callBids: _callBids + }; +}; + +module.exports = SekindoAdapter; + diff --git a/src/adapters/sonobi.js b/src/adapters/sonobi.js new file mode 100644 index 00000000000..3421bceabc5 --- /dev/null +++ b/src/adapters/sonobi.js @@ -0,0 +1,91 @@ +var bidfactory = require('../bidfactory.js'); +var bidmanager = require('../bidmanager.js'); +var adloader = require('../adloader.js'); +var utils = require('../utils'); + +var SonobiAdapter = function SonobiAdapter(){ + var test = false; // tag tester = true || false + var cb_map = {}; + + function _phone_in(params){ + var trinity = 'https://apex.go.sonobi.com/trinity.js?key_maker='; + var bids = params.bids || []; + adloader.loadScript(trinity + JSON.stringify(_keymaker(bids)) + '&cv=' + _operator(), null); + } + + function _keymaker(bids){ // Make keys + var keyring = {}; + utils._each(bids, function(o){ + var sizes = utils.parseSizesInput(o.sizes).toString(); + if (utils.isEmpty(sizes)){ + utils.logWarn('Sonobi adapter expects sizes for ' + o.placementCode); + } + switch(true){ + case (!o.params.ad_unit && !o.params.placement_id): + utils.logError('Sonobi unable to bid: Missing parameters for ' + o.placementCode); + break; + case (!!o.params.ad_unit && !!o.params.placement_id): + utils.logError('Sonobi unable to bid: Extra parameters for ' + o.placementCode); + break; + case (!!o.params.ad_unit && o.params.ad_unit.length === 0): + utils.logError('Sonobi unable to bid: Empty ad_unit for ' + o.placementCode); + break; + case (!!o.params.placement_id && o.params.placement_id.length === 0): + utils.logError('Sonobi unable to bid: Empty placement_id for ' + o.placementCode); + break; + case (!!o.params.placement_id): // Morpeus style + keyring[o.params.dom_id] = o.params.placement_id + (test ? '-test' : '') + '|' + sizes; + cb_map[o.params.dom_id] = o.placementCode; + break; + case (!!o.params.ad_unit && o.params.ad_unit.charAt(0) !== '/'): + // DFP docs do not necessarily require leading slash? - add it in if it's not there. + o.params.ad_unit = '/' + o.params.ad_unit; + case (!!o.params.ad_unit): // Cypher style + keyring[o.params.ad_unit + '|' + o.params.dom_id] = sizes; + cb_map[o.params.ad_unit + '|' + o.params.dom_id] = o.placementCode; + break; + default: // I don't know how it's broken, but it is. + utils.logError('Sonobi unable to bid: Improper parameters for ' + o.placementCode); + } + }); + return keyring; + } + + function _operator(){ // Uniqify callbacks + var uniq = "cb" + utils.getUniqueIdentifierStr(); + window[uniq] = _trinity; + return uniq; + } + + function _trinity(response){ // Callback + var slots = response.slots || {}; + var sbi_dc = response.sbi_dc || ''; + var bidObject = {}; + for (var slot in slots) { + if (slots[slot].sbi_aid){ + bidObject = bidfactory.createBid(1); + bidObject.bidderCode = 'sonobi'; + bidObject.cpm = Number(slots[slot].sbi_mouse); + bidObject.ad = _get_creative(sbi_dc, slots[slot].sbi_aid); + bidObject.width = Number(slots[slot].sbi_size.split('x')[0]); + bidObject.height = Number(slots[slot].sbi_size.split('x')[1]); + bidmanager.addBidResponse(cb_map[slot], bidObject); + } else { // No aid? No ad. + bidObject = bidfactory.createBid(2); + bidObject.bidderCode = 'sonobi'; + bidmanager.addBidResponse(cb_map[slot], bidObject); + } + } + } + + function _get_creative(sbi_dc, sbi_aid){ + var creative = ''; + return creative; + } + + return { callBids: _phone_in }; +}; + +module.exports = SonobiAdapter; \ No newline at end of file diff --git a/src/adapters/sovrn.js b/src/adapters/sovrn.js index bafee8a2bc3..fc598c226bf 100644 --- a/src/adapters/sovrn.js +++ b/src/adapters/sovrn.js @@ -1,183 +1,177 @@ -var CONSTANTS = require('../constants.json'); -var utils = require('../utils.js'); -var bidfactory = require('../bidfactory.js'); -var bidmanager = require('../bidmanager.js'); -var adloader = require('../adloader'); - -var defaultPlacementForBadBid = ''; - -/** - * Adapter for requesting bids from Sovrn - */ -var SovrnAdapter = function SovrnAdapter() { - var sovrnUrl = 'ap.lijit.com/rtb/bid'; - - function _callBids(params) { - var sovrnBids = params.bids || []; - // De-dupe by tagid then issue single bid request for all bids - _requestBids(_getUniqueTagids(sovrnBids)); - } - - // filter bids to de-dupe them? - function _getUniqueTagids(bids) { - var key; - var map = {}; - var Tagids = []; - bids.forEach(function(bid) { - map[utils.getBidIdParamater('tagid', bid.params)] = bid; - }); - for (key in map) { - if (map.hasOwnProperty(key)) { - Tagids.push(map[key]); - } - } - return Tagids; - } - - function _requestBids(bidReqs) { - // build bid request object - var domain = window.location.host; - var page = window.location.pathname + location.search + location.hash; - - var sovrnImps = []; - //assign the first adUnit (placement) for bad bids; - defaultPlacementForBadBid = bidReqs[0].placementCode; - - //build impression array for sovrn - utils._each(bidReqs, function(bid) - { - var tagId = utils.getBidIdParamater('tagid', bid.params); - var bidFloor = utils.getBidIdParamater('bidfloor', bid.params); - var adW=0,adH=0; - - //sovrn supports only one size per tagid, so we just take the first size if there are more - //if we are a 2 item array of 2 numbers, we must be a SingleSize array - var sizeArrayLength = bid.sizes.length; - if (sizeArrayLength === 2 && typeof bid.sizes[0] === 'number' && typeof bid.sizes[1] === 'number') { - adW=bid.sizes[0]; - adH=bid.sizes[1]; - } - else - { - adW=bid.sizes[0][0]; - adH=bid.sizes[0][1]; - } - imp = - { - id: utils.getUniqueIdentifierStr(), - banner: { - w: adW, - h: adH - }, - tagid: tagId, - bidfloor: bidFloor - }; - sovrnImps.push(imp); - bidmanager.pbCallbackMap[imp.id] = bid; - }); - - // build bid request with impressions - var sovrnBidReq = { - id: utils.getUniqueIdentifierStr(), - imp: sovrnImps, - site:{ - domain: domain, - page: page - } - }; - - var scriptUrl = '//'+sovrnUrl+'?callback=window.pbjs.sovrnResponse' + - '&br=' + encodeURIComponent(JSON.stringify(sovrnBidReq)); - adloader.loadScript(scriptUrl, null); - } - - //expose the callback to the global object: - pbjs.sovrnResponse = function(sovrnResponseObj) { - var bid = {}; - // valid object? - if (sovrnResponseObj && sovrnResponseObj.id) { - // valid object w/ bid responses? - if (sovrnResponseObj.seatbid && sovrnResponseObj.seatbid.length !==0 && sovrnResponseObj.seatbid[0].bid && sovrnResponseObj.seatbid[0].bid.length !== 0) { - - sovrnResponseObj.seatbid[0].bid.forEach(function(sovrnBid){ - - var responseCPM; - var placementCode = ''; - var id = sovrnBid.impid; - - // try to fetch the bid request we sent Sovrn - var bidObj = bidmanager.getPlacementIdByCBIdentifer(id); - if (bidObj){ - placementCode = bidObj.placementCode; - bidObj.status = CONSTANTS.STATUS.GOOD; - - //place ad response on bidmanager._adResponsesByBidderId - responseCPM = parseFloat(sovrnBid.price); - - if(responseCPM !== 0) { - sovrnBid.placementCode = placementCode; - sovrnBid.size = bidObj.sizes; - var responseAd = sovrnBid.adm; - - // build impression url from response - var responseNurl = ''; - - //store bid response - //bid status is good (indicating 1) - bid = bidfactory.createBid(1); - bid.creative_id = sovrnBid.Id; - bid.bidderCode = 'sovrn'; - bid.cpm = responseCPM; - - //set ad content + impression url - // sovrn returns ' + - ''; - }, - /** - * Bid response builder. - * - * @param {Object} slotCriteria - Yieldbot bid criteria - * @private - */ - buildBid: function(slotCriteria) { - var bid = {}; - - if (slotCriteria && slotCriteria.ybot_ad && slotCriteria.ybot_ad !== 'n') { - - bid = bidfactory.createBid(ybotlib.BID_STATUS.AVAILABLE); - - bid.cpm = parseInt(slotCriteria.ybot_cpm) / 100.0 || 0; // Yieldbot CPM bids are in cents - - var szArr = slotCriteria.ybot_size ? slotCriteria.ybot_size.split('x') : [0,0], - slot = slotCriteria.ybot_slot || '', - sizeStr = slotCriteria.ybot_size || ''; // Creative template needs the dimensions string - - bid.width = szArr[0] || 0; - bid.height = szArr[1] || 0; - - bid.ad = ybotlib.buildCreative(slot, sizeStr); - - // Add Yieldbot parameters to allow publisher bidderSettings.yieldbot specific targeting - for (var k in slotCriteria) { - bid[k] = slotCriteria[k]; - } - - } else { - bid = bidfactory.createBid(ybotlib.BID_STATUS.EMPTY); - } - - bid.bidderCode = 'yieldbot'; - return bid; - }, - /** - * Yieldbot implementation of {@link module:adaptermanger.callBids} - * @param {Object} params - Adapter bid configuration object - * @private - */ - callBids: function(params) { - - var bids = params.bids || [], - ybotq = window.ybotq || []; - - ybotlib.pageLevelOption = false; - - ybotq.push(function () { - var yieldbot = window.yieldbot; - - utils._each(bids, function(v) { - var bid = v, - psn = bid.params && bid.params.psn || 'ERROR_DEFINE_YB_PSN', - slot = bid.params && bid.params.slot || 'ERROR_DEFINE_YB_SLOT'; - - yieldbot.pub(psn); - yieldbot.defineSlot(slot, {sizes: bid.sizes || []}); - - var cbId = utils.getUniqueIdentifierStr(); - bidmanager.pbCallbackMap[cbId] = bid; - ybotlib.definedSlots.push(cbId); - }); - - yieldbot.enableAsync(); - yieldbot.go(); - }); - - ybotq.push(function () { - ybotlib.handleUpdateState(); - }); - - adloader.loadScript('//cdn.yldbt.com/js/yieldbot.intent.js'); - }, - /** - * Yieldbot bid request callback handler. - * - * @see {@link YieldbotAdapter~_callBids} - * @private - */ - handleUpdateState: function() { - var yieldbot = window.yieldbot; - - utils._each(ybotlib.definedSlots, function(v) { - var slot, - criteria, - placementCode, - adapterConfig; - - adapterConfig = bidmanager.getPlacementIdByCBIdentifer(v) || {}; - slot = adapterConfig.params.slot || ''; - criteria = yieldbot.getSlotCriteria(slot); - - placementCode = adapterConfig.placementCode || 'ERROR_YB_NO_PLACEMENT'; - var bid = ybotlib.buildBid(criteria); - - bidmanager.addBidResponse(placementCode, bid); - - }); - } - } - return { - callBids: ybotlib.callBids - }; -}; - -module.exports = YieldbotAdapter; +/** + * @overview Yieldbot sponsored Prebid.js adapter. + * @author elljoh + */ +var adloader = require('../adloader'); +var bidfactory = require('../bidfactory'); +var bidmanager = require('../bidmanager'); +var utils = require('../utils'); + +/** + * Adapter for requesting bids from Yieldbot. + * + * @returns {Object} Object containing implementation for invocation in {@link module:adaptermanger.callBids} + * @class + */ +var YieldbotAdapter = function YieldbotAdapter() { + + window.ybotq = window.ybotq || []; + + var ybotlib = { + BID_STATUS: { + PENDING: 0, + AVAILABLE: 1, + EMPTY: 2 + }, + definedSlots: [], + pageLevelOption: false, + /** + * Builds the Yieldbot creative tag. + * + * @param {String} slot - The slot name to bid for + * @param {String} size - The dimenstions of the slot + * @private + */ + buildCreative: function (slot, size) { + return '' + + ''; + }, + /** + * Bid response builder. + * + * @param {Object} slotCriteria - Yieldbot bid criteria + * @private + */ + buildBid: function (slotCriteria) { + var bid = {}; + + if (slotCriteria && slotCriteria.ybot_ad && slotCriteria.ybot_ad !== 'n') { + + bid = bidfactory.createBid(ybotlib.BID_STATUS.AVAILABLE); + + bid.cpm = parseInt(slotCriteria.ybot_cpm) / 100.0 || 0; // Yieldbot CPM bids are in cents + + var szArr = slotCriteria.ybot_size ? slotCriteria.ybot_size.split('x') : [0, 0]; + var slot = slotCriteria.ybot_slot || ''; + var sizeStr = slotCriteria.ybot_size || ''; // Creative template needs the dimensions string + + bid.width = szArr[0] || 0; + bid.height = szArr[1] || 0; + + bid.ad = ybotlib.buildCreative(slot, sizeStr); + + // Add Yieldbot parameters to allow publisher bidderSettings.yieldbot specific targeting + for (var k in slotCriteria) { + bid[k] = slotCriteria[k]; + } + + } else { + bid = bidfactory.createBid(ybotlib.BID_STATUS.EMPTY); + } + + bid.bidderCode = 'yieldbot'; + return bid; + }, + /** + * Yieldbot implementation of {@link module:adaptermanger.callBids} + * @param {Object} params - Adapter bid configuration object + * @private + */ + callBids: function (params) { + + var bids = params.bids || []; + var ybotq = window.ybotq || []; + + ybotlib.pageLevelOption = false; + + ybotq.push(function () { + var yieldbot = window.yieldbot; + + utils._each(bids, function (v) { + var bid = v; + var psn = bid.params && bid.params.psn || 'ERROR_DEFINE_YB_PSN'; + var slot = bid.params && bid.params.slot || 'ERROR_DEFINE_YB_SLOT'; + + yieldbot.pub(psn); + yieldbot.defineSlot(slot, { sizes: bid.sizes || [] }); + + ybotlib.definedSlots.push(bid.bidId); + }); + + yieldbot.enableAsync(); + yieldbot.go(); + }); + + ybotq.push(function () { + ybotlib.handleUpdateState(); + }); + + adloader.loadScript('//cdn.yldbt.com/js/yieldbot.intent.js'); + }, + /** + * Yieldbot bid request callback handler. + * + * @see {@link YieldbotAdapter~_callBids} + * @private + */ + handleUpdateState: function () { + var yieldbot = window.yieldbot; + + utils._each(ybotlib.definedSlots, function (v) { + var slot; + var criteria; + var placementCode; + var adapterConfig; + + adapterConfig = pbjs._bidsRequested + .find(bidderRequest => bidderRequest.bidderCode === 'yieldbot').bids + .find(bid => bid.bidId === v) || {}; + slot = adapterConfig.params.slot || ''; + criteria = yieldbot.getSlotCriteria(slot); + + placementCode = adapterConfig.placementCode || 'ERROR_YB_NO_PLACEMENT'; + var bid = ybotlib.buildBid(criteria); + + bidmanager.addBidResponse(placementCode, bid); + + }); + } + }; + return { + callBids: ybotlib.callBids + }; +}; + +module.exports = YieldbotAdapter; diff --git a/src/adloader.js b/src/adloader.js index 746211a50c0..9a527d53e33 100644 --- a/src/adloader.js +++ b/src/adloader.js @@ -1,60 +1,97 @@ -var utils = require('./utils'); -//add a script tag to the page, used to add /jpt call to page -exports.loadScript = function(tagSrc, callback) { - if(!tagSrc){ - utils.logError('Error attempting to request empty URL', 'adloader.js:loadScript'); - return; - } - var jptScript = document.createElement('script'); - jptScript.type = 'text/javascript'; - jptScript.async = true; - - - // Execute a callback if necessary - if (callback && typeof callback === "function") { - if (jptScript.readyState) { - jptScript.onreadystatechange = function() { - if (jptScript.readyState == "loaded" || jptScript.readyState == "complete") { - jptScript.onreadystatechange = null; - callback(); - } - }; - } else { - jptScript.onload = function() { - callback(); - }; - } - } - - //call function to build the JPT call - jptScript.src = tagSrc; - - //add the new script tag to the page - var elToAppend = document.getElementsByTagName('head'); - elToAppend = elToAppend.length ? elToAppend : document.getElementsByTagName('body'); - if (elToAppend.length) { - elToAppend = elToAppend[0]; - elToAppend.insertBefore(jptScript, elToAppend.firstChild); - } -}; - -exports.trackPixel = function(pixelUrl) { - //track a impbus tracking pixel - - //TODO: Decide of tracking via AJAX is sufficent, or do we need to - //run impression trackers via page pixels? - try { - - //add a cachebuster so we don't end up dropping any impressions - pixelUrl += '&rnd=' + Math.random(); - - if (pixelUrl) { - var img = document.createElement('img'); - img.src = pixelUrl; - } - - - } catch (e) { - - } -}; \ No newline at end of file +var utils = require('./utils'); +let _requestCache = {}; + +//add a script tag to the page, used to add /jpt call to page +exports.loadScript = function (tagSrc, callback, cacheRequest) { + if (!tagSrc) { + utils.logError('Error attempting to request empty URL', 'adloader.js:loadScript'); + return; + } + + if (cacheRequest) { + if (_requestCache[tagSrc]) { + if (_requestCache[tagSrc].loaded) { + //invokeCallbacks immediately + callback(); + } else { + //queue the callback + _requestCache[tagSrc].callbacks.push(callback); + } + } else { + _requestCache[tagSrc] = { + loaded:false, + callbacks:[] + }; + _requestCache[tagSrc].callbacks.push(callback); + requestResource(tagSrc, function() { + _requestCache[tagSrc].loaded = true; + try { + for (let i = 0; i < _requestCache[tagSrc].callbacks.length; i++) { + _requestCache[tagSrc].callbacks[i](); + } + } + catch (e) { + utils.logError('Error executing callback', 'adloader.js:loadScript', e); + } + }); + } + } + + //trigger one time request + else { + requestResource(tagSrc, callback); + } + +}; + +function requestResource(tagSrc, callback) { + var jptScript = document.createElement('script'); + jptScript.type = 'text/javascript'; + jptScript.async = true; + + // Execute a callback if necessary + if (callback && typeof callback === 'function') { + if (jptScript.readyState) { + jptScript.onreadystatechange = function () { + if (jptScript.readyState === 'loaded' || jptScript.readyState === 'complete') { + jptScript.onreadystatechange = null; + callback(); + } + }; + } else { + jptScript.onload = function () { + callback(); + }; + } + } + + jptScript.src = tagSrc; + + //add the new script tag to the page + var elToAppend = document.getElementsByTagName('head'); + elToAppend = elToAppend.length ? elToAppend : document.getElementsByTagName('body'); + if (elToAppend.length) { + elToAppend = elToAppend[0]; + elToAppend.insertBefore(jptScript, elToAppend.firstChild); + } +} + +//track a impbus tracking pixel +//TODO: Decide if tracking via AJAX is sufficent, or do we need to +//run impression trackers via page pixels? +exports.trackPixel = function (pixelUrl) { + let delimiter; + let trackingPixel; + + if (!pixelUrl || typeof (pixelUrl) !== 'string') { + utils.logMessage('Missing or invalid pixelUrl.'); + return; + } + + delimiter = pixelUrl.indexOf('?') > 0 ? '&' : '?'; + + //add a cachebuster so we don't end up dropping any impressions + trackingPixel = pixelUrl + delimiter + 'rnd=' + Math.floor(Math.random() * 1E7); + (new Image()).src = trackingPixel; + return trackingPixel; +}; diff --git a/src/bidfactory.js b/src/bidfactory.js index 364c38d28e8..67bf5dae7d4 100644 --- a/src/bidfactory.js +++ b/src/bidfactory.js @@ -1,59 +1,54 @@ -var utils = require('./utils.js'); - -/** - Required paramaters - bidderCode, - height, - width, - statusCode - Optional paramaters - adId, - cpm, - ad, - adUrl, - dealId, - priceKeyString; - */ -function Bid(statusCode) { - var _bidId = utils.getUniqueIdentifierStr(), - _statusCode = statusCode || 0; - this.bidderCode = ''; - this.width = 0; - this.height = 0; - this.statusMessage = _getStatus(); - this.adId = _bidId; - - function _getStatus() { - switch (_statusCode) { - case 0: - return 'Pending'; - case 1: - return 'Bid available'; - case 2: - return 'Bid returned empty or error response'; - case 3: - return 'Bid timed out'; - } - } - this.getStatusCode = function() { - return _statusCode; - }; - - function _setStatusCode(status) { - this._statusCode = status; - //update status msg - this._statusMessage = this._getStatus(); - } - //returns the size of the bid creative. Concatenation of width and height by ‘x’. - this.getSize = function() { - return this.width + 'x' + this.height; - }; - -} - -// Bid factory function. -exports.createBid = function(statusCde) { - return new Bid(statusCde); -}; - -//module.exports = Bid; \ No newline at end of file +var utils = require('./utils.js'); + +/** + Required paramaters + bidderCode, + height, + width, + statusCode + Optional paramaters + adId, + cpm, + ad, + adUrl, + dealId, + priceKeyString; + */ +function Bid(statusCode) { + var _bidId = utils.getUniqueIdentifierStr(); + var _statusCode = statusCode || 0; + + this.bidderCode = ''; + this.width = 0; + this.height = 0; + this.statusMessage = _getStatus(); + this.adId = _bidId; + + function _getStatus() { + switch (_statusCode) { + case 0: + return 'Pending'; + case 1: + return 'Bid available'; + case 2: + return 'Bid returned empty or error response'; + case 3: + return 'Bid timed out'; + } + } + + this.getStatusCode = function () { + return _statusCode; + }; + + //returns the size of the bid creative. Concatenation of width and height by ‘x’. + this.getSize = function () { + return this.width + 'x' + this.height; + }; + +} + +// Bid factory function. +exports.createBid = function (statusCode) { + return new Bid(statusCode); +}; diff --git a/src/bidmanager.js b/src/bidmanager.js index 09ddb7604c0..c763d85cfa4 100644 --- a/src/bidmanager.js +++ b/src/bidmanager.js @@ -1,467 +1,398 @@ -var CONSTANTS = require('./constants.json'); -var utils = require('./utils.js'); -var adaptermanager = require('./adaptermanager'); -var events = require('./events'); - -var objectType_function = 'function'; -var objectType_undefined = 'undefined'; - -var externalCallbackByAdUnitArr = []; -var externalCallbackArr = []; -var externalOneTimeCallback = null; -var biddersByPlacementMap = {}; - -var pbCallbackMap = {}; -exports.pbCallbackMap = pbCallbackMap; - -var pbBidResponseByPlacement = {}; -exports.pbBidResponseByPlacement = pbBidResponseByPlacement; - -//this is used to look up the bid by bid ID later -var _adResponsesByBidderId = {}; -exports._adResponsesByBidderId = _adResponsesByBidderId; - -var bidResponseReceivedCount = {}; -exports.bidResponseReceivedCount = bidResponseReceivedCount; - -var expectedBidsCount = {}; - -var _allBidsAvailable = false; - -var _callbackExecuted = false; - -var defaultBidderSettingsMap = {}; -var bidderStartTimes = {}; - -exports.getPlacementIdByCBIdentifer = function(id) { - return pbCallbackMap[id]; -}; - - -exports.getBidResponseByAdUnit = function(adUnitCode) { - return pbBidResponseByPlacement; - -}; - - -exports.clearAllBidResponses = function(adUnitCode) { - _allBidsAvailable = false; - _callbackExecuted = false; - - //init bid response received count - initbidResponseReceivedCount(); - //init expected bids count - initExpectedBidsCount(); - //clear the callback handler flag - externalCallbackArr.called = false; - - for (var prop in this.pbBidResponseByPlacement) { - delete this.pbBidResponseByPlacement[prop]; - } -}; - -/** - * Returns a list of bidders that we haven't received a response yet - * @return {array} [description] - */ -exports.getTimedOutBidders = function(){ - var bidderArr = []; - utils._each(bidResponseReceivedCount,function(count,bidderCode){ - if(count === 0){ - bidderArr.push(bidderCode); - } - }); - - return bidderArr; -}; - -function initbidResponseReceivedCount(){ - - bidResponseReceivedCount = {}; - - for(var i=0; i pbjs._bidsReceived + .map(getBidders) + .filter(uniques) + .indexOf(bidder) < 0); +}; + +function timestamp() { return new Date().getTime(); } + +function getBidderCode(bidSet) { + return bidSet.bidderCode; +} + +function getBidders(bid) { + return bid.bidder; +} + +function bidsBackAdUnit(adUnitCode) { + const requested = pbjs.adUnits.find(unit => unit.code === adUnitCode).bids.length; + const received = pbjs._bidsReceived.filter(bid => bid.adUnitCode === adUnitCode).length; + return requested === received; +} + +function add(a, b) { + return a + b; +} + +function bidsBackAll() { + const requested = pbjs._bidsRequested.map(bidSet => bidSet.bids.length).reduce(add); + const received = pbjs._bidsReceived.length; + return requested === received; +} + +exports.bidsBackAll = function() { + return bidsBackAll(); +}; + +function getBidSetForBidder(bidder) { + return pbjs._bidsRequested.find(bidSet => bidSet.bidderCode === bidder); +} + +/* + * This function should be called to by the bidder adapter to register a bid response + */ +exports.addBidResponse = function (adUnitCode, bid) { + if (bid) { + Object.assign(bid, { + responseTimestamp: timestamp(), + requestTimestamp: getBidSetForBidder(bid.bidderCode).start, + cpm: bid.cpm || 0, + bidder: bid.bidderCode, + adUnitCode + }); + bid.timeToRespond = bid.responseTimestamp - bid.requestTimestamp; + + //emit the bidAdjustment event before bidResponse, so bid response has the adjusted bid value + events.emit(CONSTANTS.EVENTS.BID_ADJUSTMENT, bid); + + //emit the bidResponse event + events.emit(CONSTANTS.EVENTS.BID_RESPONSE, adUnitCode, bid); + + //append price strings + const priceStringsObj = getPriceBucketString(bid.cpm, bid.height, bid.width); + bid.pbLg = priceStringsObj.low; + bid.pbMg = priceStringsObj.med; + bid.pbHg = priceStringsObj.high; + bid.pbAg = priceStringsObj.auto; + bid.pbDg = priceStringsObj.dense; + + //if there is any key value pairs to map do here + var keyValues = {}; + if (bid.bidderCode && bid.cpm !== 0) { + keyValues = getKeyValueTargetingPairs(bid.bidderCode, bid); + + if (bid.dealId) { + keyValues[`hb_deal_${bid.bidderCode}`] = bid.dealId; + } + + bid.adserverTargeting = keyValues; + } + + pbjs._bidsReceived.push(bid); + } + + if (bidsBackAdUnit(bid.adUnitCode)) { + triggerAdUnitCallbacks(bid.adUnitCode); + } + + if (bidsBackAll()) { + this.executeCallback(); + } + + if (bid.timeToRespond > pbjs.bidderTimeout) { + + events.emit(CONSTANTS.EVENTS.BID_TIMEOUT, this.getTimedOutBidders()); + this.executeCallback(); + } +}; + +function getKeyValueTargetingPairs(bidderCode, custBidObj) { + var keyValues = {}; + var bidder_settings = pbjs.bidderSettings || {}; + + //1) set the keys from "standard" setting or from prebid defaults + if (custBidObj && bidder_settings) { + if (!bidder_settings[CONSTANTS.JSON_MAPPING.BD_SETTING_STANDARD]) { + bidder_settings[CONSTANTS.JSON_MAPPING.BD_SETTING_STANDARD] = { + adserverTargeting: [ + { + key: 'hb_bidder', + val: function (bidResponse) { + return bidResponse.bidderCode; + } + }, { + key: 'hb_adid', + val: function (bidResponse) { + return bidResponse.adId; + } + }, { + key: 'hb_pb', + val: function (bidResponse) { + if (_granularity === CONSTANTS.GRANULARITY_OPTIONS.AUTO) { + return bidResponse.pbAg; + } else if (_granularity === CONSTANTS.GRANULARITY_OPTIONS.DENSE) { + return bidResponse.pbDg; + } else if (_granularity === CONSTANTS.GRANULARITY_OPTIONS.LOW) { + return bidResponse.pbLg; + } else if (_granularity === CONSTANTS.GRANULARITY_OPTIONS.MEDIUM) { + return bidResponse.pbMg; + } else if (_granularity === CONSTANTS.GRANULARITY_OPTIONS.HIGH) { + return bidResponse.pbHg; + } + } + }, { + key: 'hb_size', + val: function (bidResponse) { + return bidResponse.size; + + } + } + ] + }; + } + + setKeys(keyValues, bidder_settings[CONSTANTS.JSON_MAPPING.BD_SETTING_STANDARD], custBidObj); + } + + //2) set keys from specific bidder setting override if they exist + if (bidderCode && custBidObj && bidder_settings && bidder_settings[bidderCode] && bidder_settings[bidderCode][CONSTANTS.JSON_MAPPING.ADSERVER_TARGETING]) { + setKeys(keyValues, bidder_settings[bidderCode], custBidObj); + custBidObj.alwaysUseBid = bidder_settings[bidderCode].alwaysUseBid; + } + + //2) set keys from standard setting. NOTE: this API doesn't seem to be in use by any Adapter + else if (defaultBidderSettingsMap[bidderCode]) { + setKeys(keyValues, defaultBidderSettingsMap[bidderCode], custBidObj); + custBidObj.alwaysUseBid = defaultBidderSettingsMap[bidderCode].alwaysUseBid; + } + + return keyValues; +} + +exports.getKeyValueTargetingPairs = function() { + return getKeyValueTargetingPairs(...arguments); +}; + +function setKeys(keyValues, bidderSettings, custBidObj) { + var targeting = bidderSettings[CONSTANTS.JSON_MAPPING.ADSERVER_TARGETING]; + custBidObj.size = custBidObj.getSize(); + + utils._each(targeting, function (kvPair) { + var key = kvPair.key; + var value = kvPair.val; + + if (keyValues[key]) { + utils.logWarn('The key: ' + key + ' is getting ovewritten'); + } + + if (utils.isFn(value)) { + try { + keyValues[key] = value(custBidObj); + } catch (e) { + utils.logError('bidmanager', 'ERROR', e); + } + } else { + keyValues[key] = value; + } + }); + + return keyValues; +} + +exports.setPriceGranularity = function setPriceGranularity(granularity) { + var granularityOptions = CONSTANTS.GRANULARITY_OPTIONS; + if (Object.keys(granularityOptions).filter(option => granularity === granularityOptions[option])) { + _granularity = granularity; + } else { + utils.logWarn('Prebid Warning: setPriceGranularity was called with invalid setting, using' + + ' `medium` as default.'); + _granularity = CONSTANTS.GRANULARITY_OPTIONS.MEDIUM; + } +}; + +exports.registerDefaultBidderSetting = function (bidderCode, defaultSetting) { + defaultBidderSettingsMap[bidderCode] = defaultSetting; +}; + +exports.executeCallback = function () { + if (externalCallbackArr.called !== true) { + processCallbacks(externalCallbackArr); + externalCallbackArr.called = true; + } + + //execute one time callback + if (externalOneTimeCallback) { + processCallbacks([externalOneTimeCallback]); + externalOneTimeCallback = null; + } + + pbjs.clearAuction(); +}; + +function triggerAdUnitCallbacks(adUnitCode) { + //todo : get bid responses and send in args + var params = [adUnitCode]; + processCallbacks(externalCallbackByAdUnitArr, params); +} + +function processCallbacks(callbackQueue) { + var i; + if (utils.isArray(callbackQueue)) { + for (i = 0; i < callbackQueue.length; i++) { + var func = callbackQueue[i]; + func.call(pbjs, pbjs._bidsReceived.reduce(groupByPlacement, {})); + } + } +} + +/** + * groupByPlacement is a reduce function that converts an array of Bid objects + * to an object with placement codes as keys, with each key representing an object + * with an array of `Bid` objects for that placement + * @param prev previous value as accumulator object + * @param item current array item + * @param idx current index + * @param arr the array being reduced + * @returns {*} as { [adUnitCode]: { bids: [Bid, Bid, Bid] } } + */ +function groupByPlacement(prev, item, idx, arr) { + // this uses a standard "array to map" operation that could be abstracted further + if (item.adUnitCode in Object.keys(prev)) { + // if the adUnitCode key is present in the accumulator object, continue + return prev; + } else { + // otherwise add the adUnitCode key to the accumulator object and set to an object with an + // array of Bids for that adUnitCode + prev[item.adUnitCode] = { + bids: arr.filter(bid => bid.adUnitCode === item.adUnitCode) + }; + return prev; + } +} + +/** + * Add a one time callback, that is discarded after it is called + * @param {Function} callback [description] + */ +exports.addOneTimeCallback = function (callback) { + externalOneTimeCallback = callback; +}; + +exports.addCallback = function (id, callback, cbEvent) { + callback.id = id; + if (CONSTANTS.CB.TYPE.ALL_BIDS_BACK === cbEvent) { + externalCallbackArr.push(callback); + } else if (CONSTANTS.CB.TYPE.AD_UNIT_BIDS_BACK === cbEvent) { + externalCallbackByAdUnitArr.push(callback); + } +}; + +//register event for bid adjustment +events.on(CONSTANTS.EVENTS.BID_ADJUSTMENT, function (bid) { + adjustBids(bid); +}); + +function adjustBids(bid) { + var code = bid.bidderCode; + var bidPriceAdjusted = bid.cpm; + if (code && pbjs.bidderSettings && pbjs.bidderSettings[code]) { + if (typeof pbjs.bidderSettings[code].bidCpmAdjustment === objectType_function) { + try { + bidPriceAdjusted = pbjs.bidderSettings[code].bidCpmAdjustment.call(null, bid.cpm); + } + catch (e) { + utils.logError('Error during bid adjustment', 'bidmanager.js', e); + } + } + } + + if (bidPriceAdjusted !== 0) { + bid.cpm = bidPriceAdjusted; + } +} + +function getPriceBucketString(cpm) { + var cpmFloat = 0; + var returnObj = { + low: '', + med: '', + high: '', + auto: '', + dense: '' + }; + try { + cpmFloat = parseFloat(cpm); + if (cpmFloat) { + //round to closest .5 + if (cpmFloat > _lgPriceCap) { + returnObj.low = _lgPriceCap.toFixed(2); + } else { + returnObj.low = (Math.floor(cpm * 2) / 2).toFixed(2); + } + + //round to closest .1 + if (cpmFloat > _mgPriceCap) { + returnObj.med = _mgPriceCap.toFixed(2); + } else { + returnObj.med = (Math.floor(cpm * 10) / 10).toFixed(2); + } + + //round to closest .01 + if (cpmFloat > _hgPriceCap) { + returnObj.high = _hgPriceCap.toFixed(2); + } else { + returnObj.high = (Math.floor(cpm * 100) / 100).toFixed(2); + } + + // round auto default sliding scale + if (cpmFloat <= 5) { + // round to closest .05 + returnObj.auto = (Math.floor(cpm * 20) / 20).toFixed(2); + } else if (cpmFloat <= 10) { + // round to closest .10 + returnObj.auto = (Math.floor(cpm * 10) / 10).toFixed(2); + } else if (cpmFloat <= 20) { + // round to closest .50 + returnObj.auto = (Math.floor(cpm * 2) / 2).toFixed(2); + } else { + // cap at 20.00 + returnObj.auto = '20.00'; + } + + // dense mode + if (cpmFloat <= 3) { + // round to closest .01 + returnObj.dense = (Math.floor(cpm * 100) / 100).toFixed(2); + } else if (cpmFloat <= 8) { + // round to closest .05 + returnObj.dense = (Math.floor(cpm * 20) / 20).toFixed(2); + } else if (cpmFloat <= 20) { + // round to closest .50 + returnObj.dense = (Math.floor(cpm * 2) / 2).toFixed(2); + } else { + // cap at 20.00 + returnObj.dense = '20.00'; + } + } + } catch (e) { + this.logError('Exception parsing CPM :' + e.message); + } + + return returnObj; +} diff --git a/src/constants.json b/src/constants.json index 04f7e7d7200..8ab59678f42 100644 --- a/src/constants.json +++ b/src/constants.json @@ -1,37 +1,53 @@ -{ - "JSON_MAPPING": { - "PL_CODE": "code", - "PL_SIZE": "sizes", - "PL_BIDS": "bids", - "BD_BIDDER": "bidder", - "BD_ID": "paramsd", - "BD_PL_ID": "placementId", - "ADSERVER_TARGETING": "adserverTargeting", - "BD_SETTING_STANDARD" : "standard" - }, - "DEBUG_MODE": "pbjs_debug", - "STATUS": { - "GOOD": "good", - "TIMEOUT": "timed out" - }, - "CB" : { - "TYPE" : { - "ALL_BIDS_BACK" : "allRequestedBidsBack", - "AD_UNIT_BIDS_BACK" : "adUnitBidsBack" - } - }, - "objectType_function" : "function", - "objectType_undefined" : "undefined", - "objectType_object" : "object", - "objectType_string" : "string", - "objectType_number" : "number", - - - "EVENTS" : { - "BID_ADJUSTMENT" : "bidAdjustment", - "BID_TIMEOUT" : "bidTimeout", - "BID_REQUESTED" : "bidRequested", - "BID_RESPONSE" : "bidResponse", - "BID_WON" : "bidWon" - } -} +{ + "JSON_MAPPING": { + "PL_CODE": "code", + "PL_SIZE": "sizes", + "PL_BIDS": "bids", + "BD_BIDDER": "bidder", + "BD_ID": "paramsd", + "BD_PL_ID": "placementId", + "ADSERVER_TARGETING": "adserverTargeting", + "BD_SETTING_STANDARD": "standard" + }, + "REPO_AND_VERSION": "%%REPO_AND_VERSION%%", + "DEBUG_MODE": "pbjs_debug", + "STATUS": { + "GOOD": 1, + "NO_BID": 2 + }, + "CB": { + "TYPE": { + "ALL_BIDS_BACK": "allRequestedBidsBack", + "AD_UNIT_BIDS_BACK": "adUnitBidsBack", + "BID_WON": "bidWon" + } + }, + "objectType_function": "function", + "objectType_undefined": "undefined", + "objectType_object": "object", + "objectType_string": "string", + "objectType_number": "number", + "EVENTS": { + "BID_ADJUSTMENT": "bidAdjustment", + "BID_TIMEOUT": "bidTimeout", + "BID_REQUESTED": "bidRequested", + "BID_RESPONSE": "bidResponse", + "BID_WON": "bidWon" + }, + "EVENT_ID_PATHS": { + "bidWon": "adUnitCode" + }, + "GRANULARITY_OPTIONS": { + "LOW": "low", + "MEDIUM": "medium", + "HIGH": "high", + "AUTO": "auto", + "DENSE": "dense" + }, + "TARGETING_KEYS": [ + "hb_bidder", + "hb_adid", + "hb_pb", + "hb_size" + ] +} diff --git a/src/events.js b/src/events.js index 068104da894..f2ea52ea8e5 100644 --- a/src/events.js +++ b/src/events.js @@ -1,91 +1,153 @@ -/** - * events.js - */ -var utils = require('./utils'), -CONSTANTS = require('./constants'), - slice = Array.prototype.slice; - -//define entire events -//var allEvents = ['bidRequested','bidResponse','bidWon','bidTimeout']; -var allEvents = utils._map(CONSTANTS.EVENTS, function (v){ return v; }); -//keep a record of all events fired -var eventsFired = []; - -module.exports = (function (){ - - var _handlers = {}, - _public = {}; - - function _dispatch(event, args) { - utils.logMessage('Emitting event for: ' + event ); - //record the event: - eventsFired.push({ - eventType : event, - args : args - }); - utils._each(_handlers[event], function (fn) { - if (!fn) return; - try{ - fn.apply(null, args); - } - catch(e){ - utils.logError('Error executing handler:', 'events.js', e); - } - - }); - } - - function _checkAvailableEvent(event){ - return utils.contains(allEvents,event); - } - - _public.on = function (event, handler) { - - //check whether available event or not - if(_checkAvailableEvent(event)){ - _handlers[event] = _handlers[event] || []; - _handlers[event].push(handler); - } - else{ - utils.logError('Wrong event name : ' + event + ' Valid event names :' + allEvents); - } - }; - - _public.emit = function (event) { - var args = slice.call(arguments, 1); - _dispatch(event, args); - }; - - _public.off = function (event, id, handler) { - if (utils.isEmpty(_handlers[event])) { - return; - } - - utils._each(_handlers[event],function(h){ - if(h[id] !== null && h[id] !== undefined ){ - if(typeof handler === 'undefined' || h[id] === handler){ - h[id] = null; - } - } - }); - }; - - _public.get = function(){ - return _handlers; - }; - - /** - * This method can return a copy of all the events fired - * @return {array[object]} array of events fired - */ - _public.getEvents = function(){ - var arrayCopy = []; - utils._each(eventsFired, function(value){ - var newProp = utils.extend({}, value); - arrayCopy.push(newProp); - }); - return arrayCopy; - }; - - return _public; -}()); +/** + * events.js + */ +var utils = require('./utils'); +var CONSTANTS = require('./constants'); +var slice = Array.prototype.slice; +var push = Array.prototype.push; + +//define entire events +//var allEvents = ['bidRequested','bidResponse','bidWon','bidTimeout']; +var allEvents = utils._map(CONSTANTS.EVENTS, function (v) { + return v; +}); + +var idPaths = CONSTANTS.EVENT_ID_PATHS; + +//keep a record of all events fired +var eventsFired = []; + +module.exports = (function () { + + var _handlers = {}; + var _public = {}; + + /** + * + * @param {String} eventString The name of the event. + * @param {Array} args The payload emitted with the event. + * @private + */ + function _dispatch(eventString, args) { + utils.logMessage('Emitting event for: ' + eventString); + + var eventPayload = args[0]; + var idPath = idPaths[eventString]; + var key = eventPayload[idPath]; + var event = _handlers[eventString] || { que: [] }; + var eventKeys = utils._map(event, function (v, k) { + return k; + }); + + var callbacks = []; + + //record the event: + eventsFired.push({ + eventType: eventString, + args: eventPayload, + id: key + }); + + /** Push each specific callback to the `callbacks` array. + * If the `event` map has a key that matches the value of the + * event payload id path, e.g. `eventPayload[idPath]`, then apply + * each function in the `que` array as an argument to push to the + * `callbacks` array + * */ + if (key && utils.contains(eventKeys, key)) { + push.apply(callbacks, event[key].que); + } + + /** Push each general callback to the `callbacks` array. */ + push.apply(callbacks, event.que); + + /** call each of the callbacks */ + utils._each(callbacks, function (fn) { + if (!fn) return; + try { + fn.apply(null, args); + } + catch (e) { + utils.logError('Error executing handler:', 'events.js', e); + } + }); + } + + function _checkAvailableEvent(event) { + return utils.contains(allEvents, event); + } + + _public.on = function (eventString, handler, id) { + + //check whether available event or not + if (_checkAvailableEvent(eventString)) { + var event = _handlers[eventString] || { que: [] }; + + if (id) { + event[id] = event[id] || { que: [] }; + event[id].que.push(handler); + } else { + event.que.push(handler); + } + + _handlers[eventString] = event; + } else { + utils.logError('Wrong event name : ' + eventString + ' Valid event names :' + allEvents); + } + }; + + _public.emit = function (event) { + var args = slice.call(arguments, 1); + _dispatch(event, args); + }; + + _public.off = function (eventString, handler, id) { + var event = _handlers[eventString]; + + if (utils.isEmpty(event) || utils.isEmpty(event.que) && utils.isEmpty(event[id])) { + return; + } + + if (id && (utils.isEmpty(event[id]) || utils.isEmpty(event[id].que))) { + return; + } + + if (id) { + utils._each(event[id].que, function (_handler) { + var que = event[id].que; + if (_handler === handler) { + que.splice(utils.indexOf.call(que, _handler), 1); + } + }); + } else { + utils._each(event.que, function (_handler) { + var que = event.que; + if (_handler === handler) { + que.splice(utils.indexOf.call(que, _handler), 1); + } + }); + } + + _handlers[eventString] = event; + }; + + _public.get = function () { + return _handlers; + }; + + /** + * This method can return a copy of all the events fired + * @return {Array} array of events fired + */ + _public.getEvents = function () { + var arrayCopy = []; + utils._each(eventsFired, function (value) { + var newProp = utils.extend({}, value); + arrayCopy.push(newProp); + }); + + return arrayCopy; + }; + + return _public; +}()); diff --git a/src/ga.js b/src/ga.js index 83d72addc07..e5966728a41 100644 --- a/src/ga.js +++ b/src/ga.js @@ -1,244 +1,250 @@ -/** - * ga.js - analytics adapter for google analytics - */ - -var events = require('./events'); -var utils = require('./utils'); -var CONSTANTS = require('./constants.json'); - -var BID_REQUESTED = CONSTANTS.EVENTS.BID_REQUESTED; -var BID_TIMEOUT = CONSTANTS.EVENTS.BID_TIMEOUT; -var BID_RESPONSE = CONSTANTS.EVENTS.BID_RESPONSE; -var BID_WON = CONSTANTS.EVENTS.BID_WON; - -var _disibleInteraction = { nonInteraction : true }, - _analyticsQueue = [], - _gaGlobal = null, - _enableCheck = true, - _category = 'Prebid.js Bids', - //to track how many events we are sending to GA. - //GA limits the # of events to be sent see here ==> https://developers.google.com/analytics/devguides/collection/ios/v3/limits-quotas?hl=en - _eventCount = 0, - //limit data sent by leaving this false - _enableDistribution = false, - _timedOutBidders = []; - - -/** - * This will enable sending data to google analytics. Only call once, or duplicate data will be sent! - * @param {object} gaOptions to set distribution and GA global (if renamed); - * @return {[type]} [description] - */ -exports.enableAnalytics = function(gaOptions) { - if(typeof gaOptions.global !== 'undefined'){ - _gaGlobal = gaOptions.global; - } - else{ - //default global is window.ga - _gaGlobal = 'ga'; - } - if(typeof gaOptions.enableDistribution !== 'undefined'){ - _enableDistribution = gaOptions.enableDistribution; - } - - var bid = null; - - //first send all events fired before enableAnalytics called - - var existingEvents = events.getEvents(); - utils._each(existingEvents, function(eventObj) { - var args = eventObj.args; - if (!eventObj) { - return; - } - if (eventObj.eventType === BID_REQUESTED) { - //bid is 1st args - bid = args[0]; - sendBidRequestToGa(bid); - } else if (eventObj.eventType === BID_RESPONSE) { - //bid is 2nd args - bid = args[1]; - sendBidResponseToGa(bid); - - } else if (eventObj.eventType === BID_TIMEOUT) { - var bidderArray = args[0]; - _timedOutBidders = bidderArray; - //todo disable event listeners - - } else if (eventObj.eventType === BID_WON) { - bid = args[0]; - sendBidWonToGa(bid); - } - }); - - //Next register event listeners to send data immediately - - //bidRequests - events.on(BID_REQUESTED, function(bidRequestObj) { - sendBidRequestToGa(bidRequestObj); - }); - - //bidResponses - events.on(BID_RESPONSE, function(adunit, bid) { - sendBidResponseToGa(bid); - sendBidTimeouts(bid); - }); - - //bidTimeouts - events.on(BID_TIMEOUT, function(bidderArray) { - _timedOutBidders = bidderArray; - }); - - //wins - events.on(BID_WON, function(bid) { - sendBidWonToGa(bid); - }); -}; - -/** - * Check if gaGlobal or window.ga is defined on page. If defined execute all the GA commands - */ -function checkAnalytics() { - if (_enableCheck && typeof window[_gaGlobal] === 'function' ) { - - for (var i = 0; i < _analyticsQueue.length; i++) { - _analyticsQueue[i].call(); - } - //override push to execute the command immediately from now on - _analyticsQueue.push = function(fn) { - fn.call(); - }; - //turn check into NOOP - _enableCheck = false; - } - utils.logMessage('event count sent to GA: ' + _eventCount); -} - - -function convertToCents(dollars) { - if (dollars) { - return Math.floor(dollars * 100); - } - return 0; -} - -function getLoadTimeDistribution(time) { - var distribution; - if (time >= 0 && time < 200) { - distribution = '0-200ms'; - } else if (time >= 200 && time < 300) { - distribution = '200-300ms'; - } else if (time >= 300 && time < 400) { - distribution = '300-400ms'; - } else if (time >= 400 && time < 500) { - distribution = '400-500ms'; - } else if (time >= 500 && time < 600) { - distribution = '500-600ms'; - } else if (time >= 600 && time < 800) { - distribution = '600-800ms'; - } else if (time >= 800 && time < 1000) { - distribution = '800-1000ms'; - } else if (time >= 1000 && time < 1200) { - distribution = '1000-1200ms'; - } else if (time >= 1200 && time < 1500) { - distribution = '1200-1500ms'; - } else if (time >= 1500 && time < 2000) { - distribution = '1500-2000ms'; - } else if (time >= 2000) { - distribution = '2000ms above'; - } - - return distribution; -} - - -function getCpmDistribution(cpm) { - var distribution; - if (cpm >= 0 && cpm < 0.5) { - distribution = '$0-0.5'; - } else if (cpm >= 0.5 && cpm < 1) { - distribution = '$0.5-1'; - } else if (cpm >= 1 && cpm < 1.5) { - distribution = '$1-1.5'; - } else if (cpm >= 1.5 && cpm < 2) { - distribution = '$1.5-2'; - } else if (cpm >= 2 && cpm < 2.5) { - distribution = '$2-2.5'; - } else if (cpm >= 2.5 && cpm < 3) { - distribution = '$2.5-3'; - } else if (cpm >= 3 && cpm < 4) { - distribution = '$3-4'; - } else if (cpm >= 4 && cpm < 6) { - distribution = '$4-6'; - } else if (cpm >= 6 && cpm < 8) { - distribution = '$6-8'; - } else if (cpm >= 8) { - distribution = '$8 above'; - } - return distribution; -} - - - -function sendBidRequestToGa(bid) { - if (bid && bid.bidderCode) { - _analyticsQueue.push(function() { - _eventCount++; - window[_gaGlobal]('send', 'event', _category, 'Requests', bid.bidderCode, 1, _disibleInteraction); - }); - } - //check the queue - checkAnalytics(); -} - - -function sendBidResponseToGa(bid) { - - if (bid && bid.bidderCode) { - _analyticsQueue.push(function() { - var cpmCents = convertToCents(bid.cpm), - bidder = bid.bidderCode; - if (typeof bid.timeToRespond !== 'undefined' && _enableDistribution) { - _eventCount++; - var dis = getLoadTimeDistribution(bid.timeToRespond); - window[_gaGlobal]('send', 'event', 'Prebid.js Load Time Distribution', dis, bidder, 1, _disibleInteraction); - } - if (bid.cpm > 0) { - _eventCount = _eventCount + 2; - var cpmDis = getCpmDistribution(bid.cpm); - if(_enableDistribution){ - _eventCount++; - window[_gaGlobal]('send', 'event', 'Prebid.js CPM Distribution', cpmDis, bidder, 1, _disibleInteraction); - } - window[_gaGlobal]('send', 'event', _category, 'Bids', bidder, cpmCents, _disibleInteraction); - window[_gaGlobal]('send', 'event', _category, 'Bid Load Time', bidder, bid.timeToRespond, _disibleInteraction); - } - }); - } - //check the queue - checkAnalytics(); -} - -function sendBidTimeouts(bid){ - - if(bid && bid.bidder){ - _analyticsQueue.push(function(){ - utils._each(_timedOutBidders, function(bidderCode){ - if(bid.bidder === bidderCode){ - _eventCount++; - window[_gaGlobal]('send', 'event', _category, 'Timeouts', bidderCode, bid.timeToRespond, _disibleInteraction); - } - }); - }); - } - checkAnalytics(); -} - -function sendBidWonToGa(bid) { - var cpmCents = convertToCents(bid.cpm); - _analyticsQueue.push(function() { - _eventCount++; - window[_gaGlobal]('send', 'event', _category, 'Wins', bid.bidderCode, cpmCents, _disibleInteraction); - }); - checkAnalytics(); -} +/** + * ga.js - analytics adapter for google analytics + */ + +var events = require('./events'); +var utils = require('./utils'); +var CONSTANTS = require('./constants.json'); + +var BID_REQUESTED = CONSTANTS.EVENTS.BID_REQUESTED; +var BID_TIMEOUT = CONSTANTS.EVENTS.BID_TIMEOUT; +var BID_RESPONSE = CONSTANTS.EVENTS.BID_RESPONSE; +var BID_WON = CONSTANTS.EVENTS.BID_WON; + +var _disibleInteraction = { nonInteraction: true }; +var _analyticsQueue = []; +var _gaGlobal = null; +var _enableCheck = true; +var _category = 'Prebid.js Bids'; +var _eventCount = 0; +var _enableDistribution = false; +var _timedOutBidders = []; +var _trackerSend = null; + +/** + * This will enable sending data to google analytics. Only call once, or duplicate data will be sent! + * @param {object} gaOptions to set distribution and GA global (if renamed); + * @return {[type]} [description] + */ +exports.enableAnalytics = function (gaOptions) { + if (typeof gaOptions.global !== 'undefined') { + _gaGlobal = gaOptions.global; + } else { + //default global is window.ga + _gaGlobal = 'ga'; + } + + _trackerSend = gaOptions.trackerName ? gaOptions.trackerName + '.send' : 'send'; + + if (typeof gaOptions.enableDistribution !== 'undefined') { + _enableDistribution = gaOptions.enableDistribution; + } + + var bid = null; + + //first send all events fired before enableAnalytics called + + var existingEvents = events.getEvents(); + utils._each(existingEvents, function (eventObj) { + var args = eventObj.args; + if (!eventObj) { + return; + } + + if (eventObj.eventType === BID_REQUESTED) { + bid = args; + sendBidRequestToGa(bid); + } else if (eventObj.eventType === BID_RESPONSE) { + //bid is 2nd args + bid = args; + sendBidResponseToGa(bid); + + } else if (eventObj.eventType === BID_TIMEOUT) { + _timedOutBidders = args.bidderCode; + } else if (eventObj.eventType === BID_WON) { + bid = args; + sendBidWonToGa(bid); + } + }); + + //Next register event listeners to send data immediately + + //bidRequests + events.on(BID_REQUESTED, function (bidRequestObj) { + sendBidRequestToGa(bidRequestObj); + }); + + //bidResponses + events.on(BID_RESPONSE, function (adunit, bid) { + sendBidResponseToGa(bid); + sendBidTimeouts(bid); + }); + + //bidTimeouts + events.on(BID_TIMEOUT, function (bidderArray) { + _timedOutBidders = bidderArray; + }); + + //wins + events.on(BID_WON, function (bid) { + sendBidWonToGa(bid); + }); +}; + +exports.getTrackerSend = function getTrackerSend() { + return _trackerSend; +}; + +/** + * Check if gaGlobal or window.ga is defined on page. If defined execute all the GA commands + */ +function checkAnalytics() { + if (_enableCheck && typeof window[_gaGlobal] === 'function') { + + for (var i = 0; i < _analyticsQueue.length; i++) { + _analyticsQueue[i].call(); + } + + //override push to execute the command immediately from now on + _analyticsQueue.push = function (fn) { + fn.call(); + }; + + //turn check into NOOP + _enableCheck = false; + } + + utils.logMessage('event count sent to GA: ' + _eventCount); +} + +function convertToCents(dollars) { + if (dollars) { + return Math.floor(dollars * 100); + } + + return 0; +} + +function getLoadTimeDistribution(time) { + var distribution; + if (time >= 0 && time < 200) { + distribution = '0-200ms'; + } else if (time >= 200 && time < 300) { + distribution = '200-300ms'; + } else if (time >= 300 && time < 400) { + distribution = '300-400ms'; + } else if (time >= 400 && time < 500) { + distribution = '400-500ms'; + } else if (time >= 500 && time < 600) { + distribution = '500-600ms'; + } else if (time >= 600 && time < 800) { + distribution = '600-800ms'; + } else if (time >= 800 && time < 1000) { + distribution = '800-1000ms'; + } else if (time >= 1000 && time < 1200) { + distribution = '1000-1200ms'; + } else if (time >= 1200 && time < 1500) { + distribution = '1200-1500ms'; + } else if (time >= 1500 && time < 2000) { + distribution = '1500-2000ms'; + } else if (time >= 2000) { + distribution = '2000ms above'; + } + + return distribution; +} + +function getCpmDistribution(cpm) { + var distribution; + if (cpm >= 0 && cpm < 0.5) { + distribution = '$0-0.5'; + } else if (cpm >= 0.5 && cpm < 1) { + distribution = '$0.5-1'; + } else if (cpm >= 1 && cpm < 1.5) { + distribution = '$1-1.5'; + } else if (cpm >= 1.5 && cpm < 2) { + distribution = '$1.5-2'; + } else if (cpm >= 2 && cpm < 2.5) { + distribution = '$2-2.5'; + } else if (cpm >= 2.5 && cpm < 3) { + distribution = '$2.5-3'; + } else if (cpm >= 3 && cpm < 4) { + distribution = '$3-4'; + } else if (cpm >= 4 && cpm < 6) { + distribution = '$4-6'; + } else if (cpm >= 6 && cpm < 8) { + distribution = '$6-8'; + } else if (cpm >= 8) { + distribution = '$8 above'; + } + + return distribution; +} + +function sendBidRequestToGa(bid) { + if (bid && bid.bidderCode) { + _analyticsQueue.push(function () { + _eventCount++; + window[_gaGlobal](_trackerSend, 'event', _category, 'Requests', bid.bidderCode, 1, _disibleInteraction); + }); + } + + //check the queue + checkAnalytics(); +} + +function sendBidResponseToGa(bid) { + + if (bid && bid.bidderCode) { + _analyticsQueue.push(function () { + var cpmCents = convertToCents(bid.cpm); + var bidder = bid.bidderCode; + if (typeof bid.timeToRespond !== 'undefined' && _enableDistribution) { + _eventCount++; + var dis = getLoadTimeDistribution(bid.timeToRespond); + window[_gaGlobal](_trackerSend, 'event', 'Prebid.js Load Time Distribution', dis, bidder, 1, _disibleInteraction); + } + + if (bid.cpm > 0) { + _eventCount = _eventCount + 2; + var cpmDis = getCpmDistribution(bid.cpm); + if (_enableDistribution) { + _eventCount++; + window[_gaGlobal](_trackerSend, 'event', 'Prebid.js CPM Distribution', cpmDis, bidder, 1, _disibleInteraction); + } + + window[_gaGlobal](_trackerSend, 'event', _category, 'Bids', bidder, cpmCents, _disibleInteraction); + window[_gaGlobal](_trackerSend, 'event', _category, 'Bid Load Time', bidder, bid.timeToRespond, _disibleInteraction); + } + }); + } + + //check the queue + checkAnalytics(); +} + +function sendBidTimeouts(bid) { + + if (bid && bid.bidder) { + _analyticsQueue.push(function () { + utils._each(_timedOutBidders, function (bidderCode) { + if (bid.bidder === bidderCode) { + _eventCount++; + window[_gaGlobal](_trackerSend, 'event', _category, 'Timeouts', bidderCode, bid.timeToRespond, _disibleInteraction); + } + }); + }); + } + + checkAnalytics(); +} + +function sendBidWonToGa(bid) { + var cpmCents = convertToCents(bid.cpm); + _analyticsQueue.push(function () { + _eventCount++; + window[_gaGlobal](_trackerSend, 'event', _category, 'Wins', bid.bidderCode, cpmCents, _disibleInteraction); + }); + + checkAnalytics(); +} diff --git a/src/polyfill.js b/src/polyfill.js new file mode 100644 index 00000000000..a4bc5227319 --- /dev/null +++ b/src/polyfill.js @@ -0,0 +1,26 @@ +/** @module polyfill +Misc polyfills +*/ + +if (!Array.prototype.find) { + Array.prototype.find = function(predicate) { + if (this === null) { + throw new TypeError('Array.prototype.find called on null or undefined'); + } + if (typeof predicate !== 'function') { + throw new TypeError('predicate must be a function'); + } + var list = Object(this); + var length = list.length >>> 0; + var thisArg = arguments[1]; + var value; + + for (var i = 0; i < length; i++) { + value = list[i]; + if (predicate.call(thisArg, value, i, list)) { + return value; + } + } + return undefined; + }; +} diff --git a/src/prebid.js b/src/prebid.js index c7e416c0fe1..3be970493aa 100644 --- a/src/prebid.js +++ b/src/prebid.js @@ -1,930 +1,705 @@ -/** @module pbjs */ -// if pbjs already exists in global dodcument scope, use it, if not, create the object -window.pbjs = (window.pbjs || {}); -window.pbjs.que = window.pbjs.que || []; -var pbjs = window.pbjs; -var CONSTANTS = require('./constants.json'); -var utils = require('./utils.js'); -var bidmanager = require('./bidmanager.js'); -var adaptermanager = require('./adaptermanager'); -var bidfactory = require('./bidfactory'); -var adloader = require('./adloader'); -var ga = require('./ga'); -var events = require('./events'); - -/* private variables */ - -var objectType_function = 'function'; -var objectType_undefined = 'undefined'; -var objectType_object = 'object'; -var objectType_string = 'string'; -var objectType_number = 'number'; -var BID_WON = CONSTANTS.EVENTS.BID_WON; -var BID_TIMEOUT = CONSTANTS.EVENTS.BID_TIMEOUT; - -var pb_preBidders = [], - pb_placements = [], - pb_bidderMap = {}, - pb_targetingMap = {}, - pb_keyHistoryMap = {}, - pb_bidsTimedOut = false; - - -/* Public vars */ -//default timeout for all bids -pbjs.bidderTimeout = pbjs.bidderTimeout || 3000; -pbjs.logging = pbjs.logging || false; - -//let the world know we are loaded -pbjs.libLoaded = true; - -//create adUnit array -pbjs.adUnits = pbjs.adUnits || []; - -/** - * Command queue that functions will execute once prebid.js is loaded - * @param {function} cmd Annoymous function to execute - * @alias module:pbjs.que.push - */ -pbjs.que.push = function(cmd) { - if (typeof cmd === objectType_function) { - try { - cmd.call(); - } catch (e) { - utils.logError('Error processing command :' + e.message); - } - } else { - utils.logError('Commands written into pbjs.que.push must wrapped in a function'); - } -}; - -function processQue() { - for (var i = 0; i < pbjs.que.length; i++) { - if (typeof pbjs.que[i].called === objectType_undefined) { - try{ - pbjs.que[i].call(); - pbjs.que[i].called = true; - } - catch(e){ - utils.logError('Error processing command :', 'prebid.js', e); - } - - } - } -} - -/* - * Main method entry point method - */ -function init(timeout, adUnitCodeArr) { - var cbTimeout = 0; - if(typeof timeout === objectType_undefined || timeout === null){ - cbTimeout = pbjs.bidderTimeout; - } - else{ - cbTimeout = timeout; - } - - if (!isValidAdUnitSetting()) { - utils.logMessage('No adUnits configured. No bids requested.'); - return; - } - //set timeout for all bids - setTimeout(bidmanager.executeCallback, cbTimeout); - //parse settings into internal vars - if (adUnitCodeArr && utils.isArray(adUnitCodeArr)) { - for (var k = 0; k < adUnitCodeArr.length; k++) { - for (var i = 0; i < pbjs.adUnits.length; i++) { - if (pbjs.adUnits[i].code === adUnitCodeArr[k]) { - pb_placements.push(pbjs.adUnits[i]); - } - } - } - loadPreBidders(); - sortAndCallBids(); - } else { - pb_placements = pbjs.adUnits; - //Aggregrate prebidders by their codes - loadPreBidders(); - //sort and call // default no sort - sortAndCallBids(); - } -} - -function isValidAdUnitSetting() { - if (pbjs.adUnits && pbjs.adUnits.length !== 0) { - return true; - } - return false; -} - -function timeOutBidders(){ - if(!pb_bidsTimedOut){ - pb_bidsTimedOut = true; - var timedOutBidders = bidmanager.getTimedOutBidders(); - events.emit(BID_TIMEOUT, timedOutBidders); - } -} - -function sortAndCallBids(sortFunc) { - //Translate the bidder map into array so we can sort later if wanted - var pbArr = Object.keys(pb_bidderMap).map(function(key) { - return pb_bidderMap[key]; - }); - if (typeof sortFunc === objectType_function) { - pbArr.sort(sortFunc); - } - adaptermanager.callBids(pbArr); -} - -function loadPreBidders() { - - for (var i = 0; i < pb_placements.length; i++) { - var bids = pb_placements[i][CONSTANTS.JSON_MAPPING.PL_BIDS]; - var placementCode = pb_placements[i][CONSTANTS.JSON_MAPPING.PL_CODE]; - storeBidRequestByBidder(pb_placements[i][CONSTANTS.JSON_MAPPING.PL_CODE], pb_placements[i][CONSTANTS.JSON_MAPPING.PL_SIZE], bids); - //store pending response by placement - bidmanager.addBidResponse(placementCode); - } - - for (i = 0; i < pb_preBidders.length; i++) { - pb_preBidders[i].loadPreBid(); - } - //send a reference to bidmanager - bidmanager.setBidderMap(pb_bidderMap); -} - -function storeBidRequestByBidder(placementCode, sizes, bids) { - for (var i = 0; i < bids.length; i++) { - - var currentBid = bids[i]; - currentBid.placementCode = placementCode; - currentBid.sizes = sizes; - - //look up bidder on map - var bidderName = bids[i][CONSTANTS.JSON_MAPPING.BD_BIDDER]; - var bidderObj = pb_bidderMap[bidderName]; - if (typeof bidderObj === objectType_undefined) { - //bid not found - var partnerBids = { - bidderCode: bidderName, - bids: [] - }; - partnerBids.bids.push(currentBid); - //put bidder on map with bids - pb_bidderMap[bidderName] = partnerBids; - } else { - bidderObj.bids.push(currentBid); - } - - } -} - -//use in place of hasOwnPropery - as opposed to a polyfill -function hasOwn(o, p) { - if (o.hasOwnProperty) { - return o.hasOwnProperty(p); - } else { - return (typeof o[p] !== objectType_undefined) && (o.constructor.prototype[p] !== o[p]); - } -} - -function isEmptyObject(obj) { - var name; - for (name in obj) { - return false; - } - return true; -} - -function getWinningBid(bidArray) { - var winningBid = {}; - if (bidArray && bidArray.length !== 0) { - bidArray.sort(function(a, b) { - //put the highest CPM first - return b.cpm - a.cpm; - }); - //the first item has the highest cpm - winningBid = bidArray[0]; - //TODO : if winning bid CPM === 0 - we need to indicate no targeting should be set - } - return winningBid.bid; - -} - - -function setGPTAsyncTargeting(code, slot) { - //get the targeting that is already configured - var keyStrings = getTargetingfromGPTIdentifier(slot); - //copy keyStrings into pb_keyHistoryMap by code - if(!pb_keyHistoryMap[code]){ - pb_keyHistoryMap[code] = keyStrings; - } - else{ - utils.extend(pb_keyHistoryMap[code], keyStrings); - } - utils._each(pb_keyHistoryMap[code], function(value, key){ - //since DFP doesn't support deleting a single key, we will set all to empty string - //This is "clear" for that key - slot.setTargeting(key, ''); - //utils.logMessage('Attempting to clear the key : ' + key + ' to empty string for code: ' + code); - }); - for (var key in keyStrings) { - if (keyStrings.hasOwnProperty(key)) { - try { - utils.logMessage('Attempting to set key value for slot: ' + slot.getSlotElementId() + ' key: ' + key + ' value: ' + encodeURIComponent(keyStrings[key])); - slot.setTargeting(key, keyStrings[key]); - } catch (e) { - utils.logMessage('Problem setting key value pairs in slot: ' + e.message); - } - } - } -} -/* - * This function returns the object map of placements or - * if placement code is specified return just 1 placement bids - */ -function getBidResponsesByAdUnit(adunitCode) { - var returnObj = {}; - if (adunitCode) { - returnObj = bidmanager.pbBidResponseByPlacement[adunitCode]; - return returnObj; - } - else { - return bidmanager.pbBidResponseByPlacement; - } -} - - -/* - * Copies bids into a bidArray response - */ -function buildBidResponse(bidArray) { - var bidResponseArray = []; - var adUnitCode = ''; - //temp array to hold auction for bids - var bidArrayTargeting = []; - var bidClone = {}; - if (bidArray && bidArray[0] && bidArray[0].adUnitCode) { - // init the pb_targetingMap for the adUnitCode - adUnitCode = bidArray[0] && bidArray[0].adUnitCode; - pb_targetingMap[adUnitCode] = {}; - for (var i = 0; i < bidArray.length; i++) { - var bid = bidArray[i]; - //clone by json parse. This also gets rid of unwanted function properties - bidClone = getCloneBid(bid); - - if (bid.alwaysUseBid && bidClone.adserverTargeting) { // add the bid if alwaysUse and bid has returned - // push key into targeting - pb_targetingMap[bidClone.adUnitCode] = utils.extend(pb_targetingMap[bidClone.adUnitCode], bidClone.adserverTargeting); - } else if (bid.cpm && bid.cpm > 0){ - //else put into auction array if cpm > 0 - bidArrayTargeting.push({ - cpm: bid.cpm, - bid: bid - }); - } - //put all bids into bidArray by default - bidResponseArray.push(bidClone); - } - } - - // push the winning bid into targeting map - if (adUnitCode && bidArrayTargeting.length !== 0) { - var winningBid = getWinningBid(bidArrayTargeting); - var keyValues = winningBid.adserverTargeting; - pb_targetingMap[adUnitCode] = utils.extend(pb_targetingMap[adUnitCode], keyValues); - } - - return bidResponseArray; -} - -function getCloneBid(bid) { - var bidClone = {}; - //clone by json parse. This also gets rid of unwanted function properties - if (bid) { - var jsonBid = JSON.stringify(bid); - bidClone = JSON.parse(jsonBid); - - //clean up bid clone - delete bidClone.pbLg; - delete bidClone.pbMg; - delete bidClone.pbHg; - } - return bidClone; -} - -function resetBids() { - bidmanager.clearAllBidResponses(); - pb_bidderMap = {}; - pb_placements = []; - pb_targetingMap = {}; - pb_bidsTimedOut = false; -} - -function requestAllBids(tmout){ - var timeout = tmout; - resetBids(); - init(timeout); -} - - -////////////////////////////////// -// // -// Start Public APIs // -// // -////////////////////////////////// -/** - * This function returns the query string targeting parameters available at this moment for a given ad unit. Note that some bidder's response may not have been received if you call this function too quickly after the requests are sent. - * @param {string} [adunitCode] adUnitCode to get the bid responses for - * @alias module:pbjs.getAdserverTargetingForAdUnitCodeStr - * @return {array} returnObj return bids array - */ -pbjs.getAdserverTargetingForAdUnitCodeStr = function(adunitCode) { - // call to retrieve bids array - if(adunitCode){ - var res = pbjs.getAdserverTargetingForAdUnitCode(adunitCode); - return utils.transformAdServerTargetingObj(res); - } - else{ - utils.logMessage('Need to call getAdserverTargetingForAdUnitCodeStr with adunitCode'); - } -}; -/** - * This function returns the query string targeting parameters available at this moment for a given ad unit. Note that some bidder's response may not have been received if you call this function too quickly after the requests are sent. - * @param {string} [adunitCode] adUnitCode to get the bid responses for - * @alias module:pbjs.getAdserverTargetingForAdUnitCode - * @return {object} returnObj return bids - */ -pbjs.getAdserverTargetingForAdUnitCode = function(adunitCode) { - // call to populate pb_targetingMap - pbjs.getBidResponses(adunitCode); - - if (adunitCode) { - return pb_targetingMap[adunitCode]; - } - return pb_targetingMap; -}; -/** - * returns all ad server targeting for all ad units - * @return {object} Map of adUnitCodes and targeting values [] - * @alias module:pbjs.getAdserverTargeting - */ -pbjs.getAdserverTargeting = function() { - return pbjs.getAdserverTargetingForAdUnitCode(); -}; - -/** - * This function returns the bid responses at the given moment. - * @param {string} [adunitCode] adunitCode adUnitCode to get the bid responses for - * @alias module:pbjs.getBidResponses - * @return {object} map | object that contains the bidResponses - */ -pbjs.getBidResponses = function(adunitCode) { - var bidArrayTargeting = []; - var response = {}; - var bidArray = []; - var returnObj = {}; - - if (adunitCode) { - response = getBidResponsesByAdUnit(adunitCode); - bidArray = []; - if (response && response.bids) { - bidArray = buildBidResponse(response.bids); - } - - returnObj = { - bids: bidArray - }; - - } else { - response = getBidResponsesByAdUnit(); - for (var adUnit in response) { - if (response.hasOwnProperty(adUnit)) { - if (response && response[adUnit] && response[adUnit].bids) { - bidArray = buildBidResponse(response[adUnit].bids); - } - - returnObj[adUnit] = { - bids: bidArray - }; - - } - } - } - - return returnObj; - -}; -/** - * Returns bidResponses for the specified adUnitCode - * @param {String} adUnitCode adUnitCode - * @alias module:pbjs.getBidResponsesForAdUnitCode - * @return {Object} bidResponse object - */ -pbjs.getBidResponsesForAdUnitCode = function(adUnitCode) { - return pbjs.getBidResponses(adUnitCode); -}; -/** - * Set query string targeting on adUnits specified. The logic for deciding query strings is described in the section Configure AdServer Targeting. Note that this function has to be called after all ad units on page are defined. - * @param {array} [codeArr] an array of adUnitodes to set targeting for. - * @alias module:pbjs.setTargetingForAdUnitsGPTAsync - */ -pbjs.setTargetingForAdUnitsGPTAsync = function(codeArr) { - if (!window.googletag || !utils.isFn(window.googletag.pubads) || !utils.isFn(window.googletag.pubads().getSlots)) { - utils.logError('window.googletag is not defined on the page'); - return; - } - - //emit bid timeout event here - timeOutBidders(); - - var adUnitCodesArr = codeArr; - - if (typeof codeArr === objectType_string) { - adUnitCodesArr = [codeArr]; - } else if (typeof codeArr === objectType_object) { - adUnitCodesArr = codeArr; - } - - var placementBids = {}, - i = 0; - if (adUnitCodesArr) { - for (i = 0; i < adUnitCodesArr.length; i++) { - var code = adUnitCodesArr[i]; - //get all the slots from google tag - var slots = window.googletag.pubads().getSlots(); - for (var k = 0; k < slots.length; k++) { - - if (slots[k].getSlotElementId() === code || slots[k].getAdUnitPath() === code) { - placementBids = getBidResponsesByAdUnit(code); - setGPTAsyncTargeting(code, slots[k]); - } - } - } - } else { - //get all the slots from google tag - var slots = window.googletag.pubads().getSlots(); - for (i = 0; i < slots.length; i++) { - var adUnitCode = slots[i].getSlotElementId(); - if (adUnitCode) { - //placementBids = getBidsFromGTPIdentifier(slots[i]); - setGPTAsyncTargeting(adUnitCode, slots[i]); - } - } - } - -}; -/** - * Returns a string identifier (either DivId or adUnitPath) - * @param {[type]} slot [description] - * @return {[type]} [description] - */ -function getTargetingfromGPTIdentifier(slot){ - var targeting = null; - if(slot){ - //first get by elementId - targeting = pbjs.getAdserverTargetingForAdUnitCode(slot.getSlotElementId()); - //if not available, try by adUnitPath - if(!targeting){ - targeting = pbjs.getAdserverTargetingForAdUnitCode(slot.getAdUnitPath()); - } - } - return targeting; -} - -/** - - -/** - * Set query string targeting on all GPT ad units. - * @alias module:pbjs.setTargetingForGPTAsync - */ -pbjs.setTargetingForGPTAsync = function(codeArr) { - pbjs.setTargetingForAdUnitsGPTAsync(codeArr); -}; - -/** - * Returns a bool if all the bids have returned or timed out - * @alias module:pbjs.allBidsAvailable - * @return {bool} all bids available - */ -pbjs.allBidsAvailable = function() { - return bidmanager.allBidsBack(); -}; - -/** - * This function will render the ad (based on params) in the given iframe document passed through. Note that doc SHOULD NOT be the parent document page as we can't doc.write() asynchrounsly - * @param {object} doc document - * @param {string} id bid id to locate the ad - * @alias module:pbjs.renderAd - */ -pbjs.renderAd = function(doc, id) { - utils.logMessage('Calling renderAd with adId :' + id); - if (doc && id) { - try { - //lookup ad by ad Id - var adObject = bidmanager._adResponsesByBidderId[id]; - if (adObject) { - //emit 'bid won' event here - events.emit(BID_WON, adObject); - var height = adObject.height; - var width = adObject.width; - var url = adObject.adUrl; - var ad = adObject.ad; - - if (ad) { - doc.write(ad); - doc.close(); - if (doc.defaultView && doc.defaultView.frameElement) { - doc.defaultView.frameElement.width = width; - doc.defaultView.frameElement.height = height; - } - } - //doc.body.style.width = width; - //doc.body.style.height = height; - else if (url) { - doc.write(''); - doc.close(); - - if (doc.defaultView && doc.defaultView.frameElement) { - doc.defaultView.frameElement.width = width; - doc.defaultView.frameElement.height = height; - } - - } else { - utils.logError('Error trying to write ad. No ad for bid response id: ' + id); - } - - } else { - utils.logError('Error trying to write ad. Cannot find ad by given id : ' + id); - } - - } catch (e) { - utils.logError('Error trying to write ad Id :' + id + ' to the page:' + e.message); - } - } else { - utils.logError('Error trying to write ad Id :' + id + ' to the page. Missing document or adId'); - } - -}; - - -/* - * @deprecated - will be removed next release. Use pbjs.requestBids - */ -pbjs.requestBidsForAdUnit = function(adUnitCode) { - resetBids(); - init(adUnitCode); - -}; - -/** - * @deprecated - will be removed next release. Use pbjs.requestBids - */ -pbjs.requestBidsForAdUnits = function(adUnitsObj) { - if (!adUnitsObj || adUnitsObj.constructor !== Array) { - utils.logError('requestBidsForAdUnits must pass an array of adUnits'); - return; - } - resetBids(); - var adUnitBackup = pbjs.adUnits.slice(0); - pbjs.adUnits = adUnitsObj; - init(); - pbjs.adUnits = adUnitBackup; - -}; - -/** - * Remove adUnit from the pbjs configuration - * @param {String} adUnitCode the adUnitCode to remove - * @alias module:pbjs.removeAdUnit - */ -pbjs.removeAdUnit = function(adUnitCode) { - if (adUnitCode) { - for (var i = 0; i < pbjs.adUnits.length; i++) { - if (pbjs.adUnits[i].code === adUnitCode) { - pbjs.adUnits.splice(i, 1); - } - } - } -}; - - -/** - * Request bids ad-hoc. This function does not add or remove adUnits already configured. - * @param {Object} requestObj - * @param {string[]} requestObj.adUnitCodes adUnit codes to request. Use this or requestObj.adUnits - * @param {object[]} requestObj.adUnits AdUnitObjects to request. Use this or requestObj.adUnitCodes - * @param {number} [requestObj.timeout] Timeout for requesting the bids specified in milliseconds - * @param {function} [requestObj.bidsBackHandler] Callback to execute when all the bid responses are back or the timeout hits. - * @alias module:pbjs.requestBids - */ -pbjs.requestBids = function(requestObj) { - if (!requestObj) { - //utils.logMessage('requesting all bids'); - - requestAllBids(); - - } - else{ - var adUnitCodes = requestObj.adUnitCodes; - var adUnits = requestObj.adUnits; - var timeout = requestObj.timeout; - var bidsBackHandler = requestObj.bidsBackHandler; - var adUnitBackup = pbjs.adUnits.slice(0); - - if (typeof bidsBackHandler === objectType_function) { - bidmanager.addOneTimeCallback(bidsBackHandler); - } - - if (adUnitCodes && utils.isArray(adUnitCodes)) { - resetBids(); - init(timeout, adUnitCodes); - - } else if (adUnits && utils.isArray(adUnits)) { - resetBids(); - pbjs.adUnits = adUnits; - init(timeout); - } else { - //request all ads - requestAllBids(timeout); - } - - pbjs.adUnits = adUnitBackup; - } - -}; - -/** - * - * Add adunit(s) - * @param {(string|string[])} Array of adUnits or single adUnit Object. - * @alias module:pbjs.addAdUnits - */ -pbjs.addAdUnits = function(adUnitArr) { - if (utils.isArray(adUnitArr)) { - //append array to existing - pbjs.adUnits.push.apply(pbjs.adUnits, adUnitArr); - } else if (typeof adUnitArr === objectType_object) { - pbjs.adUnits.push(adUnitArr); - } -}; - - -/** - * Add a callback event - * @param {String} event event to attach callback to Options: "allRequestedBidsBack" | "adUnitBidsBack" - * @param {Function} func function to execute. Paramaters passed into the function: (bidResObj), [adUnitCode]); - * @alias module:pbjs.addCallback - * @returns {String} id for callback - */ -pbjs.addCallback = function(eventStr, func) { - var id = null; - if (!eventStr || !func || typeof func !== objectType_function) { - utils.logError('error registering callback. Check method signature'); - return id; - } - - id = utils.getUniqueIdentifierStr; - bidmanager.addCallback(id, func, eventStr); - return id; -}; - -/** - * Remove a callback event - * @param {string} cbId id of the callback to remove - * @alias module:pbjs.removeCallback - * @returns {String} id for callback - */ -pbjs.removeCallback = function(cbId) { - //todo -}; - -/** - * Wrapper to register bidderAdapter externally (adaptermanager.registerBidAdapter()) - * @param {[type]} bidderAdaptor [description] - * @param {[type]} bidderCode [description] - * @return {[type]} [description] - */ -pbjs.registerBidAdapter = function(bidderAdaptor, bidderCode){ - try{ - adaptermanager.registerBidAdapter(bidderAdaptor(), bidderCode); - } - catch(e){ - utils.logError('Error registering bidder adapter : ' + e.message); - } -}; - -/** - * - */ - pbjs.bidsAvailableForAdapter = function(bidderCode){ - - //TODO getAd - var bids = pb_bidderMap[bidderCode].bids; - - for (var i = 0; i < bids.length; i++) { - var adunitCode = bids[i].placementCode; - var responseObj = bidmanager.pbBidResponseByPlacement[adunitCode]; - - var bid = bidfactory.createBid(1); - // bid.creative_id = adId; - bid.bidderCode = bidderCode; - bid.adUnitCode = adunitCode; - bid.bidder = bidderCode; - // bid.cpm = responseCPM; - // bid.adUrl = jptResponseObj.result.ad; - // bid.width = jptResponseObj.result.width; - // bid.height = jptResponseObj.result.height; - // bid.dealId = jptResponseObj.result.deal_id; - - responseObj.bids.push(bid); - responseObj.bidsReceivedCount++; - bidmanager.pbBidResponseByPlacement[adunitCode] = responseObj; - }; - - bidmanager.increaseBidResponseReceivedCount(bidderCode); -} - -/** - * Wrapper to bidfactory.createBid() - * @param {[type]} statusCode [description] - * @return {[type]} [description] - */ -pbjs.createBid = function(statusCode){ - return bidfactory.createBid(statusCode); -}; - -/** - * Wrapper to bidmanager.addBidResponse - * @param {[type]} adUnitCode [description] - * @param {[type]} bid [description] - */ -pbjs.addBidResponse = function(adUnitCode, bid){ - bidmanager.addBidResponse(adUnitCode, bid); -}; - -/** - * Wrapper to adloader.loadScript - * @param {[type]} tagSrc [description] - * @param {Function} callback [description] - * @return {[type]} [description] - */ -pbjs.loadScript = function(tagSrc, callback){ - adloader.loadScript(tagSrc, callback); -}; - -/** - * This isn't ready yet - * return data for analytics - * @param {Function} [description] - * @return {[type]} [description] - -pbjs.getAnalyticsData = function(){ - var returnObj = {}; - var bidResponses = pbjs.getBidResponses(); - - //create return obj for all adUnits - for(var i=0;i bidSet.bids.map(bid => bid.placementCode)) + .reduce(flatten) + .filter(uniques); + + if (!utils.contains(placementCodes, id)) { + utils.logError('The "' + id + '" placement is not defined.'); + return; + } + + return true; +} + +function resetPresetTargeting() { + if (isGptPubadsDefined()) { + window.googletag.pubads().getSlots().forEach(slot => { + slot.clearTargeting(); + }); + + setTargeting(presetTargeting); + } +} + +function setTargeting(targetingConfig) { + window.googletag.pubads().getSlots().forEach(slot => { + targetingConfig.filter(targeting => Object.keys(targeting)[0] === slot.getAdUnitPath() || + Object.keys(targeting)[0] === slot.getSlotElementId()) + .forEach(targeting => targeting[Object.keys(targeting)[0]] + .forEach(key => { + key[Object.keys(key)[0]] + .map((value) => { + utils.logMessage(`Attempting to set key value for slot: ${slot.getSlotElementId()} key: ${Object.keys(key)[0]} value: ${value}`); + return value; + }) + .forEach(value => slot.setTargeting(Object.keys(key)[0], value)); + })); + }); +} + +function getWinningBidTargeting() { + if (isGptPubadsDefined()) { + presetTargeting = (function getPresetTargeting() { + return window.googletag.pubads().getSlots().map(slot => { + return { + [slot.getAdUnitPath()]: slot.getTargetingKeys().map(key => { + return { [key]: slot.getTargeting(key) }; + }) + }; + }); + })(); + } + + let winners = pbjs._bidsReceived.map(bid => bid.adUnitCode) + .filter(uniques) + .map(adUnitCode => pbjs._bidsReceived + .filter(bid => bid.adUnitCode === adUnitCode ? bid : null) + .reduce(getHighestCpm, + { + adUnitCode: adUnitCode, + cpm: 0, + adserverTargeting: {}, + timeToRespond: 0 + })); + + // winning bids with deals need an hb_deal targeting key + winners + .filter(bid => bid.dealId) + .map(bid => bid.adserverTargeting.hb_deal = bid.dealId); + + winners = winners.map(winner => { + return { + [winner.adUnitCode]: Object.keys(winner.adserverTargeting, key => key) + .map(key => { + return { [key.substring(0, 20)]: [winner.adserverTargeting[key]] }; + }) + }; + }); + + if (presetTargeting) { + winners.concat(presetTargeting); + } + + return winners; +} + +function getDealTargeting() { + return pbjs._bidsReceived.filter(bid => bid.dealId).map(bid => { + const dealKey = `hb_deal_${bid.bidderCode}`; + return { + [bid.adUnitCode]: CONSTANTS.TARGETING_KEYS.map(key => { + return { + [`${key}_${bid.bidderCode}`.substring(0, 20)]: [bid.adserverTargeting[key]] + }; + }) + .concat({ [dealKey]: [bid.adserverTargeting[dealKey]] }) + }; + }); +} + +/** + * Get custom targeting keys for bids that have `alwaysUseBid=true`. + */ +function getAlwaysUseBidTargeting() { + return pbjs._bidsReceived.map(bid => { + if (bid.alwaysUseBid) { + const standardKeys = CONSTANTS.TARGETING_KEYS; + return { + [bid.adUnitCode]: Object.keys(bid.adserverTargeting, key => key).map(key => { + // Get only the non-standard keys of the losing bids, since we + // don't want to override the standard keys of the winning bid. + if (standardKeys.indexOf(key) > -1) { + return; + } + + return { [key.substring(0, 20)]: [bid.adserverTargeting[key]] }; + + }).filter(key => key) // remove empty elements + }; + } + }).filter(bid => bid); // removes empty elements in array; +} + +function getBidLandscapeTargeting() { + const standardKeys = CONSTANTS.TARGETING_KEYS; + + return pbjs._bidsReceived.map(bid => { + if (bid.adserverTargeting) { + return { + [bid.adUnitCode]: standardKeys.map(key => { + return { + [`${key}_${bid.bidderCode}`.substring(0, 20)]: [bid.adserverTargeting[key]] + }; + }) + }; + } + }).filter(bid => bid); // removes empty elements in array +} + +function getAllTargeting() { + // Get targeting for the winning bid. Add targeting for any bids that have + // `alwaysUseBid=true`. If sending all bids is enabled, add targeting for losing bids. + return getDealTargeting() + .concat(getWinningBidTargeting()) + .concat(getAlwaysUseBidTargeting()) + .concat(pbjs._sendAllBids ? getBidLandscapeTargeting() : []); +} + +////////////////////////////////// +// // +// Start Public APIs // +// // +////////////////////////////////// + +/** + * This function returns the query string targeting parameters available at this moment for a given ad unit. Note that some bidder's response may not have been received if you call this function too quickly after the requests are sent. + * @param {string} [adunitCode] adUnitCode to get the bid responses for + * @alias module:pbjs.getAdserverTargetingForAdUnitCodeStr + * @return {array} returnObj return bids array + */ +pbjs.getAdserverTargetingForAdUnitCodeStr = function (adunitCode) { + utils.logInfo('Invoking pbjs.getAdserverTargetingForAdUnitCodeStr', arguments); + + // call to retrieve bids array + if (adunitCode) { + var res = pbjs.getAdserverTargetingForAdUnitCode(adunitCode); + return utils.transformAdServerTargetingObj(res); + } else { + utils.logMessage('Need to call getAdserverTargetingForAdUnitCodeStr with adunitCode'); + } +}; + +/** +* This function returns the query string targeting parameters available at this moment for a given ad unit. Note that some bidder's response may not have been received if you call this function too quickly after the requests are sent. + * @param adUnitCode {string} adUnitCode to get the bid responses for + * @returns {object} returnObj return bids + */ +pbjs.getAdserverTargetingForAdUnitCode = function (adUnitCode) { + utils.logInfo('Invoking pbjs.getAdserverTargetingForAdUnitCode', arguments); + + return getAllTargeting().filter(targeting => getKeys(targeting)[0] === adUnitCode) + .map(targeting => { + return { + [Object.keys(targeting)[0]]: targeting[Object.keys(targeting)[0]] + .map(target => { + return { + [Object.keys(target)[0]]: target[Object.keys(target)[0]].join(', ') + }; + }).reduce((p, c) => Object.assign(c, p), {}) + }; + }) + .reduce(function (accumulator, targeting) { + var key = Object.keys(targeting)[0]; + accumulator[key] = Object.assign({}, accumulator[key], targeting[key]); + return accumulator; + }, {})[adUnitCode]; +}; + +/** + * returns all ad server targeting for all ad units + * @return {object} Map of adUnitCodes and targeting values [] + * @alias module:pbjs.getAdserverTargeting + */ + +pbjs.getAdserverTargeting = function () { + utils.logInfo('Invoking pbjs.getAdserverTargeting', arguments); + return getAllTargeting() + .map(targeting => { + return { + [Object.keys(targeting)[0]]: targeting[Object.keys(targeting)[0]] + .map(target => { + return { + [Object.keys(target)[0]]: target[Object.keys(target)[0]].join(', ') + }; + }).reduce((p, c) => Object.assign(c, p), {}) + }; + }) + .reduce(function (accumulator, targeting) { + var key = Object.keys(targeting)[0]; + accumulator[key] = Object.assign({}, accumulator[key], targeting[key]); + return accumulator; + }, {}); +}; + +/** + * This function returns the bid responses at the given moment. + * @alias module:pbjs.getBidResponses + * @return {object} map | object that contains the bidResponses + */ + +pbjs.getBidResponses = function () { + utils.logInfo('Invoking pbjs.getBidResponses', arguments); + + return pbjs._bidsReceived.map(bid => bid.adUnitCode) + .filter(uniques).map(adUnitCode => pbjs._bidsReceived + .filter(bid => bid.adUnitCode === adUnitCode)) + .map(bids => { + return { + [bids[0].adUnitCode]: { bids: bids } + }; + }) + .reduce((a, b) => Object.assign(a, b), {}); +}; + +/** + * Returns bidResponses for the specified adUnitCode + * @param {String} adUnitCode adUnitCode + * @alias module:pbjs.getBidResponsesForAdUnitCode + * @return {Object} bidResponse object + */ + +pbjs.getBidResponsesForAdUnitCode = function (adUnitCode) { + const bids = pbjs._bidsReceived.filter(bid => bid.adUnitCode === adUnitCode); + return { + bids: bids + }; +}; + +/** + * Set query string targeting on all GPT ad units. + * @alias module:pbjs.setTargetingForGPTAsync + */ +pbjs.setTargetingForGPTAsync = function () { + utils.logInfo('Invoking pbjs.setTargetingForGPTAsync', arguments); + if (!isGptPubadsDefined()) { + utils.logError('window.googletag is not defined on the page'); + return; + } + + setTargeting(getAllTargeting()); +}; + +/** + * Returns a bool if all the bids have returned or timed out + * @alias module:pbjs.allBidsAvailable + * @return {bool} all bids available + */ +pbjs.allBidsAvailable = function () { + utils.logInfo('Invoking pbjs.allBidsAvailable', arguments); + return bidmanager.bidsBackAll(); +}; + +/** + * This function will render the ad (based on params) in the given iframe document passed through. Note that doc SHOULD NOT be the parent document page as we can't doc.write() asynchrounsly + * @param {object} doc document + * @param {string} id bid id to locate the ad + * @alias module:pbjs.renderAd + */ +pbjs.renderAd = function (doc, id) { + utils.logInfo('Invoking pbjs.renderAd', arguments); + utils.logMessage('Calling renderAd with adId :' + id); + if (doc && id) { + try { + //lookup ad by ad Id + var adObject = pbjs._bidsReceived.find(bid => bid.adId === id); + if (adObject) { + //emit 'bid won' event here + events.emit(BID_WON, adObject); + var height = adObject.height; + var width = adObject.width; + var url = adObject.adUrl; + var ad = adObject.ad; + + if (ad) { + doc.write(ad); + doc.close(); + if (doc.defaultView && doc.defaultView.frameElement) { + doc.defaultView.frameElement.width = width; + doc.defaultView.frameElement.height = height; + } + } + + //doc.body.style.width = width; + //doc.body.style.height = height; + else if (url) { + doc.write(''); + doc.close(); + + if (doc.defaultView && doc.defaultView.frameElement) { + doc.defaultView.frameElement.width = width; + doc.defaultView.frameElement.height = height; + } + + } else { + utils.logError('Error trying to write ad. No ad for bid response id: ' + id); + } + + } else { + utils.logError('Error trying to write ad. Cannot find ad by given id : ' + id); + } + + } catch (e) { + utils.logError('Error trying to write ad Id :' + id + ' to the page:' + e.message); + } + } else { + utils.logError('Error trying to write ad Id :' + id + ' to the page. Missing document or adId'); + } + +}; + +/** + * Remove adUnit from the pbjs configuration + * @param {String} adUnitCode the adUnitCode to remove + * @alias module:pbjs.removeAdUnit + */ +pbjs.removeAdUnit = function (adUnitCode) { + utils.logInfo('Invoking pbjs.removeAdUnit', arguments); + if (adUnitCode) { + for (var i = 0; i < pbjs.adUnits.length; i++) { + if (pbjs.adUnits[i].code === adUnitCode) { + pbjs.adUnits.splice(i, 1); + } + } + } +}; + +pbjs.clearAuction = function() { + auctionRunning = false; + utils.logMessage('Prebid auction cleared'); +}; + +/** + * + * @param bidsBackHandler + * @param timeout + * @param adUnits + * @param adUnitCodes + */ +pbjs.requestBids = function ({ bidsBackHandler, timeout, adUnits, adUnitCodes }) { + if (auctionRunning) { + utils.logError('Prebid Error: `pbjs.requestBids` was called while a previous auction was' + + ' still running. Resubmit this request.'); + return; + } else { + auctionRunning = true; + pbjs._bidsRequested = []; + pbjs._bidsReceived = []; + resetPresetTargeting(); + } + + const cbTimeout = timeout || pbjs.bidderTimeout; + + // use adUnits provided or from pbjs global + adUnits = adUnits || pbjs.adUnits; + + // if specific adUnitCodes filter adUnits for those codes + if (adUnitCodes && adUnitCodes.length) { + adUnits = adUnits.filter(adUnit => adUnitCodes.includes(adUnit.code)); + } + + if (typeof bidsBackHandler === objectType_function) { + bidmanager.addOneTimeCallback(bidsBackHandler); + } + + utils.logInfo('Invoking pbjs.requestBids', arguments); + + if (!adUnits || adUnits.length === 0) { + utils.logMessage('No adUnits configured. No bids requested.'); + return; + } + + //set timeout for all bids + setTimeout(bidmanager.executeCallback, cbTimeout); + + adaptermanager.callBids({ adUnits, adUnitCodes }); +}; + +/** + * + * Add adunit(s) + * @param {Array|String} adUnitArr Array of adUnits or single adUnit Object. + * @alias module:pbjs.addAdUnits + */ +pbjs.addAdUnits = function (adUnitArr) { + utils.logInfo('Invoking pbjs.addAdUnits', arguments); + if (utils.isArray(adUnitArr)) { + //append array to existing + pbjs.adUnits.push.apply(pbjs.adUnits, adUnitArr); + } else if (typeof adUnitArr === objectType_object) { + pbjs.adUnits.push(adUnitArr); + } +}; + +/** + * @param {String} event the name of the event + * @param {Function} handler a callback to set on event + * @param {String} id an identifier in the context of the event + * + * This API call allows you to register a callback to handle a Prebid.js event. + * An optional `id` parameter provides more finely-grained event callback registration. + * This makes it possible to register callback events for a specific item in the + * event context. For example, `bidWon` events will accept an `id` for ad unit code. + * `bidWon` callbacks registered with an ad unit code id will be called when a bid + * for that ad unit code wins the auction. Without an `id` this method registers the + * callback for every `bidWon` event. + * + * Currently `bidWon` is the only event that accepts an `id` parameter. + */ +pbjs.onEvent = function (event, handler, id) { + utils.logInfo('Invoking pbjs.onEvent', arguments); + if (!utils.isFn(handler)) { + utils.logError('The event handler provided is not a function and was not set on event "' + event + '".'); + return; + } + + if (id && !eventValidators[event].call(null, id)) { + utils.logError('The id provided is not valid for event "' + event + '" and no handler was set.'); + return; + } + + events.on(event, handler, id); +}; + +/** + * @param {String} event the name of the event + * @param {Function} handler a callback to remove from the event + * @param {String} id an identifier in the context of the event (see `pbjs.onEvent`) + */ +pbjs.offEvent = function (event, handler, id) { + utils.logInfo('Invoking pbjs.offEvent', arguments); + if (id && !eventValidators[event].call(null, id)) { + return; + } + + events.off(event, handler, id); +}; + +/** + * Add a callback event + * @param {String} eventStr event to attach callback to Options: "allRequestedBidsBack" | "adUnitBidsBack" + * @param {Function} func function to execute. Paramaters passed into the function: (bidResObj), [adUnitCode]); + * @alias module:pbjs.addCallback + * @returns {String} id for callback + */ +pbjs.addCallback = function (eventStr, func) { + utils.logInfo('Invoking pbjs.addCallback', arguments); + var id = null; + if (!eventStr || !func || typeof func !== objectType_function) { + utils.logError('error registering callback. Check method signature'); + return id; + } + + id = utils.getUniqueIdentifierStr; + bidmanager.addCallback(id, func, eventStr); + return id; +}; + +/** + * Remove a callback event + * //@param {string} cbId id of the callback to remove + * @alias module:pbjs.removeCallback + * @returns {String} id for callback + */ +pbjs.removeCallback = function (/* cbId */) { + //todo + return null; +}; + +/** + * Wrapper to register bidderAdapter externally (adaptermanager.registerBidAdapter()) + * @param {[type]} bidderAdaptor [description] + * @param {[type]} bidderCode [description] + * @return {[type]} [description] + */ +pbjs.registerBidAdapter = function (bidderAdaptor, bidderCode) { + utils.logInfo('Invoking pbjs.registerBidAdapter', arguments); + try { + adaptermanager.registerBidAdapter(bidderAdaptor(), bidderCode); + } + catch (e) { + utils.logError('Error registering bidder adapter : ' + e.message); + } +}; + +pbjs.bidsAvailableForAdapter = function (bidderCode) { + utils.logInfo('Invoking pbjs.bidsAvailableForAdapter', arguments); + + pbjs._bidsRequested.find(bidderRequest => bidderRequest.bidderCode === bidderCode).bids + .map(bid => { + return Object.assign(bid, bidfactory.createBid(1), { + bidderCode, + adUnitCode: bid.placementCode + }); + }) + .map(bid => pbjs._bidsReceived.push(bid)); +}; + +/** + * Wrapper to bidfactory.createBid() + * @param {[type]} statusCode [description] + * @return {[type]} [description] + */ +pbjs.createBid = function (statusCode) { + utils.logInfo('Invoking pbjs.createBid', arguments); + return bidfactory.createBid(statusCode); +}; + +/** + * Wrapper to bidmanager.addBidResponse + * @param {[type]} adUnitCode [description] + * @param {[type]} bid [description] + */ +pbjs.addBidResponse = function (adUnitCode, bid) { + utils.logInfo('Invoking pbjs.addBidResponse', arguments); + bidmanager.addBidResponse(adUnitCode, bid); +}; + +/** + * Wrapper to adloader.loadScript + * @param {[type]} tagSrc [description] + * @param {Function} callback [description] + * @return {[type]} [description] + */ +pbjs.loadScript = function (tagSrc, callback, useCache) { + utils.logInfo('Invoking pbjs.loadScript', arguments); + adloader.loadScript(tagSrc, callback, useCache); +}; + +/** + * Will enable sendinga prebid.js to data provider specified + * @param {object} options object {provider : 'string', options : {}} + */ +pbjs.enableAnalytics = function (options) { + utils.logInfo('Invoking pbjs.enableAnalytics', arguments); + if (!options) { + utils.logError('pbjs.enableAnalytics should be called with option {}', 'prebid.js'); + return; + } + + if (options.provider === 'ga') { + try { + ga.enableAnalytics(typeof options.options === 'undefined' ? {} : options.options); + } + catch (e) { + utils.logError('Error calling GA: ', 'prebid.js', e); + } + } else if (options.provider === 'other_provider') { + //todo + return null; + } +}; + +/** + * This will tell analytics that all bids received after are "timed out" + */ +pbjs.sendTimeoutEvent = function () { + utils.logInfo('Invoking pbjs.sendTimeoutEvent', arguments); + timeOutBidders(); +}; + +pbjs.aliasBidder = function (bidderCode, alias) { + utils.logInfo('Invoking pbjs.aliasBidder', arguments); + if (bidderCode && alias) { + adaptermanager.aliasBidAdapter(bidderCode, alias); + } else { + utils.logError('bidderCode and alias must be passed as arguments', 'pbjs.aliasBidder'); + } +}; + +pbjs.setPriceGranularity = function (granularity) { + utils.logInfo('Invoking pbjs.setPriceGranularity', arguments); + if (!granularity) { + utils.logError('Prebid Error: no value passed to `setPriceGranularity()`'); + } else { + bidmanager.setPriceGranularity(granularity); + } +}; + +pbjs.enableSendAllBids = function () { + pbjs._sendAllBids = true; +}; + +processQue(); diff --git a/src/utils.js b/src/utils.js index ef8f5f91317..4adab3f74ea 100644 --- a/src/utils.js +++ b/src/utils.js @@ -1,432 +1,496 @@ -var CONSTANTS = require('./constants.json'); -var objectType_function = 'function'; -var objectType_undefined = 'undefined'; -var objectType_object = 'object'; -var objectType_string = 'string'; -var objectType_number = 'number'; - -var _loggingChecked = false; - -var _lgPriceCap = 5.00; -var _mgPriceCap = 20.00; -var _hgPriceCap = 20.00; - -var t_Arr = 'Array', - t_Str = 'String', - t_Fn = 'Function', - toString = Object.prototype.toString, - hasOwnProperty = Object.prototype.hasOwnProperty, - slice = Array.prototype.slice; - -/* - * Substitues into a string from a given map using the token - * Usage - * var str = 'text %%REPLACE%% this text with %%SOMETHING%%'; - * var map = {}; - * map['replace'] = 'it was subbed'; - * map['something'] = 'something else'; - * console.log(replaceTokenInString(str, map, '%%')); => "text it was subbed this text with something else" - */ -exports.replaceTokenInString = function(str, map, token) { - this._each(map, function (value, key) { - value = (value === undefined) ? '' : value; - - var keyString = token + key.toUpperCase() + token, - re = new RegExp(keyString, 'g'); - - str = str.replace(re, value); - }); - return str; -}; - -/* utility method to get incremental integer starting from 1 */ -var getIncrementalInteger = (function() { - var count = 0; - return function() { - count++; - return count; - }; -})(); - -function _getUniqueIdentifierStr() { - return getIncrementalInteger() + Math.random().toString(16).substr(2); -} - -//generate a random string (to be used as a dynamic JSONP callback) -exports.getUniqueIdentifierStr = _getUniqueIdentifierStr; - -exports.getBidIdParamater = function(key, paramsObj) { - if (paramsObj && paramsObj[key]) { - return paramsObj[key]; - } - return ''; -}; - -exports.tryAppendQueryString = function(existingUrl, key, value) { - if (value) { - return existingUrl += key + '=' + encodeURIComponent(value) + '&'; - } - return existingUrl; -}; - - -//parse a query string object passed in bid params -//bid params should be an object such as {key: "value", key1 : "value1"} -exports.parseQueryStringParameters = function(queryObj) { - var result = ""; - for (var k in queryObj){ - if (queryObj.hasOwnProperty(k)) - result += k + "=" + encodeURIComponent(queryObj[k]) + "&"; - } - return result; -}; - - -//transform an AdServer targeting bids into a query string to send to the adserver -//bid params should be an object such as {key: "value", key1 : "value1"} -exports.transformAdServerTargetingObj = function(adServerTargeting) { - var result = ""; - if (!adServerTargeting) - return ""; - for (var k in adServerTargeting) - if (adServerTargeting.hasOwnProperty(k)) - result += k + "=" + encodeURIComponent(adServerTargeting[k]) + "&"; - return result; -}; - -//Copy all of the properties in the source objects over to the target object -//return the target object. -exports.extend = function(target, source){ - target = target || {}; - - this._each(source,function(value,prop){ - if (typeof source[prop] === objectType_object) { - target[prop] = this.extend(target[prop], source[prop]); - } else { - target[prop] = source[prop]; - } - }); - return target; -}; - -//parse a GPT-Style General Size Array or a string like "300x250" into a format -//suitable for passing to a GPT tag, may include size and/or promo sizes -exports.parseSizesInput = function(sizeObj) { - var sizeQueryString; - var parsedSizes = []; - - //if a string for now we can assume it is a single size, like "300x250" - if (typeof sizeObj === objectType_string) { - //multiple sizes will be comma-separated - var sizes = sizeObj.split(','); - //regular expression to match strigns like 300x250 - //start of line, at least 1 number, an "x" , then at least 1 number, and the then end of the line - var sizeRegex = /^(\d)+x(\d)+$/i; - if (sizes) { - for (var curSizePos in sizes) { - if (hasOwn(sizes, curSizePos) && sizes[curSizePos].match(sizeRegex)) { - parsedSizes.push(sizes[curSizePos]); - } - } - } - } else if (typeof sizeObj === objectType_object) { - var sizeArrayLength = sizeObj.length; - //don't process empty array - if (sizeArrayLength > 0) { - //if we are a 2 item array of 2 numbers, we must be a SingleSize array - if (sizeArrayLength === 2 && typeof sizeObj[0] === objectType_number && typeof sizeObj[1] === objectType_number) { - parsedSizes.push(this.parseGPTSingleSizeArray(sizeObj)); - } else { - //otherwise, we must be a MultiSize array - for (var i = 0; i < sizeArrayLength; i++) { - parsedSizes.push(this.parseGPTSingleSizeArray(sizeObj[i])); - } - - } - } - } - - - //combine string into proper querystring for impbus - var parsedSizesLength = parsedSizes.length; - if (parsedSizesLength > 0) { - //first value should be "size" - sizeQueryString = 'size=' + parsedSizes[0]; - if (parsedSizesLength > 1) { - //any subsequent values should be "promo_sizes" - sizeQueryString += '&promo_sizes='; - for (var j = 1; j < parsedSizesLength; j++) { - sizeQueryString += parsedSizes[j] += ','; - } - //remove trailing comma - if (sizeQueryString && sizeQueryString.charAt(sizeQueryString.length - 1) === ',') { - sizeQueryString = sizeQueryString.slice(0, sizeQueryString.length - 1); - } - } - } - - return sizeQueryString; - -}; - -//parse a GPT style sigle size array, (i.e [300,250]) -//into an AppNexus style string, (i.e. 300x250) -exports.parseGPTSingleSizeArray = function(singleSize) { - //if we aren't exactly 2 items in this array, it is invalid - if (this.isArray(singleSize) && singleSize.length === 2 && (!isNaN(singleSize[0]) && !isNaN(singleSize[1]))) { - return singleSize[0] + 'x' + singleSize[1]; - } -}; - -exports.getTopWindowUrl = function() { - try { - return window.top.location.href; - } catch (e) { - return window.location.href; - } -}; - -exports.logMessage = function(msg) { - if (debugTurnedOn() && hasConsoleLogger()) { - console.log('MESSAGE: ' + msg); - } -}; - -function hasConsoleLogger() { - return (window.console && window.console.log); -} -exports.hasConsoleLogger = hasConsoleLogger; - -var errLogFn = (function (hasLogger) { - if (!hasLogger) return ''; - return window.console.error ? 'error' : 'log'; -}(hasConsoleLogger())); - -var debugTurnedOn = function() { - if (pbjs.logging === false && _loggingChecked === false) { - pbjs.logging = getParameterByName(CONSTANTS.DEBUG_MODE).toUpperCase() === 'TRUE'; - _loggingChecked = true; - } - - if (pbjs.logging) { - return true; - } - return false; - -}; -exports.debugTurnedOn = debugTurnedOn; - -exports.logError = function(msg, code, exception) { - var errCode = code || 'ERROR'; - if (debugTurnedOn() && hasConsoleLogger()) { - console[errLogFn].call(console, errCode + ': ' + msg, exception || ''); - } -}; - -exports.createInvisibleIframe = function _createInvisibleIframe() { - var f = document.createElement('iframe'); - f.id = _getUniqueIdentifierStr(); - f.height = 0; - f.width = 0; - f.border = '0px'; - f.hspace = '0'; - f.vspace = '0'; - f.marginWidth = '0'; - f.marginHeight = '0'; - f.style.border = '0'; - f.scrolling = 'no'; - f.frameBorder = '0'; - f.src = 'about:self'; - f.style = 'display:none'; - return f; -}; - -/* - * Check if a given paramater name exists in query string - * and if it does return the value - */ -var getParameterByName = function(name) { - var regexS = '[\\?&]' + name + '=([^&#]*)', - regex = new RegExp(regexS), - results = regex.exec(window.location.search); - if (results === null) { - return ''; - } - return decodeURIComponent(results[1].replace(/\+/g, ' ')); -}; - -exports.getPriceBucketString = function(cpm) { - var low = '', - med = '', - high = '', - cpmFloat = 0, - returnObj = { - low: low, - med: med, - high: high - }; - try { - cpmFloat = parseFloat(cpm); - if (cpmFloat) { - //round to closet .5 - if (cpmFloat > _lgPriceCap) { - returnObj.low = _lgPriceCap.toFixed(2); - } else { - returnObj.low = (Math.floor(cpm * 2) / 2).toFixed(2); - } - - //round to closet .1 - if (cpmFloat > _mgPriceCap) { - returnObj.med = _mgPriceCap.toFixed(2); - } else { - returnObj.med = (Math.floor(cpm * 10) / 10).toFixed(2); - } - - //round to closet .01 - if (cpmFloat > _hgPriceCap) { - returnObj.high = _hgPriceCap.toFixed(2); - } else { - returnObj.high = (Math.floor(cpm * 100) / 100).toFixed(2); - } - } - } catch (e) { - this.logError('Exception parsing CPM :' + e.message); - } - return returnObj; - -}; - -/** - * This function validates paramaters. - * @param {object[string]} paramObj [description] - * @param {string[]} requiredParamsArr [description] - * @return {bool} Bool if paramaters are valid - */ -exports.hasValidBidRequest = function(paramObj, requiredParamsArr, adapter){ - - for(var i = 0; i < requiredParamsArr.length; i++){ - var found = false; - - this._each(paramObj, function (value, key) { - if (key === requiredParamsArr[i]) { - found = true; - } - }); - - if(!found){ - this.logError('Params are missing for bid request. One of these required paramaters are missing: ' + requiredParamsArr, adapter); - return false; - } - } - - return true; -}; - -// Handle addEventListener gracefully in older browsers -exports.addEventHandler = function(element, event, func) { - if (element.addEventListener) { - element.addEventListener(event, func, true); - } else if (element.attachEvent) { - element.attachEvent('on' + event, func); - } - }; - /** - * Return if the object is of the - * given type. - * @param {*} object to test - * @param {String} _t type string (e.g., Array) - * @return {Boolean} if object is of type _t - */ -exports.isA = function(object, _t) { - return toString.call(object) === '[object ' + _t + ']'; -}; - -exports.isFn = function (object) { - return this.isA(object, t_Fn); -}; - -exports.isStr = function (object) { - return this.isA(object, t_Str); -}; - -exports.isArray = function (object) { - return this.isA(object, t_Arr); -}; - -/** - * Return if the object is "empty"; - * this includes falsey, no keys, or no items at indices - * @param {*} object object to test - * @return {Boolean} if object is empty - */ -exports.isEmpty = function(object) { - if (!object) return true; - if (this.isArray(object) || this.isStr(object)) return !(object.length > 0); - for (var k in object) { - if (hasOwnProperty.call(object, k)) return false; - } - return true; - }; - - /** - * Iterate object with the function - * falls back to es5 `forEach` - * @param {Array|Object} object - * @param {Function(value, key, object)} fn - */ -exports._each = function(object, fn) { - if (this.isEmpty(object)) return; - if (this.isFn(object.forEach)) return object.forEach(fn, this); - - var k = 0, - l = object.length; - - if (l > 0) { - for (; k < l; k++) fn(object[k], k, object); - } else { - for (k in object) { - if (hasOwnProperty.call(object, k)) fn.call(this, object[k], k); - } - } - }; - -exports.contains = function(a, obj) { - if(this.isEmpty(a)){ - return false; - } - if (this.isFn(a.indexOf)) { - return a.indexOf(obj) !== -1; - } - var i = a.length; - while (i--) { - if (a[i] === obj) { - return true; - } - } - return false; -}; - -/** - * Map an array or object into another array - * given a function - * @param {Array|Object} object - * @param {Function(value, key, object)} callback - * @return {Array} - */ -exports._map = function (object, callback) { - if (this.isEmpty(object)) return []; - if (this.isFn(object.map)) return object.map(callback); - var output = []; - this._each(object, function (value, key) { - output.push(callback(value, key, object)); - }); - return output; -}; - -var hasOwn = function(objectToCheck, propertyToCheckFor) { - if (objectToCheck.hasOwnProperty) { - return objectToCheck.hasOwnProperty(propertyToCheckFor); - } else { - return (typeof objectToCheck[propertyToCheckFor] !== UNDEFINED) && (objectToCheck.constructor.prototype[propertyToCheckFor] !== objectToCheck[propertyToCheckFor]); - } -}; \ No newline at end of file +var CONSTANTS = require('./constants.json'); + +var objectType_object = 'object'; +var objectType_string = 'string'; +var objectType_number = 'number'; + +var _loggingChecked = false; + +var t_Arr = 'Array'; +var t_Str = 'String'; +var t_Fn = 'Function'; +var toString = Object.prototype.toString; +let infoLogger = null; +try { + infoLogger = console.info.bind(window.console); +} +catch (e) { +} + +/* + * Substitutes into a string from a given map using the token + * Usage + * var str = 'text %%REPLACE%% this text with %%SOMETHING%%'; + * var map = {}; + * map['replace'] = 'it was subbed'; + * map['something'] = 'something else'; + * console.log(replaceTokenInString(str, map, '%%')); => "text it was subbed this text with something else" + */ +exports.replaceTokenInString = function (str, map, token) { + this._each(map, function (value, key) { + value = (value === undefined) ? '' : value; + + var keyString = token + key.toUpperCase() + token; + var re = new RegExp(keyString, 'g'); + + str = str.replace(re, value); + }); + + return str; +}; + +/* utility method to get incremental integer starting from 1 */ +var getIncrementalInteger = (function () { + var count = 0; + return function () { + count++; + return count; + }; +})(); + +function _getUniqueIdentifierStr() { + return getIncrementalInteger() + Math.random().toString(16).substr(2); +} + +//generate a random string (to be used as a dynamic JSONP callback) +exports.getUniqueIdentifierStr = _getUniqueIdentifierStr; + +exports.getBidIdParamater = function (key, paramsObj) { + if (paramsObj && paramsObj[key]) { + return paramsObj[key]; + } + + return ''; +}; + +exports.tryAppendQueryString = function (existingUrl, key, value) { + if (value) { + return existingUrl += key + '=' + encodeURIComponent(value) + '&'; + } + + return existingUrl; +}; + +//parse a query string object passed in bid params +//bid params should be an object such as {key: "value", key1 : "value1"} +exports.parseQueryStringParameters = function (queryObj) { + var result = ''; + for (var k in queryObj) { + if (queryObj.hasOwnProperty(k)) + result += k + '=' + encodeURIComponent(queryObj[k]) + '&'; + } + + return result; +}; + +//transform an AdServer targeting bids into a query string to send to the adserver +exports.transformAdServerTargetingObj = function (targeting) { + // we expect to receive targeting for a single slot at a time + if (targeting && Object.getOwnPropertyNames(targeting).length > 0) { + + return getKeys(targeting) + .map(key => `${key}=${encodeURIComponent(getValue(targeting, key))}`).join('&'); + } else { + return ''; + } +}; + +//Copy all of the properties in the source objects over to the target object +//return the target object. +exports.extend = function (target, source) { + target = target || {}; + + this._each(source, function (value, prop) { + if (typeof source[prop] === objectType_object) { + target[prop] = this.extend(target[prop], source[prop]); + } else { + target[prop] = source[prop]; + } + }); + + return target; +}; + +/** + * Parse a GPT-Style general size Array like `[[300, 250]]` or `"300x250,970x90"` into an array of sizes `["300x250"]` or '['300x250', '970x90']' + * @param {array[array|number]} sizeObj Input array or double array [300,250] or [[300,250], [728,90]] + * @return {array[string]} Array of strings like `["300x250"]` or `["300x250", "728x90"]` + */ +exports.parseSizesInput = function (sizeObj) { + var parsedSizes = []; + + //if a string for now we can assume it is a single size, like "300x250" + if (typeof sizeObj === objectType_string) { + //multiple sizes will be comma-separated + var sizes = sizeObj.split(','); + + //regular expression to match strigns like 300x250 + //start of line, at least 1 number, an "x" , then at least 1 number, and the then end of the line + var sizeRegex = /^(\d)+x(\d)+$/i; + if (sizes) { + for (var curSizePos in sizes) { + if (hasOwn(sizes, curSizePos) && sizes[curSizePos].match(sizeRegex)) { + parsedSizes.push(sizes[curSizePos]); + } + } + } + } else if (typeof sizeObj === objectType_object) { + var sizeArrayLength = sizeObj.length; + + //don't process empty array + if (sizeArrayLength > 0) { + //if we are a 2 item array of 2 numbers, we must be a SingleSize array + if (sizeArrayLength === 2 && typeof sizeObj[0] === objectType_number && typeof sizeObj[1] === objectType_number) { + parsedSizes.push(this.parseGPTSingleSizeArray(sizeObj)); + } else { + //otherwise, we must be a MultiSize array + for (var i = 0; i < sizeArrayLength; i++) { + parsedSizes.push(this.parseGPTSingleSizeArray(sizeObj[i])); + } + + } + } + } + + return parsedSizes; + +}; + +//parse a GPT style sigle size array, (i.e [300,250]) +//into an AppNexus style string, (i.e. 300x250) +exports.parseGPTSingleSizeArray = function (singleSize) { + //if we aren't exactly 2 items in this array, it is invalid + if (this.isArray(singleSize) && singleSize.length === 2 && (!isNaN(singleSize[0]) && !isNaN(singleSize[1]))) { + return singleSize[0] + 'x' + singleSize[1]; + } +}; + +exports.getTopWindowUrl = function () { + try { + return window.top.location.href; + } catch (e) { + return window.location.href; + } +}; + +exports.logWarn = function (msg) { + if (debugTurnedOn() && console.warn) { + console.warn('WARNING: ' + msg); + } +}; + +exports.logInfo = function (msg, args) { + if (debugTurnedOn() && hasConsoleLogger()) { + if (infoLogger) { + if (!args || args.length === 0) { + args = ''; + } + + infoLogger('INFO: ' + msg + ((args === '') ? '' : ' : params : '), args); + } + } +}; + +exports.logMessage = function (msg) { + if (debugTurnedOn() && hasConsoleLogger()) { + console.log('MESSAGE: ' + msg); + } +}; + +function hasConsoleLogger() { + return (window.console && window.console.log); +} + +exports.hasConsoleLogger = hasConsoleLogger; + +var errLogFn = (function (hasLogger) { + if (!hasLogger) return ''; + return window.console.error ? 'error' : 'log'; +}(hasConsoleLogger())); + +var debugTurnedOn = function () { + if (pbjs.logging === false && _loggingChecked === false) { + pbjs.logging = getParameterByName(CONSTANTS.DEBUG_MODE).toUpperCase() === 'TRUE'; + _loggingChecked = true; + } + + return !!pbjs.logging; +}; + +exports.debugTurnedOn = debugTurnedOn; + +exports.logError = function (msg, code, exception) { + var errCode = code || 'ERROR'; + if (debugTurnedOn() && hasConsoleLogger()) { + console[errLogFn].call(console, errCode + ': ' + msg, exception || ''); + } +}; + +exports.createInvisibleIframe = function _createInvisibleIframe() { + var f = document.createElement('iframe'); + f.id = _getUniqueIdentifierStr(); + f.height = 0; + f.width = 0; + f.border = '0px'; + f.hspace = '0'; + f.vspace = '0'; + f.marginWidth = '0'; + f.marginHeight = '0'; + f.style.border = '0'; + f.scrolling = 'no'; + f.frameBorder = '0'; + f.src = 'about:blank'; + f.style.display = 'none'; + return f; +}; + +/* + * Check if a given parameter name exists in query string + * and if it does return the value + */ +var getParameterByName = function (name) { + var regexS = '[\\?&]' + name + '=([^&#]*)'; + var regex = new RegExp(regexS); + var results = regex.exec(window.location.search); + if (results === null) { + return ''; + } + + return decodeURIComponent(results[1].replace(/\+/g, ' ')); +}; + +/** + * This function validates paramaters. + * @param {object[string]} paramObj [description] + * @param {string[]} requiredParamsArr [description] + * @return {bool} Bool if paramaters are valid + */ +exports.hasValidBidRequest = function (paramObj, requiredParamsArr, adapter) { + var found = false; + + function findParam(value, key) { + if (key === requiredParamsArr[i]) { + found = true; + } + } + + for (var i = 0; i < requiredParamsArr.length; i++) { + found = false; + + this._each(paramObj, findParam); + + if (!found) { + this.logError('Params are missing for bid request. One of these required paramaters are missing: ' + requiredParamsArr, adapter); + return false; + } + } + + return true; +}; + +// Handle addEventListener gracefully in older browsers +exports.addEventHandler = function (element, event, func) { + if (element.addEventListener) { + element.addEventListener(event, func, true); + } else if (element.attachEvent) { + element.attachEvent('on' + event, func); + } +}; +/** + * Return if the object is of the + * given type. + * @param {*} object to test + * @param {String} _t type string (e.g., Array) + * @return {Boolean} if object is of type _t + */ +exports.isA = function (object, _t) { + return toString.call(object) === '[object ' + _t + ']'; +}; + +exports.isFn = function (object) { + return this.isA(object, t_Fn); +}; + +exports.isStr = function (object) { + return this.isA(object, t_Str); +}; + +exports.isArray = function (object) { + return this.isA(object, t_Arr); +}; + +/** + * Return if the object is "empty"; + * this includes falsey, no keys, or no items at indices + * @param {*} object object to test + * @return {Boolean} if object is empty + */ +exports.isEmpty = function (object) { + if (!object) return true; + if (this.isArray(object) || this.isStr(object)) { + return !(object.length > 0); // jshint ignore:line + } + + for (var k in object) { + if (hasOwnProperty.call(object, k)) return false; + } + + return true; +}; + +/** + * Iterate object with the function + * falls back to es5 `forEach` + * @param {Array|Object} object + * @param {Function(value, key, object)} fn + */ +exports._each = function (object, fn) { + if (this.isEmpty(object)) return; + if (this.isFn(object.forEach)) return object.forEach(fn, this); + + var k = 0; + var l = object.length; + + if (l > 0) { + for (; k < l; k++) fn(object[k], k, object); + } else { + for (k in object) { + if (hasOwnProperty.call(object, k)) fn.call(this, object[k], k); + } + } +}; + +exports.contains = function (a, obj) { + if (this.isEmpty(a)) { + return false; + } + + if (this.isFn(a.indexOf)) { + return a.indexOf(obj) !== -1; + } + + var i = a.length; + while (i--) { + if (a[i] === obj) { + return true; + } + } + + return false; +}; + +exports.indexOf = (function () { + if (Array.prototype.indexOf) { + return Array.prototype.indexOf; + } + + // ie8 no longer supported + //return polyfills.indexOf; +}()); + +/** + * Map an array or object into another array + * given a function + * @param {Array|Object} object + * @param {Function(value, key, object)} callback + * @return {Array} + */ +exports._map = function (object, callback) { + if (this.isEmpty(object)) return []; + if (this.isFn(object.map)) return object.map(callback); + var output = []; + this._each(object, function (value, key) { + output.push(callback(value, key, object)); + }); + + return output; +}; + +var hasOwn = function (objectToCheck, propertyToCheckFor) { + if (objectToCheck.hasOwnProperty) { + return objectToCheck.hasOwnProperty(propertyToCheckFor); + } else { + return (typeof objectToCheck[propertyToCheckFor] !== 'undefined') && (objectToCheck.constructor.prototype[propertyToCheckFor] !== objectToCheck[propertyToCheckFor]); + } +}; +/** + * Creates a snippet of HTML that retrieves the specified `url` + * @param {string} url URL to be requested + * @return {string} HTML snippet that contains the img src = set to `url` + */ +exports.createTrackPixelHtml = function (url) { + if (!url) { + return ''; + } + + let escapedUrl = encodeURI(url); + let img = '
'; + img += '
'; + return img; +}; + +/** + * Returns iframe document in a browser agnostic way + * @param {object} iframe reference + * @return {object} iframe `document` reference + */ +exports.getIframeDocument = function (iframe) { + if (!iframe) { + return; + } + + let doc; + try { + if (iframe.contentWindow) { + doc = iframe.contentWindow.document; + } else if (iframe.contentDocument.document) { + doc = iframe.contentDocument.document; + } else { + doc = iframe.contentDocument; + } + } + catch (e) { + this.logError('Cannot get iframe document', e); + } + + return doc; +}; + +export function uniques(value, index, arry) { + return arry.indexOf(value) === index; +} + +export function flatten(a, b) { + return a.concat(b); +} + +export function getBidRequest(id) { + return pbjs._bidsRequested.map(bidSet => bidSet.bids.find(bid => bid.bidId === id)).find(bid => bid); +} + +export function getKeys(obj) { + return Object.keys(obj); +} + +export function getValue(obj, key) { + return obj[key]; +} + +export function getBidderCodes() { + // this could memoize adUnits + return pbjs.adUnits.map(unit => unit.bids.map(bid => bid.bidder) + .reduce(flatten, [])).reduce(flatten).filter(uniques); +} + +export function isGptPubadsDefined() { + if (window.googletag && exports.isFn(window.googletag.pubads) && exports.isFn(window.googletag.pubads().getSlots)) { + return true; + } +} + +export function getHighestCpm(previous, current) { + if (previous.cpm === current.cpm) { + return previous.timeToRespond > current.timeToRespond ? current : previous; + } + return previous.cpm < current.cpm ? current : previous; +} diff --git a/styleRules.jscs.json b/styleRules.jscs.json deleted file mode 100644 index 46978d54cac..00000000000 --- a/styleRules.jscs.json +++ /dev/null @@ -1,5 +0,0 @@ -{ - "excludeFiles": [""], - "validateQuoteMarks": "'", - "disallowNewlineBeforeBlockStatements": true -} \ No newline at end of file diff --git a/test/automatedRunnner.html b/test/automatedRunnner.html deleted file mode 100644 index 2f82a9a36da..00000000000 --- a/test/automatedRunnner.html +++ /dev/null @@ -1,29 +0,0 @@ - - - - - - Unit Tests for Prebid.js - - - -
- - - - - - - - - - - \ No newline at end of file diff --git a/test/fixtures/ad-server-targeting.json b/test/fixtures/ad-server-targeting.json new file mode 100644 index 00000000000..e6ab25fd306 --- /dev/null +++ b/test/fixtures/ad-server-targeting.json @@ -0,0 +1,12 @@ +{ + "/9968336/header-bid-tag-0": { + "hb_bidder": "rubicon", + "hb_adid": "13f44b0d3c", + "hb_pb": "1.50" + }, + "/9968336/header-bid-tag1": { + "hb_bidder": "openx", + "hb_adid": "147ac541a", + "hb_pb": "1.00" + } +} diff --git a/test/fixtures/bid-responses-cloned.json b/test/fixtures/bid-responses-cloned.json new file mode 100644 index 00000000000..71110214449 --- /dev/null +++ b/test/fixtures/bid-responses-cloned.json @@ -0,0 +1,130 @@ +{ + "/123456/header-bid-tag-1": { + "bids": [] + }, + "/123456/header-bid-tag-0": { + "bids": [ + { + "bidderCode": "criteo", + "width": 0, + "height": 0, + "statusMessage": "Bid returned empty or error response", + "adId": "83fb6a073", + "requestTimestamp": 1454535718619, + "responseTimestamp": 1454535720575, + "timeToRespond": 1956, + "cpm": 0, + "adUnitCode": "/123456/header-bid-tag-0", + "bidder": "criteo" + }, + { + "bidderCode": "sovrn", + "width": 0, + "height": 0, + "statusMessage": "Bid returned empty or error response", + "adId": "9d64dde8c", + "requestTimestamp": 1454535718628, + "responseTimestamp": 1454535721135, + "timeToRespond": 2507, + "cpm": 0, + "adUnitCode": "/123456/header-bid-tag-0", + "bidder": "sovrn" + }, + { + "bidderCode": "pulsepoint", + "width": 0, + "height": 0, + "statusMessage": "Bid returned empty or error response", + "adId": "102e25872d", + "requestTimestamp": 1454535718629, + "responseTimestamp": 1454535721687, + "timeToRespond": 3058, + "cpm": 0, + "adUnitCode": "/123456/header-bid-tag-0", + "bidder": "pulsepoint" + }, + { + "bidderCode": "amazon", + "width": 0, + "height": 0, + "statusMessage": "Bid available", + "adId": "112cdb3eff", + "adUnitCode": "/123456/header-bid-tag-0", + "bidder": "amazon" + }, + { + "bidderCode": "yieldbot", + "width": 0, + "height": 0, + "statusMessage": "Bid returned empty or error response", + "adId": "1234cc92d8", + "requestTimestamp": 1454535718624, + "responseTimestamp": 1454535722273, + "timeToRespond": 3649, + "cpm": 0, + "adUnitCode": "/123456/header-bid-tag-0", + "bidder": "yieldbot" + }, + { + "bidderCode": "openx", + "width": 0, + "height": 0, + "statusMessage": "Bid returned empty or error response", + "adId": "1383ffde21", + "requestTimestamp": 1454535718611, + "responseTimestamp": 1454535724228, + "timeToRespond": 5617, + "cpm": 0, + "adUnitCode": "/123456/header-bid-tag-0", + "bidder": "openx" + }, + { + "bidderCode": "rubicon", + "width": "300", + "height": "250", + "statusMessage": "Bid available", + "adId": "148018fe5e", + "cpm": 0.537234, + "ad": "", + "ad_id": "3163950", + "sizeId": "15", + "requestTimestamp": 1454535718610, + "responseTimestamp": 1454535724863, + "timeToRespond": 6253, + "adUnitCode": "/123456/header-bid-tag-0", + "bidder": "rubicon", + "size": "300x250", + "adserverTargeting": { + "hb_bidder": "rubicon", + "hb_adid": "148018fe5e", + "hb_pb": "10.00", + "foobar": "300x250" + } + }, + { + "bidderCode": "pubmatic", + "width": "300", + "height": "250", + "statusMessage": "Bid available", + "adId": "15bea0b1db", + "adSlot": "39620189@300x250", + "cpm": 0.01, + "ad": "\n ", + "adUrl": "http://aktrack.pubmatic.com/AdServer/AdDisplayTrackerServlet?operId=1&pubId=39741&siteId=66156&adId=148827&adServerId=243&kefact=0.010000&kaxefact=0.010000&kadNetFrequecy=1&kadwidth=300&kadheight=250&kadsizeid=9&kltstamp=1454535719&indirectAdId=0&adServerOptimizerId=2&ranreq=0.052495126612484455&kpbmtpfact=0.011000&dcId=1&tldId=13890466&passback=0&imprId=529C7210-AB7A-4217-A9BD-A3190CA2382A&oid=529C7210-AB7A-4217-A9BD-A3190CA2382A&ias=272&fbs=1&campaignId=5400&creativeId=0&pctr=0.000000&wDSPByrId=1&pageURL=http%3A%2F%2Flocalhost%3A9999%2FintegrationExamples%2Fgpt%2Fgpt.html&lpu=www.xfinity.com", + "dealId": "", + "requestTimestamp": 1454535718617, + "responseTimestamp": 1454535725437, + "timeToRespond": 6820, + "adUnitCode": "/123456/header-bid-tag-0", + "bidder": "pubmatic", + "size": "300x250", + "adserverTargeting": { + "hb_bidder": "pubmatic", + "hb_adid": "15bea0b1db", + "hb_pb": "10.00", + "foobar": "300x250" + } + } + ] + } +} \ No newline at end of file diff --git a/test/fixtures/bid-responses.json b/test/fixtures/bid-responses.json new file mode 100644 index 00000000000..674f3b51f32 --- /dev/null +++ b/test/fixtures/bid-responses.json @@ -0,0 +1,155 @@ +{ + "/123456/header-bid-tag-1": { + "bids": [], + "allBidsAvailable": false, + "bidsReceivedCount": 0 + }, + "/123456/header-bid-tag-0": { + "bids": [ + { + "bidderCode": "criteo", + "width": 0, + "height": 0, + "statusMessage": "Bid returned empty or error response", + "adId": "83fb6a073", + "requestTimestamp": 1454535718619, + "responseTimestamp": 1454535720575, + "timeToRespond": 1956, + "cpm": 0, + "pbLg": "", + "pbMg": "", + "pbHg": "", + "adUnitCode": "/123456/header-bid-tag-0", + "bidder": "criteo" + }, + { + "bidderCode": "sovrn", + "width": 0, + "height": 0, + "statusMessage": "Bid returned empty or error response", + "adId": "9d64dde8c", + "requestTimestamp": 1454535718628, + "responseTimestamp": 1454535721135, + "timeToRespond": 2507, + "cpm": 0, + "pbLg": "", + "pbMg": "", + "pbHg": "", + "adUnitCode": "/123456/header-bid-tag-0", + "bidder": "sovrn" + }, + { + "bidderCode": "pulsepoint", + "width": 0, + "height": 0, + "statusMessage": "Bid returned empty or error response", + "adId": "102e25872d", + "requestTimestamp": 1454535718629, + "responseTimestamp": 1454535721687, + "timeToRespond": 3058, + "cpm": 0, + "pbLg": "", + "pbMg": "", + "pbHg": "", + "adUnitCode": "/123456/header-bid-tag-0", + "bidder": "pulsepoint" + }, + { + "bidderCode": "amazon", + "width": 0, + "height": 0, + "statusMessage": "Bid available", + "adId": "112cdb3eff", + "adUnitCode": "/123456/header-bid-tag-0", + "bidder": "amazon" + }, + { + "bidderCode": "yieldbot", + "width": 0, + "height": 0, + "statusMessage": "Bid returned empty or error response", + "adId": "1234cc92d8", + "requestTimestamp": 1454535718624, + "responseTimestamp": 1454535722273, + "timeToRespond": 3649, + "cpm": 0, + "pbLg": "", + "pbMg": "", + "pbHg": "", + "adUnitCode": "/123456/header-bid-tag-0", + "bidder": "yieldbot" + }, + { + "bidderCode": "openx", + "width": 0, + "height": 0, + "statusMessage": "Bid returned empty or error response", + "adId": "1383ffde21", + "requestTimestamp": 1454535718611, + "responseTimestamp": 1454535724228, + "timeToRespond": 5617, + "cpm": 0, + "pbLg": "", + "pbMg": "", + "pbHg": "", + "adUnitCode": "/123456/header-bid-tag-0", + "bidder": "openx" + }, + { + "bidderCode": "rubicon", + "width": "300", + "height": "250", + "statusMessage": "Bid available", + "adId": "148018fe5e", + "cpm": 0.537234, + "ad": "", + "ad_id": "3163950", + "sizeId": "15", + "requestTimestamp": 1454535718610, + "responseTimestamp": 1454535724863, + "timeToRespond": 6253, + "pbLg": "0.50", + "pbMg": "0.50", + "pbHg": "0.53", + "adUnitCode": "/123456/header-bid-tag-0", + "bidder": "rubicon", + "size": "300x250", + "adserverTargeting": { + "hb_bidder": "rubicon", + "hb_adid": "148018fe5e", + "hb_pb": "10.00", + "foobar": "300x250" + } + }, + { + "bidderCode": "pubmatic", + "width": "300", + "height": "250", + "statusMessage": "Bid available", + "adId": "15bea0b1db", + "adSlot": "39620189@300x250", + "cpm": 0.01, + "ad": "\n ", + "adUrl": "http://aktrack.pubmatic.com/AdServer/AdDisplayTrackerServlet?operId=1&pubId=39741&siteId=66156&adId=148827&adServerId=243&kefact=0.010000&kaxefact=0.010000&kadNetFrequecy=1&kadwidth=300&kadheight=250&kadsizeid=9&kltstamp=1454535719&indirectAdId=0&adServerOptimizerId=2&ranreq=0.052495126612484455&kpbmtpfact=0.011000&dcId=1&tldId=13890466&passback=0&imprId=529C7210-AB7A-4217-A9BD-A3190CA2382A&oid=529C7210-AB7A-4217-A9BD-A3190CA2382A&ias=272&fbs=1&campaignId=5400&creativeId=0&pctr=0.000000&wDSPByrId=1&pageURL=http%3A%2F%2Flocalhost%3A9999%2FintegrationExamples%2Fgpt%2Fgpt.html&lpu=www.xfinity.com", + "dealId": "", + "requestTimestamp": 1454535718617, + "responseTimestamp": 1454535725437, + "timeToRespond": 6820, + "pbLg": "0.00", + "pbMg": "0.00", + "pbHg": "0.01", + "adUnitCode": "/123456/header-bid-tag-0", + "bidder": "pubmatic", + "size": "300x250", + "adserverTargeting": { + "hb_bidder": "pubmatic", + "hb_adid": "15bea0b1db", + "hb_pb": "10.00", + "foobar": "300x250" + } + } + ], + "allBidsAvailable": false, + "bidsReceivedCount": 8 + } +} \ No newline at end of file diff --git a/test/fixtures/config.json b/test/fixtures/config.json new file mode 100644 index 00000000000..bee7090a3bc --- /dev/null +++ b/test/fixtures/config.json @@ -0,0 +1,12 @@ +{ + "adUnitElementIDs": [ + "div-test-ad-0", + "div-test-ad-1", + "div-test-ad-2" + ], + "adUnitCodes": [ + "/19968336/header-bid-tag-0", + "/123456/header-bid-tag-1", + "/123456/header-bid-tag-2" + ] +} \ No newline at end of file diff --git a/test/fixtures/fixtures.js b/test/fixtures/fixtures.js new file mode 100644 index 00000000000..0af3d9aaa72 --- /dev/null +++ b/test/fixtures/fixtures.js @@ -0,0 +1,1279 @@ +// jscs:disable + +export function getBidRequests() { + return [ + { + "bidderCode": "appnexus", + "requestId": "1863e370099523", + "bidderRequestId": "2946b569352ef2", + "bids": [ + { + "bidder": "appnexus", + "params": { + "placementId": "4799418", + "test": "me" + }, + "placementCode": "/19968336/header-bid-tag1", + "sizes": [ + [ + 728, + 90 + ], + [ + 970, + 90 + ] + ], + "bidId": "392b5a6b05d648", + "bidderRequestId": "2946b569352ef2", + "requestId": "1863e370099523", + "startTime": 1462918897462, + "status": 1 + }, + { + "bidder": "appnexus", + "params": { + "placementId": "4799418" + }, + "placementCode": "/19968336/header-bid-tag-0", + "sizes": [ + [ + 300, + 250 + ], + [ + 300, + 600 + ] + ], + "bidId": "4dccdc37746135", + "bidderRequestId": "2946b569352ef2", + "requestId": "1863e370099523", + "startTime": 1462918897463, + "status": 1 + } + ], + "start": 1462918897460 + }, + { + "bidderCode": "pubmatic", + "requestId": "1863e370099523", + "bidderRequestId": "5e1525bae3eb11", + "bids": [ + { + "bidder": "pubmatic", + "params": { + "publisherId": 39741, + "adSlot": "39620189@300x250" + }, + "placementCode": "/19968336/header-bid-tag-0", + "sizes": [ + [ + 300, + 250 + ], + [ + 300, + 600 + ] + ], + "bidId": "6d11aa2d5b3659", + "bidderRequestId": "5e1525bae3eb11", + "requestId": "1863e370099523" + } + ], + "start": 1462918897463 + }, + { + "bidderCode": "rubicon", + "requestId": "1863e370099523", + "bidderRequestId": "8778750ee15a77", + "bids": [ + { + "bidder": "rubicon", + "params": { + "accountId": "14062", + "siteId": "70608", + "zoneId": "335918", + "userId": "12346", + "keywords": [ + "a", + "b", + "c" + ], + "inventory": { + "rating": "5-star", + "prodtype": "tech" + }, + "visitor": { + "ucat": "new", + "search": "iphone" + }, + "sizes": [ + 15, + 10 + ] + }, + "placementCode": "/19968336/header-bid-tag-0", + "sizes": [ + [ + 300, + 250 + ], + [ + 300, + 600 + ] + ], + "bidId": "96aff279720d39", + "bidderRequestId": "8778750ee15a77", + "requestId": "1863e370099523" + } + ], + "start": 1462918897474 + }, + { + "bidderCode": "triplelift", + "requestId": "1863e370099523", + "bidderRequestId": "107f5e6e98dcf09", + "bids": [ + { + "bidder": "triplelift", + "params": { + "inventoryCode": "sortable_all_right_sports" + }, + "placementCode": "/19968336/header-bid-tag-0", + "sizes": [ + [ + 300, + 250 + ], + [ + 300, + 600 + ] + ], + "bidId": "1144e2f0de84363", + "bidderRequestId": "107f5e6e98dcf09", + "requestId": "1863e370099523", + "startTime": 1462918897477 + } + ], + "start": 1462918897475 + }, + { + "bidderCode": "brightcom", + "requestId": "1863e370099523", + "bidderRequestId": "12eeded736650b4", + "bids": [ + { + "bidder": "brightcom", + "params": { + "tagId": 16577 + }, + "placementCode": "/19968336/header-bid-tag-0", + "sizes": [ + [ + 300, + 250 + ], + [ + 300, + 600 + ] + ], + "bidId": "135e89c039705da", + "bidderRequestId": "12eeded736650b4", + "requestId": "1863e370099523", + "status": 1 + } + ], + "start": 1462918897477 + }, + { + "bidderCode": "brealtime", + "requestId": "1863e370099523", + "bidderRequestId": "167c4d79b615948", + "bids": [ + { + "bidder": "brealtime", + "params": { + "placementId": "4799418" + }, + "placementCode": "/19968336/header-bid-tag-0", + "sizes": [ + [ + 300, + 250 + ], + [ + 300, + 600 + ] + ], + "bidId": "17dd1d869bed44e", + "bidderRequestId": "167c4d79b615948", + "requestId": "1863e370099523", + "startTime": 1462918897480, + "status": 1 + } + ], + "start": 1462918897479 + }, + { + "bidderCode": "pagescience", + "requestId": "1863e370099523", + "bidderRequestId": "18bed198c172a69", + "bids": [ + { + "bidder": "pagescience", + "params": { + "placementId": "4799418" + }, + "placementCode": "/19968336/header-bid-tag-0", + "sizes": [ + [ + 300, + 250 + ], + [ + 300, + 600 + ] + ], + "bidId": "192c8c1df0f5d1d", + "bidderRequestId": "18bed198c172a69", + "requestId": "1863e370099523", + "startTime": 1462918897481, + "status": 1 + } + ], + "start": 1462918897480 + }, + { + "bidderCode": "amazon", + "requestId": "1863e370099523", + "bidderRequestId": "20d0d30333715a7", + "bids": [ + { + "bidder": "amazon", + "params": { + "aId": 3080 + }, + "placementCode": "/19968336/header-bid-tag-0", + "sizes": [ + [ + 300, + 250 + ], + [ + 300, + 600 + ] + ], + "bidId": "21ae8131ec04f6e", + "bidderRequestId": "20d0d30333715a7", + "requestId": "1863e370099523" + } + ], + "start": 1462918897482 + } + ]; +} + +export function getBidResponses() { + return [ + { + "bidderCode": "triplelift", + "width": 0, + "height": 0, + "statusMessage": "Bid available", + "adId": "222bb26f9e8bd", + "cpm": 0.112256, + "ad": "", + "responseTimestamp": 1462919239337, + "requestTimestamp": 1462919238936, + "bidder": "triplelift", + "adUnitCode": "/19968336/header-bid-tag-0", + "timeToRespond": 401, + "pbLg": "0.00", + "pbMg": "0.10", + "pbHg": "0.11", + "pbAg": "0.10", + "size": "0x0", + "adserverTargeting": { + "hb_bidder": "triplelift", + "hb_adid": "222bb26f9e8bd", + "hb_pb": "10.00", + "hb_size": "0x0", + "foobar": "0x0" + } + }, + { + "bidderCode": "appnexus", + "width": 300, + "height": 250, + "statusMessage": "Bid available", + "adId": "233bcbee889d46d", + "creative_id": 29681110, + "cpm": 10, + "adUrl": "http://lax1-ib.adnxs.com/ab?e=wqT_3QL8BKh8AgAAAwDWAAUBCMjAybkFEMLLiJWTu9PsVxjL84KE1tzG-kkgASotCQAAAQII4D8RAQcQAADgPxkJCQjwPyEJCQjgPykRCaAwuvekAji-B0C-B0gCUNbLkw5YweAnYABokUB4190DgAEBigEDVVNEkgUG8FKYAawCoAH6AagBAbABALgBAcABA8gBANABANgBAOABAPABAIoCOnVmKCdhJywgNDk0NDcyLCAxNDYyOTE5MjQwKTt1ZigncicsIDI5NjgxMTEwLDIeAPBskgLZASFmU21rZ0FpNjBJY0VFTmJMa3c0WUFDREI0Q2N3QURnQVFBUkl2Z2RRdXZla0FsZ0FZSk1IYUFCd0EzZ0RnQUVEaUFFRGtBRUJtQUVCb0FFQnFBRURzQUVBdVFFQUFBQUFBQURnUDhFQgkMTEFBNERfSkFRMkxMcEVUMU93XzJRFSggd1AtQUJBUFVCBSxASmdDaW9EVTJnV2dBZ0MxQWcBFgRDOQkIqERBQWdQSUFnUFFBZ1BZQWdQZ0FnRG9BZ0Q0QWdDQUF3RS6aAiUhV1FrbmI63AAcd2VBbklBUW8JXPCVVS7YAugH4ALH0wHqAh9odHRwOi8vcHJlYmlkLm9yZzo5OTk5L2dwdC5odG1sgAMAiAMBkAMAmAMFoAMBqgMAsAMAuAMAwAOsAsgDANgDAOADAOgDAPgDA4AEAJIEBC9qcHSYBACiBAoxMC4xLjEzLjM3qAQAsgQICAAQABgAIAC4BADABADIBADSBAoxMC4wLjg1Ljkx&s=1bf15e8cdc7c0c8c119614c6386ab1496560da39&referrer=http%3A%2F%2Fprebid.org%3A9999%2Fgpt.html", + "responseTimestamp": 1462919239340, + "requestTimestamp": 1462919238919, + "bidder": "appnexus", + "adUnitCode": "/19968336/header-bid-tag-0", + "timeToRespond": 421, + "pbLg": "5.00", + "pbMg": "10.00", + "pbHg": "10.00", + "pbAg": "10.00", + "size": "300x250", + "alwaysUseBid": true, + "adserverTargeting": { + "hb_bidder": "appnexus", + "hb_adid": "233bcbee889d46d", + "hb_pb": "10.00", + "hb_size": "300x250", + "foobar": "300x250" + } + }, + { + "bidderCode": "appnexus", + "width": 728, + "height": 90, + "statusMessage": "Bid available", + "adId": "24bd938435ec3fc", + "creative_id": 33989846, + "cpm": 10, + "adUrl": "http://lax1-ib.adnxs.com/ab?e=wqT_3QLyBKhyAgAAAwDWAAUBCMjAybkFEOOryfjI7rGNWhjL84KE1tzG-kkgASotCQAAAQII4D8RAQcQAADgPxkJCQjwPyEJCQjgPykRCaAwuvekAji-B0C-B0gCUNbJmhBYweAnYABokUB4mt0CgAEBigEDVVNEkgUG8ECYAdgFoAFaqAEBsAEAuAEBwAEDyAEA0AEA2AEA4AEA8AEAigI6dWYoJ2EnLCA0OTQ0NzIsIDE0NjI5MTkyNDApOwEcLHInLCAzMzk4OTg0NjYeAPBvkgLNASFwU2Y1YUFpNjBJY0VFTmJKbWhBWUFDREI0Q2N3QURnQVFBUkl2Z2RRdXZla0FsZ0FZSk1IYUFCd3lnNTRDb0FCcGh5SUFRcVFBUUdZQVFHZ0FRR29BUU93QVFDNUFRQUFBQUFBQU9BX3dRRQkMSEFEZ1A4a0JJNTJDbGs5VjB6X1oVKCRQQV80QUVBOVFFBSw8bUFLS2dNQ0NENkFDQUxVQwUVBEwwCQh0T0FDQU9nQ0FQZ0NBSUFEQVEuLpoCJSFfZ2lqYXdpMtAA8KZ3ZUFuSUFRb2lvREFnZzgu2ALoB-ACx9MB6gIfaHR0cDovL3ByZWJpZC5vcmc6OTk5OS9ncHQuaHRtbIADAIgDAZADAJgDBaADAaoDALADALgDAMADrALIAwDYAwDgAwDoAwD4AwOABACSBAQvanB0mAQAogQKMTAuMS4xMy4zN6gEi-wJsgQICAAQABgAIAC4BADABADIBADSBAsxMC4wLjgwLjI0MA..&s=1f584d32c2d7ae3ce3662cfac7ca24e710bc7fd0&referrer=http%3A%2F%2Fprebid.org%3A9999%2Fgpt.html", + "responseTimestamp": 1462919239342, + "requestTimestamp": 1462919238919, + "bidder": "appnexus", + "adUnitCode": "/19968336/header-bid-tag1", + "timeToRespond": 423, + "pbLg": "5.00", + "pbMg": "10.00", + "pbHg": "10.00", + "pbAg": "10.00", + "size": "728x90", + "alwaysUseBid": true, + "adserverTargeting": { + "hb_bidder": "appnexus", + "hb_adid": "24bd938435ec3fc", + "hb_pb": "10.00", + "hb_size": "728x90", + "foobar": "728x90" + } + }, + { + "bidderCode": "pagescience", + "width": 300, + "height": 250, + "statusMessage": "Bid available", + "adId": "25bedd4813632d7", + "creative_id": 29681110, + "cpm": 0.5, + "adUrl": "http://lax1-ib.adnxs.com/ab?e=wqT_3QLzBKhzAgAAAwDWAAUBCMjAybkFEM7fioW41qjIQRjL84KE1tzG-kkgASotCQAAAQII4D8RAQcQAADgPxkJCQjwPyEJCQjgPykRCaAwuvekAji-B0C-B0gCUNbLkw5YweAnYABokUB4yIsEgAEBigEDVVNEkgUG8FKYAawCoAH6AagBAbABALgBAcABA8gBANABANgBAOABAPABAIoCOnVmKCdhJywgNDk0NDcyLCAxNDYyOTE5MjQwKTt1ZigncicsIDI5NjgxMTEwLDIeAPBvkgLNASFfeWVLYndpNjBJY0VFTmJMa3c0WUFDREI0Q2N3QURnQVFBUkl2Z2RRdXZla0FsZ0FZSk1IYUFCdzNBMTRDb0FCcGh5SUFRcVFBUUdZQVFHZ0FRR29BUU93QVFDNUFRQUFBQUFBQU9BX3dRRQkMSEFEZ1A4a0JSR3RLaGp1UTFEX1oVKCRQQV80QUVBOVFFBSw8bUFLS2dQVFNES0FDQUxVQwUVBEwwCQhwT0FDQU9nQ0FQZ0NBSUFEQVEuLpoCJSFlQWwtYkE20ADwpndlQW5JQVFvaW9EMDBndy7YAugH4ALH0wHqAh9odHRwOi8vcHJlYmlkLm9yZzo5OTk5L2dwdC5odG1sgAMAiAMBkAMAmAMFoAMBqgMAsAMAuAMAwAOsAsgDANgDAOADAOgDAPgDA4AEAJIEBC9qcHSYBACiBAoxMC4xLjEzLjM3qASL7AmyBAgIABAAGAAgALgEAMAEAMgEANIECzEwLjAuOTMuMjAy&s=1fd8d5650fa1fb8d918a2f403d6a1f97c10d7ec2&referrer=http%3A%2F%2Fprebid.org%3A9999%2Fgpt.html", + "responseTimestamp": 1462919239343, + "requestTimestamp": 1462919238943, + "bidder": "pagescience", + "adUnitCode": "/19968336/header-bid-tag-0", + "timeToRespond": 400, + "pbLg": "0.50", + "pbMg": "0.50", + "pbHg": "0.50", + "pbAg": "0.50", + "size": "300x250", + "adserverTargeting": { + "hb_bidder": "pagescience", + "hb_adid": "25bedd4813632d7", + "hb_pb": "10.00", + "hb_size": "300x250", + "foobar": "300x250" + } + }, + { + "bidderCode": "brightcom", + "width": 300, + "height": 250, + "statusMessage": "Bid available", + "adId": "26e0795ab963896", + "cpm": 0.17, + "ad": "", + "responseTimestamp": 1462919239420, + "requestTimestamp": 1462919238937, + "bidder": "brightcom", + "adUnitCode": "/19968336/header-bid-tag-0", + "timeToRespond": 483, + "pbLg": "0.00", + "pbMg": "0.10", + "pbHg": "0.17", + "pbAg": "0.15", + "size": "300x250", + "adserverTargeting": { + "hb_bidder": "brightcom", + "hb_adid": "26e0795ab963896", + "hb_pb": "10.00", + "hb_size": "300x250", + "foobar": "300x250" + } + }, + { + "bidderCode": "brealtime", + "width": 300, + "height": 250, + "statusMessage": "Bid available", + "adId": "275bd666f5a5a5d", + "creative_id": 29681110, + "cpm": 0.5, + "adUrl": "http://lax1-ib.adnxs.com/ab?e=wqT_3QLzBKhzAgAAAwDWAAUBCMjAybkFEIPr4YfMvKLoQBjL84KE1tzG-kkgASotCQAAAQII4D8RAQcQAADgPxkJCQjwPyEJCQjgPykRCaAwuvekAji-B0C-B0gCUNbLkw5YweAnYABokUB4mo8EgAEBigEDVVNEkgUG8FKYAawCoAH6AagBAbABALgBAcABA8gBANABANgBAOABAPABAIoCOnVmKCdhJywgNDk0NDcyLCAxNDYyOTE5MjQwKTt1ZigncicsIDI5NjgxMTEwLDIeAPBvkgLNASFsU2NQWlFpNjBJY0VFTmJMa3c0WUFDREI0Q2N3QURnQVFBUkl2Z2RRdXZla0FsZ0FZSk1IYUFCdzNBMTRDb0FCcGh5SUFRcVFBUUdZQVFHZ0FRR29BUU93QVFDNUFRQUFBQUFBQU9BX3dRRQkMSEFEZ1A4a0JHZmNvazFBejFUX1oVKCRQQV80QUVBOVFFBSw8bUFLS2dOU0NEYUFDQUxVQwUVBEwwCQh0T0FDQU9nQ0FQZ0NBSUFEQVEuLpoCJSFDUWxfYXdpMtAA8KZ3ZUFuSUFRb2lvRFVnZzAu2ALoB-ACx9MB6gIfaHR0cDovL3ByZWJpZC5vcmc6OTk5OS9ncHQuaHRtbIADAIgDAZADAJgDBaADAaoDALADALgDAMADrALIAwDYAwDgAwDoAwD4AwOABACSBAQvanB0mAQAogQKMTAuMS4xMy4zN6gEi-wJsgQICAAQABgAIAC4BADABADIBADSBAsxMC4wLjg1LjIwOA..&s=975cfe6518f064683541240f0d780d93a5f973da&referrer=http%3A%2F%2Fprebid.org%3A9999%2Fgpt.html", + "responseTimestamp": 1462919239486, + "requestTimestamp": 1462919238941, + "bidder": "brealtime", + "adUnitCode": "/19968336/header-bid-tag-0", + "timeToRespond": 545, + "pbLg": "0.50", + "pbMg": "0.50", + "pbHg": "0.50", + "pbAg": "0.50", + "size": "300x250", + "adserverTargeting": { + "hb_bidder": "brealtime", + "hb_adid": "275bd666f5a5a5d", + "hb_pb": "10.00", + "hb_size": "300x250", + "foobar": "300x250" + } + }, + { + "bidderCode": "pubmatic", + "width": "300", + "height": "250", + "statusMessage": "Bid available", + "adId": "28f4039c636b6a7", + "adSlot": "39620189@300x250", + "cpm": 5.9396, + "ad": "\r
", + "dealId": "", + "responseTimestamp": 1462919239544, + "requestTimestamp": 1462919238922, + "bidder": "pubmatic", + "adUnitCode": "/19968336/header-bid-tag-0", + "timeToRespond": 622, + "pbLg": "5.00", + "pbMg": "5.90", + "pbHg": "5.93", + "pbAg": "5.90", + "size": "300x250", + "adserverTargeting": { + "hb_bidder": "pubmatic", + "hb_adid": "28f4039c636b6a7", + "hb_pb": "10.00", + "hb_size": "300x250", + "foobar": "300x250" + } + }, + { + "bidderCode": "rubicon", + "width": 300, + "height": 600, + "statusMessage": "Bid available", + "adId": "29019e2ab586a5a", + "cpm": 2.74, + "ad": "", + "responseTimestamp": 1462919239860, + "requestTimestamp": 1462919238934, + "bidder": "rubicon", + "adUnitCode": "/19968336/header-bid-tag-0", + "timeToRespond": 926, + "pbLg": "2.50", + "pbMg": "2.70", + "pbHg": "2.74", + "pbAg": "2.70", + "size": "300x600", + "adserverTargeting": { + "hb_bidder": "rubicon", + "hb_adid": "29019e2ab586a5a", + "hb_pb": "10.00", + "hb_size": "300x600", + "foobar": "300x600" + } + } + ]; +} + +export function getSlotTargeting() { + return { + "/19968336/header-bid-tag-0": [ + { + "hb_bidder": [ + "appnexus" + ] + }, + { + "hb_adid": [ + "233bcbee889d46d" + ] + }, + { + "hb_pb": [ + "10.00" + ] + }, + { + "hb_size": [ + "300x250" + ] + }, + { + "foobar": [ + "300x250" + ] + } + ] + }; +} + +export function getAdUnits() { + return [ + { + "code": "/19968336/header-bid-tag1", + "sizes": [ + [ + 728, + 90 + ], + [ + 970, + 90 + ] + ], + "bids": [ + { + "bidder": "adequant", + "params": { + "publisher_id": "1234567", + "bidfloor": 0.01 + }, + "placementCode": "/19968336/header-bid-tag1", + "sizes": [ + [ + 728, + 90 + ], + [ + 970, + 90 + ] + ], + "bidId": "3692954f816efc", + "bidderRequestId": "2b1a75d5e826c4", + "requestId": "1ff753bd4ae5cb" + }, + { + "bidder": "appnexus", + "params": { + "placementId": "543221", + "test": "me" + }, + "placementCode": "/19968336/header-bid-tag1", + "sizes": [ + [ + 728, + 90 + ], + [ + 970, + 90 + ] + ], + "bidId": "68136e1c47023d", + "bidderRequestId": "55e24a66bed717", + "requestId": "1ff753bd4ae5cb", + "startTime": 1463510220995, + "status": 1 + } + ] + }, + { + "code": "/19968336/header-bid-tag-0", + "sizes": [ + [ + 300, + 250 + ], + [ + 300, + 600 + ] + ], + "bids": [ + { + "bidder": "appnexus", + "params": { + "placementId": "5324321" + }, + "placementCode": "/19968336/header-bid-tag-0", + "sizes": [ + [ + 300, + 250 + ], + [ + 300, + 600 + ] + ], + "bidId": "7e5d6af25ed188", + "bidderRequestId": "55e24a66bed717", + "requestId": "1ff753bd4ae5cb", + "startTime": 1463510220996 + }, + { + "bidder": "adequant", + "params": { + "publisher_id": "12353433", + "bidfloor": 0.01 + }, + "placementCode": "/19968336/header-bid-tag-0", + "sizes": [ + [ + 300, + 250 + ], + [ + 300, + 600 + ] + ], + "bidId": "4448d80ac1374e", + "bidderRequestId": "2b1a75d5e826c4", + "requestId": "1ff753bd4ae5cb" + }, + { + "bidder": "triplelift", + "params": { + "inventoryCode": "inv_code_here" + }, + "placementCode": "/19968336/header-bid-tag-0", + "sizes": [ + [ + 300, + 250 + ], + [ + 300, + 600 + ] + ], + "bidId": "9514d586c52abf", + "bidderRequestId": "8c4f03b838d7ee", + "requestId": "1ff753bd4ae5cb", + "startTime": 1463510220997 + }, + { + "bidder": "springserve", + "params": { + "impId": 1234, + "supplyPartnerId": 1, + "test": true + }, + "placementCode": "/19968336/header-bid-tag-0", + "sizes": [ + [ + 300, + 250 + ], + [ + 300, + 600 + ] + ], + "bidId": "113079fed03f58c", + "bidderRequestId": "1048e0df882e965", + "requestId": "1ff753bd4ae5cb" + }, + { + "bidder": "rubicon", + "params": { + "accountId": "123456", + "siteId": "345678", + "zoneId": "234567", + "userId": "12346", + "keywords": [ + "a", + "b", + "c" + ], + "inventory": { + "rating": "5-star", + "prodtype": "tech" + }, + "visitor": { + "ucat": "new", + "search": "iphone" + }, + "sizes": [ + 15, + 10 + ] + }, + "placementCode": "/19968336/header-bid-tag-0", + "sizes": [ + [ + 300, + 250 + ], + [ + 300, + 600 + ] + ], + "bidId": "13c2c2a79d155ea", + "bidderRequestId": "129e383ac549e5d", + "requestId": "1ff753bd4ae5cb" + }, + { + "bidder": "openx", + "params": { + "jstag_url": "http://servedbyopenx.com/w/1.0/jstag?nc=account_key", + "unit": 2345677 + }, + "placementCode": "/19968336/header-bid-tag-0", + "sizes": [ + [ + 300, + 250 + ], + [ + 300, + 600 + ] + ], + "bidId": "154f9cbf82df565", + "bidderRequestId": "1448569c2453b84", + "requestId": "1ff753bd4ae5cb" + }, + { + "bidder": "pubmatic", + "params": { + "publisherId": 1234567, + "adSlot": "1234567@300x250" + }, + "placementCode": "/19968336/header-bid-tag-0", + "sizes": [ + [ + 300, + 250 + ], + [ + 300, + 600 + ] + ], + "bidId": "17f8c3a8fb13308", + "bidderRequestId": "16095445eeb05e4", + "requestId": "1ff753bd4ae5cb" + }, + { + "bidder": "pagescience", + "params": { + "placementId": "1234567" + }, + "placementCode": "/19968336/header-bid-tag-0", + "sizes": [ + [ + 300, + 250 + ], + [ + 300, + 600 + ] + ], + "bidId": "2074d5757675542", + "bidderRequestId": "19883380ef5453a", + "requestId": "1ff753bd4ae5cb", + "startTime": 1463510221014 + }, + { + "bidder": "brealtime", + "params": { + "placementId": "1234567" + }, + "placementCode": "/19968336/header-bid-tag-0", + "sizes": [ + [ + 300, + 250 + ], + [ + 300, + 600 + ] + ], + "bidId": "222b6ad5a9b835d", + "bidderRequestId": "2163409fdf6f333", + "requestId": "1ff753bd4ae5cb", + "startTime": 1463510221015 + }, + { + "bidder": "indexExchange", + "params": { + "id": "1", + "siteID": 123456, + "timeout": 10000 + }, + "placementCode": "/19968336/header-bid-tag-0", + "sizes": [ + [ + 300, + 250 + ], + [ + 300, + 600 + ] + ], + "bidId": "2499961ab3f937a", + "bidderRequestId": "23b57a2de4ae50b", + "requestId": "1ff753bd4ae5cb" + }, + { + "bidder": "adform", + "params": { + "adxDomain": "adx.adform.net", + "mid": 123456, + "test": 1 + }, + "placementCode": "/19968336/header-bid-tag-0", + "sizes": [ + [ + 300, + 250 + ], + [ + 300, + 600 + ] + ], + "bidId": "26605265bf5e9c5", + "bidderRequestId": "25a0902299c17d3", + "requestId": "1ff753bd4ae5cb" + }, + { + "bidder": "amazon", + "params": { + "aId": 3080 + }, + "placementCode": "/19968336/header-bid-tag-0", + "sizes": [ + [ + 300, + 250 + ], + [ + 300, + 600 + ] + ], + "bidId": "2935d8f6764fe45", + "bidderRequestId": "28afa21ca9246c1", + "requestId": "1ff753bd4ae5cb" + }, + { + "bidder": "aol", + "params": { + "network": "112345.45", + "placement": 12345 + }, + "placementCode": "/19968336/header-bid-tag-0", + "sizes": [ + [ + 300, + 250 + ], + [ + 300, + 600 + ] + ], + "bidId": "31d1489681dc539", + "bidderRequestId": "30bf32da9080fdd", + "requestId": "1ff753bd4ae5cb" + }, + { + "bidder": "sovrn", + "params": { + "tagid": "123556" + }, + "placementCode": "/19968336/header-bid-tag-0", + "sizes": [ + [ + 300, + 250 + ], + [ + 300, + 600 + ] + ], + "bidId": "33c1a8028d91563", + "bidderRequestId": "324bcb47cfcf034", + "requestId": "1ff753bd4ae5cb" + }, + { + "bidder": "pulsepoint", + "params": { + "cf": "300X250", + "cp": 1233456, + "ct": 12357 + }, + "placementCode": "/19968336/header-bid-tag-0", + "sizes": [ + [ + 300, + 250 + ], + [ + 300, + 600 + ] + ], + "bidId": "379219f0506a26f", + "bidderRequestId": "360ec66bbb0719c", + "requestId": "1ff753bd4ae5cb" + }, + { + "bidder": "brightcom", + "params": { + "tagId": 75423 + }, + "placementCode": "/19968336/header-bid-tag-0", + "sizes": [ + [ + 300, + 250 + ], + [ + 300, + 600 + ] + ], + "bidId": "395cfcf496e7d6d", + "bidderRequestId": "38a776c7f001ea", + "requestId": "1ff753bd4ae5cb" + } + ] + }, + { + "code": "/7780971/apex_sparks_300", + "sizes": [ + [ + 300, + 250 + ] + ], + "bids": [ + { + "bidder": "sonobi", + "params": { + "dom_id": "div-gpt-ad-1455548812677-0", + "ad_unit": "/7780971/apex_sparks_300" + }, + "placementCode": "/7780971/apex_sparks_300", + "sizes": [ + [ + 300, + 250 + ] + ], + "bidId": "43a45a2cd8c6ef9", + "bidderRequestId": "42e2fe519b7c653", + "requestId": "1ff753bd4ae5cb" + } + ] + }, + { + "code": "/7780971/apex_sparks_skyscraper_x600", + "sizes": [ + [ + 300, + 600 + ] + ], + "bids": [ + { + "bidder": "sonobi", + "params": { + "dom_id": "div-gpt-ad-1455548812677-1", + "placement_id": "21d2da738fe0ba795cfb-test" + }, + "placementCode": "/7780971/apex_sparks_skyscraper_x600", + "sizes": [ + [ + 300, + 600 + ] + ], + "bidId": "44f16af92ca7607", + "bidderRequestId": "42e2fe519b7c653", + "requestId": "1ff753bd4ae5cb" + } + ] + }, + { + "code": "/7780971/apex_sparks_banner_x90", + "sizes": [ + [ + 728, + 90 + ] + ], + "bids": [ + { + "bidder": "sonobi", + "params": { + "dom_id": "div-gpt-ad-1455548812677-2", + "ad_unit": "/7780971/apex_sparks_banner_x90" + }, + "placementCode": "/7780971/apex_sparks_banner_x90", + "sizes": [ + [ + 728, + 90 + ] + ], + "bidId": "45e5c0084508efb", + "bidderRequestId": "42e2fe519b7c653", + "requestId": "1ff753bd4ae5cb" + } + ] + } + ] +}; + +// Ad server targeting when `pbjs.enableSendAllBids()` is called. +export function getAdServerTargeting() { + return { + "/19968336/header-bid-tag-0": { + "foobar": "300x250", + "hb_size": "300x250", + "hb_pb": "10.00", + "hb_adid": "233bcbee889d46d", + "hb_bidder": "appnexus", + "hb_size_triplelift": "0x0", + "hb_pb_triplelift": "10.00", + "hb_adid_triplelift": "222bb26f9e8bd", + "hb_bidder_triplelift": "triplelift", + "hb_size_appnexus": "300x250", + "hb_pb_appnexus": "10.00", + "hb_adid_appnexus": "233bcbee889d46d", + "hb_bidder_appnexus": "appnexus", + "hb_size_pagescience": "300x250", + "hb_pb_pagescience": "10.00", + "hb_adid_pagescience": "25bedd4813632d7", + "hb_bidder_pagescienc": "pagescience", + "hb_size_brightcom": "300x250", + "hb_pb_brightcom": "10.00", + "hb_adid_brightcom": "26e0795ab963896", + "hb_bidder_brightcom": "brightcom", + "hb_size_brealtime": "300x250", + "hb_pb_brealtime": "10.00", + "hb_adid_brealtime": "275bd666f5a5a5d", + "hb_bidder_brealtime": "brealtime", + "hb_size_pubmatic": "300x250", + "hb_pb_pubmatic": "10.00", + "hb_adid_pubmatic": "28f4039c636b6a7", + "hb_bidder_pubmatic": "pubmatic", + "hb_size_rubicon": "300x600", + "hb_pb_rubicon": "10.00", + "hb_adid_rubicon": "29019e2ab586a5a", + "hb_bidder_rubicon": "rubicon" + }, + "/19968336/header-bid-tag1": { + "foobar": "728x90", + "hb_size": "728x90", + "hb_pb": "10.00", + "hb_adid": "24bd938435ec3fc", + "hb_bidder": "appnexus", + "hb_size_appnexus": "728x90", + "hb_pb_appnexus": "10.00", + "hb_adid_appnexus": "24bd938435ec3fc", + "hb_bidder_appnexus": "appnexus" + } + }; +} + +// Key/values used to set ad server targeting. +export function getTargetingKeys() { + return [ + [ + "hb_bidder", + "appnexus" + ], + [ + "hb_adid", + "233bcbee889d46d" + ], + [ + "hb_pb", + "10.00" + ], + [ + "hb_size", + "300x250" + ], + [ + "foobar", + "300x250" + ], + [ + "foobar", + "300x250" + ] + ]; +} + +// Key/values used to set ad server targeting when bid landscape +// targeting is on. +export function getTargetingKeysBidLandscape() { + return [ + [ + "hb_bidder", + "appnexus" + ], + [ + "hb_adid", + "233bcbee889d46d" + ], + [ + "hb_pb", + "10.00" + ], + [ + "hb_size", + "300x250" + ], + [ + "foobar", + "300x250" + ], + [ + "foobar", + "300x250" + ], + [ + "hb_bidder_triplelift", + "triplelift" + ], + [ + "hb_adid_triplelift", + "222bb26f9e8bd" + ], + [ + "hb_pb_triplelift", + "10.00" + ], + [ + "hb_size_triplelift", + "0x0" + ], + [ + "hb_bidder_appnexus", + "appnexus" + ], + [ + "hb_adid_appnexus", + "233bcbee889d46d" + ], + [ + "hb_pb_appnexus", + "10.00" + ], + [ + "hb_size_appnexus", + "300x250" + ], + [ + "hb_bidder_pagescienc", + "pagescience" + ], + [ + "hb_adid_pagescience", + "25bedd4813632d7" + ], + [ + "hb_pb_pagescience", + "10.00" + ], + [ + "hb_size_pagescience", + "300x250" + ], + [ + "hb_bidder_brightcom", + "brightcom" + ], + [ + "hb_adid_brightcom", + "26e0795ab963896" + ], + [ + "hb_pb_brightcom", + "10.00" + ], + [ + "hb_size_brightcom", + "300x250" + ], + [ + "hb_bidder_brealtime", + "brealtime" + ], + [ + "hb_adid_brealtime", + "275bd666f5a5a5d" + ], + [ + "hb_pb_brealtime", + "10.00" + ], + [ + "hb_size_brealtime", + "300x250" + ], + [ + "hb_bidder_pubmatic", + "pubmatic" + ], + [ + "hb_adid_pubmatic", + "28f4039c636b6a7" + ], + [ + "hb_pb_pubmatic", + "10.00" + ], + [ + "hb_size_pubmatic", + "300x250" + ], + [ + "hb_bidder_rubicon", + "rubicon" + ], + [ + "hb_adid_rubicon", + "29019e2ab586a5a" + ], + [ + "hb_pb_rubicon", + "10.00" + ], + [ + "hb_size_rubicon", + "300x600" + ] + ]; +} diff --git a/test/fixtures/googletag-slots.json b/test/fixtures/googletag-slots.json new file mode 100644 index 00000000000..17f678fb918 --- /dev/null +++ b/test/fixtures/googletag-slots.json @@ -0,0 +1,11 @@ +[ + { + "getSlotElementId": "function(){ return \"div-gpt-ad-1438287399331-0\"; }" + }, + { + "getSlotElementId": "function(){ return \"div-gpt-ad-1438287399331-1\"; }" + }, + { + "getSlotElementId": "function(){ return \"div-gpt-ad-1438287399331-2\"; }" + } +] \ No newline at end of file diff --git a/test/fixtures/targeting-map.json b/test/fixtures/targeting-map.json new file mode 100644 index 00000000000..ce0e24d6fcd --- /dev/null +++ b/test/fixtures/targeting-map.json @@ -0,0 +1,29 @@ +{ + "/19968336/header-bid-tag-0": [ + { + "hb_bidder": [ + "appnexus" + ] + }, + { + "hb_adid": [ + "233bcbee889d46d" + ] + }, + { + "hb_pb": [ + "10.00" + ] + }, + { + "hb_size": [ + "300x250" + ] + }, + { + "foobar": [ + "300x250" + ] + } + ] +} \ No newline at end of file diff --git a/test/helpers/pbjs-test-only.js b/test/helpers/pbjs-test-only.js new file mode 100644 index 00000000000..d4096412d37 --- /dev/null +++ b/test/helpers/pbjs-test-only.js @@ -0,0 +1,10 @@ +export const pbjsTestOnly = { + + getAdUnits() { + return pbjs.adUnits; + }, + + clearAllAdUnits() { + pbjs.adUnits = []; + } +}; diff --git a/test/lib/browser/jquery.js b/test/lib/browser/jquery.js deleted file mode 100755 index ee0233703da..00000000000 --- a/test/lib/browser/jquery.js +++ /dev/null @@ -1,4 +0,0 @@ -/*! jQuery v1.7.1 jquery.com | jquery.org/license */ -(function(a,b){function cy(a){return f.isWindow(a)?a:a.nodeType===9?a.defaultView||a.parentWindow:!1}function cv(a){if(!ck[a]){var b=c.body,d=f("<"+a+">").appendTo(b),e=d.css("display");d.remove();if(e==="none"||e===""){cl||(cl=c.createElement("iframe"),cl.frameBorder=cl.width=cl.height=0),b.appendChild(cl);if(!cm||!cl.createElement)cm=(cl.contentWindow||cl.contentDocument).document,cm.write((c.compatMode==="CSS1Compat"?"":"")+""),cm.close();d=cm.createElement(a),cm.body.appendChild(d),e=f.css(d,"display"),b.removeChild(cl)}ck[a]=e}return ck[a]}function cu(a,b){var c={};f.each(cq.concat.apply([],cq.slice(0,b)),function(){c[this]=a});return c}function ct(){cr=b}function cs(){setTimeout(ct,0);return cr=f.now()}function cj(){try{return new a.ActiveXObject("Microsoft.XMLHTTP")}catch(b){}}function ci(){try{return new a.XMLHttpRequest}catch(b){}}function cc(a,c){a.dataFilter&&(c=a.dataFilter(c,a.dataType));var d=a.dataTypes,e={},g,h,i=d.length,j,k=d[0],l,m,n,o,p;for(g=1;g0){if(c!=="border")for(;g=0===c})}function S(a){return!a||!a.parentNode||a.parentNode.nodeType===11}function K(){return!0}function J(){return!1}function n(a,b,c){var d=b+"defer",e=b+"queue",g=b+"mark",h=f._data(a,d);h&&(c==="queue"||!f._data(a,e))&&(c==="mark"||!f._data(a,g))&&setTimeout(function(){!f._data(a,e)&&!f._data(a,g)&&(f.removeData(a,d,!0),h.fire())},0)}function m(a){for(var b in a){if(b==="data"&&f.isEmptyObject(a[b]))continue;if(b!=="toJSON")return!1}return!0}function l(a,c,d){if(d===b&&a.nodeType===1){var e="data-"+c.replace(k,"-$1").toLowerCase();d=a.getAttribute(e);if(typeof d=="string"){try{d=d==="true"?!0:d==="false"?!1:d==="null"?null:f.isNumeric(d)?parseFloat(d):j.test(d)?f.parseJSON(d):d}catch(g){}f.data(a,c,d)}else d=b}return d}function h(a){var b=g[a]={},c,d;a=a.split(/\s+/);for(c=0,d=a.length;c)[^>]*$|#([\w\-]*)$)/,j=/\S/,k=/^\s+/,l=/\s+$/,m=/^<(\w+)\s*\/?>(?:<\/\1>)?$/,n=/^[\],:{}\s]*$/,o=/\\(?:["\\\/bfnrt]|u[0-9a-fA-F]{4})/g,p=/"[^"\\\n\r]*"|true|false|null|-?\d+(?:\.\d*)?(?:[eE][+\-]?\d+)?/g,q=/(?:^|:|,)(?:\s*\[)+/g,r=/(webkit)[ \/]([\w.]+)/,s=/(opera)(?:.*version)?[ \/]([\w.]+)/,t=/(msie) ([\w.]+)/,u=/(mozilla)(?:.*? rv:([\w.]+))?/,v=/-([a-z]|[0-9])/ig,w=/^-ms-/,x=function(a,b){return(b+"").toUpperCase()},y=d.userAgent,z,A,B,C=Object.prototype.toString,D=Object.prototype.hasOwnProperty,E=Array.prototype.push,F=Array.prototype.slice,G=String.prototype.trim,H=Array.prototype.indexOf,I={};e.fn=e.prototype={constructor:e,init:function(a,d,f){var g,h,j,k;if(!a)return this;if(a.nodeType){this.context=this[0]=a,this.length=1;return this}if(a==="body"&&!d&&c.body){this.context=c,this[0]=c.body,this.selector=a,this.length=1;return this}if(typeof a=="string"){a.charAt(0)!=="<"||a.charAt(a.length-1)!==">"||a.length<3?g=i.exec(a):g=[null,a,null];if(g&&(g[1]||!d)){if(g[1]){d=d instanceof e?d[0]:d,k=d?d.ownerDocument||d:c,j=m.exec(a),j?e.isPlainObject(d)?(a=[c.createElement(j[1])],e.fn.attr.call(a,d,!0)):a=[k.createElement(j[1])]:(j=e.buildFragment([g[1]],[k]),a=(j.cacheable?e.clone(j.fragment):j.fragment).childNodes);return e.merge(this,a)}h=c.getElementById(g[2]);if(h&&h.parentNode){if(h.id!==g[2])return f.find(a);this.length=1,this[0]=h}this.context=c,this.selector=a;return this}return!d||d.jquery?(d||f).find(a):this.constructor(d).find(a)}if(e.isFunction(a))return f.ready(a);a.selector!==b&&(this.selector=a.selector,this.context=a.context);return e.makeArray(a,this)},selector:"",jquery:"1.7.1",length:0,size:function(){return this.length},toArray:function(){return F.call(this,0)},get:function(a){return a==null?this.toArray():a<0?this[this.length+a]:this[a]},pushStack:function(a,b,c){var d=this.constructor();e.isArray(a)?E.apply(d,a):e.merge(d,a),d.prevObject=this,d.context=this.context,b==="find"?d.selector=this.selector+(this.selector?" ":"")+c:b&&(d.selector=this.selector+"."+b+"("+c+")");return d},each:function(a,b){return e.each(this,a,b)},ready:function(a){e.bindReady(),A.add(a);return this},eq:function(a){a=+a;return a===-1?this.slice(a):this.slice(a,a+1)},first:function(){return this.eq(0)},last:function(){return this.eq(-1)},slice:function(){return this.pushStack(F.apply(this,arguments),"slice",F.call(arguments).join(","))},map:function(a){return this.pushStack(e.map(this,function(b,c){return a.call(b,c,b)}))},end:function(){return this.prevObject||this.constructor(null)},push:E,sort:[].sort,splice:[].splice},e.fn.init.prototype=e.fn,e.extend=e.fn.extend=function(){var a,c,d,f,g,h,i=arguments[0]||{},j=1,k=arguments.length,l=!1;typeof i=="boolean"&&(l=i,i=arguments[1]||{},j=2),typeof i!="object"&&!e.isFunction(i)&&(i={}),k===j&&(i=this,--j);for(;j0)return;A.fireWith(c,[e]),e.fn.trigger&&e(c).trigger("ready").off("ready")}},bindReady:function(){if(!A){A=e.Callbacks("once memory");if(c.readyState==="complete")return setTimeout(e.ready,1);if(c.addEventListener)c.addEventListener("DOMContentLoaded",B,!1),a.addEventListener("load",e.ready,!1);else if(c.attachEvent){c.attachEvent("onreadystatechange",B),a.attachEvent("onload",e.ready);var b=!1;try{b=a.frameElement==null}catch(d){}c.documentElement.doScroll&&b&&J()}}},isFunction:function(a){return e.type(a)==="function"},isArray:Array.isArray||function(a){return e.type(a)==="array"},isWindow:function(a){return a&&typeof a=="object"&&"setInterval"in a},isNumeric:function(a){return!isNaN(parseFloat(a))&&isFinite(a)},type:function(a){return a==null?String(a):I[C.call(a)]||"object"},isPlainObject:function(a){if(!a||e.type(a)!=="object"||a.nodeType||e.isWindow(a))return!1;try{if(a.constructor&&!D.call(a,"constructor")&&!D.call(a.constructor.prototype,"isPrototypeOf"))return!1}catch(c){return!1}var d;for(d in a);return d===b||D.call(a,d)},isEmptyObject:function(a){for(var b in a)return!1;return!0},error:function(a){throw new Error(a)},parseJSON:function(b){if(typeof b!="string"||!b)return null;b=e.trim(b);if(a.JSON&&a.JSON.parse)return a.JSON.parse(b);if(n.test(b.replace(o,"@").replace(p,"]").replace(q,"")))return(new Function("return "+b))();e.error("Invalid JSON: "+b)},parseXML:function(c){var d,f;try{a.DOMParser?(f=new DOMParser,d=f.parseFromString(c,"text/xml")):(d=new ActiveXObject("Microsoft.XMLDOM"),d.async="false",d.loadXML(c))}catch(g){d=b}(!d||!d.documentElement||d.getElementsByTagName("parsererror").length)&&e.error("Invalid XML: "+c);return d},noop:function(){},globalEval:function(b){b&&j.test(b)&&(a.execScript||function(b){a.eval.call(a,b)})(b)},camelCase:function(a){return a.replace(w,"ms-").replace(v,x)},nodeName:function(a,b){return a.nodeName&&a.nodeName.toUpperCase()===b.toUpperCase()},each:function(a,c,d){var f,g=0,h=a.length,i=h===b||e.isFunction(a);if(d){if(i){for(f in a)if(c.apply(a[f],d)===!1)break}else for(;g0&&a[0]&&a[j-1]||j===0||e.isArray(a));if(k)for(;i1?i.call(arguments,0):b,j.notifyWith(k,e)}}function l(a){return function(c){b[a]=arguments.length>1?i.call(arguments,0):c,--g||j.resolveWith(j,b)}}var b=i.call(arguments,0),c=0,d=b.length,e=Array(d),g=d,h=d,j=d<=1&&a&&f.isFunction(a.promise)?a:f.Deferred(),k=j.promise();if(d>1){for(;c
a",d=q.getElementsByTagName("*"),e=q.getElementsByTagName("a")[0];if(!d||!d.length||!e)return{};g=c.createElement("select"),h=g.appendChild(c.createElement("option")),i=q.getElementsByTagName("input")[0],b={leadingWhitespace:q.firstChild.nodeType===3,tbody:!q.getElementsByTagName("tbody").length,htmlSerialize:!!q.getElementsByTagName("link").length,style:/top/.test(e.getAttribute("style")),hrefNormalized:e.getAttribute("href")==="/a",opacity:/^0.55/.test(e.style.opacity),cssFloat:!!e.style.cssFloat,checkOn:i.value==="on",optSelected:h.selected,getSetAttribute:q.className!=="t",enctype:!!c.createElement("form").enctype,html5Clone:c.createElement("nav").cloneNode(!0).outerHTML!=="<:nav>",submitBubbles:!0,changeBubbles:!0,focusinBubbles:!1,deleteExpando:!0,noCloneEvent:!0,inlineBlockNeedsLayout:!1,shrinkWrapBlocks:!1,reliableMarginRight:!0},i.checked=!0,b.noCloneChecked=i.cloneNode(!0).checked,g.disabled=!0,b.optDisabled=!h.disabled;try{delete q.test}catch(s){b.deleteExpando=!1}!q.addEventListener&&q.attachEvent&&q.fireEvent&&(q.attachEvent("onclick",function(){b.noCloneEvent=!1}),q.cloneNode(!0).fireEvent("onclick")),i=c.createElement("input"),i.value="t",i.setAttribute("type","radio"),b.radioValue=i.value==="t",i.setAttribute("checked","checked"),q.appendChild(i),k=c.createDocumentFragment(),k.appendChild(q.lastChild),b.checkClone=k.cloneNode(!0).cloneNode(!0).lastChild.checked,b.appendChecked=i.checked,k.removeChild(i),k.appendChild(q),q.innerHTML="",a.getComputedStyle&&(j=c.createElement("div"),j.style.width="0",j.style.marginRight="0",q.style.width="2px",q.appendChild(j),b.reliableMarginRight=(parseInt((a.getComputedStyle(j,null)||{marginRight:0}).marginRight,10)||0)===0);if(q.attachEvent)for(o in{submit:1,change:1,focusin:1})n="on"+o,p=n in q,p||(q.setAttribute(n,"return;"),p=typeof q[n]=="function"),b[o+"Bubbles"]=p;k.removeChild(q),k=g=h=j=q=i=null,f(function(){var a,d,e,g,h,i,j,k,m,n,o,r=c.getElementsByTagName("body")[0];!r||(j=1,k="position:absolute;top:0;left:0;width:1px;height:1px;margin:0;",m="visibility:hidden;border:0;",n="style='"+k+"border:5px solid #000;padding:0;'",o="
"+""+"
",a=c.createElement("div"),a.style.cssText=m+"width:0;height:0;position:static;top:0;margin-top:"+j+"px",r.insertBefore(a,r.firstChild),q=c.createElement("div"),a.appendChild(q),q.innerHTML="
t
",l=q.getElementsByTagName("td"),p=l[0].offsetHeight===0,l[0].style.display="",l[1].style.display="none",b.reliableHiddenOffsets=p&&l[0].offsetHeight===0,q.innerHTML="",q.style.width=q.style.paddingLeft="1px",f.boxModel=b.boxModel=q.offsetWidth===2,typeof q.style.zoom!="undefined"&&(q.style.display="inline",q.style.zoom=1,b.inlineBlockNeedsLayout=q.offsetWidth===2,q.style.display="",q.innerHTML="
",b.shrinkWrapBlocks=q.offsetWidth!==2),q.style.cssText=k+m,q.innerHTML=o,d=q.firstChild,e=d.firstChild,h=d.nextSibling.firstChild.firstChild,i={doesNotAddBorder:e.offsetTop!==5,doesAddBorderForTableAndCells:h.offsetTop===5},e.style.position="fixed",e.style.top="20px",i.fixedPosition=e.offsetTop===20||e.offsetTop===15,e.style.position=e.style.top="",d.style.overflow="hidden",d.style.position="relative",i.subtractsBorderForOverflowNotVisible=e.offsetTop===-5,i.doesNotIncludeMarginInBodyOffset=r.offsetTop!==j,r.removeChild(a),q=a=null,f.extend(b,i))});return b}();var j=/^(?:\{.*\}|\[.*\])$/,k=/([A-Z])/g;f.extend({cache:{},uuid:0,expando:"jQuery"+(f.fn.jquery+Math.random()).replace(/\D/g,""),noData:{embed:!0,object:"clsid:D27CDB6E-AE6D-11cf-96B8-444553540000",applet:!0},hasData:function(a){a=a.nodeType?f.cache[a[f.expando]]:a[f.expando];return!!a&&!m(a)},data:function(a,c,d,e){if(!!f.acceptData(a)){var g,h,i,j=f.expando,k=typeof c=="string",l=a.nodeType,m=l?f.cache:a,n=l?a[j]:a[j]&&j,o=c==="events";if((!n||!m[n]||!o&&!e&&!m[n].data)&&k&&d===b)return;n||(l?a[j]=n=++f.uuid:n=j),m[n]||(m[n]={},l||(m[n].toJSON=f.noop));if(typeof c=="object"||typeof c=="function")e?m[n]=f.extend(m[n],c):m[n].data=f.extend(m[n].data,c);g=h=m[n],e||(h.data||(h.data={}),h=h.data),d!==b&&(h[f.camelCase(c)]=d);if(o&&!h[c])return g.events;k?(i=h[c],i==null&&(i=h[f.camelCase(c)])):i=h;return i}},removeData:function(a,b,c){if(!!f.acceptData(a)){var d,e,g,h=f.expando,i=a.nodeType,j=i?f.cache:a,k=i?a[h]:h;if(!j[k])return;if(b){d=c?j[k]:j[k].data;if(d){f.isArray(b)||(b in d?b=[b]:(b=f.camelCase(b),b in d?b=[b]:b=b.split(" ")));for(e=0,g=b.length;e-1)return!0;return!1},val:function(a){var c,d,e,g=this[0];{if(!!arguments.length){e=f.isFunction(a);return this.each(function(d){var g=f(this),h;if(this.nodeType===1){e?h=a.call(this,d,g.val()):h=a,h==null?h="":typeof h=="number"?h+="":f.isArray(h)&&(h=f.map(h,function(a){return a==null?"":a+""})),c=f.valHooks[this.nodeName.toLowerCase()]||f.valHooks[this.type];if(!c||!("set"in c)||c.set(this,h,"value")===b)this.value=h}})}if(g){c=f.valHooks[g.nodeName.toLowerCase()]||f.valHooks[g.type];if(c&&"get"in c&&(d=c.get(g,"value"))!==b)return d;d=g.value;return typeof d=="string"?d.replace(q,""):d==null?"":d}}}}),f.extend({valHooks:{option:{get:function(a){var b=a.attributes.value;return!b||b.specified?a.value:a.text}},select:{get:function(a){var b,c,d,e,g=a.selectedIndex,h=[],i=a.options,j=a.type==="select-one";if(g<0)return null;c=j?g:0,d=j?g+1:i.length;for(;c=0}),c.length||(a.selectedIndex=-1);return c}}},attrFn:{val:!0,css:!0,html:!0,text:!0,data:!0,width:!0,height:!0,offset:!0},attr:function(a,c,d,e){var g,h,i,j=a.nodeType;if(!!a&&j!==3&&j!==8&&j!==2){if(e&&c in f.attrFn)return f(a)[c](d);if(typeof a.getAttribute=="undefined")return f.prop(a,c,d);i=j!==1||!f.isXMLDoc(a),i&&(c=c.toLowerCase(),h=f.attrHooks[c]||(u.test(c)?x:w));if(d!==b){if(d===null){f.removeAttr(a,c);return}if(h&&"set"in h&&i&&(g=h.set(a,d,c))!==b)return g;a.setAttribute(c,""+d);return d}if(h&&"get"in h&&i&&(g=h.get(a,c))!==null)return g;g=a.getAttribute(c);return g===null?b:g}},removeAttr:function(a,b){var c,d,e,g,h=0;if(b&&a.nodeType===1){d=b.toLowerCase().split(p),g=d.length;for(;h=0}})});var z=/^(?:textarea|input|select)$/i,A=/^([^\.]*)?(?:\.(.+))?$/,B=/\bhover(\.\S+)?\b/,C=/^key/,D=/^(?:mouse|contextmenu)|click/,E=/^(?:focusinfocus|focusoutblur)$/,F=/^(\w*)(?:#([\w\-]+))?(?:\.([\w\-]+))?$/,G=function(a){var b=F.exec(a);b&&(b[1]=(b[1]||"").toLowerCase(),b[3]=b[3]&&new RegExp("(?:^|\\s)"+b[3]+"(?:\\s|$)"));return b},H=function(a,b){var c=a.attributes||{};return(!b[1]||a.nodeName.toLowerCase()===b[1])&&(!b[2]||(c.id||{}).value===b[2])&&(!b[3]||b[3].test((c["class"]||{}).value))},I=function(a){return f.event.special.hover?a:a.replace(B,"mouseenter$1 mouseleave$1")}; -f.event={add:function(a,c,d,e,g){var h,i,j,k,l,m,n,o,p,q,r,s;if(!(a.nodeType===3||a.nodeType===8||!c||!d||!(h=f._data(a)))){d.handler&&(p=d,d=p.handler),d.guid||(d.guid=f.guid++),j=h.events,j||(h.events=j={}),i=h.handle,i||(h.handle=i=function(a){return typeof f!="undefined"&&(!a||f.event.triggered!==a.type)?f.event.dispatch.apply(i.elem,arguments):b},i.elem=a),c=f.trim(I(c)).split(" ");for(k=0;k=0&&(h=h.slice(0,-1),k=!0),h.indexOf(".")>=0&&(i=h.split("."),h=i.shift(),i.sort());if((!e||f.event.customEvent[h])&&!f.event.global[h])return;c=typeof c=="object"?c[f.expando]?c:new f.Event(h,c):new f.Event(h),c.type=h,c.isTrigger=!0,c.exclusive=k,c.namespace=i.join("."),c.namespace_re=c.namespace?new RegExp("(^|\\.)"+i.join("\\.(?:.*\\.)?")+"(\\.|$)"):null,o=h.indexOf(":")<0?"on"+h:"";if(!e){j=f.cache;for(l in j)j[l].events&&j[l].events[h]&&f.event.trigger(c,d,j[l].handle.elem,!0);return}c.result=b,c.target||(c.target=e),d=d!=null?f.makeArray(d):[],d.unshift(c),p=f.event.special[h]||{};if(p.trigger&&p.trigger.apply(e,d)===!1)return;r=[[e,p.bindType||h]];if(!g&&!p.noBubble&&!f.isWindow(e)){s=p.delegateType||h,m=E.test(s+h)?e:e.parentNode,n=null;for(;m;m=m.parentNode)r.push([m,s]),n=m;n&&n===e.ownerDocument&&r.push([n.defaultView||n.parentWindow||a,s])}for(l=0;le&&i.push({elem:this,matches:d.slice(e)});for(j=0;j0?this.on(b,null,a,c):this.trigger(b)},f.attrFn&&(f.attrFn[b]=!0),C.test(b)&&(f.event.fixHooks[b]=f.event.keyHooks),D.test(b)&&(f.event.fixHooks[b]=f.event.mouseHooks)}),function(){function x(a,b,c,e,f,g){for(var h=0,i=e.length;h0){k=j;break}}j=j[a]}e[h]=k}}}function w(a,b,c,e,f,g){for(var h=0,i=e.length;h+~,(\[\\]+)+|[>+~])(\s*,\s*)?((?:.|\r|\n)*)/g,d="sizcache"+(Math.random()+"").replace(".",""),e=0,g=Object.prototype.toString,h=!1,i=!0,j=/\\/g,k=/\r\n/g,l=/\W/;[0,0].sort(function(){i=!1;return 0});var m=function(b,d,e,f){e=e||[],d=d||c;var h=d;if(d.nodeType!==1&&d.nodeType!==9)return[];if(!b||typeof b!="string")return e;var i,j,k,l,n,q,r,t,u=!0,v=m.isXML(d),w=[],x=b;do{a.exec(""),i=a.exec(x);if(i){x=i[3],w.push(i[1]);if(i[2]){l=i[3];break}}}while(i);if(w.length>1&&p.exec(b))if(w.length===2&&o.relative[w[0]])j=y(w[0]+w[1],d,f);else{j=o.relative[w[0]]?[d]:m(w.shift(),d);while(w.length)b=w.shift(),o.relative[b]&&(b+=w.shift()),j=y(b,j,f)}else{!f&&w.length>1&&d.nodeType===9&&!v&&o.match.ID.test(w[0])&&!o.match.ID.test(w[w.length-1])&&(n=m.find(w.shift(),d,v),d=n.expr?m.filter(n.expr,n.set)[0]:n.set[0]);if(d){n=f?{expr:w.pop(),set:s(f)}:m.find(w.pop(),w.length===1&&(w[0]==="~"||w[0]==="+")&&d.parentNode?d.parentNode:d,v),j=n.expr?m.filter(n.expr,n.set):n.set,w.length>0?k=s(j):u=!1;while(w.length)q=w.pop(),r=q,o.relative[q]?r=w.pop():q="",r==null&&(r=d),o.relative[q](k,r,v)}else k=w=[]}k||(k=j),k||m.error(q||b);if(g.call(k)==="[object Array]")if(!u)e.push.apply(e,k);else if(d&&d.nodeType===1)for(t=0;k[t]!=null;t++)k[t]&&(k[t]===!0||k[t].nodeType===1&&m.contains(d,k[t]))&&e.push(j[t]);else for(t=0;k[t]!=null;t++)k[t]&&k[t].nodeType===1&&e.push(j[t]);else s(k,e);l&&(m(l,h,e,f),m.uniqueSort(e));return e};m.uniqueSort=function(a){if(u){h=i,a.sort(u);if(h)for(var b=1;b0},m.find=function(a,b,c){var d,e,f,g,h,i;if(!a)return[];for(e=0,f=o.order.length;e":function(a,b){var c,d=typeof b=="string",e=0,f=a.length;if(d&&!l.test(b)){b=b.toLowerCase();for(;e=0)?c||d.push(h):c&&(b[g]=!1));return!1},ID:function(a){return a[1].replace(j,"")},TAG:function(a,b){return a[1].replace(j,"").toLowerCase()},CHILD:function(a){if(a[1]==="nth"){a[2]||m.error(a[0]),a[2]=a[2].replace(/^\+|\s*/g,"");var b=/(-?)(\d*)(?:n([+\-]?\d*))?/.exec(a[2]==="even"&&"2n"||a[2]==="odd"&&"2n+1"||!/\D/.test(a[2])&&"0n+"+a[2]||a[2]);a[2]=b[1]+(b[2]||1)-0,a[3]=b[3]-0}else a[2]&&m.error(a[0]);a[0]=e++;return a},ATTR:function(a,b,c,d,e,f){var g=a[1]=a[1].replace(j,"");!f&&o.attrMap[g]&&(a[1]=o.attrMap[g]),a[4]=(a[4]||a[5]||"").replace(j,""),a[2]==="~="&&(a[4]=" "+a[4]+" ");return a},PSEUDO:function(b,c,d,e,f){if(b[1]==="not")if((a.exec(b[3])||"").length>1||/^\w/.test(b[3]))b[3]=m(b[3],null,null,c);else{var g=m.filter(b[3],c,d,!0^f);d||e.push.apply(e,g);return!1}else if(o.match.POS.test(b[0])||o.match.CHILD.test(b[0]))return!0;return b},POS:function(a){a.unshift(!0);return a}},filters:{enabled:function(a){return a.disabled===!1&&a.type!=="hidden"},disabled:function(a){return a.disabled===!0},checked:function(a){return a.checked===!0},selected:function(a){a.parentNode&&a.parentNode.selectedIndex;return a.selected===!0},parent:function(a){return!!a.firstChild},empty:function(a){return!a.firstChild},has:function(a,b,c){return!!m(c[3],a).length},header:function(a){return/h\d/i.test(a.nodeName)},text:function(a){var b=a.getAttribute("type"),c=a.type;return a.nodeName.toLowerCase()==="input"&&"text"===c&&(b===c||b===null)},radio:function(a){return a.nodeName.toLowerCase()==="input"&&"radio"===a.type},checkbox:function(a){return a.nodeName.toLowerCase()==="input"&&"checkbox"===a.type},file:function(a){return a.nodeName.toLowerCase()==="input"&&"file"===a.type},password:function(a){return a.nodeName.toLowerCase()==="input"&&"password"===a.type},submit:function(a){var b=a.nodeName.toLowerCase();return(b==="input"||b==="button")&&"submit"===a.type},image:function(a){return a.nodeName.toLowerCase()==="input"&&"image"===a.type},reset:function(a){var b=a.nodeName.toLowerCase();return(b==="input"||b==="button")&&"reset"===a.type},button:function(a){var b=a.nodeName.toLowerCase();return b==="input"&&"button"===a.type||b==="button"},input:function(a){return/input|select|textarea|button/i.test(a.nodeName)},focus:function(a){return a===a.ownerDocument.activeElement}},setFilters:{first:function(a,b){return b===0},last:function(a,b,c,d){return b===d.length-1},even:function(a,b){return b%2===0},odd:function(a,b){return b%2===1},lt:function(a,b,c){return bc[3]-0},nth:function(a,b,c){return c[3]-0===b},eq:function(a,b,c){return c[3]-0===b}},filter:{PSEUDO:function(a,b,c,d){var e=b[1],f=o.filters[e];if(f)return f(a,c,b,d);if(e==="contains")return(a.textContent||a.innerText||n([a])||"").indexOf(b[3])>=0;if(e==="not"){var g=b[3];for(var h=0,i=g.length;h=0}},ID:function(a,b){return a.nodeType===1&&a.getAttribute("id")===b},TAG:function(a,b){return b==="*"&&a.nodeType===1||!!a.nodeName&&a.nodeName.toLowerCase()===b},CLASS:function(a,b){return(" "+(a.className||a.getAttribute("class"))+" ").indexOf(b)>-1},ATTR:function(a,b){var c=b[1],d=m.attr?m.attr(a,c):o.attrHandle[c]?o.attrHandle[c](a):a[c]!=null?a[c]:a.getAttribute(c),e=d+"",f=b[2],g=b[4];return d==null?f==="!=":!f&&m.attr?d!=null:f==="="?e===g:f==="*="?e.indexOf(g)>=0:f==="~="?(" "+e+" ").indexOf(g)>=0:g?f==="!="?e!==g:f==="^="?e.indexOf(g)===0:f==="$="?e.substr(e.length-g.length)===g:f==="|="?e===g||e.substr(0,g.length+1)===g+"-":!1:e&&d!==!1},POS:function(a,b,c,d){var e=b[2],f=o.setFilters[e];if(f)return f(a,c,b,d)}}},p=o.match.POS,q=function(a,b){return"\\"+(b-0+1)};for(var r in o.match)o.match[r]=new RegExp(o.match[r].source+/(?![^\[]*\])(?![^\(]*\))/.source),o.leftMatch[r]=new RegExp(/(^(?:.|\r|\n)*?)/.source+o.match[r].source.replace(/\\(\d+)/g,q));var s=function(a,b){a=Array.prototype.slice.call(a,0);if(b){b.push.apply(b,a);return b}return a};try{Array.prototype.slice.call(c.documentElement.childNodes,0)[0].nodeType}catch(t){s=function(a,b){var c=0,d=b||[];if(g.call(a)==="[object Array]")Array.prototype.push.apply(d,a);else if(typeof a.length=="number")for(var e=a.length;c",e.insertBefore(a,e.firstChild),c.getElementById(d)&&(o.find.ID=function(a,c,d){if(typeof c.getElementById!="undefined"&&!d){var e=c.getElementById(a[1]);return e?e.id===a[1]||typeof e.getAttributeNode!="undefined"&&e.getAttributeNode("id").nodeValue===a[1]?[e]:b:[]}},o.filter.ID=function(a,b){var c=typeof a.getAttributeNode!="undefined"&&a.getAttributeNode("id");return a.nodeType===1&&c&&c.nodeValue===b}),e.removeChild(a),e=a=null}(),function(){var a=c.createElement("div");a.appendChild(c.createComment("")),a.getElementsByTagName("*").length>0&&(o.find.TAG=function(a,b){var c=b.getElementsByTagName(a[1]);if(a[1]==="*"){var d=[];for(var e=0;c[e];e++)c[e].nodeType===1&&d.push(c[e]);c=d}return c}),a.innerHTML="",a.firstChild&&typeof a.firstChild.getAttribute!="undefined"&&a.firstChild.getAttribute("href")!=="#"&&(o.attrHandle.href=function(a){return a.getAttribute("href",2)}),a=null}(),c.querySelectorAll&&function(){var a=m,b=c.createElement("div"),d="__sizzle__";b.innerHTML="

";if(!b.querySelectorAll||b.querySelectorAll(".TEST").length!==0){m=function(b,e,f,g){e=e||c;if(!g&&!m.isXML(e)){var h=/^(\w+$)|^\.([\w\-]+$)|^#([\w\-]+$)/.exec(b);if(h&&(e.nodeType===1||e.nodeType===9)){if(h[1])return s(e.getElementsByTagName(b),f);if(h[2]&&o.find.CLASS&&e.getElementsByClassName)return s(e.getElementsByClassName(h[2]),f)}if(e.nodeType===9){if(b==="body"&&e.body)return s([e.body],f);if(h&&h[3]){var i=e.getElementById(h[3]);if(!i||!i.parentNode)return s([],f);if(i.id===h[3])return s([i],f)}try{return s(e.querySelectorAll(b),f)}catch(j){}}else if(e.nodeType===1&&e.nodeName.toLowerCase()!=="object"){var k=e,l=e.getAttribute("id"),n=l||d,p=e.parentNode,q=/^\s*[+~]/.test(b);l?n=n.replace(/'/g,"\\$&"):e.setAttribute("id",n),q&&p&&(e=e.parentNode);try{if(!q||p)return s(e.querySelectorAll("[id='"+n+"'] "+b),f)}catch(r){}finally{l||k.removeAttribute("id")}}}return a(b,e,f,g)};for(var e in a)m[e]=a[e];b=null}}(),function(){var a=c.documentElement,b=a.matchesSelector||a.mozMatchesSelector||a.webkitMatchesSelector||a.msMatchesSelector;if(b){var d=!b.call(c.createElement("div"),"div"),e=!1;try{b.call(c.documentElement,"[test!='']:sizzle")}catch(f){e=!0}m.matchesSelector=function(a,c){c=c.replace(/\=\s*([^'"\]]*)\s*\]/g,"='$1']");if(!m.isXML(a))try{if(e||!o.match.PSEUDO.test(c)&&!/!=/.test(c)){var f=b.call(a,c);if(f||!d||a.document&&a.document.nodeType!==11)return f}}catch(g){}return m(c,null,null,[a]).length>0}}}(),function(){var a=c.createElement("div");a.innerHTML="
";if(!!a.getElementsByClassName&&a.getElementsByClassName("e").length!==0){a.lastChild.className="e";if(a.getElementsByClassName("e").length===1)return;o.order.splice(1,0,"CLASS"),o.find.CLASS=function(a,b,c){if(typeof b.getElementsByClassName!="undefined"&&!c)return b.getElementsByClassName(a[1])},a=null}}(),c.documentElement.contains?m.contains=function(a,b){return a!==b&&(a.contains?a.contains(b):!0)}:c.documentElement.compareDocumentPosition?m.contains=function(a,b){return!!(a.compareDocumentPosition(b)&16)}:m.contains=function(){return!1},m.isXML=function(a){var b=(a?a.ownerDocument||a:0).documentElement;return b?b.nodeName!=="HTML":!1};var y=function(a,b,c){var d,e=[],f="",g=b.nodeType?[b]:b;while(d=o.match.PSEUDO.exec(a))f+=d[0],a=a.replace(o.match.PSEUDO,"");a=o.relative[a]?a+"*":a;for(var h=0,i=g.length;h0)for(h=g;h=0:f.filter(a,this).length>0:this.filter(a).length>0)},closest:function(a,b){var c=[],d,e,g=this[0];if(f.isArray(a)){var h=1;while(g&&g.ownerDocument&&g!==b){for(d=0;d-1:f.find.matchesSelector(g,a)){c.push(g);break}g=g.parentNode;if(!g||!g.ownerDocument||g===b||g.nodeType===11)break}}c=c.length>1?f.unique(c):c;return this.pushStack(c,"closest",a)},index:function(a){if(!a)return this[0]&&this[0].parentNode?this.prevAll().length:-1;if(typeof a=="string")return f.inArray(this[0],f(a));return f.inArray(a.jquery?a[0]:a,this)},add:function(a,b){var c=typeof a=="string"?f(a,b):f.makeArray(a&&a.nodeType?[a]:a),d=f.merge(this.get(),c);return this.pushStack(S(c[0])||S(d[0])?d:f.unique(d))},andSelf:function(){return this.add(this.prevObject)}}),f.each({parent:function(a){var b=a.parentNode;return b&&b.nodeType!==11?b:null},parents:function(a){return f.dir(a,"parentNode")},parentsUntil:function(a,b,c){return f.dir(a,"parentNode",c)},next:function(a){return f.nth(a,2,"nextSibling")},prev:function(a){return f.nth(a,2,"previousSibling")},nextAll:function(a){return f.dir(a,"nextSibling")},prevAll:function(a){return f.dir(a,"previousSibling")},nextUntil:function(a,b,c){return f.dir(a,"nextSibling",c)},prevUntil:function(a,b,c){return f.dir(a,"previousSibling",c)},siblings:function(a){return f.sibling(a.parentNode.firstChild,a)},children:function(a){return f.sibling(a.firstChild)},contents:function(a){return f.nodeName(a,"iframe")?a.contentDocument||a.contentWindow.document:f.makeArray(a.childNodes)}},function(a,b){f.fn[a]=function(c,d){var e=f.map(this,b,c);L.test(a)||(d=c),d&&typeof d=="string"&&(e=f.filter(d,e)),e=this.length>1&&!R[a]?f.unique(e):e,(this.length>1||N.test(d))&&M.test(a)&&(e=e.reverse());return this.pushStack(e,a,P.call(arguments).join(","))}}),f.extend({filter:function(a,b,c){c&&(a=":not("+a+")");return b.length===1?f.find.matchesSelector(b[0],a)?[b[0]]:[]:f.find.matches(a,b)},dir:function(a,c,d){var e=[],g=a[c];while(g&&g.nodeType!==9&&(d===b||g.nodeType!==1||!f(g).is(d)))g.nodeType===1&&e.push(g),g=g[c];return e},nth:function(a,b,c,d){b=b||1;var e=0;for(;a;a=a[c])if(a.nodeType===1&&++e===b)break;return a},sibling:function(a,b){var c=[];for(;a;a=a.nextSibling)a.nodeType===1&&a!==b&&c.push(a);return c}});var V="abbr|article|aside|audio|canvas|datalist|details|figcaption|figure|footer|header|hgroup|mark|meter|nav|output|progress|section|summary|time|video",W=/ jQuery\d+="(?:\d+|null)"/g,X=/^\s+/,Y=/<(?!area|br|col|embed|hr|img|input|link|meta|param)(([\w:]+)[^>]*)\/>/ig,Z=/<([\w:]+)/,$=/",""],legend:[1,"
","
"],thead:[1,"","
"],tr:[2,"","
"],td:[3,"","
"],col:[2,"","
"],area:[1,"",""],_default:[0,"",""]},bh=U(c);bg.optgroup=bg.option,bg.tbody=bg.tfoot=bg.colgroup=bg.caption=bg.thead,bg.th=bg.td,f.support.htmlSerialize||(bg._default=[1,"div
","
"]),f.fn.extend({text:function(a){if(f.isFunction(a))return this.each(function(b){var c=f(this);c.text(a.call(this,b,c.text()))});if(typeof a!="object"&&a!==b)return this.empty().append((this[0]&&this[0].ownerDocument||c).createTextNode(a));return f.text(this)},wrapAll:function(a){if(f.isFunction(a))return this.each(function(b){f(this).wrapAll(a.call(this,b))});if(this[0]){var b=f(a,this[0].ownerDocument).eq(0).clone(!0);this[0].parentNode&&b.insertBefore(this[0]),b.map(function(){var a=this;while(a.firstChild&&a.firstChild.nodeType===1)a=a.firstChild;return a}).append(this)}return this},wrapInner:function(a){if(f.isFunction(a))return this.each(function(b){f(this).wrapInner(a.call(this,b))});return this.each(function(){var b=f(this),c=b.contents();c.length?c.wrapAll(a):b.append(a)})},wrap:function(a){var b=f.isFunction(a);return this.each(function(c){f(this).wrapAll(b?a.call(this,c):a)})},unwrap:function(){return this.parent().each(function(){f.nodeName(this,"body")||f(this).replaceWith(this.childNodes)}).end()},append:function(){return this.domManip(arguments,!0,function(a){this.nodeType===1&&this.appendChild(a)})},prepend:function(){return this.domManip(arguments,!0,function(a){this.nodeType===1&&this.insertBefore(a,this.firstChild)})},before:function(){if(this[0]&&this[0].parentNode)return this.domManip(arguments,!1,function(a){this.parentNode.insertBefore(a,this)});if(arguments.length){var a=f.clean(arguments);a.push.apply(a,this.toArray());return this.pushStack(a,"before",arguments)}},after:function(){if(this[0]&&this[0].parentNode)return this.domManip(arguments,!1,function(a){this.parentNode.insertBefore(a,this.nextSibling)});if(arguments.length){var a=this.pushStack(this,"after",arguments);a.push.apply(a,f.clean(arguments));return a}},remove:function(a,b){for(var c=0,d;(d=this[c])!=null;c++)if(!a||f.filter(a,[d]).length)!b&&d.nodeType===1&&(f.cleanData(d.getElementsByTagName("*")),f.cleanData([d])),d.parentNode&&d.parentNode.removeChild(d);return this},empty:function() -{for(var a=0,b;(b=this[a])!=null;a++){b.nodeType===1&&f.cleanData(b.getElementsByTagName("*"));while(b.firstChild)b.removeChild(b.firstChild)}return this},clone:function(a,b){a=a==null?!1:a,b=b==null?a:b;return this.map(function(){return f.clone(this,a,b)})},html:function(a){if(a===b)return this[0]&&this[0].nodeType===1?this[0].innerHTML.replace(W,""):null;if(typeof a=="string"&&!ba.test(a)&&(f.support.leadingWhitespace||!X.test(a))&&!bg[(Z.exec(a)||["",""])[1].toLowerCase()]){a=a.replace(Y,"<$1>");try{for(var c=0,d=this.length;c1&&l0?this.clone(!0):this).get();f(e[h])[b](j),d=d.concat(j)}return this.pushStack(d,a,e.selector)}}),f.extend({clone:function(a,b,c){var d,e,g,h=f.support.html5Clone||!bc.test("<"+a.nodeName)?a.cloneNode(!0):bo(a);if((!f.support.noCloneEvent||!f.support.noCloneChecked)&&(a.nodeType===1||a.nodeType===11)&&!f.isXMLDoc(a)){bk(a,h),d=bl(a),e=bl(h);for(g=0;d[g];++g)e[g]&&bk(d[g],e[g])}if(b){bj(a,h);if(c){d=bl(a),e=bl(h);for(g=0;d[g];++g)bj(d[g],e[g])}}d=e=null;return h},clean:function(a,b,d,e){var g;b=b||c,typeof b.createElement=="undefined"&&(b=b.ownerDocument||b[0]&&b[0].ownerDocument||c);var h=[],i;for(var j=0,k;(k=a[j])!=null;j++){typeof k=="number"&&(k+="");if(!k)continue;if(typeof k=="string")if(!_.test(k))k=b.createTextNode(k);else{k=k.replace(Y,"<$1>");var l=(Z.exec(k)||["",""])[1].toLowerCase(),m=bg[l]||bg._default,n=m[0],o=b.createElement("div");b===c?bh.appendChild(o):U(b).appendChild(o),o.innerHTML=m[1]+k+m[2];while(n--)o=o.lastChild;if(!f.support.tbody){var p=$.test(k),q=l==="table"&&!p?o.firstChild&&o.firstChild.childNodes:m[1]===""&&!p?o.childNodes:[];for(i=q.length-1;i>=0;--i)f.nodeName(q[i],"tbody")&&!q[i].childNodes.length&&q[i].parentNode.removeChild(q[i])}!f.support.leadingWhitespace&&X.test(k)&&o.insertBefore(b.createTextNode(X.exec(k)[0]),o.firstChild),k=o.childNodes}var r;if(!f.support.appendChecked)if(k[0]&&typeof (r=k.length)=="number")for(i=0;i=0)return b+"px"}}}),f.support.opacity||(f.cssHooks.opacity={get:function(a,b){return br.test((b&&a.currentStyle?a.currentStyle.filter:a.style.filter)||"")?parseFloat(RegExp.$1)/100+"":b?"1":""},set:function(a,b){var c=a.style,d=a.currentStyle,e=f.isNumeric(b)?"alpha(opacity="+b*100+")":"",g=d&&d.filter||c.filter||"";c.zoom=1;if(b>=1&&f.trim(g.replace(bq,""))===""){c.removeAttribute("filter");if(d&&!d.filter)return}c.filter=bq.test(g)?g.replace(bq,e):g+" "+e}}),f(function(){f.support.reliableMarginRight||(f.cssHooks.marginRight={get:function(a,b){var c;f.swap(a,{display:"inline-block"},function(){b?c=bz(a,"margin-right","marginRight"):c=a.style.marginRight});return c}})}),c.defaultView&&c.defaultView.getComputedStyle&&(bA=function(a,b){var c,d,e;b=b.replace(bs,"-$1").toLowerCase(),(d=a.ownerDocument.defaultView)&&(e=d.getComputedStyle(a,null))&&(c=e.getPropertyValue(b),c===""&&!f.contains(a.ownerDocument.documentElement,a)&&(c=f.style(a,b)));return c}),c.documentElement.currentStyle&&(bB=function(a,b){var c,d,e,f=a.currentStyle&&a.currentStyle[b],g=a.style;f===null&&g&&(e=g[b])&&(f=e),!bt.test(f)&&bu.test(f)&&(c=g.left,d=a.runtimeStyle&&a.runtimeStyle.left,d&&(a.runtimeStyle.left=a.currentStyle.left),g.left=b==="fontSize"?"1em":f||0,f=g.pixelLeft+"px",g.left=c,d&&(a.runtimeStyle.left=d));return f===""?"auto":f}),bz=bA||bB,f.expr&&f.expr.filters&&(f.expr.filters.hidden=function(a){var b=a.offsetWidth,c=a.offsetHeight;return b===0&&c===0||!f.support.reliableHiddenOffsets&&(a.style&&a.style.display||f.css(a,"display"))==="none"},f.expr.filters.visible=function(a){return!f.expr.filters.hidden(a)});var bD=/%20/g,bE=/\[\]$/,bF=/\r?\n/g,bG=/#.*$/,bH=/^(.*?):[ \t]*([^\r\n]*)\r?$/mg,bI=/^(?:color|date|datetime|datetime-local|email|hidden|month|number|password|range|search|tel|text|time|url|week)$/i,bJ=/^(?:about|app|app\-storage|.+\-extension|file|res|widget):$/,bK=/^(?:GET|HEAD)$/,bL=/^\/\//,bM=/\?/,bN=/)<[^<]*)*<\/script>/gi,bO=/^(?:select|textarea)/i,bP=/\s+/,bQ=/([?&])_=[^&]*/,bR=/^([\w\+\.\-]+:)(?:\/\/([^\/?#:]*)(?::(\d+))?)?/,bS=f.fn.load,bT={},bU={},bV,bW,bX=["*/"]+["*"];try{bV=e.href}catch(bY){bV=c.createElement("a"),bV.href="",bV=bV.href}bW=bR.exec(bV.toLowerCase())||[],f.fn.extend({load:function(a,c,d){if(typeof a!="string"&&bS)return bS.apply(this,arguments);if(!this.length)return this;var e=a.indexOf(" ");if(e>=0){var g=a.slice(e,a.length);a=a.slice(0,e)}var h="GET";c&&(f.isFunction(c)?(d=c,c=b):typeof c=="object"&&(c=f.param(c,f.ajaxSettings.traditional),h="POST"));var i=this;f.ajax({url:a,type:h,dataType:"html",data:c,complete:function(a,b,c){c=a.responseText,a.isResolved()&&(a.done(function(a){c=a}),i.html(g?f("
").append(c.replace(bN,"")).find(g):c)),d&&i.each(d,[c,b,a])}});return this},serialize:function(){return f.param(this.serializeArray())},serializeArray:function(){return this.map(function(){return this.elements?f.makeArray(this.elements):this}).filter(function(){return this.name&&!this.disabled&&(this.checked||bO.test(this.nodeName)||bI.test(this.type))}).map(function(a,b){var c=f(this).val();return c==null?null:f.isArray(c)?f.map(c,function(a,c){return{name:b.name,value:a.replace(bF,"\r\n")}}):{name:b.name,value:c.replace(bF,"\r\n")}}).get()}}),f.each("ajaxStart ajaxStop ajaxComplete ajaxError ajaxSuccess ajaxSend".split(" "),function(a,b){f.fn[b]=function(a){return this.on(b,a)}}),f.each(["get","post"],function(a,c){f[c]=function(a,d,e,g){f.isFunction(d)&&(g=g||e,e=d,d=b);return f.ajax({type:c,url:a,data:d,success:e,dataType:g})}}),f.extend({getScript:function(a,c){return f.get(a,b,c,"script")},getJSON:function(a,b,c){return f.get(a,b,c,"json")},ajaxSetup:function(a,b){b?b_(a,f.ajaxSettings):(b=a,a=f.ajaxSettings),b_(a,b);return a},ajaxSettings:{url:bV,isLocal:bJ.test(bW[1]),global:!0,type:"GET",contentType:"application/x-www-form-urlencoded",processData:!0,async:!0,accepts:{xml:"application/xml, text/xml",html:"text/html",text:"text/plain",json:"application/json, text/javascript","*":bX},contents:{xml:/xml/,html:/html/,json:/json/},responseFields:{xml:"responseXML",text:"responseText"},converters:{"* text":a.String,"text html":!0,"text json":f.parseJSON,"text xml":f.parseXML},flatOptions:{context:!0,url:!0}},ajaxPrefilter:bZ(bT),ajaxTransport:bZ(bU),ajax:function(a,c){function w(a,c,l,m){if(s!==2){s=2,q&&clearTimeout(q),p=b,n=m||"",v.readyState=a>0?4:0;var o,r,u,w=c,x=l?cb(d,v,l):b,y,z;if(a>=200&&a<300||a===304){if(d.ifModified){if(y=v.getResponseHeader("Last-Modified"))f.lastModified[k]=y;if(z=v.getResponseHeader("Etag"))f.etag[k]=z}if(a===304)w="notmodified",o=!0;else try{r=cc(d,x),w="success",o=!0}catch(A){w="parsererror",u=A}}else{u=w;if(!w||a)w="error",a<0&&(a=0)}v.status=a,v.statusText=""+(c||w),o?h.resolveWith(e,[r,w,v]):h.rejectWith(e,[v,w,u]),v.statusCode(j),j=b,t&&g.trigger("ajax"+(o?"Success":"Error"),[v,d,o?r:u]),i.fireWith(e,[v,w]),t&&(g.trigger("ajaxComplete",[v,d]),--f.active||f.event.trigger("ajaxStop"))}}typeof a=="object"&&(c=a,a=b),c=c||{};var d=f.ajaxSetup({},c),e=d.context||d,g=e!==d&&(e.nodeType||e instanceof f)?f(e):f.event,h=f.Deferred(),i=f.Callbacks("once memory"),j=d.statusCode||{},k,l={},m={},n,o,p,q,r,s=0,t,u,v={readyState:0,setRequestHeader:function(a,b){if(!s){var c=a.toLowerCase();a=m[c]=m[c]||a,l[a]=b}return this},getAllResponseHeaders:function(){return s===2?n:null},getResponseHeader:function(a){var c;if(s===2){if(!o){o={};while(c=bH.exec(n))o[c[1].toLowerCase()]=c[2]}c=o[a.toLowerCase()]}return c===b?null:c},overrideMimeType:function(a){s||(d.mimeType=a);return this},abort:function(a){a=a||"abort",p&&p.abort(a),w(0,a);return this}};h.promise(v),v.success=v.done,v.error=v.fail,v.complete=i.add,v.statusCode=function(a){if(a){var b;if(s<2)for(b in a)j[b]=[j[b],a[b]];else b=a[v.status],v.then(b,b)}return this},d.url=((a||d.url)+"").replace(bG,"").replace(bL,bW[1]+"//"),d.dataTypes=f.trim(d.dataType||"*").toLowerCase().split(bP),d.crossDomain==null&&(r=bR.exec(d.url.toLowerCase()),d.crossDomain=!(!r||r[1]==bW[1]&&r[2]==bW[2]&&(r[3]||(r[1]==="http:"?80:443))==(bW[3]||(bW[1]==="http:"?80:443)))),d.data&&d.processData&&typeof d.data!="string"&&(d.data=f.param(d.data,d.traditional)),b$(bT,d,c,v);if(s===2)return!1;t=d.global,d.type=d.type.toUpperCase(),d.hasContent=!bK.test(d.type),t&&f.active++===0&&f.event.trigger("ajaxStart");if(!d.hasContent){d.data&&(d.url+=(bM.test(d.url)?"&":"?")+d.data,delete d.data),k=d.url;if(d.cache===!1){var x=f.now(),y=d.url.replace(bQ,"$1_="+x);d.url=y+(y===d.url?(bM.test(d.url)?"&":"?")+"_="+x:"")}}(d.data&&d.hasContent&&d.contentType!==!1||c.contentType)&&v.setRequestHeader("Content-Type",d.contentType),d.ifModified&&(k=k||d.url,f.lastModified[k]&&v.setRequestHeader("If-Modified-Since",f.lastModified[k]),f.etag[k]&&v.setRequestHeader("If-None-Match",f.etag[k])),v.setRequestHeader("Accept",d.dataTypes[0]&&d.accepts[d.dataTypes[0]]?d.accepts[d.dataTypes[0]]+(d.dataTypes[0]!=="*"?", "+bX+"; q=0.01":""):d.accepts["*"]);for(u in d.headers)v.setRequestHeader(u,d.headers[u]);if(d.beforeSend&&(d.beforeSend.call(e,v,d)===!1||s===2)){v.abort();return!1}for(u in{success:1,error:1,complete:1})v[u](d[u]);p=b$(bU,d,c,v);if(!p)w(-1,"No Transport");else{v.readyState=1,t&&g.trigger("ajaxSend",[v,d]),d.async&&d.timeout>0&&(q=setTimeout(function(){v.abort("timeout")},d.timeout));try{s=1,p.send(l,w)}catch(z){if(s<2)w(-1,z);else throw z}}return v},param:function(a,c){var d=[],e=function(a,b){b=f.isFunction(b)?b():b,d[d.length]=encodeURIComponent(a)+"="+encodeURIComponent(b)};c===b&&(c=f.ajaxSettings.traditional);if(f.isArray(a)||a.jquery&&!f.isPlainObject(a))f.each(a,function(){e(this.name,this.value)});else for(var g in a)ca(g,a[g],c,e);return d.join("&").replace(bD,"+")}}),f.extend({active:0,lastModified:{},etag:{}});var cd=f.now(),ce=/(\=)\?(&|$)|\?\?/i;f.ajaxSetup({jsonp:"callback",jsonpCallback:function(){return f.expando+"_"+cd++}}),f.ajaxPrefilter("json jsonp",function(b,c,d){var e=b.contentType==="application/x-www-form-urlencoded"&&typeof b.data=="string";if(b.dataTypes[0]==="jsonp"||b.jsonp!==!1&&(ce.test(b.url)||e&&ce.test(b.data))){var g,h=b.jsonpCallback=f.isFunction(b.jsonpCallback)?b.jsonpCallback():b.jsonpCallback,i=a[h],j=b.url,k=b.data,l="$1"+h+"$2";b.jsonp!==!1&&(j=j.replace(ce,l),b.url===j&&(e&&(k=k.replace(ce,l)),b.data===k&&(j+=(/\?/.test(j)?"&":"?")+b.jsonp+"="+h))),b.url=j,b.data=k,a[h]=function(a){g=[a]},d.always(function(){a[h]=i,g&&f.isFunction(i)&&a[h](g[0])}),b.converters["script json"]=function(){g||f.error(h+" was not called");return g[0]},b.dataTypes[0]="json";return"script"}}),f.ajaxSetup({accepts:{script:"text/javascript, application/javascript, application/ecmascript, application/x-ecmascript"},contents:{script:/javascript|ecmascript/},converters:{"text script":function(a){f.globalEval(a);return a}}}),f.ajaxPrefilter("script",function(a){a.cache===b&&(a.cache=!1),a.crossDomain&&(a.type="GET",a.global=!1)}),f.ajaxTransport("script",function(a){if(a.crossDomain){var d,e=c.head||c.getElementsByTagName("head")[0]||c.documentElement;return{send:function(f,g){d=c.createElement("script"),d.async="async",a.scriptCharset&&(d.charset=a.scriptCharset),d.src=a.url,d.onload=d.onreadystatechange=function(a,c){if(c||!d.readyState||/loaded|complete/.test(d.readyState))d.onload=d.onreadystatechange=null,e&&d.parentNode&&e.removeChild(d),d=b,c||g(200,"success")},e.insertBefore(d,e.firstChild)},abort:function(){d&&d.onload(0,1)}}}});var cf=a.ActiveXObject?function(){for(var a in ch)ch[a](0,1)}:!1,cg=0,ch;f.ajaxSettings.xhr=a.ActiveXObject?function(){return!this.isLocal&&ci()||cj()}:ci,function(a){f.extend(f.support,{ajax:!!a,cors:!!a&&"withCredentials"in a})}(f.ajaxSettings.xhr()),f.support.ajax&&f.ajaxTransport(function(c){if(!c.crossDomain||f.support.cors){var d;return{send:function(e,g){var h=c.xhr(),i,j;c.username?h.open(c.type,c.url,c.async,c.username,c.password):h.open(c.type,c.url,c.async);if(c.xhrFields)for(j in c.xhrFields)h[j]=c.xhrFields[j];c.mimeType&&h.overrideMimeType&&h.overrideMimeType(c.mimeType),!c.crossDomain&&!e["X-Requested-With"]&&(e["X-Requested-With"]="XMLHttpRequest");try{for(j in e)h.setRequestHeader(j,e[j])}catch(k){}h.send(c.hasContent&&c.data||null),d=function(a,e){var j,k,l,m,n;try{if(d&&(e||h.readyState===4)){d=b,i&&(h.onreadystatechange=f.noop,cf&&delete ch[i]);if(e)h.readyState!==4&&h.abort();else{j=h.status,l=h.getAllResponseHeaders(),m={},n=h.responseXML,n&&n.documentElement&&(m.xml=n),m.text=h.responseText;try{k=h.statusText}catch(o){k=""}!j&&c.isLocal&&!c.crossDomain?j=m.text?200:404:j===1223&&(j=204)}}}catch(p){e||g(-1,p)}m&&g(j,k,m,l)},!c.async||h.readyState===4?d():(i=++cg,cf&&(ch||(ch={},f(a).unload(cf)),ch[i]=d),h.onreadystatechange=d)},abort:function(){d&&d(0,1)}}}});var ck={},cl,cm,cn=/^(?:toggle|show|hide)$/,co=/^([+\-]=)?([\d+.\-]+)([a-z%]*)$/i,cp,cq=[["height","marginTop","marginBottom","paddingTop","paddingBottom"],["width","marginLeft","marginRight","paddingLeft","paddingRight"],["opacity"]],cr;f.fn.extend({show:function(a,b,c){var d,e;if(a||a===0)return this.animate(cu("show",3),a,b,c);for(var g=0,h=this.length;g=i.duration+this.startTime){this.now=this.end,this.pos=this.state=1,this.update(),i.animatedProperties[this.prop]=!0;for(b in i.animatedProperties)i.animatedProperties[b]!==!0&&(g=!1);if(g){i.overflow!=null&&!f.support.shrinkWrapBlocks&&f.each(["","X","Y"],function(a,b){h.style["overflow"+b]=i.overflow[a]}),i.hide&&f(h).hide();if(i.hide||i.show)for(b in i.animatedProperties)f.style(h,b,i.orig[b]),f.removeData(h,"fxshow"+b,!0),f.removeData(h,"toggle"+b,!0);d=i.complete,d&&(i.complete=!1,d.call(h))}return!1}i.duration==Infinity?this.now=e:(c=e-this.startTime,this.state=c/i.duration,this.pos=f.easing[i.animatedProperties[this.prop]](this.state,c,0,1,i.duration),this.now=this.start+(this.end-this.start)*this.pos),this.update();return!0}},f.extend(f.fx,{tick:function(){var a,b=f.timers,c=0;for(;c-1,k={},l={},m,n;j?(l=e.position(),m=l.top,n=l.left):(m=parseFloat(h)||0,n=parseFloat(i)||0),f.isFunction(b)&&(b=b.call(a,c,g)),b.top!=null&&(k.top=b.top-g.top+m),b.left!=null&&(k.left=b.left-g.left+n),"using"in b?b.using.call(a,k):e.css(k)}},f.fn.extend({position:function(){if(!this[0])return null;var a=this[0],b=this.offsetParent(),c=this.offset(),d=cx.test(b[0].nodeName)?{top:0,left:0}:b.offset();c.top-=parseFloat(f.css(a,"marginTop"))||0,c.left-=parseFloat(f.css(a,"marginLeft"))||0,d.top+=parseFloat(f.css(b[0],"borderTopWidth"))||0,d.left+=parseFloat(f.css(b[0],"borderLeftWidth"))||0;return{top:c.top-d.top,left:c.left-d.left}},offsetParent:function(){return this.map(function(){var a=this.offsetParent||c.body;while(a&&!cx.test(a.nodeName)&&f.css(a,"position")==="static")a=a.offsetParent;return a})}}),f.each(["Left","Top"],function(a,c){var d="scroll"+c;f.fn[d]=function(c){var e,g;if(c===b){e=this[0];if(!e)return null;g=cy(e);return g?"pageXOffset"in g?g[a?"pageYOffset":"pageXOffset"]:f.support.boxModel&&g.document.documentElement[d]||g.document.body[d]:e[d]}return this.each(function(){g=cy(this),g?g.scrollTo(a?f(g).scrollLeft():c,a?c:f(g).scrollTop()):this[d]=c})}}),f.each(["Height","Width"],function(a,c){var d=c.toLowerCase();f.fn["inner"+c]=function(){var a=this[0];return a?a.style?parseFloat(f.css(a,d,"padding")):this[d]():null},f.fn["outer"+c]=function(a){var b=this[0];return b?b.style?parseFloat(f.css(b,d,a?"margin":"border")):this[d]():null},f.fn[d]=function(a){var e=this[0];if(!e)return a==null?null:this;if(f.isFunction(a))return this.each(function(b){var c=f(this);c[d](a.call(this,b,c[d]()))});if(f.isWindow(e)){var g=e.document.documentElement["client"+c],h=e.document.body;return e.document.compatMode==="CSS1Compat"&&g||h&&h["client"+c]||g}if(e.nodeType===9)return Math.max(e.documentElement["client"+c],e.body["scroll"+c],e.documentElement["scroll"+c],e.body["offset"+c],e.documentElement["offset"+c]);if(a===b){var i=f.css(e,d),j=parseFloat(i);return f.isNumeric(j)?j:i}return this.css(d,typeof a=="string"?a:a+"px")}}),a.jQuery=a.$=f,typeof define=="function"&&define.amd&&define.amd.jQuery&&define("jquery",[],function(){return f})})(window); \ No newline at end of file diff --git a/test/lib/browser/mocha.css b/test/lib/browser/mocha.css deleted file mode 100755 index 01bac1abc18..00000000000 --- a/test/lib/browser/mocha.css +++ /dev/null @@ -1,199 +0,0 @@ -@charset "UTF-8"; -body { - font: 20px/1.5 "Helvetica Neue", Helvetica, Arial, sans-serif; - padding: 60px 50px; -} - -#mocha ul, #mocha li { - margin: 0; - padding: 0; -} - -#mocha ul { - list-style: none; -} - -#mocha h1, #mocha h2 { - margin: 0; -} - -#mocha h1 { - margin-top: 15px; - font-size: 1em; - font-weight: 200; -} - -#mocha h1 a { - text-decoration: none; - color: inherit; -} - -#mocha h1 a:hover { - text-decoration: underline; -} - -#mocha .suite .suite h1 { - margin-top: 0; - font-size: .8em; -} - -#mocha h2 { - font-size: 12px; - font-weight: normal; - cursor: pointer; -} - -#mocha .suite { - margin-left: 15px; -} - -#mocha .test { - margin-left: 15px; -} - -#mocha .test:hover h2::after { - position: relative; - top: 0; - right: -10px; - content: '(view source)'; - font-size: 12px; - font-family: arial; - color: #888; -} - -#mocha .test.pending:hover h2::after { - content: '(pending)'; - font-family: arial; -} - -#mocha .test.pass.medium .duration { - background: #C09853; -} - -#mocha .test.pass.slow .duration { - background: #B94A48; -} - -#mocha .test.pass::before { - content: '✓'; - font-size: 12px; - display: block; - float: left; - margin-right: 5px; - color: #00d6b2; -} - -#mocha .test.pass .duration { - font-size: 9px; - margin-left: 5px; - padding: 2px 5px; - color: white; - -webkit-box-shadow: inset 0 1px 1px rgba(0,0,0,.2); - -moz-box-shadow: inset 0 1px 1px rgba(0,0,0,.2); - box-shadow: inset 0 1px 1px rgba(0,0,0,.2); - -webkit-border-radius: 5px; - -moz-border-radius: 5px; - -ms-border-radius: 5px; - -o-border-radius: 5px; - border-radius: 5px; -} - -#mocha .test.pass.fast .duration { - display: none; -} - -#mocha .test.pending { - color: #0b97c4; -} - -#mocha .test.pending::before { - content: '◦'; - color: #0b97c4; -} - -#mocha .test.fail { - color: #c00; -} - -#mocha .test.fail pre { - color: black; -} - -#mocha .test.fail::before { - content: '✖'; - font-size: 12px; - display: block; - float: left; - margin-right: 5px; - color: #c00; -} - -#mocha .test pre.error { - color: #c00; -} - -#mocha .test pre { - display: inline-block; - font: 12px/1.5 monaco, monospace; - margin: 5px; - padding: 15px; - border: 1px solid #eee; - border-bottom-color: #ddd; - -webkit-border-radius: 3px; - -webkit-box-shadow: 0 1px 3px #eee; -} - -#report.pass .test.fail { - display: none; -} - -#report.fail .test.pass { - display: none; -} - -#error { - color: #c00; - font-size: 1.5 em; - font-weight: 100; - letter-spacing: 1px; -} - -#stats { - position: fixed; - top: 15px; - right: 10px; - font-size: 12px; - margin: 0; - color: #888; -} - -#stats .progress { - float: right; - padding-top: 0; -} - -#stats em { - color: black; -} - -#stats a { - text-decoration: none; - color: inherit; -} - -#stats a:hover { - border-bottom: 1px solid #eee; -} - -#stats li { - display: inline-block; - margin: 0 5px; - list-style: none; - padding-top: 11px; -} - -code .comment { color: #ddd } -code .init { color: #2F6FAD } -code .string { color: #5890AD } -code .keyword { color: #8A6343 } -code .number { color: #2F6FAD } diff --git a/test/lib/chai.js b/test/lib/chai.js deleted file mode 100644 index 7c9017149ee..00000000000 --- a/test/lib/chai.js +++ /dev/null @@ -1,5332 +0,0 @@ - -;(function(){ - -/** - * Require the module at `name`. - * - * @param {String} name - * @return {Object} exports - * @api public - */ - -function require(name) { - var module = require.modules[name]; - if (!module) throw new Error('failed to require "' + name + '"'); - - if (!('exports' in module) && typeof module.definition === 'function') { - module.client = module.component = true; - module.definition.call(this, module.exports = {}, module); - delete module.definition; - } - - return module.exports; -} - -/** - * Meta info, accessible in the global scope unless you use AMD option. - */ - -require.loader = 'component'; - -/** - * Internal helper object, contains a sorting function for semantiv versioning - */ -require.helper = {}; -require.helper.semVerSort = function(a, b) { - var aArray = a.version.split('.'); - var bArray = b.version.split('.'); - for (var i=0; i bLex ? 1 : -1; - continue; - } else if (aInt > bInt) { - return 1; - } else { - return -1; - } - } - return 0; -} - -/** - * Find and require a module which name starts with the provided name. - * If multiple modules exists, the highest semver is used. - * This function can only be used for remote dependencies. - - * @param {String} name - module name: `user~repo` - * @param {Boolean} returnPath - returns the canonical require path if true, - * otherwise it returns the epxorted module - */ -require.latest = function (name, returnPath) { - function showError(name) { - throw new Error('failed to find latest module of "' + name + '"'); - } - // only remotes with semvers, ignore local files conataining a '/' - var versionRegexp = /(.*)~(.*)@v?(\d+\.\d+\.\d+[^\/]*)$/; - var remoteRegexp = /(.*)~(.*)/; - if (!remoteRegexp.test(name)) showError(name); - var moduleNames = Object.keys(require.modules); - var semVerCandidates = []; - var otherCandidates = []; // for instance: name of the git branch - for (var i=0; i 0) { - var module = semVerCandidates.sort(require.helper.semVerSort).pop().name; - if (returnPath === true) { - return module; - } - return require(module); - } - // if the build contains more than one branch of the same module - // you should not use this funciton - var module = otherCandidates.sort(function(a, b) {return a.name > b.name})[0].name; - if (returnPath === true) { - return module; - } - return require(module); -} - -/** - * Registered modules. - */ - -require.modules = {}; - -/** - * Register module at `name` with callback `definition`. - * - * @param {String} name - * @param {Function} definition - * @api private - */ - -require.register = function (name, definition) { - require.modules[name] = { - definition: definition - }; -}; - -/** - * Define a module's exports immediately with `exports`. - * - * @param {String} name - * @param {Generic} exports - * @api private - */ - -require.define = function (name, exports) { - require.modules[name] = { - exports: exports - }; -}; -require.register("chaijs~assertion-error@1.0.0", function (exports, module) { -/*! - * assertion-error - * Copyright(c) 2013 Jake Luer - * MIT Licensed - */ - -/*! - * Return a function that will copy properties from - * one object to another excluding any originally - * listed. Returned function will create a new `{}`. - * - * @param {String} excluded properties ... - * @return {Function} - */ - -function exclude () { - var excludes = [].slice.call(arguments); - - function excludeProps (res, obj) { - Object.keys(obj).forEach(function (key) { - if (!~excludes.indexOf(key)) res[key] = obj[key]; - }); - } - - return function extendExclude () { - var args = [].slice.call(arguments) - , i = 0 - , res = {}; - - for (; i < args.length; i++) { - excludeProps(res, args[i]); - } - - return res; - }; -}; - -/*! - * Primary Exports - */ - -module.exports = AssertionError; - -/** - * ### AssertionError - * - * An extension of the JavaScript `Error` constructor for - * assertion and validation scenarios. - * - * @param {String} message - * @param {Object} properties to include (optional) - * @param {callee} start stack function (optional) - */ - -function AssertionError (message, _props, ssf) { - var extend = exclude('name', 'message', 'stack', 'constructor', 'toJSON') - , props = extend(_props || {}); - - // default values - this.message = message || 'Unspecified AssertionError'; - this.showDiff = false; - - // copy from properties - for (var key in props) { - this[key] = props[key]; - } - - // capture stack trace - ssf = ssf || arguments.callee; - if (ssf && Error.captureStackTrace) { - Error.captureStackTrace(this, ssf); - } -} - -/*! - * Inherit from Error.prototype - */ - -AssertionError.prototype = Object.create(Error.prototype); - -/*! - * Statically set name - */ - -AssertionError.prototype.name = 'AssertionError'; - -/*! - * Ensure correct constructor - */ - -AssertionError.prototype.constructor = AssertionError; - -/** - * Allow errors to be converted to JSON for static transfer. - * - * @param {Boolean} include stack (default: `true`) - * @return {Object} object that can be `JSON.stringify` - */ - -AssertionError.prototype.toJSON = function (stack) { - var extend = exclude('constructor', 'toJSON', 'stack') - , props = extend({ name: this.name }, this); - - // include stack if exists and not turned off - if (false !== stack && this.stack) { - props.stack = this.stack; - } - - return props; -}; - -}); - -require.register("chaijs~type-detect@0.1.1", function (exports, module) { -/*! - * type-detect - * Copyright(c) 2013 jake luer - * MIT Licensed - */ - -/*! - * Primary Exports - */ - -var exports = module.exports = getType; - -/*! - * Detectable javascript natives - */ - -var natives = { - '[object Array]': 'array' - , '[object RegExp]': 'regexp' - , '[object Function]': 'function' - , '[object Arguments]': 'arguments' - , '[object Date]': 'date' -}; - -/** - * ### typeOf (obj) - * - * Use several different techniques to determine - * the type of object being tested. - * - * - * @param {Mixed} object - * @return {String} object type - * @api public - */ - -function getType (obj) { - var str = Object.prototype.toString.call(obj); - if (natives[str]) return natives[str]; - if (obj === null) return 'null'; - if (obj === undefined) return 'undefined'; - if (obj === Object(obj)) return 'object'; - return typeof obj; -} - -exports.Library = Library; - -/** - * ### Library - * - * Create a repository for custom type detection. - * - * ```js - * var lib = new type.Library; - * ``` - * - */ - -function Library () { - this.tests = {}; -} - -/** - * #### .of (obj) - * - * Expose replacement `typeof` detection to the library. - * - * ```js - * if ('string' === lib.of('hello world')) { - * // ... - * } - * ``` - * - * @param {Mixed} object to test - * @return {String} type - */ - -Library.prototype.of = getType; - -/** - * #### .define (type, test) - * - * Add a test to for the `.test()` assertion. - * - * Can be defined as a regular expression: - * - * ```js - * lib.define('int', /^[0-9]+$/); - * ``` - * - * ... or as a function: - * - * ```js - * lib.define('bln', function (obj) { - * if ('boolean' === lib.of(obj)) return true; - * var blns = [ 'yes', 'no', 'true', 'false', 1, 0 ]; - * if ('string' === lib.of(obj)) obj = obj.toLowerCase(); - * return !! ~blns.indexOf(obj); - * }); - * ``` - * - * @param {String} type - * @param {RegExp|Function} test - * @api public - */ - -Library.prototype.define = function (type, test) { - if (arguments.length === 1) return this.tests[type]; - this.tests[type] = test; - return this; -}; - -/** - * #### .test (obj, test) - * - * Assert that an object is of type. Will first - * check natives, and if that does not pass it will - * use the user defined custom tests. - * - * ```js - * assert(lib.test('1', 'int')); - * assert(lib.test('yes', 'bln')); - * ``` - * - * @param {Mixed} object - * @param {String} type - * @return {Boolean} result - * @api public - */ - -Library.prototype.test = function (obj, type) { - if (type === getType(obj)) return true; - var test = this.tests[type]; - - if (test && 'regexp' === getType(test)) { - return test.test(obj); - } else if (test && 'function' === getType(test)) { - return test(obj); - } else { - throw new ReferenceError('Type test "' + type + '" not defined or invalid.'); - } -}; - -}); - -require.register("chaijs~deep-eql@0.1.3", function (exports, module) { -/*! - * deep-eql - * Copyright(c) 2013 Jake Luer - * MIT Licensed - */ - -/*! - * Module dependencies - */ - -var type = require('chaijs~type-detect@0.1.1'); - -/*! - * Buffer.isBuffer browser shim - */ - -var Buffer; -try { Buffer = require('buffer').Buffer; } -catch(ex) { - Buffer = {}; - Buffer.isBuffer = function() { return false; } -} - -/*! - * Primary Export - */ - -module.exports = deepEqual; - -/** - * Assert super-strict (egal) equality between - * two objects of any type. - * - * @param {Mixed} a - * @param {Mixed} b - * @param {Array} memoised (optional) - * @return {Boolean} equal match - */ - -function deepEqual(a, b, m) { - if (sameValue(a, b)) { - return true; - } else if ('date' === type(a)) { - return dateEqual(a, b); - } else if ('regexp' === type(a)) { - return regexpEqual(a, b); - } else if (Buffer.isBuffer(a)) { - return bufferEqual(a, b); - } else if ('arguments' === type(a)) { - return argumentsEqual(a, b, m); - } else if (!typeEqual(a, b)) { - return false; - } else if (('object' !== type(a) && 'object' !== type(b)) - && ('array' !== type(a) && 'array' !== type(b))) { - return sameValue(a, b); - } else { - return objectEqual(a, b, m); - } -} - -/*! - * Strict (egal) equality test. Ensures that NaN always - * equals NaN and `-0` does not equal `+0`. - * - * @param {Mixed} a - * @param {Mixed} b - * @return {Boolean} equal match - */ - -function sameValue(a, b) { - if (a === b) return a !== 0 || 1 / a === 1 / b; - return a !== a && b !== b; -} - -/*! - * Compare the types of two given objects and - * return if they are equal. Note that an Array - * has a type of `array` (not `object`) and arguments - * have a type of `arguments` (not `array`/`object`). - * - * @param {Mixed} a - * @param {Mixed} b - * @return {Boolean} result - */ - -function typeEqual(a, b) { - return type(a) === type(b); -} - -/*! - * Compare two Date objects by asserting that - * the time values are equal using `saveValue`. - * - * @param {Date} a - * @param {Date} b - * @return {Boolean} result - */ - -function dateEqual(a, b) { - if ('date' !== type(b)) return false; - return sameValue(a.getTime(), b.getTime()); -} - -/*! - * Compare two regular expressions by converting them - * to string and checking for `sameValue`. - * - * @param {RegExp} a - * @param {RegExp} b - * @return {Boolean} result - */ - -function regexpEqual(a, b) { - if ('regexp' !== type(b)) return false; - return sameValue(a.toString(), b.toString()); -} - -/*! - * Assert deep equality of two `arguments` objects. - * Unfortunately, these must be sliced to arrays - * prior to test to ensure no bad behavior. - * - * @param {Arguments} a - * @param {Arguments} b - * @param {Array} memoize (optional) - * @return {Boolean} result - */ - -function argumentsEqual(a, b, m) { - if ('arguments' !== type(b)) return false; - a = [].slice.call(a); - b = [].slice.call(b); - return deepEqual(a, b, m); -} - -/*! - * Get enumerable properties of a given object. - * - * @param {Object} a - * @return {Array} property names - */ - -function enumerable(a) { - var res = []; - for (var key in a) res.push(key); - return res; -} - -/*! - * Simple equality for flat iterable objects - * such as Arrays or Node.js buffers. - * - * @param {Iterable} a - * @param {Iterable} b - * @return {Boolean} result - */ - -function iterableEqual(a, b) { - if (a.length !== b.length) return false; - - var i = 0; - var match = true; - - for (; i < a.length; i++) { - if (a[i] !== b[i]) { - match = false; - break; - } - } - - return match; -} - -/*! - * Extension to `iterableEqual` specifically - * for Node.js Buffers. - * - * @param {Buffer} a - * @param {Mixed} b - * @return {Boolean} result - */ - -function bufferEqual(a, b) { - if (!Buffer.isBuffer(b)) return false; - return iterableEqual(a, b); -} - -/*! - * Block for `objectEqual` ensuring non-existing - * values don't get in. - * - * @param {Mixed} object - * @return {Boolean} result - */ - -function isValue(a) { - return a !== null && a !== undefined; -} - -/*! - * Recursively check the equality of two objects. - * Once basic sameness has been established it will - * defer to `deepEqual` for each enumerable key - * in the object. - * - * @param {Mixed} a - * @param {Mixed} b - * @return {Boolean} result - */ - -function objectEqual(a, b, m) { - if (!isValue(a) || !isValue(b)) { - return false; - } - - if (a.prototype !== b.prototype) { - return false; - } - - var i; - if (m) { - for (i = 0; i < m.length; i++) { - if ((m[i][0] === a && m[i][1] === b) - || (m[i][0] === b && m[i][1] === a)) { - return true; - } - } - } else { - m = []; - } - - try { - var ka = enumerable(a); - var kb = enumerable(b); - } catch (ex) { - return false; - } - - ka.sort(); - kb.sort(); - - if (!iterableEqual(ka, kb)) { - return false; - } - - m.push([ a, b ]); - - var key; - for (i = ka.length - 1; i >= 0; i--) { - key = ka[i]; - if (!deepEqual(a[key], b[key], m)) { - return false; - } - } - - return true; -} - -}); - -require.register("chai", function (exports, module) { -module.exports = require('chai/lib/chai.js'); - -}); - -require.register("chai/lib/chai.js", function (exports, module) { -/*! - * chai - * Copyright(c) 2011-2014 Jake Luer - * MIT Licensed - */ - -var used = [] - , exports = module.exports = {}; - -/*! - * Chai version - */ - -exports.version = '2.1.0'; - -/*! - * Assertion Error - */ - -exports.AssertionError = require('chaijs~assertion-error@1.0.0'); - -/*! - * Utils for plugins (not exported) - */ - -var util = require('chai/lib/chai/utils/index.js'); - -/** - * # .use(function) - * - * Provides a way to extend the internals of Chai - * - * @param {Function} - * @returns {this} for chaining - * @api public - */ - -exports.use = function (fn) { - if (!~used.indexOf(fn)) { - fn(this, util); - used.push(fn); - } - - return this; -}; - -/*! - * Utility Functions - */ - -exports.util = util; - -/*! - * Configuration - */ - -var config = require('chai/lib/chai/config.js'); -exports.config = config; - -/*! - * Primary `Assertion` prototype - */ - -var assertion = require('chai/lib/chai/assertion.js'); -exports.use(assertion); - -/*! - * Core Assertions - */ - -var core = require('chai/lib/chai/core/assertions.js'); -exports.use(core); - -/*! - * Expect interface - */ - -var expect = require('chai/lib/chai/interface/expect.js'); -exports.use(expect); - -/*! - * Should interface - */ - -var should = require('chai/lib/chai/interface/should.js'); -exports.use(should); - -/*! - * Assert interface - */ - -var assert = require('chai/lib/chai/interface/assert.js'); -exports.use(assert); - -}); - -require.register("chai/lib/chai/assertion.js", function (exports, module) { -/*! - * chai - * http://chaijs.com - * Copyright(c) 2011-2014 Jake Luer - * MIT Licensed - */ - -var config = require('chai/lib/chai/config.js'); - -module.exports = function (_chai, util) { - /*! - * Module dependencies. - */ - - var AssertionError = _chai.AssertionError - , flag = util.flag; - - /*! - * Module export. - */ - - _chai.Assertion = Assertion; - - /*! - * Assertion Constructor - * - * Creates object for chaining. - * - * @api private - */ - - function Assertion (obj, msg, stack) { - flag(this, 'ssfi', stack || arguments.callee); - flag(this, 'object', obj); - flag(this, 'message', msg); - } - - Object.defineProperty(Assertion, 'includeStack', { - get: function() { - console.warn('Assertion.includeStack is deprecated, use chai.config.includeStack instead.'); - return config.includeStack; - }, - set: function(value) { - console.warn('Assertion.includeStack is deprecated, use chai.config.includeStack instead.'); - config.includeStack = value; - } - }); - - Object.defineProperty(Assertion, 'showDiff', { - get: function() { - console.warn('Assertion.showDiff is deprecated, use chai.config.showDiff instead.'); - return config.showDiff; - }, - set: function(value) { - console.warn('Assertion.showDiff is deprecated, use chai.config.showDiff instead.'); - config.showDiff = value; - } - }); - - Assertion.addProperty = function (name, fn) { - util.addProperty(this.prototype, name, fn); - }; - - Assertion.addMethod = function (name, fn) { - util.addMethod(this.prototype, name, fn); - }; - - Assertion.addChainableMethod = function (name, fn, chainingBehavior) { - util.addChainableMethod(this.prototype, name, fn, chainingBehavior); - }; - - Assertion.overwriteProperty = function (name, fn) { - util.overwriteProperty(this.prototype, name, fn); - }; - - Assertion.overwriteMethod = function (name, fn) { - util.overwriteMethod(this.prototype, name, fn); - }; - - Assertion.overwriteChainableMethod = function (name, fn, chainingBehavior) { - util.overwriteChainableMethod(this.prototype, name, fn, chainingBehavior); - }; - - /*! - * ### .assert(expression, message, negateMessage, expected, actual) - * - * Executes an expression and check expectations. Throws AssertionError for reporting if test doesn't pass. - * - * @name assert - * @param {Philosophical} expression to be tested - * @param {String or Function} message or function that returns message to display if fails - * @param {String or Function} negatedMessage or function that returns negatedMessage to display if negated expression fails - * @param {Mixed} expected value (remember to check for negation) - * @param {Mixed} actual (optional) will default to `this.obj` - * @api private - */ - - Assertion.prototype.assert = function (expr, msg, negateMsg, expected, _actual, showDiff) { - var ok = util.test(this, arguments); - if (true !== showDiff) showDiff = false; - if (true !== config.showDiff) showDiff = false; - - if (!ok) { - var msg = util.getMessage(this, arguments) - , actual = util.getActual(this, arguments); - throw new AssertionError(msg, { - actual: actual - , expected: expected - , showDiff: showDiff - }, (config.includeStack) ? this.assert : flag(this, 'ssfi')); - } - }; - - /*! - * ### ._obj - * - * Quick reference to stored `actual` value for plugin developers. - * - * @api private - */ - - Object.defineProperty(Assertion.prototype, '_obj', - { get: function () { - return flag(this, 'object'); - } - , set: function (val) { - flag(this, 'object', val); - } - }); -}; - -}); - -require.register("chai/lib/chai/config.js", function (exports, module) { -module.exports = { - - /** - * ### config.includeStack - * - * User configurable property, influences whether stack trace - * is included in Assertion error message. Default of false - * suppresses stack trace in the error message. - * - * chai.config.includeStack = true; // enable stack on error - * - * @param {Boolean} - * @api public - */ - - includeStack: false, - - /** - * ### config.showDiff - * - * User configurable property, influences whether or not - * the `showDiff` flag should be included in the thrown - * AssertionErrors. `false` will always be `false`; `true` - * will be true when the assertion has requested a diff - * be shown. - * - * @param {Boolean} - * @api public - */ - - showDiff: true, - - /** - * ### config.truncateThreshold - * - * User configurable property, sets length threshold for actual and - * expected values in assertion errors. If this threshold is exceeded, - * the value is truncated. - * - * Set it to zero if you want to disable truncating altogether. - * - * chai.config.truncateThreshold = 0; // disable truncating - * - * @param {Number} - * @api public - */ - - truncateThreshold: 40 - -}; - -}); - -require.register("chai/lib/chai/core/assertions.js", function (exports, module) { -/*! - * chai - * http://chaijs.com - * Copyright(c) 2011-2014 Jake Luer - * MIT Licensed - */ - -module.exports = function (chai, _) { - var Assertion = chai.Assertion - , toString = Object.prototype.toString - , flag = _.flag; - - /** - * ### Language Chains - * - * The following are provided as chainable getters to - * improve the readability of your assertions. They - * do not provide testing capabilities unless they - * have been overwritten by a plugin. - * - * **Chains** - * - * - to - * - be - * - been - * - is - * - that - * - which - * - and - * - has - * - have - * - with - * - at - * - of - * - same - * - * @name language chains - * @api public - */ - - [ 'to', 'be', 'been' - , 'is', 'and', 'has', 'have' - , 'with', 'that', 'which', 'at' - , 'of', 'same' ].forEach(function (chain) { - Assertion.addProperty(chain, function () { - return this; - }); - }); - - /** - * ### .not - * - * Negates any of assertions following in the chain. - * - * expect(foo).to.not.equal('bar'); - * expect(goodFn).to.not.throw(Error); - * expect({ foo: 'baz' }).to.have.property('foo') - * .and.not.equal('bar'); - * - * @name not - * @api public - */ - - Assertion.addProperty('not', function () { - flag(this, 'negate', true); - }); - - /** - * ### .deep - * - * Sets the `deep` flag, later used by the `equal` and - * `property` assertions. - * - * expect(foo).to.deep.equal({ bar: 'baz' }); - * expect({ foo: { bar: { baz: 'quux' } } }) - * .to.have.deep.property('foo.bar.baz', 'quux'); - * - * @name deep - * @api public - */ - - Assertion.addProperty('deep', function () { - flag(this, 'deep', true); - }); - - /** - * ### .any - * - * Sets the `any` flag, (opposite of the `all` flag) - * later used in the `keys` assertion. - * - * expect(foo).to.have.any.keys('bar', 'baz'); - * - * @name any - * @api public - */ - - Assertion.addProperty('any', function () { - flag(this, 'any', true); - flag(this, 'all', false) - }); - - - /** - * ### .all - * - * Sets the `all` flag (opposite of the `any` flag) - * later used by the `keys` assertion. - * - * expect(foo).to.have.all.keys('bar', 'baz'); - * - * @name all - * @api public - */ - - Assertion.addProperty('all', function () { - flag(this, 'all', true); - flag(this, 'any', false); - }); - - /** - * ### .a(type) - * - * The `a` and `an` assertions are aliases that can be - * used either as language chains or to assert a value's - * type. - * - * // typeof - * expect('test').to.be.a('string'); - * expect({ foo: 'bar' }).to.be.an('object'); - * expect(null).to.be.a('null'); - * expect(undefined).to.be.an('undefined'); - * - * // language chain - * expect(foo).to.be.an.instanceof(Foo); - * - * @name a - * @alias an - * @param {String} type - * @param {String} message _optional_ - * @api public - */ - - function an (type, msg) { - if (msg) flag(this, 'message', msg); - type = type.toLowerCase(); - var obj = flag(this, 'object') - , article = ~[ 'a', 'e', 'i', 'o', 'u' ].indexOf(type.charAt(0)) ? 'an ' : 'a '; - - this.assert( - type === _.type(obj) - , 'expected #{this} to be ' + article + type - , 'expected #{this} not to be ' + article + type - ); - } - - Assertion.addChainableMethod('an', an); - Assertion.addChainableMethod('a', an); - - /** - * ### .include(value) - * - * The `include` and `contain` assertions can be used as either property - * based language chains or as methods to assert the inclusion of an object - * in an array or a substring in a string. When used as language chains, - * they toggle the `contains` flag for the `keys` assertion. - * - * expect([1,2,3]).to.include(2); - * expect('foobar').to.contain('foo'); - * expect({ foo: 'bar', hello: 'universe' }).to.include.keys('foo'); - * - * @name include - * @alias contain - * @alias includes - * @alias contains - * @param {Object|String|Number} obj - * @param {String} message _optional_ - * @api public - */ - - function includeChainingBehavior () { - flag(this, 'contains', true); - } - - function include (val, msg) { - if (msg) flag(this, 'message', msg); - var obj = flag(this, 'object'); - var expected = false; - if (_.type(obj) === 'array' && _.type(val) === 'object') { - for (var i in obj) { - if (_.eql(obj[i], val)) { - expected = true; - break; - } - } - } else if (_.type(val) === 'object') { - if (!flag(this, 'negate')) { - for (var k in val) new Assertion(obj).property(k, val[k]); - return; - } - var subset = {}; - for (var k in val) subset[k] = obj[k]; - expected = _.eql(subset, val); - } else { - expected = obj && ~obj.indexOf(val); - } - this.assert( - expected - , 'expected #{this} to include ' + _.inspect(val) - , 'expected #{this} to not include ' + _.inspect(val)); - } - - Assertion.addChainableMethod('include', include, includeChainingBehavior); - Assertion.addChainableMethod('contain', include, includeChainingBehavior); - Assertion.addChainableMethod('contains', include, includeChainingBehavior); - Assertion.addChainableMethod('includes', include, includeChainingBehavior); - - /** - * ### .ok - * - * Asserts that the target is truthy. - * - * expect('everthing').to.be.ok; - * expect(1).to.be.ok; - * expect(false).to.not.be.ok; - * expect(undefined).to.not.be.ok; - * expect(null).to.not.be.ok; - * - * @name ok - * @api public - */ - - Assertion.addProperty('ok', function () { - this.assert( - flag(this, 'object') - , 'expected #{this} to be truthy' - , 'expected #{this} to be falsy'); - }); - - /** - * ### .true - * - * Asserts that the target is `true`. - * - * expect(true).to.be.true; - * expect(1).to.not.be.true; - * - * @name true - * @api public - */ - - Assertion.addProperty('true', function () { - this.assert( - true === flag(this, 'object') - , 'expected #{this} to be true' - , 'expected #{this} to be false' - , this.negate ? false : true - ); - }); - - /** - * ### .false - * - * Asserts that the target is `false`. - * - * expect(false).to.be.false; - * expect(0).to.not.be.false; - * - * @name false - * @api public - */ - - Assertion.addProperty('false', function () { - this.assert( - false === flag(this, 'object') - , 'expected #{this} to be false' - , 'expected #{this} to be true' - , this.negate ? true : false - ); - }); - - /** - * ### .null - * - * Asserts that the target is `null`. - * - * expect(null).to.be.null; - * expect(undefined).not.to.be.null; - * - * @name null - * @api public - */ - - Assertion.addProperty('null', function () { - this.assert( - null === flag(this, 'object') - , 'expected #{this} to be null' - , 'expected #{this} not to be null' - ); - }); - - /** - * ### .undefined - * - * Asserts that the target is `undefined`. - * - * expect(undefined).to.be.undefined; - * expect(null).to.not.be.undefined; - * - * @name undefined - * @api public - */ - - Assertion.addProperty('undefined', function () { - this.assert( - undefined === flag(this, 'object') - , 'expected #{this} to be undefined' - , 'expected #{this} not to be undefined' - ); - }); - - /** - * ### .exist - * - * Asserts that the target is neither `null` nor `undefined`. - * - * var foo = 'hi' - * , bar = null - * , baz; - * - * expect(foo).to.exist; - * expect(bar).to.not.exist; - * expect(baz).to.not.exist; - * - * @name exist - * @api public - */ - - Assertion.addProperty('exist', function () { - this.assert( - null != flag(this, 'object') - , 'expected #{this} to exist' - , 'expected #{this} to not exist' - ); - }); - - - /** - * ### .empty - * - * Asserts that the target's length is `0`. For arrays, it checks - * the `length` property. For objects, it gets the count of - * enumerable keys. - * - * expect([]).to.be.empty; - * expect('').to.be.empty; - * expect({}).to.be.empty; - * - * @name empty - * @api public - */ - - Assertion.addProperty('empty', function () { - var obj = flag(this, 'object') - , expected = obj; - - if (Array.isArray(obj) || 'string' === typeof object) { - expected = obj.length; - } else if (typeof obj === 'object') { - expected = Object.keys(obj).length; - } - - this.assert( - !expected - , 'expected #{this} to be empty' - , 'expected #{this} not to be empty' - ); - }); - - /** - * ### .arguments - * - * Asserts that the target is an arguments object. - * - * function test () { - * expect(arguments).to.be.arguments; - * } - * - * @name arguments - * @alias Arguments - * @api public - */ - - function checkArguments () { - var obj = flag(this, 'object') - , type = Object.prototype.toString.call(obj); - this.assert( - '[object Arguments]' === type - , 'expected #{this} to be arguments but got ' + type - , 'expected #{this} to not be arguments' - ); - } - - Assertion.addProperty('arguments', checkArguments); - Assertion.addProperty('Arguments', checkArguments); - - /** - * ### .equal(value) - * - * Asserts that the target is strictly equal (`===`) to `value`. - * Alternately, if the `deep` flag is set, asserts that - * the target is deeply equal to `value`. - * - * expect('hello').to.equal('hello'); - * expect(42).to.equal(42); - * expect(1).to.not.equal(true); - * expect({ foo: 'bar' }).to.not.equal({ foo: 'bar' }); - * expect({ foo: 'bar' }).to.deep.equal({ foo: 'bar' }); - * - * @name equal - * @alias equals - * @alias eq - * @alias deep.equal - * @param {Mixed} value - * @param {String} message _optional_ - * @api public - */ - - function assertEqual (val, msg) { - if (msg) flag(this, 'message', msg); - var obj = flag(this, 'object'); - if (flag(this, 'deep')) { - return this.eql(val); - } else { - this.assert( - val === obj - , 'expected #{this} to equal #{exp}' - , 'expected #{this} to not equal #{exp}' - , val - , this._obj - , true - ); - } - } - - Assertion.addMethod('equal', assertEqual); - Assertion.addMethod('equals', assertEqual); - Assertion.addMethod('eq', assertEqual); - - /** - * ### .eql(value) - * - * Asserts that the target is deeply equal to `value`. - * - * expect({ foo: 'bar' }).to.eql({ foo: 'bar' }); - * expect([ 1, 2, 3 ]).to.eql([ 1, 2, 3 ]); - * - * @name eql - * @alias eqls - * @param {Mixed} value - * @param {String} message _optional_ - * @api public - */ - - function assertEql(obj, msg) { - if (msg) flag(this, 'message', msg); - this.assert( - _.eql(obj, flag(this, 'object')) - , 'expected #{this} to deeply equal #{exp}' - , 'expected #{this} to not deeply equal #{exp}' - , obj - , this._obj - , true - ); - } - - Assertion.addMethod('eql', assertEql); - Assertion.addMethod('eqls', assertEql); - - /** - * ### .above(value) - * - * Asserts that the target is greater than `value`. - * - * expect(10).to.be.above(5); - * - * Can also be used in conjunction with `length` to - * assert a minimum length. The benefit being a - * more informative error message than if the length - * was supplied directly. - * - * expect('foo').to.have.length.above(2); - * expect([ 1, 2, 3 ]).to.have.length.above(2); - * - * @name above - * @alias gt - * @alias greaterThan - * @param {Number} value - * @param {String} message _optional_ - * @api public - */ - - function assertAbove (n, msg) { - if (msg) flag(this, 'message', msg); - var obj = flag(this, 'object'); - if (flag(this, 'doLength')) { - new Assertion(obj, msg).to.have.property('length'); - var len = obj.length; - this.assert( - len > n - , 'expected #{this} to have a length above #{exp} but got #{act}' - , 'expected #{this} to not have a length above #{exp}' - , n - , len - ); - } else { - this.assert( - obj > n - , 'expected #{this} to be above ' + n - , 'expected #{this} to be at most ' + n - ); - } - } - - Assertion.addMethod('above', assertAbove); - Assertion.addMethod('gt', assertAbove); - Assertion.addMethod('greaterThan', assertAbove); - - /** - * ### .least(value) - * - * Asserts that the target is greater than or equal to `value`. - * - * expect(10).to.be.at.least(10); - * - * Can also be used in conjunction with `length` to - * assert a minimum length. The benefit being a - * more informative error message than if the length - * was supplied directly. - * - * expect('foo').to.have.length.of.at.least(2); - * expect([ 1, 2, 3 ]).to.have.length.of.at.least(3); - * - * @name least - * @alias gte - * @param {Number} value - * @param {String} message _optional_ - * @api public - */ - - function assertLeast (n, msg) { - if (msg) flag(this, 'message', msg); - var obj = flag(this, 'object'); - if (flag(this, 'doLength')) { - new Assertion(obj, msg).to.have.property('length'); - var len = obj.length; - this.assert( - len >= n - , 'expected #{this} to have a length at least #{exp} but got #{act}' - , 'expected #{this} to have a length below #{exp}' - , n - , len - ); - } else { - this.assert( - obj >= n - , 'expected #{this} to be at least ' + n - , 'expected #{this} to be below ' + n - ); - } - } - - Assertion.addMethod('least', assertLeast); - Assertion.addMethod('gte', assertLeast); - - /** - * ### .below(value) - * - * Asserts that the target is less than `value`. - * - * expect(5).to.be.below(10); - * - * Can also be used in conjunction with `length` to - * assert a maximum length. The benefit being a - * more informative error message than if the length - * was supplied directly. - * - * expect('foo').to.have.length.below(4); - * expect([ 1, 2, 3 ]).to.have.length.below(4); - * - * @name below - * @alias lt - * @alias lessThan - * @param {Number} value - * @param {String} message _optional_ - * @api public - */ - - function assertBelow (n, msg) { - if (msg) flag(this, 'message', msg); - var obj = flag(this, 'object'); - if (flag(this, 'doLength')) { - new Assertion(obj, msg).to.have.property('length'); - var len = obj.length; - this.assert( - len < n - , 'expected #{this} to have a length below #{exp} but got #{act}' - , 'expected #{this} to not have a length below #{exp}' - , n - , len - ); - } else { - this.assert( - obj < n - , 'expected #{this} to be below ' + n - , 'expected #{this} to be at least ' + n - ); - } - } - - Assertion.addMethod('below', assertBelow); - Assertion.addMethod('lt', assertBelow); - Assertion.addMethod('lessThan', assertBelow); - - /** - * ### .most(value) - * - * Asserts that the target is less than or equal to `value`. - * - * expect(5).to.be.at.most(5); - * - * Can also be used in conjunction with `length` to - * assert a maximum length. The benefit being a - * more informative error message than if the length - * was supplied directly. - * - * expect('foo').to.have.length.of.at.most(4); - * expect([ 1, 2, 3 ]).to.have.length.of.at.most(3); - * - * @name most - * @alias lte - * @param {Number} value - * @param {String} message _optional_ - * @api public - */ - - function assertMost (n, msg) { - if (msg) flag(this, 'message', msg); - var obj = flag(this, 'object'); - if (flag(this, 'doLength')) { - new Assertion(obj, msg).to.have.property('length'); - var len = obj.length; - this.assert( - len <= n - , 'expected #{this} to have a length at most #{exp} but got #{act}' - , 'expected #{this} to have a length above #{exp}' - , n - , len - ); - } else { - this.assert( - obj <= n - , 'expected #{this} to be at most ' + n - , 'expected #{this} to be above ' + n - ); - } - } - - Assertion.addMethod('most', assertMost); - Assertion.addMethod('lte', assertMost); - - /** - * ### .within(start, finish) - * - * Asserts that the target is within a range. - * - * expect(7).to.be.within(5,10); - * - * Can also be used in conjunction with `length` to - * assert a length range. The benefit being a - * more informative error message than if the length - * was supplied directly. - * - * expect('foo').to.have.length.within(2,4); - * expect([ 1, 2, 3 ]).to.have.length.within(2,4); - * - * @name within - * @param {Number} start lowerbound inclusive - * @param {Number} finish upperbound inclusive - * @param {String} message _optional_ - * @api public - */ - - Assertion.addMethod('within', function (start, finish, msg) { - if (msg) flag(this, 'message', msg); - var obj = flag(this, 'object') - , range = start + '..' + finish; - if (flag(this, 'doLength')) { - new Assertion(obj, msg).to.have.property('length'); - var len = obj.length; - this.assert( - len >= start && len <= finish - , 'expected #{this} to have a length within ' + range - , 'expected #{this} to not have a length within ' + range - ); - } else { - this.assert( - obj >= start && obj <= finish - , 'expected #{this} to be within ' + range - , 'expected #{this} to not be within ' + range - ); - } - }); - - /** - * ### .instanceof(constructor) - * - * Asserts that the target is an instance of `constructor`. - * - * var Tea = function (name) { this.name = name; } - * , Chai = new Tea('chai'); - * - * expect(Chai).to.be.an.instanceof(Tea); - * expect([ 1, 2, 3 ]).to.be.instanceof(Array); - * - * @name instanceof - * @param {Constructor} constructor - * @param {String} message _optional_ - * @alias instanceOf - * @api public - */ - - function assertInstanceOf (constructor, msg) { - if (msg) flag(this, 'message', msg); - var name = _.getName(constructor); - this.assert( - flag(this, 'object') instanceof constructor - , 'expected #{this} to be an instance of ' + name - , 'expected #{this} to not be an instance of ' + name - ); - }; - - Assertion.addMethod('instanceof', assertInstanceOf); - Assertion.addMethod('instanceOf', assertInstanceOf); - - /** - * ### .property(name, [value]) - * - * Asserts that the target has a property `name`, optionally asserting that - * the value of that property is strictly equal to `value`. - * If the `deep` flag is set, you can use dot- and bracket-notation for deep - * references into objects and arrays. - * - * // simple referencing - * var obj = { foo: 'bar' }; - * expect(obj).to.have.property('foo'); - * expect(obj).to.have.property('foo', 'bar'); - * - * // deep referencing - * var deepObj = { - * green: { tea: 'matcha' } - * , teas: [ 'chai', 'matcha', { tea: 'konacha' } ] - * }; - - * expect(deepObj).to.have.deep.property('green.tea', 'matcha'); - * expect(deepObj).to.have.deep.property('teas[1]', 'matcha'); - * expect(deepObj).to.have.deep.property('teas[2].tea', 'konacha'); - * - * You can also use an array as the starting point of a `deep.property` - * assertion, or traverse nested arrays. - * - * var arr = [ - * [ 'chai', 'matcha', 'konacha' ] - * , [ { tea: 'chai' } - * , { tea: 'matcha' } - * , { tea: 'konacha' } ] - * ]; - * - * expect(arr).to.have.deep.property('[0][1]', 'matcha'); - * expect(arr).to.have.deep.property('[1][2].tea', 'konacha'); - * - * Furthermore, `property` changes the subject of the assertion - * to be the value of that property from the original object. This - * permits for further chainable assertions on that property. - * - * expect(obj).to.have.property('foo') - * .that.is.a('string'); - * expect(deepObj).to.have.property('green') - * .that.is.an('object') - * .that.deep.equals({ tea: 'matcha' }); - * expect(deepObj).to.have.property('teas') - * .that.is.an('array') - * .with.deep.property('[2]') - * .that.deep.equals({ tea: 'konacha' }); - * - * @name property - * @alias deep.property - * @param {String} name - * @param {Mixed} value (optional) - * @param {String} message _optional_ - * @returns value of property for chaining - * @api public - */ - - Assertion.addMethod('property', function (name, val, msg) { - if (msg) flag(this, 'message', msg); - - var isDeep = !!flag(this, 'deep') - , descriptor = isDeep ? 'deep property ' : 'property ' - , negate = flag(this, 'negate') - , obj = flag(this, 'object') - , pathInfo = isDeep ? _.getPathInfo(name, obj) : null - , hasProperty = isDeep - ? pathInfo.exists - : _.hasProperty(name, obj) - , value = isDeep - ? pathInfo.value - : obj[name]; - - if (negate && undefined !== val) { - if (undefined === value) { - msg = (msg != null) ? msg + ': ' : ''; - throw new Error(msg + _.inspect(obj) + ' has no ' + descriptor + _.inspect(name)); - } - } else { - this.assert( - hasProperty - , 'expected #{this} to have a ' + descriptor + _.inspect(name) - , 'expected #{this} to not have ' + descriptor + _.inspect(name)); - } - - if (undefined !== val) { - this.assert( - val === value - , 'expected #{this} to have a ' + descriptor + _.inspect(name) + ' of #{exp}, but got #{act}' - , 'expected #{this} to not have a ' + descriptor + _.inspect(name) + ' of #{act}' - , val - , value - ); - } - - flag(this, 'object', value); - }); - - - /** - * ### .ownProperty(name) - * - * Asserts that the target has an own property `name`. - * - * expect('test').to.have.ownProperty('length'); - * - * @name ownProperty - * @alias haveOwnProperty - * @param {String} name - * @param {String} message _optional_ - * @api public - */ - - function assertOwnProperty (name, msg) { - if (msg) flag(this, 'message', msg); - var obj = flag(this, 'object'); - this.assert( - obj.hasOwnProperty(name) - , 'expected #{this} to have own property ' + _.inspect(name) - , 'expected #{this} to not have own property ' + _.inspect(name) - ); - } - - Assertion.addMethod('ownProperty', assertOwnProperty); - Assertion.addMethod('haveOwnProperty', assertOwnProperty); - - /** - * ### .length(value) - * - * Asserts that the target's `length` property has - * the expected value. - * - * expect([ 1, 2, 3]).to.have.length(3); - * expect('foobar').to.have.length(6); - * - * Can also be used as a chain precursor to a value - * comparison for the length property. - * - * expect('foo').to.have.length.above(2); - * expect([ 1, 2, 3 ]).to.have.length.above(2); - * expect('foo').to.have.length.below(4); - * expect([ 1, 2, 3 ]).to.have.length.below(4); - * expect('foo').to.have.length.within(2,4); - * expect([ 1, 2, 3 ]).to.have.length.within(2,4); - * - * @name length - * @alias lengthOf - * @param {Number} length - * @param {String} message _optional_ - * @api public - */ - - function assertLengthChain () { - flag(this, 'doLength', true); - } - - function assertLength (n, msg) { - if (msg) flag(this, 'message', msg); - var obj = flag(this, 'object'); - new Assertion(obj, msg).to.have.property('length'); - var len = obj.length; - - this.assert( - len == n - , 'expected #{this} to have a length of #{exp} but got #{act}' - , 'expected #{this} to not have a length of #{act}' - , n - , len - ); - } - - Assertion.addChainableMethod('length', assertLength, assertLengthChain); - Assertion.addMethod('lengthOf', assertLength); - - /** - * ### .match(regexp) - * - * Asserts that the target matches a regular expression. - * - * expect('foobar').to.match(/^foo/); - * - * @name match - * @param {RegExp} RegularExpression - * @param {String} message _optional_ - * @api public - */ - - Assertion.addMethod('match', function (re, msg) { - if (msg) flag(this, 'message', msg); - var obj = flag(this, 'object'); - this.assert( - re.exec(obj) - , 'expected #{this} to match ' + re - , 'expected #{this} not to match ' + re - ); - }); - - /** - * ### .string(string) - * - * Asserts that the string target contains another string. - * - * expect('foobar').to.have.string('bar'); - * - * @name string - * @param {String} string - * @param {String} message _optional_ - * @api public - */ - - Assertion.addMethod('string', function (str, msg) { - if (msg) flag(this, 'message', msg); - var obj = flag(this, 'object'); - new Assertion(obj, msg).is.a('string'); - - this.assert( - ~obj.indexOf(str) - , 'expected #{this} to contain ' + _.inspect(str) - , 'expected #{this} to not contain ' + _.inspect(str) - ); - }); - - - /** - * ### .keys(key1, [key2], [...]) - * - * Asserts that the target contains any or all of the passed-in keys. - * Use in combination with `any`, `all`, `contains`, or `have` will affect - * what will pass. - * - * When used in conjunction with `any`, at least one key that is passed - * in must exist in the target object. This is regardless whether or not - * the `have` or `contain` qualifiers are used. Note, either `any` or `all` - * should be used in the assertion. If neither are used, the assertion is - * defaulted to `all`. - * - * When both `all` and `contain` are used, the target object must have at - * least all of the passed-in keys but may have more keys not listed. - * - * When both `all` and `have` are used, the target object must both contain - * all of the passed-in keys AND the number of keys in the target object must - * match the number of keys passed in (in other words, a target object must - * have all and only all of the passed-in keys). - * - * expect({ foo: 1, bar: 2 }).to.have.any.keys('foo', 'baz'); - * expect({ foo: 1, bar: 2 }).to.have.any.keys('foo'); - * expect({ foo: 1, bar: 2 }).to.contain.any.keys('bar', 'baz'); - * expect({ foo: 1, bar: 2 }).to.contain.any.keys(['foo']); - * expect({ foo: 1, bar: 2 }).to.contain.any.keys({'foo': 6}); - * expect({ foo: 1, bar: 2 }).to.have.all.keys(['bar', 'foo']); - * expect({ foo: 1, bar: 2 }).to.have.all.keys({'bar': 6, 'foo', 7}); - * expect({ foo: 1, bar: 2, baz: 3 }).to.contain.all.keys(['bar', 'foo']); - * expect({ foo: 1, bar: 2, baz: 3 }).to.contain.all.keys([{'bar': 6}}]); - * - * - * @name keys - * @alias key - * @param {String...|Array|Object} keys - * @api public - */ - - function assertKeys (keys) { - var obj = flag(this, 'object') - , str - , ok = true - , mixedArgsMsg = 'keys must be given single argument of Array|Object|String, or multiple String arguments'; - - switch (_.type(keys)) { - case "array": - if (arguments.length > 1) throw (new Error(mixedArgsMsg)); - break; - case "object": - if (arguments.length > 1) throw (new Error(mixedArgsMsg)); - keys = Object.keys(keys); - break; - default: - keys = Array.prototype.slice.call(arguments); - } - - if (!keys.length) throw new Error('keys required'); - - var actual = Object.keys(obj) - , expected = keys - , len = keys.length - , any = flag(this, 'any') - , all = flag(this, 'all'); - - if (!any && !all) { - all = true; - } - - // Has any - if (any) { - var intersection = expected.filter(function(key) { - return ~actual.indexOf(key); - }); - ok = intersection.length > 0; - } - - // Has all - if (all) { - ok = keys.every(function(key){ - return ~actual.indexOf(key); - }); - if (!flag(this, 'negate') && !flag(this, 'contains')) { - ok = ok && keys.length == actual.length; - } - } - - // Key string - if (len > 1) { - keys = keys.map(function(key){ - return _.inspect(key); - }); - var last = keys.pop(); - if (all) { - str = keys.join(', ') + ', and ' + last; - } - if (any) { - str = keys.join(', ') + ', or ' + last; - } - } else { - str = _.inspect(keys[0]); - } - - // Form - str = (len > 1 ? 'keys ' : 'key ') + str; - - // Have / include - str = (flag(this, 'contains') ? 'contain ' : 'have ') + str; - - // Assertion - this.assert( - ok - , 'expected #{this} to ' + str - , 'expected #{this} to not ' + str - , expected.slice(0).sort() - , actual.sort() - , true - ); - } - - Assertion.addMethod('keys', assertKeys); - Assertion.addMethod('key', assertKeys); - - /** - * ### .throw(constructor) - * - * Asserts that the function target will throw a specific error, or specific type of error - * (as determined using `instanceof`), optionally with a RegExp or string inclusion test - * for the error's message. - * - * var err = new ReferenceError('This is a bad function.'); - * var fn = function () { throw err; } - * expect(fn).to.throw(ReferenceError); - * expect(fn).to.throw(Error); - * expect(fn).to.throw(/bad function/); - * expect(fn).to.not.throw('good function'); - * expect(fn).to.throw(ReferenceError, /bad function/); - * expect(fn).to.throw(err); - * expect(fn).to.not.throw(new RangeError('Out of range.')); - * - * Please note that when a throw expectation is negated, it will check each - * parameter independently, starting with error constructor type. The appropriate way - * to check for the existence of a type of error but for a message that does not match - * is to use `and`. - * - * expect(fn).to.throw(ReferenceError) - * .and.not.throw(/good function/); - * - * @name throw - * @alias throws - * @alias Throw - * @param {ErrorConstructor} constructor - * @param {String|RegExp} expected error message - * @param {String} message _optional_ - * @see https://developer.mozilla.org/en/JavaScript/Reference/Global_Objects/Error#Error_types - * @returns error for chaining (null if no error) - * @api public - */ - - function assertThrows (constructor, errMsg, msg) { - if (msg) flag(this, 'message', msg); - var obj = flag(this, 'object'); - new Assertion(obj, msg).is.a('function'); - - var thrown = false - , desiredError = null - , name = null - , thrownError = null; - - if (arguments.length === 0) { - errMsg = null; - constructor = null; - } else if (constructor && (constructor instanceof RegExp || 'string' === typeof constructor)) { - errMsg = constructor; - constructor = null; - } else if (constructor && constructor instanceof Error) { - desiredError = constructor; - constructor = null; - errMsg = null; - } else if (typeof constructor === 'function') { - name = constructor.prototype.name || constructor.name; - if (name === 'Error' && constructor !== Error) { - name = (new constructor()).name; - } - } else { - constructor = null; - } - - try { - obj(); - } catch (err) { - // first, check desired error - if (desiredError) { - this.assert( - err === desiredError - , 'expected #{this} to throw #{exp} but #{act} was thrown' - , 'expected #{this} to not throw #{exp}' - , (desiredError instanceof Error ? desiredError.toString() : desiredError) - , (err instanceof Error ? err.toString() : err) - ); - - flag(this, 'object', err); - return this; - } - - // next, check constructor - if (constructor) { - this.assert( - err instanceof constructor - , 'expected #{this} to throw #{exp} but #{act} was thrown' - , 'expected #{this} to not throw #{exp} but #{act} was thrown' - , name - , (err instanceof Error ? err.toString() : err) - ); - - if (!errMsg) { - flag(this, 'object', err); - return this; - } - } - - // next, check message - var message = 'object' === _.type(err) && "message" in err - ? err.message - : '' + err; - - if ((message != null) && errMsg && errMsg instanceof RegExp) { - this.assert( - errMsg.exec(message) - , 'expected #{this} to throw error matching #{exp} but got #{act}' - , 'expected #{this} to throw error not matching #{exp}' - , errMsg - , message - ); - - flag(this, 'object', err); - return this; - } else if ((message != null) && errMsg && 'string' === typeof errMsg) { - this.assert( - ~message.indexOf(errMsg) - , 'expected #{this} to throw error including #{exp} but got #{act}' - , 'expected #{this} to throw error not including #{act}' - , errMsg - , message - ); - - flag(this, 'object', err); - return this; - } else { - thrown = true; - thrownError = err; - } - } - - var actuallyGot = '' - , expectedThrown = name !== null - ? name - : desiredError - ? '#{exp}' //_.inspect(desiredError) - : 'an error'; - - if (thrown) { - actuallyGot = ' but #{act} was thrown' - } - - this.assert( - thrown === true - , 'expected #{this} to throw ' + expectedThrown + actuallyGot - , 'expected #{this} to not throw ' + expectedThrown + actuallyGot - , (desiredError instanceof Error ? desiredError.toString() : desiredError) - , (thrownError instanceof Error ? thrownError.toString() : thrownError) - ); - - flag(this, 'object', thrownError); - }; - - Assertion.addMethod('throw', assertThrows); - Assertion.addMethod('throws', assertThrows); - Assertion.addMethod('Throw', assertThrows); - - /** - * ### .respondTo(method) - * - * Asserts that the object or class target will respond to a method. - * - * Klass.prototype.bar = function(){}; - * expect(Klass).to.respondTo('bar'); - * expect(obj).to.respondTo('bar'); - * - * To check if a constructor will respond to a static function, - * set the `itself` flag. - * - * Klass.baz = function(){}; - * expect(Klass).itself.to.respondTo('baz'); - * - * @name respondTo - * @param {String} method - * @param {String} message _optional_ - * @api public - */ - - Assertion.addMethod('respondTo', function (method, msg) { - if (msg) flag(this, 'message', msg); - var obj = flag(this, 'object') - , itself = flag(this, 'itself') - , context = ('function' === _.type(obj) && !itself) - ? obj.prototype[method] - : obj[method]; - - this.assert( - 'function' === typeof context - , 'expected #{this} to respond to ' + _.inspect(method) - , 'expected #{this} to not respond to ' + _.inspect(method) - ); - }); - - /** - * ### .itself - * - * Sets the `itself` flag, later used by the `respondTo` assertion. - * - * function Foo() {} - * Foo.bar = function() {} - * Foo.prototype.baz = function() {} - * - * expect(Foo).itself.to.respondTo('bar'); - * expect(Foo).itself.not.to.respondTo('baz'); - * - * @name itself - * @api public - */ - - Assertion.addProperty('itself', function () { - flag(this, 'itself', true); - }); - - /** - * ### .satisfy(method) - * - * Asserts that the target passes a given truth test. - * - * expect(1).to.satisfy(function(num) { return num > 0; }); - * - * @name satisfy - * @param {Function} matcher - * @param {String} message _optional_ - * @api public - */ - - Assertion.addMethod('satisfy', function (matcher, msg) { - if (msg) flag(this, 'message', msg); - var obj = flag(this, 'object'); - var result = matcher(obj); - this.assert( - result - , 'expected #{this} to satisfy ' + _.objDisplay(matcher) - , 'expected #{this} to not satisfy' + _.objDisplay(matcher) - , this.negate ? false : true - , result - ); - }); - - /** - * ### .closeTo(expected, delta) - * - * Asserts that the target is equal `expected`, to within a +/- `delta` range. - * - * expect(1.5).to.be.closeTo(1, 0.5); - * - * @name closeTo - * @param {Number} expected - * @param {Number} delta - * @param {String} message _optional_ - * @api public - */ - - Assertion.addMethod('closeTo', function (expected, delta, msg) { - if (msg) flag(this, 'message', msg); - var obj = flag(this, 'object'); - - new Assertion(obj, msg).is.a('number'); - if (_.type(expected) !== 'number' || _.type(delta) !== 'number') { - throw new Error('the arguments to closeTo must be numbers'); - } - - this.assert( - Math.abs(obj - expected) <= delta - , 'expected #{this} to be close to ' + expected + ' +/- ' + delta - , 'expected #{this} not to be close to ' + expected + ' +/- ' + delta - ); - }); - - function isSubsetOf(subset, superset, cmp) { - return subset.every(function(elem) { - if (!cmp) return superset.indexOf(elem) !== -1; - - return superset.some(function(elem2) { - return cmp(elem, elem2); - }); - }) - } - - /** - * ### .members(set) - * - * Asserts that the target is a superset of `set`, - * or that the target and `set` have the same strictly-equal (===) members. - * Alternately, if the `deep` flag is set, set members are compared for deep - * equality. - * - * expect([1, 2, 3]).to.include.members([3, 2]); - * expect([1, 2, 3]).to.not.include.members([3, 2, 8]); - * - * expect([4, 2]).to.have.members([2, 4]); - * expect([5, 2]).to.not.have.members([5, 2, 1]); - * - * expect([{ id: 1 }]).to.deep.include.members([{ id: 1 }]); - * - * @name members - * @param {Array} set - * @param {String} message _optional_ - * @api public - */ - - Assertion.addMethod('members', function (subset, msg) { - if (msg) flag(this, 'message', msg); - var obj = flag(this, 'object'); - - new Assertion(obj).to.be.an('array'); - new Assertion(subset).to.be.an('array'); - - var cmp = flag(this, 'deep') ? _.eql : undefined; - - if (flag(this, 'contains')) { - return this.assert( - isSubsetOf(subset, obj, cmp) - , 'expected #{this} to be a superset of #{act}' - , 'expected #{this} to not be a superset of #{act}' - , obj - , subset - ); - } - - this.assert( - isSubsetOf(obj, subset, cmp) && isSubsetOf(subset, obj, cmp) - , 'expected #{this} to have the same members as #{act}' - , 'expected #{this} to not have the same members as #{act}' - , obj - , subset - ); - }); - - /** - * ### .change(function) - * - * Asserts that a function changes an object property - * - * var obj = { val: 10 }; - * var fn = function() { obj.val += 3 }; - * var noChangeFn = function() { return 'foo' + 'bar'; } - * expect(fn).to.change(obj, 'val'); - * expect(noChangFn).to.not.change(obj, 'val') - * - * @name change - * @alias changes - * @alias Change - * @param {String} object - * @param {String} property name - * @param {String} message _optional_ - * @api public - */ - - function assertChanges (object, prop, msg) { - if (msg) flag(this, 'message', msg); - var fn = flag(this, 'object'); - new Assertion(object, msg).to.have.property(prop); - new Assertion(fn).is.a('function'); - - var initial = object[prop]; - fn(); - - this.assert( - initial !== object[prop] - , 'expected .' + prop + ' to change' - , 'expected .' + prop + ' to not change' - ); - } - - Assertion.addChainableMethod('change', assertChanges); - Assertion.addChainableMethod('changes', assertChanges); - - /** - * ### .increase(function) - * - * Asserts that a function increases an object property - * - * var obj = { val: 10 }; - * var fn = function() { obj.val = 15 }; - * expect(fn).to.increase(obj, 'val'); - * - * @name increase - * @alias increases - * @alias Increase - * @param {String} object - * @param {String} property name - * @param {String} message _optional_ - * @api public - */ - - function assertIncreases (object, prop, msg) { - if (msg) flag(this, 'message', msg); - var fn = flag(this, 'object'); - new Assertion(object, msg).to.have.property(prop); - new Assertion(fn).is.a('function'); - - var initial = object[prop]; - fn(); - - this.assert( - object[prop] - initial > 0 - , 'expected .' + prop + ' to increase' - , 'expected .' + prop + ' to not increase' - ); - } - - Assertion.addChainableMethod('increase', assertIncreases); - Assertion.addChainableMethod('increases', assertIncreases); - - /** - * ### .decrease(function) - * - * Asserts that a function decreases an object property - * - * var obj = { val: 10 }; - * var fn = function() { obj.val = 5 }; - * expect(fn).to.decrease(obj, 'val'); - * - * @name decrease - * @alias decreases - * @alias Decrease - * @param {String} object - * @param {String} property name - * @param {String} message _optional_ - * @api public - */ - - function assertDecreases (object, prop, msg) { - if (msg) flag(this, 'message', msg); - var fn = flag(this, 'object'); - new Assertion(object, msg).to.have.property(prop); - new Assertion(fn).is.a('function'); - - var initial = object[prop]; - fn(); - - this.assert( - object[prop] - initial < 0 - , 'expected .' + prop + ' to decrease' - , 'expected .' + prop + ' to not decrease' - ); - } - - Assertion.addChainableMethod('decrease', assertDecreases); - Assertion.addChainableMethod('decreases', assertDecreases); - -}; - -}); - -require.register("chai/lib/chai/interface/assert.js", function (exports, module) { -/*! - * chai - * Copyright(c) 2011-2014 Jake Luer - * MIT Licensed - */ - - -module.exports = function (chai, util) { - - /*! - * Chai dependencies. - */ - - var Assertion = chai.Assertion - , flag = util.flag; - - /*! - * Module export. - */ - - /** - * ### assert(expression, message) - * - * Write your own test expressions. - * - * assert('foo' !== 'bar', 'foo is not bar'); - * assert(Array.isArray([]), 'empty arrays are arrays'); - * - * @param {Mixed} expression to test for truthiness - * @param {String} message to display on error - * @name assert - * @api public - */ - - var assert = chai.assert = function (express, errmsg) { - var test = new Assertion(null, null, chai.assert); - test.assert( - express - , errmsg - , '[ negation message unavailable ]' - ); - }; - - /** - * ### .fail(actual, expected, [message], [operator]) - * - * Throw a failure. Node.js `assert` module-compatible. - * - * @name fail - * @param {Mixed} actual - * @param {Mixed} expected - * @param {String} message - * @param {String} operator - * @api public - */ - - assert.fail = function (actual, expected, message, operator) { - message = message || 'assert.fail()'; - throw new chai.AssertionError(message, { - actual: actual - , expected: expected - , operator: operator - }, assert.fail); - }; - - /** - * ### .ok(object, [message]) - * - * Asserts that `object` is truthy. - * - * assert.ok('everything', 'everything is ok'); - * assert.ok(false, 'this will fail'); - * - * @name ok - * @param {Mixed} object to test - * @param {String} message - * @api public - */ - - assert.ok = function (val, msg) { - new Assertion(val, msg).is.ok; - }; - - /** - * ### .notOk(object, [message]) - * - * Asserts that `object` is falsy. - * - * assert.notOk('everything', 'this will fail'); - * assert.notOk(false, 'this will pass'); - * - * @name notOk - * @param {Mixed} object to test - * @param {String} message - * @api public - */ - - assert.notOk = function (val, msg) { - new Assertion(val, msg).is.not.ok; - }; - - /** - * ### .equal(actual, expected, [message]) - * - * Asserts non-strict equality (`==`) of `actual` and `expected`. - * - * assert.equal(3, '3', '== coerces values to strings'); - * - * @name equal - * @param {Mixed} actual - * @param {Mixed} expected - * @param {String} message - * @api public - */ - - assert.equal = function (act, exp, msg) { - var test = new Assertion(act, msg, assert.equal); - - test.assert( - exp == flag(test, 'object') - , 'expected #{this} to equal #{exp}' - , 'expected #{this} to not equal #{act}' - , exp - , act - ); - }; - - /** - * ### .notEqual(actual, expected, [message]) - * - * Asserts non-strict inequality (`!=`) of `actual` and `expected`. - * - * assert.notEqual(3, 4, 'these numbers are not equal'); - * - * @name notEqual - * @param {Mixed} actual - * @param {Mixed} expected - * @param {String} message - * @api public - */ - - assert.notEqual = function (act, exp, msg) { - var test = new Assertion(act, msg, assert.notEqual); - - test.assert( - exp != flag(test, 'object') - , 'expected #{this} to not equal #{exp}' - , 'expected #{this} to equal #{act}' - , exp - , act - ); - }; - - /** - * ### .strictEqual(actual, expected, [message]) - * - * Asserts strict equality (`===`) of `actual` and `expected`. - * - * assert.strictEqual(true, true, 'these booleans are strictly equal'); - * - * @name strictEqual - * @param {Mixed} actual - * @param {Mixed} expected - * @param {String} message - * @api public - */ - - assert.strictEqual = function (act, exp, msg) { - new Assertion(act, msg).to.equal(exp); - }; - - /** - * ### .notStrictEqual(actual, expected, [message]) - * - * Asserts strict inequality (`!==`) of `actual` and `expected`. - * - * assert.notStrictEqual(3, '3', 'no coercion for strict equality'); - * - * @name notStrictEqual - * @param {Mixed} actual - * @param {Mixed} expected - * @param {String} message - * @api public - */ - - assert.notStrictEqual = function (act, exp, msg) { - new Assertion(act, msg).to.not.equal(exp); - }; - - /** - * ### .deepEqual(actual, expected, [message]) - * - * Asserts that `actual` is deeply equal to `expected`. - * - * assert.deepEqual({ tea: 'green' }, { tea: 'green' }); - * - * @name deepEqual - * @param {Mixed} actual - * @param {Mixed} expected - * @param {String} message - * @api public - */ - - assert.deepEqual = function (act, exp, msg) { - new Assertion(act, msg).to.eql(exp); - }; - - /** - * ### .notDeepEqual(actual, expected, [message]) - * - * Assert that `actual` is not deeply equal to `expected`. - * - * assert.notDeepEqual({ tea: 'green' }, { tea: 'jasmine' }); - * - * @name notDeepEqual - * @param {Mixed} actual - * @param {Mixed} expected - * @param {String} message - * @api public - */ - - assert.notDeepEqual = function (act, exp, msg) { - new Assertion(act, msg).to.not.eql(exp); - }; - - /** - * ### .isTrue(value, [message]) - * - * Asserts that `value` is true. - * - * var teaServed = true; - * assert.isTrue(teaServed, 'the tea has been served'); - * - * @name isTrue - * @param {Mixed} value - * @param {String} message - * @api public - */ - - assert.isAbove = function (val, abv, msg) { - new Assertion(val, msg).to.be.above(abv); - }; - - /** - * ### .isAbove(valueToCheck, valueToBeAbove, [message]) - * - * Asserts `valueToCheck` is strictly greater than (>) `valueToBeAbove` - * - * assert.isAbove(5, 2, '5 is strictly greater than 2'); - * - * @name isAbove - * @param {Mixed} valueToCheck - * @param {Mixed} valueToBeAbove - * @param {String} message - * @api public - */ - - assert.isBelow = function (val, blw, msg) { - new Assertion(val, msg).to.be.below(blw); - }; - - /** - * ### .isBelow(valueToCheck, valueToBeBelow, [message]) - * - * Asserts `valueToCheck` is strictly less than (<) `valueToBeBelow` - * - * assert.isBelow(3, 6, '3 is strictly less than 6'); - * - * @name isBelow - * @param {Mixed} valueToCheck - * @param {Mixed} valueToBeBelow - * @param {String} message - * @api public - */ - - assert.isTrue = function (val, msg) { - new Assertion(val, msg).is['true']; - }; - - /** - * ### .isFalse(value, [message]) - * - * Asserts that `value` is false. - * - * var teaServed = false; - * assert.isFalse(teaServed, 'no tea yet? hmm...'); - * - * @name isFalse - * @param {Mixed} value - * @param {String} message - * @api public - */ - - assert.isFalse = function (val, msg) { - new Assertion(val, msg).is['false']; - }; - - /** - * ### .isNull(value, [message]) - * - * Asserts that `value` is null. - * - * assert.isNull(err, 'there was no error'); - * - * @name isNull - * @param {Mixed} value - * @param {String} message - * @api public - */ - - assert.isNull = function (val, msg) { - new Assertion(val, msg).to.equal(null); - }; - - /** - * ### .isNotNull(value, [message]) - * - * Asserts that `value` is not null. - * - * var tea = 'tasty chai'; - * assert.isNotNull(tea, 'great, time for tea!'); - * - * @name isNotNull - * @param {Mixed} value - * @param {String} message - * @api public - */ - - assert.isNotNull = function (val, msg) { - new Assertion(val, msg).to.not.equal(null); - }; - - /** - * ### .isUndefined(value, [message]) - * - * Asserts that `value` is `undefined`. - * - * var tea; - * assert.isUndefined(tea, 'no tea defined'); - * - * @name isUndefined - * @param {Mixed} value - * @param {String} message - * @api public - */ - - assert.isUndefined = function (val, msg) { - new Assertion(val, msg).to.equal(undefined); - }; - - /** - * ### .isDefined(value, [message]) - * - * Asserts that `value` is not `undefined`. - * - * var tea = 'cup of chai'; - * assert.isDefined(tea, 'tea has been defined'); - * - * @name isDefined - * @param {Mixed} value - * @param {String} message - * @api public - */ - - assert.isDefined = function (val, msg) { - new Assertion(val, msg).to.not.equal(undefined); - }; - - /** - * ### .isFunction(value, [message]) - * - * Asserts that `value` is a function. - * - * function serveTea() { return 'cup of tea'; }; - * assert.isFunction(serveTea, 'great, we can have tea now'); - * - * @name isFunction - * @param {Mixed} value - * @param {String} message - * @api public - */ - - assert.isFunction = function (val, msg) { - new Assertion(val, msg).to.be.a('function'); - }; - - /** - * ### .isNotFunction(value, [message]) - * - * Asserts that `value` is _not_ a function. - * - * var serveTea = [ 'heat', 'pour', 'sip' ]; - * assert.isNotFunction(serveTea, 'great, we have listed the steps'); - * - * @name isNotFunction - * @param {Mixed} value - * @param {String} message - * @api public - */ - - assert.isNotFunction = function (val, msg) { - new Assertion(val, msg).to.not.be.a('function'); - }; - - /** - * ### .isObject(value, [message]) - * - * Asserts that `value` is an object (as revealed by - * `Object.prototype.toString`). - * - * var selection = { name: 'Chai', serve: 'with spices' }; - * assert.isObject(selection, 'tea selection is an object'); - * - * @name isObject - * @param {Mixed} value - * @param {String} message - * @api public - */ - - assert.isObject = function (val, msg) { - new Assertion(val, msg).to.be.a('object'); - }; - - /** - * ### .isNotObject(value, [message]) - * - * Asserts that `value` is _not_ an object. - * - * var selection = 'chai' - * assert.isNotObject(selection, 'tea selection is not an object'); - * assert.isNotObject(null, 'null is not an object'); - * - * @name isNotObject - * @param {Mixed} value - * @param {String} message - * @api public - */ - - assert.isNotObject = function (val, msg) { - new Assertion(val, msg).to.not.be.a('object'); - }; - - /** - * ### .isArray(value, [message]) - * - * Asserts that `value` is an array. - * - * var menu = [ 'green', 'chai', 'oolong' ]; - * assert.isArray(menu, 'what kind of tea do we want?'); - * - * @name isArray - * @param {Mixed} value - * @param {String} message - * @api public - */ - - assert.isArray = function (val, msg) { - new Assertion(val, msg).to.be.an('array'); - }; - - /** - * ### .isNotArray(value, [message]) - * - * Asserts that `value` is _not_ an array. - * - * var menu = 'green|chai|oolong'; - * assert.isNotArray(menu, 'what kind of tea do we want?'); - * - * @name isNotArray - * @param {Mixed} value - * @param {String} message - * @api public - */ - - assert.isNotArray = function (val, msg) { - new Assertion(val, msg).to.not.be.an('array'); - }; - - /** - * ### .isString(value, [message]) - * - * Asserts that `value` is a string. - * - * var teaOrder = 'chai'; - * assert.isString(teaOrder, 'order placed'); - * - * @name isString - * @param {Mixed} value - * @param {String} message - * @api public - */ - - assert.isString = function (val, msg) { - new Assertion(val, msg).to.be.a('string'); - }; - - /** - * ### .isNotString(value, [message]) - * - * Asserts that `value` is _not_ a string. - * - * var teaOrder = 4; - * assert.isNotString(teaOrder, 'order placed'); - * - * @name isNotString - * @param {Mixed} value - * @param {String} message - * @api public - */ - - assert.isNotString = function (val, msg) { - new Assertion(val, msg).to.not.be.a('string'); - }; - - /** - * ### .isNumber(value, [message]) - * - * Asserts that `value` is a number. - * - * var cups = 2; - * assert.isNumber(cups, 'how many cups'); - * - * @name isNumber - * @param {Number} value - * @param {String} message - * @api public - */ - - assert.isNumber = function (val, msg) { - new Assertion(val, msg).to.be.a('number'); - }; - - /** - * ### .isNotNumber(value, [message]) - * - * Asserts that `value` is _not_ a number. - * - * var cups = '2 cups please'; - * assert.isNotNumber(cups, 'how many cups'); - * - * @name isNotNumber - * @param {Mixed} value - * @param {String} message - * @api public - */ - - assert.isNotNumber = function (val, msg) { - new Assertion(val, msg).to.not.be.a('number'); - }; - - /** - * ### .isBoolean(value, [message]) - * - * Asserts that `value` is a boolean. - * - * var teaReady = true - * , teaServed = false; - * - * assert.isBoolean(teaReady, 'is the tea ready'); - * assert.isBoolean(teaServed, 'has tea been served'); - * - * @name isBoolean - * @param {Mixed} value - * @param {String} message - * @api public - */ - - assert.isBoolean = function (val, msg) { - new Assertion(val, msg).to.be.a('boolean'); - }; - - /** - * ### .isNotBoolean(value, [message]) - * - * Asserts that `value` is _not_ a boolean. - * - * var teaReady = 'yep' - * , teaServed = 'nope'; - * - * assert.isNotBoolean(teaReady, 'is the tea ready'); - * assert.isNotBoolean(teaServed, 'has tea been served'); - * - * @name isNotBoolean - * @param {Mixed} value - * @param {String} message - * @api public - */ - - assert.isNotBoolean = function (val, msg) { - new Assertion(val, msg).to.not.be.a('boolean'); - }; - - /** - * ### .typeOf(value, name, [message]) - * - * Asserts that `value`'s type is `name`, as determined by - * `Object.prototype.toString`. - * - * assert.typeOf({ tea: 'chai' }, 'object', 'we have an object'); - * assert.typeOf(['chai', 'jasmine'], 'array', 'we have an array'); - * assert.typeOf('tea', 'string', 'we have a string'); - * assert.typeOf(/tea/, 'regexp', 'we have a regular expression'); - * assert.typeOf(null, 'null', 'we have a null'); - * assert.typeOf(undefined, 'undefined', 'we have an undefined'); - * - * @name typeOf - * @param {Mixed} value - * @param {String} name - * @param {String} message - * @api public - */ - - assert.typeOf = function (val, type, msg) { - new Assertion(val, msg).to.be.a(type); - }; - - /** - * ### .notTypeOf(value, name, [message]) - * - * Asserts that `value`'s type is _not_ `name`, as determined by - * `Object.prototype.toString`. - * - * assert.notTypeOf('tea', 'number', 'strings are not numbers'); - * - * @name notTypeOf - * @param {Mixed} value - * @param {String} typeof name - * @param {String} message - * @api public - */ - - assert.notTypeOf = function (val, type, msg) { - new Assertion(val, msg).to.not.be.a(type); - }; - - /** - * ### .instanceOf(object, constructor, [message]) - * - * Asserts that `value` is an instance of `constructor`. - * - * var Tea = function (name) { this.name = name; } - * , chai = new Tea('chai'); - * - * assert.instanceOf(chai, Tea, 'chai is an instance of tea'); - * - * @name instanceOf - * @param {Object} object - * @param {Constructor} constructor - * @param {String} message - * @api public - */ - - assert.instanceOf = function (val, type, msg) { - new Assertion(val, msg).to.be.instanceOf(type); - }; - - /** - * ### .notInstanceOf(object, constructor, [message]) - * - * Asserts `value` is not an instance of `constructor`. - * - * var Tea = function (name) { this.name = name; } - * , chai = new String('chai'); - * - * assert.notInstanceOf(chai, Tea, 'chai is not an instance of tea'); - * - * @name notInstanceOf - * @param {Object} object - * @param {Constructor} constructor - * @param {String} message - * @api public - */ - - assert.notInstanceOf = function (val, type, msg) { - new Assertion(val, msg).to.not.be.instanceOf(type); - }; - - /** - * ### .include(haystack, needle, [message]) - * - * Asserts that `haystack` includes `needle`. Works - * for strings and arrays. - * - * assert.include('foobar', 'bar', 'foobar contains string "bar"'); - * assert.include([ 1, 2, 3 ], 3, 'array contains value'); - * - * @name include - * @param {Array|String} haystack - * @param {Mixed} needle - * @param {String} message - * @api public - */ - - assert.include = function (exp, inc, msg) { - new Assertion(exp, msg, assert.include).include(inc); - }; - - /** - * ### .notInclude(haystack, needle, [message]) - * - * Asserts that `haystack` does not include `needle`. Works - * for strings and arrays. - *i - * assert.notInclude('foobar', 'baz', 'string not include substring'); - * assert.notInclude([ 1, 2, 3 ], 4, 'array not include contain value'); - * - * @name notInclude - * @param {Array|String} haystack - * @param {Mixed} needle - * @param {String} message - * @api public - */ - - assert.notInclude = function (exp, inc, msg) { - new Assertion(exp, msg, assert.notInclude).not.include(inc); - }; - - /** - * ### .match(value, regexp, [message]) - * - * Asserts that `value` matches the regular expression `regexp`. - * - * assert.match('foobar', /^foo/, 'regexp matches'); - * - * @name match - * @param {Mixed} value - * @param {RegExp} regexp - * @param {String} message - * @api public - */ - - assert.match = function (exp, re, msg) { - new Assertion(exp, msg).to.match(re); - }; - - /** - * ### .notMatch(value, regexp, [message]) - * - * Asserts that `value` does not match the regular expression `regexp`. - * - * assert.notMatch('foobar', /^foo/, 'regexp does not match'); - * - * @name notMatch - * @param {Mixed} value - * @param {RegExp} regexp - * @param {String} message - * @api public - */ - - assert.notMatch = function (exp, re, msg) { - new Assertion(exp, msg).to.not.match(re); - }; - - /** - * ### .property(object, property, [message]) - * - * Asserts that `object` has a property named by `property`. - * - * assert.property({ tea: { green: 'matcha' }}, 'tea'); - * - * @name property - * @param {Object} object - * @param {String} property - * @param {String} message - * @api public - */ - - assert.property = function (obj, prop, msg) { - new Assertion(obj, msg).to.have.property(prop); - }; - - /** - * ### .notProperty(object, property, [message]) - * - * Asserts that `object` does _not_ have a property named by `property`. - * - * assert.notProperty({ tea: { green: 'matcha' }}, 'coffee'); - * - * @name notProperty - * @param {Object} object - * @param {String} property - * @param {String} message - * @api public - */ - - assert.notProperty = function (obj, prop, msg) { - new Assertion(obj, msg).to.not.have.property(prop); - }; - - /** - * ### .deepProperty(object, property, [message]) - * - * Asserts that `object` has a property named by `property`, which can be a - * string using dot- and bracket-notation for deep reference. - * - * assert.deepProperty({ tea: { green: 'matcha' }}, 'tea.green'); - * - * @name deepProperty - * @param {Object} object - * @param {String} property - * @param {String} message - * @api public - */ - - assert.deepProperty = function (obj, prop, msg) { - new Assertion(obj, msg).to.have.deep.property(prop); - }; - - /** - * ### .notDeepProperty(object, property, [message]) - * - * Asserts that `object` does _not_ have a property named by `property`, which - * can be a string using dot- and bracket-notation for deep reference. - * - * assert.notDeepProperty({ tea: { green: 'matcha' }}, 'tea.oolong'); - * - * @name notDeepProperty - * @param {Object} object - * @param {String} property - * @param {String} message - * @api public - */ - - assert.notDeepProperty = function (obj, prop, msg) { - new Assertion(obj, msg).to.not.have.deep.property(prop); - }; - - /** - * ### .propertyVal(object, property, value, [message]) - * - * Asserts that `object` has a property named by `property` with value given - * by `value`. - * - * assert.propertyVal({ tea: 'is good' }, 'tea', 'is good'); - * - * @name propertyVal - * @param {Object} object - * @param {String} property - * @param {Mixed} value - * @param {String} message - * @api public - */ - - assert.propertyVal = function (obj, prop, val, msg) { - new Assertion(obj, msg).to.have.property(prop, val); - }; - - /** - * ### .propertyNotVal(object, property, value, [message]) - * - * Asserts that `object` has a property named by `property`, but with a value - * different from that given by `value`. - * - * assert.propertyNotVal({ tea: 'is good' }, 'tea', 'is bad'); - * - * @name propertyNotVal - * @param {Object} object - * @param {String} property - * @param {Mixed} value - * @param {String} message - * @api public - */ - - assert.propertyNotVal = function (obj, prop, val, msg) { - new Assertion(obj, msg).to.not.have.property(prop, val); - }; - - /** - * ### .deepPropertyVal(object, property, value, [message]) - * - * Asserts that `object` has a property named by `property` with value given - * by `value`. `property` can use dot- and bracket-notation for deep - * reference. - * - * assert.deepPropertyVal({ tea: { green: 'matcha' }}, 'tea.green', 'matcha'); - * - * @name deepPropertyVal - * @param {Object} object - * @param {String} property - * @param {Mixed} value - * @param {String} message - * @api public - */ - - assert.deepPropertyVal = function (obj, prop, val, msg) { - new Assertion(obj, msg).to.have.deep.property(prop, val); - }; - - /** - * ### .deepPropertyNotVal(object, property, value, [message]) - * - * Asserts that `object` has a property named by `property`, but with a value - * different from that given by `value`. `property` can use dot- and - * bracket-notation for deep reference. - * - * assert.deepPropertyNotVal({ tea: { green: 'matcha' }}, 'tea.green', 'konacha'); - * - * @name deepPropertyNotVal - * @param {Object} object - * @param {String} property - * @param {Mixed} value - * @param {String} message - * @api public - */ - - assert.deepPropertyNotVal = function (obj, prop, val, msg) { - new Assertion(obj, msg).to.not.have.deep.property(prop, val); - }; - - /** - * ### .lengthOf(object, length, [message]) - * - * Asserts that `object` has a `length` property with the expected value. - * - * assert.lengthOf([1,2,3], 3, 'array has length of 3'); - * assert.lengthOf('foobar', 5, 'string has length of 6'); - * - * @name lengthOf - * @param {Mixed} object - * @param {Number} length - * @param {String} message - * @api public - */ - - assert.lengthOf = function (exp, len, msg) { - new Assertion(exp, msg).to.have.length(len); - }; - - /** - * ### .throws(function, [constructor/string/regexp], [string/regexp], [message]) - * - * Asserts that `function` will throw an error that is an instance of - * `constructor`, or alternately that it will throw an error with message - * matching `regexp`. - * - * assert.throw(fn, 'function throws a reference error'); - * assert.throw(fn, /function throws a reference error/); - * assert.throw(fn, ReferenceError); - * assert.throw(fn, ReferenceError, 'function throws a reference error'); - * assert.throw(fn, ReferenceError, /function throws a reference error/); - * - * @name throws - * @alias throw - * @alias Throw - * @param {Function} function - * @param {ErrorConstructor} constructor - * @param {RegExp} regexp - * @param {String} message - * @see https://developer.mozilla.org/en/JavaScript/Reference/Global_Objects/Error#Error_types - * @api public - */ - - assert.Throw = function (fn, errt, errs, msg) { - if ('string' === typeof errt || errt instanceof RegExp) { - errs = errt; - errt = null; - } - - var assertErr = new Assertion(fn, msg).to.Throw(errt, errs); - return flag(assertErr, 'object'); - }; - - /** - * ### .doesNotThrow(function, [constructor/regexp], [message]) - * - * Asserts that `function` will _not_ throw an error that is an instance of - * `constructor`, or alternately that it will not throw an error with message - * matching `regexp`. - * - * assert.doesNotThrow(fn, Error, 'function does not throw'); - * - * @name doesNotThrow - * @param {Function} function - * @param {ErrorConstructor} constructor - * @param {RegExp} regexp - * @param {String} message - * @see https://developer.mozilla.org/en/JavaScript/Reference/Global_Objects/Error#Error_types - * @api public - */ - - assert.doesNotThrow = function (fn, type, msg) { - if ('string' === typeof type) { - msg = type; - type = null; - } - - new Assertion(fn, msg).to.not.Throw(type); - }; - - /** - * ### .operator(val1, operator, val2, [message]) - * - * Compares two values using `operator`. - * - * assert.operator(1, '<', 2, 'everything is ok'); - * assert.operator(1, '>', 2, 'this will fail'); - * - * @name operator - * @param {Mixed} val1 - * @param {String} operator - * @param {Mixed} val2 - * @param {String} message - * @api public - */ - - assert.operator = function (val, operator, val2, msg) { - if (!~['==', '===', '>', '>=', '<', '<=', '!=', '!=='].indexOf(operator)) { - throw new Error('Invalid operator "' + operator + '"'); - } - var test = new Assertion(eval(val + operator + val2), msg); - test.assert( - true === flag(test, 'object') - , 'expected ' + util.inspect(val) + ' to be ' + operator + ' ' + util.inspect(val2) - , 'expected ' + util.inspect(val) + ' to not be ' + operator + ' ' + util.inspect(val2) ); - }; - - /** - * ### .closeTo(actual, expected, delta, [message]) - * - * Asserts that the target is equal `expected`, to within a +/- `delta` range. - * - * assert.closeTo(1.5, 1, 0.5, 'numbers are close'); - * - * @name closeTo - * @param {Number} actual - * @param {Number} expected - * @param {Number} delta - * @param {String} message - * @api public - */ - - assert.closeTo = function (act, exp, delta, msg) { - new Assertion(act, msg).to.be.closeTo(exp, delta); - }; - - /** - * ### .sameMembers(set1, set2, [message]) - * - * Asserts that `set1` and `set2` have the same members. - * Order is not taken into account. - * - * assert.sameMembers([ 1, 2, 3 ], [ 2, 1, 3 ], 'same members'); - * - * @name sameMembers - * @param {Array} set1 - * @param {Array} set2 - * @param {String} message - * @api public - */ - - assert.sameMembers = function (set1, set2, msg) { - new Assertion(set1, msg).to.have.same.members(set2); - } - - /** - * ### .sameDeepMembers(set1, set2, [message]) - * - * Asserts that `set1` and `set2` have the same members - using a deep equality checking. - * Order is not taken into account. - * - * assert.sameDeepMembers([ {b: 3}, {a: 2}, {c: 5} ], [ {c: 5}, {b: 3}, {a: 2} ], 'same deep members'); - * - * @name sameDeepMembers - * @param {Array} set1 - * @param {Array} set2 - * @param {String} message - * @api public - */ - - assert.sameDeepMembers = function (set1, set2, msg) { - new Assertion(set1, msg).to.have.same.deep.members(set2); - } - - /** - * ### .includeMembers(superset, subset, [message]) - * - * Asserts that `subset` is included in `superset`. - * Order is not taken into account. - * - * assert.includeMembers([ 1, 2, 3 ], [ 2, 1 ], 'include members'); - * - * @name includeMembers - * @param {Array} superset - * @param {Array} subset - * @param {String} message - * @api public - */ - - assert.includeMembers = function (superset, subset, msg) { - new Assertion(superset, msg).to.include.members(subset); - } - - /** - * ### .changes(function, object, property) - * - * Asserts that a function changes the value of a property - * - * var obj = { val: 10 }; - * var fn = function() { obj.val = 22 }; - * assert.changes(fn, obj, 'val'); - * - * @name changes - * @param {Function} modifier function - * @param {Object} object - * @param {String} property name - * @param {String} message _optional_ - * @api public - */ - - assert.changes = function (fn, obj, prop) { - new Assertion(fn).to.change(obj, prop); - } - - /** - * ### .doesNotChange(function, object, property) - * - * Asserts that a function does not changes the value of a property - * - * var obj = { val: 10 }; - * var fn = function() { console.log('foo'); }; - * assert.doesNotChange(fn, obj, 'val'); - * - * @name doesNotChange - * @param {Function} modifier function - * @param {Object} object - * @param {String} property name - * @param {String} message _optional_ - * @api public - */ - - assert.doesNotChange = function (fn, obj, prop) { - new Assertion(fn).to.not.change(obj, prop); - } - - /** - * ### .increases(function, object, property) - * - * Asserts that a function increases an object property - * - * var obj = { val: 10 }; - * var fn = function() { obj.val = 13 }; - * assert.increases(fn, obj, 'val'); - * - * @name increases - * @param {Function} modifier function - * @param {Object} object - * @param {String} property name - * @param {String} message _optional_ - * @api public - */ - - assert.increases = function (fn, obj, prop) { - new Assertion(fn).to.increase(obj, prop); - } - - /** - * ### .doesNotIncrease(function, object, property) - * - * Asserts that a function does not increase object property - * - * var obj = { val: 10 }; - * var fn = function() { obj.val = 8 }; - * assert.doesNotIncrease(fn, obj, 'val'); - * - * @name doesNotIncrease - * @param {Function} modifier function - * @param {Object} object - * @param {String} property name - * @param {String} message _optional_ - * @api public - */ - - assert.doesNotIncrease = function (fn, obj, prop) { - new Assertion(fn).to.not.increase(obj, prop); - } - - /** - * ### .decreases(function, object, property) - * - * Asserts that a function decreases an object property - * - * var obj = { val: 10 }; - * var fn = function() { obj.val = 5 }; - * assert.decreases(fn, obj, 'val'); - * - * @name decreases - * @param {Function} modifier function - * @param {Object} object - * @param {String} property name - * @param {String} message _optional_ - * @api public - */ - - assert.decreases = function (fn, obj, prop) { - new Assertion(fn).to.decrease(obj, prop); - } - - /** - * ### .doesNotDecrease(function, object, property) - * - * Asserts that a function does not decreases an object property - * - * var obj = { val: 10 }; - * var fn = function() { obj.val = 15 }; - * assert.doesNotDecrease(fn, obj, 'val'); - * - * @name doesNotDecrease - * @param {Function} modifier function - * @param {Object} object - * @param {String} property name - * @param {String} message _optional_ - * @api public - */ - - assert.doesNotDecrease = function (fn, obj, prop) { - new Assertion(fn).to.not.decrease(obj, prop); - } - - /*! - * Undocumented / untested - */ - - assert.ifError = function (val, msg) { - new Assertion(val, msg).to.not.be.ok; - }; - - /*! - * Aliases. - */ - - (function alias(name, as){ - assert[as] = assert[name]; - return alias; - }) - ('Throw', 'throw') - ('Throw', 'throws'); -}; - -}); - -require.register("chai/lib/chai/interface/expect.js", function (exports, module) { -/*! - * chai - * Copyright(c) 2011-2014 Jake Luer - * MIT Licensed - */ - -module.exports = function (chai, util) { - chai.expect = function (val, message) { - return new chai.Assertion(val, message); - }; - - /** - * ### .fail(actual, expected, [message], [operator]) - * - * Throw a failure. - * - * @name fail - * @param {Mixed} actual - * @param {Mixed} expected - * @param {String} message - * @param {String} operator - * @api public - */ - - chai.expect.fail = function (actual, expected, message, operator) { - message = message || 'expect.fail()'; - throw new chai.AssertionError(message, { - actual: actual - , expected: expected - , operator: operator - }, chai.expect.fail); - }; -}; - -}); - -require.register("chai/lib/chai/interface/should.js", function (exports, module) { -/*! - * chai - * Copyright(c) 2011-2014 Jake Luer - * MIT Licensed - */ - -module.exports = function (chai, util) { - var Assertion = chai.Assertion; - - function loadShould () { - // explicitly define this method as function as to have it's name to include as `ssfi` - function shouldGetter() { - if (this instanceof String || this instanceof Number) { - return new Assertion(this.constructor(this), null, shouldGetter); - } else if (this instanceof Boolean) { - return new Assertion(this == true, null, shouldGetter); - } - return new Assertion(this, null, shouldGetter); - } - function shouldSetter(value) { - // See https://github.com/chaijs/chai/issues/86: this makes - // `whatever.should = someValue` actually set `someValue`, which is - // especially useful for `global.should = require('chai').should()`. - // - // Note that we have to use [[DefineProperty]] instead of [[Put]] - // since otherwise we would trigger this very setter! - Object.defineProperty(this, 'should', { - value: value, - enumerable: true, - configurable: true, - writable: true - }); - } - // modify Object.prototype to have `should` - Object.defineProperty(Object.prototype, 'should', { - set: shouldSetter - , get: shouldGetter - , configurable: true - }); - - var should = {}; - - /** - * ### .fail(actual, expected, [message], [operator]) - * - * Throw a failure. - * - * @name fail - * @param {Mixed} actual - * @param {Mixed} expected - * @param {String} message - * @param {String} operator - * @api public - */ - - should.fail = function (actual, expected, message, operator) { - message = message || 'should.fail()'; - throw new chai.AssertionError(message, { - actual: actual - , expected: expected - , operator: operator - }, should.fail); - }; - - should.equal = function (val1, val2, msg) { - new Assertion(val1, msg).to.equal(val2); - }; - - should.Throw = function (fn, errt, errs, msg) { - new Assertion(fn, msg).to.Throw(errt, errs); - }; - - should.exist = function (val, msg) { - new Assertion(val, msg).to.exist; - } - - // negation - should.not = {} - - should.not.equal = function (val1, val2, msg) { - new Assertion(val1, msg).to.not.equal(val2); - }; - - should.not.Throw = function (fn, errt, errs, msg) { - new Assertion(fn, msg).to.not.Throw(errt, errs); - }; - - should.not.exist = function (val, msg) { - new Assertion(val, msg).to.not.exist; - } - - should['throw'] = should['Throw']; - should.not['throw'] = should.not['Throw']; - - return should; - }; - - chai.should = loadShould; - chai.Should = loadShould; -}; - -}); - -require.register("chai/lib/chai/utils/addChainableMethod.js", function (exports, module) { -/*! - * Chai - addChainingMethod utility - * Copyright(c) 2012-2014 Jake Luer - * MIT Licensed - */ - -/*! - * Module dependencies - */ - -var transferFlags = require('chai/lib/chai/utils/transferFlags.js'); -var flag = require('chai/lib/chai/utils/flag.js'); -var config = require('chai/lib/chai/config.js'); - -/*! - * Module variables - */ - -// Check whether `__proto__` is supported -var hasProtoSupport = '__proto__' in Object; - -// Without `__proto__` support, this module will need to add properties to a function. -// However, some Function.prototype methods cannot be overwritten, -// and there seems no easy cross-platform way to detect them (@see chaijs/chai/issues/69). -var excludeNames = /^(?:length|name|arguments|caller)$/; - -// Cache `Function` properties -var call = Function.prototype.call, - apply = Function.prototype.apply; - -/** - * ### addChainableMethod (ctx, name, method, chainingBehavior) - * - * Adds a method to an object, such that the method can also be chained. - * - * utils.addChainableMethod(chai.Assertion.prototype, 'foo', function (str) { - * var obj = utils.flag(this, 'object'); - * new chai.Assertion(obj).to.be.equal(str); - * }); - * - * Can also be accessed directly from `chai.Assertion`. - * - * chai.Assertion.addChainableMethod('foo', fn, chainingBehavior); - * - * The result can then be used as both a method assertion, executing both `method` and - * `chainingBehavior`, or as a language chain, which only executes `chainingBehavior`. - * - * expect(fooStr).to.be.foo('bar'); - * expect(fooStr).to.be.foo.equal('foo'); - * - * @param {Object} ctx object to which the method is added - * @param {String} name of method to add - * @param {Function} method function to be used for `name`, when called - * @param {Function} chainingBehavior function to be called every time the property is accessed - * @name addChainableMethod - * @api public - */ - -module.exports = function (ctx, name, method, chainingBehavior) { - if (typeof chainingBehavior !== 'function') { - chainingBehavior = function () { }; - } - - var chainableBehavior = { - method: method - , chainingBehavior: chainingBehavior - }; - - // save the methods so we can overwrite them later, if we need to. - if (!ctx.__methods) { - ctx.__methods = {}; - } - ctx.__methods[name] = chainableBehavior; - - Object.defineProperty(ctx, name, - { get: function () { - chainableBehavior.chainingBehavior.call(this); - - var assert = function assert() { - var old_ssfi = flag(this, 'ssfi'); - if (old_ssfi && config.includeStack === false) - flag(this, 'ssfi', assert); - var result = chainableBehavior.method.apply(this, arguments); - return result === undefined ? this : result; - }; - - // Use `__proto__` if available - if (hasProtoSupport) { - // Inherit all properties from the object by replacing the `Function` prototype - var prototype = assert.__proto__ = Object.create(this); - // Restore the `call` and `apply` methods from `Function` - prototype.call = call; - prototype.apply = apply; - } - // Otherwise, redefine all properties (slow!) - else { - var asserterNames = Object.getOwnPropertyNames(ctx); - asserterNames.forEach(function (asserterName) { - if (!excludeNames.test(asserterName)) { - var pd = Object.getOwnPropertyDescriptor(ctx, asserterName); - Object.defineProperty(assert, asserterName, pd); - } - }); - } - - transferFlags(this, assert); - return assert; - } - , configurable: true - }); -}; - -}); - -require.register("chai/lib/chai/utils/addMethod.js", function (exports, module) { -/*! - * Chai - addMethod utility - * Copyright(c) 2012-2014 Jake Luer - * MIT Licensed - */ - -var config = require('chai/lib/chai/config.js'); - -/** - * ### .addMethod (ctx, name, method) - * - * Adds a method to the prototype of an object. - * - * utils.addMethod(chai.Assertion.prototype, 'foo', function (str) { - * var obj = utils.flag(this, 'object'); - * new chai.Assertion(obj).to.be.equal(str); - * }); - * - * Can also be accessed directly from `chai.Assertion`. - * - * chai.Assertion.addMethod('foo', fn); - * - * Then can be used as any other assertion. - * - * expect(fooStr).to.be.foo('bar'); - * - * @param {Object} ctx object to which the method is added - * @param {String} name of method to add - * @param {Function} method function to be used for name - * @name addMethod - * @api public - */ -var flag = require('chai/lib/chai/utils/flag.js'); - -module.exports = function (ctx, name, method) { - ctx[name] = function () { - var old_ssfi = flag(this, 'ssfi'); - if (old_ssfi && config.includeStack === false) - flag(this, 'ssfi', ctx[name]); - var result = method.apply(this, arguments); - return result === undefined ? this : result; - }; -}; - -}); - -require.register("chai/lib/chai/utils/addProperty.js", function (exports, module) { -/*! - * Chai - addProperty utility - * Copyright(c) 2012-2014 Jake Luer - * MIT Licensed - */ - -/** - * ### addProperty (ctx, name, getter) - * - * Adds a property to the prototype of an object. - * - * utils.addProperty(chai.Assertion.prototype, 'foo', function () { - * var obj = utils.flag(this, 'object'); - * new chai.Assertion(obj).to.be.instanceof(Foo); - * }); - * - * Can also be accessed directly from `chai.Assertion`. - * - * chai.Assertion.addProperty('foo', fn); - * - * Then can be used as any other assertion. - * - * expect(myFoo).to.be.foo; - * - * @param {Object} ctx object to which the property is added - * @param {String} name of property to add - * @param {Function} getter function to be used for name - * @name addProperty - * @api public - */ - -module.exports = function (ctx, name, getter) { - Object.defineProperty(ctx, name, - { get: function () { - var result = getter.call(this); - return result === undefined ? this : result; - } - , configurable: true - }); -}; - -}); - -require.register("chai/lib/chai/utils/flag.js", function (exports, module) { -/*! - * Chai - flag utility - * Copyright(c) 2012-2014 Jake Luer - * MIT Licensed - */ - -/** - * ### flag(object, key, [value]) - * - * Get or set a flag value on an object. If a - * value is provided it will be set, else it will - * return the currently set value or `undefined` if - * the value is not set. - * - * utils.flag(this, 'foo', 'bar'); // setter - * utils.flag(this, 'foo'); // getter, returns `bar` - * - * @param {Object} object constructed Assertion - * @param {String} key - * @param {Mixed} value (optional) - * @name flag - * @api private - */ - -module.exports = function (obj, key, value) { - var flags = obj.__flags || (obj.__flags = Object.create(null)); - if (arguments.length === 3) { - flags[key] = value; - } else { - return flags[key]; - } -}; - -}); - -require.register("chai/lib/chai/utils/getActual.js", function (exports, module) { -/*! - * Chai - getActual utility - * Copyright(c) 2012-2014 Jake Luer - * MIT Licensed - */ - -/** - * # getActual(object, [actual]) - * - * Returns the `actual` value for an Assertion - * - * @param {Object} object (constructed Assertion) - * @param {Arguments} chai.Assertion.prototype.assert arguments - */ - -module.exports = function (obj, args) { - return args.length > 4 ? args[4] : obj._obj; -}; - -}); - -require.register("chai/lib/chai/utils/getEnumerableProperties.js", function (exports, module) { -/*! - * Chai - getEnumerableProperties utility - * Copyright(c) 2012-2014 Jake Luer - * MIT Licensed - */ - -/** - * ### .getEnumerableProperties(object) - * - * This allows the retrieval of enumerable property names of an object, - * inherited or not. - * - * @param {Object} object - * @returns {Array} - * @name getEnumerableProperties - * @api public - */ - -module.exports = function getEnumerableProperties(object) { - var result = []; - for (var name in object) { - result.push(name); - } - return result; -}; - -}); - -require.register("chai/lib/chai/utils/getMessage.js", function (exports, module) { -/*! - * Chai - message composition utility - * Copyright(c) 2012-2014 Jake Luer - * MIT Licensed - */ - -/*! - * Module dependancies - */ - -var flag = require('chai/lib/chai/utils/flag.js') - , getActual = require('chai/lib/chai/utils/getActual.js') - , inspect = require('chai/lib/chai/utils/inspect.js') - , objDisplay = require('chai/lib/chai/utils/objDisplay.js'); - -/** - * ### .getMessage(object, message, negateMessage) - * - * Construct the error message based on flags - * and template tags. Template tags will return - * a stringified inspection of the object referenced. - * - * Message template tags: - * - `#{this}` current asserted object - * - `#{act}` actual value - * - `#{exp}` expected value - * - * @param {Object} object (constructed Assertion) - * @param {Arguments} chai.Assertion.prototype.assert arguments - * @name getMessage - * @api public - */ - -module.exports = function (obj, args) { - var negate = flag(obj, 'negate') - , val = flag(obj, 'object') - , expected = args[3] - , actual = getActual(obj, args) - , msg = negate ? args[2] : args[1] - , flagMsg = flag(obj, 'message'); - - if(typeof msg === "function") msg = msg(); - msg = msg || ''; - msg = msg - .replace(/#{this}/g, objDisplay(val)) - .replace(/#{act}/g, objDisplay(actual)) - .replace(/#{exp}/g, objDisplay(expected)); - - return flagMsg ? flagMsg + ': ' + msg : msg; -}; - -}); - -require.register("chai/lib/chai/utils/getName.js", function (exports, module) { -/*! - * Chai - getName utility - * Copyright(c) 2012-2014 Jake Luer - * MIT Licensed - */ - -/** - * # getName(func) - * - * Gets the name of a function, in a cross-browser way. - * - * @param {Function} a function (usually a constructor) - */ - -module.exports = function (func) { - if (func.name) return func.name; - - var match = /^\s?function ([^(]*)\(/.exec(func); - return match && match[1] ? match[1] : ""; -}; - -}); - -require.register("chai/lib/chai/utils/getPathValue.js", function (exports, module) { -/*! - * Chai - getPathValue utility - * Copyright(c) 2012-2014 Jake Luer - * @see https://github.com/logicalparadox/filtr - * MIT Licensed - */ - -var getPathInfo = require('chai/lib/chai/utils/getPathInfo.js'); - -/** - * ### .getPathValue(path, object) - * - * This allows the retrieval of values in an - * object given a string path. - * - * var obj = { - * prop1: { - * arr: ['a', 'b', 'c'] - * , str: 'Hello' - * } - * , prop2: { - * arr: [ { nested: 'Universe' } ] - * , str: 'Hello again!' - * } - * } - * - * The following would be the results. - * - * getPathValue('prop1.str', obj); // Hello - * getPathValue('prop1.att[2]', obj); // b - * getPathValue('prop2.arr[0].nested', obj); // Universe - * - * @param {String} path - * @param {Object} object - * @returns {Object} value or `undefined` - * @name getPathValue - * @api public - */ -module.exports = function(path, obj) { - var info = getPathInfo(path, obj); - return info.value; -}; - -}); - -require.register("chai/lib/chai/utils/getPathInfo.js", function (exports, module) { -/*! - * Chai - getPathInfo utility - * Copyright(c) 2012-2014 Jake Luer - * MIT Licensed - */ - -var hasProperty = require('chai/lib/chai/utils/hasProperty.js'); - -/** - * ### .getPathInfo(path, object) - * - * This allows the retrieval of property info in an - * object given a string path. - * - * The path info consists of an object with the - * following properties: - * - * * parent - The parent object of the property referenced by `path` - * * name - The name of the final property, a number if it was an array indexer - * * value - The value of the property, if it exists, otherwise `undefined` - * * exists - Whether the property exists or not - * - * @param {String} path - * @param {Object} object - * @returns {Object} info - * @name getPathInfo - * @api public - */ - -module.exports = function getPathInfo(path, obj) { - var parsed = parsePath(path), - last = parsed[parsed.length - 1]; - - var info = { - parent: _getPathValue(parsed, obj, parsed.length - 1), - name: last.p || last.i, - value: _getPathValue(parsed, obj), - }; - info.exists = hasProperty(info.name, info.parent); - - return info; -}; - - -/*! - * ## parsePath(path) - * - * Helper function used to parse string object - * paths. Use in conjunction with `_getPathValue`. - * - * var parsed = parsePath('myobject.property.subprop'); - * - * ### Paths: - * - * * Can be as near infinitely deep and nested - * * Arrays are also valid using the formal `myobject.document[3].property`. - * - * @param {String} path - * @returns {Object} parsed - * @api private - */ - -function parsePath (path) { - var str = path.replace(/\[/g, '.[') - , parts = str.match(/(\\\.|[^.]+?)+/g); - return parts.map(function (value) { - var re = /\[(\d+)\]$/ - , mArr = re.exec(value); - if (mArr) return { i: parseFloat(mArr[1]) }; - else return { p: value }; - }); -} - - -/*! - * ## _getPathValue(parsed, obj) - * - * Helper companion function for `.parsePath` that returns - * the value located at the parsed address. - * - * var value = getPathValue(parsed, obj); - * - * @param {Object} parsed definition from `parsePath`. - * @param {Object} object to search against - * @param {Number} object to search against - * @returns {Object|Undefined} value - * @api private - */ - -function _getPathValue (parsed, obj, index) { - var tmp = obj - , res; - - index = (index === undefined ? parsed.length : index); - - for (var i = 0, l = index; i < l; i++) { - var part = parsed[i]; - if (tmp) { - if ('undefined' !== typeof part.p) - tmp = tmp[part.p]; - else if ('undefined' !== typeof part.i) - tmp = tmp[part.i]; - if (i == (l - 1)) res = tmp; - } else { - res = undefined; - } - } - return res; -} - -}); - -require.register("chai/lib/chai/utils/hasProperty.js", function (exports, module) { -/*! - * Chai - hasProperty utility - * Copyright(c) 2012-2014 Jake Luer - * MIT Licensed - */ - -var type = require('chai/lib/chai/utils/type.js'); - -/** - * ### .hasProperty(object, name) - * - * This allows checking whether an object has - * named property or numeric array index. - * - * Basically does the same thing as the `in` - * operator but works properly with natives - * and null/undefined values. - * - * var obj = { - * arr: ['a', 'b', 'c'] - * , str: 'Hello' - * } - * - * The following would be the results. - * - * hasProperty('str', obj); // true - * hasProperty('constructor', obj); // true - * hasProperty('bar', obj); // false - * - * hasProperty('length', obj.str); // true - * hasProperty(1, obj.str); // true - * hasProperty(5, obj.str); // false - * - * hasProperty('length', obj.arr); // true - * hasProperty(2, obj.arr); // true - * hasProperty(3, obj.arr); // false - * - * @param {Objuect} object - * @param {String|Number} name - * @returns {Boolean} whether it exists - * @name getPathInfo - * @api public - */ - -var literals = { - 'number': Number - , 'string': String -}; - -module.exports = function hasProperty(name, obj) { - var ot = type(obj); - - // Bad Object, obviously no props at all - if(ot === 'null' || ot === 'undefined') - return false; - - // The `in` operator does not work with certain literals - // box these before the check - if(literals[ot] && typeof obj !== 'object') - obj = new literals[ot](obj); - - return name in obj; -}; - -}); - -require.register("chai/lib/chai/utils/getProperties.js", function (exports, module) { -/*! - * Chai - getProperties utility - * Copyright(c) 2012-2014 Jake Luer - * MIT Licensed - */ - -/** - * ### .getProperties(object) - * - * This allows the retrieval of property names of an object, enumerable or not, - * inherited or not. - * - * @param {Object} object - * @returns {Array} - * @name getProperties - * @api public - */ - -module.exports = function getProperties(object) { - var result = Object.getOwnPropertyNames(subject); - - function addProperty(property) { - if (result.indexOf(property) === -1) { - result.push(property); - } - } - - var proto = Object.getPrototypeOf(subject); - while (proto !== null) { - Object.getOwnPropertyNames(proto).forEach(addProperty); - proto = Object.getPrototypeOf(proto); - } - - return result; -}; - -}); - -require.register("chai/lib/chai/utils/index.js", function (exports, module) { -/*! - * chai - * Copyright(c) 2011 Jake Luer - * MIT Licensed - */ - -/*! - * Main exports - */ - -var exports = module.exports = {}; - -/*! - * test utility - */ - -exports.test = require('chai/lib/chai/utils/test.js'); - -/*! - * type utility - */ - -exports.type = require('chai/lib/chai/utils/type.js'); - -/*! - * message utility - */ - -exports.getMessage = require('chai/lib/chai/utils/getMessage.js'); - -/*! - * actual utility - */ - -exports.getActual = require('chai/lib/chai/utils/getActual.js'); - -/*! - * Inspect util - */ - -exports.inspect = require('chai/lib/chai/utils/inspect.js'); - -/*! - * Object Display util - */ - -exports.objDisplay = require('chai/lib/chai/utils/objDisplay.js'); - -/*! - * Flag utility - */ - -exports.flag = require('chai/lib/chai/utils/flag.js'); - -/*! - * Flag transferring utility - */ - -exports.transferFlags = require('chai/lib/chai/utils/transferFlags.js'); - -/*! - * Deep equal utility - */ - -exports.eql = require('chaijs~deep-eql@0.1.3'); - -/*! - * Deep path value - */ - -exports.getPathValue = require('chai/lib/chai/utils/getPathValue.js'); - -/*! - * Deep path info - */ - -exports.getPathInfo = require('chai/lib/chai/utils/getPathInfo.js'); - -/*! - * Check if a property exists - */ - -exports.hasProperty = require('chai/lib/chai/utils/hasProperty.js'); - -/*! - * Function name - */ - -exports.getName = require('chai/lib/chai/utils/getName.js'); - -/*! - * add Property - */ - -exports.addProperty = require('chai/lib/chai/utils/addProperty.js'); - -/*! - * add Method - */ - -exports.addMethod = require('chai/lib/chai/utils/addMethod.js'); - -/*! - * overwrite Property - */ - -exports.overwriteProperty = require('chai/lib/chai/utils/overwriteProperty.js'); - -/*! - * overwrite Method - */ - -exports.overwriteMethod = require('chai/lib/chai/utils/overwriteMethod.js'); - -/*! - * Add a chainable method - */ - -exports.addChainableMethod = require('chai/lib/chai/utils/addChainableMethod.js'); - -/*! - * Overwrite chainable method - */ - -exports.overwriteChainableMethod = require('chai/lib/chai/utils/overwriteChainableMethod.js'); - - -}); - -require.register("chai/lib/chai/utils/inspect.js", function (exports, module) { -// This is (almost) directly from Node.js utils -// https://github.com/joyent/node/blob/f8c335d0caf47f16d31413f89aa28eda3878e3aa/lib/util.js - -var getName = require('chai/lib/chai/utils/getName.js'); -var getProperties = require('chai/lib/chai/utils/getProperties.js'); -var getEnumerableProperties = require('chai/lib/chai/utils/getEnumerableProperties.js'); - -module.exports = inspect; - -/** - * Echos the value of a value. Trys to print the value out - * in the best way possible given the different types. - * - * @param {Object} obj The object to print out. - * @param {Boolean} showHidden Flag that shows hidden (not enumerable) - * properties of objects. - * @param {Number} depth Depth in which to descend in object. Default is 2. - * @param {Boolean} colors Flag to turn on ANSI escape codes to color the - * output. Default is false (no coloring). - */ -function inspect(obj, showHidden, depth, colors) { - var ctx = { - showHidden: showHidden, - seen: [], - stylize: function (str) { return str; } - }; - return formatValue(ctx, obj, (typeof depth === 'undefined' ? 2 : depth)); -} - -// Returns true if object is a DOM element. -var isDOMElement = function (object) { - if (typeof HTMLElement === 'object') { - return object instanceof HTMLElement; - } else { - return object && - typeof object === 'object' && - object.nodeType === 1 && - typeof object.nodeName === 'string'; - } -}; - -function formatValue(ctx, value, recurseTimes) { - // Provide a hook for user-specified inspect functions. - // Check that value is an object with an inspect function on it - if (value && typeof value.inspect === 'function' && - // Filter out the util module, it's inspect function is special - value.inspect !== exports.inspect && - // Also filter out any prototype objects using the circular check. - !(value.constructor && value.constructor.prototype === value)) { - var ret = value.inspect(recurseTimes); - if (typeof ret !== 'string') { - ret = formatValue(ctx, ret, recurseTimes); - } - return ret; - } - - // Primitive types cannot have properties - var primitive = formatPrimitive(ctx, value); - if (primitive) { - return primitive; - } - - // If this is a DOM element, try to get the outer HTML. - if (isDOMElement(value)) { - if ('outerHTML' in value) { - return value.outerHTML; - // This value does not have an outerHTML attribute, - // it could still be an XML element - } else { - // Attempt to serialize it - try { - if (document.xmlVersion) { - var xmlSerializer = new XMLSerializer(); - return xmlSerializer.serializeToString(value); - } else { - // Firefox 11- do not support outerHTML - // It does, however, support innerHTML - // Use the following to render the element - var ns = "http://www.w3.org/1999/xhtml"; - var container = document.createElementNS(ns, '_'); - - container.appendChild(value.cloneNode(false)); - html = container.innerHTML - .replace('><', '>' + value.innerHTML + '<'); - container.innerHTML = ''; - return html; - } - } catch (err) { - // This could be a non-native DOM implementation, - // continue with the normal flow: - // printing the element as if it is an object. - } - } - } - - // Look up the keys of the object. - var visibleKeys = getEnumerableProperties(value); - var keys = ctx.showHidden ? getProperties(value) : visibleKeys; - - // Some type of object without properties can be shortcutted. - // In IE, errors have a single `stack` property, or if they are vanilla `Error`, - // a `stack` plus `description` property; ignore those for consistency. - if (keys.length === 0 || (isError(value) && ( - (keys.length === 1 && keys[0] === 'stack') || - (keys.length === 2 && keys[0] === 'description' && keys[1] === 'stack') - ))) { - if (typeof value === 'function') { - var name = getName(value); - var nameSuffix = name ? ': ' + name : ''; - return ctx.stylize('[Function' + nameSuffix + ']', 'special'); - } - if (isRegExp(value)) { - return ctx.stylize(RegExp.prototype.toString.call(value), 'regexp'); - } - if (isDate(value)) { - return ctx.stylize(Date.prototype.toUTCString.call(value), 'date'); - } - if (isError(value)) { - return formatError(value); - } - } - - var base = '', array = false, braces = ['{', '}']; - - // Make Array say that they are Array - if (isArray(value)) { - array = true; - braces = ['[', ']']; - } - - // Make functions say that they are functions - if (typeof value === 'function') { - var name = getName(value); - var nameSuffix = name ? ': ' + name : ''; - base = ' [Function' + nameSuffix + ']'; - } - - // Make RegExps say that they are RegExps - if (isRegExp(value)) { - base = ' ' + RegExp.prototype.toString.call(value); - } - - // Make dates with properties first say the date - if (isDate(value)) { - base = ' ' + Date.prototype.toUTCString.call(value); - } - - // Make error with message first say the error - if (isError(value)) { - return formatError(value); - } - - if (keys.length === 0 && (!array || value.length == 0)) { - return braces[0] + base + braces[1]; - } - - if (recurseTimes < 0) { - if (isRegExp(value)) { - return ctx.stylize(RegExp.prototype.toString.call(value), 'regexp'); - } else { - return ctx.stylize('[Object]', 'special'); - } - } - - ctx.seen.push(value); - - var output; - if (array) { - output = formatArray(ctx, value, recurseTimes, visibleKeys, keys); - } else { - output = keys.map(function(key) { - return formatProperty(ctx, value, recurseTimes, visibleKeys, key, array); - }); - } - - ctx.seen.pop(); - - return reduceToSingleString(output, base, braces); -} - - -function formatPrimitive(ctx, value) { - switch (typeof value) { - case 'undefined': - return ctx.stylize('undefined', 'undefined'); - - case 'string': - var simple = '\'' + JSON.stringify(value).replace(/^"|"$/g, '') - .replace(/'/g, "\\'") - .replace(/\\"/g, '"') + '\''; - return ctx.stylize(simple, 'string'); - - case 'number': - if (value === 0 && (1/value) === -Infinity) { - return ctx.stylize('-0', 'number'); - } - return ctx.stylize('' + value, 'number'); - - case 'boolean': - return ctx.stylize('' + value, 'boolean'); - } - // For some reason typeof null is "object", so special case here. - if (value === null) { - return ctx.stylize('null', 'null'); - } -} - - -function formatError(value) { - return '[' + Error.prototype.toString.call(value) + ']'; -} - - -function formatArray(ctx, value, recurseTimes, visibleKeys, keys) { - var output = []; - for (var i = 0, l = value.length; i < l; ++i) { - if (Object.prototype.hasOwnProperty.call(value, String(i))) { - output.push(formatProperty(ctx, value, recurseTimes, visibleKeys, - String(i), true)); - } else { - output.push(''); - } - } - keys.forEach(function(key) { - if (!key.match(/^\d+$/)) { - output.push(formatProperty(ctx, value, recurseTimes, visibleKeys, - key, true)); - } - }); - return output; -} - - -function formatProperty(ctx, value, recurseTimes, visibleKeys, key, array) { - var name, str; - if (value.__lookupGetter__) { - if (value.__lookupGetter__(key)) { - if (value.__lookupSetter__(key)) { - str = ctx.stylize('[Getter/Setter]', 'special'); - } else { - str = ctx.stylize('[Getter]', 'special'); - } - } else { - if (value.__lookupSetter__(key)) { - str = ctx.stylize('[Setter]', 'special'); - } - } - } - if (visibleKeys.indexOf(key) < 0) { - name = '[' + key + ']'; - } - if (!str) { - if (ctx.seen.indexOf(value[key]) < 0) { - if (recurseTimes === null) { - str = formatValue(ctx, value[key], null); - } else { - str = formatValue(ctx, value[key], recurseTimes - 1); - } - if (str.indexOf('\n') > -1) { - if (array) { - str = str.split('\n').map(function(line) { - return ' ' + line; - }).join('\n').substr(2); - } else { - str = '\n' + str.split('\n').map(function(line) { - return ' ' + line; - }).join('\n'); - } - } - } else { - str = ctx.stylize('[Circular]', 'special'); - } - } - if (typeof name === 'undefined') { - if (array && key.match(/^\d+$/)) { - return str; - } - name = JSON.stringify('' + key); - if (name.match(/^"([a-zA-Z_][a-zA-Z_0-9]*)"$/)) { - name = name.substr(1, name.length - 2); - name = ctx.stylize(name, 'name'); - } else { - name = name.replace(/'/g, "\\'") - .replace(/\\"/g, '"') - .replace(/(^"|"$)/g, "'"); - name = ctx.stylize(name, 'string'); - } - } - - return name + ': ' + str; -} - - -function reduceToSingleString(output, base, braces) { - var numLinesEst = 0; - var length = output.reduce(function(prev, cur) { - numLinesEst++; - if (cur.indexOf('\n') >= 0) numLinesEst++; - return prev + cur.length + 1; - }, 0); - - if (length > 60) { - return braces[0] + - (base === '' ? '' : base + '\n ') + - ' ' + - output.join(',\n ') + - ' ' + - braces[1]; - } - - return braces[0] + base + ' ' + output.join(', ') + ' ' + braces[1]; -} - -function isArray(ar) { - return Array.isArray(ar) || - (typeof ar === 'object' && objectToString(ar) === '[object Array]'); -} - -function isRegExp(re) { - return typeof re === 'object' && objectToString(re) === '[object RegExp]'; -} - -function isDate(d) { - return typeof d === 'object' && objectToString(d) === '[object Date]'; -} - -function isError(e) { - return typeof e === 'object' && objectToString(e) === '[object Error]'; -} - -function objectToString(o) { - return Object.prototype.toString.call(o); -} - -}); - -require.register("chai/lib/chai/utils/objDisplay.js", function (exports, module) { -/*! - * Chai - flag utility - * Copyright(c) 2012-2014 Jake Luer - * MIT Licensed - */ - -/*! - * Module dependancies - */ - -var inspect = require('chai/lib/chai/utils/inspect.js'); -var config = require('chai/lib/chai/config.js'); - -/** - * ### .objDisplay (object) - * - * Determines if an object or an array matches - * criteria to be inspected in-line for error - * messages or should be truncated. - * - * @param {Mixed} javascript object to inspect - * @name objDisplay - * @api public - */ - -module.exports = function (obj) { - var str = inspect(obj) - , type = Object.prototype.toString.call(obj); - - if (config.truncateThreshold && str.length >= config.truncateThreshold) { - if (type === '[object Function]') { - return !obj.name || obj.name === '' - ? '[Function]' - : '[Function: ' + obj.name + ']'; - } else if (type === '[object Array]') { - return '[ Array(' + obj.length + ') ]'; - } else if (type === '[object Object]') { - var keys = Object.keys(obj) - , kstr = keys.length > 2 - ? keys.splice(0, 2).join(', ') + ', ...' - : keys.join(', '); - return '{ Object (' + kstr + ') }'; - } else { - return str; - } - } else { - return str; - } -}; - -}); - -require.register("chai/lib/chai/utils/overwriteMethod.js", function (exports, module) { -/*! - * Chai - overwriteMethod utility - * Copyright(c) 2012-2014 Jake Luer - * MIT Licensed - */ - -/** - * ### overwriteMethod (ctx, name, fn) - * - * Overwites an already existing method and provides - * access to previous function. Must return function - * to be used for name. - * - * utils.overwriteMethod(chai.Assertion.prototype, 'equal', function (_super) { - * return function (str) { - * var obj = utils.flag(this, 'object'); - * if (obj instanceof Foo) { - * new chai.Assertion(obj.value).to.equal(str); - * } else { - * _super.apply(this, arguments); - * } - * } - * }); - * - * Can also be accessed directly from `chai.Assertion`. - * - * chai.Assertion.overwriteMethod('foo', fn); - * - * Then can be used as any other assertion. - * - * expect(myFoo).to.equal('bar'); - * - * @param {Object} ctx object whose method is to be overwritten - * @param {String} name of method to overwrite - * @param {Function} method function that returns a function to be used for name - * @name overwriteMethod - * @api public - */ - -module.exports = function (ctx, name, method) { - var _method = ctx[name] - , _super = function () { return this; }; - - if (_method && 'function' === typeof _method) - _super = _method; - - ctx[name] = function () { - var result = method(_super).apply(this, arguments); - return result === undefined ? this : result; - } -}; - -}); - -require.register("chai/lib/chai/utils/overwriteProperty.js", function (exports, module) { -/*! - * Chai - overwriteProperty utility - * Copyright(c) 2012-2014 Jake Luer - * MIT Licensed - */ - -/** - * ### overwriteProperty (ctx, name, fn) - * - * Overwites an already existing property getter and provides - * access to previous value. Must return function to use as getter. - * - * utils.overwriteProperty(chai.Assertion.prototype, 'ok', function (_super) { - * return function () { - * var obj = utils.flag(this, 'object'); - * if (obj instanceof Foo) { - * new chai.Assertion(obj.name).to.equal('bar'); - * } else { - * _super.call(this); - * } - * } - * }); - * - * - * Can also be accessed directly from `chai.Assertion`. - * - * chai.Assertion.overwriteProperty('foo', fn); - * - * Then can be used as any other assertion. - * - * expect(myFoo).to.be.ok; - * - * @param {Object} ctx object whose property is to be overwritten - * @param {String} name of property to overwrite - * @param {Function} getter function that returns a getter function to be used for name - * @name overwriteProperty - * @api public - */ - -module.exports = function (ctx, name, getter) { - var _get = Object.getOwnPropertyDescriptor(ctx, name) - , _super = function () {}; - - if (_get && 'function' === typeof _get.get) - _super = _get.get - - Object.defineProperty(ctx, name, - { get: function () { - var result = getter(_super).call(this); - return result === undefined ? this : result; - } - , configurable: true - }); -}; - -}); - -require.register("chai/lib/chai/utils/overwriteChainableMethod.js", function (exports, module) { -/*! - * Chai - overwriteChainableMethod utility - * Copyright(c) 2012-2014 Jake Luer - * MIT Licensed - */ - -/** - * ### overwriteChainableMethod (ctx, name, method, chainingBehavior) - * - * Overwites an already existing chainable method - * and provides access to the previous function or - * property. Must return functions to be used for - * name. - * - * utils.overwriteChainableMethod(chai.Assertion.prototype, 'length', - * function (_super) { - * } - * , function (_super) { - * } - * ); - * - * Can also be accessed directly from `chai.Assertion`. - * - * chai.Assertion.overwriteChainableMethod('foo', fn, fn); - * - * Then can be used as any other assertion. - * - * expect(myFoo).to.have.length(3); - * expect(myFoo).to.have.length.above(3); - * - * @param {Object} ctx object whose method / property is to be overwritten - * @param {String} name of method / property to overwrite - * @param {Function} method function that returns a function to be used for name - * @param {Function} chainingBehavior function that returns a function to be used for property - * @name overwriteChainableMethod - * @api public - */ - -module.exports = function (ctx, name, method, chainingBehavior) { - var chainableBehavior = ctx.__methods[name]; - - var _chainingBehavior = chainableBehavior.chainingBehavior; - chainableBehavior.chainingBehavior = function () { - var result = chainingBehavior(_chainingBehavior).call(this); - return result === undefined ? this : result; - }; - - var _method = chainableBehavior.method; - chainableBehavior.method = function () { - var result = method(_method).apply(this, arguments); - return result === undefined ? this : result; - }; -}; - -}); - -require.register("chai/lib/chai/utils/test.js", function (exports, module) { -/*! - * Chai - test utility - * Copyright(c) 2012-2014 Jake Luer - * MIT Licensed - */ - -/*! - * Module dependancies - */ - -var flag = require('chai/lib/chai/utils/flag.js'); - -/** - * # test(object, expression) - * - * Test and object for expression. - * - * @param {Object} object (constructed Assertion) - * @param {Arguments} chai.Assertion.prototype.assert arguments - */ - -module.exports = function (obj, args) { - var negate = flag(obj, 'negate') - , expr = args[0]; - return negate ? !expr : expr; -}; - -}); - -require.register("chai/lib/chai/utils/transferFlags.js", function (exports, module) { -/*! - * Chai - transferFlags utility - * Copyright(c) 2012-2014 Jake Luer - * MIT Licensed - */ - -/** - * ### transferFlags(assertion, object, includeAll = true) - * - * Transfer all the flags for `assertion` to `object`. If - * `includeAll` is set to `false`, then the base Chai - * assertion flags (namely `object`, `ssfi`, and `message`) - * will not be transferred. - * - * - * var newAssertion = new Assertion(); - * utils.transferFlags(assertion, newAssertion); - * - * var anotherAsseriton = new Assertion(myObj); - * utils.transferFlags(assertion, anotherAssertion, false); - * - * @param {Assertion} assertion the assertion to transfer the flags from - * @param {Object} object the object to transfer the flags to; usually a new assertion - * @param {Boolean} includeAll - * @name transferFlags - * @api private - */ - -module.exports = function (assertion, object, includeAll) { - var flags = assertion.__flags || (assertion.__flags = Object.create(null)); - - if (!object.__flags) { - object.__flags = Object.create(null); - } - - includeAll = arguments.length === 3 ? includeAll : true; - - for (var flag in flags) { - if (includeAll || - (flag !== 'object' && flag !== 'ssfi' && flag != 'message')) { - object.__flags[flag] = flags[flag]; - } - } -}; - -}); - -require.register("chai/lib/chai/utils/type.js", function (exports, module) { -/*! - * Chai - type utility - * Copyright(c) 2012-2014 Jake Luer - * MIT Licensed - */ - -/*! - * Detectable javascript natives - */ - -var natives = { - '[object Arguments]': 'arguments' - , '[object Array]': 'array' - , '[object Date]': 'date' - , '[object Function]': 'function' - , '[object Number]': 'number' - , '[object RegExp]': 'regexp' - , '[object String]': 'string' -}; - -/** - * ### type(object) - * - * Better implementation of `typeof` detection that can - * be used cross-browser. Handles the inconsistencies of - * Array, `null`, and `undefined` detection. - * - * utils.type({}) // 'object' - * utils.type(null) // `null' - * utils.type(undefined) // `undefined` - * utils.type([]) // `array` - * - * @param {Mixed} object to detect type of - * @name type - * @api private - */ - -module.exports = function (obj) { - var str = Object.prototype.toString.call(obj); - if (natives[str]) return natives[str]; - if (obj === null) return 'null'; - if (obj === undefined) return 'undefined'; - if (obj === Object(obj)) return 'object'; - return typeof obj; -}; - -}); - -if (typeof exports == "object") { - module.exports = require("chai"); -} else if (typeof define == "function" && define.amd) { - define("chai", [], function(){ return require("chai"); }); -} else { - (this || window)["chai"] = require("chai"); -} -})() \ No newline at end of file diff --git a/test/lib/mocha.js b/test/lib/mocha.js deleted file mode 100755 index 061ee3cf52b..00000000000 --- a/test/lib/mocha.js +++ /dev/null @@ -1,4597 +0,0 @@ -;(function(){ - - -// CommonJS require() - -function require(p){ - var path = require.resolve(p) - , mod = require.modules[path]; - if (!mod) throw new Error('failed to require "' + p + '"'); - if (!mod.exports) { - mod.exports = {}; - mod.call(mod.exports, mod, mod.exports, require.relative(path)); - } - return mod.exports; - } - -require.modules = {}; - -require.resolve = function (path){ - var orig = path - , reg = path + '.js' - , index = path + '/index.js'; - return require.modules[reg] && reg - || require.modules[index] && index - || orig; - }; - -require.register = function (path, fn){ - require.modules[path] = fn; - }; - -require.relative = function (parent) { - return function(p){ - if ('.' != p.charAt(0)) return require(p); - - var path = parent.split('/') - , segs = p.split('/'); - path.pop(); - - for (var i = 0; i < segs.length; i++) { - var seg = segs[i]; - if ('..' == seg) path.pop(); - else if ('.' != seg) path.push(seg); - } - - return require(path.join('/')); - }; - }; - - -require.register("browser/debug.js", function(module, exports, require){ - -module.exports = function(type){ - return function(){ - - } -}; -}); // module: browser/debug.js - -require.register("browser/diff.js", function(module, exports, require){ - -}); // module: browser/diff.js - -require.register("browser/events.js", function(module, exports, require){ - -/** - * Module exports. - */ - -exports.EventEmitter = EventEmitter; - -/** - * Check if `obj` is an array. - */ - -function isArray(obj) { - return '[object Array]' == {}.toString.call(obj); -} - -/** - * Event emitter constructor. - * - * @api public - */ - -function EventEmitter(){}; - -/** - * Adds a listener. - * - * @api public - */ - -EventEmitter.prototype.on = function (name, fn) { - if (!this.$events) { - this.$events = {}; - } - - if (!this.$events[name]) { - this.$events[name] = fn; - } else if (isArray(this.$events[name])) { - this.$events[name].push(fn); - } else { - this.$events[name] = [this.$events[name], fn]; - } - - return this; -}; - -EventEmitter.prototype.addListener = EventEmitter.prototype.on; - -/** - * Adds a volatile listener. - * - * @api public - */ - -EventEmitter.prototype.once = function (name, fn) { - var self = this; - - function on () { - self.removeListener(name, on); - fn.apply(this, arguments); - }; - - on.listener = fn; - this.on(name, on); - - return this; -}; - -/** - * Removes a listener. - * - * @api public - */ - -EventEmitter.prototype.removeListener = function (name, fn) { - if (this.$events && this.$events[name]) { - var list = this.$events[name]; - - if (isArray(list)) { - var pos = -1; - - for (var i = 0, l = list.length; i < l; i++) { - if (list[i] === fn || (list[i].listener && list[i].listener === fn)) { - pos = i; - break; - } - } - - if (pos < 0) { - return this; - } - - list.splice(pos, 1); - - if (!list.length) { - delete this.$events[name]; - } - } else if (list === fn || (list.listener && list.listener === fn)) { - delete this.$events[name]; - } - } - - return this; -}; - -/** - * Removes all listeners for an event. - * - * @api public - */ - -EventEmitter.prototype.removeAllListeners = function (name) { - if (name === undefined) { - this.$events = {}; - return this; - } - - if (this.$events && this.$events[name]) { - this.$events[name] = null; - } - - return this; -}; - -/** - * Gets all listeners for a certain event. - * - * @api public - */ - -EventEmitter.prototype.listeners = function (name) { - if (!this.$events) { - this.$events = {}; - } - - if (!this.$events[name]) { - this.$events[name] = []; - } - - if (!isArray(this.$events[name])) { - this.$events[name] = [this.$events[name]]; - } - - return this.$events[name]; -}; - -/** - * Emits an event. - * - * @api public - */ - -EventEmitter.prototype.emit = function (name) { - if (!this.$events) { - return false; - } - - var handler = this.$events[name]; - - if (!handler) { - return false; - } - - var args = [].slice.call(arguments, 1); - - if ('function' == typeof handler) { - handler.apply(this, args); - } else if (isArray(handler)) { - var listeners = handler.slice(); - - for (var i = 0, l = listeners.length; i < l; i++) { - listeners[i].apply(this, args); - } - } else { - return false; - } - - return true; -}; -}); // module: browser/events.js - -require.register("browser/fs.js", function(module, exports, require){ - -}); // module: browser/fs.js - -require.register("browser/path.js", function(module, exports, require){ - -}); // module: browser/path.js - -require.register("browser/progress.js", function(module, exports, require){ - -/** - * Expose `Progress`. - */ - -module.exports = Progress; - -/** - * Initialize a new `Progress` indicator. - */ - -function Progress() { - this.percent = 0; - this.size(0); - this.fontSize(11); - this.font('helvetica, arial, sans-serif'); -} - -/** - * Set progress size to `n`. - * - * @param {Number} n - * @return {Progress} for chaining - * @api public - */ - -Progress.prototype.size = function(n){ - this._size = n; - return this; -}; - -/** - * Set text to `str`. - * - * @param {String} str - * @return {Progress} for chaining - * @api public - */ - -Progress.prototype.text = function(str){ - this._text = str; - return this; -}; - -/** - * Set font size to `n`. - * - * @param {Number} n - * @return {Progress} for chaining - * @api public - */ - -Progress.prototype.fontSize = function(n){ - this._fontSize = n; - return this; -}; - -/** - * Set font `family`. - * - * @param {String} family - * @return {Progress} for chaining - */ - -Progress.prototype.font = function(family){ - this._font = family; - return this; -}; - -/** - * Update percentage to `n`. - * - * @param {Number} n - * @return {Progress} for chaining - */ - -Progress.prototype.update = function(n){ - this.percent = n; - return this; -}; - -/** - * Draw on `ctx`. - * - * @param {CanvasRenderingContext2d} ctx - * @return {Progress} for chaining - */ - -Progress.prototype.draw = function(ctx){ - var percent = Math.min(this.percent, 100) - , size = this._size - , half = size / 2 - , x = half - , y = half - , rad = half - 1 - , fontSize = this._fontSize; - - ctx.font = fontSize + 'px ' + this._font; - - var angle = Math.PI * 2 * (percent / 100); - ctx.clearRect(0, 0, size, size); - - // outer circle - ctx.strokeStyle = '#9f9f9f'; - ctx.beginPath(); - ctx.arc(x, y, rad, 0, angle, false); - ctx.stroke(); - - // inner circle - ctx.strokeStyle = '#eee'; - ctx.beginPath(); - ctx.arc(x, y, rad - 1, 0, angle, true); - ctx.stroke(); - - // text - var text = this._text || (percent | 0) + '%' - , w = ctx.measureText(text).width; - - ctx.fillText( - text - , x - w / 2 + 1 - , y + fontSize / 2 - 1); - - return this; -}; - -}); // module: browser/progress.js - -require.register("browser/tty.js", function(module, exports, require){ - -exports.isatty = function(){ - return true; -}; - -exports.getWindowSize = function(){ - return [window.innerHeight, window.innerWidth]; -}; -}); // module: browser/tty.js - -require.register("context.js", function(module, exports, require){ - -/** - * Expose `Context`. - */ - -module.exports = Context; - -/** - * Initialize a new `Context`. - * - * @api private - */ - -function Context(){} - -/** - * Set or get the context `Runnable` to `runnable`. - * - * @param {Runnable} runnable - * @return {Context} - * @api private - */ - -Context.prototype.runnable = function(runnable){ - if (0 == arguments.length) return this._runnable; - this.test = this._runnable = runnable; - return this; -}; - -/** - * Set test timeout `ms`. - * - * @param {Number} ms - * @return {Context} self - * @api private - */ - -Context.prototype.timeout = function(ms){ - this.runnable().timeout(ms); - return this; -}; - -/** - * Inspect the context void of `._runnable`. - * - * @return {String} - * @api private - */ - -Context.prototype.inspect = function(){ - return JSON.stringify(this, function(key, val){ - if ('_runnable' == key) return; - if ('test' == key) return; - return val; - }, 2); -}; - -}); // module: context.js - -require.register("hook.js", function(module, exports, require){ - -/** - * Module dependencies. - */ - -var Runnable = require('./runnable'); - -/** - * Expose `Hook`. - */ - -module.exports = Hook; - -/** - * Initialize a new `Hook` with the given `title` and callback `fn`. - * - * @param {String} title - * @param {Function} fn - * @api private - */ - -function Hook(title, fn) { - Runnable.call(this, title, fn); - this.type = 'hook'; -} - -/** - * Inherit from `Runnable.prototype`. - */ - -Hook.prototype = new Runnable; -Hook.prototype.constructor = Hook; - - -/** - * Get or set the test `err`. - * - * @param {Error} err - * @return {Error} - * @api public - */ - -Hook.prototype.error = function(err){ - if (0 == arguments.length) { - var err = this._error; - this._error = null; - return err; - } - - this._error = err; -}; - - -}); // module: hook.js - -require.register("interfaces/bdd.js", function(module, exports, require){ - -/** - * Module dependencies. - */ - -var Suite = require('../suite') - , Test = require('../test'); - -/** - * BDD-style interface: - * - * describe('Array', function(){ - * describe('#indexOf()', function(){ - * it('should return -1 when not present', function(){ - * - * }); - * - * it('should return the index when present', function(){ - * - * }); - * }); - * }); - * - */ - -module.exports = function(suite){ - var suites = [suite]; - - suite.on('pre-require', function(context){ - - /** - * Execute before running tests. - */ - - context.before = function(fn){ - suites[0].beforeAll(fn); - }; - - /** - * Execute after running tests. - */ - - context.after = function(fn){ - suites[0].afterAll(fn); - }; - - /** - * Execute before each test case. - */ - - context.beforeEach = function(fn){ - suites[0].beforeEach(fn); - }; - - /** - * Execute after each test case. - */ - - context.afterEach = function(fn){ - suites[0].afterEach(fn); - }; - - /** - * Pending describe. - */ - - context.xdescribe = context.xcontext = function(title, fn){ - var suite = Suite.create(suites[0], title); - suite.pending = true; - suites.unshift(suite); - fn(); - suites.shift(); - }; - - /** - * Describe a "suite" with the given `title` - * and callback `fn` containing nested suites - * and/or tests. - */ - - context.describe = context.context = function(title, fn){ - var suite = Suite.create(suites[0], title); - suites.unshift(suite); - fn(); - suites.shift(); - }; - - /** - * Describe a specification or test-case - * with the given `title` and callback `fn` - * acting as a thunk. - */ - - context.it = context.specify = function(title, fn){ - var suite = suites[0]; - if (suite.pending) var fn = null; - suite.addTest(new Test(title, fn)); - }; - - /** - * Pending test case. - */ - - context.xit = context.xspecify = function(title){ - context.it(title); - }; - }); -}; - -}); // module: interfaces/bdd.js - -require.register("interfaces/exports.js", function(module, exports, require){ - -/** - * Module dependencies. - */ - -var Suite = require('../suite') - , Test = require('../test'); - -/** - * TDD-style interface: - * - * exports.Array = { - * '#indexOf()': { - * 'should return -1 when the value is not present': function(){ - * - * }, - * - * 'should return the correct index when the value is present': function(){ - * - * } - * } - * }; - * - */ - -module.exports = function(suite){ - var suites = [suite]; - - suite.on('require', visit); - - function visit(obj) { - var suite; - for (var key in obj) { - if ('function' == typeof obj[key]) { - var fn = obj[key]; - switch (key) { - case 'before': - suites[0].beforeAll(fn); - break; - case 'after': - suites[0].afterAll(fn); - break; - case 'beforeEach': - suites[0].beforeEach(fn); - break; - case 'afterEach': - suites[0].afterEach(fn); - break; - default: - suites[0].addTest(new Test(key, fn)); - } - } else { - var suite = Suite.create(suites[0], key); - suites.unshift(suite); - visit(obj[key]); - suites.shift(); - } - } - } -}; -}); // module: interfaces/exports.js - -require.register("interfaces/index.js", function(module, exports, require){ - -exports.bdd = require('./bdd'); -exports.tdd = require('./tdd'); -exports.qunit = require('./qunit'); -exports.exports = require('./exports'); - -}); // module: interfaces/index.js - -require.register("interfaces/qunit.js", function(module, exports, require){ - -/** - * Module dependencies. - */ - -var Suite = require('../suite') - , Test = require('../test'); - -/** - * QUnit-style interface: - * - * suite('Array'); - * - * test('#length', function(){ - * var arr = [1,2,3]; - * ok(arr.length == 3); - * }); - * - * test('#indexOf()', function(){ - * var arr = [1,2,3]; - * ok(arr.indexOf(1) == 0); - * ok(arr.indexOf(2) == 1); - * ok(arr.indexOf(3) == 2); - * }); - * - * suite('String'); - * - * test('#length', function(){ - * ok('foo'.length == 3); - * }); - * - */ - -module.exports = function(suite){ - var suites = [suite]; - - suite.on('pre-require', function(context){ - - /** - * Execute before running tests. - */ - - context.before = function(fn){ - suites[0].beforeAll(fn); - }; - - /** - * Execute after running tests. - */ - - context.after = function(fn){ - suites[0].afterAll(fn); - }; - - /** - * Execute before each test case. - */ - - context.beforeEach = function(fn){ - suites[0].beforeEach(fn); - }; - - /** - * Execute after each test case. - */ - - context.afterEach = function(fn){ - suites[0].afterEach(fn); - }; - - /** - * Describe a "suite" with the given `title`. - */ - - context.suite = function(title){ - if (suites.length > 1) suites.shift(); - var suite = Suite.create(suites[0], title); - suites.unshift(suite); - }; - - /** - * Describe a specification or test-case - * with the given `title` and callback `fn` - * acting as a thunk. - */ - - context.test = function(title, fn){ - suites[0].addTest(new Test(title, fn)); - }; - }); -}; - -}); // module: interfaces/qunit.js - -require.register("interfaces/tdd.js", function(module, exports, require){ - -/** - * Module dependencies. - */ - -var Suite = require('../suite') - , Test = require('../test'); - -/** - * TDD-style interface: - * - * suite('Array', function(){ - * suite('#indexOf()', function(){ - * suiteSetup(function(){ - * - * }); - * - * test('should return -1 when not present', function(){ - * - * }); - * - * test('should return the index when present', function(){ - * - * }); - * - * suiteTeardown(function(){ - * - * }); - * }); - * }); - * - */ - -module.exports = function(suite){ - var suites = [suite]; - - suite.on('pre-require', function(context){ - - /** - * Execute before each test case. - */ - - context.setup = function(fn){ - suites[0].beforeEach(fn); - }; - - /** - * Execute after each test case. - */ - - context.teardown = function(fn){ - suites[0].afterEach(fn); - }; - - /** - * Execute before the suite. - */ - - context.suiteSetup = function(fn){ - suites[0].beforeAll(fn); - }; - - /** - * Execute after the suite. - */ - - context.suiteTeardown = function(fn){ - suites[0].afterAll(fn); - }; - - /** - * Describe a "suite" with the given `title` - * and callback `fn` containing nested suites - * and/or tests. - */ - - context.suite = function(title, fn){ - var suite = Suite.create(suites[0], title); - suites.unshift(suite); - fn(); - suites.shift(); - }; - - /** - * Describe a specification or test-case - * with the given `title` and callback `fn` - * acting as a thunk. - */ - - context.test = function(title, fn){ - suites[0].addTest(new Test(title, fn)); - }; - }); -}; - -}); // module: interfaces/tdd.js - -require.register("mocha.js", function(module, exports, require){ -/*! - * mocha - * Copyright(c) 2011 TJ Holowaychuk - * MIT Licensed - */ - -/** - * Module dependencies. - */ - -var path = require('browser/path'); - -/** - * Expose `Mocha`. - */ - -exports = module.exports = Mocha; - -/** - * Expose internals. - */ - -exports.utils = require('./utils'); -exports.interfaces = require('./interfaces'); -exports.reporters = require('./reporters'); -exports.Runnable = require('./runnable'); -exports.Context = require('./context'); -exports.Runner = require('./runner'); -exports.Suite = require('./suite'); -exports.Hook = require('./hook'); -exports.Test = require('./test'); - -/** - * Return image `name` path. - * - * @param {String} name - * @return {String} - * @api private - */ - -function image(name) { - return __dirname + '/../images/' + name + '.png'; -} - -/** - * Setup mocha with `options`. - * - * Options: - * - * - `ui` name "bdd", "tdd", "exports" etc - * - `reporter` reporter instance, defaults to `mocha.reporters.Dot` - * - `globals` array of accepted globals - * - `timeout` timeout in milliseconds - * - `ignoreLeaks` ignore global leaks - * - `grep` string or regexp to filter tests with - * - * @param {Object} options - * @api public - */ - -function Mocha(options) { - options = options || {}; - this.files = []; - this.options = options; - this.grep(options.grep); - this.suite = new exports.Suite('', new exports.Context); - this.ui(options.ui); - this.reporter(options.reporter); - if (options.timeout) this.suite.timeout(options.timeout); -} - -/** - * Add test `file`. - * - * @param {String} file - * @api public - */ - -Mocha.prototype.addFile = function(file){ - this.files.push(file); - return this; -}; - -/** - * Set reporter to `name`, defaults to "dot". - * - * @param {String} name - * @api public - */ - -Mocha.prototype.reporter = function(name){ - name = name || 'dot'; - this._reporter = require('./reporters/' + name); - if (!this._reporter) throw new Error('invalid reporter "' + name + '"'); - return this; -}; - -/** - * Set test UI `name`, defaults to "bdd". - * - * @param {String} bdd - * @api public - */ - -Mocha.prototype.ui = function(name){ - name = name || 'bdd'; - this._ui = exports.interfaces[name]; - if (!this._ui) throw new Error('invalid interface "' + name + '"'); - this._ui = this._ui(this.suite); - return this; -}; - -/** - * Load registered files. - * - * @api private - */ - -Mocha.prototype.loadFiles = function(fn){ - var suite = this.suite; - var pending = this.files.length; - this.files.forEach(function(file){ - file = path.resolve(file); - suite.emit('pre-require', global, file); - suite.emit('require', require(file), file); - suite.emit('post-require', global, file); - --pending || (fn && fn()); - }); -}; - -/** - * Enable growl support. - * - * @api private - */ - -Mocha.prototype._growl = function(runner, reporter) { - var notify = require('growl'); - - runner.on('end', function(){ - var stats = reporter.stats; - if (stats.failures) { - var msg = stats.failures + ' of ' + runner.total + ' tests failed'; - notify(msg, { name: 'mocha', title: 'Failed', image: image('error') }); - } else { - notify(stats.passes + ' tests passed in ' + stats.duration + 'ms', { - name: 'mocha' - , title: 'Passed' - , image: image('ok') - }); - } - }); -}; - -/** - * Add regexp to grep for to the options object - * - * @param {RegExp} or {String} re - * @return {Mocha} - * @api public - */ - -Mocha.prototype.grep = function(re){ - this.options.grep = 'string' == typeof re - ? new RegExp(re) - : re; - return this; -}; - -/** - * Invert `.grep()` matches. - * - * @return {Mocha} - * @api public - */ - -Mocha.prototype.invert = function(){ - this.options.invert = true; - return this; -}; - -/** - * Ignore global leaks. - * - * @return {Mocha} - * @api public - */ - -Mocha.prototype.ignoreLeaks = function(){ - this.options.ignoreLeaks = true; - return this; -}; - -/** - * Enable growl support. - * - * @return {Mocha} - * @api public - */ - -Mocha.prototype.growl = function(){ - this.options.growl = true; - return this; -}; - -/** - * Ignore `globals`. - * - * @param {Array} globals - * @return {Mocha} - * @api public - */ - -Mocha.prototype.globals = function(globals){ - this.options.globals = globals; - return this; -}; - -/** - * Run tests and invoke `fn()` when complete. - * - * @param {Function} fn - * @return {Runner} - * @api public - */ - -Mocha.prototype.run = function(fn){ - this.loadFiles(); - var suite = this.suite; - var options = this.options; - var runner = new exports.Runner(suite); - var reporter = new this._reporter(runner); - runner.ignoreLeaks = options.ignoreLeaks; - if (options.grep) runner.grep(options.grep, options.invert); - if (options.globals) runner.globals(options.globals); - if (options.growl) this._growl(runner, reporter); - return runner.run(fn); -}; - -}); // module: mocha.js - -require.register("reporters/base.js", function(module, exports, require){ - -/** - * Module dependencies. - */ - -var tty = require('browser/tty') - , diff = require('browser/diff'); - -/** - * Save timer references to avoid Sinon interfering (see GH-237). - */ - -var Date = global.Date - , setTimeout = global.setTimeout - , setInterval = global.setInterval - , clearTimeout = global.clearTimeout - , clearInterval = global.clearInterval; - -/** - * Check if both stdio streams are associated with a tty. - */ - -var isatty = tty.isatty(1) && tty.isatty(2); - -/** - * Expose `Base`. - */ - -exports = module.exports = Base; - -/** - * Enable coloring by default. - */ - -exports.useColors = isatty; - -/** - * Default color map. - */ - -exports.colors = { - 'pass': 90 - , 'fail': 31 - , 'bright pass': 92 - , 'bright fail': 91 - , 'bright yellow': 93 - , 'pending': 36 - , 'suite': 0 - , 'error title': 0 - , 'error message': 31 - , 'error stack': 90 - , 'checkmark': 32 - , 'fast': 90 - , 'medium': 33 - , 'slow': 31 - , 'green': 32 - , 'light': 90 - , 'diff gutter': 90 - , 'diff added': 42 - , 'diff removed': 41 -}; - -/** - * Color `str` with the given `type`, - * allowing colors to be disabled, - * as well as user-defined color - * schemes. - * - * @param {String} type - * @param {String} str - * @return {String} - * @api private - */ - -var color = exports.color = function(type, str) { - if (!exports.useColors) return str; - return '\u001b[' + exports.colors[type] + 'm' + str + '\u001b[0m'; -}; - -/** - * Expose term window size, with some - * defaults for when stderr is not a tty. - */ - -exports.window = { - width: isatty - ? process.stdout.getWindowSize - ? process.stdout.getWindowSize(1)[0] - : tty.getWindowSize()[1] - : 75 -}; - -/** - * Expose some basic cursor interactions - * that are common among reporters. - */ - -exports.cursor = { - hide: function(){ - process.stdout.write('\u001b[?25l'); - }, - - show: function(){ - process.stdout.write('\u001b[?25h'); - }, - - deleteLine: function(){ - process.stdout.write('\u001b[2K'); - }, - - beginningOfLine: function(){ - process.stdout.write('\u001b[0G'); - }, - - CR: function(){ - exports.cursor.deleteLine(); - exports.cursor.beginningOfLine(); - } -}; - -/** - * A test is considered slow if it - * exceeds the following value in milliseconds. - */ - -exports.slow = 75; - -/** - * Outut the given `failures` as a list. - * - * @param {Array} failures - * @api public - */ - -exports.list = function(failures){ - console.error(); - failures.forEach(function(test, i){ - // format - var fmt = color('error title', ' %s) %s:\n') - + color('error message', ' %s') - + color('error stack', '\n%s\n'); - - // msg - var err = test.err - , message = err.message || '' - , stack = err.stack || message - , index = stack.indexOf(message) + message.length - , msg = stack.slice(0, index) - , actual = err.actual - , expected = err.expected; - - // actual / expected diff - if ('string' == typeof actual && 'string' == typeof expected) { - var len = Math.max(actual.length, expected.length); - - if (len < 20) msg = errorDiff(err, 'Chars'); - else msg = errorDiff(err, 'Words'); - - // linenos - var lines = msg.split('\n'); - if (lines.length > 4) { - var width = String(lines.length).length; - msg = lines.map(function(str, i){ - return pad(++i, width) + ' |' + ' ' + str; - }).join('\n'); - } - - // legend - msg = '\n' - + color('diff removed', 'actual') - + ' ' - + color('diff added', 'expected') - + '\n\n' - + msg - + '\n'; - - // indent - msg = msg.replace(/^/gm, ' '); - - fmt = color('error title', ' %s) %s:\n%s') - + color('error stack', '\n%s\n'); - } - - // indent stack trace without msg - stack = stack.slice(index ? index + 1 : index) - .replace(/^/gm, ' '); - - console.error(fmt, (i + 1), test.fullTitle(), msg, stack); - }); -}; - -/** - * Initialize a new `Base` reporter. - * - * All other reporters generally - * inherit from this reporter, providing - * stats such as test duration, number - * of tests passed / failed etc. - * - * @param {Runner} runner - * @api public - */ - -function Base(runner) { - var self = this - , stats = this.stats = { suites: 0, tests: 0, passes: 0, pending: 0, failures: 0 } - , failures = this.failures = []; - - if (!runner) return; - this.runner = runner; - - runner.on('start', function(){ - stats.start = new Date; - }); - - runner.on('suite', function(suite){ - stats.suites = stats.suites || 0; - suite.root || stats.suites++; - }); - - runner.on('test end', function(test){ - stats.tests = stats.tests || 0; - stats.tests++; - }); - - runner.on('pass', function(test){ - stats.passes = stats.passes || 0; - - var medium = exports.slow / 2; - test.speed = test.duration > exports.slow - ? 'slow' - : test.duration > medium - ? 'medium' - : 'fast'; - - stats.passes++; - }); - - runner.on('fail', function(test, err){ - stats.failures = stats.failures || 0; - stats.failures++; - test.err = err; - failures.push(test); - }); - - runner.on('end', function(){ - stats.end = new Date; - stats.duration = new Date - stats.start; - }); - - runner.on('pending', function(){ - stats.pending++; - }); -} - -/** - * Output common epilogue used by many of - * the bundled reporters. - * - * @api public - */ - -Base.prototype.epilogue = function(){ - var stats = this.stats - , fmt - , tests; - - console.log(); - - function pluralize(n) { - return 1 == n ? 'test' : 'tests'; - } - - // failure - if (stats.failures) { - fmt = color('bright fail', ' ✖') - + color('fail', ' %d of %d %s failed') - + color('light', ':') - - console.error(fmt, - stats.failures, - this.runner.total, - pluralize(this.runner.total)); - - Base.list(this.failures); - console.error(); - return; - } - - // pass - fmt = color('bright pass', ' ✔') - + color('green', ' %d %s complete') - + color('light', ' (%dms)'); - - console.log(fmt, - stats.tests || 0, - pluralize(stats.tests), - stats.duration); - - // pending - if (stats.pending) { - fmt = color('pending', ' •') - + color('pending', ' %d %s pending'); - - console.log(fmt, stats.pending, pluralize(stats.pending)); - } - - console.log(); -}; - -/** - * Pad the given `str` to `len`. - * - * @param {String} str - * @param {String} len - * @return {String} - * @api private - */ - -function pad(str, len) { - str = String(str); - return Array(len - str.length + 1).join(' ') + str; -} - -/** - * Return a character diff for `err`. - * - * @param {Error} err - * @return {String} - * @api private - */ - -function errorDiff(err, type) { - return diff['diff' + type](err.actual, err.expected).map(function(str){ - if (/^(\n+)$/.test(str.value)) str.value = Array(++RegExp.$1.length).join(''); - if (str.added) return colorLines('diff added', str.value); - if (str.removed) return colorLines('diff removed', str.value); - return str.value; - }).join(''); -} - -/** - * Color lines for `str`, using the color `name`. - * - * @param {String} name - * @param {String} str - * @return {String} - * @api private - */ - -function colorLines(name, str) { - return str.split('\n').map(function(str){ - return color(name, str); - }).join('\n'); -} - -}); // module: reporters/base.js - -require.register("reporters/doc.js", function(module, exports, require){ - -/** - * Module dependencies. - */ - -var Base = require('./base') - , utils = require('../utils'); - -/** - * Expose `Doc`. - */ - -exports = module.exports = Doc; - -/** - * Initialize a new `Doc` reporter. - * - * @param {Runner} runner - * @api public - */ - -function Doc(runner) { - Base.call(this, runner); - - var self = this - , stats = this.stats - , total = runner.total - , indents = 2; - - function indent() { - return Array(indents).join(' '); - } - - runner.on('suite', function(suite){ - if (suite.root) return; - ++indents; - console.log('%s
', indent()); - ++indents; - console.log('%s

%s

', indent(), suite.title); - console.log('%s
', indent()); - }); - - runner.on('suite end', function(suite){ - if (suite.root) return; - console.log('%s
', indent()); - --indents; - console.log('%s
', indent()); - --indents; - }); - - runner.on('pass', function(test){ - console.log('%s
%s
', indent(), test.title); - var code = utils.escape(utils.clean(test.fn.toString())); - console.log('%s
%s
', indent(), code); - }); -} - -}); // module: reporters/doc.js - -require.register("reporters/dot.js", function(module, exports, require){ - -/** - * Module dependencies. - */ - -var Base = require('./base') - , color = Base.color; - -/** - * Expose `Dot`. - */ - -exports = module.exports = Dot; - -/** - * Initialize a new `Dot` matrix test reporter. - * - * @param {Runner} runner - * @api public - */ - -function Dot(runner) { - Base.call(this, runner); - - var self = this - , stats = this.stats - , width = Base.window.width * .75 | 0 - , c = '․' - , n = 0; - - runner.on('start', function(){ - process.stdout.write('\n '); - }); - - runner.on('pending', function(test){ - process.stdout.write(color('pending', c)); - }); - - runner.on('pass', function(test){ - if (++n % width == 0) process.stdout.write('\n '); - if ('slow' == test.speed) { - process.stdout.write(color('bright yellow', c)); - } else { - process.stdout.write(color(test.speed, c)); - } - }); - - runner.on('fail', function(test, err){ - if (++n % width == 0) process.stdout.write('\n '); - process.stdout.write(color('fail', c)); - }); - - runner.on('end', function(){ - console.log(); - self.epilogue(); - }); -} - -/** - * Inherit from `Base.prototype`. - */ - -Dot.prototype = new Base; -Dot.prototype.constructor = Dot; - -}); // module: reporters/dot.js - -require.register("reporters/html-cov.js", function(module, exports, require){ - -/** - * Module dependencies. - */ - -var JSONCov = require('./json-cov') - , fs = require('browser/fs'); - -/** - * Expose `HTMLCov`. - */ - -exports = module.exports = HTMLCov; - -/** - * Initialize a new `JsCoverage` reporter. - * - * @param {Runner} runner - * @api public - */ - -function HTMLCov(runner) { - var jade = require('jade') - , file = __dirname + '/templates/coverage.jade' - , str = fs.readFileSync(file, 'utf8') - , fn = jade.compile(str, { filename: file }) - , self = this; - - JSONCov.call(this, runner, false); - - runner.on('end', function(){ - process.stdout.write(fn({ - cov: self.cov - , coverageClass: coverageClass - })); - }); -} - -/** - * Return coverage class for `n`. - * - * @return {String} - * @api private - */ - -function coverageClass(n) { - if (n >= 75) return 'high'; - if (n >= 50) return 'medium'; - if (n >= 25) return 'low'; - return 'terrible'; -} -}); // module: reporters/html-cov.js - -require.register("reporters/html.js", function(module, exports, require){ - -/** - * Module dependencies. - */ - -var Base = require('./base') - , utils = require('../utils') - , Progress = require('../browser/progress') - , escape = utils.escape; - -/** - * Save timer references to avoid Sinon interfering (see GH-237). - */ - -var Date = global.Date - , setTimeout = global.setTimeout - , setInterval = global.setInterval - , clearTimeout = global.clearTimeout - , clearInterval = global.clearInterval; - -/** - * Expose `Doc`. - */ - -exports = module.exports = HTML; - -/** - * Stats template. - */ - -var statsTemplate = ''; - -/** - * Initialize a new `Doc` reporter. - * - * @param {Runner} runner - * @api public - */ - -function HTML(runner) { - Base.call(this, runner); - - var self = this - , stats = this.stats - , total = runner.total - , root = document.getElementById('mocha') - , stat = fragment(statsTemplate) - , items = stat.getElementsByTagName('li') - , passes = items[1].getElementsByTagName('em')[0] - , passesLink = items[1].getElementsByTagName('a')[0] - , failures = items[2].getElementsByTagName('em')[0] - , failuresLink = items[2].getElementsByTagName('a')[0] - , duration = items[3].getElementsByTagName('em')[0] - , canvas = stat.getElementsByTagName('canvas')[0] - , report = fragment('
    ') - , stack = [report] - , progress - , ctx - - if (canvas.getContext) { - ctx = canvas.getContext('2d'); - progress = new Progress; - } - - if (!root) return error('#mocha div missing, add it to your document'); - - // pass toggle - on(passesLink, 'click', function () { - var className = /pass/.test(report.className) ? '' : ' pass'; - report.className = report.className.replace(/fail|pass/g, '') + className; - }); - - // failure toggle - on(failuresLink, 'click', function () { - var className = /fail/.test(report.className) ? '' : ' fail'; - report.className = report.className.replace(/fail|pass/g, '') + className; - }); - - root.appendChild(stat); - root.appendChild(report); - - if (progress) progress.size(40); - - runner.on('suite', function(suite){ - if (suite.root) return; - - // suite - var url = location.protocol + '//' + location.host + location.pathname + '?grep=^' + utils.escapeRegexp(suite.fullTitle()); - var el = fragment('
  • %s

  • ', url, escape(suite.title)); - - // container - stack[0].appendChild(el); - stack.unshift(document.createElement('ul')); - el.appendChild(stack[0]); - }); - - runner.on('suite end', function(suite){ - if (suite.root) return; - stack.shift(); - }); - - runner.on('fail', function(test, err){ - if ('hook' == test.type || err.uncaught) runner.emit('test end', test); - }); - - runner.on('test end', function(test){ - window.scrollTo(0, document.body.scrollHeight); - - // TODO: add to stats - var percent = stats.tests / total * 100 | 0; - if (progress) progress.update(percent).draw(ctx); - - // update stats - var ms = new Date - stats.start; - text(passes, stats.passes); - text(failures, stats.failures); - text(duration, (ms / 1000).toFixed(2)); - - // test - if ('passed' == test.state) { - var el = fragment('
  • %e%ems

  • ', test.speed, test.title, test.duration); - } else if (test.pending) { - var el = fragment('
  • %e

  • ', test.title); - } else { - var el = fragment('
  • %e

  • ', test.title); - var str = test.err.stack || test.err.toString(); - - // FF / Opera do not add the message - if (!~str.indexOf(test.err.message)) { - str = test.err.message + '\n' + str; - } - - // <=IE7 stringifies to [Object Error]. Since it can be overloaded, we - // check for the result of the stringifying. - if ('[object Error]' == str) str = test.err.message; - - // Safari doesn't give you a stack. Let's at least provide a source line. - if (!test.err.stack && test.err.sourceURL && test.err.line !== undefined) { - str += "\n(" + test.err.sourceURL + ":" + test.err.line + ")"; - } - - el.appendChild(fragment('
    %e
    ', str)); - } - - // toggle code - // TODO: defer - if (!test.pending) { - var h2 = el.getElementsByTagName('h2')[0]; - - on(h2, 'click', function(){ - pre.style.display = 'none' == pre.style.display - ? 'inline-block' - : 'none'; - }); - - var pre = fragment('
    %e
    ', utils.clean(test.fn.toString())); - el.appendChild(pre); - pre.style.display = 'none'; - } - - stack[0].appendChild(el); - }); -} - -/** - * Display error `msg`. - */ - -function error(msg) { - document.body.appendChild(fragment('
    %s
    ', msg)); -} - -/** - * Return a DOM fragment from `html`. - */ - -function fragment(html) { - var args = arguments - , div = document.createElement('div') - , i = 1; - - div.innerHTML = html.replace(/%([se])/g, function(_, type){ - switch (type) { - case 's': return String(args[i++]); - case 'e': return escape(args[i++]); - } - }); - - return div.firstChild; -} - -/** - * Set `el` text to `str`. - */ - -function text(el, str) { - if (el.textContent) { - el.textContent = str; - } else { - el.innerText = str; - } -} - -/** - * Listen on `event` with callback `fn`. - */ - -function on(el, event, fn) { - if (el.addEventListener) { - el.addEventListener(event, fn, false); - } else { - el.attachEvent('on' + event, fn); - } -} - -}); // module: reporters/html.js - -require.register("reporters/index.js", function(module, exports, require){ - -exports.Base = require('./base'); -exports.Dot = require('./dot'); -exports.Doc = require('./doc'); -exports.TAP = require('./tap'); -exports.JSON = require('./json'); -exports.HTML = require('./html'); -exports.List = require('./list'); -exports.Min = require('./min'); -exports.Spec = require('./spec'); -exports.Nyan = require('./nyan'); -exports.XUnit = require('./xunit'); -exports.Progress = require('./progress'); -exports.Landing = require('./landing'); -exports.JSONCov = require('./json-cov'); -exports.HTMLCov = require('./html-cov'); -exports.JSONStream = require('./json-stream'); -exports.Teamcity = require('./teamcity'); - -}); // module: reporters/index.js - -require.register("reporters/json-cov.js", function(module, exports, require){ - -/** - * Module dependencies. - */ - -var Base = require('./base'); - -/** - * Expose `JSONCov`. - */ - -exports = module.exports = JSONCov; - -/** - * Initialize a new `JsCoverage` reporter. - * - * @param {Runner} runner - * @param {Boolean} output - * @api public - */ - -function JSONCov(runner, output) { - var self = this - , output = 1 == arguments.length ? true : output; - - Base.call(this, runner); - - var tests = [] - , failures = [] - , passes = []; - - runner.on('test end', function(test){ - tests.push(test); - }); - - runner.on('pass', function(test){ - passes.push(test); - }); - - runner.on('fail', function(test){ - failures.push(test); - }); - - runner.on('end', function(){ - var cov = global._$jscoverage || {}; - var result = self.cov = map(cov); - result.stats = self.stats; - result.tests = tests.map(clean); - result.failures = failures.map(clean); - result.passes = passes.map(clean); - if (!output) return; - process.stdout.write(JSON.stringify(result, null, 2 )); - }); -} - -/** - * Map jscoverage data to a JSON structure - * suitable for reporting. - * - * @param {Object} cov - * @return {Object} - * @api private - */ - -function map(cov) { - var ret = { - instrumentation: 'node-jscoverage' - , sloc: 0 - , hits: 0 - , misses: 0 - , coverage: 0 - , files: [] - }; - - for (var filename in cov) { - var data = coverage(filename, cov[filename]); - ret.files.push(data); - ret.hits += data.hits; - ret.misses += data.misses; - ret.sloc += data.sloc; - } - - if (ret.sloc > 0) { - ret.coverage = (ret.hits / ret.sloc) * 100; - } - - return ret; -}; - -/** - * Map jscoverage data for a single source file - * to a JSON structure suitable for reporting. - * - * @param {String} filename name of the source file - * @param {Object} data jscoverage coverage data - * @return {Object} - * @api private - */ - -function coverage(filename, data) { - var ret = { - filename: filename, - coverage: 0, - hits: 0, - misses: 0, - sloc: 0, - source: {} - }; - - data.source.forEach(function(line, num){ - num++; - - if (data[num] === 0) { - ret.misses++; - ret.sloc++; - } else if (data[num] !== undefined) { - ret.hits++; - ret.sloc++; - } - - ret.source[num] = { - source: line - , coverage: data[num] === undefined - ? '' - : data[num] - }; - }); - - ret.coverage = ret.hits / ret.sloc * 100; - - return ret; -} - -/** - * Return a plain-object representation of `test` - * free of cyclic properties etc. - * - * @param {Object} test - * @return {Object} - * @api private - */ - -function clean(test) { - return { - title: test.title - , fullTitle: test.fullTitle() - , duration: test.duration - } -} - -}); // module: reporters/json-cov.js - -require.register("reporters/json-stream.js", function(module, exports, require){ - -/** - * Module dependencies. - */ - -var Base = require('./base') - , color = Base.color; - -/** - * Expose `List`. - */ - -exports = module.exports = List; - -/** - * Initialize a new `List` test reporter. - * - * @param {Runner} runner - * @api public - */ - -function List(runner) { - Base.call(this, runner); - - var self = this - , stats = this.stats - , total = runner.total; - - runner.on('start', function(){ - console.log(JSON.stringify(['start', { total: total }])); - }); - - runner.on('pass', function(test){ - console.log(JSON.stringify(['pass', clean(test)])); - }); - - runner.on('fail', function(test, err){ - console.log(JSON.stringify(['fail', clean(test)])); - }); - - runner.on('end', function(){ - process.stdout.write(JSON.stringify(['end', self.stats])); - }); -} - -/** - * Return a plain-object representation of `test` - * free of cyclic properties etc. - * - * @param {Object} test - * @return {Object} - * @api private - */ - -function clean(test) { - return { - title: test.title - , fullTitle: test.fullTitle() - , duration: test.duration - } -} -}); // module: reporters/json-stream.js - -require.register("reporters/json.js", function(module, exports, require){ - -/** - * Module dependencies. - */ - -var Base = require('./base') - , cursor = Base.cursor - , color = Base.color; - -/** - * Expose `JSON`. - */ - -exports = module.exports = JSONReporter; - -/** - * Initialize a new `JSON` reporter. - * - * @param {Runner} runner - * @api public - */ - -function JSONReporter(runner) { - var self = this; - Base.call(this, runner); - - var tests = [] - , failures = [] - , passes = []; - - runner.on('test end', function(test){ - tests.push(test); - }); - - runner.on('pass', function(test){ - passes.push(test); - }); - - runner.on('fail', function(test){ - failures.push(test); - }); - - runner.on('end', function(){ - var obj = { - stats: self.stats - , tests: tests.map(clean) - , failures: failures.map(clean) - , passes: passes.map(clean) - }; - - process.stdout.write(JSON.stringify(obj, null, 2)); - }); -} - -/** - * Return a plain-object representation of `test` - * free of cyclic properties etc. - * - * @param {Object} test - * @return {Object} - * @api private - */ - -function clean(test) { - return { - title: test.title - , fullTitle: test.fullTitle() - , duration: test.duration - } -} -}); // module: reporters/json.js - -require.register("reporters/landing.js", function(module, exports, require){ - -/** - * Module dependencies. - */ - -var Base = require('./base') - , cursor = Base.cursor - , color = Base.color; - -/** - * Expose `Landing`. - */ - -exports = module.exports = Landing; - -/** - * Airplane color. - */ - -Base.colors.plane = 0; - -/** - * Airplane crash color. - */ - -Base.colors['plane crash'] = 31; - -/** - * Runway color. - */ - -Base.colors.runway = 90; - -/** - * Initialize a new `Landing` reporter. - * - * @param {Runner} runner - * @api public - */ - -function Landing(runner) { - Base.call(this, runner); - - var self = this - , stats = this.stats - , width = Base.window.width * .75 | 0 - , total = runner.total - , stream = process.stdout - , plane = color('plane', '✈') - , crashed = -1 - , n = 0; - - function runway() { - var buf = Array(width).join('-'); - return ' ' + color('runway', buf); - } - - runner.on('start', function(){ - stream.write('\n '); - cursor.hide(); - }); - - runner.on('test end', function(test){ - // check if the plane crashed - var col = -1 == crashed - ? width * ++n / total | 0 - : crashed; - - // show the crash - if ('failed' == test.state) { - plane = color('plane crash', '✈'); - crashed = col; - } - - // render landing strip - stream.write('\u001b[4F\n\n'); - stream.write(runway()); - stream.write('\n '); - stream.write(color('runway', Array(col).join('⋅'))); - stream.write(plane) - stream.write(color('runway', Array(width - col).join('⋅') + '\n')); - stream.write(runway()); - stream.write('\u001b[0m'); - }); - - runner.on('end', function(){ - cursor.show(); - console.log(); - self.epilogue(); - }); -} - -/** - * Inherit from `Base.prototype`. - */ - -Landing.prototype = new Base; -Landing.prototype.constructor = Landing; - -}); // module: reporters/landing.js - -require.register("reporters/list.js", function(module, exports, require){ - -/** - * Module dependencies. - */ - -var Base = require('./base') - , cursor = Base.cursor - , color = Base.color; - -/** - * Expose `List`. - */ - -exports = module.exports = List; - -/** - * Initialize a new `List` test reporter. - * - * @param {Runner} runner - * @api public - */ - -function List(runner) { - Base.call(this, runner); - - var self = this - , stats = this.stats - , n = 0; - - runner.on('start', function(){ - console.log(); - }); - - runner.on('test', function(test){ - process.stdout.write(color('pass', ' ' + test.fullTitle() + ': ')); - }); - - runner.on('pending', function(test){ - var fmt = color('checkmark', ' -') - + color('pending', ' %s'); - console.log(fmt, test.fullTitle()); - }); - - runner.on('pass', function(test){ - var fmt = color('checkmark', ' ✓') - + color('pass', ' %s: ') - + color(test.speed, '%dms'); - cursor.CR(); - console.log(fmt, test.fullTitle(), test.duration); - }); - - runner.on('fail', function(test, err){ - cursor.CR(); - console.log(color('fail', ' %d) %s'), ++n, test.fullTitle()); - }); - - runner.on('end', self.epilogue.bind(self)); -} - -/** - * Inherit from `Base.prototype`. - */ - -List.prototype = new Base; -List.prototype.constructor = List; - - -}); // module: reporters/list.js - -require.register("reporters/markdown.js", function(module, exports, require){ -/** - * Module dependencies. - */ - -var Base = require('./base') - , utils = require('../utils'); - -/** - * Expose `Markdown`. - */ - -exports = module.exports = Markdown; - -/** - * Initialize a new `Markdown` reporter. - * - * @param {Runner} runner - * @api public - */ - -function Markdown(runner) { - Base.call(this, runner); - - var self = this - , stats = this.stats - , total = runner.total - , level = 0 - , buf = ''; - - function title(str) { - return Array(level).join('#') + ' ' + str; - } - - function indent() { - return Array(level).join(' '); - } - - function mapTOC(suite, obj) { - var ret = obj; - obj = obj[suite.title] = obj[suite.title] || { suite: suite }; - suite.suites.forEach(function(suite){ - mapTOC(suite, obj); - }); - return ret; - } - - function stringifyTOC(obj, level) { - ++level; - var buf = ''; - var link; - for (var key in obj) { - if ('suite' == key) continue; - if (key) link = ' - [' + key + '](#' + utils.slug(obj[key].suite.fullTitle()) + ')\n'; - if (key) buf += Array(level).join(' ') + link; - buf += stringifyTOC(obj[key], level); - } - --level; - return buf; - } - - function generateTOC(suite) { - var obj = mapTOC(suite, {}); - return stringifyTOC(obj, 0); - } - - generateTOC(runner.suite); - - runner.on('suite', function(suite){ - ++level; - var slug = utils.slug(suite.fullTitle()); - buf += '' + '\n'; - buf += title(suite.title) + '\n'; - }); - - runner.on('suite end', function(suite){ - --level; - }); - - runner.on('pass', function(test){ - var code = utils.clean(test.fn.toString()); - buf += test.title + '.\n'; - buf += '\n```js\n'; - buf += code + '\n'; - buf += '```\n\n'; - }); - - runner.on('end', function(){ - process.stdout.write('# TOC\n'); - process.stdout.write(generateTOC(runner.suite)); - process.stdout.write(buf); - }); -} -}); // module: reporters/markdown.js - -require.register("reporters/min.js", function(module, exports, require){ - -/** - * Module dependencies. - */ - -var Base = require('./base'); - -/** - * Expose `Min`. - */ - -exports = module.exports = Min; - -/** - * Initialize a new `Min` minimal test reporter (best used with --watch). - * - * @param {Runner} runner - * @api public - */ - -function Min(runner) { - Base.call(this, runner); - - runner.on('start', function(){ - // clear screen - process.stdout.write('\u001b[2J'); - // set cursor position - process.stdout.write('\u001b[1;3H'); - }); - - runner.on('end', this.epilogue.bind(this)); -} - -/** - * Inherit from `Base.prototype`. - */ - -Min.prototype = new Base; -Min.prototype.constructor = Min; - -}); // module: reporters/min.js - -require.register("reporters/nyan.js", function(module, exports, require){ - -/** - * Module dependencies. - */ - -var Base = require('./base') - , color = Base.color; - -/** - * Expose `Dot`. - */ - -exports = module.exports = NyanCat; - -/** - * Initialize a new `Dot` matrix test reporter. - * - * @param {Runner} runner - * @api public - */ - -function NyanCat(runner) { - Base.call(this, runner); - - var self = this - , stats = this.stats - , width = Base.window.width * .75 | 0 - , rainbowColors = this.rainbowColors = self.generateColors() - , colorIndex = this.colorIndex = 0 - , numerOfLines = this.numberOfLines = 4 - , trajectories = this.trajectories = [[], [], [], []] - , nyanCatWidth = this.nyanCatWidth = 11 - , trajectoryWidthMax = this.trajectoryWidthMax = (width - nyanCatWidth) - , scoreboardWidth = this.scoreboardWidth = 5 - , tick = this.tick = 0 - , n = 0; - - runner.on('start', function(){ - Base.cursor.hide(); - self.draw('start'); - }); - - runner.on('pending', function(test){ - self.draw('pending'); - }); - - runner.on('pass', function(test){ - self.draw('pass'); - }); - - runner.on('fail', function(test, err){ - self.draw('fail'); - }); - - runner.on('end', function(){ - Base.cursor.show(); - for (var i = 0; i < self.numberOfLines; i++) write('\n'); - self.epilogue(); - }); -} - -/** - * Draw the nyan cat with runner `status`. - * - * @param {String} status - * @api private - */ - -NyanCat.prototype.draw = function(status){ - this.appendRainbow(); - this.drawScoreboard(); - this.drawRainbow(); - this.drawNyanCat(status); - this.tick = !this.tick; -}; - -/** - * Draw the "scoreboard" showing the number - * of passes, failures and pending tests. - * - * @api private - */ - -NyanCat.prototype.drawScoreboard = function(){ - var stats = this.stats; - var colors = Base.colors; - - function draw(color, n) { - write(' '); - write('\u001b[' + color + 'm' + n + '\u001b[0m'); - write('\n'); - } - - draw(colors.green, stats.passes); - draw(colors.fail, stats.failures); - draw(colors.pending, stats.pending); - write('\n'); - - this.cursorUp(this.numberOfLines); -}; - -/** - * Append the rainbow. - * - * @api private - */ - -NyanCat.prototype.appendRainbow = function(){ - var segment = this.tick ? '_' : '-'; - var rainbowified = this.rainbowify(segment); - - for (var index = 0; index < this.numberOfLines; index++) { - var trajectory = this.trajectories[index]; - if (trajectory.length >= this.trajectoryWidthMax) trajectory.shift(); - trajectory.push(rainbowified); - } -}; - -/** - * Draw the rainbow. - * - * @api private - */ - -NyanCat.prototype.drawRainbow = function(){ - var self = this; - - this.trajectories.forEach(function(line, index) { - write('\u001b[' + self.scoreboardWidth + 'C'); - write(line.join('')); - write('\n'); - }); - - this.cursorUp(this.numberOfLines); -}; - -/** - * Draw the nyan cat with `status`. - * - * @param {String} status - * @api private - */ - -NyanCat.prototype.drawNyanCat = function(status) { - var self = this; - var startWidth = this.scoreboardWidth + this.trajectories[0].length; - - [0, 1, 2, 3].forEach(function(index) { - write('\u001b[' + startWidth + 'C'); - - switch (index) { - case 0: - write('_,------,'); - write('\n'); - break; - case 1: - var padding = self.tick ? ' ' : ' '; - write('_|' + padding + '/\\_/\\ '); - write('\n'); - break; - case 2: - var padding = self.tick ? '_' : '__'; - var tail = self.tick ? '~' : '^'; - var face; - switch (status) { - case 'pass': - face = '( ^ .^)'; - break; - case 'fail': - face = '( o .o)'; - break; - default: - face = '( - .-)'; - } - write(tail + '|' + padding + face + ' '); - write('\n'); - break; - case 3: - var padding = self.tick ? ' ' : ' '; - write(padding + '"" "" '); - write('\n'); - break; - } - }); - - this.cursorUp(this.numberOfLines); -}; - -/** - * Move cursor up `n`. - * - * @param {Number} n - * @api private - */ - -NyanCat.prototype.cursorUp = function(n) { - write('\u001b[' + n + 'A'); -}; - -/** - * Move cursor down `n`. - * - * @param {Number} n - * @api private - */ - -NyanCat.prototype.cursorDown = function(n) { - write('\u001b[' + n + 'B'); -}; - -/** - * Generate rainbow colors. - * - * @return {Array} - * @api private - */ - -NyanCat.prototype.generateColors = function(){ - var colors = []; - - for (var i = 0; i < (6 * 7); i++) { - var pi3 = Math.floor(Math.PI / 3); - var n = (i * (1.0 / 6)); - var r = Math.floor(3 * Math.sin(n) + 3); - var g = Math.floor(3 * Math.sin(n + 2 * pi3) + 3); - var b = Math.floor(3 * Math.sin(n + 4 * pi3) + 3); - colors.push(36 * r + 6 * g + b + 16); - } - - return colors; -}; - -/** - * Apply rainbow to the given `str`. - * - * @param {String} str - * @return {String} - * @api private - */ - -NyanCat.prototype.rainbowify = function(str){ - var color = this.rainbowColors[this.colorIndex % this.rainbowColors.length]; - this.colorIndex += 1; - return '\u001b[38;5;' + color + 'm' + str + '\u001b[0m'; -}; - -/** - * Stdout helper. - */ - -function write(string) { - process.stdout.write(string); -} - -/** - * Inherit from `Base.prototype`. - */ - -NyanCat.prototype = new Base; -NyanCat.prototype.constructor = NyanCat; - - -}); // module: reporters/nyan.js - -require.register("reporters/progress.js", function(module, exports, require){ - -/** - * Module dependencies. - */ - -var Base = require('./base') - , cursor = Base.cursor - , color = Base.color; - -/** - * Expose `Progress`. - */ - -exports = module.exports = Progress; - -/** - * General progress bar color. - */ - -Base.colors.progress = 90; - -/** - * Initialize a new `Progress` bar test reporter. - * - * @param {Runner} runner - * @param {Object} options - * @api public - */ - -function Progress(runner, options) { - Base.call(this, runner); - - var self = this - , options = options || {} - , stats = this.stats - , width = Base.window.width * .50 | 0 - , total = runner.total - , complete = 0 - , max = Math.max; - - // default chars - options.open = options.open || '['; - options.complete = options.complete || '▬'; - options.incomplete = options.incomplete || '⋅'; - options.close = options.close || ']'; - options.verbose = false; - - // tests started - runner.on('start', function(){ - console.log(); - cursor.hide(); - }); - - // tests complete - runner.on('test end', function(){ - complete++; - var incomplete = total - complete - , percent = complete / total - , n = width * percent | 0 - , i = width - n; - - cursor.CR(); - process.stdout.write('\u001b[J'); - process.stdout.write(color('progress', ' ' + options.open)); - process.stdout.write(Array(n).join(options.complete)); - process.stdout.write(Array(i).join(options.incomplete)); - process.stdout.write(color('progress', options.close)); - if (options.verbose) { - process.stdout.write(color('progress', ' ' + complete + ' of ' + total)); - } - }); - - // tests are complete, output some stats - // and the failures if any - runner.on('end', function(){ - cursor.show(); - console.log(); - self.epilogue(); - }); -} - -/** - * Inherit from `Base.prototype`. - */ - -Progress.prototype = new Base; -Progress.prototype.constructor = Progress; - - -}); // module: reporters/progress.js - -require.register("reporters/spec.js", function(module, exports, require){ - -/** - * Module dependencies. - */ - -var Base = require('./base') - , cursor = Base.cursor - , color = Base.color; - -/** - * Expose `Spec`. - */ - -exports = module.exports = Spec; - -/** - * Initialize a new `Spec` test reporter. - * - * @param {Runner} runner - * @api public - */ - -function Spec(runner) { - Base.call(this, runner); - - var self = this - , stats = this.stats - , indents = 0 - , n = 0; - - function indent() { - return Array(indents).join(' ') - } - - runner.on('start', function(){ - console.log(); - }); - - runner.on('suite', function(suite){ - ++indents; - console.log(color('suite', '%s%s'), indent(), suite.title); - }); - - runner.on('suite end', function(suite){ - --indents; - if (1 == indents) console.log(); - }); - - runner.on('test', function(test){ - process.stdout.write(indent() + color('pass', ' ◦ ' + test.title + ': ')); - }); - - runner.on('pending', function(test){ - var fmt = indent() + color('pending', ' - %s'); - console.log(fmt, test.title); - }); - - runner.on('pass', function(test){ - if ('fast' == test.speed) { - var fmt = indent() - + color('checkmark', ' ✓') - + color('pass', ' %s '); - cursor.CR(); - console.log(fmt, test.title); - } else { - var fmt = indent() - + color('checkmark', ' ✓') - + color('pass', ' %s ') - + color(test.speed, '(%dms)'); - cursor.CR(); - console.log(fmt, test.title, test.duration); - } - }); - - runner.on('fail', function(test, err){ - cursor.CR(); - console.log(indent() + color('fail', ' %d) %s'), ++n, test.title); - }); - - runner.on('end', self.epilogue.bind(self)); -} - -/** - * Inherit from `Base.prototype`. - */ - -Spec.prototype = new Base; -Spec.prototype.constructor = Spec; - - -}); // module: reporters/spec.js - -require.register("reporters/tap.js", function(module, exports, require){ - -/** - * Module dependencies. - */ - -var Base = require('./base') - , cursor = Base.cursor - , color = Base.color; - -/** - * Expose `TAP`. - */ - -exports = module.exports = TAP; - -/** - * Initialize a new `TAP` reporter. - * - * @param {Runner} runner - * @api public - */ - -function TAP(runner) { - Base.call(this, runner); - - var self = this - , stats = this.stats - , total = runner.total - , n = 1; - - runner.on('start', function(){ - console.log('%d..%d', 1, total); - }); - - runner.on('test end', function(){ - ++n; - }); - - runner.on('pending', function(test){ - console.log('ok %d %s # SKIP -', n, title(test)); - }); - - runner.on('pass', function(test){ - console.log('ok %d %s', n, title(test)); - }); - - runner.on('fail', function(test, err){ - console.log('not ok %d %s', n, title(test)); - console.log(err.stack.replace(/^/gm, ' ')); - }); -} - -/** - * Return a TAP-safe title of `test` - * - * @param {Object} test - * @return {String} - * @api private - */ - -function title(test) { - return test.fullTitle().replace(/#/g, ''); -} - -}); // module: reporters/tap.js - -require.register("reporters/teamcity.js", function(module, exports, require){ - -/** - * Module dependencies. - */ - -var Base = require('./base'); - -/** - * Expose `Teamcity`. - */ - -exports = module.exports = Teamcity; - -/** - * Initialize a new `Teamcity` reporter. - * - * @param {Runner} runner - * @api public - */ - -function Teamcity(runner) { - Base.call(this, runner); - var stats = this.stats; - - runner.on('start', function() { - console.log("##teamcity[testSuiteStarted name='mocha.suite']"); - }); - - runner.on('test', function(test) { - console.log("##teamcity[testStarted name='" + escape(test.fullTitle()) + "']"); - }); - - runner.on('fail', function(test, err) { - console.log("##teamcity[testFailed name='" + escape(test.fullTitle()) + "' message='" + escape(err.message) + "']"); - }); - - runner.on('pending', function(test) { - console.log("##teamcity[testIgnored name='" + escape(test.fullTitle()) + "' message='pending']"); - }); - - runner.on('test end', function(test) { - console.log("##teamcity[testFinished name='" + escape(test.fullTitle()) + "' duration='" + test.duration + "']"); - }); - - runner.on('end', function() { - console.log("##teamcity[testSuiteFinished name='mocha.suite' duration='" + stats.duration + "']"); - }); -} - -/** - * Escape the given `str`. - */ - -function escape(str) { - return str - .replace(/\|/g, "||") - .replace(/\n/g, "|n") - .replace(/\r/g, "|r") - .replace(/\[/g, "|[") - .replace(/\]/g, "|]") - .replace(/\u0085/g, "|x") - .replace(/\u2028/g, "|l") - .replace(/\u2029/g, "|p") - .replace(/'/g, "|'"); -} - -}); // module: reporters/teamcity.js - -require.register("reporters/xunit.js", function(module, exports, require){ - -/** - * Module dependencies. - */ - -var Base = require('./base') - , utils = require('../utils') - , escape = utils.escape; - -/** - * Save timer references to avoid Sinon interfering (see GH-237). - */ - -var Date = global.Date - , setTimeout = global.setTimeout - , setInterval = global.setInterval - , clearTimeout = global.clearTimeout - , clearInterval = global.clearInterval; - -/** - * Expose `XUnit`. - */ - -exports = module.exports = XUnit; - -/** - * Initialize a new `XUnit` reporter. - * - * @param {Runner} runner - * @api public - */ - -function XUnit(runner) { - Base.call(this, runner); - var stats = this.stats - , tests = [] - , self = this; - - runner.on('pass', function(test){ - tests.push(test); - }); - - runner.on('fail', function(test){ - tests.push(test); - }); - - runner.on('end', function(){ - console.log(tag('testsuite', { - name: 'Mocha Tests' - , tests: stats.tests - , failures: stats.failures - , errors: stats.failures - , skip: stats.tests - stats.failures - stats.passes - , timestamp: (new Date).toUTCString() - , time: stats.duration / 1000 - }, false)); - - tests.forEach(test); - console.log(''); - }); -} - -/** - * Inherit from `Base.prototype`. - */ - -XUnit.prototype = new Base; -XUnit.prototype.constructor = XUnit; - - -/** - * Output tag for the given `test.` - */ - -function test(test) { - var attrs = { - classname: test.parent.fullTitle() - , name: test.title - , time: test.duration / 1000 - }; - - if ('failed' == test.state) { - var err = test.err; - attrs.message = escape(err.message); - console.log(tag('testcase', attrs, false, tag('failure', attrs, false, cdata(err.stack)))); - } else if (test.pending) { - console.log(tag('testcase', attrs, false, tag('skipped', {}, true))); - } else { - console.log(tag('testcase', attrs, true) ); - } -} - -/** - * HTML tag helper. - */ - -function tag(name, attrs, close, content) { - var end = close ? '/>' : '>' - , pairs = [] - , tag; - - for (var key in attrs) { - pairs.push(key + '="' + escape(attrs[key]) + '"'); - } - - tag = '<' + name + (pairs.length ? ' ' + pairs.join(' ') : '') + end; - if (content) tag += content + ''; -} - -}); // module: reporters/xunit.js - -require.register("runnable.js", function(module, exports, require){ - -/** - * Module dependencies. - */ - -var EventEmitter = require('browser/events').EventEmitter - , debug = require('browser/debug')('mocha:runnable'); - -/** - * Save timer references to avoid Sinon interfering (see GH-237). - */ - -var Date = global.Date - , setTimeout = global.setTimeout - , setInterval = global.setInterval - , clearTimeout = global.clearTimeout - , clearInterval = global.clearInterval; - -/** - * Expose `Runnable`. - */ - -module.exports = Runnable; - -/** - * Initialize a new `Runnable` with the given `title` and callback `fn`. - * - * @param {String} title - * @param {Function} fn - * @api private - */ - -function Runnable(title, fn) { - this.title = title; - this.fn = fn; - this.async = fn && fn.length; - this.sync = ! this.async; - this._timeout = 2000; - this.timedOut = false; -} - -/** - * Inherit from `EventEmitter.prototype`. - */ - -Runnable.prototype = new EventEmitter; -Runnable.prototype.constructor = Runnable; - - -/** - * Set & get timeout `ms`. - * - * @param {Number} ms - * @return {Runnable|Number} ms or self - * @api private - */ - -Runnable.prototype.timeout = function(ms){ - if (0 == arguments.length) return this._timeout; - debug('timeout %d', ms); - this._timeout = ms; - if (this.timer) this.resetTimeout(); - return this; -}; - -/** - * Return the full title generated by recursively - * concatenating the parent's full title. - * - * @return {String} - * @api public - */ - -Runnable.prototype.fullTitle = function(){ - return this.parent.fullTitle() + ' ' + this.title; -}; - -/** - * Clear the timeout. - * - * @api private - */ - -Runnable.prototype.clearTimeout = function(){ - clearTimeout(this.timer); -}; - -/** - * Inspect the runnable void of private properties. - * - * @return {String} - * @api private - */ - -Runnable.prototype.inspect = function(){ - return JSON.stringify(this, function(key, val){ - if ('_' == key[0]) return; - if ('parent' == key) return '#'; - if ('ctx' == key) return '#'; - return val; - }, 2); -}; - -/** - * Reset the timeout. - * - * @api private - */ - -Runnable.prototype.resetTimeout = function(){ - var self = this - , ms = this.timeout(); - - this.clearTimeout(); - if (ms) { - this.timer = setTimeout(function(){ - self.callback(new Error('timeout of ' + ms + 'ms exceeded')); - self.timedOut = true; - }, ms); - } -}; - -/** - * Run the test and invoke `fn(err)`. - * - * @param {Function} fn - * @api private - */ - -Runnable.prototype.run = function(fn){ - var self = this - , ms = this.timeout() - , start = new Date - , ctx = this.ctx - , finished - , emitted; - - if (ctx) ctx.runnable(this); - - // timeout - if (this.async) { - if (ms) { - this.timer = setTimeout(function(){ - done(new Error('timeout of ' + ms + 'ms exceeded')); - self.timedOut = true; - }, ms); - } - } - - // called multiple times - function multiple(err) { - if (emitted) return; - emitted = true; - self.emit('error', err || new Error('done() called multiple times')); - } - - // finished - function done(err) { - if (self.timedOut) return; - if (finished) return multiple(err); - self.clearTimeout(); - self.duration = new Date - start; - finished = true; - fn(err); - } - - // for .resetTimeout() - this.callback = done; - - // async - if (this.async) { - try { - this.fn.call(ctx, function(err){ - if (err instanceof Error) return done(err); - if (null != err) return done(new Error('done() invoked with non-Error: ' + err)); - done(); - }); - } catch (err) { - done(err); - } - return; - } - - // sync - try { - if (!this.pending) this.fn.call(ctx); - this.duration = new Date - start; - fn(); - } catch (err) { - fn(err); - } -}; - -}); // module: runnable.js - -require.register("runner.js", function(module, exports, require){ - -/** - * Module dependencies. - */ - -var EventEmitter = require('browser/events').EventEmitter - , debug = require('browser/debug')('mocha:runner') - , Test = require('./test') - , utils = require('./utils') - , filter = utils.filter - , keys = utils.keys - , noop = function(){}; - -/** - * Expose `Runner`. - */ - -module.exports = Runner; - -/** - * Initialize a `Runner` for the given `suite`. - * - * Events: - * - * - `start` execution started - * - `end` execution complete - * - `suite` (suite) test suite execution started - * - `suite end` (suite) all tests (and sub-suites) have finished - * - `test` (test) test execution started - * - `test end` (test) test completed - * - `hook` (hook) hook execution started - * - `hook end` (hook) hook complete - * - `pass` (test) test passed - * - `fail` (test, err) test failed - * - * @api public - */ - -function Runner(suite) { - var self = this; - this._globals = []; - this.suite = suite; - this.total = suite.total(); - this.failures = 0; - this.on('test end', function(test){ self.checkGlobals(test); }); - this.on('hook end', function(hook){ self.checkGlobals(hook); }); - this.grep(/.*/); - this.globals(utils.keys(global).concat(['errno'])); -} - -/** - * Inherit from `EventEmitter.prototype`. - */ - -Runner.prototype = new EventEmitter; -Runner.prototype.constructor = Runner; - - -/** - * Run tests with full titles matching `re`. Updates runner.total - * with number of tests matched. - * - * @param {RegExp} re - * @param {Boolean} invert - * @return {Runner} for chaining - * @api public - */ - -Runner.prototype.grep = function(re, invert){ - debug('grep %s', re); - this._grep = re; - this._invert = invert; - this.total = this.grepTotal(this.suite); - return this; -}; - -/** - * Returns the number of tests matching the grep search for the - * given suite. - * - * @param {Suite} suite - * @return {Number} - * @api public - */ - -Runner.prototype.grepTotal = function(suite) { - var self = this; - var total = 0; - - suite.eachTest(function(test){ - var match = self._grep.test(test.fullTitle()); - if (self._invert) match = !match; - if (match) total++; - }); - - return total; -}; - -/** - * Allow the given `arr` of globals. - * - * @param {Array} arr - * @return {Runner} for chaining - * @api public - */ - -Runner.prototype.globals = function(arr){ - if (0 == arguments.length) return this._globals; - debug('globals %j', arr); - utils.forEach(arr, function(arr){ - this._globals.push(arr); - }, this); - return this; -}; - -/** - * Check for global variable leaks. - * - * @api private - */ - -Runner.prototype.checkGlobals = function(test){ - if (this.ignoreLeaks) return; - var leaks = filterLeaks(this._globals); - - this._globals = this._globals.concat(leaks); - - if (leaks.length > 1) { - this.fail(test, new Error('global leaks detected: ' + leaks.join(', ') + '')); - } else if (leaks.length) { - this.fail(test, new Error('global leak detected: ' + leaks[0])); - } -}; - -/** - * Fail the given `test`. - * - * @param {Test} test - * @param {Error} err - * @api private - */ - -Runner.prototype.fail = function(test, err){ - ++this.failures; - test.state = 'failed'; - if ('string' == typeof err) { - err = new Error('the string "' + err + '" was thrown, throw an Error :)'); - } - this.emit('fail', test, err); -}; - -/** - * Fail the given `hook` with `err`. - * - * Hook failures (currently) hard-end due - * to that fact that a failing hook will - * surely cause subsequent tests to fail, - * causing jumbled reporting. - * - * @param {Hook} hook - * @param {Error} err - * @api private - */ - -Runner.prototype.failHook = function(hook, err){ - this.fail(hook, err); - this.emit('end'); -}; - -/** - * Run hook `name` callbacks and then invoke `fn()`. - * - * @param {String} name - * @param {Function} function - * @api private - */ - -Runner.prototype.hook = function(name, fn){ - var suite = this.suite - , hooks = suite['_' + name] - , ms = suite._timeout - , self = this - , timer; - - function next(i) { - var hook = hooks[i]; - if (!hook) return fn(); - self.currentRunnable = hook; - - self.emit('hook', hook); - - hook.on('error', function(err){ - self.failHook(hook, err); - }); - - hook.run(function(err){ - hook.removeAllListeners('error'); - var testError = hook.error(); - if (testError) self.fail(self.test, testError); - if (err) return self.failHook(hook, err); - self.emit('hook end', hook); - next(++i); - }); - } - - process.nextTick(function(){ - next(0); - }); -}; - -/** - * Run hook `name` for the given array of `suites` - * in order, and callback `fn(err)`. - * - * @param {String} name - * @param {Array} suites - * @param {Function} fn - * @api private - */ - -Runner.prototype.hooks = function(name, suites, fn){ - var self = this - , orig = this.suite; - - function next(suite) { - self.suite = suite; - - if (!suite) { - self.suite = orig; - return fn(); - } - - self.hook(name, function(err){ - if (err) { - self.suite = orig; - return fn(err); - } - - next(suites.pop()); - }); - } - - next(suites.pop()); -}; - -/** - * Run hooks from the top level down. - * - * @param {String} name - * @param {Function} fn - * @api private - */ - -Runner.prototype.hookUp = function(name, fn){ - var suites = [this.suite].concat(this.parents()).reverse(); - this.hooks(name, suites, fn); -}; - -/** - * Run hooks from the bottom up. - * - * @param {String} name - * @param {Function} fn - * @api private - */ - -Runner.prototype.hookDown = function(name, fn){ - var suites = [this.suite].concat(this.parents()); - this.hooks(name, suites, fn); -}; - -/** - * Return an array of parent Suites from - * closest to furthest. - * - * @return {Array} - * @api private - */ - -Runner.prototype.parents = function(){ - var suite = this.suite - , suites = []; - while (suite = suite.parent) suites.push(suite); - return suites; -}; - -/** - * Run the current test and callback `fn(err)`. - * - * @param {Function} fn - * @api private - */ - -Runner.prototype.runTest = function(fn){ - var test = this.test - , self = this; - - try { - test.on('error', function(err){ - self.fail(test, err); - }); - test.run(fn); - } catch (err) { - fn(err); - } -}; - -/** - * Run tests in the given `suite` and invoke - * the callback `fn()` when complete. - * - * @param {Suite} suite - * @param {Function} fn - * @api private - */ - -Runner.prototype.runTests = function(suite, fn){ - var self = this - , tests = suite.tests - , test; - - function next(err) { - // if we bail after first err - if (self.failures && suite._bail) return fn(); - - // next test - test = tests.shift(); - - // all done - if (!test) return fn(); - - // grep - var match = self._grep.test(test.fullTitle()); - if (self._invert) match = !match; - if (!match) return next(); - - // pending - if (test.pending) { - self.emit('pending', test); - self.emit('test end', test); - return next(); - } - - // execute test and hook(s) - self.emit('test', self.test = test); - self.hookDown('beforeEach', function(){ - self.currentRunnable = self.test; - self.runTest(function(err){ - test = self.test; - - if (err) { - self.fail(test, err); - self.emit('test end', test); - return self.hookUp('afterEach', next); - } - - test.state = 'passed'; - self.emit('pass', test); - self.emit('test end', test); - self.hookUp('afterEach', next); - }); - }); - } - - this.next = next; - next(); -}; - -/** - * Run the given `suite` and invoke the - * callback `fn()` when complete. - * - * @param {Suite} suite - * @param {Function} fn - * @api private - */ - -Runner.prototype.runSuite = function(suite, fn){ - var total = this.grepTotal(suite) - , self = this - , i = 0; - - debug('run suite %s', suite.fullTitle()); - - if (!total) return fn(); - - this.emit('suite', this.suite = suite); - - function next() { - var curr = suite.suites[i++]; - if (!curr) return done(); - self.runSuite(curr, next); - } - - function done() { - self.suite = suite; - self.hook('afterAll', function(){ - self.emit('suite end', suite); - fn(); - }); - } - - this.hook('beforeAll', function(){ - self.runTests(suite, next); - }); -}; - -/** - * Handle uncaught exceptions. - * - * @param {Error} err - * @api private - */ - -Runner.prototype.uncaught = function(err){ - debug('uncaught exception %s', err.message); - var runnable = this.currentRunnable; - if (!runnable || 'failed' == runnable.state) return; - runnable.clearTimeout(); - err.uncaught = true; - this.fail(runnable, err); - - // recover from test - if ('test' == runnable.type) { - this.emit('test end', runnable); - this.hookUp('afterEach', this.next); - return; - } - - // bail on hooks - this.emit('end'); -}; - -/** - * Run the root suite and invoke `fn(failures)` - * on completion. - * - * @param {Function} fn - * @return {Runner} for chaining - * @api public - */ - -Runner.prototype.run = function(fn){ - var self = this - , fn = fn || function(){}; - - debug('start'); - - // uncaught callback - function uncaught(err) { - self.uncaught(err); - } - - // callback - this.on('end', function(){ - debug('end'); - process.removeListener('uncaughtException', uncaught); - fn(self.failures); - }); - - // run suites - this.emit('start'); - this.runSuite(this.suite, function(){ - debug('finished running'); - self.emit('end'); - }); - - // uncaught exception - process.on('uncaughtException', uncaught); - - return this; -}; - -/** - * Filter leaks with the given globals flagged as `ok`. - * - * @param {Array} ok - * @return {Array} - * @api private - */ - -function filterLeaks(ok) { - return filter(keys(global), function(key){ - var matched = filter(ok, function(ok){ - if (~ok.indexOf('*')) return 0 == key.indexOf(ok.split('*')[0]); - return key == ok; - }); - return matched.length == 0 && (!global.navigator || 'onerror' !== key); - }); -} -}); // module: runner.js - -require.register("suite.js", function(module, exports, require){ - -/** - * Module dependencies. - */ - -var EventEmitter = require('browser/events').EventEmitter - , debug = require('browser/debug')('mocha:suite') - , utils = require('./utils') - , Hook = require('./hook'); - -/** - * Expose `Suite`. - */ - -exports = module.exports = Suite; - -/** - * Create a new `Suite` with the given `title` - * and parent `Suite`. When a suite with the - * same title is already present, that suite - * is returned to provide nicer reporter - * and more flexible meta-testing. - * - * @param {Suite} parent - * @param {String} title - * @return {Suite} - * @api public - */ - -exports.create = function(parent, title){ - var suite = new Suite(title, parent.ctx); - suite.parent = parent; - if (parent.pending) suite.pending = true; - title = suite.fullTitle(); - parent.addSuite(suite); - return suite; -}; - -/** - * Initialize a new `Suite` with the given - * `title` and `ctx`. - * - * @param {String} title - * @param {Context} ctx - * @api private - */ - -function Suite(title, ctx) { - this.title = title; - this.ctx = ctx; - this.suites = []; - this.tests = []; - this.pending = false; - this._beforeEach = []; - this._beforeAll = []; - this._afterEach = []; - this._afterAll = []; - this.root = !title; - this._timeout = 2000; - this._bail = false; -} - -/** - * Inherit from `EventEmitter.prototype`. - */ - -Suite.prototype = new EventEmitter; -Suite.prototype.constructor = Suite; - - -/** - * Return a clone of this `Suite`. - * - * @return {Suite} - * @api private - */ - -Suite.prototype.clone = function(){ - var suite = new Suite(this.title); - debug('clone'); - suite.ctx = this.ctx; - suite.timeout(this.timeout()); - suite.bail(this.bail()); - return suite; -}; - -/** - * Set timeout `ms` or short-hand such as "2s". - * - * @param {Number|String} ms - * @return {Suite|Number} for chaining - * @api private - */ - -Suite.prototype.timeout = function(ms){ - if (0 == arguments.length) return this._timeout; - if (String(ms).match(/s$/)) ms = parseFloat(ms) * 1000; - debug('timeout %d', ms); - this._timeout = parseInt(ms, 10); - return this; -}; - -/** - * Sets whether to bail after first error. - * - * @parma {Boolean} bail - * @return {Suite|Number} for chaining - * @api private - */ - -Suite.prototype.bail = function(bail){ - if (0 == arguments.length) return this._bail; - debug('bail %s', bail); - this._bail = bail; - return this; -}; - -/** - * Run `fn(test[, done])` before running tests. - * - * @param {Function} fn - * @return {Suite} for chaining - * @api private - */ - -Suite.prototype.beforeAll = function(fn){ - if (this.pending) return this; - var hook = new Hook('"before all" hook', fn); - hook.parent = this; - hook.timeout(this.timeout()); - hook.ctx = this.ctx; - this._beforeAll.push(hook); - this.emit('beforeAll', hook); - return this; -}; - -/** - * Run `fn(test[, done])` after running tests. - * - * @param {Function} fn - * @return {Suite} for chaining - * @api private - */ - -Suite.prototype.afterAll = function(fn){ - if (this.pending) return this; - var hook = new Hook('"after all" hook', fn); - hook.parent = this; - hook.timeout(this.timeout()); - hook.ctx = this.ctx; - this._afterAll.push(hook); - this.emit('afterAll', hook); - return this; -}; - -/** - * Run `fn(test[, done])` before each test case. - * - * @param {Function} fn - * @return {Suite} for chaining - * @api private - */ - -Suite.prototype.beforeEach = function(fn){ - if (this.pending) return this; - var hook = new Hook('"before each" hook', fn); - hook.parent = this; - hook.timeout(this.timeout()); - hook.ctx = this.ctx; - this._beforeEach.push(hook); - this.emit('beforeEach', hook); - return this; -}; - -/** - * Run `fn(test[, done])` after each test case. - * - * @param {Function} fn - * @return {Suite} for chaining - * @api private - */ - -Suite.prototype.afterEach = function(fn){ - if (this.pending) return this; - var hook = new Hook('"after each" hook', fn); - hook.parent = this; - hook.timeout(this.timeout()); - hook.ctx = this.ctx; - this._afterEach.push(hook); - this.emit('afterEach', hook); - return this; -}; - -/** - * Add a test `suite`. - * - * @param {Suite} suite - * @return {Suite} for chaining - * @api private - */ - -Suite.prototype.addSuite = function(suite){ - suite.parent = this; - suite.timeout(this.timeout()); - suite.bail(this.bail()); - this.suites.push(suite); - this.emit('suite', suite); - return this; -}; - -/** - * Add a `test` to this suite. - * - * @param {Test} test - * @return {Suite} for chaining - * @api private - */ - -Suite.prototype.addTest = function(test){ - test.parent = this; - test.timeout(this.timeout()); - test.ctx = this.ctx; - this.tests.push(test); - this.emit('test', test); - return this; -}; - -/** - * Return the full title generated by recursively - * concatenating the parent's full title. - * - * @return {String} - * @api public - */ - -Suite.prototype.fullTitle = function(){ - if (this.parent) { - var full = this.parent.fullTitle(); - if (full) return full + ' ' + this.title; - } - return this.title; -}; - -/** - * Return the total number of tests. - * - * @return {Number} - * @api public - */ - -Suite.prototype.total = function(){ - return utils.reduce(this.suites, function(sum, suite){ - return sum + suite.total(); - }, 0) + this.tests.length; -}; - -/** - * Iterates through each suite recursively to find - * all tests. Applies a function in the format - * `fn(test)`. - * - * @param {Function} fn - * @return {Suite} - * @api private - */ - -Suite.prototype.eachTest = function(fn){ - utils.forEach(this.tests, fn); - utils.forEach(this.suites, function(suite){ - suite.eachTest(fn); - }); - return this; -}; - -}); // module: suite.js - -require.register("test.js", function(module, exports, require){ - -/** - * Module dependencies. - */ - -var Runnable = require('./runnable'); - -/** - * Expose `Test`. - */ - -module.exports = Test; - -/** - * Initialize a new `Test` with the given `title` and callback `fn`. - * - * @param {String} title - * @param {Function} fn - * @api private - */ - -function Test(title, fn) { - Runnable.call(this, title, fn); - this.pending = !fn; - this.type = 'test'; -} - -/** - * Inherit from `Runnable.prototype`. - */ - -Test.prototype = new Runnable; -Test.prototype.constructor = Test; - - -}); // module: test.js - -require.register("utils.js", function(module, exports, require){ - -/** - * Module dependencies. - */ - -var fs = require('browser/fs') - , path = require('browser/path') - , join = path.join - , debug = require('browser/debug')('mocha:watch'); - -/** - * Ignored directories. - */ - -var ignore = ['node_modules', '.git']; - -/** - * Escape special characters in the given string of html. - * - * @param {String} html - * @return {String} - * @api private - */ - -exports.escape = function(html) { - return String(html) - .replace(/&/g, '&') - .replace(/"/g, '"') - .replace(//g, '>'); -}; - -/** - * Array#forEach (<=IE8) - * - * @param {Array} array - * @param {Function} fn - * @param {Object} scope - * @api private - */ - -exports.forEach = function(arr, fn, scope) { - for (var i = 0, l = arr.length; i < l; i++) - fn.call(scope, arr[i], i); -}; - -/** - * Array#indexOf (<=IE8) - * - * @parma {Array} arr - * @param {Object} obj to find index of - * @param {Number} start - * @api private - */ - -exports.indexOf = function (arr, obj, start) { - for (var i = start || 0, l = arr.length; i < l; i++) { - if (arr[i] === obj) - return i; - } - return -1; -}; - -/** - * Array#reduce (<=IE8) - * - * @param {Array} array - * @param {Function} fn - * @param {Object} initial value - * @param {Object} scope - * @api private - */ - -exports.reduce = function(arr, fn, val, scope) { - var rval = val; - - for (var i = 0, l = arr.length; i < l; i++) { - rval = fn.call(scope, rval, arr[i], i, arr); - } - - return rval; -}; - -/** - * Array#filter (<=IE8) - * - * @param {Array} array - * @param {Function} fn - * @param {Object} scope - * @api private - */ - -exports.filter = function(arr, fn, scope) { - var ret = []; - - for (var i = 0, l = arr.length; i < l; i++) { - var val = arr[i]; - if (fn.call(scope, val, i, arr)) - ret.push(val); - } - - return ret; -}; - -/** - * Object.keys (<=IE8) - * - * @param {Object} obj - * @return {Array} keys - * @api private - */ - -exports.keys = Object.keys || function(obj) { - var keys = [] - , has = Object.prototype.hasOwnProperty // for `window` on <=IE8 - - for (var key in obj) { - if (has.call(obj, key)) { - keys.push(key); - } - } - - return keys; -}; - -/** - * Watch the given `files` for changes - * and invoke `fn(file)` on modification. - * - * @param {Array} files - * @param {Function} fn - * @api private - */ - -exports.watch = function(files, fn){ - var options = { interval: 100 }; - files.forEach(function(file){ - debug('file %s', file); - fs.watchFile(file, options, function(curr, prev){ - if (prev.mtime < curr.mtime) fn(file); - }); - }); -}; - -/** - * Ignored files. - */ - -function ignored(path){ - return !~ignore.indexOf(path); -} - -/** - * Lookup files in the given `dir`. - * - * @return {Array} - * @api private - */ - -exports.files = function(dir, ret){ - ret = ret || []; - - fs.readdirSync(dir) - .filter(ignored) - .forEach(function(path){ - path = join(dir, path); - if (fs.statSync(path).isDirectory()) { - exports.files(path, ret); - } else if (path.match(/\.(js|coffee)$/)) { - ret.push(path); - } - }); - - return ret; -}; - -/** - * Compute a slug from the given `str`. - * - * @param {String} str - * @return {String} - * @api private - */ - -exports.slug = function(str){ - return str - .toLowerCase() - .replace(/ +/g, '-') - .replace(/[^-\w]/g, ''); -}; - -/** - * Strip the function definition from `str`, - * and re-indent for pre whitespace. - */ - -exports.clean = function(str) { - str = str - .replace(/^function *\(.*\) *{/, '') - .replace(/\s+\}$/, ''); - - var spaces = str.match(/^\n?( *)/)[1].length - , re = new RegExp('^ {' + spaces + '}', 'gm'); - - str = str.replace(re, ''); - - return str.trim(); -}; - -/** - * Escape regular expression characters in `str`. - * - * @param {String} str - * @return {String} - * @api private - */ - -exports.escapeRegexp = function(str){ - return str.replace(/[-\\^$*+?.()|[\]{}]/g, "\\$&"); -}; -}); // module: utils.js -/** - * Node shims. - * - * These are meant only to allow - * mocha.js to run untouched, not - * to allow running node code in - * the browser. - */ - -process = {}; -process.exit = function(status){}; -process.stdout = {}; -global = window; - -/** - * next tick implementation. - */ - -process.nextTick = (function(){ - // postMessage behaves badly on IE8 - if (window.ActiveXObject || !window.postMessage) { - return function(fn){ fn() }; - } - - // based on setZeroTimeout by David Baron - // - http://dbaron.org/log/20100309-faster-timeouts - var timeouts = [] - , name = 'mocha-zero-timeout' - - window.addEventListener('message', function(e){ - if (e.source == window && e.data == name) { - if (e.stopPropagation) e.stopPropagation(); - if (timeouts.length) timeouts.shift()(); - } - }, true); - - return function(fn){ - timeouts.push(fn); - window.postMessage(name, '*'); - } -})(); - -/** - * Remove uncaughtException listener. - */ - -process.removeListener = function(e){ - if ('uncaughtException' == e) { - window.onerror = null; - } -}; - -/** - * Implements uncaughtException listener. - */ - -process.on = function(e, fn){ - if ('uncaughtException' == e) { - window.onerror = fn; - } -}; - -/** - * Expose mocha. - */ - -window.mocha = require('mocha'); - -// boot -;(function(){ - var utils = mocha.utils - , options = {} - - mocha.suite = new mocha.Suite('', new mocha.Context()); - - /** - * Highlight the given string of `js`. - */ - - function highlight(js) { - return js - .replace(//g, '>') - .replace(/\/\/(.*)/gm, '//$1') - .replace(/('.*?')/gm, '$1') - .replace(/(\d+\.\d+)/gm, '$1') - .replace(/(\d+)/gm, '$1') - .replace(/\bnew *(\w+)/gm, 'new $1') - .replace(/\b(function|new|throw|return|var|if|else)\b/gm, '$1') - } - - /** - * Highlight code contents. - */ - - function highlightCode() { - var code = document.getElementsByTagName('code'); - for (var i = 0, len = code.length; i < len; ++i) { - code[i].innerHTML = highlight(code[i].innerHTML); - } - } - - /** - * Parse the given `qs`. - */ - - function parse(qs) { - return utils.reduce(qs.replace('?', '').split('&'), function(obj, pair){ - var i = pair.indexOf('=') - , key = pair.slice(0, i) - , val = pair.slice(++i); - - obj[key] = decodeURIComponent(val); - return obj; - }, {}); - } - - /** - * Setup mocha with the given setting options. - */ - - mocha.setup = function(opts){ - if ('string' === typeof opts) options.ui = opts; - else options = opts; - - ui = mocha.interfaces[options.ui]; - if (!ui) throw new Error('invalid mocha interface "' + ui + '"'); - if (options.timeout) mocha.suite.timeout(options.timeout); - ui(mocha.suite); - mocha.suite.emit('pre-require', window); - }; - - /** - * Run mocha, returning the Runner. - */ - - mocha.run = function(fn){ - mocha.suite.emit('run'); - var runner = new mocha.Runner(mocha.suite); - var Reporter = options.reporter || mocha.reporters.HTML; - var reporter = new Reporter(runner); - var query = parse(window.location.search || ""); - if (query.grep) runner.grep(new RegExp(query.grep)); - if (options.ignoreLeaks) runner.ignoreLeaks = true; - if (options.globals) runner.globals(options.globals); - runner.globals(['location']); - runner.on('end', highlightCode); - return runner.run(fn); - }; -})(); -})(); \ No newline at end of file diff --git a/test/runner.html b/test/runner.html deleted file mode 100755 index 6130020a1ee..00000000000 --- a/test/runner.html +++ /dev/null @@ -1,33 +0,0 @@ - - - Prebid.js UnitTests - - - - - - - - - - - - - - - - - - -
    - - diff --git a/test/spec/adUnits_spec.js b/test/spec/adUnits_spec.js index 5d2ffa4f1b2..bb781468ec0 100644 --- a/test/spec/adUnits_spec.js +++ b/test/spec/adUnits_spec.js @@ -1,128 +1,125 @@ -describe("Publisher API _ AdUnits", function() { - var assert = chai.assert, - should = chai.should(), - expect = chai.expect; - - before(function(){ - var adUnits = [{ - code: "/1996833/slot-1", - sizes: [[300, 250], [728, 90]], - bids: [ - { - bidder: "openx", - params: { - pgid: "2342353", - unit: "234234", - jstag_url: "http://" - } - },{ - bidder: "appnexus", - params: { - placementId: "234235" - } - } - ] - },{ - code: "/1996833/slot-2", - sizes: [[468, 60]], - bids: [ - { - bidder: "rubicon", - params: { - rp_account: "4934", - rp_site: "13945", - rp_zonesize: "23948-15" - } - },{ - bidder: "appnexus", - params: { - placementId: "827326" - } - } - ] - }]; - - pbjs.addAdUnits(adUnits); - }); - - after(function(){ - pbjs_testonly.clearAllAdUnits(); - }); - - - describe('addAdUnits', function() { - - var adUnits,adUnit1,bids1,adUnit2,bids2; - - it('should have two adUnits',function(){ - adUnits = pbjs_testonly.getAdUnits(); - adUnit1 = adUnits[0]; - bids1 = adUnit1.bids; - adUnit2 = adUnits[1]; - bids2 = adUnit2.bids; - }); - - it('the first adUnits value should be same with the adUnits that is added by pbjs.addAdUnits();',function(){ - assert.strictEqual(adUnit1.code,'/1996833/slot-1','adUnit1 code'); - assert.deepEqual(adUnit1.sizes,[[300, 250], [728, 90]],'adUnit1 sizes'); - assert.strictEqual(bids1[0].bidder,'openx','adUnit1 bids1 bidder'); - assert.strictEqual(bids1[0].params.pgid,'2342353','adUnit1 bids1 params.pgid'); - assert.strictEqual(bids1[0].params.unit,'234234','adUnit1 bids1 params.unit'); - assert.strictEqual(bids1[0].params.jstag_url,'http://','adUnit1 bids1 params.jstag_url'); - - assert.strictEqual(bids1[1].bidder,'appnexus','adUnit1 bids2 bidder'); - assert.strictEqual(bids1[1].params.placementId,'234235','adUnit1 bids2 params.placementId'); - - assert.strictEqual(adUnit2.code,'/1996833/slot-2','adUnit2 code'); - assert.deepEqual(adUnit2.sizes,[[468, 60]],'adUnit2 sizes'); - assert.strictEqual(bids2[0].bidder,'rubicon','adUnit2 bids1 bidder'); - assert.strictEqual(bids2[0].params.rp_account,'4934','adUnit2 bids1 params.rp_account'); - assert.strictEqual(bids2[0].params.rp_zonesize,'23948-15','adUnit2 bids1 params.rp_zonesize'); - assert.strictEqual(bids2[0].params.rp_site,'13945','adUnit2 bids1 params.rp_site'); - - assert.strictEqual(bids2[1].bidder,'appnexus','adUnit2 bids2 bidder'); - assert.strictEqual(bids2[1].params.placementId,'827326','adUnit2 bids2 params.placementId'); - }); - - it('the second adUnits value should be same with the adUnits that is added by pbjs.addAdUnits();',function(){ - - assert.strictEqual(adUnit2.code,'/1996833/slot-2','adUnit2 code'); - assert.deepEqual(adUnit2.sizes,[[468, 60]],'adUnit2 sizes'); - assert.strictEqual(bids2[0].bidder,'rubicon','adUnit2 bids1 bidder'); - assert.strictEqual(bids2[0].params.rp_account,'4934','adUnit2 bids1 params.rp_account'); - assert.strictEqual(bids2[0].params.rp_zonesize,'23948-15','adUnit2 bids1 params.rp_zonesize'); - assert.strictEqual(bids2[0].params.rp_site,'13945','adUnit2 bids1 params.rp_site'); - - assert.strictEqual(bids2[1].bidder,'appnexus','adUnit2 bids2 bidder'); - assert.strictEqual(bids2[1].params.placementId,'827326','adUnit2 bids2 params.placementId'); - }); - }); - - describe('removeAdUnit',function(){ - - var adUnits,adUnit2,bids2; - - - it('the first adUnit should be not existed',function(){ - pbjs.removeAdUnit('/1996833/slot-1'); - adUnits = pbjs_testonly.getAdUnits(); - adUnit2 = adUnits[0]; - bids2 = adUnit2.bids; - expect(adUnits[1]).not.exist; - }); - - it('the second adUnit should be still existed',function(){ - assert.strictEqual(adUnit2.code,'/1996833/slot-2','adUnit2 code'); - assert.deepEqual(adUnit2.sizes,[[468, 60]],'adUnit2 sizes'); - assert.strictEqual(bids2[0].bidder,'rubicon','adUnit2 bids1 bidder'); - assert.strictEqual(bids2[0].params.rp_account,'4934','adUnit2 bids1 params.rp_account'); - assert.strictEqual(bids2[0].params.rp_zonesize,'23948-15','adUnit2 bids1 params.rp_zonesize'); - assert.strictEqual(bids2[0].params.rp_site,'13945','adUnit2 bids1 params.rp_site'); - - assert.strictEqual(bids2[1].bidder,'appnexus','adUnit2 bids2 bidder'); - assert.strictEqual(bids2[1].params.placementId,'827326','adUnit2 bids2 params.placementId'); - }); - }); - - -}); +describe('Publisher API _ AdUnits', function () { + var assert = require('chai').assert; + var expect = require('chai').expect; + var pbjsTestOnly = require('../helpers/pbjs-test-only').pbjsTestOnly; + + before(function () { + var adUnits = [{ + code: '/1996833/slot-1', + sizes: [[300, 250], [728, 90]], + bids: [ + { + bidder: 'openx', + params: { + pgid: '2342353', + unit: '234234', + jstag_url: 'http://' + } + }, { + bidder: 'appnexus', + params: { + placementId: '234235' + } + } + ] + }, { + code: '/1996833/slot-2', + sizes: [[468, 60]], + bids: [ + { + bidder: 'rubicon', + params: { + rp_account: '4934', + rp_site: '13945', + rp_zonesize: '23948-15' + } + }, { + bidder: 'appnexus', + params: { + placementId: '827326' + } + } + ] + }]; + + pbjs.addAdUnits(adUnits); + }); + + after(function () { + pbjsTestOnly.clearAllAdUnits(); + }); + + describe('addAdUnits', function () { + + var adUnits, adUnit1, bids1, adUnit2, bids2; + + it('should have two adUnits', function () { + adUnits = pbjsTestOnly.getAdUnits(); + adUnit1 = adUnits[0]; + bids1 = adUnit1.bids; + adUnit2 = adUnits[1]; + bids2 = adUnit2.bids; + }); + + it('the first adUnits value should be same with the adUnits that is added by pbjs.addAdUnits();', function () { + assert.strictEqual(adUnit1.code, '/1996833/slot-1', 'adUnit1 code'); + assert.deepEqual(adUnit1.sizes, [[300, 250], [728, 90]], 'adUnit1 sizes'); + assert.strictEqual(bids1[0].bidder, 'openx', 'adUnit1 bids1 bidder'); + assert.strictEqual(bids1[0].params.pgid, '2342353', 'adUnit1 bids1 params.pgid'); + assert.strictEqual(bids1[0].params.unit, '234234', 'adUnit1 bids1 params.unit'); + assert.strictEqual(bids1[0].params.jstag_url, 'http://', 'adUnit1 bids1 params.jstag_url'); + + assert.strictEqual(bids1[1].bidder, 'appnexus', 'adUnit1 bids2 bidder'); + assert.strictEqual(bids1[1].params.placementId, '234235', 'adUnit1 bids2 params.placementId'); + + assert.strictEqual(adUnit2.code, '/1996833/slot-2', 'adUnit2 code'); + assert.deepEqual(adUnit2.sizes, [[468, 60]], 'adUnit2 sizes'); + assert.strictEqual(bids2[0].bidder, 'rubicon', 'adUnit2 bids1 bidder'); + assert.strictEqual(bids2[0].params.rp_account, '4934', 'adUnit2 bids1 params.rp_account'); + assert.strictEqual(bids2[0].params.rp_zonesize, '23948-15', 'adUnit2 bids1 params.rp_zonesize'); + assert.strictEqual(bids2[0].params.rp_site, '13945', 'adUnit2 bids1 params.rp_site'); + + assert.strictEqual(bids2[1].bidder, 'appnexus', 'adUnit2 bids2 bidder'); + assert.strictEqual(bids2[1].params.placementId, '827326', 'adUnit2 bids2 params.placementId'); + }); + + it('the second adUnits value should be same with the adUnits that is added by pbjs.addAdUnits();', function () { + + assert.strictEqual(adUnit2.code, '/1996833/slot-2', 'adUnit2 code'); + assert.deepEqual(adUnit2.sizes, [[468, 60]], 'adUnit2 sizes'); + assert.strictEqual(bids2[0].bidder, 'rubicon', 'adUnit2 bids1 bidder'); + assert.strictEqual(bids2[0].params.rp_account, '4934', 'adUnit2 bids1 params.rp_account'); + assert.strictEqual(bids2[0].params.rp_zonesize, '23948-15', 'adUnit2 bids1 params.rp_zonesize'); + assert.strictEqual(bids2[0].params.rp_site, '13945', 'adUnit2 bids1 params.rp_site'); + + assert.strictEqual(bids2[1].bidder, 'appnexus', 'adUnit2 bids2 bidder'); + assert.strictEqual(bids2[1].params.placementId, '827326', 'adUnit2 bids2 params.placementId'); + }); + }); + + describe('removeAdUnit', function () { + + var adUnits, adUnit2, bids2; + + it('the first adUnit should be not existed', function () { + pbjs.removeAdUnit('/1996833/slot-1'); + adUnits = pbjsTestOnly.getAdUnits(); + adUnit2 = adUnits[0]; + bids2 = adUnit2.bids; + expect(adUnits[1]).not.exist; + }); + + it('the second adUnit should be still existed', function () { + assert.strictEqual(adUnit2.code, '/1996833/slot-2', 'adUnit2 code'); + assert.deepEqual(adUnit2.sizes, [[468, 60]], 'adUnit2 sizes'); + assert.strictEqual(bids2[0].bidder, 'rubicon', 'adUnit2 bids1 bidder'); + assert.strictEqual(bids2[0].params.rp_account, '4934', 'adUnit2 bids1 params.rp_account'); + assert.strictEqual(bids2[0].params.rp_zonesize, '23948-15', 'adUnit2 bids1 params.rp_zonesize'); + assert.strictEqual(bids2[0].params.rp_site, '13945', 'adUnit2 bids1 params.rp_site'); + + assert.strictEqual(bids2[1].bidder, 'appnexus', 'adUnit2 bids2 bidder'); + assert.strictEqual(bids2[1].params.placementId, '827326', 'adUnit2 bids2 params.placementId'); + }); + }); + +}); diff --git a/test/spec/adapters/wideorbit_spec.js b/test/spec/adapters/wideorbit_spec.js new file mode 100644 index 00000000000..f530940e40c --- /dev/null +++ b/test/spec/adapters/wideorbit_spec.js @@ -0,0 +1,491 @@ +describe('wideorbit adapter tests', function () { + + var expect = require('chai').expect; + var urlParse = require('url-parse'); + + // FYI: querystringify will perform encoding/decoding + var querystringify = require('querystringify'); + + var adapter = require('src/adapters/wideorbit'); + var adLoader = require('src/adloader'); + var bidmanager = require('src/bidmanager'); + + describe('creation of bid url', function () { + + var spyLoadScript; + + beforeEach(function () { + spyLoadScript = sinon.spy(adLoader, 'loadScript'); + }); + + afterEach(function () { + spyLoadScript.restore(); + }); + + it('should be called only once', function () { + + var params = { + bidderCode: 'wideorbit', + bids: [ + { + bidder: 'wideorbit', + params: { + pbId: 1, + pId: 101 + }, + placementCode: 'div-gpt-ad-12345-1' + }, + { + bidder: 'wideorbit', + params: { + pbId: 1, + site: 'Site 1', + page: 'Page 1', + width: 100, + height: 200, + subPublisher: 'Sub Publisher 1' + }, + placementCode: 'div-gpt-ad-12345-2' + } + ] + }; + + adapter().callBids(params); + + sinon.assert.calledOnce(spyLoadScript); + + }); + + it('should fix parameters name', function () { + + var params = { + bidderCode: 'wideorbit', + bids: [ + { + bidder: 'wideorbit', + params: { + PBiD: 1, + PID: 101 + }, + placementCode: 'div-gpt-ad-12345-1' + }, + { + bidder: 'wideorbit', + params: { + pbid: 1, + SiTe: 'Site 1', + Page: 'Page 1', + widTH: 100, + HEIGHT: 200, + SUBPublisher: 'Sub Publisher 1' + }, + placementCode: 'div-gpt-ad-12345-2' + } + ] + }; + + adapter().callBids(params); + + var bidUrl = spyLoadScript.getCall(0).args[0]; + + sinon.assert.calledWith(spyLoadScript, bidUrl); + + var parsedBidUrl = urlParse(bidUrl); + var parsedBidUrlQueryString = querystringify.parse(parsedBidUrl.query); + + expect(parsedBidUrl.hostname).to.equal('p1.atemda.com') + expect(parsedBidUrl.pathname).to.equal('/JSAdservingMP.ashx') + expect(parsedBidUrlQueryString).to.have.property('pc').and.to.equal('2'); + expect(parsedBidUrlQueryString).to.have.property('pbId').and.to.equal('1'); + expect(parsedBidUrlQueryString).to.have.property('jsv').and.to.equal('1.0'); + expect(parsedBidUrlQueryString).to.have.property('tsv').and.to.equal('1.0'); + expect(parsedBidUrlQueryString).to.have.property('cts').to.have.length.above(0); + expect(parsedBidUrlQueryString).to.have.property('arp').and.to.equal('0'); + expect(parsedBidUrlQueryString).to.have.property('fl').and.to.equal('0'); + expect(parsedBidUrlQueryString).to.have.property('jscb').and.to.equal('window.pbjs.handleWideOrbitCallback'); + expect(parsedBidUrlQueryString).to.have.property('mpp').and.to.equal('0'); + expect(parsedBidUrlQueryString).to.have.property('cb').to.have.length.above(0); + expect(parsedBidUrlQueryString).to.have.property('hb').and.to.equal('1'); + + expect(parsedBidUrlQueryString).to.have.property('gid0').and.to.equal('div-gpt-ad-12345-1'); + expect(parsedBidUrlQueryString).to.have.property('rpos0').and.to.equal('0'); + expect(parsedBidUrlQueryString).to.have.property('ecpm0').and.to.equal(''); + + expect(parsedBidUrlQueryString).to.have.property('gid1').and.to.equal('div-gpt-ad-12345-2'); + expect(parsedBidUrlQueryString).to.have.property('rpos1').and.to.equal('0'); + expect(parsedBidUrlQueryString).to.have.property('ecpm1').and.to.equal(''); + + expect(parsedBidUrlQueryString).to.have.property('pId0').and.to.equal('101'); + expect(parsedBidUrlQueryString).to.have.property('rank0').and.to.equal('0'); + + expect(parsedBidUrlQueryString).to.have.property('wsName1').and.to.equal('Site 1'); + expect(parsedBidUrlQueryString).to.have.property('wName1').and.to.equal('Page 1'); + expect(parsedBidUrlQueryString).to.have.property('rank1').and.to.equal('1'); + expect(parsedBidUrlQueryString).to.have.property('bfDim1').and.to.equal('100x200'); + expect(parsedBidUrlQueryString).to.have.property('subp1').and.to.equal('Sub Publisher 1'); + + }); + + describe('placement by name', function () { + + it('should be called with specific parameters for two bids', function () { + + var params = { + bidderCode: 'wideorbit', + bids: [ + { + bidder: 'wideorbit', + params: { + pbId: 1, + site: 'Site 1', + page: 'Page 1', + width: 100, + height: 200, + subPublisher: 'Sub Publisher 1', + atf: true + }, + placementCode: 'div-gpt-ad-12345-1' + }, + { + bidder: 'wideorbit', + params: { + pbId: 1, + site: 'Site 2', + page: 'Page 2', + width: 200, + height: 300, + rank: 123, + ecpm: 1.8 + }, + placementCode: 'div-gpt-ad-12345-2' + } + ] + }; + + adapter().callBids(params); + + var bidUrl = spyLoadScript.getCall(0).args[0]; + + sinon.assert.calledWith(spyLoadScript, bidUrl); + + var parsedBidUrl = urlParse(bidUrl); + var parsedBidUrlQueryString = querystringify.parse(parsedBidUrl.query); + + expect(parsedBidUrl.hostname).to.equal('p1.atemda.com') + expect(parsedBidUrl.pathname).to.equal('/JSAdservingMP.ashx') + expect(parsedBidUrlQueryString).to.have.property('pc').and.to.equal('2'); + expect(parsedBidUrlQueryString).to.have.property('pbId').and.to.equal('1'); + expect(parsedBidUrlQueryString).to.have.property('jsv').and.to.equal('1.0'); + expect(parsedBidUrlQueryString).to.have.property('tsv').and.to.equal('1.0'); + expect(parsedBidUrlQueryString).to.have.property('cts').to.have.length.above(0); + expect(parsedBidUrlQueryString).to.have.property('arp').and.to.equal('0'); + expect(parsedBidUrlQueryString).to.have.property('fl').and.to.equal('0'); + expect(parsedBidUrlQueryString).to.have.property('jscb').and.to.equal('window.pbjs.handleWideOrbitCallback'); + expect(parsedBidUrlQueryString).to.have.property('mpp').and.to.equal('0'); + expect(parsedBidUrlQueryString).to.have.property('cb').to.have.length.above(0); + expect(parsedBidUrlQueryString).to.have.property('hb').and.to.equal('1'); + + expect(parsedBidUrlQueryString).to.have.property('gid0').and.to.equal('div-gpt-ad-12345-1'); + expect(parsedBidUrlQueryString).to.have.property('rpos0').and.to.equal('1001'); + expect(parsedBidUrlQueryString).to.have.property('ecpm0').and.to.equal(''); + + expect(parsedBidUrlQueryString).to.have.property('gid1').and.to.equal('div-gpt-ad-12345-2'); + expect(parsedBidUrlQueryString).to.have.property('rpos1').and.to.equal('0'); + expect(parsedBidUrlQueryString).to.have.property('ecpm1').and.to.equal('1.8'); + + expect(parsedBidUrlQueryString).to.have.property('wsName0').and.to.equal('Site 1'); + expect(parsedBidUrlQueryString).to.have.property('wName0').and.to.equal('Page 1'); + expect(parsedBidUrlQueryString).to.have.property('rank0').and.to.equal('0'); + expect(parsedBidUrlQueryString).to.have.property('bfDim0').and.to.equal('100x200'); + expect(parsedBidUrlQueryString).to.have.property('subp0').and.to.equal('Sub Publisher 1'); + + expect(parsedBidUrlQueryString).to.have.property('wsName1').and.to.equal('Site 2'); + expect(parsedBidUrlQueryString).to.have.property('wName1').and.to.equal('Page 2'); + expect(parsedBidUrlQueryString).to.have.property('rank1').and.to.equal('123'); + expect(parsedBidUrlQueryString).to.have.property('bfDim1').and.to.equal('200x300'); + expect(parsedBidUrlQueryString).to.have.property('subp1').and.to.equal(''); + + }); + + }); + + describe('placement by id', function () { + + it('should be called with specific parameters for two bids', function () { + + var params = { + bidderCode: 'wideorbit', + bids: [ + { + bidder: 'wideorbit', + params: { + pbId: 1, + pId: 101, + atf: true, + ecpm: 0.8 + }, + placementCode: 'div-gpt-ad-12345-1' + }, + { + bidder: 'wideorbit', + params: { + pbId: 1, + pId: 102, + rank: 123 + }, + placementCode: 'div-gpt-ad-12345-2' + } + ] + }; + + adapter().callBids(params); + + var bidUrl = spyLoadScript.getCall(0).args[0]; + + sinon.assert.calledWith(spyLoadScript, bidUrl); + + var parsedBidUrl = urlParse(bidUrl); + var parsedBidUrlQueryString = querystringify.parse(parsedBidUrl.query); + + expect(parsedBidUrl.hostname).to.equal('p1.atemda.com') + expect(parsedBidUrl.pathname).to.equal('/JSAdservingMP.ashx') + expect(parsedBidUrlQueryString).to.have.property('pc').and.to.equal('2'); + expect(parsedBidUrlQueryString).to.have.property('pbId').and.to.equal('1'); + expect(parsedBidUrlQueryString).to.have.property('jsv').and.to.equal('1.0'); + expect(parsedBidUrlQueryString).to.have.property('tsv').and.to.equal('1.0'); + expect(parsedBidUrlQueryString).to.have.property('cts').to.have.length.above(0); + expect(parsedBidUrlQueryString).to.have.property('arp').and.to.equal('0'); + expect(parsedBidUrlQueryString).to.have.property('fl').and.to.equal('0'); + expect(parsedBidUrlQueryString).to.have.property('jscb').and.to.equal('window.pbjs.handleWideOrbitCallback'); + expect(parsedBidUrlQueryString).to.have.property('mpp').and.to.equal('0'); + expect(parsedBidUrlQueryString).to.have.property('cb').to.have.length.above(0); + expect(parsedBidUrlQueryString).to.have.property('hb').and.to.equal('1'); + + expect(parsedBidUrlQueryString).to.have.property('gid0').and.to.equal('div-gpt-ad-12345-1'); + expect(parsedBidUrlQueryString).to.have.property('rpos0').and.to.equal('1001'); + expect(parsedBidUrlQueryString).to.have.property('ecpm0').and.to.equal('0.8'); + + expect(parsedBidUrlQueryString).to.have.property('gid1').and.to.equal('div-gpt-ad-12345-2'); + expect(parsedBidUrlQueryString).to.have.property('rpos1').and.to.equal('0'); + expect(parsedBidUrlQueryString).to.have.property('ecpm1').and.to.equal(''); + + expect(parsedBidUrlQueryString).to.have.property('pId0').and.to.equal('101'); + expect(parsedBidUrlQueryString).to.have.property('rank0').and.to.equal('0'); + + expect(parsedBidUrlQueryString).to.have.property('pId1').and.to.equal('102'); + expect(parsedBidUrlQueryString).to.have.property('rank1').and.to.equal('123'); + + }); + + }); + + }); + + describe('handling of the callback response', function () { + + var placements = [ + { + ExtPlacementId: 'div-gpt-ad-12345-1', + Type: 'DirectHTML', + Bid: 1.3, + Width: 50, + Height: 100, + Source: '
    The AD 1 itself...
    ', + TrackingCodes: [ + 'https://www.admeta.com/1.gif' + ] + }, + { + ExtPlacementId: 'div-gpt-ad-12345-2', + Type: 'DirectHTML', + Bid: 1.5, + Width: 100, + Height: 200, + Source: '
    The AD 2 itself...
    ', + TrackingCodes: [ + 'http://www.admeta.com/2a.gif', + '' + ] + }, + { + ExtPlacementId: 'div-gpt-ad-12345-3', + Type: 'Other', + Bid: 1.7, + Width: 150, + Height: 250, + Source: '
    The AD 3 itself...
    ', + TrackingCodes: [ + 'http://www.admeta.com/3.gif' + ] + } + ]; + + it('callback function should exist', function () { + expect(pbjs.handleWideOrbitCallback).to.exist.and.to.be.a('function'); + }); + + it('bidmanager.addBidResponse should be called thrice with correct arguments', function () { + + var stubAddBidResponse = sinon.stub(bidmanager, 'addBidResponse'); + + var params = { + bidderCode: 'wideorbit', + bids: [ + { + bidder: 'wideorbit', + params: { + pbId: 1, + pId: 101 + }, + placementCode: 'div-gpt-ad-12345-1' + }, + { + bidder: 'wideorbit', + params: { + pbId: 1, + site: 'Site 1', + page: 'Page 1', + width: 100, + height: 200, + subPublisher: 'Sub Publisher 1' + }, + placementCode: 'div-gpt-ad-12345-2' + }, + { + bidder: 'wideorbit', + params: { + pbId: 1, + pId: 102 + }, + placementCode: 'div-gpt-ad-12345-3' + }, + ] + }; + + var response = { + UserMatchings: [ + { + Type: 'redirect', + Url: 'http%3A%2F%2Fwww.admeta.com%2F1.gif' + } + ], + Placements: placements + }; + + adapter().callBids(params); + pbjs.handleWideOrbitCallback(response); + + var bidPlacementCode1 = stubAddBidResponse.getCall(0).args[0]; + var bidObject1 = stubAddBidResponse.getCall(0).args[1]; + var bidPlacementCode2 = stubAddBidResponse.getCall(1).args[0]; + var bidObject2 = stubAddBidResponse.getCall(1).args[1]; + var bidPlacementCode3 = stubAddBidResponse.getCall(2).args[0]; + var bidObject3 = stubAddBidResponse.getCall(2).args[1]; + + expect(bidPlacementCode1).to.equal('div-gpt-ad-12345-1'); + expect(bidObject1.cpm).to.equal(1.3); + expect(bidObject1.ad).to.equal('
    The AD 1 itself...
    '); + expect(bidObject1.width).to.equal(50); + expect(bidObject1.height).to.equal(100); + expect(bidObject1.getStatusCode()).to.equal(1); + expect(bidObject1.bidderCode).to.equal('wideorbit'); + + expect(bidPlacementCode2).to.equal('div-gpt-ad-12345-2'); + expect(bidObject2.cpm).to.equal(1.50); + expect(bidObject2.ad).to.equal('
    The AD 2 itself...
    '); + expect(bidObject2.width).to.equal(100); + expect(bidObject2.height).to.equal(200); + expect(bidObject2.getStatusCode()).to.equal(1); + expect(bidObject2.bidderCode).to.equal('wideorbit'); + + expect(bidPlacementCode3).to.equal('div-gpt-ad-12345-3'); + expect(bidObject3.getStatusCode()).to.equal(2); + expect(bidObject3.bidderCode).to.equal('wideorbit'); + + sinon.assert.calledWith(stubAddBidResponse, bidPlacementCode1, bidObject1); + sinon.assert.calledWith(stubAddBidResponse, bidPlacementCode2, bidObject2); + sinon.assert.calledWith(stubAddBidResponse, bidPlacementCode3, bidObject3); + + sinon.assert.calledThrice(stubAddBidResponse); + + stubAddBidResponse.restore(); + + }); + + it('should append an image to the head when type is set to redirect', function () { + + var stubAddBidResponse = sinon.stub(bidmanager, 'addBidResponse'); + + var response = { + UserMatchings: [ + { + Type: 'redirect', + Url: 'http%3A%2F%2Fwww.admeta.com%2F1.gif' + } + ], + Placements: placements + }; + + pbjs.handleWideOrbitCallback(response); + + var imgElement = document.querySelectorAll("head img")[0]; + + expect(imgElement).to.exist; + expect(imgElement.src).to.equal('http://www.admeta.com/1.gif'); + + stubAddBidResponse.restore(); + }); + + it('should append an iframe to the head when type is set to iframe', function () { + + var stubAddBidResponse = sinon.stub(bidmanager, 'addBidResponse'); + + var response = { + UserMatchings: [ + { + Type: 'iframe', + Url: 'http%3A%2F%2Fwww.admeta.com%2F1.ashx' + } + ], + Placements: placements + }; + + pbjs.handleWideOrbitCallback(response); + + var iframeElement = document.querySelectorAll("head iframe")[0]; + + expect(iframeElement).to.exist; + expect(iframeElement.src).to.equal('http://www.admeta.com/1.ashx'); + + stubAddBidResponse.restore(); + + }); + + it('should append an script to the head when type is set to javascript', function () { + + var stubAddBidResponse = sinon.stub(bidmanager, 'addBidResponse'); + + var response = { + UserMatchings: [ + { + Type: 'javascript', + Url: 'http%3A%2F%2Fwww.admeta.com%2F1.js' + } + ], + Placements: placements + }; + + pbjs.handleWideOrbitCallback(response); + + var scriptElement = document.querySelectorAll("head script")[0]; + + expect(scriptElement).to.exist; + expect(scriptElement.src).to.equal('http://www.admeta.com/1.js'); + + stubAddBidResponse.restore(); + }); + + }); + +}); + \ No newline at end of file diff --git a/test/spec/adloader_spec.js b/test/spec/adloader_spec.js new file mode 100644 index 00000000000..fd127bfeb3e --- /dev/null +++ b/test/spec/adloader_spec.js @@ -0,0 +1,40 @@ +describe('adLoader', function () { + var assert = require('chai').assert, + adLoader = require('../../src/adloader'); + + describe('trackPixel', function () { + it('correctly appends a cachebuster query paramter to a pixel with no existing parameters', function () { + var inputUrl = 'http://www.example.com/tracking_pixel', + token = '?rnd=', + expectedPartialUrl = inputUrl + token, + actual = adLoader.trackPixel(inputUrl), + actualPartialUrl = actual.split(token)[0] + token, + randomNumber = parseInt(actual.split(token)[1]); + assert.strictEqual(actualPartialUrl, expectedPartialUrl); + assert.isNumber(randomNumber); + }); + }); + + it('correctly appends a cachebuster query paramter to a pixel with one existing parameter', function () { + var inputUrl = 'http://www.example.com/tracking_pixel?food=bard', + token = '&rnd=', + expectedPartialUrl = inputUrl + token, + actual = adLoader.trackPixel(inputUrl), + actualPartialUrl = actual.split(token)[0] + token, + randomNumber = parseInt(actual.split(token)[1]); + assert.strictEqual(actualPartialUrl, expectedPartialUrl); + assert.isNumber(randomNumber); + }); + + it('correctly appends a cachebuster query paramter to a pixel with multiple existing parameters', function () { + var inputUrl = 'http://www.example.com/tracking_pixel?food=bard&zing=zang', + token = '&rnd=', + expectedPartialUrl = inputUrl + token, + actual = adLoader.trackPixel(inputUrl), + actualPartialUrl = actual.split(token)[0] + token, + randomNumber = parseInt(actual.split(token)[1]); + assert.strictEqual(actualPartialUrl, expectedPartialUrl); + assert.isNumber(randomNumber); + }); + +}); diff --git a/test/spec/aliasBidder_spec.js b/test/spec/aliasBidder_spec.js index 21809e36c72..d3892e3aa3d 100644 --- a/test/spec/aliasBidder_spec.js +++ b/test/spec/aliasBidder_spec.js @@ -1,38 +1,42 @@ -describe("Publisher API _ Alias Bidder", function() { - var assert = chai.assert, - should = chai.should(), - expect = chai.expect; - - before(function(){ - - var topSlotCode = '/19968336/header-bid-tag1'; - var topSlotSizes = [[728, 90], [970, 90]]; - var adUnit = { - code: topSlotCode, - sizes: topSlotSizes, - bids: [{ - bidder: 'appnexus', - params: { - placementId : '5215561' - } - }] - }; - - pbjs.addAdUnits(adUnit); - }); - - after(function(){ - pbjs_testonly.clearAllAdUnits(); - }); - - describe('set Alias Bidder', function () { - - it('should have both of target bidder and alias bidder', function() { - - pbjs.aliasBidder('appnexus','bRealTime1'); - - }); - }); - - -}); +import { pbjsTestOnly } from 'test/helpers/pbjs-test-only'; + +describe('Publisher API _ Alias Bidder', function () { + var assert = require('chai').assert; + var expect = require('chai').expect; + var should = require('chai').should(); + var prebid = require('../../src/prebid'); + + before(function () { + + var topSlotCode = '/19968336/header-bid-tag1'; + var topSlotSizes = [[728, 90], [970, 90]]; + var adUnit = { + code: topSlotCode, + sizes: topSlotSizes, + bids: [ + { + bidder: 'appnexus', + params: { + placementId: '5215561' + } + } + ] + }; + + pbjs.addAdUnits(adUnit); + }); + + after(function () { + pbjsTestOnly.clearAllAdUnits(); + }); + + describe('set Alias Bidder', function () { + + it('should have both of target bidder and alias bidder', function () { + + pbjs.aliasBidder('appnexus', 'bRealTime1'); + + }); + }); + +}); diff --git a/test/spec/api_spec.js b/test/spec/api_spec.js index 98de887233a..8e3f0df5dd5 100755 --- a/test/spec/api_spec.js +++ b/test/spec/api_spec.js @@ -1,63 +1,78 @@ -describe("Publisher API", function() { - var assert = chai.assert; - - describe('api of command queue',function(){ - - it('should have a global variable pbjs', function() { - assert.isObject(pbjs); - }); - - it('should have a global variable pbjs.que as an array',function(){ - assert.isArray(pbjs.que); - }); - - it('should have pbjs.que.push function', function(){ - assert.isFunction(pbjs.que.push); - }); - }); - - describe('has function',function(){ - - it('should have function pbjs.getAdserverTargeting',function(){ - assert.isFunction(pbjs.getAdserverTargeting); - }); - it('should have function pbjs.getAdserverTargetingForAdUnitCode',function(){ - assert.isFunction(pbjs.getAdserverTargetingForAdUnitCode); - }); - it('should have function pbjs.getBidResponses',function(){ - assert.isFunction(pbjs.getBidResponses); - }); - it('should have function pbjs.getBidResponsesForAdUnitCode',function(){ - assert.isFunction(pbjs.getBidResponsesForAdUnitCode); - }); - it('should have function pbjs.setTargetingForGPTAsync',function(){ - assert.isFunction(pbjs.setTargetingForGPTAsync); - }); - it('should have function pbjs.allBidsAvailable',function(){ - assert.isFunction(pbjs.allBidsAvailable); - }); - it('should have function pbjs.renderAd',function(){ - assert.isFunction(pbjs.renderAd); - }); - it('should have function pbjs.removeAdUnit',function(){ - assert.isFunction(pbjs.removeAdUnit); - }); - it('should have function pbjs.requestBids',function(){ - assert.isFunction(pbjs.requestBids); - }); - it('should have function pbjs.addAdUnits',function(){ - assert.isFunction(pbjs.addAdUnits); - }); - it('should have function pbjs.addCallback',function(){ - assert.isFunction(pbjs.addCallback); - }); - it('should have function pbjs.removeCallback',function(){ - assert.isFunction(pbjs.removeCallback); - }); - it('should have function pbjs.aliasBidder',function(){ - assert.isFunction(pbjs.aliasBidder); - }); - - }); - -}); +var assert = require('chai').assert; +var prebid = require('../../src/prebid'); + +describe('Publisher API', function () { + // var assert = chai.assert; + + describe('api of command queue', function () { + + it('should have a global variable pbjs', function () { + assert.isObject(pbjs); + }); + + it('should have a global variable pbjs.que as an array', function () { + assert.isArray(pbjs.que); + }); + + it('should have pbjs.que.push function', function () { + assert.isFunction(pbjs.que.push); + }); + }); + + describe('has function', function () { + + it('should have function pbjs.getAdserverTargeting', function () { + assert.isFunction(pbjs.getAdserverTargeting); + }); + + it('should have function pbjs.getAdserverTargetingForAdUnitCode', function () { + assert.isFunction(pbjs.getAdserverTargetingForAdUnitCode); + }); + + it('should have function pbjs.getBidResponses', function () { + assert.isFunction(pbjs.getBidResponses); + }); + + it('should have function pbjs.getBidResponsesForAdUnitCode', function () { + assert.isFunction(pbjs.getBidResponsesForAdUnitCode); + }); + + it('should have function pbjs.setTargetingForGPTAsync', function () { + assert.isFunction(pbjs.setTargetingForGPTAsync); + }); + + it('should have function pbjs.allBidsAvailable', function () { + assert.isFunction(pbjs.allBidsAvailable); + }); + + it('should have function pbjs.renderAd', function () { + assert.isFunction(pbjs.renderAd); + }); + + it('should have function pbjs.removeAdUnit', function () { + assert.isFunction(pbjs.removeAdUnit); + }); + + it('should have function pbjs.requestBids', function () { + assert.isFunction(pbjs.requestBids); + }); + + it('should have function pbjs.addAdUnits', function () { + assert.isFunction(pbjs.addAdUnits); + }); + + it('should have function pbjs.addCallback', function () { + assert.isFunction(pbjs.addCallback); + }); + + it('should have function pbjs.removeCallback', function () { + assert.isFunction(pbjs.removeCallback); + }); + + it('should have function pbjs.aliasBidder', function () { + assert.isFunction(pbjs.aliasBidder); + }); + + }); + +}); diff --git a/test/spec/bidmanager_spec.js b/test/spec/bidmanager_spec.js new file mode 100644 index 00000000000..7f02dc5fb53 --- /dev/null +++ b/test/spec/bidmanager_spec.js @@ -0,0 +1,406 @@ +var assert = require("assert"); + +/* use this method to test individual files instead of the whole prebid.js project */ + +//TODO refactor to use the spec files +var utils = require('../../src/utils'); +var bidmanager = require('../../src/bidmanager'); +var bidfactory = require('../../src/bidfactory'); +var fixtures = require('../fixtures/fixtures'); + +describe('replaceTokenInString', function () { + + it('should replace all given tokens in a String', function () { + var tokensToReplace = { + 'foo': 'bar', + 'zap': 'quux' + }; + + var output = utils.replaceTokenInString("hello %FOO%, I am %ZAP%", tokensToReplace, "%"); + assert.equal(output, "hello bar, I am quux"); + }); + + it('should ignore tokens it does not see', function () { + var output = utils.replaceTokenInString("hello %FOO%", {}, "%"); + + assert.equal(output, "hello %FOO%"); + }); +}); + +describe('bidmanager.js', function () { + + describe('getKeyValueTargetingPairs', function () { + var bid = {}; + var bidPriceCpm = 5.578; + var bidPbLg = 5.50; + var bidPbMg = 5.50; + var bidPbHg = 5.57; + var bidPbAg = 5.50; + + var adUnitCode = '12345'; + var bidderCode = 'appnexus'; + var size = '300x250'; + var adId = '1adId'; + + before(function () { + bid.cpm = bidPriceCpm; + bid.pbLg = bidPbLg; + bid.pbMg = bidPbMg; + bid.pbHg = bidPbHg; + bid.pbAg = bidPbAg; + + bid.height = 300; + bid.width = 250; + bid.adUnitCode = adUnitCode; + bid.getSize = function () { + return this.height + 'x' + this.width; + }; + bid.bidderCode = bidderCode; + bid.adId = adId; + + }); + + it('No bidder level configuration defined - default', function () { + var expected = { + "hb_bidder": bidderCode, + "hb_adid": adId, + "hb_pb": bidPbMg, + "hb_size": size + }; + var response = bidmanager.getKeyValueTargetingPairs(bidderCode, bid); + assert.deepEqual(response, expected); + + }); + + it('Custom configuration for all bidders', function () { + pbjs.bidderSettings = + { + standard: { + adserverTargeting: [ + { + key: "hb_bidder", + val: function (bidResponse) { + return bidResponse.bidderCode; + } + }, { + key: "hb_adid", + val: function (bidResponse) { + return bidResponse.adId; + } + }, { + key: "hb_pb", + val: function (bidResponse) { + //change default here + return bidResponse.pbHg; + } + }, { + key: "hb_size", + val: function (bidResponse) { + return bidResponse.size; + + } + } + ] + + } + }; + + var expected = { + "hb_bidder": bidderCode, + "hb_adid": adId, + "hb_pb": bidPbHg, + "hb_size": size + }; + var response = bidmanager.getKeyValueTargetingPairs(bidderCode, bid); + assert.deepEqual(response, expected); + + }); + + it('Custom configuration for one bidder', function () { + pbjs.bidderSettings = + { + appnexus: { + adserverTargeting: [ + { + key: "hb_bidder", + val: function (bidResponse) { + return bidResponse.bidderCode; + } + }, { + key: "hb_adid", + val: function (bidResponse) { + return bidResponse.adId; + } + }, { + key: "hb_pb", + val: function (bidResponse) { + //change default here + return bidResponse.pbHg; + } + }, { + key: "hb_size", + val: function (bidResponse) { + return bidResponse.size; + + } + } + ] + + } + }; + + var expected = { + "hb_bidder": bidderCode, + "hb_adid": adId, + "hb_pb": bidPbHg, + "hb_size": size + }; + var response = bidmanager.getKeyValueTargetingPairs(bidderCode, bid); + assert.deepEqual(response, expected); + + }); + + it('Custom configuration for one bidder - not matched', function () { + pbjs.bidderSettings = + { + nonExistentBidder: { + adserverTargeting: [ + { + key: "hb_bidder", + val: function (bidResponse) { + return bidResponse.bidderCode; + } + }, { + key: "hb_adid", + val: function (bidResponse) { + return bidResponse.adId; + } + }, { + key: "hb_pb", + val: function (bidResponse) { + //change default here + return bidResponse.pbHg; + } + }, { + key: "hb_size", + val: function (bidResponse) { + return bidResponse.size; + + } + } + ] + + } + }; + + var expected = { + "hb_bidder": bidderCode, + "hb_adid": adId, + "hb_pb": bidPbMg, + "hb_size": size + }; + var response = bidmanager.getKeyValueTargetingPairs(bidderCode, bid); + assert.deepEqual(response, expected); + + }); + + it('Custom bidCpmAdjustment for one bidder and inherit standard', function () { + pbjs.bidderSettings = + { + appnexus: { + bidCpmAdjustment: function (bidCpm) { + return bidCpm * 0.7; + }, + }, + standard: { + adserverTargeting: [ + { + key: "hb_bidder", + val: function (bidResponse) { + return bidResponse.bidderCode; + } + }, { + key: "hb_adid", + val: function (bidResponse) { + return bidResponse.adId; + } + }, { + key: "hb_pb", + val: function (bidResponse) { + //change default here + return 10.00; + } + } + ] + + } + }; + + var expected = { "hb_bidder": bidderCode, "hb_adid": adId, "hb_pb": 10.0 }; + var response = bidmanager.getKeyValueTargetingPairs(bidderCode, bid); + assert.deepEqual(response, expected); + + }); + + it('Custom bidCpmAdjustment AND custom configuration for one bidder and inherit standard settings', function () { + pbjs.bidderSettings = + { + appnexus: { + bidCpmAdjustment: function (bidCpm) { + return bidCpm * 0.7; + }, + adserverTargeting: [ + { + key: "hb_bidder", + val: function (bidResponse) { + return bidResponse.bidderCode; + } + }, { + key: "hb_adid", + val: function (bidResponse) { + return bidResponse.adId; + } + }, { + key: "hb_pb", + val: function (bidResponse) { + //change default here + return 15.00; + } + } + ] + }, + standard: { + adserverTargeting: [ + { + key: "hb_bidder", + val: function (bidResponse) { + return bidResponse.bidderCode; + } + }, { + key: "hb_adid", + val: function (bidResponse) { + return bidResponse.adId; + } + }, { + key: "hb_pb", + val: function (bidResponse) { + //change default here + return 10.00; + }, + }, + { + key: "hb_size", + val: function (bidResponse) { + return bidResponse.size; + + } + } + ] + + } + }; + + var expected = { + "hb_bidder": bidderCode, + "hb_adid": adId, + "hb_pb": 15.0, + "hb_size": "300x250" + }; + var response = bidmanager.getKeyValueTargetingPairs(bidderCode, bid); + assert.deepEqual(response, expected); + + }); + + it('alwaysUseBid=true and inherit custom', function () { + pbjs.bidderSettings = + { + appnexus: { + alwaysUseBid: true, + adserverTargeting: [ + { + key: "hb_bidder", + val: function (bidResponse) { + return bidResponse.bidderCode; + } + }, { + key: "hb_adid", + val: function (bidResponse) { + return bidResponse.adId; + } + }, { + key: "hb_pb", + val: function (bidResponse) { + return bidResponse.pbHg; + } + } + ] + } + }; + + var expected = { + "hb_bidder": bidderCode, + "hb_adid": adId, + "hb_pb": 5.57, + "hb_size": "300x250" + }; + var response = bidmanager.getKeyValueTargetingPairs(bidderCode, bid); + assert.deepEqual(response, expected); + + }); + + }); + + describe('addBidResponse', () => { + before(() => { + pbjs.adUnits = fixtures.getAdUnits(); + }); + + it('should return proper price bucket increments for dense mode', () => { + const bid = Object.assign({}, + bidfactory.createBid(2), + fixtures.getBidResponses()[5] + ); + + // 0 - 3 dollars + bid.cpm = '1.99'; + let expectedIncrement = '1.99'; + bidmanager.addBidResponse(bid.adUnitCode, bid); + // pop this bid because another test relies on global pbjs._bidsReceived + let registeredBid = pbjs._bidsReceived.pop(); + assert.equal(registeredBid.pbDg, expectedIncrement, '0 - 3 hits at to 1 cent increment'); + + // 3 - 8 dollars + bid.cpm = '4.39'; + expectedIncrement = '4.35'; + bidmanager.addBidResponse(bid.adUnitCode, bid); + registeredBid = pbjs._bidsReceived.pop(); + assert.equal(registeredBid.pbDg, expectedIncrement, '3 - 8 hits at 5 cent increment'); + + // 8 - 20 dollars + bid.cpm = '19.99'; + expectedIncrement = '19.50'; + bidmanager.addBidResponse(bid.adUnitCode, bid); + registeredBid = pbjs._bidsReceived.pop(); + assert.equal(registeredBid.pbDg, expectedIncrement, '8 - 20 hits at 50 cent increment'); + + // 20+ dollars + bid.cpm = '73.07'; + expectedIncrement = '20.00'; + bidmanager.addBidResponse(bid.adUnitCode, bid); + registeredBid = pbjs._bidsReceived.pop(); + assert.equal(registeredBid.pbDg, expectedIncrement, '20+ caps at 20.00'); + }); + + it('should place dealIds in adserver targeting', () => { + const bid = Object.assign({}, + bidfactory.createBid(2), + fixtures.getBidResponses()[0] + ); + + bid.dealId = "test deal"; + bidmanager.addBidResponse(bid.adUnitCode, bid); + const addedBid = pbjs._bidsReceived.pop(); + assert.equal(addedBid.adserverTargeting[`hb_deal_${bid.bidderCode}`], bid.dealId, 'dealId placed in adserverTargeting'); + }); + }); +}); diff --git a/test/spec/bids_spec.js b/test/spec/bids_spec.js deleted file mode 100644 index 0d885226f48..00000000000 --- a/test/spec/bids_spec.js +++ /dev/null @@ -1,184 +0,0 @@ -describe("Publisher API _ Bids", function() { - var assert = chai.assert, - expect = chai.expect, - should = chai.should(); - - var rightSlotCode = '/19968336/header-bid-tag-0'; - var rightDivCode = 'div-gpt-ad-1438287399331-0'; - var rightSlotSizes = [[300, 250], [300, 600]]; - - var topSlotCode = '/19968336/header-bid-tag1'; - var topDivCode = 'div-gpt-ad-1438287399331-1'; - var topSlotSizes = [[728, 90], [970, 90]]; - - - before(function(){ - - var adUnit1 = { - code: rightDivCode, - sizes: rightSlotSizes, - bids: [{ - bidder: 'appnexus', - params: { - placementId: '2251610' - } - }, - { - bidder: 'rubicon', - params: { - rp_account : '9707', - rp_site: '17955', - rp_zonesize : '50983-15', - rp_tracking : 'affiliate-1701207318', - rp_inventory : '{ deals : "mobv3_excl,atf,demo1849,csm1834,znexcl1,exunisite,exmars,extargt,ldacomp,ent19116,rn14858,ukent,g03070,qc12170,qc2690,qc2695,qc1988,asov1,qc12172,qc12169,qc27434,rn24858,ent29116,lngen,cntq,cntauto,anthea,smg_blklist,amnetctr,ntflxblk,amtblk,zentend,nortb,deschoeff,js,excltop," }', - rp_floor : '0.1' - } - }, - { - bidder: 'openx', - params: { - unit: 537245128, - pageURL : 'http://drudgereport.com', - refererURL : 'http://drudgereport.com', - jstag_url : 'http://ox-d.intermarkets.net/w/1.0/jstag' - } - }, - { - bidder: 'pubmatic', - params: { - publisherId: 39741, - adSlot: '39620189@300x250' - } - }, - { - bidder: 'criteo', - params: { - nid: '2612', - cookiename: 'cto_topix', - varname : 'crtg_content' - } - }, - { - bidder: 'casale', - params: { - slotId: 2, - casaleUrl: 'http://js.indexww.com/ht/elitedaily.js' - } - }, - { - bidder: 'casale', - params: { - slotId: 3 - } - }, - { - bidder: 'yieldbot', - params: { - pub: 'id', - name: 'name' - } - }, - { - bidder: 'amazon', - params: { - aId : 3080 - } - } - ] - }; //adUnit1 end - - var adUnit2 = { - code: topDivCode, - sizes: topSlotSizes, - bids: [{ - bidder: 'appnexus', - params: { - placementId : '5215561' - } - }] - }; - - var arr = [adUnit2, adUnit1]; - pbjs.addAdUnits([adUnit2, adUnit1]); - - }); - - after(function(){ - pbjs_testonly.clearAllAdUnits(); - }); - - it('Functions', function() { - - - }); - - // describe('Request and callback', function() { - - // it('bidsBackHandler callBack', function() { - - // pbjs.requestBids({ - // timeout: 500, - // bidsBackHandler : function(a){ - // assertTargeting(); - // assertArguments(a); - // } - // }); - - // var assertTargeting = function(){ - - // var top = pbjs.getAdserverTargetingForAdUnitCode(topDivCode), - // right = pbjs.getAdserverTargetingForAdUnitCode(rightDivCode), - // all = pbjs.getAdserverTargeting(); - - // console.log('top = ' + top); - // if(top!==undefined){ - // assert.typeOf(top,'object'); - // assert.typeOf(top.hb_adid,'string'); - // assert.typeOf(top.hb_bidder,'string'); - // assert.typeOf(top.hb_pb,'string'); - // assert.typeOf(top.hb_size,'string'); - // } - - - // console.log('right=' + right); - - // if(right!==undefined){ - // assert.typeOf(right,'object'); - // assert.typeOf(right.hb_adid,'string'); - // assert.typeOf(right.hb_bidder,'string'); - // assert.typeOf(right.hb_pb,'string'); - // assert.typeOf(right.hb_size,'string'); - // } - - // console.log('all = ' + all); - // assert.typeOf(all,'object'); - // should.exist(all[rightDivCode]); - // should.exist(all[topDivCode]); - - // assert.deepEqual(top,all[topDivCode],'top slot targeting'); - // assert.deepEqual(right,all[rightDivCode],'right slot targeting'); - // }; - - // var assertArguments = function(arg){ - // assert.typeOf(arg,'object'); - // should.exist(arg[rightDivCode]); - // should.exist(arg[topDivCode]); - - // console.log(arg); - // var responses = pbjs.getBidResponses(); - // console.log(responses); - // assert.typeOf(responses,'object'); - // should.exist(responses[rightDivCode]); - // should.exist(responses[topDivCode]); - // assert.deepEqual(arg,responses,'reponse object'); - - // var topResponse = pbjs.getBidResponsesForAdUnitCode(topDivCode); - // var rightResponse = pbjs.getBidResponsesForAdUnitCode(rightDivCode); - - // assert.deepEqual(topResponse,responses[topDivCode],'top slot response'); - // assert.deepEqual(rightResponse,responses[rightDivCode],'right slot response'); - - // }; - // }); - // }); -}); diff --git a/test/spec/ga_spec.js b/test/spec/ga_spec.js new file mode 100644 index 00000000000..1e41958bf2e --- /dev/null +++ b/test/spec/ga_spec.js @@ -0,0 +1,28 @@ +var assert = require('assert'); +var ga = require('../../src/ga'); + +describe('Ga', function () { + + describe('enableAnalytics', function () { + + it('should accept a tracker name option and output prefixed send string', function () { + var options = { trackerName: 'foo' }; + ga.enableAnalytics(options); + + var output = ga.getTrackerSend(); + assert.equal(output, 'foo.send'); + }); + + + it('should output normal send string when trackerName is not set', function () { + var options = {}; + ga.enableAnalytics(options); + + var output = ga.getTrackerSend(); + assert.equal(output, 'send'); + }); + + }); + + +}); diff --git a/test/spec/unit/pbjs_api_spec.js b/test/spec/unit/pbjs_api_spec.js new file mode 100644 index 00000000000..9ffa5f97aeb --- /dev/null +++ b/test/spec/unit/pbjs_api_spec.js @@ -0,0 +1,728 @@ +import { + getAdServerTargeting, + getBidRequests, + getBidResponses, + getTargetingKeys, + getTargetingKeysBidLandscape, +} from 'test/fixtures/fixtures'; + +var assert = require('chai').assert; + +var prebid = require('src/prebid'); +var utils = require('src/utils'); +var bidmanager = require('src/bidmanager'); +var adloader = require('src/adloader'); +var adaptermanager = require('src/adaptermanager'); +var events = require('src/events'); +var ga = require('src/ga'); +var CONSTANTS = require('src/constants.json'); + +var bidResponses = require('test/fixtures/bid-responses.json'); +var targetingMap = require('test/fixtures/targeting-map.json'); +var config = require('test/fixtures/config.json'); + +pbjs = pbjs || {}; +pbjs._bidsRequested = getBidRequests(); +pbjs._bidsReceived = getBidResponses(); + +function resetAuction() { + pbjs._sendAllBids = false; + pbjs.clearAuction(); + pbjs._bidsRequested = getBidRequests(); + pbjs._bidsReceived = getBidResponses(); +} + +var Slot = function Slot(elementId, pathId) { + var slot = { + getSlotElementId: function getSlotElementId() { + return elementId; + }, + + getAdUnitPath: function getAdUnitPath() { + return pathId; + }, + + setTargeting: function setTargeting(key, value) { + }, + + getTargeting: function getTargeting() { + return [{ testKey: ['a test targeting value'] }]; + }, + + getTargetingKeys: function getTargetingKeys() { + return ['testKey']; + }, + + clearTargeting: function clearTargeting() { + return googletag.pubads().getSlots(); + } + }; + slot.spySetTargeting = sinon.spy(slot, 'setTargeting'); + return slot; +}; + +var createSlotArray = function createSlotArray() { + return [ + new Slot(config.adUnitElementIDs[0], config.adUnitCodes[0]), + new Slot(config.adUnitElementIDs[1], config.adUnitCodes[1]), + new Slot(config.adUnitElementIDs[2], config.adUnitCodes[2]) + ]; +}; + +window.googletag = { + _slots: [], + pubads: function () { + var self = this; + return { + getSlots: function () { + return self._slots; + }, + + setSlots: function (slots) { + self._slots = slots; + } + }; + } +}; + +describe('Unit: Prebid Module', function () { + + describe('getAdserverTargetingForAdUnitCodeStr', function () { + it('should return targeting info as a string', function () { + const adUnitCode = config.adUnitCodes[0]; + pbjs.enableSendAllBids(); + var expected = 'foobar=300x250&hb_size=300x250&hb_pb=10.00&hb_adid=233bcbee889d46d&hb_bidder=appnexus&hb_size_triplelift=0x0&hb_pb_triplelift=10.00&hb_adid_triplelift=222bb26f9e8bd&hb_bidder_triplelift=triplelift&hb_size_appnexus=300x250&hb_pb_appnexus=10.00&hb_adid_appnexus=233bcbee889d46d&hb_bidder_appnexus=appnexus&hb_size_pagescience=300x250&hb_pb_pagescience=10.00&hb_adid_pagescience=25bedd4813632d7&hb_bidder_pagescienc=pagescience&hb_size_brightcom=300x250&hb_pb_brightcom=10.00&hb_adid_brightcom=26e0795ab963896&hb_bidder_brightcom=brightcom&hb_size_brealtime=300x250&hb_pb_brealtime=10.00&hb_adid_brealtime=275bd666f5a5a5d&hb_bidder_brealtime=brealtime&hb_size_pubmatic=300x250&hb_pb_pubmatic=10.00&hb_adid_pubmatic=28f4039c636b6a7&hb_bidder_pubmatic=pubmatic&hb_size_rubicon=300x600&hb_pb_rubicon=10.00&hb_adid_rubicon=29019e2ab586a5a&hb_bidder_rubicon=rubicon'; + var result = pbjs.getAdserverTargetingForAdUnitCodeStr(adUnitCode); + assert.equal(expected, result, 'returns expected string of ad targeting info'); + }); + + it('should log message if adunitCode param is falsey', function () { + var spyLogMessage = sinon.spy(utils, 'logMessage'); + var result = pbjs.getAdserverTargetingForAdUnitCodeStr(); + assert.ok(spyLogMessage.calledWith('Need to call getAdserverTargetingForAdUnitCodeStr with adunitCode'), 'expected message was logged'); + assert.equal(result, undefined, 'result is undefined'); + utils.logMessage.restore(); + }); + }); + + describe('getAdserverTargetingForAdUnitCode', function () { + it('should return targeting info as an object', function () { + const adUnitCode = config.adUnitCodes[0]; + pbjs.enableSendAllBids(); + var result = pbjs.getAdserverTargetingForAdUnitCode(adUnitCode); + const expected = getAdServerTargeting()[adUnitCode]; + assert.deepEqual(result, expected, 'returns expected' + + ' targeting info object'); + }); + }); + + describe('getAdServerTargeting', function () { + + beforeEach(() => { + resetAuction(); + }); + + afterEach(() => { + resetAuction(); + }); + + it('should return current targeting data for slots', function () { + pbjs.enableSendAllBids(); + const targeting = pbjs.getAdserverTargeting(); + const expected = getAdServerTargeting(); + assert.deepEqual(targeting, expected, 'targeting ok'); + }); + + it('should return correct targeting with default settings', () => { + var targeting = pbjs.getAdserverTargeting(); + var expected = { + "/19968336/header-bid-tag-0": { + "foobar": "300x250", + "hb_size": "300x250", + "hb_pb": "10.00", + "hb_adid": "233bcbee889d46d", + "hb_bidder": "appnexus" + }, + "/19968336/header-bid-tag1": { + "foobar": "728x90", + "hb_size": "728x90", + "hb_pb": "10.00", + "hb_adid": "24bd938435ec3fc", + "hb_bidder": "appnexus" + } + }; + assert.deepEqual(targeting, expected); + }); + + it('should return correct targeting with bid landscape targeting on', () => { + pbjs.enableSendAllBids(); + var targeting = pbjs.getAdserverTargeting(); + var expected = getAdServerTargeting(); + assert.deepEqual(targeting, expected); + }); + + it("should include a losing bid's custom ad targeting key when the bid has `alwaysUseBid` set to `true`", () => { + + // Let's make sure we're getting the expected losing bid. + assert.equal(pbjs._bidsReceived[0]['bidderCode'], 'triplelift'); + assert.equal(pbjs._bidsReceived[0]['cpm'], 0.112256); + + // Modify the losing bid to have `alwaysUseBid=true` and a custom `adserverTargeting` key. + pbjs._bidsReceived[0]['alwaysUseBid'] = true; + pbjs._bidsReceived[0]['adserverTargeting'] = { + 'always_use_me': 'abc', + }; + + var targeting = pbjs.getAdserverTargeting(); + + // Ensure targeting for both ad placements includes the custom key. + assert.equal( + targeting['/19968336/header-bid-tag-0'].hasOwnProperty('always_use_me'), + true + ); + + var expected = { + "/19968336/header-bid-tag-0": { + "foobar": "300x250", + "hb_size": "300x250", + "hb_pb": "10.00", + "hb_adid": "233bcbee889d46d", + "hb_bidder": "appnexus", + "always_use_me": "abc" + }, + "/19968336/header-bid-tag1": { + "foobar": "728x90", + "hb_size": "728x90", + "hb_pb": "10.00", + "hb_adid": "24bd938435ec3fc", + "hb_bidder": "appnexus" + } + }; + + assert.deepEqual(targeting, expected); + }); + }); + + describe('getBidResponses', function () { + it('should return expected bid responses when not passed an adunitCode', function () { + var result = pbjs.getBidResponses(); + var compare = getBidResponses().map(bid => bid.adUnitCode) + .filter((v, i, a) => a.indexOf(v) === i).map(adUnitCode => pbjs._bidsReceived + .filter(bid => bid.adUnitCode === adUnitCode)) + .map(bids => { + return { + [bids[0].adUnitCode]: { bids: bids } + }; + }) + .reduce((a, b) => Object.assign(a, b), {}); + + assert.deepEqual(result, compare, 'expected bid responses are returned'); + }); + }); + + describe('getBidResponsesForAdUnitCode', function () { + it('should return bid responses as expected', function () { + const adUnitCode = '/19968336/header-bid-tag-0'; + const result = pbjs.getBidResponsesForAdUnitCode(adUnitCode); + const bids = getBidResponses().filter(bid => bid.adUnitCode === adUnitCode); + const compare = { bids: bids}; + assert.deepEqual(result, compare, 'expected id responses for ad unit code are returned'); + }); + }); + + describe('setTargetingForGPTAsync', function () { + let logErrorSpy; + + beforeEach(() => { + logErrorSpy = sinon.spy(utils, 'logError'); + resetAuction(); + }); + + afterEach(() => { + utils.logError.restore(); + resetAuction(); + }); + + it('should set targeting when passed an array of ad unit codes', function () { + var slots = createSlotArray(); + window.googletag.pubads().setSlots(slots); + + pbjs.setTargetingForGPTAsync(config.adUnitCodes); + assert.deepEqual(slots[0].spySetTargeting.args[0], ['hb_bidder', 'appnexus'], 'slot.setTargeting was called with expected key/values'); + }); + + it('should set targeting from googletag data', function () { + var slots = createSlotArray(); + window.googletag.pubads().setSlots(slots); + + pbjs.setTargetingForGPTAsync(); + + var expected = getTargetingKeys(); + assert.deepEqual(slots[0].spySetTargeting.args, expected); + }); + + it('Calling enableSendAllBids should set targeting to include standard keys with bidder' + + ' append to key name', function () { + var slots = createSlotArray(); + window.googletag.pubads().setSlots(slots); + + pbjs.enableSendAllBids(); + pbjs.setTargetingForGPTAsync(); + + var expected = getTargetingKeysBidLandscape(); + assert.deepEqual(slots[0].spySetTargeting.args, expected); + }); + + it('should set targeting for bids with `alwaysUseBid=true`', function () { + + // Make sure we're getting the expected losing bid. + assert.equal(pbjs._bidsReceived[0]['bidderCode'], 'triplelift'); + assert.equal(pbjs._bidsReceived[0]['cpm'], 0.112256); + + // Modify the losing bid to have `alwaysUseBid=true` and a custom `adserverTargeting` key. + pbjs._bidsReceived[0]['alwaysUseBid'] = true; + pbjs._bidsReceived[0]['adserverTargeting'] = { + 'always_use_me': 'abc', + }; + + var slots = createSlotArray(); + window.googletag.pubads().setSlots(slots); + + pbjs.setTargetingForGPTAsync(config.adUnitCodes); + + var expected = [ + [ + "hb_bidder", + "appnexus" + ], + [ + "hb_adid", + "233bcbee889d46d" + ], + [ + "hb_pb", + "10.00" + ], + [ + "hb_size", + "300x250" + ], + [ + "foobar", + "300x250" + ], + [ + "always_use_me", + "abc" + ], + [ + "foobar", + "300x250" + ] + ]; + + assert.deepEqual(slots[0].spySetTargeting.args, expected); + }); + + it('should log error when googletag is not defined on page', function () { + const error = 'window.googletag is not defined on the page'; + const windowGoogletagBackup = window.googletag; + window.googletag = {}; + + pbjs.setTargetingForGPTAsync(); + assert.ok(logErrorSpy.calledWith(error), 'expected error was logged'); + window.googletag = windowGoogletagBackup; + }); + }); + + describe('allBidsAvailable', function () { + it('should call bidmanager.allBidsBack', function () { + var spyAllBidsBack = sinon.spy(bidmanager, 'bidsBackAll'); + + pbjs.allBidsAvailable(); + assert.ok(spyAllBidsBack.called, 'called bidmanager.allBidsBack'); + bidmanager.bidsBackAll.restore(); + }); + }); + + describe('renderAd', function () { + var bidId = 1; + var doc = {}; + var adResponse = {}; + var spyLogError = null; + var spyLogMessage = null; + + beforeEach(function () { + doc = { + write: sinon.spy(), + close: sinon.spy(), + defaultView: { + frameElement: { + width: 0, + height: 0 + } + } + }; + + adResponse = { + "adId": bidId, + "width": 300, + "height": 250, + }; + pbjs._bidsReceived.push(adResponse); + + spyLogError = sinon.spy(utils, 'logError'); + spyLogMessage = sinon.spy(utils, 'logMessage'); + }); + + afterEach(function () { + pbjs._bidsReceived.splice(pbjs._bidsReceived.indexOf(adResponse), 1); + utils.logError.restore(); + utils.logMessage.restore(); + }); + + it('should require doc and id params', function () { + pbjs.renderAd(); + var error = 'Error trying to write ad Id :undefined to the page. Missing document or adId'; + assert.ok(spyLogError.calledWith(error), 'expected param error was logged'); + }); + + it('should log message with bid id', function () { + pbjs.renderAd(doc, bidId); + var message = 'Calling renderAd with adId :' + bidId; + assert.ok(spyLogMessage.calledWith(message), 'expected message was logged'); + }); + + it('should write the ad to the doc', function () { + adResponse.ad = ""; + pbjs.renderAd(doc, bidId); + assert.ok(doc.write.calledWith(adResponse.ad), 'ad was written to doc'); + assert.ok(doc.close.called, 'close method called'); + }); + + it('should place the url inside an iframe on the doc', function () { + adResponse.adUrl = "http://server.example.com/ad/ad.js"; + pbjs.renderAd(doc, bidId); + var iframe = '' + assert.ok(doc.write.calledWith(iframe), 'url was written to iframe in doc'); + }); + + it('should log an error when no ad or url', function () { + pbjs.renderAd(doc, bidId); + var error = 'Error trying to write ad. No ad for bid response id: ' + bidId; + assert.ok(spyLogError.calledWith(error), 'expected error was logged'); + }); + + it('should catch errors thrown when trying to write ads to the page', function () { + adResponse.ad = ""; + + var error = { message: 'doc write error' }; + doc.write = sinon.stub().throws(error); + pbjs.renderAd(doc, bidId); + + var errorMessage = 'Error trying to write ad Id :' + bidId + ' to the page:' + error.message; + assert.ok(spyLogError.calledWith(errorMessage), 'expected error was logged'); + }); + + it('should log an error when ad not found', function () { + var fakeId = 99; + pbjs.renderAd(doc, fakeId); + var error = 'Error trying to write ad. Cannot find ad by given id : ' + fakeId; + assert.ok(spyLogError.calledWith(error), 'expected error was logged'); + }); + }); + + describe('requestBids', () => { + it('should add bidsBackHandler callback to bidmanager', () => { + var spyAddOneTimeCallBack = sinon.spy(bidmanager, 'addOneTimeCallback'); + var requestObj = { + bidsBackHandler: function bidsBackHandlerCallback() {} + }; + pbjs.requestBids(requestObj); + assert.ok(spyAddOneTimeCallBack.calledWith(requestObj.bidsBackHandler), + 'called bidmanager.addOneTimeCallback'); + bidmanager.addOneTimeCallback.restore(); + resetAuction(); + }); + + it('should log message when adUnits not configured', () => { + const logMessageSpy = sinon.spy(utils, 'logMessage'); + const adUnitsBackup = pbjs.adUnits; + + pbjs.adUnits = []; + pbjs.requestBids({}); + + assert.ok(logMessageSpy.calledWith('No adUnits configured. No bids requested.'), 'expected message was logged'); + utils.logMessage.restore(); + pbjs.adUnits = adUnitsBackup; + resetAuction(); + }); + + it('should execute callback after timeout', () => { + var spyExecuteCallback = sinon.spy(bidmanager, 'executeCallback'); + var clock = sinon.useFakeTimers(); + var requestObj = { + bidsBackHandler: function bidsBackHandlerCallback() {}, + timeout: 2000 + }; + + pbjs.requestBids(requestObj); + + clock.tick(requestObj.timeout - 1); + assert.ok(spyExecuteCallback.notCalled, 'bidmanager.executeCallback not called'); + + clock.tick(1); + assert.ok(spyExecuteCallback.called, 'called bidmanager.executeCallback'); + + bidmanager.executeCallback.restore(); + clock.restore(); + resetAuction(); + }); + + it('should call callBids function on adaptermanager', () => { + var spyCallBids = sinon.spy(adaptermanager, 'callBids'); + pbjs.requestBids({}); + assert.ok(spyCallBids.called, 'called adaptermanager.callBids'); + adaptermanager.callBids.restore(); + resetAuction(); + }); + }); + + describe('onEvent', () => { + it('should log an error when handler is not a function', () => { + var spyLogError = sinon.spy(utils, 'logError'); + var event = 'testEvent'; + pbjs.onEvent(event); + assert.ok(spyLogError.calledWith('The event handler provided is not a function and was not set on event "' + event + '".'), + 'expected error was logged'); + utils.logError.restore(); + }); + + it('should log an error when id provided is not valid for event', () => { + var spyLogError = sinon.spy(utils, 'logError'); + var event = 'bidWon'; + pbjs.onEvent(event, Function, 'testId'); + assert.ok(spyLogError.calledWith('The id provided is not valid for event "' + event + '" and no handler was set.'), + 'expected error was logged'); + utils.logError.restore(); + }); + + it('should call events.on with valid parameters', () => { + var spyEventsOn = sinon.spy(events, 'on'); + pbjs.onEvent('bidWon', Function); + assert.ok(spyEventsOn.calledWith('bidWon', Function)); + events.on.restore(); + }); + }); + + describe('offEvent', () => { + it('should return when id provided is not valid for event', () => { + var spyEventsOff = sinon.spy(events, 'off'); + pbjs.offEvent('bidWon', Function, 'testId'); + assert.ok(spyEventsOff.notCalled); + events.off.restore(); + }); + + it('should call events.off with valid parameters', () => { + var spyEventsOff = sinon.spy(events, 'off'); + pbjs.offEvent('bidWon', Function); + assert.ok(spyEventsOff.calledWith('bidWon', Function)); + events.off.restore(); + }); + }); + + describe('addCallback', () => { + it('should log error and return null id when error registering callback', () => { + var spyLogError = sinon.spy(utils, 'logError'); + var id = pbjs.addCallback('event', 'fakeFunction'); + assert.equal(id, null, 'id returned was null'); + assert.ok(spyLogError.calledWith('error registering callback. Check method signature'), + 'expected error was logged'); + utils.logError.restore(); + }); + + it('should add callback to bidmanager', () => { + var spyAddCallback = sinon.spy(bidmanager, 'addCallback'); + var id = pbjs.addCallback('event', Function); + assert.ok(spyAddCallback.calledWith(id, Function, 'event'), 'called bidmanager.addCallback'); + bidmanager.addCallback.restore(); + }); + }); + + describe('removeCallback', () => { + it('should return null', () => { + const id = pbjs.removeCallback(); + assert.equal(id, null); + }); + }); + + describe('registerBidAdapter', () => { + it('should register bidAdaptor with adaptermanager', () => { + var registerBidAdapterSpy = sinon.spy(adaptermanager, 'registerBidAdapter'); + pbjs.registerBidAdapter(Function, 'biddercode'); + assert.ok(registerBidAdapterSpy.called, 'called adaptermanager.registerBidAdapter'); + adaptermanager.registerBidAdapter.restore(); + }); + + it('should catch thrown errors', () => { + var spyLogError = sinon.spy(utils, 'logError'); + var errorObject = {message: 'bidderAdaptor error'}; + var bidderAdaptor = sinon.stub().throws(errorObject); + + pbjs.registerBidAdapter(bidderAdaptor, 'biddercode'); + + var errorMessage = 'Error registering bidder adapter : ' + errorObject.message; + assert.ok(spyLogError.calledWith(errorMessage), 'expected error was caught'); + utils.logError.restore(); + }); + }); + + describe('bidsAvailableForAdapter', () => { + it('should update requested bid with status set to available', () => { + const bidderCode = 'appnexus'; + pbjs.bidsAvailableForAdapter(bidderCode); + + const requestedBids = pbjs._bidsRequested.find(bid => bid.bidderCode === bidderCode); + requestedBids.bids.forEach(bid => { + assert.equal(bid.bidderCode, bidderCode, 'bidderCode was set'); + assert.equal(bid.statusMessage, 'Bid available', 'bid set as available'); + }); + }); + }); + + describe('createBid', () => { + it('should return a bid object', () => { + const statusCode = 1; + const bid = pbjs.createBid(statusCode); + assert.isObject(bid, 'bid is an object'); + assert.equal(bid.getStatusCode(), statusCode, 'bid has correct status'); + + const defaultStatusBid = pbjs.createBid(); + assert.isObject(defaultStatusBid, 'bid is an object'); + assert.equal(defaultStatusBid.getStatusCode(), 0, 'bid has correct status'); + }); + }); + + describe('addBidResponse', () => { + it('should call bidmanager.addBidResponse', () => { + const addBidResponseStub = sinon.stub(bidmanager, 'addBidResponse'); + const adUnitCode = 'testcode'; + const bid = pbjs.createBid(0); + + pbjs.addBidResponse(adUnitCode, bid); + assert.ok(addBidResponseStub.calledWith(adUnitCode, bid), 'called bidmanager.addBidResponse'); + bidmanager.addBidResponse.restore(); + }); + }); + + describe('loadScript', () => { + it('should call adloader.loadScript', () => { + const loadScriptSpy = sinon.spy(adloader, 'loadScript'); + const tagSrc = 'testsrc'; + const callback = Function; + const useCache = false; + + pbjs.loadScript(tagSrc, callback, useCache); + assert.ok(loadScriptSpy.calledWith(tagSrc, callback, useCache), 'called adloader.loadScript'); + adloader.loadScript.restore(); + }); + }); + + describe('enableAnalytics', () => { + let logErrorSpy; + + beforeEach(() => { + logErrorSpy = sinon.spy(utils, 'logError'); + }); + + afterEach(() => { + utils.logError.restore(); + }); + + it('should log error when not passed options', () => { + const error = 'pbjs.enableAnalytics should be called with option {}'; + pbjs.enableAnalytics(); + assert.ok(logErrorSpy.calledWith(error), 'expected error was logged'); + }); + + it('should call ga.enableAnalytics with options', () => { + const enableAnalyticsSpy = sinon.spy(ga, 'enableAnalytics'); + + let options = {'provider': 'ga'}; + pbjs.enableAnalytics(options); + assert.ok(enableAnalyticsSpy.calledWith({}), 'ga.enableAnalytics called with empty options object'); + + options['options'] = 'testoptions'; + pbjs.enableAnalytics(options); + assert.ok(enableAnalyticsSpy.calledWith(options.options), 'ga.enableAnalytics called with provided options'); + + ga.enableAnalytics.restore(); + }); + + it('should catch errors thrown from ga.enableAnalytics', () => { + const error = {message: 'Error calling GA: '}; + const enableAnalyticsStub = sinon.stub(ga, 'enableAnalytics').throws(error); + const options = {'provider': 'ga'}; + + pbjs.enableAnalytics(options); + assert.ok(logErrorSpy.calledWith(error.message), 'expected error was caught'); + ga.enableAnalytics.restore(); + }); + + it('should return null for other providers', () => { + const options = {'provider': 'other_provider'}; + const returnValue = pbjs.enableAnalytics(options); + assert.equal(returnValue, null, 'expected return value'); + }); + }); + + describe('sendTimeoutEvent', () => { + it('should emit BID_TIMEOUT for timed out bids', () => { + const eventsEmitSpy = sinon.spy(events, 'emit'); + pbjs.sendTimeoutEvent(); + assert.ok(eventsEmitSpy.calledWith(CONSTANTS.EVENTS.BID_TIMEOUT), 'emitted events BID_TIMEOUT'); + events.emit.restore(); + }); + }); + + describe('aliasBidder', () => { + it('should call adaptermanager.aliasBidder', () => { + const aliasBidAdapterSpy = sinon.spy(adaptermanager, 'aliasBidAdapter'); + const bidderCode = 'testcode'; + const alias = 'testalias'; + + pbjs.aliasBidder(bidderCode, alias); + assert.ok(aliasBidAdapterSpy.calledWith(bidderCode, alias), 'called adaptermanager.aliasBidAdapterSpy'); + adaptermanager.aliasBidAdapter.restore(); + }); + + it('should log error when not passed correct arguments', () => { + const logErrorSpy = sinon.spy(utils, 'logError'); + const error = 'bidderCode and alias must be passed as arguments'; + + pbjs.aliasBidder(); + assert.ok(logErrorSpy.calledWith(error), 'expected error was logged'); + utils.logError.restore(); + }); + }); + + describe('setPriceGranularity', () => { + it('should log error when not passed granularity', () => { + const logErrorSpy = sinon.spy(utils, 'logError'); + const error = 'Prebid Error: no value passed to `setPriceGranularity()`'; + + pbjs.setPriceGranularity(); + assert.ok(logErrorSpy.calledWith(error), 'expected error was logged'); + utils.logError.restore(); + }); + + it('should call bidmanager.setPriceGranularity with granularity', () => { + const setPriceGranularitySpy = sinon.spy(bidmanager, 'setPriceGranularity'); + const granularity = 'low'; + + pbjs.setPriceGranularity(granularity); + assert.ok(setPriceGranularitySpy.called, 'called bidmanager.setPriceGranularity'); + bidmanager.setPriceGranularity.restore(); + }); + }); +}); diff --git a/test/spec/utils_spec.js b/test/spec/utils_spec.js index bb27c221155..d129d4535e4 100755 --- a/test/spec/utils_spec.js +++ b/test/spec/utils_spec.js @@ -1,223 +1,530 @@ -describe("Utils", function() { - var assert = chai.assert; - - describe('replaceTokenInString', function(){ - - it('should replace all given tokens in a String', function() { - var tokensToReplace = { - 'foo': 'bar', - 'zap': 'quux' - }; - - var output = pbjs_testonly.utils_replaceTokenInString("hello %FOO%, I am %ZAP%", tokensToReplace, "%"); - assert.equal(output, "hello bar, I am quux"); - }); - - it('should ignore tokens it does not see', function() { - var output = pbjs_testonly.utils_replaceTokenInString("hello %FOO%", {}, "%"); - - assert.equal(output, "hello %FOO%"); - }); - }); - - describe('getBidIdParamater',function(){ - it('should return value of the key in input object',function(){ - var obj = { - 'a' : 'valueA', - 'b' : 'valueB' - } - var output = pbjs_testonly.utils_getBidIdParamater('a',obj); - assert.equal(output,'valueA'); - }); - - it('should return empty string, if the key is not existsed in the object',function(){ - var obj = { - 'a' : 'valueA', - 'b' : 'valueB' - } - var output = pbjs_testonly.utils_getBidIdParamater('c',obj); - assert.equal(output,''); - }); - }); - - describe('tryAppendQueryString',function(){ - it('should append query string to existing url',function(){ - var url = 'www.a.com?'; - var key = 'b'; - var value = 'c'; - - var output = pbjs_testonly.utils_tryAppendQueryString(url, key, value); - - var expectedResult = url + key + "=" + encodeURIComponent(value) + '&'; - assert.equal(output,expectedResult); - }); - - it('should return existing url, if the value is empty',function(){ - var url = 'www.a.com?'; - var key = 'b'; - var value = ''; - - var output = pbjs_testonly.utils_tryAppendQueryString(url, key, value); - assert.equal(output,url); - }); - }); - - describe('parseQueryStringParameters',function(){ - it('should append query string to existing using the input obj',function(){ - var obj={ - 'a':'1', - 'b':'2' - }; - - var output = pbjs_testonly.utils_parseQueryStringParameters(obj); - var expectedResult = "a=" + encodeURIComponent('1') + "&b=" + encodeURIComponent('2') + "&"; - assert.equal(output,expectedResult); - }); - - it('should return an empty string, if input obj is empty',function(){ - var obj ={}; - var output = pbjs_testonly.utils_parseQueryStringParameters(obj); - assert.equal(output,''); - }); - }); - - describe('transformAdServerTargetingObj',function(){ - it('should append query string to existing using the input obj',function(){ - var obj = { - 'a':'1', - 'b':'2' - }; - - var output = pbjs_testonly.utils_transformAdServerTargetingObj(obj); - var expectedResult = "a=" + encodeURIComponent('1') + "&b=" + encodeURIComponent('2') + "&"; - assert.equal(output,expectedResult); - }); - - it('should return an empty string, if input obj is empty',function(){ - var obj ={}; - var output = pbjs_testonly.utils_transformAdServerTargetingObj(obj); - assert.equal(output,''); - }); - }); - - describe('extend',function(){ - it('should merge two input object',function(){ - var target = { - 'a':'1', - 'b':'2' - }; - - var source = { - 'c':'3' - }; - - var expectedResult = { - 'a':'1', - 'b':'2', - 'c':'3' - }; - - var output = pbjs_testonly.utils_extend(target, source); - assert.deepEqual(output,expectedResult); - }); - - it('should merge two input object even though target object is empty',function(){ - var target = {}; - var source = { - 'c':'3' - }; - - var output = pbjs_testonly.utils_extend(target, source); - assert.deepEqual(output,source); - }); - - it('just return target object, if the source object is empty',function(){ - var target = { - 'a':'1', - 'b':'2' - }; - var source = {}; - - var output = pbjs_testonly.utils_extend(target, source); - assert.deepEqual(output,target); - }); - }); - - describe('parseSizesInput',function(){ - - it('should return query string using multi size array',function(){ - var sizes = [[728, 90], [970, 90]]; - var output = pbjs_testonly.utils_parseSizesInput(sizes); - assert.equal(output,'size=728x90&promo_sizes=970x90'); - }); - - it('should return query string using single size array',function(){ - var sizes = [728, 90]; - var output = pbjs_testonly.utils_parseSizesInput(sizes); - assert.equal(output,'size=728x90'); - }); - - it('should return query string using string input',function(){ - var sizes = '300x250,970x90'; - var output = pbjs_testonly.utils_parseSizesInput(sizes); - assert.equal(output,'size=300x250&promo_sizes=970x90'); - }); - - it('return undefined if input array is empty',function(){ - var sizes =[]; - var output = pbjs_testonly.utils_parseSizesInput(sizes); - assert.equal(output,undefined); - }); - }); - - describe('parseGPTSingleSizeArray',function(){ - - it('should return size string with input single size array',function(){ - var size = [300,250]; - var output = pbjs_testonly.utils_parseGPTSingleSizeArray(size); - assert.equal(output,'300x250'); - }); - - it('should return size string with input single size array',function(){ - var size =['300','250']; - var output = pbjs_testonly.utils_parseGPTSingleSizeArray(size); - assert.equal(output,'300x250'); - }); - - it('return undefined using string input',function(){ - var size ='1'; - var output = pbjs_testonly.utils_parseGPTSingleSizeArray(size); - assert.equal(output,undefined); - }); - - it('return undefined using number input',function(){ - var size =1; - var output = pbjs_testonly.utils_parseGPTSingleSizeArray(size); - assert.equal(output,undefined); - }); - - it('return undefined using one length single array',function(){ - var size =[300]; - var output = pbjs_testonly.utils_parseGPTSingleSizeArray(size); - assert.equal(output,undefined); - }); - - it('return undefined if the input is empty',function(){ - var size =''; - var output = pbjs_testonly.utils_parseGPTSingleSizeArray(size); - assert.equal(output,undefined); - }); - - it('return undefined if the input is not a number',function(){ - var size =['foo','bar']; - var output = pbjs_testonly.utils_parseGPTSingleSizeArray(size); - assert.equal(output,undefined); - }); - - it('return undefined if the input is not a number 2',function(){ - var size =['foo',300]; - var output = pbjs_testonly.utils_parseGPTSingleSizeArray(size); - assert.equal(output,undefined); - }); - }); -}); +import { getSlotTargeting, getAdServerTargeting } from 'test/fixtures/fixtures'; + +var assert = require('assert'); +var utils = require('../../src/utils'); + +describe('Utils', function () { + + var obj_string = 's', + obj_number = 1, + obj_object = {}, + obj_array = [], + obj_function = function () {}; + + var type_string = 'String', + type_number = 'Number', + type_object = 'Object', + type_array = 'Array', + type_function = 'Function'; + + describe('replaceTokenInString', function () { + + it('should replace all given tokens in a String', function () { + var tokensToReplace = { + foo: 'bar', + zap: 'quux' + }; + + var output = utils.replaceTokenInString('hello %FOO%, I am %ZAP%', tokensToReplace, '%'); + assert.equal(output, 'hello bar, I am quux'); + }); + + it('should ignore tokens it does not see', function () { + var output = utils.replaceTokenInString('hello %FOO%', {}, '%'); + + assert.equal(output, 'hello %FOO%'); + }); + }); + + describe('getBidIdParamater', function () { + it('should return value of the key in input object', function () { + var obj = { + a: 'valueA', + b: 'valueB' + }; + var output = utils.getBidIdParamater('a', obj); + assert.equal(output, 'valueA'); + }); + + it('should return empty string, if the key is not existsed in the object', function () { + var obj = { + a: 'valueA', + b: 'valueB' + }; + var output = utils.getBidIdParamater('c', obj); + assert.equal(output, ''); + }); + }); + + describe('tryAppendQueryString', function () { + it('should append query string to existing url', function () { + var url = 'www.a.com?'; + var key = 'b'; + var value = 'c'; + + var output = utils.tryAppendQueryString(url, key, value); + + var expectedResult = url + key + '=' + encodeURIComponent(value) + '&'; + assert.equal(output, expectedResult); + }); + + it('should return existing url, if the value is empty', function () { + var url = 'www.a.com?'; + var key = 'b'; + var value = ''; + + var output = utils.tryAppendQueryString(url, key, value); + assert.equal(output, url); + }); + }); + + describe('parseQueryStringParameters', function () { + it('should append query string to existing using the input obj', function () { + var obj = { + a:'1', + b:'2' + }; + + var output = utils.parseQueryStringParameters(obj); + var expectedResult = 'a=' + encodeURIComponent('1') + '&b=' + encodeURIComponent('2') + '&'; + assert.equal(output, expectedResult); + }); + + it('should return an empty string, if input obj is empty', function () { + var obj = {}; + var output = utils.parseQueryStringParameters(obj); + assert.equal(output, ''); + }); + }); + + describe('transformAdServerTargetingObj', function () { + it('should append query string to existing using the input obj', function () { + var obj = getAdServerTargeting(); + + var output = utils.transformAdServerTargetingObj(obj[Object.keys(obj)[0]]); + var expected = 'foobar=300x250&hb_size=300x250&hb_pb=10.00&hb_adid=233bcbee889d46d&hb_bidder=appnexus&hb_size_triplelift=0x0&hb_pb_triplelift=10.00&hb_adid_triplelift=222bb26f9e8bd&hb_bidder_triplelift=triplelift&hb_size_appnexus=300x250&hb_pb_appnexus=10.00&hb_adid_appnexus=233bcbee889d46d&hb_bidder_appnexus=appnexus&hb_size_pagescience=300x250&hb_pb_pagescience=10.00&hb_adid_pagescience=25bedd4813632d7&hb_bidder_pagescienc=pagescience&hb_size_brightcom=300x250&hb_pb_brightcom=10.00&hb_adid_brightcom=26e0795ab963896&hb_bidder_brightcom=brightcom&hb_size_brealtime=300x250&hb_pb_brealtime=10.00&hb_adid_brealtime=275bd666f5a5a5d&hb_bidder_brealtime=brealtime&hb_size_pubmatic=300x250&hb_pb_pubmatic=10.00&hb_adid_pubmatic=28f4039c636b6a7&hb_bidder_pubmatic=pubmatic&hb_size_rubicon=300x600&hb_pb_rubicon=10.00&hb_adid_rubicon=29019e2ab586a5a&hb_bidder_rubicon=rubicon'; + assert.equal(output, expected); + }); + + it('should return an empty string, if input obj is empty', function () { + var obj = {}; + var output = utils.transformAdServerTargetingObj(obj); + assert.equal(output, ''); + }); + }); + + describe('extend', function () { + it('should merge two input object', function () { + var target = { + a:'1', + b:'2' + }; + + var source = { + c:'3' + }; + + var expectedResult = { + a:'1', + b:'2', + c:'3' + }; + + var output = utils.extend(target, source); + assert.deepEqual(output, expectedResult); + }); + + it('should merge two input object even though target object is empty', function () { + var target = {}; + var source = { + c:'3' + }; + + var output = utils.extend(target, source); + assert.deepEqual(output, source); + }); + + it('just return target object, if the source object is empty', function () { + var target = { + a:'1', + b:'2' + }; + var source = {}; + + var output = utils.extend(target, source); + assert.deepEqual(output, target); + }); + }); + + describe('parseSizesInput', function () { + + it('should return query string using multi size array', function () { + var sizes = [[728, 90], [970, 90]]; + var output = utils.parseSizesInput(sizes); + assert.deepEqual(output, ['728x90', '970x90']); + }); + + it('should return query string using single size array', function () { + var sizes = [728, 90]; + var output = utils.parseSizesInput(sizes); + assert.deepEqual(output, ['728x90']); + }); + + it('should return query string using string input', function () { + var sizes = '300x250,970x90'; + var output = utils.parseSizesInput(sizes); + assert.deepEqual(output, ['300x250', '970x90']); + }); + + it('return undefined if input array is empty', function () { + var sizes = []; + var output = utils.parseSizesInput(sizes); + assert.deepEqual(output, []); + }); + }); + + describe('parseGPTSingleSizeArray', function () { + + it('should return size string with input single size array', function () { + var size = [300, 250]; + var output = utils.parseGPTSingleSizeArray(size); + assert.equal(output, '300x250'); + }); + + it('should return size string with input single size array', function () { + var size = ['300', '250']; + var output = utils.parseGPTSingleSizeArray(size); + assert.equal(output, '300x250'); + }); + + it('return undefined using string input', function () { + var size = '1'; + var output = utils.parseGPTSingleSizeArray(size); + assert.equal(output, undefined); + }); + + it('return undefined using number input', function () { + var size = 1; + var output = utils.parseGPTSingleSizeArray(size); + assert.equal(output, undefined); + }); + + it('return undefined using one length single array', function () { + var size = [300]; + var output = utils.parseGPTSingleSizeArray(size); + assert.equal(output, undefined); + }); + + it('return undefined if the input is empty', function () { + var size = ''; + var output = utils.parseGPTSingleSizeArray(size); + assert.equal(output, undefined); + }); + + it('return undefined if the input is not a number', function () { + var size = ['foo', 'bar']; + var output = utils.parseGPTSingleSizeArray(size); + assert.equal(output, undefined); + }); + + it('return undefined if the input is not a number 2', function () { + var size = ['foo', 300]; + var output = utils.parseGPTSingleSizeArray(size); + assert.equal(output, undefined); + }); + }); + + describe('isA', function () { + it('should return true with string object', function () { + var output = utils.isA(obj_string, type_string); + assert.deepEqual(output, true); + }); + + it('should return false with object', function () { + var output = utils.isA(obj_object, type_string); + assert.deepEqual(output, false); + }); + + it('should return true with object', function () { + var output = utils.isA(obj_object, type_object); + assert.deepEqual(output, true); + }); + + it('should return false with array object', function () { + var output = utils.isA(obj_array, type_object); + assert.deepEqual(output, false); + }); + + it('should return true with array object', function () { + var output = utils.isA(obj_array, type_array); + assert.deepEqual(output, true); + }); + + it('should return false with array object', function () { + var output = utils.isA(obj_array, type_function); + assert.deepEqual(output, false); + }); + + it('should return true with function', function () { + var output = utils.isA(obj_function, type_function); + assert.deepEqual(output, true); + }); + + it('should return false with number', function () { + var output = utils.isA(obj_function, type_number); + assert.deepEqual(output, false); + }); + + it('should return true with number', function () { + var output = utils.isA(obj_number, type_number); + assert.deepEqual(output, true); + }); + }); + + describe('isFn', function () { + it('should return true with input function', function () { + var output = utils.isFn(obj_function); + assert.deepEqual(output, true); + }); + + it('should return false with input string', function () { + var output = utils.isFn(obj_string); + assert.deepEqual(output, false); + }); + + it('should return false with input number', function () { + var output = utils.isFn(obj_number); + assert.deepEqual(output, false); + }); + + it('should return false with input Array', function () { + var output = utils.isFn(obj_array); + assert.deepEqual(output, false); + }); + + it('should return false with input object', function () { + var output = utils.isFn(obj_object); + assert.deepEqual(output, false); + }); + }); + + describe('isStr', function () { + it('should return true with input string', function () { + var output = utils.isStr(obj_string); + assert.deepEqual(output, true); + }); + + it('should return false with input number', function () { + var output = utils.isStr(obj_number); + assert.deepEqual(output, false); + }); + + it('should return false with input object', function () { + var output = utils.isStr(obj_object); + assert.deepEqual(output, false); + }); + + it('should return false with input array', function () { + var output = utils.isStr(obj_array); + assert.deepEqual(output, false); + }); + + it('should return false with input function', function () { + var output = utils.isStr(obj_function); + assert.deepEqual(output, false); + }); + + }); + + describe('isArray', function () { + it('should return false with input string', function () { + var output = utils.isArray(obj_string); + assert.deepEqual(output, false); + }); + + it('should return false with input number', function () { + var output = utils.isArray(obj_number); + assert.deepEqual(output, false); + }); + + it('should return false with input object', function () { + var output = utils.isArray(obj_object); + assert.deepEqual(output, false); + }); + + it('should return true with input array', function () { + var output = utils.isArray(obj_array); + assert.deepEqual(output, true); + }); + + it('should return false with input function', function () { + var output = utils.isArray(obj_function); + assert.deepEqual(output, false); + }); + + }); + + describe('isEmpty', function () { + it('should return true with empty object', function () { + var output = utils.isEmpty(obj_object); + assert.deepEqual(output, true); + }); + + it('should return false with non-empty object', function () { + var obj = { a:'b' }; + var output = utils.isEmpty(obj); + assert.deepEqual(output, false); + }); + + it('should return false with null', function () { + var obj = null; + var output = utils.isEmpty(obj); + assert.deepEqual(output, true); + }); + }); + + describe('contains', function () { + it('should return true if the input string contains in the input obj', function () { + var output = utils.contains('123', '1'); + assert.deepEqual(output, true); + }); + + it('should return false if the input string do not contain in the input obj', function () { + var output = utils.contains('234', '1'); + assert.deepEqual(output, false); + }); + + it('should return false if the input string is empty', function () { + var output = utils.contains(); + assert.ok(!output, 'an empty string returns false'); + }); + }); + + describe('_map', function () { + it('return empty array when input object is empty', function () { + var input = {}; + var callback = function () {}; + + var output = utils._map(input, callback); + assert.deepEqual(output, []); + }); + + it('return value array with vaild input object', function () { + var input = { a:'A', b:'B' }; + var callback = function (v) {return v;}; + + var output = utils._map(input, callback); + assert.deepEqual(output, ['A', 'B']); + }); + + it('return value array with vaild input object_callback func changed 1', function () { + var input = { a:'A', b:'B' }; + var callback = function (v, k) {return v + k;}; + + var output = utils._map(input, callback); + assert.deepEqual(output, ['Aa', 'Bb']); + }); + + it('return value array with vaild input object_callback func changed 2', function () { + var input = { a:'A', b:'B' }; + var callback = function (v, k, o) {return o;}; + + var output = utils._map(input, callback); + assert.deepEqual(output, [input, input]); + }); + }); + + describe('createInvisibleIframe', function () { + var output = utils.createInvisibleIframe(); + + it('return iframe - id', function () { + assert.ok(output.id); + }); + + it('return iframe - height', function () { + assert.deepEqual(output.height, 0); + }); + + it('return iframe - width', function () { + assert.deepEqual(output.width, 0); + }); + + it('return iframe - border', function () { + assert.deepEqual(output.border, '0px'); + }); + + it('return iframe - hspace', function () { + assert.deepEqual(output.hspace, '0'); + }); + + it('return iframe - vspace', function () { + assert.deepEqual(output.vspace, '0'); + }); + + it('return iframe - marginWidth', function () { + assert.deepEqual(output.marginWidth, '0'); + }); + + it('return iframe - marginHeight', function () { + assert.deepEqual(output.marginHeight, '0'); + }); + + //it('return iframe - style.border',function(){ + // assert.deepEqual(output.style.border,'0px'); + //}); + it('return iframe - scrolling', function () { + assert.deepEqual(output.scrolling, 'no'); + }); + + it('return iframe - frameBorder', function () { + assert.deepEqual(output.frameBorder, '0'); + }); + + it('return iframe - src', function () { + assert.deepEqual(output.src, 'about:blank'); + }); + + it('return iframe - style', function () { + assert.ok(output.style); + }); + }); + + describe('getHighestCpm', function () { + it('should pick the existing highest cpm', function () { + var previous = { + cpm: 2, + timeToRespond: 100 + }; + var current = { + cpm: 1, + timeToRespond: 100 + }; + assert.equal(utils.getHighestCpm(previous, current), previous); + }); + + it('should pick the new highest cpm', function () { + var previous = { + cpm: 1, + timeToRespond: 100 + }; + var current = { + cpm: 2, + timeToRespond: 100 + }; + assert.equal(utils.getHighestCpm(previous, current), current); + }); + + it('should pick the fastest cpm in case of tie', function () { + var previous = { + cpm: 1, + timeToRespond: 100 + }; + var current = { + cpm: 1, + timeToRespond: 50 + }; + assert.equal(utils.getHighestCpm(previous, current), current); + }); + }); + +}); diff --git a/test/test.js b/test/test.js deleted file mode 100644 index 800ec0f6817..00000000000 --- a/test/test.js +++ /dev/null @@ -1,316 +0,0 @@ -var assert = require("assert"); - -/* use this method to test individual files instead of the whole prebid.js project */ - -//TODO refactor to use the spec files -var utils = require('../src/utils'); -var bidmanager = require('../src/bidmanager'); -var appnexus = require('../src/adapters/appnexus'); - - describe('appnexus adapter unit tests', function(){ - - var adapterInstance = null; - var bidRequest = { - bidder: "appnexus", - params: { - memberId : "123", - placementId: "123345", - invCode : 'inv_code', - referrer: "url.com", - alt_referrer: "url.com", - extraParam: "foobar", - somethingElse : 'hello', - query : { - foo : 'bar', - tasty : 'treat' - } - }, - placementCode: "/19968336/header-bid-tag-0", - sizes: [ - [300, 250], - [300, 600] - ] - }; - var callbackId = 'cbId'; - - - it('Get instance', function() { - adapterInstance = appnexus.createNew(); - assert.ok(adapterInstance); - }); - - it('buildJPTCall()', function() { - var expectedUrl = 'http://ib.adnxs.com/jpt?callback=pbjs.handleAnCB&callback_uid=cbId&psa=0&id=123345&member_id=123&code=inv_code&size=300x250&promo_sizes=300x600&foo=bar&tasty=treat&extraParam=foobar&somethingElse=hello&referrer=url.com&alt_referrer=url.com'; - var url = adapterInstance.buildJPTCall(bidRequest, callbackId); - assert.equal(url, expectedUrl); - }); - - - }); - - describe('replaceTokenInString', function(){ - - it('should replace all given tokens in a String', function() { - var tokensToReplace = { - 'foo': 'bar', - 'zap': 'quux' - }; - - var output = utils.replaceTokenInString("hello %FOO%, I am %ZAP%", tokensToReplace, "%"); - assert.equal(output, "hello bar, I am quux"); - }); - - it('should ignore tokens it does not see', function() { - var output = utils.replaceTokenInString("hello %FOO%", {}, "%"); - - assert.equal(output, "hello %FOO%"); - }); - }); - - - describe('bidmanager.js', function(){ - - describe('getKeyValueTargetingPairs', function(){ - var bid = {}; - var bidPriceCpm = 5.578; - var bidPbLg = 5.50; - var bidPbMg = 5.50; - var bidPbHg = 5.57; - var adUnitCode = '12345'; - var bidderCode = 'appnexus'; - var size = '300x250'; - var adId = '1adId'; - - before(function() { - bid.cpm = bidPriceCpm; - bid.pbLg = bidPbLg; - bid.pbMg = bidPbMg; - bid.pbHg = bidPbHg; - bid.height = 300; - bid.width = 250; - bid.adUnitCode = adUnitCode; - bid.getSize = function(){ - return this.height + 'x' + this.width; - }; - bid.bidderCode = bidderCode; - bid.adId = adId; - - }); - - - it('No bidder level configuration defined - default', function() { - var expected = {"hb_bidder": bidderCode, "hb_adid": adId,"hb_pb": bidPbMg,"hb_size": size}; - var response = bidmanager.getKeyValueTargetingPairs(bidderCode, bid); - assert.deepEqual(response, expected); - - }); - - it('Custom configuration for all bidders', function() { - pbjs.bidderSettings = - { - standard: { - adserverTargeting: [{ - key: "hb_bidder", - val: function(bidResponse) { - return bidResponse.bidderCode; - } - }, { - key: "hb_adid", - val: function(bidResponse) { - return bidResponse.adId; - } - }, { - key: "hb_pb", - val: function(bidResponse) { - //change default here - return bidResponse.pbHg; - } - }, { - key: "hb_size", - val: function(bidResponse) { - return bidResponse.size; - - } - }] - - } - }; - - var expected = {"hb_bidder": bidderCode, "hb_adid": adId,"hb_pb": bidPbHg,"hb_size": size}; - var response = bidmanager.getKeyValueTargetingPairs(bidderCode, bid); - assert.deepEqual(response, expected); - - }); - - it('Custom configuration for one bidder', function() { - pbjs.bidderSettings = - { - appnexus: { - adserverTargeting: [{ - key: "hb_bidder", - val: function(bidResponse) { - return bidResponse.bidderCode; - } - }, { - key: "hb_adid", - val: function(bidResponse) { - return bidResponse.adId; - } - }, { - key: "hb_pb", - val: function(bidResponse) { - //change default here - return bidResponse.pbHg; - } - }, { - key: "hb_size", - val: function(bidResponse) { - return bidResponse.size; - - } - }] - - } - }; - - var expected = {"hb_bidder": bidderCode, "hb_adid": adId,"hb_pb": bidPbHg,"hb_size": size}; - var response = bidmanager.getKeyValueTargetingPairs(bidderCode, bid); - assert.deepEqual(response, expected); - - }); - - it('Custom configuration for one bidder - not matched', function() { - pbjs.bidderSettings = - { - nonExistentBidder: { - adserverTargeting: [{ - key: "hb_bidder", - val: function(bidResponse) { - return bidResponse.bidderCode; - } - }, { - key: "hb_adid", - val: function(bidResponse) { - return bidResponse.adId; - } - }, { - key: "hb_pb", - val: function(bidResponse) { - //change default here - return bidResponse.pbHg; - } - }, { - key: "hb_size", - val: function(bidResponse) { - return bidResponse.size; - - } - }] - - } - }; - - var expected = {"hb_bidder": bidderCode, "hb_adid": adId,"hb_pb": bidPbMg,"hb_size": size}; - var response = bidmanager.getKeyValueTargetingPairs(bidderCode, bid); - assert.deepEqual(response, expected); - - }); - - it('Custom bidCpmAdjustment for one bidder and inherit standard', function() { - pbjs.bidderSettings = - { - appnexus: { - bidCpmAdjustment : function(bidCpm){ - return bidCpm * 0.7; - }, - }, - standard: { - adserverTargeting: [{ - key: "hb_bidder", - val: function(bidResponse) { - return bidResponse.bidderCode; - } - }, { - key: "hb_adid", - val: function(bidResponse) { - return bidResponse.adId; - } - }, { - key: "hb_pb", - val: function(bidResponse) { - //change default here - return 10.00; - } - }] - - } - }; - - var expected = {"hb_bidder": bidderCode, "hb_adid": adId,"hb_pb": 10.0 }; - var response = bidmanager.getKeyValueTargetingPairs(bidderCode, bid); - assert.deepEqual(response, expected); - - }); - - it('Custom bidCpmAdjustment AND custom configuration for one bidder and do NOT inherit standard', function() { - pbjs.bidderSettings = - { - appnexus: { - bidCpmAdjustment : function(bidCpm){ - return bidCpm * 0.7; - }, - adserverTargeting: [{ - key: "hb_bidder", - val: function(bidResponse) { - return bidResponse.bidderCode; - } - }, { - key: "hb_adid", - val: function(bidResponse) { - return bidResponse.adId; - } - }, { - key: "hb_pb", - val: function(bidResponse) { - //change default here - return 15.00; - } - }] - }, - standard: { - adserverTargeting: [{ - key: "hb_bidder", - val: function(bidResponse) { - return bidResponse.bidderCode; - } - }, { - key: "hb_adid", - val: function(bidResponse) { - return bidResponse.adId; - } - }, { - key: "hb_pb", - val: function(bidResponse) { - //change default here - return 10.00; - }, - }, - { - key: "hb_size", - val: function(bidResponse) { - return bidResponse.size; - - } - }] - - } - }; - - var expected = {"hb_bidder": bidderCode, "hb_adid": adId,"hb_pb": 15.0 }; - var response = bidmanager.getKeyValueTargetingPairs(bidderCode, bid); - assert.deepEqual(response, expected); - - }); - - }); - }); \ No newline at end of file diff --git a/test/utilsTest.js b/test/utilsTest.js deleted file mode 100644 index d367ab66278..00000000000 --- a/test/utilsTest.js +++ /dev/null @@ -1,28 +0,0 @@ -var assert = require("assert"); - -describe('Utils', function () { - - window = { console: console }; - - var utils = require('../src/utils.js'); - - describe('#replaceTokenInString', function () { - - it('should replace all given tokens in a String', function () { - var tokensToReplace = { - 'foo': 'bar', - 'zap': 'quux' - }; - - var output = utils.replaceTokenInString("hello %FOO%, I am %ZAP%", tokensToReplace, "%"); - - assert.equal(output, "hello bar, I am quux"); - }); - - it('should ignore tokens it does not see', function() { - var output = utils.replaceTokenInString("hello %FOO%", {}, "%"); - - assert.equal(output, "hello %FOO%"); - }); - }) -}); \ No newline at end of file diff --git a/webpack.conf.js b/webpack.conf.js new file mode 100644 index 00000000000..e471f71e821 --- /dev/null +++ b/webpack.conf.js @@ -0,0 +1,53 @@ +var prebid = require('./package.json'); +var StringReplacePlugin = require('string-replace-webpack-plugin'); + +module.exports = { + output: { + filename: 'prebid.js' + }, + devtool: 'source-map', + resolve: { + modulesDirectories: ['', 'node_modules', 'src'] + }, + resolveLoader: { + modulesDirectories: ['loaders', 'node_modules'] + }, + module: { + loaders: [ + { + test: /\.js$/, + include: /(src|test)/, + loader: 'babel', // 'babel-loader' is also a legal name to reference + query: { + presets: ['es2015'] + } + }, + { + test: /\.json$/, + loader: 'json' + }, + { + test: /adaptermanager.js/, + include: /(src)/, + loader: 'adapterLoader' + }, + { + test: /constants.json$/, + include: /(src)/, + loader: StringReplacePlugin.replace({ + replacements: [ + { + pattern: /%%REPO_AND_VERSION%%/g, + replacement: function (match, p1, offset, string) { + return `${prebid.repository.url.split('/')[3]}_prebid_${prebid.version}`; + } + } + ] + }) + } + ] + }, + plugins: [ + new StringReplacePlugin() + ] +};