Torkel Ödegaard 12 лет назад
Родитель
Сommit
8f77a82170

+ 779 - 0
src/app/services/graphite/lexer.js

@@ -0,0 +1,779 @@
+define([
+], function() {
+  'use strict';
+
+  // This is auto generated from the unicode tables.
+  // The tables are at:
+  // http://www.fileformat.info/info/unicode/category/Lu/list.htm
+  // http://www.fileformat.info/info/unicode/category/Ll/list.htm
+  // http://www.fileformat.info/info/unicode/category/Lt/list.htm
+  // http://www.fileformat.info/info/unicode/category/Lm/list.htm
+  // http://www.fileformat.info/info/unicode/category/Lo/list.htm
+  // http://www.fileformat.info/info/unicode/category/Nl/list.htm
+
+  var unicodeLetterTable = [
+    170, 170, 181, 181, 186, 186, 192, 214,
+    216, 246, 248, 705, 710, 721, 736, 740, 748, 748, 750, 750,
+    880, 884, 886, 887, 890, 893, 902, 902, 904, 906, 908, 908,
+    910, 929, 931, 1013, 1015, 1153, 1162, 1319, 1329, 1366,
+    1369, 1369, 1377, 1415, 1488, 1514, 1520, 1522, 1568, 1610,
+    1646, 1647, 1649, 1747, 1749, 1749, 1765, 1766, 1774, 1775,
+    1786, 1788, 1791, 1791, 1808, 1808, 1810, 1839, 1869, 1957,
+    1969, 1969, 1994, 2026, 2036, 2037, 2042, 2042, 2048, 2069,
+    2074, 2074, 2084, 2084, 2088, 2088, 2112, 2136, 2308, 2361,
+    2365, 2365, 2384, 2384, 2392, 2401, 2417, 2423, 2425, 2431,
+    2437, 2444, 2447, 2448, 2451, 2472, 2474, 2480, 2482, 2482,
+    2486, 2489, 2493, 2493, 2510, 2510, 2524, 2525, 2527, 2529,
+    2544, 2545, 2565, 2570, 2575, 2576, 2579, 2600, 2602, 2608,
+    2610, 2611, 2613, 2614, 2616, 2617, 2649, 2652, 2654, 2654,
+    2674, 2676, 2693, 2701, 2703, 2705, 2707, 2728, 2730, 2736,
+    2738, 2739, 2741, 2745, 2749, 2749, 2768, 2768, 2784, 2785,
+    2821, 2828, 2831, 2832, 2835, 2856, 2858, 2864, 2866, 2867,
+    2869, 2873, 2877, 2877, 2908, 2909, 2911, 2913, 2929, 2929,
+    2947, 2947, 2949, 2954, 2958, 2960, 2962, 2965, 2969, 2970,
+    2972, 2972, 2974, 2975, 2979, 2980, 2984, 2986, 2990, 3001,
+    3024, 3024, 3077, 3084, 3086, 3088, 3090, 3112, 3114, 3123,
+    3125, 3129, 3133, 3133, 3160, 3161, 3168, 3169, 3205, 3212,
+    3214, 3216, 3218, 3240, 3242, 3251, 3253, 3257, 3261, 3261,
+    3294, 3294, 3296, 3297, 3313, 3314, 3333, 3340, 3342, 3344,
+    3346, 3386, 3389, 3389, 3406, 3406, 3424, 3425, 3450, 3455,
+    3461, 3478, 3482, 3505, 3507, 3515, 3517, 3517, 3520, 3526,
+    3585, 3632, 3634, 3635, 3648, 3654, 3713, 3714, 3716, 3716,
+    3719, 3720, 3722, 3722, 3725, 3725, 3732, 3735, 3737, 3743,
+    3745, 3747, 3749, 3749, 3751, 3751, 3754, 3755, 3757, 3760,
+    3762, 3763, 3773, 3773, 3776, 3780, 3782, 3782, 3804, 3805,
+    3840, 3840, 3904, 3911, 3913, 3948, 3976, 3980, 4096, 4138,
+    4159, 4159, 4176, 4181, 4186, 4189, 4193, 4193, 4197, 4198,
+    4206, 4208, 4213, 4225, 4238, 4238, 4256, 4293, 4304, 4346,
+    4348, 4348, 4352, 4680, 4682, 4685, 4688, 4694, 4696, 4696,
+    4698, 4701, 4704, 4744, 4746, 4749, 4752, 4784, 4786, 4789,
+    4792, 4798, 4800, 4800, 4802, 4805, 4808, 4822, 4824, 4880,
+    4882, 4885, 4888, 4954, 4992, 5007, 5024, 5108, 5121, 5740,
+    5743, 5759, 5761, 5786, 5792, 5866, 5870, 5872, 5888, 5900,
+    5902, 5905, 5920, 5937, 5952, 5969, 5984, 5996, 5998, 6000,
+    6016, 6067, 6103, 6103, 6108, 6108, 6176, 6263, 6272, 6312,
+    6314, 6314, 6320, 6389, 6400, 6428, 6480, 6509, 6512, 6516,
+    6528, 6571, 6593, 6599, 6656, 6678, 6688, 6740, 6823, 6823,
+    6917, 6963, 6981, 6987, 7043, 7072, 7086, 7087, 7104, 7141,
+    7168, 7203, 7245, 7247, 7258, 7293, 7401, 7404, 7406, 7409,
+    7424, 7615, 7680, 7957, 7960, 7965, 7968, 8005, 8008, 8013,
+    8016, 8023, 8025, 8025, 8027, 8027, 8029, 8029, 8031, 8061,
+    8064, 8116, 8118, 8124, 8126, 8126, 8130, 8132, 8134, 8140,
+    8144, 8147, 8150, 8155, 8160, 8172, 8178, 8180, 8182, 8188,
+    8305, 8305, 8319, 8319, 8336, 8348, 8450, 8450, 8455, 8455,
+    8458, 8467, 8469, 8469, 8473, 8477, 8484, 8484, 8486, 8486,
+    8488, 8488, 8490, 8493, 8495, 8505, 8508, 8511, 8517, 8521,
+    8526, 8526, 8544, 8584, 11264, 11310, 11312, 11358,
+    11360, 11492, 11499, 11502, 11520, 11557, 11568, 11621,
+    11631, 11631, 11648, 11670, 11680, 11686, 11688, 11694,
+    11696, 11702, 11704, 11710, 11712, 11718, 11720, 11726,
+    11728, 11734, 11736, 11742, 11823, 11823, 12293, 12295,
+    12321, 12329, 12337, 12341, 12344, 12348, 12353, 12438,
+    12445, 12447, 12449, 12538, 12540, 12543, 12549, 12589,
+    12593, 12686, 12704, 12730, 12784, 12799, 13312, 13312,
+    19893, 19893, 19968, 19968, 40907, 40907, 40960, 42124,
+    42192, 42237, 42240, 42508, 42512, 42527, 42538, 42539,
+    42560, 42606, 42623, 42647, 42656, 42735, 42775, 42783,
+    42786, 42888, 42891, 42894, 42896, 42897, 42912, 42921,
+    43002, 43009, 43011, 43013, 43015, 43018, 43020, 43042,
+    43072, 43123, 43138, 43187, 43250, 43255, 43259, 43259,
+    43274, 43301, 43312, 43334, 43360, 43388, 43396, 43442,
+    43471, 43471, 43520, 43560, 43584, 43586, 43588, 43595,
+    43616, 43638, 43642, 43642, 43648, 43695, 43697, 43697,
+    43701, 43702, 43705, 43709, 43712, 43712, 43714, 43714,
+    43739, 43741, 43777, 43782, 43785, 43790, 43793, 43798,
+    43808, 43814, 43816, 43822, 43968, 44002, 44032, 44032,
+    55203, 55203, 55216, 55238, 55243, 55291, 63744, 64045,
+    64048, 64109, 64112, 64217, 64256, 64262, 64275, 64279,
+    64285, 64285, 64287, 64296, 64298, 64310, 64312, 64316,
+    64318, 64318, 64320, 64321, 64323, 64324, 64326, 64433,
+    64467, 64829, 64848, 64911, 64914, 64967, 65008, 65019,
+    65136, 65140, 65142, 65276, 65313, 65338, 65345, 65370,
+    65382, 65470, 65474, 65479, 65482, 65487, 65490, 65495,
+    65498, 65500, 65536, 65547, 65549, 65574, 65576, 65594,
+    65596, 65597, 65599, 65613, 65616, 65629, 65664, 65786,
+    65856, 65908, 66176, 66204, 66208, 66256, 66304, 66334,
+    66352, 66378, 66432, 66461, 66464, 66499, 66504, 66511,
+    66513, 66517, 66560, 66717, 67584, 67589, 67592, 67592,
+    67594, 67637, 67639, 67640, 67644, 67644, 67647, 67669,
+    67840, 67861, 67872, 67897, 68096, 68096, 68112, 68115,
+    68117, 68119, 68121, 68147, 68192, 68220, 68352, 68405,
+    68416, 68437, 68448, 68466, 68608, 68680, 69635, 69687,
+    69763, 69807, 73728, 74606, 74752, 74850, 77824, 78894,
+    92160, 92728, 110592, 110593, 119808, 119892, 119894, 119964,
+    119966, 119967, 119970, 119970, 119973, 119974, 119977, 119980,
+    119982, 119993, 119995, 119995, 119997, 120003, 120005, 120069,
+    120071, 120074, 120077, 120084, 120086, 120092, 120094, 120121,
+    120123, 120126, 120128, 120132, 120134, 120134, 120138, 120144,
+    120146, 120485, 120488, 120512, 120514, 120538, 120540, 120570,
+    120572, 120596, 120598, 120628, 120630, 120654, 120656, 120686,
+    120688, 120712, 120714, 120744, 120746, 120770, 120772, 120779,
+    131072, 131072, 173782, 173782, 173824, 173824, 177972, 177972,
+    177984, 177984, 178205, 178205, 194560, 195101
+  ];
+
+  var identifierStartTable = [];
+
+  for (var i = 0; i < 128; i++) {
+    identifierStartTable[i] =
+      i === 36 ||           // $
+      i >= 65 && i <= 90 || // A-Z
+      i === 95 ||           // _
+      i >= 97 && i <= 122;  // a-z
+  }
+
+  var identifierPartTable = [];
+
+  for (var i2 = 0; i2 < 128; i2++) {
+    identifierPartTable[i2] =
+      identifierStartTable[i2] || // $, _, A-Z, a-z
+      i2 >= 48 && i2 <= 57;        // 0-9
+  }
+
+  var Token = {
+    Identifier: "Identifier",
+    NumericLiteral: "NumericLiteral",
+    StringLiteral: "StringLiteral",
+    Punctuator: "Punctuator"
+  };
+
+
+  function Lexer(expression) {
+    this.input = expression;
+    this.char = 1;
+    this.from = 1;
+  }
+
+  Lexer.Token = Token;
+
+  Lexer.prototype = {
+
+    peek: function (i) {
+      return this.input.charAt(i || 0);
+    },
+
+    skip: function (i) {
+      i = i || 1;
+      this.char += i;
+      this.input = this.input.slice(i);
+    },
+
+    tokenize: function() {
+      var list = [];
+      var token;
+      while(token = this.next()) {
+        list.push(token);
+      }
+      return list;
+    },
+
+    next: function() {
+      this.from = this.char;
+
+      // Move to the next non-space character.
+      var start;
+      if (/\s/.test(this.peek())) {
+        start = this.char;
+
+        while (/\s/.test(this.peek())) {
+          this.from += 1;
+          this.skip();
+        }
+
+        if (this.peek() === "") { // EOL
+          return null;
+        }
+      }
+
+      var match =
+        this.scanIdentifier() ||
+        this.scanPunctuator() ||
+        this.scanStringLiteral() ||
+        this.scanNumericLiteral();
+
+      if (match) {
+        this.skip(match.value.length);
+        return match;
+      }
+
+      // No token could be matched, give up.
+      return null;
+    },
+
+      /*
+     * Extract a JavaScript identifier out of the next sequence of
+     * characters or return 'null' if its not possible. In addition,
+     * to Identifier this method can also produce BooleanLiteral
+     * (true/false) and NullLiteral (null).
+     */
+    scanIdentifier: function() {
+      var id = "";
+      var index = 0;
+      var type, char;
+
+      // Detects any character in the Unicode categories "Uppercase
+      // letter (Lu)", "Lowercase letter (Ll)", "Titlecase letter
+      // (Lt)", "Modifier letter (Lm)", "Other letter (Lo)", or
+      // "Letter number (Nl)".
+      //
+      // Both approach and unicodeLetterTable were borrowed from
+      // Google's Traceur.
+
+      function isUnicodeLetter(code) {
+        for (var i = 0; i < unicodeLetterTable.length;) {
+          if (code < unicodeLetterTable[i++]) {
+            return false;
+          }
+
+          if (code <= unicodeLetterTable[i++]) {
+            return true;
+          }
+        }
+
+        return false;
+      }
+
+      function isHexDigit(str) {
+        return (/^[0-9a-fA-F]$/).test(str);
+      }
+
+      var readUnicodeEscapeSequence = function () {
+        /*jshint validthis:true */
+        index += 1;
+
+        if (this.peek(index) !== "u") {
+          return null;
+        }
+
+        var ch1 = this.peek(index + 1);
+        var ch2 = this.peek(index + 2);
+        var ch3 = this.peek(index + 3);
+        var ch4 = this.peek(index + 4);
+        var code;
+
+        if (isHexDigit(ch1) && isHexDigit(ch2) && isHexDigit(ch3) && isHexDigit(ch4)) {
+          code = parseInt(ch1 + ch2 + ch3 + ch4, 16);
+
+          if (isUnicodeLetter(code)) {
+            index += 5;
+            return "\\u" + ch1 + ch2 + ch3 + ch4;
+          }
+
+          return null;
+        }
+
+        return null;
+      }.bind(this);
+
+      var getIdentifierStart = function () {
+        /*jshint validthis:true */
+        var chr = this.peek(index);
+        var code = chr.charCodeAt(0);
+
+        if (chr === '*') {
+          index += 1;
+          return chr;
+        }
+
+        if (code === 92) {
+          return readUnicodeEscapeSequence();
+        }
+
+        if (code < 128) {
+          if (identifierStartTable[code]) {
+            index += 1;
+            return chr;
+          }
+
+          return null;
+        }
+
+        if (isUnicodeLetter(code)) {
+          index += 1;
+          return chr;
+        }
+
+        return null;
+      }.bind(this);
+
+      var getIdentifierPart = function () {
+        /*jshint validthis:true */
+        var chr = this.peek(index);
+        var code = chr.charCodeAt(0);
+
+        if (code === 92) {
+          return readUnicodeEscapeSequence();
+        }
+
+        if (code < 128) {
+          if (identifierPartTable[code]) {
+            index += 1;
+            return chr;
+          }
+
+          return null;
+        }
+
+        if (isUnicodeLetter(code)) {
+          index += 1;
+          return chr;
+        }
+
+        return null;
+      }.bind(this);
+
+      char = getIdentifierStart();
+      if (char === null) {
+        return null;
+      }
+
+      id = char;
+      for (;;) {
+        char = getIdentifierPart();
+
+        if (char === null) {
+          break;
+        }
+
+        id += char;
+      }
+
+      switch (id) {
+      default:
+        type = Token.Identifier;
+      }
+
+      return {
+        type: type,
+        value: id
+      };
+
+    },
+
+      /*
+     * Extract a numeric literal out of the next sequence of
+     * characters or return 'null' if its not possible. This method
+     * supports all numeric literals described in section 7.8.3
+     * of the EcmaScript 5 specification.
+     *
+     * This method's implementation was heavily influenced by the
+     * scanNumericLiteral function in the Esprima parser's source code.
+     */
+    scanNumericLiteral: function () {
+      var index = 0;
+      var value = "";
+      var length = this.input.length;
+      var char = this.peek(index);
+      var bad;
+
+      function isDecimalDigit(str) {
+        return (/^[0-9]$/).test(str);
+      }
+
+      function isOctalDigit(str) {
+        return (/^[0-7]$/).test(str);
+      }
+
+      function isHexDigit(str) {
+        return (/^[0-9a-fA-F]$/).test(str);
+      }
+
+      function isIdentifierStart(ch) {
+        return (ch === "$") || (ch === "_") || (ch === "\\") ||
+          (ch >= "a" && ch <= "z") || (ch >= "A" && ch <= "Z");
+      }
+
+      // Numbers must start either with a decimal digit or a point.
+
+      if (char !== "." && !isDecimalDigit(char)) {
+        return null;
+      }
+
+      if (char !== ".") {
+        value = this.peek(index);
+        index += 1;
+        char = this.peek(index);
+
+        if (value === "0") {
+          // Base-16 numbers.
+          if (char === "x" || char === "X") {
+            index += 1;
+            value += char;
+
+            while (index < length) {
+              char = this.peek(index);
+              if (!isHexDigit(char)) {
+                break;
+              }
+              value += char;
+              index += 1;
+            }
+
+            if (value.length <= 2) { // 0x
+              return {
+                type: Token.NumericLiteral,
+                value: value,
+                isMalformed: true
+              };
+            }
+
+            if (index < length) {
+              char = this.peek(index);
+              if (isIdentifierStart(char)) {
+                return null;
+              }
+            }
+
+            return {
+              type: Token.NumericLiteral,
+              value: value,
+              base: 16,
+              isMalformed: false
+            };
+          }
+
+          // Base-8 numbers.
+          if (isOctalDigit(char)) {
+            index += 1;
+            value += char;
+            bad = false;
+
+            while (index < length) {
+              char = this.peek(index);
+
+              // Numbers like '019' (note the 9) are not valid octals
+              // but we still parse them and mark as malformed.
+
+              if (isDecimalDigit(char)) {
+                bad = true;
+              } else if (!isOctalDigit(char)) {
+                break;
+              }
+              value += char;
+              index += 1;
+            }
+
+            if (index < length) {
+              char = this.peek(index);
+              if (isIdentifierStart(char)) {
+                return null;
+              }
+            }
+
+            return {
+              type: Token.NumericLiteral,
+              value: value,
+              base: 8,
+              isMalformed: false
+            };
+          }
+
+          // Decimal numbers that start with '0' such as '09' are illegal
+          // but we still parse them and return as malformed.
+
+          if (isDecimalDigit(char)) {
+            index += 1;
+            value += char;
+          }
+        }
+
+        while (index < length) {
+          char = this.peek(index);
+          if (!isDecimalDigit(char)) {
+            break;
+          }
+          value += char;
+          index += 1;
+        }
+      }
+
+      // Decimal digits.
+
+      if (char === ".") {
+        value += char;
+        index += 1;
+
+        while (index < length) {
+          char = this.peek(index);
+          if (!isDecimalDigit(char)) {
+            break;
+          }
+          value += char;
+          index += 1;
+        }
+      }
+
+      // Exponent part.
+
+      if (char === "e" || char === "E") {
+        value += char;
+        index += 1;
+        char = this.peek(index);
+
+        if (char === "+" || char === "-") {
+          value += this.peek(index);
+          index += 1;
+        }
+
+        char = this.peek(index);
+        if (isDecimalDigit(char)) {
+          value += char;
+          index += 1;
+
+          while (index < length) {
+            char = this.peek(index);
+            if (!isDecimalDigit(char)) {
+              break;
+            }
+            value += char;
+            index += 1;
+          }
+        } else {
+          return null;
+        }
+      }
+
+      if (index < length) {
+        char = this.peek(index);
+        if (isIdentifierStart(char)) {
+          return null;
+        }
+      }
+
+      return {
+        type: Token.NumericLiteral,
+        value: value,
+        base: 10,
+        isMalformed: !isFinite(value)
+      };
+    },
+
+    scanPunctuator: function () {
+      var ch1 = this.peek();
+
+      switch (ch1) {
+      case ".":
+      case "(":
+      case ")":
+      case ",":
+      case "{":
+      case "}":
+        return {
+          type: Token.Punctuator,
+          value: ch1
+        };
+      }
+
+      return null;
+    },
+
+      /*
+     * Extract a string out of the next sequence of characters and/or
+     * lines or return 'null' if its not possible. Since strings can
+     * span across multiple lines this method has to move the char
+     * pointer.
+     *
+     * This method recognizes pseudo-multiline JavaScript strings:
+     *
+     *   var str = "hello\
+     *   world";
+     */
+    scanStringLiteral: function (checks) {
+      /*jshint loopfunc:true */
+      var quote = this.peek();
+
+      // String must start with a quote.
+      if (quote !== "\"" && quote !== "'") {
+        return null;
+      }
+
+      var value = "";
+      var startLine = this.line;
+      var startChar = this.char;
+      var allowNewLine = false;
+
+      this.skip();
+
+      while (this.peek() !== quote) {
+        while (this.peek() === "") { // End Of Line
+
+          // If an EOL is not preceded by a backslash, show a warning
+          // and proceed like it was a legit multi-line string where
+          // author simply forgot to escape the newline symbol.
+          //
+          // Another approach is to implicitly close a string on EOL
+          // but it generates too many false positives.
+
+          if (!allowNewLine) {
+            this.trigger("warning", {
+              code: "W112",
+              line: this.line,
+              character: this.char
+            });
+          } else {
+            allowNewLine = false;
+
+            // Otherwise show a warning if multistr option was not set.
+            // For JSON, show warning no matter what.
+
+            this.triggerAsync("warning", {
+              code: "W043",
+              line: this.line,
+              character: this.char
+            }, checks, function () { return !state.option.multistr; });
+
+            this.triggerAsync("warning", {
+              code: "W042",
+              line: this.line,
+              character: this.char
+            }, checks, function () { return state.jsonMode && state.option.multistr; });
+          }
+
+          // If we get an EOF inside of an unclosed string, show an
+          // error and implicitly close it at the EOF point.
+
+          if (!this.nextLine()) {
+            this.trigger("error", {
+              code: "E029",
+              line: startLine,
+              character: startChar
+            });
+
+            return {
+              type: Token.StringLiteral,
+              value: value,
+              isUnclosed: true,
+              quote: quote
+            };
+          }
+        }
+
+        allowNewLine = false;
+        var char = this.peek();
+        var jump = 1; // A length of a jump, after we're done
+                      // parsing this character.
+
+        if (char < " ") {
+          // Warn about a control character in a string.
+          this.trigger("warning", {
+            code: "W113",
+            line: this.line,
+            character: this.char,
+            data: [ "<non-printable>" ]
+          });
+        }
+
+        // Special treatment for some escaped characters.
+
+        if (char === "\\") {
+          this.skip();
+          char = this.peek();
+
+          switch (char) {
+          case "'":
+            this.triggerAsync("warning", {
+              code: "W114",
+              line: this.line,
+              character: this.char,
+              data: [ "\\'" ]
+            }, checks, function () {return state.jsonMode; });
+            break;
+          case "b":
+            char = "\b";
+            break;
+          case "f":
+            char = "\f";
+            break;
+          case "n":
+            char = "\n";
+            break;
+          case "r":
+            char = "\r";
+            break;
+          case "t":
+            char = "\t";
+            break;
+          case "0":
+            char = "\0";
+
+            // Octal literals fail in strict mode.
+            // Check if the number is between 00 and 07.
+            var n = parseInt(this.peek(1), 10);
+            this.triggerAsync("warning", {
+              code: "W115",
+              line: this.line,
+              character: this.char
+            }, checks,
+            function () { return n >= 0 && n <= 7 && state.directive["use strict"]; });
+            break;
+          case "u":
+            char = String.fromCharCode(parseInt(this.input.substr(1, 4), 16));
+            jump = 5;
+            break;
+          case "v":
+            this.triggerAsync("warning", {
+              code: "W114",
+              line: this.line,
+              character: this.char,
+              data: [ "\\v" ]
+            }, checks, function () { return state.jsonMode; });
+
+            char = "\v";
+            break;
+          case "x":
+            var x = parseInt(this.input.substr(1, 2), 16);
+
+            this.triggerAsync("warning", {
+              code: "W114",
+              line: this.line,
+              character: this.char,
+              data: [ "\\x-" ]
+            }, checks, function () { return state.jsonMode; });
+
+            char = String.fromCharCode(x);
+            jump = 3;
+            break;
+          case "\\":
+          case "\"":
+          case "/":
+            break;
+          case "":
+            allowNewLine = true;
+            char = "";
+            break;
+          case "!":
+            if (value.slice(value.length - 2) === "<") {
+              break;
+            }
+
+            /*falls through */
+          default:
+            // Weird escaping.
+            this.trigger("warning", {
+              code: "W044",
+              line: this.line,
+              character: this.char
+            });
+          }
+        }
+
+        value += char;
+        this.skip(jump);
+      }
+
+      this.skip();
+      return {
+        type: Token.StringLiteral,
+        value: value,
+        isUnclosed: false,
+        quote: quote
+      };
+    },
+
+  };
+
+  return Lexer;
+
+});
+
+

