Full Code of ElemeFE/bowl for AI

master 00f864f8e4fd cached
32 files
515.0 KB
139.9k tokens
393 symbols
1 requests
Download .txt
Showing preview only (536K chars total). Download the full file or copy to clipboard to get everything.
Repository: ElemeFE/bowl
Branch: master
Commit: 00f864f8e4fd
Files: 32
Total size: 515.0 KB

Directory structure:
gitextract_z5ynrdnk/

├── .babelrc
├── .gitignore
├── CHANGELOG.md
├── LICENSE
├── README.md
├── build/
│   ├── build.js
│   ├── config.js
│   ├── karma.config.js
│   └── release.sh
├── docs/
│   ├── README.md
│   ├── index.html
│   └── zh-cn.md
├── lib/
│   └── bowl.js
├── package.json
├── src/
│   ├── bowl.class.js
│   ├── graph.class.js
│   ├── index.js
│   ├── injector/
│   │   ├── cacheUnit.class.js
│   │   └── injector.class.js
│   └── utils.js
└── test/
    ├── e2e/
    │   ├── assets/
    │   │   ├── a.js
    │   │   ├── b.js
    │   │   ├── c.js
    │   │   └── style.css
    │   ├── test.html
    │   ├── test.js
    │   └── vendor/
    │       ├── expect.js
    │       ├── mocha.css
    │       └── mocha.js
    └── unit/
        ├── index.js
        └── specs/
            ├── graph.spec.js
            └── utils.spec.js

================================================
FILE CONTENTS
================================================

================================================
FILE: .babelrc
================================================
{
  "presets": ["es2015"]
}


================================================
FILE: .gitignore
================================================
*.swp
node_modules
.idea
*.log

test/e2e/bowl.js
notes.md


================================================
FILE: CHANGELOG.md
================================================
## 0.1.0
### Features
- Basic Features

## 0.1.1
- make `key` property required
- handle situations where resources have dependencies with each other

## 0.1.2
- fix bugs

## 0.1.3
- optimize cached item in localStorage
- inject method always returns promise


================================================
FILE: LICENSE
================================================
MIT License

Copyright (c) 2017 Shuang.Wu@ElemeFE<shuang.wu@ele.me>

Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:

The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.

THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.


================================================
FILE: README.md
================================================
<p align="center"><image src="https://github.com/classicemi/bowl.js/blob/develop/assets/logo.png?raw=true" width="128"></p>

