├── src ├── extension │ ├── firefox │ │ ├── README.md │ │ ├── doc │ │ │ └── main.md │ │ ├── package.json │ │ ├── test │ │ │ └── test-main.js │ │ ├── data │ │ │ └── content.js │ │ └── lib │ │ │ └── main.js │ ├── icon128.png │ ├── chrome │ │ ├── content.js │ │ ├── background.js │ │ └── manifest.json │ └── resizer.js ├── frame-to-element.js └── polyfill │ ├── main.js │ ├── options.js │ ├── style.scss │ ├── state-manager.js │ ├── mixins.scss │ ├── mraid.js │ └── web-view.js ├── .jshintignore ├── .nodemonignore ├── test ├── chai-expect.js ├── get-screen-size.js ├── get-max-size.js ├── create-calendar-event.js ├── get-resize-properties.js ├── store-picture.js ├── open.js ├── get-expand-properties.js ├── close.js ├── supports.js ├── get-default-position.js ├── expand.js ├── play-video.js ├── get-current-position.js ├── use-custom-close.js ├── main.js ├── util.js ├── resize.js ├── get-state.js └── events.js ├── public ├── creatives │ ├── test.html │ └── test_ad.html ├── iframe.html └── mocha │ ├── index.html │ ├── mocha.css │ └── mocha.js ├── .jshintrc ├── .gitignore ├── app.js ├── package.json ├── README.md └── LICENSE /src/extension/firefox/README.md: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /src/extension/firefox/doc/main.md: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /.jshintignore: -------------------------------------------------------------------------------- 1 | public/* 2 | node_modules/* 3 | dist/* 4 | -------------------------------------------------------------------------------- /.nodemonignore: -------------------------------------------------------------------------------- 1 | :.git* 2 | :dist\/* 3 | :public\/mocha\/tests.js 4 | -------------------------------------------------------------------------------- /src/extension/icon128.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/appnexus/mraid-extension/HEAD/src/extension/icon128.png -------------------------------------------------------------------------------- /test/chai-expect.js: -------------------------------------------------------------------------------- 1 | var chai = require('chai'); 2 | chai.use(require('sinon-chai')); 3 | chai.use(require('chai-jquery')); 4 | 5 | module.exports = chai.expect; 6 | -------------------------------------------------------------------------------- /public/creatives/test.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | -------------------------------------------------------------------------------- /public/iframe.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /.jshintrc: -------------------------------------------------------------------------------- 1 | { 2 | "expr": true, 3 | "esnext": true, 4 | "node" : true, 5 | "browser" : true, 6 | "globals": { 7 | "console": false, 8 | "describe": false, 9 | "it": false 10 | } 11 | } 12 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | /public/mocha/tests.js 2 | /dist/* 3 | lib-cov 4 | *.seed 5 | *.log 6 | *.csv 7 | *.dat 8 | *.out 9 | *.pid 10 | *.gz 11 | *.xpi 12 | 13 | pids 14 | logs 15 | results 16 | 17 | npm-debug.log 18 | node_modules 19 | -------------------------------------------------------------------------------- /src/extension/firefox/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "mraid", 3 | "fullName": "mraid", 4 | "id": "jid1-O3xsuQMvpe32qg", 5 | "description": "View MRAID creatives in a desktop browser", 6 | "author": "AppNexus", 7 | "license": "MPL 2.0", 8 | "version": "0.1" 9 | } 10 | -------------------------------------------------------------------------------- /src/extension/firefox/test/test-main.js: -------------------------------------------------------------------------------- 1 | var main = require("./main"); 2 | 3 | exports["test main"] = function(assert) { 4 | assert.pass("Unit test running!"); 5 | }; 6 | 7 | exports["test main async"] = function(assert, done) { 8 | assert.pass("async Unit test running!"); 9 | done(); 10 | }; 11 | 12 | require("sdk/test").run(exports); 13 | -------------------------------------------------------------------------------- /test/get-screen-size.js: -------------------------------------------------------------------------------- 1 | var expect = require('./chai-expect'); 2 | 3 | describe('#getScreenSize()', function(){ 4 | it('should return ipad size', function(){ 5 | var size = window.mraid.getScreenSize(); 6 | 7 | expect(size).to.be.ok; 8 | expect(size.width).to.be.equal(768); 9 | expect(size.height).to.be.equal(1024); 10 | }); 11 | }); 12 | 13 | -------------------------------------------------------------------------------- /test/get-max-size.js: -------------------------------------------------------------------------------- 1 | var expect = require('./chai-expect'); 2 | 3 | describe('#getMaxSize()', function(){ 4 | it('should return the screen size', function(){ 5 | var size = window.mraid.getMaxSize(); 6 | 7 | expect(size).to.be.ok; 8 | expect(size.width).to.be.equal(768); 9 | expect(size.height).to.be.equal(1024); 10 | }); 11 | }); 12 | 13 | 14 | -------------------------------------------------------------------------------- /src/frame-to-element.js: -------------------------------------------------------------------------------- 1 | module.exports = function(iframe, w){ 2 | w = w || iframe.parent; 3 | 4 | let iframes = w.document.getElementsByTagName('iframe'); 5 | 6 | if (!iframes) return; 7 | 8 | for (let x=0; x']}, 7 | ['blocking'] 8 | ); 9 | 10 | function _beforeRequest(details){ 11 | if (!/mraid\.js($|\?)/i.test(details.url)) return; 12 | if (details.url === mraidUrl) return; 13 | 14 | chrome.tabs.getSelected(null, function(tab){ 15 | chrome.pageAction.show(tab.id); 16 | }); 17 | 18 | return { redirectUrl: mraidUrl }; 19 | } 20 | -------------------------------------------------------------------------------- /test/open.js: -------------------------------------------------------------------------------- 1 | var expect = require('./chai-expect'); 2 | var util = require('./util'); 3 | 4 | describe('#open()', function(){ 5 | it('should show the overlay for tel:', function(){ 6 | var $ad = util.get$Ad(); 7 | mraid.open('tel:555-555-5555'); 8 | 9 | var $msg = $ad.find('.anx-mraid-msg'); 10 | expect($msg).to.be.visible; 11 | }); 12 | it('should show the overlay for sms:', function(){ 13 | var $ad = util.get$Ad(); 14 | mraid.open('sms:555-555-5555'); 15 | 16 | var $msg = $ad.find('.anx-mraid-msg'); 17 | expect($msg).to.be.visible; 18 | }); 19 | }); 20 | -------------------------------------------------------------------------------- /src/polyfill/main.js: -------------------------------------------------------------------------------- 1 | var Mraid, options, $; 2 | 3 | if (window.mraid){ 4 | if (typeof window.mraid.enable === 'function'){ 5 | window.mraid.enable(); 6 | } 7 | 8 | return; 9 | } 10 | 11 | Mraid = require('./mraid'); 12 | options = require('./options'); 13 | $ = require('jquery'); 14 | 15 | window.mraid = new Mraid({ 16 | placementType: 'inline', 17 | screen: options.getScreenSize() 18 | }); 19 | 20 | if (!window.mocha){ 21 | if (window.document.readyState === 'complete'){ 22 | window.mraid.triggerReady(); 23 | } else { 24 | $(function(){ 25 | window.mraid.triggerReady(); 26 | }); 27 | } 28 | } 29 | -------------------------------------------------------------------------------- /public/mocha/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | Mocha 5 | 6 | 7 | 8 | 9 | 10 |
11 | 12 | 13 | 14 | 17 | 18 |
19 |
20 | 21 | 22 | 23 | -------------------------------------------------------------------------------- /test/get-expand-properties.js: -------------------------------------------------------------------------------- 1 | var expect = require('./chai-expect'); 2 | 3 | describe('#getExpandProperties()', function(){ 4 | it('should default to screen size', function(){ 5 | var xp = mraid.getExpandProperties(); 6 | 7 | expect(xp).to.be.ok; 8 | expect(xp.width).to.be.equal(768); 9 | expect(xp.height).to.be.equal(1024); 10 | }); 11 | 12 | it('should give you back what you set', function(){ 13 | var o = {width: 600, height: 200}; 14 | mraid.setExpandProperties(o); 15 | 16 | expect(mraid.getExpandProperties()).to.be.equal(o); 17 | expect(mraid.getResizeProperties()).to.be.not.equal(o); 18 | }); 19 | }); 20 | -------------------------------------------------------------------------------- /test/close.js: -------------------------------------------------------------------------------- 1 | var expect = require('./chai-expect'); 2 | var util = require('./util'); 3 | 4 | describe('#close()', function(){ 5 | it('should return expanded ad to regular size', function(){ 6 | mraid.expand(); 7 | mraid.close(); 8 | 9 | util.expectAdSize(320, 50); 10 | }); 11 | 12 | it('should hide the close button', function(){ 13 | var $ad = util.get$Ad(); 14 | 15 | mraid.expand(); 16 | mraid.close(); 17 | 18 | expect($ad.find('.anx-mraid-close')).to.be.not.visible; 19 | }); 20 | 21 | it('should hide the ad', function(){ 22 | var $ad = util.get$Ad(); 23 | 24 | mraid.close(); 25 | 26 | expect($ad).to.be.not.visible; 27 | }); 28 | }); 29 | -------------------------------------------------------------------------------- /test/supports.js: -------------------------------------------------------------------------------- 1 | var expect = require('./chai-expect'); 2 | var util = require('./util'); 3 | 4 | describe('#supports()', function(){ 5 | it('should report "support" for everything in spec by default', function(){ 6 | expect(mraid.supports('sms')).to.be.true; 7 | expect(mraid.supports('SMS')).to.be.true; 8 | expect(mraid.supports('tel')).to.be.true; 9 | expect(mraid.supports('calendar')).to.be.true; 10 | expect(mraid.supports('storePicture')).to.be.true; 11 | expect(mraid.supports('inlineVideo')).to.be.true; 12 | }); 13 | it('should not report "support" for anything NOT spec by default', function(){ 14 | expect(mraid.supports('time-travel')).to.be.false; 15 | }); 16 | 17 | }); 18 | -------------------------------------------------------------------------------- /src/extension/chrome/manifest.json: -------------------------------------------------------------------------------- 1 | { 2 | "manifest_version": 2, 3 | "name": "AppNexus MRAID Viewer", 4 | "description": "View mobile ads on the desktop.", 5 | "version": "0.2", 6 | "icons": { "128": "icon128.png" }, 7 | "permissions": [ 8 | "storage", 9 | "webRequest", 10 | "webRequestBlocking", 11 | "" 12 | ], 13 | "page_action": { 14 | "default_icon": "icon128.png", 15 | "default_title": "AppNexus MRAID Viewer" 16 | }, 17 | "background": { 18 | "scripts": ["background.js"], 19 | "persistent": true 20 | }, 21 | "content_scripts": [{ 22 | "matches": [""], 23 | "allframes": true, 24 | "run_at": "document_start", 25 | "js": ["content.compiled.js"] 26 | }] 27 | } 28 | -------------------------------------------------------------------------------- /test/get-default-position.js: -------------------------------------------------------------------------------- 1 | var expect = require('./chai-expect'); 2 | 3 | describe('#getDefaultPosition()', function(){ 4 | it('should be the top left corner', function(){ 5 | var pos = window.mraid.getDefaultPosition(); 6 | 7 | expect(pos).to.be.ok; 8 | expect(pos.x).to.be.equal(0); 9 | expect(pos.y).to.be.equal(0); 10 | expect(pos.width).to.be.equal(320); 11 | expect(pos.height).to.be.equal(50); 12 | }); 13 | 14 | it('should not change when expanded', function(){ 15 | window.mraid.expand(); 16 | var pos = window.mraid.getDefaultPosition(); 17 | 18 | expect(pos).to.be.ok; 19 | expect(pos.x).to.be.equal(0); 20 | expect(pos.y).to.be.equal(0); 21 | expect(pos.width).to.be.equal(320); 22 | expect(pos.height).to.be.equal(50); 23 | }); 24 | }); 25 | -------------------------------------------------------------------------------- /app.js: -------------------------------------------------------------------------------- 1 | var express = require('express'), 2 | http = require('http'), 3 | sys = require('sys'), 4 | exec = require('child_process').exec, 5 | path = require('path') ; 6 | 7 | exec('./build.sh -d', function(err, stdout){ 8 | sys.puts(stdout); 9 | }); 10 | 11 | var app = express(); 12 | var port = process.env.PORT || 9000; 13 | 14 | app.use(function(req, res, next){ 15 | res.setHeader('Access-Control-Allow-Origin', '*'); 16 | return next(); 17 | }); 18 | 19 | app.use(function(req, res, next){ 20 | if (!/mraid.js$/.test(req.url)) return next(); 21 | 22 | res.sendfile('dist/mraid.js'); 23 | }); 24 | 25 | app.use(express.static(path.join(__dirname, '/public'))); 26 | 27 | http.createServer(app).listen(port, function(){ 28 | console.log('listening on port ' + port); 29 | }); 30 | -------------------------------------------------------------------------------- /test/expand.js: -------------------------------------------------------------------------------- 1 | var expect = require('./chai-expect'); 2 | var util = require('./util'); 3 | 4 | describe('#expand()', function(){ 5 | it('should expand to the valid expandProperties', function(){ 6 | mraid.setExpandProperties({ 7 | width:320, 8 | height:450 9 | }); 10 | 11 | mraid.expand(); 12 | 13 | util.expectAdSize(320, 450); 14 | }); 15 | it('should not expand to larger than the screen', function(){ 16 | mraid.setExpandProperties({ 17 | width:5000, 18 | height:9000 19 | }); 20 | 21 | mraid.expand(); 22 | 23 | util.expectAdSize(768, 1024); 24 | }); 25 | it('should expand to the full screen if no expand properties are set', function(){ 26 | mraid.expand(); 27 | var pos = mraid.getCurrentPosition(); 28 | 29 | util.expectAdSize(768, 1024); 30 | }); 31 | }); 32 | -------------------------------------------------------------------------------- /src/polyfill/options.js: -------------------------------------------------------------------------------- 1 | const url = require('url'); 2 | const IPAD_SIZE = { width: 768, height: 1024 }; 3 | 4 | function _getScreenSize(){ 5 | const PARAM_NAME = 'anx-mraid-screen'; 6 | let queryString = url.parse(window.top.location.toString(), true).query; 7 | 8 | if (!queryString || !queryString[PARAM_NAME]) return IPAD_SIZE; 9 | 10 | let strSize = queryString[PARAM_NAME]; 11 | let dimensions = strSize.split(/x/i); 12 | 13 | if (!dimensions || dimensions.length < 2) return IPAD_SIZE; 14 | 15 | return { width: dimensions[0], height: dimensions[1] }; 16 | } 17 | 18 | exports.getScreenSize = function(){ 19 | try { 20 | return _getScreenSize(); 21 | } 22 | catch(e) { 23 | console.error('anx-mraid: failed looking for screen size parameter in the url.'); 24 | return IPAD_SIZE; 25 | } 26 | }; 27 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "browser-mraid", 3 | "version": "0.0.1", 4 | "description": "MRAID v2 polyfill for desktop browsers.", 5 | "scripts": { 6 | "start": "./node_modules/nodemon/nodemon.js app.js" 7 | }, 8 | "repository": "", 9 | "author": "AppNexus", 10 | "license": "apache v2", 11 | "dependencies": { 12 | "express": "~3.4.0", 13 | "browserify": "~2.34.1", 14 | "sassify2": "0.0.2", 15 | "cssify2": "0.0.4", 16 | "jquery": "~2.1.0", 17 | "video.js": "~4.4.2", 18 | "browser-unpack": "~0.1.0", 19 | "intreq": "0.0.0", 20 | "browser-pack": "~2.0.1" 21 | }, 22 | "devDependencies": { 23 | "mocha": "~1.13.0", 24 | "chai": "~1.8.1", 25 | "sinon": "~1.7.3", 26 | "sinon-chai": "~2.4.0", 27 | "chai-jquery": "~1.2.1", 28 | "uglify-js": "~2.4.1", 29 | "nodemon": "~0.7.10", 30 | "es6ify": "~0.4.3", 31 | "remove-console-logs": "0.0.3" 32 | } 33 | } 34 | -------------------------------------------------------------------------------- /test/play-video.js: -------------------------------------------------------------------------------- 1 | var sinon = require('sinon'); 2 | var expect = require('./chai-expect'); 3 | var util = require('./util'); 4 | var videojs = require('video.js'); 5 | 6 | describe('#playVideo', function(){ 7 | afterEach(function(){ 8 | // getting weird audio from videojs without this 9 | videojs('anx-mraid-video-1').ready(function(){ 10 | this.volume(0); 11 | util.unloadAd(); 12 | }); 13 | }); 14 | 15 | it('should show the video when not expanded', function(){ 16 | var $ad = util.get$Ad(); 17 | 18 | mraid.playVideo('http://video-js.zencoder.com/oceans-clip.mp4'); 19 | 20 | var $video = $ad.find('video'); 21 | expect($video).to.be.visible; 22 | }); 23 | 24 | it('should show the video even when expanded', function(){ 25 | var $ad = util.get$Ad(); 26 | 27 | mraid.expand(); 28 | mraid.playVideo('http://video-js.zencoder.com/oceans-clip.mp4'); 29 | 30 | var $video = $ad.find('video'); 31 | expect($video).to.be.visible; 32 | }); 33 | }); 34 | -------------------------------------------------------------------------------- /test/get-current-position.js: -------------------------------------------------------------------------------- 1 | var expect = require('./chai-expect'); 2 | var util = require('./util'); 3 | 4 | describe('#getCurrentPosition()', function(){ 5 | it('should put the ad in the top left corner', function(){ 6 | var pos = window.mraid.getCurrentPosition(); 7 | 8 | expect(pos).to.be.ok; 9 | expect(pos.x).to.be.equal(0); 10 | expect(pos.y).to.be.equal(0); 11 | expect(pos.width).to.be.equal(320); 12 | expect(pos.height).to.be.equal(50); 13 | }); 14 | it('should change when expanded', function(){ 15 | window.mraid.expand(); 16 | var pos = window.mraid.getCurrentPosition(); 17 | 18 | expect(pos).to.be.ok; 19 | expect(pos.x).to.be.equal(0); 20 | expect(pos.y).to.be.equal(0); 21 | expect(pos.width).to.be.equal(768); 22 | expect(pos.height).to.be.equal(1024); 23 | }); 24 | it('should match the ad element dimensions', function(){ 25 | var pos = window.mraid.getCurrentPosition(); 26 | util.expectAdSize(pos.width, pos.height); 27 | }); 28 | }); 29 | -------------------------------------------------------------------------------- /test/use-custom-close.js: -------------------------------------------------------------------------------- 1 | var expect = require('./chai-expect'); 2 | var util = require('./util'); 3 | 4 | describe('#useCustomClose', function(){ 5 | it('should default to false', function(){ 6 | expect(mraid.getExpandProperties().useCustomClose).to.be.false; 7 | }); 8 | 9 | it('should update the expand properties', function(){ 10 | mraid.useCustomClose(true); 11 | 12 | var customClose = mraid.getExpandProperties().useCustomClose; 13 | expect(customClose).to.be.true; 14 | 15 | mraid.useCustomClose(false); 16 | 17 | customClose = mraid.getExpandProperties().useCustomClose; 18 | expect(customClose).to.be.false; 19 | }); 20 | 21 | it('should NOT show the button when customclose is true', function(){ 22 | mraid.useCustomClose(true); 23 | 24 | mraid.expand(); 25 | 26 | var $closeBtn = $('.anx-mraid-close'); 27 | expect($closeBtn).to.not.be.visible; 28 | }); 29 | 30 | it('should show the button when customclose is false', function(){ 31 | mraid.useCustomClose(false); 32 | 33 | mraid.expand(); 34 | 35 | var $closeBtn = $('.anx-mraid-close'); 36 | expect($closeBtn.length).to.be.equal(1); 37 | expect($closeBtn).to.be.visible; 38 | }); 39 | 40 | }); 41 | -------------------------------------------------------------------------------- /src/polyfill/style.scss: -------------------------------------------------------------------------------- 1 | @import "src/polyfill/mixins.scss"; 2 | 3 | body.anx-mraid-webview{ 4 | padding:0; 5 | margin:0; 6 | } 7 | 8 | .anx-mraid-webview{ 9 | overflow:hidden; 10 | position: relative; 11 | 12 | .anx-mraid-close{ 13 | 14 | span{ 15 | position: absolute; 16 | } 17 | padding: 4px; 18 | padding-top: 1px; 19 | display: none; 20 | background-color: red; 21 | color: white; 22 | position: absolute; 23 | top: 0; 24 | right: 0; 25 | z-index: 3000; 26 | -webkit-border-radius: 999px; 27 | -moz-border-radius: 999px; 28 | border-radius: 999px; 29 | width: 11px; 30 | height: 14px; 31 | background: black; 32 | border: 3px solid white; 33 | cursor: pointer; 34 | 35 | @include box-shadow(3px, 3px, 3px, black); 36 | } 37 | 38 | .anx-mraid-msg{ 39 | display: none; 40 | opacity: .7; 41 | position: absolute; 42 | top:0; 43 | bottom: 0; 44 | left: 0; 45 | right: 0; 46 | background-color: black; 47 | color:#c46e07; 48 | text-align: center; 49 | z-index:2000; 50 | outline: 1px solid #eee; 51 | font-weight: bold; 52 | text-shadow: 0px 2px 3px black; 53 | } 54 | 55 | .anx-mraid-url{ 56 | position: absolute; 57 | top:0; 58 | bottom: 0; 59 | left: 0; 60 | right: 0; 61 | z-index: 200; 62 | border: 0; 63 | width: 100%; 64 | height:100%; 65 | } 66 | } 67 | 68 | // mocha 69 | #anx-mraid-ad-cntr{ 70 | position: fixed; 71 | top:100px; 72 | right:200px; 73 | } 74 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | #mraid-extension 2 | Enable viewing of [MRAID](http://www.iab.net/mraid) creatives in a desktop browser. 3 | 4 | There are two components, a polyfill that creates the window.mraid object and a browser extension that intercepts any request for mraid.js files and redirects the request to the polyfill. 5 | 6 | #Installation 7 | The extension is available for download from the chrome store [here](https://chrome.google.com/webstore/detail/appnexus-mraid-viewer/kljmljefjfkglealiaheaapimodndfno). 8 | 9 | #Running the extension locally 10 | ````````` 11 | npm install 12 | npm start 13 | ````````` 14 | After starting the app, you will be serving mraid.js from localhost:9000/mraid.js. You can also load the local version of the chrome extension by opening chrome://extensions, click the 'Developer Mode' checkbox, click 'Load upacked extension' button and select the dist/chrome folder. At this point, your browser will redirect all requests for any mraid.js file to localhost:9000/mraid.js. 15 | 16 | ##Editing the local files 17 | After running npm start, any changes under the src/polyfill/ directory will be picked up automatically, but changes under src/extension/ will require reloading the extension from the chrome://extensions page. 18 | 19 | Building a release version 20 | -------------------------- 21 | ````````` 22 | npm install 23 | ./build.sh 24 | ````````` 25 | Release version will be located in the dist/ directory. The dist/chrome\_release.zip can be uploaded the chrome store. 26 | -------------------------------------------------------------------------------- /test/main.js: -------------------------------------------------------------------------------- 1 | var util = require('./util'), 2 | expect = require('./chai-expect'); 3 | 4 | describe('mraid for browsers', function(){ 5 | beforeEach(function(done){ 6 | util.loadAd({ 7 | width: 320, 8 | height: 50 9 | }, done); 10 | }); 11 | 12 | it('should implement every method in the spec', function(){ 13 | 14 | var methodNames = [ 15 | 'addEventListener', 16 | 'createCalendarEvent', 17 | 'close', 18 | 'expand', 19 | 'getCurrentPosition', 20 | 'getDefaultPosition', 21 | 'getExpandProperties', 22 | 'getMaxSize', 23 | 'getPlacementType', 24 | 'getResizeProperties', 25 | 'getScreenSize', 26 | 'getState', 27 | 'getVersion', 28 | 'isViewable', 29 | 'open', 30 | 'playVideo', 31 | 'removeEventListener', 32 | 'resize', 33 | 'setExpandProperties', 34 | 'setOrientationProperties', 35 | 'setResizeProperties', 36 | 'storePicture', 37 | 'supports', 38 | 'useCustomClose' 39 | ]; 40 | 41 | methodNames.forEach(function(methodName){ 42 | expect(mraid[methodName], methodName).to.be.a('function'); 43 | }); 44 | }); 45 | 46 | // whatever whatever. deal with it. 47 | require('./get-state'); 48 | require('./close'); 49 | require('./events'); 50 | require('./expand'); 51 | require('./resize'); 52 | require('./supports'); 53 | require('./create-calendar-event'); 54 | require('./play-video'); 55 | require('./use-custom-close'); 56 | require('./store-picture'); 57 | require('./open'); 58 | require('./get-expand-properties'); 59 | require('./get-resize-properties'); 60 | require('./get-default-position'); 61 | require('./get-current-position'); 62 | require('./get-max-size'); 63 | require('./get-screen-size'); 64 | }); 65 | -------------------------------------------------------------------------------- /src/polyfill/state-manager.js: -------------------------------------------------------------------------------- 1 | var util = require('util'); 2 | var EventEmitter = require('events').EventEmitter; 3 | var _STATES = { 4 | loading:'loading', 5 | default: 'default', 6 | expanded: 'expanded', 7 | resized: 'resized', 8 | hidden: 'hidden' 9 | }; 10 | 11 | var StateManager = function(){ 12 | EventEmitter.call(this); 13 | this.__state = 'loading'; 14 | }; 15 | 16 | util.inherits(StateManager, EventEmitter); 17 | 18 | StateManager.prototype.get = function() { return this.__state; }; 19 | StateManager.prototype.set = function(state, isTest){ 20 | var s = _STATES[(state || '').toLowerCase()]; 21 | 22 | if (!s){ 23 | throw {msg: 'bad state: ' + state}; 24 | } 25 | 26 | // only resize can fire again and again 27 | if (this.__state === s && s !== 'resized') return; 28 | 29 | if (!this.isValid(s)){ 30 | _checkForError(this, this.__state, s); 31 | return; 32 | } 33 | 34 | this.__state = s; 35 | this.emit('stateChange', this.__state); 36 | 37 | return s; 38 | }; 39 | StateManager.prototype.isValid = function(newState){ 40 | var oldState = this.__state; 41 | if (!oldState || !newState) return; 42 | 43 | switch (newState){ 44 | case 'resized': 45 | switch (oldState){ 46 | case 'resized': 47 | return true; 48 | case 'hidden': 49 | case 'loading': 50 | return false; 51 | case 'expanded': 52 | return false; 53 | } 54 | break; 55 | case 'expanded': 56 | switch (oldState){ 57 | case 'loading': 58 | case 'hidden': 59 | return false; 60 | } 61 | break; 62 | case 'loading': 63 | return false; 64 | } 65 | 66 | return true; 67 | }; 68 | function _checkForError(obj, oldState, newState){ 69 | if (oldState === 'expanded' && newState === 'resized'){ 70 | obj.emit('error', 'cannot transition from expanded to resized.'); 71 | } 72 | } 73 | module.exports = StateManager; 74 | -------------------------------------------------------------------------------- /test/util.js: -------------------------------------------------------------------------------- 1 | var $ = require('jquery'); 2 | var expect = require('chai').expect; 3 | 4 | // chai-jquery doesn't work unless i do this 5 | window.$ = window.jQuery = $; 6 | 7 | function _loadAd(options, done){ 8 | console.log('loading ad'); 9 | options.state = options.state || 'default'; 10 | var w = options.width; 11 | var h = options.height; 12 | 13 | var $ad = $('
') 14 | .css('width', w + 'px') 15 | .css('height', h + 'px'); 16 | 17 | function _setImgSrc(w, h){ 18 | $img.attr('src', 'http://placekitten.com/' + w + '/' + h); 19 | } 20 | 21 | var $img = $('') 22 | .attr('onclick', 'mraid.expand();'); 23 | 24 | _setImgSrc(w, h); 25 | $ad.append($img); 26 | 27 | delete window.mraid; 28 | 29 | var script = document.createElement( 'script' ); 30 | script.src = 'mraid.js'; 31 | script.onload = function(){ 32 | mraid.addListener('stateChange', function(s){ 33 | var size = mraid.getCurrentPosition(); 34 | _setImgSrc(size.width, size.height); 35 | }); 36 | 37 | if (options.state !== 'loading'){ 38 | mraid.triggerReady(); 39 | } 40 | 41 | if (options.state === 'hidden'){ 42 | mraid.close(); 43 | } 44 | if (options.state === 'expanded'){ 45 | mraid.expand(); 46 | } 47 | if (options.state === 'resized'){ 48 | mraid.setResizeProperties({}); 49 | mraid.resize(); 50 | } 51 | 52 | expect(mraid.getState()).to.be.equal(options.state); 53 | 54 | done(); 55 | }; 56 | 57 | $('#anx-mraid-ad-cntr').html($ad); 58 | $ad[0].appendChild( script ); 59 | } 60 | 61 | 62 | exports.loadAd = _loadAd; 63 | exports.get$Ad = function(){ 64 | return $('#anx-mraid-ad-cntr').first(); 65 | }; 66 | exports.unloadAd = function(){ 67 | $('#anx-mraid-ad-cntr').empty(); 68 | }; 69 | 70 | exports.expectAdSize = function(w, h){ 71 | var $ad = this.get$Ad(); 72 | 73 | expect($ad).to.be.ok; 74 | expect($ad.width()).to.be.equal(w); 75 | expect($ad.height()).to.be.equal(h); 76 | }; 77 | -------------------------------------------------------------------------------- /src/polyfill/mixins.scss: -------------------------------------------------------------------------------- 1 | @mixin box-shadow($top, $left, $blur, $color, $inset: false) { 2 | @if $inset { 3 | -webkit-box-shadow:inset $top $left $blur $color; 4 | -moz-box-shadow:inset $top $left $blur $color; 5 | box-shadow:inset $top $left $blur $color; 6 | } @else { 7 | -webkit-box-shadow: $top $left $blur $color; 8 | -moz-box-shadow: $top $left $blur $color; 9 | box-shadow: $top $left $blur $color; 10 | } 11 | } 12 | 13 | @mixin text-field { 14 | display: inline-block; 15 | outline: none; 16 | text-decoration: none; 17 | font: 14px/100% Arial, Helvetica, sans-serif; 18 | padding: .5em; 19 | text-shadow: 0 1px 1px rgba(0,0,0,.3); 20 | @include rounded(); 21 | @include box-shadow(0, 1px, 2px, rgba(0, 0, 0, 0.2)); 22 | } 23 | 24 | @mixin button($color: $red, $text_color: $white) { 25 | display: inline-block; 26 | outline: none; 27 | cursor: pointer; 28 | text-align: center; 29 | text-decoration: none; 30 | font: 14px/100% Arial, Helvetica, sans-serif; 31 | padding: .5em 2em .55em; 32 | text-shadow: 0 1px 1px rgba(0,0,0,.3); 33 | @include rounded(); 34 | @include box-shadow(0, 1px, 2px, rgba(0, 0, 0, 0.2)); 35 | 36 | color: $text_color !important; 37 | font-weight: bold; 38 | border: solid 1px darken($color, 18%); 39 | background: $color; 40 | @include gradient(saturate($color, 15%), darken($color, 15%)); 41 | 42 | &:hover { 43 | text-decoration: none; 44 | background: saturate($color, 10%); 45 | @include gradient(saturate($color, 5%), darken($color, 5%)); 46 | } 47 | 48 | &:active { 49 | position: relative; 50 | top: 1px; 51 | color: saturate($color, 15%); 52 | @include gradient(saturate($color, 15%), lighten($color, 15%)); 53 | } 54 | } 55 | 56 | @mixin rounded($radius: 0.5em) { 57 | -webkit-border-radius: $radius; 58 | -moz-border-radius: $radius; 59 | border-radius: $radius; 60 | } 61 | 62 | @mixin gradient($from, $to) { 63 | background: -webkit-gradient(linear, left top, left bottom, from($from), to($to)); 64 | background: -moz-linear-gradient(top, $from, $to); 65 | } 66 | -------------------------------------------------------------------------------- /test/resize.js: -------------------------------------------------------------------------------- 1 | var sinon = require('sinon'); 2 | var expect = require('./chai-expect'); 3 | var util = require('./util'); 4 | 5 | describe('#resize()', function(){ 6 | it('should error if no resize properties', function(){ 7 | var errorCallback = sinon.spy(); 8 | mraid.addListener('error', errorCallback); 9 | 10 | mraid.resize(); 11 | 12 | expect(errorCallback).to.have.been.calledOnce; 13 | }); 14 | it('should not change state if no resize properties', function(){ 15 | var errorCallback = sinon.spy(); 16 | mraid.addListener('error', errorCallback); 17 | 18 | mraid.resize(); 19 | 20 | expect(mraid.getState()).to.be.equal('default'); 21 | }); 22 | it('should change the size', function(){ 23 | mraid.setResizeProperties({ 24 | width:150, 25 | height:200 26 | }); 27 | 28 | mraid.resize(); 29 | 30 | util.expectAdSize(150, 200); 31 | }); 32 | it('should show the close button', function(){ 33 | mraid.setResizeProperties({ 34 | width:150, 35 | height:175 36 | }); 37 | 38 | mraid.resize(); 39 | 40 | 41 | var $closeBtn = $('.anx-mraid-close'); 42 | expect($closeBtn).to.be.visible; 43 | }); 44 | 45 | it('should fire sizeChange event everytime resize is called and the size really is different', function(){ 46 | mraid.setResizeProperties({ 47 | width:154, 48 | height:172 49 | }); 50 | 51 | var stateCallback = sinon.spy(); 52 | var errorCallback = sinon.spy(); 53 | 54 | mraid.addEventListener('error', errorCallback); 55 | mraid.addEventListener('sizeChange', stateCallback); 56 | mraid.resize(); 57 | mraid.resize(); 58 | mraid.resize(); 59 | 60 | expect(stateCallback).to.have.been.calledOnce; 61 | expect(errorCallback).to.have.not.been.called; 62 | }); 63 | 64 | describe('resizing smaller', function(){ 65 | beforeEach(function(done){ 66 | util.loadAd({ 67 | width: 320, 68 | height: 480 69 | }, done); 70 | }); 71 | 72 | it('should get smaller', function(){ 73 | mraid.setResizeProperties({ 74 | width:320, 75 | height:50 76 | }); 77 | 78 | mraid.resize(); 79 | util.expectAdSize(320, 50); 80 | }); 81 | }); 82 | 83 | }); 84 | -------------------------------------------------------------------------------- /src/extension/resizer.js: -------------------------------------------------------------------------------- 1 | /* global confirm:true */ 2 | const util = require('util'), 3 | getFrameElement = require('../frame-to-element'), 4 | {EventEmitter} = require('events'); 5 | 6 | const anxOrigins = [ 7 | 'adnxs.net', 8 | 'adnxs.com', 9 | 'appnexus.com', 10 | 'appnexus.net', 11 | 'devnxs.net' 12 | ], 13 | anOriginRegex = new RegExp(anxOrigins.join('|').replace(/\./, '\\.') + '$', 'i'); 14 | 15 | function Resizer(){ 16 | EventEmitter.call(this); 17 | 18 | var allowedOrigins, 19 | self = this; 20 | 21 | 22 | function _getOrigins(cb){ 23 | if (allowedOrigins) return setTimeout(cb.bind(null, allowedOrigins), 0); 24 | 25 | self.emit('load-origins', { 26 | cb: function(origins){ 27 | allowedOrigins = origins || []; 28 | 29 | cb(allowedOrigins || []); 30 | } 31 | }); 32 | } 33 | 34 | function _allowOrigin(origin){ 35 | _getOrigins(function(origins){ 36 | origins.push(origin); 37 | allowedOrigins = origins; 38 | 39 | self.emit('save-origins', { origins: origins || [] }); 40 | }); 41 | } 42 | 43 | function _checkOrigin(message, cb){ 44 | if (anOriginRegex.test(message.origin)) return setTimeout(cb.bind(null, true), 0); 45 | 46 | _getOrigins(function(origins){ 47 | for (var x=0; x'; 80 | } else { 81 | view.content = ' '; 82 | } 83 | } 84 | -------------------------------------------------------------------------------- /public/creatives/test_ad.html: -------------------------------------------------------------------------------- 1 | 16 | 17 | 18 | 19 | 50 |
51 | 52 |
53 |
54 | 55 | -------------------------------------------------------------------------------- /test/get-state.js: -------------------------------------------------------------------------------- 1 | var util = require('./util'), 2 | sinon = require('sinon'), 3 | chai = require('chai'); 4 | 5 | chai.use(require('sinon-chai')); 6 | chai.use(require('chai-jquery')); 7 | 8 | var expect = chai.expect; 9 | 10 | describe('#getState', function(){ 11 | 12 | describe('when in loading state', function(){ 13 | 14 | beforeEach(function(done){ 15 | util.loadAd({ 16 | state: 'loading', 17 | width: 320, 18 | height: 50 19 | }, done); 20 | }); 21 | it('should not change state when expand() is called', function(){ 22 | mraid.expand(); 23 | expect(mraid.getState()).to.be.equal('loading'); 24 | }); 25 | 26 | it('should not change state when expand() is called', function(){ 27 | mraid.close(); 28 | expect(mraid.getState()).to.be.equal('loading'); 29 | }); 30 | 31 | it('should not change state when resize() is called', function(){ 32 | mraid.resize(); 33 | expect(mraid.getState()).to.be.equal('loading'); 34 | }); 35 | 36 | it('should not do anything when resize(), expand() or close() are called', function(){ 37 | mraid.expand(); 38 | mraid.close(); 39 | mraid.resize(); 40 | mraid.expand(); 41 | mraid.expand(); 42 | 43 | expect(mraid.getState()).to.be.equal('loading'); 44 | }); 45 | }); 46 | 47 | describe('when in hidden state', function(){ 48 | beforeEach(function(done){ 49 | util.loadAd({ 50 | state: 'hidden', 51 | width: 320, 52 | height: 50 53 | }, done); 54 | 55 | }); 56 | 57 | it('should not do anything when resize(), expand() or close() are called', function(){ 58 | mraid.expand(); 59 | mraid.close(); 60 | mraid.resize(); 61 | mraid.expand(); 62 | mraid.expand(); 63 | 64 | expect(mraid.getState()).to.be.equal('hidden'); 65 | }); 66 | 67 | it('should not be visible', function(){ 68 | expect(util.get$Ad()).to.not.be.visible; 69 | }); 70 | 71 | }); 72 | 73 | describe('when in default state', function(){ 74 | beforeEach(function(done){ 75 | util.loadAd({ 76 | width: 320, 77 | height: 50 78 | }, done); 79 | }); 80 | 81 | it('should change state to expand when expand() is called', function(){ 82 | mraid.expand(); 83 | expect(mraid.getState()).to.be.equal('expanded'); 84 | }); 85 | 86 | it('should change state to hidden when close() is called', function(){ 87 | mraid.close(); 88 | expect(mraid.getState()).to.be.equal('hidden'); 89 | }); 90 | 91 | it('should change state to "resized" when resize() is called', function(){ 92 | mraid.setResizeProperties({}); 93 | mraid.resize(); 94 | expect(mraid.getState()).to.be.equal('resized'); 95 | }); 96 | 97 | 98 | }); 99 | 100 | describe('when in expanded state', function(){ 101 | 102 | beforeEach(function(done){ 103 | util.loadAd({ 104 | state: 'expanded', 105 | width: 320, 106 | height: 50 107 | }, done); 108 | }); 109 | 110 | it('should not change state when resize() is called', function(){ 111 | mraid.resize(); 112 | mraid.setResizeProperties({}); 113 | expect(mraid.getState()).to.be.equal('expanded'); 114 | }); 115 | it('should change state to "default" when close() is called', function(){ 116 | mraid.close(); 117 | expect(mraid.getState()).to.be.equal('default'); 118 | }); 119 | it('should do nothing at all when expand() is called', function(){ 120 | mraid.expand(); 121 | 122 | expect(mraid.getState()).to.be.equal('expanded'); 123 | }); 124 | }); 125 | describe('when in resized state', function(){ 126 | 127 | beforeEach(function(done){ 128 | util.loadAd({ 129 | state: 'resized', 130 | width: 320, 131 | height: 50 132 | }, function(){ 133 | mraid.setResizeProperties({}); 134 | done(); 135 | }); 136 | }); 137 | 138 | it('should change state when expand() is called', function(){ 139 | mraid.expand(); 140 | expect(mraid.getState()).to.be.equal('expanded'); 141 | }); 142 | it('should change state to "default" when close() is called', function(){ 143 | mraid.close(); 144 | expect(mraid.getState()).to.be.equal('default'); 145 | }); 146 | }); 147 | }); 148 | -------------------------------------------------------------------------------- /test/events.js: -------------------------------------------------------------------------------- 1 | var util = require('./util'), 2 | sinon = require('sinon'), 3 | expect = require('./chai-expect'); 4 | 5 | 6 | describe('events', function(){ 7 | 8 | describe('when in loading state', function(){ 9 | 10 | beforeEach(function(done){ 11 | util.loadAd({ 12 | state: 'loading', 13 | width: 320, 14 | height: 50 15 | }, done); 16 | }); 17 | it('should not do anything when resize(), expand() or close() are called', function(){ 18 | var stateCallback = sinon.spy(); 19 | var errorCallback = sinon.spy(); 20 | 21 | mraid.addEventListener('error', errorCallback); 22 | mraid.addEventListener('stateChange', stateCallback); 23 | mraid.expand(); 24 | mraid.close(); 25 | mraid.setResizeProperties({}); 26 | mraid.resize(); 27 | mraid.expand(); 28 | mraid.expand(); 29 | 30 | expect(errorCallback).to.have.not.been.called; 31 | expect(errorCallback).to.have.not.been.called; 32 | }); 33 | it('should fire stateChange before ready', function(){ 34 | var t; 35 | mraid.addEventListener('ready', function(){t='1';}); 36 | mraid.addEventListener('stateChange', function(){t='2';}); 37 | mraid.triggerReady(); 38 | 39 | expect(t).to.be.equal('1'); 40 | }); 41 | }); 42 | 43 | describe('when in hidden state', function(){ 44 | beforeEach(function(done){ 45 | util.loadAd({ 46 | state: 'hidden', 47 | width: 320, 48 | height: 50 49 | }, done); 50 | 51 | }); 52 | 53 | it('should not do anything when resize(), expand() or close() are called', function(){ 54 | var stateCallback = sinon.spy(); 55 | var errorCallback = sinon.spy(); 56 | 57 | mraid.addEventListener('error', errorCallback); 58 | mraid.addEventListener('stateChange', stateCallback); 59 | mraid.expand(); 60 | mraid.close(); 61 | mraid.setResizeProperties({}); 62 | mraid.resize(); 63 | mraid.expand(); 64 | mraid.expand(); 65 | 66 | expect(errorCallback).to.have.not.been.called; 67 | expect(errorCallback).to.have.not.been.called; 68 | }); 69 | }); 70 | 71 | describe('when in default state', function(){ 72 | beforeEach(function(done){ 73 | util.loadAd({ 74 | width: 320, 75 | height: 50 76 | }, done); 77 | }); 78 | 79 | it('should publish stateChange event when expand() is called', function(){ 80 | var stateCallback = sinon.spy(); 81 | var errorCallback = sinon.spy(); 82 | 83 | mraid.addEventListener('error', errorCallback); 84 | mraid.addEventListener('stateChange', stateCallback); 85 | mraid.expand(); 86 | 87 | expect(stateCallback).to.have.been.calledOnce; 88 | expect(errorCallback).to.have.not.been.called; 89 | }); 90 | 91 | it('should publish stateChange event only once when expand() is called multiple times', function(){ 92 | var stateCallback = sinon.spy(); 93 | var errorCallback = sinon.spy(); 94 | 95 | mraid.addEventListener('error', errorCallback); 96 | mraid.addEventListener('stateChange', stateCallback); 97 | mraid.expand(); 98 | mraid.expand(); 99 | mraid.expand(); 100 | 101 | expect(stateCallback).to.have.been.calledOnce; 102 | expect(errorCallback).to.have.not.been.called; 103 | }); 104 | }); 105 | 106 | describe('when in expanded state', function(){ 107 | 108 | beforeEach(function(done){ 109 | util.loadAd({ 110 | state: 'expanded', 111 | width: 320, 112 | height: 50 113 | }, done); 114 | }); 115 | 116 | it('should error and nothing else when resize is called', function(){ 117 | var stateCallback = sinon.spy(); 118 | var errorCallback = sinon.spy(); 119 | 120 | mraid.addEventListener('error', errorCallback); 121 | mraid.addEventListener('stateChange', stateCallback); 122 | mraid.resize(); 123 | 124 | expect(stateCallback).to.have.not.been.called; 125 | expect(errorCallback).to.have.been.calledOnce; 126 | }); 127 | it('should do nothing at all when expand() is called', function(){ 128 | var stateCallback = sinon.spy(); 129 | var errorCallback = sinon.spy(); 130 | 131 | mraid.addEventListener('error', errorCallback); 132 | mraid.addEventListener('stateChange', stateCallback); 133 | mraid.expand(); 134 | 135 | expect(stateCallback).to.have.not.been.called; 136 | expect(errorCallback).to.have.not.been.called; 137 | }); 138 | }); 139 | describe('when in resized state', function(){ 140 | 141 | beforeEach(function(done){ 142 | util.loadAd({ 143 | state: 'resized', 144 | width: 320, 145 | height: 50 146 | }, done); 147 | }); 148 | 149 | it('should fire resize event everytime resize is called', function(){ 150 | var stateCallback = sinon.spy(); 151 | var errorCallback = sinon.spy(); 152 | 153 | mraid.addEventListener('error', errorCallback); 154 | mraid.addEventListener('stateChange', stateCallback); 155 | mraid.resize(); 156 | mraid.resize(); 157 | mraid.resize(); 158 | 159 | expect(stateCallback).to.have.been.calledThrice; 160 | expect(errorCallback).to.have.not.been.called; 161 | }); 162 | }); 163 | }); 164 | -------------------------------------------------------------------------------- /public/mocha/mocha.css: -------------------------------------------------------------------------------- 1 | @charset "utf-8"; 2 | 3 | body { 4 | margin:0; 5 | } 6 | 7 | #mocha { 8 | font: 20px/1.5 "Helvetica Neue", Helvetica, Arial, sans-serif; 9 | margin: 60px 50px; 10 | } 11 | 12 | #mocha ul, #mocha li { 13 | margin: 0; 14 | padding: 0; 15 | } 16 | 17 | #mocha ul { 18 | list-style: none; 19 | } 20 | 21 | #mocha h1, #mocha h2 { 22 | margin: 0; 23 | } 24 | 25 | #mocha h1 { 26 | margin-top: 15px; 27 | font-size: 1em; 28 | font-weight: 200; 29 | } 30 | 31 | #mocha h1 a { 32 | text-decoration: none; 33 | color: inherit; 34 | } 35 | 36 | #mocha h1 a:hover { 37 | text-decoration: underline; 38 | } 39 | 40 | #mocha .suite .suite h1 { 41 | margin-top: 0; 42 | font-size: .8em; 43 | } 44 | 45 | #mocha .hidden { 46 | display: none; 47 | } 48 | 49 | #mocha h2 { 50 | font-size: 12px; 51 | font-weight: normal; 52 | cursor: pointer; 53 | } 54 | 55 | #mocha .suite { 56 | margin-left: 15px; 57 | } 58 | 59 | #mocha .test { 60 | margin-left: 15px; 61 | overflow: hidden; 62 | } 63 | 64 | #mocha .test.pending:hover h2::after { 65 | content: '(pending)'; 66 | font-family: arial, sans-serif; 67 | } 68 | 69 | #mocha .test.pass.medium .duration { 70 | background: #C09853; 71 | } 72 | 73 | #mocha .test.pass.slow .duration { 74 | background: #B94A48; 75 | } 76 | 77 | #mocha .test.pass::before { 78 | content: '✓'; 79 | font-size: 12px; 80 | display: block; 81 | float: left; 82 | margin-right: 5px; 83 | color: #00d6b2; 84 | } 85 | 86 | #mocha .test.pass .duration { 87 | font-size: 9px; 88 | margin-left: 5px; 89 | padding: 2px 5px; 90 | color: white; 91 | -webkit-box-shadow: inset 0 1px 1px rgba(0,0,0,.2); 92 | -moz-box-shadow: inset 0 1px 1px rgba(0,0,0,.2); 93 | box-shadow: inset 0 1px 1px rgba(0,0,0,.2); 94 | -webkit-border-radius: 5px; 95 | -moz-border-radius: 5px; 96 | -ms-border-radius: 5px; 97 | -o-border-radius: 5px; 98 | border-radius: 5px; 99 | } 100 | 101 | #mocha .test.pass.fast .duration { 102 | display: none; 103 | } 104 | 105 | #mocha .test.pending { 106 | color: #0b97c4; 107 | } 108 | 109 | #mocha .test.pending::before { 110 | content: '◦'; 111 | color: #0b97c4; 112 | } 113 | 114 | #mocha .test.fail { 115 | color: #c00; 116 | } 117 | 118 | #mocha .test.fail pre { 119 | color: black; 120 | } 121 | 122 | #mocha .test.fail::before { 123 | content: '✖'; 124 | font-size: 12px; 125 | display: block; 126 | float: left; 127 | margin-right: 5px; 128 | color: #c00; 129 | } 130 | 131 | #mocha .test pre.error { 132 | color: #c00; 133 | max-height: 300px; 134 | overflow: auto; 135 | } 136 | 137 | #mocha .test pre { 138 | display: block; 139 | float: left; 140 | clear: left; 141 | font: 12px/1.5 monaco, monospace; 142 | margin: 5px; 143 | padding: 15px; 144 | border: 1px solid #eee; 145 | border-bottom-color: #ddd; 146 | -webkit-border-radius: 3px; 147 | -webkit-box-shadow: 0 1px 3px #eee; 148 | -moz-border-radius: 3px; 149 | -moz-box-shadow: 0 1px 3px #eee; 150 | } 151 | 152 | #mocha .test h2 { 153 | position: relative; 154 | } 155 | 156 | #mocha .test a.replay { 157 | position: absolute; 158 | top: 3px; 159 | right: 0; 160 | text-decoration: none; 161 | vertical-align: middle; 162 | display: block; 163 | width: 15px; 164 | height: 15px; 165 | line-height: 15px; 166 | text-align: center; 167 | background: #eee; 168 | font-size: 15px; 169 | -moz-border-radius: 15px; 170 | border-radius: 15px; 171 | -webkit-transition: opacity 200ms; 172 | -moz-transition: opacity 200ms; 173 | transition: opacity 200ms; 174 | opacity: 0.3; 175 | color: #888; 176 | } 177 | 178 | #mocha .test:hover a.replay { 179 | opacity: 1; 180 | } 181 | 182 | #mocha-report.pass .test.fail { 183 | display: none; 184 | } 185 | 186 | #mocha-report.fail .test.pass { 187 | display: none; 188 | } 189 | 190 | #mocha-error { 191 | color: #c00; 192 | font-size: 1.5em; 193 | font-weight: 100; 194 | letter-spacing: 1px; 195 | } 196 | 197 | #mocha-stats { 198 | position: fixed; 199 | top: 15px; 200 | right: 10px; 201 | font-size: 12px; 202 | margin: 0; 203 | color: #888; 204 | } 205 | 206 | #mocha-stats .progress { 207 | float: right; 208 | padding-top: 0; 209 | } 210 | 211 | #mocha-stats em { 212 | color: black; 213 | } 214 | 215 | #mocha-stats a { 216 | text-decoration: none; 217 | color: inherit; 218 | } 219 | 220 | #mocha-stats a:hover { 221 | border-bottom: 1px solid #eee; 222 | } 223 | 224 | #mocha-stats li { 225 | display: inline-block; 226 | margin: 0 5px; 227 | list-style: none; 228 | padding-top: 11px; 229 | } 230 | 231 | #mocha-stats canvas { 232 | width: 40px; 233 | height: 40px; 234 | } 235 | 236 | #mocha code .comment { color: #ddd } 237 | #mocha code .init { color: #2F6FAD } 238 | #mocha code .string { color: #5890AD } 239 | #mocha code .keyword { color: #8A6343 } 240 | #mocha code .number { color: #2F6FAD } 241 | 242 | @media screen and (max-device-width: 480px) { 243 | #mocha { 244 | margin: 60px 0px; 245 | } 246 | 247 | #mocha #stats { 248 | position: absolute; 249 | } 250 | } 251 | -------------------------------------------------------------------------------- /src/polyfill/mraid.js: -------------------------------------------------------------------------------- 1 | var util = require('util'); 2 | var EventEmitter = require('events').EventEmitter; 3 | var StateManager = require('./state-manager'); 4 | var WebView = require('./web-view'); 5 | 6 | var defaultSupports = { 7 | 'sms': true, 8 | 'tel': true, 9 | 'calendar': true, 10 | 'storepicture': true, 11 | 'inlinevideo': true 12 | }; 13 | 14 | function Mraid(options){ 15 | EventEmitter.call(this); 16 | 17 | var self = this, 18 | webView = new WebView(options.screen), 19 | expandProperties = Object.create(webView.getScreenSize()), 20 | resizeProperties, 21 | placementType = options.placementType || 'inline', 22 | stateManager = new StateManager(); 23 | 24 | expandProperties.useCustomClose = false; 25 | 26 | /** 27 | * Cache the default window.open function because some MRAID modules overwrite 28 | * window.open. This works because mraid.js is expected to be called first. 29 | */ 30 | var defaultWindowOpen = window.open; 31 | 32 | this.open = function(url){ 33 | 34 | if (/^(tel|sms):/.test(url)){ 35 | webView.showMessage(url); 36 | return; 37 | } 38 | 39 | defaultWindowOpen(url, '_blank'); 40 | }; 41 | 42 | this.close = function(){ 43 | switch (stateManager.get()){ 44 | case 'default': 45 | webView.hide(); 46 | webView.resetSize(); 47 | stateManager.set('hidden'); 48 | break; 49 | 50 | case 'resized': 51 | case 'expanded': 52 | webView.hideClose(); 53 | webView.resetSize(); 54 | stateManager.set('default'); 55 | break; 56 | } 57 | }; 58 | 59 | this.resize = function(){ 60 | var rp = this.getResizeProperties(); 61 | if (!rp){ 62 | this.emit('error'); 63 | return; 64 | } 65 | 66 | switch (stateManager.get()){ 67 | case 'expanded': 68 | case 'default': 69 | case 'resized': 70 | webView.showClose(); 71 | 72 | var size = webView.getSize(); 73 | 74 | if (size.width != rp.width || size.height != rp.height){ 75 | webView.setSize(rp.width || 100, rp.height || 100); 76 | this.emit('sizeChange', +rp.width, +rp.height); 77 | } 78 | 79 | stateManager.set('resized'); 80 | break; 81 | } 82 | }; 83 | 84 | this.expand = function(url){ 85 | if (!stateManager.isValid('expanded'))return; 86 | 87 | var expandProps = this.getExpandProperties(); 88 | 89 | webView.setSize(expandProps.width, expandProps.height); 90 | 91 | if (expandProps.useCustomClose){ 92 | webView.hideClose(); 93 | } else { 94 | webView.showClose(); 95 | } 96 | 97 | if (url){ 98 | webView.showUrl(url); 99 | } 100 | 101 | stateManager.set('expanded'); 102 | }; 103 | 104 | this.getPlacementType = function(){ return placementType; }; 105 | this.getExpandProperties = function(){ return expandProperties; }; 106 | this.getResizeProperties = function(){ return resizeProperties; }; 107 | this.setOrientationProperties = function(){}; 108 | 109 | this.setExpandProperties = function(p){ 110 | expandProperties = p; 111 | console.log('expand properties set: ' + expandProperties.width + 'x' + expandProperties.height); 112 | }; 113 | 114 | this.setResizeProperties = function(p){ 115 | resizeProperties = p; 116 | console.log('resize properties set: ' + resizeProperties.width + 'x' + resizeProperties.height); 117 | }; 118 | 119 | this.playVideo = function(url){ webView.showVideo(url); }; 120 | 121 | this.storePicture = function(a){ 122 | console.log('mraid.storePicture("'+a+'") '); 123 | webView.showMessage('mraid.storePicture(...)'); 124 | }; 125 | 126 | this.createCalendarEvent = function(a){ 127 | console.log('mraid.createCalendarEvent(...) called with following argument: '); 128 | console.log(a); 129 | webView.showMessage('mraid.createCalendarEvent(...)'); 130 | }; 131 | 132 | this.getCurrentPosition = function(){ return webView.getCurrentPosition(); }; 133 | this.getDefaultPosition = function(){ return webView.getDefaultPosition(); }; 134 | this.getMaxSize = function(){ return webView.getScreenSize();}; 135 | this.getScreenSize = function(){return webView.getScreenSize();}; 136 | this.supports = function(feature){ 137 | return typeof feature === 'string' && 138 | feature.toLowerCase() in defaultSupports; 139 | }; 140 | 141 | this.getVersion = function(){ return 'appnexus'; }; 142 | this.getState = function(){ return stateManager.get(); }; 143 | this.isViewable = function(){ return true; }; 144 | 145 | this.addEventListener = function(event_name, method){ this.on(event_name, method); }; 146 | 147 | this.removeEventListener=function(eventName, method){ 148 | if (method === undefined){ 149 | this.removeAllListeners(eventName); 150 | } else { 151 | this.removeListener(eventName, method); 152 | } 153 | }; 154 | 155 | this.useCustomClose = function(b){ 156 | var ep = this.getExpandProperties(); 157 | ep.useCustomClose = b; 158 | this.setExpandProperties(ep); 159 | }; 160 | 161 | this.triggerReady = function(){ 162 | if (webView.triggerReady() === false) return; 163 | 164 | stateManager.set('default'); 165 | 166 | console.log('ready!'); 167 | self.emit('ready'); 168 | 169 | // let anyone who cares know that we have loaded an MRAID creative 170 | window.top.postMessage({ name:'mraid-proclamation' }, '*'); 171 | }; 172 | 173 | function init(){ 174 | stateManager.on('stateChange', function(data){ 175 | console.log('state change: ' + data); 176 | self.emit('stateChange', data); 177 | }); 178 | 179 | stateManager.on('error', function(data){ 180 | self.emit('error', data); 181 | }); 182 | 183 | webView.on('close-click', function(){ 184 | self.close(); 185 | }); 186 | 187 | self.on('error', function(){}); // this is so node doesn't throw if no one is listening 188 | } 189 | 190 | init(); 191 | } 192 | 193 | util.inherits(Mraid, EventEmitter); 194 | 195 | module.exports = Mraid; 196 | -------------------------------------------------------------------------------- /src/polyfill/web-view.js: -------------------------------------------------------------------------------- 1 | var videoJs = require('video.js'), 2 | util = require('util'), 3 | url = require('url'), 4 | $ = require('jquery'), 5 | {EventEmitter} = require('events'), 6 | getFrameElement = require('../frame-to-element'); 7 | 8 | const inIframe = window !== window.top ; 9 | 10 | 11 | // todo: handle this like a grownup 12 | try { 13 | var css = require('./style.scss'); 14 | } 15 | catch (e){} 16 | 17 | var WebView = function(options){ 18 | EventEmitter.call(this); 19 | 20 | var self = this, 21 | STANDARD_SIZES = { 22 | '480x80': true, 23 | '300x50': true, 24 | '320x50': true, 25 | '320x250': true, 26 | '728x90': true, 27 | '300x480': true 28 | }, 29 | IFRAME_MARKER_NAME = 'anx-mraid-marker', 30 | $mraidTag, 31 | $webView, 32 | $close, 33 | videoCount = 0, 34 | initialSize, 35 | screenSize; 36 | 37 | options = options || {}; 38 | screenSize = { 39 | width: options.width || 768, 40 | height: options.height || 1024 41 | }; 42 | 43 | function buildCloseButton(){ 44 | var $close = $('
') 45 | .attr('class', 'anx-mraid-close') 46 | .append('X') 47 | .hide(); 48 | 49 | return $close; 50 | } 51 | 52 | function canAccessParent(){ 53 | if (!inIframe) return false; 54 | 55 | try { 56 | window.parent.location.toString(); 57 | return true; 58 | } 59 | catch(e){ 60 | return false; 61 | } 62 | } 63 | 64 | function getCurrentUrlWithIframeParameter(){ 65 | var myUrl = url.parse(window.location.toString(), true); 66 | myUrl.query[IFRAME_MARKER_NAME] = 1; 67 | delete myUrl.search; // make sure the query object is used during format call. 68 | 69 | return url.format(myUrl); 70 | } 71 | 72 | function findWebView(){ 73 | var $el; 74 | $el = $mraidTag.parent(); 75 | if ($el.length && $el.is('head')){ 76 | $el = $('body'); 77 | } 78 | 79 | if (!inIframe && $el.is('body')){ 80 | // if we are the only thing on page then hide everything and re-request the 81 | // creative from within an iframe so that we have a container that we can size. 82 | $el.children().hide(); 83 | 84 | console.log('***restarting in iframe***'); 85 | 86 | var $iframe = $('') 206 | .attr('src', url); 207 | 208 | $webView.append($iframe); 209 | }; 210 | 211 | this.showVideo = function(url){ 212 | videoCount += 1; 213 | url = url || ''; 214 | 215 | var beforeVideoSize = this.getCurrentPosition(); 216 | var maxSize = this.getScreenSize(); 217 | var $children = $webView.children(); 218 | $children.hide(); 219 | 220 | var videoOptions = { 221 | autoplay: true, 222 | controls: false 223 | }; 224 | 225 | var videoId = 'anx-mraid-video-' + videoCount; 226 | var $video = $('') 227 | .css('max-width', maxSize.width + 'px') 228 | .css('max-height', maxSize.height + 'px') 229 | .attr('id', videoId); 230 | 231 | var $source = $('') 232 | .attr('type', 'video/'+ url.match(/\.(\w*)($|\?)/)[1]) 233 | .attr('src', url); 234 | 235 | $video.append($source); 236 | $webView.append($video); 237 | 238 | var $closeBtn = buildCloseButton(); 239 | $closeBtn.addClass('anx-mraid-video-close'); 240 | $closeBtn.show(); 241 | 242 | $closeBtn.click(function(){ 243 | $('#'+videoId).remove(); 244 | $closeBtn.remove(); 245 | $children.show(); 246 | 247 | self.setSize(beforeVideoSize.width, beforeVideoSize.height); 248 | }); 249 | 250 | videoJs(videoId, videoOptions, function(){ 251 | this.on('loadedmetadata', function(){ 252 | // todo: seems weird... need to handle resize or something 253 | self.setSize(this.width(), this.height()); 254 | 255 | $video 256 | .parent() 257 | .append($closeBtn); 258 | }); 259 | }); 260 | }; 261 | 262 | this.setSize = function(width, height){ 263 | ensureInitialSizeIsSet(); 264 | 265 | console.log('setting size to ' + width + 'x' + height); 266 | 267 | width = width.toString().match(/^(\d+)/)[1] * 1; 268 | height = height.toString().match(/^(\d+)/)[1] * 1; 269 | 270 | width = Math.min(width, screenSize.width); 271 | height = Math.min(height, screenSize.height); 272 | 273 | if (inIframe){ 274 | if (canAccessParent()){ 275 | $(getFrameElement(window)) 276 | .css('width', width + 'px') 277 | .css('height', height + 'px'); 278 | } else { 279 | // the browser extensions will be listening for this message 280 | 281 | window.parent.postMessage({ 282 | name:'mraid-resize', 283 | src: window.location.toString(), 284 | width: width, 285 | height: height 286 | }, '*'); 287 | } 288 | } else { 289 | $webView 290 | .css('width', width + 'px') 291 | .css('height', height + 'px'); 292 | } 293 | }; 294 | 295 | this.triggerReady = function(){ 296 | $mraidTag = $('script[src*="mraid.js"]'); 297 | if (!$mraidTag || !$mraidTag.length){ 298 | // no mraid tag, bail! 299 | return; 300 | } 301 | 302 | $webView = findWebView(); 303 | if (!$webView) return false; 304 | 305 | console.log('screen size ' + screenSize.width + 'x' + screenSize.height); 306 | 307 | $('head').prepend($('').html(css)); 308 | $webView.addClass('anx-mraid-webview'); 309 | 310 | $close = buildCloseButton(); 311 | 312 | $close.click(function(){ 313 | $webView.find('.anx-mraid-url').remove(); 314 | self.emit('close-click'); 315 | self.hideClose(); 316 | }); 317 | 318 | $webView.append($close); 319 | 320 | let size = getCreativeSize(); 321 | if (isInSelfMadeFrame()){ 322 | console.log('initializing size: ' + size.width + 'x' + size.height + ' (' + size.from + ')'); 323 | ensureInitialSizeIsSet(size); 324 | this.setSize(size.width, size.height); 325 | } else if (size) { 326 | console.log('ignoring initial size: ' + size.width + 'x' + size.height + ' (' + size.from + ')'); 327 | } 328 | 329 | }; 330 | }; 331 | 332 | util.inherits(WebView, EventEmitter); 333 | module.exports = WebView; 334 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Apache License 2 | Version 2.0, January 2004 3 | http://www.apache.org/licenses/ 4 | 5 | TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION 6 | 7 | 1. Definitions. 8 | 9 | "License" shall mean the terms and conditions for use, reproduction, 10 | and distribution as defined by Sections 1 through 9 of this document. 11 | 12 | "Licensor" shall mean the copyright owner or entity authorized by 13 | the copyright owner that is granting the License. 14 | 15 | "Legal Entity" shall mean the union of the acting entity and all 16 | other entities that control, are controlled by, or are under common 17 | control with that entity. For the purposes of this definition, 18 | "control" means (i) the power, direct or indirect, to cause the 19 | direction or management of such entity, whether by contract or 20 | otherwise, or (ii) ownership of fifty percent (50%) or more of the 21 | outstanding shares, or (iii) beneficial ownership of such entity. 22 | 23 | "You" (or "Your") shall mean an individual or Legal Entity 24 | exercising permissions granted by this License. 25 | 26 | "Source" form shall mean the preferred form for making modifications, 27 | including but not limited to software source code, documentation 28 | source, and configuration files. 29 | 30 | "Object" form shall mean any form resulting from mechanical 31 | transformation or translation of a Source form, including but 32 | not limited to compiled object code, generated documentation, 33 | and conversions to other media types. 34 | 35 | "Work" shall mean the work of authorship, whether in Source or 36 | Object form, made available under the License, as indicated by a 37 | copyright notice that is included in or attached to the work 38 | (an example is provided in the Appendix below). 39 | 40 | "Derivative Works" shall mean any work, whether in Source or Object 41 | form, that is based on (or derived from) the Work and for which the 42 | editorial revisions, annotations, elaborations, or other modifications 43 | represent, as a whole, an original work of authorship. For the purposes 44 | of this License, Derivative Works shall not include works that remain 45 | separable from, or merely link (or bind by name) to the interfaces of, 46 | the Work and Derivative Works thereof. 47 | 48 | "Contribution" shall mean any work of authorship, including 49 | the original version of the Work and any modifications or additions 50 | to that Work or Derivative Works thereof, that is intentionally 51 | submitted to Licensor for inclusion in the Work by the copyright owner 52 | or by an individual or Legal Entity authorized to submit on behalf of 53 | the copyright owner. For the purposes of this definition, "submitted" 54 | means any form of electronic, verbal, or written communication sent 55 | to the Licensor or its representatives, including but not limited to 56 | communication on electronic mailing lists, source code control systems, 57 | and issue tracking systems that are managed by, or on behalf of, the 58 | Licensor for the purpose of discussing and improving the Work, but 59 | excluding communication that is conspicuously marked or otherwise 60 | designated in writing by the copyright owner as "Not a Contribution." 61 | 62 | "Contributor" shall mean Licensor and any individual or Legal Entity 63 | on behalf of whom a Contribution has been received by Licensor and 64 | subsequently incorporated within the Work. 65 | 66 | 2. Grant of Copyright License. Subject to the terms and conditions of 67 | this License, each Contributor hereby grants to You a perpetual, 68 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 69 | copyright license to reproduce, prepare Derivative Works of, 70 | publicly display, publicly perform, sublicense, and distribute the 71 | Work and such Derivative Works in Source or Object form. 72 | 73 | 3. Grant of Patent License. Subject to the terms and conditions of 74 | this License, each Contributor hereby grants to You a perpetual, 75 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 76 | (except as stated in this section) patent license to make, have made, 77 | use, offer to sell, sell, import, and otherwise transfer the Work, 78 | where such license applies only to those patent claims licensable 79 | by such Contributor that are necessarily infringed by their 80 | Contribution(s) alone or by combination of their Contribution(s) 81 | with the Work to which such Contribution(s) was submitted. If You 82 | institute patent litigation against any entity (including a 83 | cross-claim or counterclaim in a lawsuit) alleging that the Work 84 | or a Contribution incorporated within the Work constitutes direct 85 | or contributory patent infringement, then any patent licenses 86 | granted to You under this License for that Work shall terminate 87 | as of the date such litigation is filed. 88 | 89 | 4. Redistribution. You may reproduce and distribute copies of the 90 | Work or Derivative Works thereof in any medium, with or without 91 | modifications, and in Source or Object form, provided that You 92 | meet the following conditions: 93 | 94 | (a) You must give any other recipients of the Work or 95 | Derivative Works a copy of this License; and 96 | 97 | (b) You must cause any modified files to carry prominent notices 98 | stating that You changed the files; and 99 | 100 | (c) You must retain, in the Source form of any Derivative Works 101 | that You distribute, all copyright, patent, trademark, and 102 | attribution notices from the Source form of the Work, 103 | excluding those notices that do not pertain to any part of 104 | the Derivative Works; and 105 | 106 | (d) If the Work includes a "NOTICE" text file as part of its 107 | distribution, then any Derivative Works that You distribute must 108 | include a readable copy of the attribution notices contained 109 | within such NOTICE file, excluding those notices that do not 110 | pertain to any part of the Derivative Works, in at least one 111 | of the following places: within a NOTICE text file distributed 112 | as part of the Derivative Works; within the Source form or 113 | documentation, if provided along with the Derivative Works; or, 114 | within a display generated by the Derivative Works, if and 115 | wherever such third-party notices normally appear. The contents 116 | of the NOTICE file are for informational purposes only and 117 | do not modify the License. You may add Your own attribution 118 | notices within Derivative Works that You distribute, alongside 119 | or as an addendum to the NOTICE text from the Work, provided 120 | that such additional attribution notices cannot be construed 121 | as modifying the License. 122 | 123 | You may add Your own copyright statement to Your modifications and 124 | may provide additional or different license terms and conditions 125 | for use, reproduction, or distribution of Your modifications, or 126 | for any such Derivative Works as a whole, provided Your use, 127 | reproduction, and distribution of the Work otherwise complies with 128 | the conditions stated in this License. 129 | 130 | 5. Submission of Contributions. Unless You explicitly state otherwise, 131 | any Contribution intentionally submitted for inclusion in the Work 132 | by You to the Licensor shall be under the terms and conditions of 133 | this License, without any additional terms or conditions. 134 | Notwithstanding the above, nothing herein shall supersede or modify 135 | the terms of any separate license agreement you may have executed 136 | with Licensor regarding such Contributions. 137 | 138 | 6. Trademarks. This License does not grant permission to use the trade 139 | names, trademarks, service marks, or product names of the Licensor, 140 | except as required for reasonable and customary use in describing the 141 | origin of the Work and reproducing the content of the NOTICE file. 142 | 143 | 7. Disclaimer of Warranty. Unless required by applicable law or 144 | agreed to in writing, Licensor provides the Work (and each 145 | Contributor provides its Contributions) on an "AS IS" BASIS, 146 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or 147 | implied, including, without limitation, any warranties or conditions 148 | of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A 149 | PARTICULAR PURPOSE. You are solely responsible for determining the 150 | appropriateness of using or redistributing the Work and assume any 151 | risks associated with Your exercise of permissions under this License. 152 | 153 | 8. Limitation of Liability. In no event and under no legal theory, 154 | whether in tort (including negligence), contract, or otherwise, 155 | unless required by applicable law (such as deliberate and grossly 156 | negligent acts) or agreed to in writing, shall any Contributor be 157 | liable to You for damages, including any direct, indirect, special, 158 | incidental, or consequential damages of any character arising as a 159 | result of this License or out of the use or inability to use the 160 | Work (including but not limited to damages for loss of goodwill, 161 | work stoppage, computer failure or malfunction, or any and all 162 | other commercial damages or losses), even if such Contributor 163 | has been advised of the possibility of such damages. 164 | 165 | 9. Accepting Warranty or Additional Liability. While redistributing 166 | the Work or Derivative Works thereof, You may choose to offer, 167 | and charge a fee for, acceptance of support, warranty, indemnity, 168 | or other liability obligations and/or rights consistent with this 169 | License. However, in accepting such obligations, You may act only 170 | on Your own behalf and on Your sole responsibility, not on behalf 171 | of any other Contributor, and only if You agree to indemnify, 172 | defend, and hold each Contributor harmless for any liability 173 | incurred by, or claims asserted against, such Contributor by reason 174 | of your accepting any such warranty or additional liability. 175 | 176 | END OF TERMS AND CONDITIONS 177 | 178 | APPENDIX: How to apply the Apache License to your work. 179 | 180 | To apply the Apache License to your work, attach the following 181 | boilerplate notice, with the fields enclosed by brackets "[]" 182 | replaced with your own identifying information. (Don't include 183 | the brackets!) The text should be enclosed in the appropriate 184 | comment syntax for the file format. We also recommend that a 185 | file or class name and description of purpose be included on the 186 | same "printed page" as the copyright notice for easier 187 | identification within third-party archives. 188 | 189 | Copyright 2013 APPNEXUS INC 190 | 191 | Licensed under the Apache License, Version 2.0 (the "License"); 192 | you may not use this file except in compliance with the License. 193 | You may obtain a copy of the License at 194 | 195 | http://www.apache.org/licenses/LICENSE-2.0 196 | 197 | Unless required by applicable law or agreed to in writing, software 198 | distributed under the License is distributed on an "AS IS" BASIS, 199 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 200 | See the License for the specific language governing permissions and 201 | limitations under the License. 202 | -------------------------------------------------------------------------------- /public/mocha/mocha.js: -------------------------------------------------------------------------------- 1 | ;(function(){ 2 | 3 | // CommonJS require() 4 | 5 | function require(p){ 6 | var path = require.resolve(p) 7 | , mod = require.modules[path]; 8 | if (!mod) throw new Error('failed to require "' + p + '"'); 9 | if (!mod.exports) { 10 | mod.exports = {}; 11 | mod.call(mod.exports, mod, mod.exports, require.relative(path)); 12 | } 13 | return mod.exports; 14 | } 15 | 16 | require.modules = {}; 17 | 18 | require.resolve = function (path){ 19 | var orig = path 20 | , reg = path + '.js' 21 | , index = path + '/index.js'; 22 | return require.modules[reg] && reg 23 | || require.modules[index] && index 24 | || orig; 25 | }; 26 | 27 | require.register = function (path, fn){ 28 | require.modules[path] = fn; 29 | }; 30 | 31 | require.relative = function (parent) { 32 | return function(p){ 33 | if ('.' != p.charAt(0)) return require(p); 34 | 35 | var path = parent.split('/') 36 | , segs = p.split('/'); 37 | path.pop(); 38 | 39 | for (var i = 0; i < segs.length; i++) { 40 | var seg = segs[i]; 41 | if ('..' == seg) path.pop(); 42 | else if ('.' != seg) path.push(seg); 43 | } 44 | 45 | return require(path.join('/')); 46 | }; 47 | }; 48 | 49 | 50 | require.register("browser/debug.js", function(module, exports, require){ 51 | 52 | module.exports = function(type){ 53 | return function(){ 54 | } 55 | }; 56 | 57 | }); // module: browser/debug.js 58 | 59 | require.register("browser/diff.js", function(module, exports, require){ 60 | /* See license.txt for terms of usage */ 61 | 62 | /* 63 | * Text diff implementation. 64 | * 65 | * This library supports the following APIS: 66 | * JsDiff.diffChars: Character by character diff 67 | * JsDiff.diffWords: Word (as defined by \b regex) diff which ignores whitespace 68 | * JsDiff.diffLines: Line based diff 69 | * 70 | * JsDiff.diffCss: Diff targeted at CSS content 71 | * 72 | * These methods are based on the implementation proposed in 73 | * "An O(ND) Difference Algorithm and its Variations" (Myers, 1986). 74 | * http://citeseerx.ist.psu.edu/viewdoc/summary?doi=10.1.1.4.6927 75 | */ 76 | var JsDiff = (function() { 77 | function clonePath(path) { 78 | return { newPos: path.newPos, components: path.components.slice(0) }; 79 | } 80 | function removeEmpty(array) { 81 | var ret = []; 82 | for (var i = 0; i < array.length; i++) { 83 | if (array[i]) { 84 | ret.push(array[i]); 85 | } 86 | } 87 | return ret; 88 | } 89 | function escapeHTML(s) { 90 | var n = s; 91 | n = n.replace(/&/g, "&"); 92 | n = n.replace(//g, ">"); 94 | n = n.replace(/"/g, """); 95 | 96 | return n; 97 | } 98 | 99 | 100 | var fbDiff = function(ignoreWhitespace) { 101 | this.ignoreWhitespace = ignoreWhitespace; 102 | }; 103 | fbDiff.prototype = { 104 | diff: function(oldString, newString) { 105 | // Handle the identity case (this is due to unrolling editLength == 0 106 | if (newString == oldString) { 107 | return [{ value: newString }]; 108 | } 109 | if (!newString) { 110 | return [{ value: oldString, removed: true }]; 111 | } 112 | if (!oldString) { 113 | return [{ value: newString, added: true }]; 114 | } 115 | 116 | newString = this.tokenize(newString); 117 | oldString = this.tokenize(oldString); 118 | 119 | var newLen = newString.length, oldLen = oldString.length; 120 | var maxEditLength = newLen + oldLen; 121 | var bestPath = [{ newPos: -1, components: [] }]; 122 | 123 | // Seed editLength = 0 124 | var oldPos = this.extractCommon(bestPath[0], newString, oldString, 0); 125 | if (bestPath[0].newPos+1 >= newLen && oldPos+1 >= oldLen) { 126 | return bestPath[0].components; 127 | } 128 | 129 | for (var editLength = 1; editLength <= maxEditLength; editLength++) { 130 | for (var diagonalPath = -1*editLength; diagonalPath <= editLength; diagonalPath+=2) { 131 | var basePath; 132 | var addPath = bestPath[diagonalPath-1], 133 | removePath = bestPath[diagonalPath+1]; 134 | oldPos = (removePath ? removePath.newPos : 0) - diagonalPath; 135 | if (addPath) { 136 | // No one else is going to attempt to use this value, clear it 137 | bestPath[diagonalPath-1] = undefined; 138 | } 139 | 140 | var canAdd = addPath && addPath.newPos+1 < newLen; 141 | var canRemove = removePath && 0 <= oldPos && oldPos < oldLen; 142 | if (!canAdd && !canRemove) { 143 | bestPath[diagonalPath] = undefined; 144 | continue; 145 | } 146 | 147 | // Select the diagonal that we want to branch from. We select the prior 148 | // path whose position in the new string is the farthest from the origin 149 | // and does not pass the bounds of the diff graph 150 | if (!canAdd || (canRemove && addPath.newPos < removePath.newPos)) { 151 | basePath = clonePath(removePath); 152 | this.pushComponent(basePath.components, oldString[oldPos], undefined, true); 153 | } else { 154 | basePath = clonePath(addPath); 155 | basePath.newPos++; 156 | this.pushComponent(basePath.components, newString[basePath.newPos], true, undefined); 157 | } 158 | 159 | var oldPos = this.extractCommon(basePath, newString, oldString, diagonalPath); 160 | 161 | if (basePath.newPos+1 >= newLen && oldPos+1 >= oldLen) { 162 | return basePath.components; 163 | } else { 164 | bestPath[diagonalPath] = basePath; 165 | } 166 | } 167 | } 168 | }, 169 | 170 | pushComponent: function(components, value, added, removed) { 171 | var last = components[components.length-1]; 172 | if (last && last.added === added && last.removed === removed) { 173 | // We need to clone here as the component clone operation is just 174 | // as shallow array clone 175 | components[components.length-1] = 176 | {value: this.join(last.value, value), added: added, removed: removed }; 177 | } else { 178 | components.push({value: value, added: added, removed: removed }); 179 | } 180 | }, 181 | extractCommon: function(basePath, newString, oldString, diagonalPath) { 182 | var newLen = newString.length, 183 | oldLen = oldString.length, 184 | newPos = basePath.newPos, 185 | oldPos = newPos - diagonalPath; 186 | while (newPos+1 < newLen && oldPos+1 < oldLen && this.equals(newString[newPos+1], oldString[oldPos+1])) { 187 | newPos++; 188 | oldPos++; 189 | 190 | this.pushComponent(basePath.components, newString[newPos], undefined, undefined); 191 | } 192 | basePath.newPos = newPos; 193 | return oldPos; 194 | }, 195 | 196 | equals: function(left, right) { 197 | var reWhitespace = /\S/; 198 | if (this.ignoreWhitespace && !reWhitespace.test(left) && !reWhitespace.test(right)) { 199 | return true; 200 | } else { 201 | return left == right; 202 | } 203 | }, 204 | join: function(left, right) { 205 | return left + right; 206 | }, 207 | tokenize: function(value) { 208 | return value; 209 | } 210 | }; 211 | 212 | var CharDiff = new fbDiff(); 213 | 214 | var WordDiff = new fbDiff(true); 215 | WordDiff.tokenize = function(value) { 216 | return removeEmpty(value.split(/(\s+|\b)/)); 217 | }; 218 | 219 | var CssDiff = new fbDiff(true); 220 | CssDiff.tokenize = function(value) { 221 | return removeEmpty(value.split(/([{}:;,]|\s+)/)); 222 | }; 223 | 224 | var LineDiff = new fbDiff(); 225 | LineDiff.tokenize = function(value) { 226 | return value.split(/^/m); 227 | }; 228 | 229 | return { 230 | diffChars: function(oldStr, newStr) { return CharDiff.diff(oldStr, newStr); }, 231 | diffWords: function(oldStr, newStr) { return WordDiff.diff(oldStr, newStr); }, 232 | diffLines: function(oldStr, newStr) { return LineDiff.diff(oldStr, newStr); }, 233 | 234 | diffCss: function(oldStr, newStr) { return CssDiff.diff(oldStr, newStr); }, 235 | 236 | createPatch: function(fileName, oldStr, newStr, oldHeader, newHeader) { 237 | var ret = []; 238 | 239 | ret.push("Index: " + fileName); 240 | ret.push("==================================================================="); 241 | ret.push("--- " + fileName + (typeof oldHeader === "undefined" ? "" : "\t" + oldHeader)); 242 | ret.push("+++ " + fileName + (typeof newHeader === "undefined" ? "" : "\t" + newHeader)); 243 | 244 | var diff = LineDiff.diff(oldStr, newStr); 245 | if (!diff[diff.length-1].value) { 246 | diff.pop(); // Remove trailing newline add 247 | } 248 | diff.push({value: "", lines: []}); // Append an empty value to make cleanup easier 249 | 250 | function contextLines(lines) { 251 | return lines.map(function(entry) { return ' ' + entry; }); 252 | } 253 | function eofNL(curRange, i, current) { 254 | var last = diff[diff.length-2], 255 | isLast = i === diff.length-2, 256 | isLastOfType = i === diff.length-3 && (current.added === !last.added || current.removed === !last.removed); 257 | 258 | // Figure out if this is the last line for the given file and missing NL 259 | if (!/\n$/.test(current.value) && (isLast || isLastOfType)) { 260 | curRange.push('\\ No newline at end of file'); 261 | } 262 | } 263 | 264 | var oldRangeStart = 0, newRangeStart = 0, curRange = [], 265 | oldLine = 1, newLine = 1; 266 | for (var i = 0; i < diff.length; i++) { 267 | var current = diff[i], 268 | lines = current.lines || current.value.replace(/\n$/, "").split("\n"); 269 | current.lines = lines; 270 | 271 | if (current.added || current.removed) { 272 | if (!oldRangeStart) { 273 | var prev = diff[i-1]; 274 | oldRangeStart = oldLine; 275 | newRangeStart = newLine; 276 | 277 | if (prev) { 278 | curRange = contextLines(prev.lines.slice(-4)); 279 | oldRangeStart -= curRange.length; 280 | newRangeStart -= curRange.length; 281 | } 282 | } 283 | curRange.push.apply(curRange, lines.map(function(entry) { return (current.added?"+":"-") + entry; })); 284 | eofNL(curRange, i, current); 285 | 286 | if (current.added) { 287 | newLine += lines.length; 288 | } else { 289 | oldLine += lines.length; 290 | } 291 | } else { 292 | if (oldRangeStart) { 293 | // Close out any changes that have been output (or join overlapping) 294 | if (lines.length <= 8 && i < diff.length-2) { 295 | // Overlapping 296 | curRange.push.apply(curRange, contextLines(lines)); 297 | } else { 298 | // end the range and output 299 | var contextSize = Math.min(lines.length, 4); 300 | ret.push( 301 | "@@ -" + oldRangeStart + "," + (oldLine-oldRangeStart+contextSize) 302 | + " +" + newRangeStart + "," + (newLine-newRangeStart+contextSize) 303 | + " @@"); 304 | ret.push.apply(ret, curRange); 305 | ret.push.apply(ret, contextLines(lines.slice(0, contextSize))); 306 | if (lines.length <= 4) { 307 | eofNL(ret, i, current); 308 | } 309 | 310 | oldRangeStart = 0; newRangeStart = 0; curRange = []; 311 | } 312 | } 313 | oldLine += lines.length; 314 | newLine += lines.length; 315 | } 316 | } 317 | 318 | return ret.join('\n') + '\n'; 319 | }, 320 | 321 | convertChangesToXML: function(changes){ 322 | var ret = []; 323 | for ( var i = 0; i < changes.length; i++) { 324 | var change = changes[i]; 325 | if (change.added) { 326 | ret.push(""); 327 | } else if (change.removed) { 328 | ret.push(""); 329 | } 330 | 331 | ret.push(escapeHTML(change.value)); 332 | 333 | if (change.added) { 334 | ret.push(""); 335 | } else if (change.removed) { 336 | ret.push(""); 337 | } 338 | } 339 | return ret.join(""); 340 | } 341 | }; 342 | })(); 343 | 344 | if (typeof module !== "undefined") { 345 | module.exports = JsDiff; 346 | } 347 | 348 | }); // module: browser/diff.js 349 | 350 | require.register("browser/events.js", function(module, exports, require){ 351 | 352 | /** 353 | * Module exports. 354 | */ 355 | 356 | exports.EventEmitter = EventEmitter; 357 | 358 | /** 359 | * Check if `obj` is an array. 360 | */ 361 | 362 | function isArray(obj) { 363 | return '[object Array]' == {}.toString.call(obj); 364 | } 365 | 366 | /** 367 | * Event emitter constructor. 368 | * 369 | * @api public 370 | */ 371 | 372 | function EventEmitter(){}; 373 | 374 | /** 375 | * Adds a listener. 376 | * 377 | * @api public 378 | */ 379 | 380 | EventEmitter.prototype.on = function (name, fn) { 381 | if (!this.$events) { 382 | this.$events = {}; 383 | } 384 | 385 | if (!this.$events[name]) { 386 | this.$events[name] = fn; 387 | } else if (isArray(this.$events[name])) { 388 | this.$events[name].push(fn); 389 | } else { 390 | this.$events[name] = [this.$events[name], fn]; 391 | } 392 | 393 | return this; 394 | }; 395 | 396 | EventEmitter.prototype.addListener = EventEmitter.prototype.on; 397 | 398 | /** 399 | * Adds a volatile listener. 400 | * 401 | * @api public 402 | */ 403 | 404 | EventEmitter.prototype.once = function (name, fn) { 405 | var self = this; 406 | 407 | function on () { 408 | self.removeListener(name, on); 409 | fn.apply(this, arguments); 410 | }; 411 | 412 | on.listener = fn; 413 | this.on(name, on); 414 | 415 | return this; 416 | }; 417 | 418 | /** 419 | * Removes a listener. 420 | * 421 | * @api public 422 | */ 423 | 424 | EventEmitter.prototype.removeListener = function (name, fn) { 425 | if (this.$events && this.$events[name]) { 426 | var list = this.$events[name]; 427 | 428 | if (isArray(list)) { 429 | var pos = -1; 430 | 431 | for (var i = 0, l = list.length; i < l; i++) { 432 | if (list[i] === fn || (list[i].listener && list[i].listener === fn)) { 433 | pos = i; 434 | break; 435 | } 436 | } 437 | 438 | if (pos < 0) { 439 | return this; 440 | } 441 | 442 | list.splice(pos, 1); 443 | 444 | if (!list.length) { 445 | delete this.$events[name]; 446 | } 447 | } else if (list === fn || (list.listener && list.listener === fn)) { 448 | delete this.$events[name]; 449 | } 450 | } 451 | 452 | return this; 453 | }; 454 | 455 | /** 456 | * Removes all listeners for an event. 457 | * 458 | * @api public 459 | */ 460 | 461 | EventEmitter.prototype.removeAllListeners = function (name) { 462 | if (name === undefined) { 463 | this.$events = {}; 464 | return this; 465 | } 466 | 467 | if (this.$events && this.$events[name]) { 468 | this.$events[name] = null; 469 | } 470 | 471 | return this; 472 | }; 473 | 474 | /** 475 | * Gets all listeners for a certain event. 476 | * 477 | * @api public 478 | */ 479 | 480 | EventEmitter.prototype.listeners = function (name) { 481 | if (!this.$events) { 482 | this.$events = {}; 483 | } 484 | 485 | if (!this.$events[name]) { 486 | this.$events[name] = []; 487 | } 488 | 489 | if (!isArray(this.$events[name])) { 490 | this.$events[name] = [this.$events[name]]; 491 | } 492 | 493 | return this.$events[name]; 494 | }; 495 | 496 | /** 497 | * Emits an event. 498 | * 499 | * @api public 500 | */ 501 | 502 | EventEmitter.prototype.emit = function (name) { 503 | if (!this.$events) { 504 | return false; 505 | } 506 | 507 | var handler = this.$events[name]; 508 | 509 | if (!handler) { 510 | return false; 511 | } 512 | 513 | var args = [].slice.call(arguments, 1); 514 | 515 | if ('function' == typeof handler) { 516 | handler.apply(this, args); 517 | } else if (isArray(handler)) { 518 | var listeners = handler.slice(); 519 | 520 | for (var i = 0, l = listeners.length; i < l; i++) { 521 | listeners[i].apply(this, args); 522 | } 523 | } else { 524 | return false; 525 | } 526 | 527 | return true; 528 | }; 529 | }); // module: browser/events.js 530 | 531 | require.register("browser/fs.js", function(module, exports, require){ 532 | 533 | }); // module: browser/fs.js 534 | 535 | require.register("browser/path.js", function(module, exports, require){ 536 | 537 | }); // module: browser/path.js 538 | 539 | require.register("browser/progress.js", function(module, exports, require){ 540 | 541 | /** 542 | * Expose `Progress`. 543 | */ 544 | 545 | module.exports = Progress; 546 | 547 | /** 548 | * Initialize a new `Progress` indicator. 549 | */ 550 | 551 | function Progress() { 552 | this.percent = 0; 553 | this.size(0); 554 | this.fontSize(11); 555 | this.font('helvetica, arial, sans-serif'); 556 | } 557 | 558 | /** 559 | * Set progress size to `n`. 560 | * 561 | * @param {Number} n 562 | * @return {Progress} for chaining 563 | * @api public 564 | */ 565 | 566 | Progress.prototype.size = function(n){ 567 | this._size = n; 568 | return this; 569 | }; 570 | 571 | /** 572 | * Set text to `str`. 573 | * 574 | * @param {String} str 575 | * @return {Progress} for chaining 576 | * @api public 577 | */ 578 | 579 | Progress.prototype.text = function(str){ 580 | this._text = str; 581 | return this; 582 | }; 583 | 584 | /** 585 | * Set font size to `n`. 586 | * 587 | * @param {Number} n 588 | * @return {Progress} for chaining 589 | * @api public 590 | */ 591 | 592 | Progress.prototype.fontSize = function(n){ 593 | this._fontSize = n; 594 | return this; 595 | }; 596 | 597 | /** 598 | * Set font `family`. 599 | * 600 | * @param {String} family 601 | * @return {Progress} for chaining 602 | */ 603 | 604 | Progress.prototype.font = function(family){ 605 | this._font = family; 606 | return this; 607 | }; 608 | 609 | /** 610 | * Update percentage to `n`. 611 | * 612 | * @param {Number} n 613 | * @return {Progress} for chaining 614 | */ 615 | 616 | Progress.prototype.update = function(n){ 617 | this.percent = n; 618 | return this; 619 | }; 620 | 621 | /** 622 | * Draw on `ctx`. 623 | * 624 | * @param {CanvasRenderingContext2d} ctx 625 | * @return {Progress} for chaining 626 | */ 627 | 628 | Progress.prototype.draw = function(ctx){ 629 | var percent = Math.min(this.percent, 100) 630 | , size = this._size 631 | , half = size / 2 632 | , x = half 633 | , y = half 634 | , rad = half - 1 635 | , fontSize = this._fontSize; 636 | 637 | ctx.font = fontSize + 'px ' + this._font; 638 | 639 | var angle = Math.PI * 2 * (percent / 100); 640 | ctx.clearRect(0, 0, size, size); 641 | 642 | // outer circle 643 | ctx.strokeStyle = '#9f9f9f'; 644 | ctx.beginPath(); 645 | ctx.arc(x, y, rad, 0, angle, false); 646 | ctx.stroke(); 647 | 648 | // inner circle 649 | ctx.strokeStyle = '#eee'; 650 | ctx.beginPath(); 651 | ctx.arc(x, y, rad - 1, 0, angle, true); 652 | ctx.stroke(); 653 | 654 | // text 655 | var text = this._text || (percent | 0) + '%' 656 | , w = ctx.measureText(text).width; 657 | 658 | ctx.fillText( 659 | text 660 | , x - w / 2 + 1 661 | , y + fontSize / 2 - 1); 662 | 663 | return this; 664 | }; 665 | 666 | }); // module: browser/progress.js 667 | 668 | require.register("browser/tty.js", function(module, exports, require){ 669 | 670 | exports.isatty = function(){ 671 | return true; 672 | }; 673 | 674 | exports.getWindowSize = function(){ 675 | return [window.innerHeight, window.innerWidth]; 676 | }; 677 | }); // module: browser/tty.js 678 | 679 | require.register("context.js", function(module, exports, require){ 680 | 681 | /** 682 | * Expose `Context`. 683 | */ 684 | 685 | module.exports = Context; 686 | 687 | /** 688 | * Initialize a new `Context`. 689 | * 690 | * @api private 691 | */ 692 | 693 | function Context(){} 694 | 695 | /** 696 | * Set or get the context `Runnable` to `runnable`. 697 | * 698 | * @param {Runnable} runnable 699 | * @return {Context} 700 | * @api private 701 | */ 702 | 703 | Context.prototype.runnable = function(runnable){ 704 | if (0 == arguments.length) return this._runnable; 705 | this.test = this._runnable = runnable; 706 | return this; 707 | }; 708 | 709 | /** 710 | * Set test timeout `ms`. 711 | * 712 | * @param {Number} ms 713 | * @return {Context} self 714 | * @api private 715 | */ 716 | 717 | Context.prototype.timeout = function(ms){ 718 | this.runnable().timeout(ms); 719 | return this; 720 | }; 721 | 722 | /** 723 | * Set test slowness threshold `ms`. 724 | * 725 | * @param {Number} ms 726 | * @return {Context} self 727 | * @api private 728 | */ 729 | 730 | Context.prototype.slow = function(ms){ 731 | this.runnable().slow(ms); 732 | return this; 733 | }; 734 | 735 | /** 736 | * Inspect the context void of `._runnable`. 737 | * 738 | * @return {String} 739 | * @api private 740 | */ 741 | 742 | Context.prototype.inspect = function(){ 743 | return JSON.stringify(this, function(key, val){ 744 | if ('_runnable' == key) return; 745 | if ('test' == key) return; 746 | return val; 747 | }, 2); 748 | }; 749 | 750 | }); // module: context.js 751 | 752 | require.register("hook.js", function(module, exports, require){ 753 | 754 | /** 755 | * Module dependencies. 756 | */ 757 | 758 | var Runnable = require('./runnable'); 759 | 760 | /** 761 | * Expose `Hook`. 762 | */ 763 | 764 | module.exports = Hook; 765 | 766 | /** 767 | * Initialize a new `Hook` with the given `title` and callback `fn`. 768 | * 769 | * @param {String} title 770 | * @param {Function} fn 771 | * @api private 772 | */ 773 | 774 | function Hook(title, fn) { 775 | Runnable.call(this, title, fn); 776 | this.type = 'hook'; 777 | } 778 | 779 | /** 780 | * Inherit from `Runnable.prototype`. 781 | */ 782 | 783 | function F(){}; 784 | F.prototype = Runnable.prototype; 785 | Hook.prototype = new F; 786 | Hook.prototype.constructor = Hook; 787 | 788 | 789 | /** 790 | * Get or set the test `err`. 791 | * 792 | * @param {Error} err 793 | * @return {Error} 794 | * @api public 795 | */ 796 | 797 | Hook.prototype.error = function(err){ 798 | if (0 == arguments.length) { 799 | var err = this._error; 800 | this._error = null; 801 | return err; 802 | } 803 | 804 | this._error = err; 805 | }; 806 | 807 | }); // module: hook.js 808 | 809 | require.register("interfaces/bdd.js", function(module, exports, require){ 810 | 811 | /** 812 | * Module dependencies. 813 | */ 814 | 815 | var Suite = require('../suite') 816 | , Test = require('../test'); 817 | 818 | /** 819 | * BDD-style interface: 820 | * 821 | * describe('Array', function(){ 822 | * describe('#indexOf()', function(){ 823 | * it('should return -1 when not present', function(){ 824 | * 825 | * }); 826 | * 827 | * it('should return the index when present', function(){ 828 | * 829 | * }); 830 | * }); 831 | * }); 832 | * 833 | */ 834 | 835 | module.exports = function(suite){ 836 | var suites = [suite]; 837 | 838 | suite.on('pre-require', function(context, file, mocha){ 839 | 840 | /** 841 | * Execute before running tests. 842 | */ 843 | 844 | context.before = function(fn){ 845 | suites[0].beforeAll(fn); 846 | }; 847 | 848 | /** 849 | * Execute after running tests. 850 | */ 851 | 852 | context.after = function(fn){ 853 | suites[0].afterAll(fn); 854 | }; 855 | 856 | /** 857 | * Execute before each test case. 858 | */ 859 | 860 | context.beforeEach = function(fn){ 861 | suites[0].beforeEach(fn); 862 | }; 863 | 864 | /** 865 | * Execute after each test case. 866 | */ 867 | 868 | context.afterEach = function(fn){ 869 | suites[0].afterEach(fn); 870 | }; 871 | 872 | /** 873 | * Describe a "suite" with the given `title` 874 | * and callback `fn` containing nested suites 875 | * and/or tests. 876 | */ 877 | 878 | context.describe = context.context = function(title, fn){ 879 | var suite = Suite.create(suites[0], title); 880 | suites.unshift(suite); 881 | fn.call(suite); 882 | suites.shift(); 883 | return suite; 884 | }; 885 | 886 | /** 887 | * Pending describe. 888 | */ 889 | 890 | context.xdescribe = 891 | context.xcontext = 892 | context.describe.skip = function(title, fn){ 893 | var suite = Suite.create(suites[0], title); 894 | suite.pending = true; 895 | suites.unshift(suite); 896 | fn.call(suite); 897 | suites.shift(); 898 | }; 899 | 900 | /** 901 | * Exclusive suite. 902 | */ 903 | 904 | context.describe.only = function(title, fn){ 905 | var suite = context.describe(title, fn); 906 | mocha.grep(suite.fullTitle()); 907 | }; 908 | 909 | /** 910 | * Describe a specification or test-case 911 | * with the given `title` and callback `fn` 912 | * acting as a thunk. 913 | */ 914 | 915 | context.it = context.specify = function(title, fn){ 916 | var suite = suites[0]; 917 | if (suite.pending) var fn = null; 918 | var test = new Test(title, fn); 919 | suite.addTest(test); 920 | return test; 921 | }; 922 | 923 | /** 924 | * Exclusive test-case. 925 | */ 926 | 927 | context.it.only = function(title, fn){ 928 | var test = context.it(title, fn); 929 | mocha.grep(test.fullTitle()); 930 | }; 931 | 932 | /** 933 | * Pending test case. 934 | */ 935 | 936 | context.xit = 937 | context.xspecify = 938 | context.it.skip = function(title){ 939 | context.it(title); 940 | }; 941 | }); 942 | }; 943 | 944 | }); // module: interfaces/bdd.js 945 | 946 | require.register("interfaces/exports.js", function(module, exports, require){ 947 | 948 | /** 949 | * Module dependencies. 950 | */ 951 | 952 | var Suite = require('../suite') 953 | , Test = require('../test'); 954 | 955 | /** 956 | * TDD-style interface: 957 | * 958 | * exports.Array = { 959 | * '#indexOf()': { 960 | * 'should return -1 when the value is not present': function(){ 961 | * 962 | * }, 963 | * 964 | * 'should return the correct index when the value is present': function(){ 965 | * 966 | * } 967 | * } 968 | * }; 969 | * 970 | */ 971 | 972 | module.exports = function(suite){ 973 | var suites = [suite]; 974 | 975 | suite.on('require', visit); 976 | 977 | function visit(obj) { 978 | var suite; 979 | for (var key in obj) { 980 | if ('function' == typeof obj[key]) { 981 | var fn = obj[key]; 982 | switch (key) { 983 | case 'before': 984 | suites[0].beforeAll(fn); 985 | break; 986 | case 'after': 987 | suites[0].afterAll(fn); 988 | break; 989 | case 'beforeEach': 990 | suites[0].beforeEach(fn); 991 | break; 992 | case 'afterEach': 993 | suites[0].afterEach(fn); 994 | break; 995 | default: 996 | suites[0].addTest(new Test(key, fn)); 997 | } 998 | } else { 999 | var suite = Suite.create(suites[0], key); 1000 | suites.unshift(suite); 1001 | visit(obj[key]); 1002 | suites.shift(); 1003 | } 1004 | } 1005 | } 1006 | }; 1007 | 1008 | }); // module: interfaces/exports.js 1009 | 1010 | require.register("interfaces/index.js", function(module, exports, require){ 1011 | 1012 | exports.bdd = require('./bdd'); 1013 | exports.tdd = require('./tdd'); 1014 | exports.qunit = require('./qunit'); 1015 | exports.exports = require('./exports'); 1016 | 1017 | }); // module: interfaces/index.js 1018 | 1019 | require.register("interfaces/qunit.js", function(module, exports, require){ 1020 | 1021 | /** 1022 | * Module dependencies. 1023 | */ 1024 | 1025 | var Suite = require('../suite') 1026 | , Test = require('../test'); 1027 | 1028 | /** 1029 | * QUnit-style interface: 1030 | * 1031 | * suite('Array'); 1032 | * 1033 | * test('#length', function(){ 1034 | * var arr = [1,2,3]; 1035 | * ok(arr.length == 3); 1036 | * }); 1037 | * 1038 | * test('#indexOf()', function(){ 1039 | * var arr = [1,2,3]; 1040 | * ok(arr.indexOf(1) == 0); 1041 | * ok(arr.indexOf(2) == 1); 1042 | * ok(arr.indexOf(3) == 2); 1043 | * }); 1044 | * 1045 | * suite('String'); 1046 | * 1047 | * test('#length', function(){ 1048 | * ok('foo'.length == 3); 1049 | * }); 1050 | * 1051 | */ 1052 | 1053 | module.exports = function(suite){ 1054 | var suites = [suite]; 1055 | 1056 | suite.on('pre-require', function(context, file, mocha){ 1057 | 1058 | /** 1059 | * Execute before running tests. 1060 | */ 1061 | 1062 | context.before = function(fn){ 1063 | suites[0].beforeAll(fn); 1064 | }; 1065 | 1066 | /** 1067 | * Execute after running tests. 1068 | */ 1069 | 1070 | context.after = function(fn){ 1071 | suites[0].afterAll(fn); 1072 | }; 1073 | 1074 | /** 1075 | * Execute before each test case. 1076 | */ 1077 | 1078 | context.beforeEach = function(fn){ 1079 | suites[0].beforeEach(fn); 1080 | }; 1081 | 1082 | /** 1083 | * Execute after each test case. 1084 | */ 1085 | 1086 | context.afterEach = function(fn){ 1087 | suites[0].afterEach(fn); 1088 | }; 1089 | 1090 | /** 1091 | * Describe a "suite" with the given `title`. 1092 | */ 1093 | 1094 | context.suite = function(title){ 1095 | if (suites.length > 1) suites.shift(); 1096 | var suite = Suite.create(suites[0], title); 1097 | suites.unshift(suite); 1098 | return suite; 1099 | }; 1100 | 1101 | /** 1102 | * Exclusive test-case. 1103 | */ 1104 | 1105 | context.suite.only = function(title, fn){ 1106 | var suite = context.suite(title, fn); 1107 | mocha.grep(suite.fullTitle()); 1108 | }; 1109 | 1110 | /** 1111 | * Describe a specification or test-case 1112 | * with the given `title` and callback `fn` 1113 | * acting as a thunk. 1114 | */ 1115 | 1116 | context.test = function(title, fn){ 1117 | var test = new Test(title, fn); 1118 | suites[0].addTest(test); 1119 | return test; 1120 | }; 1121 | 1122 | /** 1123 | * Exclusive test-case. 1124 | */ 1125 | 1126 | context.test.only = function(title, fn){ 1127 | var test = context.test(title, fn); 1128 | mocha.grep(test.fullTitle()); 1129 | }; 1130 | 1131 | /** 1132 | * Pending test case. 1133 | */ 1134 | 1135 | context.test.skip = function(title){ 1136 | context.test(title); 1137 | }; 1138 | }); 1139 | }; 1140 | 1141 | }); // module: interfaces/qunit.js 1142 | 1143 | require.register("interfaces/tdd.js", function(module, exports, require){ 1144 | 1145 | /** 1146 | * Module dependencies. 1147 | */ 1148 | 1149 | var Suite = require('../suite') 1150 | , Test = require('../test'); 1151 | 1152 | /** 1153 | * TDD-style interface: 1154 | * 1155 | * suite('Array', function(){ 1156 | * suite('#indexOf()', function(){ 1157 | * suiteSetup(function(){ 1158 | * 1159 | * }); 1160 | * 1161 | * test('should return -1 when not present', function(){ 1162 | * 1163 | * }); 1164 | * 1165 | * test('should return the index when present', function(){ 1166 | * 1167 | * }); 1168 | * 1169 | * suiteTeardown(function(){ 1170 | * 1171 | * }); 1172 | * }); 1173 | * }); 1174 | * 1175 | */ 1176 | 1177 | module.exports = function(suite){ 1178 | var suites = [suite]; 1179 | 1180 | suite.on('pre-require', function(context, file, mocha){ 1181 | 1182 | /** 1183 | * Execute before each test case. 1184 | */ 1185 | 1186 | context.setup = function(fn){ 1187 | suites[0].beforeEach(fn); 1188 | }; 1189 | 1190 | /** 1191 | * Execute after each test case. 1192 | */ 1193 | 1194 | context.teardown = function(fn){ 1195 | suites[0].afterEach(fn); 1196 | }; 1197 | 1198 | /** 1199 | * Execute before the suite. 1200 | */ 1201 | 1202 | context.suiteSetup = function(fn){ 1203 | suites[0].beforeAll(fn); 1204 | }; 1205 | 1206 | /** 1207 | * Execute after the suite. 1208 | */ 1209 | 1210 | context.suiteTeardown = function(fn){ 1211 | suites[0].afterAll(fn); 1212 | }; 1213 | 1214 | /** 1215 | * Describe a "suite" with the given `title` 1216 | * and callback `fn` containing nested suites 1217 | * and/or tests. 1218 | */ 1219 | 1220 | context.suite = function(title, fn){ 1221 | var suite = Suite.create(suites[0], title); 1222 | suites.unshift(suite); 1223 | fn.call(suite); 1224 | suites.shift(); 1225 | return suite; 1226 | }; 1227 | 1228 | /** 1229 | * Exclusive test-case. 1230 | */ 1231 | 1232 | context.suite.only = function(title, fn){ 1233 | var suite = context.suite(title, fn); 1234 | mocha.grep(suite.fullTitle()); 1235 | }; 1236 | 1237 | /** 1238 | * Describe a specification or test-case 1239 | * with the given `title` and callback `fn` 1240 | * acting as a thunk. 1241 | */ 1242 | 1243 | context.test = function(title, fn){ 1244 | var test = new Test(title, fn); 1245 | suites[0].addTest(test); 1246 | return test; 1247 | }; 1248 | 1249 | /** 1250 | * Exclusive test-case. 1251 | */ 1252 | 1253 | context.test.only = function(title, fn){ 1254 | var test = context.test(title, fn); 1255 | mocha.grep(test.fullTitle()); 1256 | }; 1257 | 1258 | /** 1259 | * Pending test case. 1260 | */ 1261 | 1262 | context.test.skip = function(title){ 1263 | context.test(title); 1264 | }; 1265 | }); 1266 | }; 1267 | 1268 | }); // module: interfaces/tdd.js 1269 | 1270 | require.register("mocha.js", function(module, exports, require){ 1271 | /*! 1272 | * mocha 1273 | * Copyright(c) 2011 TJ Holowaychuk 1274 | * MIT Licensed 1275 | */ 1276 | 1277 | /** 1278 | * Module dependencies. 1279 | */ 1280 | 1281 | var path = require('browser/path') 1282 | , utils = require('./utils'); 1283 | 1284 | /** 1285 | * Expose `Mocha`. 1286 | */ 1287 | 1288 | exports = module.exports = Mocha; 1289 | 1290 | /** 1291 | * Expose internals. 1292 | */ 1293 | 1294 | exports.utils = utils; 1295 | exports.interfaces = require('./interfaces'); 1296 | exports.reporters = require('./reporters'); 1297 | exports.Runnable = require('./runnable'); 1298 | exports.Context = require('./context'); 1299 | exports.Runner = require('./runner'); 1300 | exports.Suite = require('./suite'); 1301 | exports.Hook = require('./hook'); 1302 | exports.Test = require('./test'); 1303 | 1304 | /** 1305 | * Return image `name` path. 1306 | * 1307 | * @param {String} name 1308 | * @return {String} 1309 | * @api private 1310 | */ 1311 | 1312 | function image(name) { 1313 | return __dirname + '/../images/' + name + '.png'; 1314 | } 1315 | 1316 | /** 1317 | * Setup mocha with `options`. 1318 | * 1319 | * Options: 1320 | * 1321 | * - `ui` name "bdd", "tdd", "exports" etc 1322 | * - `reporter` reporter instance, defaults to `mocha.reporters.Dot` 1323 | * - `globals` array of accepted globals 1324 | * - `timeout` timeout in milliseconds 1325 | * - `bail` bail on the first test failure 1326 | * - `slow` milliseconds to wait before considering a test slow 1327 | * - `ignoreLeaks` ignore global leaks 1328 | * - `grep` string or regexp to filter tests with 1329 | * 1330 | * @param {Object} options 1331 | * @api public 1332 | */ 1333 | 1334 | function Mocha(options) { 1335 | options = options || {}; 1336 | this.files = []; 1337 | this.options = options; 1338 | this.grep(options.grep); 1339 | this.suite = new exports.Suite('', new exports.Context); 1340 | this.ui(options.ui); 1341 | this.bail(options.bail); 1342 | this.reporter(options.reporter); 1343 | if (options.timeout) this.timeout(options.timeout); 1344 | if (options.slow) this.slow(options.slow); 1345 | } 1346 | 1347 | /** 1348 | * Enable or disable bailing on the first failure. 1349 | * 1350 | * @param {Boolean} [bail] 1351 | * @api public 1352 | */ 1353 | 1354 | Mocha.prototype.bail = function(bail){ 1355 | if (0 == arguments.length) bail = true; 1356 | this.suite.bail(bail); 1357 | return this; 1358 | }; 1359 | 1360 | /** 1361 | * Add test `file`. 1362 | * 1363 | * @param {String} file 1364 | * @api public 1365 | */ 1366 | 1367 | Mocha.prototype.addFile = function(file){ 1368 | this.files.push(file); 1369 | return this; 1370 | }; 1371 | 1372 | /** 1373 | * Set reporter to `reporter`, defaults to "dot". 1374 | * 1375 | * @param {String|Function} reporter name or constructor 1376 | * @api public 1377 | */ 1378 | 1379 | Mocha.prototype.reporter = function(reporter){ 1380 | if ('function' == typeof reporter) { 1381 | this._reporter = reporter; 1382 | } else { 1383 | reporter = reporter || 'dot'; 1384 | try { 1385 | this._reporter = require('./reporters/' + reporter); 1386 | } catch (err) { 1387 | this._reporter = require(reporter); 1388 | } 1389 | if (!this._reporter) throw new Error('invalid reporter "' + reporter + '"'); 1390 | } 1391 | return this; 1392 | }; 1393 | 1394 | /** 1395 | * Set test UI `name`, defaults to "bdd". 1396 | * 1397 | * @param {String} bdd 1398 | * @api public 1399 | */ 1400 | 1401 | Mocha.prototype.ui = function(name){ 1402 | name = name || 'bdd'; 1403 | this._ui = exports.interfaces[name]; 1404 | if (!this._ui) throw new Error('invalid interface "' + name + '"'); 1405 | this._ui = this._ui(this.suite); 1406 | return this; 1407 | }; 1408 | 1409 | /** 1410 | * Load registered files. 1411 | * 1412 | * @api private 1413 | */ 1414 | 1415 | Mocha.prototype.loadFiles = function(fn){ 1416 | var self = this; 1417 | var suite = this.suite; 1418 | var pending = this.files.length; 1419 | this.files.forEach(function(file){ 1420 | file = path.resolve(file); 1421 | suite.emit('pre-require', global, file, self); 1422 | suite.emit('require', require(file), file, self); 1423 | suite.emit('post-require', global, file, self); 1424 | --pending || (fn && fn()); 1425 | }); 1426 | }; 1427 | 1428 | /** 1429 | * Enable growl support. 1430 | * 1431 | * @api private 1432 | */ 1433 | 1434 | Mocha.prototype._growl = function(runner, reporter) { 1435 | var notify = require('growl'); 1436 | 1437 | runner.on('end', function(){ 1438 | var stats = reporter.stats; 1439 | if (stats.failures) { 1440 | var msg = stats.failures + ' of ' + runner.total + ' tests failed'; 1441 | notify(msg, { name: 'mocha', title: 'Failed', image: image('error') }); 1442 | } else { 1443 | notify(stats.passes + ' tests passed in ' + stats.duration + 'ms', { 1444 | name: 'mocha' 1445 | , title: 'Passed' 1446 | , image: image('ok') 1447 | }); 1448 | } 1449 | }); 1450 | }; 1451 | 1452 | /** 1453 | * Add regexp to grep, if `re` is a string it is escaped. 1454 | * 1455 | * @param {RegExp|String} re 1456 | * @return {Mocha} 1457 | * @api public 1458 | */ 1459 | 1460 | Mocha.prototype.grep = function(re){ 1461 | this.options.grep = 'string' == typeof re 1462 | ? new RegExp(utils.escapeRegexp(re)) 1463 | : re; 1464 | return this; 1465 | }; 1466 | 1467 | /** 1468 | * Invert `.grep()` matches. 1469 | * 1470 | * @return {Mocha} 1471 | * @api public 1472 | */ 1473 | 1474 | Mocha.prototype.invert = function(){ 1475 | this.options.invert = true; 1476 | return this; 1477 | }; 1478 | 1479 | /** 1480 | * Ignore global leaks. 1481 | * 1482 | * @return {Mocha} 1483 | * @api public 1484 | */ 1485 | 1486 | Mocha.prototype.ignoreLeaks = function(){ 1487 | this.options.ignoreLeaks = true; 1488 | return this; 1489 | }; 1490 | 1491 | /** 1492 | * Enable global leak checking. 1493 | * 1494 | * @return {Mocha} 1495 | * @api public 1496 | */ 1497 | 1498 | Mocha.prototype.checkLeaks = function(){ 1499 | this.options.ignoreLeaks = false; 1500 | return this; 1501 | }; 1502 | 1503 | /** 1504 | * Enable growl support. 1505 | * 1506 | * @return {Mocha} 1507 | * @api public 1508 | */ 1509 | 1510 | Mocha.prototype.growl = function(){ 1511 | this.options.growl = true; 1512 | return this; 1513 | }; 1514 | 1515 | /** 1516 | * Ignore `globals` array or string. 1517 | * 1518 | * @param {Array|String} globals 1519 | * @return {Mocha} 1520 | * @api public 1521 | */ 1522 | 1523 | Mocha.prototype.globals = function(globals){ 1524 | this.options.globals = (this.options.globals || []).concat(globals); 1525 | return this; 1526 | }; 1527 | 1528 | /** 1529 | * Set the timeout in milliseconds. 1530 | * 1531 | * @param {Number} timeout 1532 | * @return {Mocha} 1533 | * @api public 1534 | */ 1535 | 1536 | Mocha.prototype.timeout = function(timeout){ 1537 | this.suite.timeout(timeout); 1538 | return this; 1539 | }; 1540 | 1541 | /** 1542 | * Set slowness threshold in milliseconds. 1543 | * 1544 | * @param {Number} slow 1545 | * @return {Mocha} 1546 | * @api public 1547 | */ 1548 | 1549 | Mocha.prototype.slow = function(slow){ 1550 | this.suite.slow(slow); 1551 | return this; 1552 | }; 1553 | 1554 | /** 1555 | * Makes all tests async (accepting a callback) 1556 | * 1557 | * @return {Mocha} 1558 | * @api public 1559 | */ 1560 | 1561 | Mocha.prototype.asyncOnly = function(){ 1562 | this.options.asyncOnly = true; 1563 | return this; 1564 | }; 1565 | 1566 | /** 1567 | * Run tests and invoke `fn()` when complete. 1568 | * 1569 | * @param {Function} fn 1570 | * @return {Runner} 1571 | * @api public 1572 | */ 1573 | 1574 | Mocha.prototype.run = function(fn){ 1575 | if (this.files.length) this.loadFiles(); 1576 | var suite = this.suite; 1577 | var options = this.options; 1578 | var runner = new exports.Runner(suite); 1579 | var reporter = new this._reporter(runner); 1580 | runner.ignoreLeaks = false !== options.ignoreLeaks; 1581 | runner.asyncOnly = options.asyncOnly; 1582 | if (options.grep) runner.grep(options.grep, options.invert); 1583 | if (options.globals) runner.globals(options.globals); 1584 | if (options.growl) this._growl(runner, reporter); 1585 | return runner.run(fn); 1586 | }; 1587 | 1588 | }); // module: mocha.js 1589 | 1590 | require.register("ms.js", function(module, exports, require){ 1591 | 1592 | /** 1593 | * Helpers. 1594 | */ 1595 | 1596 | var s = 1000; 1597 | var m = s * 60; 1598 | var h = m * 60; 1599 | var d = h * 24; 1600 | 1601 | /** 1602 | * Parse or format the given `val`. 1603 | * 1604 | * @param {String|Number} val 1605 | * @return {String|Number} 1606 | * @api public 1607 | */ 1608 | 1609 | module.exports = function(val){ 1610 | if ('string' == typeof val) return parse(val); 1611 | return format(val); 1612 | } 1613 | 1614 | /** 1615 | * Parse the given `str` and return milliseconds. 1616 | * 1617 | * @param {String} str 1618 | * @return {Number} 1619 | * @api private 1620 | */ 1621 | 1622 | function parse(str) { 1623 | var m = /^((?:\d+)?\.?\d+) *(ms|seconds?|s|minutes?|m|hours?|h|days?|d|years?|y)?$/i.exec(str); 1624 | if (!m) return; 1625 | var n = parseFloat(m[1]); 1626 | var type = (m[2] || 'ms').toLowerCase(); 1627 | switch (type) { 1628 | case 'years': 1629 | case 'year': 1630 | case 'y': 1631 | return n * 31557600000; 1632 | case 'days': 1633 | case 'day': 1634 | case 'd': 1635 | return n * 86400000; 1636 | case 'hours': 1637 | case 'hour': 1638 | case 'h': 1639 | return n * 3600000; 1640 | case 'minutes': 1641 | case 'minute': 1642 | case 'm': 1643 | return n * 60000; 1644 | case 'seconds': 1645 | case 'second': 1646 | case 's': 1647 | return n * 1000; 1648 | case 'ms': 1649 | return n; 1650 | } 1651 | } 1652 | 1653 | /** 1654 | * Format the given `ms`. 1655 | * 1656 | * @param {Number} ms 1657 | * @return {String} 1658 | * @api public 1659 | */ 1660 | 1661 | function format(ms) { 1662 | if (ms == d) return Math.round(ms / d) + ' day'; 1663 | if (ms > d) return Math.round(ms / d) + ' days'; 1664 | if (ms == h) return Math.round(ms / h) + ' hour'; 1665 | if (ms > h) return Math.round(ms / h) + ' hours'; 1666 | if (ms == m) return Math.round(ms / m) + ' minute'; 1667 | if (ms > m) return Math.round(ms / m) + ' minutes'; 1668 | if (ms == s) return Math.round(ms / s) + ' second'; 1669 | if (ms > s) return Math.round(ms / s) + ' seconds'; 1670 | return ms + ' ms'; 1671 | } 1672 | }); // module: ms.js 1673 | 1674 | require.register("reporters/base.js", function(module, exports, require){ 1675 | 1676 | /** 1677 | * Module dependencies. 1678 | */ 1679 | 1680 | var tty = require('browser/tty') 1681 | , diff = require('browser/diff') 1682 | , ms = require('../ms'); 1683 | 1684 | /** 1685 | * Save timer references to avoid Sinon interfering (see GH-237). 1686 | */ 1687 | 1688 | var Date = global.Date 1689 | , setTimeout = global.setTimeout 1690 | , setInterval = global.setInterval 1691 | , clearTimeout = global.clearTimeout 1692 | , clearInterval = global.clearInterval; 1693 | 1694 | /** 1695 | * Check if both stdio streams are associated with a tty. 1696 | */ 1697 | 1698 | var isatty = tty.isatty(1) && tty.isatty(2); 1699 | 1700 | /** 1701 | * Expose `Base`. 1702 | */ 1703 | 1704 | exports = module.exports = Base; 1705 | 1706 | /** 1707 | * Enable coloring by default. 1708 | */ 1709 | 1710 | exports.useColors = isatty; 1711 | 1712 | /** 1713 | * Default color map. 1714 | */ 1715 | 1716 | exports.colors = { 1717 | 'pass': 90 1718 | , 'fail': 31 1719 | , 'bright pass': 92 1720 | , 'bright fail': 91 1721 | , 'bright yellow': 93 1722 | , 'pending': 36 1723 | , 'suite': 0 1724 | , 'error title': 0 1725 | , 'error message': 31 1726 | , 'error stack': 90 1727 | , 'checkmark': 32 1728 | , 'fast': 90 1729 | , 'medium': 33 1730 | , 'slow': 31 1731 | , 'green': 32 1732 | , 'light': 90 1733 | , 'diff gutter': 90 1734 | , 'diff added': 42 1735 | , 'diff removed': 41 1736 | }; 1737 | 1738 | /** 1739 | * Default symbol map. 1740 | */ 1741 | 1742 | exports.symbols = { 1743 | ok: '✓', 1744 | err: '✖', 1745 | dot: '․' 1746 | }; 1747 | 1748 | // With node.js on Windows: use symbols available in terminal default fonts 1749 | if ('win32' == process.platform) { 1750 | exports.symbols.ok = '\u221A'; 1751 | exports.symbols.err = '\u00D7'; 1752 | exports.symbols.dot = '.'; 1753 | } 1754 | 1755 | /** 1756 | * Color `str` with the given `type`, 1757 | * allowing colors to be disabled, 1758 | * as well as user-defined color 1759 | * schemes. 1760 | * 1761 | * @param {String} type 1762 | * @param {String} str 1763 | * @return {String} 1764 | * @api private 1765 | */ 1766 | 1767 | var color = exports.color = function(type, str) { 1768 | if (!exports.useColors) return str; 1769 | return '\u001b[' + exports.colors[type] + 'm' + str + '\u001b[0m'; 1770 | }; 1771 | 1772 | /** 1773 | * Expose term window size, with some 1774 | * defaults for when stderr is not a tty. 1775 | */ 1776 | 1777 | exports.window = { 1778 | width: isatty 1779 | ? process.stdout.getWindowSize 1780 | ? process.stdout.getWindowSize(1)[0] 1781 | : tty.getWindowSize()[1] 1782 | : 75 1783 | }; 1784 | 1785 | /** 1786 | * Expose some basic cursor interactions 1787 | * that are common among reporters. 1788 | */ 1789 | 1790 | exports.cursor = { 1791 | hide: function(){ 1792 | process.stdout.write('\u001b[?25l'); 1793 | }, 1794 | 1795 | show: function(){ 1796 | process.stdout.write('\u001b[?25h'); 1797 | }, 1798 | 1799 | deleteLine: function(){ 1800 | process.stdout.write('\u001b[2K'); 1801 | }, 1802 | 1803 | beginningOfLine: function(){ 1804 | process.stdout.write('\u001b[0G'); 1805 | }, 1806 | 1807 | CR: function(){ 1808 | exports.cursor.deleteLine(); 1809 | exports.cursor.beginningOfLine(); 1810 | } 1811 | }; 1812 | 1813 | /** 1814 | * Outut the given `failures` as a list. 1815 | * 1816 | * @param {Array} failures 1817 | * @api public 1818 | */ 1819 | 1820 | exports.list = function(failures){ 1821 | console.error(); 1822 | failures.forEach(function(test, i){ 1823 | // format 1824 | var fmt = color('error title', ' %s) %s:\n') 1825 | + color('error message', ' %s') 1826 | + color('error stack', '\n%s\n'); 1827 | 1828 | // msg 1829 | var err = test.err 1830 | , message = err.message || '' 1831 | , stack = err.stack || message 1832 | , index = stack.indexOf(message) + message.length 1833 | , msg = stack.slice(0, index) 1834 | , actual = err.actual 1835 | , expected = err.expected 1836 | , escape = true; 1837 | 1838 | // explicitly show diff 1839 | if (err.showDiff) { 1840 | escape = false; 1841 | err.actual = actual = JSON.stringify(actual, null, 2); 1842 | err.expected = expected = JSON.stringify(expected, null, 2); 1843 | } 1844 | 1845 | // actual / expected diff 1846 | if ('string' == typeof actual && 'string' == typeof expected) { 1847 | msg = errorDiff(err, 'Words', escape); 1848 | 1849 | // linenos 1850 | var lines = msg.split('\n'); 1851 | if (lines.length > 4) { 1852 | var width = String(lines.length).length; 1853 | msg = lines.map(function(str, i){ 1854 | return pad(++i, width) + ' |' + ' ' + str; 1855 | }).join('\n'); 1856 | } 1857 | 1858 | // legend 1859 | msg = '\n' 1860 | + color('diff removed', 'actual') 1861 | + ' ' 1862 | + color('diff added', 'expected') 1863 | + '\n\n' 1864 | + msg 1865 | + '\n'; 1866 | 1867 | // indent 1868 | msg = msg.replace(/^/gm, ' '); 1869 | 1870 | fmt = color('error title', ' %s) %s:\n%s') 1871 | + color('error stack', '\n%s\n'); 1872 | } 1873 | 1874 | // indent stack trace without msg 1875 | stack = stack.slice(index ? index + 1 : index) 1876 | .replace(/^/gm, ' '); 1877 | 1878 | console.error(fmt, (i + 1), test.fullTitle(), msg, stack); 1879 | }); 1880 | }; 1881 | 1882 | /** 1883 | * Initialize a new `Base` reporter. 1884 | * 1885 | * All other reporters generally 1886 | * inherit from this reporter, providing 1887 | * stats such as test duration, number 1888 | * of tests passed / failed etc. 1889 | * 1890 | * @param {Runner} runner 1891 | * @api public 1892 | */ 1893 | 1894 | function Base(runner) { 1895 | var self = this 1896 | , stats = this.stats = { suites: 0, tests: 0, passes: 0, pending: 0, failures: 0 } 1897 | , failures = this.failures = []; 1898 | 1899 | if (!runner) return; 1900 | this.runner = runner; 1901 | 1902 | runner.stats = stats; 1903 | 1904 | runner.on('start', function(){ 1905 | stats.start = new Date; 1906 | }); 1907 | 1908 | runner.on('suite', function(suite){ 1909 | stats.suites = stats.suites || 0; 1910 | suite.root || stats.suites++; 1911 | }); 1912 | 1913 | runner.on('test end', function(test){ 1914 | stats.tests = stats.tests || 0; 1915 | stats.tests++; 1916 | }); 1917 | 1918 | runner.on('pass', function(test){ 1919 | stats.passes = stats.passes || 0; 1920 | 1921 | var medium = test.slow() / 2; 1922 | test.speed = test.duration > test.slow() 1923 | ? 'slow' 1924 | : test.duration > medium 1925 | ? 'medium' 1926 | : 'fast'; 1927 | 1928 | stats.passes++; 1929 | }); 1930 | 1931 | runner.on('fail', function(test, err){ 1932 | stats.failures = stats.failures || 0; 1933 | stats.failures++; 1934 | test.err = err; 1935 | failures.push(test); 1936 | }); 1937 | 1938 | runner.on('end', function(){ 1939 | stats.end = new Date; 1940 | stats.duration = new Date - stats.start; 1941 | }); 1942 | 1943 | runner.on('pending', function(){ 1944 | stats.pending++; 1945 | }); 1946 | } 1947 | 1948 | /** 1949 | * Output common epilogue used by many of 1950 | * the bundled reporters. 1951 | * 1952 | * @api public 1953 | */ 1954 | 1955 | Base.prototype.epilogue = function(){ 1956 | var stats = this.stats 1957 | , fmt 1958 | , tests; 1959 | 1960 | console.log(); 1961 | 1962 | function pluralize(n) { 1963 | return 1 == n ? 'test' : 'tests'; 1964 | } 1965 | 1966 | // failure 1967 | if (stats.failures) { 1968 | fmt = color('bright fail', ' ' + exports.symbols.err) 1969 | + color('fail', ' %d of %d %s failed') 1970 | + color('light', ':') 1971 | 1972 | console.error(fmt, 1973 | stats.failures, 1974 | this.runner.total, 1975 | pluralize(this.runner.total)); 1976 | 1977 | Base.list(this.failures); 1978 | console.error(); 1979 | return; 1980 | } 1981 | 1982 | // pass 1983 | fmt = color('bright pass', ' ') 1984 | + color('green', ' %d %s complete') 1985 | + color('light', ' (%s)'); 1986 | 1987 | console.log(fmt, 1988 | stats.tests || 0, 1989 | pluralize(stats.tests), 1990 | ms(stats.duration)); 1991 | 1992 | // pending 1993 | if (stats.pending) { 1994 | fmt = color('pending', ' ') 1995 | + color('pending', ' %d %s pending'); 1996 | 1997 | console.log(fmt, stats.pending, pluralize(stats.pending)); 1998 | } 1999 | 2000 | console.log(); 2001 | }; 2002 | 2003 | /** 2004 | * Pad the given `str` to `len`. 2005 | * 2006 | * @param {String} str 2007 | * @param {String} len 2008 | * @return {String} 2009 | * @api private 2010 | */ 2011 | 2012 | function pad(str, len) { 2013 | str = String(str); 2014 | return Array(len - str.length + 1).join(' ') + str; 2015 | } 2016 | 2017 | /** 2018 | * Return a character diff for `err`. 2019 | * 2020 | * @param {Error} err 2021 | * @return {String} 2022 | * @api private 2023 | */ 2024 | 2025 | function errorDiff(err, type, escape) { 2026 | return diff['diff' + type](err.actual, err.expected).map(function(str){ 2027 | if (escape) { 2028 | str.value = str.value 2029 | .replace(/\t/g, '') 2030 | .replace(/\r/g, '') 2031 | .replace(/\n/g, '\n'); 2032 | } 2033 | if (str.added) return colorLines('diff added', str.value); 2034 | if (str.removed) return colorLines('diff removed', str.value); 2035 | return str.value; 2036 | }).join(''); 2037 | } 2038 | 2039 | /** 2040 | * Color lines for `str`, using the color `name`. 2041 | * 2042 | * @param {String} name 2043 | * @param {String} str 2044 | * @return {String} 2045 | * @api private 2046 | */ 2047 | 2048 | function colorLines(name, str) { 2049 | return str.split('\n').map(function(str){ 2050 | return color(name, str); 2051 | }).join('\n'); 2052 | } 2053 | 2054 | }); // module: reporters/base.js 2055 | 2056 | require.register("reporters/doc.js", function(module, exports, require){ 2057 | 2058 | /** 2059 | * Module dependencies. 2060 | */ 2061 | 2062 | var Base = require('./base') 2063 | , utils = require('../utils'); 2064 | 2065 | /** 2066 | * Expose `Doc`. 2067 | */ 2068 | 2069 | exports = module.exports = Doc; 2070 | 2071 | /** 2072 | * Initialize a new `Doc` reporter. 2073 | * 2074 | * @param {Runner} runner 2075 | * @api public 2076 | */ 2077 | 2078 | function Doc(runner) { 2079 | Base.call(this, runner); 2080 | 2081 | var self = this 2082 | , stats = this.stats 2083 | , total = runner.total 2084 | , indents = 2; 2085 | 2086 | function indent() { 2087 | return Array(indents).join(' '); 2088 | } 2089 | 2090 | runner.on('suite', function(suite){ 2091 | if (suite.root) return; 2092 | ++indents; 2093 | console.log('%s
', indent()); 2094 | ++indents; 2095 | console.log('%s

%s

', indent(), utils.escape(suite.title)); 2096 | console.log('%s
', indent()); 2097 | }); 2098 | 2099 | runner.on('suite end', function(suite){ 2100 | if (suite.root) return; 2101 | console.log('%s
', indent()); 2102 | --indents; 2103 | console.log('%s
', indent()); 2104 | --indents; 2105 | }); 2106 | 2107 | runner.on('pass', function(test){ 2108 | console.log('%s
%s
', indent(), utils.escape(test.title)); 2109 | var code = utils.escape(utils.clean(test.fn.toString())); 2110 | console.log('%s
%s
', indent(), code); 2111 | }); 2112 | } 2113 | 2114 | }); // module: reporters/doc.js 2115 | 2116 | require.register("reporters/dot.js", function(module, exports, require){ 2117 | 2118 | /** 2119 | * Module dependencies. 2120 | */ 2121 | 2122 | var Base = require('./base') 2123 | , color = Base.color; 2124 | 2125 | /** 2126 | * Expose `Dot`. 2127 | */ 2128 | 2129 | exports = module.exports = Dot; 2130 | 2131 | /** 2132 | * Initialize a new `Dot` matrix test reporter. 2133 | * 2134 | * @param {Runner} runner 2135 | * @api public 2136 | */ 2137 | 2138 | function Dot(runner) { 2139 | Base.call(this, runner); 2140 | 2141 | var self = this 2142 | , stats = this.stats 2143 | , width = Base.window.width * .75 | 0 2144 | , n = 0; 2145 | 2146 | runner.on('start', function(){ 2147 | process.stdout.write('\n '); 2148 | }); 2149 | 2150 | runner.on('pending', function(test){ 2151 | process.stdout.write(color('pending', Base.symbols.dot)); 2152 | }); 2153 | 2154 | runner.on('pass', function(test){ 2155 | if (++n % width == 0) process.stdout.write('\n '); 2156 | if ('slow' == test.speed) { 2157 | process.stdout.write(color('bright yellow', Base.symbols.dot)); 2158 | } else { 2159 | process.stdout.write(color(test.speed, Base.symbols.dot)); 2160 | } 2161 | }); 2162 | 2163 | runner.on('fail', function(test, err){ 2164 | if (++n % width == 0) process.stdout.write('\n '); 2165 | process.stdout.write(color('fail', Base.symbols.dot)); 2166 | }); 2167 | 2168 | runner.on('end', function(){ 2169 | console.log(); 2170 | self.epilogue(); 2171 | }); 2172 | } 2173 | 2174 | /** 2175 | * Inherit from `Base.prototype`. 2176 | */ 2177 | 2178 | function F(){}; 2179 | F.prototype = Base.prototype; 2180 | Dot.prototype = new F; 2181 | Dot.prototype.constructor = Dot; 2182 | 2183 | }); // module: reporters/dot.js 2184 | 2185 | require.register("reporters/html-cov.js", function(module, exports, require){ 2186 | 2187 | /** 2188 | * Module dependencies. 2189 | */ 2190 | 2191 | var JSONCov = require('./json-cov') 2192 | , fs = require('browser/fs'); 2193 | 2194 | /** 2195 | * Expose `HTMLCov`. 2196 | */ 2197 | 2198 | exports = module.exports = HTMLCov; 2199 | 2200 | /** 2201 | * Initialize a new `JsCoverage` reporter. 2202 | * 2203 | * @param {Runner} runner 2204 | * @api public 2205 | */ 2206 | 2207 | function HTMLCov(runner) { 2208 | var jade = require('jade') 2209 | , file = __dirname + '/templates/coverage.jade' 2210 | , str = fs.readFileSync(file, 'utf8') 2211 | , fn = jade.compile(str, { filename: file }) 2212 | , self = this; 2213 | 2214 | JSONCov.call(this, runner, false); 2215 | 2216 | runner.on('end', function(){ 2217 | process.stdout.write(fn({ 2218 | cov: self.cov 2219 | , coverageClass: coverageClass 2220 | })); 2221 | }); 2222 | } 2223 | 2224 | /** 2225 | * Return coverage class for `n`. 2226 | * 2227 | * @return {String} 2228 | * @api private 2229 | */ 2230 | 2231 | function coverageClass(n) { 2232 | if (n >= 75) return 'high'; 2233 | if (n >= 50) return 'medium'; 2234 | if (n >= 25) return 'low'; 2235 | return 'terrible'; 2236 | } 2237 | }); // module: reporters/html-cov.js 2238 | 2239 | require.register("reporters/html.js", function(module, exports, require){ 2240 | 2241 | /** 2242 | * Module dependencies. 2243 | */ 2244 | 2245 | var Base = require('./base') 2246 | , utils = require('../utils') 2247 | , Progress = require('../browser/progress') 2248 | , escape = utils.escape; 2249 | 2250 | /** 2251 | * Save timer references to avoid Sinon interfering (see GH-237). 2252 | */ 2253 | 2254 | var Date = global.Date 2255 | , setTimeout = global.setTimeout 2256 | , setInterval = global.setInterval 2257 | , clearTimeout = global.clearTimeout 2258 | , clearInterval = global.clearInterval; 2259 | 2260 | /** 2261 | * Expose `Doc`. 2262 | */ 2263 | 2264 | exports = module.exports = HTML; 2265 | 2266 | /** 2267 | * Stats template. 2268 | */ 2269 | 2270 | var statsTemplate = '
    ' 2271 | + '
  • ' 2272 | + '
  • passes: 0
  • ' 2273 | + '
  • failures: 0
  • ' 2274 | + '
  • duration: 0s
  • ' 2275 | + '
'; 2276 | 2277 | /** 2278 | * Initialize a new `Doc` reporter. 2279 | * 2280 | * @param {Runner} runner 2281 | * @api public 2282 | */ 2283 | 2284 | function HTML(runner, root) { 2285 | Base.call(this, runner); 2286 | 2287 | var self = this 2288 | , stats = this.stats 2289 | , total = runner.total 2290 | , stat = fragment(statsTemplate) 2291 | , items = stat.getElementsByTagName('li') 2292 | , passes = items[1].getElementsByTagName('em')[0] 2293 | , passesLink = items[1].getElementsByTagName('a')[0] 2294 | , failures = items[2].getElementsByTagName('em')[0] 2295 | , failuresLink = items[2].getElementsByTagName('a')[0] 2296 | , duration = items[3].getElementsByTagName('em')[0] 2297 | , canvas = stat.getElementsByTagName('canvas')[0] 2298 | , report = fragment('
    ') 2299 | , stack = [report] 2300 | , progress 2301 | , ctx 2302 | 2303 | root = root || document.getElementById('mocha'); 2304 | 2305 | if (canvas.getContext) { 2306 | var ratio = window.devicePixelRatio || 1; 2307 | canvas.style.width = canvas.width; 2308 | canvas.style.height = canvas.height; 2309 | canvas.width *= ratio; 2310 | canvas.height *= ratio; 2311 | ctx = canvas.getContext('2d'); 2312 | ctx.scale(ratio, ratio); 2313 | progress = new Progress; 2314 | } 2315 | 2316 | if (!root) return error('#mocha div missing, add it to your document'); 2317 | 2318 | // pass toggle 2319 | on(passesLink, 'click', function(){ 2320 | unhide(); 2321 | var name = /pass/.test(report.className) ? '' : ' pass'; 2322 | report.className = report.className.replace(/fail|pass/g, '') + name; 2323 | if (report.className.trim()) hideSuitesWithout('test pass'); 2324 | }); 2325 | 2326 | // failure toggle 2327 | on(failuresLink, 'click', function(){ 2328 | unhide(); 2329 | var name = /fail/.test(report.className) ? '' : ' fail'; 2330 | report.className = report.className.replace(/fail|pass/g, '') + name; 2331 | if (report.className.trim()) hideSuitesWithout('test fail'); 2332 | }); 2333 | 2334 | root.appendChild(stat); 2335 | root.appendChild(report); 2336 | 2337 | if (progress) progress.size(40); 2338 | 2339 | runner.on('suite', function(suite){ 2340 | if (suite.root) return; 2341 | 2342 | // suite 2343 | var url = '?grep=' + encodeURIComponent(suite.fullTitle()); 2344 | var el = fragment('
  • %s

  • ', url, escape(suite.title)); 2345 | 2346 | // container 2347 | stack[0].appendChild(el); 2348 | stack.unshift(document.createElement('ul')); 2349 | el.appendChild(stack[0]); 2350 | }); 2351 | 2352 | runner.on('suite end', function(suite){ 2353 | if (suite.root) return; 2354 | stack.shift(); 2355 | }); 2356 | 2357 | runner.on('fail', function(test, err){ 2358 | if ('hook' == test.type) runner.emit('test end', test); 2359 | }); 2360 | 2361 | runner.on('test end', function(test){ 2362 | // TODO: add to stats 2363 | var percent = stats.tests / this.total * 100 | 0; 2364 | if (progress) progress.update(percent).draw(ctx); 2365 | 2366 | // update stats 2367 | var ms = new Date - stats.start; 2368 | text(passes, stats.passes); 2369 | text(failures, stats.failures); 2370 | text(duration, (ms / 1000).toFixed(2)); 2371 | 2372 | // test 2373 | if ('passed' == test.state) { 2374 | var el = fragment('
  • %e%ems

  • ', test.speed, test.title, test.duration, encodeURIComponent(test.fullTitle())); 2375 | } else if (test.pending) { 2376 | var el = fragment('
  • %e

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

  • ', test.title, encodeURIComponent(test.fullTitle())); 2379 | var str = test.err.stack || test.err.toString(); 2380 | 2381 | // FF / Opera do not add the message 2382 | if (!~str.indexOf(test.err.message)) { 2383 | str = test.err.message + '\n' + str; 2384 | } 2385 | 2386 | // <=IE7 stringifies to [Object Error]. Since it can be overloaded, we 2387 | // check for the result of the stringifying. 2388 | if ('[object Error]' == str) str = test.err.message; 2389 | 2390 | // Safari doesn't give you a stack. Let's at least provide a source line. 2391 | if (!test.err.stack && test.err.sourceURL && test.err.line !== undefined) { 2392 | str += "\n(" + test.err.sourceURL + ":" + test.err.line + ")"; 2393 | } 2394 | 2395 | el.appendChild(fragment('
    %e
    ', str)); 2396 | } 2397 | 2398 | // toggle code 2399 | // TODO: defer 2400 | if (!test.pending) { 2401 | var h2 = el.getElementsByTagName('h2')[0]; 2402 | 2403 | on(h2, 'click', function(){ 2404 | pre.style.display = 'none' == pre.style.display 2405 | ? 'block' 2406 | : 'none'; 2407 | }); 2408 | 2409 | var pre = fragment('
    %e
    ', utils.clean(test.fn.toString())); 2410 | el.appendChild(pre); 2411 | pre.style.display = 'none'; 2412 | } 2413 | 2414 | // Don't call .appendChild if #mocha-report was already .shift()'ed off the stack. 2415 | if (stack[0]) stack[0].appendChild(el); 2416 | }); 2417 | } 2418 | 2419 | /** 2420 | * Display error `msg`. 2421 | */ 2422 | 2423 | function error(msg) { 2424 | document.body.appendChild(fragment('
    %s
    ', msg)); 2425 | } 2426 | 2427 | /** 2428 | * Return a DOM fragment from `html`. 2429 | */ 2430 | 2431 | function fragment(html) { 2432 | var args = arguments 2433 | , div = document.createElement('div') 2434 | , i = 1; 2435 | 2436 | div.innerHTML = html.replace(/%([se])/g, function(_, type){ 2437 | switch (type) { 2438 | case 's': return String(args[i++]); 2439 | case 'e': return escape(args[i++]); 2440 | } 2441 | }); 2442 | 2443 | return div.firstChild; 2444 | } 2445 | 2446 | /** 2447 | * Check for suites that do not have elements 2448 | * with `classname`, and hide them. 2449 | */ 2450 | 2451 | function hideSuitesWithout(classname) { 2452 | var suites = document.getElementsByClassName('suite'); 2453 | for (var i = 0; i < suites.length; i++) { 2454 | var els = suites[i].getElementsByClassName(classname); 2455 | if (0 == els.length) suites[i].className += ' hidden'; 2456 | } 2457 | } 2458 | 2459 | /** 2460 | * Unhide .hidden suites. 2461 | */ 2462 | 2463 | function unhide() { 2464 | var els = document.getElementsByClassName('suite hidden'); 2465 | for (var i = 0; i < els.length; ++i) { 2466 | els[i].className = els[i].className.replace('suite hidden', 'suite'); 2467 | } 2468 | } 2469 | 2470 | /** 2471 | * Set `el` text to `str`. 2472 | */ 2473 | 2474 | function text(el, str) { 2475 | if (el.textContent) { 2476 | el.textContent = str; 2477 | } else { 2478 | el.innerText = str; 2479 | } 2480 | } 2481 | 2482 | /** 2483 | * Listen on `event` with callback `fn`. 2484 | */ 2485 | 2486 | function on(el, event, fn) { 2487 | if (el.addEventListener) { 2488 | el.addEventListener(event, fn, false); 2489 | } else { 2490 | el.attachEvent('on' + event, fn); 2491 | } 2492 | } 2493 | 2494 | }); // module: reporters/html.js 2495 | 2496 | require.register("reporters/index.js", function(module, exports, require){ 2497 | 2498 | exports.Base = require('./base'); 2499 | exports.Dot = require('./dot'); 2500 | exports.Doc = require('./doc'); 2501 | exports.TAP = require('./tap'); 2502 | exports.JSON = require('./json'); 2503 | exports.HTML = require('./html'); 2504 | exports.List = require('./list'); 2505 | exports.Min = require('./min'); 2506 | exports.Spec = require('./spec'); 2507 | exports.Nyan = require('./nyan'); 2508 | exports.XUnit = require('./xunit'); 2509 | exports.Markdown = require('./markdown'); 2510 | exports.Progress = require('./progress'); 2511 | exports.Landing = require('./landing'); 2512 | exports.JSONCov = require('./json-cov'); 2513 | exports.HTMLCov = require('./html-cov'); 2514 | exports.JSONStream = require('./json-stream'); 2515 | exports.Teamcity = require('./teamcity'); 2516 | 2517 | }); // module: reporters/index.js 2518 | 2519 | require.register("reporters/json-cov.js", function(module, exports, require){ 2520 | 2521 | /** 2522 | * Module dependencies. 2523 | */ 2524 | 2525 | var Base = require('./base'); 2526 | 2527 | /** 2528 | * Expose `JSONCov`. 2529 | */ 2530 | 2531 | exports = module.exports = JSONCov; 2532 | 2533 | /** 2534 | * Initialize a new `JsCoverage` reporter. 2535 | * 2536 | * @param {Runner} runner 2537 | * @param {Boolean} output 2538 | * @api public 2539 | */ 2540 | 2541 | function JSONCov(runner, output) { 2542 | var self = this 2543 | , output = 1 == arguments.length ? true : output; 2544 | 2545 | Base.call(this, runner); 2546 | 2547 | var tests = [] 2548 | , failures = [] 2549 | , passes = []; 2550 | 2551 | runner.on('test end', function(test){ 2552 | tests.push(test); 2553 | }); 2554 | 2555 | runner.on('pass', function(test){ 2556 | passes.push(test); 2557 | }); 2558 | 2559 | runner.on('fail', function(test){ 2560 | failures.push(test); 2561 | }); 2562 | 2563 | runner.on('end', function(){ 2564 | var cov = global._$jscoverage || {}; 2565 | var result = self.cov = map(cov); 2566 | result.stats = self.stats; 2567 | result.tests = tests.map(clean); 2568 | result.failures = failures.map(clean); 2569 | result.passes = passes.map(clean); 2570 | if (!output) return; 2571 | process.stdout.write(JSON.stringify(result, null, 2 )); 2572 | }); 2573 | } 2574 | 2575 | /** 2576 | * Map jscoverage data to a JSON structure 2577 | * suitable for reporting. 2578 | * 2579 | * @param {Object} cov 2580 | * @return {Object} 2581 | * @api private 2582 | */ 2583 | 2584 | function map(cov) { 2585 | var ret = { 2586 | instrumentation: 'node-jscoverage' 2587 | , sloc: 0 2588 | , hits: 0 2589 | , misses: 0 2590 | , coverage: 0 2591 | , files: [] 2592 | }; 2593 | 2594 | for (var filename in cov) { 2595 | var data = coverage(filename, cov[filename]); 2596 | ret.files.push(data); 2597 | ret.hits += data.hits; 2598 | ret.misses += data.misses; 2599 | ret.sloc += data.sloc; 2600 | } 2601 | 2602 | ret.files.sort(function(a, b) { 2603 | return a.filename.localeCompare(b.filename); 2604 | }); 2605 | 2606 | if (ret.sloc > 0) { 2607 | ret.coverage = (ret.hits / ret.sloc) * 100; 2608 | } 2609 | 2610 | return ret; 2611 | }; 2612 | 2613 | /** 2614 | * Map jscoverage data for a single source file 2615 | * to a JSON structure suitable for reporting. 2616 | * 2617 | * @param {String} filename name of the source file 2618 | * @param {Object} data jscoverage coverage data 2619 | * @return {Object} 2620 | * @api private 2621 | */ 2622 | 2623 | function coverage(filename, data) { 2624 | var ret = { 2625 | filename: filename, 2626 | coverage: 0, 2627 | hits: 0, 2628 | misses: 0, 2629 | sloc: 0, 2630 | source: {} 2631 | }; 2632 | 2633 | data.source.forEach(function(line, num){ 2634 | num++; 2635 | 2636 | if (data[num] === 0) { 2637 | ret.misses++; 2638 | ret.sloc++; 2639 | } else if (data[num] !== undefined) { 2640 | ret.hits++; 2641 | ret.sloc++; 2642 | } 2643 | 2644 | ret.source[num] = { 2645 | source: line 2646 | , coverage: data[num] === undefined 2647 | ? '' 2648 | : data[num] 2649 | }; 2650 | }); 2651 | 2652 | ret.coverage = ret.hits / ret.sloc * 100; 2653 | 2654 | return ret; 2655 | } 2656 | 2657 | /** 2658 | * Return a plain-object representation of `test` 2659 | * free of cyclic properties etc. 2660 | * 2661 | * @param {Object} test 2662 | * @return {Object} 2663 | * @api private 2664 | */ 2665 | 2666 | function clean(test) { 2667 | return { 2668 | title: test.title 2669 | , fullTitle: test.fullTitle() 2670 | , duration: test.duration 2671 | } 2672 | } 2673 | 2674 | }); // module: reporters/json-cov.js 2675 | 2676 | require.register("reporters/json-stream.js", function(module, exports, require){ 2677 | 2678 | /** 2679 | * Module dependencies. 2680 | */ 2681 | 2682 | var Base = require('./base') 2683 | , color = Base.color; 2684 | 2685 | /** 2686 | * Expose `List`. 2687 | */ 2688 | 2689 | exports = module.exports = List; 2690 | 2691 | /** 2692 | * Initialize a new `List` test reporter. 2693 | * 2694 | * @param {Runner} runner 2695 | * @api public 2696 | */ 2697 | 2698 | function List(runner) { 2699 | Base.call(this, runner); 2700 | 2701 | var self = this 2702 | , stats = this.stats 2703 | , total = runner.total; 2704 | 2705 | runner.on('start', function(){ 2706 | console.log(JSON.stringify(['start', { total: total }])); 2707 | }); 2708 | 2709 | runner.on('pass', function(test){ 2710 | console.log(JSON.stringify(['pass', clean(test)])); 2711 | }); 2712 | 2713 | runner.on('fail', function(test, err){ 2714 | console.log(JSON.stringify(['fail', clean(test)])); 2715 | }); 2716 | 2717 | runner.on('end', function(){ 2718 | process.stdout.write(JSON.stringify(['end', self.stats])); 2719 | }); 2720 | } 2721 | 2722 | /** 2723 | * Return a plain-object representation of `test` 2724 | * free of cyclic properties etc. 2725 | * 2726 | * @param {Object} test 2727 | * @return {Object} 2728 | * @api private 2729 | */ 2730 | 2731 | function clean(test) { 2732 | return { 2733 | title: test.title 2734 | , fullTitle: test.fullTitle() 2735 | , duration: test.duration 2736 | } 2737 | } 2738 | }); // module: reporters/json-stream.js 2739 | 2740 | require.register("reporters/json.js", function(module, exports, require){ 2741 | 2742 | /** 2743 | * Module dependencies. 2744 | */ 2745 | 2746 | var Base = require('./base') 2747 | , cursor = Base.cursor 2748 | , color = Base.color; 2749 | 2750 | /** 2751 | * Expose `JSON`. 2752 | */ 2753 | 2754 | exports = module.exports = JSONReporter; 2755 | 2756 | /** 2757 | * Initialize a new `JSON` reporter. 2758 | * 2759 | * @param {Runner} runner 2760 | * @api public 2761 | */ 2762 | 2763 | function JSONReporter(runner) { 2764 | var self = this; 2765 | Base.call(this, runner); 2766 | 2767 | var tests = [] 2768 | , failures = [] 2769 | , passes = []; 2770 | 2771 | runner.on('test end', function(test){ 2772 | tests.push(test); 2773 | }); 2774 | 2775 | runner.on('pass', function(test){ 2776 | passes.push(test); 2777 | }); 2778 | 2779 | runner.on('fail', function(test){ 2780 | failures.push(test); 2781 | }); 2782 | 2783 | runner.on('end', function(){ 2784 | var obj = { 2785 | stats: self.stats 2786 | , tests: tests.map(clean) 2787 | , failures: failures.map(clean) 2788 | , passes: passes.map(clean) 2789 | }; 2790 | 2791 | process.stdout.write(JSON.stringify(obj, null, 2)); 2792 | }); 2793 | } 2794 | 2795 | /** 2796 | * Return a plain-object representation of `test` 2797 | * free of cyclic properties etc. 2798 | * 2799 | * @param {Object} test 2800 | * @return {Object} 2801 | * @api private 2802 | */ 2803 | 2804 | function clean(test) { 2805 | return { 2806 | title: test.title 2807 | , fullTitle: test.fullTitle() 2808 | , duration: test.duration 2809 | } 2810 | } 2811 | }); // module: reporters/json.js 2812 | 2813 | require.register("reporters/landing.js", function(module, exports, require){ 2814 | 2815 | /** 2816 | * Module dependencies. 2817 | */ 2818 | 2819 | var Base = require('./base') 2820 | , cursor = Base.cursor 2821 | , color = Base.color; 2822 | 2823 | /** 2824 | * Expose `Landing`. 2825 | */ 2826 | 2827 | exports = module.exports = Landing; 2828 | 2829 | /** 2830 | * Airplane color. 2831 | */ 2832 | 2833 | Base.colors.plane = 0; 2834 | 2835 | /** 2836 | * Airplane crash color. 2837 | */ 2838 | 2839 | Base.colors['plane crash'] = 31; 2840 | 2841 | /** 2842 | * Runway color. 2843 | */ 2844 | 2845 | Base.colors.runway = 90; 2846 | 2847 | /** 2848 | * Initialize a new `Landing` reporter. 2849 | * 2850 | * @param {Runner} runner 2851 | * @api public 2852 | */ 2853 | 2854 | function Landing(runner) { 2855 | Base.call(this, runner); 2856 | 2857 | var self = this 2858 | , stats = this.stats 2859 | , width = Base.window.width * .75 | 0 2860 | , total = runner.total 2861 | , stream = process.stdout 2862 | , plane = color('plane', '✈') 2863 | , crashed = -1 2864 | , n = 0; 2865 | 2866 | function runway() { 2867 | var buf = Array(width).join('-'); 2868 | return ' ' + color('runway', buf); 2869 | } 2870 | 2871 | runner.on('start', function(){ 2872 | stream.write('\n '); 2873 | cursor.hide(); 2874 | }); 2875 | 2876 | runner.on('test end', function(test){ 2877 | // check if the plane crashed 2878 | var col = -1 == crashed 2879 | ? width * ++n / total | 0 2880 | : crashed; 2881 | 2882 | // show the crash 2883 | if ('failed' == test.state) { 2884 | plane = color('plane crash', '✈'); 2885 | crashed = col; 2886 | } 2887 | 2888 | // render landing strip 2889 | stream.write('\u001b[4F\n\n'); 2890 | stream.write(runway()); 2891 | stream.write('\n '); 2892 | stream.write(color('runway', Array(col).join('⋅'))); 2893 | stream.write(plane) 2894 | stream.write(color('runway', Array(width - col).join('⋅') + '\n')); 2895 | stream.write(runway()); 2896 | stream.write('\u001b[0m'); 2897 | }); 2898 | 2899 | runner.on('end', function(){ 2900 | cursor.show(); 2901 | console.log(); 2902 | self.epilogue(); 2903 | }); 2904 | } 2905 | 2906 | /** 2907 | * Inherit from `Base.prototype`. 2908 | */ 2909 | 2910 | function F(){}; 2911 | F.prototype = Base.prototype; 2912 | Landing.prototype = new F; 2913 | Landing.prototype.constructor = Landing; 2914 | 2915 | }); // module: reporters/landing.js 2916 | 2917 | require.register("reporters/list.js", function(module, exports, require){ 2918 | 2919 | /** 2920 | * Module dependencies. 2921 | */ 2922 | 2923 | var Base = require('./base') 2924 | , cursor = Base.cursor 2925 | , color = Base.color; 2926 | 2927 | /** 2928 | * Expose `List`. 2929 | */ 2930 | 2931 | exports = module.exports = List; 2932 | 2933 | /** 2934 | * Initialize a new `List` test reporter. 2935 | * 2936 | * @param {Runner} runner 2937 | * @api public 2938 | */ 2939 | 2940 | function List(runner) { 2941 | Base.call(this, runner); 2942 | 2943 | var self = this 2944 | , stats = this.stats 2945 | , n = 0; 2946 | 2947 | runner.on('start', function(){ 2948 | console.log(); 2949 | }); 2950 | 2951 | runner.on('test', function(test){ 2952 | process.stdout.write(color('pass', ' ' + test.fullTitle() + ': ')); 2953 | }); 2954 | 2955 | runner.on('pending', function(test){ 2956 | var fmt = color('checkmark', ' -') 2957 | + color('pending', ' %s'); 2958 | console.log(fmt, test.fullTitle()); 2959 | }); 2960 | 2961 | runner.on('pass', function(test){ 2962 | var fmt = color('checkmark', ' '+Base.symbols.dot) 2963 | + color('pass', ' %s: ') 2964 | + color(test.speed, '%dms'); 2965 | cursor.CR(); 2966 | console.log(fmt, test.fullTitle(), test.duration); 2967 | }); 2968 | 2969 | runner.on('fail', function(test, err){ 2970 | cursor.CR(); 2971 | console.log(color('fail', ' %d) %s'), ++n, test.fullTitle()); 2972 | }); 2973 | 2974 | runner.on('end', self.epilogue.bind(self)); 2975 | } 2976 | 2977 | /** 2978 | * Inherit from `Base.prototype`. 2979 | */ 2980 | 2981 | function F(){}; 2982 | F.prototype = Base.prototype; 2983 | List.prototype = new F; 2984 | List.prototype.constructor = List; 2985 | 2986 | 2987 | }); // module: reporters/list.js 2988 | 2989 | require.register("reporters/markdown.js", function(module, exports, require){ 2990 | /** 2991 | * Module dependencies. 2992 | */ 2993 | 2994 | var Base = require('./base') 2995 | , utils = require('../utils'); 2996 | 2997 | /** 2998 | * Expose `Markdown`. 2999 | */ 3000 | 3001 | exports = module.exports = Markdown; 3002 | 3003 | /** 3004 | * Initialize a new `Markdown` reporter. 3005 | * 3006 | * @param {Runner} runner 3007 | * @api public 3008 | */ 3009 | 3010 | function Markdown(runner) { 3011 | Base.call(this, runner); 3012 | 3013 | var self = this 3014 | , stats = this.stats 3015 | , level = 0 3016 | , buf = ''; 3017 | 3018 | function title(str) { 3019 | return Array(level).join('#') + ' ' + str; 3020 | } 3021 | 3022 | function indent() { 3023 | return Array(level).join(' '); 3024 | } 3025 | 3026 | function mapTOC(suite, obj) { 3027 | var ret = obj; 3028 | obj = obj[suite.title] = obj[suite.title] || { suite: suite }; 3029 | suite.suites.forEach(function(suite){ 3030 | mapTOC(suite, obj); 3031 | }); 3032 | return ret; 3033 | } 3034 | 3035 | function stringifyTOC(obj, level) { 3036 | ++level; 3037 | var buf = ''; 3038 | var link; 3039 | for (var key in obj) { 3040 | if ('suite' == key) continue; 3041 | if (key) link = ' - [' + key + '](#' + utils.slug(obj[key].suite.fullTitle()) + ')\n'; 3042 | if (key) buf += Array(level).join(' ') + link; 3043 | buf += stringifyTOC(obj[key], level); 3044 | } 3045 | --level; 3046 | return buf; 3047 | } 3048 | 3049 | function generateTOC(suite) { 3050 | var obj = mapTOC(suite, {}); 3051 | return stringifyTOC(obj, 0); 3052 | } 3053 | 3054 | generateTOC(runner.suite); 3055 | 3056 | runner.on('suite', function(suite){ 3057 | ++level; 3058 | var slug = utils.slug(suite.fullTitle()); 3059 | buf += '' + '\n'; 3060 | buf += title(suite.title) + '\n'; 3061 | }); 3062 | 3063 | runner.on('suite end', function(suite){ 3064 | --level; 3065 | }); 3066 | 3067 | runner.on('pass', function(test){ 3068 | var code = utils.clean(test.fn.toString()); 3069 | buf += test.title + '.\n'; 3070 | buf += '\n```js\n'; 3071 | buf += code + '\n'; 3072 | buf += '```\n\n'; 3073 | }); 3074 | 3075 | runner.on('end', function(){ 3076 | process.stdout.write('# TOC\n'); 3077 | process.stdout.write(generateTOC(runner.suite)); 3078 | process.stdout.write(buf); 3079 | }); 3080 | } 3081 | }); // module: reporters/markdown.js 3082 | 3083 | require.register("reporters/min.js", function(module, exports, require){ 3084 | 3085 | /** 3086 | * Module dependencies. 3087 | */ 3088 | 3089 | var Base = require('./base'); 3090 | 3091 | /** 3092 | * Expose `Min`. 3093 | */ 3094 | 3095 | exports = module.exports = Min; 3096 | 3097 | /** 3098 | * Initialize a new `Min` minimal test reporter (best used with --watch). 3099 | * 3100 | * @param {Runner} runner 3101 | * @api public 3102 | */ 3103 | 3104 | function Min(runner) { 3105 | Base.call(this, runner); 3106 | 3107 | runner.on('start', function(){ 3108 | // clear screen 3109 | process.stdout.write('\u001b[2J'); 3110 | // set cursor position 3111 | process.stdout.write('\u001b[1;3H'); 3112 | }); 3113 | 3114 | runner.on('end', this.epilogue.bind(this)); 3115 | } 3116 | 3117 | /** 3118 | * Inherit from `Base.prototype`. 3119 | */ 3120 | 3121 | function F(){}; 3122 | F.prototype = Base.prototype; 3123 | Min.prototype = new F; 3124 | Min.prototype.constructor = Min; 3125 | 3126 | 3127 | }); // module: reporters/min.js 3128 | 3129 | require.register("reporters/nyan.js", function(module, exports, require){ 3130 | /** 3131 | * Module dependencies. 3132 | */ 3133 | 3134 | var Base = require('./base') 3135 | , color = Base.color; 3136 | 3137 | /** 3138 | * Expose `Dot`. 3139 | */ 3140 | 3141 | exports = module.exports = NyanCat; 3142 | 3143 | /** 3144 | * Initialize a new `Dot` matrix test reporter. 3145 | * 3146 | * @param {Runner} runner 3147 | * @api public 3148 | */ 3149 | 3150 | function NyanCat(runner) { 3151 | Base.call(this, runner); 3152 | 3153 | var self = this 3154 | , stats = this.stats 3155 | , width = Base.window.width * .75 | 0 3156 | , rainbowColors = this.rainbowColors = self.generateColors() 3157 | , colorIndex = this.colorIndex = 0 3158 | , numerOfLines = this.numberOfLines = 4 3159 | , trajectories = this.trajectories = [[], [], [], []] 3160 | , nyanCatWidth = this.nyanCatWidth = 11 3161 | , trajectoryWidthMax = this.trajectoryWidthMax = (width - nyanCatWidth) 3162 | , scoreboardWidth = this.scoreboardWidth = 5 3163 | , tick = this.tick = 0 3164 | , n = 0; 3165 | 3166 | runner.on('start', function(){ 3167 | Base.cursor.hide(); 3168 | self.draw('start'); 3169 | }); 3170 | 3171 | runner.on('pending', function(test){ 3172 | self.draw('pending'); 3173 | }); 3174 | 3175 | runner.on('pass', function(test){ 3176 | self.draw('pass'); 3177 | }); 3178 | 3179 | runner.on('fail', function(test, err){ 3180 | self.draw('fail'); 3181 | }); 3182 | 3183 | runner.on('end', function(){ 3184 | Base.cursor.show(); 3185 | for (var i = 0; i < self.numberOfLines; i++) write('\n'); 3186 | self.epilogue(); 3187 | }); 3188 | } 3189 | 3190 | /** 3191 | * Draw the nyan cat with runner `status`. 3192 | * 3193 | * @param {String} status 3194 | * @api private 3195 | */ 3196 | 3197 | NyanCat.prototype.draw = function(status){ 3198 | this.appendRainbow(); 3199 | this.drawScoreboard(); 3200 | this.drawRainbow(); 3201 | this.drawNyanCat(status); 3202 | this.tick = !this.tick; 3203 | }; 3204 | 3205 | /** 3206 | * Draw the "scoreboard" showing the number 3207 | * of passes, failures and pending tests. 3208 | * 3209 | * @api private 3210 | */ 3211 | 3212 | NyanCat.prototype.drawScoreboard = function(){ 3213 | var stats = this.stats; 3214 | var colors = Base.colors; 3215 | 3216 | function draw(color, n) { 3217 | write(' '); 3218 | write('\u001b[' + color + 'm' + n + '\u001b[0m'); 3219 | write('\n'); 3220 | } 3221 | 3222 | draw(colors.green, stats.passes); 3223 | draw(colors.fail, stats.failures); 3224 | draw(colors.pending, stats.pending); 3225 | write('\n'); 3226 | 3227 | this.cursorUp(this.numberOfLines); 3228 | }; 3229 | 3230 | /** 3231 | * Append the rainbow. 3232 | * 3233 | * @api private 3234 | */ 3235 | 3236 | NyanCat.prototype.appendRainbow = function(){ 3237 | var segment = this.tick ? '_' : '-'; 3238 | var rainbowified = this.rainbowify(segment); 3239 | 3240 | for (var index = 0; index < this.numberOfLines; index++) { 3241 | var trajectory = this.trajectories[index]; 3242 | if (trajectory.length >= this.trajectoryWidthMax) trajectory.shift(); 3243 | trajectory.push(rainbowified); 3244 | } 3245 | }; 3246 | 3247 | /** 3248 | * Draw the rainbow. 3249 | * 3250 | * @api private 3251 | */ 3252 | 3253 | NyanCat.prototype.drawRainbow = function(){ 3254 | var self = this; 3255 | 3256 | this.trajectories.forEach(function(line, index) { 3257 | write('\u001b[' + self.scoreboardWidth + 'C'); 3258 | write(line.join('')); 3259 | write('\n'); 3260 | }); 3261 | 3262 | this.cursorUp(this.numberOfLines); 3263 | }; 3264 | 3265 | /** 3266 | * Draw the nyan cat with `status`. 3267 | * 3268 | * @param {String} status 3269 | * @api private 3270 | */ 3271 | 3272 | NyanCat.prototype.drawNyanCat = function(status) { 3273 | var self = this; 3274 | var startWidth = this.scoreboardWidth + this.trajectories[0].length; 3275 | var color = '\u001b[' + startWidth + 'C'; 3276 | var padding = ''; 3277 | 3278 | write(color); 3279 | write('_,------,'); 3280 | write('\n'); 3281 | 3282 | write(color); 3283 | padding = self.tick ? ' ' : ' '; 3284 | write('_|' + padding + '/\\_/\\ '); 3285 | write('\n'); 3286 | 3287 | write(color); 3288 | padding = self.tick ? '_' : '__'; 3289 | var tail = self.tick ? '~' : '^'; 3290 | var face; 3291 | switch (status) { 3292 | case 'pass': 3293 | face = '( ^ .^)'; 3294 | break; 3295 | case 'fail': 3296 | face = '( o .o)'; 3297 | break; 3298 | default: 3299 | face = '( - .-)'; 3300 | } 3301 | write(tail + '|' + padding + face + ' '); 3302 | write('\n'); 3303 | 3304 | write(color); 3305 | padding = self.tick ? ' ' : ' '; 3306 | write(padding + '"" "" '); 3307 | write('\n'); 3308 | 3309 | this.cursorUp(this.numberOfLines); 3310 | }; 3311 | 3312 | /** 3313 | * Move cursor up `n`. 3314 | * 3315 | * @param {Number} n 3316 | * @api private 3317 | */ 3318 | 3319 | NyanCat.prototype.cursorUp = function(n) { 3320 | write('\u001b[' + n + 'A'); 3321 | }; 3322 | 3323 | /** 3324 | * Move cursor down `n`. 3325 | * 3326 | * @param {Number} n 3327 | * @api private 3328 | */ 3329 | 3330 | NyanCat.prototype.cursorDown = function(n) { 3331 | write('\u001b[' + n + 'B'); 3332 | }; 3333 | 3334 | /** 3335 | * Generate rainbow colors. 3336 | * 3337 | * @return {Array} 3338 | * @api private 3339 | */ 3340 | 3341 | NyanCat.prototype.generateColors = function(){ 3342 | var colors = []; 3343 | 3344 | for (var i = 0; i < (6 * 7); i++) { 3345 | var pi3 = Math.floor(Math.PI / 3); 3346 | var n = (i * (1.0 / 6)); 3347 | var r = Math.floor(3 * Math.sin(n) + 3); 3348 | var g = Math.floor(3 * Math.sin(n + 2 * pi3) + 3); 3349 | var b = Math.floor(3 * Math.sin(n + 4 * pi3) + 3); 3350 | colors.push(36 * r + 6 * g + b + 16); 3351 | } 3352 | 3353 | return colors; 3354 | }; 3355 | 3356 | /** 3357 | * Apply rainbow to the given `str`. 3358 | * 3359 | * @param {String} str 3360 | * @return {String} 3361 | * @api private 3362 | */ 3363 | 3364 | NyanCat.prototype.rainbowify = function(str){ 3365 | var color = this.rainbowColors[this.colorIndex % this.rainbowColors.length]; 3366 | this.colorIndex += 1; 3367 | return '\u001b[38;5;' + color + 'm' + str + '\u001b[0m'; 3368 | }; 3369 | 3370 | /** 3371 | * Stdout helper. 3372 | */ 3373 | 3374 | function write(string) { 3375 | process.stdout.write(string); 3376 | } 3377 | 3378 | /** 3379 | * Inherit from `Base.prototype`. 3380 | */ 3381 | 3382 | function F(){}; 3383 | F.prototype = Base.prototype; 3384 | NyanCat.prototype = new F; 3385 | NyanCat.prototype.constructor = NyanCat; 3386 | 3387 | 3388 | }); // module: reporters/nyan.js 3389 | 3390 | require.register("reporters/progress.js", function(module, exports, require){ 3391 | 3392 | /** 3393 | * Module dependencies. 3394 | */ 3395 | 3396 | var Base = require('./base') 3397 | , cursor = Base.cursor 3398 | , color = Base.color; 3399 | 3400 | /** 3401 | * Expose `Progress`. 3402 | */ 3403 | 3404 | exports = module.exports = Progress; 3405 | 3406 | /** 3407 | * General progress bar color. 3408 | */ 3409 | 3410 | Base.colors.progress = 90; 3411 | 3412 | /** 3413 | * Initialize a new `Progress` bar test reporter. 3414 | * 3415 | * @param {Runner} runner 3416 | * @param {Object} options 3417 | * @api public 3418 | */ 3419 | 3420 | function Progress(runner, options) { 3421 | Base.call(this, runner); 3422 | 3423 | var self = this 3424 | , options = options || {} 3425 | , stats = this.stats 3426 | , width = Base.window.width * .50 | 0 3427 | , total = runner.total 3428 | , complete = 0 3429 | , max = Math.max; 3430 | 3431 | // default chars 3432 | options.open = options.open || '['; 3433 | options.complete = options.complete || '▬'; 3434 | options.incomplete = options.incomplete || Base.symbols.dot; 3435 | options.close = options.close || ']'; 3436 | options.verbose = false; 3437 | 3438 | // tests started 3439 | runner.on('start', function(){ 3440 | console.log(); 3441 | cursor.hide(); 3442 | }); 3443 | 3444 | // tests complete 3445 | runner.on('test end', function(){ 3446 | complete++; 3447 | var incomplete = total - complete 3448 | , percent = complete / total 3449 | , n = width * percent | 0 3450 | , i = width - n; 3451 | 3452 | cursor.CR(); 3453 | process.stdout.write('\u001b[J'); 3454 | process.stdout.write(color('progress', ' ' + options.open)); 3455 | process.stdout.write(Array(n).join(options.complete)); 3456 | process.stdout.write(Array(i).join(options.incomplete)); 3457 | process.stdout.write(color('progress', options.close)); 3458 | if (options.verbose) { 3459 | process.stdout.write(color('progress', ' ' + complete + ' of ' + total)); 3460 | } 3461 | }); 3462 | 3463 | // tests are complete, output some stats 3464 | // and the failures if any 3465 | runner.on('end', function(){ 3466 | cursor.show(); 3467 | console.log(); 3468 | self.epilogue(); 3469 | }); 3470 | } 3471 | 3472 | /** 3473 | * Inherit from `Base.prototype`. 3474 | */ 3475 | 3476 | function F(){}; 3477 | F.prototype = Base.prototype; 3478 | Progress.prototype = new F; 3479 | Progress.prototype.constructor = Progress; 3480 | 3481 | 3482 | }); // module: reporters/progress.js 3483 | 3484 | require.register("reporters/spec.js", function(module, exports, require){ 3485 | 3486 | /** 3487 | * Module dependencies. 3488 | */ 3489 | 3490 | var Base = require('./base') 3491 | , cursor = Base.cursor 3492 | , color = Base.color; 3493 | 3494 | /** 3495 | * Expose `Spec`. 3496 | */ 3497 | 3498 | exports = module.exports = Spec; 3499 | 3500 | /** 3501 | * Initialize a new `Spec` test reporter. 3502 | * 3503 | * @param {Runner} runner 3504 | * @api public 3505 | */ 3506 | 3507 | function Spec(runner) { 3508 | Base.call(this, runner); 3509 | 3510 | var self = this 3511 | , stats = this.stats 3512 | , indents = 0 3513 | , n = 0; 3514 | 3515 | function indent() { 3516 | return Array(indents).join(' ') 3517 | } 3518 | 3519 | runner.on('start', function(){ 3520 | console.log(); 3521 | }); 3522 | 3523 | runner.on('suite', function(suite){ 3524 | ++indents; 3525 | console.log(color('suite', '%s%s'), indent(), suite.title); 3526 | }); 3527 | 3528 | runner.on('suite end', function(suite){ 3529 | --indents; 3530 | if (1 == indents) console.log(); 3531 | }); 3532 | 3533 | runner.on('test', function(test){ 3534 | process.stdout.write(indent() + color('pass', ' ◦ ' + test.title + ': ')); 3535 | }); 3536 | 3537 | runner.on('pending', function(test){ 3538 | var fmt = indent() + color('pending', ' - %s'); 3539 | console.log(fmt, test.title); 3540 | }); 3541 | 3542 | runner.on('pass', function(test){ 3543 | if ('fast' == test.speed) { 3544 | var fmt = indent() 3545 | + color('checkmark', ' ' + Base.symbols.ok) 3546 | + color('pass', ' %s '); 3547 | cursor.CR(); 3548 | console.log(fmt, test.title); 3549 | } else { 3550 | var fmt = indent() 3551 | + color('checkmark', ' ' + Base.symbols.ok) 3552 | + color('pass', ' %s ') 3553 | + color(test.speed, '(%dms)'); 3554 | cursor.CR(); 3555 | console.log(fmt, test.title, test.duration); 3556 | } 3557 | }); 3558 | 3559 | runner.on('fail', function(test, err){ 3560 | cursor.CR(); 3561 | console.log(indent() + color('fail', ' %d) %s'), ++n, test.title); 3562 | }); 3563 | 3564 | runner.on('end', self.epilogue.bind(self)); 3565 | } 3566 | 3567 | /** 3568 | * Inherit from `Base.prototype`. 3569 | */ 3570 | 3571 | function F(){}; 3572 | F.prototype = Base.prototype; 3573 | Spec.prototype = new F; 3574 | Spec.prototype.constructor = Spec; 3575 | 3576 | 3577 | }); // module: reporters/spec.js 3578 | 3579 | require.register("reporters/tap.js", function(module, exports, require){ 3580 | 3581 | /** 3582 | * Module dependencies. 3583 | */ 3584 | 3585 | var Base = require('./base') 3586 | , cursor = Base.cursor 3587 | , color = Base.color; 3588 | 3589 | /** 3590 | * Expose `TAP`. 3591 | */ 3592 | 3593 | exports = module.exports = TAP; 3594 | 3595 | /** 3596 | * Initialize a new `TAP` reporter. 3597 | * 3598 | * @param {Runner} runner 3599 | * @api public 3600 | */ 3601 | 3602 | function TAP(runner) { 3603 | Base.call(this, runner); 3604 | 3605 | var self = this 3606 | , stats = this.stats 3607 | , n = 1 3608 | , passes = 0 3609 | , failures = 0; 3610 | 3611 | runner.on('start', function(){ 3612 | var total = runner.grepTotal(runner.suite); 3613 | console.log('%d..%d', 1, total); 3614 | }); 3615 | 3616 | runner.on('test end', function(){ 3617 | ++n; 3618 | }); 3619 | 3620 | runner.on('pending', function(test){ 3621 | console.log('ok %d %s # SKIP -', n, title(test)); 3622 | }); 3623 | 3624 | runner.on('pass', function(test){ 3625 | passes++; 3626 | console.log('ok %d %s', n, title(test)); 3627 | }); 3628 | 3629 | runner.on('fail', function(test, err){ 3630 | failures++; 3631 | console.log('not ok %d %s', n, title(test)); 3632 | if (err.stack) console.log(err.stack.replace(/^/gm, ' ')); 3633 | }); 3634 | 3635 | runner.on('end', function(){ 3636 | console.log('# tests ' + (passes + failures)); 3637 | console.log('# pass ' + passes); 3638 | console.log('# fail ' + failures); 3639 | }); 3640 | } 3641 | 3642 | /** 3643 | * Return a TAP-safe title of `test` 3644 | * 3645 | * @param {Object} test 3646 | * @return {String} 3647 | * @api private 3648 | */ 3649 | 3650 | function title(test) { 3651 | return test.fullTitle().replace(/#/g, ''); 3652 | } 3653 | 3654 | }); // module: reporters/tap.js 3655 | 3656 | require.register("reporters/teamcity.js", function(module, exports, require){ 3657 | 3658 | /** 3659 | * Module dependencies. 3660 | */ 3661 | 3662 | var Base = require('./base'); 3663 | 3664 | /** 3665 | * Expose `Teamcity`. 3666 | */ 3667 | 3668 | exports = module.exports = Teamcity; 3669 | 3670 | /** 3671 | * Initialize a new `Teamcity` reporter. 3672 | * 3673 | * @param {Runner} runner 3674 | * @api public 3675 | */ 3676 | 3677 | function Teamcity(runner) { 3678 | Base.call(this, runner); 3679 | var stats = this.stats; 3680 | 3681 | runner.on('start', function() { 3682 | console.log("##teamcity[testSuiteStarted name='mocha.suite']"); 3683 | }); 3684 | 3685 | runner.on('test', function(test) { 3686 | console.log("##teamcity[testStarted name='" + escape(test.fullTitle()) + "']"); 3687 | }); 3688 | 3689 | runner.on('fail', function(test, err) { 3690 | console.log("##teamcity[testFailed name='" + escape(test.fullTitle()) + "' message='" + escape(err.message) + "']"); 3691 | }); 3692 | 3693 | runner.on('pending', function(test) { 3694 | console.log("##teamcity[testIgnored name='" + escape(test.fullTitle()) + "' message='pending']"); 3695 | }); 3696 | 3697 | runner.on('test end', function(test) { 3698 | console.log("##teamcity[testFinished name='" + escape(test.fullTitle()) + "' duration='" + test.duration + "']"); 3699 | }); 3700 | 3701 | runner.on('end', function() { 3702 | console.log("##teamcity[testSuiteFinished name='mocha.suite' duration='" + stats.duration + "']"); 3703 | }); 3704 | } 3705 | 3706 | /** 3707 | * Escape the given `str`. 3708 | */ 3709 | 3710 | function escape(str) { 3711 | return str 3712 | .replace(/\|/g, "||") 3713 | .replace(/\n/g, "|n") 3714 | .replace(/\r/g, "|r") 3715 | .replace(/\[/g, "|[") 3716 | .replace(/\]/g, "|]") 3717 | .replace(/\u0085/g, "|x") 3718 | .replace(/\u2028/g, "|l") 3719 | .replace(/\u2029/g, "|p") 3720 | .replace(/'/g, "|'"); 3721 | } 3722 | 3723 | }); // module: reporters/teamcity.js 3724 | 3725 | require.register("reporters/xunit.js", function(module, exports, require){ 3726 | 3727 | /** 3728 | * Module dependencies. 3729 | */ 3730 | 3731 | var Base = require('./base') 3732 | , utils = require('../utils') 3733 | , escape = utils.escape; 3734 | 3735 | /** 3736 | * Save timer references to avoid Sinon interfering (see GH-237). 3737 | */ 3738 | 3739 | var Date = global.Date 3740 | , setTimeout = global.setTimeout 3741 | , setInterval = global.setInterval 3742 | , clearTimeout = global.clearTimeout 3743 | , clearInterval = global.clearInterval; 3744 | 3745 | /** 3746 | * Expose `XUnit`. 3747 | */ 3748 | 3749 | exports = module.exports = XUnit; 3750 | 3751 | /** 3752 | * Initialize a new `XUnit` reporter. 3753 | * 3754 | * @param {Runner} runner 3755 | * @api public 3756 | */ 3757 | 3758 | function XUnit(runner) { 3759 | Base.call(this, runner); 3760 | var stats = this.stats 3761 | , tests = [] 3762 | , self = this; 3763 | 3764 | runner.on('pass', function(test){ 3765 | tests.push(test); 3766 | }); 3767 | 3768 | runner.on('fail', function(test){ 3769 | tests.push(test); 3770 | }); 3771 | 3772 | runner.on('end', function(){ 3773 | console.log(tag('testsuite', { 3774 | name: 'Mocha Tests' 3775 | , tests: stats.tests 3776 | , failures: stats.failures 3777 | , errors: stats.failures 3778 | , skip: stats.tests - stats.failures - stats.passes 3779 | , timestamp: (new Date).toUTCString() 3780 | , time: stats.duration / 1000 3781 | }, false)); 3782 | 3783 | tests.forEach(test); 3784 | console.log(''); 3785 | }); 3786 | } 3787 | 3788 | /** 3789 | * Inherit from `Base.prototype`. 3790 | */ 3791 | 3792 | function F(){}; 3793 | F.prototype = Base.prototype; 3794 | XUnit.prototype = new F; 3795 | XUnit.prototype.constructor = XUnit; 3796 | 3797 | 3798 | /** 3799 | * Output tag for the given `test.` 3800 | */ 3801 | 3802 | function test(test) { 3803 | var attrs = { 3804 | classname: test.parent.fullTitle() 3805 | , name: test.title 3806 | , time: test.duration / 1000 3807 | }; 3808 | 3809 | if ('failed' == test.state) { 3810 | var err = test.err; 3811 | attrs.message = escape(err.message); 3812 | console.log(tag('testcase', attrs, false, tag('failure', attrs, false, cdata(err.stack)))); 3813 | } else if (test.pending) { 3814 | console.log(tag('testcase', attrs, false, tag('skipped', {}, true))); 3815 | } else { 3816 | console.log(tag('testcase', attrs, true) ); 3817 | } 3818 | } 3819 | 3820 | /** 3821 | * HTML tag helper. 3822 | */ 3823 | 3824 | function tag(name, attrs, close, content) { 3825 | var end = close ? '/>' : '>' 3826 | , pairs = [] 3827 | , tag; 3828 | 3829 | for (var key in attrs) { 3830 | pairs.push(key + '="' + escape(attrs[key]) + '"'); 3831 | } 3832 | 3833 | tag = '<' + name + (pairs.length ? ' ' + pairs.join(' ') : '') + end; 3834 | if (content) tag += content + ''; 3844 | } 3845 | 3846 | }); // module: reporters/xunit.js 3847 | 3848 | require.register("runnable.js", function(module, exports, require){ 3849 | 3850 | /** 3851 | * Module dependencies. 3852 | */ 3853 | 3854 | var EventEmitter = require('browser/events').EventEmitter 3855 | , debug = require('browser/debug')('mocha:runnable') 3856 | , milliseconds = require('./ms'); 3857 | 3858 | /** 3859 | * Save timer references to avoid Sinon interfering (see GH-237). 3860 | */ 3861 | 3862 | var Date = global.Date 3863 | , setTimeout = global.setTimeout 3864 | , setInterval = global.setInterval 3865 | , clearTimeout = global.clearTimeout 3866 | , clearInterval = global.clearInterval; 3867 | 3868 | /** 3869 | * Object#toString(). 3870 | */ 3871 | 3872 | var toString = Object.prototype.toString; 3873 | 3874 | /** 3875 | * Expose `Runnable`. 3876 | */ 3877 | 3878 | module.exports = Runnable; 3879 | 3880 | /** 3881 | * Initialize a new `Runnable` with the given `title` and callback `fn`. 3882 | * 3883 | * @param {String} title 3884 | * @param {Function} fn 3885 | * @api private 3886 | */ 3887 | 3888 | function Runnable(title, fn) { 3889 | this.title = title; 3890 | this.fn = fn; 3891 | this.async = fn && fn.length; 3892 | this.sync = ! this.async; 3893 | this._timeout = 2000; 3894 | this._slow = 75; 3895 | this.timedOut = false; 3896 | } 3897 | 3898 | /** 3899 | * Inherit from `EventEmitter.prototype`. 3900 | */ 3901 | 3902 | function F(){}; 3903 | F.prototype = EventEmitter.prototype; 3904 | Runnable.prototype = new F; 3905 | Runnable.prototype.constructor = Runnable; 3906 | 3907 | 3908 | /** 3909 | * Set & get timeout `ms`. 3910 | * 3911 | * @param {Number|String} ms 3912 | * @return {Runnable|Number} ms or self 3913 | * @api private 3914 | */ 3915 | 3916 | Runnable.prototype.timeout = function(ms){ 3917 | if (0 == arguments.length) return this._timeout; 3918 | if ('string' == typeof ms) ms = milliseconds(ms); 3919 | debug('timeout %d', ms); 3920 | this._timeout = ms; 3921 | if (this.timer) this.resetTimeout(); 3922 | return this; 3923 | }; 3924 | 3925 | /** 3926 | * Set & get slow `ms`. 3927 | * 3928 | * @param {Number|String} ms 3929 | * @return {Runnable|Number} ms or self 3930 | * @api private 3931 | */ 3932 | 3933 | Runnable.prototype.slow = function(ms){ 3934 | if (0 === arguments.length) return this._slow; 3935 | if ('string' == typeof ms) ms = milliseconds(ms); 3936 | debug('timeout %d', ms); 3937 | this._slow = ms; 3938 | return this; 3939 | }; 3940 | 3941 | /** 3942 | * Return the full title generated by recursively 3943 | * concatenating the parent's full title. 3944 | * 3945 | * @return {String} 3946 | * @api public 3947 | */ 3948 | 3949 | Runnable.prototype.fullTitle = function(){ 3950 | return this.parent.fullTitle() + ' ' + this.title; 3951 | }; 3952 | 3953 | /** 3954 | * Clear the timeout. 3955 | * 3956 | * @api private 3957 | */ 3958 | 3959 | Runnable.prototype.clearTimeout = function(){ 3960 | clearTimeout(this.timer); 3961 | }; 3962 | 3963 | /** 3964 | * Inspect the runnable void of private properties. 3965 | * 3966 | * @return {String} 3967 | * @api private 3968 | */ 3969 | 3970 | Runnable.prototype.inspect = function(){ 3971 | return JSON.stringify(this, function(key, val){ 3972 | if ('_' == key[0]) return; 3973 | if ('parent' == key) return '#'; 3974 | if ('ctx' == key) return '#'; 3975 | return val; 3976 | }, 2); 3977 | }; 3978 | 3979 | /** 3980 | * Reset the timeout. 3981 | * 3982 | * @api private 3983 | */ 3984 | 3985 | Runnable.prototype.resetTimeout = function(){ 3986 | var self = this 3987 | , ms = this.timeout(); 3988 | 3989 | this.clearTimeout(); 3990 | if (ms) { 3991 | this.timer = setTimeout(function(){ 3992 | self.callback(new Error('timeout of ' + ms + 'ms exceeded')); 3993 | self.timedOut = true; 3994 | }, ms); 3995 | } 3996 | }; 3997 | 3998 | /** 3999 | * Run the test and invoke `fn(err)`. 4000 | * 4001 | * @param {Function} fn 4002 | * @api private 4003 | */ 4004 | 4005 | Runnable.prototype.run = function(fn){ 4006 | var self = this 4007 | , ms = this.timeout() 4008 | , start = new Date 4009 | , ctx = this.ctx 4010 | , finished 4011 | , emitted; 4012 | 4013 | if (ctx) ctx.runnable(this); 4014 | 4015 | // timeout 4016 | if (this.async) { 4017 | if (ms) { 4018 | this.timer = setTimeout(function(){ 4019 | done(new Error('timeout of ' + ms + 'ms exceeded')); 4020 | self.timedOut = true; 4021 | }, ms); 4022 | } 4023 | } 4024 | 4025 | // called multiple times 4026 | function multiple(err) { 4027 | if (emitted) return; 4028 | emitted = true; 4029 | self.emit('error', err || new Error('done() called multiple times')); 4030 | } 4031 | 4032 | // finished 4033 | function done(err) { 4034 | if (self.timedOut) return; 4035 | if (finished) return multiple(err); 4036 | self.clearTimeout(); 4037 | self.duration = new Date - start; 4038 | finished = true; 4039 | fn(err); 4040 | } 4041 | 4042 | // for .resetTimeout() 4043 | this.callback = done; 4044 | 4045 | // async 4046 | if (this.async) { 4047 | try { 4048 | this.fn.call(ctx, function(err){ 4049 | if (err instanceof Error || toString.call(err) === "[object Error]") return done(err); 4050 | if (null != err) return done(new Error('done() invoked with non-Error: ' + err)); 4051 | done(); 4052 | }); 4053 | } catch (err) { 4054 | done(err); 4055 | } 4056 | return; 4057 | } 4058 | 4059 | if (this.asyncOnly) { 4060 | return done(new Error('--async-only option in use without declaring `done()`')); 4061 | } 4062 | 4063 | // sync 4064 | try { 4065 | if (!this.pending) this.fn.call(ctx); 4066 | this.duration = new Date - start; 4067 | fn(); 4068 | } catch (err) { 4069 | fn(err); 4070 | } 4071 | }; 4072 | 4073 | }); // module: runnable.js 4074 | 4075 | require.register("runner.js", function(module, exports, require){ 4076 | 4077 | /** 4078 | * Module dependencies. 4079 | */ 4080 | 4081 | var EventEmitter = require('browser/events').EventEmitter 4082 | , debug = require('browser/debug')('mocha:runner') 4083 | , Test = require('./test') 4084 | , utils = require('./utils') 4085 | , filter = utils.filter 4086 | , keys = utils.keys; 4087 | 4088 | /** 4089 | * Non-enumerable globals. 4090 | */ 4091 | 4092 | var globals = [ 4093 | 'setTimeout', 4094 | 'clearTimeout', 4095 | 'setInterval', 4096 | 'clearInterval', 4097 | 'XMLHttpRequest', 4098 | 'Date' 4099 | ]; 4100 | 4101 | /** 4102 | * Expose `Runner`. 4103 | */ 4104 | 4105 | module.exports = Runner; 4106 | 4107 | /** 4108 | * Initialize a `Runner` for the given `suite`. 4109 | * 4110 | * Events: 4111 | * 4112 | * - `start` execution started 4113 | * - `end` execution complete 4114 | * - `suite` (suite) test suite execution started 4115 | * - `suite end` (suite) all tests (and sub-suites) have finished 4116 | * - `test` (test) test execution started 4117 | * - `test end` (test) test completed 4118 | * - `hook` (hook) hook execution started 4119 | * - `hook end` (hook) hook complete 4120 | * - `pass` (test) test passed 4121 | * - `fail` (test, err) test failed 4122 | * 4123 | * @api public 4124 | */ 4125 | 4126 | function Runner(suite) { 4127 | var self = this; 4128 | this._globals = []; 4129 | this.suite = suite; 4130 | this.total = suite.total(); 4131 | this.failures = 0; 4132 | this.on('test end', function(test){ self.checkGlobals(test); }); 4133 | this.on('hook end', function(hook){ self.checkGlobals(hook); }); 4134 | this.grep(/.*/); 4135 | this.globals(this.globalProps().concat(['errno'])); 4136 | } 4137 | 4138 | /** 4139 | * Wrapper for setImmediate, process.nextTick, or browser polyfill. 4140 | * 4141 | * @param {Function} fn 4142 | * @api private 4143 | */ 4144 | 4145 | Runner.immediately = global.setImmediate || process.nextTick; 4146 | 4147 | /** 4148 | * Inherit from `EventEmitter.prototype`. 4149 | */ 4150 | 4151 | function F(){}; 4152 | F.prototype = EventEmitter.prototype; 4153 | Runner.prototype = new F; 4154 | Runner.prototype.constructor = Runner; 4155 | 4156 | 4157 | /** 4158 | * Run tests with full titles matching `re`. Updates runner.total 4159 | * with number of tests matched. 4160 | * 4161 | * @param {RegExp} re 4162 | * @param {Boolean} invert 4163 | * @return {Runner} for chaining 4164 | * @api public 4165 | */ 4166 | 4167 | Runner.prototype.grep = function(re, invert){ 4168 | debug('grep %s', re); 4169 | this._grep = re; 4170 | this._invert = invert; 4171 | this.total = this.grepTotal(this.suite); 4172 | return this; 4173 | }; 4174 | 4175 | /** 4176 | * Returns the number of tests matching the grep search for the 4177 | * given suite. 4178 | * 4179 | * @param {Suite} suite 4180 | * @return {Number} 4181 | * @api public 4182 | */ 4183 | 4184 | Runner.prototype.grepTotal = function(suite) { 4185 | var self = this; 4186 | var total = 0; 4187 | 4188 | suite.eachTest(function(test){ 4189 | var match = self._grep.test(test.fullTitle()); 4190 | if (self._invert) match = !match; 4191 | if (match) total++; 4192 | }); 4193 | 4194 | return total; 4195 | }; 4196 | 4197 | /** 4198 | * Return a list of global properties. 4199 | * 4200 | * @return {Array} 4201 | * @api private 4202 | */ 4203 | 4204 | Runner.prototype.globalProps = function() { 4205 | var props = utils.keys(global); 4206 | 4207 | // non-enumerables 4208 | for (var i = 0; i < globals.length; ++i) { 4209 | if (~utils.indexOf(props, globals[i])) continue; 4210 | props.push(globals[i]); 4211 | } 4212 | 4213 | return props; 4214 | }; 4215 | 4216 | /** 4217 | * Allow the given `arr` of globals. 4218 | * 4219 | * @param {Array} arr 4220 | * @return {Runner} for chaining 4221 | * @api public 4222 | */ 4223 | 4224 | Runner.prototype.globals = function(arr){ 4225 | if (0 == arguments.length) return this._globals; 4226 | debug('globals %j', arr); 4227 | utils.forEach(arr, function(arr){ 4228 | this._globals.push(arr); 4229 | }, this); 4230 | return this; 4231 | }; 4232 | 4233 | /** 4234 | * Check for global variable leaks. 4235 | * 4236 | * @api private 4237 | */ 4238 | 4239 | Runner.prototype.checkGlobals = function(test){ 4240 | if (this.ignoreLeaks) return; 4241 | var ok = this._globals; 4242 | var globals = this.globalProps(); 4243 | var isNode = process.kill; 4244 | var leaks; 4245 | 4246 | // check length - 2 ('errno' and 'location' globals) 4247 | if (isNode && 1 == ok.length - globals.length) return 4248 | else if (2 == ok.length - globals.length) return; 4249 | 4250 | leaks = filterLeaks(ok, globals); 4251 | this._globals = this._globals.concat(leaks); 4252 | 4253 | if (leaks.length > 1) { 4254 | this.fail(test, new Error('global leaks detected: ' + leaks.join(', ') + '')); 4255 | } else if (leaks.length) { 4256 | this.fail(test, new Error('global leak detected: ' + leaks[0])); 4257 | } 4258 | }; 4259 | 4260 | /** 4261 | * Fail the given `test`. 4262 | * 4263 | * @param {Test} test 4264 | * @param {Error} err 4265 | * @api private 4266 | */ 4267 | 4268 | Runner.prototype.fail = function(test, err){ 4269 | ++this.failures; 4270 | test.state = 'failed'; 4271 | 4272 | if ('string' == typeof err) { 4273 | err = new Error('the string "' + err + '" was thrown, throw an Error :)'); 4274 | } 4275 | 4276 | this.emit('fail', test, err); 4277 | }; 4278 | 4279 | /** 4280 | * Fail the given `hook` with `err`. 4281 | * 4282 | * Hook failures (currently) hard-end due 4283 | * to that fact that a failing hook will 4284 | * surely cause subsequent tests to fail, 4285 | * causing jumbled reporting. 4286 | * 4287 | * @param {Hook} hook 4288 | * @param {Error} err 4289 | * @api private 4290 | */ 4291 | 4292 | Runner.prototype.failHook = function(hook, err){ 4293 | this.fail(hook, err); 4294 | this.emit('end'); 4295 | }; 4296 | 4297 | /** 4298 | * Run hook `name` callbacks and then invoke `fn()`. 4299 | * 4300 | * @param {String} name 4301 | * @param {Function} function 4302 | * @api private 4303 | */ 4304 | 4305 | Runner.prototype.hook = function(name, fn){ 4306 | var suite = this.suite 4307 | , hooks = suite['_' + name] 4308 | , self = this 4309 | , timer; 4310 | 4311 | function next(i) { 4312 | var hook = hooks[i]; 4313 | if (!hook) return fn(); 4314 | self.currentRunnable = hook; 4315 | 4316 | self.emit('hook', hook); 4317 | 4318 | hook.on('error', function(err){ 4319 | self.failHook(hook, err); 4320 | }); 4321 | 4322 | hook.run(function(err){ 4323 | hook.removeAllListeners('error'); 4324 | var testError = hook.error(); 4325 | if (testError) self.fail(self.test, testError); 4326 | if (err) return self.failHook(hook, err); 4327 | self.emit('hook end', hook); 4328 | next(++i); 4329 | }); 4330 | } 4331 | 4332 | Runner.immediately(function(){ 4333 | next(0); 4334 | }); 4335 | }; 4336 | 4337 | /** 4338 | * Run hook `name` for the given array of `suites` 4339 | * in order, and callback `fn(err)`. 4340 | * 4341 | * @param {String} name 4342 | * @param {Array} suites 4343 | * @param {Function} fn 4344 | * @api private 4345 | */ 4346 | 4347 | Runner.prototype.hooks = function(name, suites, fn){ 4348 | var self = this 4349 | , orig = this.suite; 4350 | 4351 | function next(suite) { 4352 | self.suite = suite; 4353 | 4354 | if (!suite) { 4355 | self.suite = orig; 4356 | return fn(); 4357 | } 4358 | 4359 | self.hook(name, function(err){ 4360 | if (err) { 4361 | self.suite = orig; 4362 | return fn(err); 4363 | } 4364 | 4365 | next(suites.pop()); 4366 | }); 4367 | } 4368 | 4369 | next(suites.pop()); 4370 | }; 4371 | 4372 | /** 4373 | * Run hooks from the top level down. 4374 | * 4375 | * @param {String} name 4376 | * @param {Function} fn 4377 | * @api private 4378 | */ 4379 | 4380 | Runner.prototype.hookUp = function(name, fn){ 4381 | var suites = [this.suite].concat(this.parents()).reverse(); 4382 | this.hooks(name, suites, fn); 4383 | }; 4384 | 4385 | /** 4386 | * Run hooks from the bottom up. 4387 | * 4388 | * @param {String} name 4389 | * @param {Function} fn 4390 | * @api private 4391 | */ 4392 | 4393 | Runner.prototype.hookDown = function(name, fn){ 4394 | var suites = [this.suite].concat(this.parents()); 4395 | this.hooks(name, suites, fn); 4396 | }; 4397 | 4398 | /** 4399 | * Return an array of parent Suites from 4400 | * closest to furthest. 4401 | * 4402 | * @return {Array} 4403 | * @api private 4404 | */ 4405 | 4406 | Runner.prototype.parents = function(){ 4407 | var suite = this.suite 4408 | , suites = []; 4409 | while (suite = suite.parent) suites.push(suite); 4410 | return suites; 4411 | }; 4412 | 4413 | /** 4414 | * Run the current test and callback `fn(err)`. 4415 | * 4416 | * @param {Function} fn 4417 | * @api private 4418 | */ 4419 | 4420 | Runner.prototype.runTest = function(fn){ 4421 | var test = this.test 4422 | , self = this; 4423 | 4424 | if (this.asyncOnly) test.asyncOnly = true; 4425 | 4426 | try { 4427 | test.on('error', function(err){ 4428 | self.fail(test, err); 4429 | }); 4430 | test.run(fn); 4431 | } catch (err) { 4432 | fn(err); 4433 | } 4434 | }; 4435 | 4436 | /** 4437 | * Run tests in the given `suite` and invoke 4438 | * the callback `fn()` when complete. 4439 | * 4440 | * @param {Suite} suite 4441 | * @param {Function} fn 4442 | * @api private 4443 | */ 4444 | 4445 | Runner.prototype.runTests = function(suite, fn){ 4446 | var self = this 4447 | , tests = suite.tests.slice() 4448 | , test; 4449 | 4450 | function next(err) { 4451 | // if we bail after first err 4452 | if (self.failures && suite._bail) return fn(); 4453 | 4454 | // next test 4455 | test = tests.shift(); 4456 | 4457 | // all done 4458 | if (!test) return fn(); 4459 | 4460 | // grep 4461 | var match = self._grep.test(test.fullTitle()); 4462 | if (self._invert) match = !match; 4463 | if (!match) return next(); 4464 | 4465 | // pending 4466 | if (test.pending) { 4467 | self.emit('pending', test); 4468 | self.emit('test end', test); 4469 | return next(); 4470 | } 4471 | 4472 | // execute test and hook(s) 4473 | self.emit('test', self.test = test); 4474 | self.hookDown('beforeEach', function(){ 4475 | self.currentRunnable = self.test; 4476 | self.runTest(function(err){ 4477 | test = self.test; 4478 | 4479 | if (err) { 4480 | self.fail(test, err); 4481 | self.emit('test end', test); 4482 | return self.hookUp('afterEach', next); 4483 | } 4484 | 4485 | test.state = 'passed'; 4486 | self.emit('pass', test); 4487 | self.emit('test end', test); 4488 | self.hookUp('afterEach', next); 4489 | }); 4490 | }); 4491 | } 4492 | 4493 | this.next = next; 4494 | next(); 4495 | }; 4496 | 4497 | /** 4498 | * Run the given `suite` and invoke the 4499 | * callback `fn()` when complete. 4500 | * 4501 | * @param {Suite} suite 4502 | * @param {Function} fn 4503 | * @api private 4504 | */ 4505 | 4506 | Runner.prototype.runSuite = function(suite, fn){ 4507 | var total = this.grepTotal(suite) 4508 | , self = this 4509 | , i = 0; 4510 | 4511 | debug('run suite %s', suite.fullTitle()); 4512 | 4513 | if (!total) return fn(); 4514 | 4515 | this.emit('suite', this.suite = suite); 4516 | 4517 | function next() { 4518 | var curr = suite.suites[i++]; 4519 | if (!curr) return done(); 4520 | self.runSuite(curr, next); 4521 | } 4522 | 4523 | function done() { 4524 | self.suite = suite; 4525 | self.hook('afterAll', function(){ 4526 | self.emit('suite end', suite); 4527 | fn(); 4528 | }); 4529 | } 4530 | 4531 | this.hook('beforeAll', function(){ 4532 | self.runTests(suite, next); 4533 | }); 4534 | }; 4535 | 4536 | /** 4537 | * Handle uncaught exceptions. 4538 | * 4539 | * @param {Error} err 4540 | * @api private 4541 | */ 4542 | 4543 | Runner.prototype.uncaught = function(err){ 4544 | debug('uncaught exception %s', err.message); 4545 | var runnable = this.currentRunnable; 4546 | if (!runnable || 'failed' == runnable.state) return; 4547 | runnable.clearTimeout(); 4548 | err.uncaught = true; 4549 | this.fail(runnable, err); 4550 | 4551 | // recover from test 4552 | if ('test' == runnable.type) { 4553 | this.emit('test end', runnable); 4554 | this.hookUp('afterEach', this.next); 4555 | return; 4556 | } 4557 | 4558 | // bail on hooks 4559 | this.emit('end'); 4560 | }; 4561 | 4562 | /** 4563 | * Run the root suite and invoke `fn(failures)` 4564 | * on completion. 4565 | * 4566 | * @param {Function} fn 4567 | * @return {Runner} for chaining 4568 | * @api public 4569 | */ 4570 | 4571 | Runner.prototype.run = function(fn){ 4572 | var self = this 4573 | , fn = fn || function(){}; 4574 | 4575 | function uncaught(err){ 4576 | self.uncaught(err); 4577 | } 4578 | 4579 | debug('start'); 4580 | 4581 | // callback 4582 | this.on('end', function(){ 4583 | debug('end'); 4584 | process.removeListener('uncaughtException', uncaught); 4585 | fn(self.failures); 4586 | }); 4587 | 4588 | // run suites 4589 | this.emit('start'); 4590 | this.runSuite(this.suite, function(){ 4591 | debug('finished running'); 4592 | self.emit('end'); 4593 | }); 4594 | 4595 | // uncaught exception 4596 | process.on('uncaughtException', uncaught); 4597 | 4598 | return this; 4599 | }; 4600 | 4601 | /** 4602 | * Filter leaks with the given globals flagged as `ok`. 4603 | * 4604 | * @param {Array} ok 4605 | * @param {Array} globals 4606 | * @return {Array} 4607 | * @api private 4608 | */ 4609 | 4610 | function filterLeaks(ok, globals) { 4611 | return filter(globals, function(key){ 4612 | // Firefox and Chrome exposes iframes as index inside the window object 4613 | if (/^d+/.test(key)) return false; 4614 | var matched = filter(ok, function(ok){ 4615 | if (~ok.indexOf('*')) return 0 == key.indexOf(ok.split('*')[0]); 4616 | // Opera and IE expose global variables for HTML element IDs (issue #243) 4617 | if (/^mocha-/.test(key)) return true; 4618 | return key == ok; 4619 | }); 4620 | return matched.length == 0 && (!global.navigator || 'onerror' !== key); 4621 | }); 4622 | } 4623 | 4624 | }); // module: runner.js 4625 | 4626 | require.register("suite.js", function(module, exports, require){ 4627 | 4628 | /** 4629 | * Module dependencies. 4630 | */ 4631 | 4632 | var EventEmitter = require('browser/events').EventEmitter 4633 | , debug = require('browser/debug')('mocha:suite') 4634 | , milliseconds = require('./ms') 4635 | , utils = require('./utils') 4636 | , Hook = require('./hook'); 4637 | 4638 | /** 4639 | * Expose `Suite`. 4640 | */ 4641 | 4642 | exports = module.exports = Suite; 4643 | 4644 | /** 4645 | * Create a new `Suite` with the given `title` 4646 | * and parent `Suite`. When a suite with the 4647 | * same title is already present, that suite 4648 | * is returned to provide nicer reporter 4649 | * and more flexible meta-testing. 4650 | * 4651 | * @param {Suite} parent 4652 | * @param {String} title 4653 | * @return {Suite} 4654 | * @api public 4655 | */ 4656 | 4657 | exports.create = function(parent, title){ 4658 | var suite = new Suite(title, parent.ctx); 4659 | suite.parent = parent; 4660 | if (parent.pending) suite.pending = true; 4661 | title = suite.fullTitle(); 4662 | parent.addSuite(suite); 4663 | return suite; 4664 | }; 4665 | 4666 | /** 4667 | * Initialize a new `Suite` with the given 4668 | * `title` and `ctx`. 4669 | * 4670 | * @param {String} title 4671 | * @param {Context} ctx 4672 | * @api private 4673 | */ 4674 | 4675 | function Suite(title, ctx) { 4676 | this.title = title; 4677 | this.ctx = ctx; 4678 | this.suites = []; 4679 | this.tests = []; 4680 | this.pending = false; 4681 | this._beforeEach = []; 4682 | this._beforeAll = []; 4683 | this._afterEach = []; 4684 | this._afterAll = []; 4685 | this.root = !title; 4686 | this._timeout = 2000; 4687 | this._slow = 75; 4688 | this._bail = false; 4689 | } 4690 | 4691 | /** 4692 | * Inherit from `EventEmitter.prototype`. 4693 | */ 4694 | 4695 | function F(){}; 4696 | F.prototype = EventEmitter.prototype; 4697 | Suite.prototype = new F; 4698 | Suite.prototype.constructor = Suite; 4699 | 4700 | 4701 | /** 4702 | * Return a clone of this `Suite`. 4703 | * 4704 | * @return {Suite} 4705 | * @api private 4706 | */ 4707 | 4708 | Suite.prototype.clone = function(){ 4709 | var suite = new Suite(this.title); 4710 | debug('clone'); 4711 | suite.ctx = this.ctx; 4712 | suite.timeout(this.timeout()); 4713 | suite.slow(this.slow()); 4714 | suite.bail(this.bail()); 4715 | return suite; 4716 | }; 4717 | 4718 | /** 4719 | * Set timeout `ms` or short-hand such as "2s". 4720 | * 4721 | * @param {Number|String} ms 4722 | * @return {Suite|Number} for chaining 4723 | * @api private 4724 | */ 4725 | 4726 | Suite.prototype.timeout = function(ms){ 4727 | if (0 == arguments.length) return this._timeout; 4728 | if ('string' == typeof ms) ms = milliseconds(ms); 4729 | debug('timeout %d', ms); 4730 | this._timeout = parseInt(ms, 10); 4731 | return this; 4732 | }; 4733 | 4734 | /** 4735 | * Set slow `ms` or short-hand such as "2s". 4736 | * 4737 | * @param {Number|String} ms 4738 | * @return {Suite|Number} for chaining 4739 | * @api private 4740 | */ 4741 | 4742 | Suite.prototype.slow = function(ms){ 4743 | if (0 === arguments.length) return this._slow; 4744 | if ('string' == typeof ms) ms = milliseconds(ms); 4745 | debug('slow %d', ms); 4746 | this._slow = ms; 4747 | return this; 4748 | }; 4749 | 4750 | /** 4751 | * Sets whether to bail after first error. 4752 | * 4753 | * @parma {Boolean} bail 4754 | * @return {Suite|Number} for chaining 4755 | * @api private 4756 | */ 4757 | 4758 | Suite.prototype.bail = function(bail){ 4759 | if (0 == arguments.length) return this._bail; 4760 | debug('bail %s', bail); 4761 | this._bail = bail; 4762 | return this; 4763 | }; 4764 | 4765 | /** 4766 | * Run `fn(test[, done])` before running tests. 4767 | * 4768 | * @param {Function} fn 4769 | * @return {Suite} for chaining 4770 | * @api private 4771 | */ 4772 | 4773 | Suite.prototype.beforeAll = function(fn){ 4774 | if (this.pending) return this; 4775 | var hook = new Hook('"before all" hook', fn); 4776 | hook.parent = this; 4777 | hook.timeout(this.timeout()); 4778 | hook.slow(this.slow()); 4779 | hook.ctx = this.ctx; 4780 | this._beforeAll.push(hook); 4781 | this.emit('beforeAll', hook); 4782 | return this; 4783 | }; 4784 | 4785 | /** 4786 | * Run `fn(test[, done])` after running tests. 4787 | * 4788 | * @param {Function} fn 4789 | * @return {Suite} for chaining 4790 | * @api private 4791 | */ 4792 | 4793 | Suite.prototype.afterAll = function(fn){ 4794 | if (this.pending) return this; 4795 | var hook = new Hook('"after all" hook', fn); 4796 | hook.parent = this; 4797 | hook.timeout(this.timeout()); 4798 | hook.slow(this.slow()); 4799 | hook.ctx = this.ctx; 4800 | this._afterAll.push(hook); 4801 | this.emit('afterAll', hook); 4802 | return this; 4803 | }; 4804 | 4805 | /** 4806 | * Run `fn(test[, done])` before each test case. 4807 | * 4808 | * @param {Function} fn 4809 | * @return {Suite} for chaining 4810 | * @api private 4811 | */ 4812 | 4813 | Suite.prototype.beforeEach = function(fn){ 4814 | if (this.pending) return this; 4815 | var hook = new Hook('"before each" hook', fn); 4816 | hook.parent = this; 4817 | hook.timeout(this.timeout()); 4818 | hook.slow(this.slow()); 4819 | hook.ctx = this.ctx; 4820 | this._beforeEach.push(hook); 4821 | this.emit('beforeEach', hook); 4822 | return this; 4823 | }; 4824 | 4825 | /** 4826 | * Run `fn(test[, done])` after each test case. 4827 | * 4828 | * @param {Function} fn 4829 | * @return {Suite} for chaining 4830 | * @api private 4831 | */ 4832 | 4833 | Suite.prototype.afterEach = function(fn){ 4834 | if (this.pending) return this; 4835 | var hook = new Hook('"after each" hook', fn); 4836 | hook.parent = this; 4837 | hook.timeout(this.timeout()); 4838 | hook.slow(this.slow()); 4839 | hook.ctx = this.ctx; 4840 | this._afterEach.push(hook); 4841 | this.emit('afterEach', hook); 4842 | return this; 4843 | }; 4844 | 4845 | /** 4846 | * Add a test `suite`. 4847 | * 4848 | * @param {Suite} suite 4849 | * @return {Suite} for chaining 4850 | * @api private 4851 | */ 4852 | 4853 | Suite.prototype.addSuite = function(suite){ 4854 | suite.parent = this; 4855 | suite.timeout(this.timeout()); 4856 | suite.slow(this.slow()); 4857 | suite.bail(this.bail()); 4858 | this.suites.push(suite); 4859 | this.emit('suite', suite); 4860 | return this; 4861 | }; 4862 | 4863 | /** 4864 | * Add a `test` to this suite. 4865 | * 4866 | * @param {Test} test 4867 | * @return {Suite} for chaining 4868 | * @api private 4869 | */ 4870 | 4871 | Suite.prototype.addTest = function(test){ 4872 | test.parent = this; 4873 | test.timeout(this.timeout()); 4874 | test.slow(this.slow()); 4875 | test.ctx = this.ctx; 4876 | this.tests.push(test); 4877 | this.emit('test', test); 4878 | return this; 4879 | }; 4880 | 4881 | /** 4882 | * Return the full title generated by recursively 4883 | * concatenating the parent's full title. 4884 | * 4885 | * @return {String} 4886 | * @api public 4887 | */ 4888 | 4889 | Suite.prototype.fullTitle = function(){ 4890 | if (this.parent) { 4891 | var full = this.parent.fullTitle(); 4892 | if (full) return full + ' ' + this.title; 4893 | } 4894 | return this.title; 4895 | }; 4896 | 4897 | /** 4898 | * Return the total number of tests. 4899 | * 4900 | * @return {Number} 4901 | * @api public 4902 | */ 4903 | 4904 | Suite.prototype.total = function(){ 4905 | return utils.reduce(this.suites, function(sum, suite){ 4906 | return sum + suite.total(); 4907 | }, 0) + this.tests.length; 4908 | }; 4909 | 4910 | /** 4911 | * Iterates through each suite recursively to find 4912 | * all tests. Applies a function in the format 4913 | * `fn(test)`. 4914 | * 4915 | * @param {Function} fn 4916 | * @return {Suite} 4917 | * @api private 4918 | */ 4919 | 4920 | Suite.prototype.eachTest = function(fn){ 4921 | utils.forEach(this.tests, fn); 4922 | utils.forEach(this.suites, function(suite){ 4923 | suite.eachTest(fn); 4924 | }); 4925 | return this; 4926 | }; 4927 | 4928 | }); // module: suite.js 4929 | 4930 | require.register("test.js", function(module, exports, require){ 4931 | 4932 | /** 4933 | * Module dependencies. 4934 | */ 4935 | 4936 | var Runnable = require('./runnable'); 4937 | 4938 | /** 4939 | * Expose `Test`. 4940 | */ 4941 | 4942 | module.exports = Test; 4943 | 4944 | /** 4945 | * Initialize a new `Test` with the given `title` and callback `fn`. 4946 | * 4947 | * @param {String} title 4948 | * @param {Function} fn 4949 | * @api private 4950 | */ 4951 | 4952 | function Test(title, fn) { 4953 | Runnable.call(this, title, fn); 4954 | this.pending = !fn; 4955 | this.type = 'test'; 4956 | } 4957 | 4958 | /** 4959 | * Inherit from `Runnable.prototype`. 4960 | */ 4961 | 4962 | function F(){}; 4963 | F.prototype = Runnable.prototype; 4964 | Test.prototype = new F; 4965 | Test.prototype.constructor = Test; 4966 | 4967 | 4968 | }); // module: test.js 4969 | 4970 | require.register("utils.js", function(module, exports, require){ 4971 | 4972 | /** 4973 | * Module dependencies. 4974 | */ 4975 | 4976 | var fs = require('browser/fs') 4977 | , path = require('browser/path') 4978 | , join = path.join 4979 | , debug = require('browser/debug')('mocha:watch'); 4980 | 4981 | /** 4982 | * Ignored directories. 4983 | */ 4984 | 4985 | var ignore = ['node_modules', '.git']; 4986 | 4987 | /** 4988 | * Escape special characters in the given string of html. 4989 | * 4990 | * @param {String} html 4991 | * @return {String} 4992 | * @api private 4993 | */ 4994 | 4995 | exports.escape = function(html){ 4996 | return String(html) 4997 | .replace(/&/g, '&') 4998 | .replace(/"/g, '"') 4999 | .replace(//g, '>'); 5001 | }; 5002 | 5003 | /** 5004 | * Array#forEach (<=IE8) 5005 | * 5006 | * @param {Array} array 5007 | * @param {Function} fn 5008 | * @param {Object} scope 5009 | * @api private 5010 | */ 5011 | 5012 | exports.forEach = function(arr, fn, scope){ 5013 | for (var i = 0, l = arr.length; i < l; i++) 5014 | fn.call(scope, arr[i], i); 5015 | }; 5016 | 5017 | /** 5018 | * Array#indexOf (<=IE8) 5019 | * 5020 | * @parma {Array} arr 5021 | * @param {Object} obj to find index of 5022 | * @param {Number} start 5023 | * @api private 5024 | */ 5025 | 5026 | exports.indexOf = function(arr, obj, start){ 5027 | for (var i = start || 0, l = arr.length; i < l; i++) { 5028 | if (arr[i] === obj) 5029 | return i; 5030 | } 5031 | return -1; 5032 | }; 5033 | 5034 | /** 5035 | * Array#reduce (<=IE8) 5036 | * 5037 | * @param {Array} array 5038 | * @param {Function} fn 5039 | * @param {Object} initial value 5040 | * @api private 5041 | */ 5042 | 5043 | exports.reduce = function(arr, fn, val){ 5044 | var rval = val; 5045 | 5046 | for (var i = 0, l = arr.length; i < l; i++) { 5047 | rval = fn(rval, arr[i], i, arr); 5048 | } 5049 | 5050 | return rval; 5051 | }; 5052 | 5053 | /** 5054 | * Array#filter (<=IE8) 5055 | * 5056 | * @param {Array} array 5057 | * @param {Function} fn 5058 | * @api private 5059 | */ 5060 | 5061 | exports.filter = function(arr, fn){ 5062 | var ret = []; 5063 | 5064 | for (var i = 0, l = arr.length; i < l; i++) { 5065 | var val = arr[i]; 5066 | if (fn(val, i, arr)) ret.push(val); 5067 | } 5068 | 5069 | return ret; 5070 | }; 5071 | 5072 | /** 5073 | * Object.keys (<=IE8) 5074 | * 5075 | * @param {Object} obj 5076 | * @return {Array} keys 5077 | * @api private 5078 | */ 5079 | 5080 | exports.keys = Object.keys || function(obj) { 5081 | var keys = [] 5082 | , has = Object.prototype.hasOwnProperty // for `window` on <=IE8 5083 | 5084 | for (var key in obj) { 5085 | if (has.call(obj, key)) { 5086 | keys.push(key); 5087 | } 5088 | } 5089 | 5090 | return keys; 5091 | }; 5092 | 5093 | /** 5094 | * Watch the given `files` for changes 5095 | * and invoke `fn(file)` on modification. 5096 | * 5097 | * @param {Array} files 5098 | * @param {Function} fn 5099 | * @api private 5100 | */ 5101 | 5102 | exports.watch = function(files, fn){ 5103 | var options = { interval: 100 }; 5104 | files.forEach(function(file){ 5105 | debug('file %s', file); 5106 | fs.watchFile(file, options, function(curr, prev){ 5107 | if (prev.mtime < curr.mtime) fn(file); 5108 | }); 5109 | }); 5110 | }; 5111 | 5112 | /** 5113 | * Ignored files. 5114 | */ 5115 | 5116 | function ignored(path){ 5117 | return !~ignore.indexOf(path); 5118 | } 5119 | 5120 | /** 5121 | * Lookup files in the given `dir`. 5122 | * 5123 | * @return {Array} 5124 | * @api private 5125 | */ 5126 | 5127 | exports.files = function(dir, ret){ 5128 | ret = ret || []; 5129 | 5130 | fs.readdirSync(dir) 5131 | .filter(ignored) 5132 | .forEach(function(path){ 5133 | path = join(dir, path); 5134 | if (fs.statSync(path).isDirectory()) { 5135 | exports.files(path, ret); 5136 | } else if (path.match(/\.(js|coffee)$/)) { 5137 | ret.push(path); 5138 | } 5139 | }); 5140 | 5141 | return ret; 5142 | }; 5143 | 5144 | /** 5145 | * Compute a slug from the given `str`. 5146 | * 5147 | * @param {String} str 5148 | * @return {String} 5149 | * @api private 5150 | */ 5151 | 5152 | exports.slug = function(str){ 5153 | return str 5154 | .toLowerCase() 5155 | .replace(/ +/g, '-') 5156 | .replace(/[^-\w]/g, ''); 5157 | }; 5158 | 5159 | /** 5160 | * Strip the function definition from `str`, 5161 | * and re-indent for pre whitespace. 5162 | */ 5163 | 5164 | exports.clean = function(str) { 5165 | str = str 5166 | .replace(/^function *\(.*\) *{/, '') 5167 | .replace(/\s+\}$/, ''); 5168 | 5169 | var spaces = str.match(/^\n?( *)/)[1].length 5170 | , re = new RegExp('^ {' + spaces + '}', 'gm'); 5171 | 5172 | str = str.replace(re, ''); 5173 | 5174 | return exports.trim(str); 5175 | }; 5176 | 5177 | /** 5178 | * Escape regular expression characters in `str`. 5179 | * 5180 | * @param {String} str 5181 | * @return {String} 5182 | * @api private 5183 | */ 5184 | 5185 | exports.escapeRegexp = function(str){ 5186 | return str.replace(/[-\\^$*+?.()|[\]{}]/g, "\\$&"); 5187 | }; 5188 | 5189 | /** 5190 | * Trim the given `str`. 5191 | * 5192 | * @param {String} str 5193 | * @return {String} 5194 | * @api private 5195 | */ 5196 | 5197 | exports.trim = function(str){ 5198 | return str.replace(/^\s+|\s+$/g, ''); 5199 | }; 5200 | 5201 | /** 5202 | * Parse the given `qs`. 5203 | * 5204 | * @param {String} qs 5205 | * @return {Object} 5206 | * @api private 5207 | */ 5208 | 5209 | exports.parseQuery = function(qs){ 5210 | return exports.reduce(qs.replace('?', '').split('&'), function(obj, pair){ 5211 | var i = pair.indexOf('=') 5212 | , key = pair.slice(0, i) 5213 | , val = pair.slice(++i); 5214 | 5215 | obj[key] = decodeURIComponent(val); 5216 | return obj; 5217 | }, {}); 5218 | }; 5219 | 5220 | /** 5221 | * Highlight the given string of `js`. 5222 | * 5223 | * @param {String} js 5224 | * @return {String} 5225 | * @api private 5226 | */ 5227 | 5228 | function highlight(js) { 5229 | return js 5230 | .replace(//g, '>') 5232 | .replace(/\/\/(.*)/gm, '//$1') 5233 | .replace(/('.*?')/gm, '$1') 5234 | .replace(/(\d+\.\d+)/gm, '$1') 5235 | .replace(/(\d+)/gm, '$1') 5236 | .replace(/\bnew *(\w+)/gm, 'new $1') 5237 | .replace(/\b(function|new|throw|return|var|if|else)\b/gm, '$1') 5238 | } 5239 | 5240 | /** 5241 | * Highlight the contents of tag `name`. 5242 | * 5243 | * @param {String} name 5244 | * @api private 5245 | */ 5246 | 5247 | exports.highlightTags = function(name) { 5248 | var code = document.getElementsByTagName(name); 5249 | for (var i = 0, len = code.length; i < len; ++i) { 5250 | code[i].innerHTML = highlight(code[i].innerHTML); 5251 | } 5252 | }; 5253 | 5254 | }); // module: utils.js 5255 | 5256 | /** 5257 | * Save timer references to avoid Sinon interfering (see GH-237). 5258 | */ 5259 | 5260 | var Date = window.Date; 5261 | var setTimeout = window.setTimeout; 5262 | var setInterval = window.setInterval; 5263 | var clearTimeout = window.clearTimeout; 5264 | var clearInterval = window.clearInterval; 5265 | 5266 | /** 5267 | * Node shims. 5268 | * 5269 | * These are meant only to allow 5270 | * mocha.js to run untouched, not 5271 | * to allow running node code in 5272 | * the browser. 5273 | */ 5274 | 5275 | var process = {}; 5276 | process.exit = function(status){}; 5277 | process.stdout = {}; 5278 | global = window; 5279 | 5280 | /** 5281 | * Remove uncaughtException listener. 5282 | */ 5283 | 5284 | process.removeListener = function(e){ 5285 | if ('uncaughtException' == e) { 5286 | window.onerror = null; 5287 | } 5288 | }; 5289 | 5290 | /** 5291 | * Implements uncaughtException listener. 5292 | */ 5293 | 5294 | process.on = function(e, fn){ 5295 | if ('uncaughtException' == e) { 5296 | window.onerror = function(err, url, line){ 5297 | fn(new Error(err + ' (' + url + ':' + line + ')')); 5298 | }; 5299 | } 5300 | }; 5301 | 5302 | /** 5303 | * Expose mocha. 5304 | */ 5305 | 5306 | var Mocha = window.Mocha = require('mocha'), 5307 | mocha = window.mocha = new Mocha({ reporter: 'html' }); 5308 | 5309 | var immediateQueue = [] 5310 | , immediateTimeout; 5311 | 5312 | function timeslice() { 5313 | var immediateStart = new Date().getTime(); 5314 | while (immediateQueue.length && (new Date().getTime() - immediateStart) < 100) { 5315 | immediateQueue.shift()(); 5316 | } 5317 | if (immediateQueue.length) { 5318 | immediateTimeout = setTimeout(timeslice, 0); 5319 | } else { 5320 | immediateTimeout = null; 5321 | } 5322 | } 5323 | 5324 | /** 5325 | * High-performance override of Runner.immediately. 5326 | */ 5327 | 5328 | Mocha.Runner.immediately = function(callback) { 5329 | immediateQueue.push(callback); 5330 | if (!immediateTimeout) { 5331 | immediateTimeout = setTimeout(timeslice, 0); 5332 | } 5333 | }; 5334 | 5335 | /** 5336 | * Override ui to ensure that the ui functions are initialized. 5337 | * Normally this would happen in Mocha.prototype.loadFiles. 5338 | */ 5339 | 5340 | mocha.ui = function(ui){ 5341 | Mocha.prototype.ui.call(this, ui); 5342 | this.suite.emit('pre-require', window, null, this); 5343 | return this; 5344 | }; 5345 | 5346 | /** 5347 | * Setup mocha with the given setting options. 5348 | */ 5349 | 5350 | mocha.setup = function(opts){ 5351 | if ('string' == typeof opts) opts = { ui: opts }; 5352 | for (var opt in opts) this[opt](opts[opt]); 5353 | return this; 5354 | }; 5355 | 5356 | /** 5357 | * Run mocha, returning the Runner. 5358 | */ 5359 | 5360 | mocha.run = function(fn){ 5361 | var options = mocha.options; 5362 | mocha.globals('location'); 5363 | 5364 | var query = Mocha.utils.parseQuery(window.location.search || ''); 5365 | if (query.grep) mocha.grep(query.grep); 5366 | if (query.invert) mocha.invert(); 5367 | 5368 | return Mocha.prototype.run.call(mocha, function(){ 5369 | Mocha.utils.highlightTags('code'); 5370 | if (fn) fn(); 5371 | }); 5372 | }; 5373 | })(); --------------------------------------------------------------------------------