├── 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 = $('')
87 | .css('border', 'none')
88 | .css('width', '100%')
89 | .css('height', '100%')
90 | .attr('src', getCurrentUrlWithIframeParameter()); // this is necessary because some browsers block an iframe to the same url.
91 |
92 | $el.append($iframe);
93 | return null;
94 | }
95 |
96 | return $el;
97 | }
98 |
99 | function isStandardSize(size){
100 | return size && (size.width + 'x' + size.height) in STANDARD_SIZES;
101 | }
102 |
103 | function getCreativeSize(){
104 | let size = {
105 | width: $webView.width(),
106 | height: $webView.height(),
107 | from: 'html'
108 | };
109 |
110 | if (isStandardSize(size)) return size;
111 |
112 | let sizeFromUrl = sniffCreativeSizeFromUrl();
113 |
114 | if (sizeFromUrl && size.width == sizeFromUrl.width && size.height == sizeFromUrl.height){
115 | sizeFromUrl.from = 'html/url';
116 | }
117 |
118 | return sizeFromUrl || size;
119 | }
120 |
121 | function sniffCreativeSizeFromUrl(){
122 | var urlSizeRegEx = /\b\d{2,4}[x\/]\d{2,4}/i,
123 | search = window.location.search.replace(/\banx-mraid-screen=\d{2,4}x\d{2,4}\b/ig, ''),
124 | sizeFromUrl = search.match(urlSizeRegEx),
125 | dimensions;
126 |
127 | if (sizeFromUrl && sizeFromUrl.length){
128 | dimensions = sizeFromUrl[0].split(/[^\d]/);
129 | return { width: +dimensions[0], height: +dimensions[1], from: 'url' };
130 | }
131 |
132 | let widthMatch = search.match(/\bwidth=(\d{2,4})\b/i);
133 | if (!widthMatch || widthMatch.length < 2) return null;
134 |
135 | let heightMatch = search.match(/\bheight=(\d{2,4})\b/i);
136 | if (!heightMatch || heightMatch.length < 2) return null;
137 |
138 | return { width: +widthMatch[1], height: +heightMatch[1], from: 'url' };
139 | }
140 |
141 | function ensureInitialSizeIsSet(size){
142 | if (!initialSize){
143 | initialSize = size || getCreativeSize();
144 | }
145 |
146 | return initialSize;
147 | }
148 |
149 | function isInSelfMadeFrame(){
150 | let query = url.parse(window.location.toString(), true).query;
151 | return query && query[IFRAME_MARKER_NAME];
152 | }
153 |
154 | this.hide = function() { $webView.hide(); };
155 | this.show = function() { $webView.show(); };
156 | this.showClose = function(){ $close.show(); };
157 | this.hideClose = function(){ $close.hide(); };
158 |
159 | this.resetSize = function(){
160 | if (!initialSize) return;
161 |
162 | this.setSize(initialSize.width, initialSize.height);
163 | };
164 |
165 | this.getInitialSize = function() { return ensureInitialSizeIsSet(); };
166 | this.getScreenSize = function() { return screenSize; };
167 | this.getCurrentPosition = this.getSize = function() {
168 | if (!$webView) return {x: 0, y: 0};
169 |
170 | return {
171 | x: 0,
172 | y: 0,
173 | width: $webView.width(),
174 | height: $webView.height()
175 | };
176 | };
177 |
178 | this.getDefaultPosition = function(){
179 | var pos = Object.create(this.getInitialSize());
180 | pos.x = 0;
181 | pos.y = 0;
182 |
183 | return pos;
184 | };
185 |
186 | this.showMessage = function(txt){
187 | var $msg = $('
');
188 |
189 | $msg.append($('
')
190 | .text(txt));
191 |
192 | var $closeBtn = buildCloseButton();
193 |
194 | $closeBtn.click(function(){
195 | $msg.remove();
196 | $(this).remove();
197 | });
198 |
199 | $webView.append($closeBtn);
200 | $webView.append($msg);
201 | $msg.slideDown('fast', function (){ $closeBtn.fadeIn('fast'); });
202 | };
203 |
204 | this.showUrl = function(url){
205 | 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: 0 s '
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(' ', 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 + '' + name + end;
3835 | return tag;
3836 | }
3837 |
3838 | /**
3839 | * Return cdata escaped CDATA `str`.
3840 | */
3841 |
3842 | function cdata(str) {
3843 | return '';
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, '')
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 | })();
--------------------------------------------------------------------------------