# bowl.js
[![npm](https://img.shields.io/npm/v/bowl.js.svg?style=flat-square)](https://www.npmjs.com/package/bowl.js)
[![npm](https://img.shields.io/npm/dt/bowl.js.svg?style=flat-square)](https://www.npmjs.com/package/bowl.js)
[![license](https://img.shields.io/github/license/elemefe/bowl.svg?style=flat-square)](https://github.com/ElemeFE/bowl)
[![GitHub stars](https://img.shields.io/github/stars/elemefe/bowl.svg?style=social&label=Star)](https://github.com/ElemeFE/bowl)
> 🍚 static resources front-end storage solving strategy

## Links
+ [Documentation](https://elemefe.github.io/bowl/)

## Screenshot
![](https://raw.githubusercontent.com/classicemi/bowl/develop/assets/demo.gif)

## Quick Start
For those resources that need to be cached, you do not have to insert `script` tags to the pages. Just write a little piece of JavaScript and **bowl** will take care of it.
```html
<script>
var bowl = new Bowl()
bowl.add([
  { url: 'dist/vendor.[hash].js', key: 'vendor' },
  { url: 'dist/app.[hash].js', key: 'app' },
  { url: 'dist/style.[hash].css', key: 'style' }
])
bowl.inject().then(() => {
  // do some stuff
})
</script>
```
**bowl** will add these resources to cache(currently localStorage). whenever the url of the resources get modified, bowl will update the files in the cache. For more useful functions of **bowl**, just checkout the [API document](https://elemefe.github.io/bowl/).

## Development Setup
After cloning the repo, run:
```shell
$ yarn install # yep, yarn is recommended. :)
```
### Commonly used NPM scripts:
```shell
# watch and auto rebuild lib/bowl.js and lib/bowl.min.js
$ npm run dev

# watch and auto re-run unit tests in Chrome
$ npm run test:unit

# build all dist files
$ npm run build
```

## License
MIT


================================================
FILE: build/build.js
================================================
const rollup = require('rollup');
const fs = require('fs');
const path = require('path');
const uglify = require('uglify-js');
const config = require('./config');
const version = require('../package.json').version;

function getSize(str) {
  return (str.length / 1024).toFixed(2) + 'kb';
}

function blue(str) {
  return '\x1b[1m\x1b[34m' + str + '\x1b[39m\x1b[22m';
}

function write(dest, code) {
  return new Promise(function(resolve, reject) {
    fs.writeFile(dest, code, function(err) {
      if (err) return reject(err);
      console.log(blue(path.relative(process.cwd(), dest)) + ' ' + getSize(code));
      resolve();
    });
  });
}

function build() {
  rollup.rollup(config).then(bundle => {
    const code = bundle.generate(config).code;
    const minified = (config.banner ? `${config.banner}\n` : '') + uglify.minify(code, {
      fromString: true,
      output: {
        screw_ie8: true,
        ascii_only: true
      }
    }).code;
    const libPath = path.resolve(__dirname, '../lib');
    if (!fs.existsSync(libPath)) {
      fs.mkdirSync(libPath);
    }
    write(config.dest, code);
    write(config.dest.replace(/\.js$/, '.min.js'), minified);
  });
}

build();



================================================
FILE: build/config.js
================================================
const path = require('path');
const buble = require('rollup-plugin-buble');
const version = process.env.VERSION || require('../package.json').version;

const banner =
`/*
 * bowl.js v${version}
 * (c) 2016-${new Date().getFullYear()} classicemi
 * Released under the MIT license.
 */`;

const configs = {
  'dev': {
    entry: path.resolve(__dirname, '../src/index.js'),
    dest: path.resolve(__dirname, '../test/e2e/bowl.js'),
    format: 'umd',
    banner,
    moduleName: 'Bowl',
    plugins: [
      buble()
    ]
  },
  'production': {
    entry: path.resolve(__dirname, '../src/index.js'),
    dest: path.resolve(__dirname, '../lib/bowl.js'),
    format: 'umd',
    banner,
    moduleName: 'Bowl',
    plugins: [
      buble()
    ]
  }
};

if (process.env.TARGET) {
  module.exports = configs[process.env.TARGET];
} else {
  module.exports = configs['production'];
}


================================================
FILE: build/karma.config.js
================================================
const webpack = require('webpack');

let webpackConfig = {
  module: {
    loaders: [
      {
        test: /\.js$/,
        loader: 'babel',
        exclude: /node_modules/
      }
    ]
  }
};

// Karma configuration
// Generated on Tue Nov 01 2016 17:43:02 GMT+0800 (CST)

module.exports = function(config) {
  config.set({

    // base path that will be used to resolve all patterns (eg. files, exclude)
    basePath: '',


    // frameworks to use
    // available frameworks: https://npmjs.org/browse/keyword/karma-adapter
    frameworks: ['jasmine'],


    // list of files / patterns to load in the browser
    files: [
      '../test/unit/index.js'
    ],


    // list of files to exclude
    exclude: [
    ],


    // preprocess matching files before serving them to the browser
    // available preprocessors: https://npmjs.org/browse/keyword/karma-preprocessor
    preprocessors: {
      '../test/**/*.js': [ 'webpack' ]
    },

    webpack: webpackConfig,


    // test results reporter to use
    // possible values: 'dots', 'progress'
    // available reporters: https://npmjs.org/browse/keyword/karma-reporter
    reporters: ['progress'],


    // web server port
    port: 9876,


    // enable / disable colors in the output (reporters and logs)
    colors: true,


    // level of logging
    // possible values: config.LOG_DISABLE || config.LOG_ERROR || config.LOG_WARN || config.LOG_INFO || config.LOG_DEBUG
    logLevel: config.LOG_INFO,


    // enable / disable watching file and executing tests whenever any file changes
    autoWatch: true,


    // start these browsers
    // available browser launchers: https://npmjs.org/browse/keyword/karma-launcher
    // browsers: ['Chrome', 'ChromeCanary', 'Firefox', 'Safari', 'IE'],
    browsers: ['Chrome'],


    // Continuous Integration mode
    // if true, Karma captures browsers, runs the tests and exits
    singleRun: false,

    // Concurrency level
    // how many browser should be started simultaneous
    concurrency: Infinity
  })
}


================================================
FILE: build/release.sh
================================================
set -e

if [[ -z $1 ]]; then
  echo "Enter new version: "
  read VERSION
else
  VERSION=$1
fi

read -p "Releasing v$VERSION - are you sure? (y/n)" -n 1 -r
echo
if [[ $REPLY =~ ^[Yy]$ ]]; then
  echo "Releasing v$VERSION ..."

  # build
  VERSION=$VERSION npm run build

  # commit
  git add -A
  git commit -m "[build] v$VERSION"
  npm version $VERSION --message "[release] $VERSION"

  # publish
  git push eleme refs/tags/v$VERSION
  git push eleme master
  npm publish
fi


================================================
FILE: docs/README.md
================================================
<p align="center"><image src="https://github.com/classicemi/bowl.js/blob/develop/assets/logo.png?raw=true" width="128"></p>

# bowl.js
[![npm](https://img.shields.io/npm/v/bowl.js.svg?style=flat-square)](https://www.npmjs.com/package/bowl.js)
[![npm](https://img.shields.io/npm/dt/bowl.js.svg?style=flat-square)](https://www.npmjs.com/package/bowl.js)
[![license](https://img.shields.io/github/license/elemefe/bowl.svg?style=flat-square)](https://github.com/ElemeFE/bowl)
[![GitHub stars](https://img.shields.io/github/stars/elemefe/bowl.svg?style=social&label=Star)](https://github.com/ElemeFE/bowl)

**bowl** is a loader that caches scripts and stylesheets with localStorage. After receiving any scripts or stylesheets, this tiny JavaScript library will save them to the browser's localStorage. When the file is requested next time, bowl will read it from localStorage and inject it to the webpage.

## Installation
``` shell
$ npm install bowl.js
```
then insert an `script` tag to your page(`head` tag is recommended):
``` html
<script src="https://unpkg.com/bowl.js/lib/bowl.min.js"></script>
```
or
``` html
<script src="node_modules/bowl.js/lib/bowl.min.js"></script>
```

## Demo
For those resources that need to be cached, you do not have to insert `script` tags to the pages. Just write a little piece of JavaScript and **bowl** will take care of it.
```html
<script>
var bowl = new Bowl()
bowl.add([
  { url: 'dist/vendor.[hash].js', key: 'vendor' },
  { url: 'dist/app.[hash].js', key: 'app' },
  { url: 'dist/style.[hash].css', key: 'style' }
])
bowl.inject().then(() => {
  // do some stuff
})
</script>
```
**bowl** will add these resources to cache(currently localStorage). whenever the url of the resources get modified, bowl will update the files in the cache. For more useful functions of **bowl**, just checkout the API document.

## Development Setup
After cloning the repo, run:
```shell
$ yarn install # yep, yarn is recommended. :)
```
### Commonly used NPM scripts:
```shell
# watch and auto rebuild lib/bowl.js and lib/bowl.min.js
$ npm run dev

# watch and auto re-run unit tests in Chrome
$ npm run test:unit

# build all dist files
$ npm run build
```

## Project Structure
+ **`assets`**: logo files.
+ **`build`**: contains build-related configuration files.
+ **`docs`**: project's home page and documents.
+ **`lib`**: contains build files for distribution, after running **`npm run build`** script, files in this directory will be updated as well.
+ **`test`**: contains all tests. The unit tests are written with [Jasmine](http://jasmine.github.io/2.5/introduction) and run with [Karma](http://karma-runner.github.io/1.0/index.html), e2e tests are written with [Mocha](https://mochajs.org/).
+ **`src`**: source code directory.

## API
**bowl** will add a class named `Bowl` to the global object, which is `window` in browsers. Bowl instance has several methods for you.
*localStorage and Promise are required by bowl. You can use a Promise Polyfill if your project needs to be compatible with browsers that do not support Promise.*

### `bowl.configure`
`bowl.configure(config)`

*config:* an object contains custom settings of bowl instance, supported settings are:
+ **timeout**: time limit of fetching resources(in milliseconds).

**Examples**
```javascript
bowl.configure({
  timeout: 10000
})
```

### `bowl.add`
`bowl.add(resources)`

*scripts:* an array of objects with the following fields:
+ **url**(required): *String* the URI of the resources to be handled. Because of the CORS restrictions, the URI should be on the same origin as the caller. Resources which are cross-origin won't be cached. You can Either use an absolute address or just use path.
+ **key**(required): *String* the name for **bowl** to identify the resource, if you don't specify this field, the resource will be ignored.
+ **noCache**: *Boolean* defaults to false. Bowl won't cache the resource if it's true.
+ **dependencies**: *Array* an array containing the resource's dependencies' keys.
+ **expireAfter**: *Number* specify how long before the resource is expired after being injected(in milliseconds).
+ **expireWhen**: *Number* specify the time when the resource is expired(in milliseconds).
**`expireWhen`'s priority is higher than `expireAfter`'**

**Examples**
```javascript
bowl.add([
  { url: '/vendor.js', key: 'vendor', expireAfter: 30 * 24 * 60 * 60 * 1000 }
  { url: '/main.js', key: 'main', dependencies: ['vendor'] }
])
```

`bowl.add(resource)`

*resource:* single resource object
```javascript
bowl.add({ url: '/main.js', key: 'main' })
```

### `bowl.inject`
`bowl.inject()`

this method triggers the handling of the scripts added by `bowl.add()` method. Bowl will check if the resource has been stored in the localStorage. If not, bowl will fetch it from the server and save it to cache(localStorage).
```javascript
bowl.inject().then(() => {
  // do some stuff
})
```

### `bowl.remove`
`bowl.remove(resource)`  
*resource:* this parameter supports two types:  
*String:* indicates the key of the resource to be removed.

*Object:* an object with the following fields:
+ **url**: url of the resource you want to remove from the controlling scope of bowl.
+ **key**: key of the resource to be removed.

`bowl.remove()`  
Parameter `resource` is optional. When `resource` is not not Provided, bowl will remove all the resources from bowl instance and local storage.

## License
MIT


================================================
FILE: docs/index.html
================================================
<!DOCTYPE html>
<html lang="en">
<head>
  <meta charset="UTF-8">
  <title>bowl</title>
  <meta name="description" content="static resources front-end storage solving strategy">
  <meta name="viewport" content="width=device-width, user-scalable=no, initial-scale=1.0, maximum-scale=1.0, minimum-scale=1.0">
  <link rel="stylesheet" href="//unpkg.com/docsify/themes/vue.css">
</head>
<body>
  <nav>
    <a href="/bowl/#/">En</a>
    <a href="/bowl/#/zh-cn">中文</a>
  </nav>
  <div id="app"></div>
</body>
<script src="//unpkg.com/docsify" data-router></script>
</html>


================================================
FILE: docs/zh-cn.md
================================================
<p align="center"><image src="https://github.com/classicemi/bowl.js/blob/develop/assets/logo.png?raw=true" width="128"></p>

# bowl.js
[![npm](https://img.shields.io/npm/v/bowl.js.svg?style=flat-square)](https://www.npmjs.com/package/bowl.js)
[![npm](https://img.shields.io/npm/dt/bowl.js.svg?style=flat-square)](https://www.npmjs.com/package/bowl.js)
[![license](https://img.shields.io/github/license/elemefe/bowl.svg?style=flat-square)](https://github.com/ElemeFE/bowl)
[![GitHub stars](https://img.shields.io/github/stars/elemefe/bowl.svg?style=social&label=Star)](https://github.com/ElemeFE/bowl)

**bowl** 是一个用 localStorage 来缓存脚本和样式资源的加载器。在获取脚本和样式之后,这个小巧的 JavaScript 库会将它们保存到浏览器的 localStorage 中。当这个文件下次再被请求的时候,bowl 将会从 localStorage 中读取并将它插入到页面中。

## 安装
``` shell
$ npm install bowl.js
```
然后,在页面中插入一个 `script` 标签(推荐在 `head` 标签中插入):
``` html
<script src="https://unpkg.com/bowl.js/lib/bowl.min.js"></script>
```
or
``` html
<script src="node_modules/bowl.js/lib/bowl.min.js"></script>
```

## 示例
对于那些需要被缓存的资源,你不需要在页面中写入标签。只需要写一些简单的 JS 脚本,bowl 会替你处理接下来的事情。
```html
<script>
var bowl = new Bowl()
bowl.add([
  { url: 'dist/vendor.[hash].js', key: 'vendor' },
  { url: 'dist/app.[hash].js', key: 'app' },
  { url: 'dist/style.[hash].css', key: 'style' }
])
bowl.inject().then(() => {
  // 你的代码
})
</script>
```
**bowl** 会把这些资源加入缓存(目前是 localStorage)。只要资源的 URL 发生了改变,bowl 会更新缓存中的资源。要了解更多 **bowl** 的功能,请参考 API 文档。  
**受同源策略和内部实现机制限制,bowl 只能对非跨域资源进行缓存,跨域资源无法被缓存,但仍然可以使用 bowl 将它们注入页面。**

## 设置开发环境
克隆仓库之后,运行:
```shell
$ yarn install # 是的,推荐使用 yarn。 :)
```
### 常用 NPM 脚本:
```shell
# 监听并自动重新构建 lib/bowl.js 和 lib/bowl.min.js 文件
$ npm run dev

# 监听并自动在 Chrome 中重新执行单元测试
$ npm run test:unit

# 构建所有发布文件
$ npm run build
```

## 项目结构
+ **`assets`**: logo 文件。
+ **`build`**: 包含所有和构建过程相关的配置文件。
+ **`docs`**: 项目主页及文档。
+ **`lib`**: 包含用来发布的文件,执行 `npm run build` 脚本后,这个目录中的文件会被更新。
+ **`test`**: 包含所有的测试,单元测试使用 [Jasmine](http://jasmine.github.io/2.5/introduction) 编写,依赖 [Karma](http://karma-runner.github.io/1.0/index.html) 执行,e2e 测试使用 [Mocha](https://mochajs.org/)。
+ **`src`**: 源代码目录。

## API
**bowl** 将会在全局对象上添加一个 `Bowl` 类,在浏览器环境中是在 window 对象上。Bowl 的实例包含了一些方法供你使用。  
*bowl 的正常运行需要 localStorage 和 Promise 的支持,如果你的项目需要兼容不支持 Promise 的浏览器,你可以使用 Promise 的 Polyfill,在全局对象上添加 Promise 属性供 bowl 识别即可。*

### `bowl.configure`
`bowl.configure(config)`

*config:* 一个包含 bowl 实例自定义设置的对象,支持的配置项有:
+ **timeout**: 获取资源操作的时间限制(单位为毫秒)。

**示例**
```javascript
bowl.configure({
  timeout: 10000
})
```

### `bowl.add`
`bowl.add(resources)`

*resources:* 包含一系列对象的数组,对象支持的属性如下:
+ **url**(必需): *String* 需要被处理的脚本资源 URI 地址。 由于跨域问题的限制,URI 必需和页面同域,如果不同域的话,资源将不会被缓存。你既可以使用绝对路径,也可以省略 origin。
+ **key**(必需): *String* 用来让 **bowl** 识别资源的名字,缺少这个属性的资源将被忽略。
+ **noCache**: *Boolean* 默认为 `false`。当它为 `true` 时,Bowl 将不会缓存这个资源。
+ **dependencies**: *Array* 包含该资源所有依赖资源的 key 的数组。
+ **expireAfter**: *Number* 指定资源自第一次被缓存起经过多久过期,单位为毫秒。
+ **expireWhen**: *Number* 指定资源在哪个时间点过期,单位为毫秒。
**如果同时指定了 `expireAfter` 和 `expireWhen`,则以 `expireWhen` 为准。**

**示例**
```javascript
bowl.add([
  { url: '/vendor.js', key: 'vendor', expireAfter: 30 * 24 * 60 * 60 * 1000 }
  { url: '/main.js', key: 'main', dependencies: ['vendor'] }
])
```

`bowl.add(resource)`

*resource:* 包含单个资源的对象:
``` javascript
bowl.add({ url: '/main.js', key: 'main' })
```

### `bowl.inject`
`bowl.inject()`

这个方法触发对在 `bowl.add()` 方法中添加的资源进行的处理。Bowl 会检查资源是否在 localStorage 中有缓存。如果没有,bowl 将会从服务器获取资源并将它缓存至 localStorage。该方法返回一个 Promise,当资源全部被注入后 Promise 的回调函数会被执行。
```javascript
bowl.inject().then(() => {
  // 你的代码
})
```

### `bowl.remove`
`bowl.remove(resource)`  
*resource:* 这个参数支持两种类型:  
*String:* 表示将要从 bowl 中被移除的资源的 key。

*Object:* 包含以下属性的对象:
+ **url**: 想要从 bowl 中被移除的资源的 url。
+ **key**: 要被移除资源的 key。

`bowl.remove()`  
参数 `resource` 是可选的。当没有提供 `resource` 参数时,bowl 将会从 bowl 实例和缓存中移除所有的资源。

## 开源许可类型
MIT

## FAQ
Q: 浏览器不是有缓存吗,用浏览器缓存不就好了?  
A: 浏览器的缓存并不可靠,用户有可能关闭缓存或手动清除缓存,另外即使有缓存,如果不是通过 Expire 或 Cache-Control 设定强缓存的话,每次还是要向服务器发送请求确认资源是否过期,bowl 可以完全控制这一环节,也不会发送任何请求。

Q: 它和 basket.js 有什么区别?  
A: basket.js 是很好的项目,bowl 也是受到了它的启发。目前和 basket 相比,bowl 还可以不需要额外配置就可以缓存加载 CSS 资源,不支持缓存(如跨域)和不需要缓存的资源也可以使用 bowl 进行普通加载。资源之间存在相互依赖关系的可以通过声明依赖关系来让 bowl 进行分析并按照依赖顺序加载,并不需要类似 require.js 之类的解决方案(实际上 require.js 也要在模块内部声明依赖关系的)。

Q: 未来是否会和 webpack 或 cooking 进行整合?  
A: 这是个好问题,bowl 未来还有很多事情要做,和周边工具进行整合是很重要的环节,不仅是 webpack 和 cooking,团队内部已经做的和正在做的还有不少好的东西,只要是合适的都有可能接入。

Q: 将来 bowl 还会怎么发展?  
A: 可以做的还有很多,但有些是不是要做可能还需要讨论,目前基本确定要做的包括但不限于:配合服务端进行资源差异化更新,降低更新资源时的数据传输量;接入 web worker 或 indexedDB,提供更新的缓存解决方案;支持更多类型资源的缓存,如字体、图片等,etc


================================================
FILE: lib/bowl.js
================================================
/*
 * bowl.js v0.1.4
 * (c) 2016-2017 classicemi
 * Released under the MIT license.
 */
(function (global, factory) {
  typeof exports === 'object' && typeof module !== 'undefined' ? module.exports = factory() :
  typeof define === 'function' && define.amd ? define(factory) :
  (global.Bowl = factory());
}(this, (function () { 'use strict';

var global$1 = window;

/**
 * check if the argument is an instance of Object
 */
function isObject(obj) {
  return Object.prototype.toString.call(obj) === '[object Object]'
}

/**
 * check if the argument is an instance of String
 */
function isString(str) {
  return Object.prototype.toString.call(str) === '[object String]'
}

/**
 * check if the argument is an instance of Array
 */
function isArray(arr) {
  return Object.prototype.toString.call(arr) === '[object Array]'
}

/**
 * check if a given path is a valid url
 */
function isUrl(path) {
  return /^(https?|\/\/)/.test(path)
}

/**
 * make and return a copy of `obj`
 */
function clone(obj) {
  var result = null;
  if (isObject(obj)) {
    result = {};
    for (var key in obj) {
      if (isObject(obj[key]) || isArray(obj[key])) {
        result[key] = clone(obj[key]);
        continue
      }
      result[key] = obj[key];
    }
  } else if (isArray(obj)) {
    result = [];
    obj.forEach(function (item, index) {
      if (isObject(item) || isArray(item)) {
        result[index] = clone(item);
        return
      }
      result[index] = item;
    });
  } else {
    result = obj;
  }
  return result
}

/**
 * merge `target` with `source` and return a new object
 * param force {Boolean} whether to overwrite property in `target` whice has a same name in `source`
 */
function merge(target, source, force) {
  if ( force === void 0 ) force = false;

  if (!isObject(target) || !isObject(source)) { return }
  var result = clone(target);
  for (var key in source) {
    if (!result.hasOwnProperty(key)) {
      result[key] = clone(source[key]);
    } else {
      result[key] = force ?
          clone(source[key]) :
          result[key];
    }
  }
  return result
}

/**
 * test if two urls are cross-origin
 */
function isCrossOrigin(hostUrl, targetUrl) {
  var originRegExp = /^(https?:\/\/)?([^\/:]+)?:?(\d+)?/;
  var defaultProtocol = 'http://';
  var defaultPort = '80';
  var hostOrigin = hostUrl.match(originRegExp);
  var targetOrigin = targetUrl.match(originRegExp);var assign;
  (assign = [
    hostOrigin[1] ? hostOrigin[1] : defaultProtocol,
    hostOrigin[2] ? hostOrigin[2] : location.hostname,
    hostOrigin[3] ? hostOrigin[3] : defaultPort
  ], hostOrigin[1] = assign[0], hostOrigin[2] = assign[1], hostOrigin[3] = assign[2]);
  if (!targetOrigin[3]) {
    if (targetOrigin[2]) {
      targetOrigin[3] = defaultPort;
    } else {
      targetOrigin[3] = hostOrigin[3];
    }
  }
  var assign$1;
  (assign$1 = [
    targetOrigin[1] ? targetOrigin[1] : hostOrigin[1],
    targetOrigin[2] ? targetOrigin[2] : hostOrigin[2]
  ], targetOrigin[1] = assign$1[0], targetOrigin[2] = assign$1[1]);
  hostOrigin[0] = "" + (hostOrigin[1]) + (hostOrigin[2]) + ":" + (hostOrigin[3]);
  targetOrigin[0] = "" + (targetOrigin[1]) + (targetOrigin[2]) + ":" + (targetOrigin[3]);
  return hostOrigin[0] !== targetOrigin[0]
}

var storage = global$1.localStorage;
/**
 * get item from local storage
 */
function get$1(key) {
  if (isString(key)) {
    return JSON.parse(storage.getItem(key))
  }
  if (isArray(key)) {
    return key.map(function (k) { return JSON.parse(storage.getItem(k)); })
  }
}

/**
 * [set description]
 * @param {String} key key of the object to be cached
 * @param {Object} o   object to be cached
 */
function set(key, o) {
  if (!isObject(o)) {
    return
  }
  storage.setItem(key, JSON.stringify(o));
}

/**
 * remove data object from cache
 */
function remove$1(key) {
  if (isString(key)) {
    return storage.removeItem(key)
  }
  if (isArray(key)) {
    return key.forEach(function (k) { return storage.removeItem(k); })
  }
}

var CacheUnit = function CacheUnit(ingredient) {
  this.key = ingredient.key;
  this.url = ingredient.url;
  this.content = ingredient.content;
  this.expire = ingredient.expire;
};

var Injector = function Injector(config) {
  this.config = config;
};

Injector.prototype.inject = function inject (o) {
    var this$1 = this;

  if (o.noCache) {
    return this.normalInject(o)
  }

  var local = get$1(o.key);
  var ext = o.ext;

  var current = new Date().getTime();
  var expire = o.expireAfter ? (new Date()).getTime() + o.expireAfter : null;
  expire = o.expireWhen ? o.expireWhen : expire;
  o.expire = expire;

  if (local && o.url === local.url && (!local.expire || current < local.expire)) { // hit
    return new Promise(function (resolve, reject) {
      try {
        this$1.appendToPage(ext, local.content);
        resolve();
      } catch (err) {
        reject(err);
      }
    })
  } else {
    return new Promise(function (resolve, reject) {
      this$1.fetchByXHR(o.url).then(function (data) {
        o.content = data.content;
        var unit = new CacheUnit(o);
        set(unit.key, unit);
        this$1.appendToPage(ext, o.content);
        resolve();
      }).catch(function (err) { return reject(err); });
    })
  }
};

Injector.prototype.appendToPage = function appendToPage (ext, content) {
  switch (ext) {
    case 'css':
      var style = document.createElement('style');
      style.innerText = content;
      document.getElementsByTagName('head')[0].appendChild(style);
      break
    case 'js':
      var script = document.createElement('script');
      script.text = content;
      script.defer = true;
      document.getElementsByTagName('head')[0].appendChild(script);
      break
  }
};

Injector.prototype.normalInject = function normalInject (o) {
  var promise;
  switch (o.ext) {
    case 'js':
      promise = new Promise(function (resolve, reject) {
        var script = document.createElement('script');
        script.src = o.url;
        script.defer = true;
        document.getElementsByTagName('body')[0].appendChild(script);
        script.onload = function () {
          resolve();
        };
        script.onerror = function () {
          reject();
        };
      });
      break
    case 'css':
      promise = new Promise(function (resolve, reject) {
        var link = document.createElement('link');
        link.rel = 'stylesheet';
        link.href = o.url;
        document.getElementsByTagName('head')[0].appendChild(link);
        link.onload = function () {
          resolve();
        };
        link.onerror = function () {
          reject();
        };
      });
      break
    default:
      break
  }
  return promise
};

Injector.prototype.fetchByXHR = function fetchByXHR (url) {
  var xhr = new XMLHttpRequest();
  var promise = new Promise(function (resolve, reject) {
    xhr.open('GET', url);
    xhr.onreadystatechange = function () {
      if (xhr.readyState === 4) {
        if ((xhr.status === 200) || ((xhr.status === 0) && xhr.responseText)) {
          resolve({
            content: xhr.responseText
          });
        } else {
          reject(new Error(xhr.statusText));
        }
      }
    };
  });
  setTimeout(function () {
    if (xhr.readyState < 4) {
      xhr.abort();
    }
  }, this.config.timeout);
  xhr.send();
  return promise
};

var Graph = function Graph(vertices) {
  this.vertices = isObject(vertices) ? merge({}, vertices) : {};
};

Graph.prototype.addVertex = function addVertex (v) {
  if (isObject(this.vertices[v])) {
    return
  }
  var newVertex = {
    name: v,
    prev: 0,
    next: 0,
    adjList: []
  };
  this.vertices[v] = newVertex;
};

Graph.prototype.addEdge = function addEdge (begin, end) {
  // check if two vertices exist
  if (!this.vertices[begin] ||
      !this.vertices[end] ||
      this.vertices[begin].adjList.indexOf(end) > -1) {
    return
  }
  ++this.vertices[begin].next;
  this.vertices[begin].adjList.push(end);
  ++this.vertices[end].prev;
};

Graph.prototype.hasCycle = function hasCycle () {
  var cycleTestStack = [];
  var vertices = merge({}, this.vertices);
  var popVertex = null;

  for (var k in vertices) {
    if (vertices[k].prev === 0) {
      cycleTestStack.push(vertices[k]);
    }
  }
  while (cycleTestStack.length > 0) {
    popVertex = cycleTestStack.pop();
    delete vertices[popVertex.name];
    popVertex.adjList.forEach(function (nextVertex) {
      --vertices[nextVertex].prev;
      if (vertices[nextVertex].prev === 0) {
        cycleTestStack.push(vertices[nextVertex]);
      }
    });
  }
  return Object.keys(vertices).length > 0
};

Graph.prototype.getBFS = function getBFS () {
  if (this.hasCycle()) {
    throw new Error('There are cycles in resource\'s dependency relations')
    return
  }
  var result = [];
  var graphCopy = new Graph(this.vertices);
  while (Object.keys(graphCopy.vertices).length) {
    var noPrevVertices = [];
    for (var k in graphCopy.vertices) {
      if (graphCopy.vertices[k].prev === 0) {
        noPrevVertices.push(k);
      }
    }
    if (noPrevVertices.length) {
      result.push(noPrevVertices);
      noPrevVertices.forEach(function (vertex) {
        graphCopy.vertices[vertex].adjList.forEach(function (next) {
          --graphCopy.vertices[next].prev;
        });
        delete graphCopy.vertices[vertex];
      });
    }
  }
  return result
};

var global = window;
var prefix = 'bowl-';
var isSystemDependencyAvailable = global.localStorage && global.Promise;

var Bowl$1 = function Bowl$1() {
  this.config = {
    timeout: 60000,
    expireAfter: null,
    expireWhen: null
  };
  var ingredients = [];
  Object.defineProperty(this, 'ingredients', {
    __proto__: null,
    configurable: true,
    get: function get() {
      return ingredients
    }
  });
  this.injector = new Injector(this.config);
};

/**
 * @param {Object} custom config object to be merged with the default config
 */
Bowl$1.prototype.configure = function configure (opts) {
  this.config = merge(this.config, opts, true);
};

/**
 * @param {Object, Array} items to be cached, wrapped in supported structure
 */
Bowl$1.prototype.add = function add (opts) {
    var this$1 = this;

  if (!isArray(opts)) {
    if (isObject(opts)) {
      opts = [opts];
    } else {
      return
    }
  }

  /**
   * take options and return corresponding ingredient
   */
  var makeIngredient = function (opt) {
    var option = merge(this$1.config, opt, true);
    var ingredient = {};
    var now = new Date().getTime();
    var isUrl$$1 = isUrl(option.url);
    var extRE = /\.(\w+)(\?.+)?$/i;

    ingredient.key = "" + prefix + (option.key || option.url);

    ingredient.expireAfter = option.expireAfter;
    ingredient.expireWhen = option.expireWhen;

    ingredient.url = isUrl$$1 ?
      option.url :
      ((global.location.origin) + "/" + (option.url.replace(new RegExp('^\/*'), '')));

    if (!isSystemDependencyAvailable) {
      ingredient.noCache = true;
    } else {
      ingredient.noCache = !!option.noCache;
      if (isCrossOrigin(global.location.origin, ingredient.url)) {
        ingredient.noCache = true;
      }
    }

    var match = ingredient.url.match(extRE);
    ingredient.ext = match ? match[1] : match;

    ingredient.dependencies = option.dependencies;

    return ingredient
  };

  var handle = function (obj) {
    if (!obj.key || !/^[a-zA-z0-9_]+$/.test(obj.key)) {
      throw new Error('invalid key of bowl ingredient')
      return
    }
    if (!obj.url) {
      throw new Error('no valid url of bowl ingredient')
      return
    }
    var ingredient = makeIngredient(obj);
    var existingIndexFound = this$1.ingredients.findIndex(function (item) {
      return item.key === ingredient.key
    });
    if (existingIndexFound > -1) {
      this$1.ingredients.splice(existingIndexFound, 1, ingredient);
      return
    }
    this$1.ingredients.push(ingredient);
  };

  opts.forEach(function (opt) { return handle(opt); });
};

Bowl$1.prototype.inject = function inject () {
    var this$1 = this;

  if (!this.ingredients.length) { return Promise.resolve() }

  var ingredientsGraph = new Graph();
  this.ingredients.forEach(function (item) { return ingredientsGraph.addVertex(item.key); });
  this.ingredients.forEach(function (item) {
    if (item.dependencies && item.dependencies.length) {
      item.dependencies.forEach(function (dep) {
        ingredientsGraph.addEdge(("" + prefix + dep), item.key);
      });
    }
  });
  var resolvedIngredients = ingredientsGraph.getBFS();

  var batchFetch = function (group) {
    var fetches = [];
    group.forEach(function (item) {
      fetches.push(this$1.injector.inject(this$1.ingredients.find(function (ingredient) { return ingredient.key === item; })));
    });
    return Promise.all(fetches)
  };

  var ret = Promise.resolve();
  resolvedIngredients.forEach(function (group) {
    ret = ret.then(function() {
      return batchFetch(group)
    });
  });
  return ret
};

Bowl$1.prototype.remove = function remove (rule) {
    var this$1 = this;

  if (!rule) {
    var keys = this.ingredients.map(function (item) { return item.key.replace(new RegExp(("^" + prefix), 'i'), ''); });
    keys.forEach(function (key) { return this$1.remove(key); });
    return
  }
  if (isString(rule)) {
    var key = "" + prefix + rule;
    var index = this.ingredients.findIndex(function (item) { return item.key === key; });
    this.ingredients.splice(index, 1);
    remove$1(key);
  } else if (isArray(rule)) {
    var key$1 = "" + prefix + (rule.key ? rule.key : rule.url ? rule.url : '');
    rule.forEach(function (key) { return this$1.remove(key); });
    return
  }
};

return Bowl$1;

})));


================================================
FILE: package.json
================================================
{
  "name": "bowl.js",
  "version": "0.1.4",
  "description": "static resources front-end storage solving strategy",
  "main": "lib/bowl.js",
  "directories": {
    "test": "test"
  },
  "scripts": {
    "dev": "TARGET=dev rollup -w -c build/config.js & node node_modules/.bin/http-server test/e2e",
    "build": "TARGET=production rm -rf lib && node build/build.js",
    "test:unit": "karma start build/karma.config.js",
    "release": "bash build/release.sh"
  },
  "repository": {
    "type": "git",
    "url": "git+https://github.com/elemefe/bowl.git"
  },
  "author": "classicemi <wushuang_1227@163.com> (https://github.com/classicemi)",
  "license": "MIT",
  "bugs": {
    "url": "https://github.com/classicemi/bowl/issues"
  },
  "homepage": "https://elemefe.github.io/bowl/",
  "devDependencies": {
    "babel-cli": "^6.18.0",
    "babel-loader": "^6.2.7",
    "babel-preset-es2015": "^6.18.0",
    "http-server": "^0.9.0",
    "jasmine": "^2.5.2",
    "karma": "^1.3.0",
    "karma-chrome-launcher": "^2.0.0",
    "karma-jasmine": "^1.0.2",
    "karma-webpack": "^1.8.0",
    "rollup": "^0.36.3",
    "rollup-plugin-buble": "^0.14.0",
    "rollup-watch": "^2.5.0",
    "uglify-js": "^2.7.4",
    "webpack": "^1.13.3"
  }
}


================================================
FILE: src/bowl.class.js
================================================
import * as utils from './utils'
import Injector from './injector/injector.class'
import Graph from './graph.class'

const global = window
const prefix = 'bowl-'
const isSystemDependencyAvailable = global.localStorage && global.Promise

export default class Bowl {
  constructor() {
    this.config = {
      timeout: 60000,
      expireAfter: null,
      expireWhen: null
    }
    const ingredients = []
    Object.defineProperty(this, 'ingredients', {
      __proto__: null,
      configurable: true,
      get() {
        return ingredients
      }
    })
    this.injector = new Injector(this.config)
  }

  /**
   * @param {Object} custom config object to be merged with the default config
   */
  configure(opts) {
    this.config = utils.merge(this.config, opts, true)
  }

  /**
   * @param {Object, Array} items to be cached, wrapped in supported structure
   */
  add(opts) {
    if (!utils.isArray(opts)) {
      if (utils.isObject(opts)) {
        opts = [opts]
      } else {
        return
      }
    }

    /**
     * take options and return corresponding ingredient
     */
    const makeIngredient = (opt) => {
      const option = utils.merge(this.config, opt, true)
      const ingredient = {}
      const now = new Date().getTime()
      const isUrl = utils.isUrl(option.url)
      const extRE = /\.(\w+)(\?.+)?$/i

      ingredient.key = `${prefix}${option.key || option.url}`

      ingredient.expireAfter = option.expireAfter
      ingredient.expireWhen = option.expireWhen

      ingredient.url = isUrl ?
        option.url :
        `${global.location.origin}/${option.url.replace(new RegExp('^\/*'), '')}`

      if (!isSystemDependencyAvailable) {
        ingredient.noCache = true
      } else {
        ingredient.noCache = !!option.noCache
        if (utils.isCrossOrigin(global.location.origin, ingredient.url)) {
          ingredient.noCache = true
        }
      }

      const match = ingredient.url.match(extRE)
      ingredient.ext = match ? match[1] : match

      ingredient.dependencies = option.dependencies

      return ingredient
    }

    const handle = (obj) => {
      if (!obj.key || !/^[a-zA-z0-9_]+$/.test(obj.key)) {
        throw new Error('invalid key of bowl ingredient')
        return
      }
      if (!obj.url) {
        throw new Error('no valid url of bowl ingredient')
        return
      }
      const ingredient = makeIngredient(obj)
      const existingIndexFound = this.ingredients.findIndex(item => {
        return item.key === ingredient.key
      })
      if (existingIndexFound > -1) {
        this.ingredients.splice(existingIndexFound, 1, ingredient)
        return
      }
      this.ingredients.push(ingredient)
    }

    opts.forEach(opt => handle(opt))
  }

  inject() {
    if (!this.ingredients.length) return Promise.resolve()

    const ingredientsGraph = new Graph()
    this.ingredients.forEach(item => ingredientsGraph.addVertex(item.key))
    this.ingredients.forEach(item => {
      if (item.dependencies && item.dependencies.length) {
        item.dependencies.forEach(dep => {
          ingredientsGraph.addEdge(`${prefix}${dep}`, item.key)
        })
      }
    })
    const resolvedIngredients = ingredientsGraph.getBFS()

    const batchFetch = (group) => {
      const fetches = []
      group.forEach(item => {
        fetches.push(this.injector.inject(this.ingredients.find(ingredient => ingredient.key === item)))
      })
      return Promise.all(fetches)
    }

    let ret = Promise.resolve()
    resolvedIngredients.forEach(group => {
      ret = ret.then(function() {
        return batchFetch(group)
      })
    })
    return ret
  }

  remove(rule) {
    if (!rule) {
      let keys = this.ingredients.map(item => item.key.replace(new RegExp(`^${prefix}`, 'i'), ''))
      keys.forEach(key => this.remove(key))
      return
    }
    if (utils.isString(rule)) {
      const key = `${prefix}${rule}`
      const index = this.ingredients.findIndex(item => item.key === key)
      this.ingredients.splice(index, 1)
      utils.remove(key)
    } else if (utils.isArray(rule)) {
      const key = `${prefix}${rule.key ? rule.key : rule.url ? rule.url : ''}`
      rule.forEach(key => this.remove(key))
      return
    }
  }

}


================================================
FILE: src/graph.class.js
================================================
import {
  isString,
  isObject,
  merge
} from './utils'

export default class Graph {
  constructor(vertices) {
    this.vertices = isObject(vertices) ? merge({}, vertices) : {}
  }

  addVertex(v) {
    if (isObject(this.vertices[v])) {
      return
    }
    const newVertex = {
      name: v,
      prev: 0,
      next: 0,
      adjList: []
    }
    this.vertices[v] = newVertex
  }

  addEdge(begin, end) {
    // check if two vertices exist
    if (!this.vertices[begin] ||
        !this.vertices[end] ||
        this.vertices[begin].adjList.indexOf(end) > -1) {
      return
    }
    ++this.vertices[begin].next
    this.vertices[begin].adjList.push(end)
    ++this.vertices[end].prev
  }

  hasCycle() {
    const cycleTestStack = []
    const vertices = merge({}, this.vertices)
    let popVertex = null

    for (let k in vertices) {
      if (vertices[k].prev === 0) {
        cycleTestStack.push(vertices[k])
      }
    }
    while (cycleTestStack.length > 0) {
      popVertex = cycleTestStack.pop()
      delete vertices[popVertex.name]
      popVertex.adjList.forEach(nextVertex => {
        --vertices[nextVertex].prev
        if (vertices[nextVertex].prev === 0) {
          cycleTestStack.push(vertices[nextVertex])
        }
      })
    }
    return Object.keys(vertices).length > 0
  }

  getBFS() {
    if (this.hasCycle()) {
      throw new Error('There are cycles in resource\'s dependency relations')
      return
    }
    const result = []
    const graphCopy = new Graph(this.vertices)
    while (Object.keys(graphCopy.vertices).length) {
      const noPrevVertices = []
      for (let k in graphCopy.vertices) {
        if (graphCopy.vertices[k].prev === 0) {
          noPrevVertices.push(k)
        }
      }
      if (noPrevVertices.length) {
        result.push(noPrevVertices)
        noPrevVertices.forEach(vertex => {
          graphCopy.vertices[vertex].adjList.forEach(next => {
            --graphCopy.vertices[next].prev
          })
          delete graphCopy.vertices[vertex]
        })
      }
    }
    return result
  }
}


================================================
FILE: src/index.js
================================================
import Bowl from './bowl.class'

export default Bowl


================================================
FILE: src/injector/cacheUnit.class.js
================================================
export default class CacheUnit {
  constructor(ingredient) {
    this.key = ingredient.key
    this.url = ingredient.url
    this.content = ingredient.content
    this.expire = ingredient.expire
  }
}


================================================
FILE: src/injector/injector.class.js
================================================
import * as utils from '../utils'
import Unit from './cacheUnit.class'

export default class Injector {
  constructor(config) {
    this.config = config
  }

  inject(o) {
    if (o.noCache) {
      return this.normalInject(o)
    }

    const local = utils.get(o.key)
    const ext = o.ext

    const current = new Date().getTime()
    let expire = o.expireAfter ? (new Date()).getTime() + o.expireAfter : null
    expire = o.expireWhen ? o.expireWhen : expire
    o.expire = expire

    if (local && o.url === local.url && (!local.expire || current < local.expire)) { // hit
      return new Promise((resolve, reject) => {
        try {
          this.appendToPage(ext, local.content)
          resolve()
        } catch (err) {
          reject(err)
        }
      })
    } else {
      return new Promise((resolve, reject) => {
        this.fetchByXHR(o.url).then((data) => {
          o.content = data.content
          const unit = new Unit(o)
          utils.set(unit.key, unit)
          this.appendToPage(ext, o.content)
          resolve()
        }).catch(err => reject(err))
      })
    }
  }

  appendToPage(ext, content) {
    switch (ext) {
      case 'css':
        const style = document.createElement('style')
        style.innerText = content
        document.getElementsByTagName('head')[0].appendChild(style)
        break
      case 'js':
        const script = document.createElement('script')
        script.text = content
        script.defer = true
        document.getElementsByTagName('head')[0].appendChild(script)
        break
    }
  }

  normalInject(o) {
    let promise
    switch (o.ext) {
      case 'js':
        promise = new Promise((resolve, reject) => {
          const script = document.createElement('script')
          script.src = o.url
          script.defer = true
          document.getElementsByTagName('body')[0].appendChild(script)
          script.onload = () => {
            resolve()
          }
          script.onerror = () => {
            reject()
          }
        })
        break
      case 'css':
        promise = new Promise((resolve, reject) => {
          const link = document.createElement('link')
          link.rel = 'stylesheet'
          link.href = o.url
          document.getElementsByTagName('head')[0].appendChild(link)
          link.onload = () => {
            resolve()
          }
          link.onerror = () => {
            reject()
          }
        })
        break
      default:
        break
    }
    return promise
  }

  fetchByXHR(url) {
    const xhr = new XMLHttpRequest()
    const promise = new Promise((resolve, reject) => {
      xhr.open('GET', url)
      xhr.onreadystatechange = () => {
        if (xhr.readyState === 4) {
          if ((xhr.status === 200) || ((xhr.status === 0) && xhr.responseText)) {
            resolve({
              content: xhr.responseText
            })
          } else {
            reject(new Error(xhr.statusText))
          }
        }
      }
    })
    setTimeout(() => {
      if (xhr.readyState < 4) {
        xhr.abort()
      }
    }, this.config.timeout)
    xhr.send()
    return promise
  }
}


================================================
FILE: src/utils.js
================================================
const global = window

/**
 * check if the argument is an instance of Object
 */
export function isObject(obj) {
  return Object.prototype.toString.call(obj) === '[object Object]'
}

/**
 * check if the argument is an instance of String
 */
export function isString(str) {
  return Object.prototype.toString.call(str) === '[object String]'
}

/**
 * check if the argument is an instance of Array
 */
export function isArray(arr) {
  return Object.prototype.toString.call(arr) === '[object Array]'
}

/**
 * check if a given path is a valid url
 */
export function isUrl(path) {
  return /^(https?|\/\/)/.test(path)
}

/**
 * make and return a copy of `obj`
 */
export function clone(obj) {
  let result = null
  if (isObject(obj)) {
    result = {}
    for (let key in obj) {
      if (isObject(obj[key]) || isArray(obj[key])) {
        result[key] = clone(obj[key])
        continue
      }
      result[key] = obj[key]
    }
  } else if (isArray(obj)) {
    result = []
    obj.forEach((item, index) => {
      if (isObject(item) || isArray(item)) {
        result[index] = clone(item)
        return
      }
      result[index] = item
    })
  } else {
    result = obj
  }
  return result
}

/**
 * merge `target` with `source` and return a new object
 * param force {Boolean} whether to overwrite property in `target` whice has a same name in `source`
 */
export function merge(target, source, force = false) {
  if (!isObject(target) || !isObject(source)) return
  let result = clone(target)
  for (let key in source) {
    if (!result.hasOwnProperty(key)) {
      result[key] = clone(source[key])
    } else {
      result[key] = force ?
          clone(source[key]) :
          result[key]
    }
  }
  return result
}

/**
 * test if two urls are cross-origin
 */
export function isCrossOrigin(hostUrl, targetUrl) {
  const originRegExp = /^(https?:\/\/)?([^\/:]+)?:?(\d+)?/
  const defaultProtocol = 'http://'
  const defaultPort = '80'
  const hostOrigin = hostUrl.match(originRegExp)
  const targetOrigin = targetUrl.match(originRegExp)

  // if urls don't have protocols, add default protocols to them
  ;[hostOrigin[1], hostOrigin[2], hostOrigin[3]] = [
    hostOrigin[1] ? hostOrigin[1] : defaultProtocol,
    hostOrigin[2] ? hostOrigin[2] : location.hostname,
    hostOrigin[3] ? hostOrigin[3] : defaultPort
  ]
  if (!targetOrigin[3]) {
    if (targetOrigin[2]) {
      targetOrigin[3] = defaultPort
    } else {
      targetOrigin[3] = hostOrigin[3]
    }
  }
  ;[targetOrigin[1], targetOrigin[2]] = [
    targetOrigin[1] ? targetOrigin[1] : hostOrigin[1],
    targetOrigin[2] ? targetOrigin[2] : hostOrigin[2]
  ]
  hostOrigin[0] = `${hostOrigin[1]}${hostOrigin[2]}:${hostOrigin[3]}`
  targetOrigin[0] = `${targetOrigin[1]}${targetOrigin[2]}:${targetOrigin[3]}`
  return hostOrigin[0] !== targetOrigin[0]
}

const storage = global.localStorage
/**
 * get item from local storage
 */
export function get(key) {
  if (isString(key)) {
    return JSON.parse(storage.getItem(key))
  }
  if (isArray(key)) {
    return key.map(k => JSON.parse(storage.getItem(k)))
  }
}

/**
 * [set description]
 * @param {String} key key of the object to be cached
 * @param {Object} o   object to be cached
 */
export function set(key, o) {
  if (!isObject(o)) {
    return
  }
  storage.setItem(key, JSON.stringify(o))
}

/**
 * remove data object from cache
 */
export function remove(key) {
  if (isString(key)) {
    return storage.removeItem(key)
  }
  if (isArray(key)) {
    return key.forEach(k => storage.removeItem(k))
  }
}


================================================
FILE: test/e2e/assets/a.js
================================================
window.aFlag++


================================================
FILE: test/e2e/assets/b.js
================================================
window.bFlag++


================================================
FILE: test/e2e/assets/c.js
================================================
window.cFlag++


================================================
FILE: test/e2e/assets/style.css
================================================
#mocha {
  font-size: 10px
}


================================================
FILE: test/e2e/test.html
================================================
<html>
  <head>
    <title>bowl.js e2e tests</title>
    <meta charset="utf-8">
    <link rel="stylesheet" href="vendor/mocha.css">
    <script src="vendor/mocha.js"></script>
    <script src="vendor/expect.js"></script>
    <script src="bowl.js"></script>
  </head>
  <body>
    <div id="mocha"></div>
    <script>mocha.setup('bdd')</script>
    <script src="test.js"></script>
    <script>
      mocha.checkLeaks();
      mocha.run();
    </script>
  </body>
</html>


================================================
FILE: test/e2e/test.js
================================================
window.aFlag = 0
window.bFlag = 0
window.cFlag = 0

describe('bowl instance', () => {
  let bowl = new Bowl()

  beforeEach(() => {
    bowl = new Bowl()
    localStorage.clear()
  })

  afterEach(() => {
    localStorage.clear()
  })

  describe('config method', () => {
    it('merge custom configure with the default configure by `config` method', () => {
      bowl.configure({
        timeout: 10000
      })
      expect(bowl.config.timeout).to.be(10000)
    })
  })

  describe('add method', () => {
    it('adds `scripts` to bowl.ingredient', () => {
      bowl.add({
        key: 'a',
        url: 'assets/a.js'
      })
      expect(bowl.ingredients.length).to.be(1)
    })

    it('jumps out of `bowl.add` if the param is neither array nor object', () => {
      bowl.add('assets/a.js')
      expect(bowl.ingredients.length).to.be(0)
    })

    it('uses `key` if provided', () => {
      bowl.add({ url: 'assets/a.js', key: 'a' })
      expect(bowl.ingredients[0].key).to.be('bowl-a')
    })

    it('overwrites the older ingredient if two ingredients have a same key', () => {
      bowl.add({ url: 'assets/a.js', key: 'test' })
      bowl.add({ url: 'assets/b.js', key: 'test' })
      expect(bowl.ingredients.length).to.be(1)
      expect(/b\.js$/.test(bowl.ingredients[0].url)).to.be(true)
    })
  })

  describe('remove method', () => {
    beforeEach(() => {
      bowl.remove()
    })

    it('remove all ingredients if no params are provided', () => {
      bowl.add([
        { key: 'foo', url: 'foo' },
        { key: 'bar', url: 'bar' }
      ])
      bowl.remove()
      expect(bowl.ingredients.length).to.be(0)
      expect(localStorage.getItem('bowl-foo')).to.be(null)
      expect(localStorage.getItem('bowl-bar')).to.be(null)
    })

    it('remove the correct item(key is provided when added) out of ingredients', () => {
      bowl.add([
        { url: 'assets/app.js', key: 'app' },
        { url: 'assets/foo.js', key: 'foo' },
        { url: 'assets/bar.js', key: 'bar' }
      ])
      bowl.remove('app')
      expect(bowl.ingredients.length).to.be(2)
      expect(bowl.ingredients[0].key).to.be('bowl-foo')
      expect(bowl.ingredients[1].key).to.be('bowl-bar')
      bowl.remove(['foo'])
      expect(bowl.ingredients.length).to.be(1)
      expect(bowl.ingredients[0].key).to.be('bowl-bar')
    })
  })

  describe('inject method', () => {
    beforeEach(() => {
      window.aFlag = 0
      window.bFlag = 0
      const scripts = document.querySelectorAll('head script[defer]')
      scripts.forEach(script => {
        script.remove()
      })
    })

    it('returns false if there is no ingredients', (done) => {
      try {
        bowl.inject().then(() => {
          done()
        })
      } catch (err) {
        done(err)
      }
    })

    it('returns a promise if there is any ingredient', (done) => {
      bowl.add({ key: 'a', url: 'assets/a.js' })
      const returnValue = bowl.inject()
      expect(returnValue instanceof Promise).to.be(true)
      returnValue.then(() => done())
    })

    it('fetches the script added to bowl and save it to localStorage', (done) => {
      bowl.add({ key: 'a', url: 'assets/a.js' })
      bowl.inject().then(() => {
        if (window.aFlag === 1) {
          done()
        } else {
          done(new Error())
        }
      })
    })

    it('fetches mutiple scripts added to bowl and save them to localStorage', (done) => {
      bowl.add([
        { key: 'a', url: 'assets/a.js' },
        { key: 'b', url: 'assets/b.js' }
      ])
      bowl.inject().then(() => {
        const a = JSON.parse(localStorage.getItem('bowl-a'))
        const b = JSON.parse(localStorage.getItem('bowl-b'))
        if (a.content.trim() === 'window.aFlag++' &&
            b.content.trim() === 'window.bFlag++') {
          done()
        } else {
          done(new Error())
        }
      })
    })

    it('insert all ingredients to the page', (done) => {
      bowl.add([
        { key: 'a', url: 'assets/a.js' }
      ])
      bowl.inject().then(() => {
        if (window.aFlag === 1) {
          done()
        } else {
          done(new Error())
        }
      })
    })

    it('won\'t cache ingredient if it has `noCache` flag', (done) => {
      bowl.add({ url: 'assets/a.js', key: 'a', noCache: true })
      bowl.inject().then(() => {
        if (localStorage.getItem('bowl-a') === null && window.aFlag === 1) {
          done()
        } else {
          done(new Error())
        }
      })
    })

    it('can fetch and save cross-origin ingredient to cache', (done) => {
      const hostname = location.hostname
      const targetHostName = hostname === '127.0.0.1' ? 'localhost' : '127.0.0.1'
      bowl.add({ url: `http://${targetHostName}:8080/assets/a.js`, key: 'a' })
      bowl.inject().then(() => {
        if (window.aFlag === 1) {
          window.aFlag = 0
          done()
        } else {
          done(new Error())
        }
      })
    });

    it('can fetch and inject CSS', (done) => {
      bowl.add({ url: 'assets/style.css', key: 'style' })
      bowl.inject().then(() => {
        const container = document.getElementById('mocha')
        const fontSize = window.getComputedStyle(container).fontSize
        document.querySelector('head style').remove()
        if (fontSize === '10px') {
          done()
        } else {
          done(new Error())
        }
      })
    })

    it('injects ingredient\'s dependencies', (done) => {
      bowl.add([{
        key: 'a',
        url: 'assets/a.js',
        dependencies: ['c']
      }, {
        key: 'b',
        url: 'assets/b.js'
      }, {
        key: 'c',
        url: 'assets/c.js',
        dependencies: ['b']
      }])
      bowl.inject().then(() => {
        if (window.aFlag === 1 && window.bFlag === 1 && window.cFlag === 1) {
          done()
        } else {
          done(new Error())
        }
      })
    })
  })

  describe('expire related properties', () => {
    beforeEach(() => {
      const scripts = document.querySelectorAll('head script[defer]')
      scripts.forEach(script => {
        script.remove()
      })
    })
    afterEach(() => {
      window.aFlag = 0
      window.bFlag = 0
    })

    it('ingredients expires after `expireAfter` time passed', done => {
      bowl.add({
        url: 'assets/a.js',
        expireAfter: 500,
        key: 'test'
      })
      bowl.inject().then(() => {
        const scripts = document.querySelectorAll('head script[defer]')
        if (scripts.length !== 1) {
          done(new Error('wrong injected scripts number'))
          return
        }
        const originIngredient = JSON.parse(localStorage.getItem('bowl-test'))
        const originExpire = originIngredient.expire
        bowl.add({
          url: 'assets/a.js',
          expireAfter: 600,
          key: 'test'
        })
        bowl.inject().then(() => {
          const test = JSON.parse(localStorage.getItem('bowl-test'))
          if (test.expire !== originExpire) {
            done(new Error('ingredient shouldn\'t be replaced before `expireAfter` time'))
          }
        })
        setTimeout(() => {
          bowl.inject().then(() => {
            const test = JSON.parse(localStorage.getItem('bowl-test'))
            if (test.expire !== originExpire) {
              done()
            } else {
              done(new Error('ingredient should be replaced after `expireAfter` time'))
            }
          })
        }, 600)
      })
    })

    it('ingredients expires after `expireWhen`', done => {
      bowl.add({
        url: 'assets/a.js',
        expireWhen: (new Date()).getTime() + 500,
        key: 'test'
      })
      bowl.inject().then(() => {
        const scripts = document.querySelectorAll('head script[defer]')
        if (scripts.length !== 1) {
          done(new Error('wrong injected scripts number'))
          return
        }
        const originIngredient = JSON.parse(localStorage.getItem('bowl-test'))
        const originExpire = originIngredient.expire
        bowl.add({
          url: 'assets/a.js',
          expireWhen: (new Date()).getTime() + 600,
          key: 'test'
        })
        bowl.inject().then(() => {
          const test = JSON.parse(localStorage.getItem('bowl-test'))
          if (test.expire !== originExpire) {
            done(new Error('ingredient shouldn\'t be replaced before `expireWhen`'))
          }
        })
        setTimeout(() => {
          bowl.inject().then(() => {
            const test = JSON.parse(localStorage.getItem('bowl-test'))
            if (test.expire !== originExpire) {
              done()
            } else {
              done(new Error('ingredient should be replaced after `expireWhen`'))
            }
          })
        }, 600)
      })
    })
  })

  // describe('html tags support', (done) => {
  //   it('supports `link` tags that inject stylesheet', () => {
  //     const head = document.getElementsByTagName('head')[0]
  //     const link = document.createElement('link')
  //     link.setAttribute('bowl-url', 'assets/style.css')
  //     link.setAttribute('bowl-key', 'style')
  //     head.appendChild(link)
  //     bowl.inject().then(() => {
  //       const container = document.getElementById('mocha')
  //       const fontSize = window.getComputedStyle(container).fontSize
  //       document.querySelector('head style').remove()
  //       if (fontSize === '10px') {
  //         done()
  //       } else {
  //         done(new Error())
  //       }
  //     })
  //   })
  // })
})


================================================
FILE: test/e2e/vendor/expect.js
================================================
(function (global, module) {

  var exports = module.exports;

  /**
   * Exports.
   */

  module.exports = expect;
  expect.Assertion = Assertion;

  /**
   * Exports version.
   */

  expect.version = '0.3.1';

  /**
   * Possible assertion flags.
   */

  var flags = {
      not: ['to', 'be', 'have', 'include', 'only']
    , to: ['be', 'have', 'include', 'only', 'not']
    , only: ['have']
    , have: ['own']
    , be: ['an']
  };

  function expect (obj) {
    return new Assertion(obj);
  }

  /**
   * Constructor
   *
   * @api private
   */

  function Assertion (obj, flag, parent) {
    this.obj = obj;
    this.flags = {};

    if (undefined != parent) {
      this.flags[flag] = true;

      for (var i in parent.flags) {
        if (parent.flags.hasOwnProperty(i)) {
          this.flags[i] = true;
        }
      }
    }

    var $flags = flag ? flags[flag] : keys(flags)
      , self = this;

    if ($flags) {
      for (var i = 0, l = $flags.length; i < l; i++) {
        // avoid recursion
        if (this.flags[$flags[i]]) continue;

        var name = $flags[i]
          , assertion = new Assertion(this.obj, name, this)

        if ('function' == typeof Assertion.prototype[name]) {
          // clone the function, make sure we dont touch the prot reference
          var old = this[name];
          this[name] = function () {
            return old.apply(self, arguments);
          };

          for (var fn in Assertion.prototype) {
            if (Assertion.prototype.hasOwnProperty(fn) && fn != name) {
              this[name][fn] = bind(assertion[fn], assertion);
            }
          }
        } else {
          this[name] = assertion;
        }
      }
    }
  }

  /**
   * Performs an assertion
   *
   * @api private
   */

  Assertion.prototype.assert = function (truth, msg, error, expected) {
    var msg = this.flags.not ? error : msg
      , ok = this.flags.not ? !truth : truth
      , err;

    if (!ok) {
      err = new Error(msg.call(this));
      if (arguments.length > 3) {
        err.actual = this.obj;
        err.expected = expected;
        err.showDiff = true;
      }
      throw err;
    }

    this.and = new Assertion(this.obj);
  };

  /**
   * Check if the value is truthy
   *
   * @api public
   */

  Assertion.prototype.ok = function () {
    this.assert(
        !!this.obj
      , function(){ return 'expected ' + i(this.obj) + ' to be truthy' }
      , function(){ return 'expected ' + i(this.obj) + ' to be falsy' });
  };

  /**
   * Creates an anonymous function which calls fn with arguments.
   *
   * @api public
   */

  Assertion.prototype.withArgs = function() {
    expect(this.obj).to.be.a('function');
    var fn = this.obj;
    var args = Array.prototype.slice.call(arguments);
    return expect(function() { fn.apply(null, args); });
  };

  /**
   * Assert that the function throws.
   *
   * @param {Function|RegExp} callback, or regexp to match error string against
   * @api public
   */

  Assertion.prototype['throw'] =
  Assertion.prototype.throwError =
  Assertion.prototype.throwException = function (fn) {
    expect(this.obj).to.be.a('function');

    var thrown = false
      , not = this.flags.not;

    try {
      this.obj();
    } catch (e) {
      if (isRegExp(fn)) {
        var subject = 'string' == typeof e ? e : e.message;
        if (not) {
          expect(subject).to.not.match(fn);
        } else {
          expect(subject).to.match(fn);
        }
      } else if ('function' == typeof fn) {
        fn(e);
      }
      thrown = true;
    }

    if (isRegExp(fn) && not) {
      // in the presence of a matcher, ensure the `not` only applies to
      // the matching.
      this.flags.not = false;
    }

    var name = this.obj.name || 'fn';
    this.assert(
        thrown
      , function(){ return 'expected ' + name + ' to throw an exception' }
      , function(){ return 'expected ' + name + ' not to throw an exception' });
  };

  /**
   * Checks if the array is empty.
   *
   * @api public
   */

  Assertion.prototype.empty = function () {
    var expectation;

    if ('object' == typeof this.obj && null !== this.obj && !isArray(this.obj)) {
      if ('number' == typeof this.obj.length) {
        expectation = !this.obj.length;
      } else {
        expectation = !keys(this.obj).length;
      }
    } else {
      if ('string' != typeof this.obj) {
        expect(this.obj).to.be.an('object');
      }

      expect(this.obj).to.have.property('length');
      expectation = !this.obj.length;
    }

    this.assert(
        expectation
      , function(){ return 'expected ' + i(this.obj) + ' to be empty' }
      , function(){ return 'expected ' + i(this.obj) + ' to not be empty' });
    return this;
  };

  /**
   * Checks if the obj exactly equals another.
   *
   * @api public
   */

  Assertion.prototype.be =
  Assertion.prototype.equal = function (obj) {
    this.assert(
        obj === this.obj
      , function(){ return 'expected ' + i(this.obj) + ' to equal ' + i(obj) }
      , function(){ return 'expected ' + i(this.obj) + ' to not equal ' + i(obj) });
    return this;
  };

  /**
   * Checks if the obj sortof equals another.
   *
   * @api public
   */

  Assertion.prototype.eql = function (obj) {
    this.assert(
        expect.eql(this.obj, obj)
      , function(){ return 'expected ' + i(this.obj) + ' to sort of equal ' + i(obj) }
      , function(){ return 'expected ' + i(this.obj) + ' to sort of not equal ' + i(obj) }
      , obj);
    return this;
  };

  /**
   * Assert within start to finish (inclusive).
   *
   * @param {Number} start
   * @param {Number} finish
   * @api public
   */

  Assertion.prototype.within = function (start, finish) {
    var range = start + '..' + finish;
    this.assert(
        this.obj >= start && this.obj <= finish
      , function(){ return 'expected ' + i(this.obj) + ' to be within ' + range }
      , function(){ return 'expected ' + i(this.obj) + ' to not be within ' + range });
    return this;
  };

  /**
   * Assert typeof / instance of
   *
   * @api public
   */

  Assertion.prototype.a =
  Assertion.prototype.an = function (type) {
    if ('string' == typeof type) {
      // proper english in error msg
      var n = /^[aeiou]/.test(type) ? 'n' : '';

      // typeof with support for 'array'
      this.assert(
          'array' == type ? isArray(this.obj) :
            'regexp' == type ? isRegExp(this.obj) :
              'object' == type
                ? 'object' == typeof this.obj && null !== this.obj
                : type == typeof this.obj
        , function(){ return 'expected ' + i(this.obj) + ' to be a' + n + ' ' + type }
        , function(){ return 'expected ' + i(this.obj) + ' not to be a' + n + ' ' + type });
    } else {
      // instanceof
      var name = type.name || 'supplied constructor';
      this.assert(
          this.obj instanceof type
        , function(){ return 'expected ' + i(this.obj) + ' to be an instance of ' + name }
        , function(){ return 'expected ' + i(this.obj) + ' not to be an instance of ' + name });
    }

    return this;
  };

  /**
   * Assert numeric value above _n_.
   *
   * @param {Number} n
   * @api public
   */

  Assertion.prototype.greaterThan =
  Assertion.prototype.above = function (n) {
    this.assert(
        this.obj > n
      , function(){ return 'expected ' + i(this.obj) + ' to be above ' + n }
      , function(){ return 'expected ' + i(this.obj) + ' to be below ' + n });
    return this;
  };

  /**
   * Assert numeric value below _n_.
   *
   * @param {Number} n
   * @api public
   */

  Assertion.prototype.lessThan =
  Assertion.prototype.below = function (n) {
    this.assert(
        this.obj < n
      , function(){ return 'expected ' + i(this.obj) + ' to be below ' + n }
      , function(){ return 'expected ' + i(this.obj) + ' to be above ' + n });
    return this;
  };

  /**
   * Assert string value matches _regexp_.
   *
   * @param {RegExp} regexp
   * @api public
   */

  Assertion.prototype.match = function (regexp) {
    this.assert(
        regexp.exec(this.obj)
      , function(){ return 'expected ' + i(this.obj) + ' to match ' + regexp }
      , function(){ return 'expected ' + i(this.obj) + ' not to match ' + regexp });
    return this;
  };

  /**
   * Assert property "length" exists and has value of _n_.
   *
   * @param {Number} n
   * @api public
   */

  Assertion.prototype.length = function (n) {
    expect(this.obj).to.have.property('length');
    var len = this.obj.length;
    this.assert(
        n == len
      , function(){ return 'expected ' + i(this.obj) + ' to have a length of ' + n + ' but got ' + len }
      , function(){ return 'expected ' + i(this.obj) + ' to not have a length of ' + len });
    return this;
  };

  /**
   * Assert property _name_ exists, with optional _val_.
   *
   * @param {String} name
   * @param {Mixed} val
   * @api public
   */

  Assertion.prototype.property = function (name, val) {
    if (this.flags.own) {
      this.assert(
          Object.prototype.hasOwnProperty.call(this.obj, name)
        , function(){ return 'expected ' + i(this.obj) + ' to have own property ' + i(name) }
        , function(){ return 'expected ' + i(this.obj) + ' to not have own property ' + i(name) });
      return this;
    }

    if (this.flags.not && undefined !== val) {
      if (undefined === this.obj[name]) {
        throw new Error(i(this.obj) + ' has no property ' + i(name));
      }
    } else {
      var hasProp;
      try {
        hasProp = name in this.obj
      } catch (e) {
        hasProp = undefined !== this.obj[name]
      }

      this.assert(
          hasProp
        , function(){ return 'expected ' + i(this.obj) + ' to have a property ' + i(name) }
        , function(){ return 'expected ' + i(this.obj) + ' to not have a property ' + i(name) });
    }

    if (undefined !== val) {
      this.assert(
          val === this.obj[name]
        , function(){ return 'expected ' + i(this.obj) + ' to have a property ' + i(name)
          + ' of ' + i(val) + ', but got ' + i(this.obj[name]) }
        , function(){ return 'expected ' + i(this.obj) + ' to not have a property ' + i(name)
          + ' of ' + i(val) });
    }

    this.obj = this.obj[name];
    return this;
  };

  /**
   * Assert that the array contains _obj_ or string contains _obj_.
   *
   * @param {Mixed} obj|string
   * @api public
   */

  Assertion.prototype.string =
  Assertion.prototype.contain = function (obj) {
    if ('string' == typeof this.obj) {
      this.assert(
          ~this.obj.indexOf(obj)
        , function(){ return 'expected ' + i(this.obj) + ' to contain ' + i(obj) }
        , function(){ return 'expected ' + i(this.obj) + ' to not contain ' + i(obj) });
    } else {
      this.assert(
          ~indexOf(this.obj, obj)
        , function(){ return 'expected ' + i(this.obj) + ' to contain ' + i(obj) }
        , function(){ return 'expected ' + i(this.obj) + ' to not contain ' + i(obj) });
    }
    return this;
  };

  /**
   * Assert exact keys or inclusion of keys by using
   * the `.own` modifier.
   *
   * @param {Array|String ...} keys
   * @api public
   */

  Assertion.prototype.key =
  Assertion.prototype.keys = function ($keys) {
    var str
      , ok = true;

    $keys = isArray($keys)
      ? $keys
      : Array.prototype.slice.call(arguments);

    if (!$keys.length) throw new Error('keys required');

    var actual = keys(this.obj)
      , len = $keys.length;

    // Inclusion
    ok = every($keys, function (key) {
      return ~indexOf(actual, key);
    });

    // Strict
    if (!this.flags.not && this.flags.only) {
      ok = ok && $keys.length == actual.length;
    }

    // Key string
    if (len > 1) {
      $keys = map($keys, function (key) {
        return i(key);
      });
      var last = $keys.pop();
      str = $keys.join(', ') + ', and ' + last;
    } else {
      str = i($keys[0]);
    }

    // Form
    str = (len > 1 ? 'keys ' : 'key ') + str;

    // Have / include
    str = (!this.flags.only ? 'include ' : 'only have ') + str;

    // Assertion
    this.assert(
        ok
      , function(){ return 'expected ' + i(this.obj) + ' to ' + str }
      , function(){ return 'expected ' + i(this.obj) + ' to not ' + str });

    return this;
  };

  /**
   * Assert a failure.
   *
   * @param {String ...} custom message
   * @api public
   */
  Assertion.prototype.fail = function (msg) {
    var error = function() { return msg || "explicit failure"; }
    this.assert(false, error, error);
    return this;
  };

  /**
   * Function bind implementation.
   */

  function bind (fn, scope) {
    return function () {
      return fn.apply(scope, arguments);
    }
  }

  /**
   * Array every compatibility
   *
   * @see bit.ly/5Fq1N2
   * @api public
   */

  function every (arr, fn, thisObj) {
    var scope = thisObj || global;
    for (var i = 0, j = arr.length; i < j; ++i) {
      if (!fn.call(scope, arr[i], i, arr)) {
        return false;
      }
    }
    return true;
  }

  /**
   * Array indexOf compatibility.
   *
   * @see bit.ly/a5Dxa2
   * @api public
   */

  function indexOf (arr, o, i) {
    if (Array.prototype.indexOf) {
      return Array.prototype.indexOf.call(arr, o, i);
    }

    if (arr.length === undefined) {
      return -1;
    }

    for (var j = arr.length, i = i < 0 ? i + j < 0 ? 0 : i + j : i || 0
        ; i < j && arr[i] !== o; i++);

    return j <= i ? -1 : i;
  }

  // https://gist.github.com/1044128/
  var getOuterHTML = function(element) {
    if ('outerHTML' in element) return element.outerHTML;
    var ns = "http://www.w3.org/1999/xhtml";
    var container = document.createElementNS(ns, '_');
    var xmlSerializer = new XMLSerializer();
    var html;
    if (document.xmlVersion) {
      return xmlSerializer.serializeToString(element);
    } else {
      container.appendChild(element.cloneNode(false));
      html = container.innerHTML.replace('><', '>' + element.innerHTML + '<');
      container.innerHTML = '';
      return html;
    }
  };

  // Returns true if object is a DOM element.
  var isDOMElement = function (object) {
    if (typeof HTMLElement === 'object') {
      return object instanceof HTMLElement;
    } else {
      return object &&
        typeof object === 'object' &&
        object.nodeType === 1 &&
        typeof object.nodeName === 'string';
    }
  };

  /**
   * Inspects an object.
   *
   * @see taken from node.js `util` module (copyright Joyent, MIT license)
   * @api private
   */

  function i (obj, showHidden, depth) {
    var seen = [];

    function stylize (str) {
      return str;
    }

    function format (value, recurseTimes) {
      // Provide a hook for user-specified inspect functions.
      // Check that value is an object with an inspect function on it
      if (value && typeof value.inspect === 'function' &&
          // Filter out the util module, it's inspect function is special
          value !== exports &&
          // Also filter out any prototype objects using the circular check.
          !(value.constructor && value.constructor.prototype === value)) {
        return value.inspect(recurseTimes);
      }

      // Primitive types cannot have properties
      switch (typeof value) {
        case 'undefined':
          return stylize('undefined', 'undefined');

        case 'string':
          var simple = '\'' + json.stringify(value).replace(/^"|"$/g, '')
                                                   .replace(/'/g, "\\'")
                                                   .replace(/\\"/g, '"') + '\'';
          return stylize(simple, 'string');

        case 'number':
          return stylize('' + value, 'number');

        case 'boolean':
          return stylize('' + value, 'boolean');
      }
      // For some reason typeof null is "object", so special case here.
      if (value === null) {
        return stylize('null', 'null');
      }

      if (isDOMElement(value)) {
        return getOuterHTML(value);
      }

      // Look up the keys of the object.
      var visible_keys = keys(value);
      var $keys = showHidden ? Object.getOwnPropertyNames(value) : visible_keys;

      // Functions without properties can be shortcutted.
      if (typeof value === 'function' && $keys.length === 0) {
        if (isRegExp(value)) {
          return stylize('' + value, 'regexp');
        } else {
          var name = value.name ? ': ' + value.name : '';
          return stylize('[Function' + name + ']', 'special');
        }
      }

      // Dates without properties can be shortcutted
      if (isDate(value) && $keys.length === 0) {
        return stylize(value.toUTCString(), 'date');
      }

      // Error objects can be shortcutted
      if (value instanceof Error) {
        return stylize("["+value.toString()+"]", 'Error');
      }

      var base, type, braces;
      // Determine the object type
      if (isArray(value)) {
        type = 'Array';
        braces = ['[', ']'];
      } else {
        type = 'Object';
        braces = ['{', '}'];
      }

      // Make functions say that they are functions
      if (typeof value === 'function') {
        var n = value.name ? ': ' + value.name : '';
        base = (isRegExp(value)) ? ' ' + value : ' [Function' + n + ']';
      } else {
        base = '';
      }

      // Make dates with properties first say the date
      if (isDate(value)) {
        base = ' ' + value.toUTCString();
      }

      if ($keys.length === 0) {
        return braces[0] + base + braces[1];
      }

      if (recurseTimes < 0) {
        if (isRegExp(value)) {
          return stylize('' + value, 'regexp');
        } else {
          return stylize('[Object]', 'special');
        }
      }

      seen.push(value);

      var output = map($keys, function (key) {
        var name, str;
        if (value.__lookupGetter__) {
          if (value.__lookupGetter__(key)) {
            if (value.__lookupSetter__(key)) {
              str = stylize('[Getter/Setter]', 'special');
            } else {
              str = stylize('[Getter]', 'special');
            }
          } else {
            if (value.__lookupSetter__(key)) {
              str = stylize('[Setter]', 'special');
            }
          }
        }
        if (indexOf(visible_keys, key) < 0) {
          name = '[' + key + ']';
        }
        if (!str) {
          if (indexOf(seen, value[key]) < 0) {
            if (recurseTimes === null) {
              str = format(value[key]);
            } else {
              str = format(value[key], recurseTimes - 1);
            }
            if (str.indexOf('\n') > -1) {
              if (isArray(value)) {
                str = map(str.split('\n'), function (line) {
                  return '  ' + line;
                }).join('\n').substr(2);
              } else {
                str = '\n' + map(str.split('\n'), function (line) {
                  return '   ' + line;
                }).join('\n');
              }
            }
          } else {
            str = stylize('[Circular]', 'special');
          }
        }
        if (typeof name === 'undefined') {
          if (type === 'Array' && key.match(/^\d+$/)) {
            return str;
          }
          name = json.stringify('' + key);
          if (name.match(/^"([a-zA-Z_][a-zA-Z_0-9]*)"$/)) {
            name = name.substr(1, name.length - 2);
            name = stylize(name, 'name');
          } else {
            name = name.replace(/'/g, "\\'")
                       .replace(/\\"/g, '"')
                       .replace(/(^"|"$)/g, "'");
            name = stylize(name, 'string');
          }
        }

        return name + ': ' + str;
      });

      seen.pop();

      var numLinesEst = 0;
      var length = reduce(output, function (prev, cur) {
        numLinesEst++;
        if (indexOf(cur, '\n') >= 0) numLinesEst++;
        return prev + cur.length + 1;
      }, 0);

      if (length > 50) {
        output = braces[0] +
                 (base === '' ? '' : base + '\n ') +
                 ' ' +
                 output.join(',\n  ') +
                 ' ' +
                 braces[1];

      } else {
        output = braces[0] + base + ' ' + output.join(', ') + ' ' + braces[1];
      }

      return output;
    }
    return format(obj, (typeof depth === 'undefined' ? 2 : depth));
  }

  expect.stringify = i;

  function isArray (ar) {
    return Object.prototype.toString.call(ar) === '[object Array]';
  }

  function isRegExp(re) {
    var s;
    try {
      s = '' + re;
    } catch (e) {
      return false;
    }

    return re instanceof RegExp || // easy case
           // duck-type for context-switching evalcx case
           typeof(re) === 'function' &&
           re.constructor.name === 'RegExp' &&
           re.compile &&
           re.test &&
           re.exec &&
           s.match(/^\/.*\/[gim]{0,3}$/);
  }

  function isDate(d) {
    return d instanceof Date;
  }

  function keys (obj) {
    if (Object.keys) {
      return Object.keys(obj);
    }

    var keys = [];

    for (var i in obj) {
      if (Object.prototype.hasOwnProperty.call(obj, i)) {
        keys.push(i);
      }
    }

    return keys;
  }

  function map (arr, mapper, that) {
    if (Array.prototype.map) {
      return Array.prototype.map.call(arr, mapper, that);
    }

    var other= new Array(arr.length);

    for (var i= 0, n = arr.length; i<n; i++)
      if (i in arr)
        other[i] = mapper.call(that, arr[i], i, arr);

    return other;
  }

  function reduce (arr, fun) {
    if (Array.prototype.reduce) {
      return Array.prototype.reduce.apply(
          arr
        , Array.prototype.slice.call(arguments, 1)
      );
    }

    var len = +this.length;

    if (typeof fun !== "function")
      throw new TypeError();

    // no value to return if no initial value and an empty array
    if (len === 0 && arguments.length === 1)
      throw new TypeError();

    var i = 0;
    if (arguments.length >= 2) {
      var rv = arguments[1];
    } else {
      do {
        if (i in this) {
          rv = this[i++];
          break;
        }

        // if array contains no values, no initial value to return
        if (++i >= len)
          throw new TypeError();
      } while (true);
    }

    for (; i < len; i++) {
      if (i in this)
        rv = fun.call(null, rv, this[i], i, this);
    }

    return rv;
  }

  /**
   * Asserts deep equality
   *
   * @see taken from node.js `assert` module (copyright Joyent, MIT license)
   * @api private
   */

  expect.eql = function eql(actual, expected) {
    // 7.1. All identical values are equivalent, as determined by ===.
    if (actual === expected) {
      return true;
    } else if ('undefined' != typeof Buffer
      && Buffer.isBuffer(actual) && Buffer.isBuffer(expected)) {
      if (actual.length != expected.length) return false;

      for (var i = 0; i < actual.length; i++) {
        if (actual[i] !== expected[i]) return false;
      }

      return true;

      // 7.2. If the expected value is a Date object, the actual value is
      // equivalent if it is also a Date object that refers to the same time.
    } else if (actual instanceof Date && expected instanceof Date) {
      return actual.getTime() === expected.getTime();

      // 7.3. Other pairs that do not both pass typeof value == "object",
      // equivalence is determined by ==.
    } else if (typeof actual != 'object' && typeof expected != 'object') {
      return actual == expected;
    // If both are regular expression use the special `regExpEquiv` method
    // to determine equivalence.
    } else if (isRegExp(actual) && isRegExp(expected)) {
      return regExpEquiv(actual, expected);
    // 7.4. For all other Object pairs, including Array objects, equivalence is
    // determined by having the same number of owned properties (as verified
    // with Object.prototype.hasOwnProperty.call), the same set of keys
    // (although not necessarily the same order), equivalent values for every
    // corresponding key, and an identical "prototype" property. Note: this
    // accounts for both named and indexed properties on Arrays.
    } else {
      return objEquiv(actual, expected);
    }
  };

  function isUndefinedOrNull (value) {
    return value === null || value === undefined;
  }

  function isArguments (object) {
    return Object.prototype.toString.call(object) == '[object Arguments]';
  }

  function regExpEquiv (a, b) {
    return a.source === b.source && a.global === b.global &&
           a.ignoreCase === b.ignoreCase && a.multiline === b.multiline;
  }

  function objEquiv (a, b) {
    if (isUndefinedOrNull(a) || isUndefinedOrNull(b))
      return false;
    // an identical "prototype" property.
    if (a.prototype !== b.prototype) return false;
    //~~~I've managed to break Object.keys through screwy arguments passing.
    //   Converting to array solves the problem.
    if (isArguments(a)) {
      if (!isArguments(b)) {
        return false;
      }
      a = pSlice.call(a);
      b = pSlice.call(b);
      return expect.eql(a, b);
    }
    try{
      var ka = keys(a),
        kb = keys(b),
        key, i;
    } catch (e) {//happens when one is a string literal and the other isn't
      return false;
    }
    // having the same number of owned properties (keys incorporates hasOwnProperty)
    if (ka.length != kb.length)
      return false;
    //the same set of keys (although not necessarily the same order),
    ka.sort();
    kb.sort();
    //~~~cheap key test
    for (i = ka.length - 1; i >= 0; i--) {
      if (ka[i] != kb[i])
        return false;
    }
    //equivalent values for every corresponding key, and
    //~~~possibly expensive deep test
    for (i = ka.length - 1; i >= 0; i--) {
      key = ka[i];
      if (!expect.eql(a[key], b[key]))
         return false;
    }
    return true;
  }

  var json = (function () {
    "use strict";

    if ('object' == typeof JSON && JSON.parse && JSON.stringify) {
      return {
          parse: nativeJSON.parse
        , stringify: nativeJSON.stringify
      }
    }

    var JSON = {};

    function f(n) {
        // Format integers to have at least two digits.
        return n < 10 ? '0' + n : n;
    }

    function date(d, key) {
      return isFinite(d.valueOf()) ?
          d.getUTCFullYear()     + '-' +
          f(d.getUTCMonth() + 1) + '-' +
          f(d.getUTCDate())      + 'T' +
          f(d.getUTCHours())     + ':' +
          f(d.getUTCMinutes())   + ':' +
          f(d.getUTCSeconds())   + 'Z' : null;
    }

    var cx = /[\u0000\u00ad\u0600-\u0604\u070f\u17b4\u17b5\u200c-\u200f\u2028-\u202f\u2060-\u206f\ufeff\ufff0-\uffff]/g,
        escapable = /[\\\"\x00-\x1f\x7f-\x9f\u00ad\u0600-\u0604\u070f\u17b4\u17b5\u200c-\u200f\u2028-\u202f\u2060-\u206f\ufeff\ufff0-\uffff]/g,
        gap,
        indent,
        meta = {    // table of character substitutions
            '\b': '\\b',
            '\t': '\\t',
            '\n': '\\n',
            '\f': '\\f',
            '\r': '\\r',
            '"' : '\\"',
            '\\': '\\\\'
        },
        rep;


    function quote(string) {

  // If the string contains no control characters, no quote characters, and no
  // backslash characters, then we can safely slap some quotes around it.
  // Otherwise we must also replace the offending characters with safe escape
  // sequences.

        escapable.lastIndex = 0;
        return escapable.test(string) ? '"' + string.replace(escapable, function (a) {
            var c = meta[a];
            return typeof c === 'string' ? c :
                '\\u' + ('0000' + a.charCodeAt(0).toString(16)).slice(-4);
        }) + '"' : '"' + string + '"';
    }


    function str(key, holder) {

  // Produce a string from holder[key].

        var i,          // The loop counter.
            k,          // The member key.
            v,          // The member value.
            length,
            mind = gap,
            partial,
            value = holder[key];

  // If the value has a toJSON method, call it to obtain a replacement value.

        if (value instanceof Date) {
            value = date(key);
        }

  // If we were called with a replacer function, then call the replacer to
  // obtain a replacement value.

        if (typeof rep === 'function') {
            value = rep.call(holder, key, value);
        }

  // What happens next depends on the value's type.

        switch (typeof value) {
        case 'string':
            return quote(value);

        case 'number':

  // JSON numbers must be finite. Encode non-finite numbers as null.

            return isFinite(value) ? String(value) : 'null';

        case 'boolean':
        case 'null':

  // If the value is a boolean or null, convert it to a string. Note:
  // typeof null does not produce 'null'. The case is included here in
  // the remote chance that this gets fixed someday.

            return String(value);

  // If the type is 'object', we might be dealing with an object or an array or
  // null.

        case 'object':

  // Due to a specification blunder in ECMAScript, typeof null is 'object',
  // so watch out for that case.

            if (!value) {
                return 'null';
            }

  // Make an array to hold the partial results of stringifying this object value.

            gap += indent;
            partial = [];

  // Is the value an array?

            if (Object.prototype.toString.apply(value) === '[object Array]') {

  // The value is an array. Stringify every element. Use null as a placeholder
  // for non-JSON values.

                length = value.length;
                for (i = 0; i < length; i += 1) {
                    partial[i] = str(i, value) || 'null';
                }

  // Join all of the elements together, separated with commas, and wrap them in
  // brackets.

                v = partial.length === 0 ? '[]' : gap ?
                    '[\n' + gap + partial.join(',\n' + gap) + '\n' + mind + ']' :
                    '[' + partial.join(',') + ']';
                gap = mind;
                return v;
            }

  // If the replacer is an array, use it to select the members to be stringified.

            if (rep && typeof rep === 'object') {
                length = rep.length;
                for (i = 0; i < length; i += 1) {
                    if (typeof rep[i] === 'string') {
                        k = rep[i];
                        v = str(k, value);
                        if (v) {
                            partial.push(quote(k) + (gap ? ': ' : ':') + v);
                        }
                    }
                }
            } else {

  // Otherwise, iterate through all of the keys in the object.

                for (k in value) {
                    if (Object.prototype.hasOwnProperty.call(value, k)) {
                        v = str(k, value);
                        if (v) {
                            partial.push(quote(k) + (gap ? ': ' : ':') + v);
                        }
                    }
                }
            }

  // Join all of the member texts together, separated with commas,
  // and wrap them in braces.

            v = partial.length === 0 ? '{}' : gap ?
                '{\n' + gap + partial.join(',\n' + gap) + '\n' + mind + '}' :
                '{' + partial.join(',') + '}';
            gap = mind;
            return v;
        }
    }

  // If the JSON object does not yet have a stringify method, give it one.

    JSON.stringify = function (value, replacer, space) {

  // The stringify method takes a value and an optional replacer, and an optional
  // space parameter, and returns a JSON text. The replacer can be a function
  // that can replace values, or an array of strings that will select the keys.
  // A default replacer method can be provided. Use of the space parameter can
  // produce text that is more easily readable.

        var i;
        gap = '';
        indent = '';

  // If the space parameter is a number, make an indent string containing that
  // many spaces.

        if (typeof space === 'number') {
            for (i = 0; i < space; i += 1) {
                indent += ' ';
            }

  // If the space parameter is a string, it will be used as the indent string.

        } else if (typeof space === 'string') {
            indent = space;
        }

  // If there is a replacer, it must be a function or an array.
  // Otherwise, throw an error.

        rep = replacer;
        if (replacer && typeof replacer !== 'function' &&
                (typeof replacer !== 'object' ||
                typeof replacer.length !== 'number')) {
            throw new Error('JSON.stringify');
        }

  // Make a fake root object containing our value under the key of ''.
  // Return the result of stringifying the value.

        return str('', {'': value});
    };

  // If the JSON object does not yet have a parse method, give it one.

    JSON.parse = function (text, reviver) {
    // The parse method takes a text and an optional reviver function, and returns
    // a JavaScript value if the text is a valid JSON text.

        var j;

        function walk(holder, key) {

    // The walk method is used to recursively walk the resulting structure so
    // that modifications can be made.

            var k, v, value = holder[key];
            if (value && typeof value === 'object') {
                for (k in value) {
                    if (Object.prototype.hasOwnProperty.call(value, k)) {
                        v = walk(value, k);
                        if (v !== undefined) {
                            value[k] = v;
                        } else {
                            delete value[k];
                        }
                    }
                }
            }
            return reviver.call(holder, key, value);
        }


    // Parsing happens in four stages. In the first stage, we replace certain
    // Unicode characters with escape sequences. JavaScript handles many characters
    // incorrectly, either silently deleting them, or treating them as line endings.

        text = String(text);
        cx.lastIndex = 0;
        if (cx.test(text)) {
            text = text.replace(cx, function (a) {
                return '\\u' +
                    ('0000' + a.charCodeAt(0).toString(16)).slice(-4);
            });
        }

    // In the second stage, we run the text against regular expressions that look
    // for non-JSON patterns. We are especially concerned with '()' and 'new'
    // because they can cause invocation, and '=' because it can cause mutation.
    // But just to be safe, we want to reject all unexpected forms.

    // We split the second stage into 4 regexp operations in order to work around
    // crippling inefficiencies in IE's and Safari's regexp engines. First we
    // replace the JSON backslash pairs with '@' (a non-JSON character). Second, we
    // replace all simple value tokens with ']' characters. Third, we delete all
    // open brackets that follow a colon or comma or that begin the text. Finally,
    // we look to see that the remaining characters are only whitespace or ']' or
    // ',' or ':' or '{' or '}'. If that is so, then the text is safe for eval.

        if (/^[\],:{}\s]*$/
                .test(text.replace(/\\(?:["\\\/bfnrt]|u[0-9a-fA-F]{4})/g, '@')
                    .replace(/"[^"\\\n\r]*"|true|false|null|-?\d+(?:\.\d*)?(?:[eE][+\-]?\d+)?/g, ']')
                    .replace(/(?:^|:|,)(?:\s*\[)+/g, ''))) {

    // In the third stage we use the eval function to compile the text into a
    // JavaScript structure. The '{' operator is subject to a syntactic ambiguity
    // in JavaScript: it can begin a block or an object literal. We wrap the text
    // in parens to eliminate the ambiguity.

            j = eval('(' + text + ')');

    // In the optional fourth stage, we recursively walk the new structure, passing
    // each name/value pair to a reviver function for possible transformation.

            return typeof reviver === 'function' ?
                walk({'': j}, '') : j;
        }

    // If the text is not JSON parseable, then a SyntaxError is thrown.

        throw new SyntaxError('JSON.parse');
    };

    return JSON;
  })();

  if ('undefined' != typeof window) {
    window.expect = module.exports;
  }

})(
    this
  , 'undefined' != typeof module ? module : {exports: {}}
);


================================================
FILE: test/e2e/vendor/mocha.css
================================================
@charset "utf-8";

body {
  margin:0;
}

#mocha {
  font: 20px/1.5 "Helvetica Neue", Helvetica, Arial, sans-serif;
  margin: 60px 50px;
}

#mocha ul,
#mocha li {
  margin: 0;
  padding: 0;
}

#mocha ul {
  list-style: none;
}

#mocha h1,
#mocha h2 {
  margin: 0;
}

#mocha h1 {
  margin-top: 15px;
  font-size: 1em;
  font-weight: 200;
}

#mocha h1 a {
  text-decoration: none;
  color: inherit;
}

#mocha h1 a:hover {
  text-decoration: underline;
}

#mocha .suite .suite h1 {
  margin-top: 0;
  font-size: .8em;
}

#mocha .hidden {
  display: none;
}

#mocha h2 {
  font-size: 12px;
  font-weight: normal;
  cursor: pointer;
}

#mocha .suite {
  margin-left: 15px;
}

#mocha .test {
  margin-left: 15px;
  overflow: hidden;
}

#mocha .test.pending:hover h2::after {
  content: '(pending)';
  font-family: arial, sans-serif;
}

#mocha .test.pass.medium .duration {
  background: #c09853;
}

#mocha .test.pass.slow .duration {
  background: #b94a48;
}

#mocha .test.pass::before {
  content: '✓';
  font-size: 12px;
  display: block;
  float: left;
  margin-right: 5px;
  color: #00d6b2;
}

#mocha .test.pass .duration {
  font-size: 9px;
  margin-left: 5px;
  padding: 2px 5px;
  color: #fff;
  -webkit-box-shadow: inset 0 1px 1px rgba(0,0,0,.2);
  -moz-box-shadow: inset 0 1px 1px rgba(0,0,0,.2);
  box-shadow: inset 0 1px 1px rgba(0,0,0,.2);
  -webkit-border-radius: 5px;
  -moz-border-radius: 5px;
  -ms-border-radius: 5px;
  -o-border-radius: 5px;
  border-radius: 5px;
}

#mocha .test.pass.fast .duration {
  display: none;
}

#mocha .test.pending {
  color: #0b97c4;
}

#mocha .test.pending::before {
  content: '◦';
  color: #0b97c4;
}

#mocha .test.fail {
  color: #c00;
}

#mocha .test.fail pre {
  color: black;
}

#mocha .test.fail::before {
  content: '✖';
  font-size: 12px;
  display: block;
  float: left;
  margin-right: 5px;
  color: #c00;
}

#mocha .test pre.error {
  color: #c00;
  max-height: 300px;
  overflow: auto;
}

#mocha .test .html-error {
  overflow: auto;
  color: black;
  line-height: 1.5;
  display: block;
  float: left;
  clear: left;
  font: 12px/1.5 monaco, monospace;
  margin: 5px;
  padding: 15px;
  border: 1px solid #eee;
  max-width: 85%; /*(1)*/
  max-width: -webkit-calc(100% - 42px);
  max-width: -moz-calc(100% - 42px);
  max-width: calc(100% - 42px); /*(2)*/
  max-height: 300px;
  word-wrap: break-word;
  border-bottom-color: #ddd;
  -webkit-box-shadow: 0 1px 3px #eee;
  -moz-box-shadow: 0 1px 3px #eee;
  box-shadow: 0 1px 3px #eee;
  -webkit-border-radius: 3px;
  -moz-border-radius: 3px;
  border-radius: 3px;
}

#mocha .test .html-error pre.error {
  border: none;
  -webkit-border-radius: 0;
  -moz-border-radius: 0;
  border-radius: 0;
  -webkit-box-shadow: 0;
  -moz-box-shadow: 0;
  box-shadow: 0;
  padding: 0;
  margin: 0;
  margin-top: 18px;
  max-height: none;
}

/**
 * (1): approximate for browsers not supporting calc
 * (2): 42 = 2*15 + 2*10 + 2*1 (padding + margin + border)
 *      ^^ seriously
 */
#mocha .test pre {
  display: block;
  float: left;
  clear: left;
  font: 12px/1.5 monaco, monospace;
  margin: 5px;
  padding: 15px;
  border: 1px solid #eee;
  max-width: 85%; /*(1)*/
  max-width: -webkit-calc(100% - 42px);
  max-width: -moz-calc(100% - 42px);
  max-width: calc(100% - 42px); /*(2)*/
  word-wrap: break-word;
  border-bottom-color: #ddd;
  -webkit-box-shadow: 0 1px 3px #eee;
  -moz-box-shadow: 0 1px 3px #eee;
  box-shadow: 0 1px 3px #eee;
  -webkit-border-radius: 3px;
  -moz-border-radius: 3px;
  border-radius: 3px;
}

#mocha .test h2 {
  position: relative;
}

#mocha .test a.replay {
  position: absolute;
  top: 3px;
  right: 0;
  text-decoration: none;
  vertical-align: middle;
  display: block;
  width: 15px;
  height: 15px;
  line-height: 15px;
  text-align: center;
  background: #eee;
  font-size: 15px;
  -webkit-border-radius: 15px;
  -moz-border-radius: 15px;
  border-radius: 15px;
  -webkit-transition:opacity 200ms;
  -moz-transition:opacity 200ms;
  -o-transition:opacity 200ms;
  transition: opacity 200ms;
  opacity: 0.3;
  color: #888;
}

#mocha .test:hover a.replay {
  opacity: 1;
}

#mocha-report.pass .test.fail {
  display: none;
}

#mocha-report.fail .test.pass {
  display: none;
}

#mocha-report.pending .test.pass,
#mocha-report.pending .test.fail {
  display: none;
}
#mocha-report.pending .test.pass.pending {
  display: block;
}

#mocha-error {
  color: #c00;
  font-size: 1.5em;
  font-weight: 100;
  letter-spacing: 1px;
}

#mocha-stats {
  position: fixed;
  top: 15px;
  right: 10px;
  font-size: 12px;
  margin: 0;
  color: #888;
  z-index: 1;
}

