summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorTrey Hunner <trey@treyhunner.com>2015-04-14 10:55:57 -0400
committerTim Graham <timograham@gmail.com>2015-06-30 21:04:16 -0400
commit2d0dead224b6448072b72b37d2fbcc8dc3afa007 (patch)
tree6dfb01a60cefc0dcf5cdcb35ac5c946a2cab98e0
parent3bbaf84d6533fb61ac0038f2bbe52ee0d7b4fd10 (diff)
downloaddjango-2d0dead224b6448072b72b37d2fbcc8dc3afa007.tar.gz
DEP 0003 -- Added JavaScript unit tests.
Setup QUnit, added tests, and measured test coverage. Thanks to Nick Sanford for the initial tests.
-rw-r--r--.gitignore1
-rw-r--r--Gruntfile.js20
-rw-r--r--docs/internals/contributing/writing-code/javascript.txt78
-rw-r--r--docs/spelling_wordlist1
-rw-r--r--js_tests/admin/DateTimeShortcuts.test.js15
-rw-r--r--js_tests/admin/RelatedObjectLookups.test.js22
-rw-r--r--js_tests/admin/SelectBox.test.js20
-rw-r--r--js_tests/admin/SelectFilter2.test.js14
-rw-r--r--js_tests/admin/actions.test.js18
-rw-r--r--js_tests/admin/core.test.js59
-rw-r--r--js_tests/admin/inlines.test.js28
-rw-r--r--js_tests/admin/jsi18n-mocks.test.js82
-rw-r--r--js_tests/admin/timeparse.test.js28
-rw-r--r--js_tests/qunit/blanket.min.js39
-rw-r--r--js_tests/qunit/grunt-reporter.js78
-rw-r--r--js_tests/qunit/qunit.css291
-rw-r--r--js_tests/qunit/qunit.js3828
-rw-r--r--js_tests/tests.html88
-rw-r--r--package.json8
19 files changed, 4716 insertions, 2 deletions
diff --git a/.gitignore b/.gitignore
index e873831f50..504361b225 100644
--- a/.gitignore
+++ b/.gitignore
@@ -10,3 +10,4 @@ node_modules/
tests/coverage_html/
tests/.coverage
build/
+tests/report/
diff --git a/Gruntfile.js b/Gruntfile.js
new file mode 100644
index 0000000000..9fa6826535
--- /dev/null
+++ b/Gruntfile.js
@@ -0,0 +1,20 @@
+var globalThreshold = 50; // Global code coverage threshold (as a percentage)
+
+module.exports = function(grunt) {
+ grunt.initConfig({
+ // Configuration to be run (and then tested).
+ blanket_qunit: {
+ default_options: {
+ options: {
+ urls: ['js_tests/tests.html?coverage=true&gruntReport'],
+ globalThreshold: globalThreshold,
+ threshold: 10
+ }
+ }
+ }
+ });
+
+ grunt.loadNpmTasks('grunt-blanket-qunit');
+ grunt.registerTask('test', ['blanket_qunit']);
+ grunt.registerTask('default', ['test']);
+};
diff --git a/docs/internals/contributing/writing-code/javascript.txt b/docs/internals/contributing/writing-code/javascript.txt
index fa3685720e..f1d6e44d16 100644
--- a/docs/internals/contributing/writing-code/javascript.txt
+++ b/docs/internals/contributing/writing-code/javascript.txt
@@ -58,7 +58,85 @@ independently. The Closure Compiler library requires `Java`_ 7 or higher.
Please don't forget to run ``compress.py`` and include the ``diff`` of the
minified scripts when submitting patches for Django's JavaScript.
+JavaScript tests
+----------------
+
+Django's JavaScript tests can be run in a browser or from the command line.
+The tests are located in a top level ``js_tests`` directory.
+
+Writing tests
+~~~~~~~~~~~~~
+
+Django's JavaScript tests use `QUnit`_. Here is an example test module:
+
+.. code-block:: javascript
+
+ module('magicTricks', {
+ beforeEach: function() {
+ var $ = django.jQuery;
+ $('#qunit-fixture').append('<button class="button"></button>');
+ }
+ });
+
+ test('removeOnClick removes button on click', function(assert) {
+ var $ = django.jQuery;
+ removeOnClick('.button');
+ assert.equal($('.button').length === 1);
+ $('.button').click();
+ assert.equal($('.button').length === 0);
+ });
+
+ test('copyOnClick adds button on click', function(assert) {
+ var $ = django.jQuery;
+ copyOnClick('.button');
+ assert.equal($('.button').length === 1);
+ $('.button').click();
+ assert.equal($('.button').length === 2);
+ });
+
+
+Please consult the QUnit documentation for information on the types of
+`assertions supported by QUnit <https://api.qunitjs.com/category/assert/>`_.
+
+Running tests
+~~~~~~~~~~~~~
+
+The JavaScript tests may be run from a web browser or from the command line.
+
+Testing from a web browser
+^^^^^^^^^^^^^^^^^^^^^^^^^^
+
+To run the tests from a web browser, open up ``js_tests/tests.html`` in your
+browser.
+
+To measure code coverage when running the tests, you need to view that file
+over HTTP. To view code coverage:
+
+* Execute ``python -m http.server`` (or ``python -m SimpleHTTPServer`` on
+ Python 2) from the root directory (not from inside ``js_tests``).
+* Open http://localhost:8000/js_tests/tests.html in your web browser.
+
+Testing from the command line
+^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
+
+To run the tests from the command line, you need to have `Node.js`_ installed.
+
+After installing `Node.js`, install the JavaScript test dependencies by running
+the following from the root of your Django checkout:
+
+.. code-block:: console
+
+ $ npm install
+
+Then run the tests with:
+
+.. code-block:: console
+
+ $ npm test
+
.. _Closure Compiler: https://developers.google.com/closure/compiler/
.. _EditorConfig: http://editorconfig.org/
.. _Java: https://www.java.com
.. _jshint: http://jshint.com/
+.. _node.js: https://nodejs.org/
+.. _qunit: https://qunitjs.com/
diff --git a/docs/spelling_wordlist b/docs/spelling_wordlist
index ff37b3159e..ebc20f9b23 100644
--- a/docs/spelling_wordlist
+++ b/docs/spelling_wordlist
@@ -621,6 +621,7 @@ Querysets
querystring
queueing
Quickstart
+QUnit
quo
quoteless
Radziej
diff --git a/js_tests/admin/DateTimeShortcuts.test.js b/js_tests/admin/DateTimeShortcuts.test.js
new file mode 100644
index 0000000000..fb5f15c5aa
--- /dev/null
+++ b/js_tests/admin/DateTimeShortcuts.test.js
@@ -0,0 +1,15 @@
+module('admin.DateTimeShortcuts');
+
+test('init', function(assert) {
+ var $ = django.jQuery;
+
+ var dateField = $('<input type="text" class="vDateField" value="2015-03-16"><br>');
+ $('#qunit-fixture').append(dateField);
+
+ DateTimeShortcuts.init();
+
+ var shortcuts = $('.datetimeshortcuts');
+ assert.equal(shortcuts.length, 1);
+ assert.equal(shortcuts.find('a:first').text(), 'Today');
+ assert.equal(shortcuts.find('a:last .date-icon').length, 1);
+});
diff --git a/js_tests/admin/RelatedObjectLookups.test.js b/js_tests/admin/RelatedObjectLookups.test.js
new file mode 100644
index 0000000000..b2e2e1841b
--- /dev/null
+++ b/js_tests/admin/RelatedObjectLookups.test.js
@@ -0,0 +1,22 @@
+module('admin.RelatedObjectLookups');
+
+test('html_unescape', function(assert) {
+ function assert_unescape(then, expected, message) {
+ assert.equal(html_unescape(then), expected, message);
+ }
+ assert_unescape('&lt;', '<', 'less thans are unescaped');
+ assert_unescape('&gt;', '>', 'greater thans are unescaped');
+ assert_unescape('&quot;', '"', 'double quotes are unescaped');
+ assert_unescape('&#39;', "'", 'single quotes are unescaped');
+ assert_unescape('&amp;', '&', 'ampersands are unescaped');
+});
+
+test('id_to_windowname', function(assert) {
+ assert.equal(id_to_windowname('.test'), '__dot__test');
+ assert.equal(id_to_windowname('misc-test'), 'misc__dash__test');
+});
+
+test('windowname_to_id', function(assert) {
+ assert.equal(windowname_to_id('__dot__test'), '.test');
+ assert.equal(windowname_to_id('misc__dash__test'), 'misc-test');
+});
diff --git a/js_tests/admin/SelectBox.test.js b/js_tests/admin/SelectBox.test.js
new file mode 100644
index 0000000000..b2e36e0ab2
--- /dev/null
+++ b/js_tests/admin/SelectBox.test.js
@@ -0,0 +1,20 @@
+module('admin.SelectBox');
+
+test('init: no options', function(assert) {
+ var $ = django.jQuery;
+ $('<select id="id"></select>').appendTo('#qunit-fixture');
+ SelectBox.init('id');
+ assert.equal(SelectBox.cache['id'].length, 0);
+});
+
+test('filter', function(assert) {
+ var $ = django.jQuery;
+ $('<select id="id"></select>').appendTo('#qunit-fixture');
+ $('<option value="0">A</option>').appendTo('#id');
+ $('<option value="1">B</option>').appendTo('#id');
+ SelectBox.init('id');
+ assert.equal($('#id option').length, 2);
+ SelectBox.filter('id', "A");
+ assert.equal($('#id option').length, 1);
+ assert.equal($('#id option').text(), "A");
+});
diff --git a/js_tests/admin/SelectFilter2.test.js b/js_tests/admin/SelectFilter2.test.js
new file mode 100644
index 0000000000..a06c3ab49c
--- /dev/null
+++ b/js_tests/admin/SelectFilter2.test.js
@@ -0,0 +1,14 @@
+module('admin.SelectFilter2');
+
+test('init', function(assert) {
+ var $ = django.jQuery;
+ $('<form><select id="id"></select></form>').appendTo('#qunit-fixture');
+ $('<option value="0">A</option>').appendTo('#id');
+ SelectFilter.init('id', 'things', 0);
+ assert.equal($('.selector-available h2').text().trim(), "Available things");
+ assert.equal($('.selector-chosen h2').text().trim(), "Chosen things");
+ assert.equal($('.selector-chooseall').text(), "Choose all");
+ assert.equal($('.selector-add').text(), "Choose");
+ assert.equal($('.selector-remove').text(), "Remove");
+ assert.equal($('.selector-clearall').text(), "Remove all");
+});
diff --git a/js_tests/admin/actions.test.js b/js_tests/admin/actions.test.js
new file mode 100644
index 0000000000..01e9267b94
--- /dev/null
+++ b/js_tests/admin/actions.test.js
@@ -0,0 +1,18 @@
+module('admin.actions', {
+ beforeEach: function() {
+ // Number of results shown on page
+ window._actions_icnt = '100';
+
+ var $ = django.jQuery;
+ $('#qunit-fixture').append($('#result-table').text());
+
+ $('tr input.action-select').actions();
+ }
+});
+
+test('check', function(assert) {
+ var $ = django.jQuery;
+ assert.notOk($('.action-select').is(':checked'));
+ $('#action-toggle').click();
+ assert.ok($('.action-select').is(':checked'));
+});
diff --git a/js_tests/admin/core.test.js b/js_tests/admin/core.test.js
new file mode 100644
index 0000000000..044a1d5787
--- /dev/null
+++ b/js_tests/admin/core.test.js
@@ -0,0 +1,59 @@
+module('admin.core');
+
+test('Date.getTwelveHours', function(assert) {
+ assert.equal(new Date(2011, 0, 1, 0, 0).getTwelveHours(), 12, '0:00');
+ assert.equal(new Date(2011, 0, 1, 11, 0).getTwelveHours(), 11, '11:00');
+ assert.equal(new Date(2011, 0, 1, 16, 0).getTwelveHours(), 4, '16:00');
+});
+
+test('Date.getTwoDigitMonth', function(assert) {
+ assert.equal(new Date(2011, 0, 1).getTwoDigitMonth(), '01', 'jan 1');
+ assert.equal(new Date(2011, 9, 1).getTwoDigitMonth(), '10', 'oct 1');
+});
+
+test('Date.getTwoDigitDate', function(assert) {
+ assert.equal(new Date(2011, 0, 1).getTwoDigitDate(), '01', 'jan 1');
+ assert.equal(new Date(2011, 0, 15).getTwoDigitDate(), '15', 'jan 15');
+});
+
+test('Date.getTwoDigitTwelveHour', function(assert) {
+ assert.equal(new Date(2011, 0, 1, 0, 0).getTwoDigitTwelveHour(), '12', '0:00');
+ assert.equal(new Date(2011, 0, 1, 4, 0).getTwoDigitTwelveHour(), '04', '4:00');
+ assert.equal(new Date(2011, 0, 1, 22, 0).getTwoDigitTwelveHour(), '10', '22:00');
+});
+
+test('Date.getTwoDigitHour', function(assert) {
+ assert.equal(new Date(2014, 6, 1, 9, 0).getTwoDigitHour(), '09', '9:00 am is 09');
+ assert.equal(new Date(2014, 6, 1, 11, 0).getTwoDigitHour(), '11', '11:00 am is 11');
+});
+
+test('Date.getTwoDigitMinute', function(assert) {
+ assert.equal(new Date(2014, 6, 1, 0, 5).getTwoDigitMinute(), '05', '12:05 am is 05');
+ assert.equal(new Date(2014, 6, 1, 0, 15).getTwoDigitMinute(), '15', '12:15 am is 15');
+});
+
+test('Date.getTwoDigitSecond', function(assert) {
+ assert.equal(new Date(2014, 6, 1, 0, 0, 2).getTwoDigitSecond(), '02', '12:00:02 am is 02');
+ assert.equal(new Date(2014, 6, 1, 0, 0, 20).getTwoDigitSecond(), '20', '12:00:20 am is 20');
+});
+
+test('Date.getHourMinute', function(assert) {
+ assert.equal(new Date(2014, 6, 1, 11, 0).getHourMinute(), '11:00', '11:00 am is 11:00');
+ assert.equal(new Date(2014, 6, 1, 13, 25).getHourMinute(), '13:25', '1:25 pm is 13:25');
+});
+
+test('Date.getHourMinuteSecond', function(assert) {
+ assert.equal(new Date(2014, 6, 1, 11, 0, 0).getHourMinuteSecond(), '11:00:00', '11:00 am is 11:00:00');
+ assert.equal(new Date(2014, 6, 1, 17, 45, 30).getHourMinuteSecond(), '17:45:30', '5:45:30 pm is 17:45:30');
+});
+
+test('Date.strftime', function(assert) {
+ var date = new Date(2014, 6, 1, 11, 0, 5);
+ assert.equal(date.strftime('%Y-%m-%d %H:%M:%S'), '2014-07-01 11:00:05');
+});
+
+test('String.strptime', function(assert) {
+ var date = new Date(1988, 1, 26);
+ assert.equal('1988-02-26'.strptime('%Y-%m-%d').toString(), date.toString());
+ assert.equal('26/02/88'.strptime('%d/%m/%y').toString(), date.toString());
+});
diff --git a/js_tests/admin/inlines.test.js b/js_tests/admin/inlines.test.js
new file mode 100644
index 0000000000..1edf2b511c
--- /dev/null
+++ b/js_tests/admin/inlines.test.js
@@ -0,0 +1,28 @@
+module('admin.inlines: tabular formsets', {
+ beforeEach: function() {
+ var $ = django.jQuery;
+ var that = this;
+ this.addText = 'Add another';
+
+ $('#qunit-fixture').append($('#tabular-formset').text());
+ this.table = $('table.inline');
+ this.inlineRow = this.table.find('tr');
+ that.inlineRow.tabularFormset({
+ prefix: 'first',
+ addText: that.addText,
+ deleteText: 'Remove'
+ });
+ }
+});
+
+test('no forms', function(assert) {
+ assert.ok(this.inlineRow.hasClass('dynamic-first'));
+ assert.equal(this.table.find('.add-row a').text(), this.addText);
+});
+
+test('add form', function(assert) {
+ var addButton = this.table.find('.add-row a');
+ assert.equal(addButton.text(), this.addText);
+ addButton.click();
+ assert.ok(this.table.find('#first-1').hasClass('row2'));
+});
diff --git a/js_tests/admin/jsi18n-mocks.test.js b/js_tests/admin/jsi18n-mocks.test.js
new file mode 100644
index 0000000000..dcd7c67c3e
--- /dev/null
+++ b/js_tests/admin/jsi18n-mocks.test.js
@@ -0,0 +1,82 @@
+(function (globals) {
+
+ var django = globals.django || (globals.django = {});
+
+ django.pluralidx = function (count) { return (count == 1) ? 0 : 1; };
+
+ /* gettext identity library */
+
+ django.gettext = function (msgid) { return msgid; };
+ django.ngettext = function (singular, plural, count) { return (count == 1) ? singular : plural; };
+ django.gettext_noop = function (msgid) { return msgid; };
+ django.pgettext = function (context, msgid) { return msgid; };
+ django.npgettext = function (context, singular, plural, count) { return (count == 1) ? singular : plural; };
+
+ django.interpolate = function (fmt, obj, named) {
+ if (named) {
+ return fmt.replace(/%\(\w+\)s/g, function(match){return String(obj[match.slice(2,-2)])});
+ } else {
+ return fmt.replace(/%s/g, function(match){return String(obj.shift())});
+ }
+ };
+
+ /* formatting library */
+
+ django.formats = {
+ "DATETIME_FORMAT": "N j, Y, P",
+ "DATETIME_INPUT_FORMATS": [
+ "%Y-%m-%d %H:%M:%S",
+ "%Y-%m-%d %H:%M:%S.%f",
+ "%Y-%m-%d %H:%M",
+ "%Y-%m-%d",
+ "%m/%d/%Y %H:%M:%S",
+ "%m/%d/%Y %H:%M:%S.%f",
+ "%m/%d/%Y %H:%M",
+ "%m/%d/%Y",
+ "%m/%d/%y %H:%M:%S",
+ "%m/%d/%y %H:%M:%S.%f",
+ "%m/%d/%y %H:%M",
+ "%m/%d/%y"
+ ],
+ "DATE_FORMAT": "N j, Y",
+ "DATE_INPUT_FORMATS": [
+ "%Y-%m-%d",
+ "%m/%d/%Y",
+ "%m/%d/%y"
+ ],
+ "DECIMAL_SEPARATOR": ".",
+ "FIRST_DAY_OF_WEEK": "0",
+ "MONTH_DAY_FORMAT": "F j",
+ "NUMBER_GROUPING": "3",
+ "SHORT_DATETIME_FORMAT": "m/d/Y P",
+ "SHORT_DATE_FORMAT": "m/d/Y",
+ "THOUSAND_SEPARATOR": ",",
+ "TIME_FORMAT": "P",
+ "TIME_INPUT_FORMATS": [
+ "%H:%M:%S",
+ "%H:%M:%S.%f",
+ "%H:%M"
+ ],
+ "YEAR_MONTH_FORMAT": "F Y"
+ };
+
+ django.get_format = function (format_type) {
+ var value = django.formats[format_type];
+ if (typeof(value) == 'undefined') {
+ return format_type;
+ } else {
+ return value;
+ }
+ };
+
+ /* add to global namespace */
+ globals.pluralidx = django.pluralidx;
+ globals.gettext = django.gettext;
+ globals.ngettext = django.ngettext;
+ globals.gettext_noop = django.gettext_noop;
+ globals.pgettext = django.pgettext;
+ globals.npgettext = django.npgettext;
+ globals.interpolate = django.interpolate;
+ globals.get_format = django.get_format;
+
+}(this));
diff --git a/js_tests/admin/timeparse.test.js b/js_tests/admin/timeparse.test.js
new file mode 100644
index 0000000000..86479c4f3f
--- /dev/null
+++ b/js_tests/admin/timeparse.test.js
@@ -0,0 +1,28 @@
+module('admin.timeparse');
+
+test('parseTimeString', function(assert) {
+ function time(then, expected) {
+ assert.equal(parseTimeString(then), expected);
+ }
+ time('9', '09:00');
+ time('09', '09:00');
+ time('13:00', '13:00');
+ time('13.00', '13:00');
+ time('9:00', '09:00');
+ time('9.00', '09:00');
+ time('3 am', '03:00');
+ time('3 a.m.', '03:00');
+ time('12 am', '00:00');
+ time('11 am', '11:00');
+ time('12 pm', '12:00');
+ time('3am', '03:00');
+ time('3.30 am', '03:30');
+ time('3:15 a.m.', '03:15');
+ time('3.00am', '03:00');
+ time('12.00am', '00:00');
+ time('11.00am', '11:00');
+ time('12.00pm', '12:00');
+ time('noon', '12:00');
+ time('midnight', '00:00');
+ time('something else', 'something else');
+});
diff --git a/js_tests/qunit/blanket.min.js b/js_tests/qunit/blanket.min.js
new file mode 100644
index 0000000000..b5ce8aaff2
--- /dev/null
+++ b/js_tests/qunit/blanket.min.js
@@ -0,0 +1,39 @@
+/*! blanket - v1.1.5 */
+"undefined"!=typeof QUnit&&(QUnit.config.autostart=!1),function(a){/*
+ Copyright (C) 2013 Ariya Hidayat <ariya.hidayat@gmail.com>
+ Copyright (C) 2013 Thaddee Tyl <thaddee.tyl@gmail.com>
+ Copyright (C) 2013 Mathias Bynens <mathias@qiwi.be>
+ Copyright (C) 2012 Ariya Hidayat <ariya.hidayat@gmail.com>
+ Copyright (C) 2012 Mathias Bynens <mathias@qiwi.be>
+ Copyright (C) 2012 Joost-Wim Boekesteijn <joost-wim@boekesteijn.nl>
+ Copyright (C) 2012 Kris Kowal <kris.kowal@cixar.com>
+ Copyright (C) 2012 Yusuke Suzuki <utatane.tea@gmail.com>
+ Copyright (C) 2012 Arpad Borsos <arpad.borsos@googlemail.com>
+ Copyright (C) 2011 Ariya Hidayat <ariya.hidayat@gmail.com>
+
+ Redistribution and use in source and binary forms, with or without
+ modification, are permitted provided that the following conditions are met:
+
+ * Redistributions of source code must retain the above copyright
+ notice, this list of conditions and the following disclaimer.
+ * Redistributions in binary form must reproduce the above copyright
+ notice, this list of conditions and the following disclaimer in the
+ documentation and/or other materials provided with the distribution.
+
+ THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ ARE DISCLAIMED. IN NO EVENT SHALL <COPYRIGHT HOLDER> BE LIABLE FOR ANY
+ DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
+ (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
+ LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
+ ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
+ THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+*/
+!function(b,c){"use strict";"function"==typeof a&&a.amd?a(["exports"],c):c("undefined"!=typeof exports?exports:b.esprima={})}(this,function(a){"use strict";function b(a,b){if(!a)throw new Error("ASSERT: "+b)}function c(a){return a>=48&&57>=a}function d(a){return"0123456789abcdefABCDEF".indexOf(a)>=0}function e(a){return"01234567".indexOf(a)>=0}function f(a){return 32===a||9===a||11===a||12===a||160===a||a>=5760&&[5760,6158,8192,8193,8194,8195,8196,8197,8198,8199,8200,8201,8202,8239,8287,12288,65279].indexOf(a)>=0}function g(a){return 10===a||13===a||8232===a||8233===a}function h(a){return 36===a||95===a||a>=65&&90>=a||a>=97&&122>=a||92===a||a>=128&&ec.NonAsciiIdentifierStart.test(String.fromCharCode(a))}function i(a){return 36===a||95===a||a>=65&&90>=a||a>=97&&122>=a||a>=48&&57>=a||92===a||a>=128&&ec.NonAsciiIdentifierPart.test(String.fromCharCode(a))}function j(a){switch(a){case"class":case"enum":case"export":case"extends":case"import":case"super":return!0;default:return!1}}function k(a){switch(a){case"implements":case"interface":case"package":case"private":case"protected":case"public":case"static":case"yield":case"let":return!0;default:return!1}}function l(a){return"eval"===a||"arguments"===a}function m(a){if(hc&&k(a))return!0;switch(a.length){case 2:return"if"===a||"in"===a||"do"===a;case 3:return"var"===a||"for"===a||"new"===a||"try"===a||"let"===a;case 4:return"this"===a||"else"===a||"case"===a||"void"===a||"with"===a||"enum"===a;case 5:return"while"===a||"break"===a||"catch"===a||"throw"===a||"const"===a||"yield"===a||"class"===a||"super"===a;case 6:return"return"===a||"typeof"===a||"delete"===a||"switch"===a||"export"===a||"import"===a;case 7:return"default"===a||"finally"===a||"extends"===a;case 8:return"function"===a||"continue"===a||"debugger"===a;case 10:return"instanceof"===a;default:return!1}}function n(a,c,d,e,f){var g;b("number"==typeof d,"Comment must have valid position"),oc.lastCommentStart>=d||(oc.lastCommentStart=d,g={type:a,value:c},pc.range&&(g.range=[d,e]),pc.loc&&(g.loc=f),pc.comments.push(g),pc.attachComment&&(pc.leadingComments.push(g),pc.trailingComments.push(g)))}function o(a){var b,c,d,e;for(b=ic-a,c={start:{line:jc,column:ic-kc-a}};lc>ic;)if(d=gc.charCodeAt(ic),++ic,g(d))return pc.comments&&(e=gc.slice(b+a,ic-1),c.end={line:jc,column:ic-kc-1},n("Line",e,b,ic-1,c)),13===d&&10===gc.charCodeAt(ic)&&++ic,++jc,void(kc=ic);pc.comments&&(e=gc.slice(b+a,ic),c.end={line:jc,column:ic-kc},n("Line",e,b,ic,c))}function p(){var a,b,c,d;for(pc.comments&&(a=ic-2,b={start:{line:jc,column:ic-kc-2}});lc>ic;)if(c=gc.charCodeAt(ic),g(c))13===c&&10===gc.charCodeAt(ic+1)&&++ic,++jc,++ic,kc=ic,ic>=lc&&O({},dc.UnexpectedToken,"ILLEGAL");else if(42===c){if(47===gc.charCodeAt(ic+1))return++ic,++ic,void(pc.comments&&(d=gc.slice(a+2,ic-2),b.end={line:jc,column:ic-kc},n("Block",d,a,ic,b)));++ic}else++ic;O({},dc.UnexpectedToken,"ILLEGAL")}function q(){var a,b;for(b=0===ic;lc>ic;)if(a=gc.charCodeAt(ic),f(a))++ic;else if(g(a))++ic,13===a&&10===gc.charCodeAt(ic)&&++ic,++jc,kc=ic,b=!0;else if(47===a)if(a=gc.charCodeAt(ic+1),47===a)++ic,++ic,o(2),b=!0;else{if(42!==a)break;++ic,++ic,p()}else if(b&&45===a){if(45!==gc.charCodeAt(ic+1)||62!==gc.charCodeAt(ic+2))break;ic+=3,o(3)}else{if(60!==a)break;if("!--"!==gc.slice(ic+1,ic+4))break;++ic,++ic,++ic,++ic,o(4)}}function r(a){var b,c,e,f=0;for(c="u"===a?4:2,b=0;c>b;++b){if(!(lc>ic&&d(gc[ic])))return"";e=gc[ic++],f=16*f+"0123456789abcdef".indexOf(e.toLowerCase())}return String.fromCharCode(f)}function s(){var a,b;for(a=gc.charCodeAt(ic++),b=String.fromCharCode(a),92===a&&(117!==gc.charCodeAt(ic)&&O({},dc.UnexpectedToken,"ILLEGAL"),++ic,a=r("u"),a&&"\\"!==a&&h(a.charCodeAt(0))||O({},dc.UnexpectedToken,"ILLEGAL"),b=a);lc>ic&&(a=gc.charCodeAt(ic),i(a));)++ic,b+=String.fromCharCode(a),92===a&&(b=b.substr(0,b.length-1),117!==gc.charCodeAt(ic)&&O({},dc.UnexpectedToken,"ILLEGAL"),++ic,a=r("u"),a&&"\\"!==a&&i(a.charCodeAt(0))||O({},dc.UnexpectedToken,"ILLEGAL"),b+=a);return b}function t(){var a,b;for(a=ic++;lc>ic;){if(b=gc.charCodeAt(ic),92===b)return ic=a,s();if(!i(b))break;++ic}return gc.slice(a,ic)}function u(){var a,b,c;return a=ic,b=92===gc.charCodeAt(ic)?s():t(),c=1===b.length?$b.Identifier:m(b)?$b.Keyword:"null"===b?$b.NullLiteral:"true"===b||"false"===b?$b.BooleanLiteral:$b.Identifier,{type:c,value:b,lineNumber:jc,lineStart:kc,start:a,end:ic}}function v(){var a,b,c,d,e=ic,f=gc.charCodeAt(ic),g=gc[ic];switch(f){case 46:case 40:case 41:case 59:case 44:case 123:case 125:case 91:case 93:case 58:case 63:case 126:return++ic,pc.tokenize&&(40===f?pc.openParenToken=pc.tokens.length:123===f&&(pc.openCurlyToken=pc.tokens.length)),{type:$b.Punctuator,value:String.fromCharCode(f),lineNumber:jc,lineStart:kc,start:e,end:ic};default:if(a=gc.charCodeAt(ic+1),61===a)switch(f){case 43:case 45:case 47:case 60:case 62:case 94:case 124:case 37:case 38:case 42:return ic+=2,{type:$b.Punctuator,value:String.fromCharCode(f)+String.fromCharCode(a),lineNumber:jc,lineStart:kc,start:e,end:ic};case 33:case 61:return ic+=2,61===gc.charCodeAt(ic)&&++ic,{type:$b.Punctuator,value:gc.slice(e,ic),lineNumber:jc,lineStart:kc,start:e,end:ic}}}return d=gc.substr(ic,4),">>>="===d?(ic+=4,{type:$b.Punctuator,value:d,lineNumber:jc,lineStart:kc,start:e,end:ic}):(c=d.substr(0,3),">>>"===c||"<<="===c||">>="===c?(ic+=3,{type:$b.Punctuator,value:c,lineNumber:jc,lineStart:kc,start:e,end:ic}):(b=c.substr(0,2),g===b[1]&&"+-<>&|".indexOf(g)>=0||"=>"===b?(ic+=2,{type:$b.Punctuator,value:b,lineNumber:jc,lineStart:kc,start:e,end:ic}):"<>=!+-*%&|^/".indexOf(g)>=0?(++ic,{type:$b.Punctuator,value:g,lineNumber:jc,lineStart:kc,start:e,end:ic}):void O({},dc.UnexpectedToken,"ILLEGAL")))}function w(a){for(var b="";lc>ic&&d(gc[ic]);)b+=gc[ic++];return 0===b.length&&O({},dc.UnexpectedToken,"ILLEGAL"),h(gc.charCodeAt(ic))&&O({},dc.UnexpectedToken,"ILLEGAL"),{type:$b.NumericLiteral,value:parseInt("0x"+b,16),lineNumber:jc,lineStart:kc,start:a,end:ic}}function x(a){for(var b="0"+gc[ic++];lc>ic&&e(gc[ic]);)b+=gc[ic++];return(h(gc.charCodeAt(ic))||c(gc.charCodeAt(ic)))&&O({},dc.UnexpectedToken,"ILLEGAL"),{type:$b.NumericLiteral,value:parseInt(b,8),octal:!0,lineNumber:jc,lineStart:kc,start:a,end:ic}}function y(){var a,d,f;if(f=gc[ic],b(c(f.charCodeAt(0))||"."===f,"Numeric literal must start with a decimal digit or a decimal point"),d=ic,a="","."!==f){if(a=gc[ic++],f=gc[ic],"0"===a){if("x"===f||"X"===f)return++ic,w(d);if(e(f))return x(d);f&&c(f.charCodeAt(0))&&O({},dc.UnexpectedToken,"ILLEGAL")}for(;c(gc.charCodeAt(ic));)a+=gc[ic++];f=gc[ic]}if("."===f){for(a+=gc[ic++];c(gc.charCodeAt(ic));)a+=gc[ic++];f=gc[ic]}if("e"===f||"E"===f)if(a+=gc[ic++],f=gc[ic],("+"===f||"-"===f)&&(a+=gc[ic++]),c(gc.charCodeAt(ic)))for(;c(gc.charCodeAt(ic));)a+=gc[ic++];else O({},dc.UnexpectedToken,"ILLEGAL");return h(gc.charCodeAt(ic))&&O({},dc.UnexpectedToken,"ILLEGAL"),{type:$b.NumericLiteral,value:parseFloat(a),lineNumber:jc,lineStart:kc,start:d,end:ic}}function z(){var a,c,d,f,h,i,j,k,l="",m=!1;for(j=jc,k=kc,a=gc[ic],b("'"===a||'"'===a,"String literal must starts with a quote"),c=ic,++ic;lc>ic;){if(d=gc[ic++],d===a){a="";break}if("\\"===d)if(d=gc[ic++],d&&g(d.charCodeAt(0)))++jc,"\r"===d&&"\n"===gc[ic]&&++ic,kc=ic;else switch(d){case"u":case"x":i=ic,h=r(d),h?l+=h:(ic=i,l+=d);break;case"n":l+="\n";break;case"r":l+="\r";break;case"t":l+=" ";break;case"b":l+="\b";break;case"f":l+="\f";break;case"v":l+=" ";break;default:e(d)?(f="01234567".indexOf(d),0!==f&&(m=!0),lc>ic&&e(gc[ic])&&(m=!0,f=8*f+"01234567".indexOf(gc[ic++]),"0123".indexOf(d)>=0&&lc>ic&&e(gc[ic])&&(f=8*f+"01234567".indexOf(gc[ic++]))),l+=String.fromCharCode(f)):l+=d}else{if(g(d.charCodeAt(0)))break;l+=d}}return""!==a&&O({},dc.UnexpectedToken,"ILLEGAL"),{type:$b.StringLiteral,value:l,octal:m,startLineNumber:j,startLineStart:k,lineNumber:jc,lineStart:kc,start:c,end:ic}}function A(a,b){var c;try{c=new RegExp(a,b)}catch(d){O({},dc.InvalidRegExp)}return c}function B(){var a,c,d,e,f;for(a=gc[ic],b("/"===a,"Regular expression literal must start with a slash"),c=gc[ic++],d=!1,e=!1;lc>ic;)if(a=gc[ic++],c+=a,"\\"===a)a=gc[ic++],g(a.charCodeAt(0))&&O({},dc.UnterminatedRegExp),c+=a;else if(g(a.charCodeAt(0)))O({},dc.UnterminatedRegExp);else if(d)"]"===a&&(d=!1);else{if("/"===a){e=!0;break}"["===a&&(d=!0)}return e||O({},dc.UnterminatedRegExp),f=c.substr(1,c.length-2),{value:f,literal:c}}function C(){var a,b,c,d;for(b="",c="";lc>ic&&(a=gc[ic],i(a.charCodeAt(0)));)if(++ic,"\\"===a&&lc>ic)if(a=gc[ic],"u"===a){if(++ic,d=ic,a=r("u"))for(c+=a,b+="\\u";ic>d;++d)b+=gc[d];else ic=d,c+="u",b+="\\u";P({},dc.UnexpectedToken,"ILLEGAL")}else b+="\\",P({},dc.UnexpectedToken,"ILLEGAL");else c+=a,b+=a;return{value:c,literal:b}}function D(){var a,b,c,d;return nc=null,q(),a=ic,b=B(),c=C(),d=A(b.value,c.value),pc.tokenize?{type:$b.RegularExpression,value:d,lineNumber:jc,lineStart:kc,start:a,end:ic}:{literal:b.literal+c.literal,value:d,start:a,end:ic}}function E(){var a,b,c,d;return q(),a=ic,b={start:{line:jc,column:ic-kc}},c=D(),b.end={line:jc,column:ic-kc},pc.tokenize||(pc.tokens.length>0&&(d=pc.tokens[pc.tokens.length-1],d.range[0]===a&&"Punctuator"===d.type&&("/"===d.value||"/="===d.value)&&pc.tokens.pop()),pc.tokens.push({type:"RegularExpression",value:c.literal,range:[a,ic],loc:b})),c}function F(a){return a.type===$b.Identifier||a.type===$b.Keyword||a.type===$b.BooleanLiteral||a.type===$b.NullLiteral}function G(){var a,b;if(a=pc.tokens[pc.tokens.length-1],!a)return E();if("Punctuator"===a.type){if("]"===a.value)return v();if(")"===a.value)return b=pc.tokens[pc.openParenToken-1],!b||"Keyword"!==b.type||"if"!==b.value&&"while"!==b.value&&"for"!==b.value&&"with"!==b.value?v():E();if("}"===a.value){if(pc.tokens[pc.openCurlyToken-3]&&"Keyword"===pc.tokens[pc.openCurlyToken-3].type){if(b=pc.tokens[pc.openCurlyToken-4],!b)return v()}else{if(!pc.tokens[pc.openCurlyToken-4]||"Keyword"!==pc.tokens[pc.openCurlyToken-4].type)return v();if(b=pc.tokens[pc.openCurlyToken-5],!b)return E()}return ac.indexOf(b.value)>=0?v():E()}return E()}return"Keyword"===a.type?E():v()}function H(){var a;return q(),ic>=lc?{type:$b.EOF,lineNumber:jc,lineStart:kc,start:ic,end:ic}:(a=gc.charCodeAt(ic),h(a)?u():40===a||41===a||59===a?v():39===a||34===a?z():46===a?c(gc.charCodeAt(ic+1))?y():v():c(a)?y():pc.tokenize&&47===a?G():v())}function I(){var a,b,c;return q(),a={start:{line:jc,column:ic-kc}},b=H(),a.end={line:jc,column:ic-kc},b.type!==$b.EOF&&(c=gc.slice(b.start,b.end),pc.tokens.push({type:_b[b.type],value:c,range:[b.start,b.end],loc:a})),b}function J(){var a;return a=nc,ic=a.end,jc=a.lineNumber,kc=a.lineStart,nc="undefined"!=typeof pc.tokens?I():H(),ic=a.end,jc=a.lineNumber,kc=a.lineStart,a}function K(){var a,b,c;a=ic,b=jc,c=kc,nc="undefined"!=typeof pc.tokens?I():H(),ic=a,jc=b,kc=c}function L(a,b){this.line=a,this.column=b}function M(a,b,c,d){this.start=new L(a,b),this.end=new L(c,d)}function N(){var a,b,c,d;return a=ic,b=jc,c=kc,q(),d=jc!==b,ic=a,jc=b,kc=c,d}function O(a,c){var d,e=Array.prototype.slice.call(arguments,2),f=c.replace(/%(\d)/g,function(a,c){return b(c<e.length,"Message reference must be in range"),e[c]});throw"number"==typeof a.lineNumber?(d=new Error("Line "+a.lineNumber+": "+f),d.index=a.start,d.lineNumber=a.lineNumber,d.column=a.start-kc+1):(d=new Error("Line "+jc+": "+f),d.index=ic,d.lineNumber=jc,d.column=ic-kc+1),d.description=f,d}function P(){try{O.apply(null,arguments)}catch(a){if(!pc.errors)throw a;pc.errors.push(a)}}function Q(a){if(a.type===$b.EOF&&O(a,dc.UnexpectedEOS),a.type===$b.NumericLiteral&&O(a,dc.UnexpectedNumber),a.type===$b.StringLiteral&&O(a,dc.UnexpectedString),a.type===$b.Identifier&&O(a,dc.UnexpectedIdentifier),a.type===$b.Keyword){if(j(a.value))O(a,dc.UnexpectedReserved);else if(hc&&k(a.value))return void P(a,dc.StrictReservedWord);O(a,dc.UnexpectedToken,a.value)}O(a,dc.UnexpectedToken,a.value)}function R(a){var b=J();(b.type!==$b.Punctuator||b.value!==a)&&Q(b)}function S(a){var b=J();(b.type!==$b.Keyword||b.value!==a)&&Q(b)}function T(a){return nc.type===$b.Punctuator&&nc.value===a}function U(a){return nc.type===$b.Keyword&&nc.value===a}function V(){var a;return nc.type!==$b.Punctuator?!1:(a=nc.value,"="===a||"*="===a||"/="===a||"%="===a||"+="===a||"-="===a||"<<="===a||">>="===a||">>>="===a||"&="===a||"^="===a||"|="===a)}function W(){var a;return 59===gc.charCodeAt(ic)||T(";")?void J():(a=jc,q(),void(jc===a&&(nc.type===$b.EOF||T("}")||Q(nc))))}function X(a){return a.type===bc.Identifier||a.type===bc.MemberExpression}function Y(){var a,b=[];for(a=nc,R("[");!T("]");)T(",")?(J(),b.push(null)):(b.push(pb()),T("]")||R(","));return J(),mc.markEnd(mc.createArrayExpression(b),a)}function Z(a,b){var c,d,e;return c=hc,e=nc,d=Qb(),b&&hc&&l(a[0].name)&&P(b,dc.StrictParamName),hc=c,mc.markEnd(mc.createFunctionExpression(null,a,[],d),e)}function $(){var a,b;return b=nc,a=J(),a.type===$b.StringLiteral||a.type===$b.NumericLiteral?(hc&&a.octal&&P(a,dc.StrictOctalLiteral),mc.markEnd(mc.createLiteral(a),b)):mc.markEnd(mc.createIdentifier(a.value),b)}function _(){var a,b,c,d,e,f;return a=nc,f=nc,a.type===$b.Identifier?(c=$(),"get"!==a.value||T(":")?"set"!==a.value||T(":")?(R(":"),d=pb(),mc.markEnd(mc.createProperty("init",c,d),f)):(b=$(),R("("),a=nc,a.type!==$b.Identifier?(R(")"),P(a,dc.UnexpectedToken,a.value),d=Z([])):(e=[tb()],R(")"),d=Z(e,a)),mc.markEnd(mc.createProperty("set",b,d),f)):(b=$(),R("("),R(")"),d=Z([]),mc.markEnd(mc.createProperty("get",b,d),f))):a.type!==$b.EOF&&a.type!==$b.Punctuator?(b=$(),R(":"),d=pb(),mc.markEnd(mc.createProperty("init",b,d),f)):void Q(a)}function ab(){var a,b,c,d,e,f=[],g={},h=String;for(e=nc,R("{");!T("}");)a=_(),b=a.key.type===bc.Identifier?a.key.name:h(a.key.value),d="init"===a.kind?cc.Data:"get"===a.kind?cc.Get:cc.Set,c="$"+b,Object.prototype.hasOwnProperty.call(g,c)?(g[c]===cc.Data?hc&&d===cc.Data?P({},dc.StrictDuplicateProperty):d!==cc.Data&&P({},dc.AccessorDataProperty):d===cc.Data?P({},dc.AccessorDataProperty):g[c]&d&&P({},dc.AccessorGetSet),g[c]|=d):g[c]=d,f.push(a),T("}")||R(",");return R("}"),mc.markEnd(mc.createObjectExpression(f),e)}function bb(){var a;return R("("),a=qb(),R(")"),a}function cb(){var a,b,c,d;if(T("("))return bb();if(T("["))return Y();if(T("{"))return ab();if(a=nc.type,d=nc,a===$b.Identifier)c=mc.createIdentifier(J().value);else if(a===$b.StringLiteral||a===$b.NumericLiteral)hc&&nc.octal&&P(nc,dc.StrictOctalLiteral),c=mc.createLiteral(J());else if(a===$b.Keyword){if(U("function"))return Tb();U("this")?(J(),c=mc.createThisExpression()):Q(J())}else a===$b.BooleanLiteral?(b=J(),b.value="true"===b.value,c=mc.createLiteral(b)):a===$b.NullLiteral?(b=J(),b.value=null,c=mc.createLiteral(b)):T("/")||T("/=")?(c=mc.createLiteral("undefined"!=typeof pc.tokens?E():D()),K()):Q(J());return mc.markEnd(c,d)}function db(){var a=[];if(R("("),!T(")"))for(;lc>ic&&(a.push(pb()),!T(")"));)R(",");return R(")"),a}function eb(){var a,b;return b=nc,a=J(),F(a)||Q(a),mc.markEnd(mc.createIdentifier(a.value),b)}function fb(){return R("."),eb()}function gb(){var a;return R("["),a=qb(),R("]"),a}function hb(){var a,b,c;return c=nc,S("new"),a=jb(),b=T("(")?db():[],mc.markEnd(mc.createNewExpression(a,b),c)}function ib(){var a,b,c,d,e;for(e=nc,a=oc.allowIn,oc.allowIn=!0,b=U("new")?hb():cb(),oc.allowIn=a;;){if(T("."))d=fb(),b=mc.createMemberExpression(".",b,d);else if(T("("))c=db(),b=mc.createCallExpression(b,c);else{if(!T("["))break;d=gb(),b=mc.createMemberExpression("[",b,d)}mc.markEnd(b,e)}return b}function jb(){var a,b,c,d;for(d=nc,a=oc.allowIn,b=U("new")?hb():cb(),oc.allowIn=a;T(".")||T("[");)T("[")?(c=gb(),b=mc.createMemberExpression("[",b,c)):(c=fb(),b=mc.createMemberExpression(".",b,c)),mc.markEnd(b,d);return b}function kb(){var a,b,c=nc;return a=ib(),nc.type===$b.Punctuator&&(!T("++")&&!T("--")||N()||(hc&&a.type===bc.Identifier&&l(a.name)&&P({},dc.StrictLHSPostfix),X(a)||P({},dc.InvalidLHSInAssignment),b=J(),a=mc.markEnd(mc.createPostfixExpression(b.value,a),c))),a}function lb(){var a,b,c;return nc.type!==$b.Punctuator&&nc.type!==$b.Keyword?b=kb():T("++")||T("--")?(c=nc,a=J(),b=lb(),hc&&b.type===bc.Identifier&&l(b.name)&&P({},dc.StrictLHSPrefix),X(b)||P({},dc.InvalidLHSInAssignment),b=mc.createUnaryExpression(a.value,b),b=mc.markEnd(b,c)):T("+")||T("-")||T("~")||T("!")?(c=nc,a=J(),b=lb(),b=mc.createUnaryExpression(a.value,b),b=mc.markEnd(b,c)):U("delete")||U("void")||U("typeof")?(c=nc,a=J(),b=lb(),b=mc.createUnaryExpression(a.value,b),b=mc.markEnd(b,c),hc&&"delete"===b.operator&&b.argument.type===bc.Identifier&&P({},dc.StrictDelete)):b=kb(),b}function mb(a,b){var c=0;if(a.type!==$b.Punctuator&&a.type!==$b.Keyword)return 0;switch(a.value){case"||":c=1;break;case"&&":c=2;break;case"|":c=3;break;case"^":c=4;break;case"&":c=5;break;case"==":case"!=":case"===":case"!==":c=6;break;case"<":case">":case"<=":case">=":case"instanceof":c=7;break;case"in":c=b?7:0;break;case"<<":case">>":case">>>":c=8;break;case"+":case"-":c=9;break;case"*":case"/":case"%":c=11}return c}function nb(){var a,b,c,d,e,f,g,h,i,j;if(a=nc,i=lb(),d=nc,e=mb(d,oc.allowIn),0===e)return i;for(d.prec=e,J(),b=[a,nc],g=lb(),f=[i,d,g];(e=mb(nc,oc.allowIn))>0;){for(;f.length>2&&e<=f[f.length-2].prec;)g=f.pop(),h=f.pop().value,i=f.pop(),c=mc.createBinaryExpression(h,i,g),b.pop(),a=b[b.length-1],mc.markEnd(c,a),f.push(c);d=J(),d.prec=e,f.push(d),b.push(nc),c=lb(),f.push(c)}for(j=f.length-1,c=f[j],b.pop();j>1;)c=mc.createBinaryExpression(f[j-1].value,f[j-2],c),j-=2,a=b.pop(),mc.markEnd(c,a);return c}function ob(){var a,b,c,d,e;return e=nc,a=nb(),T("?")&&(J(),b=oc.allowIn,oc.allowIn=!0,c=pb(),oc.allowIn=b,R(":"),d=pb(),a=mc.createConditionalExpression(a,c,d),mc.markEnd(a,e)),a}function pb(){var a,b,c,d,e;return a=nc,e=nc,d=b=ob(),V()&&(X(b)||P({},dc.InvalidLHSInAssignment),hc&&b.type===bc.Identifier&&l(b.name)&&P(a,dc.StrictLHSAssignment),a=J(),c=pb(),d=mc.markEnd(mc.createAssignmentExpression(a.value,b,c),e)),d}function qb(){var a,b=nc;if(a=pb(),T(",")){for(a=mc.createSequenceExpression([a]);lc>ic&&T(",");)J(),a.expressions.push(pb());mc.markEnd(a,b)}return a}function rb(){for(var a,b=[];lc>ic&&!T("}")&&(a=Ub(),"undefined"!=typeof a);)b.push(a);return b}function sb(){var a,b;return b=nc,R("{"),a=rb(),R("}"),mc.markEnd(mc.createBlockStatement(a),b)}function tb(){var a,b;return b=nc,a=J(),a.type!==$b.Identifier&&Q(a),mc.markEnd(mc.createIdentifier(a.value),b)}function ub(a){var b,c,d=null;return c=nc,b=tb(),hc&&l(b.name)&&P({},dc.StrictVarName),"const"===a?(R("="),d=pb()):T("=")&&(J(),d=pb()),mc.markEnd(mc.createVariableDeclarator(b,d),c)}function vb(a){var b=[];do{if(b.push(ub(a)),!T(","))break;J()}while(lc>ic);return b}function wb(){var a;return S("var"),a=vb(),W(),mc.createVariableDeclaration(a,"var")}function xb(a){var b,c;return c=nc,S(a),b=vb(a),W(),mc.markEnd(mc.createVariableDeclaration(b,a),c)}function yb(){return R(";"),mc.createEmptyStatement()}function zb(){var a=qb();return W(),mc.createExpressionStatement(a)}function Ab(){var a,b,c;return S("if"),R("("),a=qb(),R(")"),b=Pb(),U("else")?(J(),c=Pb()):c=null,mc.createIfStatement(a,b,c)}function Bb(){var a,b,c;return S("do"),c=oc.inIteration,oc.inIteration=!0,a=Pb(),oc.inIteration=c,S("while"),R("("),b=qb(),R(")"),T(";")&&J(),mc.createDoWhileStatement(a,b)}function Cb(){var a,b,c;return S("while"),R("("),a=qb(),R(")"),c=oc.inIteration,oc.inIteration=!0,b=Pb(),oc.inIteration=c,mc.createWhileStatement(a,b)}function Db(){var a,b,c;return c=nc,a=J(),b=vb(),mc.markEnd(mc.createVariableDeclaration(b,a.value),c)}function Eb(){var a,b,c,d,e,f,g;return a=b=c=null,S("for"),R("("),T(";")?J():(U("var")||U("let")?(oc.allowIn=!1,a=Db(),oc.allowIn=!0,1===a.declarations.length&&U("in")&&(J(),d=a,e=qb(),a=null)):(oc.allowIn=!1,a=qb(),oc.allowIn=!0,U("in")&&(X(a)||P({},dc.InvalidLHSInForIn),J(),d=a,e=qb(),a=null)),"undefined"==typeof d&&R(";")),"undefined"==typeof d&&(T(";")||(b=qb()),R(";"),T(")")||(c=qb())),R(")"),g=oc.inIteration,oc.inIteration=!0,f=Pb(),oc.inIteration=g,"undefined"==typeof d?mc.createForStatement(a,b,c,f):mc.createForInStatement(d,e,f)}function Fb(){var a,b=null;return S("continue"),59===gc.charCodeAt(ic)?(J(),oc.inIteration||O({},dc.IllegalContinue),mc.createContinueStatement(null)):N()?(oc.inIteration||O({},dc.IllegalContinue),mc.createContinueStatement(null)):(nc.type===$b.Identifier&&(b=tb(),a="$"+b.name,Object.prototype.hasOwnProperty.call(oc.labelSet,a)||O({},dc.UnknownLabel,b.name)),W(),null!==b||oc.inIteration||O({},dc.IllegalContinue),mc.createContinueStatement(b))}function Gb(){var a,b=null;return S("break"),59===gc.charCodeAt(ic)?(J(),oc.inIteration||oc.inSwitch||O({},dc.IllegalBreak),mc.createBreakStatement(null)):N()?(oc.inIteration||oc.inSwitch||O({},dc.IllegalBreak),mc.createBreakStatement(null)):(nc.type===$b.Identifier&&(b=tb(),a="$"+b.name,Object.prototype.hasOwnProperty.call(oc.labelSet,a)||O({},dc.UnknownLabel,b.name)),W(),null!==b||oc.inIteration||oc.inSwitch||O({},dc.IllegalBreak),mc.createBreakStatement(b))}function Hb(){var a=null;return S("return"),oc.inFunctionBody||P({},dc.IllegalReturn),32===gc.charCodeAt(ic)&&h(gc.charCodeAt(ic+1))?(a=qb(),W(),mc.createReturnStatement(a)):N()?mc.createReturnStatement(null):(T(";")||T("}")||nc.type===$b.EOF||(a=qb()),W(),mc.createReturnStatement(a))}function Ib(){var a,b;return hc&&(q(),P({},dc.StrictModeWith)),S("with"),R("("),a=qb(),R(")"),b=Pb(),mc.createWithStatement(a,b)}function Jb(){var a,b,c,d=[];for(c=nc,U("default")?(J(),a=null):(S("case"),a=qb()),R(":");lc>ic&&!(T("}")||U("default")||U("case"));)b=Pb(),d.push(b);return mc.markEnd(mc.createSwitchCase(a,d),c)}function Kb(){var a,b,c,d,e;if(S("switch"),R("("),a=qb(),R(")"),R("{"),b=[],T("}"))return J(),mc.createSwitchStatement(a,b);for(d=oc.inSwitch,oc.inSwitch=!0,e=!1;lc>ic&&!T("}");)c=Jb(),null===c.test&&(e&&O({},dc.MultipleDefaultsInSwitch),e=!0),b.push(c);return oc.inSwitch=d,R("}"),mc.createSwitchStatement(a,b)}function Lb(){var a;return S("throw"),N()&&O({},dc.NewlineAfterThrow),a=qb(),W(),mc.createThrowStatement(a)}function Mb(){var a,b,c;return c=nc,S("catch"),R("("),T(")")&&Q(nc),a=tb(),hc&&l(a.name)&&P({},dc.StrictCatchVariable),R(")"),b=sb(),mc.markEnd(mc.createCatchClause(a,b),c)}function Nb(){var a,b=[],c=null;return S("try"),a=sb(),U("catch")&&b.push(Mb()),U("finally")&&(J(),c=sb()),0!==b.length||c||O({},dc.NoCatchOrFinally),mc.createTryStatement(a,[],b,c)}function Ob(){return S("debugger"),W(),mc.createDebuggerStatement()}function Pb(){var a,b,c,d,e=nc.type;if(e===$b.EOF&&Q(nc),e===$b.Punctuator&&"{"===nc.value)return sb();if(d=nc,e===$b.Punctuator)switch(nc.value){case";":return mc.markEnd(yb(),d);case"(":return mc.markEnd(zb(),d)}if(e===$b.Keyword)switch(nc.value){case"break":return mc.markEnd(Gb(),d);case"continue":return mc.markEnd(Fb(),d);case"debugger":return mc.markEnd(Ob(),d);case"do":return mc.markEnd(Bb(),d);case"for":return mc.markEnd(Eb(),d);case"function":return mc.markEnd(Sb(),d);case"if":return mc.markEnd(Ab(),d);case"return":return mc.markEnd(Hb(),d);case"switch":return mc.markEnd(Kb(),d);case"throw":return mc.markEnd(Lb(),d);case"try":return mc.markEnd(Nb(),d);case"var":return mc.markEnd(wb(),d);case"while":return mc.markEnd(Cb(),d);case"with":return mc.markEnd(Ib(),d)}return a=qb(),a.type===bc.Identifier&&T(":")?(J(),c="$"+a.name,Object.prototype.hasOwnProperty.call(oc.labelSet,c)&&O({},dc.Redeclaration,"Label",a.name),oc.labelSet[c]=!0,b=Pb(),delete oc.labelSet[c],mc.markEnd(mc.createLabeledStatement(a,b),d)):(W(),mc.markEnd(mc.createExpressionStatement(a),d))}function Qb(){var a,b,c,d,e,f,g,h,i,j=[];for(i=nc,R("{");lc>ic&&nc.type===$b.StringLiteral&&(b=nc,a=Ub(),j.push(a),a.expression.type===bc.Literal);)c=gc.slice(b.start+1,b.end-1),"use strict"===c?(hc=!0,d&&P(d,dc.StrictOctalLiteral)):!d&&b.octal&&(d=b);for(e=oc.labelSet,f=oc.inIteration,g=oc.inSwitch,h=oc.inFunctionBody,oc.labelSet={},oc.inIteration=!1,oc.inSwitch=!1,oc.inFunctionBody=!0;lc>ic&&!T("}")&&(a=Ub(),"undefined"!=typeof a);)j.push(a);return R("}"),oc.labelSet=e,oc.inIteration=f,oc.inSwitch=g,oc.inFunctionBody=h,mc.markEnd(mc.createBlockStatement(j),i)}function Rb(a){var b,c,d,e,f,g,h=[];if(R("("),!T(")"))for(e={};lc>ic&&(c=nc,b=tb(),f="$"+c.value,hc?(l(c.value)&&(d=c,g=dc.StrictParamName),Object.prototype.hasOwnProperty.call(e,f)&&(d=c,g=dc.StrictParamDupe)):a||(l(c.value)?(a=c,g=dc.StrictParamName):k(c.value)?(a=c,g=dc.StrictReservedWord):Object.prototype.hasOwnProperty.call(e,f)&&(a=c,g=dc.StrictParamDupe)),h.push(b),e[f]=!0,!T(")"));)R(",");return R(")"),{params:h,stricted:d,firstRestricted:a,message:g}}function Sb(){var a,b,c,d,e,f,g,h,i,j=[];return i=nc,S("function"),c=nc,a=tb(),hc?l(c.value)&&P(c,dc.StrictFunctionName):l(c.value)?(f=c,g=dc.StrictFunctionName):k(c.value)&&(f=c,g=dc.StrictReservedWord),e=Rb(f),j=e.params,d=e.stricted,f=e.firstRestricted,e.message&&(g=e.message),h=hc,b=Qb(),hc&&f&&O(f,g),hc&&d&&P(d,g),hc=h,mc.markEnd(mc.createFunctionDeclaration(a,j,[],b),i)}function Tb(){var a,b,c,d,e,f,g,h,i=null,j=[];return h=nc,S("function"),T("(")||(a=nc,i=tb(),hc?l(a.value)&&P(a,dc.StrictFunctionName):l(a.value)?(c=a,d=dc.StrictFunctionName):k(a.value)&&(c=a,d=dc.StrictReservedWord)),e=Rb(c),j=e.params,b=e.stricted,c=e.firstRestricted,e.message&&(d=e.message),g=hc,f=Qb(),hc&&c&&O(c,d),hc&&b&&P(b,d),hc=g,mc.markEnd(mc.createFunctionExpression(i,j,[],f),h)}function Ub(){if(nc.type===$b.Keyword)switch(nc.value){case"const":case"let":return xb(nc.value);case"function":return Sb();default:return Pb()}return nc.type!==$b.EOF?Pb():void 0}function Vb(){for(var a,b,c,d,e=[];lc>ic&&(b=nc,b.type===$b.StringLiteral)&&(a=Ub(),e.push(a),a.expression.type===bc.Literal);)c=gc.slice(b.start+1,b.end-1),"use strict"===c?(hc=!0,d&&P(d,dc.StrictOctalLiteral)):!d&&b.octal&&(d=b);for(;lc>ic&&(a=Ub(),"undefined"!=typeof a);)e.push(a);return e}function Wb(){var a,b;return q(),K(),b=nc,hc=!1,a=Vb(),mc.markEnd(mc.createProgram(a),b)}function Xb(){var a,b,c,d=[];for(a=0;a<pc.tokens.length;++a)b=pc.tokens[a],c={type:b.type,value:b.value},pc.range&&(c.range=b.range),pc.loc&&(c.loc=b.loc),d.push(c);pc.tokens=d}function Yb(a,b){var c,d,e;c=String,"string"==typeof a||a instanceof String||(a=c(a)),mc=fc,gc=a,ic=0,jc=gc.length>0?1:0,kc=0,lc=gc.length,nc=null,oc={allowIn:!0,labelSet:{},inFunctionBody:!1,inIteration:!1,inSwitch:!1,lastCommentStart:-1},pc={},b=b||{},b.tokens=!0,pc.tokens=[],pc.tokenize=!0,pc.openParenToken=-1,pc.openCurlyToken=-1,pc.range="boolean"==typeof b.range&&b.range,pc.loc="boolean"==typeof b.loc&&b.loc,"boolean"==typeof b.comment&&b.comment&&(pc.comments=[]),"boolean"==typeof b.tolerant&&b.tolerant&&(pc.errors=[]);try{if(K(),nc.type===$b.EOF)return pc.tokens;for(d=J();nc.type!==$b.EOF;)try{d=J()}catch(f){if(d=nc,pc.errors){pc.errors.push(f);break}throw f}Xb(),e=pc.tokens,"undefined"!=typeof pc.comments&&(e.comments=pc.comments),"undefined"!=typeof pc.errors&&(e.errors=pc.errors)}catch(g){throw g}finally{pc={}}return e}function Zb(a,b){var c,d;d=String,"string"==typeof a||a instanceof String||(a=d(a)),mc=fc,gc=a,ic=0,jc=gc.length>0?1:0,kc=0,lc=gc.length,nc=null,oc={allowIn:!0,labelSet:{},inFunctionBody:!1,inIteration:!1,inSwitch:!1,lastCommentStart:-1},pc={},"undefined"!=typeof b&&(pc.range="boolean"==typeof b.range&&b.range,pc.loc="boolean"==typeof b.loc&&b.loc,pc.attachComment="boolean"==typeof b.attachComment&&b.attachComment,pc.loc&&null!==b.source&&void 0!==b.source&&(pc.source=d(b.source)),"boolean"==typeof b.tokens&&b.tokens&&(pc.tokens=[]),"boolean"==typeof b.comment&&b.comment&&(pc.comments=[]),"boolean"==typeof b.tolerant&&b.tolerant&&(pc.errors=[]),pc.attachComment&&(pc.range=!0,pc.comments=[],pc.bottomRightStack=[],pc.trailingComments=[],pc.leadingComments=[]));try{c=Wb(),"undefined"!=typeof pc.comments&&(c.comments=pc.comments),"undefined"!=typeof pc.tokens&&(Xb(),c.tokens=pc.tokens),"undefined"!=typeof pc.errors&&(c.errors=pc.errors)}catch(e){throw e}finally{pc={}}return c}var $b,_b,ac,bc,cc,dc,ec,fc,gc,hc,ic,jc,kc,lc,mc,nc,oc,pc;$b={BooleanLiteral:1,EOF:2,Identifier:3,Keyword:4,NullLiteral:5,NumericLiteral:6,Punctuator:7,StringLiteral:8,RegularExpression:9},_b={},_b[$b.BooleanLiteral]="Boolean",_b[$b.EOF]="<end>",_b[$b.Identifier]="Identifier",_b[$b.Keyword]="Keyword",_b[$b.NullLiteral]="Null",_b[$b.NumericLiteral]="Numeric",_b[$b.Punctuator]="Punctuator",_b[$b.StringLiteral]="String",_b[$b.RegularExpression]="RegularExpression",ac=["(","{","[","in","typeof","instanceof","new","return","case","delete","throw","void","=","+=","-=","*=","/=","%=","<<=",">>=",">>>=","&=","|=","^=",",","+","-","*","/","%","++","--","<<",">>",">>>","&","|","^","!","~","&&","||","?",":","===","==",">=","<=","<",">","!=","!=="],bc={AssignmentExpression:"AssignmentExpression",ArrayExpression:"ArrayExpression",BlockStatement:"BlockStatement",BinaryExpression:"BinaryExpression",BreakStatement:"BreakStatement",CallExpression:"CallExpression",CatchClause:"CatchClause",ConditionalExpression:"ConditionalExpression",ContinueStatement:"ContinueStatement",DoWhileStatement:"DoWhileStatement",DebuggerStatement:"DebuggerStatement",EmptyStatement:"EmptyStatement",ExpressionStatement:"ExpressionStatement",ForStatement:"ForStatement",ForInStatement:"ForInStatement",FunctionDeclaration:"FunctionDeclaration",FunctionExpression:"FunctionExpression",Identifier:"Identifier",IfStatement:"IfStatement",Literal:"Literal",LabeledStatement:"LabeledStatement",LogicalExpression:"LogicalExpression",MemberExpression:"MemberExpression",NewExpression:"NewExpression",ObjectExpression:"ObjectExpression",Program:"Program",Property:"Property",ReturnStatement:"ReturnStatement",SequenceExpression:"SequenceExpression",SwitchStatement:"SwitchStatement",SwitchCase:"SwitchCase",ThisExpression:"ThisExpression",ThrowStatement:"ThrowStatement",TryStatement:"TryStatement",UnaryExpression:"UnaryExpression",UpdateExpression:"UpdateExpression",VariableDeclaration:"VariableDeclaration",VariableDeclarator:"VariableDeclarator",WhileStatement:"WhileStatement",WithStatement:"WithStatement"},cc={Data:1,Get:2,Set:4},dc={UnexpectedToken:"Unexpected token %0",UnexpectedNumber:"Unexpected number",UnexpectedString:"Unexpected string",UnexpectedIdentifier:"Unexpected identifier",UnexpectedReserved:"Unexpected reserved word",UnexpectedEOS:"Unexpected end of input",NewlineAfterThrow:"Illegal newline after throw",InvalidRegExp:"Invalid regular expression",UnterminatedRegExp:"Invalid regular expression: missing /",InvalidLHSInAssignment:"Invalid left-hand side in assignment",InvalidLHSInForIn:"Invalid left-hand side in for-in",MultipleDefaultsInSwitch:"More than one default clause in switch statement",NoCatchOrFinally:"Missing catch or finally after try",UnknownLabel:"Undefined label '%0'",Redeclaration:"%0 '%1' has already been declared",IllegalContinue:"Illegal continue statement",IllegalBreak:"Illegal break statement",IllegalReturn:"Illegal return statement",StrictModeWith:"Strict mode code may not include a with statement",StrictCatchVariable:"Catch variable may not be eval or arguments in strict mode",StrictVarName:"Variable name may not be eval or arguments in strict mode",StrictParamName:"Parameter name eval or arguments is not allowed in strict mode",StrictParamDupe:"Strict mode function may not have duplicate parameter names",StrictFunctionName:"Function name may not be eval or arguments in strict mode",StrictOctalLiteral:"Octal literals are not allowed in strict mode.",StrictDelete:"Delete of an unqualified identifier in strict mode.",StrictDuplicateProperty:"Duplicate data property in object literal not allowed in strict mode",AccessorDataProperty:"Object literal may not have data and accessor property with the same name",AccessorGetSet:"Object literal may not have multiple get/set accessors with the same name",StrictLHSAssignment:"Assignment to eval or arguments is not allowed in strict mode",StrictLHSPostfix:"Postfix increment/decrement may not have eval or arguments operand in strict mode",StrictLHSPrefix:"Prefix increment/decrement may not have eval or arguments operand in strict mode",StrictReservedWord:"Use of future reserved word in strict mode"},ec={NonAsciiIdentifierStart:new RegExp("[\xaa\xb5\xba\xc0-\xd6\xd8-\xf6\xf8-\u02c1\u02c6-\u02d1\u02e0-\u02e4\u02ec\u02ee\u0370-\u0374\u0376\u0377\u037a-\u037d\u0386\u0388-\u038a\u038c\u038e-\u03a1\u03a3-\u03f5\u03f7-\u0481\u048a-\u0527\u0531-\u0556\u0559\u0561-\u0587\u05d0-\u05ea\u05f0-\u05f2\u0620-\u064a\u066e\u066f\u0671-\u06d3\u06d5\u06e5\u06e6\u06ee\u06ef\u06fa-\u06fc\u06ff\u0710\u0712-\u072f\u074d-\u07a5\u07b1\u07ca-\u07ea\u07f4\u07f5\u07fa\u0800-\u0815\u081a\u0824\u0828\u0840-\u0858\u08a0\u08a2-\u08ac\u0904-\u0939\u093d\u0950\u0958-\u0961\u0971-\u0977\u0979-\u097f\u0985-\u098c\u098f\u0990\u0993-\u09a8\u09aa-\u09b0\u09b2\u09b6-\u09b9\u09bd\u09ce\u09dc\u09dd\u09df-\u09e1\u09f0\u09f1\u0a05-\u0a0a\u0a0f\u0a10\u0a13-\u0a28\u0a2a-\u0a30\u0a32\u0a33\u0a35\u0a36\u0a38\u0a39\u0a59-\u0a5c\u0a5e\u0a72-\u0a74\u0a85-\u0a8d\u0a8f-\u0a91\u0a93-\u0aa8\u0aaa-\u0ab0\u0ab2\u0ab3\u0ab5-\u0ab9\u0abd\u0ad0\u0ae0\u0ae1\u0b05-\u0b0c\u0b0f\u0b10\u0b13-\u0b28\u0b2a-\u0b30\u0b32\u0b33\u0b35-\u0b39\u0b3d\u0b5c\u0b5d\u0b5f-\u0b61\u0b71\u0b83\u0b85-\u0b8a\u0b8e-\u0b90\u0b92-\u0b95\u0b99\u0b9a\u0b9c\u0b9e\u0b9f\u0ba3\u0ba4\u0ba8-\u0baa\u0bae-\u0bb9\u0bd0\u0c05-\u0c0c\u0c0e-\u0c10\u0c12-\u0c28\u0c2a-\u0c33\u0c35-\u0c39\u0c3d\u0c58\u0c59\u0c60\u0c61\u0c85-\u0c8c\u0c8e-\u0c90\u0c92-\u0ca8\u0caa-\u0cb3\u0cb5-\u0cb9\u0cbd\u0cde\u0ce0\u0ce1\u0cf1\u0cf2\u0d05-\u0d0c\u0d0e-\u0d10\u0d12-\u0d3a\u0d3d\u0d4e\u0d60\u0d61\u0d7a-\u0d7f\u0d85-\u0d96\u0d9a-\u0db1\u0db3-\u0dbb\u0dbd\u0dc0-\u0dc6\u0e01-\u0e30\u0e32\u0e33\u0e40-\u0e46\u0e81\u0e82\u0e84\u0e87\u0e88\u0e8a\u0e8d\u0e94-\u0e97\u0e99-\u0e9f\u0ea1-\u0ea3\u0ea5\u0ea7\u0eaa\u0eab\u0ead-\u0eb0\u0eb2\u0eb3\u0ebd\u0ec0-\u0ec4\u0ec6\u0edc-\u0edf\u0f00\u0f40-\u0f47\u0f49-\u0f6c\u0f88-\u0f8c\u1000-\u102a\u103f\u1050-\u1055\u105a-\u105d\u1061\u1065\u1066\u106e-\u1070\u1075-\u1081\u108e\u10a0-\u10c5\u10c7\u10cd\u10d0-\u10fa\u10fc-\u1248\u124a-\u124d\u1250-\u1256\u1258\u125a-\u125d\u1260-\u1288\u128a-\u128d\u1290-\u12b0\u12b2-\u12b5\u12b8-\u12be\u12c0\u12c2-\u12c5\u12c8-\u12d6\u12d8-\u1310\u1312-\u1315\u1318-\u135a\u1380-\u138f\u13a0-\u13f4\u1401-\u166c\u166f-\u167f\u1681-\u169a\u16a0-\u16ea\u16ee-\u16f0\u1700-\u170c\u170e-\u1711\u1720-\u1731\u1740-\u1751\u1760-\u176c\u176e-\u1770\u1780-\u17b3\u17d7\u17dc\u1820-\u1877\u1880-\u18a8\u18aa\u18b0-\u18f5\u1900-\u191c\u1950-\u196d\u1970-\u1974\u1980-\u19ab\u19c1-\u19c7\u1a00-\u1a16\u1a20-\u1a54\u1aa7\u1b05-\u1b33\u1b45-\u1b4b\u1b83-\u1ba0\u1bae\u1baf\u1bba-\u1be5\u1c00-\u1c23\u1c4d-\u1c4f\u1c5a-\u1c7d\u1ce9-\u1cec\u1cee-\u1cf1\u1cf5\u1cf6\u1d00-\u1dbf\u1e00-\u1f15\u1f18-\u1f1d\u1f20-\u1f45\u1f48-\u1f4d\u1f50-\u1f57\u1f59\u1f5b\u1f5d\u1f5f-\u1f7d\u1f80-\u1fb4\u1fb6-\u1fbc\u1fbe\u1fc2-\u1fc4\u1fc6-\u1fcc\u1fd0-\u1fd3\u1fd6-\u1fdb\u1fe0-\u1fec\u1ff2-\u1ff4\u1ff6-\u1ffc\u2071\u207f\u2090-\u209c\u2102\u2107\u210a-\u2113\u2115\u2119-\u211d\u2124\u2126\u2128\u212a-\u212d\u212f-\u2139\u213c-\u213f\u2145-\u2149\u214e\u2160-\u2188\u2c00-\u2c2e\u2c30-\u2c5e\u2c60-\u2ce4\u2ceb-\u2cee\u2cf2\u2cf3\u2d00-\u2d25\u2d27\u2d2d\u2d30-\u2d67\u2d6f\u2d80-\u2d96\u2da0-\u2da6\u2da8-\u2dae\u2db0-\u2db6\u2db8-\u2dbe\u2dc0-\u2dc6\u2dc8-\u2dce\u2dd0-\u2dd6\u2dd8-\u2dde\u2e2f\u3005-\u3007\u3021-\u3029\u3031-\u3035\u3038-\u303c\u3041-\u3096\u309d-\u309f\u30a1-\u30fa\u30fc-\u30ff\u3105-\u312d\u3131-\u318e\u31a0-\u31ba\u31f0-\u31ff\u3400-\u4db5\u4e00-\u9fcc\ua000-\ua48c\ua4d0-\ua4fd\ua500-\ua60c\ua610-\ua61f\ua62a\ua62b\ua640-\ua66e\ua67f-\ua697\ua6a0-\ua6ef\ua717-\ua71f\ua722-\ua788\ua78b-\ua78e\ua790-\ua793\ua7a0-\ua7aa\ua7f8-\ua801\ua803-\ua805\ua807-\ua80a\ua80c-\ua822\ua840-\ua873\ua882-\ua8b3\ua8f2-\ua8f7\ua8fb\ua90a-\ua925\ua930-\ua946\ua960-\ua97c\ua984-\ua9b2\ua9cf\uaa00-\uaa28\uaa40-\uaa42\uaa44-\uaa4b\uaa60-\uaa76\uaa7a\uaa80-\uaaaf\uaab1\uaab5\uaab6\uaab9-\uaabd\uaac0\uaac2\uaadb-\uaadd\uaae0-\uaaea\uaaf2-\uaaf4\uab01-\uab06\uab09-\uab0e\uab11-\uab16\uab20-\uab26\uab28-\uab2e\uabc0-\uabe2\uac00-\ud7a3\ud7b0-\ud7c6\ud7cb-\ud7fb\uf900-\ufa6d\ufa70-\ufad9\ufb00-\ufb06\ufb13-\ufb17\ufb1d\ufb1f-\ufb28\ufb2a-\ufb36\ufb38-\ufb3c\ufb3e\ufb40\ufb41\ufb43\ufb44\ufb46-\ufbb1\ufbd3-\ufd3d\ufd50-\ufd8f\ufd92-\ufdc7\ufdf0-\ufdfb\ufe70-\ufe74\ufe76-\ufefc\uff21-\uff3a\uff41-\uff5a\uff66-\uffbe\uffc2-\uffc7\uffca-\uffcf\uffd2-\uffd7\uffda-\uffdc]"),NonAsciiIdentifierPart:new RegExp("[\xaa\xb5\xba\xc0-\xd6\xd8-\xf6\xf8-\u02c1\u02c6-\u02d1\u02e0-\u02e4\u02ec\u02ee\u0300-\u0374\u0376\u0377\u037a-\u037d\u0386\u0388-\u038a\u038c\u038e-\u03a1\u03a3-\u03f5\u03f7-\u0481\u0483-\u0487\u048a-\u0527\u0531-\u0556\u0559\u0561-\u0587\u0591-\u05bd\u05bf\u05c1\u05c2\u05c4\u05c5\u05c7\u05d0-\u05ea\u05f0-\u05f2\u0610-\u061a\u0620-\u0669\u066e-\u06d3\u06d5-\u06dc\u06df-\u06e8\u06ea-\u06fc\u06ff\u0710-\u074a\u074d-\u07b1\u07c0-\u07f5\u07fa\u0800-\u082d\u0840-\u085b\u08a0\u08a2-\u08ac\u08e4-\u08fe\u0900-\u0963\u0966-\u096f\u0971-\u0977\u0979-\u097f\u0981-\u0983\u0985-\u098c\u098f\u0990\u0993-\u09a8\u09aa-\u09b0\u09b2\u09b6-\u09b9\u09bc-\u09c4\u09c7\u09c8\u09cb-\u09ce\u09d7\u09dc\u09dd\u09df-\u09e3\u09e6-\u09f1\u0a01-\u0a03\u0a05-\u0a0a\u0a0f\u0a10\u0a13-\u0a28\u0a2a-\u0a30\u0a32\u0a33\u0a35\u0a36\u0a38\u0a39\u0a3c\u0a3e-\u0a42\u0a47\u0a48\u0a4b-\u0a4d\u0a51\u0a59-\u0a5c\u0a5e\u0a66-\u0a75\u0a81-\u0a83\u0a85-\u0a8d\u0a8f-\u0a91\u0a93-\u0aa8\u0aaa-\u0ab0\u0ab2\u0ab3\u0ab5-\u0ab9\u0abc-\u0ac5\u0ac7-\u0ac9\u0acb-\u0acd\u0ad0\u0ae0-\u0ae3\u0ae6-\u0aef\u0b01-\u0b03\u0b05-\u0b0c\u0b0f\u0b10\u0b13-\u0b28\u0b2a-\u0b30\u0b32\u0b33\u0b35-\u0b39\u0b3c-\u0b44\u0b47\u0b48\u0b4b-\u0b4d\u0b56\u0b57\u0b5c\u0b5d\u0b5f-\u0b63\u0b66-\u0b6f\u0b71\u0b82\u0b83\u0b85-\u0b8a\u0b8e-\u0b90\u0b92-\u0b95\u0b99\u0b9a\u0b9c\u0b9e\u0b9f\u0ba3\u0ba4\u0ba8-\u0baa\u0bae-\u0bb9\u0bbe-\u0bc2\u0bc6-\u0bc8\u0bca-\u0bcd\u0bd0\u0bd7\u0be6-\u0bef\u0c01-\u0c03\u0c05-\u0c0c\u0c0e-\u0c10\u0c12-\u0c28\u0c2a-\u0c33\u0c35-\u0c39\u0c3d-\u0c44\u0c46-\u0c48\u0c4a-\u0c4d\u0c55\u0c56\u0c58\u0c59\u0c60-\u0c63\u0c66-\u0c6f\u0c82\u0c83\u0c85-\u0c8c\u0c8e-\u0c90\u0c92-\u0ca8\u0caa-\u0cb3\u0cb5-\u0cb9\u0cbc-\u0cc4\u0cc6-\u0cc8\u0cca-\u0ccd\u0cd5\u0cd6\u0cde\u0ce0-\u0ce3\u0ce6-\u0cef\u0cf1\u0cf2\u0d02\u0d03\u0d05-\u0d0c\u0d0e-\u0d10\u0d12-\u0d3a\u0d3d-\u0d44\u0d46-\u0d48\u0d4a-\u0d4e\u0d57\u0d60-\u0d63\u0d66-\u0d6f\u0d7a-\u0d7f\u0d82\u0d83\u0d85-\u0d96\u0d9a-\u0db1\u0db3-\u0dbb\u0dbd\u0dc0-\u0dc6\u0dca\u0dcf-\u0dd4\u0dd6\u0dd8-\u0ddf\u0df2\u0df3\u0e01-\u0e3a\u0e40-\u0e4e\u0e50-\u0e59\u0e81\u0e82\u0e84\u0e87\u0e88\u0e8a\u0e8d\u0e94-\u0e97\u0e99-\u0e9f\u0ea1-\u0ea3\u0ea5\u0ea7\u0eaa\u0eab\u0ead-\u0eb9\u0ebb-\u0ebd\u0ec0-\u0ec4\u0ec6\u0ec8-\u0ecd\u0ed0-\u0ed9\u0edc-\u0edf\u0f00\u0f18\u0f19\u0f20-\u0f29\u0f35\u0f37\u0f39\u0f3e-\u0f47\u0f49-\u0f6c\u0f71-\u0f84\u0f86-\u0f97\u0f99-\u0fbc\u0fc6\u1000-\u1049\u1050-\u109d\u10a0-\u10c5\u10c7\u10cd\u10d0-\u10fa\u10fc-\u1248\u124a-\u124d\u1250-\u1256\u1258\u125a-\u125d\u1260-\u1288\u128a-\u128d\u1290-\u12b0\u12b2-\u12b5\u12b8-\u12be\u12c0\u12c2-\u12c5\u12c8-\u12d6\u12d8-\u1310\u1312-\u1315\u1318-\u135a\u135d-\u135f\u1380-\u138f\u13a0-\u13f4\u1401-\u166c\u166f-\u167f\u1681-\u169a\u16a0-\u16ea\u16ee-\u16f0\u1700-\u170c\u170e-\u1714\u1720-\u1734\u1740-\u1753\u1760-\u176c\u176e-\u1770\u1772\u1773\u1780-\u17d3\u17d7\u17dc\u17dd\u17e0-\u17e9\u180b-\u180d\u1810-\u1819\u1820-\u1877\u1880-\u18aa\u18b0-\u18f5\u1900-\u191c\u1920-\u192b\u1930-\u193b\u1946-\u196d\u1970-\u1974\u1980-\u19ab\u19b0-\u19c9\u19d0-\u19d9\u1a00-\u1a1b\u1a20-\u1a5e\u1a60-\u1a7c\u1a7f-\u1a89\u1a90-\u1a99\u1aa7\u1b00-\u1b4b\u1b50-\u1b59\u1b6b-\u1b73\u1b80-\u1bf3\u1c00-\u1c37\u1c40-\u1c49\u1c4d-\u1c7d\u1cd0-\u1cd2\u1cd4-\u1cf6\u1d00-\u1de6\u1dfc-\u1f15\u1f18-\u1f1d\u1f20-\u1f45\u1f48-\u1f4d\u1f50-\u1f57\u1f59\u1f5b\u1f5d\u1f5f-\u1f7d\u1f80-\u1fb4\u1fb6-\u1fbc\u1fbe\u1fc2-\u1fc4\u1fc6-\u1fcc\u1fd0-\u1fd3\u1fd6-\u1fdb\u1fe0-\u1fec\u1ff2-\u1ff4\u1ff6-\u1ffc\u200c\u200d\u203f\u2040\u2054\u2071\u207f\u2090-\u209c\u20d0-\u20dc\u20e1\u20e5-\u20f0\u2102\u2107\u210a-\u2113\u2115\u2119-\u211d\u2124\u2126\u2128\u212a-\u212d\u212f-\u2139\u213c-\u213f\u2145-\u2149\u214e\u2160-\u2188\u2c00-\u2c2e\u2c30-\u2c5e\u2c60-\u2ce4\u2ceb-\u2cf3\u2d00-\u2d25\u2d27\u2d2d\u2d30-\u2d67\u2d6f\u2d7f-\u2d96\u2da0-\u2da6\u2da8-\u2dae\u2db0-\u2db6\u2db8-\u2dbe\u2dc0-\u2dc6\u2dc8-\u2dce\u2dd0-\u2dd6\u2dd8-\u2dde\u2de0-\u2dff\u2e2f\u3005-\u3007\u3021-\u302f\u3031-\u3035\u3038-\u303c\u3041-\u3096\u3099\u309a\u309d-\u309f\u30a1-\u30fa\u30fc-\u30ff\u3105-\u312d\u3131-\u318e\u31a0-\u31ba\u31f0-\u31ff\u3400-\u4db5\u4e00-\u9fcc\ua000-\ua48c\ua4d0-\ua4fd\ua500-\ua60c\ua610-\ua62b\ua640-\ua66f\ua674-\ua67d\ua67f-\ua697\ua69f-\ua6f1\ua717-\ua71f\ua722-\ua788\ua78b-\ua78e\ua790-\ua793\ua7a0-\ua7aa\ua7f8-\ua827\ua840-\ua873\ua880-\ua8c4\ua8d0-\ua8d9\ua8e0-\ua8f7\ua8fb\ua900-\ua92d\ua930-\ua953\ua960-\ua97c\ua980-\ua9c0\ua9cf-\ua9d9\uaa00-\uaa36\uaa40-\uaa4d\uaa50-\uaa59\uaa60-\uaa76\uaa7a\uaa7b\uaa80-\uaac2\uaadb-\uaadd\uaae0-\uaaef\uaaf2-\uaaf6\uab01-\uab06\uab09-\uab0e\uab11-\uab16\uab20-\uab26\uab28-\uab2e\uabc0-\uabea\uabec\uabed\uabf0-\uabf9\uac00-\ud7a3\ud7b0-\ud7c6\ud7cb-\ud7fb\uf900-\ufa6d\ufa70-\ufad9\ufb00-\ufb06\ufb13-\ufb17\ufb1d-\ufb28\ufb2a-\ufb36\ufb38-\ufb3c\ufb3e\ufb40\ufb41\ufb43\ufb44\ufb46-\ufbb1\ufbd3-\ufd3d\ufd50-\ufd8f\ufd92-\ufdc7\ufdf0-\ufdfb\ufe00-\ufe0f\ufe20-\ufe26\ufe33\ufe34\ufe4d-\ufe4f\ufe70-\ufe74\ufe76-\ufefc\uff10-\uff19\uff21-\uff3a\uff3f\uff41-\uff5a\uff66-\uffbe\uffc2-\uffc7\uffca-\uffcf\uffd2-\uffd7\uffda-\uffdc]")},fc={name:"SyntaxTree",processComment:function(a){var b,c;
+if(!(a.type===bc.Program&&a.body.length>0)){for(pc.trailingComments.length>0?pc.trailingComments[0].range[0]>=a.range[1]?(c=pc.trailingComments,pc.trailingComments=[]):pc.trailingComments.length=0:pc.bottomRightStack.length>0&&pc.bottomRightStack[pc.bottomRightStack.length-1].trailingComments&&pc.bottomRightStack[pc.bottomRightStack.length-1].trailingComments[0].range[0]>=a.range[1]&&(c=pc.bottomRightStack[pc.bottomRightStack.length-1].trailingComments,delete pc.bottomRightStack[pc.bottomRightStack.length-1].trailingComments);pc.bottomRightStack.length>0&&pc.bottomRightStack[pc.bottomRightStack.length-1].range[0]>=a.range[0];)b=pc.bottomRightStack.pop();b?b.leadingComments&&b.leadingComments[b.leadingComments.length-1].range[1]<=a.range[0]&&(a.leadingComments=b.leadingComments,delete b.leadingComments):pc.leadingComments.length>0&&pc.leadingComments[pc.leadingComments.length-1].range[1]<=a.range[0]&&(a.leadingComments=pc.leadingComments,pc.leadingComments=[]),c&&(a.trailingComments=c),pc.bottomRightStack.push(a)}},markEnd:function(a,b){return pc.range&&(a.range=[b.start,ic]),pc.loc&&(a.loc=new M(void 0===b.startLineNumber?b.lineNumber:b.startLineNumber,b.start-(void 0===b.startLineStart?b.lineStart:b.startLineStart),jc,ic-kc),this.postProcess(a)),pc.attachComment&&this.processComment(a),a},postProcess:function(a){return pc.source&&(a.loc.source=pc.source),a},createArrayExpression:function(a){return{type:bc.ArrayExpression,elements:a}},createAssignmentExpression:function(a,b,c){return{type:bc.AssignmentExpression,operator:a,left:b,right:c}},createBinaryExpression:function(a,b,c){var d="||"===a||"&&"===a?bc.LogicalExpression:bc.BinaryExpression;return{type:d,operator:a,left:b,right:c}},createBlockStatement:function(a){return{type:bc.BlockStatement,body:a}},createBreakStatement:function(a){return{type:bc.BreakStatement,label:a}},createCallExpression:function(a,b){return{type:bc.CallExpression,callee:a,arguments:b}},createCatchClause:function(a,b){return{type:bc.CatchClause,param:a,body:b}},createConditionalExpression:function(a,b,c){return{type:bc.ConditionalExpression,test:a,consequent:b,alternate:c}},createContinueStatement:function(a){return{type:bc.ContinueStatement,label:a}},createDebuggerStatement:function(){return{type:bc.DebuggerStatement}},createDoWhileStatement:function(a,b){return{type:bc.DoWhileStatement,body:a,test:b}},createEmptyStatement:function(){return{type:bc.EmptyStatement}},createExpressionStatement:function(a){return{type:bc.ExpressionStatement,expression:a}},createForStatement:function(a,b,c,d){return{type:bc.ForStatement,init:a,test:b,update:c,body:d}},createForInStatement:function(a,b,c){return{type:bc.ForInStatement,left:a,right:b,body:c,each:!1}},createFunctionDeclaration:function(a,b,c,d){return{type:bc.FunctionDeclaration,id:a,params:b,defaults:c,body:d,rest:null,generator:!1,expression:!1}},createFunctionExpression:function(a,b,c,d){return{type:bc.FunctionExpression,id:a,params:b,defaults:c,body:d,rest:null,generator:!1,expression:!1}},createIdentifier:function(a){return{type:bc.Identifier,name:a}},createIfStatement:function(a,b,c){return{type:bc.IfStatement,test:a,consequent:b,alternate:c}},createLabeledStatement:function(a,b){return{type:bc.LabeledStatement,label:a,body:b}},createLiteral:function(a){return{type:bc.Literal,value:a.value,raw:gc.slice(a.start,a.end)}},createMemberExpression:function(a,b,c){return{type:bc.MemberExpression,computed:"["===a,object:b,property:c}},createNewExpression:function(a,b){return{type:bc.NewExpression,callee:a,arguments:b}},createObjectExpression:function(a){return{type:bc.ObjectExpression,properties:a}},createPostfixExpression:function(a,b){return{type:bc.UpdateExpression,operator:a,argument:b,prefix:!1}},createProgram:function(a){return{type:bc.Program,body:a}},createProperty:function(a,b,c){return{type:bc.Property,key:b,value:c,kind:a}},createReturnStatement:function(a){return{type:bc.ReturnStatement,argument:a}},createSequenceExpression:function(a){return{type:bc.SequenceExpression,expressions:a}},createSwitchCase:function(a,b){return{type:bc.SwitchCase,test:a,consequent:b}},createSwitchStatement:function(a,b){return{type:bc.SwitchStatement,discriminant:a,cases:b}},createThisExpression:function(){return{type:bc.ThisExpression}},createThrowStatement:function(a){return{type:bc.ThrowStatement,argument:a}},createTryStatement:function(a,b,c,d){return{type:bc.TryStatement,block:a,guardedHandlers:b,handlers:c,finalizer:d}},createUnaryExpression:function(a,b){return"++"===a||"--"===a?{type:bc.UpdateExpression,operator:a,argument:b,prefix:!0}:{type:bc.UnaryExpression,operator:a,argument:b,prefix:!0}},createVariableDeclaration:function(a,b){return{type:bc.VariableDeclaration,declarations:a,kind:b}},createVariableDeclarator:function(a,b){return{type:bc.VariableDeclarator,id:a,init:b}},createWhileStatement:function(a,b){return{type:bc.WhileStatement,test:a,body:b}},createWithStatement:function(a,b){return{type:bc.WithStatement,object:a,body:b}}},a.version="1.2.2",a.tokenize=Yb,a.parse=Zb,a.Syntax=function(){var a,b={};"function"==typeof Object.create&&(b=Object.create(null));for(a in bc)bc.hasOwnProperty(a)&&(b[a]=bc[a]);return"function"==typeof Object.freeze&&Object.freeze(b),b}()})}(null),/*!
+ * falafel (c) James Halliday / MIT License
+ * https://github.com/substack/node-falafel
+ */
+function(a,b){function c(a,b,c){function d(b){c[a.range[0]]=b;for(var d=a.range[0]+1;d<a.range[1];d++)c[d]=""}if(a.range)if(a.parent=b,a.source=function(){return c.slice(a.range[0],a.range[1]).join("")},a.update&&"object"==typeof a.update){var g=a.update;f(e(g),function(a){d[a]=g[a]}),a.update=d}else a.update=d}var d=a("esprima").parse,e=Object.keys||function(a){var b=[];for(var c in a)b.push(c);return b},f=function(a,b){if(a.forEach)return a.forEach(b);for(var c=0;c<a.length;c++)b.call(a,a[c],c,a)},g=Array.isArray||function(a){return"[object Array]"===Object.prototype.toString.call(a)};b.exports=function(a,b,h){"function"==typeof b&&(h=b,b={}),"object"==typeof a&&(b=a,a=b.source,delete b.source),a=void 0===a?b.source:a,b.range=!0,"string"!=typeof a&&(a=String(a));var i=d(a,b),j={chunks:a.split(""),toString:function(){return j.chunks.join("")},inspect:function(){return j.toString()}};return function k(a,b){c(a,b,j.chunks),f(e(a),function(b){if("parent"!==b){var d=a[b];g(d)?f(d,function(b){b&&"string"==typeof b.type&&k(b,a)}):d&&"string"==typeof d.type&&(c(d,a,j.chunks),k(d,a))}}),h(a)}(i,void 0),j},window.falafel=b.exports}(function(){return{parse:esprima.parse}},{exports:{}});var inBrowser="undefined"!=typeof window&&this===window,parseAndModify=inBrowser?window.falafel:require("falafel");(inBrowser?window:exports).blanket=function(){var a,b=["ExpressionStatement","BreakStatement","ContinueStatement","VariableDeclaration","ReturnStatement","ThrowStatement","TryStatement","FunctionDeclaration","IfStatement","WhileStatement","DoWhileStatement","ForStatement","ForInStatement","SwitchStatement","WithStatement"],c=["IfStatement","WhileStatement","DoWhileStatement","ForStatement","ForInStatement","WithStatement"],d=Math.floor(1e3*Math.random()),e={},f={reporter:null,adapter:null,filter:null,customVariable:null,loader:null,ignoreScriptError:!1,existingRequireJS:!1,autoStart:!1,timeout:180,ignoreCors:!1,branchTracking:!1,sourceURL:!1,debug:!1,engineOnly:!1,testReadyCallback:null,commonJS:!1,instrumentCache:!1,modulePattern:null};return inBrowser&&"undefined"!=typeof window.blanket&&(a=window.blanket.noConflict()),_blanket={noConflict:function(){return a?a:_blanket},_getCopyNumber:function(){return d},extend:function(a){_blanket._extend(_blanket,a)},_extend:function(a,b){if(b)for(var c in b)a[c]instanceof Object&&"function"!=typeof a[c]?_blanket._extend(a[c],b[c]):a[c]=b[c]},getCovVar:function(){var a=_blanket.options("customVariable");return a?(_blanket.options("debug")&&console.log("BLANKET-Using custom tracking variable:",a),inBrowser?"window."+a:a):inBrowser?"window._$blanket":"_$jscoverage"},options:function(a,b){if("string"!=typeof a)_blanket._extend(f,a);else{if("undefined"==typeof b)return f[a];f[a]=b}},instrument:function(a,b){var c=a.inputFile,d=a.inputFileName;if(_blanket.options("instrumentCache")&&sessionStorage&&sessionStorage.getItem("blanket_instrument_store-"+d))_blanket.options("debug")&&console.log("BLANKET-Reading instrumentation from cache: ",d),b(sessionStorage.getItem("blanket_instrument_store-"+d));else{var e=_blanket._prepareSource(c);_blanket._trackingArraySetup=[],c=c.replace(/^\#\!.*/,"");var f=parseAndModify(c,{loc:!0,comment:!0},_blanket._addTracking(d));f=_blanket._trackingSetup(d,e)+f,_blanket.options("sourceURL")&&(f+="\n//@ sourceURL="+d.replace("http://","")),_blanket.options("debug")&&console.log("BLANKET-Instrumented file: ",d),_blanket.options("instrumentCache")&&sessionStorage&&(_blanket.options("debug")&&console.log("BLANKET-Saving instrumentation to cache: ",d),sessionStorage.setItem("blanket_instrument_store-"+d,f)),b(f)}},_trackingArraySetup:[],_branchingArraySetup:[],_prepareSource:function(a){return a.replace(/\\/g,"\\\\").replace(/'/g,"\\'").replace(/(\r\n|\n|\r)/gm,"\n").split("\n")},_trackingSetup:function(a,b){var c=_blanket.options("branchTracking"),d=b.join("',\n'"),e="",f=_blanket.getCovVar();return e+="if (typeof "+f+" === 'undefined') "+f+" = {};\n",c&&(e+="var _$branchFcn=function(f,l,c,r){ ",e+="if (!!r) { ",e+=f+"[f].branchData[l][c][0] = "+f+"[f].branchData[l][c][0] || [];",e+=f+"[f].branchData[l][c][0].push(r); }",e+="else { ",e+=f+"[f].branchData[l][c][1] = "+f+"[f].branchData[l][c][1] || [];",e+=f+"[f].branchData[l][c][1].push(r); }",e+="return r;};\n"),e+="if (typeof "+f+"['"+a+"'] === 'undefined'){",e+=f+"['"+a+"']=[];\n",c&&(e+=f+"['"+a+"'].branchData=[];\n"),e+=f+"['"+a+"'].source=['"+d+"'];\n",_blanket._trackingArraySetup.sort(function(a,b){return parseInt(a,10)>parseInt(b,10)}).forEach(function(b){e+=f+"['"+a+"']["+b+"]=0;\n"}),c&&_blanket._branchingArraySetup.sort(function(a,b){return a.line>b.line}).sort(function(a,b){return a.column>b.column}).forEach(function(b){b.file===a&&(e+="if (typeof "+f+"['"+a+"'].branchData["+b.line+"] === 'undefined'){\n",e+=f+"['"+a+"'].branchData["+b.line+"]=[];\n",e+="}",e+=f+"['"+a+"'].branchData["+b.line+"]["+b.column+"] = [];\n",e+=f+"['"+a+"'].branchData["+b.line+"]["+b.column+"].consequent = "+JSON.stringify(b.consequent)+";\n",e+=f+"['"+a+"'].branchData["+b.line+"]["+b.column+"].alternate = "+JSON.stringify(b.alternate)+";\n")}),e+="}"},_blockifyIf:function(a){if(c.indexOf(a.type)>-1){var b=a.consequent||a.body,d=a.alternate;d&&"BlockStatement"!==d.type&&d.update("{\n"+d.source()+"}\n"),b&&"BlockStatement"!==b.type&&b.update("{\n"+b.source()+"}\n")}},_trackBranch:function(a,b){var c=a.loc.start.line,d=a.loc.start.column;_blanket._branchingArraySetup.push({line:c,column:d,file:b,consequent:a.consequent.loc,alternate:a.alternate.loc});var e="_$branchFcn('"+b+"',"+c+","+d+","+a.test.source()+")?"+a.consequent.source()+":"+a.alternate.source();a.update(e)},_addTracking:function(a){var c=_blanket.getCovVar();return function(d){if(_blanket._blockifyIf(d),b.indexOf(d.type)>-1&&"LabeledStatement"!==d.parent.type){if(_blanket._checkDefs(d,a),"VariableDeclaration"===d.type&&("ForStatement"===d.parent.type||"ForInStatement"===d.parent.type))return;if(!d.loc||!d.loc.start)throw new Error("The instrumenter encountered a node with no location: "+Object.keys(d));d.update(c+"['"+a+"']["+d.loc.start.line+"]++;\n"+d.source()),_blanket._trackingArraySetup.push(d.loc.start.line)}else _blanket.options("branchTracking")&&"ConditionalExpression"===d.type&&_blanket._trackBranch(d,a)}},_checkDefs:function(a,b){if(inBrowser){if("VariableDeclaration"===a.type&&a.declarations&&a.declarations.forEach(function(c){if("window"===c.id.name)throw new Error("Instrumentation error, you cannot redefine the 'window' variable in "+b+":"+a.loc.start.line)}),"FunctionDeclaration"===a.type&&a.params&&a.params.forEach(function(c){if("window"===c.name)throw new Error("Instrumentation error, you cannot redefine the 'window' variable in "+b+":"+a.loc.start.line)}),"ExpressionStatement"===a.type&&a.expression&&a.expression.left&&a.expression.left.object&&a.expression.left.property&&a.expression.left.object.name+"."+a.expression.left.property.name===_blanket.getCovVar())throw new Error("Instrumentation error, you cannot redefine the coverage variable in "+b+":"+a.loc.start.line)}else if("ExpressionStatement"===a.type&&a.expression&&a.expression.left&&!a.expression.left.object&&!a.expression.left.property&&a.expression.left.name===_blanket.getCovVar())throw new Error("Instrumentation error, you cannot redefine the coverage variable in "+b+":"+a.loc.start.line)},setupCoverage:function(){e.instrumentation="blanket",e.stats={suites:0,tests:0,passes:0,pending:0,failures:0,start:new Date}},_checkIfSetup:function(){if(!e.stats)throw new Error("You must call blanket.setupCoverage() first.")},onTestStart:function(){_blanket.options("debug")&&console.log("BLANKET-Test event started"),this._checkIfSetup(),e.stats.tests++,e.stats.pending++},onTestDone:function(a,b){this._checkIfSetup(),b===a?e.stats.passes++:e.stats.failures++,e.stats.pending--},onModuleStart:function(){this._checkIfSetup(),e.stats.suites++},onTestsDone:function(){_blanket.options("debug")&&console.log("BLANKET-Test event done"),this._checkIfSetup(),e.stats.end=new Date,inBrowser?this.report(e):(_blanket.options("branchTracking")||delete(inBrowser?window:global)[_blanket.getCovVar()].branchFcn,this.options("reporter").call(this,e))}}}(),function(a){var b=a.options;a.extend({outstandingRequireFiles:[],options:function(c,d){var e={};if("string"!=typeof c)b(c),e=c;else{if("undefined"==typeof d)return b(c);b(c,d),e[c]=d}e.adapter&&a._loadFile(e.adapter),e.loader&&a._loadFile(e.loader)},requiringFile:function(b,c){"undefined"==typeof b?a.outstandingRequireFiles=[]:"undefined"==typeof c?a.outstandingRequireFiles.push(b):a.outstandingRequireFiles.splice(a.outstandingRequireFiles.indexOf(b),1)},requireFilesLoaded:function(){return 0===a.outstandingRequireFiles.length},showManualLoader:function(){if(!document.getElementById("blanketLoaderDialog")){var a="<div class='blanketDialogOverlay'>";a+="&nbsp;</div>",a+="<div class='blanketDialogVerticalOffset'>",a+="<div class='blanketDialogBox'>",a+="<b>Error:</b> Blanket.js encountered a cross origin request error while instrumenting the source files. ",a+="<br><br>This is likely caused by the source files being referenced locally (using the file:// protocol). ",a+="<br><br>Some solutions include <a href='http://askubuntu.com/questions/160245/making-google-chrome-option-allow-file-access-from-files-permanent' target='_blank'>starting Chrome with special flags</a>, <a target='_blank' href='https://github.com/remy/servedir'>running a server locally</a>, or using a browser without these CORS restrictions (Safari).",a+="<br>","undefined"!=typeof FileReader&&(a+="<br>Or, try the experimental loader. When prompted, simply click on the directory containing all the source files you want covered.",a+="<a href='javascript:document.getElementById(\"fileInput\").click();'>Start Loader</a>",a+="<input type='file' type='application/x-javascript' accept='application/x-javascript' webkitdirectory id='fileInput' multiple onchange='window.blanket.manualFileLoader(this.files)' style='visibility:hidden;position:absolute;top:-50;left:-50'/>"),a+="<br><span style='float:right;cursor:pointer;' onclick=document.getElementById('blanketLoaderDialog').style.display='none';>Close</span>",a+="<div style='clear:both'></div>",a+="</div></div>";var b=".blanketDialogWrapper {";b+="display:block;",b+="position:fixed;",b+="z-index:40001; }",b+=".blanketDialogOverlay {",b+="position:fixed;",b+="width:100%;",b+="height:100%;",b+="background-color:black;",b+="opacity:.5; ",b+="-ms-filter:'progid:DXImageTransform.Microsoft.Alpha(Opacity=50)'; ",b+="filter:alpha(opacity=50); ",b+="z-index:40001; }",b+=".blanketDialogVerticalOffset { ",b+="position:fixed;",b+="top:30%;",b+="width:100%;",b+="z-index:40002; }",b+=".blanketDialogBox { ",b+="width:405px; ",b+="position:relative;",b+="margin:0 auto;",b+="background-color:white;",b+="padding:10px;",b+="border:1px solid black; }";var c=document.createElement("style");c.innerHTML=b,document.head.appendChild(c);var d=document.createElement("div");d.id="blanketLoaderDialog",d.className="blanketDialogWrapper",d.innerHTML=a,document.body.insertBefore(d,document.body.firstChild)}},manualFileLoader:function(a){function b(a){var b=new FileReader;b.onload=g,b.readAsText(a)}var c=Array.prototype.slice;a=c.call(a).filter(function(a){return""!==a.type});var d=a.length-1,e=0,f={};sessionStorage.blanketSessionLoader&&(f=JSON.parse(sessionStorage.blanketSessionLoader));var g=function(c){var g=c.currentTarget.result,h=a[e],i=h.webkitRelativePath&&""!==h.webkitRelativePath?h.webkitRelativePath:h.name;f[i]=g,e++,e===d?(sessionStorage.setItem("blanketSessionLoader",JSON.stringify(f)),document.location.reload()):b(a[e])};b(a[e])},_loadFile:function(b){if("undefined"!=typeof b){var c=new XMLHttpRequest;c.open("GET",b,!1),c.send(),a._addScript(c.responseText)}},_addScript:function(a){var b=document.createElement("script");b.type="text/javascript",b.text=a,(document.body||document.getElementsByTagName("head")[0]).appendChild(b)},hasAdapter:function(){return null!==a.options("adapter")},report:function(b){document.getElementById("blanketLoaderDialog")||(a.blanketSession=null),b.files=window._$blanket;blanket.options("commonJS")?blanket._commonjs.require:window.require;if(!b.files||!Object.keys(b.files).length)return void(a.options("debug")&&console.log("BLANKET-Reporting No files were instrumented."));if("undefined"!=typeof b.files.branchFcn&&delete b.files.branchFcn,"string"==typeof a.options("reporter"))a._loadFile(a.options("reporter")),a.customReporter(b,a.options("reporter_options"));else if("function"==typeof a.options("reporter"))a.options("reporter")(b,a.options("reporter_options"));else{if("function"!=typeof a.defaultReporter)throw new Error("no reporter defined.");a.defaultReporter(b,a.options("reporter_options"))}},_bindStartTestRunner:function(a,b){a?a(b):window.addEventListener("load",b,!1)},_loadSourceFiles:function(b){blanket.options("commonJS")?blanket._commonjs.require:window.require;a.options("debug")&&console.log("BLANKET-Collecting page scripts");var c=a.utils.collectPageScripts();if(0===c.length)b();else{sessionStorage.blanketSessionLoader&&(a.blanketSession=JSON.parse(sessionStorage.blanketSessionLoader)),c.forEach(function(b){a.utils.cache[b]={loaded:!1}});var d=-1;a.utils.loadAll(function(a){return a?"undefined"!=typeof c[d+1]:(d++,d>=c.length?null:c[d])},b)}},beforeStartTestRunner:function(b){b=b||{},b.checkRequirejs="undefined"==typeof b.checkRequirejs?!0:b.checkRequirejs,b.callback=b.callback||function(){},b.coverage="undefined"==typeof b.coverage?!0:b.coverage,b.coverage?a._bindStartTestRunner(b.bindEvent,function(){a._loadSourceFiles(function(){var c=function(){return b.condition?b.condition():a.requireFilesLoaded()},d=function(){if(c()){a.options("debug")&&console.log("BLANKET-All files loaded, init start test runner callback.");var e=a.options("testReadyCallback");e?"function"==typeof e?e(b.callback):"string"==typeof e&&(a._addScript(e),b.callback()):b.callback()}else setTimeout(d,13)};d()})}):b.callback()},utils:{qualifyURL:function(a){var b=document.createElement("a");return b.href=a,b.href}}})}(blanket),blanket.defaultReporter=function(a){function b(a){var b=document.getElementById(a);b.style.display="block"===b.style.display?"none":"block"}function c(a){return a.replace(/\&/g,"&amp;").replace(/</g,"&lt;").replace(/\>/g,"&gt;").replace(/\"/g,"&quot;").replace(/\'/g,"&apos;")}function d(a,b){var c=b?0:1;return"undefined"==typeof a||null===typeof a||"undefined"==typeof a[c]?!1:a[c].length>0}function e(a,b,f,g,h){var i="",j="";if(q.length>0)if(i+="<span class='"+(d(q[0][1],q[0][1].consequent===q[0][0])?"branchOkay":"branchWarning")+"'>",q[0][0].end.line===h){if(i+=c(b.slice(0,q[0][0].end.column))+"</span>",b=b.slice(q[0][0].end.column),q.shift(),q.length>0)if(i+="<span class='"+(d(q[0][1],!1)?"branchOkay":"branchWarning")+"'>",q[0][0].end.line===h){if(i+=c(b.slice(0,q[0][0].end.column))+"</span>",b=b.slice(q[0][0].end.column),q.shift(),!f)return{src:i+c(b),cols:f}}else{if(!f)return{src:i+c(b)+"</span>",cols:f};j="</span>"}else if(!f)return{src:i+c(b),cols:f}}else{if(!f)return{src:i+c(b)+"</span>",cols:f};j="</span>"}var k=f[a],l=k.consequent;if(l.start.line>h)q.unshift([k.alternate,k]),q.unshift([l,k]),b=c(b);else{var m="<span class='"+(d(k,!0)?"branchOkay":"branchWarning")+"'>";if(i+=c(b.slice(0,l.start.column-g))+m,f.length>a+1&&f[a+1].consequent.start.line===h&&f[a+1].consequent.start.column-g<f[a].consequent.end.column-g){var n=e(a+1,b.slice(l.start.column-g,l.end.column-g),f,l.start.column-g,h);i+=n.src,f=n.cols,f[a+1]=f[a+2],f.length--}else i+=c(b.slice(l.start.column-g,l.end.column-g));i+="</span>";var o=k.alternate;if(o.start.line>h)i+=c(b.slice(l.end.column-g)),q.unshift([o,k]);else{if(i+=c(b.slice(l.end.column-g,o.start.column-g)),m="<span class='"+(d(k,!1)?"branchOkay":"branchWarning")+"'>",i+=m,f.length>a+1&&f[a+1].consequent.start.line===h&&f[a+1].consequent.start.column-g<f[a].alternate.end.column-g){var p=e(a+1,b.slice(o.start.column-g,o.end.column-g),f,o.start.column-g,h);i+=p.src,f=p.cols,f[a+1]=f[a+2],f.length--}else i+=c(b.slice(o.start.column-g,o.end.column-g));i+="</span>",i+=c(b.slice(o.end.column-g)),b=i}}return{src:b+j,cols:f}}var f="#blanket-main {margin:2px;background:#EEE;color:#333;clear:both;font-family:'Helvetica Neue Light', 'HelveticaNeue-Light', 'Helvetica Neue', Calibri, Helvetica, Arial, sans-serif; font-size:17px;} #blanket-main a {color:#333;text-decoration:none;} #blanket-main a:hover {text-decoration:underline;} .blanket {margin:0;padding:5px;clear:both;border-bottom: 1px solid #FFFFFF;} .bl-error {color:red;}.bl-success {color:#5E7D00;} .bl-file{width:auto;} .bl-cl{float:left;} .blanket div.rs {margin-left:50px; width:150px; float:right} .bl-nb {padding-right:10px;} #blanket-main a.bl-logo {color: #EB1764;cursor: pointer;font-weight: bold;text-decoration: none} .bl-source{ overflow-x:scroll; background-color: #FFFFFF; border: 1px solid #CBCBCB; color: #363636; margin: 25px 20px; width: 80%;} .bl-source div{white-space: pre;font-family: monospace;} .bl-source > div > span:first-child{background-color: #EAEAEA;color: #949494;display: inline-block;padding: 0 10px;text-align: center;width: 30px;} .bl-source .miss{background-color:#e6c3c7} .bl-source span.branchWarning{color:#000;background-color:yellow;} .bl-source span.branchOkay{color:#000;background-color:transparent;}",g=60,h=document.head,i=0,j=document.body,k=Object.keys(a.files).some(function(b){return"undefined"!=typeof a.files[b].branchData}),l="<div id='blanket-main'><div class='blanket bl-title'><div class='bl-cl bl-file'><a href='http://alex-seville.github.com/blanket/' target='_blank' class='bl-logo'>Blanket.js</a> results</div><div class='bl-cl rs'>Coverage (%)</div><div class='bl-cl rs'>Covered/Total Smts.</div>"+(k?"<div class='bl-cl rs'>Covered/Total Branches</div>":"")+"<div style='clear:both;'></div></div>",m="<div class='blanket {{statusclass}}'><div class='bl-cl bl-file'><span class='bl-nb'>{{fileNumber}}.</span><a href='javascript:blanket_toggleSource(\"file-{{fileNumber}}\")'>{{file}}</a></div><div class='bl-cl rs'>{{percentage}} %</div><div class='bl-cl rs'>{{numberCovered}}/{{totalSmts}}</div>"+(k?"<div class='bl-cl rs'>{{passedBranches}}/{{totalBranches}}</div>":"")+"<div id='file-{{fileNumber}}' class='bl-source' style='display:none;'>{{source}}</div><div style='clear:both;'></div></div>";grandTotalTemplate="<div class='blanket grand-total {{statusclass}}'><div class='bl-cl'>{{rowTitle}}</div><div class='bl-cl rs'>{{percentage}} %</div><div class='bl-cl rs'>{{numberCovered}}/{{totalSmts}}</div>"+(k?"<div class='bl-cl rs'>{{passedBranches}}/{{totalBranches}}</div>":"")+"<div style='clear:both;'></div></div>";var n=document.createElement("script");n.type="text/javascript",n.text=b.toString().replace("function "+b.name,"function blanket_toggleSource"),j.appendChild(n);var o=function(a,b){return Math.round(a/b*100*100)/100},p=function(a,b,c){var d=document.createElement(a);d.innerHTML=c,b.appendChild(d)},q=[],r=function(a){return"undefined"!=typeof a},s=a.files,t={totalSmts:0,numberOfFilesCovered:0,passedBranches:0,totalBranches:0,moduleTotalStatements:{},moduleTotalCoveredStatements:{},moduleTotalBranches:{},moduleTotalCoveredBranches:{}},u=_blanket.options("modulePattern"),v=u?new RegExp(u):null;for(var w in s)if(s.hasOwnProperty(w)){i++;var x,y=s[w],z=0,A=0,B=[];for(x=0;x<y.source.length;x+=1){var C=y.source[x];if(q.length>0||"undefined"!=typeof y.branchData)if("undefined"!=typeof y.branchData[x+1]){var D=y.branchData[x+1].filter(r),E=0;C=e(E,C,D,0,x+1).src}else C=q.length?e(0,C,null,0,x+1).src:c(C);else C=c(C);var F="";y[x+1]?(A+=1,z+=1,F="hit"):0===y[x+1]&&(z++,F="miss"),B[x+1]="<div class='"+F+"'><span class=''>"+(x+1)+"</span>"+C+"</div>"}t.totalSmts+=z,t.numberOfFilesCovered+=A;var G=0,H=0;if("undefined"!=typeof y.branchData)for(var I=0;I<y.branchData.length;I++)if("undefined"!=typeof y.branchData[I])for(var J=0;J<y.branchData[I].length;J++)"undefined"!=typeof y.branchData[I][J]&&(G++,"undefined"!=typeof y.branchData[I][J][0]&&y.branchData[I][J][0].length>0&&"undefined"!=typeof y.branchData[I][J][1]&&y.branchData[I][J][1].length>0&&H++);if(t.passedBranches+=H,t.totalBranches+=G,v){var K=w.match(v)[1];t.moduleTotalStatements.hasOwnProperty(K)||(t.moduleTotalStatements[K]=0,t.moduleTotalCoveredStatements[K]=0),t.moduleTotalStatements[K]+=z,t.moduleTotalCoveredStatements[K]+=A,t.moduleTotalBranches.hasOwnProperty(K)||(t.moduleTotalBranches[K]=0,t.moduleTotalCoveredBranches[K]=0),t.moduleTotalBranches[K]+=G,t.moduleTotalCoveredBranches[K]+=H}var L=o(A,z),M=m.replace("{{file}}",w).replace("{{percentage}}",L).replace("{{numberCovered}}",A).replace(/\{\{fileNumber\}\}/g,i).replace("{{totalSmts}}",z).replace("{{totalBranches}}",G).replace("{{passedBranches}}",H).replace("{{source}}",B.join(" "));M=g>L?M.replace("{{statusclass}}","bl-error"):M.replace("{{statusclass}}","bl-success"),l+=M}var N=function(a,b,c,d,e){var f=o(b,a),h=g>f?"bl-error":"bl-success",i=e?"Total for module: "+e:"Global total",j=grandTotalTemplate.replace("{{rowTitle}}",i).replace("{{percentage}}",f).replace("{{numberCovered}}",b).replace("{{totalSmts}}",a).replace("{{passedBranches}}",d).replace("{{totalBranches}}",c).replace("{{statusclass}}",h);l+=j};if(v)for(var O in t.moduleTotalStatements)if(t.moduleTotalStatements.hasOwnProperty(O)){var P=t.moduleTotalStatements[O],Q=t.moduleTotalCoveredStatements[O],R=t.moduleTotalBranches[O],S=t.moduleTotalCoveredBranches[O];N(P,Q,R,S,O)}N(t.totalSmts,t.numberOfFilesCovered,t.totalBranches,t.passedBranches,null),l+="</div>",p("style",h,f),document.getElementById("blanket-main")?document.getElementById("blanket-main").innerHTML=l.slice(23,-6):p("div",j,l)},function(){var a={},b=Array.prototype.slice,c=b.call(document.scripts);b.call(c[c.length-1].attributes).forEach(function(b){if("data-cover-only"===b.nodeName&&(a.filter=b.nodeValue),"data-cover-never"===b.nodeName&&(a.antifilter=b.nodeValue),"data-cover-reporter"===b.nodeName&&(a.reporter=b.nodeValue),"data-cover-adapter"===b.nodeName&&(a.adapter=b.nodeValue),"data-cover-loader"===b.nodeName&&(a.loader=b.nodeValue),"data-cover-timeout"===b.nodeName&&(a.timeout=b.nodeValue),"data-cover-modulepattern"===b.nodeName&&(a.modulePattern=b.nodeValue),"data-cover-reporter-options"===b.nodeName)try{a.reporter_options=JSON.parse(b.nodeValue)}catch(c){if(blanket.options("debug"))throw new Error("Invalid reporter options. Must be a valid stringified JSON object.")}if("data-cover-testReadyCallback"===b.nodeName&&(a.testReadyCallback=b.nodeValue),"data-cover-customVariable"===b.nodeName&&(a.customVariable=b.nodeValue),"data-cover-flags"===b.nodeName){var d=" "+b.nodeValue+" ";d.indexOf(" ignoreError ")>-1&&(a.ignoreScriptError=!0),d.indexOf(" autoStart ")>-1&&(a.autoStart=!0),d.indexOf(" ignoreCors ")>-1&&(a.ignoreCors=!0),d.indexOf(" branchTracking ")>-1&&(a.branchTracking=!0),d.indexOf(" sourceURL ")>-1&&(a.sourceURL=!0),d.indexOf(" debug ")>-1&&(a.debug=!0),d.indexOf(" engineOnly ")>-1&&(a.engineOnly=!0),d.indexOf(" commonJS ")>-1&&(a.commonJS=!0),d.indexOf(" instrumentCache ")>-1&&(a.instrumentCache=!0)}}),blanket.options(a),"undefined"!=typeof requirejs&&blanket.options("existingRequireJS",!0),blanket.options("commonJS")&&(blanket._commonjs={})}(),function(a){a.extend({utils:{normalizeBackslashes:function(a){return a.replace(/\\/g,"/")},matchPatternAttribute:function(b,c){if("string"==typeof c){if(0===c.indexOf("[")){var d=c.slice(1,c.length-1).split(",");return d.some(function(c){return a.utils.matchPatternAttribute(b,a.utils.normalizeBackslashes(c.slice(1,-1)))})}if(0===c.indexOf("//")){var e=c.slice(2,c.lastIndexOf("/")),f=c.slice(c.lastIndexOf("/")+1),g=new RegExp(e,f);return g.test(b)}return 0===c.indexOf("#")?window[c.slice(1)].call(window,b):b.indexOf(a.utils.normalizeBackslashes(c))>-1}return c instanceof Array?c.some(function(c){return a.utils.matchPatternAttribute(b,c)}):c instanceof RegExp?c.test(b):"function"==typeof c?c.call(window,b):void 0},blanketEval:function(b){a._addScript(b)},collectPageScripts:function(){var b=Array.prototype.slice,c=(b.call(document.scripts),[]),d=[],e=a.options("filter");if(null!=e){var f=a.options("antifilter");c=b.call(document.scripts).filter(function(c){return 1===b.call(c.attributes).filter(function(b){return"src"===b.nodeName&&a.utils.matchPatternAttribute(b.nodeValue,e)&&("undefined"==typeof f||!a.utils.matchPatternAttribute(b.nodeValue,f))}).length})}else c=b.call(document.querySelectorAll("script[data-cover]"));return d=c.map(function(c){return a.utils.qualifyURL(b.call(c.attributes).filter(function(a){return"src"===a.nodeName})[0].nodeValue)}),e||a.options("filter","['"+d.join("','")+"']"),d},loadAll:function(b,c){var d=b(),e=a.utils.scriptIsLoaded(d,a.utils.ifOrdered,b,c);if(a.utils.cache[d]&&a.utils.cache[d].loaded)e();else{var f=function(){a.options("debug")&&console.log("BLANKET-Mark script:"+d+", as loaded and move to next script."),e()},g=function(b){a.options("debug")&&console.log("BLANKET-File loading finished"),"undefined"!=typeof b&&(a.options("debug")&&console.log("BLANKET-Add file to DOM."),a._addScript(b)),f()};a.utils.attachScript({url:d},function(b){a.utils.processFile(b,d,g,g)})}},attachScript:function(b,c){var d=a.options("timeout")||3e3;setTimeout(function(){if(!a.utils.cache[b.url].loaded)throw new Error("error loading source script")},d),a.utils.getFile(b.url,c,function(){throw new Error("error loading source script")})},ifOrdered:function(b,c){var d=b(!0);d?a.utils.loadAll(b,c):c(new Error("Error in loading chain."))},scriptIsLoaded:function(b,c,d,e){return a.options("debug")&&console.log("BLANKET-Returning function"),function(){a.options("debug")&&console.log("BLANKET-Marking file as loaded: "+b),a.utils.cache[b].loaded=!0,a.utils.allLoaded()?(a.options("debug")&&console.log("BLANKET-All files loaded"),e()):c&&(a.options("debug")&&console.log("BLANKET-Load next file."),c(d,e))}},cache:{},allLoaded:function(){for(var b=Object.keys(a.utils.cache),c=0;c<b.length;c++)if(!a.utils.cache[b[c]].loaded)return!1;return!0},processFile:function(b,c,d,e){var f=a.options("filter"),g=a.options("antifilter");"undefined"!=typeof g&&a.utils.matchPatternAttribute(c,g)?(e(b),a.options("debug")&&console.log("BLANKET-File will never be instrumented:"+c),a.requiringFile(c,!0)):a.utils.matchPatternAttribute(c,f)?(a.options("debug")&&console.log("BLANKET-Attempting instrument of:"+c),a.instrument({inputFile:b,inputFileName:c},function(e){try{a.options("debug")&&console.log("BLANKET-instrument of:"+c+" was successfull."),a.utils.blanketEval(e),d(),a.requiringFile(c,!0)}catch(f){if(!a.options("ignoreScriptError"))throw new Error("Error parsing instrumented code: "+f);a.options("debug")&&console.log("BLANKET-There was an error loading the file:"+c),d(b),a.requiringFile(c,!0)}})):(a.options("debug")&&console.log("BLANKET-Loading (without instrumenting) the file:"+c),e(b),a.requiringFile(c,!0))},cacheXhrConstructor:function(){var a,b,c;if("undefined"!=typeof XMLHttpRequest)a=XMLHttpRequest,this.createXhr=function(){return new a};else if("undefined"!=typeof ActiveXObject){for(a=ActiveXObject,b=0;3>b;b+=1){c=progIds[b];try{new ActiveXObject(c);break}catch(d){}}this.createXhr=function(){return new a(c)}}},craeteXhr:function(){throw new Error("cacheXhrConstructor is supposed to overwrite this function.")},getFile:function(b,c,d,e){var f=!1;if(a.blanketSession)for(var g=Object.keys(a.blanketSession),h=0;h<g.length;h++){var i=g[h];if(b.indexOf(i)>-1)return c(a.blanketSession[i]),void(f=!0)}if(!f){var j=a.utils.createXhr();j.open("GET",b,!0),e&&e(j,b),j.onreadystatechange=function(){var a,e;4===j.readyState&&(a=j.status,a>399&&600>a?(e=new Error(b+" HTTP status: "+a),e.xhr=j,d(e)):c(j.responseText))};try{j.send(null)}catch(k){if(!k.code||101!==k.code&&1012!==k.code||a.options("ignoreCors")!==!1)throw k;a.showManualLoader()}}}}}),function(){var b=(blanket.options("commonJS")?blanket._commonjs.require:window.require,blanket.options("commonJS")?blanket._commonjs.requirejs:window.requirejs);!a.options("engineOnly")&&a.options("existingRequireJS")&&(a.utils.oldloader=b.load,b.load=function(b,c,d){a.requiringFile(d),a.utils.getFile(d,function(e){a.utils.processFile(e,d,function(){b.completeLoad(c)},function(){a.utils.oldloader(b,c,d)})},function(b){throw a.requiringFile(),b})}),a.utils.cacheXhrConstructor()}()}(blanket),function(){if("undefined"!=typeof QUnit){var a=function(){return window.QUnit.config.queue.length>0&&blanket.noConflict().requireFilesLoaded()};QUnit.config.urlConfig[0].tooltip?(QUnit.config.urlConfig.push({id:"coverage",label:"Enable coverage",tooltip:"Enable code coverage."}),QUnit.urlParams.coverage||blanket.options("autoStart")?(QUnit.begin(function(){blanket.noConflict().setupCoverage()}),QUnit.done(function(){blanket.noConflict().onTestsDone()}),QUnit.moduleStart(function(){blanket.noConflict().onModuleStart()}),QUnit.testStart(function(){blanket.noConflict().onTestStart()}),QUnit.testDone(function(a){blanket.noConflict().onTestDone(a.total,a.passed)}),blanket.noConflict().beforeStartTestRunner({condition:a,callback:function(){(!blanket.options("existingRequireJS")||blanket.options("autoStart"))&&QUnit.start()}})):(blanket.options("existingRequireJS")&&(requirejs.load=_blanket.utils.oldloader),blanket.noConflict().beforeStartTestRunner({condition:a,callback:function(){(!blanket.options("existingRequireJS")||blanket.options("autoStart"))&&QUnit.start()},coverage:!1}))):(QUnit.begin=function(){blanket.noConflict().setupCoverage()},QUnit.done=function(){blanket.noConflict().onTestsDone()},QUnit.moduleStart=function(){blanket.noConflict().onModuleStart()},QUnit.testStart=function(){blanket.noConflict().onTestStart()},QUnit.testDone=function(a){blanket.noConflict().onTestDone(a.total,a.passed)},blanket.beforeStartTestRunner({condition:a,callback:QUnit.start}))}}();
diff --git a/js_tests/qunit/grunt-reporter.js b/js_tests/qunit/grunt-reporter.js
new file mode 100644
index 0000000000..f73e362d23
--- /dev/null
+++ b/js_tests/qunit/grunt-reporter.js
@@ -0,0 +1,78 @@
+// grunt-reporter.js
+//
+// A communication bridge between blanket.js and the grunt-blanket-qunit plugin
+// Distributed as part of the grunt-blanket-qunit library
+//
+// Copyright (C) 2013 Model N, Inc.
+// Distributed under the MIT License
+//
+// Documentation and full license available at:
+// https://github.com/ModelN/grunt-blanket-qunit
+//
+(function (){
+ "use strict";
+
+ // this is an ugly hack, but it's the official way of communicating between
+ // the parent phantomjs and the inner grunt-contrib-qunit library...
+ var sendMessage = function sendMessage() {
+ var args = [].slice.call(arguments);
+ alert(JSON.stringify(args));
+ };
+
+ // helper function for computing coverage info for a particular file
+ var reportFile = function( data ) {
+ var ret = {
+ coverage: 0,
+ hits: 0,
+ misses: 0,
+ sloc: 0
+ };
+ for (var i = 0; i < data.source.length; i++) {
+ var line = data.source[i];
+ var num = i + 1;
+ if (data[num] === 0) {
+ ret.misses++;
+ ret.sloc++;
+ } else if (data[num] !== undefined) {
+ ret.hits++;
+ ret.sloc++;
+ }
+ }
+ ret.coverage = ret.hits / ret.sloc * 100;
+
+ return [ret.hits,ret.sloc];
+
+ };
+
+ // this function is invoked by blanket.js when the coverage data is ready. it will
+ // compute per-file coverage info, and send a message to the parent phantomjs process
+ // for each file, which the grunt task will use to report passes & failures.
+ var reporter = function(cov){
+ cov = window._$blanket;
+
+ var sortedFileNames = [];
+
+ var totals =[];
+
+ for (var filename in cov) {
+ if (cov.hasOwnProperty(filename)) {
+ sortedFileNames.push(filename);
+ }
+ }
+
+ sortedFileNames.sort();
+
+ for (var i = 0; i < sortedFileNames.length; i++) {
+ var thisFile = sortedFileNames[i];
+ var data = cov[thisFile];
+ var thisTotal= reportFile( data );
+ sendMessage("blanket:fileDone", thisTotal, thisFile);
+ }
+
+ sendMessage("blanket:done");
+
+ };
+
+ blanket.customReporter = reporter;
+
+})();
diff --git a/js_tests/qunit/qunit.css b/js_tests/qunit/qunit.css
new file mode 100644
index 0000000000..f1dcd4e1cc
--- /dev/null
+++ b/js_tests/qunit/qunit.css
@@ -0,0 +1,291 @@
+/*!
+ * QUnit 1.18.0
+ * http://qunitjs.com/
+ *
+ * Copyright jQuery Foundation and other contributors
+ * Released under the MIT license
+ * http://jquery.org/license
+ *
+ * Date: 2015-04-03T10:23Z
+ */
+
+/** Font Family and Sizes */
+
+#qunit-tests, #qunit-header, #qunit-banner, #qunit-testrunner-toolbar, #qunit-userAgent, #qunit-testresult {
+ font-family: "Helvetica Neue Light", "HelveticaNeue-Light", "Helvetica Neue", Calibri, Helvetica, Arial, sans-serif;
+}
+
+#qunit-testrunner-toolbar, #qunit-userAgent, #qunit-testresult, #qunit-tests li { font-size: small; }
+#qunit-tests { font-size: smaller; }
+
+
+/** Resets */
+
+#qunit-tests, #qunit-header, #qunit-banner, #qunit-userAgent, #qunit-testresult, #qunit-modulefilter {
+ margin: 0;
+ padding: 0;
+}
+
+
+/** Header */
+
+#qunit-header {
+ padding: 0.5em 0 0.5em 1em;
+
+ color: #8699A4;
+ background-color: #0D3349;
+
+ font-size: 1.5em;
+ line-height: 1em;
+ font-weight: 400;
+
+ border-radius: 5px 5px 0 0;
+}
+
+#qunit-header a {
+ text-decoration: none;
+ color: #C2CCD1;
+}
+
+#qunit-header a:hover,
+#qunit-header a:focus {
+ color: #FFF;
+}
+
+#qunit-testrunner-toolbar label {
+ display: inline-block;
+ padding: 0 0.5em 0 0.1em;
+}
+
+#qunit-banner {
+ height: 5px;
+}
+
+#qunit-testrunner-toolbar {
+ padding: 0.5em 1em 0.5em 1em;
+ color: #5E740B;
+ background-color: #EEE;
+ overflow: hidden;
+}
+
+#qunit-userAgent {
+ padding: 0.5em 1em 0.5em 1em;
+ background-color: #2B81AF;
+ color: #FFF;
+ text-shadow: rgba(0, 0, 0, 0.5) 2px 2px 1px;
+}
+
+#qunit-modulefilter-container {
+ float: right;
+ padding: 0.2em;
+}
+
+.qunit-url-config {
+ display: inline-block;
+ padding: 0.1em;
+}
+
+.qunit-filter {
+ display: block;
+ float: right;
+ margin-left: 1em;
+}
+
+/** Tests: Pass/Fail */
+
+#qunit-tests {
+ list-style-position: inside;
+}
+
+#qunit-tests li {
+ padding: 0.4em 1em 0.4em 1em;
+ border-bottom: 1px solid #FFF;
+ list-style-position: inside;
+}
+
+#qunit-tests > li {
+ display: none;
+}
+
+#qunit-tests li.running,
+#qunit-tests li.pass,
+#qunit-tests li.fail,
+#qunit-tests li.skipped {
+ display: list-item;
+}
+
+#qunit-tests.hidepass li.running,
+#qunit-tests.hidepass li.pass {
+ visibility: hidden;
+ position: absolute;
+ width: 0px;
+ height: 0px;
+ padding: 0;
+ border: 0;
+ margin: 0;
+}
+
+#qunit-tests li strong {
+ cursor: pointer;
+}
+
+#qunit-tests li.skipped strong {
+ cursor: default;
+}
+
+#qunit-tests li a {
+ padding: 0.5em;
+ color: #C2CCD1;
+ text-decoration: none;
+}
+
+#qunit-tests li p a {
+ padding: 0.25em;
+ color: #6B6464;
+}
+#qunit-tests li a:hover,
+#qunit-tests li a:focus {
+ color: #000;
+}
+
+#qunit-tests li .runtime {
+ float: right;
+ font-size: smaller;
+}
+
+.qunit-assert-list {
+ margin-top: 0.5em;
+ padding: 0.5em;
+
+ background-color: #FFF;
+
+ border-radius: 5px;
+}
+
+.qunit-collapsed {
+ display: none;
+}
+
+#qunit-tests table {
+ border-collapse: collapse;
+ margin-top: 0.2em;
+}
+
+#qunit-tests th {
+ text-align: right;
+ vertical-align: top;
+ padding: 0 0.5em 0 0;
+}
+
+#qunit-tests td {
+ vertical-align: top;
+}
+
+#qunit-tests pre {
+ margin: 0;
+ white-space: pre-wrap;
+ word-wrap: break-word;
+}
+
+#qunit-tests del {
+ background-color: #E0F2BE;
+ color: #374E0C;
+ text-decoration: none;
+}
+
+#qunit-tests ins {
+ background-color: #FFCACA;
+ color: #500;
+ text-decoration: none;
+}
+
+/*** Test Counts */
+
+#qunit-tests b.counts { color: #000; }
+#qunit-tests b.passed { color: #5E740B; }
+#qunit-tests b.failed { color: #710909; }
+
+#qunit-tests li li {
+ padding: 5px;
+ background-color: #FFF;
+ border-bottom: none;
+ list-style-position: inside;
+}
+
+/*** Passing Styles */
+
+#qunit-tests li li.pass {
+ color: #3C510C;
+ background-color: #FFF;
+ border-left: 10px solid #C6E746;
+}
+
+#qunit-tests .pass { color: #528CE0; background-color: #D2E0E6; }
+#qunit-tests .pass .test-name { color: #366097; }
+
+#qunit-tests .pass .test-actual,
+#qunit-tests .pass .test-expected { color: #999; }
+
+#qunit-banner.qunit-pass { background-color: #C6E746; }
+
+/*** Failing Styles */
+
+#qunit-tests li li.fail {
+ color: #710909;
+ background-color: #FFF;
+ border-left: 10px solid #EE5757;
+ white-space: pre;
+}
+
+#qunit-tests > li:last-child {
+ border-radius: 0 0 5px 5px;
+}
+
+#qunit-tests .fail { color: #000; background-color: #EE5757; }
+#qunit-tests .fail .test-name,
+#qunit-tests .fail .module-name { color: #000; }
+
+#qunit-tests .fail .test-actual { color: #EE5757; }
+#qunit-tests .fail .test-expected { color: #008000; }
+
+#qunit-banner.qunit-fail { background-color: #EE5757; }
+
+/*** Skipped tests */
+
+#qunit-tests .skipped {
+ background-color: #EBECE9;
+}
+
+#qunit-tests .qunit-skipped-label {
+ background-color: #F4FF77;
+ display: inline-block;
+ font-style: normal;
+ color: #366097;
+ line-height: 1.8em;
+ padding: 0 0.5em;
+ margin: -0.4em 0.4em -0.4em 0;
+}
+
+/** Result */
+
+#qunit-testresult {
+ padding: 0.5em 1em 0.5em 1em;
+
+ color: #2B81AF;
+ background-color: #D2E0E6;
+
+ border-bottom: 1px solid #FFF;
+}
+#qunit-testresult .module-name {
+ font-weight: 700;
+}
+
+/** Fixture */
+
+#qunit-fixture {
+ position: absolute;
+ top: -10000px;
+ left: -10000px;
+ width: 1000px;
+ height: 1000px;
+}
diff --git a/js_tests/qunit/qunit.js b/js_tests/qunit/qunit.js
new file mode 100644
index 0000000000..f3542ca9d4
--- /dev/null
+++ b/js_tests/qunit/qunit.js
@@ -0,0 +1,3828 @@
+/*!
+ * QUnit 1.18.0
+ * http://qunitjs.com/
+ *
+ * Copyright jQuery Foundation and other contributors
+ * Released under the MIT license
+ * http://jquery.org/license
+ *
+ * Date: 2015-04-03T10:23Z
+ */
+
+(function( window ) {
+
+var QUnit,
+ config,
+ onErrorFnPrev,
+ loggingCallbacks = {},
+ fileName = ( sourceFromStacktrace( 0 ) || "" ).replace( /(:\d+)+\)?/, "" ).replace( /.+\//, "" ),
+ toString = Object.prototype.toString,
+ hasOwn = Object.prototype.hasOwnProperty,
+ // Keep a local reference to Date (GH-283)
+ Date = window.Date,
+ now = Date.now || function() {
+ return new Date().getTime();
+ },
+ globalStartCalled = false,
+ runStarted = false,
+ setTimeout = window.setTimeout,
+ clearTimeout = window.clearTimeout,
+ defined = {
+ document: window.document !== undefined,
+ setTimeout: window.setTimeout !== undefined,
+ sessionStorage: (function() {
+ var x = "qunit-test-string";
+ try {
+ sessionStorage.setItem( x, x );
+ sessionStorage.removeItem( x );
+ return true;
+ } catch ( e ) {
+ return false;
+ }
+ }())
+ },
+ /**
+ * Provides a normalized error string, correcting an issue
+ * with IE 7 (and prior) where Error.prototype.toString is
+ * not properly implemented
+ *
+ * Based on http://es5.github.com/#x15.11.4.4
+ *
+ * @param {String|Error} error
+ * @return {String} error message
+ */
+ errorString = function( error ) {
+ var name, message,
+ errorString = error.toString();
+ if ( errorString.substring( 0, 7 ) === "[object" ) {
+ name = error.name ? error.name.toString() : "Error";
+ message = error.message ? error.message.toString() : "";
+ if ( name && message ) {
+ return name + ": " + message;
+ } else if ( name ) {
+ return name;
+ } else if ( message ) {
+ return message;
+ } else {
+ return "Error";
+ }
+ } else {
+ return errorString;
+ }
+ },
+ /**
+ * Makes a clone of an object using only Array or Object as base,
+ * and copies over the own enumerable properties.
+ *
+ * @param {Object} obj
+ * @return {Object} New object with only the own properties (recursively).
+ */
+ objectValues = function( obj ) {
+ var key, val,
+ vals = QUnit.is( "array", obj ) ? [] : {};
+ for ( key in obj ) {
+ if ( hasOwn.call( obj, key ) ) {
+ val = obj[ key ];
+ vals[ key ] = val === Object( val ) ? objectValues( val ) : val;
+ }
+ }
+ return vals;
+ };
+
+QUnit = {};
+
+/**
+ * Config object: Maintain internal state
+ * Later exposed as QUnit.config
+ * `config` initialized at top of scope
+ */
+config = {
+ // The queue of tests to run
+ queue: [],
+
+ // block until document ready
+ blocking: true,
+
+ // by default, run previously failed tests first
+ // very useful in combination with "Hide passed tests" checked
+ reorder: true,
+
+ // by default, modify document.title when suite is done
+ altertitle: true,
+
+ // by default, scroll to top of the page when suite is done
+ scrolltop: true,
+
+ // when enabled, all tests must call expect()
+ requireExpects: false,
+
+ // depth up-to which object will be dumped
+ maxDepth: 5,
+
+ // add checkboxes that are persisted in the query-string
+ // when enabled, the id is set to `true` as a `QUnit.config` property
+ urlConfig: [
+ {
+ id: "hidepassed",
+ label: "Hide passed tests",
+ tooltip: "Only show tests and assertions that fail. Stored as query-strings."
+ },
+ {
+ id: "noglobals",
+ label: "Check for Globals",
+ tooltip: "Enabling this will test if any test introduces new properties on the " +
+ "`window` object. Stored as query-strings."
+ },
+ {
+ id: "notrycatch",
+ label: "No try-catch",
+ tooltip: "Enabling this will run tests outside of a try-catch block. Makes debugging " +
+ "exceptions in IE reasonable. Stored as query-strings."
+ }
+ ],
+
+ // Set of all modules.
+ modules: [],
+
+ // The first unnamed module
+ currentModule: {
+ name: "",
+ tests: []
+ },
+
+ callbacks: {}
+};
+
+// Push a loose unnamed module to the modules collection
+config.modules.push( config.currentModule );
+
+// Initialize more QUnit.config and QUnit.urlParams
+(function() {
+ var i, current,
+ location = window.location || { search: "", protocol: "file:" },
+ params = location.search.slice( 1 ).split( "&" ),
+ length = params.length,
+ urlParams = {};
+
+ if ( params[ 0 ] ) {
+ for ( i = 0; i < length; i++ ) {
+ current = params[ i ].split( "=" );
+ current[ 0 ] = decodeURIComponent( current[ 0 ] );
+
+ // allow just a key to turn on a flag, e.g., test.html?noglobals
+ current[ 1 ] = current[ 1 ] ? decodeURIComponent( current[ 1 ] ) : true;
+ if ( urlParams[ current[ 0 ] ] ) {
+ urlParams[ current[ 0 ] ] = [].concat( urlParams[ current[ 0 ] ], current[ 1 ] );
+ } else {
+ urlParams[ current[ 0 ] ] = current[ 1 ];
+ }
+ }
+ }
+
+ if ( urlParams.filter === true ) {
+ delete urlParams.filter;
+ }
+
+ QUnit.urlParams = urlParams;
+
+ // String search anywhere in moduleName+testName
+ config.filter = urlParams.filter;
+
+ if ( urlParams.maxDepth ) {
+ config.maxDepth = parseInt( urlParams.maxDepth, 10 ) === -1 ?
+ Number.POSITIVE_INFINITY :
+ urlParams.maxDepth;
+ }
+
+ config.testId = [];
+ if ( urlParams.testId ) {
+
+ // Ensure that urlParams.testId is an array
+ urlParams.testId = decodeURIComponent( urlParams.testId ).split( "," );
+ for ( i = 0; i < urlParams.testId.length; i++ ) {
+ config.testId.push( urlParams.testId[ i ] );
+ }
+ }
+
+ // Figure out if we're running the tests from a server or not
+ QUnit.isLocal = location.protocol === "file:";
+
+ // Expose the current QUnit version
+ QUnit.version = "1.18.0";
+}());
+
+// Root QUnit object.
+// `QUnit` initialized at top of scope
+extend( QUnit, {
+
+ // call on start of module test to prepend name to all tests
+ module: function( name, testEnvironment ) {
+ var currentModule = {
+ name: name,
+ testEnvironment: testEnvironment,
+ tests: []
+ };
+
+ // DEPRECATED: handles setup/teardown functions,
+ // beforeEach and afterEach should be used instead
+ if ( testEnvironment && testEnvironment.setup ) {
+ testEnvironment.beforeEach = testEnvironment.setup;
+ delete testEnvironment.setup;
+ }
+ if ( testEnvironment && testEnvironment.teardown ) {
+ testEnvironment.afterEach = testEnvironment.teardown;
+ delete testEnvironment.teardown;
+ }
+
+ config.modules.push( currentModule );
+ config.currentModule = currentModule;
+ },
+
+ // DEPRECATED: QUnit.asyncTest() will be removed in QUnit 2.0.
+ asyncTest: function( testName, expected, callback ) {
+ if ( arguments.length === 2 ) {
+ callback = expected;
+ expected = null;
+ }
+
+ QUnit.test( testName, expected, callback, true );
+ },
+
+ test: function( testName, expected, callback, async ) {
+ var test;
+
+ if ( arguments.length === 2 ) {
+ callback = expected;
+ expected = null;
+ }
+
+ test = new Test({
+ testName: testName,
+ expected: expected,
+ async: async,
+ callback: callback
+ });
+
+ test.queue();
+ },
+
+ skip: function( testName ) {
+ var test = new Test({
+ testName: testName,
+ skip: true
+ });
+
+ test.queue();
+ },
+
+ // DEPRECATED: The functionality of QUnit.start() will be altered in QUnit 2.0.
+ // In QUnit 2.0, invoking it will ONLY affect the `QUnit.config.autostart` blocking behavior.
+ start: function( count ) {
+ var globalStartAlreadyCalled = globalStartCalled;
+
+ if ( !config.current ) {
+ globalStartCalled = true;
+
+ if ( runStarted ) {
+ throw new Error( "Called start() outside of a test context while already started" );
+ } else if ( globalStartAlreadyCalled || count > 1 ) {
+ throw new Error( "Called start() outside of a test context too many times" );
+ } else if ( config.autostart ) {
+ throw new Error( "Called start() outside of a test context when " +
+ "QUnit.config.autostart was true" );
+ } else if ( !config.pageLoaded ) {
+
+ // The page isn't completely loaded yet, so bail out and let `QUnit.load` handle it
+ config.autostart = true;
+ return;
+ }
+ } else {
+
+ // If a test is running, adjust its semaphore
+ config.current.semaphore -= count || 1;
+
+ // Don't start until equal number of stop-calls
+ if ( config.current.semaphore > 0 ) {
+ return;
+ }
+
+ // throw an Error if start is called more often than stop
+ if ( config.current.semaphore < 0 ) {
+ config.current.semaphore = 0;
+
+ QUnit.pushFailure(
+ "Called start() while already started (test's semaphore was 0 already)",
+ sourceFromStacktrace( 2 )
+ );
+ return;
+ }
+ }
+
+ resumeProcessing();
+ },
+
+ // DEPRECATED: QUnit.stop() will be removed in QUnit 2.0.
+ stop: function( count ) {
+
+ // If there isn't a test running, don't allow QUnit.stop() to be called
+ if ( !config.current ) {
+ throw new Error( "Called stop() outside of a test context" );
+ }
+
+ // If a test is running, adjust its semaphore
+ config.current.semaphore += count || 1;
+
+ pauseProcessing();
+ },
+
+ config: config,
+
+ // Safe object type checking
+ is: function( type, obj ) {
+ return QUnit.objectType( obj ) === type;
+ },
+
+ objectType: function( obj ) {
+ if ( typeof obj === "undefined" ) {
+ return "undefined";
+ }
+
+ // Consider: typeof null === object
+ if ( obj === null ) {
+ return "null";
+ }
+
+ var match = toString.call( obj ).match( /^\[object\s(.*)\]$/ ),
+ type = match && match[ 1 ] || "";
+
+ switch ( type ) {
+ case "Number":
+ if ( isNaN( obj ) ) {
+ return "nan";
+ }
+ return "number";
+ case "String":
+ case "Boolean":
+ case "Array":
+ case "Date":
+ case "RegExp":
+ case "Function":
+ return type.toLowerCase();
+ }
+ if ( typeof obj === "object" ) {
+ return "object";
+ }
+ return undefined;
+ },
+
+ extend: extend,
+
+ load: function() {
+ config.pageLoaded = true;
+
+ // Initialize the configuration options
+ extend( config, {
+ stats: { all: 0, bad: 0 },
+ moduleStats: { all: 0, bad: 0 },
+ started: 0,
+ updateRate: 1000,
+ autostart: true,
+ filter: ""
+ }, true );
+
+ config.blocking = false;
+
+ if ( config.autostart ) {
+ resumeProcessing();
+ }
+ }
+});
+
+// Register logging callbacks
+(function() {
+ var i, l, key,
+ callbacks = [ "begin", "done", "log", "testStart", "testDone",
+ "moduleStart", "moduleDone" ];
+
+ function registerLoggingCallback( key ) {
+ var loggingCallback = function( callback ) {
+ if ( QUnit.objectType( callback ) !== "function" ) {
+ throw new Error(
+ "QUnit logging methods require a callback function as their first parameters."
+ );
+ }
+
+ config.callbacks[ key ].push( callback );
+ };
+
+ // DEPRECATED: This will be removed on QUnit 2.0.0+
+ // Stores the registered functions allowing restoring
+ // at verifyLoggingCallbacks() if modified
+ loggingCallbacks[ key ] = loggingCallback;
+
+ return loggingCallback;
+ }
+
+ for ( i = 0, l = callbacks.length; i < l; i++ ) {
+ key = callbacks[ i ];
+
+ // Initialize key collection of logging callback
+ if ( QUnit.objectType( config.callbacks[ key ] ) === "undefined" ) {
+ config.callbacks[ key ] = [];
+ }
+
+ QUnit[ key ] = registerLoggingCallback( key );
+ }
+})();
+
+// `onErrorFnPrev` initialized at top of scope
+// Preserve other handlers
+onErrorFnPrev = window.onerror;
+
+// Cover uncaught exceptions
+// Returning true will suppress the default browser handler,
+// returning false will let it run.
+window.onerror = function( error, filePath, linerNr ) {
+ var ret = false;
+ if ( onErrorFnPrev ) {
+ ret = onErrorFnPrev( error, filePath, linerNr );
+ }
+
+ // Treat return value as window.onerror itself does,
+ // Only do our handling if not suppressed.
+ if ( ret !== true ) {
+ if ( QUnit.config.current ) {
+ if ( QUnit.config.current.ignoreGlobalErrors ) {
+ return true;
+ }
+ QUnit.pushFailure( error, filePath + ":" + linerNr );
+ } else {
+ QUnit.test( "global failure", extend(function() {
+ QUnit.pushFailure( error, filePath + ":" + linerNr );
+ }, { validTest: true } ) );
+ }
+ return false;
+ }
+
+ return ret;
+};
+
+function done() {
+ var runtime, passed;
+
+ config.autorun = true;
+
+ // Log the last module results
+ if ( config.previousModule ) {
+ runLoggingCallbacks( "moduleDone", {
+ name: config.previousModule.name,
+ tests: config.previousModule.tests,
+ failed: config.moduleStats.bad,
+ passed: config.moduleStats.all - config.moduleStats.bad,
+ total: config.moduleStats.all,
+ runtime: now() - config.moduleStats.started
+ });
+ }
+ delete config.previousModule;
+
+ runtime = now() - config.started;
+ passed = config.stats.all - config.stats.bad;
+
+ runLoggingCallbacks( "done", {
+ failed: config.stats.bad,
+ passed: passed,
+ total: config.stats.all,
+ runtime: runtime
+ });
+}
+
+// Doesn't support IE6 to IE9, it will return undefined on these browsers
+// See also https://developer.mozilla.org/en/JavaScript/Reference/Global_Objects/Error/Stack
+function extractStacktrace( e, offset ) {
+ offset = offset === undefined ? 4 : offset;
+
+ var stack, include, i;
+
+ if ( e.stack ) {
+ stack = e.stack.split( "\n" );
+ if ( /^error$/i.test( stack[ 0 ] ) ) {
+ stack.shift();
+ }
+ if ( fileName ) {
+ include = [];
+ for ( i = offset; i < stack.length; i++ ) {
+ if ( stack[ i ].indexOf( fileName ) !== -1 ) {
+ break;
+ }
+ include.push( stack[ i ] );
+ }
+ if ( include.length ) {
+ return include.join( "\n" );
+ }
+ }
+ return stack[ offset ];
+
+ // Support: Safari <=6 only
+ } else if ( e.sourceURL ) {
+
+ // exclude useless self-reference for generated Error objects
+ if ( /qunit.js$/.test( e.sourceURL ) ) {
+ return;
+ }
+
+ // for actual exceptions, this is useful
+ return e.sourceURL + ":" + e.line;
+ }
+}
+
+function sourceFromStacktrace( offset ) {
+ var error = new Error();
+
+ // Support: Safari <=7 only, IE <=10 - 11 only
+ // Not all browsers generate the `stack` property for `new Error()`, see also #636
+ if ( !error.stack ) {
+ try {
+ throw error;
+ } catch ( err ) {
+ error = err;
+ }
+ }
+
+ return extractStacktrace( error, offset );
+}
+
+function synchronize( callback, last ) {
+ if ( QUnit.objectType( callback ) === "array" ) {
+ while ( callback.length ) {
+ synchronize( callback.shift() );
+ }
+ return;
+ }
+ config.queue.push( callback );
+
+ if ( config.autorun && !config.blocking ) {
+ process( last );
+ }
+}
+
+function process( last ) {
+ function next() {
+ process( last );
+ }
+ var start = now();
+ config.depth = ( config.depth || 0 ) + 1;
+
+ while ( config.queue.length && !config.blocking ) {
+ if ( !defined.setTimeout || config.updateRate <= 0 ||
+ ( ( now() - start ) < config.updateRate ) ) {
+ if ( config.current ) {
+
+ // Reset async tracking for each phase of the Test lifecycle
+ config.current.usedAsync = false;
+ }
+ config.queue.shift()();
+ } else {
+ setTimeout( next, 13 );
+ break;
+ }
+ }
+ config.depth--;
+ if ( last && !config.blocking && !config.queue.length && config.depth === 0 ) {
+ done();
+ }
+}
+
+function begin() {
+ var i, l,
+ modulesLog = [];
+
+ // If the test run hasn't officially begun yet
+ if ( !config.started ) {
+
+ // Record the time of the test run's beginning
+ config.started = now();
+
+ verifyLoggingCallbacks();
+
+ // Delete the loose unnamed module if unused.
+ if ( config.modules[ 0 ].name === "" && config.modules[ 0 ].tests.length === 0 ) {
+ config.modules.shift();
+ }
+
+ // Avoid unnecessary information by not logging modules' test environments
+ for ( i = 0, l = config.modules.length; i < l; i++ ) {
+ modulesLog.push({
+ name: config.modules[ i ].name,
+ tests: config.modules[ i ].tests
+ });
+ }
+
+ // The test run is officially beginning now
+ runLoggingCallbacks( "begin", {
+ totalTests: Test.count,
+ modules: modulesLog
+ });
+ }
+
+ config.blocking = false;
+ process( true );
+}
+
+function resumeProcessing() {
+ runStarted = true;
+
+ // A slight delay to allow this iteration of the event loop to finish (more assertions, etc.)
+ if ( defined.setTimeout ) {
+ setTimeout(function() {
+ if ( config.current && config.current.semaphore > 0 ) {
+ return;
+ }
+ if ( config.timeout ) {
+ clearTimeout( config.timeout );
+ }
+
+ begin();
+ }, 13 );
+ } else {
+ begin();
+ }
+}
+
+function pauseProcessing() {
+ config.blocking = true;
+
+ if ( config.testTimeout && defined.setTimeout ) {
+ clearTimeout( config.timeout );
+ config.timeout = setTimeout(function() {
+ if ( config.current ) {
+ config.current.semaphore = 0;
+ QUnit.pushFailure( "Test timed out", sourceFromStacktrace( 2 ) );
+ } else {
+ throw new Error( "Test timed out" );
+ }
+ resumeProcessing();
+ }, config.testTimeout );
+ }
+}
+
+function saveGlobal() {
+ config.pollution = [];
+
+ if ( config.noglobals ) {
+ for ( var key in window ) {
+ if ( hasOwn.call( window, key ) ) {
+ // in Opera sometimes DOM element ids show up here, ignore them
+ if ( /^qunit-test-output/.test( key ) ) {
+ continue;
+ }
+ config.pollution.push( key );
+ }
+ }
+ }
+}
+
+function checkPollution() {
+ var newGlobals,
+ deletedGlobals,
+ old = config.pollution;
+
+ saveGlobal();
+
+ newGlobals = diff( config.pollution, old );
+ if ( newGlobals.length > 0 ) {
+ QUnit.pushFailure( "Introduced global variable(s): " + newGlobals.join( ", " ) );
+ }
+
+ deletedGlobals = diff( old, config.pollution );
+ if ( deletedGlobals.length > 0 ) {
+ QUnit.pushFailure( "Deleted global variable(s): " + deletedGlobals.join( ", " ) );
+ }
+}
+
+// returns a new Array with the elements that are in a but not in b
+function diff( a, b ) {
+ var i, j,
+ result = a.slice();
+
+ for ( i = 0; i < result.length; i++ ) {
+ for ( j = 0; j < b.length; j++ ) {
+ if ( result[ i ] === b[ j ] ) {
+ result.splice( i, 1 );
+ i--;
+ break;
+ }
+ }
+ }
+ return result;
+}
+
+function extend( a, b, undefOnly ) {
+ for ( var prop in b ) {
+ if ( hasOwn.call( b, prop ) ) {
+
+ // Avoid "Member not found" error in IE8 caused by messing with window.constructor
+ if ( !( prop === "constructor" && a === window ) ) {
+ if ( b[ prop ] === undefined ) {
+ delete a[ prop ];
+ } else if ( !( undefOnly && typeof a[ prop ] !== "undefined" ) ) {
+ a[ prop ] = b[ prop ];
+ }
+ }
+ }
+ }
+
+ return a;
+}
+
+function runLoggingCallbacks( key, args ) {
+ var i, l, callbacks;
+
+ callbacks = config.callbacks[ key ];
+ for ( i = 0, l = callbacks.length; i < l; i++ ) {
+ callbacks[ i ]( args );
+ }
+}
+
+// DEPRECATED: This will be removed on 2.0.0+
+// This function verifies if the loggingCallbacks were modified by the user
+// If so, it will restore it, assign the given callback and print a console warning
+function verifyLoggingCallbacks() {
+ var loggingCallback, userCallback;
+
+ for ( loggingCallback in loggingCallbacks ) {
+ if ( QUnit[ loggingCallback ] !== loggingCallbacks[ loggingCallback ] ) {
+
+ userCallback = QUnit[ loggingCallback ];
+
+ // Restore the callback function
+ QUnit[ loggingCallback ] = loggingCallbacks[ loggingCallback ];
+
+ // Assign the deprecated given callback
+ QUnit[ loggingCallback ]( userCallback );
+
+ if ( window.console && window.console.warn ) {
+ window.console.warn(
+ "QUnit." + loggingCallback + " was replaced with a new value.\n" +
+ "Please, check out the documentation on how to apply logging callbacks.\n" +
+ "Reference: http://api.qunitjs.com/category/callbacks/"
+ );
+ }
+ }
+ }
+}
+
+// from jquery.js
+function inArray( elem, array ) {
+ if ( array.indexOf ) {
+ return array.indexOf( elem );
+ }
+
+ for ( var i = 0, length = array.length; i < length; i++ ) {
+ if ( array[ i ] === elem ) {
+ return i;
+ }
+ }
+
+ return -1;
+}
+
+function Test( settings ) {
+ var i, l;
+
+ ++Test.count;
+
+ extend( this, settings );
+ this.assertions = [];
+ this.semaphore = 0;
+ this.usedAsync = false;
+ this.module = config.currentModule;
+ this.stack = sourceFromStacktrace( 3 );
+
+ // Register unique strings
+ for ( i = 0, l = this.module.tests; i < l.length; i++ ) {
+ if ( this.module.tests[ i ].name === this.testName ) {
+ this.testName += " ";
+ }
+ }
+
+ this.testId = generateHash( this.module.name, this.testName );
+
+ this.module.tests.push({
+ name: this.testName,
+ testId: this.testId
+ });
+
+ if ( settings.skip ) {
+
+ // Skipped tests will fully ignore any sent callback
+ this.callback = function() {};
+ this.async = false;
+ this.expected = 0;
+ } else {
+ this.assert = new Assert( this );
+ }
+}
+
+Test.count = 0;
+
+Test.prototype = {
+ before: function() {
+ if (
+
+ // Emit moduleStart when we're switching from one module to another
+ this.module !== config.previousModule ||
+
+ // They could be equal (both undefined) but if the previousModule property doesn't
+ // yet exist it means this is the first test in a suite that isn't wrapped in a
+ // module, in which case we'll just emit a moduleStart event for 'undefined'.
+ // Without this, reporters can get testStart before moduleStart which is a problem.
+ !hasOwn.call( config, "previousModule" )
+ ) {
+ if ( hasOwn.call( config, "previousModule" ) ) {
+ runLoggingCallbacks( "moduleDone", {
+ name: config.previousModule.name,
+ tests: config.previousModule.tests,
+ failed: config.moduleStats.bad,
+ passed: config.moduleStats.all - config.moduleStats.bad,
+ total: config.moduleStats.all,
+ runtime: now() - config.moduleStats.started
+ });
+ }
+ config.previousModule = this.module;
+ config.moduleStats = { all: 0, bad: 0, started: now() };
+ runLoggingCallbacks( "moduleStart", {
+ name: this.module.name,
+ tests: this.module.tests
+ });
+ }
+
+ config.current = this;
+
+ this.testEnvironment = extend( {}, this.module.testEnvironment );
+ delete this.testEnvironment.beforeEach;
+ delete this.testEnvironment.afterEach;
+
+ this.started = now();
+ runLoggingCallbacks( "testStart", {
+ name: this.testName,
+ module: this.module.name,
+ testId: this.testId
+ });
+
+ if ( !config.pollution ) {
+ saveGlobal();
+ }
+ },
+
+ run: function() {
+ var promise;
+
+ config.current = this;
+
+ if ( this.async ) {
+ QUnit.stop();
+ }
+
+ this.callbackStarted = now();
+
+ if ( config.notrycatch ) {
+ promise = this.callback.call( this.testEnvironment, this.assert );
+ this.resolvePromise( promise );
+ return;
+ }
+
+ try {
+ promise = this.callback.call( this.testEnvironment, this.assert );
+ this.resolvePromise( promise );
+ } catch ( e ) {
+ this.pushFailure( "Died on test #" + ( this.assertions.length + 1 ) + " " +
+ this.stack + ": " + ( e.message || e ), extractStacktrace( e, 0 ) );
+
+ // else next test will carry the responsibility
+ saveGlobal();
+
+ // Restart the tests if they're blocking
+ if ( config.blocking ) {
+ QUnit.start();
+ }
+ }
+ },
+
+ after: function() {
+ checkPollution();
+ },
+
+ queueHook: function( hook, hookName ) {
+ var promise,
+ test = this;
+ return function runHook() {
+ config.current = test;
+ if ( config.notrycatch ) {
+ promise = hook.call( test.testEnvironment, test.assert );
+ test.resolvePromise( promise, hookName );
+ return;
+ }
+ try {
+ promise = hook.call( test.testEnvironment, test.assert );
+ test.resolvePromise( promise, hookName );
+ } catch ( error ) {
+ test.pushFailure( hookName + " failed on " + test.testName + ": " +
+ ( error.message || error ), extractStacktrace( error, 0 ) );
+ }
+ };
+ },
+
+ // Currently only used for module level hooks, can be used to add global level ones
+ hooks: function( handler ) {
+ var hooks = [];
+
+ // Hooks are ignored on skipped tests
+ if ( this.skip ) {
+ return hooks;
+ }
+
+ if ( this.module.testEnvironment &&
+ QUnit.objectType( this.module.testEnvironment[ handler ] ) === "function" ) {
+ hooks.push( this.queueHook( this.module.testEnvironment[ handler ], handler ) );
+ }
+
+ return hooks;
+ },
+
+ finish: function() {
+ config.current = this;
+ if ( config.requireExpects && this.expected === null ) {
+ this.pushFailure( "Expected number of assertions to be defined, but expect() was " +
+ "not called.", this.stack );
+ } else if ( this.expected !== null && this.expected !== this.assertions.length ) {
+ this.pushFailure( "Expected " + this.expected + " assertions, but " +
+ this.assertions.length + " were run", this.stack );
+ } else if ( this.expected === null && !this.assertions.length ) {
+ this.pushFailure( "Expected at least one assertion, but none were run - call " +
+ "expect(0) to accept zero assertions.", this.stack );
+ }
+
+ var i,
+ bad = 0;
+
+ this.runtime = now() - this.started;
+ config.stats.all += this.assertions.length;
+ config.moduleStats.all += this.assertions.length;
+
+ for ( i = 0; i < this.assertions.length; i++ ) {
+ if ( !this.assertions[ i ].result ) {
+ bad++;
+ config.stats.bad++;
+ config.moduleStats.bad++;
+ }
+ }
+
+ runLoggingCallbacks( "testDone", {
+ name: this.testName,
+ module: this.module.name,
+ skipped: !!this.skip,
+ failed: bad,
+ passed: this.assertions.length - bad,
+ total: this.assertions.length,
+ runtime: this.runtime,
+
+ // HTML Reporter use
+ assertions: this.assertions,
+ testId: this.testId,
+
+ // DEPRECATED: this property will be removed in 2.0.0, use runtime instead
+ duration: this.runtime
+ });
+
+ // QUnit.reset() is deprecated and will be replaced for a new
+ // fixture reset function on QUnit 2.0/2.1.
+ // It's still called here for backwards compatibility handling
+ QUnit.reset();
+
+ config.current = undefined;
+ },
+
+ queue: function() {
+ var bad,
+ test = this;
+
+ if ( !this.valid() ) {
+ return;
+ }
+
+ function run() {
+
+ // each of these can by async
+ synchronize([
+ function() {
+ test.before();
+ },
+
+ test.hooks( "beforeEach" ),
+
+ function() {
+ test.run();
+ },
+
+ test.hooks( "afterEach" ).reverse(),
+
+ function() {
+ test.after();
+ },
+ function() {
+ test.finish();
+ }
+ ]);
+ }
+
+ // `bad` initialized at top of scope
+ // defer when previous test run passed, if storage is available
+ bad = QUnit.config.reorder && defined.sessionStorage &&
+ +sessionStorage.getItem( "qunit-test-" + this.module.name + "-" + this.testName );
+
+ if ( bad ) {
+ run();
+ } else {
+ synchronize( run, true );
+ }
+ },
+
+ push: function( result, actual, expected, message ) {
+ var source,
+ details = {
+ module: this.module.name,
+ name: this.testName,
+ result: result,
+ message: message,
+ actual: actual,
+ expected: expected,
+ testId: this.testId,
+ runtime: now() - this.started
+ };
+
+ if ( !result ) {
+ source = sourceFromStacktrace();
+
+ if ( source ) {
+ details.source = source;
+ }
+ }
+
+ runLoggingCallbacks( "log", details );
+
+ this.assertions.push({
+ result: !!result,
+ message: message
+ });
+ },
+
+ pushFailure: function( message, source, actual ) {
+ if ( !this instanceof Test ) {
+ throw new Error( "pushFailure() assertion outside test context, was " +
+ sourceFromStacktrace( 2 ) );
+ }
+
+ var details = {
+ module: this.module.name,
+ name: this.testName,
+ result: false,
+ message: message || "error",
+ actual: actual || null,
+ testId: this.testId,
+ runtime: now() - this.started
+ };
+
+ if ( source ) {
+ details.source = source;
+ }
+
+ runLoggingCallbacks( "log", details );
+
+ this.assertions.push({
+ result: false,
+ message: message
+ });
+ },
+
+ resolvePromise: function( promise, phase ) {
+ var then, message,
+ test = this;
+ if ( promise != null ) {
+ then = promise.then;
+ if ( QUnit.objectType( then ) === "function" ) {
+ QUnit.stop();
+ then.call(
+ promise,
+ QUnit.start,
+ function( error ) {
+ message = "Promise rejected " +
+ ( !phase ? "during" : phase.replace( /Each$/, "" ) ) +
+ " " + test.testName + ": " + ( error.message || error );
+ test.pushFailure( message, extractStacktrace( error, 0 ) );
+
+ // else next test will carry the responsibility
+ saveGlobal();
+
+ // Unblock
+ QUnit.start();
+ }
+ );
+ }
+ }
+ },
+
+ valid: function() {
+ var include,
+ filter = config.filter && config.filter.toLowerCase(),
+ module = QUnit.urlParams.module && QUnit.urlParams.module.toLowerCase(),
+ fullName = ( this.module.name + ": " + this.testName ).toLowerCase();
+
+ // Internally-generated tests are always valid
+ if ( this.callback && this.callback.validTest ) {
+ return true;
+ }
+
+ if ( config.testId.length > 0 && inArray( this.testId, config.testId ) < 0 ) {
+ return false;
+ }
+
+ if ( module && ( !this.module.name || this.module.name.toLowerCase() !== module ) ) {
+ return false;
+ }
+
+ if ( !filter ) {
+ return true;
+ }
+
+ include = filter.charAt( 0 ) !== "!";
+ if ( !include ) {
+ filter = filter.slice( 1 );
+ }
+
+ // If the filter matches, we need to honour include
+ if ( fullName.indexOf( filter ) !== -1 ) {
+ return include;
+ }
+
+ // Otherwise, do the opposite
+ return !include;
+ }
+
+};
+
+// Resets the test setup. Useful for tests that modify the DOM.
+/*
+DEPRECATED: Use multiple tests instead of resetting inside a test.
+Use testStart or testDone for custom cleanup.
+This method will throw an error in 2.0, and will be removed in 2.1
+*/
+QUnit.reset = function() {
+
+ // Return on non-browser environments
+ // This is necessary to not break on node tests
+ if ( typeof window === "undefined" ) {
+ return;
+ }
+
+ var fixture = defined.document && document.getElementById &&
+ document.getElementById( "qunit-fixture" );
+
+ if ( fixture ) {
+ fixture.innerHTML = config.fixture;
+ }
+};
+
+QUnit.pushFailure = function() {
+ if ( !QUnit.config.current ) {
+ throw new Error( "pushFailure() assertion outside test context, in " +
+ sourceFromStacktrace( 2 ) );
+ }
+
+ // Gets current test obj
+ var currentTest = QUnit.config.current;
+
+ return currentTest.pushFailure.apply( currentTest, arguments );
+};
+
+// Based on Java's String.hashCode, a simple but not
+// rigorously collision resistant hashing function
+function generateHash( module, testName ) {
+ var hex,
+ i = 0,
+ hash = 0,
+ str = module + "\x1C" + testName,
+ len = str.length;
+
+ for ( ; i < len; i++ ) {
+ hash = ( ( hash << 5 ) - hash ) + str.charCodeAt( i );
+ hash |= 0;
+ }
+
+ // Convert the possibly negative integer hash code into an 8 character hex string, which isn't
+ // strictly necessary but increases user understanding that the id is a SHA-like hash
+ hex = ( 0x100000000 + hash ).toString( 16 );
+ if ( hex.length < 8 ) {
+ hex = "0000000" + hex;
+ }
+
+ return hex.slice( -8 );
+}
+
+function Assert( testContext ) {
+ this.test = testContext;
+}
+
+// Assert helpers
+QUnit.assert = Assert.prototype = {
+
+ // Specify the number of expected assertions to guarantee that failed test
+ // (no assertions are run at all) don't slip through.
+ expect: function( asserts ) {
+ if ( arguments.length === 1 ) {
+ this.test.expected = asserts;
+ } else {
+ return this.test.expected;
+ }
+ },
+
+ // Increment this Test's semaphore counter, then return a single-use function that
+ // decrements that counter a maximum of once.
+ async: function() {
+ var test = this.test,
+ popped = false;
+
+ test.semaphore += 1;
+ test.usedAsync = true;
+ pauseProcessing();
+
+ return function done() {
+ if ( !popped ) {
+ test.semaphore -= 1;
+ popped = true;
+ resumeProcessing();
+ } else {
+ test.pushFailure( "Called the callback returned from `assert.async` more than once",
+ sourceFromStacktrace( 2 ) );
+ }
+ };
+ },
+
+ // Exports test.push() to the user API
+ push: function( /* result, actual, expected, message */ ) {
+ var assert = this,
+ currentTest = ( assert instanceof Assert && assert.test ) || QUnit.config.current;
+
+ // Backwards compatibility fix.
+ // Allows the direct use of global exported assertions and QUnit.assert.*
+ // Although, it's use is not recommended as it can leak assertions
+ // to other tests from async tests, because we only get a reference to the current test,
+ // not exactly the test where assertion were intended to be called.
+ if ( !currentTest ) {
+ throw new Error( "assertion outside test context, in " + sourceFromStacktrace( 2 ) );
+ }
+
+ if ( currentTest.usedAsync === true && currentTest.semaphore === 0 ) {
+ currentTest.pushFailure( "Assertion after the final `assert.async` was resolved",
+ sourceFromStacktrace( 2 ) );
+
+ // Allow this assertion to continue running anyway...
+ }
+
+ if ( !( assert instanceof Assert ) ) {
+ assert = currentTest.assert;
+ }
+ return assert.test.push.apply( assert.test, arguments );
+ },
+
+ ok: function( result, message ) {
+ message = message || ( result ? "okay" : "failed, expected argument to be truthy, was: " +
+ QUnit.dump.parse( result ) );
+ this.push( !!result, result, true, message );
+ },
+
+ notOk: function( result, message ) {
+ message = message || ( !result ? "okay" : "failed, expected argument to be falsy, was: " +
+ QUnit.dump.parse( result ) );
+ this.push( !result, result, false, message );
+ },
+
+ equal: function( actual, expected, message ) {
+ /*jshint eqeqeq:false */
+ this.push( expected == actual, actual, expected, message );
+ },
+
+ notEqual: function( actual, expected, message ) {
+ /*jshint eqeqeq:false */
+ this.push( expected != actual, actual, expected, message );
+ },
+
+ propEqual: function( actual, expected, message ) {
+ actual = objectValues( actual );
+ expected = objectValues( expected );
+ this.push( QUnit.equiv( actual, expected ), actual, expected, message );
+ },
+
+ notPropEqual: function( actual, expected, message ) {
+ actual = objectValues( actual );
+ expected = objectValues( expected );
+ this.push( !QUnit.equiv( actual, expected ), actual, expected, message );
+ },
+
+ deepEqual: function( actual, expected, message ) {
+ this.push( QUnit.equiv( actual, expected ), actual, expected, message );
+ },
+
+ notDeepEqual: function( actual, expected, message ) {
+ this.push( !QUnit.equiv( actual, expected ), actual, expected, message );
+ },
+
+ strictEqual: function( actual, expected, message ) {
+ this.push( expected === actual, actual, expected, message );
+ },
+
+ notStrictEqual: function( actual, expected, message ) {
+ this.push( expected !== actual, actual, expected, message );
+ },
+
+ "throws": function( block, expected, message ) {
+ var actual, expectedType,
+ expectedOutput = expected,
+ ok = false,
+ currentTest = ( this instanceof Assert && this.test ) || QUnit.config.current;
+
+ // 'expected' is optional unless doing string comparison
+ if ( message == null && typeof expected === "string" ) {
+ message = expected;
+ expected = null;
+ }
+
+ currentTest.ignoreGlobalErrors = true;
+ try {
+ block.call( currentTest.testEnvironment );
+ } catch (e) {
+ actual = e;
+ }
+ currentTest.ignoreGlobalErrors = false;
+
+ if ( actual ) {
+ expectedType = QUnit.objectType( expected );
+
+ // we don't want to validate thrown error
+ if ( !expected ) {
+ ok = true;
+ expectedOutput = null;
+
+ // expected is a regexp
+ } else if ( expectedType === "regexp" ) {
+ ok = expected.test( errorString( actual ) );
+
+ // expected is a string
+ } else if ( expectedType === "string" ) {
+ ok = expected === errorString( actual );
+
+ // expected is a constructor, maybe an Error constructor
+ } else if ( expectedType === "function" && actual instanceof expected ) {
+ ok = true;
+
+ // expected is an Error object
+ } else if ( expectedType === "object" ) {
+ ok = actual instanceof expected.constructor &&
+ actual.name === expected.name &&
+ actual.message === expected.message;
+
+ // expected is a validation function which returns true if validation passed
+ } else if ( expectedType === "function" && expected.call( {}, actual ) === true ) {
+ expectedOutput = null;
+ ok = true;
+ }
+ }
+
+ currentTest.assert.push( ok, actual, expectedOutput, message );
+ }
+};
+
+// Provide an alternative to assert.throws(), for enviroments that consider throws a reserved word
+// Known to us are: Closure Compiler, Narwhal
+(function() {
+ /*jshint sub:true */
+ Assert.prototype.raises = Assert.prototype[ "throws" ];
+}());
+
+// Test for equality any JavaScript type.
+// Author: Philippe Rathé <prathe@gmail.com>
+QUnit.equiv = (function() {
+
+ // Call the o related callback with the given arguments.
+ function bindCallbacks( o, callbacks, args ) {
+ var prop = QUnit.objectType( o );
+ if ( prop ) {
+ if ( QUnit.objectType( callbacks[ prop ] ) === "function" ) {
+ return callbacks[ prop ].apply( callbacks, args );
+ } else {
+ return callbacks[ prop ]; // or undefined
+ }
+ }
+ }
+
+ // the real equiv function
+ var innerEquiv,
+
+ // stack to decide between skip/abort functions
+ callers = [],
+
+ // stack to avoiding loops from circular referencing
+ parents = [],
+ parentsB = [],
+
+ getProto = Object.getPrototypeOf || function( obj ) {
+ /* jshint camelcase: false, proto: true */
+ return obj.__proto__;
+ },
+ callbacks = (function() {
+
+ // for string, boolean, number and null
+ function useStrictEquality( b, a ) {
+
+ /*jshint eqeqeq:false */
+ if ( b instanceof a.constructor || a instanceof b.constructor ) {
+
+ // to catch short annotation VS 'new' annotation of a
+ // declaration
+ // e.g. var i = 1;
+ // var j = new Number(1);
+ return a == b;
+ } else {
+ return a === b;
+ }
+ }
+
+ return {
+ "string": useStrictEquality,
+ "boolean": useStrictEquality,
+ "number": useStrictEquality,
+ "null": useStrictEquality,
+ "undefined": useStrictEquality,
+
+ "nan": function( b ) {
+ return isNaN( b );
+ },
+
+ "date": function( b, a ) {
+ return QUnit.objectType( b ) === "date" && a.valueOf() === b.valueOf();
+ },
+
+ "regexp": function( b, a ) {
+ return QUnit.objectType( b ) === "regexp" &&
+
+ // the regex itself
+ a.source === b.source &&
+
+ // and its modifiers
+ a.global === b.global &&
+
+ // (gmi) ...
+ a.ignoreCase === b.ignoreCase &&
+ a.multiline === b.multiline &&
+ a.sticky === b.sticky;
+ },
+
+ // - skip when the property is a method of an instance (OOP)
+ // - abort otherwise,
+ // initial === would have catch identical references anyway
+ "function": function() {
+ var caller = callers[ callers.length - 1 ];
+ return caller !== Object && typeof caller !== "undefined";
+ },
+
+ "array": function( b, a ) {
+ var i, j, len, loop, aCircular, bCircular;
+
+ // b could be an object literal here
+ if ( QUnit.objectType( b ) !== "array" ) {
+ return false;
+ }
+
+ len = a.length;
+ if ( len !== b.length ) {
+ // safe and faster
+ return false;
+ }
+
+ // track reference to avoid circular references
+ parents.push( a );
+ parentsB.push( b );
+ for ( i = 0; i < len; i++ ) {
+ loop = false;
+ for ( j = 0; j < parents.length; j++ ) {
+ aCircular = parents[ j ] === a[ i ];
+ bCircular = parentsB[ j ] === b[ i ];
+ if ( aCircular || bCircular ) {
+ if ( a[ i ] === b[ i ] || aCircular && bCircular ) {
+ loop = true;
+ } else {
+ parents.pop();
+ parentsB.pop();
+ return false;
+ }
+ }
+ }
+ if ( !loop && !innerEquiv( a[ i ], b[ i ] ) ) {
+ parents.pop();
+ parentsB.pop();
+ return false;
+ }
+ }
+ parents.pop();
+ parentsB.pop();
+ return true;
+ },
+
+ "object": function( b, a ) {
+
+ /*jshint forin:false */
+ var i, j, loop, aCircular, bCircular,
+ // Default to true
+ eq = true,
+ aProperties = [],
+ bProperties = [];
+
+ // comparing constructors is more strict than using
+ // instanceof
+ if ( a.constructor !== b.constructor ) {
+
+ // Allow objects with no prototype to be equivalent to
+ // objects with Object as their constructor.
+ if ( !( ( getProto( a ) === null && getProto( b ) === Object.prototype ) ||
+ ( getProto( b ) === null && getProto( a ) === Object.prototype ) ) ) {
+ return false;
+ }
+ }
+
+ // stack constructor before traversing properties
+ callers.push( a.constructor );
+
+ // track reference to avoid circular references
+ parents.push( a );
+ parentsB.push( b );
+
+ // be strict: don't ensure hasOwnProperty and go deep
+ for ( i in a ) {
+ loop = false;
+ for ( j = 0; j < parents.length; j++ ) {
+ aCircular = parents[ j ] === a[ i ];
+ bCircular = parentsB[ j ] === b[ i ];
+ if ( aCircular || bCircular ) {
+ if ( a[ i ] === b[ i ] || aCircular && bCircular ) {
+ loop = true;
+ } else {
+ eq = false;
+ break;
+ }
+ }
+ }
+ aProperties.push( i );
+ if ( !loop && !innerEquiv( a[ i ], b[ i ] ) ) {
+ eq = false;
+ break;
+ }
+ }
+
+ parents.pop();
+ parentsB.pop();
+ callers.pop(); // unstack, we are done
+
+ for ( i in b ) {
+ bProperties.push( i ); // collect b's properties
+ }
+
+ // Ensures identical properties name
+ return eq && innerEquiv( aProperties.sort(), bProperties.sort() );
+ }
+ };
+ }());
+
+ innerEquiv = function() { // can take multiple arguments
+ var args = [].slice.apply( arguments );
+ if ( args.length < 2 ) {
+ return true; // end transition
+ }
+
+ return ( (function( a, b ) {
+ if ( a === b ) {
+ return true; // catch the most you can
+ } else if ( a === null || b === null || typeof a === "undefined" ||
+ typeof b === "undefined" ||
+ QUnit.objectType( a ) !== QUnit.objectType( b ) ) {
+
+ // don't lose time with error prone cases
+ return false;
+ } else {
+ return bindCallbacks( a, callbacks, [ b, a ] );
+ }
+
+ // apply transition with (1..n) arguments
+ }( args[ 0 ], args[ 1 ] ) ) &&
+ innerEquiv.apply( this, args.splice( 1, args.length - 1 ) ) );
+ };
+
+ return innerEquiv;
+}());
+
+// Based on jsDump by Ariel Flesler
+// http://flesler.blogspot.com/2008/05/jsdump-pretty-dump-of-any-javascript.html
+QUnit.dump = (function() {
+ function quote( str ) {
+ return "\"" + str.toString().replace( /"/g, "\\\"" ) + "\"";
+ }
+ function literal( o ) {
+ return o + "";
+ }
+ function join( pre, arr, post ) {
+ var s = dump.separator(),
+ base = dump.indent(),
+ inner = dump.indent( 1 );
+ if ( arr.join ) {
+ arr = arr.join( "," + s + inner );
+ }
+ if ( !arr ) {
+ return pre + post;
+ }
+ return [ pre, inner + arr, base + post ].join( s );
+ }
+ function array( arr, stack ) {
+ var i = arr.length,
+ ret = new Array( i );
+
+ if ( dump.maxDepth && dump.depth > dump.maxDepth ) {
+ return "[object Array]";
+ }
+
+ this.up();
+ while ( i-- ) {
+ ret[ i ] = this.parse( arr[ i ], undefined, stack );
+ }
+ this.down();
+ return join( "[", ret, "]" );
+ }
+
+ var reName = /^function (\w+)/,
+ dump = {
+
+ // objType is used mostly internally, you can fix a (custom) type in advance
+ parse: function( obj, objType, stack ) {
+ stack = stack || [];
+ var res, parser, parserType,
+ inStack = inArray( obj, stack );
+
+ if ( inStack !== -1 ) {
+ return "recursion(" + ( inStack - stack.length ) + ")";
+ }
+
+ objType = objType || this.typeOf( obj );
+ parser = this.parsers[ objType ];
+ parserType = typeof parser;
+
+ if ( parserType === "function" ) {
+ stack.push( obj );
+ res = parser.call( this, obj, stack );
+ stack.pop();
+ return res;
+ }
+ return ( parserType === "string" ) ? parser : this.parsers.error;
+ },
+ typeOf: function( obj ) {
+ var type;
+ if ( obj === null ) {
+ type = "null";
+ } else if ( typeof obj === "undefined" ) {
+ type = "undefined";
+ } else if ( QUnit.is( "regexp", obj ) ) {
+ type = "regexp";
+ } else if ( QUnit.is( "date", obj ) ) {
+ type = "date";
+ } else if ( QUnit.is( "function", obj ) ) {
+ type = "function";
+ } else if ( obj.setInterval !== undefined &&
+ obj.document !== undefined &&
+ obj.nodeType === undefined ) {
+ type = "window";
+ } else if ( obj.nodeType === 9 ) {
+ type = "document";
+ } else if ( obj.nodeType ) {
+ type = "node";
+ } else if (
+
+ // native arrays
+ toString.call( obj ) === "[object Array]" ||
+
+ // NodeList objects
+ ( typeof obj.length === "number" && obj.item !== undefined &&
+ ( obj.length ? obj.item( 0 ) === obj[ 0 ] : ( obj.item( 0 ) === null &&
+ obj[ 0 ] === undefined ) ) )
+ ) {
+ type = "array";
+ } else if ( obj.constructor === Error.prototype.constructor ) {
+ type = "error";
+ } else {
+ type = typeof obj;
+ }
+ return type;
+ },
+ separator: function() {
+ return this.multiline ? this.HTML ? "<br />" : "\n" : this.HTML ? "&#160;" : " ";
+ },
+ // extra can be a number, shortcut for increasing-calling-decreasing
+ indent: function( extra ) {
+ if ( !this.multiline ) {
+ return "";
+ }
+ var chr = this.indentChar;
+ if ( this.HTML ) {
+ chr = chr.replace( /\t/g, " " ).replace( / /g, "&#160;" );
+ }
+ return new Array( this.depth + ( extra || 0 ) ).join( chr );
+ },
+ up: function( a ) {
+ this.depth += a || 1;
+ },
+ down: function( a ) {
+ this.depth -= a || 1;
+ },
+ setParser: function( name, parser ) {
+ this.parsers[ name ] = parser;
+ },
+ // The next 3 are exposed so you can use them
+ quote: quote,
+ literal: literal,
+ join: join,
+ //
+ depth: 1,
+ maxDepth: QUnit.config.maxDepth,
+
+ // This is the list of parsers, to modify them, use dump.setParser
+ parsers: {
+ window: "[Window]",
+ document: "[Document]",
+ error: function( error ) {
+ return "Error(\"" + error.message + "\")";
+ },
+ unknown: "[Unknown]",
+ "null": "null",
+ "undefined": "undefined",
+ "function": function( fn ) {
+ var ret = "function",
+
+ // functions never have name in IE
+ name = "name" in fn ? fn.name : ( reName.exec( fn ) || [] )[ 1 ];
+
+ if ( name ) {
+ ret += " " + name;
+ }
+ ret += "( ";
+
+ ret = [ ret, dump.parse( fn, "functionArgs" ), "){" ].join( "" );
+ return join( ret, dump.parse( fn, "functionCode" ), "}" );
+ },
+ array: array,
+ nodelist: array,
+ "arguments": array,
+ object: function( map, stack ) {
+ var keys, key, val, i, nonEnumerableProperties,
+ ret = [];
+
+ if ( dump.maxDepth && dump.depth > dump.maxDepth ) {
+ return "[object Object]";
+ }
+
+ dump.up();
+ keys = [];
+ for ( key in map ) {
+ keys.push( key );
+ }
+
+ // Some properties are not always enumerable on Error objects.
+ nonEnumerableProperties = [ "message", "name" ];
+ for ( i in nonEnumerableProperties ) {
+ key = nonEnumerableProperties[ i ];
+ if ( key in map && inArray( key, keys ) < 0 ) {
+ keys.push( key );
+ }
+ }
+ keys.sort();
+ for ( i = 0; i < keys.length; i++ ) {
+ key = keys[ i ];
+ val = map[ key ];
+ ret.push( dump.parse( key, "key" ) + ": " +
+ dump.parse( val, undefined, stack ) );
+ }
+ dump.down();
+ return join( "{", ret, "}" );
+ },
+ node: function( node ) {
+ var len, i, val,
+ open = dump.HTML ? "&lt;" : "<",
+ close = dump.HTML ? "&gt;" : ">",
+ tag = node.nodeName.toLowerCase(),
+ ret = open + tag,
+ attrs = node.attributes;
+
+ if ( attrs ) {
+ for ( i = 0, len = attrs.length; i < len; i++ ) {
+ val = attrs[ i ].nodeValue;
+
+ // IE6 includes all attributes in .attributes, even ones not explicitly
+ // set. Those have values like undefined, null, 0, false, "" or
+ // "inherit".
+ if ( val && val !== "inherit" ) {
+ ret += " " + attrs[ i ].nodeName + "=" +
+ dump.parse( val, "attribute" );
+ }
+ }
+ }
+ ret += close;
+
+ // Show content of TextNode or CDATASection
+ if ( node.nodeType === 3 || node.nodeType === 4 ) {
+ ret += node.nodeValue;
+ }
+
+ return ret + open + "/" + tag + close;
+ },
+
+ // function calls it internally, it's the arguments part of the function
+ functionArgs: function( fn ) {
+ var args,
+ l = fn.length;
+
+ if ( !l ) {
+ return "";
+ }
+
+ args = new Array( l );
+ while ( l-- ) {
+
+ // 97 is 'a'
+ args[ l ] = String.fromCharCode( 97 + l );
+ }
+ return " " + args.join( ", " ) + " ";
+ },
+ // object calls it internally, the key part of an item in a map
+ key: quote,
+ // function calls it internally, it's the content of the function
+ functionCode: "[code]",
+ // node calls it internally, it's an html attribute value
+ attribute: quote,
+ string: quote,
+ date: quote,
+ regexp: literal,
+ number: literal,
+ "boolean": literal
+ },
+ // if true, entities are escaped ( <, >, \t, space and \n )
+ HTML: false,
+ // indentation unit
+ indentChar: " ",
+ // if true, items in a collection, are separated by a \n, else just a space.
+ multiline: true
+ };
+
+ return dump;
+}());
+
+// back compat
+QUnit.jsDump = QUnit.dump;
+
+// For browser, export only select globals
+if ( typeof window !== "undefined" ) {
+
+ // Deprecated
+ // Extend assert methods to QUnit and Global scope through Backwards compatibility
+ (function() {
+ var i,
+ assertions = Assert.prototype;
+
+ function applyCurrent( current ) {
+ return function() {
+ var assert = new Assert( QUnit.config.current );
+ current.apply( assert, arguments );
+ };
+ }
+
+ for ( i in assertions ) {
+ QUnit[ i ] = applyCurrent( assertions[ i ] );
+ }
+ })();
+
+ (function() {
+ var i, l,
+ keys = [
+ "test",
+ "module",
+ "expect",
+ "asyncTest",
+ "start",
+ "stop",
+ "ok",
+ "notOk",
+ "equal",
+ "notEqual",
+ "propEqual",
+ "notPropEqual",
+ "deepEqual",
+ "notDeepEqual",
+ "strictEqual",
+ "notStrictEqual",
+ "throws"
+ ];
+
+ for ( i = 0, l = keys.length; i < l; i++ ) {
+ window[ keys[ i ] ] = QUnit[ keys[ i ] ];
+ }
+ })();
+
+ window.QUnit = QUnit;
+}
+
+// For nodejs
+if ( typeof module !== "undefined" && module && module.exports ) {
+ module.exports = QUnit;
+
+ // For consistency with CommonJS environments' exports
+ module.exports.QUnit = QUnit;
+}
+
+// For CommonJS with exports, but without module.exports, like Rhino
+if ( typeof exports !== "undefined" && exports ) {
+ exports.QUnit = QUnit;
+}
+
+if ( typeof define === "function" && define.amd ) {
+ define( function() {
+ return QUnit;
+ } );
+ QUnit.config.autostart = false;
+}
+
+// Get a reference to the global object, like window in browsers
+}( (function() {
+ return this;
+})() ));
+
+/*istanbul ignore next */
+// jscs:disable maximumLineLength
+/*
+ * This file is a modified version of google-diff-match-patch's JavaScript implementation
+ * (https://code.google.com/p/google-diff-match-patch/source/browse/trunk/javascript/diff_match_patch_uncompressed.js),
+ * modifications are licensed as more fully set forth in LICENSE.txt.
+ *
+ * The original source of google-diff-match-patch is attributable and licensed as follows:
+ *
+ * Copyright 2006 Google Inc.
+ * http://code.google.com/p/google-diff-match-patch/
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ *
+ * More Info:
+ * https://code.google.com/p/google-diff-match-patch/
+ *
+ * Usage: QUnit.diff(expected, actual)
+ *
+ * QUnit.diff( "the quick brown fox jumped over", "the quick fox jumps over" ) === "the quick <del>brown </del> fox jump<ins>s</ins><del>ed</del over"
+ */
+QUnit.diff = (function() {
+
+ function DiffMatchPatch() {
+
+ // Defaults.
+ // Redefine these in your program to override the defaults.
+
+ // Number of seconds to map a diff before giving up (0 for infinity).
+ this.DiffTimeout = 1.0;
+ // Cost of an empty edit operation in terms of edit characters.
+ this.DiffEditCost = 4;
+ }
+
+ // DIFF FUNCTIONS
+
+ /**
+ * The data structure representing a diff is an array of tuples:
+ * [[DIFF_DELETE, 'Hello'], [DIFF_INSERT, 'Goodbye'], [DIFF_EQUAL, ' world.']]
+ * which means: delete 'Hello', add 'Goodbye' and keep ' world.'
+ */
+ var DIFF_DELETE = -1,
+ DIFF_INSERT = 1,
+ DIFF_EQUAL = 0;
+
+ /**
+ * Find the differences between two texts. Simplifies the problem by stripping
+ * any common prefix or suffix off the texts before diffing.
+ * @param {string} text1 Old string to be diffed.
+ * @param {string} text2 New string to be diffed.
+ * @param {boolean=} optChecklines Optional speedup flag. If present and false,
+ * then don't run a line-level diff first to identify the changed areas.
+ * Defaults to true, which does a faster, slightly less optimal diff.
+ * @param {number} optDeadline Optional time when the diff should be complete
+ * by. Used internally for recursive calls. Users should set DiffTimeout
+ * instead.
+ * @return {!Array.<!DiffMatchPatch.Diff>} Array of diff tuples.
+ */
+ DiffMatchPatch.prototype.DiffMain = function( text1, text2, optChecklines, optDeadline ) {
+ var deadline, checklines, commonlength,
+ commonprefix, commonsuffix, diffs;
+ // Set a deadline by which time the diff must be complete.
+ if ( typeof optDeadline === "undefined" ) {
+ if ( this.DiffTimeout <= 0 ) {
+ optDeadline = Number.MAX_VALUE;
+ } else {
+ optDeadline = ( new Date() ).getTime() + this.DiffTimeout * 1000;
+ }
+ }
+ deadline = optDeadline;
+
+ // Check for null inputs.
+ if ( text1 === null || text2 === null ) {
+ throw new Error( "Null input. (DiffMain)" );
+ }
+
+ // Check for equality (speedup).
+ if ( text1 === text2 ) {
+ if ( text1 ) {
+ return [
+ [ DIFF_EQUAL, text1 ]
+ ];
+ }
+ return [];
+ }
+
+ if ( typeof optChecklines === "undefined" ) {
+ optChecklines = true;
+ }
+
+ checklines = optChecklines;
+
+ // Trim off common prefix (speedup).
+ commonlength = this.diffCommonPrefix( text1, text2 );
+ commonprefix = text1.substring( 0, commonlength );
+ text1 = text1.substring( commonlength );
+ text2 = text2.substring( commonlength );
+
+ // Trim off common suffix (speedup).
+ /////////
+ commonlength = this.diffCommonSuffix( text1, text2 );
+ commonsuffix = text1.substring( text1.length - commonlength );
+ text1 = text1.substring( 0, text1.length - commonlength );
+ text2 = text2.substring( 0, text2.length - commonlength );
+
+ // Compute the diff on the middle block.
+ diffs = this.diffCompute( text1, text2, checklines, deadline );
+
+ // Restore the prefix and suffix.
+ if ( commonprefix ) {
+ diffs.unshift( [ DIFF_EQUAL, commonprefix ] );
+ }
+ if ( commonsuffix ) {
+ diffs.push( [ DIFF_EQUAL, commonsuffix ] );
+ }
+ this.diffCleanupMerge( diffs );
+ return diffs;
+ };
+
+ /**
+ * Reduce the number of edits by eliminating operationally trivial equalities.
+ * @param {!Array.<!DiffMatchPatch.Diff>} diffs Array of diff tuples.
+ */
+ DiffMatchPatch.prototype.diffCleanupEfficiency = function( diffs ) {
+ var changes, equalities, equalitiesLength, lastequality,
+ pointer, preIns, preDel, postIns, postDel;
+ changes = false;
+ equalities = []; // Stack of indices where equalities are found.
+ equalitiesLength = 0; // Keeping our own length var is faster in JS.
+ /** @type {?string} */
+ lastequality = null;
+ // Always equal to diffs[equalities[equalitiesLength - 1]][1]
+ pointer = 0; // Index of current position.
+ // Is there an insertion operation before the last equality.
+ preIns = false;
+ // Is there a deletion operation before the last equality.
+ preDel = false;
+ // Is there an insertion operation after the last equality.
+ postIns = false;
+ // Is there a deletion operation after the last equality.
+ postDel = false;
+ while ( pointer < diffs.length ) {
+ if ( diffs[ pointer ][ 0 ] === DIFF_EQUAL ) { // Equality found.
+ if ( diffs[ pointer ][ 1 ].length < this.DiffEditCost && ( postIns || postDel ) ) {
+ // Candidate found.
+ equalities[ equalitiesLength++ ] = pointer;
+ preIns = postIns;
+ preDel = postDel;
+ lastequality = diffs[ pointer ][ 1 ];
+ } else {
+ // Not a candidate, and can never become one.
+ equalitiesLength = 0;
+ lastequality = null;
+ }
+ postIns = postDel = false;
+ } else { // An insertion or deletion.
+ if ( diffs[ pointer ][ 0 ] === DIFF_DELETE ) {
+ postDel = true;
+ } else {
+ postIns = true;
+ }
+ /*
+ * Five types to be split:
+ * <ins>A</ins><del>B</del>XY<ins>C</ins><del>D</del>
+ * <ins>A</ins>X<ins>C</ins><del>D</del>
+ * <ins>A</ins><del>B</del>X<ins>C</ins>
+ * <ins>A</del>X<ins>C</ins><del>D</del>
+ * <ins>A</ins><del>B</del>X<del>C</del>
+ */
+ if ( lastequality && ( ( preIns && preDel && postIns && postDel ) ||
+ ( ( lastequality.length < this.DiffEditCost / 2 ) &&
+ ( preIns + preDel + postIns + postDel ) === 3 ) ) ) {
+ // Duplicate record.
+ diffs.splice( equalities[equalitiesLength - 1], 0, [ DIFF_DELETE, lastequality ] );
+ // Change second copy to insert.
+ diffs[ equalities[ equalitiesLength - 1 ] + 1 ][ 0 ] = DIFF_INSERT;
+ equalitiesLength--; // Throw away the equality we just deleted;
+ lastequality = null;
+ if (preIns && preDel) {
+ // No changes made which could affect previous entry, keep going.
+ postIns = postDel = true;
+ equalitiesLength = 0;
+ } else {
+ equalitiesLength--; // Throw away the previous equality.
+ pointer = equalitiesLength > 0 ? equalities[ equalitiesLength - 1 ] : -1;
+ postIns = postDel = false;
+ }
+ changes = true;
+ }
+ }
+ pointer++;
+ }
+
+ if ( changes ) {
+ this.diffCleanupMerge( diffs );
+ }
+ };
+
+ /**
+ * Convert a diff array into a pretty HTML report.
+ * @param {!Array.<!DiffMatchPatch.Diff>} diffs Array of diff tuples.
+ * @param {integer} string to be beautified.
+ * @return {string} HTML representation.
+ */
+ DiffMatchPatch.prototype.diffPrettyHtml = function( diffs ) {
+ var op, data, x, html = [];
+ for ( x = 0; x < diffs.length; x++ ) {
+ op = diffs[x][0]; // Operation (insert, delete, equal)
+ data = diffs[x][1]; // Text of change.
+ switch ( op ) {
+ case DIFF_INSERT:
+ html[x] = "<ins>" + data + "</ins>";
+ break;
+ case DIFF_DELETE:
+ html[x] = "<del>" + data + "</del>";
+ break;
+ case DIFF_EQUAL:
+ html[x] = "<span>" + data + "</span>";
+ break;
+ }
+ }
+ return html.join("");
+ };
+
+ /**
+ * Determine the common prefix of two strings.
+ * @param {string} text1 First string.
+ * @param {string} text2 Second string.
+ * @return {number} The number of characters common to the start of each
+ * string.
+ */
+ DiffMatchPatch.prototype.diffCommonPrefix = function( text1, text2 ) {
+ var pointermid, pointermax, pointermin, pointerstart;
+ // Quick check for common null cases.
+ if ( !text1 || !text2 || text1.charAt(0) !== text2.charAt(0) ) {
+ return 0;
+ }
+ // Binary search.
+ // Performance analysis: http://neil.fraser.name/news/2007/10/09/
+ pointermin = 0;
+ pointermax = Math.min( text1.length, text2.length );
+ pointermid = pointermax;
+ pointerstart = 0;
+ while ( pointermin < pointermid ) {
+ if ( text1.substring( pointerstart, pointermid ) === text2.substring( pointerstart, pointermid ) ) {
+ pointermin = pointermid;
+ pointerstart = pointermin;
+ } else {
+ pointermax = pointermid;
+ }
+ pointermid = Math.floor( ( pointermax - pointermin ) / 2 + pointermin );
+ }
+ return pointermid;
+ };
+
+ /**
+ * Determine the common suffix of two strings.
+ * @param {string} text1 First string.
+ * @param {string} text2 Second string.
+ * @return {number} The number of characters common to the end of each string.
+ */
+ DiffMatchPatch.prototype.diffCommonSuffix = function( text1, text2 ) {
+ var pointermid, pointermax, pointermin, pointerend;
+ // Quick check for common null cases.
+ if (!text1 || !text2 || text1.charAt(text1.length - 1) !== text2.charAt(text2.length - 1)) {
+ return 0;
+ }
+ // Binary search.
+ // Performance analysis: http://neil.fraser.name/news/2007/10/09/
+ pointermin = 0;
+ pointermax = Math.min(text1.length, text2.length);
+ pointermid = pointermax;
+ pointerend = 0;
+ while ( pointermin < pointermid ) {
+ if (text1.substring( text1.length - pointermid, text1.length - pointerend ) ===
+ text2.substring( text2.length - pointermid, text2.length - pointerend ) ) {
+ pointermin = pointermid;
+ pointerend = pointermin;
+ } else {
+ pointermax = pointermid;
+ }
+ pointermid = Math.floor( ( pointermax - pointermin ) / 2 + pointermin );
+ }
+ return pointermid;
+ };
+
+ /**
+ * Find the differences between two texts. Assumes that the texts do not
+ * have any common prefix or suffix.
+ * @param {string} text1 Old string to be diffed.
+ * @param {string} text2 New string to be diffed.
+ * @param {boolean} checklines Speedup flag. If false, then don't run a
+ * line-level diff first to identify the changed areas.
+ * If true, then run a faster, slightly less optimal diff.
+ * @param {number} deadline Time when the diff should be complete by.
+ * @return {!Array.<!DiffMatchPatch.Diff>} Array of diff tuples.
+ * @private
+ */
+ DiffMatchPatch.prototype.diffCompute = function( text1, text2, checklines, deadline ) {
+ var diffs, longtext, shorttext, i, hm,
+ text1A, text2A, text1B, text2B,
+ midCommon, diffsA, diffsB;
+
+ if ( !text1 ) {
+ // Just add some text (speedup).
+ return [
+ [ DIFF_INSERT, text2 ]
+ ];
+ }
+
+ if (!text2) {
+ // Just delete some text (speedup).
+ return [
+ [ DIFF_DELETE, text1 ]
+ ];
+ }
+
+ longtext = text1.length > text2.length ? text1 : text2;
+ shorttext = text1.length > text2.length ? text2 : text1;
+ i = longtext.indexOf( shorttext );
+ if ( i !== -1 ) {
+ // Shorter text is inside the longer text (speedup).
+ diffs = [
+ [ DIFF_INSERT, longtext.substring( 0, i ) ],
+ [ DIFF_EQUAL, shorttext ],
+ [ DIFF_INSERT, longtext.substring( i + shorttext.length ) ]
+ ];
+ // Swap insertions for deletions if diff is reversed.
+ if ( text1.length > text2.length ) {
+ diffs[0][0] = diffs[2][0] = DIFF_DELETE;
+ }
+ return diffs;
+ }
+
+ if ( shorttext.length === 1 ) {
+ // Single character string.
+ // After the previous speedup, the character can't be an equality.
+ return [
+ [ DIFF_DELETE, text1 ],
+ [ DIFF_INSERT, text2 ]
+ ];
+ }
+
+ // Check to see if the problem can be split in two.
+ hm = this.diffHalfMatch(text1, text2);
+ if (hm) {
+ // A half-match was found, sort out the return data.
+ text1A = hm[0];
+ text1B = hm[1];
+ text2A = hm[2];
+ text2B = hm[3];
+ midCommon = hm[4];
+ // Send both pairs off for separate processing.
+ diffsA = this.DiffMain(text1A, text2A, checklines, deadline);
+ diffsB = this.DiffMain(text1B, text2B, checklines, deadline);
+ // Merge the results.
+ return diffsA.concat([
+ [ DIFF_EQUAL, midCommon ]
+ ], diffsB);
+ }
+
+ if (checklines && text1.length > 100 && text2.length > 100) {
+ return this.diffLineMode(text1, text2, deadline);
+ }
+
+ return this.diffBisect(text1, text2, deadline);
+ };
+
+ /**
+ * Do the two texts share a substring which is at least half the length of the
+ * longer text?
+ * This speedup can produce non-minimal diffs.
+ * @param {string} text1 First string.
+ * @param {string} text2 Second string.
+ * @return {Array.<string>} Five element Array, containing the prefix of
+ * text1, the suffix of text1, the prefix of text2, the suffix of
+ * text2 and the common middle. Or null if there was no match.
+ * @private
+ */
+ DiffMatchPatch.prototype.diffHalfMatch = function(text1, text2) {
+ var longtext, shorttext, dmp,
+ text1A, text2B, text2A, text1B, midCommon,
+ hm1, hm2, hm;
+ if (this.DiffTimeout <= 0) {
+ // Don't risk returning a non-optimal diff if we have unlimited time.
+ return null;
+ }
+ longtext = text1.length > text2.length ? text1 : text2;
+ shorttext = text1.length > text2.length ? text2 : text1;
+ if (longtext.length < 4 || shorttext.length * 2 < longtext.length) {
+ return null; // Pointless.
+ }
+ dmp = this; // 'this' becomes 'window' in a closure.
+
+ /**
+ * Does a substring of shorttext exist within longtext such that the substring
+ * is at least half the length of longtext?
+ * Closure, but does not reference any external variables.
+ * @param {string} longtext Longer string.
+ * @param {string} shorttext Shorter string.
+ * @param {number} i Start index of quarter length substring within longtext.
+ * @return {Array.<string>} Five element Array, containing the prefix of
+ * longtext, the suffix of longtext, the prefix of shorttext, the suffix
+ * of shorttext and the common middle. Or null if there was no match.
+ * @private
+ */
+ function diffHalfMatchI(longtext, shorttext, i) {
+ var seed, j, bestCommon, prefixLength, suffixLength,
+ bestLongtextA, bestLongtextB, bestShorttextA, bestShorttextB;
+ // Start with a 1/4 length substring at position i as a seed.
+ seed = longtext.substring(i, i + Math.floor(longtext.length / 4));
+ j = -1;
+ bestCommon = "";
+ while ((j = shorttext.indexOf(seed, j + 1)) !== -1) {
+ prefixLength = dmp.diffCommonPrefix(longtext.substring(i),
+ shorttext.substring(j));
+ suffixLength = dmp.diffCommonSuffix(longtext.substring(0, i),
+ shorttext.substring(0, j));
+ if (bestCommon.length < suffixLength + prefixLength) {
+ bestCommon = shorttext.substring(j - suffixLength, j) +
+ shorttext.substring(j, j + prefixLength);
+ bestLongtextA = longtext.substring(0, i - suffixLength);
+ bestLongtextB = longtext.substring(i + prefixLength);
+ bestShorttextA = shorttext.substring(0, j - suffixLength);
+ bestShorttextB = shorttext.substring(j + prefixLength);
+ }
+ }
+ if (bestCommon.length * 2 >= longtext.length) {
+ return [ bestLongtextA, bestLongtextB,
+ bestShorttextA, bestShorttextB, bestCommon
+ ];
+ } else {
+ return null;
+ }
+ }
+
+ // First check if the second quarter is the seed for a half-match.
+ hm1 = diffHalfMatchI(longtext, shorttext,
+ Math.ceil(longtext.length / 4));
+ // Check again based on the third quarter.
+ hm2 = diffHalfMatchI(longtext, shorttext,
+ Math.ceil(longtext.length / 2));
+ if (!hm1 && !hm2) {
+ return null;
+ } else if (!hm2) {
+ hm = hm1;
+ } else if (!hm1) {
+ hm = hm2;
+ } else {
+ // Both matched. Select the longest.
+ hm = hm1[4].length > hm2[4].length ? hm1 : hm2;
+ }
+
+ // A half-match was found, sort out the return data.
+ text1A, text1B, text2A, text2B;
+ if (text1.length > text2.length) {
+ text1A = hm[0];
+ text1B = hm[1];
+ text2A = hm[2];
+ text2B = hm[3];
+ } else {
+ text2A = hm[0];
+ text2B = hm[1];
+ text1A = hm[2];
+ text1B = hm[3];
+ }
+ midCommon = hm[4];
+ return [ text1A, text1B, text2A, text2B, midCommon ];
+ };
+
+ /**
+ * Do a quick line-level diff on both strings, then rediff the parts for
+ * greater accuracy.
+ * This speedup can produce non-minimal diffs.
+ * @param {string} text1 Old string to be diffed.
+ * @param {string} text2 New string to be diffed.
+ * @param {number} deadline Time when the diff should be complete by.
+ * @return {!Array.<!DiffMatchPatch.Diff>} Array of diff tuples.
+ * @private
+ */
+ DiffMatchPatch.prototype.diffLineMode = function(text1, text2, deadline) {
+ var a, diffs, linearray, pointer, countInsert,
+ countDelete, textInsert, textDelete, j;
+ // Scan the text on a line-by-line basis first.
+ a = this.diffLinesToChars(text1, text2);
+ text1 = a.chars1;
+ text2 = a.chars2;
+ linearray = a.lineArray;
+
+ diffs = this.DiffMain(text1, text2, false, deadline);
+
+ // Convert the diff back to original text.
+ this.diffCharsToLines(diffs, linearray);
+ // Eliminate freak matches (e.g. blank lines)
+ this.diffCleanupSemantic(diffs);
+
+ // Rediff any replacement blocks, this time character-by-character.
+ // Add a dummy entry at the end.
+ diffs.push( [ DIFF_EQUAL, "" ] );
+ pointer = 0;
+ countDelete = 0;
+ countInsert = 0;
+ textDelete = "";
+ textInsert = "";
+ while (pointer < diffs.length) {
+ switch ( diffs[pointer][0] ) {
+ case DIFF_INSERT:
+ countInsert++;
+ textInsert += diffs[pointer][1];
+ break;
+ case DIFF_DELETE:
+ countDelete++;
+ textDelete += diffs[pointer][1];
+ break;
+ case DIFF_EQUAL:
+ // Upon reaching an equality, check for prior redundancies.
+ if (countDelete >= 1 && countInsert >= 1) {
+ // Delete the offending records and add the merged ones.
+ diffs.splice(pointer - countDelete - countInsert,
+ countDelete + countInsert);
+ pointer = pointer - countDelete - countInsert;
+ a = this.DiffMain(textDelete, textInsert, false, deadline);
+ for (j = a.length - 1; j >= 0; j--) {
+ diffs.splice( pointer, 0, a[j] );
+ }
+ pointer = pointer + a.length;
+ }
+ countInsert = 0;
+ countDelete = 0;
+ textDelete = "";
+ textInsert = "";
+ break;
+ }
+ pointer++;
+ }
+ diffs.pop(); // Remove the dummy entry at the end.
+
+ return diffs;
+ };
+
+ /**
+ * Find the 'middle snake' of a diff, split the problem in two
+ * and return the recursively constructed diff.
+ * See Myers 1986 paper: An O(ND) Difference Algorithm and Its Variations.
+ * @param {string} text1 Old string to be diffed.
+ * @param {string} text2 New string to be diffed.
+ * @param {number} deadline Time at which to bail if not yet complete.
+ * @return {!Array.<!DiffMatchPatch.Diff>} Array of diff tuples.
+ * @private
+ */
+ DiffMatchPatch.prototype.diffBisect = function(text1, text2, deadline) {
+ var text1Length, text2Length, maxD, vOffset, vLength,
+ v1, v2, x, delta, front, k1start, k1end, k2start,
+ k2end, k2Offset, k1Offset, x1, x2, y1, y2, d, k1, k2;
+ // Cache the text lengths to prevent multiple calls.
+ text1Length = text1.length;
+ text2Length = text2.length;
+ maxD = Math.ceil((text1Length + text2Length) / 2);
+ vOffset = maxD;
+ vLength = 2 * maxD;
+ v1 = new Array(vLength);
+ v2 = new Array(vLength);
+ // Setting all elements to -1 is faster in Chrome & Firefox than mixing
+ // integers and undefined.
+ for (x = 0; x < vLength; x++) {
+ v1[x] = -1;
+ v2[x] = -1;
+ }
+ v1[vOffset + 1] = 0;
+ v2[vOffset + 1] = 0;
+ delta = text1Length - text2Length;
+ // If the total number of characters is odd, then the front path will collide
+ // with the reverse path.
+ front = (delta % 2 !== 0);
+ // Offsets for start and end of k loop.
+ // Prevents mapping of space beyond the grid.
+ k1start = 0;
+ k1end = 0;
+ k2start = 0;
+ k2end = 0;
+ for (d = 0; d < maxD; d++) {
+ // Bail out if deadline is reached.
+ if ((new Date()).getTime() > deadline) {
+ break;
+ }
+
+ // Walk the front path one step.
+ for (k1 = -d + k1start; k1 <= d - k1end; k1 += 2) {
+ k1Offset = vOffset + k1;
+ if ( k1 === -d || ( k1 !== d && v1[ k1Offset - 1 ] < v1[ k1Offset + 1 ] ) ) {
+ x1 = v1[k1Offset + 1];
+ } else {
+ x1 = v1[k1Offset - 1] + 1;
+ }
+ y1 = x1 - k1;
+ while (x1 < text1Length && y1 < text2Length &&
+ text1.charAt(x1) === text2.charAt(y1)) {
+ x1++;
+ y1++;
+ }
+ v1[k1Offset] = x1;
+ if (x1 > text1Length) {
+ // Ran off the right of the graph.
+ k1end += 2;
+ } else if (y1 > text2Length) {
+ // Ran off the bottom of the graph.
+ k1start += 2;
+ } else if (front) {
+ k2Offset = vOffset + delta - k1;
+ if (k2Offset >= 0 && k2Offset < vLength && v2[k2Offset] !== -1) {
+ // Mirror x2 onto top-left coordinate system.
+ x2 = text1Length - v2[k2Offset];
+ if (x1 >= x2) {
+ // Overlap detected.
+ return this.diffBisectSplit(text1, text2, x1, y1, deadline);
+ }
+ }
+ }
+ }
+
+ // Walk the reverse path one step.
+ for (k2 = -d + k2start; k2 <= d - k2end; k2 += 2) {
+ k2Offset = vOffset + k2;
+ if ( k2 === -d || (k2 !== d && v2[ k2Offset - 1 ] < v2[ k2Offset + 1 ] ) ) {
+ x2 = v2[k2Offset + 1];
+ } else {
+ x2 = v2[k2Offset - 1] + 1;
+ }
+ y2 = x2 - k2;
+ while (x2 < text1Length && y2 < text2Length &&
+ text1.charAt(text1Length - x2 - 1) ===
+ text2.charAt(text2Length - y2 - 1)) {
+ x2++;
+ y2++;
+ }
+ v2[k2Offset] = x2;
+ if (x2 > text1Length) {
+ // Ran off the left of the graph.
+ k2end += 2;
+ } else if (y2 > text2Length) {
+ // Ran off the top of the graph.
+ k2start += 2;
+ } else if (!front) {
+ k1Offset = vOffset + delta - k2;
+ if (k1Offset >= 0 && k1Offset < vLength && v1[k1Offset] !== -1) {
+ x1 = v1[k1Offset];
+ y1 = vOffset + x1 - k1Offset;
+ // Mirror x2 onto top-left coordinate system.
+ x2 = text1Length - x2;
+ if (x1 >= x2) {
+ // Overlap detected.
+ return this.diffBisectSplit(text1, text2, x1, y1, deadline);
+ }
+ }
+ }
+ }
+ }
+ // Diff took too long and hit the deadline or
+ // number of diffs equals number of characters, no commonality at all.
+ return [
+ [ DIFF_DELETE, text1 ],
+ [ DIFF_INSERT, text2 ]
+ ];
+ };
+
+ /**
+ * Given the location of the 'middle snake', split the diff in two parts
+ * and recurse.
+ * @param {string} text1 Old string to be diffed.
+ * @param {string} text2 New string to be diffed.
+ * @param {number} x Index of split point in text1.
+ * @param {number} y Index of split point in text2.
+ * @param {number} deadline Time at which to bail if not yet complete.
+ * @return {!Array.<!DiffMatchPatch.Diff>} Array of diff tuples.
+ * @private
+ */
+ DiffMatchPatch.prototype.diffBisectSplit = function( text1, text2, x, y, deadline ) {
+ var text1a, text1b, text2a, text2b, diffs, diffsb;
+ text1a = text1.substring(0, x);
+ text2a = text2.substring(0, y);
+ text1b = text1.substring(x);
+ text2b = text2.substring(y);
+
+ // Compute both diffs serially.
+ diffs = this.DiffMain(text1a, text2a, false, deadline);
+ diffsb = this.DiffMain(text1b, text2b, false, deadline);
+
+ return diffs.concat(diffsb);
+ };
+
+ /**
+ * Reduce the number of edits by eliminating semantically trivial equalities.
+ * @param {!Array.<!DiffMatchPatch.Diff>} diffs Array of diff tuples.
+ */
+ DiffMatchPatch.prototype.diffCleanupSemantic = function(diffs) {
+ var changes, equalities, equalitiesLength, lastequality,
+ pointer, lengthInsertions2, lengthDeletions2, lengthInsertions1,
+ lengthDeletions1, deletion, insertion, overlapLength1, overlapLength2;
+ changes = false;
+ equalities = []; // Stack of indices where equalities are found.
+ equalitiesLength = 0; // Keeping our own length var is faster in JS.
+ /** @type {?string} */
+ lastequality = null;
+ // Always equal to diffs[equalities[equalitiesLength - 1]][1]
+ pointer = 0; // Index of current position.
+ // Number of characters that changed prior to the equality.
+ lengthInsertions1 = 0;
+ lengthDeletions1 = 0;
+ // Number of characters that changed after the equality.
+ lengthInsertions2 = 0;
+ lengthDeletions2 = 0;
+ while (pointer < diffs.length) {
+ if (diffs[pointer][0] === DIFF_EQUAL) { // Equality found.
+ equalities[equalitiesLength++] = pointer;
+ lengthInsertions1 = lengthInsertions2;
+ lengthDeletions1 = lengthDeletions2;
+ lengthInsertions2 = 0;
+ lengthDeletions2 = 0;
+ lastequality = diffs[pointer][1];
+ } else { // An insertion or deletion.
+ if (diffs[pointer][0] === DIFF_INSERT) {
+ lengthInsertions2 += diffs[pointer][1].length;
+ } else {
+ lengthDeletions2 += diffs[pointer][1].length;
+ }
+ // Eliminate an equality that is smaller or equal to the edits on both
+ // sides of it.
+ if (lastequality && (lastequality.length <=
+ Math.max(lengthInsertions1, lengthDeletions1)) &&
+ (lastequality.length <= Math.max(lengthInsertions2,
+ lengthDeletions2))) {
+ // Duplicate record.
+ diffs.splice( equalities[ equalitiesLength - 1 ], 0, [ DIFF_DELETE, lastequality ] );
+ // Change second copy to insert.
+ diffs[equalities[equalitiesLength - 1] + 1][0] = DIFF_INSERT;
+ // Throw away the equality we just deleted.
+ equalitiesLength--;
+ // Throw away the previous equality (it needs to be reevaluated).
+ equalitiesLength--;
+ pointer = equalitiesLength > 0 ? equalities[equalitiesLength - 1] : -1;
+ lengthInsertions1 = 0; // Reset the counters.
+ lengthDeletions1 = 0;
+ lengthInsertions2 = 0;
+ lengthDeletions2 = 0;
+ lastequality = null;
+ changes = true;
+ }
+ }
+ pointer++;
+ }
+
+ // Normalize the diff.
+ if (changes) {
+ this.diffCleanupMerge(diffs);
+ }
+
+ // Find any overlaps between deletions and insertions.
+ // e.g: <del>abcxxx</del><ins>xxxdef</ins>
+ // -> <del>abc</del>xxx<ins>def</ins>
+ // e.g: <del>xxxabc</del><ins>defxxx</ins>
+ // -> <ins>def</ins>xxx<del>abc</del>
+ // Only extract an overlap if it is as big as the edit ahead or behind it.
+ pointer = 1;
+ while (pointer < diffs.length) {
+ if (diffs[pointer - 1][0] === DIFF_DELETE &&
+ diffs[pointer][0] === DIFF_INSERT) {
+ deletion = diffs[pointer - 1][1];
+ insertion = diffs[pointer][1];
+ overlapLength1 = this.diffCommonOverlap(deletion, insertion);
+ overlapLength2 = this.diffCommonOverlap(insertion, deletion);
+ if (overlapLength1 >= overlapLength2) {
+ if (overlapLength1 >= deletion.length / 2 ||
+ overlapLength1 >= insertion.length / 2) {
+ // Overlap found. Insert an equality and trim the surrounding edits.
+ diffs.splice( pointer, 0, [ DIFF_EQUAL, insertion.substring( 0, overlapLength1 ) ] );
+ diffs[pointer - 1][1] =
+ deletion.substring(0, deletion.length - overlapLength1);
+ diffs[pointer + 1][1] = insertion.substring(overlapLength1);
+ pointer++;
+ }
+ } else {
+ if (overlapLength2 >= deletion.length / 2 ||
+ overlapLength2 >= insertion.length / 2) {
+ // Reverse overlap found.
+ // Insert an equality and swap and trim the surrounding edits.
+ diffs.splice( pointer, 0, [ DIFF_EQUAL, deletion.substring( 0, overlapLength2 ) ] );
+ diffs[pointer - 1][0] = DIFF_INSERT;
+ diffs[pointer - 1][1] =
+ insertion.substring(0, insertion.length - overlapLength2);
+ diffs[pointer + 1][0] = DIFF_DELETE;
+ diffs[pointer + 1][1] =
+ deletion.substring(overlapLength2);
+ pointer++;
+ }
+ }
+ pointer++;
+ }
+ pointer++;
+ }
+ };
+
+ /**
+ * Determine if the suffix of one string is the prefix of another.
+ * @param {string} text1 First string.
+ * @param {string} text2 Second string.
+ * @return {number} The number of characters common to the end of the first
+ * string and the start of the second string.
+ * @private
+ */
+ DiffMatchPatch.prototype.diffCommonOverlap = function(text1, text2) {
+ var text1Length, text2Length, textLength,
+ best, length, pattern, found;
+ // Cache the text lengths to prevent multiple calls.
+ text1Length = text1.length;
+ text2Length = text2.length;
+ // Eliminate the null case.
+ if (text1Length === 0 || text2Length === 0) {
+ return 0;
+ }
+ // Truncate the longer string.
+ if (text1Length > text2Length) {
+ text1 = text1.substring(text1Length - text2Length);
+ } else if (text1Length < text2Length) {
+ text2 = text2.substring(0, text1Length);
+ }
+ textLength = Math.min(text1Length, text2Length);
+ // Quick check for the worst case.
+ if (text1 === text2) {
+ return textLength;
+ }
+
+ // Start by looking for a single character match
+ // and increase length until no match is found.
+ // Performance analysis: http://neil.fraser.name/news/2010/11/04/
+ best = 0;
+ length = 1;
+ while (true) {
+ pattern = text1.substring(textLength - length);
+ found = text2.indexOf(pattern);
+ if (found === -1) {
+ return best;
+ }
+ length += found;
+ if (found === 0 || text1.substring(textLength - length) ===
+ text2.substring(0, length)) {
+ best = length;
+ length++;
+ }
+ }
+ };
+
+ /**
+ * Split two texts into an array of strings. Reduce the texts to a string of
+ * hashes where each Unicode character represents one line.
+ * @param {string} text1 First string.
+ * @param {string} text2 Second string.
+ * @return {{chars1: string, chars2: string, lineArray: !Array.<string>}}
+ * An object containing the encoded text1, the encoded text2 and
+ * the array of unique strings.
+ * The zeroth element of the array of unique strings is intentionally blank.
+ * @private
+ */
+ DiffMatchPatch.prototype.diffLinesToChars = function(text1, text2) {
+ var lineArray, lineHash, chars1, chars2;
+ lineArray = []; // e.g. lineArray[4] === 'Hello\n'
+ lineHash = {}; // e.g. lineHash['Hello\n'] === 4
+
+ // '\x00' is a valid character, but various debuggers don't like it.
+ // So we'll insert a junk entry to avoid generating a null character.
+ lineArray[0] = "";
+
+ /**
+ * Split a text into an array of strings. Reduce the texts to a string of
+ * hashes where each Unicode character represents one line.
+ * Modifies linearray and linehash through being a closure.
+ * @param {string} text String to encode.
+ * @return {string} Encoded string.
+ * @private
+ */
+ function diffLinesToCharsMunge(text) {
+ var chars, lineStart, lineEnd, lineArrayLength, line;
+ chars = "";
+ // Walk the text, pulling out a substring for each line.
+ // text.split('\n') would would temporarily double our memory footprint.
+ // Modifying text would create many large strings to garbage collect.
+ lineStart = 0;
+ lineEnd = -1;
+ // Keeping our own length variable is faster than looking it up.
+ lineArrayLength = lineArray.length;
+ while (lineEnd < text.length - 1) {
+ lineEnd = text.indexOf("\n", lineStart);
+ if (lineEnd === -1) {
+ lineEnd = text.length - 1;
+ }
+ line = text.substring(lineStart, lineEnd + 1);
+ lineStart = lineEnd + 1;
+
+ if (lineHash.hasOwnProperty ? lineHash.hasOwnProperty(line) :
+ (lineHash[line] !== undefined)) {
+ chars += String.fromCharCode( lineHash[ line ] );
+ } else {
+ chars += String.fromCharCode(lineArrayLength);
+ lineHash[line] = lineArrayLength;
+ lineArray[lineArrayLength++] = line;
+ }
+ }
+ return chars;
+ }
+
+ chars1 = diffLinesToCharsMunge(text1);
+ chars2 = diffLinesToCharsMunge(text2);
+ return {
+ chars1: chars1,
+ chars2: chars2,
+ lineArray: lineArray
+ };
+ };
+
+ /**
+ * Rehydrate the text in a diff from a string of line hashes to real lines of
+ * text.
+ * @param {!Array.<!DiffMatchPatch.Diff>} diffs Array of diff tuples.
+ * @param {!Array.<string>} lineArray Array of unique strings.
+ * @private
+ */
+ DiffMatchPatch.prototype.diffCharsToLines = function( diffs, lineArray ) {
+ var x, chars, text, y;
+ for ( x = 0; x < diffs.length; x++ ) {
+ chars = diffs[x][1];
+ text = [];
+ for ( y = 0; y < chars.length; y++ ) {
+ text[y] = lineArray[chars.charCodeAt(y)];
+ }
+ diffs[x][1] = text.join("");
+ }
+ };
+
+ /**
+ * Reorder and merge like edit sections. Merge equalities.
+ * Any edit section can move as long as it doesn't cross an equality.
+ * @param {!Array.<!DiffMatchPatch.Diff>} diffs Array of diff tuples.
+ */
+ DiffMatchPatch.prototype.diffCleanupMerge = function(diffs) {
+ var pointer, countDelete, countInsert, textInsert, textDelete,
+ commonlength, changes;
+ diffs.push( [ DIFF_EQUAL, "" ] ); // Add a dummy entry at the end.
+ pointer = 0;
+ countDelete = 0;
+ countInsert = 0;
+ textDelete = "";
+ textInsert = "";
+ commonlength;
+ while (pointer < diffs.length) {
+ switch ( diffs[ pointer ][ 0 ] ) {
+ case DIFF_INSERT:
+ countInsert++;
+ textInsert += diffs[pointer][1];
+ pointer++;
+ break;
+ case DIFF_DELETE:
+ countDelete++;
+ textDelete += diffs[pointer][1];
+ pointer++;
+ break;
+ case DIFF_EQUAL:
+ // Upon reaching an equality, check for prior redundancies.
+ if (countDelete + countInsert > 1) {
+ if (countDelete !== 0 && countInsert !== 0) {
+ // Factor out any common prefixies.
+ commonlength = this.diffCommonPrefix(textInsert, textDelete);
+ if (commonlength !== 0) {
+ if ((pointer - countDelete - countInsert) > 0 &&
+ diffs[pointer - countDelete - countInsert - 1][0] ===
+ DIFF_EQUAL) {
+ diffs[pointer - countDelete - countInsert - 1][1] +=
+ textInsert.substring(0, commonlength);
+ } else {
+ diffs.splice( 0, 0, [ DIFF_EQUAL,
+ textInsert.substring( 0, commonlength )
+ ] );
+ pointer++;
+ }
+ textInsert = textInsert.substring(commonlength);
+ textDelete = textDelete.substring(commonlength);
+ }
+ // Factor out any common suffixies.
+ commonlength = this.diffCommonSuffix(textInsert, textDelete);
+ if (commonlength !== 0) {
+ diffs[pointer][1] = textInsert.substring(textInsert.length -
+ commonlength) + diffs[pointer][1];
+ textInsert = textInsert.substring(0, textInsert.length -
+ commonlength);
+ textDelete = textDelete.substring(0, textDelete.length -
+ commonlength);
+ }
+ }
+ // Delete the offending records and add the merged ones.
+ if (countDelete === 0) {
+ diffs.splice( pointer - countInsert,
+ countDelete + countInsert, [ DIFF_INSERT, textInsert ] );
+ } else if (countInsert === 0) {
+ diffs.splice( pointer - countDelete,
+ countDelete + countInsert, [ DIFF_DELETE, textDelete ] );
+ } else {
+ diffs.splice( pointer - countDelete - countInsert,
+ countDelete + countInsert, [ DIFF_DELETE, textDelete ], [ DIFF_INSERT, textInsert ] );
+ }
+ pointer = pointer - countDelete - countInsert +
+ (countDelete ? 1 : 0) + (countInsert ? 1 : 0) + 1;
+ } else if (pointer !== 0 && diffs[pointer - 1][0] === DIFF_EQUAL) {
+ // Merge this equality with the previous one.
+ diffs[pointer - 1][1] += diffs[pointer][1];
+ diffs.splice(pointer, 1);
+ } else {
+ pointer++;
+ }
+ countInsert = 0;
+ countDelete = 0;
+ textDelete = "";
+ textInsert = "";
+ break;
+ }
+ }
+ if (diffs[diffs.length - 1][1] === "") {
+ diffs.pop(); // Remove the dummy entry at the end.
+ }
+
+ // Second pass: look for single edits surrounded on both sides by equalities
+ // which can be shifted sideways to eliminate an equality.
+ // e.g: A<ins>BA</ins>C -> <ins>AB</ins>AC
+ changes = false;
+ pointer = 1;
+ // Intentionally ignore the first and last element (don't need checking).
+ while (pointer < diffs.length - 1) {
+ if (diffs[pointer - 1][0] === DIFF_EQUAL &&
+ diffs[pointer + 1][0] === DIFF_EQUAL) {
+ // This is a single edit surrounded by equalities.
+ if ( diffs[ pointer ][ 1 ].substring( diffs[ pointer ][ 1 ].length -
+ diffs[ pointer - 1 ][ 1 ].length ) === diffs[ pointer - 1 ][ 1 ] ) {
+ // Shift the edit over the previous equality.
+ diffs[pointer][1] = diffs[pointer - 1][1] +
+ diffs[pointer][1].substring(0, diffs[pointer][1].length -
+ diffs[pointer - 1][1].length);
+ diffs[pointer + 1][1] = diffs[pointer - 1][1] + diffs[pointer + 1][1];
+ diffs.splice(pointer - 1, 1);
+ changes = true;
+ } else if ( diffs[ pointer ][ 1 ].substring( 0, diffs[ pointer + 1 ][ 1 ].length ) ===
+ diffs[ pointer + 1 ][ 1 ] ) {
+ // Shift the edit over the next equality.
+ diffs[pointer - 1][1] += diffs[pointer + 1][1];
+ diffs[pointer][1] =
+ diffs[pointer][1].substring(diffs[pointer + 1][1].length) +
+ diffs[pointer + 1][1];
+ diffs.splice(pointer + 1, 1);
+ changes = true;
+ }
+ }
+ pointer++;
+ }
+ // If shifts were made, the diff needs reordering and another shift sweep.
+ if (changes) {
+ this.diffCleanupMerge(diffs);
+ }
+ };
+
+ return function(o, n) {
+ var diff, output, text;
+ diff = new DiffMatchPatch();
+ output = diff.DiffMain(o, n);
+ //console.log(output);
+ diff.diffCleanupEfficiency(output);
+ text = diff.diffPrettyHtml(output);
+
+ return text;
+ };
+}());
+// jscs:enable
+
+(function() {
+
+// Deprecated QUnit.init - Ref #530
+// Re-initialize the configuration options
+QUnit.init = function() {
+ var tests, banner, result, qunit,
+ config = QUnit.config;
+
+ config.stats = { all: 0, bad: 0 };
+ config.moduleStats = { all: 0, bad: 0 };
+ config.started = 0;
+ config.updateRate = 1000;
+ config.blocking = false;
+ config.autostart = true;
+ config.autorun = false;
+ config.filter = "";
+ config.queue = [];
+
+ // Return on non-browser environments
+ // This is necessary to not break on node tests
+ if ( typeof window === "undefined" ) {
+ return;
+ }
+
+ qunit = id( "qunit" );
+ if ( qunit ) {
+ qunit.innerHTML =
+ "<h1 id='qunit-header'>" + escapeText( document.title ) + "</h1>" +
+ "<h2 id='qunit-banner'></h2>" +
+ "<div id='qunit-testrunner-toolbar'></div>" +
+ "<h2 id='qunit-userAgent'></h2>" +
+ "<ol id='qunit-tests'></ol>";
+ }
+
+ tests = id( "qunit-tests" );
+ banner = id( "qunit-banner" );
+ result = id( "qunit-testresult" );
+
+ if ( tests ) {
+ tests.innerHTML = "";
+ }
+
+ if ( banner ) {
+ banner.className = "";
+ }
+
+ if ( result ) {
+ result.parentNode.removeChild( result );
+ }
+
+ if ( tests ) {
+ result = document.createElement( "p" );
+ result.id = "qunit-testresult";
+ result.className = "result";
+ tests.parentNode.insertBefore( result, tests );
+ result.innerHTML = "Running...<br />&#160;";
+ }
+};
+
+// Don't load the HTML Reporter on non-Browser environments
+if ( typeof window === "undefined" ) {
+ return;
+}
+
+var config = QUnit.config,
+ hasOwn = Object.prototype.hasOwnProperty,
+ defined = {
+ document: window.document !== undefined,
+ sessionStorage: (function() {
+ var x = "qunit-test-string";
+ try {
+ sessionStorage.setItem( x, x );
+ sessionStorage.removeItem( x );
+ return true;
+ } catch ( e ) {
+ return false;
+ }
+ }())
+ },
+ modulesList = [];
+
+/**
+* Escape text for attribute or text content.
+*/
+function escapeText( s ) {
+ if ( !s ) {
+ return "";
+ }
+ s = s + "";
+
+ // Both single quotes and double quotes (for attributes)
+ return s.replace( /['"<>&]/g, function( s ) {
+ switch ( s ) {
+ case "'":
+ return "&#039;";
+ case "\"":
+ return "&quot;";
+ case "<":
+ return "&lt;";
+ case ">":
+ return "&gt;";
+ case "&":
+ return "&amp;";
+ }
+ });
+}
+
+/**
+ * @param {HTMLElement} elem
+ * @param {string} type
+ * @param {Function} fn
+ */
+function addEvent( elem, type, fn ) {
+ if ( elem.addEventListener ) {
+
+ // Standards-based browsers
+ elem.addEventListener( type, fn, false );
+ } else if ( elem.attachEvent ) {
+
+ // support: IE <9
+ elem.attachEvent( "on" + type, function() {
+ var event = window.event;
+ if ( !event.target ) {
+ event.target = event.srcElement || document;
+ }
+
+ fn.call( elem, event );
+ });
+ }
+}
+
+/**
+ * @param {Array|NodeList} elems
+ * @param {string} type
+ * @param {Function} fn
+ */
+function addEvents( elems, type, fn ) {
+ var i = elems.length;
+ while ( i-- ) {
+ addEvent( elems[ i ], type, fn );
+ }
+}
+
+function hasClass( elem, name ) {
+ return ( " " + elem.className + " " ).indexOf( " " + name + " " ) >= 0;
+}
+
+function addClass( elem, name ) {
+ if ( !hasClass( elem, name ) ) {
+ elem.className += ( elem.className ? " " : "" ) + name;
+ }
+}
+
+function toggleClass( elem, name ) {
+ if ( hasClass( elem, name ) ) {
+ removeClass( elem, name );
+ } else {
+ addClass( elem, name );
+ }
+}
+
+function removeClass( elem, name ) {
+ var set = " " + elem.className + " ";
+
+ // Class name may appear multiple times
+ while ( set.indexOf( " " + name + " " ) >= 0 ) {
+ set = set.replace( " " + name + " ", " " );
+ }
+
+ // trim for prettiness
+ elem.className = typeof set.trim === "function" ? set.trim() : set.replace( /^\s+|\s+$/g, "" );
+}
+
+function id( name ) {
+ return defined.document && document.getElementById && document.getElementById( name );
+}
+
+function getUrlConfigHtml() {
+ var i, j, val,
+ escaped, escapedTooltip,
+ selection = false,
+ len = config.urlConfig.length,
+ urlConfigHtml = "";
+
+ for ( i = 0; i < len; i++ ) {
+ val = config.urlConfig[ i ];
+ if ( typeof val === "string" ) {
+ val = {
+ id: val,
+ label: val
+ };
+ }
+
+ escaped = escapeText( val.id );
+ escapedTooltip = escapeText( val.tooltip );
+
+ if ( config[ val.id ] === undefined ) {
+ config[ val.id ] = QUnit.urlParams[ val.id ];
+ }
+
+ if ( !val.value || typeof val.value === "string" ) {
+ urlConfigHtml += "<input id='qunit-urlconfig-" + escaped +
+ "' name='" + escaped + "' type='checkbox'" +
+ ( val.value ? " value='" + escapeText( val.value ) + "'" : "" ) +
+ ( config[ val.id ] ? " checked='checked'" : "" ) +
+ " title='" + escapedTooltip + "' /><label for='qunit-urlconfig-" + escaped +
+ "' title='" + escapedTooltip + "'>" + val.label + "</label>";
+ } else {
+ urlConfigHtml += "<label for='qunit-urlconfig-" + escaped +
+ "' title='" + escapedTooltip + "'>" + val.label +
+ ": </label><select id='qunit-urlconfig-" + escaped +
+ "' name='" + escaped + "' title='" + escapedTooltip + "'><option></option>";
+
+ if ( QUnit.is( "array", val.value ) ) {
+ for ( j = 0; j < val.value.length; j++ ) {
+ escaped = escapeText( val.value[ j ] );
+ urlConfigHtml += "<option value='" + escaped + "'" +
+ ( config[ val.id ] === val.value[ j ] ?
+ ( selection = true ) && " selected='selected'" : "" ) +
+ ">" + escaped + "</option>";
+ }
+ } else {
+ for ( j in val.value ) {
+ if ( hasOwn.call( val.value, j ) ) {
+ urlConfigHtml += "<option value='" + escapeText( j ) + "'" +
+ ( config[ val.id ] === j ?
+ ( selection = true ) && " selected='selected'" : "" ) +
+ ">" + escapeText( val.value[ j ] ) + "</option>";
+ }
+ }
+ }
+ if ( config[ val.id ] && !selection ) {
+ escaped = escapeText( config[ val.id ] );
+ urlConfigHtml += "<option value='" + escaped +
+ "' selected='selected' disabled='disabled'>" + escaped + "</option>";
+ }
+ urlConfigHtml += "</select>";
+ }
+ }
+
+ return urlConfigHtml;
+}
+
+// Handle "click" events on toolbar checkboxes and "change" for select menus.
+// Updates the URL with the new state of `config.urlConfig` values.
+function toolbarChanged() {
+ var updatedUrl, value,
+ field = this,
+ params = {};
+
+ // Detect if field is a select menu or a checkbox
+ if ( "selectedIndex" in field ) {
+ value = field.options[ field.selectedIndex ].value || undefined;
+ } else {
+ value = field.checked ? ( field.defaultValue || true ) : undefined;
+ }
+
+ params[ field.name ] = value;
+ updatedUrl = setUrl( params );
+
+ if ( "hidepassed" === field.name && "replaceState" in window.history ) {
+ config[ field.name ] = value || false;
+ if ( value ) {
+ addClass( id( "qunit-tests" ), "hidepass" );
+ } else {
+ removeClass( id( "qunit-tests" ), "hidepass" );
+ }
+
+ // It is not necessary to refresh the whole page
+ window.history.replaceState( null, "", updatedUrl );
+ } else {
+ window.location = updatedUrl;
+ }
+}
+
+function setUrl( params ) {
+ var key,
+ querystring = "?";
+
+ params = QUnit.extend( QUnit.extend( {}, QUnit.urlParams ), params );
+
+ for ( key in params ) {
+ if ( hasOwn.call( params, key ) ) {
+ if ( params[ key ] === undefined ) {
+ continue;
+ }
+ querystring += encodeURIComponent( key );
+ if ( params[ key ] !== true ) {
+ querystring += "=" + encodeURIComponent( params[ key ] );
+ }
+ querystring += "&";
+ }
+ }
+ return location.protocol + "//" + location.host +
+ location.pathname + querystring.slice( 0, -1 );
+}
+
+function applyUrlParams() {
+ var selectedModule,
+ modulesList = id( "qunit-modulefilter" ),
+ filter = id( "qunit-filter-input" ).value;
+
+ selectedModule = modulesList ?
+ decodeURIComponent( modulesList.options[ modulesList.selectedIndex ].value ) :
+ undefined;
+
+ window.location = setUrl({
+ module: ( selectedModule === "" ) ? undefined : selectedModule,
+ filter: ( filter === "" ) ? undefined : filter,
+
+ // Remove testId filter
+ testId: undefined
+ });
+}
+
+function toolbarUrlConfigContainer() {
+ var urlConfigContainer = document.createElement( "span" );
+
+ urlConfigContainer.innerHTML = getUrlConfigHtml();
+ addClass( urlConfigContainer, "qunit-url-config" );
+
+ // For oldIE support:
+ // * Add handlers to the individual elements instead of the container
+ // * Use "click" instead of "change" for checkboxes
+ addEvents( urlConfigContainer.getElementsByTagName( "input" ), "click", toolbarChanged );
+ addEvents( urlConfigContainer.getElementsByTagName( "select" ), "change", toolbarChanged );
+
+ return urlConfigContainer;
+}
+
+function toolbarLooseFilter() {
+ var filter = document.createElement( "form" ),
+ label = document.createElement( "label" ),
+ input = document.createElement( "input" ),
+ button = document.createElement( "button" );
+
+ addClass( filter, "qunit-filter" );
+
+ label.innerHTML = "Filter: ";
+
+ input.type = "text";
+ input.value = config.filter || "";
+ input.name = "filter";
+ input.id = "qunit-filter-input";
+
+ button.innerHTML = "Go";
+
+ label.appendChild( input );
+
+ filter.appendChild( label );
+ filter.appendChild( button );
+ addEvent( filter, "submit", function( ev ) {
+ applyUrlParams();
+
+ if ( ev && ev.preventDefault ) {
+ ev.preventDefault();
+ }
+
+ return false;
+ });
+
+ return filter;
+}
+
+function toolbarModuleFilterHtml() {
+ var i,
+ moduleFilterHtml = "";
+
+ if ( !modulesList.length ) {
+ return false;
+ }
+
+ modulesList.sort(function( a, b ) {
+ return a.localeCompare( b );
+ });
+
+ moduleFilterHtml += "<label for='qunit-modulefilter'>Module: </label>" +
+ "<select id='qunit-modulefilter' name='modulefilter'><option value='' " +
+ ( QUnit.urlParams.module === undefined ? "selected='selected'" : "" ) +
+ ">< All Modules ></option>";
+
+ for ( i = 0; i < modulesList.length; i++ ) {
+ moduleFilterHtml += "<option value='" +
+ escapeText( encodeURIComponent( modulesList[ i ] ) ) + "' " +
+ ( QUnit.urlParams.module === modulesList[ i ] ? "selected='selected'" : "" ) +
+ ">" + escapeText( modulesList[ i ] ) + "</option>";
+ }
+ moduleFilterHtml += "</select>";
+
+ return moduleFilterHtml;
+}
+
+function toolbarModuleFilter() {
+ var toolbar = id( "qunit-testrunner-toolbar" ),
+ moduleFilter = document.createElement( "span" ),
+ moduleFilterHtml = toolbarModuleFilterHtml();
+
+ if ( !toolbar || !moduleFilterHtml ) {
+ return false;
+ }
+
+ moduleFilter.setAttribute( "id", "qunit-modulefilter-container" );
+ moduleFilter.innerHTML = moduleFilterHtml;
+
+ addEvent( moduleFilter.lastChild, "change", applyUrlParams );
+
+ toolbar.appendChild( moduleFilter );
+}
+
+function appendToolbar() {
+ var toolbar = id( "qunit-testrunner-toolbar" );
+
+ if ( toolbar ) {
+ toolbar.appendChild( toolbarUrlConfigContainer() );
+ toolbar.appendChild( toolbarLooseFilter() );
+ }
+}
+
+function appendHeader() {
+ var header = id( "qunit-header" );
+
+ if ( header ) {
+ header.innerHTML = "<a href='" +
+ setUrl({ filter: undefined, module: undefined, testId: undefined }) +
+ "'>" + header.innerHTML + "</a> ";
+ }
+}
+
+function appendBanner() {
+ var banner = id( "qunit-banner" );
+
+ if ( banner ) {
+ banner.className = "";
+ }
+}
+
+function appendTestResults() {
+ var tests = id( "qunit-tests" ),
+ result = id( "qunit-testresult" );
+
+ if ( result ) {
+ result.parentNode.removeChild( result );
+ }
+
+ if ( tests ) {
+ tests.innerHTML = "";
+ result = document.createElement( "p" );
+ result.id = "qunit-testresult";
+ result.className = "result";
+ tests.parentNode.insertBefore( result, tests );
+ result.innerHTML = "Running...<br />&#160;";
+ }
+}
+
+function storeFixture() {
+ var fixture = id( "qunit-fixture" );
+ if ( fixture ) {
+ config.fixture = fixture.innerHTML;
+ }
+}
+
+function appendUserAgent() {
+ var userAgent = id( "qunit-userAgent" );
+
+ if ( userAgent ) {
+ userAgent.innerHTML = "";
+ userAgent.appendChild(
+ document.createTextNode(
+ "QUnit " + QUnit.version + "; " + navigator.userAgent
+ )
+ );
+ }
+}
+
+function appendTestsList( modules ) {
+ var i, l, x, z, test, moduleObj;
+
+ for ( i = 0, l = modules.length; i < l; i++ ) {
+ moduleObj = modules[ i ];
+
+ if ( moduleObj.name ) {
+ modulesList.push( moduleObj.name );
+ }
+
+ for ( x = 0, z = moduleObj.tests.length; x < z; x++ ) {
+ test = moduleObj.tests[ x ];
+
+ appendTest( test.name, test.testId, moduleObj.name );
+ }
+ }
+}
+
+function appendTest( name, testId, moduleName ) {
+ var title, rerunTrigger, testBlock, assertList,
+ tests = id( "qunit-tests" );
+
+ if ( !tests ) {
+ return;
+ }
+
+ title = document.createElement( "strong" );
+ title.innerHTML = getNameHtml( name, moduleName );
+
+ rerunTrigger = document.createElement( "a" );
+ rerunTrigger.innerHTML = "Rerun";
+ rerunTrigger.href = setUrl({ testId: testId });
+
+ testBlock = document.createElement( "li" );
+ testBlock.appendChild( title );
+ testBlock.appendChild( rerunTrigger );
+ testBlock.id = "qunit-test-output-" + testId;
+
+ assertList = document.createElement( "ol" );
+ assertList.className = "qunit-assert-list";
+
+ testBlock.appendChild( assertList );
+
+ tests.appendChild( testBlock );
+}
+
+// HTML Reporter initialization and load
+QUnit.begin(function( details ) {
+ var qunit = id( "qunit" );
+
+ // Fixture is the only one necessary to run without the #qunit element
+ storeFixture();
+
+ if ( qunit ) {
+ qunit.innerHTML =
+ "<h1 id='qunit-header'>" + escapeText( document.title ) + "</h1>" +
+ "<h2 id='qunit-banner'></h2>" +
+ "<div id='qunit-testrunner-toolbar'></div>" +
+ "<h2 id='qunit-userAgent'></h2>" +
+ "<ol id='qunit-tests'></ol>";
+ }
+
+ appendHeader();
+ appendBanner();
+ appendTestResults();
+ appendUserAgent();
+ appendToolbar();
+ appendTestsList( details.modules );
+ toolbarModuleFilter();
+
+ if ( qunit && config.hidepassed ) {
+ addClass( qunit.lastChild, "hidepass" );
+ }
+});
+
+QUnit.done(function( details ) {
+ var i, key,
+ banner = id( "qunit-banner" ),
+ tests = id( "qunit-tests" ),
+ html = [
+ "Tests completed in ",
+ details.runtime,
+ " milliseconds.<br />",
+ "<span class='passed'>",
+ details.passed,
+ "</span> assertions of <span class='total'>",
+ details.total,
+ "</span> passed, <span class='failed'>",
+ details.failed,
+ "</span> failed."
+ ].join( "" );
+
+ if ( banner ) {
+ banner.className = details.failed ? "qunit-fail" : "qunit-pass";
+ }
+
+ if ( tests ) {
+ id( "qunit-testresult" ).innerHTML = html;
+ }
+
+ if ( config.altertitle && defined.document && document.title ) {
+
+ // show ✖ for good, ✔ for bad suite result in title
+ // use escape sequences in case file gets loaded with non-utf-8-charset
+ document.title = [
+ ( details.failed ? "\u2716" : "\u2714" ),
+ document.title.replace( /^[\u2714\u2716] /i, "" )
+ ].join( " " );
+ }
+
+ // clear own sessionStorage items if all tests passed
+ if ( config.reorder && defined.sessionStorage && details.failed === 0 ) {
+ for ( i = 0; i < sessionStorage.length; i++ ) {
+ key = sessionStorage.key( i++ );
+ if ( key.indexOf( "qunit-test-" ) === 0 ) {
+ sessionStorage.removeItem( key );
+ }
+ }
+ }
+
+ // scroll back to top to show results
+ if ( config.scrolltop && window.scrollTo ) {
+ window.scrollTo( 0, 0 );
+ }
+});
+
+function getNameHtml( name, module ) {
+ var nameHtml = "";
+
+ if ( module ) {
+ nameHtml = "<span class='module-name'>" + escapeText( module ) + "</span>: ";
+ }
+
+ nameHtml += "<span class='test-name'>" + escapeText( name ) + "</span>";
+
+ return nameHtml;
+}
+
+QUnit.testStart(function( details ) {
+ var running, testBlock, bad;
+
+ testBlock = id( "qunit-test-output-" + details.testId );
+ if ( testBlock ) {
+ testBlock.className = "running";
+ } else {
+
+ // Report later registered tests
+ appendTest( details.name, details.testId, details.module );
+ }
+
+ running = id( "qunit-testresult" );
+ if ( running ) {
+ bad = QUnit.config.reorder && defined.sessionStorage &&
+ +sessionStorage.getItem( "qunit-test-" + details.module + "-" + details.name );
+
+ running.innerHTML = ( bad ?
+ "Rerunning previously failed test: <br />" :
+ "Running: <br />" ) +
+ getNameHtml( details.name, details.module );
+ }
+
+});
+
+QUnit.log(function( details ) {
+ var assertList, assertLi,
+ message, expected, actual,
+ testItem = id( "qunit-test-output-" + details.testId );
+
+ if ( !testItem ) {
+ return;
+ }
+
+ message = escapeText( details.message ) || ( details.result ? "okay" : "failed" );
+ message = "<span class='test-message'>" + message + "</span>";
+ message += "<span class='runtime'>@ " + details.runtime + " ms</span>";
+
+ // pushFailure doesn't provide details.expected
+ // when it calls, it's implicit to also not show expected and diff stuff
+ // Also, we need to check details.expected existence, as it can exist and be undefined
+ if ( !details.result && hasOwn.call( details, "expected" ) ) {
+ expected = escapeText( QUnit.dump.parse( details.expected ) );
+ actual = escapeText( QUnit.dump.parse( details.actual ) );
+ message += "<table><tr class='test-expected'><th>Expected: </th><td><pre>" +
+ expected +
+ "</pre></td></tr>";
+
+ if ( actual !== expected ) {
+ message += "<tr class='test-actual'><th>Result: </th><td><pre>" +
+ actual + "</pre></td></tr>" +
+ "<tr class='test-diff'><th>Diff: </th><td><pre>" +
+ QUnit.diff( expected, actual ) + "</pre></td></tr>";
+ } else {
+ if ( expected.indexOf( "[object Array]" ) !== -1 ||
+ expected.indexOf( "[object Object]" ) !== -1 ) {
+ message += "<tr class='test-message'><th>Message: </th><td>" +
+ "Diff suppressed as the depth of object is more than current max depth (" +
+ QUnit.config.maxDepth + ").<p>Hint: Use <code>QUnit.dump.maxDepth</code> to " +
+ " run with a higher max depth or <a href='" + setUrl({ maxDepth: -1 }) + "'>" +
+ "Rerun</a> without max depth.</p></td></tr>";
+ }
+ }
+
+ if ( details.source ) {
+ message += "<tr class='test-source'><th>Source: </th><td><pre>" +
+ escapeText( details.source ) + "</pre></td></tr>";
+ }
+
+ message += "</table>";
+
+ // this occours when pushFailure is set and we have an extracted stack trace
+ } else if ( !details.result && details.source ) {
+ message += "<table>" +
+ "<tr class='test-source'><th>Source: </th><td><pre>" +
+ escapeText( details.source ) + "</pre></td></tr>" +
+ "</table>";
+ }
+
+ assertList = testItem.getElementsByTagName( "ol" )[ 0 ];
+
+ assertLi = document.createElement( "li" );
+ assertLi.className = details.result ? "pass" : "fail";
+ assertLi.innerHTML = message;
+ assertList.appendChild( assertLi );
+});
+
+QUnit.testDone(function( details ) {
+ var testTitle, time, testItem, assertList,
+ good, bad, testCounts, skipped,
+ tests = id( "qunit-tests" );
+
+ if ( !tests ) {
+ return;
+ }
+
+ testItem = id( "qunit-test-output-" + details.testId );
+
+ assertList = testItem.getElementsByTagName( "ol" )[ 0 ];
+
+ good = details.passed;
+ bad = details.failed;
+
+ // store result when possible
+ if ( config.reorder && defined.sessionStorage ) {
+ if ( bad ) {
+ sessionStorage.setItem( "qunit-test-" + details.module + "-" + details.name, bad );
+ } else {
+ sessionStorage.removeItem( "qunit-test-" + details.module + "-" + details.name );
+ }
+ }
+
+ if ( bad === 0 ) {
+ addClass( assertList, "qunit-collapsed" );
+ }
+
+ // testItem.firstChild is the test name
+ testTitle = testItem.firstChild;
+
+ testCounts = bad ?
+ "<b class='failed'>" + bad + "</b>, " + "<b class='passed'>" + good + "</b>, " :
+ "";
+
+ testTitle.innerHTML += " <b class='counts'>(" + testCounts +
+ details.assertions.length + ")</b>";
+
+ if ( details.skipped ) {
+ testItem.className = "skipped";
+ skipped = document.createElement( "em" );
+ skipped.className = "qunit-skipped-label";
+ skipped.innerHTML = "skipped";
+ testItem.insertBefore( skipped, testTitle );
+ } else {
+ addEvent( testTitle, "click", function() {
+ toggleClass( assertList, "qunit-collapsed" );
+ });
+
+ testItem.className = bad ? "fail" : "pass";
+
+ time = document.createElement( "span" );
+ time.className = "runtime";
+ time.innerHTML = details.runtime + " ms";
+ testItem.insertBefore( time, assertList );
+ }
+});
+
+if ( defined.document ) {
+ if ( document.readyState === "complete" ) {
+ QUnit.load();
+ } else {
+ addEvent( window, "load", QUnit.load );
+ }
+} else {
+ config.pageLoaded = true;
+ config.autorun = true;
+}
+
+})();
diff --git a/js_tests/tests.html b/js_tests/tests.html
new file mode 100644
index 0000000000..13c6bcd693
--- /dev/null
+++ b/js_tests/tests.html
@@ -0,0 +1,88 @@
+<!DOCTYPE html>
+<html>
+<head>
+ <meta charset="utf-8">
+ <title>Django JavaScript Tests</title>
+ <link rel="stylesheet" href="./qunit/qunit.css">
+</head>
+<body>
+
+ <div id="qunit"></div>
+ <div id="qunit-fixture">
+ </div>
+ <script type="text/html" id="result-table">
+ <table id="result_list">
+ <tr>
+ <th>
+ <input type="checkbox" id="action-toggle">
+ </th>
+ </tr>
+ <tr>
+ <td class="action-checkbox">
+ <input class="action-select" type="checkbox" value="618">
+ </td>
+ </tr>
+ </table>
+ </script>
+ <script type="text/html" id="tabular-formset">
+ <input id="id_first-TOTAL_FORMS" value="1">
+ <input id="id_first-MAX_NUM_FORMS" value="">
+ <table class="inline">
+ <tr id="first-0" class="form-row">
+ <td class="field-test_field">
+ <input id="id_first-test_field">
+ </td>
+ </tr>
+ <tr id="first-empty" class="empty-row">
+ <td class="field-test_field">
+ <input id="id_first-test_field">
+ </td>
+ </tr>
+ </table>
+ </script>
+
+ <script src="./qunit/qunit.js"></script>
+ <script src="./qunit/blanket.min.js" data-cover-flags="branchTracking"></script>
+
+ <script src='../django/contrib/admin/static/admin/js/vendor/jquery/jquery.min.js'></script>
+ <script src='../django/contrib/admin/static/admin/js/jquery.init.js'></script>
+ <script src='./admin/jsi18n-mocks.test.js'></script>
+
+ <script src='../django/contrib/admin/static/admin/js/core.js' data-cover></script>
+ <script src='./admin/core.test.js'></script>
+
+ <script src='../django/contrib/admin/static/admin/js/timeparse.js' data-cover></script>
+ <script src='./admin/timeparse.test.js'></script>
+
+ <script src='../django/contrib/admin/static/admin/js/admin/RelatedObjectLookups.js' data-cover></script>
+ <script src='./admin/RelatedObjectLookups.test.js'></script>
+
+ <script src='./admin/DateTimeShortcuts.test.js'></script>
+ <script src='../django/contrib/admin/static/admin/js/calendar.js' data-cover></script>
+ <script src='../django/contrib/admin/static/admin/js/admin/DateTimeShortcuts.js' data-cover></script>
+
+ <script src='../django/contrib/admin/static/admin/js/actions.js' data-cover></script>
+ <script src='./admin/actions.test.js'></script>
+
+ <script src='../django/contrib/admin/static/admin/js/SelectBox.js' data-cover></script>
+ <script src='./admin/SelectBox.test.js'></script>
+
+ <script src='../django/contrib/admin/static/admin/js/SelectFilter2.js' data-cover></script>
+ <script src='./admin/SelectFilter2.test.js'></script>
+
+ <script src='../django/contrib/admin/static/admin/js/inlines.js' data-cover></script>
+ <script src='./admin/inlines.test.js'></script>
+
+ <script src='../django/contrib/admin/static/admin/js/actions.js' data-cover></script>
+ <script src='../django/contrib/admin/static/admin/js/collapse.js' data-cover></script>
+ <script src='../django/contrib/admin/static/admin/js/prepopulate.js' data-cover></script>
+ <script src='../django/contrib/admin/static/admin/js/urlify.js' data-cover></script>
+
+ <script>
+ if (location.href.match(/(\?|&)gruntReport($|&|=)/)) {
+ blanket.options("reporter", "qunit/grunt-reporter.js");
+ }
+ </script>
+
+</body>
+</html>
diff --git a/package.json b/package.json
index 3370c8c04e..d926bdc5fa 100644
--- a/package.json
+++ b/package.json
@@ -2,9 +2,13 @@
"name": "Django",
"private": true,
"scripts": {
- "pretest": "eslint django/"
+ "pretest": "eslint django/",
+ "test": "grunt test --verbose"
},
"devDependencies": {
- "eslint": "^0.22.1"
+ "eslint": "^0.22.1",
+ "grunt": "^0.4.5",
+ "grunt-blanket-qunit": "^0.2.0",
+ "grunt-cli": "^0.1.13"
}
}