+ 167 - 0
src/app/services/graphite/parser.js

@@ -0,0 +1,167 @@
+define([
+  './lexer'
+], function (Lexer) {
+
+  var NodeTypes = {
+    MetricExpression = 1,
+    MetricNode: 2,
+    FunctionCall: 4
+  };
+
+  function Node(type, value) {
+    this.type = type;
+    this.value = value;
+  }
+
+  function Parser(expression) {
+    this.expression = expression;
+    this.lexer = new Lexer(expression);
+    this.state = "start";
+    this.error = null;
+  }
+
+  Parser.prototype = {
+      getAst: function () {
+        return parse('start');
+      },
+
+      checkToken: function (token, expected) {
+        if (token === null) {
+          this.error = "Expected token: " + expected + " instead found end of string";
+          return;
+        }
+
+        if (token.type === expected) {
+          return true;
+        }
+
+        this.error = "Expected  token "
+            + expected + " instead found + "
+            found + " at position: " + lexer.char;
+
+        return false;
+      },
+
+      parse: function (state) {
+        var node = { children: [] };
+
+        var token = lexer.next();
+        var nextToken = lexer.next();
+
+        if (checkToken(token, Lexer.Token.Identifier) {
+          return null;
+        }
+
+        if (nextToken == null) {
+          return {
+            type: NodeTypes.MetricExpression,
+            nodes: [
+              {
+                type: NodeTypes.MetricNode,
+                value: token.value
+              }
+            ]
+          }
+        }
+
+        if (checkToken(nextToken, Lexer.Token.Punctuator)) {
+          return null;
+        }
+
+        if (nextToken.value === '.') {
+          return parseMetricExpression(token);
+        }
+      },
+
+      parseMetricExpression: function(firstToken) {
+        var node = {
+          type: NodeTypes.MetricExpression,
+          nodes: [
+            {
+              type: NodeTypes.MetricNode,
+              value: firstToken.value
+            }
+          ]
+        };
+
+        var token;
+
+        while(true) {
+          token = lexer.nextToken();
+          if (checkToken(token, Lexer.Token.Identifier)) {
+            return null;
+          }
+
+        }
+      }
+        /*while(true) {
+          token = lexer.next();
+
+          switch(state) {
+
+          case "start":
+            if (checkToken(token, Lexer.Token.Identifier) {
+              return;
+            }
+
+            state = "identifier";
+            prevToken = token;
+            break;
+
+          case "identifier":
+            if (token == null) {
+              node.type = NodeTypes.MetricExpression;
+              node.children.push([
+                type: NodeTypes.MetricNode,
+                value: prevToken.value;
+              ]);
+
+              return node;
+            }
+
+            if (checkToken(token, Lexer.Token.Punctuator)) {
+              return;
+            }
+
+            if (token.value === '.') {
+              state = "metricNode";
+              node.type = NodeTypes.MetricExpression;
+              node.children.push({
+                type: NodeTypes.MetricNode,
+                value: prevToken.value
+              });
+            }
+
+            if (token.value === '(') {
+              state = 'function';
+            }
+
+            break;
+          case 'metricEnd':
+            if (token === null) {
+              return node;
+            }
+
+            if (checkToken(token, Lexer.Token.Punctuator)) {
+              return null;
+            }
+
+          case 'metricNode':
+            if (checkToken(token, Lexer.Token.Identifier)) {
+              return null;
+            }
+
+            node.children.push([
+              type: NodeTypes.MetricNode,
+              value: token.value
+            ]);
+
+            state = 'metricEnd';
+            break;
+          }
+        }
+      }*/
+  };
+
+  return Parser;
+});

+ 1253 - 0
src/test/assets/expect.js

@@ -0,0 +1,1253 @@
+
+(function (global, module) {
+
+  if ('undefined' == typeof module) {
+    var module = { exports: {} }
+      , exports = module.exports
+  }
+
+  /**
+   * Exports.
+   */
+
+  module.exports = expect;
+  expect.Assertion = Assertion;
+
+  /**
+   * Exports version.
+   */
+
+  expect.version = '0.1.2';
+
+  /**
+   * 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) {
+    var msg = this.flags.not ? error : msg
+      , ok = this.flags.not ? !truth : truth;
+
+    if (!ok) {
+      throw new Error(msg.call(this));
+    }
+
+    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' });
+  };
+
+  /**
+   * Assert that the function throws.
+   *
+   * @param {Function|RegExp} callback, or regexp to match error string against
+   * @api public
+   */
+
+  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 ('function' == typeof fn) {
+        fn(e);
+      } else if ('object' == typeof fn) {
+        var subject = 'string' == typeof e ? e : e.message;
+        if (not) {
+          expect(subject).to.not.match(fn);
+        } else {
+          expect(subject).to.match(fn);
+        }
+      }
+      thrown = true;
+    }
+
+    if ('object' == typeof 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(obj, this.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) });
+    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) :
+            '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) {
+    msg = msg || "explicit failure";
+    this.assert(false, msg, msg);
+    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 elemProto = (window.HTMLElement || window.Element).prototype;
+    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');
+      }
+
+      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));
+  };
+
+  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) {
+    if (d instanceof Date) return true;
+    return false;
+  };
+
+  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;
+
+    // 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 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 : {}
+  , 'undefined' != typeof exports ? exports : {}
+);

+ 270 - 0
src/test/assets/mocha.css

@@ -0,0 +1,270 @@
+@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;
+}
+
+/**
+ * (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: calc(100% - 42px); /*(2)*/
+  word-wrap: break-word;
+  border-bottom-color: #ddd;
+  -webkit-border-radius: 3px;
+  -webkit-box-shadow: 0 1px 3px #eee;
+  -moz-border-radius: 3px;
+  -moz-box-shadow: 0 1px 3px #eee;
+  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;
+  -moz-border-radius: 15px;
+  border-radius: 15px;
+  -webkit-transition: opacity 200ms;
+  -moz-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;
+}
+
+#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;
+  }
+}

+ 5726 - 0
src/test/assets/mocha.js