#mocha-stats .progress {
  float: right;
  padding-top: 0;

  /**
   * Set safe initial values, so mochas .progress does not inherit these
   * properties from Bootstrap .progress (which causes .progress height to
   * equal line height set in Bootstrap).
   */
  height: auto;
  -webkit-box-shadow: none;
  -moz-box-shadow: none;
  box-shadow: none;
  background-color: initial;
}

#mocha-stats em {
  color: black;
}

#mocha-stats a {
  text-decoration: none;
  color: inherit;
}

#mocha-stats a:hover {
  border-bottom: 1px solid #eee;
}

#mocha-stats li {
  display: inline-block;
  margin: 0 5px;
  list-style: none;
  padding-top: 11px;
}

#mocha-stats canvas {
  width: 40px;
  height: 40px;
}

#mocha code .comment { color: #ddd; }
#mocha code .init { color: #2f6fad; }
#mocha code .string { color: #5890ad; }
#mocha code .keyword { color: #8a6343; }
#mocha code .number { color: #2f6fad; }

@media screen and (max-device-width: 480px) {
  #mocha {
    margin: 60px 0px;
  }

  #mocha #stats {
    position: absolute;
  }
}


================================================
FILE: test/e2e/vendor/mocha.js
================================================
(function e(t,n,r){function s(o,u){if(!n[o]){if(!t[o]){var a=typeof require=="function"&&require;if(!u&&a)return a(o,!0);if(i)return i(o,!0);var f=new Error("Cannot find module '"+o+"'");throw f.code="MODULE_NOT_FOUND",f}var l=n[o]={exports:{}};t[o][0].call(l.exports,function(e){var n=t[o][1][e];return s(n?n:e)},l,l.exports,e,t,n,r)}return n[o].exports}var i=typeof require=="function"&&require;for(var o=0;o<r.length;o++)s(r[o]);return s})({1:[function(require,module,exports){
(function (process,global){
'use strict';

/* eslint no-unused-vars: off */
/* eslint-env commonjs */

/**
 * Shim process.stdout.
 */

process.stdout = require('browser-stdout')();

var Mocha = require('./lib/mocha');

/**
 * Create a Mocha instance.
 *
 * @return {undefined}
 */

var mocha = new Mocha({ reporter: 'html' });

/**
 * Save timer references to avoid Sinon interfering (see GH-237).
 */

var Date = global.Date;
var setTimeout = global.setTimeout;
var setInterval = global.setInterval;
var clearTimeout = global.clearTimeout;
var clearInterval = global.clearInterval;

var uncaughtExceptionHandlers = [];

var originalOnerrorHandler = global.onerror;

/**
 * Remove uncaughtException listener.
 * Revert to original onerror handler if previously defined.
 */

process.removeListener = function (e, fn) {
  if (e === 'uncaughtException') {
    if (originalOnerrorHandler) {
      global.onerror = originalOnerrorHandler;
    } else {
      global.onerror = function () {};
    }
    var i = Mocha.utils.indexOf(uncaughtExceptionHandlers, fn);
    if (i !== -1) {
      uncaughtExceptionHandlers.splice(i, 1);
    }
  }
};

/**
 * Implements uncaughtException listener.
 */

process.on = function (e, fn) {
  if (e === 'uncaughtException') {
    global.onerror = function (err, url, line) {
      fn(new Error(err + ' (' + url + ':' + line + ')'));
      return !mocha.allowUncaught;
    };
    uncaughtExceptionHandlers.push(fn);
  }
};

// The BDD UI is registered by default, but no UI will be functional in the
// browser without an explicit call to the overridden `mocha.ui` (see below).
// Ensure that this default UI does not expose its methods to the global scope.
mocha.suite.removeAllListeners('pre-require');

var immediateQueue = [];
var immediateTimeout;

function timeslice () {
  var immediateStart = new Date().getTime();
  while (immediateQueue.length && (new Date().getTime() - immediateStart) < 100) {
    immediateQueue.shift()();
  }
  if (immediateQueue.length) {
    immediateTimeout = setTimeout(timeslice, 0);
  } else {
    immediateTimeout = null;
  }
}

/**
 * High-performance override of Runner.immediately.
 */

Mocha.Runner.immediately = function (callback) {
  immediateQueue.push(callback);
  if (!immediateTimeout) {
    immediateTimeout = setTimeout(timeslice, 0);
  }
};

/**
 * Function to allow assertion libraries to throw errors directly into mocha.
 * This is useful when running tests in a browser because window.onerror will
 * only receive the 'message' attribute of the Error.
 */
mocha.throwError = function (err) {
  Mocha.utils.forEach(uncaughtExceptionHandlers, function (fn) {
    fn(err);
  });
  throw err;
};

/**
 * Override ui to ensure that the ui functions are initialized.
 * Normally this would happen in Mocha.prototype.loadFiles.
 */

mocha.ui = function (ui) {
  Mocha.prototype.ui.call(this, ui);
  this.suite.emit('pre-require', global, null, this);
  return this;
};

/**
 * Setup mocha with the given setting options.
 */

mocha.setup = function (opts) {
  if (typeof opts === 'string') {
    opts = { ui: opts };
  }
  for (var opt in opts) {
    if (opts.hasOwnProperty(opt)) {
      this[opt](opts[opt]);
    }
  }
  return this;
};

/**
 * Run mocha, returning the Runner.
 */

mocha.run = function (fn) {
  var options = mocha.options;
  mocha.globals('location');

  var query = Mocha.utils.parseQuery(global.location.search || '');
  if (query.grep) {
    mocha.grep(query.grep);
  }
  if (query.fgrep) {
    mocha.fgrep(query.fgrep);
  }
  if (query.invert) {
    mocha.invert();
  }

  return Mocha.prototype.run.call(mocha, function (err) {
    // The DOM Document is not available in Web Workers.
    var document = global.document;
    if (document && document.getElementById('mocha') && options.noHighlighting !== true) {
      Mocha.utils.highlightTags('code');
    }
    if (fn) {
      fn(err);
    }
  });
};

/**
 * Expose the process shim.
 * https://github.com/mochajs/mocha/pull/916
 */

Mocha.process = process;

/**
 * Expose mocha.
 */

global.Mocha = Mocha;
global.mocha = mocha;

// this allows test/acceptance/required-tokens.js to pass; thus,
// you can now do `const describe = require('mocha').describe` in a
// browser context (assuming browserification).  should fix #880
module.exports = global;

}).call(this,require('_process'),typeof global !== "undefined" ? global : typeof self !== "undefined" ? self : typeof window !== "undefined" ? window : {})
},{"./lib/mocha":14,"_process":67,"browser-stdout":41}],2:[function(require,module,exports){
'use strict';

function noop () {}

module.exports = function () {
  return noop;
};

},{}],3:[function(require,module,exports){
'use strict';

/**
 * Module exports.
 */

exports.EventEmitter = EventEmitter;

/**
 * Object#toString reference.
 */
var objToString = Object.prototype.toString;

/**
 * Check if a value is an array.
 *
 * @api private
 * @param {*} val The value to test.
 * @return {boolean} true if the value is an array, otherwise false.
 */
function isArray (val) {
  return objToString.call(val) === '[object Array]';
}

/**
 * Event emitter constructor.
 *
 * @api public
 */
function EventEmitter () {}

/**
 * Add a listener.
 *
 * @api public
 * @param {string} name Event name.
 * @param {Function} fn Event handler.
 * @return {EventEmitter} Emitter instance.
 */
EventEmitter.prototype.on = function (name, fn) {
  if (!this.$events) {
    this.$events = {};
  }

  if (!this.$events[name]) {
    this.$events[name] = fn;
  } else if (isArray(this.$events[name])) {
    this.$events[name].push(fn);
  } else {
    this.$events[name] = [this.$events[name], fn];
  }

  return this;
};

EventEmitter.prototype.addListener = EventEmitter.prototype.on;

/**
 * Adds a volatile listener.
 *
 * @api public
 * @param {string} name Event name.
 * @param {Function} fn Event handler.
 * @return {EventEmitter} Emitter instance.
 */
EventEmitter.prototype.once = function (name, fn) {
  var self = this;

  function on () {
    self.removeListener(name, on);
    fn.apply(this, arguments);
  }

  on.listener = fn;
  this.on(name, on);

  return this;
};

/**
 * Remove a listener.
 *
 * @api public
 * @param {string} name Event name.
 * @param {Function} fn Event handler.
 * @return {EventEmitter} Emitter instance.
 */
EventEmitter.prototype.removeListener = function (name, fn) {
  if (this.$events && this.$events[name]) {
    var list = this.$events[name];

    if (isArray(list)) {
      var pos = -1;

      for (var i = 0, l = list.length; i < l; i++) {
        if (list[i] === fn || (list[i].listener && list[i].listener === fn)) {
          pos = i;
          break;
        }
      }

      if (pos < 0) {
        return this;
      }

      list.splice(pos, 1);

      if (!list.length) {
        delete this.$events[name];
      }
    } else if (list === fn || (list.listener && list.listener === fn)) {
      delete this.$events[name];
    }
  }

  return this;
};

/**
 * Remove all listeners for an event.
 *
 * @api public
 * @param {string} name Event name.
 * @return {EventEmitter} Emitter instance.
 */
EventEmitter.prototype.removeAllListeners = function (name) {
  if (name === undefined) {
    this.$events = {};
    return this;
  }

  if (this.$events && this.$events[name]) {
    this.$events[name] = null;
  }

  return this;
};

/**
 * Get all listeners for a given event.
 *
 * @api public
 * @param {string} name Event name.
 * @return {EventEmitter} Emitter instance.
 */
EventEmitter.prototype.listeners = function (name) {
  if (!this.$events) {
    this.$events = {};
  }

  if (!this.$events[name]) {
    this.$events[name] = [];
  }

  if (!isArray(this.$events[name])) {
    this.$events[name] = [this.$events[name]];
  }

  return this.$events[name];
};

/**
 * Emit an event.
 *
 * @api public
 * @param {string} name Event name.
 * @return {boolean} true if at least one handler was invoked, else false.
 */
EventEmitter.prototype.emit = function (name) {
  if (!this.$events) {
    return false;
  }

  var handler = this.$events[name];

  if (!handler) {
    return false;
  }

  var args = Array.prototype.slice.call(arguments, 1);

  if (typeof handler === 'function') {
    handler.apply(this, args);
  } else if (isArray(handler)) {
    var listeners = handler.slice();

    for (var i = 0, l = listeners.length; i < l; i++) {
      listeners[i].apply(this, args);
    }
  } else {
    return false;
  }

  return true;
};

},{}],4:[function(require,module,exports){
'use strict';

/**
 * Expose `Progress`.
 */

module.exports = Progress;

/**
 * Initialize a new `Progress` indicator.
 */
function Progress () {
  this.percent = 0;
  this.size(0);
  this.fontSize(11);
  this.font('helvetica, arial, sans-serif');
}

/**
 * Set progress size to `size`.
 *
 * @api public
 * @param {number} size
 * @return {Progress} Progress instance.
 */
Progress.prototype.size = function (size) {
  this._size = size;
  return this;
};

/**
 * Set text to `text`.
 *
 * @api public
 * @param {string} text
 * @return {Progress} Progress instance.
 */
Progress.prototype.text = function (text) {
  this._text = text;
  return this;
};

/**
 * Set font size to `size`.
 *
 * @api public
 * @param {number} size
 * @return {Progress} Progress instance.
 */
Progress.prototype.fontSize = function (size) {
  this._fontSize = size;
  return this;
};

/**
 * Set font to `family`.
 *
 * @param {string} family
 * @return {Progress} Progress instance.
 */
Progress.prototype.font = function (family) {
  this._font = family;
  return this;
};

/**
 * Update percentage to `n`.
 *
 * @param {number} n
 * @return {Progress} Progress instance.
 */
Progress.prototype.update = function (n) {
  this.percent = n;
  return this;
};

/**
 * Draw on `ctx`.
 *
 * @param {CanvasRenderingContext2d} ctx
 * @return {Progress} Progress instance.
 */
Progress.prototype.draw = function (ctx) {
  try {
    var percent = Math.min(this.percent, 100);
    var size = this._size;
    var half = size / 2;
    var x = half;
    var y = half;
    var rad = half - 1;
    var fontSize = this._fontSize;

    ctx.font = fontSize + 'px ' + this._font;

    var angle = Math.PI * 2 * (percent / 100);
    ctx.clearRect(0, 0, size, size);

    // outer circle
    ctx.strokeStyle = '#9f9f9f';
    ctx.beginPath();
    ctx.arc(x, y, rad, 0, angle, false);
    ctx.stroke();

    // inner circle
    ctx.strokeStyle = '#eee';
    ctx.beginPath();
    ctx.arc(x, y, rad - 1, 0, angle, true);
    ctx.stroke();

    // text
    var text = this._text || (percent | 0) + '%';
    var w = ctx.measureText(text).width;

    ctx.fillText(text, x - w / 2 + 1, y + fontSize / 2 - 1);
  } catch (err) {
    // don't fail if we can't render progress
  }
  return this;
};

},{}],5:[function(require,module,exports){
(function (global){
'use strict';

exports.isatty = function isatty () {
  return true;
};

exports.getWindowSize = function getWindowSize () {
  if ('innerHeight' in global) {
    return [global.innerHeight, global.innerWidth];
  }
  // In a Web Worker, the DOM Window is not available.
  return [640, 480];
};

}).call(this,typeof global !== "undefined" ? global : typeof self !== "undefined" ? self : typeof window !== "undefined" ? window : {})
},{}],6:[function(require,module,exports){
'use strict';

/**
 * Module dependencies.
 */

var JSON = require('json3');

/**
 * Expose `Context`.
 */

module.exports = Context;

/**
 * Initialize a new `Context`.
 *
 * @api private
 */
function Context () {}

/**
 * Set or get the context `Runnable` to `runnable`.
 *
 * @api private
 * @param {Runnable} runnable
 * @return {Context}
 */
Context.prototype.runnable = function (runnable) {
  if (!arguments.length) {
    return this._runnable;
  }
  this.test = this._runnable = runnable;
  return this;
};

/**
 * Set test timeout `ms`.
 *
 * @api private
 * @param {number} ms
 * @return {Context} self
 */
Context.prototype.timeout = function (ms) {
  if (!arguments.length) {
    return this.runnable().timeout();
  }
  this.runnable().timeout(ms);
  return this;
};

/**
 * Set test timeout `enabled`.
 *
 * @api private
 * @param {boolean} enabled
 * @return {Context} self
 */
Context.prototype.enableTimeouts = function (enabled) {
  this.runnable().enableTimeouts(enabled);
  return this;
};

/**
 * Set test slowness threshold `ms`.
 *
 * @api private
 * @param {number} ms
 * @return {Context} self
 */
Context.prototype.slow = function (ms) {
  this.runnable().slow(ms);
  return this;
};

/**
 * Mark a test as skipped.
 *
 * @api private
 * @return {Context} self
 */
Context.prototype.skip = function () {
  this.runnable().skip();
  return this;
};

/**
 * Allow a number of retries on failed tests
 *
 * @api private
 * @param {number} n
 * @return {Context} self
 */
Context.prototype.retries = function (n) {
  if (!arguments.length) {
    return this.runnable().retries();
  }
  this.runnable().retries(n);
  return this;
};

/**
 * Inspect the context void of `._runnable`.
 *
 * @api private
 * @return {string}
 */
Context.prototype.inspect = function () {
  return JSON.stringify(this, function (key, val) {
    return key === 'runnable' || key === 'test' ? undefined : val;
  }, 2);
};

},{"json3":54}],7:[function(require,module,exports){
'use strict';

/**
 * Module dependencies.
 */

var Runnable = require('./runnable');
var inherits = require('./utils').inherits;

/**
 * Expose `Hook`.
 */

module.exports = Hook;

/**
 * Initialize a new `Hook` with the given `title` and callback `fn`.
 *
 * @param {String} title
 * @param {Function} fn
 * @api private
 */
function Hook (title, fn) {
  Runnable.call(this, title, fn);
  this.type = 'hook';
}

/**
 * Inherit from `Runnable.prototype`.
 */
inherits(Hook, Runnable);

/**
 * Get or set the test `err`.
 *
 * @param {Error} err
 * @return {Error}
 * @api public
 */
Hook.prototype.error = function (err) {
  if (!arguments.length) {
    err = this._error;
    this._error = null;
    return err;
  }

  this._error = err;
};

},{"./runnable":33,"./utils":38}],8:[function(require,module,exports){
'use strict';

/**
 * Module dependencies.
 */

var Test = require('../test');

/**
 * BDD-style interface:
 *
 *      describe('Array', function() {
 *        describe('#indexOf()', function() {
 *          it('should return -1 when not present', function() {
 *            // ...
 *          });
 *
 *          it('should return the index when present', function() {
 *            // ...
 *          });
 *        });
 *      });
 *
 * @param {Suite} suite Root suite.
 */
module.exports = function (suite) {
  var suites = [suite];

  suite.on('pre-require', function (context, file, mocha) {
    var common = require('./common')(suites, context, mocha);

    context.before = common.before;
    context.after = common.after;
    context.beforeEach = common.beforeEach;
    context.afterEach = common.afterEach;
    context.run = mocha.options.delay && common.runWithSuite(suite);
    /**
     * Describe a "suite" with the given `title`
     * and callback `fn` containing nested suites
     * and/or tests.
     */

    context.describe = context.context = function (title, fn) {
      return common.suite.create({
        title: title,
        file: file,
        fn: fn
      });
    };

    /**
     * Pending describe.
     */

    context.xdescribe = context.xcontext = context.describe.skip = function (title, fn) {
      return common.suite.skip({
        title: title,
        file: file,
        fn: fn
      });
    };

    /**
     * Exclusive suite.
     */

    context.describe.only = function (title, fn) {
      return common.suite.only({
        title: title,
        file: file,
        fn: fn
      });
    };

    /**
     * Describe a specification or test-case
     * with the given `title` and callback `fn`
     * acting as a thunk.
     */

    context.it = context.specify = function (title, fn) {
      var suite = suites[0];
      if (suite.isPending()) {
        fn = null;
      }
      var test = new Test(title, fn);
      test.file = file;
      suite.addTest(test);
      return test;
    };

    /**
     * Exclusive test-case.
     */

    context.it.only = function (title, fn) {
      return common.test.only(mocha, context.it(title, fn));
    };

    /**
     * Pending test case.
     */

    context.xit = context.xspecify = context.it.skip = function (title) {
      context.it(title);
    };

    /**
     * Number of attempts to retry.
     */
    context.it.retries = function (n) {
      context.retries(n);
    };
  });
};

},{"../test":36,"./common":9}],9:[function(require,module,exports){
'use strict';

var Suite = require('../suite');

/**
 * Functions common to more than one interface.
 *
 * @param {Suite[]} suites
 * @param {Context} context
 * @param {Mocha} mocha
 * @return {Object} An object containing common functions.
 */
module.exports = function (suites, context, mocha) {
  return {
    /**
     * This is only present if flag --delay is passed into Mocha. It triggers
     * root suite execution.
     *
     * @param {Suite} suite The root wuite.
     * @return {Function} A function which runs the root suite
     */
    runWithSuite: function runWithSuite (suite) {
      return function run () {
        suite.run();
      };
    },

    /**
     * Execute before running tests.
     *
     * @param {string} name
     * @param {Function} fn
     */
    before: function (name, fn) {
      suites[0].beforeAll(name, fn);
    },

    /**
     * Execute after running tests.
     *
     * @param {string} name
     * @param {Function} fn
     */
    after: function (name, fn) {
      suites[0].afterAll(name, fn);
    },

    /**
     * Execute before each test case.
     *
     * @param {string} name
     * @param {Function} fn
     */
    beforeEach: function (name, fn) {
      suites[0].beforeEach(name, fn);
    },

    /**
     * Execute after each test case.
     *
     * @param {string} name
     * @param {Function} fn
     */
    afterEach: function (name, fn) {
      suites[0].afterEach(name, fn);
    },

    suite: {
      /**
       * Create an exclusive Suite; convenience function
       * See docstring for create() below.
       *
       * @param {Object} opts
       * @returns {Suite}
       */
      only: function only (opts) {
        mocha.options.hasOnly = true;
        opts.isOnly = true;
        return this.create(opts);
      },

      /**
       * Create a Suite, but skip it; convenience function
       * See docstring for create() below.
       *
       * @param {Object} opts
       * @returns {Suite}
       */
      skip: function skip (opts) {
        opts.pending = true;
        return this.create(opts);
      },

      /**
       * Creates a suite.
       * @param {Object} opts Options
       * @param {string} opts.title Title of Suite
       * @param {Function} [opts.fn] Suite Function (not always applicable)
       * @param {boolean} [opts.pending] Is Suite pending?
       * @param {string} [opts.file] Filepath where this Suite resides
       * @param {boolean} [opts.isOnly] Is Suite exclusive?
       * @returns {Suite}
       */
      create: function create (opts) {
        var suite = Suite.create(suites[0], opts.title);
        suite.pending = Boolean(opts.pending);
        suite.file = opts.file;
        suites.unshift(suite);
        if (opts.isOnly) {
          suite.parent._onlySuites = suite.parent._onlySuites.concat(suite);
          mocha.options.hasOnly = true;
        }
        if (typeof opts.fn === 'function') {
          opts.fn.call(suite);
          suites.shift();
        } else if (typeof opts.fn === 'undefined' && !suite.pending) {
          throw new Error('Suite "' + suite.fullTitle() + '" was defined but no callback was supplied. Supply a callback or explicitly skip the suite.');
        }

        return suite;
      }
    },

    test: {

      /**
       * Exclusive test-case.
       *
       * @param {Object} mocha
       * @param {Function} test
       * @returns {*}
       */
      only: function (mocha, test) {
        test.parent._onlyTests = test.parent._onlyTests.concat(test);
        mocha.options.hasOnly = true;
        return test;
      },

      /**
       * Pending test case.
       *
       * @param {string} title
       */
      skip: function (title) {
        context.test(title);
      },

      /**
       * Number of retry attempts
       *
       * @param {number} n
       */
      retries: function (n) {
        context.retries(n);
      }
    }
  };
};

},{"../suite":35}],10:[function(require,module,exports){
'use strict';

/**
 * Module dependencies.
 */

var Suite = require('../suite');
var Test = require('../test');

/**
 * Exports-style (as Node.js module) interface:
 *
 *     exports.Array = {
 *       '#indexOf()': {
 *         'should return -1 when the value is not present': function() {
 *
 *         },
 *
 *         'should return the correct index when the value is present': function() {
 *
 *         }
 *       }
 *     };
 *
 * @param {Suite} suite Root suite.
 */
module.exports = function (suite) {
  var suites = [suite];

  suite.on('require', visit);

  function visit (obj, file) {
    var suite;
    for (var key in obj) {
      if (typeof obj[key] === 'function') {
        var fn = obj[key];
        switch (key) {
          case 'before':
            suites[0].beforeAll(fn);
            break;
          case 'after':
            suites[0].afterAll(fn);
            break;
          case 'beforeEach':
            suites[0].beforeEach(fn);
            break;
          case 'afterEach':
            suites[0].afterEach(fn);
            break;
          default:
            var test = new Test(key, fn);
            test.file = file;
            suites[0].addTest(test);
        }
      } else {
        suite = Suite.create(suites[0], key);
        suites.unshift(suite);
        visit(obj[key], file);
        suites.shift();
      }
    }
  }
};

},{"../suite":35,"../test":36}],11:[function(require,module,exports){
'use strict';

exports.bdd = require('./bdd');
exports.tdd = require('./tdd');
exports.qunit = require('./qunit');
exports.exports = require('./exports');

},{"./bdd":8,"./exports":10,"./qunit":12,"./tdd":13}],12:[function(require,module,exports){
'use strict';

/**
 * Module dependencies.
 */

var Test = require('../test');

/**
 * QUnit-style interface:
 *
 *     suite('Array');
 *
 *     test('#length', function() {
 *       var arr = [1,2,3];
 *       ok(arr.length == 3);
 *     });
 *
 *     test('#indexOf()', function() {
 *       var arr = [1,2,3];
 *       ok(arr.indexOf(1) == 0);
 *       ok(arr.indexOf(2) == 1);
 *       ok(arr.indexOf(3) == 2);
 *     });
 *
 *     suite('String');
 *
 *     test('#length', function() {
 *       ok('foo'.length == 3);
 *     });
 *
 * @param {Suite} suite Root suite.
 */
module.exports = function (suite) {
  var suites = [suite];

  suite.on('pre-require', function (context, file, mocha) {
    var common = require('./common')(suites, context, mocha);

    context.before = common.before;
    context.after = common.after;
    context.beforeEach = common.beforeEach;
    context.afterEach = common.afterEach;
    context.run = mocha.options.delay && common.runWithSuite(suite);
    /**
     * Describe a "suite" with the given `title`.
     */

    context.suite = function (title) {
      if (suites.length > 1) {
        suites.shift();
      }
      return common.suite.create({
        title: title,
        file: file,
        fn: false
      });
    };

    /**
     * Exclusive Suite.
     */

    context.suite.only = function (title) {
      if (suites.length > 1) {
        suites.shift();
      }
      return common.suite.only({
        title: title,
        file: file,
        fn: false
      });
    };

    /**
     * Describe a specification or test-case
     * with the given `title` and callback `fn`
     * acting as a thunk.
     */

    context.test = function (title, fn) {
      var test = new Test(title, fn);
      test.file = file;
      suites[0].addTest(test);
      return test;
    };

    /**
     * Exclusive test-case.
     */

    context.test.only = function (title, fn) {
      return common.test.only(mocha, context.test(title, fn));
    };

    context.test.skip = common.test.skip;
    context.test.retries = common.test.retries;
  });
};

},{"../test":36,"./common":9}],13:[function(require,module,exports){
'use strict';

/**
 * Module dependencies.
 */

var Test = require('../test');

/**
 * TDD-style interface:
 *
 *      suite('Array', function() {
 *        suite('#indexOf()', function() {
 *          suiteSetup(function() {
 *
 *          });
 *
 *          test('should return -1 when not present', function() {
 *
 *          });
 *
 *          test('should return the index when present', function() {
 *
 *          });
 *
 *          suiteTeardown(function() {
 *
 *          });
 *        });
 *      });
 *
 * @param {Suite} suite Root suite.
 */
module.exports = function (suite) {
  var suites = [suite];

  suite.on('pre-require', function (context, file, mocha) {
    var common = require('./common')(suites, context, mocha);

    context.setup = common.beforeEach;
    context.teardown = common.afterEach;
    context.suiteSetup = common.before;
    context.suiteTeardown = common.after;
    context.run = mocha.options.delay && common.runWithSuite(suite);

    /**
     * Describe a "suite" with the given `title` and callback `fn` containing
     * nested suites and/or tests.
     */
    context.suite = function (title, fn) {
      return common.suite.create({
        title: title,
        file: file,
        fn: fn
      });
    };

    /**
     * Pending suite.
     */
    context.suite.skip = function (title, fn) {
      return common.suite.skip({
        title: title,
        file: file,
        fn: fn
      });
    };

    /**
     * Exclusive test-case.
     */
    context.suite.only = function (title, fn) {
      return common.suite.only({
        title: title,
        file: file,
        fn: fn
      });
    };

    /**
     * Describe a specification or test-case with the given `title` and
     * callback `fn` acting as a thunk.
     */
    context.test = function (title, fn) {
      var suite = suites[0];
      if (suite.isPending()) {
        fn = null;
      }
      var test = new Test(title, fn);
      test.file = file;
      suite.addTest(test);
      return test;
    };

    /**
     * Exclusive test-case.
     */

    context.test.only = function (title, fn) {
      return common.test.only(mocha, context.test(title, fn));
    };

    context.test.skip = common.test.skip;
    context.test.retries = common.test.retries;
  });
};

},{"../test":36,"./common":9}],14:[function(require,module,exports){
(function (process,global,__dirname){
'use strict';

/*!
 * mocha
 * Copyright(c) 2011 TJ Holowaychuk <tj@vision-media.ca>
 * MIT Licensed
 */

/**
 * Module dependencies.
 */

var escapeRe = require('escape-string-regexp');
var path = require('path');
var reporters = require('./reporters');
var utils = require('./utils');

/**
 * Expose `Mocha`.
 */

exports = module.exports = Mocha;

/**
 * To require local UIs and reporters when running in node.
 */

if (!process.browser) {
  var cwd = process.cwd();
  module.paths.push(cwd, path.join(cwd, 'node_modules'));
}

/**
 * Expose internals.
 */

exports.utils = utils;
exports.interfaces = require('./interfaces');
exports.reporters = reporters;
exports.Runnable = require('./runnable');
exports.Context = require('./context');
exports.Runner = require('./runner');
exports.Suite = require('./suite');
exports.Hook = require('./hook');
exports.Test = require('./test');

/**
 * Return image `name` path.
 *
 * @api private
 * @param {string} name
 * @return {string}
 */
function image (name) {
  return path.join(__dirname, '../images', name + '.png');
}

/**
 * Set up mocha with `options`.
 *
 * Options:
 *
 *   - `ui` name "bdd", "tdd", "exports" etc
 *   - `reporter` reporter instance, defaults to `mocha.reporters.spec`
 *   - `globals` array of accepted globals
 *   - `timeout` timeout in milliseconds
 *   - `retries` number of times to retry failed tests
 *   - `bail` bail on the first test failure
 *   - `slow` milliseconds to wait before considering a test slow
 *   - `ignoreLeaks` ignore global leaks
 *   - `fullTrace` display the full stack-trace on failing
 *   - `grep` string or regexp to filter tests with
 *
 * @param {Object} options
 * @api public
 */
function Mocha (options) {
  options = options || {};
  this.files = [];
  this.options = options;
  if (options.grep) {
    this.grep(new RegExp(options.grep));
  }
  if (options.fgrep) {
    this.fgrep(options.fgrep);
  }
  this.suite = new exports.Suite('', new exports.Context());
  this.ui(options.ui);
  this.bail(options.bail);
  this.reporter(options.reporter, options.reporterOptions);
  if (typeof options.timeout !== 'undefined' && options.timeout !== null) {
    this.timeout(options.timeout);
  }
  if (typeof options.retries !== 'undefined' && options.retries !== null) {
    this.retries(options.retries);
  }
  this.useColors(options.useColors);
  if (options.enableTimeouts !== null) {
    this.enableTimeouts(options.enableTimeouts);
  }
  if (options.slow) {
    this.slow(options.slow);
  }
}

/**
 * Enable or disable bailing on the first failure.
 *
 * @api public
 * @param {boolean} [bail]
 */
Mocha.prototype.bail = function (bail) {
  if (!arguments.length) {
    bail = true;
  }
  this.suite.bail(bail);
  return this;
};

/**
 * Add test `file`.
 *
 * @api public
 * @param {string} file
 */
Mocha.prototype.addFile = function (file) {
  this.files.push(file);
  return this;
};

/**
 * Set reporter to `reporter`, defaults to "spec".
 *
 * @param {String|Function} reporter name or constructor
 * @param {Object} reporterOptions optional options
 * @api public
 * @param {string|Function} reporter name or constructor
 * @param {Object} reporterOptions optional options
 */
Mocha.prototype.reporter = function (reporter, reporterOptions) {
  if (typeof reporter === 'function') {
    this._reporter = reporter;
  } else {
    reporter = reporter || 'spec';
    var _reporter;
    // Try to load a built-in reporter.
    if (reporters[reporter]) {
      _reporter = reporters[reporter];
    }
    // Try to load reporters from process.cwd() and node_modules
    if (!_reporter) {
      try {
        _reporter = require(reporter);
      } catch (err) {
        err.message.indexOf('Cannot find module') !== -1
          ? console.warn('"' + reporter + '" reporter not found')
          : console.warn('"' + reporter + '" reporter blew up with error:\n' + err.stack);
      }
    }
    if (!_reporter && reporter === 'teamcity') {
      console.warn('The Teamcity reporter was moved to a package named ' +
        'mocha-teamcity-reporter ' +
        '(https://npmjs.org/package/mocha-teamcity-reporter).');
    }
    if (!_reporter) {
      throw new Error('invalid reporter "' + reporter + '"');
    }
    this._reporter = _reporter;
  }
  this.options.reporterOptions = reporterOptions;
  return this;
};

/**
 * Set test UI `name`, defaults to "bdd".
 *
 * @api public
 * @param {string} bdd
 */
Mocha.prototype.ui = function (name) {
  name = name || 'bdd';
  this._ui = exports.interfaces[name];
  if (!this._ui) {
    try {
      this._ui = require(name);
    } catch (err) {
      throw new Error('invalid interface "' + name + '"');
    }
  }
  this._ui = this._ui(this.suite);

  this.suite.on('pre-require', function (context) {
    exports.afterEach = context.afterEach || context.teardown;
    exports.after = context.after || context.suiteTeardown;
    exports.beforeEach = context.beforeEach || context.setup;
    exports.before = context.before || context.suiteSetup;
    exports.describe = context.describe || context.suite;
    exports.it = context.it || context.test;
    exports.setup = context.setup || context.beforeEach;
    exports.suiteSetup = context.suiteSetup || context.before;
    exports.suiteTeardown = context.suiteTeardown || context.after;
    exports.suite = context.suite || context.describe;
    exports.teardown = context.teardown || context.afterEach;
    exports.test = context.test || context.it;
    exports.run = context.run;
  });

  return this;
};

/**
 * Load registered files.
 *
 * @api private
 */
Mocha.prototype.loadFiles = function (fn) {
  var self = this;
  var suite = this.suite;
  this.files.forEach(function (file) {
    file = path.resolve(file);
    suite.emit('pre-require', global, file, self);
    suite.emit('require', require(file), file, self);
    suite.emit('post-require', global, file, self);
  });
  fn && fn();
};

/**
 * Enable growl support.
 *
 * @api private
 */
Mocha.prototype._growl = function (runner, reporter) {
  var notify = require('growl');

  runner.on('end', function () {
    var stats = reporter.stats;
    if (stats.failures) {
      var msg = stats.failures + ' of ' + runner.total + ' tests failed';
      notify(msg, { name: 'mocha', title: 'Failed', image: image('error') });
    } else {
      notify(stats.passes + ' tests passed in ' + stats.duration + 'ms', {
        name: 'mocha',
        title: 'Passed',
        image: image('ok')
      });
    }
  });
};

/**
 * Escape string and add it to grep as a regexp.
 *
 * @api public
 * @param str
 * @returns {Mocha}
 */
Mocha.prototype.fgrep = function (str) {
  return this.grep(new RegExp(escapeRe(str)));
};

/**
 * Add regexp to grep, if `re` is a string it is escaped.
 *
 * @param {RegExp|String} re
 * @return {Mocha}
 * @api public
 * @param {RegExp|string} re
 * @return {Mocha}
 */
Mocha.prototype.grep = function (re) {
  if (utils.isString(re)) {
    // extract args if it's regex-like, i.e: [string, pattern, flag]
    var arg = re.match(/^\/(.*)\/(g|i|)$|.*/);
    this.options.grep = new RegExp(arg[1] || arg[0], arg[2]);
  } else {
    this.options.grep = re;
  }
  return this;
};
/**
 * Invert `.grep()` matches.
 *
 * @return {Mocha}
 * @api public
 */
Mocha.prototype.invert = function () {
  this.options.invert = true;
  return this;
};

/**
 * Ignore global leaks.
 *
 * @param {Boolean} ignore
 * @return {Mocha}
 * @api public
 * @param {boolean} ignore
 * @return {Mocha}
 */
Mocha.prototype.ignoreLeaks = function (ignore) {
  this.options.ignoreLeaks = Boolean(ignore);
  return this;
};

/**
 * Enable global leak checking.
 *
 * @return {Mocha}
 * @api public
 */
Mocha.prototype.checkLeaks = function () {
  this.options.ignoreLeaks = false;
  return this;
};

/**
 * Display long stack-trace on failing
 *
 * @return {Mocha}
 * @api public
 */
Mocha.prototype.fullTrace = function () {
  this.options.fullStackTrace = true;
  return this;
};

/**
 * Enable growl support.
 *
 * @return {Mocha}
 * @api public
 */
Mocha.prototype.growl = function () {
  this.options.growl = true;
  return this;
};

/**
 * Ignore `globals` array or string.
 *
 * @param {Array|String} globals
 * @return {Mocha}
 * @api public
 * @param {Array|string} globals
 * @return {Mocha}
 */
Mocha.prototype.globals = function (globals) {
  this.options.globals = (this.options.globals || []).concat(globals);
  return this;
};

/**
 * Emit color output.
 *
 * @param {Boolean} colors
 * @return {Mocha}
 * @api public
 * @param {boolean} colors
 * @return {Mocha}
 */
Mocha.prototype.useColors = function (colors) {
  if (colors !== undefined) {
    this.options.useColors = colors;
  }
  return this;
};

/**
 * Use inline diffs rather than +/-.
 *
 * @param {Boolean} inlineDiffs
 * @return {Mocha}
 * @api public
 * @param {boolean} inlineDiffs
 * @return {Mocha}
 */
Mocha.prototype.useInlineDiffs = function (inlineDiffs) {
  this.options.useInlineDiffs = inlineDiffs !== undefined && inlineDiffs;
  return this;
};

/**
 * Set the timeout in milliseconds.
 *
 * @param {Number} timeout
 * @return {Mocha}
 * @api public
 * @param {number} timeout
 * @return {Mocha}
 */
Mocha.prototype.timeout = function (timeout) {
  this.suite.timeout(timeout);
  return this;
};

/**
 * Set the number of times to retry failed tests.
 *
 * @param {Number} retry times
 * @return {Mocha}
 * @api public
 */
Mocha.prototype.retries = function (n) {
  this.suite.retries(n);
  return this;
};

/**
 * Set slowness threshold in milliseconds.
 *
 * @param {Number} slow
 * @return {Mocha}
 * @api public
 * @param {number} slow
 * @return {Mocha}
 */
Mocha.prototype.slow = function (slow) {
  this.suite.slow(slow);
  return this;
};

/**
 * Enable timeouts.
 *
 * @param {Boolean} enabled
 * @return {Mocha}
 * @api public
 * @param {boolean} enabled
 * @return {Mocha}
 */
Mocha.prototype.enableTimeouts = function (enabled) {
  this.suite.enableTimeouts(arguments.length && enabled !== undefined ? enabled : true);
  return this;
};

/**
 * Makes all tests async (accepting a callback)
 *
 * @return {Mocha}
 * @api public
 */
Mocha.prototype.asyncOnly = function () {
  this.options.asyncOnly = true;
  return this;
};

/**
 * Disable syntax highlighting (in browser).
 *
 * @api public
 */
Mocha.prototype.noHighlighting = function () {
  this.options.noHighlighting = true;
  return this;
};

/**
 * Enable uncaught errors to propagate (in browser).
 *
 * @return {Mocha}
 * @api public
 */
Mocha.prototype.allowUncaught = function () {
  this.options.allowUncaught = true;
  return this;
};

/**
 * Delay root suite execution.
 * @returns {Mocha}
 */
Mocha.prototype.delay = function delay () {
  this.options.delay = true;
  return this;
};

/**
 * Run tests and invoke `fn()` when complete.
 *
 * @api public
 * @param {Function} fn
 * @return {Runner}
 */
Mocha.prototype.run = function (fn) {
  if (this.files.length) {
    this.loadFiles();
  }
  var suite = this.suite;
  var options = this.options;
  options.files = this.files;
  var runner = new exports.Runner(suite, options.delay);
  var reporter = new this._reporter(runner, options);
  runner.ignoreLeaks = options.ignoreLeaks !== false;
  runner.fullStackTrace = options.fullStackTrace;
  runner.hasOnly = options.hasOnly;
  runner.asyncOnly = options.asyncOnly;
  runner.allowUncaught = options.allowUncaught;
  if (options.grep) {
    runner.grep(options.grep, options.invert);
  }
  if (options.globals) {
    runner.globals(options.globals);
  }
  if (options.growl) {
    this._growl(runner, reporter);
  }
  if (options.useColors !== undefined) {
    exports.reporters.Base.useColors = options.useColors;
  }
  exports.reporters.Base.inlineDiffs = options.useInlineDiffs;

  function done (failures) {
    if (reporter.done) {
      reporter.done(failures, fn);
    } else {
      fn && fn(failures);
    }
  }

  return runner.run(done);
};

}).call(this,require('_process'),typeof global !== "undefined" ? global : typeof self !== "undefined" ? self : typeof window !== "undefined" ? window : {},"/lib")
},{"./context":6,"./hook":7,"./interfaces":11,"./reporters":21,"./runnable":33,"./runner":34,"./suite":35,"./test":36,"./utils":38,"_process":67,"escape-string-regexp":47,"growl":49,"path":42}],15:[function(require,module,exports){
'use strict';

/**
 * Helpers.
 */

var s = 1000;
var m = s * 60;
var h = m * 60;
var d = h * 24;
var y = d * 365.25;

/**
 * Parse or format the given `val`.
 *
 * Options:
 *
 *  - `long` verbose formatting [false]
 *
 * @api public
 * @param {string|number} val
 * @param {Object} options
 * @return {string|number}
 */
module.exports = function (val, options) {
  options = options || {};
  if (typeof val === 'string') {
    return parse(val);
  }
  // https://github.com/mochajs/mocha/pull/1035
  return options['long'] ? longFormat(val) : shortFormat(val);
};

/**
 * Parse the given `str` and return milliseconds.
 *
 * @api private
 * @param {string} str
 * @return {number}
 */
function parse (str) {
  var match = (/^((?:\d+)?\.?\d+) *(ms|seconds?|s|minutes?|m|hours?|h|days?|d|years?|y)?$/i).exec(str);
  if (!match) {
    return;
  }
  var n = parseFloat(match[1]);
  var type = (match[2] || 'ms').toLowerCase();
  switch (type) {
    case 'years':
    case 'year':
    case 'y':
      return n * y;
    case 'days':
    case 'day':
    case 'd':
      return n * d;
    case 'hours':
    case 'hour':
    case 'h':
      return n * h;
    case 'minutes':
    case 'minute':
    case 'm':
      return n * m;
    case 'seconds':
    case 'second':
    case 's':
      return n * s;
    case 'ms':
      return n;
    default:
      // No default case
  }
}

/**
 * Short format for `ms`.
 *
 * @api private
 * @param {number} ms
 * @return {string}
 */
function shortFormat (ms) {
  if (ms >= d) {
    return Math.round(ms / d) + 'd';
  }
  if (ms >= h) {
    return Math.round(ms / h) + 'h';
  }
  if (ms >= m) {
    return Math.round(ms / m) + 'm';
  }
  if (ms >= s) {
    return Math.round(ms / s) + 's';
  }
  return ms + 'ms';
}

/**
 * Long format for `ms`.
 *
 * @api private
 * @param {number} ms
 * @return {string}
 */
function longFormat (ms) {
  return plural(ms, d, 'day') ||
    plural(ms, h, 'hour') ||
    plural(ms, m, 'minute') ||
    plural(ms, s, 'second') ||
    ms + ' ms';
}

/**
 * Pluralization helper.
 *
 * @api private
 * @param {number} ms
 * @param {number} n
 * @param {string} name
 */
function plural (ms, n, name) {
  if (ms < n) {
    return;
  }
  if (ms < n * 1.5) {
    return Math.floor(ms / n) + ' ' + name;
  }
  return Math.ceil(ms / n) + ' ' + name + 's';
}

},{}],16:[function(require,module,exports){
'use strict';

/**
 * Expose `Pending`.
 */

module.exports = Pending;

/**
 * Initialize a new `Pending` error with the given message.
 *
 * @param {string} message
 */
function Pending (message) {
  this.message = message;
}

},{}],17:[function(require,module,exports){
(function (process,global){
'use strict';

/**
 * Module dependencies.
 */

var tty = require('tty');
var diff = require('diff');
var ms = require('../ms');
var utils = require('../utils');
var supportsColor = process.browser ? null : require('supports-color');

/**
 * Expose `Base`.
 */

exports = module.exports = Base;

/**
 * Save timer references to avoid Sinon interfering.
 * See: https://github.com/mochajs/mocha/issues/237
 */

/* eslint-disable no-unused-vars, no-native-reassign */
var Date = global.Date;
var setTimeout = global.setTimeout;
var setInterval = global.setInterval;
var clearTimeout = global.clearTimeout;
var clearInterval = global.clearInterval;
/* eslint-enable no-unused-vars, no-native-reassign */

/**
 * Check if both stdio streams are associated with a tty.
 */

var isatty = tty.isatty(1) && tty.isatty(2);

/**
 * Enable coloring by default, except in the browser interface.
 */

exports.useColors = !process.browser && (supportsColor || (process.env.MOCHA_COLORS !== undefined));

/**
 * Inline diffs instead of +/-
 */

exports.inlineDiffs = false;

/**
 * Default color map.
 */

exports.colors = {
  pass: 90,
  fail: 31,
  'bright pass': 92,
  'bright fail': 91,
  'bright yellow': 93,
  pending: 36,
  suite: 0,
  'error title': 0,
  'error message': 31,
  'error stack': 90,
  checkmark: 32,
  fast: 90,
  medium: 33,
  slow: 31,
  green: 32,
  light: 90,
  'diff gutter': 90,
  'diff added': 32,
  'diff removed': 31
};

/**
 * Default symbol map.
 */

exports.symbols = {
  ok: '✓',
  err: '✖',
  dot: '․',
  comma: ',',
  bang: '!'
};

// With node.js on Windows: use symbols available in terminal default fonts
if (process.platform === 'win32') {
  exports.symbols.ok = '\u221A';
  exports.symbols.err = '\u00D7';
  exports.symbols.dot = '.';
}

/**
 * Color `str` with the given `type`,
 * allowing colors to be disabled,
 * as well as user-defined color
 * schemes.
 *
 * @param {string} type
 * @param {string} str
 * @return {string}
 * @api private
 */
var color = exports.color = function (type, str) {
  if (!exports.useColors) {
    return String(str);
  }
  return '\u001b[' + exports.colors[type] + 'm' + str + '\u001b[0m';
};

/**
 * Expose term window size, with some defaults for when stderr is not a tty.
 */

exports.window = {
  width: 75
};

if (isatty) {
  exports.window.width = process.stdout.getWindowSize
      ? process.stdout.getWindowSize(1)[0]
      : tty.getWindowSize()[1];
}

/**
 * Expose some basic cursor interactions that are common among reporters.
 */

exports.cursor = {
  hide: function () {
    isatty && process.stdout.write('\u001b[?25l');
  },

  show: function () {
    isatty && process.stdout.write('\u001b[?25h');
  },

  deleteLine: function () {
    isatty && process.stdout.write('\u001b[2K');
  },

  beginningOfLine: function () {
    isatty && process.stdout.write('\u001b[0G');
  },

  CR: function () {
    if (isatty) {
      exports.cursor.deleteLine();
      exports.cursor.beginningOfLine();
    } else {
      process.stdout.write('\r');
    }
  }
};

/**
 * Outut the given `failures` as a list.
 *
 * @param {Array} failures
 * @api public
 */

exports.list = function (failures) {
  console.log();
  failures.forEach(function (test, i) {
    // format
    var fmt = color('error title', '  %s) %s:\n') +
      color('error message', '     %s') +
      color('error stack', '\n%s\n');

    // msg
    var msg;
    var err = test.err;
    var message;
    if (err.message && typeof err.message.toString === 'function') {
      message = err.message + '';
    } else if (typeof err.inspect === 'function') {
      message = err.inspect() + '';
    } else {
      message = '';
    }
    var stack = err.stack || message;
    var index = message ? stack.indexOf(message) : -1;
    var actual = err.actual;
    var expected = err.expected;
    var escape = true;

    if (index === -1) {
      msg = message;
    } else {
      index += message.length;
      msg = stack.slice(0, index);
      // remove msg from stack
      stack = stack.slice(index + 1);
    }

    // uncaught
    if (err.uncaught) {
      msg = 'Uncaught ' + msg;
    }
    // explicitly show diff
    if (err.showDiff !== false && sameType(actual, expected) && expected !== undefined) {
      escape = false;
      if (!(utils.isString(actual) && utils.isString(expected))) {
        err.actual = actual = utils.stringify(actual);
        err.expected = expected = utils.stringify(expected);
      }

      fmt = color('error title', '  %s) %s:\n%s') + color('error stack', '\n%s\n');
      var match = message.match(/^([^:]+): expected/);
      msg = '\n      ' + color('error message', match ? match[1] : msg);

      if (exports.inlineDiffs) {
        msg += inlineDiff(err, escape);
      } else {
        msg += unifiedDiff(err, escape);
      }
    }

    // indent stack trace
    stack = stack.replace(/^/gm, '  ');

    console.log(fmt, (i + 1), test.fullTitle(), msg, stack);
  });
};

/**
 * Initialize a new `Base` reporter.
 *
 * All other reporters generally
 * inherit from this reporter, providing
 * stats such as test duration, number
 * of tests passed / failed etc.
 *
 * @param {Runner} runner
 * @api public
 */

function Base (runner) {
  var stats = this.stats = { suites: 0, tests: 0, passes: 0, pending: 0, failures: 0 };
  var failures = this.failures = [];

  if (!runner) {
    return;
  }
  this.runner = runner;

  runner.stats = stats;

  runner.on('start', function () {
    stats.start = new Date();
  });

  runner.on('suite', function (suite) {
    stats.suites = stats.suites || 0;
    suite.root || stats.suites++;
  });

  runner.on('test end', function () {
    stats.tests = stats.tests || 0;
    stats.tests++;
  });

  runner.on('pass', function (test) {
    stats.passes = stats.passes || 0;

    if (test.duration > test.slow()) {
      test.speed = 'slow';
    } else if (test.duration > test.slow() / 2) {
      test.speed = 'medium';
    } else {
      test.speed = 'fast';
    }

    stats.passes++;
  });

  runner.on('fail', function (test, err) {
    stats.failures = stats.failures || 0;
    stats.failures++;
    test.err = err;
    failures.push(test);
  });

  runner.on('end', function () {
    stats.end = new Date();
    stats.duration = new Date() - stats.start;
  });

  runner.on('pending', function () {
    stats.pending++;
  });
}

/**
 * Output common epilogue used by many of
 * the bundled reporters.
 *
 * @api public
 */
Base.prototype.epilogue = function () {
  var stats = this.stats;
  var fmt;

  console.log();

  // passes
  fmt = color('bright pass', ' ') +
    color('green', ' %d passing') +
    color('light', ' (%s)');

  console.log(fmt,
    stats.passes || 0,
    ms(stats.duration));

  // pending
  if (stats.pending) {
    fmt = color('pending', ' ') +
      color('pending', ' %d pending');

    console.log(fmt, stats.pending);
  }

  // failures
  if (stats.failures) {
    fmt = color('fail', '  %d failing');

    console.log(fmt, stats.failures);

    Base.list(this.failures);
    console.log();
  }

  console.log();
};

/**
 * Pad the given `str` to `len`.
 *
 * @api private
 * @param {string} str
 * @param {string} len
 * @return {string}
 */
function pad (str, len) {
  str = String(str);
  return Array(len - str.length + 1).join(' ') + str;
}

/**
 * Returns an inline diff between 2 strings with coloured ANSI output
 *
 * @api private
 * @param {Error} err with actual/expected
 * @param {boolean} escape
 * @return {string} Diff
 */
function inlineDiff (err, escape) {
  var msg = errorDiff(err, 'WordsWithSpace', escape);

  // linenos
  var lines = msg.split('\n');
  if (lines.length > 4) {
    var width = String(lines.length).length;
    msg = lines.map(function (str, i) {
      return pad(++i, width) + ' |' + ' ' + str;
    }).join('\n');
  }

  // legend
  msg = '\n' +
    color('diff removed', 'actual') +
    ' ' +
    color('diff added', 'expected') +
    '\n\n' +
    msg +
    '\n';

  // indent
  msg = msg.replace(/^/gm, '      ');
  return msg;
}

/**
 * Returns a unified diff between two strings.
 *
 * @api private
 * @param {Error} err with actual/expected
 * @param {boolean} escape
 * @return {string} The diff.
 */
function unifiedDiff (err, escape) {
  var indent = '      ';
  function cleanUp (line) {
    if (escape) {
      line = escapeInvisibles(line);
    }
    if (line[0] === '+') {
      return indent + colorLines('diff added', line);
    }
    if (line[0] === '-') {
      return indent + colorLines('diff removed', line);
    }
    if (line.match(/@@/)) {
      return null;
    }
    if (line.match(/\\ No newline/)) {
      return null;
    }
    return indent + line;
  }
  function notBlank (line) {
    return typeof line !== 'undefined' && line !== null;
  }
  var msg = diff.createPatch('string', err.actual, err.expected);
  var lines = msg.split('\n').splice(4);
  return '\n      ' +
    colorLines('diff added', '+ expected') + ' ' +
    colorLines('diff removed', '- actual') +
    '\n\n' +
    lines.map(cleanUp).filter(notBlank).join('\n');
}

/**
 * Return a character diff for `err`.
 *
 * @api private
 * @param {Error} err
 * @param {string} type
 * @param {boolean} escape
 * @return {string}
 */
function errorDiff (err, type, escape) {
  var actual = escape ? escapeInvisibles(err.actual) : err.actual;
  var expected = escape ? escapeInvisibles(err.expected) : err.expected;
  return diff['diff' + type](actual, expected).map(function (str) {
    if (str.added) {
      return colorLines('diff added', str.value);
    }
    if (str.removed) {
      return colorLines('diff removed', str.value);
    }
    return str.value;
  }).join('');
}

/**
 * Returns a string with all invisible characters in plain text
 *
 * @api private
 * @param {string} line
 * @return {string}
 */
function escapeInvisibles (line) {
  return line.replace(/\t/g, '<tab>')
    .replace(/\r/g, '<CR>')
    .replace(/\n/g, '<LF>\n');
}

/**
 * Color lines for `str`, using the color `name`.
 *
 * @api private
 * @param {string} name
 * @param {string} str
 * @return {string}
 */
function colorLines (name, str) {
  return str.split('\n').map(function (str) {
    return color(name, str);
  }).join('\n');
}

/**
 * Object#toString reference.
 */
var objToString = Object.prototype.toString;

/**
 * Check that a / b have the same type.
 *
 * @api private
 * @param {Object} a
 * @param {Object} b
 * @return {boolean}
 */
function sameType (a, b) {
  return objToString.call(a) === objToString.call(b);
}

}).call(this,require('_process'),typeof global !== "undefined" ? global : typeof self !== "undefined" ? self : typeof window !== "undefined" ? window : {})
},{"../ms":15,"../utils":38,"_process":67,"diff":46,"supports-color":42,"tty":5}],18:[function(require,module,exports){
'use strict';

/**
 * Module dependencies.
 */

var Base = require('./base');
var utils = require('../utils');

/**
 * Expose `Doc`.
 */

exports = module.exports = Doc;

/**
 * Initialize a new `Doc` reporter.
 *
 * @param {Runner} runner
 * @api public
 */
function Doc (runner) {
  Base.call(this, runner);

  var indents = 2;

  function indent () {
    return Array(indents).join('  ');
  }

  runner.on('suite', function (suite) {
    if (suite.root) {
      return;
    }
    ++indents;
    console.log('%s<section class="suite">', indent());
    ++indents;
    console.log('%s<h1>%s</h1>', indent(), utils.escape(suite.title));
    console.log('%s<dl>', indent());
  });

  runner.on('suite end', function (suite) {
    if (suite.root) {
      return;
    }
    console.log('%s</dl>', indent());
    --indents;
    console.log('%s</section>', indent());
    --indents;
  });

  runner.on('pass', function (test) {
    console.log('%s  <dt>%s</dt>', indent(), utils.escape(test.title));
    var code = utils.escape(utils.clean(test.body));
    console.log('%s  <dd><pre><code>%s</code></pre></dd>', indent(), code);
  });

  runner.on('fail', function (test, err) {
    console.log('%s  <dt class="error">%s</dt>', indent(), utils.escape(test.title));
    var code = utils.escape(utils.clean(test.body));
    console.log('%s  <dd class="error"><pre><code>%s</code></pre></dd>', indent(), code);
    console.log('%s  <dd class="error">%s</dd>', indent(), utils.escape(err));
  });
}

},{"../utils":38,"./base":17}],19:[function(require,module,exports){
(function (process){
'use strict';

/**
 * Module dependencies.
 */

var Base = require('./base');
var inherits = require('../utils').inherits;
var color = Base.color;

/**
 * Expose `Dot`.
 */

exports = module.exports = Dot;

/**
 * Initialize a new `Dot` matrix test reporter.
 *
 * @api public
 * @param {Runner} runner
 */
function Dot (runner) {
  Base.call(this, runner);

  var self = this;
  var width = Base.window.width * 0.75 | 0;
  var n = -1;

  runner.on('start', function () {
    process.stdout.write('\n');
  });

  runner.on('pending', function () {
    if (++n % width === 0) {
      process.stdout.write('\n  ');
    }
    process.stdout.write(color('pending', Base.symbols.comma));
  });

  runner.on('pass', function (test) {
    if (++n % width === 0) {
      process.stdout.write('\n  ');
    }
    if (test.speed === 'slow') {
      process.stdout.write(color('bright yellow', Base.symbols.dot));
    } else {
      process.stdout.write(color(test.speed, Base.symbols.dot));
    }
  });

  runner.on('fail', function () {
    if (++n % width === 0) {
      process.stdout.write('\n  ');
    }
    process.stdout.write(color('fail', Base.symbols.bang));
  });

  runner.on('end', function () {
    console.log();
    self.epilogue();
  });
}

/**
 * Inherit from `Base.prototype`.
 */
inherits(Dot, Base);

}).call(this,require('_process'))
},{"../utils":38,"./base":17,"_process":67}],20:[function(require,module,exports){
(function (global){
'use strict';

/* eslint-env browser */

/**
 * Module dependencies.
 */

var Base = require('./base');
var utils = require('../utils');
var Progress = require('../browser/progress');
var escapeRe = require('escape-string-regexp');
var escape = utils.escape;

/**
 * Save timer references to avoid Sinon interfering (see GH-237).
 */

/* eslint-disable no-unused-vars, no-native-reassign */
var Date = global.Date;
var setTimeout = global.setTimeout;
var setInterval = global.setInterval;
var clearTimeout = global.clearTimeout;
var clearInterval = global.clearInterval;
/* eslint-enable no-unused-vars, no-native-reassign */

/**
 * Expose `HTML`.
 */

exports = module.exports = HTML;

/**
 * Stats template.
 */

var statsTemplate = '<ul id="mocha-stats">' +
  '<li class="progress"><canvas width="40" height="40"></canvas></li>' +
  '<li class="passes"><a href="javascript:void(0);">passes:</a> <em>0</em></li>' +
  '<li class="failures"><a href="javascript:void(0);">failures:</a> <em>0</em></li>' +
  '<li class="duration">duration: <em>0</em>s</li>' +
  '</ul>';

/**
 * Initialize a new `HTML` reporter.
 *
 * @api public
 * @param {Runner} runner
 */
function HTML (runner) {
  Base.call(this, runner);

  var self = this;
  var stats = this.stats;
  var stat = fragment(statsTemplate);
  var items = stat.getElementsByTagName('li');
  var passes = items[1].getElementsByTagName('em')[0];
  var passesLink = items[1].getElementsByTagName('a')[0];
  var failures = items[2].getElementsByTagName('em')[0];
  var failuresLink = items[2].getElementsByTagName('a')[0];
  var duration = items[3].getElementsByTagName('em')[0];
  var canvas = stat.getElementsByTagName('canvas')[0];
  var report = fragment('<ul id="mocha-report"></ul>');
  var stack = [report];
  var progress;
  var ctx;
  var root = document.getElementById('mocha');

  if (canvas.getContext) {
    var ratio = window.devicePixelRatio || 1;
    canvas.style.width = canvas.width;
    canvas.style.height = canvas.height;
    canvas.width *= ratio;
    canvas.height *= ratio;
    ctx = canvas.getContext('2d');
    ctx.scale(ratio, ratio);
    progress = new Progress();
  }

  if (!root) {
    return error('#mocha div missing, add it to your document');
  }

  // pass toggle
  on(passesLink, 'click', function (evt) {
    evt.preventDefault();
    unhide();
    var name = (/pass/).test(report.className) ? '' : ' pass';
    report.className = report.className.replace(/fail|pass/g, '') + name;
    if (report.className.trim()) {
      hideSuitesWithout('test pass');
    }
  });

  // failure toggle
  on(failuresLink, 'click', function (evt) {
    evt.preventDefault();
    unhide();
    var name = (/fail/).test(report.className) ? '' : ' fail';
    report.className = report.className.replace(/fail|pass/g, '') + name;
    if (report.className.trim()) {
      hideSuitesWithout('test fail');
    }
  });

  root.appendChild(stat);
  root.appendChild(report);

  if (progress) {
    progress.size(40);
  }

  runner.on('suite', function (suite) {
    if (suite.root) {
      return;
    }

    // suite
    var url = self.suiteURL(suite);
    var el = fragment('<li class="suite"><h1><a href="%s">%s</a></h1></li>', url, escape(suite.title));

    // container
    stack[0].appendChild(el);
    stack.unshift(document.createElement('ul'));
    el.appendChild(stack[0]);
  });

  runner.on('suite end', function (suite) {
    if (suite.root) {
      updateStats();
      return;
    }
    stack.shift();
  });

  runner.on('pass', function (test) {
    var url = self.testURL(test);
    var markup = '<li class="test pass %e"><h2>%e<span class="duration">%ems</span> ' +
      '<a href="%s" class="replay">‣</a></h2></li>';
    var el = fragment(markup, test.speed, test.title, test.duration, url);
    self.addCodeToggle(el, test.body);
    appendToStack(el);
    updateStats();
  });

  runner.on('fail', function (test) {
    var el = fragment('<li class="test fail"><h2>%e <a href="%e" class="replay">‣</a></h2></li>',
      test.title, self.testURL(test));
    var stackString; // Note: Includes leading newline
    var message = test.err.toString();

    // <=IE7 stringifies to [Object Error]. Since it can be overloaded, we
    // check for the result of the stringifying.
    if (message === '[object Error]') {
      message = test.err.message;
    }

    if (test.err.stack) {
      var indexOfMessage = test.err.stack.indexOf(test.err.message);
      if (indexOfMessage === -1) {
        stackString = test.err.stack;
      } else {
        stackString = test.err.stack.substr(test.err.message.length + indexOfMessage);
      }
    } else if (test.err.sourceURL && test.err.line !== undefined) {
      // Safari doesn't give you a stack. Let's at least provide a source line.
      stackString = '\n(' + test.err.sourceURL + ':' + test.err.line + ')';
    }

    stackString = stackString || '';

    if (test.err.htmlMessage && stackString) {
      el.appendChild(fragment('<div class="html-error">%s\n<pre class="error">%e</pre></div>',
        test.err.htmlMessage, stackString));
    } else if (test.err.htmlMessage) {
      el.appendChild(fragment('<div class="html-error">%s</div>', test.err.htmlMessage));
    } else {
      el.appendChild(fragment('<pre class="error">%e%e</pre>', message, stackString));
    }

    self.addCodeToggle(el, test.body);
    appendToStack(el);
    updateStats();
  });

  runner.on('pending', function (test) {
    var el = fragment('<li class="test pass pending"><h2>%e</h2></li>', test.title);
    appendToStack(el);
    updateStats();
  });

  function appendToStack (el) {
    // Don't call .appendChild if #mocha-report was already .shift()'ed off the stack.
    if (stack[0]) {
      stack[0].appendChild(el);
    }
  }

  function updateStats () {
    // TODO: add to stats
    var percent = stats.tests / runner.total * 100 | 0;
    if (progress) {
      progress.update(percent).draw(ctx);
    }

    // update stats
    var ms = new Date() - stats.start;
    text(passes, stats.passes);
    text(failures, stats.failures);
    text(duration, (ms / 1000).toFixed(2));
  }
}

/**
 * Makes a URL, preserving querystring ("search") parameters.
 *
 * @param {string} s
 * @return {string} A new URL.
 */
function makeUrl (s) {
  var search = window.location.search;

  // Remove previous grep query parameter if present
  if (search) {
    search = search.replace(/[?&]grep=[^&\s]*/g, '').replace(/^&/, '?');
  }

  return window.location.pathname + (search ? search + '&' : '?') + 'grep=' + encodeURIComponent(escapeRe(s));
}

/**
 * Provide suite URL.
 *
 * @param {Object} [suite]
 */
HTML.prototype.suiteURL = function (suite) {
  return makeUrl(suite.fullTitle());
};

/**
 * Provide test URL.
 *
 * @param {Object} [test]
 */
HTML.prototype.testURL = function (test) {
  return makeUrl(test.fullTitle());
};

/**
 * Adds code toggle functionality for the provided test's list element.
 *
 * @param {HTMLLIElement} el
 * @param {string} contents
 */
HTML.prototype.addCodeToggle = function (el, contents) {
  var h2 = el.getElementsByTagName('h2')[0];

  on(h2, 'click', function () {
    pre.style.display = pre.style.display === 'none' ? 'block' : 'none';
  });

  var pre = fragment('<pre><code>%e</code></pre>', utils.clean(contents));
  el.appendChild(pre);
  pre.style.display = 'none';
};

/**
 * Display error `msg`.
 *
 * @param {string} msg
 */
function error (msg) {
  document.body.appendChild(fragment('<div id="mocha-error">%s</div>', msg));
}

/**
 * Return a DOM fragment from `html`.
 *
 * @param {string} html
 */
function fragment (html) {
  var args = arguments;
  var div = document.createElement('div');
  var i = 1;

  div.innerHTML = html.replace(/%([se])/g, function (_, type) {
    switch (type) {
      case 's': return String(args[i++]);
      case 'e': return escape(args[i++]);
      // no default
    }
  });

  return div.firstChild;
}

/**
 * Check for suites that do not have elements
 * with `classname`, and hide them.
 *
 * @param {text} classname
 */
function hideSuitesWithout (classname) {
  var suites = document.getElementsByClassName('suite');
  for (var i = 0; i < suites.length; i++) {
    var els = suites[i].getElementsByClassName(classname);
    if (!els.length) {
      suites[i].className += ' hidden';
    }
  }
}

/**
 * Unhide .hidden suites.
 */
function unhide () {
  var els = document.getElementsByClassName('suite hidden');
  for (var i = 0; i < els.length; ++i) {
    els[i].className = els[i].className.replace('suite hidden', 'suite');
  }
}

/**
 * Set an element's text contents.
 *
 * @param {HTMLElement} el
 * @param {string} contents
 */
function text (el, contents) {
  if (el.textContent) {
    el.textContent = contents;
  } else {
    el.innerText = contents;
  }
}

/**
 * Listen on `event` with callback `fn`.
 */
function on (el, event, fn) {
  if (el.addEventListener) {
    el.addEventListener(event, fn, false);
  } else {
    el.attachEvent('on' + event, fn);
  }
}

}).call(this,typeof global !== "undefined" ? global : typeof self !== "undefined" ? self : typeof window !== "undefined" ? window : {})
},{"../browser/progress":4,"../utils":38,"./base":17,"escape-string-regexp":47}],21:[function(require,module,exports){
'use strict';

// Alias exports to a their normalized format Mocha#reporter to prevent a need
// for dynamic (try/catch) requires, which Browserify doesn't handle.
exports.Base = exports.base = require('./base');
exports.Dot = exports.dot = require('./dot');
exports.Doc = exports.doc = require('./doc');
exports.TAP = exports.tap = require('./tap');
exports.JSON = exports.json = require('./json');
exports.HTML = exports.html = require('./html');
exports.List = exports.list = require('./list');
exports.Min = exports.min = require('./min');
exports.Spec = exports.spec = require('./spec');
exports.Nyan = exports.nyan = require('./nyan');
exports.XUnit = exports.xunit = require('./xunit');
exports.Markdown = exports.markdown = require('./markdown');
exports.Progress = exports.progress = require('./progress');
exports.Landing = exports.landing = require('./landing');
exports.JSONStream = exports['json-stream'] = require('./json-stream');

},{"./base":17,"./doc":18,"./dot":19,"./html":20,"./json":23,"./json-stream":22,"./landing":24,"./list":25,"./markdown":26,"./min":27,"./nyan":28,"./progress":29,"./spec":30,"./tap":31,"./xunit":32}],22:[function(require,module,exports){
(function (process){
'use strict';

/**
 * Module dependencies.
 */

var Base = require('./base');
var JSON = require('json3');

/**
 * Expose `List`.
 */

exports = module.exports = List;

/**
 * Initialize a new `List` test reporter.
 *
 * @api public
 * @param {Runner} runner
 */
function List (runner) {
  Base.call(this, runner);

  var self = this;
  var total = runner.total;

  runner.on('start', function () {
    console.log(JSON.stringify(['start', { total: total }]));
  });

  runner.on('pass', function (test) {
    console.log(JSON.stringify(['pass', clean(test)]));
  });

  runner.on('fail', function (test, err) {
    test = clean(test);
    test.err = err.message;
    test.stack = err.stack || null;
    console.log(JSON.stringify(['fail', test]));
  });

  runner.on('end', function () {
    process.stdout.write(JSON.stringify(['end', self.stats]));
  });
}

/**
 * Return a plain-object representation of `test`
 * free of cyclic properties etc.
 *
 * @api private
 * @param {Object} test
 * @return {Object}
 */
function clean (test) {
  return {
    title: test.title,
    fullTitle: test.fullTitle(),
    duration: test.duration,
    currentRetry: test.currentRetry()
  };
}

}).call(this,require('_process'))
},{"./base":17,"_process":67,"json3":54}],23:[function(require,module,exports){
(function (process){
'use strict';

/**
 * Module dependencies.
 */

var Base = require('./base');

/**
 * Expose `JSON`.
 */

exports = module.exports = JSONReporter;

/**
 * Initialize a new `JSON` reporter.
 *
 * @api public
 * @param {Runner} runner
 */
function JSONReporter (runner) {
  Base.call(this, runner);

  var self = this;
  var tests = [];
  var pending = [];
  var failures = [];
  var passes = [];

  runner.on('test end', function (test) {
    tests.push(test);
  });

  runner.on('pass', function (test) {
    passes.push(test);
  });

  runner.on('fail', function (test) {
    failures.push(test);
  });

  runner.on('pending', function (test) {
    pending.push(test);
  });

  runner.on('end', function () {
    var obj = {
      stats: self.stats,
      tests: tests.map(clean),
      pending: pending.map(clean),
      failures: failures.map(clean),
      passes: passes.map(clean)
    };

    runner.testResults = obj;

    process.stdout.write(JSON.stringify(obj, null, 2));
  });
}

/**
 * Return a plain-object representation of `test`
 * free of cyclic properties etc.
 *
 * @api private
 * @param {Object} test
 * @return {Object}
 */
function clean (test) {
  return {
    title: test.title,
    fullTitle: test.fullTitle(),
    duration: test.duration,
    currentRetry: test.currentRetry(),
    err: errorJSON(test.err || {})
  };
}

/**
 * Transform `error` into a JSON object.
 *
 * @api private
 * @param {Error} err
 * @return {Object}
 */
function errorJSON (err) {
  var res = {};
  Object.getOwnPropertyNames(err).forEach(function (key) {
    res[key] = err[key];
  }, err);
  return res;
}

}).call(this,require('_process'))
},{"./base":17,"_process":67}],24:[function(require,module,exports){
(function (process){
'use strict';

/**
 * Module dependencies.
 */

var Base = require('./base');
var inherits = require('../utils').inherits;
var cursor = Base.cursor;
var color = Base.color;

/**
 * Expose `Landing`.
 */

exports = module.exports = Landing;

/**
 * Airplane color.
 */

Base.colors.plane = 0;

/**
 * Airplane crash color.
 */

Base.colors['plane crash'] = 31;

/**
 * Runway color.
 */

Base.colors.runway = 90;

/**
 * Initialize a new `Landing` reporter.
 *
 * @api public
 * @param {Runner} runner
 */
function Landing (runner) {
  Base.call(this, runner);

  var self = this;
  var width = Base.window.width * 0.75 | 0;
  var total = runner.total;
  var stream = process.stdout;
  var plane = color('plane', '✈');
  var crashed = -1;
  var n = 0;

  function runway () {
    var buf = Array(width).join('-');
    return '  ' + color('runway', buf);
  }

  runner.on('start', function () {
    stream.write('\n\n\n  ');
    cursor.hide();
  });

  runner.on('test end', function (test) {
    // check if the plane crashed
    var col = crashed === -1 ? width * ++n / total | 0 : crashed;

    // show the crash
    if (test.state === 'failed') {
      plane = color('plane crash', '✈');
      crashed = col;
    }

    // render landing strip
    stream.write('\u001b[' + (width + 1) + 'D\u001b[2A');
    stream.write(runway());
    stream.write('\n  ');
    stream.write(color('runway', Array(col).join('⋅')));
    stream.write(plane);
    stream.write(color('runway', Array(width - col).join('⋅') + '\n'));
    stream.write(runway());
    stream.write('\u001b[0m');
  });

  runner.on('end', function () {
    cursor.show();
    console.log();
    self.epilogue();
  });
}

/**
 * Inherit from `Base.prototype`.
 */
inherits(Landing, Base);

}).call(this,require('_process'))
},{"../utils":38,"./base":17,"_process":67}],25:[function(require,module,exports){
(function (process){
'use strict';

/**
 * Module dependencies.
 */

var Base = require('./base');
var inherits = require('../utils').inherits;
var color = Base.color;
var cursor = Base.cursor;

/**
 * Expose `List`.
 */

exports = module.exports = List;

/**
 * Initialize a new `List` test reporter.
 *
 * @api public
 * @param {Runner} runner
 */
function List (runner) {
  Base.call(this, runner);

  var self = this;
  var n = 0;

  runner.on('start', function () {
    console.log();
  });

  runner.on('test', function (test) {
    process.stdout.write(color('pass', '    ' + test.fullTitle() + ': '));
  });

  runner.on('pending', function (test) {
    var fmt = color('checkmark', '  -') +
      color('pending', ' %s');
    console.log(fmt, test.fullTitle());
  });

  runner.on('pass', function (test) {
    var fmt = color('checkmark', '  ' + Base.symbols.dot) +
      color('pass', ' %s: ') +
      color(test.speed, '%dms');
    cursor.CR();
    console.log(fmt, test.fullTitle(), test.duration);
  });

  runner.on('fail', function (test) {
    cursor.CR();
    console.log(color('fail', '  %d) %s'), ++n, test.fullTitle());
  });

  runner.on('end', self.epilogue.bind(self));
}

/**
 * Inherit from `Base.prototype`.
 */
inherits(List, Base);

}).call(this,require('_process'))
},{"../utils":38,"./base":17,"_process":67}],26:[function(require,module,exports){
(function (process){
'use strict';

/**
 * Module dependencies.
 */

var Base = require('./base');
var utils = require('../utils');

/**
 * Constants
 */

var SUITE_PREFIX = '$';

/**
 * Expose `Markdown`.
 */

exports = module.exports = Markdown;

/**
 * Initialize a new `Markdown` reporter.
 *
 * @api public
 * @param {Runner} runner
 */
function Markdown (runner) {
  Base.call(this, runner);

  var level = 0;
  var buf = '';

  function title (str) {
    return Array(level).join('#') + ' ' + str;
  }

  function mapTOC (suite, obj) {
    var ret = obj;
    var key = SUITE_PREFIX + suite.title;

    obj = obj[key] = obj[key] || { suite: suite };
    suite.suites.forEach(function (suite) {
      mapTOC(suite, obj);
    });

    return ret;
  }

  function stringifyTOC (obj, level) {
    ++level;
    var buf = '';
    var link;
    for (var key in obj) {
      if (key === 'suite') {
        continue;
      }
      if (key !== SUITE_PREFIX) {
        link = ' - [' + key.substring(1) + ']';
        link += '(#' + utils.slug(obj[key].suite.fullTitle()) + ')\n';
        buf += Array(level).join('  ') + link;
      }
      buf += stringifyTOC(obj[key], level);
    }
    return buf;
  }

  function generateTOC (suite) {
    var obj = mapTOC(suite, {});
    return stringifyTOC(obj, 0);
  }

  generateTOC(runner.suite);

  runner.on('suite', function (suite) {
    ++level;
    var slug = utils.slug(suite.fullTitle());
    buf += '<a name="' + slug + '"></a>' + '\n';
    buf += title(suite.title) + '\n';
  });

  runner.on('suite end', function () {
    --level;
  });

  runner.on('pass', function (test) {
    var code = utils.clean(test.body);
    buf += test.title + '.\n';
    buf += '\n```js\n';
    buf += code + '\n';
    buf += '```\n\n';
  });

  runner.on('end', function () {
    process.stdout.write('# TOC\n');
    process.stdout.write(generateTOC(runner.suite));
    process.stdout.write(buf);
  });
}

}).call(this,require('_process'))
},{"../utils":38,"./base":17,"_process":67}],27:[function(require,module,exports){
(function (process){
'use strict';

/**
 * Module dependencies.
 */

var Base = require('./base');
var inherits = require('../utils').inherits;

/**
 * Expose `Min`.
 */

exports = module.exports = Min;

/**
 * Initialize a new `Min` minimal test reporter (best used with --watch).
 *
 * @api public
 * @param {Runner} runner
 */
function Min (runner) {
  Base.call(this, runner);

  runner.on('start', function () {
    // clear screen
    process.stdout.write('\u001b[2J');
    // set cursor position
    process.stdout.write('\u001b[1;3H');
  });

  runner.on('end', this.epilogue.bind(this));
}

/**
 * Inherit from `Base.prototype`.
 */
inherits(Min, Base);

}).call(this,require('_process'))
},{"../utils":38,"./base":17,"_process":67}],28:[function(require,module,exports){
(function (process){
'use strict';

/**
 * Module dependencies.
 */

var Base = require('./base');
var inherits = require('../utils').inherits;

/**
 * Expose `Dot`.
 */

exports = module.exports = NyanCat;

/**
 * Initialize a new `Dot` matrix test reporter.
 *
 * @param {Runner} runner
 * @api public
 */

function NyanCat (runner) {
  Base.call(this, runner);

  var self = this;
  var width = Base.window.width * 0.75 | 0;
  var nyanCatWidth = this.nyanCatWidth = 11;

  this.colorIndex = 0;
  this.numberOfLines = 4;
  this.rainbowColors = self.generateColors();
  this.scoreboardWidth = 5;
  this.tick = 0;
  this.trajectories = [[], [], [], []];
  this.trajectoryWidthMax = (width - nyanCatWidth);

  runner.on('start', function () {
    Base.cursor.hide();
    self.draw();
  });

  runner.on('pending', function () {
    self.draw();
  });

  runner.on('pass', function () {
    self.draw();
  });

  runner.on('fail', function () {
    self.draw();
  });

  runner.on('end', function () {
    Base.cursor.show();
    for (var i = 0; i < self.numberOfLines; i++) {
      write('\n');
    }
    self.epilogue();
  });
}

/**
 * Inherit from `Base.prototype`.
 */
inherits(NyanCat, Base);

/**
 * Draw the nyan cat
 *
 * @api private
 */

NyanCat.prototype.draw = function () {
  this.appendRainbow();
  this.drawScoreboard();
  this.drawRainbow();
  this.drawNyanCat();
  this.tick = !this.tick;
};

/**
 * Draw the "scoreboard" showing the number
 * of passes, failures and pending tests.
 *
 * @api private
 */

NyanCat.prototype.drawScoreboard = function () {
  var stats = this.stats;

  function draw (type, n) {
    write(' ');
    write(Base.color(type, n));
    write('\n');
  }

  draw('green', stats.passes);
  draw('fail', stats.failures);
  draw('pending', stats.pending);
  write('\n');

  this.cursorUp(this.numberOfLines);
};

/**
 * Append the rainbow.
 *
 * @api private
 */

NyanCat.prototype.appendRainbow = function () {
  var segment = this.tick ? '_' : '-';
  var rainbowified = this.rainbowify(segment);

  for (var index = 0; index < this.numberOfLines; index++) {
    var trajectory = this.trajectories[index];
    if (trajectory.length >= this.trajectoryWidthMax) {
      trajectory.shift();
    }
    trajectory.push(rainbowified);
  }
};

/**
 * Draw the rainbow.
 *
 * @api private
 */

NyanCat.prototype.drawRainbow = function () {
  var self = this;

  this.trajectories.forEach(function (line) {
    write('\u001b[' + self.scoreboardWidth + 'C');
    write(line.join(''));
    write('\n');
  });

  this.cursorUp(this.numberOfLines);
};

/**
 * Draw the nyan cat
 *
 * @api private
 */
NyanCat.prototype.drawNyanCat = function () {
  var self = this;
  var startWidth = this.scoreboardWidth + this.trajectories[0].length;
  var dist = '\u001b[' + startWidth + 'C';
  var padding = '';

  write(dist);
  write('_,------,');
  write('\n');

  write(dist);
  padding = self.tick ? '  ' : '   ';
  write('_|' + padding + '/\\_/\\ ');
  write('\n');

  write(dist);
  padding = self.tick ? '_' : '__';
  var tail = self.tick ? '~' : '^';
  write(tail + '|' + padding + this.face() + ' ');
  write('\n');

  write(dist);
  padding = self.tick ? ' ' : '  ';
  write(padding + '""  "" ');
  write('\n');

  this.cursorUp(this.numberOfLines);
};

/**
 * Draw nyan cat face.
 *
 * @api private
 * @return {string}
 */

NyanCat.prototype.face = function () {
  var stats = this.stats;
  if (stats.failures) {
    return '( x .x)';
  } else if (stats.pending) {
    return '( o .o)';
  } else if (stats.passes) {
    return '( ^ .^)';
  }
  return '( - .-)';
};

/**
 * Move cursor up `n`.
 *
 * @api private
 * @param {number} n
 */

NyanCat.prototype.cursorUp = function (n) {
  write('\u001b[' + n + 'A');
};

/**
 * Move cursor down `n`.
 *
 * @api private
 * @param {number} n
 */

NyanCat.prototype.cursorDown = function (n) {
  write('\u001b[' + n + 'B');
};

/**
 * Generate rainbow colors.
 *
 * @api private
 * @return {Array}
 */
NyanCat.prototype.generateColors = function () {
  var colors = [];

  for (var i = 0; i < (6 * 7); i++) {
    var pi3 = Math.floor(Math.PI / 3);
    var n = (i * (1.0 / 6));
    var r = Math.floor(3 * Math.sin(n) + 3);
    var g = Math.floor(3 * Math.sin(n + 2 * pi3) + 3);
    var b = Math.floor(3 * Math.sin(n + 4 * pi3) + 3);
    colors.push(36 * r + 6 * g + b + 16);
  }

  return colors;
};

/**
 * Apply rainbow to the given `str`.
 *
 * @api private
 * @param {string} str
 * @return {string}
 */
NyanCat.prototype.rainbowify = function (str) {
  if (!Base.useColors) {
    return str;
  }
  var color = this.rainbowColors[this.colorIndex % this.rainbowColors.length];
  this.colorIndex += 1;
  return '\u001b[38;5;' + color + 'm' + str + '\u001b[0m';
};

/**
 * Stdout helper.
 *
 * @param {string} string A message to write to stdout.
 */
function write (string) {
  process.stdout.write(string);
}

}).call(this,require('_process'))
},{"../utils":38,"./base":17,"_process":67}],29:[function(require,module,exports){
(function (process){
'use strict';

/**
 * Module dependencies.
 */

var Base = require('./base');
var inherits = require('../utils').inherits;
var color = Base.color;
var cursor = Base.cursor;

/**
 * Expose `Progress`.
 */

exports = module.exports = Progress;

/**
 * General progress bar color.
 */

Base.colors.progress = 90;

/**
 * Initialize a new `Progress` bar test reporter.
 *
 * @api public
 * @param {Runner} runner
 * @param {Object} options
 */
function Progress (runner, options) {
  Base.call(this, runner);

  var self = this;
  var width = Base.window.width * 0.50 | 0;
  var total = runner.total;
  var complete = 0;
  var lastN = -1;

  // default chars
  options = options || {};
  options.open = options.open || '[';
  options.complete = options.complete || '▬';
  options.incomplete = options.incomplete || Base.symbols.dot;
  options.close = options.close || ']';
  options.verbose = false;

  // tests started
  runner.on('start', function () {
    console.log();
    cursor.hide();
  });

  // tests complete
  runner.on('test end', function () {
    complete++;

    var percent = complete / total;
    var n = width * percent | 0;
    var i = width - n;

    if (n === lastN && !options.verbose) {
      // Don't re-render the line if it hasn't changed
      return;
    }
    lastN = n;

    cursor.CR();
    process.stdout.write('\u001b[J');
    process.stdout.write(color('progress', '  ' + options.open));
    process.stdout.write(Array(n).join(options.complete));
    process.stdout.write(Array(i).join(options.incomplete));
    process.stdout.write(color('progress', options.close));
    if (options.verbose) {
      process.stdout.write(color('progress', ' ' + complete + ' of ' + total));
    }
  });

  // tests are complete, output some stats
  // and the failures if any
  runner.on('end', function () {
    cursor.show();
    console.log();
    self.epilogue();
  });
}

/**
 * Inherit from `Base.prototype`.
 */
inherits(Progress, Base);

}).call(this,require('_process'))
},{"../utils":38,"./base":17,"_process":67}],30:[function(require,module,exports){
'use strict';

/**
 * Module dependencies.
 */

var Base = require('./base');
var inherits = require('../utils').inherits;
var color = Base.color;

/**
 * Expose `Spec`.
 */

exports = module.exports = Spec;

/**
 * Initialize a new `Spec` test reporter.
 *
 * @api public
 * @param {Runner} runner
 */
function Spec (runner) {
  Base.call(this, runner);

  var self = this;
  var indents = 0;
  var n = 0;

  function indent () {
    return Array(indents).join('  ');
  }

  runner.on('start', function () {
    console.log();
  });

  runner.on('suite', function (suite) {
    ++indents;
    console.log(color('suite', '%s%s'), indent(), suite.title);
  });

  runner.on('suite end', function () {
    --indents;
    if (indents === 1) {
      console.log();
    }
  });

  runner.on('pending', function (test) {
    var fmt = indent() + color('pending', '  - %s');
    console.log(fmt, test.title);
  });

  runner.on('pass', function (test) {
    var fmt;
    if (test.speed === 'fast') {
      fmt = indent() +
        color('checkmark', '  ' + Base.symbols.ok) +
        color('pass', ' %s');
      console.log(fmt, test.title);
    } else {
      fmt = indent() +
        color('checkmark', '  ' + Base.symbols.ok) +
        color('pass', ' %s') +
        color(test.speed, ' (%dms)');
      console.log(fmt, test.title, test.duration);
    }
  });

  runner.on('fail', function (test) {
    console.log(indent() + color('fail', '  %d) %s'), ++n, test.title);
  });

  runner.on('end', self.epilogue.bind(self));
}

/**
 * Inherit from `Base.prototype`.
 */
inherits(Spec, Base);

},{"../utils":38,"./base":17}],31:[function(require,module,exports){
'use strict';

/**
 * Module dependencies.
 */

var Base = require('./base');

/**
 * Expose `TAP`.
 */

exports = module.exports = TAP;

/**
 * Initialize a new `TAP` reporter.
 *
 * @api public
 * @param {Runner} runner
 */
function TAP (runner) {
  Base.call(this, runner);

  var n = 1;
  var passes = 0;
  var failures = 0;

  runner.on('start', function () {
    var total = runner.grepTotal(runner.suite);
    console.log('%d..%d', 1, total);
  });

  runner.on('test end', function () {
    ++n;
  });

  runner.on('pending', function (test) {
    console.log('ok %d %s # SKIP -', n, title(test));
  });

  runner.on('pass', function (test) {
    passes++;
    console.log('ok %d %s', n, title(test));
  });

  runner.on('fail', function (test, err) {
    failures++;
    console.log('not ok %d %s', n, title(test));
    if (err.stack) {
      console.log(err.stack.replace(/^/gm, '  '));
    }
  });

  runner.on('end', function () {
    console.log('# tests ' + (passes + failures));
    console.log('# pass ' + passes);
    console.log('# fail ' + failures);
  });
}

/**
 * Return a TAP-safe title of `test`
 *
 * @api private
 * @param {Object} test
 * @return {String}
 */
function title (test) {
  return test.fullTitle().replace(/#/g, '');
}

},{"./base":17}],32:[function(require,module,exports){
(function (process,global){
'use strict';

/**
 * Module dependencies.
 */

var Base = require('./base');
var utils = require('../utils');
var inherits = utils.inherits;
var fs = require('fs');
var escape = utils.escape;
var mkdirp = require('mkdirp');
var path = require('path');

/**
 * Save timer references to avoid Sinon interfering (see GH-237).
 */

/* eslint-disable no-unused-vars, no-native-reassign */
var Date = global.Date;
var setTimeout = global.setTimeout;
var setInterval = global.setInterval;
var clearTimeout = global.clearTimeout;
var clearInterval = global.clearInterval;
/* eslint-enable no-unused-vars, no-native-reassign */

/**
 * Expose `XUnit`.
 */

exports = module.exports = XUnit;

/**
 * Initialize a new `XUnit` reporter.
 *
 * @api public
 * @param {Runner} runner
 */
function XUnit (runner, options) {
  Base.call(this, runner);

  var stats = this.stats;
  var tests = [];
  var self = this;

  if (options.reporterOptions && options.reporterOptions.output) {
    if (!fs.createWriteStream) {
      throw new Error('file output not supported in browser');
    }
    mkdirp.sync(path.dirname(options.reporterOptions.output));
    self.fileStream = fs.createWriteStream(options.reporterOptions.output);
  }

  runner.on('pending', function (test) {
    tests.push(test);
  });

  runner.on('pass', function (test) {
    tests.push(test);
  });

  runner.on('fail', function (test) {
    tests.push(test);
  });

  runner.on('end', function () {
    self.write(tag('testsuite', {
      name: 'Mocha Tests',
      tests: stats.tests,
      failures: stats.failures,
      errors: stats.failures,
      skipped: stats.tests - stats.failures - stats.passes,
      timestamp: (new Date()).toUTCString(),
      time: (stats.duration / 1000) || 0
    }, false));

    tests.forEach(function (t) {
      self.test(t);
    });

    self.write('</testsuite>');
  });
}

/**
 * Inherit from `Base.prototype`.
 */
inherits(XUnit, Base);

/**
 * Override done to close the stream (if it's a file).
 *
 * @param failures
 * @param {Function} fn
 */
XUnit.prototype.done = function (failures, fn) {
  if (this.fileStream) {
    this.fileStream.end(function () {
      fn(failures);
    });
  } else {
    fn(failures);
  }
};

/**
 * Write out the given line.
 *
 * @param {string} line
 */
XUnit.prototype.write = function (line) {
  if (this.fileStream) {
    this.fileStream.write(line + '\n');
  } else if (typeof process === 'object' && process.stdout) {
    process.stdout.write(line + '\n');
  } else {
    console.log(line);
  }
};

/**
 * Output tag for the given `test.`
 *
 * @param {Test} test
 */
XUnit.prototype.test = function (test) {
  var attrs = {
    classname: test.parent.fullTitle(),
    name: test.title,
    time: (test.duration / 1000) || 0
  };

  if (test.state === 'failed') {
    var err = test.err;
    this.write(tag('testcase', attrs, false, tag('failure', {}, false, escape(err.message) + '\n' + escape(err.stack))));
  } else if (test.isPending()) {
    this.write(tag('testcase', attrs, false, tag('skipped', {}, true)));
  } else {
    this.write(tag('testcase', attrs, true));
  }
};

/**
 * HTML tag helper.
 *
 * @param name
 * @param attrs
 * @param close
 * @param content
 * @return {string}
 */
function tag (name, attrs, close, content) {
  var end = close ? '/>' : '>';
  var pairs = [];
  var tag;

  for (var key in attrs) {
    if (Object.prototype.hasOwnProperty.call(attrs, key)) {
      pairs.push(key + '="' + escape(attrs[key]) + '"');
    }
  }

  tag = '<' + name + (pairs.length ? ' ' + pairs.join(' ') : '') + end;
  if (content) {
    tag += content + '</' + name + end;
  }
  return tag;
}

}).call(this,require('_process'),typeof global !== "undefined" ? global : typeof self !== "undefined" ? self : typeof window !== "undefined" ? window : {})
},{"../utils":38,"./base":17,"_process":67,"fs":42,"mkdirp":64,"path":42}],33:[function(require,module,exports){
(function (global){
'use strict';

/**
 * Module dependencies.
 */

var EventEmitter = require('events').EventEmitter;
var JSON = require('json3');
var Pending = require('./pending');
var debug = require('debug')('mocha:runnable');
var milliseconds = require('./ms');
var utils = require('./utils');
var create = require('lodash.create');

/**
 * Save timer references to avoid Sinon interfering (see GH-237).
 */

/* eslint-disable no-unused-vars, no-native-reassign */
var Date = global.Date;
var setTimeout = global.setTimeout;
var setInterval = global.setInterval;
var clearTimeout = global.clearTimeout;
var clearInterval = global.clearInterval;
/* eslint-enable no-unused-vars, no-native-reassign */

/**
 * Object#toString().
 */

var toString = Object.prototype.toString;

/**
 * Expose `Runnable`.
 */

module.exports = Runnable;

/**
 * Initialize a new `Runnable` with the given `title` and callback `fn`.
 *
 * @param {String} title
 * @param {Function} fn
 * @api private
 * @param {string} title
 * @param {Function} fn
 */
function Runnable (title, fn) {
  this.title = title;
  this.fn = fn;
  this.body = (fn || '').toString();
  this.async = fn && fn.length;
  this.sync = !this.async;
  this._timeout = 2000;
  this._slow = 75;
  this._enableTimeouts = true;
  this.timedOut = false;
  this._trace = new Error('done() called multiple times');
  this._retries = -1;
  this._currentRetry = 0;
  this.pending = false;
}

/**
 * Inherit from `EventEmitter.prototype`.
 */
Runnable.prototype = create(EventEmitter.prototype, {
  constructor: Runnable
});

/**
 * Set & get timeout `ms`.
 *
 * @api private
 * @param {number|string} ms
 * @return {Runnable|number} ms or Runnable instance.
 */
Runnable.prototype.timeout = function (ms) {
  if (!arguments.length) {
    return this._timeout;
  }
  // see #1652 for reasoning
  if (ms === 0 || ms > Math.pow(2, 31)) {
    this._enableTimeouts = false;
  }
  if (typeof ms === 'string') {
    ms = milliseconds(ms);
  }
  debug('timeout %d', ms);
  this._timeout = ms;
  if (this.timer) {
    this.resetTimeout();
  }
  return this;
};

/**
 * Set & get slow `ms`.
 *
 * @api private
 * @param {number|string} ms
 * @return {Runnable|number} ms or Runnable instance.
 */
Runnable.prototype.slow = function (ms) {
  if (typeof ms === 'undefined') {
    return this._slow;
  }
  if (typeof ms === 'string') {
    ms = milliseconds(ms);
  }
  debug('timeout %d', ms);
  this._slow = ms;
  return this;
};

/**
 * Set and get whether timeout is `enabled`.
 *
 * @api private
 * @param {boolean} enabled
 * @return {Runnable|boolean} enabled or Runnable instance.
 */
Runnable.prototype.enableTimeouts = function (enabled) {
  if (!arguments.length) {
    return this._enableTimeouts;
  }
  debug('enableTimeouts %s', enabled);
  this._enableTimeouts = enabled;
  return this;
};

/**
 * Halt and mark as pending.
 *
 * @api public
 */
Runnable.prototype.skip = function () {
  throw new Pending('sync skip');
};

/**
 * Check if this runnable or its parent suite is marked as pending.
 *
 * @api private
 */
Runnable.prototype.isPending = function () {
  return this.pending || (this.parent && this.parent.isPending());
};

/**
 * Set number of retries.
 *
 * @api private
 */
Runnable.prototype.retries = function (n) {
  if (!arguments.length) {
    return this._retries;
  }
  this._retries = n;
};

/**
 * Get current retry
 *
 * @api private
 */
Runnable.prototype.currentRetry = function (n) {
  if (!arguments.length) {
    return this._currentRetry;
  }
  this._currentRetry = n;
};

/**
 * Return the full title generated by recursively concatenating the parent's
 * full title.
 *
 * @api public
 * @return {string}
 */
Runnable.prototype.fullTitle = function () {
  return this.parent.fullTitle() + ' ' + this.title;
};

/**
 * Clear the timeout.
 *
 * @api private
 */
Runnable.prototype.clearTimeout = function () {
  clearTimeout(this.timer);
};

/**
 * Inspect the runnable void of private properties.
 *
 * @api private
 * @return {string}
 */
Runnable.prototype.inspect = function () {
  return JSON.stringify(this, function (key, val) {
    if (key[0] === '_') {
      return;
    }
    if (key === 'parent') {
      return '#<Suite>';
    }
    if (key === 'ctx') {
      return '#<Context>';
    }
    return val;
  }, 2);
};

/**
 * Reset the timeout.
 *
 * @api private
 */
Runnable.prototype.resetTimeout = function () {
  var self = this;
  var ms = this.timeout() || 1e9;

  if (!this._enableTimeouts) {
    return;
  }
  this.clearTimeout();
  this.timer = setTimeout(function () {
    if (!self._enableTimeouts) {
      return;
    }
    self.callback(new Error('timeout of ' + ms + 'ms exceeded. Ensure the done() callback is being called in this test.'));
    self.timedOut = true;
  }, ms);
};

/**
 * Whitelist a list of globals for this test run.
 *
 * @api private
 * @param {string[]} globals
 */
Runnable.prototype.globals = function (globals) {
  if (!arguments.length) {
    return this._allowedGlobals;
  }
  this._allowedGlobals = globals;
};

/**
 * Run the test and invoke `fn(err)`.
 *
 * @param {Function} fn
 * @api private
 */
Runnable.prototype.run = function (fn) {
  var self = this;
  var start = new Date();
  var ctx = this.ctx;
  var finished;
  var emitted;

  // Sometimes the ctx exists, but it is not runnable
  if (ctx && ctx.runnable) {
    ctx.runnable(this);
  }

  // called multiple times
  function multiple (err) {
    if (emitted) {
      return;
    }
    emitted = true;
    self.emit('error', err || new Error('done() called multiple times; stacktrace may be inaccurate'));
  }

  // finished
  function done (err) {
    var ms = self.timeout();
    if (self.timedOut) {
      return;
    }
    if (finished) {
      return multiple(err || self._trace);
    }

    self.clearTimeout();
    self.duration = new Date() - start;
    finished = true;
    if (!err && self.duration > ms && self._enableTimeouts) {
      err = new Error('timeout of ' + ms + 'ms exceeded. Ensure the done() callback is being called in this test.');
    }
    fn(err);
  }

  // for .resetTimeout()
  this.callback = done;

  // explicit async with `done` argument
  if (this.async) {
    this.resetTimeout();

    // allows skip() to be used in an explicit async context
    this.skip = function asyncSkip () {
      done(new Pending('async skip call'));
      // halt execution.  the Runnable will be marked pending
      // by the previous call, and the uncaught handler will ignore
      // the failure.
      throw new Pending('async skip; aborting execution');
    };

    if (this.allowUncaught) {
      return callFnAsync(this.fn);
    }
    try {
      callFnAsync(this.fn);
    } catch (err) {
      emitted = true;
      done(utils.getError(err));
    }
    return;
  }

  if (this.allowUncaught) {
    callFn(this.fn);
    done();
    return;
  }

  // sync or promise-returning
  try {
    if (this.isPending()) {
      done();
    } else {
      callFn(this.fn);
    }
  } catch (err) {
    emitted = true;
    done(utils.getError(err));
  }

  function callFn (fn) {
    var result = fn.call(ctx);
    if (result && typeof result.then === 'function') {
      self.resetTimeout();
      result
        .then(function () {
          done();
          // Return null so libraries like bluebird do not warn about
          // subsequently constructed Promises.
          return null;
        },
        function (reason) {
          done(reason || new Error('Promise rejected with no or falsy reason'));
        });
    } else {
      if (self.asyncOnly) {
        return done(new Error('--async-only option in use without declaring `done()` or returning a promise'));
      }

      done();
    }
  }

  function callFnAsync (fn) {
    var result = fn.call(ctx, function (err) {
      if (err instanceof Error || toString.call(err) === '[object Error]') {
        return done(err);
      }
      if (err) {
        if (Object.prototype.toString.call(err) === '[object Object]') {
          return done(new Error('done() invoked with non-Error: ' +
            JSON.stringify(err)));
        }
        return done(new Erro
Download .txt
gitextract_z5ynrdnk/

├── .babelrc
├── .gitignore
├── CHANGELOG.md
├── LICENSE
├── README.md
├── build/
│   ├── build.js
│   ├── config.js
│   ├── karma.config.js
│   └── release.sh
├── docs/
│   ├── README.md
│   ├── index.html
│   └── zh-cn.md
├── lib/
│   └── bowl.js
├── package.json
├── src/
│   ├── bowl.class.js
│   ├── graph.class.js
│   ├── index.js
│   ├── injector/
│   │   ├── cacheUnit.class.js
│   │   └── injector.class.js
│   └── utils.js
└── test/
    ├── e2e/
    │   ├── assets/
    │   │   ├── a.js
    │   │   ├── b.js
    │   │   ├── c.js
    │   │   └── style.css
    │   ├── test.html
    │   ├── test.js
    │   └── vendor/
    │       ├── expect.js
    │       ├── mocha.css
    │       └── mocha.js
    └── unit/
        ├── index.js
        └── specs/
            ├── graph.spec.js
            └── utils.spec.js
Download .txt
SYMBOL INDEX (393 symbols across 9 files)

FILE: build/build.js
  function getSize (line 8) | function getSize(str) {
  function blue (line 12) | function blue(str) {
  function write (line 16) | function write(dest, code) {
  function build (line 26) | function build() {

FILE: lib/bowl.js
  function isObject (line 17) | function isObject(obj) {
  function isString (line 24) | function isString(str) {
  function isArray (line 31) | function isArray(arr) {
  function isUrl (line 38) | function isUrl(path) {
  function clone (line 45) | function clone(obj) {
  function merge (line 75) | function merge(target, source, force) {
  function isCrossOrigin (line 95) | function isCrossOrigin(hostUrl, targetUrl) {
  function get$1 (line 127) | function get$1(key) {
  function set (line 141) | function set(key, o) {
  function remove$1 (line 151) | function remove$1(key) {

FILE: src/bowl.class.js
  class Bowl (line 9) | class Bowl {
    method constructor (line 10) | constructor() {
    method configure (line 30) | configure(opts) {
    method add (line 37) | add(opts) {
    method inject (line 105) | inject() {
    method remove (line 136) | remove(rule) {

FILE: src/graph.class.js
  class Graph (line 7) | class Graph {
    method constructor (line 8) | constructor(vertices) {
    method addVertex (line 12) | addVertex(v) {
    method addEdge (line 25) | addEdge(begin, end) {
    method hasCycle (line 37) | hasCycle() {
    method getBFS (line 60) | getBFS() {

FILE: src/injector/cacheUnit.class.js
  class CacheUnit (line 1) | class CacheUnit {
    method constructor (line 2) | constructor(ingredient) {

FILE: src/injector/injector.class.js
  class Injector (line 4) | class Injector {
    method constructor (line 5) | constructor(config) {
    method inject (line 9) | inject(o) {
    method appendToPage (line 44) | appendToPage(ext, content) {
    method normalInject (line 60) | normalInject(o) {
    method fetchByXHR (line 97) | fetchByXHR(url) {

FILE: src/utils.js
  function isObject (line 6) | function isObject(obj) {
  function isString (line 13) | function isString(str) {
  function isArray (line 20) | function isArray(arr) {
  function isUrl (line 27) | function isUrl(path) {
  function clone (line 34) | function clone(obj) {
  function merge (line 64) | function merge(target, source, force = false) {
  function isCrossOrigin (line 82) | function isCrossOrigin(hostUrl, targetUrl) {
  function get (line 115) | function get(key) {
  function set (line 129) | function set(key, o) {
  function remove (line 139) | function remove(key) {

FILE: test/e2e/vendor/expect.js
  function expect (line 30) | function expect (obj) {
  function Assertion (line 40) | function Assertion (obj, flag, parent) {
  function bind (line 498) | function bind (fn, scope) {
  function every (line 511) | function every (arr, fn, thisObj) {
  function indexOf (line 528) | function indexOf (arr, o, i) {
  function i (line 579) | function i (obj, showHidden, depth) {
  function isArray (line 771) | function isArray (ar) {
  function isRegExp (line 775) | function isRegExp(re) {
  function isDate (line 793) | function isDate(d) {
  function keys (line 797) | function keys (obj) {
  function map (line 813) | function map (arr, mapper, that) {
  function reduce (line 827) | function reduce (arr, fun) {
  function isUndefinedOrNull (line 913) | function isUndefinedOrNull (value) {
  function isArguments (line 917) | function isArguments (object) {
  function regExpEquiv (line 921) | function regExpEquiv (a, b) {
  function objEquiv (line 926) | function objEquiv (a, b) {
  function f (line 981) | function f(n) {
  function date (line 986) | function date(d, key) {
  function quote (line 1012) | function quote(string) {
  function str (line 1028) | function str(key, holder) {
  function walk (line 1203) | function walk(holder, key) {

FILE: test/e2e/vendor/mocha.js
  function s (line 1) | function s(o,u){if(!n[o]){if(!t[o]){var a=typeof require=="function"&&re...
  function timeslice (line 79) | function timeslice () {
  function noop (line 195) | function noop () {}
  function isArray (line 222) | function isArray (val) {
  function EventEmitter (line 231) | function EventEmitter () {}
  function on (line 270) | function on () {
  function Progress (line 410) | function Progress () {
  function Context (line 556) | function Context () {}
  function Hook (line 673) | function Hook (title, fn) {
  function visit (line 1009) | function visit (obj, file) {
  function image (line 1314) | function image (name) {
  function Mocha (line 1337) | function Mocha (options) {
  function done (line 1773) | function done (failures) {
  function parse (line 1826) | function parse (str) {
  function shortFormat (line 1868) | function shortFormat (ms) {
  function longFormat (line 1891) | function longFormat (ms) {
  function plural (line 1907) | function plural (ms, n, name) {
  function Pending (line 1931) | function Pending (message) {
  function Base (line 2177) | function Base (runner) {
  function pad (line 2283) | function pad (str, len) {
  function inlineDiff (line 2296) | function inlineDiff (err, escape) {
  function unifiedDiff (line 2330) | function unifiedDiff (err, escape) {
  function errorDiff (line 2371) | function errorDiff (err, type, escape) {
  function escapeInvisibles (line 2392) | function escapeInvisibles (line) {
  function colorLines (line 2406) | function colorLines (name, str) {
  function sameType (line 2425) | function sameType (a, b) {
  function Doc (line 2452) | function Doc (runner) {
  function Dot (line 2520) | function Dot (runner) {
  function HTML (line 2619) | function HTML (runner) {
  function makeUrl (line 2789) | function makeUrl (s) {
  function error (line 2841) | function error (msg) {
  function fragment (line 2850) | function fragment (html) {
  function hideSuitesWithout (line 2872) | function hideSuitesWithout (classname) {
  function unhide (line 2885) | function unhide () {
  function text (line 2898) | function text (el, contents) {
  function on (line 2909) | function on (el, event, fn) {
  function List (line 2962) | function List (runner) {
  function clean (line 2996) | function clean (test) {
  function JSONReporter (line 3028) | function JSONReporter (runner) {
  function clean (line 3076) | function clean (test) {
  function errorJSON (line 3093) | function errorJSON (err) {
  function Landing (line 3145) | function Landing (runner) {
  function List (line 3225) | function List (runner) {
  function Markdown (line 3296) | function Markdown (runner) {
  function Min (line 3393) | function Min (runner) {
  function NyanCat (line 3436) | function NyanCat (runner) {
  function draw (line 3506) | function draw (type, n) {
  function write (line 3674) | function write (string) {
  function Progress (line 3711) | function Progress (runner, options) {
  function Spec (line 3797) | function Spec (runner) {
  function TAP (line 3878) | function TAP (runner) {
  function title (line 3925) | function title (test) {
  function XUnit (line 3969) | function XUnit (runner, options) {
  function tag (line 4082) | function tag (name, attrs, close, content) {
  function Runnable (line 4150) | function Runnable (title, fn) {
  function multiple (line 4371) | function multiple (err) {
  function done (line 4380) | function done (err) {
  function callFn (line 4444) | function callFn (fn) {
  function callFnAsync (line 4467) | function callFnAsync (fn) {
  function Runner (line 4556) | function Runner (suite, delay) {
  function next (line 4782) | function next (i) {
  function next (line 4846) | function next (suite) {
  function hookErr (line 4952) | function hookErr (_, errSuite, after) {
  function next (line 4978) | function next (err, errSuite) {
  function next (line 5101) | function next (errSuite) {
  function done (line 5135) | function done (errSuite) {
  function cleanSuiteReferences (line 5245) | function cleanSuiteReferences (suite) {
  function uncaught (line 5294) | function uncaught (err) {
  function start (line 5298) | function start () {
  function filterOnly (line 5354) | function filterOnly (suite) {
  function hasOnly (line 5385) | function hasOnly (suite) {
  function filterLeaks (line 5397) | function filterLeaks (ok, globals) {
  function extraGlobals (line 5438) | function extraGlobals () {
  function Suite (line 5501) | function Suite (title, parentContext) {
  function Test (line 5884) | function Test (title, fn) {
  function pad (line 5923) | function pad(number) {
  function toISOString (line 5936) | function toISOString(date) {
  function ignored (line 6186) | function ignored (path) {
  function highlight (line 6290) | function highlight (js) {
  function emptyRepresentation (line 6329) | function emptyRepresentation (value, typeHint) {
  function jsonStringify (line 6433) | function jsonStringify (object, spaces, depth) {
  function withStack (line 6542) | function withStack (value, fn) {
  function isMochaInternal (line 6701) | function isMochaInternal (line) {
  function isNodeInternal (line 6708) | function isNodeInternal (line) {
  function placeHoldersCount (line 6779) | function placeHoldersCount (b64) {
  function byteLength (line 6793) | function byteLength (b64) {
  function toByteArray (line 6798) | function toByteArray (b64) {
  function tripletToBase64 (line 6829) | function tripletToBase64 (num) {
  function encodeChunk (line 6833) | function encodeChunk (uint8, start, end) {
  function fromByteArray (line 6843) | function fromByteArray (uint8) {
  function BrowserStdout (line 6887) | function BrowserStdout(opts) {
  function typedArraySupport (line 7073) | function typedArraySupport () {
  function kMaxLength (line 7085) | function kMaxLength () {
  function createBuffer (line 7091) | function createBuffer (that, length) {
  function Buffer (line 7120) | function Buffer (arg, encodingOrOffset, length) {
  function from (line 7145) | function from (that, value, encodingOrOffset, length) {
  function assertSize (line 7186) | function assertSize (size) {
  function alloc (line 7194) | function alloc (that, size, fill, encoding) {
  function allocUnsafe (line 7218) | function allocUnsafe (that, size) {
  function fromString (line 7242) | function fromString (that, string, encoding) {
  function fromArrayLike (line 7266) | function fromArrayLike (that, array) {
  function fromArrayBuffer (line 7275) | function fromArrayBuffer (that, array, byteOffset, length) {
  function fromObject (line 7305) | function fromObject (that, obj) {
  function checked (line 7335) | function checked (length) {
  function SlowBuffer (line 7345) | function SlowBuffer (length) {
  function byteLength (line 7428) | function byteLength (string, encoding) {
  function slowToString (line 7473) | function slowToString (encoding, start, end) {
  function swap (line 7547) | function swap (b, n, m) {
  function bidirectionalIndexOf (line 7681) | function bidirectionalIndexOf (buffer, val, byteOffset, encoding, dir) {
  function arrayIndexOf (line 7738) | function arrayIndexOf (arr, val, byteOffset, encoding, dir) {
  function hexWrite (line 7806) | function hexWrite (buf, string, offset, length) {
  function utf8Write (line 7833) | function utf8Write (buf, string, offset, length) {
  function asciiWrite (line 7837) | function asciiWrite (buf, string, offset, length) {
  function latin1Write (line 7841) | function latin1Write (buf, string, offset, length) {
  function base64Write (line 7845) | function base64Write (buf, string, offset, length) {
  function ucs2Write (line 7849) | function ucs2Write (buf, string, offset, length) {
  function base64Slice (line 7932) | function base64Slice (buf, start, end) {
  function utf8Slice (line 7940) | function utf8Slice (buf, start, end) {
  function decodeCodePointsArray (line 8018) | function decodeCodePointsArray (codePoints) {
  function asciiSlice (line 8036) | function asciiSlice (buf, start, end) {
  function latin1Slice (line 8046) | function latin1Slice (buf, start, end) {
  function hexSlice (line 8056) | function hexSlice (buf, start, end) {
  function utf16leSlice (line 8069) | function utf16leSlice (buf, start, end) {
  function checkOffset (line 8117) | function checkOffset (offset, ext, length) {
  function checkInt (line 8278) | function checkInt (buf, value, offset, ext, max, min) {
  function objectWriteUInt16 (line 8331) | function objectWriteUInt16 (buf, value, offset, littleEndian) {
  function objectWriteUInt32 (line 8365) | function objectWriteUInt32 (buf, value, offset, littleEndian) {
  function checkIEEE754 (line 8515) | function checkIEEE754 (buf, value, offset, ext, max, min) {
  function writeFloat (line 8520) | function writeFloat (buf, value, offset, littleEndian, noAssert) {
  function writeDouble (line 8536) | function writeDouble (buf, value, offset, littleEndian, noAssert) {
  function base64clean (line 8669) | function base64clean (str) {
  function stringtrim (line 8681) | function stringtrim (str) {
  function toHex (line 8686) | function toHex (n) {
  function utf8ToBytes (line 8691) | function utf8ToBytes (string, units) {
  function asciiToBytes (line 8771) | function asciiToBytes (str) {
  function utf16leToBytes (line 8780) | function utf16leToBytes (str, units) {
  function base64ToBytes (line 8796) | function base64ToBytes (str) {
  function blitBuffer (line 8800) | function blitBuffer (src, dst, offset, length) {
  function isnan (line 8808) | function isnan (val) {
  function isArray (line 8839) | function isArray(arg) {
  function isBoolean (line 8847) | function isBoolean(arg) {
  function isNull (line 8852) | function isNull(arg) {
  function isNullOrUndefined (line 8857) | function isNullOrUndefined(arg) {
  function isNumber (line 8862) | function isNumber(arg) {
  function isString (line 8867) | function isString(arg) {
  function isSymbol (line 8872) | function isSymbol(arg) {
  function isUndefined (line 8877) | function isUndefined(arg) {
  function isRegExp (line 8882) | function isRegExp(re) {
  function isObject (line 8887) | function isObject(arg) {
  function isDate (line 8892) | function isDate(d) {
  function isError (line 8897) | function isError(e) {
  function isFunction (line 8902) | function isFunction(arg) {
  function isPrimitive (line 8907) | function isPrimitive(arg) {
  function objectToString (line 8919) | function objectToString(o) {
  function map (line 8945) | function map(arr, mapper, that) {
  function clonePath (line 8957) | function clonePath(path) {
  function removeEmpty (line 8960) | function removeEmpty(array) {
  function escapeHTML (line 8969) | function escapeHTML(s) {
  function canonicalize (line 8981) | function canonicalize(obj, stack, replacementStack) {
  function buildValues (line 9026) | function buildValues(components, newString, oldString, useLongestToken) {
  function Diff (line 9070) | function Diff(ignoreWhitespace) {
  function done (line 9077) | function done(value) {
  function execEditLength (line 9113) | function execEditLength() {
  function contextLines (line 9340) | function contextLines(lines) {
  function eofNL (line 9345) | function eofNL(curRange, i, current) {
  function EventEmitter (line 9580) | function EventEmitter() {
  function g (line 9718) | function g() {
  function isFunction (line 9846) | function isFunction(arg) {
  function isNumber (line 9850) | function isNumber(arg) {
  function isObject (line 9854) | function isObject(arg) {
  function isUndefined (line 9858) | function isUndefined(arg) {
  function which (line 9878) | function which(name) {
  function growl (line 10027) | function growl(msg, options, fn) {
  function isBuffer (line 10281) | function isBuffer (obj) {
  function isSlowBuffer (line 10286) | function isSlowBuffer (obj) {
  function runInContext (line 10327) | function runInContext(context, exports) {
  function baseAssign (line 11224) | function baseAssign(object, source) {
  function baseCopy (line 11251) | function baseCopy(source, props, object) {
  function object (line 11285) | function object() {}
  function isObject (line 11316) | function isObject(value) {
  function isObjectLike (line 11348) | function isObjectLike(value) {
  function getNative (line 11381) | function getNative(object, key) {
  function isFunction (line 11402) | function isFunction(value) {
  function isObject (line 11429) | function isObject(value) {
  function isNative (line 11452) | function isNative(value) {
  function baseProperty (line 11490) | function baseProperty(key) {
  function isArrayLike (line 11515) | function isArrayLike(value) {
  function isIndex (line 11527) | function isIndex(value, length) {
  function isIterateeCall (line 11542) | function isIterateeCall(value, index, object) {
  function isLength (line 11565) | function isLength(value) {
  function isObject (line 11589) | function isObject(value) {
  function create (line 11645) | function create(prototype, properties, guard) {
  function isArguments (line 11707) | function isArguments(value) {
  function isArrayLike (line 11738) | function isArrayLike(value) {
  function isArrayLikeObject (line 11767) | function isArrayLikeObject(value) {
  function isFunction (line 11788) | function isFunction(value) {
  function isLength (line 11821) | function isLength(value) {
  function isObject (line 11851) | function isObject(value) {
  function isObjectLike (line 11880) | function isObjectLike(value) {
  function isObjectLike (line 11910) | function isObjectLike(value) {
  function getNative (line 11952) | function getNative(object, key) {
  function isLength (line 11966) | function isLength(value) {
  function isFunction (line 12006) | function isFunction(value) {
  function isObject (line 12033) | function isObject(value) {
  function isNative (line 12056) | function isNative(value) {
  function baseProperty (line 12106) | function baseProperty(key) {
  function isArrayLike (line 12131) | function isArrayLike(value) {
  function isIndex (line 12143) | function isIndex(value, length) {
  function isLength (line 12158) | function isLength(value) {
  function shimKeys (line 12170) | function shimKeys(object) {
  function isObject (line 12210) | function isObject(value) {
  function keysIn (line 12275) | function keysIn(object) {
  function mkdirP (line 12314) | function mkdirP (p, opts, f, made) {
  function nextTick (line 12467) | function nextTick(fn, arg1, arg2, arg3) {
  function defaultSetTimout (line 12514) | function defaultSetTimout() {
  function defaultClearTimeout (line 12517) | function defaultClearTimeout () {
  function runTimeout (line 12540) | function runTimeout(fun) {
  function runClearTimeout (line 12565) | function runClearTimeout(marker) {
  function cleanUpNextTick (line 12597) | function cleanUpNextTick() {
  function drainQueue (line 12612) | function drainQueue() {
  function Item (line 12650) | function Item(fun, array) {
  function noop (line 12664) | function noop() {}
  function Duplex (line 12727) | function Duplex(options) {
  function onend (line 12744) | function onend() {
  function onEndNT (line 12754) | function onEndNT(self) {
  function forEach (line 12758) | function forEach(xs, f) {
  function PassThrough (line 12781) | function PassThrough(options) {
  function prependListener (line 12850) | function prependListener(emitter, event, fn) {
  function ReadableState (line 12863) | function ReadableState(options, stream) {
  function Readable (line 12933) | function Readable(options) {
  function readableAddChunk (line 12976) | function readableAddChunk(stream, state, chunk, encoding, addToFront) {
  function needMoreData (line 13031) | function needMoreData(state) {
  function computeNewHighWaterMark (line 13045) | function computeNewHighWaterMark(n) {
  function howMuchToRead (line 13064) | function howMuchToRead(n, state) {
  function chunkInvalid (line 13183) | function chunkInvalid(state, chunk) {
  function onEofChunk (line 13191) | function onEofChunk(stream, state) {
  function emitReadable (line 13209) | function emitReadable(stream) {
  function emitReadable_ (line 13219) | function emitReadable_(stream) {
  function maybeReadMore (line 13231) | function maybeReadMore(stream, state) {
  function maybeReadMore_ (line 13238) | function maybeReadMore_(stream, state) {
  function onunpipe (line 13282) | function onunpipe(readable) {
  function onend (line 13289) | function onend() {
  function cleanup (line 13302) | function cleanup() {
  function ondata (line 13330) | function ondata(chunk) {
  function onerror (line 13350) | function onerror(er) {
  function onclose (line 13361) | function onclose() {
  function onfinish (line 13366) | function onfinish() {
  function unpipe (line 13373) | function unpipe() {
  function pipeOnDrain (line 13390) | function pipeOnDrain(src) {
  function nReadingNextTick (line 13476) | function nReadingNextTick(self) {
  function resume (line 13493) | function resume(stream, state) {
  function resume_ (line 13500) | function resume_(stream, state) {
  function flow (line 13523) | function flow(stream) {
  function fromList (line 13599) | function fromList(n, state) {
  function fromListPartial (line 13619) | function fromListPartial(n, list, hasStrings) {
  function copyFromBufferString (line 13639) | function copyFromBufferString(n, list) {
  function copyFromBuffer (line 13668) | function copyFromBuffer(n, list) {
  function endReadable (line 13695) | function endReadable(stream) {
  function endReadableNT (line 13708) | function endReadableNT(state, stream) {
  function forEach (line 13717) | function forEach(xs, f) {
  function indexOf (line 13723) | function indexOf(xs, x) {
  function TransformState (line 13786) | function TransformState(stream) {
  function afterTransform (line 13798) | function afterTransform(stream, er, data) {
  function Transform (line 13820) | function Transform(options) {
  function done (line 13897) | function done(stream, er) {
  function nop (line 13960) | function nop() {}
  function WriteReq (line 13962) | function WriteReq(chunk, encoding, cb) {
  function WritableState (line 13970) | function WritableState(options, stream) {
  function Writable (line 14086) | function Writable(options) {
  function writeAfterEnd (line 14112) | function writeAfterEnd(stream, cb) {
  function validChunk (line 14124) | function validChunk(stream, state, chunk, cb) {
  function decodeChunk (line 14188) | function decodeChunk(state, chunk, encoding) {
  function writeOrBuffer (line 14198) | function writeOrBuffer(stream, state, chunk, encoding, cb) {
  function doWrite (line 14226) | function doWrite(stream, state, writev, len, chunk, encoding, cb) {
  function onwriteError (line 14235) | function onwriteError(stream, state, sync, er, cb) {
  function onwriteStateUpdate (line 14243) | function onwriteStateUpdate(state) {
  function onwrite (line 14250) | function onwrite(stream, er) {
  function afterWrite (line 14275) | function afterWrite(stream, state, finished, cb) {
  function onwriteDrain (line 14285) | function onwriteDrain(stream, state) {
  function clearBuffer (line 14293) | function clearBuffer(stream, state) {
  function needFinish (line 14380) | function needFinish(state) {
  function prefinish (line 14384) | function prefinish(stream, state) {
  function finishMaybe (line 14391) | function finishMaybe(stream, state) {
  function endWritable (line 14405) | function endWritable(stream, state, cb) {
  function CorkedRequest (line 14417) | function CorkedRequest(state) {
  function BufferList (line 14450) | function BufferList() {
  function Stream (line 14576) | function Stream() {
  function ondata (line 14583) | function ondata(chunk) {
  function ondrain (line 14593) | function ondrain() {
  function onend (line 14609) | function onend() {
  function onclose (line 14617) | function onclose() {
  function onerror (line 14625) | function onerror(er) {
  function cleanup (line 14636) | function cleanup() {
  function assertEncoding (line 14696) | function assertEncoding(encoding) {
  function passThroughWrite (line 14872) | function passThroughWrite(buffer) {
  function utf16DetectIncompleteChar (line 14876) | function utf16DetectIncompleteChar(buffer) {
  function base64DetectIncompleteChar (line 14881) | function base64DetectIncompleteChar(buffer) {
  function deprecate (line 14913) | function deprecate (fn, msg) {
  function config (line 14944) | function config (name) {
  function deprecated (line 15045) | function deprecated() {
  function inspect (line 15092) | function inspect(obj, opts) {
  function stylizeWithColor (line 15150) | function stylizeWithColor(str, styleType) {
  function stylizeNoColor (line 15162) | function stylizeNoColor(str, styleType) {
  function arrayToHash (line 15167) | function arrayToHash(array) {
  function formatValue (line 15178) | function formatValue(ctx, value, recurseTimes) {
  function formatPrimitive (line 15291) | function formatPrimitive(ctx, value) {
  function formatError (line 15310) | function formatError(value) {
  function formatArray (line 15315) | function formatArray(ctx, value, recurseTimes, visibleKeys, keys) {
  function formatProperty (line 15335) | function formatProperty(ctx, value, recurseTimes, visibleKeys, key, arra...
  function reduceToSingleString (line 15394) | function reduceToSingleString(output, base, braces) {
  function isArray (line 15417) | function isArray(ar) {
  function isBoolean (line 15422) | function isBoolean(arg) {
  function isNull (line 15427) | function isNull(arg) {
  function isNullOrUndefined (line 15432) | function isNullOrUndefined(arg) {
  function isNumber (line 15437) | function isNumber(arg) {
  function isString (line 15442) | function isString(arg) {
  function isSymbol (line 15447) | function isSymbol(arg) {
  function isUndefined (line 15452) | function isUndefined(arg) {
  function isRegExp (line 15457) | function isRegExp(re) {
  function isObject (line 15462) | function isObject(arg) {
  function isDate (line 15467) | function isDate(d) {
  function isError (line 15472) | function isError(e) {
  function isFunction (line 15478) | function isFunction(arg) {
  function isPrimitive (line 15483) | function isPrimitive(arg) {
  function objectToString (line 15495) | function objectToString(o) {
  function pad (line 15500) | function pad(n) {
  function timestamp (line 15509) | function timestamp() {
  function hasOwnProperty (line 15551) | function hasOwnProperty(obj, prop) {
Condensed preview — 32 files, each showing path, character count, and a content snippet. Download the .json file or copy for the full structured content (553K chars).
[
  {
    "path": ".babelrc",
    "chars": 28,
    "preview": "{\n  \"presets\": [\"es2015\"]\n}\n"
  },
  {
    "path": ".gitignore",
    "chars": 58,
    "preview": "*.swp\nnode_modules\n.idea\n*.log\n\ntest/e2e/bowl.js\nnotes.md\n"
  },
  {
    "path": "CHANGELOG.md",
    "chars": 259,
    "preview": "## 0.1.0\n### Features\n- Basic Features\n\n## 0.1.1\n- make `key` property required\n- handle situations where resources have"
  },
  {
    "path": "LICENSE",
    "chars": 1092,
    "preview": "MIT License\n\nCopyright (c) 2017 Shuang.Wu@ElemeFE<shuang.wu@ele.me>\n\nPermission is hereby granted, free of charge, to an"
  },
  {
    "path": "README.md",
    "chars": 1872,
    "preview": "<p align=\"center\"><image src=\"https://github.com/classicemi/bowl.js/blob/develop/assets/logo.png?raw=true\" width=\"128\"><"
  },
  {
    "path": "build/build.js",
    "chars": 1188,
    "preview": "const rollup = require('rollup');\nconst fs = require('fs');\nconst path = require('path');\nconst uglify = require('uglify"
  },
  {
    "path": "build/config.js",
    "chars": 875,
    "preview": "const path = require('path');\nconst buble = require('rollup-plugin-buble');\nconst version = process.env.VERSION || requi"
  },
  {
    "path": "build/karma.config.js",
    "chars": 2020,
    "preview": "const webpack = require('webpack');\n\nlet webpackConfig = {\n  module: {\n    loaders: [\n      {\n        test: /\\.js$/,\n   "
  },
  {
    "path": "build/release.sh",
    "chars": 475,
    "preview": "set -e\n\nif [[ -z $1 ]]; then\n  echo \"Enter new version: \"\n  read VERSION\nelse\n  VERSION=$1\nfi\n\nread -p \"Releasing v$VERS"
  },
  {
    "path": "docs/README.md",
    "chars": 5420,
    "preview": "<p align=\"center\"><image src=\"https://github.com/classicemi/bowl.js/blob/develop/assets/logo.png?raw=true\" width=\"128\"><"
  },
  {
    "path": "docs/index.html",
    "chars": 566,
    "preview": "<!DOCTYPE html>\n<html lang=\"en\">\n<head>\n  <meta charset=\"UTF-8\">\n  <title>bowl</title>\n  <meta name=\"description\" conten"
  },
  {
    "path": "docs/zh-cn.md",
    "chars": 4507,
    "preview": "<p align=\"center\"><image src=\"https://github.com/classicemi/bowl.js/blob/develop/assets/logo.png?raw=true\" width=\"128\"><"
  },
  {
    "path": "lib/bowl.js",
    "chars": 13665,
    "preview": "/*\n * bowl.js v0.1.4\n * (c) 2016-2017 classicemi\n * Released under the MIT license.\n */\n(function (global, factory) {\n  "
  },
  {
    "path": "package.json",
    "chars": 1232,
    "preview": "{\n  \"name\": \"bowl.js\",\n  \"version\": \"0.1.4\",\n  \"description\": \"static resources front-end storage solving strategy\",\n  \""
  },
  {
    "path": "src/bowl.class.js",
    "chars": 4231,
    "preview": "import * as utils from './utils'\nimport Injector from './injector/injector.class'\nimport Graph from './graph.class'\n\ncon"
  },
  {
    "path": "src/graph.class.js",
    "chars": 2071,
    "preview": "import {\n  isString,\n  isObject,\n  merge\n} from './utils'\n\nexport default class Graph {\n  constructor(vertices) {\n    th"
  },
  {
    "path": "src/index.js",
    "chars": 53,
    "preview": "import Bowl from './bowl.class'\n\nexport default Bowl\n"
  },
  {
    "path": "src/injector/cacheUnit.class.js",
    "chars": 201,
    "preview": "export default class CacheUnit {\n  constructor(ingredient) {\n    this.key = ingredient.key\n    this.url = ingredient.url"
  },
  {
    "path": "src/injector/injector.class.js",
    "chars": 3145,
    "preview": "import * as utils from '../utils'\nimport Unit from './cacheUnit.class'\n\nexport default class Injector {\n  constructor(co"
  },
  {
    "path": "src/utils.js",
    "chars": 3534,
    "preview": "const global = window\n\n/**\n * check if the argument is an instance of Object\n */\nexport function isObject(obj) {\n  retur"
  },
  {
    "path": "test/e2e/assets/a.js",
    "chars": 15,
    "preview": "window.aFlag++\n"
  },
  {
    "path": "test/e2e/assets/b.js",
    "chars": 15,
    "preview": "window.bFlag++\n"
  },
  {
    "path": "test/e2e/assets/c.js",
    "chars": 15,
    "preview": "window.cFlag++\n"
  },
  {
    "path": "test/e2e/assets/style.css",
    "chars": 29,
    "preview": "#mocha {\n  font-size: 10px\n}\n"
  },
  {
    "path": "test/e2e/test.html",
    "chars": 469,
    "preview": "<html>\n  <head>\n    <title>bowl.js e2e tests</title>\n    <meta charset=\"utf-8\">\n    <link rel=\"stylesheet\" href=\"vendor/"
  },
  {
    "path": "test/e2e/test.js",
    "chars": 9528,
    "preview": "window.aFlag = 0\nwindow.bFlag = 0\nwindow.cFlag = 0\n\ndescribe('bowl instance', () => {\n  let bowl = new Bowl()\n\n  beforeE"
  },
  {
    "path": "test/e2e/vendor/expect.js",
    "chars": 36474,
    "preview": "(function (global, module) {\n\n  var exports = module.exports;\n\n  /**\n   * Exports.\n   */\n\n  module.exports = expect;\n  e"
  },
  {
    "path": "test/e2e/vendor/mocha.css",
    "chars": 5610,
    "preview": "@charset \"utf-8\";\n\nbody {\n  margin:0;\n}\n\n#mocha {\n  font: 20px/1.5 \"Helvetica Neue\", Helvetica, Arial, sans-serif;\n  mar"
  },
  {
    "path": "test/e2e/vendor/mocha.js",
    "chars": 422364,
    "preview": "(function e(t,n,r){function s(o,u){if(!n[o]){if(!t[o]){var a=typeof require==\"function\"&&require;if(!u&&a)return a(o,!0)"
  },
  {
    "path": "test/unit/index.js",
    "chars": 107,
    "preview": "const testsContext = require.context('./specs', true, /\\.spec$/)\ntestsContext.keys().forEach(testsContext)\n"
  },
  {
    "path": "test/unit/specs/graph.spec.js",
    "chars": 1988,
    "preview": "import Graph from '../../../src/graph.class'\n\ndescribe('Graph', () => {\n  let g = null\n  beforeEach(() => {\n    g = new "
  },
  {
    "path": "test/unit/specs/utils.spec.js",
    "chars": 4258,
    "preview": "import * as utils from '../../../src/utils'\n\ndescribe('utils', () => {\n  it('can tell if a variable is instance of `Obje"
  }
]

About this extraction

This page contains the full source code of the ElemeFE/bowl GitHub repository, extracted and formatted as plain text for AI agents and large language models (LLMs). The extraction includes 32 files (515.0 KB), approximately 139.9k tokens, and a symbol index with 393 extracted functions, classes, methods, constants, and types. Use this with OpenClaw, Claude, ChatGPT, Cursor, Windsurf, or any other AI tool that accepts text input. You can copy the full output to your clipboard or download it as a .txt file.

Extracted by GitExtract — free GitHub repo to text converter for AI. Built by Nikandr Surkov.

Copied to clipboard!