@@ -0,0 +1,5726 @@
+;(function(){
+
+// CommonJS require()
+
+function require(p){
+    var path = require.resolve(p)
+      , mod = require.modules[path];
+    if (!mod) throw new Error('failed to require "' + p + '"');
+    if (!mod.exports) {
+      mod.exports = {};
+      mod.call(mod.exports, mod, mod.exports, require.relative(path));
+    }
+    return mod.exports;
+  }
+
+require.modules = {};
+
+require.resolve = function (path){
+    var orig = path
+      , reg = path + '.js'
+      , index = path + '/index.js';
+    return require.modules[reg] && reg
+      || require.modules[index] && index
+      || orig;
+  };
+
+require.register = function (path, fn){
+    require.modules[path] = fn;
+  };
+
+require.relative = function (parent) {
+    return function(p){
+      if ('.' != p.charAt(0)) return require(p);
+
+      var path = parent.split('/')
+        , segs = p.split('/');
+      path.pop();
+
+      for (var i = 0; i < segs.length; i++) {
+        var seg = segs[i];
+        if ('..' == seg) path.pop();
+        else if ('.' != seg) path.push(seg);
+      }
+
+      return require(path.join('/'));
+    };
+  };
+
+
+require.register("browser/debug.js", function(module, exports, require){
+
+module.exports = function(type){
+  return function(){
+  }
+};
+
+}); // module: browser/debug.js
+
+require.register("browser/diff.js", function(module, exports, require){
+/* See LICENSE file for terms of use */
+
+/*
+ * Text diff implementation.
+ *
+ * This library supports the following APIS:
+ * JsDiff.diffChars: Character by character diff
+ * JsDiff.diffWords: Word (as defined by \b regex) diff which ignores whitespace
+ * JsDiff.diffLines: Line based diff
+ *
+ * JsDiff.diffCss: Diff targeted at CSS content
+ *
+ * These methods are based on the implementation proposed in
+ * "An O(ND) Difference Algorithm and its Variations" (Myers, 1986).
+ * http://citeseerx.ist.psu.edu/viewdoc/summary?doi=10.1.1.4.6927
+ */
+var JsDiff = (function() {
+  /*jshint maxparams: 5*/
+  function clonePath(path) {
+    return { newPos: path.newPos, components: path.components.slice(0) };
+  }
+  function removeEmpty(array) {
+    var ret = [];
+    for (var i = 0; i < array.length; i++) {
+      if (array[i]) {
+        ret.push(array[i]);
+      }
+    }
+    return ret;
+  }
+  function escapeHTML(s) {
+    var n = s;
+    n = n.replace(/&/g, '&amp;');
+    n = n.replace(/</g, '&lt;');
+    n = n.replace(/>/g, '&gt;');
+    n = n.replace(/"/g, '&quot;');
+
+    return n;
+  }
+
+  var Diff = function(ignoreWhitespace) {
+    this.ignoreWhitespace = ignoreWhitespace;
+  };
+  Diff.prototype = {
+      diff: function(oldString, newString) {
+        // Handle the identity case (this is due to unrolling editLength == 0
+        if (newString === oldString) {
+          return [{ value: newString }];
+        }
+        if (!newString) {
+          return [{ value: oldString, removed: true }];
+        }
+        if (!oldString) {
+          return [{ value: newString, added: true }];
+        }
+
+        newString = this.tokenize(newString);
+        oldString = this.tokenize(oldString);
+
+        var newLen = newString.length, oldLen = oldString.length;
+        var maxEditLength = newLen + oldLen;
+        var bestPath = [{ newPos: -1, components: [] }];
+
+        // Seed editLength = 0
+        var oldPos = this.extractCommon(bestPath[0], newString, oldString, 0);
+        if (bestPath[0].newPos+1 >= newLen && oldPos+1 >= oldLen) {
+          return bestPath[0].components;
+        }
+
+        for (var editLength = 1; editLength <= maxEditLength; editLength++) {
+          for (var diagonalPath = -1*editLength; diagonalPath <= editLength; diagonalPath+=2) {
+            var basePath;
+            var addPath = bestPath[diagonalPath-1],
+                removePath = bestPath[diagonalPath+1];
+            oldPos = (removePath ? removePath.newPos : 0) - diagonalPath;
+            if (addPath) {
+              // No one else is going to attempt to use this value, clear it
+              bestPath[diagonalPath-1] = undefined;
+            }
+
+            var canAdd = addPath && addPath.newPos+1 < newLen;
+            var canRemove = removePath && 0 <= oldPos && oldPos < oldLen;
+            if (!canAdd && !canRemove) {
+              bestPath[diagonalPath] = undefined;
+              continue;
+            }
+
+            // Select the diagonal that we want to branch from. We select the prior
+            // path whose position in the new string is the farthest from the origin
+            // and does not pass the bounds of the diff graph
+            if (!canAdd || (canRemove && addPath.newPos < removePath.newPos)) {
+              basePath = clonePath(removePath);
+              this.pushComponent(basePath.components, oldString[oldPos], undefined, true);
+            } else {
+              basePath = clonePath(addPath);
+              basePath.newPos++;
+              this.pushComponent(basePath.components, newString[basePath.newPos], true, undefined);
+            }
+
+            var oldPos = this.extractCommon(basePath, newString, oldString, diagonalPath);
+
+            if (basePath.newPos+1 >= newLen && oldPos+1 >= oldLen) {
+              return basePath.components;
+            } else {
+              bestPath[diagonalPath] = basePath;
+            }
+          }
+        }
+      },
+
+      pushComponent: function(components, value, added, removed) {
+        var last = components[components.length-1];
+        if (last && last.added === added && last.removed === removed) {
+          // We need to clone here as the component clone operation is just
+          // as shallow array clone
+          components[components.length-1] =
+            {value: this.join(last.value, value), added: added, removed: removed };
+        } else {
+          components.push({value: value, added: added, removed: removed });
+        }
+      },
+      extractCommon: function(basePath, newString, oldString, diagonalPath) {
+        var newLen = newString.length,
+            oldLen = oldString.length,
+            newPos = basePath.newPos,
+            oldPos = newPos - diagonalPath;
+        while (newPos+1 < newLen && oldPos+1 < oldLen && this.equals(newString[newPos+1], oldString[oldPos+1])) {
+          newPos++;
+          oldPos++;
+
+          this.pushComponent(basePath.components, newString[newPos], undefined, undefined);
+        }
+        basePath.newPos = newPos;
+        return oldPos;
+      },
+
+      equals: function(left, right) {
+        var reWhitespace = /\S/;
+        if (this.ignoreWhitespace && !reWhitespace.test(left) && !reWhitespace.test(right)) {
+          return true;
+        } else {
+          return left === right;
+        }
+      },
+      join: function(left, right) {
+        return left + right;
+      },
+      tokenize: function(value) {
+        return value;
+      }
+  };
+
+  var CharDiff = new Diff();
+
+  var WordDiff = new Diff(true);
+  var WordWithSpaceDiff = new Diff();
+  WordDiff.tokenize = WordWithSpaceDiff.tokenize = function(value) {
+    return removeEmpty(value.split(/(\s+|\b)/));
+  };
+
+  var CssDiff = new Diff(true);
+  CssDiff.tokenize = function(value) {
+    return removeEmpty(value.split(/([{}:;,]|\s+)/));
+  };
+
+  var LineDiff = new Diff();
+  LineDiff.tokenize = function(value) {
+    return value.split(/^/m);
+  };
+
+  return {
+    Diff: Diff,
+
+    diffChars: function(oldStr, newStr) { return CharDiff.diff(oldStr, newStr); },
+    diffWords: function(oldStr, newStr) { return WordDiff.diff(oldStr, newStr); },
+    diffWordsWithSpace: function(oldStr, newStr) { return WordWithSpaceDiff.diff(oldStr, newStr); },
+    diffLines: function(oldStr, newStr) { return LineDiff.diff(oldStr, newStr); },
+
+    diffCss: function(oldStr, newStr) { return CssDiff.diff(oldStr, newStr); },
+
+    createPatch: function(fileName, oldStr, newStr, oldHeader, newHeader) {
+      var ret = [];
+
+      ret.push('Index: ' + fileName);
+      ret.push('===================================================================');
+      ret.push('--- ' + fileName + (typeof oldHeader === 'undefined' ? '' : '\t' + oldHeader));
+      ret.push('+++ ' + fileName + (typeof newHeader === 'undefined' ? '' : '\t' + newHeader));
+
+      var diff = LineDiff.diff(oldStr, newStr);
+      if (!diff[diff.length-1].value) {
+        diff.pop();   // Remove trailing newline add
+      }
+      diff.push({value: '', lines: []});   // Append an empty value to make cleanup easier
+
+      function contextLines(lines) {
+        return lines.map(function(entry) { return ' ' + entry; });
+      }
+      function eofNL(curRange, i, current) {
+        var last = diff[diff.length-2],
+            isLast = i === diff.length-2,
+            isLastOfType = i === diff.length-3 && (current.added !== last.added || current.removed !== last.removed);
+
+        // Figure out if this is the last line for the given file and missing NL
+        if (!/\n$/.test(current.value) && (isLast || isLastOfType)) {
+          curRange.push('\\ No newline at end of file');
+        }
+      }
+
+      var oldRangeStart = 0, newRangeStart = 0, curRange = [],
+          oldLine = 1, newLine = 1;
+      for (var i = 0; i < diff.length; i++) {
+        var current = diff[i],
+            lines = current.lines || current.value.replace(/\n$/, '').split('\n');
+        current.lines = lines;
+
+        if (current.added || current.removed) {
+          if (!oldRangeStart) {
+            var prev = diff[i-1];
+            oldRangeStart = oldLine;
+            newRangeStart = newLine;
+
+            if (prev) {
+              curRange = contextLines(prev.lines.slice(-4));
+              oldRangeStart -= curRange.length;
+              newRangeStart -= curRange.length;
+            }
+          }
+          curRange.push.apply(curRange, lines.map(function(entry) { return (current.added?'+':'-') + entry; }));
+          eofNL(curRange, i, current);
+
+          if (current.added) {
+            newLine += lines.length;
+          } else {
+            oldLine += lines.length;
+          }
+        } else {
+          if (oldRangeStart) {
+            // Close out any changes that have been output (or join overlapping)
+            if (lines.length <= 8 && i < diff.length-2) {
+              // Overlapping
+              curRange.push.apply(curRange, contextLines(lines));
+            } else {
+              // end the range and output
+              var contextSize = Math.min(lines.length, 4);
+              ret.push(
+                  '@@ -' + oldRangeStart + ',' + (oldLine-oldRangeStart+contextSize)
+                  + ' +' + newRangeStart + ',' + (newLine-newRangeStart+contextSize)
+                  + ' @@');
+              ret.push.apply(ret, curRange);
+              ret.push.apply(ret, contextLines(lines.slice(0, contextSize)));
+              if (lines.length <= 4) {
+                eofNL(ret, i, current);
+              }
+
+              oldRangeStart = 0;  newRangeStart = 0; curRange = [];
+            }
+          }
+          oldLine += lines.length;
+          newLine += lines.length;
+        }
+      }
+
+      return ret.join('\n') + '\n';
+    },
+
+    applyPatch: function(oldStr, uniDiff) {
+      var diffstr = uniDiff.split('\n');
+      var diff = [];
+      var remEOFNL = false,
+          addEOFNL = false;
+
+      for (var i = (diffstr[0][0]==='I'?4:0); i < diffstr.length; i++) {
+        if(diffstr[i][0] === '@') {
+          var meh = diffstr[i].split(/@@ -(\d+),(\d+) \+(\d+),(\d+) @@/);
+          diff.unshift({
+            start:meh[3],
+            oldlength:meh[2],
+            oldlines:[],
+            newlength:meh[4],
+            newlines:[]
+          });
+        } else if(diffstr[i][0] === '+') {
+          diff[0].newlines.push(diffstr[i].substr(1));
+        } else if(diffstr[i][0] === '-') {
+          diff[0].oldlines.push(diffstr[i].substr(1));
+        } else if(diffstr[i][0] === ' ') {
+          diff[0].newlines.push(diffstr[i].substr(1));
+          diff[0].oldlines.push(diffstr[i].substr(1));
+        } else if(diffstr[i][0] === '\\') {
+          if (diffstr[i-1][0] === '+') {
+            remEOFNL = true;
+          } else if(diffstr[i-1][0] === '-') {
+            addEOFNL = true;
+          }
+        }
+      }
+
+      var str = oldStr.split('\n');
+      for (var i = diff.length - 1; i >= 0; i--) {
+        var d = diff[i];
+        for (var j = 0; j < d.oldlength; j++) {
+          if(str[d.start-1+j] !== d.oldlines[j]) {
+            return false;
+          }
+        }
+        Array.prototype.splice.apply(str,[d.start-1,+d.oldlength].concat(d.newlines));
+      }
+
+      if (remEOFNL) {
+        while (!str[str.length-1]) {
+          str.pop();
+        }
+      } else if (addEOFNL) {
+        str.push('');
+      }
+      return str.join('\n');
+    },
+
+    convertChangesToXML: function(changes){
+      var ret = [];
+      for ( var i = 0; i < changes.length; i++) {
+        var change = changes[i];
+        if (change.added) {
+          ret.push('<ins>');
+        } else if (change.removed) {
+          ret.push('<del>');
+        }
+
+        ret.push(escapeHTML(change.value));
+
+        if (change.added) {
+          ret.push('</ins>');
+        } else if (change.removed) {
+          ret.push('</del>');
+        }
+      }
+      return ret.join('');
+    },
+
+    // See: http://code.google.com/p/google-diff-match-patch/wiki/API
+    convertChangesToDMP: function(changes){
+      var ret = [], change;
+      for ( var i = 0; i < changes.length; i++) {
+        change = changes[i];
+        ret.push([(change.added ? 1 : change.removed ? -1 : 0), change.value]);
+      }
+      return ret;
+    }
+  };
+})();
+
+if (typeof module !== 'undefined') {
+    module.exports = JsDiff;
+}
+
+}); // module: browser/diff.js
+
+require.register("browser/events.js", function(module, exports, require){
+
+/**
+ * Module exports.
+ */
+
+exports.EventEmitter = EventEmitter;
+
+/**
+ * Check if `obj` is an array.
+ */
+
+function isArray(obj) {
+  return '[object Array]' == {}.toString.call(obj);
+}
+
+/**
+ * Event emitter constructor.
+ *
+ * @api public
+ */
+
+function EventEmitter(){};
+
+/**
+ * Adds a listener.
+ *
+ * @api public
+ */
+
+EventEmitter.prototype.on = function (name, fn) {
+  if (!this.$events) {
+    this.$events = {};
+  }
+
+  if (!this.$events[name]) {
+    this.$events[name] = fn;
+  } else if (isArray(this.$events[name])) {
+    this.$events[name].push(fn);
+  } else {
+    this.$events[name] = [this.$events[name], fn];
+  }
+
+  return this;
+};
+
+EventEmitter.prototype.addListener = EventEmitter.prototype.on;
+
+/**
+ * Adds a volatile listener.
+ *
+ * @api public
+ */
+
+EventEmitter.prototype.once = function (name, fn) {
+  var self = this;
+
+  function on () {
+    self.removeListener(name, on);
+    fn.apply(this, arguments);
+  };
+
+  on.listener = fn;
+  this.on(name, on);
+
+  return this;
+};
+
+/**
+ * Removes a listener.
+ *
+ * @api public
+ */
+
+EventEmitter.prototype.removeListener = function (name, fn) {
+  if (this.$events && this.$events[name]) {
+    var list = this.$events[name];
+
+    if (isArray(list)) {
+      var pos = -1;
+
+      for (var i = 0, l = list.length; i < l; i++) {
+        if (list[i] === fn || (list[i].listener && list[i].listener === fn)) {
+          pos = i;
+          break;
+        }
+      }
+
+      if (pos < 0) {
+        return this;
+      }
+
+      list.splice(pos, 1);
+
+      if (!list.length) {
+        delete this.$events[name];
+      }
+    } else if (list === fn || (list.listener && list.listener === fn)) {
+      delete this.$events[name];
+    }
+  }
+
+  return this;
+};
+
+/**
+ * Removes all listeners for an event.
+ *
+ * @api public
+ */
+
+EventEmitter.prototype.removeAllListeners = function (name) {
+  if (name === undefined) {
+    this.$events = {};
+    return this;
+  }
+
+  if (this.$events && this.$events[name]) {
+    this.$events[name] = null;
+  }
+
+  return this;
+};
+
+/**
+ * Gets all listeners for a certain event.
+ *
+ * @api public
+ */
+
+EventEmitter.prototype.listeners = function (name) {
+  if (!this.$events) {
+    this.$events = {};
+  }
+
+  if (!this.$events[name]) {
+    this.$events[name] = [];
+  }
+
+  if (!isArray(this.$events[name])) {
+    this.$events[name] = [this.$events[name]];
+  }
+
+  return this.$events[name];
+};
+
+/**
+ * Emits an event.
+ *
+ * @api public
+ */
+
+EventEmitter.prototype.emit = function (name) {
+  if (!this.$events) {
+    return false;
+  }
+
+  var handler = this.$events[name];
+
+  if (!handler) {
+    return false;
+  }
+
+  var args = [].slice.call(arguments, 1);
+
+  if ('function' == typeof handler) {
+    handler.apply(this, args);
+  } else if (isArray(handler)) {
+    var listeners = handler.slice();
+
+    for (var i = 0, l = listeners.length; i < l; i++) {
+      listeners[i].apply(this, args);
+    }
+  } else {
+    return false;
+  }
+
+  return true;
+};
+}); // module: browser/events.js
+
+require.register("browser/fs.js", function(module, exports, require){
+
+}); // module: browser/fs.js
+
+require.register("browser/path.js", function(module, exports, require){
+
+}); // module: browser/path.js
+
+require.register("browser/progress.js", function(module, exports, require){
+
+/**
+ * Expose `Progress`.
+ */
+
+module.exports = Progress;
+
+/**
+ * Initialize a new `Progress` indicator.
+ */
+
+function Progress() {
+  this.percent = 0;
+  this.size(0);
+  this.fontSize(11);
+  this.font('helvetica, arial, sans-serif');
+}
+
+/**
+ * Set progress size to `n`.
+ *
+ * @param {Number} n
+ * @return {Progress} for chaining
+ * @api public
+ */
+
+Progress.prototype.size = function(n){
+  this._size = n;
+  return this;
+};
+
+/**
+ * Set text to `str`.
+ *
+ * @param {String} str
+ * @return {Progress} for chaining
+ * @api public
+ */
+
+Progress.prototype.text = function(str){
+  this._text = str;
+  return this;
+};
+
+/**
+ * Set font size to `n`.
+ *
+ * @param {Number} n
+ * @return {Progress} for chaining
+ * @api public
+ */
+
+Progress.prototype.fontSize = function(n){
+  this._fontSize = n;
+  return this;
+};
+
+/**
+ * Set font `family`.
+ *
+ * @param {String} family
+ * @return {Progress} for chaining
+ */
+
+Progress.prototype.font = function(family){
+  this._font = family;
+  return this;
+};
+
+/**
+ * Update percentage to `n`.
+ *
+ * @param {Number} n
+ * @return {Progress} for chaining
+ */
+
+Progress.prototype.update = function(n){
+  this.percent = n;
+  return this;
+};
+
+/**
+ * Draw on `ctx`.
+ *
+ * @param {CanvasRenderingContext2d} ctx
+ * @return {Progress} for chaining
+ */
+
+Progress.prototype.draw = function(ctx){
+  var percent = Math.min(this.percent, 100)
+    , size = this._size
+    , half = size / 2
+    , x = half
+    , y = half
+    , rad = half - 1
+    , fontSize = this._fontSize;
+
+  ctx.font = fontSize + 'px ' + this._font;
+
+  var angle = Math.PI * 2 * (percent / 100);
+  ctx.clearRect(0, 0, size, size);
+
+  // outer circle
+  ctx.strokeStyle = '#9f9f9f';
+  ctx.beginPath();
+  ctx.arc(x, y, rad, 0, angle, false);
+  ctx.stroke();
+
+  // inner circle
+  ctx.strokeStyle = '#eee';
+  ctx.beginPath();
+  ctx.arc(x, y, rad - 1, 0, angle, true);
+  ctx.stroke();
+
+  // text
+  var text = this._text || (percent | 0) + '%'
+    , w = ctx.measureText(text).width;
+
+  ctx.fillText(
+      text
+    , x - w / 2 + 1
+    , y + fontSize / 2 - 1);
+
+  return this;
+};
+
+}); // module: browser/progress.js
+
+require.register("browser/tty.js", function(module, exports, require){
+
+exports.isatty = function(){
+  return true;
+};
+
+exports.getWindowSize = function(){
+  if ('innerHeight' in global) {
+    return [global.innerHeight, global.innerWidth];
+  } else {
+    // In a Web Worker, the DOM Window is not available.
+    return [640, 480];
+  }
+};
+
+}); // module: browser/tty.js
+
+require.register("context.js", function(module, exports, require){
+
+/**
+ * Expose `Context`.
+ */
+
+module.exports = Context;
+
+/**
+ * Initialize a new `Context`.
+ *
+ * @api private
+ */
+
+function Context(){}
+
+/**
+ * Set or get the context `Runnable` to `runnable`.
+ *
+ * @param {Runnable} runnable
+ * @return {Context}
+ * @api private
+ */
+
+Context.prototype.runnable = function(runnable){
+  if (0 == arguments.length) return this._runnable;
+  this.test = this._runnable = runnable;
+  return this;
+};
+
+/**
+ * Set test timeout `ms`.
+ *
+ * @param {Number} ms
+ * @return {Context} self
+ * @api private
+ */
+
+Context.prototype.timeout = function(ms){
+  this.runnable().timeout(ms);
+  return this;
+};
+
+/**
+ * Set test slowness threshold `ms`.
+ *
+ * @param {Number} ms
+ * @return {Context} self
+ * @api private
+ */
+
+Context.prototype.slow = function(ms){
+  this.runnable().slow(ms);
+  return this;
+};
+
+/**
+ * Inspect the context void of `._runnable`.
+ *
+ * @return {String}
+ * @api private
+ */
+
+Context.prototype.inspect = function(){
+  return JSON.stringify(this, function(key, val){
+    if ('_runnable' == key) return;
+    if ('test' == key) return;
+    return val;
+  }, 2);
+};
+
+}); // module: context.js
+
+require.register("hook.js", function(module, exports, require){
+
+/**
+ * Module dependencies.
+ */
+
+var Runnable = require('./runnable');
+
+/**
+ * Expose `Hook`.
+ */
+
+module.exports = Hook;
+
+/**
+ * Initialize a new `Hook` with the given `title` and callback `fn`.
+ *
+ * @param {String} title
+ * @param {Function} fn
+ * @api private
+ */
+
+function Hook(title, fn) {
+  Runnable.call(this, title, fn);
+  this.type = 'hook';
+}
+
+/**
+ * Inherit from `Runnable.prototype`.
+ */
+
+function F(){};
+F.prototype = Runnable.prototype;
+Hook.prototype = new F;
+Hook.prototype.constructor = Hook;
+
+
+/**
+ * Get or set the test `err`.
+ *
+ * @param {Error} err
+ * @return {Error}
+ * @api public
+ */
+
+Hook.prototype.error = function(err){
+  if (0 == arguments.length) {
+    var err = this._error;
+    this._error = null;
+    return err;
+  }
+
+  this._error = err;
+};
+
+}); // module: hook.js
+
+require.register("interfaces/bdd.js", function(module, exports, require){
+
+/**
+ * Module dependencies.
+ */
+
+var Suite = require('../suite')
+  , Test = require('../test')
+  , utils = require('../utils');
+
+/**
+ * BDD-style interface:
+ *
+ *      describe('Array', function(){
+ *        describe('#indexOf()', function(){
+ *          it('should return -1 when not present', function(){
+ *
+ *          });
+ *
+ *          it('should return the index when present', function(){
+ *
+ *          });
+ *        });
+ *      });
+ *
+ */
+
+module.exports = function(suite){
+  var suites = [suite];
+
+  suite.on('pre-require', function(context, file, mocha){
+
+    /**
+     * Execute before running tests.
+     */
+
+    context.before = function(fn){
+      suites[0].beforeAll(fn);
+    };
+
+    /**
+     * Execute after running tests.
+     */
+
+    context.after = function(fn){
+      suites[0].afterAll(fn);
+    };
+
+    /**
+     * Execute before each test case.
+     */
+
+    context.beforeEach = function(fn){
+      suites[0].beforeEach(fn);
+    };
+
+    /**
+     * Execute after each test case.
+     */
+
+    context.afterEach = function(fn){
+      suites[0].afterEach(fn);
+    };
+
+    /**
+     * Describe a "suite" with the given `title`
+     * and callback `fn` containing nested suites
+     * and/or tests.
+     */
+
+    context.describe = context.context = function(title, fn){
+      var suite = Suite.create(suites[0], title);
+      suites.unshift(suite);
+      fn.call(suite);
+      suites.shift();
+      return suite;
+    };
+
+    /**
+     * Pending describe.
+     */
+
+    context.xdescribe =
+    context.xcontext =
+    context.describe.skip = function(title, fn){
+      var suite = Suite.create(suites[0], title);
+      suite.pending = true;
+      suites.unshift(suite);
+      fn.call(suite);
+      suites.shift();
+    };
+
+    /**
+     * Exclusive suite.
+     */
+
+    context.describe.only = function(title, fn){
+      var suite = context.describe(title, fn);
+      mocha.grep(suite.fullTitle());
+      return suite;
+    };
+
+    /**
+     * Describe a specification or test-case
+     * with the given `title` and callback `fn`
+     * acting as a thunk.
+     */
+
+    context.it = context.specify = function(title, fn){
+      var suite = suites[0];
+      if (suite.pending) var fn = null;
+      var test = new Test(title, fn);
+      suite.addTest(test);
+      return test;
+    };
+
+    /**
+     * Exclusive test-case.
+     */
+
+    context.it.only = function(title, fn){
+      var test = context.it(title, fn);
+      var reString = '^' + utils.escapeRegexp(test.fullTitle()) + '$';
+      mocha.grep(new RegExp(reString));
+      return test;
+    };
+
+    /**
+     * Pending test case.
+     */
+
+    context.xit =
+    context.xspecify =
+    context.it.skip = function(title){
+      context.it(title);
+    };
+  });
+};
+
+}); // module: interfaces/bdd.js
+
+require.register("interfaces/exports.js", function(module, exports, require){
+
+/**
+ * Module dependencies.
+ */
+
+var Suite = require('../suite')
+  , Test = require('../test');
+
+/**
+ * TDD-style interface:
+ *
+ *     exports.Array = {
+ *       '#indexOf()': {
+ *         'should return -1 when the value is not present': function(){
+ *
+ *         },
+ *
+ *         'should return the correct index when the value is present': function(){
+ *
+ *         }
+ *       }
+ *     };
+ *
+ */
+
+module.exports = function(suite){
+  var suites = [suite];
+
+  suite.on('require', visit);
+
+  function visit(obj) {
+    var suite;
+    for (var key in obj) {
+      if ('function' == typeof obj[key]) {
+        var fn = obj[key];
+        switch (key) {
+          case 'before':
+            suites[0].beforeAll(fn);
+            break;
+          case 'after':
+            suites[0].afterAll(fn);
+            break;
+          case 'beforeEach':
+            suites[0].beforeEach(fn);
+            break;
+          case 'afterEach':
+            suites[0].afterEach(fn);
+            break;
+          default:
+            suites[0].addTest(new Test(key, fn));
+        }
+      } else {
+        var suite = Suite.create(suites[0], key);
+        suites.unshift(suite);
+        visit(obj[key]);
+        suites.shift();
+      }
+    }
+  }
+};
+
+}); // module: interfaces/exports.js
+
+require.register("interfaces/index.js", function(module, exports, require){
+
+exports.bdd = require('./bdd');
+exports.tdd = require('./tdd');
+exports.qunit = require('./qunit');
+exports.exports = require('./exports');
+
+}); // module: interfaces/index.js
+
+require.register("interfaces/qunit.js", function(module, exports, require){
+
+/**
+ * Module dependencies.
+ */
+
+var Suite = require('../suite')
+  , Test = require('../test')
+  , utils = require('../utils');
+
+/**
+ * QUnit-style interface:
+ *
+ *     suite('Array');
+ *
+ *     test('#length', function(){
+ *       var arr = [1,2,3];
+ *       ok(arr.length == 3);
+ *     });
+ *
+ *     test('#indexOf()', function(){
+ *       var arr = [1,2,3];
+ *       ok(arr.indexOf(1) == 0);
+ *       ok(arr.indexOf(2) == 1);
+ *       ok(arr.indexOf(3) == 2);
+ *     });
+ *
+ *     suite('String');
+ *
+ *     test('#length', function(){
+ *       ok('foo'.length == 3);
+ *     });
+ *
+ */
+
+module.exports = function(suite){
+  var suites = [suite];
+
+  suite.on('pre-require', function(context, file, mocha){
+
+    /**
+     * Execute before running tests.
+     */
+
+    context.before = function(fn){
+      suites[0].beforeAll(fn);
+    };
+
+    /**
+     * Execute after running tests.
+     */
+
+    context.after = function(fn){
+      suites[0].afterAll(fn);
+    };
+
+    /**
+     * Execute before each test case.
+     */
+
+    context.beforeEach = function(fn){
+      suites[0].beforeEach(fn);
+    };
+
+    /**
+     * Execute after each test case.
+     */
+
+    context.afterEach = function(fn){
+      suites[0].afterEach(fn);
+    };
+
+    /**
+     * Describe a "suite" with the given `title`.
+     */
+
+    context.suite = function(title){
+      if (suites.length > 1) suites.shift();
+      var suite = Suite.create(suites[0], title);
+      suites.unshift(suite);
+      return suite;
+    };
+
+    /**
+     * Exclusive test-case.
+     */
+
+    context.suite.only = function(title, fn){
+      var suite = context.suite(title, fn);
+      mocha.grep(suite.fullTitle());
+    };
+
+    /**
+     * 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);
+      suites[0].addTest(test);
+      return test;
+    };
+
+    /**
+     * Exclusive test-case.
+     */
+
+    context.test.only = function(title, fn){
+      var test = context.test(title, fn);
+      var reString = '^' + utils.escapeRegexp(test.fullTitle()) + '$';
+      mocha.grep(new RegExp(reString));
+    };
+
+    /**
+     * Pending test case.
+     */
+
+    context.test.skip = function(title){
+      context.test(title);
+    };
+  });
+};
+
+}); // module: interfaces/qunit.js
+
+require.register("interfaces/tdd.js", function(module, exports, require){
+
+/**
+ * Module dependencies.
+ */
+
+var Suite = require('../suite')
+  , Test = require('../test')
+  , utils = require('../utils');;
+
+/**
+ * TDD-style interface:
+ *
+ *      suite('Array', function(){
+ *        suite('#indexOf()', function(){
+ *          suiteSetup(function(){
+ *
+ *          });
+ *
+ *          test('should return -1 when not present', function(){
+ *
+ *          });
+ *
+ *          test('should return the index when present', function(){
+ *
+ *          });
+ *
+ *          suiteTeardown(function(){
+ *
+ *          });
+ *        });
+ *      });
+ *
+ */
+
+module.exports = function(suite){
+  var suites = [suite];
+
+  suite.on('pre-require', function(context, file, mocha){
+
+    /**
+     * Execute before each test case.
+     */
+
+    context.setup = function(fn){
+      suites[0].beforeEach(fn);
+    };
+
+    /**
+     * Execute after each test case.
+     */
+
+    context.teardown = function(fn){
+      suites[0].afterEach(fn);
+    };
+
+    /**
+     * Execute before the suite.
+     */
+
+    context.suiteSetup = function(fn){
+      suites[0].beforeAll(fn);
+    };
+
+    /**
+     * Execute after the suite.
+     */
+
+    context.suiteTeardown = function(fn){
+      suites[0].afterAll(fn);
+    };
+
+    /**
+     * Describe a "suite" with the given `title`
+     * and callback `fn` containing nested suites
+     * and/or tests.
+     */
+
+    context.suite = function(title, fn){
+      var suite = Suite.create(suites[0], title);
+      suites.unshift(suite);
+      fn.call(suite);
+      suites.shift();
+      return suite;
+    };
+
+    /**
+     * Pending suite.
+     */
+    context.suite.skip = function(title, fn) {
+      var suite = Suite.create(suites[0], title);
+      suite.pending = true;
+      suites.unshift(suite);
+      fn.call(suite);
+      suites.shift();
+    };
+
+    /**
+     * Exclusive test-case.
+     */
+
+    context.suite.only = function(title, fn){
+      var suite = context.suite(title, fn);
+      mocha.grep(suite.fullTitle());
+    };
+
+    /**
+     * 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.pending) var fn = null;
+      var test = new Test(title, fn);
+      suite.addTest(test);
+      return test;
+    };
+
+    /**
+     * Exclusive test-case.
+     */
+
+    context.test.only = function(title, fn){
+      var test = context.test(title, fn);
+      var reString = '^' + utils.escapeRegexp(test.fullTitle()) + '$';
+      mocha.grep(new RegExp(reString));
+    };
+
+    /**
+     * Pending test case.
+     */
+
+    context.test.skip = function(title){
+      context.test(title);
+    };
+  });
+};
+
+}); // module: interfaces/tdd.js
+
+require.register("mocha.js", function(module, exports, require){
+/*!
+ * mocha
+ * Copyright(c) 2011 TJ Holowaychuk <tj@vision-media.ca>
+ * MIT Licensed
+ */
+
+/**
+ * Module dependencies.
+ */
+
+var path = require('browser/path')
+  , utils = require('./utils');
+
+/**
+ * Expose `Mocha`.
+ */
+
+exports = module.exports = Mocha;
+
+/**
+ * Expose internals.
+ */
+
+exports.utils = utils;
+exports.interfaces = require('./interfaces');
+exports.reporters = require('./reporters');
+exports.Runnable = require('./runnable');
+exports.Context = require('./context');
+exports.Runner = require('./runner');
+exports.Suite = require('./suite');
+exports.Hook = require('./hook');
+exports.Test = require('./test');
+
+/**
+ * Return image `name` path.
+ *
+ * @param {String} name
+ * @return {String}
+ * @api private
+ */
+
+function image(name) {
+  return __dirname + '/../images/' + name + '.png';
+}
+
+/**
+ * Setup mocha with `options`.
+ *
+ * Options:
+ *
+ *   - `ui` name "bdd", "tdd", "exports" etc
+ *   - `reporter` reporter instance, defaults to `mocha.reporters.Dot`
+ *   - `globals` array of accepted globals
+ *   - `timeout` timeout in milliseconds
+ *   - `bail` bail on the first test failure
+ *   - `slow` milliseconds to wait before considering a test slow
+ *   - `ignoreLeaks` ignore global leaks
+ *   - `grep` string or regexp to filter tests with
+ *
+ * @param {Object} options
+ * @api public
+ */
+
+function Mocha(options) {
+  options = options || {};
+  this.files = [];
+  this.options = options;
+  this.grep(options.grep);
+  this.suite = new exports.Suite('', new exports.Context);
+  this.ui(options.ui);
+  this.bail(options.bail);
+  this.reporter(options.reporter);
+  if (null != options.timeout) this.timeout(options.timeout);
+  this.useColors(options.useColors)
+  if (options.slow) this.slow(options.slow);
+}
+
+/**
+ * Enable or disable bailing on the first failure.
+ *
+ * @param {Boolean} [bail]
+ * @api public
+ */
+
+Mocha.prototype.bail = function(bail){
+  if (0 == arguments.length) bail = true;
+  this.suite.bail(bail);
+  return this;
+};
+
+/**
+ * Add test `file`.
+ *
+ * @param {String} file
+ * @api public
+ */
+
+Mocha.prototype.addFile = function(file){
+  this.files.push(file);
+  return this;
+};
+
+/**
+ * Set reporter to `reporter`, defaults to "dot".
+ *
+ * @param {String|Function} reporter name or constructor
+ * @api public
+ */
+
+Mocha.prototype.reporter = function(reporter){
+  if ('function' == typeof reporter) {
+    this._reporter = reporter;
+  } else {
+    reporter = reporter || 'dot';
+    var _reporter;
+    try { _reporter = require('./reporters/' + reporter); } catch (err) {};
+    if (!_reporter) try { _reporter = require(reporter); } catch (err) {};
+    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;
+  }
+  return this;
+};
+
+/**
+ * Set test UI `name`, defaults to "bdd".
+ *
+ * @param {String} bdd
+ * @api public
+ */
+
+Mocha.prototype.ui = function(name){
+  name = name || 'bdd';
+  this._ui = exports.interfaces[name];
+  if (!this._ui) try { this._ui = require(name); } catch (err) {};
+  if (!this._ui) throw new Error('invalid interface "' + name + '"');
+  this._ui = this._ui(this.suite);
+  return this;
+};
+
+/**
+ * Load registered files.
+ *
+ * @api private
+ */
+
+Mocha.prototype.loadFiles = function(fn){
+  var self = this;
+  var suite = this.suite;
+  var pending = this.files.length;
+  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);
+    --pending || (fn && fn());
+  });
+};
+
+/**
+ * Enable growl support.
+ *
+ * @api private
+ */
+
+Mocha.prototype._growl = function(runner, reporter) {
+  var notify = require('growl');
+
+  runner.on('end', function(){
+    var stats = reporter.stats;
+    if (stats.failures) {
+      var msg = stats.failures + ' of ' + runner.total + ' tests failed';
+      notify(msg, { name: 'mocha', title: 'Failed', image: image('error') });
+    } else {
+      notify(stats.passes + ' tests passed in ' + stats.duration + 'ms', {
+          name: 'mocha'
+        , title: 'Passed'
+        , image: image('ok')
+      });
+    }
+  });
+};
+
+/**
+ * Add regexp to grep, if `re` is a string it is escaped.
+ *
+ * @param {RegExp|String} re
+ * @return {Mocha}
+ * @api public
+ */
+
+Mocha.prototype.grep = function(re){
+  this.options.grep = 'string' == typeof re
+    ? new RegExp(utils.escapeRegexp(re))
+    : 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
+ */
+
+Mocha.prototype.ignoreLeaks = function(ignore){
+  this.options.ignoreLeaks = !!ignore;
+  return this;
+};
+
+/**
+ * Enable global leak checking.
+ *
+ * @return {Mocha}
+ * @api public
+ */
+
+Mocha.prototype.checkLeaks = function(){
+  this.options.ignoreLeaks = false;
+  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
+ */
+
+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
+ */
+
+Mocha.prototype.useColors = function(colors){
+  this.options.useColors = arguments.length && colors != undefined
+    ? colors
+    : true;
+  return this;
+};
+
+/**
+ * Use inline diffs rather than +/-.
+ *
+ * @param {Boolean} inlineDiffs
+ * @return {Mocha}
+ * @api public
+ */
+
+Mocha.prototype.useInlineDiffs = function(inlineDiffs) {
+  this.options.useInlineDiffs = arguments.length && inlineDiffs != undefined
+  ? inlineDiffs
+  : false;
+  return this;
+};
+
+/**
+ * Set the timeout in milliseconds.
+ *
+ * @param {Number} timeout
+ * @return {Mocha}
+ * @api public
+ */
+
+Mocha.prototype.timeout = function(timeout){
+  this.suite.timeout(timeout);
+  return this;
+};
+
+/**
+ * Set slowness threshold in milliseconds.
+ *
+ * @param {Number} slow
+ * @return {Mocha}
+ * @api public
+ */
+
+Mocha.prototype.slow = function(slow){
+  this.suite.slow(slow);
+  return this;
+};
+
+/**
+ * Makes all tests async (accepting a callback)
+ *
+ * @return {Mocha}
+ * @api public
+ */
+
+Mocha.prototype.asyncOnly = function(){
+  this.options.asyncOnly = true;
+  return this;
+};
+
+/**
+ * Run tests and invoke `fn()` when complete.
+ *
+ * @param {Function} fn
+ * @return {Runner}
+ * @api public
+ */
+
+Mocha.prototype.run = function(fn){
+  if (this.files.length) this.loadFiles();
+  var suite = this.suite;
+  var options = this.options;
+  var runner = new exports.Runner(suite);
+  var reporter = new this._reporter(runner);
+  runner.ignoreLeaks = false !== options.ignoreLeaks;
+  runner.asyncOnly = options.asyncOnly;
+  if (options.grep) runner.grep(options.grep, options.invert);
+  if (options.globals) runner.globals(options.globals);
+  if (options.growl) this._growl(runner, reporter);
+  exports.reporters.Base.useColors = options.useColors;
+  exports.reporters.Base.inlineDiffs = options.useInlineDiffs;
+  return runner.run(fn);
+};
+
+}); // module: mocha.js
+
+require.register("ms.js", function(module, exports, require){
+/**
+ * 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]
+ *
+ * @param {String|Number} val
+ * @param {Object} options
+ * @return {String|Number}
+ * @api public
+ */
+
+module.exports = function(val, options){
+  options = options || {};
+  if ('string' == typeof val) return parse(val);
+  return options.long ? longFormat(val) : shortFormat(val);
+};
+
+/**
+ * Parse the given `str` and return milliseconds.
+ *
+ * @param {String} str
+ * @return {Number}
+ * @api private
+ */
+
+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;
+  }
+}
+
+/**
+ * Short format for `ms`.
+ *
+ * @param {Number} ms
+ * @return {String}
+ * @api private
+ */
+
+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`.
+ *
+ * @param {Number} ms
+ * @return {String}
+ * @api private
+ */
+
+function longFormat(ms) {
+  return plural(ms, d, 'day')
+    || plural(ms, h, 'hour')
+    || plural(ms, m, 'minute')
+    || plural(ms, s, 'second')
+    || ms + ' ms';
+}
+
+/**
+ * Pluralization helper.
+ */
+
+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';
+}
+
+}); // module: ms.js
+
+require.register("reporters/base.js", function(module, exports, require){
+
+/**
+ * Module dependencies.
+ */
+
+var tty = require('browser/tty')
+  , diff = require('browser/diff')
+  , ms = require('../ms');
+
+/**
+ * Save timer references to avoid Sinon interfering (see GH-237).
+ */
+
+var Date = global.Date
+  , setTimeout = global.setTimeout
+  , setInterval = global.setInterval
+  , clearTimeout = global.clearTimeout
+  , clearInterval = global.clearInterval;
+
+/**
+ * Check if both stdio streams are associated with a tty.
+ */
+
+var isatty = tty.isatty(1) && tty.isatty(2);
+
+/**
+ * Expose `Base`.
+ */
+
+exports = module.exports = Base;
+
+/**
+ * Enable coloring by default.
+ */
+
+exports.useColors = isatty || (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': 42
+  , 'diff removed': 41
+};
+
+/**
+ * Default symbol map.
+ */
+
+exports.symbols = {
+  ok: '✓',
+  err: '✖',
+  dot: '․'
+};
+
+// With node.js on Windows: use symbols available in terminal default fonts
+if ('win32' == process.platform) {
+  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 str;
+  return '\u001b[' + exports.colors[type] + 'm' + str + '\u001b[0m';
+};
+
+/**
+ * Expose term window size, with some
+ * defaults for when stderr is not a tty.
+ */
+
+exports.window = {
+  width: isatty
+    ? process.stdout.getWindowSize
+      ? process.stdout.getWindowSize(1)[0]
+      : tty.getWindowSize()[1]
+    : 75
+};
+
+/**
+ * Expose some basic cursor interactions
+ * that are common among reporters.
+ */
+
+exports.cursor = {
+  hide: function(){
+    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('\n');
+    }
+  }
+};
+
+/**
+ * Outut the given `failures` as a list.
+ *
+ * @param {Array} failures
+ * @api public
+ */
+
+exports.list = function(failures){
+  console.error();
+  failures.forEach(function(test, i){
+    // format
+    var fmt = color('error title', '  %s) %s:\n')
+      + color('error message', '     %s')
+      + color('error stack', '\n%s\n');
+
+    // msg
+    var err = test.err
+      , message = err.message || ''
+      , stack = err.stack || message
+      , index = stack.indexOf(message) + message.length
+      , msg = stack.slice(0, index)
+      , actual = err.actual
+      , expected = err.expected
+      , escape = true;
+
+    // uncaught
+    if (err.uncaught) {
+      msg = 'Uncaught ' + msg;
+    }
+
+    // explicitly show diff
+    if (err.showDiff && sameType(actual, expected)) {
+      escape = false;
+      err.actual = actual = stringify(actual);
+      err.expected = expected = stringify(expected);
+    }
+
+    // actual / expected diff
+    if ('string' == typeof actual && 'string' == typeof expected) {
+      fmt = color('error title', '  %s) %s:\n%s') + color('error stack', '\n%s\n');
+      var match = message.match(/^([^:]+): expected/);
+      msg = match ? '\n      ' + color('error message', match[1]) : '';
+
+      if (exports.inlineDiffs) {
+        msg += inlineDiff(err, escape);
+      } else {
+        msg += unifiedDiff(err, escape);
+      }
+    }
+
+    // indent stack trace without msg
+    stack = stack.slice(index ? index + 1 : index)
+      .replace(/^/gm, '  ');
+
+    console.error(fmt, (i + 1), test.fullTitle(), msg, stack);
+  });
+};
+
+/**
+ * Initialize a new `Base` reporter.
+ *
+ * All other reporters generally
+ * inherit from this reporter, providing
+ * stats such as test duration, number
+ * of tests passed / failed etc.
+ *
+ * @param {Runner} runner
+ * @api public
+ */
+
+function Base(runner) {
+  var self = this
+    , stats = this.stats = { suites: 0, tests: 0, passes: 0, pending: 0, failures: 0 }
+    , failures = this.failures = [];
+
+  if (!runner) return;
+  this.runner = runner;
+
+  runner.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(test){
+    stats.tests = stats.tests || 0;
+    stats.tests++;
+  });
+
+  runner.on('pass', function(test){
+    stats.passes = stats.passes || 0;
+
+    var medium = test.slow() / 2;
+    test.speed = test.duration > test.slow()
+      ? 'slow'
+      : test.duration > medium
+        ? 'medium'
+        : 'fast';
+
+    stats.passes++;
+  });
+
+  runner.on('fail', function(test, err){
+    stats.failures = stats.failures || 0;
+    stats.failures++;
+    test.err = err;
+    failures.push(test);
+  });
+
+  runner.on('end', function(){
+    stats.end = new Date;
+    stats.duration = new Date - stats.start;
+  });
+
+  runner.on('pending', function(){
+    stats.pending++;
+  });
+}
+
+/**
+ * Output common epilogue used by many of
+ * the bundled reporters.
+ *
+ * @api public
+ */
+
+Base.prototype.epilogue = function(){
+  var stats = this.stats;
+  var tests;
+  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.error(fmt,
+      stats.failures);
+
+    Base.list(this.failures);
+    console.error();
+  }
+
+  console.log();
+};
+
+/**
+ * Pad the given `str` to `len`.
+ *
+ * @param {String} str
+ * @param {String} len
+ * @return {String}
+ * @api private
+ */
+
+function pad(str, len) {
+  str = String(str);
+  return Array(len - str.length + 1).join(' ') + str;
+}
+
+
+/**
+ * Returns an inline diff between 2 strings with coloured ANSI output
+ *
+ * @param {Error} Error with actual/expected
+ * @return {String} Diff
+ * @api private
+ */
+
+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 2 strings
+ *
+ * @param {Error} Error with actual/expected
+ * @return {String} Diff
+ * @api private
+ */
+
+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;
+    else return indent + line;
+  }
+  function notBlank(line) {
+    return line != null;
+  }
+  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`.
+ *
+ * @param {Error} err
+ * @return {String}
+ * @api private
+ */
+
+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
+ *
+ * @param {String} line
+ * @return {String}
+ * @api private
+ */
+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`.
+ *
+ * @param {String} name
+ * @param {String} str
+ * @return {String}
+ * @api private
+ */
+
+function colorLines(name, str) {
+  return str.split('\n').map(function(str){
+    return color(name, str);
+  }).join('\n');
+}
+
+/**
+ * Stringify `obj`.
+ *
+ * @param {Mixed} obj
+ * @return {String}
+ * @api private
+ */
+
+function stringify(obj) {
+  if (obj instanceof RegExp) return obj.toString();
+  return JSON.stringify(obj, null, 2);
+}
+
+/**
+ * Check that a / b have the same type.
+ *
+ * @param {Object} a
+ * @param {Object} b
+ * @return {Boolean}
+ * @api private
+ */
+
+function sameType(a, b) {
+  a = Object.prototype.toString.call(a);
+  b = Object.prototype.toString.call(b);
+  return a == b;
+}
+
+
+
+}); // module: reporters/base.js
+
+require.register("reporters/doc.js", function(module, exports, require){
+
+/**
+ * Module dependencies.
+ */
+
+var Base = require('./base')
+  , utils = require('../utils');
+
+/**
+ * Expose `Doc`.
+ */
+
+exports = module.exports = Doc;
+
+/**
+ * Initialize a new `Doc` reporter.
+ *
+ * @param {Runner} runner
+ * @api public
+ */
+
+function Doc(runner) {
+  Base.call(this, runner);
+
+  var self = this
+    , stats = this.stats
+    , total = runner.total
+    , indents = 2;
+
+  function indent() {
+    return Array(indents).join('  ');
+  }
+
+  runner.on('suite', function(suite){
+    if (suite.root) return;
+    ++indents;
+    console.log('%s<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.fn.toString()));
+    console.log('%s  <dd><pre><code>%s</code></pre></dd>', indent(), code);
+  });
+}
+
+}); // module: reporters/doc.js
+
+require.register("reporters/dot.js", function(module, exports, require){
+
+/**
+ * Module dependencies.
+ */
+
+var Base = require('./base')
+  , color = Base.color;
+
+/**
+ * Expose `Dot`.
+ */
+
+exports = module.exports = Dot;
+
+/**
+ * Initialize a new `Dot` matrix test reporter.
+ *
+ * @param {Runner} runner
+ * @api public
+ */
+
+function Dot(runner) {
+  Base.call(this, runner);
+
+  var self = this
+    , stats = this.stats
+    , width = Base.window.width * .75 | 0
+    , n = 0;
+
+  runner.on('start', function(){
+    process.stdout.write('\n  ');
+  });
+
+  runner.on('pending', function(test){
+    process.stdout.write(color('pending', Base.symbols.dot));
+  });
+
+  runner.on('pass', function(test){
+    if (++n % width == 0) process.stdout.write('\n  ');
+    if ('slow' == test.speed) {
+      process.stdout.write(color('bright yellow', Base.symbols.dot));
+    } else {
+      process.stdout.write(color(test.speed, Base.symbols.dot));
+    }
+  });
+
+  runner.on('fail', function(test, err){
+    if (++n % width == 0) process.stdout.write('\n  ');
+    process.stdout.write(color('fail', Base.symbols.dot));
+  });
+
+  runner.on('end', function(){
+    console.log();
+    self.epilogue();
+  });
+}
+
+/**
+ * Inherit from `Base.prototype`.
+ */
+
+function F(){};
+F.prototype = Base.prototype;
+Dot.prototype = new F;
+Dot.prototype.constructor = Dot;
+
+}); // module: reporters/dot.js
+
+require.register("reporters/html-cov.js", function(module, exports, require){
+
+/**
+ * Module dependencies.
+ */
+
+var JSONCov = require('./json-cov')
+  , fs = require('browser/fs');
+
+/**
+ * Expose `HTMLCov`.
+ */
+
+exports = module.exports = HTMLCov;
+
+/**
+ * Initialize a new `JsCoverage` reporter.
+ *
+ * @param {Runner} runner
+ * @api public
+ */
+
+function HTMLCov(runner) {
+  var jade = require('jade')
+    , file = __dirname + '/templates/coverage.jade'
+    , str = fs.readFileSync(file, 'utf8')
+    , fn = jade.compile(str, { filename: file })
+    , self = this;
+
+  JSONCov.call(this, runner, false);
+
+  runner.on('end', function(){
+    process.stdout.write(fn({
+        cov: self.cov
+      , coverageClass: coverageClass
+    }));
+  });
+}
+
+/**
+ * Return coverage class for `n`.
+ *
+ * @return {String}
+ * @api private
+ */
+
+function coverageClass(n) {
+  if (n >= 75) return 'high';
+  if (n >= 50) return 'medium';
+  if (n >= 25) return 'low';
+  return 'terrible';
+}
+}); // module: reporters/html-cov.js
+
+require.register("reporters/html.js", function(module, exports, require){
+
+/**
+ * Module dependencies.
+ */
+
+var Base = require('./base')
+  , utils = require('../utils')
+  , Progress = require('../browser/progress')
+  , escape = utils.escape;
+
+/**
+ * Save timer references to avoid Sinon interfering (see GH-237).
+ */
+
+var Date = global.Date
+  , setTimeout = global.setTimeout
+  , setInterval = global.setInterval
+  , clearTimeout = global.clearTimeout
+  , clearInterval = global.clearInterval;
+
+/**
+ * Expose `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="#">passes:</a> <em>0</em></li>'
+  + '<li class="failures"><a href="#">failures:</a> <em>0</em></li>'
+  + '<li class="duration">duration: <em>0</em>s</li>'
+  + '</ul>';
+
+/**
+ * Initialize a new `HTML` reporter.
+ *
+ * @param {Runner} runner
+ * @api public
+ */
+
+function HTML(runner, root) {
+  Base.call(this, runner);
+
+  var self = this
+    , stats = this.stats
+    , total = runner.total
+    , stat = fragment(statsTemplate)
+    , items = stat.getElementsByTagName('li')
+    , passes = items[1].getElementsByTagName('em')[0]
+    , passesLink = items[1].getElementsByTagName('a')[0]
+    , failures = items[2].getElementsByTagName('em')[0]
+    , failuresLink = items[2].getElementsByTagName('a')[0]
+    , duration = items[3].getElementsByTagName('em')[0]
+    , canvas = stat.getElementsByTagName('canvas')[0]
+    , report = fragment('<ul id="mocha-report"></ul>')
+    , stack = [report]
+    , progress
+    , ctx
+
+  root = 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(){
+    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(){
+    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) return;
+    stack.shift();
+  });
+
+  runner.on('fail', function(test, err){
+    if ('hook' == test.type) runner.emit('test end', test);
+  });
+
+  runner.on('test end', function(test){
+    // TODO: add to stats
+    var percent = stats.tests / this.total * 100 | 0;
+    if (progress) progress.update(percent).draw(ctx);
+
+    // update stats
+    var ms = new Date - stats.start;
+    text(passes, stats.passes);
+    text(failures, stats.failures);
+    text(duration, (ms / 1000).toFixed(2));
+
+    // test
+    if ('passed' == test.state) {
+      var url = self.testURL(test);
+      var el = fragment('<li class="test pass %e"><h2>%e<span class="duration">%ems</span> <a href="%s" class="replay">‣</a></h2></li>', test.speed, test.title, test.duration, url);
+    } else if (test.pending) {
+      var el = fragment('<li class="test pass pending"><h2>%e</h2></li>', test.title);
+    } else {
+      var el = fragment('<li class="test fail"><h2>%e <a href="?grep=%e" class="replay">‣</a></h2></li>', test.title, encodeURIComponent(test.fullTitle()));
+      var str = test.err.stack || test.err.toString();
+
+      // FF / Opera do not add the message
+      if (!~str.indexOf(test.err.message)) {
+        str = test.err.message + '\n' + str;
+      }
+
+      // <=IE7 stringifies to [Object Error]. Since it can be overloaded, we
+      // check for the result of the stringifying.
+      if ('[object Error]' == str) str = test.err.message;
+
+      // Safari doesn't give you a stack. Let's at least provide a source line.
+      if (!test.err.stack && test.err.sourceURL && test.err.line !== undefined) {
+        str += "\n(" + test.err.sourceURL + ":" + test.err.line + ")";
+      }
+
+      el.appendChild(fragment('<pre class="error">%e</pre>', str));
+    }
+
+    // toggle code
+    // TODO: defer
+    if (!test.pending) {
+      var h2 = el.getElementsByTagName('h2')[0];
+
+      on(h2, 'click', function(){
+        pre.style.display = 'none' == pre.style.display
+          ? 'block'
+          : 'none';
+      });
+
+      var pre = fragment('<pre><code>%e</code></pre>', utils.clean(test.fn.toString()));
+      el.appendChild(pre);
+      pre.style.display = 'none';
+    }
+
+    // Don't call .appendChild if #mocha-report was already .shift()'ed off the stack.
+    if (stack[0]) stack[0].appendChild(el);
+  });
+}
+
+/**
+ * Provide suite URL
+ *
+ * @param {Object} [suite]
+ */
+
+HTML.prototype.suiteURL = function(suite){
+  return '?grep=' + encodeURIComponent(suite.fullTitle());
+};
+
+/**
+ * Provide test URL
+ *
+ * @param {Object} [test]
+ */
+
+HTML.prototype.testURL = function(test){
+  return '?grep=' + encodeURIComponent(test.fullTitle());
+};
+
+/**
+ * Display error `msg`.
+ */
+
+function error(msg) {
+  document.body.appendChild(fragment('<div id="mocha-error">%s</div>', msg));
+}
+
+/**
+ * Return a DOM fragment from `html`.
+ */
+
+function fragment(html) {
+  var args = arguments
+    , div = document.createElement('div')
+    , i = 1;
+
+  div.innerHTML = html.replace(/%([se])/g, function(_, type){
+    switch (type) {
+      case 's': return String(args[i++]);
+      case 'e': return escape(args[i++]);
+    }
+  });
+
+  return div.firstChild;
+}
+
+/**
+ * Check for suites that do not have elements
+ * with `classname`, and hide them.
+ */
+
+function hideSuitesWithout(classname) {
+  var suites = document.getElementsByClassName('suite');
+  for (var i = 0; i < suites.length; i++) {
+    var els = suites[i].getElementsByClassName(classname);
+    if (0 == 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 `el` text to `str`.
+ */
+
+function text(el, str) {
+  if (el.textContent) {
+    el.textContent = str;
+  } else {
+    el.innerText = str;
+  }
+}
+
+/**
+ * Listen on `event` with callback `fn`.
+ */
+
+function on(el, event, fn) {
+  if (el.addEventListener) {
+    el.addEventListener(event, fn, false);
+  } else {
+    el.attachEvent('on' + event, fn);
+  }
+}
+
+}); // module: reporters/html.js
+
+require.register("reporters/index.js", function(module, exports, require){
+
+exports.Base = require('./base');
+exports.Dot = require('./dot');
+exports.Doc = require('./doc');
+exports.TAP = require('./tap');
+exports.JSON = require('./json');
+exports.HTML = require('./html');
+exports.List = require('./list');
+exports.Min = require('./min');
+exports.Spec = require('./spec');
+exports.Nyan = require('./nyan');
+exports.XUnit = require('./xunit');
+exports.Markdown = require('./markdown');
+exports.Progress = require('./progress');
+exports.Landing = require('./landing');
+exports.JSONCov = require('./json-cov');
+exports.HTMLCov = require('./html-cov');
+exports.JSONStream = require('./json-stream');
+
+}); // module: reporters/index.js
+
+require.register("reporters/json-cov.js", function(module, exports, require){
+
+/**
+ * Module dependencies.
+ */
+
+var Base = require('./base');
+
+/**
+ * Expose `JSONCov`.
+ */
+
+exports = module.exports = JSONCov;
+
+/**
+ * Initialize a new `JsCoverage` reporter.
+ *
+ * @param {Runner} runner
+ * @param {Boolean} output
+ * @api public
+ */
+
+function JSONCov(runner, output) {
+  var self = this
+    , output = 1 == arguments.length ? true : output;
+
+  Base.call(this, runner);
+
+  var tests = []
+    , failures = []
+    , passes = [];
+
+  runner.on('test end', function(test){
+    tests.push(test);
+  });
+
+  runner.on('pass', function(test){
+    passes.push(test);
+  });
+
+  runner.on('fail', function(test){
+    failures.push(test);
+  });
+
+  runner.on('end', function(){
+    var cov = global._$jscoverage || {};
+    var result = self.cov = map(cov);
+    result.stats = self.stats;
+    result.tests = tests.map(clean);
+    result.failures = failures.map(clean);
+    result.passes = passes.map(clean);
+    if (!output) return;
+    process.stdout.write(JSON.stringify(result, null, 2 ));
+  });
+}
+
+/**
+ * Map jscoverage data to a JSON structure
+ * suitable for reporting.
+ *
+ * @param {Object} cov
+ * @return {Object}
+ * @api private
+ */
+
+function map(cov) {
+  var ret = {
+      instrumentation: 'node-jscoverage'
+    , sloc: 0
+    , hits: 0
+    , misses: 0
+    , coverage: 0
+    , files: []
+  };
+
+  for (var filename in cov) {
+    var data = coverage(filename, cov[filename]);
+    ret.files.push(data);
+    ret.hits += data.hits;
+    ret.misses += data.misses;
+    ret.sloc += data.sloc;
+  }
+
+  ret.files.sort(function(a, b) {
+    return a.filename.localeCompare(b.filename);
+  });
+
+  if (ret.sloc > 0) {
+    ret.coverage = (ret.hits / ret.sloc) * 100;
+  }
+
+  return ret;
+};
+
+/**
+ * Map jscoverage data for a single source file
+ * to a JSON structure suitable for reporting.
+ *
+ * @param {String} filename name of the source file
+ * @param {Object} data jscoverage coverage data
+ * @return {Object}
+ * @api private
+ */
+
+function coverage(filename, data) {
+  var ret = {
+    filename: filename,
+    coverage: 0,
+    hits: 0,
+    misses: 0,
+    sloc: 0,
+    source: {}
+  };
+
+  data.source.forEach(function(line, num){
+    num++;
+
+    if (data[num] === 0) {
+      ret.misses++;
+      ret.sloc++;
+    } else if (data[num] !== undefined) {
+      ret.hits++;
+      ret.sloc++;
+    }
+
+    ret.source[num] = {
+        source: line
+      , coverage: data[num] === undefined
+        ? ''
+        : data[num]
+    };
+  });
+
+  ret.coverage = ret.hits / ret.sloc * 100;
+
+  return ret;
+}
+
+/**
+ * Return a plain-object representation of `test`
+ * free of cyclic properties etc.
+ *
+ * @param {Object} test
+ * @return {Object}
+ * @api private
+ */
+
+function clean(test) {
+  return {
+      title: test.title
+    , fullTitle: test.fullTitle()
+    , duration: test.duration
+  }
+}
+
+}); // module: reporters/json-cov.js
+
+require.register("reporters/json-stream.js", function(module, exports, require){
+
+/**
+ * Module dependencies.
+ */
+
+var Base = require('./base')
+  , color = Base.color;
+
+/**
+ * Expose `List`.
+ */
+
+exports = module.exports = List;
+
+/**
+ * Initialize a new `List` test reporter.
+ *
+ * @param {Runner} runner
+ * @api public
+ */
+
+function List(runner) {
+  Base.call(this, runner);
+
+  var self = this
+    , stats = this.stats
+    , total = runner.total;
+
+  runner.on('start', function(){
+    console.log(JSON.stringify(['start', { total: total }]));
+  });
+
+  runner.on('pass', function(test){
+    console.log(JSON.stringify(['pass', clean(test)]));
+  });
+
+  runner.on('fail', function(test, err){
+    console.log(JSON.stringify(['fail', clean(test)]));
+  });
+
+  runner.on('end', function(){
+    process.stdout.write(JSON.stringify(['end', self.stats]));
+  });
+}
+
+/**
+ * Return a plain-object representation of `test`
+ * free of cyclic properties etc.
+ *
+ * @param {Object} test
+ * @return {Object}
+ * @api private
+ */
+
+function clean(test) {
+  return {
+      title: test.title
+    , fullTitle: test.fullTitle()
+    , duration: test.duration
+  }
+}
+}); // module: reporters/json-stream.js
+
+require.register("reporters/json.js", function(module, exports, require){
+
+/**
+ * Module dependencies.
+ */
+
+var Base = require('./base')
+  , cursor = Base.cursor
+  , color = Base.color;
+
+/**
+ * Expose `JSON`.
+ */
+
+exports = module.exports = JSONReporter;
+
+/**
+ * Initialize a new `JSON` reporter.
+ *
+ * @param {Runner} runner
+ * @api public
+ */
+
+function JSONReporter(runner) {
+  var self = this;
+  Base.call(this, runner);
+
+  var tests = []
+    , failures = []
+    , passes = [];
+
+  runner.on('test end', function(test){
+    tests.push(test);
+  });
+
+  runner.on('pass', function(test){
+    passes.push(test);
+  });
+
+  runner.on('fail', function(test){
+    failures.push(test);
+  });
+
+  runner.on('end', function(){
+    var obj = {
+        stats: self.stats
+      , tests: tests.map(clean)
+      , failures: failures.map(clean)
+      , passes: passes.map(clean)
+    };
+
+    process.stdout.write(JSON.stringify(obj, null, 2));
+  });
+}
+
+/**
+ * Return a plain-object representation of `test`
+ * free of cyclic properties etc.
+ *
+ * @param {Object} test
+ * @return {Object}
+ * @api private
+ */
+
+function clean(test) {
+  return {
+      title: test.title
+    , fullTitle: test.fullTitle()
+    , duration: test.duration
+  }
+}
+}); // module: reporters/json.js
+
+require.register("reporters/landing.js", function(module, exports, require){
+
+/**
+ * Module dependencies.
+ */
+
+var Base = require('./base')
+  , cursor = Base.cursor
+  , color = Base.color;
+
+/**
+ * Expose `Landing`.
+ */
+
+exports = module.exports = Landing;
+
+/**
+ * Airplane color.
+ */
+
+Base.colors.plane = 0;
+
+/**
+ * Airplane crash color.
+ */
+
+Base.colors['plane crash'] = 31;
+
+/**
+ * Runway color.
+ */
+
+Base.colors.runway = 90;
+
+/**
+ * Initialize a new `Landing` reporter.
+ *
+ * @param {Runner} runner
+ * @api public
+ */
+
+function Landing(runner) {
+  Base.call(this, runner);
+
+  var self = this
+    , stats = this.stats
+    , width = Base.window.width * .75 | 0
+    , total = runner.total
+    , stream = process.stdout
+    , plane = color('plane', '✈')
+    , crashed = -1
+    , n = 0;
+
+  function runway() {
+    var buf = Array(width).join('-');
+    return '  ' + color('runway', buf);
+  }
+
+  runner.on('start', function(){
+    stream.write('\n  ');
+    cursor.hide();
+  });
+
+  runner.on('test end', function(test){
+    // check if the plane crashed
+    var col = -1 == crashed
+      ? width * ++n / total | 0
+      : crashed;
+
+    // show the crash
+    if ('failed' == test.state) {
+      plane = color('plane crash', '✈');
+      crashed = col;
+    }
+
+    // render landing strip
+    stream.write('\u001b[4F\n\n');
+    stream.write(runway());
+    stream.write('\n  ');
+    stream.write(color('runway', Array(col).join('⋅')));
+    stream.write(plane)
+    stream.write(color('runway', Array(width - col).join('⋅') + '\n'));
+    stream.write(runway());
+    stream.write('\u001b[0m');
+  });
+
+  runner.on('end', function(){
+    cursor.show();
+    console.log();
+    self.epilogue();
+  });
+}
+
+/**
+ * Inherit from `Base.prototype`.
+ */
+
+function F(){};
+F.prototype = Base.prototype;
+Landing.prototype = new F;
+Landing.prototype.constructor = Landing;
+
+}); // module: reporters/landing.js
+
+require.register("reporters/list.js", function(module, exports, require){
+
+/**
+ * Module dependencies.
+ */
+
+var Base = require('./base')
+  , cursor = Base.cursor
+  , color = Base.color;
+
+/**
+ * Expose `List`.
+ */
+
+exports = module.exports = List;
+
+/**
+ * Initialize a new `List` test reporter.
+ *
+ * @param {Runner} runner
+ * @api public
+ */
+
+function List(runner) {
+  Base.call(this, runner);
+
+  var self = this
+    , stats = this.stats
+    , n = 0;
+
+  runner.on('start', function(){
+    console.log();
+  });
+
+  runner.on('test', function(test){
+    process.stdout.write(color('pass', '    ' + test.fullTitle() + ': '));
+  });
+
+  runner.on('pending', function(test){
+    var fmt = color('checkmark', '  -')
+      + color('pending', ' %s');
+    console.log(fmt, test.fullTitle());
+  });
+
+  runner.on('pass', function(test){
+    var fmt = color('checkmark', '  '+Base.symbols.dot)
+      + color('pass', ' %s: ')
+      + color(test.speed, '%dms');
+    cursor.CR();
+    console.log(fmt, test.fullTitle(), test.duration);
+  });
+
+  runner.on('fail', function(test, err){
+    cursor.CR();
+    console.log(color('fail', '  %d) %s'), ++n, test.fullTitle());
+  });
+
+  runner.on('end', self.epilogue.bind(self));
+}
+
+/**
+ * Inherit from `Base.prototype`.
+ */
+
+function F(){};
+F.prototype = Base.prototype;
+List.prototype = new F;
+List.prototype.constructor = List;
+
+
+}); // module: reporters/list.js
+
+require.register("reporters/markdown.js", function(module, exports, require){
+/**
+ * Module dependencies.
+ */
+
+var Base = require('./base')
+  , utils = require('../utils');
+
+/**
+ * Expose `Markdown`.
+ */
+
+exports = module.exports = Markdown;
+
+/**
+ * Initialize a new `Markdown` reporter.
+ *
+ * @param {Runner} runner
+ * @api public
+ */
+
+function Markdown(runner) {
+  Base.call(this, runner);
+
+  var self = this
+    , stats = this.stats
+    , level = 0
+    , buf = '';
+
+  function title(str) {
+    return Array(level).join('#') + ' ' + str;
+  }
+
+  function indent() {
+    return Array(level).join('  ');
+  }
+
+  function mapTOC(suite, obj) {
+    var ret = obj;
+    obj = obj[suite.title] = obj[suite.title] || { suite: suite };
+    suite.suites.forEach(function(suite){
+      mapTOC(suite, obj);
+    });
+    return ret;
+  }
+
+  function stringifyTOC(obj, level) {
+    ++level;
+    var buf = '';
+    var link;
+    for (var key in obj) {
+      if ('suite' == key) continue;
+      if (key) link = ' - [' + key + '](#' + utils.slug(obj[key].suite.fullTitle()) + ')\n';
+      if (key) buf += Array(level).join('  ') + link;
+      buf += stringifyTOC(obj[key], level);
+    }
+    --level;
+    return buf;
+  }
+
+  function generateTOC(suite) {
+    var obj = mapTOC(suite, {});
+    return stringifyTOC(obj, 0);
+  }
+
+  generateTOC(runner.suite);
+
+  runner.on('suite', function(suite){
+    ++level;
+    var slug = utils.slug(suite.fullTitle());
+    buf += '<a name="' + slug + '"></a>' + '\n';
+    buf += title(suite.title) + '\n';
+  });
+
+  runner.on('suite end', function(suite){
+    --level;
+  });
+
+  runner.on('pass', function(test){
+    var code = utils.clean(test.fn.toString());
+    buf += test.title + '.\n';
+    buf += '\n```js\n';
+    buf += code + '\n';
+    buf += '```\n\n';
+  });
+
+  runner.on('end', function(){
+    process.stdout.write('# TOC\n');
+    process.stdout.write(generateTOC(runner.suite));
+    process.stdout.write(buf);
+  });
+}
+}); // module: reporters/markdown.js
+
+require.register("reporters/min.js", function(module, exports, require){
+
+/**
+ * Module dependencies.
+ */
+
+var Base = require('./base');
+
+/**
+ * Expose `Min`.
+ */
+
+exports = module.exports = Min;
+
+/**
+ * Initialize a new `Min` minimal test reporter (best used with --watch).
+ *
+ * @param {Runner} runner
+ * @api public
+ */
+
+function Min(runner) {
+  Base.call(this, runner);
+
+  runner.on('start', function(){
+    // clear screen
+    process.stdout.write('\u001b[2J');
+    // set cursor position
+    process.stdout.write('\u001b[1;3H');
+  });
+
+  runner.on('end', this.epilogue.bind(this));
+}
+
+/**
+ * Inherit from `Base.prototype`.
+ */
+
+function F(){};
+F.prototype = Base.prototype;
+Min.prototype = new F;
+Min.prototype.constructor = Min;
+
+
+}); // module: reporters/min.js
+
+require.register("reporters/nyan.js", function(module, exports, require){
+/**
+ * Module dependencies.
+ */
+
+var Base = require('./base')
+  , color = Base.color;
+
+/**
+ * Expose `Dot`.
+ */
+
+exports = module.exports = NyanCat;
+
+/**
+ * Initialize a new `Dot` matrix test reporter.
+ *
+ * @param {Runner} runner
+ * @api public
+ */
+
+function NyanCat(runner) {
+  Base.call(this, runner);
+  var self = this
+    , stats = this.stats
+    , width = Base.window.width * .75 | 0
+    , rainbowColors = this.rainbowColors = self.generateColors()
+    , colorIndex = this.colorIndex = 0
+    , numerOfLines = this.numberOfLines = 4
+    , trajectories = this.trajectories = [[], [], [], []]
+    , nyanCatWidth = this.nyanCatWidth = 11
+    , trajectoryWidthMax = this.trajectoryWidthMax = (width - nyanCatWidth)
+    , scoreboardWidth = this.scoreboardWidth = 5
+    , tick = this.tick = 0
+    , n = 0;
+
+  runner.on('start', function(){
+    Base.cursor.hide();
+    self.draw();
+  });
+
+  runner.on('pending', function(test){
+    self.draw();
+  });
+
+  runner.on('pass', function(test){
+    self.draw();
+  });
+
+  runner.on('fail', function(test, err){
+    self.draw();
+  });
+
+  runner.on('end', function(){
+    Base.cursor.show();
+    for (var i = 0; i < self.numberOfLines; i++) write('\n');
+    self.epilogue();
+  });
+}
+
+/**
+ * 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;
+  var colors = Base.colors;
+
+  function draw(color, n) {
+    write(' ');
+    write('\u001b[' + color + 'm' + n + '\u001b[0m');
+    write('\n');
+  }
+
+  draw(colors.green, stats.passes);
+  draw(colors.fail, stats.failures);
+  draw(colors.pending, stats.pending);
+  write('\n');
+
+  this.cursorUp(this.numberOfLines);
+};
+
+/**
+ * Append the rainbow.
+ *
+ * @api private
+ */
+
+NyanCat.prototype.appendRainbow = function(){
+  var segment = this.tick ? '_' : '-';
+  var rainbowified = this.rainbowify(segment);
+
+  for (var index = 0; index < this.numberOfLines; index++) {
+    var trajectory = this.trajectories[index];
+    if (trajectory.length >= this.trajectoryWidthMax) trajectory.shift();
+    trajectory.push(rainbowified);
+  }
+};
+
+/**
+ * Draw the rainbow.
+ *
+ * @api private
+ */
+
+NyanCat.prototype.drawRainbow = function(){
+  var self = this;
+
+  this.trajectories.forEach(function(line, index) {
+    write('\u001b[' + self.scoreboardWidth + 'C');
+    write(line.join(''));
+    write('\n');
+  });
+
+  this.cursorUp(this.numberOfLines);
+};
+
+/**
+ * Draw the nyan cat
+ *
+ * @api private
+ */
+
+NyanCat.prototype.drawNyanCat = function() {
+  var self = this;
+  var startWidth = this.scoreboardWidth + this.trajectories[0].length;
+  var color = '\u001b[' + startWidth + 'C';
+  var padding = '';
+
+  write(color);
+  write('_,------,');
+  write('\n');
+
+  write(color);
+  padding = self.tick ? '  ' : '   ';
+  write('_|' + padding + '/\\_/\\ ');
+  write('\n');
+
+  write(color);
+  padding = self.tick ? '_' : '__';
+  var tail = self.tick ? '~' : '^';
+  var face;
+  write(tail + '|' + padding + this.face() + ' ');
+  write('\n');
+
+  write(color);
+  padding = self.tick ? ' ' : '  ';
+  write(padding + '""  "" ');
+  write('\n');
+
+  this.cursorUp(this.numberOfLines);
+};
+
+/**
+ * Draw nyan cat face.
+ *
+ * @return {String}
+ * @api private
+ */
+
+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 '( ^ .^)';
+  } else {
+    return '( - .-)';
+  }
+}
+
+/**
+ * Move cursor up `n`.
+ *
+ * @param {Number} n
+ * @api private
+ */
+
+NyanCat.prototype.cursorUp = function(n) {
+  write('\u001b[' + n + 'A');
+};
+
+/**
+ * Move cursor down `n`.
+ *
+ * @param {Number} n
+ * @api private
+ */
+
+NyanCat.prototype.cursorDown = function(n) {
+  write('\u001b[' + n + 'B');
+};
+
+/**
+ * Generate rainbow colors.
+ *
+ * @return {Array}
+ * @api private
+ */
+
+NyanCat.prototype.generateColors = function(){
+  var colors = [];
+
+  for (var i = 0; i < (6 * 7); i++) {
+    var pi3 = Math.floor(Math.PI / 3);
+    var n = (i * (1.0 / 6));
+    var r = Math.floor(3 * Math.sin(n) + 3);
+    var g = Math.floor(3 * Math.sin(n + 2 * pi3) + 3);
+    var b = Math.floor(3 * Math.sin(n + 4 * pi3) + 3);
+    colors.push(36 * r + 6 * g + b + 16);
+  }
+
+  return colors;
+};
+
+/**
+ * Apply rainbow to the given `str`.
+ *
+ * @param {String} str
+ * @return {String}
+ * @api private
+ */
+
+NyanCat.prototype.rainbowify = function(str){
+  var color = this.rainbowColors[this.colorIndex % this.rainbowColors.length];
+  this.colorIndex += 1;
+  return '\u001b[38;5;' + color + 'm' + str + '\u001b[0m';
+};
+
+/**
+ * Stdout helper.
+ */
+
+function write(string) {
+  process.stdout.write(string);
+}
+
+/**
+ * Inherit from `Base.prototype`.
+ */
+
+function F(){};
+F.prototype = Base.prototype;
+NyanCat.prototype = new F;
+NyanCat.prototype.constructor = NyanCat;
+
+
+}); // module: reporters/nyan.js
+
+require.register("reporters/progress.js", function(module, exports, require){
+
+/**
+ * Module dependencies.
+ */
+
+var Base = require('./base')
+  , cursor = Base.cursor
+  , color = Base.color;
+
+/**
+ * Expose `Progress`.
+ */
+
+exports = module.exports = Progress;
+
+/**
+ * General progress bar color.
+ */
+
+Base.colors.progress = 90;
+
+/**
+ * Initialize a new `Progress` bar test reporter.
+ *
+ * @param {Runner} runner
+ * @param {Object} options
+ * @api public
+ */
+
+function Progress(runner, options) {
+  Base.call(this, runner);
+
+  var self = this
+    , options = options || {}
+    , stats = this.stats
+    , width = Base.window.width * .50 | 0
+    , total = runner.total
+    , complete = 0
+    , max = Math.max;
+
+  // default chars
+  options.open = options.open || '[';
+  options.complete = options.complete || '▬';
+  options.incomplete = options.incomplete || 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 incomplete = total - complete
+      , percent = complete / total
+      , n = width * percent | 0
+      , i = width - n;
+
+    cursor.CR();
+    process.stdout.write('\u001b[J');
+    process.stdout.write(color('progress', '  ' + options.open));
+    process.stdout.write(Array(n).join(options.complete));
+    process.stdout.write(Array(i).join(options.incomplete));
+    process.stdout.write(color('progress', options.close));
+    if (options.verbose) {
+      process.stdout.write(color('progress', ' ' + complete + ' of ' + total));
+    }
+  });
+
+  // tests are complete, output some stats
+  // and the failures if any
+  runner.on('end', function(){
+    cursor.show();
+    console.log();
+    self.epilogue();
+  });
+}
+
+/**
+ * Inherit from `Base.prototype`.
+ */
+
+function F(){};
+F.prototype = Base.prototype;
+Progress.prototype = new F;
+Progress.prototype.constructor = Progress;
+
+
+}); // module: reporters/progress.js
+
+require.register("reporters/spec.js", function(module, exports, require){
+
+/**
+ * Module dependencies.
+ */
+
+var Base = require('./base')
+  , cursor = Base.cursor
+  , color = Base.color;
+
+/**
+ * Expose `Spec`.
+ */
+
+exports = module.exports = Spec;
+
+/**
+ * Initialize a new `Spec` test reporter.
+ *
+ * @param {Runner} runner
+ * @api public
+ */
+
+function Spec(runner) {
+  Base.call(this, runner);
+
+  var self = this
+    , stats = this.stats
+    , indents = 0
+    , n = 0;
+
+  function indent() {
+    return Array(indents).join('  ')
+  }
+
+  runner.on('start', function(){
+    console.log();
+  });
+
+  runner.on('suite', function(suite){
+    ++indents;
+    console.log(color('suite', '%s%s'), indent(), suite.title);
+  });
+
+  runner.on('suite end', function(suite){
+    --indents;
+    if (1 == indents) console.log();
+  });
+
+  runner.on('pending', function(test){
+    var fmt = indent() + color('pending', '  - %s');
+    console.log(fmt, test.title);
+  });
+
+  runner.on('pass', function(test){
+    if ('fast' == test.speed) {
+      var fmt = indent()
+        + color('checkmark', '  ' + Base.symbols.ok)
+        + color('pass', ' %s ');
+      cursor.CR();
+      console.log(fmt, test.title);
+    } else {
+      var fmt = indent()
+        + color('checkmark', '  ' + Base.symbols.ok)
+        + color('pass', ' %s ')
+        + color(test.speed, '(%dms)');
+      cursor.CR();
+      console.log(fmt, test.title, test.duration);
+    }
+  });
+
+  runner.on('fail', function(test, err){
+    cursor.CR();
+    console.log(indent() + color('fail', '  %d) %s'), ++n, test.title);
+  });
+
+  runner.on('end', self.epilogue.bind(self));
+}
+
+/**
+ * Inherit from `Base.prototype`.
+ */
+
+function F(){};
+F.prototype = Base.prototype;
+Spec.prototype = new F;
+Spec.prototype.constructor = Spec;
+
+
+}); // module: reporters/spec.js
+
+require.register("reporters/tap.js", function(module, exports, require){
+
+/**
+ * Module dependencies.
+ */
+
+var Base = require('./base')
+  , cursor = Base.cursor
+  , color = Base.color;
+
+/**
+ * Expose `TAP`.
+ */
+
+exports = module.exports = TAP;
+
+/**
+ * Initialize a new `TAP` reporter.
+ *
+ * @param {Runner} runner
+ * @api public
+ */
+
+function TAP(runner) {
+  Base.call(this, runner);
+
+  var self = this
+    , stats = this.stats
+    , n = 1
+    , passes = 0
+    , 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`
+ *
+ * @param {Object} test
+ * @return {String}
+ * @api private
+ */
+
+function title(test) {
+  return test.fullTitle().replace(/#/g, '');
+}
+
+}); // module: reporters/tap.js
+
+require.register("reporters/xunit.js", function(module, exports, require){
+
+/**
+ * Module dependencies.
+ */
+
+var Base = require('./base')
+  , utils = require('../utils')
+  , escape = utils.escape;
+
+/**
+ * Save timer references to avoid Sinon interfering (see GH-237).
+ */
+
+var Date = global.Date
+  , setTimeout = global.setTimeout
+  , setInterval = global.setInterval
+  , clearTimeout = global.clearTimeout
+  , clearInterval = global.clearInterval;
+
+/**
+ * Expose `XUnit`.
+ */
+
+exports = module.exports = XUnit;
+
+/**
+ * Initialize a new `XUnit` reporter.
+ *
+ * @param {Runner} runner
+ * @api public
+ */
+
+function XUnit(runner) {
+  Base.call(this, runner);
+  var stats = this.stats
+    , tests = []
+    , self = this;
+
+  runner.on('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(){
+    process.stdout.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(test);
+    process.stdout.write('</testsuite>');
+  });
+}
+
+/**
+ * Inherit from `Base.prototype`.
+ */
+
+function F(){};
+F.prototype = Base.prototype;
+XUnit.prototype = new F;
+XUnit.prototype.constructor = XUnit;
+
+
+/**
+ * Output tag for the given `test.`
+ */
+
+function test(test) {
+  var attrs = {
+      classname: test.parent.fullTitle()
+    , name: test.title
+    , time: (test.duration / 1000) || 0
+  };
+
+  if ('failed' == test.state) {
+    var err = test.err;
+    attrs.message = escape(err.message);
+    process.stdout.write(tag('testcase', attrs, false, tag('failure', attrs, false, cdata(err.stack))));
+  } else if (test.pending) {
+    process.stdout.write(tag('testcase', attrs, false, tag('skipped', {}, true)));
+  } else {
+    process.stdout.write(tag('testcase', attrs, true) );
+  }
+}
+
+/**
+ * HTML tag helper.
+ */
+
+function tag(name, attrs, close, content) {
+  var end = close ? '/>' : '>'
+    , pairs = []
+    , tag;
+
+  for (var key in attrs) {
+    pairs.push(key + '="' + escape(attrs[key]) + '"');
+  }
+
+  tag = '<' + name + (pairs.length ? ' ' + pairs.join(' ') : '') + end;
+  if (content) tag += content + '</' + name + end;
+  return tag;
+}
+
+/**
+ * Return cdata escaped CDATA `str`.
+ */
+
+function cdata(str) {
+  return '<![CDATA[' + escape(str) + ']]>';
+}
+
+}); // module: reporters/xunit.js
+
+require.register("runnable.js", function(module, exports, require){
+
+/**
+ * Module dependencies.
+ */
+
+var EventEmitter = require('browser/events').EventEmitter
+  , debug = require('browser/debug')('mocha:runnable')
+  , milliseconds = require('./ms');
+
+/**
+ * Save timer references to avoid Sinon interfering (see GH-237).
+ */
+
+var Date = global.Date
+  , setTimeout = global.setTimeout
+  , setInterval = global.setInterval
+  , clearTimeout = global.clearTimeout
+  , clearInterval = global.clearInterval;
+
+/**
+ * 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
+ */
+
+function Runnable(title, fn) {
+  this.title = title;
+  this.fn = fn;
+  this.async = fn && fn.length;
+  this.sync = ! this.async;
+  this._timeout = 2000;
+  this._slow = 75;
+  this.timedOut = false;
+}
+
+/**
+ * Inherit from `EventEmitter.prototype`.
+ */
+
+function F(){};
+F.prototype = EventEmitter.prototype;
+Runnable.prototype = new F;
+Runnable.prototype.constructor = Runnable;
+
+
+/**
+ * Set & get timeout `ms`.
+ *
+ * @param {Number|String} ms
+ * @return {Runnable|Number} ms or self
+ * @api private
+ */
+
+Runnable.prototype.timeout = function(ms){
+  if (0 == arguments.length) return this._timeout;
+  if ('string' == typeof ms) ms = milliseconds(ms);
+  debug('timeout %d', ms);
+  this._timeout = ms;
+  if (this.timer) this.resetTimeout();
+  return this;
+};
+
+/**
+ * Set & get slow `ms`.
+ *
+ * @param {Number|String} ms
+ * @return {Runnable|Number} ms or self
+ * @api private
+ */
+
+Runnable.prototype.slow = function(ms){
+  if (0 === arguments.length) return this._slow;
+  if ('string' == typeof ms) ms = milliseconds(ms);
+  debug('timeout %d', ms);
+  this._slow = ms;
+  return this;
+};
+
+/**
+ * Return the full title generated by recursively
+ * concatenating the parent's full title.
+ *
+ * @return {String}
+ * @api public
+ */
+
+Runnable.prototype.fullTitle = function(){
+  return this.parent.fullTitle() + ' ' + this.title;
+};
+
+/**
+ * Clear the timeout.
+ *
+ * @api private
+ */
+
+Runnable.prototype.clearTimeout = function(){
+  clearTimeout(this.timer);
+};
+
+/**
+ * Inspect the runnable void of private properties.
+ *
+ * @return {String}
+ * @api private
+ */
+
+Runnable.prototype.inspect = function(){
+  return JSON.stringify(this, function(key, val){
+    if ('_' == key[0]) return;
+    if ('parent' == key) return '#<Suite>';
+    if ('ctx' == key) return '#<Context>';
+    return val;
+  }, 2);
+};
+
+/**
+ * Reset the timeout.
+ *
+ * @api private
+ */
+
+Runnable.prototype.resetTimeout = function(){
+  var self = this;
+  var ms = this.timeout() || 1e9;
+
+  this.clearTimeout();
+  this.timer = setTimeout(function(){
+    self.callback(new Error('timeout of ' + ms + 'ms exceeded'));
+    self.timedOut = true;
+  }, ms);
+};
+
+/**
+ * Whitelist these globals for this test run
+ *
+ * @api private
+ */
+Runnable.prototype.globals = function(arr){
+  var self = this;
+  this._allowedGlobals = arr;
+};
+
+/**
+ * Run the test and invoke `fn(err)`.
+ *
+ * @param {Function} fn
+ * @api private
+ */
+
+Runnable.prototype.run = function(fn){
+  var self = this
+    , ms = this.timeout()
+    , start = new Date
+    , ctx = this.ctx
+    , finished
+    , emitted;
+
+  if (ctx) ctx.runnable(this);
+
+  // timeout
+  if (this.async) {
+    if (ms) {
+      this.timer = setTimeout(function(){
+        done(new Error('timeout of ' + ms + 'ms exceeded'));
+        self.timedOut = true;
+      }, ms);
+    }
+  }
+
+  // called multiple times
+  function multiple(err) {
+    if (emitted) return;
+    emitted = true;
+    self.emit('error', err || new Error('done() called multiple times'));
+  }
+
+  // finished
+  function done(err) {
+    if (self.timedOut) return;
+    if (finished) return multiple(err);
+    self.clearTimeout();
+    self.duration = new Date - start;
+    finished = true;
+    fn(err);
+  }
+
+  // for .resetTimeout()
+  this.callback = done;
+
+  // async
+  if (this.async) {
+    try {
+      this.fn.call(ctx, function(err){
+        if (err instanceof Error || toString.call(err) === "[object Error]") return done(err);
+        if (null != err) return done(new Error('done() invoked with non-Error: ' + err));
+        done();
+      });
+    } catch (err) {
+      done(err);
+    }
+    return;
+  }
+
+  if (this.asyncOnly) {
+    return done(new Error('--async-only option in use without declaring `done()`'));
+  }
+
+  // sync
+  try {
+    if (!this.pending) this.fn.call(ctx);
+    this.duration = new Date - start;
+    fn();
+  } catch (err) {
+    fn(err);
+  }
+};
+
+}); // module: runnable.js
+
+require.register("runner.js", function(module, exports, require){
+/**
+ * Module dependencies.
+ */
+
+var EventEmitter = require('browser/events').EventEmitter
+  , debug = require('browser/debug')('mocha:runner')
+  , Test = require('./test')
+  , utils = require('./utils')
+  , filter = utils.filter
+  , keys = utils.keys;
+
+/**
+ * Non-enumerable globals.
+ */
+
+var globals = [
+  'setTimeout',
+  'clearTimeout',
+  'setInterval',
+  'clearInterval',
+  'XMLHttpRequest',
+  'Date'
+];
+
+/**
+ * Expose `Runner`.
+ */
+
+module.exports = Runner;
+
+/**
+ * Initialize a `Runner` for the given `suite`.
+ *
+ * Events:
+ *
+ *   - `start`  execution started
+ *   - `end`  execution complete
+ *   - `suite`  (suite) test suite execution started
+ *   - `suite end`  (suite) all tests (and sub-suites) have finished
+ *   - `test`  (test) test execution started
+ *   - `test end`  (test) test completed
+ *   - `hook`  (hook) hook execution started
+ *   - `hook end`  (hook) hook complete
+ *   - `pass`  (test) test passed
+ *   - `fail`  (test, err) test failed
+ *   - `pending`  (test) test pending
+ *
+ * @api public
+ */
+
+function Runner(suite) {
+  var self = this;
+  this._globals = [];
+  this._abort = false;
+  this.suite = suite;
+  this.total = suite.total();
+  this.failures = 0;
+  this.on('test end', function(test){ self.checkGlobals(test); });
+  this.on('hook end', function(hook){ self.checkGlobals(hook); });
+  this.grep(/.*/);
+  this.globals(this.globalProps().concat(['errno']));
+}
+
+/**
+ * Wrapper for setImmediate, process.nextTick, or browser polyfill.
+ *
+ * @param {Function} fn
+ * @api private
+ */
+
+Runner.immediately = global.setImmediate || process.nextTick;
+
+/**
+ * Inherit from `EventEmitter.prototype`.
+ */
+
+function F(){};
+F.prototype = EventEmitter.prototype;
+Runner.prototype = new F;
+Runner.prototype.constructor = Runner;
+
+
+/**
+ * Run tests with full titles matching `re`. Updates runner.total
+ * with number of tests matched.
+ *
+ * @param {RegExp} re
+ * @param {Boolean} invert
+ * @return {Runner} for chaining
+ * @api public
+ */
+
+Runner.prototype.grep = function(re, invert){
+  debug('grep %s', re);
+  this._grep = re;
+  this._invert = invert;
+  this.total = this.grepTotal(this.suite);
+  return this;
+};
+
+/**
+ * Returns the number of tests matching the grep search for the
+ * given suite.
+ *
+ * @param {Suite} suite
+ * @return {Number}
+ * @api public
+ */
+
+Runner.prototype.grepTotal = function(suite) {
+  var self = this;
+  var total = 0;
+
+  suite.eachTest(function(test){
+    var match = self._grep.test(test.fullTitle());
+    if (self._invert) match = !match;
+    if (match) total++;
+  });
+
+  return total;
+};
+
+/**
+ * Return a list of global properties.
+ *
+ * @return {Array}
+ * @api private
+ */
+
+Runner.prototype.globalProps = function() {
+  var props = utils.keys(global);
+
+  // non-enumerables
+  for (var i = 0; i < globals.length; ++i) {
+    if (~utils.indexOf(props, globals[i])) continue;
+    props.push(globals[i]);
+  }
+
+  return props;
+};
+
+/**
+ * Allow the given `arr` of globals.
+ *
+ * @param {Array} arr
+ * @return {Runner} for chaining
+ * @api public
+ */
+
+Runner.prototype.globals = function(arr){
+  if (0 == arguments.length) return this._globals;
+  debug('globals %j', arr);
+  this._globals = this._globals.concat(arr);
+  return this;
+};
+
+/**
+ * Check for global variable leaks.
+ *
+ * @api private
+ */
+
+Runner.prototype.checkGlobals = function(test){
+  if (this.ignoreLeaks) return;
+  var ok = this._globals;
+
+  var globals = this.globalProps();
+  var isNode = process.kill;
+  var leaks;
+
+  if (test) {
+    ok = ok.concat(test._allowedGlobals || []);
+  }
+
+  // check length - 2 ('errno' and 'location' globals)
+  if (isNode && 1 == ok.length - globals.length) return;
+  else if (2 == ok.length - globals.length) return;
+
+  if(this.prevGlobalsLength == globals.length) return;
+  this.prevGlobalsLength = globals.length;
+
+  leaks = filterLeaks(ok, globals);
+  this._globals = this._globals.concat(leaks);
+
+  if (leaks.length > 1) {
+    this.fail(test, new Error('global leaks detected: ' + leaks.join(', ') + ''));
+  } else if (leaks.length) {
+    this.fail(test, new Error('global leak detected: ' + leaks[0]));
+  }
+};
+
+/**
+ * Fail the given `test`.
+ *
+ * @param {Test} test
+ * @param {Error} err
+ * @api private
+ */
+
+Runner.prototype.fail = function(test, err){
+  ++this.failures;
+  test.state = 'failed';
+
+  if ('string' == typeof err) {
+    err = new Error('the string "' + err + '" was thrown, throw an Error :)');
+  }
+
+  this.emit('fail', test, err);
+};
+
+/**
+ * Fail the given `hook` with `err`.
+ *
+ * Hook failures work in the following pattern:
+ * - If bail, then exit
+ * - Failed `before` hook skips all tests in a suite and subsuites,
+ *   but jumps to corresponding `after` hook
+ * - Failed `before each` hook skips remaining tests in a
+ *   suite and jumps to corresponding `after each` hook,
+ *   which is run only once
+ * - Failed `after` hook does not alter
+ *   execution order
+ * - Failed `after each` hook skips remaining tests in a
+ *   suite and subsuites, but executes other `after each`
+ *   hooks
+ *
+ * @param {Hook} hook
+ * @param {Error} err
+ * @api private
+ */
+
+Runner.prototype.failHook = function(hook, err){
+  this.fail(hook, err);
+  if (this.suite.bail()) {
+    this.emit('end');
+  }
+};
+
+/**
+ * Run hook `name` callbacks and then invoke `fn()`.
+ *
+ * @param {String} name
+ * @param {Function} function
+ * @api private
+ */
+
+Runner.prototype.hook = function(name, fn){
+  var suite = this.suite
+    , hooks = suite['_' + name]
+    , self = this
+    , timer;
+
+  function next(i) {
+    var hook = hooks[i];
+    if (!hook) return fn();
+    if (self.failures && suite.bail()) return fn();
+    self.currentRunnable = hook;
+
+    hook.ctx.currentTest = self.test;
+
+    self.emit('hook', hook);
+
+    hook.on('error', function(err){
+      self.failHook(hook, err);
+    });
+
+    hook.run(function(err){
+      hook.removeAllListeners('error');
+      var testError = hook.error();
+      if (testError) self.fail(self.test, testError);
+      if (err) {
+        self.failHook(hook, err);
+
+        // stop executing hooks, notify callee of hook err
+        return fn(err);
+      }
+      self.emit('hook end', hook);
+      delete hook.ctx.currentTest;
+      next(++i);
+    });
+  }
+
+  Runner.immediately(function(){
+    next(0);
+  });
+};
+
+/**
+ * Run hook `name` for the given array of `suites`
+ * in order, and callback `fn(err, errSuite)`.
+ *
+ * @param {String} name
+ * @param {Array} suites
+ * @param {Function} fn
+ * @api private
+ */
+
+Runner.prototype.hooks = function(name, suites, fn){
+  var self = this
+    , orig = this.suite;
+
+  function next(suite) {
+    self.suite = suite;
+
+    if (!suite) {
+      self.suite = orig;
+      return fn();
+    }
+
+    self.hook(name, function(err){
+      if (err) {
+        var errSuite = self.suite;
+        self.suite = orig;
+        return fn(err, errSuite);
+      }
+
+      next(suites.pop());
+    });
+  }
+
+  next(suites.pop());
+};
+
+/**
+ * Run hooks from the top level down.
+ *
+ * @param {String} name
+ * @param {Function} fn
+ * @api private
+ */
+
+Runner.prototype.hookUp = function(name, fn){
+  var suites = [this.suite].concat(this.parents()).reverse();
+  this.hooks(name, suites, fn);
+};
+
+/**
+ * Run hooks from the bottom up.
+ *
+ * @param {String} name
+ * @param {Function} fn
+ * @api private
+ */
+
+Runner.prototype.hookDown = function(name, fn){
+  var suites = [this.suite].concat(this.parents());
+  this.hooks(name, suites, fn);
+};
+
+/**
+ * Return an array of parent Suites from
+ * closest to furthest.
+ *
+ * @return {Array}
+ * @api private
+ */
+
+Runner.prototype.parents = function(){
+  var suite = this.suite
+    , suites = [];
+  while (suite = suite.parent) suites.push(suite);
+  return suites;
+};
+
+/**
+ * Run the current test and callback `fn(err)`.
+ *
+ * @param {Function} fn
+ * @api private
+ */
+
+Runner.prototype.runTest = function(fn){
+  var test = this.test
+    , self = this;
+
+  if (this.asyncOnly) test.asyncOnly = true;
+
+  try {
+    test.on('error', function(err){
+      self.fail(test, err);
+    });
+    test.run(fn);
+  } catch (err) {
+    fn(err);
+  }
+};
+
+/**
+ * Run tests in the given `suite` and invoke
+ * the callback `fn()` when complete.
+ *
+ * @param {Suite} suite
+ * @param {Function} fn
+ * @api private
+ */
+
+Runner.prototype.runTests = function(suite, fn){
+  var self = this
+    , tests = suite.tests.slice()
+    , test;
+
+
+  function hookErr(err, errSuite, after) {
+    // before/after Each hook for errSuite failed:
+    var orig = self.suite;
+
+    // for failed 'after each' hook start from errSuite parent,
+    // otherwise start from errSuite itself
+    self.suite = after ? errSuite.parent : errSuite;
+
+    if (self.suite) {
+      // call hookUp afterEach
+      self.hookUp('afterEach', function(err2, errSuite2) {
+        self.suite = orig;
+        // some hooks may fail even now
+        if (err2) return hookErr(err2, errSuite2, true);
+        // report error suite
+        fn(errSuite);
+      });
+    } else {
+      // there is no need calling other 'after each' hooks
+      self.suite = orig;
+      fn(errSuite);
+    }
+  }
+
+  function next(err, errSuite) {
+    // if we bail after first err
+    if (self.failures && suite._bail) return fn();
+
+    if (self._abort) return fn();
+
+    if (err) return hookErr(err, errSuite, true);
+
+    // next test
+    test = tests.shift();
+
+    // all done
+    if (!test) return fn();
+
+    // grep
+    var match = self._grep.test(test.fullTitle());
+    if (self._invert) match = !match;
+    if (!match) return next();
+
+    // pending
+    if (test.pending) {
+      self.emit('pending', test);
+      self.emit('test end', test);
+      return next();
+    }
+
+    // execute test and hook(s)
+    self.emit('test', self.test = test);
+    self.hookDown('beforeEach', function(err, errSuite){
+
+      if (err) return hookErr(err, errSuite, false);
+
+      self.currentRunnable = self.test;
+      self.runTest(function(err){
+        test = self.test;
+
+        if (err) {
+          self.fail(test, err);
+          self.emit('test end', test);
+          return self.hookUp('afterEach', next);
+        }
+
+        test.state = 'passed';
+        self.emit('pass', test);
+        self.emit('test end', test);
+        self.hookUp('afterEach', next);
+      });
+    });
+  }
+
+  this.next = next;
+  next();
+};
+
+/**
+ * Run the given `suite` and invoke the
+ * callback `fn()` when complete.
+ *
+ * @param {Suite} suite
+ * @param {Function} fn
+ * @api private
+ */
+
+Runner.prototype.runSuite = function(suite, fn){
+  var total = this.grepTotal(suite)
+    , self = this
+    , i = 0;
+
+  debug('run suite %s', suite.fullTitle());
+
+  if (!total) return fn();
+
+  this.emit('suite', this.suite = suite);
+
+  function next(errSuite) {
+    if (errSuite) {
+      // current suite failed on a hook from errSuite
+      if (errSuite == suite) {
+        // if errSuite is current suite
+        // continue to the next sibling suite
+        return done();
+      } else {
+        // errSuite is among the parents of current suite
+        // stop execution of errSuite and all sub-suites
+        return done(errSuite);
+      }
+    }
+
+    if (self._abort) return done();
+
+    var curr = suite.suites[i++];
+    if (!curr) return done();
+    self.runSuite(curr, next);
+  }
+
+  function done(errSuite) {
+    self.suite = suite;
+    self.hook('afterAll', function(){
+      self.emit('suite end', suite);
+      fn(errSuite);
+    });
+  }
+
+  this.hook('beforeAll', function(err){
+    if (err) return done();
+    self.runTests(suite, next);
+  });
+};
+
+/**
+ * Handle uncaught exceptions.
+ *
+ * @param {Error} err
+ * @api private
+ */
+
+Runner.prototype.uncaught = function(err){
+  debug('uncaught exception %s', err.message);
+  var runnable = this.currentRunnable;
+  if (!runnable || 'failed' == runnable.state) return;
+  runnable.clearTimeout();
+  err.uncaught = true;
+  this.fail(runnable, err);
+
+  // recover from test
+  if ('test' == runnable.type) {
+    this.emit('test end', runnable);
+    this.hookUp('afterEach', this.next);
+    return;
+  }
+
+  // bail on hooks
+  this.emit('end');
+};
+
+/**
+ * Run the root suite and invoke `fn(failures)`
+ * on completion.
+ *
+ * @param {Function} fn
+ * @return {Runner} for chaining
+ * @api public
+ */
+
+Runner.prototype.run = function(fn){
+  var self = this
+    , fn = fn || function(){};
+
+  function uncaught(err){
+    self.uncaught(err);
+  }
+
+  debug('start');
+
+  // callback
+  this.on('end', function(){
+    debug('end');
+    process.removeListener('uncaughtException', uncaught);
+    fn(self.failures);
+  });
+
+  // run suites
+  this.emit('start');
+  this.runSuite(this.suite, function(){
+    debug('finished running');
+    self.emit('end');
+  });
+
+  // uncaught exception
+  process.on('uncaughtException', uncaught);
+
+  return this;
+};
+
+/**
+ * Cleanly abort execution
+ *
+ * @return {Runner} for chaining
+ * @api public
+ */
+Runner.prototype.abort = function(){
+  debug('aborting');
+  this._abort = true;
+}
+
+/**
+ * Filter leaks with the given globals flagged as `ok`.
+ *
+ * @param {Array} ok
+ * @param {Array} globals
+ * @return {Array}
+ * @api private
+ */
+
+function filterLeaks(ok, globals) {
+  return filter(globals, function(key){
+    // Firefox and Chrome exposes iframes as index inside the window object
+    if (/^d+/.test(key)) return false;
+
+    // in firefox
+    // if runner runs in an iframe, this iframe's window.getInterface method not init at first
+    // it is assigned in some seconds
+    if (global.navigator && /^getInterface/.test(key)) return false;
+
+    // an iframe could be approached by window[iframeIndex]
+    // in ie6,7,8 and opera, iframeIndex is enumerable, this could cause leak
+    if (global.navigator && /^\d+/.test(key)) return false;
+
+    // Opera and IE expose global variables for HTML element IDs (issue #243)
+    if (/^mocha-/.test(key)) return false;
+
+    var matched = filter(ok, function(ok){
+      if (~ok.indexOf('*')) return 0 == key.indexOf(ok.split('*')[0]);
+      return key == ok;
+    });
+    return matched.length == 0 && (!global.navigator || 'onerror' !== key);
+  });
+}
+
+}); // module: runner.js
+
+require.register("suite.js", function(module, exports, require){
+
+/**
+ * Module dependencies.
+ */
+
+var EventEmitter = require('browser/events').EventEmitter
+  , debug = require('browser/debug')('mocha:suite')
+  , milliseconds = require('./ms')
+  , utils = require('./utils')
+  , Hook = require('./hook');
+
+/**
+ * Expose `Suite`.
+ */
+
+exports = module.exports = Suite;
+
+/**
+ * Create a new `Suite` with the given `title`
+ * and parent `Suite`. When a suite with the
+ * same title is already present, that suite
+ * is returned to provide nicer reporter
+ * and more flexible meta-testing.
+ *
+ * @param {Suite} parent
+ * @param {String} title
+ * @return {Suite}
+ * @api public
+ */
+
+exports.create = function(parent, title){
+  var suite = new Suite(title, parent.ctx);
+  suite.parent = parent;
+  if (parent.pending) suite.pending = true;
+  title = suite.fullTitle();
+  parent.addSuite(suite);
+  return suite;
+};
+
+/**
+ * Initialize a new `Suite` with the given
+ * `title` and `ctx`.
+ *
+ * @param {String} title
+ * @param {Context} ctx
+ * @api private
+ */
+
+function Suite(title, ctx) {
+  this.title = title;
+  this.ctx = ctx;
+  this.suites = [];
+  this.tests = [];
+  this.pending = false;
+  this._beforeEach = [];
+  this._beforeAll = [];
+  this._afterEach = [];
+  this._afterAll = [];
+  this.root = !title;
+  this._timeout = 2000;
+  this._slow = 75;
+  this._bail = false;
+}
+
+/**
+ * Inherit from `EventEmitter.prototype`.
+ */
+
+function F(){};
+F.prototype = EventEmitter.prototype;
+Suite.prototype = new F;
+Suite.prototype.constructor = Suite;
+
+
+/**
+ * Return a clone of this `Suite`.
+ *
+ * @return {Suite}
+ * @api private
+ */
+
+Suite.prototype.clone = function(){
+  var suite = new Suite(this.title);
+  debug('clone');
+  suite.ctx = this.ctx;
+  suite.timeout(this.timeout());
+  suite.slow(this.slow());
+  suite.bail(this.bail());
+  return suite;
+};
+
+/**
+ * Set timeout `ms` or short-hand such as "2s".
+ *
+ * @param {Number|String} ms
+ * @return {Suite|Number} for chaining
+ * @api private
+ */
+
+Suite.prototype.timeout = function(ms){
+  if (0 == arguments.length) return this._timeout;
+  if ('string' == typeof ms) ms = milliseconds(ms);
+  debug('timeout %d', ms);
+  this._timeout = parseInt(ms, 10);
+  return this;
+};
+
+/**
+ * Set slow `ms` or short-hand such as "2s".
+ *
+ * @param {Number|String} ms
+ * @return {Suite|Number} for chaining
+ * @api private
+ */
+
+Suite.prototype.slow = function(ms){
+  if (0 === arguments.length) return this._slow;
+  if ('string' == typeof ms) ms = milliseconds(ms);
+  debug('slow %d', ms);
+  this._slow = ms;
+  return this;
+};
+
+/**
+ * Sets whether to bail after first error.
+ *
+ * @parma {Boolean} bail
+ * @return {Suite|Number} for chaining
+ * @api private
+ */
+
+Suite.prototype.bail = function(bail){
+  if (0 == arguments.length) return this._bail;
+  debug('bail %s', bail);
+  this._bail = bail;
+  return this;
+};
+
+/**
+ * Run `fn(test[, done])` before running tests.
+ *
+ * @param {Function} fn
+ * @return {Suite} for chaining
+ * @api private
+ */
+
+Suite.prototype.beforeAll = function(fn){
+  if (this.pending) return this;
+  var hook = new Hook('"before all" hook', fn);
+  hook.parent = this;
+  hook.timeout(this.timeout());
+  hook.slow(this.slow());
+  hook.ctx = this.ctx;
+  this._beforeAll.push(hook);
+  this.emit('beforeAll', hook);
+  return this;
+};
+
+/**
+ * Run `fn(test[, done])` after running tests.
+ *
+ * @param {Function} fn
+ * @return {Suite} for chaining
+ * @api private
+ */
+
+Suite.prototype.afterAll = function(fn){
+  if (this.pending) return this;
+  var hook = new Hook('"after all" hook', fn);
+  hook.parent = this;
+  hook.timeout(this.timeout());
+  hook.slow(this.slow());
+  hook.ctx = this.ctx;
+  this._afterAll.push(hook);
+  this.emit('afterAll', hook);
+  return this;
+};
+
+/**
+ * Run `fn(test[, done])` before each test case.
+ *
+ * @param {Function} fn
+ * @return {Suite} for chaining
+ * @api private
+ */
+
+Suite.prototype.beforeEach = function(fn){
+  if (this.pending) return this;
+  var hook = new Hook('"before each" hook', fn);
+  hook.parent = this;
+  hook.timeout(this.timeout());
+  hook.slow(this.slow());
+  hook.ctx = this.ctx;
+  this._beforeEach.push(hook);
+  this.emit('beforeEach', hook);
+  return this;
+};
+
+/**
+ * Run `fn(test[, done])` after each test case.
+ *
+ * @param {Function} fn
+ * @return {Suite} for chaining
+ * @api private
+ */
+
+Suite.prototype.afterEach = function(fn){
+  if (this.pending) return this;
+  var hook = new Hook('"after each" hook', fn);
+  hook.parent = this;
+  hook.timeout(this.timeout());
+  hook.slow(this.slow());
+  hook.ctx = this.ctx;
+  this._afterEach.push(hook);
+  this.emit('afterEach', hook);
+  return this;
+};
+
+/**
+ * Add a test `suite`.
+ *
+ * @param {Suite} suite
+ * @return {Suite} for chaining
+ * @api private
+ */
+
+Suite.prototype.addSuite = function(suite){
+  suite.parent = this;
+  suite.timeout(this.timeout());
+  suite.slow(this.slow());
+  suite.bail(this.bail());
+  this.suites.push(suite);
+  this.emit('suite', suite);
+  return this;
+};
+
+/**
+ * Add a `test` to this suite.
+ *
+ * @param {Test} test
+ * @return {Suite} for chaining
+ * @api private
+ */
+
+Suite.prototype.addTest = function(test){
+  test.parent = this;
+  test.timeout(this.timeout());
+  test.slow(this.slow());
+  test.ctx = this.ctx;
+  this.tests.push(test);
+  this.emit('test', test);
+  return this;
+};
+
+/**
+ * Return the full title generated by recursively
+ * concatenating the parent's full title.
+ *
+ * @return {String}
+ * @api public
+ */
+
+Suite.prototype.fullTitle = function(){
+  if (this.parent) {
+    var full = this.parent.fullTitle();
+    if (full) return full + ' ' + this.title;
+  }
+  return this.title;
+};
+
+/**
+ * Return the total number of tests.
+ *
+ * @return {Number}
+ * @api public
+ */
+
+Suite.prototype.total = function(){
+  return utils.reduce(this.suites, function(sum, suite){
+    return sum + suite.total();
+  }, 0) + this.tests.length;
+};
+
+/**
+ * Iterates through each suite recursively to find
+ * all tests. Applies a function in the format
+ * `fn(test)`.
+ *
+ * @param {Function} fn
+ * @return {Suite}
+ * @api private
+ */
+
+Suite.prototype.eachTest = function(fn){
+  utils.forEach(this.tests, fn);
+  utils.forEach(this.suites, function(suite){
+    suite.eachTest(fn);
+  });
+  return this;
+};
+
+}); // module: suite.js
+
+require.register("test.js", function(module, exports, require){
+
+/**
+ * Module dependencies.
+ */
+
+var Runnable = require('./runnable');
+
+/**
+ * Expose `Test`.
+ */
+
+module.exports = Test;
+
+/**
+ * Initialize a new `Test` with the given `title` and callback `fn`.
+ *
+ * @param {String} title
+ * @param {Function} fn
+ * @api private
+ */
+
+function Test(title, fn) {
+  Runnable.call(this, title, fn);
+  this.pending = !fn;
+  this.type = 'test';
+}
+
+/**
+ * Inherit from `Runnable.prototype`.
+ */
+
+function F(){};
+F.prototype = Runnable.prototype;
+Test.prototype = new F;
+Test.prototype.constructor = Test;
+
+
+}); // module: test.js
+
+require.register("utils.js", function(module, exports, require){
+/**
+ * Module dependencies.
+ */
+
+var fs = require('browser/fs')
+  , path = require('browser/path')
+  , join = path.join
+  , debug = require('browser/debug')('mocha:watch');
+
+/**
+ * Ignored directories.
+ */
+
+var ignore = ['node_modules', '.git'];
+
+/**
+ * Escape special characters in the given string of html.
+ *
+ * @param  {String} html
+ * @return {String}
+ * @api private
+ */
+
+exports.escape = function(html){
+  return String(html)
+    .replace(/&/g, '&amp;')
+    .replace(/"/g, '&quot;')
+    .replace(/</g, '&lt;')
+    .replace(/>/g, '&gt;');
+};
+
+/**
+ * Array#forEach (<=IE8)
+ *
+ * @param {Array} array
+ * @param {Function} fn
+ * @param {Object} scope
+ * @api private
+ */
+
+exports.forEach = function(arr, fn, scope){
+  for (var i = 0, l = arr.length; i < l; i++)
+    fn.call(scope, arr[i], i);
+};
+
+/**
+ * Array#indexOf (<=IE8)
+ *
+ * @parma {Array} arr
+ * @param {Object} obj to find index of
+ * @param {Number} start
+ * @api private
+ */
+
+exports.indexOf = function(arr, obj, start){
+  for (var i = start || 0, l = arr.length; i < l; i++) {
+    if (arr[i] === obj)
+      return i;
+  }
+  return -1;
+};
+
+/**
+ * Array#reduce (<=IE8)
+ *
+ * @param {Array} array
+ * @param {Function} fn
+ * @param {Object} initial value
+ * @api private
+ */
+
+exports.reduce = function(arr, fn, val){
+  var rval = val;
+
+  for (var i = 0, l = arr.length; i < l; i++) {
+    rval = fn(rval, arr[i], i, arr);
+  }
+
+  return rval;
+};
+
+/**
+ * Array#filter (<=IE8)
+ *
+ * @param {Array} array
+ * @param {Function} fn
+ * @api private
+ */
+
+exports.filter = function(arr, fn){
+  var ret = [];
+
+  for (var i = 0, l = arr.length; i < l; i++) {
+    var val = arr[i];
+    if (fn(val, i, arr)) ret.push(val);
+  }
+
+  return ret;
+};
+
+/**
+ * Object.keys (<=IE8)
+ *
+ * @param {Object} obj
+ * @return {Array} keys
+ * @api private
+ */
+
+exports.keys = Object.keys || function(obj) {
+  var keys = []
+    , has = Object.prototype.hasOwnProperty // for `window` on <=IE8
+
+  for (var key in obj) {
+    if (has.call(obj, key)) {
+      keys.push(key);
+    }
+  }
+
+  return keys;
+};
+
+/**
+ * Watch the given `files` for changes
+ * and invoke `fn(file)` on modification.
+ *
+ * @param {Array} files
+ * @param {Function} fn
+ * @api private
+ */
+
+exports.watch = function(files, fn){
+  var options = { interval: 100 };
+  files.forEach(function(file){
+    debug('file %s', file);
+    fs.watchFile(file, options, function(curr, prev){
+      if (prev.mtime < curr.mtime) fn(file);
+    });
+  });
+};
+
+/**
+ * Ignored files.
+ */
+
+function ignored(path){
+  return !~ignore.indexOf(path);
+}
+
+/**
+ * Lookup files in the given `dir`.
+ *
+ * @return {Array}
+ * @api private
+ */
+
+exports.files = function(dir, ret){
+  ret = ret || [];
+
+  fs.readdirSync(dir)
+  .filter(ignored)
+  .forEach(function(path){
+    path = join(dir, path);
+    if (fs.statSync(path).isDirectory()) {
+      exports.files(path, ret);
+    } else if (path.match(/\.(js|coffee|litcoffee|coffee.md)$/)) {
+      ret.push(path);
+    }
+  });
+
+  return ret;
+};
+
+/**
+ * Compute a slug from the given `str`.
+ *
+ * @param {String} str
+ * @return {String}
+ * @api private
+ */
+
+exports.slug = function(str){
+  return str
+    .toLowerCase()
+    .replace(/ +/g, '-')
+    .replace(/[^-\w]/g, '');
+};
+
+/**
+ * Strip the function definition from `str`,
+ * and re-indent for pre whitespace.
+ */
+
+exports.clean = function(str) {
+  str = str
+    .replace(/\r\n?|[\n\u2028\u2029]/g, "\n").replace(/^\uFEFF/, '')
+    .replace(/^function *\(.*\) *{/, '')
+    .replace(/\s+\}$/, '');
+
+  var spaces = str.match(/^\n?( *)/)[1].length
+    , tabs = str.match(/^\n?(\t*)/)[1].length
+    , re = new RegExp('^\n?' + (tabs ? '\t' : ' ') + '{' + (tabs ? tabs : spaces) + '}', 'gm');
+
+  str = str.replace(re, '');
+
+  return exports.trim(str);
+};
+
+/**
+ * Escape regular expression characters in `str`.
+ *
+ * @param {String} str
+ * @return {String}
+ * @api private
+ */
+
+exports.escapeRegexp = function(str){
+  return str.replace(/[-\\^$*+?.()|[\]{}]/g, "\\$&");
+};
+
+/**
+ * Trim the given `str`.
+ *
+ * @param {String} str
+ * @return {String}
+ * @api private
+ */
+
+exports.trim = function(str){
+  return str.replace(/^\s+|\s+$/g, '');
+};
+
+/**
+ * Parse the given `qs`.
+ *
+ * @param {String} qs
+ * @return {Object}
+ * @api private
+ */
+
+exports.parseQuery = function(qs){
+  return exports.reduce(qs.replace('?', '').split('&'), function(obj, pair){
+    var i = pair.indexOf('=')
+      , key = pair.slice(0, i)
+      , val = pair.slice(++i);
+
+    obj[key] = decodeURIComponent(val);
+    return obj;
+  }, {});
+};
+
+/**
+ * Highlight the given string of `js`.
+ *
+ * @param {String} js
+ * @return {String}
+ * @api private
+ */
+
+function highlight(js) {
+  return js
+    .replace(/</g, '&lt;')
+    .replace(/>/g, '&gt;')
+    .replace(/\/\/(.*)/gm, '<span class="comment">//$1</span>')
+    .replace(/('.*?')/gm, '<span class="string">$1</span>')
+    .replace(/(\d+\.\d+)/gm, '<span class="number">$1</span>')
+    .replace(/(\d+)/gm, '<span class="number">$1</span>')
+    .replace(/\bnew *(\w+)/gm, '<span class="keyword">new</span> <span class="init">$1</span>')
+    .replace(/\b(function|new|throw|return|var|if|else)\b/gm, '<span class="keyword">$1</span>')
+}
+
+/**
+ * Highlight the contents of tag `name`.
+ *
+ * @param {String} name
+ * @api private
+ */
+
+exports.highlightTags = function(name) {
+  var code = document.getElementsByTagName(name);
+  for (var i = 0, len = code.length; i < len; ++i) {
+    code[i].innerHTML = highlight(code[i].innerHTML);
+  }
+};
+
+}); // module: utils.js
+// The global object is "self" in Web Workers.
+global = (function() { return this; })();
+
+/**
+ * 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;
+
+/**
+ * Node shims.
+ *
+ * These are meant only to allow
+ * mocha.js to run untouched, not
+ * to allow running node code in
+ * the browser.
+ */
+
+var process = {};
+process.exit = function(status){};
+process.stdout = {};
+
+var uncaughtExceptionHandlers = [];
+
+/**
+ * Remove uncaughtException listener.
+ */
+
+process.removeListener = function(e, fn){
+  if ('uncaughtException' == e) {
+    global.onerror = function() {};
+    var i = uncaughtExceptionHandlers.indexOf(fn);
+    if (i != -1) { uncaughtExceptionHandlers.splice(i, 1); }
+  }
+};
+
+/**
+ * Implements uncaughtException listener.
+ */
+
+process.on = function(e, fn){
+  if ('uncaughtException' == e) {
+    global.onerror = function(err, url, line){
+      fn(new Error(err + ' (' + url + ':' + line + ')'));
+      return true;
+    };
+    uncaughtExceptionHandlers.push(fn);
+  }
+};
+
+/**
+ * Expose mocha.
+ */
+
+var Mocha = global.Mocha = require('mocha'),
+    mocha = global.mocha = new Mocha({ reporter: 'html' });
+
+// 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 = []
+  , 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) {
+  uncaughtExceptionHandlers.forEach(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 ('string' == typeof opts) opts = { ui: opts };
+  for (var opt in opts) 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.invert) mocha.invert();
+
+  return Mocha.prototype.run.call(mocha, function(){
+    // The DOM Document is not available in Web Workers.
+    if (global.document) {
+      Mocha.utils.highlightTags('code');
+    }
+    if (fn) fn();
+  });
+};
+
+/**
+ * Expose the process shim.
+ */
+
+Mocha.process = process;
+})();

+ 33 - 0
src/test/specs/lexer-specs.js

@@ -0,0 +1,33 @@
+define([
+  '../../app/services/graphite/lexer'
+], function(Lexer) {
+
+  describe('when lexing graphite expression', function() {
+
+    it('should tokenize metric expression', function() {
+      var lexer = new Lexer('metric.test.*.asd.count');
+      var tokens = lexer.tokenize();
+      expect(tokens[0].value).to.be('metric');
+      expect(tokens[1].value).to.be('.');
+      expect(tokens[2].type).to.be(Lexer.Token.Identifier);
+      expect(tokens[3].type).to.be(Lexer.Token.Punctuator);
+    });
+
+    it('should tokenize functions and args', function() {
+      var lexer = new Lexer("sum(metric.test, 12, 'test')");
+      var tokens = lexer.tokenize();
+      expect(tokens[0].value).to.be('sum');
+      expect(tokens[0].type).to.be(Lexer.Token.Identifier);
+      expect(tokens[1].value).to.be('(');
+      expect(tokens[1].type).to.be(Lexer.Token.Punctuator);
+      expect(tokens[5].type).to.be(Lexer.Token.Punctuator);
+      expect(tokens[5].value).to.be(',');
+      expect(tokens[6].type).to.be(Lexer.Token.NumericLiteral);
+      expect(tokens[6].value).to.be('12');
+      expect(tokens[8].type).to.be(Lexer.Token.StringLiteral);
+      expect(tokens[8].value).to.be('test');
+    });
+
+  });
+
+});

+ 19 - 0
src/test/specs/parser-specs.js

@@ -0,0 +1,19 @@
+define([
+  '../../app/services/graphite/Parser'
+], function(Parser) {
+
+  describe('when parsing graphite expression', function() {
+
+    it('should return ast', function() {
+      var parser = new Parser('metric.test.*.asd.count');
+      var ast = parser.getAst();
+      expect(ast[0].type).to.be(Parser.Nodes.MetricExpression);
+      expect(ast[0].nodes.length).to.be(5);
+      expect(ast[0].nodes[0].value).to.be('metric');
+
+    });
+
+
+  });
+
+});

+ 26 - 0
src/test/tests.html

@@ -0,0 +1,26 @@
+<html>
+  <head>
+    <meta charset="utf-8"/>
+    <title>Tests</title>
+    <link rel="stylesheet" href="assets/mocha.css"/>
+  </head>
+  <body>
+    <div id="mocha"></div>
+    <script src="../vendor/require/require.js"></script>
+    <script src="../app/components/require.config.js"></script>
+    <script src="assets/mocha.js"></script>
+    <script src="assets/expect.js"></script>
+    <script>
+
+      /*globals mocha */
+      mocha.setup('bdd');
+
+      require([
+        '../specs/lexer-specs',
+        '../specs/parser-specs',
+        ], function () {
+        mocha.run();
+      });
+    </script>
+  </body>
+</html>